@aventara/testing 0.1.0-pilot.4 → 1.0.0-rc.1

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/README.md CHANGED
@@ -1,24 +1,75 @@
1
1
  # `@aventara/testing`
2
2
 
3
- Conformance fixtures and testing utilities for Aventara extensions: what an adapter or a transport host is held to,
4
- from outside.
3
+ Conformance fixtures and testing utilities for Aventara extensions. They test an adapter or a transport host from
4
+ outside.
5
5
 
6
- - `createFakeAdapter` — an in-memory adapter, with transaction tracing, for testing code that runs a `Framework`
6
+ - `createFakeAdapter`: an in-memory adapter, with transaction tracing, for testing code that runs an `AvFramework`
7
7
  without a database.
8
- - `runAvProtocolConformance(driver)` — drives the protocol corpus (`AvProtocolFixtures`) through one host and reports,
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
- `AvProtocolDriver`: given a `Framework`, a session that sends one protocol request and returns the answer, and closes.
10
+ `AvProtocolDriver`: given an `AvFramework`, 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
 
14
17
  ## Install
15
18
 
16
19
  ```bash
17
- npm install --save-dev @aventara/testing
20
+ npm install --save-dev @aventara/testing@rc
18
21
  ```
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 `AvFramework` itself. To serve it from a host or test bench of your own,
28
+ for example to point a client you are writing at it, 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 `AvFramework`, 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. A client generated against it sends this hash, and
43
+ `GET /_contract` names it.
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. It is generated from
52
+ `AvProtocolFixtures` when the package is built, with every row in the same order and with the same fields, so a client
53
+ or 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
@@ -26,5 +77,6 @@ corpus on Express and Fastify in its own tests. Each package's README ships in i
26
77
 
27
78
  ## License
28
79
 
29
- PolyForm Shield 1.0.0 with an additional permission — free to use, including in commercial applications; you may not
30
- use it to build a product that competes with Aventara itself. See `LICENSE` and `LICENSE-ADDITIONAL-PERMISSION.md`.
80
+ PolyForm Shield 1.0.0 with an additional permission. It is free to use, including in commercial applications, but you
81
+ may not use it to build a product that competes with Aventara itself. See `LICENSE` and
82
+ `LICENSE-ADDITIONAL-PERMISSION.md`.
@@ -1,3 +1,3 @@
1
- import type { Adapter, AdapterModel } from "@aventara/core";
1
+ import type { AvAdapter, AvAdapterModel } from "@aventara/core";
2
2
  /** Builds the Adapter under test; reusable adapter conformance suites take one. */
3
- export type AdapterFactory<M extends AdapterModel = AdapterModel> = () => Adapter<M> | Promise<Adapter<M>>;
3
+ export type AvAdapterFactory<M extends AvAdapterModel = AvAdapterModel> = () => AvAdapter<M> | Promise<AvAdapter<M>>;
@@ -1,42 +1,43 @@
1
- import type { Adapter, AdapterModel, AdapterOperation } from "@aventara/core";
1
+ import type { AvAdapter, AvAdapterModel } from "@aventara/core";
2
+ import type { AvAdapterOperation } from "@aventara/core/internal";
2
3
  /** Answers one operation the fake Adapter is handed. */
3
- export type FakeAdapterExecute<M extends AdapterModel> = (operation: AdapterOperation<M>) => unknown | Promise<unknown>;
4
+ export type AvFakeAdapterExecute<M extends AvAdapterModel> = (operation: AvAdapterOperation<M>) => unknown | Promise<unknown>;
4
5
  /** How a transaction ended: `"committed"` or `"rolled-back"`. */
5
- export type FakeTransactionOutcome = "committed" | "rolled-back";
6
+ export type AvFakeTransactionOutcome = "committed" | "rolled-back";
6
7
  /** One settled transaction boundary: what ran inside it, and how it ended. */
