@aventara/core 0.1.0-pilot.4 → 1.0.0-rc.0
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 +338 -0
- package/README.md +100 -3
- package/dist/adapters/adapter-model.d.ts +8 -0
- package/dist/adapters/adapter.interface.d.ts +6 -2
- package/dist/compiler/contract-compiler-diagnostic.d.ts +6 -0
- package/dist/compiler/contract.compiler.js +67 -0
- package/dist/config/config.resolver.d.ts +3 -0
- package/dist/config/config.resolver.js +1 -0
- package/dist/config/config.validator.js +6 -0
- package/dist/config/define-fields.d.ts +10 -3
- package/dist/config/framework-config.d.ts +4 -1
- package/dist/config/framework-fields-config.d.ts +6 -0
- package/dist/contracts/client-contract-structure.validator.d.ts +21 -1
- package/dist/contracts/client-contract-structure.validator.js +99 -2
- package/dist/contracts/contract-canonicalizer.js +32 -0
- package/dist/contracts/contract.d.ts +7 -0
- package/dist/contracts/field.d.ts +10 -0
- package/dist/contracts/json-shape.d.ts +65 -0
- package/dist/contracts/json-shape.js +1 -0
- package/dist/contracts/reference-write-limit.d.ts +7 -0
- package/dist/contracts/reference-write-limit.js +1 -0
- package/dist/contracts/scalar-value-type.d.ts +12 -1
- package/dist/framework/contract-view.d.ts +126 -21
- package/dist/framework/framework.d.ts +5 -3
- package/dist/framework/framework.factory.js +56 -13
- package/dist/index.d.ts +8 -40
- package/dist/index.js +0 -20
- package/dist/internal.d.ts +35 -0
- package/dist/internal.js +16 -0
- package/dist/json/avz.d.ts +96 -0
- package/dist/json/avz.js +212 -0
- package/dist/json/index.d.ts +6 -0
- package/dist/json/index.js +1 -0
- package/dist/json/json-shape.registry.d.ts +22 -0
- package/dist/json/json-shape.registry.js +16 -0
- package/dist/json/zod-issue.translator.d.ts +51 -0
- package/dist/json/zod-issue.translator.js +142 -0
- package/dist/operations/operation-arguments.d.ts +151 -101
- package/dist/operations/operation-identity.d.ts +2 -0
- package/dist/operations/operation-identity.js +3 -0
- package/dist/operations/operation-validator-core.d.ts +1 -1
- package/dist/operations/operation-validator-core.js +28 -10
- package/dist/operations/operation-validator.d.ts +20 -5
- package/dist/operations/operation-validator.js +26 -10
- package/dist/operations/projection-scope.resolver.d.ts +1 -1
- package/dist/operations/structural-limits.walker.d.ts +19 -0
- package/dist/operations/structural-limits.walker.js +344 -0
- package/dist/protocol/absent-route.responder.d.ts +2 -1
- package/dist/protocol/absent-route.responder.js +32 -6
- package/dist/protocol/http-request.decoder.d.ts +6 -0
- package/dist/protocol/http-request.decoder.js +11 -3
- package/dist/protocol/protocol.binder.d.ts +4 -2
- package/dist/protocol/protocol.binder.js +3 -1
- package/dist/protocol/protocol.handler.d.ts +6 -2
- package/dist/protocol/protocol.handler.js +14 -4
- package/dist/protocol/unmounted-route.error.d.ts +19 -0
- package/dist/protocol/unmounted-route.error.js +10 -0
- package/dist/runtime/caller-data.copier.d.ts +59 -0
- package/dist/runtime/caller-data.copier.js +470 -0
- package/dist/runtime/caller-data.door.d.ts +56 -0
- package/dist/runtime/caller-data.door.js +65 -0
- package/dist/runtime/computed-read-runtime.js +3 -10
- package/dist/runtime/computed-value.error.d.ts +13 -0
- package/dist/runtime/computed-value.error.js +9 -0
- package/dist/runtime/create-requirement.d.ts +43 -7
- package/dist/runtime/create-requirement.js +89 -9
- package/dist/runtime/diagnostics-reporter.d.ts +20 -4
- package/dist/runtime/diagnostics-reporter.js +11 -4
- package/dist/runtime/error-boundary.d.ts +8 -0
- package/dist/runtime/error-boundary.js +65 -35
- package/dist/runtime/field-behavior.d.ts +2 -2
- package/dist/runtime/framework-error.d.ts +12 -0
- package/dist/runtime/misuse-reporter.d.ts +32 -0
- package/dist/runtime/misuse-reporter.js +19 -0
- package/dist/runtime/mutation-planner.js +13 -11
- package/dist/runtime/operation-request.d.ts +4 -11
- package/dist/runtime/operation-runtime.d.ts +14 -7
- package/dist/runtime/operation-runtime.js +37 -18
- package/dist/runtime/outcome-mapper.js +12 -2
- package/dist/runtime/output-validator.d.ts +10 -0
- package/dist/runtime/output-validator.js +11 -0
- package/dist/runtime/owned-execution.registry.d.ts +13 -0
- package/dist/runtime/owned-execution.registry.js +7 -0
- package/dist/runtime/pipeline.d.ts +19 -7
- package/dist/runtime/referential-action.d.ts +7 -0
- package/dist/runtime/request-envelope.validator.d.ts +5 -0
- package/dist/runtime/request-envelope.validator.js +118 -0
- package/dist/runtime/scalar-runtime.d.ts +30 -2
- package/dist/runtime/scalar-runtime.js +162 -79
- package/dist/transactions/transaction-plan-assembler.d.ts +5 -1
- package/dist/transactions/transaction-plan-assembler.js +74 -75
- package/dist/transactions/transaction-plan-validator.d.ts +30 -7
- package/dist/transactions/transaction-plan-validator.js +204 -153
- package/dist/transactions/transaction-reference-collector.d.ts +30 -0
- package/dist/transactions/transaction-reference-collector.js +204 -0
- package/dist/transactions/transaction-reference-resolver.js +10 -5
- package/dist/transactions/transaction-reference-validator.js +24 -17
- package/dist/transactions/transaction-reference.d.ts +23 -51
- package/dist/transactions/transaction-request.d.ts +3 -3
- package/dist/transactions/transaction-response.d.ts +6 -1
- package/dist/transactions/transaction-response.js +24 -1
- package/dist/transactions/transaction-runtime.d.ts +4 -2
- package/dist/transactions/transaction-runtime.js +4 -3
- package/package.json +17 -3
- package/dist/operations/operation-boolean-nodes.d.ts +0 -11
- package/dist/operations/operation-boolean-nodes.js +0 -283
- package/dist/operations/operation-nesting.d.ts +0 -8
- package/dist/operations/operation-nesting.js +0 -307
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
# @aventara/core
|
|
2
|
+
|
|
3
|
+
## 1.0.0-rc.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- First release candidate of Aventara 1.0. The public API is now considered stable; changes before 1.0.0 are possible
|
|
8
|
+
based on feedback.
|
|
9
|
+
|
|
10
|
+
### Minor Changes
|
|
11
|
+
|
|
12
|
+
- 24ffbab: A `Json` field can now have a type. Build its shape with `AvZ` from the new entry `@aventara/core/json` and declare it
|
|
13
|
+
on the field: `fields: { Post: { meta: { json: AvZ.object({ title: AvZ.string() }) } } }`. `framework.application`,
|
|
14
|
+
`framework.client`, `appTx`/`clientTx` and the generated client then read and write the field as that type instead of
|
|
15
|
+
generic JSON, and every write to it — create, update, upsert, nested writes and transaction steps, a value taken from
|
|
16
|
+
an earlier step included — is checked before the database is asked: a value out of shape is refused with `A2004` and
|
|
17
|
+
one issue per problem at its exact path (`V1000` missing key, `V1001` wrong type, `V1002` outside an enum or literal,
|
|
18
|
+
`V1004` integer out of range, `V1005` unknown key). Shapes are types only and their objects are closed; reads and
|
|
19
|
+
filters are never checked. Only a field the adapter discovered as `Json` takes a shape, and a Zod schema is not
|
|
20
|
+
accepted. The shape is part of the ClientContract (as JSON Schema), so declaring or changing one changes its hash:
|
|
21
|
+
regenerate the client. `@aventara/core` now depends on `zod` (`^4.6.5`), which only `@aventara/core/json` loads. The
|
|
22
|
+
shape types `AvZShape`, `AvZObject`, `AvZOptional` and `AvZNullable` are exported too, so a project that emits
|
|
23
|
+
declarations can export a shape or a configuration holding one.
|
|
24
|
+
- 24ffbab: Transaction steps can now reference earlier results in `$connect`, `$disconnect`, `$set`, `$connectOrCreate`'s `where`,
|
|
25
|
+
a `cursor`, a unique `where`, and anywhere in a filter — `AND`/`OR`/`NOT` and relation filters included, with every
|
|
26
|
+
scalar operator. `framework.appTx` and `framework.clientTx` type each position by what it holds: a filter's value,
|
|
27
|
+
`equals` and `not` take a source of the field's own type (nullable only where the field is, and a `null` then matches
|
|
28
|
+
the rows where the field is unset, as a literal `null` does); a unique key, a `cursor`, a comparison or a text operator a
|
|
29
|
+
non-null source; `in`, `notIn`, `hasEvery` and `hasSome` a list field; `has` one value. The server checks the same rule
|
|
30
|
+
before anything runs and refuses a mismatch with `A2007`/`V1011`. Nested relation writes take references at any depth
|
|
31
|
+
too: the values and keys of nested `$create`, `$update` and `$upsert` data, and the `where` of a nested `$update`,
|
|
32
|
+
`$delete` or `$upsert`. A reference in an ordering or a projection's nested `where` is still refused.
|
|
33
|
+
- 24ffbab: **Behaviour changes:** server-side calls now copy what you pass them. `framework.application`, `framework.client`,
|
|
34
|
+
`framework.execute`, `framework.executeTransaction` and the `appTx`/`clientTx` builders read your arguments once, when
|
|
35
|
+
you call them, into plain data the framework owns; validation, guards, pipes, computed values and the adapter see only
|
|
36
|
+
that copy, so changing or revoking your object afterwards (an Immer draft after `produce`) no longer affects the
|
|
37
|
+
operation. A live Proxy (Vue `reactive`, MobX) still works.
|
|
38
|
+
|
|
39
|
+
A value that cannot be read — a revoked Proxy, a getter that throws — or that is not plain data — a class instance or a
|
|
40
|
+
`Map`; `Date`, `Decimal` and `Uint8Array` are fine — is now refused as your request's error: `A2004` with one `V1001` at
|
|
41
|
+
the value's path (`["arguments", …]`, or `["operations", i, "args", …]` with `cause.operation` in a transaction), before
|
|
42
|
+
any guard, pipe or adapter runs. Before, it was `A3000` (an internal error) or, from `appTx`/`clientTx`, a `TypeError`
|
|
43
|
+
thrown at the builder call; a builder now never throws, and `framework.transaction([...])` answers the refusal. The same
|
|
44
|
+
holds for a `framework.execute` / `executeTransaction` request object that cannot be read. Two answers change for
|
|
45
|
+
inputs that were never supported: a class instance where an object of arguments belongs (`where`, `data`, …) was
|
|
46
|
+
accepted or failed as `A3000`, and is now refused; inherited, non-enumerable and symbol keys are dropped, as JSON drops
|
|
47
|
+
them.
|
|
48
|
+
|
|
49
|
+
New optional root setting `misuse` (types `FrameworkMisuseSink`, `FrameworkMisuseEvent`), beside `diagnostics`: it
|
|
50
|
+
receives one event per refused value — its `kind` (`"unreadable-value"` or `"non-plain-value"`), the `path` the caller
|
|
51
|
+
received, and the operation's `origin`, `resource`, `family`, `variant` and transaction step (`operation`) when they
|
|
52
|
+
could be read — never the value itself. It cannot change a response; an error it throws or a promise it rejects is
|
|
53
|
+
ignored. `diagnostics` keeps receiving only unexpected failures.
|
|
54
|
+
|
|
55
|
+
HTTP requests are not copied again: a body the protocol parses from raw content is already plain data.
|
|
56
|
+
|
|
57
|
+
The generated client reads your arguments through one guard too: a value that cannot be read is refused with a
|
|
58
|
+
`TypeError` naming where (`… the value at /where/id could not be read.`) before anything is sent, and a `tx` builder
|
|
59
|
+
never throws — `transaction([...])` rejects with that error instead. Regenerate the client to pick this up.
|
|
60
|
+
|
|
61
|
+
- 24ffbab: A nested `$update` or `$delete` through a to-many relation filters plain fields only on Prisma 7, and Aventara now says
|
|
62
|
+
so up front: a relation filter in its `where` — `{ posts: { $update: { where: { category: { name: "x" } }, … } } }`, at
|
|
63
|
+
any depth, under `AND`/`OR`/`NOT` too — is refused with `A2004` / `V1006` before the database is asked, and is a type
|
|
64
|
+
error in `framework.application`, `framework.client`, `appTx`/`clientTx` and the generated client. Before, it reached
|
|
65
|
+
Prisma and fail as `A3001`. A to-one relation's nested filter keeps relation filters. The limit is stated by the adapter
|
|
66
|
+
in the Contract (`nestedWriteFilter: "scalar"`), so the ClientContract hash changes: regenerate the client.
|
|
67
|
+
|
|
68
|
+
### Patch Changes
|
|
69
|
+
|
|
70
|
+
- 24ffbab: **Behaviour changes:** `@aventara/core` exports fewer names. Every name it no longer exports was undocumented; every
|
|
71
|
+
documented name (`createFramework`, `FrameworkConfig`, `defineFields`, `Decimal`, `FrameworkError`, the pipeline types and
|
|
72
|
+
the rest) is still exported. Types shown in a hover or an error message still resolve; what changes is only which names
|
|
73
|
+
your own code can import. `OperationScope` was a deprecated alias: use `RequestOrigin`.
|
|
74
|
+
|
|
75
|
+
What Aventara's own packages need from core moved to `@aventara/core/internal`. It is not public API: it carries no
|
|
76
|
+
stability promise, and any of its exports may change or disappear in any release. Applications import from
|
|
77
|
+
`@aventara/core`.
|
|
78
|
+
|
|
79
|
+
Moved to `@aventara/core/internal`: `AdapterField`, `AdapterFieldKey`, `AdapterFieldMap`, `AdapterIdentifier`, `AdapterLifecycleTrait`, `AdapterOperation`, `AdapterOperationExecutor`, `AdapterRelationField`, `AdapterResource`, `AdapterScalarField`, `AdapterScalarFieldKey`, `advertisedDirectives`, `advertisedReducers`, `ApplicationContractView`, `ApplicationOperationRequest`, `ApplicationTransactionRequest`, `AVENTARA_PROTOCOL_VERSION`, `BUILT_IN_SCALARS`, `BuiltinNonRow`, `canonicalizeContract`, `canonicalizeJson`, `CanonicalJsonError`, `ClientContractHashInput`, `ClientOperationRequest`, `ClientProtocolIdentity`, `ClientTransactionRequest`, `compileContracts`, `computeClientContractHash`, `computeOperationFingerprint`, `DEFAULT_CONTRACT_LIMITS`, `FrameworkOperationRequest`, `FrameworkTransactionRequest`, `isOperationVariantAvailable`, `LIFECYCLE_TRAITS`, `OPERATION_CODES`, `OperationFingerprint`, `PROJECTION_SCOPES`, `projectionScopeOf`, `resolveFrameworkConfig`, `SemanticOperationArguments`, `TRANSACTION_CAPABILITIES`, `validateClientContractStructure`, `VALIDATION_CODES`, `verifyClientContractHash`, `WRITE_SCOPES`, `writeScopeOf`.
|
|
80
|
+
|
|
81
|
+
No longer exported from any entry: `advertisedDirectivesAt`, `advertisedReducersAt`, `ClientContractStructureVerdict`, `ContractCompilationResult`, `DeferredOperation`, `EffectiveFieldKeyAt`, `holdsAt`, `holdsInAnyScope`, `isOperationFingerprint`, `isTransactionReferenceCandidate`, `isTransactionReferenceObject`, `operationFailure`, `OperationFingerprintNode`, `OperationInternalErrorCode`, `operationNoMatch`, `OperationScope`, `operationSuccess`, `OperationValidationResult`, `RelationMutationAction`, `ResolvedFrameworkConfig`, `ResolvedFrameworkScopeConfig`, `ResolvedPipelineConfig`, `ScalarFilterInput`, `ScalarMutationAction`, `transactionCommitted`, `TransactionCreateDataInput`, `transactionInfrastructureFailure`, `TransactionOperationArgumentsFor`, `transactionOperationFailure`, `TransactionPlan`, `TransactionReferenceCandidate`, `TransactionScalarValue`, `TransactionUpdateDataInput`, `validateFrameworkConfig`, `validateOperationArguments`, `VALIDATION_CODE_MESSAGES`.
|
|
82
|
+
|
|
83
|
+
- 24ffbab: Each package ships its `CHANGELOG.md`, so the release notes are in the installed package beside the README.
|
|
84
|
+
- 24ffbab: A `null` given to a filter operator that has no `null` meaning — `gt`, `contains`, `in`, `has`, `mode`, `isEmpty`,
|
|
85
|
+
`path`, a JSON `string*` operator and the rest — is now refused as `A2004` / `V1001` at that operator, before the
|
|
86
|
+
database is asked, and is a type error. Before, it reached the database and failed as `A3001`. `equals: null` and
|
|
87
|
+
`not: null` still match the rows where a nullable field is unset or set.
|
|
88
|
+
- 24ffbab: **Diagnostics:** if your own HTTP host mounts only some of `protocol.surface()` and a request for a route it left out
|
|
89
|
+
reaches `protocol.answerAbsent`, your `diagnostics` sink now receives one `A3000` diagnostic naming the route: an
|
|
90
|
+
`"operation"` diagnostic with the Resource, family and variant (a `"transaction"` one for `/_transactions`), whose
|
|
91
|
+
`error` is named `"UnmountedRouteError"` and says which `surface()` entry to mount. The caller's `500 A3000` answer is
|
|
92
|
+
unchanged, and a route your ClientContract does not offer is still answered without a report. `@aventara/nest` mounts
|
|
93
|
+
every route, so its applications are not affected.
|
|
94
|
+
- 24ffbab: **Diagnostics:** a `FrameworkError` you throw on purpose with an `A3xxx` code — from a guard, pipe, hook, interceptor,
|
|
95
|
+
computed field or adapter, or returned by a filter — now reaches your `diagnostics` sink once, as the error you threw,
|
|
96
|
+
under its code, with `deliberate: true` on the diagnostic. Before, the caller received the generic `A3000`/`A3001` and
|
|
97
|
+
your message and issues were reported nowhere. A failure Aventara did not expect carries no `deliberate` field, so a sink
|
|
98
|
+
can tell the two apart. What the caller receives is unchanged, and `A2xxx`/`A4xxx` outcomes are still not reported.
|
|
99
|
+
- 24ffbab: **Diagnostics:** a result that does not match its Contract — an adapter row with a wrong value, a missing field, a
|
|
100
|
+
result of the wrong shape — now reaches your `diagnostics` sink as one `A3000` diagnostic whose `error` (`name`
|
|
101
|
+
`"OutputValidationError"`) names the operation and lists the problems (`error.issues`). Before, the caller got `A3000`
|
|
102
|
+
and the sink got nothing unless reading the result threw. The same now holds for a computed field: a read or write
|
|
103
|
+
behavior that returns a value its field refuses is reported as an error named `"ComputedValueError"` with the issues
|
|
104
|
+
(before, the sink received only the field's location). What the caller receives is unchanged. A filter that inspects
|
|
105
|
+
the error now sees that error instead of a `FrameworkError` with code `A3000`.
|
|
106
|
+
|
|
107
|
+
The `FrameworkError` documentation now states the message rule: for any code outside `A3xxx`, the message is sent to
|
|
108
|
+
the caller exactly as written, so it must not carry database text, SQL, a stack, a path or a secret.
|
|
109
|
+
|
|
110
|
+
- 24ffbab: **Behaviour changes:** Pipelines and guards read where a request came from as `request.origin` (was `request.scope`).
|
|
111
|
+
The same property is renamed everywhere it means `"application"` or `"client"`: the pipeline context
|
|
112
|
+
(`context.origin`), the request objects `framework.execute` and `framework.executeTransaction` take
|
|
113
|
+
(`{ origin: "client", … }`), the context a computed field's behaviour receives, and every diagnostic the diagnostics
|
|
114
|
+
sink receives. There is no `scope` alias: replace `scope` with `origin` in those places. Nothing sent over HTTP
|
|
115
|
+
changes.
|
|
116
|
+
|
|
117
|
+
The Nest host's log line for an internal failure now reads `(client origin, request …)` instead of
|
|
118
|
+
`(client scope, request …)`.
|
|
119
|
+
|
|
120
|
+
- 24ffbab: **Types:** a guard, pipe, hook, interceptor or filter typed with `Guard`, `Pipe`, `BeforeHook`, `AfterHook`,
|
|
121
|
+
`Interceptor` or `Filter` without a model type now type-checks wherever one typed for your model is expected, so you
|
|
122
|
+
can put it in an `as const` configuration (the shape `aventara init` writes), in `client.pipelines` or
|
|
123
|
+
`application.pipelines`, and mix it with model-typed callbacks in one list. Before, TypeScript refused it there, and
|
|
124
|
+
the way around it was a structural parameter type such as `Pick<PipelineContext, "family" | "transport">`, which you no
|
|
125
|
+
longer need (it keeps working). A callback typed for a different model is still refused.
|
|
126
|
+
- a973e89: An issue your own code throws now reaches the caller with the message you wrote. When a pipe, guard, hook, interceptor,
|
|
127
|
+
filter or computed behavior throws `new FrameworkError("A2004", { message, issues: [{ path, code, message }] })`, each
|
|
128
|
+
issue's `message` is sent as written instead of the generic text for its code — standalone, inside a transaction plan,
|
|
129
|
+
and over HTTP. The message must have a visible character and be at most 1,000 characters; otherwise the caller receives
|
|
130
|
+
the generic text for the code and your message goes to the `diagnostics` sink. The rest is unchanged: an issue whose
|
|
131
|
+
`code` is not a validation code (`V1000` and up) is dropped, a `path` that does not name a member of the request's own
|
|
132
|
+
arguments is dropped, an `A3xxx` outcome still carries no issues, and an issue an Adapter throws still carries the
|
|
133
|
+
generic text for its code.
|
|
134
|
+
- 1e65e9a: `framework.execute(request)` and `framework.executeTransaction(request)` now check the request object itself before
|
|
135
|
+
running it. A wrong one — not an object, a missing or empty `requestId`, an `origin` other than `"application"` or
|
|
136
|
+
`"client"`, a client request without a `protocol` of `{ version, hash }`, malformed `transport`, `protocol` or
|
|
137
|
+
`transport` on an application request, or a missing or non-string `resource`, `family` or `variant` — is answered
|
|
138
|
+
`A2004` with one issue per wrong member at its own path (`["requestId"]`, `["protocol", "hash"]`, …), and nothing
|
|
139
|
+
runs: no guard, pipe or hook, no adapter call. Before, most of these answered `A3000` (an internal error) or ran as
|
|
140
|
+
though the request were well formed. A well-formed request is unchanged, including `A2005`/`A2006` for a stale
|
|
141
|
+
`protocol` and `A2001` for an unknown Resource.
|
|
142
|
+
- a973e89: A write that sets an optional relation both ways — its reference field and the relation itself, such as `categoryId`
|
|
143
|
+
and `category: { $connect: … }` in the same record — is now refused before it reaches the database, exactly like a
|
|
144
|
+
required relation: `A2004` with a `V1012` issue at the relation, message `Set "categoryId" or "category", not both.`
|
|
145
|
+
Before, the database refused it and the caller received `A3001`. It applies to `create`, `update` and `upsert`, to
|
|
146
|
+
nested writes at any depth, and to each step of a transaction.
|
|
147
|
+
|
|
148
|
+
The discovery artifact now records, for each relation, the fields that hold its reference (`references` on each edge
|
|
149
|
+
of `referentialActions`), and a create requirement no longer repeats them. Regenerate your artifact with
|
|
150
|
+
`npm run aventara:prepare` (or `aventara-prisma7-generate`); an artifact from an earlier version is refused with a
|
|
151
|
+
message that says so. An adapter's `referentialActions` edges carry the same `references` member.
|
|
152
|
+
|
|
153
|
+
- a973e89: With the Prisma adapter, a record that writes one relation through its reference field and another relation directly —
|
|
154
|
+
such as `authorId` beside `category: { $connect: … }` — is now refused before it reaches the database: `A2004` with a
|
|
155
|
+
`V1012` issue at the relation write, message
|
|
156
|
+
`Use relation fields only, or reference fields only, in one record: "authorId" is a reference field and "category" is a relation.`
|
|
157
|
+
Before, Prisma refused it and the caller received `A3001`. Write the record's relations all as relations
|
|
158
|
+
(`author: { $connect: … }, category: { $connect: … }`) or all through their fields (`authorId`, `categoryId`). A
|
|
159
|
+
reference field beside a relation that holds no reference on that side — `mentorId` beside `posts: { $create: … }` on
|
|
160
|
+
an author — is unchanged and still accepted. It applies to `create`, `update` and `upsert`, to nested writes at any
|
|
161
|
+
depth, and to each step of a transaction.
|
|
162
|
+
|
|
163
|
+
The limit is part of the Contract: a Contract whose data source has it carries `referenceWrites: "uniform"`, so the
|
|
164
|
+
Contract hash of every Prisma-backed application changes. Regenerate your discovery artifact
|
|
165
|
+
(`npm run aventara:prepare`) and your generated client (`avclient generate`). An adapter states the limit with
|
|
166
|
+
`referenceWrites: "uniform"` in its model; without it, nothing changes.
|
|
167
|
+
|
|
168
|
+
- 1e65e9a: A write that sets a required relation both ways — its reference field and the relation itself, such as `authorId` and
|
|
169
|
+
`author: { $connect: … }` in the same `data` — is now refused before it reaches the database: `A2004` with a `V1012`
|
|
170
|
+
issue at the relation (`["arguments", "data", "author"]`), message `Set "authorId" or "author", not both.` It applies to
|
|
171
|
+
`create`, `update` and `upsert`, to nested writes at any depth, and to each step of a transaction, and nothing is
|
|
172
|
+
written. Before, the database refused it and the caller received `A3001`. Writing either one alone is unchanged.
|
|
173
|
+
- 1e65e9a: A guard, pipe, hook, interceptor or filter typed for any Resource — `Guard`, `Pipe`, `BeforeHook` and the rest without a
|
|
174
|
+
model argument, or a callback whose context is a `Pick<PipelineContext, …>` — in the root `pipelines` no longer erases
|
|
175
|
+
`framework.application`, `framework.client`, `framework.appTx` and `framework.clientTx` to an untyped object. They stay
|
|
176
|
+
typed from your adapter's model with any mix of stage types, at the root and in the `application` and `client`
|
|
177
|
+
overlays, with or without `as const`, and a callback written inline is still told your Resource names.
|
|
178
|
+
- 24ffbab: **Behaviour changes, transactions:** an object carrying `$ref` in a step's `args` is now copied when you call
|
|
179
|
+
`framework.executeTransaction`, like the rest of your arguments. Where a `$ref` belongs (a field value, a filter
|
|
180
|
+
value, a `$connect` key) it is answered exactly as before; changing it after the call no longer affects the plan.
|
|
181
|
+
Placed anywhere else — the whole `where`, a whole relation value, the whole `data` — a member of it that cannot be
|
|
182
|
+
read (a getter that throws, a revoked Proxy) is now your request's error, `A2004` with one `V1001` at that member's
|
|
183
|
+
path and `cause.operation`, reported to `misuse`; before, it was `A3000`. The framework never runs a getter inside an
|
|
184
|
+
object that carries `$ref`, wherever it stands, so such a getter is refused unread.
|
|
185
|
+
|
|
186
|
+
A value whose first read throws is now refused even if reading it again would succeed: `A2004`/`V1001` at the object
|
|
187
|
+
or array whose read failed. Before, it was copied from the second read.
|
|
188
|
+
- 2d00ad7: An issue your own code throws from a transaction step now keeps its `path`. A pipe, guard, hook, interceptor,
|
|
189
|
+
filter or computed behavior does not know where its operation stands in a plan, so it writes the path as it would for
|
|
190
|
+
a single call — `["arguments", "data", "name"]`. Inside `framework.transaction`, `framework.executeTransaction` and
|
|
191
|
+
`POST /_transactions` that path is now checked against that step's own arguments and sent as
|
|
192
|
+
`["operations", i, "args", "data", "name"]` beside `cause.operation`; before, it was dropped and the issue reached the
|
|
193
|
+
caller without a path. A path written as `["operations", i, "args", …]` with the step's own index is accepted as
|
|
194
|
+
before, and a path that does not name a member of the step's arguments is still dropped.
|
|
195
|
+
- 384a50c: **Behaviour changes, transactions:** every issue about a transaction step's arguments now names where it is
|
|
196
|
+
in the plan, `["operations", i, "args", …]`, beside `cause.operation: i`. Before, the framework's own checks — a wrong
|
|
197
|
+
type, an unknown field or operator, a structural limit, a Json value out of its `AvZ` shape, a value that is not valid
|
|
198
|
+
JSON — answered `["arguments", …]` inside a plan while a bad `$ref`, a refused value and a step that depends on a
|
|
199
|
+
cascaded delete already answered `["operations", i, "args", …]`; now one plan uses one spelling, through
|
|
200
|
+
`framework.transaction`, `framework.executeTransaction` and `POST /_transactions` alike. A single call is unchanged
|
|
201
|
+
(`["arguments", …]`). If your code reads `issue.path` from a transaction's answer, drop the `"operations", i, "args"`
|
|
202
|
+
prefix to get the path inside that step's arguments. `@aventara/testing`'s protocol corpus row "a client-hidden
|
|
203
|
+
field — /_transactions" and `fixtures/protocol-v1.json` carry the new path.
|
|
204
|
+
- d43d1cc: **Types:** `diagnostic.deliberate` now type-checks on any `FrameworkDiagnostic`, without checking
|
|
205
|
+
`diagnostic.kind` first, so a `diagnostics` sink can log it for every report. It is `true` only on an `"operation"`
|
|
206
|
+
diagnostic for a `FrameworkError` you threw on purpose with an `A3xxx` code, and always absent on a `"transaction"`
|
|
207
|
+
diagnostic, which reports the transaction's own failure (`A3002`). Before, reading it on the union was a TypeScript
|
|
208
|
+
error (`TS2339`).
|
|
209
|
+
- 3f98749: **Types:** `framework.application`, `framework.client`, `framework.appTx` and `framework.clientTx` now follow
|
|
210
|
+
your `restrictions` the way the generated client does. A field whose capability you turn off — `select`, `filter`,
|
|
211
|
+
`order`, `create` or `update` — is refused in that position and left out of the result types; an operation you turn
|
|
212
|
+
off (`operations: { delete: { unique: false } }`) is not on the façade; and a unique operation disappears when its
|
|
213
|
+
only identifier is hidden. Each façade reads its own layer: `framework.client` the root restrictions with
|
|
214
|
+
`client.restrictions` over them, `framework.application` the root restrictions with `application.restrictions` over
|
|
215
|
+
them — so `hidden` now applies to `framework.application` too. Before, only `client`'s `hidden` was reflected, so with
|
|
216
|
+
`passwordHash: { capabilities: { select: false, … } }` a call such as
|
|
217
|
+
`framework.client.User.find.many({ select: ["passwordHash"] })` compiled and every result type carried `passwordHash`.
|
|
218
|
+
Nothing changes at runtime: the server refused those calls before and still does. A restriction value typed as a plain
|
|
219
|
+
`boolean` rather than `true`/`false` still leaves the field as it was.
|
|
220
|
+
- 032f792: **Behaviour changes, transactions:** an error about a transaction step itself — rather than its arguments — now
|
|
221
|
+
names the step too, so every path in a transaction's answer is a path through the request you sent: an unknown or
|
|
222
|
+
hidden Resource is at `["operations", i, "resource"]`, a fingerprint that does not match at
|
|
223
|
+
`["operations", i, "fingerprint"]`, a missing or unknown key on the step at `["operations", i, "<key>"]`, and a step
|
|
224
|
+
that is not an object at `["operations", i]`. An operation the step cannot run points at the key that is wrong:
|
|
225
|
+
`["operations", i, "family"]` when the family is not a string, not a standard family, or one the Resource offers no
|
|
226
|
+
variant of; `["operations", i, "variant"]` when the family is right and the variant is not (unknown, not offered or
|
|
227
|
+
restricted for it). Before, these answered `["resource"]`, `["operation"]`, `["fingerprint"]` and `["<key>"]`.
|
|
228
|
+
`cause.operation` and the outcome code are unchanged: an unknown Resource is still `A2001`, an unavailable operation
|
|
229
|
+
`A2002`. `framework.transaction([...])` refuses an entry that is not a handle, or one handle placed twice, at
|
|
230
|
+
`["operations", i]`. A single call is unchanged. `@aventara/testing`'s protocol corpus and `fixtures/protocol-v1.json`
|
|
231
|
+
carry the new paths.
|
|
232
|
+
- dbe6961: **Behaviour changes:** a single call to an operation a Resource does not offer now names the key of the
|
|
233
|
+
request that is wrong instead of `["operation"]`, which no request has: `["family"]` when the family is not a standard
|
|
234
|
+
one or the Resource offers none of its operations, `["variant"]` when the family is right and the variant is not. This
|
|
235
|
+
holds for `framework.execute` and for an HTTP route, whose `/_resources/:resource/:family/:variant` segments are named
|
|
236
|
+
by those same keys. The code is unchanged (`A2002` / `V1006`), and so is `["resource"]` for an unknown Resource.
|
|
237
|
+
`@aventara/testing`'s protocol corpus and `fixtures/protocol-v1.json` carry the new paths.
|
|
238
|
+
|
|
239
|
+
## 0.1.0-pilot.4
|
|
240
|
+
|
|
241
|
+
### Patch Changes
|
|
242
|
+
|
|
243
|
+
- f37dcd6: The documentation shipped with the package — the comments an editor shows on hover, and the README — now describes
|
|
244
|
+
what each export does, how to use it, what it returns and what it throws. Notes about how the package was built and
|
|
245
|
+
why are no longer part of what is published.
|
|
246
|
+
|
|
247
|
+
The declarations a generated client copies from `@aventara/core` carry the same documentation.
|
|
248
|
+
|
|
249
|
+
## 0.1.0-pilot.3
|
|
250
|
+
|
|
251
|
+
## 0.1.0-pilot.2
|
|
252
|
+
|
|
253
|
+
### Patch Changes
|
|
254
|
+
|
|
255
|
+
- c2a3b7c: A `bigint` value outside the signed 64-bit range is refused as `A2004` with a `V1004` issue at its path, from the wire
|
|
256
|
+
and from the application alike, before any pipeline or adapter runs. Before this release it reached the database and
|
|
257
|
+
answered `A3001`.
|
|
258
|
+
- e69e88d: The published packages are written for the people who use them. The JavaScript ships without comments; the type
|
|
259
|
+
declarations keep their documentation, and it, the error messages, the protocol fixtures' labels and the comments in a
|
|
260
|
+
generated client no longer carry the project's internal development notes.
|
|
261
|
+
- ab58424: A pipe's output is checked against the server's own rules before it reaches the adapter. The request is validated
|
|
262
|
+
against the caller's Contract; after each pipe, the arguments are validated against the Core Contract (plus fields the
|
|
263
|
+
caller's scope adds), so a pipe may still write what the caller cannot — a field the client Contract hides, such as a
|
|
264
|
+
password hash — but not what the server cannot store. A refusal is the new internal code `A3004 PIPE_OUTPUT_INVALID` (HTTP
|
|
265
|
+
500). The caller receives only the generic `A server pipeline produced invalid arguments.`, like every internal
|
|
266
|
+
failure; the pipe (`pipes[i]` and its function name), the operation, the offending field and its V-code go to the
|
|
267
|
+
diagnostics sink as a `PipeOutputError`, and `@aventara/nest` writes them into its error log line. Nothing reaches the
|
|
268
|
+
adapter. This holds for operations and transaction steps alike (a plan answers with `cause.operation`). Generated
|
|
269
|
+
clients map `A3004` to `InternalError`; the protocol fixtures in `@aventara/testing` include an `A3004` row.
|
|
270
|
+
- 23937f8: A create that lacks a value the database requires is refused as `A2004` with a `V1000` issue at the value's path,
|
|
271
|
+
instead of reaching the database and answering `A3001`. It covers `create.one`, every row of `create.many`, `upsert`'s
|
|
272
|
+
`create` branch, nested `$create` (and `$connectOrCreate`/`$upsert` creates) at any depth, and transaction steps
|
|
273
|
+
(`["operations", i, "args", …]`). A required relation is satisfied by writing it or every scalar that holds its
|
|
274
|
+
reference; a nested create never needs the relation it is created through. Defaults, `@updatedAt`, autoincrement,
|
|
275
|
+
optional and list columns are never required. The check runs after pipes and computed behaviours, so a value they
|
|
276
|
+
supply counts.
|
|
277
|
+
|
|
278
|
+
An adapter states the requirements through the new optional `Adapter.createRequirements`; the Prisma 7 adapter reads
|
|
279
|
+
them from the generated client's create inputs at generate time and records them in the discovery artifact, so the
|
|
280
|
+
artifact must be regenerated (`aventara:prepare`).
|
|
281
|
+
|
|
282
|
+
## 0.1.0-pilot.1
|
|
283
|
+
|
|
284
|
+
### Patch Changes
|
|
285
|
+
|
|
286
|
+
- a45f8a8: The `400 A2000` for a request without its identity headers now says which one: `cause.message` is "The
|
|
287
|
+
Aventara-Contract-Hash header is missing." (or "The Aventara-Protocol-Version header is missing.", or both, naming both),
|
|
288
|
+
instead of the generic "The request is malformed and cannot be interpreted." It names headers only and echoes no value.
|
|
289
|
+
The code, status and every other `A2000` are unchanged. The protocol fixtures in `@aventara/testing` check the new messages, with a row
|
|
290
|
+
for a request with neither header.
|
|
291
|
+
- 799346e: The package READMEs (each package's npm page) are rewritten for the people who use the packages. The Prisma 7
|
|
292
|
+
adapter's README now states the actual behaviour of a to-many relation or `$count` projected on
|
|
293
|
+
`update.many`/`create.many`: a validation refusal, `A2004` with `V1008`, before any statement runs (it said `A3001`).
|
|
294
|
+
Every install or run command names the `@pilot` dist-tag.
|
|
295
|
+
|
|
296
|
+
## 0.1.0-pilot.0
|
|
297
|
+
|
|
298
|
+
### Minor Changes
|
|
299
|
+
|
|
300
|
+
- First pilot release of `@aventara/core`: the framework itself — configuration, Contracts, validation, operations,
|
|
301
|
+
pipelines and transactions — independent of any HTTP server or ORM.
|
|
302
|
+
|
|
303
|
+
- **`createFramework(config)`** builds a Framework from an adapter and a configuration. The configuration is typed
|
|
304
|
+
against the adapter's model, so restriction keys, behaviors, `dependsOn` and pipeline `resource` guards are checked
|
|
305
|
+
against the Resources and fields that exist; `defineFields` declares the fields a configuration adds so the rest of
|
|
306
|
+
it can name them.
|
|
307
|
+
- **Contracts.** The adapter's capabilities and your configuration compile into the Core, Application and Client
|
|
308
|
+
Contracts at startup. The ClientContract is what clients are generated from, and its hash identifies it. A scoped
|
|
309
|
+
configuration (`application`, `client`) only narrows what the root allows, and a configuration asking for a
|
|
310
|
+
capability the root or the adapter does not offer fails at startup with `COMPILER_UNAVAILABLE_CAPABILITY` at its
|
|
311
|
+
path. A value the Contract cannot represent, or an invalid ClientContract (`COMPILER_INVALID_CLIENT_CONTRACT`), also
|
|
312
|
+
fails at startup.
|
|
313
|
+
- **Capabilities by argument position.** A relation the database projects or writes only in some operations is
|
|
314
|
+
advertised only there; asking for it elsewhere is refused with `A2004` (`V1008` for a projection, `V1006` for a
|
|
315
|
+
write) before any query, and is a type error.
|
|
316
|
+
- **Typed calls.** `framework.application.<resource>.<family>.<variant>(args)` and `framework.client.…` take arguments
|
|
317
|
+
and return results typed from the Contract (`OperationResultFor`); an optional second argument, `ExecutionOptions`
|
|
318
|
+
(`{ requestId }`), sets the request id every pipeline context, adapter call and diagnostic of the call carries.
|
|
319
|
+
- **Transactions.** `framework.appTx` and `framework.clientTx` build typed plans whose steps can use earlier steps'
|
|
320
|
+
results through `$ref`; `framework.transaction(steps, options)` runs them. A step that depends on rows an earlier
|
|
321
|
+
step's cascading delete removed is refused before the plan runs (`A2004`/`V1019`).
|
|
322
|
+
- **Fields and pipelines.** Computed and virtual fields under `application.fields` and `client.fields`; guards, pipes,
|
|
323
|
+
before and after hooks, interceptors and filters.
|
|
324
|
+
- **Scalars.** `Decimal` (string-backed; `toJSON()` writes its digits; a non-string argument throws a `TypeError`),
|
|
325
|
+
`JsonValue`, and the `ApplicationScalarForms` / `WireScalarForms` maps. A `Date`, `Decimal` or `Uint8Array` at a
|
|
326
|
+
filter position is shorthand for equality.
|
|
327
|
+
- **`@aventara/core/protocol`**, a second entry point: the HTTP protocol as data and pure functions a host mounts —
|
|
328
|
+
`AvProtocol.headers`, `httpStatus`, `scalarFormats`, `normalizeEntrypoint` and `isOperationResponse`, and
|
|
329
|
+
`AvProtocol.bind(framework)` with `surface()` (the routes to mount), `handleOperation`, `handleTransaction`,
|
|
330
|
+
`encodeContract` (`GET /_contract`, with `ETag` and `304`), `answerAbsent`, `failure`, and the decoders and encoders
|
|
331
|
+
under them. A request without an identity header answers `400 A2000`, a wrong protocol version `A2006`, a stale
|
|
332
|
+
contract hash `A2005`; a body larger than `limits.maxRequestBytes` `413 A2010`, a content type other than
|
|
333
|
+
`application/json` `415 A2011`; every `405 A2012` carries `Allow`. A malformed request never throws: it answers
|
|
334
|
+
`400 A2000`.
|
|
335
|
+
- **The entrypoint** is normalized once: `"api"`, `"/api/"` and `"//api//"` become `"/api"`, `"/"` becomes `""`, and an
|
|
336
|
+
entrypoint that is not a plain path (whitespace, a scheme, a query, a `..` segment, a `%` and the like) fails at
|
|
337
|
+
startup with `CONFIG_INVALID_VALUE`.
|
|
338
|
+
- Every export has a `default` condition, so a CommonJS project can `require()` the package.
|
package/README.md
CHANGED
|
@@ -11,9 +11,9 @@ Core is ORM- and transport-agnostic: it imports neither Prisma nor NestJS. An ap
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
npx @aventara/cli@
|
|
15
|
-
npx @aventara/cli@
|
|
16
|
-
npm install @aventara/core@
|
|
14
|
+
npx @aventara/cli@rc new my-api # a new NestJS project with Prisma and Aventara
|
|
15
|
+
npx @aventara/cli@rc init # or: add Aventara to an existing NestJS 12 project
|
|
16
|
+
npm install @aventara/core@rc # the package on its own
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
Node `^22.18.0 || >=24.2.0`. ES modules, with a CommonJS `default` condition.
|
|
@@ -24,6 +24,103 @@ Start from the `@aventara/cli` README (what `new` and `init` write), then `@aven
|
|
|
24
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
|
+
## Typed Json fields
|
|
28
|
+
|
|
29
|
+
A `Json` field is any JSON by default. To give it a shape, build one with `AvZ` from `@aventara/core/json` and declare
|
|
30
|
+
it on the field in the configuration:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { AvZ } from "@aventara/core/json";
|
|
34
|
+
|
|
35
|
+
const postMeta = AvZ.object({
|
|
36
|
+
seo: AvZ.object({ title: AvZ.string(), keywords: AvZ.array(AvZ.string()) }),
|
|
37
|
+
status: AvZ.enum(["draft", "published"]),
|
|
38
|
+
rating: AvZ.optional(AvZ.int()),
|
|
39
|
+
cover: AvZ.nullable(AvZ.string()),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export type PostMeta = AvZ.infer<typeof postMeta>;
|
|
43
|
+
|
|
44
|
+
const framework = await createFramework({
|
|
45
|
+
entrypoint: "/api",
|
|
46
|
+
adapter,
|
|
47
|
+
fields: { Post: { meta: { json: postMeta } } },
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- **Typed.** `framework.application`, `framework.client`, `appTx` and `clientTx` read and write `Post.meta` as
|
|
52
|
+
`PostMeta`, not as generic JSON, and so does a client generated with `avclient` (it emits the type; it needs no
|
|
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:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{ "code": "V1001", "path": ["arguments", "data", "meta", "seo", "title"], "message": "Value must be a string." }
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
A missing key is `V1000`, a value outside an enum or a literal `V1002`, an integer outside the safe range `V1004`,
|
|
63
|
+
and a key the shape does not declare `V1005`, once per key. Reads and filters are never checked against a shape, so
|
|
64
|
+
rows written before the shape existed stay readable.
|
|
65
|
+
- **Types only.** `AvZ` has `string`, `number`, `int`, `boolean`, `null`, `literal`, `enum`, `object`, `array`,
|
|
66
|
+
`tuple`, `record`, `union`, `discriminatedUnion`, `optional` and `nullable` — no lengths, ranges, patterns,
|
|
67
|
+
refinements, transforms or defaults. Objects are closed: a key the shape does not declare is refused. `optional`
|
|
68
|
+
marks an object key that may be left out.
|
|
69
|
+
- **Where it applies.** Only a field the adapter discovered as `Json` takes a shape, and the shape itself cannot admit
|
|
70
|
+
`null` or absence at its top level — whether the field may be `null` comes from your schema. A shape is built with
|
|
71
|
+
`AvZ` only: a Zod schema is not accepted, as a type error and when the framework starts.
|
|
72
|
+
- **In the Contract.** The shape is part of the ClientContract, as JSON Schema; adding or changing one changes the
|
|
73
|
+
ClientContract hash, so regenerate the client.
|
|
74
|
+
|
|
75
|
+
The shape types `AvZShape`, `AvZObject`, `AvZOptional` and `AvZNullable` are exported so a project that emits
|
|
76
|
+
declarations can name the shapes it exports.
|
|
77
|
+
|
|
78
|
+
`@aventara/core/json` is the only entry that loads Zod (`zod/mini`); an application that declares no shape never loads
|
|
79
|
+
it.
|
|
80
|
+
|
|
81
|
+
## Server-side calls copy their input
|
|
82
|
+
|
|
83
|
+
`framework.application`, `framework.client`, `framework.execute`, `framework.executeTransaction`, and the `appTx` /
|
|
84
|
+
`clientTx` builders read what you pass them once, when you call them, and copy it into plain data the framework owns.
|
|
85
|
+
Everything after that — validation, your guards and pipes, computed values, the adapter — sees only the copy:
|
|
86
|
+
|
|
87
|
+
- **Changing your object afterwards changes nothing.** An Immer draft revoked after `produce`, or a reactive store that
|
|
88
|
+
moves on, is safe to pass: the call already has its own copy.
|
|
89
|
+
- **A live Proxy works** (Vue `reactive`, MobX): it is read through its traps once.
|
|
90
|
+
- **`Date`, `Decimal` and `Uint8Array`** (a Node `Buffer` too) are copied as themselves; `bigint` and other primitives are
|
|
91
|
+
values.
|
|
92
|
+
- **Only plain data.** Arrays and plain objects (`{}` or `Object.create(null)`) are copied with their own enumerable
|
|
93
|
+
string keys, like JSON: inherited, non-enumerable and symbol keys are dropped. Any other object — a class instance, a
|
|
94
|
+
`Map` — is refused.
|
|
95
|
+
- **A value that cannot be read** (a revoked Proxy, a getter that throws) is refused too. Both refusals are your
|
|
96
|
+
request's error, not the server's: `A2004` with one `V1001` at the value's path — `["arguments", …]`, or
|
|
97
|
+
`["operations", i, "args", …]` with `cause.operation` in a transaction — before any guard, pipe or adapter runs:
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{ "code": "V1001", "path": ["arguments", "where", "id"], "message": "Value could not be read." }
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
A builder never throws: `appTx`/`clientTx` keep the refusal, and `framework.transaction([...])` answers it.
|
|
104
|
+
|
|
105
|
+
To count these refusals, set `misuse` at the root of the configuration, beside `diagnostics`:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
const framework = await createFramework({
|
|
109
|
+
entrypoint: "/api",
|
|
110
|
+
adapter,
|
|
111
|
+
misuse: (event) => metrics.increment("aventara.misuse", { kind: event.kind }),
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
It receives one `FrameworkMisuseEvent` per refusal — `kind` (`"unreadable-value"` or `"non-plain-value"`), the `path`
|
|
116
|
+
the caller received, and `origin`, `resource`, `family`, `variant` and the transaction step (`operation`) when they could
|
|
117
|
+
be read — never the value itself. It cannot change the response: an error it throws, or a promise it rejects, is
|
|
118
|
+
ignored. `diagnostics` keeps receiving the server's own unexpected failures, and nothing is reported to both.
|
|
119
|
+
|
|
120
|
+
HTTP requests cost nothing extra: a body that `protocol.handleOperation` / `handleTransaction` parse from raw content
|
|
121
|
+
(as `@aventara/nest` hands it to them) is already plain data and is not copied again. A body your host parsed itself,
|
|
122
|
+
passed as `{ kind: "parsed" }`, is copied like any server-side input.
|
|
123
|
+
|
|
27
124
|
## License
|
|
28
125
|
|
|
29
126
|
PolyForm Shield 1.0.0 with an additional permission — free to use, including in commercial applications; you may not
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { EnumDefinition } from "../contracts/enum-definition.js";
|
|
2
|
+
import type { ReferenceWriteLimit } from "../contracts/reference-write-limit.js";
|
|
2
3
|
import type { ScalarRegistry } from "../contracts/scalar.js";
|
|
3
4
|
import type { TransactionCapability } from "../contracts/transaction-capability.js";
|
|
4
5
|
import type { AdapterResource } from "./adapter-resource.js";
|
|
@@ -15,4 +16,11 @@ export interface AdapterModel {
|
|
|
15
16
|
readonly resources: Readonly<Record<string, AdapterResource<this>>>;
|
|
16
17
|
/** Whether the adapter can run interactive transactions. */
|
|
17
18
|
readonly transactions: TransactionCapability;
|
|
19
|
+
/**
|
|
20
|
+
* `"uniform"` when the data source takes one record's references only one
|
|
21
|
+
* way at a time: every relation that holds a reference written as a
|
|
22
|
+
* relation, or every one through its reference fields. Absent: each relation
|
|
23
|
+
* either way. Compiled into every Contract.
|
|
24
|
+
*/
|
|
25
|
+
readonly referenceWrites?: ReferenceWriteLimit;
|
|
18
26
|
}
|
|
@@ -11,14 +11,18 @@ export interface Adapter<M extends AdapterModel = AdapterModel> extends AdapterO
|
|
|
11
11
|
readonly model: M;
|
|
12
12
|
/**
|
|
13
13
|
* Optional referential actions: for each Resource that holds a reference,
|
|
14
|
+
* every relation holding one — the fields that hold it (`references`) and
|
|
14
15
|
* what deleting a referenced row does to its rows. Read once when the
|
|
15
16
|
* framework is constructed; never part of a Contract or its hash.
|
|
16
17
|
*
|
|
17
18
|
* An edge listed under Resource `R` says **deleting a `target` row does
|
|
18
|
-
* `onDelete` to rows of `R
|
|
19
|
+
* `onDelete` to rows of `R`**, and that `R`'s `references` fields hold the
|
|
20
|
+
* relation `field`'s reference.
|
|
19
21
|
*
|
|
20
22
|
* When absent, no cascade is planned and no transaction plan is refused
|
|
21
|
-
* because of one (`V1019`)
|
|
23
|
+
* because of one (`V1019`); a write that sets a relation and a field holding
|
|
24
|
+
* its reference both is not refused before the store; and a required
|
|
25
|
+
* relation can be satisfied only by writing the relation.
|
|
22
26
|
*/
|
|
23
27
|
readonly referentialActions?: ReferentialActionTable;
|
|
24
28
|
/**
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
/** Codes of {@link ContractCompilerDiagnostic}. */
|
|
2
2
|
export type ContractCompilerDiagnosticCode = "COMPILER_UNKNOWN_RESOURCE" | "COMPILER_UNKNOWN_FIELD" | "COMPILER_RELATION_FIELD_CONFIG_UNSUPPORTED" | "COMPILER_INCOMPLETE_FIELD" | "COMPILER_STRUCTURAL_FIELD_OVERRIDE" | "COMPILER_MISSING_SCALAR" | "COMPILER_MISSING_ENUM" | "COMPILER_DANGLING_RELATION" | "COMPILER_UNAVAILABLE_OPERATION" | "COMPILER_UNAVAILABLE_CAPABILITY" | "COMPILER_INVALID_LIFECYCLE" | "COMPILER_MISSING_READ_BEHAVIOR" | "COMPILER_INVALID_BEHAVIOR_DEPENDENCY"
|
|
3
|
+
/**
|
|
4
|
+
* A field's `json` shape cannot apply: the field is not a Json field the
|
|
5
|
+
* adapter discovered, the value is not a shape built with `AvZ`, or the shape
|
|
6
|
+
* admits `null` or absence at its top level.
|
|
7
|
+
*/
|
|
8
|
+
| "COMPILER_INVALID_JSON_SHAPE"
|
|
3
9
|
/**
|
|
4
10
|
* The finished, hashed client projection is not a structurally valid
|
|
5
11
|
* `ClientContract`. A framework-bug guard: the server never serves what its own
|