@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
@@ -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,50 @@ 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>>;
396
+ /** Whether a field's capabilities offer a filter operator. */
397
+ type Filters<Field> = Field extends {
398
+ readonly capabilities: infer Caps;
399
+ } ? [MemberTokens<NonNullable<Caps[keyof Caps & "filter"]>>] extends [never] ? false : true : false;
400
+ /** The fields a `fields` block stops filtering on: the parent filters on them, the layer no longer does. */
401
+ type UnfilteredField<Fields, Block, Style> = [Block] extends [never] ? never : {
402
+ [F in keyof Fields & keyof Block]-?: Filters<Fields[F]> extends true ? Filters<ExemptedField<Fields[F], NonNullable<Block[F]>, Style>> extends true ? never : F : never;
403
+ }[keyof Fields & keyof Block];
404
+ /** The fields no identifier of the layer may name. */
405
+ type UnaddressableField<Fields, Block, Style> = RemovedField<Fields, Block, Style> | UnfilteredField<Fields, Block, Style>;
322
406
  /** One family's entry in an operations entry: `true` for a listed family. */
323
407
  type FamilyEntry<Operations, Family> = Operations extends readonly unknown[] ? Family extends Listed<Operations> ? true : unknown : [Operations] extends [boolean] ? Operations : Family extends keyof Operations ? NonNullable<Operations[Family]> : unknown;
324
408
  /** The keys of a per-variant object entry set to a literal `true`. */
@@ -342,16 +426,24 @@ type KeptOperations<Operations, Entry, Style, UniqueLost> = {
342
426
  readonly [V in KeptVariant<NonNullable<Operations[F]>, FamilyEntry<Entry, F>, Style, UniqueLost>]: NonNullable<Operations[F]>[V];
343
427
  };
344
428
  };
345
- /** A Resource's identifiers less those naming a removed field. */
429
+ /** A Resource's identifiers less those naming an unaddressable field. */
346
430
  type KeptIdentifiers<Identifiers, Removed> = [Removed] extends [never] ? Identifiers : Identifiers extends readonly (infer Identifier)[] ? readonly (Identifier extends readonly (infer Field)[] ? [Extract<Field, Removed>] extends [never] ? Identifier : never : never)[] : Identifiers;
347
431
  /** Whether removing fields left a Resource with no identifier at all. */
348
432
  type IdentifiersLost<Identifiers, Removed> = [Removed] extends [never] ? false : [
349
433
  KeptIdentifiers<Identifiers, Removed> extends readonly (infer Kept)[] ? Kept : never
350
434
  ] extends [never] ? true : false;
351
435
  /** A Resource's `fields` and `operations` entries, read off its exemption object. */
