@aventara/core 1.0.0-rc.12 → 1.0.0-rc.14

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 +152 -1
  2. package/ERRORS.md +7 -6
  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 +10 -0
  9. package/dist/compiler/contract-compiler-diagnostic.guidance.js +5 -0
  10. package/dist/compiler/contract.compiler.js +102 -12
  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 +91 -9
  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
@@ -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",
@@ -1,7 +1,9 @@
1
1
  import type { AvAdapterModel } from "../adapters/adapter-model.js";
2
2
  import type { AvConfigAny } from "../config/any-framework-config.js";
3
3
  import type { AvRequestOrigin } from "../config/config-layer.js";
4
+ import type { ReadCapabilityName } from "../config/framework-exemptions-config.js";
4
5
  import type { ErasedResources } from "../config/stage-type.eraser.js";
6
+ import type { AvContract } from "../contracts/contract.js";
5
7
  import type { AvContractLimits } from "../contracts/contract-limits.js";
6
8
  import type { JsonShape, ShapedJsonSchema } from "../contracts/json-shape.js";
7
9
  /**
@@ -181,11 +183,47 @@ type ManagedResources<in out Resources, in out Managed, in out Hidden> = {
181
183
  /**
182
184
  * One Resource before exposure: the model's own, with its fields layered and
183
185
  * marked where governed, and its create requirements less what the layers'
184
- * compute stages fill.
186
+ * compute stages fill, its non-null added fields among them.
185
187
  */
186
188
  type LayeredResource<in out Resource, in out ResourceConfig, in out OverlayConfig, in out Governed = never> = {
187
- readonly [P in keyof Resource]: P extends "fields" ? GovernedFields<LayeredFields<Resource[P], EntryOf<ResourceConfig, "fields">>, Governed> : P extends "createRequirements" ? RequirementsLessComputed<Exclude<Resource[P], undefined>, ComputedOnCreateKey<EntryOf<ResourceConfig, "fields">> | ComputedOnCreateKey<EntryOf<OverlayConfig, "fields">>> : Resource[P];
189
+ readonly [P in keyof (Resource & {
190
+ readonly createRequirements?: unknown;
191
+ })]: P extends "fields" ? GovernedFields<LayeredFields<Resource[P & keyof Resource], EntryOf<ResourceConfig, "fields">>, Governed> : P extends "createRequirements" ? LayeredRequirements<"createRequirements" extends keyof Resource ? Exclude<Resource[P & keyof Resource], undefined> : never, NonNullAddedKey<(Resource & {
192
+ readonly fields: unknown;
193
+ })["fields"], EntryOf<ResourceConfig, "fields">>, EntryOf<ResourceConfig, "fields">, EntryOf<OverlayConfig, "fields">> : Resource[P & keyof Resource];
188
194
  };
195
+ /**
196
+ * The model's statement (`never` when it states none) less what a
197
+ * `compute.create` of either layer fills, with the non-null added fields
198
+ * (`NonNull`) it leaves required too; `never` when nothing is required. The
199
+ * fields compute fills are read only where there is something to subtract them
200
+ * from.
201
+ */
202
+ type LayeredRequirements<Requirements, NonNull, FieldsBlock, OverlayFieldsBlock> = [Requirements] extends [never] ? [NonNull] extends [never] ? never : AddedOnly<Exclude<NonNull, ComputedOnCreateKey<FieldsBlock> | ComputedOnCreateKey<OverlayFieldsBlock>>> : LayeredStatement<Requirements, NonNull, ComputedOnCreateKey<FieldsBlock> | ComputedOnCreateKey<OverlayFieldsBlock>>;
203
+ /** {@link LayeredRequirements} of a statement the model makes, `Computed` named once. */
204
+ type LayeredStatement<Requirements, NonNull, Computed> = RequirementsWithAdded<RequirementsLessComputed<Requirements, Computed>, [
205
+ NonNull
206
+ ] extends [never] ? never : Exclude<NonNull, Computed>>;
207
+ /** `Added` as a statement of its own; `never` (no statement) when it names none. */
208
+ type AddedOnly<Added> = [Added] extends [never] ? never : {
209
+ readonly required: RequiredNames<Added>;
210
+ };
211
+ /** The fields a `fields` block adds as `nullable: false`. */
212
+ type NonNullAddedKey<ModelFields, FieldsBlock> = {
213
+ [F in AddedFieldKey<ModelFields, FieldsBlock>]-?: DefinitionOf<EntryOf<FieldsBlock, F>> extends {
214
+ readonly nullable: false;
215
+ } ? F : never;
216
+ }[AddedFieldKey<ModelFields, FieldsBlock>];
217
+ /** A create-requirement statement with `Added` required too. */
218
+ type RequirementsWithAdded<Requirements, Added> = [Added] extends [never] ? Requirements : {
219
+ readonly [P in keyof Requirements as P extends "required" ? never : P]: Requirements[P];
220
+ } & {
221
+ readonly required: RequiredNames<(Requirements extends {
222
+ readonly required?: infer List;
223
+ } ? List extends readonly (infer Name)[] ? Name : never : never) | Added>;
224
+ };
225
+ /** Names as a statement's `required` member, a non-empty list (their order is the compiler's). */
226
+ type RequiredNames<Names> = readonly [Names, ...Names[]];
189
227
  /** The fields of a `fields` block with a `compute.create` stage. */
