@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,542 @@
|
|
|
1
|
+
import Compile from "typebox/compile";
|
|
2
|
+
import { Locale, Settings } from "typebox/system";
|
|
3
|
+
const KEPT_KEYWORDS = new Set([
|
|
4
|
+
"type",
|
|
5
|
+
"required",
|
|
6
|
+
"$ref",
|
|
7
|
+
"$id",
|
|
8
|
+
"$anchor",
|
|
9
|
+
"$dynamicRef",
|
|
10
|
+
"$dynamicAnchor"
|
|
11
|
+
]);
|
|
12
|
+
const SCHEMA_KEYWORDS = new Set([
|
|
13
|
+
"additionalProperties",
|
|
14
|
+
"items",
|
|
15
|
+
"additionalItems",
|
|
16
|
+
"prefixItems",
|
|
17
|
+
"anyOf",
|
|
18
|
+
"oneOf",
|
|
19
|
+
"allOf",
|
|
20
|
+
"contains",
|
|
21
|
+
"propertyNames"
|
|
22
|
+
]);
|
|
23
|
+
const SCHEMA_MAP_KEYWORDS = new Set([
|
|
24
|
+
"properties",
|
|
25
|
+
"patternProperties",
|
|
26
|
+
"dependentSchemas",
|
|
27
|
+
"$defs",
|
|
28
|
+
"definitions"
|
|
29
|
+
]);
|
|
30
|
+
const JSON_TYPES = new Set([
|
|
31
|
+
"null",
|
|
32
|
+
"array",
|
|
33
|
+
"string",
|
|
34
|
+
"number",
|
|
35
|
+
"boolean",
|
|
36
|
+
"object"
|
|
37
|
+
]);
|
|
38
|
+
function typeOfValue(value) {
|
|
39
|
+
const type = value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
|
|
40
|
+
return JSON_TYPES.has(type) ? type : undefined;
|
|
41
|
+
}
|
|
42
|
+
function shapeOf(schema) {
|
|
43
|
+
if (Array.isArray(schema))
|
|
44
|
+
return schema.map(shapeOf);
|
|
45
|
+
if (typeof schema !== "object" || schema === null)
|
|
46
|
+
return schema;
|
|
47
|
+
const shape = {};
|
|
48
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
49
|
+
if (KEPT_KEYWORDS.has(key))
|
|
50
|
+
shape[key] = value;
|
|
51
|
+
else if (SCHEMA_KEYWORDS.has(key))
|
|
52
|
+
shape[key] = shapeOf(value);
|
|
53
|
+
else if (SCHEMA_MAP_KEYWORDS.has(key)) {
|
|
54
|
+
shape[key] = Object.fromEntries(Object.entries(value).map(([name, item]) => [
|
|
55
|
+
name,
|
|
56
|
+
shapeOf(item)
|
|
57
|
+
]));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (!("type" in shape)) {
|
|
61
|
+
const values = "const" in schema ? [schema.const] : schema.enum;
|
|
62
|
+
if (Array.isArray(values)) {
|
|
63
|
+
const types = values.map(typeOfValue);
|
|
64
|
+
if (types.every((type) => type !== undefined)) {
|
|
65
|
+
shape.type = [...new Set(types)];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return shape;
|
|
70
|
+
}
|
|
71
|
+
export function describeError(error) {
|
|
72
|
+
return `${error.instancePath || "/"}: ${error.message}`;
|
|
73
|
+
}
|
|
74
|
+
const INERT_KEYWORDS = new Set([
|
|
75
|
+
"~kind",
|
|
76
|
+
"~optional",
|
|
77
|
+
"~readonly",
|
|
78
|
+
"~unsafe",
|
|
79
|
+
"title",
|
|
80
|
+
"description",
|
|
81
|
+
"default",
|
|
82
|
+
"examples",
|
|
83
|
+
"deprecated",
|
|
84
|
+
"readOnly",
|
|
85
|
+
"writeOnly",
|
|
86
|
+
"$comment"
|
|
87
|
+
]);
|
|
88
|
+
const isNumeric = (value) => typeof value === "number" || typeof value === "bigint";
|
|
89
|
+
const LEAF_KEYWORDS = [
|
|
90
|
+
[
|
|
91
|
+
"type",
|
|
92
|
+
(value) => typeof value === "string" || Array.isArray(value) && value.every((item) => typeof item === "string"),
|
|
93
|
+
(type) => ({ type })
|
|
94
|
+
],
|
|
95
|
+
["minLength", (value) => typeof value === "number", (limit) => ({ limit })],
|
|
96
|
+
["maxLength", (value) => typeof value === "number", (limit) => ({ limit })],
|
|
97
|
+
["format", (value) => typeof value === "string", (format) => ({ format })],
|
|
98
|
+
["pattern", (value) => typeof value === "string", (pattern) => ({ pattern })],
|
|
99
|
+
["exclusiveMinimum", isNumeric, (limit) => ({ comparison: ">", limit })],
|
|
100
|
+
["exclusiveMaximum", isNumeric, (limit) => ({ comparison: "<", limit })],
|
|
101
|
+
["minimum", isNumeric, (limit) => ({ comparison: ">=", limit })],
|
|
102
|
+
["maximum", isNumeric, (limit) => ({ comparison: "<=", limit })],
|
|
103
|
+
["multipleOf", isNumeric, (multipleOf) => ({ multipleOf })],
|
|
104
|
+
["const", () => true, (allowedValue) => ({ allowedValue })],
|
|
105
|
+
["enum", Array.isArray, (allowedValues) => ({ allowedValues })]
|
|
106
|
+
];
|
|
107
|
+
const LEAF_KEYWORD_NAMES = new Set(LEAF_KEYWORDS.map(([keyword]) => keyword));
|
|
108
|
+
function leafChecksOf(schema) {
|
|
109
|
+
if (typeof schema !== "object" || schema === null)
|
|
110
|
+
return;
|
|
111
|
+
for (const key of Reflect.ownKeys(schema)) {
|
|
112
|
+
if (!LEAF_KEYWORD_NAMES.has(key) && !INERT_KEYWORDS.has(key)) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const checks = [];
|
|
117
|
+
for (const [keyword, reads, params] of LEAF_KEYWORDS) {
|
|
118
|
+
if (!Object.hasOwn(schema, keyword))
|
|
119
|
+
continue;
|
|
120
|
+
const value = schema[keyword];
|
|
121
|
+
if (!reads(value))
|
|
122
|
+
return;
|
|
123
|
+
const validator = Compile({ [keyword]: value });
|
|
124
|
+
checks.push({
|
|
125
|
+
keyword,
|
|
126
|
+
params: params(value),
|
|
127
|
+
check: (candidate) => validator.Check(candidate)
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return checks;
|
|
131
|
+
}
|
|
132
|
+
const leafChecks = new WeakMap;
|
|
133
|
+
function addError(sink, keyword, schemaPath, instancePath, params) {
|
|
134
|
+
if (sink.errors.length < sink.max) {
|
|
135
|
+
const error = { keyword, schemaPath, instancePath, params };
|
|
136
|
+
sink.errors.push({ ...error, message: sink.locale(error) });
|
|
137
|
+
}
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
const segmentOf = (key) => key.replaceAll("~", "~0").replaceAll("/", "~1");
|
|
141
|
+
const hasKey = (value, key) => key === "__proto__" || key === "constructor" || key === "prototype" ? Object.hasOwn(value, key) : (key in value);
|
|
142
|
+
const STRUCTURE_KEYWORDS = new Set([
|
|
143
|
+
"required",
|
|
144
|
+
"properties",
|
|
145
|
+
"additionalProperties",
|
|
146
|
+
"items",
|
|
147
|
+
"minItems",
|
|
148
|
+
"maxItems",
|
|
149
|
+
"anyOf"
|
|
150
|
+
]);
|
|
151
|
+
function explainerOf(schema) {
|
|
152
|
+
if (!isRecord(schema))
|
|
153
|
+
return;
|
|
154
|
+
for (const key of Reflect.ownKeys(schema)) {
|
|
155
|
+
if (!LEAF_KEYWORD_NAMES.has(key) && !INERT_KEYWORDS.has(key) && !STRUCTURE_KEYWORDS.has(key)) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
let type;
|
|
160
|
+
const leaves = [];
|
|
161
|
+
for (const [keyword, reads, params] of LEAF_KEYWORDS) {
|
|
162
|
+
if (!Object.hasOwn(schema, keyword))
|
|
163
|
+
continue;
|
|
164
|
+
const value = schema[keyword];
|
|
165
|
+
if (!reads(value))
|
|
166
|
+
return;
|
|
167
|
+
const validator = Compile({ [keyword]: value });
|
|
168
|
+
const leaf = {
|
|
169
|
+
keyword,
|
|
170
|
+
params: params(value),
|
|
171
|
+
check: (candidate) => validator.Check(candidate)
|
|
172
|
+
};
|
|
173
|
+
if (keyword === "type")
|
|
174
|
+
type = leaf;
|
|
175
|
+
else
|
|
176
|
+
leaves.push(leaf);
|
|
177
|
+
}
|
|
178
|
+
let required;
|
|
179
|
+
if (Object.hasOwn(schema, "required")) {
|
|
180
|
+
const value = schema.required;
|
|
181
|
+
if (!Array.isArray(value))
|
|
182
|
+
return;
|
|
183
|
+
if (!value.every((key) => typeof key === "string"))
|
|
184
|
+
return;
|
|
185
|
+
required = value;
|
|
186
|
+
}
|
|
187
|
+
let properties;
|
|
188
|
+
if (Object.hasOwn(schema, "properties")) {
|
|
189
|
+
if (!isRecord(schema.properties))
|
|
190
|
+
return;
|
|
191
|
+
const list = [];
|
|
192
|
+
for (const [key, property] of Object.entries(schema.properties)) {
|
|
193
|
+
const explain = explainerOf(property);
|
|
194
|
+
if (!explain)
|
|
195
|
+
return;
|
|
196
|
+
list.push({
|
|
197
|
+
key,
|
|
198
|
+
segment: segmentOf(key),
|
|
199
|
+
required: required?.includes(key) ?? false,
|
|
200
|
+
explain
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
properties = list;
|
|
204
|
+
}
|
|
205
|
+
let declared;
|
|
206
|
+
if (Object.hasOwn(schema, "additionalProperties")) {
|
|
207
|
+
if (schema.additionalProperties !== false)
|
|
208
|
+
return;
|
|
209
|
+
declared = new Set(properties?.map(({ key }) => key));
|
|
210
|
+
}
|
|
211
|
+
let items;
|
|
212
|
+
if (Object.hasOwn(schema, "items")) {
|
|
213
|
+
items = explainerOf(schema.items);
|
|
214
|
+
if (!items)
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const limits = [];
|
|
218
|
+
for (const keyword of ["minItems", "maxItems"]) {
|
|
219
|
+
if (!Object.hasOwn(schema, keyword))
|
|
220
|
+
continue;
|
|
221
|
+
const limit = schema[keyword];
|
|
222
|
+
if (typeof limit !== "number")
|
|
223
|
+
return;
|
|
224
|
+
const validator = Compile({ [keyword]: limit });
|
|
225
|
+
limits.push({
|
|
226
|
+
keyword,
|
|
227
|
+
params: { limit },
|
|
228
|
+
check: (candidate) => validator.Check(candidate)
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
let anyOf;
|
|
232
|
+
if (Object.hasOwn(schema, "anyOf")) {
|
|
233
|
+
if (!Array.isArray(schema.anyOf))
|
|
234
|
+
return;
|
|
235
|
+
const list = [];
|
|
236
|
+
for (const option of schema.anyOf) {
|
|
237
|
+
const explain = explainerOf(option);
|
|
238
|
+
if (!explain)
|
|
239
|
+
return;
|
|
240
|
+
list.push(explain);
|
|
241
|
+
}
|
|
242
|
+
anyOf = list;
|
|
243
|
+
}
|
|
244
|
+
return (value, schemaPath, instancePath, sink) => {
|
|
245
|
+
if (sink.errors.length >= sink.max)
|
|
246
|
+
return false;
|
|
247
|
+
let accepted = true;
|
|
248
|
+
if (type && !type.check(value)) {
|
|
249
|
+
accepted = addError(sink, "type", schemaPath, instancePath, type.params);
|
|
250
|
+
}
|
|
251
|
+
if (isRecord(value)) {
|
|
252
|
+
if (required) {
|
|
253
|
+
const missing = required.filter((key) => !hasKey(value, key));
|
|
254
|
+
if (missing.length > 0) {
|
|
255
|
+
accepted = addError(sink, "required", schemaPath, instancePath, {
|
|
256
|
+
requiredProperties: missing
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (declared) {
|
|
261
|
+
const undeclared = [];
|
|
262
|
+
for (const key of Object.getOwnPropertyNames(value)) {
|
|
263
|
+
if (declared.has(key))
|
|
264
|
+
continue;
|
|
265
|
+
undeclared.push(key);
|
|
266
|
+
addError(sink, "boolean", `${schemaPath}/additionalProperties`, `${instancePath}/${segmentOf(key)}`, {});
|
|
267
|
+
}
|
|
268
|
+
if (undeclared.length > 0) {
|
|
269
|
+
accepted = addError(sink, "additionalProperties", schemaPath, instancePath, { additionalProperties: undeclared });
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (properties) {
|
|
273
|
+
for (const property of properties) {
|
|
274
|
+
const { key } = property;
|
|
275
|
+
if (!property.required && !sink.exact && value[key] === undefined) {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
if (!hasKey(value, key))
|
|
279
|
+
continue;
|
|
280
|
+
const explained = property.explain(value[key], `${schemaPath}/properties/${property.segment}`, `${instancePath}/${property.segment}`, sink);
|
|
281
|
+
if (!explained)
|
|
282
|
+
accepted = false;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (Array.isArray(value)) {
|
|
287
|
+
if (items) {
|
|
288
|
+
const explain = items;
|
|
289
|
+
value.forEach((item, index) => {
|
|
290
|
+
const explained = explain(item, `${schemaPath}/items`, `${instancePath}/${index}`, sink);
|
|
291
|
+
if (!explained)
|
|
292
|
+
accepted = false;
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
for (const limit of limits) {
|
|
296
|
+
if (limit.check(value))
|
|
297
|
+
continue;
|
|
298
|
+
accepted = addError(sink, limit.keyword, schemaPath, instancePath, limit.params);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
for (const leaf of leaves) {
|
|
302
|
+
if (leaf.check(value))
|
|
303
|
+
continue;
|
|
304
|
+
accepted = addError(sink, leaf.keyword, schemaPath, instancePath, leaf.params);
|
|
305
|
+
}
|
|
306
|
+
if (anyOf) {
|
|
307
|
+
const failed = [];
|
|
308
|
+
let matched = false;
|
|
309
|
+
for (const [index, explain] of anyOf.entries()) {
|
|
310
|
+
const own = { ...sink, errors: [] };
|
|
311
|
+
const explained = explain(value, `${schemaPath}/anyOf/${index}`, instancePath, own);
|
|
312
|
+
if (explained)
|
|
313
|
+
matched = true;
|
|
314
|
+
else
|
|
315
|
+
failed.push(own.errors);
|
|
316
|
+
}
|
|
317
|
+
if (!matched) {
|
|
318
|
+
for (const errors of failed) {
|
|
319
|
+
for (const error of errors) {
|
|
320
|
+
if (sink.errors.length < sink.max)
|
|
321
|
+
sink.errors.push(error);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
accepted = addError(sink, "anyOf", schemaPath, instancePath, {});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return accepted;
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
const explainers = new WeakMap;
|
|
331
|
+
function sinkOf() {
|
|
332
|
+
const settings = Settings.Get();
|
|
333
|
+
const locale = Locale.Get();
|
|
334
|
+
if (typeof settings?.maxErrors !== "number" || typeof locale !== "function") {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
return {
|
|
338
|
+
errors: [],
|
|
339
|
+
max: settings.maxErrors,
|
|
340
|
+
locale,
|
|
341
|
+
exact: settings.exactOptionalPropertyTypes === true
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
345
|
+
function literalOf(variant, key) {
|
|
346
|
+
if (!isRecord(variant) || variant.type !== "object")
|
|
347
|
+
return;
|
|
348
|
+
const { properties, required } = variant;
|
|
349
|
+
if (!isRecord(properties) || !Array.isArray(required))
|
|
350
|
+
return;
|
|
351
|
+
if (!required.includes(key) || !Object.hasOwn(properties, key)) {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
const property = properties[key];
|
|
355
|
+
if (!isRecord(property) || !Object.hasOwn(property, "const"))
|
|
356
|
+
return;
|
|
357
|
+
const value = property.const;
|
|
358
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? { value } : undefined;
|
|
359
|
+
}
|
|
360
|
+
function discriminatedUnionOf(schema) {
|
|
361
|
+
if (!isRecord(schema) || !Array.isArray(schema.anyOf))
|
|
362
|
+
return;
|
|
363
|
+
for (const key of Reflect.ownKeys(schema)) {
|
|
364
|
+
if (key !== "anyOf" && !INERT_KEYWORDS.has(key))
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
const options = schema.anyOf;
|
|
368
|
+
const [first] = options;
|
|
369
|
+
if (options.length < 2 || !isRecord(first) || !isRecord(first.properties)) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
for (const key of Object.keys(first.properties)) {
|
|
373
|
+
const variants = new Map;
|
|
374
|
+
for (const [index, option] of options.entries()) {
|
|
375
|
+
const literal = literalOf(option, key);
|
|
376
|
+
if (!literal || variants.has(literal.value))
|
|
377
|
+
break;
|
|
378
|
+
let validator;
|
|
379
|
+
variants.set(literal.value, {
|
|
380
|
+
index,
|
|
381
|
+
explain: explainerOf(option),
|
|
382
|
+
Errors: (value) => {
|
|
383
|
+
validator ??= Compile(option);
|
|
384
|
+
return validator.Errors(value);
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
if (variants.size !== options.length)
|
|
389
|
+
continue;
|
|
390
|
+
return {
|
|
391
|
+
key,
|
|
392
|
+
segment: segmentOf(key),
|
|
393
|
+
allowedValues: [...variants.keys()],
|
|
394
|
+
variants
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
const discriminatedUnions = new WeakMap;
|
|
400
|
+
function errorsOfVariant(union, value) {
|
|
401
|
+
const variant = union.variants.get(value[union.key]);
|
|
402
|
+
if (variant) {
|
|
403
|
+
const at = `#/anyOf/${variant.index}`;
|
|
404
|
+
const sink = variant.explain && sinkOf();
|
|
405
|
+
if (variant.explain && sink) {
|
|
406
|
+
variant.explain(value, at, "", sink);
|
|
407
|
+
return sink.errors.length > 0 ? sink.errors : undefined;
|
|
408
|
+
}
|
|
409
|
+
const errors = variant.Errors(value);
|
|
410
|
+
if (errors.length === 0)
|
|
411
|
+
return;
|
|
412
|
+
return errors.map((error) => ({
|
|
413
|
+
...error,
|
|
414
|
+
schemaPath: at + error.schemaPath.slice(1)
|
|
415
|
+
}));
|
|
416
|
+
}
|
|
417
|
+
const maxErrors = Settings.Get().maxErrors;
|
|
418
|
+
const locale = Locale.Get();
|
|
419
|
+
if (typeof maxErrors !== "number" || typeof locale !== "function") {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
if (maxErrors < 1)
|
|
423
|
+
return [];
|
|
424
|
+
const error = {
|
|
425
|
+
keyword: "enum",
|
|
426
|
+
schemaPath: "#/anyOf",
|
|
427
|
+
instancePath: `/${union.segment}`,
|
|
428
|
+
params: { allowedValues: union.allowedValues }
|
|
429
|
+
};
|
|
430
|
+
return [{ ...error, message: locale(error) }];
|
|
431
|
+
}
|
|
432
|
+
export function errorsOfSchema(schema, value) {
|
|
433
|
+
let checks = leafChecks.get(schema);
|
|
434
|
+
if (checks === undefined) {
|
|
435
|
+
checks = leafChecksOf(rawTypeOf(schema)) ?? null;
|
|
436
|
+
leafChecks.set(schema, checks);
|
|
437
|
+
}
|
|
438
|
+
const maxErrors = Settings.Get().maxErrors;
|
|
439
|
+
const locale = Locale.Get();
|
|
440
|
+
if (checks !== null && typeof maxErrors === "number" && typeof locale === "function") {
|
|
441
|
+
const errors = [];
|
|
442
|
+
for (const { keyword, params, check } of checks) {
|
|
443
|
+
if (errors.length >= maxErrors)
|
|
444
|
+
break;
|
|
445
|
+
if (check(value))
|
|
446
|
+
continue;
|
|
447
|
+
const error = { keyword, schemaPath: "#", instancePath: "", params };
|
|
448
|
+
errors.push({ ...error, message: locale(error) });
|
|
449
|
+
}
|
|
450
|
+
if (errors.length > 0)
|
|
451
|
+
return errors;
|
|
452
|
+
}
|
|
453
|
+
if (checks === null && isRecord(value)) {
|
|
454
|
+
let union = discriminatedUnions.get(schema);
|
|
455
|
+
if (union === undefined) {
|
|
456
|
+
union = discriminatedUnionOf(rawTypeOf(schema)) ?? null;
|
|
457
|
+
discriminatedUnions.set(schema, union);
|
|
458
|
+
}
|
|
459
|
+
const errors = union && errorsOfVariant(union, value);
|
|
460
|
+
if (errors)
|
|
461
|
+
return errors;
|
|
462
|
+
}
|
|
463
|
+
if (checks === null) {
|
|
464
|
+
let explain = explainers.get(schema);
|
|
465
|
+
if (explain === undefined) {
|
|
466
|
+
explain = explainerOf(rawTypeOf(schema)) ?? null;
|
|
467
|
+
explainers.set(schema, explain);
|
|
468
|
+
}
|
|
469
|
+
const sink = explain && sinkOf();
|
|
470
|
+
if (explain && sink) {
|
|
471
|
+
explain(value, "#", "", sink);
|
|
472
|
+
if (sink.errors.length > 0)
|
|
473
|
+
return sink.errors;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return schema.type.Errors(value);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
class AbstractSchema {
|
|
480
|
+
_type;
|
|
481
|
+
validator;
|
|
482
|
+
shapeValidator;
|
|
483
|
+
constructor(_type) {
|
|
484
|
+
this._type = _type;
|
|
485
|
+
}
|
|
486
|
+
match(value) {
|
|
487
|
+
return this.type.Check(value);
|
|
488
|
+
}
|
|
489
|
+
matchShape(value) {
|
|
490
|
+
this.shapeValidator ??= Compile(shapeOf(this._type));
|
|
491
|
+
return this.shapeValidator.Check(value);
|
|
492
|
+
}
|
|
493
|
+
checks() {
|
|
494
|
+
const validator = this.type;
|
|
495
|
+
const shape = shapeOf(this._type);
|
|
496
|
+
if (shape.type === "string" && Object.keys(shape).length === 1) {
|
|
497
|
+
return {
|
|
498
|
+
match: (value) => validator.Check(value),
|
|
499
|
+
matchShape: (value) => typeof value === "string"
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
return {
|
|
503
|
+
match: (value) => validator.Check(value),
|
|
504
|
+
matchShape: (value) => this.matchShape(value)
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
errors(value) {
|
|
508
|
+
return this.type.Errors(value).map(describeError);
|
|
509
|
+
}
|
|
510
|
+
toString() {
|
|
511
|
+
return JSON.stringify(this._type);
|
|
512
|
+
}
|
|
513
|
+
get type() {
|
|
514
|
+
this.validator ??= Compile(this._type);
|
|
515
|
+
return this.validator;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
export function checksOfSchema(schema) {
|
|
519
|
+
return schema instanceof AbstractSchema ? schema.checks() : {
|
|
520
|
+
match: (value) => schema.match(value),
|
|
521
|
+
matchShape: (value) => schema.matchShape(value)
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
export function rawTypeOf(schema) {
|
|
525
|
+
return schema instanceof AbstractSchema ? schema._type : schema.type.Type();
|
|
526
|
+
}
|
|
527
|
+
export function schemaOf(name, schema) {
|
|
528
|
+
|
|
529
|
+
class Schema extends AbstractSchema {
|
|
530
|
+
constructor(type = schema) {
|
|
531
|
+
super(type);
|
|
532
|
+
}
|
|
533
|
+
static fromString(value) {
|
|
534
|
+
return new Schema(JSON.parse(value));
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
Object.defineProperty(Schema, "name", { value: name });
|
|
538
|
+
Object.defineProperty(Schema, Symbol.for("nodejs.util.inspect.custom"), {
|
|
539
|
+
value: () => `@afora::schema:${name}`
|
|
540
|
+
});
|
|
541
|
+
return Schema;
|
|
542
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const STANDARD_VENDOR = "afora";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { STANDARD_VENDOR } from "./standard-schema.js";
|
|
2
|
+
function standardOf(parse) {
|
|
3
|
+
return Object.freeze({
|
|
4
|
+
version: 1,
|
|
5
|
+
vendor: STANDARD_VENDOR,
|
|
6
|
+
validate: parse
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
export function withStandard(target, parse) {
|
|
10
|
+
return Object.defineProperty(target, "~standard", {
|
|
11
|
+
value: standardOf(parse),
|
|
12
|
+
enumerable: false
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
const cache = new WeakMap;
|
|
16
|
+
export function standardOfClass(Class) {
|
|
17
|
+
let standard = cache.get(Class);
|
|
18
|
+
if (!standard) {
|
|
19
|
+
standard = standardOf((value) => Class.safeParse(value));
|
|
20
|
+
cache.set(Class, standard);
|
|
21
|
+
}
|
|
22
|
+
return standard;
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const Demo = Symbol("demo");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const Kind = Symbol("kind");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const Meta = Symbol("meta");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const State = Symbol("state");
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ExceptionLayers } from "../exception/enums/exception-layers.js";
|
|
2
|
+
import { exceptionOf } from "../exception/exception.js";
|
|
3
|
+
export const InvalidTraceDeclaration = exceptionOf({
|
|
4
|
+
layer: ExceptionLayers.Application,
|
|
5
|
+
name: "InvalidTraceDeclaration",
|
|
6
|
+
message: ({ reason }) => reason
|
|
7
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { ExceptionLayers } from "../exception/enums/exception-layers.js";
|
|
2
|
+
/**
|
|
3
|
+
* What a span carries: names and outcomes only, never an input, an argument,
|
|
4
|
+
* a response or any value of a record.
|
|
5
|
+
*/
|
|
6
|
+
export type TraceAttributes = Readonly<Record<string, string | number | boolean>>;
|
|
7
|
+
/** How a span ended. */
|
|
8
|
+
export type TraceStatus = "ok" | "error";
|
|
9
|
+
/** A span afora started; it ends it once, with the attributes of the outcome. */
|
|
10
|
+
export interface ITraceSpan {
|
|
11
|
+
end(status: TraceStatus, attributes: TraceAttributes): void;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* What traces an application: a span per command operation, use case and
|
|
15
|
+
* repository call made through a use case. `@afora/otel` adapts an
|
|
16
|
+
* OpenTelemetry `Tracer` to it; `memoryTracer()` keeps the spans for tests.
|
|
17
|
+
*/
|
|
18
|
+
export interface ITracer {
|
|
19
|
+
/**
|
|
20
|
+
* Starts a span. `parent` is the span of the call that made this one (the
|
|
21
|
+
* command of a use case, the use case of a repository call), `undefined`
|
|
22
|
+
* for a call that starts a trace.
|
|
23
|
+
*/
|
|
24
|
+
startSpan(name: string, attributes: TraceAttributes, parent: ITraceSpan | undefined): ITraceSpan;
|
|
25
|
+
/**
|
|
26
|
+
* Runs `run` with `span` as the current span, when the tracer has a notion
|
|
27
|
+
* of one (an OpenTelemetry context): what your code and the instrumented
|
|
28
|
+
* drivers start inside the call is nested in its span.
|
|
29
|
+
*/
|
|
30
|
+
withSpan?<Result>(span: ITraceSpan, run: () => Result): Result;
|
|
31
|
+
}
|
|
32
|
+
/** Throws unless `value` can trace an application. */
|
|
33
|
+
export declare function checkTracer(value: unknown): ITracer;
|
|
34
|
+
/**
|
|
35
|
+
* Carries the tracer of an application, when it has a `trace`: on the
|
|
36
|
+
* application and on the use case classes it binds.
|
|
37
|
+
*/
|
|
38
|
+
export declare const ApplicationTracerKey: unique symbol;
|
|
39
|
+
/**
|
|
40
|
+
* The tracer of a call: the one its scope carries, else the one of the
|
|
41
|
+
* application its class was bound by, else the started application's.
|
|
42
|
+
*/
|
|
43
|
+
export declare function resolveTracer(scoped: ITracer | undefined, owner: unknown): ITracer | undefined;
|
|
44
|
+
/** Starts a span; a tracer that throws never breaks the call it traces. */
|
|
45
|
+
export declare function startSpan(tracer: ITracer, name: string, attributes: TraceAttributes, parent: ITraceSpan | undefined): ITraceSpan | undefined;
|
|
46
|
+
/** Runs `run` inside `span`, if the tracer can make it current. */
|
|
47
|
+
export declare function runInSpan<Result>(tracer: ITracer, span: ITraceSpan, run: () => Result): Result;
|
|
48
|
+
/** Ends a span that succeeded. */
|
|
49
|
+
export declare function endSpan(span: ITraceSpan | undefined): void;
|
|
50
|
+
/**
|
|
51
|
+
* Ends a span that failed, with the layer and name of the exception where it
|
|
52
|
+
* was first raised (`layer` and `UnexpectedError` for anything else thrown).
|
|
53
|
+
* Never its message, details or cause: they can hold values.
|
|
54
|
+
*/
|
|
55
|
+
export declare function failSpan(span: ITraceSpan | undefined, error: unknown, layer: ExceptionLayers): void;
|
|
56
|
+
/** A span as `memoryTracer` keeps it. */
|
|
57
|
+
export interface MemorySpan {
|
|
58
|
+
readonly name: string;
|
|
59
|
+
/** Those it started with, then those it ended with. */
|
|
60
|
+
readonly attributes: TraceAttributes;
|
|
61
|
+
readonly parent: MemorySpan | undefined;
|
|
62
|
+
/** `undefined` until it ends. */
|
|
63
|
+
readonly status: TraceStatus | undefined;
|
|
64
|
+
}
|
|
65
|
+
/** A tracer that keeps the spans, for tests. */
|
|
66
|
+
export interface MemoryTracer extends ITracer {
|
|
67
|
+
/** Every span, in the order they started. */
|
|
68
|
+
readonly spans: readonly MemorySpan[];
|
|
69
|
+
clear(): void;
|
|
70
|
+
}
|
|
71
|
+
/** Keeps every span in `spans`, with its parent, for tests. */
|
|
72
|
+
export declare function memoryTracer(): MemoryTracer;
|