@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.
- package/CHANGELOG.md +544 -1
- package/README.md +23 -22
- package/dist/adapters/adapter-factory.d.ts +2 -2
- package/dist/adapters/fake-adapter.d.ts +16 -16
- package/dist/index.d.ts +2 -2
- package/dist/protocol/corpus-contract.fixture.d.ts +226 -51
- package/dist/protocol/corpus-contract.fixture.js +74 -35
- package/dist/protocol/in-process.driver.d.ts +2 -2
- package/dist/protocol/in-process.driver.js +2 -0
- package/dist/protocol/per-route.driver.d.ts +2 -1
- package/dist/protocol/per-route.driver.js +2 -0
- package/dist/protocol/protocol-conformance.d.ts +4 -4
- package/dist/protocol/protocol-corpus.d.ts +6 -4
- package/dist/protocol/protocol-driver.d.ts +2 -2
- package/dist/protocol/protocol-fixture.d.ts +4 -4
- package/dist/protocol/protocol-fixtures.js +146 -4
- package/dist/protocol/protocol-fixtures.json-codec.d.ts +2 -1
- package/dist/protocol/protocol-fixtures.json-codec.js +5 -1
- package/fixtures/protocol-v1.json +113 -100
- package/package.json +3 -3
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
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
|
|
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
|
|
6
|
+
export type AvFakeTransactionOutcome = "committed" | "rolled-back";
|
|
7
7
|
/** One settled transaction boundary: what ran inside it, and how it ended. */
|
|
8
|
-
export type
|
|
8
|
+
export type AvFakeTransactionTrace<M extends AvAdapterModel> = {
|
|
9
9
|
/** The operations run inside the transaction, in order. */
|
|
10
|
-
readonly operations: readonly
|
|
10
|
+
readonly operations: readonly AvAdapterOperation<M>[];
|
|
11
11
|
/** How the transaction ended. */
|
|
12
|
-
readonly outcome:
|
|
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
|
|
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
|
|
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?:
|
|
31
|
-
/** Makes transactions roll back the test's own state; see {@link
|
|
32
|
-
readonly rollback?:
|
|
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
|
|
39
|
-
readonly operations: readonly
|
|
40
|
-
readonly transactions: readonly
|
|
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
|
|
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 {
|
|
2
|
-
export type {
|
|
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 {
|
|
2
|
-
import {
|
|
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
|
-
*
|
|
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
|
-
* `
|
|
287
|
-
* `
|
|
288
|
-
* - `A2008`, `A2013`, `A2014` — the Adapter throws `
|
|
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:
|
|
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
|
|
318
|
-
*
|
|
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
|
|
322
|
-
readonly
|
|
323
|
-
readonly
|
|
321
|
+
export declare const CORPUS_RESOURCES: {
|
|
322
|
+
readonly books: {
|
|
323
|
+
readonly fields: {
|
|
324
324
|
readonly label: {
|
|
325
|
-
readonly
|
|
326
|
-
readonly
|
|
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
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
-
|
|
361
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
427
|
+
readonly amount: AvDecimal;
|
|
408
428
|
readonly blob: Uint8Array<ArrayBuffer>;
|
|
409
429
|
};
|
|
410
430
|
/**
|
|
411
|
-
*
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
*
|
|
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
|
|
418
|
-
|
|
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 {
|
|
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
|
|
116
|
+
switch (whereIdOf(context.$args)) {
|
|
117
117
|
case CORPUS_TRIGGER.unauthenticated:
|
|
118
|
-
throw new
|
|
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
|
|
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
|
|
137
|
-
|
|
138
|
-
|
|
136
|
+
export const CORPUS_RESOURCES = {
|
|
137
|
+
books: {
|
|
138
|
+
fields: {
|
|
139
139
|
label: {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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:
|
|
168
|
-
export const CORPUS_CLIENT_CONTRACT_DOCUMENT = '{"enums":{},"limits":{"maxBooleanNodes":50,"maxListLimit":250,"maxNestingDepth":12,"maxRequestBytes":1048576,"maxTransactionOperations":20},"protocol":{"hash":"sha256:
|
|
169
|
-
export const CORPUS_NONE_CLIENT_CONTRACT_HASH = "sha256:
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
client: CORPUS_CLIENT_CONFIG,
|
|
288
|
+
...config,
|
|
289
|
+
...(prebuilt === undefined ? {} : { prebuilt }),
|
|
251
290
|
});
|
|
252
|
-
return { framework, adapterCalls
|
|
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
|
-
*
|
|
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;
|