@aventara/core 1.0.0-rc.0 → 1.0.0-rc.10
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 +2068 -1
- package/ERRORS.md +142 -0
- package/README.md +485 -60
- package/dist/adapters/adapter-discovery-diagnostic.d.ts +4 -4
- package/dist/adapters/adapter-discovery-diagnostic.guidance.d.ts +6 -0
- package/dist/adapters/adapter-discovery-diagnostic.guidance.js +7 -0
- package/dist/adapters/adapter-field.d.ts +33 -11
- package/dist/adapters/adapter-identifier.d.ts +15 -6
- package/dist/adapters/adapter-identifier.js +6 -1
- package/dist/adapters/adapter-model.d.ts +18 -9
- package/dist/adapters/adapter-operation-executor.interface.d.ts +4 -4
- package/dist/adapters/adapter-operation.d.ts +21 -6
- package/dist/adapters/adapter-resource.d.ts +23 -9
- package/dist/adapters/adapter-sql-statement.d.ts +19 -0
- package/dist/adapters/adapter-transaction-executor.interface.d.ts +19 -0
- package/dist/adapters/adapter.interface.d.ts +59 -29
- package/dist/avz/avz-check.factory.d.ts +83 -0
- package/dist/avz/avz-check.factory.js +116 -0
- package/dist/avz/avz-check.registry.d.ts +35 -0
- package/dist/avz/avz-check.registry.js +9 -0
- package/dist/avz/avz.d.ts +203 -0
- package/dist/{json → avz}/avz.js +150 -8
- package/dist/{json → avz}/index.d.ts +2 -1
- package/dist/{json → avz}/json-shape.registry.d.ts +9 -8
- package/dist/{json → avz}/zod-issue.translator.d.ts +8 -2
- package/dist/{json → avz}/zod-issue.translator.js +7 -0
- package/dist/compiler/contract-compiler-diagnostic.d.ts +31 -5
- package/dist/compiler/contract-compiler-diagnostic.guidance.d.ts +6 -0
- package/dist/compiler/contract-compiler-diagnostic.guidance.js +97 -0
- package/dist/compiler/contract.compiler.d.ts +23 -17
- package/dist/compiler/contract.compiler.js +753 -443
- package/dist/compiler/create-requirements.projector.d.ts +49 -0
- package/dist/compiler/create-requirements.projector.js +423 -0
- package/dist/compiler/field-validator.compiler.d.ts +47 -0
- package/dist/compiler/field-validator.compiler.js +238 -0
- package/dist/compiler/managed-elsewhere.projector.d.ts +52 -0
- package/dist/compiler/managed-elsewhere.projector.js +179 -0
- package/dist/compiler/managed-elsewhere.validator.d.ts +9 -0
- package/dist/compiler/managed-elsewhere.validator.js +74 -0
- package/dist/config/any-framework-config.d.ts +19 -12
- package/dist/config/computed-fields.d.ts +62 -0
- package/dist/config/computed-fields.js +73 -0
- package/dist/config/config-diagnostic.d.ts +11 -4
- package/dist/config/config-diagnostic.guidance.d.ts +6 -0
- package/dist/config/config-diagnostic.guidance.js +32 -0
- package/dist/config/config-layer.d.ts +15 -6
- package/dist/config/config.resolver.d.ts +98 -31
- package/dist/config/config.resolver.js +173 -44
- package/dist/config/config.validator.d.ts +7 -7
- package/dist/config/config.validator.js +792 -69
- package/dist/config/define-resource.d.ts +53 -0
- package/dist/config/define-resource.js +14 -0
- package/dist/config/effective-fields.d.ts +19 -23
- package/dist/config/entrypoint.normalizer.d.ts +4 -3
- package/dist/config/exact-framework-config.d.ts +325 -0
- package/dist/config/field-compute.resolver.d.ts +21 -0
- package/dist/config/field-compute.resolver.js +123 -0
- package/dist/config/field-pipeline-config.d.ts +47 -0
- package/dist/config/forced-operation.validator.d.ts +10 -0
- package/dist/config/forced-operation.validator.js +66 -0
- package/dist/config/framework-config.d.ts +148 -39
- package/dist/config/framework-exemptions-config.d.ts +91 -0
- package/dist/config/framework-exemptions-config.js +1 -0
- package/dist/config/framework-field-definition.d.ts +78 -0
- package/dist/config/framework-field-definition.js +28 -0
- package/dist/config/framework-resources-config.d.ts +127 -0
- package/dist/config/framework-resources-config.js +1 -0
- package/dist/config/pipeline-config.d.ts +56 -20
- package/dist/config/pipeline-tree.resolver.d.ts +79 -0
- package/dist/config/pipeline-tree.resolver.js +220 -0
- package/dist/config/stage-type.eraser.d.ts +65 -0
- package/dist/config/stage-type.eraser.js +1 -0
- package/dist/contracts/access-route.rules.d.ts +10 -0
- package/dist/contracts/access-route.rules.js +45 -0
- package/dist/contracts/capability.d.ts +37 -12
- package/dist/contracts/capability.js +20 -0
- package/dist/contracts/client-contract-structure.validator.d.ts +621 -114
- package/dist/contracts/client-contract-structure.validator.js +189 -10
- package/dist/contracts/contract-access.d.ts +29 -0
- package/dist/contracts/contract-access.js +1 -0
- package/dist/contracts/contract-canonicalizer.d.ts +27 -3
- package/dist/contracts/contract-canonicalizer.js +168 -29
- package/dist/contracts/contract-hash.d.ts +11 -7
- package/dist/contracts/contract-hash.js +4 -3
- package/dist/contracts/contract-limits.d.ts +4 -1
- package/dist/contracts/contract-limits.js +1 -0
- package/dist/contracts/contract-lookup.d.ts +5 -5
- package/dist/contracts/contract-protocol.d.ts +4 -4
- package/dist/contracts/contract.d.ts +51 -17
- package/dist/contracts/create-requirements.d.ts +35 -0
- package/dist/contracts/create-requirements.js +1 -0
- package/dist/contracts/enum-definition.d.ts +1 -1
- package/dist/contracts/field-check.d.ts +101 -0
- package/dist/contracts/field-check.js +1 -0
- package/dist/contracts/field.d.ts +109 -42
- package/dist/contracts/identifier.d.ts +4 -4
- package/dist/contracts/json-schema.projector.d.ts +11 -0
- package/dist/contracts/json-schema.projector.js +78 -0
- package/dist/contracts/json-shape.d.ts +12 -1
- package/dist/contracts/lifecycle-trait.d.ts +2 -2
- package/dist/contracts/resource-operations.d.ts +28 -28
- package/dist/contracts/resource.d.ts +15 -9
- package/dist/contracts/scalar-value-type.d.ts +17 -15
- package/dist/contracts/scalar.d.ts +3 -3
- package/dist/contracts/transaction-capability.d.ts +1 -1
- package/dist/contracts/wire-scalar-format.d.ts +4 -4
- package/dist/framework/access-facade.d.ts +116 -0
- package/dist/framework/access-facade.js +120 -0
- package/dist/framework/contract-view.d.ts +315 -145
- package/dist/framework/framework-construction.error.d.ts +21 -11
- package/dist/framework/framework-construction.error.js +20 -4
- package/dist/framework/framework.d.ts +71 -26
- package/dist/framework/framework.factory.d.ts +8 -6
- package/dist/framework/framework.factory.js +150 -38
- package/dist/framework/operation-facade.d.ts +192 -54
- package/dist/framework/operation-facade.js +10 -1
- package/dist/index.d.ts +67 -50
- package/dist/index.js +7 -6
- package/dist/internal.d.ts +20 -10
- package/dist/internal.js +9 -1
- package/dist/operations/database-refusal.d.ts +51 -0
- package/dist/operations/database-refusal.js +45 -0
- package/dist/operations/operation-arguments.d.ts +381 -228
- package/dist/operations/operation-call.d.ts +31 -28
- package/dist/operations/operation-identity.d.ts +10 -10
- package/dist/operations/operation-result.d.ts +25 -23
- package/dist/operations/operation-validator-core.d.ts +6 -6
- package/dist/operations/operation-validator-core.js +172 -38
- package/dist/operations/operation-validator.d.ts +10 -10
- package/dist/operations/projection-scope.resolver.d.ts +6 -6
- package/dist/operations/structural-limits.walker.d.ts +7 -7
- package/dist/operations/structural-limits.walker.js +145 -0
- package/dist/operations/validation-code.d.ts +11 -6
- package/dist/operations/validation-code.guidance.d.ts +6 -0
- package/dist/operations/validation-code.guidance.js +90 -0
- package/dist/operations/validation-code.js +4 -0
- package/dist/operations/write-scope.resolver.d.ts +10 -6
- package/dist/operations/write-scope.resolver.js +2 -0
- package/dist/policy/access-provider.interface.d.ts +91 -0
- package/dist/policy/access-provider.interface.js +1 -0
- package/dist/policy/access-provider.registry.d.ts +17 -0
- package/dist/policy/access-provider.registry.js +15 -0
- package/dist/policy/access-question.d.ts +20 -0
- package/dist/policy/access-question.js +1 -0
- package/dist/policy/access-question.registry.d.ts +5 -0
- package/dist/policy/access-question.registry.js +19 -0
- package/dist/policy/access-question.runtime.d.ts +36 -0
- package/dist/policy/access-question.runtime.js +297 -0
- package/dist/policy/access-verb.table.d.ts +56 -0
- package/dist/policy/access-verb.table.js +21 -0
- package/dist/policy/access-verbs.d.ts +46 -0
- package/dist/policy/access-verbs.js +1 -0
- package/dist/policy/policy-admission.d.ts +20 -0
- package/dist/policy/policy-admission.js +409 -0
- package/dist/policy/policy-after-update.runtime.d.ts +79 -0
- package/dist/policy/policy-after-update.runtime.js +208 -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 +205 -0
- package/dist/policy/policy-engine.js +1 -0
- package/dist/policy/policy-named-rows.d.ts +18 -0
- package/dist/policy/policy-named-rows.js +51 -0
- package/dist/policy/policy-nested-write.runtime.d.ts +44 -0
- package/dist/policy/policy-nested-write.runtime.js +545 -0
- package/dist/policy/policy-read.runtime.d.ts +81 -0
- package/dist/policy/policy-read.runtime.js +503 -0
- package/dist/policy/policy-reference-write.runtime.d.ts +51 -0
- package/dist/policy/policy-reference-write.runtime.js +345 -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 +106 -0
- package/dist/policy/policy-session.js +295 -0
- package/dist/policy/policy-startup.validator.d.ts +10 -0
- package/dist/policy/policy-startup.validator.js +112 -0
- package/dist/policy/policy-write.runtime.d.ts +39 -0
- package/dist/policy/policy-write.runtime.js +532 -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 +29 -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 +19 -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 +229 -0
- package/dist/protocol/absent-route.responder.d.ts +17 -34
- package/dist/protocol/absent-route.responder.js +53 -20
- package/dist/protocol/contract-document.responder.d.ts +5 -4
- package/dist/protocol/contract-document.responder.js +2 -2
- package/dist/protocol/http-request.decoder.d.ts +33 -13
- package/dist/protocol/http-request.decoder.js +34 -11
- package/dist/protocol/http-request.reader.d.ts +9 -6
- package/dist/protocol/http-request.reader.js +1 -0
- package/dist/protocol/http-response.encoder.d.ts +4 -4
- package/dist/protocol/http-response.encoder.js +94 -55
- package/dist/protocol/http-status.table.d.ts +2 -2
- package/dist/protocol/operation-response.guard.d.ts +2 -2
- package/dist/protocol/protocol-headers.d.ts +3 -3
- package/dist/protocol/protocol-surface.d.ts +10 -5
- package/dist/protocol/protocol-surface.js +7 -0
- package/dist/protocol/protocol.binder.d.ts +66 -28
- package/dist/protocol/protocol.binder.js +16 -10
- package/dist/protocol/protocol.handler.d.ts +34 -14
- package/dist/protocol/protocol.handler.js +122 -18
- package/dist/protocol/unmounted-route.error.d.ts +4 -4
- package/dist/protocol/unmounted-route.error.js +2 -2
- package/dist/runtime/caller-data.copier.d.ts +22 -7
- package/dist/runtime/caller-data.copier.js +30 -17
- package/dist/runtime/caller-data.door.d.ts +18 -11
- package/dist/runtime/caller-data.door.js +6 -4
- package/dist/runtime/computed-read-runtime.d.ts +12 -6
- package/dist/runtime/computed-read-runtime.js +271 -71
- package/dist/runtime/computed-value.error.d.ts +7 -6
- package/dist/runtime/computed-value.error.js +5 -4
- package/dist/runtime/context-executor.runtime.d.ts +60 -0
- package/dist/runtime/context-executor.runtime.js +277 -0
- package/dist/runtime/core-class.brand.d.ts +10 -0
- package/dist/runtime/core-class.brand.js +21 -0
- package/dist/runtime/create-requirement.d.ts +34 -79
- package/dist/runtime/create-requirement.js +205 -74
- package/dist/runtime/decimal.d.ts +16 -2
- package/dist/runtime/decimal.js +22 -1
- package/dist/runtime/diagnostics-reporter.d.ts +91 -30
- package/dist/runtime/diagnostics-reporter.js +37 -1
- package/dist/runtime/error-boundary.d.ts +73 -9
- package/dist/runtime/error-boundary.js +57 -19
- package/dist/runtime/error-guidance.d.ts +12 -0
- package/dist/runtime/error-guidance.js +1 -0
- package/dist/runtime/error.catalog.d.ts +49 -0
- package/dist/runtime/error.catalog.js +49 -0
- package/dist/runtime/execution-options.d.ts +18 -2
- package/dist/runtime/field-check.evaluator.d.ts +27 -0
- package/dist/runtime/field-check.evaluator.js +521 -0
- package/dist/runtime/field-compute.d.ts +65 -0
- package/dist/runtime/field-compute.js +1 -0
- package/dist/runtime/field-stage.plan.d.ts +15 -0
- package/dist/runtime/field-stage.plan.js +27 -0
- package/dist/runtime/field-stage.runtime.d.ts +37 -0
- package/dist/runtime/field-stage.runtime.js +147 -0
- package/dist/runtime/field-validator.runtime.d.ts +29 -0
- package/dist/runtime/field-validator.runtime.js +126 -0
- package/dist/runtime/framework-error-class.table.d.ts +33 -0
- package/dist/runtime/framework-error-class.table.js +25 -0
- package/dist/runtime/framework-error.d.ts +68 -8
- package/dist/runtime/framework-error.factory.d.ts +4 -0
- package/dist/runtime/framework-error.factory.js +15 -0
- package/dist/runtime/framework-error.js +32 -2
- package/dist/runtime/framework-warning.emitter.d.ts +6 -0
- package/dist/runtime/framework-warning.emitter.js +8 -0
- package/dist/runtime/interceptor-next.error.d.ts +5 -0
- package/dist/runtime/interceptor-next.error.js +6 -0
- package/dist/runtime/json-value.d.ts +11 -6
- package/dist/runtime/level-lifecycle.runtime.d.ts +26 -0
- package/dist/runtime/level-lifecycle.runtime.js +40 -0
- package/dist/runtime/misuse-reporter.d.ts +12 -12
- package/dist/runtime/mutation-planner.d.ts +20 -16
- package/dist/runtime/mutation-planner.js +48 -35
- package/dist/runtime/nested-write.walker.d.ts +32 -0
- package/dist/runtime/nested-write.walker.js +73 -0
- package/dist/runtime/operation-code.guidance.d.ts +6 -0
- package/dist/runtime/operation-code.guidance.js +167 -0
- package/dist/runtime/operation-request.d.ts +45 -11
- package/dist/runtime/operation-response.d.ts +17 -17
- package/dist/runtime/operation-runtime.d.ts +133 -27
- package/dist/runtime/operation-runtime.js +576 -84
- package/dist/runtime/outcome-mapper.d.ts +17 -5
- package/dist/runtime/outcome-mapper.js +23 -6
- package/dist/runtime/output-validator.d.ts +11 -11
- package/dist/runtime/output-validator.js +3 -3
- package/dist/runtime/owned-execution.registry.d.ts +16 -6
- package/dist/runtime/pipe-output.validator.d.ts +43 -18
- package/dist/runtime/pipe-output.validator.js +15 -13
- package/dist/runtime/pipeline.d.ts +221 -34
- package/dist/runtime/readonly-value.d.ts +47 -0
- package/dist/runtime/readonly-value.js +189 -0
- package/dist/runtime/referential-action.d.ts +1 -1
- package/dist/runtime/request-envelope.validator.d.ts +3 -3
- package/dist/runtime/request-envelope.validator.js +12 -7
- package/dist/runtime/request-id.d.ts +1 -1
- package/dist/runtime/request-state.d.ts +14 -0
- package/dist/runtime/request-state.js +1 -0
- package/dist/runtime/request-state.resolver.d.ts +12 -0
- package/dist/runtime/request-state.resolver.js +20 -0
- package/dist/runtime/scalar-runtime.d.ts +33 -25
- package/dist/runtime/scalar-runtime.js +105 -38
- package/dist/runtime/unsent-value.normalizer.d.ts +15 -0
- package/dist/runtime/unsent-value.normalizer.js +128 -0
- package/dist/transactions/deferred-operation.d.ts +35 -20
- package/dist/transactions/operation-arguments-encoder.d.ts +3 -3
- package/dist/transactions/operation-arguments-encoder.js +8 -3
- package/dist/transactions/operation-fingerprint.js +2 -2
- package/dist/transactions/sql-statement.d.ts +85 -0
- package/dist/transactions/sql-statement.js +47 -0
- package/dist/transactions/sql-statement.validator.d.ts +44 -0
- package/dist/transactions/sql-statement.validator.js +167 -0
- package/dist/transactions/transaction-envelope.d.ts +9 -8
- package/dist/transactions/transaction-integrity-error.d.ts +6 -6
- package/dist/transactions/transaction-integrity-error.js +7 -2
- package/dist/transactions/transaction-plan-assembler.d.ts +30 -13
- package/dist/transactions/transaction-plan-assembler.js +184 -32
- package/dist/transactions/transaction-plan-validator.d.ts +50 -25
- package/dist/transactions/transaction-plan-validator.js +96 -38
- package/dist/transactions/transaction-reference-collector.d.ts +5 -5
- package/dist/transactions/transaction-reference-resolver.d.ts +21 -3
- package/dist/transactions/transaction-reference-resolver.js +26 -14
- package/dist/transactions/transaction-reference-validator.d.ts +74 -4
- package/dist/transactions/transaction-reference-validator.js +129 -71
- package/dist/transactions/transaction-reference.d.ts +14 -14
- package/dist/transactions/transaction-reference.js +5 -0
- package/dist/transactions/transaction-request.d.ts +22 -9
- package/dist/transactions/transaction-response.d.ts +6 -6
- package/dist/transactions/transaction-runtime.d.ts +12 -8
- package/dist/transactions/transaction-runtime.js +218 -50
- package/dist/transactions/transaction-settlement.runtime.d.ts +52 -0
- package/dist/transactions/transaction-settlement.runtime.js +120 -0
- package/dist/transactions/transaction-slot-path-error.d.ts +4 -4
- package/dist/transactions/transaction-slot-path-error.js +4 -2
- package/dist/transactions/transaction-slot-writer.js +2 -2
- package/dist/transactions/transaction-stage.runtime.d.ts +83 -0
- package/dist/transactions/transaction-stage.runtime.js +126 -0
- package/package.json +11 -9
- package/src/runtime/field-check.evaluator.ts +720 -0
- package/dist/config/define-fields.d.ts +0 -42
- package/dist/config/define-fields.js +0 -3
- package/dist/config/framework-behaviors-config.d.ts +0 -30
- package/dist/config/framework-fields-config.d.ts +0 -42
- package/dist/config/framework-restrictions-config.d.ts +0 -142
- package/dist/json/avz.d.ts +0 -96
- package/dist/runtime/field-behavior.d.ts +0 -48
- /package/dist/{config/framework-behaviors-config.js → adapters/adapter-sql-statement.js} +0 -0
- /package/dist/{config/framework-fields-config.js → adapters/adapter-transaction-executor.interface.js} +0 -0
- /package/dist/{json → avz}/index.js +0 -0
- /package/dist/{json → avz}/json-shape.registry.js +0 -0
- /package/dist/config/{framework-restrictions-config.js → exact-framework-config.js} +0 -0
- /package/dist/{runtime/field-behavior.js → config/field-pipeline-config.js} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,2071 @@
|
|
|
1
1
|
# @aventara/core
|
|
2
2
|
|
|
3
|
+
## 1.0.0-rc.10
|
|
4
|
+
|
|
5
|
+
### Security
|
|
6
|
+
|
|
7
|
+
- A nested to-one relink could bypass a policy's update scope. In rc.9 and earlier, a to-one `$update` or `$upsert`
|
|
8
|
+
beside `$connect`, `$connectOrCreate`, `$create`, `$set` or `$disconnect` on the same relation ran the relink first
|
|
9
|
+
and then wrote the newly linked row, while the update scope, field grants, `grantsOf` and the after check had been
|
|
10
|
+
asked about the row linked before the call. A caller whose read scope reached a row its update scope did not could
|
|
11
|
+
change that row (`Post.update.unique({ data: { author: { $connect: { id }, $update: { data } } } })`). rc.10 refuses
|
|
12
|
+
the combination for every caller (`A2004`, one `V1012` issue at the relation) before anything runs, and the argument
|
|
13
|
+
types reject it. Upgrade if a policy engine guards updates through to-one relations; to relink and update, use two
|
|
14
|
+
writes. Details under Patch Changes below.
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- 587dd01: With `access` configured, the protocol serves `POST {api}/_access/can`: a page asks the policy engine about all of its
|
|
19
|
+
buttons in one request and gets one boolean per question, in order.
|
|
20
|
+
|
|
21
|
+
```http
|
|
22
|
+
POST /api/_access/can
|
|
23
|
+
{ "questions": [
|
|
24
|
+
{ "resource": "Order", "action": "update", "where": { "id": 41 } },
|
|
25
|
+
{ "resource": "Refund", "action": "create" }
|
|
26
|
+
] }
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
200 { "code": "A1000", "data": [true, false], "cause": null }
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
- Each answer is what the call the question stands for would do before any data is sent, under the principal
|
|
34
|
+
`ctx.$state.me` (seeded by the provider's `state`) and the origin `client`:
|
|
35
|
+
- an operation the Client Contract does not offer (with `where`, the action's unique variant) answers `false`;
|
|
36
|
+
- a question without `where` is the operation-level `policy.can`;
|
|
37
|
+
- a question with `where` (the Resource's unique where) is that unique call's policy admission (`can` without a
|
|
38
|
+
row, then every field it names against `fieldsOf`), then one `exists` for the row within its scopes: the read
|
|
39
|
+
scope, and for an update or delete the action's scope too. A row out of scope answers `false` exactly as a
|
|
40
|
+
missing row does, and no value of the row is read or handed to the engine;
|
|
41
|
+
- an update question answers `false` where the real update is refused before it runs: an update the engine lists
|
|
42
|
+
in `afterChecks` on a database without interactive transactions;
|
|
43
|
+
- an `AvError` the engine throws with a refusal code and a well-formed cause answers `false` for its question;
|
|
44
|
+
anything else it throws is `500 A3000`.
|
|
45
|
+
- The answer is about the row and the action, not about what a page reads of the row: it is the call selecting the
|
|
46
|
+
Resource's identifier. A page's own call that selects a field readable on some rows only is checked on its own.
|
|
47
|
+
- The answer only shapes a UI: the real call is still checked, and the route never answers `403`. It refuses malformed
|
|
48
|
+
input only, with codes the other routes already use: `404 A2002` (`V1006` at `["questions"]`) without `access`,
|
|
49
|
+
`405 A2012`, `400 A2000`, `409 A2005`, `400 A2006`, `404 A2001` for an unknown or client-hidden Resource,
|
|
50
|
+
`422 A2004` with each issue at `["questions", i, …]` (a `create` question with `where` included), `422 A2009`
|
|
51
|
+
(`V1014`) for more questions than the Client Contract's `maxListLimit`, and `500 A3000` when the engine fails
|
|
52
|
+
(`A3001` when the adapter fails under it). Questions are answered at most eight at a time, and
|
|
53
|
+
`questions: []` answers `200` with `[]`.
|
|
54
|
+
- `AvProtocol.bind(framework).handleCan(request, { state })` answers it, and `protocol.surface()` lists it as
|
|
55
|
+
`{ kind: "can", method: "POST", path: "/_access/can" }`, last, exactly when the route exists. An agent binding has
|
|
56
|
+
no `can` route.
|
|
57
|
+
- The Client Contract carries an `access` section exactly when `access` is configured (`{}` for now), so the
|
|
58
|
+
Contract hash changes only for apps that configure `access`. `validateClientContractStructure` accepts it.
|
|
59
|
+
- A failure while answering reaches the `diagnostics` sink as a new kind, `AvCanDiagnostic`
|
|
60
|
+
(`{ kind: "can", error, code, origin: "client", requestId }`). When one question met it, it also names that
|
|
61
|
+
question's index (`question`), `resource` and `action`; a failure of the whole request (the state seed, say) names
|
|
62
|
+
none.
|
|
63
|
+
- New main-entry types: `AvAccessQuestion` (one question, generic over the Resource names and their unique where),
|
|
64
|
+
`AvContractClientAccess` (the section) and `AvCanDiagnostic`.
|
|
65
|
+
|
|
66
|
+
- d7e7369: `$access` on `framework.serverClient`, and the access provider's optional `client`.
|
|
67
|
+
|
|
68
|
+
- `AvAccessProvider.client?: { url, verbs }` states where the app mounted Aventara Access and the verbs it offers:
|
|
69
|
+
`signIn`, `signUp`, `signOut`, `resetPassword` and `me`, each optional (a `service` profile has no `signIn` or
|
|
70
|
+
`signUp`). Each verb is `{ method, path, args?, result, call }`: its route, the JSON Schema of its arguments and
|
|
71
|
+
result, and `call`, the verb run in process with `{ args, headers, request?, response? }`. `url` must be an
|
|
72
|
+
absolute `http(s)` URL or a root-relative path; each verb is checked at startup (`access.client.*`).
|
|
73
|
+
- The Client Contract's `access` section carries `url` and the stated verbs, each `call` dropped, its schemas checked
|
|
74
|
+
against the `json` field subset. Apps without `access`, or whose provider states no `client`, keep their hash. The
|
|
75
|
+
prebuilt fingerprint reads the `client`'s data, never its `call`.
|
|
76
|
+
- `framework.serverClient.$access` exists exactly when `access` is configured (a type error otherwise): `can(questions,
|
|
77
|
+
options?)` is answered in process by the `/_access/can` route's own answerer, under the call's `state`, a
|
|
78
|
+
question's `where` read in the application forms; `me()` and the other verbs the provider offers run its `call`
|
|
79
|
+
with the `headers` you pass (a Web `Headers` or a Node request's `headers`) and, when given, the host's `request`
|
|
80
|
+
and `response` (Access sets a sign-in's cookie on the response under Nest). `me()` resolves the token identity, or
|
|
81
|
+
`null`. A provider without `client` offers `can` alone.
|
|
82
|
+
- New main-entry type `AvAccessVerbs`: `$access`'s type on every client.
|
|
83
|
+
- `validateClientContractStructure` holds the section's `url` (absolute `http(s)` or root-relative, never `//host`) and
|
|
84
|
+
each verb's `path` (one leading `/`) to the same rule the configuration is held to, so a consumer refuses a served
|
|
85
|
+
ClientContract that points elsewhere.
|
|
86
|
+
- `serverClient.$access.can` called with `headers` and no `state` is answered under the state the provider seeds from
|
|
87
|
+
those headers; with neither, anonymously.
|
|
88
|
+
|
|
89
|
+
- 7ee547b: `AvAccessProvider.client.verbs` takes Aventara Access's `kiosk` profile verbs too: `switchUser`, `lock`, `setPin`,
|
|
90
|
+
`enrollDevice` and `revokeDevice`, each optional and shaped as the other verbs (`{ method, path, args?, result, call }`); any
|
|
91
|
+
other name is still refused at startup. They reach the Client Contract's `access` section only when the provider
|
|
92
|
+
states them, so an app without them keeps its hash. `framework.serverClient.$access` runs each through its `call` in
|
|
93
|
+
process, with the headers, request and response you pass (Access sets the rotated device cookie on the response under
|
|
94
|
+
Nest): `switchUser(args)` and `enrollDevice(args)` resolve Access's answer; `lock()`, `setPin(args)` and
|
|
95
|
+
`revokeDevice(args)` resolve nothing. `AvAccessVerbs` gains the five members, and its `Args` names `switchUser`,
|
|
96
|
+
`setPin`, `enrollDevice` and `revokeDevice`. The MCP agent surface offers none of them.
|
|
97
|
+
|
|
98
|
+
The `access` section's `url` and each verb's `path` now also refuse U+0000–U+0020 and DEL, `.`/`..` segments
|
|
99
|
+
(percent-encoded too) and a bare `"/"` url, in the configuration and in every ClientContract read: a URL parser drops
|
|
100
|
+
tab, LF and CR, so `"/\t/evil.example"` resolved to another host. `serverClient.$access` now projects every verb's
|
|
101
|
+
answer (and `me`'s identity) onto its result schema, as the generated client and MCP `me` do; an answer the schema
|
|
102
|
+
cannot describe throws.
|
|
103
|
+
|
|
104
|
+
- f52c499: A model whose table another service owns can be marked at the root: `resources.<R>.managedElsewhere: true`. Aventara
|
|
105
|
+
reads it and joins through it, and no standard operation writes it.
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
createFramework({
|
|
109
|
+
api: "/api",
|
|
110
|
+
adapter,
|
|
111
|
+
resources: { Account: { managedElsewhere: true } },
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- No `create`, `update`, `delete` or `upsert` on it in any layer, and no field write. A call is a type error, and an
|
|
116
|
+
untyped one is `A2002`.
|
|
117
|
+
- A relation into it keeps only `$connect`/`$disconnect`, and only where the source holds the key; a nested
|
|
118
|
+
`$create`/`$update`/`$upsert` into it is a type error and `A2004` (`V1006`).
|
|
119
|
+
- The client and agent Contracts leave it out, with every relation into it. `framework.application` reads and joins it.
|
|
120
|
+
- A stage on one of its write families is refused at startup (`COMPILER_UNAVAILABLE_OPERATION`); the key in an overlay
|
|
121
|
+
is `CONFIG_FORBIDDEN_IN_OVERLAY`.
|
|
122
|
+
- The Core Contract marks it `managedElsewhere: true` and states each Resource's database `schema` when the adapter
|
|
123
|
+
does (`AvCoreResource.schema`, `AvCoreResource.managedElsewhere`; the adapter states it as `AvAdapterResource.schema`).
|
|
124
|
+
No other Contract carries them, so no Client Contract hash moves.
|
|
125
|
+
- A delete whose referential action would write it (`cascade`, `setNull`, `setDefault`, directly or through other
|
|
126
|
+
cascades) is refused at startup (`CONFIG_INVALID_VALUE` at `resources.<R>.managedElsewhere`), and a create that only
|
|
127
|
+
the hidden relation could satisfy is `COMPILER_HIDDEN_REQUIRED_FIELD`.
|
|
128
|
+
- Not covered: key updates follow the owner's foreign key (core has no `onUpdate` facts), and `ctx.$sql` or a stage
|
|
129
|
+
calling the ORM can still write it.
|
|
130
|
+
|
|
131
|
+
- 2907484: `mcp.authorization.servers` accepts an http authorization server on this machine, so an MCP setup on a laptop needs no
|
|
132
|
+
TLS: `http://127.0.0.1:4000` or `http://[::1]:4000`, with any port and path.
|
|
133
|
+
|
|
134
|
+
- `localhost` (any case) is refused with the address to use: `… is an http loopback URL; write it as
|
|
135
|
+
http://127.0.0.1:4000, the spelling every client reads as this machine`. A name is looked up, and `localhost` can
|
|
136
|
+
resolve somewhere else (RFC 8252 §8.3).
|
|
137
|
+
|
|
138
|
+
- Any other http URL is refused as before (`mcp.authorization.servers[0] must be an absolute https URL`). That covers
|
|
139
|
+
`0.0.0.0`, `[::]`, other `127.x` addresses, IPv4-mapped addresses, names such as `auth.localhost` or
|
|
140
|
+
`localhost.example.com`, and URLs with credentials.
|
|
141
|
+
- The host is read the way the WHATWG URL parser reads it, and the URL must be written the way that parser writes it
|
|
142
|
+
back. `http://127.1:4000`, `http://2130706433:4000` and `http://127.0.0.1:4000\@example.com` are refused with the
|
|
143
|
+
spelling to use (`… is an http loopback URL; write it as http://127.0.0.1:4000, the spelling every client reads as
|
|
144
|
+
this machine`), so no client can read a different host from the URL your metadata advertises.
|
|
145
|
+
- Every server, https included, is refused when its path has an empty segment (`//`) or it names port 0. A client
|
|
146
|
+
builds its discovery URLs from the path, and a path starting `//` is protocol-relative: `http://127.0.0.1//evil.com`
|
|
147
|
+
would send the client to `evil.com` for its metadata. A loopback http server also takes no query or fragment.
|
|
148
|
+
- An http authorization server is for local development only; deploy with https.
|
|
149
|
+
|
|
150
|
+
- a6bbdf0: For an update the engine lists in `afterChecks`, the after check now also hands the engine the changed row as it was
|
|
151
|
+
before the update: `can({ action: "update", row, before })`. Core reads it inside the update's transaction, ahead of
|
|
152
|
+
the write, with the fields `row` carries, so an engine can require that one grant covers a row both before and after
|
|
153
|
+
the update and stop an update moving a row from one grant's rows to another's. `before` is optional and present only
|
|
154
|
+
for a listed update; an unlisted update gets none and runs exactly the statements it ran before. A listed root
|
|
155
|
+
`update.unique`, `update.many` or `update.count` runs one read more (a nested write reuses the read it already makes).
|
|
156
|
+
|
|
157
|
+
The listed update is held to the rows as that read saw them (their values ANDed into the write's selector), so a row
|
|
158
|
+
changed concurrently is missed instead of checked against a stale `before`. A listed update that writes several rows
|
|
159
|
+
and cannot pair a changed row with its `before`, or of a Resource offering no `find.many`, is refused (`A4002`).
|
|
160
|
+
|
|
161
|
+
Fixed: an update whose rows are checked after it without being returned (a nested `$update`, a linked row, an
|
|
162
|
+
`update.count` that reads its rows first) skipped the after check for a row whose identifier it changed. It is now
|
|
163
|
+
refused (`A4002`) and rolled back.
|
|
164
|
+
|
|
165
|
+
- 3e26549: A policy engine may answer a new optional question, `grantsOf({ principal, origin, resource, action: "update", row,
|
|
166
|
+
exists })`, with a list of strings: its own keys for the grants that cover `row`. For an update the engine lists in
|
|
167
|
+
`afterChecks`, core asks it about each row as it was before the update, right after reading that row and before any
|
|
168
|
+
interceptor, hook or the write itself, inside the update's transaction, so the rule's `exists` lookups see nothing the
|
|
169
|
+
request writes. The after check of the same row then states the answer, unchanged, as `beforeGrants` beside `before`.
|
|
170
|
+
An engine that requires one grant to cover a row both before and after its update can require the grant covering
|
|
171
|
+
`row` to be one of `beforeGrants`, so a nested write in the same request (a membership created alongside the update)
|
|
172
|
+
can no longer make `before` appear to meet a relation-based grant it did not meet.
|
|
173
|
+
|
|
174
|
+
Additive: an engine without `grantsOf`, and an update `afterChecks` does not list, are asked exactly what they were
|
|
175
|
+
before. An answer that is not a list of strings answers `A3000` and nothing is written; a `grantsOf` that is present
|
|
176
|
+
and not a function fails construction (`CONFIG_INVALID_TYPE`).
|
|
177
|
+
|
|
178
|
+
- a6bbdf0: A policy engine may state a typed literal in a fragment it answers (a `scopeOf` scope, a `fieldsOf` per-row grant, an
|
|
179
|
+
`exists` question) in its wire form as well as its application form: `"2026-01-01T00:00:00.000Z"` or a `Date`,
|
|
180
|
+
`"0.3"` or an `AvDecimal`, `"9007199254740993"` or a `bigint`. Both reach the adapter as the runtime value and select
|
|
181
|
+
the same rows. Before, a wire form answered `A3000`, so an engine whose rules are data (read from JSON) could not
|
|
182
|
+
compare a `datetime`, `decimal` or `bigint` field. A value in neither form is still the server's `A3000`.
|
|
183
|
+
- 6f3f6f6: **Security.** A to-one `$update` or `$upsert` next to `$connect`, `$connectOrCreate`, `$create`, `$set` or
|
|
184
|
+
`$disconnect` on the same relation is now refused for every caller: `A2004` with one `V1012` issue at the relation,
|
|
185
|
+
before anything runs. The argument types now reject the combination too. Before this fix (rc.9 and earlier), the ORM ran
|
|
186
|
+
the relink first and the update then wrote the newly linked row. But a policy's update scope, its field grants,
|
|
187
|
+
`grantsOf` and the after check had all been asked about the row linked before the call. So a caller whose read scope
|
|
188
|
+
reached a row that its update scope did not could still change that row, for example with
|
|
189
|
+
`Post.update.unique({ data: { author: { $connect: { id }, $update: { data } } } })`. To do both, relink in one write and
|
|
190
|
+
update the linked row in another. A to-many operand may still combine them, because each entry names its own rows.
|
|
191
|
+
|
|
192
|
+
A `grantsOf` answer that is a sparse list (one with a hole) is now the server's defect (`A3000`), as any other answer
|
|
193
|
+
that is not a list of strings already was. Before, the hole reached `can` as an `undefined` grant.
|
|
194
|
+
|
|
195
|
+
- 04bb39a: `engines.node` is now `^22.23.4 || >=24.22.1`, the Node security releases of 2026-10-20 (CVE-2026-105481,
|
|
196
|
+
CVE-2026-105482, CVE-2026-105483, CVE-2026-106069, CVE-2026-106072, CVE-2026-106073, CVE-2026-106074,
|
|
197
|
+
CVE-2026-111497). The bins refuse an older Node in one sentence naming that range.
|
|
198
|
+
- 3fb3333: `@aventara/mcp` pins `@modelcontextprotocol/sdk` 1.32.2 (was 1.30.1) and `zod` `^4.6.7` (was `^4.6.5`); `@aventara/core`
|
|
199
|
+
also takes `zod` `^4.6.7`. This clears the advisories against those versions:
|
|
200
|
+
|
|
201
|
+
| Advisory | Severity | Package | What it was |
|
|
202
|
+
| ------------------------------------- | -------- | ------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
203
|
+
| GHSA-46jr-4wx8-v453 (CVE-2026-111768) | high | sdk < 1.32.0 | One HTTP request within the default size limit could exhaust server memory during validation |
|
|
204
|
+
| GHSA-22jm-h49p-29qw (CVE-2026-111767) | high | sdk 1.24.0 – 1.31.x | Experimental tasks were not tied to the session that created them |
|
|
205
|
+
| GHSA-6qxp-vccf-f47h (CVE-2026-104850) | high | sdk 1.12.0 – 1.30.x | The OAuth client could send credentials to an authorization server chosen by the MCP server |
|
|
206
|
+
| GHSA-mrhx-c7xc-m3w5 | high | zod ≤ 4.6.5 | Validating an oversized unconstrained array could exhaust memory |
|
|
207
|
+
| GHSA-rvq5-wwqv-78pq (CVE-2026-111771) | moderate | sdk 1.6.0 – 1.31.x | Server bearer authentication accepted tokens issued for another service |
|
|
208
|
+
| GHSA-6prh-2h8m-c8cw (CVE-2026-111769) | moderate | sdk < 1.32.0 | HTTP client transports and OAuth requests followed cross-origin redirects with custom headers and bodies |
|
|
209
|
+
|
|
210
|
+
Aventara's MCP server was already outside most of these: it uses the low-level `Server` (no `taskStore`, no
|
|
211
|
+
`requireBearerAuth`, no SDK OAuth client), and its transport's `maxRequestBodySize` is the agent layer's
|
|
212
|
+
`limits.maxRequestBytes`. No API of `@aventara/mcp` changes.
|
|
213
|
+
- 827ccb6: A listed update (`afterChecks`) no longer refuses an unchanged row on PostgreSQL. The compare-and-swap that holds the
|
|
214
|
+
write to the rows as they were read compares each field by `equals`, and the adapter's `equals` now matches every row
|
|
215
|
+
that reads as the value:
|
|
216
|
+
|
|
217
|
+
- a scalar list a create left out, which Prisma stores as `NULL` and reads as `[]`: an operator object that holds for
|
|
218
|
+
`[]` (`equals: []`, `isEmpty: true`, `hasEvery: []`) holds for that row too, in every filter;
|
|
219
|
+
- a timestamp finer than a millisecond (a `@db.Timestamptz(6)` the database writes), which Prisma reads truncated:
|
|
220
|
+
equality with an instant also holds the instants inside its millisecond.
|
|
221
|
+
|
|
222
|
+
On a `Bytes[]` column, `equals`, `hasEvery` and `hasSome` are no longer offered: Prisma 7.10.1 binds their operand's
|
|
223
|
+
members as base64 text, so they matched other rows than they named. `has` and `isEmpty` remain. Such a column is not
|
|
224
|
+
part of the compare-and-swap.
|
|
225
|
+
|
|
226
|
+
The compare-and-swap takes its fields from what the adapter compares, so an exemption that hides `equals` from callers
|
|
227
|
+
no longer takes a field out of it.
|
|
228
|
+
- 7f5abc3: An editor's hover over a provider written `satisfies AvAccessProvider`, and the declarations a consumer emits for one,
|
|
229
|
+
no longer name `AccessRequestHeaders`, a module-local alias no entry exports. `state`'s `headers` is written inline
|
|
230
|
+
again, and each verb's `call` reads the same type from `state` (`Parameters<AvAccessProvider["state"]>[0]["headers"]`),
|
|
231
|
+
so both print as the part of a Web `Headers` they are. A test now holds what a provider's hover names to the
|
|
232
|
+
entry's exports and TypeScript's library.
|
|
233
|
+
- 88263a1: Startup checks, tightened:
|
|
234
|
+
|
|
235
|
+
- An `mcp.authorization.servers` entry with a query or a fragment is refused for https URLs too, not only for loopback
|
|
236
|
+
http ones: an authorization server's issuer has neither (RFC 8414).
|
|
237
|
+
- A `client.resources` or `agent.resources` entry for a Resource managed elsewhere is refused (`COMPILER_UNKNOWN_RESOURCE`):
|
|
238
|
+
those layers never have the Resource, so nothing configured there would run. Configure it at the root or in
|
|
239
|
+
`application`.
|
|
240
|
+
|
|
241
|
+
- 10341d5: Policy checks, tightened:
|
|
242
|
+
|
|
243
|
+
- Writing a reference field itself (`authorId: "u2"`) is held to the target's read scope, as `$connect` is: the row it
|
|
244
|
+
names must be one the caller may read, or the write is refused (`A4002`). Creates, updates, both branches of an
|
|
245
|
+
upsert and the records of nested writes are all checked. The reference is handed to the adapter as a `$connect` that
|
|
246
|
+
carries the read scope, so the write itself refuses a row outside it, with no extra query; a row that is not there is
|
|
247
|
+
refused the same way. Where Prisma's input takes no relation (`createMany`, `updateMany`, a to-many nested
|
|
248
|
+
`$update`), and where a create writes a reference `null` (left out, the column's default would fill it), one count
|
|
249
|
+
per target Resource asks before the write whether every row named is there and readable. Nothing is asked where that
|
|
250
|
+
Resource is read by everyone, and a `null` reference is not asked about. A record written this way writes its other
|
|
251
|
+
references as links too, so a missing row there answers `A2003` rather than `A2008`.
|
|
252
|
+
- A row a write names to link that is not there is refused (`A4002`) like a row the caller cannot read, wherever the
|
|
253
|
+
target has a scope: a reference field, and a `$connect`, `$set` or `$disconnect` selector. Before, a missing row
|
|
254
|
+
answered the store's `A2008` or `A2003`, which told a caller which rows it cannot read exist. Without a scope, the
|
|
255
|
+
codes are unchanged.
|
|
256
|
+
- An update the engine lists in `afterChecks` now holds a to-one nested `$update` to its row as it was read. A row a
|
|
257
|
+
concurrent write changed in between refuses the write (`A4002`) and rolls it back, with no extra query.
|
|
258
|
+
- A listed `update.many` or `update.count` that changes fewer rows than it read (a concurrent write changed one in
|
|
259
|
+
between) is refused (`A4002`) and rolled back, rather than answering a partial success.
|
|
260
|
+
- The client and agent Contracts leave out an enum that only a Resource managed elsewhere uses. The ClientContract hash
|
|
261
|
+
of an app with such an enum changes.
|
|
262
|
+
- Seven public types write their members out in an editor's hover instead of naming a type no entry exports (`ctx.$sql`,
|
|
263
|
+
a compute stage's context, a pipe's return, `defineResource`'s overlays, a family's variants, the `access` and
|
|
264
|
+
`policy` check, `AvConfigComputedFields.agentExtends`).
|
|
265
|
+
|
|
266
|
+
- 8129844: A policy engine may take the optional `hosted({ held })`. Core calls it once, when a Framework is built on the engine
|
|
267
|
+
and every construction check has passed, with the Resources a written reference is held to the read scope of: every
|
|
268
|
+
Resource of the Core Contract, so none the root's exemptions hide. An engine can check its rules against that list and
|
|
269
|
+
warn about a write its rules allow that the hold would always refuse. It changes no answer. A `hosted` that is not a
|
|
270
|
+
function fails construction (`CONFIG_INVALID_TYPE`), as `grantsOf` does.
|
|
271
|
+
- 88263a1: Policy checks, tightened:
|
|
272
|
+
|
|
273
|
+
- The rows a write returns are checked against the database as the write left it. An `exists` answer asked before the
|
|
274
|
+
write (by `grantsOf`, say) is no longer reused for the same question after it.
|
|
275
|
+
- A `fieldsOf` answer whose `always` is a sparse list is the server's defect (`A3000`), as a sparse `grantsOf` answer
|
|
276
|
+
is.
|
|
277
|
+
- An access provider's verbs are its own enumerable `client.verbs` keys. A verb `verbs` only inherits, or holds
|
|
278
|
+
non-enumerable, is no verb: never offered on `$access` and never compiled into the Client Contract.
|
|
279
|
+
|
|
280
|
+
- 3020b5c: A policy engine may list `unheld`: the Resources its rules read in conditions and decide no read of, such as one another
|
|
281
|
+
service owns. A reference a write sends to a row of one, and a `$connect` or `$set` that links one by the reference's
|
|
282
|
+
own values where the writing row holds the reference, is no longer held to the caller's read scope, which is `false` for every caller there and refused
|
|
283
|
+
every such write (`A4002`). The write's own grant decides which row may be linked (`can`, and the after check). Reads of
|
|
284
|
+
those Resources are decided by `scopeOf` as before, and `hosted({ held })` leaves them out. A value that is no list of
|
|
285
|
+
strings fails construction (`CONFIG_INVALID_TYPE`), and so does a name the data source's model does not have
|
|
286
|
+
(`CONFIG_INVALID_VALUE`).
|
|
287
|
+
- 88263a1: Documentation: `$access.can([])` answers `[]`, not a refusal; the MCP README states the query and fragment rule for
|
|
288
|
+
every authorization server.
|
|
289
|
+
- 88263a1: A value projected onto an `access` verb's result schema is refused with the projector's own `TypeError` when the schema
|
|
290
|
+
is outside the forms a Client Contract states (a free-form object or list, a tuple read past its length), whatever the
|
|
291
|
+
value, an empty one included. A Client Contract that passed its structure check never holds such a schema.
|
|
292
|
+
|
|
293
|
+
## 1.0.0-rc.9
|
|
294
|
+
|
|
295
|
+
### Patch Changes
|
|
296
|
+
|
|
297
|
+
- 205a379: `createFramework` takes `access`, an access provider: the policy engine and the request state from one object.
|
|
298
|
+
`AvAccessProvider` is the new main-entry type it is written against, with two members:
|
|
299
|
+
|
|
300
|
+
- `policy`: the engine every client and agent request is checked against, as `policy.engine` takes it.
|
|
301
|
+
- `state({ headers, request })`: seeds each request's `ctx.$state`. Its `me` is the principal the engine is asked
|
|
302
|
+
about, `null` for an anonymous caller.
|
|
303
|
+
|
|
304
|
+
Aventara Access provides one, and a hand-written provider works the same way (a till's PIN sign-in, say: see the
|
|
305
|
+
README). The host calls `state` once per request; server code that calls the Framework directly passes the state
|
|
306
|
+
itself. A governed field of the provider's engine is left out of the default projection's type, as with `policy`.
|
|
307
|
+
`access` together with `policy` is refused at startup (`CONFIG_INVALID_VALUE` at `policy`) and is a type error at
|
|
308
|
+
`policy`. A provider whose `policy` is no engine or whose `state` is no function is refused at startup, member by
|
|
309
|
+
member, and the engine's `governed`, `paths` and `afterChecks` are checked against the Core Contract under
|
|
310
|
+
`access.policy`. A prebuilt Contracts file counts the provider by its engine's governed set alone.
|
|
311
|
+
|
|
312
|
+
- a0bb05e: A Framework's `diagnostics` sink receives every report when the process holds more than one copy of `@aventara/core`
|
|
313
|
+
(Next.js giving each layer its own copy, a duplicate install, a monorepo). A protocol failure such as a result with no
|
|
314
|
+
wire form (`500 A3003`), or an MCP endpoint failure, raised through another copy than the one that built the Framework
|
|
315
|
+
now reaches the sink instead of being silently dropped. Copies of one major version share the sinks; nothing in the
|
|
316
|
+
public API or its types changed.
|
|
317
|
+
- 86ab319: Values and errors from `@aventara/core` are recognized across duplicate copies of the package in one process. A
|
|
318
|
+
bundler that gives each layer its own copy (Next.js instrumentation, route handlers and pages), a duplicate install, a
|
|
319
|
+
monorepo, Vite SSR, per-function bundles and test runners' module isolation all load core more than once.
|
|
320
|
+
|
|
321
|
+
- A decimal made by one copy is an `AvDecimal` to every other: `value instanceof AvDecimal` is `true`, the protocol
|
|
322
|
+
encodes it (a `find.many` selecting a decimal no longer answers `500 A3003` "Result serialization failed"), and a
|
|
323
|
+
Framework accepts it in arguments, `$sql` values and Adapter results.
|
|
324
|
+
- `AvError` and its subclasses (`AvValidationError`, `AvNotFoundError`, `AvConflictError`, `AvAuthError`,
|
|
325
|
+
`AvInternalError`, `AvProtocolError`, `AvContractMismatchError`), `AvConstructionError`,
|
|
326
|
+
`AvTransactionIntegrityError` and `AvTransactionSlotPathError` pass `instanceof` across copies, with the same
|
|
327
|
+
subclass rules: every one is an `AvError`, and each is only its own subclass. An `AvError` a pipeline throws with
|
|
328
|
+
another copy's class is answered as the outcome it names. A subclass you declare yourself is checked as before.
|
|
329
|
+
- Copies recognize each other within one major version only. Nothing in the public API or its types changed.
|
|
330
|
+
|
|
331
|
+
- fdf52e6: `framework.serverClient` and `framework.serverClientTx`: the client layer for server code, called with application
|
|
332
|
+
values, such as a Next.js Server Component's.
|
|
333
|
+
|
|
334
|
+
- `framework.serverClient.<Resource>.<family>.<variant>(args, options?)` is typed from the Client Contract's view, as
|
|
335
|
+
`framework.client` is: what the client layer hides is absent. Its arguments are the application forms (`Date`,
|
|
336
|
+
`bigint`, `AvDecimal`, `Uint8Array`), the ones `framework.application` takes, and their wire strings are type errors.
|
|
337
|
+
Its results are `framework.client`'s. At runtime a value already in its wire form is accepted too, as
|
|
338
|
+
`framework.client` reads it.
|
|
339
|
+
- Each call copies its arguments, encodes the copy into the wire forms against the Client Contract, and runs the request
|
|
340
|
+
`framework.client` would run for those wire forms: the same client pipelines, validation, exposure and policy, the
|
|
341
|
+
same answers and the same `AvError` classes. `framework.client` itself is unchanged and keeps the wire forms.
|
|
342
|
+
- `framework.serverClientTx` builds client-origin transaction handles from application values. One
|
|
343
|
+
`framework.transaction([...])` may hold them beside `clientTx` handles, never beside `appTx` ones.
|
|
344
|
+
- New main-entry type names: `AvServerClientFacade`, `AvServerClientResourceFacade`, `AvServerClientFamilyFacade`,
|
|
345
|
+
`AvServerClientProjectingCall`, `AvServerClientPlainCall`, `AvServerClientTxFacade`,
|
|
346
|
+
`AvServerClientTxResourceFacade`, `AvServerClientTxFamilyFacade`, `AvServerClientTxProjectingCall`,
|
|
347
|
+
`AvServerClientTxPlainCall`.
|
|
348
|
+
|
|
349
|
+
## 1.0.0-rc.8
|
|
350
|
+
|
|
351
|
+
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.
|
|
352
|
+
|
|
353
|
+
### Patch Changes
|
|
354
|
+
|
|
355
|
+
- 1c8e4ee: Helpers typed off the façades compile again in projects that emit declarations. In 1.0.0-rc.7 a function that took
|
|
356
|
+
`Parameters<AppFramework["application"]["Post"]["find"]["many"]>[0]` and returned its `where` (or built a new one
|
|
357
|
+
from it, `{ AND: [args.where ?? {}, { published: { equals: true } }] }`) without a return type failed `nest build`
|
|
358
|
+
(and `tsc` with `"declaration": true`) with `TS4023: ... has or is using name 'ScalarFilterOperands' ... but cannot be
|
|
359
|
+
named`, usually beside `TS7056`. A nested write's data could fail the same way, or name `NO_ADVERTISED_MEMBER`, and a
|
|
360
|
+
nested `$update`'s `where` was written into the `.d.ts` as `any`. The declaration now names the argument types
|
|
361
|
+
`@aventara/core` exports (`AvWhereInput`, `AvUpdateDataInput`, `AvCreateDataInput`, ...), and these new exports cover
|
|
362
|
+
the rest:
|
|
363
|
+
|
|
364
|
+
- `AvScalarFilterOperands`: one field's filter operators, which a filter's `not` holds again.
|
|
365
|
+
- `AvScalarWhereInput`: the `where` of a nested `$update` or `$delete` that only takes plain fields.
|
|
366
|
+
- `AvNoAdvertisedMember`: a write member that advertises nothing; it takes `{}` and refuses every key.
|
|
367
|
+
- `AvSqlStatementBuilder` and `AvSqlStatementValue`: the type of `framework.appTx.$sql` and the values it binds.
|
|
368
|
+
A method returning `framework.appTx.$sql` failed with `TS2883`/`TS2742` before.
|
|
369
|
+
|
|
370
|
+
`framework.execute` and `framework.executeTransaction` take request types that `@aventara/core` exports:
|
|
371
|
+
`AvOperationRequest` (`AvApplicationOperationRequest`, `AvClientOperationRequest`, `AvAgentOperationRequest`),
|
|
372
|
+
`AvTransactionRequest` (`AvApplicationTransactionRequest`, `AvClientTransactionRequest`,
|
|
373
|
+
`AvAgentTransactionRequest`) and `AvRequestProtocol` (a remote request's `protocol`). They were named from
|
|
374
|
+
`@aventara/core/internal` (`FrameworkOperationRequest`, `FrameworkTransactionRequest`, ...), so a declaration that
|
|
375
|
+
spelled them reached into the package's internal entry. Those names are gone from `@aventara/core/internal`.
|
|
376
|
+
|
|
377
|
+
An adapter's `referentialActions` edges name the fields that hold the reference `referenceFields`, as a relation of
|
|
378
|
+
the Core Contract does. They were called `references`, which the Core Contract uses for the target's fields.
|
|
379
|
+
|
|
380
|
+
Reading a projecting variant's argument type (`Parameters<…["find"]["many"]>[0]`) also type-checks faster: about
|
|
381
|
+
6,000 type instantiations over a three-field model, where rc.7 took about 29,000. The part of the argument other than
|
|
382
|
+
`select`/`include` is now marked `NoInfer` in the call's parameter, so an editor shows that parameter as
|
|
383
|
+
`NoInfer<Omit<…>> & …`. What it accepts is unchanged.
|
|
384
|
+
|
|
385
|
+
A create's `data` type now states each required field once, as a required member (`Readonly<{ title: string; } & {
|
|
386
|
+
body?: ... }>` in an editor's expanded hover). Before, a required field appeared as an optional member beside a
|
|
387
|
+
separate `{ readonly title: WrittenValue }`, and a declaration file wrote both. What a create accepts and refuses is
|
|
388
|
+
unchanged.
|
|
389
|
+
|
|
390
|
+
- Operation results read as their value types in an editor and in emitted declarations. A result field that is nullable,
|
|
391
|
+
an enum or a `boolean` (such as a `Boolean @default(false)` column) was shown as `AvScalarValueType<…>`, with the
|
|
392
|
+
whole Contract view as its argument, and a project with `"declaration": true` wrote that view into its `.d.ts` once
|
|
393
|
+
per such field: a Nest service of five methods with no return types came out at 524 lines. Those fields now read
|
|
394
|
+
`body: string | null`, `published: boolean`, `publishedAt: Date | null`, and that service's declaration is 44 lines.
|
|
395
|
+
A result, and a relation included in it, is shown as its fields rather than as `ProjectedFields<…>`. This covers
|
|
396
|
+
`framework.application` and `framework.client` results, the same fields in a write's `data`, and clients generated
|
|
397
|
+
by `avclient`. What each type accepts is unchanged.
|
|
398
|
+
|
|
399
|
+
## 1.0.0-rc.7
|
|
400
|
+
|
|
401
|
+
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.
|
|
402
|
+
|
|
403
|
+
### Patch Changes
|
|
404
|
+
|
|
405
|
+
- f917c1c: A helper that reads `framework.contracts.core` compiles again in projects that emit declarations. In 1.0.0-rc.6 a
|
|
406
|
+
function returning a Resource or a field of the Core Contract without a return type, such as
|
|
407
|
+
`core.resources[resource]?.fields[relation]`, failed `nest build` (and `tsc` with `"declaration": true`, which Nest's
|
|
408
|
+
`tsconfig.json` sets) with `TS2883: The inferred type of 'relationOf' cannot be named without a reference to
|
|
409
|
+
'AvCoreField'` under TypeScript 6 (`TS2742` under 5.9), or with `TS4058`/`TS4023` naming `AvCoreResource`.
|
|
410
|
+
`@aventara/core` now exports the three types the Core Contract is written with: `AvCoreResource` (a Resource),
|
|
411
|
+
`AvCoreField` (one of its fields) and `AvCoreRelationField` (a relation field, with the target fields its reference
|
|
412
|
+
names in `references`).
|
|
413
|
+
|
|
414
|
+
A policy's startup check states the path once: `CONFIG_INVALID_VALUE at policy.engine.governed.Nope: "Nope" is no
|
|
415
|
+
Resource of the Core Contract`, where rc.6 repeated `policy.engine.governed.Nope:` inside the message. Each
|
|
416
|
+
diagnostic's `message` no longer starts with its `path`; read the path from `path`.
|
|
417
|
+
|
|
418
|
+
## 1.0.0-rc.6
|
|
419
|
+
|
|
420
|
+
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.
|
|
421
|
+
|
|
422
|
+
### Patch Changes
|
|
423
|
+
|
|
424
|
+
- 1cf72e0: Typed policy engines compile again in projects that emit declarations. In 1.0.0-rc.5 an engine written
|
|
425
|
+
`as const satisfies AvPolicyEngine` failed `nest build` (and `tsc` with `"declaration": true`, which Nest's
|
|
426
|
+
`tsconfig.json` sets) with `TS2883: The inferred type of 'policyEngine' cannot be named without a reference to
|
|
427
|
+
'PolicyFieldsAccess'` under TypeScript 6, or `TS2742` under 5.9, and so did the configuration and the framework holding
|
|
428
|
+
it. `fieldsOf`'s question is now written out in full in `AvPolicyEngine`, so a declaration can spell it.
|
|
429
|
+
|
|
430
|
+
A read question's `action` is now typed `undefined`, so an engine can destructure it from either question:
|
|
431
|
+
`fieldsOf({ resource, access, action })`. A write question's `action` is `"create"` or `"update"`, as before.
|
|
432
|
+
|
|
433
|
+
## 1.0.0-rc.5
|
|
434
|
+
|
|
435
|
+
### Patch Changes
|
|
436
|
+
|
|
437
|
+
- 4795ff1: Two additions for policy engines. A Framework without a policy, and every Contract a caller is served, are unchanged.
|
|
438
|
+
|
|
439
|
+
- 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.
|
|
440
|
+
- `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.
|
|
441
|
+
|
|
442
|
+
- 9e178ca: Fixes for applications that check requests with a policy engine. A Framework without a policy behaves as before.
|
|
443
|
+
|
|
444
|
+
- 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.
|
|
445
|
+
- 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`.
|
|
446
|
+
- The `exists` questions a policy asks select the Resource's primary identifier, never another unique field such as a governed `email`.
|
|
447
|
+
- `policy.principal` may answer with a promise. A synchronous principal works as before; a rejection answers `A3000`, as a throw does.
|
|
448
|
+
- `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.
|
|
449
|
+
|
|
450
|
+
## 1.0.0-rc.4
|
|
451
|
+
|
|
452
|
+
### Patch Changes
|
|
453
|
+
|
|
454
|
+
- e3d03a7: The error catalog says more about policies. `A2003` now names the cases a policy adds: a unique read of a row outside the
|
|
455
|
+
caller's read scope, and a single-row update or delete of a row the caller cannot read, are misses, while a row the
|
|
456
|
+
caller can read but not change answers `A4002`. `CONFIG_REQUIRED`, `CONFIG_UNKNOWN_PROPERTY` and `CONFIG_INVALID_VALUE`
|
|
457
|
+
name the `policy` key's rules: `policy.engine` and `policy.principal` are required, `policy` and `prebuilt` are root-only,
|
|
458
|
+
and the engine's `governed`, `paths` and `afterChecks` lists must name what the Core Contract has. Wording only; no
|
|
459
|
+
behaviour changes.
|
|
460
|
+
- d10639a: Faster reads. The read-only copies stages are told cost about half what they did: a read with a computed field
|
|
461
|
+
copies each row in one pass, a row with several computed fields shares its nested objects between the stages'
|
|
462
|
+
copies, and after hooks share one frozen copy of the result. A row whose computed field returns a value at once no
|
|
463
|
+
longer waits a turn per row. The MCP endpoint builds its JSON Schema validator once instead of on every request.
|
|
464
|
+
What stages are told, the order they run in and what a request answers are unchanged.
|
|
465
|
+
- f67e68c: A service method can return any façade node or variant without a return type. Under `declaration: true` (a Nest
|
|
466
|
+
build) the declaration file now writes a short reference, under half a kilobyte per method at any schema size, where it
|
|
467
|
+
wrote the whole Contract view (about 440 KB per method over 50 Resources). A method returning a variant
|
|
468
|
+
(`this.framework.application.Post.find.many`) no longer needs an annotation to compile, and one returning a transaction
|
|
469
|
+
variant (`this.framework.appTx.Post.create.one`) no longer runs the compiler out of memory.
|
|
470
|
+
|
|
471
|
+
New type names, exported from `@aventara/core` so a declaration can write them (you never need to write them yourself):
|
|
472
|
+
|
|
473
|
+
- `AvContractView`, the Contract view the façades are typed over;
|
|
474
|
+
- one call type per surface and shape: `AvApplicationProjectingCall`, `AvApplicationPlainCall`,
|
|
475
|
+
`AvClientProjectingCall`, `AvClientPlainCall`, `AvAppTxProjectingCall`, `AvAppTxPlainCall`,
|
|
476
|
+
`AvClientTxProjectingCall`, `AvClientTxPlainCall`.
|
|
477
|
+
|
|
478
|
+
A configuration with `fields` entries still writes those entries after each reference; annotate the method if that
|
|
479
|
+
matters (see the README's "Returning a façade from a method").
|
|
480
|
+
|
|
481
|
+
- 708556c: Policy engines check nested writes. A create, update or upsert whose data writes a relation through a directive
|
|
482
|
+
(`$create`, `$connect`, `$connectOrCreate`, `$update`, `$upsert`, `$delete`, `$disconnect`, `$set`) now runs under a
|
|
483
|
+
policy, where before it was refused. It runs as one adapter call inside the write's interactive transaction, and one
|
|
484
|
+
failed check rolls the whole write back with `403 A4002`.
|
|
485
|
+
|
|
486
|
+
- Every row the nested write creates is read back as stored (its parent's reference filled, database defaults
|
|
487
|
+
present) and asked `can({ action: "create", row })`, its `exists` lookups seeing the rows the same call wrote. A
|
|
488
|
+
create whose own data holds a nested write is asked about its row the same way, after the insert.
|
|
489
|
+
- A row a directive links or unlinks is held to the scope of the row that changes: a linked row that holds the
|
|
490
|
+
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
|
|
491
|
+
update scope, one whose parent holds the reference (a to-one relation's target) in its read scope. One `exists`
|
|
492
|
+
per Resource answers it, inside the transaction. A linked row that changes is an update: after the call it gets
|
|
493
|
+
the after check (`can({ action: "update", row })`) and the move-out re-read. Linking a new row to a one-to-one's
|
|
494
|
+
back end unlinks the old one, which is held the same way.
|
|
495
|
+
- A to-many nested `$update` or `$delete` acts only on the rows within the update (or delete) scope, as
|
|
496
|
+
`update.many` and `delete.many` do: core reads those rows' identifiers inside the transaction and hands the
|
|
497
|
+
adapter `where: { id: { in: [...] } }`. A to-one one refuses the write when its row is outside the scope. A nested
|
|
498
|
+
`$update`'s changed rows get the after check and the move-out re-read after the call.
|
|
499
|
+
- The fields a nested record sends are held to its Resource's `fieldsOf({ access: "write" })`, and a relation written
|
|
500
|
+
through a directive to the parent's grant, its reference fields with it.
|
|
501
|
+
- Where the operation's `transactionsStyle` is `"none"`, a nested write under a policy is refused before it runs.
|
|
502
|
+
|
|
503
|
+
- 3a0dfca: A write the policy checks costs fewer database round trips. What each check decides and refuses is unchanged.
|
|
504
|
+
|
|
505
|
+
- A single-row update or delete carries the caller's scopes inside its own `where` and runs as one statement, with no
|
|
506
|
+
read of its row before it. One that matches nothing still answers `A4002` when the caller can read the row and
|
|
507
|
+
`A2003` when it cannot, with one lookup on that miss only. The Prisma adapter sends it as
|
|
508
|
+
`update({ where: { id, AND: [scope] } })`.
|
|
509
|
+
- A create asks `can({ action: "create", row })` and its `exists` lookups before the insert, without a transaction. A
|
|
510
|
+
transaction around them never closed the race between the lookup and the insert at the database's default
|
|
511
|
+
isolation, and cost a BEGIN and a COMMIT.
|
|
512
|
+
- A transaction opens only when a check after the write may roll it back: an update's after check or move-out, a
|
|
513
|
+
created row held to a write grant, a nested write, or returned rows checked by `exists`. A plain checked create or
|
|
514
|
+
delete is one statement, its interceptors and hooks outside any transaction.
|
|
515
|
+
- The after check asks `can` about the row the update returned, so it reads nothing again. Only a move-out reads the
|
|
516
|
+
changed rows again. An `update.count` runs as the returning `update.many` when the Contract offers it.
|
|
517
|
+
|
|
518
|
+
Round trips per request on PostgreSQL: `create.one` 3 → 1, `delete.unique` 4 → 1, `update.unique` 5 → 3,
|
|
519
|
+
`update.many` 4 → 3, `update.count` 5 → 3. Reads, and a Framework without a policy, are unchanged.
|
|
520
|
+
|
|
521
|
+
- 99169d2: Policy engines, reads first. `createFramework` takes an optional root `policy: { engine, principal }`: an engine that
|
|
522
|
+
answers three questions (`can`, `scopeOf`, `fieldsOf`, each now or as a promise), and a function that derives a
|
|
523
|
+
request's principal from its `ctx.$state`. Core never imports an engine; one satisfies `AvPolicyEngine` by its shape,
|
|
524
|
+
and a scope or grant is a fragment of Aventara's own `where` (`AvPolicyWhere`).
|
|
525
|
+
|
|
526
|
+
- Client and agent requests are checked (an MCP tool call is an agent request). An application call is checked only
|
|
527
|
+
when it names a principal: `framework.application.Note.find.many(args, { as: user })`. A call a stage makes through
|
|
528
|
+
`ctx` never is.
|
|
529
|
+
- Before any guard, `can` and the fields the request names or receives (`fieldsOf`); a refusal is `403 A4002`. At the
|
|
530
|
+
adapter call, after every developer stage, the read scope is ANDed into the root `where`, every to-many include and
|
|
531
|
+
its `$count`, and every relation filter, at every level. A to-one include of a row out of scope refuses the request;
|
|
532
|
+
it is never nulled.
|
|
533
|
+
- Governed fields (the engine's `governed` list) are left out of every default projection and carry `governed: true`
|
|
534
|
+
in the Contracts. Name one to get it: `select: ["$defaults", "email"]`, where `$defaults` is the level's default
|
|
535
|
+
projection. It is refused where a returned row does not grant it, and a filter or sort by it is checked before the
|
|
536
|
+
query runs, so it cannot reveal values the caller may not read.
|
|
537
|
+
- The engine's `governed` and `paths` lists are checked against the Core Contract at startup. A rule's paths are lists
|
|
538
|
+
of segments, as other paths in core are: `["Note", "campaign", "members", "some", "userId", "equals"]`.
|
|
539
|
+
- Writes are checked as well; see the policy writes entry.
|
|
540
|
+
|
|
541
|
+
Without `policy`, nothing changes: the Contracts and their hashes are the same bytes.
|
|
542
|
+
|
|
543
|
+
- 0fd2608: A write the policy checks costs less CPU after the round-trip changes. The operation core hands its adapter no longer
|
|
544
|
+
carries the request's policy session, so an adapter that keeps its operations no longer keeps each request's session
|
|
545
|
+
with them. The fields a write returns only for the after check are dropped as soon as the check has read them, so a
|
|
546
|
+
read compute stage is told the row the request asked for, not every scalar field. What each check decides, the
|
|
547
|
+
fields `can` is asked about, and the database round trips are unchanged.
|
|
548
|
+
- 154e440: Policy engines check writes. Every create, update, delete and upsert a client or agent makes (or an application call
|
|
549
|
+
made `{ as }` a principal) is now checked by the configuration's `policy`, where before it was refused.
|
|
550
|
+
|
|
551
|
+
- The fields a write sends are held to `fieldsOf({ access: "write" })` before any guard; a field outside it answers
|
|
552
|
+
`403 A4002`. A field writable on some rows only is checked against every row an update reaches, and against the
|
|
553
|
+
row a create inserts, after the insert and inside its transaction.
|
|
554
|
+
- A create asks `can({ action: "create", row })` about each new row on its final data. Its rules can look rows up
|
|
555
|
+
through `exists`, which runs inside the write's transaction, so a plan that creates a campaign and then its first
|
|
556
|
+
note sees the campaign.
|
|
557
|
+
- An update or delete acts only on rows the caller may both change (`scopeOf` for the action) and read. A single-row
|
|
558
|
+
one that matches nothing answers `A4002` when the caller can read the row, and `A2003` when it cannot, so the
|
|
559
|
+
answer does not reveal that the row exists. A `many` or `count` one answers for the caller's rows only.
|
|
560
|
+
- An update that moves a row out of what the caller reads is rolled back with `A4002`. So is one that fails an `after`
|
|
561
|
+
condition: inside the transaction core asks `can({ action: "update", row })` about each changed row of every
|
|
562
|
+
checked update. The engine's new `afterChecks` member (`{ Note: ["update"] }`) names the updates whose grants carry
|
|
563
|
+
such a condition, which are the ones refused where there is no interactive transaction.
|
|
564
|
+
- Where the operation's `transactionsStyle` is `"interactive"`, a checked write runs in a transaction of its own. Where
|
|
565
|
+
it is `"none"` (Cloudflare D1), a write that can only be checked after it runs is refused before it runs; every other
|
|
566
|
+
write runs, a create looking up before it inserts, and an unlisted update without the after check.
|
|
567
|
+
- Nested writes (a relation directive in a write's data) are refused under a policy for now.
|
|
568
|
+
|
|
569
|
+
`afterChecks` is checked against the Core Contract at startup and changes no Contract and no prebuilt fingerprint.
|
|
570
|
+
|
|
571
|
+
- 2fcffdb: Prebuilt Contracts. `createFramework` takes an optional `prebuilt` key: the four Contracts compiled ahead of time (type
|
|
572
|
+
`AvPrebuiltContracts`), so a serverless or edge cold start loads them instead of compiling them. A file is used only
|
|
573
|
+
when this core compiled it, for this protocol, from the same configuration and adapter model; its Contracts, hashes
|
|
574
|
+
and `/_contract` bytes are then the ones compiling would give. Any other file is ignored whole: the framework compiles
|
|
575
|
+
at startup exactly as without one and warns once (`AVENTARA_PREBUILT_STALE`), naming the Resources whose configuration
|
|
576
|
+
or model changed, or the version that differs. Without `prebuilt`, nothing changes. The build step that writes the
|
|
577
|
+
file arrives with `@aventara/build`.
|
|
578
|
+
|
|
579
|
+
## 1.0.0-rc.3
|
|
580
|
+
|
|
581
|
+
### Patch Changes
|
|
582
|
+
|
|
583
|
+
- b38034d: An issue inside one entry of a to-many relation directive's array names that entry's index:
|
|
584
|
+
`["arguments", "data", "posts", "$update", 1, "data", "category"]`, where it read `[…, "$update", "data", "category"]`.
|
|
585
|
+
This holds for `$create`, `$connect`, `$connectOrCreate`, `$update`, `$delete`, `$upsert`, `$set` and `$disconnect`,
|
|
586
|
+
as the runtime's own nested-write paths already did. A directive given one object, not an array, keeps its issue at the
|
|
587
|
+
directive.
|
|
588
|
+
- b38034d: An empty boolean node in the `where` of a write is refused, as an `undefined` there is, and nothing runs. In
|
|
589
|
+
`update.many`, `update.count`, `update.first`, `delete.many`, `delete.count`, `delete.first` and a to-many `$update`
|
|
590
|
+
or `$delete`, an empty `NOT: {}`, `AND: {}` or `[]`, `OR: []` or `AND`/`OR` entry `{}`, at any depth, answers
|
|
591
|
+
`422 A2004` with a `V1001` issue at the node:
|
|
592
|
+
`A boolean node must hold a condition here: an empty one matches every row, and the write would reach more rows.`
|
|
593
|
+
The ORM reads these as no condition, so `delete.count({ where: { NOT: {} } })` deleted every row, and `delete.first`
|
|
594
|
+
with one deleted an arbitrary row. A `where` that is `{}` itself still means every row, and reads pass empty nodes through as before. Field filters are unchanged: a scalar's `{}` and a
|
|
595
|
+
to-many relation's `{}` were already refused, and a to-one relation's `{}` matches the rows that have a related row.
|
|
596
|
+
- b38034d: The error catalog (`AvErrorCatalog`, `ERRORS.md`) catches up with the code: `A2004` says a value the database refused
|
|
597
|
+
may stand at the value itself where the adapter can place it (a bigint its driver cannot bind) and may be too long for
|
|
598
|
+
its index; `V1001` lists an `undefined` in a filter, selector or cursor and an empty boolean node in a bulk write's
|
|
599
|
+
`where`; `V1004` lists a value the database refused as too long or out of range, a bigint a driver cannot bind among
|
|
600
|
+
them.
|
|
601
|
+
- b38034d: A value write compute writes to a field the caller's layer hides is judged by that field's validators and Json shape
|
|
602
|
+
(the Core Contract's), and a value they refuse is the server's error: `500 A3000`, the issues to the `diagnostics` sink
|
|
603
|
+
on an `AvComputedValueError`, with nothing of the hidden field in the answer. Before, a shaped Json field hidden from
|
|
604
|
+
the layer was not judged at all, and a hidden field's own validators answered the caller `A2004` with an issue at the
|
|
605
|
+
hidden field's path. A layer that shows the field still answers `A2004` at it.
|
|
606
|
+
- 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
|
|
607
|
+
`54000`) is answered `422 A2004`, not `500 A3001`: one `V1004` issue at `["arguments"]`, as a value too long for its
|
|
608
|
+
column already was. The adapter reads the SQLSTATE, never the message. The issue's message for a value too long now
|
|
609
|
+
reads `The database refused this value: it is longer than the database can hold for its column.`
|
|
610
|
+
(it said `longer than its column can hold`, which an index refusal is not).
|
|
611
|
+
- b38034d: A Pick layer's list entry that names what its parent hid is refused with the sentence written inline too. Inline in
|
|
612
|
+
`createFramework` or `AventaraModule.forRoot`, the error is at the key and reads:
|
|
613
|
+
|
|
614
|
+
```text
|
|
615
|
+
Type '["select"]' is not assignable to type '…'.
|
|
616
|
+
…
|
|
617
|
+
Property '"Error: client cannot expose 'Post.internalNote': hidden by root"' is missing in type '["select"]' but required in type '{ readonly "Error: client cannot expose 'Post.internalNote': hidden by root": true; }'.
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
where it ended at `Type 'string' is not assignable to type 'never'.` at the list's element. A list entry is now typed as
|
|
621
|
+
an object keyed by the sentence, which no list has, so a configuration that is not inline (an `aventaraConfig()` handed
|
|
622
|
+
to `AventaraModule.forRootAsync`) ends at the same `Property '"Error: …"' is missing` line, where it ended at
|
|
623
|
+
`Type '"select"' is not assignable to type '"Error: …"'`. An entry written `true` or as an object is unchanged.
|
|
624
|
+
|
|
625
|
+
## 1.0.0-rc.2
|
|
626
|
+
|
|
627
|
+
### Patch Changes
|
|
628
|
+
|
|
629
|
+
- A pipe may move a transaction step's `$ref`. A pipe sees a `$ref` as the reference it is, unresolved, and a
|
|
630
|
+
scoping pipe moves it: `({ args }) => ({ ...args, where: { AND: [args.where ?? {}, { published: true }] } })` puts
|
|
631
|
+
the caller's `where: { categoryId: cat.$ref("id") }` at `where.AND.0.categoryId`. The reference is now resolved
|
|
632
|
+
wherever the pipe put it: in an `AND`, an `OR` or a `NOT`, in a relation filter, under any operator, or copied from a
|
|
633
|
+
data value into another field or into a `$connect` key. Before, only a reference still standing where the caller
|
|
634
|
+
wrote it was resolved, and the step failed inside the transaction with `500 A3004` ("A server pipeline produced
|
|
635
|
+
invalid arguments."), the `diagnostics` sink naming the moved `$ref` (`V1006: Filter operator "$ref" is not
|
|
636
|
+
available on field "categoryId"`, or `V1001` under `gte`). A moved reference is held to the rules a reference the
|
|
637
|
+
caller writes at that place is held to (where a `$ref` may stand, its source step, and its source field's type
|
|
638
|
+
against its new place): one that breaks them, such as a `$ref` moved into an `orderBy` or onto a field of another
|
|
639
|
+
type, is still the server's `A3004`, and an operation pipe's is now answered before the transaction opens. A
|
|
640
|
+
transaction pipe that moves a `$ref` is held to the same rules at its step. A literal a pipe writes where the caller
|
|
641
|
+
had a `$ref` now goes through that field's pipes, as any other value does.
|
|
642
|
+
- A relation the request writes together with its reference field (`{ authorId: 1, author: { $connect: { id: 1 } } }`)
|
|
643
|
+
is refused by the framework validation, before any guard, pipe or compute stage runs and, in a plan, before the
|
|
644
|
+
transaction opens: `A2004`, `V1012` at the relation, as before. In rc.1 it was judged only after the guards, pipes
|
|
645
|
+
and write compute, so the developer's stages ran on the request first, and a plan opened its transaction and ran the
|
|
646
|
+
steps before it.
|
|
647
|
+
- The field validators run after write compute. The order of a call is now the framework validation, the guards, the
|
|
648
|
+
pipes and field pipes, write compute, then the re-check: Aventara's own checks again on what the pipes and compute
|
|
649
|
+
produced, then the field validators (a Json field's shape among them) on every value the adapter will be handed,
|
|
650
|
+
then the interceptors and hooks. So a validator judges what is stored: a value a `compute.create` or
|
|
651
|
+
`compute.update` stage replaces is judged as computed, not as the caller sent it. In rc.1 the validators ran after
|
|
652
|
+
the pipes and before write compute, and a computed value was not judged by them. A plan step and a forced call still
|
|
653
|
+
judge before the transaction opens the literal values no write compute stage of their phase changes, so a caller's
|
|
654
|
+
refusal there opens nothing; the rest is judged at the step, and a refusal rolls the transaction back.
|
|
655
|
+
- A computed write value its field's validators or Json shape refuse is answered `A2004` at the field, as any validator
|
|
656
|
+
refusal is (`{ code: "V1003", path: ["arguments", "data", "slug"], check: "string_format", … }`), at the top level,
|
|
657
|
+
in nested writes and in both branches of an upsert. In rc.1 such a value was not judged at all. A computed value of
|
|
658
|
+
the wrong type is still the server's error: `A3000`, its issues to the `diagnostics` sink on an
|
|
659
|
+
`AvComputedValueError`. `compute.read` values are output and are not judged.
|
|
660
|
+
- A field computed on read with no `dependsOn` (the function form, `read: () => "stamped"`) has the Resource's
|
|
661
|
+
identifier read for it, the adapter's first one (with Prisma, the primary key when the model declares it first), so
|
|
662
|
+
each row comes back whatever else is selected; a `dependsOn` field is read as before. The answer holds exactly what
|
|
663
|
+
was selected: `{ select: ["stamp"] }` answers `[{ stamp: "stamped" }]` on every reading variant and in a write's
|
|
664
|
+
`select`, and inside a relation projection (`{ select: [{ category: { select: ["stamp"] } }] }`) the relation's row
|
|
665
|
+
is read and computed. In rc.1 a select of only such fields handed the adapter an empty projection, which Prisma
|
|
666
|
+
cannot express: `500 A3001` at the top level, and inside a relation the row was not read (`500 A3000`).
|
|
667
|
+
- Every stage told a frozen view gets its own copy of a `Date` or a `Uint8Array`, which freezing cannot reach: a guard,
|
|
668
|
+
a field write guard, a pipe (its input and its `ctx.$args`), a field pipe, write compute (`data`, `value`,
|
|
669
|
+
`ctx.$args`), an interceptor, a before hook, each after hook (the result and `ctx.$args`), a filter and a Resource
|
|
670
|
+
transaction stage. Changing one in place changes only that stage's copy, never what a later stage is told, what is
|
|
671
|
+
written or what the caller receives; data changes only by returning it. In rc.1 every stage after write compute
|
|
672
|
+
shared one copy, so an after hook was told the `Date` a before hook had changed in place, and a guard or a pipe was
|
|
673
|
+
told the framework's own value. Arguments holding no such value are handed on as they are, at no cost per stage; a
|
|
674
|
+
copy is made only along the path to one (about 3 µs per stage for a record with a `Date` and a 64-byte
|
|
675
|
+
`Uint8Array`).
|
|
676
|
+
- A `compute.read` stage is told the row as its own deep-frozen copy, a `Date` or a `Uint8Array` in it copied, like
|
|
677
|
+
every stage above; its field's `value` too. It returns its field's value: writing into `record` throws a
|
|
678
|
+
`TypeError`, answered `A3000` when the stage lets it through, as a pipe's write into its arguments is. The answer is
|
|
679
|
+
built from the row as read. In rc.1 the stage was told a mutable copy of the row and the answer was built from it,
|
|
680
|
+
so `record.title = "x"` changed the title every surface answered. The copy costs about 1.3 µs per row and stage
|
|
681
|
+
(1.8 µs with a `Date` and 64 bytes): a 100-row read with one computed field took 0.33 ms instead of 0.19 ms.
|
|
682
|
+
- A record written through a relation cannot write that relation back. Inside a post created in `author.posts`,
|
|
683
|
+
`author` and `authorId` are what the write the post sits in supplies; inside a category created in a post's
|
|
684
|
+
`category`, `posts` is (`{ posts: { $create: { category: { $create: { posts: { $create: … } } } } } }`). The
|
|
685
|
+
framework validation refuses such a write before any guard, pipe or adapter work: `A2004` with one `V1012` issue at
|
|
686
|
+
the field (`["arguments", "data", "posts", "$create", "category", "$create", "posts"]`), message `Relation "posts"
|
|
687
|
+
is the relation this Category is written through, from its other end ("Post.category"): the write it sits in
|
|
688
|
+
supplies it.`, under every directive that carries a record (`$create`, `$connectOrCreate`, `$update`, `$upsert`),
|
|
689
|
+
to-one and to-many, and in a plan step. Only the edge walked is refused: a self-relation's other edge to the same
|
|
690
|
+
Resource stays writable. It is a type error on the façades, the builders and the generated client. In rc.1 the
|
|
691
|
+
Contract admitted these writes and the Prisma adapter answered `500 A3001`.
|
|
692
|
+
- The Contract states each relation's other end, `inverse` on a relation field (`User.posts` has `inverse: "author"`),
|
|
693
|
+
as the adapter pairs them, and only where the layer shows that other end. The write axis has a third position,
|
|
694
|
+
`"loopback"`: that other end inside a record written through the relation. An adapter states whether a relation is
|
|
695
|
+
writable there as it states the other two positions (an arm that lists `"loopback"`, or an unqualified arm). A
|
|
696
|
+
Contract whose adapter states a pairing has a new hash; one that states none is unchanged, and so is an arm that
|
|
697
|
+
lists every position. `AvAdapterRelationField.inverse` is how an adapter states the pairing.
|
|
698
|
+
- The Contract states which scalar fields hold each relation's reference: `referenceFields` on a relation field
|
|
699
|
+
(`Post.category` has `referenceFields: ["categoryId"]`; a composite reference lists every field, in the order the
|
|
700
|
+
data source pairs them with the target's key), on an optional relation as on a required one. It is absent on the
|
|
701
|
+
end that holds no reference (`Category.posts`), never an empty list. A layer keeps the fields it shows, in the
|
|
702
|
+
adapter's order, and leaves the property out when it shows none. The argument types now leave a loopback edge's
|
|
703
|
+
reference fields out for an optional relation too: `categoryId` in a post created in `Category.posts` is a type
|
|
704
|
+
error on the façades, the builders and the generated client, as the framework validation refuses it (`V1012`). In
|
|
705
|
+
rc.1 only a required relation's reference fields were left out, read off `createRequirements`.
|
|
706
|
+
`AvAdapterRelationField.referenceFields` is how an adapter states them. A Contract whose adapter states them has a
|
|
707
|
+
new hash.
|
|
708
|
+
- A computed value is held to every named `format` its field declares, not only `uuid`: a `compute.read`,
|
|
709
|
+
`compute.create` or `compute.update` value that breaks `email`, `url`, `ipv4` or any other of zod's named formats
|
|
710
|
+
is the server's error, `A3000`, its `V1003` issue to the `diagnostics` sink on an `AvComputedValueError`, as a value
|
|
711
|
+
of the wrong type is. Each entry of a list field is judged. In rc.1 only `uuid` was checked and any other format
|
|
712
|
+
reached the caller unchecked. A relative path is no URL: compute an absolute one into a `format: "url"` field
|
|
713
|
+
(`` `https://example.com/p/${record.slug}` ``), or leave `format` out. A caller's own value is still held only to
|
|
714
|
+
`uuid` (a format describes the value; a rule on caller input is a validator, `AvZ.url()`).
|
|
715
|
+
- `AvErrorCatalog` (and `ERRORS.md`): `A2004` and `V1012` list a record written through a relation that writes that
|
|
716
|
+
relation, or a field holding its reference, back. An adapter that meets one core cannot see (the layer hides the
|
|
717
|
+
relation's other end, so its Contract states no pairing) answers it with the new `V1012` sentence `The data source
|
|
718
|
+
refused this nested write: a record written through a relation cannot write that relation or its reference fields
|
|
719
|
+
back.`, which the response boundary keeps as it keeps a database refusal's.
|
|
720
|
+
- A nested `$update` on a to-many relation is a bulk write: its one `data` applies to every related row its `where`
|
|
721
|
+
matches, so it takes what `update.many`'s `data` takes. A relation the adapter does not offer in a bulk write is
|
|
722
|
+
refused before the adapter with `A2004` and `V1006` at the member
|
|
723
|
+
(`["arguments", "data", "posts", "$update", "data", "category"]`, `Relation "category" is not writable during
|
|
724
|
+
update.`), and is a type error on the façades, the builders and the generated client. A to-one `$update` is
|
|
725
|
+
unchanged. In rc.1 the Contract admitted it and the Prisma adapter refused it as `500 A3001`.
|
|
726
|
+
- `AvErrorCatalog` (and `ERRORS.md`): `A3002` lists a transaction that expired or was closed under a step among its
|
|
727
|
+
causes, and `V1006` a relation write in a to-many `$update`'s `data`.
|
|
728
|
+
- A relation a pipe or a compute stage writes together with its reference field is the server's error: the caller
|
|
729
|
+
gets `A3004` with the generic pipe-output message, and the `diagnostics` sink gets an `AvPipeOutputError` holding the
|
|
730
|
+
`V1012` issue and its path. In rc.1 the caller got `A2004`/`V1012`, as if it had sent both. A pipe's output that
|
|
731
|
+
breaks a field validator or a Json field's shape is still the caller's `A2004`: those are the developer's rules for
|
|
732
|
+
what may be stored, not Aventara's.
|
|
733
|
+
- In a transaction plan, a `$ref` holding no object (`{ payload: { $ref: "x" } }`, `{ $ref: null }`) is answered as a
|
|
734
|
+
single call answers it: `A2004` with `V1006` at the `$ref` key (`Mutation directive "$ref" is not available on field
|
|
735
|
+
"payload".`), at `["operations", i, "args", …, "$ref"]`. In rc.1 a plan read it as a malformed reference:
|
|
736
|
+
`A2007` with `V1016` at `…, "$ref", "operation"`. A `$ref` holding an object is still a reference, and a broken one
|
|
737
|
+
still `A2007`.
|
|
738
|
+
- `AvJsonValue` admits a key holding `undefined`, so an object with optional properties
|
|
739
|
+
(`{ readonly note?: string }`) is assignable to a `json` field; the key is left out at runtime. In rc.1 the type
|
|
740
|
+
refused it. An array element holding `undefined` is still a type error (at runtime it is `null`).
|
|
741
|
+
- `AvErrorCatalog` (and `ERRORS.md`): `V1001` lists two directives in one scalar action object among its causes,
|
|
742
|
+
`A2004` a computed value the validators refuse, `A3000` a computed value of the wrong type, and `A3004` a reference
|
|
743
|
+
a pipe or write compute wrote together with its relation.
|
|
744
|
+
- A scalar action object holds one directive. `{ score: { $increment: 1, $decrement: 2 } }` is refused before the
|
|
745
|
+
adapter with `A2004` and `V1001` at the field (`Scalar mutation action objects may contain only one directive.`),
|
|
746
|
+
and is a type error on the façades, the builders and the generated client. In rc.1 it passed validation and the
|
|
747
|
+
ORM refused it: `500 A3001`. A directive the field does not offer is still `V1006` at the directive.
|
|
748
|
+
- An `undefined` inside a `json` value is what JSON makes of it, on every surface: a key holding it is left out and an
|
|
749
|
+
array element holding it (or a hole) is `null`, as `JSON.stringify` writes them, so
|
|
750
|
+
`{ meta: { a: undefined, b: [undefined] } }` stores `{"b":[null]}` through `framework.application`,
|
|
751
|
+
`framework.client`, `framework.execute`, the ctx executor and a plan step, as it does through the generated client
|
|
752
|
+
and over HTTP. The same holds in a `json` filter operand, and an operation's fingerprint is computed from the value
|
|
753
|
+
as JSON carries it. In rc.1 the in-process surfaces refused the key with `A2004`/`V1001`, and a hole in an array
|
|
754
|
+
inside a `json` value was dropped, moving the elements after it down one index.
|
|
755
|
+
- `DATABASE_REFUSALS` has two more sentences an adapter answers a refused value with, both `V1004`:
|
|
756
|
+
`The database refused this value: it divides by zero.` and `The database refused this value: the write computes no
|
|
757
|
+
value for a column that requires one, as a division by zero does.`
|
|
758
|
+
- `AvErrorCatalog`'s `A2004` entry (and `ERRORS.md`) covers a value the database refused: an adapter answers it with
|
|
759
|
+
one issue at `["arguments"]`, `V1004` for a length or range, `V1001` otherwise, and the issue's message says what
|
|
760
|
+
the database refused (`The database refused this value: its text cannot contain the NUL character (U+0000).`).
|
|
761
|
+
The response boundary keeps such a message only when it is core's own sentence for that refusal under its own
|
|
762
|
+
code; every other adapter issue message is still replaced by the code's published text.
|
|
763
|
+
- An `appTx` or `clientTx` builder no longer runs a getter inside an object carrying `$ref` when it copies a step's
|
|
764
|
+
arguments: it mirrors the object as `framework.executeTransaction` does, and the run refuses it the same way
|
|
765
|
+
(`A2004`, `V1001` at the extra key). In rc.1 the builder ran the getter at the call.
|
|
766
|
+
- `AvProtocol.bind(framework).handleOperation` copies a `{ kind: "parsed" }` body at the call, before it awaits the
|
|
767
|
+
state seed, so a host that changes its parsed object after the call changes nothing, and a body that cannot be
|
|
768
|
+
copied is refused at the call (`A2004`, `V1001` at its path). In rc.1 the copy was taken after the first await.
|
|
769
|
+
- `AvProtocol.bind(framework).handleTransaction` takes a `{ kind: "parsed" }` plan at the call, before it awaits the
|
|
770
|
+
state seed, as `framework.executeTransaction` takes one: the plan's structure is mirrored by its property
|
|
771
|
+
descriptors, so no getter the plan validator refuses runs, and each step's arguments are copied, its `$ref` control
|
|
772
|
+
objects mirrored. A host that changes the plan after the call changes nothing, and a step whose arguments cannot be
|
|
773
|
+
copied is answered at its turn (`A2004`, `V1001` at its path). In rc.1 the plan was read after the first await, so a
|
|
774
|
+
step changed after the call failed its fingerprint (`A2007`, `V1017`) or ran as changed.
|
|
775
|
+
- `framework.appTx.$sql` mirrors a `$ref` control object among its values as the builders mirror one in a step's
|
|
776
|
+
arguments: no getter inside it runs, and a malformed one is refused as `framework.executeTransaction` refuses it
|
|
777
|
+
(`A2004`, `V1001` at its extra key). A well-formed hand-written `{ $ref: { operation, fingerprint, path } }` is read
|
|
778
|
+
as a reference to that step's field, held to the same checks as one in an operation step. In rc.1 the value was
|
|
779
|
+
copied by value at the call, which ran its getters, and bound as JSON text.
|
|
780
|
+
- An `orderBy` value that is no order says what is wrong with it. A `$ref` there is refused with `A transaction
|
|
781
|
+
reference cannot stand in orderBy: order "email" by "asc", "desc" or { sort, nulls }.`, and a malformed null-order
|
|
782
|
+
object with `Null ordering for "email" must be exactly { sort: "asc" | "desc", nulls: "first" | "last" }.`. The
|
|
783
|
+
code (`V1001`) and path are unchanged; rc.1 answered both with `Extended null ordering for "email" must contain sort
|
|
784
|
+
and nulls.`
|
|
785
|
+
- Each filter operator object stands alone: a `mode` or `path` qualifies only the operators written beside it, and an
|
|
786
|
+
object under `not` has its own. A `mode` or `path` with no comparing operator beside it is refused with `A2004` and
|
|
787
|
+
`V1001` at that key, before the adapter, with the message `mode on field "title" qualifies no operator: write it next
|
|
788
|
+
to the operator it applies to (inside "not" to apply to a negated one).`. That covers `{ mode: "insensitive", not: {
|
|
789
|
+
contains: "x" } }` (write `{ not: { contains: "x", mode: "insensitive" } }`), `{ path: ["label"], not: { … } }`
|
|
790
|
+
(write the `path` inside the `not`), and a `mode` or `path` alone, such as `{ title: { mode: "default" } }`, which
|
|
791
|
+
reached the adapter as no condition and matched every row. A value under `not` (`{ path: ["label"], not: "x" }`)
|
|
792
|
+
compares at its own level and is accepted. The typed calls refuse the same objects at compile time.
|
|
793
|
+
- An error catalog: `AvErrorCatalog`, exported from `@aventara/core`, maps every outcome code (`A1000` to `A4002`),
|
|
794
|
+
validation code (`V1000` to `V1021`) and startup code (`CONFIG_…`, `DISCOVERY_…`, `COMPILER_…`) to its `kind`,
|
|
795
|
+
`message`, `cause`, `fix` and `docs` URL, the code's entry on the error-codes page
|
|
796
|
+
(`https://aventara-framework.vercel.app/docs/error-codes#a2004`: the code in lower case). The package also ships the
|
|
797
|
+
catalog as `ERRORS.md`, one table per kind. New names: `AvErrorCatalog` (a value and its type),
|
|
798
|
+
`AvErrorCatalogEntry`, `AvErrorCatalogCode`, `AvErrorKind`.
|
|
799
|
+
- A startup diagnostic links to its documentation: every entry of `AvConstructionError`'s `diagnostics` is now
|
|
800
|
+
`{ code, path, message, docs }`, where `docs` is `AvErrorCatalog[code].docs`. Codes, paths and messages are
|
|
801
|
+
unchanged. A test that compares a whole diagnostic with `toEqual` now sees the `docs` key.
|
|
802
|
+
- A spread of the arguments compiles on a Resource with a `json` field. A variant pipe returning
|
|
803
|
+
`{ ...args, data: { ...args.data, slug } }`, or a guard spreading `ctx.$args.data`, was TS2589 ("Type instantiation
|
|
804
|
+
is excessively deep and possibly infinite"), inline and in `defineResource`: the deep read-only typing re-entered
|
|
805
|
+
`AvJsonValue` through its array member without end. `AvDeepReadonly` now maps an array through its own
|
|
806
|
+
`readonly …[]` branch (a tuple keeps its positions); the types a stage is told are unchanged, and a wrong value in a
|
|
807
|
+
pipe's return is still a compile error.
|
|
808
|
+
- An explicit `undefined` in write data means the key was not sent, as Prisma reads its data. In a create's or
|
|
809
|
+
update's `data`, an upsert's `create` and `update`, each record of a bulk create and every nested create or update,
|
|
810
|
+
`{ name: undefined }` is now `{}`: on a create an optional field takes what leaving it out gives it and a required one
|
|
811
|
+
is missing (`A2004` with `V1000` at its path); an update leaves the field untouched. Before, it was refused as
|
|
812
|
+
`A2004`/`V1001` "Value must be a string.". The same holds for the in-process façades, ctx calls, `appTx` plans and
|
|
813
|
+
the arguments a pipe or a transaction pipe returns: an optional field a pipe sets to `undefined` is omitted, a
|
|
814
|
+
required one is a dropped field (`A3004`). Filters and unique selectors are unchanged: `where: { id: undefined }` is
|
|
815
|
+
still refused (`V1001`), so an `update.many` or `delete.many` never widens to every row. The refusal says why, in the
|
|
816
|
+
generated client's words where they apply in process: `Value must not be undefined: it would be left out, and the
|
|
817
|
+
request would reach more rows.` (it said what the value is not, such as `Value must be a string.`). This holds at
|
|
818
|
+
any depth of a filter, a unique selector or a cursor: an operator's operand, an `in` entry, an `AND` entry, a `NOT`,
|
|
819
|
+
a relation filter, a relation's `where` under `select` or `include`, and a nested write's `$connect`, `$disconnect`,
|
|
820
|
+
`$set` and `$update` selectors. Code and path are unchanged.
|
|
821
|
+
- An `undefined` `mode`, `isEmpty` or `path` in a filter is refused (`A2004` with `V1001` at the operand), as every
|
|
822
|
+
other operator's `undefined` already was. These three are passed to the adapter as given, so
|
|
823
|
+
`{ tags: { isEmpty: undefined } }` reached it as no condition and matched every row.
|
|
824
|
+
- A filter operand of the wrong type is refused (`A2004` with `V1001` at the operand) before the adapter is called:
|
|
825
|
+
`mode` takes `"default"`, `"sensitive"` or `"insensitive"`, `isEmpty` a boolean, `path` an array of strings (a wrong
|
|
826
|
+
entry is named at its index) and a `json` field's `stringContains`, `stringStartsWith` and `stringEndsWith` a string.
|
|
827
|
+
Before, a value such as `mode: "loud"`, `isEmpty: 3`, `path: "a.b"` or `stringContains: 3` from an HTTP or MCP caller
|
|
828
|
+
reached the adapter, and the ORM refused it as `A3001`. The typed calls and the generated client type these operands
|
|
829
|
+
the same way: `mode`, `isEmpty` and `path` were `unknown`, and a `json` `string*` operand accepted any non-null JSON
|
|
830
|
+
value.
|
|
831
|
+
- Filter modes are advertised per field. `mode` has three values: `"default"` is the database's own comparison and
|
|
832
|
+
every field that offers `mode` takes it; `"sensitive"` and `"insensitive"` are an explicit promise, taken only where
|
|
833
|
+
the field's new `capabilities.filterModes` lists them (and, on a `json` field that offers `path`,
|
|
834
|
+
`capabilities.pathFilterModes` when the filter names a `path`). The Contract and the ClientContract carry both lists,
|
|
835
|
+
configuration never names them, and a layer that hides `mode` or `path` drops them. A mode the field does not offer is
|
|
836
|
+
refused like an unadvertised operator: `A2004` with `V1006` at the `mode`, `Filter mode "insensitive" is not available
|
|
837
|
+
on field "email".`; the typed calls offer only the field's modes, and on a `json` field the list that applies: an
|
|
838
|
+
operator object with `path` takes `pathFilterModes`, one without it `filterModes`. On Postgres
|
|
839
|
+
`where: { meta: { stringContains: "x", mode: "insensitive" } }` is a compile error (`Property 'path' is missing`),
|
|
840
|
+
and the same filter with `path: ["label"]` compiles. Contract hashes change for every field that offers `mode`:
|
|
841
|
+
regenerate the client.
|
|
842
|
+
- A `where`, `limit`, `offset` or `cursor` argument set to `undefined` is refused with a message that says what to
|
|
843
|
+
write: `where must not be undefined: leave the key out to mean no filter.` (and the same for `limit`, `offset` and
|
|
844
|
+
`cursor`), at the top level and in a relation under `select` or `include`. The code and path are unchanged
|
|
845
|
+
(`A2004` with `V1001` at the key; `V1009` for `cursor`). A wrong value keeps its message, such as
|
|
846
|
+
`where must be an object.` or `limit must be a non-negative integer.`.
|
|
847
|
+
- 9366e16: MCP endpoint polish:
|
|
848
|
+
|
|
849
|
+
- A request with no bearer credentials (no `Authorization` header, or another scheme such as `Basic`) is answered
|
|
850
|
+
`401` with `WWW-Authenticate: Bearer resource_metadata="…"` and no error code, as RFC 6750 §3.1 asks; its body no
|
|
851
|
+
longer says "Missing Authorization header". A bad bearer token keeps `error="invalid_token"`.
|
|
852
|
+
- A `mcp.authorization.verify` that throws is still answered `401`, and is now reported to the `diagnostics` sink as
|
|
853
|
+
an `AvMcpDiagnostic` holding the error.
|
|
854
|
+
- With `mcp` configured and `@aventara/mcp` missing, the startup error names the command that installs it at the
|
|
855
|
+
installed `@aventara/core`'s exact version: `npm install --save-exact @aventara/mcp@<version>`.
|
|
856
|
+
|
|
857
|
+
- 9366e16: A variant's guards, pipes and filters may leave out of a create exactly the fields your configuration's
|
|
858
|
+
`compute.create` stages fill. Write compute runs after those stages, so a field it fills may still be missing when
|
|
859
|
+
they run, and a pipe may return a create without it: in a pipe, `args.data.slug` is `string | undefined` when
|
|
860
|
+
`slug` has a `compute.create`, and every other required field stays required. A stage's type cannot read the
|
|
861
|
+
configuration it is written in, so the adapter's generate step reads the configuration's type (`aventara:prepare`
|
|
862
|
+
passes `--config src/aventara.config.ts`) and writes which fields each layer computes into the model's type. A
|
|
863
|
+
stage counts the layers whose compute runs for its requests: a root stage the root's, the application's, the
|
|
864
|
+
client's and the agent's; an `application` or `client` stage the root's and its own (and the agent's, when the agent
|
|
865
|
+
extends it); an `agent` stage the root's, its own and its parent's. This holds inline in `createFramework`, in
|
|
866
|
+
`AventaraModule`'s `config`, and in `defineResource` blocks and their overlays, for a create's `data`, an upsert's
|
|
867
|
+
`create` and a nested create. Values are still typed. Hooks and interceptors run after compute and keep every
|
|
868
|
+
requirement. A model generated without a configuration lifts every create requirement in those stages, as before.
|
|
869
|
+
- Breaking: a create missing a required field is refused before any pipeline stage runs. The framework's own
|
|
870
|
+
validation (types, unknown fields, structure) now also checks that every create position holds the fields its
|
|
871
|
+
layer requires (`A2004`/`V1000` at the field), before the guards, where it ran after the pipes and compute. A field a
|
|
872
|
+
`compute.create` stage fills is not required there and stays optional until compute. So a guard or a pipe is always
|
|
873
|
+
told a create that holds every other required field, which is what their types say, and a pipe can no longer fill a
|
|
874
|
+
required field the caller left out: give that field a `compute.create` stage or a database default. This holds for
|
|
875
|
+
a create's `data`, every bulk record, an upsert's `create` branch and nested creates, and for each step of a
|
|
876
|
+
transaction before it opens (a `$ref` counts as a value). Which fields are required is already public in the client
|
|
877
|
+
contract, so the earlier answer reveals nothing new.
|
|
878
|
+
- A required create field the caller sent and a pipe then dropped is now the server's error: the caller gets `A3004`
|
|
879
|
+
with the generic pipe-output message, and the `diagnostics` sink gets an `AvPipeOutputError` holding the `V1000`
|
|
880
|
+
issue. Until now the caller got `A2004`/`V1000`, as if it had left the field out.
|
|
881
|
+
- New: `AvConfigComputedFields`, the shape of that statement: per layer (`root`, `application`, `client`, `agent`),
|
|
882
|
+
per Resource, the fields with a `compute.create` and a `compute.update` stage, and the layer the agent extends. The
|
|
883
|
+
adapter hands it to the framework as `AvAdapter.computedFields`, and the framework compares it with the
|
|
884
|
+
configuration it starts with. When they differ (you changed a compute stage and did not run `aventara:prepare`),
|
|
885
|
+
startup emits one warning, `AVENTARA_STALE_GENERATED_TYPES`, naming each difference:
|
|
886
|
+
|
|
887
|
+
```text
|
|
888
|
+
(node:1234) [AVENTARA_STALE_GENERATED_TYPES] AventaraWarning: generated types are stale: run aventara:prepare. The computed fields the pre-compute stages are typed with differ from the configuration's:
|
|
889
|
+
- computed in the configuration, not in the types: root User.handle (update)
|
|
890
|
+
```
|
|
891
|
+
|
|
892
|
+
It is a warning and changes nothing at runtime: the statement only types the stages.
|
|
893
|
+
|
|
894
|
+
- A `compute.create` stage that returns `undefined` for a field a create cannot do without (not nullable, no default,
|
|
895
|
+
not generated) and that the caller did not send now fails as the server's error: the caller gets `A3000`, and the
|
|
896
|
+
`diagnostics` sink gets an `AvComputedValueError` whose message is
|
|
897
|
+
`compute.create on <Resource>.<field> returned undefined for a required field`. The field left the create
|
|
898
|
+
requirements because the stage fills it; until now the create reached the database without it, and the
|
|
899
|
+
database's refusal answered `A3001`. This holds for a create's `data`, every record of a bulk create, an upsert's `create`
|
|
900
|
+
branch and a nested create. A reference field counts as missing only when the record writes neither its relation nor
|
|
901
|
+
every reference field, and a nested create's field the parent supplies is never missing. Updates are not checked:
|
|
902
|
+
nothing is required of an update.
|
|
903
|
+
- 9366e16: A Pick layer (`exposed: false`) that names what its parent hid now says why in the type error. A Resource, field,
|
|
904
|
+
family, variant or capability the root (or, for the agent, the layer it extends) hid is typed as
|
|
905
|
+
`readonly ["Error: client cannot expose 'Post.internalNote': hidden by root"]`, so the error TypeScript reports through
|
|
906
|
+
the scaffold's `aventaraConfig()` and `AventaraModule.forRootAsync` ends at:
|
|
907
|
+
|
|
908
|
+
```text
|
|
909
|
+
The types of 'client.exemptions.resources.Post.fields.internalNote' are incompatible between these types.
|
|
910
|
+
Type 'readonly ["select"]' is not assignable to type 'readonly ["Error: client cannot expose 'Post.internalNote': hidden by root"]'.
|
|
911
|
+
Type '"select"' is not assignable to type '"Error: client cannot expose 'Post.internalNote': hidden by root"'.
|
|
912
|
+
```
|
|
913
|
+
|
|
914
|
+
where it ended at `Type 'readonly ["select"]' is not assignable to type 'never'.` An entry written `true` names it
|
|
915
|
+
the same way (`Type 'boolean' is not assignable to type 'readonly ["Error: client cannot expose 'Comment': hidden by
|
|
916
|
+
root"]'.`), where it ended at `Type 'true' is not assignable to type 'never'.`. Written inline in `createFramework`
|
|
917
|
+
or `AventaraModule.forRoot`, the error at a `true` entry starts with the sentence:
|
|
918
|
+
`Type 'true' is not assignable to type 'true & (readonly ["Error: client cannot expose 'Comment': hidden by root"] & …)'`.
|
|
919
|
+
An inline literal's list still reads `not assignable to type 'never'` at the key.
|
|
920
|
+
|
|
921
|
+
## 1.0.0-rc.1
|
|
922
|
+
|
|
923
|
+
### Patch Changes
|
|
924
|
+
|
|
925
|
+
- 800123b: New: `framework.appTx.$sql`, a developer SQL step in an application transaction. Write it as a tagged template,
|
|
926
|
+
`` framework.appTx.$sql<Row>`select pgmq.send('jobs', jsonb_build_object('postId', ${post.$ref("id")}))` ``, and put it in
|
|
927
|
+
`framework.transaction([...])` beside the other steps. Every interpolated value is a bound parameter, never SQL text:
|
|
928
|
+
strings, numbers, booleans, `bigint`, `null`, `Date`, `Decimal`, bytes, JSON values (bound as JSON text, so cast them
|
|
929
|
+
with `::jsonb`), and a `$ref` to an earlier step's field, read after that step ran. The statement runs in order in the
|
|
930
|
+
same database transaction, so a failure anywhere rolls everything back, and its rows are that step's entry in the
|
|
931
|
+
result, typed `Row[]` (or `unknown[]`). A plain-string call is refused, a later step cannot `$ref` a SQL step's rows,
|
|
932
|
+
and no pipeline runs around it.
|
|
933
|
+
|
|
934
|
+
It is server-side only: `clientTx` has no `$sql`, the ClientContract and its hash are unchanged, and `/_transactions`
|
|
935
|
+
cannot carry one.
|
|
936
|
+
|
|
937
|
+
The Prisma 7 adapter runs it with `$queryRaw` in its tagged form on the transaction client. A raw query's failure is
|
|
938
|
+
`P2010` on Prisma 7.10.0: a uniqueness or foreign-key violation answers `A2008`, anything else `A3001`. The rule is now
|
|
939
|
+
that Aventara never writes raw SQL itself, and developer SQL only goes through this bound template.
|
|
940
|
+
|
|
941
|
+
Adapter authors: an adapter declares `sqlStatements: true`, has an `executeSql` of its own (a plain statement, outside
|
|
942
|
+
any transaction: a stage's `ctx.$sql` there) and gives its transaction executor one too (`AvAdapter.executeSql`,
|
|
943
|
+
`AvAdapterTransactionExecutor`, `AvAdapterSqlStatement` in `@aventara/core/internal`). Without the declaration, a plan
|
|
944
|
+
with a SQL step is refused with `A2002`. The Prisma 7 adapter runs a statement outside a transaction with `$queryRaw`
|
|
945
|
+
on the base client, and declares `sqlStatements` with every driver.
|
|
946
|
+
|
|
947
|
+
- 8bb1d6a: Breaking: public type and class names take the `Av` prefix. Every public type and class is now `Av` plus what
|
|
948
|
+
it is, with a namespace only where the bare name would be ambiguous: configuration types are `AvConfig…`
|
|
949
|
+
(`AvConfig`, `AvConfigLayer`, `AvConfigResource`, …) and the three Contracts are `AvContract…` (`AvContractCore`,
|
|
950
|
+
`AvContractApplication`, `AvContractClient`). Functions keep their names (`createFramework`, `createFakeAdapter`,
|
|
951
|
+
`createPrismaAdapter`, `defineClientConfig`, `env`), and so do the names that already started with `Av`,
|
|
952
|
+
`AventaraModule`, `AventaraModuleOptions` and `AventaraModuleAsyncOptions`. There are no
|
|
953
|
+
aliases: replace each old name with the new one.
|
|
954
|
+
|
|
955
|
+
Three names merged or left:
|
|
956
|
+
|
|
957
|
+
- `PipelineContext` and `ComputeContext` (which was the same type) are one name, `AvContext`. A compute stage
|
|
958
|
+
now gets its own read-only ctx, `AvComputeContext` (see the ctx entry below).
|
|
959
|
+
- `ComputedRead` is `AvComputeRead`, the one name of a read compute stage: a function, or `{ compute, dependsOn }`.
|
|
960
|
+
`ComputeOnRead`, the bare function type, is no longer exported; `AvComputeRead` accepts the function as it is.
|
|
961
|
+
- `defineFields` and `FrameworkFieldsShape` are removed; `defineResource` replaces them (below).
|
|
962
|
+
|
|
963
|
+
`defineResource` replaces `defineFields`. A Resource's configuration, its root block and its overlay blocks, can
|
|
964
|
+
live in its own module and be spread into each layer's `resources` tree:
|
|
965
|
+
|
|
966
|
+
```ts
|
|
967
|
+
export const submission = defineResource<Model>()(
|
|
968
|
+
"Submission",
|
|
969
|
+
{
|
|
970
|
+
pipelines: { guards: [signedIn] },
|
|
971
|
+
fields: { title: { pipelines: { pipes: [trim] } } },
|
|
972
|
+
},
|
|
973
|
+
{ client: { fields: { title: { pipelines: { validators: [AvZ.maxLength(80)] } } } } },
|
|
974
|
+
);
|
|
975
|
+
|
|
976
|
+
// aventara.config.ts
|
|
977
|
+
resources: { ...submission.root, ...payment.root },
|
|
978
|
+
client: { resources: { ...submission.client } },
|
|
979
|
+
```
|
|
980
|
+
|
|
981
|
+
It returns `{ root, application?, client? }`, each `{ Submission: block }`; a layer given no block is left out. The
|
|
982
|
+
root block is typed exactly as `resources.Submission` is typed in place: field definitions are checked against what
|
|
983
|
+
the adapter discovered, stages are typed for the Resource, and a key no level admits is a type error at the key.
|
|
984
|
+
Each overlay block is typed as `application.resources.Submission` or `client.resources.Submission` is in place,
|
|
985
|
+
against the root block: it may name a field the root block defines, and a `definition` in it is a type error (and
|
|
986
|
+
`CONFIG_FORBIDDEN_IN_OVERLAY` at startup). Spread, the configuration is the inline one: the same Contracts and
|
|
987
|
+
ClientContract hash. A field `definition` that `defineFields` returned now sits at
|
|
988
|
+
`fields.<field>.definition` inside the block.
|
|
989
|
+
|
|
990
|
+
The generated client's names follow the same rule. Regenerate with `avclient generate`; then rename the
|
|
991
|
+
imports from `AvClient.ts`:
|
|
992
|
+
|
|
993
|
+
| Before | After |
|
|
994
|
+
| ----------------------- | ------------------------- |
|
|
995
|
+
| `AuthError` | `AvAuthError` |
|
|
996
|
+
| `CallOptions` | `AvCallOptions` |
|
|
997
|
+
| `Cause` | `AvCause` |
|
|
998
|
+
| `ConflictError` | `AvConflictError` |
|
|
999
|
+
| `ContractMismatchError` | `AvContractMismatchError` |
|
|
1000
|
+
| `Decimal` | `AvDecimal` |
|
|
1001
|
+
| `Fetch` | `AvFetch` |
|
|
1002
|
+
| `FrameworkError` | `AvError` |
|
|
1003
|
+
| `InternalError` | `AvInternalError` |
|
|
1004
|
+
| `NotFoundError` | `AvNotFoundError` |
|
|
1005
|
+
| `Operation<T>` | `AvOperation<T>` |
|
|
1006
|
+
| `OperationCode` | `AvOperationCode` |
|
|
1007
|
+
| `ProtocolError` | `AvProtocolError` |
|
|
1008
|
+
| `TransportError` | `AvTransportError` |
|
|
1009
|
+
| `ValidationCode` | `AvValidationCode` |
|
|
1010
|
+
| `ValidationError` | `AvValidationError` |
|
|
1011
|
+
| `ValidationIssue` | `AvValidationIssue` |
|
|
1012
|
+
|
|
1013
|
+
An enum whose name is one of these now walks the rename ladder (`AvDecimal` → `AvDecimalEnum`); an enum named
|
|
1014
|
+
`Decimal` is emitted as `Decimal`.
|
|
1015
|
+
|
|
1016
|
+
`@aventara/core`, every renamed export:
|
|
1017
|
+
|
|
1018
|
+
| Before | After |
|
|
1019
|
+
| ------------------------------------- | ---------------------------------- |
|
|
1020
|
+
| `Adapter` | `AvAdapter` |
|
|
1021
|
+
| `AdapterDiscoveryDiagnostic` | `AvAdapterDiscoveryDiagnostic` |
|
|
1022
|
+
| `AdapterDiscoveryDiagnosticCode` | `AvAdapterDiscoveryDiagnosticCode` |
|
|
1023
|
+
| `AdapterModel` | `AvAdapterModel` |
|
|
1024
|
+
| `AddedFieldDefinition` | `AvConfigAddedFieldDefinition` |
|
|
1025
|
+
| `AfterHook` | `AvAfterHook` |
|
|
1026
|
+
| `AnyFrameworkConfig` | `AvConfigAny` |
|
|
1027
|
+
| `AnyLayerConfig` | `AvConfigLayerAny` |
|
|
1028
|
+
| `AnyResourcesBlock` | `AvConfigResourcesAny` |
|
|
1029
|
+
| `ApplicationContract` | `AvContractApplication` |
|
|
1030
|
+
| `ApplicationScalarForms` | `AvApplicationScalarForms` |
|
|
1031
|
+
| `BeforeHook` | `AvBeforeHook` |
|
|
1032
|
+
| `BuiltInScalar` | `AvBuiltInScalar` |
|
|
1033
|
+
| `Cause` | `AvCause` |
|
|
1034
|
+
| `ClientContract` | `AvContractClient` |
|
|
1035
|
+
| `ClientContractProtocol` | `AvContractClientProtocol` |
|
|
1036
|
+
| `ComputedRead` | `AvComputeRead` |
|
|
1037
|
+
| `ComputeOnCreate` | `AvComputeCreate` |
|
|
1038
|
+
| `ComputeOnUpdate` | `AvComputeUpdate` |
|
|
1039
|
+
| `ConfigDiagnostic` | `AvConfigDiagnostic` |
|
|
1040
|
+
| `ConfigDiagnosticCode` | `AvConfigDiagnosticCode` |
|
|
1041
|
+
| `ConfigFieldKey` | `AvConfigFieldKey` |
|
|
1042
|
+
| `ConfigLayer` | `AvConfigLayerName` |
|
|
1043
|
+
| `Contract` | `AvContract` |
|
|
1044
|
+
| `ContractCompilerDiagnostic` | `AvContractCompilerDiagnostic` |
|
|
1045
|
+
| `ContractCompilerDiagnosticCode` | `AvContractCompilerDiagnosticCode` |
|
|
1046
|
+
| `ContractHash` | `AvContractHash` |
|
|
1047
|
+
| `ContractLimits` | `AvContractLimits` |
|
|
1048
|
+
| `ContractProtocol` | `AvContractProtocol` |
|
|
1049
|
+
| `CoreContract` | `AvContractCore` |
|
|
1050
|
+
| `CreateDataInput` | `AvCreateDataInput` |
|
|
1051
|
+
| `CursorInput` | `AvCursorInput` |
|
|
1052
|
+
| `Decimal` | `AvDecimal` |
|
|
1053
|
+
| `DeferredOperationCall` | `AvDeferredOperationCall` |
|
|
1054
|
+
| `EffectiveFieldKey` | `AvEffectiveFieldKey` |
|
|
1055
|
+
| `EnumDefinition` | `AvEnumDefinition` |
|
|
1056
|
+
| `ExactFrameworkConfig` | `AvConfigExact` |
|
|
1057
|
+
| `ExecutionOptions` | `AvExecutionOptions` |
|
|
1058
|
+
| `Field` | `AvField` |
|
|
1059
|
+
| `FieldComputeConfig` | `AvFieldCompute` |
|
|
1060
|
+
| `FieldKey` | `AvFieldKey` |
|
|
1061
|
+
| `FieldPipe` | `AvFieldPipe` |
|
|
1062
|
+
| `FieldWriteGuard` | `AvFieldGuard` |
|
|
1063
|
+
| `Filter` | `AvFilter` |
|
|
1064
|
+
| `Framework` | `AvFramework` |
|
|
1065
|
+
| `FrameworkConfig` | `AvConfig` |
|
|
1066
|
+
| `FrameworkConstructionDiagnostic` | `AvConstructionDiagnostic` |
|
|
1067
|
+
| `FrameworkConstructionError` | `AvConstructionError` |
|
|
1068
|
+
| `FrameworkConstructionStage` | `AvConstructionStage` |
|
|
1069
|
+
| `FrameworkContracts` | `AvContracts` |
|
|
1070
|
+
| `FrameworkDiagnostic` | `AvDiagnostic` |
|
|
1071
|
+
| `FrameworkDiagnosticsSink` | `AvDiagnosticsSink` |
|
|
1072
|
+
| `FrameworkError` | `AvError` |
|
|
1073
|
+
| `FrameworkExemptionsConfig` | `AvConfigExemptions` |
|
|
1074
|
+
| `FrameworkFamilyConfig` | `AvConfigFamily` |
|
|
1075
|
+
| `FrameworkFieldDefinition` | `AvConfigFieldDefinition` |
|
|
1076
|
+
| `FrameworkFieldLifecycleTrait` | `AvConfigFieldLifecycleTrait` |
|
|
1077
|
+
| `FrameworkLayerConfig` | `AvConfigLayer` |
|
|
1078
|
+
| `FrameworkLayerKind` | `AvConfigLayerKind` |
|
|
1079
|
+
| `FrameworkLimitsConfig` | `AvConfigLimits` |
|
|
1080
|
+
| `FrameworkMisuseEvent` | `AvMisuseEvent` |
|
|
1081
|
+
| `FrameworkMisuseSink` | `AvMisuseSink` |
|
|
1082
|
+
| `FrameworkOperationsConfig` | `AvConfigOperations` |
|
|
1083
|
+
| `FrameworkOperationsExemption` | `AvConfigOperationsExemption` |
|
|
1084
|
+
| `FrameworkOverlayFieldConfig` | `AvConfigOverlayField` |
|
|
1085
|
+
| `FrameworkRelationFieldExemption` | `AvConfigRelationFieldExemption` |
|
|
1086
|
+
| `FrameworkResourceConfig` | `AvConfigResource` |
|
|
1087
|
+
| `FrameworkResourceExemption` | `AvConfigResourceExemption` |
|
|
1088
|
+
| `FrameworkResourceFieldsConfig` | `AvConfigResourceFields` |
|
|
1089
|
+
| `FrameworkResourceKey` | `AvConfigResourceKey` |
|
|
1090
|
+
| `FrameworkResourcesConfig` | `AvConfigResources` |
|
|
1091
|
+
| `FrameworkResourceTransactionsConfig` | `AvConfigResourceTransactions` |
|
|
1092
|
+
| `FrameworkRootFieldConfig` | `AvConfigRootField` |
|
|
1093
|
+
| `FrameworkScalarFieldExemption` | `AvConfigScalarFieldExemption` |
|
|
1094
|
+
| `FrameworkScalarTypeRef` | `AvConfigScalarTypeRef` |
|
|
1095
|
+
| `FrameworkVariantConfig` | `AvConfigVariant` |
|
|
1096
|
+
| `Guard` | `AvGuard` |
|
|
1097
|
+
| `Identifier` | `AvIdentifier` |
|
|
1098
|
+
| `IncludeInput` | `AvIncludeInput` |
|
|
1099
|
+
| `Interceptor` | `AvInterceptor` |
|
|
1100
|
+
| `JsonValue` | `AvJsonValue` |
|
|
1101
|
+
| `KnownFieldDefinition` | `AvConfigKnownFieldDefinition` |
|
|
1102
|
+
| `LifecycleTrait` | `AvLifecycleTrait` |
|
|
1103
|
+
| `NullsOrder` | `AvNullsOrder` |
|
|
1104
|
+
| `Operation` | `AvOperation` |
|
|
1105
|
+
| `OperationArgumentMap` | `AvOperationArgumentMap` |
|
|
1106
|
+
| `OperationArgumentsFor` | `AvOperationArgumentsFor` |
|
|
1107
|
+
| `OperationAvailability` | `AvOperationAvailability` |
|
|
1108
|
+
| `OperationCall` | `AvOperationCall` |
|
|
1109
|
+
| `OperationCode` | `AvOperationCode` |
|
|
1110
|
+
| `OperationDiagnostic` | `AvOperationDiagnostic` |
|
|
1111
|
+
| `OperationErrorCode` | `AvOperationErrorCode` |
|
|
1112
|
+
| `OperationFailureResponse` | `AvOperationFailureResponse` |
|
|
1113
|
+
| `OperationFamily` | `AvOperationFamily` |
|
|
1114
|
+
| `OperationGrammar` | `AvOperationGrammar` |
|
|
1115
|
+
| `OperationIdentity` | `AvOperationIdentity` |
|
|
1116
|
+
| `OperationNoMatchResponse` | `AvOperationNoMatchResponse` |
|
|
1117
|
+
| `OperationReferenceSurface` | `AvOperationReferenceSurface` |
|
|
1118
|
+
| `OperationResponse` | `AvOperationResponse` |
|
|
1119
|
+
| `OperationResultFor` | `AvOperationResultFor` |
|
|
1120
|
+
| `OperationSlot` | `AvOperationSlot` |
|
|
1121
|
+
| `OperationSuccessCode` | `AvOperationSuccessCode` |
|
|
1122
|
+
| `OperationSuccessDataCode` | `AvOperationSuccessDataCode` |
|
|
1123
|
+
| `OperationSuccessResponse` | `AvOperationSuccessResponse` |
|
|
1124
|
+
| `OperationVariant` | `AvOperationVariant` |
|
|
1125
|
+
| `OperationVariantScopes` | `AvOperationVariantScopes` |
|
|
1126
|
+
| `OrderByEntry` | `AvOrderByEntry` |
|
|
1127
|
+
| `OrderByInput` | `AvOrderByInput` |
|
|
1128
|
+
| `PaginationCapability` | `AvPaginationCapability` |
|
|
1129
|
+
| `Pipe` | `AvPipe` |
|
|
1130
|
+
| `PipelineConfig` | `AvPipeline` |
|
|
1131
|
+
| `PipelineContext` | `AvContext` |
|
|
1132
|
+
| `PipelineTransportContext` | `AvTransportContext` |
|
|
1133
|
+
| `ProjectionEntry` | `AvProjectionEntry` |
|
|
1134
|
+
| `ProjectionScope` | `AvProjectionScope` |
|
|
1135
|
+
| `ProjectionScopes` | `AvProjectionScopes` |
|
|
1136
|
+
| `ReferenceableField` | `AvReferenceableField` |
|
|
1137
|
+
| `ReferenceBuilder` | `AvReferenceBuilder` |
|
|
1138
|
+
| `RelationField` | `AvRelationField` |
|
|
1139
|
+
| `RelationFieldCapabilities` | `AvRelationFieldCapabilities` |
|
|
1140
|
+
| `RelationFieldCapabilityName` | `AvRelationFieldCapabilityName` |
|
|
1141
|
+
| `RelationFieldPipelineConfig` | `AvRelationFieldPipeline` |
|
|
1142
|
+
| `RelationFilterCapability` | `AvRelationFilterCapability` |
|
|
1143
|
+
| `RelationMutationCapability` | `AvRelationMutationCapability` |
|
|
1144
|
+
| `RelationMutationDirective` | `AvRelationMutationDirective` |
|
|
1145
|
+
| `RelationProjectionCapability` | `AvRelationProjectionCapability` |
|
|
1146
|
+
| `RelationProjectionInput` | `AvRelationProjectionInput` |
|
|
1147
|
+
| `RelationReducer` | `AvRelationReducer` |
|
|
1148
|
+
| `RelationReducerCapability` | `AvRelationReducerCapability` |
|
|
1149
|
+
| `RequestOrigin` | `AvRequestOrigin` |
|
|
1150
|
+
| `Resource` | `AvResource` |
|
|
1151
|
+
| `ResourceFieldMap` | `AvResourceFieldMap` |
|
|
1152
|
+
| `ResourceKey` | `AvResourceKey` |
|
|
1153
|
+
| `ResourceOperations` | `AvResourceOperations` |
|
|
1154
|
+
| `ScalarDefinition` | `AvScalarDefinition` |
|
|
1155
|
+
| `ScalarField` | `AvScalarField` |
|
|
1156
|
+
| `ScalarFieldCapabilities` | `AvScalarFieldCapabilities` |
|
|
1157
|
+
| `ScalarFieldCapabilityName` | `AvScalarFieldCapabilityName` |
|
|
1158
|
+
| `ScalarFieldKey` | `AvScalarFieldKey` |
|
|
1159
|
+
| `ScalarFieldPipelineConfig` | `AvScalarFieldPipeline` |
|
|
1160
|
+
| `ScalarFilterOperator` | `AvScalarFilterOperator` |
|
|
1161
|
+
| `ScalarFormat` | `AvScalarFormat` |
|
|
1162
|
+
| `ScalarMutationDirective` | `AvScalarMutationDirective` |
|
|
1163
|
+
| `ScalarOrderInput` | `AvScalarOrderInput` |
|
|
1164
|
+
| `ScalarRegistry` | `AvScalarRegistry` |
|
|
1165
|
+
| `ScalarValueType` | `AvScalarValueType` |
|
|
1166
|
+
| `ScopedRelationMutations` | `AvScopedRelationMutations` |
|
|
1167
|
+
| `ScopedRelationReducers` | `AvScopedRelationReducers` |
|
|
1168
|
+
| `SelectInput` | `AvSelectInput` |
|
|
1169
|
+
| `SingleRowResult` | `AvSingleRowResult` |
|
|
1170
|
+
| `SortDirection` | `AvSortDirection` |
|
|
1171
|
+
| `TransactionAfterHook` | `AvTransactionAfterHook` |
|
|
1172
|
+
| `TransactionBeforeHook` | `AvTransactionBeforeHook` |
|
|
1173
|
+
| `TransactionCapability` | `AvTransactionCapability` |
|
|
1174
|
+
| `TransactionCommittedResponse` | `AvTransactionCommittedResponse` |
|
|
1175
|
+
| `TransactionDiagnostic` | `AvTransactionDiagnostic` |
|
|
1176
|
+
| `TransactionFilter` | `AvTransactionFilter` |
|
|
1177
|
+
| `TransactionGuard` | `AvTransactionGuard` |
|
|
1178
|
+
| `TransactionIntegrityError` | `AvTransactionIntegrityError` |
|
|
1179
|
+
| `TransactionIntegrityViolation` | `AvTransactionIntegrityViolation` |
|
|
1180
|
+
| `TransactionInterceptor` | `AvTransactionInterceptor` |
|
|
1181
|
+
| `TransactionPipe` | `AvTransactionPipe` |
|
|
1182
|
+
| `TransactionPipelineConfig` | `AvTransactionPipeline` |
|
|
1183
|
+
| `TransactionPipelineContext` | `AvTransactionContext` |
|
|
1184
|
+
| `TransactionReference` | `AvTransactionReference` |
|
|
1185
|
+
| `TransactionResponse` | `AvTransactionResponse` |
|
|
1186
|
+
| `TransactionResults` | `AvTransactionResults` |
|
|
1187
|
+
| `TransactionRunner` | `AvTransactionRunner` |
|
|
1188
|
+
| `TransactionSlotPathError` | `AvTransactionSlotPathError` |
|
|
1189
|
+
| `TransactionSlotPathViolation` | `AvTransactionSlotPathViolation` |
|
|
1190
|
+
| `UniqueWhereInput` | `AvUniqueWhereInput` |
|
|
1191
|
+
| `UpdateDataInput` | `AvUpdateDataInput` |
|
|
1192
|
+
| `ValidationCode` | `AvValidationCode` |
|
|
1193
|
+
| `ValidationIssue` | `AvValidationIssue` |
|
|
1194
|
+
| `WhereInput` | `AvWhereInput` |
|
|
1195
|
+
| `WireScalarForms` | `AvWireScalarForms` |
|
|
1196
|
+
| `WriteScope` | `AvWriteScope` |
|
|
1197
|
+
| `WriteScopes` | `AvWriteScopes` |
|
|
1198
|
+
|
|
1199
|
+
Error classes whose `name` a log or a test may read:
|
|
1200
|
+
|
|
1201
|
+
| Before | After |
|
|
1202
|
+
| ------------------------ | -------------------------- |
|
|
1203
|
+
| `CallerDataError` | `AvCallerDataError` |
|
|
1204
|
+
| `ClientConfigError` | `AvClientConfigError` |
|
|
1205
|
+
| `ComputedValueError` | `AvComputedValueError` |
|
|
1206
|
+
| `ContractProtocolError` | `AvContractProtocolError` |
|
|
1207
|
+
| `ContractTransportError` | `AvContractTransportError` |
|
|
1208
|
+
| `EnvFileError` | `AvEnvFileError` |
|
|
1209
|
+
| `OutputValidationError` | `AvOutputValidationError` |
|
|
1210
|
+
| `OutputWriteError` | `AvOutputWriteError` |
|
|
1211
|
+
| `PipeOutputError` | `AvPipeOutputError` |
|
|
1212
|
+
| `UnmountedRouteError` | `AvUnmountedRouteError` |
|
|
1213
|
+
|
|
1214
|
+
`@aventara/testing`:
|
|
1215
|
+
|
|
1216
|
+
| Before | After |
|
|
1217
|
+
| ------------------------ | -------------------------- |
|
|
1218
|
+
| `AdapterFactory` | `AvAdapterFactory` |
|
|
1219
|
+
| `FakeAdapter` | `AvFakeAdapter` |
|
|
1220
|
+
| `FakeAdapterExecute` | `AvFakeAdapterExecute` |
|
|
1221
|
+
| `FakeAdapterOptions` | `AvFakeAdapterOptions` |
|
|
1222
|
+
| `FakeAdapterRollback` | `AvFakeAdapterRollback` |
|
|
1223
|
+
| `FakeTransactionOutcome` | `AvFakeTransactionOutcome` |
|
|
1224
|
+
| `FakeTransactionTrace` | `AvFakeTransactionTrace` |
|
|
1225
|
+
|
|
1226
|
+
`@aventara/client` (the `framework.client.ts` config types):
|
|
1227
|
+
|
|
1228
|
+
| Before | After |
|
|
1229
|
+
| ------------------- | --------------------- |
|
|
1230
|
+
| `ClientConfigInput` | `AvClientConfigInput` |
|
|
1231
|
+
| `ConfigValue` | `AvClientConfigValue` |
|
|
1232
|
+
| `EnvReference` | `AvEnvReference` |
|
|
1233
|
+
|
|
1234
|
+
`@aventara/prisma7-adapter`: its public types and error classes (`createPrismaAdapter` keeps its name):
|
|
1235
|
+
|
|
1236
|
+
| Before | After |
|
|
1237
|
+
| ---------------------------------- | ------------------------------------ |
|
|
1238
|
+
| `PrismaAdapterModel` | `AvPrismaAdapterModel` |
|
|
1239
|
+
| `PrismaAdapterOptions` | `AvPrismaAdapterOptions` |
|
|
1240
|
+
| `PrismaDelegate` | `AvPrismaDelegate` |
|
|
1241
|
+
| `PrismaDelegateHost` | `AvPrismaDelegateHost` |
|
|
1242
|
+
| `PrismaDelegateIndex` | `AvPrismaDelegateIndex` |
|
|
1243
|
+
| `PrismaDiscoveryArtifact` | `AvPrismaDiscoveryArtifact` |
|
|
1244
|
+
| `PrismaDriver` | `AvPrismaDriver` |
|
|
1245
|
+
| `PrismaIsolationLevel` | `AvPrismaIsolationLevel` |
|
|
1246
|
+
| `PrismaProvider` | `AvPrismaProvider` |
|
|
1247
|
+
| `PrismaProviderProfile` | `AvPrismaProviderProfile` |
|
|
1248
|
+
| `PrismaReferentialAction` | `AvPrismaReferentialAction` |
|
|
1249
|
+
| `PrismaReferentialActionTable` | `AvPrismaReferentialActionTable` |
|
|
1250
|
+
| `PrismaReferentialEdge` | `AvPrismaReferentialEdge` |
|
|
1251
|
+
| `PrismaResourceReferentialActions` | `AvPrismaResourceReferentialActions` |
|
|
1252
|
+
| `PrismaTransactionOptions` | `AvPrismaTransactionOptions` |
|
|
1253
|
+
| `PrismaCapabilityDerivationError` | `AvPrismaCapabilityDerivationError` |
|
|
1254
|
+
| `PrismaDiscoveryArtifactError` | `AvPrismaDiscoveryArtifactError` |
|
|
1255
|
+
| `PrismaEmulationError` | `AvPrismaEmulationError` |
|
|
1256
|
+
| `PrismaExecutionError` | `AvPrismaExecutionError` |
|
|
1257
|
+
| `PrismaExistenceGateError` | `AvPrismaExistenceGateError` |
|
|
1258
|
+
| `PrismaFieldProjectionError` | `AvPrismaFieldProjectionError` |
|
|
1259
|
+
| `PrismaGenerateCliError` | `AvPrismaGenerateCliError` |
|
|
1260
|
+
| `PrismaGenerateStepError` | `AvPrismaGenerateStepError` |
|
|
1261
|
+
| `PrismaModelCompositionError` | `AvPrismaModelCompositionError` |
|
|
1262
|
+
| `PrismaMutationTranslationError` | `AvPrismaMutationTranslationError` |
|
|
1263
|
+
| `PrismaOperationTranslationError` | `AvPrismaOperationTranslationError` |
|
|
1264
|
+
| `PrismaOrderTranslationError` | `AvPrismaOrderTranslationError` |
|
|
1265
|
+
| `PrismaProjectionTranslationError` | `AvPrismaProjectionTranslationError` |
|
|
1266
|
+
| `PrismaResultShapeError` | `AvPrismaResultShapeError` |
|
|
1267
|
+
| `PrismaScalarConversionError` | `AvPrismaScalarConversionError` |
|
|
1268
|
+
| `PrismaSchemaReaderError` | `AvPrismaSchemaReaderError` |
|
|
1269
|
+
| `PrismaTypesReaderError` | `AvPrismaTypesReaderError` |
|
|
1270
|
+
| `PrismaWhereTranslationError` | `AvPrismaWhereTranslationError` |
|
|
1271
|
+
|
|
1272
|
+
- 68d4395: New: the generated client checks field validators before it sends. When the ClientContract carries a field's
|
|
1273
|
+
`validators` or a Json Shape's checks, `avclient generate` emits them into the client with the check code that runs
|
|
1274
|
+
them, and every call and every transaction checks the values it holds first. A call that breaks a check rejects with
|
|
1275
|
+
the same `AvValidationError` the server would answer, `A2004` with the same issues (`code`, `path`, `check`,
|
|
1276
|
+
`params`, `message`), and no request is sent:
|
|
1277
|
+
|
|
1278
|
+
```ts
|
|
1279
|
+
try {
|
|
1280
|
+
await avClient.projects.create.one({ data: { id: "p1", name: "no" } });
|
|
1281
|
+
} catch (error) {
|
|
1282
|
+
// error instanceof AvValidationError, error.code === "A2004", nothing was sent:
|
|
1283
|
+
// error.cause.issues[0] → { code: "V1004", path: ["arguments", "data", "name"],
|
|
1284
|
+
// message: "Value must be at least 3 characters long.", check: "min_length", params: { minimum: 3 } }
|
|
1285
|
+
}
|
|
1286
|
+
```
|
|
1287
|
+
|
|
1288
|
+
- The server checks every value again. The client checks a value as the caller wrote it and the server checks it
|
|
1289
|
+
after its field pipes, so a field pipe that normalizes a value (trims it, lower-cases it) must not turn a value the
|
|
1290
|
+
client accepted into one the server refuses.
|
|
1291
|
+
- A transaction is checked whole before it is sent: a bad literal value in any step rejects with the server's answer,
|
|
1292
|
+
`cause.operation` naming the step. A `$ref` value is checked by the server, at its step.
|
|
1293
|
+
- Nested write data, list fields (each entry and the list's length), `$push` entries and Json Shape checks are all
|
|
1294
|
+
checked, a Json Shape's checks with the other validators in the order the call wrote the fields, and a plan step
|
|
1295
|
+
by step, as the server does. A Json value that does not match its shape is sent, and the server answers it.
|
|
1296
|
+
- The check code is `@aventara/core`'s own, copied into the generated output; the client still imports nothing from
|
|
1297
|
+
`@aventara/*` or zod at runtime. A contract with no checks generates the same files as before.
|
|
1298
|
+
- `AvValidationIssue` declares `check` and `params`, and the client keeps them on the issues a server sends (they
|
|
1299
|
+
were dropped before).
|
|
1300
|
+
- The server lists validator issues in the order the call wrote the fields (it used the model's field order), and a
|
|
1301
|
+
Json Shape's issues in the order of its keys as the Contract lists them, so the client can list the same issues in
|
|
1302
|
+
the same order. An issue inside a nested write whose operand is one entry rather than a list no longer names an
|
|
1303
|
+
index the request does not have (`[…, "$update", "data", …]`, not `[…, "$update", 0, "data", …]`).
|
|
1304
|
+
|
|
1305
|
+
- 7e3d06f: Breaking: the configuration has a new shape. Every layer (the root, `application` and `client`) now holds the
|
|
1306
|
+
same six keys: `exposed`, `transactionsStyle`, `limits`, `pipelines`, `resources` and `exemptions`. Everything about
|
|
1307
|
+
one Resource lives under `resources.<Resource>`: its fields, and each field's `definition` and `pipelines`. The old
|
|
1308
|
+
keys are refused when the framework starts, with a message that names where each one moved:
|
|
1309
|
+
|
|
1310
|
+
| Before (rc.0) | Now (rc.1) |
|
|
1311
|
+
| ------------------------------------------------------- | ----------------------------------------------------------------------------------- |
|
|
1312
|
+
| `fields: { Post: { url: { type, nullable, … } } }` | `resources: { Post: { fields: { url: { definition: { type, nullable, … } } } } }` |
|
|
1313
|
+
| `behaviors: { Post: { fields: { slug: { create } } } }` | `resources: { Post: { fields: { slug: { pipelines: { compute: { create } } } } } }` |
|
|
1314
|
+
| `transactions: "none"` | `transactionsStyle: "none"` |
|
|
1315
|
+
| `restrictions` | `exposed` + `exemptions` (see below) |
|
|
1316
|
+
| `@aventara/core/json` | `@aventara/core/avz` |
|
|
1317
|
+
|
|
1318
|
+
Before:
|
|
1319
|
+
|
|
1320
|
+
```ts
|
|
1321
|
+
createFramework({
|
|
1322
|
+
api: "/api",
|
|
1323
|
+
adapter,
|
|
1324
|
+
transactions: "interactive",
|
|
1325
|
+
fields: {
|
|
1326
|
+
Post: {
|
|
1327
|
+
url: {
|
|
1328
|
+
type: { scalar: "string" },
|
|
1329
|
+
nullable: false,
|
|
1330
|
+
list: false,
|
|
1331
|
+
lifecycle: ["VIRTUAL"],
|
|
1332
|
+
},
|
|
1333
|
+
},
|
|
1334
|
+
},
|
|
1335
|
+
behaviors: {
|
|
1336
|
+
Post: {
|
|
1337
|
+
fields: {
|
|
1338
|
+
url: {
|
|
1339
|
+
read: {
|
|
1340
|
+
dependsOn: ["slug"],
|
|
1341
|
+
compute: ({ record }) => `/p/${record.slug}`,
|
|
1342
|
+
},
|
|
1343
|
+
},
|
|
1344
|
+
},
|
|
1345
|
+
},
|
|
1346
|
+
},
|
|
1347
|
+
});
|
|
1348
|
+
```
|
|
1349
|
+
|
|
1350
|
+
After:
|
|
1351
|
+
|
|
1352
|
+
```ts
|
|
1353
|
+
createFramework({
|
|
1354
|
+
api: "/api",
|
|
1355
|
+
adapter,
|
|
1356
|
+
transactionsStyle: "interactive",
|
|
1357
|
+
resources: {
|
|
1358
|
+
Post: {
|
|
1359
|
+
fields: {
|
|
1360
|
+
url: {
|
|
1361
|
+
definition: {
|
|
1362
|
+
type: { scalar: "string" },
|
|
1363
|
+
nullable: false,
|
|
1364
|
+
list: false,
|
|
1365
|
+
lifecycle: ["VIRTUAL"],
|
|
1366
|
+
},
|
|
1367
|
+
pipelines: {
|
|
1368
|
+
compute: {
|
|
1369
|
+
read: {
|
|
1370
|
+
dependsOn: ["slug"],
|
|
1371
|
+
compute: ({ record }) => `/p/${record.slug}`,
|
|
1372
|
+
},
|
|
1373
|
+
},
|
|
1374
|
+
},
|
|
1375
|
+
},
|
|
1376
|
+
},
|
|
1377
|
+
},
|
|
1378
|
+
},
|
|
1379
|
+
});
|
|
1380
|
+
```
|
|
1381
|
+
|
|
1382
|
+
What else changed:
|
|
1383
|
+
|
|
1384
|
+
- A field is defined at the root only. A `definition` in `application` or `client` is refused
|
|
1385
|
+
(`CONFIG_FORBIDDEN_IN_OVERLAY`). Define the field
|
|
1386
|
+
at the root; `application` and `client` can still add compute stages to it.
|
|
1387
|
+
- Computed values chain. When the root and an overlay both compute a field, the root's stage runs first and the
|
|
1388
|
+
overlay's receives its result as `value`. A stage can no longer be turned off with `false`. Returning `undefined`
|
|
1389
|
+
keeps the current value. An upsert computes its create branch with `compute.create` and its update branch with
|
|
1390
|
+
`compute.update`.
|
|
1391
|
+
- A discovered field keeps the adapter's facts. On a field the adapter discovered you may restate its own `type`,
|
|
1392
|
+
`nullable` and `list`, and give a Json field its shape (`definition.json`). Setting a different type, any `format`
|
|
1393
|
+
or any `capabilities` on it is refused. `format` is for fields you add, and it must be one of zod's named string
|
|
1394
|
+
formats (`"email"`, `"url"`, `"uuid"`, …); any other name is a type error and refused at startup.
|
|
1395
|
+
- A misplaced key is a type error. In a configuration written inline in `createFramework({ ... })` or
|
|
1396
|
+
`AventaraModule.forRoot`, a key that has no place there (a typo, an old key, `compute` on a relation field, a field
|
|
1397
|
+
name neither your schema nor the root has) is now a type error at that key, and still refused at startup.
|
|
1398
|
+
- A Resource whose name starts with `$` is refused at startup (`$` names are the framework's).
|
|
1399
|
+
- The Json shape builder moved from `@aventara/core/json` to `@aventara/core/avz`. Change the import; `AvZ` is
|
|
1400
|
+
the same.
|
|
1401
|
+
|
|
1402
|
+
`exposed` and `exemptions`, which replace `restrictions`, have their own entry.
|
|
1403
|
+
|
|
1404
|
+
- 1b5d3c0: Breaking: every framework member of a pipeline ctx is `$`-prefixed, and ctx is now the framework's executor.
|
|
1405
|
+
The rest of ctx is your Resources: `ctx.<Resource>.<family>.<variant>(args)`, plus `ctx.$sql` and
|
|
1406
|
+
`ctx.$transaction`. Because a Resource name can never start with `$` (`COMPILER_RESERVED_RESOURCE_NAME`), a Resource
|
|
1407
|
+
never shadows a framework member. The old names are gone with no alias. Reading one is a type error; at runtime it
|
|
1408
|
+
is `undefined`.
|
|
1409
|
+
|
|
1410
|
+
| rc.0 | rc.1 |
|
|
1411
|
+
| --------------------------------------------------------- | --------------------------------------------------------------------------- |
|
|
1412
|
+
| `ctx.resource`, `ctx.family`, `ctx.variant`, `ctx.nested` | `ctx.$operation.resource`, `.family`, `.variant`, `.nested` |
|
|
1413
|
+
| `ctx.args` | `ctx.$args` (a transaction stage: the plan, `readonly AvTransactionStep[]`) |
|
|
1414
|
+
| `ctx.origin` | `ctx.$origin` |
|
|
1415
|
+
| `ctx.requestId` | `ctx.$requestId` |
|
|
1416
|
+
| `ctx.transport` | `ctx.$transport` |
|
|
1417
|
+
| `ctx.contract` | `ctx.$contract` |
|
|
1418
|
+
|
|
1419
|
+
A transaction stage's ctx has no `$operation`. Every ctx also carries `ctx.$state`, the request's writable state (see
|
|
1420
|
+
the `$state` entry below).
|
|
1421
|
+
|
|
1422
|
+
```ts
|
|
1423
|
+
hooks: {
|
|
1424
|
+
after: [
|
|
1425
|
+
async (post, ctx) => {
|
|
1426
|
+
await ctx.User.update.unique({ where: { id: authorId }, data: { postCount: { $increment: 1 } } });
|
|
1427
|
+
await ctx.$sql`insert into audit_events (actor, action) values (${actorId}, ${"post.create"})`;
|
|
1428
|
+
},
|
|
1429
|
+
],
|
|
1430
|
+
},
|
|
1431
|
+
```
|
|
1432
|
+
|
|
1433
|
+
- A ctx call is raw and application-origin. It runs the Framework Validation Layer against the
|
|
1434
|
+
ApplicationContract, plus field pipes, field validators and compute. It runs no developer stage: no guards, pipes,
|
|
1435
|
+
hooks, interceptors or filters at any level, no field write guards, no nested target guards, no Resource
|
|
1436
|
+
transaction stages, and no `forceTransaction` wrapping. So a hook's call never re-enters the hook that made it.
|
|
1437
|
+
The application layer's exposure limits it. The client's does not.
|
|
1438
|
+
- A call resolves the operation's data directly (a row, a list, a count, or `null` for a first-style miss). A
|
|
1439
|
+
failure throws the `AvError` subclass of its code's class (`AvNotFoundError`, `AvValidationError`, …; see the
|
|
1440
|
+
in-process calls entry below) carrying the failure's `code` and `cause`.
|
|
1441
|
+
- It joins the current transaction. That covers a plan step's interceptors and hooks, a forced call's, a Resource's
|
|
1442
|
+
transaction stages and a `ctx.$transaction` callback. A step's guards and pipes run before the transaction opens,
|
|
1443
|
+
so their calls run on their own, as every call outside a transaction does. Under
|
|
1444
|
+
`forceTransaction`, an after hook's ctx writes commit with the operation or not at all (the outbox and
|
|
1445
|
+
counter-on-another-Resource cases).
|
|
1446
|
+
- `ctx.$sql` is the bound tagged template of `appTx.$sql`, run at once. It takes only the tagged-template form,
|
|
1447
|
+
binds every value, needs the Adapter's `sqlStatements`, and accepts no `$ref`. It resolves the rows, typed by
|
|
1448
|
+
`ctx.$sql<Row>`. Inside a transaction it runs on it; outside one it is a plain statement, with no transaction
|
|
1449
|
+
opened around it, and `transactionsStyle: "none"` does not affect it (only `ctx.$transaction` is refused there).
|
|
1450
|
+
- `ctx.$transaction(async (tx) => …)` runs the callback in one transaction, with `tx` the ctx bound to it, and
|
|
1451
|
+
resolves what the callback returns. A throw rolls it back and is rethrown as it was. Inside an open transaction it
|
|
1452
|
+
opens nothing and joins.
|
|
1453
|
+
- The executor is typed from the adapter's model, with the application forms and the operation's data
|
|
1454
|
+
as the result, like `framework.application`'s success data. A stage typed for any model (`AvGuard` with no model)
|
|
1455
|
+
names no Resource.
|
|
1456
|
+
- Compute gets a read-only ctx, `AvComputeContext` (new public name). It carries the `$` facts, a read-only view
|
|
1457
|
+
of `ctx.$state`, and `ctx.<Resource>.find.*` (`find.count` included), and nothing that writes: no write families, no `$sql`, no
|
|
1458
|
+
`$transaction`. This holds at runtime too: reaching one throws, and the operation answers `A3000`. A compute
|
|
1459
|
+
stage's input is now `{ field, value, data, ctx }` (`{ field, value, record, ctx }` on read). rc.0's `origin`,
|
|
1460
|
+
`resource` and `operation` members are gone: use `ctx.$origin`, `ctx.$operation`, and the stage's placement for
|
|
1461
|
+
its Resource.
|
|
1462
|
+
- A transaction pipe's returned plan is typed `readonly AvTransactionStep[]` (any model). The runtime holds it to
|
|
1463
|
+
the plan it was given, as before.
|
|
1464
|
+
|
|
1465
|
+
- 6317811: Breaking: `exposed` and `exemptions` replace `restrictions`. Every layer (the root, `application` and `client`)
|
|
1466
|
+
says what it exposes with two keys: `exposed`, the layer's default, and `exemptions`, the entries that are the
|
|
1467
|
+
opposite of it.
|
|
1468
|
+
|
|
1469
|
+
- `exposed: true` (the root's default) exposes everything the layer above offers, and `exemptions` lists what to
|
|
1470
|
+
hide. `exposed: false` exposes nothing, and `exemptions` lists the only things to expose.
|
|
1471
|
+
- In `exemptions`, `true` exempts an entry whole, `false` and `[]` exempt nothing, a list exempts the listed
|
|
1472
|
+
entries one level down, and an object goes one level deeper. A Resource takes `true`, `false` or
|
|
1473
|
+
`{ operations, fields }`. Operations are nested: `operations: { find: ["many"], update: { unique: true } }`.
|
|
1474
|
+
A field takes a list of capability names, or an object per capability whose token lists name filter operators,
|
|
1475
|
+
directives, reducers or pagination kinds.
|
|
1476
|
+
- A list now always means "exempt these". rc.0's `filter: ["equals"]` meant "keep only `equals`"; under
|
|
1477
|
+
`exposed: true` the same intent is the list of the operators to remove.
|
|
1478
|
+
- Overlays only narrow. `application` and `client` start from the root's view. An overlay can no longer bring
|
|
1479
|
+
back what the root hid (rc.0's `hidden: false`); that is now refused at startup, and the message names the other
|
|
1480
|
+
overlay to hide it in instead. Expose an entry at the root and hide it in the overlay that must not see it.
|
|
1481
|
+
- An overlay that sets `exemptions` must also set its own `exposed`.
|
|
1482
|
+
- Relation positions are the adapter's: an exemption can no longer narrow where a relation is projected or written.
|
|
1483
|
+
- A unique operation (`find.unique`, `update.unique`, `delete.unique`, `upsert.unique`) needs an identifier. Picking
|
|
1484
|
+
one without the identifier's fields, or hiding every identifier while one stays exposed, is refused at startup
|
|
1485
|
+
with `COMPILER_MISSING_IDENTIFIER` instead of the operation silently disappearing.
|
|
1486
|
+
|
|
1487
|
+
Every name in `exemptions` must exist, in both styles: a Resource, field, family, variant, capability or token the
|
|
1488
|
+
adapter does not offer is refused at startup with its path (`COMPILER_UNKNOWN_RESOURCE`, `COMPILER_UNKNOWN_FIELD`,
|
|
1489
|
+
`COMPILER_UNAVAILABLE_OPERATION`, `COMPILER_UNAVAILABLE_CAPABILITY`), and in a configuration written inline in
|
|
1490
|
+
`createFramework({ ... })` it is a type error first. Under `exposed: true`, hiding in an overlay what the root
|
|
1491
|
+
already hid does nothing.
|
|
1492
|
+
|
|
1493
|
+
Before (rc.0):
|
|
1494
|
+
|
|
1495
|
+
```ts
|
|
1496
|
+
createFramework({
|
|
1497
|
+
api: "/api",
|
|
1498
|
+
adapter,
|
|
1499
|
+
restrictions: {
|
|
1500
|
+
User: {
|
|
1501
|
+
fields: {
|
|
1502
|
+
passwordHash: { hidden: true },
|
|
1503
|
+
email: { capabilities: { filter: ["equals"] } },
|
|
1504
|
+
},
|
|
1505
|
+
},
|
|
1506
|
+
},
|
|
1507
|
+
client: {
|
|
1508
|
+
restrictions: {
|
|
1509
|
+
AuditLog: { hidden: true },
|
|
1510
|
+
Post: { operations: { delete: { unique: false } } },
|
|
1511
|
+
},
|
|
1512
|
+
},
|
|
1513
|
+
});
|
|
1514
|
+
```
|
|
1515
|
+
|
|
1516
|
+
After (rc.1):
|
|
1517
|
+
|
|
1518
|
+
```ts
|
|
1519
|
+
createFramework({
|
|
1520
|
+
api: "/api",
|
|
1521
|
+
adapter,
|
|
1522
|
+
// The root: `exposed` defaults to true, so these are hidden from every origin.
|
|
1523
|
+
exemptions: {
|
|
1524
|
+
resources: {
|
|
1525
|
+
User: {
|
|
1526
|
+
fields: {
|
|
1527
|
+
passwordHash: true,
|
|
1528
|
+
email: { filter: ["contains", "startsWith"] }, // remove every operator but `equals`
|
|
1529
|
+
},
|
|
1530
|
+
},
|
|
1531
|
+
},
|
|
1532
|
+
},
|
|
1533
|
+
client: {
|
|
1534
|
+
exposed: true,
|
|
1535
|
+
exemptions: {
|
|
1536
|
+
resources: {
|
|
1537
|
+
AuditLog: true,
|
|
1538
|
+
Post: { operations: { delete: ["unique"] } },
|
|
1539
|
+
},
|
|
1540
|
+
},
|
|
1541
|
+
},
|
|
1542
|
+
});
|
|
1543
|
+
```
|
|
1544
|
+
|
|
1545
|
+
Or expose only what a frontend calls:
|
|
1546
|
+
|
|
1547
|
+
```ts
|
|
1548
|
+
client: {
|
|
1549
|
+
exposed: false,
|
|
1550
|
+
exemptions: {
|
|
1551
|
+
resources: {
|
|
1552
|
+
Post: {
|
|
1553
|
+
operations: { find: ["many", "unique"], create: ["one"] },
|
|
1554
|
+
fields: { id: ["select"], title: ["select", "create"], body: ["select", "create"] },
|
|
1555
|
+
},
|
|
1556
|
+
},
|
|
1557
|
+
},
|
|
1558
|
+
},
|
|
1559
|
+
```
|
|
1560
|
+
|
|
1561
|
+
`aventara init` and `aventara new` now write `exposed: true` into `src/aventara.config.ts`, with a comment that
|
|
1562
|
+
links the exposure documentation.
|
|
1563
|
+
|
|
1564
|
+
- 6d0e5d6: New: field validators. `resources.<R>.fields.<f>.pipelines.validators` takes declarative checks written with
|
|
1565
|
+
`AvZ` from `@aventara/core/avz`, in zod's check vocabulary:
|
|
1566
|
+
|
|
1567
|
+
```ts
|
|
1568
|
+
import { AvZ } from "@aventara/core/avz";
|
|
1569
|
+
|
|
1570
|
+
resources: {
|
|
1571
|
+
Project: {
|
|
1572
|
+
fields: {
|
|
1573
|
+
name: { pipelines: { validators: [AvZ.minLength(3), AvZ.maxLength(80)] } },
|
|
1574
|
+
slug: { pipelines: { validators: [AvZ.regex(/^[a-z0-9-]+$/)] } },
|
|
1575
|
+
budget: { pipelines: { validators: [AvZ.gte(0), AvZ.multipleOf(100)] } },
|
|
1576
|
+
tags: { pipelines: { validators: [AvZ.maxItems(5), AvZ.maxLength(20)] } }, // at most 5 tags of at most 20 characters
|
|
1577
|
+
},
|
|
1578
|
+
},
|
|
1579
|
+
},
|
|
1580
|
+
```
|
|
1581
|
+
|
|
1582
|
+
- The builders: `lt`, `lte`, `gt`, `gte`, `positive`, `nonnegative`, `negative`, `nonpositive`, `multipleOf`,
|
|
1583
|
+
`minLength`, `maxLength`, `length`, `minItems`, `maxItems`, `minSize`, `maxSize`, `size`, `regex`, `includes`,
|
|
1584
|
+
`startsWith`, `endsWith`, `lowercase`, `uppercase`, and one per named format: `email`, `url`, `uuid`, `e164`,
|
|
1585
|
+
`ipv4`, `ipv6`, `base64`, `nanoid`, `cuid`, `ulid` and the rest, with `AvZ.iso.datetime()`, `.date()`, `.time()`
|
|
1586
|
+
and `.duration()`. Each behaves as zod 4's check does.
|
|
1587
|
+
- A check has to fit the field's type: `AvZ.maxLength` on an `int` field, or `AvZ.maxItems` on a field that is not a
|
|
1588
|
+
list, is a type error and a startup error (`COMPILER_INVALID_VALIDATOR`, naming the path and why). On a list field
|
|
1589
|
+
`minItems`/`maxItems` check the list and every other check applies to each entry. A bound is a number, a bigint or a
|
|
1590
|
+
`Date`, matching the field.
|
|
1591
|
+
- A Json field takes no `validators` key. Its checks go in its `definition.json` shape, written as zod/mini writes
|
|
1592
|
+
them: `AvZ.object({ code: AvZ.string().check(AvZ.length(4)), tags: AvZ.array(AvZ.string()).check(AvZ.maxItems(3)) })`.
|
|
1593
|
+
- Where they run: once, after every pipe (the guards, then the operation's pipes, then the field pipes) and before
|
|
1594
|
+
any interceptor or hook; for a transaction plan or a forced call, on every step's literal values before the
|
|
1595
|
+
transaction opens, and on a `$ref` value at its step; on nested write data with the target Resource's checks.
|
|
1596
|
+
What they judged is what is written: no later stage can change it. Validators add up across layers: the root's
|
|
1597
|
+
apply to both origins, an overlay's to its own.
|
|
1598
|
+
- A Json field's shape is one of its validators. Its structure and its checks are judged where validators run, after
|
|
1599
|
+
every pipe (rc.0 judged the shape before the pipes), with the same reach: plan literals before the transaction
|
|
1600
|
+
opens, a `$ref` value at its step, and nested write data. Its issues come in the order the call wrote the fields,
|
|
1601
|
+
with the other validators'. Before the
|
|
1602
|
+
pipes the framework only checks that a Json value is plain JSON. A field or operation pipe whose Json output breaks
|
|
1603
|
+
the shape is answered `A2004`, as the caller's, not `A3004`. The shape still types the field.
|
|
1604
|
+
- A failure answers `A2004` (HTTP 422) with one issue per broken rule, on `V1004` (range, length, size) or `V1003`
|
|
1605
|
+
(pattern, format). Each issue now also carries zod's `check` name and the check's `params`, for example
|
|
1606
|
+
`{ "code": "V1004", "path": ["arguments", "data", "name"], "check": "max_length", "params": { "maximum": 80 }, "message": "Value must be at most 80 characters long." }`.
|
|
1607
|
+
- The checks are in the Contract: a field's `validators` (`AvFieldCheck`, now exported) and a Json Shape node's
|
|
1608
|
+
`checks`, only on fields the layer exposes. Adding, removing or changing a client-visible check changes the
|
|
1609
|
+
ClientContract hash, so regenerate the client. The generated client runs the same checks before it sends a call.
|
|
1610
|
+
|
|
1611
|
+
- 7a0cab9: A `where` now offers each field only the filter operators that field allows. The types of `framework.client`,
|
|
1612
|
+
`framework.application`, `framework.clientTx`, `framework.appTx` and a generated client read the operators from the
|
|
1613
|
+
field's effective `filter` capability, so an operator the server would refuse with `V1006` is now a type error
|
|
1614
|
+
instead. That covers an operator the adapter does not offer for the field's type (no `contains` on an Int), an
|
|
1615
|
+
operator a layer's exemptions removed (the client layer narrowing `email` to `equals` is refused on
|
|
1616
|
+
`framework.client` and still allowed on `framework.application`), and operators inside a nested `not`. A plain
|
|
1617
|
+
value, `null` or `$ref` as shorthand is equality, so it is only accepted where the field allows `equals`, and a field
|
|
1618
|
+
narrowed to no operators at all can no longer be named in a `where`. Runtime behavior is unchanged.
|
|
1619
|
+
|
|
1620
|
+
A generated client picks this up when it is regenerated with this version of `@aventara/client`.
|
|
1621
|
+
|
|
1622
|
+
- eb2a718: New limit `maxJsonDepth` (default 64): how deeply a `json` field's value may nest. Each array or object inside the value
|
|
1623
|
+
is one level, counted from the value itself, wherever it is written or filtered on. A value past the limit is refused
|
|
1624
|
+
with `A2009` and the new issue code `V1021` at the field's path, before anything else reads it, in single calls,
|
|
1625
|
+
transaction steps and over HTTP. It is set like the other limits, at the root or per layer, and appears in the
|
|
1626
|
+
Contract's `limits`, so the ClientContract hash changes: regenerate your client. `maxNestingDepth` does not count Json
|
|
1627
|
+
values. Before, a value 10,000 levels deep was written and then failed over HTTP with an internal `A3003`, and in a
|
|
1628
|
+
transaction failed with `A3000`. The HTTP response encoder and `canonicalizeJson` no longer recurse on a value's depth,
|
|
1629
|
+
so a deep value already in your database is read without an error.
|
|
1630
|
+
- 03312c5: Fixed a bug where operators inside `not` weren't checked against a field's allowed operators. On a field whose
|
|
1631
|
+
`filter` capability allowed only `equals` and `not`, a request like `where: { id: { not: { contains: "a" } } }` passed
|
|
1632
|
+
validation and reached the adapter, so a remote caller could use an operator the client layer had removed. Operators
|
|
1633
|
+
inside `not` are now checked at every depth, in single calls and transaction steps, under `AND`/`OR`/`NOT` and relation
|
|
1634
|
+
filters, and are refused with `A2004`/`V1006` at the nested operator's path. A `not` written as a plain value, `null` or
|
|
1635
|
+
`$ref` means "not equal", so it is accepted only where the field allows `equals`; the types now say the same. An empty
|
|
1636
|
+
`not: {}` is refused with `V1001`, like an empty filter object.
|
|
1637
|
+
- 123f6dc: Breaking: nested writes now run the target's guards. When a create, update or upsert reaches another Resource
|
|
1638
|
+
through a relation directive, that target Resource's own guards and field stages now run on the nested data, in
|
|
1639
|
+
Node, before the single adapter call (still one call). In rc.0 a nested write ran none of them, so a client could
|
|
1640
|
+
create a Category through `Post.create({ data: { category: { $create: … } } })` past a guard that refuses
|
|
1641
|
+
`Category.create`. That call is now refused with `A4001`.
|
|
1642
|
+
|
|
1643
|
+
Which stages a directive runs on its target:
|
|
1644
|
+
|
|
1645
|
+
| Directive | The target's stages |
|
|
1646
|
+
| -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
1647
|
+
| `$create`, `$connectOrCreate` | Resource and `create` guards; field stages on the created data |
|
|
1648
|
+
| `$update`, `$upsert` | Resource and `update` guards; field stages (an `$upsert`'s `create` branch gets the create field stages and `compute.create`, its `update` branch the update ones) |
|
|
1649
|
+
| `$delete` | Resource and `delete` guards |
|
|
1650
|
+
| `$connect`, `$disconnect`, `$set` on a to-one whose key the parent holds | None: the parent's relation field write guard covers it |
|
|
1651
|
+
| `$connect`, `$disconnect`, `$set` on a to-many, or a to-one whose key the target holds | Resource and `update` guards |
|
|
1652
|
+
|
|
1653
|
+
- The field stages are the target's field pipes, field validators, field write guards and write compute.
|
|
1654
|
+
- Order: each kind runs after the top-level operation's stages of the same kind, for the targets depth first in
|
|
1655
|
+
data order: the operation's guards and field write guards, then each target's Resource guards, family guards and
|
|
1656
|
+
field write guards; the operation's pipes; the operation's field pipes, then the targets'; the validators; the
|
|
1657
|
+
operation's write compute, then the targets'.
|
|
1658
|
+
- What a target's stage is told: `ctx.$operation.resource` is the target, `ctx.$operation.family` the family its directive maps to,
|
|
1659
|
+
`ctx.$operation.variant` is `"one"`, `ctx.$operation.nested` is `true` and `ctx.$args` is the directive's entry (for example the
|
|
1660
|
+
`$create` data, or a `$connect`'s identifier). A family stage's `ctx.$operation.variant` is now typed as the family's
|
|
1661
|
+
variants or `"one"`, for `create`, `update` and `delete`.
|
|
1662
|
+
- Not run for a target: its layer and variant stages, operation pipes, hooks, interceptors and filters, and its
|
|
1663
|
+
Resource transaction stages. A nested write is part of its parent's operation; an error a target's guard throws
|
|
1664
|
+
goes through the parent operation's filters.
|
|
1665
|
+
- Who holds the key is read from the adapter's referential edges (`AvAdapter.referentialActions`). An adapter
|
|
1666
|
+
that states no edge for a to-one relation is read as the target holding it, so the target's `update` guards run.
|
|
1667
|
+
- The same holds for a transaction step and a forced call: each step's nested targets' guards, field pipes and
|
|
1668
|
+
validators run with the step's own before the transaction opens, and a nested `$ref` value goes through the
|
|
1669
|
+
target's field pipes and validators when it resolves at its step.
|
|
1670
|
+
|
|
1671
|
+
- b65df2e: Fixed a gap where a chain of `not` inside a scalar filter (`{ id: { not: { not: … } } }`) wasn't counted toward
|
|
1672
|
+
`maxNestingDepth`. A 50-level chain reached the adapter, and a 5,000-level one failed with an internal `A3000` error
|
|
1673
|
+
(a stack overflow) instead of a validation error. Each `not` whose value is an object now counts as one level of
|
|
1674
|
+
`where` nesting, like `NOT`, so a chain past the limit is refused with `A2009`/`V1013` before anything runs, in single
|
|
1675
|
+
calls, transaction steps and over HTTP. `framework.transaction` no longer overflows on deeply nested arguments either.
|
|
1676
|
+
- c6e5010: Breaking: one stage order, and hooks now run inside interceptors. Every operation runs
|
|
1677
|
+
|
|
1678
|
+
```
|
|
1679
|
+
guards → pipes → validators → interceptors (before) → hooks.before → operation → hooks.after → interceptors (after) → filters
|
|
1680
|
+
```
|
|
1681
|
+
|
|
1682
|
+
Each stage runs over every level, outer to inner, before the next starts, so every interceptor wraps every hook: all
|
|
1683
|
+
interceptors enter, then every `before` hook runs, then the operation, then every `after` hook (inner to outer), then
|
|
1684
|
+
the interceptors unwind (inner to outer). In rc.0 every `before` hook ran ahead of every interceptor, every `after`
|
|
1685
|
+
hook after every interceptor had unwound, and the overlay's `after` hooks after the root's; now the overlay's run
|
|
1686
|
+
before the root's. Filters run inner to outer, and the first filter that handles an error wins. A call that a guard,
|
|
1687
|
+
a pipe's output or a validator refuses runs no interceptor and no hook; the filters still see a thrown error. Inside
|
|
1688
|
+
a transaction, a step's error its own filters leave unhandled continues to the Resource transaction filters (the
|
|
1689
|
+
transaction's outer layer) before it is answered.
|
|
1690
|
+
|
|
1691
|
+
New: stages at every level of `resources`. `resources.<R>.pipelines`, `resources.<R>.operations.<family>.pipelines`
|
|
1692
|
+
and `resources.<R>.operations.<family>.<variant>.pipelines` run now, in both layers, level by level: root layer,
|
|
1693
|
+
overlay layer, root Resource, overlay Resource, root family, overlay family, root variant, overlay variant. Nothing
|
|
1694
|
+
below removes a stage above. A stage is told the level it sits at: a Resource's `ctx.$operation.resource` is its name, a
|
|
1695
|
+
family's `ctx.$operation.family` too, a variant's `ctx.$operation.variant` as well.
|
|
1696
|
+
|
|
1697
|
+
- Field pipes (`resources.<R>.fields.<f>.pipelines.pipes`) are pure: value in, value out, no `ctx`. They run on
|
|
1698
|
+
each value the caller sent in top-level create, update or upsert data, right after the operation's pipes, root's
|
|
1699
|
+
then overlay's; guards see the value as sent. In a transaction they run on every step's literal values before the
|
|
1700
|
+
transaction opens, and on a `$ref` value when it is resolved at its step. A value a field pipe returns is held to the server's rules
|
|
1701
|
+
(`A3004`). A relation field takes no field pipes (`COMPILER_RELATION_FIELD_CONFIG_UNSUPPORTED`).
|
|
1702
|
+
- Field write guards (`resources.<R>.fields.<f>.pipelines.guards`) refuse with `A4001` when the caller sends
|
|
1703
|
+
the field in create, update or upsert data, a relation field's `$connect` included. They run right after the
|
|
1704
|
+
operation's guards, last among the guards.
|
|
1705
|
+
- Transactions: every step's guards, pipes and validators run before the transaction opens, in plan order; an
|
|
1706
|
+
operation pipe sees a `$ref` as the reference it is. A refused step opens nothing. Then BEGIN, and at each step
|
|
1707
|
+
its `$ref`s are resolved, their field pipes and validators run, then the step's interceptors and hooks around its
|
|
1708
|
+
adapter call. A forced call runs the same way. A step's guards and pipes no longer run inside the transaction, so
|
|
1709
|
+
their ctx calls do not join it.
|
|
1710
|
+
- Resource transaction stages (`resources.<R>.transactions.pipelines`) run once per transaction with a
|
|
1711
|
+
top-level step on the Resource, inside it: guards and pipes see the whole plan (`ctx.$args`, `$ref`s unresolved),
|
|
1712
|
+
interceptors and hooks wrap every step, and the `after` hooks see every step's result before the commit, so a
|
|
1713
|
+
throw rolls everything back. Touched Resources run in the order their first step appears; a SQL step touches
|
|
1714
|
+
nothing. A transaction pipe may change a step's arguments; they are held to the server's rules and their
|
|
1715
|
+
validators at the step.
|
|
1716
|
+
- `forceTransaction` on a family or a variant runs a plain call of that operation as a one-step transaction,
|
|
1717
|
+
so its after hooks commit with the write or not at all, and the Resource's transaction stages run. It holds when
|
|
1718
|
+
any layer sets it. As a step of a plan it is just a step. Under an effective `transactionsStyle: "none"` it is a
|
|
1719
|
+
startup error (`CONFIG_INVALID_VALUE`).
|
|
1720
|
+
|
|
1721
|
+
Breaking: a transaction has one origin. `framework.transaction` refuses a list that mixes `appTx` and
|
|
1722
|
+
`clientTx` handles: `A2004` with one new `V1020` issue, "Transaction mixes application and client steps.", at the
|
|
1723
|
+
first step of the other origin, before anything runs. An `appTx.$sql` step is application-origin. Run each
|
|
1724
|
+
origin's steps as its own transaction. The generated client's error codes include `V1020`.
|
|
1725
|
+
|
|
1726
|
+
- c1f9389: A forced operation no longer commits a failed step when a Resource transaction interceptor catches the rollback.
|
|
1727
|
+
A transaction plan and a plain call of a `forceTransaction` operation now settle through one path, so the check that
|
|
1728
|
+
already kept a plan from committing a swallowed failure covers forced calls too.
|
|
1729
|
+
|
|
1730
|
+
`next()` runs once. A Resource transaction interceptor that calls it a second time, or resolves without calling it,
|
|
1731
|
+
rolls the whole transaction back and answers `A3002`, with an `AvTransactionIntegrityError` (`next-called-twice` or
|
|
1732
|
+
`steps-not-run`) on the diagnostics channel. An operation interceptor's second `next()` is refused the same way and
|
|
1733
|
+
answers `A3000`. Both hold even when the interceptor catches the refusal, and no filter sees it.
|
|
1734
|
+
|
|
1735
|
+
What a field pipe returns is checked against the server's rules before the transaction opens, for a plan step and
|
|
1736
|
+
for a forced call. A refusal (`A3004`) no longer opens a transaction or runs transaction stages first.
|
|
1737
|
+
|
|
1738
|
+
A `compute.read` `dependsOn` is resolved against the adapter's model, before exposure. A field hidden at the root can
|
|
1739
|
+
now be a dependency: it is read for the compute and never returned. A dependency must be a scalar field the adapter
|
|
1740
|
+
stores on the same Resource; the type of `dependsOn` now says so.
|
|
1741
|
+
|
|
1742
|
+
Under `exposed: false`, an exemption object that grants nothing (`{}`, `{ select: false }`, `{ create: [] }`, or a
|
|
1743
|
+
Resource object that grants no field and no operation) leaves the field or Resource out of the Contract, exactly as
|
|
1744
|
+
leaving the entry out does. Its name no longer reaches the ClientContract, and the hash is the same either way.
|
|
1745
|
+
|
|
1746
|
+
Compute mistakes are reported once, at the path where they were written. A root mistake is no longer repeated under
|
|
1747
|
+
`application` and `client` paths.
|
|
1748
|
+
|
|
1749
|
+
`forceTransaction: true` is a type error where the types know the effective `transactionsStyle` is `"none"`: the
|
|
1750
|
+
adapter's model states `transactions: "none"`, or the layers state a literal `"none"`.
|
|
1751
|
+
|
|
1752
|
+
Resource transaction stages are told a typed plan. `ctx.$args` is `readonly AvTransactionStep[]`, an operation step
|
|
1753
|
+
`{ resource, family, variant, args }` or a SQL step `{ sql }`, and a transaction pipe returns that type or `undefined`.
|
|
1754
|
+
`AvTransactionStep` is a new public type.
|
|
1755
|
+
|
|
1756
|
+
Renamed compiler codes: `COMPILER_MISSING_READ_BEHAVIOR` is now `COMPILER_MISSING_READ_COMPUTE`, and
|
|
1757
|
+
`COMPILER_INVALID_BEHAVIOR_DEPENDENCY` is now `COMPILER_INVALID_COMPUTE_DEPENDENCY`.
|
|
1758
|
+
|
|
1759
|
+
Smaller fixes: a plan with steps of two origins is refused with `V1020` when the plan is validated too, not only by the
|
|
1760
|
+
typed runner; a transaction pipe may return a deep copy of a SQL step; `validators: []` is accepted; a `hidden` key on a Resource or field entry is `CONFIG_MOVED_PROPERTY`; and
|
|
1761
|
+
an overlay's moved `restrictions` key now names `client.exposed + client.exemptions`.
|
|
1762
|
+
|
|
1763
|
+
- 9c52482: A `$connect` to a row that does not exist now answers `404 A2003` (not found), the same as reading a missing row.
|
|
1764
|
+
Before, it answered `422 A2004`. This covers a to-one `$connect` in `create`, `update` and `upsert`, a to-many
|
|
1765
|
+
`$connect`, and the same writes inside a transaction plan, where `cause.operation` names the step. The message is
|
|
1766
|
+
unchanged: "The operation's arguments referenced a record that does not exist." On `update.first`, the same mistake
|
|
1767
|
+
answered `A2014` before, as if the row had changed underneath; it now answers `A2003` too, and a duplicate unique value
|
|
1768
|
+
written through `update.first` answers `409 A2008` instead of `A3001`.
|
|
1769
|
+
|
|
1770
|
+
Clearer top-level messages for an unknown Resource or operation. `A2001` now says "The requested Resource is not
|
|
1771
|
+
available." and `A2002` says "The requested operation is not available on this Resource.", instead of "Operation
|
|
1772
|
+
arguments failed framework validation.", which stays the message of `A2004`, `A2007` and `A2009`. A transaction step
|
|
1773
|
+
whose operation is outside the standard grammar now carries the same issue message as a single call, naming the
|
|
1774
|
+
operation and the Resource (for example `Operation "dance.one" is not available on Resource "posts".`), instead of
|
|
1775
|
+
"Operation is not part of the standard operation grammar."
|
|
1776
|
+
|
|
1777
|
+
A `$ref` with no `fingerprint` is `V1000` (required value missing) and answers `422 A2004`. Before, it was `V1017`
|
|
1778
|
+
(fingerprint mismatch) under `A2007`. A fingerprint that is present but wrong is still `V1017`.
|
|
1779
|
+
|
|
1780
|
+
`aventara init` and `aventara new` link to the documentation at https://aventara-framework.vercel.app. The old
|
|
1781
|
+
address redirects, so links already written into projects keep working.
|
|
1782
|
+
|
|
1783
|
+
- 8396148: Types: a method that returns a call's result without a return type now compiles in a project that emits
|
|
1784
|
+
declarations (`declaration: true`, Nest's default). With a shaped Json field, or a `$sql` or `clientTx` handle in
|
|
1785
|
+
the result, TypeScript stopped with TS2742 (TS2883 in TypeScript 6) or TS4053, because the declaration had to name
|
|
1786
|
+
a type that was not exported. Two type exports are new: `AvZShapedSchema` from `@aventara/core/avz` (a shaped Json
|
|
1787
|
+
field's schema in a typed Contract view) and `AvOperationHandle<T, Origin>` from `@aventara/core` (the opaque part of
|
|
1788
|
+
a transaction handle).
|
|
1789
|
+
|
|
1790
|
+
A handle now carries its builder's origin: `framework.transaction([...])` with `appTx` and `clientTx` handles in one
|
|
1791
|
+
list is a type error (it was refused only at runtime, `A2004` / `V1020`, which still guards). `AvOperation<T>`
|
|
1792
|
+
takes an optional second type argument, `"application"` or `"client"`; a handle typed without it joins either list.
|
|
1793
|
+
|
|
1794
|
+
A method that returns a façade node without a return type now compiles there too:
|
|
1795
|
+
`posts() { return this.framework.application.Post; }`, a whole surface, a Resource, a family, on any of
|
|
1796
|
+
`application`, `client`, `appTx` and `clientTx`. The declaration writes the node's name with the Contract view as
|
|
1797
|
+
its argument, instead of expanding every call under it, which TypeScript refused (TS4053, then TS7056 "exceeds the
|
|
1798
|
+
maximum length the compiler will serialize") from a few Resources up. Twelve type exports are new, one set per
|
|
1799
|
+
surface: `AvApplicationFacade`, `AvApplicationResourceFacade`, `AvApplicationFamilyFacade`, and the same for
|
|
1800
|
+
`AvClient…`, `AvAppTx…` and `AvClientTx…`. Measured over 50 Resources (Nest 12's `tsconfig`, TypeScript 6): each such
|
|
1801
|
+
method emits about 400 KB of declaration and the build peaks at about 370 MB. A variant (`…Post.find.many`)
|
|
1802
|
+
is still a call type the declaration cannot name: give that method a return type, or return the call's result.
|
|
1803
|
+
|
|
1804
|
+
An exported `defineResource(...)` result, and an exported configuration or Framework builder that holds typed
|
|
1805
|
+
stages (spread from a `defineResource` result or written inline in `createFramework`), now compile there too. Before,
|
|
1806
|
+
one typed variant pipe was enough for TS7056: a declaration wrote each inline stage's parameter types, and with them
|
|
1807
|
+
the whole model, once per parameter. The types that carry a configuration onwards now state each stage list by its
|
|
1808
|
+
public type for any model (`readonly AvGuard[]`, `readonly AvPipe[]`, …): `defineResource` returns the new
|
|
1809
|
+
`AvDefinedResource<R, Block, Overlays>`, and `createFramework`'s `AvFramework<M, Cfg>` carries the configuration that
|
|
1810
|
+
way. Every key, field definition, `forceTransaction`, validator and compute phase is kept as written, so a spread
|
|
1811
|
+
block is still checked where it is placed and the façades are unchanged; `AvFramework<M, typeof config>` still
|
|
1812
|
+
receives `createFramework(config)`. Measured with the scaffold's shape over 2 Resources, the declarations shrink from
|
|
1813
|
+
5.4 MB to 84 KB; over 50 Resources they emit (before: TS7056) and the build peaks under 400 MB.
|
|
1814
|
+
|
|
1815
|
+
- e8fbb02: Breaking: data is read-only once it is final, and every stage gets `ctx.$state`.
|
|
1816
|
+
|
|
1817
|
+
Pipes are now the only stage that changes the arguments. A pipe is told frozen arguments and returns new ones (or
|
|
1818
|
+
`undefined` to keep them); changing them in place throws a `TypeError`. Guards, interceptors, hooks and filters are
|
|
1819
|
+
told frozen arguments too, and an `after` hook a frozen copy of the result. The validators judge what the pipes
|
|
1820
|
+
returned, and that is exactly what the adapter writes: a hook or an interceptor can no longer change the write after
|
|
1821
|
+
validation. An interceptor may still return a different result after `next()`; the output check holds it. The result a
|
|
1822
|
+
caller receives is not frozen. A `Date` or a `Uint8Array` cannot be frozen, so after validation each stage gets its
|
|
1823
|
+
own copy of it. Compute fills its fields on the framework's own copy, and its `data` is a frozen snapshot.
|
|
1824
|
+
|
|
1825
|
+
```ts
|
|
1826
|
+
// rc.1: return new arguments instead of changing them.
|
|
1827
|
+
pipes: [(args) => ({ ...args, data: { ...args.data, ownerId: ownerOf(args) } })],
|
|
1828
|
+
```
|
|
1829
|
+
|
|
1830
|
+
`ctx.$state` is the one writable space per request. Declare what it holds once, then read and write it in any stage:
|
|
1831
|
+
|
|
1832
|
+
```ts
|
|
1833
|
+
declare module "@aventara/core" {
|
|
1834
|
+
interface AvState {
|
|
1835
|
+
user?: { id: string; roles: string[] };
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
const signedIn: AvGuard = (ctx) => ctx.$state.user !== undefined;
|
|
1840
|
+
```
|
|
1841
|
+
|
|
1842
|
+
- One object per request. A transaction's steps and its Resource transaction stages share it, and a ctx call,
|
|
1843
|
+
`ctx.$sql` and `ctx.$transaction` inherit the stage's. Compute gets a read-only view; writing through it throws a
|
|
1844
|
+
`TypeError`.
|
|
1845
|
+
- Seed it per call: `framework.application.Post.create.one(args, { state })`, the same on `framework.client`, and
|
|
1846
|
+
`framework.transaction(handles, { state })`. The object is used as it is, so you see what the stages wrote. A call
|
|
1847
|
+
that seeds nothing starts with `{}`.
|
|
1848
|
+
- `framework.execute` and `executeTransaction` take it as the request's `state` member (not an object: `A2004`,
|
|
1849
|
+
`V1001` at `["state"]`). `AvProtocol.bind(framework).handleOperation(request, { state: () => … })` and
|
|
1850
|
+
`handleTransaction` take a seed function, called once the request is decoded. A seed that throws an `AvError`
|
|
1851
|
+
answers that error; any other throw answers `A3000` and reaches diagnostics.
|
|
1852
|
+
- `@aventara/nest` seeds it from the platform request (see its changelog).
|
|
1853
|
+
|
|
1854
|
+
New public types: `AvState` and `AvDeepReadonly<T>`, the read-only shape to claim when a stage reads
|
|
1855
|
+
`ctx.$args` (`ctx.$args as AvDeepReadonly<PostArgs>`). A generated client copies `AvState`'s declaration with the
|
|
1856
|
+
rest of core's types; it does nothing there.
|
|
1857
|
+
|
|
1858
|
+
- 916e5f6: Breaking: in-process calls return data and throw, with the generated client's error classes.
|
|
1859
|
+
`framework.application`, `framework.client` and `framework.transaction` now settle the way the generated client
|
|
1860
|
+
does: they resolve the data and throw every failure as the `AvError` subclass of its code's class. The subclasses
|
|
1861
|
+
are new exports of `@aventara/core`, chosen by one code→class table the generated client reads too:
|
|
1862
|
+
|
|
1863
|
+
| Class | Codes |
|
|
1864
|
+
| ------------------------- | --------------------------------------- |
|
|
1865
|
+
| `AvProtocolError` | A2000, A2001, A2002, A2006, A2010–A2012 |
|
|
1866
|
+
| `AvNotFoundError` | A2003 |
|
|
1867
|
+
| `AvValidationError` | A2004, A2007, A2009 |
|
|
1868
|
+
| `AvContractMismatchError` | A2005 |
|
|
1869
|
+
| `AvConflictError` | A2008, A2013, A2014 |
|
|
1870
|
+
| `AvInternalError` | A3000–A3004 |
|
|
1871
|
+
| `AvAuthError` | A4000–A4002 |
|
|
1872
|
+
|
|
1873
|
+
`instanceof` works, and `error.code` and `error.cause` are what the envelope carried (a transaction's
|
|
1874
|
+
`cause.operation` names the failing step). A stage's ctx calls throw the same subclasses. `framework.execute`,
|
|
1875
|
+
`framework.executeTransaction` and the protocol binding still answer the envelope: it is the transport's format.
|
|
1876
|
+
|
|
1877
|
+
```ts
|
|
1878
|
+
// Before
|
|
1879
|
+
const res = await framework.application.Post.find.unique({
|
|
1880
|
+
where: { id },
|
|
1881
|
+
select: ["id", "title"],
|
|
1882
|
+
});
|
|
1883
|
+
if (res.code !== "A1000") return null;
|
|
1884
|
+
return res.data;
|
|
1885
|
+
const result = await framework.transaction([user, post]);
|
|
1886
|
+
if (result.code === "A1009") use(result.data[1]);
|
|
1887
|
+
|
|
1888
|
+
// After
|
|
1889
|
+
try {
|
|
1890
|
+
return await framework.application.Post.find.unique({
|
|
1891
|
+
where: { id },
|
|
1892
|
+
select: ["id", "title"],
|
|
1893
|
+
});
|
|
1894
|
+
} catch (error) {
|
|
1895
|
+
if (error instanceof AvNotFoundError) return null;
|
|
1896
|
+
throw error;
|
|
1897
|
+
}
|
|
1898
|
+
const [created, written] = await framework.transaction([user, post]);
|
|
1899
|
+
```
|
|
1900
|
+
|
|
1901
|
+
The façades' result types follow: a call resolves the operation's slot (`T`, or `T | null` for a `first`), and
|
|
1902
|
+
`framework.transaction` resolves its results tuple. `AvOperationCall` loses its `Settlement` parameter (every direct
|
|
1903
|
+
call settles to data), and `AvError` takes its code as a type parameter (`AvError<"A2003">`); `new AvError(code,
|
|
1904
|
+
cause)` is unchanged.
|
|
1905
|
+
|
|
1906
|
+
New: a stage registered at a variant (`resources.<R>.operations.<family>.<variant>.pipelines`) gets its operation's
|
|
1907
|
+
arguments typed. `ctx.$args` is that operation's arguments, deep read-only, so a write is a type error and the editor
|
|
1908
|
+
completes `data`, `where`, `select`; a pipe there is told that type, deep read-only too, and must return it (or
|
|
1909
|
+
`undefined`). The type is read from the model's Application Contract view in the application forms, and a reference
|
|
1910
|
+
position also admits a plan step's unresolved `$ref`. Layer, Resource and family stages keep `unknown`. A pipe typed
|
|
1911
|
+
for any operation (`AvPipe`) still fits a variant; its output is held to the server's rules at runtime (`A3004`). The
|
|
1912
|
+
stage types `AvContext`, `AvGuard`, `AvPipe`, `AvBeforeHook`, `AvAfterHook`, `AvInterceptor`, `AvFilter` and
|
|
1913
|
+
`AvPipeline` take the arguments as a new last type parameter, `unknown` by default.
|
|
1914
|
+
|
|
1915
|
+
Behaviour change: a Resource's transaction filters are the transaction's outer layer, and they run last. An error a
|
|
1916
|
+
step throws inside a transaction goes through that step's own filters first, inner to outer; when none handles it,
|
|
1917
|
+
the transaction interceptors unwind (their `next()` rejects), and then it goes to the touched Resources' transaction
|
|
1918
|
+
filters (the last-touched Resource's first) before it is answered. A transaction interceptor's `next()` rejects with
|
|
1919
|
+
the failed step's outcome as the `AvError` a façade would throw for it (`AvNotFoundError` for a unique miss,
|
|
1920
|
+
`instanceof` works, `code` and `cause` are set); before, it rejected with an internal rollback object. An error the
|
|
1921
|
+
step's filters leave unhandled reaches the interceptor sanitized, as the caller would get it. Catching it still cannot
|
|
1922
|
+
commit the failed step. An error a transaction stage throws goes to the
|
|
1923
|
+
transaction filters alone, also after the interceptors have unwound. ROLLBACK follows both. Before, a step's
|
|
1924
|
+
unhandled error was answered without reaching them.
|
|
1925
|
+
|
|
1926
|
+
Behaviour change: a strict unique operation that matches no row (`find.unique`, `update.unique`, `delete.unique`)
|
|
1927
|
+
now fails at the operation, inside the pipeline, as a missing `$connect` target already did. No `after` hook runs
|
|
1928
|
+
for it, an interceptor's `next()` throws the `AvNotFoundError` (it resolved `null` before), the filters see it and
|
|
1929
|
+
may answer another outcome, and a forced call or a transaction step rolls back. Unhandled, it is the same `A2003`
|
|
1930
|
+
as before: the façades throw `AvNotFoundError` and HTTP answers `404`. A `find.first`, `update.first` or
|
|
1931
|
+
`delete.first` that matches no row still resolves `null`, and its `after` hooks run.
|
|
1932
|
+
|
|
1933
|
+
Performance: field write guards and field pipes are dispatched from lists planned at startup, per origin and
|
|
1934
|
+
Resource. A call to a Resource with no field guard reads no record for one, a `create.many` walks its records for
|
|
1935
|
+
guards only when a guarded field is registered, and nested data is walked for guards only when some guard exists.
|
|
1936
|
+
|
|
1937
|
+
- Breaking: required create fields are typed. Each Resource now states what a create cannot do without, and every
|
|
1938
|
+
create payload is typed by it: `framework.application`, `framework.client`, `appTx`/`clientTx`, a stage's `ctx` and
|
|
1939
|
+
typed `args`, and generated clients. Leaving out a required field is a type error, and so is writing a required
|
|
1940
|
+
relation neither way or both ways:
|
|
1941
|
+
|
|
1942
|
+
```ts
|
|
1943
|
+
await framework.application.Post.create.one({
|
|
1944
|
+
data: { title: "Hi", authorId },
|
|
1945
|
+
}); // ok
|
|
1946
|
+
await framework.application.Post.create.one({
|
|
1947
|
+
data: { title: "Hi", author: { $connect: { id } } },
|
|
1948
|
+
}); // ok
|
|
1949
|
+
await framework.application.Post.create.one({ data: { title: "Hi" } }); // type error: `author` or `authorId`
|
|
1950
|
+
await framework.application.Post.create.one({ data: { authorId } }); // type error: `title`
|
|
1951
|
+
```
|
|
1952
|
+
|
|
1953
|
+
It covers `create.one`'s `data`, every `create.many` and `create.count` record, `upsert`'s `create`, and nested
|
|
1954
|
+
`$create`, `$connectOrCreate` and `$upsert` payloads. A create nested through a relation gets that relation from its
|
|
1955
|
+
parent, so `author: { $create: … }` is not needed inside `Author.posts.$create`. A `$ref` satisfies a field.
|
|
1956
|
+
|
|
1957
|
+
The fact is a new optional member of every Resource in every Contract, `createRequirements`
|
|
1958
|
+
(`AvCreateRequirements`, `AvCreateRequirementGroup`): `required` (fields), `oneOf` (each required to-one relation and
|
|
1959
|
+
the fields holding its reference, `[["author", "authorId"]]`; a composite reference is all of its fields together),
|
|
1960
|
+
and `parentSupplies` (what a nested create gets from its parent). Each layer's Contract states only what that layer
|
|
1961
|
+
lets a caller write: when the layer hides `authorId`, `author` alone is required; when it hides `author`, `authorId`
|
|
1962
|
+
is. A field a `compute.create` stage fills is not required. The Client Contract's hash changes for every schema with a
|
|
1963
|
+
required field or relation, so regenerate your clients.
|
|
1964
|
+
|
|
1965
|
+
Adapter authors: the requirements move from the adapter's own `createRequirements` table, which is removed, onto its
|
|
1966
|
+
model (`AvAdapterResource.createRequirements`, the same shape as the Contract's, before any layer is applied). A name
|
|
1967
|
+
there that is no field of the right kind fails construction with `COMPILER_UNKNOWN_FIELD`.
|
|
1968
|
+
|
|
1969
|
+
The runtime check is unchanged in what it answers (`A2004` with `V1000` at the missing field, or at the relation where
|
|
1970
|
+
the caller may write it; `V1012` for both), and now reads the active Contract's statement. Two consequences: a
|
|
1971
|
+
relation the layer requires alone is reported without naming a reference field the layer hides, and a required
|
|
1972
|
+
relation's reference fields come from the statement, so an adapter that states no referential edges still has a
|
|
1973
|
+
required relation written either way.
|
|
1974
|
+
|
|
1975
|
+
A field an operation pipe fills on the server is now required of the caller. Give it a `compute.create` stage or a
|
|
1976
|
+
database default so callers can leave it out, or have them pass it.
|
|
1977
|
+
|
|
1978
|
+
A layer that still creates a Resource can no longer leave a requirement unwritable. Hiding a required field (or
|
|
1979
|
+
exempting its `create` capability), or a required relation together with a reference field it needs, while the layer
|
|
1980
|
+
keeps a `create` or `upsert` variant or a nested create into the Resource, fails construction with
|
|
1981
|
+
`COMPILER_HIDDEN_REQUIRED_FIELD`. A composite reference is written whole: each of its blocked fields needs a
|
|
1982
|
+
`compute.create`. For example:
|
|
1983
|
+
|
|
1984
|
+
```text
|
|
1985
|
+
the client layer still creates resource "Post" (create.one, upsert.unique) but hides its required field "tenantId",
|
|
1986
|
+
which every create must write; expose "tenantId", give "tenantId" a compute.create, or exempt the create operation
|
|
1987
|
+
```
|
|
1988
|
+
|
|
1989
|
+
A pipe that filled the hidden field is not enough. Move the value into a `compute.create` stage on the field (at the
|
|
1990
|
+
root or in that layer), keep the field exposed, or exempt the layer's create operations.
|
|
1991
|
+
|
|
1992
|
+
A `create.many` or `create.count` record cannot write a relation, so a required relation is met there by its
|
|
1993
|
+
reference fields alone. In those records `authorId` is plainly required by the types, and a record without it answers
|
|
1994
|
+
`A2004` with `V1000` at `data.<n>.authorId` (before, a layer that hid `authorId` let it reach the database, which
|
|
1995
|
+
answered `A3001`). A layer that hides `authorId`, even with `author` exposed, while it keeps `create.many` or
|
|
1996
|
+
`create.count` now fails construction with `COMPILER_HIDDEN_REQUIRED_FIELD`:
|
|
1997
|
+
|
|
1998
|
+
```text
|
|
1999
|
+
the client layer still creates resource "Post" in bulk (create.count, create.many) but hides its reference field
|
|
2000
|
+
"authorId", which every bulk create must write: a bulk record cannot write the relation "author"; expose "authorId",
|
|
2001
|
+
give "authorId" a compute.create, or exempt create.count and create.many
|
|
2002
|
+
```
|
|
2003
|
+
|
|
2004
|
+
A `compute.create` written inline in `createFramework({ ... })` without parameter types now counts, at the root and in
|
|
2005
|
+
an overlay: the field it fills is no longer required by the types. Before, such a stage made the whole inline
|
|
2006
|
+
configuration's type fall back to the untyped one, so the façades ignored it (and everything else the literal stated).
|
|
2007
|
+
For that, a `compute.create` or `compute.update` stage's `field` and `data` are keyed by `string` now, not by your
|
|
2008
|
+
field names; their values were `unknown` already. `AvComputeCreate` and `AvComputeUpdate` take the model alone
|
|
2009
|
+
(`AvComputeCreate<Model>`).
|
|
2010
|
+
|
|
2011
|
+
- Breaking: the root key `entrypoint` is now `api`, and `framework.entrypoint` is `framework.api`. The value is the
|
|
2012
|
+
same mount path, normalized the same way:
|
|
2013
|
+
|
|
2014
|
+
```ts
|
|
2015
|
+
createFramework({ api: "/api", adapter });
|
|
2016
|
+
```
|
|
2017
|
+
|
|
2018
|
+
`entrypoint` is refused with `CONFIG_MOVED_PROPERTY` ("entrypoint moved in rc.1: use api") and is a type error. A
|
|
2019
|
+
generated client still takes `entrypoint` (the server's URL), and so do `defineClientConfig` and
|
|
2020
|
+
`avclient init --entrypoint`: those name where a client points, not where the server mounts.
|
|
2021
|
+
|
|
2022
|
+
- New: the `agent` layer and the Agent Contract, the base for serving AI agents over MCP. `agent` holds the six layer
|
|
2023
|
+
keys an overlay holds, plus `extends`: the layer it starts from, `"client"` (the default), `"application"` or
|
|
2024
|
+
`"root"`. Its view is that layer's final view, narrowed by its own `exposed`/`exemptions`; it never widens it. An
|
|
2025
|
+
exemption that would expose what its parent hid fails construction, and the message names the parent:
|
|
2026
|
+
|
|
2027
|
+
```text
|
|
2028
|
+
field "Post.body" is hidden by client, the layer agent extends, and the agent layer only narrows that view;
|
|
2029
|
+
expose it in client, or extend a layer that exposes it
|
|
2030
|
+
```
|
|
2031
|
+
|
|
2032
|
+
Stages run along the chain: the root's, then the parent overlay's, then the agent's, level by level, and every stage
|
|
2033
|
+
is told `ctx.$origin === "agent"` (`AvRequestOrigin` and `AvConfigLayerName` gain `"agent"`). Compute chains the same
|
|
2034
|
+
way. `limits` and `transactionsStyle` are the parent's effective values unless the agent states its own. Left out,
|
|
2035
|
+
the agent layer is its parent's view exactly. `defineResource` takes an `agent` block beside `application` and
|
|
2036
|
+
`client`.
|
|
2037
|
+
|
|
2038
|
+
The layer compiles to `framework.contracts.agent` (`AvContractAgent`), built and identified like the Client Contract:
|
|
2039
|
+
its protocol version and its own hash over its own content, so an agent that exposes what the client exposes has the
|
|
2040
|
+
client's hash. Required create fields, hidden-requirement checks and `forceTransaction` checks apply to it as to any
|
|
2041
|
+
layer, and a refusal names `agent`.
|
|
2042
|
+
|
|
2043
|
+
`AvProtocol.bind(framework, { origin: "agent" })` runs agent calls through the same decoding and validation as HTTP
|
|
2044
|
+
client calls: its surface and `/_contract` are the Agent Contract's, the identity headers must carry the Agent
|
|
2045
|
+
Contract's hash (the Client Contract's is `A2005`), and the calls run under the agent's pipelines. The default
|
|
2046
|
+
binding is unchanged. `framework.execute` and `executeTransaction` take `origin: "agent"` with the Agent Contract's
|
|
2047
|
+
protocol identity.
|
|
2048
|
+
|
|
2049
|
+
- New: the root key `mcp` turns MCP on. It is a mount path (`mcp: "/mcp"`), or `{ path, authorization }` where
|
|
2050
|
+
`authorization` is `{ servers, verify }`: the OAuth authorization servers (https URLs) a caller gets its token from,
|
|
2051
|
+
and `verify(token)`, which resolves the caller or `null`. The path is written as `api` is and must not be `api` itself
|
|
2052
|
+
or inside its `_` namespace. Each mistake is a startup error at its path (`mcp.path`,
|
|
2053
|
+
`mcp.authorization.servers[1]`, …). The resolved endpoint is `framework.mcp` (`AvMcpEndpoint`), for the host that
|
|
2054
|
+
serves it; `undefined` when MCP is off. `agent` without `mcp` is allowed.
|
|
2055
|
+
|
|
2056
|
+
- New: `AvDiagnostic` has a third kind, `AvMcpDiagnostic` (`kind: "mcp"`). When the MCP endpoint itself fails, outside
|
|
2057
|
+
any tool call's operation (the MCP SDK's server or transport), the caller gets a bare `500` and the diagnostics sink
|
|
2058
|
+
gets the raw error with `code: "A3000"`, `origin: "agent"` and a minted `requestId`. A sink that narrows on `kind`
|
|
2059
|
+
now has three arms.
|
|
2060
|
+
|
|
2061
|
+
- Faster startup: each remote Contract (client and agent) is written as canonical JSON once. Its hash, the Contract
|
|
2062
|
+
object and the `/_contract` body all come from that one text instead of four separate passes. The bytes and
|
|
2063
|
+
hashes are unchanged. `createFramework` plus binding the client and agent protocols took 186 ms at 117 Resources and
|
|
2064
|
+
now takes 133 ms (90 to 67 ms at 50, 20 to 15 ms at 6).
|
|
2065
|
+
|
|
2066
|
+
- Smaller edge bundles: `@aventara/core/avz` imports only the zod builders it uses, so a bundler drops the rest of
|
|
2067
|
+
`zod/mini`, its locales included. A minified bundle of the entry went from 453 KB to 48 KB.
|
|
2068
|
+
|
|
3
2069
|
## 1.0.0-rc.0
|
|
4
2070
|
|
|
5
2071
|
### Major Changes
|
|
@@ -185,6 +2251,7 @@
|
|
|
185
2251
|
|
|
186
2252
|
A value whose first read throws is now refused even if reading it again would succeed: `A2004`/`V1001` at the object
|
|
187
2253
|
or array whose read failed. Before, it was copied from the second read.
|
|
2254
|
+
|
|
188
2255
|
- 2d00ad7: An issue your own code throws from a transaction step now keeps its `path`. A pipe, guard, hook, interceptor,
|
|
189
2256
|
filter or computed behavior does not know where its operation stands in a plan, so it writes the path as it would for
|
|
190
2257
|
a single call — `["arguments", "data", "name"]`. Inside `framework.transaction`, `framework.executeTransaction` and
|
|
@@ -200,7 +2267,7 @@
|
|
|
200
2267
|
`framework.transaction`, `framework.executeTransaction` and `POST /_transactions` alike. A single call is unchanged
|
|
201
2268
|
(`["arguments", …]`). If your code reads `issue.path` from a transaction's answer, drop the `"operations", i, "args"`
|
|
202
2269
|
prefix to get the path inside that step's arguments. `@aventara/testing`'s protocol corpus row "a client-hidden
|
|
203
|
-
field —
|
|
2270
|
+
field — /\_transactions" and `fixtures/protocol-v1.json` carry the new path.
|
|
204
2271
|
- d43d1cc: **Types:** `diagnostic.deliberate` now type-checks on any `FrameworkDiagnostic`, without checking
|
|
205
2272
|
`diagnostic.kind` first, so a `diagnostics` sink can log it for every report. It is `true` only on an `"operation"`
|
|
206
2273
|
diagnostic for a `FrameworkError` you threw on purpose with an `A3xxx` code, and always absent on a `"transaction"`
|