@abloatai/ablo 0.35.0 → 0.36.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 (215) hide show
  1. package/CHANGELOG.md +713 -629
  2. package/README.md +56 -519
  3. package/bin/ablo.cjs +39 -0
  4. package/dist/BaseSyncedStore.d.ts +24 -4
  5. package/dist/BaseSyncedStore.js +53 -37
  6. package/dist/Database.d.ts +8 -20
  7. package/dist/Database.js +61 -59
  8. package/dist/InstanceCache.d.ts +6 -2
  9. package/dist/InstanceCache.js +18 -16
  10. package/dist/Model.d.ts +17 -7
  11. package/dist/Model.js +17 -7
  12. package/dist/ModelRegistry.d.ts +4 -0
  13. package/dist/ModelRegistry.js +17 -15
  14. package/dist/NetworkMonitor.d.ts +3 -1
  15. package/dist/NetworkMonitor.js +7 -5
  16. package/dist/SyncClient.d.ts +5 -15
  17. package/dist/SyncClient.js +60 -57
  18. package/dist/client/Ablo.js +30 -19
  19. package/dist/client/createInternalComponents.d.ts +4 -0
  20. package/dist/client/createInternalComponents.js +8 -2
  21. package/dist/client/createModelProxy.d.ts +21 -1
  22. package/dist/client/createModelProxy.js +123 -57
  23. package/dist/client/humans.d.ts +30 -9
  24. package/dist/client/humans.js +45 -19
  25. package/dist/client/reactiveEngine.d.ts +16 -11
  26. package/dist/client/reactiveEngine.js +113 -335
  27. package/dist/client/storeCluster.d.ts +47 -0
  28. package/dist/client/storeCluster.js +118 -0
  29. package/dist/client/storeLifecycle.d.ts +61 -0
  30. package/dist/client/storeLifecycle.js +231 -0
  31. package/dist/context.d.ts +13 -0
  32. package/dist/context.js +23 -0
  33. package/dist/core/index.d.ts +1 -1
  34. package/dist/docs/catalog.js +6 -3
  35. package/dist/index.d.ts +4 -2
  36. package/dist/index.js +4 -2
  37. package/dist/query/client.d.ts +3 -0
  38. package/dist/query/client.js +6 -5
  39. package/dist/react/AbloProvider.d.ts +13 -1
  40. package/dist/react/AbloProvider.js +5 -2
  41. package/dist/react/context.d.ts +2 -2
  42. package/dist/react/createAbloReact.d.ts +56 -0
  43. package/dist/react/createAbloReact.js +51 -0
  44. package/dist/react/index.d.ts +1 -0
  45. package/dist/react/index.js +3 -0
  46. package/dist/react/useAblo.d.ts +9 -2
  47. package/dist/react/useAblo.js +25 -7
  48. package/dist/schema/coordination.js +5 -1
  49. package/dist/schema/index.d.ts +1 -0
  50. package/dist/schema/index.js +4 -0
  51. package/dist/schema/select.js +3 -0
  52. package/dist/schema/serialize.js +3 -0
  53. package/dist/source/adapter.d.ts +7 -5
  54. package/dist/source/adapter.js +7 -5
  55. package/dist/source/index.d.ts +1 -1
  56. package/dist/source/index.js +1 -1
  57. package/dist/{core/storeContract.d.ts → storeContract.d.ts} +5 -5
  58. package/dist/{core → stores}/DatabaseManager.d.ts +3 -1
  59. package/dist/{core → stores}/DatabaseManager.js +13 -12
  60. package/dist/{core → stores}/StoreManager.d.ts +5 -3
  61. package/dist/{core → stores}/StoreManager.js +24 -22
  62. package/dist/stores/SyncActionStore.d.ts +3 -1
  63. package/dist/stores/SyncActionStore.js +9 -7
  64. package/dist/sync/BootstrapFetcher.d.ts +4 -0
  65. package/dist/sync/BootstrapFetcher.js +28 -26
  66. package/dist/sync/OnDemandLoader.d.ts +3 -0
  67. package/dist/sync/OnDemandLoader.js +1 -0
  68. package/dist/sync/bootstrapApply.d.ts +3 -0
  69. package/dist/sync/bootstrapApply.js +2 -2
  70. package/dist/sync/deltaPipeline.d.ts +13 -12
  71. package/dist/sync/deltaPipeline.js +21 -4
  72. package/dist/sync/groupChange.d.ts +3 -0
  73. package/dist/sync/groupChange.js +16 -14
  74. package/dist/sync/participants.d.ts +19 -2
  75. package/dist/sync/participants.js +3 -1
  76. package/dist/sync/schemas.d.ts +2 -1
  77. package/dist/sync/schemas.js +3 -3
  78. package/dist/syncLog/contract.d.ts +20 -0
  79. package/dist/syncLog/contract.js +19 -0
  80. package/dist/syncLog/index.d.ts +1 -0
  81. package/dist/syncLog/index.js +1 -0
  82. package/dist/transaction/auth/capability.d.ts +35 -0
  83. package/dist/transaction/auth/capability.js +25 -0
  84. package/dist/transaction/coordination/awaitClaimGrant.d.ts +7 -0
  85. package/dist/transaction/coordination/awaitClaimGrant.js +12 -0
  86. package/dist/transaction/coordination/claimHeartbeatLoop.d.ts +34 -0
  87. package/dist/transaction/coordination/claimHeartbeatLoop.js +20 -0
  88. package/dist/transaction/coordination/index.d.ts +4 -4
  89. package/dist/transaction/coordination/index.js +4 -3
  90. package/dist/transaction/coordination/locator.d.ts +23 -2
  91. package/dist/transaction/coordination/locator.js +22 -2
  92. package/dist/transaction/coordination/schema.d.ts +125 -62
  93. package/dist/transaction/coordination/schema.js +228 -64
  94. package/dist/transaction/coordination/targetConflict.js +32 -28
  95. package/dist/transaction/errorCodes.d.ts +2 -2
  96. package/dist/transaction/errorCodes.js +13 -9
  97. package/dist/transaction/plugin.d.ts +95 -2
  98. package/dist/transaction/plugin.js +21 -2
  99. package/dist/transaction/resources/httpResources.d.ts +57 -2
  100. package/dist/transaction/resources/modelOperations.d.ts +148 -40
  101. package/dist/transaction/resources/where.d.ts +16 -0
  102. package/dist/transaction/resources/where.js +45 -0
  103. package/dist/transaction/schema/field.d.ts +12 -18
  104. package/dist/transaction/schema/fieldRef.d.ts +38 -0
  105. package/dist/transaction/schema/fieldRef.js +11 -0
  106. package/dist/transaction/schema/openapi.d.ts +16 -15
  107. package/dist/transaction/schema/openapi.js +186 -25
  108. package/dist/transaction/schema/relation.d.ts +7 -2
  109. package/dist/transaction/schema/schema.d.ts +27 -0
  110. package/dist/transaction/schema/schema.js +20 -0
  111. package/dist/transaction/transactions/settlement/commitEnvelope.d.ts +1 -1
  112. package/dist/transaction/transactions/settlement/pendingWrite.d.ts +1 -1
  113. package/dist/transaction/transport/httpClient.d.ts +9 -1
  114. package/dist/transaction/transport/httpClient.js +1 -0
  115. package/dist/transaction/transport/httpTransport.js +156 -44
  116. package/dist/transaction/transport/wsTransport.d.ts +2 -4
  117. package/dist/transaction/transport/wsTransport.js +16 -10
  118. package/dist/transaction/types/streams.d.ts +10 -0
  119. package/dist/transaction/utils/duration.d.ts +25 -0
  120. package/dist/transaction/utils/duration.js +32 -0
  121. package/dist/transaction/wire/accountResponses.d.ts +69 -0
  122. package/dist/transaction/wire/accountResponses.js +36 -1
  123. package/dist/transaction/wire/auth.d.ts +9 -2
  124. package/dist/transaction/wire/auth.js +7 -1
  125. package/dist/transaction/wire/claims.d.ts +164 -97
  126. package/dist/transaction/wire/claims.js +126 -28
  127. package/dist/transaction/wire/commit.d.ts +1 -1
  128. package/dist/transaction/wire/feedEvent.d.ts +27 -0
  129. package/dist/transaction/wire/feedEvent.js +27 -1
  130. package/dist/transaction/wire/frames.d.ts +2 -2
  131. package/dist/transaction/wire/inboundFrames.d.ts +12 -2
  132. package/dist/transaction/wire/index.d.ts +10 -6
  133. package/dist/transaction/wire/index.js +12 -3
  134. package/dist/transaction/wire/modelMutations.d.ts +31 -0
  135. package/dist/transaction/wire/modelMutations.js +52 -0
  136. package/dist/transaction/wire/modelShape.d.ts +78 -0
  137. package/dist/transaction/wire/modelShape.js +74 -0
  138. package/dist/transactions/mutations/MutationQueue.d.ts +6 -0
  139. package/dist/transactions/mutations/MutationQueue.js +55 -45
  140. package/dist/transactions/mutations/commitPayload.d.ts +3 -2
  141. package/dist/transactions/mutations/commitPayload.js +5 -5
  142. package/dist/transactions/mutations/deltaConfirmation.d.ts +4 -0
  143. package/dist/transactions/mutations/deltaConfirmation.js +10 -8
  144. package/dist/transactions/mutations/replayValidation.d.ts +2 -1
  145. package/dist/transactions/mutations/replayValidation.js +3 -2
  146. package/dist/{core → views}/QueryView.d.ts +1 -1
  147. package/dist/{core → views}/QueryView.js +1 -1
  148. package/dist/{core → views}/ViewRegistry.d.ts +1 -1
  149. package/dist/{core/queryUtils.d.ts → views/incrementalView.d.ts} +6 -6
  150. package/dist/{core/queryUtils.js → views/incrementalView.js} +6 -6
  151. package/docs/agents.md +1 -1
  152. package/docs/api-keys.md +6 -6
  153. package/docs/api.md +5 -43
  154. package/docs/audit.md +4 -3
  155. package/docs/cli.md +11 -11
  156. package/docs/client-behavior.md +4 -4
  157. package/docs/concurrency-convention.md +28 -42
  158. package/docs/coordination.md +235 -83
  159. package/docs/data-sources.md +4 -4
  160. package/docs/debugging.md +34 -12
  161. package/docs/deployment.md +8 -8
  162. package/docs/examples/scoped-agent.md +3 -3
  163. package/docs/groups.md +57 -3
  164. package/docs/guarantees.md +37 -10
  165. package/docs/how-it-works.md +29 -5
  166. package/docs/idempotency.md +6 -6
  167. package/docs/identity.md +22 -23
  168. package/docs/index.md +8 -8
  169. package/docs/integration-guide.md +14 -3
  170. package/docs/mcp.md +7 -7
  171. package/docs/migration.md +34 -15
  172. package/docs/projects.md +1 -1
  173. package/docs/react.md +19 -8
  174. package/docs/sessions.md +1 -1
  175. package/docs/webhooks.md +9 -9
  176. package/llms.txt +4 -4
  177. package/package.json +13 -20
  178. package/dist/cli.cjs +0 -288600
  179. package/dist/testing/fixtures/bootstrap.d.ts +0 -49
  180. package/dist/testing/fixtures/bootstrap.js +0 -59
  181. package/dist/testing/fixtures/deltas.d.ts +0 -83
  182. package/dist/testing/fixtures/deltas.js +0 -136
  183. package/dist/testing/fixtures/httpResponses.d.ts +0 -70
  184. package/dist/testing/fixtures/httpResponses.js +0 -90
  185. package/dist/testing/fixtures/models.d.ts +0 -83
  186. package/dist/testing/fixtures/models.js +0 -272
  187. package/dist/testing/helpers/reactWrapper.d.ts +0 -69
  188. package/dist/testing/helpers/reactWrapper.js +0 -67
  189. package/dist/testing/helpers/syncEngineHarness.d.ts +0 -54
  190. package/dist/testing/helpers/syncEngineHarness.js +0 -73
  191. package/dist/testing/helpers/wait.d.ts +0 -30
  192. package/dist/testing/helpers/wait.js +0 -49
  193. package/dist/testing/index.d.ts +0 -23
  194. package/dist/testing/index.js +0 -33
  195. package/dist/testing/mocks/FakeDatabase.d.ts +0 -18
  196. package/dist/testing/mocks/FakeDatabase.js +0 -10
  197. package/dist/testing/mocks/MockMutationExecutor.d.ts +0 -87
  198. package/dist/testing/mocks/MockMutationExecutor.js +0 -186
  199. package/dist/testing/mocks/MockNetworkMonitor.d.ts +0 -20
  200. package/dist/testing/mocks/MockNetworkMonitor.js +0 -46
  201. package/dist/testing/mocks/MockSyncContext.d.ts +0 -51
  202. package/dist/testing/mocks/MockSyncContext.js +0 -72
  203. package/dist/testing/mocks/MockSyncStore.d.ts +0 -88
  204. package/dist/testing/mocks/MockSyncStore.js +0 -171
  205. package/dist/testing/mocks/MockWebSocket.d.ts +0 -71
  206. package/dist/testing/mocks/MockWebSocket.js +0 -118
  207. package/docs/interaction-model.md +0 -99
  208. /package/dist/{core → query}/QueryProcessor.d.ts +0 -0
  209. /package/dist/{core → query}/QueryProcessor.js +0 -0
  210. /package/dist/{core/storeContract.js → storeContract.js} +0 -0
  211. /package/dist/{core → stores}/openIDBWithTimeout.d.ts +0 -0
  212. /package/dist/{core → stores}/openIDBWithTimeout.js +0 -0
  213. /package/dist/{source → transaction}/footprint.d.ts +0 -0
  214. /package/dist/{source → transaction}/footprint.js +0 -0
  215. /package/dist/{core → views}/ViewRegistry.js +0 -0
