@aventara/core 1.0.0-rc.3 → 1.0.0-rc.8
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 +231 -1
- package/ERRORS.md +6 -6
- package/README.md +76 -12
- package/dist/adapters/adapter-field.d.ts +8 -0
- package/dist/adapters/adapter-identifier.d.ts +9 -0
- package/dist/adapters/adapter-identifier.js +6 -1
- package/dist/adapters/adapter-operation.d.ts +13 -0
- package/dist/compiler/contract.compiler.d.ts +6 -0
- package/dist/compiler/contract.compiler.js +99 -15
- package/dist/config/computed-fields.js +2 -9
- package/dist/config/config-diagnostic.guidance.js +3 -3
- package/dist/config/config.resolver.d.ts +14 -0
- package/dist/config/config.resolver.js +31 -0
- package/dist/config/config.validator.js +82 -0
- package/dist/config/exact-framework-config.d.ts +5 -0
- package/dist/config/framework-config.d.ts +30 -1
- package/dist/contracts/client-contract-structure.validator.d.ts +8 -0
- package/dist/contracts/client-contract-structure.validator.js +6 -1
- package/dist/contracts/contract.d.ts +15 -1
- package/dist/contracts/field.d.ts +28 -0
- package/dist/contracts/scalar-value-type.d.ts +3 -1
- package/dist/framework/contract-view.d.ts +41 -10
- package/dist/framework/framework-construction.error.d.ts +2 -1
- package/dist/framework/framework-construction.error.js +11 -1
- package/dist/framework/framework.d.ts +4 -4
- package/dist/framework/framework.factory.js +39 -11
- package/dist/framework/operation-facade.d.ts +29 -10
- package/dist/framework/operation-facade.js +2 -1
- package/dist/index.d.ts +10 -4
- package/dist/internal.d.ts +2 -2
- package/dist/internal.js +2 -0
- package/dist/operations/operation-arguments.d.ts +44 -28
- package/dist/operations/operation-call.d.ts +9 -5
- package/dist/operations/operation-result.d.ts +6 -4
- package/dist/operations/operation-validator-core.js +6 -0
- package/dist/policy/policy-admission.d.ts +22 -0
- package/dist/policy/policy-admission.js +409 -0
- package/dist/policy/policy-after-update.runtime.d.ts +29 -0
- package/dist/policy/policy-after-update.runtime.js +132 -0
- package/dist/policy/policy-condition.d.ts +15 -0
- package/dist/policy/policy-condition.js +36 -0
- package/dist/policy/policy-engine.d.ts +118 -0
- package/dist/policy/policy-engine.js +1 -0
- package/dist/policy/policy-nested-write.runtime.d.ts +38 -0
- package/dist/policy/policy-nested-write.runtime.js +453 -0
- package/dist/policy/policy-read.runtime.d.ts +81 -0
- package/dist/policy/policy-read.runtime.js +503 -0
- package/dist/policy/policy-request.d.ts +41 -0
- package/dist/policy/policy-request.js +63 -0
- package/dist/policy/policy-session.d.ts +93 -0
- package/dist/policy/policy-session.js +235 -0
- package/dist/policy/policy-startup.validator.d.ts +8 -0
- package/dist/policy/policy-startup.validator.js +104 -0
- package/dist/policy/policy-write.runtime.d.ts +37 -0
- package/dist/policy/policy-write.runtime.js +409 -0
- package/dist/prebuilt/core-version.generated.d.ts +2 -0
- package/dist/prebuilt/core-version.generated.js +1 -0
- package/dist/prebuilt/prebuilt-changes.d.ts +28 -0
- package/dist/prebuilt/prebuilt-changes.js +65 -0
- package/dist/prebuilt/prebuilt-contracts.d.ts +40 -0
- package/dist/prebuilt/prebuilt-contracts.emitter.d.ts +16 -0
- package/dist/prebuilt/prebuilt-contracts.emitter.js +50 -0
- package/dist/prebuilt/prebuilt-contracts.js +1 -0
- package/dist/prebuilt/prebuilt-contracts.loader.d.ts +21 -0
- package/dist/prebuilt/prebuilt-contracts.loader.js +77 -0
- package/dist/prebuilt/prebuilt-inputs.fingerprint.d.ts +16 -0
- package/dist/prebuilt/prebuilt-inputs.fingerprint.js +192 -0
- package/dist/protocol/absent-route.responder.d.ts +4 -4
- package/dist/protocol/http-request.decoder.d.ts +2 -2
- package/dist/protocol/http-request.reader.d.ts +2 -2
- package/dist/protocol/protocol-headers.d.ts +3 -3
- package/dist/runtime/computed-read-runtime.d.ts +6 -1
- package/dist/runtime/computed-read-runtime.js +226 -52
- package/dist/runtime/context-executor.runtime.d.ts +2 -2
- package/dist/runtime/context-executor.runtime.js +4 -2
- package/dist/runtime/create-requirement.js +1 -1
- package/dist/runtime/diagnostics-reporter.d.ts +5 -5
- package/dist/runtime/execution-options.d.ts +8 -0
- package/dist/runtime/framework-warning.emitter.d.ts +6 -0
- package/dist/runtime/framework-warning.emitter.js +8 -0
- package/dist/runtime/level-lifecycle.runtime.js +3 -2
- package/dist/runtime/operation-code.guidance.js +3 -3
- package/dist/runtime/operation-request.d.ts +16 -9
- package/dist/runtime/operation-runtime.d.ts +15 -7
- package/dist/runtime/operation-runtime.js +139 -14
- package/dist/runtime/owned-execution.registry.d.ts +2 -2
- package/dist/runtime/readonly-value.d.ts +5 -1
- package/dist/runtime/readonly-value.js +67 -28
- package/dist/runtime/referential-action.d.ts +1 -1
- package/dist/transactions/sql-statement.d.ts +2 -2
- package/dist/transactions/transaction-plan-assembler.d.ts +2 -2
- package/dist/transactions/transaction-plan-validator.d.ts +5 -5
- package/dist/transactions/transaction-request.d.ts +8 -8
- package/dist/transactions/transaction-runtime.d.ts +5 -2
- package/dist/transactions/transaction-runtime.js +33 -6
- package/package.json +2 -2
|
@@ -3,6 +3,9 @@ import { createComputedFields } from "../config/field-compute.resolver.js";
|
|
|
3
3
|
import { fieldStagesOf, isForcedOperation, nestedTargetLevels, operationLevels, rawPipelineTree, transactionLevelsOf, } from "../config/pipeline-tree.resolver.js";
|
|
4
4
|
import { AVENTARA_PROTOCOL_VERSION } from "../contracts/contract-protocol.js";
|
|
5
5
|
import { findOperationAvailabilityIssue, validateOperationArguments, } from "../operations/operation-validator.js";
|
|
6
|
+
import { enforceReadPolicy, } from "../policy/policy-read.runtime.js";
|
|
7
|
+
import { admitRequestPolicy, openRequestPolicy, } from "../policy/policy-request.js";
|
|
8
|
+
import { enforceWritePolicy, writeNeedsTransaction, } from "../policy/policy-write.runtime.js";
|
|
6
9
|
import { transactionInfrastructureFailure } from "../transactions/transaction-response.js";
|
|
7
10
|
import { settleTransaction } from "../transactions/transaction-settlement.runtime.js";
|
|
8
11
|
import { TRANSACTION_PIPES, transactionFacts, } from "../transactions/transaction-stage.runtime.js";
|
|
@@ -40,6 +43,22 @@ function scopeOf(request, binding) {
|
|
|
40
43
|
export function withRequestState(request) {
|
|
41
44
|
return { ...request, state: requestStateOf(request.state) };
|
|
42
45
|
}
|
|
46
|
+
export function withRequestPolicy(dependencies, request, caller, report) {
|
|
47
|
+
const failed = (error) => {
|
|
48
|
+
report(error, "A3000");
|
|
49
|
+
return internalFailure();
|
|
50
|
+
};
|
|
51
|
+
const withPolicy = (policy) => policy === undefined ? request : { ...request, policy };
|
|
52
|
+
try {
|
|
53
|
+
const policy = openRequestPolicy(dependencies.config.policy, dependencies.contracts.core, request, caller, dependencies.adapter);
|
|
54
|
+
return policy instanceof Promise
|
|
55
|
+
? policy.then(withPolicy, failed)
|
|
56
|
+
: withPolicy(policy);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
return failed(error);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
43
62
|
export function createPipelineDependencies(dependencies, model) {
|
|
44
63
|
const misuse = createMisuseReporter(dependencies.config.misuse);
|
|
45
64
|
const recordWrites = recordWriteFactsOf({
|
|
@@ -170,7 +189,7 @@ function outputAccepted(contract, request, projection, data, report) {
|
|
|
170
189
|
}
|
|
171
190
|
export function createOperationRuntime(dependencies, pipelineDependencies) {
|
|
172
191
|
const misuse = createMisuseReporter(dependencies.config.misuse);
|
|
173
|
-
return async (request, report, source) => {
|
|
192
|
+
return async (request, report, source, caller) => {
|
|
174
193
|
let validated;
|
|
175
194
|
try {
|
|
176
195
|
validated = validateOperation(dependencies, request, source, misuse);
|
|
@@ -184,7 +203,11 @@ export function createOperationRuntime(dependencies, pipelineDependencies) {
|
|
|
184
203
|
if (!validated.ok) {
|
|
185
204
|
return validated.response;
|
|
186
205
|
}
|
|
187
|
-
const
|
|
206
|
+
const opened = withRequestPolicy(dependencies, withRequestState(request), caller, report);
|
|
207
|
+
const stateful = opened instanceof Promise ? await opened : opened;
|
|
208
|
+
if (!("state" in stateful)) {
|
|
209
|
+
return stateful;
|
|
210
|
+
}
|
|
188
211
|
const forced = isForcedOperation(activeScopeConfig(dependencies.config, request).pipelines, request);
|
|
189
212
|
const admitted = await admitOperation(pipelineDependencies, stateful, validated.contract, validated.arguments, OPERATION_ARGUMENTS_PATH, report, {
|
|
190
213
|
requestId: request.requestId,
|
|
@@ -194,8 +217,20 @@ export function createOperationRuntime(dependencies, pipelineDependencies) {
|
|
|
194
217
|
if (!admitted.ok) {
|
|
195
218
|
return admitted.response;
|
|
196
219
|
}
|
|
197
|
-
|
|
198
|
-
|
|
220
|
+
let checkedWrite = false;
|
|
221
|
+
if (stateful.policy !== undefined &&
|
|
222
|
+
request.family !== "find" &&
|
|
223
|
+
validated.contract.transactions === "interactive" &&
|
|
224
|
+
!forced) {
|
|
225
|
+
try {
|
|
226
|
+
checkedWrite = await writeNeedsTransaction(stateful.policy, dependencies.contracts.core, stateful, () => planReadOperation(dependencies.contracts.core, dependencies.config.adapter.model, validated.contract, activeScopeConfig(dependencies.config, request).compute, stateful, admitted.args), admitted.args);
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
return sanitizeOperationFailure(error, stateful.policy.execution.boundary.originOf(error), report, { path: OPERATION_ARGUMENTS_PATH, arguments: admitted.args });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (forced || checkedWrite) {
|
|
233
|
+
return executeForcedOperation(pipelineDependencies, dependencies.adapter, stateful, validated.contract, admitted.args, report, forced);
|
|
199
234
|
}
|
|
200
235
|
return executeAdmittedOperation(pipelineDependencies, stateful, validated.contract, admitted.args, OPERATION_ARGUMENTS_PATH, { kind: "standalone", executor: dependencies.adapter }, report);
|
|
201
236
|
};
|
|
@@ -346,6 +381,12 @@ export async function admitOperation(dependencies, request, contract, args, argu
|
|
|
346
381
|
if (absent.length > 0) {
|
|
347
382
|
return { ok: false, response: mapValidationOutcome(absent) };
|
|
348
383
|
}
|
|
384
|
+
if (request.policy !== undefined) {
|
|
385
|
+
const refused = await admitRequestPolicy(request.policy, dependencies.contracts.core, contract, request, current, report, { path: argumentsPath, arguments: current });
|
|
386
|
+
if (refused !== undefined) {
|
|
387
|
+
return { ok: false, response: refused };
|
|
388
|
+
}
|
|
389
|
+
}
|
|
349
390
|
try {
|
|
350
391
|
return await admit();
|
|
351
392
|
}
|
|
@@ -441,7 +482,7 @@ export async function prepareStep(dependencies, request, contract, args, argumen
|
|
|
441
482
|
};
|
|
442
483
|
}
|
|
443
484
|
}
|
|
444
|
-
async function executeForcedOperation(dependencies, adapter, request, contract, args, report) {
|
|
485
|
+
async function executeForcedOperation(dependencies, adapter, request, contract, args, report, forced) {
|
|
445
486
|
const plan = [
|
|
446
487
|
{
|
|
447
488
|
resource: request.resource,
|
|
@@ -452,7 +493,9 @@ async function executeForcedOperation(dependencies, adapter, request, contract,
|
|
|
452
493
|
];
|
|
453
494
|
const settled = await settleTransaction({
|
|
454
495
|
adapter,
|
|
455
|
-
levels:
|
|
496
|
+
levels: forced
|
|
497
|
+
? transactionLevelsOf(activeScopeConfig(dependencies.config, request).pipelines, request.resource)
|
|
498
|
+
: [],
|
|
456
499
|
plan,
|
|
457
500
|
contextOf: (current, executor) => dependencies.contexts.transaction({
|
|
458
501
|
requestId: request.requestId,
|
|
@@ -544,6 +587,25 @@ export async function executeAdmittedOperation(dependencies, request, contract,
|
|
|
544
587
|
throw hiddenRefusal;
|
|
545
588
|
}
|
|
546
589
|
args = deepFreeze(computed);
|
|
590
|
+
let policyWrite;
|
|
591
|
+
if (request.policy !== undefined && request.family !== "find") {
|
|
592
|
+
const planned = planReadOperation(dependencies.contracts.core, dependencies.config.adapter.model, contract, scopeConfig.compute, request, args);
|
|
593
|
+
const plannedArguments = await stripVirtualMutationInput(contracts, request, planned.arguments);
|
|
594
|
+
try {
|
|
595
|
+
policyWrite = await enforceWritePolicy(request.policy.on({ executor, boundary: adapterBoundary }), dependencies.contracts.core, dependencies.config.adapter.model, request, planned, plannedArguments, binding.kind === "transaction");
|
|
596
|
+
}
|
|
597
|
+
catch (error) {
|
|
598
|
+
return sanitizeOperationFailure(error, adapterBoundary.originOf(error), report, { path: argumentsPath, arguments: args });
|
|
599
|
+
}
|
|
600
|
+
if (policyWrite.kind === "refused") {
|
|
601
|
+
return policyWrite.response;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
let policyFailure;
|
|
605
|
+
const refuseWrite = (response) => {
|
|
606
|
+
policyFailure = response;
|
|
607
|
+
throw thrownFailure(response);
|
|
608
|
+
};
|
|
547
609
|
let readMaterialized = false;
|
|
548
610
|
let executedReadPlan;
|
|
549
611
|
const materialize = async (data, plan) => {
|
|
@@ -552,24 +614,87 @@ export async function executeAdmittedOperation(dependencies, request, contract,
|
|
|
552
614
|
return materializeReadOperation(contract, scopeConfig.compute, request, plan, data, computeContext);
|
|
553
615
|
};
|
|
554
616
|
const invoke = async () => {
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
617
|
+
let readPlan = planReadOperation(dependencies.contracts.core, dependencies.config.adapter.model, contract, scopeConfig.compute, request, args);
|
|
618
|
+
let adapterArguments = await stripVirtualMutationInput(contracts, request, readPlan.arguments);
|
|
619
|
+
let operation = request;
|
|
620
|
+
let settle;
|
|
621
|
+
let writeMiss;
|
|
622
|
+
let failedWriteMiss;
|
|
623
|
+
if (policyWrite !== undefined && policyWrite.kind !== "refused") {
|
|
624
|
+
writeMiss = policyWrite.miss;
|
|
625
|
+
failedWriteMiss = policyWrite.failedMiss;
|
|
626
|
+
if (policyWrite.kind === "empty") {
|
|
627
|
+
if (writeMiss !== undefined && policyWrite.data === null) {
|
|
628
|
+
refuseWrite(await writeMiss());
|
|
629
|
+
}
|
|
630
|
+
return materialize(policyWrite.data, readPlan);
|
|
631
|
+
}
|
|
632
|
+
operation = policyWrite.identity;
|
|
633
|
+
adapterArguments = policyWrite.arguments;
|
|
634
|
+
readPlan = policyWrite.plan;
|
|
635
|
+
settle = policyWrite.settle;
|
|
636
|
+
}
|
|
637
|
+
else if (request.policy !== undefined && request.family === "find") {
|
|
638
|
+
const read = await enforceReadPolicy(request.policy.on({ executor, boundary: adapterBoundary }), dependencies.contracts.core, dependencies.config.adapter.model, request, readPlan, adapterArguments);
|
|
639
|
+
if (read.kind === "refused") {
|
|
640
|
+
throw thrownFailure(read.response);
|
|
641
|
+
}
|
|
642
|
+
if (read.kind === "empty") {
|
|
643
|
+
const miss = uniqueMissFailure(request, read.data);
|
|
644
|
+
if (miss !== undefined) {
|
|
645
|
+
throw thrownFailure(miss);
|
|
646
|
+
}
|
|
647
|
+
return materialize(read.data, readPlan);
|
|
648
|
+
}
|
|
649
|
+
operation = read.identity;
|
|
650
|
+
adapterArguments = read.arguments;
|
|
651
|
+
readPlan = read.plan;
|
|
652
|
+
settle = read.settle;
|
|
653
|
+
}
|
|
654
|
+
const { policy: _session, ...identity } = operation;
|
|
655
|
+
let adapterData;
|
|
656
|
+
try {
|
|
657
|
+
adapterData = await adapterBoundary.execute(() => executor.execute({
|
|
658
|
+
...identity,
|
|
659
|
+
resource: request.resource,
|
|
660
|
+
arguments: adapterArguments,
|
|
661
|
+
}));
|
|
662
|
+
}
|
|
663
|
+
catch (error) {
|
|
664
|
+
const missed = failedWriteMiss === undefined
|
|
665
|
+
? undefined
|
|
666
|
+
: await failedWriteMiss().catch(() => undefined);
|
|
667
|
+
if (missed !== undefined) {
|
|
668
|
+
refuseWrite(missed);
|
|
669
|
+
}
|
|
670
|
+
throw error;
|
|
671
|
+
}
|
|
672
|
+
if (writeMiss !== undefined && adapterData === null) {
|
|
673
|
+
refuseWrite(await writeMiss());
|
|
674
|
+
}
|
|
563
675
|
const miss = uniqueMissFailure(request, adapterData);
|
|
564
676
|
if (miss !== undefined) {
|
|
565
677
|
throw thrownFailure(miss);
|
|
566
678
|
}
|
|
679
|
+
if (settle !== undefined) {
|
|
680
|
+
const settled = await settle(adapterData);
|
|
681
|
+
if (!settled.ok) {
|
|
682
|
+
if (policyWrite !== undefined) {
|
|
683
|
+
refuseWrite(settled.response);
|
|
684
|
+
}
|
|
685
|
+
throw thrownFailure(settled.response);
|
|
686
|
+
}
|
|
687
|
+
adapterData = settled.data;
|
|
688
|
+
}
|
|
567
689
|
return materialize(adapterData, readPlan);
|
|
568
690
|
};
|
|
569
691
|
const materialized = async (data) => readMaterialized
|
|
570
692
|
? data
|
|
571
693
|
: materialize(data, planReadOperation(dependencies.contracts.core, dependencies.config.adapter.model, contract, scopeConfig.compute, request, args));
|
|
572
694
|
const data = await materialized(await runLevelLifecycle(levels, context, invoke, materialized, reuse, () => new AvInterceptorNextError()));
|
|
695
|
+
if (policyFailure !== undefined) {
|
|
696
|
+
return policyFailure;
|
|
697
|
+
}
|
|
573
698
|
if (!outputAccepted(contract, request, executedReadPlan?.projection, data, report)) {
|
|
574
699
|
return internalFailure();
|
|
575
700
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AvAgentTransactionRequest, AvClientTransactionRequest } from "../transactions/transaction-request.js";
|
|
2
2
|
import type { AvTransactionResponse } from "../transactions/transaction-response.js";
|
|
3
3
|
import type { AvCallerDataError } from "./caller-data.copier.js";
|
|
4
4
|
import type { RemoteOperationRequest } from "./operation-request.js";
|
|
@@ -15,7 +15,7 @@ export type OwnedExecution = {
|
|
|
15
15
|
* at the call, and these steps' arguments could not be copied there: each is
|
|
16
16
|
* answered at its turn as the door answers that failure.
|
|
17
17
|
*/
|
|
18
|
-
readonly executeTransaction: (request:
|
|
18
|
+
readonly executeTransaction: (request: AvClientTransactionRequest | AvAgentTransactionRequest, refused?: ReadonlyMap<number, AvCallerDataError>) => Promise<AvTransactionResponse>;
|
|
19
19
|
};
|
|
20
20
|
/** Records `framework`'s owned entries; called once, at construction. */
|
|
21
21
|
export declare function recordOwnedExecution(framework: object, execution: OwnedExecution): void;
|
|
@@ -32,8 +32,12 @@ export declare function deepFreeze<T>(value: T): T;
|
|
|
32
32
|
* only reads is told — the validated arguments after validation, a result in an
|
|
33
33
|
* after hook — so neither what is written nor the result the caller receives can
|
|
34
34
|
* be changed through it.
|
|
35
|
+
*
|
|
36
|
+
* With `snapshot`, a copied container found to hold no `Date` or `Uint8Array`
|
|
37
|
+
* is remembered as such at once, so a later `frozenCopy` of the copy shares it
|
|
38
|
+
* without walking it: for a copy that is itself copied again for each stage.
|
|
35
39
|
*/
|
|
36
|
-
export declare function frozenCopy<T>(value: T): AvDeepReadonly<T>;
|
|
40
|
+
export declare function frozenCopy<T>(value: T, snapshot?: boolean): AvDeepReadonly<T>;
|
|
37
41
|
/**
|
|
38
42
|
* A read-only view of `value`: reads see the live value, nested plain objects
|
|
39
43
|
* and arrays as views too, and any write, definition or deletion throws a
|
|
@@ -37,38 +37,79 @@ function scalarCopy(value) {
|
|
|
37
37
|
return undefined;
|
|
38
38
|
}
|
|
39
39
|
const SCALAR_FREE = new WeakSet();
|
|
40
|
-
|
|
40
|
+
function scalarFree(value) {
|
|
41
|
+
return Object.isFrozen(value) && SCALAR_FREE.has(value);
|
|
42
|
+
}
|
|
43
|
+
const WALKING = Symbol("walking");
|
|
44
|
+
function place(target, key, value, enumerable) {
|
|
45
|
+
if (enumerable && typeof key === "string" && !(key in target)) {
|
|
46
|
+
target[key] = value;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
Object.defineProperty(target, key, {
|
|
50
|
+
value,
|
|
51
|
+
enumerable,
|
|
52
|
+
writable: true,
|
|
53
|
+
configurable: true,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
export function frozenCopy(value, snapshot = false) {
|
|
57
|
+
const scalar = scalarCopy(value);
|
|
58
|
+
if (scalar !== undefined) {
|
|
59
|
+
return scalar;
|
|
60
|
+
}
|
|
61
|
+
if (!isContainer(value) || scalarFree(value)) {
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
41
64
|
const copies = new Map();
|
|
42
|
-
const walking = new Set();
|
|
43
65
|
let cycled = false;
|
|
66
|
+
let holdsScalar = false;
|
|
44
67
|
const copy = (current) => {
|
|
68
|
+
if (current === null || typeof current !== "object") {
|
|
69
|
+
return current;
|
|
70
|
+
}
|
|
45
71
|
const scalar = scalarCopy(current);
|
|
46
72
|
if (scalar !== undefined) {
|
|
73
|
+
holdsScalar = true;
|
|
47
74
|
return scalar;
|
|
48
75
|
}
|
|
49
|
-
if (!isContainer(current) ||
|
|
76
|
+
if (!isContainer(current) || scalarFree(current)) {
|
|
50
77
|
return current;
|
|
51
78
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
79
|
+
const known = copies.get(current);
|
|
80
|
+
if (known === WALKING) {
|
|
81
|
+
cycled = true;
|
|
82
|
+
return current;
|
|
83
|
+
}
|
|
84
|
+
if (known !== undefined) {
|
|
85
|
+
holdsScalar ||= !scalarFree(known);
|
|
86
|
+
return known;
|
|
55
87
|
}
|
|
56
|
-
copies.set(current,
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
let changed = !DEEP_FROZEN.has(current);
|
|
88
|
+
copies.set(current, WALKING);
|
|
89
|
+
const outer = holdsScalar;
|
|
90
|
+
holdsScalar = false;
|
|
91
|
+
let changed = !(Object.isFrozen(current) && DEEP_FROZEN.has(current));
|
|
92
|
+
const array = Array.isArray(current);
|
|
93
|
+
const prototype = Object.getPrototypeOf(current);
|
|
94
|
+
const target = array
|
|
95
|
+
? new Array(current.length)
|
|
96
|
+
: prototype === Object.prototype
|
|
97
|
+
? {}
|
|
98
|
+
: Object.create(prototype);
|
|
60
99
|
for (const key of Reflect.ownKeys(current)) {
|
|
100
|
+
if (array && key === "length") {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
61
103
|
const descriptor = Object.getOwnPropertyDescriptor(current, key);
|
|
62
|
-
if (descriptor === undefined ||
|
|
63
|
-
!("value" in descriptor) ||
|
|
64
|
-
(Array.isArray(current) && key === "length")) {
|
|
104
|
+
if (descriptor === undefined || !("value" in descriptor)) {
|
|
65
105
|
continue;
|
|
66
106
|
}
|
|
67
107
|
const next = copy(descriptor.value);
|
|
68
108
|
changed ||= next !== descriptor.value;
|
|
69
|
-
|
|
109
|
+
place(target, key, next, descriptor.enumerable === true);
|
|
70
110
|
}
|
|
71
|
-
|
|
111
|
+
const held = holdsScalar;
|
|
112
|
+
holdsScalar = outer || held;
|
|
72
113
|
if (!changed) {
|
|
73
114
|
copies.set(current, current);
|
|
74
115
|
if (!cycled) {
|
|
@@ -76,20 +117,18 @@ export function frozenCopy(value) {
|
|
|
76
117
|
}
|
|
77
118
|
return current;
|
|
78
119
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
});
|
|
120
|
+
Object.freeze(target);
|
|
121
|
+
DEEP_FROZEN.add(target);
|
|
122
|
+
if (cycled) {
|
|
123
|
+
for (const key of Reflect.ownKeys(target)) {
|
|
124
|
+
deepFreeze(target[key]);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (snapshot && !held && !cycled) {
|
|
128
|
+
SCALAR_FREE.add(target);
|
|
89
129
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
return frozen;
|
|
130
|
+
copies.set(current, target);
|
|
131
|
+
return target;
|
|
93
132
|
};
|
|
94
133
|
return copy(value);
|
|
95
134
|
}
|
|
@@ -29,7 +29,7 @@ export type ReferentialEdge = {
|
|
|
29
29
|
* relation `field` and any of these names the reference twice and is refused;
|
|
30
30
|
* a required relation may be written through all of them instead.
|
|
31
31
|
*/
|
|
32
|
-
readonly
|
|
32
|
+
readonly referenceFields: readonly string[];
|
|
33
33
|
/** What deleting a `target` row does to rows of the ENCLOSING Resource. */
|
|
34
34
|
readonly onDelete: ReferentialDeleteEffect;
|
|
35
35
|
};
|
|
@@ -10,7 +10,7 @@ import type { AvTransactionReference } from "./transaction-reference.js";
|
|
|
10
10
|
* `${value}::jsonb`), or a `$ref` to an earlier step's field, resolved when the
|
|
11
11
|
* earlier steps have run and then bound like any other value.
|
|
12
12
|
*/
|
|
13
|
-
export type
|
|
13
|
+
export type AvSqlStatementValue = AvAdapterSqlValue | AvTransactionReference;
|
|
14
14
|
/**
|
|
15
15
|
* `framework.appTx.$sql`: a developer SQL step, written only as a tagged template —
|
|
16
16
|
* ``framework.appTx.$sql`select pgmq.send('jobs', jsonb_build_object('id', ${ref}))` ``.
|
|
@@ -23,7 +23,7 @@ export type SqlStatementValue = AvAdapterSqlValue | AvTransactionReference;
|
|
|
23
23
|
* SQL step's rows. No guard, pipe, hook, interceptor or filter runs around it: it
|
|
24
24
|
* has no Resource or operation for one to wrap.
|
|
25
25
|
*/
|
|
26
|
-
export type
|
|
26
|
+
export type AvSqlStatementBuilder = <Row = unknown>(strings: TemplateStringsArray, ...values: readonly AvSqlStatementValue[]) => AvOperation<Row[], "application">;
|
|
27
27
|
/** One value of a planned SQL step, as the typed runner bound it. */
|
|
28
28
|
export type PlannedSqlValue = {
|
|
29
29
|
readonly kind: "value";
|
|
@@ -2,7 +2,7 @@ import type { AvContracts } from "../compiler/contract.compiler.js";
|
|
|
2
2
|
import type { AvOperationIdentity } from "../operations/operation-identity.js";
|
|
3
3
|
import { AvCallerDataError } from "../runtime/caller-data.copier.js";
|
|
4
4
|
import type { MisuseReporter } from "../runtime/misuse-reporter.js";
|
|
5
|
-
import type {
|
|
5
|
+
import type { AvRequestProtocol } from "../runtime/operation-request.js";
|
|
6
6
|
import type { AvOperationFailureResponse } from "../runtime/operation-response.js";
|
|
7
7
|
import type { AvOperation, OperationFingerprint } from "./deferred-operation.js";
|
|
8
8
|
import type { PlannedSqlStatement } from "./sql-statement.js";
|
|
@@ -49,5 +49,5 @@ export type TransactionPlanAssembler = {
|
|
|
49
49
|
* handle, so a handle another instance built — typed against another model,
|
|
50
50
|
* bound to other Contracts — is refused rather than run.
|
|
51
51
|
*/
|
|
52
|
-
export declare function createTransactionPlanAssembler(contracts: AvContracts, protocol:
|
|
52
|
+
export declare function createTransactionPlanAssembler(contracts: AvContracts, protocol: AvRequestProtocol, misuse: MisuseReporter): TransactionPlanAssembler;
|
|
53
53
|
export {};
|
|
@@ -4,7 +4,7 @@ import type { AvScalarField } from "../contracts/field.js";
|
|
|
4
4
|
import type { SemanticOperationArguments } from "../operations/operation-validator.js";
|
|
5
5
|
import { AvCallerDataError } from "../runtime/caller-data.copier.js";
|
|
6
6
|
import { type ReadProjectionPlan } from "../runtime/computed-read-runtime.js";
|
|
7
|
-
import type {
|
|
7
|
+
import type { AvOperationRequest } from "../runtime/operation-request.js";
|
|
8
8
|
import type { AvOperationFailureResponse } from "../runtime/operation-response.js";
|
|
9
9
|
import type { OperationPipelineDependencies } from "../runtime/operation-runtime.js";
|
|
10
10
|
import type { CascadeClosure } from "../runtime/referential-action.js";
|
|
@@ -13,7 +13,7 @@ import type { PlannedSqlStatement, ValidatedSqlStatement } from "./sql-statement
|
|
|
13
13
|
import type { AvTransactionReference } from "./transaction-reference.js";
|
|
14
14
|
import { type TransactionSlotShape } from "./transaction-reference.js";
|
|
15
15
|
import { type TransactionReferenceCandidateSlot } from "./transaction-reference-collector.js";
|
|
16
|
-
import type {
|
|
16
|
+
import type { AvTransactionRequest, TransactionOperationOrigin } from "./transaction-request.js";
|
|
17
17
|
/**
|
|
18
18
|
* Where a plan's step arguments came from, so the validator knows whether to copy
|
|
19
19
|
* them at the door (see `ArgumentsSource`):
|
|
@@ -65,7 +65,7 @@ export type TransactionReferenceSlot = {
|
|
|
65
65
|
};
|
|
66
66
|
export type CandidateTransactionOperation = {
|
|
67
67
|
readonly index: number;
|
|
68
|
-
readonly request:
|
|
68
|
+
readonly request: AvOperationRequest;
|
|
69
69
|
readonly arguments: SemanticOperationArguments;
|
|
70
70
|
readonly projection: ReadProjectionPlan | undefined;
|
|
71
71
|
readonly declaredFingerprint: unknown;
|
|
@@ -74,7 +74,7 @@ export type CandidateTransactionOperation = {
|
|
|
74
74
|
export type ValidatedTransactionOperation = {
|
|
75
75
|
readonly kind: "operation";
|
|
76
76
|
readonly index: number;
|
|
77
|
-
readonly request:
|
|
77
|
+
readonly request: AvOperationRequest;
|
|
78
78
|
/**
|
|
79
79
|
* The Contract of this operation's origin, which is the plan's: the one it was
|
|
80
80
|
* validated against, the one it runs under, and the one a later `$ref` reads its
|
|
@@ -118,4 +118,4 @@ export declare function transactionsUnavailableFailure(): AvOperationFailureResp
|
|
|
118
118
|
/**
|
|
119
119
|
* Whole-plan Framework Validation Layer for transaction structure and identity.
|
|
120
120
|
*/
|
|
121
|
-
export declare function validateTransactionPlan<M extends AvAdapterModel>(dependencies: TransactionPlanDependencies<M>, request:
|
|
121
|
+
export declare function validateTransactionPlan<M extends AvAdapterModel>(dependencies: TransactionPlanDependencies<M>, request: AvTransactionRequest, origins?: readonly TransactionOperationOrigin[], planArguments?: PlanArguments): Promise<TransactionPlanValidationResult>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AvRequestProtocol } from "../runtime/operation-request.js";
|
|
2
2
|
import type { AvTransportContext } from "../runtime/pipeline.js";
|
|
3
3
|
import type { AvState } from "../runtime/request-state.js";
|
|
4
4
|
type TransactionRequestBase = {
|
|
@@ -11,29 +11,29 @@ type TransactionRequestBase = {
|
|
|
11
11
|
readonly state?: AvState;
|
|
12
12
|
};
|
|
13
13
|
/** An in-process transaction request, run under the Application Contract. */
|
|
14
|
-
export type
|
|
14
|
+
export type AvApplicationTransactionRequest = TransactionRequestBase & {
|
|
15
15
|
readonly origin: "application";
|
|
16
16
|
readonly protocol?: never;
|
|
17
17
|
readonly transport?: never;
|
|
18
18
|
};
|
|
19
19
|
/** A remote transaction request, run under the Client Contract. */
|
|
20
|
-
export type
|
|
20
|
+
export type AvClientTransactionRequest = TransactionRequestBase & {
|
|
21
21
|
readonly origin: "client";
|
|
22
|
-
readonly protocol:
|
|
22
|
+
readonly protocol: AvRequestProtocol;
|
|
23
23
|
readonly transport?: AvTransportContext;
|
|
24
24
|
};
|
|
25
25
|
/** An AI agent's transaction request, run under the Agent Contract. */
|
|
26
|
-
export type
|
|
26
|
+
export type AvAgentTransactionRequest = TransactionRequestBase & {
|
|
27
27
|
readonly origin: "agent";
|
|
28
|
-
readonly protocol:
|
|
28
|
+
readonly protocol: AvRequestProtocol;
|
|
29
29
|
readonly transport?: AvTransportContext;
|
|
30
30
|
};
|
|
31
31
|
/** One transaction request, as `framework.executeTransaction` takes it. */
|
|
32
|
-
export type
|
|
32
|
+
export type AvTransactionRequest = AvApplicationTransactionRequest | AvClientTransactionRequest | AvAgentTransactionRequest;
|
|
33
33
|
/**
|
|
34
34
|
* The origin one plan operation runs under: its own layer's Contract, its own
|
|
35
35
|
* pipelines, and — for a client or agent step — the protocol identity it is
|
|
36
36
|
* accepted with.
|
|
37
37
|
*/
|
|
38
|
-
export type TransactionOperationOrigin = Pick<
|
|
38
|
+
export type TransactionOperationOrigin = Pick<AvApplicationTransactionRequest, "origin"> | Pick<AvClientTransactionRequest, "origin" | "protocol" | "transport"> | Pick<AvAgentTransactionRequest, "origin" | "protocol" | "transport">;
|
|
39
39
|
export {};
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { AvAdapterModel } from "../adapters/adapter-model.js";
|
|
2
|
+
import { type PolicyCaller } from "../policy/policy-request.js";
|
|
2
3
|
import type { TransactionDiagnosticsChannel } from "../runtime/diagnostics-reporter.js";
|
|
3
4
|
import type { OperationPipelineDependencies, OperationRuntimeDependencies } from "../runtime/operation-runtime.js";
|
|
4
5
|
import type { PlanArguments } from "./transaction-plan-validator.js";
|
|
5
|
-
import type {
|
|
6
|
+
import type { AvTransactionRequest, TransactionOperationOrigin } from "./transaction-request.js";
|
|
6
7
|
import type { AvTransactionResponse } from "./transaction-response.js";
|
|
7
8
|
/**
|
|
8
9
|
* The transaction executor core. Like the operation executor it owns no
|
|
@@ -13,7 +14,9 @@ import type { AvTransactionResponse } from "./transaction-response.js";
|
|
|
13
14
|
* runner's mixed plans), and `planArguments` whether the steps' arguments are
|
|
14
15
|
* copied at the door; see `validateTransactionPlan`.
|
|
15
16
|
*/
|
|
16
|
-
export type FrameworkTransactionExecutor = (request:
|
|
17
|
+
export type FrameworkTransactionExecutor = (request: AvTransactionRequest, diagnostics: TransactionDiagnosticsChannel, origins: readonly TransactionOperationOrigin[] | undefined, planArguments: PlanArguments,
|
|
18
|
+
/** The principal an application run's steps run as (`{ as }`), when it names one. */
|
|
19
|
+
caller?: PolicyCaller) => Promise<AvTransactionResponse>;
|
|
17
20
|
/**
|
|
18
21
|
* Validates a whole plan through the Framework Validation Layer, then runs it inside
|
|
19
22
|
* exactly one Adapter transaction. The touched Resources' transaction stages and
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { transactionLevelsOf } from "../config/pipeline-tree.resolver.js";
|
|
2
|
+
import { openRequestPolicy, } from "../policy/policy-request.js";
|
|
2
3
|
import { AdapterBoundary, planOperationArgumentsPath, planStepPath, sanitizeOperationFailure, } from "../runtime/error-boundary.js";
|
|
3
4
|
import { internalFailure, operationSuccess, } from "../runtime/operation-response.js";
|
|
4
5
|
import { admitOperation, EVERY_FIELD, executeAdmittedOperation, prepareStep, STEP_PIPES, withRequestState, } from "../runtime/operation-runtime.js";
|
|
@@ -20,13 +21,13 @@ export function createTransactionRuntime(dependencies, pipelineDependencies) {
|
|
|
20
21
|
cascades: resolveCascadeClosure(adapter.referentialActions),
|
|
21
22
|
sqlStatements: adapter.sqlStatements === true,
|
|
22
23
|
};
|
|
23
|
-
return async (seeded, diagnostics, origins, planArguments) => {
|
|
24
|
+
return async (seeded, diagnostics, origins, planArguments, caller) => {
|
|
24
25
|
const request = withRequestState(seeded);
|
|
25
26
|
const validation = await validateTransactionPlan(planDependencies, request, origins, planArguments);
|
|
26
27
|
if (!validation.ok) {
|
|
27
28
|
return validation.response;
|
|
28
29
|
}
|
|
29
|
-
return executeTransactionPlan(
|
|
30
|
+
return executeTransactionPlan(dependencies, pipelineDependencies, request, validation.plan, request.state, diagnostics, caller);
|
|
30
31
|
};
|
|
31
32
|
}
|
|
32
33
|
function planOrigin(request, plan) {
|
|
@@ -79,8 +80,24 @@ function standingReferences(core, operation, args, steps, subject) {
|
|
|
79
80
|
}
|
|
80
81
|
return { ok: false, ...refused };
|
|
81
82
|
}
|
|
82
|
-
async function executeTransactionPlan(
|
|
83
|
-
const
|
|
83
|
+
async function executeTransactionPlan(runtime, dependencies, request, plan, state, diagnostics, caller) {
|
|
84
|
+
const { adapter } = runtime;
|
|
85
|
+
const steps = new Map();
|
|
86
|
+
const stateful = (operation) => {
|
|
87
|
+
const known = steps.get(operation);
|
|
88
|
+
if (known !== undefined) {
|
|
89
|
+
return known;
|
|
90
|
+
}
|
|
91
|
+
const settle = (policy) => {
|
|
92
|
+
const step = policy === undefined
|
|
93
|
+
? { ...operation.request, state }
|
|
94
|
+
: { ...operation.request, state, policy };
|
|
95
|
+
steps.set(operation, step);
|
|
96
|
+
return step;
|
|
97
|
+
};
|
|
98
|
+
const policy = openRequestPolicy(runtime.config.policy, runtime.contracts.core, { origin: operation.request.origin, state }, caller, adapter);
|
|
99
|
+
return policy instanceof Promise ? policy.then(settle) : settle(policy);
|
|
100
|
+
};
|
|
84
101
|
const admitted = [];
|
|
85
102
|
const standing = [];
|
|
86
103
|
for (const step of plan.operations) {
|
|
@@ -90,7 +107,16 @@ async function executeTransactionPlan(adapter, dependencies, request, plan, stat
|
|
|
90
107
|
continue;
|
|
91
108
|
}
|
|
92
109
|
const report = diagnostics.operation(step.request, step.index);
|
|
93
|
-
|
|
110
|
+
let stepRequest;
|
|
111
|
+
try {
|
|
112
|
+
const opened = stateful(step);
|
|
113
|
+
stepRequest = opened instanceof Promise ? await opened : opened;
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
report(error, "A3000");
|
|
117
|
+
return transactionOperationFailure(internalFailure(), step.index);
|
|
118
|
+
}
|
|
119
|
+
const admission = await admitOperation(dependencies, stepRequest, step.contract, step.arguments, planOperationArgumentsPath(step.index), report, { requestId: request.requestId, transaction: undefined, state }, {
|
|
94
120
|
select: EVERY_FIELD,
|
|
95
121
|
validation: { transactionReferences: true },
|
|
96
122
|
beforeBegin: true,
|
|
@@ -118,7 +144,8 @@ async function executeTransactionPlan(adapter, dependencies, request, plan, stat
|
|
|
118
144
|
const runPlanOperation = async (operation, args, transactionPiped, executor, results, outer) => {
|
|
119
145
|
const report = diagnostics.operation(operation.request, operation.index);
|
|
120
146
|
const argumentsPath = planOperationArgumentsPath(operation.index);
|
|
121
|
-
const
|
|
147
|
+
const opened = stateful(operation);
|
|
148
|
+
const stepRequest = opened instanceof Promise ? await opened : opened;
|
|
122
149
|
try {
|
|
123
150
|
let live = standing[operation.index] ?? operation.references;
|
|
124
151
|
if (transactionPiped) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aventara/core",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.8",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"description": "Core runtime, contract compiler, and extension APIs for Aventara.",
|
|
6
6
|
"type": "module",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"access": "public"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
|
-
"build": "node ../../scripts/build/build-output.cleaner.ts dist && tsc -p tsconfig.json --removeComments --declaration false && tsc -p tsconfig.json --emitDeclarationOnly && node ../../scripts/build/declaration-comments.sanitizer.ts dist && node scripts/errors-markdown.generator.ts",
|
|
48
|
+
"build": "node ../../scripts/build/build-output.cleaner.ts dist && node scripts/core-version.generator.ts && tsc -p tsconfig.json --removeComments --declaration false && tsc -p tsconfig.json --emitDeclarationOnly && node ../../scripts/build/declaration-comments.sanitizer.ts dist && node scripts/errors-markdown.generator.ts",
|
|
49
49
|
"typecheck": "tsc -p tsconfig.typecheck.json --noEmit",
|
|
50
50
|
"test": "vitest run --typecheck --config ../../vitest.config.mts --root .",
|
|
51
51
|
"lint": "biome lint src scripts"
|