352
- 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 & {
353
439
  readonly fields?: unknown;
354
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>;
355
447
  type OperationsEntryOf<Entry> = (Entry & {
356
448
  readonly operations?: unknown;
357
449
  })["operations"];
@@ -359,14 +451,14 @@ type OperationsEntryOf<Entry> = (Entry & {
359
451
  type ExemptedResourceObject<in out Resource, in out FieldsBlock, in out OperationsEntry, in out Style> = {
360
452
  readonly [P in keyof Resource]: P extends "fields" ? ExemptedFields<Resource[P], FieldsBlock, Style> : P extends "operations" ? ExemptedOperations<Resource[P], OperationsEntry, Style, IdentifiersLost<Resource extends {
361
453
  readonly identifiers: infer Ids;
362
- } ? Ids : never, RemovedField<Resource extends {
454
+ } ? Ids : never, UnaddressableField<Resource extends {
363
455
  readonly fields: infer Fields;
364
- } ? Fields : never, FieldsBlock, Style>>> : P extends "identifiers" ? KeptIdentifiers<Resource[P], RemovedField<Resource extends {
456
+ } ? Fields : never, FieldsBlock, Style>>> : P extends "identifiers" ? KeptIdentifiers<Resource[P], UnaddressableField<Resource extends {
365
457
  readonly fields: infer Fields;
366
458
  } ? Fields : never, FieldsBlock, Style>> : Resource[P];
367
459
  };
368
460
  /** One Resource under its exemption: `true` (or `boolean`) keeps the parent's. */
369
- 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>;
370
462
  /** A layer's Resources: its parent's, with its exemptions applied in its style. */
371
463
  type ExposedResources<Resources, Style, Block> = Style extends "omit" ? [Block] extends [never] ? Resources : {
372
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.11";
2
+ export declare const CORE_VERSION = "1.0.0-rc.13";
@@ -1 +1 @@
1
- export const CORE_VERSION = "1.0.0-rc.11";
1
+ export const CORE_VERSION = "1.0.0-rc.13";
@@ -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, {
@@ -21,12 +21,16 @@ export type RecordWriteFacts = {
21
21
  readonly references: ReferentialActionTable;
22
22
  readonly stated: (resource: string) => AvCreateRequirements | undefined;
23
23
  };
24
- /** The facts an Adapter stated, read off it once; an absent table states nothing. */
24
+ /**
25
+ * The facts an Adapter stated, read off it once, with the non-null added
26
+ * (`VIRTUAL`) fields of `core` required too; an absent table states nothing.
27
+ */
25
28
  export declare function recordWriteFactsOf(adapter: {
26
29
  readonly referentialActions?: ReferentialActionTable;
27
30
  readonly model?: {
28
31
  readonly resources: Readonly<Record<string, unknown>>;
29
32
  };
33
+ readonly core?: AvContract;
30
34
  }): RecordWriteFacts;
31
35
  /**
32
36
  * The `V1000` issues for every create position in one operation's arguments
@@ -1,3 +1,4 @@
1
+ import { withVirtualRequirements } from "../compiler/create-requirements.projector.js";
1
2
  import { lookupField, lookupResource } from "../contracts/contract-lookup.js";
2
3
  import { advertisedDirectivesAt } from "../contracts/field.js";
3
4
  import { LOOPBACK_WRITE_SCOPE, NESTED_WRITE_SCOPE, resolveWriteScope, } from "../operations/write-scope.resolver.js";
@@ -292,14 +293,21 @@ function walkNestedCreates(context, resource, record, path) {
292
293
  }
293
294
  export function recordWriteFactsOf(adapter) {
294
295
  const resources = adapter.model?.resources ?? {};
296
+ const contracted = adapter.core?.resources ?? {};
295
297
  return {
296
298
  references: adapter.referentialActions ?? {},
297
299
  stated: (resource) => {
298
300
  const found = Object.hasOwn(resources, resource)
299
301
  ? resources[resource]
300
302
  : undefined;
301
- const stated = isRecord(found) ? found.createRequirements : undefined;
302
- return isRecord(stated) ? stated : undefined;
303
+ const own = isRecord(found) ? found.createRequirements : undefined;
304
+ const stated = isRecord(own) ? own : undefined;
305
+ const fields = Object.hasOwn(contracted, resource)
306
+ ? contracted[resource]?.fields
307
+ : undefined;
308
+ return fields === undefined
309
+ ? stated
310
+ : withVirtualRequirements(stated, { fields });
303
311
  },
304
312
  };
305
313
  }
@@ -418,10 +418,47 @@ const FORMAT_LABELS = {
418
418
  function plural(count, one, many) {
419
419
  return `${count} ${count === 1 ? one : many}`;
420
420
  }
421
+ const PATTERN_SYNTAX = "\\^$.*+?()[]{}|/";
422
+ function listedValues(pattern) {
423
+ const { source, flags } = pattern;
424
+ if (flags !== "" || !source.startsWith("^(?:") || !source.endsWith(")$")) {
425
+ return undefined;
426
+ }
427
+ const body = source.slice(4, -2);
428
+ const values = [];
429
+ let value = "";
430
+ for (let index = 0; index < body.length; index += 1) {
431
+ const character = body.charAt(index);
432
+ if (character === "\\") {
433
+ const escaped = body.charAt(index + 1);
434
+ if (escaped === "" || !PATTERN_SYNTAX.includes(escaped)) {
435
+ return undefined;
436
+ }
437
+ value += escaped;
438
+ index += 1;
439
+ }
440
+ else if (character === "|") {
441
+ values.push(value);
442
+ value = "";
443
+ }
444
+ else if (PATTERN_SYNTAX.includes(character)) {
445
+ return undefined;
446
+ }
447
+ else {
448
+ value += character;
449
+ }
450
+ }
451
+ values.push(value);
452
+ return values;
453
+ }
421
454
  function formatMessage(check) {
422
455
  switch (check.format) {
423
- case "regex":
424
- return `Value must match the pattern /${check.pattern.source}/${check.pattern.flags}.`;
456
+ case "regex": {
457
+ const listed = listedValues(check.pattern);
458
+ return listed === undefined
459
+ ? `Value must match the pattern /${check.pattern.source}/${check.pattern.flags}.`
460
+ : `Value must be one of ${listed.map((value) => JSON.stringify(value)).join(", ")}.`;
461
+ }
425
462
  case "includes":
426
463
  return `Value must include ${JSON.stringify(check.includes)}.`;
427
464
  case "starts_with":
@@ -7,11 +7,12 @@ import type { ReferentialActionTable } from "./referential-action.js";
7
7
  /** The write phase of a record: a created row (an upsert's `create` branch too) or an updated one. */
8
8
  type WriteStage = "create" | "update";
9
9
  /**
10
- * The fields a judgment leaves for later: a plan step's (or a forced call's)
11
- * literal values are judged before BEGIN, except a field a write compute stage
12
- * of that phase may still change, which is judged after write compute.
10
+ * The fields a judgment leaves out (`hiddenFieldRefusal`: the ones the active
11
+ * layer shows, which its own judgment covers).
13
12
  */
14
13
  type DeferredField = (resource: string, stage: WriteStage, field: string) => boolean;
14
+ /** Whether the value at a path differs between `before` and `after`. */
15
+ export declare function valuesChangedBetween(before: unknown, after: unknown): (path: Path) => boolean;
15
16
  /**
16
17
  * Every way the sent fields `select` admits break their validators: the
17
18
  * operation's own records, then each nested write target's. Paths are the
@@ -1,5 +1,6 @@
1
1
  import { hasFieldValidators, validatedFieldsOf, } from "../compiler/field-validator.compiler.js";
2
2
  import { AvComputedValueError } from "./computed-value.error.js";
3
+ import { isAvDecimal } from "./decimal.js";
3
4
  import { entryCheckIssues, fieldCheckIssues } from "./field-check.evaluator.js";
4
5
  import { valueAt, writeRecordPathsOf, } from "./field-stage.runtime.js";
5
6
  import { nestedWriteTargetsOf } from "./nested-write.walker.js";
@@ -41,6 +42,34 @@ function entryIssues(rules, entry, at) {
41
42
  const { compiled } = rules;
42
43
  return entryCheckIssues(compiled.checks, entry, compiled.subject ?? "string", at);
43
44
  }
45
+ function sameWrittenValue(left, right) {
46
+ if (Object.is(left, right)) {
47
+ return true;
48
+ }
49
+ if (left instanceof Date && right instanceof Date) {
50
+ return Object.is(left.getTime(), right.getTime());
51
+ }
52
+ if (isAvDecimal(left) && isAvDecimal(right)) {
53
+ return left.toString() === right.toString();
54
+ }
55
+ if (left instanceof Uint8Array && right instanceof Uint8Array) {
56
+ return (left.length === right.length &&
57
+ left.every((byte, index) => byte === right[index]));
58
+ }
59
+ if (Array.isArray(left) && Array.isArray(right)) {
60
+ return (left.length === right.length &&
61
+ left.every((entry, index) => sameWrittenValue(entry, right[index])));
62
+ }
63
+ if (isPlainRecord(left) && isPlainRecord(right)) {
64
+ const keys = Object.keys(left);
65
+ return (keys.length === Object.keys(right).length &&
66
+ keys.every((key) => Object.hasOwn(right, key) && sameWrittenValue(left[key], right[key])));
67
+ }
68
+ return false;
69
+ }
70
+ export function valuesChangedBetween(before, after) {
71
+ return (path) => !sameWrittenValue(valueAt(before, path), valueAt(after, path));
72
+ }
44
73
  function recordIssues(contract, resource, records, args, argumentsPath, select, deferred) {
45
74
  const compiled = validatedFieldsOf(contract, resource);
46
75
  const issues = [];