@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.
- package/CHANGELOG.md +341 -1
- package/ERRORS.md +142 -0
- package/README.md +24 -1
- package/dist/adapters/adapter-discovery-diagnostic.guidance.d.ts +6 -0
- package/dist/adapters/adapter-discovery-diagnostic.guidance.js +7 -0
- package/dist/adapters/adapter-field.d.ts +14 -0
- package/dist/adapters/adapter-model.d.ts +9 -0
- package/dist/adapters/adapter.interface.d.ts +10 -0
- package/dist/compiler/contract-compiler-diagnostic.guidance.d.ts +6 -0
- package/dist/compiler/contract-compiler-diagnostic.guidance.js +97 -0
- package/dist/compiler/contract.compiler.js +94 -6
- package/dist/config/computed-fields.d.ts +62 -0
- package/dist/config/computed-fields.js +80 -0
- package/dist/config/config-diagnostic.guidance.d.ts +6 -0
- package/dist/config/config-diagnostic.guidance.js +32 -0
- package/dist/config/define-resource.d.ts +1 -1
- package/dist/config/exact-framework-config.d.ts +50 -20
- package/dist/config/field-compute.resolver.d.ts +2 -0
- package/dist/config/field-compute.resolver.js +8 -0
- package/dist/config/framework-config.d.ts +7 -6
- package/dist/config/framework-resources-config.d.ts +18 -12
- package/dist/config/pipeline-config.d.ts +4 -4
- package/dist/contracts/capability.d.ts +10 -1
- package/dist/contracts/capability.js +2 -0
- package/dist/contracts/client-contract-structure.validator.d.ts +65 -35
- package/dist/contracts/client-contract-structure.validator.js +4 -2
- package/dist/contracts/contract-canonicalizer.d.ts +7 -0
- package/dist/contracts/contract-canonicalizer.js +18 -4
- package/dist/contracts/field.d.ts +30 -1
- package/dist/framework/contract-view.d.ts +4 -4
- package/dist/framework/framework-construction.error.d.ts +12 -3
- package/dist/framework/framework-construction.error.js +5 -1
- package/dist/framework/framework.factory.d.ts +1 -1
- package/dist/framework/framework.factory.js +9 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/internal.d.ts +2 -0
- package/dist/internal.js +1 -0
- package/dist/operations/database-refusal.d.ts +51 -0
- package/dist/operations/database-refusal.js +45 -0
- package/dist/operations/operation-arguments.d.ts +102 -31
- package/dist/operations/operation-validator-core.js +145 -36
- package/dist/operations/validation-code.guidance.d.ts +6 -0
- package/dist/operations/validation-code.guidance.js +90 -0
- package/dist/operations/write-scope.resolver.d.ts +4 -0
- package/dist/operations/write-scope.resolver.js +2 -0
- package/dist/protocol/protocol.handler.js +55 -16
- package/dist/runtime/caller-data.copier.d.ts +17 -0
- package/dist/runtime/caller-data.copier.js +18 -1
- package/dist/runtime/caller-data.door.d.ts +11 -5
- package/dist/runtime/computed-read-runtime.js +15 -2
- package/dist/runtime/computed-value.error.d.ts +3 -2
- package/dist/runtime/computed-value.error.js +3 -2
- package/dist/runtime/create-requirement.d.ts +41 -5
- package/dist/runtime/create-requirement.js +137 -20
- package/dist/runtime/diagnostics-reporter.d.ts +2 -1
- package/dist/runtime/error-boundary.js +4 -0
- package/dist/runtime/error-guidance.d.ts +12 -0
- package/dist/runtime/error-guidance.js +1 -0
- package/dist/runtime/error.catalog.d.ts +49 -0
- package/dist/runtime/error.catalog.js +49 -0
- package/dist/runtime/field-stage.runtime.d.ts +2 -0
- package/dist/runtime/field-stage.runtime.js +8 -4
- package/dist/runtime/field-validator.runtime.d.ts +18 -1
- package/dist/runtime/field-validator.runtime.js +47 -8
- package/dist/runtime/json-value.d.ts +10 -5
- package/dist/runtime/level-lifecycle.runtime.d.ts +2 -2
- package/dist/runtime/level-lifecycle.runtime.js +2 -5
- package/dist/runtime/mutation-planner.js +3 -14
- package/dist/runtime/operation-code.guidance.d.ts +6 -0
- package/dist/runtime/operation-code.guidance.js +167 -0
- package/dist/runtime/operation-runtime.d.ts +20 -6
- package/dist/runtime/operation-runtime.js +82 -36
- package/dist/runtime/owned-execution.registry.d.ts +12 -2
- package/dist/runtime/pipe-output.validator.d.ts +10 -0
- package/dist/runtime/pipe-output.validator.js +4 -2
- package/dist/runtime/pipeline.d.ts +35 -8
- package/dist/runtime/readonly-value.d.ts +14 -3
- package/dist/runtime/readonly-value.js +10 -1
- package/dist/runtime/scalar-runtime.d.ts +5 -1
- package/dist/runtime/scalar-runtime.js +86 -16
- package/dist/runtime/unsent-value.normalizer.d.ts +15 -0
- package/dist/runtime/unsent-value.normalizer.js +128 -0
- package/dist/transactions/operation-fingerprint.js +2 -2
- package/dist/transactions/sql-statement.d.ts +8 -0
- package/dist/transactions/sql-statement.validator.js +36 -3
- package/dist/transactions/transaction-plan-assembler.d.ts +1 -1
- package/dist/transactions/transaction-plan-assembler.js +33 -10
- package/dist/transactions/transaction-plan-validator.d.ts +16 -0
- package/dist/transactions/transaction-plan-validator.js +27 -1
- package/dist/transactions/transaction-reference-validator.d.ts +42 -0
- package/dist/transactions/transaction-reference-validator.js +82 -56
- package/dist/transactions/transaction-reference.js +5 -0
- package/dist/transactions/transaction-runtime.js +47 -22
- package/dist/transactions/transaction-stage.runtime.d.ts +2 -1
- package/dist/transactions/transaction-stage.runtime.js +7 -4
- 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(
|
|
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 (
|
|
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
|
|
210
|
+
if (source !== "owned" &&
|
|
202
211
|
findOperationAvailabilityIssue(contract, request) === undefined) {
|
|
203
|
-
const copy =
|
|
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
|
|
303
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
-
|
|
469
|
-
const
|
|
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
|
|
497
|
-
|
|
498
|
-
|
|
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
|
-
|
|
8
|
-
|
|
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
|
-
|
|
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> =
|
|
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;
|
|
87
|
-
* `
|
|
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
|
|
107
|
-
*
|
|
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
|
|
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
|
|
7
|
-
* `Uint8Array`, an `AvDecimal` and functions are left as
|
|
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
|
|
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
|
|
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
|