@afora/core 0.0.9 → 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 +52 -30
- package/dist/adapters.js +31 -55
- package/dist/core/application/application.d.ts +30 -0
- package/dist/core/application/application.js +1291 -0
- package/dist/core/application/exceptions.js +85 -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.js +398 -0
- package/dist/core/application/start.js +162 -0
- package/dist/core/application/types/application.interface.d.ts +23 -3
- package/dist/core/auth/jwt.js +200 -0
- package/dist/core/auth/types/jwt.interface.d.ts +10 -2
- 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.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.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 +16 -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 +19 -0
- package/dist/core/entity/abstract-entity.js +181 -0
- 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.js +27 -0
- package/dist/core/entity/trust.js +7 -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.js +66 -0
- package/dist/core/output/coerce.js +38 -0
- package/dist/core/output/exceptions.js +22 -0
- package/dist/{index-y280ew0k.js → core/output/http.js} +35 -80
- package/dist/core/output/openapi-sink.d.ts +10 -2
- package/dist/{index-hg1z2s2v.js → core/output/openapi-sink.js} +60 -544
- 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.js +111 -0
- package/dist/core/output/status.js +52 -0
- package/dist/core/output/types/http.interface.d.ts +5 -1
- package/dist/core/output/types/output.interface.d.ts +24 -0
- package/dist/core/parse/issue.js +31 -0
- package/dist/core/parse/parse.d.ts +8 -1
- package/dist/core/parse/parse.js +323 -0
- package/dist/core/parse/strip.js +48 -0
- package/dist/core/property/abstract-property.js +37 -0
- 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/record/abstract-record.d.ts +2 -1
- 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 +5 -0
- package/dist/core/repository/cursor.js +83 -0
- package/dist/core/repository/exceptions.js +72 -0
- package/dist/core/repository/in-memory.js +196 -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.js +138 -0
- package/dist/core/repository/types/query.interface.d.ts +13 -0
- package/dist/core/repository/types/repository.interface.d.ts +20 -8
- package/dist/core/repository/types/schema.interface.d.ts +8 -2
- package/dist/core/repository/upcast.js +179 -0
- package/dist/core/schema/schema.d.ts +9 -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.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.js +56 -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.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 +27 -8
- 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/route.interface.d.ts +25 -5
- package/dist/core/use-case/use-case.d.ts +77 -2
- package/dist/core/use-case/use-case.js +493 -0
- package/dist/experimental.d.ts +5 -0
- package/dist/experimental.js +18 -161
- package/dist/index.d.ts +7 -5
- package/dist/index.js +213 -367
- 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.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.js +73 -0
- package/dist/std/record.d.ts +20 -1
- package/dist/std/record.js +3 -0
- package/dist/std/resource.js +39 -0
- package/dist/std/runtime.js +193 -0
- package/dist/std/stages.js +4 -0
- package/dist/std/use-cases/auth.js +193 -0
- package/dist/std/use-cases/crud.js +261 -0
- package/dist/std/use-cases/index.js +12 -0
- package/dist/std/use-cases/lifecycle.js +97 -0
- package/dist/std/use-cases/shared.js +232 -0
- package/dist/std/use-cases/types.d.ts +5 -1
- package/dist/std/use-cases/types.js +0 -0
- package/dist/testing/http-provider-contract.js +344 -0
- package/dist/testing/index.js +3 -1435
- package/dist/testing/memory-publisher.js +12 -0
- package/dist/testing/provider-contract.d.ts +12 -3
- package/dist/testing/provider-contract.js +1071 -0
- package/package.json +10 -3
- package/dist/index-3aae5jfq.js +0 -2997
- package/dist/index-ny3as79r.js +0 -4567
- package/dist/index-xegv6a1p.js +0 -4291
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { schemaOf } from "../schema/schema.js";
|
|
3
|
+
import { Demo } from "../symbols/demo.js";
|
|
4
|
+
import { AbstractProperty } from "./abstract-property.js";
|
|
5
|
+
import { AbstractDefineProperty, declarePipeline } from "./define-property.js";
|
|
6
|
+
export function propertyOf({
|
|
7
|
+
name,
|
|
8
|
+
schema,
|
|
9
|
+
optional,
|
|
10
|
+
sensitive = false,
|
|
11
|
+
unique = false,
|
|
12
|
+
default: _default,
|
|
13
|
+
preHandle: onPreHandle,
|
|
14
|
+
afterHandle: onAfterHandle
|
|
15
|
+
}) {
|
|
16
|
+
const valueSchema = optional ? Type.Optional(schema) : schema;
|
|
17
|
+
const SchemaConstructor = schemaOf(name, valueSchema);
|
|
18
|
+
const type = {
|
|
19
|
+
[Demo]: _default,
|
|
20
|
+
name,
|
|
21
|
+
schema: new SchemaConstructor,
|
|
22
|
+
optional: optional ?? false,
|
|
23
|
+
sensitive,
|
|
24
|
+
unique
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
class DefineProperty extends AbstractDefineProperty {
|
|
28
|
+
get type() {
|
|
29
|
+
return type;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const steps = DefineProperty.prototype;
|
|
33
|
+
if (onPreHandle)
|
|
34
|
+
steps.preHandle = (value) => onPreHandle(value);
|
|
35
|
+
if (onAfterHandle)
|
|
36
|
+
steps.afterHandle = (value) => onAfterHandle(value);
|
|
37
|
+
declarePipeline(DefineProperty.prototype);
|
|
38
|
+
|
|
39
|
+
class Property extends AbstractProperty {
|
|
40
|
+
static type = type;
|
|
41
|
+
createDefinition() {
|
|
42
|
+
return new DefineProperty;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return Property;
|
|
46
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { rawTypeOf, schemaOf } from "../schema/schema.js";
|
|
2
|
+
import { Demo } from "../symbols/demo.js";
|
|
3
|
+
import { recordPropertyLineage } from "./lineage.js";
|
|
4
|
+
const METADATA_KEYS = [
|
|
5
|
+
"title",
|
|
6
|
+
"description",
|
|
7
|
+
"examples",
|
|
8
|
+
"deprecated",
|
|
9
|
+
"readOnly",
|
|
10
|
+
"writeOnly"
|
|
11
|
+
];
|
|
12
|
+
const LOWER_BOUNDS = {
|
|
13
|
+
min: ["minimum", "minLength"],
|
|
14
|
+
exclusiveMin: ["exclusiveMinimum"]
|
|
15
|
+
};
|
|
16
|
+
const UPPER_BOUNDS = {
|
|
17
|
+
max: ["maximum", "maxLength"],
|
|
18
|
+
exclusiveMax: ["exclusiveMaximum"]
|
|
19
|
+
};
|
|
20
|
+
export function derivePropertyOf(Property, type) {
|
|
21
|
+
const Base = Property;
|
|
22
|
+
|
|
23
|
+
class Derived extends Base {
|
|
24
|
+
static type = type;
|
|
25
|
+
createDefinition() {
|
|
26
|
+
const definition = super.createDefinition();
|
|
27
|
+
Object.defineProperty(definition, "type", {
|
|
28
|
+
value: type,
|
|
29
|
+
configurable: true
|
|
30
|
+
});
|
|
31
|
+
return definition;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
Object.defineProperty(Derived, "name", { value: Property.name });
|
|
35
|
+
return Derived;
|
|
36
|
+
}
|
|
37
|
+
function copyOf(schema) {
|
|
38
|
+
return Object.defineProperties({}, Object.getOwnPropertyDescriptors(schema));
|
|
39
|
+
}
|
|
40
|
+
function addAllOf(schema, constraint) {
|
|
41
|
+
schema.allOf = [...schema.allOf ?? [], constraint];
|
|
42
|
+
}
|
|
43
|
+
function refinedSchemaOf(base, options) {
|
|
44
|
+
const schema = copyOf(base);
|
|
45
|
+
const keyword = (keys) => keys.length === 1 || schema.type !== "string" ? keys[0] : keys[1];
|
|
46
|
+
for (const key of METADATA_KEYS) {
|
|
47
|
+
if (options[key] !== undefined)
|
|
48
|
+
schema[key] = options[key];
|
|
49
|
+
}
|
|
50
|
+
for (const [option, keys] of Object.entries(LOWER_BOUNDS)) {
|
|
51
|
+
const value = options[option];
|
|
52
|
+
if (value === undefined)
|
|
53
|
+
continue;
|
|
54
|
+
const name = keyword(keys);
|
|
55
|
+
const current = schema[name];
|
|
56
|
+
schema[name] = current === undefined ? value : Math.max(current, value);
|
|
57
|
+
}
|
|
58
|
+
for (const [option, keys] of Object.entries(UPPER_BOUNDS)) {
|
|
59
|
+
const value = options[option];
|
|
60
|
+
if (value === undefined)
|
|
61
|
+
continue;
|
|
62
|
+
const name = keyword(keys);
|
|
63
|
+
const current = schema[name];
|
|
64
|
+
schema[name] = current === undefined ? value : Math.min(current, value);
|
|
65
|
+
}
|
|
66
|
+
if (options.multipleOf !== undefined) {
|
|
67
|
+
if (schema.multipleOf === undefined)
|
|
68
|
+
schema.multipleOf = options.multipleOf;
|
|
69
|
+
else
|
|
70
|
+
addAllOf(schema, { multipleOf: options.multipleOf });
|
|
71
|
+
}
|
|
72
|
+
if (options.pattern !== undefined) {
|
|
73
|
+
const pattern = options.pattern instanceof RegExp ? options.pattern.source : options.pattern;
|
|
74
|
+
if (schema.pattern === undefined)
|
|
75
|
+
schema.pattern = pattern;
|
|
76
|
+
else
|
|
77
|
+
addAllOf(schema, { pattern });
|
|
78
|
+
}
|
|
79
|
+
return schema;
|
|
80
|
+
}
|
|
81
|
+
export function refinePropertyOf(Property, options) {
|
|
82
|
+
const base = Property.type;
|
|
83
|
+
const settings = options;
|
|
84
|
+
const schema = refinedSchemaOf(rawTypeOf(base.schema), settings);
|
|
85
|
+
const SchemaConstructor = schemaOf(base.name, schema);
|
|
86
|
+
const examples = settings.examples;
|
|
87
|
+
const type = {
|
|
88
|
+
...base,
|
|
89
|
+
[Demo]: settings.default ?? examples?.[0] ?? base[Demo],
|
|
90
|
+
schema: new SchemaConstructor,
|
|
91
|
+
...settings.fill !== undefined && { fill: settings.fill },
|
|
92
|
+
...typeof settings.renamedFrom === "string" && {
|
|
93
|
+
renamedFrom: settings.renamedFrom
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
const Refined = derivePropertyOf(Property, type);
|
|
97
|
+
recordPropertyLineage(Refined, {
|
|
98
|
+
kind: "refined",
|
|
99
|
+
base: Property,
|
|
100
|
+
options: Object.freeze({ ...settings })
|
|
101
|
+
});
|
|
102
|
+
return Refined;
|
|
103
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BlueprintField, BlueprintInput, BlueprintValue, IBlueprint, IBlueprintCollection } from "../blueprint/types/blueprint.interface.js";
|
|
2
|
+
import { type Complete } from "../parse/parse.js";
|
|
2
3
|
import type { ParseResult } from "../parse/types/parse.interface.js";
|
|
3
4
|
import type { StandardSchemaV1 } from "../standard/standard-schema.js";
|
|
4
5
|
import { Meta } from "../symbols/meta.js";
|
|
@@ -21,7 +22,7 @@ export declare function exposeRecordKeys(prototype: object, keys: readonly strin
|
|
|
21
22
|
* way to `super()`, as an entity does of its id.
|
|
22
23
|
* @internal
|
|
23
24
|
*/
|
|
24
|
-
export declare function vouchFor(keys: readonly string[]): void;
|
|
25
|
+
export declare function vouchFor(keys: readonly string[], complete?: Complete): void;
|
|
25
26
|
/** How `recordDataOf` stores the records a record holds. */
|
|
26
27
|
export interface RecordDataOptions {
|
|
27
28
|
/**
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { isBlueprint } from "../blueprint/is-blueprint.js";
|
|
2
|
+
import { isBlueprintList } from "../blueprint/list.js";
|
|
3
|
+
import {
|
|
4
|
+
isBlueprintVariant,
|
|
5
|
+
variantNameOf,
|
|
6
|
+
variantOfValue
|
|
7
|
+
} from "../blueprint/variant.js";
|
|
8
|
+
import { ExceptionLayers } from "../exception/enums/exception-layers.js";
|
|
9
|
+
import { Exception } from "../exception/exception.js";
|
|
10
|
+
import { collectionParserOf, parseField } from "../parse/parse.js";
|
|
11
|
+
import { standardOfClass } from "../standard/standard.js";
|
|
12
|
+
import { Meta } from "../symbols/meta.js";
|
|
13
|
+
import {
|
|
14
|
+
InvalidRecordValue,
|
|
15
|
+
ReservedRecordKey,
|
|
16
|
+
UndeclaredRecordProperty
|
|
17
|
+
} from "./exceptions.js";
|
|
18
|
+
import { maskCollection } from "./mask.js";
|
|
19
|
+
import { recordNameOf } from "./name.js";
|
|
20
|
+
const values = new WeakMap;
|
|
21
|
+
const metadata = new WeakMap;
|
|
22
|
+
export const RESERVED_RECORD_KEYS = Object.freeze([
|
|
23
|
+
"onSet",
|
|
24
|
+
"onSetAll",
|
|
25
|
+
"set",
|
|
26
|
+
"toJSON",
|
|
27
|
+
"toString",
|
|
28
|
+
"toLocaleString",
|
|
29
|
+
"valueOf",
|
|
30
|
+
"hasOwnProperty",
|
|
31
|
+
"propertyIsEnumerable",
|
|
32
|
+
"isPrototypeOf",
|
|
33
|
+
"__defineGetter__",
|
|
34
|
+
"__defineSetter__",
|
|
35
|
+
"__lookupGetter__",
|
|
36
|
+
"__lookupSetter__",
|
|
37
|
+
"__proto__",
|
|
38
|
+
"constructor"
|
|
39
|
+
]);
|
|
40
|
+
export function exposeRecordKeys(prototype, keys) {
|
|
41
|
+
for (const key of keys) {
|
|
42
|
+
Object.defineProperty(prototype, key, {
|
|
43
|
+
get() {
|
|
44
|
+
return values.get(this)?.[key];
|
|
45
|
+
},
|
|
46
|
+
enumerable: true,
|
|
47
|
+
configurable: true
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const checked = new WeakSet;
|
|
52
|
+
function checkMembers(record, properties) {
|
|
53
|
+
const Class = record.constructor;
|
|
54
|
+
if (checked.has(Class))
|
|
55
|
+
return;
|
|
56
|
+
const keys = Object.keys(properties);
|
|
57
|
+
for (let prototype = Object.getPrototypeOf(record);prototype !== null && !Object.hasOwn(prototype.constructor, "blueprint"); prototype = Object.getPrototypeOf(prototype)) {
|
|
58
|
+
const key = Object.getOwnPropertyNames(prototype).find((name) => keys.includes(name));
|
|
59
|
+
if (key !== undefined) {
|
|
60
|
+
throw new ReservedRecordKey({
|
|
61
|
+
source: recordNameOf(Class),
|
|
62
|
+
details: { key }
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
checked.add(Class);
|
|
67
|
+
}
|
|
68
|
+
let vouched;
|
|
69
|
+
let vouchedComplete;
|
|
70
|
+
export function vouchFor(keys, complete) {
|
|
71
|
+
vouched = keys;
|
|
72
|
+
vouchedComplete = complete;
|
|
73
|
+
}
|
|
74
|
+
const constructions = new WeakMap;
|
|
75
|
+
function constructionOf(record) {
|
|
76
|
+
const Class = record.constructor;
|
|
77
|
+
let construction = constructions.get(Class);
|
|
78
|
+
if (!construction) {
|
|
79
|
+
const { properties } = record[Meta].blueprint;
|
|
80
|
+
checkMembers(record, properties);
|
|
81
|
+
const source = recordNameOf(Class);
|
|
82
|
+
construction = { source, parse: collectionParserOf(properties, source) };
|
|
83
|
+
constructions.set(Class, construction);
|
|
84
|
+
}
|
|
85
|
+
return construction;
|
|
86
|
+
}
|
|
87
|
+
function withIssues(details, issues) {
|
|
88
|
+
return Object.defineProperty(details, "issues", {
|
|
89
|
+
value: Object.freeze([...issues]),
|
|
90
|
+
enumerable: false
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function recordMessageOf(cause, key, source) {
|
|
94
|
+
if (key === "" || cause.layer !== ExceptionLayers.Property) {
|
|
95
|
+
return cause.message;
|
|
96
|
+
}
|
|
97
|
+
if (cause.name === "PropertyRequired") {
|
|
98
|
+
return `Property "${key}" of record "${source}" is required.`;
|
|
99
|
+
}
|
|
100
|
+
const errors = cause.details?.errors;
|
|
101
|
+
if (cause.name === "InvalidPropertyValue" && Array.isArray(errors)) {
|
|
102
|
+
return `Invalid value for "${key}" of record "${source}": ${errors.join("; ")}`;
|
|
103
|
+
}
|
|
104
|
+
return cause.message;
|
|
105
|
+
}
|
|
106
|
+
function exceptionOfIssues(issues, source) {
|
|
107
|
+
const [first] = issues;
|
|
108
|
+
const key = first.path.join(".");
|
|
109
|
+
if (first.cause) {
|
|
110
|
+
return first.cause.propagate({
|
|
111
|
+
layer: ExceptionLayers.Record,
|
|
112
|
+
source,
|
|
113
|
+
message: recordMessageOf(first.cause, key, source),
|
|
114
|
+
details: withIssues({ key }, issues)
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
if (first.code === "undeclared_key") {
|
|
118
|
+
return new UndeclaredRecordProperty({
|
|
119
|
+
source,
|
|
120
|
+
details: withIssues({ key }, issues)
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return new InvalidRecordValue({
|
|
124
|
+
source,
|
|
125
|
+
details: withIssues({ key, errors: [first.message] }, issues)
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
function issuesOfError(error, source) {
|
|
129
|
+
const issues = error?.details?.issues;
|
|
130
|
+
if (error instanceof Exception && issues)
|
|
131
|
+
return issues;
|
|
132
|
+
const cause = Exception.from(error, {
|
|
133
|
+
layer: ExceptionLayers.Record,
|
|
134
|
+
source
|
|
135
|
+
});
|
|
136
|
+
return [
|
|
137
|
+
{
|
|
138
|
+
code: "invalid_value",
|
|
139
|
+
message: cause.message,
|
|
140
|
+
path: [],
|
|
141
|
+
source,
|
|
142
|
+
cause
|
|
143
|
+
}
|
|
144
|
+
];
|
|
145
|
+
}
|
|
146
|
+
let request;
|
|
147
|
+
function exceptionOfRejection(rejection) {
|
|
148
|
+
rejection.exception ??= exceptionOfIssues(rejection.issues, rejection.source);
|
|
149
|
+
return rejection.exception;
|
|
150
|
+
}
|
|
151
|
+
const REJECTION_HANDLER = {
|
|
152
|
+
get: (rejection, key) => Reflect.get(exceptionOfRejection(rejection), key),
|
|
153
|
+
set: (rejection, key, value) => Reflect.set(exceptionOfRejection(rejection), key, value),
|
|
154
|
+
has: (rejection, key) => Reflect.has(exceptionOfRejection(rejection), key),
|
|
155
|
+
getPrototypeOf: (rejection) => Reflect.getPrototypeOf(exceptionOfRejection(rejection)),
|
|
156
|
+
ownKeys: (rejection) => Reflect.ownKeys(exceptionOfRejection(rejection)),
|
|
157
|
+
getOwnPropertyDescriptor: (rejection, key) => {
|
|
158
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(exceptionOfRejection(rejection), key);
|
|
159
|
+
return descriptor && { ...descriptor, configurable: true };
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
function parseRecord(Class, value) {
|
|
163
|
+
if (value instanceof Class)
|
|
164
|
+
return { value };
|
|
165
|
+
const outer = request;
|
|
166
|
+
const own = { Class };
|
|
167
|
+
request = own;
|
|
168
|
+
try {
|
|
169
|
+
return { value: new Class(value) };
|
|
170
|
+
} catch (error) {
|
|
171
|
+
if (own.issues !== undefined && own.rejection === error) {
|
|
172
|
+
return { issues: own.issues };
|
|
173
|
+
}
|
|
174
|
+
return { issues: issuesOfError(error, recordNameOf(Class)) };
|
|
175
|
+
} finally {
|
|
176
|
+
request = outer;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function recordValue(record) {
|
|
180
|
+
return values.get(record);
|
|
181
|
+
}
|
|
182
|
+
export function recordDataOf(record, options) {
|
|
183
|
+
const { properties } = record[Meta].blueprint;
|
|
184
|
+
return dataOfCollection(properties, values.get(record), options);
|
|
185
|
+
}
|
|
186
|
+
function dataOfCollection(properties, value, options) {
|
|
187
|
+
return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
188
|
+
key,
|
|
189
|
+
recordFieldDataOf(properties[key], item, options)
|
|
190
|
+
])));
|
|
191
|
+
}
|
|
192
|
+
export function recordFieldDataOf(field, value, options) {
|
|
193
|
+
if (value === undefined || value === null)
|
|
194
|
+
return value;
|
|
195
|
+
if (isBlueprintVariant(field) && value instanceof AbstractRecord) {
|
|
196
|
+
const name = variantNameOf(field, value);
|
|
197
|
+
if (name !== undefined) {
|
|
198
|
+
const reference = options?.reference?.(value);
|
|
199
|
+
return Object.freeze(reference === undefined ? { [field.key]: name, ...recordDataOf(value, options) } : { [field.key]: name, id: reference });
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (value instanceof AbstractRecord) {
|
|
203
|
+
return options?.reference?.(value) ?? recordDataOf(value, options);
|
|
204
|
+
}
|
|
205
|
+
if (isBlueprintList(field) && Array.isArray(value)) {
|
|
206
|
+
return Object.freeze(value.map((item) => recordFieldDataOf(field.item, item, options)));
|
|
207
|
+
}
|
|
208
|
+
if (isBlueprint(field) && typeof value === "object") {
|
|
209
|
+
return dataOfCollection(field.properties, value, options);
|
|
210
|
+
}
|
|
211
|
+
if (isBlueprintVariant(field)) {
|
|
212
|
+
const variant = variantOfValue(field, value);
|
|
213
|
+
return variant ? dataOfCollection(variant.properties, value, options) : value;
|
|
214
|
+
}
|
|
215
|
+
return value;
|
|
216
|
+
}
|
|
217
|
+
export function storeRecordValue(record, key, value) {
|
|
218
|
+
const source = recordNameOf(record.constructor);
|
|
219
|
+
const { properties } = record[Meta].blueprint;
|
|
220
|
+
if (!Object.hasOwn(properties, key)) {
|
|
221
|
+
throw exceptionOfIssues([
|
|
222
|
+
{
|
|
223
|
+
code: "undeclared_key",
|
|
224
|
+
message: "is not declared",
|
|
225
|
+
path: [key],
|
|
226
|
+
source
|
|
227
|
+
}
|
|
228
|
+
], source);
|
|
229
|
+
}
|
|
230
|
+
const current = values.get(record);
|
|
231
|
+
const previous = current[key];
|
|
232
|
+
const result = parseField(properties[key], value, [key], source);
|
|
233
|
+
if (result.issues)
|
|
234
|
+
throw exceptionOfIssues(result.issues, source);
|
|
235
|
+
const next = result.value;
|
|
236
|
+
const { [key]: _, ...rest } = current;
|
|
237
|
+
values.set(record, Object.freeze(next === undefined ? rest : { ...rest, [key]: next }));
|
|
238
|
+
return { next, previous };
|
|
239
|
+
}
|
|
240
|
+
export const RecordPluginHooks = Symbol("recordPluginHooks");
|
|
241
|
+
export function runRecordEffects(record, key, next, previous) {
|
|
242
|
+
const pluginHooks = record.constructor[RecordPluginHooks];
|
|
243
|
+
if (pluginHooks !== undefined && pluginHooks.length > 0) {
|
|
244
|
+
const name = String(key);
|
|
245
|
+
for (const hook of pluginHooks) {
|
|
246
|
+
if (!hook.keys.includes(name))
|
|
247
|
+
continue;
|
|
248
|
+
hook.onSetAll?.call(record, { key, value: next, previous });
|
|
249
|
+
hook.onSet?.call(record)[name]?.call(record, next, previous);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
const hooks = record;
|
|
253
|
+
hooks.onSetAll({ key, value: next, previous });
|
|
254
|
+
hooks.onSet()[key]?.(next, previous);
|
|
255
|
+
return record;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export class AbstractRecord {
|
|
259
|
+
get [Meta]() {
|
|
260
|
+
const Class = this.constructor;
|
|
261
|
+
let declaration = metadata.get(Class);
|
|
262
|
+
if (!declaration) {
|
|
263
|
+
const { blueprint, options } = Class;
|
|
264
|
+
declaration = Object.freeze({ blueprint, options });
|
|
265
|
+
metadata.set(Class, declaration);
|
|
266
|
+
}
|
|
267
|
+
return declaration;
|
|
268
|
+
}
|
|
269
|
+
constructor(value) {
|
|
270
|
+
let asked;
|
|
271
|
+
if (request !== undefined && request.Class === this.constructor) {
|
|
272
|
+
asked = request;
|
|
273
|
+
request = undefined;
|
|
274
|
+
}
|
|
275
|
+
const made = vouched;
|
|
276
|
+
const complete = vouchedComplete;
|
|
277
|
+
vouched = undefined;
|
|
278
|
+
vouchedComplete = undefined;
|
|
279
|
+
const { source, parse } = constructionOf(this);
|
|
280
|
+
const result = parse(value, made, complete);
|
|
281
|
+
if (result.issues) {
|
|
282
|
+
if (asked === undefined)
|
|
283
|
+
throw exceptionOfIssues(result.issues, source);
|
|
284
|
+
asked.issues = result.issues;
|
|
285
|
+
asked.rejection = new Proxy({ issues: asked.issues, source }, REJECTION_HANDLER);
|
|
286
|
+
throw asked.rejection;
|
|
287
|
+
}
|
|
288
|
+
values.set(this, result.value);
|
|
289
|
+
}
|
|
290
|
+
static safeParse(value) {
|
|
291
|
+
return parseRecord(this, value);
|
|
292
|
+
}
|
|
293
|
+
static get "~standard"() {
|
|
294
|
+
return standardOfClass(this);
|
|
295
|
+
}
|
|
296
|
+
toJSON() {
|
|
297
|
+
return maskCollection(this[Meta].blueprint.properties, recordValue(this), this[Meta].options.sensitive);
|
|
298
|
+
}
|
|
299
|
+
set(key, value) {
|
|
300
|
+
const { next, previous } = storeRecordValue(this, String(key), value);
|
|
301
|
+
return runRecordEffects(this, key, next, previous);
|
|
302
|
+
}
|
|
303
|
+
onSetAll(_change) {}
|
|
304
|
+
onSet() {
|
|
305
|
+
return {};
|
|
306
|
+
}
|
|
307
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ExceptionLayers } from "../exception/enums/exception-layers.js";
|
|
2
|
+
import { exceptionOf } from "../exception/exception.js";
|
|
3
|
+
export const InvalidRecordValue = exceptionOf({
|
|
4
|
+
layer: ExceptionLayers.Record,
|
|
5
|
+
name: "InvalidRecordValue",
|
|
6
|
+
message: ({
|
|
7
|
+
key,
|
|
8
|
+
errors
|
|
9
|
+
}, source) => `Invalid value for ${key ? `"${key}" of ` : ""}record "${source}": ${errors.join("; ")}`
|
|
10
|
+
});
|
|
11
|
+
export const InvalidRecordDeclaration = exceptionOf({
|
|
12
|
+
layer: ExceptionLayers.Record,
|
|
13
|
+
name: "InvalidRecordDeclaration",
|
|
14
|
+
message: (reason, source) => `Invalid declaration of record "${source}": ${reason}`
|
|
15
|
+
});
|
|
16
|
+
export const UndeclaredRecordProperty = exceptionOf({
|
|
17
|
+
layer: ExceptionLayers.Record,
|
|
18
|
+
name: "UndeclaredRecordProperty",
|
|
19
|
+
message: ({ key }, source) => `Property "${key}" is not declared in record "${source}".`
|
|
20
|
+
});
|
|
21
|
+
export const ReservedRecordKey = exceptionOf({
|
|
22
|
+
layer: ExceptionLayers.Record,
|
|
23
|
+
name: "ReservedRecordKey",
|
|
24
|
+
message: ({ key }, source) => `"${key}" is a member of record "${source}" and cannot be a key of its blueprint.`
|
|
25
|
+
});
|
|
26
|
+
export const InvalidRecordPlugin = exceptionOf({
|
|
27
|
+
layer: ExceptionLayers.Record,
|
|
28
|
+
name: "InvalidRecordPlugin",
|
|
29
|
+
message: ({ reason }, source) => `Invalid record plugin "${source}": ${reason}.`
|
|
30
|
+
});
|
|
31
|
+
export const RecordPluginConflict = exceptionOf({
|
|
32
|
+
layer: ExceptionLayers.Record,
|
|
33
|
+
name: "RecordPluginConflict",
|
|
34
|
+
message: ({
|
|
35
|
+
plugin,
|
|
36
|
+
key,
|
|
37
|
+
owner
|
|
38
|
+
}, source) => `Plugin "${plugin}" of record "${source}" adds "${key}", which ${owner} already has.`
|
|
39
|
+
});
|
|
40
|
+
export const UnknownRecordPlugin = exceptionOf({
|
|
41
|
+
layer: ExceptionLayers.Record,
|
|
42
|
+
name: "UnknownRecordPlugin",
|
|
43
|
+
message: ({ name }) => `No record plugin named "${name}" in the catalog.`
|
|
44
|
+
});
|
|
45
|
+
export const IncompatibleRecordPlugin = exceptionOf({
|
|
46
|
+
layer: ExceptionLayers.Record,
|
|
47
|
+
name: "IncompatibleRecordPlugin",
|
|
48
|
+
message: ({ reason }, source) => `Record plugin "${source}" does not satisfy the reference: ${reason}.`
|
|
49
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { isBlueprintList } from "../blueprint/list.js";
|
|
2
|
+
import { isProperty } from "../property/is-property.js";
|
|
3
|
+
export function isMarked(field, mark, { lists = false } = {}) {
|
|
4
|
+
if (lists && isBlueprintList(field))
|
|
5
|
+
return isMarked(field.item, mark, { lists });
|
|
6
|
+
return isProperty(field) && field.type[mark] === true;
|
|
7
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { isBlueprint } from "../blueprint/is-blueprint.js";
|
|
2
|
+
import { isBlueprintList } from "../blueprint/list.js";
|
|
3
|
+
import { isRecordClass, recordFieldOf } from "../blueprint/record-field.js";
|
|
4
|
+
import {
|
|
5
|
+
isBlueprintVariant,
|
|
6
|
+
variantNameOf,
|
|
7
|
+
variantOfValue
|
|
8
|
+
} from "../blueprint/variant.js";
|
|
9
|
+
import { isMarked } from "./marks.js";
|
|
10
|
+
export const MASK = "********";
|
|
11
|
+
function maskAll(value) {
|
|
12
|
+
if (value === undefined || value === null)
|
|
13
|
+
return value;
|
|
14
|
+
if (Array.isArray(value))
|
|
15
|
+
return Object.freeze(value.map(maskAll));
|
|
16
|
+
if (typeof value === "object" && isRecordClass(value.constructor)) {
|
|
17
|
+
return maskAll(value.toJSON());
|
|
18
|
+
}
|
|
19
|
+
if (typeof value === "object") {
|
|
20
|
+
return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, item]) => [key, maskAll(item)])));
|
|
21
|
+
}
|
|
22
|
+
return MASK;
|
|
23
|
+
}
|
|
24
|
+
function maskField(field, value, sensitive) {
|
|
25
|
+
if (value === undefined)
|
|
26
|
+
return value;
|
|
27
|
+
if (isBlueprintVariant(field)) {
|
|
28
|
+
const name = variantNameOf(field, value);
|
|
29
|
+
if (name !== undefined) {
|
|
30
|
+
const json = Object.freeze({
|
|
31
|
+
[field.key]: name,
|
|
32
|
+
...value.toJSON()
|
|
33
|
+
});
|
|
34
|
+
return sensitive ? maskAll(json) : json;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (recordFieldOf(field)) {
|
|
38
|
+
const json = value.toJSON();
|
|
39
|
+
return sensitive ? maskAll(json) : json;
|
|
40
|
+
}
|
|
41
|
+
if (sensitive || isMarked(field, "sensitive"))
|
|
42
|
+
return maskAll(value);
|
|
43
|
+
if (isBlueprintList(field) && Array.isArray(value)) {
|
|
44
|
+
return Object.freeze(value.map((item) => maskField(field.item, item, false)));
|
|
45
|
+
}
|
|
46
|
+
if (isBlueprint(field) && typeof value === "object" && value !== null) {
|
|
47
|
+
return maskCollection(field.properties, value);
|
|
48
|
+
}
|
|
49
|
+
if (isBlueprintVariant(field)) {
|
|
50
|
+
const variant = variantOfValue(field, value);
|
|
51
|
+
return variant ? maskCollection(variant.properties, value) : value;
|
|
52
|
+
}
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
export function maskCollection(properties, value, sensitive = []) {
|
|
56
|
+
return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
57
|
+
key,
|
|
58
|
+
maskField(properties[key], item, sensitive.includes(key))
|
|
59
|
+
])));
|
|
60
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const RECORD_NAME = /^[A-Za-z_$][\w$]*$/;
|
|
2
|
+
export function isRecordName(name) {
|
|
3
|
+
return typeof name === "string" && RECORD_NAME.test(name);
|
|
4
|
+
}
|
|
5
|
+
export function recordNameOf(Class) {
|
|
6
|
+
const { recordName, name } = Class;
|
|
7
|
+
return typeof recordName === "string" ? recordName : String(name);
|
|
8
|
+
}
|
|
9
|
+
const declaredNames = new WeakMap;
|
|
10
|
+
export function declareRecordName(Class, name) {
|
|
11
|
+
declaredNames.set(Class, name);
|
|
12
|
+
}
|
|
13
|
+
export function declaredRecordNameOf(Class) {
|
|
14
|
+
for (let current = Class;typeof current === "function"; current = Object.getPrototypeOf(current)) {
|
|
15
|
+
const own = Object.getOwnPropertyDescriptor(current, "recordName")?.value;
|
|
16
|
+
if (typeof own === "string")
|
|
17
|
+
return own;
|
|
18
|
+
const declared = declaredNames.get(current);
|
|
19
|
+
if (declared !== undefined)
|
|
20
|
+
return declared;
|
|
21
|
+
}
|
|
22
|
+
return;
|
|
23
|
+
}
|