@aventara/core 1.0.0-rc.11 → 1.0.0-rc.13
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 +157 -0
- package/ERRORS.md +8 -7
- package/README.md +3 -2
- package/dist/avz/avz-check.factory.d.ts +2 -0
- package/dist/avz/avz-check.factory.js +12 -0
- package/dist/avz/avz.d.ts +6 -0
- package/dist/avz/avz.js +1 -0
- package/dist/compiler/contract-compiler-diagnostic.d.ts +13 -2
- package/dist/compiler/contract-compiler-diagnostic.guidance.js +7 -2
- package/dist/compiler/contract.compiler.js +110 -15
- package/dist/compiler/create-requirements.projector.d.ts +45 -3
- package/dist/compiler/create-requirements.projector.js +239 -28
- package/dist/compiler/managed-elsewhere.projector.d.ts +3 -0
- package/dist/compiler/managed-elsewhere.projector.js +1 -1
- package/dist/config/computed-fields.d.ts +8 -0
- package/dist/config/computed-fields.js +1 -1
- package/dist/config/config-diagnostic.guidance.js +2 -2
- package/dist/config/config.resolver.d.ts +2 -2
- package/dist/config/config.validator.js +15 -6
- package/dist/config/exact-framework-config.d.ts +1 -0
- package/dist/config/field-pipeline-config.d.ts +2 -2
- package/dist/config/forced-operation.validator.js +3 -2
- package/dist/config/framework-config.d.ts +8 -3
- package/dist/config/framework-exemptions-config.d.ts +13 -8
- package/dist/config/framework-resources-config.d.ts +11 -3
- package/dist/contracts/client-contract-structure.validator.d.ts +4 -1
- package/dist/contracts/client-contract-structure.validator.js +3 -2
- package/dist/contracts/contract-limits.d.ts +8 -1
- package/dist/contracts/contract-limits.js +1 -0
- package/dist/contracts/contract.d.ts +7 -3
- package/dist/contracts/transaction-capability.d.ts +8 -0
- package/dist/contracts/transaction-capability.js +7 -0
- package/dist/framework/access-facade.js +14 -6
- package/dist/framework/contract-view.d.ts +104 -12
- package/dist/framework/operation-facade.d.ts +8 -3
- package/dist/internal.d.ts +1 -0
- package/dist/internal.js +1 -0
- package/dist/operations/validation-code.guidance.js +1 -1
- package/dist/policy/access-provider.interface.d.ts +7 -2
- package/dist/policy/access-question.runtime.js +2 -1
- package/dist/prebuilt/core-version.generated.d.ts +1 -1
- package/dist/prebuilt/core-version.generated.js +1 -1
- package/dist/protocol/absent-route.responder.d.ts +2 -2
- package/dist/protocol/absent-route.responder.js +2 -1
- package/dist/protocol/av-protocol.d.ts +1 -0
- package/dist/protocol/av-protocol.js +1 -0
- package/dist/protocol/protocol-surface.js +2 -1
- package/dist/protocol/protocol.handler.js +3 -1
- package/dist/runtime/context-executor.runtime.js +2 -1
- package/dist/runtime/create-requirement.d.ts +5 -1
- package/dist/runtime/create-requirement.js +10 -2
- package/dist/runtime/field-check.evaluator.js +39 -2
- package/dist/runtime/field-validator.runtime.d.ts +4 -3
- package/dist/runtime/field-validator.runtime.js +29 -0
- package/dist/runtime/mutation-planner.js +26 -9
- package/dist/runtime/operation-code.guidance.js +3 -3
- package/dist/runtime/operation-runtime.d.ts +10 -9
- package/dist/runtime/operation-runtime.js +20 -18
- package/dist/runtime/pipeline.d.ts +1 -15
- package/dist/runtime/state-refusal.mapper.d.ts +16 -0
- package/dist/runtime/state-refusal.mapper.js +40 -0
- package/dist/transactions/operation-fingerprint.d.ts +15 -2
- package/dist/transactions/transaction-plan-validator.js +31 -1
- package/dist/transactions/transaction-runtime.js +1 -1
- package/package.json +1 -1
- package/src/runtime/field-check.evaluator.ts +46 -2
|
@@ -38,6 +38,14 @@ type CreateComputedOf<Layer> = Layer extends object ? {
|
|
|
38
38
|
export type AvPreComputeLift<M, L extends AvConfigLayerName> = M extends {
|
|
39
39
|
readonly "~computedFields"?: infer Facts;
|
|
40
40
|
} ? Exclude<Facts, undefined> extends infer Stated extends AvConfigComputedFields ? string extends keyof Stated["root"] ? string : CreateComputedOf<Stated[ComputingLayers<Stated, L>]> : string : string;
|
|
41
|
+
/**
|
|
42
|
+
* The create fields a ctx call need not write, as `"<Resource>.<field>"`: those
|
|
43
|
+
* the root's or the application's `compute.create` fills. `never` when the
|
|
44
|
+
* model's type carries no statement of them.
|
|
45
|
+
*/
|
|
46
|
+
export type AvContextComputeLift<M> = M extends {
|
|
47
|
+
readonly "~computedFields"?: infer Facts;
|
|
48
|
+
} ? Exclude<Facts, undefined> extends infer Stated extends AvConfigComputedFields ? string extends keyof Stated["root"] ? never : CreateComputedOf<Stated["root" | "application"]> : never : never;
|
|
41
49
|
/** The configuration's layers' `resources` trees and the agent's parent. */
|
|
42
50
|
type ComputedFieldsSource = {
|
|
43
51
|
readonly trees: Readonly<Record<AvConfigLayerName, unknown>>;
|
|
@@ -66,7 +66,7 @@ export function warnOnStaleComputedFields(generated, configured) {
|
|
|
66
66
|
if (drift.length === 0) {
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
69
|
-
const message = `generated types are stale: run aventara:prepare. The computed fields the pre-compute stages are typed with differ from the configuration's:\n${drift
|
|
69
|
+
const message = `generated types are stale: run aventara:prepare. The computed fields the pre-compute stages and ctx creates are typed with differ from the configuration's:\n${drift
|
|
70
70
|
.map((line) => ` - ${line}`)
|
|
71
71
|
.join("\n")}`;
|
|
72
72
|
emitFrameworkWarning(message, STALE_GENERATED_TYPES_WARNING);
|
|
@@ -26,7 +26,7 @@ export const CONFIG_DIAGNOSTIC_GUIDANCE = {
|
|
|
26
26
|
},
|
|
27
27
|
CONFIG_FORBIDDEN_IN_OVERLAY: {
|
|
28
28
|
message: "A root-only key is written in an overlay.",
|
|
29
|
-
cause: "A field definition in application, client or agent.",
|
|
30
|
-
fix: "
|
|
29
|
+
cause: "A field definition, or a Resource's managedElsewhere, in application, client or agent.",
|
|
30
|
+
fix: "State it once at the root (`resources.<R>.fields.<f>.definition`, `resources.<R>.managedElsewhere`); an overlay only chooses visibility.",
|
|
31
31
|
},
|
|
32
32
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AvAdapter } from "../adapters/adapter.interface.js";
|
|
2
2
|
import type { AvAdapterModel } from "../adapters/adapter-model.js";
|
|
3
3
|
import type { AvContractClientAccess } from "../contracts/contract-access.js";
|
|
4
|
-
import type {
|
|
4
|
+
import type { TransactionsStyle } from "../contracts/transaction-capability.js";
|
|
5
5
|
import type { AvPolicyEngine } from "../policy/policy-engine.js";
|
|
6
6
|
import type { AvDiagnosticsSink } from "../runtime/diagnostics-reporter.js";
|
|
7
7
|
import type { AvMisuseSink } from "../runtime/misuse-reporter.js";
|
|
@@ -45,7 +45,7 @@ export type ResolvedFrameworkLayerConfig = {
|
|
|
45
45
|
* registrations, then the overlay's, at each level.
|
|
46
46
|
*/
|
|
47
47
|
readonly pipelines: ResolvedPipelineTree;
|
|
48
|
-
readonly transactionsStyle:
|
|
48
|
+
readonly transactionsStyle: TransactionsStyle | undefined;
|
|
49
49
|
};
|
|
50
50
|
/**
|
|
51
51
|
* The MCP endpoint a Framework serves (`mcp` resolved): its mount path in
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isAccessPath, isAccessUrl } from "../contracts/access-route.rules.js";
|
|
2
|
-
import {
|
|
2
|
+
import { TRANSACTIONS_STYLES } from "../contracts/transaction-capability.js";
|
|
3
3
|
import { ACCESS_VERB_NAMES, isAccessVerbName, providerVerb, } from "../policy/access-verb.table.js";
|
|
4
4
|
import { isPlainRecord } from "../runtime/record.guard.js";
|
|
5
5
|
import { normalizeEntrypoint } from "./entrypoint.normalizer.js";
|
|
@@ -69,13 +69,14 @@ const OPERATION_FAMILIES = new Set([
|
|
|
69
69
|
"upsert",
|
|
70
70
|
]);
|
|
71
71
|
const OPERATION_VARIANTS = new Set(["first", "unique", "many", "count", "one"]);
|
|
72
|
-
const
|
|
72
|
+
const TRANSACTIONS_STYLE_SET = new Set(TRANSACTIONS_STYLES);
|
|
73
73
|
const AVZ_FORMAT_SET = new Set(AVZ_FORMATS);
|
|
74
74
|
const LIMIT_PROPERTIES = new Set([
|
|
75
75
|
"maxRequestBytes",
|
|
76
76
|
"maxNestingDepth",
|
|
77
77
|
"maxListLimit",
|
|
78
78
|
"maxTransactionOperations",
|
|
79
|
+
"maxTransactionWrites",
|
|
79
80
|
"maxBooleanNodes",
|
|
80
81
|
"maxJsonDepth",
|
|
81
82
|
"maxAll",
|
|
@@ -436,6 +437,7 @@ function validateResources(value, path, isRoot, diagnostics) {
|
|
|
436
437
|
const EXEMPTIONS_PROPERTIES = new Set(["resources"]);
|
|
437
438
|
const RESOURCE_EXEMPTION_PROPERTIES = new Set(["operations", "fields"]);
|
|
438
439
|
const FLAG_CAPABILITIES = new Set(["select", "include", "order", "orderNulls"]);
|
|
440
|
+
const READ_EXEMPTION = "read";
|
|
439
441
|
function movedHidden(path) {
|
|
440
442
|
return diagnostic("CONFIG_MOVED_PROPERTY", path, `${path} moved in rc.1: an entry is hidden with \`true\` under exposed: true`);
|
|
441
443
|
}
|
|
@@ -492,9 +494,12 @@ function validateOperationsExemption(value, path, diagnostics) {
|
|
|
492
494
|
}
|
|
493
495
|
}
|
|
494
496
|
function validateFieldExemption(value, path, diagnostics) {
|
|
497
|
+
if (value === READ_EXEMPTION) {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
495
500
|
const kind = validateExemptionValue(value, path, "a capability name", diagnostics);
|
|
496
501
|
if (kind === undefined) {
|
|
497
|
-
diagnostics.push(diagnostic("CONFIG_INVALID_TYPE", path, `${path} must be true, false, a list of capability names, or an object of them`));
|
|
502
|
+
diagnostics.push(diagnostic("CONFIG_INVALID_TYPE", path, `${path} must be true, false, "read", a list of capability names, or an object of them`));
|
|
498
503
|
return;
|
|
499
504
|
}
|
|
500
505
|
if (kind !== "object") {
|
|
@@ -543,7 +548,11 @@ function validateResourceExemption(value, path, diagnostics) {
|
|
|
543
548
|
validateOperationsExemption(entry, keyPath, diagnostics);
|
|
544
549
|
continue;
|
|
545
550
|
}
|
|
546
|
-
if (
|
|
551
|
+
if (entry === READ_EXEMPTION) {
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
if (!isPlainRecord(entry)) {
|
|
555
|
+
diagnostics.push(diagnostic("CONFIG_INVALID_TYPE", keyPath, `${keyPath} must be "read" or an object of field exemptions`));
|
|
547
556
|
continue;
|
|
548
557
|
}
|
|
549
558
|
for (const [field, fieldEntry] of Object.entries(entry)) {
|
|
@@ -616,9 +625,9 @@ function validateLayer(value, path, kind, diagnostics) {
|
|
|
616
625
|
validateExemptions(value.exemptions, exemptionsPath, diagnostics);
|
|
617
626
|
}
|
|
618
627
|
if (Object.hasOwn(value, "transactionsStyle") &&
|
|
619
|
-
!
|
|
628
|
+
!TRANSACTIONS_STYLE_SET.has(value.transactionsStyle)) {
|
|
620
629
|
const stylePath = childPath(path, "transactionsStyle");
|
|
621
|
-
diagnostics.push(diagnostic("CONFIG_INVALID_VALUE", stylePath, `${stylePath} must be "none" or "interactive"`));
|
|
630
|
+
diagnostics.push(diagnostic("CONFIG_INVALID_VALUE", stylePath, `${stylePath} must be "none", "server" or "interactive"`));
|
|
622
631
|
}
|
|
623
632
|
if (Object.hasOwn(value, "limits")) {
|
|
624
633
|
validateLimits(value.limits, childPath(path, "limits"), diagnostics);
|
|
@@ -26,6 +26,7 @@ type LimitsSchema = {
|
|
|
26
26
|
readonly maxNestingDepth: Leaf;
|
|
27
27
|
readonly maxListLimit: Leaf;
|
|
28
28
|
readonly maxTransactionOperations: Leaf;
|
|
29
|
+
readonly maxTransactionWrites: Leaf;
|
|
29
30
|
readonly maxBooleanNodes: Leaf;
|
|
30
31
|
readonly maxJsonDepth: Leaf;
|
|
31
32
|
readonly maxAll: Leaf;
|
|
@@ -20,9 +20,9 @@ export type AvFieldCompute<M extends AvAdapterModel = AvAdapterModel, Cfg = AvCo
|
|
|
20
20
|
/**
|
|
21
21
|
* A scalar field's stages: validators, field pipes, compute and write guards.
|
|
22
22
|
* `Check` is the checks the field takes (any, when the field's type is not
|
|
23
|
-
* known here).
|
|
23
|
+
* known here), or the sentence a type that takes none refuses them with.
|
|
24
24
|
*/
|
|
25
|
-
export type AvScalarFieldPipeline<M extends AvAdapterModel = AvAdapterModel, Cfg = AvConfigAny, R extends AvConfigResourceKey<M> = AvConfigResourceKey<M>, Check extends AvZCheck = AvZCheck> = {
|
|
25
|
+
export type AvScalarFieldPipeline<M extends AvAdapterModel = AvAdapterModel, Cfg = AvConfigAny, R extends AvConfigResourceKey<M> = AvConfigResourceKey<M>, Check extends AvZCheck | `Error: ${string}` = AvZCheck> = {
|
|
26
26
|
/**
|
|
27
27
|
* Declarative checks on the value, built with `AvZ` and carried in the Contract;
|
|
28
28
|
* root's, then the overlay's. On a list field `AvZ.minItems` and `AvZ.maxItems`
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isOperationVariantAvailable } from "../contracts/resource-operations.js";
|
|
2
|
+
import { opensServerTransactions } from "../contracts/transaction-capability.js";
|
|
2
3
|
import { forcedOperationsOf } from "./pipeline-tree.resolver.js";
|
|
3
4
|
function reachable(contract, resource, family, variant) {
|
|
4
5
|
if (!Object.hasOwn(contract.resources, resource)) {
|
|
@@ -45,7 +46,7 @@ export function validateForcedOperations(config, contracts) {
|
|
|
45
46
|
: ["application", "client"];
|
|
46
47
|
for (const origin of origins) {
|
|
47
48
|
const contract = contracts[origin];
|
|
48
|
-
if (contract.transactions
|
|
49
|
+
if (opensServerTransactions(contract.transactions)) {
|
|
49
50
|
continue;
|
|
50
51
|
}
|
|
51
52
|
const forced = [
|
|
@@ -61,6 +62,6 @@ export function validateForcedOperations(config, contracts) {
|
|
|
61
62
|
return [...refused].map(([path, layers]) => ({
|
|
62
63
|
code: "CONFIG_INVALID_VALUE",
|
|
63
64
|
path,
|
|
64
|
-
message: `${path}: forceTransaction runs a plain call as a one-step transaction, and the effective transactionsStyle of the ${layers.join(" and ")} ${layers.length === 1 ? "layer is" : "layers are"} "none"; set transactionsStyle: "interactive" there (the adapter must offer interactive transactions) or remove forceTransaction`,
|
|
65
|
+
message: `${path}: forceTransaction runs a plain call as a one-step transaction, and the effective transactionsStyle of the ${layers.join(" and ")} ${layers.length === 1 ? "layer is" : "layers are"} "none"; set transactionsStyle: "server" (no plans from callers) or "interactive" there (the adapter must offer interactive transactions) or remove forceTransaction`,
|
|
65
66
|
}));
|
|
66
67
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { AvAdapter } from "../adapters/adapter.interface.js";
|
|
2
2
|
import type { AvAdapterModel } from "../adapters/adapter-model.js";
|
|
3
3
|
import type { AvContractLimits } from "../contracts/contract-limits.js";
|
|
4
|
-
import type { AvTransactionCapability } from "../contracts/transaction-capability.js";
|
|
5
4
|
import type { AvAccessProvider } from "../policy/access-provider.interface.js";
|
|
6
5
|
import type { AvPolicyEngine } from "../policy/policy-engine.js";
|
|
7
6
|
import type { AvPrebuiltContracts } from "../prebuilt/prebuilt-contracts.js";
|
|
@@ -38,8 +37,14 @@ export type AvConfigLayer<M extends AvAdapterModel = AvAdapterModel, Cfg = AvCon
|
|
|
38
37
|
* the adapter's model, an overlay the root's view.
|
|
39
38
|
*/
|
|
40
39
|
readonly exposed?: boolean;
|
|
41
|
-
/**
|
|
42
|
-
|
|
40
|
+
/**
|
|
41
|
+
* `"interactive"`: the server opens transactions and callers send plans
|
|
42
|
+
* (`/_transactions`, a generated client's `tx`). `"server"`: only the server
|
|
43
|
+
* opens them (`forceTransaction`, a policy-checked write, `ctx.$transaction`);
|
|
44
|
+
* a plan answers as an absent route. `"none"`: no transactions. Both
|
|
45
|
+
* `"server"` and `"interactive"` need adapter support.
|
|
46
|
+
*/
|
|
47
|
+
readonly transactionsStyle?: "none" | "server" | "interactive";
|
|
43
48
|
/** Request limits; see {@link AvConfigLimits}. */
|
|
44
49
|
readonly limits?: AvConfigLimits;
|
|
45
50
|
/** Guards, pipes, hooks, interceptors and filters run around every operation of the layer. */
|
|
@@ -8,8 +8,10 @@ import type { AvConfigResourceKey } from "./framework-field-definition.js";
|
|
|
8
8
|
export type AvScalarFieldCapabilityName = "select" | "filter" | "order" | "orderNulls" | "create" | "update";
|
|
9
9
|
/** The capability names a relation field's exemption can list. */
|
|
10
10
|
export type AvRelationFieldCapabilityName = "select" | "include" | "filter" | "order" | "pagination" | "reducers" | "create" | "update";
|
|
11
|
-
/**
|
|
12
|
-
export type
|
|
11
|
+
/** The capabilities `"read"` names: every exemptable one but `create` and `update`. */
|
|
12
|
+
export type ReadCapabilityName = Exclude<AvScalarFieldCapabilityName | AvRelationFieldCapabilityName, "create" | "update">;
|
|
13
|
+
/** A scalar field's exemption: whole, its reads, by capability name, or per capability. */
|
|
14
|
+
export type AvConfigScalarFieldExemption = boolean | "read" | readonly AvScalarFieldCapabilityName[] | ({
|
|
13
15
|
readonly select?: boolean;
|
|
14
16
|
readonly filter?: boolean | readonly AvScalarFilterOperator[];
|
|
15
17
|
readonly order?: boolean;
|
|
@@ -17,8 +19,8 @@ export type AvConfigScalarFieldExemption = boolean | readonly AvScalarFieldCapab
|
|
|
17
19
|
readonly create?: boolean | readonly AvScalarMutationDirective[];
|
|
18
20
|
readonly update?: boolean | readonly AvScalarMutationDirective[];
|
|
19
21
|
} & NotAList);
|
|
20
|
-
/** A relation field's exemption: whole, by capability name, or per capability. */
|
|
21
|
-
export type AvConfigRelationFieldExemption = boolean | readonly AvRelationFieldCapabilityName[] | ({
|
|
22
|
+
/** A relation field's exemption: whole, its reads, by capability name, or per capability. */
|
|
23
|
+
export type AvConfigRelationFieldExemption = boolean | "read" | readonly AvRelationFieldCapabilityName[] | ({
|
|
22
24
|
readonly select?: boolean;
|
|
23
25
|
readonly include?: boolean;
|
|
24
26
|
readonly filter?: boolean | readonly AvRelationFilterCapability[];
|
|
@@ -58,13 +60,13 @@ type TokenExemption<Caps, K> = K extends keyof Caps ? boolean | readonly MemberT
|
|
|
58
60
|
/** The capability names a discovered scalar's exemption can name: its own, and `select`. */
|
|
59
61
|
type ScalarNameOf<Caps> = Extract<keyof Caps, AvScalarFieldCapabilityName> | "select";
|
|
60
62
|
/** A discovered scalar field's exemption, by its capabilities. */
|
|
61
|
-
type ScalarFieldExemptionOf<Caps> = boolean | readonly ScalarNameOf<Caps>[] | ({
|
|
63
|
+
type ScalarFieldExemptionOf<Caps> = boolean | "read" | readonly ScalarNameOf<Caps>[] | ({
|
|
62
64
|
readonly [K in ScalarNameOf<Caps>]?: K extends "filter" | "create" | "update" ? TokenExemption<Caps, K> : boolean;
|
|
63
65
|
} & NotAList);
|
|
64
66
|
/** The capability names a discovered relation's exemption can name: its own. */
|
|
65
67
|
type RelationNameOf<Caps> = Extract<keyof Caps, AvRelationFieldCapabilityName>;
|
|
66
68
|
/** A discovered relation field's exemption, by its capabilities. */
|
|
67
|
-
type RelationFieldExemptionOf<Caps> = boolean | readonly RelationNameOf<Caps>[] | ({
|
|
69
|
+
type RelationFieldExemptionOf<Caps> = boolean | "read" | readonly RelationNameOf<Caps>[] | ({
|
|
68
70
|
readonly [K in RelationNameOf<Caps>]?: K extends "select" | "include" | "order" ? boolean : TokenExemption<Caps, K>;
|
|
69
71
|
} & NotAList);
|
|
70
72
|
/** The exemption shape one effective field name admits. */
|
|
@@ -74,10 +76,13 @@ type FieldExemptionFor<M extends AvAdapterModel, R extends AvConfigResourceKey<M
|
|
|
74
76
|
} ? RelationFieldExemptionOf<Caps> : M["resources"][R]["fields"][F] extends {
|
|
75
77
|
readonly capabilities: infer Caps;
|
|
76
78
|
} ? ScalarFieldExemptionOf<Caps> : AvConfigScalarFieldExemption : AvConfigScalarFieldExemption;
|
|
77
|
-
/**
|
|
79
|
+
/**
|
|
80
|
+
* A Resource's exemption: whole, or its operations and fields, `fields` being
|
|
81
|
+
* per field or `"read"` (every field's reads). No list.
|
|
82
|
+
*/
|
|
78
83
|
export type AvConfigResourceExemption<M extends AvAdapterModel = AvAdapterModel, Cfg = AvConfigAny, R extends AvConfigResourceKey<M> = AvConfigResourceKey<M>> = boolean | ({
|
|
79
84
|
readonly operations?: AvConfigOperationsExemption<M, R>;
|
|
80
|
-
readonly fields?: {
|
|
85
|
+
readonly fields?: "read" | {
|
|
81
86
|
readonly [F in AvEffectiveFieldKey<M, Cfg, R>]?: FieldExemptionFor<M, R, F>;
|
|
82
87
|
};
|
|
83
88
|
} & NotAList);
|
|
@@ -59,13 +59,21 @@ export type AvConfigOverlayField<M extends AvAdapterModel = AvAdapterModel, Cfg
|
|
|
59
59
|
};
|
|
60
60
|
/** The checks a scalar type takes, or `never`. */
|
|
61
61
|
type ScalarChecks<Scalar> = Scalar extends "string" ? AvZCheck<"string"> : Scalar extends "int" | "float" ? AvZCheck<"number"> : Scalar extends "bigint" | "decimal" | "datetime" | "bytes" ? AvZCheck<Scalar> : never;
|
|
62
|
+
/** The checks a field's type takes, or `never` (an enum, a boolean). */
|
|
63
|
+
type TypeChecks<Type> = Type extends {
|
|
64
|
+
readonly scalar: infer Scalar;
|
|
65
|
+
} ? string extends Scalar ? AvZCheck : ScalarChecks<Scalar> : never;
|
|
66
|
+
/** Why a field whose type takes no check refuses one; a list still takes its length. */
|
|
67
|
+
type NoCheckRefusal<Type, List> = Type extends {
|
|
68
|
+
readonly enum: string;
|
|
69
|
+
} ? false extends List ? "Error: an enum field takes no check: its enum already is the closed set of values it admits" : "Error: an enum list takes only a list length (AvZ.minItems, AvZ.maxItems): its enum already is the closed set of values it admits" : Type extends {
|
|
70
|
+
readonly scalar: infer Scalar extends string;
|
|
71
|
+
} ? false extends List ? `Error: a ${Scalar} field takes no check` : `Error: a ${Scalar} list takes only a list length (AvZ.minItems, AvZ.maxItems)` : false extends List ? "Error: this field's type takes no check" : "Error: this field's type takes only a list length (AvZ.minItems, AvZ.maxItems)";
|
|
62
72
|
/** The checks a discovered scalar field takes: its type's, and a list's length. */
|
|
63
73
|
type FieldCheckOf<Field> = Field extends {
|
|
64
74
|
readonly type: infer Type;
|
|
65
75
|
readonly list: infer List;
|
|
66
|
-
} ? (Type extends
|
|
67
|
-
readonly scalar: infer Scalar;
|
|
68
|
-
} ? string extends Scalar ? AvZCheck : ScalarChecks<Scalar> : never) | (false extends List ? never : AvZCheck<"list">) : AvZCheck;
|
|
76
|
+
} ? ([TypeChecks<Type>] extends [never] ? NoCheckRefusal<Type, List> : TypeChecks<Type>) | (false extends List ? never : AvZCheck<"list">) : AvZCheck;
|
|
69
77
|
/** A discovered scalar field's stages, its `validators` typed by the field. */
|
|
70
78
|
type KnownScalarFieldPipeline<M extends AvAdapterModel, Cfg, R extends AvConfigResourceKey<M>, Field> = Field extends {
|
|
71
79
|
readonly type: {
|
|
@@ -1383,6 +1383,9 @@ export declare const CLIENT_CONTRACT_STRUCTURE: {
|
|
|
1383
1383
|
readonly maxTransactionOperations: {
|
|
1384
1384
|
readonly is: "number";
|
|
1385
1385
|
};
|
|
1386
|
+
readonly maxTransactionWrites: {
|
|
1387
|
+
readonly is: "number";
|
|
1388
|
+
};
|
|
1386
1389
|
readonly maxBooleanNodes: {
|
|
1387
1390
|
readonly is: "number";
|
|
1388
1391
|
};
|
|
@@ -1400,7 +1403,7 @@ export declare const CLIENT_CONTRACT_STRUCTURE: {
|
|
|
1400
1403
|
};
|
|
1401
1404
|
readonly transactions: {
|
|
1402
1405
|
readonly is: "literal";
|
|
1403
|
-
readonly values: readonly ["none", "interactive"];
|
|
1406
|
+
readonly values: readonly ["none", "server", "interactive"];
|
|
1404
1407
|
};
|
|
1405
1408
|
readonly protocol: {
|
|
1406
1409
|
readonly is: "object";
|
|
@@ -3,7 +3,7 @@ import { FILTER_MODES, PROJECTION_SCOPES, WRITE_SCOPES } from "./capability.js";
|
|
|
3
3
|
import { AVENTARA_PROTOCOL_VERSION } from "./contract-protocol.js";
|
|
4
4
|
import { LIFECYCLE_TRAITS } from "./lifecycle-trait.js";
|
|
5
5
|
import { REFERENCE_WRITE_LIMITS } from "./reference-write-limit.js";
|
|
6
|
-
import {
|
|
6
|
+
import { TRANSACTIONS_STYLES } from "./transaction-capability.js";
|
|
7
7
|
const STRING = { is: "string" };
|
|
8
8
|
const NUMBER = { is: "number" };
|
|
9
9
|
const BOOLEAN = { is: "boolean" };
|
|
@@ -344,12 +344,13 @@ export const CLIENT_CONTRACT_STRUCTURE = object({
|
|
|
344
344
|
maxNestingDepth: NUMBER,
|
|
345
345
|
maxListLimit: NUMBER,
|
|
346
346
|
maxTransactionOperations: NUMBER,
|
|
347
|
+
maxTransactionWrites: NUMBER,
|
|
347
348
|
maxBooleanNodes: NUMBER,
|
|
348
349
|
maxJsonDepth: NUMBER,
|
|
349
350
|
maxAll: NUMBER,
|
|
350
351
|
maxAllConcurrency: NUMBER,
|
|
351
352
|
}),
|
|
352
|
-
transactions: literal(
|
|
353
|
+
transactions: literal(TRANSACTIONS_STYLES),
|
|
353
354
|
protocol: object({
|
|
354
355
|
version: literal([AVENTARA_PROTOCOL_VERSION]),
|
|
355
356
|
hash: { is: "prefixed", prefix: "sha256:" },
|
|
@@ -6,8 +6,14 @@ export type AvContractLimits = {
|
|
|
6
6
|
readonly maxNestingDepth: number;
|
|
7
7
|
/** The largest `limit` a list read may ask for. */
|
|
8
8
|
readonly maxListLimit: number;
|
|
9
|
-
/** The most
|
|
9
|
+
/** The most steps one transaction plan may hold: reads, writes and `$sql` steps alike. */
|
|
10
10
|
readonly maxTransactionOperations: number;
|
|
11
|
+
/**
|
|
12
|
+
* The most write steps one transaction plan may hold: `create`, `update`,
|
|
13
|
+
* `upsert` and `delete` steps, and every `$sql` step (a statement is not
|
|
14
|
+
* classed, so it counts as a write).
|
|
15
|
+
*/
|
|
16
|
+
readonly maxTransactionWrites: number;
|
|
11
17
|
/** The most boolean and filter nodes one request may hold. */
|
|
12
18
|
readonly maxBooleanNodes: number;
|
|
13
19
|
/** How deeply a `json` field's value may nest: its arrays and objects, counted from the value. */
|
|
@@ -23,6 +29,7 @@ export declare const DEFAULT_CONTRACT_LIMITS: {
|
|
|
23
29
|
readonly maxNestingDepth: 12;
|
|
24
30
|
readonly maxListLimit: 250;
|
|
25
31
|
readonly maxTransactionOperations: 20;
|
|
32
|
+
readonly maxTransactionWrites: 20;
|
|
26
33
|
readonly maxBooleanNodes: 50;
|
|
27
34
|
readonly maxJsonDepth: 64;
|
|
28
35
|
readonly maxAll: 10;
|
|
@@ -6,7 +6,6 @@ import type { AvCoreField } from "./field.js";
|
|
|
6
6
|
import type { ReferenceWriteLimit } from "./reference-write-limit.js";
|
|
7
7
|
import type { AvResource } from "./resource.js";
|
|
8
8
|
import type { AvScalarRegistry } from "./scalar.js";
|
|
9
|
-
import type { AvTransactionCapability } from "./transaction-capability.js";
|
|
10
9
|
/** What every Contract describes: scalars, enums, Resources, limits and transactions. */
|
|
11
10
|
export interface AvContract {
|
|
12
11
|
/** The scalar types available. */
|
|
@@ -17,8 +16,13 @@ export interface AvContract {
|
|
|
17
16
|
readonly resources: Readonly<Record<string, AvResource<this>>>;
|
|
18
17
|
/** The request limits in force. */
|
|
19
18
|
readonly limits: AvContractLimits;
|
|
20
|
-
/**
|
|
21
|
-
|
|
19
|
+
/**
|
|
20
|
+
* What the layer's transactions allow: `"interactive"`, transactions the
|
|
21
|
+
* server opens and plans callers send; `"server"`, only those the server
|
|
22
|
+
* opens (a forced call, a policy-checked write, `ctx.$transaction`), with no
|
|
23
|
+
* plan; `"none"`, neither.
|
|
24
|
+
*/
|
|
25
|
+
readonly transactions: "none" | "server" | "interactive";
|
|
22
26
|
/**
|
|
23
27
|
* `"uniform"` when one record must write the relations that hold a reference
|
|
24
28
|
* either all as relations or all through their reference fields; a record
|
|
@@ -2,3 +2,11 @@
|
|
|
2
2
|
export declare const TRANSACTION_CAPABILITIES: readonly ["none", "interactive"];
|
|
3
3
|
/** `"none"` or `"interactive"`: whether transactions can run. */
|
|
4
4
|
export type AvTransactionCapability = (typeof TRANSACTION_CAPABILITIES)[number];
|
|
5
|
+
/** The transaction styles a layer can state, and its Contract's `transactions` carries. */
|
|
6
|
+
export declare const TRANSACTIONS_STYLES: readonly ["none", "server", "interactive"];
|
|
7
|
+
/** `"none"`, `"server"` or `"interactive"`: what a layer's transactions allow. */
|
|
8
|
+
export type TransactionsStyle = (typeof TRANSACTIONS_STYLES)[number];
|
|
9
|
+
/** Whether the server may open a transaction under `style`: `"server"` or `"interactive"`. */
|
|
10
|
+
export declare function opensServerTransactions(style: TransactionsStyle): boolean;
|
|
11
|
+
/** Whether callers may send a transaction plan under `style`: `"interactive"` alone. */
|
|
12
|
+
export declare function takesTransactionPlans(style: TransactionsStyle): boolean;
|
|
@@ -1 +1,8 @@
|
|
|
1
1
|
export const TRANSACTION_CAPABILITIES = ["none", "interactive"];
|
|
2
|
+
export const TRANSACTIONS_STYLES = ["none", "server", "interactive"];
|
|
3
|
+
export function opensServerTransactions(style) {
|
|
4
|
+
return style === "server" || style === "interactive";
|
|
5
|
+
}
|
|
6
|
+
export function takesTransactionPlans(style) {
|
|
7
|
+
return style === "interactive";
|
|
8
|
+
}
|
|
@@ -4,6 +4,7 @@ import { copyAtDoor } from "../runtime/caller-data.door.js";
|
|
|
4
4
|
import { isRecord } from "../runtime/record.guard.js";
|
|
5
5
|
import { requestIdOf } from "../runtime/request-id.js";
|
|
6
6
|
import { stateOptionOf } from "../runtime/request-state.resolver.js";
|
|
7
|
+
import { inProcessStateRejection } from "../runtime/state-refusal.mapper.js";
|
|
7
8
|
import { encodeOperationArgumentsForWire } from "../transactions/operation-arguments-encoder.js";
|
|
8
9
|
import { settleOperation } from "./operation-facade.js";
|
|
9
10
|
export function createServerClientAccess(dependencies) {
|
|
@@ -12,12 +13,19 @@ export function createServerClientAccess(dependencies) {
|
|
|
12
13
|
if (options?.state !== undefined || options?.headers === undefined) {
|
|
13
14
|
return stateOf(options);
|
|
14
15
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
try {
|
|
17
|
+
return {
|
|
18
|
+
state: await dependencies.state({
|
|
19
|
+
headers: requestHeadersOf(options.headers),
|
|
20
|
+
...(options.request === undefined
|
|
21
|
+
? {}
|
|
22
|
+
: { request: options.request }),
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
throw inProcessStateRejection(error);
|
|
28
|
+
}
|
|
21
29
|
};
|
|
22
30
|
const can = async (questions, options) => settleOperation(await answer({
|
|
23
31
|
origin: "client",
|