@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
@@ -11,8 +11,9 @@
11
11
  * and {@link flushPendingDeltas}.
12
12
  */
13
13
  import { runInAction } from 'mobx';
14
- import { getContext } from '../context.js';
14
+ import { globalRuntime } from '../context.js';
15
15
  import { ModelScope } from '../InstanceCache.js';
16
+ import { runStage, pluginsForStage, } from '../transaction/plugin.js';
16
17
  /**
17
18
  * Handles a sync-group ('G' or 'S') delta whose handler rejected after the
18
19
  * applied watermark had already advanced. That delta will never be redelivered,
@@ -22,7 +23,7 @@ import { ModelScope } from '../InstanceCache.js';
22
23
  * It never throws, because it runs inside the pipeline's fire-and-forget path.
23
24
  */
24
25
  export function handleGroupHandlerFailure(ctx, delta, error) {
25
- getContext().logger.error('Your access changed but cached data could not be cleared — resetting local data.', {
26
+ (ctx.runtime ?? globalRuntime).logger.error('Your access changed but cached data could not be cleared — resetting local data.', {
26
27
  syncId: delta.id,
27
28
  error: error instanceof Error ? error.message : String(error),
28
29
  });
@@ -166,6 +167,8 @@ export function enqueueDelta(ctx, delta, options = {}) {
166
167
  if (delta.actionType === 'D') {
167
168
  ctx.cascadeCancelTransactionsForDeletedParent(delta.modelName, delta.modelId);
168
169
  }
170
+ // The delta is accepted and queued — the `receive` stage boundary.
171
+ runStage(ctx.stagePlugins ?? [], 'receive', { delta });
169
172
  ctx.pendingDeltas.push(delta);
170
173
  return true;
171
174
  }
@@ -190,7 +193,9 @@ export function scheduleDeltaFlush(ctx) {
190
193
  export async function flushPendingDeltas(ctx) {
191
194
  if (ctx.pendingDeltas.length === 0)
192
195
  return;
196
+ const stagePlugins = ctx.stagePlugins ?? [];
193
197
  const deduplicatedDeltas = ctx.deduplicateDeltas(ctx.pendingDeltas);
198
+ runStage(stagePlugins, 'dedupe', { deltas: deduplicatedDeltas });
194
199
  // Custom entities → apply straight to the pool, skipping the local store.
195
200
  const customDeltas = deduplicatedDeltas.filter((d) => ctx.isCustomEntity(d.modelName));
196
201
  if (customDeltas.length > 0) {
@@ -237,8 +242,18 @@ export async function flushPendingDeltas(ctx) {
237
242
  transactionId: d.transactionId,
238
243
  })));
239
244
  const dbResults = batch.results;
240
- // Apply the batch results to the in-memory pool.
241
- ctx.applyDeltaBatchToPool(dbResults);
245
+ runStage(stagePlugins, 'persist', { deltas: regularDeltas });
246
+ // Apply the batch results to the in-memory graph. When a plugin has
247
+ // declared the `apply` stage, its handlers ARE the apply — the
248
+ // materialiser attached where it said it would. The direct call is the
249
+ // bridge for stores constructed without plugins (subclasses, tests),
250
+ // whose own apply is the whole pipeline.
251
+ if (pluginsForStage(stagePlugins, 'apply').length > 0) {
252
+ runStage(stagePlugins, 'apply', { changes: dbResults });
253
+ }
254
+ else {
255
+ ctx.applyDeltaBatchToPool(dbResults);
256
+ }
242
257
  // Acknowledge and advance the sync cursor, gated on persistence.
243
258
  //
244
259
  // We must acknowledge `persistedSyncId` — the high-water mark of deltas whose
@@ -251,8 +266,10 @@ export async function flushPendingDeltas(ctx) {
251
266
  if (persistedSyncId > ctx.lastAckedId) {
252
267
  ctx.acknowledge(persistedSyncId);
253
268
  ctx.advancePersisted(persistedSyncId);
269
+ runStage(stagePlugins, 'acknowledge', { syncId: persistedSyncId });
254
270
  }
255
271
  // Cache invalidation happens automatically via the 'models:changed' event.
272
+ runStage(stagePlugins, 'notify', { changes: dbResults });
256
273
  ctx.pendingDeltas = [];
257
274
  if (ctx.batchTimer) {
258
275
  clearTimeout(ctx.batchTimer);
@@ -9,6 +9,7 @@
9
9
  * Every handler takes a {@link GroupChangeContext}, the narrow facade through
10
10
  * which it reaches the client's local storage and connection lifecycle hooks.
11
11
  */
12
+ import type { RuntimeContext } from '../RuntimeContext.js';
12
13
  import type { SyncDelta, SyncGroupChangePayload, GroupAddedPayload } from './SyncWebSocket.js';
13
14
  /**
14
15
  * The collaborators the group-change handlers depend on. It gathers the
@@ -55,6 +56,8 @@ export interface GroupChangeContext {
55
56
  handleGroupAdded(payload: GroupAddedPayload, syncId: number): Promise<void>;
56
57
  computeUpdatedSyncGroups(payload: SyncGroupChangePayload): string[];
57
58
  forceFullRebootstrap(): void;
59
+ /** The owning client's runtime. Defaults to the module-global bridge. */
60
+ readonly runtime?: RuntimeContext;
58
61
  }
59
62
  /**
60
63
  * Handles a 'G' (group-change) delta. The server sends two shapes of this
@@ -9,7 +9,9 @@
9
9
  * Every handler takes a {@link GroupChangeContext}, the narrow facade through
10
10
  * which it reaches the client's local storage and connection lifecycle hooks.
11
11
  */
12
- import { getContext } from '../context.js';
12
+ import { globalRuntime } from '../context.js';
13
+ /** The context's runtime, falling back to the module-global bridge. */
14
+ const runtimeOf = (ctx) => ctx.runtime ?? globalRuntime;
13
15
  /**
14
16
  * Marker returned when a group-change payload cannot be parsed, kept distinct
15
17
  * from a valid null or absent payload, which the handlers accept normally.
@@ -22,14 +24,14 @@ const MALFORMED_PAYLOAD = Symbol('malformed-group-change-payload');
22
24
  * would leave the delta pipeline after the watermark has already advanced. The
23
25
  * delta is never re-delivered, so the security clear it carried would be lost.
24
26
  */
25
- function parseGroupChangePayload(delta) {
27
+ function parseGroupChangePayload(delta, runtime) {
26
28
  if (typeof delta.data !== 'string')
27
29
  return delta.data;
28
30
  try {
29
31
  return JSON.parse(delta.data);
30
32
  }
31
33
  catch (error) {
32
- getContext().logger.debug('[BaseSyncedStore] Malformed group-change payload', {
34
+ runtime.logger.debug('[BaseSyncedStore] Malformed group-change payload', {
33
35
  syncId: delta.id,
34
36
  actionType: delta.actionType,
35
37
  error: error instanceof Error ? error.message : String(error),
@@ -44,7 +46,7 @@ function parseGroupChangePayload(delta) {
44
46
  * re-bootstrap from the server.
45
47
  */
46
48
  async function clearForUnknownGroupChange(ctx, delta, kind) {
47
- getContext().logger.debug(`[BaseSyncedStore] Unreadable ${kind} payload — clearing cached data and re-bootstrapping`, { syncId: delta.id });
49
+ runtimeOf(ctx).logger.debug(`[BaseSyncedStore] Unreadable ${kind} payload — clearing cached data and re-bootstrapping`, { syncId: delta.id });
48
50
  // Revoked data must not persist if the device goes offline before the
49
51
  // re-bootstrap, the same reasoning as the explicit removed-groups path.
50
52
  await ctx.database.clear();
@@ -64,7 +66,7 @@ async function clearForUnknownGroupChange(ctx, delta, kind) {
64
66
  * and refetch), clearing cached data first if any group was removed.
65
67
  */
66
68
  export async function handleSyncGroupChange(ctx, delta) {
67
- const raw = parseGroupChangePayload(delta);
69
+ const raw = parseGroupChangePayload(delta, runtimeOf(ctx));
68
70
  if (raw === MALFORMED_PAYLOAD) {
69
71
  // The payload is unreadable, so we cannot tell which groups changed, and
70
72
  // this delta will never be re-delivered because the watermark has already
@@ -88,7 +90,7 @@ export async function handleSyncGroupChange(ctx, delta) {
88
90
  removedGroups: rawObj.removedGroups ?? [],
89
91
  addedGroups: rawObj.addedGroups ?? [],
90
92
  };
91
- getContext().logger.info('[BaseSyncedStore] Sync group change received (legacy)', {
93
+ runtimeOf(ctx).logger.info('[BaseSyncedStore] Sync group change received (legacy)', {
92
94
  removedGroups: payload.removedGroups,
93
95
  addedGroups: payload.addedGroups,
94
96
  syncId: delta.id,
@@ -99,7 +101,7 @@ export async function handleSyncGroupChange(ctx, delta) {
99
101
  if (payload.removedGroups.length > 0) {
100
102
  await ctx.database.clear();
101
103
  ctx.objectPool.clear();
102
- getContext().logger.info('[BaseSyncedStore] Cleared cached data due to revoked sync groups', {
104
+ runtimeOf(ctx).logger.info('[BaseSyncedStore] Cleared cached data due to revoked sync groups', {
103
105
  removedGroups: payload.removedGroups,
104
106
  });
105
107
  }
@@ -114,7 +116,7 @@ export async function handleSyncGroupChange(ctx, delta) {
114
116
  * through the normal insert path.
115
117
  */
116
118
  export async function handleGroupAdded(ctx, payload, syncId) {
117
- getContext().logger.info('[BaseSyncedStore] Group added (incremental)', {
119
+ runtimeOf(ctx).logger.info('[BaseSyncedStore] Group added (incremental)', {
118
120
  group: payload.group,
119
121
  syncId,
120
122
  });
@@ -130,7 +132,7 @@ export async function handleGroupAdded(ctx, payload, syncId) {
130
132
  * local state and forces a re-bootstrap with the updated group list.
131
133
  */
132
134
  export async function handleGroupRemoved(ctx, delta) {
133
- const raw = parseGroupChangePayload(delta);
135
+ const raw = parseGroupChangePayload(delta, runtimeOf(ctx));
134
136
  if (raw === MALFORMED_PAYLOAD) {
135
137
  // The payload is unreadable: access was revoked but we cannot tell which
136
138
  // group. Fall back to a full clear, the safe direction for an
@@ -141,12 +143,12 @@ export async function handleGroupRemoved(ctx, delta) {
141
143
  const rawObj = (raw ?? {});
142
144
  const groupKey = typeof rawObj.group === 'string' ? rawObj.group : undefined;
143
145
  if (!groupKey) {
144
- getContext().logger.debug('[BaseSyncedStore] Group removed delta missing group key', {
146
+ runtimeOf(ctx).logger.debug('[BaseSyncedStore] Group removed delta missing group key', {
145
147
  syncId: delta.id,
146
148
  });
147
149
  return;
148
150
  }
149
- getContext().logger.info('[BaseSyncedStore] Group removed', {
151
+ runtimeOf(ctx).logger.info('[BaseSyncedStore] Group removed', {
150
152
  group: groupKey,
151
153
  syncId: delta.id,
152
154
  });
@@ -181,7 +183,7 @@ export function computeUpdatedSyncGroups(ctx, payload) {
181
183
  */
182
184
  export function forceFullRebootstrap(ctx) {
183
185
  if (ctx.getBootstrapMode() === 'none') {
184
- getContext().logger.info('[BaseSyncedStore] forceFullRebootstrap skipped (bootstrapMode=none)');
186
+ runtimeOf(ctx).logger.info('[BaseSyncedStore] forceFullRebootstrap skipped (bootstrapMode=none)');
185
187
  return;
186
188
  }
187
189
  ctx.database.markRequiresFullBootstrap();
@@ -219,7 +221,7 @@ export async function checkSyncGroupShrinkage(ctx) {
219
221
  const currentGroups = new Set(currentSyncGroups);
220
222
  const removedGroups = stored.filter((g) => !currentGroups.has(g));
221
223
  if (removedGroups.length > 0) {
222
- getContext().logger.info('[BaseSyncedStore] Sync groups shrank — forcing full bootstrap', {
224
+ runtimeOf(ctx).logger.info('[BaseSyncedStore] Sync groups shrank — forcing full bootstrap', {
223
225
  removedGroups,
224
226
  storedCount: stored.length,
225
227
  currentCount: currentGroups.size,
@@ -235,7 +237,7 @@ export async function checkSyncGroupShrinkage(ctx) {
235
237
  });
236
238
  }
237
239
  catch (error) {
238
- getContext().logger.debug('[BaseSyncedStore] Failed to check sync group shrinkage', {
240
+ runtimeOf(ctx).logger.debug('[BaseSyncedStore] Failed to check sync group shrinkage', {
239
241
  error: error instanceof Error ? error.message : String(error),
240
242
  });
241
243
  }
@@ -1,3 +1,4 @@
1
+ import type { JoinOptions } from '../transaction/resources/modelOperations.js';
1
2
  import type { SyncWebSocket } from './SyncWebSocket.js';
2
3
  import type { Schema } from '../transaction/schema/schema.js';
3
4
  import type { Claim, Activity, ClaimTarget, ClaimStream, Peer, PresenceStream, PresenceTarget } from '../transaction/types/streams.js';
@@ -17,7 +18,13 @@ export interface EngineParticipant {
17
18
  readonly presence: PresenceStream;
18
19
  readonly claims: ClaimStream;
19
20
  }
20
- export interface ParticipantJoinOptions {
21
+ /**
22
+ * The options for a participant join. It extends the public per-model
23
+ * {@link JoinOptions} rather than restating its members, so the lease dial is
24
+ * declared once, in the core, and this surface adds only what a lower-level
25
+ * join can additionally say.
26
+ */
27
+ export interface ParticipantJoinOptions extends JoinOptions {
21
28
  /**
22
29
  * The initial focus target, named in your schema's vocabulary and optionally
23
30
  * narrowed to a path, field, or range. When `scope` is omitted, this target
@@ -34,7 +41,17 @@ export interface ParticipantJoinOptions {
34
41
  readonly scope?: ParticipantScope;
35
42
  /** Present a narrower capability for this logical participant. */
36
43
  readonly capabilityToken?: string;
37
- /** How long the claim lives, in seconds or a compact duration string (`30s`, `5m`). */
44
+ /**
45
+ * @deprecated Use `ttl`. Removed in 0.37.0.
46
+ *
47
+ * One lease, spelled two ways, and the seconds spelling was the one that
48
+ * misled: it accepted a duration string, so `ablo.<model>.join(ids, { ttl:
49
+ * '5m' })` reached this surface as `ttlSeconds: '5m'` — a field whose name
50
+ * asserts a unit its value did not carry. `ttl` takes the same values and is
51
+ * the spelling every other lease in the SDK already uses (`claim`'s `ttl`,
52
+ * `ClaimLeaseOptions.ttl`). The wire is unchanged: it has always carried
53
+ * seconds, and still does.
54
+ */
38
55
  readonly ttlSeconds?: number | string | null;
39
56
  /**
40
57
  * The activity to announce as soon as the claim is acknowledged. Defaults to
@@ -17,7 +17,9 @@ export function createParticipantManager(config) {
17
17
  if (syncGroups.length > 0) {
18
18
  await transport.sendClaim(claimId, syncGroups, {
19
19
  capabilityToken: options.capabilityToken,
20
- ttlSeconds: parseParticipantTtlSeconds(options.ttlSeconds),
20
+ // `ttl` is the spelling; `ttlSeconds` is the deprecated one, read
21
+ // second so a caller passing both gets the current name honored.
22
+ ttlSeconds: parseParticipantTtlSeconds(options.ttl ?? options.ttlSeconds),
21
23
  });
22
24
  }
23
25
  const participant = createJoinedParticipant({
@@ -6,6 +6,7 @@
6
6
  * that predates it.
7
7
  */
8
8
  import { z } from 'zod';
9
+ import type { RuntimeContext } from "../RuntimeContext.js";
9
10
  /**
10
11
  * A delta as it arrives in a bootstrap payload.
11
12
  *
@@ -67,4 +68,4 @@ export type ValidatedBootstrapResponse = z.infer<typeof BootstrapResponseSchema>
67
68
  * result. On failure it records a diagnostic breadcrumb and throws an
68
69
  * {@link AbloValidationError} describing which fields were invalid.
69
70
  */
70
- export declare function parseBootstrapResponse(raw: unknown): ValidatedBootstrapResponse;
71
+ export declare function parseBootstrapResponse(raw: unknown, runtime?: RuntimeContext): ValidatedBootstrapResponse;
@@ -6,7 +6,7 @@
6
6
  * that predates it.
7
7
  */
8
8
  import { z } from 'zod';
9
- import { getContext } from "../context.js";
9
+ import { globalRuntime } from "../context.js";
10
10
  import { AbloValidationError } from "../transaction/errors.js";
11
11
  import { syncDeltaWireCoreSchema } from '../transaction/wire/delta.js';
12
12
  // ─── Server Delta Schema ─────────────────────────────────────────────────────
@@ -77,11 +77,11 @@ export const BootstrapResponseSchema = z
77
77
  * result. On failure it records a diagnostic breadcrumb and throws an
78
78
  * {@link AbloValidationError} describing which fields were invalid.
79
79
  */
80
- export function parseBootstrapResponse(raw) {
80
+ export function parseBootstrapResponse(raw, runtime = globalRuntime) {
81
81
  const result = BootstrapResponseSchema.safeParse(raw);
82
82
  if (!result.success) {
83
83
  const issues = result.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join('; ');
84
- getContext().observability.breadcrumb('Bootstrap response validation failed', 'sync.bootstrap', 'error', {
84
+ runtime.observability.breadcrumb('Bootstrap response validation failed', 'sync.bootstrap', 'error', {
85
85
  issues,
86
86
  rawType: typeof raw,
87
87
  rawKeys: raw && typeof raw === 'object' ? Object.keys(raw).join(',') : 'n/a',
@@ -0,0 +1,20 @@
1
+ /** Canonical sync-log identity and position contracts shared by client/server. */
2
+ import { z } from 'zod';
3
+ export declare const deltaPositionSchema: z.ZodNumber;
4
+ export type DeltaPosition = z.infer<typeof deltaPositionSchema>;
5
+ export declare const clientMutationIdSchema: z.ZodString;
6
+ export type ClientMutationId = z.infer<typeof clientMutationIdSchema>;
7
+ export declare const sourceCorrelationIdSchema: z.ZodString;
8
+ export type SourceCorrelationId = z.infer<typeof sourceCorrelationIdSchema>;
9
+ export declare const sourceChangeIdSchema: z.ZodString;
10
+ export type SourceChangeId = z.infer<typeof sourceChangeIdSchema>;
11
+ export declare const replicationLsnSchema: z.ZodString;
12
+ export type ReplicationLSN = z.infer<typeof replicationLsnSchema>;
13
+ export declare const commitDispatchMarkerSchema: z.ZodObject<{
14
+ kind: z.ZodLiteral<"sync_deltas">;
15
+ organizationId: z.ZodString;
16
+ environment: z.ZodString;
17
+ firstSyncId: z.ZodNumber;
18
+ lastSyncId: z.ZodNumber;
19
+ }, z.core.$strict>;
20
+ export type CommitDispatchMarker = z.infer<typeof commitDispatchMarkerSchema>;
@@ -0,0 +1,19 @@
1
+ /** Canonical sync-log identity and position contracts shared by client/server. */
2
+ import { z } from 'zod';
3
+ import { environmentSchema } from '../environment.js';
4
+ export const deltaPositionSchema = z.number().int().nonnegative().safe();
5
+ export const clientMutationIdSchema = z.string().min(1).max(255);
6
+ export const sourceCorrelationIdSchema = z.string().min(1).max(255);
7
+ export const sourceChangeIdSchema = z.string().min(1).max(512);
8
+ export const replicationLsnSchema = z.string().regex(/^[0-9A-Fa-f]+\/[0-9A-Fa-f]+$/);
9
+ export const commitDispatchMarkerSchema = z.strictObject({
10
+ kind: z.literal('sync_deltas'),
11
+ organizationId: z.string().min(1),
12
+ environment: environmentSchema,
13
+ firstSyncId: deltaPositionSchema,
14
+ lastSyncId: deltaPositionSchema,
15
+ }).superRefine((value, ctx) => {
16
+ if (value.firstSyncId <= 0 || value.lastSyncId < value.firstSyncId) {
17
+ ctx.addIssue({ code: 'custom', message: 'invalid delta position range' });
18
+ }
19
+ });
@@ -0,0 +1 @@
1
+ export * from './contract.js';
@@ -0,0 +1 @@
1
+ export * from './contract.js';
@@ -148,6 +148,41 @@ export declare const capabilityScopeSchema: z.ZodObject<{
148
148
  participantId: z.ZodString;
149
149
  }, z.core.$strip>;
150
150
  export type CapabilityScope = z.infer<typeof capabilityScopeSchema>;
151
+ /**
152
+ * What `POST /v1/capabilities` answers with — **201**, the credential is minted.
153
+ *
154
+ * This is the first call any non-TypeScript client makes, and until it had a
155
+ * schema the published contract described it as `{ type: 'object' }`: a caller
156
+ * working from the reference could see that a capability could be minted and
157
+ * not where the token was in the reply.
158
+ *
159
+ * `scope` echoes what was MINTED, not what was asked. A `wideScope` mint stores
160
+ * the org-wide default and read-your-writes widens the verb axis, so a client
161
+ * that assumed its request came back verbatim would report a scope narrower
162
+ * than the one being enforced.
163
+ *
164
+ * `userMeta` is the caller's own blob, echoed. Ablo has no view into their user
165
+ * directory — the API key is what is trusted — so this is deliberately open.
166
+ */
167
+ export declare const capabilityMintResponseSchema: z.ZodObject<{
168
+ capabilityId: z.ZodString;
169
+ token: z.ZodString;
170
+ expiresAt: z.ZodString;
171
+ organizationId: z.ZodString;
172
+ scope: z.ZodObject<{
173
+ organizationId: z.ZodString;
174
+ syncGroups: z.ZodArray<z.ZodString>;
175
+ operations: z.ZodArray<z.ZodTemplateLiteral<`${string}.update` | `${string}.create` | `${string}.delete` | `${string}.read`>>;
176
+ participantKind: z.ZodEnum<{
177
+ user: "user";
178
+ agent: "agent";
179
+ system: "system";
180
+ }>;
181
+ participantId: z.ZodString;
182
+ }, z.core.$strip>;
183
+ userMeta: z.ZodRecord<z.ZodString, z.ZodUnknown>;
184
+ }, z.core.$strip>;
185
+ export type CapabilityMintResponse = z.infer<typeof capabilityMintResponseSchema>;
151
186
  /**
152
187
  * `POST /v1/capabilities` — mint a capability for a participant.
153
188
  *
@@ -157,6 +157,31 @@ export const capabilityScopeSchema = z.object({
157
157
  participantKind: participantKindSchema,
158
158
  participantId: z.string().min(1),
159
159
  });
160
+ /**
161
+ * What `POST /v1/capabilities` answers with — **201**, the credential is minted.
162
+ *
163
+ * This is the first call any non-TypeScript client makes, and until it had a
164
+ * schema the published contract described it as `{ type: 'object' }`: a caller
165
+ * working from the reference could see that a capability could be minted and
166
+ * not where the token was in the reply.
167
+ *
168
+ * `scope` echoes what was MINTED, not what was asked. A `wideScope` mint stores
169
+ * the org-wide default and read-your-writes widens the verb axis, so a client
170
+ * that assumed its request came back verbatim would report a scope narrower
171
+ * than the one being enforced.
172
+ *
173
+ * `userMeta` is the caller's own blob, echoed. Ablo has no view into their user
174
+ * directory — the API key is what is trusted — so this is deliberately open.
175
+ */
176
+ export const capabilityMintResponseSchema = z.object({
177
+ capabilityId: z.string().min(1),
178
+ token: z.string().min(1),
179
+ /** ISO 8601. */
180
+ expiresAt: z.string().min(1),
181
+ organizationId: z.string().min(1),
182
+ scope: capabilityScopeSchema,
183
+ userMeta: z.record(z.string(), z.unknown()),
184
+ });
160
185
  /**
161
186
  * `POST /v1/capabilities` — mint a capability for a participant.
162
187
  *
@@ -44,6 +44,13 @@ export declare function awaitClaimGrant(transport: GrantTransport, claimId: stri
44
44
  * already ahead of us). Omit to wait however deep the queue is.
45
45
  */
46
46
  maxQueueDepth?: number;
47
+ /**
48
+ * Abort the wait from outside — the same signal that cancels everything
49
+ * else in the program. Rejects with `claim_wait_aborted`; once the grant
50
+ * has arrived the signal is ignored, so a held lease is never torn down
51
+ * by a late abort.
52
+ */
53
+ signal?: AbortSignal;
47
54
  /** Where grant transitions are logged. Defaults to silent. */
48
55
  logger?: Logger;
49
56
  }): Promise<ClaimGrantInfo>;
@@ -101,6 +101,18 @@ export function awaitClaimGrant(transport, claimId, options) {
101
101
  });
102
102
  }
103
103
  }));
104
+ if (options?.signal) {
105
+ const signal = options.signal;
106
+ const abort = () => settle(() => {
107
+ reject(new AbloClaimedError(`The wait for claim ${claimId} was aborted before the grant arrived.`, { code: 'claim_wait_aborted' }));
108
+ });
109
+ if (signal.aborted) {
110
+ abort();
111
+ return;
112
+ }
113
+ signal.addEventListener('abort', abort, { once: true });
114
+ unsubs.push(() => { signal.removeEventListener('abort', abort); });
115
+ }
104
116
  if (options?.timeoutMs && options.timeoutMs > 0) {
105
117
  timer = setTimeout(() => {
106
118
  settle(() => {
@@ -23,6 +23,40 @@ import type { ClaimHeartbeat, ClaimHeartbeatOptions, Duration } from '../types/s
23
23
  * shorthand cannot drift.
24
24
  */
25
25
  export declare function resolveHeartbeatOptions(input: Duration | ClaimHeartbeatOptions | undefined): ClaimHeartbeatOptions;
26
+ /**
27
+ * The structured spelling of the auto-heartbeat — cadence and both callbacks
28
+ * in one place, so the lease axis of a claim is two members (`ttl`,
29
+ * `heartbeat`) rather than four. `true` and a bare Duration remain the
30
+ * shorthands: `heartbeat: '2m'` ≡ `heartbeat: { every: '2m' }`.
31
+ */
32
+ export interface ClaimHeartbeatPlan {
33
+ /** Cadence between beats. Omitted: a third of the TTL, so two beats can
34
+ * fail before the lease is at risk. */
35
+ readonly every?: Duration;
36
+ /** Called after every successful beat (auto or manual) with the server's
37
+ * answer — chiefly `queueDepth`, the cooperative-yield pressure signal. */
38
+ readonly onBeat?: (beat: ClaimHeartbeat) => void;
39
+ /** Called once when a beat learns the lease is no longer yours. The loop
40
+ * has already stopped; abandon the work or re-claim. */
41
+ readonly onLost?: (error: AbloClaimedError) => void;
42
+ }
43
+ /** What the handle assembly reads, whichever spelling the caller used. */
44
+ export interface ResolvedHeartbeatPlan {
45
+ /** Whether the auto-beat loop runs at all. */
46
+ readonly loop: boolean;
47
+ /** Feed for {@link heartbeatCadenceMs} — `true` means the TTL-derived default. */
48
+ readonly cadence: true | Duration;
49
+ readonly onBeat?: (beat: ClaimHeartbeat) => void;
50
+ readonly onLost?: (error: AbloClaimedError) => void;
51
+ }
52
+ /**
53
+ * One reading of the heartbeat option, shared by both transports' handle
54
+ * assembly — cadence and callbacks from whichever spelling the caller used
55
+ * (`true`, a bare Duration, or the structured plan).
56
+ */
57
+ export declare function resolveHeartbeatPlan(options: {
58
+ readonly heartbeat?: true | Duration | ClaimHeartbeatPlan;
59
+ }): ResolvedHeartbeatPlan;
26
60
  /**
27
61
  * The beat cadence for a lease of `ttlMs`: an explicit duration when the
28
62
  * caller set one, otherwise a third of the TTL (floored at 1s) — the
@@ -30,6 +30,26 @@ export function resolveHeartbeatOptions(input) {
30
30
  }
31
31
  return input;
32
32
  }
33
+ /**
34
+ * One reading of the heartbeat option, shared by both transports' handle
35
+ * assembly — cadence and callbacks from whichever spelling the caller used
36
+ * (`true`, a bare Duration, or the structured plan).
37
+ */
38
+ export function resolveHeartbeatPlan(options) {
39
+ const { heartbeat } = options;
40
+ if (heartbeat !== undefined && typeof heartbeat === 'object') {
41
+ return {
42
+ loop: true,
43
+ cadence: heartbeat.every ?? true,
44
+ ...(heartbeat.onBeat ? { onBeat: heartbeat.onBeat } : {}),
45
+ ...(heartbeat.onLost ? { onLost: heartbeat.onLost } : {}),
46
+ };
47
+ }
48
+ return {
49
+ loop: Boolean(heartbeat),
50
+ cadence: heartbeat === undefined || heartbeat === true ? true : heartbeat,
51
+ };
52
+ }
33
53
  /**
34
54
  * The beat cadence for a lease of `ttlMs`: an explicit duration when the
35
55
  * caller set one, otherwise a third of the TTL (floored at 1s) — the
@@ -9,11 +9,11 @@
9
9
  * Exports are listed by name rather than re-exported wholesale, so every symbol
10
10
  * that becomes part of this package's public API is a deliberate choice.
11
11
  */
12
- export { targetRangeSchema, participantKindSchema, wireParticipantKindSchema, participantKindFromWire, descriptionFromMeta, claimDescription, DEFAULT_CLAIM_DESCRIPTION, targetRefSchema, onStaleModeSchema, writeGuardSchema, staleNotificationSchema, readDependencySchema, trackDependencySchema, claimStatusSchema, wireClaimStatusSchema, wireClaimSummarySchema, claimErrorSchema, wireClaimSchema, claimRejectionSchema, claimLostSchema, claimAcquiredSchema, claimGrantedSchema, claimQueuedSchema, claimQueueSchema, claimQueueEntrySchema, claimExpiredSchema, claimEventReasonSchema, modelTargetSchema, claimRecordSchema, heldClaimStatusSchema, modelClaimSchema, claimBeginPayloadSchema, claimAbandonPayloadSchema, claimReorderPayloadSchema, claimHeartbeatPayloadSchema, claimHeartbeatAckPayloadSchema, claimHeartbeatBatchPayloadSchema, claimHeartbeatBatchAckPayloadSchema, updateSubscriptionPayloadSchema, subscriptionAckPayloadSchema, commitOperationTypeSchema, commitOperationSchema, presenceKindSchema, presenceActivitySchema, presenceUpdateSchema, presenceUpdatePayloadSchema, presenceUpdateFrameSchema, } from './schema.js';
12
+ export { targetRangeSchema, participantKindSchema, wireParticipantKindSchema, participantKindFromWire, descriptionFromMeta, claimDescription, DEFAULT_CLAIM_DESCRIPTION, targetRefSchema, onStaleModeSchema, writeGuardSchema, staleNotificationSchema, readDependencySchema, trackDependencySchema, claimStatusSchema, wireClaimStatusSchema, wireClaimSummarySchema, claimErrorSchema, wireClaimSchema, claimRejectionSchema, claimLostSchema, claimAcquiredSchema, claimGrantedSchema, claimQueuedSchema, claimQueueSchema, claimQueueEntrySchema, claimExpiredSchema, claimEventReasonSchema, modelTargetSchema, claimRecordSchema, heldClaimStatusSchema, modelClaimSchema, claimBeginPayloadSchema, claimAbandonPayloadSchema, claimReorderPayloadSchema, claimHeartbeatPayloadSchema, claimHeartbeatAckPayloadSchema, claimHeartbeatBatchPayloadSchema, claimHeartbeatBatchAckPayloadSchema, MAX_FRAME_SYNC_GROUPS, participantClaimPayloadSchema, participantReleasePayloadSchema, updateSubscriptionPayloadSchema, subscriptionAckPayloadSchema, commitOperationTypeSchema, commitOperationSchema, presenceKindSchema, presenceActivitySchema, presenceUpdateSchema, presenceUpdatePayloadSchema, presenceUpdateFrameSchema, } from './schema.js';
13
13
  export { defaultPolicy, capabilityPreemptPolicy, interpretConflictAxis } from '../policy/types.js';
14
14
  export type { Conflict, ConflictAxis, ConflictDecision, ConflictKind, ConflictOperation, ConflictPolicy, StaleContextConflict, ClaimHeldConflict, } from '../policy/types.js';
15
- export type { TargetRange, ParticipantKind, TargetRef, OnStaleMode, WriteGuard, StaleNotification, ReadDependency, TrackDependency, ClaimStatus, WireClaimStatus, WireClaimSummary, ClaimError, WireClaim, ClaimRejection, ClaimLost, ClaimAcquired, ClaimGranted, ClaimQueued, ClaimQueue, ClaimQueueEntry, ClaimExpired, ClaimEventReason, ModelTarget, ClaimRecord, HeldClaimStatus, ModelClaim, ClaimBeginPayload, ClaimAbandonPayload, ClaimReorderPayload, ClaimHeartbeatPayload, ClaimHeartbeatAckPayload, ClaimHeartbeatBatchPayload, ClaimHeartbeatBatchAckPayload, UpdateSubscriptionPayload, SubscriptionAckPayload, CommitOperationType, CommitOperation, AnyCommitOperation, PresenceKind, PresenceActivity, PresenceUpdate, PresenceUpdatePayload, PresenceUpdateFrame, } from './schema.js';
15
+ export type { TargetRange, ParticipantKind, TargetRef, OnStaleMode, WriteGuard, StaleNotification, ReadDependency, TrackDependency, ClaimStatus, WireClaimStatus, WireClaimSummary, ClaimError, WireClaim, ClaimRejection, ClaimLost, ClaimAcquired, ClaimGranted, ClaimQueued, ClaimQueue, ClaimQueueEntry, ClaimExpired, ClaimEventReason, ModelTarget, ClaimRecord, HeldClaimStatus, ModelClaim, ClaimBeginPayload, ClaimAbandonPayload, ClaimReorderPayload, ClaimHeartbeatPayload, ClaimHeartbeatAckPayload, ClaimHeartbeatBatchPayload, ClaimHeartbeatBatchAckPayload, ParticipantClaimPayload, ParticipantReleasePayload, UpdateSubscriptionPayload, SubscriptionAckPayload, CommitOperationType, CommitOperation, AnyCommitOperation, PresenceKind, PresenceActivity, PresenceUpdate, PresenceUpdatePayload, PresenceUpdateFrame, } from './schema.js';
16
16
  export type { ClaimEvent, ClaimCounterparty, ConflictEvent, CoordinationObserver, } from './events.js';
17
17
  export { targetsConflict } from './targetConflict.js';
18
- export type { ClaimTargetDetails, ClaimTargetSource, EntityLocator, } from './locator.js';
19
- export { isTargetTuple, subTarget, wireTarget, modelTarget, streamTarget, } from './locator.js';
18
+ export type { BatchFence, ClaimTargetDetails, ClaimTargetSource, EntityLocator, } from './locator.js';
19
+ export { batchFence, fenceTokenFor, isTargetTuple, subTarget, wireTarget, modelTarget, streamTarget, } from './locator.js';
@@ -22,8 +22,9 @@ claimStatusSchema, wireClaimStatusSchema, wireClaimSummarySchema, claimErrorSche
22
22
  claimLostSchema, claimAcquiredSchema, claimGrantedSchema, claimQueuedSchema, claimQueueSchema, claimQueueEntrySchema, claimExpiredSchema, claimEventReasonSchema, modelTargetSchema,
23
23
  // The one claim record, and the peer-visible projection of it.
24
24
  claimRecordSchema, heldClaimStatusSchema, modelClaimSchema, claimBeginPayloadSchema, claimAbandonPayloadSchema, claimReorderPayloadSchema, claimHeartbeatPayloadSchema, claimHeartbeatAckPayloadSchema, claimHeartbeatBatchPayloadSchema, claimHeartbeatBatchAckPayloadSchema,
25
- // Read interest — area-of-interest navigation
26
- updateSubscriptionPayloadSchema, subscriptionAckPayloadSchema,
25
+ // Read interest — what a connection receives, leased (`claim`/`release`,
26
+ // the frames behind `join`) and unleased (`update_subscription`).
27
+ MAX_FRAME_SYNC_GROUPS, participantClaimPayloadSchema, participantReleasePayloadSchema, updateSubscriptionPayloadSchema, subscriptionAckPayloadSchema,
27
28
  // Commit operation — carries the optimistic write-guard
28
29
  commitOperationTypeSchema, commitOperationSchema,
29
30
  // Layer 1 — presence
@@ -41,4 +42,4 @@ export { defaultPolicy, capabilityPreemptPolicy, interpretConflictAxis } from '.
41
42
  // protocol's decision, not the server's deployment of it, so it lives here and
42
43
  // every claim authority imports it.
43
44
  export { targetsConflict } from './targetConflict.js';
44
- export { isTargetTuple, subTarget, wireTarget, modelTarget, streamTarget, } from './locator.js';
45
+ export { batchFence, fenceTokenFor, isTargetTuple, subTarget, wireTarget, modelTarget, streamTarget, } from './locator.js';
@@ -15,7 +15,7 @@
15
15
  * no path carried one, so the fix looked complete and changed nothing. Widening
16
16
  * the locator again should touch this module and the schema, not every hop.
17
17
  */
18
- import type { ModelTarget, TargetRef, WireClaim } from './schema.js';
18
+ import type { ClaimPart, ModelTarget, TargetRef, WireClaim } from './schema.js';
19
19
  import type { ClaimTarget, PresenceTarget } from '../types/streams.js';
20
20
  import type { ResolveClaimMeta } from '../types/global.js';
21
21
  /**
@@ -38,7 +38,10 @@ export type ClaimTargetDetails = Pick<WireClaim, 'path' | 'range' | 'field' | 'f
38
38
  * handle, the model surface, the HTTP claim params — which is the same
39
39
  * member-by-member copying this module exists to end.
40
40
  */
41
- export type ClaimTargetSource = Omit<ClaimTargetDetails, 'meta'> & {
41
+ export type ClaimTargetSource = Omit<ClaimTargetDetails, 'meta' | 'field' | 'fields'> & {
42
+ /** A schema field name, or an app-defined part named with `part()`. */
43
+ readonly field?: string | ClaimPart;
44
+ readonly fields?: readonly (string | ClaimPart)[];
42
45
  readonly meta?: ClaimTargetDetails['meta'] | ResolveClaimMeta;
43
46
  };
44
47
  /**
@@ -81,3 +84,21 @@ export declare function wireTarget(source: EntityLocator): Pick<TargetRef, 'enti
81
84
  export declare function modelTarget(source: EntityLocator): Pick<ModelTarget, 'model' | 'id'>;
82
85
  /** The entity half as the claim handle and the wait line carry it. */
83
86
  export declare function streamTarget(source: EntityLocator): Pick<ClaimTarget, 'type' | 'id'>;
87
+ /**
88
+ * A batch claim's grant: its fencing token, and the one row it was granted over.
89
+ *
90
+ * A claim's `readAt` generalises across a batch; its token does not. The token
91
+ * is evidence of one row's place in the grant order, so it belongs only on the
92
+ * operation writing the row the claim covers. Presented anywhere else the server
93
+ * refuses it, because a token that travels is a fence any writer can raise.
94
+ */
95
+ export type BatchFence = Pick<ModelTarget, 'model' | 'id'> & {
96
+ readonly token: number;
97
+ };
98
+ /** The batch's grant, or nothing when there is no claim, or none with a token. */
99
+ export declare function batchFence(source: EntityLocator | null | undefined, token: number | null | undefined): BatchFence | null;
100
+ /**
101
+ * The token an operation carries: its own if it names one, the batch's when it
102
+ * writes the claimed row, otherwise none.
103
+ */
104
+ export declare function fenceTokenFor(fence: BatchFence | null, model: string, id: string | null): number | null;