@aventara/core 1.0.0-rc.11 → 1.0.0-rc.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +157 -0
- package/ERRORS.md +8 -7
- package/README.md +3 -2
- package/dist/avz/avz-check.factory.d.ts +2 -0
- package/dist/avz/avz-check.factory.js +12 -0
- package/dist/avz/avz.d.ts +6 -0
- package/dist/avz/avz.js +1 -0
- package/dist/compiler/contract-compiler-diagnostic.d.ts +13 -2
- package/dist/compiler/contract-compiler-diagnostic.guidance.js +7 -2
- package/dist/compiler/contract.compiler.js +110 -15
- package/dist/compiler/create-requirements.projector.d.ts +45 -3
- package/dist/compiler/create-requirements.projector.js +239 -28
- package/dist/compiler/managed-elsewhere.projector.d.ts +3 -0
- package/dist/compiler/managed-elsewhere.projector.js +1 -1
- package/dist/config/computed-fields.d.ts +8 -0
- package/dist/config/computed-fields.js +1 -1
- package/dist/config/config-diagnostic.guidance.js +2 -2
- package/dist/config/config.resolver.d.ts +2 -2
- package/dist/config/config.validator.js +15 -6
- package/dist/config/exact-framework-config.d.ts +1 -0
- package/dist/config/field-pipeline-config.d.ts +2 -2
- package/dist/config/forced-operation.validator.js +3 -2
- package/dist/config/framework-config.d.ts +8 -3
- package/dist/config/framework-exemptions-config.d.ts +13 -8
- package/dist/config/framework-resources-config.d.ts +11 -3
- package/dist/contracts/client-contract-structure.validator.d.ts +4 -1
- package/dist/contracts/client-contract-structure.validator.js +3 -2
- package/dist/contracts/contract-limits.d.ts +8 -1
- package/dist/contracts/contract-limits.js +1 -0
- package/dist/contracts/contract.d.ts +7 -3
- package/dist/contracts/transaction-capability.d.ts +8 -0
- package/dist/contracts/transaction-capability.js +7 -0
- package/dist/framework/access-facade.js +14 -6
- package/dist/framework/contract-view.d.ts +104 -12
- package/dist/framework/operation-facade.d.ts +8 -3
- package/dist/internal.d.ts +1 -0
- package/dist/internal.js +1 -0
- package/dist/operations/validation-code.guidance.js +1 -1
- package/dist/policy/access-provider.interface.d.ts +7 -2
- package/dist/policy/access-question.runtime.js +2 -1
- package/dist/prebuilt/core-version.generated.d.ts +1 -1
- package/dist/prebuilt/core-version.generated.js +1 -1
- package/dist/protocol/absent-route.responder.d.ts +2 -2
- package/dist/protocol/absent-route.responder.js +2 -1
- package/dist/protocol/av-protocol.d.ts +1 -0
- package/dist/protocol/av-protocol.js +1 -0
- package/dist/protocol/protocol-surface.js +2 -1
- package/dist/protocol/protocol.handler.js +3 -1
- package/dist/runtime/context-executor.runtime.js +2 -1
- package/dist/runtime/create-requirement.d.ts +5 -1
- package/dist/runtime/create-requirement.js +10 -2
- package/dist/runtime/field-check.evaluator.js +39 -2
- package/dist/runtime/field-validator.runtime.d.ts +4 -3
- package/dist/runtime/field-validator.runtime.js +29 -0
- package/dist/runtime/mutation-planner.js +26 -9
- package/dist/runtime/operation-code.guidance.js +3 -3
- package/dist/runtime/operation-runtime.d.ts +10 -9
- package/dist/runtime/operation-runtime.js +20 -18
- package/dist/runtime/pipeline.d.ts +1 -15
- package/dist/runtime/state-refusal.mapper.d.ts +16 -0
- package/dist/runtime/state-refusal.mapper.js +40 -0
- package/dist/transactions/operation-fingerprint.d.ts +15 -2
- package/dist/transactions/transaction-plan-validator.js +31 -1
- package/dist/transactions/transaction-runtime.js +1 -1
- package/package.json +1 -1
- package/src/runtime/field-check.evaluator.ts +46 -2
|
@@ -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
|
|
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
|
|
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,
|
|
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
|
|
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<
|
|
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,
|
|
454
|
+
} ? Ids : never, UnaddressableField<Resource extends {
|
|
363
455
|
readonly fields: infer Fields;
|
|
364
|
-
} ? Fields : never, FieldsBlock, Style>>> : P extends "identifiers" ? KeptIdentifiers<Resource[P],
|
|
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,
|
|
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]]: {
|
package/dist/internal.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
*
|
|
22
|
-
*
|
|
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
|
|
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.
|
|
2
|
+
export declare const CORE_VERSION = "1.0.0-rc.13";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CORE_VERSION = "1.0.0-rc.
|
|
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
|
|
45
|
-
*
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
/**
|
|
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
|
|
302
|
-
|
|
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
|
-
|
|
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
|
|
11
|
-
*
|
|
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 = [];
|