@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
package/dist/app.d.ts ADDED
@@ -0,0 +1,115 @@
1
+ import type { DataRouter } from 'react-router';
2
+ import type { AppApi } from './api.ts';
3
+ import { AppStore } from './appInfo.ts';
4
+ import { SessionStore } from './auth/sessionStore.ts';
5
+ import type { Cache } from './cache.ts';
6
+ import type { Clock } from './clock.ts';
7
+ import type { Gate } from './gate.ts';
8
+ import { ActionsStore } from './actions.ts';
9
+ import type { Action, ActionsBuilder } from './actions.ts';
10
+ import { Toasts } from './builtins/Toasts.ts';
11
+ import { PageStore } from './page.ts';
12
+ import type { BuiltInModels } from './registered.ts';
13
+ import type { Snapshots } from './store.ts';
14
+ import type { Json, Persisted } from './state.ts';
15
+ import type { DerivedOf, Instances, MessagesOf, ModelClassLike, ModelSnapshots } from './model.ts';
16
+ import type { PageOf, PathOf, RouteDefinition } from './routes.ts';
17
+ import type { Services } from './services.ts';
18
+ import type { Tables } from './table.ts';
19
+ import { PublicTableStore, TableStore } from './tables.ts';
20
+ type Observed = {
21
+ readonly state: object;
22
+ readonly subscribe: (listener: () => void) => () => void;
23
+ };
24
+ export type { DataRouter };
25
+ export type ModelServicesOf<Path extends string> = {
26
+ navigate: (to: Path) => Promise<void>;
27
+ every: (ms: number, run: () => void | Promise<void>) => () => void;
28
+ };
29
+ type StaticTypeError<Text extends string> = Text;
30
+ type NonJsonGetters<Instance> = {
31
+ [Key in keyof DerivedOf<Instance>]: DerivedOf<Instance>[Key] extends Json | undefined ? never : Key;
32
+ }[keyof DerivedOf<Instance>];
33
+ export type PlainJsonModel<Class> = Class extends abstract new (...args: never[]) => infer Instance ? [NonJsonGetters<Instance>] extends [never] ? Class : StaticTypeError<`Getter ${NonJsonGetters<Instance> & string} must return plain JSON (strings, numbers, booleans, null, and arrays and objects of those, or undefined), because flows compare the snapshot`> : Class;
34
+ export type NavigateCommand<Path extends string> = {
35
+ type: 'app.navigate';
36
+ to: Path;
37
+ };
38
+ export type PressKeyCommand = {
39
+ type: 'app.pressKey';
40
+ key: string;
41
+ };
42
+ export type AppDependencies<Api> = {
43
+ api: Api;
44
+ cache: Cache;
45
+ gate: Gate;
46
+ clock?: Clock;
47
+ };
48
+ export type RunningApp<Command, Snapshot extends {
49
+ readonly path: string;
50
+ }> = {
51
+ readonly router: DataRouter;
52
+ send(command: Command): Promise<void>;
53
+ parse(raw: unknown): Command | {
54
+ error: string;
55
+ };
56
+ getSnapshot(): Snapshot;
57
+ whenIdle(): Promise<void>;
58
+ tick(): void;
59
+ };
60
+ export type StoreClasses = Record<string, new (persisted: Persisted, services: never) => Observed>;
61
+ export type StoreInstances<Classes extends StoreClasses> = {
62
+ [Key in keyof Classes]: InstanceType<Classes[Key]>;
63
+ };
64
+ export type TableStores<Group extends Tables> = {
65
+ [Key in keyof Group as Group[Key]['access']['read'] extends 'server' ? never : Key]: TableStore<Group[Key]>;
66
+ };
67
+ export type PublicStores<Group extends Tables> = {
68
+ [Key in keyof Group & string as [Group[Key]['public'][number]] extends [
69
+ never
70
+ ] ? never : `public${Capitalize<Key>}`]: PublicTableStore<Group[Key]>;
71
+ };
72
+ export type StoresOf<Group extends Tables, Custom, Item extends Action = Action> = {
73
+ session: SessionStore;
74
+ app: AppStore;
75
+ actions: ActionsStore<Item>;
76
+ toasts: Toasts;
77
+ page: PageStore;
78
+ } & TableStores<Group> & PublicStores<Group> & Custom;
79
+ export type AppDefinition<Group extends Tables, Custom extends StoreClasses, Classes extends Record<string, ModelClassLike>, Routes extends readonly RouteDefinition[]> = {
80
+ tables: Group;
81
+ stores?: Custom;
82
+ models: {
83
+ [Key in keyof Classes]: Key extends keyof BuiltInModels | 'app' ? StaticTypeError<`${Key & string} is a built-in model of @trypattern/sdk: its state, answers and commands come from Pattern. Name yours for what it holds.`> : PlainJsonModel<Classes[Key]>;
84
+ };
85
+ routes: Routes;
86
+ actions?: readonly ActionsBuilder<never, Action>[];
87
+ actionGroups?: readonly string[];
88
+ services?: Services;
89
+ memory?: object;
90
+ };
91
+ export type AppClass<Group extends Tables, Instance> = {
92
+ new (router: DataRouter, deps: AppDependencies<AppApi<Group>>): Instance;
93
+ readonly definition: {
94
+ readonly tables: Group;
95
+ readonly routes: readonly RouteDefinition[];
96
+ readonly services?: Services;
97
+ readonly memory?: object;
98
+ };
99
+ };
100
+ export type AppSnapshotShape<Models, Stores, Page> = {
101
+ page: Page | null;
102
+ path: string;
103
+ models: ModelSnapshots<Models>;
104
+ stores: Snapshots<Stores>;
105
+ };
106
+ type AppCommandOf<Classes extends Record<string, ModelClassLike>, Routes extends readonly RouteDefinition[]> = MessagesOf<Instances<BuiltInModels>> | MessagesOf<Instances<Classes>> | NavigateCommand<PathOf<Routes>> | PressKeyCommand;
107
+ export type DefinedApp<Group extends Tables, Classes extends Record<string, ModelClassLike>, Routes extends readonly RouteDefinition[], Custom extends StoreClasses> = {
108
+ new (router: DataRouter, dependencies: AppDependencies<AppApi<Group>>): RunningApp<AppCommandOf<Classes, Routes>, AppSnapshotShape<Instances<BuiltInModels & Classes>, StoresOf<Group, StoreInstances<Custom>>, PageOf<Routes>>> & {
109
+ readonly models: Instances<BuiltInModels & Classes>;
110
+ readonly stores: StoresOf<Group, StoreInstances<Custom>>;
111
+ pressKey(key: string): Promise<void> | undefined;
112
+ };
113
+ readonly definition: AppDefinition<Group, Custom, Classes, Routes>;
114
+ };
115
+ export declare function defineApp<Group extends Tables, Classes extends Record<string, ModelClassLike>, Routes extends readonly RouteDefinition[], Custom extends StoreClasses = Record<never, never>>(definition: AppDefinition<Group, Custom, Classes, Routes>): DefinedApp<Group, Classes, Routes, Custom>;
package/dist/app.js ADDED
@@ -0,0 +1,266 @@
1
+ import { z } from 'zod';
2
+ import { viewerOf } from "./access.js";
3
+ import { AppStore } from "./appInfo.js";
4
+ import { SessionStore } from "./auth/sessionStore.js";
5
+ import { realClock } from "./clock.js";
6
+ import { ActionsStore } from "./actions.js";
7
+ import { Account } from "./builtins/Account.js";
8
+ import { AppSettings } from "./builtins/AppSettings.js";
9
+ import { BUILT_IN_GROUPS, builtInActions } from "./builtins/actions.js";
10
+ import { Palette } from "./builtins/Palette.js";
11
+ import { SignIn } from "./builtins/SignIn.js";
12
+ import { Toasts } from "./builtins/Toasts.js";
13
+ import { PageStore } from "./page.js";
14
+ import { assertNotBuilding, snapshotsOf } from "./store.js";
15
+ import { ModelBase, command, createModels, modelSnapshotsOf, parseMessage, sendMessage } from "./model.js";
16
+ import { isRoutedPath, pageIds, registerRoutes } from "./routes.js";
17
+ import { PublicTableStore, TableStore, publicStoreName } from "./tables.js";
18
+ function dataStores(tables, api, cache, viewer) {
19
+ const stores = { tables: {}, publicViews: {} };
20
+ for (const [key, table] of Object.entries(tables)) {
21
+ const found = Reflect.get(api, key);
22
+ const tableApi = found;
23
+ const publicView = table.public.length > 0
24
+ ? new PublicTableStore(table, { api: tableApi, cache })
25
+ : undefined;
26
+ if (publicView !== undefined) {
27
+ stores.publicViews[publicStoreName(key)] = publicView;
28
+ }
29
+ if (table.access.read !== 'server') {
30
+ stores.tables[key] = new TableStore(table, {
31
+ api: tableApi,
32
+ cache,
33
+ viewer,
34
+ ...(publicView === undefined ? {} : { publicView })
35
+ });
36
+ }
37
+ }
38
+ return stores;
39
+ }
40
+ function clashOf(groups) {
41
+ const seen = new Set();
42
+ for (const name of groups.flat()) {
43
+ if (seen.has(name))
44
+ return name;
45
+ seen.add(name);
46
+ }
47
+ return undefined;
48
+ }
49
+ const builtInModels = {
50
+ account: Account,
51
+ appSettings: AppSettings,
52
+ palette: Palette,
53
+ signIn: SignIn
54
+ };
55
+ const appPathInput = z.custom((value) => typeof value === 'string' && isRoutedPath(value), { error: 'Not a page in src/routes.tsx' });
56
+ export function defineApp(definition) {
57
+ const pages = new Set(pageIds(definition.routes));
58
+ registerRoutes(definition.routes);
59
+ if (Object.hasOwn(definition.models, 'app')) {
60
+ throw new Error('A model can\'t be called "app": app.navigate is the built-in command that changes the page');
61
+ }
62
+ for (const name of Object.keys(builtInModels)) {
63
+ if (Object.hasOwn(definition.models, name)) {
64
+ throw new Error(`${name} is a built-in model of @trypattern/sdk: its state, answers and commands come from Pattern. Name yours for what it holds.`);
65
+ }
66
+ }
67
+ return class App {
68
+ static definition = definition;
69
+ router;
70
+ models;
71
+ stores;
72
+ #commandGroups;
73
+ #page = new PageStore();
74
+ #cache;
75
+ #gate;
76
+ #session;
77
+ #data;
78
+ #navigations = new Set();
79
+ #repeats = new Set();
80
+ #clock;
81
+ #failures = [];
82
+ #path;
83
+ #user;
84
+ constructor(router, { api, cache, gate, clock = realClock() }) {
85
+ this.router = router;
86
+ this.#cache = cache;
87
+ this.#gate = gate;
88
+ this.#clock = clock;
89
+ const session = new SessionStore({ api: api.session });
90
+ const app = new AppStore({
91
+ api: api.app,
92
+ remembered: cache.scope('app.title', z.string())
93
+ });
94
+ const data = dataStores(definition.tables, api.tables, cache, () => viewerOf(session.state.user));
95
+ const custom = Object.fromEntries(Object.entries(definition.stores ?? {}).map(([key, Store]) => [
96
+ key,
97
+ new Store(cache.persisted(`stores.${key}`), api.services)
98
+ ]));
99
+ const toasts = new Toasts(cache.persisted('stores.toasts'), {});
100
+ const clash = clashOf([
101
+ ['session', 'app', 'actions', 'toasts', 'page'],
102
+ Object.keys(data.tables),
103
+ Object.keys(data.publicViews),
104
+ Object.keys(custom)
105
+ ]);
106
+ if (clash !== undefined) {
107
+ throw new Error(`Two stores are called "${clash}". The built-in session, app, actions, toasts and page stores, each table, each table's public view and each store in src/stores/index.ts need their own name.`);
108
+ }
109
+ const actions = new ActionsStore(this.#page, [...(definition.actions ?? []), ...builtInActions], [
110
+ ...(definition.actionGroups ?? []),
111
+ ...BUILT_IN_GROUPS.filter((group) => !(definition.actionGroups ?? []).includes(group))
112
+ ], (message) => this.#runAction(message));
113
+ this.stores = {
114
+ session,
115
+ app,
116
+ actions,
117
+ toasts,
118
+ page: this.#page,
119
+ ...data.tables,
120
+ ...data.publicViews,
121
+ ...custom
122
+ };
123
+ this.#session = session;
124
+ this.#data = data;
125
+ const services = {
126
+ navigate: (to) => {
127
+ assertNotBuilding('navigate');
128
+ return this.#navigate(to);
129
+ },
130
+ every: (ms, run) => {
131
+ assertNotBuilding('repeat');
132
+ return this.#every(ms, run);
133
+ }
134
+ };
135
+ this.models = createModels({ ...builtInModels, ...definition.models }, this.stores, services, (model) => cache.persisted(model));
136
+ this.#commandGroups = {
137
+ ...this.models,
138
+ app: {
139
+ navigate: command({ input: { to: appPathInput } }, ({ to }) => this.#navigate(to)),
140
+ pressKey: command({ input: { key: z.string() } }, ({ key }) => this.pressKey(key))
141
+ }
142
+ };
143
+ this.#path = router.state.location.pathname;
144
+ this.#page.moved(this.#currentPage(), this.#currentParams());
145
+ actions.watch(this.models);
146
+ this.#user = session.state.user?.id ?? null;
147
+ router.subscribe(() => this.#routeChanged());
148
+ session.subscribe(() => this.#sessionChanged());
149
+ void session.confirm();
150
+ void app.load();
151
+ for (const view of Object.values(data.publicViews))
152
+ void view.load();
153
+ this.#loadReadableTables();
154
+ }
155
+ send(command) {
156
+ return this.#send(command);
157
+ }
158
+ pressKey(key) {
159
+ return this.stores.actions.pressKey(key);
160
+ }
161
+ parse(raw) {
162
+ return parseMessage(raw, this.#commandGroups);
163
+ }
164
+ getSnapshot() {
165
+ return {
166
+ page: this.#currentPage(),
167
+ path: this.router.state.location.pathname,
168
+ models: modelSnapshotsOf(this.models),
169
+ stores: snapshotsOf(this.stores)
170
+ };
171
+ }
172
+ tick() {
173
+ this.#clock.tick();
174
+ }
175
+ async whenIdle() {
176
+ do {
177
+ await Promise.allSettled(this.#navigations);
178
+ await this.#gate.whenIdle();
179
+ } while (this.#navigations.size > 0);
180
+ const failure = this.#failures.shift();
181
+ if (failure !== undefined)
182
+ throw failure;
183
+ }
184
+ #runAction(message) {
185
+ const parsed = this.parse(message);
186
+ if ('error' in parsed) {
187
+ return Promise.reject(new Error(String(parsed.error)));
188
+ }
189
+ return this.#send(parsed);
190
+ }
191
+ #send(command) {
192
+ return sendMessage(this.#commandGroups, command).catch((failure) => {
193
+ this.#failures.push(failure);
194
+ });
195
+ }
196
+ #currentPage() {
197
+ const id = this.router.state.matches.at(-1)?.route.id;
198
+ return id !== undefined && pages.has(id) ? id : null;
199
+ }
200
+ #currentParams() {
201
+ return this.router.state.matches.at(-1)?.params ?? {};
202
+ }
203
+ #navigate(to) {
204
+ const navigation = this.router.navigate(to).finally(() => {
205
+ this.#navigations.delete(navigation);
206
+ });
207
+ this.#navigations.add(navigation);
208
+ return navigation;
209
+ }
210
+ #every(ms, run) {
211
+ const stop = this.#clock.every(ms, () => {
212
+ void Promise.resolve(run()).catch((failure) => {
213
+ this.#failures.push(failure);
214
+ });
215
+ });
216
+ const remove = () => {
217
+ stop();
218
+ this.#repeats.delete(remove);
219
+ };
220
+ this.#repeats.add(remove);
221
+ return remove;
222
+ }
223
+ #stopRepeats() {
224
+ for (const stop of [...this.#repeats])
225
+ stop();
226
+ }
227
+ #routeChanged() {
228
+ const path = this.router.state.location.pathname;
229
+ if (path === this.#path)
230
+ return;
231
+ this.#path = path;
232
+ this.#stopRepeats();
233
+ this.#page.moved(this.#currentPage(), this.#currentParams());
234
+ this.#openCurrentModel();
235
+ }
236
+ #sessionChanged() {
237
+ const user = this.#session.state.user?.id ?? null;
238
+ if (user === this.#user)
239
+ return;
240
+ this.#user = user;
241
+ this.#stopRepeats();
242
+ this.#cache.clear();
243
+ for (const store of Object.values(this.#data.tables))
244
+ store.forget();
245
+ this.#loadReadableTables();
246
+ }
247
+ #loadReadableTables() {
248
+ for (const store of Object.values(this.#data.tables))
249
+ void store.load();
250
+ this.#openCurrentModel();
251
+ }
252
+ #openCurrentModel() {
253
+ const match = this.router.state.matches.at(-1);
254
+ if (match === undefined)
255
+ return;
256
+ const model = Object.hasOwn(this.models, match.route.id)
257
+ ? Reflect.get(this.models, match.route.id)
258
+ : undefined;
259
+ if (!(model instanceof ModelBase))
260
+ return;
261
+ void Promise.resolve(model.onPageOpen(match.params)).catch((failure) => {
262
+ this.#failures.push(failure);
263
+ });
264
+ }
265
+ };
266
+ }
@@ -0,0 +1,35 @@
1
+ import type { AppInfo } from './appTitle.ts';
2
+ import type { Cached } from './cache.ts';
3
+ import type { Gate } from './gate.ts';
4
+ import type { MemoryApiOptions } from './memoryApi.ts';
5
+ import { StoreBase } from './store.ts';
6
+ export type AppInfoApi = {
7
+ read: () => Promise<AppInfo>;
8
+ rename: (title: string) => Promise<AppInfo>;
9
+ };
10
+ export type AppInfoCall = 'read' | 'rename';
11
+ export declare const appInfoCalls: readonly AppInfoCall[];
12
+ export declare class HttpAppInfoApi implements AppInfoApi {
13
+ #private;
14
+ constructor(gate: Gate);
15
+ read(): Promise<AppInfo>;
16
+ rename(title: string): Promise<AppInfo>;
17
+ }
18
+ export declare class MemoryAppInfoApi implements AppInfoApi {
19
+ #private;
20
+ constructor(title: string, options: MemoryApiOptions);
21
+ read(): Promise<AppInfo>;
22
+ rename(title: string): Promise<AppInfo>;
23
+ }
24
+ export type AppState = {
25
+ title: string;
26
+ };
27
+ export declare class AppStore extends StoreBase<AppState, {
28
+ api: AppInfoApi;
29
+ remembered: Cached<string>;
30
+ }> {
31
+ #private;
32
+ protected initialState(): AppState;
33
+ load(): Promise<void>;
34
+ rename(title: string): Promise<void>;
35
+ }
@@ -0,0 +1,67 @@
1
+ import { appInfo } from "./appTitle.js";
2
+ import { MemoryServer } from "./memoryApi.js";
3
+ import { StoreBase } from "./store.js";
4
+ export const appInfoCalls = ['read', 'rename'];
5
+ export class HttpAppInfoApi {
6
+ #gate;
7
+ constructor(gate) {
8
+ this.#gate = gate;
9
+ }
10
+ read() {
11
+ return this.#gate.pass('app.read', async () => {
12
+ const response = await fetch('/api/app');
13
+ if (!response.ok)
14
+ throw new Error('app.read failed');
15
+ return appInfo.parse(await response.json());
16
+ });
17
+ }
18
+ rename(title) {
19
+ return this.#gate.pass('app.rename', async () => {
20
+ const response = await fetch('/api/app', {
21
+ method: 'PUT',
22
+ headers: { 'content-type': 'application/json' },
23
+ body: JSON.stringify({ title })
24
+ });
25
+ if (!response.ok)
26
+ throw new Error('app.rename failed');
27
+ return appInfo.parse(await response.json());
28
+ });
29
+ }
30
+ }
31
+ export class MemoryAppInfoApi {
32
+ #server;
33
+ #title;
34
+ constructor(title, options) {
35
+ this.#server = new MemoryServer('app', options);
36
+ this.#title = title;
37
+ }
38
+ read() {
39
+ return this.#server.reply('read', () => ({ title: this.#title }));
40
+ }
41
+ rename(title) {
42
+ return this.#server.reply('rename', () => {
43
+ this.#title = title;
44
+ return { title };
45
+ });
46
+ }
47
+ }
48
+ export class AppStore extends StoreBase {
49
+ initialState() {
50
+ return { title: this.deps.remembered.read() ?? '' };
51
+ }
52
+ async load() {
53
+ try {
54
+ this.#keep(await this.deps.api.read());
55
+ }
56
+ catch {
57
+ return;
58
+ }
59
+ }
60
+ async rename(title) {
61
+ this.#keep(await this.deps.api.rename(title));
62
+ }
63
+ #keep({ title }) {
64
+ this.set({ title });
65
+ this.deps.remembered.write(title);
66
+ }
67
+ }
@@ -0,0 +1,6 @@
1
+ import { z } from 'zod';
2
+ export declare const appTitle: z.ZodString;
3
+ export declare const appInfo: z.ZodObject<{
4
+ title: z.ZodString;
5
+ }, z.core.$strip>;
6
+ export type AppInfo = z.output<typeof appInfo>;
@@ -0,0 +1,3 @@
1
+ import { z } from 'zod';
2
+ export const appTitle = z.string().trim().min(1).max(60);
3
+ export const appInfo = z.object({ title: z.string() });
@@ -0,0 +1,7 @@
1
+ import { z } from 'zod';
2
+ export declare const appearance: z.ZodEnum<{
3
+ system: "system";
4
+ light: "light";
5
+ dark: "dark";
6
+ }>;
7
+ export type Appearance = z.infer<typeof appearance>;
@@ -0,0 +1,2 @@
1
+ import { z } from 'zod';
2
+ export const appearance = z.enum(['system', 'light', 'dark']);
@@ -0,0 +1,41 @@
1
+ import type { Gate } from '../gate.ts';
2
+ import type { MemoryApiOptions } from '../memoryApi.ts';
3
+ import type { User } from './user.ts';
4
+ export type SessionApi = {
5
+ rememberedUser: () => User | undefined;
6
+ me: () => Promise<User | undefined>;
7
+ devSignIn: (email: string) => Promise<User>;
8
+ sendEmailCode: (email: string) => Promise<EmailCodeOutcome>;
9
+ verifyEmailCode: (code: string) => Promise<User | undefined>;
10
+ signOut: () => Promise<void>;
11
+ startGoogleSignIn: () => void;
12
+ onSignedOut: (listener: () => void) => void;
13
+ };
14
+ export type EmailCodeOutcome = 'sent' | 'refused';
15
+ export type SessionCall = 'me' | 'devSignIn' | 'sendEmailCode' | 'verifyEmailCode' | 'signOut';
16
+ export declare const sessionCalls: readonly SessionCall[];
17
+ export declare class HttpSessionApi implements SessionApi {
18
+ #private;
19
+ constructor(gate: Gate);
20
+ rememberedUser(): User | undefined;
21
+ me(): Promise<User | undefined>;
22
+ devSignIn(email: string): Promise<User>;
23
+ sendEmailCode(email: string): Promise<EmailCodeOutcome>;
24
+ verifyEmailCode(code: string): Promise<User | undefined>;
25
+ signOut(): Promise<void>;
26
+ startGoogleSignIn(): void;
27
+ onSignedOut(listener: () => void): void;
28
+ reportSignedOut(): void;
29
+ }
30
+ export declare class MemorySessionApi implements SessionApi {
31
+ #private;
32
+ constructor(signedInAs: string | null, admins: readonly string[], options: MemoryApiOptions);
33
+ rememberedUser(): User | undefined;
34
+ me(): Promise<User | undefined>;
35
+ devSignIn(email: string): Promise<User>;
36
+ sendEmailCode(email: string): Promise<EmailCodeOutcome>;
37
+ verifyEmailCode(code: string): Promise<User | undefined>;
38
+ signOut(): Promise<void>;
39
+ startGoogleSignIn(): void;
40
+ onSignedOut(): void;
41
+ }