@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.
- package/README.md +454 -0
- package/dist/auth-headers.d.ts +8 -0
- package/dist/auth-headers.js +35 -0
- package/dist/diagnostics.d.ts +2 -0
- package/dist/diagnostics.js +1 -0
- package/dist/document.d.ts +8 -0
- package/dist/document.js +6 -0
- package/dist/identity.d.ts +23 -0
- package/dist/identity.js +73 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +6 -0
- package/dist/internal/cache-engine/create.d.ts +5 -0
- package/dist/internal/cache-engine/create.js +7 -0
- package/dist/internal/cache-engine/engine.d.ts +32 -0
- package/dist/internal/cache-engine/engine.js +1287 -0
- package/dist/internal/cache-engine/errors.d.ts +5 -0
- package/dist/internal/cache-engine/errors.js +10 -0
- package/dist/internal/cache-engine/helpers.d.ts +55 -0
- package/dist/internal/cache-engine/helpers.js +545 -0
- package/dist/internal/cache-engine/index.d.ts +3 -0
- package/dist/internal/cache-engine/index.js +2 -0
- package/dist/internal/cache-engine/types.d.ts +300 -0
- package/dist/internal/cache-engine/types.js +1 -0
- package/dist/internal/cache-engine.d.ts +3 -0
- package/dist/internal/cache-engine.js +2 -0
- package/dist/lib/assert-name.d.ts +2 -0
- package/dist/lib/assert-name.js +6 -0
- package/dist/lib/compare-code-units.d.ts +2 -0
- package/dist/lib/compare-code-units.js +4 -0
- package/dist/lib/deep-equal.d.ts +2 -0
- package/dist/lib/deep-equal.js +24 -0
- package/dist/lib/freeze-record.d.ts +2 -0
- package/dist/lib/freeze-record.js +13 -0
- package/dist/lib/index.d.ts +6 -0
- package/dist/lib/index.js +6 -0
- package/dist/lib/is-plain-record.d.ts +2 -0
- package/dist/lib/is-plain-record.js +8 -0
- package/dist/lib/report.d.ts +7 -0
- package/dist/lib/report.js +26 -0
- package/dist/protocol.d.ts +179 -0
- package/dist/protocol.js +558 -0
- package/dist/react/context.d.ts +20 -0
- package/dist/react/context.js +24 -0
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.js +3 -0
- package/dist/react/query.d.ts +20 -0
- package/dist/react/query.js +99 -0
- package/dist/replica/command-id.d.ts +2 -0
- package/dist/replica/command-id.js +19 -0
- package/dist/replica/command-runtime/constants.d.ts +5 -0
- package/dist/replica/command-runtime/constants.js +5 -0
- package/dist/replica/command-runtime/create.d.ts +2 -0
- package/dist/replica/command-runtime/create.js +644 -0
- package/dist/replica/command-runtime/errors.d.ts +16 -0
- package/dist/replica/command-runtime/errors.js +41 -0
- package/dist/replica/command-runtime/index.d.ts +5 -0
- package/dist/replica/command-runtime/index.js +4 -0
- package/dist/replica/command-runtime/lib/binding.d.ts +4 -0
- package/dist/replica/command-runtime/lib/binding.js +64 -0
- package/dist/replica/command-runtime/lib/effects.d.ts +11 -0
- package/dist/replica/command-runtime/lib/effects.js +89 -0
- package/dist/replica/command-runtime/lib/index.d.ts +9 -0
- package/dist/replica/command-runtime/lib/index.js +9 -0
- package/dist/replica/command-runtime/lib/inventory.d.ts +10 -0
- package/dist/replica/command-runtime/lib/inventory.js +141 -0
- package/dist/replica/command-runtime/lib/output.d.ts +13 -0
- package/dist/replica/command-runtime/lib/output.js +190 -0
- package/dist/replica/command-runtime/lib/projection.d.ts +20 -0
- package/dist/replica/command-runtime/lib/projection.js +255 -0
- package/dist/replica/command-runtime/lib/status.d.ts +7 -0
- package/dist/replica/command-runtime/lib/status.js +127 -0
- package/dist/replica/command-runtime/lib/transport.d.ts +16 -0
- package/dist/replica/command-runtime/lib/transport.js +112 -0
- package/dist/replica/command-runtime/lib/util.d.ts +16 -0
- package/dist/replica/command-runtime/lib/util.js +127 -0
- package/dist/replica/command-runtime/lifecycle.d.ts +11 -0
- package/dist/replica/command-runtime/lifecycle.js +13 -0
- package/dist/replica/command-runtime/symbols.d.ts +30 -0
- package/dist/replica/command-runtime/symbols.js +30 -0
- package/dist/replica/command-runtime/types.d.ts +222 -0
- package/dist/replica/command-runtime/types.js +1 -0
- package/dist/replica/command-runtime.d.ts +3 -0
- package/dist/replica/command-runtime.js +2 -0
- package/dist/replica/commands/clone.d.ts +12 -0
- package/dist/replica/commands/clone.js +187 -0
- package/dist/replica/commands/constants.d.ts +6 -0
- package/dist/replica/commands/constants.js +6 -0
- package/dist/replica/commands/errors.d.ts +7 -0
- package/dist/replica/commands/errors.js +22 -0
- package/dist/replica/commands/implementation.d.ts +8 -0
- package/dist/replica/commands/implementation.js +8 -0
- package/dist/replica/commands/index.d.ts +5 -0
- package/dist/replica/commands/index.js +4 -0
- package/dist/replica/commands/prepare.d.ts +27 -0
- package/dist/replica/commands/prepare.js +77 -0
- package/dist/replica/commands/presets.d.ts +12 -0
- package/dist/replica/commands/presets.js +84 -0
- package/dist/replica/commands/receipt.d.ts +3 -0
- package/dist/replica/commands/receipt.js +36 -0
- package/dist/replica/commands/resolve.d.ts +11 -0
- package/dist/replica/commands/resolve.js +165 -0
- package/dist/replica/commands/types.d.ts +278 -0
- package/dist/replica/commands/types.js +2 -0
- package/dist/replica/commands/util.d.ts +21 -0
- package/dist/replica/commands/util.js +96 -0
- package/dist/replica/commands/validate.d.ts +18 -0
- package/dist/replica/commands/validate.js +477 -0
- package/dist/replica/commands.d.ts +3 -0
- package/dist/replica/commands.js +2 -0
- package/dist/replica/diagnostics/event-log.d.ts +4 -0
- package/dist/replica/diagnostics/event-log.js +463 -0
- package/dist/replica/diagnostics/implementation.d.ts +4 -0
- package/dist/replica/diagnostics/implementation.js +4 -0
- package/dist/replica/diagnostics/index.d.ts +2 -0
- package/dist/replica/diagnostics/index.js +1 -0
- package/dist/replica/diagnostics/inspect.d.ts +6 -0
- package/dist/replica/diagnostics/inspect.js +170 -0
- package/dist/replica/diagnostics/types.d.ts +295 -0
- package/dist/replica/diagnostics/types.js +2 -0
- package/dist/replica/diagnostics.d.ts +3 -0
- package/dist/replica/diagnostics.js +2 -0
- package/dist/replica/distributed-replica/clocks.d.ts +23 -0
- package/dist/replica/distributed-replica/clocks.js +156 -0
- package/dist/replica/distributed-replica/constants.d.ts +12 -0
- package/dist/replica/distributed-replica/constants.js +10 -0
- package/dist/replica/distributed-replica/helpers.d.ts +48 -0
- package/dist/replica/distributed-replica/helpers.js +445 -0
- package/dist/replica/distributed-replica/hydration.d.ts +19 -0
- package/dist/replica/distributed-replica/hydration.js +380 -0
- package/dist/replica/distributed-replica/impl-diagnostics.d.ts +22 -0
- package/dist/replica/distributed-replica/impl-diagnostics.js +189 -0
- package/dist/replica/distributed-replica/impl-fetch-live.d.ts +39 -0
- package/dist/replica/distributed-replica/impl-fetch-live.js +395 -0
- package/dist/replica/distributed-replica/impl-hydration-orchestrate.d.ts +56 -0
- package/dist/replica/distributed-replica/impl-hydration-orchestrate.js +299 -0
- package/dist/replica/distributed-replica/impl-optimistic.d.ts +31 -0
- package/dist/replica/distributed-replica/impl-optimistic.js +172 -0
- package/dist/replica/distributed-replica/impl-protocol.d.ts +53 -0
- package/dist/replica/distributed-replica/impl-protocol.js +108 -0
- package/dist/replica/distributed-replica/impl.d.ts +44 -0
- package/dist/replica/distributed-replica/impl.js +1776 -0
- package/dist/replica/distributed-replica/index.d.ts +2 -0
- package/dist/replica/distributed-replica/index.js +4 -0
- package/dist/replica/distributed-replica/optimistic.d.ts +30 -0
- package/dist/replica/distributed-replica/optimistic.js +221 -0
- package/dist/replica/distributed-replica/types.d.ts +147 -0
- package/dist/replica/distributed-replica/types.js +1 -0
- package/dist/replica/distributed-replica/watch.d.ts +19 -0
- package/dist/replica/distributed-replica/watch.js +95 -0
- package/dist/replica/distributed-replica.d.ts +2 -0
- package/dist/replica/distributed-replica.js +2 -0
- package/dist/replica/graphql-transport.d.ts +24 -0
- package/dist/replica/graphql-transport.js +122 -0
- package/dist/replica/identity/clone.d.ts +6 -0
- package/dist/replica/identity/clone.js +54 -0
- package/dist/replica/identity/codec.d.ts +48 -0
- package/dist/replica/identity/codec.js +789 -0
- package/dist/replica/identity/constants.d.ts +3 -0
- package/dist/replica/identity/constants.js +18 -0
- package/dist/replica/identity/implementation.d.ts +5 -0
- package/dist/replica/identity/implementation.js +5 -0
- package/dist/replica/identity/index.d.ts +3 -0
- package/dist/replica/identity/index.js +3 -0
- package/dist/replica/identity/keys.d.ts +29 -0
- package/dist/replica/identity/keys.js +173 -0
- package/dist/replica/identity.d.ts +2 -0
- package/dist/replica/identity.js +2 -0
- package/dist/replica/index-maintenance/engine.d.ts +84 -0
- package/dist/replica/index-maintenance/engine.js +677 -0
- package/dist/replica/index-maintenance/implementation.d.ts +4 -0
- package/dist/replica/index-maintenance/implementation.js +4 -0
- package/dist/replica/index-maintenance/index.d.ts +2 -0
- package/dist/replica/index-maintenance/index.js +1 -0
- package/dist/replica/index-maintenance/registry.d.ts +2 -0
- package/dist/replica/index-maintenance/registry.js +49 -0
- package/dist/replica/index-maintenance/types.d.ts +58 -0
- package/dist/replica/index-maintenance/types.js +1 -0
- package/dist/replica/index-maintenance.d.ts +3 -0
- package/dist/replica/index-maintenance.js +2 -0
- package/dist/replica/index.d.ts +17 -0
- package/dist/replica/index.js +9 -0
- package/dist/replica/materialize.d.ts +10 -0
- package/dist/replica/materialize.js +163 -0
- package/dist/replica/normalize.d.ts +27 -0
- package/dist/replica/normalize.js +298 -0
- package/dist/replica/operation-binding.d.ts +14 -0
- package/dist/replica/operation-binding.js +76 -0
- package/dist/replica/persistence/idb.d.ts +20 -0
- package/dist/replica/persistence/idb.js +193 -0
- package/dist/replica/persistence/implementation.d.ts +4 -0
- package/dist/replica/persistence/implementation.js +4 -0
- package/dist/replica/persistence/index.d.ts +2 -0
- package/dist/replica/persistence/index.js +1 -0
- package/dist/replica/persistence/state.d.ts +104 -0
- package/dist/replica/persistence/state.js +734 -0
- package/dist/replica/persistence/types.d.ts +54 -0
- package/dist/replica/persistence/types.js +1 -0
- package/dist/replica/persistence.d.ts +3 -0
- package/dist/replica/persistence.js +2 -0
- package/dist/replica/query-plan/constants.d.ts +13 -0
- package/dist/replica/query-plan/constants.js +7 -0
- package/dist/replica/query-plan/filter.d.ts +39 -0
- package/dist/replica/query-plan/filter.js +544 -0
- package/dist/replica/query-plan/index.d.ts +4 -0
- package/dist/replica/query-plan/index.js +3 -0
- package/dist/replica/query-plan/order.d.ts +11 -0
- package/dist/replica/query-plan/order.js +158 -0
- package/dist/replica/query-plan/pagination.d.ts +6 -0
- package/dist/replica/query-plan/pagination.js +90 -0
- package/dist/replica/query-plan/resolve.d.ts +14 -0
- package/dist/replica/query-plan/resolve.js +163 -0
- package/dist/replica/query-plan/types.d.ts +89 -0
- package/dist/replica/query-plan/types.js +1 -0
- package/dist/replica/query-plan/util.d.ts +13 -0
- package/dist/replica/query-plan/util.js +62 -0
- package/dist/replica/query-plan.d.ts +3 -0
- package/dist/replica/query-plan.js +2 -0
- package/dist/replica/revalidation.d.ts +9 -0
- package/dist/replica/revalidation.js +86 -0
- package/dist/replica/selection.d.ts +20 -0
- package/dist/replica/selection.js +109 -0
- package/dist/replica/types.d.ts +657 -0
- package/dist/replica/types.js +1 -0
- package/dist/request.d.ts +17 -0
- package/dist/request.js +123 -0
- package/dist/sveltekit/auth.d.ts +18 -0
- package/dist/sveltekit/auth.js +9 -0
- package/dist/sveltekit/context.d.ts +28 -0
- package/dist/sveltekit/context.js +58 -0
- package/dist/sveltekit/index.d.ts +4 -0
- package/dist/sveltekit/index.js +4 -0
- package/dist/sveltekit/replica.d.ts +145 -0
- package/dist/sveltekit/replica.js +491 -0
- package/dist/sveltekit/server-replica.d.ts +52 -0
- package/dist/sveltekit/server-replica.js +176 -0
- package/dist/sveltekit/vite.d.ts +103 -0
- package/dist/sveltekit/vite.js +878 -0
- package/dist/types.d.ts +33 -0
- package/dist/types.js +1 -0
- package/dist/websocket.d.ts +41 -0
- package/dist/websocket.js +187 -0
- package/package.json +95 -0
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
import { parseGraphqlResponseExtensions } from '../../protocol.js';
|
|
2
|
+
import { matchReplicaTrustedPresetInventory, prepareReplicaCommandWithTrustedPresets, verifyReplicaCommandReceipt } from '../commands.js';
|
|
3
|
+
import { applyOptimisticEffects, attachAuthorityAbort, callerProjectedPromise, cloneScope, commandOutput, commandStatusArtifact, commandStatusRequest, commandSurfaceContract, commandTransportRequest, confirmDirectProjection, defineBoundCommand, dispatchPrepared, failTrackedProjection, freezeCommandTree, graphqlCommandRejection, linkAbortSignals, monitorPendingProjection, normalizeInventory, normalizeRetries, pendingProjection, preparedSemanticChanges, requireCommandEnvelope, requireCommandRejectionEnvelope, requireStatusEnvelope, sameScope, settleProjectionFailure, settleProjectionSuccess, settleTrackedProjection, validateStatusProgression, waitForCommandOperation } from './lib/index.js';
|
|
4
|
+
import { ReplicaCommandRuntimeError } from './errors.js';
|
|
5
|
+
import { replicaCommandAuthority, replicaCommandProjectedLifecycle, replicaResultObservation } from './symbols.js';
|
|
6
|
+
export function createReplicaCommandRuntime(replica, transport, entries, options = {}) {
|
|
7
|
+
const inventory = normalizeInventory(entries);
|
|
8
|
+
if (options.diagnostics !== undefined) {
|
|
9
|
+
for (const { artifact } of inventory) {
|
|
10
|
+
try {
|
|
11
|
+
options.diagnostics.command(artifact);
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
options.onBackgroundError?.(error);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const contract = commandSurfaceContract(inventory.map(({ artifact }) => artifact), options.status?.protocol.trustedPresets);
|
|
19
|
+
const statusArtifact = options.status === undefined
|
|
20
|
+
? undefined
|
|
21
|
+
: commandStatusArtifact(options.status, contract);
|
|
22
|
+
const replicaRevalidate = typeof replica.revalidate === 'function'
|
|
23
|
+
? replica.revalidate.bind(replica)
|
|
24
|
+
: undefined;
|
|
25
|
+
if (statusArtifact !== undefined && transport.status === undefined) {
|
|
26
|
+
throw new TypeError('generated command status artifact requires transport.status');
|
|
27
|
+
}
|
|
28
|
+
if (replicaRevalidate === undefined &&
|
|
29
|
+
inventory.some(({ artifact }) => artifact.revalidation.required)) {
|
|
30
|
+
throw new TypeError('generated command revalidation plan requires replica.revalidate');
|
|
31
|
+
}
|
|
32
|
+
const registration = replica[replicaCommandAuthority]?.(contract);
|
|
33
|
+
const pending = new Map();
|
|
34
|
+
const unmanagedLayers = new Set();
|
|
35
|
+
const runtimeAbort = new AbortController();
|
|
36
|
+
let disposed = false;
|
|
37
|
+
const rejectUnmanagedLayer = (commandId) => {
|
|
38
|
+
unmanagedLayers.delete(commandId);
|
|
39
|
+
return replica.rejectOptimisticLayer(commandId);
|
|
40
|
+
};
|
|
41
|
+
const retireLayerAfterAuthoritativeRevalidation = (commandId) => {
|
|
42
|
+
if (!replica.markOptimisticLayerAccepted(commandId)) {
|
|
43
|
+
// The authoritative result may already have retired this layer.
|
|
44
|
+
unmanagedLayers.delete(commandId);
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
replica.confirmOptimisticLayer(commandId, () => undefined);
|
|
48
|
+
unmanagedLayers.delete(commandId);
|
|
49
|
+
return true;
|
|
50
|
+
};
|
|
51
|
+
const authoritySnapshot = () => registration?.read() ?? {
|
|
52
|
+
generation: replica.authorizationGeneration,
|
|
53
|
+
scope: replica.scope,
|
|
54
|
+
trustedPresets: Object.freeze([])
|
|
55
|
+
};
|
|
56
|
+
const currentAuthority = () => {
|
|
57
|
+
if (disposed) {
|
|
58
|
+
throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_DISPOSED');
|
|
59
|
+
}
|
|
60
|
+
const snapshot = authoritySnapshot();
|
|
61
|
+
const scope = snapshot.scope;
|
|
62
|
+
if (scope === undefined ||
|
|
63
|
+
scope.protocolVersion !== contract.protocolVersion ||
|
|
64
|
+
scope.schemaHash !== contract.schemaHash) {
|
|
65
|
+
throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_AUTHORITY_UNAVAILABLE');
|
|
66
|
+
}
|
|
67
|
+
const matched = matchReplicaTrustedPresetInventory(contract.trustedPresets, snapshot.trustedPresets);
|
|
68
|
+
return Object.freeze({
|
|
69
|
+
generation: snapshot.generation,
|
|
70
|
+
scope: cloneScope(scope),
|
|
71
|
+
trustedPresets: matched.values,
|
|
72
|
+
...(snapshot.signal === undefined ? {} : { signal: snapshot.signal })
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
const stillCurrent = (captured) => {
|
|
76
|
+
const current = authoritySnapshot();
|
|
77
|
+
return (!disposed &&
|
|
78
|
+
current.generation === captured.generation &&
|
|
79
|
+
current.scope !== undefined &&
|
|
80
|
+
sameScope(current.scope, captured.scope));
|
|
81
|
+
};
|
|
82
|
+
const revalidate = async (prepared, authoritySignal) => {
|
|
83
|
+
if (replicaRevalidate === undefined)
|
|
84
|
+
return false;
|
|
85
|
+
const revalidationSignals = linkAbortSignals([
|
|
86
|
+
authoritySignal,
|
|
87
|
+
runtimeAbort.signal
|
|
88
|
+
]);
|
|
89
|
+
try {
|
|
90
|
+
await waitForCommandOperation(replicaRevalidate(prepared.revalidation), revalidationSignals.signal);
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
if (!disposed && authoritySignal?.aborted !== true) {
|
|
95
|
+
options.onBackgroundError?.(error);
|
|
96
|
+
}
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
revalidationSignals.dispose();
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const revalidateInBackground = (prepared, authority) => {
|
|
104
|
+
void revalidate(prepared, authority?.signal).catch(() => undefined);
|
|
105
|
+
};
|
|
106
|
+
const revalidateAndConfirmUnmanagedInBackground = (prepared, authority) => {
|
|
107
|
+
void revalidate(prepared, authority.signal)
|
|
108
|
+
.then((refreshed) => {
|
|
109
|
+
if (refreshed &&
|
|
110
|
+
stillCurrent(authority) &&
|
|
111
|
+
unmanagedLayers.has(prepared.commandId)) {
|
|
112
|
+
/*
|
|
113
|
+
* A generated required revalidation is the canonical base
|
|
114
|
+
* fence for commands without a finite observation contract.
|
|
115
|
+
* Retire their accepted overlay only after that fetch settles.
|
|
116
|
+
*/
|
|
117
|
+
retireLayerAfterAuthoritativeRevalidation(prepared.commandId);
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
.catch(() => undefined);
|
|
121
|
+
};
|
|
122
|
+
const statusReader = (prepared, authority, tracker) => {
|
|
123
|
+
const read = async () => {
|
|
124
|
+
if (disposed) {
|
|
125
|
+
throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_DISPOSED', {
|
|
126
|
+
commandId: prepared.commandId
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
if (statusArtifact === undefined || transport.status === undefined) {
|
|
130
|
+
throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_STATUS_UNAVAILABLE', { commandId: prepared.commandId });
|
|
131
|
+
}
|
|
132
|
+
if (!stillCurrent(authority)) {
|
|
133
|
+
throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_SCOPE_INVALIDATED', { commandId: prepared.commandId });
|
|
134
|
+
}
|
|
135
|
+
let result;
|
|
136
|
+
const statusSignals = linkAbortSignals([
|
|
137
|
+
authority.signal,
|
|
138
|
+
runtimeAbort.signal
|
|
139
|
+
]);
|
|
140
|
+
try {
|
|
141
|
+
result = await waitForCommandOperation(transport.status(commandStatusRequest(statusArtifact, prepared.commandId, statusSignals.signal)), statusSignals.signal);
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
if (disposed) {
|
|
145
|
+
throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_DISPOSED', { commandId: prepared.commandId, cause: error });
|
|
146
|
+
}
|
|
147
|
+
throw new ReplicaCommandRuntimeError(authority.signal?.aborted
|
|
148
|
+
? 'REPLICA_COMMAND_SCOPE_INVALIDATED'
|
|
149
|
+
: 'REPLICA_COMMAND_TRANSPORT_AMBIGUOUS', { commandId: prepared.commandId, cause: error });
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
statusSignals.dispose();
|
|
153
|
+
}
|
|
154
|
+
if (!stillCurrent(authority)) {
|
|
155
|
+
throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_SCOPE_INVALIDATED', { commandId: prepared.commandId });
|
|
156
|
+
}
|
|
157
|
+
let status;
|
|
158
|
+
try {
|
|
159
|
+
status = requireStatusEnvelope(result, statusArtifact, prepared, authority, contract);
|
|
160
|
+
validateStatusProgression(tracker.state, tracker.metadata, status);
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
revalidateInBackground(prepared, authority);
|
|
164
|
+
throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_PROTOCOL_INVALID', { commandId: prepared.commandId, cause: error });
|
|
165
|
+
}
|
|
166
|
+
tracker.state = status.state;
|
|
167
|
+
if (status.metadata !== undefined) {
|
|
168
|
+
tracker.metadata = status.metadata;
|
|
169
|
+
if (tracker.pending !== undefined) {
|
|
170
|
+
tracker.pending.metadata = status.metadata;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
switch (status.state) {
|
|
174
|
+
case 'rejected':
|
|
175
|
+
rejectUnmanagedLayer(prepared.commandId);
|
|
176
|
+
failTrackedProjection(tracker, pending, new ReplicaCommandRuntimeError('REPLICA_COMMAND_REJECTED', {
|
|
177
|
+
commandId: prepared.commandId,
|
|
178
|
+
state: status.state
|
|
179
|
+
}));
|
|
180
|
+
break;
|
|
181
|
+
case 'projection_failed':
|
|
182
|
+
case 'expired':
|
|
183
|
+
rejectUnmanagedLayer(prepared.commandId);
|
|
184
|
+
revalidateInBackground(prepared, authority);
|
|
185
|
+
failTrackedProjection(tracker, pending, new ReplicaCommandRuntimeError('REPLICA_COMMAND_PROJECTION_FAILED', {
|
|
186
|
+
commandId: prepared.commandId,
|
|
187
|
+
state: status.state
|
|
188
|
+
}));
|
|
189
|
+
break;
|
|
190
|
+
case 'accepted':
|
|
191
|
+
case 'accepted_pending_projection':
|
|
192
|
+
case 'projected': {
|
|
193
|
+
const metadata = status.metadata;
|
|
194
|
+
const remainsPending = replica.markOptimisticLayerAccepted(prepared.commandId, metadata);
|
|
195
|
+
if (!remainsPending) {
|
|
196
|
+
unmanagedLayers.delete(prepared.commandId);
|
|
197
|
+
if (tracker.pending !== undefined) {
|
|
198
|
+
settleTrackedProjection(tracker, pending);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
else if (metadata.state === 'projected' ||
|
|
202
|
+
(metadata.state === 'accepted' &&
|
|
203
|
+
prepared.confirmations === undefined &&
|
|
204
|
+
prepared.revalidation.required)) {
|
|
205
|
+
/*
|
|
206
|
+
* Status is causal truth, but it carries no canonical
|
|
207
|
+
* query payload. `projected` fences asynchronous
|
|
208
|
+
* projection; `accepted` is terminal when the generated
|
|
209
|
+
* contract has no confirmations. In both cases, only a
|
|
210
|
+
* query started after that fence may retire optimism.
|
|
211
|
+
*/
|
|
212
|
+
let refreshed = false;
|
|
213
|
+
try {
|
|
214
|
+
refreshed = await revalidate(prepared, authority.signal);
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
if (disposed) {
|
|
218
|
+
throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_DISPOSED', {
|
|
219
|
+
commandId: prepared.commandId,
|
|
220
|
+
cause: error
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
if (authority.signal?.aborted ||
|
|
224
|
+
!stillCurrent(authority)) {
|
|
225
|
+
throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_SCOPE_INVALIDATED', {
|
|
226
|
+
commandId: prepared.commandId,
|
|
227
|
+
cause: error
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
// Keep the accepted layer and let the background
|
|
231
|
+
// status monitor retry the exact generated plan.
|
|
232
|
+
}
|
|
233
|
+
if (disposed) {
|
|
234
|
+
throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_DISPOSED', { commandId: prepared.commandId });
|
|
235
|
+
}
|
|
236
|
+
if (authority.signal?.aborted ||
|
|
237
|
+
!stillCurrent(authority)) {
|
|
238
|
+
throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_SCOPE_INVALIDATED', { commandId: prepared.commandId });
|
|
239
|
+
}
|
|
240
|
+
const controller = tracker.pending;
|
|
241
|
+
if (refreshed) {
|
|
242
|
+
if (controller !== undefined &&
|
|
243
|
+
!controller.settled &&
|
|
244
|
+
pending.get(prepared.commandId) === controller) {
|
|
245
|
+
retireLayerAfterAuthoritativeRevalidation(prepared.commandId);
|
|
246
|
+
settleTrackedProjection(tracker, pending);
|
|
247
|
+
}
|
|
248
|
+
else if (unmanagedLayers.has(prepared.commandId)) {
|
|
249
|
+
/*
|
|
250
|
+
* Recovery receipts and commands without a
|
|
251
|
+
* finite projection awaitable have no
|
|
252
|
+
* PendingProjection controller. The same
|
|
253
|
+
* post-status canonical fetch still owns
|
|
254
|
+
* retirement of their retained overlay.
|
|
255
|
+
*/
|
|
256
|
+
retireLayerAfterAuthoritativeRevalidation(prepared.commandId);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
case 'unknown':
|
|
263
|
+
case 'in_progress':
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
return status;
|
|
267
|
+
};
|
|
268
|
+
return () => {
|
|
269
|
+
if (tracker.inFlight !== undefined)
|
|
270
|
+
return tracker.inFlight;
|
|
271
|
+
let current;
|
|
272
|
+
current = read().finally(() => {
|
|
273
|
+
if (tracker.inFlight === current)
|
|
274
|
+
tracker.inFlight = undefined;
|
|
275
|
+
});
|
|
276
|
+
tracker.inFlight = current;
|
|
277
|
+
return current;
|
|
278
|
+
};
|
|
279
|
+
};
|
|
280
|
+
const invoke = async (artifact, input, callOptions) => {
|
|
281
|
+
const authority = currentAuthority();
|
|
282
|
+
const prepared = prepareReplicaCommandWithTrustedPresets(artifact, input, authority.trustedPresets, callOptions);
|
|
283
|
+
const transportRetries = normalizeRetries(callOptions.transportRetries);
|
|
284
|
+
const semanticChanges = preparedSemanticChanges(prepared);
|
|
285
|
+
const requestSignals = linkAbortSignals([
|
|
286
|
+
authority.signal,
|
|
287
|
+
callOptions.signal,
|
|
288
|
+
runtimeAbort.signal
|
|
289
|
+
]);
|
|
290
|
+
const request = commandTransportRequest(prepared, requestSignals.signal);
|
|
291
|
+
if (request.signal?.aborted) {
|
|
292
|
+
requestSignals.dispose();
|
|
293
|
+
throw new ReplicaCommandRuntimeError(stillCurrent(authority)
|
|
294
|
+
? 'REPLICA_COMMAND_ABORTED'
|
|
295
|
+
: 'REPLICA_COMMAND_SCOPE_INVALIDATED', { commandId: prepared.commandId, cause: request.signal.reason });
|
|
296
|
+
}
|
|
297
|
+
try {
|
|
298
|
+
replica.createOptimisticLayer(prepared.commandId, (writer) => applyOptimisticEffects(writer, prepared.optimistic.operations), semanticChanges);
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
requestSignals.dispose();
|
|
302
|
+
throw error;
|
|
303
|
+
}
|
|
304
|
+
unmanagedLayers.add(prepared.commandId);
|
|
305
|
+
const statusTracker = {
|
|
306
|
+
state: undefined,
|
|
307
|
+
metadata: undefined,
|
|
308
|
+
inFlight: undefined
|
|
309
|
+
};
|
|
310
|
+
const readStatus = statusReader(prepared, authority, statusTracker);
|
|
311
|
+
const recovery = Object.freeze({
|
|
312
|
+
commandId: prepared.commandId,
|
|
313
|
+
status: readStatus
|
|
314
|
+
});
|
|
315
|
+
const recoveryForRetainedLayer = (revalidateOnRollback = false) => {
|
|
316
|
+
if (statusArtifact !== undefined)
|
|
317
|
+
return recovery;
|
|
318
|
+
/*
|
|
319
|
+
* A manual status-less runtime cannot make an ambiguous layer
|
|
320
|
+
* reachable again. Roll it back and let generated revalidation
|
|
321
|
+
* restore canonical truth instead of leaking optimism forever.
|
|
322
|
+
*/
|
|
323
|
+
rejectUnmanagedLayer(prepared.commandId);
|
|
324
|
+
if (revalidateOnRollback) {
|
|
325
|
+
revalidateInBackground(prepared, authority);
|
|
326
|
+
}
|
|
327
|
+
return undefined;
|
|
328
|
+
};
|
|
329
|
+
let result;
|
|
330
|
+
let dispatchAttempted = false;
|
|
331
|
+
try {
|
|
332
|
+
result = await dispatchPrepared(transport, request, transportRetries, () => {
|
|
333
|
+
dispatchAttempted = true;
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
if (disposed) {
|
|
338
|
+
rejectUnmanagedLayer(prepared.commandId);
|
|
339
|
+
throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_DISPOSED', { commandId: prepared.commandId, cause: error });
|
|
340
|
+
}
|
|
341
|
+
if (!dispatchAttempted) {
|
|
342
|
+
rejectUnmanagedLayer(prepared.commandId);
|
|
343
|
+
throw new ReplicaCommandRuntimeError(stillCurrent(authority)
|
|
344
|
+
? 'REPLICA_COMMAND_ABORTED'
|
|
345
|
+
: 'REPLICA_COMMAND_SCOPE_INVALIDATED', { commandId: prepared.commandId, cause: error });
|
|
346
|
+
}
|
|
347
|
+
if (!stillCurrent(authority)) {
|
|
348
|
+
rejectUnmanagedLayer(prepared.commandId);
|
|
349
|
+
throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_SCOPE_INVALIDATED', { commandId: prepared.commandId, cause: error });
|
|
350
|
+
}
|
|
351
|
+
throw new ReplicaCommandRuntimeError(request.signal?.aborted
|
|
352
|
+
? 'REPLICA_COMMAND_ABORTED'
|
|
353
|
+
: 'REPLICA_COMMAND_TRANSPORT_AMBIGUOUS', {
|
|
354
|
+
commandId: prepared.commandId,
|
|
355
|
+
cause: error,
|
|
356
|
+
recovery: recoveryForRetainedLayer(true)
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
finally {
|
|
360
|
+
requestSignals.dispose();
|
|
361
|
+
}
|
|
362
|
+
if (!stillCurrent(authority)) {
|
|
363
|
+
rejectUnmanagedLayer(prepared.commandId);
|
|
364
|
+
throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_SCOPE_INVALIDATED', { commandId: prepared.commandId });
|
|
365
|
+
}
|
|
366
|
+
const rejection = graphqlCommandRejection(result);
|
|
367
|
+
if (rejection !== undefined) {
|
|
368
|
+
try {
|
|
369
|
+
const rejectionEnvelope = requireCommandRejectionEnvelope(result, prepared, authority);
|
|
370
|
+
matchReplicaTrustedPresetInventory(contract.trustedPresets, rejectionEnvelope.trustedPresets);
|
|
371
|
+
}
|
|
372
|
+
catch (error) {
|
|
373
|
+
revalidateInBackground(prepared, authority);
|
|
374
|
+
throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_PROTOCOL_INVALID', {
|
|
375
|
+
commandId: prepared.commandId,
|
|
376
|
+
cause: error,
|
|
377
|
+
recovery: recoveryForRetainedLayer()
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
rejectUnmanagedLayer(prepared.commandId);
|
|
381
|
+
throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_REJECTED', {
|
|
382
|
+
commandId: prepared.commandId,
|
|
383
|
+
cause: new Error(rejection.message)
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
let distributed;
|
|
387
|
+
try {
|
|
388
|
+
distributed = requireCommandEnvelope(result, prepared, authority);
|
|
389
|
+
matchReplicaTrustedPresetInventory(contract.trustedPresets, distributed.trustedPresets);
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
revalidateInBackground(prepared, authority);
|
|
393
|
+
throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_PROTOCOL_INVALID', {
|
|
394
|
+
commandId: prepared.commandId,
|
|
395
|
+
cause: error,
|
|
396
|
+
recovery: recoveryForRetainedLayer()
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
const metadata = distributed.command;
|
|
400
|
+
statusTracker.state = metadata.state;
|
|
401
|
+
statusTracker.metadata = metadata;
|
|
402
|
+
if (metadata.state === 'rejected') {
|
|
403
|
+
rejectUnmanagedLayer(prepared.commandId);
|
|
404
|
+
throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_REJECTED', {
|
|
405
|
+
commandId: prepared.commandId,
|
|
406
|
+
state: metadata.state
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
if (metadata.state === 'expired') {
|
|
410
|
+
rejectUnmanagedLayer(prepared.commandId);
|
|
411
|
+
revalidateInBackground(prepared, authority);
|
|
412
|
+
throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_PROJECTION_FAILED', { commandId: prepared.commandId, state: metadata.state });
|
|
413
|
+
}
|
|
414
|
+
if (metadata.state === 'unknown' || metadata.state === 'in_progress') {
|
|
415
|
+
throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_OUTCOME_PENDING', {
|
|
416
|
+
commandId: prepared.commandId,
|
|
417
|
+
state: metadata.state,
|
|
418
|
+
recovery: recoveryForRetainedLayer(true)
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
if (metadata.state === 'projection_failed') {
|
|
422
|
+
rejectUnmanagedLayer(prepared.commandId);
|
|
423
|
+
revalidateInBackground(prepared, authority);
|
|
424
|
+
throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_PROJECTION_FAILED', { commandId: prepared.commandId, state: metadata.state });
|
|
425
|
+
}
|
|
426
|
+
if ((result.errors?.length ?? 0) > 0) {
|
|
427
|
+
let retainedRecovery;
|
|
428
|
+
if (prepared.confirmations?.kind === 'finite') {
|
|
429
|
+
replica.markOptimisticLayerAccepted(prepared.commandId, metadata);
|
|
430
|
+
retainedRecovery = recoveryForRetainedLayer();
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
rejectUnmanagedLayer(prepared.commandId);
|
|
434
|
+
}
|
|
435
|
+
revalidateInBackground(prepared, authority);
|
|
436
|
+
throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_PROTOCOL_INVALID', { commandId: prepared.commandId, recovery: retainedRecovery });
|
|
437
|
+
}
|
|
438
|
+
let output;
|
|
439
|
+
try {
|
|
440
|
+
output = commandOutput(artifact, result.data, prepared.transport.mutationField);
|
|
441
|
+
}
|
|
442
|
+
catch (error) {
|
|
443
|
+
let retainedRecovery;
|
|
444
|
+
if (prepared.consistency === 'projected' ||
|
|
445
|
+
prepared.confirmations?.kind !== 'finite') {
|
|
446
|
+
rejectUnmanagedLayer(prepared.commandId);
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
replica.markOptimisticLayerAccepted(prepared.commandId, metadata);
|
|
450
|
+
retainedRecovery = recoveryForRetainedLayer();
|
|
451
|
+
}
|
|
452
|
+
revalidateInBackground(prepared, authority);
|
|
453
|
+
throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_PROTOCOL_INVALID', {
|
|
454
|
+
commandId: prepared.commandId,
|
|
455
|
+
cause: error,
|
|
456
|
+
recovery: retainedRecovery
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
let projected;
|
|
460
|
+
let projectionLifecycle;
|
|
461
|
+
if (prepared.consistency === 'projected') {
|
|
462
|
+
if (metadata.state !== 'projected') {
|
|
463
|
+
throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_OUTCOME_PENDING', {
|
|
464
|
+
commandId: prepared.commandId,
|
|
465
|
+
state: metadata.state,
|
|
466
|
+
recovery: recoveryForRetainedLayer(true)
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
try {
|
|
470
|
+
confirmDirectProjection(replica, prepared, output, metadata);
|
|
471
|
+
unmanagedLayers.delete(prepared.commandId);
|
|
472
|
+
}
|
|
473
|
+
catch (error) {
|
|
474
|
+
rejectUnmanagedLayer(prepared.commandId);
|
|
475
|
+
revalidateInBackground(prepared, authority);
|
|
476
|
+
throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_PROTOCOL_INVALID', { commandId: prepared.commandId, cause: error });
|
|
477
|
+
}
|
|
478
|
+
projected = Promise.resolve(Object.freeze({
|
|
479
|
+
commandId: prepared.commandId,
|
|
480
|
+
state: 'projected',
|
|
481
|
+
result: output,
|
|
482
|
+
metadata
|
|
483
|
+
}));
|
|
484
|
+
}
|
|
485
|
+
else {
|
|
486
|
+
const remainsPending = replica.markOptimisticLayerAccepted(prepared.commandId, metadata);
|
|
487
|
+
if (prepared.confirmations?.kind === 'finite') {
|
|
488
|
+
const controller = pendingProjection(authority, metadata, prepared);
|
|
489
|
+
statusTracker.pending = controller;
|
|
490
|
+
if (!remainsPending) {
|
|
491
|
+
unmanagedLayers.delete(prepared.commandId);
|
|
492
|
+
settleProjectionSuccess(controller);
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
/*
|
|
496
|
+
* Receipt/status observations prove durable causality, not
|
|
497
|
+
* that canonical query data was atomically ingested. Keep
|
|
498
|
+
* the layer and await DistributedReplica retirement even
|
|
499
|
+
* when every observation is already present here.
|
|
500
|
+
*/
|
|
501
|
+
pending.set(prepared.commandId, controller);
|
|
502
|
+
unmanagedLayers.delete(prepared.commandId);
|
|
503
|
+
attachAuthorityAbort(controller, () => pending.delete(prepared.commandId));
|
|
504
|
+
if (statusArtifact !== undefined &&
|
|
505
|
+
replicaRevalidate !== undefined) {
|
|
506
|
+
monitorPendingProjection(controller, readStatus, () => pending.get(prepared.commandId) === controller, options.onBackgroundError);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
projectionLifecycle =
|
|
510
|
+
controller.promise;
|
|
511
|
+
projected = callerProjectedPromise(controller, callOptions.signal);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
if (prepared.revalidation.required) {
|
|
515
|
+
revalidateAndConfirmUnmanagedInBackground(prepared, authority);
|
|
516
|
+
}
|
|
517
|
+
const receiptValue = {
|
|
518
|
+
commandId: prepared.commandId,
|
|
519
|
+
state: metadata.state,
|
|
520
|
+
result: output,
|
|
521
|
+
metadata,
|
|
522
|
+
status: readStatus,
|
|
523
|
+
...(projected === undefined ? {} : { projected })
|
|
524
|
+
};
|
|
525
|
+
if (projectionLifecycle !== undefined) {
|
|
526
|
+
Object.defineProperty(receiptValue, replicaCommandProjectedLifecycle, {
|
|
527
|
+
value: projectionLifecycle,
|
|
528
|
+
enumerable: false,
|
|
529
|
+
configurable: false,
|
|
530
|
+
writable: false
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
const receipt = Object.freeze(receiptValue);
|
|
534
|
+
try {
|
|
535
|
+
await callOptions.onAccepted?.(receipt);
|
|
536
|
+
}
|
|
537
|
+
catch (error) {
|
|
538
|
+
options.onBackgroundError?.(error);
|
|
539
|
+
}
|
|
540
|
+
return receipt;
|
|
541
|
+
};
|
|
542
|
+
const commands = Object.create(null);
|
|
543
|
+
for (const { key, artifact } of inventory) {
|
|
544
|
+
defineBoundCommand(commands, key, artifact.input.kind === 'none'
|
|
545
|
+
? (callOptions = {}) => invoke(artifact, undefined, callOptions)
|
|
546
|
+
: (input, callOptions = {}) => invoke(artifact, input, callOptions));
|
|
547
|
+
}
|
|
548
|
+
freezeCommandTree(commands);
|
|
549
|
+
const observeResult = (envelope) => {
|
|
550
|
+
if (disposed || pending.size === 0)
|
|
551
|
+
return;
|
|
552
|
+
let distributed;
|
|
553
|
+
try {
|
|
554
|
+
distributed = parseGraphqlResponseExtensions(envelope.extensions)
|
|
555
|
+
?.distributed;
|
|
556
|
+
}
|
|
557
|
+
catch (error) {
|
|
558
|
+
options.onBackgroundError?.(error);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
if (distributed === undefined)
|
|
562
|
+
return;
|
|
563
|
+
for (const [commandId, controller] of pending) {
|
|
564
|
+
if (!stillCurrent(controller.authority)) {
|
|
565
|
+
settleProjectionFailure(controller, new ReplicaCommandRuntimeError('REPLICA_COMMAND_SCOPE_INVALIDATED', { commandId }));
|
|
566
|
+
pending.delete(commandId);
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
if (distributed.schemaHash !== controller.authority.scope.schemaHash ||
|
|
570
|
+
distributed.cacheScope !== controller.authority.scope.cacheScope) {
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
const command = distributed.command;
|
|
574
|
+
if (command?.commandId === commandId) {
|
|
575
|
+
try {
|
|
576
|
+
verifyReplicaCommandReceipt(controller.prepared, command);
|
|
577
|
+
}
|
|
578
|
+
catch (error) {
|
|
579
|
+
revalidateInBackground(controller.prepared, controller.authority);
|
|
580
|
+
settleProjectionFailure(controller, new ReplicaCommandRuntimeError('REPLICA_COMMAND_PROTOCOL_INVALID', { commandId, cause: error }));
|
|
581
|
+
pending.delete(commandId);
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
if (command.causationId !== controller.causationId) {
|
|
585
|
+
settleProjectionFailure(controller, new ReplicaCommandRuntimeError('REPLICA_COMMAND_PROTOCOL_INVALID', { commandId }));
|
|
586
|
+
pending.delete(commandId);
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
controller.metadata = command;
|
|
590
|
+
if (command.state === 'projection_failed') {
|
|
591
|
+
replica.rejectOptimisticLayer(commandId);
|
|
592
|
+
revalidateInBackground(controller.prepared, controller.authority);
|
|
593
|
+
settleProjectionFailure(controller, new ReplicaCommandRuntimeError('REPLICA_COMMAND_PROJECTION_FAILED', { commandId, state: command.state }));
|
|
594
|
+
pending.delete(commandId);
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
if (command.state === 'rejected' ||
|
|
598
|
+
command.state === 'expired') {
|
|
599
|
+
replica.rejectOptimisticLayer(commandId);
|
|
600
|
+
revalidateInBackground(controller.prepared, controller.authority);
|
|
601
|
+
settleProjectionFailure(controller, new ReplicaCommandRuntimeError(command.state === 'rejected'
|
|
602
|
+
? 'REPLICA_COMMAND_REJECTED'
|
|
603
|
+
: 'REPLICA_COMMAND_PROJECTION_FAILED', { commandId, state: command.state }));
|
|
604
|
+
pending.delete(commandId);
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
/*
|
|
609
|
+
* DistributedReplica is the authority on whether this frame's
|
|
610
|
+
* snapshot/observations were admissible. This callback runs only
|
|
611
|
+
* after that exact frame committed.
|
|
612
|
+
*/
|
|
613
|
+
const remainsPending = replica.markOptimisticLayerAccepted(commandId);
|
|
614
|
+
if (!remainsPending) {
|
|
615
|
+
settleProjectionSuccess(controller);
|
|
616
|
+
pending.delete(commandId);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
const observationRegistration = replica[replicaResultObservation]?.(observeResult);
|
|
621
|
+
return Object.freeze({
|
|
622
|
+
commands: commands,
|
|
623
|
+
observeResult,
|
|
624
|
+
dispose() {
|
|
625
|
+
if (disposed)
|
|
626
|
+
return;
|
|
627
|
+
disposed = true;
|
|
628
|
+
runtimeAbort.abort(new ReplicaCommandRuntimeError('REPLICA_COMMAND_DISPOSED'));
|
|
629
|
+
observationRegistration?.dispose();
|
|
630
|
+
registration?.dispose();
|
|
631
|
+
for (const commandId of unmanagedLayers) {
|
|
632
|
+
replica.rejectOptimisticLayer(commandId);
|
|
633
|
+
}
|
|
634
|
+
unmanagedLayers.clear();
|
|
635
|
+
for (const [commandId, controller] of pending) {
|
|
636
|
+
replica.rejectOptimisticLayer(commandId);
|
|
637
|
+
settleProjectionFailure(controller, new ReplicaCommandRuntimeError('REPLICA_COMMAND_DISPOSED', {
|
|
638
|
+
commandId
|
|
639
|
+
}));
|
|
640
|
+
}
|
|
641
|
+
pending.clear();
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { DistributedCommandState } from '../../protocol.js';
|
|
2
|
+
import type { ReplicaCommandRecoveryReceipt, ReplicaCommandRuntimeErrorCode } from './types.js';
|
|
3
|
+
export declare function commandRuntimeErrorMessage(code: ReplicaCommandRuntimeErrorCode): string;
|
|
4
|
+
/** Redacted typed failure from the integrated generated-command lifecycle. */
|
|
5
|
+
export declare class ReplicaCommandRuntimeError extends Error {
|
|
6
|
+
readonly code: ReplicaCommandRuntimeErrorCode;
|
|
7
|
+
readonly commandId?: string;
|
|
8
|
+
readonly state?: DistributedCommandState;
|
|
9
|
+
readonly recovery?: ReplicaCommandRecoveryReceipt;
|
|
10
|
+
constructor(code: ReplicaCommandRuntimeErrorCode, options?: Readonly<{
|
|
11
|
+
commandId?: string;
|
|
12
|
+
state?: DistributedCommandState;
|
|
13
|
+
cause?: unknown;
|
|
14
|
+
recovery?: ReplicaCommandRecoveryReceipt;
|
|
15
|
+
}>);
|
|
16
|
+
}
|