1.0.0-rc.0 was the first release candidate, 1.0.0-rc.1 the second, 1.0.0-rc.2 the third, 1.0.0-rc.3 the fourth, 1.0.0-rc.4 the fifth, 1.0.0-rc.8 the sixth, 1.0.0-rc.9 the seventh, 1.0.0-rc.10 the eighth, 1.0.0-rc.11 the ninth and 1.0.0-rc.12 is the tenth (1.0.0-rc.5, 1.0.0-rc.6 and 1.0.0-rc.7 were never published); the releases before them are pilots, and a pilot's API could change. A release candidate changes the API only to fix what testing finds. Pin exact versions. All @aventara/* packages are released together at one version. Each package also ships this history for itself as a CHANGELOG.md beside its README.
1.0.0-rc.12
The current release, published under the rc dist-tag (install with @rc). A unique field a layer may not filter on stops being a lookup key, and the scaffold writes String ids and Prisma ranges. Upgrading from rc.11: see Upgrading.
Lookup keys
- A unique field a layer may not filter on is no longer an identifier of that layer: a write-only field (
["select", "filter", "order"]), one withfilterexempted (["filter"]), or a Pick that leavesfilterout. Before,find.unique,update.unique,upsert.unique,delete.uniqueand a nested$connector$connectOrCreatestill looked a record up by it, over every route including/_alland/_transactions. Such a selector is now refusedV1007before the adapter is called, a compound identifier goes with any one of its fields, and the generated client and the server façades no longer offer it. - A layer that keeps a unique operation with no filterable identifier left fails at startup with
COMPILER_MISSING_IDENTIFIER. Underexposed: false, pickid: ["select", "filter"], notid: ["select"], to keepfind.unique. The agent skill's Pick example says so.
Scaffold
aventara newandaventara init(with no ORM yet) write the starter model asUser { id String @id @default(cuid()) }instead of anIntautoincrement id.cuid()works on every database the CLI offers. A project already scaffolded withIntkeeps its schema. The agent skill's examples no longer assume a numericUserid.aventara newandaventara initwrite Prisma,@prisma/clientand the driver adapter at the adapter's range^7.10.1instead of its lowest version, so a new project installs the newest Prisma 7 and never Prisma 8.aventara initon a project that already uses Prisma leaves its Prisma versions as they are.aventara initadds.envto.gitignorewhenever it writes to.env. Before, it ignored.envonly when the ORM scaffold created or filled it, so appendingCORS_ORIGINSto your own.envcould leave its secrets committable. A.envthe CLI leaves untouched leaves.gitignoreas it was.
Hosts and client
@aventara/nestis built and tested against NestJS 12.3.2. The peer range stays^12.0.0.avclient generateno longer renames a Resource namedalltoallModel; the server already refuses such a Resource in the Client Contract.
1.0.0-rc.11
Published under the rc dist-tag before rc.12. It adds Operation Sets (several calls in one request), cached GET reads, and a stricter avLazy, and it moves the docs. Every ClientContract hash changes, so regenerate your clients. Upgrading from rc.8: see Upgrading.
Operation Sets
POST {api}/_allanswers an Operation Set: several independent operations in one request, under keys the caller names ({ operations: { <key>: { resource, family, variant, args } } }). Every entry runs concurrently and exactly as it would through its own route. The answer is200 A1000with each key holding that entry's own envelope. WithAccept: application/x-ndjsonit answers one line per entry as each settles, withCache-Control: no-store, and a client that disconnects stops the entries not yet started. The state is seeded once and the principal derived once for the whole request.limits.maxAll(default 10) bounds the entries in one set andlimits.maxAllConcurrency(default 4) caps how many run at once, at the root and per layer. Both are part of the ClientContract's limits, so its hash moves.AvOperationDiagnostic.keynames the entry a failure met.- The generated client groups the calls made in one tick into one
/_all, split atmaxAll. A call still runs when it is made, likefetch; newavLazy(() => call)makes one only when it is first awaited. A call withheadersor arequestId, a transaction, and any call made withgrouping: false(per call, ornew AvClient({ grouping: false })) goes alone. A server without/_allgets the calls one by one. A call'ssignalaborts that call only. avClient.all({ ... })awaits an object of calls: strict by default, or{ settle: true }for one settled entry per key. A Resource namedallis refused at construction (COMPILER_RESERVED_RESOURCE_NAME); rename the model or keep it out of the Client Contract.- On the server,
framework.serverClient.all(entries, { settle?, signal? })isavClient.allin process.avLazyis exported from@aventara/core;allstarts itsavLazyentries itself, at mostmaxAllConcurrencyat once.serverClientcalls stay eager. See Several operations in one round trip. @/aventara/universalin a Next.js app now returns the generated client's own calls, sosignal,avClient.allandavLazyreach it. In the browser it groups as before; on the server it groups nothing, and each call is sent from the async context of the code that made it. Before, one queue served every request, so calls from two concurrent requests could share a flush and run with the first one's headers andstate.avLazykeeps no answer across requests on a server. The generated client's keeps its answer in a page and none on a server;@aventara/core's keeps none;@/aventara/universalexports its own, which keeps one answer per request inside a React render. Before, anavLazymade at module scope handed the first request's answer to every request after it.
Cached reads
- A
cache({ for, maxAge })stage (from@aventara/core) atpipelines.cacheof the root or client layer, a Resource, thefindfamily or onefindvariant serves those reads asGET /_resources/<R>/find/<variant>?q=<arguments>&v=<contract hash>beside their POST. The most specific level wins. A write, a transaction, or the application or agent layer refuses it (CONFIG_INVALID_VALUE). forsays who may share one answer:"user"(Cache-Control: private),"anonymous"(publicfor an anonymous caller,privatefor a signed-in one) or"everyone"(public). Writeforevery time. A200carries anETagandReferrer-Policy: no-referrer, and a matchingIf-None-Matchis304. Aqoverlimits.maxRequestBytesis413 A2010, and avthat is not the ClientContract's hash is409 A2005. Every answer to aGET,/_contractincluded, carriesX-Content-Type-Options: nosniff.- The ClientContract marks each cached read, so its hash moves. The generated client sends a cached read as a
GET, alone and never grouped; a URL over 4096 characters isPOSTed instead.ctx.$transport.methodis"POST" | "GET". See Caching reads.
Docs and types
- The docs moved to https://aventara.razor34.dev/framework. Every
docslink in the error catalog, and every docs linkaventara new,aventara initandaventara skillwrite, now points there. @aventara/core's declarations name no platform global: asignalis typed by the members core reads, so a project with neither DOM's lib nor Node's types type-checks against it.- The agent skill's error reference names
allas a reserved name.
1.0.0-rc.10
Published under the rc dist-tag before rc.11. It fixes a policy bypass through nested relinks, adds the /_access/can route and $access on serverClient, adds managedElsewhere, and tightens policy and startup checks. It raises the Node and Prisma floors. Upgrading from rc.8: see Upgrading.
Security
- A nested to-one relink could bypass a policy's update scope. In rc.9 and earlier, a to-one
$updateor$upsertbeside$connect,$connectOrCreate,$create,$setor$disconnecton the same relation ran the relink first and then wrote the newly linked row, while the update scope, field grants,grantsOfand the after check had been asked about the row linked before the call. rc.10 refuses the combination for every caller (A2004, oneV1012issue at the relation) before anything runs, and the argument types reject it. To relink and update, use two writes. A to-many operand may still combine them. @aventara/mcppins@modelcontextprotocol/sdk1.32.2 andzod^4.6.7, which clears six advisories (two high in the SDK, one high inzod, three moderate). No MCP API changes.- An MCP endpoint without
authorizationalso requires a loopbackHostheader (localhost,127.x.x.xor[::1]); any otherHost, or none, is403. This closes DNS rebinding. - An access section's
urland each verb'spathrefuse control characters,./..segments and a bare/, so"/\t/evil.example"no longer resolves to another host.
Runtime floors
engines.nodeis^22.23.4 || >=24.22.1, the Node security releases of 2026-10-20. The bins refuse an older Node in one sentence naming that range.- The Prisma 7 adapter's peer floor is
^7.10.1.aventara newandaventara initinstall Prisma at the floor, andaventara initrefuses a project whose Prisma is below it.
Access seam
- With
accessconfigured,POST {api}/_access/cananswers a page's questions about all of its buttons in one request: one boolean per question, in order. It never answers403; it refuses malformed input only.AvProtocol.bind(framework).handleCananswers it, and the host mounts it on Express and Fastify. The ClientContract carries anaccesssection exactly whenaccessis configured. framework.serverClient.$accessexists exactly whenaccessis configured:can(questions)in process, andme()and the provider's other verbs.AvAccessProvider.clientstates where Access is mounted and the verbs it offers. The generated client gets$access, and the MCP server ametool.- A policy engine may answer
grantsOfand receivebeforein its after check, state typed literals in their wire form, listunheldResources and takehosted({ held }).
Policy and startup checks
- Writing a reference field itself (
authorId: "u2") is held to the target's read scope, as$connectis: a row the caller may not read is refused (A4002). A row a write names to link that is not there is refused the same way (A4002) wherever the target has a scope; before, it answeredA2008orA2003. Without a scope the codes are unchanged. See Policies. - A listed update (
afterChecks) holds a to-one nested$updateto its row as it was read, and a listedupdate.manyorupdate.countthat changes fewer rows than it read is refused (A4002) and rolled back. - A model whose table another service owns can be marked
resources.<R>.managedElsewhere: true: Aventara reads and joins it, and no standard operation writes it. The client and agent Contracts leave it out. Aclient.resourcesoragent.resourcesentry for it is refused (COMPILER_UNKNOWN_RESOURCE). - The Prisma 7 generate step reads multiSchema's
@@schema("auth"). mcp.authorization.serversacceptshttp://127.0.0.1:4000andhttp://[::1]:4000for local development.localhostis refused with the address to use, and every server is refused with a query, a fragment, an empty path segment or port 0.- On PostgreSQL, a listed update no longer refuses an unchanged row, and a scalar list a create left out (stored as
NULL) answers every list filter as[]does.Bytes[]columns no longer offerequals,hasEveryandhasSome. aventara-buildreads the project's.envfiles before it runs the configuration, and prints a defect asString(error)when its stack is empty. The Next.js scaffold keeps one PrismaClient per generated schema, sonext devneeds no restart.
1.0.0-rc.9
Published under the rc dist-tag before rc.10. It adds the Next.js host to the scaffold, framework.serverClient, and the access provider. Upgrading from rc.8: see Upgrading.
aventara new <name> --host nextrunscreate-next-app@latest, then the sameinitsteps.aventara inittells the host bypackage.json(NestJS 12 or Next.js 16) and refuses a project that depends on both or neither. A Next.js app getssrc/aventara/(config, adapter, server), an API route,src/instrumentation.tsandsrc/prisma.ts, andserverExternalPackagesinnext.config.tsso one core and one Prisma client serve every route.aventara-build --clientwrites the browser client with no running server. See Next.js host.framework.serverClientandframework.serverClientTxare the client layer for server code, called with application values (Date,bigint,AvDecimal,Uint8Array). Each call runs the same client pipelines, validation, exposure and policy asframework.client, with the same answers and errors.createFrameworktakesaccess, an access provider: the policy engine and the request state from one object (policyandstate({ headers, request })).accesstogether withpolicyis refused at startup.AventaraModuleseeds each request'sctx.$statefrom the provider'sstate; its ownstateoption besideaccessis refused.- Values and errors from
@aventara/coreare recognized across duplicate copies of the package in one process (Next.js layers, a monorepo):AvDecimaland everyAvErrorclass passinstanceof, and thediagnosticssink receives reports raised through another copy. Afind.manyselecting a decimal no longer answers500 A3003there. - The agent skill has a section "Calling the API over HTTP", with a working curl example. Run
aventara skillto refresh it.
1.0.0-rc.8
Published under the rc dist-tag before rc.9. 1.0.0-rc.5, 1.0.0-rc.6 and 1.0.0-rc.7 were never published; everything planned for them is in this release, plus the fixes their checks found. It adds two things for people who write a policy engine, fixes what testing a policy engine over HTTP and MCP found, and makes helpers that take or return a call's arguments compile in projects that emit declarations. A project with no policy serves the same Contracts and hashes as rc.4, so a client generated against rc.4 keeps working. Upgrading from rc.4: see Upgrading.
Policy engines
- An engine can check a create whose row reaches another Resource through a relation. Discovery now records, on the end of a relation that holds the reference, which of the target's fields its reference fields name (
Note.campaign:referenceFields: ["campaignId"],references: ["id"]). The engine reads them from the model it is built from or fromframework.contracts.core, and asks oneexistsabout the row they name, such as "a player creates a note only in a campaign they play in". The served Contracts,/_contract, the MCP tools and every ClientContract hash are unchanged. fieldsOf({ access: "write" })also names the action that writes the record,action: "create" | "update", so an engine can lock a field on update and still let a create write it. A read question names none, and an engine that ignores it keeps working.policy.principalmay answer with a promise. A rejection answersA3000, as a throw does.
Fixes
- A cursor on a row the caller cannot read is answered exactly as a cursor on a row that does not exist, at the root and in a to-many relation. Before, the page started at the hidden row, which told the caller the row exists and where its values sort. On the Prisma adapter a scoped cursor costs one extra lookup.
- A unique update with a nested write (
$create,$connectand the rest) on a row the caller can read but not change answersA4002, as the same update without one does. It answeredA2003. A row the caller cannot read still answersA2003. - The
existsquestions a policy engine is asked select the Resource's primary identifier, never another unique field such as a governedemail. - Helpers that read the Core Contract compile in projects that emit declarations. A function returning a Resource or a field of
framework.contracts.corewithout a return type, such ascore.resources[resource]?.fields[relation], failednest buildandtscwith"declaration": true(TS2883, orTS2742on TypeScript 5.9).@aventara/corenow exports the types the Core Contract is written with:AvCoreResource(a Resource),AvCoreField(one of its fields) andAvCoreRelationField(a relation field, with the target fields its reference names inreferences). - A policy's startup check states the path once:
CONFIG_INVALID_VALUE at policy.engine.governed.Nope: "Nope" is no Resource of the Core Contract. Each diagnostic'smessageno longer starts with itspath; read the path frompath. - Typed policy engines compile in projects that emit declarations. An engine written
as const satisfies AvPolicyEnginefailednest buildandtscwith"declaration": true(TS2883, orTS2742on TypeScript 5.9). A read question'sactionis now typedundefined, so an engine can writefieldsOf({ resource, access, action })for either question. AvConstructionError.messagenames the first problem and how many more there are, for exampleAventara framework configuration validation failed: CONFIG_INVALID_TYPE at adapter.execute: adapter.execute must be a function (+2 more).stageanddiagnosticsare unchanged; code that compares the message to the old sentence should compareerror.stage.
Declarations and types
- Helpers typed off the façades compile in projects that emit declarations. A function that took
Parameters<AppFramework["application"]["Post"]["find"]["many"]>[0]and returned itswhere, or built a new one from it, without a return type failednest buildandtscwith"declaration": true(TS4023:has or is using name 'ScalarFilterOperands' … but cannot be named, usually besideTS7056). A nested write's data could fail the same way, and a nested$update'swherewas written into the.d.tsasany. The declaration now names the argument types@aventara/coreexports (AvWhereInput,AvCreateDataInput,AvUpdateDataInput). See Server-side usage. - New exports from
@aventara/core:AvScalarFilterOperands(one field's filter operators),AvScalarWhereInput(thewhereof a nested$updateor$deletethat takes plain fields only),AvNoAdvertisedMember(a write member that advertises nothing; it takes{}), andAvSqlStatementBuilderandAvSqlStatementValue(the type offramework.appTx.$sqland the values it binds). A method returningframework.appTx.$sqlfailed withTS2883(TS2742on TypeScript 5.9) before. framework.executeandframework.executeTransactiontake request types exported from@aventara/core:AvOperationRequestandAvTransactionRequest, each with an application, client and agent member (AvApplicationOperationRequest,AvClientOperationRequest,AvAgentOperationRequest, and the three…TransactionRequest), andAvRequestProtocolfor a remote request'sprotocol. They were named from@aventara/core/internal(FrameworkOperationRequest,FrameworkTransactionRequest,ClientProtocolIdentity, ...) and are gone from there; change the import.- Reading a projecting variant's argument type (
Parameters<…["find"]["many"]>[0]) type-checks faster: about 6,000 type instantiations over a three-field model, where rc.7's build took about 29,000. The part of the argument other thanselectandincludeis markedNoInferin the call's parameter, so an editor shows it asNoInfer<Omit<…>> & …. What a call accepts is unchanged. - A create's
datatype states each required field once, as a required member (Readonly<{ title: string; } & { body?: … }>in an editor's expanded hover). Before, a required field appeared as an optional member beside a separate{ readonly title: WrittenValue }. What a create accepts and refuses is unchanged.
Discovery artifact and build
- The discovery artifact's
referentialActions.<Resource>.owningEdges[]call the fields that hold the referencereferenceFields; they werereferences, which a model's relation fields use for the target's fields. Regenerate the artifact (npm run aventara:prepare). Only those keys change: the model hash and the prebuilt Contracts file are the same. See Upgrading. aventara-build --checksays what changed when a file is stale, in the words of theAVENTARA_PREBUILT_STALEwarning at startup: the@aventara/coreversion that wrote it against the installed one, a file format or protocol version that differs, the Resources that changed, are new or are gone, the configuration or model outside the Resources, or a file that no longer reads as a prebuilt Contracts file. See CLI reference.
Rebuild
- The discovery artifact's model hash changes, and so does the fingerprint of a prebuilt Contracts file. Run your generate step and
aventara-buildagain (npm run aventara:preparein the scaffold;pnpm run aventara:preparewith pnpm,yarn aventara:preparewith yarn). Until you do, the prebuilt file reads as stale (AVENTARA_PREBUILT_STALE) and the Contracts compile at startup.
1.0.0-rc.4
Published under the rc dist-tag before rc.8. It adds @aventara/build, a build step for serverless and edge starts, changes the scaffold's configuration shape, and adds a policy seam for access rules. A project with no policy keeps the Contracts and hashes it had in rc.3, so a client generated against rc.3 keeps working. Upgrading from rc.3: see Upgrading.
Build-time Contracts
- New package
@aventara/build, a development dependency with the binaventara-build. It runssrc/aventara.config.tsandsrc/aventara.adapter.tswithout a database client, compiles the Contracts with your@aventara/coreand writescontracts.generated.ts(aventaraContracts). A serverless or edge entry passes it tocreateFrameworkasprebuilt, so a cold start loads the Contracts instead of compiling them. A configuration that startup would refuse fails the build with the same diagnostics, andaventara-build --checkwrites nothing and exits 1 when a generated file is missing or stale. A file that does not match the running framework is ignored whole: the framework compiles at startup as it did before and warns once withAVENTARA_PREBUILT_STALE. See Deploying and CLI reference. aventara-buildalso writesresources.generated.ts, the tree of yourdefineResourceresults fromsrc/resources/**/*.resource.ts, andcomputed-fields.generated.ts, which fields your write compute fills. The scaffold takes its Resources from the tree (resources: aventaraResources.root), so a new Resource file needs no configuration edit, and two definitions of one Resource stop the build. See Configuration.- The scaffold's
src/aventara.config.tsis now a plainexport const aventaraConfig = { … } as constwith no adapter. A newsrc/aventara.adapter.tsdefault-exportsdescribePrismaAdapter({ discovery, provider, driver }), andapp.module.tsjoins them withcreatePrismaAdapter({ ...adapter, client: prisma }).aventara:prepareends with&& aventara-build. aventara-prisma7-generateno longer reads your configuration. With--config, the discovery artifact imports the computed fields from the fileaventara-buildwrites.readConfigComputedFields,AvPrismaConfigReaderErrorandConfigComputedFieldsReadingare removed from@aventara/prisma7-adapter/generate.- All of Aventara's generated files sit in one directory. In a project whose Prisma client is generated into
src/generated,prisma generateused to delete the filesaventara-buildwrote there; they now go beside the configuration'saventaraResourcesimport, and.gitignoregets one/src/aventara/line for all four files.
Access policies
createFrameworktakes an optional rootpolicy: { engine, principal }. The engine answers three questions (can,scopeOf,fieldsOf, each now or as a promise), andprincipalderives the caller fromctx.$state. Core never imports an engine; any object with that shape works.- Client and agent requests are checked, and an application call is checked when it names a principal:
framework.application.Note.find.many(args, { as: user }). A refusal is403A4002. The read scope is added to the rootwhere, every to-many include and its count, and every relation filter. A to-one include of a row out of scope refuses the request instead of returningnull. - Fields the engine marks as governed are left out of every default projection and carry
governed: truein the Contracts. Name one to get it:select: ["$defaults", "email"], where$defaultsis the level's default projection. A filter or sort by a governed field is checked before the query runs. - Writes are checked too, nested writes included. The fields a write sends must be within the caller's write grant. An update or delete acts only on rows the caller may both read and change. An update that moves a row out of what the caller reads, or fails an
aftercondition, is rolled back withA4002. One failed check in a nested write rolls the whole write back. - A single-row update or delete of a row the caller cannot read answers
A2003, the same as a row that does not exist, and a unique read of a row outside the read scope is a miss in the same way. A row the caller can read but not change answersA4002. See Error codes. - A checked write costs few round trips. On PostgreSQL:
create.one1 (was 3),delete.unique1 (was 4),update.unique3 (was 5),update.many3 (was 4),update.count3 (was 5). Reads, and a framework without apolicy, are unchanged. policyandprebuiltare root-only. Apolicythat names a governed field or path the Core Contract does not have is refused at startup, with aCONFIG_…code that names the rule.
Types
- A service method can return any façade node or variant without a return type. Under
declaration: true(a Nest build) the declaration file now writes a short reference, under half a kilobyte per method, where it wrote the whole Contract view (about 440 KB per method over 50 Resources). A method that returns a variant such asthis.framework.application.Post.find.manyno longer needs an annotation, and one that returns a transaction variant no longer runs the compiler out of memory. An annotation you wrote keeps working. See Server-side usage.
Performance
- Reads are faster: the read-only copies that computed fields and hooks are given cost about half what they did, a row whose computed field returns a value at once no longer waits a turn per row, and the MCP endpoint builds its JSON Schema validator once instead of on every request. What stages are told, the order they run in and what a request answers are unchanged.
1.0.0-rc.3
Published under the rc dist-tag before rc.4. Fixes found while testing rc.2: no configuration key moves, nothing is renamed, and the contract hash does not change. Upgrading from rc.2: see Upgrading.
Writes
- An empty boolean node in the
whereof a write is refused, as anundefinedthere is, and nothing runs. Inupdate.many,update.count,update.first,delete.count,delete.firstand a to-many$updateor$delete, an emptyNOT: {},AND: {}or[],OR: [], or anAND/ORentry{}, at any depth, answers422A2004with aV1001issue at the node. Before, the ORM read it as no condition:delete.count({ where: { NOT: {} } })deleted every row, anddelete.firstwith one deleted an arbitrary row. Awherethat is{}itself still means every row, reads pass empty nodes through as before, and a to-one relation's{ author: {} }still matches the rows that have a related row. See Filtering. - An issue inside one entry of a to-many directive's array names the entry's index, such as
["arguments","data","posts","$update",1,"data","category"]. This holds for$create,$connect,$connectOrCreate,$update,$delete,$upsert,$setand$disconnect. - On PostgreSQL, a
@uniquevalue too long for its index is answered422A2004with oneV1004issue at["arguments"], instead of500A3001. The issue's message for a value too long now readsThe database refused this value: it is longer than the database can hold for its column.
Computed values
- A value a compute stage writes to a field the caller's layer hides is judged by the root's validators and Json shape. A value they refuse answers
500A3000, with anAvComputedValueErrorin thediagnosticssink and nothing of the hidden field in the answer. Before, a hidden shaped Json field was not judged at all, and a hidden field's own validators answered the callerA2004at the hidden field's path. See Field validators.
Types
- A
clientoragentlist entry that names what its parent hid now names the sentence when written inline increateFrameworkorAventaraModule.forRoottoo, where it readType 'string' is not assignable to type 'never'. Inline or not, the error ends atProperty '"Error: client cannot expose 'User.internalNotes': hidden by root"' is missing in type ….
Generate step and CLI
aventara-prisma7-generaterefuses a--providerthat the schema'sdatasourceblock contradicts, naming the flag to pass; PSL'spostgrescounts aspostgresql. Before, a wrong flag wrote the wrong provider into the artifact and the wrong filter modes into the contract.- When the file system refuses the artifact file (
--out), the generate step says so in one sentence naming the file and why, and leaves no staged file behind. Before, it printed a stack trace. - A refusal from
aventarathat quotes a failed command's output (npm install,nest new) ends with that output as written, without an extra period. - The error catalog (
AvErrorCatalog,ERRORS.md) now describesA2004,V1001andV1004as the code answers them. See Error codes.
1.0.0-rc.2
Published under the rc dist-tag before rc.3. Upgrading from rc.1: see Upgrading.
Smaller declarations
- The discovery artifact exports the model's type by name,
AvModel, and declaresmodelwith it. A project that emits declarations (Nest's default) writes the name instead of the whole model: over 50 models, the scaffold'saventara.config.d.tsgoes from about 485 KB to under 1 KB, and abuildFramework/AppFrameworkmodule from about 930 KB to under 1 KB. Runaventara:prepareto regenerate the artifact.
Types
- TypeScript 5.7 is the floor: the Prisma adapter's
typescriptpeer is>=5.7.0 <7and the client generator's>=5.7.0. Under 5.5 and 5.6 a variant's stages written inline increateFrameworkwere toldunknownarguments. - A variant's guards, pipes and filters run before write compute, so their typed arguments no longer require any field of a create (create data, an upsert's
create, nested creates): a pipe may return a create without a fieldcompute.createfills. Hooks and interceptors keep the requirements. - A
clientoragentlayer underexposed: falsethat names what its parent hid is a type error that now ends with a sentence naming the key and who hid it, such asError: client cannot expose 'User.internalNotes': hidden by root.
Computed values
compute.createandcompute.updatevalues must pass the field's validators. One that does not is answeredA2004at the field, like any refusal; a value of the wrong type staysA3000, with the details on anAvComputedValueErrorin thediagnosticssink.compute.readvalues are not judged.- Validators now run after write compute, on every value that is written, so they see the piped and computed value. A call judges once.
- A pipe's output that fails Aventara's own checks (a wrong type, an unknown key, a required field it dropped, a reference written twice) answers
500A3004, and so does a reference a compute stage wrote twice. A compute stage returning a value of the wrong type staysA3000.
undefined values
- An explicit
undefinedin write data counts as not sent, the same as leaving the key out: an optional field is omitted, an update leaves the field untouched, and a required create field is missing (A2004,V1000). This holds for server-side calls,ctxcalls, pipe output and transaction steps. Before, it was refused withV1001. - The generated client refuses
undefinedin a filter, a unique selector or a cursor (and awhere,cursor,limitoroffsetkey set toundefined; leaving the key out still means no filter) before anything is sent, withAvValidationErrorA2004/V1001at the same path the server uses. Before, JSON dropped the value and the request matched every row:update.many({ where: { id: undefined } })updated the whole table. Regenerate the client to get the check. undefinedinside ajsonvalue becomes what JSON makes of it on every surface: a key holding it is left out, and an array element holding it isnull. In rc.1 server-side calls refused the key withV1001, and a hole in an array was dropped, shifting the elements after it. Regenerate the client.
Writes
- One scalar action object holds one directive:
{ score: { $increment: 1, $decrement: 2 } }is a type error and is refused withA2004/V1001at the field. In rc.1 the ORM refused it with500A3001. - A
$divideby zero is answered422A2004/V1004with the database's reason. In rc.1 it answered500A3001. - A Json value whose top level is an object of
$keys answersA2004/V1006in a plan step too. Before, it answeredA2007/V1016there. - A relation written together with its reference field (
V1012) is refused before any guard, pipe or compute stage runs, and in a plan before the transaction opens. In rc.1 the developer's stages ran first.
Filters
modetakes"default","sensitive"or"insensitive", and each field lists the modes it offers in the contract (filterModes,pathFilterModes). An unoffered mode is a type error and is refused withA2004/V1006before the database is asked. With Prisma 7: PostgreSQL offers all three (a wholeJsondocument not"insensitive", which PostgreSQL could not run and answeredA3001); SQLite offers"default". Runaventara:prepareand regenerate the client.mode,isEmpty,pathand aJsonfield'sstring*operands of the wrong type (isEmpty: 3,mode: "loud",path: "a.b") are refused withA2004/V1001at the operand. Before, they reached the ORM and answeredA3001.notholding operators negates the whole object, with SQL's rule for unknown rows (aNULLor missing value matches neither). In rc.1 the Prisma adapter matched every row for aJsonfield'snotwith operators (updates and deletes included), matched no row fornotwith two operators, and answered500formodeinsidenot.- Each filter object stands alone: a
modeorpathwith no operator beside it is refused withA2004/V1001and is a type error. - A value the database refuses (out of range, a NUL character in PostgreSQL text, too long for its column, an invalid character) is answered
422A2004with the database's reason instead of500A3001. - On D1, a
BigIntbeyond ±(2^53 − 1) (written, compared or in$increment) is answered422A2004/V1004at the value, instead of500A3001. AvProtocol.handleTransactioncopies a parsed plan when it is called, and a$sqlstep never runs a getter inside an object carrying$ref.- A server-side
appTxbuilder never runs a getter inside an object carrying$ref, andAvProtocol.handleOperationcopies a parsed body when it is called, as documented. - Clearer
orderByrefusals: a$refinorderBy, and a malformed null ordering, say what is wrong. AvJsonValueadmits a property holdingundefined.- On a
Json?field, writingnullstores the databaseNULL, andfield: null/equals: nullmatch every row that reads backnull. In rc.1 the Prisma adapter stored a JSONnull,equals: nullmatched only JSONnulls, andfield: nullanswered500A3001. - An empty to-one relation filter (
{ author: {} }) matches the rows that have one. In rc.1 the Prisma adapter matched every row, and itsNOTdid too, sodelete.count({ where: { NOT: { author: {} } } })deleted every row. - In a filter,
mode,isEmptyandpathset toundefinedare refused (A2004,V1001), like every other operator. Before, they reached the database as no condition.
MCP
- A request with no bearer token, or with another scheme, gets
401withWWW-Authenticate: Bearer resource_metadata="..."and noerrorcode. A bad bearer token keepserror="invalid_token". - A
verifythat throws is answered401and reported to thediagnosticssink (kind: "mcp"). - With
mcpconfigured and@aventara/mcpmissing, the startup error printsnpm install --save-exact @aventara/mcp@<version>with your@aventara/coreversion.
CLI
- An agent skill for your AI coding agent:
aventara newandaventara initwrite.claude/skills/aventara/(SKILL.mdandreference/config.md,stages.md,recipes.md,errors.md) and a short Aventara section inAGENTS.md, between<!-- aventara:start -->and<!-- aventara:end -->.CLAUDE.mdis never written. aventara skillrefreshes those files for the@aventara/coreinstalled in the project. It keeps a skill file you changed (--forcereplaces it) and replaces only the marked section ofAGENTS.md.- The scaffold's
aventara:preparescript passes--config src/aventara.config.ts, so the generated types know which fields your configuration computes.
1.0.0-rc.1
Published under the rc dist-tag before rc.2. Upgrading from rc.0: see Upgrading. Every application's client contract hash changes (the contract now lists maxJsonDepth), so regenerate the frontend client.
Breaking: the configuration has a new shape
- The mount path's key is
api(it wasentrypoint), read back asframework.api.entrypointis refused withCONFIG_MOVED_PROPERTY. The frontend'sentrypoint(the server's URL inframework.client.ts) keeps its name. - Every layer (the root,
applicationandclient) holds the same six keys:exposed,exemptions,transactionsStyle,limits,pipelinesandresources. Everything about one Resource lives underresources.<Resource>: stages per Resource, family and variant, its transaction stages, and its fields with theirdefinitionandpipelines. See Configuration. - The rc.0 keys are refused at startup with
CONFIG_MOVED_PROPERTY, naming the new place:restrictions(nowexposed+exemptions),fields(nowresources.<R>.fields.<f>.definition),behaviors(nowresources.<R>.fields.<f>.pipelines.compute),transactions(nowtransactionsStyle), andbefore/afterdirectly inpipelines(nowpipelines.hooks). In a configuration TypeScript checks against your model, each is a type error at the key first, and so is any key that has no place where it is written. - A field is defined at the root only. A
definitioninapplicationorclientis refused (CONFIG_FORBIDDEN_IN_OVERLAY). On a discovered field you may restate the adapter'stype,nullableandlist, and give a Json field its shape; a configured field'sformatmust be one of zod's named string formats. @aventara/core/jsonis now@aventara/core/avz.AvZis the same.defineFieldsis gone.defineResource<Model>()("Post", rootBlock, { application?, client? })defines one Resource's configuration in its own module, its root block and its overlay blocks, each typed as it is in place. It returns{ root, application?, client? }, to spread asresources: { ...post.root }andclient: { resources: { ...post.client } }.
Breaking: exposure replaces restrictions
exposedis a layer's default andexemptionslists the entries that are its opposite: withexposed: truethey are what to hide, withexposed: falsethe only things exposed. A list always means "exempt these" (rc.0'sfilter: ["equals"]meant "keep onlyequals"). See Exposure.- Overlays only narrow. An overlay can no longer bring back what the root hid; that is refused, and the message names the other overlay to hide it in instead. An overlay that sets
exemptionsmust set its ownexposed. - Every name in
exemptionsmust exist and be offered by the adapter. A unique operation left without an identifier is refused (COMPILER_MISSING_IDENTIFIER) instead of disappearing. Where a relation is projected or written is the adapter's and cannot be narrowed. aventara initandaventara newwriteexposed: trueintosrc/aventara.config.ts, with a comment linking the exposure page.
Breaking: required create fields are typed
- Each Resource's contract states what a create requires (
createRequirements:requiredfields,oneOfrequired relations with their reference fields, andparentSuppliesfor nested creates), per layer. The generated client,framework.application,framework.client, the transaction builders and a stage'sctxrequire it: leaving out a required field, or writing a required relation neither way or both ways (authorandauthorId), is a type error. The runtime answers what it answered before (A2004withV1000, orV1012for both). See Relations and nested writes. - The Prisma discovery artifact carries the requirements in its model (format 3): regenerate it with
npm run aventara:prepare. The client contract hash changes for every schema with a required field. - A field a pipe fills on the server is now required of callers. Give it a
compute.createstage or a database default, or have callers send it. - A layer that can still create a Resource can no longer hide a required field (or exempt its
create), or a required relation together with a field that holds its reference (any one field of a composite reference), unless acompute.createstage fills it. Startup refuses it withCOMPILER_HIDDEN_REQUIRED_FIELD, naming the layer, the Resource and the field. Keep the field exposed, move the pipe's value into acompute.createstage, or exempt the create operations. See Exposure. - A
create.manyorcreate.countrecord cannot write a relation, so each record needs the reference field (authorId), in the types and at runtime (V1000atdata.<n>.authorId). A layer that hidesauthorIdwhile it keeps those variants is refused at startup, even withauthorexposed: keepauthorId, give it acompute.create, or exemptcreate.manyandcreate.countthere. See Exposure. - A
compute.createwritten inline increateFramework({ ... })counts in the create types without annotations, at the root and in an overlay. Acreateorupdatestage'sfieldanddataare keyed bystring.
Breaking: Av names
- Every public type and class is
Avplus what it is:AvError,AvConfig,AvFramework,AvGuard,AvContext,AvConstructionError,AvDecimal,AvContractClient, ... The generated client's names follow (AvValidationError,AvNotFoundError,AvTransportError,AvCallOptions, ...), as do@aventara/client's,@aventara/testing's and the Prisma adapter's.PipelineContextisAvContext, andComputeContextisAvComputeContext, a compute stage's read-onlyctx. Functions keep their names. No aliases.
Breaking: server-side calls return data and throw
framework.application,framework.clientandframework.transactionsettle like the generated client: they resolve the data (a committed transaction its steps' results in order) and throw every failure as anAvErrorsubclass,AvValidationError,AvNotFoundError,AvConflictError,AvAuthError,AvInternalError,AvProtocolErrororAvContractMismatchError, now exported from@aventara/core, by the same code table as the generated client. A stage'sctxcalls throw them too.error.codeanderror.causeare what the envelope carried.framework.executeandframework.executeTransactionstill answer the envelope. See Server-side usage.
Breaking: pipelines
- Every framework member of
ctxstarts with$:ctx.$operation({ resource, family, variant, nested }),ctx.$args,ctx.$origin,ctx.$requestId,ctx.$transport,ctx.$contract, and the newctx.$state. A model whose name starts with$is refused (COMPILER_RESERVED_RESOURCE_NAME). - One stage order: guards, pipes, validators, interceptors,
beforehooks, the operation,afterhooks, interceptors unwinding, filters. Every interceptor wraps every hook (in rc.0 everybeforehook ran ahead of every interceptor), and an overlay'safterhooks run before the root's. A guard, a pipe's output or a validator that refuses a call runs no interceptor and no hook. In a transaction, every step's guards, pipes and validators run before it opens, so a refused step opens nothing. See Pipelines. - Data is read-only once final. Only a pipe changes the arguments, by returning new ones; changing
argsorctx.$argsin place throws aTypeError. What the validators checked is what the adapter writes. Anafterhook gets a frozen copy of the result. See Read-only data. - Nested writes run the target Resource's own guards and field stages. A nested
$createof aCategoryunderPost.createis refused whenCategory's create guard refuses it (A4001). - A transaction has one origin. A list that mixes
appTxandclientTxsteps is a type error, andframework.transactionrefuses one that gets past the types:A2004with the new issue codeV1020. - A Json field's shape is one of its validators. It is checked where validators run, after every pipe (in rc.0 it was checked before the pipes), and its issues come in the order the call wrote the fields, with the other validators'. Before the pipes the framework only checks that the value is plain JSON. A field or operation pipe whose Json output breaks the shape is answered
A2004, notA3004. - A Resource's transaction filters are the transaction's outer layer: an error a step throws that its own filters leave unhandled continues to them before it is answered. A transaction interceptor's
next()throws the failed step'sAvError(AvNotFoundErrorfor a unique miss), and catching it still rolls back. See Pipelines. - A compute stage receives
{ field, value, data, ctx }(recordon read); itsctxis read-only (AvComputeContext). Compute stages chain across layers (the root's runs first, the overlay's gets its result);falseno longer turns a stage off. An upsert computes its branches withcompute.createandcompute.update.
New
- The
agentlayer, for AI agents served over MCP: the six layer keys plusextends("client"by default,"application"or"root"). It narrows that layer's view, its stages run after that layer's, and each stage seesctx.$origin === "agent". It compiles toframework.contracts.agent, with its own hash.AvProtocol.bind(framework, { origin: "agent" })runs agent calls through the same validation as HTTP calls. See The agent layer and MCP. mcpturns MCP on: a mount path, or{ path, authorization: { servers, verify } }for OAuth. The resolved endpoint isframework.mcp.@aventara/mcpserves it: install it beside@aventara/nest, which mounts it. See MCP.- Stages at every level:
resources.<R>.pipelines,.operations.<family>.pipelines,.operations.<family>.<variant>.pipelines. - Field stages: validators, pure field
pipes, field writeguards(a relation field's guard covers$connectand friends). - Field validators, written with
AvZin zod's check vocabulary (AvZ.maxLength(80),AvZ.gte(0),AvZ.email(),AvZ.maxItems(5), ...). They are part of the contract, the server checks them after the guards and pipes, before any interceptor or hook and before a transaction opens, and the generated client checks them before it sends. A failure isA2004withV1003orV1004, and each issue carriescheckandparams. Json shapes take the same checks:AvZ.string().check(AvZ.maxLength(80)). - Resource transaction stages,
resources.<R>.transactions.pipelines: once per transaction with a step on R, inside it, seeing the whole plan. forceTransactionon a family or a variant: a plain call runs as a one-step transaction, so its after hooks commit with the write or not at all.ctx.$state: one writable object per request, shared by every stage, a transaction's steps and the ctx calls they make. Declare its members by augmentingAvState. Seed it withAventaraModule.forRoot({ state: (req) => ({ user: req.user }) }), or per call with{ state }besiderequestId. See The pipeline context.ctxis the framework's executor:ctx.<Resource>.<family>.<variant>(args)(raw, application-origin, joins the current transaction),ctx.$sql(outside a transaction, a plain statement with no transaction around it) andctx.$transaction(async (tx) => ...). See The pipeline context, with an outbox and a likes-counter recipe.- A stage registered on one operation (
operations.<family>.<variant>.pipelines) getsctx.$argstyped as that operation's arguments, read-only, and a pipe there takes and returns that type. See Typed arguments. framework.appTx.$sql, a bound tagged-template SQL step in an application transaction. See Transactions.- A new limit,
maxJsonDepth(default 64), for the depth of a Json value:A2009with the new codeV1021. - A
whereoffers each field only the filter operators that field allows, inframework.client,framework.application, the transaction builders and the generated client. - A project made by
aventara neworaventara inithas adiagnosticssink: it logs the raw error of a server failure, with its stack, through Nest'sLogger. - A server method that returns a call's result without a return type compiles in a project that emits declarations (Nest's default), Json shapes and transaction handles included. The new type exports
AvZShapedSchema(@aventara/core/avz) andAvOperationHandle(@aventara/core) are what such a declaration names.
Fixes and behaviour changes
- A
$connectto a record that does not exist answers404 A2003(it was422 A2004), also inside a transaction. Onupdate.firstit wasA2014; a duplicate value throughupdate.firstis now409 A2008(it wasA3001). A2001saysThe requested Resource is not available.andA2002saysThe requested operation is not available on this Resource.A transaction step with an unknown operation carries the same issue message as a single call.- A
$refwith nofingerprintisA2004/V1000(it wasA2007/V1017). - Operators inside
notare checked against the field's allowed operators at every depth, and each objectnotcounts towardmaxNestingDepth. - A forced operation no longer commits a failed step when a transaction interceptor catches the rollback. A transaction interceptor that calls
next()twice or never rolls back withA3002. - A
compute.readdependency may be a field the root hides; it is read for the compute and never returned. The renamed compiler codes areCOMPILER_MISSING_READ_COMPUTEandCOMPILER_INVALID_COMPUTE_DEPENDENCY. - Field write guards and field pipes are dispatched from lists planned at startup: a call to a Resource with none does no field-stage work, and a
create.manyreads its records for guards only when a guarded field is registered.
1.0.0-rc.0
Upgrading from pilot.4: see Upgrading. The renames and behaviour changes are marked Breaking or Behaviour change; nothing sent over HTTP changes.
Breaking
scopeis noworigin. Where a request came from ("application"or"client") isctx.originin every guard, pipe, hook, interceptor and filter, in the context a computed field's behavior receives, in every diagnostic yourdiagnosticsfunction receives, and in the request objectsframework.executeandframework.executeTransactiontake. There is no alias: replacescopewithorigin. The deprecated typeOperationScopeis gone; useRequestOrigin. The Nest log line for an internal failure now reads(client origin, request ...).@aventara/coreexports fewer names. Every name removed was undocumented, and every documented name is still exported. What Aventara's own packages need moved to@aventara/core/internal, which is not public API: it has no stability promise and may change or disappear in any release. Applications import from@aventara/core.
Transactions
$refworks almost anywhere a value does. A step can use an earlier step's result in$connect,$disconnect,$setand thewhereof$connectOrCreate; in acursor; in a uniquewhere; in any filter operator at any depth,AND,OR,NOTand relation filters included; and at any depth of nested writes (the data of$create,$updateand$upsert, and thewhereof$update,$deleteand$upsert). The typed builders and the generated client accept a reference exactly where the server does: of the field's own type, nullable only for an equality on a nullable field, a list field forin,notIn,hasEveryandhasSome. An equality with a nullable source matches the rows where the field is unset, which can be many rows; comparison and text operators, unique keys and cursors refuse a nullable source (A2007/V1011). A reference in anorderByor in a projection'swhereis still refused. See Transactions with $ref.
Typed JSON
- A
Jsonfield can have a type. Build a shape withAvZfrom the new entry@aventara/core/jsonand declare it underfields. Server code and the generated client read and write the field as that type, and every write is checked before the database is asked, with one issue per problem at its exact path (V1000,V1001,V1002,V1004,V1005). Shapes are types only, with closed objects; reads and filters are never checked. The shape is part of the client contract, so declaring one changes the hash: regenerate the client. See JSON fields.
Type-checking and validation
- Pipeline callbacks type-check in an
as constconfiguration. A guard, pipe, hook, interceptor or filter annotated withGuard,Pipe,BeforeHook,AfterHook,InterceptororFilter, with no model type, is accepted wherever one typed for your model is, so thePick<PipelineContext, ...>workaround is no longer needed (it keeps working). See Pipelines. - A
nullin an operator that cannot take it is a422, not a500.gt: null,contains: null,in: nulland the rest answerA2004/V1001at that operator, and are type errors.equals: nullandnot: nullare unchanged. - A relation filter in a nested
$updateor$deleteis a422. Through a to-many relation on Prisma 7, the nestedwheretakes plain fields only; a relation filter there answersA2004/V1006before the database is asked (it was500A3001), and is a type error. A to-one relation keeps relation filters. The contract states the limit, so the hash changes for a schema with a to-many relation: regenerate the client.
Fixes
framework.clientandframework.applicationtypes follow your restrictions. Hidden fields, turned-offselect,filter,order,createandupdatecapabilities, and disabled operations are now reflected per layer, like the generated client. A field hidden at the root is hidden onframework.applicationtoo. Filter operator narrowing is still not reflected in types.- A pipeline stage's issue keeps its path inside a transaction. It arrives at
["operations", i, "args", ...]. - Every transaction error path is the literal address in the request body. Argument issues are at
["operations", i, "args", ...]; a step's own defects are at["operations", i, "resource"],"family","variant","fingerprint", an unknown key, or["operations", i]. See Error codes. - An unavailable operation points at
["family"]or["variant"](A2002/V1006), never["operation"]. deliberateis readable on every diagnostic yourdiagnosticsfunction receives; only operation diagnostics ever set it.
Server-side calls
- Behaviour change: server-side calls copy what you pass.
framework.application,framework.client,framework.execute,framework.executeTransactionand theappTx/clientTxbuilders read your arguments once, when you call them. Changing or revoking your object afterwards (an Immer draft afterproduce) no longer affects the operation; a live Proxy still works. A value that cannot be read or is not plain data (a revoked Proxy, a throwing getter, a class instance, aMap) is refused as422 A2004/V1001at its path, where it used to beA3000, aTypeErrorfrom a builder, or accepted. Inherited, non-enumerable and symbol keys are dropped. See Server-side usage. - A
misusehook counts those refusals. The optional root settingmisusereceives aFrameworkMisuseEvent(kind,path, and the operation'sorigin,resource,family,variantand step), never the value. See Configuration reference. - The generated client guards its arguments too. A value it cannot read is refused with a
TypeErrorthat names where, before anything is sent.
Fixes found in testing
- Root pipelines keep the framework typed. A guard, pipe, hook, interceptor or filter with a bare stage type in the root
pipelinesno longer turnsframework.application,framework.client,framework.appTxandframework.clientTxinto untyped objects. - A reference field and its relation in one record is a
422.authorIdwithauthor: { $connect: … }(required or optional relation) answersA2004/V1012,Set "authorId" or "author", not both., where it used to be500 A3001. With Prisma, a record that writes one relation through its field and another directly is refused the same way, and the Contract states it asreferenceWrites: "uniform". Regenerate the discovery artifact (npm run aventara:prepare) and the client (avclient generate): the artifact format is now 2 and the Contract hash of a Prisma-backed application changed. - Your issue messages reach the caller. A
FrameworkError("A2004", { message, issues })you throw from a pipeline or computed behavior sends each issue's message as written (non-blank, at most 1,000 characters; the path must name a member of the request's arguments). - A malformed
framework.executerequest is a422. A wrongrequestId,origin,protocol,transport,resource,familyorvariantanswersA2004with one issue per wrong member, and nothing runs; it used to beA3000or ran anyway.
Diagnostics
diagnosticshears more. It now also receives anA3xxxerror you threw on purpose (markeddeliberate: true), a result that does not match its Contract (OutputValidationError), a computed field that returns a refused value (ComputedValueError), and, in a custom host that mounted only some ofprotocol.surface(), a request for a route it left out (UnmountedRouteError). What the caller receives is unchanged. See Request IDs and diagnostics.FrameworkErrormessages. For any code outsideA3xxxthe message reaches the caller exactly as written: keep database text, SQL, stacks, paths and secrets out of it.
CLI and packages
- Progress while it works.
aventara new,aventara init,avclient initandavclient generateshow their steps on stderr (a spinner and the seconds so far on a terminal, one plain line per step in a pipe, in CI or withNO_COLOR), then a closing line with the total time.aventaragains--verboseto show the output ofnest newand the install as it runs. See CLI reference. - Next steps print last.
aventara newends with the next steps, after the commit and the closing line. The scaffold'sprebuildandprestarthooks are unchanged. @aventara/testingloads under CommonJS, and addsAvProtocolCorpus(the corpus's server side, to serve through your own client or host) and the corpus as plain JSON at@aventara/testing/fixtures/protocol-v1.json. The corpus gained rows for a transaction whose uniquewhereand filter reference an earlier step, and for a reference to a step that does not exist. See Testing and conformance.- Each package ships its
CHANGELOG.md, beside its README. @aventara/coredepends onzod, which only@aventara/core/jsonloads.
0.1.0-pilot.4
Published under the pilot dist-tag. Upgrading from pilot.3: see Upgrading.
Server and CLI
- The discovery artifact regenerates when you build or start.
aventara newandaventara initaddprebuild,prestart,prestart:devandprestart:debugscripts that runaventara:prepare. After a change toschema.prisma, the nextnpm run start:dev(orbuild,start,start:debug) regenerates the Prisma client and the artifact before the server compiles; no reinstall needed. npm and pnpm run these hooks; Yarn Berry and pnpm withenable-pre-post-scripts=falsedo not, so runaventara:prepareyourself there. On an existing project,aventara initkeeps aprebuildorprestart*script you already have unless you confirm or pass--yes. - A clear error for an old artifact. An artifact written by an older adapter now fails the TypeScript build with
Property 'regenerate_with_aventara_prepare' is missing, and the adapter refuses it at start-up with a message that namesaventara:prepare. The fix isnpm run aventara:prepare. See Troubleshooting. - The scaffold's rate-limiting note links to the docs: Rate limiting, where it used to carry a placeholder.
- Plainer messages. The generate step's error messages say "the generated client's types" in words, not with Prisma's emoji labels.
Packages
- The comments an editor shows on hover, and the READMEs, now describe what each export does, how to use it, what it returns and what it throws. This applies to
@aventara/core,@aventara/nest,@aventara/prisma7-adapter,@aventara/clientand@aventara/testing, and to the declarations a generated client copies from@aventara/core. @aventara/cliships no type declarations: it is a command, run withnpxor installed globally, and has no API to import.
0.1.0-pilot.3
Published under the pilot dist-tag. Upgrading from pilot.1 or pilot.2: see Upgrading.
Server and CLI
- CORS in the scaffold.
aventara newandaventara initturn CORS on insrc/main.tsfor the origins listed inCORS_ORIGINS(defaulthttp://localhost:5173,http://localhost:3001), and write that variable into.env, an existing.envincluded. Unset or empty means CORS is off, never open to every origin. Amain.tsthat already configures CORS is left alone, with a warning; one with noNestFactory.createline is not edited, and the lines are printed. Before this, a browser frontend on another port got a CORS error. See Deploying. - Rate limiting. The scaffold's
main.tsends with a commented-out platform-level limiter and a note:@nestjs/throttleris a Nest guard and does not run on Aventara's routes,express-rate-limitdoes. See Rate limiting. - A first commit.
aventara newends with a git commit,Initial Aventara Scaffold(never.env), unless git is not installed or configured, the directory is already inside a repository, or you pass--skip-git. --ormtakes an adapter name,prisma7;--helplists the values for--orm,--dband--package-manager.prismaandprisma@7are no longer accepted.
Frontend
avclient initrefuses a project with notsconfig.jsonbefore it writes or installs anything (plain JavaScript is not supported yet).avclient initadds.envto the frontend's.gitignore.- The generated client is type-checked with your project's own TypeScript, so the "typescript could not be resolved" warning appears only in a project without it, and Node's
stripTypeScriptTypeswarning is gone.
0.1.0-pilot.2
Generated client
- The client fits your project. It is still TypeScript source (
AvClient.tsandgenerated/), but its imports are now spelled the way yourtsconfig.jsonsays: extensionless underbundlerresolution (Next.js with Turbopack or webpack) or CommonJS,.jsundernodenext(NestJS 12),.tswhere your imports name.tsfiles (Vite'sreact-ts, Node's type stripping). You import it like your own files. Next.js 16 with Turbopack, which could not resolve the pilot.1 client, builds it. See Client setup. - The config loads like
prisma.config.avclient initwritesframework.client.ts;.ts,.mts,.cts,.js,.mjsand.cjsare read, in ESM or CommonJS syntax. A pilot.1framework.client.mtskeeps working. Two config files are refused. - The client type-checks under
"strict": false, thetsconfigNext.js writes. A project without atsconfig.jsonis refused. avclient --versionand-v;aventara -v.
Server
- Pipe output is validated. What a pipe returns is checked against the server's own contract before it reaches the database. A refusal is the new internal code
A3004(500) with the generic messageA server pipeline produced invalid arguments.; the pipe, operation and field go to the server log and your diagnostics sink only. See Pipelines. - A create that lacks a required field is
422A2004with aV1000issue at the field's path, instead of500A3001. It coverscreate.one, every row ofcreate.many, the create branch of an upsert, nested creates and transaction steps. - A
BigIntoutside signed 64-bit isA2004/V1004, instead ofA3001. - Orphaning a required child (
$set: []or$disconnecton a to-many whose children require the parent) is409A2008, instead ofA3001. - Internal failures are logged.
A3000toA3004each write one line to Nest's log (code, operation, scope, request id, error class; never the error's message). See Request IDs and diagnostics. - A stale discovery artifact stops the server at startup, with a sentence that names
aventara:prepare, when the Prisma client has a model or field the artifact lacks. The artifact's format changed, so regenerate it after upgrading. aventara newandinitadd.envto the project's.gitignore.
0.1.0-pilot.1
Upgrading from pilot.0 needs no code changes; see Upgrading.
Frontend setup
avclient initwritesframework.client.mts, an ES module that works in CommonJS and ESM projects (a plainnpm init -yfrontend now works without"type": "module"). From pilot.2 the default isframework.client.tsagain, and a.mtskeeps working.- TypeScript 7 is supported in the frontend. The generator's
typescriptpeer is>=5.5.0. Under TypeScript 7 the post-generate check is a syntax and shape check, reported in one warning; the client is still written. The CLI and the Prisma adapter still require TypeScript below 7 (the scaffolded server uses 6).
Server
- NestJS logs one startup line from the host:
Aventara mounted <N> operations at /api (+ GET /api/_contract, POST /api/_transactions). - A request without the identity headers answers
400 A2000, and the message now names the missing header or headers.
CLI
- The printed next steps use your package manager's runner (
npxorpnpm dlx) and the@pilotdist-tag. aventara new --help,aventara init --help,avclient init --helpandavclient generate --helpprint that command's usage and exit 0.
Known cosmetic
- The first
npx @aventara/client@pilot initcould print a "typescript could not be resolved" warning and Node's experimental-feature warning, even in a project that had TypeScript. Fixed in pilot.3.
0.1.0-pilot.0
The first public pilot release.
aventara newandaventara initscaffold a NestJS 12 server with Prisma 7 on SQLite or PostgreSQL, or add Aventara to an existing project.- Contracts compiled from your Prisma schema and configuration into three layers (core, application, client); only the client contract is served, at
GET <entrypoint>/_contract, with its hash asETag. - Standard operations for every Resource:
find(first,unique,many,count),create(one,many,count),update(first,unique,many,count),delete(first,unique,count) andupsert.unique, with filters, relation filters,select/include, ordering, offset and cursor pagination,$countand nested writes. - Interactive transactions with references between steps (
$ref), rollback and cascade refusal. - A stable response envelope with
Acodes for outcomes andVcodes for validation issues, and a fixed HTTP status per code. - Configuration: restrictions per layer (hidden Resources and fields, write-only fields, switched-off operations), limits, pipelines (guards, pipes, hooks, interceptors, filters), virtual fields and behaviors, and a diagnostics sink.
@aventara/nesthosts the protocol on Express or Fastify.@aventara/client:avclient initandavclient generateproduce a standalone typed client with typed errors,AbortSignaland customfetchsupport, andtx/transaction. The generated client has no runtime dependency on Aventara.@aventara/core/protocol: the HTTP protocol as plain data and functions, for writing your own host.@aventara/testing: conformance fixtures for adapters and hosts.- License: PolyForm Shield 1.0.0 with an additional permission; see License.