@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
- import { receiptMismatch, sameProjectionMultiset } from './util.js';
1
+ import { receiptMismatch } from './util.js';
2
2
  export function verifyReplicaCommandReceipt(prepared, receipt) {
3
3
  if (receipt.commandId !== prepared.commandId) {
4
4
  receiptMismatch('receipt.commandId');
@@ -6,31 +6,45 @@ export function verifyReplicaCommandReceipt(prepared, receipt) {
6
6
  if (receipt.consistency !== prepared.consistency) {
7
7
  receiptMismatch('receipt.consistency');
8
8
  }
9
- const contract = prepared.confirmations;
10
- if (contract?.kind === 'unavailable') {
9
+ if (receipt.projectionDisposition === 'revalidate') {
10
+ if (prepared.projection === undefined &&
11
+ !prepared.revalidation.required) {
12
+ receiptMismatch('receipt.projectionDisposition');
13
+ }
11
14
  return Object.freeze({
12
- kind: 'revalidate',
13
- revalidate: true,
14
- reason: 'confirmation_unavailable'
15
+ kind: receipt.state === 'succeeded_pending_projection'
16
+ ? 'deferred'
17
+ : 'matched',
18
+ revalidate: true
15
19
  });
16
20
  }
17
- const expected = contract?.kind === 'finite'
18
- ? contract.expected.map(({ projector, model }) => ({
19
- projection: projector,
20
- model
21
- }))
22
- : [];
23
21
  if (receipt.state === 'in_progress' && receipt.expects.length === 0) {
24
22
  return Object.freeze({
25
23
  kind: 'deferred',
26
- revalidate: prepared.revalidation.required
24
+ revalidate: prepared.revalidation.required ||
25
+ prepared.projection?.revalidate === true
27
26
  });
28
27
  }
29
- if (!sameProjectionMultiset(expected, receipt.expects)) {
30
- receiptMismatch('receipt.expects');
28
+ if (prepared.projection === undefined) {
29
+ if (receipt.projection !== undefined || receipt.expects.length !== 0) {
30
+ receiptMismatch('receipt.expects');
31
+ }
32
+ }
33
+ else if (receipt.projection === undefined) {
34
+ /*
35
+ * Ship contract (see server routes: Atomic never persists eventual
36
+ * modeled projection metadata):
37
+ * - Eventual: response carries projection-delta (+ expects) when modeled.
38
+ * - Atomic: response carries typed row + direct `records`; optional
39
+ * `.applies` previews use the same IR but seal via the row, not a delta.
40
+ */
41
+ if (prepared.consistency !== 'atomic') {
42
+ receiptMismatch('receipt.expects');
43
+ }
31
44
  }
32
45
  return Object.freeze({
33
46
  kind: receipt.state === 'in_progress' ? 'deferred' : 'matched',
34
- revalidate: prepared.revalidation.required
47
+ revalidate: prepared.revalidation.required ||
48
+ prepared.projection?.revalidate === true
35
49
  });
36
50
  }
@@ -1,5 +1,6 @@
1
1
  import type { DistributedCommandConsistency, DistributedTrustedPreset, DistributedTrustedPresetCodec } from '../../protocol.js';
2
2
  import type { ReplicaClientSurface, ReplicaValue } from '../types.js';
3
+ import type { PreparedCommandProjection, PreparedProjectionOperation, ReplicaCommandProjection } from '../projection-delta/index.js';
3
4
  export type ReplicaCommandScalarCodec = DistributedTrustedPresetCodec;
4
5
  /** Static name/codec declaration emitted without a server-derived value. */
5
6
  export type ReplicaTrustedPresetDescriptor = {
@@ -127,14 +128,14 @@ export type ReplicaCommandRevalidationPlan = {
127
128
  readonly relationships: readonly ReplicaCommandEffectRelationship[];
128
129
  };
129
130
  /**
130
- * Framework-neutral executable command descriptor emitted by `dctl client`.
131
+ * Framework-neutral executable command descriptor emitted by `distributed client`.
131
132
  *
132
133
  * The compiler has already validated the Rust-owned declaration. Runtime
133
134
  * validation remains fail-closed so a stale or hand-edited artifact cannot
134
135
  * create optimistic cache truth.
135
136
  */
136
137
  export type ReplicaCommandArtifact<TInput = void, TOutput = unknown> = {
137
- readonly version: 1;
138
+ readonly version: 2;
138
139
  readonly name: string;
139
140
  readonly mutationField: string;
140
141
  readonly document: string;
@@ -152,8 +153,11 @@ export type ReplicaCommandArtifact<TInput = void, TOutput = unknown> = {
152
153
  readonly output: ReplicaCommandShape;
153
154
  readonly inputDefaults?: ReplicaCommandInputDefaults;
154
155
  readonly consistency: DistributedCommandConsistency;
155
- readonly effects: ReplicaCommandEffects;
156
- readonly confirmations?: ReplicaCommandConfirmations;
156
+ /** Compiler-owned projection contract. Absence means no invented delta. */
157
+ readonly projection?: ReplicaCommandProjection;
158
+ /** Artifact v1 effects and confirmations are deliberately not decoded. */
159
+ readonly effects?: never;
160
+ readonly confirmations?: never;
157
161
  readonly directProjection?: ReplicaCommandDirectProjection;
158
162
  readonly trustedPresets?: readonly ReplicaTrustedPresetDescriptor[];
159
163
  readonly revalidation: ReplicaCommandRevalidationPlan;
@@ -225,9 +229,10 @@ export type ReplicaPreparedCommand<TInput = void, TOutput = unknown> = {
225
229
  };
226
230
  readonly optimistic: {
227
231
  readonly version: 1;
228
- readonly operations: readonly ReplicaPreparedCommandEffect[];
232
+ readonly operations: readonly PreparedProjectionOperation[];
229
233
  readonly fallback: 'revalidate';
230
234
  };
235
+ readonly projection?: PreparedCommandProjection;
231
236
  readonly confirmations?: ReplicaPreparedConfirmations;
232
237
  readonly directProjection?: {
233
238
  readonly topology: ReplicaCommandDirectProjection['topology'];
@@ -7,7 +7,11 @@ export declare function collectTrustedPresetReferences<TInput, TOutput>(artifact
7
7
  export declare function validateShape(shape: ReplicaCommandShape, path: string, depth: number, active: Set<ReplicaCommandTypeDefinition>): void;
8
8
  export declare function validateDefinition(definition: ReplicaCommandTypeDefinition, path: string, depth: number, active: Set<ReplicaCommandTypeDefinition>): void;
9
9
  export declare function validateDefaults(shape: ReplicaCommandShape, defaults: ReplicaCommandInputDefaults | undefined): void;
10
- export declare function validateConfirmations<TInput, TOutput>(artifact: ReplicaCommandArtifact<TInput, TOutput>): void;
10
+ export declare function validateConfirmations<TInput, TOutput>(artifact: Readonly<{
11
+ confirmations?: import('./types.js').ReplicaCommandConfirmations;
12
+ consistency: ReplicaCommandArtifact<TInput, TOutput>['consistency'];
13
+ input: ReplicaCommandArtifact<TInput, TOutput>['input'];
14
+ }>): void;
11
15
  export declare function validateDirectProjection<TInput, TOutput>(artifact: ReplicaCommandArtifact<TInput, TOutput>): void;
12
16
  export declare function validateRevalidation<TInput, TOutput>(artifact: ReplicaCommandArtifact<TInput, TOutput>): void;
13
17
  export declare function validateEffectArtifact(effect: ReplicaCommandEffect, input: ReplicaCommandShape, path: string): void;
@@ -3,9 +3,14 @@ import { GRAPHQL_NAME, MAX_TYPE_DEPTH } from './constants.js';
3
3
  import { artifactInvalid, hash, isSupportedCodec, nonempty, requiredString } from './util.js';
4
4
  import { parseReplicaTrustedPresetDescriptors, trustedPresetName } from './presets.js';
5
5
  import { fieldAtPath } from './resolve.js';
6
+ import { ProjectionDeltaValidationError, validateCommandProjectionArtifact } from '../projection-delta/index.js';
6
7
  export function validateArtifact(artifact, allowTrustedPresets) {
7
- if (artifact.version !== 1)
8
+ if (artifact.version !== 2)
8
9
  artifactInvalid('artifact.version');
10
+ if (Object.prototype.hasOwnProperty.call(artifact, 'effects') ||
11
+ Object.prototype.hasOwnProperty.call(artifact, 'confirmations')) {
12
+ artifactInvalid('artifact.version');
13
+ }
9
14
  nonempty(artifact.name, 'artifact.name');
10
15
  if (!GRAPHQL_NAME.test(artifact.mutationField)) {
11
16
  artifactInvalid('artifact.mutationField');
@@ -37,23 +42,22 @@ export function validateArtifact(artifact, allowTrustedPresets) {
37
42
  if (artifact.output.kind === 'none')
38
43
  artifactInvalid('artifact.output.kind');
39
44
  validateDefaults(artifact.input, artifact.inputDefaults);
40
- if (artifact.consistency !== 'accepted' &&
41
- artifact.consistency !== 'fact' &&
42
- artifact.consistency !== 'projected') {
45
+ if (artifact.consistency !== 'succeeded' &&
46
+ artifact.consistency !== 'eventual' &&
47
+ artifact.consistency !== 'atomic') {
43
48
  artifactInvalid('artifact.consistency');
44
49
  }
45
- if (artifact.effects.version !== 1)
46
- artifactInvalid('artifact.effects.version');
47
- if (artifact.effects.fallback !== 'revalidate') {
48
- artifactInvalid('artifact.effects.fallback');
49
- }
50
- if (!Array.isArray(artifact.effects.operations)) {
51
- artifactInvalid('artifact.effects.operations');
52
- }
53
- for (let index = 0; index < artifact.effects.operations.length; index += 1) {
54
- validateEffectArtifact(artifact.effects.operations[index], artifact.input, `artifact.effects.operations[${index}]`);
50
+ if (artifact.projection !== undefined) {
51
+ try {
52
+ validateProjectionCapabilities(validateCommandProjectionArtifact(artifact.projection), artifact);
53
+ }
54
+ catch (error) {
55
+ if (error instanceof ProjectionDeltaValidationError) {
56
+ artifactInvalid(error.path);
57
+ }
58
+ throw error;
59
+ }
55
60
  }
56
- validateConfirmations(artifact);
57
61
  validateDirectProjection(artifact);
58
62
  validateRevalidation(artifact);
59
63
  validateTrustedPresetReferences(artifact, trustedPresets, allowTrustedPresets);
@@ -66,16 +70,33 @@ export function validateClientSurface(surface, path) {
66
70
  if (surface.kind === 'role')
67
71
  return;
68
72
  if (surface.kind !== 'application' ||
69
- !Array.isArray(surface.roles) ||
70
- surface.roles.length === 0) {
73
+ !Array.isArray(surface.eligible_roles) ||
74
+ !Array.isArray(surface.schema_roles) ||
75
+ surface.eligible_roles.length === 0 ||
76
+ surface.schema_roles.length === 0) {
71
77
  artifactInvalid(path);
72
78
  }
73
- const roles = new Set();
74
- for (let index = 0; index < surface.roles.length; index += 1) {
75
- const role = requiredString(surface.roles[index], `${path}.roles[${index}]`);
76
- if (roles.has(role))
77
- artifactInvalid(`${path}.roles[${index}]`);
78
- roles.add(role);
79
+ const eligibleRoles = new Set();
80
+ for (let index = 0; index < surface.eligible_roles.length; index += 1) {
81
+ const role = requiredString(surface.eligible_roles[index], `${path}.eligible_roles[${index}]`);
82
+ if (eligibleRoles.has(role))
83
+ artifactInvalid(`${path}.eligible_roles[${index}]`);
84
+ if (index > 0 && surface.eligible_roles[index - 1] >= role) {
85
+ artifactInvalid(`${path}.eligible_roles[${index}]`);
86
+ }
87
+ eligibleRoles.add(role);
88
+ }
89
+ const schemaRoles = new Set();
90
+ for (let index = 0; index < surface.schema_roles.length; index += 1) {
91
+ const role = requiredString(surface.schema_roles[index], `${path}.schema_roles[${index}]`);
92
+ if (schemaRoles.has(role))
93
+ artifactInvalid(`${path}.schema_roles[${index}]`);
94
+ if (index > 0 && surface.schema_roles[index - 1] >= role) {
95
+ artifactInvalid(`${path}.schema_roles[${index}]`);
96
+ }
97
+ if (!eligibleRoles.has(role))
98
+ artifactInvalid(`${path}.schema_roles[${index}]`);
99
+ schemaRoles.add(role);
79
100
  }
80
101
  }
81
102
  export function validateTrustedPresetReferences(artifact, descriptors, allowTrustedPresets) {
@@ -102,52 +123,99 @@ export function collectTrustedPresetReferences(artifact) {
102
123
  out.push(Object.freeze({ name: value.name, path }));
103
124
  }
104
125
  };
105
- const fields = (value, path) => {
106
- for (let index = 0; index < value.length; index += 1) {
107
- expression(value[index].value, `${path}[${index}].value`);
126
+ if (artifact.projection !== undefined) {
127
+ for (let index = 0; index < artifact.projection.preview.operations.length; index += 1) {
128
+ const mutation = artifact.projection.preview.operations[index].mutation;
129
+ collectPreviewMutationPresets(mutation, `artifact.projection.preview.operations[${index}].mutation`, out);
130
+ }
131
+ }
132
+ if (artifact.directProjection?.partition !== undefined) {
133
+ expression(artifact.directProjection.partition, 'artifact.directProjection.partition');
134
+ }
135
+ return Object.freeze(out);
136
+ }
137
+ function collectPreviewMutationPresets(mutation, path, out) {
138
+ const value = (expression, valuePath) => {
139
+ switch (expression.kind) {
140
+ case 'trusted_preset':
141
+ out.push(Object.freeze({ name: expression.name, path: valuePath }));
142
+ break;
143
+ case 'list':
144
+ expression.values.forEach((item, index) => value(item, `${valuePath}.values[${index}]`));
145
+ break;
146
+ case 'object':
147
+ expression.fields.forEach((field, index) => value(field.value, `${valuePath}.fields[${index}].value`));
148
+ break;
149
+ case 'transform':
150
+ expression.arguments.forEach((item, index) => value(item, `${valuePath}.arguments[${index}]`));
151
+ break;
152
+ default:
153
+ break;
108
154
  }
109
155
  };
110
- const key = (value, path) => {
111
- fields(value.fields, `${path}.fields`);
156
+ const scope = (candidate, scopePath) => {
157
+ if (candidate.partition.kind === 'expression') {
158
+ value(candidate.partition.expression, `${scopePath}.partition.expression`);
159
+ }
160
+ candidate.key.forEach((field, index) => value(field.value, `${scopePath}.key[${index}].value`));
112
161
  };
113
- for (let index = 0; index < artifact.effects.operations.length; index += 1) {
114
- const effect = artifact.effects.operations[index];
115
- const path = `artifact.effects.operations[${index}]`;
116
- switch (effect.kind) {
162
+ switch (mutation.op) {
163
+ case 'upsert':
164
+ case 'patch':
165
+ scope(mutation.scope, `${path}.scope`);
166
+ (mutation.op === 'upsert' ? mutation.fields : mutation.set).forEach((field, index) => value(field.value, `${path}.fields[${index}].value`));
167
+ break;
168
+ case 'delete':
169
+ scope(mutation.scope, `${path}.scope`);
170
+ break;
171
+ case 'link':
172
+ case 'unlink':
173
+ scope(mutation.source, `${path}.source`);
174
+ scope(mutation.target, `${path}.target`);
175
+ break;
176
+ case 'invalidate_relationship':
177
+ scope(mutation.source, `${path}.source`);
178
+ break;
179
+ case 'invalidate_model':
180
+ if (mutation.partition?.kind === 'expression') {
181
+ value(mutation.partition.expression, `${path}.partition.expression`);
182
+ }
183
+ break;
184
+ }
185
+ }
186
+ function validateProjectionCapabilities(projection, artifact) {
187
+ const models = new Set(artifact.revalidation.models);
188
+ const relationships = new Set(artifact.revalidation.relationships.map((item) => `${item.sourceModel}\0${item.field}\0${item.targetModel}`));
189
+ const requireModel = (model, path) => {
190
+ if (!models.has(model))
191
+ artifactInvalid(path);
192
+ };
193
+ for (let index = 0; index < projection.preview.operations.length; index += 1) {
194
+ const mutation = projection.preview.operations[index].mutation;
195
+ const path = `artifact.projection.preview.operations[${index}].mutation`;
196
+ switch (mutation.op) {
117
197
  case 'upsert':
118
198
  case 'patch':
119
- key(effect.key, `${path}.key`);
120
- fields(effect.fields, `${path}.fields`);
121
- break;
122
199
  case 'delete':
123
- key(effect.key, `${path}.key`);
200
+ requireModel(mutation.scope.model, `${path}.scope.model`);
124
201
  break;
125
202
  case 'link':
126
203
  case 'unlink':
127
- key(effect.source, `${path}.source`);
128
- key(effect.target, `${path}.target`);
129
- break;
130
- case 'invalidate_relationship':
131
- key(effect.source, `${path}.source`);
204
+ requireModel(mutation.source.model, `${path}.source.model`);
205
+ requireModel(mutation.target.model, `${path}.target.model`);
206
+ if (!relationships.has(`${mutation.source.model}\0${mutation.relationship}\0${mutation.target.model}`))
207
+ artifactInvalid(`${path}.relationship`);
132
208
  break;
133
209
  case 'invalidate_model':
210
+ requireModel(mutation.model, `${path}.model`);
211
+ break;
212
+ case 'invalidate_relationship':
213
+ requireModel(mutation.source.model, `${path}.source.model`);
214
+ if (![...relationships].some((relationship) => relationship.startsWith(`${mutation.source.model}\0${mutation.relationship}\0`)))
215
+ artifactInvalid(`${path}.relationship`);
134
216
  break;
135
217
  }
136
218
  }
137
- if (artifact.confirmations?.kind === 'finite') {
138
- for (let index = 0; index < artifact.confirmations.expected.length; index += 1) {
139
- const confirmation = artifact.confirmations.expected[index];
140
- const path = `artifact.confirmations.expected[${index}]`;
141
- key(confirmation.key, `${path}.key`);
142
- if (confirmation.partition !== undefined) {
143
- expression(confirmation.partition, `${path}.partition`);
144
- }
145
- }
146
- }
147
- if (artifact.directProjection?.partition !== undefined) {
148
- expression(artifact.directProjection.partition, 'artifact.directProjection.partition');
149
- }
150
- return Object.freeze(out);
151
219
  }
152
220
  export function validateShape(shape, path, depth, active) {
153
221
  if (depth > MAX_TYPE_DEPTH)
@@ -236,7 +304,7 @@ export function validateDefaults(shape, defaults) {
236
304
  export function validateConfirmations(artifact) {
237
305
  const confirmations = artifact.confirmations;
238
306
  if (confirmations === undefined) {
239
- if (artifact.consistency === 'fact') {
307
+ if (artifact.consistency === 'eventual') {
240
308
  artifactInvalid('artifact.confirmations');
241
309
  }
242
310
  return;
@@ -259,7 +327,7 @@ export function validateConfirmations(artifact) {
259
327
  else {
260
328
  artifactInvalid('artifact.confirmations.kind');
261
329
  }
262
- if (artifact.consistency === 'projected') {
330
+ if (artifact.consistency === 'atomic') {
263
331
  artifactInvalid('artifact.confirmations');
264
332
  }
265
333
  if (confirmations.kind === 'finite') {
@@ -277,10 +345,10 @@ export function validateConfirmations(artifact) {
277
345
  }
278
346
  export function validateDirectProjection(artifact) {
279
347
  const direct = artifact.directProjection;
280
- if (artifact.consistency === 'projected' && direct === undefined) {
348
+ if (artifact.consistency === 'atomic' && direct === undefined) {
281
349
  artifactInvalid('artifact.directProjection');
282
350
  }
283
- if (artifact.consistency !== 'projected' && direct !== undefined) {
351
+ if (artifact.consistency !== 'atomic' && direct !== undefined) {
284
352
  artifactInvalid('artifact.directProjection');
285
353
  }
286
354
  if (direct === undefined)
@@ -338,9 +406,8 @@ export function validateRevalidation(artifact) {
338
406
  for (const [index, relationship] of plan.relationships.entries()) {
339
407
  validateRelationshipArtifact(relationship, `artifact.revalidation.relationships[${index}]`);
340
408
  }
341
- if ((artifact.effects.operations.length === 0 ||
342
- (artifact.consistency !== 'projected' &&
343
- artifact.confirmations?.kind !== 'finite')) &&
409
+ if (((artifact.projection?.preview.operations.length ?? 0) === 0 &&
410
+ artifact.consistency !== 'atomic') &&
344
411
  !plan.required) {
345
412
  artifactInvalid('artifact.revalidation.required');
346
413
  }
@@ -29,43 +29,47 @@ export function inspectReplicaOperationArtifact(artifact) {
29
29
  });
30
30
  }
31
31
  export function inspectReplicaCommandArtifact(artifact) {
32
- const effects = artifact.effects.operations.map((effect) => {
32
+ const effects = (artifact.projection?.preview.operations ?? []).map(({ mutation: effect }) => {
33
33
  const models = new Set();
34
34
  const fields = new Set();
35
35
  const sources = new Set();
36
- if ('model' in effect)
36
+ if (effect.op === 'invalidate_model')
37
37
  models.add(effect.model);
38
- if ('relationship' in effect) {
39
- models.add(effect.relationship.sourceModel);
40
- models.add(effect.relationship.targetModel);
41
- fields.add(effect.relationship.field);
42
- }
43
- if ('key' in effect) {
44
- for (const field of effect.key.fields) {
38
+ if ('scope' in effect) {
39
+ models.add(effect.scope.model);
40
+ for (const field of effect.scope.key) {
45
41
  fields.add(field.field);
46
- sources.add(expressionSource(field.value));
42
+ collectPreviewValueSources(field.value, sources);
47
43
  }
48
44
  }
49
45
  if ('source' in effect) {
50
- for (const field of effect.source.fields) {
46
+ models.add(effect.source.model);
47
+ for (const field of effect.source.key) {
51
48
  fields.add(field.field);
52
- sources.add(expressionSource(field.value));
49
+ collectPreviewValueSources(field.value, sources);
53
50
  }
54
51
  }
55
52
  if ('target' in effect) {
56
- for (const field of effect.target.fields) {
53
+ models.add(effect.target.model);
54
+ for (const field of effect.target.key) {
57
55
  fields.add(field.field);
58
- sources.add(expressionSource(field.value));
56
+ collectPreviewValueSources(field.value, sources);
59
57
  }
60
58
  }
61
59
  if ('fields' in effect) {
62
60
  for (const field of effect.fields) {
63
61
  fields.add(field.field);
64
- sources.add(expressionSource(field.value));
62
+ collectPreviewValueSources(field.value, sources);
63
+ }
64
+ }
65
+ if ('set' in effect) {
66
+ for (const field of effect.set) {
67
+ fields.add(field.field);
68
+ collectPreviewValueSources(field.value, sources);
65
69
  }
66
70
  }
67
71
  return Object.freeze({
68
- kind: effect.kind,
72
+ kind: effect.op,
69
73
  models: Object.freeze([...models].sort()),
70
74
  fields: Object.freeze([...fields].sort()),
71
75
  valueSources: Object.freeze([...sources].sort())
@@ -84,6 +88,37 @@ export function inspectReplicaCommandArtifact(artifact) {
84
88
  })
85
89
  });
86
90
  }
91
+ function collectPreviewValueSources(value, out) {
92
+ switch (value.kind) {
93
+ case 'input':
94
+ case 'generated_default':
95
+ out.add('input');
96
+ return;
97
+ case 'trusted_preset':
98
+ out.add('trusted_preset');
99
+ return;
100
+ case 'constant':
101
+ out.add('constant');
102
+ return;
103
+ case 'null':
104
+ out.add('null');
105
+ return;
106
+ case 'list':
107
+ for (const item of value.values)
108
+ collectPreviewValueSources(item, out);
109
+ return;
110
+ case 'object':
111
+ for (const field of value.fields) {
112
+ collectPreviewValueSources(field.value, out);
113
+ }
114
+ return;
115
+ case 'transform':
116
+ for (const argument of value.arguments) {
117
+ collectPreviewValueSources(argument, out);
118
+ }
119
+ return;
120
+ }
121
+ }
87
122
  function inspectRoot(root, path, injected, dependencies, indexes) {
88
123
  for (const dependency of root.dependencies)
89
124
  dependencies.add(dependency);
@@ -165,6 +200,3 @@ function inspectSelection(selection, path, injected, dependencies, indexes) {
165
200
  inspectSelection(member.selection, memberPath, injected, dependencies, indexes);
166
201
  }
167
202
  }
168
- function expressionSource(expression) {
169
- return expression.kind;
170
- }
@@ -99,8 +99,8 @@ export type ReplicaDiagnosticReceiptExpectationInput = Readonly<{
99
99
  }>;
100
100
  export type ReplicaDiagnosticReceiptInput = Readonly<{
101
101
  commandId: string;
102
- state: 'optimistic' | 'accepted' | 'accepted_pending_projection';
103
- consistency?: 'accepted' | 'fact' | 'projected';
102
+ state: 'optimistic' | 'succeeded' | 'succeeded_pending_projection';
103
+ consistency?: 'succeeded' | 'eventual' | 'atomic';
104
104
  expectations: readonly ReplicaDiagnosticReceiptExpectationInput[];
105
105
  }>;
106
106
  /** Engine-independent state contract implemented by DistributedReplica. */
@@ -138,7 +138,7 @@ export type ReplicaDiagnosticEventInput = Readonly<{
138
138
  }> | Readonly<{
139
139
  kind: 'receipt';
140
140
  command: string;
141
- state: 'optimistic' | 'accepted' | 'accepted_pending_projection' | 'projected' | 'rejected';
141
+ state: 'optimistic' | 'succeeded' | 'succeeded_pending_projection' | 'atomic' | 'rejected';
142
142
  obligations: number;
143
143
  observed: number;
144
144
  }> | Readonly<{
@@ -152,7 +152,7 @@ export type ReplicaDiagnosticEventInput = Readonly<{
152
152
  }> | Readonly<{
153
153
  kind: 'hydration';
154
154
  action: 'accepted' | 'rejected';
155
- reason: 'accepted' | 'invalid' | 'scope-mismatch' | 'artifact-mismatch' | 'active-scope-mismatch' | 'metadata-mismatch';
155
+ reason: 'accepted' | 'same-scope-merge' | 'invalid' | 'scope-mismatch' | 'artifact-mismatch' | 'active-scope-mismatch' | 'metadata-mismatch';
156
156
  }> | Readonly<{
157
157
  kind: 'scope';
158
158
  action: 'established' | 'changed' | 'invalidated';
@@ -200,8 +200,8 @@ export type ReplicaDiagnosticLayer = Readonly<{
200
200
  }>;
201
201
  export type ReplicaDiagnosticReceipt = Readonly<{
202
202
  commandId: string;
203
- state: 'optimistic' | 'accepted' | 'accepted_pending_projection';
204
- consistency?: 'accepted' | 'fact' | 'projected';
203
+ state: 'optimistic' | 'succeeded' | 'succeeded_pending_projection';
204
+ consistency?: 'succeeded' | 'eventual' | 'atomic';
205
205
  expectations: readonly ReplicaDiagnosticReceiptExpectationInput[];
206
206
  }>;
207
207
  export type ReplicaArtifactSourceLocation = Readonly<{
@@ -246,7 +246,7 @@ export type ReplicaCommandArtifactInspection = Readonly<{
246
246
  kind: 'command';
247
247
  name: string;
248
248
  operation: string;
249
- consistency: 'accepted' | 'fact' | 'projected';
249
+ consistency: 'succeeded' | 'eventual' | 'atomic';
250
250
  effects: readonly ReplicaCommandEffectInspection[];
251
251
  revalidation: Readonly<{
252
252
  required: boolean;
@@ -272,7 +272,10 @@ export function replicaClientRequestExtensions(artifact) {
272
272
  : Object.freeze({
273
273
  kind: 'application',
274
274
  name: protocol.surface.name,
275
- roles: Object.freeze([...protocol.surface.roles])
275
+ eligible_roles: Object.freeze([
276
+ ...protocol.surface.eligible_roles
277
+ ]),
278
+ schema_roles: Object.freeze([...protocol.surface.schema_roles])
276
279
  });
277
280
  return Object.freeze({
278
281
  extensions: Object.freeze({
@@ -364,14 +367,25 @@ export function validatedSurfaceIdentity(value) {
364
367
  return JSON.stringify(['role', value.name]);
365
368
  }
366
369
  if (value.kind !== 'application' ||
367
- !Array.isArray(value.roles) ||
368
- value.roles.length === 0 ||
369
- value.roles.some((role) => typeof role !== 'string' || role.length === 0) ||
370
- new Set(value.roles).size !== value.roles.length ||
371
- [...value.roles].sort().some((role, index) => role !== value.roles[index])) {
370
+ !Array.isArray(value.eligible_roles) ||
371
+ !Array.isArray(value.schema_roles) ||
372
+ value.eligible_roles.length === 0 ||
373
+ value.schema_roles.length === 0 ||
374
+ value.eligible_roles.some((role) => typeof role !== 'string' || role.length === 0) ||
375
+ value.schema_roles.some((role) => typeof role !== 'string' || role.length === 0) ||
376
+ new Set(value.eligible_roles).size !== value.eligible_roles.length ||
377
+ new Set(value.schema_roles).size !== value.schema_roles.length ||
378
+ [...value.eligible_roles].sort().some((role, index) => role !== value.eligible_roles[index]) ||
379
+ [...value.schema_roles].sort().some((role, index) => role !== value.schema_roles[index]) ||
380
+ value.schema_roles.some((role) => !value.eligible_roles.includes(role))) {
372
381
  throw new TypeError('replica artifact client surface is invalid');
373
382
  }
374
- return JSON.stringify(['application', value.name, value.roles]);
383
+ return JSON.stringify([
384
+ 'application',
385
+ value.name,
386
+ value.eligible_roles,
387
+ value.schema_roles
388
+ ]);
375
389
  }
376
390
  export function snapshotFrom(materialized, state) {
377
391
  const status = state.errors.length > 0
@@ -434,7 +448,7 @@ export function graphqlError(error) {
434
448
  });
435
449
  }
436
450
  export function assertWriteSource(source) {
437
- if (!['network', 'live', 'ssr', 'restore', 'projected'].includes(source)) {
451
+ if (!['network', 'live', 'ssr', 'restore', 'atomic'].includes(source)) {
438
452
  throw new TypeError(`unsupported replica write source: ${source}`);
439
453
  }
440
454
  }
@@ -45,13 +45,19 @@ export function serializeOperationProtocolState(state) {
45
45
  }
46
46
  export function parseAuthoritativeScope(value) {
47
47
  try {
48
- const scope = hydrationRecord(value, 'authoritativeScope', ['protocolVersion', 'schemaHash', 'cacheScope']);
48
+ const scope = hydrationRecord(value, 'authoritativeScope', [
49
+ 'protocolVersion',
50
+ 'schemaHash',
51
+ 'authorizationGeneration',
52
+ 'cacheScope'
53
+ ]);
49
54
  if (scope.protocolVersion !== 1) {
50
55
  hydrationInvalid('authoritativeScope.protocolVersion');
51
56
  }
52
57
  return Object.freeze({
53
58
  protocolVersion: 1,
54
59
  schemaHash: hydrationString(scope.schemaHash, 'authoritativeScope.schemaHash'),
60
+ authorizationGeneration: hydrationString(scope.authorizationGeneration, 'authoritativeScope.authorizationGeneration'),
55
61
  cacheScope: hydrationOpaque(scope.cacheScope, 'authoritativeScope.cacheScope')
56
62
  });
57
63
  }
@@ -133,13 +139,19 @@ export function parseReplicaHydration(value) {
133
139
  const state = hydrationRecord(value, 'state', ['version', 'scope', 'payload']);
134
140
  if (state.version !== 1)
135
141
  hydrationInvalid('state.version');
136
- const scopeValue = hydrationRecord(state.scope, 'state.scope', ['protocolVersion', 'schemaHash', 'cacheScope']);
142
+ const scopeValue = hydrationRecord(state.scope, 'state.scope', [
143
+ 'protocolVersion',
144
+ 'schemaHash',
145
+ 'authorizationGeneration',
146
+ 'cacheScope'
147
+ ]);
137
148
  if (scopeValue.protocolVersion !== 1) {
138
149
  hydrationInvalid('state.scope.protocolVersion');
139
150
  }
140
151
  const scope = Object.freeze({
141
152
  protocolVersion: 1,
142
153
  schemaHash: hydrationString(scopeValue.schemaHash, 'state.scope.schemaHash'),
154
+ authorizationGeneration: hydrationString(scopeValue.authorizationGeneration, 'state.scope.authorizationGeneration'),
143
155
  cacheScope: hydrationOpaque(scopeValue.cacheScope, 'state.scope.cacheScope')
144
156
  });
145
157
  const payload = hydrationRecord(state.payload, 'state.payload', [