@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.
Files changed (273) hide show
  1. package/README.md +127 -2
  2. package/dist/access.d.ts +31 -0
  3. package/dist/access.js +72 -0
  4. package/dist/actions.d.ts +57 -0
  5. package/dist/actions.js +198 -0
  6. package/dist/api.d.ts +30 -0
  7. package/dist/api.js +60 -0
  8. package/dist/app.d.ts +115 -0
  9. package/dist/app.js +266 -0
  10. package/dist/appInfo.d.ts +35 -0
  11. package/dist/appInfo.js +67 -0
  12. package/dist/appTitle.d.ts +6 -0
  13. package/dist/appTitle.js +3 -0
  14. package/dist/appearance.d.ts +7 -0
  15. package/dist/appearance.js +2 -0
  16. package/dist/auth/sessionApi.d.ts +41 -0
  17. package/dist/auth/sessionApi.js +158 -0
  18. package/dist/auth/sessionStore.d.ts +20 -0
  19. package/dist/auth/sessionStore.js +40 -0
  20. package/dist/auth/user.d.ts +11 -0
  21. package/dist/auth/user.js +9 -0
  22. package/dist/builtins/Account.d.ts +25 -0
  23. package/dist/builtins/Account.js +15 -0
  24. package/dist/builtins/AppSettings.d.ts +12 -0
  25. package/dist/builtins/AppSettings.js +31 -0
  26. package/dist/builtins/Palette.d.ts +29 -0
  27. package/dist/builtins/Palette.js +63 -0
  28. package/dist/builtins/SignIn.d.ts +28 -0
  29. package/dist/builtins/SignIn.js +60 -0
  30. package/dist/builtins/Toasts.d.ts +32 -0
  31. package/dist/builtins/Toasts.js +25 -0
  32. package/dist/builtins/actions.d.ts +2 -0
  33. package/dist/builtins/actions.js +45 -0
  34. package/dist/cache.d.ts +24 -0
  35. package/dist/cache.js +112 -0
  36. package/dist/cli/app.d.ts +2 -0
  37. package/dist/cli/app.js +1406 -0
  38. package/dist/cli/publishing.d.ts +21 -0
  39. package/dist/cli/publishing.js +493 -0
  40. package/dist/cli/screenshot.d.ts +1 -0
  41. package/dist/cli/screenshot.js +96 -0
  42. package/dist/cli/tools.d.ts +12 -0
  43. package/dist/cli/tools.js +35 -0
  44. package/dist/clock.d.ts +6 -0
  45. package/dist/clock.js +31 -0
  46. package/dist/components/AppFrame.d.ts +14 -0
  47. package/dist/components/AppFrame.js +64 -0
  48. package/dist/components/AppearancePicker.d.ts +1 -0
  49. package/dist/components/AppearancePicker.js +12 -0
  50. package/dist/components/Avatar.d.ts +9 -0
  51. package/dist/components/Avatar.js +63 -0
  52. package/dist/components/Badge.d.ts +11 -0
  53. package/dist/components/Badge.js +87 -0
  54. package/dist/components/Button.d.ts +24 -0
  55. package/dist/components/Button.js +252 -0
  56. package/dist/components/Card.d.ts +12 -0
  57. package/dist/components/Card.js +38 -0
  58. package/dist/components/CommandMenu.d.ts +23 -0
  59. package/dist/components/CommandMenu.js +214 -0
  60. package/dist/components/CommandPalette.d.ts +1 -0
  61. package/dist/components/CommandPalette.js +7 -0
  62. package/dist/components/Content.d.ts +7 -0
  63. package/dist/components/Content.js +24 -0
  64. package/dist/components/ContextMenu.d.ts +8 -0
  65. package/dist/components/ContextMenu.js +23 -0
  66. package/dist/components/CopyButton.d.ts +11 -0
  67. package/dist/components/CopyButton.js +16 -0
  68. package/dist/components/Dialog.d.ts +11 -0
  69. package/dist/components/Dialog.js +81 -0
  70. package/dist/components/Divider.d.ts +7 -0
  71. package/dist/components/Divider.js +30 -0
  72. package/dist/components/EmptyState.d.ts +8 -0
  73. package/dist/components/EmptyState.js +24 -0
  74. package/dist/components/Form.d.ts +9 -0
  75. package/dist/components/Form.js +23 -0
  76. package/dist/components/Icon.d.ts +10 -0
  77. package/dist/components/Icon.js +22 -0
  78. package/dist/components/IconButton.d.ts +14 -0
  79. package/dist/components/IconButton.js +53 -0
  80. package/dist/components/Kbd.d.ts +6 -0
  81. package/dist/components/Kbd.js +67 -0
  82. package/dist/components/Link.d.ts +14 -0
  83. package/dist/components/Link.js +44 -0
  84. package/dist/components/ListRow.d.ts +12 -0
  85. package/dist/components/ListRow.js +62 -0
  86. package/dist/components/LoadingRows.d.ts +5 -0
  87. package/dist/components/LoadingRows.js +6 -0
  88. package/dist/components/Menu.d.ts +12 -0
  89. package/dist/components/Menu.js +33 -0
  90. package/dist/components/MenuItems.d.ts +21 -0
  91. package/dist/components/MenuItems.js +32 -0
  92. package/dist/components/Placement.d.ts +21 -0
  93. package/dist/components/Placement.js +1 -0
  94. package/dist/components/Popup.d.ts +8 -0
  95. package/dist/components/Popup.js +43 -0
  96. package/dist/components/PopupItem.d.ts +8 -0
  97. package/dist/components/PopupItem.js +51 -0
  98. package/dist/components/PropertyRow.d.ts +7 -0
  99. package/dist/components/PropertyRow.js +23 -0
  100. package/dist/components/RadioGroup.d.ts +18 -0
  101. package/dist/components/RadioGroup.js +85 -0
  102. package/dist/components/Row.d.ts +16 -0
  103. package/dist/components/Row.js +27 -0
  104. package/dist/components/Screen.d.ts +13 -0
  105. package/dist/components/Screen.js +56 -0
  106. package/dist/components/Scrim.d.ts +8 -0
  107. package/dist/components/Scrim.js +40 -0
  108. package/dist/components/ScrollArea.d.ts +15 -0
  109. package/dist/components/ScrollArea.js +13 -0
  110. package/dist/components/SegmentedControl.d.ts +17 -0
  111. package/dist/components/SegmentedControl.js +68 -0
  112. package/dist/components/Select.d.ts +17 -0
  113. package/dist/components/Select.js +107 -0
  114. package/dist/components/SettingsRow.d.ts +8 -0
  115. package/dist/components/SettingsRow.js +32 -0
  116. package/dist/components/Sidebar.d.ts +20 -0
  117. package/dist/components/Sidebar.js +137 -0
  118. package/dist/components/SignInScreen.d.ts +1 -0
  119. package/dist/components/SignInScreen.js +40 -0
  120. package/dist/components/Size.d.ts +1 -0
  121. package/dist/components/Size.js +1 -0
  122. package/dist/components/Skeleton.d.ts +8 -0
  123. package/dist/components/Skeleton.js +48 -0
  124. package/dist/components/Spinner.d.ts +6 -0
  125. package/dist/components/Spinner.js +54 -0
  126. package/dist/components/Stack.d.ts +14 -0
  127. package/dist/components/Stack.js +19 -0
  128. package/dist/components/Switch.d.ts +10 -0
  129. package/dist/components/Switch.js +72 -0
  130. package/dist/components/Text.d.ts +21 -0
  131. package/dist/components/Text.js +77 -0
  132. package/dist/components/TextField.d.ts +25 -0
  133. package/dist/components/TextField.js +68 -0
  134. package/dist/components/Toaster.d.ts +1 -0
  135. package/dist/components/Toaster.js +83 -0
  136. package/dist/components/Tooltip.d.ts +8 -0
  137. package/dist/components/Tooltip.js +43 -0
  138. package/dist/components/controlLook.d.ts +70 -0
  139. package/dist/components/controlLook.js +80 -0
  140. package/dist/components/frame.d.ts +4 -0
  141. package/dist/components/frame.js +2 -0
  142. package/dist/components/icons.d.ts +59 -0
  143. package/dist/components/icons.js +111 -0
  144. package/dist/components/interaction.d.ts +12 -0
  145. package/dist/components/interaction.js +22 -0
  146. package/dist/components/spacing.d.ts +118 -0
  147. package/dist/components/spacing.js +46 -0
  148. package/dist/components/tones.d.ts +28 -0
  149. package/dist/components/tones.js +12 -0
  150. package/dist/entityStore.d.ts +46 -0
  151. package/dist/entityStore.js +160 -0
  152. package/dist/flows/accessFlows.d.ts +9 -0
  153. package/dist/flows/accessFlows.js +167 -0
  154. package/dist/flows/browser.d.ts +34 -0
  155. package/dist/flows/browser.js +167 -0
  156. package/dist/flows/contactSheet.d.ts +1 -0
  157. package/dist/flows/contactSheet.js +63 -0
  158. package/dist/flows/flowBrowser.d.ts +25 -0
  159. package/dist/flows/flowBrowser.js +23 -0
  160. package/dist/flows/flowSetup.d.ts +3 -0
  161. package/dist/flows/flowSetup.js +35 -0
  162. package/dist/flows/flows.d.ts +47 -0
  163. package/dist/flows/flows.js +175 -0
  164. package/dist/flows/screenshotName.d.ts +1 -0
  165. package/dist/flows/screenshotName.js +6 -0
  166. package/dist/gate.d.ts +8 -0
  167. package/dist/gate.js +52 -0
  168. package/dist/hooks/appContext.d.ts +15 -0
  169. package/dist/hooks/appContext.js +5 -0
  170. package/dist/hooks/appHooks.d.ts +21 -0
  171. package/dist/hooks/appHooks.js +24 -0
  172. package/dist/hooks/useAnimatesOnChange.d.ts +1 -0
  173. package/dist/hooks/useAnimatesOnChange.js +14 -0
  174. package/dist/hooks/useStoreState.d.ts +6 -0
  175. package/dist/hooks/useStoreState.js +5 -0
  176. package/dist/index.d.ts +44 -0
  177. package/dist/index.js +21 -0
  178. package/dist/memoryApi.d.ts +19 -0
  179. package/dist/memoryApi.js +42 -0
  180. package/dist/model.d.ts +94 -0
  181. package/dist/model.js +243 -0
  182. package/dist/page.d.ts +11 -0
  183. package/dist/page.js +9 -0
  184. package/dist/platform.d.ts +1 -0
  185. package/dist/platform.js +5 -0
  186. package/dist/publishWire.d.ts +271 -0
  187. package/dist/publishWire.js +168 -0
  188. package/dist/react.d.ts +49 -0
  189. package/dist/react.js +40 -0
  190. package/dist/registered.d.ts +60 -0
  191. package/dist/registered.js +19 -0
  192. package/dist/routes.d.ts +38 -0
  193. package/dist/routes.js +21 -0
  194. package/dist/rules/checkAllFiles.d.ts +1 -0
  195. package/dist/rules/checkAllFiles.js +749 -0
  196. package/dist/rules/checkApiRouting.d.ts +1 -0
  197. package/dist/rules/checkApiRouting.js +112 -0
  198. package/dist/rules/lintPlugin.d.ts +252 -0
  199. package/dist/rules/lintPlugin.js +1159 -0
  200. package/dist/rules/messages.d.ts +48 -0
  201. package/dist/rules/messages.js +48 -0
  202. package/dist/rules/themeTokens.d.ts +6 -0
  203. package/dist/rules/themeTokens.js +64 -0
  204. package/dist/schema.d.ts +4 -0
  205. package/dist/schema.js +2 -0
  206. package/dist/server/auth.d.ts +115 -0
  207. package/dist/server/auth.js +134 -0
  208. package/dist/server/authService.d.ts +9 -0
  209. package/dist/server/authService.js +50 -0
  210. package/dist/server/createServer.d.ts +402 -0
  211. package/dist/server/createServer.js +82 -0
  212. package/dist/server/env.d.ts +15 -0
  213. package/dist/server/env.js +1 -0
  214. package/dist/server/index.d.ts +13 -0
  215. package/dist/server/index.js +9 -0
  216. package/dist/server/services.d.ts +9 -0
  217. package/dist/server/services.js +47 -0
  218. package/dist/server/session.d.ts +29 -0
  219. package/dist/server/session.js +65 -0
  220. package/dist/server/sessions.d.ts +92 -0
  221. package/dist/server/sessions.js +47 -0
  222. package/dist/server/settings.d.ts +49 -0
  223. package/dist/server/settings.js +25 -0
  224. package/dist/server/signInWire.d.ts +53 -0
  225. package/dist/server/signInWire.js +23 -0
  226. package/dist/server/tables.d.ts +242 -0
  227. package/dist/server/tables.js +246 -0
  228. package/dist/server/users.d.ts +189 -0
  229. package/dist/server/users.js +49 -0
  230. package/dist/serviceClients.d.ts +5 -0
  231. package/dist/serviceClients.js +70 -0
  232. package/dist/services.d.ts +53 -0
  233. package/dist/services.js +30 -0
  234. package/dist/shortcuts.d.ts +3 -0
  235. package/dist/shortcuts.js +40 -0
  236. package/dist/start.d.ts +20 -0
  237. package/dist/start.js +145 -0
  238. package/dist/state.d.ts +14 -0
  239. package/dist/state.js +17 -0
  240. package/dist/store.d.ts +45 -0
  241. package/dist/store.js +208 -0
  242. package/dist/subscribers.d.ts +5 -0
  243. package/dist/subscribers.js +13 -0
  244. package/dist/table.d.ts +143 -0
  245. package/dist/table.js +91 -0
  246. package/dist/tables.d.ts +77 -0
  247. package/dist/tables.js +216 -0
  248. package/dist/theme/reset.css +84 -0
  249. package/dist/theme/scales.d.ts +76 -0
  250. package/dist/theme/scales.js +82 -0
  251. package/dist/theme/tokens.stylex.d.ts +218 -0
  252. package/dist/theme/tokens.stylex.js +233 -0
  253. package/dist/vite/commandBridge.d.ts +2 -0
  254. package/dist/vite/commandBridge.js +46 -0
  255. package/dist/vite/designManifest.d.ts +25 -0
  256. package/dist/vite/designManifest.js +191 -0
  257. package/dist/vite/localWorker.d.ts +4 -0
  258. package/dist/vite/localWorker.js +60 -0
  259. package/dist/vite/projectFile.d.ts +9 -0
  260. package/dist/vite/projectFile.js +17 -0
  261. package/dist/vite/rootAlias.d.ts +2 -0
  262. package/dist/vite/rootAlias.js +15 -0
  263. package/dist/vite/vite.d.ts +8 -0
  264. package/dist/vite/vite.js +95 -0
  265. package/dist/wire.d.ts +7 -0
  266. package/dist/wire.js +1 -0
  267. package/package.json +103 -3
  268. package/presets/lintPlugin.js +7 -0
  269. package/presets/oxlint.json +167 -0
  270. package/presets/register.d.ts +21 -0
  271. package/presets/server.d.ts +2 -0
  272. package/presets/tsconfig.app.json +29 -0
  273. package/presets/tsconfig.server.json +27 -0
