@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,19 @@
1
+ /**
2
+ * @ablo/sync-engine/policy — pluggable conflict resolution.
3
+ *
4
+ * The engine detects conflicts; the policy decides. Customer code
5
+ * implements `ConflictPolicy` and registers it at the sync-server.
6
+ *
7
+ * ```ts
8
+ * import { type ConflictPolicy, defaultPolicy } from '@ablo/sync-engine/policy';
9
+ *
10
+ * export const myPolicy: ConflictPolicy = (ctx) => {
11
+ * if (ctx.committer.id.startsWith('linter:')) {
12
+ * return { action: 'allow', note: 'cosmetic writer' };
13
+ * }
14
+ * return defaultPolicy(ctx);
15
+ * };
16
+ * ```
17
+ */
18
+ export type { Conflict, ConflictDecision, ConflictKind, ConflictOperation, ConflictPolicy, StaleContextConflict, IntentHeldConflict, } from './types.js';
19
+ export { defaultPolicy } from './types.js';
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @ablo/sync-engine/policy — pluggable conflict resolution.
3
+ *
4
+ * The engine detects conflicts; the policy decides. Customer code
5
+ * implements `ConflictPolicy` and registers it at the sync-server.
6
+ *
7
+ * ```ts
8
+ * import { type ConflictPolicy, defaultPolicy } from '@ablo/sync-engine/policy';
9
+ *
10
+ * export const myPolicy: ConflictPolicy = (ctx) => {
11
+ * if (ctx.committer.id.startsWith('linter:')) {
12
+ * return { action: 'allow', note: 'cosmetic writer' };
13
+ * }
14
+ * return defaultPolicy(ctx);
15
+ * };
16
+ * ```
17
+ */
18
+ export { defaultPolicy } from './types.js';
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Conflict policy — the engine detects, the policy decides.
3
+ *
4
+ * Two conflict shapes today: `stale_context` (a write whose `readAt`
5
+ * is older than the latest delta on the target) and `intent_held`
6
+ * (a participant claims a target someone else is already claiming).
7
+ * Adding new shapes is additive on the discriminated union.
8
+ */
9
+ import type { ParticipantRef } from '../types/streams.js';
10
+ export type ConflictKind = 'stale_context' | 'intent_held';
11
+ /** Fields shared by every conflict shape. */
12
+ interface ConflictBase {
13
+ readonly committer: ParticipantRef;
14
+ readonly organizationId: string;
15
+ /** Human at the root of the committer's delegation chain (if any). */
16
+ readonly delegationChainRootUserId?: string | null;
17
+ }
18
+ /** The operation whose write conflicts. */
19
+ export interface ConflictOperation {
20
+ readonly model: string;
21
+ readonly id: string;
22
+ readonly type: 'CREATE' | 'UPDATE' | 'DELETE' | 'ARCHIVE' | 'UNARCHIVE';
23
+ readonly input?: Readonly<Record<string, unknown>>;
24
+ }
25
+ export interface StaleContextConflict extends ConflictBase {
26
+ readonly kind: 'stale_context';
27
+ readonly operation: ConflictOperation;
28
+ /** Watermark the committer reasoned against. */
29
+ readonly readAt: number;
30
+ /** Most recent delta id on the target. */
31
+ readonly observedSyncId: number;
32
+ }
33
+ export interface IntentHeldConflict extends ConflictBase {
34
+ readonly kind: 'intent_held';
35
+ readonly heldBy: ParticipantRef;
36
+ readonly intentId: string;
37
+ readonly entityType: string;
38
+ readonly entityId: string;
39
+ /** Holder's intent expiry (ms since epoch). */
40
+ readonly expiresAt: number;
41
+ }
42
+ /**
43
+ * The discriminated union the policy receives. Switch on `.kind` to
44
+ * narrow to the variant.
45
+ */
46
+ export type Conflict = StaleContextConflict | IntentHeldConflict;
47
+ /** What the policy returns. */
48
+ export type ConflictDecision = {
49
+ readonly action: 'reject';
50
+ readonly reason?: string;
51
+ } | {
52
+ readonly action: 'allow';
53
+ readonly note?: string;
54
+ };
55
+ /**
56
+ * Pluggable decision function. Sync or async.
57
+ *
58
+ * ```ts
59
+ * const policy: ConflictPolicy = (conflict) => {
60
+ * if (conflict.committer.id.startsWith('linter:')) {
61
+ * return { action: 'allow', note: 'cosmetic writer' };
62
+ * }
63
+ * return defaultPolicy(conflict);
64
+ * };
65
+ * ```
66
+ */
67
+ export type ConflictPolicy = (conflict: Conflict) => ConflictDecision | Promise<ConflictDecision>;
68
+ /**
69
+ * Default: reject every conflict. Safe fallback when no custom policy
70
+ * is wired — the engine never silently allows a stale or
71
+ * intent-conflicting write through.
72
+ */
73
+ export declare const defaultPolicy: ConflictPolicy;
74
+ export {};
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Conflict policy — the engine detects, the policy decides.
3
+ *
4
+ * Two conflict shapes today: `stale_context` (a write whose `readAt`
5
+ * is older than the latest delta on the target) and `intent_held`
6
+ * (a participant claims a target someone else is already claiming).
7
+ * Adding new shapes is additive on the discriminated union.
8
+ */
9
+ /**
10
+ * Default: reject every conflict. Safe fallback when no custom policy
11
+ * is wired — the engine never silently allows a stale or
12
+ * intent-conflicting write through.
13
+ */
14
+ export const defaultPolicy = (conflict) => ({
15
+ action: 'reject',
16
+ reason: conflict.kind === 'stale_context' ? 'stale_context' : 'intent_conflict',
17
+ });
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Principal constructors — a thin typed façade over the raw
3
+ * `SessionRef` / `AgentRef` shapes so call sites don't have to memorize
4
+ * the discriminated-union tags.
5
+ *
6
+ * ```ts
7
+ * import Ablo, { session } from '@ablo/sync-engine';
8
+ *
9
+ * const ablo = Ablo({ schema, apiKey });
10
+ * const participant = await ablo.participants.join({
11
+ * type: 'Matter',
12
+ * id: 'deal-1',
13
+ * });
14
+ * ```
15
+ *
16
+ * Browser-human flows use `session(...)`. Agent-spawn-agent flows use
17
+ * `agent(...)`, but those rarely appear in customer code because the
18
+ * participant layer handles attenuation.
19
+ *
20
+ * These are pure — no I/O, no hidden state. If the shape ever grows a
21
+ * required field (say, a Biscuit scope hint), the helper is the one
22
+ * place to flag migrations.
23
+ */
24
+ import type { AgentRef, SessionRef } from './types/streams.js';
25
+ /**
26
+ * Build a `SessionRef` from the identifiers your auth system already
27
+ * holds. Typical inputs: the Better Auth session id, the user id, and
28
+ * the organization the session is scoped to.
29
+ */
30
+ export declare function session(params: {
31
+ id: string;
32
+ userId: string;
33
+ organizationId: string;
34
+ }): SessionRef;
35
+ /**
36
+ * Build an `AgentRef` from an agent id + the capability token that
37
+ * authenticates it. Rare in application code — the common path is
38
+ * `participant.join(child)` where the parent's token is attenuated
39
+ * automatically.
40
+ */
41
+ export declare function agent(params: {
42
+ id: string;
43
+ capabilityToken: string;
44
+ }): AgentRef;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Principal constructors — a thin typed façade over the raw
3
+ * `SessionRef` / `AgentRef` shapes so call sites don't have to memorize
4
+ * the discriminated-union tags.
5
+ *
6
+ * ```ts
7
+ * import Ablo, { session } from '@ablo/sync-engine';
8
+ *
9
+ * const ablo = Ablo({ schema, apiKey });
10
+ * const participant = await ablo.participants.join({
11
+ * type: 'Matter',
12
+ * id: 'deal-1',
13
+ * });
14
+ * ```
15
+ *
16
+ * Browser-human flows use `session(...)`. Agent-spawn-agent flows use
17
+ * `agent(...)`, but those rarely appear in customer code because the
18
+ * participant layer handles attenuation.
19
+ *
20
+ * These are pure — no I/O, no hidden state. If the shape ever grows a
21
+ * required field (say, a Biscuit scope hint), the helper is the one
22
+ * place to flag migrations.
23
+ */
24
+ /**
25
+ * Build a `SessionRef` from the identifiers your auth system already
26
+ * holds. Typical inputs: the Better Auth session id, the user id, and
27
+ * the organization the session is scoped to.
28
+ */
29
+ export function session(params) {
30
+ return {
31
+ kind: 'session',
32
+ id: params.id,
33
+ userId: params.userId,
34
+ organizationId: params.organizationId,
35
+ };
36
+ }
37
+ /**
38
+ * Build an `AgentRef` from an agent id + the capability token that
39
+ * authenticates it. Rare in application code — the common path is
40
+ * `participant.join(child)` where the parent's token is attenuated
41
+ * automatically.
42
+ */
43
+ export function agent(params) {
44
+ return {
45
+ kind: 'agent',
46
+ id: params.id,
47
+ capabilityToken: params.capabilityToken,
48
+ };
49
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * HTTP client for the generic /sync/query endpoint.
3
+ *
4
+ * Thin wrapper over fetch() that:
5
+ * - POSTs a QueryBatch as JSON
6
+ * - Handles auth via `credentials: 'include'` (session cookie)
7
+ * - Throws on non-2xx responses
8
+ * - Parses the response into a typed QueryBatchResult
9
+ *
10
+ * The higher-level BootstrapHelper methods (fetchDeckSlideLayers,
11
+ * fetchChatMessages, etc.) use this to issue structured queries
12
+ * without duplicating the fetch boilerplate.
13
+ */
14
+ import type { QueryBatch, QueryBatchResult } from './types.js';
15
+ export interface PostQueryOptions {
16
+ /**
17
+ * Full base URL of the sync server including the `/api` prefix.
18
+ * The query endpoint is appended as `/sync/query`, so the final
19
+ * request hits `${baseUrl}/sync/query`.
20
+ */
21
+ baseUrl: string;
22
+ /** Timeout in ms for the fetch request. Default: 30000. */
23
+ fetchTimeout?: number;
24
+ /**
25
+ * Capability token (Biscuit) to attach as `Authorization: Bearer <token>`.
26
+ * Required for Node consumers (agent-worker, server-side tests) that
27
+ * have no session cookie to ride. Browser consumers can omit this and
28
+ * fall back to `credentials: 'include'`. When both are present the
29
+ * server prefers the Bearer header (see `agentTokenProvider` in
30
+ * `apps/sync-server/src/auth`), so passing the token in browser code
31
+ * is harmless.
32
+ */
33
+ capabilityToken?: string;
34
+ }
35
+ /**
36
+ * POST a batch of queries to /sync/query. Returns the parsed
37
+ * QueryBatchResult. Throws a descriptive error on HTTP failure.
38
+ *
39
+ * The server guarantees results[i] corresponds to queries[i] in the
40
+ * request — callers can rely on index alignment to extract typed
41
+ * results from a multi-query batch.
42
+ */
43
+ export declare function postQuery(options: PostQueryOptions, batch: QueryBatch): Promise<QueryBatchResult>;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * HTTP client for the generic /sync/query endpoint.
3
+ *
4
+ * Thin wrapper over fetch() that:
5
+ * - POSTs a QueryBatch as JSON
6
+ * - Handles auth via `credentials: 'include'` (session cookie)
7
+ * - Throws on non-2xx responses
8
+ * - Parses the response into a typed QueryBatchResult
9
+ *
10
+ * The higher-level BootstrapHelper methods (fetchDeckSlideLayers,
11
+ * fetchChatMessages, etc.) use this to issue structured queries
12
+ * without duplicating the fetch boilerplate.
13
+ */
14
+ import { z } from 'zod';
15
+ // ── Response validation ─────────────────────────────────────────────────
16
+ //
17
+ // Each result slot is an array of rows (or an object for bundled
18
+ // responses). Server-side per-query failures surface here as `[]`, but
19
+ // the server logs them via `console.error('[query.error] ...')` — alert
20
+ // on that prefix, not on emptiness. Parsing through Zod normalizes
21
+ // `null` slots into empty arrays so downstream callers never see raw
22
+ // null.
23
+ const QueryResultSchema = z
24
+ .union([z.array(z.unknown()), z.record(z.string(), z.unknown()), z.null()])
25
+ .transform((val) => {
26
+ if (val === null)
27
+ return [];
28
+ return val;
29
+ });
30
+ const QueryBatchResultSchema = z
31
+ .object({
32
+ results: z.array(QueryResultSchema),
33
+ })
34
+ .passthrough();
35
+ /**
36
+ * POST a batch of queries to /sync/query. Returns the parsed
37
+ * QueryBatchResult. Throws a descriptive error on HTTP failure.
38
+ *
39
+ * The server guarantees results[i] corresponds to queries[i] in the
40
+ * request — callers can rely on index alignment to extract typed
41
+ * results from a multi-query batch.
42
+ */
43
+ export async function postQuery(options, batch) {
44
+ const url = `${options.baseUrl}/sync/query`;
45
+ const timeout = options.fetchTimeout ?? 30_000;
46
+ // Race the fetch against a timeout so hung requests don't block
47
+ // the calling helper indefinitely.
48
+ const controller = new AbortController();
49
+ const timer = setTimeout(() => controller.abort(), timeout);
50
+ try {
51
+ const headers = { 'Content-Type': 'application/json' };
52
+ if (options.capabilityToken) {
53
+ headers.Authorization = `Bearer ${options.capabilityToken}`;
54
+ }
55
+ const response = await fetch(url, {
56
+ method: 'POST',
57
+ headers,
58
+ credentials: 'include',
59
+ body: JSON.stringify(batch),
60
+ signal: controller.signal,
61
+ });
62
+ if (!response.ok) {
63
+ // Direct console.error is INTENTIONAL — operators alert on the
64
+ // `[postQuery.error]` prefix in browser console. Routing through
65
+ // an injected logger here would require a coordinated change to
66
+ // the alerting pipeline. Tracked as future work; the dual-channel
67
+ // alternative (logger + observability.captureException) is the
68
+ // production target. Never throw — fire-and-forget callers would
69
+ // kill Next.js router on unhandled rejection.
70
+ console.error(`[postQuery.error] ${response.status} ${response.statusText} for ${batch.queries.map((q) => q.model).join(',')}`);
71
+ return { results: batch.queries.map(() => []) };
72
+ }
73
+ const raw = await response.json();
74
+ const parsed = QueryBatchResultSchema.safeParse(raw);
75
+ if (!parsed.success) {
76
+ console.error('[postQuery.error] malformed response:', parsed.error.issues);
77
+ return { results: batch.queries.map(() => []) };
78
+ }
79
+ return parsed.data;
80
+ }
81
+ finally {
82
+ clearTimeout(timer);
83
+ }
84
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Query module barrel — re-exports the public surface for convenience.
3
+ * See types.ts and client.ts for the actual definitions.
4
+ */
5
+ export type { Query, QueryBatch, QueryBatchResult } from './types.js';
6
+ export { postQuery, type PostQueryOptions } from './client.js';
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Query module barrel — re-exports the public surface for convenience.
3
+ * See types.ts and client.ts for the actual definitions.
4
+ */
5
+ export { postQuery } from './client.js';
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Structured query types for the generic /sync/query endpoint.
3
+ *
4
+ * Zero-shaped ZQL-ish wire format: `where` is a flat list of `[col, op, val]`
5
+ * tuples (AND'd together), and `related` is a list of schema-declared
6
+ * relation names to traverse. The server compiler reads the schema's
7
+ * relation metadata to turn `related: ['layers']` into the right JOIN.
8
+ *
9
+ * # Why this shape
10
+ *
11
+ * An earlier revision used equality-only `where: Record<string, unknown>`
12
+ * plus a PK-only `ids: string[]` batch field. That worked for simple cases
13
+ * but collapsed on two real workloads:
14
+ *
15
+ * 1. "Fetch all layers for these slide IDs" — the IDs are foreign keys
16
+ * (`SlideLayer.slideId`), not primary keys. The old `ids` field
17
+ * filtered on `id`, silently returning empty.
18
+ *
19
+ * 2. "Fetch all layers for this deck" — needs a JOIN through
20
+ * `slides.deck_id → slide_layers.slide_id`. Equality-only `where` had
21
+ * no way to express it, so the Go server hardcoded a dispatch case.
22
+ *
23
+ * Both are generic patterns ("batch by FK column", "filter via relation")
24
+ * that should be first-class in the protocol, not model-specific escape
25
+ * hatches on the server. This shape matches Zero's ZQL:
26
+ *
27
+ * - `where('slideId', 'IN', ids)` → `['slideId', 'IN', ids]`
28
+ * - `.related('layers')` → `related: ['layers']`
29
+ *
30
+ * The server's compiler stays schema-driven: given a model name, it reads
31
+ * the schema's declared relations to emit JOIN SQL, and given a `[col, op,
32
+ * val]` tuple it emits a WHERE fragment — never a switch on specific model
33
+ * names. Adding a new model or relation is a schema change, not a server
34
+ * change.
35
+ */
36
+ /** Primitive operand types allowed in a where clause. */
37
+ export type WherePrimitive = string | number | boolean | null;
38
+ /**
39
+ * Comparison operators. Mirrors Zero's ZQL set so client authors can
40
+ * lean on familiar semantics and server compilers that already target
41
+ * ZQL stay portable.
42
+ */
43
+ export type WhereOp = '=' | '!=' | '<' | '<=' | '>' | '>=' | 'IN' | 'NOT IN' | 'IS' | 'IS NOT' | 'LIKE' | 'NOT LIKE' | 'ILIKE' | 'NOT ILIKE';
44
+ /**
45
+ * A single condition. Two supported shapes:
46
+ *
47
+ * - `[col, value]` — shortcut for `[col, '=', value]`
48
+ * - `[col, op, value]` — explicit operator
49
+ *
50
+ * The value is a single primitive for scalar operators and an array of
51
+ * primitives for IN/NOT IN.
52
+ */
53
+ export type WhereClause = readonly [col: string, value: WherePrimitive] | readonly [col: string, op: WhereOp, value: WherePrimitive | readonly WherePrimitive[]];
54
+ /**
55
+ * Client-facing where shape for `load({where})` and `deleteMany({where})`.
56
+ *
57
+ * Two shapes accepted, both AND-combined:
58
+ *
59
+ * - Object form: `{ name: 'foo', orgId: '1' }` — each entry is an `=`
60
+ * clause; array values become `IN`. Ergonomic for the common case.
61
+ * - Tuple form: `[['name', 'ILIKE', '%Goldman%'], ['orgId', '1']]` —
62
+ * explicit operators (LIKE/ILIKE/<=/etc.). Matches the wire
63
+ * `WhereClause[]` 1:1, so no translation layer.
64
+ *
65
+ * The two forms compose: pass tuple form when you need an operator,
66
+ * object form otherwise. For OR semantics, run two `load()` calls and
67
+ * union client-side — keeps the protocol AND-only.
68
+ */
69
+ export type LoadWhere<T> = Partial<T> | {
70
+ [K in keyof T]?: T[K] | readonly T[K][];
71
+ } | readonly WhereClause[];
72
+ /** A single structured fetch request. */
73
+ export interface Query {
74
+ /**
75
+ * Client-facing model name (e.g. "File", "SlideLayer", "Message").
76
+ * The server's adapter maps this to the actual database table.
77
+ */
78
+ model: string;
79
+ /**
80
+ * List of where clauses AND'd together. Empty or omitted means "no
81
+ * filter" (still subject to server-side org scoping).
82
+ *
83
+ * Use `['col', 'IN', values]` to batch by any column — the old
84
+ * primary-key-only `ids` field is subsumed by this form.
85
+ */
86
+ where?: readonly WhereClause[];
87
+ /**
88
+ * Relation names declared in the schema for this model. The server's
89
+ * compiler resolves each name via the schema's relation metadata
90
+ * (`relation.hasMany` / `relation.belongsTo`) and emits the JOIN
91
+ * SQL — no model-specific dispatch on the server.
92
+ *
93
+ * Results come back as nested objects under the relation key:
94
+ *
95
+ * { __typename: 'Slide', id: '…', layers: [{ __typename: 'SlideLayer', … }] }
96
+ */
97
+ related?: readonly string[];
98
+ /**
99
+ * Row limit. Applied after where + JOIN, before related nesting.
100
+ * Omit for no limit.
101
+ */
102
+ limit?: number;
103
+ /**
104
+ * Column to order by. For stable pagination. Omit for unordered.
105
+ */
106
+ orderBy?: string;
107
+ /** Order direction. Defaults to `'asc'`. */
108
+ order?: 'asc' | 'desc';
109
+ }
110
+ /** Request body for POST /sync/query. */
111
+ export interface QueryBatch {
112
+ /**
113
+ * Batch of queries to execute in one round trip. Results are
114
+ * returned in request order at the same indices. Keep batches
115
+ * small — the server caps at 16 queries per batch by default.
116
+ */
117
+ queries: Query[];
118
+ }
119
+ /** Response body from POST /sync/query. */
120
+ export interface QueryBatchResult {
121
+ /**
122
+ * Per-query results in request order. `results[i]` corresponds to
123
+ * `queries[i]`. Each element is an array of rows for array-shaped
124
+ * queries, or a bundled object for providers that return multiple
125
+ * collections under named keys.
126
+ *
127
+ * Each row carries `__typename` for client-side model dispatch, plus
128
+ * any `related` keys nested under the row.
129
+ *
130
+ * Failed queries surface as empty arrays. The server logs them via
131
+ * `console.error('[query.error] ...')` — alert on that prefix rather
132
+ * than trying to infer failure from empty results. A tagged-union
133
+ * wire shape that forces caller acknowledgement is the right next
134
+ * step once every `postQuery` consumer is updated at once.
135
+ */
136
+ results: unknown[];
137
+ /**
138
+ * Server watermark observed after the batch ran. Public resource reads
139
+ * expose this as `stamp` and callers thread it into `commits.create({
140
+ * readAt })` to reject stale writes.
141
+ */
142
+ lastSyncId?: number;
143
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Structured query types for the generic /sync/query endpoint.
3
+ *
4
+ * Zero-shaped ZQL-ish wire format: `where` is a flat list of `[col, op, val]`
5
+ * tuples (AND'd together), and `related` is a list of schema-declared
6
+ * relation names to traverse. The server compiler reads the schema's
7
+ * relation metadata to turn `related: ['layers']` into the right JOIN.
8
+ *
9
+ * # Why this shape
10
+ *
11
+ * An earlier revision used equality-only `where: Record<string, unknown>`
12
+ * plus a PK-only `ids: string[]` batch field. That worked for simple cases
13
+ * but collapsed on two real workloads:
14
+ *
15
+ * 1. "Fetch all layers for these slide IDs" — the IDs are foreign keys
16
+ * (`SlideLayer.slideId`), not primary keys. The old `ids` field
17
+ * filtered on `id`, silently returning empty.
18
+ *
19
+ * 2. "Fetch all layers for this deck" — needs a JOIN through
20
+ * `slides.deck_id → slide_layers.slide_id`. Equality-only `where` had
21
+ * no way to express it, so the Go server hardcoded a dispatch case.
22
+ *
23
+ * Both are generic patterns ("batch by FK column", "filter via relation")
24
+ * that should be first-class in the protocol, not model-specific escape
25
+ * hatches on the server. This shape matches Zero's ZQL:
26
+ *
27
+ * - `where('slideId', 'IN', ids)` → `['slideId', 'IN', ids]`
28
+ * - `.related('layers')` → `related: ['layers']`
29
+ *
30
+ * The server's compiler stays schema-driven: given a model name, it reads
31
+ * the schema's declared relations to emit JOIN SQL, and given a `[col, op,
32
+ * val]` tuple it emits a WHERE fragment — never a switch on specific model
33
+ * names. Adding a new model or relation is a schema change, not a server
34
+ * change.
35
+ */
36
+ export {};