@abloatai/ablo 0.3.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 (278) hide show
  1. package/CHANGELOG.md +208 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +12 -0
  4. package/README.md +230 -0
  5. package/dist/BaseSyncedStore.d.ts +709 -0
  6. package/dist/BaseSyncedStore.js +1843 -0
  7. package/dist/Database.d.ts +344 -0
  8. package/dist/Database.js +1259 -0
  9. package/dist/LazyReferenceCollection.d.ts +181 -0
  10. package/dist/LazyReferenceCollection.js +460 -0
  11. package/dist/Model.d.ts +339 -0
  12. package/dist/Model.js +715 -0
  13. package/dist/ModelRegistry.d.ts +200 -0
  14. package/dist/ModelRegistry.js +535 -0
  15. package/dist/NetworkMonitor.d.ts +27 -0
  16. package/dist/NetworkMonitor.js +73 -0
  17. package/dist/ObjectPool.d.ts +202 -0
  18. package/dist/ObjectPool.js +1106 -0
  19. package/dist/SyncClient.d.ts +489 -0
  20. package/dist/SyncClient.js +1555 -0
  21. package/dist/SyncEngineContext.d.ts +46 -0
  22. package/dist/SyncEngineContext.js +74 -0
  23. package/dist/adapters/alwaysOnline.d.ts +16 -0
  24. package/dist/adapters/alwaysOnline.js +19 -0
  25. package/dist/adapters/inMemoryStorage.d.ts +30 -0
  26. package/dist/adapters/inMemoryStorage.js +94 -0
  27. package/dist/agent/Agent.d.ts +358 -0
  28. package/dist/agent/Agent.js +500 -0
  29. package/dist/agent/index.d.ts +115 -0
  30. package/dist/agent/index.js +128 -0
  31. package/dist/agent/session.d.ts +90 -0
  32. package/dist/agent/session.js +156 -0
  33. package/dist/agent/types.d.ts +73 -0
  34. package/dist/agent/types.js +10 -0
  35. package/dist/ai-sdk/coordination-context.d.ts +51 -0
  36. package/dist/ai-sdk/coordination-context.js +107 -0
  37. package/dist/ai-sdk/index.d.ts +68 -0
  38. package/dist/ai-sdk/index.js +68 -0
  39. package/dist/ai-sdk/intent-broadcast.d.ts +77 -0
  40. package/dist/ai-sdk/intent-broadcast.js +72 -0
  41. package/dist/ai-sdk/wrap.d.ts +67 -0
  42. package/dist/ai-sdk/wrap.js +45 -0
  43. package/dist/api/index.d.ts +10 -0
  44. package/dist/api/index.js +9 -0
  45. package/dist/auth/index.d.ts +137 -0
  46. package/dist/auth/index.js +246 -0
  47. package/dist/client/Ablo.d.ts +835 -0
  48. package/dist/client/Ablo.js +1440 -0
  49. package/dist/client/ApiClient.d.ts +200 -0
  50. package/dist/client/ApiClient.js +659 -0
  51. package/dist/client/auth.d.ts +79 -0
  52. package/dist/client/auth.js +81 -0
  53. package/dist/client/createInternalComponents.d.ts +44 -0
  54. package/dist/client/createInternalComponents.js +88 -0
  55. package/dist/client/createModelProxy.d.ts +152 -0
  56. package/dist/client/createModelProxy.js +199 -0
  57. package/dist/client/identity.d.ts +63 -0
  58. package/dist/client/identity.js +156 -0
  59. package/dist/client/index.d.ts +36 -0
  60. package/dist/client/index.js +33 -0
  61. package/dist/client/persistence.d.ts +7 -0
  62. package/dist/client/persistence.js +11 -0
  63. package/dist/client/validateAbloOptions.d.ts +42 -0
  64. package/dist/client/validateAbloOptions.js +43 -0
  65. package/dist/config/index.d.ts +10 -0
  66. package/dist/config/index.js +12 -0
  67. package/dist/context.d.ts +27 -0
  68. package/dist/context.js +58 -0
  69. package/dist/core/DatabaseManager.d.ts +108 -0
  70. package/dist/core/DatabaseManager.js +361 -0
  71. package/dist/core/QueryProcessor.d.ts +77 -0
  72. package/dist/core/QueryProcessor.js +262 -0
  73. package/dist/core/QueryView.d.ts +64 -0
  74. package/dist/core/QueryView.js +219 -0
  75. package/dist/core/StoreManager.d.ts +131 -0
  76. package/dist/core/StoreManager.js +334 -0
  77. package/dist/core/ViewRegistry.d.ts +20 -0
  78. package/dist/core/ViewRegistry.js +55 -0
  79. package/dist/core/index.d.ts +34 -0
  80. package/dist/core/index.js +59 -0
  81. package/dist/core/openIDBWithTimeout.d.ts +27 -0
  82. package/dist/core/openIDBWithTimeout.js +63 -0
  83. package/dist/core/query-utils.d.ts +37 -0
  84. package/dist/core/query-utils.js +60 -0
  85. package/dist/errors.d.ts +235 -0
  86. package/dist/errors.js +243 -0
  87. package/dist/index.d.ts +41 -0
  88. package/dist/index.js +82 -0
  89. package/dist/interfaces/headless.d.ts +95 -0
  90. package/dist/interfaces/headless.js +41 -0
  91. package/dist/interfaces/index.d.ts +321 -0
  92. package/dist/interfaces/index.js +8 -0
  93. package/dist/mutators/RecordingTransaction.d.ts +36 -0
  94. package/dist/mutators/RecordingTransaction.js +216 -0
  95. package/dist/mutators/Transaction.d.ts +48 -0
  96. package/dist/mutators/Transaction.js +64 -0
  97. package/dist/mutators/UndoManager.d.ts +114 -0
  98. package/dist/mutators/UndoManager.js +143 -0
  99. package/dist/mutators/defineMutators.d.ts +55 -0
  100. package/dist/mutators/defineMutators.js +28 -0
  101. package/dist/policy/index.d.ts +19 -0
  102. package/dist/policy/index.js +18 -0
  103. package/dist/policy/types.d.ts +74 -0
  104. package/dist/policy/types.js +17 -0
  105. package/dist/principal.d.ts +44 -0
  106. package/dist/principal.js +49 -0
  107. package/dist/query/client.d.ts +43 -0
  108. package/dist/query/client.js +84 -0
  109. package/dist/query/index.d.ts +6 -0
  110. package/dist/query/index.js +5 -0
  111. package/dist/query/types.d.ts +143 -0
  112. package/dist/query/types.js +36 -0
  113. package/dist/react/AbloProvider.d.ts +205 -0
  114. package/dist/react/AbloProvider.js +398 -0
  115. package/dist/react/ClientSideSuspense.d.ts +36 -0
  116. package/dist/react/ClientSideSuspense.js +17 -0
  117. package/dist/react/DefaultFallback.d.ts +24 -0
  118. package/dist/react/DefaultFallback.js +43 -0
  119. package/dist/react/SyncGroupProvider.d.ts +19 -0
  120. package/dist/react/SyncGroupProvider.js +44 -0
  121. package/dist/react/context.d.ts +161 -0
  122. package/dist/react/context.js +35 -0
  123. package/dist/react/index.d.ts +64 -0
  124. package/dist/react/index.js +73 -0
  125. package/dist/react/internalContext.d.ts +35 -0
  126. package/dist/react/internalContext.js +3 -0
  127. package/dist/react/useAblo.d.ts +72 -0
  128. package/dist/react/useAblo.js +63 -0
  129. package/dist/react/useCurrentUserId.d.ts +21 -0
  130. package/dist/react/useCurrentUserId.js +33 -0
  131. package/dist/react/useErrorListener.d.ts +20 -0
  132. package/dist/react/useErrorListener.js +39 -0
  133. package/dist/react/useIntent.d.ts +29 -0
  134. package/dist/react/useIntent.js +42 -0
  135. package/dist/react/useMutate.d.ts +83 -0
  136. package/dist/react/useMutate.js +122 -0
  137. package/dist/react/useMutationFailureListener.d.ts +26 -0
  138. package/dist/react/useMutationFailureListener.js +38 -0
  139. package/dist/react/useMutators.d.ts +56 -0
  140. package/dist/react/useMutators.js +66 -0
  141. package/dist/react/usePresence.d.ts +32 -0
  142. package/dist/react/usePresence.js +41 -0
  143. package/dist/react/useQuery.d.ts +123 -0
  144. package/dist/react/useQuery.js +145 -0
  145. package/dist/react/useReactive.d.ts +35 -0
  146. package/dist/react/useReactive.js +111 -0
  147. package/dist/react/useReader.d.ts +69 -0
  148. package/dist/react/useReader.js +73 -0
  149. package/dist/react/useSyncStatus.d.ts +61 -0
  150. package/dist/react/useSyncStatus.js +76 -0
  151. package/dist/react/useUndoScope.d.ts +36 -0
  152. package/dist/react/useUndoScope.js +73 -0
  153. package/dist/realtime/index.d.ts +10 -0
  154. package/dist/realtime/index.js +9 -0
  155. package/dist/schema/field.d.ts +134 -0
  156. package/dist/schema/field.js +264 -0
  157. package/dist/schema/index.d.ts +29 -0
  158. package/dist/schema/index.js +38 -0
  159. package/dist/schema/model.d.ts +326 -0
  160. package/dist/schema/model.js +89 -0
  161. package/dist/schema/queries.d.ts +203 -0
  162. package/dist/schema/queries.js +145 -0
  163. package/dist/schema/relation.d.ts +172 -0
  164. package/dist/schema/relation.js +104 -0
  165. package/dist/schema/schema.d.ts +259 -0
  166. package/dist/schema/schema.js +188 -0
  167. package/dist/schema/sugar.d.ts +129 -0
  168. package/dist/schema/sugar.js +94 -0
  169. package/dist/source/index.d.ts +423 -0
  170. package/dist/source/index.js +320 -0
  171. package/dist/source/pushQueue.d.ts +112 -0
  172. package/dist/source/pushQueue.js +249 -0
  173. package/dist/stores/ObjectStore.d.ts +103 -0
  174. package/dist/stores/ObjectStore.js +371 -0
  175. package/dist/stores/ObjectStoreContract.d.ts +39 -0
  176. package/dist/stores/ObjectStoreContract.js +1 -0
  177. package/dist/stores/SyncActionStore.d.ts +101 -0
  178. package/dist/stores/SyncActionStore.js +481 -0
  179. package/dist/sync/BootstrapHelper.d.ts +127 -0
  180. package/dist/sync/BootstrapHelper.js +434 -0
  181. package/dist/sync/ConnectionManager.d.ts +136 -0
  182. package/dist/sync/ConnectionManager.js +465 -0
  183. package/dist/sync/HydrationCoordinator.d.ts +137 -0
  184. package/dist/sync/HydrationCoordinator.js +468 -0
  185. package/dist/sync/NetworkProbe.d.ts +43 -0
  186. package/dist/sync/NetworkProbe.js +113 -0
  187. package/dist/sync/OfflineFlush.d.ts +9 -0
  188. package/dist/sync/OfflineFlush.js +22 -0
  189. package/dist/sync/OfflineTransactionStore.d.ts +37 -0
  190. package/dist/sync/OfflineTransactionStore.js +263 -0
  191. package/dist/sync/SyncWebSocket.d.ts +663 -0
  192. package/dist/sync/SyncWebSocket.js +1336 -0
  193. package/dist/sync/createIntentStream.d.ts +33 -0
  194. package/dist/sync/createIntentStream.js +243 -0
  195. package/dist/sync/createPresenceStream.d.ts +46 -0
  196. package/dist/sync/createPresenceStream.js +192 -0
  197. package/dist/sync/createSnapshot.d.ts +33 -0
  198. package/dist/sync/createSnapshot.js +124 -0
  199. package/dist/sync/participants.d.ts +114 -0
  200. package/dist/sync/participants.js +336 -0
  201. package/dist/sync/schemas.d.ts +79 -0
  202. package/dist/sync/schemas.js +78 -0
  203. package/dist/testing/fixtures/bootstrap.d.ts +45 -0
  204. package/dist/testing/fixtures/bootstrap.js +53 -0
  205. package/dist/testing/fixtures/deltas.d.ts +86 -0
  206. package/dist/testing/fixtures/deltas.js +139 -0
  207. package/dist/testing/fixtures/models.d.ts +82 -0
  208. package/dist/testing/fixtures/models.js +270 -0
  209. package/dist/testing/helpers/react-wrapper.d.ts +66 -0
  210. package/dist/testing/helpers/react-wrapper.js +64 -0
  211. package/dist/testing/helpers/sync-engine-harness.d.ts +55 -0
  212. package/dist/testing/helpers/sync-engine-harness.js +70 -0
  213. package/dist/testing/helpers/wait.d.ts +25 -0
  214. package/dist/testing/helpers/wait.js +44 -0
  215. package/dist/testing/index.d.ts +21 -0
  216. package/dist/testing/index.js +32 -0
  217. package/dist/testing/mocks/MockMutationExecutor.d.ts +65 -0
  218. package/dist/testing/mocks/MockMutationExecutor.js +139 -0
  219. package/dist/testing/mocks/MockNetworkMonitor.d.ts +20 -0
  220. package/dist/testing/mocks/MockNetworkMonitor.js +46 -0
  221. package/dist/testing/mocks/MockSyncContext.d.ts +64 -0
  222. package/dist/testing/mocks/MockSyncContext.js +100 -0
  223. package/dist/testing/mocks/MockSyncStore.d.ts +88 -0
  224. package/dist/testing/mocks/MockSyncStore.js +171 -0
  225. package/dist/testing/mocks/MockWebSocket.d.ts +66 -0
  226. package/dist/testing/mocks/MockWebSocket.js +117 -0
  227. package/dist/transactions/OptimisticEchoTracker.d.ts +82 -0
  228. package/dist/transactions/OptimisticEchoTracker.js +104 -0
  229. package/dist/transactions/TransactionQueue.d.ts +499 -0
  230. package/dist/transactions/TransactionQueue.js +1895 -0
  231. package/dist/transactions/index.d.ts +16 -0
  232. package/dist/transactions/index.js +7 -0
  233. package/dist/transactions/mutation-error-handler.d.ts +5 -0
  234. package/dist/transactions/mutation-error-handler.js +39 -0
  235. package/dist/types/global.d.ts +107 -0
  236. package/dist/types/global.js +38 -0
  237. package/dist/types/index.d.ts +241 -0
  238. package/dist/types/index.js +70 -0
  239. package/dist/types/streams.d.ts +495 -0
  240. package/dist/types/streams.js +11 -0
  241. package/dist/utils/asyncIterator.d.ts +41 -0
  242. package/dist/utils/asyncIterator.js +142 -0
  243. package/dist/utils/duration.d.ts +28 -0
  244. package/dist/utils/duration.js +47 -0
  245. package/dist/utils/mobx-setup.d.ts +42 -0
  246. package/dist/utils/mobx-setup.js +381 -0
  247. package/docs/api-keys.md +24 -0
  248. package/docs/api.md +230 -0
  249. package/docs/audit.md +81 -0
  250. package/docs/capabilities.md +163 -0
  251. package/docs/client-behavior.md +202 -0
  252. package/docs/data-sources.md +214 -0
  253. package/docs/examples/agent-human.md +84 -0
  254. package/docs/examples/ai-sdk-tool.md +92 -0
  255. package/docs/examples/existing-python-backend.md +249 -0
  256. package/docs/examples/nextjs.md +88 -0
  257. package/docs/examples/server-agent.md +86 -0
  258. package/docs/guarantees.md +148 -0
  259. package/docs/index.md +97 -0
  260. package/docs/integration-guide.md +493 -0
  261. package/docs/interaction-model.md +140 -0
  262. package/docs/mcp/claude-code.md +43 -0
  263. package/docs/mcp/cursor.md +53 -0
  264. package/docs/mcp/windsurf.md +46 -0
  265. package/docs/mcp.md +59 -0
  266. package/docs/quickstart.md +152 -0
  267. package/docs/react.md +115 -0
  268. package/docs/roadmap.md +45 -0
  269. package/examples/README.md +54 -0
  270. package/examples/data-source/README.md +102 -0
  271. package/examples/data-source/ablo-driver.ts +89 -0
  272. package/examples/data-source/customer-server.ts +208 -0
  273. package/examples/data-source/run.ts +101 -0
  274. package/examples/data-source/schema.ts +25 -0
  275. package/examples/quickstart.ts +54 -0
  276. package/examples/tsconfig.json +16 -0
  277. package/llms.txt +143 -0
  278. package/package.json +147 -0
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Schema Definition + Type Inference
3
+ *
4
+ * defineSchema() wraps your models. Types are inferred via Zod — no custom type system.
5
+ *
6
+ * Usage:
7
+ * import { z } from 'zod';
8
+ * import { defineSchema, model, relation } from '@ablo/sync-engine/schema';
9
+ *
10
+ * const schema = defineSchema({
11
+ * tasks: model({
12
+ * title: z.string(),
13
+ * status: z.enum(['todo', 'doing', 'done']).default('todo'),
14
+ * projectId: z.string().optional(),
15
+ * }, {
16
+ * project: relation.belongsTo('projects', 'projectId'),
17
+ * }),
18
+ * });
19
+ *
20
+ * type Task = InferModel<typeof schema, 'tasks'>;
21
+ */
22
+ import { z } from 'zod';
23
+ import { AbloValidationError } from '../errors.js';
24
+ function resolveCasing(fn) {
25
+ if (fn === undefined)
26
+ return (x) => x;
27
+ if (typeof fn === 'function')
28
+ return fn;
29
+ switch (fn) {
30
+ case 'snake_case':
31
+ return camelToSnake;
32
+ case 'camelCase':
33
+ return (x) => x;
34
+ }
35
+ }
36
+ /** Pure camelCase → snake_case. Matches postgres.fromCamel semantics but
37
+ * kept local so the SDK stays free of any driver dependency — consumers
38
+ * using Prisma/Drizzle/raw pg should all get the same result. */
39
+ function camelToSnake(identifier) {
40
+ return identifier.replace(/[A-Z]/g, (ch) => `_${ch.toLowerCase()}`);
41
+ }
42
+ /** Base fields every synced model gets automatically */
43
+ const baseFieldsSchema = z.object({
44
+ id: z.string(),
45
+ createdAt: z.date(),
46
+ updatedAt: z.date(),
47
+ organizationId: z.string().optional(),
48
+ createdBy: z.string().optional(),
49
+ });
50
+ // ── Factory ───────────────────────────────────────────────────────────────
51
+ /**
52
+ * Define a sync engine schema.
53
+ *
54
+ * ```ts
55
+ * const schema = defineSchema({
56
+ * tasks: model({ title: z.string(), status: z.string().default('todo') }),
57
+ * projects: model({ name: z.string() }),
58
+ * });
59
+ * ```
60
+ */
61
+ /**
62
+ * Lowercase-first camelCase round-trip check. Must match the convention used
63
+ * by `postgres.camel` in the client driver (porsager/postgres), which is:
64
+ *
65
+ * `content_json` → `contentJson` (snake → camel)
66
+ * `contentJson` → `content_json` (camel → snake)
67
+ * `contentJSON` → `content_j_s_o_n` (BROKEN — doesn't round-trip)
68
+ *
69
+ * Any field name that doesn't round-trip under this pair of transforms will
70
+ * silently fail to populate on the client: the wire delivers one casing,
71
+ * the dynamic class's constructor reads another, and the field lands as
72
+ * `undefined`. We catch it here so schema authors see an error at
73
+ * definition time rather than at runtime.
74
+ *
75
+ * Rule: a standard camelCase identifier has runs of one uppercase letter
76
+ * followed by lowercase letters — never two consecutive uppercase letters.
77
+ * `contentJSON` has `JSON` all-uppercase, so we reject it.
78
+ */
79
+ function assertRoundTrippableCamelCase(modelName, fieldName) {
80
+ // Base fields merged in by defineSchema are already validated; skip.
81
+ if (fieldName === 'id')
82
+ return;
83
+ // Leading-lowercase constraint: fields must be camelCase, not PascalCase.
84
+ // PascalCase is reserved for typenames.
85
+ if (fieldName[0] >= 'A' && fieldName[0] <= 'Z') {
86
+ throw new AbloValidationError(`[defineSchema] ${modelName}.${fieldName}: field names must start lowercase ` +
87
+ `(camelCase). Use "${fieldName[0].toLowerCase()}${fieldName.slice(1)}" instead.`, { code: 'schema_field_not_camelcase' });
88
+ }
89
+ // Two-consecutive-uppercase check. The classic failure mode is
90
+ // `contentJSON`, `contentHTML`, `myURLParam`, etc. These don't round-trip
91
+ // through `postgres.camel` — the snake_case intermediate would be
92
+ // `content_j_s_o_n`, which is not a column that exists.
93
+ for (let i = 0; i < fieldName.length - 1; i++) {
94
+ const a = fieldName[i];
95
+ const b = fieldName[i + 1];
96
+ const aUpper = a >= 'A' && a <= 'Z';
97
+ const bUpper = b >= 'A' && b <= 'Z';
98
+ if (aUpper && bUpper) {
99
+ throw new AbloValidationError(`[defineSchema] ${modelName}.${fieldName}: two consecutive uppercase ` +
100
+ `letters ("${a}${b}") will not round-trip through the ` +
101
+ `snake_case ↔ camelCase transform used by the sync driver. ` +
102
+ `The wire delivers camelCase (lowercase after the first letter of ` +
103
+ `each word); a field named "${fieldName}" would never receive its ` +
104
+ `value and read as undefined on the client. Use standard ` +
105
+ `camelCase (e.g. "contentJson" instead of "contentJSON").`, { code: 'schema_field_consecutive_caps' });
106
+ }
107
+ }
108
+ }
109
+ export function defineSchema(models, options) {
110
+ // Build validators with base fields merged in, and resolve defaults for
111
+ // `typename` and `persist.store` so downstream code (the generic loader,
112
+ // the hydration pipeline, the Go named-query registry) can rely on these
113
+ // fields being set without re-deriving them at every call site.
114
+ //
115
+ // Defaults:
116
+ // typename ← schema key (e.g. `slideLayer` → `'slideLayer'`)
117
+ // persist.store ← typename (only resolved when `persist` was provided)
118
+ //
119
+ // A consumer that passes `typename: 'SlideLayer'` explicitly (common when
120
+ // the wire shape uses PascalCase while the schema key is camelCase) keeps
121
+ // that value — the fallback only fires when the field is unset.
122
+ //
123
+ // The models record is rebuilt rather than mutated in place because
124
+ // `ModelDef`'s fields are `readonly`. The rebuild is a shallow spread per
125
+ // entry, so the inferred shape/relations/fields metadata references are
126
+ // preserved (no type inference regression at consumer call sites).
127
+ const validators = {};
128
+ const resolvedModels = {};
129
+ const casing = resolveCasing(options?.casing);
130
+ for (const [name, def] of Object.entries(models)) {
131
+ // Catch round-trip-hostile field names at definition time. Deferring
132
+ // this check to runtime means every affected field silently reads
133
+ // `undefined` on the client — and the author only notices when a UI
134
+ // that depends on the field goes blank. Throwing here makes the
135
+ // failure immediate and unambiguous.
136
+ for (const fieldName of Object.keys(def.shape)) {
137
+ assertRoundTrippableCamelCase(name, fieldName);
138
+ }
139
+ validators[name] = baseFieldsSchema.merge(def.schema);
140
+ // Resolve every relation's `foreignKeyColumn` once, now. The builder
141
+ // constructs each RelationDef with `foreignKeyColumn = foreignKey`
142
+ // (identity) so this is a no-op when `casing` is unset — existing
143
+ // consumers get the same behavior they had before the option landed.
144
+ // When `casing: 'snake_case'` is set, every FK flips to its
145
+ // snake_case DB column name here and nowhere else. Server-side SQL
146
+ // compilers read the resolved value directly.
147
+ for (const relName of Object.keys(def.relations)) {
148
+ const rel = def.relations[relName];
149
+ rel.foreignKeyColumn = casing(rel.foreignKey);
150
+ }
151
+ const typename = def.typename ?? name;
152
+ const persist = def.persist
153
+ ? { ...def.persist, store: def.persist.store ?? typename }
154
+ : undefined;
155
+ resolvedModels[name] = { ...def, typename, persist };
156
+ }
157
+ return {
158
+ // Cast back to S: we only added values to optional fields that were
159
+ // already part of ModelDef, so the shape is structurally unchanged.
160
+ models: resolvedModels,
161
+ validators: validators,
162
+ identityRoles: options?.identityRoles ?? [],
163
+ };
164
+ }
165
+ /**
166
+ * Compose the canonical sync-group set this identity is allowed to
167
+ * subscribe to, derived purely from the schema's registered
168
+ * {@link IdentityRole}s. Walks every role, calls its `extract` against
169
+ * the identity context, and substitutes each id into the role's
170
+ * `template`. Output is stable, deduped, and never includes a literal
171
+ * convention string from this function itself — the convention lives
172
+ * 100% in the consumer's schema declaration.
173
+ *
174
+ * Returns `[]` when the schema has no identity roles registered or
175
+ * when no role's extractor produces an id. Caller decides what to do
176
+ * with `[]`; the server's intersect-with-requested logic treats it as
177
+ * "no scope" rather than "match everything."
178
+ */
179
+ export function composeIdentitySyncGroups(identity, schema) {
180
+ const out = new Set();
181
+ for (const role of schema.identityRoles) {
182
+ for (const id of role.extract(identity)) {
183
+ if (id)
184
+ out.add(role.template.replace('{id}', id));
185
+ }
186
+ }
187
+ return Array.from(out);
188
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Intent-first shorthand for `model(...)` — the Modal-inspired DX layer.
3
+ *
4
+ * The factory verbs encode the two orthogonal axes that matter for
5
+ * safety and bootstrap behavior:
6
+ *
7
+ * - **Writability** (axis 1): `mutable.*` means clients may send
8
+ * CREATE/UPDATE/DELETE over the `commit` wire protocol.
9
+ * `readOnly.*` means the model is server-managed — deltas stream
10
+ * to clients but clients cannot mutate.
11
+ * - **Load strategy** (axis 2): `.instant` loads at bootstrap,
12
+ * `.lazy` loads on first access, `.manual` requires explicit
13
+ * queries.
14
+ *
15
+ * The two-token form (`mutable.lazy({...})`) reads the safety intent
16
+ * in the first token and the load shape in the second — you know both
17
+ * key facts about the entity before scanning its fields.
18
+ *
19
+ * This is additive: the original `model(...)` factory keeps working.
20
+ * New entities should prefer the verbs; existing entities can migrate
21
+ * entity-by-entity.
22
+ *
23
+ * Example:
24
+ * ```ts
25
+ * // Before — 7 options to read before the fields make sense
26
+ * tasks: model({ title: z.string() }, { ... }, {
27
+ * typename: 'Task', tableName: 'tasks', mutable: true,
28
+ * load: 'lazy', lazyObservable: true, computed: tasksComputed,
29
+ * }),
30
+ *
31
+ * // After — intent reads off the verb; options carry only the
32
+ * // fields that actually diverge from defaults
33
+ * tasks: mutable.lazy({ title: z.string() }, {
34
+ * typename: 'Task', tableName: 'tasks',
35
+ * relations: { ... },
36
+ * computed: tasksComputed,
37
+ * }),
38
+ * ```
39
+ */
40
+ import type { z } from 'zod';
41
+ import { type ModelDef, type ModelOptions, type ComputedRecord, type RelationRecord } from './model.js';
42
+ /**
43
+ * Options accepted by every sugar verb. A strict subset of
44
+ * {@link ModelOptions} — anything the verb infers (`mutable`, `load`,
45
+ * `lazyObservable`) is deliberately absent so the call site can't
46
+ * contradict its verb.
47
+ */
48
+ export interface SugarOptions<R extends RelationRecord = RelationRecord, C extends ComputedRecord = ComputedRecord> {
49
+ /** Relations to other models. Same shape as `model()`'s second arg. */
50
+ relations?: R;
51
+ /** Computed getters installed on the model class prototype. */
52
+ computed?: C;
53
+ /**
54
+ * Wire `__typename` (PascalCase, e.g. `'Task'`). Defaults to the schema
55
+ * key via `defineSchema` — override when the wire shape differs from
56
+ * the camelCase schema key.
57
+ */
58
+ typename?: string;
59
+ /**
60
+ * Actual Postgres table name. Override when Prisma's `@@map` diverges
61
+ * from the naive snake_case of the typename (e.g. `Member` maps to
62
+ * `'member'` singular, not `'members'`).
63
+ */
64
+ tableName?: string;
65
+ /**
66
+ * Whether the table has an `organization_id` column. Default: `true`.
67
+ * Set `false` for system-scoped tables (subscriptions, teams, etc.).
68
+ */
69
+ orgScoped?: boolean;
70
+ /**
71
+ * Scope rows via a parent table when this table has no
72
+ * `organization_id` column. See {@link ModelOptions.scopedVia}.
73
+ */
74
+ scopedVia?: ModelOptions['scopedVia'];
75
+ /**
76
+ * Template for participant scope derivation (e.g. `'matter:{id}'`).
77
+ * Omit for nested children whose access cascades from a parent.
78
+ */
79
+ syncGroupFormat?: string;
80
+ /** Max rows loaded during bootstrap. Only applies to `.instant`. */
81
+ bootstrapLimit?: number;
82
+ /** Bootstrap sort order (e.g. `'created_at DESC'`). */
83
+ bootstrapOrderBy?: string;
84
+ /** IndexedDB persistence hints — see {@link ModelOptions.persist}. */
85
+ persist?: ModelOptions['persist'];
86
+ /**
87
+ * Defer MobX observability to first access. Override the verb's
88
+ * default when a `.lazy` model is small enough that eager MobX setup
89
+ * is fine, or a `.instant` model is hot enough to justify deferral.
90
+ */
91
+ lazyObservable?: boolean;
92
+ }
93
+ /**
94
+ * Client-writable entities. `mutable.*` is the opt-in signal for wire
95
+ * mutations via `commit` — equivalent to setting
96
+ * `{ mutable: true, load: X }` on `model()`.
97
+ *
98
+ * Pick the load suffix by data-access pattern:
99
+ * - `.instant` — small, always-needed (Theme, Layout, StatusGroup)
100
+ * - `.lazy` — large collections fetched on first query
101
+ * (SlideLayer, Message, Task)
102
+ * - `.manual` — never auto-loaded; explicit queries only
103
+ */
104
+ export declare const mutable: {
105
+ instant: <Shape extends z.ZodRawShape, R extends RelationRecord = Record<string, never>, C extends ComputedRecord = Record<string, never>>(shape: Shape, opts?: SugarOptions<R, C>) => ModelDef<Shape, R, C>;
106
+ lazy: <Shape extends z.ZodRawShape, R extends RelationRecord = Record<string, never>, C extends ComputedRecord = Record<string, never>>(shape: Shape, opts?: SugarOptions<R, C>) => ModelDef<Shape, R, C>;
107
+ manual: <Shape extends z.ZodRawShape, R extends RelationRecord = Record<string, never>, C extends ComputedRecord = Record<string, never>>(shape: Shape, opts?: SugarOptions<R, C>) => ModelDef<Shape, R, C>;
108
+ };
109
+ /**
110
+ * Server-managed entities. `readOnly.*` means clients subscribe to
111
+ * deltas but cannot emit mutations — any `commit` op for this model
112
+ * is rejected at the server with "Unknown model."
113
+ *
114
+ * Use for:
115
+ * - Server-written state: `sync_deltas`, `presence`, version vectors
116
+ * - Ingestion pipelines: digest entries, filing jobs
117
+ * - Audit surfaces: anything where clients watch but only the server
118
+ * writes
119
+ */
120
+ export declare const readOnly: {
121
+ instant: <Shape extends z.ZodRawShape, R extends RelationRecord = Record<string, never>, C extends ComputedRecord = Record<string, never>>(shape: Shape, opts?: SugarOptions<R, C>) => ModelDef<Shape, R, C>;
122
+ lazy: <Shape extends z.ZodRawShape, R extends RelationRecord = Record<string, never>, C extends ComputedRecord = Record<string, never>>(shape: Shape, opts?: SugarOptions<R, C>) => ModelDef<Shape, R, C>;
123
+ /**
124
+ * Internal-only: never auto-loaded, never written by clients. The
125
+ * strongest safety posture — use for tables the SDK must know about
126
+ * (for type inference) but that should never flow over the wire.
127
+ */
128
+ internal: <Shape extends z.ZodRawShape, R extends RelationRecord = Record<string, never>, C extends ComputedRecord = Record<string, never>>(shape: Shape, opts?: SugarOptions<R, C>) => ModelDef<Shape, R, C>;
129
+ };
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Intent-first shorthand for `model(...)` — the Modal-inspired DX layer.
3
+ *
4
+ * The factory verbs encode the two orthogonal axes that matter for
5
+ * safety and bootstrap behavior:
6
+ *
7
+ * - **Writability** (axis 1): `mutable.*` means clients may send
8
+ * CREATE/UPDATE/DELETE over the `commit` wire protocol.
9
+ * `readOnly.*` means the model is server-managed — deltas stream
10
+ * to clients but clients cannot mutate.
11
+ * - **Load strategy** (axis 2): `.instant` loads at bootstrap,
12
+ * `.lazy` loads on first access, `.manual` requires explicit
13
+ * queries.
14
+ *
15
+ * The two-token form (`mutable.lazy({...})`) reads the safety intent
16
+ * in the first token and the load shape in the second — you know both
17
+ * key facts about the entity before scanning its fields.
18
+ *
19
+ * This is additive: the original `model(...)` factory keeps working.
20
+ * New entities should prefer the verbs; existing entities can migrate
21
+ * entity-by-entity.
22
+ *
23
+ * Example:
24
+ * ```ts
25
+ * // Before — 7 options to read before the fields make sense
26
+ * tasks: model({ title: z.string() }, { ... }, {
27
+ * typename: 'Task', tableName: 'tasks', mutable: true,
28
+ * load: 'lazy', lazyObservable: true, computed: tasksComputed,
29
+ * }),
30
+ *
31
+ * // After — intent reads off the verb; options carry only the
32
+ * // fields that actually diverge from defaults
33
+ * tasks: mutable.lazy({ title: z.string() }, {
34
+ * typename: 'Task', tableName: 'tasks',
35
+ * relations: { ... },
36
+ * computed: tasksComputed,
37
+ * }),
38
+ * ```
39
+ */
40
+ import { model, } from './model.js';
41
+ /** Internal helper — builds a ModelDef with baseline safety+load flags applied. */
42
+ function build(shape, opts, baseline) {
43
+ return model(shape, (opts?.relations ?? {}), {
44
+ mutable: baseline.mutable,
45
+ load: baseline.load,
46
+ lazyObservable: opts?.lazyObservable ?? baseline.lazyObservable,
47
+ typename: opts?.typename,
48
+ tableName: opts?.tableName,
49
+ orgScoped: opts?.orgScoped,
50
+ scopedVia: opts?.scopedVia,
51
+ syncGroupFormat: opts?.syncGroupFormat,
52
+ bootstrapLimit: opts?.bootstrapLimit,
53
+ bootstrapOrderBy: opts?.bootstrapOrderBy,
54
+ persist: opts?.persist,
55
+ computed: opts?.computed,
56
+ });
57
+ }
58
+ /**
59
+ * Client-writable entities. `mutable.*` is the opt-in signal for wire
60
+ * mutations via `commit` — equivalent to setting
61
+ * `{ mutable: true, load: X }` on `model()`.
62
+ *
63
+ * Pick the load suffix by data-access pattern:
64
+ * - `.instant` — small, always-needed (Theme, Layout, StatusGroup)
65
+ * - `.lazy` — large collections fetched on first query
66
+ * (SlideLayer, Message, Task)
67
+ * - `.manual` — never auto-loaded; explicit queries only
68
+ */
69
+ export const mutable = {
70
+ instant: (shape, opts) => build(shape, opts, { mutable: true, load: 'instant', lazyObservable: false }),
71
+ lazy: (shape, opts) => build(shape, opts, { mutable: true, load: 'lazy', lazyObservable: true }),
72
+ manual: (shape, opts) => build(shape, opts, { mutable: true, load: 'manual', lazyObservable: true }),
73
+ };
74
+ /**
75
+ * Server-managed entities. `readOnly.*` means clients subscribe to
76
+ * deltas but cannot emit mutations — any `commit` op for this model
77
+ * is rejected at the server with "Unknown model."
78
+ *
79
+ * Use for:
80
+ * - Server-written state: `sync_deltas`, `presence`, version vectors
81
+ * - Ingestion pipelines: digest entries, filing jobs
82
+ * - Audit surfaces: anything where clients watch but only the server
83
+ * writes
84
+ */
85
+ export const readOnly = {
86
+ instant: (shape, opts) => build(shape, opts, { mutable: false, load: 'instant', lazyObservable: false }),
87
+ lazy: (shape, opts) => build(shape, opts, { mutable: false, load: 'lazy', lazyObservable: true }),
88
+ /**
89
+ * Internal-only: never auto-loaded, never written by clients. The
90
+ * strongest safety posture — use for tables the SDK must know about
91
+ * (for type inference) but that should never flow over the wire.
92
+ */
93
+ internal: (shape, opts) => build(shape, opts, { mutable: false, load: 'manual', lazyObservable: true }),
94
+ };