@@ -0,0 +1,40 @@
1
+ import { useEffect } from 'react';
2
+ const MODIFIER_KEYS = new Set(['Shift', 'Meta', 'Control', 'Alt', 'CapsLock']);
3
+ const OWNS_KEYS = 'input, textarea, select, [contenteditable], [role=menu], [role=listbox], [role=dialog]';
4
+ function isApplePlatform() {
5
+ if (typeof navigator === 'undefined')
6
+ return false;
7
+ return /Mac|iPhone|iPad/.test(navigator.userAgent);
8
+ }
9
+ function isOwnedByControl(target) {
10
+ return target instanceof Element && target.closest(OWNS_KEYS) !== null;
11
+ }
12
+ function shortcutOf(event) {
13
+ if (event.defaultPrevented || event.isComposing)
14
+ return undefined;
15
+ const hasMod = isApplePlatform() ? event.metaKey : event.ctrlKey;
16
+ if (hasMod && !event.altKey)
17
+ return `mod+${event.key.toLowerCase()}`;
18
+ if (event.metaKey || event.ctrlKey || event.altKey || event.repeat) {
19
+ return undefined;
20
+ }
21
+ if (MODIFIER_KEYS.has(event.key) || isOwnedByControl(event.target)) {
22
+ return undefined;
23
+ }
24
+ return event.key;
25
+ }
26
+ export function useShortcuts(pressKey) {
27
+ useEffect(() => {
28
+ const onKeyDown = (event) => {
29
+ const shortcut = shortcutOf(event);
30
+ if (shortcut === undefined)
31
+ return;
32
+ const ran = pressKey(shortcut);
33
+ if (ran !== undefined && shortcut.startsWith('mod+')) {
34
+ event.preventDefault();
35
+ }
36
+ };
37
+ window.addEventListener('keydown', onKeyDown);
38
+ return () => window.removeEventListener('keydown', onKeyDown);
39
+ }, [pressKey]);
40
+ }
@@ -0,0 +1,20 @@
1
+ import type { Theme } from '@stylexjs/stylex';
2
+ import './theme/reset.css';
3
+ import type { AppClass, RunningApp } from './app.ts';
4
+ import type { IconDrawing } from './components/icons.ts';
5
+ import type { Interaction } from './components/interaction.ts';
6
+ import type { ReactApp } from './hooks/appContext.ts';
7
+ import type { Tables } from './table.ts';
8
+ import { borders, colors, controls, fontSizes, fonts, iconSizes, lineHeights, radii, shadows, space, widths } from './theme/tokens.stylex.ts';
9
+ type Themeable = typeof colors | typeof radii | typeof fonts | typeof shadows | typeof space | typeof fontSizes | typeof lineHeights | typeof controls | typeof iconSizes | typeof borders | typeof widths;
10
+ export type StartOptions = {
11
+ theme?: readonly Theme<Themeable>[];
12
+ icons?: Readonly<Record<string, IconDrawing>>;
13
+ onInteraction?: (interaction: Interaction) => void;
14
+ };
15
+ type AnyRunningApp = RunningApp<unknown, {
16
+ readonly path: string;
17
+ }>;
18
+ type RenderableApp = AnyRunningApp & ReactApp;
19
+ export declare function startApp<Group extends Tables, Instance extends RenderableApp>(App: AppClass<Group, Instance>, { theme, icons, onInteraction }?: StartOptions): Instance;
20
+ export {};
package/dist/start.js ADDED
@@ -0,0 +1,145 @@
1
+ import * as stylex from '@stylexjs/stylex';
2
+ import { StrictMode, createElement } from 'react';
3
+ import { createRoot } from 'react-dom/client';
4
+ import { createBrowserRouter } from 'react-router';
5
+ import { RouterProvider } from 'react-router/dom';
6
+ import './theme/reset.css';
7
+ import { browserApi } from "./api.js";
8
+ import { Cache } from "./cache.js";
9
+ import { setAppIcons } from "./components/icons.js";
10
+ import { setInteractionSink } from "./components/interaction.js";
11
+ import { manualClock, realClock } from "./clock.js";
12
+ import { Gate } from "./gate.js";
13
+ import { AppProvider } from "./hooks/appContext.js";
14
+ import { routeObjects } from "./routes.js";
15
+ import { borders, breakpoints, colors, controls, fontFeatures, fontSizes, fontWeights, fonts, iconSizes, lineHeights, radii, shadows, space, widths } from "./theme/tokens.stylex.js";
16
+ const TEST_SEED = 'patternSeed';
17
+ const TEST_ADMINS = 'patternAdmins';
18
+ const TEST_WORLD = 'patternWorld';
19
+ function testSeed() {
20
+ const seed = Reflect.get(window, TEST_SEED);
21
+ return typeof seed === 'object' && seed !== null ? seed : {};
22
+ }
23
+ function testAdmins() {
24
+ const admins = Reflect.get(window, TEST_ADMINS);
25
+ return Array.isArray(admins)
26
+ ? admins.filter((email) => typeof email === 'string')
27
+ : [];
28
+ }
29
+ export function startApp(App, { theme = [], icons = {}, onInteraction } = {}) {
30
+ setAppIcons(icons);
31
+ document.body.className = stylex.props(base.body, theme).className ?? '';
32
+ const hot = import.meta.hot;
33
+ const testParams = new URLSearchParams(hot ? location.search : '');
34
+ const gate = new Gate(testParams.getAll('fail'));
35
+ for (const call of testParams.getAll('hold'))
36
+ gate.hold(call);
37
+ const { tables, routes, services, memory } = App.definition;
38
+ const inMemory = testParams.has('memory');
39
+ const world = inMemory ? (Reflect.get(window, TEST_WORLD) ?? {}) : {};
40
+ const router = createBrowserRouter(routeObjects(routes));
41
+ const app = new App(router, {
42
+ api: browserApi(tables, inMemory ? testSeed() : {}, gate, {
43
+ inMemory,
44
+ admins: inMemory ? testAdmins() : [],
45
+ ...(services === undefined ? {} : { services }),
46
+ ...(memory === undefined ? {} : { memory }),
47
+ world
48
+ }),
49
+ cache: Cache.local(),
50
+ gate,
51
+ clock: inMemory ? manualClock() : realClock()
52
+ });
53
+ if (hot)
54
+ exposeToDevTools(app, gate, hot, world);
55
+ followAppearance(app);
56
+ followTitle(app);
57
+ if (onInteraction !== undefined) {
58
+ setInteractionSink((interaction) => onInteraction({
59
+ ...interaction,
60
+ page: app.router.state.matches.at(-1)?.route.id ?? null
61
+ }));
62
+ }
63
+ const root = document.getElementById('root');
64
+ if (root === null)
65
+ throw new Error('index.html needs a <div id="root">');
66
+ createRoot(root).render(createElement(StrictMode, null, createElement(AppProvider, {
67
+ app,
68
+ children: createElement(RouterProvider, { router: app.router })
69
+ })));
70
+ return app;
71
+ }
72
+ function followAppearance(app) {
73
+ const account = app.models.account;
74
+ const apply = () => {
75
+ const { appearance } = account.currentState;
76
+ document.documentElement.style.colorScheme =
77
+ appearance === 'system' ? 'light dark' : appearance;
78
+ };
79
+ account.subscribe(apply);
80
+ apply();
81
+ }
82
+ function followTitle(app) {
83
+ const store = app.stores.app;
84
+ const apply = () => {
85
+ const { title } = store.currentState;
86
+ if (title !== '')
87
+ document.title = title;
88
+ };
89
+ store.subscribe(apply);
90
+ apply();
91
+ }
92
+ function exposeToDevTools(app, gate, hot, world) {
93
+ async function snapshotWhenIdle() {
94
+ await app.whenIdle();
95
+ return app.getSnapshot();
96
+ }
97
+ function dispatch(raw) {
98
+ const command = app.parse(raw);
99
+ if (typeof command === 'object' && command !== null && 'error' in command) {
100
+ return { error: String(command.error) };
101
+ }
102
+ void app.send(command);
103
+ return { ok: true };
104
+ }
105
+ Object.assign(window, {
106
+ pattern: {
107
+ send: dispatch,
108
+ snapshot: snapshotWhenIdle,
109
+ hold: (call) => gate.hold(call),
110
+ release: (call) => gate.release(call),
111
+ tick: () => app.tick(),
112
+ world: (patch) => {
113
+ if (typeof world === 'object' && world !== null) {
114
+ Object.assign(world, patch);
115
+ }
116
+ }
117
+ }
118
+ });
119
+ if (navigator.webdriver)
120
+ return;
121
+ hot.on('pattern:command', async (message) => {
122
+ const sent = dispatch(message.command);
123
+ hot.send('pattern:reply', {
124
+ id: message.id,
125
+ ...('error' in sent ? sent : { snapshot: await snapshotWhenIdle() })
126
+ });
127
+ });
128
+ }
129
+ const base = stylex.create({
130
+ body: {
131
+ fontFamily: fonts.sans,
132
+ fontSize: {
133
+ default: fontSizes.bodySm,
134
+ [breakpoints.compact]: fontSizes.bodyBase
135
+ },
136
+ fontWeight: fontWeights.normal,
137
+ fontFeatureSettings: fontFeatures.sans,
138
+ lineHeight: {
139
+ default: lineHeights.bodySm,
140
+ [breakpoints.compact]: lineHeights.body
141
+ },
142
+ color: colors.primaryForeground,
143
+ backgroundColor: colors.shell
144
+ }
145
+ });
@@ -0,0 +1,14 @@
1
+ import type { z } from 'zod';
2
+ export type Json = string | number | boolean | null | readonly Json[] | {
3
+ readonly [key: string]: Json;
4
+ };
5
+ export type StateSpec = Record<string, z.ZodDefault<z.ZodType<Json>>>;
6
+ export type StateOf<Spec extends StateSpec> = {
7
+ [Key in keyof Spec]: z.output<Spec[Key]>;
8
+ };
9
+ export type Persisted = {
10
+ read: (key: string) => unknown;
11
+ write: (key: string, value: unknown) => void;
12
+ };
13
+ export declare function defaultsOf<Spec extends StateSpec>(spec: Spec): StateOf<Spec>;
14
+ export declare function restore<Spec extends StateSpec>(spec: Spec, persist: readonly (keyof Spec & string)[], device: Persisted): StateOf<Spec>;
package/dist/state.js ADDED
@@ -0,0 +1,17 @@
1
+ export function defaultsOf(spec) {
2
+ const entries = Object.entries(spec).map(([key, schema]) => [
3
+ key,
4
+ schema.parse(undefined)
5
+ ]);
6
+ return Object.fromEntries(entries);
7
+ }
8
+ export function restore(spec, persist, device) {
9
+ const state = defaultsOf(spec);
10
+ for (const key of persist) {
11
+ const parsed = spec[key]?.safeParse(device.read(key));
12
+ if (parsed?.success) {
13
+ state[key] = parsed.data;
14
+ }
15
+ }
16
+ return state;
17
+ }
@@ -0,0 +1,45 @@
1
+ import type { Persisted, StateOf, StateSpec } from './state.ts';
2
+ export declare function isDevelopment(): boolean;
3
+ export declare function plainJsonProblem(value: unknown, path: string): string | undefined;
4
+ export declare function keepEqualParts<Value>(previous: unknown, next: Value): Value;
5
+ export type Reads = Map<StoreBase<object, unknown>, Map<string, unknown>>;
6
+ export declare function tracked<Value>(run: () => Value): {
7
+ value: Value;
8
+ reads: Reads;
9
+ };
10
+ export declare function mergeReads(from: Reads): void;
11
+ export declare function readsAreCurrent(reads: Reads): boolean;
12
+ export declare function whileBuilding<Value>(run: () => Value): Value;
13
+ export declare function assertNotBuilding(what: string): void;
14
+ export declare abstract class StoreBase<State extends object, Deps = undefined> {
15
+ #private;
16
+ protected readonly deps: Deps;
17
+ constructor(...args: Deps extends undefined ? [] : [deps: Deps]);
18
+ protected abstract initialState(): State;
19
+ get state(): State;
20
+ get currentState(): State;
21
+ readonly subscribe: (listener: () => void) => (() => void);
22
+ protected set(patch: Partial<State>): void;
23
+ protected changed(): void;
24
+ }
25
+ export type StoreConfig<S extends StateSpec, P extends readonly (keyof S & string)[]> = {
26
+ state?: S;
27
+ persist?: P;
28
+ };
29
+ export declare abstract class DeclaredStore<State extends object, Client = Record<never, never>> extends StoreBase<State> {
30
+ protected readonly services: Client;
31
+ constructor(services: Client);
32
+ protected initialState(): State;
33
+ }
34
+ export type StoreClass<S extends StateSpec, P extends readonly (keyof S & string)[], Client = Record<never, never>> = (abstract new (persisted: Persisted, services: Client) => DeclaredStore<StateOf<S>, Client>) & {
35
+ readonly config: StoreConfig<S, P>;
36
+ };
37
+ export declare function defineStore<Client>(): <S extends StateSpec = Record<never, never>, const P extends readonly (keyof S & string)[] = []>(config: StoreConfig<S, P>) => StoreClass<S, P, Client>;
38
+ export type Snapshots<Group> = {
39
+ [Key in keyof Group]: Group[Key] extends {
40
+ readonly state: infer State;
41
+ } ? State : never;
42
+ };
43
+ export declare function snapshotsOf<Group extends Record<string, {
44
+ readonly state: object;
45
+ }>>(group: Group): Snapshots<Group>;
package/dist/store.js ADDED
@@ -0,0 +1,208 @@
1
+ import { restore } from "./state.js";
2
+ import { Subscribers } from "./subscribers.js";
3
+ export function isDevelopment() {
4
+ return import.meta.env?.DEV === true;
5
+ }
6
+ export function plainJsonProblem(value, path) {
7
+ if (value === null || value === undefined)
8
+ return undefined;
9
+ if (typeof value === 'string' || typeof value === 'boolean')
10
+ return undefined;
11
+ if (typeof value === 'number') {
12
+ return Number.isFinite(value) ? undefined : `${path} is ${String(value)}`;
13
+ }
14
+ if (Array.isArray(value)) {
15
+ for (const [index, item] of value.entries()) {
16
+ const problem = plainJsonProblem(item, `${path}[${index}]`);
17
+ if (problem !== undefined)
18
+ return problem;
19
+ }
20
+ return undefined;
21
+ }
22
+ if (typeof value === 'object') {
23
+ const prototype = Object.getPrototypeOf(value);
24
+ if (prototype !== Object.prototype && prototype !== null) {
25
+ return `${path} is a ${value.constructor.name}`;
26
+ }
27
+ for (const [key, item] of Object.entries(value)) {
28
+ const problem = plainJsonProblem(item, `${path}.${key}`);
29
+ if (problem !== undefined)
30
+ return problem;
31
+ }
32
+ return undefined;
33
+ }
34
+ return `${path} is a ${typeof value}`;
35
+ }
36
+ export function keepEqualParts(previous, next) {
37
+ if (Object.is(previous, next))
38
+ return next;
39
+ if (typeof previous !== 'object' ||
40
+ typeof next !== 'object' ||
41
+ previous === null ||
42
+ next === null ||
43
+ Array.isArray(previous) !== Array.isArray(next)) {
44
+ return next;
45
+ }
46
+ const previousKeys = Object.keys(previous);
47
+ const nextKeys = Object.keys(next);
48
+ let allSame = previousKeys.length === nextKeys.length;
49
+ const merged = Array.isArray(next)
50
+ ? Array.from({ length: next.length })
51
+ : {};
52
+ for (const key of nextKeys) {
53
+ const kept = keepEqualParts(Reflect.get(previous, key), Reflect.get(next, key));
54
+ merged[key] = kept;
55
+ if (!Object.is(kept, Reflect.get(previous, key)))
56
+ allSame = false;
57
+ }
58
+ return allSame ? previous : merged;
59
+ }
60
+ let tracking;
61
+ let building = 0;
62
+ const unproxied = new WeakMap();
63
+ export function tracked(run) {
64
+ const outer = tracking;
65
+ const reads = new Map();
66
+ const session = { reads, isLive: true };
67
+ tracking = session;
68
+ try {
69
+ const value = run();
70
+ const plain = typeof value === 'object' && value !== null
71
+ ? unproxied.get(value)
72
+ : undefined;
73
+ return { value: plain ?? value, reads };
74
+ }
75
+ finally {
76
+ session.isLive = false;
77
+ tracking = outer;
78
+ }
79
+ }
80
+ export function mergeReads(from) {
81
+ if (tracking === undefined)
82
+ return;
83
+ for (const [store, keys] of from) {
84
+ const into = tracking.reads.get(store) ?? new Map();
85
+ for (const [key, value] of keys)
86
+ into.set(key, value);
87
+ tracking.reads.set(store, into);
88
+ }
89
+ }
90
+ export function readsAreCurrent(reads) {
91
+ for (const [store, keys] of reads) {
92
+ const state = store.currentState;
93
+ for (const [key, value] of keys) {
94
+ if (!Object.is(Reflect.get(state, key), value))
95
+ return false;
96
+ }
97
+ }
98
+ return true;
99
+ }
100
+ export function whileBuilding(run) {
101
+ building += 1;
102
+ try {
103
+ return run();
104
+ }
105
+ finally {
106
+ building -= 1;
107
+ }
108
+ }
109
+ export function assertNotBuilding(what) {
110
+ if (building > 0) {
111
+ throw new Error(`A getter tried to ${what}. Getters only work answers out from state and stores; put changes in a command.`);
112
+ }
113
+ }
114
+ function hasChanges(state, patch) {
115
+ return Object.entries(patch).some(([key, value]) => !Object.is(state[key], value));
116
+ }
117
+ export class StoreBase {
118
+ deps;
119
+ #state;
120
+ #subscribers = new Subscribers();
121
+ constructor(...args) {
122
+ this.deps = args[0];
123
+ }
124
+ get state() {
125
+ const state = this.currentState;
126
+ const session = tracking;
127
+ if (session === undefined)
128
+ return state;
129
+ const reads = session.reads.get(this) ?? new Map();
130
+ session.reads.set(this, reads);
131
+ const proxy = new Proxy(state, {
132
+ get(target, key) {
133
+ const value = Reflect.get(target, key);
134
+ if (session.isLive && typeof key === 'string')
135
+ reads.set(key, value);
136
+ return value;
137
+ }
138
+ });
139
+ unproxied.set(proxy, state);
140
+ return proxy;
141
+ }
142
+ get currentState() {
143
+ this.#state ??= this.initialState();
144
+ return this.#state;
145
+ }
146
+ subscribe = (listener) => this.#subscribers.subscribe(listener);
147
+ set(patch) {
148
+ assertNotBuilding(`change ${this.constructor.name}`);
149
+ const state = this.currentState;
150
+ if (!hasChanges(state, patch))
151
+ return;
152
+ this.#state = { ...state, ...patch };
153
+ if (isDevelopment()) {
154
+ const problem = plainJsonProblem(this.#state, 'state');
155
+ if (problem !== undefined) {
156
+ throw new Error(`${this.constructor.name} state must be plain JSON, because flows compare it and the cache stores it: ${problem}`);
157
+ }
158
+ }
159
+ this.changed();
160
+ }
161
+ changed() {
162
+ this.#subscribers.notify();
163
+ }
164
+ }
165
+ export class DeclaredStore extends StoreBase {
166
+ services;
167
+ constructor(services) {
168
+ super();
169
+ this.services = services;
170
+ }
171
+ initialState() {
172
+ throw new Error(`${this.constructor.name} must extend Store({ state }) so its first state comes from the config`);
173
+ }
174
+ }
175
+ export function defineStore() {
176
+ return function Store(config) {
177
+ return declaredStore(config);
178
+ };
179
+ }
180
+ function declaredStore(config) {
181
+ const persist = config.persist ?? [];
182
+ class Declared extends DeclaredStore {
183
+ static config = config;
184
+ #persisted;
185
+ constructor(persisted, services) {
186
+ super(services);
187
+ this.#persisted = persisted;
188
+ }
189
+ initialState() {
190
+ return restore(config.state ?? {}, persist, this.#persisted);
191
+ }
192
+ set(patch) {
193
+ super.set(patch);
194
+ for (const key of persist) {
195
+ if (key in patch)
196
+ this.#persisted.write(key, this.state[key]);
197
+ }
198
+ }
199
+ }
200
+ return Declared;
201
+ }
202
+ export function snapshotsOf(group) {
203
+ const entries = Object.entries(group).map(([key, store]) => [
204
+ key,
205
+ store.state
206
+ ]);
207
+ return Object.fromEntries(entries);
208
+ }
@@ -0,0 +1,5 @@
1
+ export declare class Subscribers {
2
+ #private;
3
+ readonly subscribe: (listener: () => void) => (() => void);
4
+ notify(): void;
5
+ }
@@ -0,0 +1,13 @@
1
+ export class Subscribers {
2
+ #listeners = new Set();
3
+ subscribe = (listener) => {
4
+ this.#listeners.add(listener);
5
+ return () => {
6
+ this.#listeners.delete(listener);
7
+ };
8
+ };
9
+ notify() {
10
+ for (const listener of this.#listeners)
11
+ listener();
12
+ }
13
+ }
@@ -0,0 +1,143 @@
1
+ import { z } from 'zod';
2
+ import type { SameOnTheWire } from './wire.ts';
3
+ export type Value = string | number | boolean | null;
4
+ export type Columns = Record<string, z.ZodType<Value>>;
5
+ export type Level = 'anyone' | 'signedIn' | 'owner' | 'admin' | 'server';
6
+ export type Access = {
7
+ readonly read: Level;
8
+ readonly create: Level;
9
+ readonly update: Level;
10
+ readonly delete: Level;
11
+ };
12
+ export type Writes = 'optimistic' | 'confirmed';
13
+ export type TableDefinition = {
14
+ readonly columns: Columns;
15
+ readonly access: Access;
16
+ readonly writes: Writes;
17
+ readonly owned?: boolean;
18
+ readonly public?: readonly string[];
19
+ readonly unique?: readonly (readonly string[])[];
20
+ };
21
+ type OwnerColumn<Owned> = [Owned] extends [true] ? {
22
+ ownerId: z.ZodString;
23
+ } : Record<never, never>;
24
+ export type Table = {
25
+ readonly name: string;
26
+ readonly columns: Columns;
27
+ readonly access: Access;
28
+ readonly writes: Writes;
29
+ readonly owned: boolean;
30
+ readonly public: readonly string[];
31
+ readonly unique: readonly (readonly string[])[];
32
+ readonly fields: z.ZodObject<Columns>;
33
+ readonly incoming: z.ZodObject<{
34
+ id: z.ZodString;
35
+ } & Columns>;
36
+ readonly row: z.ZodObject<{
37
+ id: z.ZodString;
38
+ createdAt: z.ZodString;
39
+ } & Columns>;
40
+ readonly publicRow: z.ZodObject<{
41
+ id: z.ZodString;
42
+ } & Columns>;
43
+ };
44
+ export type TableOf<Name extends string, Definition extends TableDefinition> = {
45
+ readonly name: Name;
46
+ readonly columns: Definition['columns'];
47
+ readonly access: Definition['access'];
48
+ readonly writes: Definition['writes'];
49
+ readonly owned: Definition['owned'] extends true ? true : false;
50
+ readonly public: readonly PublicColumn<Definition>[];
51
+ readonly unique: readonly (readonly string[])[];
52
+ readonly fields: z.ZodObject<Definition['columns']>;
53
+ readonly incoming: z.ZodObject<{
54
+ id: z.ZodString;
55
+ } & Definition['columns']>;
56
+ readonly row: z.ZodObject<{
57
+ id: z.ZodString;
58
+ createdAt: z.ZodString;
59
+ } & OwnerColumn<Definition['owned']> & Definition['columns']>;
60
+ readonly publicRow: z.ZodObject<{
61
+ id: z.ZodString;
62
+ } & Pick<{
63
+ createdAt: z.ZodString;
64
+ } & Definition['columns'], PublicColumn<Definition>>>;
65
+ };
66
+ type PublicColumn<Definition extends TableDefinition> = Definition['public'] extends readonly (infer Column)[] ? Column & (keyof Definition['columns'] | 'createdAt') & string : never;
67
+ export type Fields<T extends Table> = z.output<T['fields']>;
68
+ type OwnerOf<T extends Table> = T['owned'] extends true ? {
69
+ ownerId: string;
70
+ } : Record<never, never>;
71
+ export type Row<T extends Table> = {
72
+ id: string;
73
+ createdAt: string;
74
+ } & OwnerOf<T> & Fields<T>;
75
+ export type PublicRow<T extends Table> = {
76
+ id: string;
77
+ } & Pick<{
78
+ createdAt: string;
79
+ } & Fields<T>, T['public'][number] & ('createdAt' | keyof Fields<T>)>;
80
+ export type Tables = Record<string, Table>;
81
+ export type SeedRow<T extends Table> = {
82
+ id: string;
83
+ } & (T['owned'] extends true ? {
84
+ owner: string;
85
+ } : Record<never, never>) & Fields<T>;
86
+ export type Seed<Group extends Tables> = {
87
+ [Key in keyof Group]?: readonly SeedRow<Group[Key]>[];
88
+ };
89
+ export type TablesOf<Definitions extends Record<string, DraftTable>> = {
90
+ [Key in keyof Definitions & string]: Definitions[Key] extends TableDefinition ? TableOf<Key, Definitions[Key]> : Table;
91
+ };
92
+ type DraftTable = {
93
+ readonly columns: Columns;
94
+ readonly access?: Partial<Access>;
95
+ readonly writes?: Writes;
96
+ readonly owned?: boolean;
97
+ readonly public?: readonly string[];
98
+ readonly unique?: readonly (readonly string[])[];
99
+ };
100
+ type StaticTypeError<Message extends string> = Message;
101
+ type IsOwned<Definition> = Definition extends {
102
+ readonly owned: true;
103
+ } ? true : false;
104
+ type HasUnique<Definition> = Definition extends {
105
+ readonly unique: readonly [unknown, ...unknown[]];
106
+ } ? true : false;
107
+ type ReadOf<Definition> = Definition extends {
108
+ readonly access: {
109
+ readonly read: infer Read;
110
+ };
111
+ } ? Read : never;
112
+ type ColumnOf<Definition> = Definition extends {
113
+ readonly columns: infer C;
114
+ } ? keyof C & string : never;
115
+ type OwnerNeedsOwned = StaticTypeError<"'owner' needs owned: true on this table, so every row records who made it">;
116
+ type CreateIsNeverOwner = StaticTypeError<"create can't be 'owner': nobody owns a row before it exists. Use 'signedIn' and the person who creates it becomes its owner">;
117
+ type OwnedNeedsSignedInCreate = StaticTypeError<"An owned table can't let anyone create rows: a signed-out visitor has no account to own the row. Use 'signedIn'">;
118
+ type UniqueNeedsConfirmed = StaticTypeError<"This table has a unique constraint, so the server can refuse a write. Declare writes: 'confirmed' so the screen waits for the server instead of showing a row that then disappears">;
119
+ type PublicNeedsPrivateRead = StaticTypeError<"public only matters when read is stricter than 'anyone': with read: 'anyone' every column is already public">;
120
+ type AccessIsRequired = StaticTypeError<"Every table says who may read, create, update and delete its rows: add access: { read, create, update, delete }, each 'anyone', 'signedIn', 'owner', 'admin' or 'server'">;
121
+ type EveryOperation = StaticTypeError<'access names all four operations: read, create, update and delete'>;
122
+ type WritesIsRequired = StaticTypeError<"Every table says whether a write shows at once or waits for the server: add writes: 'optimistic' or writes: 'confirmed'">;
123
+ type Missing<Shape, Required extends string, Message> = {
124
+ [Key in Exclude<Required, keyof Shape>]: Message;
125
+ };
126
+ type OwnerIdIsPattern = StaticTypeError<'Pattern adds ownerId to owned tables and fills it from the session: remove this column and set owned: true'>;
127
+ type CheckedLevel<Definition, Operation, Chosen> = Chosen extends 'owner' ? Operation extends 'create' ? CreateIsNeverOwner : IsOwned<Definition> extends true ? Chosen : OwnerNeedsOwned : Operation extends 'create' ? Chosen extends 'anyone' ? IsOwned<Definition> extends true ? OwnedNeedsSignedInCreate : Chosen : Chosen : Chosen;
128
+ type CheckedAccess<Definition, A> = {
129
+ [Operation in keyof A]: CheckedLevel<Definition, Operation, A[Operation]>;
130
+ } & Missing<A, keyof Access, EveryOperation>;
131
+ type CheckedColumns<C> = {
132
+ [Key in keyof C]: Key extends 'ownerId' ? OwnerIdIsPattern : C[Key];
133
+ };
134
+ type CheckedTable<Definition> = {
135
+ [Key in keyof Definition]: Key extends 'columns' ? CheckedColumns<SameOnTheWire<Definition[Key] & z.ZodRawShape>> : Key extends 'access' ? CheckedAccess<Definition, Definition[Key]> : Key extends 'writes' ? HasUnique<Definition> extends true ? Definition[Key] extends 'confirmed' ? Definition[Key] : UniqueNeedsConfirmed : Definition[Key] : Key extends 'public' ? ReadOf<Definition> extends 'anyone' ? PublicNeedsPrivateRead : Definition[Key] & readonly (ColumnOf<Definition> | 'createdAt')[] : Key extends 'unique' ? Definition[Key] & readonly (readonly ColumnOf<Definition>[])[] : Definition[Key];
136
+ } & Missing<Definition, 'access', AccessIsRequired> & Missing<Definition, 'writes', WritesIsRequired>;
137
+ export declare function defineTables<const Definitions extends Record<string, DraftTable> = Record<never, never>>(definitions: {
138
+ [Key in keyof Definitions]: CheckedTable<Definitions[Key]>;
139
+ }): TablesOf<Definitions>;
140
+ export declare function ownerIdOf(email: string): string;
141
+ export declare function seededRows(seed: Seed<Tables>, name: string): readonly Row<Table>[];
142
+ export declare function snakeCase(name: string): string;
143
+ export {};