@hops-ops/distributed 0.0.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -13
- package/dist/auth-headers.js +3 -3
- package/dist/internal/cache-engine/engine.d.ts +3 -1
- package/dist/internal/cache-engine/engine.js +137 -22
- package/dist/internal/cache-engine/errors.d.ts +4 -0
- package/dist/internal/cache-engine/errors.js +8 -0
- package/dist/internal/cache-engine/index.d.ts +2 -2
- package/dist/internal/cache-engine/index.js +1 -1
- package/dist/internal/cache-engine/types.d.ts +16 -0
- package/dist/internal/cache-engine.d.ts +2 -2
- package/dist/internal/cache-engine.js +1 -1
- package/dist/protocol.d.ts +9 -2
- package/dist/protocol.js +35 -7
- package/dist/replica/command-runtime/create.js +499 -57
- package/dist/replica/command-runtime/index.d.ts +1 -1
- package/dist/replica/command-runtime/index.js +1 -1
- package/dist/replica/command-runtime/lib/effects.d.ts +22 -9
- package/dist/replica/command-runtime/lib/effects.js +118 -20
- package/dist/replica/command-runtime/lib/projection.d.ts +2 -2
- package/dist/replica/command-runtime/lib/projection.js +14 -6
- package/dist/replica/command-runtime/lib/status.js +18 -10
- package/dist/replica/command-runtime/lib/transport.js +4 -0
- package/dist/replica/command-runtime/lib/util.js +8 -3
- package/dist/replica/command-runtime/symbols.d.ts +9 -0
- package/dist/replica/command-runtime/symbols.js +9 -0
- package/dist/replica/command-runtime/types.d.ts +15 -6
- package/dist/replica/command-runtime/types.js +1 -1
- package/dist/replica/command-runtime.d.ts +1 -1
- package/dist/replica/command-runtime.js +1 -1
- package/dist/replica/commands/clone.js +2 -1
- package/dist/replica/commands/index.d.ts +1 -0
- package/dist/replica/commands/prepare.js +7 -4
- package/dist/replica/commands/receipt.js +30 -16
- package/dist/replica/commands/types.d.ts +10 -5
- package/dist/replica/commands/validate.d.ts +5 -1
- package/dist/replica/commands/validate.js +129 -62
- package/dist/replica/diagnostics/inspect.js +51 -19
- package/dist/replica/diagnostics/types.d.ts +7 -7
- package/dist/replica/distributed-replica/helpers.js +22 -8
- package/dist/replica/distributed-replica/hydration.js +14 -2
- package/dist/replica/distributed-replica/impl-diagnostics.d.ts +1 -1
- package/dist/replica/distributed-replica/impl-diagnostics.js +4 -3
- package/dist/replica/distributed-replica/impl-hydration-orchestrate.d.ts +1 -1
- package/dist/replica/distributed-replica/impl-hydration-orchestrate.js +76 -42
- package/dist/replica/distributed-replica/impl-optimistic.d.ts +11 -2
- package/dist/replica/distributed-replica/impl-optimistic.js +22 -6
- package/dist/replica/distributed-replica/impl-protocol.js +4 -2
- package/dist/replica/distributed-replica/impl.d.ts +8 -2
- package/dist/replica/distributed-replica/impl.js +22 -5
- package/dist/replica/distributed-replica/optimistic.js +31 -3
- package/dist/replica/distributed-replica/types.d.ts +1 -0
- package/dist/replica/index-maintenance/engine.js +14 -6
- package/dist/replica/index.d.ts +4 -0
- package/dist/replica/index.js +2 -0
- package/dist/replica/materialize.js +25 -0
- package/dist/replica/mutation-cache.d.ts +62 -0
- package/dist/replica/mutation-cache.js +91 -0
- package/dist/replica/operation-binding.js +17 -6
- package/dist/replica/persistence/state.js +9 -1
- package/dist/replica/projection-delta/canonical.d.ts +6 -0
- package/dist/replica/projection-delta/canonical.js +29 -0
- package/dist/replica/projection-delta/index.d.ts +5 -0
- package/dist/replica/projection-delta/index.js +3 -0
- package/dist/replica/projection-delta/resolve.d.ts +8 -0
- package/dist/replica/projection-delta/resolve.js +266 -0
- package/dist/replica/projection-delta/types.d.ts +415 -0
- package/dist/replica/projection-delta/types.js +6 -0
- package/dist/replica/projection-delta/validate.d.ts +18 -0
- package/dist/replica/projection-delta/validate.js +1718 -0
- package/dist/replica/projection-delta/wasm-pure.d.ts +36 -0
- package/dist/replica/projection-delta/wasm-pure.js +71 -0
- package/dist/replica/query-plan/pagination.js +18 -8
- package/dist/replica/types.d.ts +13 -2
- package/dist/sveltekit/replica.d.ts +1 -1
- package/dist/sveltekit/replica.js +3 -0
- package/dist/sveltekit/vite.d.ts +6 -6
- package/dist/sveltekit/vite.js +6 -6
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { replicaCommandAuthority, replicaCommandDirectProjection, replicaCommandProjectedLifecycle, replicaResultObservation } from './symbols.js';
|
|
1
|
+
export { replicaCommandAuthority, replicaCommandDirectProjection, replicaCommandProjectionDelta, replicaCommandProjectedLifecycle, replicaCommandReadRecord, replicaResultObservation } from './symbols.js';
|
|
2
2
|
export type { ReplicaBoundCommand, ReplicaBoundCommands, ReplicaCommandAuthorityHost, ReplicaCommandAuthorityRegistration, ReplicaCommandAuthoritySnapshot, ReplicaCommandCallOptions, ReplicaCommandDirectProjection, ReplicaCommandProjectedOutcome, ReplicaCommandReceipt, ReplicaCommandRecoveryReceipt, ReplicaCommandRuntime, ReplicaCommandRuntimeErrorCode, ReplicaCommandRuntimeOptions, ReplicaCommandStatus, ReplicaCommandStatusArtifact, ReplicaCommandStatusRequest, ReplicaCommandSurfaceContract, ReplicaCommandTransport, ReplicaCommandTransportRequest, ReplicaCommandTransportResult, ReplicaResultObservationRegistration } from './types.js';
|
|
3
3
|
export { ReplicaCommandRuntimeError } from './errors.js';
|
|
4
4
|
export { replicaCommandProjectedLifecycleOf } from './lifecycle.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { replicaCommandAuthority, replicaCommandDirectProjection, replicaCommandProjectedLifecycle, replicaResultObservation } from './symbols.js';
|
|
1
|
+
export { replicaCommandAuthority, replicaCommandDirectProjection, replicaCommandProjectionDelta, replicaCommandProjectedLifecycle, replicaCommandReadRecord, replicaResultObservation } from './symbols.js';
|
|
2
2
|
export { ReplicaCommandRuntimeError } from './errors.js';
|
|
3
3
|
export { replicaCommandProjectedLifecycleOf } from './lifecycle.js';
|
|
4
4
|
export { createReplicaCommandRuntime } from './create.js';
|
|
@@ -1,11 +1,24 @@
|
|
|
1
|
-
import { type ReplicaPreparedCommand
|
|
1
|
+
import { type ReplicaPreparedCommand } from '../../commands.js';
|
|
2
|
+
import type { PreparedProjectionOperation, PreparedProjectionScope, ReplicaPureFunction } from '../../projection-delta/index.js';
|
|
2
3
|
import type { ReplicaIndexSemanticChange } from '../../index-maintenance.js';
|
|
3
|
-
import type { ReplicaModelArtifact, ReplicaOptimisticWriter, ReplicaValue } from '../../types.js';
|
|
4
|
-
export
|
|
4
|
+
import type { ReplicaIdentity, ReplicaModelArtifact, ReplicaOptimisticWriter, ReplicaValue } from '../../types.js';
|
|
5
|
+
export type PureReduceHost = Readonly<{
|
|
6
|
+
/** Read live cache fields for a known record (pre-layer). Fail-closed on miss. */
|
|
7
|
+
readRecord?: (model: ReplicaModelArtifact, identity: ReplicaIdentity) => Readonly<Record<string, ReplicaValue>> | undefined;
|
|
8
|
+
pureFunctions?: Readonly<Record<string, ReplicaPureFunction>>;
|
|
9
|
+
}>;
|
|
10
|
+
/**
|
|
11
|
+
* Expand pure reducers against known cache rows, then apply ordinary ops.
|
|
12
|
+
* Missing row / unknown pure / pure null → skip that reduce (no invent).
|
|
13
|
+
*/
|
|
14
|
+
export declare function applyOptimisticEffects(writer: ReplicaOptimisticWriter, effects: readonly PreparedProjectionOperation[], host?: PureReduceHost): void;
|
|
5
15
|
export declare function preparedSemanticChanges<TInput, TOutput>(prepared: ReplicaPreparedCommand<TInput, TOutput>): readonly ReplicaIndexSemanticChange[];
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Exact record identities whose aggregate-facing command dispatches must retain
|
|
18
|
+
* invocation order. Optimistic layers still apply immediately; only transport
|
|
19
|
+
* waits for an earlier command touching the same modeled record to settle.
|
|
20
|
+
*/
|
|
21
|
+
export declare function preparedDispatchKeys<TInput, TOutput>(prepared: ReplicaPreparedCommand<TInput, TOutput>): readonly string[];
|
|
22
|
+
export declare function modelFromKey(scope: PreparedProjectionScope): ReplicaModelArtifact;
|
|
23
|
+
export declare function identityFromKey(key: PreparedProjectionScope): readonly ReplicaValue[];
|
|
24
|
+
export declare function fieldsFromEffect(scope: PreparedProjectionScope, fields: Readonly<Record<string, ReplicaValue>>): Readonly<Record<string, ReplicaValue>>;
|
|
@@ -1,29 +1,90 @@
|
|
|
1
1
|
import {} from '../../commands.js';
|
|
2
2
|
import { replicaRecordKey } from '../../identity.js';
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Expand pure reducers against known cache rows, then apply ordinary ops.
|
|
5
|
+
* Missing row / unknown pure / pure null → skip that reduce (no invent).
|
|
6
|
+
*/
|
|
7
|
+
export function applyOptimisticEffects(writer, effects, host = {}) {
|
|
8
|
+
const expanded = expandReduceKnownRecord(effects, host);
|
|
9
|
+
for (const effect of expanded) {
|
|
5
10
|
switch (effect.kind) {
|
|
6
11
|
case 'upsert':
|
|
7
12
|
case 'patch': {
|
|
8
|
-
const model = modelFromKey(effect.
|
|
9
|
-
|
|
10
|
-
|
|
13
|
+
const model = modelFromKey(effect.scope);
|
|
14
|
+
const fields = fieldsFromEffect(effect.scope, effect.fields);
|
|
15
|
+
const unset = effect.kind === 'upsert'
|
|
16
|
+
? effect.replace.filter((field) => !Object.hasOwn(effect.fields, field))
|
|
17
|
+
: effect.unset;
|
|
18
|
+
writer.writeRecord(model, identityFromKey(effect.scope), {
|
|
19
|
+
fields,
|
|
20
|
+
...(unset.length === 0 ? {} : { unset }),
|
|
21
|
+
...(effect.kind === 'patch' ? { ifPresent: true } : {})
|
|
11
22
|
});
|
|
12
23
|
break;
|
|
13
24
|
}
|
|
14
25
|
case 'delete':
|
|
15
|
-
writer.tombstoneRecord(modelFromKey(effect.
|
|
26
|
+
writer.tombstoneRecord(modelFromKey(effect.scope), identityFromKey(effect.scope));
|
|
16
27
|
break;
|
|
17
28
|
case 'link':
|
|
18
29
|
case 'unlink':
|
|
19
30
|
case 'invalidate_model':
|
|
20
31
|
case 'invalidate_relationship':
|
|
32
|
+
case 'reduce_known_record':
|
|
21
33
|
// Task 8 consumes the exact semantic context. Guessing a to-one
|
|
22
34
|
// record link for a to-many relationship would corrupt truth.
|
|
35
|
+
// reduce_known_record is expanded above.
|
|
23
36
|
break;
|
|
24
37
|
}
|
|
25
38
|
}
|
|
26
39
|
}
|
|
40
|
+
function expandReduceKnownRecord(effects, host) {
|
|
41
|
+
const out = [];
|
|
42
|
+
for (const effect of effects) {
|
|
43
|
+
if (effect.kind !== 'reduce_known_record') {
|
|
44
|
+
out.push(effect);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const pure = host.pureFunctions?.[effect.fn];
|
|
48
|
+
const read = host.readRecord;
|
|
49
|
+
if (pure === undefined || read === undefined) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const model = modelFromKey(effect.scope);
|
|
53
|
+
const identity = identityFromKey(effect.scope);
|
|
54
|
+
const current = read(model, identity);
|
|
55
|
+
if (current === undefined) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
let next;
|
|
59
|
+
try {
|
|
60
|
+
next = pure(current, effect.args);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (next === null) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const fields = Object.create(null);
|
|
69
|
+
for (const field of effect.assign) {
|
|
70
|
+
if (!Object.prototype.hasOwnProperty.call(next, field)) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
fields[field] = next[field];
|
|
74
|
+
}
|
|
75
|
+
if (Object.keys(fields).length === 0) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
out.push(Object.freeze({
|
|
79
|
+
kind: 'patch',
|
|
80
|
+
scope: effect.scope,
|
|
81
|
+
fields: Object.freeze(fields),
|
|
82
|
+
unset: Object.freeze([]),
|
|
83
|
+
ifPresent: true
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
return Object.freeze(out);
|
|
87
|
+
}
|
|
27
88
|
export function preparedSemanticChanges(prepared) {
|
|
28
89
|
const dependencies = Object.freeze([...prepared.revalidation.dependencies]);
|
|
29
90
|
const changes = [];
|
|
@@ -32,19 +93,20 @@ export function preparedSemanticChanges(prepared) {
|
|
|
32
93
|
case 'upsert':
|
|
33
94
|
case 'patch':
|
|
34
95
|
case 'delete':
|
|
96
|
+
case 'reduce_known_record':
|
|
35
97
|
// DistributedReplica captures ordinary writer mutations into the
|
|
36
98
|
// same layer context. Supplying them again would double-apply the
|
|
37
|
-
// semantic record operation.
|
|
99
|
+
// semantic record operation. Pure reduce expands to patch first.
|
|
38
100
|
break;
|
|
39
101
|
case 'link':
|
|
40
102
|
case 'unlink': {
|
|
41
|
-
const source = modelFromKey(effect.
|
|
42
|
-
const target = modelFromKey(effect.
|
|
103
|
+
const source = modelFromKey(effect.source);
|
|
104
|
+
const target = modelFromKey(effect.target);
|
|
43
105
|
changes.push(Object.freeze({
|
|
44
106
|
kind: effect.kind,
|
|
45
|
-
sourceModel: effect.
|
|
46
|
-
field: effect.relationship
|
|
47
|
-
targetModel: effect.
|
|
107
|
+
sourceModel: effect.source.model,
|
|
108
|
+
field: effect.relationship,
|
|
109
|
+
targetModel: effect.target.model,
|
|
48
110
|
sourceKey: replicaRecordKey(source, identityFromKey(effect.source)),
|
|
49
111
|
targetKey: replicaRecordKey(target, identityFromKey(effect.target)),
|
|
50
112
|
dependencies
|
|
@@ -62,21 +124,57 @@ export function preparedSemanticChanges(prepared) {
|
|
|
62
124
|
}
|
|
63
125
|
return Object.freeze(changes);
|
|
64
126
|
}
|
|
65
|
-
|
|
127
|
+
/**
|
|
128
|
+
* Exact record identities whose aggregate-facing command dispatches must retain
|
|
129
|
+
* invocation order. Optimistic layers still apply immediately; only transport
|
|
130
|
+
* waits for an earlier command touching the same modeled record to settle.
|
|
131
|
+
*/
|
|
132
|
+
export function preparedDispatchKeys(prepared) {
|
|
133
|
+
const keys = new Set();
|
|
134
|
+
const addScope = (scope) => {
|
|
135
|
+
keys.add(replicaRecordKey(modelFromKey(scope), identityFromKey(scope)));
|
|
136
|
+
};
|
|
137
|
+
for (const effect of prepared.optimistic.operations) {
|
|
138
|
+
switch (effect.kind) {
|
|
139
|
+
case 'upsert':
|
|
140
|
+
case 'patch':
|
|
141
|
+
case 'delete':
|
|
142
|
+
case 'reduce_known_record':
|
|
143
|
+
addScope(effect.scope);
|
|
144
|
+
break;
|
|
145
|
+
case 'link':
|
|
146
|
+
case 'unlink':
|
|
147
|
+
addScope(effect.source);
|
|
148
|
+
addScope(effect.target);
|
|
149
|
+
break;
|
|
150
|
+
case 'invalidate_relationship':
|
|
151
|
+
addScope(effect.source);
|
|
152
|
+
break;
|
|
153
|
+
case 'invalidate_model':
|
|
154
|
+
/*
|
|
155
|
+
* A model invalidation has no compiler-proven aggregate
|
|
156
|
+
* identity. It cannot safely participate in a record queue.
|
|
157
|
+
*/
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return Object.freeze([...keys].sort());
|
|
162
|
+
}
|
|
163
|
+
export function modelFromKey(scope) {
|
|
66
164
|
return Object.freeze({
|
|
67
|
-
id: model,
|
|
68
|
-
identityFields: Object.freeze(key.
|
|
165
|
+
id: scope.model,
|
|
166
|
+
identityFields: Object.freeze(scope.key.map(({ field }) => field))
|
|
69
167
|
});
|
|
70
168
|
}
|
|
71
169
|
export function identityFromKey(key) {
|
|
72
|
-
return Object.freeze(key.
|
|
170
|
+
return Object.freeze(key.key.map(({ value }) => value));
|
|
73
171
|
}
|
|
74
|
-
export function fieldsFromEffect(
|
|
172
|
+
export function fieldsFromEffect(scope, fields) {
|
|
75
173
|
const result = Object.create(null);
|
|
76
174
|
for (const field of [
|
|
77
|
-
{ field: '__typename', value: model },
|
|
78
|
-
...key
|
|
79
|
-
...fields
|
|
175
|
+
{ field: '__typename', value: scope.model },
|
|
176
|
+
...scope.key,
|
|
177
|
+
...Object.entries(fields).map(([field, value]) => ({ field, value }))
|
|
80
178
|
]) {
|
|
81
179
|
Object.defineProperty(result, field.field, {
|
|
82
180
|
enumerable: true,
|
|
@@ -2,9 +2,9 @@ import { type DistributedCommandMetadata } from '../../../protocol.js';
|
|
|
2
2
|
import { type ReplicaPreparedCommand } from '../../commands.js';
|
|
3
3
|
import type { CapturedAuthority, CommandStatusTracker, PendingProjection, ReplicaCommandAuthorityHost, ReplicaCommandProjectedOutcome, ReplicaCommandStatus } from '../types.js';
|
|
4
4
|
export declare function confirmDirectProjection<TInput, TOutput>(replica: ReplicaCommandAuthorityHost, prepared: ReplicaPreparedCommand<TInput, TOutput>, output: TOutput, metadata: DistributedCommandMetadata): void;
|
|
5
|
-
export declare function pendingProjection(authority: CapturedAuthority, metadata: DistributedCommandMetadata, prepared: ReplicaPreparedCommand<unknown, unknown
|
|
5
|
+
export declare function pendingProjection(authority: CapturedAuthority, metadata: DistributedCommandMetadata, prepared: ReplicaPreparedCommand<unknown, unknown>, tracker: CommandStatusTracker): PendingProjection;
|
|
6
6
|
/**
|
|
7
|
-
* Generated
|
|
7
|
+
* Generated causal commands converge without application polling. Durable status
|
|
8
8
|
* is the only completion signal; timers merely schedule reads and never infer a
|
|
9
9
|
* successful outcome.
|
|
10
10
|
*/
|
|
@@ -31,7 +31,14 @@ export function confirmDirectProjection(replica, prepared, output, metadata) {
|
|
|
31
31
|
id: direct.model,
|
|
32
32
|
identityFields: direct.identityFields
|
|
33
33
|
});
|
|
34
|
-
const
|
|
34
|
+
const outputFields = cloneOutputJson(output, 'projected.output', new Set(), 0);
|
|
35
|
+
const fields = Object.freeze({
|
|
36
|
+
...outputFields,
|
|
37
|
+
// Query normalization always supplies the generated model identity. Atomic
|
|
38
|
+
// command rows must be canonicalized the same way or a new record remains
|
|
39
|
+
// incomplete until a later query/reload contributes `__typename`.
|
|
40
|
+
__typename: direct.model
|
|
41
|
+
});
|
|
35
42
|
const confirmProtocolProjection = replica[replicaCommandDirectProjection];
|
|
36
43
|
if (confirmProtocolProjection !== undefined) {
|
|
37
44
|
confirmProtocolProjection.call(replica, prepared.commandId, {
|
|
@@ -47,7 +54,7 @@ export function confirmDirectProjection(replica, prepared, output, metadata) {
|
|
|
47
54
|
fields
|
|
48
55
|
}));
|
|
49
56
|
}
|
|
50
|
-
export function pendingProjection(authority, metadata, prepared) {
|
|
57
|
+
export function pendingProjection(authority, metadata, prepared, tracker) {
|
|
51
58
|
let resolve;
|
|
52
59
|
let reject;
|
|
53
60
|
const promise = new Promise((resolvePromise, rejectPromise) => {
|
|
@@ -56,7 +63,7 @@ export function pendingProjection(authority, metadata, prepared) {
|
|
|
56
63
|
});
|
|
57
64
|
/*
|
|
58
65
|
* Authority loss or a terminal status can arrive before application code
|
|
59
|
-
* receives the
|
|
66
|
+
* receives the succeeded receipt. Mark the internal lifecycle promise handled
|
|
60
67
|
* eagerly while preserving its rejection for every explicit awaiter.
|
|
61
68
|
*/
|
|
62
69
|
void promise.catch(() => undefined);
|
|
@@ -64,6 +71,7 @@ export function pendingProjection(authority, metadata, prepared) {
|
|
|
64
71
|
commandId: metadata.commandId,
|
|
65
72
|
causationId: metadata.causationId,
|
|
66
73
|
authority,
|
|
74
|
+
tracker,
|
|
67
75
|
resolve,
|
|
68
76
|
reject,
|
|
69
77
|
promise,
|
|
@@ -74,7 +82,7 @@ export function pendingProjection(authority, metadata, prepared) {
|
|
|
74
82
|
return controller;
|
|
75
83
|
}
|
|
76
84
|
/**
|
|
77
|
-
* Generated
|
|
85
|
+
* Generated causal commands converge without application polling. Durable status
|
|
78
86
|
* is the only completion signal; timers merely schedule reads and never infer a
|
|
79
87
|
* successful outcome.
|
|
80
88
|
*/
|
|
@@ -176,7 +184,7 @@ export function settleProjectionSuccess(controller) {
|
|
|
176
184
|
controller.stopMonitor?.();
|
|
177
185
|
controller.resolve(Object.freeze({
|
|
178
186
|
commandId: controller.commandId,
|
|
179
|
-
state: '
|
|
187
|
+
state: 'atomic',
|
|
180
188
|
metadata: controller.metadata
|
|
181
189
|
}));
|
|
182
190
|
}
|
|
@@ -211,7 +219,7 @@ export function callerProjectedPromise(controller, signal) {
|
|
|
211
219
|
onAbort();
|
|
212
220
|
});
|
|
213
221
|
/*
|
|
214
|
-
* An AbortSignal can fire during an async `
|
|
222
|
+
* An AbortSignal can fire during an async `onSucceeded` callback, before the
|
|
215
223
|
* receipt reaches caller code. Keep that legitimate rejection observable
|
|
216
224
|
* without creating a process-level unhandled-rejection race.
|
|
217
225
|
*/
|
|
@@ -15,6 +15,8 @@ export function requireStatusEnvelope(result, artifact, prepared, authority, con
|
|
|
15
15
|
distributed.operation !== artifact.operationHash ||
|
|
16
16
|
distributed.protocolVersion !== authority.scope.protocolVersion ||
|
|
17
17
|
distributed.schemaHash !== authority.scope.schemaHash ||
|
|
18
|
+
distributed.authorizationGeneration !==
|
|
19
|
+
authority.scope.authorizationGeneration ||
|
|
18
20
|
distributed.cacheScope !== authority.scope.cacheScope ||
|
|
19
21
|
distributed.snapshot !== undefined ||
|
|
20
22
|
distributed.live !== undefined) {
|
|
@@ -57,9 +59,9 @@ export function commandStatusOutput(data) {
|
|
|
57
59
|
}
|
|
58
60
|
switch (value.state) {
|
|
59
61
|
case 'in_progress':
|
|
60
|
-
case '
|
|
61
|
-
case '
|
|
62
|
-
case '
|
|
62
|
+
case 'succeeded':
|
|
63
|
+
case 'succeeded_pending_projection':
|
|
64
|
+
case 'atomic':
|
|
63
65
|
case 'rejected':
|
|
64
66
|
case 'projection_failed':
|
|
65
67
|
case 'expired':
|
|
@@ -92,6 +94,12 @@ export function validateStatusProgression(previousState, previous, current) {
|
|
|
92
94
|
next.consistency !== previous.consistency) {
|
|
93
95
|
throw new Error('command status changed causal identity');
|
|
94
96
|
}
|
|
97
|
+
if (previous.projectionDisposition === 'revalidate' &&
|
|
98
|
+
next.projectionDisposition !== 'revalidate') {
|
|
99
|
+
throw new Error('command status lost projection disposition');
|
|
100
|
+
}
|
|
101
|
+
if (next.projectionDisposition === 'revalidate')
|
|
102
|
+
return;
|
|
95
103
|
if (!(previous.state === 'in_progress' &&
|
|
96
104
|
previous.expects.length === 0) &&
|
|
97
105
|
!sameStringMultiset(previous.expects.map(projectionExpectationFingerprint), next.expects.map(projectionExpectationFingerprint))) {
|
|
@@ -108,15 +116,15 @@ export function isStatusTransition(previous, next) {
|
|
|
108
116
|
return true;
|
|
109
117
|
case 'in_progress':
|
|
110
118
|
return next !== 'unknown';
|
|
111
|
-
case '
|
|
112
|
-
return next === '
|
|
113
|
-
case '
|
|
114
|
-
return (next === '
|
|
115
|
-
next === '
|
|
119
|
+
case 'succeeded':
|
|
120
|
+
return next === 'succeeded' || next === 'expired';
|
|
121
|
+
case 'succeeded_pending_projection':
|
|
122
|
+
return (next === 'succeeded_pending_projection' ||
|
|
123
|
+
next === 'atomic' ||
|
|
116
124
|
next === 'projection_failed' ||
|
|
117
125
|
next === 'expired');
|
|
118
|
-
case '
|
|
119
|
-
return next === '
|
|
126
|
+
case 'atomic':
|
|
127
|
+
return next === 'atomic' || next === 'expired';
|
|
120
128
|
case 'rejected':
|
|
121
129
|
return next === 'rejected' || next === 'expired';
|
|
122
130
|
case 'projection_failed':
|
|
@@ -70,6 +70,8 @@ export function requireCommandEnvelope(result, prepared, authority) {
|
|
|
70
70
|
distributed.operation !== prepared.transport.operationHash ||
|
|
71
71
|
distributed.protocolVersion !== authority.scope.protocolVersion ||
|
|
72
72
|
distributed.schemaHash !== authority.scope.schemaHash ||
|
|
73
|
+
distributed.authorizationGeneration !==
|
|
74
|
+
authority.scope.authorizationGeneration ||
|
|
73
75
|
distributed.cacheScope !== authority.scope.cacheScope) {
|
|
74
76
|
throw new Error('command response does not match its generated scope');
|
|
75
77
|
}
|
|
@@ -94,6 +96,8 @@ export function requireCommandRejectionEnvelope(result, prepared, authority) {
|
|
|
94
96
|
distributed.operation !== prepared.transport.operationHash ||
|
|
95
97
|
distributed.protocolVersion !== authority.scope.protocolVersion ||
|
|
96
98
|
distributed.schemaHash !== authority.scope.schemaHash ||
|
|
99
|
+
distributed.authorizationGeneration !==
|
|
100
|
+
authority.scope.authorizationGeneration ||
|
|
97
101
|
distributed.cacheScope !== authority.scope.cacheScope) {
|
|
98
102
|
throw new Error('command rejection does not match its generated scope');
|
|
99
103
|
}
|
|
@@ -14,12 +14,14 @@ export function cloneScope(scope) {
|
|
|
14
14
|
return Object.freeze({
|
|
15
15
|
protocolVersion: 1,
|
|
16
16
|
schemaHash: scope.schemaHash,
|
|
17
|
+
authorizationGeneration: scope.authorizationGeneration,
|
|
17
18
|
cacheScope: scope.cacheScope
|
|
18
19
|
});
|
|
19
20
|
}
|
|
20
21
|
export function sameScope(left, right) {
|
|
21
22
|
return (left.protocolVersion === right.protocolVersion &&
|
|
22
23
|
left.schemaHash === right.schemaHash &&
|
|
24
|
+
left.authorizationGeneration === right.authorizationGeneration &&
|
|
23
25
|
left.cacheScope === right.cacheScope);
|
|
24
26
|
}
|
|
25
27
|
export function sameSurface(left, right) {
|
|
@@ -30,8 +32,10 @@ export function sameSurface(left, right) {
|
|
|
30
32
|
}
|
|
31
33
|
return (left.kind === 'role' ||
|
|
32
34
|
(right.kind === 'application' &&
|
|
33
|
-
left.
|
|
34
|
-
left.
|
|
35
|
+
left.eligible_roles.length === right.eligible_roles.length &&
|
|
36
|
+
left.eligible_roles.every((role, index) => role === right.eligible_roles[index]) &&
|
|
37
|
+
left.schema_roles.length === right.schema_roles.length &&
|
|
38
|
+
left.schema_roles.every((role, index) => role === right.schema_roles[index])));
|
|
35
39
|
}
|
|
36
40
|
export function cloneSurface(surface) {
|
|
37
41
|
return surface.kind === 'role'
|
|
@@ -39,7 +43,8 @@ export function cloneSurface(surface) {
|
|
|
39
43
|
: Object.freeze({
|
|
40
44
|
kind: 'application',
|
|
41
45
|
name: surface.name,
|
|
42
|
-
|
|
46
|
+
eligible_roles: Object.freeze([...surface.eligible_roles]),
|
|
47
|
+
schema_roles: Object.freeze([...surface.schema_roles])
|
|
43
48
|
});
|
|
44
49
|
}
|
|
45
50
|
export function linkAbortSignals(signals) {
|
|
@@ -26,5 +26,14 @@ export declare const replicaResultObservation: unique symbol;
|
|
|
26
26
|
* @internal
|
|
27
27
|
*/
|
|
28
28
|
export declare const replicaCommandDirectProjection: unique symbol;
|
|
29
|
+
/** @internal Atomic preview-to-actual optimistic layer replacement. */
|
|
30
|
+
export declare const replicaCommandProjectionDelta: unique symbol;
|
|
29
31
|
/** @internal Package-private; intentionally absent from the public barrel. */
|
|
30
32
|
export declare const replicaCommandProjectedLifecycle: unique symbol;
|
|
33
|
+
/**
|
|
34
|
+
* Package-private live-record field read for pure reduce optimism.
|
|
35
|
+
* Returns undefined when the record is not present (fail closed).
|
|
36
|
+
*
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
export declare const replicaCommandReadRecord: unique symbol;
|
|
@@ -26,5 +26,14 @@ export const replicaResultObservation = Symbol('distributed.replica.result-obser
|
|
|
26
26
|
* @internal
|
|
27
27
|
*/
|
|
28
28
|
export const replicaCommandDirectProjection = Symbol('distributed.replica.command-direct-projection');
|
|
29
|
+
/** @internal Atomic preview-to-actual optimistic layer replacement. */
|
|
30
|
+
export const replicaCommandProjectionDelta = Symbol('distributed.replica.command-projection-delta');
|
|
29
31
|
/** @internal Package-private; intentionally absent from the public barrel. */
|
|
30
32
|
export const replicaCommandProjectedLifecycle = Symbol('distributed.replica.command-projected-lifecycle');
|
|
33
|
+
/**
|
|
34
|
+
* Package-private live-record field read for pure reduce optimism.
|
|
35
|
+
* Returns undefined when the record is not present (fail closed).
|
|
36
|
+
*
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
export const replicaCommandReadRecord = Symbol('distributed.replica.command-read-record');
|
|
@@ -4,7 +4,8 @@ import type { PrepareReplicaCommandOptions, ReplicaCommandArtifact, ReplicaPrepa
|
|
|
4
4
|
import type { ReplicaDiagnosticsSink } from '../diagnostics.js';
|
|
5
5
|
import type { ReplicaIndexSemanticChange } from '../index-maintenance.js';
|
|
6
6
|
import type { DistributedReplica, ReplicaAuthoritativeScope, ReplicaClientSurface, ReplicaIdentity, ReplicaModelArtifact, ReplicaOptimisticWriter, ReplicaResultEnvelope, ReplicaValue } from '../types.js';
|
|
7
|
-
import {
|
|
7
|
+
import type { ReplicaPureFunction } from '../projection-delta/index.js';
|
|
8
|
+
import { replicaCommandAuthority, replicaCommandDirectProjection, replicaCommandProjectionDelta, replicaCommandProjectedLifecycle, replicaCommandReadRecord, replicaResultObservation } from './symbols.js';
|
|
8
9
|
export type ReplicaCommandSurfaceContract = Readonly<{
|
|
9
10
|
protocolVersion: 1;
|
|
10
11
|
schemaHash: string;
|
|
@@ -36,6 +37,8 @@ export type ReplicaCommandAuthorityHost = DistributedReplica & {
|
|
|
36
37
|
readonly [replicaCommandAuthority]?: (contract: ReplicaCommandSurfaceContract) => ReplicaCommandAuthorityRegistration;
|
|
37
38
|
readonly [replicaResultObservation]?: (observer: (envelope: ReplicaResultEnvelope<unknown>) => void) => ReplicaResultObservationRegistration;
|
|
38
39
|
readonly [replicaCommandDirectProjection]?: (commandId: string, projection: ReplicaCommandDirectProjection) => void;
|
|
40
|
+
readonly [replicaCommandProjectionDelta]?: (commandId: string, update: (writer: ReplicaOptimisticWriter) => void, semanticChanges: readonly ReplicaIndexSemanticChange[]) => boolean;
|
|
41
|
+
readonly [replicaCommandReadRecord]?: (model: ReplicaModelArtifact, identity: ReplicaIdentity) => Readonly<Record<string, ReplicaValue>> | undefined;
|
|
39
42
|
};
|
|
40
43
|
export type ReplicaCommandTransportRequest = Readonly<{
|
|
41
44
|
operation: 'mutation';
|
|
@@ -87,8 +90,8 @@ export interface ReplicaCommandTransport {
|
|
|
87
90
|
}
|
|
88
91
|
export type ReplicaCommandProjectedOutcome<TOutput> = Readonly<{
|
|
89
92
|
commandId: string;
|
|
90
|
-
state: '
|
|
91
|
-
/** Present for same-transaction
|
|
93
|
+
state: 'atomic';
|
|
94
|
+
/** Present for same-transaction Atomic<T>, absent for async facts. */
|
|
92
95
|
result?: TOutput;
|
|
93
96
|
metadata?: DistributedCommandMetadata;
|
|
94
97
|
}>;
|
|
@@ -103,14 +106,14 @@ export type ReplicaCommandStatus = Readonly<{
|
|
|
103
106
|
}>;
|
|
104
107
|
export type ReplicaCommandReceipt<TOutput> = Readonly<{
|
|
105
108
|
commandId: string;
|
|
106
|
-
state: Extract<DistributedCommandState, '
|
|
109
|
+
state: Extract<DistributedCommandState, 'succeeded' | 'succeeded_pending_projection' | 'atomic'>;
|
|
107
110
|
/** Typed application payload returned by the generated mutation. */
|
|
108
111
|
result: TOutput;
|
|
109
112
|
metadata: DistributedCommandMetadata;
|
|
110
113
|
/** One exact generated status read. Calls coalesce while in flight. */
|
|
111
114
|
status(): Promise<ReplicaCommandStatus>;
|
|
112
115
|
/**
|
|
113
|
-
*
|
|
116
|
+
* Eventual visibility awaitable. It is omitted when no finite projection
|
|
114
117
|
* contract exists and never resolves because a wall-clock timer elapsed.
|
|
115
118
|
*/
|
|
116
119
|
projected?: Promise<ReplicaCommandProjectedOutcome<TOutput>>;
|
|
@@ -149,7 +152,7 @@ export type ReplicaCommandCallOptions<TOutput> = PrepareReplicaCommandOptions &
|
|
|
149
152
|
signal?: AbortSignal;
|
|
150
153
|
/** Exact same prepared request retries after thrown/ambiguous transport. */
|
|
151
154
|
transportRetries?: number;
|
|
152
|
-
|
|
155
|
+
onSucceeded?: (receipt: ReplicaCommandReceipt<TOutput>) => void | Promise<void>;
|
|
153
156
|
}>;
|
|
154
157
|
export type ReplicaCommandRuntimeErrorCode = 'REPLICA_COMMAND_ABORTED' | 'REPLICA_COMMAND_AUTHORITY_UNAVAILABLE' | 'REPLICA_COMMAND_DISPOSED' | 'REPLICA_COMMAND_OUTCOME_PENDING' | 'REPLICA_COMMAND_PROJECTION_FAILED' | 'REPLICA_COMMAND_PROTOCOL_INVALID' | 'REPLICA_COMMAND_REJECTED' | 'REPLICA_COMMAND_SCOPE_INVALIDATED' | 'REPLICA_COMMAND_STATUS_UNAVAILABLE' | 'REPLICA_COMMAND_TRANSPORT_AMBIGUOUS';
|
|
155
158
|
export type AnyCommandArtifact = ReplicaCommandArtifact<unknown, unknown>;
|
|
@@ -180,6 +183,11 @@ export type ReplicaCommandRuntimeOptions = Readonly<{
|
|
|
180
183
|
status?: ReplicaCommandStatusArtifact;
|
|
181
184
|
/** Optional shared replica diagnostics sink used only for static artifact inspection. */
|
|
182
185
|
diagnostics?: ReplicaDiagnosticsSink;
|
|
186
|
+
/**
|
|
187
|
+
* Named pure functions referenced by `artifact.projection.pureReduces`.
|
|
188
|
+
* Generated clients ship their inventory; apps do not invent board sims ad hoc.
|
|
189
|
+
*/
|
|
190
|
+
pureFunctions?: Readonly<Record<string, ReplicaPureFunction>>;
|
|
183
191
|
}>;
|
|
184
192
|
export interface ReplicaCommandRuntime<TEntries extends Readonly<Record<string, CommandEntry>>> {
|
|
185
193
|
readonly commands: ReplicaBoundCommands<TEntries>;
|
|
@@ -202,6 +210,7 @@ export type PendingProjection = {
|
|
|
202
210
|
readonly commandId: string;
|
|
203
211
|
readonly causationId: string;
|
|
204
212
|
readonly authority: CapturedAuthority;
|
|
213
|
+
readonly tracker: CommandStatusTracker;
|
|
205
214
|
readonly resolve: (value: ReplicaCommandProjectedOutcome<unknown>) => void;
|
|
206
215
|
readonly reject: (error: unknown) => void;
|
|
207
216
|
readonly promise: Promise<ReplicaCommandProjectedOutcome<unknown>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import { replicaCommandAuthority, replicaCommandDirectProjection, replicaCommandProjectedLifecycle, replicaResultObservation } from './symbols.js';
|
|
1
|
+
import { replicaCommandAuthority, replicaCommandDirectProjection, replicaCommandProjectionDelta, replicaCommandProjectedLifecycle, replicaCommandReadRecord, replicaResultObservation } from './symbols.js';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/** Public entry for command runtime; implementation lives in ./command-runtime/. */
|
|
2
|
-
export { createReplicaCommandRuntime, ReplicaCommandRuntimeError, replicaCommandAuthority, replicaCommandDirectProjection, replicaCommandProjectedLifecycle, replicaCommandProjectedLifecycleOf, replicaResultObservation } from './command-runtime/index.js';
|
|
2
|
+
export { createReplicaCommandRuntime, ReplicaCommandRuntimeError, replicaCommandAuthority, replicaCommandDirectProjection, replicaCommandProjectionDelta, replicaCommandProjectedLifecycle, replicaCommandProjectedLifecycleOf, replicaCommandReadRecord, replicaResultObservation } from './command-runtime/index.js';
|
|
3
3
|
export type { ReplicaBoundCommand, ReplicaBoundCommands, ReplicaCommandAuthorityHost, ReplicaCommandAuthorityRegistration, ReplicaCommandAuthoritySnapshot, ReplicaCommandCallOptions, ReplicaCommandDirectProjection, ReplicaCommandProjectedOutcome, ReplicaCommandReceipt, ReplicaCommandRecoveryReceipt, ReplicaCommandRuntime, ReplicaCommandRuntimeErrorCode, ReplicaCommandRuntimeOptions, ReplicaCommandStatus, ReplicaCommandStatusArtifact, ReplicaCommandStatusRequest, ReplicaCommandSurfaceContract, ReplicaCommandTransport, ReplicaCommandTransportRequest, ReplicaCommandTransportResult, ReplicaResultObservationRegistration } from './command-runtime/index.js';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Public entry for command runtime; implementation lives in ./command-runtime/. */
|
|
2
|
-
export { createReplicaCommandRuntime, ReplicaCommandRuntimeError, replicaCommandAuthority, replicaCommandDirectProjection, replicaCommandProjectedLifecycle, replicaCommandProjectedLifecycleOf, replicaResultObservation } from './command-runtime/index.js';
|
|
2
|
+
export { createReplicaCommandRuntime, ReplicaCommandRuntimeError, replicaCommandAuthority, replicaCommandDirectProjection, replicaCommandProjectionDelta, replicaCommandProjectedLifecycle, replicaCommandProjectedLifecycleOf, replicaCommandReadRecord, replicaResultObservation } from './command-runtime/index.js';
|
|
@@ -8,7 +8,8 @@ export function cloneClientSurface(surface) {
|
|
|
8
8
|
: Object.freeze({
|
|
9
9
|
kind: 'application',
|
|
10
10
|
name: surface.name,
|
|
11
|
-
|
|
11
|
+
eligible_roles: Object.freeze([...surface.eligible_roles]),
|
|
12
|
+
schema_roles: Object.freeze([...surface.schema_roles])
|
|
12
13
|
});
|
|
13
14
|
}
|
|
14
15
|
export function cloneTrustedPresetDescriptors(value) {
|
|
@@ -3,3 +3,4 @@ export { matchReplicaTrustedPresetInventory } from './presets.js';
|
|
|
3
3
|
export { verifyReplicaCommandReceipt } from './receipt.js';
|
|
4
4
|
export { ReplicaCommandContractError } from './errors.js';
|
|
5
5
|
export type { PrepareReplicaCommandOptions, ReplicaCommandArtifact, ReplicaCommandConfirmation, ReplicaCommandConfirmations, ReplicaCommandContractErrorCode, ReplicaCommandDirectProjection, ReplicaCommandEffect, ReplicaCommandEffectExpression, ReplicaCommandEffectField, ReplicaCommandEffectKey, ReplicaCommandEffectRelationship, ReplicaCommandEffects, ReplicaCommandGenerators, ReplicaCommandInputDefault, ReplicaCommandInputDefaults, ReplicaCommandRevalidationPlan, ReplicaCommandScalarCodec, ReplicaCommandShape, ReplicaCommandTypeDefinition, ReplicaCommandTypeField, ReplicaCommandVariables, ReplicaMatchedTrustedPresetInventory, ReplicaPreparedCommand, ReplicaPreparedCommandEffect, ReplicaPreparedConfirmation, ReplicaPreparedConfirmations, ReplicaPreparedEffectField, ReplicaPreparedEffectKey, ReplicaReceiptVerification, ReplicaTrustedPresetDescriptor } from './types.js';
|
|
6
|
+
export type { PreparedCommandProjection, PreparedProjectionOperation, ReplicaCommandProjection } from '../projection-delta/index.js';
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createReplicaCommandId } from '../command-id.js';
|
|
2
2
|
import { validateArtifact } from './validate.js';
|
|
3
3
|
import { selectReplicaTrustedPresetInventory } from './presets.js';
|
|
4
|
-
import { materializeInput,
|
|
4
|
+
import { materializeInput, resolveDirectProjection } from './resolve.js';
|
|
5
|
+
import { prepareCommandProjection, validateCommandProjectionArtifact } from '../projection-delta/index.js';
|
|
5
6
|
import { cloneClientSurface, cloneRevalidation, cloneTrustedPresetDescriptors } from './clone.js';
|
|
6
7
|
import { validateUuidV7 } from './util.js';
|
|
7
8
|
export function prepareReplicaCommand(artifact, input, options = {}) {
|
|
@@ -36,8 +37,10 @@ export function prepareReplicaCommandInternal(artifact, input, options, authorit
|
|
|
36
37
|
commandId,
|
|
37
38
|
...(artifact.input.kind === 'none' ? {} : { input: finalizedInput })
|
|
38
39
|
});
|
|
39
|
-
const
|
|
40
|
-
|
|
40
|
+
const projection = artifact.projection === undefined
|
|
41
|
+
? undefined
|
|
42
|
+
: prepareCommandProjection(validateCommandProjectionArtifact(artifact.projection), finalizedInput, trustedPresets?.values ?? []);
|
|
43
|
+
const operations = projection?.preview ?? Object.freeze([]);
|
|
41
44
|
const directProjection = resolveDirectProjection(artifact.directProjection, finalizedInput, trustedPresets);
|
|
42
45
|
const protocol = Object.freeze({
|
|
43
46
|
...artifact.protocol,
|
|
@@ -62,7 +65,7 @@ export function prepareReplicaCommandInternal(artifact, input, options, authorit
|
|
|
62
65
|
operations,
|
|
63
66
|
fallback: 'revalidate'
|
|
64
67
|
}),
|
|
65
|
-
...(
|
|
68
|
+
...(projection === undefined ? {} : { projection }),
|
|
66
69
|
...(directProjection === undefined ? {} : { directProjection }),
|
|
67
70
|
revalidation
|
|
68
71
|
});
|