@afora/core 0.0.7 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/README.md +157 -25
  2. package/dist/adapters.d.ts +25 -0
  3. package/dist/adapters.js +56 -0
  4. package/dist/core/application/exceptions.d.ts +17 -0
  5. package/dist/core/application/migrate.d.ts +19 -0
  6. package/dist/core/application/schema.d.ts +30 -0
  7. package/dist/core/application/start.d.ts +7 -0
  8. package/dist/core/application/types/application.interface.d.ts +73 -2
  9. package/dist/core/auth/jwt.d.ts +30 -0
  10. package/dist/core/auth/types/jwt.interface.d.ts +66 -0
  11. package/dist/core/blueprint/exceptions.d.ts +5 -0
  12. package/dist/core/blueprint/optional.d.ts +3 -1
  13. package/dist/core/blueprint/types/blueprint.interface.d.ts +79 -5
  14. package/dist/core/blueprint/variant.d.ts +32 -0
  15. package/dist/core/command/types/command.interface.d.ts +7 -0
  16. package/dist/core/entity/abstract-entity.d.ts +33 -1
  17. package/dist/core/entity/entity.d.ts +1 -1
  18. package/dist/core/entity/event.d.ts +13 -2
  19. package/dist/core/entity/exceptions.d.ts +17 -0
  20. package/dist/core/entity/trust.d.ts +24 -0
  21. package/dist/core/entity/types/entity.interface.d.ts +96 -2
  22. package/dist/core/entity/version.d.ts +13 -0
  23. package/dist/core/event-policy/event-policy.d.ts +25 -0
  24. package/dist/core/event-policy/exceptions.d.ts +11 -0
  25. package/dist/core/event-policy/publish.d.ts +17 -0
  26. package/dist/core/event-policy/publishers.d.ts +18 -0
  27. package/dist/core/event-policy/types/event-policy.interface.d.ts +72 -0
  28. package/dist/core/output/access.d.ts +16 -0
  29. package/dist/core/output/coerce.d.ts +9 -0
  30. package/dist/core/output/exceptions.d.ts +16 -0
  31. package/dist/core/output/http.d.ts +36 -0
  32. package/dist/core/output/openapi-sink.d.ts +160 -39
  33. package/dist/core/output/stage.d.ts +7 -2
  34. package/dist/core/output/status.d.ts +2 -2
  35. package/dist/core/output/types/http.interface.d.ts +130 -0
  36. package/dist/core/output/types/output.interface.d.ts +25 -5
  37. package/dist/core/parse/issue.d.ts +28 -3
  38. package/dist/core/parse/parse.d.ts +19 -0
  39. package/dist/core/parse/strip.d.ts +13 -0
  40. package/dist/core/property/abstract-property.d.ts +9 -0
  41. package/dist/core/property/define-property.d.ts +34 -6
  42. package/dist/core/property/types/property-options.interface.d.ts +10 -0
  43. package/dist/core/property/types/property-type.interface.d.ts +4 -0
  44. package/dist/core/record/abstract-record.d.ts +7 -0
  45. package/dist/core/record/types/record.interface.d.ts +12 -1
  46. package/dist/core/repository/exceptions.d.ts +43 -0
  47. package/dist/core/repository/in-memory.d.ts +16 -3
  48. package/dist/core/repository/migrate.d.ts +20 -0
  49. package/dist/core/repository/plan.d.ts +20 -0
  50. package/dist/core/repository/provider.d.ts +1 -1
  51. package/dist/core/repository/query.d.ts +9 -2
  52. package/dist/core/repository/repository.d.ts +40 -8
  53. package/dist/core/repository/state.d.ts +32 -2
  54. package/dist/core/repository/types/query.interface.d.ts +106 -5
  55. package/dist/core/repository/types/repository.interface.d.ts +139 -7
  56. package/dist/core/repository/types/schema.interface.d.ts +126 -0
  57. package/dist/core/repository/upcast.d.ts +64 -0
  58. package/dist/core/schema/schema.d.ts +28 -0
  59. package/dist/core/symbols/kind.d.ts +1 -1
  60. package/dist/core/use-case/access.d.ts +12 -0
  61. package/dist/core/use-case/decorators.d.ts +40 -0
  62. package/dist/core/use-case/route.d.ts +18 -0
  63. package/dist/core/use-case/steps/types/check.interface.d.ts +214 -0
  64. package/dist/core/use-case/types/access.interface.d.ts +39 -0
  65. package/dist/core/use-case/types/route.interface.d.ts +66 -0
  66. package/dist/core/use-case/use-case.d.ts +52 -2
  67. package/dist/experimental.d.ts +13 -1
  68. package/dist/experimental.js +64 -26
  69. package/dist/index-3aae5jfq.js +2997 -0
  70. package/dist/index-hg1z2s2v.js +1055 -0
  71. package/dist/index-ny3as79r.js +4567 -0
  72. package/dist/index-xegv6a1p.js +4291 -0
  73. package/dist/index-y280ew0k.js +396 -0
  74. package/dist/index.d.ts +55 -48
  75. package/dist/index.js +141 -169
  76. package/dist/std/blueprint-plugin.d.ts +6 -2
  77. package/dist/std/blueprint.d.ts +3 -0
  78. package/dist/std/entity.d.ts +2 -1
  79. package/dist/std/event-policies.d.ts +2 -0
  80. package/dist/std/properties/exceptions.d.ts +11 -0
  81. package/dist/std/properties/json.d.ts +54 -0
  82. package/dist/std/properties/password.d.ts +0 -2
  83. package/dist/std/properties/time.d.ts +4 -0
  84. package/dist/std/record-plugins.d.ts +2 -2
  85. package/dist/std/resource.d.ts +31 -0
  86. package/dist/std/runtime.d.ts +6 -0
  87. package/dist/std/use-cases/auth.d.ts +5 -1
  88. package/dist/std/use-cases/crud.d.ts +6 -4
  89. package/dist/std/use-cases/index.d.ts +1 -1
  90. package/dist/std/use-cases/shared.d.ts +34 -6
  91. package/dist/std/use-cases/types.d.ts +197 -22
  92. package/dist/testing/http-provider-contract.d.ts +32 -0
  93. package/dist/testing/index.d.ts +4 -0
  94. package/dist/testing/index.js +839 -3
  95. package/dist/testing/memory-publisher.d.ts +16 -0
  96. package/dist/testing/provider-contract.d.ts +31 -1
  97. package/package.json +17 -3
  98. package/dist/index-q6hggpzw.js +0 -6667
  99. package/dist/index-r78dsvtc.js +0 -1721
