@trypattern/sdk 0.0.0-stage → 0.1.0

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 (247) 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 +20 -0
  7. package/dist/api.js +34 -0
  8. package/dist/app.d.ts +106 -0
  9. package/dist/app.js +224 -0
  10. package/dist/appearance.d.ts +7 -0
  11. package/dist/appearance.js +2 -0
  12. package/dist/auth/sessionApi.d.ts +41 -0
  13. package/dist/auth/sessionApi.js +158 -0
  14. package/dist/auth/sessionStore.d.ts +20 -0
  15. package/dist/auth/sessionStore.js +40 -0
  16. package/dist/auth/user.d.ts +11 -0
  17. package/dist/auth/user.js +9 -0
  18. package/dist/builtins/Account.d.ts +25 -0
  19. package/dist/builtins/Account.js +15 -0
  20. package/dist/builtins/Palette.d.ts +29 -0
  21. package/dist/builtins/Palette.js +63 -0
  22. package/dist/builtins/SignIn.d.ts +28 -0
  23. package/dist/builtins/SignIn.js +60 -0
  24. package/dist/builtins/Toasts.d.ts +32 -0
  25. package/dist/builtins/Toasts.js +24 -0
  26. package/dist/builtins/actions.d.ts +2 -0
  27. package/dist/builtins/actions.js +45 -0
  28. package/dist/cache.d.ts +24 -0
  29. package/dist/cache.js +112 -0
  30. package/dist/cli/app.d.ts +2 -0
  31. package/dist/cli/app.js +1329 -0
  32. package/dist/cli/screenshot.d.ts +1 -0
  33. package/dist/cli/screenshot.js +96 -0
  34. package/dist/cli/tools.d.ts +12 -0
  35. package/dist/cli/tools.js +35 -0
  36. package/dist/components/AppFrame.d.ts +14 -0
  37. package/dist/components/AppFrame.js +64 -0
  38. package/dist/components/AppearancePicker.d.ts +1 -0
  39. package/dist/components/AppearancePicker.js +12 -0
  40. package/dist/components/Avatar.d.ts +9 -0
  41. package/dist/components/Avatar.js +63 -0
  42. package/dist/components/Badge.d.ts +11 -0
  43. package/dist/components/Badge.js +87 -0
  44. package/dist/components/Button.d.ts +23 -0
  45. package/dist/components/Button.js +249 -0
  46. package/dist/components/Card.d.ts +12 -0
  47. package/dist/components/Card.js +38 -0
  48. package/dist/components/CommandMenu.d.ts +23 -0
  49. package/dist/components/CommandMenu.js +214 -0
  50. package/dist/components/CommandPalette.d.ts +1 -0
  51. package/dist/components/CommandPalette.js +7 -0
  52. package/dist/components/Content.d.ts +7 -0
  53. package/dist/components/Content.js +24 -0
  54. package/dist/components/ContextMenu.d.ts +8 -0
  55. package/dist/components/ContextMenu.js +23 -0
  56. package/dist/components/Dialog.d.ts +11 -0
  57. package/dist/components/Dialog.js +81 -0
  58. package/dist/components/Divider.d.ts +6 -0
  59. package/dist/components/Divider.js +15 -0
  60. package/dist/components/EmptyState.d.ts +8 -0
  61. package/dist/components/EmptyState.js +24 -0
  62. package/dist/components/Form.d.ts +9 -0
  63. package/dist/components/Form.js +23 -0
  64. package/dist/components/Icon.d.ts +10 -0
  65. package/dist/components/Icon.js +22 -0
  66. package/dist/components/IconButton.d.ts +14 -0
  67. package/dist/components/IconButton.js +53 -0
  68. package/dist/components/Kbd.d.ts +6 -0
  69. package/dist/components/Kbd.js +67 -0
  70. package/dist/components/Link.d.ts +9 -0
  71. package/dist/components/Link.js +35 -0
  72. package/dist/components/ListRow.d.ts +12 -0
  73. package/dist/components/ListRow.js +62 -0
  74. package/dist/components/LoadingRows.d.ts +5 -0
  75. package/dist/components/LoadingRows.js +6 -0
  76. package/dist/components/Menu.d.ts +12 -0
  77. package/dist/components/Menu.js +33 -0
  78. package/dist/components/MenuItems.d.ts +21 -0
  79. package/dist/components/MenuItems.js +32 -0
  80. package/dist/components/Placement.d.ts +21 -0
  81. package/dist/components/Placement.js +1 -0
  82. package/dist/components/Popup.d.ts +8 -0
  83. package/dist/components/Popup.js +43 -0
  84. package/dist/components/PopupItem.d.ts +8 -0
  85. package/dist/components/PopupItem.js +51 -0
  86. package/dist/components/PropertyRow.d.ts +7 -0
  87. package/dist/components/PropertyRow.js +23 -0
  88. package/dist/components/Row.d.ts +16 -0
  89. package/dist/components/Row.js +27 -0
  90. package/dist/components/Screen.d.ts +13 -0
  91. package/dist/components/Screen.js +56 -0
  92. package/dist/components/Scrim.d.ts +8 -0
  93. package/dist/components/Scrim.js +40 -0
  94. package/dist/components/ScrollArea.d.ts +15 -0
  95. package/dist/components/ScrollArea.js +13 -0
  96. package/dist/components/SegmentedControl.d.ts +17 -0
  97. package/dist/components/SegmentedControl.js +68 -0
  98. package/dist/components/Select.d.ts +17 -0
  99. package/dist/components/Select.js +107 -0
  100. package/dist/components/SettingsRow.d.ts +8 -0
  101. package/dist/components/SettingsRow.js +32 -0
  102. package/dist/components/Sidebar.d.ts +20 -0
  103. package/dist/components/Sidebar.js +138 -0
  104. package/dist/components/SignInScreen.d.ts +1 -0
  105. package/dist/components/SignInScreen.js +40 -0
  106. package/dist/components/Size.d.ts +1 -0
  107. package/dist/components/Size.js +1 -0
  108. package/dist/components/Skeleton.d.ts +8 -0
  109. package/dist/components/Skeleton.js +48 -0
  110. package/dist/components/Spinner.d.ts +6 -0
  111. package/dist/components/Spinner.js +54 -0
  112. package/dist/components/Stack.d.ts +14 -0
  113. package/dist/components/Stack.js +19 -0
  114. package/dist/components/Switch.d.ts +10 -0
  115. package/dist/components/Switch.js +71 -0
  116. package/dist/components/Text.d.ts +21 -0
  117. package/dist/components/Text.js +77 -0
  118. package/dist/components/TextField.d.ts +25 -0
  119. package/dist/components/TextField.js +68 -0
  120. package/dist/components/Toaster.d.ts +1 -0
  121. package/dist/components/Toaster.js +83 -0
  122. package/dist/components/Tooltip.d.ts +8 -0
  123. package/dist/components/Tooltip.js +43 -0
  124. package/dist/components/controlLook.d.ts +60 -0
  125. package/dist/components/controlLook.js +67 -0
  126. package/dist/components/frame.d.ts +4 -0
  127. package/dist/components/frame.js +2 -0
  128. package/dist/components/icons.d.ts +58 -0
  129. package/dist/components/icons.js +105 -0
  130. package/dist/components/interaction.d.ts +12 -0
  131. package/dist/components/interaction.js +22 -0
  132. package/dist/components/spacing.d.ts +118 -0
  133. package/dist/components/spacing.js +46 -0
  134. package/dist/components/tones.d.ts +28 -0
  135. package/dist/components/tones.js +12 -0
  136. package/dist/entityStore.d.ts +46 -0
  137. package/dist/entityStore.js +160 -0
  138. package/dist/flows/accessFlows.d.ts +9 -0
  139. package/dist/flows/accessFlows.js +151 -0
  140. package/dist/flows/browser.d.ts +32 -0
  141. package/dist/flows/browser.js +164 -0
  142. package/dist/flows/contactSheet.d.ts +1 -0
  143. package/dist/flows/contactSheet.js +63 -0
  144. package/dist/flows/flowBrowser.d.ts +23 -0
  145. package/dist/flows/flowBrowser.js +21 -0
  146. package/dist/flows/flowSetup.d.ts +3 -0
  147. package/dist/flows/flowSetup.js +35 -0
  148. package/dist/flows/flows.d.ts +41 -0
  149. package/dist/flows/flows.js +148 -0
  150. package/dist/flows/screenshotName.d.ts +1 -0
  151. package/dist/flows/screenshotName.js +6 -0
  152. package/dist/gate.d.ts +8 -0
  153. package/dist/gate.js +52 -0
  154. package/dist/hooks/appContext.d.ts +14 -0
  155. package/dist/hooks/appContext.js +5 -0
  156. package/dist/hooks/appHooks.d.ts +21 -0
  157. package/dist/hooks/appHooks.js +24 -0
  158. package/dist/hooks/useAnimatesOnChange.d.ts +1 -0
  159. package/dist/hooks/useAnimatesOnChange.js +14 -0
  160. package/dist/hooks/useStoreState.d.ts +6 -0
  161. package/dist/hooks/useStoreState.js +5 -0
  162. package/dist/index.d.ts +40 -0
  163. package/dist/index.js +19 -0
  164. package/dist/memoryApi.d.ts +19 -0
  165. package/dist/memoryApi.js +42 -0
  166. package/dist/model.d.ts +94 -0
  167. package/dist/model.js +243 -0
  168. package/dist/page.d.ts +11 -0
  169. package/dist/page.js +9 -0
  170. package/dist/platform.d.ts +1 -0
  171. package/dist/platform.js +5 -0
  172. package/dist/react.d.ts +46 -0
  173. package/dist/react.js +38 -0
  174. package/dist/registered.d.ts +54 -0
  175. package/dist/registered.js +17 -0
  176. package/dist/routes.d.ts +38 -0
  177. package/dist/routes.js +21 -0
  178. package/dist/rules/checkAllFiles.d.ts +1 -0
  179. package/dist/rules/checkAllFiles.js +741 -0
  180. package/dist/rules/checkApiRouting.d.ts +1 -0
  181. package/dist/rules/checkApiRouting.js +112 -0
  182. package/dist/rules/lintPlugin.d.ts +252 -0
  183. package/dist/rules/lintPlugin.js +1157 -0
  184. package/dist/rules/messages.d.ts +48 -0
  185. package/dist/rules/messages.js +48 -0
  186. package/dist/rules/themeTokens.d.ts +6 -0
  187. package/dist/rules/themeTokens.js +64 -0
  188. package/dist/schema.d.ts +2 -0
  189. package/dist/schema.js +1 -0
  190. package/dist/server/auth.d.ts +115 -0
  191. package/dist/server/auth.js +131 -0
  192. package/dist/server/authService.d.ts +9 -0
  193. package/dist/server/authService.js +50 -0
  194. package/dist/server/createServer.d.ts +357 -0
  195. package/dist/server/createServer.js +62 -0
  196. package/dist/server/env.d.ts +15 -0
  197. package/dist/server/env.js +1 -0
  198. package/dist/server/index.d.ts +8 -0
  199. package/dist/server/index.js +5 -0
  200. package/dist/server/session.d.ts +28 -0
  201. package/dist/server/session.js +54 -0
  202. package/dist/server/sessions.d.ts +92 -0
  203. package/dist/server/sessions.js +47 -0
  204. package/dist/server/signInWire.d.ts +52 -0
  205. package/dist/server/signInWire.js +22 -0
  206. package/dist/server/tables.d.ts +242 -0
  207. package/dist/server/tables.js +246 -0
  208. package/dist/server/users.d.ts +189 -0
  209. package/dist/server/users.js +49 -0
  210. package/dist/shortcuts.d.ts +3 -0
  211. package/dist/shortcuts.js +40 -0
  212. package/dist/start.d.ts +20 -0
  213. package/dist/start.js +121 -0
  214. package/dist/state.d.ts +14 -0
  215. package/dist/state.js +17 -0
  216. package/dist/store.d.ts +43 -0
  217. package/dist/store.js +198 -0
  218. package/dist/subscribers.d.ts +5 -0
  219. package/dist/subscribers.js +13 -0
  220. package/dist/table.d.ts +143 -0
  221. package/dist/table.js +91 -0
  222. package/dist/tables.d.ts +77 -0
  223. package/dist/tables.js +216 -0
  224. package/dist/theme/reset.css +84 -0
  225. package/dist/theme/scales.d.ts +76 -0
  226. package/dist/theme/scales.js +82 -0
  227. package/dist/theme/tokens.stylex.d.ts +218 -0
  228. package/dist/theme/tokens.stylex.js +233 -0
  229. package/dist/vite/commandBridge.d.ts +2 -0
  230. package/dist/vite/commandBridge.js +46 -0
  231. package/dist/vite/designManifest.d.ts +25 -0
  232. package/dist/vite/designManifest.js +191 -0
  233. package/dist/vite/localWorker.d.ts +4 -0
  234. package/dist/vite/localWorker.js +59 -0
  235. package/dist/vite/rootAlias.d.ts +2 -0
  236. package/dist/vite/rootAlias.js +15 -0
  237. package/dist/vite/vite.d.ts +7 -0
  238. package/dist/vite/vite.js +88 -0
  239. package/dist/wire.d.ts +7 -0
  240. package/dist/wire.js +1 -0
  241. package/package.json +99 -3
  242. package/presets/lintPlugin.js +7 -0
  243. package/presets/oxlint.json +167 -0
  244. package/presets/register.d.ts +19 -0
  245. package/presets/server.d.ts +2 -0
  246. package/presets/tsconfig.app.json +29 -0
  247. package/presets/tsconfig.server.json +27 -0
