@hops-ops/distributed 0.0.0 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +26 -13
  2. package/dist/auth-headers.js +3 -3
  3. package/dist/internal/cache-engine/engine.d.ts +3 -1
  4. package/dist/internal/cache-engine/engine.js +137 -22
  5. package/dist/internal/cache-engine/errors.d.ts +4 -0
  6. package/dist/internal/cache-engine/errors.js +8 -0
  7. package/dist/internal/cache-engine/index.d.ts +2 -2
  8. package/dist/internal/cache-engine/index.js +1 -1
  9. package/dist/internal/cache-engine/types.d.ts +16 -0
  10. package/dist/internal/cache-engine.d.ts +2 -2
  11. package/dist/internal/cache-engine.js +1 -1
  12. package/dist/protocol.d.ts +9 -2
  13. package/dist/protocol.js +35 -7
  14. package/dist/replica/command-runtime/create.js +499 -57
  15. package/dist/replica/command-runtime/index.d.ts +1 -1
  16. package/dist/replica/command-runtime/index.js +1 -1
  17. package/dist/replica/command-runtime/lib/effects.d.ts +22 -9
  18. package/dist/replica/command-runtime/lib/effects.js +118 -20
  19. package/dist/replica/command-runtime/lib/projection.d.ts +2 -2
  20. package/dist/replica/command-runtime/lib/projection.js +14 -6
  21. package/dist/replica/command-runtime/lib/status.js +18 -10
  22. package/dist/replica/command-runtime/lib/transport.js +4 -0
  23. package/dist/replica/command-runtime/lib/util.js +8 -3
  24. package/dist/replica/command-runtime/symbols.d.ts +9 -0
  25. package/dist/replica/command-runtime/symbols.js +9 -0
  26. package/dist/replica/command-runtime/types.d.ts +15 -6
  27. package/dist/replica/command-runtime/types.js +1 -1
  28. package/dist/replica/command-runtime.d.ts +1 -1
  29. package/dist/replica/command-runtime.js +1 -1
  30. package/dist/replica/commands/clone.js +2 -1
  31. package/dist/replica/commands/index.d.ts +1 -0
  32. package/dist/replica/commands/prepare.js +7 -4
  33. package/dist/replica/commands/receipt.js +30 -16
  34. package/dist/replica/commands/types.d.ts +10 -5
  35. package/dist/replica/commands/validate.d.ts +5 -1
  36. package/dist/replica/commands/validate.js +129 -62
  37. package/dist/replica/diagnostics/inspect.js +51 -19
  38. package/dist/replica/diagnostics/types.d.ts +7 -7
  39. package/dist/replica/distributed-replica/helpers.js +22 -8
  40. package/dist/replica/distributed-replica/hydration.js +14 -2
  41. package/dist/replica/distributed-replica/impl-diagnostics.d.ts +1 -1
  42. package/dist/replica/distributed-replica/impl-diagnostics.js +4 -3
  43. package/dist/replica/distributed-replica/impl-hydration-orchestrate.d.ts +1 -1
  44. package/dist/replica/distributed-replica/impl-hydration-orchestrate.js +76 -42
  45. package/dist/replica/distributed-replica/impl-optimistic.d.ts +11 -2
  46. package/dist/replica/distributed-replica/impl-optimistic.js +22 -6
  47. package/dist/replica/distributed-replica/impl-protocol.js +4 -2
  48. package/dist/replica/distributed-replica/impl.d.ts +8 -2
  49. package/dist/replica/distributed-replica/impl.js +22 -5
  50. package/dist/replica/distributed-replica/optimistic.js +31 -3
  51. package/dist/replica/distributed-replica/types.d.ts +1 -0
  52. package/dist/replica/index-maintenance/engine.js +14 -6
  53. package/dist/replica/index.d.ts +4 -0
  54. package/dist/replica/index.js +2 -0
  55. package/dist/replica/materialize.js +25 -0
  56. package/dist/replica/mutation-cache.d.ts +62 -0
  57. package/dist/replica/mutation-cache.js +91 -0
  58. package/dist/replica/operation-binding.js +17 -6
  59. package/dist/replica/persistence/state.js +9 -1
  60. package/dist/replica/projection-delta/canonical.d.ts +6 -0
  61. package/dist/replica/projection-delta/canonical.js +29 -0
  62. package/dist/replica/projection-delta/index.d.ts +5 -0
  63. package/dist/replica/projection-delta/index.js +3 -0
  64. package/dist/replica/projection-delta/resolve.d.ts +8 -0
  65. package/dist/replica/projection-delta/resolve.js +266 -0
  66. package/dist/replica/projection-delta/types.d.ts +415 -0
  67. package/dist/replica/projection-delta/types.js +6 -0
  68. package/dist/replica/projection-delta/validate.d.ts +18 -0
  69. package/dist/replica/projection-delta/validate.js +1718 -0
  70. package/dist/replica/projection-delta/wasm-pure.d.ts +36 -0
  71. package/dist/replica/projection-delta/wasm-pure.js +71 -0
  72. package/dist/replica/query-plan/pagination.js +18 -8
  73. package/dist/replica/types.d.ts +13 -2
  74. package/dist/sveltekit/replica.d.ts +1 -1
  75. package/dist/sveltekit/replica.js +3 -0
  76. package/dist/sveltekit/vite.d.ts +6 -6
  77. package/dist/sveltekit/vite.js +6 -6
  78. package/package.json +3 -3