@@ -1,8 +1,13 @@
1
1
  import type { IStageCatalog, SinkFactory, SinkSettings, Stage, StageDescription, StageFactory, StageOptions, TransformFactory, TransformSettings } from "./types/output.interface.js";
2
2
  /** A factory of transform stages: `select({ tags: ["public"] })`. */
3
3
  export declare function transformOf<Options extends object = StageOptions>(settings: TransformSettings<Options>): TransformFactory<Options>;
4
- /** A factory of sink stages: `openapi({ info })`. */
5
- export declare function sinkOf<Result, Options extends object = StageOptions>(settings: SinkSettings<Result, Options>): SinkFactory<Result, Options>;
4
+ /**
5
+ * A factory of sink stages: `openapi({ info })`. A sink that needs what JSON
6
+ * cannot hold (middlewares, an authenticator) takes it as a second argument,
7
+ * its `hooks`, which `build` receives and the description of the stage
8
+ * leaves out.
9
+ */
10
+ export declare function sinkOf<Result, Options extends object = StageOptions, Hooks = never>(settings: SinkSettings<Result, Options, Hooks>): SinkFactory<Result, Options, Hooks>;
6
11
  /**
7
12
  * What a stage is: its kind, name and JSON options when it came from a
8
13
  * factory, `{ kind: "opaque" }` otherwise (a hand-written stage needs an
@@ -10,8 +10,8 @@ export declare function raisedOf(error: Exception): Exception;
10
10
  * The HTTP-style status of an error, in this order: `statuses` by exception
11
11
  * name; 400 for invalid input or context (and any exception whose
12
12
  * `details.issues` is a list, except `InvalidCommandEnvironment`, a 500); 404 for a missing entity or operation; 409 for
13
- * a unique violation or an entity that already exists; 401 for
14
- * `UseCaseInvalidCredentials`; 422 for a class in `operation.errors`; 500 for the
13
+ * a unique violation, a concurrency conflict or an entity that already exists; 401 for
14
+ * `UseCaseInvalidCredentials` and `Unauthenticated`; 403 for `Forbidden`; 422 for a class in `operation.errors`; 500 for the
15
15
  * rest, errors that are not exceptions included. Exceptions match by name,
16
16
  * or by class for `operation.errors`.
17
17
  */