7
- export type FakeTransactionTrace<M extends AdapterModel> = {
8
+ export type AvFakeTransactionTrace<M extends AvAdapterModel> = {
8
9
  /** The operations run inside the transaction, in order. */
9
- readonly operations: readonly AdapterOperation<M>[];
10
+ readonly operations: readonly AvAdapterOperation<M>[];
10
11
  /** How the transaction ended. */
11
- readonly outcome: FakeTransactionOutcome;
12
+ readonly outcome: AvFakeTransactionOutcome;
12
13
  };
13
14
  /**
14
15
  * Rollback for state the test owns: `snapshot` when a transaction begins,
15
16
  * `restore` when its work rejects. Without it, the fake Adapter records
16
17
  * transaction boundaries but keeps whatever the work wrote.
17
18
  */
18
- export type FakeAdapterRollback<S> = {
19
+ export type AvFakeAdapterRollback<S> = {
19
20
  /** Captures the state before a transaction's work runs. */
20
21
  readonly snapshot: () => S;
21
22
  /** Puts the captured state back after the work rejects. */
22
23
  readonly restore: (snapshot: S) => void;
23
24
  };
24
25
  /** Options for {@link createFakeAdapter}. */
25
- export type FakeAdapterOptions<M extends AdapterModel, S = unknown> = {
26
+ export type AvFakeAdapterOptions<M extends AvAdapterModel, S = unknown> = {
26
27
  /** The adapter model the fake Adapter reports. */
27
28
  readonly model: M;
28
29
  /** Answers each operation; without it, every operation resolves `undefined`. */
29
- readonly execute?: FakeAdapterExecute<M>;
30
- /** Makes transactions roll back the test's own state; see {@link FakeAdapterRollback}. */
31
- readonly rollback?: FakeAdapterRollback<S>;
30
+ readonly execute?: AvFakeAdapterExecute<M>;
31
+ /** Makes transactions roll back the test's own state; see {@link AvFakeAdapterRollback}. */
32
+ readonly rollback?: AvFakeAdapterRollback<S>;
32
33
  };
33
34
  /**
34
35
  * The Adapter {@link createFakeAdapter} returns, with a record of every
35
36
  * operation and transaction it ran.
36
37
  */
37
- export type FakeAdapter<M extends AdapterModel = AdapterModel> = Adapter<M> & {
38
- readonly operations: readonly AdapterOperation<M>[];
39
- readonly transactions: readonly FakeTransactionTrace<M>[];
38
+ export type AvFakeAdapter<M extends AvAdapterModel = AvAdapterModel> = AvAdapter<M> & {
39
+ readonly operations: readonly AvAdapterOperation<M>[];
40
+ readonly transactions: readonly AvFakeTransactionTrace<M>[];
40
41
  };
41
42
  /** A provider-free, in-memory Adapter for framework and conformance tests. */
42
- export declare function createFakeAdapter<M extends AdapterModel, S = unknown>(options: FakeAdapterOptions<M, S>): FakeAdapter<M>;
43
+ export declare function createFakeAdapter<M extends AvAdapterModel, S = unknown>(options: AvFakeAdapterOptions<M, S>): AvFakeAdapter<M>;
package/dist/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
- export type { AdapterFactory } from "./adapters/adapter-factory.js";
2
- export type { FakeAdapter, FakeAdapterExecute, FakeAdapterOptions, FakeAdapterRollback, FakeTransactionOutcome, FakeTransactionTrace, } from "./adapters/fake-adapter.js";
1
+ export type { AvAdapterFactory } from "./adapters/adapter-factory.js";
2
+ export type { AvFakeAdapter, AvFakeAdapterExecute, AvFakeAdapterOptions, AvFakeAdapterRollback, AvFakeTransactionOutcome, AvFakeTransactionTrace, } 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";
@@ -1,5 +1,5 @@
1
- import type { Framework, PipelineContext } from "@aventara/core";
2
- import { Decimal } from "@aventara/core";
1
+ import type { AvContext, AvFramework } from "@aventara/core";
2
+ import { AvDecimal } from "@aventara/core";
3
3
  /**
4
4
  * The corpus's adapter model:
5
5
  *
@@ -7,7 +7,7 @@ import { Decimal } from "@aventara/core";
7
7
  * client scope;
8
8
  * - `books`: references `authors`, and deleting an author cascades into it
9
9
  * ({@link CORPUS_REFERENTIAL_ACTIONS}; a plan's `V1019`); the client scope
10
- * adds a computed `label` to it ({@link CORPUS_CLIENT_CONFIG});
10
+ * defines a computed `label` the client alone sees ({@link CORPUS_RESOURCES});
11
11
  * - `secrets`: its one operation restricted away at client scope;
12
12
  * - `hiddenRes`: hidden at client scope;
13
13
  * - `samples`: one field of each scalar whose wire form is not its runtime
@@ -275,6 +275,7 @@ export declare const CORPUS_REFERENTIAL_ACTIONS: {
275
275
  readonly books: readonly [{
276
276
  readonly field: "author";
277
277
  readonly target: "authors";
278
+ readonly references: readonly ["authorId"];
278
279
  readonly onDelete: "cascade";
279
280
  }];
280
281
  };
@@ -282,9 +283,9 @@ export declare const CORPUS_REFERENTIAL_ACTIONS: {
282
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
- * `FrameworkError("A4000")`, denies (`false`), throws
286
- * `FrameworkError("A4002")`, or throws a plain `Error`;
287
- * - `A2008`, `A2013`, `A2014` — the Adapter throws `FrameworkError` with that
286
+ * `AvError("A4000")`, denies (`false`), throws
287
+ * `AvError("A4002")`, or throws a plain `Error`;
288
+ * - `A2008`, `A2013`, `A2014` — the Adapter throws `AvError` with that
288
289
  * conflict code; `A3001` — it throws a plain `Error`;
289
290
  * - `A3002` — a plan step carrying it runs, and the Adapter's transaction then
290
291
  * fails to commit;
@@ -306,65 +307,78 @@ export declare const CORPUS_TRIGGER: {
306
307
  readonly pipeOutputInvalid: "A3004";
307
308
  };
308
309
  /** The corpus's one client guard: it decides nothing unless a row names a trigger. */
309
- declare function corpusGuard(context: PipelineContext): boolean;
310
+ declare function corpusGuard(context: AvContext): boolean;
310
311
  /**
311
312
  * The corpus's one client pipe: it rewrites nothing unless a row names the
312
313
  * `A3004` trigger, and then adds a filter on a field the server does not have.
313
314
  */
314
315
  declare function corpusPipe(args: unknown): unknown;
315
316
  /**
316
- * 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
- * computed on read from `id`.
317
+ * The corpus's root `resources` tree: `books.label`, a VIRTUAL field computed on
318
+ * read from `id`.
319
319
  */
320
- export declare const CORPUS_CLIENT_CONFIG: {
321
- readonly fields: {
322
- readonly books: {
320
+ export declare const CORPUS_RESOURCES: {
321
+ readonly books: {
322
+ readonly fields: {
323
323
  readonly label: {
324
- readonly type: {
325
- readonly scalar: "string";
324
+ readonly definition: {
325
+ readonly type: {
326
+ readonly scalar: "string";
327
+ };
328
+ readonly nullable: false;
329
+ readonly list: false;
330
+ readonly lifecycle: readonly ["VIRTUAL"];
331
+ };
332
+ readonly pipelines: {
333
+ readonly compute: {
334
+ readonly read: {
335
+ readonly dependsOn: readonly ["id"];
336
+ readonly compute: ({ record, }: {
337
+ readonly record: Readonly<Record<string, unknown>>;
338
+ }) => string;
339
+ };
340
+ };
326
341
  };
327
- readonly nullable: false;
328
- readonly list: false;
329
- readonly lifecycle: readonly ["VIRTUAL"];
330
342
  };
331
343
  };
332
344
  };
333
- readonly behaviors: {
334
- readonly books: {
335
- readonly fields: {
336
- readonly label: {
337
- readonly read: {
338
- readonly dependsOn: readonly ["id"];
339
- readonly compute: ({ record, }: {
340
- readonly record: Readonly<Record<string, unknown>>;
341
- }) => string;
342
- };
345
+ };
346
+ /** The corpus's application layer: `books.label` is the client's only. */
347
+ export declare const CORPUS_APPLICATION_CONFIG: {
348
+ readonly exposed: true;
349
+ readonly exemptions: {
350
+ readonly resources: {
351
+ readonly books: {
352
+ readonly fields: {
353
+ readonly label: true;
343
354
  };
344
355
  };
345
356
  };
346
357
  };
358
+ };
359
+ /**
360
+ * The corpus's client layer: what the ClientContract hides or restricts, and
361
+ * the client's own guard and pipe.
362
+ */
363
+ export declare const CORPUS_CLIENT_CONFIG: {
347
364
  readonly pipelines: {
348
365
  readonly guards: readonly [typeof corpusGuard];
349
366
  readonly pipes: readonly [typeof corpusPipe];
350
367
  };
351
- readonly restrictions: {
352
- readonly authors: {
353
- readonly fields: {
354
- readonly secret: {
355
- readonly hidden: true;
368
+ readonly exposed: true;
369
+ readonly exemptions: {
370
+ readonly resources: {
371
+ readonly authors: {
372
+ readonly fields: {
373
+ readonly secret: true;
356
374
  };
357
375
  };
358
- };
359
- readonly secrets: {
360
- readonly operations: {
361
- readonly find: {
362
- readonly many: false;
376
+ readonly secrets: {
377
+ readonly operations: {
378
+ readonly find: readonly ["many"];
363
379
  };
364
380
  };
365
- };
366
- readonly hiddenRes: {
367
- readonly hidden: true;
381
+ readonly hiddenRes: true;
368
382
  };
369
383
  };
370
384
  };
@@ -374,20 +388,20 @@ export declare const CORPUS_ENTRYPOINT = "/api";
374
388
  * The corpus ClientContract's hash: what a generated client for it sends as
375
389
  * `Aventara-Contract-Hash`. A literal, as a generated client holds it.
376
390
  */
377
- export declare const CORPUS_CLIENT_CONTRACT_HASH = "sha256:07b1a75a6e8e88e30115ae81bcf7e3a12fa493bddce995a01867e2381bd158d0";
391
+ export declare const CORPUS_CLIENT_CONTRACT_HASH = "sha256:502530d5b918266c61f45e70035acd7d9637bbf7305c5572106185d5e63ec307";
378
392
  /**
379
393
  * The corpus ClientContract as `GET /_contract` serves it: its JCS-canonical
380
394
  * bytes, `protocol.hash` included. A literal, as a host must serve it byte for
381
395
  * byte.
382
396
  */
383
- 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\"}";
397
+ export declare const CORPUS_CLIENT_CONTRACT_DOCUMENT = "{\"enums\":{},\"limits\":{\"maxBooleanNodes\":50,\"maxJsonDepth\":64,\"maxListLimit\":250,\"maxNestingDepth\":12,\"maxRequestBytes\":1048576,\"maxTransactionOperations\":20},\"protocol\":{\"hash\":\"sha256:502530d5b918266c61f45e70035acd7d9637bbf7305c5572106185d5e63ec307\",\"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\"}";
384
398
  /**
385
399
  * The hash of the corpus's second ClientContract: the same model and
386
400
  * configuration with `transactions: "none"` (the `/_transactions` absence
387
401
  * rows). A row targets it by sending this hash, exactly as a client generated
388
402
  * against it would.
389
403
  */
390
- export declare const CORPUS_NONE_CLIENT_CONTRACT_HASH = "sha256:d333e8396b9bf9127e0859ed69b9874f230058a597e2129e6b9e9b380daef618";
404
+ export declare const CORPUS_NONE_CLIENT_CONTRACT_HASH = "sha256:5e99809294a2aae922b635e9a44b464b1b6e03359a54d30420292c62676a7ce5";
391
405
  /** The one record the corpus Adapter knows. */
392
406
  export declare const CORPUS_AUTHOR: {
393
407
  readonly id: "a1";
@@ -403,7 +417,7 @@ export declare const CORPUS_SAMPLE: {
403
417
  readonly id: "s1";
404
418
  readonly count: 9007199254740993n;
405
419
  readonly at: Date;
406
- readonly amount: Decimal;
420
+ readonly amount: AvDecimal;
407
421
  readonly blob: Uint8Array<ArrayBuffer>;
408
422
  };
409
423
  /**
@@ -414,7 +428,7 @@ export declare const CORPUS_SAMPLE: {
414
428
  * otherwise (a stale or absent hash included).
415
429
  */
416
430
  export declare function createCorpusFramework(contractHash?: string): Promise<{
417
- readonly framework: Framework;
431
+ readonly framework: AvFramework;
418
432
  readonly adapterCalls: () => number;
419
433
  }>;
420
434
  export {};
@@ -1,4 +1,4 @@
1
- import { createFramework, Decimal, FrameworkError } from "@aventara/core";
1
+ import { AvDecimal, AvError, createFramework } from "@aventara/core";
2
2
  import { createFakeAdapter } from "../adapters/fake-adapter.js";
3
3
  const identityString = {
4
4
  kind: "scalar",
@@ -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",
@@ -106,13 +113,13 @@ function whereIdOf(args) {
106
113
  return typeof id === "string" ? id : undefined;
107
114
  }
108
115
  function corpusGuard(context) {
109
- switch (whereIdOf(context.args)) {
116
+ switch (whereIdOf(context.$args)) {
110
117
  case CORPUS_TRIGGER.unauthenticated:
111
- throw new FrameworkError("A4000", "Authentication is required.");
118
+ throw new AvError("A4000", "Authentication is required.");
112
119
  case CORPUS_TRIGGER.guardDenies:
113
120
  return false;
114
121
  case CORPUS_TRIGGER.policyDenies:
115
- throw new FrameworkError("A4002", "Denied by the corpus policy.");
122
+ throw new AvError("A4002", "Denied by the corpus policy.");
116
123
  case CORPUS_TRIGGER.guardThrows:
117
124
  throw new Error("the corpus guard failed");
118
125
  default:
@@ -126,47 +133,54 @@ function corpusPipe(args) {
126
133
  const record = args;
127
134
  return { ...record, where: { ...record.where, nope: "x" } };
128
135
  }
129
- export const CORPUS_CLIENT_CONFIG = {
130
- fields: {
131
- books: {
136
+ export const CORPUS_RESOURCES = {
137
+ books: {
138
+ fields: {
132
139
  label: {
133
- type: { scalar: "string" },
134
- nullable: false,
135
- list: false,
136
- lifecycle: ["VIRTUAL"],
137
- },
138
- },
139
- },
140
- behaviors: {
141
- books: {
142
- fields: {
143
- label: {
144
- read: {
145
- dependsOn: ["id"],
146
- compute: ({ record, }) => `book:${String(record.id)}`,
140
+ definition: {
141
+ type: { scalar: "string" },
142
+ nullable: false,
143
+ list: false,
144
+ lifecycle: ["VIRTUAL"],
145
+ },
146
+ pipelines: {
147
+ compute: {
148
+ read: {
149
+ dependsOn: ["id"],
150
+ compute: ({ record, }) => `book:${String(record.id)}`,
151
+ },
147
152
  },
148
153
  },
149
154
  },
150
155
  },
151
156
  },
157
+ };
158
+ export const CORPUS_APPLICATION_CONFIG = {
159
+ exposed: true,
160
+ exemptions: { resources: { books: { fields: { label: true } } } },
161
+ };
162
+ export const CORPUS_CLIENT_CONFIG = {
152
163
  pipelines: { guards: [corpusGuard], pipes: [corpusPipe] },
153
- restrictions: {
154
- authors: { fields: { secret: { hidden: true } } },
155
- secrets: { operations: { find: { many: false } } },
156
- hiddenRes: { hidden: true },
164
+ exposed: true,
165
+ exemptions: {
166
+ resources: {
167
+ authors: { fields: { secret: true } },
168
+ secrets: { operations: { find: ["many"] } },
169
+ hiddenRes: true,
170
+ },
157
171
  },
158
172
  };
159
173
  export const CORPUS_ENTRYPOINT = "/api";
160
- export const CORPUS_CLIENT_CONTRACT_HASH = "sha256:07b1a75a6e8e88e30115ae81bcf7e3a12fa493bddce995a01867e2381bd158d0";
161
- export 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"}';
162
- export const CORPUS_NONE_CLIENT_CONTRACT_HASH = "sha256:d333e8396b9bf9127e0859ed69b9874f230058a597e2129e6b9e9b380daef618";
174
+ export const CORPUS_CLIENT_CONTRACT_HASH = "sha256:502530d5b918266c61f45e70035acd7d9637bbf7305c5572106185d5e63ec307";
175
+ export const CORPUS_CLIENT_CONTRACT_DOCUMENT = '{"enums":{},"limits":{"maxBooleanNodes":50,"maxJsonDepth":64,"maxListLimit":250,"maxNestingDepth":12,"maxRequestBytes":1048576,"maxTransactionOperations":20},"protocol":{"hash":"sha256:502530d5b918266c61f45e70035acd7d9637bbf7305c5572106185d5e63ec307","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"}';
176
+ export const CORPUS_NONE_CLIENT_CONTRACT_HASH = "sha256:5e99809294a2aae922b635e9a44b464b1b6e03359a54d30420292c62676a7ce5";
163
177
  export const CORPUS_AUTHOR = { id: "a1", name: "Ada" };
164
178
  export const CORPUS_NOBODY = "nobody";
165
179
  export const CORPUS_SAMPLE = {
166
180
  id: "s1",
167
181
  count: 9007199254740993n,
168
182
  at: new Date("2026-10-05T12:34:56.789Z"),
169
- amount: new Decimal("12.50"),
183
+ amount: new AvDecimal("12.50"),
170
184
  blob: new Uint8Array([255, 0, 16]),
171
185
  };
172
186
  const CONFLICTS = {
@@ -183,7 +197,7 @@ function corpusAnswer(operation) {
183
197
  const id = whereIdOf(operation.arguments);
184
198
  const conflict = id === undefined ? undefined : CONFLICTS[id];
185
199
  if (conflict !== undefined) {
186
- throw new FrameworkError(conflict, `The corpus Adapter answered ${conflict}.`);
200
+ throw new AvError(conflict, `The corpus Adapter answered ${conflict}.`);
187
201
  }
188
202
  if (id === CORPUS_TRIGGER.adapterThrows) {
189
203
  throw new Error("the corpus Adapter failed");
@@ -238,8 +252,10 @@ export async function createCorpusFramework(contractHash) {
238
252
  : { transaction: failingCommit(fake.transaction) }),
239
253
  });
240
254
  const framework = await createFramework({
241
- entrypoint: CORPUS_ENTRYPOINT,
255
+ api: CORPUS_ENTRYPOINT,
242
256
  adapter,
257
+ resources: CORPUS_RESOURCES,
258
+ application: CORPUS_APPLICATION_CONFIG,
243
259
  client: CORPUS_CLIENT_CONFIG,
244
260
  });
245
261
  return { framework, adapterCalls: () => adapter.operations.length };
@@ -1,6 +1,6 @@
1
1
  import type { AvProtocolDriver } from "./protocol-driver.js";
2
2
  /**
3
- * `runAvProtocolConformance(driver)` — drives the protocol fixture corpus
3
+ * `runAvProtocolConformance(driver)` drives the protocol fixture corpus
4
4
  * through one host.
5
5
  *
6
6
  * Every row a host answers is driven; rows whose `client.outcome` is
@@ -12,17 +12,17 @@ import type { AvProtocolDriver } from "./protocol-driver.js";
12
12
  * held to the row's `server` block:
13
13
  *
14
14
  * - `status`, exactly;
15
- * - `code`: the body is a framework envelope carrying that code — or, for
15
+ * - `code`: the body is a framework envelope carrying that code or, for
16
16
  * `null`, is none;
17
17
  * - `causeKeys`: the exact sorted key set of `cause`;
18
18
  * - `issues`: each issue's `code` and `path`, in order;
19
19
  * - `adapterCalls`: the operations the corpus Adapter was handed, exactly;
20
20
  * - `responseHeaders`: each named header present (names matched without
21
- * regard to case) with exactly that value — a host may add its own;
21
+ * regard to case) with exactly that value; a host may add its own;
22
22
  * - `bodyBytes`: the body text, exactly.
23
23
  *
24
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
25
+ * mismatches found (none when the host conforms). A driver that throws is a
26
26
  * mismatch of its row, not a rejection of the run. Works with any test
27
27
  * runner: assert on the results with whatever runner you use.
28
28
  */
@@ -0,0 +1,30 @@
1
+ import type { AvFramework } 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<AvFramework>;
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,4 +1,4 @@
1
- import type { Framework } from "@aventara/core";
1
+ import type { AvFramework } from "@aventara/core";
2
2
  import type { AvProtocolRequest, AvProtocolResponse } from "@aventara/core/protocol";
3
3
  /**
4
4
  * One host under test, as `runAvProtocolConformance` drives it: given the
@@ -7,7 +7,7 @@ import type { AvProtocolRequest, AvProtocolResponse } from "@aventara/core/proto
7
7
  * and body text, as an `AvProtocolResponse`). The run calls `close` once per
8
8
  * row, after its one request.
9
9
  */
10
- export type AvProtocolDriver = (framework: Framework) => Promise<{
10
+ export type AvProtocolDriver = (framework: AvFramework) => Promise<{
11
11
  readonly send: (request: AvProtocolRequest) => Promise<AvProtocolResponse>;
12
12
  readonly close: () => Promise<void>;
13
13
  }>;