@aventara/testing 0.1.0-pilot.3 → 1.0.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,159 @@
1
+ # @aventara/testing
2
+
3
+ ## 1.0.0-rc.0
4
+
5
+ ### Major Changes
6
+
7
+ - First release candidate of Aventara 1.0. The public API is now considered stable; changes before 1.0.0 are possible
8
+ based on feedback.
9
+
10
+ ### Minor Changes
11
+
12
+ - 24ffbab: The protocol fixtures gain two transaction rows: a committed plan whose unique `where` and filter reference an earlier
13
+ step's result (`200 A1009`), and a reference in a `where` to a step that does not exist (`422 A2007` / `V1016`).
14
+
15
+ ### Patch Changes
16
+
17
+ - 24ffbab: `@aventara/testing` loads from CommonJS: `require("@aventara/testing")` returns the same module `import` does (its
18
+ export gains a `default` condition, as core's and nest's have). The package stays ESM; Node's `require()` of an ES
19
+ module needs Node 22.18 or later, which the package already requires.
20
+ - 24ffbab: Each package ships its `CHANGELOG.md`, so the release notes are in the installed package beside the README.
21
+ - 24ffbab: New export `AvProtocolCorpus`: the protocol corpus's server side, for serving it through a client or host you are
22
+ writing. `await AvProtocolCorpus.framework()` gives a fresh corpus `Framework` (pass a row's `Aventara-Contract-Hash`
23
+ to get the contract that row targets), `AvProtocolCorpus.entrypoint` is where to mount it (`"/api"`, every row's path is
24
+ relative to it), and `AvProtocolCorpus.clientContractHash` is the hash a client generated against it sends. The README
25
+ has a section on serving the corpus through your own stack.
26
+ - 24ffbab: The protocol corpus now ships as plain JSON at `@aventara/testing/fixtures/protocol-v1.json`, for clients and hosts
27
+ written in any language: the protocol version, the entrypoint, each ClientContract the rows target (its hash and exact
28
+ `GET /_contract` bytes) and every row of `AvProtocolFixtures`. It is generated from the TypeScript corpus when the
29
+ package is built. The one body too large to write out (1 MiB + 1 byte) is a recipe: `prefix` + `repeat` × `times` +
30
+ `suffix`. The README describes the format.
31
+ - 384a50c: **Behaviour changes, transactions:** every issue about a transaction step's arguments now names where it is
32
+ in the plan, `["operations", i, "args", …]`, beside `cause.operation: i`. Before, the framework's own checks — a wrong
33
+ type, an unknown field or operator, a structural limit, a Json value out of its `AvZ` shape, a value that is not valid
34
+ JSON — answered `["arguments", …]` inside a plan while a bad `$ref`, a refused value and a step that depends on a
35
+ cascaded delete already answered `["operations", i, "args", …]`; now one plan uses one spelling, through
36
+ `framework.transaction`, `framework.executeTransaction` and `POST /_transactions` alike. A single call is unchanged
37
+ (`["arguments", …]`). If your code reads `issue.path` from a transaction's answer, drop the `"operations", i, "args"`
38
+ prefix to get the path inside that step's arguments. `@aventara/testing`'s protocol corpus row "a client-hidden
39
+ field — /_transactions" and `fixtures/protocol-v1.json` carry the new path.
40
+ - 032f792: **Behaviour changes, transactions:** an error about a transaction step itself — rather than its arguments — now
41
+ names the step too, so every path in a transaction's answer is a path through the request you sent: an unknown or
42
+ hidden Resource is at `["operations", i, "resource"]`, a fingerprint that does not match at
43
+ `["operations", i, "fingerprint"]`, a missing or unknown key on the step at `["operations", i, "<key>"]`, and a step
44
+ that is not an object at `["operations", i]`. An operation the step cannot run points at the key that is wrong:
45
+ `["operations", i, "family"]` when the family is not a string, not a standard family, or one the Resource offers no
46
+ variant of; `["operations", i, "variant"]` when the family is right and the variant is not (unknown, not offered or
47
+ restricted for it). Before, these answered `["resource"]`, `["operation"]`, `["fingerprint"]` and `["<key>"]`.
48
+ `cause.operation` and the outcome code are unchanged: an unknown Resource is still `A2001`, an unavailable operation
49
+ `A2002`. `framework.transaction([...])` refuses an entry that is not a handle, or one handle placed twice, at
50
+ `["operations", i]`. A single call is unchanged. `@aventara/testing`'s protocol corpus and `fixtures/protocol-v1.json`
51
+ carry the new paths.
52
+ - dbe6961: **Behaviour changes:** a single call to an operation a Resource does not offer now names the key of the
53
+ request that is wrong instead of `["operation"]`, which no request has: `["family"]` when the family is not a standard
54
+ one or the Resource offers none of its operations, `["variant"]` when the family is right and the variant is not. This
55
+ holds for `framework.execute` and for an HTTP route, whose `/_resources/:resource/:family/:variant` segments are named
56
+ by those same keys. The code is unchanged (`A2002` / `V1006`), and so is `["resource"]` for an unknown Resource.
57
+ `@aventara/testing`'s protocol corpus and `fixtures/protocol-v1.json` carry the new paths.
58
+ - Updated dependencies [24ffbab]
59
+ - Updated dependencies [24ffbab]
60
+ - Updated dependencies [24ffbab]
61
+ - Updated dependencies [24ffbab]
62
+ - Updated dependencies [24ffbab]
63
+ - Updated dependencies [24ffbab]
64
+ - Updated dependencies [24ffbab]
65
+ - Updated dependencies [24ffbab]
66
+ - Updated dependencies [24ffbab]
67
+ - Updated dependencies [24ffbab]
68
+ - Updated dependencies [24ffbab]
69
+ - Updated dependencies [24ffbab]
70
+ - Updated dependencies [a973e89]
71
+ - Updated dependencies [1e65e9a]
72
+ - Updated dependencies
73
+ - Updated dependencies [a973e89]
74
+ - Updated dependencies [a973e89]
75
+ - Updated dependencies [1e65e9a]
76
+ - Updated dependencies [1e65e9a]
77
+ - Updated dependencies [24ffbab]
78
+ - Updated dependencies [2d00ad7]
79
+ - Updated dependencies [384a50c]
80
+ - Updated dependencies [d43d1cc]
81
+ - Updated dependencies [3f98749]
82
+ - Updated dependencies [032f792]
83
+ - Updated dependencies [dbe6961]
84
+ - @aventara/core@1.0.0-rc.0
85
+
86
+ ## 0.1.0-pilot.4
87
+
88
+ ### Patch Changes
89
+
90
+ - f37dcd6: The documentation shipped with the package — the comments an editor shows on hover, and the README — now describes
91
+ what each export does, how to use it, what it returns and what it throws. Notes about how the package was built and
92
+ why are no longer part of what is published.
93
+ - Updated dependencies [f37dcd6]
94
+ - @aventara/core@0.1.0-pilot.4
95
+
96
+ ## 0.1.0-pilot.3
97
+
98
+ ### Patch Changes
99
+
100
+ - @aventara/core@0.1.0-pilot.3
101
+
102
+ ## 0.1.0-pilot.2
103
+
104
+ ### Patch Changes
105
+
106
+ - e69e88d: The published packages are written for the people who use them. The JavaScript ships without comments; the type
107
+ declarations keep their documentation, and it, the error messages, the protocol fixtures' labels and the comments in a
108
+ generated client no longer carry the project's internal development notes.
109
+ - ab58424: A pipe's output is checked against the server's own rules before it reaches the adapter. The request is validated
110
+ against the caller's Contract; after each pipe, the arguments are validated against the Core Contract (plus fields the
111
+ caller's scope adds), so a pipe may still write what the caller cannot — a field the client Contract hides, such as a
112
+ password hash — but not what the server cannot store. A refusal is the new internal code `A3004 PIPE_OUTPUT_INVALID` (HTTP
113
+ 500). The caller receives only the generic `A server pipeline produced invalid arguments.`, like every internal
114
+ failure; the pipe (`pipes[i]` and its function name), the operation, the offending field and its V-code go to the
115
+ diagnostics sink as a `PipeOutputError`, and `@aventara/nest` writes them into its error log line. Nothing reaches the
116
+ adapter. This holds for operations and transaction steps alike (a plan answers with `cause.operation`). Generated
117
+ clients map `A3004` to `InternalError`; the protocol fixtures in `@aventara/testing` include an `A3004` row.
118
+ - Updated dependencies [c2a3b7c]
119
+ - Updated dependencies [e69e88d]
120
+ - Updated dependencies [ab58424]
121
+ - Updated dependencies [23937f8]
122
+ - @aventara/core@0.1.0-pilot.2
123
+
124
+ ## 0.1.0-pilot.1
125
+
126
+ ### Patch Changes
127
+
128
+ - a45f8a8: The `400 A2000` for a request without its identity headers now says which one: `cause.message` is "The
129
+ Aventara-Contract-Hash header is missing." (or "The Aventara-Protocol-Version header is missing.", or both, naming both),
130
+ instead of the generic "The request is malformed and cannot be interpreted." It names headers only and echoes no value.
131
+ The code, status and every other `A2000` are unchanged. The protocol fixtures in `@aventara/testing` check the new messages, with a row
132
+ for a request with neither header.
133
+ - 799346e: The package READMEs (each package's npm page) are rewritten for the people who use the packages. The Prisma 7
134
+ adapter's README now states the actual behaviour of a to-many relation or `$count` projected on
135
+ `update.many`/`create.many`: a validation refusal, `A2004` with `V1008`, before any statement runs (it said `A3001`).
136
+ Every install or run command names the `@pilot` dist-tag.
137
+ - Updated dependencies [a45f8a8]
138
+ - Updated dependencies [799346e]
139
+ - @aventara/core@0.1.0-pilot.1
140
+
141
+ ## 0.1.0-pilot.0
142
+
143
+ ### Minor Changes
144
+
145
+ - First pilot release of `@aventara/testing`: tools for testing an Aventara application, an adapter or a host.
146
+
147
+ - `createFakeAdapter` — an in-memory adapter, so a Framework can be built and exercised without a database. Its
148
+ discovered model is `FakeAdapter.model`.
149
+ - `AvProtocolFixtures` (each one an `AvProtocolFixture`) — the HTTP protocol's conformance fixtures: every outcome
150
+ code and success status, contract discovery (`GET /_contract`), missing routes and a stale client, argument
151
+ validation, body, media-type and method refusals, transactions (including client-scope rules on `/_transactions`),
152
+ wire scalars and request ids.
153
+ - `AvProtocolDriver` and `runAvProtocolConformance(driver)` — run the fixtures against your own host: the driver
154
+ mounts the protocol for a Framework and answers requests, and each fixture's mismatches are reported.
155
+
156
+ ### Patch Changes
157
+
158
+ - Updated dependencies
159
+ - @aventara/core@0.1.0-pilot.0
package/README.md CHANGED
@@ -8,6 +8,9 @@ from outside.
8
8
  - `runAvProtocolConformance(driver)` — drives the protocol corpus (`AvProtocolFixtures`) through one host and reports,
9
9
  per row, every mismatch with the expected status, code, cause, headers and body. A host supplies an
10
10
  `AvProtocolDriver`: given a `Framework`, a session that sends one protocol request and returns the answer, and closes.
11
+ - `AvProtocolCorpus` — the corpus's server side, for serving it through your own client or host (below).
12
+ - `@aventara/testing/fixtures/protocol-v1.json` — the same corpus as plain JSON, for a client or host written in any
13
+ language (below).
11
14
 
12
15
  It depends on `@aventara/core` only.
13
16
 
@@ -19,6 +22,54 @@ npm install --save-dev @aventara/testing
19
22
 
20
23
  Node `^22.18.0 || >=24.2.0`. ES modules.
21
24
 
25
+ ## Serving the corpus through your own stack
26
+
27
+ `runAvProtocolConformance` builds the corpus `Framework` itself. To serve it from a host or test bench of your own — to
28
+ point a client you are writing at it, say — take it from `AvProtocolCorpus`:
29
+
30
+ ```ts
31
+ import { AvProtocolCorpus, AvProtocolFixtures } from "@aventara/testing";
32
+
33
+ const framework = await AvProtocolCorpus.framework();
34
+ // Mount `framework` under AvProtocolCorpus.entrypoint ("/api") with your host,
35
+ // then send each row's `request` and compare the answer with the row's `server`.
36
+ ```
37
+
38
+ - `framework(contractHash?)` — a fresh corpus `Framework`, backed by an in-memory adapter whose answers every row
39
+ states. Pass a row's `Aventara-Contract-Hash` to get the contract that row targets: a few rows target a second
40
+ contract without transactions, and every other hash (a stale one included) gets the main contract.
41
+ - `entrypoint` — where the corpus is mounted; every row's `path` is relative to it.
42
+ - `clientContractHash` — the main ClientContract's hash: what a client generated against it sends, and what
43
+ `GET /_contract` names.
44
+
45
+ A row whose `client.outcome` is `"transport-error"` describes an answer no host produces (a proxy's HTML page, a
46
+ dropped connection); skip it when serving, as the conformance run does. A row's `adapterCalls` counts the operations
47
+ the corpus adapter was handed, which only the conformance run can observe.
48
+
49
+ ## The corpus as JSON
50
+
51
+ `@aventara/testing/fixtures/protocol-v1.json` holds the whole corpus for protocol version 1, generated from
52
+ `AvProtocolFixtures` when the package is built — every row, in the same order and with the same fields — so a client or
53
+ host written in another language can be held to the same answers:
54
+
55
+ ```json
56
+ {
57
+ "protocolVersion": 1,
58
+ "entrypoint": "/api",
59
+ "clientContractHash": "sha256:…",
60
+ "contracts": [{ "hash": "sha256:…", "document": "{\"enums\":{},…}" }, …],
61
+ "rows": [{ "label": "…", "request": { … }, "server": { … }, "client": { … } }, …]
62
+ }
63
+ ```
64
+
65
+ - `contracts` — each ClientContract a row targets: its hash and its exact `GET /_contract` bytes (`document`, a
66
+ string). The first is the main contract, `clientContractHash`; a row targets the other by sending its hash.
67
+ - `rows` — as `AvProtocolFixture` describes them. One body is too large to write out (one byte over the 1 MiB request
68
+ limit): its `request.body.content` is a recipe, `{ "prefix": "…", "repeat": "x", "times": 1048567, "suffix": "…" }`,
69
+ standing for `prefix` + `repeat` repeated `times` times + `suffix`. Every other `content` is the body's text.
70
+
71
+ From JavaScript: `require("@aventara/testing/fixtures/protocol-v1.json")`, or `import` it `with { type: "json" }`.
72
+
22
73
  ## Documentation
23
74
 
24
75
  `@aventara/core`'s README is where to start (the framework, its adapters and hosts). `@aventara/nest` is held to this
@@ -1,3 +1,3 @@
1
1
  import type { Adapter, AdapterModel } from "@aventara/core";
2
- /** Stable construction seam consumed by reusable adapter conformance suites. */
2
+ /** Builds the Adapter under test; reusable adapter conformance suites take one. */
3
3
  export type AdapterFactory<M extends AdapterModel = AdapterModel> = () => Adapter<M> | Promise<Adapter<M>>;
@@ -1,28 +1,43 @@
1
- import type { Adapter, AdapterModel, AdapterOperation } from "@aventara/core";
1
+ import type { Adapter, AdapterModel } from "@aventara/core";
2
+ import type { AdapterOperation } from "@aventara/core/internal";
3
+ /** Answers one operation the fake Adapter is handed. */
2
4
  export type FakeAdapterExecute<M extends AdapterModel> = (operation: AdapterOperation<M>) => unknown | Promise<unknown>;
5
+ /** How a transaction ended: `"committed"` or `"rolled-back"`. */
3
6
  export type FakeTransactionOutcome = "committed" | "rolled-back";
4
7
  /** One settled transaction boundary: what ran inside it, and how it ended. */
5
8
  export type FakeTransactionTrace<M extends AdapterModel> = {
9
+ /** The operations run inside the transaction, in order. */
6
10
  readonly operations: readonly AdapterOperation<M>[];
11
+ /** How the transaction ended. */
7
12
  readonly outcome: FakeTransactionOutcome;
8
13
  };
9
14
  /**
10
- * Real rollback for caller-owned state: snapshot on begin, restore when the
11
- * work rejects. Without it the fake Adapter records boundaries but persists
12
- * whatever the work wrote, so a rollback assertion would prove nothing.
15
+ * Rollback for state the test owns: `snapshot` when a transaction begins,
16
+ * `restore` when its work rejects. Without it, the fake Adapter records
17
+ * transaction boundaries but keeps whatever the work wrote.
13
18
  */
14
19
  export type FakeAdapterRollback<S> = {
20
+ /** Captures the state before a transaction's work runs. */
15
21
  readonly snapshot: () => S;
22
+ /** Puts the captured state back after the work rejects. */
16
23
  readonly restore: (snapshot: S) => void;
17
24
  };
25
+ /** Options for {@link createFakeAdapter}. */
18
26
  export type FakeAdapterOptions<M extends AdapterModel, S = unknown> = {
27
+ /** The adapter model the fake Adapter reports. */
19
28
  readonly model: M;
29
+ /** Answers each operation; without it, every operation resolves `undefined`. */
20
30
  readonly execute?: FakeAdapterExecute<M>;
31
+ /** Makes transactions roll back the test's own state; see {@link FakeAdapterRollback}. */
21
32
  readonly rollback?: FakeAdapterRollback<S>;
22
33
  };
34
+ /**
35
+ * The Adapter {@link createFakeAdapter} returns, with a record of every
36
+ * operation and transaction it ran.
37
+ */
23
38
  export type FakeAdapter<M extends AdapterModel = AdapterModel> = Adapter<M> & {
24
39
  readonly operations: readonly AdapterOperation<M>[];
25
40
  readonly transactions: readonly FakeTransactionTrace<M>[];
26
41
  };
27
- /** Reusable provider-free Adapter implementation for framework/conformance tests. */
42
+ /** A provider-free, in-memory Adapter for framework and conformance tests. */
28
43
  export declare function createFakeAdapter<M extends AdapterModel, S = unknown>(options: FakeAdapterOptions<M, S>): FakeAdapter<M>;
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export type { AdapterFactory } from "./adapters/adapter-factory.js";
2
2
  export type { FakeAdapter, FakeAdapterExecute, FakeAdapterOptions, FakeAdapterRollback, FakeTransactionOutcome, FakeTransactionTrace, } from "./adapters/fake-adapter.js";
3
3
  export { createFakeAdapter } from "./adapters/fake-adapter.js";
4
4
  export { runAvProtocolConformance } from "./protocol/protocol-conformance.js";
5
+ export { AvProtocolCorpus } from "./protocol/protocol-corpus.js";
5
6
  export type { AvProtocolDriver } from "./protocol/protocol-driver.js";
6
7
  export type { AvProtocolFixture } from "./protocol/protocol-fixture.js";
7
8
  export { AvProtocolFixtures } from "./protocol/protocol-fixtures.js";
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { createFakeAdapter } from "./adapters/fake-adapter.js";
2
2
  export { runAvProtocolConformance } from "./protocol/protocol-conformance.js";
3
+ export { AvProtocolCorpus } from "./protocol/protocol-corpus.js";
3
4
  export { AvProtocolFixtures } from "./protocol/protocol-fixtures.js";
@@ -3,14 +3,15 @@ import { Decimal } from "@aventara/core";
3
3
  /**
4
4
  * The corpus's adapter model:
5
5
  *
6
- * - `authors` — every standard operation (family 2); its `secret` field is hidden
7
- * at client scope;
8
- * - `books` — references `authors`, and deleting an author cascades into it
9
- * ({@link CORPUS_REFERENTIAL_ACTIONS}; a plan's `V1019`); the client scope adds
10
- * a computed `label` to it;
11
- * - `secrets` — its one operation restricted away at client scope;
12
- * - `hiddenRes` — hidden at client scope;
13
- * - `samples` — one field of each scalar whose wire form is not its runtime form.
6
+ * - `authors`: every standard operation; its `secret` field is hidden at
7
+ * client scope;
8
+ * - `books`: references `authors`, and deleting an author cascades into it
9
+ * ({@link CORPUS_REFERENTIAL_ACTIONS}; a plan's `V1019`); the client scope
10
+ * adds a computed `label` to it ({@link CORPUS_CLIENT_CONFIG});
11
+ * - `secrets`: its one operation restricted away at client scope;
12
+ * - `hiddenRes`: hidden at client scope;
13
+ * - `samples`: one field of each scalar whose wire form is not its runtime
14
+ * form (`bigint`, `datetime`, `decimal`, `bytes`).
14
15
  */
15
16
  export declare const CORPUS_MODEL: {
16
17
  readonly transactions: "interactive";
@@ -274,12 +275,12 @@ export declare const CORPUS_REFERENTIAL_ACTIONS: {
274
275
  readonly books: readonly [{
275
276
  readonly field: "author";
276
277
  readonly target: "authors";
278
+ readonly references: readonly ["authorId"];
277
279
  readonly onDelete: "cascade";
278
280
  }];
279
281
  };
280
282
  /**
281
- * The `where.id` a row sends to make the corpus Framework fail one way
282
- * (families 10 and 12, "a fake adapter returning the code, labelled so"):
283
+ * The `where.id` a row sends to make the corpus Framework fail one way:
283
284
  *
284
285
  * - `A4000`, `A4001`, `A4002`, `A3000` — the client guard throws
285
286
  * `FrameworkError("A4000")`, denies (`false`), throws
@@ -314,7 +315,7 @@ declare function corpusGuard(context: PipelineContext): boolean;
314
315
  declare function corpusPipe(args: unknown): unknown;
315
316
  /**
316
317
  * The corpus's client-scope configuration: what the ClientContract hides or
317
- * restricts, and `books.label` — a VIRTUAL field added at client scope and
318
+ * restricts, and `books.label`, a VIRTUAL field added at client scope and
318
319
  * computed on read from `id`.
319
320
  */
320
321
  export declare const CORPUS_CLIENT_CONFIG: {
@@ -372,24 +373,20 @@ export declare const CORPUS_CLIENT_CONFIG: {
372
373
  export declare const CORPUS_ENTRYPOINT = "/api";
373
374
  /**
374
375
  * The corpus ClientContract's hash: what a generated client for it sends as
375
- * `Aventara-Contract-Hash`. A literal, as a generated client holds it;
376
- * `corpus-contract.spec.ts` fails, naming this constant, when the compiled
377
- * contract moves.
376
+ * `Aventara-Contract-Hash`. A literal, as a generated client holds it.
378
377
  */
379
378
  export declare const CORPUS_CLIENT_CONTRACT_HASH = "sha256:07b1a75a6e8e88e30115ae81bcf7e3a12fa493bddce995a01867e2381bd158d0";
380
379
  /**
381
380
  * The corpus ClientContract as `GET /_contract` serves it: its JCS-canonical
382
381
  * bytes, `protocol.hash` included. A literal, as a host must serve it byte for
383
- * byte; `corpus-contract.spec.ts` fails, naming this constant, when the compiled
384
- * contract moves.
382
+ * byte.
385
383
  */
386
384
  export declare const CORPUS_CLIENT_CONTRACT_DOCUMENT = "{\"enums\":{},\"limits\":{\"maxBooleanNodes\":50,\"maxListLimit\":250,\"maxNestingDepth\":12,\"maxRequestBytes\":1048576,\"maxTransactionOperations\":20},\"protocol\":{\"hash\":\"sha256:07b1a75a6e8e88e30115ae81bcf7e3a12fa493bddce995a01867e2381bd158d0\",\"version\":1},\"resources\":{\"authors\":{\"fields\":{\"id\":{\"capabilities\":{\"filter\":[\"equals\"],\"select\":true},\"kind\":\"scalar\",\"lifecycle\":[],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"string\"}},\"name\":{\"capabilities\":{\"create\":[],\"filter\":[\"equals\"],\"select\":true,\"update\":[]},\"kind\":\"scalar\",\"lifecycle\":[],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"string\"}}},\"identifiers\":[[\"id\"]],\"operations\":{\"create\":{\"count\":true,\"many\":true,\"one\":true},\"delete\":{\"count\":true,\"first\":true,\"many\":true,\"unique\":true},\"find\":{\"count\":true,\"first\":true,\"many\":true,\"unique\":true},\"update\":{\"count\":true,\"first\":true,\"many\":true,\"unique\":true},\"upsert\":{\"unique\":true}}},\"books\":{\"fields\":{\"authorId\":{\"capabilities\":{\"filter\":[\"equals\"],\"select\":true},\"kind\":\"scalar\",\"lifecycle\":[],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"string\"}},\"id\":{\"capabilities\":{\"filter\":[\"equals\"],\"select\":true},\"kind\":\"scalar\",\"lifecycle\":[],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"string\"}},\"label\":{\"capabilities\":{\"select\":true},\"kind\":\"scalar\",\"lifecycle\":[\"COMPUTED_ON_READ\",\"VIRTUAL\"],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"string\"}}},\"identifiers\":[[\"id\"]],\"operations\":{\"find\":{\"many\":true}}},\"samples\":{\"fields\":{\"amount\":{\"capabilities\":{\"create\":[],\"select\":true},\"kind\":\"scalar\",\"lifecycle\":[],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"decimal\"}},\"at\":{\"capabilities\":{\"create\":[],\"select\":true},\"kind\":\"scalar\",\"lifecycle\":[],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"datetime\"}},\"blob\":{\"capabilities\":{\"create\":[],\"select\":true},\"kind\":\"scalar\",\"lifecycle\":[],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"bytes\"}},\"count\":{\"capabilities\":{\"create\":[],\"select\":true},\"kind\":\"scalar\",\"lifecycle\":[],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"bigint\"}},\"id\":{\"capabilities\":{\"filter\":[\"equals\"],\"select\":true},\"kind\":\"scalar\",\"lifecycle\":[],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"string\"}}},\"identifiers\":[[\"id\"]],\"operations\":{\"create\":{\"one\":true},\"find\":{\"unique\":true}}},\"secrets\":{\"fields\":{\"id\":{\"capabilities\":{\"filter\":[\"equals\"],\"select\":true},\"kind\":\"scalar\",\"lifecycle\":[],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"string\"}}},\"identifiers\":[[\"id\"]],\"operations\":{}}},\"scalars\":{\"bigint\":{\"builtin\":true},\"bytes\":{\"builtin\":true},\"datetime\":{\"builtin\":true},\"decimal\":{\"builtin\":true},\"string\":{\"builtin\":true}},\"transactions\":\"interactive\"}";
387
385
  /**
388
- * The hash of the corpus's SECOND ClientContract — the same model and
389
- * configuration with `transactions: "none"` (family 6's `/_transactions` absence).
390
- * A row targets it by sending this hash, exactly as a client generated against it
391
- * would; the runner gives the row the Framework whose ClientContract it names. A
392
- * literal, held to the compiled value by `corpus-contract.spec.ts`.
386
+ * The hash of the corpus's second ClientContract: the same model and
387
+ * configuration with `transactions: "none"` (the `/_transactions` absence
388
+ * rows). A row targets it by sending this hash, exactly as a client generated
389
+ * against it would.
393
390
  */
394
391
  export declare const CORPUS_NONE_CLIENT_CONTRACT_HASH = "sha256:d333e8396b9bf9127e0859ed69b9874f230058a597e2129e6b9e9b380daef618";
395
392
  /** The one record the corpus Adapter knows. */
@@ -81,7 +81,14 @@ export const CORPUS_MODEL = {
81
81
  },
82
82
  };
83
83
  export const CORPUS_REFERENTIAL_ACTIONS = {
84
- books: [{ field: "author", target: "authors", onDelete: "cascade" }],
84
+ books: [
85
+ {
86
+ field: "author",
87
+ target: "authors",
88
+ references: ["authorId"],
89
+ onDelete: "cascade",
90
+ },
91
+ ],
85
92
  };
86
93
  export const CORPUS_TRIGGER = {
87
94
  unauthenticated: "A4000",
@@ -1,10 +1,8 @@
1
1
  import type { AvProtocolDriver } from "./protocol-driver.js";
2
2
  /**
3
- * Driver 1: the protocol hosted in process, with no HTTP stack — a catch-all host
4
- * that hands `/_contract` to `protocol.encodeContract`, `/_transactions` to the
5
- * bound shortcut `protocol.handleTransaction`, every other request to
6
- * `protocol.handleOperation`, and writes back exactly what they answer. What it
7
- * proves is the framework's own answer, host-free; driver 2 must answer every row
8
- * identically through a real server.
3
+ * The protocol hosted in process, with no HTTP stack: `/_contract` goes to
4
+ * `protocol.encodeContract`, `/_transactions` to `protocol.handleTransaction`,
5
+ * every other request to `protocol.handleOperation`, and the answer is written
6
+ * back as it is.
9
7
  */
10
8
  export declare const inProcessDriver: AvProtocolDriver;
@@ -1,11 +1,10 @@
1
1
  import type { AvProtocolDriver } from "./protocol-driver.js";
2
2
  /**
3
- * The per-route driver: the protocol hosted in process as a host that mounts ONE
4
- * route per `protocol.surface()` entry — each operation on `handleOperation`,
3
+ * The protocol hosted in process as a host that mounts one route per
4
+ * `protocol.surface()` entry (each operation on `handleOperation`,
5
5
  * `/_transactions` on `handleTransaction`, `/_contract` on `encodeContract`,
6
- * matched by method and path — and `protocol.answerAbsent` as the fallback for
7
- * everything it did not mount. `undefined` from the fallback is a path outside the
8
- * protocol, which this host answers with its own bodiless 404. Every corpus row
9
- * must answer here exactly as through driver 1's catch-all.
6
+ * matched by method and path) and uses `protocol.answerAbsent` as the fallback
7
+ * for everything it did not mount. `undefined` from the fallback is a path
8
+ * outside the protocol, which this host answers with its own bodiless 404.
10
9
  */
11
10
  export declare const perRouteDriver: AvProtocolDriver;
@@ -1,29 +1,30 @@
1
1
  import type { AvProtocolDriver } from "./protocol-driver.js";
2
2
  /**
3
- * `runAvProtocolConformance(driver)` — the corpus, driven through one host.
3
+ * `runAvProtocolConformance(driver)` — drives the protocol fixture corpus
4
+ * through one host.
4
5
  *
5
- * Every row a host answers is driven — the rows whose `client.outcome` is
6
+ * Every row a host answers is driven; rows whose `client.outcome` is
6
7
  * `"transport-error"` describe what a client receives instead of a framework
7
8
  * answer, so they are skipped. Each driven row runs against a fresh corpus
8
- * Framework, through a fresh driver session, with one request — the corpus
9
- * Framework whose ClientContract hash the row's `Aventara-Contract-Hash` names, as
10
- * a generated client names it — and its answer is held to the row's `server`
11
- * block:
9
+ * Framework, through a fresh driver session, with one request (the corpus
10
+ * Framework whose ClientContract hash the row's `Aventara-Contract-Hash`
11
+ * names; the main corpus contract for any other hash), and its answer is
12
+ * held to the row's `server` block:
12
13
  *
13
14
  * - `status`, exactly;
14
- * - `code`: the body is a framework envelope (`AvProtocol.isOperationResponse`,
15
- * the one envelope test) carrying that code — or, for `null`, is none;
15
+ * - `code`: the body is a framework envelope carrying that code — or, for
16
+ * `null`, is none;
16
17
  * - `causeKeys`: the exact sorted key set of `cause`;
17
18
  * - `issues`: each issue's `code` and `path`, in order;
18
19
  * - `adapterCalls`: the operations the corpus Adapter was handed, exactly;
19
- * - `responseHeaders`: each named header present (names matched without regard to
20
- * case) with exactly that value — a host may add its own;
20
+ * - `responseHeaders`: each named header present (names matched without
21
+ * regard to case) with exactly that value — a host may add its own;
21
22
  * - `bodyBytes`: the body text, exactly.
22
23
  *
23
- * Resolves one result per driven row, in corpus order, each with the mismatches
24
- * found — none when the host conforms. A driver that throws is a mismatch of its
25
- * row, not a rejection of the run. Test-runner free: a caller asserts on the
26
- * results with whatever runner it uses.
24
+ * Resolves one result per driven row, in corpus order, each with the
25
+ * mismatches found — none when the host conforms. A driver that throws is a
26
+ * mismatch of its row, not a rejection of the run. Works with any test
27
+ * runner: assert on the results with whatever runner you use.
27
28
  */
28
29
  export declare function runAvProtocolConformance(driver: AvProtocolDriver): Promise<readonly {
29
30
  readonly label: string;
@@ -0,0 +1,30 @@
1
+ import type { Framework } from "@aventara/core";
2
+ /**
3
+ * The server side of the protocol fixture corpus (`AvProtocolFixtures`), for
4
+ * a client or host author who serves it through their own stack: a Framework
5
+ * whose answers every corpus row states, the entrypoint its rows' paths are
6
+ * relative to, and the hash of the ClientContract it serves.
7
+ *
8
+ * ```ts
9
+ * const framework = await AvProtocolCorpus.framework();
10
+ * // mount it under AvProtocolCorpus.entrypoint with your host, then send the
11
+ * // rows of AvProtocolFixtures and compare each answer with the row's `server`
12
+ * ```
13
+ */
14
+ export declare const AvProtocolCorpus: {
15
+ /**
16
+ * A fresh corpus Framework, backed by an in-memory Adapter whose answer is a
17
+ * pure function of the operation it is handed. `contractHash` picks which
18
+ * corpus ClientContract it serves, as a row's `Aventara-Contract-Hash` does:
19
+ * the corpus's `transactions: "none"` contract for that contract's hash (the
20
+ * `/_transactions` absence rows send it), the main one for anything else.
21
+ */
22
+ readonly framework: (contractHash?: string) => Promise<Framework>;
23
+ /** Where the corpus Framework is mounted; every row's `path` is relative to it. */
24
+ readonly entrypoint: string;
25
+ /**
26
+ * The hash of the main corpus ClientContract — what a client generated against
27
+ * it sends as `Aventara-Contract-Hash`, and what `GET /_contract` names.
28
+ */
29
+ readonly clientContractHash: string;
30
+ };
@@ -0,0 +1,6 @@
1
+ import { CORPUS_CLIENT_CONTRACT_HASH, CORPUS_ENTRYPOINT, createCorpusFramework, } from "./corpus-contract.fixture.js";
2
+ export const AvProtocolCorpus = Object.freeze({
3
+ framework: async (contractHash) => (await createCorpusFramework(contractHash)).framework,
4
+ entrypoint: CORPUS_ENTRYPOINT,
5
+ clientContractHash: CORPUS_CLIENT_CONTRACT_HASH,
6
+ });
@@ -1,14 +1,11 @@
1
1
  import type { Framework } from "@aventara/core";
2
2
  import type { AvProtocolRequest, AvProtocolResponse } from "@aventara/core/protocol";
3
3
  /**
4
- * One host under test, as the protocol conformance run drives it
5
- * (`runAvProtocolConformance`): given the corpus's Framework, mount the protocol
6
- * the way that host does, then answer each corpus request as that host would
7
- * answer it over HTTP — status, headers and body text, as an `AvProtocolResponse`.
8
- *
9
- * Driver 1 (`in-process.driver.ts`) calls the bound protocol's shortcuts directly;
10
- * a real host sends the request over its own stack and reads back what arrived.
11
- * The run calls `close` once per row, after its one request.
4
+ * One host under test, as `runAvProtocolConformance` drives it: given the
5
+ * corpus's Framework, mount the protocol the way that host does, then answer
6
+ * each corpus request as that host would answer it over HTTP (status, headers
7
+ * and body text, as an `AvProtocolResponse`). The run calls `close` once per
8
+ * row, after its one request.
12
9
  */
13
10
  export type AvProtocolDriver = (framework: Framework) => Promise<{
14
11
  readonly send: (request: AvProtocolRequest) => Promise<AvProtocolResponse>;
@@ -1,25 +1,27 @@
1
1
  import type { OperationCode, ValidationCode } from "@aventara/core";
2
2
  import type { AvProtocolRequest } from "@aventara/core/protocol";
3
3
  /**
4
- * Types only; the rows live in `protocol-fixtures.ts`.
4
+ * One row of the protocol fixture corpus: the HTTP request a host receives
5
+ * (`request`), what the host must answer (`server`), and what a generated
6
+ * client must do with that answer (`client`).
5
7
  *
6
- * `request` is core's own request value, `AvProtocolRequest` from
7
- * `@aventara/core/protocol`, so a row is exactly what a host hands the bound
8
- * protocol: no second request shape exists for the corpus to drift from.
8
+ * `request` is an `AvProtocolRequest` from `@aventara/core/protocol`, exactly
9
+ * what a host hands the bound protocol.
9
10
  */
10
11
  export type AvProtocolFixture = {
11
12
  /**
12
- * A row that covers one of the specification's success rows begins with that row's
13
- * operation name (`find.many …`, `create.one …`) or with `transaction` for the
14
- * committed-plan row.
13
+ * The row's name. A row for a standard operation's success begins with that
14
+ * operation's name (`find.many …`, `create.one …`), or with `transaction` for
15
+ * the committed-plan row.
15
16
  */
16
17
  readonly label: string;
17
18
  /** Method, entrypoint-relative path, headers, and a raw or parsed body. */
18
19
  readonly request: AvProtocolRequest;
20
+ /** What the host must answer. */
19
21
  readonly server: {
20
22
  /**
21
- * The HTTP status. `0` only on a transport-error row (family 11) where no
22
- * HTTP response arrived at all — the Fetch API's network-error status.
23
+ * The HTTP status. `0` only on a transport-error row, where no HTTP
24
+ * response arrived at all (the Fetch API's network-error status).
23
25
  */
24
26
  readonly status: number;
25
27
  /** `null`: the host produces no framework envelope at all. */
@@ -36,11 +38,12 @@ export type AvProtocolFixture = {
36
38
  /** The exact body, where bytes matter. */
37
39
  readonly bodyBytes?: string;
38
40
  };
41
+ /** What a generated client must do with the answer. */
39
42
  readonly client: {
40
43
  /**
41
44
  * What a generated client does with the answer. `"transport-error"`
42
- * also marks a row no host produces (family 11): a host driver filters
43
- * on `client.outcome === "transport-error"` and does not drive those rows.
45
+ * also marks a row no host produces: a host driver filters on
46
+ * `client.outcome === "transport-error"` and does not drive those rows.
44
47
  */
45
48
  readonly outcome: "resolve" | "framework-error" | "transport-error";
46
49
  /** Present iff `outcome` is `"framework-error"`. */
@@ -1,2 +1,7 @@
1
1
  import type { AvProtocolFixture } from "./protocol-fixture.js";
2
+ /**
3
+ * The protocol fixture corpus: passive data rows, each an HTTP request, the
4
+ * answer a host must give and what a generated client must do with it. Drive
5
+ * them through a host with `runAvProtocolConformance`.
6
+ */
2
7
  export declare const AvProtocolFixtures: readonly AvProtocolFixture[];