@@ -0,0 +1,130 @@
1
+ import type { DescribedJwtOptions } from "../../auth/types/jwt.interface.js";
2
+ import type { Principal } from "../../use-case/types/access.interface.js";
3
+ import type { OpenAPIMethod, OpenAPISinkOptions, RouteOptions } from "../openapi-sink.js";
4
+ import type { OperationDescriptor } from "./output.interface.js";
5
+ /**
6
+ * The options every sink that serves HTTP takes. JSON, so the stage can live
7
+ * in an `afora.json`. The routing options (`basePath`, `route`, `responses`,
8
+ * `requestId`, `security`) are those of the `openapi` sink, so the served
9
+ * routes and the document agree.
10
+ */
11
+ export interface HttpOptions extends RouteOptions {
12
+ /** Serves the OpenAPI document of the same operations at `path`. */
13
+ readonly docs?: {
14
+ readonly path: string;
15
+ readonly info: OpenAPISinkOptions["info"];
16
+ readonly servers?: OpenAPISinkOptions["servers"];
17
+ };
18
+ /** Adds the `trace` to problems and the real `detail` to 5xx ones. Off by default. */
19
+ readonly debug?: boolean;
20
+ /**
21
+ * The context of an operation, by context key: from request headers
22
+ * (`{ headers: { locale: "accept-language" } }`) and from values a
23
+ * middleware of the server set for the request
24
+ * (`{ variables: { caller: "caller" } }`).
25
+ */
26
+ readonly context?: {
27
+ readonly headers?: {
28
+ readonly [contextKey: string]: string;
29
+ };
30
+ readonly variables?: {
31
+ readonly [contextKey: string]: string;
32
+ };
33
+ };
34
+ /**
35
+ * Who calls, from a JSON Web Token (see `jwtOf`; the secret is named by
36
+ * its environment variable, `{ env: "JWT_SECRET" }`): read from the
37
+ * `authorization` header (`Bearer <token>`; another header with `header`)
38
+ * and, with `cookie`, from that cookie when the header is absent. The
39
+ * document gets a matching security scheme unless `security` declares
40
+ * its own. An `authenticator` given in the hooks of the sink replaces it.
41
+ */
42
+ readonly auth?: {
43
+ readonly jwt: DescribedJwtOptions & {
44
+ readonly header?: string;
45
+ readonly cookie?: string;
46
+ };
47
+ };
48
+ }
49
+ /** A request, as the server of a sink gives it to the route table. */
50
+ export interface HttpRequest {
51
+ /** The value of the `{name}` segment of the path, decoded. */
52
+ param(name: string): string | undefined;
53
+ /** The query string, the first value of each name. */
54
+ query(): Readonly<Record<string, string | undefined>>;
55
+ /** The value of a header, by its name in lower case. */
56
+ header(name: string): string | undefined;
57
+ /** The body, as text. Called at most once, and only by routes with a body. */
58
+ text(): string | Promise<string>;
59
+ /** A value a middleware of the server set for this request, by name. */
60
+ variable?(name: string): unknown;
61
+ /** The request of the server itself, for an authenticator that needs it. */
62
+ readonly native?: unknown;
63
+ }
64
+ /** The answer to a request: what the server of a sink has to send, as is. */
65
+ export interface HttpResult {
66
+ readonly status: number;
67
+ /** `content-type` included, when there is a body. */
68
+ readonly headers: Readonly<Record<string, string>>;
69
+ /** One `set-cookie` header each. */
70
+ readonly cookies: readonly string[];
71
+ /** The body, already serialized (sensitive values masked), or `null` for none. */
72
+ readonly body: string | null;
73
+ }
74
+ /** What an authenticator reads of a request. */
75
+ export interface AuthRequest {
76
+ /** The value of a header, by its name in lower case. */
77
+ header(name: string): string | undefined;
78
+ /** The value of a cookie of the request. */
79
+ cookie(name: string): string | undefined;
80
+ /** The operation being called. */
81
+ readonly operation: OperationDescriptor;
82
+ /** The request of the server itself. */
83
+ readonly native?: unknown;
84
+ }
85
+ /**
86
+ * Who is calling, from the credentials of a request: a `Principal`, or
87
+ * `null` for an anonymous caller. Called for each request to an operation
88
+ * with an `access` other than `public`. It may throw `Unauthenticated` to
89
+ * refuse credentials it does not accept.
90
+ */
91
+ export type Authenticator = (request: AuthRequest) => Principal | null | Promise<Principal | null>;
92
+ /** One operation, ready to serve: the server routes `method` and `path` to `handle`. */
93
+ export interface CompiledRoute {
94
+ readonly operation: OperationDescriptor;
95
+ readonly method: OpenAPIMethod;
96
+ /** `basePath` included, parameters as `{name}`. */
97
+ readonly path: string;
98
+ /** The names of the `{name}` segments of `path`, in order. */
99
+ readonly parameters: readonly string[];
100
+ /** Never rejects: an error is a problem result. */
101
+ handle(request: HttpRequest): Promise<HttpResult>;
102
+ }
103
+ /** The routes of an output, compiled once (see `compileRoutes`). */
104
+ export interface RouteTable {
105
+ readonly routes: readonly CompiledRoute[];
106
+ /** The route that serves the OpenAPI document, with `docs`. */
107
+ readonly docs?: {
108
+ readonly path: string;
109
+ handle(request?: Pick<HttpRequest, "header">): HttpResult;
110
+ };
111
+ /** The 404 problem for a request no route serves. */
112
+ notFound(request?: Pick<HttpRequest, "header">): HttpResult;
113
+ /**
114
+ * The problem for an error raised outside `handle`: its status is the one
115
+ * the output `statuses` give its name, else `status`, else `statusOf`.
116
+ */
117
+ failure(error: unknown, options?: {
118
+ readonly operation?: OperationDescriptor;
119
+ readonly status?: number;
120
+ readonly request?: Pick<HttpRequest, "header">;
121
+ }): HttpResult;
122
+ }
123
+ /** What `compileRoutes` takes besides the operations and the options. */
124
+ export interface CompileRoutesSettings {
125
+ /** The name of the sink, for the messages of `InvalidOutputStage`. */
126
+ readonly source: string;
127
+ readonly authenticator?: Authenticator;
128
+ /** Changes the OpenAPI document before it is served (a sink with routes of its own kind). */
129
+ readonly document?: (document: unknown) => unknown;
130
+ }
@@ -2,6 +2,8 @@ import type { AnyBlueprint } from "../../blueprint/types/blueprint.interface.js"
2
2
  import type { AnyCommand } from "../../command/types/command.interface.js";
3
3
  import type { JsonValue } from "../../json.js";
4
4
  import type { Kind } from "../../symbols/kind.js";
