@aventara/core 1.0.0-rc.14 → 1.0.0-rc.15
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 +30 -0
- package/ERRORS.md +1 -1
- package/dist/adapters/adapter-model.d.ts +11 -0
- package/dist/adapters/adapter.interface.d.ts +14 -0
- package/dist/config/computed-fields.d.ts +11 -5
- package/dist/config/computed-fields.js +12 -8
- package/dist/config/config-facts.d.ts +22 -0
- package/dist/config/config-facts.js +97 -0
- package/dist/config/field-pipeline-config.d.ts +8 -8
- package/dist/config/framework-config.d.ts +1 -1
- package/dist/config/framework-resources-config.d.ts +42 -11
- package/dist/config/stage-type.eraser.d.ts +7 -4
- package/dist/contracts/json-schema-type.emitter.d.ts +9 -0
- package/dist/contracts/json-schema-type.emitter.js +58 -0
- package/dist/framework/contract-view.d.ts +73 -7
- package/dist/framework/framework.factory.js +12 -2
- package/dist/framework/operation-facade.d.ts +12 -14
- package/dist/index.d.ts +1 -1
- package/dist/internal.d.ts +2 -0
- package/dist/internal.js +2 -0
- package/dist/operations/operation-arguments.d.ts +22 -3
- package/dist/prebuilt/core-version.generated.d.ts +1 -1
- package/dist/prebuilt/core-version.generated.js +1 -1
- package/dist/prebuilt/prebuilt-contracts.emitter.js +5 -3
- package/dist/runtime/field-check.evaluator.js +2 -1
- package/dist/runtime/field-compute.d.ts +8 -8
- package/dist/runtime/pipeline.d.ts +25 -15
- package/dist/transactions/transaction-slot-writer.d.ts +2 -1
- package/dist/transactions/transaction-slot-writer.js +68 -35
- package/package.json +1 -1
- package/src/runtime/field-check.evaluator.ts +9 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @aventara/core
|
|
2
2
|
|
|
3
|
+
## 1.0.0-rc.15
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- aab6d72: The client `avclient generate` writes now passes a default `biome check` as written, on Biome 2.4.0 and later (Biome's default rules; an opt-in rule or assist such as `useSortedKeys` is not covered): every generated file opens with `biome-ignore-all` lines for formatting, lint and import order (`organizeImports`), core's copied declarations have a blank line after their imports, the copied field-check evaluator carries no line suppression that the file-wide one makes unused, and each file ends without a final line break, which Biome 2.4.0 to 2.4.10 reported on every file under `biome-ignore-all format`. `aventara-build`'s own files pass too, in NestJS and Next.js projects: `resources.generated.ts`, `computed-fields.generated.ts` and `contracts.generated.ts` now open with the same suppressions and end without a final line break, and `--client`'s `src/aventara/client.ts` and `src/aventara/universal.ts` are written the same way. The discovery artifact `aventara-prisma7-generate` writes passes too. A file an earlier version wrote is reported stale by `aventara-build --check` and replaced by the next run. A client `avclient generate` wrote before is replaced on its next run. The client skill now says the generated files need no exclusion under Biome's default rules, and how to skip them in another linter or formatter.
|
|
8
|
+
- bf98da7: A pipeline stage's types now know the root's field definitions. `aventara-build` writes `src/generated/aventara/config-facts.generated.ts` after it compiles the Contracts, and the discovery artifact puts it on the model's type:
|
|
9
|
+
|
|
10
|
+
- A Json field the root shapes reads as its shape in a stage's `ctx` calls, in `tx` and in compute's read-only `ctx`: `ctx.Product.find.many({ select: ["name"] })` types `name` as `{ ar: string; en: string }`. A read gives the shape's own type, the same as `framework.application`'s.
|
|
11
|
+
- A `VIRTUAL` field the root adds is in a variant's typed `ctx.$args` (`ctx.$args.data.tenderedMinor`), and a `ctx` create must write a non-null added input it can create.
|
|
12
|
+
- A variant's hooks and interceptors are told resolved arguments: their `ctx.$args` holds no `$ref` (an `AvTransactionReference`), since references are resolved before they run. Guards, pipes and filters still may see one.
|
|
13
|
+
- A write takes a shaped Json value read-only, deep, on every surface (`framework.application`, `ctx`, the generated client too), so a frozen value, such as a stage's `ctx.$args`, fits as well as a mutable one.
|
|
14
|
+
- A compute stage on a shaped Json field returns the shape's type. A compute stage on an added field returns the type its generated fact states, read from the model instead of the configuration. Without the generated file, its return is not typed.
|
|
15
|
+
|
|
16
|
+
The server warns at startup (`AVENTARA_STALE_GENERATED_TYPES`) when the configuration's definitions differ from the ones the generated types were written with, naming each field. It is the same one warning as for stale computed fields: one stale build gives one warning, with a paragraph for each part that is stale, including a facts file it cannot read. `aventara-build --check` also fails on a stale file. Run `aventara:prepare` after you change a `definition`.
|
|
17
|
+
|
|
18
|
+
The discovery artifact's format is now 5: regenerate it with `aventara:prepare` (an artifact of format 4 is refused, as before). The Json Shape type emitter the client generator uses now lives in `@aventara/core`; the generated clients are unchanged. The CLI's agent skill says this in `SKILL.md` and `reference/stages.md`.
|
|
19
|
+
|
|
20
|
+
- f8cd860: The transaction docs and skills now say where a `$ref` works with a Json field: as the field's whole value, from another Json field. Inside a Json value nothing is read as a reference, so a nested `$ref` is stored as the reference object (`{ "$ref": { "operation", "fingerprint", "path" } }`), unresolved. A Json value cannot have `$ref` as its own root key: it is read as a `$ref` directive and refused with `A2004` (`V1006`, or `V1001` for other shapes). To build Json from an earlier step's result, write the `$ref` into a `VIRTUAL` input field and build the Json in a compute stage. No behaviour changed.
|
|
21
|
+
- 8a64af2: A transaction plan's references are installed and resolved in time linear in their number. Before this release the cost grew with the square of the references in one nested list, so a plan with a few thousand of them, such as a long `$connect` or `$create` list where each entry holds a `$ref`, took seconds to validate. The plan's answer is unchanged.
|
|
22
|
+
- 548881c: Pipeline stages type-check faster. A stage typed for compute's read-only context (`(ctx: AvComputeContext<Model>) => …`) placed as a variant's hook or guard, and an annotated stage such as `AvGuard<Model>`, no longer make TypeScript compare two copies of the model's ctx types. On one server application this cut the check by about 40 % (from about 2.03 to 1.21 million type instantiations). The configuration's `pipelines` keys still never decide the model: it comes from the adapter, as before. A stage's `ctx.<Resource>.<family>` (and compute's `ctx.<Resource>.find`) is now named `AvContextFamilyFacade<C, R, F>`, a new export of `@aventara/core`, as `framework.application.<Resource>.<family>` is `AvApplicationFamilyFacade`.
|
|
23
|
+
- 62fa91f: A compute stage's return is now typed by its field, so a wrong return is a type error instead of an `A3000` at run time. A discovered field takes its model type and an added `VIRTUAL` field takes the type in its `definition`, in the root, in overlays, in `defineResource` blocks and inline. A Decimal is an `AvDecimal` (a number or a string is refused, as at run time), a datetime is a `Date`, a list is an array, an enum is one of its members, and `null` fits only a nullable field. Returning `undefined`, or not returning at all, still keeps the current value. A Json field takes any JSON value, and its shape is still checked at run time; type a Json value with a `type` alias, not an `interface`, which has no index signature. `AvComputeCreate`, `AvComputeUpdate`, `AvComputeRead`, `AvFieldCompute` and `AvScalarFieldPipeline` take an optional last type argument for the value, `unknown` by default. The model comes from the adapter: a stage annotated for any model no longer turns off the checks of the other stages beside it.
|
|
24
|
+
|
|
25
|
+
Some code no longer compiles:
|
|
26
|
+
|
|
27
|
+
- A stage that returns one of its inputs (`value`, `data`, `record`, all still `unknown`), such as `return data.title`. Convert it (`String(data.title)`) or check its type first.
|
|
28
|
+
- A stage annotated without the field's value type. `const slug: AvComputeCreate<Model> = …` returns `unknown`, which no field takes: write `AvComputeCreate<Model, string>`, the field's value type (the same for `AvComputeUpdate` and `AvComputeRead`).
|
|
29
|
+
- A bare enum literal, which can widen to `string` (`() => "LIVE"`), in a `defineResource` block and inline. Write `() => "LIVE" as const`.
|
|
30
|
+
|
|
31
|
+
The CLI's agent skill says this in `SKILL.md` and `reference/stages.md`, and its post recipe no longer returns an `unknown` input.
|
|
32
|
+
|
|
3
33
|
## 1.0.0-rc.14
|
|
4
34
|
|
|
5
35
|
### Patch Changes
|
package/ERRORS.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Aventara error codes
|
|
2
2
|
|
|
3
|
-
Every code `@aventara/core` 1.0.0-rc.
|
|
3
|
+
Every code `@aventara/core` 1.0.0-rc.15 answers, reports in a validation issue, or refuses to start with. Generated from `AvErrorCatalog` (exported by `@aventara/core`); do not edit.
|
|
4
4
|
|
|
5
5
|
Branch on the code, never on the message. In code, `AvErrorCatalog[code]` gives the same entry: `{ code, kind, message, cause, fix, docs }`.
|
|
6
6
|
|
|
@@ -32,4 +32,15 @@ export interface AvAdapterModel {
|
|
|
32
32
|
* those stages require none.
|
|
33
33
|
*/
|
|
34
34
|
readonly "~computedFields"?: AvConfigComputedFields;
|
|
35
|
+
/**
|
|
36
|
+
* Type-only, never present at runtime: per Resource, the TypeScript type of
|
|
37
|
+
* each Json field the root's definitions shape (`json`), and each field they
|
|
38
|
+
* add, as the Contract field it compiles to (`added`). A pipeline stage's
|
|
39
|
+
* `ctx` and its typed `$args` read them. Absent: those stages see the
|
|
40
|
+
* model's own fields, Json unshaped.
|
|
41
|
+
*/
|
|
42
|
+
readonly "~configFacts"?: {
|
|
43
|
+
readonly added: Readonly<Record<string, Readonly<Record<string, unknown>>>>;
|
|
44
|
+
readonly json: Readonly<Record<string, Readonly<Record<string, unknown>>>>;
|
|
45
|
+
} | undefined;
|
|
35
46
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AvConfigComputedFields } from "../config/computed-fields.js";
|
|
2
|
+
import type { AvJsonValue } from "../runtime/json-value.js";
|
|
2
3
|
import type { ReferentialActionTable } from "../runtime/referential-action.js";
|
|
3
4
|
import type { AvAdapterModel } from "./adapter-model.js";
|
|
4
5
|
import type { AvAdapterOperationExecutor } from "./adapter-operation-executor.interface.js";
|
|
@@ -40,6 +41,19 @@ export interface AvAdapter<M extends AvAdapterModel = AvAdapterModel> extends Av
|
|
|
40
41
|
* them); it changes no behaviour. Never part of a Contract or its hash.
|
|
41
42
|
*/
|
|
42
43
|
readonly computedFields?: AvConfigComputedFields;
|
|
44
|
+
/**
|
|
45
|
+
* Optional: the Json shapes and added fields the generated types were written
|
|
46
|
+
* with (`config-facts.generated.ts`, the same statement as the model type's
|
|
47
|
+
* `"~configFacts"`): per Resource, each shaped field's JSON Schema and each
|
|
48
|
+
* added field's Contract field. When the Framework is constructed it is
|
|
49
|
+
* compared with the Core Contract, and a difference is a warning that the
|
|
50
|
+
* generated types are stale (`aventara:prepare` regenerates them); it changes
|
|
51
|
+
* no behaviour. Never part of a Contract or its hash.
|
|
52
|
+
*/
|
|
53
|
+
readonly configFacts?: {
|
|
54
|
+
readonly added: Readonly<Record<string, Readonly<Record<string, AvJsonValue>>>>;
|
|
55
|
+
readonly json: Readonly<Record<string, Readonly<Record<string, AvJsonValue>>>>;
|
|
56
|
+
};
|
|
43
57
|
/**
|
|
44
58
|
* Optional: `true` when the Adapter runs developer SQL statements: standalone
|
|
45
59
|
* through its own {@link executeSql}, and, when it has `transaction`, inside a
|
|
@@ -61,10 +61,16 @@ export declare function computedFieldsDrift(generated: AvConfigComputedFields, c
|
|
|
61
61
|
/** The warning's code, as `process.emitWarning` reports it. */
|
|
62
62
|
export declare const STALE_GENERATED_TYPES_WARNING = "AVENTARA_STALE_GENERATED_TYPES";
|
|
63
63
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* carries none) warns nothing.
|
|
64
|
+
* The stale warning's paragraph on the computed fields, naming each difference
|
|
65
|
+
* between the generated types' computed fields and the configuration's; none
|
|
66
|
+
* when they agree or there is nothing to compare (an Adapter that carries none).
|
|
68
67
|
*/
|
|
69
|
-
export declare function
|
|
68
|
+
export declare function staleComputedFields(generated: AvConfigComputedFields | undefined, configured: AvConfigComputedFields): string | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* Warns once, through `process.emitWarning` (code
|
|
71
|
+
* {@link STALE_GENERATED_TYPES_WARNING}), with each paragraph a stale part of
|
|
72
|
+
* the generated types states (`staleComputedFields`, `staleConfigFacts`);
|
|
73
|
+
* nothing when none is stale.
|
|
74
|
+
*/
|
|
75
|
+
export declare function warnOnStaleGeneratedTypes(paragraphs: readonly (string | undefined)[]): void;
|
|
70
76
|
export {};
|
|
@@ -58,16 +58,20 @@ export function computedFieldsDrift(generated, configured) {
|
|
|
58
58
|
return drift;
|
|
59
59
|
}
|
|
60
60
|
export const STALE_GENERATED_TYPES_WARNING = "AVENTARA_STALE_GENERATED_TYPES";
|
|
61
|
-
export function
|
|
61
|
+
export function staleComputedFields(generated, configured) {
|
|
62
62
|
if (generated === undefined) {
|
|
63
|
-
return;
|
|
63
|
+
return undefined;
|
|
64
64
|
}
|
|
65
65
|
const drift = computedFieldsDrift(generated, configured);
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
return drift.length === 0
|
|
67
|
+
? undefined
|
|
68
|
+
: `The computed fields the pre-compute stages and ctx creates are typed with differ from the configuration's:\n${drift
|
|
69
|
+
.map((line) => ` - ${line}`)
|
|
70
|
+
.join("\n")}`;
|
|
71
|
+
}
|
|
72
|
+
export function warnOnStaleGeneratedTypes(paragraphs) {
|
|
73
|
+
const stale = paragraphs.filter((paragraph) => paragraph !== undefined);
|
|
74
|
+
if (stale.length > 0) {
|
|
75
|
+
emitFrameworkWarning(`generated types are stale: run aventara:prepare. ${stale.join("\n")}`, STALE_GENERATED_TYPES_WARNING);
|
|
68
76
|
}
|
|
69
|
-
const message = `generated types are stale: run aventara:prepare. The computed fields the pre-compute stages and ctx creates are typed with differ from the configuration's:\n${drift
|
|
70
|
-
.map((line) => ` - ${line}`)
|
|
71
|
-
.join("\n")}`;
|
|
72
|
-
emitFrameworkWarning(message, STALE_GENERATED_TYPES_WARNING);
|
|
73
77
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AvAdapter } from "../adapters/adapter.interface.js";
|
|
2
|
+
import type { AvContractCore } from "../contracts/contract.js";
|
|
3
|
+
/**
|
|
4
|
+
* The configuration's facts a stage's types read, in the form the generated
|
|
5
|
+
* file states them as a value: per Resource, each shaped Json field's JSON
|
|
6
|
+
* Schema (`json`) and each added field's Contract field (`added`).
|
|
7
|
+
*/
|
|
8
|
+
export type ConfigFacts = NonNullable<AvAdapter["configFacts"]>;
|
|
9
|
+
/** The facts the Core Contract states, every name sorted. */
|
|
10
|
+
export declare function configFactsOf(core: AvContractCore): ConfigFacts;
|
|
11
|
+
/**
|
|
12
|
+
* How the configuration's facts differ from the ones the generated types were
|
|
13
|
+
* written with, one sentence per difference; empty when they agree.
|
|
14
|
+
*/
|
|
15
|
+
export declare function configFactsDrift(generated: ConfigFacts, configured: ConfigFacts): string[];
|
|
16
|
+
/**
|
|
17
|
+
* The stale warning's paragraph on the configuration facts
|
|
18
|
+
* (`warnOnStaleGeneratedTypes`), naming each difference between the generated
|
|
19
|
+
* types' facts and the ones the Core Contract states; none when they agree or
|
|
20
|
+
* there is nothing to compare (an Adapter that carries none).
|
|
21
|
+
*/
|
|
22
|
+
export declare function staleConfigFacts(generated: ConfigFacts | undefined, core: AvContractCore): string | undefined;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { canonicalJson } from "../contracts/canonical-json.writer.js";
|
|
2
|
+
const ADDED_FIELD_MEMBERS = [
|
|
3
|
+
"kind",
|
|
4
|
+
"type",
|
|
5
|
+
"nullable",
|
|
6
|
+
"list",
|
|
7
|
+
"lifecycle",
|
|
8
|
+
"capabilities",
|
|
9
|
+
];
|
|
10
|
+
export function configFactsOf(core) {
|
|
11
|
+
const added = {};
|
|
12
|
+
const json = {};
|
|
13
|
+
for (const resource of Object.keys(core.resources).sort()) {
|
|
14
|
+
const fields = core.resources[resource]?.fields ?? {};
|
|
15
|
+
for (const name of Object.keys(fields).sort()) {
|
|
16
|
+
const field = fields[name];
|
|
17
|
+
if (field?.kind !== "scalar") {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (field.json !== undefined) {
|
|
21
|
+
json[resource] = {
|
|
22
|
+
...json[resource],
|
|
23
|
+
[name]: field.json,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
if (field.lifecycle.includes("VIRTUAL")) {
|
|
27
|
+
added[resource] = {
|
|
28
|
+
...added[resource],
|
|
29
|
+
[name]: Object.fromEntries(ADDED_FIELD_MEMBERS.map((member) => [
|
|
30
|
+
member,
|
|
31
|
+
field[member],
|
|
32
|
+
])),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return { added, json };
|
|
38
|
+
}
|
|
39
|
+
function differing(left, right) {
|
|
40
|
+
const found = [];
|
|
41
|
+
for (const statement of ["added", "json"]) {
|
|
42
|
+
for (const [resource, fields] of Object.entries(left[statement])) {
|
|
43
|
+
for (const [field, fact] of Object.entries(fields)) {
|
|
44
|
+
const other = right[statement][resource]?.[field];
|
|
45
|
+
if (other === undefined ||
|
|
46
|
+
canonicalJson(other) !== canonicalJson(fact)) {
|
|
47
|
+
found.push(`${statement === "added" ? "added field" : "Json shape"} ${resource}.${field}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return found;
|
|
53
|
+
}
|
|
54
|
+
export function configFactsDrift(generated, configured) {
|
|
55
|
+
const missing = new Set(differing(generated, configured));
|
|
56
|
+
return [
|
|
57
|
+
...differing(configured, generated).map((entry) => missing.delete(entry)
|
|
58
|
+
? `changed since the types were generated: ${entry}`
|
|
59
|
+
: `in the configuration, not in the types: ${entry}`),
|
|
60
|
+
...[...missing].map((entry) => `in the types, not in the configuration: ${entry}`),
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
export function staleConfigFacts(generated, core) {
|
|
64
|
+
if (generated === undefined) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
const unreadable = malformedFacts(generated);
|
|
68
|
+
if (unreadable !== undefined) {
|
|
69
|
+
return `The generated config facts (the Json shapes and added fields that stages' ctx and arguments are typed with) cannot be read (${unreadable}): run aventara:prepare to rewrite config-facts.generated.ts.`;
|
|
70
|
+
}
|
|
71
|
+
const drift = configFactsDrift(generated, configFactsOf(core));
|
|
72
|
+
return drift.length === 0
|
|
73
|
+
? undefined
|
|
74
|
+
: `The Json shapes and added fields that stages' ctx and arguments are typed with differ from the configuration's:\n${drift
|
|
75
|
+
.map((line) => ` - ${line}`)
|
|
76
|
+
.join("\n")}`;
|
|
77
|
+
}
|
|
78
|
+
function malformedFacts(facts) {
|
|
79
|
+
if (!isPlainObject(facts)) {
|
|
80
|
+
return "the statement is not an object";
|
|
81
|
+
}
|
|
82
|
+
for (const statement of ["added", "json"]) {
|
|
83
|
+
const resources = facts[statement];
|
|
84
|
+
if (!isPlainObject(resources)) {
|
|
85
|
+
return `\`${statement}\` is not an object`;
|
|
86
|
+
}
|
|
87
|
+
for (const [resource, fields] of Object.entries(resources)) {
|
|
88
|
+
if (!isPlainObject(fields)) {
|
|
89
|
+
return `\`${statement}.${resource}\` is not an object`;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
function isPlainObject(value) {
|
|
96
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
97
|
+
}
|
|
@@ -9,20 +9,20 @@ export type { AvZCheck } from "../avz/avz-check.registry.js";
|
|
|
9
9
|
* A scalar field's computed values, one stage per phase: `create` and `update`
|
|
10
10
|
* run before the write, `read` on each returned record.
|
|
11
11
|
*/
|
|
12
|
-
export type AvFieldCompute<M extends AvAdapterModel = AvAdapterModel, Cfg = AvConfigAny, R extends AvConfigResourceKey<M> = AvConfigResourceKey<M
|
|
12
|
+
export type AvFieldCompute<M extends AvAdapterModel = AvAdapterModel, Cfg = AvConfigAny, R extends AvConfigResourceKey<M> = AvConfigResourceKey<M>, Value = unknown> = {
|
|
13
13
|
/** Computes the value on every create; an upsert's create branch included. */
|
|
14
|
-
readonly create?: AvComputeCreate<M>;
|
|
14
|
+
readonly create?: AvComputeCreate<M, Value>;
|
|
15
15
|
/** Computes the value on every read of a returned record. */
|
|
16
|
-
readonly read?: AvComputeRead<M, Cfg, R>;
|
|
16
|
+
readonly read?: AvComputeRead<M, Cfg, R, Value>;
|
|
17
17
|
/** Computes the value on every update; an upsert's update branch included. */
|
|
18
|
-
readonly update?: AvComputeUpdate<M>;
|
|
18
|
+
readonly update?: AvComputeUpdate<M, Value>;
|
|
19
19
|
};
|
|
20
20
|
/**
|
|
21
21
|
* A scalar field's stages: validators, field pipes, compute and write guards.
|
|
22
22
|
* `Check` is the checks the field takes (any, when the field's type is not
|
|
23
23
|
* known here), or the sentence a type that takes none refuses them with.
|
|
24
24
|
*/
|
|
25
|
-
export type AvScalarFieldPipeline<M extends AvAdapterModel = AvAdapterModel, Cfg = AvConfigAny, R extends AvConfigResourceKey<M> = AvConfigResourceKey<M>, Check extends AvZCheck | `Error: ${string}` = AvZCheck> = {
|
|
25
|
+
export type AvScalarFieldPipeline<M extends AvAdapterModel = AvAdapterModel, Cfg = AvConfigAny, R extends AvConfigResourceKey<M> = AvConfigResourceKey<M>, Check extends AvZCheck | `Error: ${string}` = AvZCheck, Value = unknown> = {
|
|
26
26
|
/**
|
|
27
27
|
* Declarative checks on the value, built with `AvZ` and carried in the Contract;
|
|
28
28
|
* root's, then the overlay's. On a list field `AvZ.minItems` and `AvZ.maxItems`
|
|
@@ -36,12 +36,12 @@ export type AvScalarFieldPipeline<M extends AvAdapterModel = AvAdapterModel, Cfg
|
|
|
36
36
|
*/
|
|
37
37
|
readonly pipes?: readonly AvFieldPipe[];
|
|
38
38
|
/** The field's computed values. */
|
|
39
|
-
readonly compute?: AvFieldCompute<M, Cfg, R
|
|
39
|
+
readonly compute?: NoInfer<AvFieldCompute<M, Cfg, R, Value>>;
|
|
40
40
|
/** Write-only admission: run when the caller sends the field. */
|
|
41
|
-
readonly guards?: readonly AvFieldGuard<
|
|
41
|
+
readonly guards?: readonly NoInfer<AvFieldGuard<M, R>>[];
|
|
42
42
|
};
|
|
43
43
|
/** A relation field's stages: write guards only. */
|
|
44
44
|
export type AvRelationFieldPipeline<M extends AvAdapterModel = AvAdapterModel, R extends string = AvConfigResourceKey<M>> = {
|
|
45
45
|
/** Write-only admission: run for every write through the relation. */
|
|
46
|
-
readonly guards?: readonly AvFieldGuard<
|
|
46
|
+
readonly guards?: readonly NoInfer<AvFieldGuard<M, R>>[];
|
|
47
47
|
};
|
|
@@ -48,7 +48,7 @@ export type AvConfigLayer<M extends AvAdapterModel = AvAdapterModel, Cfg = AvCon
|
|
|
48
48
|
/** Request limits; see {@link AvConfigLimits}. */
|
|
49
49
|
readonly limits?: AvConfigLimits;
|
|
50
50
|
/** Guards, pipes, hooks, interceptors and filters run around every operation of the layer. */
|
|
51
|
-
readonly pipelines?: AvPipeline<
|
|
51
|
+
readonly pipelines?: NoInfer<AvPipeline<M>>;
|
|
52
52
|
/** The per-Resource tree: stages, operations, transactions and fields. */
|
|
53
53
|
readonly resources?: AvConfigResources<M, Cfg, Kind, Layer>;
|
|
54
54
|
/**
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { AvAdapterModel } from "../adapters/adapter-model.js";
|
|
2
2
|
import type { AvAdapterFieldKey } from "../adapters/adapter-resource.js";
|
|
3
3
|
import type { AvZCheck } from "../avz/avz-check.registry.js";
|
|
4
|
+
import type { AvContractLimits } from "../contracts/contract-limits.js";
|
|
5
|
+
import type { AvScalarField } from "../contracts/field.js";
|
|
6
|
+
import type { ShapedJsonSchema } from "../contracts/json-shape.js";
|
|
7
|
+
import type { AvScalarValueType } from "../contracts/scalar-value-type.js";
|
|
4
8
|
import type { AvOperationFamily } from "../operations/operation-identity.js";
|
|
5
9
|
import type { AvPreComputeStageArguments, AvStageArguments } from "../runtime/pipeline.js";
|
|
6
10
|
import type { AvConfigAny } from "./any-framework-config.js";
|
|
@@ -23,7 +27,7 @@ export type AvConfigVariant<M extends AvAdapterModel = AvAdapterModel, R extends
|
|
|
23
27
|
* pipe or a filter a create may lack the fields `compute.create` fills
|
|
24
28
|
* (write compute runs after them).
|
|
25
29
|
*/
|
|
26
|
-
readonly pipelines?: AvPipeline<
|
|
30
|
+
readonly pipelines?: NoInfer<AvPipeline<M, R, F, V, A, PreCompute>>;
|
|
27
31
|
/** Runs a plain call of this operation as a one-step transaction. */
|
|
28
32
|
readonly forceTransaction?: boolean;
|
|
29
33
|
};
|
|
@@ -43,7 +47,7 @@ export type AvConfigOperations<M extends AvAdapterModel = AvAdapterModel, R exte
|
|
|
43
47
|
/** A Resource's transaction level. */
|
|
44
48
|
export type AvConfigResourceTransactions<M extends AvAdapterModel = AvAdapterModel> = {
|
|
45
49
|
/** Stages that run once per transaction with a top-level step on the Resource. */
|
|
46
|
-
readonly pipelines?: AvTransactionPipeline<
|
|
50
|
+
readonly pipelines?: NoInfer<AvTransactionPipeline<M>>;
|
|
47
51
|
};
|
|
48
52
|
/** A field's entry at the root: its definition and its stages. */
|
|
49
53
|
export type AvConfigRootField<M extends AvAdapterModel = AvAdapterModel, Cfg = AvConfigAny, R extends AvConfigResourceKey<M> = AvConfigResourceKey<M>> = {
|
|
@@ -74,15 +78,39 @@ type FieldCheckOf<Field> = Field extends {
|
|
|
74
78
|
readonly type: infer Type;
|
|
75
79
|
readonly list: infer List;
|
|
76
80
|
} ? ([TypeChecks<Type>] extends [never] ? NoCheckRefusal<Type, List> : TypeChecks<Type>) | (false extends List ? never : AvZCheck<"list">) : AvZCheck;
|
|
77
|
-
/**
|
|
78
|
-
type
|
|
81
|
+
/** Model `M`'s scalars and enums, as the value rule reads a Contract's. */
|
|
82
|
+
type ModelValueSource<M extends AvAdapterModel> = {
|
|
83
|
+
readonly scalars: M["scalars"];
|
|
84
|
+
readonly enums: M["enums"];
|
|
85
|
+
readonly resources: Record<never, never>;
|
|
86
|
+
readonly limits: AvContractLimits;
|
|
87
|
+
readonly transactions: "none";
|
|
88
|
+
};
|
|
89
|
+
/** The value a compute stage of discovered field `F` returns, in process form. */
|
|
90
|
+
type ComputedValueOf<M extends AvAdapterModel, R extends AvConfigResourceKey<M>, F extends string> = (ShapeFactOf<M, R, F> extends infer Shape ? [Shape] extends [never] ? M["resources"][R]["fields"][F] : M["resources"][R]["fields"][F] & {
|
|
91
|
+
readonly json: ShapedJsonSchema<Shape>;
|
|
92
|
+
} : never) extends infer Field extends AvScalarField<ModelValueSource<M>> ? AvScalarValueType<ModelValueSource<M>, Field> : unknown;
|
|
93
|
+
/** A statement of the model's configuration facts (`json` or `added`), or `never`. */
|
|
94
|
+
type FactsStatementOf<M, Key extends "added" | "json"> = M extends {
|
|
95
|
+
readonly "~configFacts"?: infer Facts;
|
|
96
|
+
} ? Exclude<Facts, undefined> extends {
|
|
97
|
+
readonly [K in Key]: infer Statement;
|
|
98
|
+
} ? Statement : never : never;
|
|
99
|
+
/** The shape type the model's facts give Json field `F` of `R`, or `never`. */
|
|
100
|
+
type ShapeFactOf<M, R extends string, F extends string> = FactsStatementOf<M, "json"> extends infer Json ? R extends keyof Json ? F extends keyof Json[R] ? Json[R][F] : never : never : never;
|
|
101
|
+
/** The Contract field the model's facts state for added field `F` of `R`, or `never`. */
|
|
102
|
+
type AddedFactOf<M, R extends string, F extends string> = FactsStatementOf<M, "added"> extends infer Added ? R extends keyof Added ? F extends keyof Added[R] ? Added[R][F] : never : never : never;
|
|
103
|
+
/** The value a compute stage of added (VIRTUAL) field `F` returns, read off its fact. */
|
|
104
|
+
type AddedComputedValueOf<M extends AvAdapterModel, R extends string, F extends string> = AddedFactOf<M, R, F> extends infer Field extends AvScalarField<ModelValueSource<M>> ? [Field] extends [never] ? unknown : AvScalarValueType<ModelValueSource<M>, Field> : unknown;
|
|
105
|
+
/** A discovered scalar field's stages, its `validators` and `compute` typed by the field. */
|
|
106
|
+
type KnownScalarFieldPipeline<M extends AvAdapterModel, Cfg, R extends AvConfigResourceKey<M>, Field, Value> = Field extends {
|
|
79
107
|
readonly type: {
|
|
80
108
|
readonly scalar: "json";
|
|
81
109
|
};
|
|
82
|
-
} ? Omit<AvScalarFieldPipeline<M, Cfg, R>, "validators"> & {
|
|
110
|
+
} ? Omit<AvScalarFieldPipeline<M, Cfg, R, AvZCheck, Value>, "validators"> & {
|
|
83
111
|
/** A Json field's checks are in its shape (`definition.json`). */
|
|
84
112
|
readonly validators?: never;
|
|
85
|
-
} : AvScalarFieldPipeline<M, Cfg, R, FieldCheckOf<Field
|
|
113
|
+
} : AvScalarFieldPipeline<M, Cfg, R, FieldCheckOf<Field>, Value>;
|
|
86
114
|
/** The root entry one effective field name admits. */
|
|
87
115
|
type RootFieldConfigFor<M extends AvAdapterModel, Cfg, R extends AvConfigResourceKey<M>, F extends string> = string extends F ? AvConfigRootField<M, Cfg, R> : F extends AvAdapterFieldKey<M, R> ? M["resources"][R]["fields"][F] extends {
|
|
88
116
|
readonly kind: "relation";
|
|
@@ -91,10 +119,10 @@ type RootFieldConfigFor<M extends AvAdapterModel, Cfg, R extends AvConfigResourc
|
|
|
91
119
|
readonly pipelines?: AvRelationFieldPipeline<M, R>;
|
|
92
120
|
} : {
|
|
93
121
|
readonly definition?: AvConfigKnownFieldDefinition<M["resources"][R]["fields"][F]>;
|
|
94
|
-
readonly pipelines?: KnownScalarFieldPipeline<M, Cfg, R, M["resources"][R]["fields"][F]
|
|
122
|
+
readonly pipelines?: KnownScalarFieldPipeline<M, Cfg, R, M["resources"][R]["fields"][F], ComputedValueOf<M, R, F>>;
|
|
95
123
|
} : {
|
|
96
124
|
readonly definition: AvConfigAddedFieldDefinition<M>;
|
|
97
|
-
readonly pipelines?: AvScalarFieldPipeline<M, Cfg, R
|
|
125
|
+
readonly pipelines?: AvScalarFieldPipeline<M, Cfg, R, AvZCheck, AddedComputedValueOf<M, R, F>>;
|
|
98
126
|
};
|
|
99
127
|
/** The overlay entry one effective field name admits. */
|
|
100
128
|
type OverlayFieldConfigFor<M extends AvAdapterModel, Cfg, R extends AvConfigResourceKey<M>, F extends string> = string extends F ? AvConfigOverlayField<M, Cfg, R> : F extends AvAdapterFieldKey<M, R> ? M["resources"][R]["fields"][F] extends {
|
|
@@ -102,8 +130,11 @@ type OverlayFieldConfigFor<M extends AvAdapterModel, Cfg, R extends AvConfigReso
|
|
|
102
130
|
} ? {
|
|
103
131
|
readonly pipelines?: AvRelationFieldPipeline<M, R>;
|
|
104
132
|
} : {
|
|
105
|
-
readonly pipelines?: KnownScalarFieldPipeline<M, Cfg, R, M["resources"][R]["fields"][F]
|
|
106
|
-
} :
|
|
133
|
+
readonly pipelines?: KnownScalarFieldPipeline<M, Cfg, R, M["resources"][R]["fields"][F], ComputedValueOf<M, R, F>>;
|
|
134
|
+
} : {
|
|
135
|
+
/** The field's stages, added to the root's. */
|
|
136
|
+
readonly pipelines?: AvScalarFieldPipeline<M, Cfg, R, AvZCheck, AddedComputedValueOf<M, R, F>>;
|
|
137
|
+
};
|
|
107
138
|
/** A Resource's fields at one layer, keyed by its effective field names. */
|
|
108
139
|
export type AvConfigResourceFields<in out M extends AvAdapterModel = AvAdapterModel, in out Cfg = AvConfigAny, in out R extends AvConfigResourceKey<M> = AvConfigResourceKey<M>, Kind extends AvConfigLayerKind = "root"> = {
|
|
109
140
|
readonly [F in AvEffectiveFieldKey<M, Cfg, R>]?: Kind extends "root" ? RootFieldConfigFor<M, Cfg, R, F> : OverlayFieldConfigFor<M, Cfg, R, F>;
|
|
@@ -111,7 +142,7 @@ export type AvConfigResourceFields<in out M extends AvAdapterModel = AvAdapterMo
|
|
|
111
142
|
/** One Resource's tree: its stages, operations, transactions and fields. */
|
|
112
143
|
export type AvConfigResource<in out M extends AvAdapterModel = AvAdapterModel, in out Cfg = AvConfigAny, in out R extends AvConfigResourceKey<M> = AvConfigResourceKey<M>, Kind extends AvConfigLayerKind = "root", Layer extends AvConfigLayerName = LayerOfKind<Kind>> = {
|
|
113
144
|
/** Stages that run for every operation of this Resource. */
|
|
114
|
-
readonly pipelines?: AvPipeline<
|
|
145
|
+
readonly pipelines?: NoInfer<AvPipeline<M, R>>;
|
|
115
146
|
/** Per family, and per variant under its family. */
|
|
116
147
|
readonly operations?: AvConfigOperations<M, R, Layer>;
|
|
117
148
|
/** Stages that run once per transaction with a top-level step on this Resource. */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AvAdapterModel } from "../adapters/adapter-model.js";
|
|
1
2
|
import type { AvComputeCreate, AvComputeRead, AvComputeUpdate } from "../runtime/field-compute.js";
|
|
2
3
|
import type { AvScalarFieldPipeline } from "./field-pipeline-config.js";
|
|
3
4
|
import type { AvPipeline, AvTransactionPipeline } from "./pipeline-config.js";
|
|
@@ -11,14 +12,16 @@ type ErasedStageGroup<G, Wide> = G extends object ? {
|
|
|
11
12
|
} : G;
|
|
12
13
|
/** A field's `compute`, by phase; a read stage's `dependsOn` kept. */
|
|
13
14
|
type ErasedCompute<C> = C extends object ? {
|
|
14
|
-
[K in keyof C]: K extends "create" ? AvComputeCreate : K extends "update" ? AvComputeUpdate : K extends "read" ? C[K] extends {
|
|
15
|
+
[K in keyof C]: K extends "create" ? AvComputeCreate<AvAdapterModel, ReturnedValue<C[K]>> : K extends "update" ? AvComputeUpdate<AvAdapterModel, ReturnedValue<C[K]>> : K extends "read" ? C[K] extends {
|
|
15
16
|
readonly compute: unknown;
|
|
16
17
|
} ? {
|
|
17
|
-
[D in keyof C[K]]: D extends "compute" ? ComputeReadStage : C[K][D];
|
|
18
|
-
} : ComputeReadStage : C[K];
|
|
18
|
+
[D in keyof C[K]]: D extends "compute" ? ComputeReadStage<ReturnedValue<C[K][D]>> : C[K][D];
|
|
19
|
+
} : ComputeReadStage<ReturnedValue<C[K]>> : C[K];
|
|
19
20
|
} : C;
|
|
21
|
+
/** The value an authored compute stage returns, `undefined` and a promise taken off. */
|
|
22
|
+
type ReturnedValue<Stage> = Stage extends (context: never) => infer Returned ? Exclude<Awaited<Returned>, undefined> : unknown;
|
|
20
23
|
/** A read compute stage's function form, for any model. */
|
|
21
|
-
type ComputeReadStage = Parameters<Extract<AvComputeRead, (context: never) => unknown>>[0] extends infer Context ? (context: Context) =>
|
|
24
|
+
type ComputeReadStage<Value = unknown> = Parameters<Extract<AvComputeRead, (context: never) => unknown>>[0] extends infer Context ? (context: Context) => Value | void | Promise<Value | void> : never;
|
|
22
25
|
/** A variant's level: its `pipelines` erased, `forceTransaction` kept. */
|
|
23
26
|
type ErasedVariant<V> = V extends object ? {
|
|
24
27
|
[K in keyof V]: K extends "pipelines" ? ErasedPipelines<V[K], AvPipeline> : V[K];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type Schema = Readonly<Record<string, unknown>>;
|
|
2
|
+
/**
|
|
3
|
+
* The TypeScript type a Json Shape schema describes, as source text.
|
|
4
|
+
*
|
|
5
|
+
* @throws Error on a schema outside the accepted forms — unreachable for a
|
|
6
|
+
* ClientContract that passed the structure check.
|
|
7
|
+
*/
|
|
8
|
+
export declare function jsonSchemaType(schema: Schema): string;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const SCALAR_TYPES = {
|
|
2
|
+
string: "string",
|
|
3
|
+
number: "number",
|
|
4
|
+
integer: "number",
|
|
5
|
+
boolean: "boolean",
|
|
6
|
+
null: "null",
|
|
7
|
+
};
|
|
8
|
+
const plain = (text) => ({ text, union: false });
|
|
9
|
+
function unionOf(members) {
|
|
10
|
+
return members.length === 1
|
|
11
|
+
? members[0]
|
|
12
|
+
: { text: members.map((member) => member.text).join(" | "), union: true };
|
|
13
|
+
}
|
|
14
|
+
function operand(type) {
|
|
15
|
+
return type.union ? `(${type.text})` : type.text;
|
|
16
|
+
}
|
|
17
|
+
function emitted(schema) {
|
|
18
|
+
if (Object.hasOwn(schema, "const")) {
|
|
19
|
+
return plain(JSON.stringify(schema.const));
|
|
20
|
+
}
|
|
21
|
+
if (Array.isArray(schema.enum)) {
|
|
22
|
+
return unionOf(schema.enum.map((value) => plain(JSON.stringify(value))));
|
|
23
|
+
}
|
|
24
|
+
for (const keyword of ["anyOf", "oneOf"]) {
|
|
25
|
+
const options = schema[keyword];
|
|
26
|
+
if (Array.isArray(options)) {
|
|
27
|
+
return unionOf(options.map((option) => emitted(option)));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const type = schema.type;
|
|
31
|
+
if (typeof type === "string" && Object.hasOwn(SCALAR_TYPES, type)) {
|
|
32
|
+
return plain(SCALAR_TYPES[type]);
|
|
33
|
+
}
|
|
34
|
+
if (type === "array") {
|
|
35
|
+
if (Array.isArray(schema.prefixItems)) {
|
|
36
|
+
return plain(`[${schema.prefixItems.map((item) => emitted(item).text).join(", ")}]`);
|
|
37
|
+
}
|
|
38
|
+
return plain(`${operand(emitted(schema.items))}[]`);
|
|
39
|
+
}
|
|
40
|
+
if (type === "object") {
|
|
41
|
+
const properties = schema.properties;
|
|
42
|
+
if (properties === undefined) {
|
|
43
|
+
return plain(`{ [key: string]: ${emitted(schema.additionalProperties).text} }`);
|
|
44
|
+
}
|
|
45
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
46
|
+
const members = Object.keys(properties).map((key) => {
|
|
47
|
+
const member = emitted(properties[key]).text;
|
|
48
|
+
return required.has(key)
|
|
49
|
+
? `${JSON.stringify(key)}: ${member}`
|
|
50
|
+
: `${JSON.stringify(key)}?: ${member} | undefined`;
|
|
51
|
+
});
|
|
52
|
+
return plain(members.length === 0 ? "{}" : `{ ${members.join("; ")} }`);
|
|
53
|
+
}
|
|
54
|
+
throw new Error(`a Json Shape schema outside the accepted forms reached the emitter: ${JSON.stringify(schema)}`);
|
|
55
|
+
}
|
|
56
|
+
export function jsonSchemaType(schema) {
|
|
57
|
+
return emitted(schema).text;
|
|
58
|
+
}
|
|
@@ -57,7 +57,13 @@ type LayeredModelField<Field, FieldConfig> = Field extends {
|
|
|
57
57
|
readonly kind: "scalar";
|
|
58
58
|
} ? [FieldConfig] extends [never] ? Field extends {
|
|
59
59
|
readonly capabilities: infer Capabilities;
|
|
60
|
-
} ? "select" extends keyof Capabilities ? Field : ConfiguredModelField<Field, FieldConfig> : ConfiguredModelField<Field, FieldConfig> :
|
|
60
|
+
} ? "select" extends keyof Capabilities ? Field : ConfiguredModelField<Field, FieldConfig> : ConfiguredModelField<Field, FieldConfig> : FieldConfig extends {
|
|
61
|
+
readonly capabilities: unknown;
|
|
62
|
+
} ? ConfiguredModelField<Field, FieldConfig> : Field extends {
|
|
63
|
+
readonly capabilities: {
|
|
64
|
+
readonly select: unknown;
|
|
65
|
+
};
|
|
66
|
+
} ? Field & ConfiguredJsonShape<FieldConfig> : ConfiguredModelField<Field, FieldConfig> : Field;
|
|
61
67
|
/**
|
|
62
68
|
* A discovered scalar with its configured capabilities applied, member by member,
|
|
63
69
|
* and its configured Json Shape, whose type its value takes.
|
|
@@ -70,9 +76,22 @@ type ConfiguredJsonShape<FieldConfig> = [FieldConfig] extends [never] ? unknown
|
|
|
70
76
|
readonly json: JsonShape<infer Value>;
|
|
71
77
|
} ? {
|
|
72
78
|
readonly json: ShapedJsonSchema<Value>;
|
|
79
|
+
} : FieldConfig extends {
|
|
80
|
+
readonly json: {
|
|
81
|
+
readonly "~jsonType": infer Value;
|
|
82
|
+
};
|
|
83
|
+
} ? {
|
|
84
|
+
readonly json: ShapedJsonSchema<Value>;
|
|
73
85
|
} : unknown;
|
|
74
|
-
/**
|
|
75
|
-
|
|
86
|
+
/**
|
|
87
|
+
* A scalar the configuration adds, exactly as the configuration states it; a
|
|
88
|
+
* fact the build states (`"~configFacts"`) is the Contract field already.
|
|
89
|
+
*/
|
|
90
|
+
type AddedField<FieldConfig> = FieldConfig extends {
|
|
91
|
+
readonly kind: "scalar";
|
|
92
|
+
} ? FieldConfig : DefinedField<FieldConfig>;
|
|
93
|
+
/** A scalar a configuration's `definition` adds. */
|
|
94
|
+
type DefinedField<FieldConfig> = {
|
|
76
95
|
readonly kind: "scalar";
|
|
77
96
|
readonly type: FieldConfig extends {
|
|
78
97
|
readonly type: infer Type;
|
|
@@ -246,10 +265,9 @@ export type RequirementsLessComputed<Requirements, Computed> = [
|
|
|
246
265
|
/** One member of a statement less `Computed`: its names, or its groups. */
|
|
247
266
|
type LessComputed<Member, List, Computed> = Member extends "required" ? NamesLessComputed<List, Computed> : GroupsLessComputed<List, Computed>;
|
|
248
267
|
/** A name tuple less `Computed`, still a tuple. */
|
|
249
|
-
type NamesLessComputed<Names, Computed> = Names extends readonly [
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
] ? Head extends Computed ? NamesLessComputed<Rest, Computed> : readonly [Head, ...NamesLessComputed<Rest, Computed>] : readonly [];
|
|
268
|
+
type NamesLessComputed<Names, Computed> = Names extends readonly unknown[] ? number extends Names["length"] ? NamesListLessComputed<Exclude<Names[number], Computed>> : Names extends readonly [infer Head, ...infer Rest] ? Head extends Computed ? NamesLessComputed<Rest, Computed> : readonly [Head, ...NamesLessComputed<Rest, Computed>] : readonly [] : readonly [];
|
|
269
|
+
/** The names left, as a statement's non-empty list, or the empty tuple. */
|
|
270
|
+
type NamesListLessComputed<Names> = [Names] extends [never] ? readonly [] : RequiredNames<Names>;
|
|
253
271
|
/** A group tuple less `Computed` reference fields; a group left with none is dropped. */
|
|
254
272
|
type GroupsLessComputed<Groups, Computed> = Groups extends readonly [
|
|
255
273
|
infer Group,
|
|
@@ -508,4 +526,52 @@ export type AvContractApplicationView<M extends AvAdapterModel, Cfg = AvConfigAn
|
|
|
508
526
|
export type ClientContractView<M extends AvAdapterModel, Cfg = AvConfigAny> = [
|
|
509
527
|
Cfg
|
|
510
528
|
] extends [unknown] ? [ManagedBlock<RootBlock<Cfg, "resources">>] extends [never] ? AvContractView<M, ViewBlock<ResourcesBlock<Cfg>>, ExposureStyleOf<ExposedOf<Cfg>>, ExemptionsOf<Cfg>, ExposureStyleOf<ExposedOf<OverlayOf<Cfg, "client">>>, ExemptionsOf<OverlayOf<Cfg, "client">>, ViewBlock<OverlayResourcesBlock<Cfg, "client">>, GovernedBlock<Cfg>> : AvManagedContractView<M, ViewBlock<ResourcesBlock<Cfg>>, ExposureStyleOf<ExposedOf<Cfg>>, ExemptionsOf<Cfg>, ExposureStyleOf<ExposedOf<OverlayOf<Cfg, "client">>>, ExemptionsOf<OverlayOf<Cfg, "client">>, ViewBlock<OverlayResourcesBlock<Cfg, "client">>, GovernedBlock<Cfg>, ManagedBlock<RootBlock<Cfg, "resources">>, ManagedBlock<RootBlock<Cfg, "resources">>> : never;
|
|
529
|
+
/**
|
|
530
|
+
* The Contract view a pipeline stage's `ctx` and typed `$args` read: the model's
|
|
531
|
+
* Application Contract view with the configuration's facts the model's type
|
|
532
|
+
* carries applied (each shaped Json field typed by its shape, each added field
|
|
533
|
+
* present); the view with no configuration when it carries none.
|
|
534
|
+
*/
|
|
535
|
+
export type AvModelFactsView<M extends AvAdapterModel> = [M] extends [unknown] ? AvContractView<M, FactsBlockOf<M>, "omit", never, "omit", never, never> : never;
|
|
536
|
+
/** The facts `M`'s type carries, as a view's root `fields` block; `never` when it carries none. */
|
|
537
|
+
type FactsBlockOf<M> = M extends {
|
|
538
|
+
readonly "~configFacts"?: infer Facts;
|
|
539
|
+
} ? Exclude<Facts, undefined> extends {
|
|
540
|
+
readonly added: infer Added;
|
|
541
|
+
readonly json: infer Json;
|
|
542
|
+
} ? string extends keyof Added | keyof Json ? never : [keyof Added | keyof Json] extends [never] ? never : {
|
|
543
|
+
readonly [R in keyof Json | keyof Added]: {
|
|
544
|
+
readonly fields: {
|
|
545
|
+
readonly [F in FactKey<Json, R> | FactKey<Added, R>]: {
|
|
546
|
+
readonly definition: F extends FactKey<Json, R> ? {
|
|
547
|
+
readonly json: {
|
|
548
|
+
readonly "~jsonType": Json[R & keyof Json][F];
|
|
549
|
+
};
|
|
550
|
+
} : Added[R & keyof Added][F & keyof Added[R & keyof Added]];
|
|
551
|
+
};
|
|
552
|
+
};
|
|
553
|
+
};
|
|
554
|
+
} : never : never;
|
|
555
|
+
/** The fields Resource `R` has facts for in one statement (`json` or `added`). */
|
|
556
|
+
type FactKey<Statement, R> = R extends keyof Statement ? keyof Statement[R] : never;
|
|
557
|
+
/**
|
|
558
|
+
* The Contract view a stage's ctx calls and compute's reads are typed by: the
|
|
559
|
+
* model's view with its configuration facts applied ({@link AvModelFactsView}),
|
|
560
|
+
* its create requirements less `Lift` (`"<Resource>.<field>"`).
|
|
561
|
+
*/
|
|
562
|
+
export type ContextContractView<M extends AvAdapterModel, Lift extends string> = [Lift] extends [never] ? AvModelFactsView<M> : AvContractView<M, FactsBlockOf<M>, "omit", never, "omit", never, {
|
|
563
|
+
readonly [R in LiftedResourceOf<Lift>]: {
|
|
564
|
+
readonly fields: {
|
|
565
|
+
readonly [F in LiftedFieldOf<Lift, R>]: {
|
|
566
|
+
readonly pipelines: {
|
|
567
|
+
readonly compute: {
|
|
568
|
+
readonly create: true;
|
|
569
|
+
};
|
|
570
|
+
};
|
|
571
|
+
};
|
|
572
|
+
};
|
|
573
|
+
};
|
|
574
|
+
}>;
|
|
575
|
+
/** The Resources `Lift` names a field of. */
|
|
576
|
+
type LiftedResourceOf<Lift extends string> = Lift extends `${infer R}.${string}` ? R : never;
|
|
511
577
|
export {};
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { compileContracts, } from "../compiler/contract.compiler.js";
|
|
2
2
|
import { validateManagedDeletes } from "../compiler/managed-elsewhere.validator.js";
|
|
3
|
-
import { computedFieldsOf,
|
|
3
|
+
import { computedFieldsOf, staleComputedFields, warnOnStaleGeneratedTypes, } from "../config/computed-fields.js";
|
|
4
4
|
import { resolveFrameworkConfig, } from "../config/config.resolver.js";
|
|
5
5
|
import { validateFrameworkConfig } from "../config/config.validator.js";
|
|
6
|
+
import { staleConfigFacts } from "../config/config-facts.js";
|
|
6
7
|
import { validateForcedOperations } from "../config/forced-operation.validator.js";
|
|
7
8
|
import { AVENTARA_PROTOCOL_VERSION } from "../contracts/contract-protocol.js";
|
|
8
9
|
import { recordAccessProvider } from "../policy/access-provider.registry.js";
|
|
@@ -46,13 +47,22 @@ export async function createFramework(config) {
|
|
|
46
47
|
]);
|
|
47
48
|
}
|
|
48
49
|
const resolvedConfig = resolveFrameworkConfig(config);
|
|
49
|
-
|
|
50
|
+
const staleComputed = staleComputedFields(config.adapter.computedFields, computedFieldsOf({
|
|
50
51
|
trees: resolvedConfig.trees,
|
|
51
52
|
agentExtends: resolvedConfig.agent.parent,
|
|
52
53
|
}));
|
|
53
54
|
const compilation = (config.prebuilt === undefined
|
|
54
55
|
? undefined
|
|
55
56
|
: await prebuiltCompilation(config.prebuilt, model, config, resolvedConfig)) ?? (await compileContracts(model, resolvedConfig));
|
|
57
|
+
const staleParagraphs = [staleComputed];
|
|
58
|
+
try {
|
|
59
|
+
if (compilation.ok) {
|
|
60
|
+
staleParagraphs.push(staleConfigFacts(config.adapter.configFacts, compilation.contracts.core));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
warnOnStaleGeneratedTypes(staleParagraphs);
|
|
65
|
+
}
|
|
56
66
|
if (!compilation.ok) {
|
|
57
67
|
throw new AvConstructionError("contract-compilation", compilation.diagnostics);
|
|
58
68
|
}
|
|
@@ -13,7 +13,7 @@ import type { AvState } from "../runtime/request-state.js";
|
|
|
13
13
|
import type { AvSqlStatementBuilder } from "../transactions/sql-statement.js";
|
|
14
14
|
import type { AvTransactionResponse } from "../transactions/transaction-response.js";
|
|
15
15
|
import type { AccessProviderOf, ServerClientAccess } from "./access-facade.js";
|
|
16
|
-
import type { AvContractApplicationView, ClientContractView,
|
|
16
|
+
import type { AvContractApplicationView, ClientContractView, ContextContractView } from "./contract-view.js";
|
|
17
17
|
/**
|
|
18
18
|
* A model whose Resource names are not literal — the abstract `AvAdapterModel` behind
|
|
19
19
|
* the default `AvFramework` — has no Contract to type a call against, so a surface over
|
|
@@ -232,28 +232,26 @@ export type ServerClientTransactionBuilders<M extends AvAdapterModel, Cfg> = Ser
|
|
|
232
232
|
* field the root's or the application's `compute.create` fills, when the model's
|
|
233
233
|
* type states them.
|
|
234
234
|
*/
|
|
235
|
-
export type AvContextOperations<M extends AvAdapterModel> = ContextFacade<
|
|
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>;
|
|
235
|
+
export type AvContextOperations<M extends AvAdapterModel> = ContextFacade<ContextContractView<M, AvContextComputeLift<M>>>;
|
|
238
236
|
type ContextFacade<C> = C extends AvContract ? string extends AvResourceKey<C> ? unknown : {
|
|
239
237
|
readonly [R in AvResourceKey<C>]: {
|
|
240
|
-
readonly [F in
|
|
241
|
-
readonly [V in keyof AvOperationGrammar<C>[R][F]]: AvOperationCall<C, R, AvOperationGrammar<C>[R][F][V] & AvOperationIdentity, AvApplicationScalarForms, AvApplicationScalarForms, never>;
|
|
242
|
-
};
|
|
238
|
+
readonly [F in FamilyKey<C, R>]: AvContextFamilyFacade<C, R, F>;
|
|
243
239
|
};
|
|
244
240
|
} : never;
|
|
241
|
+
/** `ctx.<Resource>.<family>` in a pipeline stage: one direct call per variant, application forms. */
|
|
242
|
+
export type AvContextFamilyFacade<in out C extends AvContract, in out R extends AvResourceKey<C>, in out F extends FamilyKey<C, R>> = {
|
|
243
|
+
readonly [V in keyof AvOperationGrammar<C>[R][F]]: AvOperationCall<C, R, AvOperationGrammar<C>[R][F][V] & AvOperationIdentity, AvApplicationScalarForms, AvApplicationScalarForms, never>;
|
|
244
|
+
};
|
|
245
245
|
/**
|
|
246
246
|
* The Resources of compute's read-only ctx: `ctx.<Resource>.find.<variant>(args)`
|
|
247
247
|
* only (`find.count` included).
|
|
248
248
|
*/
|
|
249
|
-
export type AvComputeReads<M extends AvAdapterModel> = ComputeReadFacade<
|
|
250
|
-
type ComputeReadFacade<
|
|
251
|
-
readonly [R in
|
|
252
|
-
readonly [F in Extract<keyof
|
|
253
|
-
readonly [V in keyof AvOperationGrammar<C>[R][F]]: AvOperationCall<C, R, AvOperationGrammar<C>[R][F][V] & AvOperationIdentity, AvApplicationScalarForms, AvApplicationScalarForms, never>;
|
|
254
|
-
};
|
|
249
|
+
export type AvComputeReads<M extends AvAdapterModel> = ComputeReadFacade<AvContextOperations<M>>;
|
|
250
|
+
type ComputeReadFacade<Operations> = {
|
|
251
|
+
readonly [R in keyof Operations]: {
|
|
252
|
+
readonly [F in Extract<keyof Operations[R], "find">]: Operations[R][F];
|
|
255
253
|
};
|
|
256
|
-
}
|
|
254
|
+
};
|
|
257
255
|
/** Builds the request one façade call hands to `execute`, under its request id and state. */
|
|
258
256
|
export type FacadeRequest = (resource: string, identity: AvOperationIdentity, args: unknown, requestId: string, state: AvState | undefined) => AvOperationRequest;
|
|
259
257
|
type FacadeExecutor = (request: AvOperationRequest, caller: PolicyCaller | undefined) => Promise<AvOperationResponse>;
|
package/dist/index.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export { createFramework } from "./framework/framework.factory.js";
|
|
|
34
34
|
export type { AvFramework } from "./framework/framework.js";
|
|
35
35
|
export type { AvConstructionDiagnostic, AvConstructionStage, } from "./framework/framework-construction.error.js";
|
|
36
36
|
export { AvConstructionError } from "./framework/framework-construction.error.js";
|
|
37
|
-
export type { AvApplicationFacade, AvApplicationFamilyFacade, AvApplicationPlainCall, AvApplicationProjectingCall, AvApplicationResourceFacade, AvAppTxFacade, AvAppTxFamilyFacade, AvAppTxPlainCall, AvAppTxProjectingCall, AvAppTxResourceFacade, AvClientFacade, AvClientFamilyFacade, AvClientPlainCall, AvClientProjectingCall, AvClientResourceFacade, AvClientTxFacade, AvClientTxFamilyFacade, AvClientTxPlainCall, AvClientTxProjectingCall, AvClientTxResourceFacade, AvServerClientFacade, AvServerClientFamilyFacade, AvServerClientPlainCall, AvServerClientProjectingCall, AvServerClientResourceFacade, AvServerClientTxFacade, AvServerClientTxFamilyFacade, AvServerClientTxPlainCall, AvServerClientTxProjectingCall, AvServerClientTxResourceFacade, } from "./framework/operation-facade.js";
|
|
37
|
+
export type { AvApplicationFacade, AvApplicationFamilyFacade, AvApplicationPlainCall, AvApplicationProjectingCall, AvApplicationResourceFacade, AvAppTxFacade, AvAppTxFamilyFacade, AvAppTxPlainCall, AvAppTxProjectingCall, AvAppTxResourceFacade, AvClientFacade, AvClientFamilyFacade, AvClientPlainCall, AvClientProjectingCall, AvClientResourceFacade, AvClientTxFacade, AvClientTxFamilyFacade, AvClientTxPlainCall, AvClientTxProjectingCall, AvClientTxResourceFacade, AvContextFamilyFacade, AvServerClientFacade, AvServerClientFamilyFacade, AvServerClientPlainCall, AvServerClientProjectingCall, AvServerClientResourceFacade, AvServerClientTxFacade, AvServerClientTxFamilyFacade, AvServerClientTxPlainCall, AvServerClientTxProjectingCall, AvServerClientTxResourceFacade, } from "./framework/operation-facade.js";
|
|
38
38
|
export { avLazy } from "./framework/operation-set.runner.js";
|
|
39
39
|
export type { AvCreateDataInput, AvCursorInput, AvFieldKey, AvIncludeInput, AvNoAdvertisedMember, AvNullsOrder, AvOperationArgumentMap, AvOperationArgumentsFor, AvOrderByEntry, AvOrderByInput, AvProjectionEntry, AvRelationProjectionInput, AvResourceKey, AvScalarFilterOperands, AvScalarOrderInput, AvScalarWhereInput, AvSelectInput, AvSortDirection, AvUniqueWhereInput, AvUpdateDataInput, AvWhereInput, } from "./operations/operation-arguments.js";
|
|
40
40
|
export type { AvDeferredOperationCall, AvOperationCall, AvOperationGrammar, AvOperationSlot, } from "./operations/operation-call.js";
|
package/dist/internal.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export type { AvAdapterSqlStatement, AvAdapterSqlValue, } from "./adapters/adapt
|
|
|
15
15
|
export type { AvAdapterTransactionExecutor } from "./adapters/adapter-transaction-executor.interface.js";
|
|
16
16
|
export { compileContracts } from "./compiler/contract.compiler.js";
|
|
17
17
|
export { resolveFrameworkConfig } from "./config/config.resolver.js";
|
|
18
|
+
export { configFactsOf } from "./config/config-facts.js";
|
|
18
19
|
export { canonicalJson } from "./contracts/canonical-json.writer.js";
|
|
19
20
|
export { PROJECTION_SCOPES, WRITE_SCOPES } from "./contracts/capability.js";
|
|
20
21
|
export { validateClientContractStructure } from "./contracts/client-contract-structure.validator.js";
|
|
@@ -25,6 +26,7 @@ export { DEFAULT_CONTRACT_LIMITS } from "./contracts/contract-limits.js";
|
|
|
25
26
|
export { AVENTARA_PROTOCOL_VERSION } from "./contracts/contract-protocol.js";
|
|
26
27
|
export { advertisedDirectives, advertisedReducers } from "./contracts/field.js";
|
|
27
28
|
export { projectJsonValue } from "./contracts/json-schema.projector.js";
|
|
29
|
+
export { jsonSchemaType } from "./contracts/json-schema-type.emitter.js";
|
|
28
30
|
export { LIFECYCLE_TRAITS } from "./contracts/lifecycle-trait.js";
|
|
29
31
|
export { isOperationVariantAvailable, projectionScopeOf, writeScopeOf, } from "./contracts/resource-operations.js";
|
|
30
32
|
export { BUILT_IN_SCALARS } from "./contracts/scalar.js";
|
package/dist/internal.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { compileContracts } from "./compiler/contract.compiler.js";
|
|
2
2
|
export { resolveFrameworkConfig } from "./config/config.resolver.js";
|
|
3
|
+
export { configFactsOf } from "./config/config-facts.js";
|
|
3
4
|
export { canonicalJson } from "./contracts/canonical-json.writer.js";
|
|
4
5
|
export { PROJECTION_SCOPES, WRITE_SCOPES } from "./contracts/capability.js";
|
|
5
6
|
export { validateClientContractStructure } from "./contracts/client-contract-structure.validator.js";
|
|
@@ -9,6 +10,7 @@ export { DEFAULT_CONTRACT_LIMITS } from "./contracts/contract-limits.js";
|
|
|
9
10
|
export { AVENTARA_PROTOCOL_VERSION } from "./contracts/contract-protocol.js";
|
|
10
11
|
export { advertisedDirectives, advertisedReducers } from "./contracts/field.js";
|
|
11
12
|
export { projectJsonValue } from "./contracts/json-schema.projector.js";
|
|
13
|
+
export { jsonSchemaType } from "./contracts/json-schema-type.emitter.js";
|
|
12
14
|
export { LIFECYCLE_TRAITS } from "./contracts/lifecycle-trait.js";
|
|
13
15
|
export { isOperationVariantAvailable, projectionScopeOf, writeScopeOf, } from "./contracts/resource-operations.js";
|
|
14
16
|
export { BUILT_IN_SCALARS } from "./contracts/scalar.js";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AvProjectionScope, AvRelationMutationDirective, AvScalarFilterOperator, AvScalarMutationDirective, AvWriteScope, ScalarFilterMode, ToOneRelinkDirective, ToOneRewriteDirective } from "../contracts/capability.js";
|
|
2
2
|
import type { AvContract } from "../contracts/contract.js";
|
|
3
3
|
import type { AvProjectionScopes, AvScalarField } from "../contracts/field.js";
|
|
4
|
+
import type { JsonFieldType } from "../contracts/json-shape.js";
|
|
4
5
|
import type { AvScalarValueType, FieldAtom, ScalarForms } from "../contracts/scalar-value-type.js";
|
|
5
6
|
import type { AvJsonValue } from "../runtime/json-value.js";
|
|
6
7
|
import type { AvTransactionReference, TransactionSlotShape } from "../transactions/transaction-reference.js";
|
|
@@ -67,10 +68,28 @@ type KeysWithCapability<C extends AvContract, K extends AvResourceKey<C>, Keys e
|
|
|
67
68
|
* values. Enum values are their member union under every map.
|
|
68
69
|
*/
|
|
69
70
|
type UnstatedForms = ScalarForms;
|
|
70
|
-
/** Field `F`'s value under `Forms`,
|
|
71
|
-
type ScalarValueAt<C extends AvContract, K extends AvResourceKey<C>, F extends AvFieldKey<C, K>, Forms extends ScalarForms> = ScalarFieldAt<C, K, F> extends infer Field extends AvScalarField<C> ? AvScalarValueType<C, Field, Forms
|
|
71
|
+
/** Field `F`'s value under `Forms`, as in results; a shaped Json value read-only. */
|
|
72
|
+
type ScalarValueAt<C extends AvContract, K extends AvResourceKey<C>, F extends AvFieldKey<C, K>, Forms extends ScalarForms> = ScalarFieldAt<C, K, F> extends infer Field extends AvScalarField<C> ? ReadonlyShaped<Field, AvScalarValueType<C, Field, Forms>> : never;
|
|
72
73
|
/** One entry of field `F`'s value — the whole value when `F` is not a list. */
|
|
73
|
-
type ScalarAtomAt<C extends AvContract, K extends AvResourceKey<C>, F extends AvFieldKey<C, K>, Forms extends ScalarForms> = FieldAtom<C, ScalarFieldAt<C, K, F>, Forms
|
|
74
|
+
type ScalarAtomAt<C extends AvContract, K extends AvResourceKey<C>, F extends AvFieldKey<C, K>, Forms extends ScalarForms> = ReadonlyShaped<ScalarFieldAt<C, K, F>, FieldAtom<C, ScalarFieldAt<C, K, F>, Forms>>;
|
|
75
|
+
/** `Value` read-only, deep, when `Field` is a shaped Json field; itself otherwise. */
|
|
76
|
+
type ReadonlyShaped<Field, Value> = Field extends {
|
|
77
|
+
readonly json: JsonFieldType<unknown>;
|
|
78
|
+
} ? ReadonlyJson<Value> : Value;
|
|
79
|
+
/** A JSON value, every member and every array read-only, deep. */
|
|
80
|
+
type ReadonlyJson<Value> = Value extends object ? [OptionalKeyOf<Value>] extends [never] ? {
|
|
81
|
+
readonly [K in keyof Value]: ReadonlyJson<Value[K]>;
|
|
82
|
+
} : {
|
|
83
|
+
readonly [K in Exclude<keyof Value, OptionalKeyOf<Value>>]: ReadonlyJson<Value[K]>;
|
|
84
|
+
} & {
|
|
85
|
+
readonly [K in OptionalKeyOf<Value>]?: ReadonlyJson<Value[K]>;
|
|
86
|
+
} extends infer Members ? {
|
|
87
|
+
[K in keyof Members]: Members[K];
|
|
88
|
+
} : never : Value;
|
|
89
|
+
/** An object's optional members; an array has none, nor a record's index signature. */
|
|
90
|
+
type OptionalKeyOf<Value> = Value extends readonly unknown[] ? never : {
|
|
91
|
+
[K in keyof Value]-?: string extends K ? never : number extends K ? never : Record<never, never> extends Pick<Value, K> ? K : never;
|
|
92
|
+
}[keyof Value];
|
|
74
93
|
/**
|
|
75
94
|
* A `$ref` to a source whose value fits slot shape `Shape` of field `F`, typed in
|
|
76
95
|
* `ReferenceForms` — the forms a source's final data is read in. `never` when
|
|
@@ -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.15";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CORE_VERSION = "1.0.0-rc.
|
|
1
|
+
export const CORE_VERSION = "1.0.0-rc.15";
|
|
@@ -26,7 +26,10 @@ const literal = (value) => JSON.stringify(value);
|
|
|
26
26
|
const key = (name) => name === "__proto__" ? `[${literal(name)}]` : literal(name);
|
|
27
27
|
export function emitPrebuiltContracts(prebuilt) {
|
|
28
28
|
const resources = Object.entries(prebuilt.fingerprints.resources).map(([name, fingerprint]) => `\t\t\t${key(name)}: ${literal(fingerprint)},\n`);
|
|
29
|
-
return `//
|
|
29
|
+
return `// biome-ignore-all format: generated output
|
|
30
|
+
// biome-ignore-all lint: generated output
|
|
31
|
+
// biome-ignore-all assist/source/organizeImports: generated output
|
|
32
|
+
// Generated by @aventara/core from the configuration and the adapter's model. Do not edit: regenerate it.
|
|
30
33
|
import type { AvPrebuiltContracts } from "@aventara/core";
|
|
31
34
|
|
|
32
35
|
export const aventaraContracts: AvPrebuiltContracts = {
|
|
@@ -45,6 +48,5 @@ ${resources.join("")}\t\t},
|
|
|
45
48
|
\t\tclient: ${literal(prebuilt.contracts.client)},
|
|
46
49
|
\t\tagent: ${literal(prebuilt.contracts.agent)},
|
|
47
50
|
\t},
|
|
48
|
-
}
|
|
49
|
-
`;
|
|
51
|
+
};`;
|
|
50
52
|
}
|
|
@@ -12,9 +12,10 @@ const HHMM = "(?:[01]\\d|2[0-3]):[0-5]\\d";
|
|
|
12
12
|
const IPV6_ALPHABET = /^[0-9a-fA-F:.]+$/;
|
|
13
13
|
const CREDIT_CARD = /^\d(?:[ -]?\d){11,18}$/;
|
|
14
14
|
const IBAN = /^[A-Z]{2}(?!00|01|99)\d{2}[A-Z0-9]{11,30}$/;
|
|
15
|
+
const EMOJI_SOURCE = "^(?=[\\s\\S]*[\\p{Extended_Pictographic}\\p{Regional_Indicator}\\u20E3])[\\p{Extended_Pictographic}\\p{Emoji_Component}]+$";
|
|
15
16
|
const FORMAT_PATTERNS = {
|
|
16
17
|
email: /^(?:[A-Za-z0-9_'+-]+\.)*[A-Za-z0-9_'+-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9-]*\.)+[A-Za-z]{2,}$/,
|
|
17
|
-
emoji: new RegExp(
|
|
18
|
+
emoji: new RegExp(EMOJI_SOURCE, "u"),
|
|
18
19
|
uuid: /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/,
|
|
19
20
|
guid: /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/,
|
|
20
21
|
nanoid: /^[a-zA-Z0-9_-]{21}$/,
|
|
@@ -21,33 +21,33 @@ export type AvComputeContext<M extends AvAdapterModel = AvAdapterModel> = AvComp
|
|
|
21
21
|
* it. It receives the incoming `data` and returns the value to store; returning
|
|
22
22
|
* `undefined` keeps the current value.
|
|
23
23
|
*/
|
|
24
|
-
export type AvComputeCreate<M extends AvAdapterModel = AvAdapterModel> = (context: {
|
|
24
|
+
export type AvComputeCreate<M extends AvAdapterModel = AvAdapterModel, Value = unknown> = (context: {
|
|
25
25
|
readonly field: string;
|
|
26
26
|
readonly value: unknown;
|
|
27
27
|
readonly data: Readonly<Record<string, unknown>>;
|
|
28
28
|
readonly ctx: AvComputeContext<M>;
|
|
29
|
-
}) =>
|
|
29
|
+
}) => Value | void | Promise<Value | void>;
|
|
30
30
|
/**
|
|
31
31
|
* Computes a field's value when a row is updated, whether or not the caller sent
|
|
32
32
|
* it. It receives the incoming `data` and returns the value to store; returning
|
|
33
33
|
* `undefined` keeps the current value.
|
|
34
34
|
*/
|
|
35
|
-
export type AvComputeUpdate<M extends AvAdapterModel = AvAdapterModel> = (context: {
|
|
35
|
+
export type AvComputeUpdate<M extends AvAdapterModel = AvAdapterModel, Value = unknown> = (context: {
|
|
36
36
|
readonly field: string;
|
|
37
37
|
readonly value: unknown;
|
|
38
38
|
readonly data: Readonly<Record<string, unknown>>;
|
|
39
39
|
readonly ctx: AvComputeContext<M>;
|
|
40
|
-
}) =>
|
|
40
|
+
}) => Value | void | Promise<Value | void>;
|
|
41
41
|
/**
|
|
42
42
|
* Computes a field's value when a row is read. It receives the stored `record`
|
|
43
43
|
* and returns the value to return; returning `undefined` keeps the current value.
|
|
44
44
|
*/
|
|
45
|
-
export type ComputeReadFunction<M extends AvAdapterModel = AvAdapterModel, Cfg = AvConfigAny, R extends AvConfigResourceKey<M> = AvConfigResourceKey<M
|
|
45
|
+
export type ComputeReadFunction<M extends AvAdapterModel = AvAdapterModel, Cfg = AvConfigAny, R extends AvConfigResourceKey<M> = AvConfigResourceKey<M>, Value = unknown> = (context: {
|
|
46
46
|
readonly field: AvEffectiveFieldKey<M, Cfg, R>;
|
|
47
47
|
readonly value: unknown;
|
|
48
48
|
readonly record: Readonly<Record<AvEffectiveFieldKey<M, Cfg, R>, unknown>>;
|
|
49
49
|
readonly ctx: AvComputeContext<M>;
|
|
50
|
-
}) =>
|
|
50
|
+
}) => Value | void | Promise<Value | void>;
|
|
51
51
|
/** The fields of Resource `R` the adapter stores as scalars: what `dependsOn` may name. */
|
|
52
52
|
type StoredScalarKey<M extends AvAdapterModel, R extends AvConfigResourceKey<M>> = string extends AvAdapterFieldKey<M, R> ? string : {
|
|
53
53
|
[F in AvAdapterFieldKey<M, R>]: M["resources"][R]["fields"][F] extends {
|
|
@@ -58,8 +58,8 @@ type StoredScalarKey<M extends AvAdapterModel, R extends AvConfigResourceKey<M>>
|
|
|
58
58
|
* A read compute stage: a compute function, or `{ compute, dependsOn }` naming
|
|
59
59
|
* the fields it reads.
|
|
60
60
|
*/
|
|
61
|
-
export type AvComputeRead<M extends AvAdapterModel = AvAdapterModel, Cfg = AvConfigAny, R extends AvConfigResourceKey<M> = AvConfigResourceKey<M
|
|
61
|
+
export type AvComputeRead<M extends AvAdapterModel = AvAdapterModel, Cfg = AvConfigAny, R extends AvConfigResourceKey<M> = AvConfigResourceKey<M>, Value = unknown> = ComputeReadFunction<M, Cfg, R, Value> | {
|
|
62
62
|
readonly dependsOn?: readonly StoredScalarKey<M, R>[];
|
|
63
|
-
readonly compute: ComputeReadFunction<M, Cfg, R>;
|
|
63
|
+
readonly compute: ComputeReadFunction<M, Cfg, R, Value>;
|
|
64
64
|
};
|
|
65
65
|
export {};
|
|
@@ -4,8 +4,8 @@ import type { AvPreComputeLift } from "../config/computed-fields.js";
|
|
|
4
4
|
import type { AvRequestOrigin } from "../config/config-layer.js";
|
|
5
5
|
import type { AvConfigResourceKey } from "../config/framework-field-definition.js";
|
|
6
6
|
import type { AvContract } from "../contracts/contract.js";
|
|
7
|
-
import type { AvApplicationScalarForms } from "../contracts/scalar-value-type.js";
|
|
8
|
-
import type {
|
|
7
|
+
import type { AvApplicationScalarForms, ScalarForms } from "../contracts/scalar-value-type.js";
|
|
8
|
+
import type { AvModelFactsView, PreComputeView } from "../framework/contract-view.js";
|
|
9
9
|
import type { AvContextOperations } from "../framework/operation-facade.js";
|
|
10
10
|
import type { AvOperationArgumentsFor, AvResourceKey } from "../operations/operation-arguments.js";
|
|
11
11
|
import type { AvOperationFamily, AvOperationIdentity } from "../operations/operation-identity.js";
|
|
@@ -42,23 +42,31 @@ type AvContextFacts = {
|
|
|
42
42
|
readonly $state: AvState;
|
|
43
43
|
};
|
|
44
44
|
/**
|
|
45
|
-
* The arguments a variant
|
|
46
|
-
* the model's Application Contract view, in the application
|
|
47
|
-
*
|
|
48
|
-
* names are not literal.
|
|
45
|
+
* The arguments a variant's hooks and interceptors are told: the operation's
|
|
46
|
+
* arguments under the model's Application Contract view, in the application
|
|
47
|
+
* forms, every reference resolved to its value. `unknown` for a model whose
|
|
48
|
+
* Resource names are not literal.
|
|
49
49
|
*/
|
|
50
|
-
export type AvStageArguments<M extends AvAdapterModel, R extends string, F extends AvOperationFamily, V extends string> = StageArgumentsOf<
|
|
51
|
-
/**
|
|
52
|
-
|
|
50
|
+
export type AvStageArguments<M extends AvAdapterModel, R extends string, F extends AvOperationFamily, V extends string> = StageArgumentsOf<AvModelFactsView<M>, R, F, V, never>;
|
|
51
|
+
/**
|
|
52
|
+
* The arguments of operation `F`.`V` on `R` under the Contract view `C`, a
|
|
53
|
+
* reference position admitting an unresolved `$ref` in `References`' forms
|
|
54
|
+
* (`never`: none).
|
|
55
|
+
*/
|
|
56
|
+
type StageArgumentsOf<C, R extends string, F extends AvOperationFamily, V extends string, References extends ScalarForms> = string extends R ? unknown : C extends AvContract ? StageArgumentsIn<C, R, F, V, References> : unknown;
|
|
57
|
+
/** {@link StageArgumentsOf} once `C` is known to be a Contract. */
|
|
58
|
+
type StageArgumentsIn<C extends AvContract, R extends string, F extends AvOperationFamily, V extends string, References extends ScalarForms> = R extends AvResourceKey<C> ? AvOperationArgumentsFor<C, R, Extract<AvOperationIdentity, {
|
|
53
59
|
readonly family: F;
|
|
54
60
|
readonly variant: V;
|
|
55
|
-
}>, AvApplicationScalarForms,
|
|
61
|
+
}>, AvApplicationScalarForms, References> : unknown;
|
|
56
62
|
/**
|
|
57
63
|
* The arguments a variant's guards, pipes and filters are told: the operation's
|
|
58
64
|
* arguments before write compute has run, so a create need not hold a field
|
|
59
|
-
* `compute.create` fills (`Lift`, as `"<Resource>.<field>"`; `string`: any)
|
|
65
|
+
* `compute.create` fills (`Lift`, as `"<Resource>.<field>"`; `string`: any),
|
|
66
|
+
* and before a plan step's references are resolved, so a reference position
|
|
67
|
+
* admits an unresolved `$ref`.
|
|
60
68
|
*/
|
|
61
|
-
export type AvPreComputeStageArguments<M extends AvAdapterModel, R extends string, F extends AvOperationFamily, V extends string, Lift extends string = AvPreComputeLift<M, "root">> = F extends "find" | "delete" ?
|
|
69
|
+
export type AvPreComputeStageArguments<M extends AvAdapterModel, R extends string, F extends AvOperationFamily, V extends string, Lift extends string = AvPreComputeLift<M, "root">> = F extends "find" | "delete" ? StageArgumentsOf<AvModelFactsView<M>, R, F, V, AvApplicationScalarForms> : [Lift] extends [never] ? StageArgumentsOf<AvModelFactsView<M>, R, F, V, AvApplicationScalarForms> : StageArgumentsOf<PreComputeView<AvModelFactsView<M>, Lift>, R, F, V, AvApplicationScalarForms>;
|
|
62
70
|
/**
|
|
63
71
|
* What a pipeline callback is told about the operation it is wrapping, and the
|
|
64
72
|
* framework's executor: `ctx.<Resource>.<family>.<variant>(args)`, `ctx.$sql` and
|
|
@@ -88,9 +96,11 @@ export type AvContext<M extends AvAdapterModel = AvAdapterModel, R extends strin
|
|
|
88
96
|
* returned them in a pipe, and the validated arguments the operation executes in
|
|
89
97
|
* an interceptor, a hook or a filter. Only a pipe changes them, by returning new
|
|
90
98
|
* ones; writing to them throws a `TypeError`. At a variant, typed as that
|
|
91
|
-
* operation's arguments ({@link AvStageArguments}), deep read-only
|
|
92
|
-
* a filter a create may lack the fields
|
|
93
|
-
*
|
|
99
|
+
* operation's arguments ({@link AvStageArguments}), deep read-only, every
|
|
100
|
+
* reference resolved; in a guard, a pipe or a filter a create may lack the fields
|
|
101
|
+
* `compute.create` fills and a reference position may hold an unresolved `$ref`
|
|
102
|
+
* ({@link AvPreComputeStageArguments}: write compute has not run, nor reference
|
|
103
|
+
* resolution); above it, `unknown`.
|
|
94
104
|
*/
|
|
95
105
|
readonly $args: AvDeepReadonly<A>;
|
|
96
106
|
/**
|
|
@@ -14,7 +14,8 @@ export type TransactionSlotValue = {
|
|
|
14
14
|
* Installs values at transaction slot paths into one argument view: validated
|
|
15
15
|
* control snapshots at validation, resolved final data at execution. A slot
|
|
16
16
|
* path always names a member below `args`, so the view keeps its own
|
|
17
|
-
* container kind and prototype; only the ancestors of
|
|
17
|
+
* container kind and prototype; only the ancestors of the slots are cloned,
|
|
18
|
+
* each once, so the cost is linear in the slots and the members they cross.
|
|
18
19
|
*/
|
|
19
20
|
export declare function replaceTransactionSlotValues<T>(value: T, operation: number, slots: readonly TransactionSlotValue[]): T;
|
|
20
21
|
export {};
|
|
@@ -2,7 +2,55 @@ import { AvTransactionSlotPathError } from "./transaction-slot-path-error.js";
|
|
|
2
2
|
function slotPathMismatch(violation) {
|
|
3
3
|
throw new AvTransactionSlotPathError(violation);
|
|
4
4
|
}
|
|
5
|
-
function
|
|
5
|
+
function slotNode(source) {
|
|
6
|
+
return {
|
|
7
|
+
source,
|
|
8
|
+
replaced: false,
|
|
9
|
+
replacement: undefined,
|
|
10
|
+
children: new Map(),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function layOut(root, path, value) {
|
|
14
|
+
let node = root;
|
|
15
|
+
for (let index = 0; index < path.length; index += 1) {
|
|
16
|
+
if (node.replaced) {
|
|
17
|
+
node.source = node.replacement;
|
|
18
|
+
node.replaced = false;
|
|
19
|
+
node.replacement = undefined;
|
|
20
|
+
}
|
|
21
|
+
const container = node.source;
|
|
22
|
+
if (container === null || typeof container !== "object") {
|
|
23
|
+
slotPathMismatch("non-object-ancestor");
|
|
24
|
+
}
|
|
25
|
+
const property = String(path[index]);
|
|
26
|
+
if (property === "length" && Array.isArray(container)) {
|
|
27
|
+
slotPathMismatch("absent-member");
|
|
28
|
+
}
|
|
29
|
+
let child = node.children.get(property);
|
|
30
|
+
if (child === undefined) {
|
|
31
|
+
const descriptor = Object.getOwnPropertyDescriptor(container, property);
|
|
32
|
+
if (descriptor === undefined) {
|
|
33
|
+
slotPathMismatch("absent-member");
|
|
34
|
+
}
|
|
35
|
+
child = slotNode("value" in descriptor
|
|
36
|
+
? descriptor.value
|
|
37
|
+
: Reflect.get(container, property));
|
|
38
|
+
node.children.set(property, child);
|
|
39
|
+
}
|
|
40
|
+
node = child;
|
|
41
|
+
}
|
|
42
|
+
node.replaced = true;
|
|
43
|
+
node.replacement = value;
|
|
44
|
+
node.children.clear();
|
|
45
|
+
}
|
|
46
|
+
function install(node) {
|
|
47
|
+
if (node.replaced) {
|
|
48
|
+
return node.replacement;
|
|
49
|
+
}
|
|
50
|
+
if (node.children.size === 0) {
|
|
51
|
+
return node.source;
|
|
52
|
+
}
|
|
53
|
+
const container = node.source;
|
|
6
54
|
const array = Array.isArray(container);
|
|
7
55
|
const prototype = Object.getPrototypeOf(container);
|
|
8
56
|
const clone = array ? [] : Object.create(prototype);
|
|
@@ -19,54 +67,39 @@ function cloneContainerWithReplacement(container, property, replacement) {
|
|
|
19
67
|
lengthDescriptor = descriptor;
|
|
20
68
|
continue;
|
|
21
69
|
}
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
70
|
+
const child = typeof key === "string" ? node.children.get(key) : undefined;
|
|
71
|
+
if (child === undefined) {
|
|
72
|
+
Object.defineProperty(clone, key, descriptor);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const replacement = install(child);
|
|
76
|
+
Object.defineProperty(clone, key, "value" in descriptor
|
|
77
|
+
? { ...descriptor, value: replacement }
|
|
78
|
+
: {
|
|
79
|
+
configurable: descriptor.configurable ?? false,
|
|
80
|
+
enumerable: descriptor.enumerable ?? false,
|
|
81
|
+
value: replacement,
|
|
82
|
+
writable: true,
|
|
83
|
+
});
|
|
33
84
|
}
|
|
34
85
|
if (lengthDescriptor !== undefined) {
|
|
35
86
|
Object.defineProperty(clone, "length", lengthDescriptor);
|
|
36
87
|
}
|
|
37
88
|
return clone;
|
|
38
89
|
}
|
|
39
|
-
function replaceValueInContainer(container, property, tail, replacement) {
|
|
40
|
-
const descriptor = Object.getOwnPropertyDescriptor(container, property);
|
|
41
|
-
if (descriptor === undefined) {
|
|
42
|
-
return slotPathMismatch("absent-member");
|
|
43
|
-
}
|
|
44
|
-
const child = "value" in descriptor ? descriptor.value : Reflect.get(container, property);
|
|
45
|
-
return cloneContainerWithReplacement(container, property, replaceValueAtPath(child, tail, replacement));
|
|
46
|
-
}
|
|
47
|
-
function replaceValueAtPath(value, path, replacement) {
|
|
48
|
-
const [head, ...tail] = path;
|
|
49
|
-
if (head === undefined) {
|
|
50
|
-
return replacement;
|
|
51
|
-
}
|
|
52
|
-
if (value === null || typeof value !== "object") {
|
|
53
|
-
return slotPathMismatch("non-object-ancestor");
|
|
54
|
-
}
|
|
55
|
-
return replaceValueInContainer(value, String(head), tail, replacement);
|
|
56
|
-
}
|
|
57
90
|
export function replaceTransactionSlotValues(value, operation, slots) {
|
|
58
|
-
|
|
91
|
+
const root = slotNode(value);
|
|
59
92
|
for (const slot of slots) {
|
|
60
93
|
const [operations, index, args, head, ...tail] = slot.path;
|
|
61
94
|
if (operations !== "operations" ||
|
|
62
95
|
index !== operation ||
|
|
63
96
|
args !== "args" ||
|
|
64
97
|
head === undefined ||
|
|
65
|
-
|
|
66
|
-
typeof
|
|
98
|
+
value === null ||
|
|
99
|
+
typeof value !== "object") {
|
|
67
100
|
return slotPathMismatch("slot-prefix");
|
|
68
101
|
}
|
|
69
|
-
|
|
102
|
+
layOut(root, [head, ...tail], slot.value);
|
|
70
103
|
}
|
|
71
|
-
return
|
|
104
|
+
return install(root);
|
|
72
105
|
}
|
package/package.json
CHANGED
|
@@ -72,16 +72,18 @@ const IPV6_ALPHABET = /^[0-9a-fA-F:.]+$/;
|
|
|
72
72
|
const CREDIT_CARD = /^\d(?:[ -]?\d){11,18}$/;
|
|
73
73
|
const IBAN = /^[A-Z]{2}(?!00|01|99)\d{2}[A-Z0-9]{11,30}$/;
|
|
74
74
|
|
|
75
|
+
// zod writes this one as a string source: its class mixes a combining mark
|
|
76
|
+
// with the characters it combines with, on purpose, which a literal would
|
|
77
|
+
// trip `noMisleadingCharacterClass` over. A named source rather than a string
|
|
78
|
+
// argument, so no suppression is needed either: the generated client copies
|
|
79
|
+
// this file under its own file-wide one, where a line suppression is unused.
|
|
80
|
+
const EMOJI_SOURCE =
|
|
81
|
+
"^(?=[\\s\\S]*[\\p{Extended_Pictographic}\\p{Regional_Indicator}\\u20E3])[\\p{Extended_Pictographic}\\p{Emoji_Component}]+$";
|
|
82
|
+
|
|
75
83
|
const FORMAT_PATTERNS: Readonly<Partial<Record<AvFieldCheckFormat, RegExp>>> = {
|
|
76
84
|
email:
|
|
77
85
|
/^(?:[A-Za-z0-9_'+-]+\.)*[A-Za-z0-9_'+-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9-]*\.)+[A-Za-z]{2,}$/,
|
|
78
|
-
|
|
79
|
-
// with the characters it combines with, on purpose.
|
|
80
|
-
// biome-ignore lint/complexity/useRegexLiterals: a literal trips noMisleadingCharacterClass
|
|
81
|
-
emoji: new RegExp(
|
|
82
|
-
"^(?=[\\s\\S]*[\\p{Extended_Pictographic}\\p{Regional_Indicator}\\u20E3])[\\p{Extended_Pictographic}\\p{Emoji_Component}]+$",
|
|
83
|
-
"u",
|
|
84
|
-
),
|
|
86
|
+
emoji: new RegExp(EMOJI_SOURCE, "u"),
|
|
85
87
|
uuid: /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/,
|
|
86
88
|
guid: /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/,
|
|
87
89
|
nanoid: /^[a-zA-Z0-9_-]{21}$/,
|