@aventara/core 1.0.0-rc.0 → 1.0.0-rc.10

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