@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.
Files changed (66) hide show
  1. package/CHANGELOG.md +157 -0
  2. package/ERRORS.md +8 -7
  3. package/README.md +3 -2
  4. package/dist/avz/avz-check.factory.d.ts +2 -0
  5. package/dist/avz/avz-check.factory.js +12 -0
  6. package/dist/avz/avz.d.ts +6 -0
  7. package/dist/avz/avz.js +1 -0
  8. package/dist/compiler/contract-compiler-diagnostic.d.ts +13 -2
  9. package/dist/compiler/contract-compiler-diagnostic.guidance.js +7 -2
  10. package/dist/compiler/contract.compiler.js +110 -15
  11. package/dist/compiler/create-requirements.projector.d.ts +45 -3
  12. package/dist/compiler/create-requirements.projector.js +239 -28
  13. package/dist/compiler/managed-elsewhere.projector.d.ts +3 -0
  14. package/dist/compiler/managed-elsewhere.projector.js +1 -1
  15. package/dist/config/computed-fields.d.ts +8 -0
  16. package/dist/config/computed-fields.js +1 -1
  17. package/dist/config/config-diagnostic.guidance.js +2 -2
  18. package/dist/config/config.resolver.d.ts +2 -2
  19. package/dist/config/config.validator.js +15 -6
  20. package/dist/config/exact-framework-config.d.ts +1 -0
  21. package/dist/config/field-pipeline-config.d.ts +2 -2
  22. package/dist/config/forced-operation.validator.js +3 -2
  23. package/dist/config/framework-config.d.ts +8 -3
  24. package/dist/config/framework-exemptions-config.d.ts +13 -8
  25. package/dist/config/framework-resources-config.d.ts +11 -3
  26. package/dist/contracts/client-contract-structure.validator.d.ts +4 -1
  27. package/dist/contracts/client-contract-structure.validator.js +3 -2
  28. package/dist/contracts/contract-limits.d.ts +8 -1
  29. package/dist/contracts/contract-limits.js +1 -0
  30. package/dist/contracts/contract.d.ts +7 -3
  31. package/dist/contracts/transaction-capability.d.ts +8 -0
  32. package/dist/contracts/transaction-capability.js +7 -0
  33. package/dist/framework/access-facade.js +14 -6
  34. package/dist/framework/contract-view.d.ts +104 -12
  35. package/dist/framework/operation-facade.d.ts +8 -3
  36. package/dist/internal.d.ts +1 -0
  37. package/dist/internal.js +1 -0
  38. package/dist/operations/validation-code.guidance.js +1 -1
  39. package/dist/policy/access-provider.interface.d.ts +7 -2
  40. package/dist/policy/access-question.runtime.js +2 -1
  41. package/dist/prebuilt/core-version.generated.d.ts +1 -1
  42. package/dist/prebuilt/core-version.generated.js +1 -1
  43. package/dist/protocol/absent-route.responder.d.ts +2 -2
  44. package/dist/protocol/absent-route.responder.js +2 -1
  45. package/dist/protocol/av-protocol.d.ts +1 -0
  46. package/dist/protocol/av-protocol.js +1 -0
  47. package/dist/protocol/protocol-surface.js +2 -1
  48. package/dist/protocol/protocol.handler.js +3 -1
  49. package/dist/runtime/context-executor.runtime.js +2 -1
  50. package/dist/runtime/create-requirement.d.ts +5 -1
  51. package/dist/runtime/create-requirement.js +10 -2
  52. package/dist/runtime/field-check.evaluator.js +39 -2
  53. package/dist/runtime/field-validator.runtime.d.ts +4 -3
  54. package/dist/runtime/field-validator.runtime.js +29 -0
  55. package/dist/runtime/mutation-planner.js +26 -9
  56. package/dist/runtime/operation-code.guidance.js +3 -3
  57. package/dist/runtime/operation-runtime.d.ts +10 -9
  58. package/dist/runtime/operation-runtime.js +20 -18
  59. package/dist/runtime/pipeline.d.ts +1 -15
  60. package/dist/runtime/state-refusal.mapper.d.ts +16 -0
  61. package/dist/runtime/state-refusal.mapper.js +40 -0
  62. package/dist/transactions/operation-fingerprint.d.ts +15 -2
  63. package/dist/transactions/transaction-plan-validator.js +31 -1
  64. package/dist/transactions/transaction-runtime.js +1 -1
  65. package/package.json +1 -1
  66. 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: "Define the field once at the root (`resources.<R>.fields.<f>.definition`); an overlay only chooses its visibility.",
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 { AvTransactionCapability } from "../contracts/transaction-capability.js";
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: AvTransactionCapability | undefined;
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 { TRANSACTION_CAPABILITIES } from "../contracts/transaction-capability.js";
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 TRANSACTION_CAPABILITY_SET = new Set(TRANSACTION_CAPABILITIES);
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 (!requireRecord(entry, keyPath, diagnostics)) {
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
- !TRANSACTION_CAPABILITY_SET.has(value.transactionsStyle)) {
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 !== "none") {
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
- /** `"none"` turns transactions off; `"interactive"` needs adapter support. */
42
- readonly transactionsStyle?: AvTransactionCapability;
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
- /** A scalar field's exemption: whole, by capability name, or per capability. */
12
- export type AvConfigScalarFieldExemption = boolean | readonly AvScalarFieldCapabilityName[] | ({
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
- /** A Resource's exemption: whole, or its operations and fields. No list. */
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 { TRANSACTION_CAPABILITIES } from "./transaction-capability.js";
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(TRANSACTION_CAPABILITIES),
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 operations one transaction may hold. */
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;
@@ -3,6 +3,7 @@ export const DEFAULT_CONTRACT_LIMITS = {
3
3
  maxNestingDepth: 12,
4
4
  maxListLimit: 250,
5
5
  maxTransactionOperations: 20,
6
+ maxTransactionWrites: 20,
6
7
  maxBooleanNodes: 50,
7
8
  maxJsonDepth: 64,
8
9
  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
- /** Whether interactive transactions are available. */
21
- readonly transactions: AvTransactionCapability;
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
- return {
16
- state: await dependencies.state({
17
- headers: requestHeadersOf(options.headers),
18
- ...(options.request === undefined ? {} : { request: options.request }),
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",