@aventara/core 1.0.0-rc.11 → 1.0.0-rc.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +157 -0
  2. package/ERRORS.md +8 -7
  3. package/README.md +3 -2
  4. package/dist/avz/avz-check.factory.d.ts +2 -0
  5. package/dist/avz/avz-check.factory.js +12 -0
  6. package/dist/avz/avz.d.ts +6 -0
  7. package/dist/avz/avz.js +1 -0
  8. package/dist/compiler/contract-compiler-diagnostic.d.ts +13 -2
  9. package/dist/compiler/contract-compiler-diagnostic.guidance.js +7 -2
  10. package/dist/compiler/contract.compiler.js +110 -15
  11. package/dist/compiler/create-requirements.projector.d.ts +45 -3
  12. package/dist/compiler/create-requirements.projector.js +239 -28
  13. package/dist/compiler/managed-elsewhere.projector.d.ts +3 -0
  14. package/dist/compiler/managed-elsewhere.projector.js +1 -1
  15. package/dist/config/computed-fields.d.ts +8 -0
  16. package/dist/config/computed-fields.js +1 -1
  17. package/dist/config/config-diagnostic.guidance.js +2 -2
  18. package/dist/config/config.resolver.d.ts +2 -2
  19. package/dist/config/config.validator.js +15 -6
  20. package/dist/config/exact-framework-config.d.ts +1 -0
  21. package/dist/config/field-pipeline-config.d.ts +2 -2
  22. package/dist/config/forced-operation.validator.js +3 -2
  23. package/dist/config/framework-config.d.ts +8 -3
  24. package/dist/config/framework-exemptions-config.d.ts +13 -8
  25. package/dist/config/framework-resources-config.d.ts +11 -3
  26. package/dist/contracts/client-contract-structure.validator.d.ts +4 -1
  27. package/dist/contracts/client-contract-structure.validator.js +3 -2
  28. package/dist/contracts/contract-limits.d.ts +8 -1
  29. package/dist/contracts/contract-limits.js +1 -0
  30. package/dist/contracts/contract.d.ts +7 -3
  31. package/dist/contracts/transaction-capability.d.ts +8 -0
  32. package/dist/contracts/transaction-capability.js +7 -0
  33. package/dist/framework/access-facade.js +14 -6
  34. package/dist/framework/contract-view.d.ts +104 -12
  35. package/dist/framework/operation-facade.d.ts +8 -3
  36. package/dist/internal.d.ts +1 -0
  37. package/dist/internal.js +1 -0
  38. package/dist/operations/validation-code.guidance.js +1 -1
  39. package/dist/policy/access-provider.interface.d.ts +7 -2
  40. package/dist/policy/access-question.runtime.js +2 -1
  41. package/dist/prebuilt/core-version.generated.d.ts +1 -1
  42. package/dist/prebuilt/core-version.generated.js +1 -1
  43. package/dist/protocol/absent-route.responder.d.ts +2 -2
  44. package/dist/protocol/absent-route.responder.js +2 -1
  45. package/dist/protocol/av-protocol.d.ts +1 -0
  46. package/dist/protocol/av-protocol.js +1 -0
  47. package/dist/protocol/protocol-surface.js +2 -1
  48. package/dist/protocol/protocol.handler.js +3 -1
  49. package/dist/runtime/context-executor.runtime.js +2 -1
  50. package/dist/runtime/create-requirement.d.ts +5 -1
  51. package/dist/runtime/create-requirement.js +10 -2
  52. package/dist/runtime/field-check.evaluator.js +39 -2
  53. package/dist/runtime/field-validator.runtime.d.ts +4 -3
  54. package/dist/runtime/field-validator.runtime.js +29 -0
  55. package/dist/runtime/mutation-planner.js +26 -9
  56. package/dist/runtime/operation-code.guidance.js +3 -3
  57. package/dist/runtime/operation-runtime.d.ts +10 -9
  58. package/dist/runtime/operation-runtime.js +20 -18
  59. package/dist/runtime/pipeline.d.ts +1 -15
  60. package/dist/runtime/state-refusal.mapper.d.ts +16 -0
  61. package/dist/runtime/state-refusal.mapper.js +40 -0
  62. package/dist/transactions/operation-fingerprint.d.ts +15 -2
  63. package/dist/transactions/transaction-plan-validator.js +31 -1
  64. package/dist/transactions/transaction-runtime.js +1 -1
  65. package/package.json +1 -1
  66. package/src/runtime/field-check.evaluator.ts +46 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,162 @@
1
1
  # @aventara/core
2
2
 