@@ -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, type ReplicaPreparedCommandEffect, type ReplicaPreparedEffectKey } from '../../commands.js';
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 declare function applyOptimisticEffects(writer: ReplicaOptimisticWriter, effects: readonly ReplicaPreparedCommandEffect[]): void;
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
- export declare function modelFromKey(model: string, key: ReplicaPreparedEffectKey): ReplicaModelArtifact;
7
- export declare function identityFromKey(key: ReplicaPreparedEffectKey): readonly ReplicaValue[];
8
- export declare function fieldsFromEffect(model: string, key: ReplicaPreparedEffectKey, fields: readonly {
9
- readonly field: string;
10
- readonly value: ReplicaValue;
11
- }[]): Readonly<Record<string, ReplicaValue>>;
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
- export function applyOptimisticEffects(writer, effects) {
4
- for (const effect of effects) {
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.model, effect.key);
9
- writer.writeRecord(model, identityFromKey(effect.key), {
10
- fields: fieldsFromEffect(effect.model, effect.key, effect.fields)
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.model, effect.key), identityFromKey(effect.key));
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.relationship.sourceModel, effect.source);
42
- const target = modelFromKey(effect.relationship.targetModel, effect.target);
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.relationship.sourceModel,
46
- field: effect.relationship.field,
47
- targetModel: effect.relationship.targetModel,
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
- export function modelFromKey(model, key) {
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.fields.map(({ field }) => field))
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.fields.map(({ value }) => value));
170
+ return Object.freeze(key.key.map(({ value }) => value));
73
171
  }
74
- export function fieldsFromEffect(model, key, fields) {
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.fields,
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>): PendingProjection;
5
+ export declare function pendingProjection(authority: CapturedAuthority, metadata: DistributedCommandMetadata, prepared: ReplicaPreparedCommand<unknown, unknown>, tracker: CommandStatusTracker): PendingProjection;
6
6
  /**
7
- * Generated fact commands converge without application polling. Durable status
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 fields = cloneOutputJson(output, 'projected.output', new Set(), 0);
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 accepted receipt. Mark the internal lifecycle promise handled
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 fact commands converge without application polling. Durable status
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: 'projected',
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 `onAccepted` callback, before the
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 'accepted':
61
- case 'accepted_pending_projection':
62
- case 'projected':
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 'accepted':
112
- return next === 'accepted' || next === 'expired';
113
- case 'accepted_pending_projection':
114
- return (next === 'accepted_pending_projection' ||
115
- next === 'projected' ||
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 'projected':
119
- return next === 'projected' || next === 'expired';
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.roles.length === right.roles.length &&
34
- left.roles.every((role, index) => role === right.roles[index])));
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
- roles: Object.freeze([...surface.roles])
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 { replicaCommandAuthority, replicaCommandDirectProjection, replicaCommandProjectedLifecycle, replicaResultObservation } from './symbols.js';
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: 'projected';
91
- /** Present for same-transaction Projected<T>, absent for async facts. */
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, 'accepted' | 'accepted_pending_projection' | 'projected'>;
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
- * Causal visibility awaitable. It is omitted when no finite projection
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
- onAccepted?: (receipt: ReplicaCommandReceipt<TOutput>) => void | Promise<void>;
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
- roles: Object.freeze([...surface.roles])
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, resolveConfirmations, resolveDirectProjection, resolveEffect } from './resolve.js';
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 operations = Object.freeze(artifact.effects.operations.map((effect, index) => resolveEffect(effect, finalizedInput, `artifact.effects.operations[${index}]`, trustedPresets)));
40
- const confirmations = resolveConfirmations(artifact.confirmations, finalizedInput, trustedPresets);
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
- ...(confirmations === undefined ? {} : { confirmations }),
68
+ ...(projection === undefined ? {} : { projection }),
66
69
  ...(directProjection === undefined ? {} : { directProjection }),
67
70
  revalidation
68
71
  });