@aventara/core 1.0.0-rc.1 → 1.0.0-rc.3

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 (97) hide show
  1. package/CHANGELOG.md +341 -1
  2. package/ERRORS.md +142 -0
  3. package/README.md +24 -1
  4. package/dist/adapters/adapter-discovery-diagnostic.guidance.d.ts +6 -0
  5. package/dist/adapters/adapter-discovery-diagnostic.guidance.js +7 -0
  6. package/dist/adapters/adapter-field.d.ts +14 -0
  7. package/dist/adapters/adapter-model.d.ts +9 -0
  8. package/dist/adapters/adapter.interface.d.ts +10 -0
  9. package/dist/compiler/contract-compiler-diagnostic.guidance.d.ts +6 -0
  10. package/dist/compiler/contract-compiler-diagnostic.guidance.js +97 -0
  11. package/dist/compiler/contract.compiler.js +94 -6
  12. package/dist/config/computed-fields.d.ts +62 -0
  13. package/dist/config/computed-fields.js +80 -0
  14. package/dist/config/config-diagnostic.guidance.d.ts +6 -0
  15. package/dist/config/config-diagnostic.guidance.js +32 -0
  16. package/dist/config/define-resource.d.ts +1 -1
  17. package/dist/config/exact-framework-config.d.ts +50 -20
  18. package/dist/config/field-compute.resolver.d.ts +2 -0
  19. package/dist/config/field-compute.resolver.js +8 -0
  20. package/dist/config/framework-config.d.ts +7 -6
  21. package/dist/config/framework-resources-config.d.ts +18 -12
  22. package/dist/config/pipeline-config.d.ts +4 -4
  23. package/dist/contracts/capability.d.ts +10 -1
  24. package/dist/contracts/capability.js +2 -0
  25. package/dist/contracts/client-contract-structure.validator.d.ts +65 -35
  26. package/dist/contracts/client-contract-structure.validator.js +4 -2
  27. package/dist/contracts/contract-canonicalizer.d.ts +7 -0
  28. package/dist/contracts/contract-canonicalizer.js +18 -4
  29. package/dist/contracts/field.d.ts +30 -1
  30. package/dist/framework/contract-view.d.ts +4 -4
  31. package/dist/framework/framework-construction.error.d.ts +12 -3
  32. package/dist/framework/framework-construction.error.js +5 -1
  33. package/dist/framework/framework.factory.d.ts +1 -1
  34. package/dist/framework/framework.factory.js +9 -2
  35. package/dist/index.d.ts +2 -0
  36. package/dist/index.js +1 -0
  37. package/dist/internal.d.ts +2 -0
  38. package/dist/internal.js +1 -0
  39. package/dist/operations/database-refusal.d.ts +51 -0
  40. package/dist/operations/database-refusal.js +45 -0
  41. package/dist/operations/operation-arguments.d.ts +102 -31
  42. package/dist/operations/operation-validator-core.js +145 -36
  43. package/dist/operations/validation-code.guidance.d.ts +6 -0
  44. package/dist/operations/validation-code.guidance.js +90 -0
  45. package/dist/operations/write-scope.resolver.d.ts +4 -0
  46. package/dist/operations/write-scope.resolver.js +2 -0
  47. package/dist/protocol/protocol.handler.js +55 -16
  48. package/dist/runtime/caller-data.copier.d.ts +17 -0
  49. package/dist/runtime/caller-data.copier.js +18 -1
  50. package/dist/runtime/caller-data.door.d.ts +11 -5
  51. package/dist/runtime/computed-read-runtime.js +15 -2
  52. package/dist/runtime/computed-value.error.d.ts +3 -2
  53. package/dist/runtime/computed-value.error.js +3 -2
  54. package/dist/runtime/create-requirement.d.ts +41 -5
  55. package/dist/runtime/create-requirement.js +137 -20
  56. package/dist/runtime/diagnostics-reporter.d.ts +2 -1
  57. package/dist/runtime/error-boundary.js +4 -0
  58. package/dist/runtime/error-guidance.d.ts +12 -0
  59. package/dist/runtime/error-guidance.js +1 -0
  60. package/dist/runtime/error.catalog.d.ts +49 -0
  61. package/dist/runtime/error.catalog.js +49 -0
  62. package/dist/runtime/field-stage.runtime.d.ts +2 -0
  63. package/dist/runtime/field-stage.runtime.js +8 -4
  64. package/dist/runtime/field-validator.runtime.d.ts +18 -1
  65. package/dist/runtime/field-validator.runtime.js +47 -8
  66. package/dist/runtime/json-value.d.ts +10 -5
  67. package/dist/runtime/level-lifecycle.runtime.d.ts +2 -2
  68. package/dist/runtime/level-lifecycle.runtime.js +2 -5
  69. package/dist/runtime/mutation-planner.js +3 -14
  70. package/dist/runtime/operation-code.guidance.d.ts +6 -0
  71. package/dist/runtime/operation-code.guidance.js +167 -0
  72. package/dist/runtime/operation-runtime.d.ts +20 -6
  73. package/dist/runtime/operation-runtime.js +82 -36
  74. package/dist/runtime/owned-execution.registry.d.ts +12 -2
  75. package/dist/runtime/pipe-output.validator.d.ts +10 -0
  76. package/dist/runtime/pipe-output.validator.js +4 -2
  77. package/dist/runtime/pipeline.d.ts +35 -8
  78. package/dist/runtime/readonly-value.d.ts +14 -3
  79. package/dist/runtime/readonly-value.js +10 -1
  80. package/dist/runtime/scalar-runtime.d.ts +5 -1
  81. package/dist/runtime/scalar-runtime.js +86 -16
  82. package/dist/runtime/unsent-value.normalizer.d.ts +15 -0
  83. package/dist/runtime/unsent-value.normalizer.js +128 -0
  84. package/dist/transactions/operation-fingerprint.js +2 -2
  85. package/dist/transactions/sql-statement.d.ts +8 -0
  86. package/dist/transactions/sql-statement.validator.js +36 -3
  87. package/dist/transactions/transaction-plan-assembler.d.ts +1 -1
  88. package/dist/transactions/transaction-plan-assembler.js +33 -10
  89. package/dist/transactions/transaction-plan-validator.d.ts +16 -0
  90. package/dist/transactions/transaction-plan-validator.js +27 -1
  91. package/dist/transactions/transaction-reference-validator.d.ts +42 -0
  92. package/dist/transactions/transaction-reference-validator.js +82 -56
  93. package/dist/transactions/transaction-reference.js +5 -0
  94. package/dist/transactions/transaction-runtime.js +47 -22
  95. package/dist/transactions/transaction-stage.runtime.d.ts +2 -1
  96. package/dist/transactions/transaction-stage.runtime.js +7 -4
  97. package/package.json +4 -3
