@hops-ops/distributed 0.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 (242) hide show
  1. package/README.md +454 -0
  2. package/dist/auth-headers.d.ts +8 -0
  3. package/dist/auth-headers.js +35 -0
  4. package/dist/diagnostics.d.ts +2 -0
  5. package/dist/diagnostics.js +1 -0
  6. package/dist/document.d.ts +8 -0
  7. package/dist/document.js +6 -0
  8. package/dist/identity.d.ts +23 -0
  9. package/dist/identity.js +73 -0
  10. package/dist/index.d.ts +7 -0
  11. package/dist/index.js +6 -0
  12. package/dist/internal/cache-engine/create.d.ts +5 -0
  13. package/dist/internal/cache-engine/create.js +7 -0
  14. package/dist/internal/cache-engine/engine.d.ts +32 -0
  15. package/dist/internal/cache-engine/engine.js +1287 -0
  16. package/dist/internal/cache-engine/errors.d.ts +5 -0
  17. package/dist/internal/cache-engine/errors.js +10 -0
  18. package/dist/internal/cache-engine/helpers.d.ts +55 -0
  19. package/dist/internal/cache-engine/helpers.js +545 -0
  20. package/dist/internal/cache-engine/index.d.ts +3 -0
  21. package/dist/internal/cache-engine/index.js +2 -0
  22. package/dist/internal/cache-engine/types.d.ts +300 -0
  23. package/dist/internal/cache-engine/types.js +1 -0
  24. package/dist/internal/cache-engine.d.ts +3 -0
  25. package/dist/internal/cache-engine.js +2 -0
  26. package/dist/lib/assert-name.d.ts +2 -0
  27. package/dist/lib/assert-name.js +6 -0
  28. package/dist/lib/compare-code-units.d.ts +2 -0
  29. package/dist/lib/compare-code-units.js +4 -0
  30. package/dist/lib/deep-equal.d.ts +2 -0
  31. package/dist/lib/deep-equal.js +24 -0
  32. package/dist/lib/freeze-record.d.ts +2 -0
  33. package/dist/lib/freeze-record.js +13 -0
  34. package/dist/lib/index.d.ts +6 -0
  35. package/dist/lib/index.js +6 -0
  36. package/dist/lib/is-plain-record.d.ts +2 -0
  37. package/dist/lib/is-plain-record.js +8 -0
  38. package/dist/lib/report.d.ts +7 -0
  39. package/dist/lib/report.js +26 -0
  40. package/dist/protocol.d.ts +179 -0
  41. package/dist/protocol.js +558 -0
  42. package/dist/react/context.d.ts +20 -0
  43. package/dist/react/context.js +24 -0
  44. package/dist/react/index.d.ts +2 -0
  45. package/dist/react/index.js +3 -0
  46. package/dist/react/query.d.ts +20 -0
  47. package/dist/react/query.js +99 -0
  48. package/dist/replica/command-id.d.ts +2 -0
  49. package/dist/replica/command-id.js +19 -0
  50. package/dist/replica/command-runtime/constants.d.ts +5 -0
  51. package/dist/replica/command-runtime/constants.js +5 -0
  52. package/dist/replica/command-runtime/create.d.ts +2 -0
  53. package/dist/replica/command-runtime/create.js +644 -0
  54. package/dist/replica/command-runtime/errors.d.ts +16 -0
  55. package/dist/replica/command-runtime/errors.js +41 -0
  56. package/dist/replica/command-runtime/index.d.ts +5 -0
  57. package/dist/replica/command-runtime/index.js +4 -0
  58. package/dist/replica/command-runtime/lib/binding.d.ts +4 -0
  59. package/dist/replica/command-runtime/lib/binding.js +64 -0
  60. package/dist/replica/command-runtime/lib/effects.d.ts +11 -0
  61. package/dist/replica/command-runtime/lib/effects.js +89 -0
  62. package/dist/replica/command-runtime/lib/index.d.ts +9 -0
  63. package/dist/replica/command-runtime/lib/index.js +9 -0
  64. package/dist/replica/command-runtime/lib/inventory.d.ts +10 -0
  65. package/dist/replica/command-runtime/lib/inventory.js +141 -0
  66. package/dist/replica/command-runtime/lib/output.d.ts +13 -0
  67. package/dist/replica/command-runtime/lib/output.js +190 -0
  68. package/dist/replica/command-runtime/lib/projection.d.ts +20 -0
  69. package/dist/replica/command-runtime/lib/projection.js +255 -0
  70. package/dist/replica/command-runtime/lib/status.d.ts +7 -0
  71. package/dist/replica/command-runtime/lib/status.js +127 -0
  72. package/dist/replica/command-runtime/lib/transport.d.ts +16 -0
  73. package/dist/replica/command-runtime/lib/transport.js +112 -0
  74. package/dist/replica/command-runtime/lib/util.d.ts +16 -0
  75. package/dist/replica/command-runtime/lib/util.js +127 -0
  76. package/dist/replica/command-runtime/lifecycle.d.ts +11 -0
  77. package/dist/replica/command-runtime/lifecycle.js +13 -0
  78. package/dist/replica/command-runtime/symbols.d.ts +30 -0
  79. package/dist/replica/command-runtime/symbols.js +30 -0
  80. package/dist/replica/command-runtime/types.d.ts +222 -0
  81. package/dist/replica/command-runtime/types.js +1 -0
  82. package/dist/replica/command-runtime.d.ts +3 -0
  83. package/dist/replica/command-runtime.js +2 -0
  84. package/dist/replica/commands/clone.d.ts +12 -0
  85. package/dist/replica/commands/clone.js +187 -0
  86. package/dist/replica/commands/constants.d.ts +6 -0
  87. package/dist/replica/commands/constants.js +6 -0
  88. package/dist/replica/commands/errors.d.ts +7 -0
  89. package/dist/replica/commands/errors.js +22 -0
  90. package/dist/replica/commands/implementation.d.ts +8 -0
  91. package/dist/replica/commands/implementation.js +8 -0
  92. package/dist/replica/commands/index.d.ts +5 -0
  93. package/dist/replica/commands/index.js +4 -0
  94. package/dist/replica/commands/prepare.d.ts +27 -0
  95. package/dist/replica/commands/prepare.js +77 -0
  96. package/dist/replica/commands/presets.d.ts +12 -0
  97. package/dist/replica/commands/presets.js +84 -0
  98. package/dist/replica/commands/receipt.d.ts +3 -0
  99. package/dist/replica/commands/receipt.js +36 -0
  100. package/dist/replica/commands/resolve.d.ts +11 -0
  101. package/dist/replica/commands/resolve.js +165 -0
  102. package/dist/replica/commands/types.d.ts +278 -0
  103. package/dist/replica/commands/types.js +2 -0
  104. package/dist/replica/commands/util.d.ts +21 -0
  105. package/dist/replica/commands/util.js +96 -0
  106. package/dist/replica/commands/validate.d.ts +18 -0
  107. package/dist/replica/commands/validate.js +477 -0
  108. package/dist/replica/commands.d.ts +3 -0
  109. package/dist/replica/commands.js +2 -0
  110. package/dist/replica/diagnostics/event-log.d.ts +4 -0
  111. package/dist/replica/diagnostics/event-log.js +463 -0
  112. package/dist/replica/diagnostics/implementation.d.ts +4 -0
  113. package/dist/replica/diagnostics/implementation.js +4 -0
  114. package/dist/replica/diagnostics/index.d.ts +2 -0
  115. package/dist/replica/diagnostics/index.js +1 -0
  116. package/dist/replica/diagnostics/inspect.d.ts +6 -0
  117. package/dist/replica/diagnostics/inspect.js +170 -0
  118. package/dist/replica/diagnostics/types.d.ts +295 -0
  119. package/dist/replica/diagnostics/types.js +2 -0
  120. package/dist/replica/diagnostics.d.ts +3 -0
  121. package/dist/replica/diagnostics.js +2 -0
  122. package/dist/replica/distributed-replica/clocks.d.ts +23 -0
  123. package/dist/replica/distributed-replica/clocks.js +156 -0
  124. package/dist/replica/distributed-replica/constants.d.ts +12 -0
  125. package/dist/replica/distributed-replica/constants.js +10 -0
  126. package/dist/replica/distributed-replica/helpers.d.ts +48 -0
  127. package/dist/replica/distributed-replica/helpers.js +445 -0
  128. package/dist/replica/distributed-replica/hydration.d.ts +19 -0
  129. package/dist/replica/distributed-replica/hydration.js +380 -0
  130. package/dist/replica/distributed-replica/impl-diagnostics.d.ts +22 -0
  131. package/dist/replica/distributed-replica/impl-diagnostics.js +189 -0
  132. package/dist/replica/distributed-replica/impl-fetch-live.d.ts +39 -0
  133. package/dist/replica/distributed-replica/impl-fetch-live.js +395 -0
  134. package/dist/replica/distributed-replica/impl-hydration-orchestrate.d.ts +56 -0
  135. package/dist/replica/distributed-replica/impl-hydration-orchestrate.js +299 -0
  136. package/dist/replica/distributed-replica/impl-optimistic.d.ts +31 -0
  137. package/dist/replica/distributed-replica/impl-optimistic.js +172 -0
  138. package/dist/replica/distributed-replica/impl-protocol.d.ts +53 -0
  139. package/dist/replica/distributed-replica/impl-protocol.js +108 -0
  140. package/dist/replica/distributed-replica/impl.d.ts +44 -0
  141. package/dist/replica/distributed-replica/impl.js +1776 -0
  142. package/dist/replica/distributed-replica/index.d.ts +2 -0
  143. package/dist/replica/distributed-replica/index.js +4 -0
  144. package/dist/replica/distributed-replica/optimistic.d.ts +30 -0
  145. package/dist/replica/distributed-replica/optimistic.js +221 -0
  146. package/dist/replica/distributed-replica/types.d.ts +147 -0
  147. package/dist/replica/distributed-replica/types.js +1 -0
  148. package/dist/replica/distributed-replica/watch.d.ts +19 -0
  149. package/dist/replica/distributed-replica/watch.js +95 -0
  150. package/dist/replica/distributed-replica.d.ts +2 -0
  151. package/dist/replica/distributed-replica.js +2 -0
  152. package/dist/replica/graphql-transport.d.ts +24 -0
  153. package/dist/replica/graphql-transport.js +122 -0
  154. package/dist/replica/identity/clone.d.ts +6 -0
  155. package/dist/replica/identity/clone.js +54 -0
  156. package/dist/replica/identity/codec.d.ts +48 -0
  157. package/dist/replica/identity/codec.js +789 -0
  158. package/dist/replica/identity/constants.d.ts +3 -0
  159. package/dist/replica/identity/constants.js +18 -0
  160. package/dist/replica/identity/implementation.d.ts +5 -0
  161. package/dist/replica/identity/implementation.js +5 -0
  162. package/dist/replica/identity/index.d.ts +3 -0
  163. package/dist/replica/identity/index.js +3 -0
  164. package/dist/replica/identity/keys.d.ts +29 -0
  165. package/dist/replica/identity/keys.js +173 -0
  166. package/dist/replica/identity.d.ts +2 -0
  167. package/dist/replica/identity.js +2 -0
  168. package/dist/replica/index-maintenance/engine.d.ts +84 -0
  169. package/dist/replica/index-maintenance/engine.js +677 -0
  170. package/dist/replica/index-maintenance/implementation.d.ts +4 -0
  171. package/dist/replica/index-maintenance/implementation.js +4 -0
  172. package/dist/replica/index-maintenance/index.d.ts +2 -0
  173. package/dist/replica/index-maintenance/index.js +1 -0
  174. package/dist/replica/index-maintenance/registry.d.ts +2 -0
  175. package/dist/replica/index-maintenance/registry.js +49 -0
  176. package/dist/replica/index-maintenance/types.d.ts +58 -0
  177. package/dist/replica/index-maintenance/types.js +1 -0
  178. package/dist/replica/index-maintenance.d.ts +3 -0
  179. package/dist/replica/index-maintenance.js +2 -0
  180. package/dist/replica/index.d.ts +17 -0
  181. package/dist/replica/index.js +9 -0
  182. package/dist/replica/materialize.d.ts +10 -0
  183. package/dist/replica/materialize.js +163 -0
  184. package/dist/replica/normalize.d.ts +27 -0
  185. package/dist/replica/normalize.js +298 -0
  186. package/dist/replica/operation-binding.d.ts +14 -0
  187. package/dist/replica/operation-binding.js +76 -0
  188. package/dist/replica/persistence/idb.d.ts +20 -0
  189. package/dist/replica/persistence/idb.js +193 -0
  190. package/dist/replica/persistence/implementation.d.ts +4 -0
  191. package/dist/replica/persistence/implementation.js +4 -0
  192. package/dist/replica/persistence/index.d.ts +2 -0
  193. package/dist/replica/persistence/index.js +1 -0
  194. package/dist/replica/persistence/state.d.ts +104 -0
  195. package/dist/replica/persistence/state.js +734 -0
  196. package/dist/replica/persistence/types.d.ts +54 -0
  197. package/dist/replica/persistence/types.js +1 -0
  198. package/dist/replica/persistence.d.ts +3 -0
  199. package/dist/replica/persistence.js +2 -0
  200. package/dist/replica/query-plan/constants.d.ts +13 -0
  201. package/dist/replica/query-plan/constants.js +7 -0
  202. package/dist/replica/query-plan/filter.d.ts +39 -0
  203. package/dist/replica/query-plan/filter.js +544 -0
  204. package/dist/replica/query-plan/index.d.ts +4 -0
  205. package/dist/replica/query-plan/index.js +3 -0
  206. package/dist/replica/query-plan/order.d.ts +11 -0
  207. package/dist/replica/query-plan/order.js +158 -0
  208. package/dist/replica/query-plan/pagination.d.ts +6 -0
  209. package/dist/replica/query-plan/pagination.js +90 -0
  210. package/dist/replica/query-plan/resolve.d.ts +14 -0
  211. package/dist/replica/query-plan/resolve.js +163 -0
  212. package/dist/replica/query-plan/types.d.ts +89 -0
  213. package/dist/replica/query-plan/types.js +1 -0
  214. package/dist/replica/query-plan/util.d.ts +13 -0
  215. package/dist/replica/query-plan/util.js +62 -0
  216. package/dist/replica/query-plan.d.ts +3 -0
  217. package/dist/replica/query-plan.js +2 -0
  218. package/dist/replica/revalidation.d.ts +9 -0
  219. package/dist/replica/revalidation.js +86 -0
  220. package/dist/replica/selection.d.ts +20 -0
  221. package/dist/replica/selection.js +109 -0
  222. package/dist/replica/types.d.ts +657 -0
  223. package/dist/replica/types.js +1 -0
  224. package/dist/request.d.ts +17 -0
  225. package/dist/request.js +123 -0
  226. package/dist/sveltekit/auth.d.ts +18 -0
  227. package/dist/sveltekit/auth.js +9 -0
  228. package/dist/sveltekit/context.d.ts +28 -0
  229. package/dist/sveltekit/context.js +58 -0
  230. package/dist/sveltekit/index.d.ts +4 -0
  231. package/dist/sveltekit/index.js +4 -0
  232. package/dist/sveltekit/replica.d.ts +145 -0
  233. package/dist/sveltekit/replica.js +491 -0
  234. package/dist/sveltekit/server-replica.d.ts +52 -0
  235. package/dist/sveltekit/server-replica.js +176 -0
  236. package/dist/sveltekit/vite.d.ts +103 -0
  237. package/dist/sveltekit/vite.js +878 -0
  238. package/dist/types.d.ts +33 -0
  239. package/dist/types.js +1 -0
  240. package/dist/websocket.d.ts +41 -0
  241. package/dist/websocket.js +187 -0
  242. package/package.json +95 -0
