@hops-ops/distributed 0.0.0 → 4.0.1

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 +3 -3
@@ -19,4 +19,4 @@ export declare function syncDiagnostics(host: DiagnosticsHost): void;
19
19
  export declare function diagnosticEvent(host: DiagnosticsHost, event: ReplicaDiagnosticEventInput): void;
20
20
  export declare function diagnosticOperation<TData, TVariables extends GraphqlVariables>(host: DiagnosticsHost, artifact: ReplicaOperationArtifact<TData, TVariables>): void;
21
21
  export declare function diagnosticScopeTransition(host: DiagnosticsHost, previous: ProtocolGeneration | undefined, next: ProtocolGeneration): void;
22
- export declare function retireDiagnosticLayer(host: DiagnosticsHost, id: string, action: 'retired' | 'rejected', receiptState: 'projected' | 'rejected', receipt?: OptimisticReceiptState): void;
22
+ export declare function retireDiagnosticLayer(host: DiagnosticsHost, id: string, action: 'retired' | 'rejected', receiptState: 'atomic' | 'rejected', receipt?: OptimisticReceiptState): void;
@@ -85,8 +85,8 @@ export function syncDiagnostics(host) {
85
85
  state: receipt === undefined
86
86
  ? 'optimistic'
87
87
  : receipt.expectations.size === 0
88
- ? 'accepted'
89
- : 'accepted_pending_projection',
88
+ ? 'succeeded'
89
+ : 'succeeded_pending_projection',
90
90
  expectations: receipt === undefined
91
91
  ? Object.freeze([])
92
92
  : diagnosticReceiptExpectations(receipt)
@@ -142,7 +142,8 @@ export function diagnosticScopeTransition(host, previous, next) {
142
142
  return;
143
143
  if (previous !== undefined &&
144
144
  previous.cacheScope === next.cacheScope &&
145
- previous.schemaHash === next.schemaHash) {
145
+ previous.schemaHash === next.schemaHash &&
146
+ previous.authorizationGeneration === next.authorizationGeneration) {
146
147
  return;
147
148
  }
148
149
  diagnosticEvent(host, Object.freeze({
@@ -1,5 +1,5 @@
1
1
  import { type CacheEngine, type CacheEngineSnapshot } from '../../internal/cache-engine.js';
2
- import type { DistributedOpaqueString, DistributedTrustedPreset } from '../../protocol.js';
2
+ import { type DistributedOpaqueString, type DistributedTrustedPreset } from '../../protocol.js';
3
3
  import type { ReplicaDiagnosticEventInput } from '../diagnostics.js';
4
4
  import type { ReplicaIndexPlanRegistration } from '../index-maintenance.js';
5
5
  import type { ReplicaAuthoritativeScope, ReplicaDehydratedState } from '../types.js';
@@ -1,4 +1,5 @@
1
1
  import { createCacheEngine } from '../../internal/cache-engine.js';
2
+ import { compareDistributedDecimal } from '../../protocol.js';
2
3
  import { matchReplicaTrustedPresetInventory } from '../commands.js';
3
4
  import { replicaIndexKey, resolveArguments } from '../identity.js';
4
5
  import { runtimeRoot } from '../selection.js';
@@ -170,6 +171,7 @@ export function dehydrateReplica(host) {
170
171
  scope: Object.freeze({
171
172
  protocolVersion: 1,
172
173
  schemaHash: scope.schemaHash,
174
+ authorizationGeneration: scope.authorizationGeneration,
173
175
  cacheScope: scope.cacheScope
174
176
  }),
175
177
  payload
@@ -191,6 +193,8 @@ export function hydrateReplica(host, state, authoritativeScope) {
191
193
  }
192
194
  if (parsed.scope.protocolVersion !== expectedScope.protocolVersion ||
193
195
  parsed.scope.schemaHash !== expectedScope.schemaHash ||
196
+ parsed.scope.authorizationGeneration !==
197
+ expectedScope.authorizationGeneration ||
194
198
  parsed.scope.cacheScope !== expectedScope.cacheScope) {
195
199
  return rejected('scope-mismatch');
196
200
  }
@@ -203,6 +207,8 @@ export function hydrateReplica(host, state, authoritativeScope) {
203
207
  if (current !== undefined &&
204
208
  (current.protocolVersion !== parsed.scope.protocolVersion ||
205
209
  current.schemaHash !== parsed.scope.schemaHash ||
210
+ current.authorizationGeneration !==
211
+ parsed.scope.authorizationGeneration ||
206
212
  current.cacheScope !== parsed.scope.cacheScope)) {
207
213
  return rejected('active-scope-mismatch');
208
214
  }
@@ -230,54 +236,82 @@ export function hydrateReplica(host, state, authoritativeScope) {
230
236
  return rejected('metadata-mismatch');
231
237
  }
232
238
  if (preserveLocalCommandState) {
239
+ // Warm same-scope re-hydrate (soft nav / second SSR seed): keep confirmed
240
+ // records and indexes the route seed omitted. Seed keys upsert; purge only
241
+ // happens on auth/scope change (closeAuthorizationGeneration path).
233
242
  host.closeActiveTransports();
243
+ host.queryStates.clear();
244
+ for (const [key, group] of parsed.operationProtocols) {
245
+ host.operationProtocols.set(key, group);
246
+ }
247
+ for (const [key, generation] of parsed.operationGenerations) {
248
+ host.operationGenerations.set(key, generation);
249
+ }
250
+ for (const [key, clock] of parsed.recordClocks) {
251
+ host.recordClocks.set(key, clock);
252
+ }
253
+ for (const [scopeToken, key] of parsed.recordKeysByScope) {
254
+ host.recordKeysByScope.set(scopeToken, key);
255
+ }
256
+ for (const [scopeToken, clock] of parsed.anonymousRecordClocks) {
257
+ host.anonymousRecordClocks.set(scopeToken, clock);
258
+ }
259
+ host.setTrustedPresets(parsed.trustedPresets);
260
+ if (compareDistributedDecimal(parsed.nextIndexRevision, host.getNextIndexRevision()) > 0) {
261
+ host.setNextIndexRevision(parsed.nextIndexRevision);
262
+ }
263
+ host.setProtocolGeneration(parsed.scope);
264
+ host.setArtifactBinding(Object.freeze({
265
+ version: 1,
266
+ schemaHash: parsed.scope.schemaHash,
267
+ ...(binding?.surfaceIdentity !== undefined
268
+ ? { surfaceIdentity: binding.surfaceIdentity }
269
+ : {}),
270
+ ...(binding?.trustedPresets !== undefined
271
+ ? { trustedPresets: binding.trustedPresets }
272
+ : {})
273
+ }));
274
+ host.engine.mergeConfirmed(parsed.cache);
234
275
  }
235
276
  else {
236
277
  host.closeAuthorizationGeneration();
237
- }
238
- host.queryStates.clear();
239
- host.operationProtocols.clear();
240
- for (const [key, group] of parsed.operationProtocols) {
241
- host.operationProtocols.set(key, group);
242
- }
243
- host.operationGenerations.clear();
244
- for (const [key, generation] of parsed.operationGenerations) {
245
- host.operationGenerations.set(key, generation);
246
- }
247
- host.recordClocks.clear();
248
- for (const [key, clock] of parsed.recordClocks) {
249
- host.recordClocks.set(key, clock);
250
- }
251
- host.recordKeysByScope.clear();
252
- for (const [scopeToken, key] of parsed.recordKeysByScope) {
253
- host.recordKeysByScope.set(scopeToken, key);
254
- }
255
- host.anonymousRecordClocks.clear();
256
- for (const [scopeToken, clock] of parsed.anonymousRecordClocks) {
257
- host.anonymousRecordClocks.set(scopeToken, clock);
258
- }
259
- if (!preserveLocalCommandState) {
278
+ host.queryStates.clear();
279
+ host.operationProtocols.clear();
280
+ for (const [key, group] of parsed.operationProtocols) {
281
+ host.operationProtocols.set(key, group);
282
+ }
283
+ host.operationGenerations.clear();
284
+ for (const [key, generation] of parsed.operationGenerations) {
285
+ host.operationGenerations.set(key, generation);
286
+ }
287
+ host.recordClocks.clear();
288
+ for (const [key, clock] of parsed.recordClocks) {
289
+ host.recordClocks.set(key, clock);
290
+ }
291
+ host.recordKeysByScope.clear();
292
+ for (const [scopeToken, key] of parsed.recordKeysByScope) {
293
+ host.recordKeysByScope.set(scopeToken, key);
294
+ }
295
+ host.anonymousRecordClocks.clear();
296
+ for (const [scopeToken, clock] of parsed.anonymousRecordClocks) {
297
+ host.anonymousRecordClocks.set(scopeToken, clock);
298
+ }
260
299
  host.optimisticReceipts.clear();
261
300
  host.diagnosticLayers?.clear();
262
301
  host.setDiagnosticLayerSequence(0);
263
- }
264
- host.setTrustedPresets(parsed.trustedPresets);
265
- host.setNextIndexRevision(parsed.nextIndexRevision);
266
- host.setProtocolGeneration(parsed.scope);
267
- host.setArtifactBinding(Object.freeze({
268
- version: 1,
269
- schemaHash: parsed.scope.schemaHash,
270
- ...(binding?.surfaceIdentity !== undefined
271
- ? { surfaceIdentity: binding.surfaceIdentity }
272
- : {}),
273
- ...(binding?.trustedPresets !== undefined
274
- ? { trustedPresets: binding.trustedPresets }
275
- : {})
276
- }));
277
- if (preserveLocalCommandState) {
278
- host.engine.restoreConfirmed(parsed.cache);
279
- }
280
- else {
302
+ host.setTrustedPresets(parsed.trustedPresets);
303
+ host.setNextIndexRevision(parsed.nextIndexRevision);
304
+ host.setProtocolGeneration(parsed.scope);
305
+ host.setArtifactBinding(Object.freeze({
306
+ version: 1,
307
+ schemaHash: parsed.scope.schemaHash,
308
+ ...(binding?.surfaceIdentity !== undefined
309
+ ? { surfaceIdentity: binding.surfaceIdentity }
310
+ : {}),
311
+ ...(binding?.trustedPresets !== undefined
312
+ ? { trustedPresets: binding.trustedPresets }
313
+ : {})
314
+ }));
281
315
  host.engine.restore(parsed.cache);
282
316
  }
283
317
  host.resumeLiveWatches();
@@ -286,7 +320,7 @@ export function hydrateReplica(host, state, authoritativeScope) {
286
320
  host.diagnosticEvent(Object.freeze({
287
321
  kind: 'hydration',
288
322
  action: 'accepted',
289
- reason: 'accepted'
323
+ reason: preserveLocalCommandState ? 'same-scope-merge' : 'accepted'
290
324
  }));
291
325
  host.diagnosticEvent(Object.freeze({
292
326
  kind: 'scope',
@@ -1,4 +1,4 @@
1
- import type { CacheEngine } from '../../internal/cache-engine.js';
1
+ import type { CacheEngine, OptimisticLayerReplacement } from '../../internal/cache-engine.js';
2
2
  import { type DistributedCommandMetadata, type DistributedProjectionObservation } from '../../protocol.js';
3
3
  import type { ReplicaDiagnosticEventInput, ReplicaDiagnosticLayerInput } from '../diagnostics.js';
4
4
  import type { ReplicaIndexSemanticChange } from '../index-maintenance.js';
@@ -18,9 +18,18 @@ export type OptimisticHost = {
18
18
  setDiagnosticLayerSequence(value: number): void;
19
19
  diagnosticEvent(event: ReplicaDiagnosticEventInput): void;
20
20
  syncDiagnostics(): void;
21
- retireDiagnosticLayer(id: string, action: 'retired' | 'rejected', receiptState: 'projected' | 'rejected', receipt?: OptimisticReceiptState): void;
21
+ retireDiagnosticLayer(id: string, action: 'retired' | 'rejected', receiptState: 'atomic' | 'rejected', receipt?: OptimisticReceiptState): void;
22
22
  };
23
23
  export declare function createOptimisticLayerOn(host: OptimisticHost, id: string, update: (writer: ReplicaOptimisticWriter) => void, semanticChanges?: readonly ReplicaIndexSemanticChange[]): void;
24
+ /**
25
+ * Package-private correction seam for projection-aware command runtimes.
26
+ *
27
+ * Receipts and diagnostics remain attached to the stable layer identity. The
28
+ * cache engine owns atomic prefix evaluation and preserves the layer context.
29
+ */
30
+ export declare function replaceOptimisticLayerOn(host: OptimisticHost, id: string, replacement: OptimisticLayerReplacement, context?: import('../../internal/cache-engine.js').OptimisticLayerContext): boolean;
31
+ /** Atomically replace both record operations and semantic index context. */
32
+ export declare function replaceReplicaOptimisticLayerOn(host: OptimisticHost, id: string, update: (writer: ReplicaOptimisticWriter) => void, semanticChanges: readonly ReplicaIndexSemanticChange[]): boolean;
24
33
  export declare function markOptimisticLayerAcceptedOn(host: OptimisticHost, id: string, receipt?: DistributedCommandMetadata): boolean;
25
34
  export declare function confirmOptimisticLayerOn<T>(host: OptimisticHost, id: string, update: (writer: ReplicaBaseWriter) => T): T;
26
35
  export declare function rejectOptimisticLayerOn(host: OptimisticHost, id: string): boolean;
@@ -44,6 +44,22 @@ export function createOptimisticLayerOn(host, id, update, semanticChanges = Obje
44
44
  host.syncDiagnostics();
45
45
  }
46
46
  }
47
+ /**
48
+ * Package-private correction seam for projection-aware command runtimes.
49
+ *
50
+ * Receipts and diagnostics remain attached to the stable layer identity. The
51
+ * cache engine owns atomic prefix evaluation and preserves the layer context.
52
+ */
53
+ export function replaceOptimisticLayerOn(host, id, replacement, context) {
54
+ assertReplicaOptimisticLayerId(id);
55
+ return host.engine.replaceOptimisticLayer(id, replacement, context);
56
+ }
57
+ /** Atomically replace both record operations and semantic index context. */
58
+ export function replaceReplicaOptimisticLayerOn(host, id, update, semanticChanges) {
59
+ assertReplicaOptimisticLayerId(id);
60
+ const captured = captureReplicaOptimisticUpdate(id, update, semanticChanges);
61
+ return host.engine.replaceOptimisticLayer(id, (_reader, writer) => replayReplicaOptimisticUpdate(writer, captured.operations), captured.context);
62
+ }
47
63
  export function markOptimisticLayerAcceptedOn(host, id, receipt) {
48
64
  if (receipt !== undefined && receipt.commandId !== id) {
49
65
  throw new TypeError('optimistic layer id must equal the causal command id');
@@ -67,7 +83,7 @@ export function markOptimisticLayerAcceptedOn(host, id, receipt) {
67
83
  host.diagnosticEvent(Object.freeze({
68
84
  kind: 'receipt',
69
85
  command: id,
70
- state: 'accepted',
86
+ state: 'succeeded',
71
87
  obligations: 0,
72
88
  observed: 0
73
89
  }));
@@ -87,8 +103,8 @@ export function markOptimisticLayerAcceptedOn(host, id, receipt) {
87
103
  kind: 'receipt',
88
104
  command: id,
89
105
  state: counts.obligations === 0
90
- ? 'accepted'
91
- : 'accepted_pending_projection',
106
+ ? 'succeeded'
107
+ : 'succeeded_pending_projection',
92
108
  obligations: counts.obligations,
93
109
  observed: counts.observed
94
110
  }));
@@ -98,7 +114,7 @@ export function markOptimisticLayerAcceptedOn(host, id, receipt) {
98
114
  }
99
115
  export function confirmOptimisticLayerOn(host, id, update) {
100
116
  const result = host.engine.confirmOptimisticLayer(id, (writer) => update(baseWriter(writer)));
101
- host.retireDiagnosticLayer(id, 'retired', 'projected');
117
+ host.retireDiagnosticLayer(id, 'retired', 'atomic');
102
118
  host.optimisticReceipts.delete(id);
103
119
  host.syncDiagnostics();
104
120
  return result;
@@ -162,8 +178,8 @@ export function applyReceiptOnly(host, command) {
162
178
  kind: 'receipt',
163
179
  command: id,
164
180
  state: counts.obligations === 0
165
- ? 'accepted'
166
- : 'accepted_pending_projection',
181
+ ? 'succeeded'
182
+ : 'succeeded_pending_projection',
167
183
  obligations: counts.obligations,
168
184
  observed: counts.observed
169
185
  }));
@@ -25,13 +25,15 @@ export function stageProtocolGeneration(host, envelope) {
25
25
  const next = {
26
26
  protocolVersion: 1,
27
27
  cacheScope: envelope.cacheScope,
28
- schemaHash: envelope.schemaHash
28
+ schemaHash: envelope.schemaHash,
29
+ authorizationGeneration: envelope.authorizationGeneration
29
30
  };
30
31
  const current = host.getProtocolGeneration();
31
32
  if (current === undefined)
32
33
  return next;
33
34
  if (current.cacheScope === next.cacheScope &&
34
- current.schemaHash === next.schemaHash) {
35
+ current.schemaHash === next.schemaHash &&
36
+ current.authorizationGeneration === next.authorizationGeneration) {
35
37
  return current;
36
38
  }
37
39
  purgeProtocolGeneration(host);
@@ -1,9 +1,9 @@
1
1
  import type { GraphqlVariables } from '../../types.js';
2
2
  import { type DistributedCommandMetadata } from '../../protocol.js';
3
- import { replicaCommandAuthority, replicaCommandDirectProjection, replicaResultObservation, type ReplicaCommandAuthorityRegistration, type ReplicaCommandDirectProjection, type ReplicaCommandSurfaceContract, type ReplicaResultObservationRegistration } from '../command-runtime.js';
3
+ import { replicaCommandAuthority, replicaCommandDirectProjection, replicaCommandProjectionDelta, replicaCommandReadRecord, replicaResultObservation, type ReplicaCommandAuthorityRegistration, type ReplicaCommandDirectProjection, type ReplicaCommandSurfaceContract, type ReplicaResultObservationRegistration } from '../command-runtime.js';
4
4
  import { type MaterializedReplicaResult } from '../materialize.js';
5
5
  import { type ReplicaIndexSemanticChange } from '../index-maintenance.js';
6
- import type { DistributedReplicaOptions, DistributedReplica as DistributedReplicaApi, ReplicaAuthoritativeScope, ReplicaBaseWriter, ReplicaDehydratedState, ReplicaIdentity, ReplicaIndexInspection, ReplicaIndexTarget, ReplicaModelArtifact, ReplicaOperationArtifact, ReplicaOptimisticWriter, ReplicaRecordInspection, ReplicaRevalidationPlan, ReplicaRevision, ReplicaResultEnvelope, ReplicaSnapshot, ReplicaWatch, ReplicaWriteSource, WatchReplicaOptions } from '../types.js';
6
+ import type { DistributedReplicaOptions, DistributedReplica as DistributedReplicaApi, ReplicaAuthoritativeScope, ReplicaBaseWriter, ReplicaDehydratedState, ReplicaIdentity, ReplicaIndexInspection, ReplicaIndexTarget, ReplicaModelArtifact, ReplicaOperationArtifact, ReplicaOptimisticWriter, ReplicaRecordInspection, ReplicaRevalidationPlan, ReplicaRevision, ReplicaResultEnvelope, ReplicaSnapshot, ReplicaValue, ReplicaWatch, ReplicaWriteSource, WatchReplicaOptions } from '../types.js';
7
7
  import type { QueryState } from './types.js';
8
8
  import { ReplicaWatchState } from './watch.js';
9
9
  export declare class DistributedReplicaImpl implements DistributedReplicaApi {
@@ -14,6 +14,7 @@ export declare class DistributedReplicaImpl implements DistributedReplicaApi {
14
14
  [replicaCommandAuthority](contract: ReplicaCommandSurfaceContract): ReplicaCommandAuthorityRegistration;
15
15
  [replicaResultObservation](observer: (envelope: ReplicaResultEnvelope<unknown>) => void): ReplicaResultObservationRegistration;
16
16
  [replicaCommandDirectProjection](commandId: string, projection: ReplicaCommandDirectProjection): void;
17
+ [replicaCommandProjectionDelta](commandId: string, update: (writer: ReplicaOptimisticWriter) => void, semanticChanges: readonly ReplicaIndexSemanticChange[]): boolean;
17
18
  read<TData, TVariables extends GraphqlVariables>(artifact: ReplicaOperationArtifact<TData, TVariables>, variables: TVariables): ReplicaSnapshot<TData>;
18
19
  watch<TData, TVariables extends GraphqlVariables>(artifact: ReplicaOperationArtifact<TData, TVariables>, variables: TVariables, options?: WatchReplicaOptions): ReplicaWatch<TData>;
19
20
  writeResult<TData, TVariables extends GraphqlVariables>(artifact: ReplicaOperationArtifact<TData, TVariables>, variables: TVariables, envelope: ReplicaResultEnvelope<TData>, source: ReplicaWriteSource): void;
@@ -31,6 +32,11 @@ export declare class DistributedReplicaImpl implements DistributedReplicaApi {
31
32
  releaseRecord(model: ReplicaModelArtifact, identity: ReplicaIdentity): void;
32
33
  gc(): readonly string[];
33
34
  inspectRecord(model: ReplicaModelArtifact, identity: ReplicaIdentity): ReplicaRecordInspection | undefined;
35
+ /**
36
+ * Package-private live fields for pure-reduce optimism.
37
+ * @internal
38
+ */
39
+ [replicaCommandReadRecord](model: ReplicaModelArtifact, identity: ReplicaIdentity): Readonly<Record<string, ReplicaValue>> | undefined;
34
40
  inspectIndex(target: ReplicaIndexTarget): ReplicaIndexInspection | undefined;
35
41
  /** Package-internal hook used by one watched operation. */
36
42
  _register<TData, TVariables extends GraphqlVariables>(watch: ReplicaWatchState<TData, TVariables>): () => void;
@@ -1,7 +1,7 @@
1
1
  import { CacheRevisionConflictError, createCacheEngine } from '../../internal/cache-engine.js';
2
2
  import { compareDistributedDecimal, DistributedProtocolError, parseGraphqlResponseExtensions } from '../../protocol.js';
3
3
  import { matchReplicaTrustedPresetInventory } from '../commands.js';
4
- import { replicaCommandAuthority, replicaCommandDirectProjection, replicaResultObservation } from '../command-runtime.js';
4
+ import { replicaCommandAuthority, replicaCommandDirectProjection, replicaCommandProjectionDelta, replicaCommandReadRecord, replicaResultObservation } from '../command-runtime.js';
5
5
  import { canonicalizeOperationVariables, replicaIndexKey, replicaRecordKey, resolveArguments } from '../identity.js';
6
6
  import { materializeReplicaOperation } from '../materialize.js';
7
7
  import { normalizeReplicaResult } from '../normalize.js';
@@ -15,7 +15,7 @@ import { assertWriteSource, indexKeyFromTarget, indexMaintenanceSnapshot, indexS
15
15
  import { ReplicaWatchState } from './watch.js';
16
16
  import { closeActiveTransports as closeActiveTransportsOn, emitWatchState, fetchWatch, releaseLive as releaseLiveOn, restartLive as restartLiveOn, retainLive as retainLiveOn, resumeLiveWatches as resumeLiveWatchesOn } from './impl-fetch-live.js';
17
17
  import { closeAuthorizationGeneration as closeAuthorizationGenerationOn, purgeProtocolGeneration as purgeProtocolGenerationOn, stageProtocolGeneration as stageProtocolGenerationOn, stageTrustedPresets as stageTrustedPresetsOn, validateProtocolBinding as validateProtocolBindingOn } from './impl-protocol.js';
18
- import { applyReceiptOnly as applyReceiptOnlyOn, confirmOptimisticLayerOn, createOptimisticLayerOn, markOptimisticLayerAcceptedOn, planOptimisticReceipts as planOptimisticReceiptsOn, rejectOptimisticLayerOn } from './impl-optimistic.js';
18
+ import { applyReceiptOnly as applyReceiptOnlyOn, confirmOptimisticLayerOn, createOptimisticLayerOn, markOptimisticLayerAcceptedOn, planOptimisticReceipts as planOptimisticReceiptsOn, rejectOptimisticLayerOn, replaceReplicaOptimisticLayerOn } from './impl-optimistic.js';
19
19
  import { dehydrateReplica, finishDehydration as finishDehydrationOn, hydrateReplica } from './impl-hydration-orchestrate.js';
20
20
  import { diagnosticEvent as diagnosticEventOn, diagnosticOperation as diagnosticOperationOn, diagnosticScopeTransition as diagnosticScopeTransitionOn, retireDiagnosticLayer as retireDiagnosticLayerOn, syncDiagnostics as syncDiagnosticsOn } from './impl-diagnostics.js';
21
21
  export class DistributedReplicaImpl {
@@ -337,6 +337,7 @@ export class DistributedReplicaImpl {
337
337
  : Object.freeze({
338
338
  protocolVersion: scope.protocolVersion,
339
339
  schemaHash: scope.schemaHash,
340
+ authorizationGeneration: scope.authorizationGeneration,
340
341
  cacheScope: scope.cacheScope
341
342
  });
342
343
  }
@@ -477,6 +478,9 @@ export class DistributedReplicaImpl {
477
478
  }));
478
479
  }
479
480
  }
481
+ [replicaCommandProjectionDelta](commandId, update, semanticChanges) {
482
+ return replaceReplicaOptimisticLayerOn(this.#optimisticHost(), commandId, update, semanticChanges);
483
+ }
480
484
  read(artifact, variables) {
481
485
  this.#bindArtifact(artifact);
482
486
  const stableVariables = canonicalizeOperationVariables(artifact, variables);
@@ -927,7 +931,7 @@ export class DistributedReplicaImpl {
927
931
  }
928
932
  for (const [id, receipt] of receiptPlan.updates) {
929
933
  if (receiptPlan.satisfied.includes(id)) {
930
- this.#retireDiagnosticLayer(id, 'retired', 'projected', receipt);
934
+ this.#retireDiagnosticLayer(id, 'retired', 'atomic', receipt);
931
935
  this.#optimisticReceipts.delete(id);
932
936
  }
933
937
  else {
@@ -943,8 +947,8 @@ export class DistributedReplicaImpl {
943
947
  kind: 'receipt',
944
948
  command: id,
945
949
  state: counts.obligations === 0
946
- ? 'accepted'
947
- : 'accepted_pending_projection',
950
+ ? 'succeeded'
951
+ : 'succeeded_pending_projection',
948
952
  obligations: counts.obligations,
949
953
  observed: counts.observed
950
954
  }));
@@ -1105,6 +1109,19 @@ export class DistributedReplicaImpl {
1105
1109
  });
1106
1110
  });
1107
1111
  }
1112
+ /**
1113
+ * Package-private live fields for pure-reduce optimism.
1114
+ * @internal
1115
+ */
1116
+ [replicaCommandReadRecord](model, identity) {
1117
+ const key = replicaRecordKey(model, identity);
1118
+ return this.#engine.read((reader) => {
1119
+ const record = reader.record(key);
1120
+ if (!record)
1121
+ return undefined;
1122
+ return Object.freeze({ ...record.fields });
1123
+ });
1124
+ }
1108
1125
  inspectIndex(target) {
1109
1126
  const key = indexKeyFromTarget(target);
1110
1127
  return this.#engine.read((reader) => {
@@ -85,18 +85,29 @@ export function captureReplicaOptimisticUpdate(id, update, semanticChanges) {
85
85
  const key = replicaRecordKey(model, identity);
86
86
  const fields = cloneOptimisticFields(patch.fields);
87
87
  const links = cloneOptimisticLinks(patch.links);
88
- if (Object.keys(fields).length === 0 && Object.keys(links).length === 0) {
88
+ const unset = cloneOptimisticUnset(patch.unset, fields);
89
+ if (Object.keys(fields).length === 0 &&
90
+ Object.keys(links).length === 0 &&
91
+ unset.length === 0) {
89
92
  return;
90
93
  }
91
94
  operations.push(Object.freeze({
92
95
  kind: 'write-record',
93
- write: Object.freeze({ key, fields, links })
96
+ write: Object.freeze({
97
+ key,
98
+ fields,
99
+ links,
100
+ ...(unset.length === 0 ? {} : { unset }),
101
+ ...(patch.ifPresent === true ? { ifPresent: true } : {})
102
+ })
94
103
  }));
95
104
  changes.push(Object.freeze({
96
105
  kind: 'upsert',
97
106
  model: model.id,
98
107
  key,
99
- fields
108
+ fields,
109
+ ...(unset.length === 0 ? {} : { unset }),
110
+ ...(patch.ifPresent === true ? { ifPresent: true } : {})
100
111
  }));
101
112
  },
102
113
  tombstoneRecord(model, identity) {
@@ -174,6 +185,23 @@ export function cloneOptimisticFields(fields) {
174
185
  return [name, cloneJsonValue(value)];
175
186
  })));
176
187
  }
188
+ function cloneOptimisticUnset(unset, fields) {
189
+ if (unset === undefined)
190
+ return Object.freeze([]);
191
+ if (!Array.isArray(unset))
192
+ throw new TypeError('record unset must be an array');
193
+ const result = unset.map((name) => {
194
+ assertReplicaName(name, 'record unset field');
195
+ if (Object.hasOwn(fields, name)) {
196
+ throw new TypeError('record field cannot be set and unset');
197
+ }
198
+ return name;
199
+ });
200
+ if (new Set(result).size !== result.length) {
201
+ throw new TypeError('record unset fields must be unique');
202
+ }
203
+ return Object.freeze(result);
204
+ }
177
205
  export function cloneOptimisticLinks(links) {
178
206
  if (links === undefined)
179
207
  return Object.freeze({});
@@ -20,6 +20,7 @@ export type ProtocolGeneration = {
20
20
  protocolVersion: 1;
21
21
  cacheScope: DistributedOpaqueString;
22
22
  schemaHash: string;
23
+ authorizationGeneration: string;
23
24
  };
24
25
  export type RenderedOperation = {
25
26
  readonly artifact: ReplicaOperationArtifact<unknown, GraphqlVariables>;
@@ -199,13 +199,19 @@ export function applyLayers(base, layers) {
199
199
  continue;
200
200
  }
201
201
  const current = records.get(change.key);
202
+ if (change.ifPresent === true && current === undefined) {
203
+ continue;
204
+ }
202
205
  if (current !== undefined && current.model !== change.model) {
203
206
  throw new TypeError(`record model changed for ${change.key}`);
204
207
  }
208
+ const fields = { ...(current?.fields ?? {}), ...change.fields };
209
+ for (const field of change.unset ?? [])
210
+ delete fields[field];
205
211
  records.set(change.key, {
206
212
  key: change.key,
207
213
  model: change.model,
208
- fields: { ...(current?.fields ?? {}), ...change.fields }
214
+ fields
209
215
  });
210
216
  }
211
217
  }
@@ -421,10 +427,8 @@ export function maintainEntityIndex(planValue, index, changes, trustedPresets) {
421
427
  index.metadata.coverage.offset === 0 &&
422
428
  index.metadata.coverage.limit !== undefined &&
423
429
  records.length > index.metadata.coverage.limit) {
424
- // Locality above is possible only because the base first page was
425
- // non-full, so this is the complete optimistic ordered set. Preserve the
426
- // operation's exact window when stacked optimistic inserts cross its
427
- // limit.
430
+ // First-page inserts re-sort known members then clamp to the operation
431
+ // window so a full live page still shows optimistic rows that sort in.
428
432
  records = records.slice(0, index.metadata.coverage.limit);
429
433
  }
430
434
  return sameStringList(records, current)
@@ -451,6 +455,10 @@ export function certifyOffsetCoverage(planValue, index, confirmedRecords, change
451
455
  typeof artifact.maxLimit === 'number'
452
456
  ? Math.min(configuredLimit, artifact.maxLimit)
453
457
  : configuredLimit;
458
+ // First-page inserts remain local even when hasNext is true: membership is
459
+ // re-sorted and truncated to the limit among known rows. Delete/reorder on a
460
+ // page with a next boundary still fail closed (unseen fill-in).
461
+ const hasNextBlocksLocality = coverage.hasNext === true && changeKind !== 'insert';
454
462
  if (typeof expectedOffset !== 'number' ||
455
463
  !Number.isSafeInteger(expectedOffset) ||
456
464
  expectedOffset < 0 ||
@@ -461,7 +469,7 @@ export function certifyOffsetCoverage(planValue, index, confirmedRecords, change
461
469
  coverage.offset !== expectedOffset ||
462
470
  coverage.limit !== expectedLimit ||
463
471
  coverage.returned !== confirmedRecords ||
464
- coverage.hasNext === true) {
472
+ hasNextBlocksLocality) {
465
473
  return reason('invalid_index_metadata', ['indexes', index.key, 'coverage'], 'offset coverage does not match the request, confirmed index size, or observed page boundary');
466
474
  }
467
475
  return undefined;
@@ -10,8 +10,12 @@ export { createReplicaIndexedDbPersistence, REPLICA_OFFLINE_COMMAND_OUTBOX_SUPPO
10
10
  export type { ReplicaBoundCommand, ReplicaBoundCommands, ReplicaCommandCallOptions, ReplicaCommandProjectedOutcome, ReplicaCommandReceipt, ReplicaCommandRecoveryReceipt, ReplicaCommandRuntime, ReplicaCommandRuntimeErrorCode, ReplicaCommandRuntimeOptions, ReplicaCommandStatus, ReplicaCommandStatusArtifact, ReplicaCommandStatusRequest, ReplicaCommandTransport, ReplicaCommandTransportRequest, ReplicaCommandTransportResult } from './command-runtime.js';
11
11
  export type { ReplicaIndexedDbFactory, ReplicaIndexedDbPersistence, ReplicaIndexedDbPersistenceOptions, ReplicaPersistenceModelPolicy, ReplicaPersistencePolicy } from './persistence.js';
12
12
  export type { PrepareReplicaCommandOptions, ReplicaCommandArtifact, ReplicaCommandConfirmation, ReplicaCommandConfirmations, ReplicaCommandContractErrorCode, ReplicaCommandDirectProjection, ReplicaCommandEffect, ReplicaCommandEffectExpression, ReplicaCommandEffectField, ReplicaCommandEffectKey, ReplicaCommandEffectRelationship, ReplicaCommandEffects, ReplicaCommandGenerators, ReplicaCommandInputDefault, ReplicaCommandInputDefaults, ReplicaCommandRevalidationPlan, ReplicaCommandScalarCodec, ReplicaCommandShape, ReplicaCommandTypeDefinition, ReplicaCommandTypeField, ReplicaCommandVariables, ReplicaTrustedPresetDescriptor, ReplicaPreparedCommand, ReplicaPreparedCommandEffect, ReplicaPreparedConfirmation, ReplicaPreparedConfirmations, ReplicaPreparedEffectField, ReplicaPreparedEffectKey, ReplicaReceiptVerification } from './commands.js';
13
+ export { createWasmJsonPure } from './projection-delta/index.js';
14
+ export type { CreateWasmJsonPureOptions, PreparedCommandProjection, PreparedProjectionOperation, ReplicaCommandProjection, WasmJsonModule, WasmJsonPureHost } from './projection-delta/index.js';
13
15
  export { compareReplicaOrder, decideReplicaPaginationMaintenance, evaluateReplicaFilter } from './query-plan.js';
14
16
  export { createReplicaIndexMaintenanceRegistry, formatReplicaIndexStaleReason } from './index-maintenance.js';
15
17
  export type { ReplicaIndexDependencyChange, ReplicaIndexMaintenanceDecision, ReplicaIndexMaintenanceIndex, ReplicaIndexMaintenanceReason, ReplicaIndexMaintenanceReasonCode, ReplicaIndexMaintenanceRecord, ReplicaIndexMaintenanceRegistry, ReplicaIndexMaintenanceSnapshot, ReplicaIndexPlanRegistration, ReplicaIndexRecordChange, ReplicaIndexRelationshipChange, ReplicaIndexSemanticChange, ReplicaIndexSemanticLayer } from './index-maintenance.js';
16
18
  export type { ReplicaFilterEvaluation, ReplicaFilterEvaluationOptions, ReplicaOrderComparison, ReplicaPaginationChange, ReplicaPaginationMaintenanceDecision, ReplicaQueryPlanPath, ReplicaQueryPlanReason, ReplicaQueryPlanReasonCode, ReplicaRelationshipFilterRequest } from './query-plan.js';
17
19
  export type { DistributedReplicaOptions, DistributedReplica, ReplicaArgumentsArtifact, ReplicaArgumentValue, ReplicaAuthoritativeScope, ReplicaBaseWriter, ReplicaBranchSemantic, ReplicaClientSurface, ReplicaCoverageArtifact, ReplicaDehydratedState, ReplicaFilterArtifact, ReplicaFilterExpression, ReplicaFilterFieldArtifact, ReplicaFilterLiteral, ReplicaFilterOperand, ReplicaFilterOperator, ReplicaIdentity, ReplicaIndexCoverage, ReplicaIndexInspection, ReplicaIndexTarget, ReplicaListValue, ReplicaLiveObserver, ReplicaLiveState, ReplicaLiteralValue, ReplicaModelArtifact, ReplicaObjectBranch, ReplicaObjectMember, ReplicaObjectSelection, ReplicaObjectValue, ReplicaOperationArtifact, ReplicaOperationSourceLocation, ReplicaOperationProtocol, ReplicaOrderArtifact, ReplicaOrderFieldArtifact, ReplicaOrderTieBreakerArtifact, ReplicaOptimisticWriter, ReplicaPaginationArtifact, ReplicaPaginationDisposition, ReplicaProtocolOperationArtifact, ReplicaRecordInspection, ReplicaRecordPatch, ReplicaRevalidationPlan, ReplicaRevalidationRelationship, ReplicaRevision, ReplicaRelationshipArtifact, ReplicaRelationshipKeyMapping, ReplicaRelationshipKind, ReplicaResultEnvelope, ReplicaRowPolicyArtifact, ReplicaRootSelection, ReplicaScalarSelection, ReplicaSelectionStorage, ReplicaSparse, ReplicaSnapshot, ReplicaStatus, ReplicaTransport, ReplicaTransportRequest, ReplicaVariableValue, ReplicaVariableCodecArtifact, ReplicaVariableEnumInputRef, ReplicaVariableFilterInputDefinition, ReplicaVariableFilterInputField, ReplicaVariableFilterInputRelationship, ReplicaVariableFilterInputTarget, ReplicaVariableInputDefinition, ReplicaVariableInputRef, ReplicaVariableListInputRef, ReplicaVariableNamedInputRef, ReplicaVariableOrderInputDefinition, ReplicaVariableOrderInputField, ReplicaVariableScalarInputRef, ReplicaValue, ReplicaWatch, ReplicaWriteSource, WatchReplicaOptions } from './types.js';
20
+ export { lowerMutationCache, MUTATION_CACHE_VISIBILITY_FULL, MUTATION_CACHE_VISIBILITY_UNAUTHORIZED, } from './mutation-cache.js';
21
+ export type { MutationCacheEffect, MutationCacheProgram, MutationCacheVisibility, MutationField, MutationOperation, MutationProgram, MutationTarget, } from './mutation-cache.js';
@@ -5,5 +5,7 @@ export { canonicalizeOperationVariables, replicaIndexKey, replicaRecordKey } fro
5
5
  export { prepareReplicaCommand, ReplicaCommandContractError, verifyReplicaCommandReceipt } from './commands.js';
6
6
  export { createReplicaCommandRuntime, ReplicaCommandRuntimeError } from './command-runtime.js';
7
7
  export { createReplicaIndexedDbPersistence, REPLICA_OFFLINE_COMMAND_OUTBOX_SUPPORTED } from './persistence.js';
8
+ export { createWasmJsonPure } from './projection-delta/index.js';
8
9
  export { compareReplicaOrder, decideReplicaPaginationMaintenance, evaluateReplicaFilter } from './query-plan.js';
9
10
  export { createReplicaIndexMaintenanceRegistry, formatReplicaIndexStaleReason } from './index-maintenance.js';
11
+ export { lowerMutationCache, MUTATION_CACHE_VISIBILITY_FULL, MUTATION_CACHE_VISIBILITY_UNAUTHORIZED, } from './mutation-cache.js';
@@ -54,6 +54,16 @@ function materializeBranch(reader, selection, index, variables) {
54
54
  if (selection.cardinality === 'one') {
55
55
  const key = index.records[0];
56
56
  if (key === undefined) {
57
+ // Empty relationship edge: null when the selection allows it.
58
+ if (selection.nullable) {
59
+ return {
60
+ value: null,
61
+ present: true,
62
+ complete: indexComplete,
63
+ stale,
64
+ identitySignature: `null:${index.metadata.staleReason ?? ''}`
65
+ };
66
+ }
57
67
  return {
58
68
  present: false,
59
69
  complete: false,
@@ -144,6 +154,21 @@ function materializeNestedBranch(reader, parentKey, parentIncarnation, selection
144
154
  });
145
155
  const index = reader.index(indexKey);
146
156
  if (index?.metadata?.parentIncarnation !== parentIncarnation) {
157
+ /*
158
+ * Optimistic parent upserts often write the FK row before a relationship
159
+ * index exists. For nullable joins (e.g. ChatMessages.author), treat the
160
+ * missing edge as an authorized null so the parent list stays complete
161
+ * and visible. Required relationships still fail closed as incomplete.
162
+ */
163
+ if (selection.nullable) {
164
+ return {
165
+ value: null,
166
+ present: true,
167
+ complete: true,
168
+ stale: index !== undefined,
169
+ identitySignature: `null-missing-edge:${selection.field}`
170
+ };
171
+ }
147
172
  return {
148
173
  present: false,
149
174
  complete: false,