@trypattern/sdk 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +127 -2
- package/dist/access.d.ts +31 -0
- package/dist/access.js +72 -0
- package/dist/actions.d.ts +57 -0
- package/dist/actions.js +198 -0
- package/dist/api.d.ts +30 -0
- package/dist/api.js +60 -0
- package/dist/app.d.ts +115 -0
- package/dist/app.js +266 -0
- package/dist/appInfo.d.ts +35 -0
- package/dist/appInfo.js +67 -0
- package/dist/appTitle.d.ts +6 -0
- package/dist/appTitle.js +3 -0
- package/dist/appearance.d.ts +7 -0
- package/dist/appearance.js +2 -0
- package/dist/auth/sessionApi.d.ts +41 -0
- package/dist/auth/sessionApi.js +158 -0
- package/dist/auth/sessionStore.d.ts +20 -0
- package/dist/auth/sessionStore.js +40 -0
- package/dist/auth/user.d.ts +11 -0
- package/dist/auth/user.js +9 -0
- package/dist/builtins/Account.d.ts +25 -0
- package/dist/builtins/Account.js +15 -0
- package/dist/builtins/AppSettings.d.ts +12 -0
- package/dist/builtins/AppSettings.js +31 -0
- package/dist/builtins/Palette.d.ts +29 -0
- package/dist/builtins/Palette.js +63 -0
- package/dist/builtins/SignIn.d.ts +28 -0
- package/dist/builtins/SignIn.js +60 -0
- package/dist/builtins/Toasts.d.ts +32 -0
- package/dist/builtins/Toasts.js +25 -0
- package/dist/builtins/actions.d.ts +2 -0
- package/dist/builtins/actions.js +45 -0
- package/dist/cache.d.ts +24 -0
- package/dist/cache.js +112 -0
- package/dist/cli/app.d.ts +2 -0
- package/dist/cli/app.js +1406 -0
- package/dist/cli/publishing.d.ts +21 -0
- package/dist/cli/publishing.js +493 -0
- package/dist/cli/screenshot.d.ts +1 -0
- package/dist/cli/screenshot.js +96 -0
- package/dist/cli/tools.d.ts +12 -0
- package/dist/cli/tools.js +35 -0
- package/dist/clock.d.ts +6 -0
- package/dist/clock.js +31 -0
- package/dist/components/AppFrame.d.ts +14 -0
- package/dist/components/AppFrame.js +64 -0
- package/dist/components/AppearancePicker.d.ts +1 -0
- package/dist/components/AppearancePicker.js +12 -0
- package/dist/components/Avatar.d.ts +9 -0
- package/dist/components/Avatar.js +63 -0
- package/dist/components/Badge.d.ts +11 -0
- package/dist/components/Badge.js +87 -0
- package/dist/components/Button.d.ts +24 -0
- package/dist/components/Button.js +252 -0
- package/dist/components/Card.d.ts +12 -0
- package/dist/components/Card.js +38 -0
- package/dist/components/CommandMenu.d.ts +23 -0
- package/dist/components/CommandMenu.js +214 -0
- package/dist/components/CommandPalette.d.ts +1 -0
- package/dist/components/CommandPalette.js +7 -0
- package/dist/components/Content.d.ts +7 -0
- package/dist/components/Content.js +24 -0
- package/dist/components/ContextMenu.d.ts +8 -0
- package/dist/components/ContextMenu.js +23 -0
- package/dist/components/CopyButton.d.ts +11 -0
- package/dist/components/CopyButton.js +16 -0
- package/dist/components/Dialog.d.ts +11 -0
- package/dist/components/Dialog.js +81 -0
- package/dist/components/Divider.d.ts +7 -0
- package/dist/components/Divider.js +30 -0
- package/dist/components/EmptyState.d.ts +8 -0
- package/dist/components/EmptyState.js +24 -0
- package/dist/components/Form.d.ts +9 -0
- package/dist/components/Form.js +23 -0
- package/dist/components/Icon.d.ts +10 -0
- package/dist/components/Icon.js +22 -0
- package/dist/components/IconButton.d.ts +14 -0
- package/dist/components/IconButton.js +53 -0
- package/dist/components/Kbd.d.ts +6 -0
- package/dist/components/Kbd.js +67 -0
- package/dist/components/Link.d.ts +14 -0
- package/dist/components/Link.js +44 -0
- package/dist/components/ListRow.d.ts +12 -0
- package/dist/components/ListRow.js +62 -0
- package/dist/components/LoadingRows.d.ts +5 -0
- package/dist/components/LoadingRows.js +6 -0
- package/dist/components/Menu.d.ts +12 -0
- package/dist/components/Menu.js +33 -0
- package/dist/components/MenuItems.d.ts +21 -0
- package/dist/components/MenuItems.js +32 -0
- package/dist/components/Placement.d.ts +21 -0
- package/dist/components/Placement.js +1 -0
- package/dist/components/Popup.d.ts +8 -0
- package/dist/components/Popup.js +43 -0
- package/dist/components/PopupItem.d.ts +8 -0
- package/dist/components/PopupItem.js +51 -0
- package/dist/components/PropertyRow.d.ts +7 -0
- package/dist/components/PropertyRow.js +23 -0
- package/dist/components/RadioGroup.d.ts +18 -0
- package/dist/components/RadioGroup.js +85 -0
- package/dist/components/Row.d.ts +16 -0
- package/dist/components/Row.js +27 -0
- package/dist/components/Screen.d.ts +13 -0
- package/dist/components/Screen.js +56 -0
- package/dist/components/Scrim.d.ts +8 -0
- package/dist/components/Scrim.js +40 -0
- package/dist/components/ScrollArea.d.ts +15 -0
- package/dist/components/ScrollArea.js +13 -0
- package/dist/components/SegmentedControl.d.ts +17 -0
- package/dist/components/SegmentedControl.js +68 -0
- package/dist/components/Select.d.ts +17 -0
- package/dist/components/Select.js +107 -0
- package/dist/components/SettingsRow.d.ts +8 -0
- package/dist/components/SettingsRow.js +32 -0
- package/dist/components/Sidebar.d.ts +20 -0
- package/dist/components/Sidebar.js +137 -0
- package/dist/components/SignInScreen.d.ts +1 -0
- package/dist/components/SignInScreen.js +40 -0
- package/dist/components/Size.d.ts +1 -0
- package/dist/components/Size.js +1 -0
- package/dist/components/Skeleton.d.ts +8 -0
- package/dist/components/Skeleton.js +48 -0
- package/dist/components/Spinner.d.ts +6 -0
- package/dist/components/Spinner.js +54 -0
- package/dist/components/Stack.d.ts +14 -0
- package/dist/components/Stack.js +19 -0
- package/dist/components/Switch.d.ts +10 -0
- package/dist/components/Switch.js +72 -0
- package/dist/components/Text.d.ts +21 -0
- package/dist/components/Text.js +77 -0
- package/dist/components/TextField.d.ts +25 -0
- package/dist/components/TextField.js +68 -0
- package/dist/components/Toaster.d.ts +1 -0
- package/dist/components/Toaster.js +83 -0
- package/dist/components/Tooltip.d.ts +8 -0
- package/dist/components/Tooltip.js +43 -0
- package/dist/components/controlLook.d.ts +70 -0
- package/dist/components/controlLook.js +80 -0
- package/dist/components/frame.d.ts +4 -0
- package/dist/components/frame.js +2 -0
- package/dist/components/icons.d.ts +59 -0
- package/dist/components/icons.js +111 -0
- package/dist/components/interaction.d.ts +12 -0
- package/dist/components/interaction.js +22 -0
- package/dist/components/spacing.d.ts +118 -0
- package/dist/components/spacing.js +46 -0
- package/dist/components/tones.d.ts +28 -0
- package/dist/components/tones.js +12 -0
- package/dist/entityStore.d.ts +46 -0
- package/dist/entityStore.js +160 -0
- package/dist/flows/accessFlows.d.ts +9 -0
- package/dist/flows/accessFlows.js +167 -0
- package/dist/flows/browser.d.ts +34 -0
- package/dist/flows/browser.js +167 -0
- package/dist/flows/contactSheet.d.ts +1 -0
- package/dist/flows/contactSheet.js +63 -0
- package/dist/flows/flowBrowser.d.ts +25 -0
- package/dist/flows/flowBrowser.js +23 -0
- package/dist/flows/flowSetup.d.ts +3 -0
- package/dist/flows/flowSetup.js +35 -0
- package/dist/flows/flows.d.ts +47 -0
- package/dist/flows/flows.js +175 -0
- package/dist/flows/screenshotName.d.ts +1 -0
- package/dist/flows/screenshotName.js +6 -0
- package/dist/gate.d.ts +8 -0
- package/dist/gate.js +52 -0
- package/dist/hooks/appContext.d.ts +15 -0
- package/dist/hooks/appContext.js +5 -0
- package/dist/hooks/appHooks.d.ts +21 -0
- package/dist/hooks/appHooks.js +24 -0
- package/dist/hooks/useAnimatesOnChange.d.ts +1 -0
- package/dist/hooks/useAnimatesOnChange.js +14 -0
- package/dist/hooks/useStoreState.d.ts +6 -0
- package/dist/hooks/useStoreState.js +5 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +21 -0
- package/dist/memoryApi.d.ts +19 -0
- package/dist/memoryApi.js +42 -0
- package/dist/model.d.ts +94 -0
- package/dist/model.js +243 -0
- package/dist/page.d.ts +11 -0
- package/dist/page.js +9 -0
- package/dist/platform.d.ts +1 -0
- package/dist/platform.js +5 -0
- package/dist/publishWire.d.ts +271 -0
- package/dist/publishWire.js +168 -0
- package/dist/react.d.ts +49 -0
- package/dist/react.js +40 -0
- package/dist/registered.d.ts +60 -0
- package/dist/registered.js +19 -0
- package/dist/routes.d.ts +38 -0
- package/dist/routes.js +21 -0
- package/dist/rules/checkAllFiles.d.ts +1 -0
- package/dist/rules/checkAllFiles.js +749 -0
- package/dist/rules/checkApiRouting.d.ts +1 -0
- package/dist/rules/checkApiRouting.js +112 -0
- package/dist/rules/lintPlugin.d.ts +252 -0
- package/dist/rules/lintPlugin.js +1159 -0
- package/dist/rules/messages.d.ts +48 -0
- package/dist/rules/messages.js +48 -0
- package/dist/rules/themeTokens.d.ts +6 -0
- package/dist/rules/themeTokens.js +64 -0
- package/dist/schema.d.ts +4 -0
- package/dist/schema.js +2 -0
- package/dist/server/auth.d.ts +115 -0
- package/dist/server/auth.js +134 -0
- package/dist/server/authService.d.ts +9 -0
- package/dist/server/authService.js +50 -0
- package/dist/server/createServer.d.ts +402 -0
- package/dist/server/createServer.js +82 -0
- package/dist/server/env.d.ts +15 -0
- package/dist/server/env.js +1 -0
- package/dist/server/index.d.ts +13 -0
- package/dist/server/index.js +9 -0
- package/dist/server/services.d.ts +9 -0
- package/dist/server/services.js +47 -0
- package/dist/server/session.d.ts +29 -0
- package/dist/server/session.js +65 -0
- package/dist/server/sessions.d.ts +92 -0
- package/dist/server/sessions.js +47 -0
- package/dist/server/settings.d.ts +49 -0
- package/dist/server/settings.js +25 -0
- package/dist/server/signInWire.d.ts +53 -0
- package/dist/server/signInWire.js +23 -0
- package/dist/server/tables.d.ts +242 -0
- package/dist/server/tables.js +246 -0
- package/dist/server/users.d.ts +189 -0
- package/dist/server/users.js +49 -0
- package/dist/serviceClients.d.ts +5 -0
- package/dist/serviceClients.js +70 -0
- package/dist/services.d.ts +53 -0
- package/dist/services.js +30 -0
- package/dist/shortcuts.d.ts +3 -0
- package/dist/shortcuts.js +40 -0
- package/dist/start.d.ts +20 -0
- package/dist/start.js +145 -0
- package/dist/state.d.ts +14 -0
- package/dist/state.js +17 -0
- package/dist/store.d.ts +45 -0
- package/dist/store.js +208 -0
- package/dist/subscribers.d.ts +5 -0
- package/dist/subscribers.js +13 -0
- package/dist/table.d.ts +143 -0
- package/dist/table.js +91 -0
- package/dist/tables.d.ts +77 -0
- package/dist/tables.js +216 -0
- package/dist/theme/reset.css +84 -0
- package/dist/theme/scales.d.ts +76 -0
- package/dist/theme/scales.js +82 -0
- package/dist/theme/tokens.stylex.d.ts +218 -0
- package/dist/theme/tokens.stylex.js +233 -0
- package/dist/vite/commandBridge.d.ts +2 -0
- package/dist/vite/commandBridge.js +46 -0
- package/dist/vite/designManifest.d.ts +25 -0
- package/dist/vite/designManifest.js +191 -0
- package/dist/vite/localWorker.d.ts +4 -0
- package/dist/vite/localWorker.js +60 -0
- package/dist/vite/projectFile.d.ts +9 -0
- package/dist/vite/projectFile.js +17 -0
- package/dist/vite/rootAlias.d.ts +2 -0
- package/dist/vite/rootAlias.js +15 -0
- package/dist/vite/vite.d.ts +8 -0
- package/dist/vite/vite.js +95 -0
- package/dist/wire.d.ts +7 -0
- package/dist/wire.js +1 -0
- package/package.json +103 -3
- package/presets/lintPlugin.js +7 -0
- package/presets/oxlint.json +167 -0
- package/presets/register.d.ts +21 -0
- package/presets/server.d.ts +2 -0
- package/presets/tsconfig.app.json +29 -0
- package/presets/tsconfig.server.json +27 -0
package/dist/table.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const NAME = /^[a-z][a-zA-Z0-9]*$/;
|
|
3
|
+
const RESERVED = ['id', 'createdAt', 'ownerId'];
|
|
4
|
+
const RESERVED_TABLES = ['users', 'sessions', 'session'];
|
|
5
|
+
const SEEDED_AT = Date.UTC(2000, 0, 1);
|
|
6
|
+
function problemIn(name, definition) {
|
|
7
|
+
const { access, columns } = definition;
|
|
8
|
+
const known = Object.keys(columns);
|
|
9
|
+
if (access.create === 'owner') {
|
|
10
|
+
return `Table "${name}": create can't be 'owner', use 'signedIn'`;
|
|
11
|
+
}
|
|
12
|
+
if (definition.owned !== true && Object.values(access).includes('owner')) {
|
|
13
|
+
return `Table "${name}" uses 'owner' but is not owned: true`;
|
|
14
|
+
}
|
|
15
|
+
if (definition.owned === true && access.create === 'anyone') {
|
|
16
|
+
return `Table "${name}" is owned, so create must not be 'anyone'`;
|
|
17
|
+
}
|
|
18
|
+
const unknown = [
|
|
19
|
+
...(definition.public ?? []).filter((column) => column !== 'createdAt'),
|
|
20
|
+
...(definition.unique ?? []).flat()
|
|
21
|
+
].find((column) => !known.includes(column));
|
|
22
|
+
if (unknown !== undefined) {
|
|
23
|
+
return `Table "${name}" names "${unknown}", which is not one of its columns`;
|
|
24
|
+
}
|
|
25
|
+
if ((definition.unique ?? []).length > 0 &&
|
|
26
|
+
definition.writes !== 'confirmed') {
|
|
27
|
+
return `Table "${name}" has a unique constraint, so writes must be 'confirmed'`;
|
|
28
|
+
}
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
function table(name, definition) {
|
|
32
|
+
const { columns } = definition;
|
|
33
|
+
for (const key of [name, ...Object.keys(columns)]) {
|
|
34
|
+
if (!NAME.test(key)) {
|
|
35
|
+
throw new Error(`"${key}" can't name a table or column: use camelCase letters and digits`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (RESERVED_TABLES.includes(name)) {
|
|
39
|
+
throw new Error(`A table can't be called "${name}": Pattern keeps its own ${name} for sign-in`);
|
|
40
|
+
}
|
|
41
|
+
const reserved = RESERVED.find((key) => key in columns);
|
|
42
|
+
if (reserved !== undefined) {
|
|
43
|
+
throw new Error(`Table "${name}" can't have a "${reserved}" column: Pattern adds it`);
|
|
44
|
+
}
|
|
45
|
+
const problem = problemIn(name, definition);
|
|
46
|
+
if (problem !== undefined)
|
|
47
|
+
throw new Error(problem);
|
|
48
|
+
const owned = definition.owned === true;
|
|
49
|
+
const publicColumns = definition.public ?? [];
|
|
50
|
+
return {
|
|
51
|
+
name,
|
|
52
|
+
columns,
|
|
53
|
+
access: definition.access,
|
|
54
|
+
writes: definition.writes,
|
|
55
|
+
owned,
|
|
56
|
+
public: publicColumns,
|
|
57
|
+
unique: definition.unique ?? [],
|
|
58
|
+
fields: z.object(columns),
|
|
59
|
+
incoming: z.object({ id: z.string(), ...columns }),
|
|
60
|
+
row: z.object({
|
|
61
|
+
id: z.string(),
|
|
62
|
+
createdAt: z.string(),
|
|
63
|
+
...(owned ? { ownerId: z.string() } : {}),
|
|
64
|
+
...columns
|
|
65
|
+
}),
|
|
66
|
+
publicRow: z.object({
|
|
67
|
+
id: z.string(),
|
|
68
|
+
...Object.fromEntries(publicColumns.map((column) => [
|
|
69
|
+
column,
|
|
70
|
+
column === 'createdAt' ? z.string() : columns[column]
|
|
71
|
+
]))
|
|
72
|
+
})
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export function defineTables(definitions) {
|
|
76
|
+
return Object.fromEntries(Object.entries(definitions).map(([name, definition]) => [name, table(name, definition)]));
|
|
77
|
+
}
|
|
78
|
+
export function ownerIdOf(email) {
|
|
79
|
+
return `user-${email.toLowerCase()}`;
|
|
80
|
+
}
|
|
81
|
+
export function seededRows(seed, name) {
|
|
82
|
+
const rows = Reflect.get(seed, name) ?? [];
|
|
83
|
+
return rows.map(({ owner, ...row }, index) => ({
|
|
84
|
+
...row,
|
|
85
|
+
...(typeof owner === 'string' ? { ownerId: ownerIdOf(owner) } : {}),
|
|
86
|
+
createdAt: new Date(SEEDED_AT + index * 1000).toISOString()
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
export function snakeCase(name) {
|
|
90
|
+
return name.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
91
|
+
}
|
package/dist/tables.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { Viewer, WriteOutcome } from './access.ts';
|
|
2
|
+
import type { Cache, Cached } from './cache.ts';
|
|
3
|
+
import { EntityStore, entityCalls } from './entityStore.ts';
|
|
4
|
+
import type { EntityApi } from './entityStore.ts';
|
|
5
|
+
import type { Gate } from './gate.ts';
|
|
6
|
+
import type { MemoryApiOptions } from './memoryApi.ts';
|
|
7
|
+
import { StoreBase } from './store.ts';
|
|
8
|
+
import type { Fields, PublicRow, Row, Seed, Table, Tables } from './table.ts';
|
|
9
|
+
export type TableApi<T extends Table> = EntityApi<Row<T>> & {
|
|
10
|
+
listPublic: () => Promise<readonly PublicRow<T>[]>;
|
|
11
|
+
};
|
|
12
|
+
export type TablesApi<Group extends Tables> = {
|
|
13
|
+
[Key in keyof Group]: TableApi<Group[Key]>;
|
|
14
|
+
};
|
|
15
|
+
export type TableCall<Group extends Tables> = `${keyof Group & string}.${(typeof entityCalls)[number] | 'listPublic'}`;
|
|
16
|
+
export type CurrentViewer = () => Viewer | null;
|
|
17
|
+
export declare class HttpTableApi<T extends Table> implements TableApi<T> {
|
|
18
|
+
#private;
|
|
19
|
+
constructor(table: T, gate: Gate, onSignedOut: () => void, base?: string);
|
|
20
|
+
list(): Promise<readonly Row<T>[]>;
|
|
21
|
+
listPublic(): Promise<readonly PublicRow<T>[]>;
|
|
22
|
+
create(row: Row<T>): Promise<Row<T>>;
|
|
23
|
+
update(row: Row<T>): Promise<Row<T>>;
|
|
24
|
+
remove(id: string): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
type AnyRow = Readonly<Record<string, unknown>> & {
|
|
27
|
+
id: string;
|
|
28
|
+
ownerId?: string;
|
|
29
|
+
};
|
|
30
|
+
export declare class MemoryTableApi<T extends Table> implements TableApi<T> {
|
|
31
|
+
#private;
|
|
32
|
+
constructor(table: T, seed: readonly AnyRow[], viewer: CurrentViewer, options: MemoryApiOptions);
|
|
33
|
+
list(): Promise<readonly Row<T>[]>;
|
|
34
|
+
listPublic(): Promise<readonly PublicRow<T>[]>;
|
|
35
|
+
create(row: Row<T>): Promise<Row<T>>;
|
|
36
|
+
update(row: Row<T>): Promise<Row<T>>;
|
|
37
|
+
remove(id: string): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
export declare function httpTables<Group extends Tables>(tables: Group, gate: Gate, onSignedOut: () => void): TablesApi<Group>;
|
|
40
|
+
export declare function memoryTables<Group extends Tables>(tables: Group, seed: Seed<Group>, viewer: CurrentViewer, options: MemoryApiOptions): TablesApi<Group>;
|
|
41
|
+
export declare function tableCalls<Group extends Tables>(tables: Group): readonly TableCall<Group>[];
|
|
42
|
+
export declare function publicStoreName(table: string): string;
|
|
43
|
+
export type PublicSnapshot<Item> = {
|
|
44
|
+
status: 'idle' | 'loading' | 'ready' | 'failed';
|
|
45
|
+
items: readonly Item[];
|
|
46
|
+
};
|
|
47
|
+
type PublicDeps<Item> = {
|
|
48
|
+
list: () => Promise<readonly Item[]>;
|
|
49
|
+
cache: Cached<readonly Item[]>;
|
|
50
|
+
};
|
|
51
|
+
export declare class PublicTableStore<T extends Table> extends StoreBase<PublicSnapshot<PublicRow<T>>, PublicDeps<PublicRow<T>>> {
|
|
52
|
+
#private;
|
|
53
|
+
constructor(table: T, { api, cache }: {
|
|
54
|
+
api: TableApi<T>;
|
|
55
|
+
cache: Cache;
|
|
56
|
+
});
|
|
57
|
+
protected initialState(): PublicSnapshot<PublicRow<T>>;
|
|
58
|
+
load(): Promise<void>;
|
|
59
|
+
}
|
|
60
|
+
type TableDeps<T extends Table> = {
|
|
61
|
+
api: TableApi<T>;
|
|
62
|
+
cache: Cache;
|
|
63
|
+
viewer: CurrentViewer;
|
|
64
|
+
publicView?: {
|
|
65
|
+
load: () => Promise<void>;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
export declare class TableStore<T extends Table> extends EntityStore<Row<T>> {
|
|
69
|
+
#private;
|
|
70
|
+
constructor(table: T, { api, cache, viewer, publicView }: TableDeps<T>);
|
|
71
|
+
get mayRead(): boolean;
|
|
72
|
+
load(): Promise<void>;
|
|
73
|
+
create(fields: Fields<T>): Promise<WriteOutcome>;
|
|
74
|
+
update(row: Row<T>): Promise<WriteOutcome>;
|
|
75
|
+
remove(id: string): Promise<WriteOutcome>;
|
|
76
|
+
}
|
|
77
|
+
export {};
|
package/dist/tables.js
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { canSee, changeRefusal, clashesWith, createRefusal, listRefusal, refusalOutcome, publicRow, readScope } from "./access.js";
|
|
3
|
+
import { EntityStore, WriteRefused, entityCalls } from "./entityStore.js";
|
|
4
|
+
import { MemoryServer } from "./memoryApi.js";
|
|
5
|
+
import { StoreBase } from "./store.js";
|
|
6
|
+
import { seededRows } from "./table.js";
|
|
7
|
+
const STATUS_OUTCOMES = { 401: 'forbidden', 403: 'forbidden', 404: 'missing', 409: 'conflict' };
|
|
8
|
+
export class HttpTableApi {
|
|
9
|
+
#table;
|
|
10
|
+
#gate;
|
|
11
|
+
#url;
|
|
12
|
+
#onSignedOut;
|
|
13
|
+
constructor(table, gate, onSignedOut, base = '/api/tables') {
|
|
14
|
+
this.#table = table;
|
|
15
|
+
this.#gate = gate;
|
|
16
|
+
this.#onSignedOut = onSignedOut;
|
|
17
|
+
this.#url = `${base}/${table.name}`;
|
|
18
|
+
}
|
|
19
|
+
list() {
|
|
20
|
+
return this.#call('list', 'GET', '', undefined, z.array(this.#table.row));
|
|
21
|
+
}
|
|
22
|
+
listPublic() {
|
|
23
|
+
return this.#call('listPublic', 'GET', '/public', undefined, z.array(this.#table.publicRow));
|
|
24
|
+
}
|
|
25
|
+
create(row) {
|
|
26
|
+
return this.#call('create', 'POST', '', row, this.#table.row);
|
|
27
|
+
}
|
|
28
|
+
update(row) {
|
|
29
|
+
return this.#call('update', 'PUT', `/${row.id}`, row, this.#table.row);
|
|
30
|
+
}
|
|
31
|
+
async remove(id) {
|
|
32
|
+
await this.#call('remove', 'DELETE', `/${id}`, undefined, z.unknown());
|
|
33
|
+
}
|
|
34
|
+
#call(call, method, path, body, reply) {
|
|
35
|
+
const name = `${this.#table.name}.${call}`;
|
|
36
|
+
return this.#gate.pass(name, async () => {
|
|
37
|
+
const response = await fetch(`${this.#url}${path}`, {
|
|
38
|
+
method,
|
|
39
|
+
headers: { 'content-type': 'application/json' },
|
|
40
|
+
...(body === undefined ? {} : { body: JSON.stringify(body) })
|
|
41
|
+
});
|
|
42
|
+
if (response.status === 401)
|
|
43
|
+
this.#onSignedOut();
|
|
44
|
+
const refused = STATUS_OUTCOMES[response.status];
|
|
45
|
+
if (refused !== undefined && method !== 'GET') {
|
|
46
|
+
throw new WriteRefused(refused);
|
|
47
|
+
}
|
|
48
|
+
if (!response.ok)
|
|
49
|
+
throw new Error(`${name}: HTTP ${response.status}`);
|
|
50
|
+
return reply.parse(await response.json());
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export class MemoryTableApi {
|
|
55
|
+
#table;
|
|
56
|
+
#viewer;
|
|
57
|
+
#server;
|
|
58
|
+
#saved;
|
|
59
|
+
constructor(table, seed, viewer, options) {
|
|
60
|
+
this.#table = table;
|
|
61
|
+
this.#viewer = viewer;
|
|
62
|
+
this.#server = new MemoryServer(table.name, options);
|
|
63
|
+
this.#saved = seed;
|
|
64
|
+
}
|
|
65
|
+
list() {
|
|
66
|
+
return this.#server.reply('list', () => {
|
|
67
|
+
const viewer = this.#viewer();
|
|
68
|
+
if (listRefusal(this.#table, viewer) !== undefined) {
|
|
69
|
+
throw new Error(`${this.#table.name}.list: not allowed`);
|
|
70
|
+
}
|
|
71
|
+
return this.#saved.filter((row) => canSee(this.#table, viewer, row));
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
listPublic() {
|
|
75
|
+
return this.#server.reply('listPublic', () => this.#saved.map((row) => publicRow(this.#table, row)));
|
|
76
|
+
}
|
|
77
|
+
create(row) {
|
|
78
|
+
return this.#server.reply('create', () => {
|
|
79
|
+
const viewer = this.#viewer();
|
|
80
|
+
const refusal = createRefusal(this.#table, viewer);
|
|
81
|
+
if (refusal !== undefined)
|
|
82
|
+
throw new WriteRefused(refusalOutcome(refusal));
|
|
83
|
+
if (this.#table.owned && viewer === null) {
|
|
84
|
+
throw new WriteRefused('forbidden');
|
|
85
|
+
}
|
|
86
|
+
const saved = this.#table.owned && viewer !== null
|
|
87
|
+
? { ...row, ownerId: viewer.userId }
|
|
88
|
+
: { ...row };
|
|
89
|
+
this.#refuseClash(saved);
|
|
90
|
+
this.#saved = [...this.#saved, saved];
|
|
91
|
+
return saved;
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
update(row) {
|
|
95
|
+
return this.#server.reply('update', () => {
|
|
96
|
+
const existing = this.#allowedChange('update', row.id);
|
|
97
|
+
const saved = existing.ownerId === undefined
|
|
98
|
+
? { ...row }
|
|
99
|
+
: { ...row, ownerId: existing.ownerId };
|
|
100
|
+
this.#refuseClash(saved);
|
|
101
|
+
this.#saved = this.#saved.map((item) => item.id === saved.id ? saved : item);
|
|
102
|
+
return saved;
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
remove(id) {
|
|
106
|
+
return this.#server.reply('remove', () => {
|
|
107
|
+
this.#allowedChange('delete', id);
|
|
108
|
+
this.#saved = this.#saved.filter((item) => item.id !== id);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
#allowedChange(operation, id) {
|
|
112
|
+
const existing = this.#saved.find((item) => item.id === id);
|
|
113
|
+
const refusal = changeRefusal(this.#table, operation, this.#viewer(), existing);
|
|
114
|
+
if (refusal !== undefined || existing === undefined) {
|
|
115
|
+
throw new WriteRefused(refusalOutcome(refusal ?? 'missing'));
|
|
116
|
+
}
|
|
117
|
+
return existing;
|
|
118
|
+
}
|
|
119
|
+
#refuseClash(row) {
|
|
120
|
+
if (clashesWith(this.#table, this.#saved, row)) {
|
|
121
|
+
throw new WriteRefused('conflict');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function eachTable(tables, api) {
|
|
126
|
+
return Object.fromEntries(Object.entries(tables).map(([key, table]) => [key, api(table)]));
|
|
127
|
+
}
|
|
128
|
+
export function httpTables(tables, gate, onSignedOut) {
|
|
129
|
+
return eachTable(tables, (table) => new HttpTableApi(table, gate, onSignedOut));
|
|
130
|
+
}
|
|
131
|
+
export function memoryTables(tables, seed, viewer, options) {
|
|
132
|
+
return eachTable(tables, (table) => new MemoryTableApi(table, seededRows(seed, table.name), viewer, options));
|
|
133
|
+
}
|
|
134
|
+
export function tableCalls(tables) {
|
|
135
|
+
return Object.values(tables).flatMap((table) => [...entityCalls, ...(table.public.length > 0 ? ['listPublic'] : [])].map((call) => `${table.name}.${call}`));
|
|
136
|
+
}
|
|
137
|
+
export function publicStoreName(table) {
|
|
138
|
+
return `public${table.charAt(0).toUpperCase()}${table.slice(1)}`;
|
|
139
|
+
}
|
|
140
|
+
export class PublicTableStore extends StoreBase {
|
|
141
|
+
#loading;
|
|
142
|
+
constructor(table, { api, cache }) {
|
|
143
|
+
super({
|
|
144
|
+
list: () => api.listPublic(),
|
|
145
|
+
cache: cache.scope(`anon:${publicStoreName(table.name)}`, z.array(table.publicRow))
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
initialState() {
|
|
149
|
+
const cached = this.deps.cache.read();
|
|
150
|
+
return {
|
|
151
|
+
status: cached === undefined ? 'idle' : 'ready',
|
|
152
|
+
items: cached ?? []
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
load() {
|
|
156
|
+
this.#loading ??= this.#fetch().finally(() => {
|
|
157
|
+
this.#loading = undefined;
|
|
158
|
+
});
|
|
159
|
+
return this.#loading;
|
|
160
|
+
}
|
|
161
|
+
async #fetch() {
|
|
162
|
+
if (this.state.status !== 'ready')
|
|
163
|
+
this.set({ status: 'loading' });
|
|
164
|
+
try {
|
|
165
|
+
const items = await this.deps.list();
|
|
166
|
+
this.deps.cache.write(items);
|
|
167
|
+
this.set({ status: 'ready', items });
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
if (this.state.status === 'loading')
|
|
171
|
+
this.set({ status: 'failed' });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
export class TableStore extends EntityStore {
|
|
176
|
+
#table;
|
|
177
|
+
#viewer;
|
|
178
|
+
#publicView;
|
|
179
|
+
constructor(table, { api, cache, viewer, publicView }) {
|
|
180
|
+
super({
|
|
181
|
+
api,
|
|
182
|
+
writes: table.writes,
|
|
183
|
+
cache: cache.scope(() => `${viewer()?.userId ?? 'anon'}:${table.name}`, z.array(table.row))
|
|
184
|
+
});
|
|
185
|
+
this.#table = table;
|
|
186
|
+
this.#viewer = viewer;
|
|
187
|
+
this.#publicView = publicView;
|
|
188
|
+
}
|
|
189
|
+
get mayRead() {
|
|
190
|
+
return readScope(this.#table, this.#viewer()) !== 'none';
|
|
191
|
+
}
|
|
192
|
+
load() {
|
|
193
|
+
return this.mayRead ? super.load() : Promise.resolve();
|
|
194
|
+
}
|
|
195
|
+
async create(fields) {
|
|
196
|
+
const ownerId = this.#viewer()?.userId;
|
|
197
|
+
return this.#refreshPublic(await super.create({
|
|
198
|
+
...fields,
|
|
199
|
+
id: crypto.randomUUID(),
|
|
200
|
+
createdAt: new Date().toISOString(),
|
|
201
|
+
...(this.#table.owned && ownerId !== undefined ? { ownerId } : {})
|
|
202
|
+
}));
|
|
203
|
+
}
|
|
204
|
+
async update(row) {
|
|
205
|
+
return this.#refreshPublic(await super.update(row));
|
|
206
|
+
}
|
|
207
|
+
async remove(id) {
|
|
208
|
+
return this.#refreshPublic(await super.remove(id));
|
|
209
|
+
}
|
|
210
|
+
#refreshPublic(outcome) {
|
|
211
|
+
if (outcome === 'saved' || outcome === 'conflict') {
|
|
212
|
+
void this.#publicView?.load();
|
|
213
|
+
}
|
|
214
|
+
return outcome;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
@layer reset {
|
|
2
|
+
:root {
|
|
3
|
+
color-scheme: light dark;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
*,
|
|
7
|
+
*::before,
|
|
8
|
+
*::after {
|
|
9
|
+
box-sizing: border-box;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
html,
|
|
13
|
+
body,
|
|
14
|
+
#root {
|
|
15
|
+
height: 100%;
|
|
16
|
+
overflow: hidden;
|
|
17
|
+
overscroll-behavior: none;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
body {
|
|
21
|
+
-webkit-user-select: none;
|
|
22
|
+
user-select: none;
|
|
23
|
+
-webkit-touch-callout: none;
|
|
24
|
+
-webkit-tap-highlight-color: transparent;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
input,
|
|
28
|
+
textarea,
|
|
29
|
+
[contenteditable]:not([contenteditable='false']) {
|
|
30
|
+
-webkit-user-select: text;
|
|
31
|
+
user-select: text;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
a,
|
|
35
|
+
button,
|
|
36
|
+
label,
|
|
37
|
+
select,
|
|
38
|
+
summary,
|
|
39
|
+
[role='button'],
|
|
40
|
+
[role='link'],
|
|
41
|
+
input[type='button'],
|
|
42
|
+
input[type='checkbox'],
|
|
43
|
+
input[type='color'],
|
|
44
|
+
input[type='file'],
|
|
45
|
+
input[type='radio'],
|
|
46
|
+
input[type='range'],
|
|
47
|
+
input[type='reset'],
|
|
48
|
+
input[type='submit'] {
|
|
49
|
+
cursor: default;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
button {
|
|
53
|
+
padding: 0;
|
|
54
|
+
font: inherit;
|
|
55
|
+
color: inherit;
|
|
56
|
+
background: none;
|
|
57
|
+
border: 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
h1,
|
|
61
|
+
h2,
|
|
62
|
+
h3,
|
|
63
|
+
h4,
|
|
64
|
+
h5,
|
|
65
|
+
h6 {
|
|
66
|
+
font-size: inherit;
|
|
67
|
+
font-weight: inherit;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
body,
|
|
71
|
+
h1,
|
|
72
|
+
h2,
|
|
73
|
+
h3,
|
|
74
|
+
h4,
|
|
75
|
+
h5,
|
|
76
|
+
h6,
|
|
77
|
+
p,
|
|
78
|
+
figure,
|
|
79
|
+
blockquote,
|
|
80
|
+
ul,
|
|
81
|
+
ol {
|
|
82
|
+
margin: 0;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export declare const ascendingScales: {
|
|
2
|
+
readonly space: readonly ["xxs", "xs", "sm", "md", "lg", "xl", "xxl", "xxxl"];
|
|
3
|
+
readonly controlHeights: readonly ["heightXs", "heightSm", "heightMd", "heightLg", "heightXl"];
|
|
4
|
+
readonly controlPaddings: readonly ["paddingXs", "paddingSm", "paddingMd", "paddingLg", "paddingXl"];
|
|
5
|
+
readonly controlGaps: readonly ["gapXs", "gapSm", "gapMd", "gapLg", "gapXl"];
|
|
6
|
+
readonly contentWidths: readonly ["contentXs", "contentSm", "contentMd", "contentLg"];
|
|
7
|
+
readonly iconSizes: readonly ["xs", "sm", "md", "lg", "xl"];
|
|
8
|
+
readonly fontSizes: readonly ["captionXs", "captionSm", "captionBase", "captionLg", "bodySm", "bodyBase", "bodyLg", "heading6", "heading5", "heading4", "heading3", "heading2", "heading1"];
|
|
9
|
+
readonly lineHeights: readonly ["captionXs", "caption", "title", "bodySm", "body", "heading6", "heading5", "heading4", "heading3", "heading2", "heading1"];
|
|
10
|
+
};
|
|
11
|
+
export declare const scaleGroups: {
|
|
12
|
+
readonly space: "space";
|
|
13
|
+
readonly controlHeights: "controls";
|
|
14
|
+
readonly controlPaddings: "controls";
|
|
15
|
+
readonly controlGaps: "controls";
|
|
16
|
+
readonly contentWidths: "widths";
|
|
17
|
+
readonly iconSizes: "iconSizes";
|
|
18
|
+
readonly fontSizes: "fontSizes";
|
|
19
|
+
readonly lineHeights: "lineHeights";
|
|
20
|
+
};
|
|
21
|
+
export declare const textInControls: readonly [{
|
|
22
|
+
readonly size: "xs";
|
|
23
|
+
readonly height: "heightXs";
|
|
24
|
+
readonly lineHeight: "caption";
|
|
25
|
+
}, {
|
|
26
|
+
readonly size: "sm";
|
|
27
|
+
readonly height: "heightSm";
|
|
28
|
+
readonly lineHeight: "bodySm";
|
|
29
|
+
}, {
|
|
30
|
+
readonly size: "md";
|
|
31
|
+
readonly height: "heightMd";
|
|
32
|
+
readonly lineHeight: "bodySm";
|
|
33
|
+
}, {
|
|
34
|
+
readonly size: "lg";
|
|
35
|
+
readonly height: "heightLg";
|
|
36
|
+
readonly lineHeight: "bodySm";
|
|
37
|
+
}, {
|
|
38
|
+
readonly size: "xl";
|
|
39
|
+
readonly height: "heightXl";
|
|
40
|
+
readonly lineHeight: "body";
|
|
41
|
+
}];
|
|
42
|
+
export declare const textLines: readonly [{
|
|
43
|
+
readonly lineHeight: "captionXs";
|
|
44
|
+
readonly fontSize: "captionXs";
|
|
45
|
+
}, {
|
|
46
|
+
readonly lineHeight: "caption";
|
|
47
|
+
readonly fontSize: "captionLg";
|
|
48
|
+
}, {
|
|
49
|
+
readonly lineHeight: "title";
|
|
50
|
+
readonly fontSize: "bodyBase";
|
|
51
|
+
}, {
|
|
52
|
+
readonly lineHeight: "bodySm";
|
|
53
|
+
readonly fontSize: "bodySm";
|
|
54
|
+
}, {
|
|
55
|
+
readonly lineHeight: "body";
|
|
56
|
+
readonly fontSize: "bodyBase";
|
|
57
|
+
}, {
|
|
58
|
+
readonly lineHeight: "heading6";
|
|
59
|
+
readonly fontSize: "heading6";
|
|
60
|
+
}, {
|
|
61
|
+
readonly lineHeight: "heading5";
|
|
62
|
+
readonly fontSize: "heading5";
|
|
63
|
+
}, {
|
|
64
|
+
readonly lineHeight: "heading4";
|
|
65
|
+
readonly fontSize: "heading4";
|
|
66
|
+
}, {
|
|
67
|
+
readonly lineHeight: "heading3";
|
|
68
|
+
readonly fontSize: "heading3";
|
|
69
|
+
}, {
|
|
70
|
+
readonly lineHeight: "heading2";
|
|
71
|
+
readonly fontSize: "heading2";
|
|
72
|
+
}, {
|
|
73
|
+
readonly lineHeight: "heading1";
|
|
74
|
+
readonly fontSize: "heading1";
|
|
75
|
+
}];
|
|
76
|
+
export declare const pixelGroups: readonly ["space", "controls", "iconSizes", "fontSizes", "lineHeights", "borders", "radii", "widths"];
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export const ascendingScales = {
|
|
2
|
+
space: ['xxs', 'xs', 'sm', 'md', 'lg', 'xl', 'xxl', 'xxxl'],
|
|
3
|
+
controlHeights: ['heightXs', 'heightSm', 'heightMd', 'heightLg', 'heightXl'],
|
|
4
|
+
controlPaddings: [
|
|
5
|
+
'paddingXs',
|
|
6
|
+
'paddingSm',
|
|
7
|
+
'paddingMd',
|
|
8
|
+
'paddingLg',
|
|
9
|
+
'paddingXl'
|
|
10
|
+
],
|
|
11
|
+
controlGaps: ['gapXs', 'gapSm', 'gapMd', 'gapLg', 'gapXl'],
|
|
12
|
+
contentWidths: ['contentXs', 'contentSm', 'contentMd', 'contentLg'],
|
|
13
|
+
iconSizes: ['xs', 'sm', 'md', 'lg', 'xl'],
|
|
14
|
+
fontSizes: [
|
|
15
|
+
'captionXs',
|
|
16
|
+
'captionSm',
|
|
17
|
+
'captionBase',
|
|
18
|
+
'captionLg',
|
|
19
|
+
'bodySm',
|
|
20
|
+
'bodyBase',
|
|
21
|
+
'bodyLg',
|
|
22
|
+
'heading6',
|
|
23
|
+
'heading5',
|
|
24
|
+
'heading4',
|
|
25
|
+
'heading3',
|
|
26
|
+
'heading2',
|
|
27
|
+
'heading1'
|
|
28
|
+
],
|
|
29
|
+
lineHeights: [
|
|
30
|
+
'captionXs',
|
|
31
|
+
'caption',
|
|
32
|
+
'title',
|
|
33
|
+
'bodySm',
|
|
34
|
+
'body',
|
|
35
|
+
'heading6',
|
|
36
|
+
'heading5',
|
|
37
|
+
'heading4',
|
|
38
|
+
'heading3',
|
|
39
|
+
'heading2',
|
|
40
|
+
'heading1'
|
|
41
|
+
]
|
|
42
|
+
};
|
|
43
|
+
export const scaleGroups = {
|
|
44
|
+
space: 'space',
|
|
45
|
+
controlHeights: 'controls',
|
|
46
|
+
controlPaddings: 'controls',
|
|
47
|
+
controlGaps: 'controls',
|
|
48
|
+
contentWidths: 'widths',
|
|
49
|
+
iconSizes: 'iconSizes',
|
|
50
|
+
fontSizes: 'fontSizes',
|
|
51
|
+
lineHeights: 'lineHeights'
|
|
52
|
+
};
|
|
53
|
+
export const textInControls = [
|
|
54
|
+
{ size: 'xs', height: 'heightXs', lineHeight: 'caption' },
|
|
55
|
+
{ size: 'sm', height: 'heightSm', lineHeight: 'bodySm' },
|
|
56
|
+
{ size: 'md', height: 'heightMd', lineHeight: 'bodySm' },
|
|
57
|
+
{ size: 'lg', height: 'heightLg', lineHeight: 'bodySm' },
|
|
58
|
+
{ size: 'xl', height: 'heightXl', lineHeight: 'body' }
|
|
59
|
+
];
|
|
60
|
+
export const textLines = [
|
|
61
|
+
{ lineHeight: 'captionXs', fontSize: 'captionXs' },
|
|
62
|
+
{ lineHeight: 'caption', fontSize: 'captionLg' },
|
|
63
|
+
{ lineHeight: 'title', fontSize: 'bodyBase' },
|
|
64
|
+
{ lineHeight: 'bodySm', fontSize: 'bodySm' },
|
|
65
|
+
{ lineHeight: 'body', fontSize: 'bodyBase' },
|
|
66
|
+
{ lineHeight: 'heading6', fontSize: 'heading6' },
|
|
67
|
+
{ lineHeight: 'heading5', fontSize: 'heading5' },
|
|
68
|
+
{ lineHeight: 'heading4', fontSize: 'heading4' },
|
|
69
|
+
{ lineHeight: 'heading3', fontSize: 'heading3' },
|
|
70
|
+
{ lineHeight: 'heading2', fontSize: 'heading2' },
|
|
71
|
+
{ lineHeight: 'heading1', fontSize: 'heading1' }
|
|
72
|
+
];
|
|
73
|
+
export const pixelGroups = [
|
|
74
|
+
'space',
|
|
75
|
+
'controls',
|
|
76
|
+
'iconSizes',
|
|
77
|
+
'fontSizes',
|
|
78
|
+
'lineHeights',
|
|
79
|
+
'borders',
|
|
80
|
+
'radii',
|
|
81
|
+
'widths'
|
|
82
|
+
];
|