5
+ import type { AccessDeclaration, Principal } from "../../use-case/types/access.interface.js";
6
+ import type { RouteDeclaration } from "../../use-case/types/route.interface.js";
5
7
  import type { AnyExceptionClass } from "../../use-case/use-case.js";
6
8
  /** The options of a stage: a JSON object. */
7
9
  export type StageOptions = {
@@ -9,9 +11,15 @@ export type StageOptions = {
9
11
  };
10
12
  /** An HTTP-style status by exception name. */
11
13
  export type OutputStatuses = Readonly<Record<string, number>>;
12
- /** The call of an operation: `context` is what the command (or use case) declares. */
14
+ /**
15
+ * The call of an operation: `context` is what the command (or use case)
16
+ * declares, and `principal` is who calls, as the sink authenticated it
17
+ * (`null` or absent for an anonymous caller). An operation with an `access`
18
+ * checks the principal before it runs.
19
+ */
13
20
  export interface OperationCallOptions {
14
21
  readonly context?: unknown;
22
+ readonly principal?: Principal | null;
15
23
  }
16
24
  /**
17
25
  * One operation of a command, as an output sees it. Frozen; transforms change
@@ -39,9 +47,16 @@ export interface OperationDescriptor {
39
47
  readonly context?: AnyBlueprint;
40
48
  /** The `errors` of the use case. */
41
49
  readonly errors: readonly AnyExceptionClass[];
50
+ /** The route the use case declares (`route` or `@Route`), if any. */
51
+ readonly route?: RouteDeclaration;
52
+ /** Who may call it, as the use case declares (`access` or `@Access`), if it does. */
53
+ readonly access?: AccessDeclaration;
42
54
  /** Set by transforms; `{}` initially. */
43
55
  readonly meta: Readonly<Record<string, unknown>>;
44
- /** `command.run(name, input, options)`. */
56
+ /**
57
+ * `command.run(name, input, { context })`, after the check of `access`:
58
+ * it rejects with `Unauthenticated` or `Forbidden` before anything runs.
59
+ */
45
60
  invoke(input: unknown, options?: OperationCallOptions): Promise<unknown>;
46
61
  }
47
62
  /** What `operationOf` can change. */
@@ -92,15 +107,20 @@ export interface TransformSettings<Options extends object> {
92
107
  /** Why `options` cannot be used, or `undefined`. Checked when the stage is made. */
93
108
  readonly validate?: (options: Options) => string | undefined;
94
109
  }