190
228
  type ComputedOnCreateKey<FieldsBlock> = [FieldsBlock] extends [never] ? never : {
191
229
  [F in keyof FieldsBlock]-?: NonNullable<FieldsBlock[F]> extends {
@@ -220,6 +258,20 @@ type GroupsLessComputed<Groups, Computed> = Groups extends readonly [
220
258
  readonly [Relation, ...NamesLessComputed<References, Computed>],
221
259
  ...GroupsLessComputed<Rest, Computed>
222
260
  ] : GroupsLessComputed<Rest, Computed> : readonly [];
261
+ /** A Contract view with the create requirements `Lift` names lifted. */
262
+ export type PreComputeView<C, Lift extends string> = Extract<{
263
+ readonly [P in keyof C]: P extends "resources" ? string extends Lift ? {
264
+ readonly [R in keyof C[P]]: Omit<C[P][R], "createRequirements">;
265
+ } : {
266
+ readonly [R in keyof C[P]]: LiftedResource<C[P][R], LiftedFieldOf<Lift, R>>;
267
+ } : C[P];
268
+ }, AvContract>;
269
+ /** The fields of Resource `R` that `Lift` names. */
270
+ type LiftedFieldOf<Lift extends string, R> = Lift extends `${R & string}.${infer Field}` ? Field : never;
271
+ /** One Resource with the create requirements on `Fields` lifted; itself when none. */
272
+ type LiftedResource<Resource, Fields> = [Fields] extends [never] ? Resource : {
273
+ readonly [P in keyof Resource]: P extends "createRequirements" ? RequirementsLessComputed<Exclude<Resource[P], undefined>, Fields> : Resource[P];
274
+ };
223
275
  /** The Resources before exposure: the model, with the root's definitions applied. */
224
276
  type LayeredResources<Resources, Block, OverlayBlock, Governed = never> = {
225
277
  readonly [R in keyof Resources]: LayeredResource<Resources[R], EntryOf<Block, R>, EntryOf<OverlayBlock, R>, GovernedNames<Governed, R>>;
@@ -258,7 +310,7 @@ type AnyMemberGrants<Entry, Exclude = never> = true extends {
258
310
  [K in keyof Entry]-?: K extends Exclude ? false : Grants<NonNullable<Entry[K]>>;
259
311
  }[keyof Entry] ? true : false;
260
312
  /** Whether a Pick field entry grants a capability: an object must grant one inside. */
261
- type FieldGrants<Entry> = [Entry] extends [readonly unknown[]] ? Grants<Entry> : [Entry] extends [boolean] ? Grants<Entry> : [Entry] extends [object] ? AnyMemberGrants<Entry, AdapterLimit> : false;
313
+ type FieldGrants<Entry> = [Entry] extends ["read"] ? true : [Entry] extends [readonly unknown[]] ? Grants<Entry> : [Entry] extends [boolean] ? Grants<Entry> : [Entry] extends [object] ? AnyMemberGrants<Entry, AdapterLimit> : false;
262
314
  /** Whether one family's entry grants a variant. */
263
315
  type FamilyGrants<Entry> = [Entry] extends [readonly unknown[]] ? Grants<Entry> : [Entry] extends [boolean] ? Grants<Entry> : [Entry] extends [object] ? AnyMemberGrants<Entry> : false;
264
316
  /** Whether an operations entry grants an operation. */
@@ -266,7 +318,7 @@ type OperationsGrant<Entry> = [Entry] extends [readonly unknown[]] ? Grants<Entr
266
318
  [F in keyof Entry]-?: FamilyGrants<NonNullable<Entry[F]>>;
267
319
  }[keyof Entry] ? true : false : false;
268
320
  /** Whether a Pick Resource entry grants a field or an operation. */
269
- type ResourceGrants<Entry> = [Entry] extends [boolean] ? Grants<Entry> : [Entry] extends [object] ? true extends OperationsGrant<NonNullable<OperationsEntryOf<Entry>>> | (true extends {
321
+ type ResourceGrants<Entry> = [Entry] extends [boolean] ? Grants<Entry> : [Entry] extends [object] ? true extends OperationsGrant<NonNullable<OperationsEntryOf<Entry>>> | ([RawFieldsEntryOf<Entry>] extends ["read"] ? true : false) | (true extends {
270
322
  [F in keyof FieldsEntryOf<Entry>]-?: FieldGrants<NonNullable<FieldsEntryOf<Entry>[F]>>;
271
323
  }[keyof FieldsEntryOf<Entry>] ? true : false) ? true : false : false;
272
324
  /** The keys of a Pick `fields` block whose entry grants (see {@link FieldGrants}). */
@@ -307,18 +359,40 @@ type ExemptedCapabilities<Caps, Entry, Style> = Style extends "omit" ? Entry ext
307
359
  } : {
308
360
  readonly [P in keyof Caps as P extends AdapterLimit ? P : P extends keyof Entry ? Grants<Entry[P]> extends true ? P : never : never]: P extends keyof Entry ? PickedMember<Caps[P], Entry[P]> : Caps[P];
309
361
  };
362
+ /** The read capability names a field's capabilities carry: present and not `false`. */
363
+ type OfferedReadName<Caps> = {
364
+ [P in keyof Caps & ReadCapabilityName]-?: [
365
+ Exclude<Caps[P], undefined>
366
+ ] extends [false] ? never : P;
367
+ }[keyof Caps & ReadCapabilityName];
368
+ /** A field entry as the walk reads it: `"read"` as the list of the read names its capabilities carry. */
369
+ type EntryForm<Entry, Caps> = [Entry] extends ["read"] ? readonly OfferedReadName<Caps>[] : Entry;
310
370
  /** One field under its exemption: `true` (or `boolean`) keeps the parent's field. */
311
371
  type ExemptedField<Field, Entry, Style> = [Entry] extends [boolean] ? Field : {
312
- readonly [P in keyof Field]: P extends "capabilities" ? ExemptedCapabilities<Field[P], Entry, Style> : Field[P];
372
+ readonly [P in keyof Field]: P extends "capabilities" ? ExemptedCapabilities<Field[P], EntryForm<Entry, Field[P]>, Style> : Field[P];
313
373
  };
374
+ /** A field's capabilities, or `never`. */
375
+ type CapabilitiesOf<Field> = Field extends {
376
+ readonly capabilities: infer Caps;
377
+ } ? Caps : never;
378
+ /** The fields a Pick `"read"` names that carry no read capability. */
379
+ type UnreadableKey<Fields, Block> = [
380
+ Extract<Block[keyof Block], "read">
381
+ ] extends [never] ? never : {
382
+ [F in keyof Fields & keyof Block]-?: [NonNullable<Block[F]>] extends [
383
+ "read"
384
+ ] ? [OfferedReadName<CapabilitiesOf<Fields[F]>>] extends [never] ? F : never : never;
385
+ }[keyof Fields & keyof Block];
386
+ /** The fields a Pick `fields` block keeps. */
387
+ type PickedFieldKey<Fields, Block> = Exclude<Extract<keyof Fields, GrantedFieldKey<Block>>, UnreadableKey<Fields, Block>>;
314
388
  /** A Resource's fields under its exemptions' `fields` block. */
315
389
  type ExemptedFields<Fields, Block, Style> = Style extends "omit" ? [Block] extends [never] ? Fields : {
316
390
  readonly [F in Exclude<keyof Fields, WholeKey<Block>>]: F extends keyof Block ? ExemptedField<Fields[F], NonNullable<Block[F]>, "omit"> : Fields[F];
317
391
  } : {
318
- readonly [F in Extract<keyof Fields, GrantedFieldKey<Block>>]: ExemptedField<Fields[F], RawEntry<Block, F>, "pick">;
392
+ readonly [F in PickedFieldKey<Fields, Block>]: ExemptedField<Fields[F], RawEntry<Block, F>, "pick">;
319
393
  };
320
394
  /** The fields an exemptions `fields` block leaves a Resource without. */
321
- type RemovedField<Fields, Block, Style> = Style extends "omit" ? WholeKey<Block> : Exclude<keyof Fields, GrantedFieldKey<Block>>;
395
+ type RemovedField<Fields, Block, Style> = Style extends "omit" ? WholeKey<Block> : Exclude<keyof Fields, PickedFieldKey<Fields, Block>>;
322
396
  /** Whether a field's capabilities offer a filter operator. */
323
397
  type Filters<Field> = Field extends {
324
398
  readonly capabilities: infer Caps;
@@ -359,9 +433,17 @@ type IdentifiersLost<Identifiers, Removed> = [Removed] extends [never] ? false :
359
433
  KeptIdentifiers<Identifiers, Removed> extends readonly (infer Kept)[] ? Kept : never
360
434
  ] extends [never] ? true : false;
361
435
  /** A Resource's `fields` and `operations` entries, read off its exemption object. */
362
- type FieldsEntryOf<Entry> = LiteralRecord<(Entry & {
436
+ type FieldsEntryOf<Entry> = LiteralRecord<RawFieldsEntryOf<Entry>>;
437
+ /** A Resource exemption's `fields` as written: an object, or `"read"`. */
438
+ type RawFieldsEntryOf<Entry> = NonNullable<(Entry & {
363
439
  readonly fields?: unknown;
364
440
  })["fields"]>;
441
+ /** A Resource's `fields` block, `"read"` written out over the parent's fields. */
442
+ type FieldsBlockOf<Entry, Resource> = [RawFieldsEntryOf<Entry>] extends ["read"] ? {
443
+ readonly [F in keyof (Resource extends {
444
+ readonly fields: infer Fields;
445
+ } ? Fields : never)]: "read";
446
+ } : FieldsEntryOf<Entry>;
365
447
  type OperationsEntryOf<Entry> = (Entry & {
366
448
  readonly operations?: unknown;
367
449
  })["operations"];
@@ -376,7 +458,7 @@ type ExemptedResourceObject<in out Resource, in out FieldsBlock, in out Operatio
376
458
  } ? Fields : never, FieldsBlock, Style>> : Resource[P];
377
459
  };
378
460
  /** One Resource under its exemption: `true` (or `boolean`) keeps the parent's. */
379
- type ExemptedResource<Resource, Entry, Style> = [Entry] extends [boolean] ? Resource : ExemptedResourceObject<Resource, FieldsEntryOf<Entry>, OperationsEntryOf<Entry>, Style>;
461
+ type ExemptedResource<Resource, Entry, Style> = [Entry] extends [boolean] ? Resource : ExemptedResourceObject<Resource, FieldsBlockOf<Entry, Resource>, OperationsEntryOf<Entry>, Style>;
380
462
  /** A layer's Resources: its parent's, with its exemptions applied in its style. */
381
463
  type ExposedResources<Resources, Style, Block> = Style extends "omit" ? [Block] extends [never] ? Resources : {
382
464
  readonly [R in Exclude<keyof Resources, WholeKey<Block>>]: R extends keyof Block ? ExemptedResource<Resources[R], NonNullable<Block[R]>, "omit"> : Resources[R];
@@ -1,4 +1,5 @@
1
1
  import type { AvAdapterModel } from "../adapters/adapter-model.js";
2
+ import type { AvContextComputeLift } from "../config/computed-fields.js";
2
3
  import type { AvContract } from "../contracts/contract.js";
3
4
  import type { AvApplicationScalarForms, AvWireScalarForms } from "../contracts/scalar-value-type.js";
4
5
  import type { AvOperationArgumentsFor, AvResourceKey } from "../operations/operation-arguments.js";
@@ -12,7 +13,7 @@ import type { AvState } from "../runtime/request-state.js";
12
13
  import type { AvSqlStatementBuilder } from "../transactions/sql-statement.js";
13
14
  import type { AvTransactionResponse } from "../transactions/transaction-response.js";
14
15
  import type { AccessProviderOf, ServerClientAccess } from "./access-facade.js";
15
- import type { AvContractApplicationView, ClientContractView } from "./contract-view.js";
16
+ import type { AvContractApplicationView, ClientContractView, PreComputeView } from "./contract-view.js";
16
17
  /**
17
18
  * A model whose Resource names are not literal — the abstract `AvAdapterModel` behind
18
19
  * the default `AvFramework` — has no Contract to type a call against, so a surface over
@@ -227,9 +228,13 @@ export type ClientTransactionBuilders<M extends AvAdapterModel, Cfg> = ClientDef
227
228
  export type ServerClientTransactionBuilders<M extends AvAdapterModel, Cfg> = ServerClientDeferredSurface<ClientContractView<M, Cfg>>;
228
229
  /**
229
230
  * The Resources of a pipeline ctx: `ctx.<Resource>.<family>.<variant>(args)`,
230
- * application-origin, resolving the operation's data.
231
+ * application-origin, resolving the operation's data. A create need not write a
232
+ * field the root's or the application's `compute.create` fills, when the model's
233
+ * type states them.
231
234
  */
232
- export type AvContextOperations<M extends AvAdapterModel> = ContextFacade<AvContractApplicationView<M>>;
235
+ export type AvContextOperations<M extends AvAdapterModel> = ContextFacade<ContextView<AvContractApplicationView<M>, AvContextComputeLift<M>>>;
236
+ /** The ctx executor's view: `C`, its create requirements less `Lift`. */
237
+ type ContextView<C, Lift extends string> = [Lift] extends [never] ? C : PreComputeView<C, Lift>;
233
238
  type ContextFacade<C> = C extends AvContract ? string extends AvResourceKey<C> ? unknown : {
234
239
  readonly [R in AvResourceKey<C>]: {
235
240
  readonly [F in keyof AvOperationGrammar<C>[R]]: {
@@ -43,5 +43,6 @@ export { mcpFailureReporterOf } from "./runtime/diagnostics-reporter.js";
43
43
  export type { AvErrorClassName } from "./runtime/framework-error-class.table.js";
44
44
  export { FRAMEWORK_ERROR_CLASS_OF } from "./runtime/framework-error-class.table.js";
45
45
  export { OPERATION_CODES } from "./runtime/operation-response.js";
46
+ export { inProcessStateRejection } from "./runtime/state-refusal.mapper.js";
46
47
  export type { BuiltinNonRow, OperationFingerprint, } from "./transactions/deferred-operation.js";
47
48
  export { computeOperationFingerprint } from "./transactions/operation-fingerprint.js";
package/dist/internal.js CHANGED
@@ -23,4 +23,5 @@ export { emitPrebuiltContracts, prebuiltContractsOf, } from "./prebuilt/prebuilt
23
23
  export { mcpFailureReporterOf } from "./runtime/diagnostics-reporter.js";
24
24
  export { FRAMEWORK_ERROR_CLASS_OF } from "./runtime/framework-error-class.table.js";
25
25
  export { OPERATION_CODES } from "./runtime/operation-response.js";
26
+ export { inProcessStateRejection } from "./runtime/state-refusal.mapper.js";
26
27
  export { computeOperationFingerprint } from "./transactions/operation-fingerprint.js";
@@ -56,7 +56,7 @@ export const VALIDATION_CODE_GUIDANCE = {
56
56
  fix: "Nest less, or raise limits.maxNestingDepth.",
57
57
  },
58
58
  V1014: {
59
- cause: "A limit above maxListLimit, or more steps than maxTransactionOperations.",
59
+ cause: "A limit above maxListLimit, more steps than maxTransactionOperations, or more write steps than maxTransactionWrites.",
60
60
  fix: "Paginate or split the plan, or raise the limit.",
61
61
  },
62
62
  V1015: {
@@ -18,8 +18,13 @@ export interface AvAccessProvider {
18
18
  * the stages read. Called once per request by the host. `request` is the
19
19
  * host's own request when it has one: Express's `req`, Fastify's
20
20
  * `request`, or the Next.js route handler's `Request`. A throw is answered
21
- * like any developer failure: a thrown `AvError` as its own outcome,
22
- * anything else as an internal failure.
21
+ * as a thrown `AvError`'s own outcome. A rejection carrying `status` or
22
+ * `statusCode` 401 or 403 (an authentication library's refusal; two
23
+ * numbers that disagree are none) is `A4000` or `A4001`, with core's own
24
+ * message and nothing of the rejection's, unreported: a route answers it,
25
+ * and an in-process call (Next.js's `avClient`, `serverClient.$access.can`
26
+ * given `headers`) rejects with that code's `AvError`. Anything else is an
27
+ * internal failure on a route, and rejects an in-process call unchanged.
23
28
  */
24
29
  state(input: {
25
30
  readonly headers: {
@@ -1,6 +1,7 @@
1
1
  import { adapterPrimaryIdentifier } from "../adapters/adapter-identifier.js";
2
2
  import { lookupOwn, lookupResource } from "../contracts/contract-lookup.js";
3
3
  import { isOperationVariantAvailable } from "../contracts/resource-operations.js";
4
+ import { opensServerTransactions } from "../contracts/transaction-capability.js";
4
5
  import { resourceUnavailableIssue, validateOperationArguments, } from "../operations/operation-validator-core.js";
5
6
  import { validationIssue, } from "../operations/validation-code.js";
6
7
  import { copyAtDoor, refuseCallerData } from "../runtime/caller-data.door.js";
@@ -172,7 +173,7 @@ async function answerQuestion(session, dependencies, active, question) {
172
173
  }
173
174
  const { resource, action, where } = question;
174
175
  const identifier = adapterPrimaryIdentifier(dependencies.adapter.model, resource);
175
- const interactive = active.transactions === "interactive";
176
+ const interactive = opensServerTransactions(active.transactions);
176
177
  if (action === "update") {
177
178
  const checkedAfter = session.checksAfter(resource, "update");
178
179
  if ((!interactive && checkedAfter) ||
@@ -1,2 +1,2 @@
1
1
  /** The version of this `@aventara/core`. */
2
- export declare const CORE_VERSION = "1.0.0-rc.12";
2
+ export declare const CORE_VERSION = "1.0.0-rc.14";
@@ -1 +1 @@
1
- export const CORE_VERSION = "1.0.0-rc.12";
1
+ export const CORE_VERSION = "1.0.0-rc.14";
@@ -41,8 +41,8 @@ type OperationRouteResolution = {
41
41
  export declare function resolveOperationRoute(contracts: AvContracts, protocol: AvRequestProtocol, route: OperationRouteSegments, origin?: RemoteRequestOrigin): OperationRouteResolution;
42
42
  /**
43
43
  * The `/_transactions` route under one client protocol identity: version → hash → the
44
- * ClientContract's transaction capability. The route exists only when that capability
45
- * is `"interactive"` — tested against the value itself, as `protocol.surface()` tests
44
+ * ClientContract's transactions. The route exists only when they take plans
45
+ * (`"interactive"`; `"server"` takes none) — tested as `protocol.surface()` tests
46
46
  * it — and is otherwise absent with core's own refusal of a plan. `undefined` when the
47
47
  * route is there.
48
48
  */
@@ -1,6 +1,7 @@
1
1
  import { lookupOwn, lookupResource } from "../contracts/contract-lookup.js";
2
2
  import { AVENTARA_PROTOCOL_VERSION } from "../contracts/contract-protocol.js";
3
3
  import { isOperationVariantAvailable, } from "../contracts/resource-operations.js";
4
+ import { takesTransactionPlans } from "../contracts/transaction-capability.js";
4
5
  import { parseOperationIdentity, } from "../operations/operation-identity.js";
5
6
  import { operationUnavailableIssue } from "../operations/operation-validator.js";
6
7
  import { resourceUnavailableIssue } from "../operations/operation-validator-core.js";
@@ -45,7 +46,7 @@ export function resolveOperationRoute(contracts, protocol, route, origin = "clie
45
46
  }
46
47
  function transactionsRouteFailure(contracts, protocol, origin = "client") {
47
48
  return (protocolIdentityFailure(contracts, protocol, origin) ??
48
- (contracts[origin].transactions === "interactive"
49
+ (takesTransactionPlans(contracts[origin].transactions)
49
50
  ? undefined
50
51
  : transactionsUnavailableFailure()));
51
52
  }
@@ -4,6 +4,7 @@
4
4
  */
5
5
  export { normalizeEntrypoint } from "../config/entrypoint.normalizer.js";
6
6
  export { scalarFormats } from "../contracts/wire-scalar-format.js";
7
+ export { computeOperationFingerprint as fingerprint } from "../transactions/operation-fingerprint.js";
7
8
  export { httpStatus } from "./http-status.table.js";
8
9
  export { isOperationResponse } from "./operation-response.guard.js";
9
10
  export { bind } from "./protocol.binder.js";
@@ -1,5 +1,6 @@
1
1
  export { normalizeEntrypoint } from "../config/entrypoint.normalizer.js";
2
2
  export { scalarFormats } from "../contracts/wire-scalar-format.js";
3
+ export { computeOperationFingerprint as fingerprint } from "../transactions/operation-fingerprint.js";
3
4
  export { httpStatus } from "./http-status.table.js";
4
5
  export { isOperationResponse } from "./operation-response.guard.js";
5
6
  export { bind } from "./protocol.binder.js";
@@ -1,5 +1,6 @@
1
1
  import { lookupOwn, lookupResource } from "../contracts/contract-lookup.js";
2
2
  import { isOperationVariantAvailable } from "../contracts/resource-operations.js";
3
+ import { takesTransactionPlans } from "../contracts/transaction-capability.js";
3
4
  import { parseOperationIdentity, } from "../operations/operation-identity.js";
4
5
  const ownKeys = (record) => record === undefined ? [] : Object.keys(record);
5
6
  export function advertisesAnyOperation(client) {
@@ -56,7 +57,7 @@ export function surfaceOf(client) {
56
57
  method: "GET",
57
58
  path: "/_contract",
58
59
  }));
59
- if (client.transactions === "interactive") {
60
+ if (takesTransactionPlans(client.transactions)) {
60
61
  routes.push(Object.freeze({
61
62
  kind: "transactions",
62
63
  method: "POST",
@@ -2,6 +2,7 @@ import { copyAtDoor } from "../runtime/caller-data.door.js";
2
2
  import { createDiagnosticsReporter, createReporter, } from "../runtime/diagnostics-reporter.js";
3
3
  import { sanitizeOperationFailure } from "../runtime/error-boundary.js";
4
4
  import { internalFailure, } from "../runtime/operation-response.js";
5
+ import { stateRefusalOf } from "../runtime/state-refusal.mapper.js";
5
6
  import { capturePlanAtCall } from "../transactions/transaction-plan-validator.js";
6
7
  import { checkOperationRoute } from "./absent-route.responder.js";
7
8
  import { adoptParsedBody, decodeCanRequest, decodeOperationRead, decodeTransactionRequest, isParsedByDecoder, transportOf, } from "./http-request.decoder.js";
@@ -77,7 +78,8 @@ export async function seededState(options, report, view) {
77
78
  catch (error) {
78
79
  return {
79
80
  ok: false,
80
- response: sanitizeOperationFailure(error, "runtime", report, view),
81
+ response: stateRefusalOf(error) ??
82
+ sanitizeOperationFailure(error, "runtime", report, view),
81
83
  };
82
84
  }
83
85
  }
@@ -1,4 +1,5 @@
1
1
  import { isOperationVariantAvailable } from "../contracts/resource-operations.js";
2
+ import { opensServerTransactions } from "../contracts/transaction-capability.js";
2
3
  import { parseOperationIdentity } from "../operations/operation-identity.js";
3
4
  import { validationIssue } from "../operations/validation-code.js";
4
5
  import { readSqlTemplate } from "../transactions/sql-statement.js";
@@ -218,7 +219,7 @@ export function createContextExecutor(dependencies) {
218
219
  if (scope.transaction !== undefined) {
219
220
  return (async () => run(context))();
220
221
  }
221
- if (dependencies.application.transactions === "none") {
222
+ if (!opensServerTransactions(dependencies.application.transactions)) {
222
223
  return Promise.reject(thrownFailure(unavailable("Transactions are not available on the application Contract.")));
223
224
  }
224
225
  return openTransaction(scope, (executor) => run(contextOf(prototype, {