3
+ ## 1.0.0-rc.13
4
+
5
+ ### Minor Changes
6
+
7
+ - f3b291f: `AvProtocol.fingerprint({ resource, family, variant, args })` on `@aventara/core/protocol` computes a transaction
8
+ plan step's `fingerprint`, so a client written by hand can send a raw `POST /_transactions` plan: the RFC 8785
9
+ canonical JSON of the four members over the step's wire-form arguments, hashed with SHA-256, the first 16 bytes in
10
+ base64url without padding, after `fp1:`. It is the function the server checks a step with, and rejects arguments
11
+ that are not JSON (a lone surrogate, a non-finite number, a `bigint`, a non-plain object).
12
+ - f3b291f: A new limit, `maxTransactionWrites` (default `20`), counts the write steps of one transaction plan: each `create`,
13
+ `update`, `upsert` and `delete` step, and each `appTx.$sql` step, since a statement cannot be classed as a read. A
14
+ plan with more is refused before anything runs (`422 A2009`, one `V1014` at `["operations"]`). It is set like every
15
+ other limit (`limits` at the root, overridden per limit by a layer) and is part of each Contract's `limits`, so every
16
+ Contract hash moves with this release. `maxTransactionOperations` still counts every step, reads and `$sql` steps
17
+ included. The two limits are separate: `maxTransactionWrites` defaults to `20` whatever `maxTransactionOperations` is
18
+ set to, so raising only the step limit still caps a plan at 20 writes; raise both. The limit counts steps, not rows: a
19
+ `create` step with nested `$create`s is one write. `POST /_all` is bounded by `maxAll`, not by either plan limit, and its writes do not roll back together.
20
+ The MCP `transaction` tool's description states the write limit when it is lower than the step limit.
21
+ - f3b291f: `transactionsStyle` takes a third value, `"server"`: the server still opens transactions, and callers send no plans.
22
+ A forced call (`forceTransaction`), a write the policy checks after it runs and `ctx.$transaction` run in a
23
+ transaction as under `"interactive"`, while the layer has no `POST /_transactions` route, a plan sent to it answers as
24
+ that absent route (`404 A2002`, one `V1006` at `["operations"]`), `framework.transaction` refuses a plan of that
25
+ origin the same way, the generated client has no `tx` or `transaction`, and an MCP agent layer under it has no
26
+ `transaction` tool. `client: { transactionsStyle: "server" }` keeps forced writes atomic without letting remote
27
+ callers send multi-step plans. `forceTransaction` under `"none"` is still a startup error; under `"server"` it is
28
+ allowed. Like `"interactive"`, `"server"` needs an adapter with transactions (`COMPILER_UNAVAILABLE_CAPABILITY`
29
+ otherwise). The Contract's `transactions` carries the value, so a layer that states it gets a new Contract hash.
30
+
31
+ ### Patch Changes
32
+
33
+ - 6c3e25c: `AvZ.oneOf(["USD", "EUR"])` is a string field validator for a closed set of values. It builds an anchored pattern of
34
+ the values, each one's pattern syntax escaped (`"C++"` and `"a.b"` match only themselves), so the Contract gains no
35
+ new check: the generated client runs it like any `AvZ.regex`, and the field's type stays `string`. A refused value
36
+ answers `V1003` with the message `Value must be one of "USD", "EUR".` rather than the pattern. An empty list, or a
37
+ value that is not a string, is refused when the check is built, and the types refuse both. When the types should name
38
+ the set as a union, use a Prisma `enum`: Aventara carries it into the Contract and the generated client as a literal
39
+ union.
40
+ - 2aaa27d: A stage's `ctx` create no longer has to write a field that the root's or the `application` overlay's `compute.create`
41
+ fills. A `ctx` call runs that compute, and the generated types already state which fields it fills (the
42
+ `computed-fields.generated.ts` file `aventara-build` writes), so `ctx.Payment.create.one({ data })` type-checks without
43
+ the computed `employeeId`. A field that only the `client` or `agent` overlay computes is still required, because that
44
+ compute does not run for a `ctx` call. A model whose types state no computed fields keeps every requirement. The agent
45
+ skill says so.
46
+ - 2aaa27d: Exemptions take one more value, `"read"`, on a field and on a Resource's `fields`. It names every read capability of
47
+ the field that the layer above offers: a scalar's `select`, `filter`, `order` and `orderNulls`, a relation's `select`,
48
+ `include`, `filter`, `order`, `pagination` and `reducers`. On `fields` it applies to every field of the Resource.
49
+
50
+ - Under `exposed: false` it exposes reading and no write, so a read-only Resource no longer needs every field listed:
51
+ `Category: { operations: { find: true }, fields: "read" }`.
52
+ - Under `exposed: true` it hides reading and keeps the writes, which is the write-only field:
53
+ `passwordHash: "read"`.
54
+
55
+ It compiles to the same Contracts as the capability lists it stands for, hash included. Like every exemption it only
56
+ narrows: it never brings back a capability the layer above hid, and naming a field the layer above hid is refused as
57
+ before. `fields: "read"` names relations too, so a relation into a Resource the layer leaves out still fails with
58
+ `COMPILER_DANGLING_RELATION`. The agent skill documents it.
59
+
60
+ Read-only now means no nested write either. A relation does not write rows of a Resource its layer cannot write: one
61
+ that creates nothing (no create or upsert operation, and no field writable on create) takes no `$create`,
62
+ `$connectOrCreate` or `$upsert`; one that updates nothing (no update or upsert operation, and no field writable on
63
+ update) takes no `$update` or `$upsert`; one with no delete operation takes no `$delete`. The fields count with the
64
+ operations for creates and updates, so a Resource whose create operation is hidden but whose fields stay writable on
65
+ create is still created through its parent. Relation fields count too, even link-only (`$connect`): if a Resource must
66
+ create nothing, hide its relations' `create` as well. This holds however the Resource was made read-only, `"read"` or listed
67
+ capabilities, and also when it has no required field, where a nested `$create: {}` was accepted before. `$connect`,
68
+ `$disconnect` and `$set` stay.
69
+
70
+ What happens follows the layer's `exposed`. Under `exposed: false` the relation's directives are the ones the layer
71
+ grants (by `true` on the Resource or the field, by the relation's `create` or `update`, or by naming the directive), so
72
+ such a grant is refused at startup with `COMPILER_READ_ONLY_NESTED_WRITE`: let the relation only link,
73
+ `author: { select: true, create: ["$connect"] }`. Under `exposed: true` the relation inherits its directives, so they
74
+ are dropped from it and startup succeeds; a call that sends one gets `V1006`, or "Relation … is not writable during
75
+ update." when the relation's update had nothing else. Dropping one can leave another Resource with nothing writable, and
76
+ the nested writes into it go the same way. A layer with `exposed: false` below that names a dropped directive, or grants
77
+ a member that was removed, is refused with `COMPILER_READ_ONLY_NESTED_WRITE`.
78
+
79
+ A configuration under `exposed: true` that hides a Resource's deletes, or every way to create or update it, therefore
80
+ serves Contracts with fewer directives on the relations into it, and its ClientContract hash changes: regenerate the
81
+ frontend client. Such a configuration was refused at startup before when a hidden field was required, and otherwise
82
+ accepted nested creates.
83
+
84
+ - e4e8abd: The same rule now holds for in-process calls. When `state` rejects with an object whose `status` or `statusCode` is
85
+ the number 401 or 403, an `avClient` call in a Next.js Server Component or Server Action rejects with an `AvAuthError`
86
+ of code `A4000` or `A4001`, and so does `framework.serverClient.$access.can` given `headers`. Its message is core's own
87
+ (`"Unauthenticated."` or `"Forbidden."`) and nothing of the rejection reaches the caller. Before, the call rejected
88
+ with the library's own error. A thrown `AvError` and any other rejection still reject the call unchanged.
89
+
90
+ An object whose `status` and `statusCode` are both numbers that disagree, such as `status: 500` beside
91
+ `statusCode: 401`, is no longer read as a refusal: a route answers it `500 A3000`. A number beside a non-number, such
92
+ as Better Auth's `status: "UNAUTHORIZED"` beside `statusCode: 401`, is still read by the number.
93
+
94
+ - e662048: A request whose `state` (the host's `state` option, or the `access` provider's `state`) rejects with an object whose
95
+ `status` or `statusCode` is the number 401 or 403 is now answered `401 A4000` or `403 A4001`, not `500 A3000`. This is
96
+ how authentication libraries refuse a bad credential: Better Auth's `APIError` for a wrong API key carries
97
+ `statusCode: 401`. The answer is core's own envelope (`"Unauthenticated."` or `"Forbidden."`); nothing of the
98
+ rejection, neither its message nor its code, reaches the caller, and it is not reported to `diagnostics`, as a thrown
99
+ `AvError("A4000", …)` is not. It applies on every protocol route that seeds state: operations, cached reads,
100
+ `/_transactions`, `/_all`, `/_access/can`, and MCP tool calls. A thrown `AvError` is still its own outcome, and any
101
+ other rejection, a 401 beside a 403 included, is still `500 A3000`.
102
+ - 6c3e25c: Field validators now judge every value the caller sent before write compute, and each value once. A value the caller
103
+ sent is judged after the guards, the pipes and its field pipes, before any compute stage runs, and that includes a
104
+ field that has a `compute.create` or `compute.update` stage of its own. After compute, only a value compute set or
105
+ changed is judged: a field the caller left out that compute filled, or a caller's value compute replaced. A compute
106
+ stage that returns `undefined` or the caller's own value changes nothing, so that value is not judged again. The order
107
+ is now guards → pipes → validators of the caller's values → write compute → validators of what compute set or changed
108
+ → hooks.
109
+
110
+ A compute stage never sees a value its own validators refuse. A caller value that also breaks a compute stage (a
111
+ quantity that overflows a total, an unknown currency code handed to `Intl.NumberFormat`) answered `500 A3000` before
112
+ this release; it now answers `422 A2004` with the issue at the field, and no compute stage runs. A bad caller value is
113
+ refused even when compute would have replaced it: before this release that value was dropped silently. Codes and
114
+ issue shapes are unchanged. One answer no longer mixes the two kinds: when a caller value is refused, the computed
115
+ values are not judged yet. In a transaction plan or a forced call, every literal value is judged before the
116
+ transaction opens; a resolved `$ref` value and a value a Resource transaction pipe changed are judged at their step,
117
+ before write compute. The agent skill's stage order says so.
118
+
119
+ - 6c3e25c: A field the configuration adds (`lifecycle: ["VIRTUAL"]`) with `nullable: false` is now a create requirement, like the
120
+ adapter's own non-null columns: in each layer that lets a caller write it at create, and where no `compute.create` (the
121
+ root's or that layer's) fills it, it joins the Contract's `createRequirements.required`. A create without it answers
122
+ `422 A2004` with `V1000` at the field, before any guard runs, and `framework.application`, `framework.client` and the
123
+ generated client type it as required. A layer that hides the field creates without it, and an update is unchanged. A
124
+ nullable added field stays optional. A `compute.create` stage that fills such a field and returns `undefined` answers
125
+ `500 A3000` with an `AvComputedValueError` in the `diagnostics` sink, as it does for a required database column, and
126
+ nothing is written.
127
+
128
+ This changes the Client Contract, and so its hash, for every configuration with such a field: regenerate the frontend
129
+ client. A configuration that sent a `nullable: false` virtual input only sometimes must now send it on every create, or
130
+ state it `nullable: true`.
131
+
132
+ - a708188: A validator on a field whose type takes no check now names why in the type error, in place of `… is not assignable to
133
+ type 'readonly never[]'`. `AvZ.oneOf([...])` on an enum field reads `Type 'AvZCheck<"string">' is not assignable to
134
+ type '"Error: an enum field takes no check: its enum already is the closed set of values it admits"'`. An enum list
135
+ says it takes only a list length (`AvZ.minItems`, `AvZ.maxItems`), and a boolean field or list says the same in its
136
+ own words.
137
+
138
+ - a9260d2: The agent skills cover what an agent needs without opening `node_modules`:
139
+
140
+ - The frontend skill (`avclient skill`) now explains what a call returns (`Date`, `bigint`, `AvDecimal`, `Uint8Array`), which error class each code throws (`AvTransportError` included), `error.cause.issues` for form errors, transactions with `$ref`, and a new `reference/client-setup.md`: `framework.client.ts` (`defineClientConfig`, `env`, `AVENTARA_API_URL`, `generateAt`), regenerating after the server changed, `avclient generate --yes` in CI, and the stale-client error (`A2005`, `AvContractMismatchError`). It also links the docs for agents.
141
+ - The server skill (`aventara skill`) adds `reference/avz.md` (every `AvZ` check and Json shape builder, `AvZ.infer`) and covers the `policy` and `access` keys, MCP token claims in `ctx.$state.mcp.claims`, the Next.js `state` example, `avDehydrate`/`avHydrate` and server transactions in Next.js, testing with `createFakeAdapter` (what `execute` receives and returns), switching the database or driver, `ctx.$sql`, `ctx.$transaction` and `ctx.$transport`, the compute stage types, and the `AVENTARA_PREBUILT_STALE` fallback.
142
+ - With `@aventara/access` installed, the skill now says to write Access's own skill with `avaccess skill` (`npx avaccess skill`, or `pnpm exec avaccess skill`).
143
+ - Corrected: a write-only field is written `"read"` everywhere; a field pipe or compute stage that returns `undefined` keeps the value; a read compute may be a plain function and gets `field`, `value`, `record` and `ctx`.
144
+ - `CONFIG_FORBIDDEN_IN_OVERLAY` in the error catalog now names `managedElsewhere` as well as a field `definition`.
145
+
146
+ ## 1.0.0-rc.12
147
+
148
+ ### Patch Changes
149
+
150
+ - fdeee8a: A unique field a layer may not filter on is no longer an identifier of that layer. Exempting its `filter` (a write-only
151
+ `apiKey: ["select", "filter", "order"]`, an unsearchable `email: ["filter"]`, or a Pick that leaves `filter` out) used
152
+ to keep the field in the Contract's `identifiers`, so `find.unique`, `update.unique`, `upsert.unique`, `delete.unique`
153
+ and a nested `$connect`/`$connectOrCreate` still looked a record up by it and answered the row or `A2003`, over every
154
+ route including `/_all` and `/_transactions`. Such a selector is now refused `V1007` before the Adapter is called, a
155
+ compound identifier goes with any one of its fields, and the generated client and the server façades no longer offer
156
+ it. A layer that keeps a unique variant with no filterable identifier left fails at startup with
157
+ `COMPILER_MISSING_IDENTIFIER`, as hiding the identifier already did: under `exposed: false`, pick `id: ["select",
158
+ "filter"]`, not `id: ["select"]`, to keep `find.unique`. The agent skill's Pick example says so.
159
+
3
160
  ## 1.0.0-rc.11
