@aventara/testing 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.
@@ -1,43 +1,43 @@
1
- import type { Adapter, AdapterModel } from "@aventara/core";
2
- import type { AdapterOperation } from "@aventara/core/internal";
1
+ import type { AvAdapter, AvAdapterModel } from "@aventara/core";
2
+ import type { AvAdapterOperation } from "@aventara/core/internal";
3
3
  /** Answers one operation the fake Adapter is handed. */
4
- 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>;
5
5
  /** How a transaction ended: `"committed"` or `"rolled-back"`. */
6
- export type FakeTransactionOutcome = "committed" | "rolled-back";
6
+ export type AvFakeTransactionOutcome = "committed" | "rolled-back";
7
7
  /** One settled transaction boundary: what ran inside it, and how it ended. */
8
- export type FakeTransactionTrace<M extends AdapterModel> = {
8
+ export type AvFakeTransactionTrace<M extends AvAdapterModel> = {
9
9
  /** The operations run inside the transaction, in order. */
10
- readonly operations: readonly AdapterOperation<M>[];
10
+ readonly operations: readonly AvAdapterOperation<M>[];
11
11
  /** How the transaction ended. */
12
- readonly outcome: FakeTransactionOutcome;
12
+ readonly outcome: AvFakeTransactionOutcome;
13
13
  };
14
14
  /**
15
15
  * Rollback for state the test owns: `snapshot` when a transaction begins,
16
16
  * `restore` when its work rejects. Without it, the fake Adapter records
17
17
  * transaction boundaries but keeps whatever the work wrote.
18
18
  */
19
- export type FakeAdapterRollback<S> = {
19
+ export type AvFakeAdapterRollback<S> = {
20
20
  /** Captures the state before a transaction's work runs. */
21
21
  readonly snapshot: () => S;
22
22
  /** Puts the captured state back after the work rejects. */
23
23
  readonly restore: (snapshot: S) => void;
24
24
  };
25
25
  /** Options for {@link createFakeAdapter}. */
26
- export type FakeAdapterOptions<M extends AdapterModel, S = unknown> = {
26
+ export type AvFakeAdapterOptions<M extends AvAdapterModel, S = unknown> = {
27
27
  /** The adapter model the fake Adapter reports. */
28
28
  readonly model: M;
29
29
  /** Answers each operation; without it, every operation resolves `undefined`. */
30
- readonly execute?: FakeAdapterExecute<M>;
31
- /** Makes transactions roll back the test's own state; see {@link FakeAdapterRollback}. */
32
- 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>;
33
33
  };
34
34
  /**
35
35
  * The Adapter {@link createFakeAdapter} returns, with a record of every
36
36
  * operation and transaction it ran.
37
37
  */
38
- export type FakeAdapter<M extends AdapterModel = AdapterModel> = Adapter<M> & {
39
- readonly operations: readonly AdapterOperation<M>[];
40
- 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>[];
41
41
  };
42
42
  /** A provider-free, in-memory Adapter for framework and conformance tests. */
43
- 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,5 +1,5 @@
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
5
  export { AvProtocolCorpus } from "./protocol/protocol-corpus.js";
@@ -1,5 +1,6 @@
1
- import type { Framework, PipelineContext } from "@aventara/core";
2
- import { Decimal } from "@aventara/core";
1
+ import type { AvAdapterModel, AvContext, AvFramework, AvPrebuiltContracts } from "@aventara/core";
2
+ import { AvDecimal } from "@aventara/core";
3
+ import type { AvAdapterOperation } from "@aventara/core/internal";
3
4
  /**
4
5
  * The corpus's adapter model:
5
6
  *
@@ -7,7 +8,7 @@ import { Decimal } from "@aventara/core";
7
8
  * client scope;
8
9
  * - `books`: references `authors`, and deleting an author cascades into it
9
10
  * ({@link CORPUS_REFERENTIAL_ACTIONS}; a plan's `V1019`); the client scope
10
- * adds a computed `label` to it ({@link CORPUS_CLIENT_CONFIG});
11
+ * defines a computed `label` the client alone sees ({@link CORPUS_RESOURCES});
11
12
  * - `secrets`: its one operation restricted away at client scope;
12
13
  * - `hiddenRes`: hidden at client scope;
13
14
  * - `samples`: one field of each scalar whose wire form is not its runtime
@@ -283,9 +284,9 @@ export declare const CORPUS_REFERENTIAL_ACTIONS: {
283
284
  * The `where.id` a row sends to make the corpus Framework fail one way:
284
285
  *
285
286
  * - `A4000`, `A4001`, `A4002`, `A3000` — the client guard throws
286
- * `FrameworkError("A4000")`, denies (`false`), throws
287
- * `FrameworkError("A4002")`, or throws a plain `Error`;
288
- * - `A2008`, `A2013`, `A2014` — the Adapter throws `FrameworkError` with that
287
+ * `AvError("A4000")`, denies (`false`), throws
288
+ * `AvError("A4002")`, or throws a plain `Error`;
289
+ * - `A2008`, `A2013`, `A2014` — the Adapter throws `AvError` with that
289
290
  * conflict code; `A3001` — it throws a plain `Error`;
290
291
  * - `A3002` — a plan step carrying it runs, and the Adapter's transaction then
291
292
  * fails to commit;
@@ -307,65 +308,78 @@ export declare const CORPUS_TRIGGER: {
307
308
  readonly pipeOutputInvalid: "A3004";
308
309
  };
309
310
  /** The corpus's one client guard: it decides nothing unless a row names a trigger. */
310
- declare function corpusGuard(context: PipelineContext): boolean;
311
+ declare function corpusGuard(context: AvContext): boolean;
311
312
  /**
312
313
  * The corpus's one client pipe: it rewrites nothing unless a row names the
313
314
  * `A3004` trigger, and then adds a filter on a field the server does not have.
314
315
  */
315
316
  declare function corpusPipe(args: unknown): unknown;
316
317
  /**
317
- * The corpus's client-scope configuration: what the ClientContract hides or
318
- * restricts, and `books.label`, a VIRTUAL field added at client scope and
319
- * computed on read from `id`.
318
+ * The corpus's root `resources` tree: `books.label`, a VIRTUAL field computed on
319
+ * read from `id`.
320
320
  */
321
- export declare const CORPUS_CLIENT_CONFIG: {
322
- readonly fields: {
323
- readonly books: {
321
+ export declare const CORPUS_RESOURCES: {
322
+ readonly books: {
323
+ readonly fields: {
324
324
  readonly label: {
325
- readonly type: {
326
- readonly scalar: "string";
325
+ readonly definition: {
326
+ readonly type: {
327
+ readonly scalar: "string";
328
+ };
329
+ readonly nullable: false;
330
+ readonly list: false;
331
+ readonly lifecycle: readonly ["VIRTUAL"];
332
+ };
333
+ readonly pipelines: {
334
+ readonly compute: {
335
+ readonly read: {
336
+ readonly dependsOn: readonly ["id"];
337
+ readonly compute: ({ record, }: {
338
+ readonly record: Readonly<Record<string, unknown>>;
339
+ }) => string;
340
+ };
341
+ };
327
342
  };
328
- readonly nullable: false;
329
- readonly list: false;
330
- readonly lifecycle: readonly ["VIRTUAL"];
331
343
  };
332
344
  };
333
345
  };
334
- readonly behaviors: {
335
- readonly books: {
336
- readonly fields: {
337
- readonly label: {
338
- readonly read: {
339
- readonly dependsOn: readonly ["id"];
340
- readonly compute: ({ record, }: {
341
- readonly record: Readonly<Record<string, unknown>>;
342
- }) => string;
343
- };
346
+ };
347
+ /** The corpus's application layer: `books.label` is the client's only. */
348
+ export declare const CORPUS_APPLICATION_CONFIG: {
349
+ readonly exposed: true;
350
+ readonly exemptions: {
351
+ readonly resources: {
352
+ readonly books: {
353
+ readonly fields: {
354
+ readonly label: true;
344
355
  };
345
356
  };
346
357
  };
347
358
  };
359
+ };
360
+ /**
361
+ * The corpus's client layer: what the ClientContract hides or restricts, and
362
+ * the client's own guard and pipe.
363
+ */
364
+ export declare const CORPUS_CLIENT_CONFIG: {
348
365
  readonly pipelines: {
349
366
  readonly guards: readonly [typeof corpusGuard];
350
367
  readonly pipes: readonly [typeof corpusPipe];
351
368
  };
352
- readonly restrictions: {
353
- readonly authors: {
354
- readonly fields: {
355
- readonly secret: {
356
- readonly hidden: true;
369
+ readonly exposed: true;
370
+ readonly exemptions: {
371
+ readonly resources: {
372
+ readonly authors: {
373
+ readonly fields: {
374
+ readonly secret: true;
357
375
  };
358
376
  };
359
- };
360
- readonly secrets: {
361
- readonly operations: {
362
- readonly find: {
363
- readonly many: false;
377
+ readonly secrets: {
378
+ readonly operations: {
379
+ readonly find: readonly ["many"];
364
380
  };
365
381
  };
366
- };
367
- readonly hiddenRes: {
368
- readonly hidden: true;
382
+ readonly hiddenRes: true;
369
383
  };
370
384
  };
371
385
  };
@@ -375,20 +389,26 @@ export declare const CORPUS_ENTRYPOINT = "/api";
375
389
  * The corpus ClientContract's hash: what a generated client for it sends as
376
390
  * `Aventara-Contract-Hash`. A literal, as a generated client holds it.
377
391
  */
378
- export declare const CORPUS_CLIENT_CONTRACT_HASH = "sha256:07b1a75a6e8e88e30115ae81bcf7e3a12fa493bddce995a01867e2381bd158d0";
392
+ export declare const CORPUS_CLIENT_CONTRACT_HASH = "sha256:502530d5b918266c61f45e70035acd7d9637bbf7305c5572106185d5e63ec307";
379
393
  /**
380
394
  * The corpus ClientContract as `GET /_contract` serves it: its JCS-canonical
381
395
  * bytes, `protocol.hash` included. A literal, as a host must serve it byte for
382
396
  * byte.
383
397
  */
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\"}";
398
+ 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\"}";
385
399
  /**
386
400
  * The hash of the corpus's second ClientContract: the same model and
387
401
  * configuration with `transactions: "none"` (the `/_transactions` absence
388
402
  * rows). A row targets it by sending this hash, exactly as a client generated
389
403
  * against it would.
390
404
  */
391
- export declare const CORPUS_NONE_CLIENT_CONTRACT_HASH = "sha256:d333e8396b9bf9127e0859ed69b9874f230058a597e2129e6b9e9b380daef618";
405
+ export declare const CORPUS_NONE_CLIENT_CONTRACT_HASH = "sha256:5e99809294a2aae922b635e9a44b464b1b6e03359a54d30420292c62676a7ce5";
406
+ /**
407
+ * The hash of the corpus's third ClientContract: the same model and
408
+ * configuration with the framework's `access` option ({@link CORPUS_ACCESS}),
409
+ * so it carries the `access` section and its `POST /_access/can` route.
410
+ */
411
+ export declare const CORPUS_ACCESS_CLIENT_CONTRACT_HASH = "sha256:e45cdbc0c5f27e94058a1e0a1e4b9e532522ebeddcbb68170ac6ec6b45ca0d23";
392
412
  /** The one record the corpus Adapter knows. */
393
413
  export declare const CORPUS_AUTHOR: {
394
414
  readonly id: "a1";
@@ -404,18 +424,173 @@ export declare const CORPUS_SAMPLE: {
404
424
  readonly id: "s1";
405
425
  readonly count: 9007199254740993n;
406
426
  readonly at: Date;
407
- readonly amount: Decimal;
427
+ readonly amount: AvDecimal;
408
428
  readonly blob: Uint8Array<ArrayBuffer>;
409
429
  };
410
430
  /**
411
- * A fresh corpus Framework, and the count of operations its Adapter has been
412
- * handed so far — a row's `adapterCalls` is read from it. `contractHash`
413
- * selects which corpus ClientContract it serves: the `transactions: "none"`
414
- * one when it is {@link CORPUS_NONE_CLIENT_CONTRACT_HASH}, the main one
431
+ * The corpus's access provider, behind {@link CORPUS_ACCESS_CLIENT_CONTRACT_HASH}:
432
+ * every request is anonymous (`me: null`, the same through every host), and its
433
+ * policy answers by the question alone, never by a row. Every operation is
434
+ * allowed, every row is in scope, every field readable, except: no `authors`
435
+ * row is in the delete scope; a question about `books` is refused by an
436
+ * `AvError` the engine throws; and any question about `samples` is the engine's
437
+ * own defect.
438
+ */
439
+ export declare const CORPUS_ACCESS: {
440
+ state: () => {
441
+ me: null;
442
+ };
443
+ policy: {
444
+ can: ({ resource }: {
445
+ readonly principal: unknown;
446
+ readonly origin: import("@aventara/core").AvRequestOrigin;
447
+ readonly resource: string;
448
+ } & {
449
+ readonly action: "read" | "create" | "update" | "delete";
450
+ readonly row?: Readonly<Record<string, unknown>>;
451
+ readonly before?: Readonly<Record<string, unknown>>;
452
+ readonly beforeGrants?: readonly string[];
453
+ readonly exists: (resource: string, where: import("@aventara/core").AvPolicyWhere) => Promise<boolean>;
454
+ }) => true;
455
+ scopeOf: ({ resource, action }: {
456
+ readonly principal: unknown;
457
+ readonly origin: import("@aventara/core").AvRequestOrigin;
458
+ readonly resource: string;
459
+ } & {
460
+ readonly action: "read" | "update" | "delete";
461
+ }) => boolean;
462
+ fieldsOf: () => {
463
+ always: "all";
464
+ perRow: {};
465
+ };
466
+ };
467
+ };
468
+ /**
469
+ * The corpus Framework's configuration, over a fresh corpus Adapter, and the
470
+ * count of operations that Adapter has been handed so far. `contractHash`
471
+ * selects which corpus ClientContract it compiles to: the `transactions:
472
+ * "none"` one when it is {@link CORPUS_NONE_CLIENT_CONTRACT_HASH}, the one with
473
+ * `access` when it is {@link CORPUS_ACCESS_CLIENT_CONTRACT_HASH}, the main one
415
474
  * otherwise (a stale or absent hash included).
416
475
  */
417
- export declare function createCorpusFramework(contractHash?: string): Promise<{
418
- readonly framework: Framework;
476
+ export declare function corpusConfiguration(contractHash?: string): {
477
+ config: {
478
+ access?: {
479
+ state: () => {
480
+ me: null;
481
+ };
482
+ policy: {
483
+ can: ({ resource }: {
484
+ readonly principal: unknown;
485
+ readonly origin: import("@aventara/core").AvRequestOrigin;
486
+ readonly resource: string;
487
+ } & {
488
+ readonly action: "read" | "create" | "update" | "delete";
489
+ readonly row?: Readonly<Record<string, unknown>>;
490
+ readonly before?: Readonly<Record<string, unknown>>;
491
+ readonly beforeGrants?: readonly string[];
492
+ readonly exists: (resource: string, where: import("@aventara/core").AvPolicyWhere) => Promise<boolean>;
493
+ }) => true;
494
+ scopeOf: ({ resource, action }: {
495
+ readonly principal: unknown;
496
+ readonly origin: import("@aventara/core").AvRequestOrigin;
497
+ readonly resource: string;
498
+ } & {
499
+ readonly action: "read" | "update" | "delete";
500
+ }) => boolean;
501
+ fieldsOf: () => {
502
+ always: "all";
503
+ perRow: {};
504
+ };
505
+ };
506
+ };
507
+ api: string;
508
+ adapter: import("@aventara/core").AvAdapter<AvAdapterModel> & {
509
+ readonly operations: readonly AvAdapterOperation<AvAdapterModel>[];
510
+ readonly transactions: readonly import("../adapters/fake-adapter.js").AvFakeTransactionTrace<AvAdapterModel>[];
511
+ } & {
512
+ transaction?: <T>(work: (executor: import("@aventara/core/internal").AvAdapterTransactionExecutor<AvAdapterModel>) => Promise<T>) => Promise<T>;
513
+ referentialActions: {
514
+ readonly books: readonly [{
515
+ readonly field: "author";
516
+ readonly target: "authors";
517
+ readonly references: readonly ["authorId"];
518
+ readonly onDelete: "cascade";
519
+ }];
520
+ };
521
+ };
522
+ resources: {
523
+ readonly books: {
524
+ readonly fields: {
525
+ readonly label: {
526
+ readonly definition: {
527
+ readonly type: {
528
+ readonly scalar: "string";
529
+ };
530
+ readonly nullable: false;
531
+ readonly list: false;
532
+ readonly lifecycle: readonly ["VIRTUAL"];
533
+ };
534
+ readonly pipelines: {
535
+ readonly compute: {
536
+ readonly read: {
537
+ readonly dependsOn: readonly ["id"];
538
+ readonly compute: ({ record, }: {
539
+ readonly record: Readonly<Record<string, unknown>>;
540
+ }) => string;
541
+ };
542
+ };
543
+ };
544
+ };
545
+ };
546
+ };
547
+ };
548
+ application: {
549
+ readonly exposed: true;
550
+ readonly exemptions: {
551
+ readonly resources: {
552
+ readonly books: {
553
+ readonly fields: {
554
+ readonly label: true;
555
+ };
556
+ };
557
+ };
558
+ };
559
+ };
560
+ client: {
561
+ readonly pipelines: {
562
+ readonly guards: readonly [typeof corpusGuard];
563
+ readonly pipes: readonly [typeof corpusPipe];
564
+ };
565
+ readonly exposed: true;
566
+ readonly exemptions: {
567
+ readonly resources: {
568
+ readonly authors: {
569
+ readonly fields: {
570
+ readonly secret: true;
571
+ };
572
+ };
573
+ readonly secrets: {
574
+ readonly operations: {
575
+ readonly find: readonly ["many"];
576
+ };
577
+ };
578
+ readonly hiddenRes: true;
579
+ };
580
+ };
581
+ };
582
+ };
583
+ adapterCalls: () => number;
584
+ };
585
+ /**
586
+ * A fresh corpus Framework, and the count of operations its Adapter has been
587
+ * handed so far — a row's `adapterCalls` is read from it. `contractHash` selects
588
+ * which corpus ClientContract it serves ({@link corpusConfiguration}). With
589
+ * `prebuilt`, its Contracts are loaded from that file when it matches, as a
590
+ * serverless deployment's would be.
591
+ */
592
+ export declare function createCorpusFramework(contractHash?: string, prebuilt?: AvPrebuiltContracts): Promise<{
593
+ readonly framework: AvFramework;
419
594
  readonly adapterCalls: () => number;
420
595
  }>;
421
596
  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",
@@ -113,13 +113,13 @@ function whereIdOf(args) {
113
113
  return typeof id === "string" ? id : undefined;
114
114
  }
115
115
  function corpusGuard(context) {
116
- switch (whereIdOf(context.args)) {
116
+ switch (whereIdOf(context.$args)) {
117
117
  case CORPUS_TRIGGER.unauthenticated:
118
- throw new FrameworkError("A4000", "Authentication is required.");
118
+ throw new AvError("A4000", "Authentication is required.");
119
119
  case CORPUS_TRIGGER.guardDenies:
120
120
  return false;
121
121
  case CORPUS_TRIGGER.policyDenies:
122
- throw new FrameworkError("A4002", "Denied by the corpus policy.");
122
+ throw new AvError("A4002", "Denied by the corpus policy.");
123
123
  case CORPUS_TRIGGER.guardThrows:
124
124
  throw new Error("the corpus guard failed");
125
125
  default:
@@ -133,47 +133,55 @@ function corpusPipe(args) {
133
133
  const record = args;
134
134
  return { ...record, where: { ...record.where, nope: "x" } };
135
135
  }
136
- export const CORPUS_CLIENT_CONFIG = {
137
- fields: {
138
- books: {
136
+ export const CORPUS_RESOURCES = {
137
+ books: {
138
+ fields: {
139
139
  label: {
140
- type: { scalar: "string" },
141
- nullable: false,
142
- list: false,
143
- lifecycle: ["VIRTUAL"],
144
- },
145
- },
146
- },
147
- behaviors: {
148
- books: {
149
- fields: {
150
- label: {
151
- read: {
152
- dependsOn: ["id"],
153
- 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
+ },
154
152
  },
155
153
  },
156
154
  },
157
155
  },
158
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 = {
159
163
  pipelines: { guards: [corpusGuard], pipes: [corpusPipe] },
160
- restrictions: {
161
- authors: { fields: { secret: { hidden: true } } },
162
- secrets: { operations: { find: { many: false } } },
163
- hiddenRes: { hidden: true },
164
+ exposed: true,
165
+ exemptions: {
166
+ resources: {
167
+ authors: { fields: { secret: true } },
168
+ secrets: { operations: { find: ["many"] } },
169
+ hiddenRes: true,
170
+ },
164
171
  },
165
172
  };
166
173
  export const CORPUS_ENTRYPOINT = "/api";
167
- export const CORPUS_CLIENT_CONTRACT_HASH = "sha256:07b1a75a6e8e88e30115ae81bcf7e3a12fa493bddce995a01867e2381bd158d0";
168
- 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"}';
169
- 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";
177
+ export const CORPUS_ACCESS_CLIENT_CONTRACT_HASH = "sha256:e45cdbc0c5f27e94058a1e0a1e4b9e532522ebeddcbb68170ac6ec6b45ca0d23";
170
178
  export const CORPUS_AUTHOR = { id: "a1", name: "Ada" };
171
179
  export const CORPUS_NOBODY = "nobody";
172
180
  export const CORPUS_SAMPLE = {
173
181
  id: "s1",
174
182
  count: 9007199254740993n,
175
183
  at: new Date("2026-10-05T12:34:56.789Z"),
176
- amount: new Decimal("12.50"),
184
+ amount: new AvDecimal("12.50"),
177
185
  blob: new Uint8Array([255, 0, 16]),
178
186
  };
179
187
  const CONFLICTS = {
@@ -190,7 +198,7 @@ function corpusAnswer(operation) {
190
198
  const id = whereIdOf(operation.arguments);
191
199
  const conflict = id === undefined ? undefined : CONFLICTS[id];
192
200
  if (conflict !== undefined) {
193
- throw new FrameworkError(conflict, `The corpus Adapter answered ${conflict}.`);
201
+ throw new AvError(conflict, `The corpus Adapter answered ${conflict}.`);
194
202
  }
195
203
  if (id === CORPUS_TRIGGER.adapterThrows) {
196
204
  throw new Error("the corpus Adapter failed");
@@ -214,6 +222,22 @@ function corpusAnswer(operation) {
214
222
  }
215
223
  return CORPUS_AUTHOR;
216
224
  }
225
+ export const CORPUS_ACCESS = {
226
+ state: () => ({ me: null }),
227
+ policy: {
228
+ can: ({ resource }) => {
229
+ if (resource === "samples") {
230
+ throw new Error("the corpus policy failed");
231
+ }
232
+ if (resource === "books") {
233
+ throw new AvError("A4002", "The corpus policy refuses books.");
234
+ }
235
+ return true;
236
+ },
237
+ scopeOf: ({ resource, action }) => !(resource === "authors" && action === "delete"),
238
+ fieldsOf: () => ({ always: "all", perRow: {} }),
239
+ },
240
+ };
217
241
  function failingCommit(transaction) {
218
242
  return async (work) => {
219
243
  let doomed = false;
@@ -231,7 +255,7 @@ function failingCommit(transaction) {
231
255
  return result;
232
256
  };
233
257
  }
234
- export async function createCorpusFramework(contractHash) {
258
+ export function corpusConfiguration(contractHash) {
235
259
  const fake = createFakeAdapter({
236
260
  model: contractHash === CORPUS_NONE_CLIENT_CONTRACT_HASH
237
261
  ? { ...CORPUS_MODEL, transactions: "none" }
@@ -244,10 +268,25 @@ export async function createCorpusFramework(contractHash) {
244
268
  ? {}
245
269
  : { transaction: failingCommit(fake.transaction) }),
246
270
  });
271
+ return {
272
+ config: {
273
+ api: CORPUS_ENTRYPOINT,
274
+ adapter,
275
+ resources: CORPUS_RESOURCES,
276
+ application: CORPUS_APPLICATION_CONFIG,
277
+ client: CORPUS_CLIENT_CONFIG,
278
+ ...(contractHash === CORPUS_ACCESS_CLIENT_CONTRACT_HASH
279
+ ? { access: CORPUS_ACCESS }
280
+ : {}),
281
+ },
282
+ adapterCalls: () => adapter.operations.length,
283
+ };
284
+ }
285
+ export async function createCorpusFramework(contractHash, prebuilt) {
286
+ const { config, adapterCalls } = corpusConfiguration(contractHash);
247
287
  const framework = await createFramework({
248
- entrypoint: CORPUS_ENTRYPOINT,
249
- adapter,
250
- client: CORPUS_CLIENT_CONFIG,
288
+ ...config,
289
+ ...(prebuilt === undefined ? {} : { prebuilt }),
251
290
  });
252
- return { framework, adapterCalls: () => adapter.operations.length };
291
+ return { framework, adapterCalls };
253
292
  }
@@ -2,7 +2,7 @@ import type { AvProtocolDriver } from "./protocol-driver.js";
2
2
  /**
3
3
  * The protocol hosted in process, with no HTTP stack: `/_contract` goes to
4
4
  * `protocol.encodeContract`, `/_transactions` to `protocol.handleTransaction`,
5
- * every other request to `protocol.handleOperation`, and the answer is written
6
- * back as it is.
5
+ * `/_access/can` to `protocol.handleCan`, every other request to
6
+ * `protocol.handleOperation`, and the answer is written back as it is.
7
7
  */
8
8
  export declare const inProcessDriver: AvProtocolDriver;