@@ -302,7 +302,9 @@ export function useJoin(opts) {
302
302
  let cancelled = false;
303
303
  const claimId = createParticipantClaimId();
304
304
  ws.sendClaim(claimId, scopedSyncGroups, {
305
- ttlSeconds: parseParticipantTtlSeconds(opts.ttlSeconds),
305
+ // Reading the retired spelling IS the compatibility path; it goes at 0.37.0.
306
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
307
+ ttlSeconds: parseParticipantTtlSeconds(opts.ttl ?? opts.ttlSeconds),
306
308
  })
307
309
  .then(() => {
308
310
  if (!cancelled)
@@ -320,7 +322,8 @@ export function useJoin(opts) {
320
322
  ws.sendRelease(claimId);
321
323
  void store.unpinScope?.(scope);
322
324
  };
323
- }, [engine, paused, scopeKey, syncStatus.name, opts.ttlSeconds, opts.claim]);
325
+ // eslint-disable-next-line @typescript-eslint/no-deprecated -- same compatibility read as above.
326
+ }, [engine, paused, scopeKey, syncStatus.name, opts.ttl, opts.ttlSeconds, opts.claim]);
324
327
  // Bridge the engine's presence + claims streams into React state.
325
328
  // Plain useState + useEffect is sufficient — mid-frame tearing on a
