@abloatai/ablo 0.27.0 → 0.29.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 +59 -3
- package/README.md +1 -1
- package/dist/BaseSyncedStore.js +8 -9
- package/dist/Database.d.ts +14 -1
- package/dist/Database.js +228 -28
- package/dist/Model.d.ts +17 -0
- package/dist/Model.js +32 -1
- package/dist/SyncClient.d.ts +10 -6
- package/dist/SyncClient.js +390 -75
- package/dist/adapters/inMemoryStorage.d.ts +1 -0
- package/dist/adapters/inMemoryStorage.js +29 -13
- package/dist/ai-sdk/coordinatedTool.d.ts +15 -2
- package/dist/auth/schemas.d.ts +6 -0
- package/dist/auth/schemas.js +7 -0
- package/dist/cli.cjs +821 -402
- package/dist/client/Ablo.d.ts +53 -24
- package/dist/client/Ablo.js +12 -199
- package/dist/client/claimHeartbeatLoop.d.ts +1 -1
- package/dist/client/claimHeartbeatLoop.js +1 -1
- package/dist/client/createInternalComponents.d.ts +4 -0
- package/dist/client/createInternalComponents.js +3 -1
- package/dist/client/durableWrites.d.ts +21 -0
- package/dist/client/durableWrites.js +46 -0
- package/dist/client/httpClient.d.ts +21 -45
- package/dist/client/httpClient.js +104 -26
- package/dist/client/httpTransport.d.ts +8 -0
- package/dist/client/{ApiClient.js → httpTransport.js} +361 -308
- package/dist/client/modelRegistration.js +11 -0
- package/dist/client/options.d.ts +54 -7
- package/dist/client/options.js +3 -2
- package/dist/client/resourceTypes.d.ts +9 -85
- package/dist/client/resourceTypes.js +1 -1
- package/dist/client/sessionMint.d.ts +5 -6
- package/dist/client/sessionMint.js +4 -5
- package/dist/client/validateAbloOptions.js +3 -3
- package/dist/client/wsMutationExecutor.d.ts +3 -2
- package/dist/client/wsMutationExecutor.js +3 -2
- package/dist/coordination/schema.d.ts +30 -0
- package/dist/coordination/schema.js +14 -0
- package/dist/core/StoreManager.d.ts +2 -0
- package/dist/core/StoreManager.js +12 -0
- package/dist/core/index.d.ts +1 -1
- package/dist/errorCodes.js +6 -2
- package/dist/errors.d.ts +4 -40
- package/dist/errors.js +5 -5
- package/dist/index.d.ts +20 -8
- package/dist/index.js +9 -5
- package/dist/interfaces/index.d.ts +5 -2
- package/dist/mutators/UndoManager.d.ts +2 -0
- package/dist/mutators/UndoManager.js +32 -0
- package/dist/mutators/defineMutators.d.ts +18 -0
- package/dist/mutators/defineMutators.js +4 -8
- package/dist/react/index.d.ts +1 -1
- package/dist/react/useAblo.d.ts +6 -4
- package/dist/react/useAblo.js +25 -3
- package/dist/schema/index.d.ts +2 -2
- package/dist/schema/index.js +1 -1
- package/dist/schema/schema.d.ts +31 -1
- package/dist/schema/select.d.ts +15 -0
- package/dist/schema/select.js +27 -3
- package/dist/source/connector.d.ts +3 -3
- package/dist/source/factory.d.ts +4 -6
- package/dist/source/factory.js +4 -7
- package/dist/source/index.d.ts +4 -4
- package/dist/source/index.js +2 -2
- package/dist/source/signing.d.ts +0 -3
- package/dist/source/types.d.ts +0 -26
- package/dist/stores/ObjectStore.d.ts +14 -1
- package/dist/stores/ObjectStore.js +33 -10
- package/dist/stores/ObjectStoreContract.d.ts +2 -0
- package/dist/surface.d.ts +1 -1
- package/dist/surface.js +3 -0
- package/dist/sync/BootstrapFetcher.d.ts +10 -0
- package/dist/sync/BootstrapFetcher.js +27 -4
- package/dist/sync/SyncWebSocket.d.ts +2 -1
- package/dist/sync/createClaimStream.d.ts +11 -2
- package/dist/sync/createClaimStream.js +4 -3
- package/dist/sync/persistedPrefix.d.ts +12 -0
- package/dist/sync/persistedPrefix.js +22 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +1 -0
- package/dist/testing/mocks/FakeDatabase.d.ts +18 -0
- package/dist/testing/mocks/FakeDatabase.js +10 -0
- package/dist/testing/mocks/MockWebSocket.d.ts +2 -1
- package/dist/transactions/TransactionQueue.d.ts +66 -8
- package/dist/transactions/TransactionQueue.js +607 -89
- package/dist/transactions/commitEnvelope.d.ts +132 -0
- package/dist/transactions/commitEnvelope.js +139 -0
- package/dist/transactions/commitOutboxStore.d.ts +32 -0
- package/dist/transactions/commitOutboxStore.js +26 -0
- package/dist/transactions/commitPayload.d.ts +15 -0
- package/dist/transactions/commitPayload.js +6 -0
- package/dist/transactions/durableWriteStore.d.ts +123 -0
- package/dist/transactions/durableWriteStore.js +30 -0
- package/dist/transactions/httpCommitEnvelope.d.ts +44 -0
- package/dist/transactions/httpCommitEnvelope.js +181 -0
- package/dist/transactions/idempotencyKey.d.ts +10 -0
- package/dist/transactions/idempotencyKey.js +9 -0
- package/dist/transactions/replayValidation.d.ts +83 -0
- package/dist/transactions/replayValidation.js +46 -1
- package/dist/types/global.d.ts +10 -29
- package/dist/types/global.js +4 -7
- package/dist/types/streams.d.ts +2 -28
- package/dist/wire/bootstrapReason.d.ts +9 -0
- package/dist/wire/bootstrapReason.js +8 -0
- package/dist/wire/frames.d.ts +6 -21
- package/dist/wire/frames.js +4 -4
- package/dist/wire/index.d.ts +6 -3
- package/dist/wire/index.js +3 -2
- package/dist/wire/protocolVersion.d.ts +30 -17
- package/dist/wire/protocolVersion.js +34 -18
- package/docs/agents.md +8 -3
- package/docs/api.md +16 -14
- package/docs/client-behavior.md +6 -3
- package/docs/coordination.md +4 -5
- package/docs/data-sources.md +5 -8
- package/docs/guarantees.md +21 -0
- package/docs/integration-guide.md +1 -0
- package/docs/mcp.md +1 -1
- package/docs/migration.md +21 -1
- package/docs/quickstart.md +11 -0
- package/docs/react.md +0 -46
- package/examples/README.md +6 -5
- package/llms.txt +15 -15
- package/package.json +3 -3
- package/dist/client/ApiClient.d.ts +0 -177
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/** Crash-durable exact HTTP request used by the stateless agent client. */
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { v5 as uuidv5 } from 'uuid';
|
|
4
|
+
import { stableStringify } from '../utils/json.js';
|
|
5
|
+
import { PROTOCOL_VERSION } from '../wire/protocolVersion.js';
|
|
6
|
+
import { idempotencyKeySchema } from './idempotencyKey.js';
|
|
7
|
+
export const HTTP_COMMIT_ENVELOPE_VERSION = 1;
|
|
8
|
+
export const HTTP_COMMIT_ENVELOPE_PREFIX = 'http-commit-envelope:';
|
|
9
|
+
/** Stay one hour inside the server's 24-hour idempotency retention window. */
|
|
10
|
+
export const HTTP_COMMIT_REPLAY_WINDOW_MS = 23 * 60 * 60 * 1000;
|
|
11
|
+
const HTTP_COMMIT_SCOPE_ID_NAMESPACE = '043e8f73-86fc-5f62-af46-935d68fca729';
|
|
12
|
+
const commitPathSchema = z.literal('/v1/commits');
|
|
13
|
+
const modelCollectionPathSchema = z.string().regex(/^\/v1\/models\/[^/]+$/);
|
|
14
|
+
const modelEntityPathSchema = z.string().regex(/^\/v1\/models\/[^/]+\/[^/]+$/);
|
|
15
|
+
function hasSafeModelPathSegments(path) {
|
|
16
|
+
if (!path.startsWith('/v1/models/'))
|
|
17
|
+
return true;
|
|
18
|
+
try {
|
|
19
|
+
return path
|
|
20
|
+
.slice('/v1/models/'.length)
|
|
21
|
+
.split('/')
|
|
22
|
+
.every((segment) => {
|
|
23
|
+
const decoded = decodeURIComponent(segment);
|
|
24
|
+
return (decoded.length > 0 &&
|
|
25
|
+
decoded !== '.' &&
|
|
26
|
+
decoded !== '..' &&
|
|
27
|
+
!decoded.includes('/') &&
|
|
28
|
+
!decoded.includes('\\'));
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** Apply normal JSON semantics once, then make object-key order canonical. */
|
|
36
|
+
export function canonicalHttpCommitBody(value) {
|
|
37
|
+
const serialized = JSON.stringify(value);
|
|
38
|
+
if (serialized === undefined) {
|
|
39
|
+
throw new TypeError('HTTP commit body is not JSON serializable');
|
|
40
|
+
}
|
|
41
|
+
return stableStringify(JSON.parse(serialized));
|
|
42
|
+
}
|
|
43
|
+
export const durableHttpCommitEnvelopeSchema = z
|
|
44
|
+
.strictObject({
|
|
45
|
+
id: z.string().startsWith(HTTP_COMMIT_ENVELOPE_PREFIX),
|
|
46
|
+
type: z.literal('http_commit_envelope'),
|
|
47
|
+
storageVersion: z.literal(HTTP_COMMIT_ENVELOPE_VERSION),
|
|
48
|
+
idempotencyKey: idempotencyKeySchema,
|
|
49
|
+
/**
|
|
50
|
+
* The wire contract used when this exact request was sealed. Entries from
|
|
51
|
+
* before protocol versioning omitted the field and are v1 by definition.
|
|
52
|
+
*/
|
|
53
|
+
protocolVersion: z.number().int().positive().default(1),
|
|
54
|
+
request: z.strictObject({
|
|
55
|
+
method: z.enum(['POST', 'PATCH', 'DELETE']),
|
|
56
|
+
path: z.string().startsWith('/'),
|
|
57
|
+
body: z.string().refine((body) => {
|
|
58
|
+
try {
|
|
59
|
+
JSON.parse(body);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}, 'HTTP commit body must be valid JSON'),
|
|
66
|
+
}),
|
|
67
|
+
scopeNamespace: z.string().min(1),
|
|
68
|
+
createdAt: z.number().int().nonnegative(),
|
|
69
|
+
sealedAt: z.number().int().nonnegative(),
|
|
70
|
+
/** Monotonic within one client; disambiguates writes sealed in the same ms. */
|
|
71
|
+
sequence: z.number().int().nonnegative().optional(),
|
|
72
|
+
timestamp: z.number().int().nonnegative(),
|
|
73
|
+
})
|
|
74
|
+
.superRefine((envelope, context) => {
|
|
75
|
+
const legacyId = httpCommitEnvelopeRecordId(envelope.idempotencyKey);
|
|
76
|
+
const scopedId = httpCommitEnvelopeRecordId(envelope.idempotencyKey, envelope.scopeNamespace);
|
|
77
|
+
if (envelope.id !== legacyId && envelope.id !== scopedId) {
|
|
78
|
+
context.addIssue({
|
|
79
|
+
code: 'custom',
|
|
80
|
+
path: ['id'],
|
|
81
|
+
message: 'HTTP envelope id must be derived from its idempotency key',
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
if (envelope.sealedAt < envelope.createdAt) {
|
|
85
|
+
context.addIssue({
|
|
86
|
+
code: 'custom',
|
|
87
|
+
path: ['sealedAt'],
|
|
88
|
+
message: 'HTTP envelope cannot be sealed before it is created',
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
const { method, path } = envelope.request;
|
|
92
|
+
const allowedPath = method === 'POST'
|
|
93
|
+
? commitPathSchema.safeParse(path).success ||
|
|
94
|
+
modelCollectionPathSchema.safeParse(path).success
|
|
95
|
+
: modelEntityPathSchema.safeParse(path).success;
|
|
96
|
+
if (!allowedPath || !hasSafeModelPathSegments(path)) {
|
|
97
|
+
context.addIssue({
|
|
98
|
+
code: 'custom',
|
|
99
|
+
path: ['request', 'path'],
|
|
100
|
+
message: 'HTTP outbox records may target only commit or model-mutation routes',
|
|
101
|
+
});
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
let body;
|
|
105
|
+
try {
|
|
106
|
+
body = JSON.parse(envelope.request.body);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return; // The field-level JSON refinement reports this.
|
|
110
|
+
}
|
|
111
|
+
if (typeof body !== 'object' || body === null || Array.isArray(body)) {
|
|
112
|
+
context.addIssue({
|
|
113
|
+
code: 'custom',
|
|
114
|
+
path: ['request', 'body'],
|
|
115
|
+
message: 'HTTP commit body must be a JSON object',
|
|
116
|
+
});
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const record = body;
|
|
120
|
+
// `envelope.idempotencyKey` is replayed as the `Idempotency-Key` header.
|
|
121
|
+
// Older sealed bodies may also carry `idempotencyKey`/`clientTxId`; accept
|
|
122
|
+
// and replay them byte-for-byte, but current requests do not duplicate the
|
|
123
|
+
// key in JSON.
|
|
124
|
+
if (path === '/v1/commits') {
|
|
125
|
+
if (!Array.isArray(record.operations) || record.operations.length === 0) {
|
|
126
|
+
context.addIssue({
|
|
127
|
+
code: 'custom',
|
|
128
|
+
path: ['request', 'body'],
|
|
129
|
+
message: 'Commit-route body must carry operations',
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else if (method === 'POST' && typeof record.id !== 'string') {
|
|
134
|
+
context.addIssue({
|
|
135
|
+
code: 'custom',
|
|
136
|
+
path: ['request', 'body', 'id'],
|
|
137
|
+
message: 'Model-create body must carry its entity id',
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
else if (method === 'PATCH' &&
|
|
141
|
+
(typeof record.data !== 'object' || record.data === null || Array.isArray(record.data))) {
|
|
142
|
+
context.addIssue({
|
|
143
|
+
code: 'custom',
|
|
144
|
+
path: ['request', 'body', 'data'],
|
|
145
|
+
message: 'Model-update body must carry a JSON object patch',
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
export function httpCommitEnvelopeRecordId(idempotencyKey, scopeNamespace) {
|
|
150
|
+
if (!scopeNamespace) {
|
|
151
|
+
return `${HTTP_COMMIT_ENVELOPE_PREFIX}${idempotencyKey}`;
|
|
152
|
+
}
|
|
153
|
+
const scopeId = uuidv5(scopeNamespace, HTTP_COMMIT_SCOPE_ID_NAMESPACE);
|
|
154
|
+
return `${HTTP_COMMIT_ENVELOPE_PREFIX}${scopeId}:${idempotencyKey}`;
|
|
155
|
+
}
|
|
156
|
+
export function createDurableHttpCommitEnvelope(input) {
|
|
157
|
+
const now = Date.now();
|
|
158
|
+
const createdAt = input.createdAt ?? now;
|
|
159
|
+
const sealedAt = input.sealedAt ?? now;
|
|
160
|
+
const body = canonicalHttpCommitBody(input.request.body);
|
|
161
|
+
return durableHttpCommitEnvelopeSchema.parse({
|
|
162
|
+
id: httpCommitEnvelopeRecordId(input.idempotencyKey, input.scopeNamespace),
|
|
163
|
+
type: 'http_commit_envelope',
|
|
164
|
+
storageVersion: HTTP_COMMIT_ENVELOPE_VERSION,
|
|
165
|
+
idempotencyKey: input.idempotencyKey,
|
|
166
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
167
|
+
request: {
|
|
168
|
+
method: input.request.method,
|
|
169
|
+
path: input.request.path,
|
|
170
|
+
body,
|
|
171
|
+
},
|
|
172
|
+
scopeNamespace: input.scopeNamespace,
|
|
173
|
+
createdAt,
|
|
174
|
+
sealedAt,
|
|
175
|
+
...(input.sequence !== undefined ? { sequence: input.sequence } : {}),
|
|
176
|
+
timestamp: sealedAt,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
export function isHttpCommitReplayExpired(envelope, now = Date.now()) {
|
|
180
|
+
return now - envelope.sealedAt >= HTTP_COMMIT_REPLAY_WINDOW_MS;
|
|
181
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity shared by the durable write records.
|
|
3
|
+
*
|
|
4
|
+
* A retry must reuse the same non-empty key so the server can recognize the
|
|
5
|
+
* same logical write. This is a persistence invariant, not a second commit
|
|
6
|
+
* protocol.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
export declare const idempotencyKeySchema: z.core.$ZodBranded<z.ZodString, "IdempotencyKey", "out">;
|
|
10
|
+
export type IdempotencyKey = z.output<typeof idempotencyKeySchema>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity shared by the durable write records.
|
|
3
|
+
*
|
|
4
|
+
* A retry must reuse the same non-empty key so the server can recognize the
|
|
5
|
+
* same logical write. This is a persistence invariant, not a second commit
|
|
6
|
+
* protocol.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
export const idempotencyKeySchema = z.string().min(1).max(255).brand();
|
|
@@ -45,6 +45,14 @@ export declare const persistedTransactionSchema: z.ZodObject<{
|
|
|
45
45
|
}, z.core.$strip>;
|
|
46
46
|
createdAt: z.ZodOptional<z.ZodNumber>;
|
|
47
47
|
batchId: z.ZodOptional<z.ZodString>;
|
|
48
|
+
commitEnvelope: z.ZodOptional<z.ZodObject<{
|
|
49
|
+
idempotencyKey: z.core.$ZodBranded<z.ZodString, "IdempotencyKey", "out">;
|
|
50
|
+
operationIndex: z.ZodNumber;
|
|
51
|
+
operationCount: z.ZodNumber;
|
|
52
|
+
sealedAt: z.ZodOptional<z.ZodNumber>;
|
|
53
|
+
sequence: z.ZodOptional<z.ZodNumber>;
|
|
54
|
+
}, z.core.$strict>>;
|
|
55
|
+
sourceMutationIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
48
56
|
writeOptions: z.ZodOptional<z.ZodObject<{
|
|
49
57
|
readAt: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
50
58
|
onStale: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
@@ -76,6 +84,7 @@ export declare function deserializePersistedTransaction(row: unknown): Transacti
|
|
|
76
84
|
* queue is restored on reconnect.
|
|
77
85
|
*/
|
|
78
86
|
export declare const persistedMutationSchema: z.ZodObject<{
|
|
87
|
+
mutationId: z.ZodOptional<z.ZodString>;
|
|
79
88
|
type: z.ZodEnum<{
|
|
80
89
|
update: "update";
|
|
81
90
|
create: "create";
|
|
@@ -85,6 +94,7 @@ export declare const persistedMutationSchema: z.ZodObject<{
|
|
|
85
94
|
modelData: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
86
95
|
modelName: z.ZodString;
|
|
87
96
|
timestamp: z.ZodString;
|
|
97
|
+
capturedChanges: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
88
98
|
writeOptions: z.ZodOptional<z.ZodObject<{
|
|
89
99
|
readAt: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
90
100
|
onStale: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
@@ -97,3 +107,76 @@ export declare const persistedMutationSchema: z.ZodObject<{
|
|
|
97
107
|
}, z.core.$loose>>;
|
|
98
108
|
}, z.core.$loose>;
|
|
99
109
|
export type PersistedQueuedMutation = z.infer<typeof persistedMutationSchema>;
|
|
110
|
+
export declare const PENDING_MUTATION_RECORD_PREFIX = "pending-mutation:";
|
|
111
|
+
/**
|
|
112
|
+
* Stay one hour inside the server's 24-hour idempotency retention window.
|
|
113
|
+
* A journaled write older than this can no longer be deduplicated by the
|
|
114
|
+
* server, so restore holds it for review instead of replaying it.
|
|
115
|
+
*/
|
|
116
|
+
export declare const PENDING_MUTATION_REPLAY_WINDOW_MS: number;
|
|
117
|
+
/** Scope-less records written by the first aggregate-journal release. */
|
|
118
|
+
export declare const legacyPendingMutationRecordSchema: z.ZodObject<{
|
|
119
|
+
storageVersion: z.ZodLiteral<1>;
|
|
120
|
+
id: z.ZodString;
|
|
121
|
+
type: z.ZodLiteral<"pending_mutation">;
|
|
122
|
+
mutation: z.ZodObject<{
|
|
123
|
+
type: z.ZodEnum<{
|
|
124
|
+
update: "update";
|
|
125
|
+
create: "create";
|
|
126
|
+
delete: "delete";
|
|
127
|
+
archive: "archive";
|
|
128
|
+
}>;
|
|
129
|
+
modelData: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
130
|
+
modelName: z.ZodString;
|
|
131
|
+
timestamp: z.ZodString;
|
|
132
|
+
capturedChanges: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
133
|
+
writeOptions: z.ZodOptional<z.ZodObject<{
|
|
134
|
+
readAt: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
135
|
+
onStale: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
136
|
+
reject: "reject";
|
|
137
|
+
overwrite: "overwrite";
|
|
138
|
+
notify: "notify";
|
|
139
|
+
}>>>;
|
|
140
|
+
idempotencyKey: z.ZodOptional<z.ZodString>;
|
|
141
|
+
label: z.ZodOptional<z.ZodString>;
|
|
142
|
+
}, z.core.$loose>>;
|
|
143
|
+
mutationId: z.ZodString;
|
|
144
|
+
}, z.core.$loose>;
|
|
145
|
+
timestamp: z.ZodNumber;
|
|
146
|
+
}, z.core.$strict>;
|
|
147
|
+
export declare const pendingMutationRecordSchema: z.ZodObject<{
|
|
148
|
+
storageVersion: z.ZodLiteral<2>;
|
|
149
|
+
scope: z.ZodObject<{
|
|
150
|
+
organizationId: z.ZodString;
|
|
151
|
+
participantId: z.ZodString;
|
|
152
|
+
namespace: z.ZodString;
|
|
153
|
+
}, z.core.$strict>;
|
|
154
|
+
id: z.ZodString;
|
|
155
|
+
type: z.ZodLiteral<"pending_mutation">;
|
|
156
|
+
mutation: z.ZodObject<{
|
|
157
|
+
type: z.ZodEnum<{
|
|
158
|
+
update: "update";
|
|
159
|
+
create: "create";
|
|
160
|
+
delete: "delete";
|
|
161
|
+
archive: "archive";
|
|
162
|
+
}>;
|
|
163
|
+
modelData: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
164
|
+
modelName: z.ZodString;
|
|
165
|
+
timestamp: z.ZodString;
|
|
166
|
+
capturedChanges: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
167
|
+
writeOptions: z.ZodOptional<z.ZodObject<{
|
|
168
|
+
readAt: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
169
|
+
onStale: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
170
|
+
reject: "reject";
|
|
171
|
+
overwrite: "overwrite";
|
|
172
|
+
notify: "notify";
|
|
173
|
+
}>>>;
|
|
174
|
+
idempotencyKey: z.ZodOptional<z.ZodString>;
|
|
175
|
+
label: z.ZodOptional<z.ZodString>;
|
|
176
|
+
}, z.core.$loose>>;
|
|
177
|
+
mutationId: z.ZodString;
|
|
178
|
+
}, z.core.$loose>;
|
|
179
|
+
timestamp: z.ZodNumber;
|
|
180
|
+
}, z.core.$strict>;
|
|
181
|
+
export type PendingMutationRecord = z.infer<typeof pendingMutationRecordSchema>;
|
|
182
|
+
export declare function pendingMutationRecordId(mutationId: string): string;
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { z } from 'zod';
|
|
18
18
|
import { computePriorityScore, normalizeModelKey } from './commitPayload.js';
|
|
19
|
+
import { commitEnvelopeMemberSchema, commitOutboxScopeSchema, } from './commitEnvelope.js';
|
|
19
20
|
/** The subset of a write's options that is stored with each transaction or queued mutation. */
|
|
20
21
|
const persistedWriteOptionsSchema = z
|
|
21
22
|
.object({
|
|
@@ -49,12 +50,20 @@ export const persistedTransactionSchema = z
|
|
|
49
50
|
}),
|
|
50
51
|
createdAt: z.number().optional(),
|
|
51
52
|
batchId: z.string().optional(),
|
|
53
|
+
commitEnvelope: commitEnvelopeMemberSchema.optional(),
|
|
54
|
+
sourceMutationIds: z.array(z.string().min(1)).optional(),
|
|
52
55
|
writeOptions: persistedWriteOptionsSchema.optional(),
|
|
53
56
|
localOnly: z.boolean().optional(),
|
|
54
57
|
})
|
|
55
58
|
.loose();
|
|
56
59
|
/** The `type` values of stored rows that belong to other parts of the client and are not replayable transactions. */
|
|
57
|
-
const NON_REPLAYABLE_TYPES = new Set([
|
|
60
|
+
const NON_REPLAYABLE_TYPES = new Set([
|
|
61
|
+
'queue',
|
|
62
|
+
'awaiting_delta',
|
|
63
|
+
'pending_mutation',
|
|
64
|
+
'commit_envelope',
|
|
65
|
+
'http_commit_envelope',
|
|
66
|
+
]);
|
|
58
67
|
/**
|
|
59
68
|
* Reports whether a stored row is one of the non-transaction kinds, so callers
|
|
60
69
|
* skip it instead of treating it as a corrupt transaction.
|
|
@@ -91,6 +100,12 @@ export function deserializePersistedTransaction(row) {
|
|
|
91
100
|
priority: 'normal',
|
|
92
101
|
priorityScore: computePriorityScore(tx.type, tx.modelName),
|
|
93
102
|
...(tx.batchId !== undefined ? { batchId: tx.batchId } : {}),
|
|
103
|
+
...(tx.commitEnvelope !== undefined
|
|
104
|
+
? { commitEnvelope: tx.commitEnvelope }
|
|
105
|
+
: {}),
|
|
106
|
+
...(tx.sourceMutationIds !== undefined
|
|
107
|
+
? { sourceMutationIds: tx.sourceMutationIds }
|
|
108
|
+
: {}),
|
|
94
109
|
...(tx.writeOptions !== undefined ? { writeOptions: tx.writeOptions } : {}),
|
|
95
110
|
...(tx.localOnly !== undefined ? { localOnly: tx.localOnly } : {}),
|
|
96
111
|
};
|
|
@@ -102,10 +117,40 @@ export function deserializePersistedTransaction(row) {
|
|
|
102
117
|
*/
|
|
103
118
|
export const persistedMutationSchema = z
|
|
104
119
|
.object({
|
|
120
|
+
mutationId: z.string().min(1).optional(),
|
|
105
121
|
type: z.enum(['create', 'update', 'delete', 'archive']),
|
|
106
122
|
modelData: z.record(z.string(), z.unknown()),
|
|
107
123
|
modelName: z.string().min(1),
|
|
108
124
|
timestamp: z.string(),
|
|
125
|
+
capturedChanges: z.record(z.string(), z.unknown()).optional(),
|
|
109
126
|
writeOptions: persistedWriteOptionsSchema.optional(),
|
|
110
127
|
})
|
|
111
128
|
.loose();
|
|
129
|
+
export const PENDING_MUTATION_RECORD_PREFIX = 'pending-mutation:';
|
|
130
|
+
/**
|
|
131
|
+
* Stay one hour inside the server's 24-hour idempotency retention window.
|
|
132
|
+
* A journaled write older than this can no longer be deduplicated by the
|
|
133
|
+
* server, so restore holds it for review instead of replaying it.
|
|
134
|
+
*/
|
|
135
|
+
export const PENDING_MUTATION_REPLAY_WINDOW_MS = 23 * 60 * 60 * 1000;
|
|
136
|
+
const pendingMutationRecordBaseShape = {
|
|
137
|
+
id: z.string().startsWith(PENDING_MUTATION_RECORD_PREFIX),
|
|
138
|
+
type: z.literal('pending_mutation'),
|
|
139
|
+
mutation: persistedMutationSchema.extend({
|
|
140
|
+
mutationId: z.string().min(1),
|
|
141
|
+
}),
|
|
142
|
+
timestamp: z.number().int().nonnegative(),
|
|
143
|
+
};
|
|
144
|
+
/** Scope-less records written by the first aggregate-journal release. */
|
|
145
|
+
export const legacyPendingMutationRecordSchema = z.strictObject({
|
|
146
|
+
...pendingMutationRecordBaseShape,
|
|
147
|
+
storageVersion: z.literal(1),
|
|
148
|
+
});
|
|
149
|
+
export const pendingMutationRecordSchema = z.strictObject({
|
|
150
|
+
...pendingMutationRecordBaseShape,
|
|
151
|
+
storageVersion: z.literal(2),
|
|
152
|
+
scope: commitOutboxScopeSchema,
|
|
153
|
+
});
|
|
154
|
+
export function pendingMutationRecordId(mutationId) {
|
|
155
|
+
return `${PENDING_MUTATION_RECORD_PREFIX}${mutationId}`;
|
|
156
|
+
}
|
package/dist/types/global.d.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The single place where you tell the SDK about your application's types.
|
|
3
3
|
*
|
|
4
|
-
* You register your Schema
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* site.
|
|
4
|
+
* You register your Schema and UserMeta once by augmenting the
|
|
5
|
+
* {@link Register} interface. From then on the typed SDK surface reads its
|
|
6
|
+
* types from that registration, so you never pass a generic or a `schema`
|
|
7
|
+
* argument at a call site.
|
|
9
8
|
*
|
|
10
9
|
* Registration uses TypeScript module augmentation: any file in your project
|
|
11
10
|
* can add an `interface Register` to a `declare module '@abloatai/ablo'`
|
|
@@ -27,8 +26,6 @@
|
|
|
27
26
|
* declare module '@abloatai/ablo' {
|
|
28
27
|
* interface Register {
|
|
29
28
|
* Schema: typeof schema;
|
|
30
|
-
* Presence: { cursor: { x: number; y: number } | null };
|
|
31
|
-
* Claims: { editLayer: { layerId: string } };
|
|
32
29
|
* UserMeta: { id: string; email: string };
|
|
33
30
|
* }
|
|
34
31
|
* }
|
|
@@ -39,18 +36,18 @@
|
|
|
39
36
|
* {@link DefaultSyncShape} — a loose shape that keeps your code compiling
|
|
40
37
|
* without typed results until you opt in.
|
|
41
38
|
*/
|
|
39
|
+
import type { SchemaRecord } from '../schema/schema.js';
|
|
42
40
|
/**
|
|
43
41
|
* The fallback shapes the resolvers use when {@link Register} has not been
|
|
44
42
|
* augmented. `DefaultSyncShape.Schema` is deliberately structural — it carries
|
|
45
|
-
* `{ models:
|
|
46
|
-
*
|
|
43
|
+
* `{ models: SchemaRecord }` so hooks can still check a model-key argument
|
|
44
|
+
* against something (and satisfy the `SchemaRecord` bound), just without a
|
|
45
|
+
* typed entity shape behind it.
|
|
47
46
|
*/
|
|
48
47
|
export interface DefaultSyncShape {
|
|
49
48
|
readonly Schema: {
|
|
50
|
-
readonly models:
|
|
49
|
+
readonly models: SchemaRecord;
|
|
51
50
|
};
|
|
52
|
-
readonly Presence: Record<string, unknown>;
|
|
53
|
-
readonly Claims: Record<string, unknown>;
|
|
54
51
|
readonly UserMeta: {
|
|
55
52
|
readonly id: string;
|
|
56
53
|
};
|
|
@@ -58,7 +55,7 @@ export interface DefaultSyncShape {
|
|
|
58
55
|
/**
|
|
59
56
|
* The registration interface you augment to declare your application's types.
|
|
60
57
|
* Add keys inside a `declare module '@abloatai/ablo'` block — for example
|
|
61
|
-
* `interface Register { Schema: ...;
|
|
58
|
+
* `interface Register { Schema: ...; UserMeta: ...; }`. It is empty by default,
|
|
62
59
|
* so any key you omit falls back to {@link DefaultSyncShape}. It is exported
|
|
63
60
|
* from the package root so your augmentation merges into this declaration.
|
|
64
61
|
*
|
|
@@ -77,22 +74,6 @@ export type ResolveSchema = Register extends {
|
|
|
77
74
|
} ? S extends {
|
|
78
75
|
models: Record<string, unknown>;
|
|
79
76
|
} ? S : DefaultSyncShape['Schema'] : DefaultSyncShape['Schema'];
|
|
80
|
-
/**
|
|
81
|
-
* Your registered presence shape, or the default when none is registered.
|
|
82
|
-
* `usePresence` reads it. The shape is free-form — any JSON-serializable value
|
|
83
|
-
* you broadcast per session.
|
|
84
|
-
*/
|
|
85
|
-
export type ResolvePresence = Register extends {
|
|
86
|
-
Presence: infer P;
|
|
87
|
-
} ? P : DefaultSyncShape['Presence'];
|
|
88
|
-
/**
|
|
89
|
-
* Your registered claim vocabulary, or the default when none is registered.
|
|
90
|
-
* Each key is a claim name and its value is that claim's payload.
|
|
91
|
-
* `useClaim(claimName)` reads it.
|
|
92
|
-
*/
|
|
93
|
-
export type ResolveClaims = Register extends {
|
|
94
|
-
Claims: infer I;
|
|
95
|
-
} ? I : DefaultSyncShape['Claims'];
|
|
96
77
|
/**
|
|
97
78
|
* Your registered user-metadata shape, or the default when none is registered.
|
|
98
79
|
* It carries identity information you trust from your own auth layer; the SDK
|
package/dist/types/global.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The single place where you tell the SDK about your application's types.
|
|
3
3
|
*
|
|
4
|
-
* You register your Schema
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* site.
|
|
4
|
+
* You register your Schema and UserMeta once by augmenting the
|
|
5
|
+
* {@link Register} interface. From then on the typed SDK surface reads its
|
|
6
|
+
* types from that registration, so you never pass a generic or a `schema`
|
|
7
|
+
* argument at a call site.
|
|
9
8
|
*
|
|
10
9
|
* Registration uses TypeScript module augmentation: any file in your project
|
|
11
10
|
* can add an `interface Register` to a `declare module '@abloatai/ablo'`
|
|
@@ -27,8 +26,6 @@
|
|
|
27
26
|
* declare module '@abloatai/ablo' {
|
|
28
27
|
* interface Register {
|
|
29
28
|
* Schema: typeof schema;
|
|
30
|
-
* Presence: { cursor: { x: number; y: number } | null };
|
|
31
|
-
* Claims: { editLayer: { layerId: string } };
|
|
32
29
|
* UserMeta: { id: string; email: string };
|
|
33
30
|
* }
|
|
34
31
|
* }
|
package/dist/types/streams.d.ts
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* layer underneath.
|
|
10
10
|
*/
|
|
11
11
|
import type { InferModel, Schema } from '../schema/schema.js';
|
|
12
|
-
import type { TargetRange, OnStaleMode, WireClaim, ClaimRejection, PresenceKind, ParticipantKind } from '../coordination/schema.js';
|
|
13
|
-
export type { TargetRange, OnStaleMode, WireClaim, ClaimRejection, PresenceKind, ParticipantKind };
|
|
12
|
+
import type { TargetRange, OnStaleMode, WireClaim, ClaimRejection, ClaimLost, PresenceKind, ParticipantKind } from '../coordination/schema.js';
|
|
13
|
+
export type { TargetRange, OnStaleMode, WireClaim, ClaimRejection, ClaimLost, PresenceKind, ParticipantKind, };
|
|
14
14
|
/**
|
|
15
15
|
* Any JSON-serializable value. Used where the SDK accepts free-form metadata
|
|
16
16
|
* that is stored or transported as JSON — more precise than `unknown` while
|
|
@@ -384,32 +384,6 @@ export interface ClaimStream {
|
|
|
384
384
|
*/
|
|
385
385
|
[Symbol.asyncIterator](): AsyncIterableIterator<readonly Claim[]>;
|
|
386
386
|
}
|
|
387
|
-
/**
|
|
388
|
-
* A notification that you lost a claim you were holding — distinct from
|
|
389
|
-
* {@link ClaimRejection}, which is a claim the server refused to grant.
|
|
390
|
-
* Delivered through `onLost`.
|
|
391
|
-
*/
|
|
392
|
-
export interface ClaimLost {
|
|
393
|
-
/** The id of the claim you just lost. */
|
|
394
|
-
readonly claimId: string;
|
|
395
|
-
/**
|
|
396
|
-
* How you lost it. `'preempted'`: a privileged participant (one holding the
|
|
397
|
-
* `claim.preempt` capability) evicted you and took the lease — its work now
|
|
398
|
-
* supersedes yours, so re-plan against the new holder rather than blindly
|
|
399
|
-
* re-claiming. `'expired'`: your TTL lapsed without finishing — re-claim if
|
|
400
|
-
* you still need it.
|
|
401
|
-
*/
|
|
402
|
-
readonly reason: 'expired' | 'preempted';
|
|
403
|
-
/** The target you no longer hold. */
|
|
404
|
-
readonly target: {
|
|
405
|
-
readonly entityType: string;
|
|
406
|
-
readonly entityId: string;
|
|
407
|
-
readonly path?: string;
|
|
408
|
-
readonly range?: TargetRange;
|
|
409
|
-
readonly field?: string;
|
|
410
|
-
readonly meta?: Record<string, unknown>;
|
|
411
|
-
};
|
|
412
|
-
}
|
|
413
387
|
/**
|
|
414
388
|
* Every lifecycle state of a claim. `active` is the current holder — the lock
|
|
415
389
|
* itself. `queued` is waiting in line behind the holder and carries a
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/** Machine-readable reasons a live delta stream must resume via catch-up. */
|
|
3
|
+
export declare const bootstrapReasonSchema: z.ZodEnum<{
|
|
4
|
+
too_far_behind: "too_far_behind";
|
|
5
|
+
too_many_deltas: "too_many_deltas";
|
|
6
|
+
missing_entities: "missing_entities";
|
|
7
|
+
stream_gap: "stream_gap";
|
|
8
|
+
}>;
|
|
9
|
+
export type BootstrapReason = z.infer<typeof bootstrapReasonSchema>;
|
package/dist/wire/frames.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The write-path message shapes for the sync protocol. These cover the frames
|
|
3
|
-
* a client sends to commit work — {@link CommitMessage}
|
|
4
|
-
* operations
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* a client sends to commit work — {@link CommitMessage}, a batch of raw
|
|
4
|
+
* operations — and the server's {@link MutationResultMessage}
|
|
5
|
+
* acknowledgement. The same frames flow over a WebSocket connection and over
|
|
6
|
+
* the HTTP commit endpoint.
|
|
7
7
|
*
|
|
8
8
|
* Both the client and the server import these definitions from here, so the two
|
|
9
9
|
* sides cannot drift. Each interface is paired with a Zod validator
|
|
@@ -85,25 +85,10 @@ export declare const commitOperationSchema: z.ZodObject<{
|
|
|
85
85
|
transactionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
86
86
|
bypass: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
87
87
|
}, z.core.$strip>;
|
|
88
|
-
/**
|
|
89
|
-
* A client-to-server frame that invokes a single named mutation by name and
|
|
90
|
-
* arguments, as opposed to the raw operation batch in {@link CommitMessage}.
|
|
91
|
-
* The server resolves `mutatorName` against the set of mutations registered on
|
|
92
|
-
* it and runs the matching one.
|
|
93
|
-
*/
|
|
94
|
-
export interface MutationMessage {
|
|
95
|
-
type: 'mutation';
|
|
96
|
-
payload: {
|
|
97
|
-
mutatorName: string;
|
|
98
|
-
input: unknown;
|
|
99
|
-
clientTxId: string;
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
88
|
/**
|
|
103
89
|
* A client-to-server frame that asks the server to commit a batch of operations
|
|
104
|
-
* atomically.
|
|
105
|
-
*
|
|
106
|
-
* below.
|
|
90
|
+
* atomically. It carries a list of {@link CommitOperation} entries plus the
|
|
91
|
+
* batch metadata below.
|
|
107
92
|
*/
|
|
108
93
|
export interface CommitMessage {
|
|
109
94
|
type: 'commit';
|
package/dist/wire/frames.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The write-path message shapes for the sync protocol. These cover the frames
|
|
3
|
-
* a client sends to commit work — {@link CommitMessage}
|
|
4
|
-
* operations
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* a client sends to commit work — {@link CommitMessage}, a batch of raw
|
|
4
|
+
* operations — and the server's {@link MutationResultMessage}
|
|
5
|
+
* acknowledgement. The same frames flow over a WebSocket connection and over
|
|
6
|
+
* the HTTP commit endpoint.
|
|
7
7
|
*
|
|
8
8
|
* Both the client and the server import these definitions from here, so the two
|
|
9
9
|
* sides cannot drift. Each interface is paired with a Zod validator
|
package/dist/wire/index.d.ts
CHANGED
|
@@ -19,9 +19,12 @@ export { errorEnvelope, statusForType } from './errorEnvelope.js';
|
|
|
19
19
|
export type { ErrorEnvelope } from './errorEnvelope.js';
|
|
20
20
|
export { listEnvelope } from './listEnvelope.js';
|
|
21
21
|
export type { ListEnvelope } from './listEnvelope.js';
|
|
22
|
-
export {
|
|
23
|
-
export {
|
|
24
|
-
export
|
|
22
|
+
export { bootstrapReasonSchema } from './bootstrapReason.js';
|
|
23
|
+
export type { BootstrapReason } from './bootstrapReason.js';
|
|
24
|
+
export { commitOperationSchema, commitPayloadSchema, } from './frames.js';
|
|
25
|
+
export { PROTOCOL_VERSION, MIN_SUPPORTED_PROTOCOL_VERSION, DEFAULT_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS, WS_CLOSE_PROTOCOL_VERSION, PROTOCOL_VERSION_HEADER, protocolVersionProblem, resolveProtocolVersion, } from './protocolVersion.js';
|
|
26
|
+
export type { SupportedProtocolVersion, ProtocolVersionProblem, } from './protocolVersion.js';
|
|
27
|
+
export type { CommitOperation, CommitMessage, MutationResultMessage, } from './frames.js';
|
|
25
28
|
export { participantKindSchema, confirmationStateSchema, syncDeltaActionSchema, wireDeltaDataSchema, participantRefSchema, syncDeltaWireCoreSchema, clientSyncDeltaSchema, serverSyncDeltaSchema, } from './delta.js';
|
|
26
29
|
export type { ParticipantKind, ConfirmationState, SyncDeltaAction, WireDeltaData, ParticipantRef, SyncDeltaWireCore, ClientSyncDelta, ServerSyncDelta, } from './delta.js';
|
|
27
30
|
export { AbloError, AbloAuthenticationError, AbloPermissionError, AbloValidationError, AbloRateLimitError, AbloIdempotencyError, AbloConnectionError, AbloServerError, AbloStaleContextError, AbloClaimedError, CapabilityError, SyncSessionError, docUrlForCode, translateHttpError, errorFromWire, toAbloError, ERROR_CONTRACT_VERSION, errorCodeSpec, } from '../errors.js';
|
package/dist/wire/index.js
CHANGED
|
@@ -17,15 +17,16 @@
|
|
|
17
17
|
*/
|
|
18
18
|
export { errorEnvelope, statusForType } from './errorEnvelope.js';
|
|
19
19
|
export { listEnvelope } from './listEnvelope.js';
|
|
20
|
+
export { bootstrapReasonSchema } from './bootstrapReason.js';
|
|
20
21
|
// The write-path frame contract: the message shapes shared by the client and
|
|
21
22
|
// the server. The runtime Zod validators sit beside the interfaces and are
|
|
22
23
|
// pinned to them, and they gate every operation and payload on both commit
|
|
23
24
|
// transports.
|
|
24
|
-
export { commitOperationSchema, commitPayloadSchema } from './frames.js';
|
|
25
|
+
export { commitOperationSchema, commitPayloadSchema, } from './frames.js';
|
|
25
26
|
// Protocol versioning: the single integer the client and server compare to
|
|
26
27
|
// confirm they can speak to each other, plus the WebSocket close code used to
|
|
27
28
|
// reject a mismatch. See protocolVersion.ts for the changelog and deploy rules.
|
|
28
|
-
export { PROTOCOL_VERSION, MIN_SUPPORTED_PROTOCOL_VERSION, WS_CLOSE_PROTOCOL_VERSION, PROTOCOL_VERSION_HEADER, protocolVersionProblem, } from './protocolVersion.js';
|
|
29
|
+
export { PROTOCOL_VERSION, MIN_SUPPORTED_PROTOCOL_VERSION, DEFAULT_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS, WS_CLOSE_PROTOCOL_VERSION, PROTOCOL_VERSION_HEADER, protocolVersionProblem, resolveProtocolVersion, } from './protocolVersion.js';
|
|
29
30
|
// The read-path delta contract: the shape the server broadcasts to clients as the
|
|
30
31
|
// payload of a `delta` or `sync_response` frame, together with the shared
|
|
31
32
|
// participant vocabulary it carries. Both ends derive their delta type from these
|