@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,368 @@
|
|
|
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
|
+
blueprintOfVariant,
|
|
6
|
+
isBlueprintVariant
|
|
7
|
+
} from "../../blueprint/variant.js";
|
|
8
|
+
import { isEnvironment } from "../../environment/environment.js";
|
|
9
|
+
import { isDeclaredException } from "../../exception/exception.js";
|
|
10
|
+
import { isRepository } from "../../repository/repository.js";
|
|
11
|
+
import { Kind } from "../../symbols/kind.js";
|
|
12
|
+
import { InvalidUseCaseStep } from "../exceptions.js";
|
|
13
|
+
import {
|
|
14
|
+
isPlain,
|
|
15
|
+
isStepName,
|
|
16
|
+
parseReference,
|
|
17
|
+
RESERVED_NAMES,
|
|
18
|
+
walkValue
|
|
19
|
+
} from "./references.js";
|
|
20
|
+
const SHAPES = {
|
|
21
|
+
find: { required: ["find"], optional: ["args", "as"] },
|
|
22
|
+
ensure: {
|
|
23
|
+
required: ["ensure", "value", "error"],
|
|
24
|
+
optional: ["other", "details"]
|
|
25
|
+
},
|
|
26
|
+
build: { required: ["build", "values", "as"], optional: [] },
|
|
27
|
+
change: {
|
|
28
|
+
required: ["change", "key", "value"],
|
|
29
|
+
optional: ["ifPresent", "as"]
|
|
30
|
+
},
|
|
31
|
+
invoke: { required: ["invoke", "method"], optional: ["args", "as"] },
|
|
32
|
+
create: { required: ["create", "value"], optional: ["as"] },
|
|
33
|
+
update: { required: ["update", "value"], optional: ["as"] },
|
|
34
|
+
delete: { required: ["delete", "value"], optional: ["as"] },
|
|
35
|
+
restore: { required: ["restore", "value"], optional: ["as"] },
|
|
36
|
+
call: { required: ["call", "input"], optional: ["as"] },
|
|
37
|
+
run: { required: ["run", "fn"], optional: ["args", "params", "as"] },
|
|
38
|
+
return: { required: ["return"], optional: [] }
|
|
39
|
+
};
|
|
40
|
+
const KINDS = Object.keys(SHAPES);
|
|
41
|
+
const ENSURE_KINDS = new Set([
|
|
42
|
+
"exists",
|
|
43
|
+
"absent",
|
|
44
|
+
"equals",
|
|
45
|
+
"notEquals",
|
|
46
|
+
"true",
|
|
47
|
+
"false"
|
|
48
|
+
]);
|
|
49
|
+
const READ_ACTION = /^(?:findBy.+|findManyBy.+|findMany|exists|findPage|findCursor|count)$/;
|
|
50
|
+
const WRITE_ACTIONS = new Set([
|
|
51
|
+
"create",
|
|
52
|
+
"update",
|
|
53
|
+
"delete",
|
|
54
|
+
"restore",
|
|
55
|
+
"increment"
|
|
56
|
+
]);
|
|
57
|
+
const INDEX = /^(?:0|[1-9][0-9]*)$/;
|
|
58
|
+
const LENGTH = Symbol("length");
|
|
59
|
+
const PRIMITIVE_SCHEMA_TYPES = new Set([
|
|
60
|
+
"string",
|
|
61
|
+
"number",
|
|
62
|
+
"integer",
|
|
63
|
+
"boolean",
|
|
64
|
+
"null"
|
|
65
|
+
]);
|
|
66
|
+
function isPrimitiveProperty(field) {
|
|
67
|
+
const schema = field?.type?.schema;
|
|
68
|
+
if (typeof field !== "function" || schema === undefined)
|
|
69
|
+
return false;
|
|
70
|
+
try {
|
|
71
|
+
const type = JSON.parse(schema.toString()).type;
|
|
72
|
+
return typeof type === "string" && PRIMITIVE_SCHEMA_TYPES.has(type);
|
|
73
|
+
} catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function missingSegment(field, path) {
|
|
78
|
+
let current = field;
|
|
79
|
+
for (const segment of path) {
|
|
80
|
+
if (current === LENGTH)
|
|
81
|
+
return segment;
|
|
82
|
+
if (isBlueprint(current)) {
|
|
83
|
+
if (!Object.hasOwn(current.properties, segment))
|
|
84
|
+
return segment;
|
|
85
|
+
current = current.properties[segment];
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (isBlueprintList(current)) {
|
|
89
|
+
if (segment === "length") {
|
|
90
|
+
current = LENGTH;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (!INDEX.test(segment))
|
|
94
|
+
return segment;
|
|
95
|
+
current = current.item;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (isBlueprintVariant(current)) {
|
|
99
|
+
if (segment === current.key)
|
|
100
|
+
return;
|
|
101
|
+
const variants = current.variants;
|
|
102
|
+
const holder = Object.values(variants).map(blueprintOfVariant).find((variant) => Object.hasOwn(variant.properties, segment));
|
|
103
|
+
if (!holder)
|
|
104
|
+
return segment;
|
|
105
|
+
current = holder.properties[segment];
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const record = recordFieldOf(current)?.record;
|
|
109
|
+
if (record) {
|
|
110
|
+
const { properties } = record.blueprint;
|
|
111
|
+
if (Object.hasOwn(properties, segment)) {
|
|
112
|
+
current = properties[segment];
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
return segment in record.prototype ? undefined : segment;
|
|
116
|
+
}
|
|
117
|
+
return isPrimitiveProperty(current) ? segment : undefined;
|
|
118
|
+
}
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
export function validateSteps(declaration, steps) {
|
|
122
|
+
const { name } = declaration;
|
|
123
|
+
const fail = (index, code, reason) => {
|
|
124
|
+
throw new InvalidUseCaseStep({
|
|
125
|
+
source: name,
|
|
126
|
+
details: { index, reason, code }
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
if (!Array.isArray(steps) || steps.length === 0) {
|
|
130
|
+
fail(0, "missing_return", "Expected steps to be a list ending in return.");
|
|
131
|
+
}
|
|
132
|
+
const list = steps;
|
|
133
|
+
const bound = new Map;
|
|
134
|
+
const runs = new Set;
|
|
135
|
+
let returned = false;
|
|
136
|
+
const useCaseDependency = (key) => typeof key === "string" && Object.hasOwn(declaration.dependencies, key) ? declaration.dependencies[key] : undefined;
|
|
137
|
+
const checkReference = (index, text) => {
|
|
138
|
+
const reference = parseReference(text);
|
|
139
|
+
if (!reference) {
|
|
140
|
+
fail(index, "invalid_reference", `"${text}" is not a valid reference.`);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const { root, path } = reference;
|
|
144
|
+
const firstKey = path[0];
|
|
145
|
+
if (root === "input" || root === "context") {
|
|
146
|
+
const blueprint = root === "input" ? declaration.blueprint : declaration.context;
|
|
147
|
+
if (blueprint === undefined) {
|
|
148
|
+
fail(index, "invalid_reference", `"${text}" uses $context, but the use case declares no context.`);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const missing = missingSegment(blueprint, path);
|
|
152
|
+
if (missing !== undefined) {
|
|
153
|
+
fail(index, "unknown_key", `"${text}" names "${missing}", which the ${root} does not declare.`);
|
|
154
|
+
}
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (root === "env") {
|
|
158
|
+
if (firstKey === undefined || !isEnvironment(useCaseDependency(firstKey))) {
|
|
159
|
+
fail(index, "unknown_dependency", `"${text}" must name an environment dependency.`);
|
|
160
|
+
}
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (RESERVED_NAMES.includes(root)) {
|
|
164
|
+
fail(index, "invalid_reference", `"${root}" is reserved.`);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (!bound.has(root)) {
|
|
168
|
+
fail(index, "unbound_reference", `"${text}" uses "${root}", which no earlier step binds.`);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
const checkValue = (index, value, field) => {
|
|
172
|
+
walkValue(value, (_reference, text) => checkReference(index, text), (issue, text) => fail(index, issue, issue === "invalid_reference" ? `"${text}" is not a valid reference.` : `${field} must hold only JSON values and references.`));
|
|
173
|
+
};
|
|
174
|
+
const checkArgs = (index, args) => {
|
|
175
|
+
if (args === undefined)
|
|
176
|
+
return;
|
|
177
|
+
if (typeof args === "string" && args.startsWith("$")) {
|
|
178
|
+
checkReference(index, args);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (!Array.isArray(args)) {
|
|
182
|
+
fail(index, "invalid_step", "args must be a list of values or a reference to one.");
|
|
183
|
+
}
|
|
184
|
+
checkValue(index, args, "args");
|
|
185
|
+
};
|
|
186
|
+
const checkTarget = (index, target, kind) => {
|
|
187
|
+
if (typeof target !== "string" || !target.startsWith("$")) {
|
|
188
|
+
fail(index, "invalid_reference", `${kind} must be a reference.`);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
checkReference(index, target);
|
|
192
|
+
};
|
|
193
|
+
const repositoryOf = (index, key, action) => {
|
|
194
|
+
const dependency = useCaseDependency(key);
|
|
195
|
+
if (!isRepository(dependency)) {
|
|
196
|
+
return fail(index, "unknown_dependency", `"${String(key)}" is not a repository dependency.`);
|
|
197
|
+
}
|
|
198
|
+
const actions = dependency.actions;
|
|
199
|
+
if (!actions.includes(action)) {
|
|
200
|
+
fail(index, "unknown_action", `The repository "${String(key)}" has no "${action}" action.`);
|
|
201
|
+
}
|
|
202
|
+
return { actions };
|
|
203
|
+
};
|
|
204
|
+
for (const [index, step] of list.entries()) {
|
|
205
|
+
if (!isPlain(step) || Array.isArray(step)) {
|
|
206
|
+
fail(index, "unknown_step", "A step must be an object.");
|
|
207
|
+
}
|
|
208
|
+
const record = step;
|
|
209
|
+
const kinds = KINDS.filter((kind) => Object.hasOwn(record, kind));
|
|
210
|
+
if (kinds.length !== 1) {
|
|
211
|
+
fail(index, "unknown_step", kinds.length === 0 ? `Unknown step; expected one of ${KINDS.join(", ")}.` : `A step has one kind, found ${kinds.join(" and ")}.`);
|
|
212
|
+
}
|
|
213
|
+
const kind = kinds[0];
|
|
214
|
+
const shape = SHAPES[kind];
|
|
215
|
+
for (const key of shape.required) {
|
|
216
|
+
if (!Object.hasOwn(record, key) || record[key] === undefined) {
|
|
217
|
+
fail(index, "invalid_step", `A ${kind} step needs "${key}".`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
for (const key of Object.keys(record)) {
|
|
221
|
+
if (!shape.required.includes(key) && !shape.optional.includes(key)) {
|
|
222
|
+
fail(index, "invalid_step", `A ${kind} step has no "${key}".`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const as = record.as;
|
|
226
|
+
if (as !== undefined) {
|
|
227
|
+
if (!isStepName(as) || RESERVED_NAMES.includes(as) || bound.has(as)) {
|
|
228
|
+
fail(index, "invalid_step_name", `"${String(as)}" must be an identifier, not reserved, bound once.`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
let knownRecord;
|
|
232
|
+
switch (kind) {
|
|
233
|
+
case "find": {
|
|
234
|
+
const target = record.find;
|
|
235
|
+
const parts = typeof target === "string" ? target.split(".") : [];
|
|
236
|
+
const [repository, action] = parts;
|
|
237
|
+
if (parts.length !== 2 || !repository || !action) {
|
|
238
|
+
fail(index, "invalid_step", 'find must be "<repository>.<action>".');
|
|
239
|
+
}
|
|
240
|
+
if (WRITE_ACTIONS.has(action)) {
|
|
241
|
+
fail(index, "write_in_find", `find cannot call "${action}".`);
|
|
242
|
+
}
|
|
243
|
+
if (action === "forEachPage") {
|
|
244
|
+
fail(index, "unknown_action", '"forEachPage" runs a function on each page and cannot be a step; call it from a use case written in TypeScript.');
|
|
245
|
+
}
|
|
246
|
+
if (!READ_ACTION.test(action)) {
|
|
247
|
+
fail(index, "unknown_action", `"${action}" is not a read action.`);
|
|
248
|
+
}
|
|
249
|
+
repositoryOf(index, repository, action);
|
|
250
|
+
checkArgs(index, record.args);
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
case "ensure": {
|
|
254
|
+
if (!ENSURE_KINDS.has(record.ensure)) {
|
|
255
|
+
fail(index, "invalid_step", `ensure must be one of ${[...ENSURE_KINDS].join(", ")}.`);
|
|
256
|
+
}
|
|
257
|
+
const compares = record.ensure === "equals" || record.ensure === "notEquals";
|
|
258
|
+
if (compares && record.other === undefined) {
|
|
259
|
+
fail(index, "invalid_step", `ensure "${record.ensure}" needs other.`);
|
|
260
|
+
}
|
|
261
|
+
if (!compares && record.other !== undefined) {
|
|
262
|
+
fail(index, "invalid_step", `ensure "${String(record.ensure)}" has no other.`);
|
|
263
|
+
}
|
|
264
|
+
if (!isDeclaredException(record.error) || !declaration.errors.includes(record.error)) {
|
|
265
|
+
fail(index, "undeclared_error", "ensure.error must be a class made by exceptionOf, listed in errors.");
|
|
266
|
+
}
|
|
267
|
+
checkValue(index, record.value, "value");
|
|
268
|
+
if (record.other !== undefined)
|
|
269
|
+
checkValue(index, record.other, "other");
|
|
270
|
+
if (record.details !== undefined) {
|
|
271
|
+
checkValue(index, record.details, "details");
|
|
272
|
+
}
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
case "build": {
|
|
276
|
+
if (!isRecordClass(record.build)) {
|
|
277
|
+
fail(index, "invalid_class", "build must be a record or entity class.");
|
|
278
|
+
}
|
|
279
|
+
const values = record.values;
|
|
280
|
+
if (!(typeof values === "string" && values.startsWith("$")) && !isPlain(values)) {
|
|
281
|
+
fail(index, "invalid_step", "build.values must be a mapping or a reference.");
|
|
282
|
+
}
|
|
283
|
+
checkValue(index, values, "values");
|
|
284
|
+
knownRecord = record.build;
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
case "change": {
|
|
288
|
+
checkTarget(index, record.change, "change");
|
|
289
|
+
if (typeof record.key !== "string" || record.key === "") {
|
|
290
|
+
fail(index, "invalid_step", "change.key must be a string.");
|
|
291
|
+
}
|
|
292
|
+
if (record.ifPresent !== undefined && typeof record.ifPresent !== "boolean") {
|
|
293
|
+
fail(index, "invalid_step", "change.ifPresent must be a boolean.");
|
|
294
|
+
}
|
|
295
|
+
const reference = parseReference(record.change);
|
|
296
|
+
const known = reference && reference.path.length === 0 ? bound.get(reference.root)?.record : undefined;
|
|
297
|
+
if (known) {
|
|
298
|
+
const keys = known.options?.change ?? [];
|
|
299
|
+
if (!keys.includes(record.key)) {
|
|
300
|
+
fail(index, "invalid_change_key", `"${String(record.key)}" is not a change key of ${known.name}.`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
checkValue(index, record.value, "value");
|
|
304
|
+
knownRecord = known;
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
case "invoke": {
|
|
308
|
+
checkTarget(index, record.invoke, "invoke");
|
|
309
|
+
if (typeof record.method !== "string" || record.method === "") {
|
|
310
|
+
fail(index, "invalid_step", "invoke.method must be a string.");
|
|
311
|
+
}
|
|
312
|
+
checkArgs(index, record.args);
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
case "create":
|
|
316
|
+
case "update":
|
|
317
|
+
case "delete":
|
|
318
|
+
case "restore": {
|
|
319
|
+
repositoryOf(index, record[kind], kind);
|
|
320
|
+
checkValue(index, record.value, "value");
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
case "call": {
|
|
324
|
+
const dependency = useCaseDependency(record.call);
|
|
325
|
+
if (typeof dependency !== "function" || dependency[Kind] !== "use-case") {
|
|
326
|
+
fail(index, "unknown_dependency", `"${String(record.call)}" is not a use case dependency.`);
|
|
327
|
+
}
|
|
328
|
+
checkValue(index, record.input, "input");
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
case "run": {
|
|
332
|
+
const runName = record.run;
|
|
333
|
+
if (!isStepName(runName) || runs.has(runName)) {
|
|
334
|
+
fail(index, "invalid_step_name", `run "${String(runName)}" must be an identifier, unique in the use case.`);
|
|
335
|
+
}
|
|
336
|
+
if (typeof record.fn !== "function") {
|
|
337
|
+
fail(index, "invalid_step", "run.fn must be a function.");
|
|
338
|
+
}
|
|
339
|
+
runs.add(runName);
|
|
340
|
+
checkArgs(index, record.args);
|
|
341
|
+
if (record.params !== undefined) {
|
|
342
|
+
if (!isBlueprint(record.params)) {
|
|
343
|
+
fail(index, "invalid_step", "run.params must be a blueprint.");
|
|
344
|
+
}
|
|
345
|
+
const declared = Object.keys(record.params.properties).length;
|
|
346
|
+
const given = record.args === undefined ? 0 : Array.isArray(record.args) ? record.args.length : undefined;
|
|
347
|
+
if (given !== undefined && given !== declared) {
|
|
348
|
+
fail(index, "invalid_args", `run.params declares ${declared} parameter(s), args gives ${given}.`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
break;
|
|
352
|
+
}
|
|
353
|
+
case "return": {
|
|
354
|
+
checkValue(index, record.return, "return");
|
|
355
|
+
returned = true;
|
|
356
|
+
if (index !== list.length - 1) {
|
|
357
|
+
fail(index, "misplaced_return", "return must be the last step, exactly once.");
|
|
358
|
+
}
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (typeof as === "string")
|
|
363
|
+
bound.set(as, { record: knownRecord });
|
|
364
|
+
}
|
|
365
|
+
if (!returned) {
|
|
366
|
+
fail(list.length, "missing_return", "The steps must end with return.");
|
|
367
|
+
}
|
|
368
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { fieldOf } from "../blueprint/callable.js";
|
|
2
|
+
import { isBlueprint } from "../blueprint/is-blueprint.js";
|
|
3
|
+
import { isBlueprintList } from "../blueprint/list.js";
|
|
4
|
+
import { optionalOf } from "../blueprint/optional.js";
|
|
5
|
+
import { recordFieldOf } from "../blueprint/record-field.js";
|
|
6
|
+
import { isBlueprintVariant } from "../blueprint/variant.js";
|
|
7
|
+
import { identityKeysOf } from "../entity/abstract-entity.js";
|
|
8
|
+
import { isEntityClass } from "../entity/entity.js";
|
|
9
|
+
import { propertyInputOf } from "../property/input.js";
|
|
10
|
+
import { isProperty } from "../property/is-property.js";
|
|
11
|
+
import { recordNameOf } from "../record/name.js";
|
|
12
|
+
import { pluginsOf } from "../record/plugin.js";
|
|
13
|
+
import { findState, relatedRepositoryOf } from "../repository/state.js";
|
|
14
|
+
import { UseCaseUnavailable } from "./exceptions.js";
|
|
15
|
+
function isOptionalField(field) {
|
|
16
|
+
if (isProperty(field))
|
|
17
|
+
return field.type.optional === true;
|
|
18
|
+
const record = recordFieldOf(field);
|
|
19
|
+
if (record)
|
|
20
|
+
return record.optional;
|
|
21
|
+
return field.optional === true;
|
|
22
|
+
}
|
|
23
|
+
function planOf(entity, key, owners) {
|
|
24
|
+
const field = fieldOf(entity.blueprint.properties[key]);
|
|
25
|
+
const optional = isOptionalField(field);
|
|
26
|
+
const record = recordFieldOf(field);
|
|
27
|
+
const item = isBlueprintList(field) ? recordFieldOf(field.item) : undefined;
|
|
28
|
+
const related = [record?.record, item?.record].find(isEntityClass);
|
|
29
|
+
const kind = record ? related ? "entity" : "record" : isBlueprintList(field) ? related ? "entity-list" : "list" : isBlueprint(field) || isBlueprintVariant(field) ? "blueprint" : "property";
|
|
30
|
+
let input = { key, field };
|
|
31
|
+
if (kind === "entity" && related) {
|
|
32
|
+
const id = related.blueprint.properties.id;
|
|
33
|
+
input = { key: `${key}Id`, field: optional ? optionalOf(id) : id };
|
|
34
|
+
}
|
|
35
|
+
const declared = kind === "property" ? propertyInputOf(field) : undefined;
|
|
36
|
+
if (declared) {
|
|
37
|
+
input = {
|
|
38
|
+
key,
|
|
39
|
+
field: optional ? optionalOf(declared.accepts) : declared.accepts
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return Object.freeze({
|
|
43
|
+
key,
|
|
44
|
+
field,
|
|
45
|
+
identity: identityKeysOf(entity).includes(key),
|
|
46
|
+
plugin: owners.get(key),
|
|
47
|
+
sensitive: entity.options.sensitive.includes(key),
|
|
48
|
+
unique: entity.options.unique.includes(key),
|
|
49
|
+
optional,
|
|
50
|
+
change: entity.options.change.includes(key),
|
|
51
|
+
kind,
|
|
52
|
+
related,
|
|
53
|
+
input: Object.freeze(input),
|
|
54
|
+
convert: declared ? (value) => value === undefined ? undefined : declared.convert(value) : undefined
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
export function useCaseTargetOf(repository, source = "useCaseTargetOf") {
|
|
58
|
+
const state = findState(repository);
|
|
59
|
+
if (!state)
|
|
60
|
+
throw new TypeError("Expected a repository made by repositoryOf");
|
|
61
|
+
const { entity } = state;
|
|
62
|
+
const unavailable = (reason) => {
|
|
63
|
+
throw new UseCaseUnavailable({
|
|
64
|
+
source,
|
|
65
|
+
details: { repository: state.name, reason }
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
const owners = new Map;
|
|
69
|
+
for (const plugin of pluginsOf(entity)) {
|
|
70
|
+
for (const key of plugin.keys)
|
|
71
|
+
owners.set(key, plugin.name);
|
|
72
|
+
}
|
|
73
|
+
const fields = Object.freeze(Object.keys(entity.blueprint.properties).map((key) => planOf(entity, key, owners)));
|
|
74
|
+
const field = (key) => fields.find((plan) => plan.key === key) ?? unavailable(`the entity has no key "${key}"`);
|
|
75
|
+
return Object.freeze({
|
|
76
|
+
repository,
|
|
77
|
+
repositoryName: state.name,
|
|
78
|
+
entity,
|
|
79
|
+
entityName: recordNameOf(entity),
|
|
80
|
+
actions: state.actions,
|
|
81
|
+
deletion: state.deletion,
|
|
82
|
+
fields,
|
|
83
|
+
changeKeys: entity.options.change,
|
|
84
|
+
uniqueKeys: entity.options.unique,
|
|
85
|
+
field,
|
|
86
|
+
relation(key, actions = []) {
|
|
87
|
+
const { related } = field(key);
|
|
88
|
+
const found = related ? relatedRepositoryOf(repository, related) : undefined;
|
|
89
|
+
if (!found) {
|
|
90
|
+
return unavailable(`the repository has no relation for the entity of "${key}"`);
|
|
91
|
+
}
|
|
92
|
+
const missing = actions.filter((action) => !found.actions.includes(action));
|
|
93
|
+
if (missing.length > 0) {
|
|
94
|
+
unavailable(`the repository of "${key}" lacks the action ${missing.map((action) => `"${action}"`).join(", ")}`);
|
|
95
|
+
}
|
|
96
|
+
return found;
|
|
97
|
+
},
|
|
98
|
+
unavailable
|
|
99
|
+
});
|
|
100
|
+
}
|
|
@@ -6,13 +6,29 @@ export type RouteSuccessStatus = 200 | 201 | 202 | 204;
|
|
|
6
6
|
export interface RouteCookie {
|
|
7
7
|
/** The name of the cookie. */
|
|
8
8
|
readonly name: string;
|
|
9
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Not readable by scripts of the page.
|
|
11
|
+
*
|
|
12
|
+
* @default true
|
|
13
|
+
*/
|
|
10
14
|
readonly httpOnly?: boolean;
|
|
11
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* Sent over HTTPS only.
|
|
17
|
+
*
|
|
18
|
+
* @default true
|
|
19
|
+
*/
|
|
12
20
|
readonly secure?: boolean;
|
|
13
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* The `SameSite` attribute of the cookie.
|
|
23
|
+
*
|
|
24
|
+
* @default "lax"
|
|
25
|
+
*/
|
|
14
26
|
readonly sameSite?: "strict" | "lax" | "none";
|
|
15
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* The `Path` attribute of the cookie.
|
|
29
|
+
*
|
|
30
|
+
* @default "/"
|
|
31
|
+
*/
|
|
16
32
|
readonly path?: string;
|
|
17
33
|
readonly domain?: string;
|
|
18
34
|
/** The seconds the cookie lives; without it, until the browser closes. */
|
|
@@ -32,7 +48,11 @@ export interface RouteDeclaration {
|
|
|
32
48
|
* is the default of the sink: `/{kebab(command)}/{kebab(operation)}`.
|
|
33
49
|
*/
|
|
34
50
|
readonly path?: string;
|
|
35
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* The status of a successful response.
|
|
53
|
+
*
|
|
54
|
+
* @default 200
|
|
55
|
+
*/
|
|
36
56
|
readonly status?: RouteSuccessStatus;
|
|
37
57
|
/**
|
|
38
58
|
* Scalar input keys read from the query string. A `get` or `delete` route
|
|
@@ -37,14 +37,31 @@ export type AnyExceptionClass = DeclaredExceptionClass<any>;
|
|
|
37
37
|
* `repositoryOf`, an environment made by `environmentOf`, or `Logger`.
|
|
38
38
|
*/
|
|
39
39
|
export type UseCaseDependency = AnyUseCaseClass | AnyRepository | AnyEnvironment | LoggerDeclaration;
|
|
40
|
+
/**
|
|
41
|
+
* The `dependencies` of a use case, by name: use case classes, repositories,
|
|
42
|
+
* environments or the {@link Logger}.
|
|
43
|
+
*/
|
|
40
44
|
export type UseCaseCollection = Readonly<Record<string, UseCaseDependency>>;
|
|
41
45
|
type Declaration = {
|
|
42
46
|
readonly blueprint: AnyBlueprint;
|
|
43
47
|
readonly response: AnyBlueprint;
|
|
44
48
|
readonly dependencies: UseCaseCollection;
|
|
45
49
|
};
|
|
50
|
+
/**
|
|
51
|
+
* The input of a use case class, as its constructor takes it: the input of its
|
|
52
|
+
* `blueprint`.
|
|
53
|
+
*/
|
|
46
54
|
export type UseCaseInput<Class extends Declaration> = BlueprintInput<Class["blueprint"]["properties"]>;
|
|
55
|
+
/**
|
|
56
|
+
* The value `execute` of a use case class resolves with: the value of its
|
|
57
|
+
* `response` blueprint.
|
|
58
|
+
*/
|
|
47
59
|
export type UseCaseResponse<Class extends Declaration> = BlueprintValue<Class["response"]["properties"]>;
|
|
60
|
+
/**
|
|
61
|
+
* The dependencies of a use case class as `this.dependencies` gives them: a
|
|
62
|
+
* function for a use case, the repository itself, the variables of an
|
|
63
|
+
* environment or an {@link ILogger} (see {@link DependencyOf}).
|
|
64
|
+
*/
|
|
48
65
|
export type UseCaseDependencies<Class extends Declaration> = DependencyFunctions<Class["dependencies"]>;
|
|
49
66
|
/** The context of a use case class, as `this.context` gives it; `undefined` without one. */
|
|
50
67
|
export type UseCaseContext<Class extends {
|
|
@@ -66,6 +83,10 @@ type OptionsArgs<Context extends IBlueprintCollection | undefined> = Context ext
|
|
|
66
83
|
* repository itself, the variables of an environment, or an `ILogger`.
|
|
67
84
|
*/
|
|
68
85
|
export type DependencyOf<Dependency> = Dependency extends AnyUseCaseClass ? (input: UseCaseInput<Dependency>) => Promise<UseCaseResponse<Dependency>> : Dependency extends AnyEnvironment ? EnvironmentValue<Dependency> : Dependency extends LoggerDeclaration ? ILogger : Dependency;
|
|
86
|
+
/**
|
|
87
|
+
* A collection of dependencies as a use case receives them: each one as {@link
|
|
88
|
+
* DependencyOf} gives it.
|
|
89
|
+
*/
|
|
69
90
|
export type DependencyFunctions<Dependencies extends UseCaseCollection> = {
|
|
70
91
|
readonly [Key in keyof Dependencies]: DependencyOf<Dependencies[Key]>;
|
|
71
92
|
};
|
|
@@ -76,6 +97,12 @@ export type DependencyFunctions<Dependencies extends UseCaseCollection> = {
|
|
|
76
97
|
export type DependencyOverrides<Dependencies extends UseCaseCollection> = {
|
|
77
98
|
readonly [Key in keyof Dependencies]?: Dependencies[Key] extends AnyRepository ? Partial<Dependencies[Key]> : DependencyOf<Dependencies[Key]>;
|
|
78
99
|
};
|
|
100
|
+
/**
|
|
101
|
+
* What {@link useCaseOf} takes: the `name`, the `blueprint` of the input, the
|
|
102
|
+
* `response` blueprint, the `dependencies` and the `context`, with the
|
|
103
|
+
* documentation of the operation (`description`, `tags`, `deprecated`,
|
|
104
|
+
* `errors`) and where it is served and who may call it (`route`, `access`).
|
|
105
|
+
*/
|
|
79
106
|
export interface UseCaseSettings<Input extends IBlueprintCollection, Output extends IBlueprintCollection, Dependencies extends UseCaseCollection, Context extends IBlueprintCollection | undefined = undefined> {
|
|
80
107
|
readonly name: string;
|
|
81
108
|
readonly blueprint: IBlueprint<Input>;
|
|
@@ -125,9 +152,10 @@ export interface UseCaseDeclaration<Input extends IBlueprintCollection, Output e
|
|
|
125
152
|
* class concrete: validated here, interpreted by its `execute`, and
|
|
126
153
|
* described by `describe()`. TypeScript checks the kinds of the steps and
|
|
127
154
|
* the names of dependencies and actions; with `strictSteps`, also their
|
|
128
|
-
* references and values
|
|
155
|
+
* references and values, and the `return` against the response (see
|
|
156
|
+
* `CheckedSteps`).
|
|
129
157
|
*/
|
|
130
|
-
readonly steps?: [Strict] extends [true] ? CheckedSteps<Steps, NoInfer<Input>, NoInfer<Context>, NoInfer<Dependencies>> : Steps;
|
|
158
|
+
readonly steps?: [Strict] extends [true] ? CheckedSteps<Steps, NoInfer<Input>, NoInfer<Context>, NoInfer<Dependencies>, NoInfer<Output>> : Steps;
|
|
131
159
|
/**
|
|
132
160
|
* `true` checks the steps, written inline, at compile time: each reference
|
|
133
161
|
* must name a value in scope (the input, the context, an environment or the
|
|
@@ -137,10 +165,20 @@ export interface UseCaseDeclaration<Input extends IBlueprintCollection, Output e
|
|
|
137
165
|
*/
|
|
138
166
|
readonly strictSteps?: Strict;
|
|
139
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* A use case, or a decorator of one, is declared with what it cannot use;
|
|
170
|
+
* `reason` says what.
|
|
171
|
+
*/
|
|
140
172
|
export declare const InvalidUseCaseDeclaration: DeclaredExceptionClass<{
|
|
141
173
|
reason: string;
|
|
142
174
|
}>;
|
|
175
|
+
/** `execute` was called twice on one instance of a use case, which runs only once. */
|
|
143
176
|
export declare const UseCaseAlreadyExecuted: DeclaredExceptionClass<undefined>;
|
|
177
|
+
/**
|
|
178
|
+
* The overrides given to `execute` name a dependency the use case does not
|
|
179
|
+
* declare, or give the wrong kind of value for one; or a dependency was called
|
|
180
|
+
* after `execute` ended.
|
|
181
|
+
*/
|
|
144
182
|
export declare const InvalidUseCaseDependencies: DeclaredExceptionClass<undefined>;
|
|
145
183
|
/** An operation with validated input, dependency functions and a drainable event queue. */
|
|
146
184
|
export declare abstract class AbstractUseCase<Input extends IBlueprintCollection, Output extends IBlueprintCollection, Dependencies extends UseCaseCollection> {
|
|
@@ -148,6 +186,14 @@ export declare abstract class AbstractUseCase<Input extends IBlueprintCollection
|
|
|
148
186
|
constructor(input: BlueprintInput<Input>, ...args: unknown[]);
|
|
149
187
|
/** Default parameter for implementations: `execute(deps = this.dependencies)`. */
|
|
150
188
|
get dependencies(): DependencyFunctions<Dependencies>;
|
|
189
|
+
/**
|
|
190
|
+
* Runs the operation and gives its response. `dependencies` replace the
|
|
191
|
+
* declared ones for this call: a function for a use case, some methods of a
|
|
192
|
+
* repository, the variables of an environment.
|
|
193
|
+
*
|
|
194
|
+
* @throws {@link UseCaseAlreadyExecuted} when this instance already executed.
|
|
195
|
+
* @throws {@link InvalidUseCaseDependencies} when an override names an undeclared dependency or is of the wrong kind.
|
|
196
|
+
*/
|
|
151
197
|
abstract execute(dependencies?: DependencyOverrides<Dependencies>): Promise<BlueprintValue<Output>>;
|
|
152
198
|
/** Immediately drains collected events; never executes their handlers. */
|
|
153
199
|
pullEvents(): EntityEvent[];
|
|
@@ -176,6 +222,11 @@ type UseCaseInstanceContext<Context extends IBlueprintCollection | undefined> =
|
|
|
176
222
|
/** What the caller supplied with the call, parsed and frozen; `undefined` without a declared context. */
|
|
177
223
|
readonly context: Context extends IBlueprintCollection ? Readonly<BlueprintValue<Context>> : undefined;
|
|
178
224
|
};
|
|
225
|
+
/**
|
|
226
|
+
* What {@link useCaseOf} gives without `steps`: an abstract class to extend,
|
|
227
|
+
* implementing an async `execute`. Its constructor takes the input and, when
|
|
228
|
+
* the use case declares a context, `{ context }`.
|
|
229
|
+
*/
|
|
179
230
|
export type UseCaseConstructor<Input extends IBlueprintCollection, Output extends IBlueprintCollection, Dependencies extends UseCaseCollection, Context extends IBlueprintCollection | undefined = undefined> = (abstract new (input: BlueprintInput<Input>, ...options: OptionsArgs<Context>) => AbstractUseCase<Input, Output, Dependencies> & UseCaseInstanceContext<Context>) & UseCaseStatics<Input, Output, Dependencies, Context>;
|
|
180
231
|
/**
|
|
181
232
|
* What `useCaseOf` gives with `steps`: concrete, its `execute` interprets the
|
|
@@ -188,8 +239,32 @@ export type StepsUseCaseConstructor<Input extends IBlueprintCollection, Output e
|
|
|
188
239
|
* Declares an operation. With `steps` the class is concrete: its `execute`
|
|
189
240
|
* interprets them. Without, it is abstract: extend it and implement an async
|
|
190
241
|
* execute method.
|
|
242
|
+
*
|
|
243
|
+
* @throws {@link InvalidUseCaseDeclaration} when the name, the blueprints or the dependencies are missing or of the wrong kind, or a setting is not valid.
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* ```ts
|
|
247
|
+
* import { blueprintOf, useCaseOf } from "@afora/core";
|
|
248
|
+
*
|
|
249
|
+
* class Greet extends useCaseOf({
|
|
250
|
+
* name: "Greet",
|
|
251
|
+
* blueprint: blueprintOf((ctx) => ({ name: ctx.text })),
|
|
252
|
+
* response: blueprintOf((ctx) => ({ message: ctx.string })),
|
|
253
|
+
* }) {
|
|
254
|
+
* override async execute() {
|
|
255
|
+
* return { message: `Hello, ${this.input.name}!` };
|
|
256
|
+
* }
|
|
257
|
+
* }
|
|
258
|
+
*
|
|
259
|
+
* const { message } = await new Greet({ name: "Ana" }).execute();
|
|
260
|
+
* ```
|
|
191
261
|
*/
|
|
192
262
|
export declare function useCaseOf<Input extends IBlueprintCollection, Output extends IBlueprintCollection, const Dependencies extends UseCaseCollection = Record<never, never>, Context extends IBlueprintCollection | undefined = undefined, const Steps extends readonly UseCaseStep<NoInfer<Dependencies>>[] | undefined = undefined, Strict extends boolean = false>(settings: UseCaseDeclaration<Input, Output, Dependencies, Context, Steps, Strict>): [Steps] extends [undefined] ? UseCaseConstructor<Input, Output, Dependencies, Context> : StepsUseCaseConstructor<Input, Output, Dependencies, Context>;
|
|
263
|
+
/**
|
|
264
|
+
* Whether `value` is a use case class made by {@link useCaseOf}, abstract or
|
|
265
|
+
* concrete, or a subclass of one.
|
|
266
|
+
*/
|
|
193
267
|
export declare function isUseCaseClass(value: unknown): value is AnyUseCaseClass;
|
|
268
|
+
/** Whether `value` is an instance of a use case class. */
|
|
194
269
|
export declare function isUseCase(value: unknown): value is AbstractUseCase<IBlueprintCollection, IBlueprintCollection, UseCaseCollection>;
|
|
195
270
|
export {};
|