@hops-ops/distributed 0.0.0 → 4.0.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 (78) hide show
  1. package/README.md +26 -13
  2. package/dist/auth-headers.js +3 -3
  3. package/dist/internal/cache-engine/engine.d.ts +3 -1
  4. package/dist/internal/cache-engine/engine.js +137 -22
  5. package/dist/internal/cache-engine/errors.d.ts +4 -0
  6. package/dist/internal/cache-engine/errors.js +8 -0
  7. package/dist/internal/cache-engine/index.d.ts +2 -2
  8. package/dist/internal/cache-engine/index.js +1 -1
  9. package/dist/internal/cache-engine/types.d.ts +16 -0
  10. package/dist/internal/cache-engine.d.ts +2 -2
  11. package/dist/internal/cache-engine.js +1 -1
  12. package/dist/protocol.d.ts +9 -2
  13. package/dist/protocol.js +35 -7
  14. package/dist/replica/command-runtime/create.js +499 -57
  15. package/dist/replica/command-runtime/index.d.ts +1 -1
  16. package/dist/replica/command-runtime/index.js +1 -1
  17. package/dist/replica/command-runtime/lib/effects.d.ts +22 -9
  18. package/dist/replica/command-runtime/lib/effects.js +118 -20
  19. package/dist/replica/command-runtime/lib/projection.d.ts +2 -2
  20. package/dist/replica/command-runtime/lib/projection.js +14 -6
  21. package/dist/replica/command-runtime/lib/status.js +18 -10
  22. package/dist/replica/command-runtime/lib/transport.js +4 -0
  23. package/dist/replica/command-runtime/lib/util.js +8 -3
  24. package/dist/replica/command-runtime/symbols.d.ts +9 -0
  25. package/dist/replica/command-runtime/symbols.js +9 -0
  26. package/dist/replica/command-runtime/types.d.ts +15 -6
  27. package/dist/replica/command-runtime/types.js +1 -1
  28. package/dist/replica/command-runtime.d.ts +1 -1
  29. package/dist/replica/command-runtime.js +1 -1
  30. package/dist/replica/commands/clone.js +2 -1
  31. package/dist/replica/commands/index.d.ts +1 -0
  32. package/dist/replica/commands/prepare.js +7 -4
  33. package/dist/replica/commands/receipt.js +30 -16
  34. package/dist/replica/commands/types.d.ts +10 -5
  35. package/dist/replica/commands/validate.d.ts +5 -1
  36. package/dist/replica/commands/validate.js +129 -62
  37. package/dist/replica/diagnostics/inspect.js +51 -19
  38. package/dist/replica/diagnostics/types.d.ts +7 -7
  39. package/dist/replica/distributed-replica/helpers.js +22 -8
  40. package/dist/replica/distributed-replica/hydration.js +14 -2
  41. package/dist/replica/distributed-replica/impl-diagnostics.d.ts +1 -1
  42. package/dist/replica/distributed-replica/impl-diagnostics.js +4 -3
  43. package/dist/replica/distributed-replica/impl-hydration-orchestrate.d.ts +1 -1
  44. package/dist/replica/distributed-replica/impl-hydration-orchestrate.js +76 -42
  45. package/dist/replica/distributed-replica/impl-optimistic.d.ts +11 -2
  46. package/dist/replica/distributed-replica/impl-optimistic.js +22 -6
  47. package/dist/replica/distributed-replica/impl-protocol.js +4 -2
  48. package/dist/replica/distributed-replica/impl.d.ts +8 -2
  49. package/dist/replica/distributed-replica/impl.js +22 -5
  50. package/dist/replica/distributed-replica/optimistic.js +31 -3
  51. package/dist/replica/distributed-replica/types.d.ts +1 -0
  52. package/dist/replica/index-maintenance/engine.js +14 -6
  53. package/dist/replica/index.d.ts +4 -0
  54. package/dist/replica/index.js +2 -0
  55. package/dist/replica/materialize.js +25 -0
  56. package/dist/replica/mutation-cache.d.ts +62 -0
  57. package/dist/replica/mutation-cache.js +91 -0
  58. package/dist/replica/operation-binding.js +17 -6
  59. package/dist/replica/persistence/state.js +9 -1
  60. package/dist/replica/projection-delta/canonical.d.ts +6 -0
  61. package/dist/replica/projection-delta/canonical.js +29 -0
  62. package/dist/replica/projection-delta/index.d.ts +5 -0
  63. package/dist/replica/projection-delta/index.js +3 -0
  64. package/dist/replica/projection-delta/resolve.d.ts +8 -0
  65. package/dist/replica/projection-delta/resolve.js +266 -0
  66. package/dist/replica/projection-delta/types.d.ts +415 -0
  67. package/dist/replica/projection-delta/types.js +6 -0
  68. package/dist/replica/projection-delta/validate.d.ts +18 -0
  69. package/dist/replica/projection-delta/validate.js +1718 -0
  70. package/dist/replica/projection-delta/wasm-pure.d.ts +36 -0
  71. package/dist/replica/projection-delta/wasm-pure.js +71 -0
  72. package/dist/replica/query-plan/pagination.js +18 -8
  73. package/dist/replica/types.d.ts +13 -2
  74. package/dist/sveltekit/replica.d.ts +1 -1
  75. package/dist/sveltekit/replica.js +3 -0
  76. package/dist/sveltekit/vite.d.ts +6 -6
  77. package/dist/sveltekit/vite.js +6 -6
  78. package/package.json +1 -1