@@ -1,4 +1,5 @@
1
1
  import { hasFieldValidators } from "../compiler/field-validator.compiler.js";
2
+ import { createComputedFields } from "../config/field-compute.resolver.js";
2
3
  import { fieldStagesOf, isForcedOperation, nestedTargetLevels, operationLevels, rawPipelineTree, transactionLevelsOf, } from "../config/pipeline-tree.resolver.js";
3
4
  import { AVENTARA_PROTOCOL_VERSION } from "../contracts/contract-protocol.js";
4
5
  import { findOperationAvailabilityIssue, validateOperationArguments, } from "../operations/operation-validator.js";
@@ -7,13 +8,14 @@ import { settleTransaction } from "../transactions/transaction-settlement.runtim
7
8
  import { TRANSACTION_PIPES, transactionFacts, } from "../transactions/transaction-stage.runtime.js";
8
9
  import { copyAtDoor, misuseIdentityOf, refuseCallerData, } from "./caller-data.door.js";
9
10
  import { materializeReadOperation, planReadOperation, } from "./computed-read-runtime.js";
11
+ import { AvComputedValueError } from "./computed-value.error.js";
10
12
  import { createContextExecutor, } from "./context-executor.runtime.js";
11
- import { createRequirementIssues, recordWriteFactsOf, } from "./create-requirement.js";
13
+ import { computedRequirementGapsOf, createRequirementIssues, preliminaryRecordIssues, recordWriteFactsOf, } from "./create-requirement.js";
12
14
  import { createDiagnosticsReporter, } from "./diagnostics-reporter.js";
13
15
  import { AdapterBoundary, OPERATION_ARGUMENTS_PATH, resolveOperationFailure, sanitizeOperationFailure, } from "./error-boundary.js";
14
16
  import { planFieldStages } from "./field-stage.plan.js";
15
17
  import { guardSentFields, pipeSentFields, valueAt, writeRecordPathsOf, } from "./field-stage.runtime.js";
16
- import { fieldValidatorIssues } from "./field-validator.runtime.js";
18
+ import { fieldValidatorIssues, hiddenFieldRefusal, } from "./field-validator.runtime.js";
17
19
  import { thrownFailure } from "./framework-error.factory.js";
18
20
  import { AvInterceptorNextError } from "./interceptor-next.error.js";
19
21
  import { runLevelLifecycle, } from "./level-lifecycle.runtime.js";
@@ -23,10 +25,11 @@ import { nestedWriteTargetsOf, } from "./nested-write.walker.js";
23
25
  import { internalFailure, operationFailure } from "./operation-response.js";
24
26
  import { mapOperationOutcome, mapValidationOutcome, uniqueMissFailure, } from "./outcome-mapper.js";
25
27
  import { AvOutputValidationError, validateOperationOutput, } from "./output-validator.js";
26
- import { refusePipeOutput, refuseStageOutput, } from "./pipe-output.validator.js";
28
+ import { AvPipeOutputError, PIPE_OUTPUT_FAILURE_MESSAGE, refusePipeOutput, refuseStageOutput, } from "./pipe-output.validator.js";
27
29
  import { deepFreeze, frozenCopy } from "./readonly-value.js";
28
30
  import { requestStateOf } from "./request-state.resolver.js";