package/dist/store.js ADDED
@@ -0,0 +1,198 @@
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
+ initialState() {
167
+ throw new Error(`${this.constructor.name} must extend Store({ state }) so its first state comes from the config`);
168
+ }
169
+ }
170
+ export function Store(config) {
171
+ const persist = config.persist ?? [];
172
+ class Declared extends DeclaredStore {
173
+ static config = config;
174
+ #persisted;
175
+ constructor(persisted) {
176
+ super();
177
+ this.#persisted = persisted;
178
+ }
179
+ initialState() {
180
+ return restore(config.state ?? {}, persist, this.#persisted);
181
+ }
182
+ set(patch) {
183
+ super.set(patch);
184
+ for (const key of persist) {
185
+ if (key in patch)
186
+ this.#persisted.write(key, this.state[key]);
187
+ }
188
+ }
189
+ }
190
+ return Declared;
191
+ }
192
+ export function snapshotsOf(group) {
193
+ const entries = Object.entries(group).map(([key, store]) => [
194
+ key,
195
+ store.state
196
+ ]);
197
+ return Object.fromEntries(entries);
198
+ }
@@ -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>>(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 {};
package/dist/table.js ADDED
@@ -0,0 +1,91 @@
1
+ import { z } from 'zod';
2
+ const NAME = /^[a-z][a-zA-Z0-9]*$/;
3
+ const RESERVED = ['id', 'createdAt', 'ownerId'];
4
+ const RESERVED_TABLES = ['users', 'sessions', 'session'];
5
+ const SEEDED_AT = Date.UTC(2000, 0, 1);
6
+ function problemIn(name, definition) {
7
+ const { access, columns } = definition;
8
+ const known = Object.keys(columns);
9
+ if (access.create === 'owner') {
10
+ return `Table "${name}": create can't be 'owner', use 'signedIn'`;
11
+ }
12
+ if (definition.owned !== true && Object.values(access).includes('owner')) {
13
+ return `Table "${name}" uses 'owner' but is not owned: true`;
14
+ }
15
+ if (definition.owned === true && access.create === 'anyone') {
16
+ return `Table "${name}" is owned, so create must not be 'anyone'`;
17
+ }
18
+ const unknown = [
19
+ ...(definition.public ?? []).filter((column) => column !== 'createdAt'),
20
+ ...(definition.unique ?? []).flat()
21
+ ].find((column) => !known.includes(column));
22
+ if (unknown !== undefined) {
23
+ return `Table "${name}" names "${unknown}", which is not one of its columns`;
24
+ }
25
+ if ((definition.unique ?? []).length > 0 &&
26
+ definition.writes !== 'confirmed') {
27
+ return `Table "${name}" has a unique constraint, so writes must be 'confirmed'`;
28
+ }
29
+ return undefined;
30
+ }
31
+ function table(name, definition) {
32
+ const { columns } = definition;
33
+ for (const key of [name, ...Object.keys(columns)]) {
34
+ if (!NAME.test(key)) {
35
+ throw new Error(`"${key}" can't name a table or column: use camelCase letters and digits`);
36
+ }
37
+ }
38
+ if (RESERVED_TABLES.includes(name)) {
39
+ throw new Error(`A table can't be called "${name}": Pattern keeps its own ${name} for sign-in`);
40
+ }
41
+ const reserved = RESERVED.find((key) => key in columns);
42
+ if (reserved !== undefined) {
43
+ throw new Error(`Table "${name}" can't have a "${reserved}" column: Pattern adds it`);
44
+ }
45
+ const problem = problemIn(name, definition);
46
+ if (problem !== undefined)
47
+ throw new Error(problem);
48
+ const owned = definition.owned === true;
49
+ const publicColumns = definition.public ?? [];
50
+ return {
51
+ name,
52
+ columns,
53
+ access: definition.access,
54
+ writes: definition.writes,
55
+ owned,
56
+ public: publicColumns,
57
+ unique: definition.unique ?? [],
58
+ fields: z.object(columns),
59
+ incoming: z.object({ id: z.string(), ...columns }),
60
+ row: z.object({
61
+ id: z.string(),
62
+ createdAt: z.string(),
63
+ ...(owned ? { ownerId: z.string() } : {}),
64
+ ...columns
65
+ }),
66
+ publicRow: z.object({
67
+ id: z.string(),
68
+ ...Object.fromEntries(publicColumns.map((column) => [
69
+ column,
70
+ column === 'createdAt' ? z.string() : columns[column]
71
+ ]))
72
+ })
73
+ };
74
+ }
75
+ export function defineTables(definitions) {
76
+ return Object.fromEntries(Object.entries(definitions).map(([name, definition]) => [name, table(name, definition)]));
77
+ }
78
+ export function ownerIdOf(email) {
79
+ return `user-${email.toLowerCase()}`;
80
+ }
81
+ export function seededRows(seed, name) {
82
+ const rows = Reflect.get(seed, name) ?? [];
83
+ return rows.map(({ owner, ...row }, index) => ({
84
+ ...row,
85
+ ...(typeof owner === 'string' ? { ownerId: ownerIdOf(owner) } : {}),
86
+ createdAt: new Date(SEEDED_AT + index * 1000).toISOString()
87
+ }));
88
+ }
89
+ export function snakeCase(name) {
90
+ return name.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
91
+ }
@@ -0,0 +1,77 @@
1
+ import type { Viewer, WriteOutcome } from './access.ts';
2
+ import type { Cache, Cached } from './cache.ts';
3
+ import { EntityStore, entityCalls } from './entityStore.ts';
4
+ import type { EntityApi } from './entityStore.ts';
5
+ import type { Gate } from './gate.ts';
6
+ import type { MemoryApiOptions } from './memoryApi.ts';
7
+ import { StoreBase } from './store.ts';
8
+ import type { Fields, PublicRow, Row, Seed, Table, Tables } from './table.ts';
9
+ export type TableApi<T extends Table> = EntityApi<Row<T>> & {
10
+ listPublic: () => Promise<readonly PublicRow<T>[]>;
11
+ };
12
+ export type TablesApi<Group extends Tables> = {
13
+ [Key in keyof Group]: TableApi<Group[Key]>;
14
+ };
15
+ export type TableCall<Group extends Tables> = `${keyof Group & string}.${(typeof entityCalls)[number] | 'listPublic'}`;
16
+ export type CurrentViewer = () => Viewer | null;
17
+ export declare class HttpTableApi<T extends Table> implements TableApi<T> {
18
+ #private;
19
+ constructor(table: T, gate: Gate, onSignedOut: () => void, base?: string);
20
+ list(): Promise<readonly Row<T>[]>;
21
+ listPublic(): Promise<readonly PublicRow<T>[]>;
22
+ create(row: Row<T>): Promise<Row<T>>;
23
+ update(row: Row<T>): Promise<Row<T>>;
24
+ remove(id: string): Promise<void>;
25
+ }
26
+ type AnyRow = Readonly<Record<string, unknown>> & {
27
+ id: string;
28
+ ownerId?: string;
29
+ };
30
+ export declare class MemoryTableApi<T extends Table> implements TableApi<T> {
31
+ #private;
32
+ constructor(table: T, seed: readonly AnyRow[], viewer: CurrentViewer, options: MemoryApiOptions);
33
+ list(): Promise<readonly Row<T>[]>;
34
+ listPublic(): Promise<readonly PublicRow<T>[]>;
35
+ create(row: Row<T>): Promise<Row<T>>;
36
+ update(row: Row<T>): Promise<Row<T>>;
37
+ remove(id: string): Promise<void>;
38
+ }
39
+ export declare function httpTables<Group extends Tables>(tables: Group, gate: Gate, onSignedOut: () => void): TablesApi<Group>;
40
+ export declare function memoryTables<Group extends Tables>(tables: Group, seed: Seed<Group>, viewer: CurrentViewer, options: MemoryApiOptions): TablesApi<Group>;
41
+ export declare function tableCalls<Group extends Tables>(tables: Group): readonly TableCall<Group>[];
42
+ export declare function publicStoreName(table: string): string;
43
+ export type PublicSnapshot<Item> = {
44
+ status: 'idle' | 'loading' | 'ready' | 'failed';
45
+ items: readonly Item[];
46
+ };
47
+ type PublicDeps<Item> = {
48
+ list: () => Promise<readonly Item[]>;
49
+ cache: Cached<readonly Item[]>;
50
+ };
51
+ export declare class PublicTableStore<T extends Table> extends StoreBase<PublicSnapshot<PublicRow<T>>, PublicDeps<PublicRow<T>>> {
52
+ #private;
53
+ constructor(table: T, { api, cache }: {
54
+ api: TableApi<T>;
55
+ cache: Cache;
56
+ });
57
+ protected initialState(): PublicSnapshot<PublicRow<T>>;
58
+ load(): Promise<void>;
59
+ }
60
+ type TableDeps<T extends Table> = {
61
+ api: TableApi<T>;
62
+ cache: Cache;
63
+ viewer: CurrentViewer;
64
+ publicView?: {
65
+ load: () => Promise<void>;
66
+ };
67
+ };
68
+ export declare class TableStore<T extends Table> extends EntityStore<Row<T>> {
69
+ #private;
70
+ constructor(table: T, { api, cache, viewer, publicView }: TableDeps<T>);
71
+ get mayRead(): boolean;
72
+ load(): Promise<void>;
73
+ create(fields: Fields<T>): Promise<WriteOutcome>;
74
+ update(row: Row<T>): Promise<WriteOutcome>;
75
+ remove(id: string): Promise<WriteOutcome>;
76
+ }
77
+ export {};