@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,175 @@
|
|
|
1
|
+
import { createMemoryRouter } from 'react-router';
|
|
2
|
+
import { afterAll, expect, inject, test } from 'vitest';
|
|
3
|
+
import { apiCalls, memoryApi } from "../api.js";
|
|
4
|
+
import { Cache } from "../cache.js";
|
|
5
|
+
import { manualClock } from "../clock.js";
|
|
6
|
+
import { Gate } from "../gate.js";
|
|
7
|
+
import { routeObjects } from "../routes.js";
|
|
8
|
+
import { screenshotNameProblem } from "./screenshotName.js";
|
|
9
|
+
export { expect };
|
|
10
|
+
export { accessFlows } from "./accessFlows.js";
|
|
11
|
+
const DEFAULT_USER = 'ada@acme.test';
|
|
12
|
+
const screenshotNamesInThisFile = new Set();
|
|
13
|
+
let closesBrowserAfterThisFile = false;
|
|
14
|
+
function checkSteps(steps, calls) {
|
|
15
|
+
for (const step of steps) {
|
|
16
|
+
for (const key of ['hold', 'release']) {
|
|
17
|
+
const call = Reflect.get(step, key);
|
|
18
|
+
if (typeof call === 'string' && !calls.includes(call)) {
|
|
19
|
+
throw new Error(`{ ${key}: '${call}' } names no server call. Calls: ${calls.join(', ')}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const name = Reflect.get(step, 'screenshot');
|
|
23
|
+
if (typeof name !== 'string')
|
|
24
|
+
continue;
|
|
25
|
+
const problem = screenshotNameProblem(name);
|
|
26
|
+
if (problem !== undefined)
|
|
27
|
+
throw new Error(problem);
|
|
28
|
+
if (screenshotNamesInThisFile.has(name)) {
|
|
29
|
+
throw new Error(`Screenshot name "${name}" is used twice; each screenshot step needs its own name.`);
|
|
30
|
+
}
|
|
31
|
+
screenshotNamesInThisFile.add(name);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function runSteps(name, steps, run) {
|
|
35
|
+
for (const [index, step] of steps.entries()) {
|
|
36
|
+
if ('send' in step) {
|
|
37
|
+
await run.send(step.send);
|
|
38
|
+
}
|
|
39
|
+
else if ('screenshot' in step) {
|
|
40
|
+
await run.screenshot(step.screenshot);
|
|
41
|
+
}
|
|
42
|
+
else if ('hold' in step) {
|
|
43
|
+
await run.hold(step.hold);
|
|
44
|
+
}
|
|
45
|
+
else if ('release' in step) {
|
|
46
|
+
await run.release(step.release);
|
|
47
|
+
}
|
|
48
|
+
else if ('restart' in step) {
|
|
49
|
+
await run.restart();
|
|
50
|
+
}
|
|
51
|
+
else if ('tick' in step) {
|
|
52
|
+
await run.tick();
|
|
53
|
+
}
|
|
54
|
+
else if ('world' in step) {
|
|
55
|
+
await run.world(step.world);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
expect(await run.snapshot(), `step ${index + 1} of "${name}": expected ${JSON.stringify(step.expect)}`).toMatchObject(step.expect);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function withTestParams(path, failing, held) {
|
|
63
|
+
const params = new URLSearchParams([
|
|
64
|
+
['memory', ''],
|
|
65
|
+
...failing.map((call) => ['fail', call]),
|
|
66
|
+
...[...held].map((call) => ['hold', call])
|
|
67
|
+
]);
|
|
68
|
+
return `${path}?${params.toString()}`;
|
|
69
|
+
}
|
|
70
|
+
export function defineFlow({ App }) {
|
|
71
|
+
const { tables, routes, services, memory } = App.definition;
|
|
72
|
+
const seed = (inject('patternSeed') ?? {});
|
|
73
|
+
const admins = inject('patternAdmins') ?? [];
|
|
74
|
+
const title = inject('patternTitle') ?? '';
|
|
75
|
+
const startingWorld = inject('patternWorld') ?? {};
|
|
76
|
+
const calls = apiCalls(tables, services);
|
|
77
|
+
const router = (path) => createMemoryRouter(routeObjects(routes), { initialEntries: [path] });
|
|
78
|
+
function headless(name, steps, startAt, failing, signedInAs) {
|
|
79
|
+
test(name, async () => {
|
|
80
|
+
const gate = new Gate(failing);
|
|
81
|
+
const world = structuredClone(startingWorld);
|
|
82
|
+
const deps = {
|
|
83
|
+
api: memoryApi(tables, seed, {
|
|
84
|
+
gate,
|
|
85
|
+
signedInAs,
|
|
86
|
+
admins,
|
|
87
|
+
title,
|
|
88
|
+
world,
|
|
89
|
+
...(services === undefined ? {} : { services }),
|
|
90
|
+
...(memory === undefined ? {} : { memory })
|
|
91
|
+
}),
|
|
92
|
+
cache: Cache.memory(),
|
|
93
|
+
gate,
|
|
94
|
+
clock: manualClock()
|
|
95
|
+
};
|
|
96
|
+
let app = new App(router(startAt), deps);
|
|
97
|
+
await runSteps(name, steps, {
|
|
98
|
+
send: async (command) => void app.send(command),
|
|
99
|
+
snapshot: async () => {
|
|
100
|
+
await app.whenIdle();
|
|
101
|
+
return app.getSnapshot();
|
|
102
|
+
},
|
|
103
|
+
screenshot: async () => undefined,
|
|
104
|
+
hold: async (call) => gate.hold(call),
|
|
105
|
+
release: async (call) => gate.release(call),
|
|
106
|
+
restart: async () => {
|
|
107
|
+
app = new App(router(app.getSnapshot().path), deps);
|
|
108
|
+
},
|
|
109
|
+
tick: async () => {
|
|
110
|
+
app.tick();
|
|
111
|
+
},
|
|
112
|
+
world: async (patch) => {
|
|
113
|
+
Object.assign(Object(world), patch);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
await app.whenIdle();
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return function flow(name, steps, { startAt = '/', failing = [], signedInAs = DEFAULT_USER } = {}) {
|
|
120
|
+
checkSteps(steps, calls);
|
|
121
|
+
const visual = inject('patternVisualFlows');
|
|
122
|
+
if (visual === undefined) {
|
|
123
|
+
headless(name, steps, startAt, failing, signedInAs);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (!closesBrowserAfterThisFile) {
|
|
127
|
+
closesBrowserAfterThisFile = true;
|
|
128
|
+
afterAll(async () => {
|
|
129
|
+
const { closeFlowBrowser } = await import("./flowBrowser.js");
|
|
130
|
+
await closeFlowBrowser();
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
for (const viewport of visual.viewports) {
|
|
134
|
+
test(`${name} [${viewport}]`, async () => {
|
|
135
|
+
const { openFlowPage } = await import("./flowBrowser.js");
|
|
136
|
+
const held = new Set();
|
|
137
|
+
const page = await openFlowPage(visual, viewport, withTestParams(startAt, failing, held), signedInAs, seed, admins, startingWorld);
|
|
138
|
+
let stepFailure;
|
|
139
|
+
try {
|
|
140
|
+
await runSteps(name, steps, {
|
|
141
|
+
send: page.send,
|
|
142
|
+
snapshot: page.snapshot,
|
|
143
|
+
screenshot: page.screenshot,
|
|
144
|
+
hold: async (call) => {
|
|
145
|
+
held.add(call);
|
|
146
|
+
await page.hold(call);
|
|
147
|
+
},
|
|
148
|
+
release: async (call) => {
|
|
149
|
+
held.delete(call);
|
|
150
|
+
await page.release(call);
|
|
151
|
+
},
|
|
152
|
+
tick: page.tick,
|
|
153
|
+
world: page.world,
|
|
154
|
+
restart: async () => {
|
|
155
|
+
const snapshot = await page.snapshot();
|
|
156
|
+
const path = typeof snapshot === 'object' && snapshot !== null
|
|
157
|
+
? Reflect.get(snapshot, 'path')
|
|
158
|
+
: undefined;
|
|
159
|
+
await page.reload(withTestParams(typeof path === 'string' ? path : startAt, failing, held));
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
stepFailure = error;
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
await page.close();
|
|
168
|
+
}
|
|
169
|
+
expect(page.problems, `"${name}" [${viewport}] logged errors in the browser; fix them`).toEqual([]);
|
|
170
|
+
if (stepFailure !== undefined)
|
|
171
|
+
throw stepFailure;
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function screenshotNameProblem(name: string): string | undefined;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
const KEBAB_CASE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
2
|
+
export function screenshotNameProblem(name) {
|
|
3
|
+
return KEBAB_CASE.test(name)
|
|
4
|
+
? undefined
|
|
5
|
+
: `Screenshot name "${name}" must be kebab-case (lowercase letters, digits and dashes), like "settings" or "sidebar-drawer": it becomes the PNG file name.`;
|
|
6
|
+
}
|
package/dist/gate.d.ts
ADDED
package/dist/gate.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
function nextMacrotask() {
|
|
2
|
+
return new Promise((done) => setTimeout(done, 0));
|
|
3
|
+
}
|
|
4
|
+
export class Gate {
|
|
5
|
+
#failing;
|
|
6
|
+
#holds = new Map();
|
|
7
|
+
#active = 0;
|
|
8
|
+
#waiting = [];
|
|
9
|
+
constructor(failing = []) {
|
|
10
|
+
this.#failing = failing;
|
|
11
|
+
}
|
|
12
|
+
async pass(call, run) {
|
|
13
|
+
if (this.#failing.includes(call))
|
|
14
|
+
throw new Error(`${call} failed`);
|
|
15
|
+
this.#active += 1;
|
|
16
|
+
const value = await run().finally(() => {
|
|
17
|
+
this.#active -= 1;
|
|
18
|
+
this.#wakeIfIdle();
|
|
19
|
+
});
|
|
20
|
+
await this.#holds.get(call)?.opened;
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
hold(call) {
|
|
24
|
+
if (this.#holds.has(call))
|
|
25
|
+
return;
|
|
26
|
+
let open = () => { };
|
|
27
|
+
const opened = new Promise((resolve) => {
|
|
28
|
+
open = resolve;
|
|
29
|
+
});
|
|
30
|
+
this.#holds.set(call, { opened, open });
|
|
31
|
+
}
|
|
32
|
+
release(call) {
|
|
33
|
+
this.#holds.get(call)?.open();
|
|
34
|
+
this.#holds.delete(call);
|
|
35
|
+
}
|
|
36
|
+
async whenIdle() {
|
|
37
|
+
do {
|
|
38
|
+
if (this.#active > 0) {
|
|
39
|
+
await new Promise((wake) => this.#waiting.push(wake));
|
|
40
|
+
}
|
|
41
|
+
await nextMacrotask();
|
|
42
|
+
} while (this.#active > 0);
|
|
43
|
+
}
|
|
44
|
+
#wakeIfIdle() {
|
|
45
|
+
if (this.#active > 0)
|
|
46
|
+
return;
|
|
47
|
+
const woken = this.#waiting;
|
|
48
|
+
this.#waiting = [];
|
|
49
|
+
for (const wake of woken)
|
|
50
|
+
wake();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Toasts } from '../builtins/Toasts.ts';
|
|
2
|
+
import type { AppStore } from '../appInfo.ts';
|
|
3
|
+
import type { AppModels } from '../registered.ts';
|
|
4
|
+
import type { AppProviderOf, UseModel } from './appHooks.ts';
|
|
5
|
+
export type ReactApp = {
|
|
6
|
+
readonly models: AppModels;
|
|
7
|
+
readonly stores: {
|
|
8
|
+
readonly toasts: Toasts;
|
|
9
|
+
readonly app: AppStore;
|
|
10
|
+
};
|
|
11
|
+
pressKey(key: string): Promise<void> | undefined;
|
|
12
|
+
};
|
|
13
|
+
export declare const AppProvider: AppProviderOf<ReactApp>;
|
|
14
|
+
export declare const useApp: () => ReactApp;
|
|
15
|
+
export declare const useModel: UseModel<AppModels>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { PublishedOf } from '../model.ts';
|
|
3
|
+
type PublishingModel = {
|
|
4
|
+
readonly subscribe: (listener: () => void) => () => void;
|
|
5
|
+
readonly published: object;
|
|
6
|
+
};
|
|
7
|
+
type AppLike = {
|
|
8
|
+
readonly models: Readonly<Record<string, PublishingModel>>;
|
|
9
|
+
pressKey(key: string): Promise<void> | undefined;
|
|
10
|
+
};
|
|
11
|
+
export type UseModel<Models> = <Name extends keyof Models & string>(name: Name) => PublishedOf<Models[Name]>;
|
|
12
|
+
export type AppProviderOf<App> = (props: {
|
|
13
|
+
app: App;
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
}) => ReactNode;
|
|
16
|
+
export declare function createAppHooks<App extends AppLike>(): {
|
|
17
|
+
AppProvider: AppProviderOf<App>;
|
|
18
|
+
useApp: () => App;
|
|
19
|
+
useModel: UseModel<App['models']>;
|
|
20
|
+
};
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createContext, createElement, use, useSyncExternalStore } from 'react';
|
|
2
|
+
import { useShortcuts } from "../shortcuts.js";
|
|
3
|
+
export function createAppHooks() {
|
|
4
|
+
const AppContext = createContext(null);
|
|
5
|
+
function useApp() {
|
|
6
|
+
const app = use(AppContext);
|
|
7
|
+
if (app === null) {
|
|
8
|
+
throw new Error('Pattern hooks must be used inside <AppProvider>');
|
|
9
|
+
}
|
|
10
|
+
return app;
|
|
11
|
+
}
|
|
12
|
+
function useModel(name) {
|
|
13
|
+
const model = useApp().models[name];
|
|
14
|
+
if (model === undefined)
|
|
15
|
+
throw new Error(`There is no model named ${name}`);
|
|
16
|
+
const read = () => model.published;
|
|
17
|
+
return useSyncExternalStore(model.subscribe, read, read);
|
|
18
|
+
}
|
|
19
|
+
function AppProvider({ app, children }) {
|
|
20
|
+
useShortcuts((key) => app.pressKey(key));
|
|
21
|
+
return createElement(AppContext, { value: app }, children);
|
|
22
|
+
}
|
|
23
|
+
return { AppProvider, useApp, useModel };
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function useAnimatesOnChange(value: unknown, durationMs: number): boolean;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
export function useAnimatesOnChange(value, durationMs) {
|
|
3
|
+
const [tracked, setTracked] = useState({ value, isAnimating: false });
|
|
4
|
+
if (tracked.value !== value) {
|
|
5
|
+
setTracked({ value, isAnimating: true });
|
|
6
|
+
}
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (!tracked.isAnimating)
|
|
9
|
+
return undefined;
|
|
10
|
+
const timer = setTimeout(() => setTracked((current) => ({ ...current, isAnimating: false })), durationMs);
|
|
11
|
+
return () => clearTimeout(timer);
|
|
12
|
+
}, [tracked, durationMs]);
|
|
13
|
+
return tracked.isAnimating || tracked.value !== value;
|
|
14
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export { defineApp } from './app.ts';
|
|
2
|
+
export { ActionsStore, bindActions, rankActions } from './actions.ts';
|
|
3
|
+
export type { Action, ActionsBuilder } from './actions.ts';
|
|
4
|
+
export { startApp } from './start.ts';
|
|
5
|
+
export type { StartOptions } from './start.ts';
|
|
6
|
+
export { appearance } from './appearance.ts';
|
|
7
|
+
export type { Appearance } from './appearance.ts';
|
|
8
|
+
export { defineIcons } from './components/icons.ts';
|
|
9
|
+
export type { IconDrawing } from './components/icons.ts';
|
|
10
|
+
export type { Interaction } from './components/interaction.ts';
|
|
11
|
+
export { apiCalls, browserApi, httpApi, memoryApi } from './api.ts';
|
|
12
|
+
export type { ApiCall, AppApi } from './api.ts';
|
|
13
|
+
export { SessionStore } from './auth/sessionStore.ts';
|
|
14
|
+
export type { SessionState } from './auth/sessionStore.ts';
|
|
15
|
+
export { HttpSessionApi, MemorySessionApi } from './auth/sessionApi.ts';
|
|
16
|
+
export type { EmailCodeOutcome, SessionApi, SessionCall } from './auth/sessionApi.ts';
|
|
17
|
+
export { userSchema } from './auth/user.ts';
|
|
18
|
+
export type { User } from './auth/user.ts';
|
|
19
|
+
export type { AppClass, AppDefinition, AppDependencies, AppSnapshotShape, DataRouter, ModelServicesOf, NavigateCommand, PressKeyCommand, PublicStores, PlainJsonModel, RunningApp, StoreClasses, StoreInstances, StoresOf, TableStores } from './app.ts';
|
|
20
|
+
export { defineRoutes } from './routes.ts';
|
|
21
|
+
export { ServiceRefused, refused } from './services.ts';
|
|
22
|
+
export type { ServiceClient, ServiceMemory } from './services.ts';
|
|
23
|
+
export { manualClock, realClock } from './clock.ts';
|
|
24
|
+
export type { Clock } from './clock.ts';
|
|
25
|
+
export type { PageOf, PathOf, RouteDefinition } from './routes.ts';
|
|
26
|
+
export { Model, Store, action, appPath, defineActions } from './registered.ts';
|
|
27
|
+
export type { ActionContext, AppAction, AppActionGroup, AppCommand, AppModels, AppPage, AppPath, AppSnapshot, AppStores, ModelServices, Register } from './registered.ts';
|
|
28
|
+
export { Cache, Cached } from './cache.ts';
|
|
29
|
+
export type { CacheStorage } from './cache.ts';
|
|
30
|
+
export { EntityStore, WriteRefused, entityCalls } from './entityStore.ts';
|
|
31
|
+
export type { Entity, EntityApi, EntityCall, EntityDeps, EntitySnapshot, PendingWrite } from './entityStore.ts';
|
|
32
|
+
export { Gate } from './gate.ts';
|
|
33
|
+
export { MemoryEntityApi, MemoryServer } from './memoryApi.ts';
|
|
34
|
+
export type { MemoryApiOptions } from './memoryApi.ts';
|
|
35
|
+
export { StoreBase, keepEqualParts, snapshotsOf } from './store.ts';
|
|
36
|
+
export type { Snapshots, StoreClass, StoreConfig } from './store.ts';
|
|
37
|
+
export type { Json, Persisted, StateOf, StateSpec } from './state.ts';
|
|
38
|
+
export { defineTables } from './table.ts';
|
|
39
|
+
export type { Access, Columns, Fields, Level, PublicRow, Row, Seed, Table, TableDefinition, TableOf, Tables, Value, Writes } from './table.ts';
|
|
40
|
+
export { HttpTableApi, MemoryTableApi, PublicTableStore, TableStore, httpTables, memoryTables, publicStoreName, tableCalls } from './tables.ts';
|
|
41
|
+
export type { PublicSnapshot, TableApi, TableCall, TablesApi } from './tables.ts';
|
|
42
|
+
export type { ReadScope, Viewer, WriteOutcome } from './access.ts';
|
|
43
|
+
export { ModelBase, command, commandsOf, createModels, modelSnapshotsOf, parseMessage, sendMessage, splitType } from './model.ts';
|
|
44
|
+
export type { Command, CommandKeys, CommandOptions, CommandResult, InputOf, Instances, Message, MessagesOf, ModelSnapshots, PublishedCommands, PublishedOf, SnapshotOf, DerivedOf, RouteParams, ModelClass, ModelClassLike, ModelConfig } from './model.ts';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export { defineApp } from "./app.js";
|
|
2
|
+
export { ActionsStore, bindActions, rankActions } from "./actions.js";
|
|
3
|
+
export { startApp } from "./start.js";
|
|
4
|
+
export { appearance } from "./appearance.js";
|
|
5
|
+
export { defineIcons } from "./components/icons.js";
|
|
6
|
+
export { apiCalls, browserApi, httpApi, memoryApi } from "./api.js";
|
|
7
|
+
export { SessionStore } from "./auth/sessionStore.js";
|
|
8
|
+
export { HttpSessionApi, MemorySessionApi } from "./auth/sessionApi.js";
|
|
9
|
+
export { userSchema } from "./auth/user.js";
|
|
10
|
+
export { defineRoutes } from "./routes.js";
|
|
11
|
+
export { ServiceRefused, refused } from "./services.js";
|
|
12
|
+
export { manualClock, realClock } from "./clock.js";
|
|
13
|
+
export { Model, Store, action, appPath, defineActions } from "./registered.js";
|
|
14
|
+
export { Cache, Cached } from "./cache.js";
|
|
15
|
+
export { EntityStore, WriteRefused, entityCalls } from "./entityStore.js";
|
|
16
|
+
export { Gate } from "./gate.js";
|
|
17
|
+
export { MemoryEntityApi, MemoryServer } from "./memoryApi.js";
|
|
18
|
+
export { StoreBase, keepEqualParts, snapshotsOf } from "./store.js";
|
|
19
|
+
export { defineTables } from "./table.js";
|
|
20
|
+
export { HttpTableApi, MemoryTableApi, PublicTableStore, TableStore, httpTables, memoryTables, publicStoreName, tableCalls } from "./tables.js";
|
|
21
|
+
export { ModelBase, command, commandsOf, createModels, modelSnapshotsOf, parseMessage, sendMessage, splitType } from "./model.js";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Entity, EntityApi } from './entityStore.ts';
|
|
2
|
+
import type { Gate } from './gate.ts';
|
|
3
|
+
export type MemoryApiOptions = {
|
|
4
|
+
latencyMs: number;
|
|
5
|
+
gate: Gate;
|
|
6
|
+
};
|
|
7
|
+
export declare class MemoryServer {
|
|
8
|
+
#private;
|
|
9
|
+
constructor(name: string, options: MemoryApiOptions);
|
|
10
|
+
reply<Value>(call: string, respond: () => Value): Promise<Value>;
|
|
11
|
+
}
|
|
12
|
+
export declare class MemoryEntityApi<T extends Entity> implements EntityApi<T> {
|
|
13
|
+
#private;
|
|
14
|
+
constructor(name: string, seed: readonly T[], options: MemoryApiOptions);
|
|
15
|
+
list(): Promise<readonly T[]>;
|
|
16
|
+
create(entity: T): Promise<T>;
|
|
17
|
+
update(entity: T): Promise<T>;
|
|
18
|
+
remove(id: string): Promise<void>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export class MemoryServer {
|
|
2
|
+
#name;
|
|
3
|
+
#options;
|
|
4
|
+
constructor(name, options) {
|
|
5
|
+
this.#name = name;
|
|
6
|
+
this.#options = options;
|
|
7
|
+
}
|
|
8
|
+
reply(call, respond) {
|
|
9
|
+
return this.#options.gate.pass(`${this.#name}.${call}`, async () => {
|
|
10
|
+
await new Promise((done) => setTimeout(done, this.#options.latencyMs));
|
|
11
|
+
return respond();
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export class MemoryEntityApi {
|
|
16
|
+
#server;
|
|
17
|
+
#saved;
|
|
18
|
+
constructor(name, seed, options) {
|
|
19
|
+
this.#server = new MemoryServer(name, options);
|
|
20
|
+
this.#saved = seed;
|
|
21
|
+
}
|
|
22
|
+
list() {
|
|
23
|
+
return this.#server.reply('list', () => this.#saved);
|
|
24
|
+
}
|
|
25
|
+
create(entity) {
|
|
26
|
+
return this.#server.reply('create', () => {
|
|
27
|
+
this.#saved = [...this.#saved, entity];
|
|
28
|
+
return entity;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
update(entity) {
|
|
32
|
+
return this.#server.reply('update', () => {
|
|
33
|
+
this.#saved = this.#saved.map((item) => item.id === entity.id ? entity : item);
|
|
34
|
+
return entity;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
remove(id) {
|
|
38
|
+
return this.#server.reply('remove', () => {
|
|
39
|
+
this.#saved = this.#saved.filter((item) => item.id !== id);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
package/dist/model.d.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { StoreBase } from './store.ts';
|
|
3
|
+
import type { Persisted, StateOf, StateSpec } from './state.ts';
|
|
4
|
+
import type { SameOnTheWire } from './wire.ts';
|
|
5
|
+
type Shape = z.ZodRawShape;
|
|
6
|
+
type NoInput = Record<never, never>;
|
|
7
|
+
export type InputOf<S extends Shape> = keyof S extends never ? NoInput : z.output<z.ZodObject<S>>;
|
|
8
|
+
export type CommandResult = void | Promise<void>;
|
|
9
|
+
type Handler<S extends Shape, Result> = keyof S extends never ? () => Result : (input: InputOf<S>) => Result;
|
|
10
|
+
export type RouteParams = Readonly<Record<string, string | undefined>>;
|
|
11
|
+
declare const INPUT: unique symbol;
|
|
12
|
+
export type Command<S extends Shape, Result extends CommandResult> = Handler<S, Result> & {
|
|
13
|
+
readonly [INPUT]: z.ZodObject<S>;
|
|
14
|
+
};
|
|
15
|
+
type AnyCommand = {
|
|
16
|
+
readonly [INPUT]: z.ZodType;
|
|
17
|
+
};
|
|
18
|
+
export type CommandOptions<S extends Shape> = {
|
|
19
|
+
input: SameOnTheWire<S>;
|
|
20
|
+
};
|
|
21
|
+
export declare function command<Result extends CommandResult>(run: () => Result): Command<NoInput, Result>;
|
|
22
|
+
export declare function command<S extends Shape, Result extends CommandResult>(options: CommandOptions<S>, run: (input: InputOf<S>) => Result): Command<S, Result>;
|
|
23
|
+
export declare function commandFromSchema(schema: z.ZodType, run: (input: never) => CommandResult): AnyCommand;
|
|
24
|
+
export declare function commandsOf(model: object): Record<string, z.ZodType>;
|
|
25
|
+
export declare function answerNamesOf(model: object): readonly string[];
|
|
26
|
+
export declare abstract class ModelBase<State extends object, Stores, Services> extends StoreBase<State> {
|
|
27
|
+
#private;
|
|
28
|
+
protected readonly stores: Stores;
|
|
29
|
+
protected readonly app: Services;
|
|
30
|
+
constructor(stores: Stores, app: Services);
|
|
31
|
+
onPageOpen(_params: RouteParams): void | Promise<void>;
|
|
32
|
+
protected get params(): RouteParams;
|
|
33
|
+
protected initialState(): State;
|
|
34
|
+
get snapshot(): object;
|
|
35
|
+
get published(): object;
|
|
36
|
+
protected changed(): void;
|
|
37
|
+
}
|
|
38
|
+
type BaseMember = keyof ModelBase<object, unknown, unknown>;
|
|
39
|
+
export type DerivedOf<Model> = {
|
|
40
|
+
readonly [Key in keyof Model as Key extends BaseMember ? never : Model[Key] extends (...args: never[]) => unknown ? never : Key]: Model[Key];
|
|
41
|
+
};
|
|
42
|
+
export type SnapshotOf<Model> = Model extends {
|
|
43
|
+
readonly state: infer State;
|
|
44
|
+
} ? Readonly<State> & DerivedOf<Model> : never;
|
|
45
|
+
type Fired<Command> = Command extends (...args: infer Input) => unknown ? (...args: Input) => void : never;
|
|
46
|
+
export type PublishedCommands<Model> = {
|
|
47
|
+
readonly [Key in CommandKeys<Model>]: Fired<Model[Key]>;
|
|
48
|
+
};
|
|
49
|
+
export type PublishedOf<Model> = SnapshotOf<Model> & PublishedCommands<Model>;
|
|
50
|
+
export type ModelSnapshots<Models> = {
|
|
51
|
+
[Key in keyof Models]: SnapshotOf<Models[Key]>;
|
|
52
|
+
};
|
|
53
|
+
export declare function modelSnapshotsOf<Models extends Record<string, {
|
|
54
|
+
readonly snapshot: object;
|
|
55
|
+
}>>(models: Models): ModelSnapshots<Models>;
|
|
56
|
+
export type ModelConfig<S extends StateSpec, P extends readonly (keyof S & string)[]> = {
|
|
57
|
+
state?: S;
|
|
58
|
+
persist?: P;
|
|
59
|
+
};
|
|
60
|
+
export type ModelClass<Registry, Services, S extends StateSpec, P extends readonly (keyof S & string)[]> = (abstract new (stores: Registry, app: Services, device: Persisted) => ModelBase<StateOf<S>, Registry, Services>) & {
|
|
61
|
+
readonly config: ModelConfig<S, P>;
|
|
62
|
+
};
|
|
63
|
+
export declare function defineModel<Registry, Services>(): <S extends StateSpec = Record<never, never>, const P extends readonly (keyof S & string)[] = []>(config?: ModelConfig<S, P>) => ModelClass<Registry, Services, S, P>;
|
|
64
|
+
export type AnyModel = ModelBase<object, unknown, unknown>;
|
|
65
|
+
export type ModelClassLike = new (stores: never, app: never, device: Persisted) => AnyModel;
|
|
66
|
+
export type Instances<Classes extends Record<string, ModelClassLike>> = {
|
|
67
|
+
[Key in keyof Classes]: InstanceType<Classes[Key]>;
|
|
68
|
+
};
|
|
69
|
+
export declare function createModels<Classes extends Record<string, ModelClassLike>>(classes: Classes, stores: object, services: object, persistedFor: (model: string) => Persisted): Instances<Classes>;
|
|
70
|
+
export type CommandKeys<Model> = {
|
|
71
|
+
[Key in keyof Model]: Model[Key] extends AnyCommand ? Key : never;
|
|
72
|
+
}[keyof Model];
|
|
73
|
+
type ShapeOf<Value> = Value extends {
|
|
74
|
+
readonly [INPUT]: z.ZodObject<infer S>;
|
|
75
|
+
} ? S : never;
|
|
76
|
+
export type MessagesOf<Models extends Record<string, object>> = {
|
|
77
|
+
[Group in keyof Models & string]: {
|
|
78
|
+
[Name in CommandKeys<Models[Group]> & string]: {
|
|
79
|
+
type: `${Group}.${Name}`;
|
|
80
|
+
} & InputOf<ShapeOf<Models[Group][Name]>>;
|
|
81
|
+
}[CommandKeys<Models[Group]> & string];
|
|
82
|
+
}[keyof Models & string];
|
|
83
|
+
export type Message = {
|
|
84
|
+
readonly type: string;
|
|
85
|
+
};
|
|
86
|
+
export declare function splitType(type: string): {
|
|
87
|
+
group: string;
|
|
88
|
+
name: string;
|
|
89
|
+
};
|
|
90
|
+
export declare function parseMessage<Parsed extends Message>(raw: unknown, models: Readonly<Record<string, object>>): Parsed | {
|
|
91
|
+
error: string;
|
|
92
|
+
};
|
|
93
|
+
export declare function sendMessage(models: Readonly<Record<string, object>>, { type, ...input }: Message): Promise<void>;
|
|
94
|
+
export {};
|