@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,255 @@
1
+ import {} from '../../../protocol.js';
2
+ import {} from '../../commands.js';
3
+ import { INITIAL_STATUS_POLL_MS, MAX_STATUS_POLL_MS } from '../constants.js';
4
+ import { ReplicaCommandRuntimeError } from '../errors.js';
5
+ import { replicaCommandDirectProjection } from '../symbols.js';
6
+ import { isPlainRecord } from '../../../lib/is-plain-record.js';
7
+ import { cloneOutputJson } from './output.js';
8
+ export function confirmDirectProjection(replica, prepared, output, metadata) {
9
+ const direct = prepared.directProjection;
10
+ if (direct === undefined || !isPlainRecord(output)) {
11
+ throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_PROTOCOL_INVALID', { commandId: prepared.commandId });
12
+ }
13
+ const identity = [];
14
+ for (const field of direct.identityFields) {
15
+ const value = output[field];
16
+ if (value === undefined || value === null) {
17
+ throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_PROTOCOL_INVALID', { commandId: prepared.commandId });
18
+ }
19
+ identity.push(value);
20
+ }
21
+ const evidence = metadata.records.filter((record) => record.model === direct.model &&
22
+ !record.tombstone &&
23
+ (record.path === undefined ||
24
+ (record.path.length === 1 &&
25
+ record.path[0] === prepared.transport.mutationField)));
26
+ if (evidence.length !== 1) {
27
+ throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_PROTOCOL_INVALID', { commandId: prepared.commandId });
28
+ }
29
+ const record = evidence[0];
30
+ const model = Object.freeze({
31
+ id: direct.model,
32
+ identityFields: direct.identityFields
33
+ });
34
+ const fields = cloneOutputJson(output, 'projected.output', new Set(), 0);
35
+ const confirmProtocolProjection = replica[replicaCommandDirectProjection];
36
+ if (confirmProtocolProjection !== undefined) {
37
+ confirmProtocolProjection.call(replica, prepared.commandId, {
38
+ model,
39
+ identity: Object.freeze(identity),
40
+ evidence: record,
41
+ fields
42
+ });
43
+ return;
44
+ }
45
+ replica.confirmOptimisticLayer(prepared.commandId, (writer) => writer.writeRecord(model, Object.freeze(identity), record.revision, {
46
+ incarnation: record.incarnation,
47
+ fields
48
+ }));
49
+ }
50
+ export function pendingProjection(authority, metadata, prepared) {
51
+ let resolve;
52
+ let reject;
53
+ const promise = new Promise((resolvePromise, rejectPromise) => {
54
+ resolve = resolvePromise;
55
+ reject = rejectPromise;
56
+ });
57
+ /*
58
+ * Authority loss or a terminal status can arrive before application code
59
+ * receives the accepted receipt. Mark the internal lifecycle promise handled
60
+ * eagerly while preserving its rejection for every explicit awaiter.
61
+ */
62
+ void promise.catch(() => undefined);
63
+ const controller = {
64
+ commandId: metadata.commandId,
65
+ causationId: metadata.causationId,
66
+ authority,
67
+ resolve,
68
+ reject,
69
+ promise,
70
+ prepared,
71
+ metadata,
72
+ settled: false
73
+ };
74
+ return controller;
75
+ }
76
+ /**
77
+ * Generated fact commands converge without application polling. Durable status
78
+ * is the only completion signal; timers merely schedule reads and never infer a
79
+ * successful outcome.
80
+ */
81
+ export function monitorPendingProjection(controller, readStatus, retained, reportError) {
82
+ if (controller.settled ||
83
+ !retained() ||
84
+ projectionAuthorityAborted(controller)) {
85
+ return;
86
+ }
87
+ const monitorAbort = new AbortController();
88
+ const stopMonitor = () => monitorAbort.abort();
89
+ controller.stopMonitor = stopMonitor;
90
+ const signals = controller.authority.signal === undefined
91
+ ? [monitorAbort.signal]
92
+ : [controller.authority.signal, monitorAbort.signal];
93
+ void (async () => {
94
+ try {
95
+ let delay = INITIAL_STATUS_POLL_MS;
96
+ while (!controller.settled &&
97
+ retained() &&
98
+ !projectionAuthorityAborted(controller)) {
99
+ await waitForProjectionPoll(delay, signals);
100
+ if (controller.settled ||
101
+ !retained() ||
102
+ projectionAuthorityAborted(controller)) {
103
+ return;
104
+ }
105
+ try {
106
+ await readStatus();
107
+ }
108
+ catch (error) {
109
+ if (controller.settled ||
110
+ !retained() ||
111
+ projectionAuthorityAborted(controller)) {
112
+ return;
113
+ }
114
+ reportBackgroundErrorSafely(reportError, error);
115
+ }
116
+ delay = Math.min(delay * 2, MAX_STATUS_POLL_MS);
117
+ }
118
+ }
119
+ finally {
120
+ if (controller.stopMonitor === stopMonitor) {
121
+ controller.stopMonitor = undefined;
122
+ }
123
+ monitorAbort.abort();
124
+ }
125
+ })().catch((error) => reportBackgroundErrorSafely(reportError, error));
126
+ }
127
+ export function reportBackgroundErrorSafely(reportError, error) {
128
+ if (reportError === undefined)
129
+ return;
130
+ try {
131
+ reportError(error);
132
+ }
133
+ catch {
134
+ // Error reporting is a terminal boundary and must never reject detached work.
135
+ }
136
+ }
137
+ export function projectionAuthorityAborted(controller) {
138
+ return controller.authority.signal?.aborted === true;
139
+ }
140
+ export function waitForProjectionPoll(delay, signals) {
141
+ if (signals.some((signal) => signal.aborted))
142
+ return Promise.resolve();
143
+ return new Promise((resolve) => {
144
+ let settled = false;
145
+ let timer;
146
+ const finish = () => {
147
+ if (settled)
148
+ return;
149
+ settled = true;
150
+ if (timer !== undefined)
151
+ clearTimeout(timer);
152
+ for (const signal of signals) {
153
+ signal.removeEventListener('abort', finish);
154
+ }
155
+ resolve();
156
+ };
157
+ timer = setTimeout(finish, delay);
158
+ /*
159
+ * This poll owns the unresolved projected lifecycle. Keep Node's timer
160
+ * referenced until projection settlement or runtime disposal calls
161
+ * `finish`, which clears the timer and every abort listener.
162
+ */
163
+ for (const signal of signals) {
164
+ signal.addEventListener('abort', finish, { once: true });
165
+ }
166
+ // Close the check/register race if either signal aborted synchronously.
167
+ if (signals.some((signal) => signal.aborted))
168
+ finish();
169
+ });
170
+ }
171
+ export function settleProjectionSuccess(controller) {
172
+ if (controller.settled)
173
+ return;
174
+ controller.settled = true;
175
+ controller.abort?.();
176
+ controller.stopMonitor?.();
177
+ controller.resolve(Object.freeze({
178
+ commandId: controller.commandId,
179
+ state: 'projected',
180
+ metadata: controller.metadata
181
+ }));
182
+ }
183
+ export function callerProjectedPromise(controller, signal) {
184
+ if (signal === undefined)
185
+ return controller.promise;
186
+ const callerSignal = signal;
187
+ const promise = new Promise((resolve, reject) => {
188
+ let settled = false;
189
+ function settle(complete) {
190
+ if (settled)
191
+ return;
192
+ settled = true;
193
+ callerSignal.removeEventListener('abort', onAbort);
194
+ complete();
195
+ }
196
+ function onAbort() {
197
+ /*
198
+ * Internal causal settlement wins once selected, even if its
199
+ * promise callbacks have not run yet. Caller cancellation never
200
+ * mutates that internal lifecycle.
201
+ */
202
+ if (controller.settled)
203
+ return;
204
+ settle(() => reject(new ReplicaCommandRuntimeError('REPLICA_COMMAND_ABORTED', {
205
+ commandId: controller.commandId
206
+ })));
207
+ }
208
+ callerSignal.addEventListener('abort', onAbort, { once: true });
209
+ void controller.promise.then((value) => settle(() => resolve(value)), (error) => settle(() => reject(error)));
210
+ if (callerSignal.aborted)
211
+ onAbort();
212
+ });
213
+ /*
214
+ * An AbortSignal can fire during an async `onAccepted` callback, before the
215
+ * receipt reaches caller code. Keep that legitimate rejection observable
216
+ * without creating a process-level unhandled-rejection race.
217
+ */
218
+ void promise.catch(() => undefined);
219
+ return promise;
220
+ }
221
+ export function attachAuthorityAbort(controller, onSettled) {
222
+ const signal = controller.authority.signal;
223
+ if (signal === undefined)
224
+ return;
225
+ const onAbort = () => {
226
+ settleProjectionFailure(controller, new ReplicaCommandRuntimeError('REPLICA_COMMAND_SCOPE_INVALIDATED', { commandId: controller.commandId }));
227
+ onSettled();
228
+ };
229
+ signal.addEventListener('abort', onAbort, { once: true });
230
+ controller.abort = () => signal.removeEventListener('abort', onAbort);
231
+ if (signal.aborted)
232
+ onAbort();
233
+ }
234
+ export function settleProjectionFailure(controller, error) {
235
+ if (controller.settled)
236
+ return;
237
+ controller.settled = true;
238
+ controller.abort?.();
239
+ controller.stopMonitor?.();
240
+ controller.reject(error);
241
+ }
242
+ export function settleTrackedProjection(tracker, pending) {
243
+ const controller = tracker.pending;
244
+ if (controller === undefined || controller.settled)
245
+ return;
246
+ settleProjectionSuccess(controller);
247
+ pending.delete(controller.commandId);
248
+ }
249
+ export function failTrackedProjection(tracker, pending, error) {
250
+ const controller = tracker.pending;
251
+ if (controller === undefined)
252
+ return;
253
+ settleProjectionFailure(controller, error);
254
+ pending.delete(controller.commandId);
255
+ }
@@ -0,0 +1,7 @@
1
+ import { type DistributedCommandMetadata, type DistributedCommandState } from '../../../protocol.js';
2
+ import { type ReplicaPreparedCommand } from '../../commands.js';
3
+ import type { CapturedAuthority, ReplicaCommandStatus, ReplicaCommandStatusArtifact, ReplicaCommandSurfaceContract, ReplicaCommandTransportResult } from '../types.js';
4
+ export declare function requireStatusEnvelope<TInput, TOutput>(result: ReplicaCommandTransportResult, artifact: ReplicaCommandStatusArtifact, prepared: ReplicaPreparedCommand<TInput, TOutput>, authority: CapturedAuthority, contract: ReplicaCommandSurfaceContract): ReplicaCommandStatus;
5
+ export declare function commandStatusOutput(data: Readonly<Record<string, unknown>> | null | undefined): DistributedCommandState;
6
+ export declare function validateStatusProgression(previousState: DistributedCommandState | undefined, previous: DistributedCommandMetadata | undefined, current: ReplicaCommandStatus): void;
7
+ export declare function isStatusTransition(previous: DistributedCommandState, next: DistributedCommandState): boolean;
@@ -0,0 +1,127 @@
1
+ import { parseGraphqlResponseExtensions } from '../../../protocol.js';
2
+ import { matchReplicaTrustedPresetInventory, verifyReplicaCommandReceipt } from '../../commands.js';
3
+ import { isPlainRecord } from '../../../lib/is-plain-record.js';
4
+ import { isStringSubset, projectionExpectationFingerprint, projectionObservationFingerprint, recordRevisionFingerprint, sameStringMultiset } from './output.js';
5
+ export function requireStatusEnvelope(result, artifact, prepared, authority, contract) {
6
+ if (!Number.isSafeInteger(result.status) ||
7
+ result.status < 200 ||
8
+ result.status >= 300 ||
9
+ (result.errors?.length ?? 0) !== 0) {
10
+ throw new Error('command status request did not succeed');
11
+ }
12
+ const state = commandStatusOutput(result.data);
13
+ const distributed = parseGraphqlResponseExtensions(result.extensions)?.distributed;
14
+ if (distributed === undefined ||
15
+ distributed.operation !== artifact.operationHash ||
16
+ distributed.protocolVersion !== authority.scope.protocolVersion ||
17
+ distributed.schemaHash !== authority.scope.schemaHash ||
18
+ distributed.cacheScope !== authority.scope.cacheScope ||
19
+ distributed.snapshot !== undefined ||
20
+ distributed.live !== undefined) {
21
+ throw new Error('command status response does not match its generated scope');
22
+ }
23
+ matchReplicaTrustedPresetInventory(contract.trustedPresets, distributed.trustedPresets);
24
+ const metadata = distributed.command;
25
+ if (metadata === undefined) {
26
+ if (state !== 'unknown' && state !== 'expired') {
27
+ throw new Error('command status omitted required causal metadata');
28
+ }
29
+ return Object.freeze({
30
+ commandId: prepared.commandId,
31
+ state
32
+ });
33
+ }
34
+ verifyReplicaCommandReceipt(prepared, metadata);
35
+ if (metadata.state !== state) {
36
+ throw new Error('command status data and causal metadata disagree');
37
+ }
38
+ return Object.freeze({
39
+ commandId: prepared.commandId,
40
+ state,
41
+ metadata
42
+ });
43
+ }
44
+ export function commandStatusOutput(data) {
45
+ if (data === undefined ||
46
+ data === null ||
47
+ !isPlainRecord(data) ||
48
+ Reflect.ownKeys(data).length !== 1 ||
49
+ !Object.prototype.hasOwnProperty.call(data, 'commandStatus')) {
50
+ throw new Error('command status data has an invalid root shape');
51
+ }
52
+ const value = data.commandStatus;
53
+ if (!isPlainRecord(value) ||
54
+ Reflect.ownKeys(value).length !== 1 ||
55
+ !Object.prototype.hasOwnProperty.call(value, 'state')) {
56
+ throw new Error('command status data has an invalid result shape');
57
+ }
58
+ switch (value.state) {
59
+ case 'in_progress':
60
+ case 'accepted':
61
+ case 'accepted_pending_projection':
62
+ case 'projected':
63
+ case 'rejected':
64
+ case 'projection_failed':
65
+ case 'expired':
66
+ case 'unknown':
67
+ return value.state;
68
+ default:
69
+ throw new Error('command status data has an invalid state');
70
+ }
71
+ }
72
+ export function validateStatusProgression(previousState, previous, current) {
73
+ if (previousState !== undefined &&
74
+ !isStatusTransition(previousState, current.state)) {
75
+ throw new Error('command status regressed or changed terminal outcome');
76
+ }
77
+ const next = current.metadata;
78
+ if (next === undefined) {
79
+ if ((current.state !== 'unknown' && current.state !== 'expired') ||
80
+ (previous !== undefined && current.state === 'unknown')) {
81
+ throw new Error('command status lost causal metadata');
82
+ }
83
+ return;
84
+ }
85
+ if (next.state !== current.state) {
86
+ throw new Error('command status metadata has an inconsistent state');
87
+ }
88
+ if (previous === undefined)
89
+ return;
90
+ if (next.commandId !== previous.commandId ||
91
+ next.causationId !== previous.causationId ||
92
+ next.consistency !== previous.consistency) {
93
+ throw new Error('command status changed causal identity');
94
+ }
95
+ if (!(previous.state === 'in_progress' &&
96
+ previous.expects.length === 0) &&
97
+ !sameStringMultiset(previous.expects.map(projectionExpectationFingerprint), next.expects.map(projectionExpectationFingerprint))) {
98
+ throw new Error('command status changed projection expectations');
99
+ }
100
+ if (!isStringSubset(previous.observations.map(projectionObservationFingerprint), next.observations.map(projectionObservationFingerprint)) ||
101
+ !isStringSubset(previous.records.map(recordRevisionFingerprint), next.records.map(recordRevisionFingerprint))) {
102
+ throw new Error('command status lost causal evidence');
103
+ }
104
+ }
105
+ export function isStatusTransition(previous, next) {
106
+ switch (previous) {
107
+ case 'unknown':
108
+ return true;
109
+ case 'in_progress':
110
+ return next !== 'unknown';
111
+ case 'accepted':
112
+ return next === 'accepted' || next === 'expired';
113
+ case 'accepted_pending_projection':
114
+ return (next === 'accepted_pending_projection' ||
115
+ next === 'projected' ||
116
+ next === 'projection_failed' ||
117
+ next === 'expired');
118
+ case 'projected':
119
+ return next === 'projected' || next === 'expired';
120
+ case 'rejected':
121
+ return next === 'rejected' || next === 'expired';
122
+ case 'projection_failed':
123
+ return next === 'projection_failed' || next === 'expired';
124
+ case 'expired':
125
+ return next === 'expired';
126
+ }
127
+ }
@@ -0,0 +1,16 @@
1
+ import { type DistributedProtocolEnvelope } from '../../../protocol.js';
2
+ import type { GqlError } from '../../../types.js';
3
+ import { type ReplicaPreparedCommand } from '../../commands.js';
4
+ import type { CapturedAuthority, ReplicaCommandStatusArtifact, ReplicaCommandStatusRequest, ReplicaCommandTransport, ReplicaCommandTransportRequest, ReplicaCommandTransportResult } from '../types.js';
5
+ export declare function commandTransportRequest<TInput, TOutput>(prepared: ReplicaPreparedCommand<TInput, TOutput>, signal: AbortSignal | undefined): ReplicaCommandTransportRequest;
6
+ export declare function commandStatusRequest(artifact: ReplicaCommandStatusArtifact, commandId: string, signal: AbortSignal | undefined): ReplicaCommandStatusRequest;
7
+ export declare function dispatchPrepared(transport: ReplicaCommandTransport, request: ReplicaCommandTransportRequest, retries: number, onAttempt: () => void): Promise<ReplicaCommandTransportResult>;
8
+ export declare function requireCommandEnvelope<TInput, TOutput>(result: ReplicaCommandTransportResult, prepared: ReplicaPreparedCommand<TInput, TOutput>, authority: CapturedAuthority): DistributedProtocolEnvelope;
9
+ /**
10
+ * Domain rejection happens before a command receipt exists, so GraphQL cannot
11
+ * attach `distributed.command`. It still has to prove the exact generated
12
+ * operation and authoritative cache scope before the runtime may classify the
13
+ * response as a normal rejection instead of a protocol failure.
14
+ */
15
+ export declare function requireCommandRejectionEnvelope<TInput, TOutput>(result: ReplicaCommandTransportResult, prepared: ReplicaPreparedCommand<TInput, TOutput>, authority: CapturedAuthority): DistributedProtocolEnvelope;
16
+ export declare function graphqlCommandRejection(result: ReplicaCommandTransportResult): GqlError | undefined;
@@ -0,0 +1,112 @@
1
+ import { parseGraphqlResponseExtensions } from '../../../protocol.js';
2
+ import { verifyReplicaCommandReceipt } from '../../commands.js';
3
+ import { ReplicaCommandRuntimeError } from '../errors.js';
4
+ import { cloneSurface, waitForCommandOperation } from './util.js';
5
+ export function commandTransportRequest(prepared, signal) {
6
+ const surface = prepared.transport.protocol.surface;
7
+ if (surface === undefined) {
8
+ throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_PROTOCOL_INVALID', { commandId: prepared.commandId });
9
+ }
10
+ return Object.freeze({
11
+ operation: 'mutation',
12
+ commandName: prepared.name,
13
+ commandId: prepared.commandId,
14
+ mutationField: prepared.transport.mutationField,
15
+ document: prepared.transport.document,
16
+ operationHash: prepared.transport.operationHash,
17
+ variables: prepared.transport.variables,
18
+ extensions: Object.freeze({
19
+ distributed: Object.freeze({
20
+ client: Object.freeze({
21
+ surface: cloneSurface(surface),
22
+ schemaHash: prepared.transport.protocol.schemaHash
23
+ })
24
+ })
25
+ }),
26
+ ...(signal === undefined ? {} : { signal })
27
+ });
28
+ }
29
+ export function commandStatusRequest(artifact, commandId, signal) {
30
+ return Object.freeze({
31
+ operation: 'status',
32
+ commandId,
33
+ name: artifact.name,
34
+ document: artifact.document,
35
+ operationHash: artifact.operationHash,
36
+ variables: Object.freeze({ commandId }),
37
+ extensions: Object.freeze({
38
+ distributed: Object.freeze({
39
+ client: Object.freeze({
40
+ surface: cloneSurface(artifact.protocol.surface),
41
+ schemaHash: artifact.protocol.schemaHash
42
+ })
43
+ })
44
+ }),
45
+ ...(signal === undefined ? {} : { signal })
46
+ });
47
+ }
48
+ export async function dispatchPrepared(transport, request, retries, onAttempt) {
49
+ let error;
50
+ for (let attempt = 0; attempt <= retries; attempt += 1) {
51
+ if (request.signal?.aborted) {
52
+ throw request.signal.reason ?? new Error('command request aborted');
53
+ }
54
+ try {
55
+ onAttempt();
56
+ return await waitForCommandOperation(transport.dispatch(request), request.signal);
57
+ }
58
+ catch (candidate) {
59
+ error = candidate;
60
+ if (request.signal?.aborted)
61
+ throw candidate;
62
+ }
63
+ }
64
+ throw error;
65
+ }
66
+ export function requireCommandEnvelope(result, prepared, authority) {
67
+ const distributed = parseGraphqlResponseExtensions(result.extensions)?.distributed;
68
+ if (distributed === undefined ||
69
+ distributed.command === undefined ||
70
+ distributed.operation !== prepared.transport.operationHash ||
71
+ distributed.protocolVersion !== authority.scope.protocolVersion ||
72
+ distributed.schemaHash !== authority.scope.schemaHash ||
73
+ distributed.cacheScope !== authority.scope.cacheScope) {
74
+ throw new Error('command response does not match its generated scope');
75
+ }
76
+ verifyReplicaCommandReceipt(prepared, distributed.command);
77
+ return distributed;
78
+ }
79
+ /**
80
+ * Domain rejection happens before a command receipt exists, so GraphQL cannot
81
+ * attach `distributed.command`. It still has to prove the exact generated
82
+ * operation and authoritative cache scope before the runtime may classify the
83
+ * response as a normal rejection instead of a protocol failure.
84
+ */
85
+ export function requireCommandRejectionEnvelope(result, prepared, authority) {
86
+ const distributed = parseGraphqlResponseExtensions(result.extensions)?.distributed;
87
+ if (!Number.isSafeInteger(result.status) ||
88
+ result.status < 200 ||
89
+ result.status >= 300 ||
90
+ result.data !== null ||
91
+ graphqlCommandRejection(result) === undefined ||
92
+ distributed === undefined ||
93
+ distributed.command !== undefined ||
94
+ distributed.operation !== prepared.transport.operationHash ||
95
+ distributed.protocolVersion !== authority.scope.protocolVersion ||
96
+ distributed.schemaHash !== authority.scope.schemaHash ||
97
+ distributed.cacheScope !== authority.scope.cacheScope) {
98
+ throw new Error('command rejection does not match its generated scope');
99
+ }
100
+ return distributed;
101
+ }
102
+ export function graphqlCommandRejection(result) {
103
+ const errors = result.errors;
104
+ if (parseGraphqlResponseExtensions(result.extensions)?.distributed?.command !==
105
+ undefined ||
106
+ errors === undefined ||
107
+ errors.length === 0 ||
108
+ !errors.every((error) => error.extensions?.code === 'REJECTED')) {
109
+ return undefined;
110
+ }
111
+ return errors[0];
112
+ }
@@ -0,0 +1,16 @@
1
+ import type { ReplicaAuthoritativeScope, ReplicaClientSurface } from '../../types.js';
2
+ import { compareCodeUnits } from '../../../lib/compare-code-units.js';
3
+ import { isPlainRecord } from '../../../lib/is-plain-record.js';
4
+ export declare function normalizeRetries(value: number | undefined): number;
5
+ export declare function cloneScope(scope: ReplicaAuthoritativeScope): ReplicaAuthoritativeScope;
6
+ export declare function sameScope(left: ReplicaAuthoritativeScope, right: ReplicaAuthoritativeScope): boolean;
7
+ export declare function sameSurface(left: ReplicaClientSurface | undefined, right: ReplicaClientSurface): boolean;
8
+ export declare function cloneSurface(surface: ReplicaClientSurface): ReplicaClientSurface;
9
+ export declare function linkAbortSignals(signals: readonly (AbortSignal | undefined)[]): Readonly<{
10
+ signal: AbortSignal | undefined;
11
+ dispose(): void;
12
+ }>;
13
+ export declare function waitForCommandOperation<T>(operation: Promise<T> | T, signal: AbortSignal | undefined): Promise<T>;
14
+ export declare function outputInvalid(path: string): never;
15
+ export declare function comparePropertyKeys(left: PropertyKey, right: PropertyKey): number;
16
+ export { compareCodeUnits, isPlainRecord };
@@ -0,0 +1,127 @@
1
+ import { MAX_TRANSPORT_RETRIES } from '../constants.js';
2
+ import { ReplicaCommandRuntimeError } from '../errors.js';
3
+ import { compareCodeUnits } from '../../../lib/compare-code-units.js';
4
+ import { isPlainRecord } from '../../../lib/is-plain-record.js';
5
+ export function normalizeRetries(value) {
6
+ if (value === undefined)
7
+ return 0;
8
+ if (!Number.isSafeInteger(value) || value < 0 || value > MAX_TRANSPORT_RETRIES) {
9
+ throw new TypeError(`transportRetries must be an integer between 0 and ${MAX_TRANSPORT_RETRIES}`);
10
+ }
11
+ return value;
12
+ }
13
+ export function cloneScope(scope) {
14
+ return Object.freeze({
15
+ protocolVersion: 1,
16
+ schemaHash: scope.schemaHash,
17
+ cacheScope: scope.cacheScope
18
+ });
19
+ }
20
+ export function sameScope(left, right) {
21
+ return (left.protocolVersion === right.protocolVersion &&
22
+ left.schemaHash === right.schemaHash &&
23
+ left.cacheScope === right.cacheScope);
24
+ }
25
+ export function sameSurface(left, right) {
26
+ if (left === undefined ||
27
+ left.kind !== right.kind ||
28
+ left.name !== right.name) {
29
+ return false;
30
+ }
31
+ return (left.kind === 'role' ||
32
+ (right.kind === 'application' &&
33
+ left.roles.length === right.roles.length &&
34
+ left.roles.every((role, index) => role === right.roles[index])));
35
+ }
36
+ export function cloneSurface(surface) {
37
+ return surface.kind === 'role'
38
+ ? Object.freeze({ kind: 'role', name: surface.name })
39
+ : Object.freeze({
40
+ kind: 'application',
41
+ name: surface.name,
42
+ roles: Object.freeze([...surface.roles])
43
+ });
44
+ }
45
+ export function linkAbortSignals(signals) {
46
+ const sources = [
47
+ ...new Set(signals.filter((signal) => signal !== undefined))
48
+ ];
49
+ if (sources.length === 0) {
50
+ return Object.freeze({
51
+ signal: undefined,
52
+ dispose() { }
53
+ });
54
+ }
55
+ if (sources.length === 1) {
56
+ return Object.freeze({
57
+ signal: sources[0],
58
+ dispose() { }
59
+ });
60
+ }
61
+ const controller = new AbortController();
62
+ const listeners = new Map();
63
+ let disposed = false;
64
+ const dispose = () => {
65
+ if (disposed)
66
+ return;
67
+ disposed = true;
68
+ for (const [source, listener] of listeners) {
69
+ source.removeEventListener('abort', listener);
70
+ }
71
+ listeners.clear();
72
+ };
73
+ const abort = (signal) => {
74
+ if (!controller.signal.aborted) {
75
+ controller.abort(signal.reason);
76
+ }
77
+ dispose();
78
+ };
79
+ for (const source of sources) {
80
+ if (source.aborted) {
81
+ abort(source);
82
+ break;
83
+ }
84
+ const listener = () => abort(source);
85
+ listeners.set(source, listener);
86
+ source.addEventListener('abort', listener, { once: true });
87
+ // Close the check/register race if a source aborted synchronously.
88
+ if (source.aborted) {
89
+ listener();
90
+ break;
91
+ }
92
+ }
93
+ return Object.freeze({ signal: controller.signal, dispose });
94
+ }
95
+ export function waitForCommandOperation(operation, signal) {
96
+ const result = Promise.resolve(operation);
97
+ if (signal === undefined)
98
+ return result;
99
+ return new Promise((resolve, reject) => {
100
+ let settled = false;
101
+ const finish = (complete) => {
102
+ if (settled)
103
+ return;
104
+ settled = true;
105
+ signal.removeEventListener('abort', onAbort);
106
+ complete();
107
+ };
108
+ const onAbort = () => {
109
+ finish(() => reject(signal.reason ??
110
+ new ReplicaCommandRuntimeError('REPLICA_COMMAND_ABORTED')));
111
+ };
112
+ signal.addEventListener('abort', onAbort, { once: true });
113
+ void result.then((value) => finish(() => resolve(value)), (error) => finish(() => reject(error)));
114
+ // Close the check/register race if the signal aborted synchronously.
115
+ if (signal.aborted)
116
+ onAbort();
117
+ });
118
+ }
119
+ export function outputInvalid(path) {
120
+ throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_PROTOCOL_INVALID', {
121
+ cause: new TypeError(`invalid command output at ${path}`)
122
+ });
123
+ }
124
+ export function comparePropertyKeys(left, right) {
125
+ return compareCodeUnits(String(left), String(right));
126
+ }
127
+ export { compareCodeUnits, isPlainRecord };
@@ -0,0 +1,11 @@
1
+ import type { ReplicaCommandProjectedOutcome, ReplicaCommandReceipt } from './types.js';
2
+ /**
3
+ * Package-private causal lifecycle used by framework adapters.
4
+ *
5
+ * A caller-scoped `receipt.projected` can reject when its AbortSignal fires
6
+ * after acceptance. The underlying command remains globally pending until
7
+ * canonical projection evidence settles this independent promise.
8
+ *
9
+ * @internal
10
+ */
11
+ export declare function replicaCommandProjectedLifecycleOf(receipt: ReplicaCommandReceipt<unknown>): Promise<ReplicaCommandProjectedOutcome<unknown>> | undefined;