4
161
 
5
162
  ### 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.11 answers, reports in a validation issue, or refuses to start with. Generated from `AvErrorCatalog` (exported by `@aventara/core`); do not edit.
3
+ Every code `@aventara/core` 1.0.0-rc.13 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
 
@@ -31,7 +31,7 @@ The caller can fix these. The generated client and server-side calls throw the c
31
31
  | [`A2001`](https://aventara.razor34.dev/framework/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.razor34.dev/framework/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
33
  | [`A2003`](https://aventara.razor34.dev/framework/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
- | [`A2004`](https://aventara.razor34.dev/framework/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. |
34
+ | [`A2004`](https://aventara.razor34.dev/framework/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 every value the caller sent after its pipes and before write compute, a field with a compute stage included, so a bad value is refused even when compute would replace it; then a value write compute set or changed, 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.razor34.dev/framework/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.razor34.dev/framework/docs/error-codes#a2006) | Protocol mismatch. | Aventara-Protocol-Version is not 1. | Send `Aventara-Protocol-Version: 1`, or upgrade the client. |
37
37
  | [`A2007`](https://aventara.razor34.dev/framework/docs/error-codes#a2007) | Invalid transaction reference. | A transaction $ref or fingerprint is invalid: the issues are V1010, V1011, V1016, V1017 or V1018, and cause.operation is the step. A $ref with no fingerprint is A2004 / V1000. | Let the generated client or framework.appTx build plans; check the $ref path and that the referenced step comes earlier. |
@@ -61,8 +61,8 @@ Produced by your guards; the framework never authenticates by itself.
61
61
 
62
62
  | Code | Meaning | Cause | Fix |
63
63
  |---|---|---|---|
64
- | [`A4000`](https://aventara.razor34.dev/framework/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
- | [`A4001`](https://aventara.razor34.dev/framework/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. |
64
+ | [`A4000`](https://aventara.razor34.dev/framework/docs/error-codes#a4000) | Unauthenticated. | A guard threw new AvError("A4000", ...), or the host's or access provider's state rejected with an object whose status or statusCode is 401 (an authentication library's refusal, answered with this message only): authentication is required or failed. The framework never authenticates by itself. | Send the credentials your guards expect, such as the Authorization header. |
65
+ | [`A4001`](https://aventara.razor34.dev/framework/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, a stage threw A4001, or the host's or access provider's state rejected with an object whose status or statusCode is 403 (answered with this message only). | Call as a caller the guard admits, or stop sending the guarded field. To answer another code, throw an AvError from the guard. |
66
66
  | [`A4002`](https://aventara.razor34.dev/framework/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)
@@ -85,7 +85,7 @@ Each entry of `cause.issues[]` under `A2001`, `A2002`, `A2004`, `A2007` or `A200
85
85
  | [`V1011`](https://aventara.razor34.dev/framework/docs/error-codes#v1011) | Transaction reference type mismatch. | The referenced value's type does not fit its target, a nullable source where only a non-null value is accepted included. | Reference a field of the target's type, non-null where the target needs it. |
86
86
  | [`V1012`](https://aventara.razor34.dev/framework/docs/error-codes#v1012) | Invalid relation action/cardinality. | A nested write that does not fit the relation (a list where one record is expected), a reference field set together with its own relation, one record mixing reference fields and relation writes where the adapter cannot run both, or a record written through a relation that writes that relation (or a field holding its reference) back, where the adapter admits no such write. Also a to-one $update or $upsert beside $connect, $connectOrCreate, $create, $set or $disconnect on the same relation: the row it would update is the newly linked one. | Write either the relation or its reference field, in the relation's cardinality. Relink a to-one relation in one write and update the linked row in another. |
87
87
  | [`V1013`](https://aventara.razor34.dev/framework/docs/error-codes#v1013) | Maximum nesting depth exceeded. | Nested projections, filters or writes deeper than maxNestingDepth. | Nest less, or raise limits.maxNestingDepth. |
88
- | [`V1014`](https://aventara.razor34.dev/framework/docs/error-codes#v1014) | Configured limit exceeded. | A limit above maxListLimit, or more steps than maxTransactionOperations. | Paginate or split the plan, or raise the limit. |
88
+ | [`V1014`](https://aventara.razor34.dev/framework/docs/error-codes#v1014) | Configured limit exceeded. | A limit above maxListLimit, more steps than maxTransactionOperations, or more write steps than maxTransactionWrites. | Paginate or split the plan, or raise the limit. |
89
89
  | [`V1015`](https://aventara.razor34.dev/framework/docs/error-codes#v1015) | Boolean/filter node limit exceeded. | A where tree with more nodes than maxBooleanNodes. | Simplify the filter (in instead of many OR clauses), or raise limits.maxBooleanNodes. |
90
90
  | [`V1016`](https://aventara.razor34.dev/framework/docs/error-codes#v1016) | Invalid transaction reference index. | A $ref to the referencing step itself or a later one, or to an index no step has. | Reference an earlier step only. |
91
91
  | [`V1017`](https://aventara.razor34.dev/framework/docs/error-codes#v1017) | Transaction fingerprint mismatch. | A hand-written plan whose fingerprint does not match its step. | Let the generated client, framework.appTx or framework.clientTx build the plan. |
@@ -105,7 +105,7 @@ Each entry of `cause.issues[]` under `A2001`, `A2002`, `A2004`, `A2007` or `A200
105
105
  | [`CONFIG_INVALID_TYPE`](https://aventara.razor34.dev/framework/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
106
  | [`CONFIG_INVALID_VALUE`](https://aventara.razor34.dev/framework/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, or its unheld naming a Resource the data source's model does not have (the message names the Resource or the rule). | Use a value the message allows. |
107
107
  | [`CONFIG_MOVED_PROPERTY`](https://aventara.razor34.dev/framework/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
- | [`CONFIG_FORBIDDEN_IN_OVERLAY`](https://aventara.razor34.dev/framework/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. |
108
+ | [`CONFIG_FORBIDDEN_IN_OVERLAY`](https://aventara.razor34.dev/framework/docs/error-codes#config_forbidden_in_overlay) | A root-only key is written in an overlay. | A field definition, or a Resource's managedElsewhere, in application, client or agent. | State it once at the root (`resources.<R>.fields.<f>.definition`, `resources.<R>.managedElsewhere`); an overlay only chooses visibility. |
109
109
 
110
110
  ## Startup: discovery
111
111
 
@@ -135,8 +135,9 @@ Each entry of `cause.issues[]` under `A2001`, `A2002`, `A2004`, `A2007` or `A200
135
135
  | [`COMPILER_MISSING_READ_COMPUTE`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_missing_read_compute) | A selectable virtual field has no compute.read. | A VIRTUAL field that can be selected has no pipelines.compute.read stage. | Add a compute.read stage, or make the field write-only with capabilities.select: false. |
136
136
  | [`COMPILER_INVALID_COMPUTE_DEPENDENCY`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_invalid_compute_dependency) | Invalid compute.read dependency. | A dependsOn entry that is not a stored scalar field of the same Resource. | List only stored scalar fields of the Resource in dependsOn. |
137
137
  | [`COMPILER_RESERVED_RESOURCE_NAME`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_reserved_resource_name) | Reserved Resource name. | A model name starts with $, which is reserved for the framework's own names (ctx.$sql, $ref), or is the name of a member beside the Resources: all on serverClient (and so on Next.js's avClient), and on a host that adds its own, one of those (on Next.js: tx and transaction on avClient, appTx and transaction on avApp). | Rename the model; the table it maps to can keep its name. A host member's name is also left free when the layer it belongs to hides the Resource. |
138
- | [`COMPILER_MISSING_IDENTIFIER`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_missing_identifier) | A unique operation has no identifier. | A layer exposes a unique variant (find.unique, update.unique, delete.unique, upsert.unique) and no complete identifier of the Resource: a Pick that did not pick one, or an Omit that hid every one. | Expose an identifier field (id, or every field of one unique), or exempt the unique variants. |
138
+ | [`COMPILER_MISSING_IDENTIFIER`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_missing_identifier) | A unique operation has no identifier. | A layer exposes a unique variant (find.unique, update.unique, delete.unique, upsert.unique) and no complete identifier of the Resource it may filter on: a Pick that did not pick one with its filter, or an Omit that hid every one or its filter. | Expose an identifier field with its filter (id, or every field of one unique), or exempt the unique variants. |
139
139
  | [`COMPILER_HIDDEN_REQUIRED_FIELD`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_hidden_required_field) | A layer that creates a Resource hides a required field. | A layer still creates the Resource (a create or upsert variant, or a nested create) while a required field, or a required relation's reference field, is hidden or has its create capability exempted, and no compute.create fills it. | Expose the field, give it a compute.create stage, or exempt the layer's create operations. |
140
+ | [`COMPILER_READ_ONLY_NESTED_WRITE`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_read_only_nested_write) | A relation writes rows of a read-only Resource. | A layer with exposed: false grants a relation a write into a Resource it cannot write: $create, $connectOrCreate or $upsert into a Resource with no create or upsert operation and no field writable on create, $update or $upsert into one with no update or upsert operation and no field writable on update, or $delete into one with no delete operation. Read-only, by fields: "read" or by hand-listed capabilities, means no nested write either. Under exposed: true those directives are dropped from the relation instead, and startup succeeds. | Narrow the relation to the directives it may use (author: { select: true, create: ["$connect"] }), or give the target Resource the create, update or delete operation, or a field writable there. |
140
141
  | [`COMPILER_INVALID_VALIDATOR`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_invalid_validator) | A field validator cannot apply. | A validator that is not built with AvZ, does not fit the field's type (AvZ.maxLength on an int, AvZ.maxItems on a field that is not a list), or sits on a relation or Json field. | Use an AvZ check that fits the field's type; a Json field's checks go in its json shape. |
141
142
  | [`COMPILER_INVALID_JSON_SHAPE`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_invalid_json_shape) | A Json shape cannot apply. | A json shape on a field that is not a discovered Json field, a shape not built with AvZ, or one that admits null or absence at its top level. | Put the shape on a Json field, build it with AvZ, and keep its top level non-null (the column's nullability decides null). |
142
143
  | [`COMPILER_INVALID_CLIENT_CONTRACT`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_invalid_client_contract) | The compiled client contract is invalid. | The finished client contract failed its own structural check: a framework defect. | Report it with the diagnostic's path and message. |
package/README.md CHANGED
@@ -53,7 +53,8 @@ of generic JSON. So does a client generated with `avclient`, which emits the typ
53
53
 
54
54
  The shape is also one of `meta`'s validators. Every write of `meta` is checked against it before the database is
55
55
  asked: a create, update, upsert, nested write or transaction step, including a value a transaction step takes from an
56
- earlier step. It is checked where validators run, after every pipe and write compute and before any interceptor or hook, and its issues
56
+ earlier step. It is checked where validators run (a value you send after every pipe and before write compute, a
57
+ value write compute sets or changes after it) and before any interceptor or hook, and its issues
57
58
  come in field order with the other validators'. A value out of shape is refused with `A2004`, one issue per problem at its
58
59
  exact path:
59
60
 
@@ -396,7 +397,7 @@ answers the optional `grantsOf` is asked, before the write, which of its grants
396
397
  nothing the request writes), and gets the answer back on the after check as `beforeGrants`. Such an engine refuses
397
398
  an after check that carries `before` without `beforeGrants` rather than deciding `before` after the write.
398
399
  A typed literal in a fragment the engine answers may be in its application form (`Date`, `AvDecimal`, `bigint`) or its
399
- wire form (an ISO string, a decimal string, a digit string), as a policy read from JSON states it. Where `transactionsStyle` is `"interactive"`, a
400
+ wire form (an ISO string, a decimal string, a digit string), as a policy read from JSON states it. Where `transactionsStyle` is `"interactive"` or `"server"`, a
400
401
  checked write with a check after it (an update, a nested write) runs in a transaction of its own, and a create or
401
402
  delete checked before it runs is one statement; where it is `"none"`, a write that could only be checked after it runs
402
403
  is refused (for after conditions, the updates the engine lists in `afterChecks`).
@@ -37,6 +37,8 @@ export declare const minSize: (minimum: number) => AvZCheck<"bytes">;
37
37
  export declare const size: (exact: number) => AvZCheck<"bytes">;
38
38
  /** A string `pattern` matches (a JavaScript regular expression; its flags count). */
39
39
  export declare const regex: (pattern: RegExp) => AvZCheck<"string">;
40
+ /** A string equal to one of `values`, compared exactly. */
41
+ export declare const oneOf: <const Values extends readonly string[]>(values: Values & (Values extends readonly [] ? never : unknown)) => AvZCheck<"string">;
40
42
  /** A string containing `value`. */
41
43
  export declare const includes: (value: string) => AvZCheck<"string">;
42
44
  /** A string starting with `prefix`. */
@@ -68,6 +68,18 @@ export const regex = (pattern) => {
68
68
  pattern: { source: pattern.source, flags: pattern.flags },
69
69
  });
70
70
  };
71
+ const PATTERN_SYNTAX = /[\\^$.*+?()[\]{}|/]/g;
72
+ export const oneOf = (values) => {
73
+ if (!Array.isArray(values) || values.length === 0) {
74
+ throw new TypeError("AvZ.oneOf: give at least one value.");
75
+ }
76
+ const escaped = values.map((value) => text("oneOf", value).replace(PATTERN_SYNTAX, "\\$&"));
77
+ return build({
78
+ check: "string_format",
79
+ format: "regex",
80
+ pattern: { source: `^(?:${escaped.join("|")})$`, flags: "" },
81
+ });
82
+ };
71
83
  export const includes = (value) => build({
72
84
  check: "string_format",
73
85
  format: "includes",
package/dist/avz/avz.d.ts CHANGED
@@ -134,6 +134,12 @@ export declare class AvZ {
134
134
  static readonly size: (exact: number) => AvZCheck<"bytes">;
135
135
  /** A string the pattern matches: a JavaScript regular expression, flags included. */
136
136
  static readonly regex: (pattern: RegExp) => AvZCheck<"string">;
137
+ /**
138
+ * A string equal to one of `values`: an anchored pattern of them, each
139
+ * escaped, whose issue lists the values. The field's type stays `string`;
140
+ * for a closed set the types should name, use a database enum.
141
+ */
142
+ static readonly oneOf: <const Values extends readonly string[]>(values: Values & (Values extends readonly [] ? never : unknown)) => AvZCheck<"string">;
137
143
  /** A string containing `value`. */
138
144
  static readonly includes: (value: string) => AvZCheck<"string">;
139
145
  /** A string starting with `prefix`. */
package/dist/avz/avz.js CHANGED
@@ -323,6 +323,7 @@ export class AvZ {
323
323
  static minSize = checks.minSize;
324
324
  static size = checks.size;
325
325
  static regex = checks.regex;
326
+ static oneOf = checks.oneOf;
326
327
  static includes = checks.includes;
327
328
  static startsWith = checks.startsWith;
328
329
  static endsWith = checks.endsWith;
@@ -7,8 +7,9 @@ export type AvContractCompilerDiagnosticCode = "COMPILER_UNKNOWN_RESOURCE" | "CO
7
7
  | "COMPILER_RESERVED_RESOURCE_NAME"
8
8
  /**
9
9
  * A layer exposes a unique operation variant (`find.unique`, `update.unique`,
10
- * `delete.unique`, `upsert.unique`) and no complete identifier of its Resource:
11
- * a Pick that did not pick one, or an Omit that hid every one.
10
+ * `delete.unique`, `upsert.unique`) and no complete identifier of its Resource
11
+ * it may filter on: a Pick that did not pick one with its filter, or an Omit
12
+ * that hid every one or its filter.
12
13
  */
13
14
  | "COMPILER_MISSING_IDENTIFIER"
14
15
  /**
@@ -19,6 +20,16 @@ export type AvContractCompilerDiagnosticCode = "COMPILER_UNKNOWN_RESOURCE" | "CO
19
20
  * not writable on create with a reference field neither writable nor computed.
20
21
  */
21
22
  | "COMPILER_HIDDEN_REQUIRED_FIELD"
23
+ /**
24
+ * A Pick layer lets a relation write rows of a Resource it cannot write: a
25
+ * nested `$create`, `$connectOrCreate` or `$upsert` into a Resource with no
26
+ * create or upsert variant and no field writable on create, a nested
27
+ * `$update` or `$upsert` into one with no update or upsert variant and no
28
+ * field writable on update, or a nested `$delete` into one with no delete
29
+ * variant (an Omit layer drops those directives instead). `path` is the
30
+ * relation's exemption.
31
+ */
32
+ | "COMPILER_READ_ONLY_NESTED_WRITE"
22
33
  /**
23
34
  * A field validator cannot apply: it is not a check built with `AvZ`, it does
24
35
  * not fit the field's type (`AvZ.maxLength` on an `int` field, `AvZ.maxItems`
@@ -71,14 +71,19 @@ export const CONTRACT_COMPILER_DIAGNOSTIC_GUIDANCE = {
71
71
  },
72
72
  COMPILER_MISSING_IDENTIFIER: {
73
73
  message: "A unique operation has no identifier.",
74
- cause: "A layer exposes a unique variant (find.unique, update.unique, delete.unique, upsert.unique) and no complete identifier of the Resource: a Pick that did not pick one, or an Omit that hid every one.",
75
- fix: "Expose an identifier field (id, or every field of one unique), or exempt the unique variants.",
74
+ cause: "A layer exposes a unique variant (find.unique, update.unique, delete.unique, upsert.unique) and no complete identifier of the Resource it may filter on: a Pick that did not pick one with its filter, or an Omit that hid every one or its filter.",
75
+ fix: "Expose an identifier field with its filter (id, or every field of one unique), or exempt the unique variants.",
76
76
  },
77
77
  COMPILER_HIDDEN_REQUIRED_FIELD: {
78
78
  message: "A layer that creates a Resource hides a required field.",
79
79
  cause: "A layer still creates the Resource (a create or upsert variant, or a nested create) while a required field, or a required relation's reference field, is hidden or has its create capability exempted, and no compute.create fills it.",
80
80
  fix: "Expose the field, give it a compute.create stage, or exempt the layer's create operations.",
81
81
  },
82
+ COMPILER_READ_ONLY_NESTED_WRITE: {
83
+ message: "A relation writes rows of a read-only Resource.",
84
+ cause: 'A layer with exposed: false grants a relation a write into a Resource it cannot write: $create, $connectOrCreate or $upsert into a Resource with no create or upsert operation and no field writable on create, $update or $upsert into one with no update or upsert operation and no field writable on update, or $delete into one with no delete operation. Read-only, by fields: "read" or by hand-listed capabilities, means no nested write either. Under exposed: true those directives are dropped from the relation instead, and startup succeeds.',
85
+ fix: 'Narrow the relation to the directives it may use (author: { select: true, create: ["$connect"] }), or give the target Resource the create, update or delete operation, or a field writable there.',
86
+ },
82
87
  COMPILER_INVALID_VALIDATOR: {
83
88
  message: "A field validator cannot apply.",
84
89
  cause: "A validator that is not built with AvZ, does not fit the field's type (AvZ.maxLength on an int, AvZ.maxItems on a field that is not a list), or sits on a relation or Json field.",