@@ -0,0 +1,1776 @@
1
+ import { CacheRevisionConflictError, createCacheEngine } from '../../internal/cache-engine.js';
2
+ import { compareDistributedDecimal, DistributedProtocolError, parseGraphqlResponseExtensions } from '../../protocol.js';
3
+ import { matchReplicaTrustedPresetInventory } from '../commands.js';
4
+ import { replicaCommandAuthority, replicaCommandDirectProjection, replicaResultObservation } from '../command-runtime.js';
5
+ import { canonicalizeOperationVariables, replicaIndexKey, replicaRecordKey, resolveArguments } from '../identity.js';
6
+ import { materializeReplicaOperation } from '../materialize.js';
7
+ import { normalizeReplicaResult } from '../normalize.js';
8
+ import { createReplicaRevalidationMatcher } from '../revalidation.js';
9
+ import { validateReplicaOperationBinding as validatedArtifactBinding } from '../operation-binding.js';
10
+ import { createReplicaIndexMaintenanceRegistry, formatReplicaIndexStaleReason } from '../index-maintenance.js';
11
+ import { EMPTY_ERRORS, EMPTY_TRUSTED_PRESETS, MAX_ANONYMOUS_RECORD_CLOCKS } from './constants.js';
12
+ import { compareCanonicalDecimalStrings, compareEvidenceToProjectedFence, compareIndexVector, compareProjectedRecordFields, compareRecordClock, compareSnapshotToOperationState, incrementCanonicalDecimal, indexClockMap, isComparableHandoffDisposition, latestCursors, protocolInvalid, recordKeyMatchesModel, responsePathKey, sameRecordClock, sameRecordRevision } from './clocks.js';
13
+ import { diagnosticReceiptCounts } from './optimistic.js';
14
+ import { assertWriteSource, indexKeyFromTarget, indexMaintenanceSnapshot, indexSemanticLayer, operationKey, prepareRecordEvidence, protocolOperationSource, replicaResultIndexKeys, reportSafely, reportUnhandledObserverError, snapshotFrom, stableErrors, trustedPresetDescriptorFingerprint, validatedCommandAuthorityContract } from './helpers.js';
15
+ import { ReplicaWatchState } from './watch.js';
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
+ 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';
19
+ import { dehydrateReplica, finishDehydration as finishDehydrationOn, hydrateReplica } from './impl-hydration-orchestrate.js';
20
+ import { diagnosticEvent as diagnosticEventOn, diagnosticOperation as diagnosticOperationOn, diagnosticScopeTransition as diagnosticScopeTransitionOn, retireDiagnosticLayer as retireDiagnosticLayerOn, syncDiagnostics as syncDiagnosticsOn } from './impl-diagnostics.js';
21
+ export class DistributedReplicaImpl {
22
+ #engine;
23
+ #transport;
24
+ #reportObserverError;
25
+ #diagnostics;
26
+ #diagnosticLayers;
27
+ #inFlight = new Map();
28
+ #inFlightAborts = new Map();
29
+ #queryStates = new Map();
30
+ #watches = new Map();
31
+ #lives = new Map();
32
+ #operationProtocols = new Map();
33
+ #operationGenerations = new Map();
34
+ #recordClocks = new Map();
35
+ #recordKeysByScope = new Map();
36
+ #projectedRecordFences = new Map();
37
+ #anonymousRecordClocks = new Map();
38
+ #optimisticReceipts = new Map();
39
+ #renderedOperations = new Map();
40
+ #readOperationKeys = new Set();
41
+ #watchRenderCounts = new Map();
42
+ #resultObservers = new Set();
43
+ #indexMaintenance = createReplicaIndexMaintenanceRegistry();
44
+ #indexPlanRegistrations = new Map();
45
+ #derivedIndexReconciler = (confirmed, layers) => this.#deriveMaintainedIndexes(confirmed, layers);
46
+ #commandAuthorityContract;
47
+ #trustedPresets = EMPTY_TRUSTED_PRESETS;
48
+ #authorizationAbort = new AbortController();
49
+ #artifactBinding;
50
+ #protocolGeneration;
51
+ #protocolGenerationSequence = 0;
52
+ #projectionGeneration = 0;
53
+ #nextIndexRevision = '0';
54
+ #diagnosticLayerSequence = 0;
55
+ #fetchLiveHostCache;
56
+ #protocolHostCache;
57
+ #optimisticHostCache;
58
+ #hydrationHostCache;
59
+ #diagnosticsHostCache;
60
+ constructor(options = {}) {
61
+ this.#transport = options.transport;
62
+ this.#reportObserverError = options.onObserverError ?? reportUnhandledObserverError;
63
+ this.#diagnostics = options.diagnostics;
64
+ this.#diagnosticLayers =
65
+ options.diagnostics === undefined ? undefined : new Map();
66
+ this.#engine = createCacheEngine({ onWatcherError: this.#reportObserverError });
67
+ this.#engine.setDerivedIndexReconciler(this.#derivedIndexReconciler);
68
+ this.#syncDiagnostics();
69
+ }
70
+ #diagnosticsEnabled() {
71
+ return Object.freeze({ enabled: this.#diagnostics !== undefined });
72
+ }
73
+ #diagnosticsHost() {
74
+ if (this.#diagnosticsHostCache !== undefined)
75
+ return this.#diagnosticsHostCache;
76
+ const self = this;
77
+ this.#diagnosticsHostCache = {
78
+ get engine() {
79
+ return self.#engine;
80
+ },
81
+ get diagnostics() {
82
+ return self.#diagnostics;
83
+ },
84
+ get diagnosticLayers() {
85
+ return self.#diagnosticLayers;
86
+ },
87
+ get optimisticReceipts() {
88
+ return self.#optimisticReceipts;
89
+ },
90
+ get reportObserverError() {
91
+ return self.#reportObserverError;
92
+ },
93
+ getProtocolGeneration: () => self.#protocolGeneration,
94
+ getProtocolGenerationSequence: () => self.#protocolGenerationSequence
95
+ };
96
+ return this.#diagnosticsHostCache;
97
+ }
98
+ #fetchLiveHost() {
99
+ if (this.#fetchLiveHostCache !== undefined)
100
+ return this.#fetchLiveHostCache;
101
+ const self = this;
102
+ this.#fetchLiveHostCache = {
103
+ get transport() {
104
+ return self.#transport;
105
+ },
106
+ get inFlight() {
107
+ return self.#inFlight;
108
+ },
109
+ get inFlightAborts() {
110
+ return self.#inFlightAborts;
111
+ },
112
+ get lives() {
113
+ return self.#lives;
114
+ },
115
+ get watches() {
116
+ return self.#watches;
117
+ },
118
+ get operationProtocols() {
119
+ return self.#operationProtocols;
120
+ },
121
+ get diagnostics() {
122
+ return self.#diagnosticsEnabled();
123
+ },
124
+ protocolGenerationSequence: () => self.#protocolGenerationSequence,
125
+ projectionGeneration: () => self.#projectionGeneration,
126
+ protocolGeneration: () => self.#protocolGeneration,
127
+ queryState: (key) => self.#queryState(key),
128
+ emitState: (key, allowFetch) => self.#emitState(key, allowFetch),
129
+ operationGeneration: (key) => self.#operationGeneration(key),
130
+ allocateIndexRevision: () => self.#allocateIndexRevision(),
131
+ writeCanonicalResult: (artifact, stableVariables, envelope, source, requestRevision, responseProjectionGeneration) => self.#writeCanonicalResult(artifact, stableVariables, envelope, source, requestRevision, responseProjectionGeneration),
132
+ diagnosticEvent: (event) => self.#diagnosticEvent(event),
133
+ resumeCursors: (key) => self.#resumeCursors(key)
134
+ };
135
+ return this.#fetchLiveHostCache;
136
+ }
137
+ #protocolHost() {
138
+ if (this.#protocolHostCache !== undefined)
139
+ return this.#protocolHostCache;
140
+ const self = this;
141
+ this.#protocolHostCache = {
142
+ get engine() {
143
+ return self.#engine;
144
+ },
145
+ get queryStates() {
146
+ return self.#queryStates;
147
+ },
148
+ get operationProtocols() {
149
+ return self.#operationProtocols;
150
+ },
151
+ get operationGenerations() {
152
+ return self.#operationGenerations;
153
+ },
154
+ get recordClocks() {
155
+ return self.#recordClocks;
156
+ },
157
+ get recordKeysByScope() {
158
+ return self.#recordKeysByScope;
159
+ },
160
+ get projectedRecordFences() {
161
+ return self.#projectedRecordFences;
162
+ },
163
+ get anonymousRecordClocks() {
164
+ return self.#anonymousRecordClocks;
165
+ },
166
+ get optimisticReceipts() {
167
+ return self.#optimisticReceipts;
168
+ },
169
+ get diagnosticLayers() {
170
+ return self.#diagnosticLayers;
171
+ },
172
+ get indexPlanRegistrations() {
173
+ return self.#indexPlanRegistrations;
174
+ },
175
+ get renderedOperations() {
176
+ return self.#renderedOperations;
177
+ },
178
+ get readOperationKeys() {
179
+ return self.#readOperationKeys;
180
+ },
181
+ get watchRenderCounts() {
182
+ return self.#watchRenderCounts;
183
+ },
184
+ get watches() {
185
+ return self.#watches;
186
+ },
187
+ get diagnostics() {
188
+ return self.#diagnosticsEnabled();
189
+ },
190
+ getProtocolGeneration: () => self.#protocolGeneration,
191
+ setProtocolGeneration: (value) => {
192
+ self.#protocolGeneration = value;
193
+ },
194
+ getProtocolGenerationSequence: () => self.#protocolGenerationSequence,
195
+ bumpProtocolGenerationSequence: () => {
196
+ self.#protocolGenerationSequence += 1;
197
+ },
198
+ getTrustedPresets: () => self.#trustedPresets,
199
+ setTrustedPresets: (value) => {
200
+ self.#trustedPresets = value;
201
+ },
202
+ getCommandAuthorityContract: () => self.#commandAuthorityContract,
203
+ setProjectionGeneration: (value) => {
204
+ self.#projectionGeneration = value;
205
+ },
206
+ setNextIndexRevision: (value) => {
207
+ self.#nextIndexRevision = value;
208
+ },
209
+ setDiagnosticLayerSequence: (value) => {
210
+ self.#diagnosticLayerSequence = value;
211
+ },
212
+ clearIndexMaintenance: () => {
213
+ self.#indexMaintenance.clear();
214
+ },
215
+ abortAuthorization: () => {
216
+ self.#authorizationAbort.abort();
217
+ self.#authorizationAbort = new AbortController();
218
+ },
219
+ closeActiveTransports: () => self.#closeActiveTransports(),
220
+ syncDiagnostics: () => self.#syncDiagnostics(),
221
+ emitState: (key, allowFetch) => self.#emitState(key, allowFetch),
222
+ diagnosticEvent: (event) => self.#diagnosticEvent(event),
223
+ rememberRenderedOperation: (key, artifact, variables, source) => self.#rememberRenderedOperation(key, artifact, variables, source)
224
+ };
225
+ return this.#protocolHostCache;
226
+ }
227
+ #optimisticHost() {
228
+ if (this.#optimisticHostCache !== undefined)
229
+ return this.#optimisticHostCache;
230
+ const self = this;
231
+ this.#optimisticHostCache = {
232
+ get engine() {
233
+ return self.#engine;
234
+ },
235
+ get optimisticReceipts() {
236
+ return self.#optimisticReceipts;
237
+ },
238
+ get diagnosticLayers() {
239
+ return self.#diagnosticLayers;
240
+ },
241
+ get diagnostics() {
242
+ return self.#diagnosticsEnabled();
243
+ },
244
+ getDiagnosticLayerSequence: () => self.#diagnosticLayerSequence,
245
+ setDiagnosticLayerSequence: (value) => {
246
+ self.#diagnosticLayerSequence = value;
247
+ },
248
+ diagnosticEvent: (event) => self.#diagnosticEvent(event),
249
+ syncDiagnostics: () => self.#syncDiagnostics(),
250
+ retireDiagnosticLayer: (id, action, receiptState, receipt) => self.#retireDiagnosticLayer(id, action, receiptState, receipt)
251
+ };
252
+ return this.#optimisticHostCache;
253
+ }
254
+ #hydrationHost() {
255
+ if (this.#hydrationHostCache !== undefined)
256
+ return this.#hydrationHostCache;
257
+ const self = this;
258
+ this.#hydrationHostCache = {
259
+ get engine() {
260
+ return self.#engine;
261
+ },
262
+ get operationProtocols() {
263
+ return self.#operationProtocols;
264
+ },
265
+ get operationGenerations() {
266
+ return self.#operationGenerations;
267
+ },
268
+ get recordClocks() {
269
+ return self.#recordClocks;
270
+ },
271
+ get recordKeysByScope() {
272
+ return self.#recordKeysByScope;
273
+ },
274
+ get anonymousRecordClocks() {
275
+ return self.#anonymousRecordClocks;
276
+ },
277
+ get optimisticReceipts() {
278
+ return self.#optimisticReceipts;
279
+ },
280
+ get diagnosticLayers() {
281
+ return self.#diagnosticLayers;
282
+ },
283
+ get renderedOperations() {
284
+ return self.#renderedOperations;
285
+ },
286
+ get readOperationKeys() {
287
+ return self.#readOperationKeys;
288
+ },
289
+ get watchRenderCounts() {
290
+ return self.#watchRenderCounts;
291
+ },
292
+ get indexPlanRegistrations() {
293
+ return self.#indexPlanRegistrations;
294
+ },
295
+ get queryStates() {
296
+ return self.#queryStates;
297
+ },
298
+ get diagnostics() {
299
+ return self.#diagnosticsEnabled();
300
+ },
301
+ getProtocolGeneration: () => self.#protocolGeneration,
302
+ setProtocolGeneration: (value) => {
303
+ self.#protocolGeneration = value;
304
+ },
305
+ getProtocolGenerationSequence: () => self.#protocolGenerationSequence,
306
+ getTrustedPresets: () => self.#trustedPresets,
307
+ setTrustedPresets: (value) => {
308
+ self.#trustedPresets = value;
309
+ },
310
+ getNextIndexRevision: () => self.#nextIndexRevision,
311
+ setNextIndexRevision: (value) => {
312
+ self.#nextIndexRevision = value;
313
+ },
314
+ getArtifactBinding: () => self.#artifactBinding,
315
+ setArtifactBinding: (value) => {
316
+ self.#artifactBinding = value;
317
+ },
318
+ getCommandAuthorityContract: () => self.#commandAuthorityContract,
319
+ setDiagnosticLayerSequence: (value) => {
320
+ self.#diagnosticLayerSequence = value;
321
+ },
322
+ operationGeneration: (key) => self.#operationGeneration(key),
323
+ closeActiveTransports: () => self.#closeActiveTransports(),
324
+ closeAuthorizationGeneration: () => self.#closeAuthorizationGeneration(),
325
+ resumeLiveWatches: () => self.#resumeLiveWatches(),
326
+ syncDiagnostics: () => self.#syncDiagnostics(),
327
+ diagnosticEvent: (event) => self.#diagnosticEvent(event),
328
+ refreshIndexMaintenance: () => self.#refreshIndexMaintenance(),
329
+ finishDehydration: () => self.#finishDehydration()
330
+ };
331
+ return this.#hydrationHostCache;
332
+ }
333
+ get scope() {
334
+ const scope = this.#protocolGeneration;
335
+ return scope === undefined
336
+ ? undefined
337
+ : Object.freeze({
338
+ protocolVersion: scope.protocolVersion,
339
+ schemaHash: scope.schemaHash,
340
+ cacheScope: scope.cacheScope
341
+ });
342
+ }
343
+ get authorizationGeneration() {
344
+ return this.#protocolGenerationSequence;
345
+ }
346
+ [replicaCommandAuthority](contract) {
347
+ const next = validatedCommandAuthorityContract(contract);
348
+ const current = this.#commandAuthorityContract;
349
+ if (current !== undefined && current.fingerprint !== next.fingerprint) {
350
+ throw new TypeError('command inventory does not match the active replica client surface');
351
+ }
352
+ const binding = this.#artifactBinding;
353
+ if (binding === undefined) {
354
+ this.#artifactBinding = Object.freeze({
355
+ version: 1,
356
+ schemaHash: next.schemaHash,
357
+ surfaceIdentity: next.surfaceIdentity,
358
+ trustedPresets: next.trustedPresets
359
+ });
360
+ }
361
+ else if (binding.version !== 1 ||
362
+ binding.schemaHash !== next.schemaHash ||
363
+ (binding.surfaceIdentity !== undefined &&
364
+ binding.surfaceIdentity !== next.surfaceIdentity) ||
365
+ (binding.trustedPresets !== undefined &&
366
+ trustedPresetDescriptorFingerprint(binding.trustedPresets) !==
367
+ trustedPresetDescriptorFingerprint(next.trustedPresets))) {
368
+ throw new TypeError('command inventory does not match the active replica client surface');
369
+ }
370
+ else if (binding.surfaceIdentity === undefined ||
371
+ binding.trustedPresets === undefined) {
372
+ this.#artifactBinding = Object.freeze({
373
+ ...binding,
374
+ ...(binding.surfaceIdentity === undefined
375
+ ? { surfaceIdentity: next.surfaceIdentity }
376
+ : {}),
377
+ ...(binding.trustedPresets === undefined
378
+ ? { trustedPresets: next.trustedPresets }
379
+ : {})
380
+ });
381
+ }
382
+ if (this.#protocolGeneration !== undefined) {
383
+ try {
384
+ matchReplicaTrustedPresetInventory(next.trustedPresets, this.#trustedPresets);
385
+ }
386
+ catch (error) {
387
+ this.#purgeProtocolGeneration();
388
+ throw error;
389
+ }
390
+ }
391
+ this.#commandAuthorityContract = next;
392
+ let active = true;
393
+ const read = () => {
394
+ if (!active) {
395
+ throw new TypeError('replica command authority registration is disposed');
396
+ }
397
+ return Object.freeze({
398
+ generation: this.#protocolGenerationSequence,
399
+ scope: this.scope,
400
+ trustedPresets: this.#trustedPresets,
401
+ signal: this.#authorizationAbort.signal
402
+ });
403
+ };
404
+ return Object.freeze({
405
+ read,
406
+ dispose() {
407
+ active = false;
408
+ }
409
+ });
410
+ }
411
+ [replicaResultObservation](observer) {
412
+ if (typeof observer !== 'function') {
413
+ throw new TypeError('replica result observer must be a function');
414
+ }
415
+ this.#resultObservers.add(observer);
416
+ let active = true;
417
+ return Object.freeze({
418
+ dispose: () => {
419
+ if (!active)
420
+ return;
421
+ active = false;
422
+ this.#resultObservers.delete(observer);
423
+ }
424
+ });
425
+ }
426
+ [replicaCommandDirectProjection](commandId, projection) {
427
+ const { model, identity, evidence, fields } = projection;
428
+ if (evidence.model !== model.id || evidence.tombstone) {
429
+ protocolInvalid('extensions.distributed.command.records');
430
+ }
431
+ const recordKey = replicaRecordKey(model, identity);
432
+ const pendingRecordClocks = new Map();
433
+ const pendingRecordScopes = new Map();
434
+ const pendingAnonymousRecordClocks = new Map();
435
+ const consumedAnonymousRecordClocks = new Set();
436
+ const apply = this.#resolveRecordEvidence(recordKey, evidence, pendingRecordClocks, pendingRecordScopes, pendingAnonymousRecordClocks, consumedAnonymousRecordClocks);
437
+ this.confirmOptimisticLayer(commandId, (writer) => {
438
+ if (!apply)
439
+ return false;
440
+ return writer.writeRecord(model, identity, evidence.revision, {
441
+ incarnation: evidence.incarnation,
442
+ fields
443
+ });
444
+ });
445
+ for (const [key, clock] of pendingRecordClocks) {
446
+ this.#recordClocks.set(key, clock);
447
+ this.#recordKeysByScope.set(clock.scopeToken, key);
448
+ }
449
+ for (const [scopeToken, key] of pendingRecordScopes) {
450
+ this.#recordKeysByScope.set(scopeToken, key);
451
+ }
452
+ for (const [scopeToken, clock] of pendingAnonymousRecordClocks) {
453
+ if (!consumedAnonymousRecordClocks.has(scopeToken)) {
454
+ this.#anonymousRecordClocks.set(scopeToken, clock);
455
+ }
456
+ }
457
+ for (const scopeToken of consumedAnonymousRecordClocks) {
458
+ this.#anonymousRecordClocks.delete(scopeToken);
459
+ }
460
+ if (apply) {
461
+ this.#projectionGeneration += 1;
462
+ /*
463
+ * The command result is authoritative before an asynchronous read
464
+ * model necessarily catches up. Retain its complete row and causal
465
+ * clock as a write fence until a query acknowledges it or a newer
466
+ * record/tombstone supersedes it.
467
+ */
468
+ this.#projectedRecordFences.set(recordKey, Object.freeze({
469
+ fields: Object.freeze({ ...fields }),
470
+ clock: Object.freeze({
471
+ scopeToken: evidence.scopeToken,
472
+ incarnation: evidence.incarnation,
473
+ revision: evidence.revision,
474
+ tombstone: false
475
+ }),
476
+ projectionGeneration: this.#projectionGeneration
477
+ }));
478
+ }
479
+ }
480
+ read(artifact, variables) {
481
+ this.#bindArtifact(artifact);
482
+ const stableVariables = canonicalizeOperationVariables(artifact, variables);
483
+ const key = operationKey(artifact, stableVariables);
484
+ this.#rememberRenderedOperation(key, artifact, stableVariables, 'read');
485
+ const materialized = this.#engine.read((reader) => materializeReplicaOperation(reader, artifact, stableVariables));
486
+ return snapshotFrom(materialized, this.#queryState(key));
487
+ }
488
+ watch(artifact, variables, options = {}) {
489
+ this.#bindArtifact(artifact);
490
+ return new ReplicaWatchState(this, artifact, variables, options);
491
+ }
492
+ writeResult(artifact, variables, envelope, source) {
493
+ assertWriteSource(source);
494
+ this.#bindArtifact(artifact);
495
+ const stableVariables = canonicalizeOperationVariables(artifact, variables);
496
+ this.#writeCanonicalResult(artifact, stableVariables, envelope, source);
497
+ }
498
+ revalidate(plan) {
499
+ const matches = createReplicaRevalidationMatcher(plan);
500
+ const generation = this.#protocolGenerationSequence;
501
+ const active = new Map();
502
+ for (const [key, watches] of this.#watches) {
503
+ const watch = watches.values().next().value;
504
+ if (watch !== undefined && matches(watch.artifact)) {
505
+ active.set(key, watch);
506
+ }
507
+ }
508
+ /*
509
+ * A command-status observation may arrive while an older query is
510
+ * already in flight. Drain that request first; the authoritative
511
+ * revalidation below must start after the command fence.
512
+ */
513
+ const prior = [...active.keys()].flatMap((key) => {
514
+ const request = this.#inFlight.get(key);
515
+ return request === undefined ? [] : [request];
516
+ });
517
+ return Promise.all(prior)
518
+ .then(() => {
519
+ if (this.#protocolGenerationSequence !== generation)
520
+ return [];
521
+ /*
522
+ * Stale every rendered matching root before fetching. This both
523
+ * starts active watches through their normal coordinator and
524
+ * leaves inactive SSR/read consumers fail-closed until a future
525
+ * watch can refresh them.
526
+ */
527
+ const rootKeys = new Set();
528
+ for (const rendered of this.#renderedOperations.values()) {
529
+ if (!matches(rendered.artifact))
530
+ continue;
531
+ for (const root of rendered.artifact.roots) {
532
+ rootKeys.add(replicaIndexKey({
533
+ field: root.field,
534
+ arguments: resolveArguments(root.arguments, rendered.variables, root.coverage)
535
+ }));
536
+ }
537
+ }
538
+ for (const key of rootKeys) {
539
+ this.#engine.batch((writer) => writer.markIndexStale(key, 'command-authoritative-revalidation'));
540
+ }
541
+ // One operation key may have several component subscribers. The
542
+ // existing fetch coordinator owns deduplication and response fences.
543
+ return [...active.values()].map((watch) => this._fetch(watch, true).then(() => watch));
544
+ })
545
+ .then((requests) => Promise.all(requests))
546
+ .then((watches) => {
547
+ if (this.#protocolGenerationSequence !== generation)
548
+ return;
549
+ const failed = watches.some((watch) => {
550
+ const state = this.#queryState(watch.key);
551
+ /*
552
+ * Revalidation proves that the authoritative HTTP response
553
+ * refreshed the confirmed graph. A surviving accepted layer
554
+ * may deliberately keep the visible index stale when its row
555
+ * policy cannot be evaluated locally; the command runtime
556
+ * retires that layer only after this proof succeeds.
557
+ */
558
+ const confirmed = this.#engine.readConfirmed((reader) => materializeReplicaOperation(reader, watch.artifact, watch.variables));
559
+ return (state.errors.length > 0 ||
560
+ !confirmed.complete ||
561
+ confirmed.stale);
562
+ });
563
+ if (failed) {
564
+ throw new Error('authoritative command revalidation did not produce a complete result');
565
+ }
566
+ });
567
+ }
568
+ invalidateAuthorization() {
569
+ this.#purgeProtocolGeneration();
570
+ }
571
+ dehydrate() {
572
+ return dehydrateReplica(this.#hydrationHost());
573
+ }
574
+ hydrate(state, authoritativeScope) {
575
+ return hydrateReplica(this.#hydrationHost(), state, authoritativeScope);
576
+ }
577
+ #bindArtifact(artifact) {
578
+ const next = validatedArtifactBinding(artifact);
579
+ this.#validateActiveArtifactTrustedPresets(next);
580
+ const current = this.#artifactBinding;
581
+ if (current === undefined) {
582
+ this.#artifactBinding = Object.freeze({
583
+ version: next.version,
584
+ schemaHash: next.schemaHash,
585
+ surfaceIdentity: next.surfaceIdentity,
586
+ trustedPresets: next.trustedPresets
587
+ });
588
+ return;
589
+ }
590
+ if ((current.surfaceIdentity === undefined ||
591
+ current.trustedPresets === undefined) &&
592
+ current.version === next.version &&
593
+ current.schemaHash === next.schemaHash &&
594
+ (current.surfaceIdentity === undefined ||
595
+ current.surfaceIdentity === next.surfaceIdentity) &&
596
+ (current.trustedPresets === undefined ||
597
+ trustedPresetDescriptorFingerprint(current.trustedPresets) ===
598
+ trustedPresetDescriptorFingerprint(next.trustedPresets))) {
599
+ this.#artifactBinding = Object.freeze({
600
+ ...current,
601
+ surfaceIdentity: next.surfaceIdentity,
602
+ trustedPresets: next.trustedPresets
603
+ });
604
+ return;
605
+ }
606
+ if (current.version !== next.version ||
607
+ current.schemaHash !== next.schemaHash ||
608
+ (current.surfaceIdentity !== undefined &&
609
+ current.surfaceIdentity !== next.surfaceIdentity) ||
610
+ (current.trustedPresets !== undefined &&
611
+ trustedPresetDescriptorFingerprint(current.trustedPresets) !== trustedPresetDescriptorFingerprint(next.trustedPresets))) {
612
+ throw new TypeError('replica artifact schema does not match the active replica binding');
613
+ }
614
+ }
615
+ #validateActiveArtifactTrustedPresets(binding) {
616
+ if (this.#protocolGeneration === undefined) {
617
+ return;
618
+ }
619
+ try {
620
+ matchReplicaTrustedPresetInventory(binding.trustedPresets, this.#trustedPresets);
621
+ }
622
+ catch (error) {
623
+ this.#purgeProtocolGeneration();
624
+ throw error;
625
+ }
626
+ }
627
+ #writeCanonicalResult(artifact, stableVariables, envelope, source, requestRevision, responseProjectionGeneration) {
628
+ const extensions = parseGraphqlResponseExtensions(envelope.extensions);
629
+ const parsedEnvelope = Object.freeze({
630
+ ...envelope,
631
+ ...(extensions === undefined ? {} : { extensions })
632
+ });
633
+ const key = operationKey(artifact, stableVariables);
634
+ const distributed = extensions?.distributed;
635
+ if (distributed === undefined) {
636
+ protocolInvalid('extensions.distributed');
637
+ }
638
+ const accepted = this.#writeProtocolResult(key, artifact, stableVariables, parsedEnvelope, source, distributed, requestRevision, responseProjectionGeneration);
639
+ if (accepted)
640
+ this.#notifyResultObservers(parsedEnvelope);
641
+ return distributed;
642
+ }
643
+ #writeProtocolResult(key, artifact, stableVariables, envelope, source, distributed, requestRevision, responseProjectionGeneration) {
644
+ this.#validateProtocolBinding(artifact, distributed, source);
645
+ const previousProtocolGeneration = this.#protocolGeneration;
646
+ const nextProtocolGeneration = this.#stageProtocolGeneration(distributed);
647
+ const nextTrustedPresets = this.#stageTrustedPresets(distributed.trustedPresets, nextProtocolGeneration, artifact);
648
+ const snapshot = distributed.snapshot;
649
+ const live = distributed.live;
650
+ if (live !== undefined && snapshot === undefined) {
651
+ protocolInvalid('extensions.distributed.snapshot');
652
+ }
653
+ if (source === 'live' &&
654
+ (envelope.data !== undefined || envelope.errors !== undefined) &&
655
+ (snapshot === undefined || live === undefined)) {
656
+ protocolInvalid('extensions.distributed.live');
657
+ }
658
+ const operation = distributed.operation;
659
+ if ((snapshot !== undefined || live !== undefined) && operation === undefined) {
660
+ protocolInvalid('extensions.distributed.operation');
661
+ }
662
+ const operationSource = protocolOperationSource(source);
663
+ const operationState = operation === undefined
664
+ ? undefined
665
+ : this.#operationProtocol(key, operation, operationSource);
666
+ if (snapshot === undefined || operationState === undefined) {
667
+ this.#applyReceiptOnly(distributed.command);
668
+ this.#trustedPresets = nextTrustedPresets;
669
+ this.#protocolGeneration = nextProtocolGeneration;
670
+ this.#resumeLiveWatches();
671
+ this.#diagnosticScopeTransition(previousProtocolGeneration, nextProtocolGeneration);
672
+ this.#syncDiagnostics();
673
+ return true;
674
+ }
675
+ this.#validateLiveSnapshot(snapshot, live);
676
+ const unsupportedLive = source === 'live' && live?.supported === false;
677
+ const reset = live?.reset === true;
678
+ const group = this.#operationProtocols.get(key);
679
+ /*
680
+ * An unsupported subscription response is an authorized fallback
681
+ * snapshot, not a live source. In particular, a row-filtered snapshot
682
+ * has no comparable index vector, so retaining live ownership here
683
+ * would reject every later query handoff. Relinquish any prior live
684
+ * ownership without advancing the generation; the forced HTTP fallback
685
+ * starts against the generation that remains after this frame.
686
+ */
687
+ if (unsupportedLive && group.active === 'live') {
688
+ group.active = undefined;
689
+ }
690
+ const previousActiveSource = group.active;
691
+ const handoff = previousActiveSource !== undefined &&
692
+ previousActiveSource !== operationSource;
693
+ const activeState = previousActiveSource === undefined
694
+ ? undefined
695
+ : group[previousActiveSource];
696
+ const ownDisposition = reset
697
+ ? 'fresh'
698
+ : compareSnapshotToOperationState(operationState, snapshot);
699
+ const activeDisposition = handoff && activeState !== undefined
700
+ ? compareSnapshotToOperationState(activeState, snapshot)
701
+ : 'fresh';
702
+ const sharedDisposition = this.#sharedIndexDisposition(key, replicaResultIndexKeys(artifact, stableVariables, envelope, snapshot), snapshot, requestRevision, source);
703
+ const handoffBlocked = handoff &&
704
+ (!snapshot.indexesComparable ||
705
+ !isComparableHandoffDisposition(ownDisposition) ||
706
+ !isComparableHandoffDisposition(activeDisposition));
707
+ let disposition = handoffBlocked
708
+ ? !isComparableHandoffDisposition(activeDisposition)
709
+ ? activeDisposition
710
+ : !isComparableHandoffDisposition(ownDisposition)
711
+ ? ownDisposition
712
+ : 'incomparable'
713
+ : ownDisposition;
714
+ if (!handoffBlocked &&
715
+ isComparableHandoffDisposition(disposition) &&
716
+ sharedDisposition.compared) {
717
+ disposition =
718
+ sharedDisposition.disposition === 'lower'
719
+ ? 'lower'
720
+ : sharedDisposition.disposition ?? disposition;
721
+ }
722
+ const sourceSwitched = !unsupportedLive &&
723
+ !handoffBlocked &&
724
+ isComparableHandoffDisposition(disposition) &&
725
+ this.#activateOperationSource(key, operationSource, artifact, stableVariables);
726
+ const rejectedHandoff = handoff && !sourceSwitched;
727
+ if (snapshot.indexesComparable &&
728
+ (reset || ownDisposition === 'incomparable')) {
729
+ if (rejectedHandoff) {
730
+ this.#resetOperationState(operationState);
731
+ }
732
+ else {
733
+ this.#discardOperationSnapshot(operationState, artifact, stableVariables);
734
+ }
735
+ }
736
+ if (sourceSwitched && disposition !== 'incomparable') {
737
+ // Query and live operation hashes describe independent protocol
738
+ // streams. A handoff is authoritative, but must receive a fresh local
739
+ // index revision rather than reusing the inactive stream's revision.
740
+ disposition = 'fresh';
741
+ }
742
+ if (!snapshot.indexesComparable) {
743
+ if (rejectedHandoff) {
744
+ this.#resetOperationState(operationState);
745
+ }
746
+ else {
747
+ /*
748
+ * The server-authorized GraphQL payload is still an exact
749
+ * replacement result. Only its partition-wide causal vector is
750
+ * unavailable (most commonly because exposing that position
751
+ * would leak denied-row activity). Preserve local membership,
752
+ * while dropping every capability that requires comparison.
753
+ */
754
+ this.#resetOperationCausalState(operationState);
755
+ }
756
+ disposition = rejectedHandoff
757
+ ? 'incomparable'
758
+ : sharedDisposition.disposition === 'lower'
759
+ ? 'lower'
760
+ : 'fresh';
761
+ }
762
+ else if (disposition === 'incomparable') {
763
+ if (rejectedHandoff) {
764
+ this.#resetOperationState(operationState);
765
+ }
766
+ else {
767
+ this.#discardOperationSnapshot(operationState, artifact, stableVariables);
768
+ }
769
+ }
770
+ const writeIndexes = !rejectedHandoff &&
771
+ disposition !== 'lower' &&
772
+ disposition !== 'incomparable';
773
+ /*
774
+ * Revision zero is the cache engine's lowest legal checkpoint. It lets
775
+ * an unsupported live response fill an empty cache immediately while
776
+ * guaranteeing that any HTTP request revision can replace it.
777
+ */
778
+ const indexRevision = unsupportedLive
779
+ ? '0'
780
+ : writeIndexes &&
781
+ (sourceSwitched ||
782
+ sharedDisposition.disposition === 'higher')
783
+ ? this.#allocateIndexRevision()
784
+ : writeIndexes &&
785
+ sharedDisposition.disposition === 'equal' &&
786
+ sharedDisposition.indexRevision !== undefined
787
+ ? sharedDisposition.indexRevision
788
+ : snapshot.indexesComparable &&
789
+ disposition === 'equal' &&
790
+ operationState.indexRevision !== undefined
791
+ ? operationState.indexRevision
792
+ : (requestRevision ?? this.#allocateIndexRevision());
793
+ const recordEvidence = prepareRecordEvidence(snapshot, distributed.command?.records ?? Object.freeze([]));
794
+ const pendingRecordClocks = new Map();
795
+ const pendingRecordScopes = new Map();
796
+ const pendingAnonymousRecordClocks = new Map();
797
+ const consumedAnonymousRecordClocks = new Set();
798
+ const pendingPathRecords = new Map();
799
+ const pendingProjectedRecordFenceClears = new Map();
800
+ const consumedRecordPaths = new Set();
801
+ const observationsAdmissible = snapshot.recordsComplete &&
802
+ snapshot.indexesComparable &&
803
+ disposition !== 'incomparable' &&
804
+ (disposition !== 'lower' ||
805
+ this.#operationProtocols.get(key)?.active ===
806
+ operationSource);
807
+ const receiptPlan = this.#planOptimisticReceipts(distributed.command, [
808
+ ...(distributed.command?.observations ?? []),
809
+ ...(observationsAdmissible ? snapshot.observations : [])
810
+ ], observationsAdmissible);
811
+ const normalizationProtocol = {
812
+ indexRevision,
813
+ writeIndexes,
814
+ /*
815
+ * This is cache-membership completeness, not causal-vector
816
+ * comparability. normalizeReplicaResult independently downgrades
817
+ * GraphQL path errors and missing selected fields.
818
+ */
819
+ indexesComplete: true,
820
+ allowSnapshotOnlyRecords: !snapshot.recordsComplete,
821
+ record: (path, model, recordKey, fields) => {
822
+ const encodedPath = responsePathKey(path);
823
+ const evidence = recordEvidence.byPath.get(encodedPath);
824
+ if (evidence === undefined) {
825
+ if (snapshot.recordsComplete) {
826
+ protocolInvalid('extensions.distributed.snapshot.records');
827
+ }
828
+ return undefined;
829
+ }
830
+ if (evidence.model !== model) {
831
+ protocolInvalid('extensions.distributed.snapshot.records.model');
832
+ }
833
+ if (evidence.tombstone) {
834
+ protocolInvalid('extensions.distributed.snapshot.records.tombstone');
835
+ }
836
+ consumedRecordPaths.add(encodedPath);
837
+ const projectedFence = this.#projectedRecordFences.get(recordKey);
838
+ const projectedDisposition = projectedFence === undefined
839
+ ? undefined
840
+ : compareProjectedRecordFields(projectedFence.fields, fields);
841
+ const projectedClockComparison = projectedFence === undefined
842
+ ? undefined
843
+ : compareEvidenceToProjectedFence(evidence, projectedFence);
844
+ const newerPostProjectionRow = projectedFence !== undefined &&
845
+ projectedDisposition === 'conflict' &&
846
+ projectedClockComparison === 1 &&
847
+ responseProjectionGeneration !== undefined &&
848
+ responseProjectionGeneration >=
849
+ projectedFence.projectionGeneration;
850
+ /*
851
+ * Snapshot bodies can race: SQL may be read before a command
852
+ * while response evidence is stamped after it. Conflicting
853
+ * pre-command responses therefore cannot override a projected
854
+ * fence, even when their evidence revision is numerically later.
855
+ * A causally newer row from an HTTP request or live frame that
856
+ * began after the command is an atomic supersession and may
857
+ * release the fence without first echoing the projected body.
858
+ */
859
+ if (projectedFence !== undefined) {
860
+ const exactEcho = projectedDisposition === 'complete' &&
861
+ (projectedClockComparison === 0 ||
862
+ projectedClockComparison === 1);
863
+ const newerPartial = projectedDisposition === 'partial' &&
864
+ projectedClockComparison === 1;
865
+ if (exactEcho || newerPartial || newerPostProjectionRow) {
866
+ pendingProjectedRecordFenceClears.set(recordKey, projectedFence);
867
+ }
868
+ }
869
+ const resolution = this.#resolveRecordEvidence(recordKey, evidence, pendingRecordClocks, pendingRecordScopes, pendingAnonymousRecordClocks, consumedAnonymousRecordClocks, projectedDisposition === 'conflict' &&
870
+ !newerPostProjectionRow);
871
+ pendingPathRecords.set(encodedPath, recordKey);
872
+ return Object.freeze({ evidence, apply: resolution });
873
+ }
874
+ };
875
+ const state = this.#queryState(key);
876
+ const previousErrors = state.errors;
877
+ state.errors = stableErrors(previousErrors, envelope.errors ?? []);
878
+ let summary;
879
+ try {
880
+ const update = (writer) => {
881
+ this.#applyTombstoneEvidence(writer, recordEvidence.tombstones, operationState, pendingRecordClocks, pendingRecordScopes, pendingAnonymousRecordClocks, consumedAnonymousRecordClocks, consumedRecordPaths, pendingProjectedRecordFenceClears);
882
+ const normalized = normalizeReplicaResult(writer, artifact, stableVariables, envelope, normalizationProtocol);
883
+ for (const path of recordEvidence.livePaths) {
884
+ if (!consumedRecordPaths.has(path)) {
885
+ protocolInvalid('extensions.distributed.snapshot.records.path');
886
+ }
887
+ }
888
+ this.#applyPathlessEvidence(writer, recordEvidence.pathless, recordEvidence.byPath, consumedRecordPaths, pendingRecordClocks, pendingRecordScopes, pendingAnonymousRecordClocks, consumedAnonymousRecordClocks, pendingProjectedRecordFenceClears);
889
+ return normalized;
890
+ };
891
+ summary =
892
+ receiptPlan.satisfied.length === 0
893
+ ? this.#engine.batch(update)
894
+ : this.#engine.confirmOptimisticLayers(receiptPlan.satisfied, update);
895
+ }
896
+ catch (error) {
897
+ state.errors = previousErrors;
898
+ if (error instanceof CacheRevisionConflictError ||
899
+ error instanceof DistributedProtocolError) {
900
+ this.#discardOperationSnapshot(operationState, artifact, stableVariables);
901
+ }
902
+ this.#emitState(key, false);
903
+ throw error;
904
+ }
905
+ for (const [recordKey, clock] of pendingRecordClocks) {
906
+ this.#recordClocks.set(recordKey, clock);
907
+ this.#recordKeysByScope.set(clock.scopeToken, recordKey);
908
+ }
909
+ for (const [scopeToken, recordKey] of pendingRecordScopes) {
910
+ this.#recordKeysByScope.set(scopeToken, recordKey);
911
+ }
912
+ for (const [scopeToken, clock] of pendingAnonymousRecordClocks) {
913
+ if (!consumedAnonymousRecordClocks.has(scopeToken)) {
914
+ this.#anonymousRecordClocks.set(scopeToken, clock);
915
+ }
916
+ }
917
+ for (const scopeToken of consumedAnonymousRecordClocks) {
918
+ this.#anonymousRecordClocks.delete(scopeToken);
919
+ }
920
+ for (const [path, recordKey] of pendingPathRecords) {
921
+ operationState.pathRecords.set(path, recordKey);
922
+ }
923
+ for (const [recordKey, projectedFields] of pendingProjectedRecordFenceClears) {
924
+ if (this.#projectedRecordFences.get(recordKey) === projectedFields) {
925
+ this.#projectedRecordFences.delete(recordKey);
926
+ }
927
+ }
928
+ for (const [id, receipt] of receiptPlan.updates) {
929
+ if (receiptPlan.satisfied.includes(id)) {
930
+ this.#retireDiagnosticLayer(id, 'retired', 'projected', receipt);
931
+ this.#optimisticReceipts.delete(id);
932
+ }
933
+ else {
934
+ this.#optimisticReceipts.set(id, receipt);
935
+ this.#engine.markOptimisticLayerAccepted(id);
936
+ const layer = this.#diagnosticLayers?.get(id);
937
+ if (layer !== undefined) {
938
+ this.#diagnosticLayers.set(id, Object.freeze({ ...layer, state: 'accepted' }));
939
+ }
940
+ if (this.#diagnostics !== undefined) {
941
+ const counts = diagnosticReceiptCounts(receipt);
942
+ this.#diagnosticEvent(Object.freeze({
943
+ kind: 'receipt',
944
+ command: id,
945
+ state: counts.obligations === 0
946
+ ? 'accepted'
947
+ : 'accepted_pending_projection',
948
+ obligations: counts.obligations,
949
+ observed: counts.observed
950
+ }));
951
+ }
952
+ }
953
+ }
954
+ if (writeIndexes) {
955
+ operationState.indexRevision = indexRevision;
956
+ for (const indexKey of summary.indexKeys) {
957
+ operationState.indexKeys.add(indexKey);
958
+ }
959
+ if (snapshot.indexesComparable) {
960
+ operationState.snapshotScope = snapshot.scopeToken;
961
+ operationState.indexClocks = indexClockMap(snapshot.indexes);
962
+ operationState.cursors = latestCursors(snapshot, live);
963
+ }
964
+ else {
965
+ this.#resetOperationCausalState(operationState);
966
+ operationState.indexRevision = indexRevision;
967
+ for (const indexKey of summary.indexKeys) {
968
+ operationState.indexKeys.add(indexKey);
969
+ }
970
+ }
971
+ }
972
+ else if (live?.reset === true || !snapshot.indexesComparable) {
973
+ operationState.cursors = Object.freeze([]);
974
+ }
975
+ if (source === 'live' && !unsupportedLive) {
976
+ this.#advanceOperationGeneration(key);
977
+ }
978
+ if (source !== 'live' && sourceSwitched) {
979
+ this.#restartLive(key);
980
+ }
981
+ this.#trustedPresets = nextTrustedPresets;
982
+ this.#protocolGeneration = nextProtocolGeneration;
983
+ this.#resumeLiveWatches();
984
+ this.#emitState(key, false);
985
+ if (this.#diagnostics !== undefined) {
986
+ const cache = this.#engine.extract();
987
+ this.#diagnosticEvent(Object.freeze({
988
+ kind: 'normalization',
989
+ operation: artifact.id,
990
+ source,
991
+ records: cache.records.length,
992
+ indexes: summary.indexKeys.length,
993
+ partial: summary.partial ||
994
+ disposition === 'lower' ||
995
+ disposition === 'incomparable' ||
996
+ rejectedHandoff
997
+ }));
998
+ for (const indexKey of summary.indexKeys) {
999
+ const index = cache.indexes.find((candidate) => candidate.key === indexKey && !candidate.deleted);
1000
+ const staleReason = index?.metadata?.staleReason;
1001
+ this.#diagnosticEvent(Object.freeze({
1002
+ kind: 'index-decision',
1003
+ index: indexKey,
1004
+ decision: !writeIndexes ||
1005
+ !snapshot.indexesComparable ||
1006
+ disposition === 'incomparable'
1007
+ ? 'revalidate'
1008
+ : staleReason === undefined
1009
+ ? 'maintained'
1010
+ : 'stale',
1011
+ ...(staleReason === undefined
1012
+ ? {}
1013
+ : { reason: staleReason })
1014
+ }));
1015
+ }
1016
+ this.#diagnosticScopeTransition(previousProtocolGeneration, nextProtocolGeneration);
1017
+ this.#syncDiagnostics();
1018
+ }
1019
+ if (disposition === 'incomparable' || rejectedHandoff)
1020
+ return false;
1021
+ if (disposition !== 'lower')
1022
+ return true;
1023
+ // A lower index snapshot from the already-active source cannot replace
1024
+ // membership, but it can still carry independently fenced record clocks
1025
+ // and exact causal observations. Notify only when that transaction
1026
+ // actually committed data or receipt progress.
1027
+ return summary.wrote || receiptPlan.satisfied.length > 0;
1028
+ }
1029
+ #notifyResultObservers(envelope) {
1030
+ if (this.#resultObservers.size === 0)
1031
+ return;
1032
+ const errors = [];
1033
+ for (const observer of [...this.#resultObservers]) {
1034
+ try {
1035
+ observer(envelope);
1036
+ }
1037
+ catch (error) {
1038
+ errors.push(error);
1039
+ }
1040
+ }
1041
+ this._reportObserverErrors(errors);
1042
+ }
1043
+ createOptimisticLayer(id, update, semanticChanges = Object.freeze([])) {
1044
+ createOptimisticLayerOn(this.#optimisticHost(), id, update, semanticChanges);
1045
+ }
1046
+ markOptimisticLayerAccepted(id, receipt) {
1047
+ return markOptimisticLayerAcceptedOn(this.#optimisticHost(), id, receipt);
1048
+ }
1049
+ confirmOptimisticLayer(id, update) {
1050
+ return confirmOptimisticLayerOn(this.#optimisticHost(), id, update);
1051
+ }
1052
+ rejectOptimisticLayer(id) {
1053
+ return rejectOptimisticLayerOn(this.#optimisticHost(), id);
1054
+ }
1055
+ tombstoneRecord(model, identity, revision) {
1056
+ const wrote = this.#engine.batch((writer) => writer.tombstoneRecord(replicaRecordKey(model, identity), revision));
1057
+ if (wrote)
1058
+ this.#syncDiagnostics();
1059
+ return wrote;
1060
+ }
1061
+ markIndexStale(target, reason) {
1062
+ const key = indexKeyFromTarget(target);
1063
+ const marked = this.#engine.batch((writer) => writer.markIndexStale(key, reason));
1064
+ if (marked) {
1065
+ if (this.#diagnostics !== undefined) {
1066
+ this.#diagnosticEvent(Object.freeze({
1067
+ kind: 'index-decision',
1068
+ index: key,
1069
+ decision: 'stale',
1070
+ reason
1071
+ }));
1072
+ }
1073
+ this.#syncDiagnostics();
1074
+ }
1075
+ return marked;
1076
+ }
1077
+ retainRecord(model, identity) {
1078
+ this.#engine.retain(replicaRecordKey(model, identity));
1079
+ }
1080
+ releaseRecord(model, identity) {
1081
+ this.#engine.release(replicaRecordKey(model, identity));
1082
+ }
1083
+ gc() {
1084
+ const collected = this.#engine.gc();
1085
+ if (this.#diagnostics !== undefined) {
1086
+ this.#diagnosticEvent(Object.freeze({
1087
+ kind: 'gc',
1088
+ records: collected.length
1089
+ }));
1090
+ }
1091
+ this.#syncDiagnostics();
1092
+ return collected;
1093
+ }
1094
+ inspectRecord(model, identity) {
1095
+ const key = replicaRecordKey(model, identity);
1096
+ return this.#engine.read((reader) => {
1097
+ const record = reader.record(key);
1098
+ if (!record)
1099
+ return undefined;
1100
+ return Object.freeze({
1101
+ key,
1102
+ revision: record.revision,
1103
+ incarnation: record.incarnation,
1104
+ presentFields: Object.freeze(Object.keys(record.fields).sort())
1105
+ });
1106
+ });
1107
+ }
1108
+ inspectIndex(target) {
1109
+ const key = indexKeyFromTarget(target);
1110
+ return this.#engine.read((reader) => {
1111
+ const index = reader.index(key);
1112
+ if (!index?.metadata)
1113
+ return undefined;
1114
+ return Object.freeze({
1115
+ key,
1116
+ revision: index.revision,
1117
+ ...(index.staleRevision === undefined
1118
+ ? {}
1119
+ : { staleRevision: index.staleRevision }),
1120
+ records: Object.freeze([...index.records]),
1121
+ complete: index.complete,
1122
+ field: index.metadata.field,
1123
+ ...(index.metadata.parent === undefined
1124
+ ? {}
1125
+ : { parent: index.metadata.parent }),
1126
+ arguments: index.metadata.arguments,
1127
+ coverage: index.metadata.coverage,
1128
+ dependencies: index.metadata.dependencies,
1129
+ ...(index.metadata.staleReason === undefined
1130
+ ? {}
1131
+ : { staleReason: index.metadata.staleReason }),
1132
+ nullValue: index.metadata.nullValue === true
1133
+ });
1134
+ });
1135
+ }
1136
+ #syncDiagnostics() {
1137
+ syncDiagnosticsOn(this.#diagnosticsHost());
1138
+ }
1139
+ #diagnosticEvent(event) {
1140
+ diagnosticEventOn(this.#diagnosticsHost(), event);
1141
+ }
1142
+ #diagnosticOperation(artifact) {
1143
+ diagnosticOperationOn(this.#diagnosticsHost(), artifact);
1144
+ }
1145
+ #diagnosticScopeTransition(previous, next) {
1146
+ diagnosticScopeTransitionOn(this.#diagnosticsHost(), previous, next);
1147
+ }
1148
+ #retireDiagnosticLayer(id, action, receiptState, receipt) {
1149
+ retireDiagnosticLayerOn(this.#diagnosticsHost(), id, action, receiptState, receipt);
1150
+ }
1151
+ #validateProtocolBinding(artifact, envelope, source) {
1152
+ validateProtocolBindingOn(this.#protocolHost(), artifact, envelope, source);
1153
+ }
1154
+ #stageProtocolGeneration(envelope) {
1155
+ return stageProtocolGenerationOn(this.#protocolHost(), envelope);
1156
+ }
1157
+ #stageTrustedPresets(incoming, nextGeneration, artifact) {
1158
+ return stageTrustedPresetsOn(this.#protocolHost(), incoming, nextGeneration, artifact);
1159
+ }
1160
+ #purgeProtocolGeneration() {
1161
+ purgeProtocolGenerationOn(this.#protocolHost());
1162
+ }
1163
+ #closeAuthorizationGeneration() {
1164
+ closeAuthorizationGenerationOn(this.#protocolHost());
1165
+ }
1166
+ #closeActiveTransports() {
1167
+ closeActiveTransportsOn(this.#fetchLiveHost());
1168
+ }
1169
+ #rememberRenderedOperation(key, artifact, variables, source) {
1170
+ this.#diagnosticOperation(artifact);
1171
+ if (!this.#indexPlanRegistrations.has(key)) {
1172
+ const registration = this.#indexMaintenance.registerOperation(artifact, variables);
1173
+ this.#indexPlanRegistrations.set(key, registration);
1174
+ try {
1175
+ this.#refreshIndexMaintenance();
1176
+ }
1177
+ catch (error) {
1178
+ this.#indexPlanRegistrations.delete(key);
1179
+ registration.dispose();
1180
+ throw error;
1181
+ }
1182
+ }
1183
+ this.#renderedOperations.set(key, Object.freeze({
1184
+ artifact: artifact,
1185
+ variables
1186
+ }));
1187
+ if (source === 'read') {
1188
+ this.#readOperationKeys.add(key);
1189
+ }
1190
+ else {
1191
+ this.#watchRenderCounts.set(key, (this.#watchRenderCounts.get(key) ?? 0) + 1);
1192
+ }
1193
+ }
1194
+ #forgetRenderedWatch(key) {
1195
+ const count = this.#watchRenderCounts.get(key);
1196
+ if (count === undefined)
1197
+ return;
1198
+ if (count > 1) {
1199
+ this.#watchRenderCounts.set(key, count - 1);
1200
+ return;
1201
+ }
1202
+ this.#watchRenderCounts.delete(key);
1203
+ if (!this.#readOperationKeys.has(key)) {
1204
+ this.#renderedOperations.delete(key);
1205
+ this.#disposeIndexPlan(key);
1206
+ }
1207
+ }
1208
+ #finishDehydration() {
1209
+ finishDehydrationOn(this.#hydrationHost());
1210
+ }
1211
+ #disposeIndexPlan(key) {
1212
+ const registration = this.#indexPlanRegistrations.get(key);
1213
+ if (registration === undefined)
1214
+ return;
1215
+ this.#indexPlanRegistrations.delete(key);
1216
+ registration.dispose();
1217
+ this.#refreshIndexMaintenance();
1218
+ }
1219
+ #refreshIndexMaintenance() {
1220
+ this.#engine.setDerivedIndexReconciler(this.#derivedIndexReconciler);
1221
+ }
1222
+ #deriveMaintainedIndexes(confirmed, layers) {
1223
+ const snapshot = indexMaintenanceSnapshot(confirmed);
1224
+ const indexes = new Map(snapshot.indexes.map((index) => [index.key, index]));
1225
+ const semanticLayers = layers.map(indexSemanticLayer);
1226
+ const mutations = [];
1227
+ for (const decision of this.#indexMaintenance.evaluate(snapshot, semanticLayers, this.#trustedPresets)) {
1228
+ if (decision.kind === 'unchanged')
1229
+ continue;
1230
+ if (decision.kind === 'stale') {
1231
+ mutations.push({
1232
+ kind: 'stale',
1233
+ key: decision.indexKey,
1234
+ reason: formatReplicaIndexStaleReason(decision.reason)
1235
+ });
1236
+ continue;
1237
+ }
1238
+ const index = indexes.get(decision.indexKey);
1239
+ if (index === undefined) {
1240
+ throw new Error(`index maintenance returned an unknown index: ${decision.indexKey}`);
1241
+ }
1242
+ mutations.push({
1243
+ kind: 'write',
1244
+ write: {
1245
+ key: decision.indexKey,
1246
+ records: decision.records,
1247
+ complete: true,
1248
+ metadata: index.metadata
1249
+ }
1250
+ });
1251
+ }
1252
+ return Object.freeze(mutations);
1253
+ }
1254
+ #operationProtocol(key, operation, source) {
1255
+ let group = this.#operationProtocols.get(key);
1256
+ if (group === undefined) {
1257
+ group = {};
1258
+ this.#operationProtocols.set(key, group);
1259
+ }
1260
+ const current = group[source];
1261
+ if (current !== undefined) {
1262
+ if (current.operation !== operation) {
1263
+ protocolInvalid('extensions.distributed.operation');
1264
+ }
1265
+ return current;
1266
+ }
1267
+ const created = {
1268
+ operation,
1269
+ indexClocks: new Map(),
1270
+ indexKeys: new Set(),
1271
+ pathRecords: new Map(),
1272
+ cursors: Object.freeze([])
1273
+ };
1274
+ group[source] = created;
1275
+ return created;
1276
+ }
1277
+ #activateOperationSource(key, source, artifact, variables) {
1278
+ const group = this.#operationProtocols.get(key);
1279
+ if (group === undefined)
1280
+ return false;
1281
+ const previous = group.active;
1282
+ if (previous === source)
1283
+ return false;
1284
+ if (previous !== undefined) {
1285
+ const discardedStates = new Set([group.query, group.live].filter((state) => state !== undefined));
1286
+ const revisionsByKey = new Map();
1287
+ for (const state of [group.query, group.live]) {
1288
+ if (state?.indexRevision === undefined)
1289
+ continue;
1290
+ for (const indexKey of state.indexKeys) {
1291
+ let revisions = revisionsByKey.get(indexKey);
1292
+ if (revisions === undefined) {
1293
+ revisions = new Set();
1294
+ revisionsByKey.set(indexKey, revisions);
1295
+ }
1296
+ revisions.add(state.indexRevision);
1297
+ }
1298
+ }
1299
+ for (const root of artifact.roots) {
1300
+ const indexKey = replicaIndexKey({
1301
+ field: root.field,
1302
+ arguments: resolveArguments(root.arguments, variables, root.coverage)
1303
+ });
1304
+ let revisions = revisionsByKey.get(indexKey);
1305
+ if (revisions === undefined) {
1306
+ revisions = new Set();
1307
+ revisionsByKey.set(indexKey, revisions);
1308
+ }
1309
+ for (const state of [group.query, group.live]) {
1310
+ if (state?.indexRevision !== undefined) {
1311
+ revisions.add(state.indexRevision);
1312
+ }
1313
+ }
1314
+ }
1315
+ const confirmedFences = this.#confirmedIndexFences(revisionsByKey.keys());
1316
+ const ownedKeys = [...revisionsByKey].flatMap(([indexKey, revisions]) => {
1317
+ const revision = confirmedFences.get(indexKey);
1318
+ return revision !== undefined &&
1319
+ revisions.has(revision) &&
1320
+ !this.#indexClaimedByAnotherOperationState(indexKey, revision, discardedStates)
1321
+ ? [indexKey]
1322
+ : [];
1323
+ });
1324
+ this.#engine.discardIndexes(ownedKeys);
1325
+ }
1326
+ group.active = source;
1327
+ this.#advanceOperationGeneration(key);
1328
+ return previous !== undefined;
1329
+ }
1330
+ #operationGeneration(key) {
1331
+ return this.#operationGenerations.get(key) ?? 0;
1332
+ }
1333
+ #indexClaimedByAnotherOperationState(indexKey, revision, excluded) {
1334
+ for (const group of this.#operationProtocols.values()) {
1335
+ for (const state of [group.query, group.live]) {
1336
+ if (state !== undefined &&
1337
+ !excluded.has(state) &&
1338
+ state.indexRevision === revision &&
1339
+ state.indexKeys.has(indexKey)) {
1340
+ return true;
1341
+ }
1342
+ }
1343
+ }
1344
+ return false;
1345
+ }
1346
+ #confirmedIndexFences(indexKeys) {
1347
+ return this.#engine.confirmedIndexFences([...indexKeys]);
1348
+ }
1349
+ #sharedIndexDisposition(currentKey, incomingIndexKeys, snapshot, requestRevision, source) {
1350
+ if (incomingIndexKeys.size === 0)
1351
+ return { compared: false };
1352
+ const confirmedRevisions = this.#confirmedIndexFences(incomingIndexKeys);
1353
+ let compared = false;
1354
+ let lower = false;
1355
+ let higher = false;
1356
+ let incomparable = false;
1357
+ let equalRevision;
1358
+ let latestOwnerRevision;
1359
+ for (const [key, group] of this.#operationProtocols) {
1360
+ if (key === currentKey)
1361
+ continue;
1362
+ for (const state of [group.query, group.live]) {
1363
+ if (state?.indexRevision === undefined)
1364
+ continue;
1365
+ let ownsIncomingIndex = false;
1366
+ for (const indexKey of state.indexKeys) {
1367
+ if (incomingIndexKeys.has(indexKey) &&
1368
+ confirmedRevisions.get(indexKey) === state.indexRevision) {
1369
+ ownsIncomingIndex = true;
1370
+ break;
1371
+ }
1372
+ }
1373
+ if (!ownsIncomingIndex)
1374
+ continue;
1375
+ latestOwnerRevision =
1376
+ latestOwnerRevision === undefined ||
1377
+ compareCanonicalDecimalStrings(state.indexRevision, latestOwnerRevision) > 0
1378
+ ? state.indexRevision
1379
+ : latestOwnerRevision;
1380
+ /*
1381
+ * Snapshot scope is bound to one operation plan instance and is
1382
+ * not a cross-artifact identity. Shared semantic indexes are
1383
+ * comparable through their projection/scope/position vector.
1384
+ */
1385
+ const disposition = !snapshot.indexesComparable
1386
+ ? 'incomparable'
1387
+ : state.indexClocks.size === 0 ||
1388
+ snapshot.indexes.length === 0
1389
+ ? state.indexClocks.size === snapshot.indexes.length
1390
+ ? 'equal'
1391
+ : 'incomparable'
1392
+ : compareIndexVector(state.indexClocks, snapshot.indexes);
1393
+ if (disposition === 'fresh' ||
1394
+ disposition === 'incomparable') {
1395
+ incomparable = true;
1396
+ continue;
1397
+ }
1398
+ compared = true;
1399
+ if (disposition === 'lower')
1400
+ lower = true;
1401
+ else if (disposition === 'higher')
1402
+ higher = true;
1403
+ else {
1404
+ equalRevision =
1405
+ equalRevision === undefined ||
1406
+ compareCanonicalDecimalStrings(state.indexRevision, equalRevision) > 0
1407
+ ? state.indexRevision
1408
+ : equalRevision;
1409
+ }
1410
+ }
1411
+ }
1412
+ /*
1413
+ * One response is a coherent replacement graph. If its vector straddles
1414
+ * the owners of two shared indexes, accepting only part would fabricate
1415
+ * a snapshot the server never produced, so preserve the current graph.
1416
+ */
1417
+ if (lower)
1418
+ return { compared: true, disposition: 'lower' };
1419
+ if (incomparable) {
1420
+ /*
1421
+ * A comparable sibling must not promote an incomparable membership
1422
+ * to response-arrival order. Fall back to request-start order for
1423
+ * the whole graph, and reject it atomically when that request began
1424
+ * before any owner it would replace. Independent live streams have
1425
+ * no shared request-start fence, so they cannot safely replace it;
1426
+ * explicit synchronous ingress retains its caller-defined order.
1427
+ */
1428
+ if (requestRevision === undefined && source === 'live') {
1429
+ return { compared: true, disposition: 'lower' };
1430
+ }
1431
+ if (requestRevision === undefined)
1432
+ return { compared: false };
1433
+ return latestOwnerRevision !== undefined &&
1434
+ compareCanonicalDecimalStrings(requestRevision, latestOwnerRevision) <
1435
+ 0
1436
+ ? { compared: true, disposition: 'lower' }
1437
+ : { compared: false };
1438
+ }
1439
+ if (!compared)
1440
+ return { compared: false };
1441
+ if (higher)
1442
+ return { compared: true, disposition: 'higher' };
1443
+ return {
1444
+ compared: true,
1445
+ disposition: 'equal',
1446
+ ...(equalRevision === undefined
1447
+ ? {}
1448
+ : { indexRevision: equalRevision })
1449
+ };
1450
+ }
1451
+ #advanceOperationGeneration(key) {
1452
+ this.#operationGenerations.set(key, this.#operationGeneration(key) + 1);
1453
+ }
1454
+ #resumeCursors(key) {
1455
+ const group = this.#operationProtocols.get(key);
1456
+ if (group?.active === 'query' && group.query?.cursors.length) {
1457
+ return group.query.cursors;
1458
+ }
1459
+ if (group?.active === 'live' && group.live?.cursors.length) {
1460
+ return group.live.cursors;
1461
+ }
1462
+ return group?.live?.cursors.length
1463
+ ? group.live.cursors
1464
+ : (group?.query?.cursors ?? Object.freeze([]));
1465
+ }
1466
+ #discardOperationSnapshot(state, artifact, variables) {
1467
+ const keys = new Set(state.indexKeys);
1468
+ for (const root of artifact.roots) {
1469
+ keys.add(replicaIndexKey({
1470
+ field: root.field,
1471
+ arguments: resolveArguments(root.arguments, variables, root.coverage)
1472
+ }));
1473
+ }
1474
+ const indexRevision = state.indexRevision;
1475
+ const discardedStates = new Set([state]);
1476
+ const confirmedFence = this.#confirmedIndexFences(keys);
1477
+ const ownedKeys = indexRevision === undefined
1478
+ ? []
1479
+ : [...keys].filter((key) => confirmedFence.get(key) === indexRevision &&
1480
+ !this.#indexClaimedByAnotherOperationState(key, indexRevision, discardedStates));
1481
+ this.#engine.discardIndexes(ownedKeys);
1482
+ this.#resetOperationState(state);
1483
+ }
1484
+ #resetOperationState(state) {
1485
+ this.#resetOperationCausalState(state);
1486
+ state.indexRevision = undefined;
1487
+ state.indexKeys.clear();
1488
+ state.pathRecords.clear();
1489
+ }
1490
+ #resetOperationCausalState(state) {
1491
+ state.snapshotScope = undefined;
1492
+ state.indexClocks = new Map();
1493
+ state.cursors = Object.freeze([]);
1494
+ }
1495
+ #allocateIndexRevision() {
1496
+ this.#nextIndexRevision = incrementCanonicalDecimal(this.#nextIndexRevision);
1497
+ return this.#nextIndexRevision;
1498
+ }
1499
+ #resolveRecordEvidence(recordKey, evidence, pendingClocks, pendingScopes, pendingAnonymousClocks, consumedAnonymousClocks, projectedConflict = false) {
1500
+ if (!recordKeyMatchesModel(recordKey, evidence.model)) {
1501
+ protocolInvalid('extensions.distributed.snapshot.records.model');
1502
+ }
1503
+ const scopedKey = pendingScopes.get(evidence.scopeToken) ??
1504
+ this.#recordKeysByScope.get(evidence.scopeToken);
1505
+ if (scopedKey !== undefined && scopedKey !== recordKey) {
1506
+ protocolInvalid('extensions.distributed.snapshot.records.scopeToken');
1507
+ }
1508
+ pendingScopes.set(evidence.scopeToken, recordKey);
1509
+ const incoming = {
1510
+ scopeToken: evidence.scopeToken,
1511
+ incarnation: evidence.incarnation,
1512
+ revision: evidence.revision,
1513
+ tombstone: evidence.tombstone
1514
+ };
1515
+ const pending = pendingClocks.get(recordKey);
1516
+ if (pending !== undefined) {
1517
+ if (!sameRecordClock(pending, incoming)) {
1518
+ protocolInvalid('extensions.distributed.snapshot.records');
1519
+ }
1520
+ return true;
1521
+ }
1522
+ let current = this.#recordClocks.get(recordKey);
1523
+ const anonymous = pendingAnonymousClocks.get(evidence.scopeToken) ??
1524
+ this.#anonymousRecordClocks.get(evidence.scopeToken);
1525
+ if (anonymous !== undefined) {
1526
+ if (anonymous.model !== evidence.model) {
1527
+ protocolInvalid('extensions.distributed.snapshot.records.model');
1528
+ }
1529
+ consumedAnonymousClocks.add(evidence.scopeToken);
1530
+ if (current === undefined) {
1531
+ current = anonymous.clock;
1532
+ }
1533
+ else {
1534
+ if (current.scopeToken !== anonymous.clock.scopeToken) {
1535
+ protocolInvalid('extensions.distributed.snapshot.records.scopeToken');
1536
+ }
1537
+ const anonymousComparison = compareRecordClock(anonymous.clock, current);
1538
+ if (anonymousComparison === 0 &&
1539
+ anonymous.clock.tombstone !== current.tombstone) {
1540
+ protocolInvalid('extensions.distributed.snapshot.records.tombstone');
1541
+ }
1542
+ if (anonymousComparison > 0)
1543
+ current = anonymous.clock;
1544
+ }
1545
+ }
1546
+ if (current === undefined) {
1547
+ pendingClocks.set(recordKey, incoming);
1548
+ return true;
1549
+ }
1550
+ if (current.scopeToken !== incoming.scopeToken) {
1551
+ protocolInvalid('extensions.distributed.snapshot.records.scopeToken');
1552
+ }
1553
+ if (projectedConflict) {
1554
+ pendingClocks.set(recordKey, current);
1555
+ return false;
1556
+ }
1557
+ const comparison = compareRecordClock(incoming, current);
1558
+ if (comparison < 0) {
1559
+ pendingClocks.set(recordKey, current);
1560
+ return false;
1561
+ }
1562
+ if (comparison === 0) {
1563
+ if (current.tombstone !== incoming.tombstone) {
1564
+ protocolInvalid('extensions.distributed.snapshot.records.tombstone');
1565
+ }
1566
+ pendingClocks.set(recordKey, incoming);
1567
+ return true;
1568
+ }
1569
+ if (current.tombstone &&
1570
+ !incoming.tombstone &&
1571
+ compareDistributedDecimal(incoming.incarnation, current.incarnation) <= 0) {
1572
+ protocolInvalid('extensions.distributed.snapshot.records.incarnation');
1573
+ }
1574
+ pendingClocks.set(recordKey, incoming);
1575
+ return true;
1576
+ }
1577
+ #retainAnonymousRecordEvidence(evidence, pendingAnonymousClocks) {
1578
+ const incoming = {
1579
+ model: evidence.model,
1580
+ clock: {
1581
+ scopeToken: evidence.scopeToken,
1582
+ incarnation: evidence.incarnation,
1583
+ revision: evidence.revision,
1584
+ tombstone: evidence.tombstone
1585
+ }
1586
+ };
1587
+ const current = pendingAnonymousClocks.get(evidence.scopeToken) ??
1588
+ this.#anonymousRecordClocks.get(evidence.scopeToken);
1589
+ if (current === undefined) {
1590
+ let retained = this.#anonymousRecordClocks.size;
1591
+ for (const scopeToken of pendingAnonymousClocks.keys()) {
1592
+ if (!this.#anonymousRecordClocks.has(scopeToken))
1593
+ retained += 1;
1594
+ }
1595
+ if (retained >= MAX_ANONYMOUS_RECORD_CLOCKS) {
1596
+ protocolInvalid('extensions.distributed.snapshot.records.capacity');
1597
+ }
1598
+ pendingAnonymousClocks.set(evidence.scopeToken, incoming);
1599
+ return;
1600
+ }
1601
+ if (current.model !== incoming.model) {
1602
+ protocolInvalid('extensions.distributed.snapshot.records.model');
1603
+ }
1604
+ const comparison = compareRecordClock(incoming.clock, current.clock);
1605
+ if (comparison < 0)
1606
+ return;
1607
+ if (comparison === 0) {
1608
+ if (current.clock.tombstone !== incoming.clock.tombstone) {
1609
+ protocolInvalid('extensions.distributed.snapshot.records.tombstone');
1610
+ }
1611
+ return;
1612
+ }
1613
+ if (current.clock.tombstone &&
1614
+ !incoming.clock.tombstone &&
1615
+ compareDistributedDecimal(incoming.clock.incarnation, current.clock.incarnation) <= 0) {
1616
+ protocolInvalid('extensions.distributed.snapshot.records.incarnation');
1617
+ }
1618
+ pendingAnonymousClocks.set(evidence.scopeToken, incoming);
1619
+ }
1620
+ #applyTombstoneEvidence(writer, evidenceItems, operation, pendingClocks, pendingScopes, pendingAnonymousClocks, consumedAnonymousClocks, consumedPaths, pendingProjectedRecordFenceClears) {
1621
+ for (const evidence of evidenceItems) {
1622
+ const encodedPath = evidence.path === undefined
1623
+ ? undefined
1624
+ : responsePathKey(evidence.path);
1625
+ const recordKey = this.#recordKeysByScope.get(evidence.scopeToken) ??
1626
+ (encodedPath === undefined
1627
+ ? undefined
1628
+ : operation.pathRecords.get(encodedPath));
1629
+ if (recordKey === undefined) {
1630
+ this.#retainAnonymousRecordEvidence(evidence, pendingAnonymousClocks);
1631
+ continue;
1632
+ }
1633
+ if (encodedPath !== undefined)
1634
+ consumedPaths.add(encodedPath);
1635
+ const projectedFence = this.#projectedRecordFences.get(recordKey);
1636
+ const projectedClockComparison = projectedFence === undefined
1637
+ ? undefined
1638
+ : compareEvidenceToProjectedFence(evidence, projectedFence);
1639
+ if (projectedFence !== undefined &&
1640
+ projectedClockComparison === 1) {
1641
+ pendingProjectedRecordFenceClears.set(recordKey, projectedFence);
1642
+ }
1643
+ if (this.#resolveRecordEvidence(recordKey, evidence, pendingClocks, pendingScopes, pendingAnonymousClocks, consumedAnonymousClocks, projectedFence !== undefined &&
1644
+ projectedClockComparison !== 1)) {
1645
+ writer.tombstoneRecord(recordKey, evidence.revision, evidence.incarnation);
1646
+ }
1647
+ }
1648
+ }
1649
+ #applyPathlessEvidence(writer, evidenceItems, pathEvidence, consumedPaths, pendingClocks, pendingScopes, pendingAnonymousClocks, consumedAnonymousClocks, pendingProjectedRecordFenceClears) {
1650
+ for (const evidence of evidenceItems) {
1651
+ const recordKey = pendingScopes.get(evidence.scopeToken) ??
1652
+ this.#recordKeysByScope.get(evidence.scopeToken);
1653
+ if (recordKey === undefined) {
1654
+ this.#retainAnonymousRecordEvidence(evidence, pendingAnonymousClocks);
1655
+ continue;
1656
+ }
1657
+ if (!recordKeyMatchesModel(recordKey, evidence.model)) {
1658
+ protocolInvalid('extensions.distributed.snapshot.records.model');
1659
+ }
1660
+ let certifiedByPath = false;
1661
+ for (const [path, candidate] of pathEvidence) {
1662
+ if (consumedPaths.has(path) &&
1663
+ candidate.scopeToken === evidence.scopeToken &&
1664
+ sameRecordRevision(candidate, evidence)) {
1665
+ certifiedByPath = true;
1666
+ break;
1667
+ }
1668
+ }
1669
+ if (certifiedByPath)
1670
+ continue;
1671
+ const projectedFence = this.#projectedRecordFences.get(recordKey);
1672
+ const projectedClockComparison = projectedFence === undefined
1673
+ ? undefined
1674
+ : compareEvidenceToProjectedFence(evidence, projectedFence);
1675
+ if (projectedFence !== undefined &&
1676
+ projectedClockComparison === 1) {
1677
+ pendingProjectedRecordFenceClears.set(recordKey, projectedFence);
1678
+ }
1679
+ if (this.#resolveRecordEvidence(recordKey, evidence, pendingClocks, pendingScopes, pendingAnonymousClocks, consumedAnonymousClocks, projectedFence !== undefined &&
1680
+ projectedClockComparison !== 1)) {
1681
+ // A change-log upsert proves only that a newer row exists. It
1682
+ // advances the tuple fence, but cannot certify any cached field.
1683
+ writer.discardRecord(recordKey);
1684
+ }
1685
+ }
1686
+ }
1687
+ #validateLiveSnapshot(snapshot, live) {
1688
+ if (live === undefined || !live.supported)
1689
+ return;
1690
+ if (!snapshot.indexesComparable) {
1691
+ protocolInvalid('extensions.distributed.snapshot.indexesComparable');
1692
+ }
1693
+ const indexes = new Map(snapshot.indexes.map((index) => [index.projection, index]));
1694
+ if (indexes.size !== live.cursors.length) {
1695
+ protocolInvalid('extensions.distributed.live.cursors');
1696
+ }
1697
+ for (const cursor of live.cursors) {
1698
+ const index = indexes.get(cursor.projection);
1699
+ if (index === undefined ||
1700
+ index.position !== cursor.position ||
1701
+ (index.resume !== undefined &&
1702
+ index.resume.token !== cursor.token)) {
1703
+ protocolInvalid('extensions.distributed.live.cursors');
1704
+ }
1705
+ }
1706
+ }
1707
+ #planOptimisticReceipts(command, observations, satisfactionAdmissible) {
1708
+ return planOptimisticReceiptsOn(this.#optimisticHost(), command, observations, satisfactionAdmissible);
1709
+ }
1710
+ #applyReceiptOnly(command) {
1711
+ applyReceiptOnlyOn(this.#optimisticHost(), command);
1712
+ }
1713
+ /** Package-internal hook used by one watched operation. */
1714
+ _register(watch) {
1715
+ this.#rememberRenderedOperation(watch.key, watch.artifact, watch.variables, 'watch');
1716
+ let watches = this.#watches.get(watch.key);
1717
+ if (!watches) {
1718
+ watches = new Set();
1719
+ this.#watches.set(watch.key, watches);
1720
+ }
1721
+ watches.add(watch);
1722
+ const unwatch = this.#engine.watch((reader) => materializeReplicaOperation(reader, watch.artifact, watch.variables), (materialized) => watch._cacheChanged(materialized), { immediate: true });
1723
+ if (watch.liveRequested)
1724
+ this.#retainLive(watch);
1725
+ void this._fetch(watch, false);
1726
+ return () => {
1727
+ unwatch();
1728
+ watches?.delete(watch);
1729
+ if (watches?.size === 0)
1730
+ this.#watches.delete(watch.key);
1731
+ this.#forgetRenderedWatch(watch.key);
1732
+ if (watch.liveRequested)
1733
+ this.#releaseLive(watch.key);
1734
+ };
1735
+ }
1736
+ /** Package-internal query-state lookup. */
1737
+ _state(key) {
1738
+ return this.#queryState(key);
1739
+ }
1740
+ /** Package-internal materialization for watch construction. */
1741
+ _materialize(artifact, variables) {
1742
+ return this.#engine.read((reader) => materializeReplicaOperation(reader, artifact, variables));
1743
+ }
1744
+ /** Package-internal cache-and-live coordinator. */
1745
+ _fetch(watch, force) {
1746
+ return fetchWatch(this.#fetchLiveHost(), watch, force);
1747
+ }
1748
+ _reportObserverErrors(errors) {
1749
+ if (errors.length === 0)
1750
+ return;
1751
+ reportSafely(this.#reportObserverError, new AggregateError(errors, 'replica observer delivery failed'));
1752
+ }
1753
+ #queryState(key) {
1754
+ let state = this.#queryStates.get(key);
1755
+ if (!state) {
1756
+ state = { fetching: false, errors: EMPTY_ERRORS, live: 'off' };
1757
+ this.#queryStates.set(key, state);
1758
+ }
1759
+ return state;
1760
+ }
1761
+ #emitState(key, allowFetch) {
1762
+ emitWatchState(this.#fetchLiveHost(), key, allowFetch);
1763
+ }
1764
+ #retainLive(watch) {
1765
+ retainLiveOn(this.#fetchLiveHost(), watch);
1766
+ }
1767
+ #restartLive(key) {
1768
+ restartLiveOn(this.#fetchLiveHost(), key);
1769
+ }
1770
+ #resumeLiveWatches() {
1771
+ resumeLiveWatchesOn(this.#fetchLiveHost());
1772
+ }
1773
+ #releaseLive(key) {
1774
+ releaseLiveOn(this.#fetchLiveHost(), key);
1775
+ }
1776
+ }