@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
|
@@ -4,7 +4,11 @@ import { Kind } from "../symbols/kind.js";
|
|
|
4
4
|
export type EnvironmentSource = () => Readonly<Record<string, string | undefined>>;
|
|
5
5
|
/** What `environmentOf` takes, after the blueprint. */
|
|
6
6
|
export interface EnvironmentOptions {
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* The variables to read, e.g. `() => Bun.env`.
|
|
9
|
+
*
|
|
10
|
+
* @default `() => process.env`
|
|
11
|
+
*/
|
|
8
12
|
readonly source?: EnvironmentSource;
|
|
9
13
|
}
|
|
10
14
|
/**
|
|
@@ -15,7 +19,11 @@ export interface EnvironmentOptions {
|
|
|
15
19
|
export interface IEnvironment<Properties extends IBlueprintCollection> {
|
|
16
20
|
readonly [Kind]: "environment";
|
|
17
21
|
readonly blueprint: IBlueprint<Properties>;
|
|
18
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* The validated variables, read on the first call and the same after.
|
|
24
|
+
*
|
|
25
|
+
* @throws {@link InvalidEnvironment} when a variable is missing or rejected.
|
|
26
|
+
*/
|
|
19
27
|
load(): Readonly<BlueprintValue<Properties>>;
|
|
20
28
|
}
|
|
21
29
|
/** Any environment, whatever its blueprint. */
|
|
@@ -40,8 +48,23 @@ export declare const InvalidEnvironment: import("../../index.js").DeclaredExcept
|
|
|
40
48
|
* boolean (`"true"`, `"false"`) or a number when its property takes one
|
|
41
49
|
* instead of a string, validates them once and
|
|
42
50
|
* gives the same value after. As a dependency of a repository it reaches the
|
|
43
|
-
* provider as `context.env`; of a use case, the use case gets its value, e.g.
|
|
51
|
+
* provider as `context.env`; of a use case ({@link useCaseOf}), the use case gets its value, e.g.
|
|
44
52
|
* `environmentOf(blueprintOf((ctx) => ({ DATABASE_URL: ctx.url })))`.
|
|
53
|
+
*
|
|
54
|
+
* @throws {TypeError} when `blueprint` is not a blueprint, or is an optional one.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```ts
|
|
58
|
+
* import { blueprintOf, environmentOf } from "@afora/core";
|
|
59
|
+
*
|
|
60
|
+
* const Env = environmentOf(
|
|
61
|
+
* blueprintOf((ctx) => ({ DATABASE_URL: ctx.url, PORT: ctx.integer })),
|
|
62
|
+
* { source: () => ({ DATABASE_URL: "https://db.example.com", PORT: "3000" }) },
|
|
63
|
+
* );
|
|
64
|
+
*
|
|
65
|
+
* Env.load().PORT; // 3000, converted from the string
|
|
66
|
+
* // Env.load() throws InvalidEnvironment when a variable is missing or invalid
|
|
67
|
+
* ```
|
|
45
68
|
*/
|
|
46
69
|
export declare function environmentOf<Properties extends IBlueprintCollection>(blueprint: IBlueprint<Properties, boolean>, { source }?: EnvironmentOptions): IEnvironment<Properties>;
|
|
47
70
|
/** The variables of every environment of `dependencies`, merged in order. */
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { isBlueprint } from "../blueprint/is-blueprint.js";
|
|
2
|
+
import { ExceptionLayers } from "../exception/enums/exception-layers.js";
|
|
3
|
+
import { exceptionOf } from "../exception/exception.js";
|
|
4
|
+
import { isProperty } from "../property/is-property.js";
|
|
5
|
+
import { Kind } from "../symbols/kind.js";
|
|
6
|
+
export const InvalidEnvironment = exceptionOf({
|
|
7
|
+
layer: ExceptionLayers.Environment,
|
|
8
|
+
name: "InvalidEnvironment",
|
|
9
|
+
message: ({ keys }) => `Invalid environment variables: ${keys.join(", ")}.`
|
|
10
|
+
});
|
|
11
|
+
const processEnv = () => globalThis.process?.env ?? {};
|
|
12
|
+
export function environmentOf(blueprint, { source = processEnv } = {}) {
|
|
13
|
+
if (!isBlueprint(blueprint) || blueprint.optional) {
|
|
14
|
+
throw new TypeError("An environment requires a blueprint");
|
|
15
|
+
}
|
|
16
|
+
let value;
|
|
17
|
+
return Object.freeze({
|
|
18
|
+
[Kind]: "environment",
|
|
19
|
+
blueprint,
|
|
20
|
+
load() {
|
|
21
|
+
if (value !== undefined)
|
|
22
|
+
return value;
|
|
23
|
+
value = parseEnvironment(blueprint, source());
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function parseEnvironment(blueprint, variables) {
|
|
29
|
+
const declared = {};
|
|
30
|
+
for (const [key, field] of Object.entries(blueprint.properties)) {
|
|
31
|
+
const variable = variables[key];
|
|
32
|
+
if (variable !== undefined)
|
|
33
|
+
declared[key] = convert(field, variable);
|
|
34
|
+
}
|
|
35
|
+
const parsed = blueprint.safeParse(declared);
|
|
36
|
+
if (!parsed.issues)
|
|
37
|
+
return Object.freeze(parsed.value);
|
|
38
|
+
const issues = parsed.issues;
|
|
39
|
+
throw new InvalidEnvironment({
|
|
40
|
+
source: "environment",
|
|
41
|
+
details: {
|
|
42
|
+
keys: [...new Set(issues.map((issue) => String(issue.path[0] ?? "")))]
|
|
43
|
+
},
|
|
44
|
+
cause: issues.find((issue) => issue.cause)?.cause
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function convert(field, variable) {
|
|
48
|
+
if (!isProperty(field))
|
|
49
|
+
return variable;
|
|
50
|
+
const { schema } = field.type;
|
|
51
|
+
if (schema.matchShape(variable))
|
|
52
|
+
return variable;
|
|
53
|
+
const text = variable.trim();
|
|
54
|
+
if (text === "true" || text === "false") {
|
|
55
|
+
if (schema.matchShape(text === "true"))
|
|
56
|
+
return text === "true";
|
|
57
|
+
}
|
|
58
|
+
const number = text === "" ? Number.NaN : Number(text);
|
|
59
|
+
if (Number.isFinite(number) && schema.matchShape(number))
|
|
60
|
+
return number;
|
|
61
|
+
return variable;
|
|
62
|
+
}
|
|
63
|
+
export function loadEnvironments(dependencies) {
|
|
64
|
+
if (dependencies === undefined)
|
|
65
|
+
return Object.freeze({});
|
|
66
|
+
const list = isEnvironment(dependencies) ? [dependencies] : dependencies;
|
|
67
|
+
return Object.freeze(Object.assign({}, ...list.map((environment) => environment.load())));
|
|
68
|
+
}
|
|
69
|
+
export function isEnvironment(value) {
|
|
70
|
+
return typeof value === "object" && value !== null && value[Kind] === "environment";
|
|
71
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { frozenJson } from "../json.js";
|
|
2
|
+
import { Kind } from "../symbols/kind.js";
|
|
3
|
+
import { InvalidEventPolicy } from "./exceptions.js";
|
|
4
|
+
const described = new WeakMap;
|
|
5
|
+
const factories = new WeakMap;
|
|
6
|
+
function invalid(source, reason) {
|
|
7
|
+
throw new InvalidEventPolicy({ source, details: { reason } });
|
|
8
|
+
}
|
|
9
|
+
function optionsOf(name, given, validate) {
|
|
10
|
+
const options = frozenJson(given ?? {}, "Event policy options", (reason) => invalid(name, reason));
|
|
11
|
+
if (options === null || typeof options !== "object" || Array.isArray(options)) {
|
|
12
|
+
invalid(name, "Event policy options must be an object.");
|
|
13
|
+
}
|
|
14
|
+
const reason = validate?.(options);
|
|
15
|
+
if (reason !== undefined)
|
|
16
|
+
invalid(name, reason);
|
|
17
|
+
return options;
|
|
18
|
+
}
|
|
19
|
+
export function eventPolicyOf(settings) {
|
|
20
|
+
const name = settings?.name;
|
|
21
|
+
if (typeof name !== "string" || !/^[A-Za-z_$][\w$]*$/.test(name)) {
|
|
22
|
+
invalid("eventPolicyOf", "Expected the policy name to be an identifier.");
|
|
23
|
+
}
|
|
24
|
+
if (typeof settings.handle !== "function") {
|
|
25
|
+
invalid(name, "Expected handle to be a function.");
|
|
26
|
+
}
|
|
27
|
+
if (settings.publishers !== undefined && typeof settings.publishers !== "function") {
|
|
28
|
+
invalid(name, "Expected publishers to be a function.");
|
|
29
|
+
}
|
|
30
|
+
const { handle, validate, publishers } = settings;
|
|
31
|
+
const factory = (given) => {
|
|
32
|
+
const options = optionsOf(name, given, validate);
|
|
33
|
+
const handler = (events, info) => handle(events, info, options);
|
|
34
|
+
Object.defineProperty(handler, "name", { value: name });
|
|
35
|
+
described.set(handler, {
|
|
36
|
+
description: Object.freeze({
|
|
37
|
+
kind: "policy",
|
|
38
|
+
name,
|
|
39
|
+
options
|
|
40
|
+
}),
|
|
41
|
+
publishers: Object.freeze([...publishers?.(options) ?? []])
|
|
42
|
+
});
|
|
43
|
+
return handler;
|
|
44
|
+
};
|
|
45
|
+
factories.set(factory, name);
|
|
46
|
+
return factory;
|
|
47
|
+
}
|
|
48
|
+
export function eventPolicyDescriptionOf(handler) {
|
|
49
|
+
return typeof handler === "function" ? described.get(handler)?.description : undefined;
|
|
50
|
+
}
|
|
51
|
+
export function eventPolicyPublishersOf(handler) {
|
|
52
|
+
return typeof handler === "function" ? described.get(handler)?.publishers ?? [] : [];
|
|
53
|
+
}
|
|
54
|
+
export function eventPolicyCatalogOf(...policies) {
|
|
55
|
+
const byName = {};
|
|
56
|
+
for (const factory of policies) {
|
|
57
|
+
const name = factories.get(factory);
|
|
58
|
+
if (name === undefined) {
|
|
59
|
+
invalid("catalog", "A catalog takes factories made by `eventPolicyOf`.");
|
|
60
|
+
}
|
|
61
|
+
if (Object.hasOwn(byName, name)) {
|
|
62
|
+
invalid(name, "The catalog has another event policy with its name.");
|
|
63
|
+
}
|
|
64
|
+
byName[name] = factory;
|
|
65
|
+
}
|
|
66
|
+
return Object.freeze({
|
|
67
|
+
[Kind]: "event-policy-catalog",
|
|
68
|
+
policies: Object.freeze(byName),
|
|
69
|
+
resolve(name, options) {
|
|
70
|
+
const factory = Object.hasOwn(byName, name) ? byName[name] : undefined;
|
|
71
|
+
if (!factory)
|
|
72
|
+
invalid("catalog", `Unknown event policy "${name}".`);
|
|
73
|
+
return factory(options);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ExceptionLayers } from "../exception/enums/exception-layers.js";
|
|
2
|
+
import { exceptionOf } from "../exception/exception.js";
|
|
3
|
+
export const InvalidEventPolicy = exceptionOf({
|
|
4
|
+
layer: ExceptionLayers.Event,
|
|
5
|
+
name: "InvalidEventPolicy",
|
|
6
|
+
message: ({ reason }) => reason
|
|
7
|
+
});
|
|
8
|
+
export const PublisherNotBound = exceptionOf({
|
|
9
|
+
layer: ExceptionLayers.Application,
|
|
10
|
+
name: "PublisherNotBound",
|
|
11
|
+
message: ({ publisher }) => `No publisher "${publisher}" is declared: add it to applicationOf({ publishers }).`
|
|
12
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { CommandEventFailed } from "../command/exceptions.js";
|
|
2
|
+
import { logFailure } from "../logger/logger.js";
|
|
3
|
+
import { eventPolicyOf } from "./event-policy.js";
|
|
4
|
+
const KEYS = ["publisher", "topic", "events", "handle", "onPublishError"];
|
|
5
|
+
const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
|
|
6
|
+
const PLACEHOLDER = /\{(event|command|operation|id)\}/g;
|
|
7
|
+
function validate(options) {
|
|
8
|
+
const given = options;
|
|
9
|
+
for (const key of Object.keys(given)) {
|
|
10
|
+
if (!KEYS.includes(key))
|
|
11
|
+
return `publishEvents has no option "${key}".`;
|
|
12
|
+
}
|
|
13
|
+
const { publisher, topic, events, handle, onPublishError } = given;
|
|
14
|
+
if (typeof publisher !== "string" || !IDENTIFIER.test(publisher)) {
|
|
15
|
+
return "Expected publishEvents.publisher to be the key of a publisher of the application.";
|
|
16
|
+
}
|
|
17
|
+
if (typeof topic !== "string" || topic === "") {
|
|
18
|
+
return 'Expected publishEvents.topic to be a non-empty string, such as "posts/{id}".';
|
|
19
|
+
}
|
|
20
|
+
const unknown = topic.replaceAll(PLACEHOLDER, "").match(/\{[^}]*\}/);
|
|
21
|
+
if (unknown) {
|
|
22
|
+
return `publishEvents.topic has an unknown placeholder ${unknown[0]}: use {event}, {command}, {operation} or {id}.`;
|
|
23
|
+
}
|
|
24
|
+
if (events !== undefined && (!Array.isArray(events) || events.some((name) => typeof name !== "string" || name === ""))) {
|
|
25
|
+
return "Expected publishEvents.events to be a list of event names.";
|
|
26
|
+
}
|
|
27
|
+
if (handle !== undefined && typeof handle !== "boolean") {
|
|
28
|
+
return "Expected publishEvents.handle to be a boolean.";
|
|
29
|
+
}
|
|
30
|
+
if (onPublishError !== undefined && onPublishError !== "log" && onPublishError !== "throw") {
|
|
31
|
+
return 'Expected publishEvents.onPublishError to be "log" or "throw".';
|
|
32
|
+
}
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
function entityJson(event) {
|
|
36
|
+
const text = JSON.stringify(event.entity);
|
|
37
|
+
return text === undefined ? null : JSON.parse(text);
|
|
38
|
+
}
|
|
39
|
+
export const publishEvents = eventPolicyOf({
|
|
40
|
+
name: "publishEvents",
|
|
41
|
+
validate,
|
|
42
|
+
publishers: (options) => [options.publisher],
|
|
43
|
+
handle: async (events, info, options) => {
|
|
44
|
+
const { command, operation, logger } = info;
|
|
45
|
+
const commandName = command.commandName;
|
|
46
|
+
const source = `${commandName}.${operation}`;
|
|
47
|
+
const only = options.events ? new Set(options.events) : undefined;
|
|
48
|
+
const failed = (cause, event, index) => new CommandEventFailed({
|
|
49
|
+
source,
|
|
50
|
+
cause,
|
|
51
|
+
details: {
|
|
52
|
+
operation,
|
|
53
|
+
event: event.name,
|
|
54
|
+
handled: index,
|
|
55
|
+
remaining: events.length - index - 1
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
for (const [index, event] of events.entries()) {
|
|
59
|
+
if (options.handle !== false) {
|
|
60
|
+
try {
|
|
61
|
+
await event.handle({ logger });
|
|
62
|
+
} catch (cause) {
|
|
63
|
+
throw failed(cause, event, index);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (only && !only.has(event.name))
|
|
67
|
+
continue;
|
|
68
|
+
let publisher;
|
|
69
|
+
try {
|
|
70
|
+
publisher = info.publisher(options.publisher);
|
|
71
|
+
} catch (cause) {
|
|
72
|
+
throw failed(cause, event, index);
|
|
73
|
+
}
|
|
74
|
+
const entity = entityJson(event);
|
|
75
|
+
const id = entity?.id;
|
|
76
|
+
const topic = options.topic.replaceAll(PLACEHOLDER, (_, key) => key === "event" ? event.name : key === "command" ? commandName : key === "operation" ? operation : id === undefined ? "" : String(id));
|
|
77
|
+
const published = Object.freeze({
|
|
78
|
+
name: event.name,
|
|
79
|
+
command: commandName,
|
|
80
|
+
operation,
|
|
81
|
+
entity
|
|
82
|
+
});
|
|
83
|
+
try {
|
|
84
|
+
await publisher.publish(topic, published);
|
|
85
|
+
} catch (cause) {
|
|
86
|
+
if (options.onPublishError === "throw")
|
|
87
|
+
throw failed(cause, event, index);
|
|
88
|
+
logFailure(logger, "event-publish-failed", {
|
|
89
|
+
command: commandName,
|
|
90
|
+
operation,
|
|
91
|
+
event: event.name,
|
|
92
|
+
publisher: options.publisher,
|
|
93
|
+
topic
|
|
94
|
+
}, cause);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { startedApplication } from "../application/registry.js";
|
|
2
|
+
import { PublisherNotBound } from "./exceptions.js";
|
|
3
|
+
export const ApplicationPublishersKey = Symbol("afora.application.publishers");
|
|
4
|
+
const boundPublishers = new WeakMap;
|
|
5
|
+
export function bindPublishers(command, publishers) {
|
|
6
|
+
boundPublishers.set(command, publishers);
|
|
7
|
+
}
|
|
8
|
+
export function isEventPublisher(value) {
|
|
9
|
+
return typeof value === "object" && value !== null && typeof value.publish === "function";
|
|
10
|
+
}
|
|
11
|
+
export function resolvePublisher(command, name, source) {
|
|
12
|
+
const publishers = boundPublishers.get(command) ?? startedApplication()?.[ApplicationPublishersKey];
|
|
13
|
+
const publisher = publishers && Object.hasOwn(publishers, name) ? publishers[name] : undefined;
|
|
14
|
+
if (!publisher) {
|
|
15
|
+
throw new PublisherNotBound({ source, details: { publisher: name } });
|
|
16
|
+
}
|
|
17
|
+
return publisher;
|
|
18
|
+
}
|
|
@@ -61,12 +61,18 @@ export interface PublishEventsOptions {
|
|
|
61
61
|
readonly topic: string;
|
|
62
62
|
/** The names of the events to publish; all of them by default. */
|
|
63
63
|
readonly events?: readonly string[];
|
|
64
|
-
/**
|
|
64
|
+
/**
|
|
65
|
+
* Whether each event runs its own handler first, as `"after-commit"` does.
|
|
66
|
+
*
|
|
67
|
+
* @default true
|
|
68
|
+
*/
|
|
65
69
|
readonly handle?: boolean;
|
|
66
70
|
/**
|
|
67
|
-
* What a failed publish does: `"log"`
|
|
71
|
+
* What a failed publish does: `"log"` writes an error to the
|
|
68
72
|
* logger and goes on, since the call already committed; `"throw"` makes
|
|
69
73
|
* the call fail with `CommandEventFailed`.
|
|
74
|
+
*
|
|
75
|
+
* @default "log"
|
|
70
76
|
*/
|
|
71
77
|
readonly onPublishError?: "log" | "throw";
|
|
72
78
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export var ExceptionLayers;
|
|
2
|
+
((ExceptionLayers) => {
|
|
3
|
+
ExceptionLayers["Schema"] = "schema";
|
|
4
|
+
ExceptionLayers["Property"] = "property";
|
|
5
|
+
ExceptionLayers["Blueprint"] = "blueprint";
|
|
6
|
+
ExceptionLayers["Record"] = "record";
|
|
7
|
+
ExceptionLayers["Entity"] = "entity";
|
|
8
|
+
ExceptionLayers["UseCase"] = "use-case";
|
|
9
|
+
ExceptionLayers["Repository"] = "repository";
|
|
10
|
+
ExceptionLayers["Environment"] = "environment";
|
|
11
|
+
ExceptionLayers["Event"] = "event";
|
|
12
|
+
ExceptionLayers["Command"] = "command";
|
|
13
|
+
ExceptionLayers["Application"] = "application";
|
|
14
|
+
ExceptionLayers["Output"] = "output";
|
|
15
|
+
})(ExceptionLayers ||= {});
|
|
@@ -64,5 +64,20 @@ export declare function declaredMessageOf(Declared: DeclaredExceptionClass<never
|
|
|
64
64
|
* Declares an exception of `layer`, e.g.
|
|
65
65
|
* `throw new PropertyRequired({ source: "email" })`. Its `details` are typed
|
|
66
66
|
* by the parameter of `message`, when it is a function.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* import { ExceptionLayers, exceptionOf } from "@afora/core";
|
|
71
|
+
*
|
|
72
|
+
* const UserNotFound = exceptionOf({
|
|
73
|
+
* layer: ExceptionLayers.UseCase,
|
|
74
|
+
* name: "UserNotFound",
|
|
75
|
+
* message: ({ id }: { id: string }) => `There is no user ${id}.`,
|
|
76
|
+
* });
|
|
77
|
+
*
|
|
78
|
+
* const error = new UserNotFound({ source: "FindUser", details: { id: "42" } });
|
|
79
|
+
* error.message; // "There is no user 42."
|
|
80
|
+
* error.layer; // ExceptionLayers.UseCase
|
|
81
|
+
* ```
|
|
67
82
|
*/
|
|
68
83
|
export declare function exceptionOf<Details = undefined>({ layer, name, message, }: ExceptionDeclaration<Details>): DeclaredExceptionClass<Details>;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
export const UNEXPECTED_ERROR = "UnexpectedError";
|
|
2
|
+
|
|
3
|
+
export class Exception extends Error {
|
|
4
|
+
name;
|
|
5
|
+
layer;
|
|
6
|
+
source;
|
|
7
|
+
details;
|
|
8
|
+
cause;
|
|
9
|
+
constructor({
|
|
10
|
+
layer,
|
|
11
|
+
name,
|
|
12
|
+
source,
|
|
13
|
+
message,
|
|
14
|
+
details,
|
|
15
|
+
cause
|
|
16
|
+
}) {
|
|
17
|
+
super(message, cause === undefined ? undefined : { cause });
|
|
18
|
+
this.name = name;
|
|
19
|
+
this.layer = layer;
|
|
20
|
+
this.source = source;
|
|
21
|
+
this.details = details;
|
|
22
|
+
this.cause = cause;
|
|
23
|
+
}
|
|
24
|
+
static from(error, scope) {
|
|
25
|
+
if (error instanceof Exception)
|
|
26
|
+
return error;
|
|
27
|
+
return new Exception({
|
|
28
|
+
...scope,
|
|
29
|
+
name: UNEXPECTED_ERROR,
|
|
30
|
+
message: error instanceof Error ? error.message : String(error),
|
|
31
|
+
cause: error
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
get frame() {
|
|
35
|
+
const { layer, name, source, message, details } = this;
|
|
36
|
+
return { layer, name, source, message, details };
|
|
37
|
+
}
|
|
38
|
+
get trace() {
|
|
39
|
+
return this.chain().map((exception) => exception.frame).reverse();
|
|
40
|
+
}
|
|
41
|
+
get origin() {
|
|
42
|
+
return this.chain().at(-1);
|
|
43
|
+
}
|
|
44
|
+
find(target) {
|
|
45
|
+
return this.chain().find((exception) => typeof target === "string" ? exception.name === target : exception instanceof target);
|
|
46
|
+
}
|
|
47
|
+
is(target) {
|
|
48
|
+
return this.find(target) !== undefined;
|
|
49
|
+
}
|
|
50
|
+
passedThrough(layer) {
|
|
51
|
+
return this.chain().some((exception) => exception.layer === layer);
|
|
52
|
+
}
|
|
53
|
+
propagate({
|
|
54
|
+
layer,
|
|
55
|
+
source,
|
|
56
|
+
name = this.name,
|
|
57
|
+
message = this.message,
|
|
58
|
+
details
|
|
59
|
+
}) {
|
|
60
|
+
const keepsClass = name === this.name && details === undefined && isDeclaredInstance(this);
|
|
61
|
+
const init = {
|
|
62
|
+
layer,
|
|
63
|
+
source,
|
|
64
|
+
name,
|
|
65
|
+
message,
|
|
66
|
+
details: keepsClass ? this.details : details,
|
|
67
|
+
cause: this
|
|
68
|
+
};
|
|
69
|
+
return keepsClass ? Reflect.construct(Exception, [init], this.constructor) : new Exception(init);
|
|
70
|
+
}
|
|
71
|
+
toJSON() {
|
|
72
|
+
return { ...this.frame, trace: this.trace };
|
|
73
|
+
}
|
|
74
|
+
toString() {
|
|
75
|
+
return this.chain().map((exception, index) => `${index === 0 ? "" : " <- "}${exception.name} [${exception.layer} @ ${exception.source}]: ${exception.message}`).join(`
|
|
76
|
+
`);
|
|
77
|
+
}
|
|
78
|
+
chain() {
|
|
79
|
+
const chain = [];
|
|
80
|
+
for (let current = this;current instanceof Exception; current = current.cause) {
|
|
81
|
+
chain.push(current);
|
|
82
|
+
}
|
|
83
|
+
return chain;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const declarations = new WeakMap;
|
|
87
|
+
function isDeclaredInstance(exception) {
|
|
88
|
+
for (let current = exception.constructor;typeof current === "function"; current = Object.getPrototypeOf(current)) {
|
|
89
|
+
if (declarations.has(current))
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
export function isDeclaredException(value) {
|
|
95
|
+
return typeof value === "function" && declarations.has(value);
|
|
96
|
+
}
|
|
97
|
+
export function declaredMessageOf(Declared) {
|
|
98
|
+
const { message } = declarations.get(Declared) ?? {};
|
|
99
|
+
return typeof message === "string" ? message : undefined;
|
|
100
|
+
}
|
|
101
|
+
export function exceptionOf({
|
|
102
|
+
layer,
|
|
103
|
+
name,
|
|
104
|
+
message
|
|
105
|
+
}) {
|
|
106
|
+
|
|
107
|
+
class DeclaredException extends Exception {
|
|
108
|
+
static layer = layer;
|
|
109
|
+
constructor({
|
|
110
|
+
source,
|
|
111
|
+
details,
|
|
112
|
+
cause,
|
|
113
|
+
message: custom
|
|
114
|
+
}) {
|
|
115
|
+
super({
|
|
116
|
+
layer,
|
|
117
|
+
name,
|
|
118
|
+
source,
|
|
119
|
+
details,
|
|
120
|
+
cause,
|
|
121
|
+
message: custom ?? (typeof message === "function" ? message(details, source) : message)
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
Object.defineProperty(DeclaredException, "name", { value: name });
|
|
126
|
+
declarations.set(DeclaredException, { message });
|
|
127
|
+
return DeclaredException;
|
|
128
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as Application from "../application/exceptions.js";
|
|
2
|
+
import * as Blueprint from "../blueprint/exceptions.js";
|
|
3
|
+
import * as Command from "../command/exceptions.js";
|
|
4
|
+
import * as Entity from "../entity/exceptions.js";
|
|
5
|
+
import * as Environment from "../environment/environment.js";
|
|
6
|
+
import * as EventPolicy from "../event-policy/exceptions.js";
|
|
7
|
+
import * as Log from "../logger/exceptions.js";
|
|
8
|
+
import * as OpenAPI from "../openapi/exceptions.js";
|
|
9
|
+
import * as Output from "../output/exceptions.js";
|
|
10
|
+
import * as Property from "../property/exceptions.js";
|
|
11
|
+
import * as RecordKeys from "../record/exceptions.js";
|
|
12
|
+
import * as Repository from "../repository/exceptions.js";
|
|
13
|
+
import * as Tracing from "../tracing/exceptions.js";
|
|
14
|
+
import * as UseCaseSteps from "../use-case/exceptions.js";
|
|
15
|
+
import * as UseCase from "../use-case/use-case.js";
|
|
16
|
+
import { isDeclaredException } from "./exception.js";
|
|
17
|
+
const exported = new WeakSet;
|
|
18
|
+
let collected = false;
|
|
19
|
+
export function markExported(...classes) {
|
|
20
|
+
for (const Declared of classes) {
|
|
21
|
+
if (isDeclaredException(Declared))
|
|
22
|
+
exported.add(Declared);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function isExportedException(Declared) {
|
|
26
|
+
if (!collected) {
|
|
27
|
+
collected = true;
|
|
28
|
+
for (const module of [
|
|
29
|
+
Application,
|
|
30
|
+
Blueprint,
|
|
31
|
+
Command,
|
|
32
|
+
Entity,
|
|
33
|
+
Environment,
|
|
34
|
+
EventPolicy,
|
|
35
|
+
Log,
|
|
36
|
+
OpenAPI,
|
|
37
|
+
Output,
|
|
38
|
+
Property,
|
|
39
|
+
RecordKeys,
|
|
40
|
+
Repository,
|
|
41
|
+
Tracing,
|
|
42
|
+
UseCase,
|
|
43
|
+
UseCaseSteps
|
|
44
|
+
]) {
|
|
45
|
+
markExported(...Object.values(module));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return typeof Declared === "function" && exported.has(Declared);
|
|
49
|
+
}
|
|
@@ -1,8 +1,27 @@
|
|
|
1
1
|
import { Exception } from "./exception.js";
|
|
2
2
|
import type { ExceptionScope } from "./types/exception.interface.js";
|
|
3
3
|
/**
|
|
4
|
-
* Handles what `run` throws, or rejects with, in `scope
|
|
4
|
+
* Handles what `run` throws, or rejects with, in `scope` (see {@link ExceptionScope}). Without `resolve`,
|
|
5
5
|
* or when it calls `pass`, the exception goes up to the next layer with its
|
|
6
6
|
* trace; otherwise `resolve` returns the value to use instead.
|
|
7
|
+
*
|
|
8
|
+
* @throws {@link Exception} what `run` throws or rejects with, in `scope` and with its trace, unless `resolve` gives a value instead.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { ExceptionLayers, guard } from "@afora/core";
|
|
13
|
+
*
|
|
14
|
+
* const scope = { layer: ExceptionLayers.UseCase, source: "LoadConfig" };
|
|
15
|
+
*
|
|
16
|
+
* // Without `resolve`, what `run` throws goes up to the next layer with its trace
|
|
17
|
+
* guard(scope, () => JSON.parse('{"port":3000}'));
|
|
18
|
+
*
|
|
19
|
+
* // With `resolve`, it can give a value instead, or call `pass` to go up
|
|
20
|
+
* const config = guard(
|
|
21
|
+
* scope,
|
|
22
|
+
* () => JSON.parse("{") as { port: number },
|
|
23
|
+
* () => ({ port: 8080 }),
|
|
24
|
+
* );
|
|
25
|
+
* ```
|
|
7
26
|
*/
|
|
8
27
|
export declare function guard<Result>(scope: ExceptionScope, run: () => Result, resolve?: (exception: Exception, pass: () => never) => Result | Awaited<Result>): Result;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Exception } from "./exception.js";
|
|
2
|
+
export function guard(scope, run, resolve) {
|
|
3
|
+
const handle = (error) => {
|
|
4
|
+
const exception = Exception.from(error, scope);
|
|
5
|
+
const pass = () => {
|
|
6
|
+
throw exception === error ? exception.propagate(scope) : exception;
|
|
7
|
+
};
|
|
8
|
+
return resolve ? resolve(exception, pass) : pass();
|
|
9
|
+
};
|
|
10
|
+
try {
|
|
11
|
+
const result = run();
|
|
12
|
+
return result instanceof Promise ? result.catch(handle) : result;
|
|
13
|
+
} catch (error) {
|
|
14
|
+
return handle(error);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -11,6 +11,10 @@ export interface ExceptionFrame<Details = unknown> extends ExceptionScope {
|
|
|
11
11
|
readonly message: string;
|
|
12
12
|
readonly details: Details;
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* What the {@link Exception} constructor takes: the scope (`layer` and
|
|
16
|
+
* `source`), the `name` and `message`, optional `details` and the `cause`.
|
|
17
|
+
*/
|
|
14
18
|
export interface ExceptionInit<Details = unknown> extends ExceptionScope {
|
|
15
19
|
readonly name: string;
|
|
16
20
|
readonly message: string;
|
|
@@ -26,7 +30,16 @@ export interface ExceptionPropagation<Details = unknown> extends ExceptionScope
|
|
|
26
30
|
}
|
|
27
31
|
/** An exception class, abstract or declared with `exceptionOf`. */
|
|
28
32
|
export type ExceptionClass<Instance = any> = abstract new (...args: never[]) => Instance;
|
|
33
|
+
/**
|
|
34
|
+
* The `message` of an exception declared with {@link exceptionOf}: a string, or
|
|
35
|
+
* a function of the `details` and the `source` that gives it.
|
|
36
|
+
*/
|
|
29
37
|
export type ExceptionMessage<Details> = string | ((details: Details, source: string) => string);
|
|
38
|
+
/**
|
|
39
|
+
* What {@link exceptionOf} takes: the `layer` of the exception, its `name` and
|
|
40
|
+
* its `message`. `Details` is the type of the `details` it carries, taken from
|
|
41
|
+
* the parameter of `message` when that is a function.
|
|
42
|
+
*/
|
|
30
43
|
export interface ExceptionDeclaration<Details> {
|
|
31
44
|
readonly layer: ExceptionLayers;
|
|
32
45
|
readonly name: string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function frozenJson(value, subject, fail, seen = new Set) {
|
|
2
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
3
|
+
return value;
|
|
4
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
5
|
+
return value;
|
|
6
|
+
if (typeof value !== "object" || value === null || seen.has(value))
|
|
7
|
+
return fail(`${subject} must be finite, acyclic JSON values.`);
|
|
8
|
+
if (!Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)
|
|
9
|
+
return fail(`${subject} must contain only plain objects and arrays.`);
|
|
10
|
+
if (Object.getOwnPropertySymbols(value).length > 0)
|
|
11
|
+
return fail(`${subject} cannot contain symbol keys.`);
|
|
12
|
+
seen.add(value);
|
|
13
|
+
const result = Array.isArray(value) ? value.map((item) => frozenJson(item, subject, fail, seen)) : Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
14
|
+
key,
|
|
15
|
+
frozenJson(item, subject, fail, seen)
|
|
16
|
+
]));
|
|
17
|
+
seen.delete(value);
|
|
18
|
+
return Object.freeze(result);
|
|
19
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ExceptionLayers } from "../exception/enums/exception-layers.js";
|
|
2
|
+
import { exceptionOf } from "../exception/exception.js";
|
|
3
|
+
export const InvalidLogDeclaration = exceptionOf({
|
|
4
|
+
layer: ExceptionLayers.Application,
|
|
5
|
+
name: "InvalidLogDeclaration",
|
|
6
|
+
message: ({ reason }) => reason
|
|
7
|
+
});
|