@aventara/core 0.1.0-pilot.3 → 1.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +338 -0
- package/README.md +100 -3
- package/dist/adapters/adapter-discovery-diagnostic.d.ts +2 -0
- package/dist/adapters/adapter-field.d.ts +3 -26
- package/dist/adapters/adapter-identifier.d.ts +1 -11
- package/dist/adapters/adapter-model.d.ts +12 -0
- package/dist/adapters/adapter-operation-executor.interface.d.ts +5 -1
- package/dist/adapters/adapter-resource.d.ts +7 -28
- package/dist/adapters/adapter.interface.d.ts +25 -26
- package/dist/compiler/contract-compiler-diagnostic.d.ts +8 -0
- package/dist/compiler/contract.compiler.d.ts +11 -22
- package/dist/compiler/contract.compiler.js +67 -0
- package/dist/config/any-framework-config.d.ts +5 -26
- package/dist/config/config-diagnostic.d.ts +2 -0
- package/dist/config/config-layer.d.ts +6 -19
- package/dist/config/config.resolver.d.ts +9 -14
- package/dist/config/config.resolver.js +1 -0
- package/dist/config/config.validator.d.ts +6 -3
- package/dist/config/config.validator.js +6 -0
- package/dist/config/define-fields.d.ts +15 -48
- package/dist/config/effective-fields.d.ts +6 -29
- package/dist/config/entrypoint.normalizer.d.ts +6 -18
- package/dist/config/framework-behaviors-config.d.ts +11 -10
- package/dist/config/framework-config.d.ts +19 -16
- package/dist/config/framework-fields-config.d.ts +20 -7
- package/dist/config/framework-restrictions-config.d.ts +41 -61
- package/dist/config/pipeline-config.d.ts +7 -17
- package/dist/contracts/capability.d.ts +13 -49
- package/dist/contracts/client-contract-structure.validator.d.ts +28 -54
- package/dist/contracts/client-contract-structure.validator.js +99 -2
- package/dist/contracts/contract-canonicalizer.d.ts +4 -6
- package/dist/contracts/contract-canonicalizer.js +32 -0
- package/dist/contracts/contract-hash.d.ts +3 -2
- package/dist/contracts/contract-limits.d.ts +7 -1
- package/dist/contracts/contract-protocol.d.ts +7 -8
- package/dist/contracts/contract.d.ts +20 -4
- package/dist/contracts/enum-definition.d.ts +2 -0
- package/dist/contracts/field.d.ts +40 -167
- package/dist/contracts/identifier.d.ts +1 -16
- package/dist/contracts/json-shape.d.ts +65 -0
- package/dist/contracts/json-shape.js +1 -0
- package/dist/contracts/lifecycle-trait.d.ts +4 -3
- package/dist/contracts/reference-write-limit.d.ts +7 -0
- package/dist/contracts/reference-write-limit.js +1 -0
- package/dist/contracts/resource-operations.d.ts +2 -62
- package/dist/contracts/resource.d.ts +2 -0
- package/dist/contracts/scalar-value-type.d.ts +28 -48
- package/dist/contracts/scalar.d.ts +4 -3
- package/dist/contracts/transaction-capability.d.ts +2 -0
- package/dist/contracts/wire-scalar-format.d.ts +6 -21
- package/dist/framework/contract-view.d.ts +152 -130
- package/dist/framework/framework-construction.error.d.ts +6 -1
- package/dist/framework/framework.d.ts +33 -46
- package/dist/framework/framework.factory.d.ts +3 -17
- package/dist/framework/framework.factory.js +56 -13
- package/dist/framework/operation-facade.d.ts +3 -7
- package/dist/index.d.ts +8 -40
- package/dist/index.js +0 -20
- package/dist/internal.d.ts +35 -0
- package/dist/internal.js +16 -0
- package/dist/json/avz.d.ts +96 -0
- package/dist/json/avz.js +212 -0
- package/dist/json/index.d.ts +6 -0
- package/dist/json/index.js +1 -0
- package/dist/json/json-shape.registry.d.ts +22 -0
- package/dist/json/json-shape.registry.js +16 -0
- package/dist/json/zod-issue.translator.d.ts +51 -0
- package/dist/json/zod-issue.translator.js +142 -0
- package/dist/operations/operation-arguments.d.ts +198 -303
- package/dist/operations/operation-call.d.ts +13 -15
- package/dist/operations/operation-identity.d.ts +6 -2
- package/dist/operations/operation-identity.js +3 -0
- package/dist/operations/operation-result.d.ts +15 -61
- package/dist/operations/operation-validator-core.d.ts +6 -5
- package/dist/operations/operation-validator-core.js +28 -10
- package/dist/operations/operation-validator.d.ts +22 -9
- package/dist/operations/operation-validator.js +26 -10
- package/dist/operations/projection-scope.resolver.d.ts +1 -11
- package/dist/operations/structural-limits.walker.d.ts +19 -0
- package/dist/operations/structural-limits.walker.js +344 -0
- package/dist/operations/validation-code.d.ts +5 -17
- package/dist/operations/write-scope.resolver.d.ts +3 -28
- package/dist/protocol/absent-route.responder.d.ts +25 -39
- package/dist/protocol/absent-route.responder.js +32 -6
- package/dist/protocol/av-protocol.d.ts +2 -8
- package/dist/protocol/contract-document.responder.d.ts +5 -18
- package/dist/protocol/http-request.decoder.d.ts +18 -59
- package/dist/protocol/http-request.decoder.js +11 -3
- package/dist/protocol/http-request.reader.d.ts +5 -5
- package/dist/protocol/http-response.encoder.d.ts +14 -20
- package/dist/protocol/http-status.table.d.ts +0 -8
- package/dist/protocol/index.d.ts +2 -7
- package/dist/protocol/operation-response.guard.d.ts +0 -15
- package/dist/protocol/protocol-failure.d.ts +8 -27
- package/dist/protocol/protocol-headers.d.ts +15 -42
- package/dist/protocol/protocol-surface.d.ts +0 -22
- package/dist/protocol/protocol.binder.d.ts +19 -27
- package/dist/protocol/protocol.binder.js +3 -1
- package/dist/protocol/protocol.handler.d.ts +19 -23
- package/dist/protocol/protocol.handler.js +14 -4
- package/dist/protocol/unmounted-route.error.d.ts +19 -0
- package/dist/protocol/unmounted-route.error.js +10 -0
- package/dist/runtime/caller-data.copier.d.ts +59 -0
- package/dist/runtime/caller-data.copier.js +470 -0
- package/dist/runtime/caller-data.door.d.ts +56 -0
- package/dist/runtime/caller-data.door.js +65 -0
- package/dist/runtime/computed-read-runtime.js +3 -10
- package/dist/runtime/computed-value.error.d.ts +13 -0
- package/dist/runtime/computed-value.error.js +9 -0
- package/dist/runtime/create-requirement.d.ts +43 -7
- package/dist/runtime/create-requirement.js +89 -9
- package/dist/runtime/decimal.d.ts +5 -3
- package/dist/runtime/diagnostics-reporter.d.ts +33 -37
- package/dist/runtime/diagnostics-reporter.js +11 -4
- package/dist/runtime/error-boundary.d.ts +14 -10
- package/dist/runtime/error-boundary.js +65 -35
- package/dist/runtime/execution-options.d.ts +3 -7
- package/dist/runtime/field-behavior.d.ts +14 -21
- package/dist/runtime/framework-error.d.ts +14 -2
- package/dist/runtime/json-value.d.ts +7 -15
- package/dist/runtime/misuse-reporter.d.ts +32 -0
- package/dist/runtime/misuse-reporter.js +19 -0
- package/dist/runtime/mutation-planner.d.ts +6 -12
- package/dist/runtime/mutation-planner.js +13 -11
- package/dist/runtime/operation-request.d.ts +11 -11
- package/dist/runtime/operation-response.d.ts +18 -23
- package/dist/runtime/operation-runtime.d.ts +25 -33
- package/dist/runtime/operation-runtime.js +37 -18
- package/dist/runtime/outcome-mapper.js +12 -2
- package/dist/runtime/output-validator.d.ts +10 -0
- package/dist/runtime/output-validator.js +11 -0
- package/dist/runtime/owned-execution.registry.d.ts +13 -0
- package/dist/runtime/owned-execution.registry.js +7 -0
- package/dist/runtime/pipeline.d.ts +33 -36
- package/dist/runtime/referential-action.d.ts +8 -26
- package/dist/runtime/request-envelope.validator.d.ts +5 -0
- package/dist/runtime/request-envelope.validator.js +118 -0
- package/dist/runtime/request-id.d.ts +3 -10
- package/dist/runtime/scalar-runtime.d.ts +42 -14
- package/dist/runtime/scalar-runtime.js +162 -79
- package/dist/transactions/deferred-operation.d.ts +10 -21
- package/dist/transactions/operation-arguments-encoder.d.ts +1 -0
- package/dist/transactions/operation-fingerprint.d.ts +3 -1
- package/dist/transactions/transaction-envelope.d.ts +2 -4
- package/dist/transactions/transaction-integrity-error.d.ts +4 -15
- package/dist/transactions/transaction-plan-assembler.d.ts +6 -1
- package/dist/transactions/transaction-plan-assembler.js +74 -75
- package/dist/transactions/transaction-plan-validator.d.ts +33 -24
- package/dist/transactions/transaction-plan-validator.js +204 -153
- package/dist/transactions/transaction-reference-collector.d.ts +30 -0
- package/dist/transactions/transaction-reference-collector.js +204 -0
- package/dist/transactions/transaction-reference-resolver.d.ts +9 -12
- package/dist/transactions/transaction-reference-resolver.js +10 -5
- package/dist/transactions/transaction-reference-validator.js +24 -17
- package/dist/transactions/transaction-reference.d.ts +31 -64
- package/dist/transactions/transaction-request.d.ts +6 -8
- package/dist/transactions/transaction-response.d.ts +10 -7
- package/dist/transactions/transaction-response.js +24 -1
- package/dist/transactions/transaction-runtime.d.ts +7 -5
- package/dist/transactions/transaction-runtime.js +4 -3
- package/dist/transactions/transaction-slot-path-error.d.ts +5 -15
- package/dist/transactions/transaction-slot-writer.d.ts +3 -13
- package/package.json +17 -3
- package/dist/operations/operation-boolean-nodes.d.ts +0 -11
- package/dist/operations/operation-boolean-nodes.js +0 -283
- package/dist/operations/operation-nesting.d.ts +0 -8
- package/dist/operations/operation-nesting.js +0 -307
|
@@ -6,43 +6,50 @@ import type { ResolvedFrameworkConfig } from "../config/config.resolver.js";
|
|
|
6
6
|
import type { Contract } from "../contracts/contract.js";
|
|
7
7
|
import type { OperationValidationOptions, SemanticOperationArguments } from "../operations/operation-validator.js";
|
|
8
8
|
import type { FrameworkTransactionRequest } from "../transactions/transaction-request.js";
|
|
9
|
-
import { type
|
|
9
|
+
import { type ArgumentsSource } from "./caller-data.door.js";
|
|
10
|
+
import { type RecordWriteFacts } from "./create-requirement.js";
|
|
10
11
|
import type { DiagnosticsReporter } from "./diagnostics-reporter.js";
|
|
11
12
|
import type { ClientProtocolIdentity, FrameworkOperationRequest, OperationExecutionRequest } from "./operation-request.js";
|
|
12
13
|
import type { OperationFailureResponse, OperationResponse } from "./operation-response.js";
|
|
14
|
+
import { type NormalizationWrite } from "./scalar-runtime.js";
|
|
13
15
|
export type OperationRuntimeDependencies<M extends AdapterModel> = {
|
|
14
16
|
readonly adapter: Adapter<M>;
|
|
15
17
|
readonly contracts: FrameworkContracts;
|
|
16
18
|
readonly config: ResolvedFrameworkConfig<M>;
|
|
17
19
|
};
|
|
18
20
|
/**
|
|
19
|
-
* What a validated operation's pipeline needs: everything except the root Adapter.
|
|
20
|
-
*
|
|
21
|
+
* What a validated operation's pipeline needs: everything except the root Adapter. Its
|
|
22
|
+
* single Adapter call goes through the executor passed beside it, so inside a
|
|
21
23
|
* transaction plan the root Adapter is unreachable by type.
|
|
22
24
|
*/
|
|
23
25
|
export type OperationPipelineDependencies<M extends AdapterModel> = Omit<OperationRuntimeDependencies<M>, "adapter"> & {
|
|
24
|
-
/**
|
|
25
|
-
|
|
26
|
+
/**
|
|
27
|
+
* The Adapter's create requirements and referential edges, read once at
|
|
28
|
+
* construction.
|
|
29
|
+
*/
|
|
30
|
+
readonly recordWrites: RecordWriteFacts;
|
|
26
31
|
};
|
|
27
32
|
/**
|
|
28
33
|
* The executor core owns no diagnostics channel: the framework factory owns
|
|
29
34
|
* it and hands in one reporter already bound to this request, so every
|
|
30
35
|
* boundary — including the outermost totality guard, which runs outside this
|
|
31
|
-
* function — reports through the same channel.
|
|
36
|
+
* function — reports through the same channel. `source` says whether the
|
|
37
|
+
* request's arguments are the caller's own objects, copied at the door, or data
|
|
38
|
+
* no caller holds (see {@link ArgumentsSource}).
|
|
32
39
|
*/
|
|
33
|
-
export type FrameworkOperationExecutor = (request: FrameworkOperationRequest, report: DiagnosticsReporter) => Promise<OperationResponse>;
|
|
34
|
-
/** Selects the Contract of the request's
|
|
40
|
+
export type FrameworkOperationExecutor = (request: FrameworkOperationRequest, report: DiagnosticsReporter, source: ArgumentsSource) => Promise<OperationResponse>;
|
|
41
|
+
/** Selects the Contract of the request's origin. */
|
|
35
42
|
export declare function activeContract(contracts: FrameworkContracts, request: FrameworkOperationRequest | FrameworkTransactionRequest): Contract;
|
|
36
43
|
/** Creates the transport-independent operation executor owned by core. */
|
|
37
44
|
export declare function createOperationRuntime<M extends AdapterModel>(dependencies: OperationRuntimeDependencies<M>): FrameworkOperationExecutor;
|
|
38
|
-
/** Validates the request-level protocol identity
|
|
45
|
+
/** Validates the request-level protocol identity. */
|
|
39
46
|
export declare function validateProtocolIdentity(contracts: FrameworkContracts, request: FrameworkOperationRequest | FrameworkTransactionRequest): OperationFailureResponse | undefined;
|
|
40
47
|
/**
|
|
41
|
-
* The stale-client check on one client protocol identity: the protocol version
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* (`@aventara/core/protocol`) call it too, so a stale client hears the same
|
|
45
|
-
*
|
|
48
|
+
* The stale-client check on one client protocol identity: the protocol version first
|
|
49
|
+
* (`A2006`), then the ClientContract hash (`A2005`). Exact match only. The one
|
|
50
|
+
* implementation — the protocol's decoders and absence answers
|
|
51
|
+
* (`@aventara/core/protocol`) call it too, so a stale client hears the same envelope
|
|
52
|
+
* whichever layer answers.
|
|
46
53
|
*/
|
|
47
54
|
export declare function protocolIdentityFailure(contracts: FrameworkContracts, protocol: ClientProtocolIdentity): OperationFailureResponse | undefined;
|
|
48
55
|
export type ValidatedOperationArguments = {
|
|
@@ -52,26 +59,11 @@ export type ValidatedOperationArguments = {
|
|
|
52
59
|
readonly ok: false;
|
|
53
60
|
readonly response: OperationFailureResponse;
|
|
54
61
|
};
|
|
55
|
-
/**
|
|
56
|
-
export declare function validateOperationRequest(contract: Contract, request: FrameworkOperationRequest, options?: OperationValidationOptions): ValidatedOperationArguments;
|
|
62
|
+
/** Validates one operation's arguments through the Framework Validation Layer. */
|
|
63
|
+
export declare function validateOperationRequest(contract: Contract, request: FrameworkOperationRequest, options?: OperationValidationOptions, write?: NormalizationWrite): ValidatedOperationArguments;
|
|
57
64
|
/**
|
|
58
65
|
* Everything after the Framework Validation Layer: the developer/runtime path
|
|
59
|
-
* wrapped in the post-validation error boundary. Validation failures never
|
|
60
|
-
* this function, so developer filters never see them.
|
|
61
|
-
*
|
|
62
|
-
* `executor` is the operation's single Adapter call site: standalone execution
|
|
63
|
-
* passes the Adapter itself, a transaction plan passes the transaction-scoped
|
|
64
|
-
* executor its Adapter callback received. Every call owns one AdapterBoundary, so
|
|
65
|
-
* A3001/A3000 origin stays classified per operation.
|
|
66
|
-
*
|
|
67
|
-
* The parameter types keep two values out of reach: `dependencies` carries no root
|
|
68
|
-
* Adapter, so the executor is the only Adapter this function can call, and
|
|
69
|
-
* `request` carries no `arguments`, so no stage can read the unvalidated view
|
|
70
|
-
* instead of `validatedArguments`.
|
|
71
|
-
*
|
|
72
|
-
* `argumentsPath` is the framework's own address for these arguments —
|
|
73
|
-
* `["arguments"]` standalone, `["operations", i, "args"]` inside a plan. The
|
|
74
|
-
* response boundary needs it to decide whether a thrower-supplied issue path
|
|
75
|
-
* denotes the caller's own arguments or is unvouched text.
|
|
66
|
+
* wrapped in the post-validation error boundary. Validation failures never
|
|
67
|
+
* reach this function, so developer filters never see them.
|
|
76
68
|
*/
|
|
77
69
|
export declare function executeValidatedOperation<M extends AdapterModel>(dependencies: OperationPipelineDependencies<M>, request: OperationExecutionRequest, contract: Contract, validatedArguments: unknown, argumentsPath: readonly (string | number)[], executor: AdapterOperationExecutor<M>, report: DiagnosticsReporter): Promise<OperationResponse>;
|
|
@@ -1,32 +1,34 @@
|
|
|
1
1
|
import { AVENTARA_PROTOCOL_VERSION } from "../contracts/contract-protocol.js";
|
|
2
|
-
import { validateOperationArguments } from "../operations/operation-validator.js";
|
|
2
|
+
import { findOperationAvailabilityIssue, validateOperationArguments, } from "../operations/operation-validator.js";
|
|
3
|
+
import { copyAtDoor, misuseIdentityOf, refuseCallerData, } from "./caller-data.door.js";
|
|
3
4
|
import { materializeReadOperation, planReadOperation, } from "./computed-read-runtime.js";
|
|
4
|
-
import {
|
|
5
|
+
import { createRequirementIssues, recordWriteFactsOf, } from "./create-requirement.js";
|
|
5
6
|
import { AdapterBoundary, OPERATION_ARGUMENTS_PATH, resolveOperationFailure, sanitizeOperationFailure, } from "./error-boundary.js";
|
|
7
|
+
import { createMisuseReporter, } from "./misuse-reporter.js";
|
|
6
8
|
import { applyWriteFieldBehaviors, stripVirtualMutationInput, } from "./mutation-planner.js";
|
|
7
9
|
import { internalFailure, operationFailure } from "./operation-response.js";
|
|
8
10
|
import { mapOperationOutcome, mapValidationOutcome } from "./outcome-mapper.js";
|
|
9
|
-
import { validateOperationOutput } from "./output-validator.js";
|
|
11
|
+
import { OutputValidationError, validateOperationOutput, } from "./output-validator.js";
|
|
10
12
|
import { refusePipeOutput } from "./pipe-output.validator.js";
|
|
11
|
-
import { normalizeOperationScalarValues } from "./scalar-runtime.js";
|
|
13
|
+
import { normalizeOperationScalarValues, } from "./scalar-runtime.js";
|
|
12
14
|
export function activeContract(contracts, request) {
|
|
13
|
-
return request.
|
|
15
|
+
return request.origin === "application"
|
|
14
16
|
? contracts.application
|
|
15
17
|
: contracts.client;
|
|
16
18
|
}
|
|
17
19
|
function activeScopeConfig(config, request) {
|
|
18
|
-
return request.
|
|
20
|
+
return request.origin === "application" ? config.application : config.client;
|
|
19
21
|
}
|
|
20
22
|
function pipelineContext(contract, request, args) {
|
|
21
23
|
return {
|
|
22
24
|
contract,
|
|
23
|
-
|
|
25
|
+
origin: request.origin,
|
|
24
26
|
resource: request.resource,
|
|
25
27
|
family: request.family,
|
|
26
28
|
variant: request.variant,
|
|
27
29
|
args,
|
|
28
30
|
requestId: request.requestId,
|
|
29
|
-
...(request.
|
|
31
|
+
...(request.origin === "client" && request.transport !== undefined
|
|
30
32
|
? { transport: request.transport }
|
|
31
33
|
: {}),
|
|
32
34
|
};
|
|
@@ -37,24 +39,30 @@ function forbidden() {
|
|
|
37
39
|
});
|
|
38
40
|
}
|
|
39
41
|
function outputAccepted(contract, request, projection, data, report) {
|
|
42
|
+
let validation;
|
|
40
43
|
try {
|
|
41
|
-
|
|
44
|
+
validation = validateOperationOutput(contract, request, projection, data);
|
|
42
45
|
}
|
|
43
46
|
catch (error) {
|
|
44
47
|
report(error, "A3000");
|
|
45
48
|
return false;
|
|
46
49
|
}
|
|
50
|
+
if (!validation.ok) {
|
|
51
|
+
report(new OutputValidationError(request, validation.issues), "A3000");
|
|
52
|
+
}
|
|
53
|
+
return validation.ok;
|
|
47
54
|
}
|
|
48
55
|
export function createOperationRuntime(dependencies) {
|
|
49
56
|
const pipelineDependencies = {
|
|
50
57
|
contracts: dependencies.contracts,
|
|
51
58
|
config: dependencies.config,
|
|
52
|
-
|
|
59
|
+
recordWrites: recordWriteFactsOf(dependencies.adapter),
|
|
53
60
|
};
|
|
54
|
-
|
|
61
|
+
const misuse = createMisuseReporter(dependencies.config.misuse);
|
|
62
|
+
return async (request, report, source) => {
|
|
55
63
|
let validated;
|
|
56
64
|
try {
|
|
57
|
-
validated = validateOperation(dependencies, request);
|
|
65
|
+
validated = validateOperation(dependencies, request, source, misuse);
|
|
58
66
|
}
|
|
59
67
|
catch (error) {
|
|
60
68
|
return sanitizeOperationFailure(error, "runtime", report, {
|
|
@@ -68,20 +76,31 @@ export function createOperationRuntime(dependencies) {
|
|
|
68
76
|
return executeValidatedOperation(pipelineDependencies, request, validated.contract, validated.arguments, OPERATION_ARGUMENTS_PATH, dependencies.adapter, report);
|
|
69
77
|
};
|
|
70
78
|
}
|
|
71
|
-
function validateOperation(dependencies, request) {
|
|
79
|
+
function validateOperation(dependencies, request, source, misuse) {
|
|
72
80
|
const protocolFailure = validateProtocolIdentity(dependencies.contracts, request);
|
|
73
81
|
if (protocolFailure !== undefined) {
|
|
74
82
|
return rejected(protocolFailure);
|
|
75
83
|
}
|
|
76
84
|
const contract = activeContract(dependencies.contracts, request);
|
|
77
|
-
|
|
85
|
+
let owned = request;
|
|
86
|
+
if (source === "caller" &&
|
|
87
|
+
findOperationAvailabilityIssue(contract, request) === undefined) {
|
|
88
|
+
const copy = copyAtDoor(request.arguments);
|
|
89
|
+
if (!copy.ok) {
|
|
90
|
+
return rejected(mapValidationOutcome([
|
|
91
|
+
refuseCallerData(copy.error, OPERATION_ARGUMENTS_PATH, misuseIdentityOf(request), misuse),
|
|
92
|
+
]));
|
|
93
|
+
}
|
|
94
|
+
owned = { ...request, arguments: copy.value };
|
|
95
|
+
}
|
|
96
|
+
const validation = validateOperationRequest(contract, owned, undefined, "in-place");
|
|
78
97
|
if (!validation.ok) {
|
|
79
98
|
return rejected(validation.response);
|
|
80
99
|
}
|
|
81
100
|
return { ok: true, contract, arguments: validation.arguments };
|
|
82
101
|
}
|
|
83
102
|
export function validateProtocolIdentity(contracts, request) {
|
|
84
|
-
return request.
|
|
103
|
+
return request.origin === "client"
|
|
85
104
|
? protocolIdentityFailure(contracts, request.protocol)
|
|
86
105
|
: undefined;
|
|
87
106
|
}
|
|
@@ -98,12 +117,12 @@ export function protocolIdentityFailure(contracts, protocol) {
|
|
|
98
117
|
}
|
|
99
118
|
return undefined;
|
|
100
119
|
}
|
|
101
|
-
export function validateOperationRequest(contract, request, options) {
|
|
120
|
+
export function validateOperationRequest(contract, request, options, write = "copy") {
|
|
102
121
|
const validation = validateOperationArguments(contract, request, options);
|
|
103
122
|
if (!validation.ok) {
|
|
104
123
|
return { ok: false, response: mapValidationOutcome(validation.issues) };
|
|
105
124
|
}
|
|
106
|
-
const scalarNormalization = normalizeOperationScalarValues(contract, { ...request, arguments: validation.arguments }, request.
|
|
125
|
+
const scalarNormalization = normalizeOperationScalarValues(contract, { ...request, arguments: validation.arguments }, request.origin, options, write);
|
|
107
126
|
return scalarNormalization.ok
|
|
108
127
|
? { ok: true, arguments: scalarNormalization.arguments }
|
|
109
128
|
: {
|
|
@@ -144,7 +163,7 @@ export async function executeValidatedOperation(dependencies, request, contract,
|
|
|
144
163
|
}
|
|
145
164
|
}
|
|
146
165
|
args = await applyWriteFieldBehaviors({ core: dependencies.contracts.core, active: contract }, scopeConfig.behaviors, request, args, () => pipelineContext(contract, request, args));
|
|
147
|
-
const missing =
|
|
166
|
+
const missing = createRequirementIssues(contract, dependencies.recordWrites, request, args, argumentsPath);
|
|
148
167
|
if (missing.length > 0) {
|
|
149
168
|
return mapValidationOutcome(missing);
|
|
150
169
|
}
|
|
@@ -42,11 +42,21 @@ export function mapOperationOutcome(operation, data) {
|
|
|
42
42
|
? absenceOutcome(rule.absence)
|
|
43
43
|
: operationSuccess(data, rule.success);
|
|
44
44
|
}
|
|
45
|
+
function stepRelativeHead(issue) {
|
|
46
|
+
const path = issue.path;
|
|
47
|
+
return path?.[0] === "operations" && typeof path[1] === "number"
|
|
48
|
+
? path[2]
|
|
49
|
+
: path?.[0];
|
|
50
|
+
}
|
|
51
|
+
function namesUnavailableOperation(issue) {
|
|
52
|
+
const head = stepRelativeHead(issue);
|
|
53
|
+
return issue.code === "V1006" && (head === "family" || head === "variant");
|
|
54
|
+
}
|
|
45
55
|
function validationCode(issues) {
|
|
46
|
-
if (issues.some((issue) => issue.
|
|
56
|
+
if (issues.some((issue) => issue.code !== "V1019" && stepRelativeHead(issue) === "resource")) {
|
|
47
57
|
return "A2001";
|
|
48
58
|
}
|
|
49
|
-
if (issues.some(
|
|
59
|
+
if (issues.some(namesUnavailableOperation)) {
|
|
50
60
|
return "A2002";
|
|
51
61
|
}
|
|
52
62
|
if (issues.some((issue) => issue.code === "V1010" ||
|
|
@@ -11,6 +11,16 @@ export type OutputValidationResult = {
|
|
|
11
11
|
type OutputOperation = OperationIdentity & {
|
|
12
12
|
readonly resource: string;
|
|
13
13
|
};
|
|
14
|
+
/**
|
|
15
|
+
* The error the diagnostics channel receives when a result fails output
|
|
16
|
+
* validation: whose result it was and what the validator found. The caller
|
|
17
|
+
* receives the generic `A3000` and never this.
|
|
18
|
+
*/
|
|
19
|
+
export declare class OutputValidationError extends Error {
|
|
20
|
+
readonly issues: readonly ValidationIssue[];
|
|
21
|
+
readonly name = "OutputValidationError";
|
|
22
|
+
constructor(operation: OutputOperation, issues: readonly ValidationIssue[]);
|
|
23
|
+
}
|
|
14
24
|
/** Validate final operation output against fixed cardinality and executed projection. */
|
|
15
25
|
export declare function validateOperationOutput(contract: Contract, operation: OutputOperation, projection: ReadProjectionPlan | undefined, value: unknown): OutputValidationResult;
|
|
16
26
|
export {};
|
|
@@ -168,6 +168,17 @@ function validateProjection(contract, operation, projection, value) {
|
|
|
168
168
|
? validateExactRecord(contract, value, projection, ["data"])
|
|
169
169
|
: { ok: true };
|
|
170
170
|
}
|
|
171
|
+
export class OutputValidationError extends Error {
|
|
172
|
+
issues;
|
|
173
|
+
name = "OutputValidationError";
|
|
174
|
+
constructor(operation, issues) {
|
|
175
|
+
const [first] = issues;
|
|
176
|
+
super(`The result of ${operation.resource}.${operation.family}.${operation.variant} does not satisfy its Contract${first === undefined
|
|
177
|
+
? "."
|
|
178
|
+
: ` at "${(first.path ?? []).join(".")}" (${first.code}: ${first.message})`}`);
|
|
179
|
+
this.issues = issues;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
171
182
|
export function validateOperationOutput(contract, operation, projection, value) {
|
|
172
183
|
const cardinality = validateOperationOutputCardinality(operation, value);
|
|
173
184
|
return cardinality.ok
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ClientTransactionRequest } from "../transactions/transaction-request.js";
|
|
2
|
+
import type { TransactionResponse } from "../transactions/transaction-response.js";
|
|
3
|
+
import type { ClientOperationRequest } from "./operation-request.js";
|
|
4
|
+
import type { OperationResponse } from "./operation-response.js";
|
|
5
|
+
/** The two entries that run a request the protocol decoded itself, uncopied. */
|
|
6
|
+
export type OwnedExecution = {
|
|
7
|
+
readonly execute: (request: ClientOperationRequest) => Promise<OperationResponse>;
|
|
8
|
+
readonly executeTransaction: (request: ClientTransactionRequest) => Promise<TransactionResponse>;
|
|
9
|
+
};
|
|
10
|
+
/** Records `framework`'s owned entries; called once, at construction. */
|
|
11
|
+
export declare function recordOwnedExecution(framework: object, execution: OwnedExecution): void;
|
|
12
|
+
/** `framework`'s owned entries, or `undefined` for anything `createFramework` did not return. */
|
|
13
|
+
export declare function ownedExecutionOf(framework: object): OwnedExecution | undefined;
|
|
@@ -5,43 +5,16 @@ import type { Contract } from "../contracts/contract.js";
|
|
|
5
5
|
import type { OperationFamily } from "../operations/operation-identity.js";
|
|
6
6
|
import type { FrameworkError } from "./framework-error.js";
|
|
7
7
|
/**
|
|
8
|
-
* What a pipeline is told about the HTTP request a client-origin operation arrived
|
|
9
|
-
* on — a **deliberate exception** to core's transport-agnosticism.
|
|
10
|
-
*
|
|
11
|
-
* Core never parses HTTP to fill it. `@aventara/core/protocol`'s decoders do, with
|
|
12
|
-
* header names **lower-cased** and a repeated header's values joined with `", "`
|
|
13
|
-
* (RFC 9110 §5.3), so a pipeline reads one spelling. An application-scope request
|
|
14
|
-
* carries none (`ClientOperationRequest.transport` is optional).
|
|
8
|
+
* What a pipeline is told about the HTTP request a client-origin operation arrived on.
|
|
15
9
|
*/
|
|
16
10
|
export type PipelineTransportContext = {
|
|
17
11
|
readonly method: "POST";
|
|
18
12
|
readonly headers: Readonly<Record<string, string>>;
|
|
19
13
|
};
|
|
20
|
-
/**
|
|
21
|
-
* What a pipeline callback is told about the operation it is wrapping.
|
|
22
|
-
*
|
|
23
|
-
* `resource` reads its key set from the **AdapterModel**, not from a `Contract`:
|
|
24
|
-
* `Contract["resources"]` is an open `Record<string, …>`, so the `C extends
|
|
25
|
-
* Contract` parameter this type used to take could never name the Resources an
|
|
26
|
-
* author actually has — it erased to `string`. `M` can name them, and
|
|
27
|
-
* configuration cannot add Resources, so `M` is the whole answer.
|
|
28
|
-
*
|
|
29
|
-
* A `Cfg` parameter here would be referenced nowhere — `biome` flags it
|
|
30
|
-
* (`lint/correctness/noUnusedVariables`) and it would encode no semantic
|
|
31
|
-
* dependency. `FrameworkScopeConfig` already has the precedent one line above
|
|
32
|
-
* `pipelines`: `fields` takes `M` only.
|
|
33
|
-
*
|
|
34
|
-
* `contract` stays the erased {@link Contract} deliberately: a compiled Contract
|
|
35
|
-
* is passive semantic data whose result "stays untyped by design". Threading
|
|
36
|
-
* configuration into it would rebuild the authoring-config-to-compiled-contract
|
|
37
|
-
* bridge this work bypasses.
|
|
38
|
-
*
|
|
39
|
-
* Under the default `M` the key set degenerates to `string`, so an unannotated
|
|
40
|
-
* `PipelineContext` is what it was before.
|
|
41
|
-
*/
|
|
14
|
+
/** What a pipeline callback is told about the operation it is wrapping. */
|
|
42
15
|
export type PipelineContext<M extends AdapterModel = AdapterModel> = {
|
|
43
16
|
readonly contract: Contract;
|
|
44
|
-
readonly
|
|
17
|
+
readonly origin: RequestOrigin;
|
|
45
18
|
readonly resource: FrameworkResourceKey<M>;
|
|
46
19
|
readonly family: OperationFamily;
|
|
47
20
|
readonly variant: string;
|
|
@@ -49,9 +22,33 @@ export type PipelineContext<M extends AdapterModel = AdapterModel> = {
|
|
|
49
22
|
readonly requestId: string;
|
|
50
23
|
readonly transport?: PipelineTransportContext;
|
|
51
24
|
};
|
|
52
|
-
|
|
53
|
-
export type
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
25
|
+
/** Allows or denies an operation; returning `false` answers `A4001`. */
|
|
26
|
+
export type Guard<M extends AdapterModel = AdapterModel> = {
|
|
27
|
+
guard(context: PipelineContext<M>): boolean | undefined | Promise<boolean | undefined>;
|
|
28
|
+
}["guard"];
|
|
29
|
+
/**
|
|
30
|
+
* Transforms the arguments before the operation runs; returning `undefined`
|
|
31
|
+
* keeps them as they are.
|
|
32
|
+
*/
|
|
33
|
+
export type Pipe<M extends AdapterModel = AdapterModel> = {
|
|
34
|
+
pipe(args: unknown, context: PipelineContext<M>): unknown | Promise<unknown>;
|
|
35
|
+
}["pipe"];
|
|
36
|
+
/** Runs just before the operation executes. */
|
|
37
|
+
export type BeforeHook<M extends AdapterModel = AdapterModel> = {
|
|
38
|
+
before(context: PipelineContext<M>): void | Promise<void>;
|
|
39
|
+
}["before"];
|
|
40
|
+
/** Runs after the operation succeeds, with its result. */
|
|
41
|
+
export type AfterHook<M extends AdapterModel = AdapterModel> = {
|
|
42
|
+
after(result: unknown, context: PipelineContext<M>): void | Promise<void>;
|
|
43
|
+
}["after"];
|
|
44
|
+
/** Wraps the operation's execution; call `next()` to run it and get its result. */
|
|
45
|
+
export type Interceptor<M extends AdapterModel = AdapterModel> = {
|
|
46
|
+
intercept(context: PipelineContext<M>, next: () => Promise<unknown>): Promise<unknown>;
|
|
47
|
+
}["intercept"];
|
|
48
|
+
/**
|
|
49
|
+
* Turns a thrown error into a {@link FrameworkError} to answer with; returning
|
|
50
|
+
* `undefined` passes the error on.
|
|
51
|
+
*/
|
|
52
|
+
export type Filter<M extends AdapterModel = AdapterModel> = {
|
|
53
|
+
filter(error: unknown, context: PipelineContext<M>): FrameworkError | undefined | Promise<FrameworkError | undefined>;
|
|
54
|
+
}["filter"];
|
|
@@ -1,33 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Core's referential-action vocabulary, and the transitive cascade closure the
|
|
3
3
|
* transaction planner reads.
|
|
4
|
-
*
|
|
5
|
-
* The facts are an Adapter's: which relation edges exist, and what deleting a
|
|
6
|
-
* referenced row does to the rows that reference it. So the words here are
|
|
7
|
-
* Aventara's, stated as the effect on the referencing rows, and an Adapter
|
|
8
|
-
* translates its own provider's vocabulary into them.
|
|
9
|
-
*
|
|
10
|
-
* None of this enters a Contract. It is not part of the semantic model a Contract
|
|
11
|
-
* is compiled from, so it moves no Contract value and no hash; it is a planning
|
|
12
|
-
* fact, read once when the Framework is constructed.
|
|
13
|
-
*/
|
|
14
|
-
/**
|
|
15
|
-
* What deleting a referenced row does to the rows that reference it.
|
|
16
|
-
*
|
|
17
|
-
* - `cascade` — they are deleted with it, and their own referencing rows in turn
|
|
18
|
-
* (SQL `ON DELETE CASCADE`).
|
|
19
|
-
* - `restrict` — the delete is refused while they reference it (`RESTRICT`).
|
|
20
|
-
* - `noAction` — the delete is refused while they reference it (`NO ACTION`).
|
|
21
|
-
* Measured identical to `restrict` on both pilot engines: neither declares the
|
|
22
|
-
* constraint `DEFERRABLE`, so the end-of-statement check answers one delete
|
|
23
|
-
* exactly as `RESTRICT` does. It stays a distinct word because it is a distinct
|
|
24
|
-
* schema fact.
|
|
25
|
-
* - `setNull` — they are kept, the reference set to null (`SET NULL`).
|
|
26
|
-
* - `setDefault` — they are kept, the reference set to its column default (`SET
|
|
27
|
-
* DEFAULT`).
|
|
28
|
-
*
|
|
29
|
-
* Only `cascade` takes rows away, so only `cascade` is followed by the closure.
|
|
30
4
|
*/
|
|
5
|
+
/** What deleting a referenced row does to the rows that reference it. */
|
|
31
6
|
export type ReferentialDeleteEffect = "cascade" | "restrict" | "noAction" | "setNull" | "setDefault";
|
|
32
7
|
/**
|
|
33
8
|
* One relation edge whose referencing side holds the reference.
|
|
@@ -48,6 +23,13 @@ export type ReferentialEdge = {
|
|
|
48
23
|
readonly field: string;
|
|
49
24
|
/** The Resource the edge points AT — the one whose deletion fires `onDelete`. */
|
|
50
25
|
readonly target: string;
|
|
26
|
+
/**
|
|
27
|
+
* The scalar fields of the ENCLOSING Resource that hold the reference, in the
|
|
28
|
+
* order the store pairs them with the `target`'s key. A record that writes the
|
|
29
|
+
* relation `field` and any of these names the reference twice and is refused;
|
|
30
|
+
* a required relation may be written through all of them instead.
|
|
31
|
+
*/
|
|
32
|
+
readonly references: readonly string[];
|
|
51
33
|
/** What deleting a `target` row does to rows of the ENCLOSING Resource. */
|
|
52
34
|
readonly onDelete: ReferentialDeleteEffect;
|
|
53
35
|
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ValidationIssue } from "../operations/validation-code.js";
|
|
2
|
+
/** The issues of one malformed operation request envelope; empty when well formed. */
|
|
3
|
+
export declare function validateOperationEnvelope(request: unknown): readonly ValidationIssue[];
|
|
4
|
+
/** The issues of one malformed transaction request envelope; empty when well formed. */
|
|
5
|
+
export declare function validateTransactionEnvelope(request: unknown): readonly ValidationIssue[];
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
const ORIGINS = ["application", "client"];
|
|
2
|
+
const OPERATION_IDENTITY_MEMBERS = ["resource", "family", "variant"];
|
|
3
|
+
export function validateOperationEnvelope(request) {
|
|
4
|
+
if (!isRecord(request)) {
|
|
5
|
+
return [issue([], "V1001", "Operation request must be an object.")];
|
|
6
|
+
}
|
|
7
|
+
const issues = [...originAndIdentityIssues(request)];
|
|
8
|
+
for (const member of OPERATION_IDENTITY_MEMBERS) {
|
|
9
|
+
const failure = requiredStringIssue(request, member);
|
|
10
|
+
if (failure !== undefined) {
|
|
11
|
+
issues.push(failure);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return issues;
|
|
15
|
+
}
|
|
16
|
+
export function validateTransactionEnvelope(request) {
|
|
17
|
+
if (!isRecord(request)) {
|
|
18
|
+
return [issue([], "V1001", "Transaction request must be an object.")];
|
|
19
|
+
}
|
|
20
|
+
return originAndIdentityIssues(request);
|
|
21
|
+
}
|
|
22
|
+
function originAndIdentityIssues(request) {
|
|
23
|
+
const issues = [];
|
|
24
|
+
const origin = request.origin;
|
|
25
|
+
if (origin === undefined) {
|
|
26
|
+
issues.push(issue(["origin"], "V1000", 'Required property "origin" is missing.'));
|
|
27
|
+
}
|
|
28
|
+
else if (typeof origin !== "string") {
|
|
29
|
+
issues.push(issue(["origin"], "V1001", "Request origin must be a string."));
|
|
30
|
+
}
|
|
31
|
+
else if (!ORIGINS.includes(origin)) {
|
|
32
|
+
issues.push(issue(["origin"], "V1002", 'Request origin must be "application" or "client".'));
|
|
33
|
+
}
|
|
34
|
+
const requestId = request.requestId;
|
|
35
|
+
if (requestId === undefined || requestId === "") {
|
|
36
|
+
issues.push(issue(["requestId"], "V1000", 'Required property "requestId" is missing or empty.'));
|
|
37
|
+
}
|
|
38
|
+
else if (typeof requestId !== "string") {
|
|
39
|
+
issues.push(issue(["requestId"], "V1001", "Request id must be a string."));
|
|
40
|
+
}
|
|
41
|
+
if (origin === "client") {
|
|
42
|
+
issues.push(...protocolIssues(request.protocol));
|
|
43
|
+
if (request.transport !== undefined) {
|
|
44
|
+
issues.push(...transportIssues(request.transport));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else if (origin === "application") {
|
|
48
|
+
for (const member of ["protocol", "transport"]) {
|
|
49
|
+
if (request[member] !== undefined) {
|
|
50
|
+
issues.push(issue([member], "V1005", `Property "${member}" belongs to a client request, not an application one.`));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return issues;
|
|
55
|
+
}
|
|
56
|
+
function protocolIssues(protocol) {
|
|
57
|
+
if (protocol === undefined) {
|
|
58
|
+
return [
|
|
59
|
+
issue(["protocol"], "V1000", 'Required property "protocol" is missing from a client request.'),
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
if (!isRecord(protocol)) {
|
|
63
|
+
return [
|
|
64
|
+
issue(["protocol"], "V1001", "Protocol identity must be an object."),
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
const issues = [];
|
|
68
|
+
if (protocol.version === undefined) {
|
|
69
|
+
issues.push(issue(["protocol", "version"], "V1000", 'Required property "version" is missing.'));
|
|
70
|
+
}
|
|
71
|
+
else if (!Number.isInteger(protocol.version)) {
|
|
72
|
+
issues.push(issue(["protocol", "version"], "V1001", "Protocol version must be an integer."));
|
|
73
|
+
}
|
|
74
|
+
if (protocol.hash === undefined) {
|
|
75
|
+
issues.push(issue(["protocol", "hash"], "V1000", 'Required property "hash" is missing.'));
|
|
76
|
+
}
|
|
77
|
+
else if (typeof protocol.hash !== "string") {
|
|
78
|
+
issues.push(issue(["protocol", "hash"], "V1001", "Protocol hash must be a string."));
|
|
79
|
+
}
|
|
80
|
+
return issues;
|
|
81
|
+
}
|
|
82
|
+
function transportIssues(transport) {
|
|
83
|
+
if (!isRecord(transport)) {
|
|
84
|
+
return [
|
|
85
|
+
issue(["transport"], "V1001", "Transport metadata must be an object."),
|
|
86
|
+
];
|
|
87
|
+
}
|
|
88
|
+
const issues = [];
|
|
89
|
+
if (transport.method !== "POST") {
|
|
90
|
+
issues.push(issue(["transport", "method"], "V1001", 'Transport method must be "POST".'));
|
|
91
|
+
}
|
|
92
|
+
const headers = transport.headers;
|
|
93
|
+
if (!isRecord(headers)) {
|
|
94
|
+
issues.push(issue(["transport", "headers"], "V1001", "Transport headers must be an object."));
|
|
95
|
+
return issues;
|
|
96
|
+
}
|
|
97
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
98
|
+
if (typeof value !== "string") {
|
|
99
|
+
issues.push(issue(["transport", "headers", name], "V1001", "A transport header value must be a string."));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return issues;
|
|
103
|
+
}
|
|
104
|
+
function requiredStringIssue(request, member) {
|
|
105
|
+
const value = request[member];
|
|
106
|
+
if (value === undefined) {
|
|
107
|
+
return issue([member], "V1000", `Required property "${member}" is missing.`);
|
|
108
|
+
}
|
|
109
|
+
return typeof value === "string"
|
|
110
|
+
? undefined
|
|
111
|
+
: issue([member], "V1001", `Property "${member}" must be a string.`);
|
|
112
|
+
}
|
|
113
|
+
function isRecord(value) {
|
|
114
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
115
|
+
}
|
|
116
|
+
function issue(path, code, message) {
|
|
117
|
+
return { path, code, message };
|
|
118
|
+
}
|
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The request id of one
|
|
3
|
-
* `ExecutionOptions.requestId` when it is a non-empty string, otherwise a
|
|
4
|
-
* one.
|
|
5
|
-
* as absent, because it names nothing a log line or a diagnostic could be
|
|
6
|
-
* correlated by.
|
|
7
|
-
*
|
|
8
|
-
* Read as `unknown` because the façades are total for untyped callers too: a
|
|
9
|
-
* value the type refuses — a non-string id, a non-object options argument, a
|
|
10
|
-
* getter that throws — is ignored rather than carried into a `string` slot or
|
|
11
|
-
* thrown out of a call that must resolve.
|
|
2
|
+
* The request id of one call or transaction run: the caller's
|
|
3
|
+
* `ExecutionOptions.requestId` when it is a non-empty string, otherwise a new
|
|
4
|
+
* one. Any other value is ignored.
|
|
12
5
|
*/
|
|
13
6
|
export declare function requestIdOf(options: unknown): string;
|