@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
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { asc, eq, sql } from 'drizzle-orm';
|
|
2
|
+
import { drizzle } from 'drizzle-orm/d1';
|
|
3
|
+
import { integer, real, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core';
|
|
4
|
+
import { Hono } from 'hono';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { changeRefusal, createRefusal, listRefusal, readScope } from "../access.js";
|
|
7
|
+
import { ownerIdOf, seededRows, snakeCase } from "../table.js";
|
|
8
|
+
function typesOf(schema) {
|
|
9
|
+
const { type } = z.toJSONSchema(schema);
|
|
10
|
+
if (typeof type === 'string')
|
|
11
|
+
return [type];
|
|
12
|
+
return Array.isArray(type) ? type : [];
|
|
13
|
+
}
|
|
14
|
+
function sqlColumn(key, schema) {
|
|
15
|
+
const types = typesOf(schema);
|
|
16
|
+
const name = snakeCase(key);
|
|
17
|
+
const column = types.includes('boolean')
|
|
18
|
+
? integer(name, { mode: 'boolean' })
|
|
19
|
+
: types.includes('integer')
|
|
20
|
+
? integer(name)
|
|
21
|
+
: types.includes('number')
|
|
22
|
+
? real(name)
|
|
23
|
+
: text(name);
|
|
24
|
+
return types.includes('null') ? column : column.notNull();
|
|
25
|
+
}
|
|
26
|
+
function uniqueIndexName(table, columns) {
|
|
27
|
+
return [table.name, ...columns, 'unique'].map(snakeCase).join('_');
|
|
28
|
+
}
|
|
29
|
+
export function sqlTable(table) {
|
|
30
|
+
const columns = Object.fromEntries(Object.entries(table.columns).map(([key, schema]) => [
|
|
31
|
+
key,
|
|
32
|
+
sqlColumn(key, schema)
|
|
33
|
+
]));
|
|
34
|
+
return sqliteTable(snakeCase(table.name), {
|
|
35
|
+
id: text('id').primaryKey(),
|
|
36
|
+
...(table.owned ? { ownerId: text('owner_id').notNull() } : {}),
|
|
37
|
+
...columns,
|
|
38
|
+
createdAt: text('created_at')
|
|
39
|
+
.notNull()
|
|
40
|
+
.default(sql `(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`)
|
|
41
|
+
}, (target) => table.unique.map((unique) => {
|
|
42
|
+
const [first, ...rest] = unique.map((key) => Reflect.get(target, key));
|
|
43
|
+
return uniqueIndex(uniqueIndexName(table, unique)).on(first, ...rest);
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
export function sqlTables(tables) {
|
|
47
|
+
return Object.fromEntries(Object.entries(tables).map(([key, table]) => [key, sqlTable(table)]));
|
|
48
|
+
}
|
|
49
|
+
function column(target, key) {
|
|
50
|
+
const found = Reflect.get(target, key);
|
|
51
|
+
if (found === undefined)
|
|
52
|
+
throw new Error(`No column ${key}`);
|
|
53
|
+
return found;
|
|
54
|
+
}
|
|
55
|
+
function sqlLiteral(value) {
|
|
56
|
+
if (value === null)
|
|
57
|
+
return 'NULL';
|
|
58
|
+
if (typeof value === 'boolean')
|
|
59
|
+
return value ? '1' : '0';
|
|
60
|
+
if (typeof value === 'number')
|
|
61
|
+
return String(value);
|
|
62
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
63
|
+
}
|
|
64
|
+
function seedOwners(seed) {
|
|
65
|
+
const owners = Object.values(seed).flatMap((rows) => (rows ?? []).flatMap((row) => {
|
|
66
|
+
const owner = Reflect.get(row, 'owner');
|
|
67
|
+
return typeof owner === 'string' ? [owner.toLowerCase()] : [];
|
|
68
|
+
}));
|
|
69
|
+
return [...new Set(owners)];
|
|
70
|
+
}
|
|
71
|
+
function ownerSql(email) {
|
|
72
|
+
return `(SELECT id FROM users WHERE email = ${sqlLiteral(email)})`;
|
|
73
|
+
}
|
|
74
|
+
export function seedSql(tables, seed) {
|
|
75
|
+
const users = seedOwners(seed).map((email) => `INSERT OR IGNORE INTO users (id, email, name) VALUES (${sqlLiteral(ownerIdOf(email))}, ${sqlLiteral(email)}, ${sqlLiteral(email.split('@')[0] ?? email)});`);
|
|
76
|
+
const rowsSql = Object.values(tables).flatMap((table) => {
|
|
77
|
+
const rows = seededRows(seed, table.name);
|
|
78
|
+
if (rows.length === 0)
|
|
79
|
+
return [];
|
|
80
|
+
const keys = ['id', 'createdAt', ...Object.keys(table.columns)];
|
|
81
|
+
const source = Reflect.get(seed, table.name);
|
|
82
|
+
const owners = (Array.isArray(source) ? source : []).map((raw) => {
|
|
83
|
+
const owner = Reflect.get(Object(raw), 'owner');
|
|
84
|
+
return typeof owner === 'string' ? owner.toLowerCase() : '';
|
|
85
|
+
});
|
|
86
|
+
const values = rows
|
|
87
|
+
.map((row, index) => `(${[
|
|
88
|
+
...(table.owned ? [ownerSql(owners[index] ?? '')] : []),
|
|
89
|
+
...keys.map((key) => sqlLiteral((row[key] ?? null)))
|
|
90
|
+
].join(', ')})`)
|
|
91
|
+
.join(',\n ');
|
|
92
|
+
const names = [...(table.owned ? ['ownerId'] : []), ...keys];
|
|
93
|
+
return [
|
|
94
|
+
`INSERT INTO ${snakeCase(table.name)} (${names.map(snakeCase).join(', ')})`,
|
|
95
|
+
`SELECT * FROM (VALUES\n ${values}\n)`,
|
|
96
|
+
`WHERE NOT EXISTS (SELECT 1 FROM ${snakeCase(table.name)});`
|
|
97
|
+
];
|
|
98
|
+
});
|
|
99
|
+
return [...users, ...rowsSql].join('\n');
|
|
100
|
+
}
|
|
101
|
+
const REFUSAL_STATUSES = {
|
|
102
|
+
signedOut: { status: 401, error: 'Signed out' },
|
|
103
|
+
forbidden: { status: 403, error: 'forbidden' },
|
|
104
|
+
missing: { status: 404, error: 'No such row' }
|
|
105
|
+
};
|
|
106
|
+
function refuse(c, refusal) {
|
|
107
|
+
const { status, error } = REFUSAL_STATUSES[refusal];
|
|
108
|
+
return c.json({ error }, status);
|
|
109
|
+
}
|
|
110
|
+
function isUniqueViolation(failure) {
|
|
111
|
+
for (let error = failure; error instanceof Error; error = error.cause) {
|
|
112
|
+
if (error.message.includes('UNIQUE constraint failed'))
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
export function serveTables(tables) {
|
|
118
|
+
const known = new Map(Object.values(tables).map((table) => [
|
|
119
|
+
table.name,
|
|
120
|
+
{ table, target: sqlTable(table) }
|
|
121
|
+
]));
|
|
122
|
+
function selection(table, target) {
|
|
123
|
+
return Object.fromEntries([
|
|
124
|
+
'id',
|
|
125
|
+
'createdAt',
|
|
126
|
+
...(table.owned ? ['ownerId'] : []),
|
|
127
|
+
...Object.keys(table.columns)
|
|
128
|
+
].map((key) => [key, column(target, key)]));
|
|
129
|
+
}
|
|
130
|
+
function publicSelection(table, target) {
|
|
131
|
+
return Object.fromEntries(['id', ...table.public].map((key) => [key, column(target, key)]));
|
|
132
|
+
}
|
|
133
|
+
async function findRow(c, table, target) {
|
|
134
|
+
const row = await drizzle(c.env.DB)
|
|
135
|
+
.select(selection(table, target))
|
|
136
|
+
.from(target)
|
|
137
|
+
.where(eq(column(target, 'id'), c.req.param('id') ?? ''))
|
|
138
|
+
.get();
|
|
139
|
+
return row;
|
|
140
|
+
}
|
|
141
|
+
async function saveOrConflict(c, table, save) {
|
|
142
|
+
try {
|
|
143
|
+
return { saved: await save() };
|
|
144
|
+
}
|
|
145
|
+
catch (failure) {
|
|
146
|
+
if (!isUniqueViolation(failure))
|
|
147
|
+
throw failure;
|
|
148
|
+
return {
|
|
149
|
+
conflict: c.json({ error: 'conflict', unique: table.unique }, 409)
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return new Hono()
|
|
154
|
+
.get('/:table', async (c) => {
|
|
155
|
+
const entry = known.get(c.req.param('table'));
|
|
156
|
+
if (entry === undefined)
|
|
157
|
+
return c.json({ error: 'No such table' }, 404);
|
|
158
|
+
const { table, target } = entry;
|
|
159
|
+
const viewer = c.get('viewer');
|
|
160
|
+
const refusal = listRefusal(table, viewer);
|
|
161
|
+
if (refusal !== undefined)
|
|
162
|
+
return refuse(c, refusal);
|
|
163
|
+
const scope = readScope(table, viewer);
|
|
164
|
+
const query = drizzle(c.env.DB)
|
|
165
|
+
.select(selection(table, target))
|
|
166
|
+
.from(target);
|
|
167
|
+
const rows = await (typeof scope === 'object'
|
|
168
|
+
? query.where(eq(column(target, 'ownerId'), scope.ownerId))
|
|
169
|
+
: query).orderBy(asc(column(target, 'createdAt')), asc(column(target, 'id')));
|
|
170
|
+
return c.json(rows);
|
|
171
|
+
})
|
|
172
|
+
.get('/:table/public', async (c) => {
|
|
173
|
+
const entry = known.get(c.req.param('table'));
|
|
174
|
+
if (entry === undefined || entry.table.public.length === 0) {
|
|
175
|
+
return c.json({ error: 'No such table' }, 404);
|
|
176
|
+
}
|
|
177
|
+
const { table, target } = entry;
|
|
178
|
+
const rows = await drizzle(c.env.DB)
|
|
179
|
+
.select(publicSelection(table, target))
|
|
180
|
+
.from(target)
|
|
181
|
+
.orderBy(asc(column(target, 'createdAt')), asc(column(target, 'id')));
|
|
182
|
+
return c.json(rows);
|
|
183
|
+
})
|
|
184
|
+
.post('/:table', async (c) => {
|
|
185
|
+
const entry = known.get(c.req.param('table'));
|
|
186
|
+
if (entry === undefined)
|
|
187
|
+
return c.json({ error: 'No such table' }, 404);
|
|
188
|
+
const { table, target } = entry;
|
|
189
|
+
const viewer = c.get('viewer');
|
|
190
|
+
const refusal = createRefusal(table, viewer);
|
|
191
|
+
if (refusal !== undefined)
|
|
192
|
+
return refuse(c, refusal);
|
|
193
|
+
const parsed = table.incoming.safeParse(await c.req.json());
|
|
194
|
+
if (!parsed.success) {
|
|
195
|
+
return c.json({ error: z.prettifyError(parsed.error) }, 400);
|
|
196
|
+
}
|
|
197
|
+
const values = table.owned && viewer !== null
|
|
198
|
+
? { ...parsed.data, ownerId: viewer.userId }
|
|
199
|
+
: parsed.data;
|
|
200
|
+
const result = await saveOrConflict(c, table, () => drizzle(c.env.DB)
|
|
201
|
+
.insert(target)
|
|
202
|
+
.values(values)
|
|
203
|
+
.returning(selection(table, target))
|
|
204
|
+
.get());
|
|
205
|
+
return 'conflict' in result ? result.conflict : c.json(result.saved, 201);
|
|
206
|
+
})
|
|
207
|
+
.put('/:table/:id', async (c) => {
|
|
208
|
+
const entry = known.get(c.req.param('table'));
|
|
209
|
+
if (entry === undefined)
|
|
210
|
+
return c.json({ error: 'No such table' }, 404);
|
|
211
|
+
const { table, target } = entry;
|
|
212
|
+
const existing = await findRow(c, table, target);
|
|
213
|
+
const refusal = changeRefusal(table, 'update', c.get('viewer'), existing);
|
|
214
|
+
if (refusal !== undefined)
|
|
215
|
+
return refuse(c, refusal);
|
|
216
|
+
const parsed = table.incoming.safeParse(await c.req.json());
|
|
217
|
+
if (!parsed.success) {
|
|
218
|
+
return c.json({ error: z.prettifyError(parsed.error) }, 400);
|
|
219
|
+
}
|
|
220
|
+
const { id, ...changes } = parsed.data;
|
|
221
|
+
if (id !== c.req.param('id')) {
|
|
222
|
+
return c.json({ error: 'The row id does not match the URL' }, 400);
|
|
223
|
+
}
|
|
224
|
+
const result = await saveOrConflict(c, table, () => drizzle(c.env.DB)
|
|
225
|
+
.update(target)
|
|
226
|
+
.set(changes)
|
|
227
|
+
.where(eq(column(target, 'id'), id))
|
|
228
|
+
.returning(selection(table, target))
|
|
229
|
+
.get());
|
|
230
|
+
return 'conflict' in result ? result.conflict : c.json(result.saved, 200);
|
|
231
|
+
})
|
|
232
|
+
.delete('/:table/:id', async (c) => {
|
|
233
|
+
const entry = known.get(c.req.param('table'));
|
|
234
|
+
if (entry === undefined)
|
|
235
|
+
return c.json({ error: 'No such table' }, 404);
|
|
236
|
+
const { table, target } = entry;
|
|
237
|
+
const existing = await findRow(c, table, target);
|
|
238
|
+
const refusal = changeRefusal(table, 'delete', c.get('viewer'), existing);
|
|
239
|
+
if (refusal !== undefined)
|
|
240
|
+
return refuse(c, refusal);
|
|
241
|
+
await drizzle(c.env.DB)
|
|
242
|
+
.delete(target)
|
|
243
|
+
.where(eq(column(target, 'id'), c.req.param('id')));
|
|
244
|
+
return c.json({ ok: true });
|
|
245
|
+
});
|
|
246
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import type { D1Database } from '@cloudflare/workers-types';
|
|
2
|
+
import type { StoredUser } from '../auth/user.ts';
|
|
3
|
+
export declare const users: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
|
|
4
|
+
name: "users";
|
|
5
|
+
schema: undefined;
|
|
6
|
+
columns: {
|
|
7
|
+
id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
8
|
+
name: "id";
|
|
9
|
+
tableName: "users";
|
|
10
|
+
dataType: "string";
|
|
11
|
+
columnType: "SQLiteText";
|
|
12
|
+
data: string;
|
|
13
|
+
driverParam: string;
|
|
14
|
+
notNull: true;
|
|
15
|
+
hasDefault: false;
|
|
16
|
+
isPrimaryKey: true;
|
|
17
|
+
isAutoincrement: false;
|
|
18
|
+
hasRuntimeDefault: false;
|
|
19
|
+
enumValues: [string, ...string[]];
|
|
20
|
+
baseColumn: never;
|
|
21
|
+
identity: undefined;
|
|
22
|
+
generated: undefined;
|
|
23
|
+
}, {}, {
|
|
24
|
+
length: number | undefined;
|
|
25
|
+
}>;
|
|
26
|
+
email: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
27
|
+
name: "email";
|
|
28
|
+
tableName: "users";
|
|
29
|
+
dataType: "string";
|
|
30
|
+
columnType: "SQLiteText";
|
|
31
|
+
data: string;
|
|
32
|
+
driverParam: string;
|
|
33
|
+
notNull: true;
|
|
34
|
+
hasDefault: false;
|
|
35
|
+
isPrimaryKey: false;
|
|
36
|
+
isAutoincrement: false;
|
|
37
|
+
hasRuntimeDefault: false;
|
|
38
|
+
enumValues: [string, ...string[]];
|
|
39
|
+
baseColumn: never;
|
|
40
|
+
identity: undefined;
|
|
41
|
+
generated: undefined;
|
|
42
|
+
}, {}, {
|
|
43
|
+
length: number | undefined;
|
|
44
|
+
}>;
|
|
45
|
+
name: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
46
|
+
name: "name";
|
|
47
|
+
tableName: "users";
|
|
48
|
+
dataType: "string";
|
|
49
|
+
columnType: "SQLiteText";
|
|
50
|
+
data: string;
|
|
51
|
+
driverParam: string;
|
|
52
|
+
notNull: true;
|
|
53
|
+
hasDefault: false;
|
|
54
|
+
isPrimaryKey: false;
|
|
55
|
+
isAutoincrement: false;
|
|
56
|
+
hasRuntimeDefault: false;
|
|
57
|
+
enumValues: [string, ...string[]];
|
|
58
|
+
baseColumn: never;
|
|
59
|
+
identity: undefined;
|
|
60
|
+
generated: undefined;
|
|
61
|
+
}, {}, {
|
|
62
|
+
length: number | undefined;
|
|
63
|
+
}>;
|
|
64
|
+
avatarUrl: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
65
|
+
name: "avatar_url";
|
|
66
|
+
tableName: "users";
|
|
67
|
+
dataType: "string";
|
|
68
|
+
columnType: "SQLiteText";
|
|
69
|
+
data: string;
|
|
70
|
+
driverParam: string;
|
|
71
|
+
notNull: false;
|
|
72
|
+
hasDefault: false;
|
|
73
|
+
isPrimaryKey: false;
|
|
74
|
+
isAutoincrement: false;
|
|
75
|
+
hasRuntimeDefault: false;
|
|
76
|
+
enumValues: [string, ...string[]];
|
|
77
|
+
baseColumn: never;
|
|
78
|
+
identity: undefined;
|
|
79
|
+
generated: undefined;
|
|
80
|
+
}, {}, {
|
|
81
|
+
length: number | undefined;
|
|
82
|
+
}>;
|
|
83
|
+
createdAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
84
|
+
name: "created_at";
|
|
85
|
+
tableName: "users";
|
|
86
|
+
dataType: "string";
|
|
87
|
+
columnType: "SQLiteText";
|
|
88
|
+
data: string;
|
|
89
|
+
driverParam: string;
|
|
90
|
+
notNull: true;
|
|
91
|
+
hasDefault: true;
|
|
92
|
+
isPrimaryKey: false;
|
|
93
|
+
isAutoincrement: false;
|
|
94
|
+
hasRuntimeDefault: false;
|
|
95
|
+
enumValues: [string, ...string[]];
|
|
96
|
+
baseColumn: never;
|
|
97
|
+
identity: undefined;
|
|
98
|
+
generated: undefined;
|
|
99
|
+
}, {}, {
|
|
100
|
+
length: number | undefined;
|
|
101
|
+
}>;
|
|
102
|
+
};
|
|
103
|
+
dialect: "sqlite";
|
|
104
|
+
}>;
|
|
105
|
+
export declare const userColumns: {
|
|
106
|
+
id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
107
|
+
name: "id";
|
|
108
|
+
tableName: "users";
|
|
109
|
+
dataType: "string";
|
|
110
|
+
columnType: "SQLiteText";
|
|
111
|
+
data: string;
|
|
112
|
+
driverParam: string;
|
|
113
|
+
notNull: true;
|
|
114
|
+
hasDefault: false;
|
|
115
|
+
isPrimaryKey: true;
|
|
116
|
+
isAutoincrement: false;
|
|
117
|
+
hasRuntimeDefault: false;
|
|
118
|
+
enumValues: [string, ...string[]];
|
|
119
|
+
baseColumn: never;
|
|
120
|
+
identity: undefined;
|
|
121
|
+
generated: undefined;
|
|
122
|
+
}, {}, {
|
|
123
|
+
length: number | undefined;
|
|
124
|
+
}>;
|
|
125
|
+
email: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
126
|
+
name: "email";
|
|
127
|
+
tableName: "users";
|
|
128
|
+
dataType: "string";
|
|
129
|
+
columnType: "SQLiteText";
|
|
130
|
+
data: string;
|
|
131
|
+
driverParam: string;
|
|
132
|
+
notNull: true;
|
|
133
|
+
hasDefault: false;
|
|
134
|
+
isPrimaryKey: false;
|
|
135
|
+
isAutoincrement: false;
|
|
136
|
+
hasRuntimeDefault: false;
|
|
137
|
+
enumValues: [string, ...string[]];
|
|
138
|
+
baseColumn: never;
|
|
139
|
+
identity: undefined;
|
|
140
|
+
generated: undefined;
|
|
141
|
+
}, {}, {
|
|
142
|
+
length: number | undefined;
|
|
143
|
+
}>;
|
|
144
|
+
name: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
145
|
+
name: "name";
|
|
146
|
+
tableName: "users";
|
|
147
|
+
dataType: "string";
|
|
148
|
+
columnType: "SQLiteText";
|
|
149
|
+
data: string;
|
|
150
|
+
driverParam: string;
|
|
151
|
+
notNull: true;
|
|
152
|
+
hasDefault: false;
|
|
153
|
+
isPrimaryKey: false;
|
|
154
|
+
isAutoincrement: false;
|
|
155
|
+
hasRuntimeDefault: false;
|
|
156
|
+
enumValues: [string, ...string[]];
|
|
157
|
+
baseColumn: never;
|
|
158
|
+
identity: undefined;
|
|
159
|
+
generated: undefined;
|
|
160
|
+
}, {}, {
|
|
161
|
+
length: number | undefined;
|
|
162
|
+
}>;
|
|
163
|
+
avatarUrl: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
164
|
+
name: "avatar_url";
|
|
165
|
+
tableName: "users";
|
|
166
|
+
dataType: "string";
|
|
167
|
+
columnType: "SQLiteText";
|
|
168
|
+
data: string;
|
|
169
|
+
driverParam: string;
|
|
170
|
+
notNull: false;
|
|
171
|
+
hasDefault: false;
|
|
172
|
+
isPrimaryKey: false;
|
|
173
|
+
isAutoincrement: false;
|
|
174
|
+
hasRuntimeDefault: false;
|
|
175
|
+
enumValues: [string, ...string[]];
|
|
176
|
+
baseColumn: never;
|
|
177
|
+
identity: undefined;
|
|
178
|
+
generated: undefined;
|
|
179
|
+
}, {}, {
|
|
180
|
+
length: number | undefined;
|
|
181
|
+
}>;
|
|
182
|
+
};
|
|
183
|
+
export declare function findUser(d1: D1Database, id: string): Promise<StoredUser | undefined>;
|
|
184
|
+
export type SignedInPerson = {
|
|
185
|
+
email: string;
|
|
186
|
+
name: string | null;
|
|
187
|
+
avatarUrl: string | null;
|
|
188
|
+
};
|
|
189
|
+
export declare function saveUser(d1: D1Database, person: SignedInPerson): Promise<StoredUser>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { eq, sql } from 'drizzle-orm';
|
|
2
|
+
import { drizzle } from 'drizzle-orm/d1';
|
|
3
|
+
import { sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
|
4
|
+
export const users = sqliteTable('users', {
|
|
5
|
+
id: text('id').primaryKey(),
|
|
6
|
+
email: text('email').notNull().unique(),
|
|
7
|
+
name: text('name').notNull(),
|
|
8
|
+
avatarUrl: text('avatar_url'),
|
|
9
|
+
createdAt: text('created_at')
|
|
10
|
+
.notNull()
|
|
11
|
+
.default(sql `(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`)
|
|
12
|
+
});
|
|
13
|
+
export const userColumns = {
|
|
14
|
+
id: users.id,
|
|
15
|
+
email: users.email,
|
|
16
|
+
name: users.name,
|
|
17
|
+
avatarUrl: users.avatarUrl
|
|
18
|
+
};
|
|
19
|
+
export async function findUser(d1, id) {
|
|
20
|
+
return drizzle(d1)
|
|
21
|
+
.select(userColumns)
|
|
22
|
+
.from(users)
|
|
23
|
+
.where(eq(users.id, id))
|
|
24
|
+
.get();
|
|
25
|
+
}
|
|
26
|
+
function nameFromEmail(email) {
|
|
27
|
+
const local = email.split('@')[0] ?? email;
|
|
28
|
+
return local.charAt(0).toUpperCase() + local.slice(1);
|
|
29
|
+
}
|
|
30
|
+
export async function saveUser(d1, person) {
|
|
31
|
+
const email = person.email.toLowerCase();
|
|
32
|
+
return drizzle(d1)
|
|
33
|
+
.insert(users)
|
|
34
|
+
.values({
|
|
35
|
+
id: crypto.randomUUID(),
|
|
36
|
+
email,
|
|
37
|
+
name: person.name ?? nameFromEmail(email),
|
|
38
|
+
avatarUrl: person.avatarUrl
|
|
39
|
+
})
|
|
40
|
+
.onConflictDoUpdate({
|
|
41
|
+
target: users.email,
|
|
42
|
+
set: {
|
|
43
|
+
name: person.name === null ? sql `${users.name}` : sql `excluded.name`,
|
|
44
|
+
avatarUrl: sql `coalesce(excluded.avatar_url, ${users.avatarUrl})`
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
.returning(userColumns)
|
|
48
|
+
.get();
|
|
49
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Gate } from './gate.ts';
|
|
2
|
+
import type { MemoryApiOptions } from './memoryApi.ts';
|
|
3
|
+
import type { ServiceClient, ServiceMemory, Services } from './services.ts';
|
|
4
|
+
export declare function httpServices<Group extends Services>(services: Group, gate: Gate, onSignedOut: () => void): ServiceClient<Group>;
|
|
5
|
+
export declare function memoryServices<Group extends Services, World>(services: Group, memory: ServiceMemory<Group, World>, world: World, options: MemoryApiOptions): ServiceClient<Group>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { MemoryServer } from "./memoryApi.js";
|
|
2
|
+
import { ServiceRefused, paramNamesOf } from "./services.js";
|
|
3
|
+
function buildClient(services, make) {
|
|
4
|
+
return Object.fromEntries(Object.entries(services).map(([group, calls]) => [
|
|
5
|
+
group,
|
|
6
|
+
Object.fromEntries(Object.entries(calls).map(([name, call]) => [
|
|
7
|
+
name,
|
|
8
|
+
make(group, name, call)
|
|
9
|
+
]))
|
|
10
|
+
]));
|
|
11
|
+
}
|
|
12
|
+
function splitInput(call, input) {
|
|
13
|
+
const params = paramNamesOf(call.path);
|
|
14
|
+
const path = call.path.replaceAll(/:(\w+)/g, (_match, key) => encodeURIComponent(String(input[key] ?? '')));
|
|
15
|
+
const rest = Object.fromEntries(Object.entries(input).filter(([key]) => !params.includes(key)));
|
|
16
|
+
return { path, rest };
|
|
17
|
+
}
|
|
18
|
+
function errorOf(body, status) {
|
|
19
|
+
const error = typeof body === 'object' && body !== null
|
|
20
|
+
? Reflect.get(body, 'error')
|
|
21
|
+
: undefined;
|
|
22
|
+
return typeof error === 'string' ? error : `HTTP ${status}`;
|
|
23
|
+
}
|
|
24
|
+
export function httpServices(services, gate, onSignedOut) {
|
|
25
|
+
return buildClient(services, (group, name, call) => (input = {}) => gate.pass(`${group}.${name}`, async () => {
|
|
26
|
+
const { path, rest } = splitInput(call, input);
|
|
27
|
+
const sent = call.input.parse(rest);
|
|
28
|
+
const query = call.method === 'get' && Object.keys(sent).length > 0
|
|
29
|
+
? `?${new URLSearchParams(Object.entries(sent).map(([key, value]) => [
|
|
30
|
+
key,
|
|
31
|
+
String(value)
|
|
32
|
+
])).toString()}`
|
|
33
|
+
: '';
|
|
34
|
+
const response = await fetch(`/api${path}${query}`, {
|
|
35
|
+
method: call.method.toUpperCase(),
|
|
36
|
+
...(call.method === 'post'
|
|
37
|
+
? {
|
|
38
|
+
headers: { 'content-type': 'application/json' },
|
|
39
|
+
body: JSON.stringify(sent)
|
|
40
|
+
}
|
|
41
|
+
: {})
|
|
42
|
+
});
|
|
43
|
+
const body = await response.json().catch(() => null);
|
|
44
|
+
if (response.status === 401)
|
|
45
|
+
onSignedOut();
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
throw new ServiceRefused(response.status, errorOf(body, response.status));
|
|
48
|
+
}
|
|
49
|
+
return call.reply.parse(body);
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
export function memoryServices(services, memory, world, options) {
|
|
53
|
+
const servers = new Map();
|
|
54
|
+
return buildClient(services, (group, name, call) => (input = {}) => {
|
|
55
|
+
const server = servers.get(group) ?? new MemoryServer(group, options);
|
|
56
|
+
servers.set(group, server);
|
|
57
|
+
return server.reply(name, () => {
|
|
58
|
+
const handlers = Reflect.get(memory, group);
|
|
59
|
+
const handler = typeof handlers === 'object' && handlers !== null
|
|
60
|
+
? Reflect.get(handlers, name)
|
|
61
|
+
: undefined;
|
|
62
|
+
if (typeof handler !== 'function') {
|
|
63
|
+
throw new Error(`The memory fake has no ${group}.${name}`);
|
|
64
|
+
}
|
|
65
|
+
const { rest } = splitInput(call, input);
|
|
66
|
+
const params = Object.fromEntries(paramNamesOf(call.path).map((key) => [key, String(input[key] ?? '')]));
|
|
67
|
+
return call.reply.parse(handler(world, { ...params, ...call.input.parse(rest) }));
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { SameOnTheWire } from './wire.ts';
|
|
3
|
+
type Shape = z.ZodRawShape;
|
|
4
|
+
type NoShape = Record<never, never>;
|
|
5
|
+
type ParamNames<Path extends string> = Path extends `${string}:${infer Param}/${infer Rest}` ? Param | ParamNames<`/${Rest}`> : Path extends `${string}:${infer Param}` ? Param : never;
|
|
6
|
+
export type ParamsOf<Path extends string> = {
|
|
7
|
+
readonly [Name in ParamNames<Path>]: string;
|
|
8
|
+
};
|
|
9
|
+
export type ServiceCall<Path extends string = string, S extends Shape = Shape, Reply extends z.ZodType = z.ZodType> = {
|
|
10
|
+
readonly method: 'get' | 'post';
|
|
11
|
+
readonly path: Path;
|
|
12
|
+
readonly input: z.ZodObject<S>;
|
|
13
|
+
readonly reply: Reply;
|
|
14
|
+
};
|
|
15
|
+
type StaticTypeError<Message extends string> = Message;
|
|
16
|
+
type NotText = StaticTypeError<'A get call sends its input in the address, where everything is text: use z.string() or z.enum() here, or make it a post call'>;
|
|
17
|
+
type TextOnly<S> = {
|
|
18
|
+
[Key in keyof S]: S[Key] extends z.ZodType ? z.output<S[Key]> extends string | undefined ? S[Key] : NotText : S[Key];
|
|
19
|
+
};
|
|
20
|
+
type CallOptions<Path extends string, S extends Shape, Reply extends z.ZodType> = {
|
|
21
|
+
get: Path;
|
|
22
|
+
input?: TextOnly<S> & SameOnTheWire<S>;
|
|
23
|
+
reply: Reply;
|
|
24
|
+
} | {
|
|
25
|
+
post: Path;
|
|
26
|
+
input?: SameOnTheWire<S>;
|
|
27
|
+
reply: Reply;
|
|
28
|
+
};
|
|
29
|
+
export declare function call<Path extends string, Reply extends z.ZodType, S extends Shape = NoShape>(options: CallOptions<Path, S, Reply>): NoInfer<ServiceCall<Path, S, Reply>>;
|
|
30
|
+
export type Services = Readonly<Record<string, Readonly<Record<string, ServiceCall>>>>;
|
|
31
|
+
export declare function defineServices<const Group extends Services>(services: Group): Group;
|
|
32
|
+
export type CallInput<Call> = Call extends ServiceCall<infer Path, infer S, z.ZodType> ? ParamsOf<Path> & (keyof S extends never ? NoShape : z.output<z.ZodObject<S>>) : never;
|
|
33
|
+
export type CallReply<Call> = Call extends ServiceCall<string, Shape, infer Reply> ? z.output<Reply> : never;
|
|
34
|
+
type ClientCall<Call> = keyof CallInput<Call> extends never ? (input?: CallInput<Call>) => Promise<CallReply<Call>> : (input: CallInput<Call>) => Promise<CallReply<Call>>;
|
|
35
|
+
export type ServiceClient<Group extends Services> = {
|
|
36
|
+
readonly [Name in keyof Group]: {
|
|
37
|
+
readonly [Call in keyof Group[Name]]: ClientCall<Group[Name][Call]>;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
export type MemoryHandler<Call, World> = (world: World, input: CallInput<Call>) => CallReply<Call>;
|
|
41
|
+
export type ServiceMemory<Group extends Services, World> = {
|
|
42
|
+
readonly [Name in keyof Group]: {
|
|
43
|
+
readonly [Call in keyof Group[Name]]: MemoryHandler<Group[Name][Call], World>;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
export declare class ServiceRefused extends Error {
|
|
47
|
+
readonly status: number;
|
|
48
|
+
constructor(status: number, message: string);
|
|
49
|
+
}
|
|
50
|
+
export declare function refused(status: number, message: string): ServiceRefused;
|
|
51
|
+
export declare function paramNamesOf(path: string): readonly string[];
|
|
52
|
+
export declare function serviceCallNames(services: Services): readonly string[];
|
|
53
|
+
export {};
|
package/dist/services.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export function call(options) {
|
|
3
|
+
const method = 'get' in options ? 'get' : 'post';
|
|
4
|
+
const path = 'get' in options ? options.get : options.post;
|
|
5
|
+
return {
|
|
6
|
+
method,
|
|
7
|
+
path,
|
|
8
|
+
input: z.object((options.input ?? {})),
|
|
9
|
+
reply: options.reply
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function defineServices(services) {
|
|
13
|
+
return services;
|
|
14
|
+
}
|
|
15
|
+
export class ServiceRefused extends Error {
|
|
16
|
+
status;
|
|
17
|
+
constructor(status, message) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.status = status;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function refused(status, message) {
|
|
23
|
+
return new ServiceRefused(status, message);
|
|
24
|
+
}
|
|
25
|
+
export function paramNamesOf(path) {
|
|
26
|
+
return [...path.matchAll(/:(\w+)/g)].map((match) => match[1] ?? '');
|
|
27
|
+
}
|
|
28
|
+
export function serviceCallNames(services) {
|
|
29
|
+
return Object.entries(services).flatMap(([group, calls]) => Object.keys(calls).map((name) => `${group}.${name}`));
|
|
30
|
+
}
|