@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.
- package/CHANGELOG.md +713 -629
- package/README.md +56 -519
- package/bin/ablo.cjs +39 -0
- package/dist/BaseSyncedStore.d.ts +24 -4
- package/dist/BaseSyncedStore.js +53 -37
- package/dist/Database.d.ts +8 -20
- package/dist/Database.js +61 -59
- package/dist/InstanceCache.d.ts +6 -2
- package/dist/InstanceCache.js +18 -16
- package/dist/Model.d.ts +17 -7
- package/dist/Model.js +17 -7
- package/dist/ModelRegistry.d.ts +4 -0
- package/dist/ModelRegistry.js +17 -15
- package/dist/NetworkMonitor.d.ts +3 -1
- package/dist/NetworkMonitor.js +7 -5
- package/dist/SyncClient.d.ts +5 -15
- package/dist/SyncClient.js +60 -57
- package/dist/client/Ablo.js +30 -19
- package/dist/client/createInternalComponents.d.ts +4 -0
- package/dist/client/createInternalComponents.js +8 -2
- package/dist/client/createModelProxy.d.ts +21 -1
- package/dist/client/createModelProxy.js +123 -57
- package/dist/client/humans.d.ts +30 -9
- package/dist/client/humans.js +45 -19
- package/dist/client/reactiveEngine.d.ts +16 -11
- package/dist/client/reactiveEngine.js +113 -335
- package/dist/client/storeCluster.d.ts +47 -0
- package/dist/client/storeCluster.js +118 -0
- package/dist/client/storeLifecycle.d.ts +61 -0
- package/dist/client/storeLifecycle.js +231 -0
- package/dist/context.d.ts +13 -0
- package/dist/context.js +23 -0
- package/dist/core/index.d.ts +1 -1
- package/dist/docs/catalog.js +6 -3
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -2
- package/dist/query/client.d.ts +3 -0
- package/dist/query/client.js +6 -5
- package/dist/react/AbloProvider.d.ts +13 -1
- package/dist/react/AbloProvider.js +5 -2
- package/dist/react/context.d.ts +2 -2
- package/dist/react/createAbloReact.d.ts +56 -0
- package/dist/react/createAbloReact.js +51 -0
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.js +3 -0
- package/dist/react/useAblo.d.ts +9 -2
- package/dist/react/useAblo.js +25 -7
- package/dist/schema/coordination.js +5 -1
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +4 -0
- package/dist/schema/select.js +3 -0
- package/dist/schema/serialize.js +3 -0
- package/dist/source/adapter.d.ts +7 -5
- package/dist/source/adapter.js +7 -5
- package/dist/source/index.d.ts +1 -1
- package/dist/source/index.js +1 -1
- package/dist/{core/storeContract.d.ts → storeContract.d.ts} +5 -5
- package/dist/{core → stores}/DatabaseManager.d.ts +3 -1
- package/dist/{core → stores}/DatabaseManager.js +13 -12
- package/dist/{core → stores}/StoreManager.d.ts +5 -3
- package/dist/{core → stores}/StoreManager.js +24 -22
- package/dist/stores/SyncActionStore.d.ts +3 -1
- package/dist/stores/SyncActionStore.js +9 -7
- package/dist/sync/BootstrapFetcher.d.ts +4 -0
- package/dist/sync/BootstrapFetcher.js +28 -26
- package/dist/sync/OnDemandLoader.d.ts +3 -0
- package/dist/sync/OnDemandLoader.js +1 -0
- package/dist/sync/bootstrapApply.d.ts +3 -0
- package/dist/sync/bootstrapApply.js +2 -2
- package/dist/sync/deltaPipeline.d.ts +13 -12
- package/dist/sync/deltaPipeline.js +21 -4
- package/dist/sync/groupChange.d.ts +3 -0
- package/dist/sync/groupChange.js +16 -14
- package/dist/sync/participants.d.ts +19 -2
- package/dist/sync/participants.js +3 -1
- package/dist/sync/schemas.d.ts +2 -1
- package/dist/sync/schemas.js +3 -3
- package/dist/syncLog/contract.d.ts +20 -0
- package/dist/syncLog/contract.js +19 -0
- package/dist/syncLog/index.d.ts +1 -0
- package/dist/syncLog/index.js +1 -0
- package/dist/transaction/auth/capability.d.ts +35 -0
- package/dist/transaction/auth/capability.js +25 -0
- package/dist/transaction/coordination/awaitClaimGrant.d.ts +7 -0
- package/dist/transaction/coordination/awaitClaimGrant.js +12 -0
- package/dist/transaction/coordination/claimHeartbeatLoop.d.ts +34 -0
- package/dist/transaction/coordination/claimHeartbeatLoop.js +20 -0
- package/dist/transaction/coordination/index.d.ts +4 -4
- package/dist/transaction/coordination/index.js +4 -3
- package/dist/transaction/coordination/locator.d.ts +23 -2
- package/dist/transaction/coordination/locator.js +22 -2
- package/dist/transaction/coordination/schema.d.ts +125 -62
- package/dist/transaction/coordination/schema.js +228 -64
- package/dist/transaction/coordination/targetConflict.js +32 -28
- package/dist/transaction/errorCodes.d.ts +2 -2
- package/dist/transaction/errorCodes.js +13 -9
- package/dist/transaction/plugin.d.ts +95 -2
- package/dist/transaction/plugin.js +21 -2
- package/dist/transaction/resources/httpResources.d.ts +57 -2
- package/dist/transaction/resources/modelOperations.d.ts +148 -40
- package/dist/transaction/resources/where.d.ts +16 -0
- package/dist/transaction/resources/where.js +45 -0
- package/dist/transaction/schema/field.d.ts +12 -18
- package/dist/transaction/schema/fieldRef.d.ts +38 -0
- package/dist/transaction/schema/fieldRef.js +11 -0
- package/dist/transaction/schema/openapi.d.ts +16 -15
- package/dist/transaction/schema/openapi.js +186 -25
- package/dist/transaction/schema/relation.d.ts +7 -2
- package/dist/transaction/schema/schema.d.ts +27 -0
- package/dist/transaction/schema/schema.js +20 -0
- package/dist/transaction/transactions/settlement/commitEnvelope.d.ts +1 -1
- package/dist/transaction/transactions/settlement/pendingWrite.d.ts +1 -1
- package/dist/transaction/transport/httpClient.d.ts +9 -1
- package/dist/transaction/transport/httpClient.js +1 -0
- package/dist/transaction/transport/httpTransport.js +156 -44
- package/dist/transaction/transport/wsTransport.d.ts +2 -4
- package/dist/transaction/transport/wsTransport.js +16 -10
- package/dist/transaction/types/streams.d.ts +10 -0
- package/dist/transaction/utils/duration.d.ts +25 -0
- package/dist/transaction/utils/duration.js +32 -0
- package/dist/transaction/wire/accountResponses.d.ts +69 -0
- package/dist/transaction/wire/accountResponses.js +36 -1
- package/dist/transaction/wire/auth.d.ts +9 -2
- package/dist/transaction/wire/auth.js +7 -1
- package/dist/transaction/wire/claims.d.ts +164 -97
- package/dist/transaction/wire/claims.js +126 -28
- package/dist/transaction/wire/commit.d.ts +1 -1
- package/dist/transaction/wire/feedEvent.d.ts +27 -0
- package/dist/transaction/wire/feedEvent.js +27 -1
- package/dist/transaction/wire/frames.d.ts +2 -2
- package/dist/transaction/wire/inboundFrames.d.ts +12 -2
- package/dist/transaction/wire/index.d.ts +10 -6
- package/dist/transaction/wire/index.js +12 -3
- package/dist/transaction/wire/modelMutations.d.ts +31 -0
- package/dist/transaction/wire/modelMutations.js +52 -0
- package/dist/transaction/wire/modelShape.d.ts +78 -0
- package/dist/transaction/wire/modelShape.js +74 -0
- package/dist/transactions/mutations/MutationQueue.d.ts +6 -0
- package/dist/transactions/mutations/MutationQueue.js +55 -45
- package/dist/transactions/mutations/commitPayload.d.ts +3 -2
- package/dist/transactions/mutations/commitPayload.js +5 -5
- package/dist/transactions/mutations/deltaConfirmation.d.ts +4 -0
- package/dist/transactions/mutations/deltaConfirmation.js +10 -8
- package/dist/transactions/mutations/replayValidation.d.ts +2 -1
- package/dist/transactions/mutations/replayValidation.js +3 -2
- package/dist/{core → views}/QueryView.d.ts +1 -1
- package/dist/{core → views}/QueryView.js +1 -1
- package/dist/{core → views}/ViewRegistry.d.ts +1 -1
- package/dist/{core/queryUtils.d.ts → views/incrementalView.d.ts} +6 -6
- package/dist/{core/queryUtils.js → views/incrementalView.js} +6 -6
- package/docs/agents.md +1 -1
- package/docs/api-keys.md +6 -6
- package/docs/api.md +5 -43
- package/docs/audit.md +4 -3
- package/docs/cli.md +11 -11
- package/docs/client-behavior.md +4 -4
- package/docs/concurrency-convention.md +28 -42
- package/docs/coordination.md +235 -83
- package/docs/data-sources.md +4 -4
- package/docs/debugging.md +34 -12
- package/docs/deployment.md +8 -8
- package/docs/examples/scoped-agent.md +3 -3
- package/docs/groups.md +57 -3
- package/docs/guarantees.md +37 -10
- package/docs/how-it-works.md +29 -5
- package/docs/idempotency.md +6 -6
- package/docs/identity.md +22 -23
- package/docs/index.md +8 -8
- package/docs/integration-guide.md +14 -3
- package/docs/mcp.md +7 -7
- package/docs/migration.md +34 -15
- package/docs/projects.md +1 -1
- package/docs/react.md +19 -8
- package/docs/sessions.md +1 -1
- package/docs/webhooks.md +9 -9
- package/llms.txt +4 -4
- package/package.json +13 -20
- package/dist/cli.cjs +0 -288600
- package/dist/testing/fixtures/bootstrap.d.ts +0 -49
- package/dist/testing/fixtures/bootstrap.js +0 -59
- package/dist/testing/fixtures/deltas.d.ts +0 -83
- package/dist/testing/fixtures/deltas.js +0 -136
- package/dist/testing/fixtures/httpResponses.d.ts +0 -70
- package/dist/testing/fixtures/httpResponses.js +0 -90
- package/dist/testing/fixtures/models.d.ts +0 -83
- package/dist/testing/fixtures/models.js +0 -272
- package/dist/testing/helpers/reactWrapper.d.ts +0 -69
- package/dist/testing/helpers/reactWrapper.js +0 -67
- package/dist/testing/helpers/syncEngineHarness.d.ts +0 -54
- package/dist/testing/helpers/syncEngineHarness.js +0 -73
- package/dist/testing/helpers/wait.d.ts +0 -30
- package/dist/testing/helpers/wait.js +0 -49
- package/dist/testing/index.d.ts +0 -23
- package/dist/testing/index.js +0 -33
- package/dist/testing/mocks/FakeDatabase.d.ts +0 -18
- package/dist/testing/mocks/FakeDatabase.js +0 -10
- package/dist/testing/mocks/MockMutationExecutor.d.ts +0 -87
- package/dist/testing/mocks/MockMutationExecutor.js +0 -186
- package/dist/testing/mocks/MockNetworkMonitor.d.ts +0 -20
- package/dist/testing/mocks/MockNetworkMonitor.js +0 -46
- package/dist/testing/mocks/MockSyncContext.d.ts +0 -51
- package/dist/testing/mocks/MockSyncContext.js +0 -72
- package/dist/testing/mocks/MockSyncStore.d.ts +0 -88
- package/dist/testing/mocks/MockSyncStore.js +0 -171
- package/dist/testing/mocks/MockWebSocket.d.ts +0 -71
- package/dist/testing/mocks/MockWebSocket.js +0 -118
- package/docs/interaction-model.md +0 -99
- /package/dist/{core → query}/QueryProcessor.d.ts +0 -0
- /package/dist/{core → query}/QueryProcessor.js +0 -0
- /package/dist/{core/storeContract.js → storeContract.js} +0 -0
- /package/dist/{core → stores}/openIDBWithTimeout.d.ts +0 -0
- /package/dist/{core → stores}/openIDBWithTimeout.js +0 -0
- /package/dist/{source → transaction}/footprint.d.ts +0 -0
- /package/dist/{source → transaction}/footprint.js +0 -0
- /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 {
|
|
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
|
-
|
|
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
|
-
|
|
241
|
-
|
|
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
|
package/dist/sync/groupChange.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
|
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({
|
package/dist/sync/schemas.d.ts
CHANGED
|
@@ -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;
|
package/dist/sync/schemas.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* that predates it.
|
|
7
7
|
*/
|
|
8
8
|
import { z } from 'zod';
|
|
9
|
-
import {
|
|
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
|
-
|
|
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 —
|
|
26
|
-
|
|
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;
|