@afora/core 0.0.8 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +149 -23
- package/dist/adapters.d.ts +25 -0
- package/dist/adapters.js +32 -0
- package/dist/core/application/application.d.ts +30 -0
- package/dist/core/application/application.js +1291 -0
- package/dist/core/application/exceptions.d.ts +17 -0
- package/dist/core/application/exceptions.js +85 -0
- package/dist/core/application/migrate.d.ts +19 -0
- package/dist/core/application/migrate.js +67 -0
- package/dist/core/application/providers.js +108 -0
- package/dist/core/application/registry.js +23 -0
- package/dist/core/application/schema.d.ts +30 -0
- package/dist/core/application/schema.js +398 -0
- package/dist/core/application/start.js +162 -0
- package/dist/core/application/types/application.interface.d.ts +60 -3
- package/dist/core/auth/jwt.d.ts +30 -0
- package/dist/core/auth/jwt.js +200 -0
- package/dist/core/auth/types/jwt.interface.d.ts +74 -0
- package/dist/core/blueprint/blueprint-plugin.js +7 -0
- package/dist/core/blueprint/blueprint.js +146 -0
- package/dist/core/blueprint/callable.js +13 -0
- package/dist/core/blueprint/exceptions.d.ts +5 -0
- package/dist/core/blueprint/exceptions.js +20 -0
- package/dist/core/blueprint/is-blueprint.js +4 -0
- package/dist/core/blueprint/list.js +19 -0
- package/dist/core/blueprint/optional.d.ts +3 -1
- package/dist/core/blueprint/optional.js +69 -0
- package/dist/core/blueprint/record-field.js +14 -0
- package/dist/core/blueprint/slice.js +127 -0
- package/dist/core/blueprint/types/blueprint.interface.d.ts +95 -5
- package/dist/core/blueprint/variant.d.ts +32 -0
- package/dist/core/blueprint/variant.js +72 -0
- package/dist/core/command/command.js +428 -0
- package/dist/core/command/exceptions.js +30 -0
- package/dist/core/command/types/command.interface.d.ts +10 -1
- package/dist/core/entity/abstract-entity.d.ts +24 -0
- package/dist/core/entity/abstract-entity.js +181 -0
- package/dist/core/entity/entity.d.ts +1 -1
- package/dist/core/entity/entity.js +209 -0
- package/dist/core/entity/event.d.ts +39 -0
- package/dist/core/entity/event.js +80 -0
- package/dist/core/entity/exceptions.d.ts +9 -0
- package/dist/core/entity/exceptions.js +27 -0
- package/dist/core/entity/trust.d.ts +24 -0
- package/dist/core/entity/trust.js +7 -0
- package/dist/core/entity/types/entity.interface.d.ts +30 -0
- package/dist/core/entity/version.js +13 -0
- package/dist/core/environment/environment.d.ts +26 -3
- package/dist/core/environment/environment.js +71 -0
- package/dist/core/event-policy/event-policy.js +76 -0
- package/dist/core/event-policy/exceptions.js +12 -0
- package/dist/core/event-policy/publish.js +98 -0
- package/dist/core/event-policy/publishers.js +18 -0
- package/dist/core/event-policy/types/event-policy.interface.d.ts +8 -2
- package/dist/core/exception/enums/exception-layers.js +15 -0
- package/dist/core/exception/exception.d.ts +15 -0
- package/dist/core/exception/exception.js +128 -0
- package/dist/core/exception/exported.js +49 -0
- package/dist/core/exception/guard.d.ts +20 -1
- package/dist/core/exception/guard.js +16 -0
- package/dist/core/exception/types/exception.interface.d.ts +13 -0
- package/dist/core/json.js +19 -0
- package/dist/core/logger/exceptions.js +7 -0
- package/dist/core/logger/logger.d.ts +14 -1
- package/dist/core/logger/logger.js +178 -0
- package/dist/core/openapi/exceptions.js +7 -0
- package/dist/core/openapi/openapi.js +132 -0
- package/dist/core/openapi/types/openapi.interface.d.ts +7 -1
- package/dist/core/output/access.d.ts +16 -0
- package/dist/core/output/access.js +66 -0
- package/dist/core/output/coerce.js +38 -0
- package/dist/core/output/exceptions.d.ts +16 -0
- package/dist/core/output/exceptions.js +22 -0
- package/dist/core/output/http.d.ts +36 -0
- package/dist/core/output/http.js +351 -0
- package/dist/core/output/openapi-sink.d.ts +33 -4
- package/dist/core/output/openapi-sink.js +571 -0
- package/dist/core/output/output.js +192 -0
- package/dist/core/output/problem.js +30 -0
- package/dist/core/output/select.js +28 -0
- package/dist/core/output/stage.d.ts +7 -2
- package/dist/core/output/stage.js +111 -0
- package/dist/core/output/status.d.ts +1 -1
- package/dist/core/output/status.js +52 -0
- package/dist/core/output/types/http.interface.d.ts +134 -0
- package/dist/core/output/types/output.interface.d.ts +49 -5
- package/dist/core/parse/issue.d.ts +28 -3
- package/dist/core/parse/issue.js +31 -0
- package/dist/core/parse/parse.d.ts +16 -0
- package/dist/core/parse/parse.js +323 -0
- package/dist/core/parse/strip.js +48 -0
- package/dist/core/property/abstract-property.d.ts +9 -0
- package/dist/core/property/abstract-property.js +37 -0
- package/dist/core/property/define-property.d.ts +34 -6
- package/dist/core/property/define-property.js +209 -0
- package/dist/core/property/enums/property-states.js +8 -0
- package/dist/core/property/exceptions.js +12 -0
- package/dist/core/property/input.js +10 -0
- package/dist/core/property/is-property.js +3 -0
- package/dist/core/property/lineage.js +8 -0
- package/dist/core/property/property.d.ts +27 -0
- package/dist/core/property/property.js +46 -0
- package/dist/core/property/refine.js +103 -0
- package/dist/core/property/types/property-options.interface.d.ts +10 -0
- package/dist/core/property/types/property-type.interface.d.ts +4 -0
- package/dist/core/record/abstract-record.d.ts +8 -0
- package/dist/core/record/abstract-record.js +307 -0
- package/dist/core/record/exceptions.js +49 -0
- package/dist/core/record/marks.js +7 -0
- package/dist/core/record/mask.js +60 -0
- package/dist/core/record/name.js +23 -0
- package/dist/core/record/plugin-catalog.js +115 -0
- package/dist/core/record/plugin.js +198 -0
- package/dist/core/record/record.js +117 -0
- package/dist/core/record/types/record.interface.d.ts +17 -1
- package/dist/core/repository/cursor.js +83 -0
- package/dist/core/repository/exceptions.d.ts +13 -0
- package/dist/core/repository/exceptions.js +72 -0
- package/dist/core/repository/in-memory.d.ts +2 -2
- package/dist/core/repository/in-memory.js +196 -0
- package/dist/core/repository/migrate.d.ts +20 -0
- package/dist/core/repository/migrate.js +254 -0
- package/dist/core/repository/plan.js +27 -0
- package/dist/core/repository/provider.js +29 -0
- package/dist/core/repository/query.js +153 -0
- package/dist/core/repository/references.js +196 -0
- package/dist/core/repository/repository.d.ts +22 -2
- package/dist/core/repository/repository.js +720 -0
- package/dist/core/repository/state.d.ts +2 -0
- package/dist/core/repository/state.js +138 -0
- package/dist/core/repository/types/query.interface.d.ts +13 -0
- package/dist/core/repository/types/repository.interface.d.ts +50 -5
- package/dist/core/repository/types/schema.interface.d.ts +132 -0
- package/dist/core/repository/upcast.d.ts +64 -0
- package/dist/core/repository/upcast.js +179 -0
- package/dist/core/schema/schema.d.ts +37 -0
- package/dist/core/schema/schema.js +542 -0
- package/dist/core/standard/standard-schema.js +1 -0
- package/dist/core/standard/standard.js +23 -0
- package/dist/core/symbols/demo.d.ts +4 -0
- package/dist/core/symbols/demo.js +1 -0
- package/dist/core/symbols/kind.d.ts +1 -1
- package/dist/core/symbols/kind.js +1 -0
- package/dist/core/symbols/meta.js +1 -0
- package/dist/core/symbols/state.d.ts +5 -0
- package/dist/core/symbols/state.js +1 -0
- package/dist/core/tracing/exceptions.d.ts +4 -0
- package/dist/core/tracing/exceptions.js +7 -0
- package/dist/core/tracing/tracing.d.ts +72 -0
- package/dist/core/tracing/tracing.js +101 -0
- package/dist/core/use-case/access.d.ts +12 -0
- package/dist/core/use-case/access.js +56 -0
- package/dist/core/use-case/decorators.d.ts +40 -0
- package/dist/core/use-case/decorators.js +65 -0
- package/dist/core/use-case/exceptions.d.ts +5 -1
- package/dist/core/use-case/exceptions.js +48 -0
- package/dist/core/use-case/plugin-catalog.js +68 -0
- package/dist/core/use-case/plugin.js +133 -0
- package/dist/core/use-case/route.d.ts +18 -0
- package/dist/core/use-case/route.js +200 -0
- package/dist/core/use-case/scope.d.ts +5 -0
- package/dist/core/use-case/scope.js +4 -0
- package/dist/core/use-case/steps/describe.d.ts +5 -1
- package/dist/core/use-case/steps/describe.js +24 -0
- package/dist/core/use-case/steps/references.js +105 -0
- package/dist/core/use-case/steps/run.js +139 -0
- package/dist/core/use-case/steps/types/check.interface.d.ts +233 -0
- package/dist/core/use-case/steps/types/steps.interface.d.ts +8 -1
- package/dist/core/use-case/steps/validate.js +368 -0
- package/dist/core/use-case/target.js +100 -0
- package/dist/core/use-case/types/access.interface.d.ts +39 -0
- package/dist/core/use-case/types/route.interface.d.ts +86 -0
- package/dist/core/use-case/use-case.d.ts +127 -2
- package/dist/core/use-case/use-case.js +493 -0
- package/dist/experimental.d.ts +18 -1
- package/dist/experimental.js +18 -123
- package/dist/index.d.ts +49 -51
- package/dist/index.js +213 -443
- package/dist/std/blueprint-plugin.js +42 -0
- package/dist/std/blueprint.d.ts +21 -0
- package/dist/std/blueprint.js +3 -0
- package/dist/std/command.js +3 -0
- package/dist/std/entity.d.ts +20 -1
- package/dist/std/entity.js +30 -0
- package/dist/std/event-policies.js +3 -0
- package/dist/std/properties/contact.d.ts +3 -0
- package/dist/std/properties/contact.js +29 -0
- package/dist/std/properties/enum.js +26 -0
- package/dist/std/properties/exceptions.js +14 -0
- package/dist/std/properties/headless.js +45 -0
- package/dist/std/properties/identity.js +28 -0
- package/dist/std/properties/json.js +161 -0
- package/dist/std/properties/password.d.ts +0 -2
- package/dist/std/properties/password.js +79 -0
- package/dist/std/properties/primitives.d.ts +4 -0
- package/dist/std/properties/primitives.js +28 -0
- package/dist/std/properties/time.js +55 -0
- package/dist/std/record-plugins.d.ts +2 -2
- package/dist/std/record-plugins.js +73 -0
- package/dist/std/record.d.ts +20 -1
- package/dist/std/record.js +3 -0
- package/dist/std/resource.d.ts +31 -0
- package/dist/std/resource.js +39 -0
- package/dist/std/runtime.d.ts +6 -0
- package/dist/std/runtime.js +193 -0
- package/dist/std/stages.js +4 -0
- package/dist/std/use-cases/auth.d.ts +5 -1
- package/dist/std/use-cases/auth.js +193 -0
- package/dist/std/use-cases/crud.js +261 -0
- package/dist/std/use-cases/index.d.ts +1 -1
- package/dist/std/use-cases/index.js +12 -0
- package/dist/std/use-cases/lifecycle.js +97 -0
- package/dist/std/use-cases/shared.d.ts +2 -2
- package/dist/std/use-cases/shared.js +232 -0
- package/dist/std/use-cases/types.d.ts +102 -2
- package/dist/std/use-cases/types.js +0 -0
- package/dist/testing/http-provider-contract.d.ts +32 -0
- package/dist/testing/http-provider-contract.js +344 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +3 -944
- package/dist/testing/memory-publisher.js +12 -0
- package/dist/testing/provider-contract.d.ts +26 -1
- package/dist/testing/provider-contract.js +1071 -0
- package/package.json +23 -3
- package/dist/index-qxfkyd6k.js +0 -1721
- package/dist/index-t5qwtyj5.js +0 -8194
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { listOf } from "../../core/blueprint/list.js";
|
|
2
|
+
import { optionalOf } from "../../core/blueprint/optional.js";
|
|
3
|
+
import { UseCaseEntityNotFound } from "../../core/use-case/exceptions.js";
|
|
4
|
+
import { useCasePluginOf } from "../../core/use-case/plugin.js";
|
|
5
|
+
import { Integer } from "../properties/primitives.js";
|
|
6
|
+
import {
|
|
7
|
+
asOf,
|
|
8
|
+
capitalize,
|
|
9
|
+
checkOptions,
|
|
10
|
+
contextOf,
|
|
11
|
+
distinct,
|
|
12
|
+
entityUseCase,
|
|
13
|
+
errorOf,
|
|
14
|
+
fields,
|
|
15
|
+
HOOKS,
|
|
16
|
+
hooksOf,
|
|
17
|
+
idOf,
|
|
18
|
+
joinDependencies,
|
|
19
|
+
keysOf,
|
|
20
|
+
literal,
|
|
21
|
+
loadSteps,
|
|
22
|
+
metaOf,
|
|
23
|
+
ownerOf,
|
|
24
|
+
valuesOf
|
|
25
|
+
} from "./shared.js";
|
|
26
|
+
export function createCompilation(target, options, verb, checks = () => [], errors = []) {
|
|
27
|
+
checkOptions(target, options, ["as", "owner", "omit", "values", ...HOOKS]);
|
|
28
|
+
const owner = ownerOf(target, options);
|
|
29
|
+
const hooks = hooksOf(target, options);
|
|
30
|
+
const omitted = keysOf(target, options, "omit");
|
|
31
|
+
const constants = valuesOf(target, options, "values");
|
|
32
|
+
const notFound = errorOf(target, options, "notFound", UseCaseEntityNotFound);
|
|
33
|
+
const ownerError = errorOf(target, options, "owner", notFound);
|
|
34
|
+
const input = {};
|
|
35
|
+
const values = {};
|
|
36
|
+
const dependencies = {
|
|
37
|
+
repository: target.repository
|
|
38
|
+
};
|
|
39
|
+
const steps = [];
|
|
40
|
+
const used = new Set([...errors, ...hooks.throws]);
|
|
41
|
+
const take = (key, field) => {
|
|
42
|
+
if (Object.hasOwn(input, key)) {
|
|
43
|
+
target.unavailable(`two keys of the entity take the input "${key}"`);
|
|
44
|
+
}
|
|
45
|
+
input[key] = field;
|
|
46
|
+
};
|
|
47
|
+
const load = (key, reference, error) => {
|
|
48
|
+
const dependency = `${key}Repository`;
|
|
49
|
+
dependencies[dependency] = target.relation(key, ["findById"]);
|
|
50
|
+
used.add(error);
|
|
51
|
+
steps.push({
|
|
52
|
+
find: `${dependency}.findById`,
|
|
53
|
+
args: [reference],
|
|
54
|
+
as: `${key}Found`
|
|
55
|
+
}, { ensure: "exists", value: `$${key}Found`, error });
|
|
56
|
+
return `$${key}Found`;
|
|
57
|
+
};
|
|
58
|
+
for (const plan of target.fields) {
|
|
59
|
+
const { key } = plan;
|
|
60
|
+
if (plan.identity)
|
|
61
|
+
continue;
|
|
62
|
+
if (Object.hasOwn(constants, key)) {
|
|
63
|
+
values[key] = literal(constants[key]);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (owner?.plan.key === key) {
|
|
67
|
+
values[key] = plan.kind === "entity" ? load(key, `$context.${owner.context}`, ownerError) : `$context.${owner.context}`;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const unloadable = plan.kind === "entity-list" || plan.kind === "entity" && plan.optional;
|
|
71
|
+
if (omitted.includes(plan) || unloadable) {
|
|
72
|
+
if (!plan.optional) {
|
|
73
|
+
target.unavailable(unloadable ? `"${key}" holds a list of entities, which the input cannot take; give it in values` : `"${key}" is required and cannot be omitted`);
|
|
74
|
+
}
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
take(plan.input.key, plan.input.field);
|
|
78
|
+
const given = `${hooks.input}.${plan.input.key}`;
|
|
79
|
+
if (plan.kind === "entity") {
|
|
80
|
+
values[key] = load(key, given, notFound);
|
|
81
|
+
} else if (plan.convert) {
|
|
82
|
+
steps.push({
|
|
83
|
+
run: `convert${capitalize(key)}`,
|
|
84
|
+
fn: plan.convert,
|
|
85
|
+
args: [given],
|
|
86
|
+
as: `${key}Converted`
|
|
87
|
+
});
|
|
88
|
+
values[key] = `$${key}Converted`;
|
|
89
|
+
} else {
|
|
90
|
+
values[key] = given;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
...metaOf(target, options, verb),
|
|
95
|
+
blueprint: fields(() => input),
|
|
96
|
+
response: fields(() => ({ [asOf(target, options)]: target.entity })),
|
|
97
|
+
...contextOf(owner),
|
|
98
|
+
dependencies: joinDependencies(target, dependencies, hooks),
|
|
99
|
+
errors: distinct(...used),
|
|
100
|
+
steps: [
|
|
101
|
+
...hooks.before(),
|
|
102
|
+
...checks(hooks.input),
|
|
103
|
+
...steps,
|
|
104
|
+
{ build: target.entity, values, as: "built" },
|
|
105
|
+
{ create: "repository", value: "$built", as: "item" },
|
|
106
|
+
...hooks.after("$item"),
|
|
107
|
+
{ return: { [asOf(target, options)]: "$item" } }
|
|
108
|
+
]
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
export const create = useCasePluginOf("create", {
|
|
112
|
+
description: "Builds an entity from the input (its keys, less the identity; an entity it holds by id) and stores it.",
|
|
113
|
+
requires: { actions: ["create"] },
|
|
114
|
+
compile: (target, options) => createCompilation(target, options, "Create")
|
|
115
|
+
});
|
|
116
|
+
export const get = useCasePluginOf("get", {
|
|
117
|
+
description: "Gives the entity of an id.",
|
|
118
|
+
requires: { actions: ["findById"] },
|
|
119
|
+
compile: (target, options) => entityUseCase(target, options, "Get", [], () => ({
|
|
120
|
+
steps: [],
|
|
121
|
+
result: "$item"
|
|
122
|
+
}))
|
|
123
|
+
});
|
|
124
|
+
export const list = useCasePluginOf("list", {
|
|
125
|
+
description: "Gives a page of entities: { items, total, limit, offset }.",
|
|
126
|
+
requires: { actions: ["findPage"] },
|
|
127
|
+
compile: (target, options) => {
|
|
128
|
+
checkOptions(target, options, ["owner", "orderBy"]);
|
|
129
|
+
const owner = ownerOf(target, options);
|
|
130
|
+
const { orderBy = [{ key: "createdAt", direction: "desc" }] } = options;
|
|
131
|
+
if (!Array.isArray(orderBy)) {
|
|
132
|
+
target.unavailable("the option orderBy must be a list of orders");
|
|
133
|
+
}
|
|
134
|
+
for (const order of orderBy) {
|
|
135
|
+
target.field(String(order?.key));
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
...metaOf(target, options, "List"),
|
|
139
|
+
blueprint: fields(() => ({
|
|
140
|
+
limit: optionalOf(Integer),
|
|
141
|
+
offset: optionalOf(Integer)
|
|
142
|
+
})),
|
|
143
|
+
response: fields(() => ({
|
|
144
|
+
items: listOf(target.entity),
|
|
145
|
+
total: Integer,
|
|
146
|
+
limit: Integer,
|
|
147
|
+
offset: Integer
|
|
148
|
+
})),
|
|
149
|
+
...contextOf(owner),
|
|
150
|
+
dependencies: { repository: target.repository },
|
|
151
|
+
steps: [
|
|
152
|
+
{
|
|
153
|
+
find: "repository.findPage",
|
|
154
|
+
args: [
|
|
155
|
+
{
|
|
156
|
+
...owner ? {
|
|
157
|
+
where: {
|
|
158
|
+
[owner.plan.key]: `$context.${owner.context}`
|
|
159
|
+
}
|
|
160
|
+
} : {},
|
|
161
|
+
orderBy: literal(orderBy),
|
|
162
|
+
limit: "$input.limit",
|
|
163
|
+
offset: "$input.offset"
|
|
164
|
+
}
|
|
165
|
+
],
|
|
166
|
+
as: "page"
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
return: {
|
|
170
|
+
items: "$page.items",
|
|
171
|
+
total: "$page.total",
|
|
172
|
+
limit: "$page.limit",
|
|
173
|
+
offset: "$page.offset"
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
]
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
export const update = useCasePluginOf("update", {
|
|
181
|
+
description: "Changes the change keys the input gives, leaving the others as they are, and stores the entity.",
|
|
182
|
+
requires: { actions: ["findById", "update"] },
|
|
183
|
+
compile: (target, options) => {
|
|
184
|
+
checkOptions(target, options, ["as", "owner", ...HOOKS]);
|
|
185
|
+
const owner = ownerOf(target, options);
|
|
186
|
+
const hooks = hooksOf(target, options);
|
|
187
|
+
const notFound = errorOf(target, options, "notFound", UseCaseEntityNotFound);
|
|
188
|
+
const plans = target.changeKeys.map((key) => target.field(key)).filter((plan) => plan.kind !== "entity" && plan.kind !== "entity-list");
|
|
189
|
+
if (plans.length === 0) {
|
|
190
|
+
target.unavailable("the entity has no change key an input can take");
|
|
191
|
+
}
|
|
192
|
+
if (plans.some((plan) => plan.key === "id")) {
|
|
193
|
+
target.unavailable('"id" cannot be a change key');
|
|
194
|
+
}
|
|
195
|
+
const steps = [];
|
|
196
|
+
for (const plan of plans) {
|
|
197
|
+
const given = `${hooks.input}.${plan.input.key}`;
|
|
198
|
+
let value = given;
|
|
199
|
+
if (plan.convert) {
|
|
200
|
+
steps.push({
|
|
201
|
+
run: `convert${capitalize(plan.key)}`,
|
|
202
|
+
fn: plan.convert,
|
|
203
|
+
args: [given],
|
|
204
|
+
as: `${plan.key}Converted`
|
|
205
|
+
});
|
|
206
|
+
value = `$${plan.key}Converted`;
|
|
207
|
+
}
|
|
208
|
+
steps.push({ change: "$item", key: plan.key, value, ifPresent: true });
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
...metaOf(target, options, "Update"),
|
|
212
|
+
blueprint: fields(() => ({
|
|
213
|
+
id: idOf(target),
|
|
214
|
+
...Object.fromEntries(plans.map((plan) => [plan.input.key, optionalOf(plan.input.field)]))
|
|
215
|
+
})),
|
|
216
|
+
response: fields(() => ({ [asOf(target, options)]: target.entity })),
|
|
217
|
+
...contextOf(owner),
|
|
218
|
+
dependencies: joinDependencies(target, { repository: target.repository }, hooks),
|
|
219
|
+
errors: distinct(notFound, ...hooks.throws),
|
|
220
|
+
steps: [
|
|
221
|
+
...loadSteps(owner, notFound),
|
|
222
|
+
...hooks.before("$item"),
|
|
223
|
+
...steps,
|
|
224
|
+
{ update: "repository", value: "$item", as: "saved" },
|
|
225
|
+
...hooks.after("$saved"),
|
|
226
|
+
{ return: { [asOf(target, options)]: "$saved" } }
|
|
227
|
+
]
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
const remove = useCasePluginOf("delete", {
|
|
232
|
+
description: "Deletes the entity of an id, hard or soft as the repository declares, and gives it.",
|
|
233
|
+
requires: { actions: ["findById", "delete"] },
|
|
234
|
+
compile: (target, options) => entityUseCase(target, options, "Delete", [], () => ({
|
|
235
|
+
steps: [{ delete: "repository", value: "$item", as: "deleted" }],
|
|
236
|
+
result: "$deleted"
|
|
237
|
+
}), true)
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
export { remove as delete_ };
|
|
241
|
+
export const restore = useCasePluginOf("restore", {
|
|
242
|
+
description: "Brings back the soft-deleted entity of an id; for a repository with soft deletion and the restore action.",
|
|
243
|
+
requires: { actions: ["restore"] },
|
|
244
|
+
compile: (target, options) => {
|
|
245
|
+
checkOptions(target, options, ["as", ...HOOKS]);
|
|
246
|
+
const hooks = hooksOf(target, options);
|
|
247
|
+
return {
|
|
248
|
+
...metaOf(target, options, "Restore"),
|
|
249
|
+
blueprint: fields(() => ({ id: idOf(target) })),
|
|
250
|
+
response: fields(() => ({ [asOf(target, options)]: target.entity })),
|
|
251
|
+
dependencies: joinDependencies(target, { repository: target.repository }, hooks),
|
|
252
|
+
errors: distinct(...hooks.throws),
|
|
253
|
+
steps: [
|
|
254
|
+
...hooks.before(),
|
|
255
|
+
{ restore: "repository", value: `${hooks.input}.id`, as: "item" },
|
|
256
|
+
...hooks.after("$item"),
|
|
257
|
+
{ return: { [asOf(target, options)]: "$item" } }
|
|
258
|
+
]
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
});
|
|
@@ -6,6 +6,6 @@ import type { StdUseCasePlugins } from "./types.js";
|
|
|
6
6
|
* `list`, `update`, `delete` and `restore` for any entity; `publish`,
|
|
7
7
|
* `unpublish`, `archive`, `unarchive`, `tag` and `untag` for the entities
|
|
8
8
|
* with those record plugins; `register` and `login` for the
|
|
9
|
-
* authenticatable ones
|
|
9
|
+
* authenticatable ones, and `refresh` and `logout` for the tokens of `login`. `StdUseCases.context` is that `ctx`.
|
|
10
10
|
*/
|
|
11
11
|
export declare const StdUseCases: IUseCaseCatalog<StdUseCasePlugins>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useCaseCatalogOf } from "../../core/use-case/plugin-catalog.js";
|
|
2
|
+
import { login, logout, refresh, register } from "./auth.js";
|
|
3
|
+
import { create, delete_, get, list, restore, update } from "./crud.js";
|
|
4
|
+
import {
|
|
5
|
+
archive,
|
|
6
|
+
publish,
|
|
7
|
+
tag,
|
|
8
|
+
unarchive,
|
|
9
|
+
unpublish,
|
|
10
|
+
untag
|
|
11
|
+
} from "./lifecycle.js";
|
|
12
|
+
export const StdUseCases = useCaseCatalogOf(create, get, list, update, delete_, restore, publish, unpublish, archive, unarchive, tag, untag, register, login, refresh, logout);
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { listOf } from "../../core/blueprint/list.js";
|
|
2
|
+
import { UseCaseEntityNotFound } from "../../core/use-case/exceptions.js";
|
|
3
|
+
import { useCasePluginOf } from "../../core/use-case/plugin.js";
|
|
4
|
+
import {
|
|
5
|
+
asOf,
|
|
6
|
+
checkOptions,
|
|
7
|
+
contextOf,
|
|
8
|
+
distinct,
|
|
9
|
+
entityUseCase,
|
|
10
|
+
errorOf,
|
|
11
|
+
fields,
|
|
12
|
+
HOOKS,
|
|
13
|
+
hooksOf,
|
|
14
|
+
idOf,
|
|
15
|
+
joinDependencies,
|
|
16
|
+
literal,
|
|
17
|
+
loadSteps,
|
|
18
|
+
metaOf,
|
|
19
|
+
ownerOf,
|
|
20
|
+
valuesOf
|
|
21
|
+
} from "./shared.js";
|
|
22
|
+
function invoking(target, options, verb, method) {
|
|
23
|
+
const set = valuesOf(target, options, "set");
|
|
24
|
+
const changes = Object.entries(set).map(([key, value]) => {
|
|
25
|
+
if (!target.changeKeys.includes(key)) {
|
|
26
|
+
target.unavailable(`set.${key} is not a change key of the entity`);
|
|
27
|
+
}
|
|
28
|
+
return { change: "$item", key, value: literal(value) };
|
|
29
|
+
});
|
|
30
|
+
return entityUseCase(target, options, verb, ["set"], () => ({
|
|
31
|
+
steps: [
|
|
32
|
+
...changes,
|
|
33
|
+
{ invoke: "$item", method },
|
|
34
|
+
{ update: "repository", value: "$item", as: "saved" }
|
|
35
|
+
],
|
|
36
|
+
result: "$saved"
|
|
37
|
+
}), true);
|
|
38
|
+
}
|
|
39
|
+
const lifecycle = (plugin) => ({
|
|
40
|
+
requires: { actions: ["findById", "update"], plugins: [plugin] }
|
|
41
|
+
});
|
|
42
|
+
export const publish = useCasePluginOf("publish", {
|
|
43
|
+
description: "Publishes the publishable entity of an id and stores it.",
|
|
44
|
+
...lifecycle("publishable"),
|
|
45
|
+
compile: (target, options) => invoking(target, options, "Publish", "publish")
|
|
46
|
+
});
|
|
47
|
+
export const unpublish = useCasePluginOf("unpublish", {
|
|
48
|
+
description: "Makes the publishable entity of an id a draft again and stores it.",
|
|
49
|
+
...lifecycle("publishable"),
|
|
50
|
+
compile: (target, options) => invoking(target, options, "Unpublish", "unpublish")
|
|
51
|
+
});
|
|
52
|
+
export const archive = useCasePluginOf("archive", {
|
|
53
|
+
description: "Archives the archivable entity of an id and stores it.",
|
|
54
|
+
...lifecycle("archivable"),
|
|
55
|
+
compile: (target, options) => invoking(target, options, "Archive", "archive")
|
|
56
|
+
});
|
|
57
|
+
export const unarchive = useCasePluginOf("unarchive", {
|
|
58
|
+
description: "Makes the archived entity of an id active again and stores it.",
|
|
59
|
+
...lifecycle("archivable"),
|
|
60
|
+
compile: (target, options) => invoking(target, options, "Unarchive", "restore")
|
|
61
|
+
});
|
|
62
|
+
function tagging(target, options, verb, method) {
|
|
63
|
+
checkOptions(target, options, ["as", "owner", ...HOOKS]);
|
|
64
|
+
const owner = ownerOf(target, options);
|
|
65
|
+
const hooks = hooksOf(target, options);
|
|
66
|
+
const notFound = errorOf(target, options, "notFound", UseCaseEntityNotFound);
|
|
67
|
+
const { item } = target.field("tags").field;
|
|
68
|
+
return {
|
|
69
|
+
...metaOf(target, options, verb),
|
|
70
|
+
blueprint: fields(() => ({
|
|
71
|
+
id: idOf(target),
|
|
72
|
+
tags: listOf(item, { minItems: 1 })
|
|
73
|
+
})),
|
|
74
|
+
response: fields(() => ({ [asOf(target, options)]: target.entity })),
|
|
75
|
+
...contextOf(owner),
|
|
76
|
+
dependencies: joinDependencies(target, { repository: target.repository }, hooks),
|
|
77
|
+
errors: distinct(notFound, ...hooks.throws),
|
|
78
|
+
steps: [
|
|
79
|
+
...loadSteps(owner, notFound),
|
|
80
|
+
...hooks.before("$item"),
|
|
81
|
+
{ invoke: "$item", method, args: `${hooks.input}.tags` },
|
|
82
|
+
{ update: "repository", value: "$item", as: "saved" },
|
|
83
|
+
...hooks.after("$saved"),
|
|
84
|
+
{ return: { [asOf(target, options)]: "$saved" } }
|
|
85
|
+
]
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
export const tag = useCasePluginOf("tag", {
|
|
89
|
+
description: "Adds tags to the taggable entity of an id and stores it.",
|
|
90
|
+
...lifecycle("taggable"),
|
|
91
|
+
compile: (target, options) => tagging(target, options, "Tag", "tag")
|
|
92
|
+
});
|
|
93
|
+
export const untag = useCasePluginOf("untag", {
|
|
94
|
+
description: "Removes tags from the taggable entity of an id and stores it.",
|
|
95
|
+
...lifecycle("taggable"),
|
|
96
|
+
compile: (target, options) => tagging(target, options, "Untag", "untag")
|
|
97
|
+
});
|
|
@@ -12,8 +12,8 @@ export declare function literal(value: unknown): unknown;
|
|
|
12
12
|
export declare function checkOptions(target: UseCaseTargetInfo, options: Options, allowed: readonly string[]): void;
|
|
13
13
|
/** The options of the use cases that write: `before`, `after`, `dependencies`, `throws`. */
|
|
14
14
|
export declare const HOOKS: readonly ["before", "after", "dependencies", "throws"];
|
|
15
|
-
/** The name, description, tags
|
|
16
|
-
export declare function metaOf(target: UseCaseTargetInfo, options: Options, verb: string): Pick<UseCaseCompilation, "name" | "description" | "tags" | "deprecated">;
|
|
15
|
+
/** The name, description, tags, `deprecated`, route and access of the use case. */
|
|
16
|
+
export declare function metaOf(target: UseCaseTargetInfo, options: Options, verb: string): Pick<UseCaseCompilation, "name" | "description" | "tags" | "deprecated" | "route" | "access">;
|
|
17
17
|
/** The key of the response that holds the entity: the option `as`, `item` by default. */
|
|
18
18
|
export declare function asOf(target: UseCaseTargetInfo, options: Options): string;
|
|
19
19
|
/** The error of `kind` the options give, else `fallback`. */
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { blueprintBuilderOf } from "../../core/blueprint/blueprint.js";
|
|
2
|
+
import { isDeclaredException } from "../../core/exception/exception.js";
|
|
3
|
+
import { UseCaseEntityNotFound } from "../../core/use-case/exceptions.js";
|
|
4
|
+
import { isPlain } from "../../core/use-case/steps/references.js";
|
|
5
|
+
export const fields = blueprintBuilderOf();
|
|
6
|
+
const IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
7
|
+
export const capitalize = (key) => `${key.charAt(0).toUpperCase()}${key.slice(1)}`;
|
|
8
|
+
export function literal(value) {
|
|
9
|
+
if (typeof value === "string") {
|
|
10
|
+
return value.startsWith("$") ? { literal: value } : value;
|
|
11
|
+
}
|
|
12
|
+
if (Array.isArray(value))
|
|
13
|
+
return value.map(literal);
|
|
14
|
+
if (isPlain(value)) {
|
|
15
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, literal(item)]));
|
|
16
|
+
}
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
export function checkOptions(target, options, allowed) {
|
|
20
|
+
const known = [...COMMON, ...allowed];
|
|
21
|
+
for (const key of Object.keys(options)) {
|
|
22
|
+
if (!known.includes(key)) {
|
|
23
|
+
target.unavailable(`it has no option "${key}" (options: ${known.join(", ")})`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const COMMON = [
|
|
28
|
+
"name",
|
|
29
|
+
"description",
|
|
30
|
+
"tags",
|
|
31
|
+
"deprecated",
|
|
32
|
+
"errors",
|
|
33
|
+
"route",
|
|
34
|
+
"access"
|
|
35
|
+
];
|
|
36
|
+
export const HOOKS = ["before", "after", "dependencies", "throws"];
|
|
37
|
+
export function metaOf(target, options, verb) {
|
|
38
|
+
const { name, description, tags, deprecated, route, access } = options;
|
|
39
|
+
if (name !== undefined && (typeof name !== "string" || name === "")) {
|
|
40
|
+
target.unavailable("the option name must be a non-empty string");
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
name: name ?? `${verb}${target.entityName}`,
|
|
44
|
+
...description === undefined ? {} : { description },
|
|
45
|
+
...tags === undefined ? {} : { tags },
|
|
46
|
+
...deprecated === undefined ? {} : { deprecated },
|
|
47
|
+
...route === undefined ? {} : { route },
|
|
48
|
+
...access === undefined ? {} : { access }
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export function asOf(target, options) {
|
|
52
|
+
const { as = "item" } = options;
|
|
53
|
+
if (typeof as !== "string" || !IDENTIFIER.test(as)) {
|
|
54
|
+
target.unavailable("the option as must be an identifier");
|
|
55
|
+
}
|
|
56
|
+
return as;
|
|
57
|
+
}
|
|
58
|
+
export function errorOf(target, options, kind, fallback) {
|
|
59
|
+
const { errors } = options;
|
|
60
|
+
if (errors === undefined)
|
|
61
|
+
return fallback;
|
|
62
|
+
if (!isPlain(errors)) {
|
|
63
|
+
return target.unavailable("the option errors must be an object of exception classes");
|
|
64
|
+
}
|
|
65
|
+
const given = errors[kind];
|
|
66
|
+
if (given === undefined)
|
|
67
|
+
return fallback;
|
|
68
|
+
if (!isDeclaredException(given)) {
|
|
69
|
+
return target.unavailable(`errors.${kind} must be a class made by exceptionOf`);
|
|
70
|
+
}
|
|
71
|
+
return given;
|
|
72
|
+
}
|
|
73
|
+
export function keysOf(target, options, option) {
|
|
74
|
+
const given = options[option];
|
|
75
|
+
if (given === undefined)
|
|
76
|
+
return [];
|
|
77
|
+
if (!Array.isArray(given) || given.some((key) => typeof key !== "string")) {
|
|
78
|
+
return target.unavailable(`the option ${option} must be a list of keys`);
|
|
79
|
+
}
|
|
80
|
+
return given.map((key) => target.field(key));
|
|
81
|
+
}
|
|
82
|
+
export function valuesOf(target, options, option) {
|
|
83
|
+
const given = options[option];
|
|
84
|
+
if (given === undefined)
|
|
85
|
+
return {};
|
|
86
|
+
if (!isPlain(given)) {
|
|
87
|
+
return target.unavailable(`the option ${option} must be an object`);
|
|
88
|
+
}
|
|
89
|
+
for (const key of Object.keys(given)) {
|
|
90
|
+
if (target.field(key).identity) {
|
|
91
|
+
target.unavailable(`${option}.${key} is an identity key`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return given;
|
|
95
|
+
}
|
|
96
|
+
export function ownerOf(target, options) {
|
|
97
|
+
const { owner } = options;
|
|
98
|
+
if (owner === undefined)
|
|
99
|
+
return;
|
|
100
|
+
if (!isPlain(owner) || typeof owner.context !== "string" || !IDENTIFIER.test(owner.context) || owner.key !== undefined && typeof owner.key !== "string") {
|
|
101
|
+
return target.unavailable("the option owner must be { key?, context }, context naming a context key");
|
|
102
|
+
}
|
|
103
|
+
const plan = target.field(owner.key ?? "ownerId");
|
|
104
|
+
if (plan.identity || plan.optional) {
|
|
105
|
+
target.unavailable(`owner.key "${plan.key}" must be a required key`);
|
|
106
|
+
}
|
|
107
|
+
if (plan.kind === "entity" && plan.related) {
|
|
108
|
+
return {
|
|
109
|
+
plan,
|
|
110
|
+
context: owner.context,
|
|
111
|
+
field: plan.related.blueprint.properties.id,
|
|
112
|
+
reference: `$item.${plan.key}.id`
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
if (plan.kind !== "property") {
|
|
116
|
+
target.unavailable(`owner.key "${plan.key}" must hold an id or an entity`);
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
plan,
|
|
120
|
+
context: owner.context,
|
|
121
|
+
field: plan.field,
|
|
122
|
+
reference: `$item.${plan.key}`
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
export function contextOf(owner) {
|
|
126
|
+
return owner ? { context: fields(() => ({ [owner.context]: owner.field })) } : {};
|
|
127
|
+
}
|
|
128
|
+
export function loadSteps(owner, notFound) {
|
|
129
|
+
return [
|
|
130
|
+
{ find: "repository.findById", args: ["$input.id"], as: "item" },
|
|
131
|
+
{ ensure: "exists", value: "$item", error: notFound },
|
|
132
|
+
...owner ? [
|
|
133
|
+
{
|
|
134
|
+
ensure: "equals",
|
|
135
|
+
value: owner.reference,
|
|
136
|
+
other: `$context.${owner.context}`,
|
|
137
|
+
error: notFound
|
|
138
|
+
}
|
|
139
|
+
] : []
|
|
140
|
+
];
|
|
141
|
+
}
|
|
142
|
+
export function hooksOf(target, options) {
|
|
143
|
+
const { before, after, dependencies = {}, throws = [] } = options;
|
|
144
|
+
for (const [key, hook] of [
|
|
145
|
+
["before", before],
|
|
146
|
+
["after", after]
|
|
147
|
+
]) {
|
|
148
|
+
if (hook !== undefined && typeof hook !== "function") {
|
|
149
|
+
target.unavailable(`the option ${key} must be a function`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (!isPlain(dependencies) || Object.keys(dependencies).some((key) => !IDENTIFIER.test(key))) {
|
|
153
|
+
target.unavailable("the option dependencies must be an object of repositories, use cases or environments by name");
|
|
154
|
+
}
|
|
155
|
+
if (!Array.isArray(throws) || throws.some((error) => !isDeclaredException(error))) {
|
|
156
|
+
target.unavailable("the option throws must be a list of classes made by exceptionOf");
|
|
157
|
+
}
|
|
158
|
+
const input = before === undefined ? "$input" : "$prepared";
|
|
159
|
+
return {
|
|
160
|
+
input,
|
|
161
|
+
dependencies,
|
|
162
|
+
throws,
|
|
163
|
+
before: (entity) => before === undefined ? [] : [
|
|
164
|
+
{
|
|
165
|
+
run: "before",
|
|
166
|
+
fn: async (given, ...rest) => {
|
|
167
|
+
const { context, dependencies } = rest.at(-1);
|
|
168
|
+
const replaced = await before({
|
|
169
|
+
input: given,
|
|
170
|
+
...entity === undefined ? {} : { entity: rest[0] },
|
|
171
|
+
dependencies,
|
|
172
|
+
context
|
|
173
|
+
});
|
|
174
|
+
return replaced === undefined ? given : replaced;
|
|
175
|
+
},
|
|
176
|
+
args: entity === undefined ? ["$input"] : ["$input", entity],
|
|
177
|
+
as: "prepared"
|
|
178
|
+
}
|
|
179
|
+
],
|
|
180
|
+
after: (entity) => after === undefined ? [] : [
|
|
181
|
+
{
|
|
182
|
+
run: "after",
|
|
183
|
+
fn: async (written, given, { context, dependencies }) => {
|
|
184
|
+
await after({
|
|
185
|
+
entity: written,
|
|
186
|
+
input: given,
|
|
187
|
+
dependencies,
|
|
188
|
+
context
|
|
189
|
+
});
|
|
190
|
+
},
|
|
191
|
+
args: [entity, input]
|
|
192
|
+
}
|
|
193
|
+
]
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
export function joinDependencies(target, made, hooks) {
|
|
197
|
+
for (const key of Object.keys(hooks.dependencies)) {
|
|
198
|
+
if (Object.hasOwn(made, key)) {
|
|
199
|
+
target.unavailable(`dependencies.${key} is a name the use case gives its own dependency`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return { ...made, ...hooks.dependencies };
|
|
203
|
+
}
|
|
204
|
+
export const idOf = (target) => target.field("id").field;
|
|
205
|
+
export const distinct = (...errors) => [...new Set(errors)];
|
|
206
|
+
export function entityUseCase(target, options, verb, allowed, steps, hooks = false) {
|
|
207
|
+
checkOptions(target, options, [
|
|
208
|
+
"as",
|
|
209
|
+
"owner",
|
|
210
|
+
...allowed,
|
|
211
|
+
...hooks ? HOOKS : []
|
|
212
|
+
]);
|
|
213
|
+
const owner = ownerOf(target, options);
|
|
214
|
+
const notFound = errorOf(target, options, "notFound", UseCaseEntityNotFound);
|
|
215
|
+
const hooked = hooksOf(target, options);
|
|
216
|
+
const body = steps({ owner, input: hooked.input });
|
|
217
|
+
return {
|
|
218
|
+
...metaOf(target, options, verb),
|
|
219
|
+
blueprint: fields(() => ({ id: idOf(target) })),
|
|
220
|
+
response: fields(() => ({ [asOf(target, options)]: target.entity })),
|
|
221
|
+
...contextOf(owner),
|
|
222
|
+
dependencies: joinDependencies(target, { repository: target.repository }, hooked),
|
|
223
|
+
errors: distinct(notFound, ...hooked.throws),
|
|
224
|
+
steps: [
|
|
225
|
+
...loadSteps(owner, notFound),
|
|
226
|
+
...hooked.before("$item"),
|
|
227
|
+
...body.steps,
|
|
228
|
+
...hooked.after(body.result),
|
|
229
|
+
{ return: { [asOf(target, options)]: body.result } }
|
|
230
|
+
]
|
|
231
|
+
};
|
|
232
|
+
}
|