@afora/core 0.0.8 → 0.0.11
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 +149 -23
- package/dist/adapters.d.ts +25 -0
- package/dist/adapters.js +32 -0
- package/dist/core/application/application.d.ts +30 -0
- package/dist/core/application/application.js +1291 -0
- package/dist/core/application/exceptions.d.ts +17 -0
- package/dist/core/application/exceptions.js +85 -0
- package/dist/core/application/migrate.d.ts +19 -0
- package/dist/core/application/migrate.js +67 -0
- package/dist/core/application/providers.js +108 -0
- package/dist/core/application/registry.js +23 -0
- package/dist/core/application/schema.d.ts +30 -0
- package/dist/core/application/schema.js +398 -0
- package/dist/core/application/start.js +162 -0
- package/dist/core/application/types/application.interface.d.ts +60 -3
- package/dist/core/auth/jwt.d.ts +30 -0
- package/dist/core/auth/jwt.js +200 -0
- package/dist/core/auth/types/jwt.interface.d.ts +74 -0
- package/dist/core/blueprint/blueprint-plugin.js +7 -0
- package/dist/core/blueprint/blueprint.js +146 -0
- package/dist/core/blueprint/callable.js +13 -0
- package/dist/core/blueprint/exceptions.d.ts +5 -0
- package/dist/core/blueprint/exceptions.js +20 -0
- package/dist/core/blueprint/is-blueprint.js +4 -0
- package/dist/core/blueprint/list.js +19 -0
- package/dist/core/blueprint/optional.d.ts +3 -1
- package/dist/core/blueprint/optional.js +69 -0
- package/dist/core/blueprint/record-field.js +14 -0
- package/dist/core/blueprint/slice.js +127 -0
- package/dist/core/blueprint/types/blueprint.interface.d.ts +95 -5
- package/dist/core/blueprint/variant.d.ts +32 -0
- package/dist/core/blueprint/variant.js +72 -0
- package/dist/core/command/command.js +428 -0
- package/dist/core/command/exceptions.js +30 -0
- package/dist/core/command/types/command.interface.d.ts +10 -1
- package/dist/core/entity/abstract-entity.d.ts +24 -0
- package/dist/core/entity/abstract-entity.js +181 -0
- package/dist/core/entity/entity.d.ts +1 -1
- package/dist/core/entity/entity.js +209 -0
- package/dist/core/entity/event.d.ts +39 -0
- package/dist/core/entity/event.js +80 -0
- package/dist/core/entity/exceptions.d.ts +9 -0
- package/dist/core/entity/exceptions.js +27 -0
- package/dist/core/entity/trust.d.ts +24 -0
- package/dist/core/entity/trust.js +7 -0
- package/dist/core/entity/types/entity.interface.d.ts +30 -0
- package/dist/core/entity/version.js +13 -0
- package/dist/core/environment/environment.d.ts +26 -3
- package/dist/core/environment/environment.js +71 -0
- package/dist/core/event-policy/event-policy.js +76 -0
- package/dist/core/event-policy/exceptions.js +12 -0
- package/dist/core/event-policy/publish.js +98 -0
- package/dist/core/event-policy/publishers.js +18 -0
- package/dist/core/event-policy/types/event-policy.interface.d.ts +8 -2
- package/dist/core/exception/enums/exception-layers.js +15 -0
- package/dist/core/exception/exception.d.ts +15 -0
- package/dist/core/exception/exception.js +128 -0
- package/dist/core/exception/exported.js +49 -0
- package/dist/core/exception/guard.d.ts +20 -1
- package/dist/core/exception/guard.js +16 -0
- package/dist/core/exception/types/exception.interface.d.ts +13 -0
- package/dist/core/json.js +19 -0
- package/dist/core/logger/exceptions.js +7 -0
- package/dist/core/logger/logger.d.ts +14 -1
- package/dist/core/logger/logger.js +178 -0
- package/dist/core/openapi/exceptions.js +7 -0
- package/dist/core/openapi/openapi.js +132 -0
- package/dist/core/openapi/types/openapi.interface.d.ts +7 -1
- package/dist/core/output/access.d.ts +16 -0
- package/dist/core/output/access.js +66 -0
- package/dist/core/output/coerce.js +38 -0
- package/dist/core/output/exceptions.d.ts +16 -0
- package/dist/core/output/exceptions.js +22 -0
- package/dist/core/output/http.d.ts +36 -0
- package/dist/core/output/http.js +351 -0
- package/dist/core/output/openapi-sink.d.ts +33 -4
- package/dist/core/output/openapi-sink.js +571 -0
- package/dist/core/output/output.js +192 -0
- package/dist/core/output/problem.js +30 -0
- package/dist/core/output/select.js +28 -0
- package/dist/core/output/stage.d.ts +7 -2
- package/dist/core/output/stage.js +111 -0
- package/dist/core/output/status.d.ts +1 -1
- package/dist/core/output/status.js +52 -0
- package/dist/core/output/types/http.interface.d.ts +134 -0
- package/dist/core/output/types/output.interface.d.ts +49 -5
- package/dist/core/parse/issue.d.ts +28 -3
- package/dist/core/parse/issue.js +31 -0
- package/dist/core/parse/parse.d.ts +16 -0
- package/dist/core/parse/parse.js +323 -0
- package/dist/core/parse/strip.js +48 -0
- package/dist/core/property/abstract-property.d.ts +9 -0
- package/dist/core/property/abstract-property.js +37 -0
- package/dist/core/property/define-property.d.ts +34 -6
- package/dist/core/property/define-property.js +209 -0
- package/dist/core/property/enums/property-states.js +8 -0
- package/dist/core/property/exceptions.js +12 -0
- package/dist/core/property/input.js +10 -0
- package/dist/core/property/is-property.js +3 -0
- package/dist/core/property/lineage.js +8 -0
- package/dist/core/property/property.d.ts +27 -0
- package/dist/core/property/property.js +46 -0
- package/dist/core/property/refine.js +103 -0
- 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 +8 -0
- package/dist/core/record/abstract-record.js +307 -0
- package/dist/core/record/exceptions.js +49 -0
- package/dist/core/record/marks.js +7 -0
- package/dist/core/record/mask.js +60 -0
- package/dist/core/record/name.js +23 -0
- package/dist/core/record/plugin-catalog.js +115 -0
- package/dist/core/record/plugin.js +198 -0
- package/dist/core/record/record.js +117 -0
- package/dist/core/record/types/record.interface.d.ts +17 -1
- package/dist/core/repository/cursor.js +83 -0
- package/dist/core/repository/exceptions.d.ts +13 -0
- package/dist/core/repository/exceptions.js +72 -0
- package/dist/core/repository/in-memory.d.ts +2 -2
- package/dist/core/repository/in-memory.js +196 -0
- package/dist/core/repository/migrate.d.ts +20 -0
- package/dist/core/repository/migrate.js +254 -0
- package/dist/core/repository/plan.js +27 -0
- package/dist/core/repository/provider.js +29 -0
- package/dist/core/repository/query.js +153 -0
- package/dist/core/repository/references.js +196 -0
- package/dist/core/repository/repository.d.ts +22 -2
- package/dist/core/repository/repository.js +720 -0
- package/dist/core/repository/state.d.ts +2 -0
- package/dist/core/repository/state.js +138 -0
- package/dist/core/repository/types/query.interface.d.ts +13 -0
- package/dist/core/repository/types/repository.interface.d.ts +50 -5
- package/dist/core/repository/types/schema.interface.d.ts +132 -0
- package/dist/core/repository/upcast.d.ts +64 -0
- package/dist/core/repository/upcast.js +179 -0
- package/dist/core/schema/schema.d.ts +37 -0
- package/dist/core/schema/schema.js +542 -0
- package/dist/core/standard/standard-schema.js +1 -0
- package/dist/core/standard/standard.js +23 -0
- package/dist/core/symbols/demo.d.ts +4 -0
- package/dist/core/symbols/demo.js +1 -0
- package/dist/core/symbols/kind.d.ts +1 -1
- package/dist/core/symbols/kind.js +1 -0
- package/dist/core/symbols/meta.js +1 -0
- package/dist/core/symbols/state.d.ts +5 -0
- package/dist/core/symbols/state.js +1 -0
- package/dist/core/tracing/exceptions.d.ts +4 -0
- package/dist/core/tracing/exceptions.js +7 -0
- package/dist/core/tracing/tracing.d.ts +72 -0
- package/dist/core/tracing/tracing.js +101 -0
- package/dist/core/use-case/access.d.ts +12 -0
- package/dist/core/use-case/access.js +56 -0
- package/dist/core/use-case/decorators.d.ts +40 -0
- package/dist/core/use-case/decorators.js +65 -0
- package/dist/core/use-case/exceptions.d.ts +5 -1
- package/dist/core/use-case/exceptions.js +48 -0
- package/dist/core/use-case/plugin-catalog.js +68 -0
- package/dist/core/use-case/plugin.js +133 -0
- package/dist/core/use-case/route.d.ts +18 -0
- package/dist/core/use-case/route.js +200 -0
- package/dist/core/use-case/scope.d.ts +5 -0
- package/dist/core/use-case/scope.js +4 -0
- package/dist/core/use-case/steps/describe.d.ts +5 -1
- package/dist/core/use-case/steps/describe.js +24 -0
- package/dist/core/use-case/steps/references.js +105 -0
- package/dist/core/use-case/steps/run.js +139 -0
- package/dist/core/use-case/steps/types/check.interface.d.ts +233 -0
- package/dist/core/use-case/steps/types/steps.interface.d.ts +8 -1
- package/dist/core/use-case/steps/validate.js +368 -0
- package/dist/core/use-case/target.js +100 -0
- package/dist/core/use-case/types/access.interface.d.ts +39 -0
- package/dist/core/use-case/types/route.interface.d.ts +86 -0
- package/dist/core/use-case/use-case.d.ts +127 -2
- package/dist/core/use-case/use-case.js +493 -0
- package/dist/experimental.d.ts +18 -1
- package/dist/experimental.js +18 -123
- package/dist/index.d.ts +49 -51
- package/dist/index.js +213 -443
- package/dist/std/blueprint-plugin.js +42 -0
- package/dist/std/blueprint.d.ts +21 -0
- package/dist/std/blueprint.js +3 -0
- package/dist/std/command.js +3 -0
- package/dist/std/entity.d.ts +20 -1
- package/dist/std/entity.js +30 -0
- package/dist/std/event-policies.js +3 -0
- package/dist/std/properties/contact.d.ts +3 -0
- package/dist/std/properties/contact.js +29 -0
- package/dist/std/properties/enum.js +26 -0
- package/dist/std/properties/exceptions.js +14 -0
- package/dist/std/properties/headless.js +45 -0
- package/dist/std/properties/identity.js +28 -0
- package/dist/std/properties/json.js +161 -0
- package/dist/std/properties/password.d.ts +0 -2
- package/dist/std/properties/password.js +79 -0
- package/dist/std/properties/primitives.d.ts +4 -0
- package/dist/std/properties/primitives.js +28 -0
- package/dist/std/properties/time.js +55 -0
- package/dist/std/record-plugins.d.ts +2 -2
- package/dist/std/record-plugins.js +73 -0
- package/dist/std/record.d.ts +20 -1
- package/dist/std/record.js +3 -0
- package/dist/std/resource.d.ts +31 -0
- package/dist/std/resource.js +39 -0
- package/dist/std/runtime.d.ts +6 -0
- package/dist/std/runtime.js +193 -0
- package/dist/std/stages.js +4 -0
- package/dist/std/use-cases/auth.d.ts +5 -1
- package/dist/std/use-cases/auth.js +193 -0
- package/dist/std/use-cases/crud.js +261 -0
- package/dist/std/use-cases/index.d.ts +1 -1
- package/dist/std/use-cases/index.js +12 -0
- package/dist/std/use-cases/lifecycle.js +97 -0
- package/dist/std/use-cases/shared.d.ts +2 -2
- package/dist/std/use-cases/shared.js +232 -0
- package/dist/std/use-cases/types.d.ts +102 -2
- package/dist/std/use-cases/types.js +0 -0
- package/dist/testing/http-provider-contract.d.ts +32 -0
- package/dist/testing/http-provider-contract.js +344 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +3 -944
- package/dist/testing/memory-publisher.js +12 -0
- package/dist/testing/provider-contract.d.ts +26 -1
- package/dist/testing/provider-contract.js +1071 -0
- package/package.json +23 -3
- package/dist/index-qxfkyd6k.js +0 -1721
- package/dist/index-t5qwtyj5.js +0 -8194
|
@@ -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. */
|
|
@@ -85,6 +100,11 @@ export type StageDescription = {
|
|
|
85
100
|
} | {
|
|
86
101
|
readonly kind: "opaque";
|
|
87
102
|
};
|
|
103
|
+
/**
|
|
104
|
+
* What `transformOf` takes: the `name` of the stage, the `apply` function that
|
|
105
|
+
* changes each operation (or removes it) and an optional `validate` of its
|
|
106
|
+
* options.
|
|
107
|
+
*/
|
|
88
108
|
export interface TransformSettings<Options extends object> {
|
|
89
109
|
readonly name: string;
|
|
90
110
|
/** Returns the operation, a changed copy, or `null` to remove it. */
|
|
@@ -92,15 +112,30 @@ export interface TransformSettings<Options extends object> {
|
|
|
92
112
|
/** Why `options` cannot be used, or `undefined`. Checked when the stage is made. */
|
|
93
113
|
readonly validate?: (options: Options) => string | undefined;
|
|
94
114
|
}
|
|
95
|
-
|
|
115
|
+
/**
|
|
116
|
+
* What `sinkOf` takes: the `name` of the stage, the `build` function that turns
|
|
117
|
+
* the operations into the result of the output (a server, a document...) and an
|
|
118
|
+
* optional `validate` of its options.
|
|
119
|
+
*/
|
|
120
|
+
export interface SinkSettings<Result, Options extends object, Hooks = never> {
|
|
96
121
|
readonly name: string;
|
|
97
|
-
|
|
122
|
+
/**
|
|
123
|
+
* `hooks` is the second argument the stage was made with: what cannot be
|
|
124
|
+
* JSON (functions, an instance of a server). It is not part of the
|
|
125
|
+
* description of the stage.
|
|
126
|
+
*/
|
|
127
|
+
readonly build: (operations: readonly OperationDescriptor[], options: Options, context: StageContext, hooks: Hooks | undefined) => Result;
|
|
98
128
|
/** Why `options` cannot be used, or `undefined`. Checked when the stage is made. */
|
|
99
129
|
readonly validate?: (options: Options) => string | undefined;
|
|
100
130
|
}
|
|
101
131
|
/** Makes a stage from JSON options. */
|
|
102
132
|
export type TransformFactory<Options extends object = StageOptions> = (options?: Options) => TransformStage<Options>;
|
|
103
|
-
|
|
133
|
+
/**
|
|
134
|
+
* Makes a sink stage from JSON `options` and, second, `hooks`: what cannot be
|
|
135
|
+
* JSON (functions, an instance of a server), which the description of the stage
|
|
136
|
+
* leaves out. What `sinkOf` returns.
|
|
137
|
+
*/
|
|
138
|
+
export type SinkFactory<Result = unknown, Options extends object = StageOptions, Hooks = never> = (options?: Options, hooks?: Hooks) => SinkStage<Result, Options>;
|
|
104
139
|
/** Any stage factory. */
|
|
105
140
|
export type StageFactory = TransformFactory<any> | SinkFactory<any, any>;
|
|
106
141
|
/** Stage factories by name. */
|
|
@@ -123,6 +158,11 @@ export interface OutputApplication {
|
|
|
123
158
|
}
|
|
124
159
|
/** What an output takes its commands from. */
|
|
125
160
|
export type OutputSource = AnyCommand | OutputApplication;
|
|
161
|
+
/**
|
|
162
|
+
* What `outputOf` takes: the `name` of the output, the `commands` it exposes
|
|
163
|
+
* (commands, applications or a mix), the `statuses` by exception name and the
|
|
164
|
+
* `pipe` of stages that transform its operations and end in a sink.
|
|
165
|
+
*/
|
|
126
166
|
export interface OutputSettings<Pipe extends readonly Stage[]> {
|
|
127
167
|
readonly name: string;
|
|
128
168
|
/** The commands the output exposes: commands, applications (all their commands) or a mix. */
|
|
@@ -164,6 +204,10 @@ export interface Problem {
|
|
|
164
204
|
/** Only with `debug`. */
|
|
165
205
|
readonly trace?: string;
|
|
166
206
|
}
|
|
207
|
+
/**
|
|
208
|
+
* What `problemOf` takes: the HTTP `status` of the problem and `debug`, which
|
|
209
|
+
* adds the `trace` and the real `detail` of a 5xx.
|
|
210
|
+
*/
|
|
167
211
|
export interface ProblemOptions {
|
|
168
212
|
readonly status: number;
|
|
169
213
|
readonly debug?: boolean;
|
|
@@ -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;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export const REJECTED = Symbol("rejected");
|
|
2
|
+
|
|
3
|
+
export class LazyIssue {
|
|
4
|
+
message;
|
|
5
|
+
path;
|
|
6
|
+
code;
|
|
7
|
+
source;
|
|
8
|
+
#origin;
|
|
9
|
+
constructor(message, path, code, source, origin) {
|
|
10
|
+
this.message = message;
|
|
11
|
+
this.path = path;
|
|
12
|
+
this.code = code;
|
|
13
|
+
this.source = source;
|
|
14
|
+
this.#origin = origin;
|
|
15
|
+
}
|
|
16
|
+
get cause() {
|
|
17
|
+
return this.#origin.exception();
|
|
18
|
+
}
|
|
19
|
+
at(path) {
|
|
20
|
+
return new LazyIssue(this.message, path, this.code, this.source, this.#origin);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function movedIssue(issue, path) {
|
|
24
|
+
if (issue instanceof LazyIssue)
|
|
25
|
+
return issue.at(path);
|
|
26
|
+
const moved = { ...issue, path };
|
|
27
|
+
const cause = Object.getOwnPropertyDescriptor(issue, "cause");
|
|
28
|
+
if (cause && !cause.enumerable)
|
|
29
|
+
Object.defineProperty(moved, "cause", cause);
|
|
30
|
+
return moved;
|
|
31
|
+
}
|
|
@@ -18,6 +18,22 @@ export declare function isPlainObject(value: unknown): value is Record<string, u
|
|
|
18
18
|
* is parsing it, e.g. a record name.
|
|
19
19
|
*/
|
|
20
20
|
export declare function parseCollection(properties: IBlueprintCollection, value: unknown, path: Path, source: string): ParseResult<Readonly<Record<string, unknown>>>;
|
|
21
|
+
/**
|
|
22
|
+
* Makes, once the rest of a collection is known to be valid, the values the
|
|
23
|
+
* caller left out for it (`undefined` in `handled`); it returns the keys it
|
|
24
|
+
* made that still have to be checked.
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export type Complete = (handled: Record<string, unknown>) => readonly string[] | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* `parseCollection` at the root, as a function of the value alone: what it
|
|
30
|
+
* needs of `properties` is looked up on the first value and kept, instead of
|
|
31
|
+
* on every one. For what parses many values of one shape, a blueprint or a
|
|
32
|
+
* record class. The values of the keys in `vouched` are kept as they are,
|
|
33
|
+
* unchecked: for what the caller made itself and knows to be valid.
|
|
34
|
+
* @internal
|
|
35
|
+
*/
|
|
36
|
+
export declare function collectionParserOf(properties: IBlueprintCollection, source: string): (value: unknown, vouched?: readonly string[], complete?: Complete) => ParseResult<Readonly<Record<string, unknown>>>;
|
|
21
37
|
/**
|
|
22
38
|
* `value` handled by `field`, or every issue found: a property runs its
|
|
23
39
|
* handlers, a blueprint each of its fields, a list each of its items, and a
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { isBlueprint } from "../blueprint/is-blueprint.js";
|
|
2
|
+
import { isBlueprintList } from "../blueprint/list.js";
|
|
3
|
+
import { recordFieldOf } from "../blueprint/record-field.js";
|
|
4
|
+
import { isBlueprintVariant } from "../blueprint/variant.js";
|
|
5
|
+
import {
|
|
6
|
+
AbstractProperty,
|
|
7
|
+
parsingDefinitionOf
|
|
8
|
+
} from "../property/abstract-property.js";
|
|
9
|
+
import { parserOf, takeRejectionIssues } from "../property/define-property.js";
|
|
10
|
+
import { isProperty } from "../property/is-property.js";
|
|
11
|
+
import { recordNameOf } from "../record/name.js";
|
|
12
|
+
import { movedIssue, REJECTED } from "./issue.js";
|
|
13
|
+
const entriesOf = new WeakMap;
|
|
14
|
+
export function fieldsOf(properties) {
|
|
15
|
+
let entries = entriesOf.get(properties);
|
|
16
|
+
if (!entries) {
|
|
17
|
+
entries = Object.entries(properties);
|
|
18
|
+
entriesOf.set(properties, entries);
|
|
19
|
+
}
|
|
20
|
+
return entries;
|
|
21
|
+
}
|
|
22
|
+
export function isPlainObject(value) {
|
|
23
|
+
if (typeof value !== "object" || value === null)
|
|
24
|
+
return false;
|
|
25
|
+
const prototype = Object.getPrototypeOf(value);
|
|
26
|
+
return prototype === Object.prototype || prototype === null;
|
|
27
|
+
}
|
|
28
|
+
const PROPERTY = 0;
|
|
29
|
+
const LIST = 1;
|
|
30
|
+
const BLUEPRINT = 2;
|
|
31
|
+
const RECORD = 3;
|
|
32
|
+
const UNDECLARED_FIELD = 4;
|
|
33
|
+
const VARIANT = 5;
|
|
34
|
+
function stepOf(field) {
|
|
35
|
+
const record = recordFieldOf(field);
|
|
36
|
+
const kind = isBlueprintList(field) ? LIST : isBlueprint(field) ? BLUEPRINT : isBlueprintVariant(field) ? VARIANT : record ? RECORD : isProperty(field) ? PROPERTY : UNDECLARED_FIELD;
|
|
37
|
+
return {
|
|
38
|
+
kind,
|
|
39
|
+
property: kind === PROPERTY ? field : undefined,
|
|
40
|
+
parser: undefined,
|
|
41
|
+
list: kind === LIST ? field : undefined,
|
|
42
|
+
item: undefined,
|
|
43
|
+
blueprint: kind === BLUEPRINT ? field : undefined,
|
|
44
|
+
collection: undefined,
|
|
45
|
+
record: kind === RECORD ? record?.record : undefined,
|
|
46
|
+
optionalRecord: kind === RECORD && record?.optional === true,
|
|
47
|
+
variant: kind === VARIANT ? field : undefined,
|
|
48
|
+
variants: undefined
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const UNDECLARED = {
|
|
52
|
+
kind: UNDECLARED_FIELD,
|
|
53
|
+
property: undefined,
|
|
54
|
+
parser: undefined,
|
|
55
|
+
list: undefined,
|
|
56
|
+
item: undefined,
|
|
57
|
+
blueprint: undefined,
|
|
58
|
+
collection: undefined,
|
|
59
|
+
record: undefined,
|
|
60
|
+
optionalRecord: false,
|
|
61
|
+
variant: undefined,
|
|
62
|
+
variants: undefined
|
|
63
|
+
};
|
|
64
|
+
const steps = new WeakMap;
|
|
65
|
+
function keptStepOf(field) {
|
|
66
|
+
if (typeof field !== "object" && typeof field !== "function" || field === null) {
|
|
67
|
+
return UNDECLARED;
|
|
68
|
+
}
|
|
69
|
+
let step = steps.get(field);
|
|
70
|
+
if (!step) {
|
|
71
|
+
step = stepOf(field);
|
|
72
|
+
steps.set(field, step);
|
|
73
|
+
}
|
|
74
|
+
return step;
|
|
75
|
+
}
|
|
76
|
+
const collections = new WeakMap;
|
|
77
|
+
function collectionOf(properties) {
|
|
78
|
+
let collection = collections.get(properties);
|
|
79
|
+
if (!collection) {
|
|
80
|
+
const fields = fieldsOf(properties);
|
|
81
|
+
collection = {
|
|
82
|
+
properties,
|
|
83
|
+
names: fields.map(([name]) => name),
|
|
84
|
+
steps: fields.map(([, field]) => keptStepOf(field))
|
|
85
|
+
};
|
|
86
|
+
collections.set(properties, collection);
|
|
87
|
+
}
|
|
88
|
+
return collection;
|
|
89
|
+
}
|
|
90
|
+
function runOf(base, source) {
|
|
91
|
+
return { source, base, keys: [], issues: undefined };
|
|
92
|
+
}
|
|
93
|
+
function pathOf(run, depth) {
|
|
94
|
+
const { base, keys } = run;
|
|
95
|
+
const path = base.length === 0 ? [] : [...base];
|
|
96
|
+
for (let index = 0;index < depth; index++) {
|
|
97
|
+
path.push(keys[index]);
|
|
98
|
+
}
|
|
99
|
+
return path;
|
|
100
|
+
}
|
|
101
|
+
function reject(run, code, message, path) {
|
|
102
|
+
run.issues ??= [];
|
|
103
|
+
run.issues.push({ code, message, path, source: run.source });
|
|
104
|
+
return REJECTED;
|
|
105
|
+
}
|
|
106
|
+
function resultOf(run, handled) {
|
|
107
|
+
return handled === REJECTED ? { issues: run.issues ?? [] } : { value: handled };
|
|
108
|
+
}
|
|
109
|
+
export function parseCollection(properties, value, path, source) {
|
|
110
|
+
const run = runOf(path, source);
|
|
111
|
+
return resultOf(run, collectionAt(collectionOf(properties), value, run, 0));
|
|
112
|
+
}
|
|
113
|
+
const ROOT = Object.freeze([]);
|
|
114
|
+
export function collectionParserOf(properties, source) {
|
|
115
|
+
let collection;
|
|
116
|
+
return (value, vouched, complete) => {
|
|
117
|
+
collection ??= collectionOf(properties);
|
|
118
|
+
const run = runOf(ROOT, source);
|
|
119
|
+
return resultOf(run, collectionAt(collection, value, run, 0, vouched, undefined, complete));
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function collectionAt(collection, value, run, depth, vouched, tag, complete) {
|
|
123
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
124
|
+
return reject(run, "invalid_type", "must be an object", pathOf(run, depth));
|
|
125
|
+
}
|
|
126
|
+
const { properties, names, steps } = collection;
|
|
127
|
+
let rejected = false;
|
|
128
|
+
const keys = Object.keys(value);
|
|
129
|
+
for (let index = 0;index < keys.length; index++) {
|
|
130
|
+
const name = keys[index];
|
|
131
|
+
if (name === names[index] || name === tag || Object.hasOwn(properties, name)) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
rejected = true;
|
|
135
|
+
reject(run, "undeclared_key", "is not declared", [
|
|
136
|
+
...pathOf(run, depth),
|
|
137
|
+
name
|
|
138
|
+
]);
|
|
139
|
+
}
|
|
140
|
+
const handled = tag === undefined ? {} : { [tag]: value[tag] };
|
|
141
|
+
const first = vouched?.[0];
|
|
142
|
+
const second = vouched?.[1];
|
|
143
|
+
const third = vouched?.[2];
|
|
144
|
+
for (let index = 0;index < names.length; index++) {
|
|
145
|
+
const name = names[index];
|
|
146
|
+
if (vouched !== undefined && (name === first || name === second || name === third || vouched.length > 3 && vouched.includes(name))) {
|
|
147
|
+
handled[name] = value[name];
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
run.keys[depth] = name;
|
|
151
|
+
const item = fieldAt(steps[index], value[name], run, depth + 1);
|
|
152
|
+
if (item === REJECTED)
|
|
153
|
+
rejected = true;
|
|
154
|
+
else if (item !== undefined)
|
|
155
|
+
handled[name] = item;
|
|
156
|
+
}
|
|
157
|
+
if (!rejected && complete !== undefined) {
|
|
158
|
+
for (const name of complete(handled) ?? []) {
|
|
159
|
+
run.keys[depth] = name;
|
|
160
|
+
const item = fieldAt(steps[names.indexOf(name)], handled[name], run, depth + 1);
|
|
161
|
+
if (item === REJECTED)
|
|
162
|
+
rejected = true;
|
|
163
|
+
else if (item !== undefined)
|
|
164
|
+
handled[name] = item;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return rejected ? REJECTED : Object.freeze(handled);
|
|
168
|
+
}
|
|
169
|
+
export function parseField(field, value, path, source) {
|
|
170
|
+
const run = runOf(path, source);
|
|
171
|
+
return resultOf(run, fieldAt(keptStepOf(field), value, run, 0));
|
|
172
|
+
}
|
|
173
|
+
const DECLARED_SAFE_PARSE = AbstractProperty.safeParse;
|
|
174
|
+
function fieldAt(step, value, run, depth) {
|
|
175
|
+
switch (step.kind) {
|
|
176
|
+
case PROPERTY:
|
|
177
|
+
return propertyAt(step, value, run, depth);
|
|
178
|
+
case LIST: {
|
|
179
|
+
const list = step.list;
|
|
180
|
+
if (value === undefined) {
|
|
181
|
+
return list.optional ? value : required(run, depth);
|
|
182
|
+
}
|
|
183
|
+
if (!Array.isArray(value)) {
|
|
184
|
+
return reject(run, "invalid_type", "must be an array", pathOf(run, depth));
|
|
185
|
+
}
|
|
186
|
+
const { minItems, maxItems } = list;
|
|
187
|
+
let rejected = false;
|
|
188
|
+
if (minItems !== undefined && value.length < minItems || maxItems !== undefined && value.length > maxItems) {
|
|
189
|
+
rejected = true;
|
|
190
|
+
const path = pathOf(run, depth);
|
|
191
|
+
if (minItems !== undefined && value.length < minItems) {
|
|
192
|
+
reject(run, "too_few_items", `must have at least ${minItems} items`, path);
|
|
193
|
+
}
|
|
194
|
+
if (maxItems !== undefined && value.length > maxItems) {
|
|
195
|
+
reject(run, "too_many_items", `must have at most ${maxItems} items`, path);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
step.item ??= keptStepOf(list.item);
|
|
199
|
+
const { item } = step;
|
|
200
|
+
const items = [];
|
|
201
|
+
for (let index = 0;index < value.length; index++) {
|
|
202
|
+
run.keys[depth] = index;
|
|
203
|
+
const handled = fieldAt(item, value[index], run, depth + 1);
|
|
204
|
+
if (handled === REJECTED)
|
|
205
|
+
rejected = true;
|
|
206
|
+
else
|
|
207
|
+
items.push(handled);
|
|
208
|
+
}
|
|
209
|
+
return rejected ? REJECTED : Object.freeze(items);
|
|
210
|
+
}
|
|
211
|
+
case BLUEPRINT: {
|
|
212
|
+
const blueprint = step.blueprint;
|
|
213
|
+
if (value === undefined) {
|
|
214
|
+
return blueprint.optional ? value : required(run, depth);
|
|
215
|
+
}
|
|
216
|
+
step.collection ??= collectionOf(blueprint.properties);
|
|
217
|
+
return collectionAt(step.collection, value, run, depth);
|
|
218
|
+
}
|
|
219
|
+
case VARIANT: {
|
|
220
|
+
const variant = step.variant;
|
|
221
|
+
if (value === undefined) {
|
|
222
|
+
return variant.optional ? value : required(run, depth);
|
|
223
|
+
}
|
|
224
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
225
|
+
return reject(run, "invalid_type", "must be an object", pathOf(run, depth));
|
|
226
|
+
}
|
|
227
|
+
let { variants } = step;
|
|
228
|
+
if (variants === undefined) {
|
|
229
|
+
variants = new Map;
|
|
230
|
+
const declared = variant.variants;
|
|
231
|
+
for (const tag of Object.keys(declared)) {
|
|
232
|
+
const one = declared[tag];
|
|
233
|
+
variants.set(tag, isBlueprint(one) ? collectionOf(one.properties) : one);
|
|
234
|
+
}
|
|
235
|
+
step.variants = variants;
|
|
236
|
+
}
|
|
237
|
+
if (!isPlainObject(value)) {
|
|
238
|
+
for (const one of variants.values()) {
|
|
239
|
+
if (typeof one === "function" && value instanceof one)
|
|
240
|
+
return value;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const { key } = variant;
|
|
244
|
+
const tag = value[key];
|
|
245
|
+
const named = typeof tag === "string" ? variants.get(tag) : undefined;
|
|
246
|
+
if (named === undefined) {
|
|
247
|
+
return reject(run, "invalid_value", "must be equal to one of the allowed values", [...pathOf(run, depth), key]);
|
|
248
|
+
}
|
|
249
|
+
if (typeof named !== "function") {
|
|
250
|
+
return collectionAt(named, value, run, depth, undefined, key);
|
|
251
|
+
}
|
|
252
|
+
const { [key]: _name, ...input } = value;
|
|
253
|
+
const result = named.safeParse(input);
|
|
254
|
+
if (!result.issues)
|
|
255
|
+
return result.value;
|
|
256
|
+
const path = pathOf(run, depth);
|
|
257
|
+
run.issues ??= [];
|
|
258
|
+
for (const issue of result.issues) {
|
|
259
|
+
run.issues.push(movedIssue(issue, [...path, ...issue.path]));
|
|
260
|
+
}
|
|
261
|
+
return REJECTED;
|
|
262
|
+
}
|
|
263
|
+
case RECORD: {
|
|
264
|
+
const Record = step.record;
|
|
265
|
+
if (value === undefined) {
|
|
266
|
+
return step.optionalRecord ? value : required(run, depth);
|
|
267
|
+
}
|
|
268
|
+
if (value instanceof Record)
|
|
269
|
+
return value;
|
|
270
|
+
if (!isPlainObject(value)) {
|
|
271
|
+
return reject(run, "not_record", `must be an instance of record "${recordNameOf(Record)}"`, pathOf(run, depth));
|
|
272
|
+
}
|
|
273
|
+
const result = Record.safeParse(value);
|
|
274
|
+
if (!result.issues)
|
|
275
|
+
return result.value;
|
|
276
|
+
const path = pathOf(run, depth);
|
|
277
|
+
run.issues ??= [];
|
|
278
|
+
for (const issue of result.issues) {
|
|
279
|
+
run.issues.push(movedIssue(issue, [...path, ...issue.path]));
|
|
280
|
+
}
|
|
281
|
+
return REJECTED;
|
|
282
|
+
}
|
|
283
|
+
default:
|
|
284
|
+
return reject(run, "undeclared_key", "is not declared", pathOf(run, depth));
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
function required(run, depth) {
|
|
288
|
+
return reject(run, "required", "is required", pathOf(run, depth));
|
|
289
|
+
}
|
|
290
|
+
function propertyAt(step, value, run, depth) {
|
|
291
|
+
const property = step.property;
|
|
292
|
+
if (property.safeParse === DECLARED_SAFE_PARSE) {
|
|
293
|
+
let { parser } = step;
|
|
294
|
+
if (parser === undefined) {
|
|
295
|
+
parser = parserOf(parsingDefinitionOf(property)) ?? null;
|
|
296
|
+
step.parser = parser;
|
|
297
|
+
}
|
|
298
|
+
if (parser !== null) {
|
|
299
|
+
const handled = parser(value);
|
|
300
|
+
if (handled !== REJECTED)
|
|
301
|
+
return handled;
|
|
302
|
+
run.issues ??= [];
|
|
303
|
+
takeRejectionIssues(pathOf(run, depth), run.issues);
|
|
304
|
+
return REJECTED;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
const result = property.safeParse(value);
|
|
308
|
+
if (!result.issues)
|
|
309
|
+
return result.value;
|
|
310
|
+
const path = pathOf(run, depth);
|
|
311
|
+
run.issues ??= [];
|
|
312
|
+
if (result.issues.some((issue) => Object.isFrozen(issue))) {
|
|
313
|
+
for (const issue of result.issues) {
|
|
314
|
+
run.issues.push(movedIssue(issue, [...path, ...issue.path]));
|
|
315
|
+
}
|
|
316
|
+
} else {
|
|
317
|
+
for (const issue of result.issues) {
|
|
318
|
+
issue.path = [...path, ...issue.path];
|
|
319
|
+
run.issues.push(issue);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return REJECTED;
|
|
323
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { isBlueprint } from "../blueprint/is-blueprint.js";
|
|
2
|
+
import { isBlueprintList } from "../blueprint/list.js";
|
|
3
|
+
import { recordFieldOf } from "../blueprint/record-field.js";
|
|
4
|
+
import { isBlueprintVariant, variantOfValue } from "../blueprint/variant.js";
|
|
5
|
+
import { fieldsOf, isPlainObject } from "./parse.js";
|
|
6
|
+
function collectionOf(properties, value) {
|
|
7
|
+
if (!isPlainObject(value))
|
|
8
|
+
return value;
|
|
9
|
+
const kept = {};
|
|
10
|
+
for (const [name, field] of fieldsOf(properties)) {
|
|
11
|
+
if (!Object.hasOwn(value, name))
|
|
12
|
+
continue;
|
|
13
|
+
const item = fieldOf(field, value[name]);
|
|
14
|
+
if (item === undefined)
|
|
15
|
+
continue;
|
|
16
|
+
Object.defineProperty(kept, name, {
|
|
17
|
+
value: item,
|
|
18
|
+
enumerable: true,
|
|
19
|
+
writable: true,
|
|
20
|
+
configurable: true
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return kept;
|
|
24
|
+
}
|
|
25
|
+
function fieldOf(field, value) {
|
|
26
|
+
if (isBlueprintList(field)) {
|
|
27
|
+
return Array.isArray(value) ? value.map((item) => fieldOf(field.item, item)) : value;
|
|
28
|
+
}
|
|
29
|
+
if (isBlueprint(field))
|
|
30
|
+
return collectionOf(field.properties, value);
|
|
31
|
+
if (isBlueprintVariant(field)) {
|
|
32
|
+
const variant = variantOfValue(field, value);
|
|
33
|
+
if (!variant || !isPlainObject(value))
|
|
34
|
+
return value;
|
|
35
|
+
return {
|
|
36
|
+
[field.key]: value[field.key],
|
|
37
|
+
...collectionOf(variant.properties, value)
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const record = recordFieldOf(field);
|
|
41
|
+
if (record) {
|
|
42
|
+
return collectionOf(record.record.blueprint.properties, value);
|
|
43
|
+
}
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
export function stripUndeclared(shape, value) {
|
|
47
|
+
return fieldOf(shape, value);
|
|
48
|
+
}
|
|
@@ -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;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { standardOfClass } from "../standard/standard.js";
|
|
2
|
+
import { Demo } from "../symbols/demo.js";
|
|
3
|
+
import { State } from "../symbols/state.js";
|
|
4
|
+
import { refinePropertyOf } from "./refine.js";
|
|
5
|
+
const parsingDefinitions = new WeakMap;
|
|
6
|
+
export function parsingDefinitionOf(Class) {
|
|
7
|
+
let definition = parsingDefinitions.get(Class);
|
|
8
|
+
if (!definition) {
|
|
9
|
+
definition = Object.create(Class.prototype).createDefinition();
|
|
10
|
+
parsingDefinitions.set(Class, definition);
|
|
11
|
+
}
|
|
12
|
+
return definition;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class AbstractProperty {
|
|
16
|
+
get [State]() {
|
|
17
|
+
return this.definition[State];
|
|
18
|
+
}
|
|
19
|
+
definition;
|
|
20
|
+
value;
|
|
21
|
+
constructor(value) {
|
|
22
|
+
this.definition = this.createDefinition();
|
|
23
|
+
this.value = this.definition.make(value === Demo ? this.definition.type[Demo] : value);
|
|
24
|
+
}
|
|
25
|
+
static demo() {
|
|
26
|
+
return new this(Demo);
|
|
27
|
+
}
|
|
28
|
+
static safeParse(value) {
|
|
29
|
+
return parsingDefinitionOf(this).parse(value);
|
|
30
|
+
}
|
|
31
|
+
static with(options) {
|
|
32
|
+
return refinePropertyOf(this, options);
|
|
33
|
+
}
|
|
34
|
+
static get "~standard"() {
|
|
35
|
+
return standardOfClass(this);
|
|
36
|
+
}
|
|
37
|
+
}
|