326
329
  // peer list is harmless (users won't notice one frame of stale
@@ -1,7 +1,7 @@
1
1
  import { type ReactNode } from 'react';
2
2
  import type { Schema } from '../transaction/schema/schema.js';
3
- import type { SyncStoreContract } from '../core/storeContract.js';
4
- export type { SyncStoreContract, LocalMutation } from '../core/storeContract.js';
3
+ import type { SyncStoreContract } from '../storeContract.js';
4
+ export type { SyncStoreContract, LocalMutation } from '../storeContract.js';
5
5
  export interface SyncReactContext {
6
6
  store: SyncStoreContract;
7
7
  /** The organization id used as the default scope for reads and writes. */
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The typed react binding — the schema generic is captured ONCE, at a factory
3
+ * call in app code, and every hook the factory returns is born typed. This is
4
+ * the shape the reference libraries converged on independently (tRPC's
5
+ * `createTRPCReact`, XState's `createActorContext`, react-redux's
6
+ * `withTypes`): no module augmentation, no generic parameters at call sites,
7
+ * and — once the legacy generic erasure retires — no casts anywhere on the
8
+ * path from context to component.
9
+ *
10
+ * The app's one binding file, by convention:
11
+ *
12
+ * ```ts
13
+ * // lib/ablo.ts
14
+ * import { createAbloReact } from '@abloatai/ablo/react';
15
+ * import { schema } from './schema';
16
+ *
17
+ * export const { AbloProvider, useAblo } = createAbloReact(schema);
18
+ * ```
19
+ *
20
+ * Components then import `useAblo` from `lib/ablo` and never spell a type
21
+ * argument; `useAblo()` is `Ablo<S> | null`, and a selector's `ablo`
22
+ * parameter is the reactive-read view of the same `S`.
23
+ */
24
+ import { type ReactElement } from 'react';
25
+ import { type AbloProviderProps } from './AbloProvider.js';
26
+ import { type AbloSelector, type ModelClientSelector, type UseAbloHydratedModelResult, type UseAbloModelOptions, type UseAbloModelResult } from './useAblo.js';
27
+ import type { Ablo } from '../client/Ablo.js';
28
+ import type { ModelOperations } from '../client/createModelProxy.js';
29
+ import type { Schema, SchemaRecord } from '../transaction/schema/schema.js';
30
+ /** What a binding returns: the provider and the hook, with `S` fixed. */
31
+ export interface AbloReactBinding<S extends SchemaRecord> {
32
+ /** `AbloProvider` with its `client` prop typed `Ablo<S>` — same component,
33
+ * no per-app generics. */
34
+ AbloProvider: (props: AbloProviderProps<S>) => ReactElement;
35
+ /** `useAblo` with the schema bound — the same overloads as the global
36
+ * hook, minus the type arguments. */
37
+ useAblo: {
38
+ (): Ablo<S> | null;
39
+ <T>(select: AbloSelector<S, T>): T | undefined;
40
+ <T, C>(modelClient: ModelOperations<T, C>, id: string, options: UseAbloModelOptions<T> & {
41
+ readonly initial: T;
42
+ }): UseAbloHydratedModelResult<T>;
43
+ <T, C>(select: ModelClientSelector<S, T, C>, id: string, options: UseAbloModelOptions<T> & {
44
+ readonly initial: T;
45
+ }): UseAbloHydratedModelResult<T>;
46
+ <T, C>(modelClient: ModelOperations<T, C>, id: string, options?: UseAbloModelOptions<T>): UseAbloModelResult<T>;
47
+ <T, C>(select: ModelClientSelector<S, T, C>, id: string, options?: UseAbloModelOptions<T>): UseAbloModelResult<T>;
48
+ };
49
+ }
50
+ /**
51
+ * Bind the react surface to one schema. The schema value is taken for
52
+ * inference — write `createAbloReact(schema)`, never a hand-spelled type
53
+ * argument — and it is the seam where the binding's own typed context arrives
54
+ * when the legacy erasure retires (docs/plans/typed-react-binding.md, step 3).
55
+ */
56
+ export declare function createAbloReact<S extends SchemaRecord>(schema: Schema<S>): AbloReactBinding<S>;
@@ -0,0 +1,51 @@
1
+ 'use client';
2
+ /**
3
+ * The typed react binding — the schema generic is captured ONCE, at a factory
4
+ * call in app code, and every hook the factory returns is born typed. This is
5
+ * the shape the reference libraries converged on independently (tRPC's
6
+ * `createTRPCReact`, XState's `createActorContext`, react-redux's
7
+ * `withTypes`): no module augmentation, no generic parameters at call sites,
8
+ * and — once the legacy generic erasure retires — no casts anywhere on the
9
+ * path from context to component.
10
+ *
11
+ * The app's one binding file, by convention:
12
+ *
13
+ * ```ts
14
+ * // lib/ablo.ts
15
+ * import { createAbloReact } from '@abloatai/ablo/react';
16
+ * import { schema } from './schema';
17
+ *
18
+ * export const { AbloProvider, useAblo } = createAbloReact(schema);
19
+ * ```
20
+ *
21
+ * Components then import `useAblo` from `lib/ablo` and never spell a type
22
+ * argument; `useAblo()` is `Ablo<S> | null`, and a selector's `ablo`
23
+ * parameter is the reactive-read view of the same `S`.
24
+ */
25
+ import { createContext, createElement, useContext } from 'react';
26
+ import { AbloProvider } from './AbloProvider.js';
27
+ import { useAbloImpl, } from './useAblo.js';
28
+ /**
29
+ * Bind the react surface to one schema. The schema value is taken for
30
+ * inference — write `createAbloReact(schema)`, never a hand-spelled type
31
+ * argument — and it is the seam where the binding's own typed context arrives
32
+ * when the legacy erasure retires (docs/plans/typed-react-binding.md, step 3).
33
+ */
34
+ export function createAbloReact(schema) {
35
+ void schema;
36
+ // The binding's own context — created here, AFTER the schema generic is
37
+ // known, so it is typed `Ablo<S>` from birth (the XState
38
+ // `createActorContext` shape). A hook that reads it never rebinds and
39
+ // never casts; a binding hook mounted under a legacy provider (no bound
40
+ // provider in the tree) reads `null` here and falls through to the shared
41
+ // implementation's internal-context fallback.
42
+ const BoundClientContext = createContext(null);
43
+ function BoundAbloProvider(props) {
44
+ return createElement(BoundClientContext.Provider, { value: props.client }, createElement((AbloProvider), props));
45
+ }
46
+ function useBoundAblo(modelOrSelect, id, options) {
47
+ const bound = useContext(BoundClientContext);
48
+ return useAbloImpl(bound, modelOrSelect, id, options);
49
+ }
50
+ return { AbloProvider: BoundAbloProvider, useAblo: useBoundAblo };
51
+ }
@@ -52,6 +52,7 @@ export { useSyncStatus, type SyncStatusSnapshot, } from './useSyncStatus.js';
52
52
  export { useErrorListener } from './useErrorListener.js';
53
53
  export { useMutationFailureListener, type MutationFailurePayload, } from './useMutationFailureListener.js';
54
54
  export { useCurrentUserId } from './useCurrentUserId.js';
55
+ export { createAbloReact, type AbloReactBinding } from './createAbloReact.js';
55
56
  export { useReactive } from './useReactive.js';
56
57
  export type { MutateActions } from '../mutators/mutateActions.js';
57
58
  export type { ReaderActions, ReaderFindOptions } from '../mutators/readerActions.js';
@@ -52,6 +52,9 @@ export { useSyncStatus, } from './useSyncStatus.js';
52
52
  export { useErrorListener } from './useErrorListener.js';
53
53
  export { useMutationFailureListener, } from './useMutationFailureListener.js';
54
54
  export { useCurrentUserId } from './useCurrentUserId.js';
55
+ // The typed binding — capture the schema generic once, in the app's one
56
+ // binding file, and import born-typed hooks everywhere (the tRPC shape).
57
+ export { createAbloReact } from './createAbloReact.js';
55
58
  // ── Primitive for building custom reactive hooks ──────────────────
56
59
  //
57
60
  // Consumers building bespoke hooks on top of the SDK should call
@@ -11,8 +11,8 @@ import type { ResolveSchema } from '../transaction/types/global.js';
11
11
  type DefaultModels = ResolveSchema extends {
12
12
  models: infer M;
13
13
  } ? M extends SchemaRecord ? M : SchemaRecord : SchemaRecord;
14
- type ModelClientSelector<R extends SchemaRecord, T, C> = (ablo: AbloReads<R>) => ModelOperations<T, C>;
15
- type AbloSelector<R extends SchemaRecord, T> = (ablo: AbloReads<R>) => T;
14
+ export type ModelClientSelector<R extends SchemaRecord, T, C> = (ablo: AbloReads<R>) => ModelOperations<T, C>;
15
+ export type AbloSelector<R extends SchemaRecord, T> = (ablo: AbloReads<R>) => T;
16
16
  export interface UseAbloModelOptions<T> {
17
17
  /**
18
18
  * An initial row, usually from a server component or a route loader. The hook
@@ -42,6 +42,13 @@ export type UseAbloHydratedModelResult<T> = Omit<UseAbloModelResult<T>, 'data'>
42
42
  * Schema: typeof schema } }`); the default type then resolves through your
43
43
  * schema's models, so call sites stay clean:
44
44
  *
45
+ * **Prefer the binding.** `createAbloReact(schema)` captures the schema once
46
+ * in your app's binding file and returns a `useAblo` that needs none of the
47
+ * typing arrangements below — no type argument, no `Register` declaration
48
+ * (see `react.md`). Passing an explicit schema type argument to THIS hook is
49
+ * deprecated in favor of that binding; it keeps working for shared packages
50
+ * that cannot bind a concrete schema.
51
+ *
45
52
  * ```ts
46
53
  * // With the Register augmentation (recommended):
47
54
  * const ablo = useAblo();
@@ -15,13 +15,13 @@ function rebindEngine(engine) {
15
15
  return engine;
16
16
  }
17
17
  /**
18
- * The same rebind viewed through the reactive-read surface — the identical
19
- * runtime object, with model reads typed as snapshot rows, because everything
20
- * a selector returns is converted through `snapshotValue` before the hook
21
- * hands it back.
18
+ * The reactive-read view of a client — the identical runtime object, with
19
+ * model reads typed as snapshot rows, because everything a selector returns
20
+ * is converted through `snapshotValue` before the hook hands it back. Same
21
+ * generic in and out, so this compiles with no schema rebinding.
22
22
  */
23
23
  function reactiveReads(engine) {
24
- return rebindEngine(engine);
24
+ return engine;
25
25
  }
26
26
  function readModelResult(engine, modelClient, id, initial) {
27
27
  if (!modelClient || id === undefined) {
@@ -55,8 +55,26 @@ function snapshotValue(value) {
55
55
  return value;
56
56
  }
57
57
  export function useAblo(modelOrSelect, id, options) {
58
+ return useAbloImpl(null, modelOrSelect, id, options);
59
+ }
60
+ /**
61
+ * @internal The one implementation behind `useAblo` and the bound hooks a
62
+ * `createAbloReact` binding returns — written once so the reactive read path
63
+ * cannot fork between the global hook and a factory's.
64
+ *
65
+ * `boundClient` is a binding's own context value — typed `Ablo<S>` at the
66
+ * factory, so that path never rebinds and never casts. `null` means "no
67
+ * binding provider in this tree": the global hook always passes it, and a
68
+ * binding hook mounted under a legacy provider falls through to the erased
69
+ * internal context, which is what keeps both mounts working while the last
70
+ * legacy mount migrates.
71
+ */
72
+ export function useAbloImpl(boundClient, modelOrSelect, id, options) {
58
73
  const ctx = useContext(AbloInternalContext);
59
- const engine = ctx?.engine ?? null;
74
+ // The bound client wins — it is already `Ablo<R>`, no rebinding. The
75
+ // fallback is the ONE remaining schema rebind in the SDK; it retires with
76
+ // the last legacy provider mount (docs/plans/typed-react-binding.md).
77
+ const engine = boundClient ?? (ctx?.engine ? rebindEngine(ctx.engine) : null);
60
78
  const initial = options?.initial;
61
79
  const isSelectorOnly = typeof modelOrSelect === 'function' && id === undefined;
62
80
  const modelClient = typeof modelOrSelect === 'function' && id !== undefined
@@ -98,5 +116,5 @@ export function useAblo(modelOrSelect, id, options) {
98
116
  return selected;
99
117
  if (modelOrSelect)
100
118
  return modelResult;
101
- return engine === null ? null : rebindEngine(engine);
119
+ return engine;
102
120
  }
@@ -65,7 +65,11 @@ class CoordinationAxis {
65
65
  }
66
66
  /** Apply one more rule, later winning on a repeated kind. */
67
67
  and(rule) {
68
- return new CoordinationAxis({ ...this, ...rule });
68
+ // Assigned rather than spread, and for the reason the merge form below
69
+ // assigns too: what carries forward is the dispositions, which are own
70
+ // enumerable properties. The chaining methods live on the prototype and the
71
+ // constructor puts them back, so a chain accumulates nothing but data.
72
+ return new CoordinationAxis(Object.assign({}, this, rule));
69
73
  }
70
74
  humansOverwrite() {
71
75
  return this.and(humansOverwrite());
@@ -22,6 +22,7 @@
22
22
  */
23
23
  export { z } from 'zod';
24
24
  export { field, indexed, getFieldMeta, type FieldBuilder, type FieldMeta } from '../transaction/schema/field.js';
25
+ export { isFieldRef, type FieldRef } from '../transaction/schema/fieldRef.js';
25
26
  export { relation, type RelationDef, type RelationType } from '../transaction/schema/relation.js';
26
27
  export { tenancySchema, scopedViaRefSchema, policyInputSchema, resolvePolicy, resolveTenancy, tenancyColumn, DEFAULT_ORG_COLUMN, type Tenancy, type ScopedViaRef, type PolicyInput, } from '../transaction/schema/tenancy.js';
27
28
  export { residencySchema, DEFAULT_RESIDENCY, type ModelResidency, } from '../transaction/schema/residency.js';
@@ -24,6 +24,10 @@
24
24
  export { z } from 'zod';
25
25
  // Field helpers (optional convenience wrappers around Zod)
26
26
  export { field, indexed, getFieldMeta } from '../transaction/schema/field.js';
27
+ // A field as a value rather than a quoted name — `schema.fields.tasks.status`.
28
+ // Surfaces that name a field (claims today) take one of these so a name that
29
+ // does not exist stops compiling.
30
+ export { isFieldRef } from '../transaction/schema/fieldRef.js';
27
31
  // Relation builders
28
32
  export { relation } from '../transaction/schema/relation.js';
29
33
  // Tenancy — the single source of truth for how a model's rows are tenant-scoped.
@@ -21,6 +21,7 @@
21
21
  * scope-inheritance throws instead, because silently losing it would mis-route
22
22
  * a record's fan-out — the selected set must be closed under `parent` edges.
23
23
  */
24
+ import { buildFieldRefs } from '../transaction/schema/schema.js';
24
25
  import { AbloValidationError } from '../transaction/errors.js';
25
26
  import { schemaHash } from './serialize.js';
26
27
  export function selectModels(schema, keys) {
@@ -50,6 +51,8 @@ export function selectModels(schema, keys) {
50
51
  }
51
52
  return {
52
53
  models: models,
54
+ // References for exactly the models this projection kept.
55
+ fields: buildFieldRefs(models),
53
56
  validators: validators,
54
57
  identityRoles: schema.identityRoles,
55
58
  sessionSettings: schema.sessionSettings,
@@ -28,6 +28,7 @@
28
28
  */
29
29
  import { z } from 'zod';
30
30
  import { AbloValidationError } from '../transaction/errors.js';
31
+ import { buildFieldRefs } from '../transaction/schema/schema.js';
31
32
  import { baseFieldsSchema, } from '../transaction/schema/schema.js';
32
33
  /** Current schema-JSON envelope version. Bump this on a breaking change to the
33
34
  * JSON shape itself — not to a user's schema. */
@@ -205,6 +206,8 @@ export function fromSchemaJSON(json) {
205
206
  }
206
207
  return {
207
208
  models: models,
209
+ // A schema rebuilt from JSON carries references too — the shapes are here.
210
+ fields: buildFieldRefs(models),
208
211
  validators: validators,
209
212
  identityRoles: json.identityRoles,
210
213
  sessionSettings: json.sessionSettings ?? {},
@@ -8,16 +8,18 @@
8
8
  * write your own.
9
9
  *
10
10
  * An endpoint adapter reads and writes your database, and it owns the transactional
11
- * outbox and idempotency bookkeeping as well, so you never write those by hand:
11
+ * outbox and idempotency bookkeeping as well, so you never write those by hand.
12
+ * Hand it to the handler in one slot:
12
13
  *
13
14
  * export const POST = dataSource({
14
15
  * schema, apiKey: process.env.ABLO_API_KEY!,
15
- * ...sourceHandlersFromAdapter(prismaDataSource(prisma, schema), schema),
16
+ * adapter: prismaDataSource(prisma, schema),
16
17
  * });
17
18
  *
18
- * `sourceHandlersFromAdapter` is the bridge: it turns a single adapter into the
19
- * handler's `commit`, `events`, and per-model `load` and `list` operations, so no
20
- * code above the adapter needs to know which mapper you chose.
19
+ * That is the path to teach. {@link sourceHandlersFromAdapter} is the lower-level
20
+ * bridge behind it — it expands one adapter into the handler's `commit`, `events`,
21
+ * and per-model `load` and `list` operations, and is worth reaching for only when
22
+ * you want to override one of those while the adapter serves the rest.
21
23
  */
22
24
  import type { SourceListQuery, SourceRequestContext } from './types.js';
23
25
  import type { AdapterCapabilities, ChangeSet, EventsPage, Migration } from './contract.js';
@@ -8,15 +8,17 @@
8
8
  * write your own.
9
9
  *
10
10
  * An endpoint adapter reads and writes your database, and it owns the transactional
11
- * outbox and idempotency bookkeeping as well, so you never write those by hand:
11
+ * outbox and idempotency bookkeeping as well, so you never write those by hand.
12
+ * Hand it to the handler in one slot:
12
13
  *
13
14
  * export const POST = dataSource({
14
15
  * schema, apiKey: process.env.ABLO_API_KEY!,
15
- * ...sourceHandlersFromAdapter(prismaDataSource(prisma, schema), schema),
16
+ * adapter: prismaDataSource(prisma, schema),
16
17
  * });
17
18
  *
18
- * `sourceHandlersFromAdapter` is the bridge: it turns a single adapter into the
19
- * handler's `commit`, `events`, and per-model `load` and `list` operations, so no
20
- * code above the adapter needs to know which mapper you chose.
19
+ * That is the path to teach. {@link sourceHandlersFromAdapter} is the lower-level
20
+ * bridge behind it — it expands one adapter into the handler's `commit`, `events`,
21
+ * and per-model `load` and `list` operations, and is worth reaching for only when
22
+ * you want to override one of those while the adapter serves the rest.
21
23
  */
22
24
  export {};
@@ -20,4 +20,4 @@ export { prismaDataSource, type PrismaLike, type PrismaDataSourceOptions } from
20
20
  export { adapterTableMigrations, endpointOutboxMigrations, idempotencyLedgerMigrations, } from './migrations.js';
21
21
  export { createKyselyMutationAdapter, createKyselyMutationCore, kyselyDataSource, kyselyDirectMutation, kyselyOperationRowId, type KyselyCompiledQuery, type KyselyDeleteBuilder, type KyselyInsertBuilder, type KyselyInsertValuesBuilder, type KyselyLike, type KyselyMutationCore, type KyselyReturningExecutable, type KyselySelectBuilder, type KyselyTransactionBuilder, type KyselyUpdateBuilder, type KyselyUpdateSetBuilder, } from './adapters/kysely.js';
22
22
  export { sourceOperationsIntentHash, sourceChangeIntentHash, assertSourceIdempotencyIntent, assertSourceIdempotencyRetention, sourceEchoTransactionIdSchema, encodeSourceEchoTransactionId, decodeSourceEchoTransactionId, type SourceEchoTransactionId, } from './idempotency.js';
23
- export { ABLO_FOOTPRINT, ABLO_PUBLICATION, ABLO_REPLICATION_SLOT, ABLO_REPLICATION_ROLE, ABLO_WRITE_ROLE, ABLO_IDEMPOTENCY_TABLE, ABLO_OUTBOX_TABLE, REPLICATION_SLOT_NAME, isValidReplicationSlotName, footprintNamesFor, type FootprintArtifact, type FootprintKind, type FootprintNames, type FootprintPlane, } from './footprint.js';
23
+ export { ABLO_FOOTPRINT, ABLO_PUBLICATION, ABLO_REPLICATION_SLOT, ABLO_REPLICATION_ROLE, ABLO_WRITE_ROLE, ABLO_IDEMPOTENCY_TABLE, ABLO_OUTBOX_TABLE, REPLICATION_SLOT_NAME, isValidReplicationSlotName, footprintNamesFor, type FootprintArtifact, type FootprintKind, type FootprintNames, type FootprintPlane, } from '../transaction/footprint.js';
@@ -27,4 +27,4 @@ export { createKyselyMutationAdapter, createKyselyMutationCore, kyselyDataSource
27
27
  export { sourceOperationsIntentHash, sourceChangeIntentHash, assertSourceIdempotencyIntent, assertSourceIdempotencyRetention, sourceEchoTransactionIdSchema, encodeSourceEchoTransactionId, decodeSourceEchoTransactionId, } from './idempotency.js';
28
28
  // What Ablo leaves inside a customer's database, declared once — read by the
29
29
  // setup SQL, the replication runtime, and the audit that reports it back.
30
- export { ABLO_FOOTPRINT, ABLO_PUBLICATION, ABLO_REPLICATION_SLOT, ABLO_REPLICATION_ROLE, ABLO_WRITE_ROLE, ABLO_IDEMPOTENCY_TABLE, ABLO_OUTBOX_TABLE, REPLICATION_SLOT_NAME, isValidReplicationSlotName, footprintNamesFor, } from './footprint.js';
30
+ export { ABLO_FOOTPRINT, ABLO_PUBLICATION, ABLO_REPLICATION_SLOT, ABLO_REPLICATION_ROLE, ABLO_WRITE_ROLE, ABLO_IDEMPOTENCY_TABLE, ABLO_OUTBOX_TABLE, REPLICATION_SLOT_NAME, isValidReplicationSlotName, footprintNamesFor, } from '../transaction/footprint.js';
@@ -9,11 +9,11 @@
9
9
  * This module is type-only: it has no runtime imports and contributes nothing to
10
10
  * the runtime bundle beyond the erased interface declarations.
11
11
  */
12
- import type { Model } from '../Model.js';
13
- import type { ModelScope } from '../transaction/types/index.js';
14
- import type { QueryView, QueryViewOptions } from './QueryView.js';
15
- import type { ViewRegistry } from './ViewRegistry.js';
16
- import type { ParticipantScope } from '../sync/participants.js';
12
+ import type { Model } from './Model.js';
13
+ import type { ModelScope } from './transaction/types/index.js';
14
+ import type { QueryView, QueryViewOptions } from './views/QueryView.js';
15
+ import type { ViewRegistry } from './views/ViewRegistry.js';
16
+ import type { ParticipantScope } from './sync/participants.js';
17
17
  /**
18
18
  * A snapshot of the client's synchronization state, shaped for binding to UI.
19
19
  * {@link SyncStoreContract.syncStatus} exposes a reactive instance of this, and
@@ -5,6 +5,7 @@
5
5
  * then gets its own data database, named `ablo_<hash>`, that holds the synced
6
6
  * rows. {@link DatabaseManager} creates, versions, and deletes both tiers.
7
7
  */
8
+ import type { RuntimeContext } from '../RuntimeContext.js';
8
9
  export interface DatabaseInfo {
9
10
  name: string;
10
11
  userId: string;
@@ -34,9 +35,10 @@ export interface WorkspaceMetadata {
34
35
  * - Provides database info and metadata management
35
36
  */
36
37
  export declare class DatabaseManager {
38
+ private readonly runtime;
37
39
  private metaDb;
38
40
  private readonly metaDbName;
39
- constructor();
41
+ constructor(runtime?: RuntimeContext);
40
42
  /**
41
43
  * Initialize the meta database (ablo_databases)
42
44
  */
@@ -5,7 +5,7 @@
5
5
  * then gets its own data database, named `ablo_<hash>`, that holds the synced
6
6
  * rows. {@link DatabaseManager} creates, versions, and deletes both tiers.
7
7
  */
8
- import { getContext } from '../context.js';
8
+ import { globalRuntime } from '../context.js';
9
9
  import { openIDBWithTimeout, deleteIDBWithTimeout, IDBOpenTimeoutError, } from './openIDBWithTimeout.js';
10
10
  import { AbloConnectionError } from '../transaction/errors.js';
11
11
  import { getActiveRegistry, hasActiveRegistry } from '../ModelRegistry.js';
@@ -19,10 +19,11 @@ import { getActiveRegistry, hasActiveRegistry } from '../ModelRegistry.js';
19
19
  * - Provides database info and metadata management
20
20
  */
21
21
  export class DatabaseManager {
22
+ runtime;
22
23
  metaDb = null;
23
24
  metaDbName = 'ablo_databases';
24
- constructor() {
25
- // Singleton-like behavior
25
+ constructor(runtime = globalRuntime) {
26
+ this.runtime = runtime;
26
27
  }
27
28
  /**
28
29
  * Initialize the meta database (ablo_databases)
@@ -53,8 +54,8 @@ export class DatabaseManager {
53
54
  // safe to delete and re-create. Try exactly once: delete, then re-open.
54
55
  if (!(error instanceof IDBOpenTimeoutError))
55
56
  throw error;
56
- getContext().logger.debug('[sync-engine] meta DB open timed out — attempting self-heal (delete + retry)', { db: this.metaDbName, reason: error.reason });
57
- getContext().observability.captureBootstrapFailure(error, {
57
+ this.runtime.logger.debug('[sync-engine] meta DB open timed out — attempting self-heal (delete + retry)', { db: this.metaDbName, reason: error.reason });
58
+ this.runtime.observability.captureBootstrapFailure(error, {
58
59
  type: 'meta-db-open-timeout',
59
60
  });
60
61
  const deleted = await deleteIDBWithTimeout(this.metaDbName);
@@ -66,7 +67,7 @@ export class DatabaseManager {
66
67
  }
67
68
  // Fresh store — this open creates `ablo_databases` from scratch.
68
69
  this.metaDb = await open();
69
- getContext().logger.info('[sync-engine] meta DB self-heal succeeded');
70
+ this.runtime.logger.info('[sync-engine] meta DB self-heal succeeded');
70
71
  }
71
72
  }
72
73
  /**
@@ -92,7 +93,7 @@ export class DatabaseManager {
92
93
  const allUserDatabases = await this.getDatabasesForUser(userId);
93
94
  const allIndexedDBs = (await indexedDB.databases?.()) || [];
94
95
  const abloDatabases = allIndexedDBs.filter((db) => db.name?.startsWith('ablo_'));
95
- getContext().observability.breadcrumb('Database info calculated', 'sync.database', 'info', {
96
+ this.runtime.observability.breadcrumb('Database info calculated', 'sync.database', 'info', {
96
97
  dbName,
97
98
  schemaVersion,
98
99
  existingDbCount: allUserDatabases.length,
@@ -192,13 +193,13 @@ export class DatabaseManager {
192
193
  if (createStoresFn && tx) {
193
194
  try {
194
195
  void createStoresFn(db, tx).catch((err) => {
195
- getContext().observability.captureBootstrapFailure(err, {
196
+ this.runtime.observability.captureBootstrapFailure(err, {
196
197
  type: 'store-creation',
197
198
  });
198
199
  });
199
200
  }
200
201
  catch (err) {
201
- getContext().observability.captureBootstrapFailure(err, {
202
+ this.runtime.observability.captureBootstrapFailure(err, {
202
203
  type: 'store-creation',
203
204
  });
204
205
  }
@@ -207,7 +208,7 @@ export class DatabaseManager {
207
208
  });
208
209
  }
209
210
  catch (error) {
210
- getContext().observability.captureBootstrapFailure(error, {
211
+ this.runtime.observability.captureBootstrapFailure(error, {
211
212
  type: 'database-open',
212
213
  });
213
214
  throw error;
@@ -329,11 +330,11 @@ export class DatabaseManager {
329
330
  resolve();
330
331
  };
331
332
  deleteRequest.onerror = () => {
332
- getContext().observability.breadcrumb(`Failed to delete workspace database: ${dbInfo.name}`, 'sync.database', 'error');
333
+ this.runtime.observability.breadcrumb(`Failed to delete workspace database: ${dbInfo.name}`, 'sync.database', 'error');
333
334
  reject(deleteRequest.error);
334
335
  };
335
336
  deleteRequest.onblocked = () => {
336
- getContext().observability.breadcrumb(`Database deletion blocked: ${dbInfo.name}`, 'sync.database', 'warning');
337
+ this.runtime.observability.breadcrumb(`Database deletion blocked: ${dbInfo.name}`, 'sync.database', 'warning');
337
338
  // Could implement retry logic or user notification
338
339
  };
339
340
  });
@@ -7,8 +7,8 @@
7
7
  * when its data is loaded.
8
8
  */
9
9
  import { ModelRegistry } from '../ModelRegistry.js';
10
- import { ObjectStore } from '../stores/ObjectStore.js';
11
- import { SyncActionStore } from '../stores/SyncActionStore.js';
10
+ import { ObjectStore } from './ObjectStore.js';
11
+ import { SyncActionStore } from './SyncActionStore.js';
12
12
  import { LoadStrategy } from '../transaction/types/index.js';
13
13
  /**
14
14
  * StoreManager - Central manager for all ObjectStore instances
@@ -20,6 +20,7 @@ import { LoadStrategy } from '../transaction/types/index.js';
20
20
  * - Handles store-specific optimizations based on load strategies
21
21
  */
22
22
  export declare class StoreManager {
23
+ private readonly runtime;
23
24
  private stores;
24
25
  /** Strict-durability wrapper for the client write journal and commit outbox. */
25
26
  private transactionStore;
@@ -27,7 +28,7 @@ export declare class StoreManager {
27
28
  private db;
28
29
  private isInitialized;
29
30
  private modelRegistry;
30
- constructor(modelRegistry: ModelRegistry);
31
+ constructor(modelRegistry: ModelRegistry, runtime?: RuntimeContext);
31
32
  /**
32
33
  * Initialize all stores for registered models
33
34
  */
@@ -110,3 +111,4 @@ export declare class StoreManager {
110
111
  }[];
111
112
  }>;
112
113
  }
114
+ import type { RuntimeContext } from '../RuntimeContext.js';