@@ -0,0 +1,1718 @@
1
+ import { isPlainRecord } from '../../lib/is-plain-record.js';
2
+ import { canonicalCommandProjectionMetadata, canonicalProjectionDelta, commandProjectionMetadataByteLength, projectionDeltaByteLength } from './canonical.js';
3
+ import { COMMAND_PROJECTION_ARTIFACT_VERSION, COMMAND_PROJECTION_METADATA_WIRE_VERSION, PROJECTION_DELTA_WIRE_VERSION, PROJECTION_OPERATION_SEMANTICS_VERSION, PROJECTION_PROGRAM_IR_VERSION, PROJECTION_PROGRAM_VERSION } from './types.js';
4
+ const MAX_ITEMS = 128;
5
+ const MAX_DEPTH = 64;
6
+ const MAX_BODY_BYTES = 1024 * 1024;
7
+ const MAX_TRAVERSAL_NODES = MAX_BODY_BYTES;
8
+ const MAX_TRAVERSAL_DEPTH = MAX_DEPTH + 64;
9
+ const MAX_KEY_BYTES = 4 * 1024;
10
+ const MAX_PARTITION_BYTES = 4 * 1024;
11
+ const MAX_IDENTITY_BYTES = 4 * 1024;
12
+ const PROGRAM_ID = /^pp1:sha256:[0-9a-f]{64}$/;
13
+ const BINDING_ID = /^pb1:sha256:[0-9a-f]{64}$/;
14
+ const SHA256 = /^sha256:[0-9a-f]{64}$/;
15
+ const U64_MAX = 18446744073709551615n;
16
+ const I64_MIN = -9223372036854775808n;
17
+ const I64_MAX = 9223372036854775807n;
18
+ const encoder = new TextEncoder();
19
+ export class ProjectionDeltaValidationError extends TypeError {
20
+ path;
21
+ constructor(path) {
22
+ super(`Invalid projection delta at ${path}`);
23
+ this.name = 'ProjectionDeltaValidationError';
24
+ this.path = path;
25
+ }
26
+ }
27
+ export function parseProjectionDelta(value) {
28
+ preflightProjectionPayload(value, 'projection.delta');
29
+ const delta = exactRecord(value, ['wire_version', 'identity', 'projections', 'occurrences', 'operations'], ['recoveries'], 'projection.delta');
30
+ if (delta.wire_version !== PROJECTION_DELTA_WIRE_VERSION) {
31
+ invalid('projection.delta.wire_version');
32
+ }
33
+ const identityValue = exactRecord(delta.identity, [
34
+ 'manifest_version',
35
+ 'client_protocol_version',
36
+ 'surface',
37
+ 'schema_fingerprint',
38
+ 'protocol_fingerprint',
39
+ 'authorization_generation',
40
+ 'cache_scope_token',
41
+ 'command_causation_id'
42
+ ], [], 'projection.delta.identity');
43
+ if (identityValue.manifest_version !== 2 ||
44
+ identityValue.client_protocol_version !== 1) {
45
+ invalid('projection.delta.identity');
46
+ }
47
+ const identity = Object.freeze({
48
+ manifest_version: 2,
49
+ client_protocol_version: 1,
50
+ surface: parseSurface(identityValue.surface, 'projection.delta.identity.surface'),
51
+ schema_fingerprint: identityString(identityValue.schema_fingerprint, 'projection.delta.identity.schema_fingerprint'),
52
+ protocol_fingerprint: identityString(identityValue.protocol_fingerprint, 'projection.delta.identity.protocol_fingerprint'),
53
+ authorization_generation: identityString(identityValue.authorization_generation, 'projection.delta.identity.authorization_generation'),
54
+ cache_scope_token: protocolToken(identityValue.cache_scope_token, 'cache-scope', 'projection.delta.identity.cache_scope_token'),
55
+ command_causation_id: identityString(identityValue.command_causation_id, 'projection.delta.identity.command_causation_id')
56
+ });
57
+ const projections = boundedArray(delta.projections, 'projection.delta.projections').map((item, index) => parseProjectionIdentity(item, `projection.delta.projections[${index}]`));
58
+ assertStrictOrder(projections, compareProjectionIdentity, 'projection.delta.projections');
59
+ const occurrences = boundedArray(delta.occurrences, 'projection.delta.occurrences').map((item, index) => {
60
+ const path = `projection.delta.occurrences[${index}]`;
61
+ const occurrence = exactRecord(item, ['causation_id', 'ordinal', 'occurrence_id'], [], path);
62
+ const causationId = identityString(occurrence.causation_id, `${path}.causation_id`);
63
+ const ordinal = boundedOrdinal(occurrence.ordinal, `${path}.ordinal`);
64
+ const occurrenceId = identityString(occurrence.occurrence_id, `${path}.occurrence_id`);
65
+ if (ordinal !== index || causationId !== identity.command_causation_id) {
66
+ invalid(path);
67
+ }
68
+ return Object.freeze({
69
+ causation_id: causationId,
70
+ ordinal,
71
+ occurrence_id: occurrenceId
72
+ });
73
+ });
74
+ assertUnique(occurrences.map(({ occurrence_id }) => occurrence_id), 'projection.delta.occurrences');
75
+ const operations = boundedArray(delta.operations, 'projection.delta.operations').map((item, index) => {
76
+ const path = `projection.delta.operations[${index}]`;
77
+ const operation = exactRecord(item, ['occurrence_ordinal', 'projection_refs', 'mutation'], [], path);
78
+ const occurrenceOrdinal = boundedOrdinal(operation.occurrence_ordinal, `${path}.occurrence_ordinal`);
79
+ if (occurrenceOrdinal >= occurrences.length) {
80
+ invalid(`${path}.occurrence_ordinal`);
81
+ }
82
+ return Object.freeze({
83
+ occurrence_ordinal: occurrenceOrdinal,
84
+ projection_refs: projectionRefs(operation.projection_refs, projections.length, `${path}.projection_refs`),
85
+ mutation: parseMutation(operation.mutation, `${path}.mutation`)
86
+ });
87
+ });
88
+ assertStrictOrder(operations, (left, right) => compareTuple([operationScopeKey(left.mutation), left.occurrence_ordinal], [operationScopeKey(right.mutation), right.occurrence_ordinal]), 'projection.delta.operations');
89
+ assertUnique(operations.map(({ mutation }) => JSON.stringify(operationScopeKey(mutation))), 'projection.delta.operations');
90
+ const recoveries = boundedArray(delta.recoveries ?? [], 'projection.delta.recoveries').map((item, index) => parseRecovery(item, index, projections.length, occurrences.length));
91
+ assertStrictOrder(recoveries, (left, right) => compareTuple([recoveryTargetKey(left.target), left.occurrence_ordinal], [recoveryTargetKey(right.target), right.occurrence_ordinal]), 'projection.delta.recoveries');
92
+ assertUnique(recoveries.map(({ target }) => JSON.stringify(recoveryTargetKey(target))), 'projection.delta.recoveries');
93
+ for (const recovery of recoveries) {
94
+ if (recovery.condition !== 'if_record_missing')
95
+ continue;
96
+ if (recovery.target.kind !== 'record' ||
97
+ !operations.some(({ mutation }) => mutation.op === 'patch' &&
98
+ compareTuple(scopeKey(mutation.scope), scopeKey(recovery.target.kind === 'record'
99
+ ? recovery.target.scope
100
+ : mutation.scope)) === 0)) {
101
+ invalid('projection.delta.recoveries');
102
+ }
103
+ }
104
+ if (projections.length === 0 &&
105
+ (occurrences.length !== 0 ||
106
+ operations.length !== 0 ||
107
+ recoveries.length !== 0)) {
108
+ invalid('projection.delta.projections');
109
+ }
110
+ const parsed = Object.freeze({
111
+ wire_version: 1,
112
+ identity,
113
+ projections: Object.freeze(projections),
114
+ occurrences: Object.freeze(occurrences),
115
+ operations: Object.freeze(operations),
116
+ recoveries: Object.freeze(recoveries)
117
+ });
118
+ if (projectionDeltaByteLength(parsed) > MAX_BODY_BYTES) {
119
+ invalid('projection.delta');
120
+ }
121
+ return parsed;
122
+ }
123
+ export function parseCommandProjectionMetadata(value) {
124
+ preflightProjectionPayload(value, 'projection');
125
+ const metadata = exactRecord(value, [
126
+ 'wireVersion',
127
+ 'issuedAtUnixMs',
128
+ 'expiresAtUnixMs',
129
+ 'delta',
130
+ 'lifecycleProofs',
131
+ 'obligations',
132
+ 'revalidate'
133
+ ], [], 'projection');
134
+ if (metadata.wireVersion !== COMMAND_PROJECTION_METADATA_WIRE_VERSION ||
135
+ typeof metadata.revalidate !== 'boolean') {
136
+ invalid('projection');
137
+ }
138
+ const issuedAtUnixMs = safeUnsignedInteger(metadata.issuedAtUnixMs, 'projection.issuedAtUnixMs');
139
+ const expiresAtUnixMs = safeUnsignedInteger(metadata.expiresAtUnixMs, 'projection.expiresAtUnixMs');
140
+ if (issuedAtUnixMs >= expiresAtUnixMs)
141
+ invalid('projection.expiresAtUnixMs');
142
+ const delta = parseProjectionDelta(metadata.delta);
143
+ const lifecycleProofs = boundedArray(metadata.lifecycleProofs, 'projection.lifecycleProofs').map((item, index) => {
144
+ const path = `projection.lifecycleProofs[${index}]`;
145
+ const proof = exactRecord(item, ['projectionRef', 'token'], [], path);
146
+ const projectionRef = boundedOrdinal(proof.projectionRef, `${path}.projectionRef`);
147
+ if (projectionRef !== index || projectionRef >= delta.projections.length) {
148
+ invalid(path);
149
+ }
150
+ return Object.freeze({
151
+ projectionRef,
152
+ token: protocolToken(proof.token, 'projection-lifecycle', `${path}.token`)
153
+ });
154
+ });
155
+ if (lifecycleProofs.length !== delta.projections.length) {
156
+ invalid('projection.lifecycleProofs');
157
+ }
158
+ const obligations = boundedArray(metadata.obligations, 'projection.obligations').map((item, index) => parseObligation(item, index, delta));
159
+ assertStrictOrder(obligations, (left, right) => compareTuple([left.projectionRef, left.model, left.scopeToken], [right.projectionRef, right.model, right.scopeToken]), 'projection.obligations');
160
+ const scopeModels = new Map();
161
+ for (const obligation of obligations) {
162
+ const key = JSON.stringify([
163
+ obligation.projectionRef,
164
+ obligation.scopeToken
165
+ ]);
166
+ const previous = scopeModels.get(key);
167
+ if (previous !== undefined && previous !== obligation.model) {
168
+ invalid('projection.obligations');
169
+ }
170
+ scopeModels.set(key, obligation.model);
171
+ }
172
+ if (!metadata.revalidate && delta.recoveries.length !== 0) {
173
+ invalid('projection.revalidate');
174
+ }
175
+ const parsed = Object.freeze({
176
+ wireVersion: 1,
177
+ issuedAtUnixMs,
178
+ expiresAtUnixMs,
179
+ delta,
180
+ lifecycleProofs: Object.freeze(lifecycleProofs),
181
+ obligations: Object.freeze(obligations),
182
+ revalidate: metadata.revalidate
183
+ });
184
+ if (commandProjectionMetadataByteLength(parsed) > MAX_BODY_BYTES) {
185
+ invalid('projection');
186
+ }
187
+ return parsed;
188
+ }
189
+ export function validateCommandProjectionArtifact(value, path = 'artifact.projection') {
190
+ preflightProjectionPayload(value, path);
191
+ const projection = exactRecord(value, [
192
+ 'version',
193
+ 'deltaWireVersion',
194
+ 'projectionProgramVersion',
195
+ 'operationSemanticsVersion',
196
+ 'projections',
197
+ 'eventSet',
198
+ 'capabilities',
199
+ 'preview',
200
+ 'fallback'
201
+ ], ['pureReduces'], path);
202
+ if (projection.version !== COMMAND_PROJECTION_ARTIFACT_VERSION ||
203
+ projection.deltaWireVersion !== PROJECTION_DELTA_WIRE_VERSION ||
204
+ projection.projectionProgramVersion !== PROJECTION_PROGRAM_VERSION ||
205
+ projection.operationSemanticsVersion !==
206
+ PROJECTION_OPERATION_SEMANTICS_VERSION ||
207
+ projection.fallback !== 'revalidate') {
208
+ invalid(path);
209
+ }
210
+ const projections = boundedArray(projection.projections, `${path}.projections`).map((item, index) => {
211
+ const itemPath = `${path}.projections[${index}]`;
212
+ const identity = exactRecord(item, [
213
+ 'programId',
214
+ 'bindingId',
215
+ 'epoch',
216
+ 'programIrVersion',
217
+ 'operationSemanticsVersion'
218
+ ], [], itemPath);
219
+ if (!PROGRAM_ID.test(identity.programId) ||
220
+ !BINDING_ID.test(identity.bindingId) ||
221
+ identity.programIrVersion !== PROJECTION_PROGRAM_IR_VERSION ||
222
+ identity.operationSemanticsVersion !==
223
+ PROJECTION_OPERATION_SEMANTICS_VERSION) {
224
+ invalid(itemPath);
225
+ }
226
+ return Object.freeze({
227
+ programId: identityString(identity.programId, `${itemPath}.programId`),
228
+ bindingId: identityString(identity.bindingId, `${itemPath}.bindingId`),
229
+ epoch: projectionEpoch(identity.epoch, `${itemPath}.epoch`),
230
+ programIrVersion: 1,
231
+ operationSemanticsVersion: 1
232
+ });
233
+ });
234
+ assertStrictOrder(projections, (left, right) => compareTuple([
235
+ left.programId,
236
+ left.bindingId,
237
+ left.epoch,
238
+ left.programIrVersion,
239
+ left.operationSemanticsVersion
240
+ ], [
241
+ right.programId,
242
+ right.bindingId,
243
+ right.epoch,
244
+ right.programIrVersion,
245
+ right.operationSemanticsVersion
246
+ ]), `${path}.projections`);
247
+ const eventSet = boundedArray(projection.eventSet, `${path}.eventSet`).map((item, index) => parseEventRef(item, `${path}.eventSet[${index}]`));
248
+ assertStrictOrder(eventSet, (left, right) => compareTuple([left.id, left.name, left.version], [right.id, right.name, right.version]), `${path}.eventSet`);
249
+ const capabilitiesValue = exactRecord(projection.capabilities, ['version', 'arms'], [], `${path}.capabilities`);
250
+ if (capabilitiesValue.version !== 1) {
251
+ invalid(`${path}.capabilities.version`);
252
+ }
253
+ const capabilityArms = boundedArray(capabilitiesValue.arms, `${path}.capabilities.arms`).map((item, index) => parseCapabilityArm(item, index, projections.length, eventSet, `${path}.capabilities.arms[${index}]`));
254
+ if (capabilityArms.length === 0 ||
255
+ eventSet.some((event) => !capabilityArms.some((arm) => sameEventRef(arm.event, event)))) {
256
+ invalid(`${path}.capabilities.arms`);
257
+ }
258
+ assertStrictOrder(capabilityArms, (left, right) => compareTuple([
259
+ left.event.id,
260
+ left.event.name,
261
+ left.event.version,
262
+ left.projection_ref,
263
+ left.arm
264
+ ], [
265
+ right.event.id,
266
+ right.event.name,
267
+ right.event.version,
268
+ right.projection_ref,
269
+ right.arm
270
+ ]), `${path}.capabilities.arms`);
271
+ const previewValue = exactRecord(projection.preview, ['version', 'occurrences', 'operations', 'recoveries'], [], `${path}.preview`);
272
+ if (previewValue.version !== 1)
273
+ invalid(`${path}.preview.version`);
274
+ const occurrences = boundedArray(previewValue.occurrences, `${path}.preview.occurrences`).map((item, index) => {
275
+ const itemPath = `${path}.preview.occurrences[${index}]`;
276
+ const occurrence = exactRecord(item, ['ordinal', 'event'], [], itemPath);
277
+ const ordinal = boundedOrdinal(occurrence.ordinal, `${itemPath}.ordinal`);
278
+ const event = parseEventRef(occurrence.event, `${itemPath}.event`);
279
+ if (ordinal !== index ||
280
+ !eventSet.some((candidate) => candidate.id === event.id &&
281
+ candidate.name === event.name &&
282
+ candidate.version === event.version)) {
283
+ invalid(itemPath);
284
+ }
285
+ return Object.freeze({ ordinal, event });
286
+ });
287
+ const operations = boundedArray(previewValue.operations, `${path}.preview.operations`).map((item, index) => {
288
+ const itemPath = `${path}.preview.operations[${index}]`;
289
+ const operation = exactRecord(item, ['occurrence_ordinal', 'projection_refs', 'mutation'], [], itemPath);
290
+ const occurrenceOrdinal = boundedOrdinal(operation.occurrence_ordinal, `${itemPath}.occurrence_ordinal`);
291
+ if (occurrenceOrdinal >= occurrences.length)
292
+ invalid(itemPath);
293
+ return Object.freeze({
294
+ occurrence_ordinal: occurrenceOrdinal,
295
+ projection_refs: projectionRefs(operation.projection_refs, projections.length, `${itemPath}.projection_refs`),
296
+ mutation: parsePreviewMutation(operation.mutation, `${itemPath}.mutation`)
297
+ });
298
+ });
299
+ assertStrictOrder(operations, (left, right) => compareTuple([
300
+ previewOperationScopeKey(left.mutation),
301
+ left.occurrence_ordinal
302
+ ], [
303
+ previewOperationScopeKey(right.mutation),
304
+ right.occurrence_ordinal
305
+ ]), `${path}.preview.operations`);
306
+ assertUnique(operations.map(({ mutation }) => JSON.stringify(previewOperationScopeKey(mutation))), `${path}.preview.operations`);
307
+ const recoveries = boundedArray(previewValue.recoveries, `${path}.preview.recoveries`).map((item, index) => {
308
+ const itemPath = `${path}.preview.recoveries[${index}]`;
309
+ const recovery = exactRecord(item, ['occurrence_ordinal', 'projection_refs', 'condition', 'target'], [], itemPath);
310
+ const occurrenceOrdinal = boundedOrdinal(recovery.occurrence_ordinal, `${itemPath}.occurrence_ordinal`);
311
+ if (occurrenceOrdinal >= occurrences.length ||
312
+ (recovery.condition !== 'always' &&
313
+ recovery.condition !== 'if_record_missing')) {
314
+ invalid(itemPath);
315
+ }
316
+ return Object.freeze({
317
+ occurrence_ordinal: occurrenceOrdinal,
318
+ projection_refs: projectionRefs(recovery.projection_refs, projections.length, `${itemPath}.projection_refs`),
319
+ condition: recovery.condition,
320
+ target: parsePreviewRecoveryTarget(recovery.target, `${itemPath}.target`)
321
+ });
322
+ });
323
+ assertStrictOrder(recoveries, (left, right) => compareTuple([
324
+ previewRecoveryTargetKey(left.target),
325
+ left.occurrence_ordinal
326
+ ], [
327
+ previewRecoveryTargetKey(right.target),
328
+ right.occurrence_ordinal
329
+ ]), `${path}.preview.recoveries`);
330
+ assertUnique(recoveries.map(({ target }) => JSON.stringify(previewRecoveryTargetKey(target))), `${path}.preview.recoveries`);
331
+ for (const recovery of recoveries) {
332
+ if (recovery.condition === 'if_record_missing' &&
333
+ (recovery.target.kind !== 'record' ||
334
+ !operations.some(({ mutation }) => mutation.op === 'patch' &&
335
+ compareTuple(previewScopeKey(mutation.scope), previewScopeKey(recovery.target.kind === 'record'
336
+ ? recovery.target.scope
337
+ : mutation.scope)) === 0))) {
338
+ invalid(`${path}.preview.recoveries`);
339
+ }
340
+ }
341
+ const pureReduces = parsePureReduces(projection.pureReduces, occurrences.length, projections.length, `${path}.pureReduces`);
342
+ const parsed = Object.freeze({
343
+ version: 2,
344
+ deltaWireVersion: 1,
345
+ projectionProgramVersion: 2,
346
+ operationSemanticsVersion: 1,
347
+ projections: Object.freeze(projections),
348
+ eventSet: Object.freeze(eventSet),
349
+ capabilities: Object.freeze({
350
+ version: 1,
351
+ arms: Object.freeze(capabilityArms)
352
+ }),
353
+ preview: Object.freeze({
354
+ version: 1,
355
+ occurrences: Object.freeze(occurrences),
356
+ operations: Object.freeze(operations),
357
+ recoveries: Object.freeze(recoveries)
358
+ }),
359
+ ...(pureReduces.length === 0 ? {} : { pureReduces }),
360
+ fallback: 'revalidate'
361
+ });
362
+ if (encoder.encode(JSON.stringify(parsed)).byteLength > MAX_BODY_BYTES) {
363
+ invalid(path);
364
+ }
365
+ return parsed;
366
+ }
367
+ function parsePureReduces(value, occurrenceCount, projectionCount, path) {
368
+ if (value === undefined)
369
+ return Object.freeze([]);
370
+ return Object.freeze(boundedArray(value, path).map((item, index) => {
371
+ const itemPath = `${path}[${index}]`;
372
+ const reduce = exactRecord(item, [
373
+ 'fn',
374
+ 'scope',
375
+ 'args',
376
+ 'assign',
377
+ 'occurrence_ordinal',
378
+ 'projection_refs'
379
+ ], ['clientModule', 'clientExport'], itemPath);
380
+ const fn = reduce.fn;
381
+ if (typeof fn !== 'string' || fn.length === 0 || fn.length > 128) {
382
+ invalid(`${itemPath}.fn`);
383
+ }
384
+ const occurrenceOrdinal = boundedOrdinal(reduce.occurrence_ordinal, `${itemPath}.occurrence_ordinal`);
385
+ if (occurrenceOrdinal >= occurrenceCount)
386
+ invalid(itemPath);
387
+ const assign = boundedArray(reduce.assign, `${itemPath}.assign`).map((field, fieldIndex) => {
388
+ if (typeof field !== 'string' || field.length === 0) {
389
+ invalid(`${itemPath}.assign[${fieldIndex}]`);
390
+ }
391
+ return field;
392
+ });
393
+ if (assign.length === 0)
394
+ invalid(`${itemPath}.assign`);
395
+ const args = boundedArray(reduce.args, `${itemPath}.args`).map((arg, argIndex) => {
396
+ const argPath = `${itemPath}.args[${argIndex}]`;
397
+ const entry = exactRecord(arg, ['name', 'value'], [], argPath);
398
+ if (typeof entry.name !== 'string' || entry.name.length === 0) {
399
+ invalid(`${argPath}.name`);
400
+ }
401
+ return Object.freeze({
402
+ name: entry.name,
403
+ value: parsePreviewValue(entry.value, `${argPath}.value`, 0)
404
+ });
405
+ });
406
+ const clientModule = typeof reduce.clientModule === 'string'
407
+ ? reduce.clientModule
408
+ : undefined;
409
+ const clientExport = typeof reduce.clientExport === 'string'
410
+ ? reduce.clientExport
411
+ : undefined;
412
+ return Object.freeze({
413
+ fn: fn,
414
+ ...(clientModule === undefined ? {} : { clientModule }),
415
+ ...(clientExport === undefined ? {} : { clientExport }),
416
+ scope: parsePreviewScope(reduce.scope, `${itemPath}.scope`),
417
+ args: Object.freeze(args),
418
+ assign: Object.freeze(assign),
419
+ occurrence_ordinal: occurrenceOrdinal,
420
+ projection_refs: projectionRefs(reduce.projection_refs, projectionCount, `${itemPath}.projection_refs`)
421
+ });
422
+ }));
423
+ }
424
+ export function validateProjectionMetadataAuthority(metadata, contract, authority, nowUnixMs = Date.now()) {
425
+ const identity = metadata.delta.identity;
426
+ if (nowUnixMs >= metadata.expiresAtUnixMs ||
427
+ identity.surface.kind !== authority.surface.kind ||
428
+ identity.surface.name !== authority.surface.name ||
429
+ (identity.surface.kind === 'application' &&
430
+ (authority.surface.kind !== 'application' ||
431
+ compareStringArrays(identity.surface.eligible_roles, authority.surface.eligible_roles) !== 0 ||
432
+ compareStringArrays(identity.surface.schema_roles, authority.surface.schema_roles) !== 0)) ||
433
+ identity.schema_fingerprint !== authority.schemaHash ||
434
+ identity.protocol_fingerprint !== authority.protocolHash ||
435
+ identity.authorization_generation !== authority.authorizationGeneration ||
436
+ identity.cache_scope_token !== authority.cacheScope ||
437
+ identity.command_causation_id !== authority.causationId) {
438
+ invalid('projection.delta.identity');
439
+ }
440
+ if (metadata.delta.projections.length !== contract.projections.length) {
441
+ invalid('projection.delta.projections');
442
+ }
443
+ for (let index = 0; index < contract.projections.length; index += 1) {
444
+ const expected = contract.projections[index];
445
+ const actual = metadata.delta.projections[index];
446
+ if (expected.programId !== actual.program_id ||
447
+ expected.bindingId !== actual.binding_id ||
448
+ expected.epoch !== actual.epoch ||
449
+ expected.programIrVersion !== actual.program_ir_version ||
450
+ expected.operationSemanticsVersion !==
451
+ actual.operation_semantics_version) {
452
+ invalid(`projection.delta.projections[${index}]`);
453
+ }
454
+ }
455
+ return canonicalCommandProjectionMetadata(metadata);
456
+ }
457
+ function parseProjectionIdentity(value, path) {
458
+ const identity = exactRecord(value, [
459
+ 'program_id',
460
+ 'binding_id',
461
+ 'epoch',
462
+ 'program_ir_version',
463
+ 'operation_semantics_version'
464
+ ], [], path);
465
+ if (typeof identity.program_id !== 'string' ||
466
+ !PROGRAM_ID.test(identity.program_id) ||
467
+ typeof identity.binding_id !== 'string' ||
468
+ !BINDING_ID.test(identity.binding_id) ||
469
+ identity.program_ir_version !== PROJECTION_PROGRAM_IR_VERSION ||
470
+ identity.operation_semantics_version !==
471
+ PROJECTION_OPERATION_SEMANTICS_VERSION) {
472
+ invalid(path);
473
+ }
474
+ return Object.freeze({
475
+ program_id: identity.program_id,
476
+ binding_id: identity.binding_id,
477
+ epoch: projectionEpoch(identity.epoch, `${path}.epoch`),
478
+ program_ir_version: 1,
479
+ operation_semantics_version: 1
480
+ });
481
+ }
482
+ function parseSurface(value, path) {
483
+ if (!isPlainRecord(value))
484
+ invalid(path);
485
+ if (value.kind === 'role') {
486
+ const role = exactRecord(value, ['kind', 'name'], [], path);
487
+ return Object.freeze({
488
+ kind: 'role',
489
+ name: identityString(role.name, `${path}.name`)
490
+ });
491
+ }
492
+ if (value.kind === 'application') {
493
+ const application = exactRecord(value, ['kind', 'name', 'eligible_roles', 'schema_roles'], [], path);
494
+ const eligibleRoles = boundedArray(application.eligible_roles, `${path}.eligible_roles`).map((role, index) => identityString(role, `${path}.eligible_roles[${index}]`));
495
+ const schemaRoles = boundedArray(application.schema_roles, `${path}.schema_roles`).map((role, index) => identityString(role, `${path}.schema_roles[${index}]`));
496
+ if (eligibleRoles.length === 0)
497
+ invalid(`${path}.eligible_roles`);
498
+ if (schemaRoles.length === 0)
499
+ invalid(`${path}.schema_roles`);
500
+ assertStrictOrder(eligibleRoles, compareUtf8, `${path}.eligible_roles`);
501
+ assertStrictOrder(schemaRoles, compareUtf8, `${path}.schema_roles`);
502
+ if (schemaRoles.some((role) => !eligibleRoles.includes(role))) {
503
+ invalid(`${path}.schema_roles`);
504
+ }
505
+ return Object.freeze({
506
+ kind: 'application',
507
+ name: identityString(application.name, `${path}.name`),
508
+ eligible_roles: Object.freeze(eligibleRoles),
509
+ schema_roles: Object.freeze(schemaRoles)
510
+ });
511
+ }
512
+ invalid(`${path}.kind`);
513
+ }
514
+ function parseMutation(value, path) {
515
+ if (!isPlainRecord(value) || typeof value.op !== 'string') {
516
+ invalid(path);
517
+ }
518
+ switch (value.op) {
519
+ case 'upsert': {
520
+ const mutation = exactRecord(value, ['op', 'scope', 'fields', 'replace'], [], path);
521
+ const fields = parseFields(mutation.fields, `${path}.fields`);
522
+ const replace = sortedNames(mutation.replace, `${path}.replace`);
523
+ if (fields.some(({ field }) => !replace.includes(field)))
524
+ invalid(path);
525
+ return Object.freeze({
526
+ op: 'upsert',
527
+ scope: parseScope(mutation.scope, `${path}.scope`),
528
+ fields,
529
+ replace
530
+ });
531
+ }
532
+ case 'patch': {
533
+ const mutation = exactRecord(value, ['op', 'scope', 'if_present'], ['set', 'unset'], path);
534
+ if (mutation.if_present !== true)
535
+ invalid(`${path}.if_present`);
536
+ const set = parseFields(mutation.set ?? [], `${path}.set`);
537
+ const unset = sortedNames(mutation.unset ?? [], `${path}.unset`);
538
+ if ((set.length === 0 && unset.length === 0) ||
539
+ set.some(({ field }) => unset.includes(field))) {
540
+ invalid(path);
541
+ }
542
+ return Object.freeze({
543
+ op: 'patch',
544
+ scope: parseScope(mutation.scope, `${path}.scope`),
545
+ set,
546
+ unset,
547
+ if_present: true
548
+ });
549
+ }
550
+ case 'delete': {
551
+ const mutation = exactRecord(value, ['op', 'scope'], [], path);
552
+ return Object.freeze({
553
+ op: 'delete',
554
+ scope: parseScope(mutation.scope, `${path}.scope`)
555
+ });
556
+ }
557
+ case 'link':
558
+ case 'unlink': {
559
+ const mutation = exactRecord(value, ['op', 'relationship', 'source', 'target'], [], path);
560
+ return Object.freeze({
561
+ op: value.op,
562
+ relationship: identityString(mutation.relationship, `${path}.relationship`),
563
+ source: parseScope(mutation.source, `${path}.source`),
564
+ target: parseScope(mutation.target, `${path}.target`)
565
+ });
566
+ }
567
+ case 'invalidate_model': {
568
+ const mutation = exactRecord(value, ['op', 'model'], ['partition'], path);
569
+ return Object.freeze({
570
+ op: 'invalidate_model',
571
+ ...(mutation.partition === undefined
572
+ ? {}
573
+ : {
574
+ partition: parsePartition(mutation.partition, `${path}.partition`)
575
+ }),
576
+ model: projectionModel(mutation.model, `${path}.model`)
577
+ });
578
+ }
579
+ case 'invalidate_relationship': {
580
+ const mutation = exactRecord(value, ['op', 'relationship', 'source'], [], path);
581
+ return Object.freeze({
582
+ op: 'invalidate_relationship',
583
+ relationship: identityString(mutation.relationship, `${path}.relationship`),
584
+ source: parseScope(mutation.source, `${path}.source`)
585
+ });
586
+ }
587
+ default:
588
+ invalid(`${path}.op`);
589
+ }
590
+ }
591
+ function parseScope(value, path) {
592
+ const scope = exactRecord(value, ['partition', 'model', 'key'], [], path);
593
+ const key = boundedArray(scope.key, `${path}.key`).map((item, index) => {
594
+ const itemPath = `${path}.key[${index}]`;
595
+ const field = exactRecord(item, ['ordinal', 'field', 'value'], [], itemPath);
596
+ const ordinal = boundedOrdinal(field.ordinal, `${itemPath}.ordinal`);
597
+ const name = identityString(field.field, `${itemPath}.field`);
598
+ const fieldValue = parseValue(field.value, `${itemPath}.value`, 1);
599
+ if (ordinal !== index ||
600
+ fieldValue.type === 'null' ||
601
+ fieldValue.type === 'list' ||
602
+ fieldValue.type === 'object') {
603
+ invalid(itemPath);
604
+ }
605
+ return Object.freeze({ ordinal, field: name, value: fieldValue });
606
+ });
607
+ if (key.length === 0)
608
+ invalid(`${path}.key`);
609
+ assertUnique(key.map(({ field }) => field), `${path}.key`);
610
+ if (encoder.encode(JSON.stringify(key)).byteLength > MAX_KEY_BYTES) {
611
+ invalid(`${path}.key`);
612
+ }
613
+ return Object.freeze({
614
+ partition: parsePartition(scope.partition, `${path}.partition`),
615
+ model: projectionModel(scope.model, `${path}.model`),
616
+ key: Object.freeze(key)
617
+ });
618
+ }
619
+ function parsePartition(value, path) {
620
+ if (!isPlainRecord(value))
621
+ invalid(path);
622
+ let partition;
623
+ if (value.kind === 'unit') {
624
+ exactRecord(value, ['kind'], [], path);
625
+ partition = Object.freeze({ kind: 'unit' });
626
+ }
627
+ else if (value.kind === 'opaque') {
628
+ const opaque = exactRecord(value, ['kind', 'token'], [], path);
629
+ partition = Object.freeze({
630
+ kind: 'opaque',
631
+ token: protocolToken(opaque.token, 'projection-partition', `${path}.token`)
632
+ });
633
+ }
634
+ else {
635
+ invalid(`${path}.kind`);
636
+ }
637
+ if (encoder.encode(JSON.stringify(partition)).byteLength > MAX_PARTITION_BYTES) {
638
+ invalid(path);
639
+ }
640
+ return partition;
641
+ }
642
+ function parseFields(value, path) {
643
+ const fields = boundedArray(value, path).map((item, index) => {
644
+ const itemPath = `${path}[${index}]`;
645
+ const field = exactRecord(item, ['field', 'value'], [], itemPath);
646
+ return Object.freeze({
647
+ field: identityString(field.field, `${itemPath}.field`),
648
+ value: parseValue(field.value, `${itemPath}.value`, 1)
649
+ });
650
+ });
651
+ assertStrictOrder(fields, (left, right) => compareUtf8(left.field, right.field), path);
652
+ return Object.freeze(fields);
653
+ }
654
+ function parseValue(value, path, depth) {
655
+ if (depth > MAX_DEPTH)
656
+ invalid(path);
657
+ if (!isPlainRecord(value) || typeof value.type !== 'string')
658
+ invalid(path);
659
+ switch (value.type) {
660
+ case 'null':
661
+ exactRecord(value, ['type'], [], path);
662
+ return Object.freeze({ type: 'null' });
663
+ case 'boolean': {
664
+ const typed = exactRecord(value, ['type', 'value'], [], path);
665
+ if (typeof typed.value !== 'boolean')
666
+ invalid(`${path}.value`);
667
+ return Object.freeze({ type: 'boolean', value: typed.value });
668
+ }
669
+ case 'i64':
670
+ case 'u64': {
671
+ const typed = exactRecord(value, ['type', 'value'], [], path);
672
+ const decimal = canonicalInteger(typed.value, value.type, `${path}.value`);
673
+ return Object.freeze({ type: value.type, value: decimal });
674
+ }
675
+ case 'f64': {
676
+ const typed = exactRecord(value, ['type', 'value'], [], path);
677
+ const decimal = canonicalFloat(typed.value, `${path}.value`);
678
+ return Object.freeze({ type: 'f64', value: decimal });
679
+ }
680
+ case 'string': {
681
+ const typed = exactRecord(value, ['type', 'value'], [], path);
682
+ if (typeof typed.value !== 'string' ||
683
+ encoder.encode(typed.value).byteLength > MAX_BODY_BYTES) {
684
+ invalid(`${path}.value`);
685
+ }
686
+ return Object.freeze({ type: 'string', value: typed.value });
687
+ }
688
+ case 'enum': {
689
+ const typed = exactRecord(value, ['type', 'value'], [], path);
690
+ const enumValue = exactRecord(typed.value, ['enum_type', 'variant'], [], `${path}.value`);
691
+ return Object.freeze({
692
+ type: 'enum',
693
+ value: Object.freeze({
694
+ enum_type: identityString(enumValue.enum_type, `${path}.value.enum_type`),
695
+ variant: identityString(enumValue.variant, `${path}.value.variant`)
696
+ })
697
+ });
698
+ }
699
+ case 'list': {
700
+ const typed = exactRecord(value, ['type', 'value'], [], path);
701
+ const values = boundedArray(typed.value, `${path}.value`).map((item, index) => parseValue(item, `${path}.value[${index}]`, depth + 1));
702
+ return Object.freeze({
703
+ type: 'list',
704
+ value: Object.freeze(values)
705
+ });
706
+ }
707
+ case 'object': {
708
+ const typed = exactRecord(value, ['type', 'value'], [], path);
709
+ const fields = boundedArray(typed.value, `${path}.value`).map((item, index) => {
710
+ const itemPath = `${path}.value[${index}]`;
711
+ const field = exactRecord(item, ['field', 'value'], [], itemPath);
712
+ return Object.freeze({
713
+ field: identityString(field.field, `${itemPath}.field`),
714
+ value: parseValue(field.value, `${itemPath}.value`, depth + 1)
715
+ });
716
+ });
717
+ assertStrictOrder(fields, (left, right) => compareUtf8(left.field, right.field), `${path}.value`);
718
+ return Object.freeze({
719
+ type: 'object',
720
+ value: Object.freeze(fields)
721
+ });
722
+ }
723
+ default:
724
+ invalid(`${path}.type`);
725
+ }
726
+ }
727
+ function parseRecovery(value, index, projectionCount, occurrenceCount) {
728
+ const path = `projection.delta.recoveries[${index}]`;
729
+ const recovery = exactRecord(value, ['occurrence_ordinal', 'projection_refs', 'condition', 'target'], [], path);
730
+ const occurrenceOrdinal = boundedOrdinal(recovery.occurrence_ordinal, `${path}.occurrence_ordinal`);
731
+ if (occurrenceOrdinal >= occurrenceCount ||
732
+ (recovery.condition !== 'always' &&
733
+ recovery.condition !== 'if_record_missing')) {
734
+ invalid(path);
735
+ }
736
+ return Object.freeze({
737
+ occurrence_ordinal: occurrenceOrdinal,
738
+ projection_refs: projectionRefs(recovery.projection_refs, projectionCount, `${path}.projection_refs`),
739
+ condition: recovery.condition,
740
+ target: parseRecoveryTarget(recovery.target, `${path}.target`)
741
+ });
742
+ }
743
+ function parseRecoveryTarget(value, path) {
744
+ if (!isPlainRecord(value) || typeof value.kind !== 'string')
745
+ invalid(path);
746
+ switch (value.kind) {
747
+ case 'record': {
748
+ const target = exactRecord(value, ['kind', 'scope'], [], path);
749
+ return Object.freeze({
750
+ kind: 'record',
751
+ scope: parseScope(target.scope, `${path}.scope`)
752
+ });
753
+ }
754
+ case 'relationship': {
755
+ const target = exactRecord(value, ['kind', 'relationship', 'source'], [], path);
756
+ return Object.freeze({
757
+ kind: 'relationship',
758
+ relationship: identityString(target.relationship, `${path}.relationship`),
759
+ source: parseScope(target.source, `${path}.source`)
760
+ });
761
+ }
762
+ case 'model': {
763
+ const target = exactRecord(value, ['kind', 'model'], ['partition'], path);
764
+ return Object.freeze({
765
+ kind: 'model',
766
+ ...(target.partition === undefined
767
+ ? {}
768
+ : {
769
+ partition: parsePartition(target.partition, `${path}.partition`)
770
+ }),
771
+ model: projectionModel(target.model, `${path}.model`)
772
+ });
773
+ }
774
+ default:
775
+ invalid(`${path}.kind`);
776
+ }
777
+ }
778
+ function parseObligation(value, index, delta) {
779
+ const path = `projection.obligations[${index}]`;
780
+ const obligation = exactRecord(value, ['projectionRef', 'model', 'scopeToken'], [], path);
781
+ const projectionRef = boundedOrdinal(obligation.projectionRef, `${path}.projectionRef`);
782
+ const model = obligationModel(obligation.model, `${path}.model`);
783
+ if (projectionRef >= delta.projections.length ||
784
+ !delta.operations.some(({ projection_refs, mutation }) => projection_refs.includes(projectionRef) &&
785
+ operationCanObserveModel(mutation, model))) {
786
+ invalid(path);
787
+ }
788
+ return Object.freeze({
789
+ projectionRef,
790
+ model,
791
+ scopeToken: protocolToken(obligation.scopeToken, 'projection-obligation', `${path}.scopeToken`)
792
+ });
793
+ }
794
+ function operationCanObserveModel(mutation, model) {
795
+ switch (mutation.op) {
796
+ case 'upsert':
797
+ case 'patch':
798
+ case 'delete':
799
+ return mutation.scope.model === model;
800
+ case 'link':
801
+ case 'unlink':
802
+ return true;
803
+ case 'invalidate_model':
804
+ case 'invalidate_relationship':
805
+ return false;
806
+ }
807
+ }
808
+ function parseCapabilityArm(value, _index, projectionCount, eventSet, path) {
809
+ const arm = exactRecord(value, ['event', 'projection_ref', 'arm', 'partition', 'mutations'], [], path);
810
+ const event = parseEventRef(arm.event, `${path}.event`);
811
+ const projectionRef = boundedOrdinal(arm.projection_ref, `${path}.projection_ref`);
812
+ if (projectionRef >= projectionCount ||
813
+ !eventSet.some((candidate) => sameEventRef(candidate, event))) {
814
+ invalid(path);
815
+ }
816
+ const mutations = capabilityArray(arm.mutations, `${path}.mutations`).map((item, index) => parseCapabilityMutation(item, `${path}.mutations[${index}]`));
817
+ if (mutations.length === 0)
818
+ invalid(`${path}.mutations`);
819
+ assertStrictOrder(mutations, (left, right) => compareTuple(capabilityMutationKey(left), capabilityMutationKey(right)), `${path}.mutations`);
820
+ return Object.freeze({
821
+ event,
822
+ projection_ref: projectionRef,
823
+ arm: identityString(arm.arm, `${path}.arm`),
824
+ partition: parseCapabilityPartition(arm.partition, `${path}.partition`),
825
+ mutations: Object.freeze(mutations)
826
+ });
827
+ }
828
+ function parseCapabilityPartition(value, path) {
829
+ if (!isPlainRecord(value) || typeof value.kind !== 'string')
830
+ invalid(path);
831
+ if (value.kind === 'unit') {
832
+ exactRecord(value, ['kind'], [], path);
833
+ return Object.freeze({ kind: 'unit' });
834
+ }
835
+ if (value.kind === 'opaque') {
836
+ const partition = exactRecord(value, ['kind', 'expression_fingerprint'], [], path);
837
+ if (typeof partition.expression_fingerprint !== 'string' ||
838
+ !SHA256.test(partition.expression_fingerprint)) {
839
+ invalid(`${path}.expression_fingerprint`);
840
+ }
841
+ return Object.freeze({
842
+ kind: 'opaque',
843
+ expression_fingerprint: partition.expression_fingerprint
844
+ });
845
+ }
846
+ invalid(`${path}.kind`);
847
+ }
848
+ function parseCapabilityMutation(value, path) {
849
+ if (!isPlainRecord(value) || typeof value.kind !== 'string')
850
+ invalid(path);
851
+ switch (value.kind) {
852
+ case 'record': {
853
+ const mutation = exactRecord(value, [
854
+ 'kind',
855
+ 'model',
856
+ 'key',
857
+ 'fields',
858
+ 'replace',
859
+ 'upsert',
860
+ 'patch',
861
+ 'delete'
862
+ ], [], path);
863
+ if (typeof mutation.upsert !== 'boolean' ||
864
+ typeof mutation.patch !== 'boolean' ||
865
+ typeof mutation.delete !== 'boolean') {
866
+ invalid(path);
867
+ }
868
+ const key = uniqueNames(mutation.key, `${path}.key`);
869
+ const fields = sortedNames(mutation.fields, `${path}.fields`);
870
+ const replace = sortedNames(mutation.replace, `${path}.replace`);
871
+ if (key.length === 0 ||
872
+ (mutation.patch && fields.length === 0)) {
873
+ invalid(path);
874
+ }
875
+ return Object.freeze({
876
+ kind: 'record',
877
+ model: projectionModel(mutation.model, `${path}.model`),
878
+ key,
879
+ fields,
880
+ replace,
881
+ upsert: mutation.upsert,
882
+ patch: mutation.patch,
883
+ delete: mutation.delete
884
+ });
885
+ }
886
+ case 'relationship': {
887
+ const mutation = exactRecord(value, [
888
+ 'kind',
889
+ 'relationship',
890
+ 'source_model',
891
+ 'source_key',
892
+ 'target_model',
893
+ 'target_key',
894
+ 'link',
895
+ 'unlink'
896
+ ], [], path);
897
+ if (typeof mutation.link !== 'boolean' ||
898
+ typeof mutation.unlink !== 'boolean') {
899
+ invalid(path);
900
+ }
901
+ const sourceKey = uniqueNames(mutation.source_key, `${path}.source_key`);
902
+ const targetKey = uniqueNames(mutation.target_key, `${path}.target_key`);
903
+ if (sourceKey.length === 0 || targetKey.length === 0)
904
+ invalid(path);
905
+ return Object.freeze({
906
+ kind: 'relationship',
907
+ relationship: identityString(mutation.relationship, `${path}.relationship`),
908
+ source_model: projectionModel(mutation.source_model, `${path}.source_model`),
909
+ source_key: sourceKey,
910
+ target_model: projectionModel(mutation.target_model, `${path}.target_model`),
911
+ target_key: targetKey,
912
+ link: mutation.link,
913
+ unlink: mutation.unlink
914
+ });
915
+ }
916
+ case 'model': {
917
+ const mutation = exactRecord(value, ['kind', 'model'], [], path);
918
+ return Object.freeze({
919
+ kind: 'model',
920
+ model: projectionModel(mutation.model, `${path}.model`)
921
+ });
922
+ }
923
+ default:
924
+ invalid(`${path}.kind`);
925
+ }
926
+ }
927
+ function capabilityMutationKey(mutation) {
928
+ switch (mutation.kind) {
929
+ case 'record':
930
+ return [
931
+ 0,
932
+ mutation.model,
933
+ mutation.key,
934
+ mutation.fields,
935
+ mutation.replace,
936
+ mutation.upsert,
937
+ mutation.patch,
938
+ mutation.delete
939
+ ];
940
+ case 'relationship':
941
+ return [
942
+ 1,
943
+ mutation.relationship,
944
+ mutation.source_model,
945
+ mutation.source_key,
946
+ mutation.target_model,
947
+ mutation.target_key,
948
+ mutation.link,
949
+ mutation.unlink
950
+ ];
951
+ case 'model':
952
+ return [2, mutation.model];
953
+ }
954
+ }
955
+ function sameEventRef(left, right) {
956
+ return (left.id === right.id &&
957
+ left.name === right.name &&
958
+ left.version === right.version);
959
+ }
960
+ function parsePreviewMutation(value, path) {
961
+ if (!isPlainRecord(value) || typeof value.op !== 'string')
962
+ invalid(path);
963
+ switch (value.op) {
964
+ case 'upsert': {
965
+ const mutation = exactRecord(value, ['op', 'scope', 'fields', 'replace'], [], path);
966
+ const fields = parsePreviewFields(mutation.fields, `${path}.fields`);
967
+ const replace = sortedNames(mutation.replace, `${path}.replace`);
968
+ if (fields.some(({ field }) => !replace.includes(field)))
969
+ invalid(path);
970
+ return Object.freeze({
971
+ op: 'upsert',
972
+ scope: parsePreviewScope(mutation.scope, `${path}.scope`),
973
+ fields,
974
+ replace
975
+ });
976
+ }
977
+ case 'patch': {
978
+ const mutation = exactRecord(value, ['op', 'scope', 'if_present'], ['set', 'unset'], path);
979
+ if (mutation.if_present !== true)
980
+ invalid(`${path}.if_present`);
981
+ const set = parsePreviewFields(mutation.set ?? [], `${path}.set`);
982
+ const unset = sortedNames(mutation.unset ?? [], `${path}.unset`);
983
+ if ((set.length === 0 && unset.length === 0) ||
984
+ set.some(({ field }) => unset.includes(field))) {
985
+ invalid(path);
986
+ }
987
+ return Object.freeze({
988
+ op: 'patch',
989
+ scope: parsePreviewScope(mutation.scope, `${path}.scope`),
990
+ set,
991
+ unset,
992
+ if_present: true
993
+ });
994
+ }
995
+ case 'delete': {
996
+ const mutation = exactRecord(value, ['op', 'scope'], [], path);
997
+ return Object.freeze({
998
+ op: 'delete',
999
+ scope: parsePreviewScope(mutation.scope, `${path}.scope`)
1000
+ });
1001
+ }
1002
+ case 'link':
1003
+ case 'unlink': {
1004
+ const mutation = exactRecord(value, ['op', 'relationship', 'source', 'target'], [], path);
1005
+ return Object.freeze({
1006
+ op: value.op,
1007
+ relationship: identityString(mutation.relationship, `${path}.relationship`),
1008
+ source: parsePreviewScope(mutation.source, `${path}.source`),
1009
+ target: parsePreviewScope(mutation.target, `${path}.target`)
1010
+ });
1011
+ }
1012
+ case 'invalidate_model': {
1013
+ const mutation = exactRecord(value, ['op', 'model'], ['partition'], path);
1014
+ return Object.freeze({
1015
+ op: 'invalidate_model',
1016
+ ...(mutation.partition === undefined
1017
+ ? {}
1018
+ : {
1019
+ partition: parsePreviewPartition(mutation.partition, `${path}.partition`)
1020
+ }),
1021
+ model: projectionModel(mutation.model, `${path}.model`)
1022
+ });
1023
+ }
1024
+ case 'invalidate_relationship': {
1025
+ const mutation = exactRecord(value, ['op', 'relationship', 'source'], [], path);
1026
+ return Object.freeze({
1027
+ op: 'invalidate_relationship',
1028
+ relationship: identityString(mutation.relationship, `${path}.relationship`),
1029
+ source: parsePreviewScope(mutation.source, `${path}.source`)
1030
+ });
1031
+ }
1032
+ default:
1033
+ invalid(`${path}.op`);
1034
+ }
1035
+ }
1036
+ function parsePreviewScope(value, path) {
1037
+ const scope = exactRecord(value, ['partition', 'model', 'key'], [], path);
1038
+ const key = boundedArray(scope.key, `${path}.key`).map((item, index) => {
1039
+ const itemPath = `${path}.key[${index}]`;
1040
+ const field = exactRecord(item, ['ordinal', 'field', 'value'], [], itemPath);
1041
+ const ordinal = boundedOrdinal(field.ordinal, `${itemPath}.ordinal`);
1042
+ if (ordinal !== index)
1043
+ invalid(`${itemPath}.ordinal`);
1044
+ return Object.freeze({
1045
+ ordinal,
1046
+ field: identityString(field.field, `${itemPath}.field`),
1047
+ value: parsePreviewValue(field.value, `${itemPath}.value`, 1)
1048
+ });
1049
+ });
1050
+ if (key.length === 0)
1051
+ invalid(`${path}.key`);
1052
+ assertUnique(key.map(({ field }) => field), `${path}.key`);
1053
+ if (encoder.encode(JSON.stringify(key)).byteLength > MAX_KEY_BYTES) {
1054
+ invalid(`${path}.key`);
1055
+ }
1056
+ const partition = parsePreviewPartition(scope.partition, `${path}.partition`);
1057
+ return Object.freeze({
1058
+ partition,
1059
+ model: projectionModel(scope.model, `${path}.model`),
1060
+ key: Object.freeze(key)
1061
+ });
1062
+ }
1063
+ function parsePreviewPartition(value, path) {
1064
+ if (!isPlainRecord(value) || typeof value.kind !== 'string')
1065
+ invalid(path);
1066
+ if (value.kind === 'unit') {
1067
+ exactRecord(value, ['kind'], [], path);
1068
+ const parsed = Object.freeze({ kind: 'unit' });
1069
+ if (encoder.encode(JSON.stringify(parsed)).byteLength > MAX_PARTITION_BYTES) {
1070
+ invalid(path);
1071
+ }
1072
+ return parsed;
1073
+ }
1074
+ if (value.kind === 'expression') {
1075
+ const partition = exactRecord(value, ['kind', 'expression', 'requires'], [], path);
1076
+ if (partition.requires !== 'current_cache_partition') {
1077
+ invalid(`${path}.requires`);
1078
+ }
1079
+ const parsed = Object.freeze({
1080
+ kind: 'expression',
1081
+ expression: parsePreviewValue(partition.expression, `${path}.expression`, 1),
1082
+ requires: 'current_cache_partition'
1083
+ });
1084
+ if (encoder.encode(JSON.stringify(parsed)).byteLength > MAX_PARTITION_BYTES) {
1085
+ invalid(path);
1086
+ }
1087
+ return parsed;
1088
+ }
1089
+ invalid(`${path}.kind`);
1090
+ }
1091
+ function parsePreviewValue(value, path, depth) {
1092
+ if (depth > MAX_DEPTH || !isPlainRecord(value) || typeof value.kind !== 'string') {
1093
+ invalid(path);
1094
+ }
1095
+ switch (value.kind) {
1096
+ case 'input':
1097
+ case 'generated_default': {
1098
+ const expression = exactRecord(value, ['kind', 'path'], [], path);
1099
+ return Object.freeze({
1100
+ kind: value.kind,
1101
+ path: stringPath(expression.path, `${path}.path`)
1102
+ });
1103
+ }
1104
+ case 'trusted_preset': {
1105
+ const expression = exactRecord(value, ['kind', 'name', 'codec'], [], path);
1106
+ return Object.freeze({
1107
+ kind: 'trusted_preset',
1108
+ name: identityString(expression.name, `${path}.name`),
1109
+ codec: identityString(expression.codec, `${path}.codec`)
1110
+ });
1111
+ }
1112
+ case 'constant': {
1113
+ const expression = exactRecord(value, ['kind', 'value'], [], path);
1114
+ return Object.freeze({
1115
+ kind: 'constant',
1116
+ value: parseValue(expression.value, `${path}.value`, depth)
1117
+ });
1118
+ }
1119
+ case 'null':
1120
+ exactRecord(value, ['kind'], [], path);
1121
+ return Object.freeze({ kind: 'null' });
1122
+ case 'list': {
1123
+ const expression = exactRecord(value, ['kind', 'values'], [], path);
1124
+ return Object.freeze({
1125
+ kind: 'list',
1126
+ values: Object.freeze(boundedArray(expression.values, `${path}.values`).map((item, index) => parsePreviewValue(item, `${path}.values[${index}]`, depth + 1)))
1127
+ });
1128
+ }
1129
+ case 'object': {
1130
+ const expression = exactRecord(value, ['kind', 'fields'], [], path);
1131
+ const fields = boundedArray(expression.fields, `${path}.fields`).map((item, index) => {
1132
+ const itemPath = `${path}.fields[${index}]`;
1133
+ const field = exactRecord(item, ['name', 'value'], [], itemPath);
1134
+ return Object.freeze({
1135
+ name: identityString(field.name, `${itemPath}.name`),
1136
+ value: parsePreviewValue(field.value, `${itemPath}.value`, depth + 1)
1137
+ });
1138
+ });
1139
+ assertStrictOrder(fields, (left, right) => compareUtf8(left.name, right.name), `${path}.fields`);
1140
+ return Object.freeze({
1141
+ kind: 'object',
1142
+ fields: Object.freeze(fields)
1143
+ });
1144
+ }
1145
+ case 'transform': {
1146
+ const expression = exactRecord(value, ['kind', 'transform', 'arguments'], [], path);
1147
+ if (expression.transform !== 'string_concat' &&
1148
+ expression.transform !== 'first_present') {
1149
+ invalid(`${path}.transform`);
1150
+ }
1151
+ const args = boundedArray(expression.arguments, `${path}.arguments`).map((item, index) => parsePreviewValue(item, `${path}.arguments[${index}]`, depth + 1));
1152
+ if (args.length === 0)
1153
+ invalid(`${path}.arguments`);
1154
+ return Object.freeze({
1155
+ kind: 'transform',
1156
+ transform: expression.transform,
1157
+ arguments: Object.freeze(args)
1158
+ });
1159
+ }
1160
+ default:
1161
+ invalid(`${path}.kind`);
1162
+ }
1163
+ }
1164
+ function parsePreviewFields(value, path) {
1165
+ const fields = boundedArray(value, path).map((item, index) => {
1166
+ const itemPath = `${path}[${index}]`;
1167
+ const field = exactRecord(item, ['field', 'value'], [], itemPath);
1168
+ return Object.freeze({
1169
+ field: identityString(field.field, `${itemPath}.field`),
1170
+ value: parsePreviewValue(field.value, `${itemPath}.value`, 1)
1171
+ });
1172
+ });
1173
+ assertStrictOrder(fields, (left, right) => compareUtf8(left.field, right.field), path);
1174
+ return Object.freeze(fields);
1175
+ }
1176
+ function parsePreviewRecoveryTarget(value, path) {
1177
+ if (!isPlainRecord(value) || typeof value.kind !== 'string')
1178
+ invalid(path);
1179
+ if (value.kind === 'record') {
1180
+ const target = exactRecord(value, ['kind', 'scope'], [], path);
1181
+ return Object.freeze({
1182
+ kind: 'record',
1183
+ scope: parsePreviewScope(target.scope, `${path}.scope`)
1184
+ });
1185
+ }
1186
+ if (value.kind === 'relationship') {
1187
+ const target = exactRecord(value, ['kind', 'relationship', 'source'], [], path);
1188
+ return Object.freeze({
1189
+ kind: 'relationship',
1190
+ relationship: identityString(target.relationship, `${path}.relationship`),
1191
+ source: parsePreviewScope(target.source, `${path}.source`)
1192
+ });
1193
+ }
1194
+ if (value.kind === 'model') {
1195
+ const target = exactRecord(value, ['kind', 'model'], ['partition'], path);
1196
+ return Object.freeze({
1197
+ kind: 'model',
1198
+ ...(target.partition === undefined
1199
+ ? {}
1200
+ : {
1201
+ partition: parsePreviewPartition(target.partition, `${path}.partition`)
1202
+ }),
1203
+ model: projectionModel(target.model, `${path}.model`)
1204
+ });
1205
+ }
1206
+ invalid(`${path}.kind`);
1207
+ }
1208
+ function parseEventRef(value, path) {
1209
+ const event = exactRecord(value, ['id', 'name', 'version'], [], path);
1210
+ const version = boundedOrdinal(event.version, `${path}.version`);
1211
+ if (version === 0)
1212
+ invalid(`${path}.version`);
1213
+ return Object.freeze({
1214
+ id: identityString(event.id, `${path}.id`),
1215
+ name: identityString(event.name, `${path}.name`),
1216
+ version
1217
+ });
1218
+ }
1219
+ function projectionRefs(value, projectionCount, path) {
1220
+ const refs = boundedArray(value, path).map((item, index) => boundedOrdinal(item, `${path}[${index}]`));
1221
+ if (refs.length === 0 ||
1222
+ refs.some((reference) => reference >= projectionCount)) {
1223
+ invalid(path);
1224
+ }
1225
+ assertStrictOrder(refs, (left, right) => left - right, path);
1226
+ return Object.freeze(refs);
1227
+ }
1228
+ function previewOperationScopeKey(mutation) {
1229
+ switch (mutation.op) {
1230
+ case 'upsert':
1231
+ case 'patch':
1232
+ case 'delete':
1233
+ return [0, previewScopeKey(mutation.scope)];
1234
+ case 'link':
1235
+ case 'unlink':
1236
+ return [
1237
+ 1,
1238
+ mutation.relationship,
1239
+ previewScopeKey(mutation.source),
1240
+ previewScopeKey(mutation.target)
1241
+ ];
1242
+ case 'invalidate_model':
1243
+ return [
1244
+ 2,
1245
+ mutation.partition === undefined
1246
+ ? [0]
1247
+ : [1, previewPartitionKey(mutation.partition)],
1248
+ mutation.model
1249
+ ];
1250
+ case 'invalidate_relationship':
1251
+ return [3, mutation.relationship, previewScopeKey(mutation.source)];
1252
+ }
1253
+ }
1254
+ function previewRecoveryTargetKey(target) {
1255
+ switch (target.kind) {
1256
+ case 'record':
1257
+ return [0, previewScopeKey(target.scope)];
1258
+ case 'relationship':
1259
+ return [1, target.relationship, previewScopeKey(target.source)];
1260
+ case 'model':
1261
+ return [
1262
+ 2,
1263
+ target.partition === undefined
1264
+ ? [0]
1265
+ : [1, previewPartitionKey(target.partition)],
1266
+ target.model
1267
+ ];
1268
+ }
1269
+ }
1270
+ function previewScopeKey(scope) {
1271
+ return [
1272
+ previewPartitionKey(scope.partition),
1273
+ scope.model,
1274
+ scope.key.map((field) => [
1275
+ field.ordinal,
1276
+ field.field,
1277
+ JSON.stringify(field.value)
1278
+ ])
1279
+ ];
1280
+ }
1281
+ function previewPartitionKey(partition) {
1282
+ return partition.kind === 'unit'
1283
+ ? [0]
1284
+ : [1, JSON.stringify(partition.expression), partition.requires];
1285
+ }
1286
+ function operationScopeKey(mutation) {
1287
+ switch (mutation.op) {
1288
+ case 'upsert':
1289
+ case 'patch':
1290
+ case 'delete':
1291
+ return [0, scopeKey(mutation.scope)];
1292
+ case 'link':
1293
+ case 'unlink':
1294
+ return [
1295
+ 1,
1296
+ mutation.relationship,
1297
+ scopeKey(mutation.source),
1298
+ scopeKey(mutation.target)
1299
+ ];
1300
+ case 'invalidate_model':
1301
+ return [
1302
+ 2,
1303
+ mutation.partition === undefined
1304
+ ? [0]
1305
+ : [1, partitionKey(mutation.partition)],
1306
+ mutation.model
1307
+ ];
1308
+ case 'invalidate_relationship':
1309
+ return [3, mutation.relationship, scopeKey(mutation.source)];
1310
+ }
1311
+ }
1312
+ function recoveryTargetKey(target) {
1313
+ switch (target.kind) {
1314
+ case 'record':
1315
+ return [0, scopeKey(target.scope)];
1316
+ case 'relationship':
1317
+ return [1, target.relationship, scopeKey(target.source)];
1318
+ case 'model':
1319
+ return [
1320
+ 2,
1321
+ target.partition === undefined
1322
+ ? [0]
1323
+ : [1, partitionKey(target.partition)],
1324
+ target.model
1325
+ ];
1326
+ }
1327
+ }
1328
+ function scopeKey(scope) {
1329
+ return [
1330
+ partitionKey(scope.partition),
1331
+ scope.model,
1332
+ scope.key.map((field) => [
1333
+ field.ordinal,
1334
+ field.field,
1335
+ valueKey(field.value)
1336
+ ])
1337
+ ];
1338
+ }
1339
+ function partitionKey(partition) {
1340
+ return partition.kind === 'unit' ? [0] : [1, partition.token];
1341
+ }
1342
+ function valueKey(value) {
1343
+ switch (value.type) {
1344
+ case 'null':
1345
+ return [0];
1346
+ case 'boolean':
1347
+ return [1, value.value];
1348
+ case 'i64':
1349
+ return [2, value.value];
1350
+ case 'u64':
1351
+ return [3, value.value];
1352
+ case 'f64':
1353
+ return [4, value.value];
1354
+ case 'string':
1355
+ return [5, value.value];
1356
+ case 'enum':
1357
+ return [6, value.value.enum_type, value.value.variant];
1358
+ case 'list':
1359
+ return [7, value.value.map(valueKey)];
1360
+ case 'object':
1361
+ return [
1362
+ 8,
1363
+ value.value.map((field) => [field.field, valueKey(field.value)])
1364
+ ];
1365
+ }
1366
+ }
1367
+ function compareProjectionIdentity(left, right) {
1368
+ return compareTuple([
1369
+ left.program_id,
1370
+ left.binding_id,
1371
+ left.epoch,
1372
+ left.program_ir_version,
1373
+ left.operation_semantics_version
1374
+ ], [
1375
+ right.program_id,
1376
+ right.binding_id,
1377
+ right.epoch,
1378
+ right.program_ir_version,
1379
+ right.operation_semantics_version
1380
+ ]);
1381
+ }
1382
+ function compareTuple(left, right) {
1383
+ for (let index = 0; index < Math.min(left.length, right.length); index += 1) {
1384
+ const compared = compareValue(left[index], right[index]);
1385
+ if (compared !== 0)
1386
+ return compared;
1387
+ }
1388
+ return left.length - right.length;
1389
+ }
1390
+ function compareValue(left, right) {
1391
+ if (Array.isArray(left) && Array.isArray(right))
1392
+ return compareTuple(left, right);
1393
+ if (typeof left === 'string' && typeof right === 'string') {
1394
+ return compareUtf8(left, right);
1395
+ }
1396
+ if (typeof left === 'number' && typeof right === 'number')
1397
+ return left - right;
1398
+ if (typeof left === 'boolean' && typeof right === 'boolean') {
1399
+ return Number(left) - Number(right);
1400
+ }
1401
+ return compareUtf8(JSON.stringify(left), JSON.stringify(right));
1402
+ }
1403
+ function compareStringArrays(left, right) {
1404
+ return compareTuple(left, right);
1405
+ }
1406
+ /** Rust `String::cmp` is lexicographic over UTF-8 bytes, not UTF-16 units. */
1407
+ function compareUtf8(left, right) {
1408
+ const leftBytes = encoder.encode(left);
1409
+ const rightBytes = encoder.encode(right);
1410
+ for (let index = 0; index < Math.min(leftBytes.length, rightBytes.length); index += 1) {
1411
+ const compared = leftBytes[index] - rightBytes[index];
1412
+ if (compared !== 0)
1413
+ return compared;
1414
+ }
1415
+ return leftBytes.length - rightBytes.length;
1416
+ }
1417
+ function exactRecord(value, required, optional, path) {
1418
+ if (!isPlainRecord(value))
1419
+ invalid(path);
1420
+ const keys = Reflect.ownKeys(value);
1421
+ if (keys.some((key) => typeof key !== 'string'))
1422
+ invalid(path);
1423
+ const allowed = new Set([...required, ...optional]);
1424
+ for (const key of keys) {
1425
+ if (!allowed.has(key))
1426
+ invalid(`${path}.${key}`);
1427
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
1428
+ if (descriptor === undefined || !('value' in descriptor)) {
1429
+ invalid(`${path}.${key}`);
1430
+ }
1431
+ }
1432
+ for (const key of required) {
1433
+ if (!Object.prototype.hasOwnProperty.call(value, key)) {
1434
+ invalid(`${path}.${key}`);
1435
+ }
1436
+ }
1437
+ return value;
1438
+ }
1439
+ /**
1440
+ * Bound hostile already-decoded JSON before cloning/freezing/canonicalizing it.
1441
+ *
1442
+ * The byte counter is a lower bound (UTF-8 strings and property names only), so
1443
+ * it never rejects a wire body that could satisfy the exact final one-MiB
1444
+ * canonical check. The independent node/depth fences bound punctuation-heavy
1445
+ * or deeply nested inputs whose strings alone would stay below that byte limit.
1446
+ */
1447
+ function preflightProjectionPayload(value, path) {
1448
+ const active = new WeakSet();
1449
+ const work = [{ kind: 'visit', value, depth: 0 }];
1450
+ let scheduledVisits = 1;
1451
+ let stringBytes = 0;
1452
+ const reserveVisits = (count) => {
1453
+ if (count > MAX_TRAVERSAL_NODES - scheduledVisits)
1454
+ invalid(path);
1455
+ scheduledVisits += count;
1456
+ };
1457
+ const consumeString = (candidate) => {
1458
+ stringBytes += utf8ByteLength(candidate, MAX_BODY_BYTES - stringBytes);
1459
+ if (stringBytes > MAX_BODY_BYTES)
1460
+ invalid(path);
1461
+ };
1462
+ const enqueueVisit = (candidate, depth) => {
1463
+ if (depth > MAX_TRAVERSAL_DEPTH)
1464
+ invalid(path);
1465
+ if (typeof candidate === 'string') {
1466
+ consumeString(candidate);
1467
+ }
1468
+ else if (candidate !== null &&
1469
+ (typeof candidate === 'object' || typeof candidate === 'function')) {
1470
+ work.push({ kind: 'visit', value: candidate, depth });
1471
+ }
1472
+ };
1473
+ while (work.length !== 0) {
1474
+ const item = work.pop();
1475
+ if (item.kind === 'leave') {
1476
+ active.delete(item.value);
1477
+ continue;
1478
+ }
1479
+ if (item.depth > MAX_TRAVERSAL_DEPTH) {
1480
+ invalid(path);
1481
+ }
1482
+ const candidate = item.value;
1483
+ if (typeof candidate === 'string') {
1484
+ consumeString(candidate);
1485
+ continue;
1486
+ }
1487
+ if (candidate === null ||
1488
+ (typeof candidate !== 'object' && typeof candidate !== 'function')) {
1489
+ continue;
1490
+ }
1491
+ if (active.has(candidate))
1492
+ invalid(path);
1493
+ active.add(candidate);
1494
+ work.push({ kind: 'leave', value: candidate });
1495
+ if (Array.isArray(candidate)) {
1496
+ reserveVisits(candidate.length);
1497
+ for (let index = candidate.length - 1; index >= 0; index -= 1) {
1498
+ const descriptor = Object.getOwnPropertyDescriptor(candidate, index);
1499
+ if (descriptor === undefined || !('value' in descriptor))
1500
+ invalid(path);
1501
+ enqueueVisit(descriptor.value, item.depth + 1);
1502
+ }
1503
+ continue;
1504
+ }
1505
+ const keys = Reflect.ownKeys(candidate);
1506
+ reserveVisits(keys.length);
1507
+ for (let index = keys.length - 1; index >= 0; index -= 1) {
1508
+ const key = keys[index];
1509
+ if (typeof key !== 'string')
1510
+ invalid(path);
1511
+ consumeString(key);
1512
+ const descriptor = Object.getOwnPropertyDescriptor(candidate, key);
1513
+ if (descriptor === undefined || !('value' in descriptor))
1514
+ invalid(path);
1515
+ enqueueVisit(descriptor.value, item.depth + 1);
1516
+ }
1517
+ }
1518
+ }
1519
+ function utf8ByteLength(value, remaining) {
1520
+ let bytes = 0;
1521
+ for (let index = 0; index < value.length; index += 1) {
1522
+ const code = value.charCodeAt(index);
1523
+ if (code <= 0x7f)
1524
+ bytes += 1;
1525
+ else if (code <= 0x7ff)
1526
+ bytes += 2;
1527
+ else if (code >= 0xd800 &&
1528
+ code <= 0xdbff &&
1529
+ index + 1 < value.length &&
1530
+ value.charCodeAt(index + 1) >= 0xdc00 &&
1531
+ value.charCodeAt(index + 1) <= 0xdfff) {
1532
+ bytes += 4;
1533
+ index += 1;
1534
+ }
1535
+ else {
1536
+ bytes += 3;
1537
+ }
1538
+ if (bytes > remaining)
1539
+ return bytes;
1540
+ }
1541
+ return bytes;
1542
+ }
1543
+ function boundedArray(value, path) {
1544
+ if (!Array.isArray(value) || value.length > MAX_ITEMS)
1545
+ invalid(path);
1546
+ return value;
1547
+ }
1548
+ function capabilityArray(value, path) {
1549
+ if (!Array.isArray(value) || value.length > MAX_TRAVERSAL_NODES)
1550
+ invalid(path);
1551
+ return value;
1552
+ }
1553
+ function boundedOrdinal(value, path) {
1554
+ if (typeof value !== 'number' ||
1555
+ !Number.isSafeInteger(value) ||
1556
+ value < 0 ||
1557
+ value > 0xffff_ffff) {
1558
+ invalid(path);
1559
+ }
1560
+ return value;
1561
+ }
1562
+ function safeUnsignedInteger(value, path) {
1563
+ if (typeof value !== 'number' ||
1564
+ !Number.isSafeInteger(value) ||
1565
+ value < 0) {
1566
+ invalid(path);
1567
+ }
1568
+ return value;
1569
+ }
1570
+ function identityString(value, path) {
1571
+ if (typeof value !== 'string' ||
1572
+ value.length === 0 ||
1573
+ value.trim() !== value ||
1574
+ encoder.encode(value).byteLength > MAX_IDENTITY_BYTES) {
1575
+ invalid(path);
1576
+ }
1577
+ return value;
1578
+ }
1579
+ function projectionEpoch(value, path) {
1580
+ const epoch = identityString(value, path);
1581
+ if (encoder.encode(epoch).byteLength > 128 ||
1582
+ [...epoch].some((character) => /\p{Cc}/u.test(character))) {
1583
+ invalid(path);
1584
+ }
1585
+ return epoch;
1586
+ }
1587
+ function projectionModel(value, path) {
1588
+ const model = identityString(value, path);
1589
+ if (encoder.encode(model).byteLength > 128 ||
1590
+ [...model].some((character) => /\p{Cc}/u.test(character) || /\p{White_Space}/u.test(character))) {
1591
+ invalid(path);
1592
+ }
1593
+ return model;
1594
+ }
1595
+ function obligationModel(value, path) {
1596
+ if (typeof value !== 'string' ||
1597
+ value.length === 0 ||
1598
+ value.trim() !== value ||
1599
+ encoder.encode(value).byteLength > 255) {
1600
+ invalid(path);
1601
+ }
1602
+ return value;
1603
+ }
1604
+ function protocolToken(value, purpose, path) {
1605
+ const token = identityString(value, path);
1606
+ const prefix = `v1.${purpose}.`;
1607
+ const encoded = token.startsWith(prefix) ? token.slice(prefix.length) : '';
1608
+ if (!/^[A-Za-z0-9_-]{43}$/.test(encoded))
1609
+ invalid(path);
1610
+ try {
1611
+ const standard = `${encoded.replace(/-/g, '+').replace(/_/g, '/')}=`;
1612
+ const decoded = atob(standard);
1613
+ if (decoded.length !== 32)
1614
+ invalid(path);
1615
+ const bytes = Uint8Array.from(decoded, (character) => character.charCodeAt(0));
1616
+ let canonical = '';
1617
+ for (const byte of bytes)
1618
+ canonical += String.fromCharCode(byte);
1619
+ const roundTrip = btoa(canonical)
1620
+ .replace(/\+/g, '-')
1621
+ .replace(/\//g, '_')
1622
+ .replace(/=+$/, '');
1623
+ if (roundTrip !== encoded)
1624
+ invalid(path);
1625
+ }
1626
+ catch {
1627
+ invalid(path);
1628
+ }
1629
+ return token;
1630
+ }
1631
+ function canonicalInteger(value, type, path) {
1632
+ if (typeof value !== 'string' || !/^-?(?:0|[1-9][0-9]*)$/.test(value)) {
1633
+ invalid(path);
1634
+ }
1635
+ let parsed;
1636
+ try {
1637
+ parsed = BigInt(value);
1638
+ }
1639
+ catch {
1640
+ invalid(path);
1641
+ }
1642
+ if (parsed.toString() !== value ||
1643
+ (type === 'i64' && (parsed < I64_MIN || parsed > I64_MAX)) ||
1644
+ (type === 'u64' && (parsed < 0n || parsed > U64_MAX))) {
1645
+ invalid(path);
1646
+ }
1647
+ return value;
1648
+ }
1649
+ function canonicalFloat(value, path) {
1650
+ if (typeof value !== 'string' || value.length === 0)
1651
+ invalid(path);
1652
+ const parsed = Number(value);
1653
+ if (!Number.isFinite(parsed))
1654
+ invalid(path);
1655
+ const canonical = rustRyuFloat(parsed);
1656
+ if (canonical !== value)
1657
+ invalid(path);
1658
+ return value;
1659
+ }
1660
+ /**
1661
+ * ECMAScript and Rust Ryu choose the same shortest round-tripping digits but
1662
+ * use different fixed/scientific notation windows. serde_json's finite f64
1663
+ * representation is fixed for decimal exponents -5..=15 and scientific
1664
+ * outside that range; integral fixed values retain `.0`.
1665
+ */
1666
+ function rustRyuFloat(value) {
1667
+ if (Object.is(value, -0) || value === 0)
1668
+ return '0.0';
1669
+ const scientific = value.toExponential();
1670
+ const match = /^(-?)([0-9])(?:\.([0-9]+))?e([+-][0-9]+)$/.exec(scientific);
1671
+ if (match === null)
1672
+ throw new TypeError('invalid finite float');
1673
+ const sign = match[1];
1674
+ const digits = `${match[2]}${match[3] ?? ''}`;
1675
+ const exponent = Number(match[4]);
1676
+ if (exponent < -5 || exponent > 15) {
1677
+ const fraction = digits.slice(1);
1678
+ return `${sign}${digits[0]}${fraction.length === 0 ? '' : `.${fraction}`}e${exponent >= 0 ? '+' : ''}${exponent}`;
1679
+ }
1680
+ const point = exponent + 1;
1681
+ if (point <= 0) {
1682
+ return `${sign}0.${'0'.repeat(-point)}${digits}`;
1683
+ }
1684
+ if (point >= digits.length) {
1685
+ return `${sign}${digits}${'0'.repeat(point - digits.length)}.0`;
1686
+ }
1687
+ return `${sign}${digits.slice(0, point)}.${digits.slice(point)}`;
1688
+ }
1689
+ function sortedNames(value, path) {
1690
+ const names = boundedArray(value, path).map((item, index) => identityString(item, `${path}[${index}]`));
1691
+ assertStrictOrder(names, compareUtf8, path);
1692
+ return Object.freeze(names);
1693
+ }
1694
+ function uniqueNames(value, path) {
1695
+ const names = boundedArray(value, path).map((item, index) => identityString(item, `${path}[${index}]`));
1696
+ assertUnique(names, path);
1697
+ return Object.freeze(names);
1698
+ }
1699
+ function stringPath(value, path) {
1700
+ const values = boundedArray(value, path).map((item, index) => identityString(item, `${path}[${index}]`));
1701
+ if (values.length === 0)
1702
+ invalid(path);
1703
+ return Object.freeze(values);
1704
+ }
1705
+ function assertUnique(values, path) {
1706
+ if (new Set(values).size !== values.length)
1707
+ invalid(path);
1708
+ }
1709
+ function assertStrictOrder(values, compare, path) {
1710
+ for (let index = 1; index < values.length; index += 1) {
1711
+ if (compare(values[index - 1], values[index]) >= 0)
1712
+ invalid(path);
1713
+ }
1714
+ }
1715
+ function invalid(path) {
1716
+ throw new ProjectionDeltaValidationError(path);
1717
+ }
1718
+ export { canonicalCommandProjectionMetadata, canonicalProjectionDelta };