@aventara/core 1.0.0-rc.4 → 1.0.0-rc.9
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 +2 -2
- package/README.md +80 -7
- package/dist/adapters/adapter-field.d.ts +8 -0
- package/dist/adapters/adapter-identifier.d.ts +9 -0
- package/dist/adapters/adapter-identifier.js +6 -1
- package/dist/adapters/adapter-operation.d.ts +12 -6
- package/dist/compiler/contract-compiler-diagnostic.guidance.js +2 -2
- package/dist/compiler/contract.compiler.js +26 -3
- package/dist/config/config.resolver.d.ts +2 -0
- package/dist/config/config.resolver.js +7 -2
- package/dist/config/config.validator.js +36 -12
- package/dist/config/exact-framework-config.d.ts +11 -1
- package/dist/config/framework-config.d.ts +14 -1
- package/dist/contracts/contract-canonicalizer.js +2 -0
- package/dist/contracts/contract.d.ts +15 -1
- package/dist/contracts/field.d.ts +17 -0
- package/dist/contracts/scalar-value-type.d.ts +3 -1
- package/dist/framework/contract-view.d.ts +9 -1
- package/dist/framework/framework-construction.error.d.ts +2 -1
- package/dist/framework/framework-construction.error.js +13 -1
- package/dist/framework/framework.d.ts +26 -5
- package/dist/framework/framework.factory.js +29 -10
- package/dist/framework/operation-facade.d.ts +59 -12
- package/dist/index.d.ts +8 -4
- package/dist/internal.d.ts +2 -2
- package/dist/internal.js +2 -0
- package/dist/operations/operation-arguments.d.ts +43 -27
- package/dist/operations/operation-call.d.ts +2 -2
- package/dist/operations/operation-result.d.ts +1 -1
- package/dist/policy/access-provider.interface.d.ts +36 -0
- package/dist/policy/access-provider.interface.js +1 -0
- package/dist/policy/access-provider.registry.d.ts +9 -0
- package/dist/policy/access-provider.registry.js +10 -0
- package/dist/policy/policy-admission.js +12 -3
- package/dist/policy/policy-after-update.runtime.js +5 -4
- package/dist/policy/policy-engine.d.ts +11 -4
- package/dist/policy/policy-nested-write.runtime.js +11 -7
- package/dist/policy/policy-read.runtime.d.ts +0 -2
- package/dist/policy/policy-read.runtime.js +18 -9
- package/dist/policy/policy-request.d.ts +7 -3
- package/dist/policy/policy-request.js +14 -3
- package/dist/policy/policy-session.d.ts +14 -4
- package/dist/policy/policy-session.js +12 -11
- package/dist/policy/policy-startup.validator.js +13 -15
- package/dist/policy/policy-write.runtime.d.ts +7 -0
- package/dist/policy/policy-write.runtime.js +23 -8
- package/dist/prebuilt/core-version.generated.d.ts +1 -1
- package/dist/prebuilt/core-version.generated.js +1 -1
- package/dist/prebuilt/prebuilt-changes.d.ts +28 -0
- package/dist/prebuilt/prebuilt-changes.js +65 -0
- package/dist/prebuilt/prebuilt-contracts.loader.js +11 -56
- package/dist/prebuilt/prebuilt-inputs.fingerprint.js +16 -1
- package/dist/protocol/absent-route.responder.d.ts +4 -4
- package/dist/protocol/http-request.decoder.d.ts +2 -2
- package/dist/protocol/http-request.reader.d.ts +2 -2
- package/dist/protocol/http-response.encoder.js +2 -2
- package/dist/protocol/protocol-headers.d.ts +3 -3
- package/dist/runtime/caller-data.copier.js +3 -4
- package/dist/runtime/computed-read-runtime.js +2 -7
- package/dist/runtime/context-executor.runtime.d.ts +2 -2
- package/dist/runtime/core-class.brand.d.ts +10 -0
- package/dist/runtime/core-class.brand.js +21 -0
- package/dist/runtime/create-requirement.js +1 -1
- package/dist/runtime/decimal.d.ts +14 -0
- package/dist/runtime/decimal.js +21 -0
- package/dist/runtime/diagnostics-reporter.d.ts +5 -5
- package/dist/runtime/diagnostics-reporter.js +13 -1
- package/dist/runtime/framework-error.js +9 -0
- package/dist/runtime/operation-request.d.ts +9 -9
- package/dist/runtime/operation-runtime.d.ts +8 -8
- package/dist/runtime/operation-runtime.js +30 -9
- package/dist/runtime/owned-execution.registry.d.ts +2 -2
- package/dist/runtime/readonly-value.d.ts +1 -1
- package/dist/runtime/readonly-value.js +2 -2
- package/dist/runtime/referential-action.d.ts +1 -1
- package/dist/runtime/scalar-runtime.js +4 -5
- package/dist/transactions/operation-arguments-encoder.js +8 -3
- package/dist/transactions/sql-statement.d.ts +2 -2
- package/dist/transactions/sql-statement.validator.js +2 -2
- package/dist/transactions/transaction-integrity-error.js +2 -0
- package/dist/transactions/transaction-plan-assembler.d.ts +11 -4
- package/dist/transactions/transaction-plan-assembler.js +17 -2
- package/dist/transactions/transaction-plan-validator.d.ts +5 -5
- package/dist/transactions/transaction-request.d.ts +8 -8
- package/dist/transactions/transaction-runtime.d.ts +2 -2
- package/dist/transactions/transaction-runtime.js +12 -7
- package/dist/transactions/transaction-slot-path-error.js +2 -0
- package/package.json +1 -1
|
@@ -114,7 +114,7 @@ type FieldSlotReferences<C extends AvContract, K extends AvResourceKey<C>, F ext
|
|
|
114
114
|
* operand but `mode`, `isEmpty`, `path` and the `json` ones may be a `$ref` of
|
|
115
115
|
* the shape it holds (`References`).
|
|
116
116
|
*/
|
|
117
|
-
type
|
|
117
|
+
export type AvScalarFilterOperands<Value, Atom, Json, References extends Readonly<Record<TransactionSlotShape, unknown>>, Operator extends AvScalarFilterOperator, Mode extends ScalarFilterMode, PathMode extends ScalarFilterMode, LevelMode extends ScalarFilterMode> = {
|
|
118
118
|
equals: Value | References["value"];
|
|
119
119
|
not: NegatedValue<Value, References, Operator> | ScalarFilterOperators<Value, Atom, Json, References, Operator, Mode, PathMode>;
|
|
120
120
|
in: readonly Atom[] | References["entries"];
|
|
@@ -149,8 +149,8 @@ type ScalarFilterOperands<Value, Atom, Json, References extends Readonly<Record<
|
|
|
149
149
|
type ScalarFilterOperators<Value, Atom, Json, References extends Readonly<Record<TransactionSlotShape, unknown>>, Operator extends AvScalarFilterOperator, Mode extends ScalarFilterMode, PathMode extends ScalarFilterMode> = ScalarFilterLevel<ReadonlyOperands<Value, Atom, Json, References, Operator, Mode, PathMode, Mode>, Operator, Mode, NegatedValue<Value, References, Operator>, "path" extends Operator ? {
|
|
150
150
|
readonly path?: never;
|
|
151
151
|
} : unknown> | ("path" extends Operator ? ScalarFilterOperatorsAtPath<ReadonlyOperands<Value, Atom, Json, References, Operator, Mode, PathMode, PathMode>, Operator, PathMode, NegatedValue<Value, References, Operator>> : never);
|
|
152
|
-
/** {@link
|
|
153
|
-
type ReadonlyOperands<Value, Atom, Json, References extends Readonly<Record<TransactionSlotShape, unknown>>, Operator extends AvScalarFilterOperator, Mode extends ScalarFilterMode, PathMode extends ScalarFilterMode, LevelMode extends ScalarFilterMode> = Readonly<
|
|
152
|
+
/** {@link AvScalarFilterOperands}, each operand read-only. */
|
|
153
|
+
type ReadonlyOperands<Value, Atom, Json, References extends Readonly<Record<TransactionSlotShape, unknown>>, Operator extends AvScalarFilterOperator, Mode extends ScalarFilterMode, PathMode extends ScalarFilterMode, LevelMode extends ScalarFilterMode> = Readonly<AvScalarFilterOperands<Value, Atom, Json, References, Operator, Mode, PathMode, LevelMode>>;
|
|
154
154
|
/** The operators of a level that compare something: all but `mode` and `path`. */
|
|
155
155
|
type ComparingOperator<Operator extends AvScalarFilterOperator> = Exclude<Operator, "mode" | "path">;
|
|
156
156
|
/** `not`'s value arm: equality negated, offered only where `equals` is. */
|
|
@@ -224,7 +224,7 @@ export type AvWhereInput<C extends AvContract, K extends AvResourceKey<C>, Forms
|
|
|
224
224
|
AND?: readonly AvWhereInput<C, K, Forms, ReferenceForms>[];
|
|
225
225
|
OR?: readonly AvWhereInput<C, K, Forms, ReferenceForms>[];
|
|
226
226
|
NOT?: AvWhereInput<C, K, Forms, ReferenceForms>;
|
|
227
|
-
} & ScalarWhereFields<C, K, Forms, ReferenceForms> & RelationWhereFields<C, K, Forms, ReferenceForms
|
|
227
|
+
}> & ScalarWhereFields<C, K, Forms, ReferenceForms> & RelationWhereFields<C, K, Forms, ReferenceForms>;
|
|
228
228
|
type ActiveIdentifier<C extends AvContract, K extends AvResourceKey<C>> = C["resources"][K]["identifiers"][number];
|
|
229
229
|
type IdentifierObject<C extends AvContract, K extends AvResourceKey<C>, I extends readonly string[], Forms extends ScalarForms, ReferenceForms extends ScalarForms> = Readonly<{
|
|
230
230
|
[F in Extract<I[number], AvFieldKey<C, K>>]: ScalarValueAt<C, K, F, Forms> | SlotReference<C, K, F, ReferenceForms, "nonNullValue">;
|
|
@@ -356,18 +356,18 @@ type ManyOrOne<C extends AvContract, K extends AvResourceKey<C>, F extends Relat
|
|
|
356
356
|
* A `where` of plain fields: each scalar field's filter, combined with
|
|
357
357
|
* `AND`/`OR`/`NOT`, and no relation filter at any depth.
|
|
358
358
|
*/
|
|
359
|
-
type
|
|
360
|
-
AND?: readonly
|
|
361
|
-
OR?: readonly
|
|
362
|
-
NOT?:
|
|
363
|
-
} & ScalarWhereFields<C, K, Forms, ReferenceForms
|
|
359
|
+
export type AvScalarWhereInput<C extends AvContract, K extends AvResourceKey<C>, Forms extends ScalarForms = UnstatedForms, ReferenceForms extends ScalarForms = never> = Readonly<{
|
|
360
|
+
AND?: readonly AvScalarWhereInput<C, K, Forms, ReferenceForms>[];
|
|
361
|
+
OR?: readonly AvScalarWhereInput<C, K, Forms, ReferenceForms>[];
|
|
362
|
+
NOT?: AvScalarWhereInput<C, K, Forms, ReferenceForms>;
|
|
363
|
+
}> & ScalarWhereFields<C, K, Forms, ReferenceForms>;
|
|
364
364
|
/**
|
|
365
365
|
* The `where` of a nested `$update` or `$delete` through relation `F`: plain fields
|
|
366
366
|
* only where the relation states `nestedWriteFilter: "scalar"`, any filter otherwise.
|
|
367
367
|
*/
|
|
368
368
|
type NestedWriteWhereInput<C extends AvContract, K extends AvResourceKey<C>, F extends RelationFieldKeyOf<C, K>, Forms extends ScalarForms, ReferenceForms extends ScalarForms> = CapabilitiesOf<C, K, F> extends {
|
|
369
369
|
readonly nestedWriteFilter: "scalar";
|
|
370
|
-
} ?
|
|
370
|
+
} ? AvScalarWhereInput<C, RelationTarget<C, K, F>, Forms, ReferenceForms> : AvWhereInput<C, RelationTarget<C, K, F>, Forms, ReferenceForms>;
|
|
371
371
|
type ConnectOrCreateEntry<C extends AvContract, K extends AvResourceKey<C>, F extends RelationFieldKeyOf<C, K>, Forms extends ScalarForms, ReferenceForms extends ScalarForms> = Readonly<{
|
|
372
372
|
where: AvUniqueWhereInput<C, RelationTarget<C, K, F>, Forms, ReferenceForms>;
|
|
373
373
|
create: AvCreateDataInput<C, RelationTarget<C, K, F>, UnrestrictedWrite, Forms, ReferenceForms, readonly [K, F]>;
|
|
@@ -397,15 +397,14 @@ type ToManyUpsertEntry<C extends AvContract, K extends AvResourceKey<C>, F exten
|
|
|
397
397
|
update: AvUpdateDataInput<C, RelationTarget<C, K, F>, UnrestrictedWrite, Forms, ReferenceForms, readonly [K, F]>;
|
|
398
398
|
}>;
|
|
399
399
|
type RelationDirectiveValue<C extends AvContract, K extends AvResourceKey<C>, F extends RelationFieldKeyOf<C, K>, Directive extends AvRelationMutationDirective, Forms extends ScalarForms, ReferenceForms extends ScalarForms> = Directive extends "$create" ? ManyOrOne<C, K, F, AvCreateDataInput<C, RelationTarget<C, K, F>, UnrestrictedWrite, Forms, ReferenceForms, readonly [K, F]>> : Directive extends "$connect" ? ManyOrOne<C, K, F, AvUniqueWhereInput<C, RelationTarget<C, K, F>, Forms, ReferenceForms>> : Directive extends "$connectOrCreate" ? ManyOrOne<C, K, F, ConnectOrCreateEntry<C, K, F, Forms, ReferenceForms>> : Directive extends "$disconnect" ? RelationCardinality<C, K, F> extends "one" ? true : AvUniqueWhereInput<C, RelationTarget<C, K, F>, Forms, ReferenceForms> | readonly AvUniqueWhereInput<C, RelationTarget<C, K, F>, Forms, ReferenceForms>[] : Directive extends "$set" ? RelationCardinality<C, K, F> extends "one" ? AvUniqueWhereInput<C, RelationTarget<C, K, F>, Forms, ReferenceForms> | null : readonly AvUniqueWhereInput<C, RelationTarget<C, K, F>, Forms, ReferenceForms>[] : Directive extends "$update" ? RelationCardinality<C, K, F> extends "one" ? ToOneUpdateEntry<C, K, F, Forms, ReferenceForms> : ManyOrOne<C, K, F, ToManyUpdateEntry<C, K, F, Forms, ReferenceForms>> : Directive extends "$delete" ? RelationCardinality<C, K, F> extends "one" ? ToOneDeleteEntry<C, K, F, Forms, ReferenceForms> : ManyOrOne<C, K, F, ToManyDeleteEntry<C, K, F, Forms, ReferenceForms>> : Directive extends "$upsert" ? RelationCardinality<C, K, F> extends "one" ? ToOneUpsertEntry<C, K, F, Forms, ReferenceForms> : ManyOrOne<C, K, F, ToManyUpsertEntry<C, K, F, Forms, ReferenceForms>> : never;
|
|
400
|
-
declare const NO_ADVERTISED_MEMBER: unique symbol;
|
|
401
400
|
/**
|
|
402
401
|
* A write member that advertises nothing: it accepts `{}` and refuses every key.
|
|
403
402
|
*/
|
|
404
|
-
|
|
405
|
-
readonly [
|
|
406
|
-
}
|
|
407
|
-
/** `Write`, or {@link
|
|
408
|
-
type RefuseEveryKeyWhenEmpty<Write> = [keyof Write] extends [never] ?
|
|
403
|
+
export interface AvNoAdvertisedMember {
|
|
404
|
+
readonly [Symbol.unscopables]?: never;
|
|
405
|
+
}
|
|
406
|
+
/** `Write`, or {@link AvNoAdvertisedMember} when `Write` has no keys at all. */
|
|
407
|
+
type RefuseEveryKeyWhenEmpty<Write> = [keyof Write] extends [never] ? AvNoAdvertisedMember : Write;
|
|
409
408
|
type RelationMutationActionFor<C extends AvContract, K extends AvResourceKey<C>, F extends RelationFieldKeyOf<C, K>, Stage extends "create" | "update", Scope extends AvWriteScope, Forms extends ScalarForms, ReferenceForms extends ScalarForms> = RefuseEveryKeyWhenEmpty<Readonly<Partial<{
|
|
410
409
|
[Directive in Extract<CapabilityTokens<C, K, F, Stage, Scope>, AvRelationMutationDirective>]: RelationDirectiveValue<C, K, F, Directive, Forms, ReferenceForms>;
|
|
411
410
|
}>>>;
|
|
@@ -432,14 +431,23 @@ type OneDirective<Tokens extends AvScalarMutationDirective, C extends AvContract
|
|
|
432
431
|
/**
|
|
433
432
|
* Each writable scalar takes its value under `Forms`, or a directive object of
|
|
434
433
|
* the directives it advertises at this stage — or, in a transaction plan's
|
|
435
|
-
* top-level data, a `$ref` to a source of the same type.
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
[F in Exclude<
|
|
442
|
-
}>;
|
|
434
|
+
* top-level data, a `$ref` to a source of the same type. A field a create must
|
|
435
|
+
* write (`RequiredWriteKey`) is required.
|
|
436
|
+
*/
|
|
437
|
+
type ScalarMutationFields<C extends AvContract, K extends AvResourceKey<C>, Stage extends "create" | "update", Scope extends AvWriteScope, Forms extends ScalarForms, ReferenceForms extends ScalarForms, Via extends ParentEdge = never, Keys extends AvFieldKey<C, K> = Exclude<WritableScalarKey<C, K, Stage, Scope>, LoopbackRefused<C, K, Stage, Via>>, Required extends Keys = Extract<Keys, RequiredWriteKey<C, K, Stage, Scope, Via>>> = Readonly<([Required] extends [never] ? unknown : {
|
|
438
|
+
[F in Required]: ScalarValueAt<C, K, F, Forms> | ScalarMutationActionFor<C, K, F, Stage, Scope, Forms> | SlotReference<C, K, F, ReferenceForms, "value">;
|
|
439
|
+
}) & ([Exclude<Keys, Required>] extends [never] ? unknown : {
|
|
440
|
+
[F in Exclude<Keys, Required>]?: ScalarValueAt<C, K, F, Forms> | ScalarMutationActionFor<C, K, F, Stage, Scope, Forms> | SlotReference<C, K, F, ReferenceForms, "value">;
|
|
441
|
+
})>;
|
|
442
|
+
/**
|
|
443
|
+
* Each writable relation takes the directive object it advertises at this
|
|
444
|
+
* stage. A relation a create must write (`RequiredWriteKey`) is required.
|
|
445
|
+
*/
|
|
446
|
+
type RelationMutationFields<C extends AvContract, K extends AvResourceKey<C>, Stage extends "create" | "update", Scope extends AvWriteScope, Forms extends ScalarForms, ReferenceForms extends ScalarForms, Via extends ParentEdge = never, Keys extends AvFieldKey<C, K> = Exclude<WritableRelationKey<C, K, Stage, Scope>, LoopbackRefused<C, K, Stage, Via>>, Required extends Keys = Extract<Keys, RequiredWriteKey<C, K, Stage, Scope, Via>>> = Readonly<([Required] extends [never] ? unknown : {
|
|
447
|
+
[F in Required]: RelationMutationActionFor<C, K, F & RelationFieldKeyOf<C, K>, Stage, Scope, Forms, ReferenceForms>;
|
|
448
|
+
}) & ([Exclude<Keys, Required>] extends [never] ? unknown : {
|
|
449
|
+
[F in Exclude<Keys, Required>]?: RelationMutationActionFor<C, K, F & RelationFieldKeyOf<C, K>, Stage, Scope, Forms, ReferenceForms>;
|
|
450
|
+
})>;
|
|
443
451
|
/** A relation edge a nested create is reached through: `[parent, relation]`. */
|
|
444
452
|
type ParentEdge = readonly [string, string];
|
|
445
453
|
/** The other end of `Via`'s relation, as the Contract pairs it; `never` when it does not. */
|
|
@@ -497,13 +505,21 @@ type GroupPresence<Group, Scalars, Relations, Supplied> = Group extends readonly
|
|
|
497
505
|
}) : Present<Relation> : Present<References[number]> : unknown : unknown;
|
|
498
506
|
/** Every group of a `oneOf` tuple, each one's presence intersected. */
|
|
499
507
|
type GroupsPresence<Groups, Scalars, Relations, Supplied> = Groups extends readonly [infer Group, ...infer Rest] ? GroupPresence<Group, Scalars, Relations, Supplied> & GroupsPresence<Rest, Scalars, Relations, Supplied> : unknown;
|
|
508
|
+
/**
|
|
509
|
+
* The names a create of `K` at `Scope` must write: every `required` name it can
|
|
510
|
+
* write there, less what the parent supplies through `Via`. `never` for an
|
|
511
|
+
* update, and where the Contract states nothing.
|
|
512
|
+
*/
|
|
513
|
+
type RequiredWriteKey<C extends AvContract, K extends AvResourceKey<C>, Stage extends "create" | "update", Scope extends AvWriteScope, Via> = Stage extends "create" ? [CreateRequirementsAt<C, K>] extends [never] ? never : CreateRequirementsAt<C, K> extends infer Requirements ? string extends RequirementNames<Requirements, "required"> ? never : Exclude<Extract<RequirementNames<Requirements, "required">, WritableScalarKey<C, K, "create", Scope> | WritableRelationKey<C, K, "create", Scope>>, SuppliedThrough<Requirements, Via>> : never : never;
|
|
514
|
+
/** The members a create's two halves hold at `Scope`, reached through `Via`. */
|
|
515
|
+
type HeldCreateKey<C extends AvContract, K extends AvResourceKey<C>, Scope extends AvWriteScope, Via> = Exclude<WritableScalarKey<C, K, "create", Scope> | WritableRelationKey<C, K, "create", Scope>, LoopbackRefused<C, K, "create", Via>>;
|
|
500
516
|
/**
|
|
501
517
|
* What a create of Resource `K` at write position `Scope` must and must not
|
|
502
518
|
* write: every `required` name it can write, and one side of every `oneOf`
|
|
503
519
|
* group — less what the parent supplies when the create is nested through
|
|
504
520
|
* `Via`. `unknown` (no constraint) when the Contract states nothing.
|
|
505
521
|
*/
|
|
506
|
-
type CreateRequirementPresence<C extends AvContract, K extends AvResourceKey<C>, Scope extends AvWriteScope, Via> = [CreateRequirementsAt<C, K>] extends [never] ? unknown : CreateRequirementsAt<C, K> extends infer Requirements ? string extends RequirementNames<Requirements, "required"> ? unknown : Present<Exclude<
|
|
522
|
+
type CreateRequirementPresence<C extends AvContract, K extends AvResourceKey<C>, Scope extends AvWriteScope, Via> = [CreateRequirementsAt<C, K>] extends [never] ? unknown : CreateRequirementsAt<C, K> extends infer Requirements ? string extends RequirementNames<Requirements, "required"> ? unknown : Present<Exclude<RequiredWriteKey<C, K, "create", Scope, Via>, HeldCreateKey<C, K, Scope, Via>>> & (Requirements extends {
|
|
507
523
|
readonly oneOf?: infer Groups;
|
|
508
524
|
} ? GroupsPresence<Groups, WritableScalarKey<C, K, "create", Scope>, WritableRelationKey<C, K, "create", Scope>, SuppliedThrough<Requirements, Via>> : unknown) : unknown;
|
|
509
525
|
/**
|
|
@@ -518,12 +534,12 @@ type CreateRequirementPresence<C extends AvContract, K extends AvResourceKey<C>,
|
|
|
518
534
|
* reference fields — never both. `Via` is the `[parent, relation]` a nested
|
|
519
535
|
* create is reached through: what that parent supplies is not required.
|
|
520
536
|
*/
|
|
521
|
-
export type AvCreateDataInput<C extends AvContract, K extends AvResourceKey<C>, Scope extends AvWriteScope = UnrestrictedWrite, Forms extends ScalarForms = UnstatedForms, ReferenceForms extends ScalarForms = never, Via extends ParentEdge = never> = RefuseEveryKeyWhenEmpty<
|
|
537
|
+
export type AvCreateDataInput<C extends AvContract, K extends AvResourceKey<C>, Scope extends AvWriteScope = UnrestrictedWrite, Forms extends ScalarForms = UnstatedForms, ReferenceForms extends ScalarForms = never, Via extends ParentEdge = never> = RefuseEveryKeyWhenEmpty<ScalarMutationFields<C, K, "create", Scope, Forms, ReferenceForms, Via>> & RefuseEveryKeyWhenEmpty<RelationMutationFields<C, K, "create", Scope, Forms, ReferenceForms, Via>> & CreateRequirementPresence<C, K, Scope, Via>;
|
|
522
538
|
/**
|
|
523
539
|
* An `update` data argument, at one write position. See {@link AvCreateDataInput};
|
|
524
540
|
* `Via` is the `[parent, relation]` a nested update is reached through.
|
|
525
541
|
*/
|
|
526
|
-
export type AvUpdateDataInput<C extends AvContract, K extends AvResourceKey<C>, Scope extends AvWriteScope = UnrestrictedWrite, Forms extends ScalarForms = UnstatedForms, ReferenceForms extends ScalarForms = never, Via extends ParentEdge = never> = RefuseEveryKeyWhenEmpty<
|
|
542
|
+
export type AvUpdateDataInput<C extends AvContract, K extends AvResourceKey<C>, Scope extends AvWriteScope = UnrestrictedWrite, Forms extends ScalarForms = UnstatedForms, ReferenceForms extends ScalarForms = never, Via extends ParentEdge = never> = RefuseEveryKeyWhenEmpty<ScalarMutationFields<C, K, "update", Scope, Forms, ReferenceForms, Via>> & RefuseEveryKeyWhenEmpty<RelationMutationFields<C, K, "update", Scope, Forms, ReferenceForms, Via>>;
|
|
527
543
|
type QueryProjectionArgs<C extends AvContract, K extends AvResourceKey<C>, Scope extends AvProjectionScope, Forms extends ScalarForms> = ProjectionChoice<C, K, Scope, false, Forms>;
|
|
528
544
|
/**
|
|
529
545
|
* Every standard operation's argument object, with each argument at its
|
|
@@ -56,7 +56,7 @@ export type AvOperationSlot<I extends AvOperationIdentity, Result> = I["variant"
|
|
|
56
56
|
/** What a direct call resolves: the slot, every failure thrown. */
|
|
57
57
|
type Settled<I extends AvOperationIdentity, Result> = AvOperationSlot<I, Result>;
|
|
58
58
|
/** A direct call that projects: generic in its `select`/`include` literal. */
|
|
59
|
-
export type ProjectingCall<C extends AvContract, R extends AvResourceKey<C>, I extends AvOperationIdentity, Args, ResultForms extends ScalarForms, Options> = <const Select extends SelectOf<Args> = never, const Include extends IncludeOf<Args> = never>(...args: CallArgumentList<Omit<Args, ProjectionKey>, Omit<Args, ProjectionKey
|
|
59
|
+
export type ProjectingCall<C extends AvContract, R extends AvResourceKey<C>, I extends AvOperationIdentity, Args, ResultForms extends ScalarForms, Options> = <const Select extends SelectOf<Args> = never, const Include extends IncludeOf<Args> = never>(...args: CallArgumentList<Omit<Args, ProjectionKey>, NoInfer<Omit<Args, ProjectionKey>> & ProjectionArgument<Select, Include>, Options>) => Promise<Settled<I, AdmittedOperationResult<C, R, I, InferredProjection<Select, Include>, ResultForms>>>;
|
|
60
60
|
/** A direct call that does not project. */
|
|
61
61
|
export type PlainCall<C extends AvContract, R extends AvResourceKey<C>, I extends AvOperationIdentity, Args, ResultForms extends ScalarForms, Options> = (...args: CallArgumentList<Args, Args, Options>) => Promise<Settled<I, AdmittedOperationResult<C, R, I, unknown, ResultForms>>>;
|
|
62
62
|
/**
|
|
@@ -72,7 +72,7 @@ export type AvOperationCall<C extends AvContract, R extends AvResourceKey<C>, I
|
|
|
72
72
|
*/
|
|
73
73
|
export type DeferredArguments<C extends AvContract, R extends AvResourceKey<C>, I extends AvOperationIdentity, Forms extends ScalarForms, ResultForms extends ScalarForms> = ReferenceableArgumentsFor<C, R, I, Forms, ResultForms>;
|
|
74
74
|
/** A deferred operation that projects: generic in its `select`/`include` literal. */
|
|
75
|
-
export type DeferredProjectingCall<C extends AvContract, R extends AvResourceKey<C>, I extends AvOperationIdentity, Args, ResultForms extends ScalarForms, Origin extends HandleOrigin> = <const Select extends SelectOf<Args> = never, const Include extends IncludeOf<Args> = never>(...args: ArgumentList<Omit<Args, ProjectionKey>, Omit<Args, ProjectionKey
|
|
75
|
+
export type DeferredProjectingCall<C extends AvContract, R extends AvResourceKey<C>, I extends AvOperationIdentity, Args, ResultForms extends ScalarForms, Origin extends HandleOrigin> = <const Select extends SelectOf<Args> = never, const Include extends IncludeOf<Args> = never>(...args: ArgumentList<Omit<Args, ProjectionKey>, NoInfer<Omit<Args, ProjectionKey>> & ProjectionArgument<Select, Include>>) => AvOperation<AvOperationSlot<I, AdmittedOperationResult<C, R, I, InferredProjection<Select, Include>, ResultForms>>, Origin>;
|
|
76
76
|
/** A deferred operation that does not project. */
|
|
77
77
|
export type DeferredPlainCall<C extends AvContract, R extends AvResourceKey<C>, I extends AvOperationIdentity, Args, ResultForms extends ScalarForms, Origin extends HandleOrigin> = (...args: ArgumentList<Args, Args>) => AvOperation<AvOperationSlot<I, AdmittedOperationResult<C, R, I, unknown, ResultForms>>, Origin>;
|
|
78
78
|
/**
|
|
@@ -35,7 +35,7 @@ type EntryArguments<Entry, F extends string> = Entry extends object ? Entry exte
|
|
|
35
35
|
*/
|
|
36
36
|
type ProjectedFields<C extends AvContract, K extends AvResourceKey<C>, Keys extends AvFieldKey<C, K>, Entries, Forms extends ScalarForms> = {
|
|
37
37
|
readonly [F in Keys]: FieldOf<C, K, F> extends AvScalarField<C> ? AvScalarValueType<C, FieldOf<C, K, F>, Forms> : FieldOf<C, K, F> extends AvRelationField<C> ? RelationResult<C, Extract<FieldOf<C, K, F>["target"], AvResourceKey<C>>, FieldOf<C, K, F>, EntryArguments<Entries, F>, Forms> : never;
|
|
38
|
-
};
|
|
38
|
+
} extends infer Fields ? Fields : never;
|
|
39
39
|
/**
|
|
40
40
|
* One projection level: `select` returns exactly its entries, `include` the
|
|
41
41
|
* default fields plus the named relations, and no projection the default fields.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { AvState } from "../runtime/request-state.js";
|
|
2
|
+
import type { AvPolicyEngine } from "./policy-engine.js";
|
|
3
|
+
/**
|
|
4
|
+
* What the framework's `access` option takes: the policy engine every client
|
|
5
|
+
* and agent request is checked against, and how a request's state is seeded.
|
|
6
|
+
* Aventara Access provides one; one written by hand works the same way.
|
|
7
|
+
*
|
|
8
|
+
* The request's principal is `ctx.$state.me` (`null` when it is absent), so
|
|
9
|
+
* `state` names it as `me`. With `access`, the configuration states no
|
|
10
|
+
* `policy`, and the host no `state` of its own.
|
|
11
|
+
*/
|
|
12
|
+
export interface AvAccessProvider {
|
|
13
|
+
/** The policy engine (`AvPolicyEngine`), asked about every client and agent request. */
|
|
14
|
+
readonly policy: AvPolicyEngine;
|
|
15
|
+
/**
|
|
16
|
+
* Seeds a request's `ctx.$state` from its headers (a Web `Headers`): `me`, the principal the
|
|
17
|
+
* policy is asked about (`null` for an anonymous caller), and anything else
|
|
18
|
+
* the stages read. Called once per request by the host. `request` is the
|
|
19
|
+
* host's own request when it has one: Express's `req`, Fastify's
|
|
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.
|
|
23
|
+
*/
|
|
24
|
+
state(input: {
|
|
25
|
+
readonly headers: {
|
|
26
|
+
get(name: string): string | null;
|
|
27
|
+
has(name: string): boolean;
|
|
28
|
+
forEach(callback: (value: string, name: string) => void): void;
|
|
29
|
+
};
|
|
30
|
+
readonly request?: unknown;
|
|
31
|
+
}): (AvState & {
|
|
32
|
+
readonly me: unknown;
|
|
33
|
+
}) | Promise<AvState & {
|
|
34
|
+
readonly me: unknown;
|
|
35
|
+
}>;
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AvAccessProvider } from "./access-provider.interface.js";
|
|
2
|
+
/** Records the provider `framework` was configured with; called once, at construction. */
|
|
3
|
+
export declare function recordAccessProvider(framework: object, access: AvAccessProvider | undefined): void;
|
|
4
|
+
/**
|
|
5
|
+
* The `state` of the provider `framework` was configured with (`access`),
|
|
6
|
+
* called on the provider; `undefined` for a Framework without `access`, or a
|
|
7
|
+
* value that is no constructed Framework.
|
|
8
|
+
*/
|
|
9
|
+
export declare function accessStateOf(framework: object): AvAccessProvider["state"] | undefined;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const ACCESS_PROVIDERS = new WeakMap();
|
|
2
|
+
export function recordAccessProvider(framework, access) {
|
|
3
|
+
if (access !== undefined) {
|
|
4
|
+
ACCESS_PROVIDERS.set(framework, access);
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export function accessStateOf(framework) {
|
|
8
|
+
const access = ACCESS_PROVIDERS.get(framework);
|
|
9
|
+
return access === undefined ? undefined : (input) => access.state(input);
|
|
10
|
+
}
|
|
@@ -232,7 +232,10 @@ export async function admitByPolicy(session, core, active, request, args) {
|
|
|
232
232
|
...named.used.keys(),
|
|
233
233
|
...named.nodes.map((node) => node.resource),
|
|
234
234
|
]);
|
|
235
|
-
const answers = new Map(await Promise.all([...resources].map(async (resource) => [
|
|
235
|
+
const answers = new Map(await Promise.all([...resources].map(async (resource) => [
|
|
236
|
+
resource,
|
|
237
|
+
await session.fields(resource, { access: "read" }),
|
|
238
|
+
])));
|
|
236
239
|
const conditional = new Map();
|
|
237
240
|
for (const [resource, fields] of named.used) {
|
|
238
241
|
const answer = answers.get(resource);
|
|
@@ -300,7 +303,6 @@ async function admitNestedRecords(session, core, resource, directives) {
|
|
|
300
303
|
if (!isRecord(directives)) {
|
|
301
304
|
return undefined;
|
|
302
305
|
}
|
|
303
|
-
const fields = await session.fields(resource, "write");
|
|
304
306
|
const owner = lookupResource(core, resource);
|
|
305
307
|
for (const [directive, operand] of Object.entries(directives)) {
|
|
306
308
|
if (!isRelationMutationDirective(directive)) {
|
|
@@ -312,6 +314,10 @@ async function admitNestedRecords(session, core, resource, directives) {
|
|
|
312
314
|
if (!isRecord(record)) {
|
|
313
315
|
continue;
|
|
314
316
|
}
|
|
317
|
+
const fields = await session.fields(resource, {
|
|
318
|
+
access: "write",
|
|
319
|
+
action: site.stage,
|
|
320
|
+
});
|
|
315
321
|
for (const [field, value] of Object.entries(record)) {
|
|
316
322
|
if (value === undefined) {
|
|
317
323
|
continue;
|
|
@@ -343,10 +349,13 @@ export async function admitWriteByPolicy(session, core, request, args) {
|
|
|
343
349
|
return undefined;
|
|
344
350
|
}
|
|
345
351
|
const resource = request.resource;
|
|
346
|
-
const fields = await session.fields(resource, "write");
|
|
347
352
|
const grants = [];
|
|
348
353
|
const names = [];
|
|
349
354
|
for (const { record, stage } of records) {
|
|
355
|
+
const fields = await session.fields(resource, {
|
|
356
|
+
access: "write",
|
|
357
|
+
action: stage,
|
|
358
|
+
});
|
|
350
359
|
for (const [field, value] of Object.entries(record)) {
|
|
351
360
|
if (value === undefined) {
|
|
352
361
|
continue;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { adapterPrimaryIdentifier } from "../adapters/adapter-identifier.js";
|
|
1
2
|
import { lookupField, lookupResource } from "../contracts/contract-lookup.js";
|
|
2
3
|
import { frozenCopy } from "../runtime/readonly-value.js";
|
|
3
4
|
import { policyRefusal } from "./policy-admission.js";
|
|
4
5
|
import { allOf, whereOf } from "./policy-condition.js";
|
|
5
|
-
import { identifiedRows
|
|
6
|
+
import { identifiedRows } from "./policy-read.runtime.js";
|
|
6
7
|
function isRecord(value) {
|
|
7
8
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
8
9
|
}
|
|
@@ -48,7 +49,7 @@ function touches(core, model, resource, scope, data) {
|
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
51
|
else {
|
|
51
|
-
for (const member of
|
|
52
|
+
for (const member of adapterPrimaryIdentifier(model, resource)) {
|
|
52
53
|
read.add(member);
|
|
53
54
|
}
|
|
54
55
|
}
|
|
@@ -82,7 +83,7 @@ function identityKey(identifier, row) {
|
|
|
82
83
|
export function afterCheckFields(core, model, resource) {
|
|
83
84
|
return listOf(core, `after\u0000${resource}`, () => [
|
|
84
85
|
...new Set([
|
|
85
|
-
...
|
|
86
|
+
...adapterPrimaryIdentifier(model, resource),
|
|
86
87
|
...rowFieldsOf(core, resource),
|
|
87
88
|
]),
|
|
88
89
|
]);
|
|
@@ -101,7 +102,7 @@ export async function checkChanged(session, core, model, resource, checks, chang
|
|
|
101
102
|
return undefined;
|
|
102
103
|
}
|
|
103
104
|
const fresh = session.renewed();
|
|
104
|
-
const identifier =
|
|
105
|
+
const identifier = adapterPrimaryIdentifier(model, resource);
|
|
105
106
|
const these = identifiedRows(core, identifier, resource, changed);
|
|
106
107
|
if (these === false) {
|
|
107
108
|
return undefined;
|
|
@@ -97,11 +97,18 @@ export interface AvPolicyEngine<Governed extends {
|
|
|
97
97
|
* may use on every row it can reach (`"all"`: every field that is not
|
|
98
98
|
* governed); `perRow` maps a field it may use only on some rows to the
|
|
99
99
|
* condition those rows meet. A field in neither is refused (`A4002`)
|
|
100
|
-
* wherever the request names it.
|
|
100
|
+
* wherever the request names it. A write question names the `action` the
|
|
101
|
+
* fields are written by, `"create"` or `"update"` (an upsert's branches
|
|
102
|
+
* each by their own), so a field may be locked on update and still written
|
|
103
|
+
* at create.
|
|
101
104
|
*/
|
|
102
|
-
fieldsOf(question: PolicyQuestion & {
|
|
103
|
-
readonly access: "read"
|
|
104
|
-
|
|
105
|
+
fieldsOf(question: PolicyQuestion & ({
|
|
106
|
+
readonly access: "read";
|
|
107
|
+
readonly action?: undefined;
|
|
108
|
+
} | {
|
|
109
|
+
readonly access: "write";
|
|
110
|
+
readonly action: "create" | "update";
|
|
111
|
+
})): PolicyAnswer<{
|
|
105
112
|
readonly always: "all" | readonly string[];
|
|
106
113
|
readonly perRow: {
|
|
107
114
|
readonly [field: string]: AvPolicyWhere | undefined;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { adapterPrimaryIdentifier } from "../adapters/adapter-identifier.js";
|
|
1
2
|
import { lookupField, lookupResource } from "../contracts/contract-lookup.js";
|
|
2
3
|
import { isRelationMutationDirective } from "../runtime/mutation-planner.js";
|
|
3
4
|
import { frozenCopy } from "../runtime/readonly-value.js";
|
|
4
5
|
import { policyRefusal } from "./policy-admission.js";
|
|
5
6
|
import { afterUpdate, checkChanged, rowFieldsOf, } from "./policy-after-update.runtime.js";
|
|
6
7
|
import { allOf, noneOf, someOf, whereOf, } from "./policy-condition.js";
|
|
7
|
-
import { identifiedRows
|
|
8
|
+
import { identifiedRows } from "./policy-read.runtime.js";
|
|
8
9
|
import { fieldAccess } from "./policy-session.js";
|
|
9
10
|
function isRecord(value) {
|
|
10
11
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -106,14 +107,17 @@ class NestedWalk {
|
|
|
106
107
|
return undefined;
|
|
107
108
|
}
|
|
108
109
|
identifier(resource) {
|
|
109
|
-
const identifier =
|
|
110
|
+
const identifier = adapterPrimaryIdentifier(this.model, resource);
|
|
110
111
|
if (identifier.length === 0) {
|
|
111
112
|
throw new NestedRefusal(`The policy checks the rows a nested write reaches in Resource "${resource}" by identifier, and the Resource has none.`);
|
|
112
113
|
}
|
|
113
114
|
return identifier;
|
|
114
115
|
}
|
|
115
|
-
async grantsOf(resource, records) {
|
|
116
|
-
const fields = await this.session.fields(resource,
|
|
116
|
+
async grantsOf(resource, records, action) {
|
|
117
|
+
const fields = await this.session.fields(resource, {
|
|
118
|
+
access: "write",
|
|
119
|
+
action,
|
|
120
|
+
});
|
|
117
121
|
const grants = new Map();
|
|
118
122
|
for (const record of records) {
|
|
119
123
|
for (const [name, value] of Object.entries(record)) {
|
|
@@ -333,7 +337,7 @@ class NestedWalk {
|
|
|
333
337
|
? false
|
|
334
338
|
: identifiedRows(this.core, identifier, resource, rows);
|
|
335
339
|
}
|
|
336
|
-
const grants = await this.grantsOf(resource, [data]);
|
|
340
|
+
const grants = await this.grantsOf(resource, [data], "update");
|
|
337
341
|
if (grants.length > 0) {
|
|
338
342
|
this.ask(resource, "fields", allOf(reach, someOf(grants.map(noneOf))));
|
|
339
343
|
}
|
|
@@ -369,7 +373,7 @@ class NestedWalk {
|
|
|
369
373
|
const before = there === false
|
|
370
374
|
? []
|
|
371
375
|
: await this.session.rows(resource, whereOf(there), identifier);
|
|
372
|
-
const grants = await this.grantsOf(resource, records);
|
|
376
|
+
const grants = await this.grantsOf(resource, records, "create");
|
|
373
377
|
const inner = await this.level(resource, { kind: "new" }, records);
|
|
374
378
|
return async (parents) => {
|
|
375
379
|
if (parents.length === 0) {
|
|
@@ -415,7 +419,7 @@ async function checkCreatedRows(session, core, identifier, resource, grants, row
|
|
|
415
419
|
export async function planNestedWrites(session, core, model, resource, record, parent) {
|
|
416
420
|
const walk = new NestedWalk(session, core, model);
|
|
417
421
|
try {
|
|
418
|
-
const identifier =
|
|
422
|
+
const identifier = adapterPrimaryIdentifier(model, resource);
|
|
419
423
|
if (identifier.length === 0) {
|
|
420
424
|
throw new NestedRefusal(`The policy checks the rows a nested write reaches by identifier, and Resource "${resource}" has none.`);
|
|
421
425
|
}
|
|
@@ -59,8 +59,6 @@ export type PolicyRootRows = {
|
|
|
59
59
|
readonly carried?: readonly string[];
|
|
60
60
|
};
|
|
61
61
|
type Row = Readonly<Record<string, unknown>>;
|
|
62
|
-
/** The Resource's identifier as the adapter states it first (its primary key). */
|
|
63
|
-
export declare function identifierOf(model: AvAdapterModel, resource: string): readonly string[];
|
|
64
62
|
/**
|
|
65
63
|
* One read's policy (see the module comment): what the adapter runs, and the
|
|
66
64
|
* checks its answer goes through; or the empty answer when the read reaches no
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { adapterPrimaryIdentifier } from "../adapters/adapter-identifier.js";
|
|
1
2
|
import { lookupField, lookupResource } from "../contracts/contract-lookup.js";
|
|
2
3
|
import { policyRefusal } from "./policy-admission.js";
|
|
3
4
|
import { allOf, anyOf, everyOf, noneOf, someOf, } from "./policy-condition.js";
|
|
@@ -11,12 +12,6 @@ function relationOf(core, resource, name) {
|
|
|
11
12
|
? field
|
|
12
13
|
: undefined;
|
|
13
14
|
}
|
|
14
|
-
export function identifierOf(model, resource) {
|
|
15
|
-
const resources = model.resources;
|
|
16
|
-
return ((Object.hasOwn(resources, resource)
|
|
17
|
-
? resources[resource]?.identifiers?.[0]
|
|
18
|
-
: undefined) ?? []);
|
|
19
|
-
}
|
|
20
15
|
function innerFilters(relation, value) {
|
|
21
16
|
return relation.cardinality === "many"
|
|
22
17
|
? [value.some, value.every, value.none]
|
|
@@ -192,6 +187,12 @@ function emptyRelationValue(select) {
|
|
|
192
187
|
? { data: [], count: 0 }
|
|
193
188
|
: { count: 0 };
|
|
194
189
|
}
|
|
190
|
+
function scopedCursor(cursor, scope) {
|
|
191
|
+
if (!isRecord(cursor) || typeof scope === "boolean") {
|
|
192
|
+
return cursor;
|
|
193
|
+
}
|
|
194
|
+
return { ...cursor, AND: [scope] };
|
|
195
|
+
}
|
|
195
196
|
function withIdentifier(select, identifier) {
|
|
196
197
|
const missing = identifier.filter((member) => !select.includes(member));
|
|
197
198
|
return missing.length === 0 ? select : [...select, ...missing];
|
|
@@ -210,6 +211,7 @@ export async function enforceReadPolicy(session, core, model, request, plan, ada
|
|
|
210
211
|
const checks = await returnedRowChecks(session, core, plan);
|
|
211
212
|
let identity = request;
|
|
212
213
|
let rootWhere = args.where;
|
|
214
|
+
let rootCursor = args.cursor;
|
|
213
215
|
const scope = rows === undefined ? scoped.scope(root) : rows.scope;
|
|
214
216
|
if (scope === undefined) {
|
|
215
217
|
}
|
|
@@ -251,6 +253,7 @@ export async function enforceReadPolicy(session, core, model, request, plan, ada
|
|
|
251
253
|
};
|
|
252
254
|
}
|
|
253
255
|
rootWhere = where === true ? undefined : where;
|
|
256
|
+
rootCursor = scopedCursor(args.cursor, scope);
|
|
254
257
|
}
|
|
255
258
|
const rewriteLevel = (resource, select, level) => {
|
|
256
259
|
const entries = [];
|
|
@@ -289,6 +292,9 @@ export async function enforceReadPolicy(session, core, model, request, plan, ada
|
|
|
289
292
|
else {
|
|
290
293
|
rewritten.where = where;
|
|
291
294
|
}
|
|
295
|
+
if (value.cursor !== undefined) {
|
|
296
|
+
rewritten.cursor = scopedCursor(value.cursor, scoped.scope(relation.target));
|
|
297
|
+
}
|
|
292
298
|
}
|
|
293
299
|
const inner = rewriteLevel(relation.target, value.select, nestedLevel);
|
|
294
300
|
nested.set(name, inner);
|
|
@@ -303,7 +309,7 @@ export async function enforceReadPolicy(session, core, model, request, plan, ada
|
|
|
303
309
|
: [];
|
|
304
310
|
return {
|
|
305
311
|
select: withIdentifier(checked
|
|
306
|
-
? withIdentifier(entries,
|
|
312
|
+
? withIdentifier(entries, adapterPrimaryIdentifier(model, resource))
|
|
307
313
|
: entries, carried),
|
|
308
314
|
emptied,
|
|
309
315
|
nested,
|
|
@@ -320,6 +326,9 @@ export async function enforceReadPolicy(session, core, model, request, plan, ada
|
|
|
320
326
|
else {
|
|
321
327
|
runArguments.where = rootWhere;
|
|
322
328
|
}
|
|
329
|
+
if (rootCursor !== undefined) {
|
|
330
|
+
runArguments.cursor = rootCursor;
|
|
331
|
+
}
|
|
323
332
|
if (Array.isArray(args.select)) {
|
|
324
333
|
runArguments.select = rewritten.select;
|
|
325
334
|
}
|
|
@@ -353,7 +362,7 @@ export async function returnedRowChecks(session, core, plan) {
|
|
|
353
362
|
if (level === undefined) {
|
|
354
363
|
return;
|
|
355
364
|
}
|
|
356
|
-
const fields = await session.fields(level.resource, "read");
|
|
365
|
+
const fields = await session.fields(level.resource, { access: "read" });
|
|
357
366
|
const conditions = [];
|
|
358
367
|
if (reachedOne) {
|
|
359
368
|
const scope = await session.scope(level.resource, "read");
|
|
@@ -461,7 +470,7 @@ async function rowsReadable(session, core, model, projection, checks, data) {
|
|
|
461
470
|
if (miss === true) {
|
|
462
471
|
return true;
|
|
463
472
|
}
|
|
464
|
-
const identifier =
|
|
473
|
+
const identifier = adapterPrimaryIdentifier(model, level.resource);
|
|
465
474
|
const reach = allOf(identifiedRows(core, identifier, level.resource, rows), miss);
|
|
466
475
|
return (reach !== false &&
|
|
467
476
|
session.exists(level.resource, reach === true ? {} : reach));
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AvAdapterModel } from "../adapters/adapter-model.js";
|
|
1
2
|
import type { AvAdapterOperationExecutor } from "../adapters/adapter-operation-executor.interface.js";
|
|
2
3
|
import type { ResolvedPolicyConfig } from "../config/config.resolver.js";
|
|
3
4
|
import type { AvRequestOrigin } from "../config/config-layer.js";
|
|
@@ -19,13 +20,16 @@ export type PolicyCaller = {
|
|
|
19
20
|
export declare function callerOf(options: unknown): PolicyCaller | undefined;
|
|
20
21
|
/**
|
|
21
22
|
* The policy session of one request, or `undefined` when the policy does not
|
|
22
|
-
* check it
|
|
23
|
-
*
|
|
23
|
+
* check it; a promise of it when the principal arrives as one. Deriving the
|
|
24
|
+
* principal is server code: a throw, or a rejection, is the caller's to answer
|
|
25
|
+
* as the server's defect.
|
|
24
26
|
*/
|
|
25
27
|
export declare function openRequestPolicy(policy: ResolvedPolicyConfig | undefined, core: AvContractCore, request: {
|
|
26
28
|
readonly origin: AvRequestOrigin;
|
|
27
29
|
readonly state: AvState;
|
|
28
|
-
}, caller: PolicyCaller | undefined,
|
|
30
|
+
}, caller: PolicyCaller | undefined, adapter: AvAdapterOperationExecutor & {
|
|
31
|
+
readonly model: AvAdapterModel;
|
|
32
|
+
}): PolicySession | undefined | Promise<PolicySession | undefined>;
|
|
29
33
|
/**
|
|
30
34
|
* Policy admission inside its own boundary, as the Framework Validation Layer's
|
|
31
35
|
* is: a refusal, a defect of the engine or its answers (`A3000`), or an adapter
|
|
@@ -14,7 +14,12 @@ export function callerOf(options) {
|
|
|
14
14
|
}
|
|
15
15
|
return principal === undefined ? undefined : { principal };
|
|
16
16
|
}
|
|
17
|
-
|
|
17
|
+
function isThenable(value) {
|
|
18
|
+
return (value !== null &&
|
|
19
|
+
(typeof value === "object" || typeof value === "function") &&
|
|
20
|
+
typeof value.then === "function");
|
|
21
|
+
}
|
|
22
|
+
export function openRequestPolicy(policy, core, request, caller, adapter) {
|
|
18
23
|
if (policy === undefined) {
|
|
19
24
|
return undefined;
|
|
20
25
|
}
|
|
@@ -30,9 +35,15 @@ export function openRequestPolicy(policy, core, request, caller, executor) {
|
|
|
30
35
|
$state: request.state,
|
|
31
36
|
$origin: request.origin,
|
|
32
37
|
});
|
|
38
|
+
if (isThenable(principal)) {
|
|
39
|
+
return Promise.resolve(principal).then((settled) => sessionOf(policy, core, request.origin, adapter, settled));
|
|
40
|
+
}
|
|
33
41
|
}
|
|
34
|
-
return
|
|
35
|
-
|
|
42
|
+
return sessionOf(policy, core, request.origin, adapter, principal);
|
|
43
|
+
}
|
|
44
|
+
function sessionOf(policy, core, origin, adapter, principal) {
|
|
45
|
+
return openPolicySession(policy, core, adapter.model, principal, origin, {
|
|
46
|
+
executor: adapter,
|
|
36
47
|
boundary: new AdapterBoundary(),
|
|
37
48
|
});
|
|
38
49
|
}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
+
import type { AvAdapterModel } from "../adapters/adapter-model.js";
|
|
1
2
|
import type { AvAdapterOperationExecutor } from "../adapters/adapter-operation-executor.interface.js";
|
|
2
3
|
import type { ResolvedPolicyConfig } from "../config/config.resolver.js";
|
|
3
4
|
import type { AvRequestOrigin } from "../config/config-layer.js";
|
|
4
5
|
import type { AvContractCore } from "../contracts/contract.js";
|
|
5
6
|
import type { AdapterBoundary } from "../runtime/error-boundary.js";
|
|
6
7
|
import type { PolicyCondition } from "./policy-condition.js";
|
|
7
|
-
import type { AvPolicyWhere } from "./policy-engine.js";
|
|
8
|
+
import type { AvPolicyEngine, AvPolicyWhere } from "./policy-engine.js";
|
|
9
|
+
/**
|
|
10
|
+
* What `fieldsOf` is asked about, beside the question every method states:
|
|
11
|
+
* reading, or writing by one action. Read from the engine's own signature,
|
|
12
|
+
* which writes the union inline (see `AvPolicyEngine.fieldsOf`).
|
|
13
|
+
*/
|
|
14
|
+
export type PolicyFieldsAccess = Parameters<AvPolicyEngine["fieldsOf"]>[0] extends infer Question ? Question extends unknown ? Pick<Question, Extract<keyof Question, "access" | "action">> : never : never;
|
|
8
15
|
/** An action a policy question names. */
|
|
9
16
|
export type PolicyAction = "read" | "create" | "update" | "delete";
|
|
10
17
|
/** `fieldsOf`'s answer, held: the names read into sets once. */
|
|
@@ -39,8 +46,11 @@ export type PolicySession = {
|
|
|
39
46
|
can(resource: string, action: PolicyAction, row?: Readonly<Record<string, unknown>>): Promise<boolean>;
|
|
40
47
|
/** The rows the principal may `action` (`scopeOf`), checked. */
|
|
41
48
|
scope(resource: string, action: "read" | "update" | "delete"): Promise<PolicyCondition>;
|
|
42
|
-
/**
|
|
43
|
-
|
|
49
|
+
/**
|
|
50
|
+
* The fields the principal may read, or write by one action (`fieldsOf`),
|
|
51
|
+
* checked.
|
|
52
|
+
*/
|
|
53
|
+
fields(resource: string, access: PolicyFieldsAccess): Promise<PolicyFields>;
|
|
44
54
|
/** Whether a row of `resource` meets `where`: core's own question, unchecked. */
|
|
45
55
|
exists(resource: string, where: AvPolicyWhere): Promise<boolean>;
|
|
46
56
|
/**
|
|
@@ -70,7 +80,7 @@ export type PolicySession = {
|
|
|
70
80
|
* Opens the policy session of one request: `principal` under `origin`, its
|
|
71
81
|
* `exists` on `execution` until {@link PolicySession.on} moves it.
|
|
72
82
|
*/
|
|
73
|
-
export declare function openPolicySession(policy: ResolvedPolicyConfig, core: AvContractCore, principal: unknown, origin: AvRequestOrigin, execution: PolicyExecution): PolicySession;
|
|
83
|
+
export declare function openPolicySession(policy: ResolvedPolicyConfig, core: AvContractCore, model: AvAdapterModel, principal: unknown, origin: AvRequestOrigin, execution: PolicyExecution): PolicySession;
|
|
74
84
|
/**
|
|
75
85
|
* Whether `fields` lets the principal use `field` of `resource` on every row it
|
|
76
86
|
* reaches: listed in `always`, or covered by `"all"` when it is not governed.
|