95
- export interface SinkSettings<Result, Options extends object> {
110
+ export interface SinkSettings<Result, Options extends object, Hooks = never> {
96
111
  readonly name: string;
97
- readonly build: (operations: readonly OperationDescriptor[], options: Options, context: StageContext) => Result;
112
+ /**
113
+ * `hooks` is the second argument the stage was made with: what cannot be
114
+ * JSON (functions, an instance of a server). It is not part of the
115
+ * description of the stage.
116
+ */
117
+ readonly build: (operations: readonly OperationDescriptor[], options: Options, context: StageContext, hooks: Hooks | undefined) => Result;
98
118
  /** Why `options` cannot be used, or `undefined`. Checked when the stage is made. */
99
119
  readonly validate?: (options: Options) => string | undefined;
100
120
  }
101
121
  /** Makes a stage from JSON options. */
102
122
  export type TransformFactory<Options extends object = StageOptions> = (options?: Options) => TransformStage<Options>;
103
- export type SinkFactory<Result = unknown, Options extends object = StageOptions> = (options?: Options) => SinkStage<Result, Options>;
123
+ export type SinkFactory<Result = unknown, Options extends object = StageOptions, Hooks = never> = (options?: Options, hooks?: Hooks) => SinkStage<Result, Options>;
104
124
  /** Any stage factory. */
105
125
  export type StageFactory = TransformFactory<any> | SinkFactory<any, any>;
106
126
  /** Stage factories by name. */
@@ -1,6 +1,31 @@
1
1
  import type { Exception } from "../exception/exception.js";
2
- import type { Issue } from "./types/parse.interface.js";
3
- /** `issue` with a `cause` that is built the first time it is read, and kept. */
4
- export declare function withLazyCause(issue: Issue, build: () => Exception): Issue;
2
+ import type { Issue, IssueCode } from "./types/parse.interface.js";
3
+ /**
4
+ * What a step gives back, in place of a value, for a value it rejects; the
5
+ * issues are then found where the step says, so a valid value costs no result
6
+ * object.
7
+ * @internal
8
+ */
9
+ export declare const REJECTED: unique symbol;
10
+ /** Who knows the exception an issue stands for, and builds it only when asked. */
11
+ export interface IssueOrigin {
12
+ exception(): Exception;
13
+ }
14
+ /**
15
+ * An issue whose `cause` is built the first time it is read, by its origin,
16
+ * which keeps it: every issue of one origin has the same cause. Its own keys
17
+ * are those of any issue, `cause` is not one of them.
18
+ */
19
+ export declare class LazyIssue implements Issue {
20
+ #private;
21
+ readonly message: string;
22
+ readonly path: readonly (string | number)[];
23
+ readonly code: IssueCode;
24
+ readonly source: string;
25
+ constructor(message: string, path: readonly (string | number)[], code: IssueCode, source: string, origin: IssueOrigin);
26
+ get cause(): Exception;
27
+ /** A copy of the issue at `path`, with the same cause. */
28
+ at(path: readonly (string | number)[]): LazyIssue;
29
+ }
5
30
  /** A copy of `issue` at `path`, with the same `cause`, built lazily or not. */
6
31
  export declare function movedIssue(issue: Issue, path: readonly (string | number)[]): Issue;
@@ -1,6 +1,16 @@
1
1
  import type { BlueprintField, IBlueprintCollection } from "../blueprint/types/blueprint.interface.js";
2
2
  import type { ParseResult } from "./types/parse.interface.js";
3
3
  type Path = readonly (string | number)[];
4
+ /**
5
+ * `Object.entries(properties)`, kept for the next time.
6
+ * @internal
7
+ */
8
+ export declare function fieldsOf(properties: IBlueprintCollection): readonly (readonly [string, BlueprintField])[];
9
+ /**
10
+ * Whether `value` is an object made by `{}` or `Object.create(null)`, as parsed JSON is.
11
+ * @internal
12
+ */
13
+ export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
4
14
  /**
5
15
  * `value` handled by each field of `properties`, or every issue found: each
6
16
  * key `properties` does not declare, then those of each field, in order. Keys
@@ -8,6 +18,15 @@ type Path = readonly (string | number)[];
8
18
  * is parsing it, e.g. a record name.
9
19
  */
10
20
  export declare function parseCollection(properties: IBlueprintCollection, value: unknown, path: Path, source: string): ParseResult<Readonly<Record<string, unknown>>>;
21
+ /**
22
+ * `parseCollection` at the root, as a function of the value alone: what it
23
+ * needs of `properties` is looked up on the first value and kept, instead of
24
+ * on every one. For what parses many values of one shape, a blueprint or a
25
+ * record class. The values of the keys in `vouched` are kept as they are,
26
+ * unchecked: for what the caller made itself and knows to be valid.
27
+ * @internal
28
+ */
29
+ export declare function collectionParserOf(properties: IBlueprintCollection, source: string): (value: unknown, vouched?: readonly string[]) => ParseResult<Readonly<Record<string, unknown>>>;
11
30
  /**
12
31
  * `value` handled by `field`, or every issue found: a property runs its
13
32
  * handlers, a blueprint each of its fields, a list each of its items, and a
@@ -0,0 +1,13 @@
1
+ import type { AnyBlueprint, AnyRecordClass, BlueprintField } from "../blueprint/types/blueprint.interface.js";
2
+ /**
3
+ * A copy of `value` without the keys `shape` does not declare, at any depth:
4
+ * in nested blueprints, the items of `listOf`, `optionalOf` and the records
5
+ * and entities held as fields. For a payload with more than the operation
6
+ * reads, such as a webhook from another service, before it is parsed, since
7
+ * parsing rejects an undeclared key (`undeclared_key`).
8
+ *
9
+ * It does not validate, never throws and does not change `value`: a value of
10
+ * the wrong type, a property's value (even an object) and a record instance
11
+ * come back as they are, for parsing to accept or reject.
12
+ */
13
+ export declare function stripUndeclared(shape: AnyBlueprint | AnyRecordClass | BlueprintField, value: unknown): unknown;
@@ -9,6 +9,15 @@ import type { AnyPropertyConstructor } from "./types/property-collection.type.js
9
9
  import type { PropertyValue } from "./types/property-constructor.type.js";
10
10
  import type { PropertyOptions } from "./types/property-options.interface.js";
11
11
  import type { IPropertyType } from "./types/property-type.interface.js";
12
+ /**
13
+ * The definition `Class` parses with. It does not depend on any member the
14
+ * constructor sets, and `parse` leaves nothing on it that the next one reads,
15
+ * so every `safeParse` shares one.
16
+ * @internal
17
+ */
18
+ export declare function parsingDefinitionOf(Class: {
19
+ prototype: object;
20
+ }): AbstractDefineProperty<TSchema>;
12
21
  /** A property instance; `Value` is the value of its schema, as in `ISchema`. */
13
22
  export declare abstract class AbstractProperty<SchemaType extends TSchema, Value = Static<SchemaType>> implements IProperty<SchemaType, Value> {
14
23
  get [State](): PropertyStates;
@@ -1,9 +1,16 @@
1
1
  import type { Static, TSchema } from "typebox";
2
- import type { ParseResult } from "../parse/types/parse.interface.js";
2
+ import type { Issue, ParseResult } from "../parse/types/parse.interface.js";
3
3
  import { State } from "../symbols/state.js";
4
4
  import { PropertyStates } from "./enums/property-states.js";
5
5
  import type { IDefineProperty } from "./types/define-property.interface.js";
6
6
  import type { IPropertyType } from "./types/property-type.interface.js";
7
+ /**
8
+ * Adds to `issues` those of the value a pipeline has just rejected, each
9
+ * under `path`, where the value is. An issue of the value itself has `path`
10
+ * as it is, not a copy.
11
+ * @internal
12
+ */
13
+ export declare function takeRejectionIssues(path: readonly (string | number)[], issues: Issue[]): void;
7
14
  /** How a property handles its value; `Value` is the value of its schema, as in `ISchema`. */
8
15
  export declare abstract class AbstractDefineProperty<SchemaType extends TSchema, Value = Static<SchemaType>> implements IDefineProperty<SchemaType, Value> {
9
16
  [State]: PropertyStates;
@@ -27,9 +34,13 @@ export declare abstract class AbstractDefineProperty<SchemaType extends TSchema,
27
34
  protected afterHandle(value: Value): Value;
28
35
  /** The value, or `MISMATCH` when the schema rejects it. */
29
36
  protected handle(value: Value): Value;
30
- /** `attempt`, with the error of any step as a failure. */
37
+ /** `attempt`, with the error of any step as a rejection. */
31
38
  private attemptWithoutThrowing;
32
- /** Runs the pipeline, updating the state, and tells why it stopped if it did. */
39
+ /**
40
+ * Runs the pipeline, updating the state: the handled value, or `REJECTED`
41
+ * with the reason in `rejection`. A handler that is not declared is not
42
+ * run, and neither is the shape check that protects it.
43
+ */
33
44
  private attempt;
34
45
  /**
35
46
  * Runs a handler: an `Exception` it throws goes up as it is, any other
@@ -38,11 +49,28 @@ export declare abstract class AbstractDefineProperty<SchemaType extends TSchema,
38
49
  private runHandler;
39
50
  /** The `InvalidPropertyValue` of an error that is not an `Exception`, as its cause. */
40
51
  private invalidValue;
41
- /** The failure of an `exception` that a step threw. */
52
+ /** Rejects the value with an `exception` that a step threw. */
42
53
  private raise;
43
54
  /**
44
- * The failure of `value`, which the schema rejects: an issue for each of its
45
- * errors, found once and kept for the details of the exception too.
55
+ * Rejects `value`, which the schema rejects, with its errors, found once
56
+ * and kept for the details of the exception too.
46
57
  */
47
58
  private mismatch;
48
59
  }
60
+ /**
61
+ * Tells that the definitions of `prototype` are as `propertyOf` declares them.
62
+ * @internal
63
+ */
64
+ export declare function declarePipeline(prototype: object): void;
65
+ /**
66
+ * `definition.parse` without its result object: a function giving the handled
67
+ * value, or `REJECTED`, with the issues for `takeRejectionIssues` to take.
68
+ * `undefined` when `definition` has a `parse` of its own, which must be
69
+ * called instead.
70
+ *
71
+ * For a definition declared by `propertyOf`, the function is the pipeline of
72
+ * `attempt` with what never changes read once: which steps there are, the
73
+ * schema, and no state, which nobody can read.
74
+ * @internal
75
+ */
76
+ export declare function parserOf(definition: AbstractDefineProperty<TSchema>): ((value: unknown) => unknown) | undefined;
@@ -14,6 +14,16 @@ export interface PropertyMetadata<Value> {
14
14
  readonly writeOnly?: boolean;
15
15
  /** The demo value, as `default` in `propertyOf`; it is not written to the schema. */
16
16
  readonly default?: Value;
17
+ /**
18
+ * The value a repository gives the key when a stored row lacks it, on read
19
+ * and in a backfill (`app.migrate`); unlike `default`, it fills real data.
20
+ */
21
+ readonly fill?: Value;
22
+ /**
23
+ * The key that held the value in rows stored before the key was renamed:
24
+ * a repository moves it on read and in a backfill.
25
+ */
26
+ readonly renamedFrom?: string;
17
27
  }
18
28
  /**
19
29
  * Bounds of a number. They only narrow the property: a bound looser than one
@@ -15,6 +15,10 @@ export interface IPropertyType<SchemaType extends TSchema, Value = Static<Schema
15
15
  readonly sensitive?: boolean;
16
16
  /** Whether a record lists a key holding it in `options.unique`, for repositories to enforce. */
17
17
  readonly unique?: boolean;
18
+ /** `fill` of `.with()`: the value given to the key when a stored row lacks it. */
19
+ readonly fill?: Value;
20
+ /** `renamedFrom` of `.with()`: the key that held the value in older rows. */
21
+ readonly renamedFrom?: string;
18
22
  }
19
23
  /**
20
24
  * The type of a property declared `unique`, as `Slug`, so that the keys
@@ -15,6 +15,13 @@ interface RecordDeclaration<Properties extends IBlueprintCollection> {
15
15
  export declare const RESERVED_RECORD_KEYS: readonly string[];
16
16
  /** Makes each of `keys` a member of the records of `prototype`, e.g. `user.name`. */
17
17
  export declare function exposeRecordKeys(prototype: object, keys: readonly string[]): void;
18
+ /**
19
+ * Tells the record whose constructor runs next that the values of `keys` are
20
+ * valid as they are. For a class that makes values for its own record on the
21
+ * way to `super()`, as an entity does of its id.
22
+ * @internal
23
+ */
24
+ export declare function vouchFor(keys: readonly string[]): void;
18
25
  /** How `recordDataOf` stores the records a record holds. */
19
26
  export interface RecordDataOptions {
20
27
  /**
@@ -120,7 +120,18 @@ type JsonField<Field, Data> = Field extends {
120
120
  readonly properties: infer Properties extends IBlueprintCollection;
121
121
  } ? Data extends object ? JsonKeys<Properties, Data, never> : Data : Field extends {
122
122
  readonly item: infer Item;
123
- } ? Data extends readonly (infer ItemData)[] ? JsonField<Item, ItemData>[] : Data : Data;
123
+ } ? Data extends readonly (infer ItemData)[] ? JsonField<Item, ItemData>[] : Data : Field extends {
124
+ readonly key: infer Key extends string;
125
+ readonly variants: infer Variants;
126
+ } ? JsonVariant<Key, Variants, Data> : Data;
127
+ /** What `toJSON` gives for the value `Data` of a variant field: each variant by its own blueprint. */
128
+ type JsonVariant<Key extends string, Variants, Data> = Data extends {
129
+ readonly [Name in Key]: infer Tag extends keyof Variants;
130
+ } ? Variants[Tag] extends AnyRecordClass ? {
131
+ readonly [Name in Key]: Tag;
132
+ } & JsonOfRecord<Variants[Tag], Data> : Variants[Tag] extends {
133
+ readonly properties: infer Properties extends IBlueprintCollection;
134
+ } ? JsonKeys<Properties, Data, never> : Data : Data;
124
135
  /** What the `toJSON` of the records of `Class` gives, in place of their plain value. */
125
136
  type JsonOfRecord<Class extends AnyRecordClass, Data> = InstanceType<Class> extends {
126
137
  toJSON(): infer Json;
@@ -18,13 +18,56 @@ export declare const InvalidRepositoryDeclaration: import("../../index.js").Decl
18
18
  }>;
19
19
  /** A method of a repository got a value that is not an entity of it. */
20
20
  export declare const InvalidRepositoryEntity: import("../../index.js").DeclaredExceptionClass<undefined>;
21
+ /**
22
+ * A stored row holds a schema version (`SCHEMA_VERSION_KEY`) that is not a
23
+ * positive integer, or that is above the `schemaVersion` of its entity (it
24
+ * was written by newer code), or an `upgrade` step did not give an object.
25
+ * `source` is the repository name; reads throw it as the cause of
26
+ * `InvalidRepositoryData`.
27
+ */
28
+ export declare const InvalidStoredSchemaVersion: import("../../index.js").DeclaredExceptionClass<{
29
+ readonly id: string;
30
+ readonly stored: unknown;
31
+ readonly current: number;
32
+ readonly reason?: string;
33
+ }>;
21
34
  /** A provider gave a value its entity rejects; `source` is the repository name. */
22
35
  export declare const InvalidRepositoryData: import("../../index.js").DeclaredExceptionClass<undefined>;
23
36
  /** Another entity holds the value of a unique key; `source` is the repository name. */
24
37
  export declare const RepositoryUniqueViolation: import("../../index.js").DeclaredExceptionClass<{
25
38
  readonly key: string;
26
39
  }>;
40
+ /**
41
+ * A repository declared at module level was called after an application
42
+ * bound it to its provider: the calls of the application go through
43
+ * `app.repositories.<key>`, its use cases (`app.run`, `app.useCase`) or a
44
+ * repository given to `execute`; `source` is the repository name.
45
+ */
46
+ export declare const RepositoryUsedOutsideApplication: import("../../index.js").DeclaredExceptionClass<{
47
+ readonly application: string;
48
+ readonly repository: string;
49
+ }>;
27
50
  /** No entity of the id was found; `source` is the repository name. */
28
51
  export declare const RepositoryEntityNotFound: import("../../index.js").DeclaredExceptionClass<{
29
52
  readonly id: string;
30
53
  }>;
54
+ /**
55
+ * `update` of a versioned entity found another version stored than the one
56
+ * it read: someone else wrote it in between (optimistic concurrency).
57
+ * `actual` is the stored version, when the provider knows it; `source` is the
58
+ * repository name. An output answers it with 409.
59
+ */
60
+ export declare const RepositoryConcurrencyConflict: import("../../index.js").DeclaredExceptionClass<{
61
+ readonly id: string;
62
+ readonly expected: number;
63
+ readonly actual?: number;
64
+ }>;
65
+ /**
66
+ * `increment` got deltas it cannot apply: a key that is not a number in the
67
+ * `change` list of the entity, or a value that is not a finite number. Its
68
+ * `issues` name each, so an output answers it as invalid input.
69
+ */
70
+ export declare const InvalidRepositoryIncrement: import("../../index.js").DeclaredExceptionClass<{
71
+ readonly reason: string;
72
+ readonly issues: readonly Issue[];
73
+ }>;
@@ -1,10 +1,23 @@
1
1
  import type { IRepositoryProvider } from "./types/repository.interface.js";
2
2
  /**
3
- * A provider that keeps the values in memory, one table per entity class,
4
- * enforcing its unique keys. Each `repositoryOf` without a provider gets its
3
+ * The provider of `inMemoryProvider`: it runs transactions, reads by cursor,
4
+ * honors expected versions, increments atomically and leaves expired records out.
5
+ */
6
+ export type InMemoryProvider = IRepositoryProvider & {
7
+ readonly capabilities: {
8
+ readonly transactions: true;
9
+ readonly keyset: true;
10
+ readonly versioning: true;
11
+ readonly atomic: true;
12
+ readonly expiry: true;
13
+ };
14
+ };
15
+ /**
16
+ * A provider that keeps the values in memory, one table per entity name it
17
+ * declares (or per entity class, without one), enforcing its unique keys. Each `repositoryOf` without a provider gets its
5
18
  * own; repositories given the same one share the tables of their entities.
6
19
  * Its `transaction` works on isolated snapshots: commits on success, discards
7
20
  * on rejection, runs one at a time and makes other operations wait; nested
8
21
  * transactions are savepoints.
9
22
  */
10
- export declare function inMemoryProvider(): IRepositoryProvider;
23
+ export declare function inMemoryProvider(): InMemoryProvider;
@@ -0,0 +1,20 @@
1
+ import { type RepositoryState } from "./state.js";
2
+ import type { EntityMigrationReport, SchemaPlan } from "./types/schema.interface.js";
3
+ /** How `migrateRepository` walks one repository. */
4
+ export interface RepositoryMigration {
5
+ readonly mode: "check" | "apply";
6
+ readonly destructive: boolean;
7
+ readonly pageSize: number;
8
+ /** Given to `provider.migrate` in `apply`, before the rows are walked. */
9
+ readonly plan: SchemaPlan;
10
+ }
11
+ /**
12
+ * Brings the stored rows of the repository of `state` up to the current
13
+ * shape of its entity (see `app.migrate`): in `apply`, first gives
14
+ * `options.plan` to `provider.migrate`, then rewrites the rows below the
15
+ * current version or with a `renamedFrom` or `fill` pending, and moves the
16
+ * rows of each previous name. Keys no longer declared, and the rows of the
17
+ * previous names, are only removed when `destructive`. Idempotent: a second
18
+ * run finds every valid row up to date.
19
+ */
20
+ export declare function migrateRepository(state: RepositoryState, options: RepositoryMigration): Promise<EntityMigrationReport>;
@@ -0,0 +1,20 @@
1
+ import type { RepositoryIndex, RepositoryOrder, RepositoryPlan } from "./types/query.interface.js";
2
+ /** What the planner reads of a query: its conditions and its orders. */
3
+ export interface PlannedQuery {
4
+ readonly where?: Readonly<Record<string, unknown>>;
5
+ readonly orderBy?: readonly RepositoryOrder[];
6
+ }
7
+ /** What the planner reads of a repository: its unique keys and its indexes. */
8
+ export interface PlannedRepository {
9
+ readonly unique: readonly string[];
10
+ readonly indexes?: Readonly<Record<string, RepositoryIndex>>;
11
+ }
12
+ /**
13
+ * How `query` reaches its rows. A condition on `id`, or on another unique
14
+ * key, by equality or `in` (a batch of key lookups) gives `id` or `unique`.
15
+ * An index whose `where` keys all hold a condition by equality, and whose
16
+ * `orderBy` is the first order of the query (when it has one), gives
17
+ * `index`: the one with the most keys, the first declared on a tie. The rest
18
+ * is a `scan`. It reads nothing, and expects a query `prepareQuery` checked.
19
+ */
20
+ export declare function planQuery(repository: PlannedRepository, query?: PlannedQuery): RepositoryPlan;
@@ -1,6 +1,6 @@
1
1
  import type { ProviderDescription, ProviderOptions, ProviderSettings } from "./types/query.interface.js";
2
2
  import type { IRepositoryProvider } from "./types/repository.interface.js";
3
3
  /** Creates a provider once and attaches an immutable, serializable declaration out of band. */
4
- export declare function providerOf<Options extends ProviderOptions = ProviderOptions, Provider extends IRepositoryProvider = IRepositoryProvider>(settings: ProviderSettings<Options, Provider>): Provider;
4
+ export declare function providerOf<Options extends ProviderOptions = ProviderOptions, const Provider extends IRepositoryProvider = IRepositoryProvider>(settings: ProviderSettings<Options, Provider>): Provider;
5
5
  /** Describes a named provider; an anonymous one needs an explicit import on extraction. */
6
6
  export declare function providerDescriptionOf(provider: IRepositoryProvider): ProviderDescription;
@@ -4,6 +4,13 @@ import type { RepositoryContext, RepositoryData } from "./types/repository.inter
4
4
  export declare function prepareQuery(context: RepositoryContext, input?: unknown, map?: (key: string, value: unknown) => unknown, options?: readonly string[]): RepositoryQuery;
5
5
  /** Structural JSON/data equality without depending on object key insertion order. */
6
6
  export declare function sameValue(a: unknown, b: unknown): boolean;
7
- export declare function matchesQuery(data: RepositoryData, query: RepositoryQuery): boolean;
7
+ /** The time of `query.now`, or the current one. */
8
+ export declare function nowOf(query: ProviderQuery): number;
9
+ /**
10
+ * Whether `data` is a row of `query`: alive unless `withDeleted`, not expired
11
+ * by `expiry` (the key of `context.expiry`) unless `withExpired`, and matching
12
+ * `where`.
13
+ */
14
+ export declare function matchesQuery(data: RepositoryData, query: ProviderQuery, expiry?: string, now?: number): boolean;
8
15
  /** The rows of `query` after ordering, `after`, offset and limit. */
9
- export declare function selectRows(rows: Iterable<RepositoryData>, query: ProviderQuery): RepositoryData[];
16
+ export declare function selectRows(rows: Iterable<RepositoryData>, query: ProviderQuery, expiry?: string): RepositoryData[];