29
31
  import { normalizeOperationScalarValues, } from "./scalar-runtime.js";
32
+ import { withoutUnsentWriteValues } from "./unsent-value.normalizer.js";
30
33
  function scopeOf(request, binding) {
31
34
  return {
32
35
  requestId: request.requestId,
@@ -39,7 +42,12 @@ export function withRequestState(request) {
39
42
  }
40
43
  export function createPipelineDependencies(dependencies, model) {
41
44
  const misuse = createMisuseReporter(dependencies.config.misuse);
42
- const recordWrites = recordWriteFactsOf(dependencies.adapter);
45
+ const recordWrites = recordWriteFactsOf({
46
+ ...(dependencies.adapter.referentialActions === undefined
47
+ ? {}
48
+ : { referentialActions: dependencies.adapter.referentialActions }),
49
+ model,
50
+ });
43
51
  const contexts = createContextExecutor({
44
52
  model,
45
53
  adapter: dependencies.adapter,
@@ -121,7 +129,7 @@ function operationFacts(contract, request, args) {
121
129
  family: request.family,
122
130
  variant: request.variant,
123
131
  },
124
- $args: args,
132
+ $args: frozenCopy(args),
125
133
  $origin: request.origin,
126
134
  $requestId: request.requestId,
127
135
  $contract: contract,
@@ -177,15 +185,16 @@ export function createOperationRuntime(dependencies, pipelineDependencies) {
177
185
  return validated.response;
178
186
  }
179
187
  const stateful = withRequestState(request);
188
+ const forced = isForcedOperation(activeScopeConfig(dependencies.config, request).pipelines, request);
180
189
  const admitted = await admitOperation(pipelineDependencies, stateful, validated.contract, validated.arguments, OPERATION_ARGUMENTS_PATH, report, {
181
190
  requestId: request.requestId,
182
191
  transaction: undefined,
183
192
  state: stateful.state,
184
- });
193
+ }, forced ? FORCED_CALL : SINGLE_CALL);
185
194
  if (!admitted.ok) {
186
195
  return admitted.response;
187
196
  }
188
- if (isForcedOperation(activeScopeConfig(dependencies.config, request).pipelines, request)) {
197
+ if (forced) {
189
198
  return executeForcedOperation(pipelineDependencies, dependencies.adapter, stateful, validated.contract, admitted.args, report);
190
199
  }
191
200
  return executeAdmittedOperation(pipelineDependencies, stateful, validated.contract, admitted.args, OPERATION_ARGUMENTS_PATH, { kind: "standalone", executor: dependencies.adapter }, report);
@@ -198,9 +207,11 @@ function validateOperation(dependencies, request, source, misuse) {
198
207
  }
199
208
  const contract = activeContract(dependencies.contracts, request);
200
209
  let owned = request;
201
- if (source === "caller" &&
210
+ if (source !== "owned" &&
202
211
  findOperationAvailabilityIssue(contract, request) === undefined) {
203
- const copy = copyAtDoor(request.arguments);
212
+ const copy = source === "caller"
213
+ ? copyAtDoor(request.arguments)
214
+ : { ok: false, error: source.refused };
204
215
  if (!copy.ok) {
205
216
  return rejected(mapValidationOutcome([
206
217
  refuseCallerData(copy.error, OPERATION_ARGUMENTS_PATH, misuseIdentityOf(request), misuse),
@@ -235,6 +246,12 @@ export function protocolIdentityFailure(contracts, protocol, origin) {
235
246
  return undefined;
236
247
  }
237
248
  export function validateOperationRequest(contract, request, options, write = "copy") {
249
+ if (findOperationAvailabilityIssue(contract, request) === undefined) {
250
+ const sent = withoutUnsentWriteValues(contract, request, request.arguments);
251
+ if (sent !== request.arguments) {
252
+ request = { ...request, arguments: sent };
253
+ }
254
+ }
238
255
  const validation = validateOperationArguments(contract, request, options);
239
256
  if (!validation.ok) {
240
257
  return { ok: false, response: mapValidationOutcome(validation.issues) };
@@ -251,12 +268,13 @@ function rejected(response) {
251
268
  return { ok: false, response };
252
269
  }
253
270
  const FIELD_PIPES = "Field pipes";
254
- const STEP_PIPES = "Pipes";
271
+ export const STEP_PIPES = "Pipes";
255
272
  export const EVERY_FIELD = () => true;
256
273
  function filtersOf(levels) {
257
274
  return levels.flatMap((level) => level.filters);
258
275
  }
259
276
  const SINGLE_CALL = { select: EVERY_FIELD };
277
+ const FORCED_CALL = { select: EVERY_FIELD, beforeBegin: true };
260
278
  export async function admitOperation(dependencies, request, contract, args, argumentsPath, report, scope, admission = SINGLE_CALL) {
261
279
  const contexts = dependencies.contexts;
262
280
  const tree = activeScopeConfig(dependencies.config, request).pipelines;
@@ -299,8 +317,10 @@ export async function admitOperation(dependencies, request, contract, args, argu
299
317
  let pipeIndex = 0;
300
318
  for (const level of levels) {
301
319
  for (const pipe of level.pipes) {
302
- const returned = await pipe(current, context());
303
- if (returned !== undefined && returned !== current) {
320
+ const told = frozenCopy(current);
321
+ const output = await pipe(told, context());
322
+ if (output !== undefined && output !== told && output !== current) {
323
+ const returned = withoutUnsentWriteValues(dependencies.contracts.core, request, output);
304
324
  const refused = refusePipeOutput(contracts, request, returned, pipe, pipeIndex, admission.validation);
305
325
  if (refused !== undefined) {
306
326
  report(refused.error, "A3004");
@@ -322,6 +342,10 @@ export async function admitOperation(dependencies, request, contract, args, argu
322
342
  piped: piped || fields.piped,
323
343
  };
324
344
  };
345
+ const absent = preliminaryRecordIssues(contract, dependencies.recordWrites, request, current, argumentsPath);
346
+ if (absent.length > 0) {
347
+ return { ok: false, response: mapValidationOutcome(absent) };
348
+ }
325
349
  try {
326
350
  return await admit();
327
351
  }
@@ -333,10 +357,12 @@ export async function admitOperation(dependencies, request, contract, args, argu
333
357
  }
334
358
  }
335
359
  async function runFieldInputStages(dependencies, request, contract, args, argumentsPath, report, admission) {
336
- const tree = activeScopeConfig(dependencies.config, request).pipelines;
360
+ const scopeConfig = activeScopeConfig(dependencies.config, request);
361
+ const tree = scopeConfig.pipelines;
337
362
  const planned = dependencies.fieldStages[request.origin];
338
363
  const pipes = planned.anyPipes;
339
- if (!pipes && !hasFieldValidators(contract)) {
364
+ const judge = admission.beforeBegin === true && hasFieldValidators(contract);
365
+ if (!pipes && !judge) {
340
366
  return { ok: true, args, piped: false };
341
367
  }
342
368
  let current = args;
@@ -364,14 +390,25 @@ async function runFieldInputStages(dependencies, request, contract, args, argume
364
390
  }
365
391
  }
366
392
  }
367
- const broken = fieldValidatorIssues(contract, dependencies.recordWrites.references, request, current, argumentsPath, admission.select);
368
- if (broken.length > 0) {
369
- return { ok: false, response: mapValidationOutcome(broken) };
393
+ if (judge) {
394
+ const compute = scopeConfig.compute;
395
+ const broken = fieldValidatorIssues(contract, dependencies.recordWrites.references, request, current, argumentsPath, admission.select, (resource, stage, field) => {
396
+ const fields = compute !== undefined && Object.hasOwn(compute, resource)
397
+ ? compute[resource]
398
+ : undefined;
399
+ const phases = fields !== undefined && Object.hasOwn(fields, field)
400
+ ? fields[field]
401
+ : undefined;
402
+ return phases?.[stage] !== undefined;
403
+ });
404
+ if (broken.length > 0) {
405
+ return { ok: false, response: mapValidationOutcome(broken) };
406
+ }
370
407
  }
371
408
  return { ok: true, args: current, piped: changed };
372
409
  }
373
410
  export async function prepareStep(dependencies, request, contract, args, argumentsPath, report, executor, changes, outer) {
374
- let current = deepFreeze(args);
411
+ let current = deepFreeze(withoutUnsentWriteValues(dependencies.contracts.core, request, args));
375
412
  const scope = {
376
413
  requestId: request.requestId,
377
414
  transaction: executor,
@@ -395,12 +432,6 @@ export async function prepareStep(dependencies, request, contract, args, argumen
395
432
  return { ok: false, response: refused.response };
396
433
  }
397
434
  }
398
- if (changes.transactionPiped) {
399
- const broken = fieldValidatorIssues(contract, dependencies.recordWrites.references, request, current, argumentsPath, EVERY_FIELD);
400
- if (broken.length > 0) {
401
- return { ok: false, response: mapValidationOutcome(broken) };
402
- }
403
- }
404
435
  return { ok: true, args: deepFreeze(current), piped: resolved.piped };
405
436
  }
406
437
  catch (error) {
@@ -465,15 +496,8 @@ export async function executeAdmittedOperation(dependencies, request, contract,
465
496
  const adapterBoundary = new AdapterBoundary();
466
497
  const contracts = { core: dependencies.contracts.core, active: contract };
467
498
  let args = deepFreeze(admittedArguments);
468
- let told;
469
- const view = () => {
470
- if (told === undefined || told.of !== args) {
471
- told = { of: args, view: frozenCopy(args) };
472
- }
473
- return told.view;
474
- };
475
- const context = () => contexts.operation(scope, operationFacts(contract, request, view()));
476
- const computeContext = () => contexts.compute(scope, operationFacts(contract, request, view()));
499
+ const context = () => contexts.operation(scope, operationFacts(contract, request, args));
500
+ const computeContext = () => contexts.compute(scope, operationFacts(contract, request, args));
477
501
  const reuse = { refusal: undefined };
478
502
  const refusedReuse = (refusal) => {
479
503
  report(refusal, "A3000");
@@ -493,9 +517,31 @@ export async function executeAdmittedOperation(dependencies, request, contract,
493
517
  async function runLifecycle() {
494
518
  let computed = await applyWriteCompute(contracts, scopeConfig.compute, request, args, computeContext);
495
519
  computed = await applyNestedWriteCompute(contracts, scopeConfig.compute, computed, nestedWriteTargetsOf(contract, dependencies.recordWrites.references, request, computed), (target, payload) => contexts.compute(scope, nestedFacts(contract, request, target, payload)));
496
- const missing = createRequirementIssues(contract, dependencies.recordWrites, request, computed, argumentsPath);
497
- if (missing.length > 0) {
498
- return mapValidationOutcome(missing);
520
+ const broken = createRequirementIssues(contract, dependencies.recordWrites, request, computed, argumentsPath);
521
+ const [firstBroken] = broken;
522
+ if (firstBroken !== undefined) {
523
+ const what = firstBroken.code === "V1000"
524
+ ? "dropped a required field"
525
+ : "wrote a reference twice";
526
+ report(new AvPipeOutputError(`Pipes or write compute ${what} of ${request.resource}.${request.family}.${request.variant} at "${(firstBroken.path ?? []).join(".")}" (${firstBroken.code}: ${firstBroken.message})`, broken), "A3004");
527
+ return operationFailure("A3004", {
528
+ message: PIPE_OUTPUT_FAILURE_MESSAGE,
529
+ });
530
+ }
531
+ const unfilled = computedRequirementGapsOf(contract, dependencies.recordWrites, request, computed, argumentsPath, (resource) => createComputedFields(scopeConfig.compute, resource));
532
+ const [firstUnfilled] = unfilled;
533
+ if (firstUnfilled !== undefined) {
534
+ throw new AvComputedValueError(firstUnfilled.resource, firstUnfilled.field, "create", unfilled.map((gap) => gap.issue), `compute.create on ${firstUnfilled.resource}.${firstUnfilled.field} returned undefined for a required field`);
535
+ }
536
+ if (hasFieldValidators(contract)) {
537
+ const refused = fieldValidatorIssues(contract, dependencies.recordWrites.references, request, computed, argumentsPath, EVERY_FIELD);
538
+ if (refused.length > 0) {
539
+ return mapValidationOutcome(refused);
540
+ }
541
+ }
542
+ const hiddenRefusal = hiddenFieldRefusal({ core: dependencies.contracts.core, active: contract }, dependencies.recordWrites.references, request, computed, argumentsPath);
543
+ if (hiddenRefusal !== undefined) {
544
+ throw hiddenRefusal;
499
545
  }
500
546
  args = deepFreeze(computed);
501
547
  let readMaterialized = false;
@@ -1,11 +1,21 @@
1
1
  import type { AgentTransactionRequest, ClientTransactionRequest } from "../transactions/transaction-request.js";
2
2
  import type { AvTransactionResponse } from "../transactions/transaction-response.js";
3
+ import type { AvCallerDataError } from "./caller-data.copier.js";
3
4
  import type { RemoteOperationRequest } from "./operation-request.js";
4
5
  import type { AvOperationResponse } from "./operation-response.js";
5
6
  /** The two entries that run a request the protocol decoded itself, uncopied. */
6
7
  export type OwnedExecution = {
7
- readonly execute: (request: RemoteOperationRequest) => Promise<AvOperationResponse>;
8
- readonly executeTransaction: (request: ClientTransactionRequest | AgentTransactionRequest) => Promise<AvTransactionResponse>;
8
+ /**
9
+ * Runs `request` uncopied; with `refused`, its arguments are a host's parsed body
10
+ * whose copy at the call failed, answered as the door answers that failure.
11
+ */
12
+ readonly execute: (request: RemoteOperationRequest, refused?: AvCallerDataError) => Promise<AvOperationResponse>;
13
+ /**
14
+ * Runs `request` uncopied; with `refused`, its plan is a host's parsed plan taken
15
+ * at the call, and these steps' arguments could not be copied there: each is
16
+ * answered at its turn as the door answers that failure.
17
+ */
18
+ readonly executeTransaction: (request: ClientTransactionRequest | AgentTransactionRequest, refused?: ReadonlyMap<number, AvCallerDataError>) => Promise<AvTransactionResponse>;
9
19
  };
10
20
  /** Records `framework`'s owned entries; called once, at construction. */
11
21
  export declare function recordOwnedExecution(framework: object, execution: OwnedExecution): void;
@@ -24,6 +24,8 @@ import type { AvOperationFailureResponse } from "./operation-response.js";
24
24
  */
25
25
  /** The one text every `A3004` carries on the wire. */
26
26
  export declare const PIPE_OUTPUT_FAILURE_MESSAGE = "A server pipeline produced invalid arguments.";
27
+ /** The Contract a stage's output is held to: Core's Resources plus what `active` adds. */
28
+ export declare function serverContractFor(core: AvContractCore, active: AvContract): AvContract;
27
29
  /** The error the diagnostics channel receives for a refused pipe output. */
28
30
  export declare class AvPipeOutputError extends Error {
29
31
  readonly issues: readonly AvValidationIssue[];
@@ -61,3 +63,11 @@ export declare function refuseStageOutput(contracts: {
61
63
  }, request: AvOperationIdentity & {
62
64
  readonly resource: string;
63
65
  }, args: unknown, subject: string, options?: OperationValidationOptions): RefusedStageOutput | undefined;
66
+ /**
67
+ * The `A3004` for a stage output the server's rules raised `issues` against
68
+ * (paths under `["arguments", …]`), the first named in the diagnostic;
69
+ * `undefined` when there are none.
70
+ */
71
+ export declare function refusedStageOutput(request: AvOperationIdentity & {
72
+ readonly resource: string;
73
+ }, issues: readonly AvValidationIssue[], subject: string): RefusedStageOutput | undefined;
@@ -19,7 +19,7 @@ function serverContract(core, active) {
19
19
  return { ...core, resources };
20
20
  }
21
21
  const SERVER_VIEWS = new WeakMap();
22
- function serverContractFor(core, active) {
22
+ export function serverContractFor(core, active) {
23
23
  let view = SERVER_VIEWS.get(active);
24
24
  if (view === undefined) {
25
25
  view = serverContract(core, active);
@@ -64,7 +64,9 @@ export function refusePipeOutput(contracts, request, args, pipe, index, options)
64
64
  return refuseStageOutput(contracts, request, args, `Pipe ${pipeLabel(pipe, index)}`, options);
65
65
  }
66
66
  export function refuseStageOutput(contracts, request, args, subject, options) {
67
- const issues = serverIssues(serverContractFor(contracts.core, contracts.active), request, args, options);
67
+ return refusedStageOutput(request, serverIssues(serverContractFor(contracts.core, contracts.active), request, args, options), subject);
68
+ }
69
+ export function refusedStageOutput(request, issues, subject) {
68
70
  const [first] = issues;
69
71
  if (first === undefined) {
70
72
  return undefined;
@@ -1,16 +1,17 @@
1
1
  import type { AvAdapterModel } from "../adapters/adapter-model.js";
2
2
  import type { AvAdapterSqlValue } from "../adapters/adapter-sql-statement.js";
3
+ import type { AvPreComputeLift } from "../config/computed-fields.js";
3
4
  import type { AvRequestOrigin } from "../config/config-layer.js";
4
5
  import type { AvConfigResourceKey } from "../config/framework-field-definition.js";
5
6
  import type { AvContract } from "../contracts/contract.js";
6
7
  import type { AvApplicationScalarForms } from "../contracts/scalar-value-type.js";
7
- import type { AvContractApplicationView } from "../framework/contract-view.js";
8
+ import type { AvContractApplicationView, RequirementsLessComputed } from "../framework/contract-view.js";
8
9
  import type { AvContextOperations } from "../framework/operation-facade.js";
9
10
  import type { AvOperationArgumentsFor, AvResourceKey } from "../operations/operation-arguments.js";
10
11
  import type { AvOperationFamily, AvOperationIdentity } from "../operations/operation-identity.js";
11
12
  import type { AvTransactionReference } from "../transactions/transaction-reference.js";
12
13
  import type { AvError } from "./framework-error.js";
13
- import type { AvDeepReadonly, AvDeepReadonlyLeaf } from "./readonly-value.js";
14
+ import type { ArrayForm, AvDeepReadonly, AvDeepReadonlyLeaf } from "./readonly-value.js";
14
15
  import type { AvState } from "./request-state.js";
15
16
  /**
16
17
  * What a pipeline is told about the HTTP request a client-origin operation arrived on.
@@ -50,10 +51,32 @@ export type AvContextSql = <Row = unknown>(strings: TemplateStringsArray, ...val
50
51
  * position admitting an unresolved `$ref`. `unknown` for a model whose Resource
51
52
  * names are not literal.
52
53
  */
53
- export type AvStageArguments<M extends AvAdapterModel, R extends string, F extends AvOperationFamily, V extends string> = string extends R ? unknown : R extends AvResourceKey<AvContractApplicationView<M>> ? AvOperationArgumentsFor<AvContractApplicationView<M>, R, Extract<AvOperationIdentity, {
54
+ export type AvStageArguments<M extends AvAdapterModel, R extends string, F extends AvOperationFamily, V extends string> = StageArgumentsOf<AvContractApplicationView<M>, R, F, V>;
55
+ /** The arguments of operation `F`.`V` on `R` under the Contract view `C`. */
56
+ type StageArgumentsOf<C extends AvContract, R extends string, F extends AvOperationFamily, V extends string> = string extends R ? unknown : R extends AvResourceKey<C> ? AvOperationArgumentsFor<C, R, Extract<AvOperationIdentity, {
54
57
  readonly family: F;
55
58
  readonly variant: V;
56
59
  }>, AvApplicationScalarForms, AvApplicationScalarForms> : unknown;
60
+ /**
61
+ * The arguments a variant's guards, pipes and filters are told: the operation's
62
+ * arguments before write compute has run, so a create need not hold a field
63
+ * `compute.create` fills (`Lift`, as `"<Resource>.<field>"`; `string`: any).
64
+ */
65
+ export type AvPreComputeStageArguments<M extends AvAdapterModel, R extends string, F extends AvOperationFamily, V extends string, Lift extends string = AvPreComputeLift<M, "root">> = F extends "find" | "delete" ? AvStageArguments<M, R, F, V> : [Lift] extends [never] ? AvStageArguments<M, R, F, V> : StageArgumentsOf<PreComputeView<AvContractApplicationView<M>, Lift>, R, F, V>;
66
+ /** A Contract view with the create requirements `Lift` names lifted. */
67
+ type PreComputeView<C, Lift extends string> = Extract<{
68
+ readonly [P in keyof C]: P extends "resources" ? string extends Lift ? {
69
+ readonly [R in keyof C[P]]: Omit<C[P][R], "createRequirements">;
70
+ } : {
71
+ readonly [R in keyof C[P]]: LiftedResource<C[P][R], LiftedFieldOf<Lift, R>>;
72
+ } : C[P];
73
+ }, AvContract>;
74
+ /** The fields of Resource `R` that `Lift` names. */
75
+ type LiftedFieldOf<Lift extends string, R> = Lift extends `${R & string}.${infer Field}` ? Field : never;
76
+ /** One Resource with the create requirements on `Fields` lifted; itself when none. */
77
+ type LiftedResource<Resource, Fields> = [Fields] extends [never] ? Resource : {
78
+ readonly [P in keyof Resource]: P extends "createRequirements" ? RequirementsLessComputed<Exclude<Resource[P], undefined>, Fields> : Resource[P];
79
+ };
57
80
  /**
58
81
  * What a pipeline callback is told about the operation it is wrapping, and the
59
82
  * framework's executor: `ctx.<Resource>.<family>.<variant>(args)`, `ctx.$sql` and
@@ -83,8 +106,9 @@ export type AvContext<M extends AvAdapterModel = AvAdapterModel, R extends strin
83
106
  * returned them in a pipe, and the validated arguments the operation executes in
84
107
  * an interceptor, a hook or a filter. Only a pipe changes them, by returning new
85
108
  * ones; writing to them throws a `TypeError`. At a variant, typed as that
86
- * operation's arguments ({@link AvStageArguments}), deep read-only; above it,
87
- * `unknown`.
109
+ * operation's arguments ({@link AvStageArguments}), deep read-only; in a guard or
110
+ * a filter a create may lack the fields `compute.create` fills ({@link
111
+ * AvPreComputeStageArguments}: write compute has not run); above it, `unknown`.
88
112
  */
89
113
  readonly $args: AvDeepReadonly<A>;
90
114
  readonly $sql: AvContextSql;
@@ -103,8 +127,9 @@ export type AvGuard<M extends AvAdapterModel = AvAdapterModel, R extends string
103
127
  * Transforms the arguments before the validators judge them: the only stage
104
128
  * that changes them. `args` is deep-frozen, so a pipe returns new arguments;
105
129
  * returning `undefined` keeps them as they are. At a variant, `args` and the
106
- * return are typed as that operation's arguments ({@link AvStageArguments}),
107
- * `args` deep read-only.
130
+ * return are typed as that operation's arguments before write compute runs
131
+ * ({@link AvPreComputeStageArguments}: a create may lack the fields
132
+ * `compute.create` fills), `args` deep read-only.
108
133
  */
109
134
  export type AvPipe<M extends AvAdapterModel = AvAdapterModel, R extends string = AvConfigResourceKey<M>, F extends AvOperationFamily = AvOperationFamily, V extends string = string, A = unknown> = {
110
135
  pipe(args: PipeArguments<A>, context: AvContext<M, R, F, V, A>): PipeResult<PipeArguments<A>>;
@@ -113,7 +138,9 @@ export type AvPipe<M extends AvAdapterModel = AvAdapterModel, R extends string =
113
138
  * A variant pipe's arguments and return: the operation's arguments, deep
114
139
  * read-only, each member closed to inference.
115
140
  */
116
- type PipeArguments<T> = T extends AvDeepReadonlyLeaf | AvTransactionReference ? T : T extends object ? {
141
+ type PipeArguments<T> = T extends AvDeepReadonlyLeaf | AvTransactionReference ? T : T extends readonly unknown[] ? ArrayForm<T> extends "array" ? readonly NoInfer<PipeArguments<Exclude<T[number], undefined>>>[] : {
142
+ readonly [K in keyof T]: NoInfer<PipeArguments<Exclude<T[K], undefined>>>;
143
+ } : T extends object ? {
117
144
  readonly [K in keyof T]: NoInfer<PipeArguments<Exclude<T[K], undefined>>>;
118
145
  } : T;
119
146
  /** What a pipe returns: arguments of type `A`, or `undefined` to keep them. */
@@ -1,12 +1,23 @@
1
1
  import { AvDecimal } from "./decimal.js";
2
2
  /** A value `AvDeepReadonly` leaves as it is: a primitive, a function, or a scalar instance. */
3
3
  export type AvDeepReadonlyLeaf = string | number | boolean | bigint | symbol | null | undefined | Date | Uint8Array | AvDecimal | ((...args: never[]) => unknown);
4
+ /**
5
+ * Whether array type `T` is an open array (every length: `"array"`) or a tuple
6
+ * (`"tuple"`). A tuple whose only fixed positions are optional ones before a rest
7
+ * element (`[a?, ...b[]]`) has every length too and reads as an array.
8
+ */
9
+ export type ArrayForm<T extends readonly unknown[]> = T extends readonly [] | readonly [unknown, ...unknown[]] | readonly [...unknown[], unknown] ? "tuple" : T extends {
10
+ readonly length: infer Length;
11
+ } ? number extends Length ? "array" : "tuple" : "tuple";
4
12
  /**
5
13
  * `T` with every property, element and nested property read-only: what a stage
6
- * that only reads is told. Arrays become read-only arrays; a `Date`, a
7
- * `Uint8Array`, an `AvDecimal` and functions are left as they are.
14
+ * that only reads is told. Arrays become read-only arrays, tuples read-only
15
+ * tuples; a `Date`, a `Uint8Array`, an `AvDecimal` and functions are left as
16
+ * they are.
8
17
  */
9
- export type AvDeepReadonly<T> = T extends AvDeepReadonlyLeaf ? T : T extends object ? {
18
+ export type AvDeepReadonly<T> = T extends AvDeepReadonlyLeaf ? T : T extends readonly unknown[] ? ArrayForm<T> extends "array" ? readonly AvDeepReadonly<T[number]>[] : {
19
+ readonly [K in keyof T]: AvDeepReadonly<T[K]>;
20
+ } : T extends object ? {
10
21
  readonly [K in keyof T]: AvDeepReadonly<T[K]>;
11
22
  } : T;
12
23
  /**
@@ -36,20 +36,25 @@ function scalarCopy(value) {
36
36
  }
37
37
  return undefined;
38
38
  }
39
+ const SCALAR_FREE = new WeakSet();
39
40
  export function frozenCopy(value) {
40
41
  const copies = new Map();
42
+ const walking = new Set();
43
+ let cycled = false;
41
44
  const copy = (current) => {
42
45
  const scalar = scalarCopy(current);
43
46
  if (scalar !== undefined) {
44
47
  return scalar;
45
48
  }
46
- if (!isContainer(current)) {
49
+ if (!isContainer(current) || SCALAR_FREE.has(current)) {
47
50
  return current;
48
51
  }
49
52
  if (copies.has(current)) {
53
+ cycled ||= walking.has(current);
50
54
  return copies.get(current);
51
55
  }
52
56
  copies.set(current, current);
57
+ walking.add(current);
53
58
  const entries = [];
54
59
  let changed = !DEEP_FROZEN.has(current);
55
60
  for (const key of Reflect.ownKeys(current)) {
@@ -63,8 +68,12 @@ export function frozenCopy(value) {
63
68
  changed ||= next !== descriptor.value;
64
69
  entries.push([key, descriptor, next]);
65
70
  }
71
+ walking.delete(current);
66
72
  if (!changed) {
67
73
  copies.set(current, current);
74
+ if (!cycled) {
75
+ SCALAR_FREE.add(current);
76
+ }
68
77
  return current;
69
78
  }
70
79
  const target = Array.isArray(current)
@@ -1,5 +1,5 @@
1
1
  import type { AvRequestOrigin } from "../config/config-layer.js";
2
- import type { AvScalarFilterOperator } from "../contracts/capability.js";
2
+ import { type AvScalarFilterOperator } from "../contracts/capability.js";
3
3
  import type { AvContract } from "../contracts/contract.js";
4
4
  import type { AvScalarField } from "../contracts/field.js";
5
5
  import type { AvOperationIdentity } from "../operations/operation-identity.js";
@@ -31,6 +31,10 @@ type Path = readonly (string | number)[];
31
31
  * one reader and a value to another.
32
32
  */
33
33
  export declare function isPlainRecord(value: unknown): value is Readonly<Record<string, unknown>>;
34
+ /** What a refused `undefined` the request reads with is told. */
35
+ export declare const UNSENT_READ_VALUE_MESSAGE = "Value must not be undefined: it would be left out, and the request would reach more rows.";
36
+ /** The `V1001` of an `undefined` the request reads with. */
37
+ export declare function unsentReadValueIssue(path: Path): AvValidationIssue;
34
38
  /**
35
39
  * An operation argument's JSON value is bounded by `maxJsonDepth`, which the
36
40
  * structural walk enforces before this runs; a `$sql` step's parameter is not