@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.
- package/README.md +157 -25
- package/dist/adapters.d.ts +25 -0
- package/dist/adapters.js +56 -0
- package/dist/core/application/exceptions.d.ts +17 -0
- package/dist/core/application/migrate.d.ts +19 -0
- package/dist/core/application/schema.d.ts +30 -0
- package/dist/core/application/start.d.ts +7 -0
- package/dist/core/application/types/application.interface.d.ts +73 -2
- package/dist/core/auth/jwt.d.ts +30 -0
- package/dist/core/auth/types/jwt.interface.d.ts +66 -0
- package/dist/core/blueprint/exceptions.d.ts +5 -0
- package/dist/core/blueprint/optional.d.ts +3 -1
- package/dist/core/blueprint/types/blueprint.interface.d.ts +79 -5
- package/dist/core/blueprint/variant.d.ts +32 -0
- package/dist/core/command/types/command.interface.d.ts +7 -0
- package/dist/core/entity/abstract-entity.d.ts +33 -1
- package/dist/core/entity/entity.d.ts +1 -1
- package/dist/core/entity/event.d.ts +13 -2
- package/dist/core/entity/exceptions.d.ts +17 -0
- package/dist/core/entity/trust.d.ts +24 -0
- package/dist/core/entity/types/entity.interface.d.ts +96 -2
- package/dist/core/entity/version.d.ts +13 -0
- package/dist/core/event-policy/event-policy.d.ts +25 -0
- package/dist/core/event-policy/exceptions.d.ts +11 -0
- package/dist/core/event-policy/publish.d.ts +17 -0
- package/dist/core/event-policy/publishers.d.ts +18 -0
- package/dist/core/event-policy/types/event-policy.interface.d.ts +72 -0
- package/dist/core/output/access.d.ts +16 -0
- package/dist/core/output/coerce.d.ts +9 -0
- package/dist/core/output/exceptions.d.ts +16 -0
- package/dist/core/output/http.d.ts +36 -0
- package/dist/core/output/openapi-sink.d.ts +160 -39
- package/dist/core/output/stage.d.ts +7 -2
- package/dist/core/output/status.d.ts +2 -2
- package/dist/core/output/types/http.interface.d.ts +130 -0
- package/dist/core/output/types/output.interface.d.ts +25 -5
- package/dist/core/parse/issue.d.ts +28 -3
- package/dist/core/parse/parse.d.ts +19 -0
- package/dist/core/parse/strip.d.ts +13 -0
- package/dist/core/property/abstract-property.d.ts +9 -0
- package/dist/core/property/define-property.d.ts +34 -6
- package/dist/core/property/types/property-options.interface.d.ts +10 -0
- package/dist/core/property/types/property-type.interface.d.ts +4 -0
- package/dist/core/record/abstract-record.d.ts +7 -0
- package/dist/core/record/types/record.interface.d.ts +12 -1
- package/dist/core/repository/exceptions.d.ts +43 -0
- package/dist/core/repository/in-memory.d.ts +16 -3
- package/dist/core/repository/migrate.d.ts +20 -0
- package/dist/core/repository/plan.d.ts +20 -0
- package/dist/core/repository/provider.d.ts +1 -1
- package/dist/core/repository/query.d.ts +9 -2
- package/dist/core/repository/repository.d.ts +40 -8
- package/dist/core/repository/state.d.ts +32 -2
- package/dist/core/repository/types/query.interface.d.ts +106 -5
- package/dist/core/repository/types/repository.interface.d.ts +139 -7
- package/dist/core/repository/types/schema.interface.d.ts +126 -0
- package/dist/core/repository/upcast.d.ts +64 -0
- package/dist/core/schema/schema.d.ts +28 -0
- package/dist/core/symbols/kind.d.ts +1 -1
- package/dist/core/use-case/access.d.ts +12 -0
- package/dist/core/use-case/decorators.d.ts +40 -0
- package/dist/core/use-case/route.d.ts +18 -0
- package/dist/core/use-case/steps/types/check.interface.d.ts +214 -0
- package/dist/core/use-case/types/access.interface.d.ts +39 -0
- package/dist/core/use-case/types/route.interface.d.ts +66 -0
- package/dist/core/use-case/use-case.d.ts +52 -2
- package/dist/experimental.d.ts +13 -1
- package/dist/experimental.js +64 -26
- package/dist/index-3aae5jfq.js +2997 -0
- package/dist/index-hg1z2s2v.js +1055 -0
- package/dist/index-ny3as79r.js +4567 -0
- package/dist/index-xegv6a1p.js +4291 -0
- package/dist/index-y280ew0k.js +396 -0
- package/dist/index.d.ts +55 -48
- package/dist/index.js +141 -169
- package/dist/std/blueprint-plugin.d.ts +6 -2
- package/dist/std/blueprint.d.ts +3 -0
- package/dist/std/entity.d.ts +2 -1
- package/dist/std/event-policies.d.ts +2 -0
- package/dist/std/properties/exceptions.d.ts +11 -0
- package/dist/std/properties/json.d.ts +54 -0
- package/dist/std/properties/password.d.ts +0 -2
- package/dist/std/properties/time.d.ts +4 -0
- package/dist/std/record-plugins.d.ts +2 -2
- package/dist/std/resource.d.ts +31 -0
- package/dist/std/runtime.d.ts +6 -0
- package/dist/std/use-cases/auth.d.ts +5 -1
- package/dist/std/use-cases/crud.d.ts +6 -4
- package/dist/std/use-cases/index.d.ts +1 -1
- package/dist/std/use-cases/shared.d.ts +34 -6
- package/dist/std/use-cases/types.d.ts +197 -22
- package/dist/testing/http-provider-contract.d.ts +32 -0
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.js +839 -3
- package/dist/testing/memory-publisher.d.ts +16 -0
- package/dist/testing/provider-contract.d.ts +31 -1
- package/package.json +17 -3
- package/dist/index-q6hggpzw.js +0 -6667
- 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
|
-
/**
|
|
5
|
-
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
-
/**
|
|
4
|
-
|
|
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
|
|
37
|
+
/** `attempt`, with the error of any step as a rejection. */
|
|
31
38
|
private attemptWithoutThrowing;
|
|
32
|
-
/**
|
|
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
|
-
/**
|
|
52
|
+
/** Rejects the value with an `exception` that a step threw. */
|
|
42
53
|
private raise;
|
|
43
54
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
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 :
|
|
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
|
-
*
|
|
4
|
-
*
|
|
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():
|
|
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
|
-
|
|
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[];
|