@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/README.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
The Aventara framework itself: the framework factory (`createFramework`), configuration and its resolution, the
|
|
4
4
|
adapter interfaces an ORM adapter implements, Contract compilation and identity, validation, operation semantics, the
|
|
5
|
-
pipeline runtime, transactions and execution. Its HTTP protocol
|
|
6
|
-
|
|
5
|
+
pipeline runtime, transactions and execution. Its HTTP protocol is the separate entry point `@aventara/core/protocol`,
|
|
6
|
+
which hosts such as `@aventara/nest` serve. The protocol is passive data and pure functions, and it mounts nothing.
|
|
7
7
|
|
|
8
|
-
Core
|
|
9
|
-
|
|
8
|
+
Core imports neither Prisma nor NestJS. An application uses it through an adapter (`@aventara/prisma7-adapter`) and a
|
|
9
|
+
host (`@aventara/nest`), and the usual way to set that up is the CLI.
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
@@ -16,21 +16,21 @@ npx @aventara/cli@rc init # or: add Aventara to an existing NestJS 12
|
|
|
16
16
|
npm install @aventara/core@rc # the package on its own
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
Node `^22.
|
|
19
|
+
Node `^22.23.4 || >=24.22.1`. ES modules, with a CommonJS `default` condition.
|
|
20
20
|
|
|
21
21
|
## Documentation
|
|
22
22
|
|
|
23
|
-
Start from the `@aventara/cli` README (what `new` and `init` write), then `@aventara/nest` (mounting
|
|
24
|
-
and `@aventara/prisma7-adapter` (the Prisma 7 adapter and its generate step)
|
|
23
|
+
Start from the `@aventara/cli` README (what `new` and `init` write), then `@aventara/nest` (mounting an `AvFramework`)
|
|
24
|
+
and `@aventara/prisma7-adapter` (the Prisma 7 adapter and its generate step). Each package's README ships in its
|
|
25
25
|
tarball and shows on its npm page.
|
|
26
26
|
|
|
27
27
|
## Typed Json fields
|
|
28
28
|
|
|
29
|
-
A `Json` field is any JSON by default. To give it a shape, build one with `AvZ` from `@aventara/core/
|
|
30
|
-
it
|
|
29
|
+
A `Json` field is any JSON by default. To give it a shape, build one with `AvZ` from `@aventara/core/avz` and declare
|
|
30
|
+
it as the field's `definition.json` in the configuration's `resources` tree:
|
|
31
31
|
|
|
32
32
|
```ts
|
|
33
|
-
import { AvZ } from "@aventara/core/
|
|
33
|
+
import { AvZ } from "@aventara/core/avz";
|
|
34
34
|
|
|
35
35
|
const postMeta = AvZ.object({
|
|
36
36
|
seo: AvZ.object({ title: AvZ.string(), keywords: AvZ.array(AvZ.string()) }),
|
|
@@ -42,86 +42,511 @@ const postMeta = AvZ.object({
|
|
|
42
42
|
export type PostMeta = AvZ.infer<typeof postMeta>;
|
|
43
43
|
|
|
44
44
|
const framework = await createFramework({
|
|
45
|
-
|
|
45
|
+
api: "/api",
|
|
46
46
|
adapter,
|
|
47
|
-
|
|
47
|
+
resources: { Post: { fields: { meta: { definition: { json: postMeta } } } } },
|
|
48
48
|
});
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
Zod).
|
|
54
|
-
- **Checked on every write.** A create, update, upsert, nested write or transaction step that writes `meta` — a value
|
|
55
|
-
a transaction step takes from an earlier step included — is checked against the shape before the database is
|
|
56
|
-
asked. A value out of shape is refused with `A2004`, one issue per problem at its exact path:
|
|
51
|
+
`framework.application`, `framework.client`, `appTx` and `clientTx` read and write `Post.meta` as `PostMeta` instead
|
|
52
|
+
of generic JSON. So does a client generated with `avclient`, which emits the type and needs no Zod.
|
|
57
53
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
54
|
+
The shape is also one of `meta`'s validators. Every write of `meta` is checked against it before the database is
|
|
55
|
+
asked: a create, update, upsert, nested write or transaction step, including a value a transaction step takes from an
|
|
56
|
+
earlier step. It is checked where validators run, after every pipe and write compute and before any interceptor or hook, and its issues
|
|
57
|
+
come in field order with the other validators'. A value out of shape is refused with `A2004`, one issue per problem at its
|
|
58
|
+
exact path:
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{ "code": "V1001", "path": ["arguments", "data", "meta", "seo", "title"], "message": "Value must be a string." }
|
|
62
|
+
```
|
|
61
63
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
64
|
+
A missing key is `V1000`, a value outside an enum or a literal `V1002`, an integer outside the safe range `V1004`,
|
|
65
|
+
and a key the shape does not declare `V1005`, once per key. Reads and filters are never checked against a shape, so
|
|
66
|
+
rows written before the shape existed stay readable.
|
|
67
|
+
|
|
68
|
+
`AvZ` has `string`, `number`, `int`, `boolean`, `null`, `literal`, `enum`, `object`, `array`, `tuple`, `record`,
|
|
69
|
+
`union`, `discriminatedUnion`, `optional` and `nullable`. A shape node takes checks with `.check(...)`: a string takes
|
|
70
|
+
length, pattern and format checks, a number or an integer takes bounds and `multipleOf`, and an array takes
|
|
71
|
+
`AvZ.minItems`/`AvZ.maxItems`, as in `AvZ.string().check(AvZ.maxLength(80))`. There are no refinements, transforms or
|
|
72
|
+
defaults. Objects are closed: a key the shape does not declare is refused. `optional` marks an object key that may be
|
|
73
|
+
left out.
|
|
74
|
+
|
|
75
|
+
Only a field the adapter discovered as `Json` takes a shape. The shape cannot admit `null` or absence at its top
|
|
76
|
+
level; whether the field may be `null` comes from your schema. A shape is built with `AvZ` only. A Zod schema is
|
|
77
|
+
refused, both as a type error and when the framework starts.
|
|
78
|
+
|
|
79
|
+
The shape is part of the ClientContract, as JSON Schema. Adding or changing one changes the ClientContract hash, so
|
|
80
|
+
regenerate the client.
|
|
74
81
|
|
|
75
82
|
The shape types `AvZShape`, `AvZObject`, `AvZOptional` and `AvZNullable` are exported so a project that emits
|
|
76
83
|
declarations can name the shapes it exports.
|
|
77
84
|
|
|
78
|
-
`@aventara/core/
|
|
85
|
+
`@aventara/core/avz` is the only entry that loads Zod (`zod/mini`); an application that declares no shape never loads
|
|
79
86
|
it.
|
|
80
87
|
|
|
88
|
+
## Server-side calls resolve their data and throw their failures
|
|
89
|
+
|
|
90
|
+
`framework.application`, `framework.client`, `framework.transaction([...])` and a pipeline's `ctx` calls settle the way
|
|
91
|
+
a generated client does: they resolve the data (a row, a list, a count, `null` for a `first` that matched nothing; a
|
|
92
|
+
committed transaction its steps' results in order) and throw every failure as the `AvError` subclass of its code's
|
|
93
|
+
class, exported from `@aventara/core`:
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { AvNotFoundError, AvValidationError } from "@aventara/core";
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const post = await framework.application.Post.update.unique({ where: { id }, data: { title } });
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (error instanceof AvNotFoundError) return null; // A2003
|
|
102
|
+
if (error instanceof AvValidationError) throw badRequest(error.cause.issues);
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
| Class | Codes |
|
|
108
|
+
| ------------------------- | ------------------------------------------- |
|
|
109
|
+
| `AvProtocolError` | A2000, A2001, A2002, A2006, A2010–A2012 |
|
|
110
|
+
| `AvNotFoundError` | A2003 |
|
|
111
|
+
| `AvValidationError` | A2004, A2007, A2009 |
|
|
112
|
+
| `AvContractMismatchError` | A2005 |
|
|
113
|
+
| `AvConflictError` | A2008, A2013, A2014 |
|
|
114
|
+
| `AvInternalError` | A3000–A3004 |
|
|
115
|
+
| `AvAuthError` | A4000–A4002 |
|
|
116
|
+
|
|
117
|
+
`error.code` and `error.cause` are the envelope's (a transaction's `cause.operation` names the failing step). The
|
|
118
|
+
generated client throws the same classes by the same table. `framework.execute` and `framework.executeTransaction`, the
|
|
119
|
+
entries a transport calls, still answer the envelope.
|
|
120
|
+
|
|
121
|
+
## The client layer, called with your own values
|
|
122
|
+
|
|
123
|
+
`framework.client` reads its arguments in the wire forms a decoded request body carries: a `datetime` is its ISO string,
|
|
124
|
+
a `bigint` its base-10 string. That suits a test that should behave like a remote caller. Server code that acts for a
|
|
125
|
+
remote caller, such as a Server Component or a Server Action, holds `Date`s and `bigint`s instead. It calls
|
|
126
|
+
`framework.serverClient`:
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
const recent = await framework.serverClient.Post.find.many({
|
|
130
|
+
where: { createdAt: { gt: new Date(Date.now() - 86_400_000) } },
|
|
131
|
+
select: ["id", "title"],
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`serverClient` has the client layer's types and rules: what the client layer hides is absent, and the client
|
|
136
|
+
pipelines, validation, exposure and policy run. Its arguments are the application forms (`Date`, `bigint`,
|
|
137
|
+
`AvDecimal`, `Uint8Array`), the same ones `framework.application` takes, and its results are `framework.client`'s.
|
|
138
|
+
Each call copies its arguments, encodes them into the wire forms and runs the request `framework.client` would run.
|
|
139
|
+
`framework.serverClientTx` builds client-layer transaction handles the same way; one `framework.transaction([...])`
|
|
140
|
+
may hold them beside `clientTx` handles, never beside `appTx` ones.
|
|
141
|
+
|
|
142
|
+
## Every code, explained
|
|
143
|
+
|
|
144
|
+
`AvErrorCatalog` holds every code this package answers, reports in a validation issue, or refuses to start with: its
|
|
145
|
+
`kind`, `message`, `cause`, `fix` and `docs` URL. The same catalog ships beside `dist` as `ERRORS.md`, and each entry
|
|
146
|
+
of a startup `AvConstructionError`'s `diagnostics` carries its code's `docs` URL.
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import { AvErrorCatalog } from "@aventara/core";
|
|
150
|
+
|
|
151
|
+
AvErrorCatalog.A2004.fix; // "Fix the argument each issue's path points at, …"
|
|
152
|
+
AvErrorCatalog.V1000.docs; // "https://aventara-framework.vercel.app/docs/error-codes#v1000"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Required create fields are typed
|
|
156
|
+
|
|
157
|
+
Each Resource's Contract states what a create cannot do without (`createRequirements`), as the adapter reads it off
|
|
158
|
+
the database schema: the fields every create must write, and each required relation, which a create writes either as
|
|
159
|
+
the relation or through the fields that hold its reference, never both. The façades, `appTx`/`clientTx`, a stage's
|
|
160
|
+
`ctx` and the generated client type every create payload by it:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
await framework.application.Post.create.one({ data: { title: "Hi", authorId } }); // ok
|
|
164
|
+
await framework.application.Post.create.one({ data: { title: "Hi", author: { $connect: { id: authorId } } } }); // ok
|
|
165
|
+
// @ts-expect-error neither `author` nor `authorId`
|
|
166
|
+
await framework.application.Post.create.one({ data: { title: "Hi" } });
|
|
167
|
+
// @ts-expect-error `author` and `authorId` both
|
|
168
|
+
await framework.application.Post.create.one({ data: { title: "Hi", authorId, author: { $connect: { id: authorId } } } });
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
It covers `create.one`'s `data`, every `create.many` record, `upsert`'s `create`, and nested `$create`,
|
|
172
|
+
`$connectOrCreate` and `$upsert` payloads, where the relation the create is nested through is supplied by its parent.
|
|
173
|
+
A `$ref` satisfies a field. Each layer's Contract states only what that layer lets a caller write: hide `authorId` and
|
|
174
|
+
`author` alone is required; hide `author` and `authorId` is. A field a `compute.create` stage fills is not required,
|
|
175
|
+
whether the stage is written inline in `createFramework({...})`, in a `defineResource` block or in a typed config.
|
|
176
|
+
The runtime still refuses what the types refuse (`A2004`, `V1000` or `V1012`).
|
|
177
|
+
|
|
178
|
+
A `create.many` or `create.count` record cannot write a relation, so there `authorId` is required. A layer that hides
|
|
179
|
+
`authorId` while it keeps those variants fails at startup (`COMPILER_HIDDEN_REQUIRED_FIELD`): expose `authorId`, give
|
|
180
|
+
it a `compute.create`, or exempt `create.many` and `create.count` in that layer
|
|
181
|
+
(`exemptions: { resources: { Post: { fields: { authorId: true }, operations: { create: ["many", "count"] } } } }`).
|
|
182
|
+
|
|
183
|
+
A field your operation pipe fills server-side is now required of the caller. Give it a `compute.create` stage (or a
|
|
184
|
+
database default) so callers can leave it out, or have them pass it.
|
|
185
|
+
|
|
186
|
+
Inside the pipeline the requirements hold from write compute on. A variant's guards, pipes and filters run before
|
|
187
|
+
compute, and their typed arguments require no field of a create, since compute may still fill it: `args.data.slug` is
|
|
188
|
+
`string | undefined` in a pipe, and a pipe may return a create without it. The variant's hooks and interceptors run
|
|
189
|
+
after compute and see every required field present.
|
|
190
|
+
|
|
191
|
+
## Returning a façade from a method
|
|
192
|
+
|
|
193
|
+
A Nest project emits declarations (`declaration: true`), so a method without a return type has its type written out.
|
|
194
|
+
Every node of the typed façades has a name for that, and so does the Contract view they are typed over, so a method can
|
|
195
|
+
return any of them without an annotation:
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
@Injectable()
|
|
199
|
+
export class PostsService {
|
|
200
|
+
constructor(@InjectFramework() private readonly framework: AppFramework) {}
|
|
201
|
+
|
|
202
|
+
posts() {
|
|
203
|
+
return this.framework.application.Post;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
findMany() {
|
|
207
|
+
return this.framework.application.Post.find.many;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
The declaration file writes a short reference, under half a kilobyte per method at any schema size:
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
posts(): import("@aventara/core").AvApplicationResourceFacade<
|
|
216
|
+
import("@aventara/core").AvContractView<import("…/discovery.artifact.js").AvModel, never, "omit", never, "omit", never, never>,
|
|
217
|
+
"Post">;
|
|
218
|
+
findMany(): import("@aventara/core").AvApplicationProjectingCall<
|
|
219
|
+
import("@aventara/core").AvContractView<import("…/discovery.artifact.js").AvModel, never, "omit", never, "omit", never, never>,
|
|
220
|
+
"Post", { readonly family: "find"; readonly variant: "many" }>;
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
The names, one set per surface (`application`, `client`, `serverClient`, `appTx`, `clientTx`, `serverClientTx`):
|
|
224
|
+
|
|
225
|
+
| Node | `framework.application` | the others |
|
|
226
|
+
| --- | --- | --- |
|
|
227
|
+
| the whole surface | `AvApplicationFacade` | `AvClientFacade`, `AvServerClientFacade`, `AvAppTxFacade`, `AvClientTxFacade`, `AvServerClientTxFacade` |
|
|
228
|
+
| one Resource | `AvApplicationResourceFacade` | `AvClientResourceFacade`, … |
|
|
229
|
+
| one family | `AvApplicationFamilyFacade` | `AvClientFamilyFacade`, … |
|
|
230
|
+
| a variant that takes a `select` or `include` | `AvApplicationProjectingCall` | `AvClientProjectingCall`, `AvServerClientProjectingCall`, `AvAppTxProjectingCall`, `AvClientTxProjectingCall`, `AvServerClientTxProjectingCall` |
|
|
231
|
+
| a variant that does not (the `count` variants: `find.count`, `update.count`, …) | `AvApplicationPlainCall` | `AvClientPlainCall`, `AvServerClientPlainCall`, `AvAppTxPlainCall`, `AvClientTxPlainCall`, `AvServerClientTxPlainCall` |
|
|
232
|
+
|
|
233
|
+
Each takes the view, `AvContractView`, as its first argument. The view's arguments are your model by its name, the root's
|
|
234
|
+
and the overlay's exposure as written, and the `fields` entries your configuration's `resources` blocks hold. A
|
|
235
|
+
configuration with no `fields` entries writes `never` there. One that has them writes those entries, stages erased, after
|
|
236
|
+
every reference: measured at about 23 KB per method over 50 Resources that each configure a field. Annotate the method
|
|
237
|
+
(`posts(): AppFramework["application"]["Post"]`) if that matters to you; the annotation is written as that reference.
|
|
238
|
+
|
|
239
|
+
You never need to write these names yourself. They exist so a declaration file can name what a method returns, and a
|
|
240
|
+
project that reads only your declarations types the method exactly as your Framework does. An application that publishes
|
|
241
|
+
no declarations can set `"declaration": false` and skip the question.
|
|
242
|
+
|
|
243
|
+
The configuration itself costs almost nothing to declare. The Prisma adapter's discovery artifact names the model's
|
|
244
|
+
type (`AvModel`), so the scaffold's `aventaraConfig()`, a `buildFramework` and its `AppFramework` alias write
|
|
245
|
+
`AvAdapter<AvModel>` and `AvFramework<AvModel, …>`: about 0.5 KB at any schema size, where rc.1 wrote
|
|
246
|
+
the model out once per mention (1.2 MB over 50 Resources).
|
|
247
|
+
|
|
248
|
+
An exported `defineResource(...)` result, and an exported configuration or Framework builder holding typed stages,
|
|
249
|
+
need no annotation: their stages are written by their public types (`readonly AvGuard[]`, `readonly AvPipe[]`, …),
|
|
250
|
+
never with the model the inline stages were typed from.
|
|
251
|
+
|
|
81
252
|
## Server-side calls copy their input
|
|
82
253
|
|
|
83
|
-
`framework.application`, `framework.client`, `framework.
|
|
84
|
-
`clientTx` builders read what you pass them once, when you call them, and copy it into plain data the framework owns.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
-
|
|
96
|
-
|
|
97
|
-
|
|
254
|
+
`framework.application`, `framework.client`, `framework.serverClient`, `framework.execute`,
|
|
255
|
+
`framework.executeTransaction`, and the `appTx` / `clientTx` / `serverClientTx` builders read what you pass them once, when you call them, and copy it into plain data the framework owns.
|
|
256
|
+
Validation, your guards and pipes, computed values and the adapter all see only the copy, and your stages see it
|
|
257
|
+
frozen: only a pipe changes the arguments, by returning new ones, so what the validators checked is what the adapter
|
|
258
|
+
writes. In practice:
|
|
259
|
+
|
|
260
|
+
- Changing your object afterwards changes nothing. An Immer draft revoked after `produce`, or a reactive store that
|
|
261
|
+
moves on, is safe to pass, because the call already has its own copy.
|
|
262
|
+
- A live Proxy (Vue `reactive`, MobX) works: it is read through its traps once.
|
|
263
|
+
- `Date`, `AvDecimal` and `Uint8Array` (a Node `Buffer` too) are copied as themselves; `bigint` and other primitives
|
|
264
|
+
are values.
|
|
265
|
+
- Arrays and plain objects (`{}` or `Object.create(null)`) are copied with their own enumerable string keys, like JSON:
|
|
266
|
+
inherited, non-enumerable and symbol keys are dropped. Any other object, such as a class instance or a `Map`, is
|
|
267
|
+
refused.
|
|
268
|
+
- A value that cannot be read (a revoked Proxy, a getter that throws) is refused too. Both refusals are errors in your
|
|
269
|
+
request, not the server's: `A2004` with one `V1001` at the value's path (`["arguments", …]`, or
|
|
270
|
+
`["operations", i, "args", …]` with `cause.operation` in a transaction), before any guard, pipe or adapter runs:
|
|
98
271
|
|
|
99
272
|
```json
|
|
100
273
|
{ "code": "V1001", "path": ["arguments", "where", "id"], "message": "Value could not be read." }
|
|
101
274
|
```
|
|
102
275
|
|
|
103
|
-
A builder never throws: `appTx`/`clientTx` keep the refusal, and `framework.transaction([...])`
|
|
276
|
+
A builder never throws: `appTx`/`clientTx` keep the refusal, and `framework.transaction([...])` throws it as an
|
|
277
|
+
`AvValidationError`.
|
|
104
278
|
|
|
105
279
|
To count these refusals, set `misuse` at the root of the configuration, beside `diagnostics`:
|
|
106
280
|
|
|
107
281
|
```ts
|
|
108
282
|
const framework = await createFramework({
|
|
109
|
-
|
|
283
|
+
api: "/api",
|
|
110
284
|
adapter,
|
|
111
285
|
misuse: (event) => metrics.increment("aventara.misuse", { kind: event.kind }),
|
|
112
286
|
});
|
|
113
287
|
```
|
|
114
288
|
|
|
115
|
-
It receives one `
|
|
116
|
-
the caller received, and `origin`, `resource`, `family`, `variant` and the
|
|
117
|
-
|
|
118
|
-
ignored. `diagnostics` keeps receiving the server's own unexpected failures, and nothing is
|
|
289
|
+
It receives one `AvMisuseEvent` per refusal, never the value itself. The event has `kind` (`"unreadable-value"` or
|
|
290
|
+
`"non-plain-value"`), the `path` the caller received, and `origin`, `resource`, `family`, `variant` and the
|
|
291
|
+
transaction step (`operation`) when they could be read. It cannot change the response: an error it throws, or a
|
|
292
|
+
promise it rejects, is ignored. `diagnostics` keeps receiving the server's own unexpected failures, and nothing is
|
|
293
|
+
reported to both.
|
|
294
|
+
|
|
295
|
+
HTTP requests are not copied twice. A body that `protocol.handleOperation` / `handleTransaction` parse from raw content
|
|
296
|
+
(as `@aventara/nest` hands it to them) is already plain data. A body your host parsed itself, passed as
|
|
297
|
+
`{ kind: "parsed" }`, is copied like any server-side input.
|
|
298
|
+
|
|
299
|
+
## Developer SQL in an application transaction
|
|
300
|
+
|
|
301
|
+
`framework.appTx.$sql` adds a SQL statement to an application transaction. It is written only as a tagged template, and
|
|
302
|
+
every interpolated value is a bound parameter, never part of the SQL text:
|
|
119
303
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
304
|
+
```ts
|
|
305
|
+
const post = framework.appTx.Post.create.one({ data: { title, authorId }, select: ["id"] });
|
|
306
|
+
const job = framework.appTx.$sql<{ msg_id: bigint }>`
|
|
307
|
+
select pgmq.send('jobs', jsonb_build_object('postId', ${post.$ref("id")}))`;
|
|
308
|
+
|
|
309
|
+
const [created, rows] = await framework.transaction([post, job]);
|
|
310
|
+
// rows is { msg_id: bigint }[]; without <Row> it is unknown[]
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
- The statement runs in plan order, in the same database transaction as the other steps. If it fails, or a later step
|
|
314
|
+
fails, everything rolls back.
|
|
315
|
+
- A value is a string, number, boolean, `bigint`, `null`, `Date`, `AvDecimal`, `Uint8Array`, a JSON value (a plain
|
|
316
|
+
object or array, bound as JSON text, so cast it: `${value}::jsonb`), or a `$ref` to an earlier step's field, which
|
|
317
|
+
is read after that step ran and then bound. Values are copied like every other input (see above).
|
|
318
|
+
- The SQL text is static. A table or column name cannot come from a value.
|
|
319
|
+
- A later step cannot `$ref` a SQL step's rows (`A2007`/`V1010`).
|
|
320
|
+
- No guard, pipe, hook, interceptor or filter runs around a SQL step. A SQL error is answered as the adapter
|
|
321
|
+
classifies it (a uniqueness or reference violation is `A2008`, anything else `A3001`).
|
|
322
|
+
- A stage's `ctx.$sql` runs the same bound template at once. Inside a transaction it joins it; outside one it is a plain
|
|
323
|
+
statement, with no transaction opened around it.
|
|
324
|
+
- It exists only here. `clientTx` has no `$sql`, the ClientContract carries nothing of it, and `/_transactions` cannot
|
|
325
|
+
express it. The adapter must support it (`@aventara/prisma7-adapter` does wherever it runs transactions); otherwise a
|
|
326
|
+
plan holding one is refused with `A2002`.
|
|
327
|
+
|
|
328
|
+
Aventara itself never writes raw SQL. This tagged template is the only way SQL reaches the database, and the SQL is
|
|
329
|
+
yours.
|
|
330
|
+
|
|
331
|
+
## The agent layer
|
|
332
|
+
|
|
333
|
+
`agent` is a layer for calls an AI agent makes, served over MCP. It narrows the view of the layer it `extends`
|
|
334
|
+
(`"client"` when left out, or `"application"`, or `"root"`) and never widens it; its stages run after that layer's, and
|
|
335
|
+
each one sees `ctx.$origin === "agent"`:
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
const framework = await createFramework({
|
|
339
|
+
api: "/api",
|
|
340
|
+
mcp: "/mcp",
|
|
341
|
+
adapter,
|
|
342
|
+
agent: {
|
|
343
|
+
exposed: true,
|
|
344
|
+
exemptions: { resources: { Post: { operations: { delete: true } } } },
|
|
345
|
+
pipelines: { guards: [(ctx) => ctx.$state.user !== undefined] },
|
|
346
|
+
},
|
|
347
|
+
});
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
It compiles to `framework.contracts.agent`, identified by its own hash, and `framework.mcp` is the resolved MCP
|
|
351
|
+
endpoint. A host runs agent calls through `AvProtocol.bind(framework, { origin: "agent" })`, which validates them
|
|
352
|
+
exactly as client calls are validated, against the Agent Contract.
|
|
353
|
+
|
|
354
|
+
## A policy engine
|
|
355
|
+
|
|
356
|
+
`policy` hands authorization to an engine Aventara never imports. The engine answers three questions per
|
|
357
|
+
Resource — may the principal run this action (`can`), which rows (`scopeOf`, a `where` fragment), which fields
|
|
358
|
+
(`fieldsOf`) — and core enforces the answers on every client and agent request:
|
|
359
|
+
|
|
360
|
+
```ts
|
|
361
|
+
const framework = await createFramework({
|
|
362
|
+
api: "/api",
|
|
363
|
+
adapter,
|
|
364
|
+
policy: {
|
|
365
|
+
engine, // satisfies AvPolicyEngine
|
|
366
|
+
principal: ({ $state }) => $state.user ?? null,
|
|
367
|
+
},
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
await framework.application.Note.find.many({}, { as: user }); // checked as `user`
|
|
371
|
+
await framework.application.Note.find.many({}); // server code: not checked
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
The read scope is applied at the adapter call, after every developer stage, to the root `where`, every to-many include
|
|
375
|
+
and its `$count`, and every relation filter. A field the engine governs (its `governed` list) is never in a default
|
|
376
|
+
projection; name it to get it, `select: ["$defaults", "email"]`. A refusal answers `403 A4002`.
|
|
377
|
+
|
|
378
|
+
Writes are checked too. A create asks `can` about each new row, whose rules may look rows up through `exists`. An
|
|
379
|
+
update or delete acts only on rows the caller may change and read; a single-row one that misses answers `A4002` when
|
|
380
|
+
the caller can read the row and `A2003` when it cannot. A field the write sends must be in
|
|
381
|
+
`fieldsOf({ access: "write", action })`, `action` being `"create"` or `"update"` (an upsert's branches and a nested
|
|
382
|
+
record's directive each their own), so a field can be locked on update and still written at create. A rule that
|
|
383
|
+
reaches another Resource through a relation (a note only in a campaign its author plays in) reads which of the
|
|
384
|
+
target's fields the reference names from the Core Contract (`framework.contracts.core`, a relation's `references`
|
|
385
|
+
beside its `referenceFields`); no Contract a caller is served states them. An update that moves a row out of what the caller reads, or fails an `after` condition
|
|
386
|
+
(`can` asked about the row the update returned), is rolled back. For an update the engine lists in `afterChecks`, that
|
|
387
|
+
question also carries `before`, the row as it was before the update; it is present only then. An engine that also
|
|
388
|
+
answers the optional `grantsOf` is asked, before the write, which of its grants cover that row (`exists` then sees
|
|
389
|
+
nothing the request writes), and gets the answer back on the after check as `beforeGrants`. Such an engine refuses
|
|
390
|
+
an after check that carries `before` without `beforeGrants` rather than deciding `before` after the write.
|
|
391
|
+
A typed literal in a fragment the engine answers may be in its application form (`Date`, `AvDecimal`, `bigint`) or its
|
|
392
|
+
wire form (an ISO string, a decimal string, a digit string), as a policy read from JSON states it. Where `transactionsStyle` is `"interactive"`, a
|
|
393
|
+
checked write with a check after it (an update, a nested write) runs in a transaction of its own, and a create or
|
|
394
|
+
delete checked before it runs is one statement; where it is `"none"`, a write that could only be checked after it runs
|
|
395
|
+
is refused (for after conditions, the updates the engine lists in `afterChecks`).
|
|
396
|
+
|
|
397
|
+
Nested writes (`$create`, `$connect` and the other relation directives) run as one call inside that transaction. Before
|
|
398
|
+
it, every row they link is held to the right scope: a linked row that holds the reference must be one the caller may
|
|
399
|
+
change, one whose parent holds it one the caller may read. Writing a reference field itself (`authorId: "u2"`) links
|
|
400
|
+
the same row, and is held the same way: the row it names must be one the caller may read. Where the adapter can link
|
|
401
|
+
the row in that write, core hands it the reference as a `$connect` carrying the read scope, so the write itself refuses
|
|
402
|
+
a row outside it (`A4002`), with no query of its own; where it cannot (a bulk write on Prisma), one `exists` per target
|
|
403
|
+
Resource asks before the write. Nothing is asked where that Resource is read by everyone, and a `null` reference is not
|
|
404
|
+
asked about. Only a reference to a Resource the Core Contract shows is held; one the root's exemptions hide is not, nor
|
|
405
|
+
one the engine lists in its optional `unheld`: a Resource its rules read in conditions and decide no read of (a
|
|
406
|
+
Resource another service owns, say), so its read scope is `false` for every caller. A reference to one of those, and a
|
|
407
|
+
`$connect` or `$set` that links one by the reference's own values where the writing row holds the reference, is
|
|
408
|
+
decided by the write's own grant
|
|
409
|
+
(`can`, and the after check `afterChecks` lists); reads of it are decided by `scopeOf` as always. An engine that has the
|
|
410
|
+
optional `hosted` is told the held Resources once the Framework is built (`hosted({ held })`), so it can check its own
|
|
411
|
+
rules against them; it answers nothing differently. A to-many `$update` or
|
|
412
|
+
`$delete` acts only on the rows the caller may change or delete, as `update.many` does. After the call, every row they changed is checked as an update is
|
|
413
|
+
(the after check, the move-out re-read), and every row they created is read back as stored and asked `can`; one refusal
|
|
414
|
+
rolls the whole write back. Where `transactionsStyle` is `"none"`, a nested write under a policy is refused.
|
|
415
|
+
|
|
416
|
+
## An access provider
|
|
417
|
+
|
|
418
|
+
`access` takes the policy engine and the request state from one object, an `AvAccessProvider`. Aventara Access
|
|
419
|
+
provides one, and you can write your own. It has two members:
|
|
420
|
+
|
|
421
|
+
- `policy`: the engine, as `policy.engine` takes it.
|
|
422
|
+
- `state({ headers, request })`: seeds each request's `ctx.$state`. Its `me` is the principal the engine is asked
|
|
423
|
+
about (`null` for an anonymous caller).
|
|
424
|
+
|
|
425
|
+
The host calls `state` once per request: `@aventara/nest` with the platform request's headers and the request itself,
|
|
426
|
+
`@aventara/next` with the request's headers (and the route's `Request` on a route call). With `access`, leave out
|
|
427
|
+
`policy` and the host's own `state` option; either one beside it is refused at startup, and as a type error where the
|
|
428
|
+
configuration's type shows both. Server code that calls the Framework directly passes the state itself,
|
|
429
|
+
`{ state: await access.state({ headers }) }`.
|
|
430
|
+
|
|
431
|
+
A point-of-sale till with its own PIN sign-in, written by hand:
|
|
432
|
+
|
|
433
|
+
```ts
|
|
434
|
+
import type { AvAccessProvider } from "@aventara/core";
|
|
435
|
+
|
|
436
|
+
type Cashier = { id: string; role: "cashier" | "manager" };
|
|
437
|
+
|
|
438
|
+
declare module "@aventara/core" {
|
|
439
|
+
interface AvState {
|
|
440
|
+
me?: Cashier | null;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export const tillAccess = {
|
|
445
|
+
// The till sends the PIN the cashier typed as `x-pos-pin`.
|
|
446
|
+
state: async ({ headers }) => ({ me: await cashierByPin(headers.get("x-pos-pin")) }),
|
|
447
|
+
policy: {
|
|
448
|
+
can: ({ principal, resource, action }) => {
|
|
449
|
+
const me = principal as Cashier | null;
|
|
450
|
+
if (me === null) return false; // no PIN, no till
|
|
451
|
+
if (resource === "Refund" && action !== "read") return me.role === "manager";
|
|
452
|
+
return true;
|
|
453
|
+
},
|
|
454
|
+
scopeOf: () => true,
|
|
455
|
+
fieldsOf: () => ({ always: "all", perRow: {} }),
|
|
456
|
+
},
|
|
457
|
+
} satisfies AvAccessProvider;
|
|
458
|
+
|
|
459
|
+
const framework = await createFramework({ api: "/api", adapter, access: tillAccess });
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
Every stage reads the cashier as `ctx.$state.me`, typed by the `AvState` declaration above.
|
|
463
|
+
|
|
464
|
+
### `$access` and the provider's `client`
|
|
465
|
+
|
|
466
|
+
A provider may also state `client`: where the app mounted Aventara Access (`url`, absolute `http(s)` or root-relative)
|
|
467
|
+
and the verbs it offers (`signIn`, `signUp`, `signOut`, `resetPassword`, `me`, and on Access's `kiosk` profile
|
|
468
|
+
`switchUser`, `lock`, `setPin`, `enrollDevice`, `revokeDevice`; each optional, any other name refused), each with its route
|
|
469
|
+
(`method`, `path`), the JSON Schema of its arguments and result (the subset a `json` field's shape uses), and `call`,
|
|
470
|
+
the verb run in process. Aventara Access fills it in. It is compiled into the Client Contract's `access` section with
|
|
471
|
+
each `call` dropped, so the hash changes only for apps whose provider states it. Every client then carries `$access`:
|
|
472
|
+
|
|
473
|
+
- `can(questions, options?)`: the batched `POST {api}/_access/can`, one boolean per question. An empty list answers
|
|
474
|
+
`[]` (`200 A1000`), not a refusal: a page with nothing to ask is no client bug, unlike an empty `/_transactions` plan.
|
|
475
|
+
- `me()`: the token identity `{ sub, claims }`, or `null`; and the sign-in verbs the provider offers. A verb the
|
|
476
|
+
provider leaves out (a `service` profile has no `signIn`/`signUp`; only a `kiosk` profile has `switchUser`, `lock`,
|
|
477
|
+
`setPin`, `enrollDevice`, `revokeDevice`) is no member of `$access`. `switchUser` and `enrollDevice` resolve
|
|
478
|
+
Access's answer, `lock`, `setPin` and `revokeDevice` nothing, as `signIn` and `signOut` do.
|
|
479
|
+
|
|
480
|
+
`framework.serverClient.$access` answers `can` in process under the call's `state` (or, with `headers` and no
|
|
481
|
+
`state`, the state the provider seeds from them; with neither, anonymously), and runs each verb through its `call` with
|
|
482
|
+
the headers you pass. The section's `url` and each verb's `path` are checked wherever a ClientContract is read: an
|
|
483
|
+
absolute `http(s)` URL or a root-relative path other than `/`, a path starting with one `/`, neither holding a control
|
|
484
|
+
character, a space or a `.`/`..` segment (percent-encoded too). Under Nest, hand the platform request and response too, so Access can read the
|
|
485
|
+
session and set a sign-in's cookie:
|
|
486
|
+
|
|
487
|
+
```ts
|
|
488
|
+
const me = await framework.serverClient.$access.me({ headers: req.headers });
|
|
489
|
+
await framework.serverClient.$access.signIn(body, { headers: req.headers, request: req, response: res });
|
|
490
|
+
const [canRefund] = await framework.serverClient.$access.can([{ resource: "Refund", action: "create" }], { state });
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
Without `access`, `serverClient` has no `$access` at all; a provider without `client` offers `can` alone.
|
|
494
|
+
|
|
495
|
+
On Access's `kiosk` profile, `switchUser`, `lock` and `enrollDevice` rotate the device's credential cookie: under
|
|
496
|
+
Nest, call them with the platform `response` (`{ headers, request, response }`), or the rotated cookie never reaches
|
|
497
|
+
the device and its next switch is refused. Under Next, call them from a Server Action. Never call a verb that changes
|
|
498
|
+
anything inside a cache (Next's `"use cache"`, a memoized helper): a cached answer replays one caller's result to
|
|
499
|
+
another and skips the call. Every verb's answer is projected onto its result schema, so no session token or device
|
|
500
|
+
secret reaches server code either.
|
|
501
|
+
|
|
502
|
+
## A model managed elsewhere
|
|
503
|
+
|
|
504
|
+
Some tables belong to another service: Better Auth's `user`, or another service's schema in the same database. Your
|
|
505
|
+
service joins into them and must not write or migrate them. Mark such a model at the root:
|
|
506
|
+
|
|
507
|
+
```ts
|
|
508
|
+
const framework = await createFramework({
|
|
509
|
+
api: "/api",
|
|
510
|
+
adapter,
|
|
511
|
+
resources: { Account: { managedElsewhere: true } },
|
|
512
|
+
});
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
- `Account` has no `create`, `update`, `delete` or `upsert` in any layer, and its fields take no write. A call is a
|
|
516
|
+
type error, and an untyped one is refused with `A2002`.
|
|
517
|
+
- A relation into it keeps only `$connect` and `$disconnect`, and only where your model holds the key (`Post.authorId`
|
|
518
|
+
for `Post.author`). A nested `$create`, `$update` or `$upsert` into it is a type error and `A2004` (`V1006`).
|
|
519
|
+
- `framework.application` reads `Account` and filters and includes through `Post.author`. The client and agent layers
|
|
520
|
+
leave out `Account` and every relation into it, so `/_contract`, the generated client and the MCP tools never show
|
|
521
|
+
it. An overlay can't bring it back.
|
|
522
|
+
- A stage on one of its write families is refused at startup (`COMPILER_UNAVAILABLE_OPERATION`), and
|
|
523
|
+
`managedElsewhere` in an overlay is `CONFIG_FORBIDDEN_IN_OVERLAY`. So is any `client.resources.Account` or
|
|
524
|
+
`agent.resources.Account` entry (`COMPILER_UNKNOWN_RESOURCE`): those layers never have it, so nothing there runs.
|
|
525
|
+
- A delete that would write it through a referential action is refused at startup (`CONFIG_INVALID_VALUE` at
|
|
526
|
+
`resources.Account.managedElsewhere`): if any layer can delete a row whose deletion cascades into `Account` or sets
|
|
527
|
+
its reference to null or its default, directly or through other cascades. Take those deletes away at the root, or
|
|
528
|
+
make the reference `onDelete: Restrict`.
|
|
529
|
+
- A create that only the hidden relation could satisfy is refused at startup (`COMPILER_HIDDEN_REQUIRED_FIELD`), naming
|
|
530
|
+
the reference field to expose instead, so no client advertises a create that can't succeed.
|
|
531
|
+
- `framework.contracts.core` marks it `managedElsewhere: true`, and states every Resource's database `schema` where
|
|
532
|
+
the adapter knows it (`@@schema("auth")` with `@aventara/prisma7-adapter`). No other Contract carries either.
|
|
533
|
+
|
|
534
|
+
Two things stay yours. A key update follows the owner's foreign key (`ON UPDATE CASCADE` rewrites `Account`'s rows;
|
|
535
|
+
core has no `onUpdate` facts). And your own code — `ctx.$sql`, an application transaction's SQL step, a stage that
|
|
536
|
+
calls Prisma directly — can still write `Account`.
|
|
537
|
+
|
|
538
|
+
Aventara doesn't migrate anything, so tell Prisma too: list the table as external in `prisma.config.ts`.
|
|
539
|
+
|
|
540
|
+
```ts
|
|
541
|
+
export default defineConfig({
|
|
542
|
+
schema: "prisma/schema.prisma",
|
|
543
|
+
experimental: { externalTables: true },
|
|
544
|
+
tables: { external: ["auth.Account"] },
|
|
545
|
+
});
|
|
546
|
+
```
|
|
123
547
|
|
|
124
548
|
## License
|
|
125
549
|
|
|
126
|
-
PolyForm Shield 1.0.0 with an additional permission
|
|
127
|
-
use it to build a product that competes with Aventara itself. See `LICENSE` and
|
|
550
|
+
PolyForm Shield 1.0.0 with an additional permission. It is free to use, including in commercial applications, but you
|
|
551
|
+
may not use it to build a product that competes with Aventara itself. See `LICENSE` and
|
|
552
|
+
`LICENSE-ADDITIONAL-PERMISSION.md`.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
/** Codes of {@link
|
|
2
|
-
export type
|
|
1
|
+
/** Codes of {@link AvAdapterDiscoveryDiagnostic}. */
|
|
2
|
+
export type AvAdapterDiscoveryDiagnosticCode = "DISCOVERY_TRANSACTION_CAPABILITY_MISMATCH";
|
|
3
3
|
/** A problem found in what the adapter discovered, reported at framework construction. */
|
|
4
|
-
export type
|
|
5
|
-
readonly code:
|
|
4
|
+
export type AvAdapterDiscoveryDiagnostic = {
|
|
5
|
+
readonly code: AvAdapterDiscoveryDiagnosticCode;
|
|
6
6
|
readonly path: "adapter.transactions";
|
|
7
7
|
readonly message: string;
|
|
8
8
|
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ErrorCodeDescription } from "../runtime/error-guidance.js";
|
|
2
|
+
import type { AvAdapterDiscoveryDiagnosticCode } from "./adapter-discovery-diagnostic.js";
|
|
3
|
+
/** Meaning, cause and fix of every discovery diagnostic code. */
|
|
4
|
+
export declare const ADAPTER_DISCOVERY_DIAGNOSTIC_GUIDANCE: {
|
|
5
|
+
readonly [C in AvAdapterDiscoveryDiagnosticCode]: ErrorCodeDescription;
|
|
6
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const ADAPTER_DISCOVERY_DIAGNOSTIC_GUIDANCE = {
|
|
2
|
+
DISCOVERY_TRANSACTION_CAPABILITY_MISMATCH: {
|
|
3
|
+
message: "The adapter's transaction capability does not match it.",
|
|
4
|
+
cause: "The adapter declares interactive transactions but provides no transaction function.",
|
|
5
|
+
fix: "A defect of the adapter: install the adapter release that matches @aventara/core exactly.",
|
|
6
|
+
},
|
|
7
|
+
};
|