Compare commits
9 Commits
@nhost/das
...
@nhost/apo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0263cc9e92 | ||
|
|
d1ceedef05 | ||
|
|
bdd84dd3ca | ||
|
|
45642322f4 | ||
|
|
d092a7c395 | ||
|
|
e5d3d1a39f | ||
|
|
f88bf2d034 | ||
|
|
49f2e55cb9 | ||
|
|
598b988fc1 |
1
.github/workflows/ci.yaml
vendored
1
.github/workflows/ci.yaml
vendored
@@ -22,6 +22,7 @@ env:
|
||||
NHOST_TEST_DASHBOARD_URL: ${{ vars.NHOST_TEST_DASHBOARD_URL }}
|
||||
NHOST_TEST_WORKSPACE_NAME: ${{ vars.NHOST_TEST_WORKSPACE_NAME }}
|
||||
NHOST_TEST_PROJECT_NAME: ${{ vars.NHOST_TEST_PROJECT_NAME }}
|
||||
NHOST_PRO_TEST_PROJECT_NAME: ${{ vars.NHOST_PRO_TEST_PROJECT_NAME }}
|
||||
NHOST_TEST_USER_EMAIL: ${{ secrets.NHOST_TEST_USER_EMAIL }}
|
||||
NHOST_TEST_USER_PASSWORD: ${{ secrets.NHOST_TEST_USER_PASSWORD }}
|
||||
NHOST_TEST_PROJECT_ADMIN_SECRET: ${{ secrets.NHOST_TEST_PROJECT_ADMIN_SECRET }}
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
// $schema provides code completion hints to IDEs.
|
||||
"$schema": "https://github.com/IBM/audit-ci/raw/main/docs/schema.json",
|
||||
"moderate": true,
|
||||
"allowlist": ["trim-newlines"]
|
||||
"allowlist": ["trim-newlines", "vue-template-compiler"]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
# @nhost/dashboard
|
||||
|
||||
## 1.25.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- d1ceede: feat: add setting to migrate postgres major and/or minor versions
|
||||
- e5d3d1a: fix: allow manually typing column for custom check in database row permissions
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react-apollo@12.0.4
|
||||
- @nhost/nextjs@2.1.18
|
||||
|
||||
## 1.24.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 49f2e55: fix: use service subdomain in service form and service details dialog
|
||||
- 598b988: fix: use current project subdomain in ServiceDetailsDialog component
|
||||
|
||||
## 1.24.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -17,7 +17,7 @@ test.afterAll(async () => {
|
||||
});
|
||||
|
||||
test('should be able to create then delete a personal access token', async () => {
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForTimeout(1000);
|
||||
await page.getByRole('banner').getByRole('button').last().click();
|
||||
await page.getByRole('link', { name: /account settings/i }).click();
|
||||
await page
|
||||
|
||||
60
dashboard/e2e/ai/assistants.test.ts
Normal file
60
dashboard/e2e/ai/assistants.test.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import {
|
||||
PRO_TEST_PROJECT_NAME,
|
||||
PRO_TEST_PROJECT_SLUG,
|
||||
TEST_WORKSPACE_SLUG,
|
||||
} from '@/e2e/env';
|
||||
import { openProject } from '@/e2e/utils';
|
||||
import type { Page } from '@playwright/test';
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
let page: Page;
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
page = await browser.newPage();
|
||||
});
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await page.goto('/');
|
||||
|
||||
await openProject({
|
||||
page,
|
||||
projectName: PRO_TEST_PROJECT_NAME,
|
||||
workspaceSlug: TEST_WORKSPACE_SLUG,
|
||||
projectSlug: PRO_TEST_PROJECT_SLUG,
|
||||
});
|
||||
|
||||
await page
|
||||
.getByRole('navigation', { name: /main navigation/i })
|
||||
.getByRole('link', { name: /ai/i })
|
||||
.click();
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
await page.close();
|
||||
});
|
||||
|
||||
test('should create and delete an Assistant', async () => {
|
||||
await page.getByRole('link', { name: 'Assistants' }).click();
|
||||
|
||||
await expect(page.getByText(/no assistants are configured/i)).toBeVisible();
|
||||
|
||||
await page.getByRole('button', { name: 'Create a new assistant' }).click();
|
||||
await page.getByLabel('Name').fill('test');
|
||||
await page.getByLabel('Description').fill('test');
|
||||
await page.getByLabel('Instructions').fill('test');
|
||||
await page.getByLabel('Model').fill('gpt-3.5-turbo-1106');
|
||||
|
||||
await page.getByRole('button', { name: 'Create' }).click();
|
||||
|
||||
await expect(page.getByRole('heading', { name: /test/i })).toBeVisible();
|
||||
|
||||
await page.getByLabel(/more options/i).click();
|
||||
await page.getByRole('menuitem', { name: /delete test/i }).click();
|
||||
|
||||
await page.getByLabel('Confirm Delete Assistant').check();
|
||||
await page.getByRole('button', { name: 'Delete Assistant' }).click();
|
||||
|
||||
await expect(
|
||||
page.getByRole('heading', { name: /no assistants are configured/i }),
|
||||
).toBeVisible();
|
||||
});
|
||||
55
dashboard/e2e/ai/auto-embeddings.test.ts
Normal file
55
dashboard/e2e/ai/auto-embeddings.test.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import {
|
||||
PRO_TEST_PROJECT_NAME,
|
||||
PRO_TEST_PROJECT_SLUG,
|
||||
TEST_WORKSPACE_SLUG,
|
||||
} from '@/e2e/env';
|
||||
import { openProject } from '@/e2e/utils';
|
||||
import type { Page } from '@playwright/test';
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
let page: Page;
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
page = await browser.newPage();
|
||||
});
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await page.goto('/');
|
||||
|
||||
await openProject({
|
||||
page,
|
||||
projectName: PRO_TEST_PROJECT_NAME,
|
||||
workspaceSlug: TEST_WORKSPACE_SLUG,
|
||||
projectSlug: PRO_TEST_PROJECT_SLUG,
|
||||
});
|
||||
|
||||
await page
|
||||
.getByRole('navigation', { name: /main navigation/i })
|
||||
.getByRole('link', { name: /ai/i })
|
||||
.click();
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
await page.close();
|
||||
});
|
||||
|
||||
test('should create and delete an Auto-Embeddings', async () => {
|
||||
await page.getByRole('button', { name: 'Add a new Auto-Embeddings' }).click();
|
||||
|
||||
await page.getByLabel('Name').fill('test');
|
||||
await page.getByLabel('Schema').fill('auth');
|
||||
await page.getByLabel('Table').fill('users');
|
||||
await page.getByLabel('Column').fill('email');
|
||||
|
||||
await page.getByRole('button', { name: 'Create' }).click();
|
||||
await expect(page.getByRole('heading', { name: /test/i })).toBeVisible();
|
||||
|
||||
await page.getByLabel(/more options/i).click();
|
||||
await page.getByRole('menuitem', { name: /delete test/i }).click();
|
||||
|
||||
await page.getByLabel('Confirm Delete Auto-').check();
|
||||
await page.getByRole('button', { name: 'Delete Auto-Embeddings' }).click();
|
||||
await expect(
|
||||
page.getByRole('heading', { name: /No Auto-Embeddings are configured/i }),
|
||||
).toBeVisible();
|
||||
});
|
||||
@@ -23,6 +23,11 @@ export const TEST_WORKSPACE_SLUG = slugify(TEST_WORKSPACE_NAME, {
|
||||
*/
|
||||
export const TEST_PROJECT_NAME = process.env.NHOST_TEST_PROJECT_NAME;
|
||||
|
||||
/**
|
||||
* Name of the pro test project to test against.
|
||||
*/
|
||||
export const PRO_TEST_PROJECT_NAME = process.env.NHOST_PRO_TEST_PROJECT_NAME;
|
||||
|
||||
/**
|
||||
* Slugified name of the project to test against.
|
||||
*/
|
||||
@@ -31,6 +36,14 @@ export const TEST_PROJECT_SLUG = slugify(TEST_PROJECT_NAME, {
|
||||
strict: true,
|
||||
});
|
||||
|
||||
/**
|
||||
* Slugified name of the pro project to test against.
|
||||
*/
|
||||
export const PRO_TEST_PROJECT_SLUG = slugify(PRO_TEST_PROJECT_NAME, {
|
||||
lower: true,
|
||||
strict: true,
|
||||
});
|
||||
|
||||
/**
|
||||
* Hasura admin secret of the test project to use.
|
||||
*/
|
||||
|
||||
95
dashboard/e2e/run/run.test.ts
Normal file
95
dashboard/e2e/run/run.test.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import {
|
||||
PRO_TEST_PROJECT_NAME,
|
||||
PRO_TEST_PROJECT_SLUG,
|
||||
TEST_WORKSPACE_SLUG,
|
||||
} from '@/e2e/env';
|
||||
import { openProject } from '@/e2e/utils';
|
||||
import type { Page } from '@playwright/test';
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
let page: Page;
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
page = await browser.newPage();
|
||||
});
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await page.goto('/');
|
||||
|
||||
await openProject({
|
||||
page,
|
||||
projectName: PRO_TEST_PROJECT_NAME,
|
||||
workspaceSlug: TEST_WORKSPACE_SLUG,
|
||||
projectSlug: PRO_TEST_PROJECT_SLUG,
|
||||
});
|
||||
|
||||
await page
|
||||
.getByRole('navigation', { name: /main navigation/i })
|
||||
.getByRole('link', { name: /run/i })
|
||||
.click();
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
await page.close();
|
||||
});
|
||||
|
||||
test('should create and delete a run service', async () => {
|
||||
await page.getByRole('button', { name: 'Add service' }).first().click();
|
||||
await expect(page.getByText(/create a new service/i)).toBeVisible();
|
||||
await page.getByPlaceholder(/service name/i).click();
|
||||
await page.getByPlaceholder(/service name/i).fill('test');
|
||||
|
||||
const sliderRail = page.locator(
|
||||
'.space-y-4 > .MuiSlider-root > .MuiSlider-rail',
|
||||
);
|
||||
|
||||
// Get the bounding box of the slider rail to determine where to click
|
||||
const box = await sliderRail.boundingBox();
|
||||
|
||||
if (box) {
|
||||
// Calculate the position to click (start of the rail)
|
||||
const x = box.x + 1; // A little offset to ensure click inside the rail
|
||||
const y = box.y + box.height / 2; // Middle of the rail height-wise
|
||||
|
||||
// Perform the click
|
||||
await page.mouse.click(x, y);
|
||||
}
|
||||
|
||||
await page.getByRole('button', { name: /create/i }).click();
|
||||
|
||||
await expect(
|
||||
page.getByRole('heading', { name: /confirm resources/i }),
|
||||
).toBeVisible();
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await page.getByRole('button', { name: /confirm/i }).click();
|
||||
|
||||
await expect(page.getByRole('dialog')).toBeVisible();
|
||||
|
||||
await expect(
|
||||
page.getByRole('heading', { name: /service details/i }),
|
||||
).toBeVisible();
|
||||
|
||||
await page.getByRole('button', { name: /ok/i }).click();
|
||||
|
||||
await expect(page.getByRole('heading', { name: /test/i })).toBeVisible();
|
||||
await page.getByLabel(/more options/i).click();
|
||||
await page.getByRole('menuitem', { name: /delete service/i }).click();
|
||||
|
||||
await page.getByLabel(/confirm delete project #/i).check();
|
||||
await page
|
||||
.getByText(/delete service/i)
|
||||
.nth(2)
|
||||
.click();
|
||||
|
||||
await page.getByLabel('Close').click();
|
||||
|
||||
await expect(
|
||||
page
|
||||
.getByRole('main')
|
||||
.locator('div')
|
||||
.filter({ hasText: 'No custom services are' })
|
||||
.nth(2),
|
||||
).toBeVisible();
|
||||
});
|
||||
@@ -44,7 +44,7 @@ async function globalTeardown() {
|
||||
|
||||
// note: getByRole doesn't work here
|
||||
await hasuraPage.locator('a', { hasText: /data/i }).click();
|
||||
await hasuraPage.getByRole('link', { name: /sql/i }).click();
|
||||
await hasuraPage.locator('[data-test="sql-link"]').click();
|
||||
|
||||
// Set the value of the Ace code editor using JavaScript evaluation in the browser context
|
||||
await hasuraPage.evaluate(() => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/dashboard",
|
||||
"version": "1.24.0",
|
||||
"version": "1.25.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
|
||||
@@ -38,9 +38,12 @@ export default function Header({ className, ...props }: HeaderProps) {
|
||||
const isProjectUpdating =
|
||||
currentProject?.appStates[0]?.stateId === ApplicationStatus.Updating;
|
||||
|
||||
const isProjectMigratingDatabase =
|
||||
currentProject?.appStates[0]?.stateId === ApplicationStatus.Migrating;
|
||||
|
||||
// Poll for project updates
|
||||
useEffect(() => {
|
||||
if (!isProjectUpdating) {
|
||||
if (!isProjectUpdating && !isProjectMigratingDatabase) {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
@@ -51,7 +54,7 @@ export default function Header({ className, ...props }: HeaderProps) {
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [isProjectUpdating, refetchProject]);
|
||||
}, [isProjectUpdating, isProjectMigratingDatabase, refetchProject]);
|
||||
|
||||
const openDevAssistant = () => {
|
||||
// The dev assistant can be only answer questions related to a particular project
|
||||
@@ -92,6 +95,13 @@ export default function Header({ className, ...props }: HeaderProps) {
|
||||
{isProjectUpdating && (
|
||||
<Chip size="small" label="Updating" color="warning" />
|
||||
)}
|
||||
{isProjectMigratingDatabase && (
|
||||
<Chip
|
||||
size="small"
|
||||
label="Upgrading Postgres version"
|
||||
color="warning"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="hidden grid-flow-col items-center gap-2 sm:grid">
|
||||
|
||||
@@ -63,6 +63,10 @@ export interface SettingsContainerProps
|
||||
* @default false
|
||||
*/
|
||||
showSwitch?: boolean;
|
||||
/**
|
||||
* Custom element to be rendered at the top-right corner of the section.
|
||||
*/
|
||||
topRightElement?: ReactNode;
|
||||
/**
|
||||
* Custom class names passed to the root element.
|
||||
*/
|
||||
@@ -108,6 +112,7 @@ export default function SettingsContainer({
|
||||
showSwitch = false,
|
||||
rootClassName,
|
||||
docsTitle,
|
||||
topRightElement,
|
||||
slotProps: { root, switch: switchSlot, submitButton, footer } = {},
|
||||
}: SettingsContainerProps) {
|
||||
return (
|
||||
@@ -137,6 +142,7 @@ export default function SettingsContainer({
|
||||
{description && <Text color="secondary">{description}</Text>}
|
||||
</div>
|
||||
</div>
|
||||
{topRightElement}
|
||||
{!switchId && showSwitch && (
|
||||
<Switch
|
||||
checked={enabled}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { IconProps } from '@/components/ui/v2/icons';
|
||||
import { SvgIcon } from '@/components/ui/v2/icons/SvgIcon';
|
||||
|
||||
function RepeatIcon(props: IconProps) {
|
||||
return (
|
||||
<SvgIcon
|
||||
aria-label="Repeat"
|
||||
width="16"
|
||||
height="20"
|
||||
viewBox="0 0 16 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M11.4062 11.9779H15.9998L13.7035 8L11.4062 11.9779Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M13.1959 16.2243C13.1959 17.466 11.9655 18.4759 10.4525 18.4759L4.05328 18.4749C2.54037 18.4749 1.30989 17.2444 1.30989 15.7315V4.26843C1.30989 2.75552 2.54034 1.52504 4.05328 1.52504H10.4525C11.9654 1.52504 13.1959 2.535 13.1959 3.77661V6.53613C13.1959 6.81655 13.4235 7.04415 13.7039 7.04415C13.9844 7.04415 14.212 6.81655 14.212 6.53613V3.77557C14.212 1.97409 12.5253 0.508057 10.4526 0.508057L4.05333 0.509073C1.98056 0.509073 0.293945 2.19574 0.293945 4.26846V15.7326C0.293945 17.8054 1.98061 19.492 4.05333 19.492H10.4526C12.5253 19.492 14.212 18.0258 14.212 16.2245L14.212 13.5H13.1959L13.1959 16.2243Z"
|
||||
fill="currentColor"
|
||||
stroke="currentColor"
|
||||
strokeWidth="0.5"
|
||||
/>
|
||||
</SvgIcon>
|
||||
);
|
||||
}
|
||||
|
||||
RepeatIcon.displayName = 'NhostRepeatIcon';
|
||||
|
||||
export default RepeatIcon;
|
||||
1
dashboard/src/components/ui/v2/icons/RepeatIcon/index.ts
Normal file
1
dashboard/src/components/ui/v2/icons/RepeatIcon/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as RepeatIcon } from './RepeatIcon';
|
||||
@@ -0,0 +1 @@
|
||||
export { default as useEstimatedDatabaseMigrationDowntime } from './useEstimatedDatabaseMigrationDowntime';
|
||||
@@ -0,0 +1,93 @@
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import {
|
||||
useGetApplicationBackupsQuery,
|
||||
type GetApplicationBackupsQuery,
|
||||
type GetApplicationBackupsQueryVariables,
|
||||
} from '@/utils/__generated__/graphql';
|
||||
import type { QueryHookOptions } from '@apollo/client';
|
||||
|
||||
interface TimePeriod {
|
||||
value: number;
|
||||
unit: 'hours' | 'minutes';
|
||||
downtime: string;
|
||||
downtimeShort: string;
|
||||
}
|
||||
|
||||
export interface UseEstimatedDatabaseMigrationDowntimeOptions
|
||||
extends QueryHookOptions<
|
||||
GetApplicationBackupsQuery,
|
||||
GetApplicationBackupsQueryVariables
|
||||
> {}
|
||||
|
||||
const DEFAULT_ESTIMATED_DOWNTIME: TimePeriod = {
|
||||
value: 10,
|
||||
unit: 'minutes',
|
||||
downtime: '10 minutes',
|
||||
downtimeShort: '10min',
|
||||
};
|
||||
|
||||
function getEstimatedTime(diff: number): TimePeriod {
|
||||
if (diff > 1000 * 3600) {
|
||||
const value = Math.floor(diff / (1000 * 3600));
|
||||
const unitStr = value === 1 ? 'hour' : 'hours';
|
||||
return {
|
||||
value,
|
||||
unit: 'hours',
|
||||
downtime: `${value} ${unitStr}`,
|
||||
downtimeShort: `${value}hr`,
|
||||
};
|
||||
}
|
||||
// 10 minutes is the minimum estimated downtime
|
||||
if (diff > 1000 * 60 * 10) {
|
||||
const value = Math.floor(diff / (1000 * 60));
|
||||
const unitStr = value === 1 ? 'minute' : 'minutes';
|
||||
return {
|
||||
value,
|
||||
unit: 'minutes',
|
||||
downtime: `${value} ${unitStr}`,
|
||||
downtimeShort: `${value}min`,
|
||||
};
|
||||
}
|
||||
|
||||
return DEFAULT_ESTIMATED_DOWNTIME;
|
||||
}
|
||||
|
||||
/*
|
||||
* This hook returns the estimated downtime for a database migration.
|
||||
* The estimated downtime is calculated based on the time taken to complete the last backup.
|
||||
* If there are no backups, the estimated downtime is set to 10 minutes.
|
||||
*/
|
||||
|
||||
export default function useEstimatedDatabaseMigrationDowntime(
|
||||
options: UseEstimatedDatabaseMigrationDowntimeOptions = {},
|
||||
): TimePeriod {
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
|
||||
const isPlanFree = currentProject?.plan?.isFree;
|
||||
|
||||
const { data, loading, error } = useGetApplicationBackupsQuery({
|
||||
...options,
|
||||
variables: { ...options.variables, appId: currentProject?.id },
|
||||
skip: isPlanFree,
|
||||
});
|
||||
|
||||
if (loading || error) {
|
||||
return DEFAULT_ESTIMATED_DOWNTIME;
|
||||
}
|
||||
|
||||
const backups = data?.app?.backups;
|
||||
|
||||
let estimatedMilliseconds = 1000 * 60 * 10; // DEFAULT ESTIMATED DOWNTIME is 10 minutes
|
||||
|
||||
if (!isPlanFree && backups?.length > 0) {
|
||||
const lastBackup = backups[0];
|
||||
const createdAt = new Date(lastBackup.createdAt);
|
||||
const completedAt = new Date(lastBackup.completedAt);
|
||||
const diff = completedAt.valueOf() - createdAt.valueOf();
|
||||
estimatedMilliseconds = diff * 2;
|
||||
}
|
||||
|
||||
const estimated = getEstimatedTime(estimatedMilliseconds);
|
||||
|
||||
return estimated;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as useGetPostgresVersion } from './useGetPostgresVersion';
|
||||
@@ -0,0 +1,37 @@
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { useGetPostgresSettingsQuery } from '@/utils/__generated__/graphql';
|
||||
|
||||
/**
|
||||
* Queries the postgres version of the current project.
|
||||
* @returns Major, minor and full version of the postgres database. Loading and error states.
|
||||
*/
|
||||
export default function useGetPostgresVersion() {
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const isPlatform = useIsPlatform();
|
||||
|
||||
const {
|
||||
data: postgresSettingsData,
|
||||
loading,
|
||||
error,
|
||||
} = useGetPostgresSettingsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { version } = postgresSettingsData?.config?.postgres || {};
|
||||
const [postgresMajor, postgresMinor] = version?.split('.') || [
|
||||
undefined,
|
||||
undefined,
|
||||
];
|
||||
|
||||
return {
|
||||
version,
|
||||
postgresMajor,
|
||||
postgresMinor,
|
||||
loading,
|
||||
error,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as useIsDatabaseMigrating } from './useIsDatabaseMigrating';
|
||||
@@ -0,0 +1,98 @@
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import {
|
||||
useGetApplicationStateQuery,
|
||||
type GetApplicationStateQuery,
|
||||
type GetApplicationStateQueryVariables,
|
||||
} from '@/generated/graphql';
|
||||
import { ApplicationStatus } from '@/types/application';
|
||||
import type { QueryHookOptions } from '@apollo/client';
|
||||
import { useVisibilityChange } from '@uidotdev/usehooks';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export interface UseIsDatabaseMigratingOptions
|
||||
extends QueryHookOptions<
|
||||
GetApplicationStateQuery,
|
||||
GetApplicationStateQueryVariables
|
||||
> {
|
||||
shouldPoll?: boolean;
|
||||
}
|
||||
|
||||
/*
|
||||
* This hook returns information about the current state of database migration.
|
||||
* @param options - Options for the query.
|
||||
*
|
||||
* @returns - An object with two properties:
|
||||
* - isMigrating: true if the database is currently migrating.
|
||||
* - shouldShowUpgradeLogs: true if the database is currently migrating or the application is not live after a migration.
|
||||
*/
|
||||
export default function useIsDatabaseMigrating(
|
||||
options: UseIsDatabaseMigratingOptions = {},
|
||||
): {
|
||||
isMigrating: boolean;
|
||||
shouldShowUpgradeLogs: boolean;
|
||||
} {
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
|
||||
const isVisible = useVisibilityChange();
|
||||
|
||||
const {
|
||||
data: appStatesData,
|
||||
startPolling,
|
||||
stopPolling,
|
||||
} = useGetApplicationStateQuery({
|
||||
...options,
|
||||
variables: { ...options.variables, appId: currentProject?.id },
|
||||
skip: !currentProject,
|
||||
skipPollAttempt: () => !isVisible,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (options.shouldPoll) {
|
||||
startPolling(options.pollInterval || 5000);
|
||||
}
|
||||
|
||||
return () => stopPolling();
|
||||
}, [stopPolling, startPolling, options.shouldPoll, options.pollInterval]);
|
||||
|
||||
// Return true if the application is migrating or if the application is not live after a migration
|
||||
const shouldShowUpgradeLogs = (
|
||||
appStates: GetApplicationStateQuery['app']['appStates'],
|
||||
) => {
|
||||
for (let i = 0; i < appStates.length; i += 1) {
|
||||
if (appStates[i].stateId === ApplicationStatus.Live) {
|
||||
return false;
|
||||
}
|
||||
if (appStates[i].stateId === ApplicationStatus.Migrating) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// Return true if the application is currently migrating
|
||||
const isMigrating = (
|
||||
appStates: GetApplicationStateQuery['app']['appStates'],
|
||||
) => {
|
||||
for (let i = 0; i < appStates.length; i += 1) {
|
||||
if (appStates[i].stateId === ApplicationStatus.Live) {
|
||||
return false;
|
||||
}
|
||||
if (appStates[i].stateId === ApplicationStatus.Errored) {
|
||||
return false;
|
||||
}
|
||||
if (appStates[i].stateId === ApplicationStatus.Migrating) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
return {
|
||||
isMigrating: isMigrating(appStatesData?.app?.appStates || []),
|
||||
shouldShowUpgradeLogs: shouldShowUpgradeLogs(
|
||||
appStatesData?.app?.appStates || [],
|
||||
),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as useMigrationLogs } from './useMigrationLogs';
|
||||
@@ -0,0 +1,107 @@
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import {
|
||||
useGetApplicationStateQuery,
|
||||
useGetSystemLogsQuery,
|
||||
type GetApplicationStateQuery,
|
||||
type GetApplicationStateQueryVariables,
|
||||
type GetSystemLogsQuery,
|
||||
type GetSystemLogsQueryVariables,
|
||||
} from '@/generated/graphql';
|
||||
import { ApplicationStatus } from '@/types/application';
|
||||
import type { ApolloError, QueryHookOptions } from '@apollo/client';
|
||||
import { useVisibilityChange } from '@uidotdev/usehooks';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export interface UseIsDatabaseMigratingOptions
|
||||
extends QueryHookOptions<
|
||||
GetApplicationStateQuery,
|
||||
GetApplicationStateQueryVariables
|
||||
> {
|
||||
shouldPoll?: boolean;
|
||||
}
|
||||
|
||||
export interface UseMigrationLogsOptions
|
||||
extends QueryHookOptions<GetSystemLogsQuery, GetSystemLogsQueryVariables> {
|
||||
shouldPoll?: boolean;
|
||||
}
|
||||
|
||||
export interface Log {
|
||||
level: string;
|
||||
msg: string;
|
||||
time: string;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns logs for the current database migration.
|
||||
* @param options - Options for the getSystemLogs query.
|
||||
* @returns - An object with three properties:
|
||||
* - logs: Logs for the current/latest database migration.
|
||||
* - loading: true if the getLogs query is in a loading state.
|
||||
* - error: Error object if the query failed.
|
||||
*/
|
||||
export default function useMigrationLogs(
|
||||
options: UseMigrationLogsOptions = {},
|
||||
): {
|
||||
logs: Partial<Log>[];
|
||||
loading: boolean;
|
||||
error: ApolloError;
|
||||
} {
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
|
||||
const isVisible = useVisibilityChange();
|
||||
|
||||
const { data: appStatesData } = useGetApplicationStateQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
skip: !currentProject,
|
||||
});
|
||||
|
||||
const migrationStartTimestamp = appStatesData?.app?.appStates?.find(
|
||||
(state) => state.stateId === ApplicationStatus.Migrating,
|
||||
)?.createdAt;
|
||||
|
||||
const from = new Date(migrationStartTimestamp);
|
||||
|
||||
const { data, loading, error, startPolling, stopPolling } =
|
||||
useGetSystemLogsQuery({
|
||||
...options,
|
||||
variables: {
|
||||
...options.variables,
|
||||
appID: currentProject.id,
|
||||
action: 'change-database-version',
|
||||
from,
|
||||
},
|
||||
skip: !currentProject || !from,
|
||||
skipPollAttempt: () => !isVisible,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (options.shouldPoll) {
|
||||
startPolling(options.pollInterval || 5000);
|
||||
}
|
||||
|
||||
return () => stopPolling();
|
||||
}, [stopPolling, startPolling, options.shouldPoll, options.pollInterval]);
|
||||
|
||||
const systemLogs = data?.systemLogs ?? [];
|
||||
const sortedLogs = [...systemLogs];
|
||||
sortedLogs.sort(
|
||||
(a, b) => new Date(a.timestamp).valueOf() - new Date(b.timestamp).valueOf(),
|
||||
); // sort in ascending order
|
||||
|
||||
const logs = sortedLogs.map(({ log }) => {
|
||||
let logObj: Partial<Log> = {};
|
||||
try {
|
||||
logObj = JSON.parse(log);
|
||||
return logObj;
|
||||
} catch (e) {
|
||||
console.error('Failed to parse log', log);
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
logs,
|
||||
loading,
|
||||
error,
|
||||
};
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import { useAutocomplete } from '@mui/base/useAutocomplete';
|
||||
import type { AutocompleteRenderGroupParams } from '@mui/material/Autocomplete';
|
||||
import { autocompleteClasses } from '@mui/material/Autocomplete';
|
||||
import type {
|
||||
ChangeEvent,
|
||||
ForwardedRef,
|
||||
HTMLAttributes,
|
||||
PropsWithoutRef,
|
||||
@@ -209,6 +210,7 @@ function ColumnAutocomplete(
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
const options = useColumnGroups({
|
||||
selectedSchema,
|
||||
selectedTable,
|
||||
@@ -241,6 +243,33 @@ function ColumnAutocomplete(
|
||||
onChange: handleChange,
|
||||
});
|
||||
|
||||
|
||||
function handleInputValueChange(event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) {
|
||||
const {value} = event.target
|
||||
setInputValue(value)
|
||||
|
||||
setSelectedColumn(
|
||||
{
|
||||
value,
|
||||
label: value,
|
||||
metadata: selectedColumn?.metadata || {
|
||||
table_schema: selectedSchema,
|
||||
table_name: selectedTable,
|
||||
}
|
||||
});
|
||||
|
||||
onChange?.(event, {
|
||||
value:
|
||||
selectedRelationships.length > 0
|
||||
? [relationshipDotNotation, value].join('.')
|
||||
: value,
|
||||
columnMetadata: {
|
||||
table_schema: selectedSchema,
|
||||
table_name: selectedTable,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div {...getRootProps()} className={rootClassName}>
|
||||
@@ -293,7 +322,7 @@ function ColumnAutocomplete(
|
||||
helperText={
|
||||
String(tableError || metadataError || '') || props.helperText
|
||||
}
|
||||
onChange={(event) => setInputValue(event.target.value)}
|
||||
onChange={handleInputValueChange}
|
||||
value={inputValue}
|
||||
startAdornment={
|
||||
selectedColumn || relationshipDotNotation ? (
|
||||
@@ -305,7 +334,7 @@ function ColumnAutocomplete(
|
||||
className="!ml-2 flex-shrink-0 truncate lg:max-w-[200px]"
|
||||
>
|
||||
<Text component="span" color="disabled">
|
||||
{defaultTable}
|
||||
{selectedTable}
|
||||
</Text>
|
||||
.
|
||||
{relationshipDotNotation && (
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Alert } from '@/components/ui/v2/Alert';
|
||||
import { XIcon } from '@/components/ui/v2/icons/XIcon';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
|
||||
export default function DatabaseMigrateWarning() {
|
||||
return (
|
||||
<Alert severity="error" className="flex flex-col gap-3 text-left">
|
||||
<Text
|
||||
className="flex items-center gap-1 font-semibold"
|
||||
sx={{
|
||||
color: 'error.main',
|
||||
}}
|
||||
>
|
||||
<XIcon className="h-4 w-4" /> Error: Database version upgrade not
|
||||
possible
|
||||
</Text>
|
||||
<Text
|
||||
sx={{
|
||||
color: 'error.main',
|
||||
}}
|
||||
>
|
||||
Your project isn't currently in a healthy state. Please, review
|
||||
before proceeding with the upgrade.
|
||||
</Text>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as DatabaseMigrateDisabledError } from './DatabaseMigrateDisabledError';
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Alert } from '@/components/ui/v2/Alert';
|
||||
import { Box } from '@/components/ui/v2/Box';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { useEstimatedDatabaseMigrationDowntime } from '@/features/database/common/hooks/useEstimatedDatabaseMigrationDowntime';
|
||||
|
||||
export default function DatabaseMigrateDowntimeWarning() {
|
||||
const { downtimeShort } = useEstimatedDatabaseMigrationDowntime();
|
||||
|
||||
return (
|
||||
<Alert severity="warning" className="flex flex-col gap-3 text-left">
|
||||
<div className="flex flex-col gap-2 lg:flex-row lg:justify-between">
|
||||
<Text className="flex items-start gap-1 font-semibold">
|
||||
<span>⚠</span> Warning: upgrading Postgres major version
|
||||
</Text>
|
||||
<div className="flex">
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: 'beige.main',
|
||||
}}
|
||||
className="py-1/2 flex items-center justify-center text-nowrap rounded-full px-2 font-semibold"
|
||||
>
|
||||
Estimated downtime ~{downtimeShort}
|
||||
</Box>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<Text>
|
||||
Upgrading a major version of Postgres requires downtime. The amount of
|
||||
downtime will depend on your database size, so plan ahead in order to
|
||||
reduce the impact on your users.
|
||||
</Text>
|
||||
<Text>
|
||||
Note that it isn't possible to downgrade between major versions.
|
||||
</Text>
|
||||
</div>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as DatabaseMigrateDowntimeWarning } from './DatabaseMigrateDowntimeWarning';
|
||||
@@ -0,0 +1,112 @@
|
||||
import { Box } from '@/components/ui/v2/Box';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { useMigrationLogs } from '@/features/database/common/hooks/useMigrationLogs';
|
||||
|
||||
export default function DatabaseMigrateLogsModal() {
|
||||
const { logs, loading, error } = useMigrationLogs({
|
||||
shouldPoll: true,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Box className="pt-2">
|
||||
<Box
|
||||
className="min-h-80 p-4"
|
||||
sx={{
|
||||
backgroundColor: (theme) =>
|
||||
theme.palette.mode === 'dark' ? 'grey.300' : 'grey.700',
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
className="font-mono"
|
||||
sx={{
|
||||
color: (theme) =>
|
||||
theme.palette.mode === 'dark' ? 'grey.900' : 'grey.100',
|
||||
}}
|
||||
>
|
||||
Could not fetch logs. Error: {error.message}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Box className="pt-2">
|
||||
<Box
|
||||
className="min-h-80 p-4"
|
||||
sx={{
|
||||
backgroundColor: (theme) =>
|
||||
theme.palette.mode === 'dark' ? 'grey.300' : 'grey.700',
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
className="font-mono"
|
||||
sx={{
|
||||
color: (theme) =>
|
||||
theme.palette.mode === 'dark' ? 'grey.900' : 'grey.100',
|
||||
}}
|
||||
>
|
||||
Loading...
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (logs.length === 0) {
|
||||
return (
|
||||
<Box className="pt-2">
|
||||
<Box
|
||||
className="min-h-80 p-4"
|
||||
sx={{
|
||||
backgroundColor: (theme) =>
|
||||
theme.palette.mode === 'dark' ? 'grey.300' : 'grey.700',
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
className="font-mono"
|
||||
sx={{
|
||||
color: (theme) =>
|
||||
theme.palette.mode === 'dark' ? 'grey.900' : 'grey.100',
|
||||
}}
|
||||
>
|
||||
No logs found
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box className="pt-2">
|
||||
<Box
|
||||
className="min-h-80 p-4"
|
||||
sx={{
|
||||
backgroundColor: (theme) =>
|
||||
theme.palette.mode === 'dark' ? 'grey.300' : 'grey.700',
|
||||
}}
|
||||
>
|
||||
{logs.map((logObj) => {
|
||||
if (logObj?.level && logObj?.msg) {
|
||||
return (
|
||||
<Text
|
||||
key={`${logObj.msg}${logObj.time}`}
|
||||
className="font-mono"
|
||||
sx={{
|
||||
color: (theme) =>
|
||||
theme.palette.mode === 'dark' ? 'grey.900' : 'grey.100',
|
||||
}}
|
||||
>
|
||||
{logObj.level.toUpperCase()}: {logObj.msg}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
})}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as DatabaseMigrateLogsModal } from './DatabaseMigrateLogsModal';
|
||||
@@ -0,0 +1,121 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { Box } from '@/components/ui/v2/Box';
|
||||
import { Button } from '@/components/ui/v2/Button';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { useEstimatedDatabaseMigrationDowntime } from '@/features/database/common/hooks/useEstimatedDatabaseMigrationDowntime';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import {
|
||||
GetPostgresSettingsDocument,
|
||||
GetWorkspaceAndProjectDocument,
|
||||
useUpdateDatabaseVersionMutation,
|
||||
} from '@/utils/__generated__/graphql';
|
||||
import { useState } from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export interface DatabaseMigrateVersionConfirmationDialogProps {
|
||||
/**
|
||||
* Function to be called when the user clicks the cancel button.
|
||||
*/
|
||||
onCancel: () => void;
|
||||
/**
|
||||
* Function to be called when the user clicks the proceed button.
|
||||
*/
|
||||
onProceed: () => void;
|
||||
/**
|
||||
* New version to migrate to.
|
||||
*/
|
||||
postgresVersion: string;
|
||||
}
|
||||
|
||||
export default function DatabaseMigrateVersionConfirmationDialog({
|
||||
onCancel,
|
||||
onProceed,
|
||||
postgresVersion,
|
||||
}: DatabaseMigrateVersionConfirmationDialogProps) {
|
||||
const isPlatform = useIsPlatform();
|
||||
const { openDialog, closeDialog } = useDialog();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updatePostgresMajor] = useUpdateDatabaseVersionMutation({
|
||||
refetchQueries: [
|
||||
GetPostgresSettingsDocument,
|
||||
GetWorkspaceAndProjectDocument,
|
||||
],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { downtime } = useEstimatedDatabaseMigrationDowntime({
|
||||
fetchPolicy: 'cache-only',
|
||||
});
|
||||
|
||||
async function handleClick() {
|
||||
setLoading(true);
|
||||
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updatePostgresMajor({
|
||||
variables: {
|
||||
appId: currentProject.id,
|
||||
version: postgresVersion,
|
||||
},
|
||||
});
|
||||
|
||||
onProceed();
|
||||
closeDialog();
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Updating postgres version...',
|
||||
successMessage: 'Major version upgrade started.',
|
||||
errorMessage:
|
||||
'An error occurred while updating the database version. Please try again later.',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box className={twMerge('w-full rounded-lg p-6 pt-0 text-left')}>
|
||||
<div className="grid grid-flow-row gap-6">
|
||||
<Text>
|
||||
The upgrade process will require an{' '}
|
||||
<span className="font-semibold">
|
||||
estimated {downtime} of downtime
|
||||
</span>
|
||||
. To continue with the upgrade process, click on "Proceed".
|
||||
</Text>
|
||||
|
||||
<div className="grid grid-flow-col gap-4">
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
onClick={() => {
|
||||
onCancel();
|
||||
closeDialog();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleClick} loading={loading}>
|
||||
Proceed
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as DatabaseMigrateVersionConfirmationDialog } from './DatabaseMigrateVersionConfirmationDialog';
|
||||
@@ -5,28 +5,46 @@ import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { Box } from '@/components/ui/v2/Box';
|
||||
import { Button } from '@/components/ui/v2/Button';
|
||||
import { RepeatIcon } from '@/components/ui/v2/icons/RepeatIcon';
|
||||
import { useGetPostgresVersion } from '@/features/database/common/hooks/useGetPostgresVersion';
|
||||
import { useIsDatabaseMigrating } from '@/features/database/common/hooks/useIsDatabaseMigrating';
|
||||
import { DatabaseMigrateDisabledError } from '@/features/database/settings/components/DatabaseMigrateDisabledError';
|
||||
import { DatabaseMigrateDowntimeWarning } from '@/features/database/settings/components/DatabaseMigrateDowntimeWarning';
|
||||
import { DatabaseMigrateLogsModal } from '@/features/database/settings/components/DatabaseMigrateLogsModal';
|
||||
import { DatabaseMigrateVersionConfirmationDialog } from '@/features/database/settings/components/DatabaseMigrateVersionConfirmationDialog';
|
||||
import { DatabaseUpdateInProgressWarning } from '@/features/database/settings/components/DatabaseUpdateInProgressWarning';
|
||||
import { useAppState } from '@/features/projects/common/hooks/useAppState';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import {
|
||||
GetPostgresSettingsDocument,
|
||||
GetWorkspaceAndProjectDocument,
|
||||
Software_Type_Enum,
|
||||
useGetPostgresSettingsQuery,
|
||||
useGetSoftwareVersionsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { ApplicationStatus } from '@/types/application';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const validationSchema = Yup.object({
|
||||
version: Yup.object({
|
||||
majorVersion: Yup.object({
|
||||
label: Yup.string().required(),
|
||||
value: Yup.string().required(),
|
||||
value: Yup.string().required('Major version is a required field'),
|
||||
})
|
||||
.label('Postgres Version')
|
||||
.label('Postgres major version')
|
||||
.required(),
|
||||
minorVersion: Yup.object({
|
||||
label: Yup.string().required(),
|
||||
value: Yup.string().required('Minor version is a required field'),
|
||||
})
|
||||
.label('Postgres minor version')
|
||||
.required(),
|
||||
});
|
||||
|
||||
@@ -34,21 +52,31 @@ export type DatabaseServiceVersionFormValues = Yup.InferType<
|
||||
typeof validationSchema
|
||||
>;
|
||||
|
||||
type DatabaseServiceField = Required<
|
||||
Yup.InferType<typeof validationSchema>['majorVersion']
|
||||
>;
|
||||
|
||||
export default function DatabaseServiceVersionSettings() {
|
||||
const isPlatform = useIsPlatform();
|
||||
const { openDialog } = useDialog();
|
||||
const { openDialog, closeDialog } = useDialog();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetPostgresSettingsDocument],
|
||||
refetchQueries: [
|
||||
GetPostgresSettingsDocument,
|
||||
GetWorkspaceAndProjectDocument,
|
||||
],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetPostgresSettingsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
const {
|
||||
version: postgresVersion,
|
||||
postgresMajor: currentPostgresMajor,
|
||||
postgresMinor: currentPostgresMinor,
|
||||
error: postgresSettingsError,
|
||||
loading: loadingPostgresSettings,
|
||||
} = useGetPostgresVersion();
|
||||
|
||||
const { data: databaseVersionsData } = useGetSoftwareVersionsQuery({
|
||||
variables: {
|
||||
@@ -57,14 +85,11 @@ export default function DatabaseServiceVersionSettings() {
|
||||
skip: !isPlatform,
|
||||
});
|
||||
|
||||
const { version } = data?.config?.postgres || {};
|
||||
|
||||
const databaseVersions = databaseVersionsData?.softwareVersions || [];
|
||||
const availableVersions = Array.from(
|
||||
new Set(databaseVersions.map((el) => el.version)).add(version),
|
||||
new Set(databaseVersions.map((el) => el.version)).add(postgresVersion),
|
||||
)
|
||||
.sort()
|
||||
.reverse()
|
||||
.map((availableVersion) => ({
|
||||
label: availableVersion,
|
||||
value: availableVersion,
|
||||
@@ -72,46 +97,140 @@ export default function DatabaseServiceVersionSettings() {
|
||||
|
||||
const form = useForm<DatabaseServiceVersionFormValues>({
|
||||
reValidateMode: 'onSubmit',
|
||||
defaultValues: { version: { label: '', value: '' } },
|
||||
defaultValues: {
|
||||
minorVersion: { label: '', value: '' },
|
||||
majorVersion: { label: '', value: '' },
|
||||
},
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
const { formState, watch } = form;
|
||||
|
||||
const selectedMajor = watch('majorVersion').value;
|
||||
const selectedMinor = watch('minorVersion').value;
|
||||
|
||||
const getMajorAndMinorVersions = (): {
|
||||
availableMajorVersions: DatabaseServiceField[];
|
||||
majorToMinorVersions: Record<string, DatabaseServiceField[]>;
|
||||
} => {
|
||||
const majorToMinorVersions = {};
|
||||
const availableMajorVersions = [];
|
||||
availableVersions.forEach((availableVersion) => {
|
||||
if (!availableVersion.value) {
|
||||
return;
|
||||
}
|
||||
const [major, minor] = availableVersion.value.split('.');
|
||||
|
||||
// Don't suggest versions that are lower than the current Postgres major version (can't downgrade)
|
||||
if (Number(major) < Number(currentPostgresMajor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (availableMajorVersions.every((item) => item.value !== major)) {
|
||||
availableMajorVersions.push({
|
||||
label: major,
|
||||
value: major,
|
||||
});
|
||||
}
|
||||
|
||||
if (!majorToMinorVersions[major]) {
|
||||
majorToMinorVersions[major] = [];
|
||||
}
|
||||
|
||||
majorToMinorVersions[major].push({
|
||||
label: minor,
|
||||
value: minor,
|
||||
});
|
||||
});
|
||||
return {
|
||||
availableMajorVersions,
|
||||
majorToMinorVersions,
|
||||
};
|
||||
};
|
||||
|
||||
const { availableMajorVersions, majorToMinorVersions } = useMemo(
|
||||
getMajorAndMinorVersions,
|
||||
[availableVersions, currentPostgresMajor],
|
||||
);
|
||||
const availableMinorVersions = majorToMinorVersions[selectedMajor] || [];
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && version) {
|
||||
if (
|
||||
!loadingPostgresSettings &&
|
||||
currentPostgresMajor &&
|
||||
currentPostgresMinor
|
||||
) {
|
||||
form.reset({
|
||||
version: {
|
||||
label: version,
|
||||
value: version,
|
||||
majorVersion: {
|
||||
label: currentPostgresMajor,
|
||||
value: currentPostgresMajor,
|
||||
},
|
||||
minorVersion: {
|
||||
label: currentPostgresMinor,
|
||||
value: currentPostgresMinor,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [loading, version, form]);
|
||||
}, [
|
||||
loadingPostgresSettings,
|
||||
currentPostgresMajor,
|
||||
currentPostgresMinor,
|
||||
form,
|
||||
]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
delay={1000}
|
||||
label="Loading Postgres version..."
|
||||
className="justify-center"
|
||||
/>
|
||||
);
|
||||
}
|
||||
const { isMigrating, shouldShowUpgradeLogs } = useIsDatabaseMigrating({
|
||||
shouldPoll: true,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
const showMigrateWarning =
|
||||
Number(selectedMajor) > Number(currentPostgresMajor);
|
||||
|
||||
const { formState } = form;
|
||||
const { state } = useAppState();
|
||||
const applicationUpdating =
|
||||
state === ApplicationStatus.Updating ||
|
||||
state === ApplicationStatus.Migrating;
|
||||
const applicationUnhealthy =
|
||||
state !== ApplicationStatus.Live && !applicationUpdating;
|
||||
const isMajorVersionDirty = formState?.dirtyFields?.majorVersion;
|
||||
const isMinorVersionDirty = formState?.dirtyFields?.minorVersion;
|
||||
const isDirty = isMajorVersionDirty || isMinorVersionDirty;
|
||||
const versionFieldsDisabled =
|
||||
applicationUpdating || applicationUnhealthy || maintenanceActive;
|
||||
const saveDisabled = versionFieldsDisabled || !isDirty;
|
||||
|
||||
const handleDatabaseServiceVersionsChange = async (
|
||||
formValues: DatabaseServiceVersionFormValues,
|
||||
) => {
|
||||
const newVersion = `${formValues.majorVersion.value}.${formValues.minorVersion.value}`;
|
||||
|
||||
// Major version change
|
||||
if (isMajorVersionDirty) {
|
||||
openDialog({
|
||||
title: 'Update Postgres MAJOR version',
|
||||
component: (
|
||||
<DatabaseMigrateVersionConfirmationDialog
|
||||
postgresVersion={newVersion}
|
||||
onCancel={() => {}}
|
||||
onProceed={() => {}}
|
||||
/>
|
||||
),
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Minor version change
|
||||
const updateConfigPromise = updateConfig({
|
||||
variables: {
|
||||
appId: currentProject.id,
|
||||
config: {
|
||||
postgres: {
|
||||
version: formValues.version.value,
|
||||
version: newVersion,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -143,6 +262,33 @@ export default function DatabaseServiceVersionSettings() {
|
||||
);
|
||||
};
|
||||
|
||||
const openLatestUpgradeLogsModal = async () => {
|
||||
openDialog({
|
||||
component: <DatabaseMigrateLogsModal />,
|
||||
props: {
|
||||
PaperProps: { className: 'p-0 max-w-2xl w-full' },
|
||||
titleProps: {
|
||||
onClose: closeDialog,
|
||||
},
|
||||
},
|
||||
title: 'Postgres upgrade logs',
|
||||
});
|
||||
};
|
||||
|
||||
if (loadingPostgresSettings) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
delay={1000}
|
||||
label="Loading Postgres version..."
|
||||
className="justify-center"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (postgresSettingsError) {
|
||||
throw postgresSettingsError;
|
||||
}
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
<Form onSubmit={handleDatabaseServiceVersionsChange}>
|
||||
@@ -151,54 +297,144 @@ export default function DatabaseServiceVersionSettings() {
|
||||
description="The version of Postgres to use."
|
||||
slotProps={{
|
||||
submitButton: {
|
||||
disabled: !formState.isDirty || maintenanceActive,
|
||||
disabled: saveDisabled,
|
||||
loading: formState.isSubmitting,
|
||||
},
|
||||
}}
|
||||
docsLink="https://hub.docker.com/r/nhost/postgres/tags"
|
||||
docsTitle="the latest releases"
|
||||
className="grid grid-flow-row px-4 gap-x-4 gap-y-2 lg:grid-cols-5"
|
||||
className="flex flex-col"
|
||||
topRightElement={
|
||||
shouldShowUpgradeLogs ? (
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
size="medium"
|
||||
className="self-center"
|
||||
onClick={openLatestUpgradeLogsModal}
|
||||
startIcon={<RepeatIcon className="h-4 w-4" />}
|
||||
>
|
||||
View latest upgrade logs
|
||||
</Button>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
<ControlledAutocomplete
|
||||
id="version"
|
||||
name="version"
|
||||
autoHighlight
|
||||
freeSolo
|
||||
getOptionLabel={(option) => {
|
||||
if (typeof option === 'string') {
|
||||
return option || '';
|
||||
}
|
||||
|
||||
return option.value;
|
||||
}}
|
||||
isOptionEqualToValue={() => false}
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
const inputValueLower = inputValue.toLowerCase();
|
||||
const matched = [];
|
||||
const otherOptions = [];
|
||||
|
||||
options.forEach((option) => {
|
||||
const optionLabelLower = option.label.toLowerCase();
|
||||
|
||||
if (optionLabelLower.startsWith(inputValueLower)) {
|
||||
matched.push(option);
|
||||
} else {
|
||||
otherOptions.push(option);
|
||||
<Box className="grid grid-flow-row gap-x-4 gap-y-2 lg:grid-cols-5">
|
||||
<ControlledAutocomplete
|
||||
id="majorVersion"
|
||||
name="majorVersion"
|
||||
autoHighlight
|
||||
freeSolo
|
||||
disabled={versionFieldsDisabled}
|
||||
getOptionLabel={(option) => {
|
||||
if (typeof option === 'string') {
|
||||
return option || '';
|
||||
}
|
||||
});
|
||||
|
||||
const result = [...matched, ...otherOptions];
|
||||
return option.value;
|
||||
}}
|
||||
showCustomOption="auto"
|
||||
isOptionEqualToValue={() => false}
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
const inputValueLower = inputValue.toLowerCase();
|
||||
const matched = [];
|
||||
const otherOptions = [];
|
||||
|
||||
return result;
|
||||
}}
|
||||
fullWidth
|
||||
className="lg:col-span-2"
|
||||
options={availableVersions}
|
||||
error={!!formState.errors?.version?.message}
|
||||
helperText={formState.errors?.version?.message}
|
||||
showCustomOption="auto"
|
||||
customOptionLabel={(value) => `Use custom value: "${value}"`}
|
||||
/>
|
||||
options.forEach((option) => {
|
||||
const optionLabelLower = option.label.toLowerCase();
|
||||
|
||||
if (optionLabelLower.startsWith(inputValueLower)) {
|
||||
matched.push(option);
|
||||
} else {
|
||||
otherOptions.push(option);
|
||||
}
|
||||
});
|
||||
|
||||
const result = [...matched, ...otherOptions];
|
||||
|
||||
return result;
|
||||
}}
|
||||
onChange={(_event, value) => {
|
||||
if (typeof value !== 'string' && !Array.isArray(value)) {
|
||||
if (value.value !== selectedMajor) {
|
||||
const nextAvailableMinorVersions =
|
||||
majorToMinorVersions[value.value] || [];
|
||||
|
||||
const isSelectedMinorAvailable =
|
||||
nextAvailableMinorVersions.some(
|
||||
(minor) => minor.value === selectedMinor,
|
||||
);
|
||||
|
||||
// If the selected minor version is not available in the new major version, select the first available minor version
|
||||
if (
|
||||
!isSelectedMinorAvailable &&
|
||||
nextAvailableMinorVersions.length > 0
|
||||
) {
|
||||
form.setValue(
|
||||
'minorVersion',
|
||||
nextAvailableMinorVersions[0],
|
||||
);
|
||||
}
|
||||
}
|
||||
form.setValue('majorVersion', value);
|
||||
}
|
||||
}}
|
||||
fullWidth
|
||||
className="lg:col-span-1"
|
||||
label="MAJOR"
|
||||
options={availableMajorVersions}
|
||||
error={!!formState.errors?.majorVersion?.value?.message}
|
||||
helperText={formState.errors?.majorVersion?.value?.message}
|
||||
customOptionLabel={(value) => `Use custom value: "${value}"`}
|
||||
/>
|
||||
<ControlledAutocomplete
|
||||
id="minorVersion"
|
||||
name="minorVersion"
|
||||
autoHighlight
|
||||
freeSolo
|
||||
disabled={versionFieldsDisabled}
|
||||
getOptionLabel={(option) => {
|
||||
if (typeof option === 'string') {
|
||||
return option || '';
|
||||
}
|
||||
|
||||
return option.value;
|
||||
}}
|
||||
isOptionEqualToValue={() => false}
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
const inputValueLower = inputValue.toLowerCase();
|
||||
const matched = [];
|
||||
const otherOptions = [];
|
||||
|
||||
options.forEach((option) => {
|
||||
const optionLabelLower = option.label.toLowerCase();
|
||||
|
||||
if (optionLabelLower.startsWith(inputValueLower)) {
|
||||
matched.push(option);
|
||||
} else {
|
||||
otherOptions.push(option);
|
||||
}
|
||||
});
|
||||
|
||||
const result = [...matched, ...otherOptions];
|
||||
|
||||
return result;
|
||||
}}
|
||||
fullWidth
|
||||
className="lg:col-span-2"
|
||||
label="MINOR"
|
||||
options={availableMinorVersions}
|
||||
error={!!formState.errors?.minorVersion?.value?.message}
|
||||
helperText={formState.errors?.minorVersion?.value?.message}
|
||||
showCustomOption="auto"
|
||||
customOptionLabel={(value) => `Use custom value: "${value}"`}
|
||||
/>
|
||||
</Box>
|
||||
{showMigrateWarning && <DatabaseMigrateDowntimeWarning />}
|
||||
{applicationUpdating && <DatabaseUpdateInProgressWarning />}
|
||||
{applicationUnhealthy && !isMigrating && (
|
||||
<DatabaseMigrateDisabledError />
|
||||
)}
|
||||
</SettingsContainer>
|
||||
</Form>
|
||||
</FormProvider>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Alert } from '@/components/ui/v2/Alert';
|
||||
import { ClockIcon } from '@/components/ui/v2/icons/ClockIcon';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
|
||||
export default function DatabaseMigrateWarning() {
|
||||
return (
|
||||
<Alert severity="warning" className="flex flex-col gap-3 text-left">
|
||||
<Text className="flex items-center gap-1 font-semibold">
|
||||
<ClockIcon className="h-4 w-4" /> An update is in progress
|
||||
</Text>
|
||||
<Text>You can edit the version only after the update is complete.</Text>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as DatabaseUpdateInProgressWarning } from './DatabaseUpdateInProgressWarning';
|
||||
@@ -38,6 +38,13 @@ export default function useNavigationVisible() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
state === ApplicationStatus.Migrating &&
|
||||
currentProject.desiredState === ApplicationStatus.Live
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
state === ApplicationStatus.Live ||
|
||||
state === ApplicationStatus.Updating
|
||||
|
||||
@@ -136,7 +136,7 @@ export default function PortsFormSection() {
|
||||
<InfoCard
|
||||
title="URL"
|
||||
value={getRunServicePortURL(
|
||||
currentProject?.subdomain,
|
||||
formValues?.subdomain,
|
||||
currentProject?.region.name,
|
||||
currentProject?.region.domain,
|
||||
formValues.ports[index],
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
mutation UpdateDatabaseVersion($appId: uuid!, $version: String!) {
|
||||
changeDatabaseVersion(appID: $appId, version: $version)
|
||||
}
|
||||
11
dashboard/src/gql/logs/getSystemLogs.gql
Normal file
11
dashboard/src/gql/logs/getSystemLogs.gql
Normal file
@@ -0,0 +1,11 @@
|
||||
query getSystemLogs(
|
||||
$appID: String!
|
||||
$action: String!
|
||||
$from: Timestamp
|
||||
$to: Timestamp
|
||||
) {
|
||||
systemLogs(appID: $appID, action: $action, from: $from) {
|
||||
timestamp
|
||||
log
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,8 @@ export default function AppIndexPage() {
|
||||
return <ApplicationUnpausing />;
|
||||
case ApplicationStatus.Restoring:
|
||||
return <ApplicationRestoring />;
|
||||
case ApplicationStatus.Migrating:
|
||||
return <ApplicationLive />;
|
||||
default:
|
||||
return <ApplicationUnknown />;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ export default function ServicesPage() {
|
||||
openDrawer({
|
||||
title: (
|
||||
<Box className="flex flex-row items-center space-x-2">
|
||||
<CubeIcon className="w-5 h-5" />
|
||||
<CubeIcon className="h-5 w-5" />
|
||||
<Text>Create a new run service</Text>
|
||||
</Box>
|
||||
),
|
||||
@@ -104,7 +104,7 @@ export default function ServicesPage() {
|
||||
openDrawer({
|
||||
title: (
|
||||
<Box className="flex flex-row items-center space-x-2">
|
||||
<CubeIcon className="w-5 h-5" />
|
||||
<CubeIcon className="h-5 w-5" />
|
||||
<Text>Create a new service</Text>
|
||||
</Box>
|
||||
),
|
||||
@@ -125,23 +125,23 @@ export default function ServicesPage() {
|
||||
|
||||
if (services.length === 0 && !loading) {
|
||||
return (
|
||||
<Container className="mx-auto space-y-5 overflow-x-hidden max-w-9xl">
|
||||
<Container className="mx-auto max-w-9xl space-y-5 overflow-x-hidden">
|
||||
<div className="flex flex-row place-content-end">
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={openCreateServiceDialog}
|
||||
startIcon={<PlusIcon className="w-4 h-4" />}
|
||||
startIcon={<PlusIcon className="h-4 w-4" />}
|
||||
disabled={!isPlatform}
|
||||
>
|
||||
Add service
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Box className="flex flex-col items-center justify-center px-48 py-12 space-y-5 border rounded-lg shadow-sm">
|
||||
<ServicesIcon className="w-10 h-10" />
|
||||
<Box className="flex flex-col items-center justify-center space-y-5 rounded-lg border px-48 py-12 shadow-sm">
|
||||
<ServicesIcon className="h-10 w-10" />
|
||||
<div className="flex flex-col space-y-1">
|
||||
<Text className="font-medium text-center" variant="h3">
|
||||
<Text className="text-center font-medium" variant="h3">
|
||||
No custom services are available
|
||||
</Text>
|
||||
<Text variant="subtitle1" className="text-center">
|
||||
@@ -149,13 +149,13 @@ export default function ServicesPage() {
|
||||
</Text>
|
||||
</div>
|
||||
{isPlatform ? (
|
||||
<div className="flex flex-row rounded-lg place-content-between ">
|
||||
<div className="flex flex-row place-content-between rounded-lg ">
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className="w-full"
|
||||
onClick={openCreateServiceDialog}
|
||||
startIcon={<PlusIcon className="w-4 h-4" />}
|
||||
startIcon={<PlusIcon className="h-4 w-4" />}
|
||||
>
|
||||
Add service
|
||||
</Button>
|
||||
@@ -168,12 +168,12 @@ export default function ServicesPage() {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<Box className="flex flex-row p-4 place-content-end border-b-1">
|
||||
<Box className="flex flex-row place-content-end border-b-1 p-4">
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={openCreateServiceDialog}
|
||||
startIcon={<PlusIcon className="w-4 h-4" />}
|
||||
startIcon={<PlusIcon className="h-4 w-4" />}
|
||||
disabled={!isPlatform}
|
||||
>
|
||||
Add service
|
||||
|
||||
92
dashboard/src/utils/__generated__/graphql.ts
generated
92
dashboard/src/utils/__generated__/graphql.ts
generated
@@ -23082,6 +23082,14 @@ export type UpdateConfigMutationVariables = Exact<{
|
||||
|
||||
export type UpdateConfigMutation = { __typename?: 'mutation_root', updateConfig: { __typename?: 'ConfigConfig', id: 'ConfigConfig', postgres?: { __typename?: 'ConfigPostgres', resources?: { __typename?: 'ConfigPostgresResources', enablePublicAccess?: boolean | null, storage?: { __typename?: 'ConfigPostgresStorage', capacity: any } | null } | null } | null, ai?: { __typename?: 'ConfigAI', version?: string | null, webhookSecret: string, autoEmbeddings?: { __typename?: 'ConfigAIAutoEmbeddings', synchPeriodMinutes?: any | null } | null, openai: { __typename?: 'ConfigAIOpenai', organization?: string | null, apiKey: string }, resources: { __typename?: 'ConfigAIResources', compute: { __typename?: 'ConfigComputeResources', cpu: any, memory: any } } } | null } };
|
||||
|
||||
export type UpdateDatabaseVersionMutationVariables = Exact<{
|
||||
appId: Scalars['uuid'];
|
||||
version: Scalars['String'];
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdateDatabaseVersionMutation = { __typename?: 'mutation_root', changeDatabaseVersion: boolean };
|
||||
|
||||
export type UnpauseApplicationMutationVariables = Exact<{
|
||||
appId: Scalars['uuid'];
|
||||
}>;
|
||||
@@ -23204,6 +23212,16 @@ export type GetServiceLabelValuesQueryVariables = Exact<{
|
||||
|
||||
export type GetServiceLabelValuesQuery = { __typename?: 'query_root', getServiceLabelValues: Array<string> };
|
||||
|
||||
export type GetSystemLogsQueryVariables = Exact<{
|
||||
appID: Scalars['String'];
|
||||
action: Scalars['String'];
|
||||
from?: InputMaybe<Scalars['Timestamp']>;
|
||||
to?: InputMaybe<Scalars['Timestamp']>;
|
||||
}>;
|
||||
|
||||
|
||||
export type GetSystemLogsQuery = { __typename?: 'query_root', systemLogs: Array<{ __typename?: 'Log', timestamp: any, log: string }> };
|
||||
|
||||
export type DeletePaymentMethodMutationVariables = Exact<{
|
||||
paymentMethodId: Scalars['uuid'];
|
||||
}>;
|
||||
@@ -25854,6 +25872,38 @@ export function useUpdateConfigMutation(baseOptions?: Apollo.MutationHookOptions
|
||||
export type UpdateConfigMutationHookResult = ReturnType<typeof useUpdateConfigMutation>;
|
||||
export type UpdateConfigMutationResult = Apollo.MutationResult<UpdateConfigMutation>;
|
||||
export type UpdateConfigMutationOptions = Apollo.BaseMutationOptions<UpdateConfigMutation, UpdateConfigMutationVariables>;
|
||||
export const UpdateDatabaseVersionDocument = gql`
|
||||
mutation UpdateDatabaseVersion($appId: uuid!, $version: String!) {
|
||||
changeDatabaseVersion(appID: $appId, version: $version)
|
||||
}
|
||||
`;
|
||||
export type UpdateDatabaseVersionMutationFn = Apollo.MutationFunction<UpdateDatabaseVersionMutation, UpdateDatabaseVersionMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useUpdateDatabaseVersionMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useUpdateDatabaseVersionMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useUpdateDatabaseVersionMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [updateDatabaseVersionMutation, { data, loading, error }] = useUpdateDatabaseVersionMutation({
|
||||
* variables: {
|
||||
* appId: // value for 'appId'
|
||||
* version: // value for 'version'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useUpdateDatabaseVersionMutation(baseOptions?: Apollo.MutationHookOptions<UpdateDatabaseVersionMutation, UpdateDatabaseVersionMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<UpdateDatabaseVersionMutation, UpdateDatabaseVersionMutationVariables>(UpdateDatabaseVersionDocument, options);
|
||||
}
|
||||
export type UpdateDatabaseVersionMutationHookResult = ReturnType<typeof useUpdateDatabaseVersionMutation>;
|
||||
export type UpdateDatabaseVersionMutationResult = Apollo.MutationResult<UpdateDatabaseVersionMutation>;
|
||||
export type UpdateDatabaseVersionMutationOptions = Apollo.BaseMutationOptions<UpdateDatabaseVersionMutation, UpdateDatabaseVersionMutationVariables>;
|
||||
export const UnpauseApplicationDocument = gql`
|
||||
mutation UnpauseApplication($appId: uuid!) {
|
||||
updateApp(pk_columns: {id: $appId}, _set: {desiredState: 5}) {
|
||||
@@ -26441,6 +26491,48 @@ export type GetServiceLabelValuesQueryResult = Apollo.QueryResult<GetServiceLabe
|
||||
export function refetchGetServiceLabelValuesQuery(variables: GetServiceLabelValuesQueryVariables) {
|
||||
return { query: GetServiceLabelValuesDocument, variables: variables }
|
||||
}
|
||||
export const GetSystemLogsDocument = gql`
|
||||
query getSystemLogs($appID: String!, $action: String!, $from: Timestamp, $to: Timestamp) {
|
||||
systemLogs(appID: $appID, action: $action, from: $from) {
|
||||
timestamp
|
||||
log
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useGetSystemLogsQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useGetSystemLogsQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useGetSystemLogsQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useGetSystemLogsQuery({
|
||||
* variables: {
|
||||
* appID: // value for 'appID'
|
||||
* action: // value for 'action'
|
||||
* from: // value for 'from'
|
||||
* to: // value for 'to'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useGetSystemLogsQuery(baseOptions: Apollo.QueryHookOptions<GetSystemLogsQuery, GetSystemLogsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<GetSystemLogsQuery, GetSystemLogsQueryVariables>(GetSystemLogsDocument, options);
|
||||
}
|
||||
export function useGetSystemLogsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetSystemLogsQuery, GetSystemLogsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<GetSystemLogsQuery, GetSystemLogsQueryVariables>(GetSystemLogsDocument, options);
|
||||
}
|
||||
export type GetSystemLogsQueryHookResult = ReturnType<typeof useGetSystemLogsQuery>;
|
||||
export type GetSystemLogsLazyQueryHookResult = ReturnType<typeof useGetSystemLogsLazyQuery>;
|
||||
export type GetSystemLogsQueryResult = Apollo.QueryResult<GetSystemLogsQuery, GetSystemLogsQueryVariables>;
|
||||
export function refetchGetSystemLogsQuery(variables: GetSystemLogsQueryVariables) {
|
||||
return { query: GetSystemLogsDocument, variables: variables }
|
||||
}
|
||||
export const DeletePaymentMethodDocument = gql`
|
||||
mutation deletePaymentMethod($paymentMethodId: uuid!) {
|
||||
deletePaymentMethod(id: $paymentMethodId) {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost/docs
|
||||
|
||||
## 2.14.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 4564232: chore: update `clientStorage` docs and add usage examples
|
||||
|
||||
## 2.14.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/docs",
|
||||
"version": "2.14.2",
|
||||
"version": "2.14.3",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "mintlify dev"
|
||||
|
||||
@@ -39,14 +39,63 @@ Object where the refresh token will be persisted and read locally.
|
||||
Recommended values:
|
||||
|
||||
- `'web'` and `'cookies'`: no value is required
|
||||
- `'react-native'`: `import Storage from @react-native-async-storage/async-storage`
|
||||
- `'cookies'`: `localStorage`
|
||||
- `'react-native'`: use [@react-native-async-storage/async-storage](https://www.npmjs.com/package/@react-native-async-storage/async-storage)
|
||||
```ts
|
||||
import { NhostClient } from '@nhost/nhost-js'
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
const nhost = new NhostClient({
|
||||
...
|
||||
clientStorageType: 'react-native',
|
||||
clientStorage: AsyncStorage
|
||||
})
|
||||
```
|
||||
- `'custom'`: an object that defines the following methods:
|
||||
- `setItem` or `setItemAsync`
|
||||
- `getItem` or `getItemAsync`
|
||||
- `removeItem`
|
||||
- `'capacitor'`: `import { Storage } from @capacitor/storage`
|
||||
- `'expo-secure-store'`: `import * as SecureStore from 'expo-secure-store'`
|
||||
- `'capacitor'`:
|
||||
|
||||
- capacitor version **< 4** : use [@capacitor/storage](https://www.npmjs.com/package/@capacitor/storage)
|
||||
|
||||
```ts
|
||||
import { NhostClient } from '@nhost/nhost-js'
|
||||
import { Storage } from '@capacitor/storage'
|
||||
const nhost = new NhostClient({
|
||||
...
|
||||
clientStorageType: 'capacitor',
|
||||
clientStorage: Storage
|
||||
})
|
||||
```
|
||||
|
||||
- capacitor version **>= 4** : use [@capacitor/preferences](https://www.npmjs.com/package/@capacitor/preferences)
|
||||
|
||||
```ts
|
||||
import { NhostClient } from '@nhost/nhost-js';
|
||||
import { Preferences } from '@capacitor/preferences';
|
||||
const nhost = new NhostClient({
|
||||
...
|
||||
clientStorageType: 'custom',
|
||||
clientStorage: {
|
||||
setItemAsync: async (key, value) => Preferences.set({ key, value }),
|
||||
getItemAsync: async (key) => {
|
||||
const { value } = await Preferences.get({ key });
|
||||
return value;
|
||||
},
|
||||
removeItem(key): (key) => Preferences.remove({ key })
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
- `'expo-secure-store'`: use [expo-secure-store](https://www.npmjs.com/package/expo-secure-store)
|
||||
```ts
|
||||
import { NhostClient } from '@nhost/nhost-js'
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
const nhost = new NhostClient({
|
||||
...
|
||||
clientStorageType: 'expo-secure-store',
|
||||
clientStorage: SecureStore
|
||||
})
|
||||
```
|
||||
|
||||
| Property | Type | Required | Notes |
|
||||
| :-------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | :------: | :---- |
|
||||
|
||||
126
docs/reference/javascript/nhost-js/types/auth-options.mdx
Normal file
126
docs/reference/javascript/nhost-js/types/auth-options.mdx
Normal file
@@ -0,0 +1,126 @@
|
||||
---
|
||||
title: AuthOptions
|
||||
sidebarTitle: AuthOptions
|
||||
description: No description provided.
|
||||
---
|
||||
|
||||
# `AuthOptions`
|
||||
|
||||
## Parameters
|
||||
|
||||
---
|
||||
|
||||
**<span className="parameter-name">refreshIntervalTime</span>** <span className="optional-status">optional</span> <code>number</code>
|
||||
|
||||
Time interval until token refreshes, in seconds
|
||||
|
||||
---
|
||||
|
||||
**<span className="parameter-name">clientStorageType</span>** <span className="optional-status">optional</span> [`ClientStorageType`](/reference/javascript/nhost-js/types/client-storage-type)
|
||||
|
||||
Define a way to get information about the refresh token and its expiration date.
|
||||
|
||||
**`@default`**
|
||||
|
||||
`web`
|
||||
|
||||
---
|
||||
|
||||
**<span className="parameter-name">clientStorage</span>** <span className="optional-status">optional</span> [`ClientStorage`](/reference/javascript/nhost-js/types/client-storage)
|
||||
|
||||
Object where the refresh token will be persisted and read locally.
|
||||
|
||||
Recommended values:
|
||||
|
||||
- `'web'` and `'cookies'`: no value is required
|
||||
- `'react-native'`: use [@react-native-async-storage/async-storage](https://www.npmjs.com/package/@react-native-async-storage/async-storage)
|
||||
```ts
|
||||
import { NhostClient } from '@nhost/nhost-js'
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
const nhost = new NhostClient({
|
||||
...
|
||||
clientStorageType: 'react-native',
|
||||
clientStorage: AsyncStorage
|
||||
})
|
||||
```
|
||||
- `'custom'`: an object that defines the following methods:
|
||||
- `setItem` or `setItemAsync`
|
||||
- `getItem` or `getItemAsync`
|
||||
- `removeItem`
|
||||
- `'capacitor'`:
|
||||
|
||||
- capacitor version **< 4** : use [@capacitor/storage](https://www.npmjs.com/package/@capacitor/storage)
|
||||
|
||||
```ts
|
||||
import { NhostClient } from '@nhost/nhost-js'
|
||||
import { Storage } from '@capacitor/storage'
|
||||
const nhost = new NhostClient({
|
||||
...
|
||||
clientStorageType: 'capacitor',
|
||||
clientStorage: Storage
|
||||
})
|
||||
```
|
||||
|
||||
- capacitor version **>= 4** : use [@capacitor/preferences](https://www.npmjs.com/package/@capacitor/preferences)
|
||||
|
||||
```ts
|
||||
import { NhostClient } from '@nhost/nhost-js';
|
||||
import { Preferences } from '@capacitor/preferences';
|
||||
const nhost = new NhostClient({
|
||||
...
|
||||
clientStorageType: 'custom',
|
||||
clientStorage: {
|
||||
setItemAsync: async (key, value) => Preferences.set({ key, value }),
|
||||
getItemAsync: async (key) => {
|
||||
const { value } = await Preferences.get({ key });
|
||||
return value;
|
||||
},
|
||||
removeItem(key): (key) => Preferences.remove({ key })
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
- `'expo-secure-store'`: use [expo-secure-store](https://www.npmjs.com/package/expo-secure-store)
|
||||
```ts
|
||||
import { NhostClient } from '@nhost/nhost-js'
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
const nhost = new NhostClient({
|
||||
...
|
||||
clientStorageType: 'expo-secure-store',
|
||||
clientStorage: SecureStore
|
||||
})
|
||||
```
|
||||
|
||||
| Property | Type | Required | Notes |
|
||||
| :-------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | :------: | :---- |
|
||||
| <span className="parameter-name"><span className="light-grey">clientStorage.</span>customSet</span> | <code>(key: string, value: null | string) => void | Promise<void></code> | | |
|
||||
| <span className="parameter-name"><span className="light-grey">clientStorage.</span>customGet</span> | <code>(key: string) => null | string | Promise<null | string></code> | | |
|
||||
| <span className="parameter-name"><span className="light-grey">clientStorage.</span>deleteItemAsync</span> | <code>(key: string) => void</code> | | |
|
||||
| <span className="parameter-name"><span className="light-grey">clientStorage.</span>getItemAsync</span> | <code>(key: string) => any</code> | | |
|
||||
| <span className="parameter-name"><span className="light-grey">clientStorage.</span>setItemAsync</span> | <code>(key: string, value: string) => void</code> | | |
|
||||
| <span className="parameter-name"><span className="light-grey">clientStorage.</span>remove</span> | <code>(options: { key: string }) => void</code> | | |
|
||||
| <span className="parameter-name"><span className="light-grey">clientStorage.</span>get</span> | <code>(options: { key: string }) => any</code> | | |
|
||||
| <span className="parameter-name"><span className="light-grey">clientStorage.</span>set</span> | <code>(options: { key: string, value: string }) => void</code> | | |
|
||||
| <span className="parameter-name"><span className="light-grey">clientStorage.</span>removeItem</span> | <code>(key: string) => void</code> | | |
|
||||
| <span className="parameter-name"><span className="light-grey">clientStorage.</span>getItem</span> | <code>(key: string) => any</code> | | |
|
||||
| <span className="parameter-name"><span className="light-grey">clientStorage.</span>setItem</span> | <code>(\_key: string, \_value: string) => void</code> | | |
|
||||
|
||||
---
|
||||
|
||||
**<span className="parameter-name">autoRefreshToken</span>** <span className="optional-status">optional</span> <code>boolean</code>
|
||||
|
||||
When set to true, will automatically refresh token before it expires
|
||||
|
||||
---
|
||||
|
||||
**<span className="parameter-name">autoSignIn</span>** <span className="optional-status">optional</span> <code>boolean</code>
|
||||
|
||||
When set to true, will parse the url on startup to check if it contains a refresh token to start the session with
|
||||
|
||||
---
|
||||
|
||||
**<span className="parameter-name">devTools</span>** <span className="optional-status">optional</span> <code>boolean</code>
|
||||
|
||||
Activate devTools e.g. the ability to connect to the xstate inspector
|
||||
|
||||
---
|
||||
@@ -37,14 +37,63 @@ Object where the refresh token will be persisted and read locally.
|
||||
Recommended values:
|
||||
|
||||
- `'web'` and `'cookies'`: no value is required
|
||||
- `'react-native'`: `import Storage from @react-native-async-storage/async-storage`
|
||||
- `'cookies'`: `localStorage`
|
||||
- `'react-native'`: use [@react-native-async-storage/async-storage](https://www.npmjs.com/package/@react-native-async-storage/async-storage)
|
||||
```ts
|
||||
import { NhostClient } from '@nhost/nhost-js'
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
const nhost = new NhostClient({
|
||||
...
|
||||
clientStorageType: 'react-native',
|
||||
clientStorage: AsyncStorage
|
||||
})
|
||||
```
|
||||
- `'custom'`: an object that defines the following methods:
|
||||
- `setItem` or `setItemAsync`
|
||||
- `getItem` or `getItemAsync`
|
||||
- `removeItem`
|
||||
- `'capacitor'`: `import { Storage } from @capacitor/storage`
|
||||
- `'expo-secure-store'`: `import * as SecureStore from 'expo-secure-store'`
|
||||
- `'capacitor'`:
|
||||
|
||||
- capacitor version **< 4** : use [@capacitor/storage](https://www.npmjs.com/package/@capacitor/storage)
|
||||
|
||||
```ts
|
||||
import { NhostClient } from '@nhost/nhost-js'
|
||||
import { Storage } from '@capacitor/storage'
|
||||
const nhost = new NhostClient({
|
||||
...
|
||||
clientStorageType: 'capacitor',
|
||||
clientStorage: Storage
|
||||
})
|
||||
```
|
||||
|
||||
- capacitor version **>= 4** : use [@capacitor/preferences](https://www.npmjs.com/package/@capacitor/preferences)
|
||||
|
||||
```ts
|
||||
import { NhostClient } from '@nhost/nhost-js';
|
||||
import { Preferences } from '@capacitor/preferences';
|
||||
const nhost = new NhostClient({
|
||||
...
|
||||
clientStorageType: 'custom',
|
||||
clientStorage: {
|
||||
setItemAsync: async (key, value) => Preferences.set({ key, value }),
|
||||
getItemAsync: async (key) => {
|
||||
const { value } = await Preferences.get({ key });
|
||||
return value;
|
||||
},
|
||||
removeItem(key): (key) => Preferences.remove({ key })
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
- `'expo-secure-store'`: use [expo-secure-store](https://www.npmjs.com/package/expo-secure-store)
|
||||
```ts
|
||||
import { NhostClient } from '@nhost/nhost-js'
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
const nhost = new NhostClient({
|
||||
...
|
||||
clientStorageType: 'expo-secure-store',
|
||||
clientStorage: SecureStore
|
||||
})
|
||||
```
|
||||
|
||||
| Property | Type | Required | Notes |
|
||||
| :-------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | :------: | :---- |
|
||||
|
||||
@@ -33,14 +33,63 @@ Object where the refresh token will be persisted and read locally.
|
||||
Recommended values:
|
||||
|
||||
- `'web'` and `'cookies'`: no value is required
|
||||
- `'react-native'`: `import Storage from @react-native-async-storage/async-storage`
|
||||
- `'cookies'`: `localStorage`
|
||||
- `'react-native'`: use [@react-native-async-storage/async-storage](https://www.npmjs.com/package/@react-native-async-storage/async-storage)
|
||||
```ts
|
||||
import { NhostClient } from '@nhost/nhost-js'
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
const nhost = new NhostClient({
|
||||
...
|
||||
clientStorageType: 'react-native',
|
||||
clientStorage: AsyncStorage
|
||||
})
|
||||
```
|
||||
- `'custom'`: an object that defines the following methods:
|
||||
- `setItem` or `setItemAsync`
|
||||
- `getItem` or `getItemAsync`
|
||||
- `removeItem`
|
||||
- `'capacitor'`: `import { Storage } from @capacitor/storage`
|
||||
- `'expo-secure-store'`: `import * as SecureStore from 'expo-secure-store'`
|
||||
- `'capacitor'`:
|
||||
|
||||
- capacitor version **< 4** : use [@capacitor/storage](https://www.npmjs.com/package/@capacitor/storage)
|
||||
|
||||
```ts
|
||||
import { NhostClient } from '@nhost/nhost-js'
|
||||
import { Storage } from '@capacitor/storage'
|
||||
const nhost = new NhostClient({
|
||||
...
|
||||
clientStorageType: 'capacitor',
|
||||
clientStorage: Storage
|
||||
})
|
||||
```
|
||||
|
||||
- capacitor version **>= 4** : use [@capacitor/preferences](https://www.npmjs.com/package/@capacitor/preferences)
|
||||
|
||||
```ts
|
||||
import { NhostClient } from '@nhost/nhost-js';
|
||||
import { Preferences } from '@capacitor/preferences';
|
||||
const nhost = new NhostClient({
|
||||
...
|
||||
clientStorageType: 'custom',
|
||||
clientStorage: {
|
||||
setItemAsync: async (key, value) => Preferences.set({ key, value }),
|
||||
getItemAsync: async (key) => {
|
||||
const { value } = await Preferences.get({ key });
|
||||
return value;
|
||||
},
|
||||
removeItem(key): (key) => Preferences.remove({ key })
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
- `'expo-secure-store'`: use [expo-secure-store](https://www.npmjs.com/package/expo-secure-store)
|
||||
```ts
|
||||
import { NhostClient } from '@nhost/nhost-js'
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
const nhost = new NhostClient({
|
||||
...
|
||||
clientStorageType: 'expo-secure-store',
|
||||
clientStorage: SecureStore
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -72,14 +72,63 @@ Object where the refresh token will be persisted and read locally.
|
||||
Recommended values:
|
||||
|
||||
- `'web'` and `'cookies'`: no value is required
|
||||
- `'react-native'`: `import Storage from @react-native-async-storage/async-storage`
|
||||
- `'cookies'`: `localStorage`
|
||||
- `'react-native'`: use [@react-native-async-storage/async-storage](https://www.npmjs.com/package/@react-native-async-storage/async-storage)
|
||||
```ts
|
||||
import { NhostClient } from '@nhost/nhost-js'
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
const nhost = new NhostClient({
|
||||
...
|
||||
clientStorageType: 'react-native',
|
||||
clientStorage: AsyncStorage
|
||||
})
|
||||
```
|
||||
- `'custom'`: an object that defines the following methods:
|
||||
- `setItem` or `setItemAsync`
|
||||
- `getItem` or `getItemAsync`
|
||||
- `removeItem`
|
||||
- `'capacitor'`: `import { Storage } from @capacitor/storage`
|
||||
- `'expo-secure-store'`: `import * as SecureStore from 'expo-secure-store'`
|
||||
- `'capacitor'`:
|
||||
|
||||
- capacitor version **< 4** : use [@capacitor/storage](https://www.npmjs.com/package/@capacitor/storage)
|
||||
|
||||
```ts
|
||||
import { NhostClient } from '@nhost/nhost-js'
|
||||
import { Storage } from '@capacitor/storage'
|
||||
const nhost = new NhostClient({
|
||||
...
|
||||
clientStorageType: 'capacitor',
|
||||
clientStorage: Storage
|
||||
})
|
||||
```
|
||||
|
||||
- capacitor version **>= 4** : use [@capacitor/preferences](https://www.npmjs.com/package/@capacitor/preferences)
|
||||
|
||||
```ts
|
||||
import { NhostClient } from '@nhost/nhost-js';
|
||||
import { Preferences } from '@capacitor/preferences';
|
||||
const nhost = new NhostClient({
|
||||
...
|
||||
clientStorageType: 'custom',
|
||||
clientStorage: {
|
||||
setItemAsync: async (key, value) => Preferences.set({ key, value }),
|
||||
getItemAsync: async (key) => {
|
||||
const { value } = await Preferences.get({ key });
|
||||
return value;
|
||||
},
|
||||
removeItem(key): (key) => Preferences.remove({ key })
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
- `'expo-secure-store'`: use [expo-secure-store](https://www.npmjs.com/package/expo-secure-store)
|
||||
```ts
|
||||
import { NhostClient } from '@nhost/nhost-js'
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
const nhost = new NhostClient({
|
||||
...
|
||||
clientStorageType: 'expo-secure-store',
|
||||
clientStorage: SecureStore
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -72,14 +72,63 @@ Object where the refresh token will be persisted and read locally.
|
||||
Recommended values:
|
||||
|
||||
- `'web'` and `'cookies'`: no value is required
|
||||
- `'react-native'`: `import Storage from @react-native-async-storage/async-storage`
|
||||
- `'cookies'`: `localStorage`
|
||||
- `'react-native'`: use [@react-native-async-storage/async-storage](https://www.npmjs.com/package/@react-native-async-storage/async-storage)
|
||||
```ts
|
||||
import { NhostClient } from '@nhost/nhost-js'
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
const nhost = new NhostClient({
|
||||
...
|
||||
clientStorageType: 'react-native',
|
||||
clientStorage: AsyncStorage
|
||||
})
|
||||
```
|
||||
- `'custom'`: an object that defines the following methods:
|
||||
- `setItem` or `setItemAsync`
|
||||
- `getItem` or `getItemAsync`
|
||||
- `removeItem`
|
||||
- `'capacitor'`: `import { Storage } from @capacitor/storage`
|
||||
- `'expo-secure-store'`: `import * as SecureStore from 'expo-secure-store'`
|
||||
- `'capacitor'`:
|
||||
|
||||
- capacitor version **< 4** : use [@capacitor/storage](https://www.npmjs.com/package/@capacitor/storage)
|
||||
|
||||
```ts
|
||||
import { NhostClient } from '@nhost/nhost-js'
|
||||
import { Storage } from '@capacitor/storage'
|
||||
const nhost = new NhostClient({
|
||||
...
|
||||
clientStorageType: 'capacitor',
|
||||
clientStorage: Storage
|
||||
})
|
||||
```
|
||||
|
||||
- capacitor version **>= 4** : use [@capacitor/preferences](https://www.npmjs.com/package/@capacitor/preferences)
|
||||
|
||||
```ts
|
||||
import { NhostClient } from '@nhost/nhost-js';
|
||||
import { Preferences } from '@capacitor/preferences';
|
||||
const nhost = new NhostClient({
|
||||
...
|
||||
clientStorageType: 'custom',
|
||||
clientStorage: {
|
||||
setItemAsync: async (key, value) => Preferences.set({ key, value }),
|
||||
getItemAsync: async (key) => {
|
||||
const { value } = await Preferences.get({ key });
|
||||
return value;
|
||||
},
|
||||
removeItem(key): (key) => Preferences.remove({ key })
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
- `'expo-secure-store'`: use [expo-secure-store](https://www.npmjs.com/package/expo-secure-store)
|
||||
```ts
|
||||
import { NhostClient } from '@nhost/nhost-js'
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
const nhost = new NhostClient({
|
||||
...
|
||||
clientStorageType: 'expo-secure-store',
|
||||
clientStorage: SecureStore
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost-examples/cli
|
||||
|
||||
## 0.3.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.1.7
|
||||
|
||||
## 0.3.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/cli",
|
||||
"version": "0.3.8",
|
||||
"version": "0.3.9",
|
||||
"main": "src/index.mjs",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @nhost-examples/codegen-react-apollo
|
||||
|
||||
## 0.4.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.5.4
|
||||
- @nhost/react-apollo@12.0.4
|
||||
|
||||
## 0.4.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/codegen-react-apollo",
|
||||
"version": "0.4.8",
|
||||
"version": "0.4.9",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"codegen": "graphql-codegen",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost-examples/codegen-react-query
|
||||
|
||||
## 0.4.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.5.4
|
||||
|
||||
## 0.4.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/codegen-react-query",
|
||||
"version": "0.4.8",
|
||||
"version": "0.4.9",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"codegen": "graphql-codegen",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @nhost-examples/react-urql
|
||||
|
||||
## 0.3.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.5.4
|
||||
- @nhost/react-urql@9.0.4
|
||||
|
||||
## 0.3.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@nhost-examples/codegen-react-urql",
|
||||
"private": true,
|
||||
"version": "0.3.8",
|
||||
"version": "0.3.9",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost-examples/multi-tenant-one-to-many
|
||||
|
||||
## 2.2.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.1.7
|
||||
|
||||
## 2.2.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@nhost-examples/multi-tenant-one-to-many",
|
||||
"private": true,
|
||||
"version": "2.2.8",
|
||||
"version": "2.2.9",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {},
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @nhost-examples/nextjs
|
||||
|
||||
## 0.3.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.5.4
|
||||
- @nhost/react-apollo@12.0.4
|
||||
- @nhost/nextjs@2.1.18
|
||||
|
||||
## 0.3.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/nextjs",
|
||||
"version": "0.3.8",
|
||||
"version": "0.3.9",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost-examples/node-storage
|
||||
|
||||
## 0.2.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.1.7
|
||||
|
||||
## 0.2.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/node-storage",
|
||||
"version": "0.2.8",
|
||||
"version": "0.2.9",
|
||||
"private": true,
|
||||
"description": "This is an example of how to use the Storage with Node.js",
|
||||
"main": "src/index.mjs",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost-examples/nextjs-server-components
|
||||
|
||||
## 0.4.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.1.7
|
||||
|
||||
## 0.4.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/nextjs-server-components",
|
||||
"version": "0.4.9",
|
||||
"version": "0.4.10",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @nhost-examples/react-apollo
|
||||
|
||||
## 0.8.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.5.4
|
||||
- @nhost/react-apollo@12.0.4
|
||||
|
||||
## 0.8.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/react-apollo",
|
||||
"version": "0.8.9",
|
||||
"version": "0.8.10",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.9.9",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost-examples/react-gqty
|
||||
|
||||
## 1.2.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.5.4
|
||||
|
||||
## 1.2.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@nhost-examples/react-gqty",
|
||||
"private": true,
|
||||
"version": "1.2.8",
|
||||
"version": "1.2.9",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @nhost-examples/react-native
|
||||
|
||||
## 0.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.5.4
|
||||
- @nhost/react-apollo@12.0.4
|
||||
|
||||
## 0.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/react-native",
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.3",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"android": "react-native run-android",
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @nhost-examples/vue-apollo
|
||||
|
||||
## 0.6.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.1.7
|
||||
- @nhost/apollo@7.1.4
|
||||
- @nhost/vue@2.6.4
|
||||
|
||||
## 0.6.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@nhost-examples/vue-apollo",
|
||||
"private": true,
|
||||
"version": "0.6.8",
|
||||
"version": "0.6.9",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @nhost-examples/vue-quickstart
|
||||
|
||||
## 0.2.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/apollo@7.1.4
|
||||
- @nhost/vue@2.6.4
|
||||
|
||||
## 0.2.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/vue-quickstart",
|
||||
"version": "0.2.8",
|
||||
"version": "0.2.9",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost/apollo
|
||||
|
||||
## 7.1.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.1.7
|
||||
|
||||
## 7.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/apollo",
|
||||
"version": "7.1.3",
|
||||
"version": "7.1.4",
|
||||
"description": "Nhost Apollo Client library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @nhost/react-apollo
|
||||
|
||||
## 12.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/apollo@7.1.4
|
||||
- @nhost/react@3.5.4
|
||||
|
||||
## 12.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/react-apollo",
|
||||
"version": "12.0.3",
|
||||
"version": "12.0.4",
|
||||
"description": "Nhost React Apollo client",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost/react-urql
|
||||
|
||||
## 9.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.5.4
|
||||
|
||||
## 9.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/react-urql",
|
||||
"version": "9.0.3",
|
||||
"version": "9.0.4",
|
||||
"description": "Nhost React URQL client",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -148,7 +148,8 @@
|
||||
"braces@<3.0.3": ">=3.0.3",
|
||||
"@grpc/grpc-js@>=1.10.0 <1.10.9": ">=1.10.9",
|
||||
"undici@>=6.0.0 <6.11.1": "6.11.1",
|
||||
"undici@<5.28.4": "5.28.4"
|
||||
"undici@<5.28.4": "5.28.4",
|
||||
"fast-xml-parser@<4.4.1": ">=4.4.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost/hasura-auth-js
|
||||
|
||||
## 2.5.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 4564232: chore: update `clientStorage` docs and add usage examples
|
||||
|
||||
## 2.5.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/hasura-auth-js",
|
||||
"version": "2.5.3",
|
||||
"version": "2.5.4",
|
||||
"description": "Hasura-auth client",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -128,15 +128,65 @@ export interface AuthOptions {
|
||||
/** Object where the refresh token will be persisted and read locally.
|
||||
*
|
||||
* Recommended values:
|
||||
*
|
||||
* - `'web'` and `'cookies'`: no value is required
|
||||
* - `'react-native'`: `import Storage from @react-native-async-storage/async-storage`
|
||||
* - `'cookies'`: `localStorage`
|
||||
* - `'react-native'`: use [@react-native-async-storage/async-storage](https://www.npmjs.com/package/@react-native-async-storage/async-storage)
|
||||
* ```ts
|
||||
* import { NhostClient } from '@nhost/nhost-js'
|
||||
* import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
* const nhost = new NhostClient({
|
||||
* ...
|
||||
* clientStorageType: 'react-native',
|
||||
* clientStorage: AsyncStorage
|
||||
* })
|
||||
* ```
|
||||
* - `'custom'`: an object that defines the following methods:
|
||||
* - `setItem` or `setItemAsync`
|
||||
* - `getItem` or `getItemAsync`
|
||||
* - `removeItem`
|
||||
* - `'capacitor'`: `import { Storage } from @capacitor/storage`
|
||||
* - `'expo-secure-store'`: `import * as SecureStore from 'expo-secure-store'`
|
||||
* - `setItem` or `setItemAsync`
|
||||
* - `getItem` or `getItemAsync`
|
||||
* - `removeItem`
|
||||
* - `'capacitor'`:
|
||||
*
|
||||
* - capacitor version **< 4** : use [@capacitor/storage](https://www.npmjs.com/package/@capacitor/storage)
|
||||
*
|
||||
* ```ts
|
||||
* import { NhostClient } from '@nhost/nhost-js'
|
||||
* import { Storage } from '@capacitor/storage'
|
||||
* const nhost = new NhostClient({
|
||||
* ...
|
||||
* clientStorageType: 'capacitor',
|
||||
* clientStorage: Storage
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* - capacitor version **>= 4** : use [@capacitor/preferences](https://www.npmjs.com/package/@capacitor/preferences)
|
||||
*
|
||||
* ```ts
|
||||
* import { NhostClient } from '@nhost/nhost-js';
|
||||
* import { Preferences } from '@capacitor/preferences';
|
||||
* const nhost = new NhostClient({
|
||||
* ...
|
||||
* clientStorageType: 'custom',
|
||||
* clientStorage: {
|
||||
* setItemAsync: async (key, value) => Preferences.set({ key, value }),
|
||||
* getItemAsync: async (key) => {
|
||||
* const { value } = await Preferences.get({ key });
|
||||
* return value;
|
||||
* },
|
||||
* removeItem: (key) => Preferences.remove({ key })
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* - `'expo-secure-store'`: use [expo-secure-store](https://www.npmjs.com/package/expo-secure-store)
|
||||
* ```ts
|
||||
* import { NhostClient } from '@nhost/nhost-js'
|
||||
* import * as SecureStore from 'expo-secure-store';
|
||||
* const nhost = new NhostClient({
|
||||
* ...
|
||||
* clientStorageType: 'expo-secure-store',
|
||||
* clientStorage: SecureStore
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
clientStorage?: ClientStorage
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost/nextjs
|
||||
|
||||
## 2.1.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.5.4
|
||||
|
||||
## 2.1.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/nextjs",
|
||||
"version": "2.1.17",
|
||||
"version": "2.1.18",
|
||||
"description": "Nhost NextJS library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @nhost/nhost-js
|
||||
|
||||
## 3.1.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [4564232]
|
||||
- @nhost/hasura-auth-js@2.5.4
|
||||
|
||||
## 3.1.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/nhost-js",
|
||||
"version": "3.1.6",
|
||||
"version": "3.1.7",
|
||||
"description": "Nhost JavaScript SDK",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost/react
|
||||
|
||||
## 3.5.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.1.7
|
||||
|
||||
## 3.5.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/react",
|
||||
"version": "3.5.3",
|
||||
"version": "3.5.4",
|
||||
"description": "Nhost React library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost/vue
|
||||
|
||||
## 2.6.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.1.7
|
||||
|
||||
## 2.6.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/vue",
|
||||
"version": "2.6.3",
|
||||
"version": "2.6.4",
|
||||
"description": "Nhost Vue library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
166
pnpm-lock.yaml
generated
166
pnpm-lock.yaml
generated
@@ -54,6 +54,7 @@ overrides:
|
||||
'@grpc/grpc-js@>=1.10.0 <1.10.9': '>=1.10.9'
|
||||
undici@>=6.0.0 <6.11.1: 6.11.1
|
||||
undici@<5.28.4: 5.28.4
|
||||
fast-xml-parser@<4.4.1: '>=4.4.1'
|
||||
|
||||
importers:
|
||||
|
||||
@@ -1034,7 +1035,7 @@ importers:
|
||||
devDependencies:
|
||||
'@nhost/nhost-js':
|
||||
specifier: ^3.1.5
|
||||
version: 3.1.5(graphql@16.8.1)
|
||||
version: 3.1.6(graphql@16.8.1)
|
||||
'@playwright/test':
|
||||
specifier: ^1.42.1
|
||||
version: 1.42.1
|
||||
@@ -1073,16 +1074,16 @@ importers:
|
||||
version: 3.59.2
|
||||
svelte-check:
|
||||
specifier: ^3.6.8
|
||||
version: 3.6.8(@babel/core@7.24.7)(postcss@8.4.38)(svelte@3.59.2)
|
||||
version: 3.6.8(postcss@8.4.38)(svelte@3.59.2)
|
||||
tailwindcss:
|
||||
specifier: ^3.4.3
|
||||
version: 3.4.3(ts-node@10.9.2)
|
||||
version: 3.4.3
|
||||
typescript:
|
||||
specifier: ^5.4.3
|
||||
version: 5.4.3
|
||||
vite:
|
||||
specifier: ^5.2.7
|
||||
version: 5.2.7(@types/node@16.18.101)(sass@1.32.0)
|
||||
version: 5.2.7(@types/node@20.14.8)
|
||||
vitest:
|
||||
specifier: ^0.25.8
|
||||
version: 0.25.8
|
||||
@@ -1979,10 +1980,10 @@ importers:
|
||||
version: 3.10.6(@types/react@18.3.3)(react@18.2.0)
|
||||
'@nhost/react':
|
||||
specifier: ^3.5.2
|
||||
version: 3.5.2(@types/react@18.3.3)(react@18.2.0)
|
||||
version: link:../../../packages/react
|
||||
'@nhost/react-apollo':
|
||||
specifier: ^12.0.2
|
||||
version: 12.0.2(@apollo/client@3.10.6)(@nhost/react@3.5.2)(react@18.2.0)
|
||||
version: link:../../../integrations/react-apollo
|
||||
'@react-native-async-storage/async-storage':
|
||||
specifier: ^1.23.1
|
||||
version: 1.23.1(react-native@0.73.7)
|
||||
@@ -8577,7 +8578,7 @@ packages:
|
||||
/@graphql-tools/utils@8.13.1(graphql@16.8.1):
|
||||
resolution: {integrity: sha512-qIh9yYpdUFmctVqovwMdheVNJqFh+DQNWIhX87FJStfXYnmweBUDATok9fWPleKeFwxnW8IapKmY8m8toJEkAw==}
|
||||
peerDependencies:
|
||||
graphql: 16.8.1
|
||||
graphql: '>=16.8.1'
|
||||
dependencies:
|
||||
graphql: 16.8.1
|
||||
tslib: 2.6.2
|
||||
@@ -8616,7 +8617,7 @@ packages:
|
||||
/@graphql-typed-document-node/core@3.2.0(graphql@16.8.1):
|
||||
resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==}
|
||||
peerDependencies:
|
||||
graphql: 16.8.1
|
||||
graphql: '>=16.8.1'
|
||||
dependencies:
|
||||
graphql: 16.8.1
|
||||
|
||||
@@ -10284,18 +10285,6 @@ packages:
|
||||
requiresBuild: true
|
||||
optional: true
|
||||
|
||||
/@nhost/apollo@7.1.2(@apollo/client@3.10.6):
|
||||
resolution: {integrity: sha512-sBd4Go6HkbwdQSP/teP7py8SveMrGZQtK3SyF9QrC59m5/tmkTJbPJJdJuWAmi3Lr8/xHNFaFxAtMWjfGx/VyA==}
|
||||
peerDependencies:
|
||||
'@apollo/client': ^3.7.10
|
||||
'@nhost/nhost-js': 3.1.5
|
||||
dependencies:
|
||||
'@apollo/client': 3.10.6(@types/react@18.3.3)(react@18.2.0)
|
||||
graphql: 16.8.1
|
||||
graphql-ws: 5.16.0(graphql@16.8.1)
|
||||
jwt-decode: 4.0.0
|
||||
dev: false
|
||||
|
||||
/@nhost/graphql-js@0.3.0(graphql@16.8.1):
|
||||
resolution: {integrity: sha512-CVYq6wx0VbaYdpUBmfNO/6mZatHB5+YBCqFjWyxhpN1nzHCHEO6rgdL7j0qk31OFE6XAX0z7AQZSXg1Pn63GUw==}
|
||||
peerDependencies:
|
||||
@@ -10308,9 +10297,10 @@ packages:
|
||||
jwt-decode: 4.0.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: true
|
||||
|
||||
/@nhost/hasura-auth-js@2.5.2:
|
||||
resolution: {integrity: sha512-3O4fIJ8xbdCdKGR/1o5jMczxrLLQ2g6BNp6J9m83COVqg9ka5IXoFuM6pgbX5W7WPe9nIQntvHsfeDynXS+/fg==}
|
||||
/@nhost/hasura-auth-js@2.5.3:
|
||||
resolution: {integrity: sha512-WPDmF7vMU32I/G4Ytlo+ZUE+INzcjp1Av5KNLH/iBWDw/0HB6Vj5/+AWq+IjxQm0+HmKE+hvnc5Hc5rn0oPreg==}
|
||||
dependencies:
|
||||
'@simplewebauthn/browser': 9.0.1
|
||||
fetch-ponyfill: 7.1.0
|
||||
@@ -10319,6 +10309,7 @@ packages:
|
||||
xstate: 4.38.3
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: true
|
||||
|
||||
/@nhost/hasura-storage-js@2.5.1:
|
||||
resolution: {integrity: sha512-I3rOSa095lcR9BUmNw7dOoXLPWL39WOcrb0paUBFX4h3ltR92ILEHTZ38hN6bZSv157ZdqkIFNL/M2G45SSf7g==}
|
||||
@@ -10329,54 +10320,21 @@ packages:
|
||||
xstate: 4.38.3
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: true
|
||||
|
||||
/@nhost/nhost-js@3.1.5(graphql@16.8.1):
|
||||
resolution: {integrity: sha512-SgDGQ0APiRPc6RB2Cl1EcvQUsmWFDx32JmgqYBgLKKV9+PBDKc2GJ4GHECbxQi3RoIMXeJ777XJTEK7mGgNL+A==}
|
||||
/@nhost/nhost-js@3.1.6(graphql@16.8.1):
|
||||
resolution: {integrity: sha512-5DPm3vvsiMzJxYzMSxHwqIsn1GfDsZ1LHq5vndHILL8OH5LHPh3tVlUc4EcElbkqN81yzZ++umcDxxH+w8pkAg==}
|
||||
peerDependencies:
|
||||
graphql: '>=16.8.1'
|
||||
dependencies:
|
||||
'@nhost/graphql-js': 0.3.0(graphql@16.8.1)
|
||||
'@nhost/hasura-auth-js': 2.5.2
|
||||
'@nhost/hasura-auth-js': 2.5.3
|
||||
'@nhost/hasura-storage-js': 2.5.1
|
||||
graphql: 16.8.1
|
||||
isomorphic-unfetch: 3.1.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
/@nhost/react-apollo@12.0.2(@apollo/client@3.10.6)(@nhost/react@3.5.2)(react@18.2.0):
|
||||
resolution: {integrity: sha512-Ll5RKH5g9aFTBBnTjlQCWmrHeyjhnzy1bx0TAG1FsXjh3YbsLPqelmVtAks2t7m+Uw8XWZ37D+cL/Hum/KxG/w==}
|
||||
peerDependencies:
|
||||
'@apollo/client': ^3.7.10
|
||||
'@nhost/react': 3.5.2
|
||||
graphql: '>=16.8.1'
|
||||
react: ^17.0.0 || ^18.0.0
|
||||
react-dom: ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
'@apollo/client': 3.10.6(@types/react@18.3.3)(react@18.2.0)
|
||||
'@nhost/apollo': 7.1.2(@apollo/client@3.10.6)
|
||||
'@nhost/react': 3.5.2(@types/react@18.3.3)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
transitivePeerDependencies:
|
||||
- '@nhost/nhost-js'
|
||||
dev: false
|
||||
|
||||
/@nhost/react@3.5.2(@types/react@18.3.3)(react@18.2.0):
|
||||
resolution: {integrity: sha512-i/qE9k3ccLyMum4JM1aSDc+qPZPW/o4Rgv7mn6Dc2qWEuDEYrOffqMJ/52nnT8k2O8GeHFpu2BZY81SCuiao+w==}
|
||||
peerDependencies:
|
||||
react: ^17.0.0 || ^18.1.0
|
||||
dependencies:
|
||||
'@nhost/nhost-js': 3.1.5(graphql@16.8.1)
|
||||
'@xstate/react': 3.2.2(@types/react@18.3.3)(react@18.2.0)(xstate@4.38.3)
|
||||
jwt-decode: 4.0.0
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
xstate: 4.38.3
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
- '@xstate/fsm'
|
||||
- encoding
|
||||
- graphql
|
||||
dev: false
|
||||
dev: true
|
||||
|
||||
/@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1:
|
||||
resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==}
|
||||
@@ -11375,7 +11333,7 @@ packages:
|
||||
'@react-native-community/cli-tools': 12.3.6
|
||||
chalk: 4.1.2
|
||||
execa: 5.1.1
|
||||
fast-xml-parser: 4.4.0
|
||||
fast-xml-parser: 4.4.1
|
||||
glob: 7.2.3
|
||||
logkitty: 0.7.1
|
||||
transitivePeerDependencies:
|
||||
@@ -11389,7 +11347,7 @@ packages:
|
||||
chalk: 4.1.2
|
||||
execa: 5.1.1
|
||||
fast-glob: 3.3.2
|
||||
fast-xml-parser: 4.4.0
|
||||
fast-xml-parser: 4.4.1
|
||||
logkitty: 0.7.1
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
@@ -11401,7 +11359,7 @@ packages:
|
||||
'@react-native-community/cli-tools': 12.3.6
|
||||
chalk: 4.1.2
|
||||
execa: 5.1.1
|
||||
fast-xml-parser: 4.4.0
|
||||
fast-xml-parser: 4.4.1
|
||||
glob: 7.2.3
|
||||
ora: 5.4.1
|
||||
transitivePeerDependencies:
|
||||
@@ -13312,7 +13270,7 @@ packages:
|
||||
svelte: 3.59.2
|
||||
tiny-glob: 0.2.9
|
||||
undici: 5.28.4
|
||||
vite: 5.2.7(@types/node@16.18.101)(sass@1.32.0)
|
||||
vite: 5.2.7(@types/node@20.14.8)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
@@ -13328,7 +13286,7 @@ packages:
|
||||
'@sveltejs/vite-plugin-svelte': 2.5.3(svelte@3.59.2)(vite@5.2.7)
|
||||
debug: 4.3.5
|
||||
svelte: 3.59.2
|
||||
vite: 5.2.7(@types/node@16.18.101)(sass@1.32.0)
|
||||
vite: 5.2.7(@types/node@20.14.8)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
@@ -13347,7 +13305,7 @@ packages:
|
||||
magic-string: 0.30.8
|
||||
svelte: 3.59.2
|
||||
svelte-hmr: 0.15.3(svelte@3.59.2)
|
||||
vite: 5.2.7(@types/node@16.18.101)(sass@1.32.0)
|
||||
vite: 5.2.7(@types/node@20.14.8)
|
||||
vitefu: 0.2.5(vite@5.2.7)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -20752,8 +20710,8 @@ packages:
|
||||
dependencies:
|
||||
punycode: 1.4.1
|
||||
|
||||
/fast-xml-parser@4.4.0:
|
||||
resolution: {integrity: sha512-kLY3jFlwIYwBNDojclKsNAC12sfD6NwW74QB2CoNGPvtVxjliYehVunB3HYyNi+n4Tt1dAcgwYvmKF/Z18flqg==}
|
||||
/fast-xml-parser@4.4.1:
|
||||
resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
strnum: 1.0.5
|
||||
@@ -21818,7 +21776,7 @@ packages:
|
||||
resolution: {integrity: sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
graphql: 16.8.1
|
||||
graphql: '>=16.8.1'
|
||||
dependencies:
|
||||
graphql: 16.8.1
|
||||
tslib: 2.6.2
|
||||
@@ -23316,7 +23274,7 @@ packages:
|
||||
/isomorphic-unfetch@3.1.0:
|
||||
resolution: {integrity: sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==}
|
||||
dependencies:
|
||||
node-fetch: 2.7.0(encoding@0.1.13)
|
||||
node-fetch: 2.7.0
|
||||
unfetch: 4.2.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
@@ -26782,6 +26740,17 @@ packages:
|
||||
dependencies:
|
||||
whatwg-url: 5.0.0
|
||||
|
||||
/node-fetch@2.7.0:
|
||||
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
|
||||
engines: {node: 4.x || >=6.0.0}
|
||||
peerDependencies:
|
||||
encoding: ^0.1.0
|
||||
peerDependenciesMeta:
|
||||
encoding:
|
||||
optional: true
|
||||
dependencies:
|
||||
whatwg-url: 5.0.0
|
||||
|
||||
/node-fetch@2.7.0(encoding@0.1.13):
|
||||
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
|
||||
engines: {node: 4.x || >=6.0.0}
|
||||
@@ -27786,6 +27755,23 @@ packages:
|
||||
yaml: 1.10.2
|
||||
dev: true
|
||||
|
||||
/postcss-load-config@4.0.2(postcss@8.4.38):
|
||||
resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==}
|
||||
engines: {node: '>= 14'}
|
||||
peerDependencies:
|
||||
postcss: '>=8.4.31'
|
||||
ts-node: '>=9.0.0'
|
||||
peerDependenciesMeta:
|
||||
postcss:
|
||||
optional: true
|
||||
ts-node:
|
||||
optional: true
|
||||
dependencies:
|
||||
lilconfig: 3.1.1
|
||||
postcss: 8.4.38
|
||||
yaml: 2.4.3
|
||||
dev: true
|
||||
|
||||
/postcss-load-config@4.0.2(postcss@8.4.38)(ts-node@10.9.2):
|
||||
resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==}
|
||||
engines: {node: '>= 14'}
|
||||
@@ -30947,7 +30933,7 @@ packages:
|
||||
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
/svelte-check@3.6.8(@babel/core@7.24.7)(postcss@8.4.38)(svelte@3.59.2):
|
||||
/svelte-check@3.6.8(postcss@8.4.38)(svelte@3.59.2):
|
||||
resolution: {integrity: sha512-rhXU7YCDtL+lq2gCqfJDXKTxJfSsCgcd08d7VWBFxTw6IWIbMWSaASbAOD3N0VV9TYSSLUqEBiratLd8WxAJJA==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -30960,7 +30946,7 @@ packages:
|
||||
picocolors: 1.0.1
|
||||
sade: 1.8.1
|
||||
svelte: 3.59.2
|
||||
svelte-preprocess: 5.1.3(@babel/core@7.24.7)(postcss@8.4.38)(svelte@3.59.2)(typescript@5.4.3)
|
||||
svelte-preprocess: 5.1.3(postcss@8.4.38)(svelte@3.59.2)(typescript@5.4.3)
|
||||
typescript: 5.4.3
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
@@ -31000,7 +30986,7 @@ packages:
|
||||
svelte: 3.59.2
|
||||
dev: true
|
||||
|
||||
/svelte-preprocess@5.1.3(@babel/core@7.24.7)(postcss@8.4.38)(svelte@3.59.2)(typescript@5.4.3):
|
||||
/svelte-preprocess@5.1.3(postcss@8.4.38)(svelte@3.59.2)(typescript@5.4.3):
|
||||
resolution: {integrity: sha512-xxAkmxGHT+J/GourS5mVJeOXZzne1FR5ljeOUAMXUkfEhkLEllRreXpbl3dIYJlcJRfL1LO1uIAPpBpBfiqGPw==}
|
||||
engines: {node: '>= 16.0.0', pnpm: ^8.0.0}
|
||||
requiresBuild: true
|
||||
@@ -31038,7 +31024,6 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/core': 7.24.7
|
||||
'@types/pug': 2.0.10
|
||||
detect-indent: 6.1.0
|
||||
magic-string: 0.30.8
|
||||
@@ -31133,6 +31118,37 @@ packages:
|
||||
- ts-node
|
||||
dev: false
|
||||
|
||||
/tailwindcss@3.4.3:
|
||||
resolution: {integrity: sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@alloc/quick-lru': 5.2.0
|
||||
arg: 5.0.2
|
||||
chokidar: 3.6.0
|
||||
didyoumean: 1.2.2
|
||||
dlv: 1.1.3
|
||||
fast-glob: 3.3.2
|
||||
glob-parent: 6.0.2
|
||||
is-glob: 4.0.3
|
||||
jiti: 1.21.0
|
||||
lilconfig: 2.1.0
|
||||
micromatch: 4.0.7
|
||||
normalize-path: 3.0.0
|
||||
object-hash: 3.0.0
|
||||
picocolors: 1.0.1
|
||||
postcss: 8.4.38
|
||||
postcss-import: 15.1.0(postcss@8.4.38)
|
||||
postcss-js: 4.0.1(postcss@8.4.38)
|
||||
postcss-load-config: 4.0.2(postcss@8.4.38)
|
||||
postcss-nested: 6.0.1(postcss@8.4.38)
|
||||
postcss-selector-parser: 6.1.0
|
||||
resolve: 1.22.8
|
||||
sucrase: 3.35.0
|
||||
transitivePeerDependencies:
|
||||
- ts-node
|
||||
dev: true
|
||||
|
||||
/tailwindcss@3.4.3(ts-node@10.9.2):
|
||||
resolution: {integrity: sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
@@ -33156,7 +33172,7 @@ packages:
|
||||
vite:
|
||||
optional: true
|
||||
dependencies:
|
||||
vite: 5.2.7(@types/node@16.18.101)(sass@1.32.0)
|
||||
vite: 5.2.7(@types/node@20.14.8)
|
||||
dev: true
|
||||
|
||||
/vitest@0.25.8:
|
||||
|
||||
Reference in New Issue
Block a user