@aventara/cli 1.0.0-rc.11 → 1.0.0-rc.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +203 -0
- package/README.md +3 -3
- package/dist/apply/conflict.confirmer.js +4 -1
- package/dist/apply/lint-tooling.stripper.js +32 -0
- package/dist/apply/manifest.merger.js +65 -22
- package/dist/apply/pnpm-workspace.reader.js +179 -0
- package/dist/catalog/range.reader.js +0 -7
- package/dist/cli.js +16 -0
- package/dist/command/command.parser.js +7 -0
- package/dist/plan/nest-host.planner.js +2 -2
- package/dist/plan/next-host.planner.js +1 -0
- package/dist/plan/project.planner.js +53 -5
- package/dist/project/pnpm-workspace.locator.js +113 -0
- package/dist/project/project.inspector.js +7 -2
- package/dist/release/newest-release.reader.js +63 -0
- package/dist/release/npm-registry.resolver.js +120 -0
- package/dist/run/init.orchestrator.js +10 -1
- package/dist/run/new.orchestrator.js +51 -9
- package/dist/run/skill.orchestrator.js +18 -1
- package/dist/skill/agent-skill.generated.js +8 -6
- package/dist/skill/agent-skill.planner.js +14 -2
- package/dist/templates/prisma7/prisma7-nest.templates.js +3 -4
- package/dist/templates/prisma7/prisma7-next.templates.js +3 -4
- package/dist/templates/prisma7/prisma7-orm.templates.js +1 -1
- package/package.json +1 -1
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export const AGENT_SKILL_GUIDANCE = {
|
|
2
|
-
"SKILL.md": "---\nname: aventara\ndescription: Build and change this project's Aventara API (NestJS or Next.js, Prisma, @aventara/core). Use when editing src/aventara.config.ts, src/aventara/config.ts, src/resources/*.resource.ts or prisma/schema.prisma; when exposing or hiding Resources, fields or operations (exposed, exemptions); when adding guards, pipes, field validators (AvZ), computed fields (compute.create/update/read), hooks, interceptors, filters, transactions, forceTransaction, an outbox, ctx.$state or the agent layer; when caching reads (cache stage, avCache) or loading several calls at once (avClient.all, POST /_all, avLazy, grouping, limits.maxAll); and when an Aventara code appears (A2004, V1000, CONFIG_*, COMPILER_*, AvConstructionError, AvValidationError).\n---\n\n# Aventara\n\nAventara turns the Prisma schema into a typed API: every model is a Resource with standard operations (`find`, `create`, `update`, `delete`, `upsert`), served at `api` (`/api`) and called from the generated frontend client. You configure it; you do not write controllers. Written for `@aventara/core` {{CORE_VERSION}}.\n\nThere is one way to do each task. Follow these rules; the reference files hold the detail:\n\n- `reference/config.md`: the configuration tree, where every key goes, exposure, limits.\n- `reference/stages.md`: which stage fits a task, the order stages run in, the rules.\n- `reference/recipes.md`: complete, type-checked examples for the common tasks.\n- `reference/client.md`: calling the API from frontend and server code: several calls in one request (`avClient.all`, `POST /_all`), aborting, `avLazy`, `grouping: false`, cached reads.\n- `reference/errors.md`: every code (`A…`, `V…`, `CONFIG_…`, `COMPILER_…`) with its cause and fix.\n\nIf `@aventara/access` is installed, read its own skill, `node_modules/@aventara/access/skill/SKILL.md`, for policies.\n\n## Where things are\n\n| File | What it is |\n|---|---|\n| `prisma/schema.prisma` | The data model. Every model is a Resource, named exactly as the model. |\n| `src/aventara.config.ts` | `aventaraConfig`: the configuration, a plain value `as const`, without `adapter`. Keep the `as const`. |\n| `src/aventara.adapter.ts` | The adapter's description, `describePrismaAdapter({ discovery, provider, driver })`. |\n| `src/resources/<name>.resource.ts` | One Resource's configuration, an exported `defineResource(...)`. `aventara:prepare` gathers every one into `aventaraResources`: no spread to write, no config edit. |\n| `src/generated/aventara/` | Generated by `aventara:prepare`: `discovery.artifact.ts` and, beside it, `resources.generated.ts`, `computed-fields.generated.ts` and `contracts.generated.ts`. When Prisma's client output is `src/generated` itself, these four live in `src/aventara/` instead (the `--out` of `aventara:prepare`, and where `src/aventara.config.ts` imports `aventaraResources` from). Never edit them. |\n| Prisma's client output | Generated by `aventara:prepare` (`prisma generate`), at your schema generator's `output`. Never edit it. |\n| `src/app.module.ts` | `AventaraModule.forRootAsync(...)` builds the framework from `aventaraConfig` and the adapter (the description plus the Prisma client). |\n\n### In a Next.js project\n\nA Next.js app (`@aventara/next`, App Router under `src/app/`) keeps everything Aventara in one folder, `src/aventara/`. Everything else on this page applies; read these paths for the NestJS ones above.\n\n| File | What it is |\n|---|---|\n| `src/aventara/config.ts` | `aventaraConfig`, as `src/aventara.config.ts` above. Its `api` (`/api`) is the folder of the route file. |\n| `src/aventara/adapter.ts` | The adapter's description, as `src/aventara.adapter.ts` above. |\n| `src/aventara/server.ts` | `Aventara({ ...aventaraConfig, adapter, prebuilt, state })`, exporting `handlers`, `avClient`, `avApp` and `ready`. Import it as `@/aventara/server`, in server code only. It replaces `app.module.ts`: `state` seeds `ctx.$state` from each request's headers. |\n| `src/aventara/client.ts`, `src/aventara/universal.ts`, `src/aventara/generated/` | Generated by `aventara:prepare` (`aventara-build --client`): the browser client (`@/aventara/client`), the universal entry and the generated modules. Never edit them; they are in `.gitignore`. |\n| `src/app/api/[...aventara]/route.ts` | Exports every method of `handlers`. One line; keep it. |\n| `src/instrumentation.ts` | Awaits `ready()` on the Node.js runtime, so a bad configuration stops `next start`. |\n| `src/prisma.ts` | `prisma()`, the app's one Prisma client, made on first use. |\n| `next.config.ts` | Keeps `serverExternalPackages: [\"@aventara/core\", \"@aventara/prisma7-adapter\"]`: Node.js loads them once per server process, so every route and page shares one core, one Framework and one Prisma client. Keep the line. |\n\nServer Components and Server Actions call `avClient.<Resource>.<family>.<variant>(args)` (checked as the caller) or `avApp` (trusted code); Client Components call the generated `avClient` from `@/aventara/client`. There is no separate frontend to regenerate: `aventara:prepare` writes the browser client too, and `next build` runs it first. An `mcp` endpoint on Next.js needs `authorization`.\n\nA call reads the database, so Next.js runs it at request time (Cache Components is on: `cacheComponents: true`). Give a page that calls `avClient` or `avApp` one of: the calling component inside `<Suspense fallback={…}>` (or a `loading.tsx`), `\"use cache\"` on a function that makes the call (the result is shared by every visitor: never with `state`), or `export const instant = false` to render the whole page at request time. Without one, `next build` fails naming these three: add one (no `connection()` is needed).\n\n## Rules\n\n1. **Change the schema, then prepare.** After editing `prisma/schema.prisma` run your migration (`npx prisma migrate dev` or `npx prisma db push`), then `npm run aventara:prepare`. Run `aventara:prepare` again after adding or removing a Resource file, and after adding, moving or removing a `compute` stage; the server warns `generated types are stale` until you do. Restart the server and regenerate the frontend client (`npm run avclient:generate` in the frontend): the contract hash changed.\n2. **Configure, never wrap.** Do not write a Nest controller, service or REST route for a model. Every model already has its operations; shape them with `exposed`/`exemptions` and pipelines. Server code calls `framework.application.<Resource>.<family>.<variant>(args)`.\n3. **Expose with exemptions.** `exposed: true` (the root's default; an overlay that lists `exemptions` must say it) exposes everything the adapter offers and `exemptions` lists what to hide; `exposed: false` exposes nothing and `exemptions` lists what to expose. Hide at the root what no caller may reach; hide in `client` what only your server code may reach. An overlay (`application`, `client`, `agent`) only narrows the root's view: to expose something in one overlay only, expose it at the root and hide it in the other.\n4. **Pick the stage from the table below.** Each task has one stage. Do not authorize in an interceptor, transform in a guard, or write other rows from a compute stage.\n5. **Never cast types away.** No `as`, no `!`, no `any`, no `@ts-ignore` / `@ts-expect-error` to silence an Aventara type. The types are the contract: a type error names the exact key that is wrong (a misspelled field, an overlay granting what the root hid, a missing required field). Fix the key. To get typed arguments in a stage, register it on the operation (`resources.<R>.operations.<family>.<variant>.pipelines`) inside a `defineResource` block; to type `ctx.<Resource>` calls, give the stage type your model (`AvAfterHook<Model>`).\n6. **Annotate stages in the `as const` config.** An inline arrow in `aventaraConfig` has no contextual type (TS7006). Declare it with its stage type (`const requireUser: AvGuard = (ctx) => …`) or move it into a `defineResource` block, where stages are typed from where they sit.\n7. **Fill server-side values with `compute.create`.** A required field the caller must not send (an owner id, a slug, a hash) gets `pipelines.compute.create`; that also makes it optional on create. A pipe cannot fill a missing required field: the framework refuses the create (`A2004`, `V1000`) before any pipe runs. Hiding a required field from a layer that still creates the Resource needs a `compute.create`, or startup fails with `COMPILER_HIDDEN_REQUIRED_FIELD`.\n8. **Validate values with `AvZ`.** Length, range, pattern and format rules are field validators (`AvZ.maxLength(80)`, `AvZ.email()`); they are part of the contract, so the generated client checks them before sending. A rule across fields or one that needs the database is a guard.\n9. **Read the user from `ctx.$state`.** Declare it once (`declare module \"@aventara/core\" { interface AvState { user?: … } }`), set it from `AventaraModule`'s `state` option (remote requests) or a call's `{ state }` option (server code), and read `ctx.$state.user` in every stage. Never parse headers again in each stage.\n10. **Side effects that must commit with the write: `forceTransaction` + an `after` hook.** Set `forceTransaction: true` on the operation and write the related rows (an outbox event, a counter) through `ctx.<Resource>` or `ctx.$sql` in its `after` hook. Without it the write has already committed when the hook runs.\n11. **Errors are codes.** Branch on `code` or the error class (`AvNotFoundError`, `AvValidationError`, …), never on a message. Throw `new AvError(\"A4000\", \"Sign in first.\")` from a stage to answer a code. Look up any code in `reference/errors.md` (or `AvErrorCatalog[code]` from `@aventara/core`); a startup diagnostic carries its `docs` URL.\n12. **Do not edit generated files.** Prisma's client, the files beside the discovery artifact (`src/generated/aventara/`, or `src/aventara/` when Prisma's client output is `src/generated`) and the frontend's generated client are rebuilt by `aventara:prepare` and `avclient generate`.\n13. **The configuration runs at build time.** `aventara:prepare` ends with `aventara-build`, which runs `src/aventara.config.ts` without a database client and compiles the Contracts. Keep it a value with no `adapter`, and keep its top level free of side effects (no connection, no request). A `CONFIG_…` or `COMPILER_…` problem fails `aventara:prepare` with the diagnostics startup would print.\n14. **Load together; write atomically in a transaction.** Calls made in the same tick already go out as one `POST <api>/_all`: use `Promise.all`, or `avClient.all({ key: call }, { settle, signal })` for one typed value per key (strict by default; `settle: true` never rejects for one key). It is not atomic: writes that must hold together, or use another's result, go in `avClient.transaction([...])`. Never write a batching route or loader. Details: `reference/client.md`.\n15. **Cache a read with the `cache` stage, `for` written first.** `pipelines: { cache: cache({ for: \"user\", maxAge: 60 }) }` on a `find` (root or `client` layer, Resource, `find` family or variant; the most specific wins). `for`: `\"user\"` (the default: only the caller's browser keeps it), `\"anonymous\"` (a CDN shares it among anonymous callers) or `\"everyone\"` (a CDN shares it with every caller): pick the narrowest that is true. Write `for` even when it is the default `\"user\"`: it states who the answer is for (the one exception to rule 16). Where React's `cache` is imported too, `import { cache as avCache } from \"@aventara/core\"`. Details: `reference/config.md`, Caching reads.\n16. **Write only what changes behaviour.** Leave out what the framework already does: no option set to its default value (no `limits: { maxAll: 10 }`, no `grouping: true`, no `extends: \"client\"` on `agent`, no `exposed: true` at the root), no config key, stage or wrapper that repeats a built-in, and no type annotation the inference already gives (a stage inside a `defineResource` block is typed from where it sits). One exception: `cache({ for, maxAge })` always writes `for`, even the default `\"user\"`, because it reads as a sentence and states the audience.\n\n## Which stage\n\n| You want to | Use | Where it goes |\n|---|---|---|\n| Check a permission | a guard | `pipelines.guards`, at the layer, a Resource, a family or a variant |\n| Refuse a write to one field | a field write guard | `resources.<R>.fields.<f>.pipelines.guards` |\n| Normalize input (trim, lower-case, default, scope a query) | a pipe, or a field pipe for one value | `pipelines.pipes`, or `resources.<R>.fields.<f>.pipelines.pipes` |\n| Enforce a rule on a value (length, range, pattern, format) | a validator | `resources.<R>.fields.<f>.pipelines.validators` |\n| Fill in or derive a field's value | compute | `resources.<R>.fields.<f>.pipelines.compute` (`create`, `update`, `read`) |\n| React after a write (event, audit row, counter) | an `after` hook writing through `ctx`, with `forceTransaction` when it must commit with the write | `pipelines.hooks.after` |\n| Wrap execution (timing, a server-side cache, retries, `AsyncLocalStorage`) | an interceptor | `pipelines.interceptors` |\n| Let the browser or a CDN keep a read's answer | `cache({ for, maxAge })` | `pipelines.cache` on a `find`: layer, Resource, family or variant |\n| Turn an error into an answer | a filter | `pipelines.filters` |\n| Check a whole transaction | the Resource's transaction stages | `resources.<R>.transactions.pipelines` |\n| Share request information (the signed-in user) | `ctx.$state` | `AventaraModule`'s `state` option, or a call's `{ state }` |\n| Call another operation from a stage | `ctx.<Resource>.<family>.<variant>(args)` | the `ctx` every stage gets (compute can only read) |\n\nOrder: framework validation → guards → pipes (operation, then field) → write compute (create/update) → re-check (Aventara's checks on pipe and compute output: `A3004`, `A3000`; then field validators and Json shapes on every value, computed included: `A2004`) → interceptors → `hooks.before` → the operation (then compute read) → `hooks.after` → interceptors unwind; filters handle a failure. `ctx` calls are raw: they run validation, field pipes, compute and validators, and none of your guards, pipes, hooks, interceptors or filters.\n\n## The shape of a configuration\n\n```ts\n// src/aventara.config.ts\nimport { type AvGuard, AvError } from \"@aventara/core\";\nimport { aventaraResources } from \"./generated/aventara/resources.generated.js\";\n\nconst requireUser: AvGuard = (ctx) => {\n if (ctx.$state.user === undefined) throw new AvError(\"A4000\", \"Sign in first.\");\n return true;\n};\n\nexport const aventaraConfig = {\n api: \"/api\",\n // Root: hidden from every caller.\n exemptions: {\n resources: { User: { fields: { passwordHash: [\"select\", \"filter\", \"order\"] } } },\n },\n // Every defineResource() in src/resources/, gathered by aventara:prepare.\n resources: aventaraResources.root,\n // Remote callers (HTTP, the generated client).\n client: {\n exposed: true,\n exemptions: { resources: { User: { operations: { delete: true } } } },\n pipelines: { guards: [requireUser] },\n },\n} as const;\n```\n\n`app.module.ts` joins the adapter to it: `config: { ...aventaraConfig, adapter: await createPrismaAdapter({ ...adapter, client: prisma }) }`, where `adapter` is the default export of `src/aventara.adapter.ts`.\n\n## Calling the API over HTTP\n\nThe generated client does all of this for you. To call the API by hand (curl, a REST tool, a test), each operation is `POST <api>/_resources/<Resource>/<family>/<variant>`, where `<Resource>` is the model's name exactly as in `prisma/schema.prisma` (`User`, not `users`). Send two headers: `Aventara-Protocol-Version: 1` and `Aventara-Contract-Hash`, the hash `GET <api>/_contract` serves (its body's `protocol.hash`, and its `ETag`). The JSON body holds the operation's arguments: a create sends `{ \"data\": … }`.\n\n```sh\nHASH=$(curl -s http://localhost:3000/api/_contract | node -pe 'JSON.parse(require(\"fs\").readFileSync(0, \"utf8\")).protocol.hash')\ncurl -X POST http://localhost:3000/api/_resources/User/create/one \\\n -H 'Content-Type: application/json' \\\n -H 'Aventara-Protocol-Version: 1' \\\n -H \"Aventara-Contract-Hash: $HASH\" \\\n -d '{ \"data\": { \"email\": \"ada@example.com\", \"name\": \"Ada\" } }'\n```\n\nIt answers `201` with `{\"data\":{\"email\":\"ada@example.com\",\"id\":1,\"name\":\"Ada\"},\"code\":\"A1002\",\"cause\":null}`. Without the two headers the answer is `400 A2000`; a lower-case `user` is `404 A2001`; arguments outside `data` are `422 A2004`. After a schema or configuration change the hash changes and an old one is `409 A2005`: fetch it again. Several operations in one request: `POST <api>/_all` (`reference/client.md`, By hand).\n\n## Before you finish\n\n- `npx tsc --noEmit` passes with no casts added.\n- The server starts: an `AvConstructionError` lists each problem as `{ code, path, message, docs }`; fix the key at `path`.\n- After a schema or compute change you ran `npm run aventara:prepare`, and the frontend regenerated its client.\n\nThese files are generated by `aventara` for this project's Aventara version. Refresh them after upgrading: `npx @aventara/cli@<your @aventara/core version> skill`. A file you edited is kept (pass `--force` to replace it).\n",
|
|
3
|
-
"reference/
|
|
4
|
-
"reference/config.md": "# Configuration reference\n\n`aventaraConfig` in `src/aventara.config.ts` is one object `as const`, without `adapter`. Every name in it (Resource, field, operation) is checked against your model by TypeScript, again by `aventara-build` (in `aventara:prepare`) and again at startup.\n\n## The tree\n\n```text\nroot\n api \"/api\": where the protocol is mounted (root only)\n mcp \"/mcp\" or { path, authorization: { servers, verify } }: turns MCP on (root only)\n adapter joined in app.module.ts, never written here: await createPrismaAdapter({ ...adapter, client }) (root only)\n prebuilt serverless and edge only, joined where the framework is created: aventaraContracts (root only)\n diagnostics (d: AvDiagnostic) => void: the real error behind every A3xxx (root only)\n misuse (e: AvMisuseEvent) => void: refused unreadable server-side input (root only)\n application overlay for your server code (framework.application, appTx, ctx calls)\n client overlay for remote callers (HTTP, the generated client)\n agent overlay for AI agents over MCP, plus extends: \"client\" | \"application\" | \"root\"\n -- the six layer keys, at the root and in every overlay --\n exposed true: exemptions hide; false: exemptions are all that is exposed\n exemptions { resources: { <R>: true | { operations?, fields? } } }\n transactionsStyle \"interactive\" | \"none\"\n limits { maxListLimit, maxNestingDepth, maxTransactionOperations, maxBooleanNodes, maxJsonDepth, maxRequestBytes, maxAll, maxAllConcurrency }\n pipelines { guards, pipes, hooks: { before, after }, interceptors, filters, cache } cache: find only, never in application or agent (see Caching reads)\n resources\n <Resource>\n managedElsewhere root only: true when another service owns and migrates the table: read and joined, never written\n pipelines every operation of the Resource\n operations\n <family> find | create | update | delete | upsert\n pipelines every variant of the family\n forceTransaction true: a plain call runs as a one-step transaction\n <variant> e.g. create.one, find.many\n pipelines that one operation (typed ctx.$args)\n forceTransaction\n transactions\n pipelines once per transaction with a top-level step on the Resource\n fields\n <field>\n definition root only: add a virtual field, or give a Json field its AvZ shape\n pipelines\n validators AvZ checks\n pipes (value) => value\n compute { create, update, read }\n guards (value, ctx) => boolean\n```\n\n## How a layer combines with the root\n\n| Key | Rule |\n|---|---|\n| `exposed` + `exemptions` | Narrowing only. An overlay starts from the root's view and can hide more, never bring back what the root hid. |\n| `transactionsStyle` | Override: the overlay's value when present, else the root's. |\n| `limits` | Override per limit. |\n| `pipelines` (every level) | Additive: the root's stages run, then the overlay's. Nothing removes a stage. |\n| `pipelines.cache` | Not additive: the most specific level wins (variant, family, Resource, layer); at one level the `client` layer's replaces the root's. |\n| field `validators` | Additive. |\n| field `compute` | Chained: the root's stage runs, then the overlay's, which receives the root's result as `value`. |\n| field `definition` | Root only (`CONFIG_FORBIDDEN_IN_OVERLAY` in an overlay). |\n| `managedElsewhere` | Root only (`CONFIG_FORBIDDEN_IN_OVERLAY` in an overlay). |\n| `forceTransaction` | Forced when the root or the operation's overlay sets it. |\n\nAn override wins when present, not when truthy (`false` overrides). An explicit `undefined` is refused: leave the key out. The `agent` layer narrows the layer it `extends` (default `\"client\"`).\n\n## Exposure\n\n| Value in `exemptions` | Under `exposed: true` (Omit) | Under `exposed: false` (Pick) |\n|---|---|---|\n| `true` | hide the entry and everything under it | expose the entry and everything under it |\n| `false` | nothing (same as leaving it out) | nothing |\n| a list | hide the listed entries one level down | expose only the listed entries |\n| an object | go one level deeper | go one level deeper |\n\n| Level | Names you can list |\n|---|---|\n| `operations` | `find`, `create`, `update`, `delete`, `upsert` |\n| a family | `find`: `first`, `unique`, `many`, `count`; `create`: `one`, `many`, `count`; `update`: `first`, `unique`, `many`, `count`; `delete`: `first`, `unique`, `count`; `upsert`: `unique` |\n| a scalar field | `select`, `filter`, `order`, `orderNulls`, `create`, `update` |\n| a relation field | `select`, `include`, `filter`, `order`, `pagination`, `reducers`, `create`, `update` |\n| a capability's tokens | `filter`: operators (`equals`, `in`, `contains`, …); `create`/`update`: directives (`$increment`, `$push`, …); relation `create`/`update`: `$create`, `$connect`, `$connectOrCreate`, `$disconnect`, `$set`, `$update`, `$delete`, `$upsert` |\n\nPrisma has no `delete.many`. Common shapes:\n\n- Write-only field: `fields: { passwordHash: [\"select\", \"filter\", \"order\"] }`.\n- Read-only for remote callers: `client.exemptions…fields: { likeCount: [\"create\", \"update\"] }`.\n- Hide a Resource from remote callers: `client.exemptions.resources.AuditLog: true`. Hide the relations that point at it too, or startup fails with `COMPILER_DANGLING_RELATION`.\n- A public client that reaches only what the frontend calls: `client: { exposed: false, exemptions: { resources: { Post: { operations: { find: [\"many\", \"unique\"] }, fields: { id: [\"select\"], title: [\"select\"] } } } } }`. A unique operation needs its identifier exposed (`COMPILER_MISSING_IDENTIFIER`).\n\nA hidden field disappears from that layer's inputs, outputs, filters, ordering and the generated client. A client exemption changes the contract hash: regenerate the frontend client.\n\n## A model managed elsewhere\n\nWhen another service owns a table and migrates it (Better Auth's `user`, say, or another service's schema in the same\ndatabase), mark its model `managedElsewhere: true` at the root. Aventara then reads it and joins through it, and no\nstandard operation writes it:\n\n- it has no `create`, `update`, `delete` or `upsert`, and its fields take no write;\n- a relation into it keeps only `$connect`/`$disconnect`, and only where your model holds the key (`authorId`);\n- the client and agent layers leave it out, with every relation into it; your server code reads and joins it;\n- a stage on one of its write families is refused at startup (`COMPILER_UNAVAILABLE_OPERATION`), and so is any\n `client.resources` or `agent.resources` entry for it (`COMPILER_UNKNOWN_RESOURCE`): nothing there would run;\n- a delete whose `onDelete` would cascade into it or null its reference is refused at startup (`CONFIG_INVALID_VALUE`);\n hide that delete at the root or make the reference `onDelete: Restrict`;\n- a create only the hidden relation could satisfy is refused (`COMPILER_HIDDEN_REQUIRED_FIELD`): expose the reference\n field.\n\nStill yours: a key update follows the owner's foreign key (`ON UPDATE`), and `ctx.$sql` or a stage calling Prisma can\nstill write the table.\n\nKeep Prisma from migrating it too: list it in `prisma.config.ts` as an external table (Prisma 7.10:\n`experimental: { externalTables: true }`, `tables: { external: [\"auth.user\"] }`), and give the model its\n`@@schema(\"auth\")` when the owner keeps it in another PostgreSQL schema. If the owner's own `_prisma_migrations` table\nsits in a schema your datasource lists, list it as external as well, or `prisma migrate` stops with\n`migration persistence is not initialized`.\n\n## Limits\n\n| Limit | Default | Exceeded |\n|---|---|---|\n| `maxRequestBytes` | `1048576` | `A2010` |\n| `maxNestingDepth` | `12` | `A2009` / `V1013` |\n| `maxListLimit` | `250` | `A2009` / `V1014` |\n| `maxTransactionOperations` | `20` | `A2009` / `V1014` |\n| `maxBooleanNodes` | `50` | `A2009` / `V1015` |\n| `maxJsonDepth` | `64` | `A2009` / `V1021` |\n| `maxAll` | `10` | operations in one `POST /_all`: `A2009` / `V1014` (the generated client splits; in-process `all` ignores it) |\n| `maxAllConcurrency` | `4` | operations of one set running at once; the rest wait (at least `1`) |\n\n## Caching reads\n\nA read with a `cache` stage is also served as a `GET` the browser, or a CDN, may keep for `maxAge` seconds; the generated client sends it that way. Without one, every read is a `POST` and nothing caches it.\n\n```ts\n// src/resources/post.resource.ts\nimport { cache, defineResource } from \"@aventara/core\";\nimport { discovery } from \"../generated/aventara/discovery.artifact.js\";\n\ntype Model = typeof discovery.model;\n\nexport const post = defineResource<Model>()(\"Post\", {\n // Post.find.unique, .first, .count: each caller's own browser keeps its answer.\n pipelines: { cache: cache({ for: \"user\", maxAge: 30 }) },\n operations: {\n // Post.find.many: anonymous callers share one answer a CDN may keep.\n find: { many: { pipelines: { cache: cache({ for: \"anonymous\", maxAge: 60 }) } } },\n },\n});\n```\n\n- `cache({ for, maxAge })` is data, not a function that runs. Always write `for`, and write it first, even for the default `\"user\"`: it is the promise the read makes, and nothing checks it. This is the one exception to writing only what changes behaviour (SKILL.md rule 16).\n- `maxAge` is whole seconds.\n- Where: `pipelines.cache` at the root or the `client` layer (every find), a Resource (every find of it), its `find` family, or one `find` variant. Anywhere else (a write family or variant, `transactions`, the `application` or `agent` layer) fails startup with `CONFIG_INVALID_VALUE` at its path.\n- Precedence: the most specific level wins (variant, family, Resource, layer); at one level the `client` layer's stage replaces the root's. It never adds up.\n- A module that also imports React's `cache` (Next.js) imports ours as `import { cache as avCache } from \"@aventara/core\"`.\n- Adding, changing or removing a stage changes the contract hash: regenerate the frontend client.\n\n| `for` | Promise | A `200` carries |\n|---|---|---|\n| `\"user\"` (default) | The answer is this caller's own. | `Cache-Control: private, max-age=…`, `Vary: Authorization, Cookie` |\n| `\"anonymous\"` | Every anonymous caller gets the same answer. | `public` to an anonymous caller (a CDN may keep it), `private` to a signed-in one; `Vary: Authorization, Cookie` |\n| `\"everyone\"` | Every caller gets the same answer. | `Cache-Control: public, max-age=…`: a CDN hands it to anyone |\n\nPick the narrowest true one. `\"everyone\"` only when nothing makes the answer differ by caller: no guard, pipe, compute or policy that reads the caller, or a CDN serves one caller's answer to all. Anonymous means: with a policy, the principal it derives is `null`/`undefined`; without one, no `Cookie` and no `Authorization` header. Callers who sign in with a header of their own (`X-Api-Key`) need a policy, or no `\"anonymous\"`/`\"everyone\"`.\n\nAlso: the arguments land in the URL (server and proxy logs, history), so never cache a read whose arguments are secret; prefetches run the read and its stages; Aventara keeps no server-side copy; a stage tells the two routes apart by `ctx.$transport.method` (`\"GET\"` or `\"POST\"`). Behind a CDN: honor `Vary` (or bypass on `Cookie`/`Authorization`) and keep `Set-Cookie` off `GET <api>/_resources/…`.\n\n## Fields\n\n- Add a virtual field at the root: `definition: { type: { scalar: \"string\" }, nullable: false, list: false, lifecycle: [\"VIRTUAL\"], capabilities: { select: true } }` plus a `compute.read` stage. A write-only virtual input says `capabilities: { select: false, create: [], update: [] }`.\n- Type a `Json` column: `definition: { json: AvZ.object({ … }) }`; every write is checked against it and the client types follow it.\n- A `compute.create` stage makes its field optional on create, and must return a value when the caller sent none.\n- A `compute.read` stage with no `dependsOn` declares no field it reads, so it computes from the same inputs for every row: such a value usually belongs in response metadata or `ctx.$state`, not in a field.\n\n## Startup\n\nA wrong configuration throws one `AvConstructionError` before the server listens: `stage` (`configuration`, `discovery` or `contract-compilation`) and `diagnostics`, each `{ code, path, message, docs }`. `path` names the key; `reference/errors.md` explains each code.\n",
|
|
5
|
-
"reference/recipes.md": "# Recipes\n\nEvery block here compiles against the Aventara release this skill was written for. Model names (`User`, `Post`, `Order`, `OutboxEvent`) are examples: use your schema's.\n\n## The signed-in user, for every stage\n\nDeclare what the request state holds, once:\n\n```ts\n// src/aventara.state.ts\ndeclare module \"@aventara/core\" {\n interface AvState {\n user?: { id: number; role: \"member\" | \"admin\" };\n }\n}\nexport {};\n```\n\nSeed it from your auth middleware's result, in `src/app.module.ts`:\n\n```ts\n// src/app.module.ts\nimport { Module } from \"@nestjs/common\";\nimport { AventaraModule } from \"@aventara/nest\";\nimport { createPrismaAdapter } from \"@aventara/prisma7-adapter\";\nimport type { Request } from \"express\";\nimport adapter from \"./aventara.adapter.js\";\nimport { aventaraConfig } from \"./aventara.config.js\";\nimport { PrismaModule, PrismaService } from \"./prisma.service.js\";\n\ntype AuthedRequest = Request & { user?: { id: number; role: \"member\" | \"admin\" } };\n\n@Module({\n imports: [\n PrismaModule,\n AventaraModule.forRootAsync({\n imports: [PrismaModule],\n inject: [PrismaService],\n useFactory: async (prisma: PrismaService) => ({\n config: {\n ...aventaraConfig,\n adapter: await createPrismaAdapter({ ...adapter, client: prisma }),\n },\n state: (req: AuthedRequest) => ({ user: req.user }),\n }),\n }),\n ],\n})\nexport class AppModule {}\n```\n\nServer code passes it per call: `framework.application.Post.create.one({ data }, { state: { user } })`. Guards read it:\n\n```ts\nimport { AvError, type AvGuard } from \"@aventara/core\";\n\nexport const requireUser: AvGuard = (ctx) => {\n if (ctx.$state.user === undefined) throw new AvError(\"A4000\", \"Sign in first.\");\n return true;\n};\nexport const adminOnly: AvGuard = (ctx) => ctx.$state.user?.role === \"admin\";\n```\n\nPut `requireUser` in `client: { pipelines: { guards: [requireUser] } }` (remote callers only), and `adminOnly` where it applies (`client: { resources: { Post: { operations: { delete: { pipelines: { guards: [adminOnly] } } } } } }`).\n\n## A Resource in its own file, with a server-filled owner\n\n`Post.authorId` is required; remote callers must not choose it. `compute.create` fills it from the signed-in user, which also makes it optional on create; the `client` exemption takes both `authorId` and the `author` relation out of the remote create and update inputs. Hiding only `authorId` is not enough: `author: { $connect: … }` would still let a caller set or move the owner.\n\n```ts\n// src/resources/post.resource.ts\nimport { AvError, defineResource } from \"@aventara/core\";\nimport { AvZ } from \"@aventara/core/avz\";\nimport { discovery } from \"../generated/aventara/discovery.artifact.js\";\n\ntype Model = typeof discovery.model;\n\nconst slugify = (text: string) =>\n text.toLowerCase().trim().replace(/[^a-z0-9]+/g, \"-\").replace(/^-|-$/g, \"\");\n\nexport const post = defineResource<Model>()(\"Post\", {\n fields: {\n title: { pipelines: { validators: [AvZ.regex(/\\S/), AvZ.maxLength(200)] } },\n slug: {\n pipelines: {\n compute: {\n create: ({ data }) => slugify(String(data.title)),\n update: ({ data, value }) => (data.title === undefined ? value : slugify(String(data.title))),\n },\n },\n },\n authorId: {\n pipelines: {\n compute: {\n create: ({ value, ctx }) => {\n const user = ctx.$state.user;\n if (user !== undefined) return user.id;\n if (typeof value === \"number\") return value;\n throw new AvError(\"A4000\", \"Sign in first.\");\n },\n },\n },\n },\n },\n});\n```\n\nRun `npm run aventara:prepare`: it adds `post` to `aventaraResources` (`resources.generated.ts`, beside the discovery artifact), which the configuration's `resources: aventaraResources.root` already takes, so `aventara.config.ts` needs no edit. Two files defining the same Resource stop `aventara:prepare` with both named. A Resource that gives a layer a block (`defineResource<Model>()(\"Post\", { … }, { client: { … } })`) needs that layer's line once: `client: { resources: aventaraResources.client }`. Take `authorId`, `author` and `slug` out of the remote inputs in the `client` layer's `exemptions` (next recipe): exposure lives in a layer's `exemptions`, never in a `defineResource` block.\n\n## Exposure: what each caller may reach\n\n```ts\n// in aventaraConfig (src/aventara.config.ts)\nexemptions: {\n resources: {\n User: { fields: { passwordHash: [\"select\", \"filter\", \"order\"] } }, // write-only everywhere\n },\n},\nclient: {\n exposed: true,\n exemptions: {\n resources: {\n AuditLog: true, // remote callers never see it\n User: { operations: { delete: true }, fields: { internalNotes: true } },\n Post: { fields: { authorId: [\"create\", \"update\"], author: [\"create\", \"update\"], slug: [\"create\", \"update\"] } }, // filled by the server; a caller writing `author` gets A2004 (V1006)\n },\n },\n},\n```\n\n## Values: validators and a field pipe\n\n```ts\n// in a defineResource block's fields, or resources.<R>.fields\nemail: {\n pipelines: {\n pipes: [(value) => (typeof value === \"string\" ? value.trim().toLowerCase() : value)],\n validators: [AvZ.email(), AvZ.maxLength(254)],\n },\n},\n```\n\nValidators are `AvZ` checks only (`minLength`, `maxLength`, `regex`, `email`, `uuid`, `gte`, `lte`, `multipleOf`, `maxItems`, …). A rule across two fields, or one that reads the database, is a guard.\n\n## An outbox event that commits with the order\n\n```ts\n// src/resources/order.resource.ts\nimport { defineResource } from \"@aventara/core\";\nimport { discovery } from \"../generated/aventara/discovery.artifact.js\";\n\ntype Model = typeof discovery.model;\n\nexport const order = defineResource<Model>()(\"Order\", {\n operations: {\n create: {\n one: {\n forceTransaction: true,\n pipelines: {\n hooks: {\n after: [\n async (result, ctx) => {\n // The result holds what the caller selected: narrow it, never cast it.\n const id = typeof result === \"object\" && result !== null && \"id\" in result ? result.id : undefined;\n // Runs inside the order's transaction: a throw rolls the order back.\n if (typeof id !== \"number\") throw new Error('create orders with select: [\"id\"]');\n await ctx.OutboxEvent.create.one({\n data: { topic: \"order.created\", payload: { orderId: id } },\n select: [\"id\"],\n });\n },\n ],\n },\n },\n },\n },\n },\n});\n```\n\nOrders are created by server code, which selects `id`: `framework.application.Order.create.one({ data, select: [\"id\"] })`. Hide both from remote callers: `client.exemptions.resources: { OutboxEvent: true, Order: { operations: { create: true } } }`. A consumer reads new `OutboxEvent` rows (or, on PostgreSQL with a queue, the hook sends the event's id with ``ctx.$sql`…` ``).\n\n## Server code\n\nNest's injection carries no type argument, so a module of its own exports the framework's type once:\n\n```ts\n// src/aventara.framework.ts\nimport { createFramework } from \"@aventara/core\";\nimport { createPrismaAdapter } from \"@aventara/prisma7-adapter\";\nimport adapter from \"./aventara.adapter.js\";\nimport { aventaraConfig } from \"./aventara.config.js\";\nimport type { PrismaService } from \"./prisma.service.js\";\n\nexport const buildFramework = async (prisma: PrismaService) =>\n createFramework({ ...aventaraConfig, adapter: await createPrismaAdapter({ ...adapter, client: prisma }) });\nexport type AppFramework = Awaited<ReturnType<typeof buildFramework>>;\n```\n\n```ts\n// src/post-reader.service.ts\nimport { Injectable } from \"@nestjs/common\";\nimport { AvNotFoundError } from \"@aventara/core\";\nimport { InjectFramework } from \"@aventara/nest\";\nimport type { AppFramework } from \"./aventara.framework.js\";\n\n@Injectable()\nexport class PostReader {\n constructor(@InjectFramework() private readonly framework: AppFramework) {}\n\n async findOrNull(id: number) {\n try {\n return await this.framework.application.Post.find.unique({ where: { id }, select: [\"id\", \"title\"] });\n } catch (error) {\n if (error instanceof AvNotFoundError) return null;\n throw error;\n }\n }\n}\n```\n\n`framework.application` calls resolve the data and throw the code's `AvError` subclass; `find.first` resolves `null` instead of throwing. Several writes that must succeed together: `framework.transaction([framework.appTx.Post.create.one(…), …])`, or `ctx.$transaction(async (tx) => …)` inside a stage.\n\n## Agents (MCP)\n\n```ts\n// in aventaraConfig\nmcp: \"/mcp\",\nagent: {\n exposed: true,\n exemptions: { resources: { User: { operations: { delete: true } } } },\n},\n```\n\nWithout `mcp.authorization` the endpoint answers callers on the same machine only; set `authorization: { servers, verify }` before deploying. A guard can tell an agent apart: `ctx.$origin === \"agent\"`.\n",
|
|
6
|
-
"reference/stages.md": "# Stages reference\n\n## The stages\n\n| Stage | Key | Receives | Returns |\n|---|---|---|---|\n| Guard | `guards` | `(ctx)` | `true`/`undefined` to allow, `false` to deny (`A4001`); or throw an `AvError` |\n| Pipe | `pipes` | `(args, ctx)` | new arguments, or `undefined` to keep them; the only stage that changes arguments |\n| Hook | `hooks.before`, `hooks.after` | before `(ctx)`; after `(result, ctx)` | nothing |\n| Interceptor | `interceptors` | `(ctx, next)` | the result of `next()`, called exactly once |\n| Filter | `filters` | `(error, ctx)` | an `AvError` to answer with, or `undefined` to pass it on |\n| Validators (field) | `fields.<f>.pipelines.validators` | `AvZ` checks | data, part of the contract |\n| Field pipe | `fields.<f>.pipelines.pipes` | `(value)` | the new value; no `ctx` |\n| Compute (field) | `fields.<f>.pipelines.compute` | `create`/`update`: `({ value, data, ctx })`; `read`: `{ dependsOn, compute: ({ record }) }` | the field's value |\n| Field write guard | `fields.<f>.pipelines.guards` | `(value, ctx)` | `false` refuses when the caller sends the field |\n\nA relation field takes write guards only. Stage types to annotate with: `AvGuard`, `AvPipe`, `AvBeforeHook`, `AvAfterHook`, `AvInterceptor`, `AvFilter`, `AvFieldPipe`, `AvFieldGuard`, and `AvTransactionGuard`, `AvTransactionPipe`, `AvTransactionBeforeHook`, `AvTransactionAfterHook`, `AvTransactionInterceptor`, `AvTransactionFilter`. Pass your model (`AvAfterHook<Model>`, `type Model = typeof discovery.model`) when the stage calls `ctx.<Resource>`.\n\n## Levels\n\n| Level | Key | Runs for |\n|---|---|---|\n| Layer | `pipelines` | every operation of the layer |\n| Resource | `resources.<R>.pipelines` | every operation of R |\n| Family | `resources.<R>.operations.<family>.pipelines` | every variant of the family |\n| Variant | `resources.<R>.operations.<family>.<variant>.pipelines` | that operation; `ctx.$args` is typed |\n| Field | `resources.<R>.fields.<f>.pipelines` | field f's value |\n| Resource transactions | `resources.<R>.transactions.pipelines` | once per transaction with a top-level step on R |\n\nThe root's stages run for every origin; an overlay's only for its own (`ctx.$origin`: `\"application\"`, `\"client\"` or `\"agent\"`). Register a stage once, at the outermost level it applies to: a root stage copied into an overlay runs twice. Registering a stage does not expose an operation.\n\n## Order\n\n```text\nframework validation (required create fields included; a field compute.create fills is not required yet)\n-> guards layer -> Resource -> family -> variant (root, then overlay, at each), then field write guards\n-> pipes the same levels, then field pipes\n-> compute create or update (write compute)\n-> re-check Aventara's own checks on the pipe and compute output (A3004, A3000), then field validators and Json shapes on every value, computed included (A2004)\n-> interceptors enter, outer to inner\n-> hooks.before\n-> operation one adapter call, then compute read on the returned rows\n-> hooks.after\n-> interceptors unwind\non a failure after validation: filters, inner to outer; the first that handles it wins\n```\n\nA call refused by a guard, the re-check or a validator runs no interceptor and no hook. A `unique` operation that matches no row fails at the operation (`AvNotFoundError`, `A2003`); no `after` hook runs.\n\nWith `forceTransaction` (or as a step of a transaction plan), guards and pipes run before the transaction, and validators judge the literal values no write compute changes; a refusal there opens nothing. Then: `BEGIN -> the Resource's transaction stages -> write compute -> re-check (computed and $ref values included) -> interceptors -> hooks.before -> adapter call -> hooks.after -> interceptors -> COMMIT`. A refusal inside rolls the transaction back. The hooks' `ctx` calls and `ctx.$sql` join it; the guards' and pipes' calls do not.\n\n## Rules that are easy to miss\n\n- `ctx` calls (`ctx.<Resource>.<family>.<variant>(args)`) are raw: validation, field pipes, compute and validators run; guards, pipes, hooks, interceptors, filters and transaction stages do not. They are application-origin: the `application` overlay's exposure applies, the `client` overlay's does not. They resolve data directly and throw the `AvError` subclass of a failure.\n- `ctx.$args` is frozen. A pipe returns new arguments; an `after` hook gets a frozen result. To answer with a different result, use an interceptor. A `Date` or bytes value cannot be frozen, so each stage gets its own copy: changing one in place changes nothing another stage sees, what is written or what the caller receives.\n- `ctx.$state` is the only thing a stage writes in place: one object per request, shared by every stage and inherited by `ctx` calls. Compute can read it, not write it.\n- `compute.read` gets the row (`record`) frozen, with its own copy of each `Date` or bytes value: return the field's value. Writing into `record` throws and answers `A3000`; the answer is built from the row as read.\n- Compute gets a read-only `ctx`: `find.*` calls only, no writes, no `$sql`, no `$transaction` (reaching one answers `A3000`). Effects on other rows belong in an `after` hook under `forceTransaction`.\n- A required create field the caller left out is refused before the guards; no pipe can fill it. Use `compute.create` or a database default.\n- Validators run after write compute, so they judge the computed value too: a `compute.create` result that fails a validator answers `A2004`. Aventara's own re-check comes first: a pipe that returns a wrong type or an unknown key, or drops a required field, answers `A3004`, as does a pipe or compute that writes a reference twice; a compute that returns a wrong type or a value its field's `format` refuses (a relative path for `format: \"url\"`), or leaves a required field missing, answers `A3000`.\n- Validators do not judge a directive's operand (`{ $increment: 500 }`); `$push` entries are judged.\n- `pipelines.cache` runs nothing: it is data that lets a read be served as a cacheable `GET` (`reference/config.md`, Caching reads), and the one `pipelines` key that does not add up across levels. Every stage runs on that `GET` as on the `POST`; `ctx.$transport.method` tells them apart.\n- Calls grouped into one `POST /_all` each run their own full pipeline, exactly as alone (`reference/client.md`).\n- A Resource reached only through a nested write runs that Resource's guards and field stages, not its transaction stages, hooks or interceptors.\n- Without a transaction, a write has committed when its `after` hook runs.\n- A guard or pipe that throws a non-`AvError` answers `A3000` and the real error goes to `diagnostics`; throw `new AvError(code, message)` to answer a code. A thrown `A2004` may carry `issues: [{ path: [\"arguments\", …], code: \"V1001\", message }]`.\n- A pipe's output is re-checked against the server's own contract before compute and validators see it: returning a field the Resource does not have, or a wrong type, answers `A3004`. Name pipes (`function scopeToTenant(…)`) so the server log names them.\n"
|
|
2
|
+
"SKILL.md": "---\nname: aventara\ndescription: Build and change this project's Aventara API (NestJS or Next.js, Prisma, @aventara/core). Use when editing src/aventara.config.ts, src/aventara/config.ts, src/resources/*.resource.ts or prisma/schema.prisma; when exposing or hiding Resources, fields or operations (exposed, exemptions); when adding guards, pipes, field validators (AvZ), computed fields (compute.create/update/read), hooks, interceptors, filters, transactions, forceTransaction, an outbox, ctx.$state or the agent layer; when caching reads (cache stage, avCache) or loading several calls at once (avClient.all, POST /_all, avLazy, grouping, limits.maxAll); and when an Aventara code appears (A2004, V1000, CONFIG_*, COMPILER_*, AvConstructionError, AvValidationError).\n---\n\n# Aventara\n\nAventara turns the Prisma schema into a typed API: every model is a Resource with standard operations (`find`, `create`, `update`, `delete`, `upsert`), served at `api` (`/api`) and called from the generated frontend client. You configure the models' operations; you do not wrap them in controllers. Logic that is not one of those operations (a health check, a report, a webhook) goes in your own Nest controller or service, which calls `framework.application` and sits behind Nest guards. Written for `@aventara/core` {{CORE_VERSION}}.\n\nThere is one way to do each task. Follow these rules; the reference files hold the detail:\n\n- `reference/config.md`: the configuration tree, where every key goes, exposure, limits.\n- `reference/stages.md`: which stage fits a task, the order stages run in, the rules.\n- `reference/recipes.md`: complete, type-checked examples for the common tasks.\n- `reference/client.md`: calling the API from frontend and server code: what a call returns, which error class each code throws (`error.cause.issues`), transactions with `$ref`, several calls in one request (`avClient.all`, `POST /_all`), hooks (`onCall`, `onSuccess`, `onError`), aborting, `avLazy`, `grouping: false`, cached reads. A separate frontend gets this guide and `reference/client-setup.md` as its own skill, `.claude/skills/aventara-client/`, from `avclient init` (refresh it with `avclient skill`).\n- `reference/client-setup.md`: a separate frontend's `framework.client.ts`, regenerating its client, and the stale-client error (`A2005`).\n- `reference/avz.md`: every `AvZ` check and Json shape builder.\n- `reference/errors.md`: every code (`A…`, `V…`, `CONFIG_…`, `COMPILER_…`) with its cause and fix.\n\nFor anything these files do not cover, read the docs, which are Markdown for agents: `https://aventara.razor34.dev/framework/llms.txt` lists every page, `https://aventara.razor34.dev/framework/markdown/<slug>` is one page; Aventara Access has the same under `/access/` instead of `/framework/`. Read the page before guessing.\n\nIf `@aventara/access` is installed, write its own skill with `{{EXEC}} avaccess skill` (into `.claude/skills/aventara-access/`) and follow it for policies and the `access` key.\n\n## Where things are\n\n| File | What it is |\n|---|---|\n| `prisma/schema.prisma` | The data model. Every model is a Resource, named exactly as the model. |\n| `src/aventara.config.ts` | `aventaraConfig`: the configuration, a plain value `as const`, without `adapter`. Keep the `as const`. |\n| `src/aventara.adapter.ts` | The adapter's description, `describePrismaAdapter({ discovery, provider, driver })`. |\n| `src/resources/<name>.resource.ts` | One Resource's configuration, an exported `defineResource(...)`. `aventara:prepare` gathers every one into `aventaraResources`: no spread to write, no config edit. |\n| `src/generated/aventara/` | Generated by `aventara:prepare`: `discovery.artifact.ts` and, beside it, `resources.generated.ts`, `computed-fields.generated.ts` and `contracts.generated.ts`. When Prisma's client output is `src/generated` itself, these four live in `src/aventara/` instead (the `--out` of `aventara-prisma7-generate` in the `aventara:prepare` script, and where `src/aventara.config.ts` imports `aventaraResources` from). Never edit them. |\n| Prisma's client output | Generated by `aventara:prepare` (`prisma generate`), at your schema generator's `output`. Never edit it. |\n| `src/app.module.ts` | `AventaraModule.forRootAsync(...)` builds the framework from `aventaraConfig` and the adapter (the description plus the Prisma client). |\n\n### In a Next.js project\n\nA Next.js app (`@aventara/next`, App Router under `src/app/`) keeps everything Aventara in one folder, `src/aventara/`. Everything else on this page applies; read these paths for the NestJS ones above.\n\n| File | What it is |\n|---|---|\n| `src/aventara/config.ts` | `aventaraConfig`, as `src/aventara.config.ts` above. Its `api` (`/api`) is the folder of the route file. |\n| `src/aventara/adapter.ts` | The adapter's description, as `src/aventara.adapter.ts` above. |\n| `src/aventara/server.ts` | `Aventara({ ...aventaraConfig, adapter, prebuilt, state })`, exporting `handlers`, `avClient`, `avApp` and `ready`. Import it as `@/aventara/server`, in server code only. It replaces `app.module.ts`: `state` seeds `ctx.$state` from each request's headers (`reference/recipes.md`, The signed-in user). |\n| `src/aventara/client.ts`, `src/aventara/universal.ts`, `src/aventara/generated/` | Generated by `aventara:prepare` (`aventara-build --client`): the browser client (`@/aventara/client`), the universal entry and the generated modules. Never edit them; they are in `.gitignore`. |\n| `src/app/api/[...aventara]/route.ts` | Exports every method of `handlers`. One line; keep it. |\n| `src/instrumentation.ts` | Awaits `ready()` on the Node.js runtime, so a bad configuration stops `next start`. |\n| `src/prisma.ts` | `prisma()`, the app's one Prisma client, made on first use. |\n| `next.config.ts` | Keeps `serverExternalPackages: [\"@aventara/core\", \"@aventara/prisma7-adapter\"]`: Node.js loads them once per server process, so every route and page shares one core, one Framework and one Prisma client. Keep the line. |\n\nServer Components and Server Actions call `avClient.<Resource>.<family>.<variant>(args)` (checked as the caller) or `avApp` (trusted code); Client Components call the generated `avClient` from `@/aventara/client`. There is no separate frontend to regenerate: `aventara:prepare` writes the browser client too, and `next build` runs it first. An `mcp` endpoint on Next.js needs `authorization`.\n\nData a Server Component prefetches for a Client Component goes through TanStack Query, with the query written once over `@/aventara/universal` (it runs on both sides): the server dehydrates it with `avDehydrate` from `@aventara/next`, the browser revives it with `avHydrate` from `@/aventara/client` (`Date`, `bigint`, `Uint8Array` and `AvDecimal`, which React cannot pass as they are), and the Client Component reads it through the same query, which is where its type comes from. `avHydrate` returns `unknown`: never hand its output to a component as typed props. The pattern: `reference/recipes.md`, Prefetched data in a Client Component. Transactions on the server: `avClient.tx` with `avClient.transaction([...])` (the client layer), or `avApp.appTx` with `avApp.transaction([...])` (the application layer). They need the built Framework, which `instrumentation.ts`'s `await ready()` gives; elsewhere, `await ready()` first.\n\nA call reads the database, so Next.js runs it at request time (Cache Components is on: `cacheComponents: true`). Give a page that calls `avClient` or `avApp` one of: the calling component inside `<Suspense fallback={…}>` (or a `loading.tsx`), `\"use cache\"` on a function that makes the call (the result is shared by every visitor: never with `state`), or `export const instant = false` to render the whole page at request time. Without one, `next build` fails naming these three: add one (no `connection()` is needed).\n\n## Rules\n\n1. **Change the schema, then prepare.** After editing `prisma/schema.prisma` run your migration (`{{EXEC}} prisma migrate dev` or `{{EXEC}} prisma db push`), then `{{RUN}} aventara:prepare`. Run `aventara:prepare` again after adding or removing a Resource file, and after adding, moving or removing a `compute` stage; the server warns `generated types are stale` (`AVENTARA_STALE_GENERATED_TYPES`) until you do. Restart the server and regenerate the frontend client (`{{RUN}} avclient:generate` in the frontend) whenever the client contract changed: the schema, exposure, field validators, limits, operations, the client layer's `transactionsStyle`, a Json field's `AvZ` shape, a `cache` stage, or which fields compute fills.\n2. **Configure, never wrap.** Do not write a Nest controller, service or REST route for a model. Every model already has its operations; shape them with `exposed`/`exemptions` and pipelines. Server code calls `framework.application.<Resource>.<family>.<variant>(args)`.\n3. **Expose with exemptions.** `exposed: true` (the root's default; an overlay that lists `exemptions` must say it) exposes everything the adapter offers and `exemptions` lists what to hide; `exposed: false` exposes nothing and `exemptions` lists what to expose. Hide at the root what no caller may reach; hide in `client` what only your server code may reach. An overlay (`application`, `client`, `agent`) only narrows the root's view: to expose something in one overlay only, expose it at the root and hide it in the other.\n4. **Pick the stage from the table below.** Each task has one stage. Do not authorize in an interceptor, transform in a guard, or write other rows from a compute stage.\n5. **Never cast types away.** No `as`, no `!`, no `any`, no `@ts-ignore` / `@ts-expect-error` to silence an Aventara type. The types are the contract: a type error names the exact key that is wrong (a misspelled field, an overlay granting what the root hid, a missing required field). Fix the key. One exception: the `ctx` calls and `ctx.$args` inside a stage are typed from your model with no configuration applied, so a Json field's `AvZ` shape and a virtual input are not in those types (a field the root's or the `application` overlay's `compute.create` fills is optional in a `ctx` create, from `computed-fields.generated.ts`). There, narrow the value with a type guard (`typeof x === \"object\" && x !== null && \"en\" in x`); never cast. To get typed arguments in a stage, register it on the operation (`resources.<R>.operations.<family>.<variant>.pipelines`) inside a `defineResource` block; to type `ctx.<Resource>` calls, give the stage type your model (`AvAfterHook<Model>`).\n6. **Annotate stages in the `as const` config.** An inline arrow in `aventaraConfig` has no contextual type (TS7006). Declare it with its stage type (`const requireUser: AvGuard = (ctx) => …`) or move it into a `defineResource` block, where stages are typed from where they sit.\n7. **Fill server-side values with `compute.create`.** A required field the caller must not send (an owner id, a slug, a hash) gets `pipelines.compute.create`; that also makes it optional on create. A pipe cannot fill a missing required field: the framework refuses the create (`A2004`, `V1000`) before any pipe runs. Hiding a required field from a layer that still creates the Resource needs a `compute.create`, or startup fails with `COMPILER_HIDDEN_REQUIRED_FIELD`.\n8. **Validate values with `AvZ`.** Length, range, pattern, format and closed-set rules are field validators (`AvZ.maxLength(80)`, `AvZ.email()`, `AvZ.oneOf([\"draft\", \"live\"])`); a closed set the types should name is a Prisma `enum` instead, and an enum field takes no validator, an enum list only a length (`AvZ.minItems`, `AvZ.maxItems`); the type error says so. They are part of the contract, so the generated client checks them before sending. A rule across the call's fields is a guard or a pipe. A rule that reads the database (an order must still be open) can change between a guard's check and the write, because guards run before the transaction opens: re-check it in `hooks.before` with `forceTransaction: true` (or in the Resource's transaction stages), where a throw rolls back. A guard may check it first for a quick refusal, never as the only check. When two requests can race on one row, make the write the check: `update.first` with the expected state in its `where` (`reference/recipes.md`, Compare-and-set).\n9. **Read the user from `ctx.$state`.** Declare it once (`declare module \"@aventara/core\" { interface AvState { user?: … } }`), set it from `AventaraModule`'s `state` option (Next.js: `Aventara`'s `state`) for remote requests or a call's `{ state }` option (server code), and read `ctx.$state.user` in every stage. Never parse headers again in each stage. Nest guards, interceptors and pipes (`APP_GUARD`, Passport's `AuthGuard`, `@nestjs/throttler`) never run on Aventara's routes (`<api>/_*`), and on Express neither does `MiddlewareConsumer` middleware: only `app.use(...)` does. Find the user in `app.use` middleware and hand it over with `state`; authorize in Aventara guards. A `state` function may throw `new AvError(\"A4001\", \"…\")` to refuse with `403`; one that rejects with an object whose `status` or `statusCode` is the number `401` or `403` (Better Auth's `APIError` for a bad API key) answers `401 A4000` or `403 A4001` too, on HTTP and in-process calls, with nothing of the rejection sent. A thrown `AvError` answers its own code; on HTTP any other rejection is `500 A3000`, and in process it rejects the call unchanged. There is no built-in rate limit: add one with `app.use(rateLimit(…))`. CORS is not access control.\n10. **Side effects that must commit with the write: `forceTransaction` + an `after` hook.** Set `forceTransaction: true` on the operation and write the related rows (an outbox event, a counter) through `ctx.<Resource>` or `ctx.$sql` in its `after` hook. Without it the write has already committed when the hook runs.\n11. **Errors are codes.** Branch on `code` or the error class (`AvNotFoundError`, `AvValidationError`, …), never on a message. Throw `new AvError(\"A4000\", \"Sign in first.\")` from a stage to answer a code. Which class each code throws: `reference/client.md`, Errors. Look up any code in `reference/errors.md` (or `AvErrorCatalog[code]` from `@aventara/core`); a startup diagnostic carries its `docs` URL.\n12. **Do not edit generated files.** Prisma's client, the files beside the discovery artifact (`src/generated/aventara/`, or `src/aventara/` when Prisma's client output is `src/generated`) and the frontend's generated client are rebuilt by `aventara:prepare` and `avclient generate`.\n13. **The configuration runs at build time.** `aventara:prepare` ends with `aventara-build`, which runs `src/aventara.config.ts` without a database client and compiles the Contracts. Keep it a value with no `adapter`, and keep its top level free of side effects (no connection, no request). A `CONFIG_…` or `COMPILER_…` problem fails `aventara:prepare` with the diagnostics startup would print.\n14. **Load together; write atomically in a transaction.** Calls made in the same tick already go out as one `POST <api>/_all`: use `Promise.all`, or `avClient.all({ key: call }, { settle, signal })` for one typed value per key (strict by default; `settle: true` never rejects for one key). It is not atomic: writes that must hold together, or use another's result, go in `avClient.transaction([...])`. Never write a batching route or loader. Details: `reference/client.md`.\n15. **Cache a read with the `cache` stage, `for` written first.** `pipelines: { cache: cache({ for: \"user\", maxAge: 60 }) }` on a `find` (root or `client` layer, Resource, `find` family or variant; the most specific wins). `for`: `\"user\"` (the default: only the caller's browser keeps it), `\"anonymous\"` (a CDN shares it among anonymous callers) or `\"everyone\"` (a CDN shares it with every caller): pick the narrowest that is true. Write `for` even when it is the default `\"user\"`: it states who the answer is for (the one exception to rule 16). Where React's `cache` is imported too, `import { cache as avCache } from \"@aventara/core\"`. Details: `reference/config.md`, Caching reads.\n16. **Write only what changes behaviour.** Leave out what the framework already does: no option set to its default value (no `limits: { maxAll: 10 }`, no `grouping: true`, no `extends: \"client\"` on `agent`, no `exposed: true` at the root), no config key, stage or wrapper that repeats a built-in, and no type annotation the inference already gives (a stage inside a `defineResource` block is typed from where it sits). One exception: `cache({ for, maxAge })` always writes `for`, even the default `\"user\"`, because it reads as a sentence and states the audience.\n\n## Which stage\n\n| You want to | Use | Where it goes |\n|---|---|---|\n| Check a permission | a guard | `pipelines.guards`, at the layer, a Resource, a family or a variant |\n| Refuse a write to one field | a field write guard | `resources.<R>.fields.<f>.pipelines.guards` |\n| Normalize input (trim, lower-case, default, scope a query) | a pipe, or a field pipe for one value | `pipelines.pipes`, or `resources.<R>.fields.<f>.pipelines.pipes` |\n| Enforce a rule on a value (length, range, pattern, format) | a validator | `resources.<R>.fields.<f>.pipelines.validators` |\n| Fill in or derive a field's value | compute | `resources.<R>.fields.<f>.pipelines.compute` (`create`, `update`, `read`) |\n| React after a write (event, audit row, counter) | an `after` hook writing through `ctx`, with `forceTransaction` when it must commit with the write | `pipelines.hooks.after` |\n| Wrap execution (timing, a server-side cache, retries, `AsyncLocalStorage`) | an interceptor | `pipelines.interceptors` |\n| Let the browser or a CDN keep a read's answer | `cache({ for, maxAge })` | `pipelines.cache` on a `find`: layer, Resource, family or variant |\n| Turn an error into an answer | a filter | `pipelines.filters` |\n| Check a whole transaction | the Resource's transaction stages | `resources.<R>.transactions.pipelines` |\n| Share request information (the signed-in user) | `ctx.$state` | `AventaraModule`'s `state` option, or a call's `{ state }` |\n| Call another operation from a stage | `ctx.<Resource>.<family>.<variant>(args)` | the `ctx` every stage gets (compute can only read) |\n\nOrder: framework validation → policy admission (with a policy) → guards → pipes (operation, then field; then a nested write's targets') → field validators and Json shapes on every value the caller sent, a field with a compute stage included (`A2004`) → write compute (create/update) → re-check (Aventara's checks on pipe and compute output: `A3004`, `A3000`; then validators and Json shapes on what compute set or changed: `A2004`) → policy write check (with a policy) → interceptors → `hooks.before` → the operation (then compute read) → `hooks.after` → interceptors unwind → output validation; filters handle a failure. With `forceTransaction` or in a plan, guards, pipes and the validators of the caller's values run before `BEGIN` (`reference/stages.md`). `ctx` calls are raw: they run validation, field pipes, compute and validators, and none of your guards, pipes, hooks, interceptors or filters.\n\n## The shape of a configuration\n\n```ts\n// src/aventara.config.ts\nimport { type AvGuard, AvError } from \"@aventara/core\";\nimport { aventaraResources } from \"./generated/aventara/resources.generated.js\";\n\nconst requireUser: AvGuard = (ctx) => {\n if (ctx.$state.user === undefined) throw new AvError(\"A4000\", \"Sign in first.\");\n return true;\n};\n\nexport const aventaraConfig = {\n api: \"/api\",\n // Root: hidden from every caller.\n exemptions: {\n resources: { User: { fields: { passwordHash: \"read\" } } },\n },\n // Every defineResource() in src/resources/, gathered by aventara:prepare.\n resources: aventaraResources.root,\n // Remote callers (HTTP, the generated client).\n client: {\n exposed: true,\n exemptions: { resources: { User: { operations: { delete: true } } } },\n pipelines: { guards: [requireUser] },\n },\n} as const;\n```\n\n`app.module.ts` joins the adapter to it: `config: { ...aventaraConfig, adapter: await createPrismaAdapter({ ...adapter, client: prisma }) }`, where `adapter` is the default export of `src/aventara.adapter.ts`.\n\n## Calling the API over HTTP\n\nThe generated client does all of this for you. To call the API by hand (curl, a REST tool, a test), each operation is `POST <api>/_resources/<Resource>/<family>/<variant>`, where `<Resource>` is the model's name exactly as in `prisma/schema.prisma` (`User`, not `users`). Send two headers: `Aventara-Protocol-Version: 1` and `Aventara-Contract-Hash`, the hash `GET <api>/_contract` serves (its body's `protocol.hash`, and its `ETag`). The JSON body holds the operation's arguments: a create sends `{ \"data\": … }`.\n\n```sh\nHASH=$(curl -s http://localhost:3000/api/_contract | node -pe 'JSON.parse(require(\"fs\").readFileSync(0, \"utf8\")).protocol.hash')\ncurl -X POST http://localhost:3000/api/_resources/User/create/one \\\n -H 'Content-Type: application/json' \\\n -H 'Aventara-Protocol-Version: 1' \\\n -H \"Aventara-Contract-Hash: $HASH\" \\\n -d '{ \"data\": { \"email\": \"ada@example.com\", \"name\": \"Ada\" } }'\n```\n\nIt answers `201` with `{\"data\":{\"email\":\"ada@example.com\",\"id\":\"cmhfa2k1x0000l8v4b2c9d7e3\",\"name\":\"Ada\"},\"code\":\"A1002\",\"cause\":null}`. Without the two headers the answer is `400 A2000`; a lower-case `user` is `404 A2001`; arguments outside `data` are `422 A2004`. After a schema or configuration change the hash changes and an old one is `409 A2005`: fetch it again. Several operations in one request: `POST <api>/_all` (`reference/client.md`, By hand).\n\n## Deploying\n\n- Migrations: `migrate dev` while developing, commit `prisma/migrations/`, and run `{{EXEC}} prisma migrate deploy` as a release step before the new server starts. `db push` is for development and prototypes only.\n- CI: `{{EXEC}} aventara-build --check` exits `1` when a generated file is missing or stale (`--check --client` on Next.js).\n- The server starts without a database: the contracts come from the discovery artifact, and a call that needs the database answers `A3001` until it is back.\n- Serverless and edge: pass `prebuilt: aventaraContracts` (from `contracts.generated.ts`) where the framework is created, so a cold start does not compile the Contracts. A file compiled from another configuration, model or core is not used: startup warns `AVENTARA_PREBUILT_STALE` and compiles the Contracts as without it. Run `aventara:prepare` in the build so the file is current.\n- NestJS, while developing: a local `mcp: \"/mcp\"` with no `authorization` lets your coding agent read the contract and call the API on the development database (`claude mcp add --transport http my-api http://localhost:3000/mcp`). It answers callers on the same machine only; set `mcp.authorization` before deploying. On Next.js, `mcp` without `authorization` is a type error, and building the Framework (`ready()`, the first request) refuses it: Next.js adds `x-forwarded-for` to every request, so the endpoint would refuse every caller.\n\n## Before you finish\n\n- `{{RUN}} typecheck` passes with no casts added (the scaffold's script: `tsc --noEmit`, after `next typegen` in Next.js).\n- The server starts: an `AvConstructionError` lists each problem as `{ code, path, message, docs }`; fix the key at `path`.\n- After a schema or compute change you ran `{{RUN}} aventara:prepare`, and the frontend regenerated its client. `Type 'AvModel' does not satisfy the constraint 'AvAdapterModel'` in every Resource file means prepare failed: run it and fix the first error it prints.\n- An answer of `500 A3000` hides the real error: it went to the configuration's `diagnostics` function (root key, `(d) => …`). Read it there.\n- Tests: `reference/recipes.md`, Testing.\n\nThese files are generated by `aventara` for this project's Aventara version. Refresh them after upgrading: `{{DLX}} @aventara/cli@<your @aventara/core version> skill`. A file you edited is kept, with a warning naming it, and stops receiving fixes (pass `--force` to replace it).\n",
|
|
3
|
+
"reference/avz.md": "# AvZ: field validators and Json shapes\n\n`AvZ` (from `@aventara/core/avz`) builds two things: checks for a field's `validators`, and the shape of a `Json` field. Both are data in the contract, so the server and the generated client run the same checks. A check that does not fit the field's type is a type error.\n\n## Checks\n\n| For | Checks |\n|---|---|\n| Numbers, `BigInt`, `Decimal` | `lt`, `lte`, `gt`, `gte`, `positive`, `nonnegative`, `negative`, `nonpositive`, `multipleOf`; the bound in the field's type: a number on an `Int` or `Float`, a `bigint` on a `BigInt` (`AvZ.gte(0n)`), either on a `Decimal` |\n| `DateTime` | `lt`, `lte`, `gt`, `gte` with a `Date` (`AvZ.gte(new Date(\"2020-01-01\"))`) |\n| Strings (on a list field, each entry) | `maxLength`, `minLength`, `length`, `regex`, `oneOf`, `includes`, `startsWith`, `endsWith`, `lowercase`, `uppercase` |\n| String formats | `email`, `url`, `uuid`, `guid`, `cuid`, `cuid2`, `ulid`, `nanoid`, `xid`, `ksuid`, `emoji`, `ipv4`, `ipv6`, `cidrv4`, `cidrv6`, `base64`, `base64url`, `jsonString`, `e164`, `creditCard`, `iban`, `jwt`, `iso.datetime()`, `iso.date()`, `iso.time()`, `iso.duration()` |\n| Lists | `maxItems`, `minItems` |\n| `Bytes` | `maxSize`, `minSize`, `size` |\n\n## Json shapes\n\n| Builder | Accepts |\n|---|---|\n| `AvZ.string()`, `AvZ.number()`, `AvZ.int()` (a safe integer), `AvZ.boolean()`, `AvZ.null()` | that scalar |\n| `AvZ.literal(value)`, `AvZ.enum([\"a\", \"b\"])` | exactly that value, or one of the list |\n| `AvZ.object({ … })` | an object with exactly these keys: another key is refused |\n| `AvZ.optional(shape)` | inside `object` only: a key that may be left out |\n| `AvZ.nullable(shape)` | the shape or `null` |\n| `AvZ.array(item)`, `AvZ.tuple([a, b])` | a list, or a list of exactly these items |\n| `AvZ.record(key, value)` | an object keyed by `AvZ.string()`, an `AvZ.enum` or a string `AvZ.literal` |\n| `AvZ.union([a, b])`, `AvZ.discriminatedUnion(\"kind\", [objectA, objectB])` | one of the options |\n\nA `string`, `number`, `int` or `array` shape takes checks with `.check(…)`; an array takes `minItems` and `maxItems` only. There is no `bigint` node: keep 64-bit amounts inside Json as strings. `AvZ.infer<typeof shape>` is the TypeScript type a shape describes.\n\n```ts\n// src/resources/post.resource.ts\nimport { defineResource } from \"@aventara/core\";\nimport { AvZ } from \"@aventara/core/avz\";\nimport { discovery } from \"../generated/aventara/discovery.artifact.js\";\n\ntype Model = typeof discovery.model;\n\nconst meta = AvZ.object({\n kind: AvZ.enum([\"article\", \"video\"]),\n tags: AvZ.array(AvZ.string().check(AvZ.maxLength(30))).check(AvZ.maxItems(10)),\n cover: AvZ.optional(AvZ.nullable(AvZ.string().check(AvZ.url()))),\n});\nexport type PostMeta = AvZ.infer<typeof meta>;\n\nexport const post = defineResource<Model>()(\"Post\", {\n fields: { meta: { definition: { json: meta } } },\n});\n```\n\nA shape is checked on every write and types the generated client. Changing one changes the contract: run `aventara:prepare` and regenerate the frontend client.\n",
|
|
4
|
+
"reference/client-setup.md": "# Setting up and regenerating the client\n\nA separate frontend calls the API through a client generated from the server's client contract. In a Next.js app with Aventara inside it, `aventara:prepare` writes the browser client (`@/aventara/client`) instead, and nothing on this page applies.\n\n## `framework.client.ts`\n\n`avclient init` writes it in the frontend's root, with the `avclient:generate` script and `@aventara/client` as a devDependency:\n\n```ts\n// framework.client.ts\nimport { defineClientConfig, env } from \"@aventara/client\";\n\nexport default defineClientConfig({\n entrypoint: env(\"AVENTARA_API_URL\"),\n generateAt: \"./src/api\",\n});\n```\n\n- `entrypoint` is the server's URL with its `api` path (`http://localhost:3000/api`), with no credentials in it. `env(\"NAME\")` reads the variable from the environment, then `.env.<mode>.local`, `.env.<mode>`, `.env.local` and `.env` (`mode` is `NODE_ENV`, else `development`). `avclient init` writes `AVENTARA_API_URL` into `.env`.\n- The generated client calls the `entrypoint` it was generated with. To build a client for staging or production, set `AVENTARA_API_URL` to that server and regenerate. `new AvClient({ entrypoint })` points one client at another deployment that serves the same contract (a test server, SSR).\n- `generateAt` is where the client goes: `avclient generate` owns `AvClient.ts` and `generated/` in it and touches nothing else there.\n- `tsconfigFile` names the tsconfig that compiles the client, only when it is not the nearest `tsconfig.json` above `generateAt` (Vite's `tsconfig.app.json`).\n- `accessOrigins`, only with Aventara Access on another origin than the entrypoint's: list that origin (`[\"https://auth.example.com\"]`). Without it `avclient generate` refuses with an error naming that origin and `accessOrigins`, because sign-in credentials would go there.\n\n## Regenerate after the server changes\n\nRun the frontend's `avclient:generate` script (it runs `avclient generate`) while the server runs: it reads the client contract from `entrypoint`. Regenerate after any change to the server's client contract: the schema, exposure, field validators, limits, operations, the client layer's `transactionsStyle`, a Json field's shape, a `cache` stage, or which fields compute fills.\n\nA client generated before such a change is refused on its next call with `409 A2005`, thrown as `AvContractMismatchError`. The fix is always the same: regenerate against the server the client calls. Never edit the generated files to make a call fit.\n\n- A failed run leaves the previous client as it was.\n- `avclient generate` lists anything in `AvClient.ts` or `generated/` it did not write and asks before replacing it; `--yes` answers yes. Where nobody can answer (stdin is not a terminal, as in CI), it refuses and changes nothing: pass `--yes` there.\n- Commit the generated client. Regenerating it needs a running server, so CI and teammates build from the committed copy.\n- The same contract always gives the same bytes.\n",
|
|
5
|
+
"reference/client.md": "# Calling the API\n\nHow frontend and server code call the operations: several calls in one round trip, aborting, `avLazy`, and how cached reads go out. The generated client is the default export of the file `avclient generate` writes (`api/AvClient.ts`, at the frontend's `generateAt`); in a Next.js app it is `@/aventara/client` in the browser and `@/aventara/server` on the server.\n\n- `reference/client-setup.md`: `framework.client.ts`, pointing the client at a server, regenerating it after the server changed, CI, and the stale-client error (`A2005`).\n\nFor anything not covered here, read the docs, which are Markdown for agents: `https://aventara.razor34.dev/framework/llms.txt` lists every page, `https://aventara.razor34.dev/framework/markdown/<slug>` is one page (`…/markdown/error-codes` explains every code).\n\n## The query language is not Prisma's\n\nThe types catch each of these; knowing them saves a round per guess.\n\n| Prisma | Aventara |\n|---|---|\n| `select: { id: true, title: true }` | `select: [\"id\", \"title\"]`; a relation is `include: { author: { select: [\"name\"] } }` |\n| `take`, `skip` | `limit`, `offset`; `cursor: { id }` needs an explicit `orderBy` (`V1009` without one) |\n| `_count` | `\"$count\"` in a `select` list, or `find.count({ where })` |\n| `connect`, `create`, `set` (relations) | `$connect`, `$create`, `$connectOrCreate`, `$disconnect`, `$set`, `$update`, `$delete`, `$upsert` |\n| `increment`, `push` | `{ $increment: 1 }`, `$decrement`, `$multiply`, `$divide`, `$push` |\n| `undefined` in a `where` (ignored) | refused (`V1001`): leave the key out |\n| `findMany`, `update` | `find.many`, `update.unique`; `update.first` writes one row a `where` matches |\n\n## What a call returns\n\nA call resolves to the data: the record, the list or the count. `find.first`, `update.first` and `delete.first` resolve `null` when no row matches; a `unique` operation that matches no row throws `AvNotFoundError` (`A2003`).\n\n| Prisma type | In arguments and results |\n|---|---|\n| `DateTime` | `Date` |\n| `BigInt` | `bigint` |\n| `Decimal` | `AvDecimal`, from the generated client: `new AvDecimal(\"12.50\")`, read with `toString()`; it does no arithmetic |\n| `Bytes` | `Uint8Array` |\n| `Json` | the JSON value as it is |\n\nThe client converts to and from the strings on the wire for you. To type a component's props, import the generated types: each Resource's (`Post`, `PostWhere`, `PostCreateData`, …, each only when the operation it belongs to is exposed) and each enum, as a type and an `as const` object.\n\n## Errors\n\nEvery outcome that is not a success throws. Branch on the class or on `error.code`, never on the message. Server code (`framework.application`, `ctx` calls) throws the same classes from `@aventara/core`.\n\n| Class | Codes | When |\n|---|---|---|\n| `AvValidationError` | `A2004`, `A2007`, `A2009` | The arguments break a rule, a transaction `$ref` is wrong, or a limit is crossed. |\n| `AvNotFoundError` | `A2003` | A `unique` operation matched no row. |\n| `AvConflictError` | `A2008`, `A2013`, `A2014` | A duplicate or a missing parent (`A2008`), or the row changed under the write (`A2013`, `A2014`: read again and retry). |\n| `AvAuthError` | `A4000`, `A4001`, `A4002` | Sign in first (`A4000`), or not allowed (`A4001`, `A4002`). |\n| `AvContractMismatchError` | `A2005` | The client is stale: regenerate it (`reference/client-setup.md`). |\n| `AvProtocolError` | `A2000`, `A2001`, `A2002`, `A2006`, `A2010`, `A2011`, `A2012` | A request the server cannot read or route. |\n| `AvInternalError` | `A3000`, `A3001`, `A3002`, `A3003`, `A3004` | The server failed; the real error is in its log. |\n| `AvTransportError` | none | No Aventara answer arrived: the network failed, or a proxy answered with its own page. `error.status` is the HTTP status, or `null`. |\n\n`AvError` is the base class of every class in the table but `AvTransportError`. A `$access` call (Aventara Access) may also throw `AvAccessError`, which is not an `AvError`.\n\nAn `AvValidationError` lists each problem in `error.cause.issues`: `{ path, code, message }`, plus `check` and `params` for a failed field validator (`check: \"max_length\"`, `params: { maximum: 80 }`). `path` is where in the call (`[\"arguments\", \"data\", \"email\"]`), so a form can show `message` beside the field `path` ends with. The client runs the server's field validators before sending: a value they refuse throws this same error, and nothing is sent.\n\n```ts\nimport avClient, { AvValidationError } from \"./api/AvClient\";\n\ntry {\n await avClient.User.create.one({ data: { email: \"not-an-email\" } });\n} catch (error) {\n if (!(error instanceof AvValidationError)) throw error;\n for (const issue of error.cause.issues ?? []) console.log(issue.path?.at(-1), issue.message);\n}\n```\n\n## Calls made together share one request\n\nCalls made in the same tick (before the microtask queue settles) go out as one `POST <api>/_all`, an Operation Set. Each call still resolves or throws on its own. Write nothing for it: no batching endpoint, no loader.\n\n```ts\nimport avClient from \"./api/AvClient\";\n\nconst [posts, published] = await Promise.all([\n avClient.Post.find.many({ where: { published: true }, select: [\"id\", \"title\"] }),\n avClient.Post.find.count({ where: { published: true } }),\n]); // one POST /api/_all\n```\n\n- Each call runs exactly as alone: validation, exposure, policy, limits and pipelines are those of its own route, and so is its answer.\n- Not atomic and in no order: the calls run concurrently, and a write that ran stays written whatever the others answered. Writes that must hold together, or a call that needs another's result, belong in a transaction (`avClient.transaction([avClient.tx.Post.create.one(…), …])`, `POST /_transactions`).\n- Reads and writes mix. A group larger than `limits.maxAll` (10) or `limits.maxRequestBytes` is split by the client.\n- Always sent alone: a call with `headers` or `requestId` in its options, a call or client with `grouping: false`, a transaction, and a cached read (a `GET`). Headers every call needs (a token) go in `onCall`, set once, `new AvClient({ onCall })`, so the calls still group (see \"Hooks\" below); calls whose `onCall` additions differ never share a request.\n- A server older than rc.11 answers `/_all` with `A2002`; the client resends the calls one by one and stops grouping.\n\n## Transactions\n\nWrites that must commit together, or a step that uses an earlier step's result, go in one transaction:\n\n```ts\nimport avClient from \"./api/AvClient\";\n\nconst author = avClient.tx.User.create.one({ data: { email: \"ada@example.com\" }, select: [\"id\"] });\nconst post = avClient.tx.Post.create.one({ data: { title: \"Hello\", authorId: author.$ref(\"id\") }, select: [\"id\"] });\nconst [user, created] = await avClient.transaction([author, post]); // one result per step, typed, in order\n```\n\n- `avClient.tx.<Resource>.<family>.<variant>(args)` describes a step and sends nothing. `avClient.transaction([...])` sends the steps once, as one `POST <api>/_transactions`; they all commit or none does.\n- `step.$ref(\"field\")` stands for that field of an earlier step's result, on a step whose result is one row (`create.one`, `find.unique`, `update.unique`, …). The field must be in that step's result: put it in its `select` (a field the result does not carry is refused, `A2007` / `V1010`). Write it in `data` at any depth (a field, a `$connect`), in a `where` or in a `cursor`, never in a projection's nested `where` or `orderBy`. Its type must fit where it is written; a mismatch is a type error.\n- A failing step rolls every step back and throws its error, with `error.cause.operation` the failing step's index.\n- The client has no `tx` when the server's client layer sets `transactionsStyle: \"server\"` or `\"none\"`.\n- Server code: `framework.transaction([framework.appTx.…])`, the same `$ref`s. In Next.js, `avApp.appTx` with `avApp.transaction([...])`, or `avClient.tx` with `avClient.transaction([...])` from `@/aventara/server`, never mixed in one list.\n\n## Hooks: `onCall`, `onSuccess`, `onError`\n\nThree `AvClient` options see every call: operations, cached reads, a transaction (once), `$access` calls. `onCall` adds to the request; `onSuccess` and `onError` only watch.\n\n```ts\nimport { AvAuthError, AvClient } from \"./api/AvClient\";\n\nexport const authed = new AvClient({\n onCall: () => ({ headers: { Authorization: `Bearer ${localStorage.getItem(\"token\") ?? \"\"}` }, credentials: \"include\" }),\n onError: (_call, error) => {\n if (error instanceof AvAuthError) location.assign(\"/login\");\n },\n});\n```\n\n- `onCall(call)` runs when the call is made, before grouping, and may return or resolve to `{ headers, credentials, cache, keepalive, priority, referrerPolicy }`. Calls with equal additions share one `/_all`. An async one holds back only its own call: it is sent when its `onCall` settles, with the calls queued then, so calls group when their `onCall` settles together with equal additions. An `onCall` doing its own I/O per call (reading the token again each time) sends each call alone; to keep grouping, await one shared in-flight promise, cleared once it settles: `let inFlight; const token = () => (inFlight ??= readToken().finally(() => { inFlight = undefined; }));`. If `token()` rejects, the call is sent without `Authorization` (the hook warning fires) and will likely reach `onError` as a refusal. A hanging `onCall` stalls only its own call, and an abort settles that call at once. A call made inside a hook never waits on the call that ran it.\n- Order: `onCall`'s headers, then the call's own `headers` option (wins), then the client's own (always win). `Content-Type`, `Accept` and the `Aventara-*` headers (canonical or lower-case), and `method`, `body`, `signal`, `redirect`, `mode`, are type errors in `onCall`; at runtime those headers in any case, invalid names and values with a line break, a control character, or a character outside Latin-1 are dropped with a warning (`AVENTARA_CLIENT_HOOK_HEADER_DROPPED`), once per header name for each `onCall`.\n- `call` is `{ kind, label, resource, family, variant, requestId? }` for an operation (`kind: \"transaction\"` or `\"access\"` with `verb` otherwise); never the arguments. To type a hook declared on its own, import `AvCall` and `AvCallAdditions` from the generated client.\n- `onSuccess(call, result)` and `onError(call, error)` are sinks returning `void` (any function fits, async too): side effects only, what they return is ignored and never awaited, so they never delay the caller, and they cannot swallow, change or retry a call. `onError` sees refusals, validation errors, transport failures (`AvTransportError`) and aborts, grouped or alone.\n- A hook that throws or rejects is reported once as a warning (`AVENTARA_CLIENT_HOOK_THREW`) and skipped; the call goes on, with no additions when it was `onCall`.\n- `fetch` only swaps the transport (tests, another runtime's fetch): it must return the `Response` unread. Do not put headers or a token in it. An entrypoint URL carrying credentials (`https://user:pass@…`) is refused by `avclient`: add them through `onCall`.\n- Next.js: put the hooks in `src/aventara/client-options.ts` (or `.js`; no other extension) as `export default { onCall, onSuccess, onError } satisfies Pick<AvClientOptions, \"onCall\" | \"onSuccess\" | \"onError\">` (type from `@/aventara/client`), then run `aventara:prepare` (it runs `aventara-build --client`). Both `@/aventara/client` and `@/aventara/universal` use them, reading the default export when a hook runs, so the file may value-import from `@/aventara/client` (an error class); `fetch`, `grouping` and `entrypoint` there are type errors. A file with no default export leaves the client without hooks, with one warning (`AVENTARA_CLIENT_OPTIONS_NO_DEFAULT`). The file is bundled into the browser: no secrets or server-only imports; branch on `typeof window`. On the server, onCall's headers reach the in-process request but not `ctx.$state`, which comes from the request being served. `@/aventara/server` is not an AvClient and takes no hooks.\n\n## Calls run when you make them\n\nA call is checked, encoded and queued when it is made, as `fetch` is: a call never awaited still runs, and awaiting it twice sends it once. To describe a call now and make it later, wrap it in `avLazy`:\n\n```ts\nimport avClient, { avLazy } from \"./api/AvClient\";\n\nconst drafts = avLazy(() => avClient.Post.find.many({ where: { published: false }, select: [\"id\"] })); // nothing sent\nconst list = await drafts; // sent now\n```\n\n| Where `avLazy` comes from | Awaited again |\n|---|---|\n| The generated client (browser) | Keeps its one answer. |\n| `@aventara/core` (server code) | Calls again each time; it keeps no answer, so never hold one in module scope expecting a cache. |\n| Next.js `@/aventara/universal` on the server | Keeps its answer within one request's React render only. |\n\n## `avClient.all`\n\nAn object of calls in, one typed value per key out. The keys are yours and never sent.\n\n```ts\nimport avClient, { AvNotFoundError } from \"./api/AvClient\";\n\n// Strict (default): the first failure rejects `all` with that error and aborts its other calls.\nconst { list, total } = await avClient.all({\n list: avClient.Post.find.many({ select: [\"id\", \"title\"] }),\n total: avClient.Post.find.count({}),\n});\n\n// settle: true: every key settles on its own; `all` never rejects for one key.\nconst panels = await avClient.all(\n {\n posts: avClient.Post.find.many({ select: [\"id\", \"title\"] }),\n me: avClient.User.find.unique({ where: { email: \"ada@example.com\" }, select: [\"id\", \"email\"] }),\n },\n { settle: true },\n);\nif (panels.me.status === \"rejected\") {\n if (panels.me.reason instanceof AvNotFoundError) console.log(\"no such user\");\n} else {\n console.log(panels.me.value.email, list.length, total);\n}\n```\n\nStrict is fail-fast, not atomic: a write that already ran stays written. Use strict when the page needs every value, `settle: true` when each panel renders or fails alone. Every entry must be a call (or an `avLazy`); a plain value does not compile.\n\n## Aborting\n\nEach call has its own abort: `{ signal }` in a call's options aborts that call only; `avClient.all({ … }, { signal })` aborts every call of that `all`, and a call inside it stops on whichever signal fires first.\n\n```ts\nimport avClient from \"./api/AvClient\";\n\nconst page = new AbortController();\nconst search = new AbortController();\n\nconst result = avClient.all(\n {\n hits: avClient.Post.find.many({ where: { title: { contains: \"rc\" } } }, { signal: search.signal }),\n count: avClient.Post.find.count({}),\n },\n { signal: page.signal },\n);\nsearch.abort(); // only `hits` stops; strict `all` then rejects with its AbortError\n```\n\n- Aborted before the request is sent: the call is dropped. After: it rejects at once; the other calls of the same request go on.\n- Calls grouped into the same request but not in that `all` are never touched. The HTTP request itself is aborted only when every call in it is.\n- A started database query still finishes on the server (no cancel); a started write is written.\n- Nothing aborts for you: no default timeout, no abort on navigation. Pass the signal your data library gives (TanStack Query's `queryFn` receives one: `queryFn: ({ signal }) => avClient.Post.find.many({}, { signal })`), or abort in an effect's cleanup.\n\n## Turning grouping off\n\n```ts\nimport avClient, { AvClient } from \"./api/AvClient\";\n\nawait avClient.Post.find.many({ select: [\"id\"] }, { grouping: false }); // this call, on its own route\nconst plain = new AvClient({ grouping: false }); // every call of this client\nawait plain.Post.find.count({});\n```\n\nOnly for a reason: debugging in the network tab, per-route monitoring or rate limits, or a proxy that lets through `/_resources/…` but not `/_all`. A transaction takes no `grouping` option.\n\n## On the server\n\nServer code has the same `all`: `framework.serverClient.all` (NestJS, any host) and `avClient.all` from `@/aventara/server` (Next.js). `framework.application` and `avApp` have no `all`.\n\n```tsx\n// src/app/dashboard/page.tsx\nimport { avLazy } from \"@aventara/core\";\nimport { avClient } from \"@/aventara/server\";\n\nexport default async function Dashboard() {\n const { posts, published } = await avClient.all({\n posts: avClient.Post.find.many({ select: [\"id\", \"title\"] }),\n published: avLazy(() => avClient.Post.find.count({ where: { published: true } })),\n });\n return <p>{posts.length} posts, {published} published</p>;\n}\n```\n\n- Nothing is grouped and nothing goes over HTTP: each call runs in process, in the request that made it, with that request's state and principal (`framework.serverClient` calls take `{ state }` like any server call).\n- Any number of entries: `limits.maxAll` guards `POST /_all` only.\n- A plain call has already started when made, so `all` can only await it. An `avLazy` entry is started by `all`: at most `limits.maxAllConcurrency` (4) at once, in key order, and none after a strict `all` failed or was aborted.\n- Next.js `@/aventara/universal` groups in the browser only; on the server each of its calls runs on its own.\n\n## The two limits\n\n| Limit | Default | Bounds |\n|---|---|---|\n| `limits.maxAll` | `10` | Operations in one `POST /_all`. More is refused whole: `422 A2009` / `V1014`. The generated client splits for you. |\n| `limits.maxAllConcurrency` | `4` | Operations of one set (HTTP or in process) running at once; the rest wait, in key order. At least `1`. |\n\nOrdinary limits: set them at the root, override them in `client` or `application` (`limits: { maxAll: 20 }`). Do not grow the database pool for `/_all`; the concurrency limit keeps one request to its share.\n\n## Cached reads from the client\n\nA read with a `cache` stage (`reference/config.md`, \"Caching reads\") is sent by the generated client as `GET <api>/_resources/<R>/find/<variant>?q=<args>&v=<contract hash>`, alone, never in an Operation Set. Leave `requestId` off a cached cross-origin read (it adds a CORS preflight). A URL over 4096 characters goes as a `POST` and is not cached. The `POST` route stays for any client.\n\n## By hand (curl, a custom client)\n\n`POST <api>/_all` takes the two identity headers and `{ \"operations\": { \"<key>\": { \"resource\": \"Post\", \"family\": \"find\", \"variant\": \"many\", \"args\": { … } } } }` (keys are identifiers; each entry is exactly those four members). It answers `200 A1000` with `data` holding each key's own envelope (`{ data, code, cause }`): read each key's `code`, not the status. With `Accept: application/x-ndjson` it streams one line per entry as it finishes, `{\"key\":\"…\",\"envelope\":{…}}`; the generated client does this for you, so write it only in a client of your own. Too many entries: `422 A2009`; none: `422 A2004`.\n\nA plan of your own, `POST <api>/_transactions` with `{ \"operations\": [ … ] }`, needs each step's `fingerprint`: compute it with `await AvProtocol.fingerprint({ resource, family, variant, args })` from `@aventara/core/protocol` over the step's wire-form `args`, the function the server checks it with. It is async: a step whose `fingerprint` is the unawaited promise serializes as `{}`, and a wrong fingerprint answers `422 A2007` / `V1017`. A layer with `transactionsStyle: \"server\"` or `\"none\"` has no such route (`404 A2002`).\n",
|
|
6
|
+
"reference/config.md": "# Configuration reference\n\n`aventaraConfig` in `src/aventara.config.ts` is one object `as const`, without `adapter`. Every name in it (Resource, field, operation) is checked against your model by TypeScript, again by `aventara-build` (in `aventara:prepare`) and again at startup.\n\n## The tree\n\n```text\nroot\n api \"/api\": where the protocol is mounted (root only)\n mcp \"/mcp\" or { path, authorization: { servers, verify } }: turns MCP on (root only); verify(token) resolves the caller or null, and what it resolves is ctx.$state.mcp.claims\n adapter joined in app.module.ts, never written here: await createPrismaAdapter({ ...adapter, client }) (root only)\n prebuilt serverless and edge only, joined where the framework is created: aventaraContracts (root only); a stale file is not used (AVENTARA_PREBUILT_STALE)\n policy { engine, principal }: a policy engine checks every client and agent request (root only)\n access an access provider such as Aventara Access: its policy plus each request's state (root only; not with policy, nor with the host's state option)\n diagnostics (d: AvDiagnostic) => void: the real error behind every A3xxx (root only)\n misuse (e: AvMisuseEvent) => void: refused unreadable server-side input (root only)\n application overlay for your server code (framework.application, appTx, ctx calls)\n client overlay for remote callers (HTTP, the generated client)\n agent overlay for AI agents over MCP, plus extends: \"client\" | \"application\" | \"root\"\n -- the six layer keys, at the root and in every overlay --\n exposed true: exemptions hide; false: exemptions are all that is exposed\n exemptions { resources: { <R>: true | { operations?, fields? } } }\n transactionsStyle \"interactive\" | \"server\" | \"none\" (see Transactions)\n limits { maxListLimit, maxNestingDepth, maxTransactionOperations, maxTransactionWrites, maxBooleanNodes, maxJsonDepth, maxRequestBytes, maxAll, maxAllConcurrency }\n pipelines { guards, pipes, hooks: { before, after }, interceptors, filters, cache } cache: find only, never in application or agent (see Caching reads)\n resources\n <Resource>\n managedElsewhere root only: true when another service owns and migrates the table: read and joined, never written\n pipelines every operation of the Resource\n operations\n <family> find | create | update | delete | upsert\n pipelines every variant of the family\n forceTransaction true: a plain call runs as a one-step transaction (needs transactionsStyle \"interactive\" or \"server\")\n <variant> e.g. create.one, find.many\n pipelines that one operation (typed ctx.$args)\n forceTransaction\n transactions\n pipelines once per transaction with a top-level step on the Resource\n fields\n <field>\n definition root only: add a virtual field, or give a Json field its AvZ shape\n pipelines\n validators AvZ checks\n pipes (value) => value\n compute { create, update, read }\n guards (value, ctx) => boolean\n```\n\n## How a layer combines with the root\n\n| Key | Rule |\n|---|---|\n| `exposed` + `exemptions` | Narrowing only. An overlay starts from the root's view and can hide more, never bring back what the root hid. |\n| `transactionsStyle` | Override: the overlay's value when present, else the root's. |\n| `limits` | Override per limit. |\n| `pipelines` (every level) | Additive: the root's stages run, then the overlay's. Nothing removes a stage. |\n| `pipelines.cache` | Not additive: the most specific level wins (variant, family, Resource, layer); at one level the `client` layer's replaces the root's. |\n| field `validators` | Additive. |\n| field `compute` | Chained: the root's stage runs, then the overlay's, which receives the root's result as `value`. |\n| field `definition` | Root only (`CONFIG_FORBIDDEN_IN_OVERLAY` in an overlay). |\n| `managedElsewhere` | Root only (`CONFIG_FORBIDDEN_IN_OVERLAY` in an overlay). |\n| `forceTransaction` | Forced when the root or the operation's overlay sets it. Under an effective `transactionsStyle: \"none\"` in that layer it is a startup error (`CONFIG_INVALID_VALUE`) and a type error; `\"server\"` allows it. To keep forced transactions while refusing client plans, set `client: { transactionsStyle: \"server\" }` (Transactions, below). |\n\nAn override wins when present, not when truthy (`false` overrides). An explicit `undefined` is refused: leave the key out. The `agent` layer narrows the layer it `extends` (default `\"client\"`).\n\n## Exposure\n\n| Value in `exemptions` | Under `exposed: true` (Omit) | Under `exposed: false` (Pick) |\n|---|---|---|\n| `true` | hide the entry and everything under it | expose the entry and everything under it |\n| `false` | nothing (same as leaving it out) | nothing |\n| a list | hide the listed entries one level down | expose only the listed entries |\n| an object | go one level deeper | go one level deeper |\n| `\"read\"` (a field, or a Resource's `fields`) | hide every read capability of the field (each field's, on `fields`); its writes stay | expose every read capability the layer above offers, and no write |\n\n| Level | Names you can list |\n|---|---|\n| `operations` | `find`, `create`, `update`, `delete`, `upsert` |\n| a family | `find`: `first`, `unique`, `many`, `count`; `create`: `one`, `many`, `count`; `update`: `first`, `unique`, `many`, `count`; `delete`: `first`, `unique`, `count`; `upsert`: `unique` |\n| a scalar field | `select`, `filter`, `order`, `orderNulls`, `create`, `update` |\n| a relation field | `select`, `include`, `filter`, `order`, `pagination`, `reducers`, `create`, `update` |\n| `\"read\"` names | a scalar's `select`, `filter`, `order`, `orderNulls`; a relation's `select`, `include`, `filter`, `order`, `pagination`, `reducers` |\n| a capability's tokens | `filter`: operators (`equals`, `in`, `contains`, …); `create`/`update`: directives (`$increment`, `$push`, …); relation `create`/`update`: `$create`, `$connect`, `$connectOrCreate`, `$disconnect`, `$set`, `$update`, `$delete`, `$upsert` |\n\nPrisma has no `delete.many`. Common shapes:\n\n- Write-only field: `fields: { passwordHash: \"read\" }`.\n- Read-only for remote callers: `client.exemptions…fields: { likeCount: [\"create\", \"update\"] }`.\n- Hide a Resource from remote callers: `client.exemptions.resources.AuditLog: true`. Hide the relations that point at it too, or startup fails with `COMPILER_DANGLING_RELATION`.\n- Read-only Resource for remote callers (Pick): `client: { exposed: false, exemptions: { resources: { Category: { operations: { find: true }, fields: \"read\" } } } }`. `fields: \"read\"` names every field, relations included: expose the Resources its relations point at too (or list the fields instead), or startup fails with `COMPILER_DANGLING_RELATION`. Under `exposed: false` a relation into it that you grant `$create`, `$connectOrCreate`, `$upsert` or `$update` is refused at startup (`COMPILER_READ_ONLY_NESTED_WRITE`), so let the relation only link (`author: { select: true, create: [\"$connect\"] }`).\n- No nested write into a Resource the layer cannot write. The fields decide with the operations: no create operation and no field writable on create means no `$create`/`$connectOrCreate`/`$upsert` into it, the same on update for `$update`/`$upsert`; no delete operation means no `$delete`. Hiding only the create operation keeps nested creates (\"created only through its parent\"). Under `exposed: true` the relations into it lose those directives on their own (startup succeeds; the Contract and hash change; a directive sent anyway is `V1006`). Under `exposed: false` a grant of one (by `true`, the member, or naming it) fails startup with `COMPILER_READ_ONLY_NESTED_WRITE`: grant only the linking directives, `posts: { update: [\"$connect\", \"$disconnect\", \"$set\"] }`.\n- A public client that reaches only what the frontend calls: `client: { exposed: false, exemptions: { resources: { Post: { operations: { find: [\"many\", \"unique\"] }, fields: { id: [\"select\", \"filter\"], title: [\"select\"] } } } } }`. A unique operation needs its identifier exposed with its `filter`, since a unique `where` filters on it (`COMPILER_MISSING_IDENTIFIER`); a field a layer may not filter on, a write-only secret say, is no identifier there.\n\nA hidden field disappears from that layer's inputs, outputs, filters, ordering and the generated client. A client exemption changes the contract hash: regenerate the frontend client.\n\n## A model managed elsewhere\n\nWhen another service owns a table and migrates it (Better Auth's `user`, say, or another service's schema in the same\ndatabase), mark its model `managedElsewhere: true` at the root. Aventara then reads it and joins through it, and no\nstandard operation writes it:\n\n- it has no `create`, `update`, `delete` or `upsert`, and its fields take no write;\n- a relation into it keeps only `$connect`/`$disconnect`, and only where your model holds the key (`authorId`);\n- the client and agent layers leave it out, with every relation into it; your server code reads and joins it;\n- a stage on one of its write families is refused at startup (`COMPILER_UNAVAILABLE_OPERATION`), and so is any\n `client.resources` or `agent.resources` entry for it (`COMPILER_UNKNOWN_RESOURCE`): nothing there would run;\n- a delete whose `onDelete` would cascade into it or null its reference is refused at startup (`CONFIG_INVALID_VALUE`);\n hide that delete at the root or make the reference `onDelete: Restrict`;\n- a create only the hidden relation could satisfy is refused (`COMPILER_HIDDEN_REQUIRED_FIELD`): expose the reference\n field.\n\nStill yours: a key update follows the owner's foreign key (`ON UPDATE`), and `ctx.$sql` or a stage calling Prisma can\nstill write the table.\n\nKeep Prisma from migrating it too: list it in `prisma.config.ts` as an external table (Prisma 7.10:\n`experimental: { externalTables: true }`, `tables: { external: [\"auth.user\"] }`), and give the model its\n`@@schema(\"auth\")` when the owner keeps it in another PostgreSQL schema. If the owner's own `_prisma_migrations` table\nsits in a schema your datasource lists, list it as external as well, or `prisma migrate` stops with\n`migration persistence is not initialized`.\n\n## Limits\n\n| Limit | Default | Exceeded |\n|---|---|---|\n| `maxRequestBytes` | `1048576` | `A2010` |\n| `maxNestingDepth` | `12` | `A2009` / `V1013` |\n| `maxListLimit` | `250` | `A2009` / `V1014` |\n| `maxTransactionOperations` | `20` | steps in one transaction plan, reads and `$sql` steps included: `A2009` / `V1014` (`POST /_all` is bound by `maxAll` instead, and is not atomic) |\n| `maxTransactionWrites` | `20` | `create`/`update`/`upsert`/`delete` steps in one plan, each `$sql` step counted as a write: `A2009` / `V1014` |\n| `maxBooleanNodes` | `50` | `A2009` / `V1015` |\n| `maxJsonDepth` | `64` | `A2009` / `V1021` |\n| `maxAll` | `10` | operations in one `POST /_all`: `A2009` / `V1014` (the generated client splits; in-process `all` ignores it) |\n| `maxAllConcurrency` | `4` | operations of one set running at once; the rest wait (at least `1`) |\n\n`POST /_all` is not a transaction: `maxAll` bounds it, not the two plan limits, and its writes do not roll back together.\n\n## Transactions\n\n| `transactionsStyle` | The server opens transactions (`forceTransaction`, a policy-checked write, `ctx.$transaction`) | Callers send plans (`/_transactions`, `avClient.transaction`, `framework.transaction`) |\n|---|---|---|\n| `\"interactive\"` (the adapter's default) | yes | yes |\n| `\"server\"` | yes | no: no `/_transactions` route, no generated `tx`, a plan answers `404 A2002` |\n| `\"none\"` | no: `forceTransaction` is a startup error | no |\n\nTo keep remote callers from sending multi-step plans while forced writes still run atomically, set `client: { transactionsStyle: \"server\" }`. Keep each invariant in a `before` hook of the forced operation, inside its transaction; a guard runs before `BEGIN`.\n\n## Caching reads\n\nA read with a `cache` stage is also served as a `GET` the browser, or a CDN, may keep for `maxAge` seconds; the generated client sends it that way. Without one, every read is a `POST` and nothing caches it.\n\n```ts\n// src/resources/post.resource.ts\nimport { cache, defineResource } from \"@aventara/core\";\nimport { discovery } from \"../generated/aventara/discovery.artifact.js\";\n\ntype Model = typeof discovery.model;\n\nexport const post = defineResource<Model>()(\"Post\", {\n // Post.find.unique, .first, .count: each caller's own browser keeps its answer.\n pipelines: { cache: cache({ for: \"user\", maxAge: 30 }) },\n operations: {\n // Post.find.many: anonymous callers share one answer a CDN may keep.\n find: { many: { pipelines: { cache: cache({ for: \"anonymous\", maxAge: 60 }) } } },\n },\n});\n```\n\n- `cache({ for, maxAge })` is data, not a function that runs. Always write `for`, and write it first, even for the default `\"user\"`: it is the promise the read makes, and nothing checks it. This is the one exception to writing only what changes behaviour (SKILL.md rule 16).\n- `maxAge` is whole seconds.\n- Where: `pipelines.cache` at the root or the `client` layer (every find), a Resource (every find of it), its `find` family, or one `find` variant. Anywhere else (a write family or variant, `transactions`, the `application` or `agent` layer) fails startup with `CONFIG_INVALID_VALUE` at its path.\n- Precedence: the most specific level wins (variant, family, Resource, layer); at one level the `client` layer's stage replaces the root's. It never adds up.\n- A module that also imports React's `cache` (Next.js) imports ours as `import { cache as avCache } from \"@aventara/core\"`.\n- Adding, changing or removing a stage changes the contract hash: regenerate the frontend client.\n\n| `for` | Promise | A `200` carries |\n|---|---|---|\n| `\"user\"` (default) | The answer is this caller's own. | `Cache-Control: private, max-age=…`, `Vary: Authorization, Cookie` |\n| `\"anonymous\"` | Every anonymous caller gets the same answer. | `public` to an anonymous caller (a CDN may keep it), `private` to a signed-in one; `Vary: Authorization, Cookie` |\n| `\"everyone\"` | Every caller gets the same answer. | `Cache-Control: public, max-age=…`: a CDN hands it to anyone |\n\nPick the narrowest true one. `\"everyone\"` only when nothing makes the answer differ by caller: no guard, pipe, compute or policy that reads the caller, or a CDN serves one caller's answer to all. Anonymous means: with a policy, the principal it derives is `null`/`undefined`; without one, no `Cookie` and no `Authorization` header. Callers who sign in with a header of their own (`X-Api-Key`) need a policy, or no `\"anonymous\"`/`\"everyone\"`.\n\nAlso: the arguments land in the URL (server and proxy logs, history), so never cache a read whose arguments are secret; prefetches run the read and its stages; Aventara keeps no server-side copy; a stage tells the two routes apart by `ctx.$transport.method` (`\"GET\"` or `\"POST\"`). Behind a CDN: honor `Vary` (or bypass on `Cookie`/`Authorization`) and keep `Set-Cookie` off `GET <api>/_resources/…`.\n\n## Fields\n\n- Add a virtual field at the root: `definition: { type: { scalar: \"string\" }, nullable: false, list: false, lifecycle: [\"VIRTUAL\"], capabilities: { select: true } }` plus a `compute.read` stage. A write-only virtual input says `capabilities: { select: false, create: [], update: [] }`; with `nullable: false` a create must send it (`A2004`, `V1000`, and the client types require it) unless a `compute.create` fills it.\n- Type a `Json` column: `definition: { json: AvZ.object({ … }) }`; every write is checked against it and the client types follow it. Every check and shape builder: `reference/avz.md`.\n- A `compute.create` stage makes its field optional on create, and must return a value when the caller sent none.\n- A `compute.read` stage with no `dependsOn` declares no field it reads, so it computes from the same inputs for every row: such a value usually belongs in response metadata or `ctx.$state`, not in a field.\n\n## Scalars\n\n| Prisma type | In your code (`framework.application`, stages) | On the wire (HTTP, `framework.client`) |\n|---|---|---|\n| `DateTime` | `Date` | ISO-8601 UTC string |\n| `BigInt` | `bigint` | base-10 string; a JSON number is refused (`V1001`) |\n| `Decimal` | `AvDecimal` (from `@aventara/core`; holds the digits, does no arithmetic) | decimal string |\n| `Bytes` | `Uint8Array` | base64 |\n\n- A validator bound is written in the field's own type: `AvZ.gte(0n)` on a `BigInt`, a `Date` on a `DateTime`.\n- An `AvZ` Json shape has no `bigint` node, and `AvZ.int()` is a safe integer only: keep 64-bit amounts inside Json as strings.\n- `bigint` and `Date` are not JSON-serializable: a Nest controller that returns a server-side result holding one answers `500`. Convert first (`value.toString()`, `date.toISOString()`).\n- On SQLite a `Decimal` is a 64-bit float (15 significant digits); use `BigInt` minor units for money there.\n\n## The adapter's options and the database\n\nTransaction options belong to the adapter, never to `aventaraConfig`: `createPrismaAdapter({ ...adapter, client, transactionOptions: { maxWait, timeout, isolationLevel } })`. Prisma's defaults are `maxWait` 2000 ms and `timeout` 5000 ms: keep slow work (a printer, a network call) out of a transaction's hooks, or raise `timeout`. `driver` must name the driver adapter the Prisma client was built with (`@prisma/adapter-better-sqlite3`, `@prisma/adapter-pg` or `@prisma/adapter-d1`); another one throws in `createPrismaAdapter`.\n\nTo switch the database (SQLite to PostgreSQL, say) or the driver, change these together, then run `aventara:prepare`:\n\n- the schema's `datasource` `provider`, and `DATABASE_URL` in `.env` (and in every deployment) to the new database's URL;\n- `prisma/migrations/`: Prisma writes migrations for one provider, which `migration_lock.toml` records, and `migrate` refuses another. Start a new history for the new database (move the old folder aside, then `prisma migrate dev`) rather than reusing the old one;\n- `describePrismaAdapter({ provider, driver })` in the adapter's description file: `provider` is `\"sqlite\"` or `\"postgresql\"`, `driver` the matching driver adapter package;\n- the Prisma client's own driver adapter (`src/prisma.service.ts`, or `src/prisma.ts` in Next.js), and the installed `@prisma/adapter-…` package;\n- the `aventara:prepare` script's `aventara-prisma7-generate --provider … --driver …` flags. Moving the schema or Prisma's client `output` changes its `--schema` or `--client` the same way.\n\n| | SQLite | PostgreSQL |\n|---|---|---|\n| `isolationLevel` | `Serializable` only | every level but `Snapshot` |\n| String `mode` | `\"default\"` only (case-insensitive for ASCII) | `\"default\"`, `\"sensitive\"`, `\"insensitive\"` |\n| Json filters | no `path` | `path` |\n| Connections | one, shared by every request (see the Transactions page of the docs) | a pool |\n\nOn SQLite (`@prisma/adapter-better-sqlite3`) an open transaction holds the one connection, so a plain operation or `ctx.$sql` outside a transaction runs in a transaction of its own and waits for it to end: past `maxWait` it answers `A3002`, and `timeout` bounds it too. A plain call made inside a transaction's async context (a stage calling the root `framework` instead of its `ctx`) joins that transaction and warns `AVENTARA_PRISMA_SQLITE_TRANSACTION_JOINED`: call through `ctx` instead.\n\n## Startup\n\nA wrong configuration throws one `AvConstructionError` before the server listens: `stage` (`configuration`, `discovery` or `contract-compilation`) and `diagnostics`, each `{ code, path, message, docs }`. `path` names the key; `reference/errors.md` explains each code.\n",
|
|
7
|
+
"reference/recipes.md": "# Recipes\n\nEvery block here compiles against the Aventara release this skill was written for. Model names (`User`, `Post`, `Order`, `OutboxEvent`) are examples: use your schema's. The ids here are numbers; the scaffold's `User.id` is a `String` cuid, so write your schema's id type where a recipe names one.\n\n## The signed-in user, for every stage\n\nDeclare what the request state holds, once:\n\n```ts\n// src/aventara.state.ts\ndeclare module \"@aventara/core\" {\n interface AvState {\n user?: { id: number; role: \"member\" | \"admin\" };\n }\n}\nexport {};\n```\n\nSeed it from what your auth middleware found, in `src/app.module.ts`. Run that middleware with `app.use(...)` in `main.ts`: Nest guards (Passport's `AuthGuard`, `APP_GUARD`) never run on Aventara's routes.\n\n```ts\n// src/app.module.ts\nimport { Module } from \"@nestjs/common\";\nimport { AventaraModule } from \"@aventara/nest\";\nimport { createPrismaAdapter } from \"@aventara/prisma7-adapter\";\nimport type { Request } from \"express\";\nimport adapter from \"./aventara.adapter.js\";\nimport { aventaraConfig } from \"./aventara.config.js\";\nimport { PrismaModule, PrismaService } from \"./prisma.service.js\";\n\ntype AuthedRequest = Request & { user?: { id: number; role: \"member\" | \"admin\" } };\n\n@Module({\n imports: [\n PrismaModule,\n AventaraModule.forRootAsync({\n imports: [PrismaModule],\n inject: [PrismaService],\n useFactory: async (prisma: PrismaService) => ({\n config: {\n ...aventaraConfig,\n adapter: await createPrismaAdapter({ ...adapter, client: prisma }),\n },\n state: (req: AuthedRequest) => ({ user: req.user }),\n }),\n }),\n ],\n})\nexport class AppModule {}\n```\n\nIn Next.js, `state` goes in `Aventara({ … })` in `src/aventara/server.ts`. It gets `{ headers, request? }`, where `headers` are the request's (`next/headers` in a Server Component):\n\n```ts\n// src/aventara/server.ts\nimport \"server-only\";\nimport { Aventara } from \"@aventara/next\";\nimport { createPrismaAdapter } from \"@aventara/prisma7-adapter\";\nimport { userOf, type User } from \"@/auth/session\"; // your auth library\nimport { prisma } from \"../prisma\";\nimport adapter from \"./adapter\";\nimport { aventaraConfig } from \"./config\";\n\ndeclare module \"@aventara/core\" {\n interface AvState {\n user?: User | null;\n }\n}\n\nexport const { handlers, avClient, avApp, ready } = Aventara({\n ...aventaraConfig,\n adapter: () => createPrismaAdapter({ ...adapter, client: prisma() }),\n state: async ({ headers }) => ({ user: await userOf(headers.get(\"authorization\")) }),\n});\n```\n\nKeep the scaffold's `prebuilt` line beside it.\n\nServer code passes it per call: `framework.application.Post.create.one({ data }, { state: { user } })`. Guards read it:\n\n```ts\nimport { AvError, type AvGuard } from \"@aventara/core\";\n\nexport const requireUser: AvGuard = (ctx) => {\n if (ctx.$state.user === undefined) throw new AvError(\"A4000\", \"Sign in first.\");\n return true;\n};\nexport const adminOnly: AvGuard = (ctx) => ctx.$state.user?.role === \"admin\";\n```\n\nPut `requireUser` in `client: { pipelines: { guards: [requireUser] } }` (remote callers only), and `adminOnly` where it applies (`client: { resources: { Post: { operations: { delete: { pipelines: { guards: [adminOnly] } } } } } }`).\n\n## A Resource in its own file, with a server-filled owner\n\n`Post.authorId` is required; remote callers must not choose it. `compute.create` fills it from the signed-in user, which also makes it optional on create; the `client` exemption takes both `authorId` and the `author` relation out of the remote create and update inputs. Hiding only `authorId` is not enough: `author: { $connect: … }` would still let a caller set or move the owner.\n\n```ts\n// src/resources/post.resource.ts\nimport { AvError, defineResource } from \"@aventara/core\";\nimport { AvZ } from \"@aventara/core/avz\";\nimport { discovery } from \"../generated/aventara/discovery.artifact.js\";\n\ntype Model = typeof discovery.model;\n\nconst slugify = (text: string) =>\n text.toLowerCase().trim().replace(/[^a-z0-9]+/g, \"-\").replace(/^-|-$/g, \"\");\n\nexport const post = defineResource<Model>()(\"Post\", {\n fields: {\n title: { pipelines: { validators: [AvZ.regex(/\\S/), AvZ.maxLength(200)] } },\n slug: {\n pipelines: {\n compute: {\n create: ({ data }) => slugify(String(data.title)),\n update: ({ data, value }) => (data.title === undefined ? value : slugify(String(data.title))),\n },\n },\n },\n authorId: {\n pipelines: {\n compute: {\n create: ({ value, ctx }) => {\n const user = ctx.$state.user;\n if (user !== undefined) return user.id;\n if (value !== undefined) return value; // server code may pass the owner itself\n throw new AvError(\"A4000\", \"Sign in first.\");\n },\n },\n },\n },\n },\n});\n```\n\nRun `{{RUN}} aventara:prepare`: it adds `post` to `aventaraResources` (`resources.generated.ts`, beside the discovery artifact), which the configuration's `resources: aventaraResources.root` already takes, so `aventara.config.ts` needs no edit. Two files defining the same Resource stop `aventara:prepare` with both named. A Resource that gives a layer a block (`defineResource<Model>()(\"Post\", { … }, { client: { … } })`) needs that layer's line once: `client: { resources: aventaraResources.client }`. Take `authorId`, `author` and `slug` out of the remote inputs in the `client` layer's `exemptions` (next recipe): exposure lives in a layer's `exemptions`, never in a `defineResource` block.\n\n## Exposure: what each caller may reach\n\n```ts\n// in aventaraConfig (src/aventara.config.ts)\nexemptions: {\n resources: {\n User: { fields: { passwordHash: \"read\" } }, // write-only everywhere\n },\n},\nclient: {\n exposed: true,\n exemptions: {\n resources: {\n AuditLog: true, // remote callers never see it\n User: { operations: { delete: true }, fields: { internalNotes: true } },\n Post: { fields: { authorId: [\"create\", \"update\"], author: [\"create\", \"update\"], slug: [\"create\", \"update\"] } }, // filled by the server; a caller writing `author` gets A2004 (V1006)\n },\n },\n},\n```\n\n## Values: validators and a field pipe\n\n```ts\n// in a defineResource block's fields, or resources.<R>.fields\nemail: {\n pipelines: {\n pipes: [(value) => (typeof value === \"string\" ? value.trim().toLowerCase() : value)],\n validators: [AvZ.email(), AvZ.maxLength(254)],\n },\n},\n```\n\nValidators are `AvZ` checks only (`minLength`, `maxLength`, `regex`, `email`, `uuid`, `gte`, `lte`, `multipleOf`, `maxItems`, …). A rule across two fields is a guard or a pipe. A rule that reads the database runs where it holds at commit: `hooks.before` under `forceTransaction` (Compare-and-set, below), with a guard as an optional early check.\n\n## An outbox event that commits with the order\n\n```ts\n// src/resources/order.resource.ts\nimport { defineResource } from \"@aventara/core\";\nimport { discovery } from \"../generated/aventara/discovery.artifact.js\";\n\ntype Model = typeof discovery.model;\n\nexport const order = defineResource<Model>()(\"Order\", {\n operations: {\n create: {\n one: {\n forceTransaction: true,\n pipelines: {\n hooks: {\n after: [\n async (result, ctx) => {\n // The result holds what the caller selected: narrow it, never cast it.\n const id = typeof result === \"object\" && result !== null && \"id\" in result ? result.id : undefined;\n // Runs inside the order's transaction: a throw rolls the order back.\n if (typeof id !== \"number\" && typeof id !== \"string\") throw new Error('create orders with select: [\"id\"]');\n await ctx.OutboxEvent.create.one({\n data: { topic: \"order.created\", payload: { orderId: id } },\n select: [\"id\"],\n });\n },\n ],\n },\n },\n },\n },\n },\n});\n```\n\nOrders are created by server code, which selects `id`: `framework.application.Order.create.one({ data, select: [\"id\"] })`. Hide both from remote callers: `client.exemptions.resources: { OutboxEvent: true, Order: { operations: { create: true } } }`. A consumer reads new `OutboxEvent` rows (or, on PostgreSQL with a queue, the hook sends the event's id with ``ctx.$sql`…` ``).\n\n## Server code\n\nNest's injection carries no type argument, so a module of its own exports the framework's type once:\n\n```ts\n// src/aventara.framework.ts\nimport { createFramework } from \"@aventara/core\";\nimport { createPrismaAdapter } from \"@aventara/prisma7-adapter\";\nimport adapter from \"./aventara.adapter.js\";\nimport { aventaraConfig } from \"./aventara.config.js\";\nimport type { PrismaService } from \"./prisma.service.js\";\n\nexport const buildFramework = async (prisma: PrismaService) =>\n createFramework({ ...aventaraConfig, adapter: await createPrismaAdapter({ ...adapter, client: prisma }) });\nexport type AppFramework = Awaited<ReturnType<typeof buildFramework>>;\n```\n\n```ts\n// src/post-reader.service.ts\nimport { Injectable } from \"@nestjs/common\";\nimport { AvNotFoundError } from \"@aventara/core\";\nimport { InjectFramework } from \"@aventara/nest\";\nimport type { AppFramework } from \"./aventara.framework.js\";\n\n@Injectable()\nexport class PostReader {\n constructor(@InjectFramework() private readonly framework: AppFramework) {}\n\n async findOrNull(id: number) {\n try {\n return await this.framework.application.Post.find.unique({ where: { id }, select: [\"id\", \"title\"] });\n } catch (error) {\n if (error instanceof AvNotFoundError) return null;\n throw error;\n }\n }\n}\n```\n\n`framework.application` calls resolve the data and throw the code's `AvError` subclass; `find.first` resolves `null` instead of throwing. Several writes that must succeed together: `framework.transaction([framework.appTx.Post.create.one(…), …])`, using one step's result in another with `$ref` (`reference/client.md`, Transactions), or `ctx.$transaction(async (tx) => …)` inside a stage.\n\n## Server surfaces\n\n| Call | Runs as | Values |\n|---|---|---|\n| `framework.application.<R>.<family>.<variant>(args, options?)` | the application layer: trusted code, no policy unless `{ as }` | application (`Date`, `bigint`, `AvDecimal`, `Uint8Array`) |\n| `framework.serverClient.<R>…` | the client layer: the exposure, pipelines, limits and policy a remote caller gets | application |\n| `framework.client.<R>…` | the client layer, as above | wire (ISO strings, decimal and bigint strings, base64) |\n| `framework.transaction([...], options?)` with `framework.appTx`, `framework.clientTx` or `framework.serverClientTx` steps | one layer per plan: mixing `appTx` with client steps is `A2004` / `V1020` | as the matching call |\n\n`options`: `{ requestId, state, as, signal }` (`signal` on `serverClient` calls). A server-side call has no HTTP request, so `ctx.$transport` is absent and a guard that reads headers sees none: pass the user with `{ state }`.\n\n## Prefetched data in a Client Component (Next.js)\n\nWrite the query once over `@/aventara/universal`, which runs on the server and in the browser; the Client Component's data is typed by that query. The browser's `QueryClient` takes `defaultOptions: { hydrate: { deserializeData: avHydrate } }` (`avHydrate` from `@/aventara/client`), set once in your providers. Use TanStack Query for this: SWR's `fallback` would need a cast, since `avHydrate` returns `unknown`.\n\n```ts\n// src/app/posts/posts.queries.ts\nimport { queryOptions } from \"@tanstack/react-query\";\nimport { avClient } from \"@/aventara/universal\";\n\nexport const postsQuery = queryOptions({\n queryKey: [\"posts\"],\n queryFn: () => avClient.Post.find.many({ select: [\"id\", \"title\", \"createdAt\"] }),\n staleTime: 60_000, // the hydrated data is not refetched on mount\n});\n```\n\n```tsx\n// src/app/posts/post-list.tsx\n\"use client\";\nimport { useQuery } from \"@tanstack/react-query\";\nimport { postsQuery } from \"./posts.queries\";\n\nexport function PostList() {\n const { data } = useQuery(postsQuery); // createdAt is a Date\n return <ul>{data?.map((post) => <li key={post.id}>{post.title}, {post.createdAt.toLocaleDateString()}</li>)}</ul>;\n}\n```\n\n```tsx\n// src/app/posts/page.tsx\nimport { avDehydrate } from \"@aventara/next\";\nimport { dehydrate, HydrationBoundary, QueryClient } from \"@tanstack/react-query\";\nimport { Suspense } from \"react\";\nimport { PostList } from \"./post-list\";\nimport { postsQuery } from \"./posts.queries\";\n\nasync function Prefetched() {\n const queryClient = new QueryClient(); // one per request\n await queryClient.prefetchQuery(postsQuery);\n return (\n <HydrationBoundary state={dehydrate(queryClient, { serializeData: avDehydrate })}>\n <PostList />\n </HydrationBoundary>\n );\n}\n\nexport default function Page() {\n return <Suspense fallback={<p>Loading</p>}><Prefetched /></Suspense>;\n}\n```\n\n## Compare-and-set: a status change two requests may race on\n\nA guard's check runs before the transaction; even a `hooks.before` re-check inside it can be passed by two transactions at once under PostgreSQL's default isolation. Make the write itself the check:\n\n```ts\nconst closed = await framework.application.Ticket.update.first({\n where: { id: ticketId, status: \"OPEN\" },\n data: { status: \"CLOSED\" },\n select: [\"id\", \"status\"],\n});\nif (closed === null) {\n // no open ticket with that id: already closed, or never there\n}\n```\n\n`update.first` runs two statements in one transaction, a read and then a write with the same `where` asserted again: the second of two racing calls resolves `null` (no match) or throws `AvConflictError` `A2014` (the row changed between the read and the write); nothing is written either way. `A2013` and `A2014` may succeed if sent again. An `after` hook that must write the side effect (an event, a number from a counter) goes on `update.first` with `forceTransaction: true`. The update's answer is read before that hook runs. To answer with what the hook wrote, patch the result in an interceptor, which runs inside the same transaction: `const result = await next();`, read only the written field with `ctx` (`select: [\"number\"]`), and return `{ ...result, number }` when `result` holds that field. Never replace the result with a fresh `ctx` read: the answer is checked against the caller's own projection, and a `ctx` call is application-origin, so a read with the default fields carries fields the `client` layer hides and a remote caller gets `500 A3000`.\n\n## Testing\n\n- Through Nest: `Test.createTestingModule({ imports: [AppModule] }).compile()`, then `moduleRef.get(...)` or `createNestApplication()` and real HTTP.\n- Assert on `code` or the error class, never the message: `await expect(call).rejects.toMatchObject({ code: \"A2004\" })`, `.rejects.toBeInstanceOf(AvValidationError)`.\n- As a remote caller, in process: `framework.client` (wire values) or `framework.serverClient` (application values) run the client layer's exposure, pipelines and limits. A client plan: `framework.transaction([framework.clientTx.Post.create.one(…), …])`, held to the client's `maxTransactionOperations`.\n- Without a database: `createFakeAdapter` from `@aventara/testing`. `execute` gets each operation as the framework hands it to the adapter, `{ resource, family, variant, arguments }`, and returns what the database would: a row, a list of rows, or a count (`null` for no match). The answer is still checked against the call's `select`. Its `operations` and `transactions` record what ran; `rollback: { snapshot, restore }` lets a rolled-back transaction undo your own in-memory state.\n\n```ts\n// src/post.guards.spec.ts\nimport assert from \"node:assert/strict\";\nimport { createFramework } from \"@aventara/core\";\nimport { createFakeAdapter } from \"@aventara/testing\";\nimport { aventaraConfig } from \"./aventara.config.js\";\nimport { discovery } from \"./generated/aventara/discovery.artifact.js\";\n\nconst adapter = createFakeAdapter({\n model: discovery.model,\n // The rows this test's one read answers.\n execute: (operation) => (operation.variant === \"many\" ? [{ id: 1, title: \"Hello\" }] : null),\n});\nconst framework = await createFramework({ ...aventaraConfig, adapter });\n\n// As a remote caller: the client layer's exposure and pipelines run.\nconst posts = await framework.serverClient.Post.find.many({ select: [\"id\", \"title\"] });\nassert.deepEqual(posts, [{ id: 1, title: \"Hello\" }]);\nassert.deepEqual(\n adapter.operations.map((operation) => `${operation.resource}.${operation.family}.${operation.variant}`),\n [\"Post.find.many\"],\n);\n```\n\n- Never import `@aventara/core/internal` in tests: it is not public API and changes in any release.\n\n## Agents (MCP)\n\n```ts\n// in aventaraConfig\nmcp: \"/mcp\",\nagent: {\n exposed: true,\n exemptions: { resources: { User: { operations: { delete: true } } } },\n},\n```\n\nOn NestJS, without `mcp.authorization` the endpoint answers callers on the same machine only (for your coding agent while developing); set `authorization: { servers, verify }` before deploying. On Next.js, `mcp` always needs `authorization`: without it the configuration is a type error, and building the Framework (`ready()`) refuses it. `servers` lists the OAuth authorization servers (https URLs) your agents get tokens from; `verify(token)` checks one bearer token and resolves the caller (the token's claims, say), or `null` to answer `401`. What it resolves is each tool call's `ctx.$state.mcp.claims`, beside what the host's `state` seeds: declare it in `AvState` (`mcp?: { claims: … }`) and authorize agents from it in the `agent` layer's guards. A guard can tell an agent apart: `ctx.$origin === \"agent\"`.\n",
|
|
8
|
+
"reference/stages.md": "# Stages reference\n\n## The stages\n\n| Stage | Key | Receives | Returns |\n|---|---|---|---|\n| Guard | `guards` | `(ctx)` | `true`/`undefined` to allow, `false` to deny (`A4001`); or throw an `AvError` |\n| Pipe | `pipes` | `(args, ctx)` | new arguments, or `undefined` to keep them; the only stage that changes arguments |\n| Hook | `hooks.before`, `hooks.after` | before `(ctx)`; after `(result, ctx)` | nothing |\n| Interceptor | `interceptors` | `(ctx, next)` | the result of `next()`, called exactly once |\n| Filter | `filters` | `(error, ctx)` | an `AvError` to answer with, or `undefined` to pass it on |\n| Validators (field) | `fields.<f>.pipelines.validators` | `AvZ` checks | data, part of the contract |\n| Field pipe | `fields.<f>.pipelines.pipes` | `(value)` | the new value, or `undefined` to keep it; no `ctx` |\n| Compute (field) | `fields.<f>.pipelines.compute` | `create`/`update`: `({ field, value, data, ctx })`; `read`: `({ field, value, record, ctx })`, or `{ dependsOn, compute }` around it to name the stored fields it reads | the field's value, or `undefined` to keep the current one |\n| Field write guard | `fields.<f>.pipelines.guards` | `(value, ctx)` | `false` refuses when the caller sends the field |\n\nA relation field takes write guards only. Stage types to annotate with: `AvGuard`, `AvPipe`, `AvBeforeHook`, `AvAfterHook`, `AvInterceptor`, `AvFilter`, `AvFieldPipe`, `AvFieldGuard`, `AvComputeCreate`, `AvComputeUpdate`, `AvComputeRead`, and `AvTransactionGuard`, `AvTransactionPipe`, `AvTransactionBeforeHook`, `AvTransactionAfterHook`, `AvTransactionInterceptor`, `AvTransactionFilter`. Pass your model (`AvAfterHook<Model>`, `type Model = typeof discovery.model`) when the stage calls `ctx.<Resource>`.\n\n## Levels\n\n| Level | Key | Runs for |\n|---|---|---|\n| Layer | `pipelines` | every operation of the layer |\n| Resource | `resources.<R>.pipelines` | every operation of R |\n| Family | `resources.<R>.operations.<family>.pipelines` | every variant of the family |\n| Variant | `resources.<R>.operations.<family>.<variant>.pipelines` | that operation; `ctx.$args` is typed |\n| Field | `resources.<R>.fields.<f>.pipelines` | field f's value |\n| Resource transactions | `resources.<R>.transactions.pipelines` | once per transaction with a top-level step on R |\n\nThe root's stages run for every origin; an overlay's only for its own (`ctx.$origin`: `\"application\"`, `\"client\"` or `\"agent\"`). Register a stage once, at the outermost level it applies to: a root stage copied into an overlay runs twice. Registering a stage does not expose an operation.\n\n## Order\n\n```text\nframework validation (required create fields included; a field compute.create fills is not required yet)\n-> guards layer -> Resource -> family -> variant (root, then overlay, at each), then field write guards\n-> pipes the same levels, then field pipes\n-> validators field validators and Json shapes on every value the caller sent, a field with a compute stage included (A2004)\n-> compute create or update (write compute)\n-> re-check Aventara's own checks on the pipe and compute output (A3004, A3000), then validators and Json shapes on the values compute set or changed (A2004)\n-> interceptors enter, outer to inner\n-> hooks.before\n-> operation one adapter call, then compute read on the returned rows\n-> hooks.after\n-> interceptors unwind\non a failure after validation: filters, inner to outer; the first that handles it wins\n```\n\nA call refused by a guard, the re-check or a validator runs no interceptor and no hook. A `unique` operation that matches no row fails at the operation (`AvNotFoundError`, `A2003`); no `after` hook runs.\n\nWith `forceTransaction` (or as a step of a transaction plan), guards, pipes and the validators of every literal value the caller sent run before the transaction; a refusal there opens nothing. Then: `BEGIN -> the Resource's transaction stages -> validators of $ref values (and of what a transaction pipe changed) -> write compute -> re-check (values compute set or changed) -> interceptors -> hooks.before -> adapter call -> hooks.after -> interceptors -> COMMIT`. A refusal inside rolls the transaction back. The hooks' `ctx` calls and `ctx.$sql` join it; the guards' and pipes' calls do not. So a guard's database check can be stale by the time the write commits (check-then-act): re-check a rule that must hold at commit in `hooks.before` or a transaction stage. `forceTransaction` needs transactions: under an effective `transactionsStyle: \"none\"` it is a startup error (`CONFIG_INVALID_VALUE`).\n\n## Resource transaction stages\n\n`resources.<R>.transactions.pipelines` takes every stage kind and runs once per transaction (a plan, or a `forceTransaction` call) that has a top-level step on R, after `BEGIN`. `ctx.$args` is the plan, frozen: each step `{ resource, family, variant, args }` or `{ sql }`; there is no `ctx.$operation`. A transaction pipe may change a step's `args`, never the steps (`A3004`). There is no layer-wide transaction stage: put it on each Resource it guards. A nested write's target is not touched.\n\n```ts\nimport type { AvTransactionGuard } from \"@aventara/core\";\n\n// A client plan may delete at most one post.\nexport const onePostDeletePerPlan: AvTransactionGuard = (ctx) =>\n ctx.$origin === \"application\" ||\n ctx.$args.filter((step) => step.resource === \"Post\" && step.family === \"delete\").length <= 1;\n\n// resources: { Post: { transactions: { pipelines: { guards: [onePostDeletePerPlan] } } } }\n```\n\n## Rules that are easy to miss\n\n- `ctx` calls (`ctx.<Resource>.<family>.<variant>(args)`) are raw: validation, field pipes, compute and validators run; guards, pipes, hooks, interceptors, filters and transaction stages do not. They are application-origin: the `application` overlay's exposure applies, the `client` overlay's does not. They resolve data directly and throw the `AvError` subclass of a failure. A `ctx` create need not write a field the root's or the `application` overlay's `compute.create` fills: the generated types state those fields (run `aventara:prepare` after changing compute); a field only the `client` or `agent` overlay computes is still required.\n- `ctx.$args` is frozen. A pipe returns new arguments; an `after` hook gets a frozen result. To answer with a different result, use an interceptor. A `Date` or bytes value cannot be frozen, so each stage gets its own copy: changing one in place changes nothing another stage sees, what is written or what the caller receives.\n- ``await ctx.$sql<Row>`select id from \"Post\" where \"authorId\" = ${userId}` `` is a tagged template: each `${…}` is bound as a parameter, never spliced into the text. It resolves the rows as `Row[]` and runs in the current transaction (outside one, as a plain statement).\n- `await ctx.$transaction(async (tx) => { … })` runs the work in a transaction, `tx` being this `ctx` bound to it, and resolves what the work resolves; a throw rolls it back. Inside an open transaction it joins it.\n- `ctx.$transport` is set only for a remote call over HTTP: `method` and `headers`, with lower-cased header names (`ctx.$transport?.headers[\"x-request-source\"]`). Server code's calls have none. Who the caller is belongs in `ctx.$state`, seeded once by the host's `state`.\n- `ctx.$state` is the only thing a stage writes in place: one object per request, shared by every stage and inherited by `ctx` calls. Compute can read it, not write it.\n- `compute.read` gets the row (`record`) frozen, with its own copy of each `Date` or bytes value: return the field's value. Writing into `record` throws and answers `A3000`; the answer is built from the row as read.\n- Compute gets a read-only `ctx`: `find.*` calls only, no writes, no `$sql`, no `$transaction` (reaching one answers `A3000`). Effects on other rows belong in an `after` hook under `forceTransaction`.\n- A required create field the caller left out is refused before the guards; no pipe can fill it. Use `compute.create` or a database default.\n- Each value is judged once. Every value the caller sent is judged after its field pipes and before write compute, a field with its own compute stage included, so compute never sees a value its own validators refuse; a bad value is refused even when compute would replace it. After compute, only a value compute set or changed is judged (a compute that returns `undefined` or the caller's value changes nothing): a result that fails a validator answers `A2004` when the caller's layer shows the field. For a field the caller's layer hides, the root's validators judge it, and a refusal is the server's defect: `500 A3000`, with an `AvComputedValueError` sent to `diagnostics`. Aventara's own re-check comes first: a pipe that returns a wrong type or an unknown key, or drops a required field, answers `A3004`, as does a pipe or compute that writes a reference twice; a compute that returns a wrong type or a value its field's `format` refuses (a relative path for `format: \"url\"`), or leaves a required field missing, answers `A3000`.\n- Validators do not judge a directive's operand (`{ $increment: 500 }`); `$push` entries are judged.\n- `pipelines.cache` runs nothing: it is data that lets a read be served as a cacheable `GET` (`reference/config.md`, Caching reads), and the one `pipelines` key that does not add up across levels. Every stage runs on that `GET` as on the `POST`; `ctx.$transport.method` tells them apart.\n- Calls grouped into one `POST /_all` each run their own full pipeline, exactly as alone (`reference/client.md`).\n- A Resource reached only through a nested write runs that Resource's guards and field stages, not its transaction stages, hooks or interceptors.\n- Without a transaction, a write has committed when its `after` hook runs.\n- A guard or pipe that throws a non-`AvError` answers `A3000` and the real error goes to `diagnostics`; throw `new AvError(code, message)` to answer a code. A thrown `A2004` may carry `issues: [{ path: [\"arguments\", …], code: \"V1001\", message }]`.\n- A pipe's output is re-checked against the server's own contract before compute and validators see it: returning a field the Resource does not have, or a wrong type, answers `A3004`. Name pipes (`function scopeToTenant(…)`) so the server log names them.\n"
|
|
7
9
|
};
|
|
8
|
-
export const AGENT_SKILL_ERRORS = "# Aventara error codes\n\nEvery code `@aventara/core` 1.0.0-rc.11 answers, reports in a validation issue, or refuses to start with. Generated from `AvErrorCatalog` (exported by `@aventara/core`); do not edit.\n\nBranch on the code, never on the message. In code, `AvErrorCatalog[code]` gives the same entry: `{ code, kind, message, cause, fix, docs }`.\n\n## Success codes (A1xxx)\n\nAn operation's `code` when it succeeded. The generated client resolves for these.\n\n| Code | Meaning | Cause | Fix |\n|---|---|---|---|\n| [`A1000`](https://aventara.razor34.dev/framework/docs/error-codes#a1000) | Success. | A read, a count or another operation without a more specific success code succeeded. | Nothing: the call succeeded. |\n| [`A1001`](https://aventara.razor34.dev/framework/docs/error-codes#a1001) | No match. | A first-style operation (find.first, update.first, delete.first) matched no row; data is null and the generated client resolves null. | Nothing: handle the null result. A unique operation answers A2003 instead. |\n| [`A1002`](https://aventara.razor34.dev/framework/docs/error-codes#a1002) | Created. | create.one created a row. | Nothing: the call succeeded. |\n| [`A1003`](https://aventara.razor34.dev/framework/docs/error-codes#a1003) | Created many. | create.many or create.count created rows. | Nothing: the call succeeded. |\n| [`A1004`](https://aventara.razor34.dev/framework/docs/error-codes#a1004) | Updated. | update.first or update.unique updated a row. | Nothing: the call succeeded. |\n| [`A1005`](https://aventara.razor34.dev/framework/docs/error-codes#a1005) | Updated many. | update.many or update.count updated rows. | Nothing: the call succeeded. |\n| [`A1006`](https://aventara.razor34.dev/framework/docs/error-codes#a1006) | Deleted. | delete.first or delete.unique deleted a row. | Nothing: the call succeeded. |\n| [`A1007`](https://aventara.razor34.dev/framework/docs/error-codes#a1007) | Deleted many. | delete.count deleted rows. | Nothing: the call succeeded. |\n| [`A1008`](https://aventara.razor34.dev/framework/docs/error-codes#a1008) | Upserted. | upsert.unique created or updated a row. | Nothing: the call succeeded. |\n| [`A1009`](https://aventara.razor34.dev/framework/docs/error-codes#a1009) | Transaction committed. | A whole transaction plan committed; data holds one result per step. | Nothing: the plan committed. |\n\n## Request errors (A2xxx)\n\nThe caller can fix these. The generated client and server-side calls throw the code's `AvError` subclass.\n\n| Code | Meaning | Cause | Fix |\n|---|---|---|---|\n| [`A2000`](https://aventara.razor34.dev/framework/docs/error-codes#a2000) | Invalid request. | The HTTP request cannot be interpreted: malformed JSON, a body that is not a JSON object, an unknown `/_*` path, a body that does not decode in its Content-Encoding, or a missing Aventara-Protocol-Version or Aventara-Contract-Hash header (the message names it). | Send a JSON object with both identity headers. The generated client does this for you. |\n| [`A2001`](https://aventara.razor34.dev/framework/docs/error-codes#a2001) | Unknown Resource. | The Resource is not in the caller's contract: misspelt, hidden by exposure, or added to the schema after aventara:prepare last ran. cause.message is \"The requested Resource is not available.\" and the issue is V1005 at `[\"resource\"]`. | Use the model's exact name. After a schema change run the adapter's generate step (`npm run aventara:prepare` in a scaffolded project), restart, and regenerate the client. A Resource hidden on purpose is reached from server code (framework.application). |\n| [`A2002`](https://aventara.razor34.dev/framework/docs/error-codes#a2002) | Unknown operation. | The operation is not available on that Resource: a typo, removed by exposure, an operation the adapter does not offer (such as delete.many), or a ctx.$sql, appTx.$sql or ctx.$transaction the adapter or the layer cannot run. The issue is V1006 at `[\"family\"]` or `[\"variant\"]`. | Call an operation the contract lists (`GET <api>/_contract`), regenerate a stale client, and check the layer's exemptions and transactionsStyle. |\n| [`A2003`](https://aventara.razor34.dev/framework/docs/error-codes#a2003) | Not found. | A unique operation (find.unique, update.unique, delete.unique) matched no row, or a $connect names a record that does not exist. The operation fails, so no after hook runs. Under a policy, a row outside the caller's read scope is a miss too, and so is a single-row update or delete of a row the caller cannot read, so the answer does not reveal that the row exists. A row the caller can read but not change answers A4002. | Catch AvNotFoundError, or use a first-style operation, which answers A1001 with null. Check the $connect identifier. |\n| [`A2004`](https://aventara.razor34.dev/framework/docs/error-codes#a2004) | Validation failed. | The arguments break a contract rule or a field validator. cause.issues lists each problem by V-code and path; a validator's issue also has check and params. The validators judge the values after the pipes and write compute, so a computed value they refuse is here too, at its field. A required create field left out is here (V1000). So is a value the database itself refused: too long for its column or its index, or out of its range (V1004), or holding a character it cannot store or a pattern it cannot read (V1001), with the issue at `[\"arguments\"]`, or at the value where the adapter can place it (a bigint its driver cannot bind), and its message saying what the database refused. So is a record written through a relation that writes that relation, or a field holding its reference, back (V1012, at that field), where the adapter admits no such write. | Fix the argument each issue's path points at, as its V-code says. A field the server must fill takes a compute.create stage. For a value the database refused, send one its column holds. For a nested write that writes its relation back, leave that field out: the parent supplies it. |\n| [`A2005`](https://aventara.razor34.dev/framework/docs/error-codes#a2005) | Contract mismatch. | The caller's Aventara-Contract-Hash is not the server's current one: the schema, the configuration, the limits or the exposure changed, or the client targets another deployment. | Regenerate the client against the running server: `npm run avclient:generate`. |\n| [`A2006`](https://aventara.razor34.dev/framework/docs/error-codes#a2006) | Protocol mismatch. | Aventara-Protocol-Version is not 1. | Send `Aventara-Protocol-Version: 1`, or upgrade the client. |\n| [`A2007`](https://aventara.razor34.dev/framework/docs/error-codes#a2007) | Invalid transaction reference. | A transaction $ref or fingerprint is invalid: the issues are V1010, V1011, V1016, V1017 or V1018, and cause.operation is the step. A $ref with no fingerprint is A2004 / V1000. | Let the generated client or framework.appTx build plans; check the $ref path and that the referenced step comes earlier. |\n| [`A2008`](https://aventara.razor34.dev/framework/docs/error-codes#a2008) | Conflict. | A uniqueness or reference constraint failed: a duplicate value, a missing parent, or a $disconnect or $set that would orphan a required child (also through update.first or a developer SQL statement). Sending it again unchanged fails the same way. | Change the data. For a duplicate, update the existing row or use upsert.unique. |\n| [`A2009`](https://aventara.razor34.dev/framework/docs/error-codes#a2009) | Limit exceeded. | A contract limit was crossed: nesting depth (V1013), list or plan size (V1014), boolean nodes (V1015) or Json value depth (V1021). | Ask for less (paginate, split the plan), or raise the limit under `limits`. |\n| [`A2010`](https://aventara.razor34.dev/framework/docs/error-codes#a2010) | Payload too large. | The decoded request body exceeds maxRequestBytes (default 1048576). | Send less, or raise limits.maxRequestBytes. |\n| [`A2011`](https://aventara.razor34.dev/framework/docs/error-codes#a2011) | Unsupported media type. | The body is not application/json, or its Content-Encoding is not gzip, deflate or br. | Send `Content-Type: application/json`. |\n| [`A2012`](https://aventara.razor34.dev/framework/docs/error-codes#a2012) | Method not allowed. | The HTTP method does not fit the route; the Allow header names the right one (`POST` on operations and `/_transactions`, `GET` on `/_contract`). | Use the method the Allow header names. |\n| [`A2013`](https://aventara.razor34.dev/framework/docs/error-codes#a2013) | Concurrent modification. | A competing writer reached the state first; nothing was written. | Retry. No retry is automatic. |\n| [`A2014`](https://aventara.razor34.dev/framework/docs/error-codes#a2014) | Stale selection. | The selected state changed before the write; nothing was written. | Read again, then retry. |\n\n## Server errors (A3xxx)\n\nNot caller-actionable: `cause.message` is generic, and the real error goes to the root `diagnostics` sink.\n\n| Code | Meaning | Cause | Fix |\n|---|---|---|---|\n| [`A3000`](https://aventara.razor34.dev/framework/docs/error-codes#a3000) | Internal error. | An unexpected failure on the server: a stage threw an error that is not an AvError, an interceptor called next() twice, a compute stage reached a write through its read-only ctx, a compute stage returned a value of the wrong type, or a compute.create returned undefined for a required field. cause.message is always \"Internal framework error.\" | Read the real error in the diagnostics sink (the root `diagnostics` key) or the server log line for the request id, and fix that stage. To answer with a code of your choice, throw an AvError. |\n| [`A3001`](https://aventara.razor34.dev/framework/docs/error-codes#a3001) | Adapter error. | A database or ORM failure that is not something the caller can fix. cause.message is always \"Adapter execution failed.\" | Read the raw error in the diagnostics sink; check the database, its connection and the migration state. |\n| [`A3002`](https://aventara.razor34.dev/framework/docs/error-codes#a3002) | Transaction error. | Transaction infrastructure failed (commit or rollback, or the transaction expired or was closed under a step), or a Resource transaction interceptor called next() twice or resolved without calling it. | Read the diagnostics sink. Call next() exactly once in a transaction interceptor; retry an infrastructure failure. |\n| [`A3003`](https://aventara.razor34.dev/framework/docs/error-codes#a3003) | Serialization error. | A result could not be encoded for transport. | Read the diagnostics sink; make compute.read stages and Json values return plain data of the field's type. |\n| [`A3004`](https://aventara.razor34.dev/framework/docs/error-codes#a3004) | Pipe output invalid. | A server pipe, field pipe or transaction pipe (or write compute) returned arguments the server's own contract refuses: a field the Resource does not have, a wrong type, a dropped required field, a reference written together with its relation (V1012), or a changed plan. cause.message is always \"A server pipeline produced invalid arguments.\" | Read the server log line, which names the pipe, the operation and the path, and fix the pipe. A pipe returns a new object of the same shape, or undefined to keep the arguments. |\n\n## Authentication and authorization (A4xxx)\n\nProduced by your guards; the framework never authenticates by itself.\n\n| Code | Meaning | Cause | Fix |\n|---|---|---|---|\n| [`A4000`](https://aventara.razor34.dev/framework/docs/error-codes#a4000) | Unauthenticated. | A guard threw new AvError(\"A4000\", ...): authentication is required or failed. The framework never authenticates by itself. | Send the credentials your guards expect, such as the Authorization header. |\n| [`A4001`](https://aventara.razor34.dev/framework/docs/error-codes#a4001) | Forbidden. | A guard returned false (\"Operation denied by a framework guard.\"), a field write guard or a nested write's target guard included, or a stage threw A4001. | Call as a caller the guard admits, or stop sending the guarded field. To answer another code, throw an AvError from the guard. |\n| [`A4002`](https://aventara.razor34.dev/framework/docs/error-codes#a4002) | Policy denied. | The configuration's policy refused the request: its engine does not allow the operation, a field the request names, writes or returns, a row it reaches, a row it creates, or the row a change leaves (moved out of what the caller reads, or failing its grant's after condition, which rolls the write back); on a database without interactive transactions, a write the policy can only check after it runs; or a stage threw new AvError(\"A4002\", ...). | Read cause.message, which names what the policy refused. Call as a principal the policy allows, leave the refused field or relation out (a governed one is named only where every row grants it), or change only rows the caller may change and still read afterwards. |\n\n## Validation issues (V1xxx)\n\nEach entry of `cause.issues[]` under `A2001`, `A2002`, `A2004`, `A2007` or `A2009`: `{ code, path, message }`, plus `check` and `params` for a failed field validator.\n\n| Code | Meaning | Cause | Fix |\n|---|---|---|---|\n| [`V1000`](https://aventara.razor34.dev/framework/docs/error-codes#v1000) | Required value missing. | A required value is absent: a create leaves out a required field that has no database default and no compute.create (the path names the field), a required relation and its reference field are both missing, a typed Json field lacks a required key, or a $ref has no fingerprint. | Send the value at the issue's path. When the server must fill it, give the field a compute.create stage or a database default; a pipe cannot fill it. |\n| [`V1001`](https://aventara.razor34.dev/framework/docs/error-codes#v1001) | Invalid runtime/wire type. | A value of the wrong type: a string where a number is expected, a negative limit, a non-integer id, null given to an operator with no null meaning (gt, contains, in), two directives in one scalar action object (`{ $increment: 1, $decrement: 2 }`), or a server-side value that is not plain data. Also a value that would make the call reach more rows: `undefined` in a filter, a unique selector or a cursor, an empty boolean node (`NOT: {}`, `AND: []`, `OR: []`) in the where of a write (update or delete many, count or first, a to-many $update or $delete). | Send the type the contract states at that path (the generated client's types say it). |\n| [`V1002`](https://aventara.razor34.dev/framework/docs/error-codes#v1002) | Invalid enum value. | A value that is not a member of the enum, or outside a typed Json field's AvZ.enum or AvZ.literal. | Send one of the enum's members. |\n| [`V1003`](https://aventara.razor34.dev/framework/docs/error-codes#v1003) | Invalid format (for example UUID/DateTime/base64). | A malformed datetime, decimal, base64 or UUID string, or a failed pattern or format validator (`check: \"string_format\"`). | Send the canonical encoding (ISO-8601 UTC datetime, decimal string, base64 bytes) and satisfy the validator that check and params name. |\n| [`V1004`](https://aventara.razor34.dev/framework/docs/error-codes#v1004) | Value outside accepted range. | A number, length or size outside its range: a failed bound, length or size validator, a BigInt outside signed 64 bits, an AvZ.int() outside the safe integer range, or a transaction with no operations. Under A2004 it is also a value the database refused as too long or out of its range, a BigInt beyond what the data source's driver can bind among them (some bind at most ±(2^53 − 1)). | Send a value inside the range; check and params name the validator that failed. |\n| [`V1005`](https://aventara.razor34.dev/framework/docs/error-codes#v1005) | Unknown or inaccessible field. | A field that does not exist or is hidden in the caller's layer, an unknown Resource (under A2001), or a key a typed Json field's shape does not declare. | Use a field the caller's contract exposes, expose it through exemptions, or make the call from server code. |\n| [`V1006`](https://aventara.razor34.dev/framework/docs/error-codes#v1006) | Unsupported filter/order/mutation directive. | An operator, directive or nested write the field does not offer (at any depth, inside not too; a to-many $update's data takes only what a bulk write takes), a mode it does not offer, or an operation that is not available (under A2002). | Use what the contract lists for that field or Resource, or expose it through exemptions. |\n| [`V1007`](https://aventara.razor34.dev/framework/docs/error-codes#v1007) | Invalid unique identifier shape. | The where of a unique operation is not exactly one complete identifier. | Pass exactly one identifier: the id, or every field of one @unique or @@unique. |\n| [`V1008`](https://aventara.razor34.dev/framework/docs/error-codes#v1008) | Invalid select/include/reducer projection. | Both select and include, a write-only field selected, or a relation that is not selectable there. | Use select or include, not both, and select only readable fields. |\n| [`V1009`](https://aventara.razor34.dev/framework/docs/error-codes#v1009) | Invalid or non-deterministic cursor. | A cursor without an explicit orderBy, or cursor keys that do not match the ordered fields. | Give an orderBy and a cursor holding exactly its ordered fields. |\n| [`V1010`](https://aventara.razor34.dev/framework/docs/error-codes#v1010) | Invalid transaction reference path. | A $ref path the earlier step does not return, a $ref to a step that is not in the transaction, or to a step with no single row (a many or count step). | Select the referenced field in the earlier step, and reference a single-row step. |\n| [`V1011`](https://aventara.razor34.dev/framework/docs/error-codes#v1011) | Transaction reference type mismatch. | The referenced value's type does not fit its target, a nullable source where only a non-null value is accepted included. | Reference a field of the target's type, non-null where the target needs it. |\n| [`V1012`](https://aventara.razor34.dev/framework/docs/error-codes#v1012) | Invalid relation action/cardinality. | A nested write that does not fit the relation (a list where one record is expected), a reference field set together with its own relation, one record mixing reference fields and relation writes where the adapter cannot run both, or a record written through a relation that writes that relation (or a field holding its reference) back, where the adapter admits no such write. Also a to-one $update or $upsert beside $connect, $connectOrCreate, $create, $set or $disconnect on the same relation: the row it would update is the newly linked one. | Write either the relation or its reference field, in the relation's cardinality. Relink a to-one relation in one write and update the linked row in another. |\n| [`V1013`](https://aventara.razor34.dev/framework/docs/error-codes#v1013) | Maximum nesting depth exceeded. | Nested projections, filters or writes deeper than maxNestingDepth. | Nest less, or raise limits.maxNestingDepth. |\n| [`V1014`](https://aventara.razor34.dev/framework/docs/error-codes#v1014) | Configured limit exceeded. | A limit above maxListLimit, or more steps than maxTransactionOperations. | Paginate or split the plan, or raise the limit. |\n| [`V1015`](https://aventara.razor34.dev/framework/docs/error-codes#v1015) | Boolean/filter node limit exceeded. | A where tree with more nodes than maxBooleanNodes. | Simplify the filter (in instead of many OR clauses), or raise limits.maxBooleanNodes. |\n| [`V1016`](https://aventara.razor34.dev/framework/docs/error-codes#v1016) | Invalid transaction reference index. | A $ref to the referencing step itself or a later one, or to an index no step has. | Reference an earlier step only. |\n| [`V1017`](https://aventara.razor34.dev/framework/docs/error-codes#v1017) | Transaction fingerprint mismatch. | A hand-written plan whose fingerprint does not match its step. | Let the generated client, framework.appTx or framework.clientTx build the plan. |\n| [`V1018`](https://aventara.razor34.dev/framework/docs/error-codes#v1018) | Transaction reference resolved to no value. | The referenced step returned no value at that path, such as a first step that matched no row. | Make sure the earlier step returns a row, or use a unique step, which fails with A2003. |\n| [`V1019`](https://aventara.razor34.dev/framework/docs/error-codes#v1019) | Transaction step depends on a row removed by an earlier step's cascade | A later step uses a row that an earlier step's delete removed through a database cascade. | Reorder or drop the dependent step. |\n| [`V1020`](https://aventara.razor34.dev/framework/docs/error-codes#v1020) | Transaction mixes application and client steps. | One framework.transaction([...]) list mixes appTx and clientTx steps. | Build every step of a plan from one origin. |\n| [`V1021`](https://aventara.razor34.dev/framework/docs/error-codes#v1021) | Json value depth limit exceeded. | A Json field's value nests deeper than maxJsonDepth. | Flatten the value, or raise limits.maxJsonDepth. |\n\n## Startup: configuration\n\n`createFramework` (and `AventaraModule`) throws one `AvConstructionError` with `stage: \"configuration\"`; each diagnostic is `{ code, path, message, docs }`.\n\n| Code | Meaning | Cause | Fix |\n|---|---|---|---|\n| [`CONFIG_REQUIRED`](https://aventara.razor34.dev/framework/docs/error-codes#config_required) | A required configuration key is missing. | A key that must be present is absent: api, adapter, or an overlay's exposed when the overlay sets exemptions, or policy.engine or policy.principal when policy is set. | Add the key at the diagnostic's path. |\n| [`CONFIG_UNKNOWN_PROPERTY`](https://aventara.razor34.dev/framework/docs/error-codes#config_unknown_property) | Unknown configuration key. | A key the configuration does not define at that place: a typo, or a key written at the wrong level of the tree. `policy` and `prebuilt` are root-only keys; in an overlay they read as unknown. | Remove it, or move it where it belongs (the configuration reference's \"Where a key can go\"). |\n| [`CONFIG_INVALID_TYPE`](https://aventara.razor34.dev/framework/docs/error-codes#config_invalid_type) | A configuration key has the wrong type. | A value of the wrong type, or a key set to an explicit undefined. | Give the value the type the reference states; leave a key out rather than setting it to undefined. |\n| [`CONFIG_INVALID_VALUE`](https://aventara.razor34.dev/framework/docs/error-codes#config_invalid_value) | A configuration value is not allowed. | A malformed api or mcp path, a negative limit, an unknown transactionsStyle, a format that is not a named zod format, forceTransaction under an effective transactionsStyle \"none\", an after check on any action but update, or a policy engine's governed, paths or afterChecks naming a Resource, field or path the Core Contract does not have, or its unheld naming a Resource the data source's model does not have (the message names the Resource or the rule). | Use a value the message allows. |\n| [`CONFIG_MOVED_PROPERTY`](https://aventara.razor34.dev/framework/docs/error-codes#config_moved_property) | A configuration key moved in rc.1. | Configuration written for 1.0.0-rc.0: restrictions, entrypoint, fields, behaviors, transactions or pipelines.before/after. | Move it where the message says: restrictions to exposed + exemptions, entrypoint to api, fields to `resources.<R>.fields.<f>.definition`, transactions to transactionsStyle, before/after to pipelines.hooks. |\n| [`CONFIG_FORBIDDEN_IN_OVERLAY`](https://aventara.razor34.dev/framework/docs/error-codes#config_forbidden_in_overlay) | A root-only key is written in an overlay. | A field definition in application, client or agent. | Define the field once at the root (`resources.<R>.fields.<f>.definition`); an overlay only chooses its visibility. |\n\n## Startup: discovery\n\n`AvConstructionError` with `stage: \"discovery\"`: what the adapter reported does not hold together.\n\n| Code | Meaning | Cause | Fix |\n|---|---|---|---|\n| [`DISCOVERY_TRANSACTION_CAPABILITY_MISMATCH`](https://aventara.razor34.dev/framework/docs/error-codes#discovery_transaction_capability_mismatch) | The adapter's transaction capability does not match it. | The adapter declares interactive transactions but provides no transaction function. | A defect of the adapter: install the adapter release that matches @aventara/core exactly. |\n\n## Startup: contract compilation\n\n`AvConstructionError` with `stage: \"contract-compilation\"`; a diagnostic's `path` starts with its layer (`root.`, `application.`, `client.`, `agent.`).\n\n| Code | Meaning | Cause | Fix |\n|---|---|---|---|\n| [`COMPILER_UNKNOWN_RESOURCE`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_unknown_resource) | Unknown Resource. | A Resource name the adapter's model does not have, in resources or in exemptions; or a client or agent entry for a Resource managed elsewhere, which those layers never have. | Use the model's exact name. After a schema change run `npm run aventara:prepare`. Configure a Resource managed elsewhere at the root or in application only. |\n| [`COMPILER_UNKNOWN_FIELD`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_unknown_field) | Unknown field. | A field that is neither in the schema nor defined at the root. | Fix the name, run aventara:prepare after a schema change, or add the field with a root definition. |\n| [`COMPILER_RELATION_FIELD_CONFIG_UNSUPPORTED`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_relation_field_config_unsupported) | A relation field takes write guards only. | Field pipes or compute on a relation field. | Put the stage on the relation's reference field, or use an operation pipe. |\n| [`COMPILER_INCOMPLETE_FIELD`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_incomplete_field) | An added field is incomplete. | A definition that adds a field leaves out type, nullable or list. | Spell out type, nullable and list, with lifecycle: [\"VIRTUAL\"] for a field that has no column. |\n| [`COMPILER_STRUCTURAL_FIELD_OVERRIDE`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_structural_field_override) | A discovered field's structure was redefined. | A definition on a field the adapter discovered changes its type, format or capabilities. | Restate only its type, nullable and list. Change capabilities through exemptions and the column through the schema. |\n| [`COMPILER_MISSING_SCALAR`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_missing_scalar) | Unknown scalar type. | A definition names a scalar type that does not exist. | Use a built-in scalar: string, boolean, int, float, bigint, decimal, datetime, bytes or json. |\n| [`COMPILER_MISSING_ENUM`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_missing_enum) | Unknown enum. | A definition names an enum the model does not have. | Use an enum of the schema, and run aventara:prepare after adding one. |\n| [`COMPILER_DANGLING_RELATION`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_dangling_relation) | A visible relation points at a hidden Resource. | A layer hides a Resource but still exposes a relation field that targets it. | Hide the relation field too (`exemptions.resources.<R>.fields.<relation>: true`), or expose the target. |\n| [`COMPILER_UNAVAILABLE_OPERATION`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_unavailable_operation) | Operation not available. | An operation the adapter does not offer (such as delete.many), or an overlay granting one that its parent hid. | Remove it. To reach an operation from one overlay only, expose it at the root and hide it in the other overlay. |\n| [`COMPILER_UNAVAILABLE_CAPABILITY`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_unavailable_capability) | Capability not available. | A capability or token the field does not offer, or an overlay (or the agent layer) granting one its parent hid. Overlays only narrow. | Remove it, or expose it in the parent layer and hide it in the layer it must not reach. |\n| [`COMPILER_INVALID_LIFECYCLE`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_invalid_lifecycle) | Invalid field lifecycle. | An added field declares a lifecycle other than VIRTUAL, a VIRTUAL field claims a database-generated lifecycle or a provider-side capability, or a definition redefines a discovered field's lifecycle. | Give an added field lifecycle: [\"VIRTUAL\"] and only the capabilities it can serve; computed lifecycles come from compute stages. |\n| [`COMPILER_MISSING_READ_COMPUTE`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_missing_read_compute) | A selectable virtual field has no compute.read. | A VIRTUAL field that can be selected has no pipelines.compute.read stage. | Add a compute.read stage, or make the field write-only with capabilities.select: false. |\n| [`COMPILER_INVALID_COMPUTE_DEPENDENCY`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_invalid_compute_dependency) | Invalid compute.read dependency. | A dependsOn entry that is not a stored scalar field of the same Resource. | List only stored scalar fields of the Resource in dependsOn. |\n| [`COMPILER_RESERVED_RESOURCE_NAME`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_reserved_resource_name) | Reserved Resource name. | A model name starts with $, which is reserved for the framework's own names (ctx.$sql, $ref), or is the name of a member beside the Resources: all on serverClient (and so on Next.js's avClient), and on a host that adds its own, one of those (on Next.js: tx and transaction on avClient, appTx and transaction on avApp). | Rename the model; the table it maps to can keep its name. A host member's name is also left free when the layer it belongs to hides the Resource. |\n| [`COMPILER_MISSING_IDENTIFIER`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_missing_identifier) | A unique operation has no identifier. | A layer exposes a unique variant (find.unique, update.unique, delete.unique, upsert.unique) and no complete identifier of the Resource: a Pick that did not pick one, or an Omit that hid every one. | Expose an identifier field (id, or every field of one unique), or exempt the unique variants. |\n| [`COMPILER_HIDDEN_REQUIRED_FIELD`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_hidden_required_field) | A layer that creates a Resource hides a required field. | A layer still creates the Resource (a create or upsert variant, or a nested create) while a required field, or a required relation's reference field, is hidden or has its create capability exempted, and no compute.create fills it. | Expose the field, give it a compute.create stage, or exempt the layer's create operations. |\n| [`COMPILER_INVALID_VALIDATOR`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_invalid_validator) | A field validator cannot apply. | A validator that is not built with AvZ, does not fit the field's type (AvZ.maxLength on an int, AvZ.maxItems on a field that is not a list), or sits on a relation or Json field. | Use an AvZ check that fits the field's type; a Json field's checks go in its json shape. |\n| [`COMPILER_INVALID_JSON_SHAPE`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_invalid_json_shape) | A Json shape cannot apply. | A json shape on a field that is not a discovered Json field, a shape not built with AvZ, or one that admits null or absence at its top level. | Put the shape on a Json field, build it with AvZ, and keep its top level non-null (the column's nullability decides null). |\n| [`COMPILER_INVALID_CLIENT_CONTRACT`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_invalid_client_contract) | The compiled client contract is invalid. | The finished client contract failed its own structural check: a framework defect. | Report it with the diagnostic's path and message. |\n";
|
|
10
|
+
export const AGENT_SKILL_ERRORS = "# Aventara error codes\n\nEvery code `@aventara/core` 1.0.0-rc.13 answers, reports in a validation issue, or refuses to start with. Generated from `AvErrorCatalog` (exported by `@aventara/core`); do not edit.\n\nBranch on the code, never on the message. In code, `AvErrorCatalog[code]` gives the same entry: `{ code, kind, message, cause, fix, docs }`.\n\n## Success codes (A1xxx)\n\nAn operation's `code` when it succeeded. The generated client resolves for these.\n\n| Code | Meaning | Cause | Fix |\n|---|---|---|---|\n| [`A1000`](https://aventara.razor34.dev/framework/docs/error-codes#a1000) | Success. | A read, a count or another operation without a more specific success code succeeded. | Nothing: the call succeeded. |\n| [`A1001`](https://aventara.razor34.dev/framework/docs/error-codes#a1001) | No match. | A first-style operation (find.first, update.first, delete.first) matched no row; data is null and the generated client resolves null. | Nothing: handle the null result. A unique operation answers A2003 instead. |\n| [`A1002`](https://aventara.razor34.dev/framework/docs/error-codes#a1002) | Created. | create.one created a row. | Nothing: the call succeeded. |\n| [`A1003`](https://aventara.razor34.dev/framework/docs/error-codes#a1003) | Created many. | create.many or create.count created rows. | Nothing: the call succeeded. |\n| [`A1004`](https://aventara.razor34.dev/framework/docs/error-codes#a1004) | Updated. | update.first or update.unique updated a row. | Nothing: the call succeeded. |\n| [`A1005`](https://aventara.razor34.dev/framework/docs/error-codes#a1005) | Updated many. | update.many or update.count updated rows. | Nothing: the call succeeded. |\n| [`A1006`](https://aventara.razor34.dev/framework/docs/error-codes#a1006) | Deleted. | delete.first or delete.unique deleted a row. | Nothing: the call succeeded. |\n| [`A1007`](https://aventara.razor34.dev/framework/docs/error-codes#a1007) | Deleted many. | delete.count deleted rows. | Nothing: the call succeeded. |\n| [`A1008`](https://aventara.razor34.dev/framework/docs/error-codes#a1008) | Upserted. | upsert.unique created or updated a row. | Nothing: the call succeeded. |\n| [`A1009`](https://aventara.razor34.dev/framework/docs/error-codes#a1009) | Transaction committed. | A whole transaction plan committed; data holds one result per step. | Nothing: the plan committed. |\n\n## Request errors (A2xxx)\n\nThe caller can fix these. The generated client and server-side calls throw the code's `AvError` subclass.\n\n| Code | Meaning | Cause | Fix |\n|---|---|---|---|\n| [`A2000`](https://aventara.razor34.dev/framework/docs/error-codes#a2000) | Invalid request. | The HTTP request cannot be interpreted: malformed JSON, a body that is not a JSON object, an unknown `/_*` path, a body that does not decode in its Content-Encoding, or a missing Aventara-Protocol-Version or Aventara-Contract-Hash header (the message names it). | Send a JSON object with both identity headers. The generated client does this for you. |\n| [`A2001`](https://aventara.razor34.dev/framework/docs/error-codes#a2001) | Unknown Resource. | The Resource is not in the caller's contract: misspelt, hidden by exposure, or added to the schema after aventara:prepare last ran. cause.message is \"The requested Resource is not available.\" and the issue is V1005 at `[\"resource\"]`. | Use the model's exact name. After a schema change run the adapter's generate step (`npm run aventara:prepare` in a scaffolded project), restart, and regenerate the client. A Resource hidden on purpose is reached from server code (framework.application). |\n| [`A2002`](https://aventara.razor34.dev/framework/docs/error-codes#a2002) | Unknown operation. | The operation is not available on that Resource: a typo, removed by exposure, an operation the adapter does not offer (such as delete.many), or a ctx.$sql, appTx.$sql or ctx.$transaction the adapter or the layer cannot run. The issue is V1006 at `[\"family\"]` or `[\"variant\"]`. | Call an operation the contract lists (`GET <api>/_contract`), regenerate a stale client, and check the layer's exemptions and transactionsStyle. |\n| [`A2003`](https://aventara.razor34.dev/framework/docs/error-codes#a2003) | Not found. | A unique operation (find.unique, update.unique, delete.unique) matched no row, or a $connect names a record that does not exist. The operation fails, so no after hook runs. Under a policy, a row outside the caller's read scope is a miss too, and so is a single-row update or delete of a row the caller cannot read, so the answer does not reveal that the row exists. A row the caller can read but not change answers A4002. | Catch AvNotFoundError, or use a first-style operation, which answers A1001 with null. Check the $connect identifier. |\n| [`A2004`](https://aventara.razor34.dev/framework/docs/error-codes#a2004) | Validation failed. | The arguments break a contract rule or a field validator. cause.issues lists each problem by V-code and path; a validator's issue also has check and params. The validators judge every value the caller sent after its pipes and before write compute, a field with a compute stage included, so a bad value is refused even when compute would replace it; then a value write compute set or changed, so a computed value they refuse is here too, at its field. A required create field left out is here (V1000). So is a value the database itself refused: too long for its column or its index, or out of its range (V1004), or holding a character it cannot store or a pattern it cannot read (V1001), with the issue at `[\"arguments\"]`, or at the value where the adapter can place it (a bigint its driver cannot bind), and its message saying what the database refused. So is a record written through a relation that writes that relation, or a field holding its reference, back (V1012, at that field), where the adapter admits no such write. | Fix the argument each issue's path points at, as its V-code says. A field the server must fill takes a compute.create stage. For a value the database refused, send one its column holds. For a nested write that writes its relation back, leave that field out: the parent supplies it. |\n| [`A2005`](https://aventara.razor34.dev/framework/docs/error-codes#a2005) | Contract mismatch. | The caller's Aventara-Contract-Hash is not the server's current one: the schema, the configuration, the limits or the exposure changed, or the client targets another deployment. | Regenerate the client against the running server: `npm run avclient:generate`. |\n| [`A2006`](https://aventara.razor34.dev/framework/docs/error-codes#a2006) | Protocol mismatch. | Aventara-Protocol-Version is not 1. | Send `Aventara-Protocol-Version: 1`, or upgrade the client. |\n| [`A2007`](https://aventara.razor34.dev/framework/docs/error-codes#a2007) | Invalid transaction reference. | A transaction $ref or fingerprint is invalid: the issues are V1010, V1011, V1016, V1017 or V1018, and cause.operation is the step. A $ref with no fingerprint is A2004 / V1000. | Let the generated client or framework.appTx build plans; check the $ref path and that the referenced step comes earlier. |\n| [`A2008`](https://aventara.razor34.dev/framework/docs/error-codes#a2008) | Conflict. | A uniqueness or reference constraint failed: a duplicate value, a missing parent, or a $disconnect or $set that would orphan a required child (also through update.first or a developer SQL statement). Sending it again unchanged fails the same way. | Change the data. For a duplicate, update the existing row or use upsert.unique. |\n| [`A2009`](https://aventara.razor34.dev/framework/docs/error-codes#a2009) | Limit exceeded. | A contract limit was crossed: nesting depth (V1013), list or plan size (V1014), boolean nodes (V1015) or Json value depth (V1021). | Ask for less (paginate, split the plan), or raise the limit under `limits`. |\n| [`A2010`](https://aventara.razor34.dev/framework/docs/error-codes#a2010) | Payload too large. | The decoded request body exceeds maxRequestBytes (default 1048576). | Send less, or raise limits.maxRequestBytes. |\n| [`A2011`](https://aventara.razor34.dev/framework/docs/error-codes#a2011) | Unsupported media type. | The body is not application/json, or its Content-Encoding is not gzip, deflate or br. | Send `Content-Type: application/json`. |\n| [`A2012`](https://aventara.razor34.dev/framework/docs/error-codes#a2012) | Method not allowed. | The HTTP method does not fit the route; the Allow header names the right one (`POST` on operations and `/_transactions`, `GET` on `/_contract`). | Use the method the Allow header names. |\n| [`A2013`](https://aventara.razor34.dev/framework/docs/error-codes#a2013) | Concurrent modification. | A competing writer reached the state first; nothing was written. | Retry. No retry is automatic. |\n| [`A2014`](https://aventara.razor34.dev/framework/docs/error-codes#a2014) | Stale selection. | The selected state changed before the write; nothing was written. | Read again, then retry. |\n\n## Server errors (A3xxx)\n\nNot caller-actionable: `cause.message` is generic, and the real error goes to the root `diagnostics` sink.\n\n| Code | Meaning | Cause | Fix |\n|---|---|---|---|\n| [`A3000`](https://aventara.razor34.dev/framework/docs/error-codes#a3000) | Internal error. | An unexpected failure on the server: a stage threw an error that is not an AvError, an interceptor called next() twice, a compute stage reached a write through its read-only ctx, a compute stage returned a value of the wrong type, or a compute.create returned undefined for a required field. cause.message is always \"Internal framework error.\" | Read the real error in the diagnostics sink (the root `diagnostics` key) or the server log line for the request id, and fix that stage. To answer with a code of your choice, throw an AvError. |\n| [`A3001`](https://aventara.razor34.dev/framework/docs/error-codes#a3001) | Adapter error. | A database or ORM failure that is not something the caller can fix. cause.message is always \"Adapter execution failed.\" | Read the raw error in the diagnostics sink; check the database, its connection and the migration state. |\n| [`A3002`](https://aventara.razor34.dev/framework/docs/error-codes#a3002) | Transaction error. | Transaction infrastructure failed (commit or rollback, or the transaction expired or was closed under a step), or a Resource transaction interceptor called next() twice or resolved without calling it. | Read the diagnostics sink. Call next() exactly once in a transaction interceptor; retry an infrastructure failure. |\n| [`A3003`](https://aventara.razor34.dev/framework/docs/error-codes#a3003) | Serialization error. | A result could not be encoded for transport. | Read the diagnostics sink; make compute.read stages and Json values return plain data of the field's type. |\n| [`A3004`](https://aventara.razor34.dev/framework/docs/error-codes#a3004) | Pipe output invalid. | A server pipe, field pipe or transaction pipe (or write compute) returned arguments the server's own contract refuses: a field the Resource does not have, a wrong type, a dropped required field, a reference written together with its relation (V1012), or a changed plan. cause.message is always \"A server pipeline produced invalid arguments.\" | Read the server log line, which names the pipe, the operation and the path, and fix the pipe. A pipe returns a new object of the same shape, or undefined to keep the arguments. |\n\n## Authentication and authorization (A4xxx)\n\nProduced by your guards; the framework never authenticates by itself.\n\n| Code | Meaning | Cause | Fix |\n|---|---|---|---|\n| [`A4000`](https://aventara.razor34.dev/framework/docs/error-codes#a4000) | Unauthenticated. | A guard threw new AvError(\"A4000\", ...), or the host's or access provider's state rejected with an object whose status or statusCode is 401 (an authentication library's refusal, answered with this message only): authentication is required or failed. The framework never authenticates by itself. | Send the credentials your guards expect, such as the Authorization header. |\n| [`A4001`](https://aventara.razor34.dev/framework/docs/error-codes#a4001) | Forbidden. | A guard returned false (\"Operation denied by a framework guard.\"), a field write guard or a nested write's target guard included, a stage threw A4001, or the host's or access provider's state rejected with an object whose status or statusCode is 403 (answered with this message only). | Call as a caller the guard admits, or stop sending the guarded field. To answer another code, throw an AvError from the guard. |\n| [`A4002`](https://aventara.razor34.dev/framework/docs/error-codes#a4002) | Policy denied. | The configuration's policy refused the request: its engine does not allow the operation, a field the request names, writes or returns, a row it reaches, a row it creates, or the row a change leaves (moved out of what the caller reads, or failing its grant's after condition, which rolls the write back); on a database without interactive transactions, a write the policy can only check after it runs; or a stage threw new AvError(\"A4002\", ...). | Read cause.message, which names what the policy refused. Call as a principal the policy allows, leave the refused field or relation out (a governed one is named only where every row grants it), or change only rows the caller may change and still read afterwards. |\n\n## Validation issues (V1xxx)\n\nEach entry of `cause.issues[]` under `A2001`, `A2002`, `A2004`, `A2007` or `A2009`: `{ code, path, message }`, plus `check` and `params` for a failed field validator.\n\n| Code | Meaning | Cause | Fix |\n|---|---|---|---|\n| [`V1000`](https://aventara.razor34.dev/framework/docs/error-codes#v1000) | Required value missing. | A required value is absent: a create leaves out a required field that has no database default and no compute.create (the path names the field), a required relation and its reference field are both missing, a typed Json field lacks a required key, or a $ref has no fingerprint. | Send the value at the issue's path. When the server must fill it, give the field a compute.create stage or a database default; a pipe cannot fill it. |\n| [`V1001`](https://aventara.razor34.dev/framework/docs/error-codes#v1001) | Invalid runtime/wire type. | A value of the wrong type: a string where a number is expected, a negative limit, a non-integer id, null given to an operator with no null meaning (gt, contains, in), two directives in one scalar action object (`{ $increment: 1, $decrement: 2 }`), or a server-side value that is not plain data. Also a value that would make the call reach more rows: `undefined` in a filter, a unique selector or a cursor, an empty boolean node (`NOT: {}`, `AND: []`, `OR: []`) in the where of a write (update or delete many, count or first, a to-many $update or $delete). | Send the type the contract states at that path (the generated client's types say it). |\n| [`V1002`](https://aventara.razor34.dev/framework/docs/error-codes#v1002) | Invalid enum value. | A value that is not a member of the enum, or outside a typed Json field's AvZ.enum or AvZ.literal. | Send one of the enum's members. |\n| [`V1003`](https://aventara.razor34.dev/framework/docs/error-codes#v1003) | Invalid format (for example UUID/DateTime/base64). | A malformed datetime, decimal, base64 or UUID string, or a failed pattern or format validator (`check: \"string_format\"`). | Send the canonical encoding (ISO-8601 UTC datetime, decimal string, base64 bytes) and satisfy the validator that check and params name. |\n| [`V1004`](https://aventara.razor34.dev/framework/docs/error-codes#v1004) | Value outside accepted range. | A number, length or size outside its range: a failed bound, length or size validator, a BigInt outside signed 64 bits, an AvZ.int() outside the safe integer range, or a transaction with no operations. Under A2004 it is also a value the database refused as too long or out of its range, a BigInt beyond what the data source's driver can bind among them (some bind at most ±(2^53 − 1)). | Send a value inside the range; check and params name the validator that failed. |\n| [`V1005`](https://aventara.razor34.dev/framework/docs/error-codes#v1005) | Unknown or inaccessible field. | A field that does not exist or is hidden in the caller's layer, an unknown Resource (under A2001), or a key a typed Json field's shape does not declare. | Use a field the caller's contract exposes, expose it through exemptions, or make the call from server code. |\n| [`V1006`](https://aventara.razor34.dev/framework/docs/error-codes#v1006) | Unsupported filter/order/mutation directive. | An operator, directive or nested write the field does not offer (at any depth, inside not too; a to-many $update's data takes only what a bulk write takes), a mode it does not offer, or an operation that is not available (under A2002). | Use what the contract lists for that field or Resource, or expose it through exemptions. |\n| [`V1007`](https://aventara.razor34.dev/framework/docs/error-codes#v1007) | Invalid unique identifier shape. | The where of a unique operation is not exactly one complete identifier. | Pass exactly one identifier: the id, or every field of one @unique or @@unique. |\n| [`V1008`](https://aventara.razor34.dev/framework/docs/error-codes#v1008) | Invalid select/include/reducer projection. | Both select and include, a write-only field selected, or a relation that is not selectable there. | Use select or include, not both, and select only readable fields. |\n| [`V1009`](https://aventara.razor34.dev/framework/docs/error-codes#v1009) | Invalid or non-deterministic cursor. | A cursor without an explicit orderBy, or cursor keys that do not match the ordered fields. | Give an orderBy and a cursor holding exactly its ordered fields. |\n| [`V1010`](https://aventara.razor34.dev/framework/docs/error-codes#v1010) | Invalid transaction reference path. | A $ref path the earlier step does not return, a $ref to a step that is not in the transaction, or to a step with no single row (a many or count step). | Select the referenced field in the earlier step, and reference a single-row step. |\n| [`V1011`](https://aventara.razor34.dev/framework/docs/error-codes#v1011) | Transaction reference type mismatch. | The referenced value's type does not fit its target, a nullable source where only a non-null value is accepted included. | Reference a field of the target's type, non-null where the target needs it. |\n| [`V1012`](https://aventara.razor34.dev/framework/docs/error-codes#v1012) | Invalid relation action/cardinality. | A nested write that does not fit the relation (a list where one record is expected), a reference field set together with its own relation, one record mixing reference fields and relation writes where the adapter cannot run both, or a record written through a relation that writes that relation (or a field holding its reference) back, where the adapter admits no such write. Also a to-one $update or $upsert beside $connect, $connectOrCreate, $create, $set or $disconnect on the same relation: the row it would update is the newly linked one. | Write either the relation or its reference field, in the relation's cardinality. Relink a to-one relation in one write and update the linked row in another. |\n| [`V1013`](https://aventara.razor34.dev/framework/docs/error-codes#v1013) | Maximum nesting depth exceeded. | Nested projections, filters or writes deeper than maxNestingDepth. | Nest less, or raise limits.maxNestingDepth. |\n| [`V1014`](https://aventara.razor34.dev/framework/docs/error-codes#v1014) | Configured limit exceeded. | A limit above maxListLimit, more steps than maxTransactionOperations, or more write steps than maxTransactionWrites. | Paginate or split the plan, or raise the limit. |\n| [`V1015`](https://aventara.razor34.dev/framework/docs/error-codes#v1015) | Boolean/filter node limit exceeded. | A where tree with more nodes than maxBooleanNodes. | Simplify the filter (in instead of many OR clauses), or raise limits.maxBooleanNodes. |\n| [`V1016`](https://aventara.razor34.dev/framework/docs/error-codes#v1016) | Invalid transaction reference index. | A $ref to the referencing step itself or a later one, or to an index no step has. | Reference an earlier step only. |\n| [`V1017`](https://aventara.razor34.dev/framework/docs/error-codes#v1017) | Transaction fingerprint mismatch. | A hand-written plan whose fingerprint does not match its step. | Let the generated client, framework.appTx or framework.clientTx build the plan. |\n| [`V1018`](https://aventara.razor34.dev/framework/docs/error-codes#v1018) | Transaction reference resolved to no value. | The referenced step returned no value at that path, such as a first step that matched no row. | Make sure the earlier step returns a row, or use a unique step, which fails with A2003. |\n| [`V1019`](https://aventara.razor34.dev/framework/docs/error-codes#v1019) | Transaction step depends on a row removed by an earlier step's cascade | A later step uses a row that an earlier step's delete removed through a database cascade. | Reorder or drop the dependent step. |\n| [`V1020`](https://aventara.razor34.dev/framework/docs/error-codes#v1020) | Transaction mixes application and client steps. | One framework.transaction([...]) list mixes appTx and clientTx steps. | Build every step of a plan from one origin. |\n| [`V1021`](https://aventara.razor34.dev/framework/docs/error-codes#v1021) | Json value depth limit exceeded. | A Json field's value nests deeper than maxJsonDepth. | Flatten the value, or raise limits.maxJsonDepth. |\n\n## Startup: configuration\n\n`createFramework` (and `AventaraModule`) throws one `AvConstructionError` with `stage: \"configuration\"`; each diagnostic is `{ code, path, message, docs }`.\n\n| Code | Meaning | Cause | Fix |\n|---|---|---|---|\n| [`CONFIG_REQUIRED`](https://aventara.razor34.dev/framework/docs/error-codes#config_required) | A required configuration key is missing. | A key that must be present is absent: api, adapter, or an overlay's exposed when the overlay sets exemptions, or policy.engine or policy.principal when policy is set. | Add the key at the diagnostic's path. |\n| [`CONFIG_UNKNOWN_PROPERTY`](https://aventara.razor34.dev/framework/docs/error-codes#config_unknown_property) | Unknown configuration key. | A key the configuration does not define at that place: a typo, or a key written at the wrong level of the tree. `policy` and `prebuilt` are root-only keys; in an overlay they read as unknown. | Remove it, or move it where it belongs (the configuration reference's \"Where a key can go\"). |\n| [`CONFIG_INVALID_TYPE`](https://aventara.razor34.dev/framework/docs/error-codes#config_invalid_type) | A configuration key has the wrong type. | A value of the wrong type, or a key set to an explicit undefined. | Give the value the type the reference states; leave a key out rather than setting it to undefined. |\n| [`CONFIG_INVALID_VALUE`](https://aventara.razor34.dev/framework/docs/error-codes#config_invalid_value) | A configuration value is not allowed. | A malformed api or mcp path, a negative limit, an unknown transactionsStyle, a format that is not a named zod format, forceTransaction under an effective transactionsStyle \"none\", an after check on any action but update, or a policy engine's governed, paths or afterChecks naming a Resource, field or path the Core Contract does not have, or its unheld naming a Resource the data source's model does not have (the message names the Resource or the rule). | Use a value the message allows. |\n| [`CONFIG_MOVED_PROPERTY`](https://aventara.razor34.dev/framework/docs/error-codes#config_moved_property) | A configuration key moved in rc.1. | Configuration written for 1.0.0-rc.0: restrictions, entrypoint, fields, behaviors, transactions or pipelines.before/after. | Move it where the message says: restrictions to exposed + exemptions, entrypoint to api, fields to `resources.<R>.fields.<f>.definition`, transactions to transactionsStyle, before/after to pipelines.hooks. |\n| [`CONFIG_FORBIDDEN_IN_OVERLAY`](https://aventara.razor34.dev/framework/docs/error-codes#config_forbidden_in_overlay) | A root-only key is written in an overlay. | A field definition, or a Resource's managedElsewhere, in application, client or agent. | State it once at the root (`resources.<R>.fields.<f>.definition`, `resources.<R>.managedElsewhere`); an overlay only chooses visibility. |\n\n## Startup: discovery\n\n`AvConstructionError` with `stage: \"discovery\"`: what the adapter reported does not hold together.\n\n| Code | Meaning | Cause | Fix |\n|---|---|---|---|\n| [`DISCOVERY_TRANSACTION_CAPABILITY_MISMATCH`](https://aventara.razor34.dev/framework/docs/error-codes#discovery_transaction_capability_mismatch) | The adapter's transaction capability does not match it. | The adapter declares interactive transactions but provides no transaction function. | A defect of the adapter: install the adapter release that matches @aventara/core exactly. |\n\n## Startup: contract compilation\n\n`AvConstructionError` with `stage: \"contract-compilation\"`; a diagnostic's `path` starts with its layer (`root.`, `application.`, `client.`, `agent.`).\n\n| Code | Meaning | Cause | Fix |\n|---|---|---|---|\n| [`COMPILER_UNKNOWN_RESOURCE`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_unknown_resource) | Unknown Resource. | A Resource name the adapter's model does not have, in resources or in exemptions; or a client or agent entry for a Resource managed elsewhere, which those layers never have. | Use the model's exact name. After a schema change run `npm run aventara:prepare`. Configure a Resource managed elsewhere at the root or in application only. |\n| [`COMPILER_UNKNOWN_FIELD`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_unknown_field) | Unknown field. | A field that is neither in the schema nor defined at the root. | Fix the name, run aventara:prepare after a schema change, or add the field with a root definition. |\n| [`COMPILER_RELATION_FIELD_CONFIG_UNSUPPORTED`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_relation_field_config_unsupported) | A relation field takes write guards only. | Field pipes or compute on a relation field. | Put the stage on the relation's reference field, or use an operation pipe. |\n| [`COMPILER_INCOMPLETE_FIELD`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_incomplete_field) | An added field is incomplete. | A definition that adds a field leaves out type, nullable or list. | Spell out type, nullable and list, with lifecycle: [\"VIRTUAL\"] for a field that has no column. |\n| [`COMPILER_STRUCTURAL_FIELD_OVERRIDE`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_structural_field_override) | A discovered field's structure was redefined. | A definition on a field the adapter discovered changes its type, format or capabilities. | Restate only its type, nullable and list. Change capabilities through exemptions and the column through the schema. |\n| [`COMPILER_MISSING_SCALAR`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_missing_scalar) | Unknown scalar type. | A definition names a scalar type that does not exist. | Use a built-in scalar: string, boolean, int, float, bigint, decimal, datetime, bytes or json. |\n| [`COMPILER_MISSING_ENUM`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_missing_enum) | Unknown enum. | A definition names an enum the model does not have. | Use an enum of the schema, and run aventara:prepare after adding one. |\n| [`COMPILER_DANGLING_RELATION`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_dangling_relation) | A visible relation points at a hidden Resource. | A layer hides a Resource but still exposes a relation field that targets it. | Hide the relation field too (`exemptions.resources.<R>.fields.<relation>: true`), or expose the target. |\n| [`COMPILER_UNAVAILABLE_OPERATION`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_unavailable_operation) | Operation not available. | An operation the adapter does not offer (such as delete.many), or an overlay granting one that its parent hid. | Remove it. To reach an operation from one overlay only, expose it at the root and hide it in the other overlay. |\n| [`COMPILER_UNAVAILABLE_CAPABILITY`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_unavailable_capability) | Capability not available. | A capability or token the field does not offer, or an overlay (or the agent layer) granting one its parent hid. Overlays only narrow. | Remove it, or expose it in the parent layer and hide it in the layer it must not reach. |\n| [`COMPILER_INVALID_LIFECYCLE`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_invalid_lifecycle) | Invalid field lifecycle. | An added field declares a lifecycle other than VIRTUAL, a VIRTUAL field claims a database-generated lifecycle or a provider-side capability, or a definition redefines a discovered field's lifecycle. | Give an added field lifecycle: [\"VIRTUAL\"] and only the capabilities it can serve; computed lifecycles come from compute stages. |\n| [`COMPILER_MISSING_READ_COMPUTE`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_missing_read_compute) | A selectable virtual field has no compute.read. | A VIRTUAL field that can be selected has no pipelines.compute.read stage. | Add a compute.read stage, or make the field write-only with capabilities.select: false. |\n| [`COMPILER_INVALID_COMPUTE_DEPENDENCY`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_invalid_compute_dependency) | Invalid compute.read dependency. | A dependsOn entry that is not a stored scalar field of the same Resource. | List only stored scalar fields of the Resource in dependsOn. |\n| [`COMPILER_RESERVED_RESOURCE_NAME`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_reserved_resource_name) | Reserved Resource name. | A model name starts with $, which is reserved for the framework's own names (ctx.$sql, $ref), or is the name of a member beside the Resources: all on serverClient (and so on Next.js's avClient), and on a host that adds its own, one of those (on Next.js: tx and transaction on avClient, appTx and transaction on avApp). | Rename the model; the table it maps to can keep its name. A host member's name is also left free when the layer it belongs to hides the Resource. |\n| [`COMPILER_MISSING_IDENTIFIER`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_missing_identifier) | A unique operation has no identifier. | A layer exposes a unique variant (find.unique, update.unique, delete.unique, upsert.unique) and no complete identifier of the Resource it may filter on: a Pick that did not pick one with its filter, or an Omit that hid every one or its filter. | Expose an identifier field with its filter (id, or every field of one unique), or exempt the unique variants. |\n| [`COMPILER_HIDDEN_REQUIRED_FIELD`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_hidden_required_field) | A layer that creates a Resource hides a required field. | A layer still creates the Resource (a create or upsert variant, or a nested create) while a required field, or a required relation's reference field, is hidden or has its create capability exempted, and no compute.create fills it. | Expose the field, give it a compute.create stage, or exempt the layer's create operations. |\n| [`COMPILER_READ_ONLY_NESTED_WRITE`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_read_only_nested_write) | A relation writes rows of a read-only Resource. | A layer with exposed: false grants a relation a write into a Resource it cannot write: $create, $connectOrCreate or $upsert into a Resource with no create or upsert operation and no field writable on create, $update or $upsert into one with no update or upsert operation and no field writable on update, or $delete into one with no delete operation. Read-only, by fields: \"read\" or by hand-listed capabilities, means no nested write either. Under exposed: true those directives are dropped from the relation instead, and startup succeeds. | Narrow the relation to the directives it may use (author: { select: true, create: [\"$connect\"] }), or give the target Resource the create, update or delete operation, or a field writable there. |\n| [`COMPILER_INVALID_VALIDATOR`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_invalid_validator) | A field validator cannot apply. | A validator that is not built with AvZ, does not fit the field's type (AvZ.maxLength on an int, AvZ.maxItems on a field that is not a list), or sits on a relation or Json field. | Use an AvZ check that fits the field's type; a Json field's checks go in its json shape. |\n| [`COMPILER_INVALID_JSON_SHAPE`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_invalid_json_shape) | A Json shape cannot apply. | A json shape on a field that is not a discovered Json field, a shape not built with AvZ, or one that admits null or absence at its top level. | Put the shape on a Json field, build it with AvZ, and keep its top level non-null (the column's nullability decides null). |\n| [`COMPILER_INVALID_CLIENT_CONTRACT`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_invalid_client_contract) | The compiled client contract is invalid. | The finished client contract failed its own structural check: a framework defect. | Report it with the diagnostic's path and message. |\n";
|