@aventara/core 1.0.0-rc.3 → 1.0.0-rc.8
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 +231 -1
- package/ERRORS.md +6 -6
- package/README.md +76 -12
- 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 +13 -0
- package/dist/compiler/contract.compiler.d.ts +6 -0
- package/dist/compiler/contract.compiler.js +99 -15
- package/dist/config/computed-fields.js +2 -9
- package/dist/config/config-diagnostic.guidance.js +3 -3
- package/dist/config/config.resolver.d.ts +14 -0
- package/dist/config/config.resolver.js +31 -0
- package/dist/config/config.validator.js +82 -0
- package/dist/config/exact-framework-config.d.ts +5 -0
- package/dist/config/framework-config.d.ts +30 -1
- package/dist/contracts/client-contract-structure.validator.d.ts +8 -0
- package/dist/contracts/client-contract-structure.validator.js +6 -1
- package/dist/contracts/contract.d.ts +15 -1
- package/dist/contracts/field.d.ts +28 -0
- package/dist/contracts/scalar-value-type.d.ts +3 -1
- package/dist/framework/contract-view.d.ts +41 -10
- package/dist/framework/framework-construction.error.d.ts +2 -1
- package/dist/framework/framework-construction.error.js +11 -1
- package/dist/framework/framework.d.ts +4 -4
- package/dist/framework/framework.factory.js +39 -11
- package/dist/framework/operation-facade.d.ts +29 -10
- package/dist/framework/operation-facade.js +2 -1
- package/dist/index.d.ts +10 -4
- package/dist/internal.d.ts +2 -2
- package/dist/internal.js +2 -0
- package/dist/operations/operation-arguments.d.ts +44 -28
- package/dist/operations/operation-call.d.ts +9 -5
- package/dist/operations/operation-result.d.ts +6 -4
- package/dist/operations/operation-validator-core.js +6 -0
- package/dist/policy/policy-admission.d.ts +22 -0
- package/dist/policy/policy-admission.js +409 -0
- package/dist/policy/policy-after-update.runtime.d.ts +29 -0
- package/dist/policy/policy-after-update.runtime.js +132 -0
- package/dist/policy/policy-condition.d.ts +15 -0
- package/dist/policy/policy-condition.js +36 -0
- package/dist/policy/policy-engine.d.ts +118 -0
- package/dist/policy/policy-engine.js +1 -0
- package/dist/policy/policy-nested-write.runtime.d.ts +38 -0
- package/dist/policy/policy-nested-write.runtime.js +453 -0
- package/dist/policy/policy-read.runtime.d.ts +81 -0
- package/dist/policy/policy-read.runtime.js +503 -0
- package/dist/policy/policy-request.d.ts +41 -0
- package/dist/policy/policy-request.js +63 -0
- package/dist/policy/policy-session.d.ts +93 -0
- package/dist/policy/policy-session.js +235 -0
- package/dist/policy/policy-startup.validator.d.ts +8 -0
- package/dist/policy/policy-startup.validator.js +104 -0
- package/dist/policy/policy-write.runtime.d.ts +37 -0
- package/dist/policy/policy-write.runtime.js +409 -0
- package/dist/prebuilt/core-version.generated.d.ts +2 -0
- package/dist/prebuilt/core-version.generated.js +1 -0
- package/dist/prebuilt/prebuilt-changes.d.ts +28 -0
- package/dist/prebuilt/prebuilt-changes.js +65 -0
- package/dist/prebuilt/prebuilt-contracts.d.ts +40 -0
- package/dist/prebuilt/prebuilt-contracts.emitter.d.ts +16 -0
- package/dist/prebuilt/prebuilt-contracts.emitter.js +50 -0
- package/dist/prebuilt/prebuilt-contracts.js +1 -0
- package/dist/prebuilt/prebuilt-contracts.loader.d.ts +21 -0
- package/dist/prebuilt/prebuilt-contracts.loader.js +77 -0
- package/dist/prebuilt/prebuilt-inputs.fingerprint.d.ts +16 -0
- package/dist/prebuilt/prebuilt-inputs.fingerprint.js +192 -0
- 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/protocol-headers.d.ts +3 -3
- package/dist/runtime/computed-read-runtime.d.ts +6 -1
- package/dist/runtime/computed-read-runtime.js +226 -52
- package/dist/runtime/context-executor.runtime.d.ts +2 -2
- package/dist/runtime/context-executor.runtime.js +4 -2
- package/dist/runtime/create-requirement.js +1 -1
- package/dist/runtime/diagnostics-reporter.d.ts +5 -5
- package/dist/runtime/execution-options.d.ts +8 -0
- package/dist/runtime/framework-warning.emitter.d.ts +6 -0
- package/dist/runtime/framework-warning.emitter.js +8 -0
- package/dist/runtime/level-lifecycle.runtime.js +3 -2
- package/dist/runtime/operation-code.guidance.js +3 -3
- package/dist/runtime/operation-request.d.ts +16 -9
- package/dist/runtime/operation-runtime.d.ts +15 -7
- package/dist/runtime/operation-runtime.js +139 -14
- package/dist/runtime/owned-execution.registry.d.ts +2 -2
- package/dist/runtime/readonly-value.d.ts +5 -1
- package/dist/runtime/readonly-value.js +67 -28
- package/dist/runtime/referential-action.d.ts +1 -1
- package/dist/transactions/sql-statement.d.ts +2 -2
- package/dist/transactions/transaction-plan-assembler.d.ts +2 -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 +5 -2
- package/dist/transactions/transaction-runtime.js +33 -6
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,235 @@
|
|
|
1
1
|
# @aventara/core
|
|
2
2
|
|
|
3
|
+
## 1.0.0-rc.8
|
|
4
|
+
|
|
5
|
+
1.0.0-rc.5, rc.6 and rc.7 were tagged but never published; 1.0.0-rc.8 contains everything listed under them and the changes below.
|
|
6
|
+
|
|
7
|
+
### Patch Changes
|
|
8
|
+
|
|
9
|
+
- 1c8e4ee: Helpers typed off the façades compile again in projects that emit declarations. In 1.0.0-rc.7 a function that took
|
|
10
|
+
`Parameters<AppFramework["application"]["Post"]["find"]["many"]>[0]` and returned its `where` (or built a new one
|
|
11
|
+
from it, `{ AND: [args.where ?? {}, { published: { equals: true } }] }`) without a return type failed `nest build`
|
|
12
|
+
(and `tsc` with `"declaration": true`) with `TS4023: ... has or is using name 'ScalarFilterOperands' ... but cannot be
|
|
13
|
+
named`, usually beside `TS7056`. A nested write's data could fail the same way, or name `NO_ADVERTISED_MEMBER`, and a
|
|
14
|
+
nested `$update`'s `where` was written into the `.d.ts` as `any`. The declaration now names the argument types
|
|
15
|
+
`@aventara/core` exports (`AvWhereInput`, `AvUpdateDataInput`, `AvCreateDataInput`, ...), and these new exports cover
|
|
16
|
+
the rest:
|
|
17
|
+
|
|
18
|
+
- `AvScalarFilterOperands`: one field's filter operators, which a filter's `not` holds again.
|
|
19
|
+
- `AvScalarWhereInput`: the `where` of a nested `$update` or `$delete` that only takes plain fields.
|
|
20
|
+
- `AvNoAdvertisedMember`: a write member that advertises nothing; it takes `{}` and refuses every key.
|
|
21
|
+
- `AvSqlStatementBuilder` and `AvSqlStatementValue`: the type of `framework.appTx.$sql` and the values it binds.
|
|
22
|
+
A method returning `framework.appTx.$sql` failed with `TS2883`/`TS2742` before.
|
|
23
|
+
|
|
24
|
+
`framework.execute` and `framework.executeTransaction` take request types that `@aventara/core` exports:
|
|
25
|
+
`AvOperationRequest` (`AvApplicationOperationRequest`, `AvClientOperationRequest`, `AvAgentOperationRequest`),
|
|
26
|
+
`AvTransactionRequest` (`AvApplicationTransactionRequest`, `AvClientTransactionRequest`,
|
|
27
|
+
`AvAgentTransactionRequest`) and `AvRequestProtocol` (a remote request's `protocol`). They were named from
|
|
28
|
+
`@aventara/core/internal` (`FrameworkOperationRequest`, `FrameworkTransactionRequest`, ...), so a declaration that
|
|
29
|
+
spelled them reached into the package's internal entry. Those names are gone from `@aventara/core/internal`.
|
|
30
|
+
|
|
31
|
+
An adapter's `referentialActions` edges name the fields that hold the reference `referenceFields`, as a relation of
|
|
32
|
+
the Core Contract does. They were called `references`, which the Core Contract uses for the target's fields.
|
|
33
|
+
|
|
34
|
+
Reading a projecting variant's argument type (`Parameters<…["find"]["many"]>[0]`) also type-checks faster: about
|
|
35
|
+
6,000 type instantiations over a three-field model, where rc.7 took about 29,000. The part of the argument other than
|
|
36
|
+
`select`/`include` is now marked `NoInfer` in the call's parameter, so an editor shows that parameter as
|
|
37
|
+
`NoInfer<Omit<…>> & …`. What it accepts is unchanged.
|
|
38
|
+
|
|
39
|
+
A create's `data` type now states each required field once, as a required member (`Readonly<{ title: string; } & {
|
|
40
|
+
body?: ... }>` in an editor's expanded hover). Before, a required field appeared as an optional member beside a
|
|
41
|
+
separate `{ readonly title: WrittenValue }`, and a declaration file wrote both. What a create accepts and refuses is
|
|
42
|
+
unchanged.
|
|
43
|
+
|
|
44
|
+
- Operation results read as their value types in an editor and in emitted declarations. A result field that is nullable,
|
|
45
|
+
an enum or a `boolean` (such as a `Boolean @default(false)` column) was shown as `AvScalarValueType<…>`, with the
|
|
46
|
+
whole Contract view as its argument, and a project with `"declaration": true` wrote that view into its `.d.ts` once
|
|
47
|
+
per such field: a Nest service of five methods with no return types came out at 524 lines. Those fields now read
|
|
48
|
+
`body: string | null`, `published: boolean`, `publishedAt: Date | null`, and that service's declaration is 44 lines.
|
|
49
|
+
A result, and a relation included in it, is shown as its fields rather than as `ProjectedFields<…>`. This covers
|
|
50
|
+
`framework.application` and `framework.client` results, the same fields in a write's `data`, and clients generated
|
|
51
|
+
by `avclient`. What each type accepts is unchanged.
|
|
52
|
+
|
|
53
|
+
## 1.0.0-rc.7
|
|
54
|
+
|
|
55
|
+
1.0.0-rc.5 and 1.0.0-rc.6 were tagged but never published; 1.0.0-rc.7 contains everything listed under them and the changes below.
|
|
56
|
+
|
|
57
|
+
### Patch Changes
|
|
58
|
+
|
|
59
|
+
- f917c1c: A helper that reads `framework.contracts.core` compiles again in projects that emit declarations. In 1.0.0-rc.6 a
|
|
60
|
+
function returning a Resource or a field of the Core Contract without a return type, such as
|
|
61
|
+
`core.resources[resource]?.fields[relation]`, failed `nest build` (and `tsc` with `"declaration": true`, which Nest's
|
|
62
|
+
`tsconfig.json` sets) with `TS2883: The inferred type of 'relationOf' cannot be named without a reference to
|
|
63
|
+
'AvCoreField'` under TypeScript 6 (`TS2742` under 5.9), or with `TS4058`/`TS4023` naming `AvCoreResource`.
|
|
64
|
+
`@aventara/core` now exports the three types the Core Contract is written with: `AvCoreResource` (a Resource),
|
|
65
|
+
`AvCoreField` (one of its fields) and `AvCoreRelationField` (a relation field, with the target fields its reference
|
|
66
|
+
names in `references`).
|
|
67
|
+
|
|
68
|
+
A policy's startup check states the path once: `CONFIG_INVALID_VALUE at policy.engine.governed.Nope: "Nope" is no
|
|
69
|
+
Resource of the Core Contract`, where rc.6 repeated `policy.engine.governed.Nope:` inside the message. Each
|
|
70
|
+
diagnostic's `message` no longer starts with its `path`; read the path from `path`.
|
|
71
|
+
|
|
72
|
+
## 1.0.0-rc.6
|
|
73
|
+
|
|
74
|
+
1.0.0-rc.5 was tagged but never published. 1.0.0-rc.6 contains everything listed under 1.0.0-rc.5 and the fix below.
|
|
75
|
+
|
|
76
|
+
### Patch Changes
|
|
77
|
+
|
|
78
|
+
- 1cf72e0: Typed policy engines compile again in projects that emit declarations. In 1.0.0-rc.5 an engine written
|
|
79
|
+
`as const satisfies AvPolicyEngine` failed `nest build` (and `tsc` with `"declaration": true`, which Nest's
|
|
80
|
+
`tsconfig.json` sets) with `TS2883: The inferred type of 'policyEngine' cannot be named without a reference to
|
|
81
|
+
'PolicyFieldsAccess'` under TypeScript 6, or `TS2742` under 5.9, and so did the configuration and the framework holding
|
|
82
|
+
it. `fieldsOf`'s question is now written out in full in `AvPolicyEngine`, so a declaration can spell it.
|
|
83
|
+
|
|
84
|
+
A read question's `action` is now typed `undefined`, so an engine can destructure it from either question:
|
|
85
|
+
`fieldsOf({ resource, access, action })`. A write question's `action` is `"create"` or `"update"`, as before.
|
|
86
|
+
|
|
87
|
+
## 1.0.0-rc.5
|
|
88
|
+
|
|
89
|
+
### Patch Changes
|
|
90
|
+
|
|
91
|
+
- 4795ff1: Two additions for policy engines. A Framework without a policy, and every Contract a caller is served, are unchanged.
|
|
92
|
+
|
|
93
|
+
- A policy engine can check a create whose row reaches another Resource through a relation. Discovery now states, on the end of a relation that holds the reference, which of the target's fields its reference fields name, pair by pair (`Note.campaign`: `referenceFields: ["campaignId"]`, `references: ["id"]`). An engine reads them from the model it is built from (a Prisma discovery artifact's `model`) or from `framework.contracts.core`, and asks one `exists` about the row they name: "a player creates a note only in a campaign they play in". Only the adapter's model and the Core Contract carry them. The Application, Client and Agent Contracts, `/_contract`, the MCP tools and every ClientContract hash are unchanged. The discovery artifact's model hash, and so a prebuilt Contracts file's fingerprint, change: regenerate the artifact and rebuild the prebuilt file.
|
|
94
|
+
- `fieldsOf({ access: "write" })` also names the action that writes the record, `action: "create" | "update"` (an upsert's branches each their own, a nested record its directive's), so an engine can lock a field on update and still let a create write it. A read question names none. An engine that ignores it keeps working.
|
|
95
|
+
|
|
96
|
+
- 9e178ca: Fixes for applications that check requests with a policy engine. A Framework without a policy behaves as before.
|
|
97
|
+
|
|
98
|
+
- A cursor on a row the caller cannot read is answered exactly as a cursor on a row that does not exist, at the root and in a to-many relation, whatever values the caller sends beside the identifier. Before, the page started at the hidden row, so it told that the row exists and where its values sort. The Prisma adapter reads the cursor's row under the read scope first: one statement more for a cursor on a readable row, and only that read for one on a row the caller cannot read.
|
|
99
|
+
- A unique update with a nested write (`$create`, `$connect`, …) on a row the caller can read but not change answers `A4002`, as the same update without one does. It answered `A2003`. A row the caller cannot read still answers `A2003`.
|
|
100
|
+
- The `exists` questions a policy asks select the Resource's primary identifier, never another unique field such as a governed `email`.
|
|
101
|
+
- `policy.principal` may answer with a promise. A synchronous principal works as before; a rejection answers `A3000`, as a throw does.
|
|
102
|
+
- `AvConstructionError.message` names the first problem and how many more there are, for example `Aventara framework configuration validation failed: CONFIG_INVALID_TYPE at adapter.execute: adapter.execute must be a function (+2 more)`. `stage` and `diagnostics` are unchanged.
|
|
103
|
+
|
|
104
|
+
## 1.0.0-rc.4
|
|
105
|
+
|
|
106
|
+
### Patch Changes
|
|
107
|
+
|
|
108
|
+
- e3d03a7: The error catalog says more about policies. `A2003` now names the cases a policy adds: a unique read of a row outside the
|
|
109
|
+
caller's read scope, and a single-row update or delete of a row the caller cannot read, are misses, while a row the
|
|
110
|
+
caller can read but not change answers `A4002`. `CONFIG_REQUIRED`, `CONFIG_UNKNOWN_PROPERTY` and `CONFIG_INVALID_VALUE`
|
|
111
|
+
name the `policy` key's rules: `policy.engine` and `policy.principal` are required, `policy` and `prebuilt` are root-only,
|
|
112
|
+
and the engine's `governed`, `paths` and `afterChecks` lists must name what the Core Contract has. Wording only; no
|
|
113
|
+
behaviour changes.
|
|
114
|
+
- d10639a: Faster reads. The read-only copies stages are told cost about half what they did: a read with a computed field
|
|
115
|
+
copies each row in one pass, a row with several computed fields shares its nested objects between the stages'
|
|
116
|
+
copies, and after hooks share one frozen copy of the result. A row whose computed field returns a value at once no
|
|
117
|
+
longer waits a turn per row. The MCP endpoint builds its JSON Schema validator once instead of on every request.
|
|
118
|
+
What stages are told, the order they run in and what a request answers are unchanged.
|
|
119
|
+
- f67e68c: A service method can return any façade node or variant without a return type. Under `declaration: true` (a Nest
|
|
120
|
+
build) the declaration file now writes a short reference, under half a kilobyte per method at any schema size, where it
|
|
121
|
+
wrote the whole Contract view (about 440 KB per method over 50 Resources). A method returning a variant
|
|
122
|
+
(`this.framework.application.Post.find.many`) no longer needs an annotation to compile, and one returning a transaction
|
|
123
|
+
variant (`this.framework.appTx.Post.create.one`) no longer runs the compiler out of memory.
|
|
124
|
+
|
|
125
|
+
New type names, exported from `@aventara/core` so a declaration can write them (you never need to write them yourself):
|
|
126
|
+
|
|
127
|
+
- `AvContractView`, the Contract view the façades are typed over;
|
|
128
|
+
- one call type per surface and shape: `AvApplicationProjectingCall`, `AvApplicationPlainCall`,
|
|
129
|
+
`AvClientProjectingCall`, `AvClientPlainCall`, `AvAppTxProjectingCall`, `AvAppTxPlainCall`,
|
|
130
|
+
`AvClientTxProjectingCall`, `AvClientTxPlainCall`.
|
|
131
|
+
|
|
132
|
+
A configuration with `fields` entries still writes those entries after each reference; annotate the method if that
|
|
133
|
+
matters (see the README's "Returning a façade from a method").
|
|
134
|
+
|
|
135
|
+
- 708556c: Policy engines check nested writes. A create, update or upsert whose data writes a relation through a directive
|
|
136
|
+
(`$create`, `$connect`, `$connectOrCreate`, `$update`, `$upsert`, `$delete`, `$disconnect`, `$set`) now runs under a
|
|
137
|
+
policy, where before it was refused. It runs as one adapter call inside the write's interactive transaction, and one
|
|
138
|
+
failed check rolls the whole write back with `403 A4002`.
|
|
139
|
+
|
|
140
|
+
- Every row the nested write creates is read back as stored (its parent's reference filled, database defaults
|
|
141
|
+
present) and asked `can({ action: "create", row })`, its `exists` lookups seeing the rows the same call wrote. A
|
|
142
|
+
create whose own data holds a nested write is asked about its row the same way, after the insert.
|
|
143
|
+
- A row a directive links or unlinks is held to the scope of the row that changes: a linked row that holds the
|
|
144
|
+
reference (a to-many relation's rows, the back end of a one-to-one, a many-to-many relation's rows) must be in its
|
|
145
|
+
update scope, one whose parent holds the reference (a to-one relation's target) in its read scope. One `exists`
|
|
146
|
+
per Resource answers it, inside the transaction. A linked row that changes is an update: after the call it gets
|
|
147
|
+
the after check (`can({ action: "update", row })`) and the move-out re-read. Linking a new row to a one-to-one's
|
|
148
|
+
back end unlinks the old one, which is held the same way.
|
|
149
|
+
- A to-many nested `$update` or `$delete` acts only on the rows within the update (or delete) scope, as
|
|
150
|
+
`update.many` and `delete.many` do: core reads those rows' identifiers inside the transaction and hands the
|
|
151
|
+
adapter `where: { id: { in: [...] } }`. A to-one one refuses the write when its row is outside the scope. A nested
|
|
152
|
+
`$update`'s changed rows get the after check and the move-out re-read after the call.
|
|
153
|
+
- The fields a nested record sends are held to its Resource's `fieldsOf({ access: "write" })`, and a relation written
|
|
154
|
+
through a directive to the parent's grant, its reference fields with it.
|
|
155
|
+
- Where the operation's `transactionsStyle` is `"none"`, a nested write under a policy is refused before it runs.
|
|
156
|
+
|
|
157
|
+
- 3a0dfca: A write the policy checks costs fewer database round trips. What each check decides and refuses is unchanged.
|
|
158
|
+
|
|
159
|
+
- A single-row update or delete carries the caller's scopes inside its own `where` and runs as one statement, with no
|
|
160
|
+
read of its row before it. One that matches nothing still answers `A4002` when the caller can read the row and
|
|
161
|
+
`A2003` when it cannot, with one lookup on that miss only. The Prisma adapter sends it as
|
|
162
|
+
`update({ where: { id, AND: [scope] } })`.
|
|
163
|
+
- A create asks `can({ action: "create", row })` and its `exists` lookups before the insert, without a transaction. A
|
|
164
|
+
transaction around them never closed the race between the lookup and the insert at the database's default
|
|
165
|
+
isolation, and cost a BEGIN and a COMMIT.
|
|
166
|
+
- A transaction opens only when a check after the write may roll it back: an update's after check or move-out, a
|
|
167
|
+
created row held to a write grant, a nested write, or returned rows checked by `exists`. A plain checked create or
|
|
168
|
+
delete is one statement, its interceptors and hooks outside any transaction.
|
|
169
|
+
- The after check asks `can` about the row the update returned, so it reads nothing again. Only a move-out reads the
|
|
170
|
+
changed rows again. An `update.count` runs as the returning `update.many` when the Contract offers it.
|
|
171
|
+
|
|
172
|
+
Round trips per request on PostgreSQL: `create.one` 3 → 1, `delete.unique` 4 → 1, `update.unique` 5 → 3,
|
|
173
|
+
`update.many` 4 → 3, `update.count` 5 → 3. Reads, and a Framework without a policy, are unchanged.
|
|
174
|
+
|
|
175
|
+
- 99169d2: Policy engines, reads first. `createFramework` takes an optional root `policy: { engine, principal }`: an engine that
|
|
176
|
+
answers three questions (`can`, `scopeOf`, `fieldsOf`, each now or as a promise), and a function that derives a
|
|
177
|
+
request's principal from its `ctx.$state`. Core never imports an engine; one satisfies `AvPolicyEngine` by its shape,
|
|
178
|
+
and a scope or grant is a fragment of Aventara's own `where` (`AvPolicyWhere`).
|
|
179
|
+
|
|
180
|
+
- Client and agent requests are checked (an MCP tool call is an agent request). An application call is checked only
|
|
181
|
+
when it names a principal: `framework.application.Note.find.many(args, { as: user })`. A call a stage makes through
|
|
182
|
+
`ctx` never is.
|
|
183
|
+
- Before any guard, `can` and the fields the request names or receives (`fieldsOf`); a refusal is `403 A4002`. At the
|
|
184
|
+
adapter call, after every developer stage, the read scope is ANDed into the root `where`, every to-many include and
|
|
185
|
+
its `$count`, and every relation filter, at every level. A to-one include of a row out of scope refuses the request;
|
|
186
|
+
it is never nulled.
|
|
187
|
+
- Governed fields (the engine's `governed` list) are left out of every default projection and carry `governed: true`
|
|
188
|
+
in the Contracts. Name one to get it: `select: ["$defaults", "email"]`, where `$defaults` is the level's default
|
|
189
|
+
projection. It is refused where a returned row does not grant it, and a filter or sort by it is checked before the
|
|
190
|
+
query runs, so it cannot reveal values the caller may not read.
|
|
191
|
+
- The engine's `governed` and `paths` lists are checked against the Core Contract at startup. A rule's paths are lists
|
|
192
|
+
of segments, as other paths in core are: `["Note", "campaign", "members", "some", "userId", "equals"]`.
|
|
193
|
+
- Writes are checked as well; see the policy writes entry.
|
|
194
|
+
|
|
195
|
+
Without `policy`, nothing changes: the Contracts and their hashes are the same bytes.
|
|
196
|
+
|
|
197
|
+
- 0fd2608: A write the policy checks costs less CPU after the round-trip changes. The operation core hands its adapter no longer
|
|
198
|
+
carries the request's policy session, so an adapter that keeps its operations no longer keeps each request's session
|
|
199
|
+
with them. The fields a write returns only for the after check are dropped as soon as the check has read them, so a
|
|
200
|
+
read compute stage is told the row the request asked for, not every scalar field. What each check decides, the
|
|
201
|
+
fields `can` is asked about, and the database round trips are unchanged.
|
|
202
|
+
- 154e440: Policy engines check writes. Every create, update, delete and upsert a client or agent makes (or an application call
|
|
203
|
+
made `{ as }` a principal) is now checked by the configuration's `policy`, where before it was refused.
|
|
204
|
+
|
|
205
|
+
- The fields a write sends are held to `fieldsOf({ access: "write" })` before any guard; a field outside it answers
|
|
206
|
+
`403 A4002`. A field writable on some rows only is checked against every row an update reaches, and against the
|
|
207
|
+
row a create inserts, after the insert and inside its transaction.
|
|
208
|
+
- A create asks `can({ action: "create", row })` about each new row on its final data. Its rules can look rows up
|
|
209
|
+
through `exists`, which runs inside the write's transaction, so a plan that creates a campaign and then its first
|
|
210
|
+
note sees the campaign.
|
|
211
|
+
- An update or delete acts only on rows the caller may both change (`scopeOf` for the action) and read. A single-row
|
|
212
|
+
one that matches nothing answers `A4002` when the caller can read the row, and `A2003` when it cannot, so the
|
|
213
|
+
answer does not reveal that the row exists. A `many` or `count` one answers for the caller's rows only.
|
|
214
|
+
- An update that moves a row out of what the caller reads is rolled back with `A4002`. So is one that fails an `after`
|
|
215
|
+
condition: inside the transaction core asks `can({ action: "update", row })` about each changed row of every
|
|
216
|
+
checked update. The engine's new `afterChecks` member (`{ Note: ["update"] }`) names the updates whose grants carry
|
|
217
|
+
such a condition, which are the ones refused where there is no interactive transaction.
|
|
218
|
+
- Where the operation's `transactionsStyle` is `"interactive"`, a checked write runs in a transaction of its own. Where
|
|
219
|
+
it is `"none"` (Cloudflare D1), a write that can only be checked after it runs is refused before it runs; every other
|
|
220
|
+
write runs, a create looking up before it inserts, and an unlisted update without the after check.
|
|
221
|
+
- Nested writes (a relation directive in a write's data) are refused under a policy for now.
|
|
222
|
+
|
|
223
|
+
`afterChecks` is checked against the Core Contract at startup and changes no Contract and no prebuilt fingerprint.
|
|
224
|
+
|
|
225
|
+
- 2fcffdb: Prebuilt Contracts. `createFramework` takes an optional `prebuilt` key: the four Contracts compiled ahead of time (type
|
|
226
|
+
`AvPrebuiltContracts`), so a serverless or edge cold start loads them instead of compiling them. A file is used only
|
|
227
|
+
when this core compiled it, for this protocol, from the same configuration and adapter model; its Contracts, hashes
|
|
228
|
+
and `/_contract` bytes are then the ones compiling would give. Any other file is ignored whole: the framework compiles
|
|
229
|
+
at startup exactly as without one and warns once (`AVENTARA_PREBUILT_STALE`), naming the Resources whose configuration
|
|
230
|
+
or model changed, or the version that differs. Without `prebuilt`, nothing changes. The build step that writes the
|
|
231
|
+
file arrives with `@aventara/build`.
|
|
232
|
+
|
|
3
233
|
## 1.0.0-rc.3
|
|
4
234
|
|
|
5
235
|
### Patch Changes
|
|
@@ -27,7 +257,7 @@
|
|
|
27
257
|
on an `AvComputedValueError`, with nothing of the hidden field in the answer. Before, a shaped Json field hidden from
|
|
28
258
|
the layer was not judged at all, and a hidden field's own validators answered the caller `A2004` with an issue at the
|
|
29
259
|
hidden field's path. A layer that shows the field still answers `A2004` at it.
|
|
30
|
-
- b38034d: On PostgreSQL, a `@unique` value too long for its index (an index row past the B-tree's
|
|
260
|
+
- b38034d: On PostgreSQL, a `@unique` value too long for its index (an index row past the B-tree's limit of about 2704 bytes, SQLSTATE
|
|
31
261
|
`54000`) is answered `422 A2004`, not `500 A3001`: one `V1004` issue at `["arguments"]`, as a value too long for its
|
|
32
262
|
column already was. The adapter reads the SQLSTATE, never the message. The issue's message for a value too long now
|
|
33
263
|
reads `The database refused this value: it is longer than the database can hold for its column.`
|
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.8 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
|
|
|
@@ -30,7 +30,7 @@ The caller can fix these. The generated client and server-side calls throw the c
|
|
|
30
30
|
| [`A2000`](https://aventara-framework.vercel.app/docs/error-codes#a2000) | Invalid request. | The HTTP request cannot be interpreted: malformed JSON, a body that is not a JSON object, an unknown `/_*` path, a body that does not decode in its Content-Encoding, or a missing Aventara-Protocol-Version or Aventara-Contract-Hash header (the message names it). | Send a JSON object with both identity headers. The generated client does this for you. |
|
|
31
31
|
| [`A2001`](https://aventara-framework.vercel.app/docs/error-codes#a2001) | Unknown Resource. | The Resource is not in the caller's contract: misspelt, hidden by exposure, or added to the schema after aventara:prepare last ran. cause.message is "The requested Resource is not available." and the issue is V1005 at `["resource"]`. | Use the model's exact name. After a schema change run the adapter's generate step (`npm run aventara:prepare` in a scaffolded project), restart, and regenerate the client. A Resource hidden on purpose is reached from server code (framework.application). |
|
|
32
32
|
| [`A2002`](https://aventara-framework.vercel.app/docs/error-codes#a2002) | Unknown operation. | The operation is not available on that Resource: a typo, removed by exposure, an operation the adapter does not offer (such as delete.many), or a ctx.$sql, appTx.$sql or ctx.$transaction the adapter or the layer cannot run. The issue is V1006 at `["family"]` or `["variant"]`. | Call an operation the contract lists (`GET <api>/_contract`), regenerate a stale client, and check the layer's exemptions and transactionsStyle. |
|
|
33
|
-
| [`A2003`](https://aventara-framework.vercel.app/docs/error-codes#a2003) | Not found. | A unique operation (find.unique, update.unique, delete.unique) matched no row, or a $connect names a record that does not exist. The operation fails, so no after hook runs. | Catch AvNotFoundError, or use a first-style operation, which answers A1001 with null. Check the $connect identifier. |
|
|
33
|
+
| [`A2003`](https://aventara-framework.vercel.app/docs/error-codes#a2003) | Not found. | A unique operation (find.unique, update.unique, delete.unique) matched no row, or a $connect names a record that does not exist. The operation fails, so no after hook runs. Under a policy, a row outside the caller's read scope is a miss too, and so is a single-row update or delete of a row the caller cannot read, so the answer does not reveal that the row exists. A row the caller can read but not change answers A4002. | Catch AvNotFoundError, or use a first-style operation, which answers A1001 with null. Check the $connect identifier. |
|
|
34
34
|
| [`A2004`](https://aventara-framework.vercel.app/docs/error-codes#a2004) | Validation failed. | The arguments break a contract rule or a field validator. cause.issues lists each problem by V-code and path; a validator's issue also has check and params. The validators judge the values after the pipes and write compute, so a computed value they refuse is here too, at its field. A required create field left out is here (V1000). So is a value the database itself refused: too long for its column or its index, or out of its range (V1004), or holding a character it cannot store or a pattern it cannot read (V1001), with the issue at `["arguments"]`, or at the value where the adapter can place it (a bigint its driver cannot bind), and its message saying what the database refused. So is a record written through a relation that writes that relation, or a field holding its reference, back (V1012, at that field), where the adapter admits no such write. | Fix the argument each issue's path points at, as its V-code says. A field the server must fill takes a compute.create stage. For a value the database refused, send one its column holds. For a nested write that writes its relation back, leave that field out: the parent supplies it. |
|
|
35
35
|
| [`A2005`](https://aventara-framework.vercel.app/docs/error-codes#a2005) | Contract mismatch. | The caller's Aventara-Contract-Hash is not the server's current one: the schema, the configuration, the limits or the exposure changed, or the client targets another deployment. | Regenerate the client against the running server: `npm run avclient:generate`. |
|
|
36
36
|
| [`A2006`](https://aventara-framework.vercel.app/docs/error-codes#a2006) | Protocol mismatch. | Aventara-Protocol-Version is not 1. | Send `Aventara-Protocol-Version: 1`, or upgrade the client. |
|
|
@@ -63,7 +63,7 @@ Produced by your guards; the framework never authenticates by itself.
|
|
|
63
63
|
|---|---|---|---|
|
|
64
64
|
| [`A4000`](https://aventara-framework.vercel.app/docs/error-codes#a4000) | Unauthenticated. | A guard threw new AvError("A4000", ...): authentication is required or failed. The framework never authenticates by itself. | Send the credentials your guards expect, such as the Authorization header. |
|
|
65
65
|
| [`A4001`](https://aventara-framework.vercel.app/docs/error-codes#a4001) | Forbidden. | A guard returned false ("Operation denied by a framework guard."), a field write guard or a nested write's target guard included, or a stage threw A4001. | Call as a caller the guard admits, or stop sending the guarded field. To answer another code, throw an AvError from the guard. |
|
|
66
|
-
| [`A4002`](https://aventara-framework.vercel.app/docs/error-codes#a4002) | Policy denied. |
|
|
66
|
+
| [`A4002`](https://aventara-framework.vercel.app/docs/error-codes#a4002) | Policy denied. | The configuration's policy refused the request: its engine does not allow the operation, a field the request names, writes or returns, a row it reaches, a row it creates, or the row a change leaves (moved out of what the caller reads, or failing its grant's after condition, which rolls the write back); on a database without interactive transactions, a write the policy can only check after it runs; or a stage threw new AvError("A4002", ...). | Read cause.message, which names what the policy refused. Call as a principal the policy allows, leave the refused field or relation out (a governed one is named only where every row grants it), or change only rows the caller may change and still read afterwards. |
|
|
67
67
|
|
|
68
68
|
## Validation issues (V1xxx)
|
|
69
69
|
|
|
@@ -100,10 +100,10 @@ Each entry of `cause.issues[]` under `A2001`, `A2002`, `A2004`, `A2007` or `A200
|
|
|
100
100
|
|
|
101
101
|
| Code | Meaning | Cause | Fix |
|
|
102
102
|
|---|---|---|---|
|
|
103
|
-
| [`CONFIG_REQUIRED`](https://aventara-framework.vercel.app/docs/error-codes#config_required) | A required configuration key is missing. | A key that must be present is absent: api, adapter, or an overlay's exposed when the overlay sets exemptions. | Add the key at the diagnostic's path. |
|
|
104
|
-
| [`CONFIG_UNKNOWN_PROPERTY`](https://aventara-framework.vercel.app/docs/error-codes#config_unknown_property) | Unknown configuration key. | A key the configuration does not define at that place: a typo, or a key written at the wrong level of the tree. | Remove it, or move it where it belongs (the configuration reference's "Where a key can go"). |
|
|
103
|
+
| [`CONFIG_REQUIRED`](https://aventara-framework.vercel.app/docs/error-codes#config_required) | A required configuration key is missing. | A key that must be present is absent: api, adapter, or an overlay's exposed when the overlay sets exemptions, or policy.engine or policy.principal when policy is set. | Add the key at the diagnostic's path. |
|
|
104
|
+
| [`CONFIG_UNKNOWN_PROPERTY`](https://aventara-framework.vercel.app/docs/error-codes#config_unknown_property) | Unknown configuration key. | A key the configuration does not define at that place: a typo, or a key written at the wrong level of the tree. `policy` and `prebuilt` are root-only keys; in an overlay they read as unknown. | Remove it, or move it where it belongs (the configuration reference's "Where a key can go"). |
|
|
105
105
|
| [`CONFIG_INVALID_TYPE`](https://aventara-framework.vercel.app/docs/error-codes#config_invalid_type) | A configuration key has the wrong type. | A value of the wrong type, or a key set to an explicit undefined. | Give the value the type the reference states; leave a key out rather than setting it to undefined. |
|
|
106
|
-
| [`CONFIG_INVALID_VALUE`](https://aventara-framework.vercel.app/docs/error-codes#config_invalid_value) | A configuration value is not allowed. | A malformed api or mcp path, a negative limit, an unknown transactionsStyle, a format that is not a named zod format,
|
|
106
|
+
| [`CONFIG_INVALID_VALUE`](https://aventara-framework.vercel.app/docs/error-codes#config_invalid_value) | A configuration value is not allowed. | A malformed api or mcp path, a negative limit, an unknown transactionsStyle, a format that is not a named zod format, forceTransaction under an effective transactionsStyle "none", an after check on any action but update, or a policy engine's governed, paths or afterChecks naming a Resource, field or path the Core Contract does not have (the message names the Resource or the rule). | Use a value the message allows. |
|
|
107
107
|
| [`CONFIG_MOVED_PROPERTY`](https://aventara-framework.vercel.app/docs/error-codes#config_moved_property) | A configuration key moved in rc.1. | Configuration written for 1.0.0-rc.0: restrictions, entrypoint, fields, behaviors, transactions or pipelines.before/after. | Move it where the message says: restrictions to exposed + exemptions, entrypoint to api, fields to `resources.<R>.fields.<f>.definition`, transactions to transactionsStyle, before/after to pipelines.hooks. |
|
|
108
108
|
| [`CONFIG_FORBIDDEN_IN_OVERLAY`](https://aventara-framework.vercel.app/docs/error-codes#config_forbidden_in_overlay) | A root-only key is written in an overlay. | A field definition in application, client or agent. | Define the field once at the root (`resources.<R>.fields.<f>.definition`); an overlay only chooses its visibility. |
|
|
109
109
|
|
package/README.md
CHANGED
|
@@ -170,10 +170,8 @@ after compute and see every required field present.
|
|
|
170
170
|
## Returning a façade from a method
|
|
171
171
|
|
|
172
172
|
A Nest project emits declarations (`declaration: true`), so a method without a return type has its type written out.
|
|
173
|
-
Every node of the typed façades has a name for that
|
|
174
|
-
|
|
175
|
-
`clientTx` (`AvClientFacade`, `AvAppTxResourceFacade`, …). The declaration writes the name, with the Contract view as
|
|
176
|
-
its argument, so this compiles:
|
|
173
|
+
Every node of the typed façades has a name for that, and so does the Contract view they are typed over, so a method can
|
|
174
|
+
return any of them without an annotation:
|
|
177
175
|
|
|
178
176
|
```ts
|
|
179
177
|
@Injectable()
|
|
@@ -183,21 +181,43 @@ export class PostsService {
|
|
|
183
181
|
posts() {
|
|
184
182
|
return this.framework.application.Post;
|
|
185
183
|
}
|
|
184
|
+
|
|
185
|
+
findMany() {
|
|
186
|
+
return this.framework.application.Post.find.many;
|
|
187
|
+
}
|
|
186
188
|
}
|
|
187
189
|
```
|
|
188
190
|
|
|
189
|
-
The
|
|
190
|
-
return type to keep it small: `AppFramework["application"]["Post"]` is written as that reference, about 0.2 KB.
|
|
191
|
+
The declaration file writes a short reference, under half a kilobyte per method at any schema size:
|
|
191
192
|
|
|
192
193
|
```ts
|
|
193
|
-
posts():
|
|
194
|
-
|
|
195
|
-
|
|
194
|
+
posts(): import("@aventara/core").AvApplicationResourceFacade<
|
|
195
|
+
import("@aventara/core").AvContractView<import("…/discovery.artifact.js").AvModel, never, "omit", never, "omit", never, never>,
|
|
196
|
+
"Post">;
|
|
197
|
+
findMany(): import("@aventara/core").AvApplicationProjectingCall<
|
|
198
|
+
import("@aventara/core").AvContractView<import("…/discovery.artifact.js").AvModel, never, "omit", never, "omit", never, never>,
|
|
199
|
+
"Post", { readonly family: "find"; readonly variant: "many" }>;
|
|
196
200
|
```
|
|
197
201
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
202
|
+
The names, one set per surface (`application`, `client`, `appTx`, `clientTx`):
|
|
203
|
+
|
|
204
|
+
| Node | `framework.application` | the others |
|
|
205
|
+
| --- | --- | --- |
|
|
206
|
+
| the whole surface | `AvApplicationFacade` | `AvClientFacade`, `AvAppTxFacade`, `AvClientTxFacade` |
|
|
207
|
+
| one Resource | `AvApplicationResourceFacade` | `AvClientResourceFacade`, … |
|
|
208
|
+
| one family | `AvApplicationFamilyFacade` | `AvClientFamilyFacade`, … |
|
|
209
|
+
| a variant that takes a `select` or `include` | `AvApplicationProjectingCall` | `AvClientProjectingCall`, `AvAppTxProjectingCall`, `AvClientTxProjectingCall` |
|
|
210
|
+
| a variant that does not (the `count` variants: `find.count`, `update.count`, …) | `AvApplicationPlainCall` | `AvClientPlainCall`, `AvAppTxPlainCall`, `AvClientTxPlainCall` |
|
|
211
|
+
|
|
212
|
+
Each takes the view, `AvContractView`, as its first argument. The view's arguments are your model by its name, the root's
|
|
213
|
+
and the overlay's exposure as written, and the `fields` entries your configuration's `resources` blocks hold. A
|
|
214
|
+
configuration with no `fields` entries writes `never` there. One that has them writes those entries, stages erased, after
|
|
215
|
+
every reference: measured at about 23 KB per method over 50 Resources that each configure a field. Annotate the method
|
|
216
|
+
(`posts(): AppFramework["application"]["Post"]`) if that matters to you; the annotation is written as that reference.
|
|
217
|
+
|
|
218
|
+
You never need to write these names yourself. They exist so a declaration file can name what a method returns, and a
|
|
219
|
+
project that reads only your declarations types the method exactly as your Framework does. An application that publishes
|
|
220
|
+
no declarations can set `"declaration": false` and skip the question.
|
|
201
221
|
|
|
202
222
|
The configuration itself costs almost nothing to declare. The Prisma adapter's discovery artifact names the model's
|
|
203
223
|
type (`AvModel`), so the scaffold's `aventaraConfig()`, a `buildFramework` and its `AppFramework` alias write
|
|
@@ -310,6 +330,50 @@ It compiles to `framework.contracts.agent`, identified by its own hash, and `fra
|
|
|
310
330
|
endpoint. A host runs agent calls through `AvProtocol.bind(framework, { origin: "agent" })`, which validates them
|
|
311
331
|
exactly as client calls are validated, against the Agent Contract.
|
|
312
332
|
|
|
333
|
+
## A policy engine
|
|
334
|
+
|
|
335
|
+
`policy` hands authorization to an engine Aventara never imports. The engine answers three questions per
|
|
336
|
+
Resource — may the principal run this action (`can`), which rows (`scopeOf`, a `where` fragment), which fields
|
|
337
|
+
(`fieldsOf`) — and core enforces the answers on every client and agent request:
|
|
338
|
+
|
|
339
|
+
```ts
|
|
340
|
+
const framework = await createFramework({
|
|
341
|
+
api: "/api",
|
|
342
|
+
adapter,
|
|
343
|
+
policy: {
|
|
344
|
+
engine, // satisfies AvPolicyEngine
|
|
345
|
+
principal: ({ $state }) => $state.user ?? null,
|
|
346
|
+
},
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
await framework.application.Note.find.many({}, { as: user }); // checked as `user`
|
|
350
|
+
await framework.application.Note.find.many({}); // server code: not checked
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
The read scope is applied at the adapter call, after every developer stage, to the root `where`, every to-many include
|
|
354
|
+
and its `$count`, and every relation filter. A field the engine governs (its `governed` list) is never in a default
|
|
355
|
+
projection; name it to get it, `select: ["$defaults", "email"]`. A refusal answers `403 A4002`.
|
|
356
|
+
|
|
357
|
+
Writes are checked too. A create asks `can` about each new row, whose rules may look rows up through `exists`. An
|
|
358
|
+
update or delete acts only on rows the caller may change and read; a single-row one that misses answers `A4002` when
|
|
359
|
+
the caller can read the row and `A2003` when it cannot. A field the write sends must be in
|
|
360
|
+
`fieldsOf({ access: "write", action })`, `action` being `"create"` or `"update"` (an upsert's branches and a nested
|
|
361
|
+
record's directive each their own), so a field can be locked on update and still written at create. A rule that
|
|
362
|
+
reaches another Resource through a relation (a note only in a campaign its author plays in) reads which of the
|
|
363
|
+
target's fields the reference names from the Core Contract (`framework.contracts.core`, a relation's `references`
|
|
364
|
+
beside its `referenceFields`); no Contract a caller is served states them. An update that moves a row out of what the caller reads, or fails an `after` condition
|
|
365
|
+
(`can` asked about the row the update returned), is rolled back. Where `transactionsStyle` is `"interactive"`, a
|
|
366
|
+
checked write with a check after it (an update, a nested write) runs in a transaction of its own, and a create or
|
|
367
|
+
delete checked before it runs is one statement; where it is `"none"`, a write that could only be checked after it runs
|
|
368
|
+
is refused (for after conditions, the updates the engine lists in `afterChecks`).
|
|
369
|
+
|
|
370
|
+
Nested writes (`$create`, `$connect` and the other relation directives) run as one call inside that transaction. Before
|
|
371
|
+
it, every row they link is held to the right scope: a linked row that holds the reference must be one the caller may
|
|
372
|
+
change, one whose parent holds it one the caller may read. A to-many `$update` or `$delete` acts only on the rows the
|
|
373
|
+
caller may change or delete, as `update.many` does. After the call, every row they changed is checked as an update is
|
|
374
|
+
(the after check, the move-out re-read), and every row they created is read back as stored and asked `can`; one refusal
|
|
375
|
+
rolls the whole write back. Where `transactionsStyle` is `"none"`, a nested write under a policy is refused.
|
|
376
|
+
|
|
313
377
|
## License
|
|
314
378
|
|
|
315
379
|
PolyForm Shield 1.0.0 with an additional permission. It is free to use, including in commercial applications, but you
|
|
@@ -38,6 +38,14 @@ export type AvAdapterRelationField<M extends AvAdapterModel> = {
|
|
|
38
38
|
* one. Absent on the end that holds no reference (`User.posts`); never empty.
|
|
39
39
|
*/
|
|
40
40
|
readonly referenceFields?: readonly [string, ...string[]];
|
|
41
|
+
/**
|
|
42
|
+
* The target's scalar fields that `referenceFields` name, pair by pair in
|
|
43
|
+
* the same order (`Post.author`: `referenceFields: ["authorId"]`,
|
|
44
|
+
* `references: ["id"]`, so `Post.authorId` holds `User.id`). Stated only
|
|
45
|
+
* beside `referenceFields`, with as many entries; absent on the end that
|
|
46
|
+
* holds no reference, where the other end (`inverse`) states the pairs.
|
|
47
|
+
*/
|
|
48
|
+
readonly references?: readonly [string, ...string[]];
|
|
41
49
|
};
|
|
42
50
|
/** One field the adapter discovered: a scalar or a relation. */
|
|
43
51
|
export type AvAdapterField<M extends AvAdapterModel> = AvAdapterScalarField<M> | AvAdapterRelationField<M>;
|
|
@@ -13,3 +13,12 @@ export type AvAdapterIdentifier<TFields extends Readonly<Record<string, AvAdapte
|
|
|
13
13
|
AvAdapterScalarFieldKey<TFields>,
|
|
14
14
|
...AvAdapterScalarFieldKey<TFields>[]
|
|
15
15
|
];
|
|
16
|
+
/**
|
|
17
|
+
* The Resource's identifier as the Adapter states it first: in the Adapter's
|
|
18
|
+
* own order (an ORM adapter's is its schema's, so the primary key). The Core
|
|
19
|
+
* Contract sorts identifiers for its hash, so its first one is the
|
|
20
|
+
* alphabetically first (`email` before `id`) and says nothing about which is
|
|
21
|
+
* the primary key; the model keeps the order it declared. Empty for a Resource
|
|
22
|
+
* the model does not hold or that has no identifier.
|
|
23
|
+
*/
|
|
24
|
+
export declare function adapterPrimaryIdentifier(model: AvAdapterModel, resource: string): readonly string[];
|
|
@@ -5,6 +5,19 @@ import type { AvAdapterModel } from "./adapter-model.js";
|
|
|
5
5
|
* Provider execution request after framework semantic validation.
|
|
6
6
|
* Operation identity and arguments remain provider-neutral; adapters receive
|
|
7
7
|
* only validated semantic argument objects, never raw transport input.
|
|
8
|
+
*
|
|
9
|
+
* Two shapes exist here only, both an `AND`: a list of conditions in the
|
|
10
|
+
* general `where` grammar the addressed row must also meet.
|
|
11
|
+
*
|
|
12
|
+
* - The flat unique selector of an `update.unique` or a `delete.unique` may
|
|
13
|
+
* carry it. A row the selector names that misses them is a miss (the same
|
|
14
|
+
* `null` as a row that is not there).
|
|
15
|
+
* - A `cursor` may carry it, at the root of a `find.many` and in a to-many
|
|
16
|
+
* relation's arguments. A row the cursor names that misses them is a cursor
|
|
17
|
+
* that names no row: the same answer as a cursor on a row that is not there.
|
|
18
|
+
*
|
|
19
|
+
* An adapter must honour it or refuse the operation, never drop it: dropping it
|
|
20
|
+
* would write a row the conditions exclude, or position a page at one.
|
|
8
21
|
*/
|
|
9
22
|
export type AvAdapterOperation<M extends AvAdapterModel = AvAdapterModel> = AvOperationIdentity & {
|
|
10
23
|
readonly resource: Extract<keyof M["resources"], string>;
|
|
@@ -30,3 +30,9 @@ export type ContractCompilationResult = {
|
|
|
30
30
|
* Agent Contract from the view of the layer it extends.
|
|
31
31
|
*/
|
|
32
32
|
export declare function compileContracts<M extends AvAdapterModel>(model: M, config: ResolvedFrameworkConfig<M>): Promise<ContractCompilationResult>;
|
|
33
|
+
/**
|
|
34
|
+
* Binds Contracts loaded from a prebuilt file to the runtime pieces
|
|
35
|
+
* {@link compileContracts} binds to the ones it compiles, from the live
|
|
36
|
+
* `config` and `model` they were compiled from.
|
|
37
|
+
*/
|
|
38
|
+
export declare function bindPrebuiltContracts<M extends AvAdapterModel>(model: M, config: ResolvedFrameworkConfig<M>, contracts: AvContracts): void;
|