@afora/core 0.0.9 → 0.0.12
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 +62 -189
- 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 +17 -5
- package/dist/index-3aae5jfq.js +0 -2997
- package/dist/index-ny3as79r.js +0 -4567
- package/dist/index-xegv6a1p.js +0 -4291
package/dist/index-3aae5jfq.js
DELETED
|
@@ -1,2997 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
__export,
|
|
3
|
-
isProperty,
|
|
4
|
-
fieldOf,
|
|
5
|
-
Kind2,
|
|
6
|
-
isBlueprint,
|
|
7
|
-
isRecordClass,
|
|
8
|
-
recordFieldOf,
|
|
9
|
-
Exception2,
|
|
10
|
-
isDeclaredException,
|
|
11
|
-
exceptionOf2,
|
|
12
|
-
blueprintOfVariant,
|
|
13
|
-
namedVariantOf,
|
|
14
|
-
variantOfValue,
|
|
15
|
-
isBlueprintVariant,
|
|
16
|
-
recordNameOf2,
|
|
17
|
-
declaredRecordNameOf,
|
|
18
|
-
isBlueprintList,
|
|
19
|
-
isEnvironment,
|
|
20
|
-
silentLogger,
|
|
21
|
-
isLoggerDeclaration,
|
|
22
|
-
isLogger,
|
|
23
|
-
resolveLogger,
|
|
24
|
-
clock,
|
|
25
|
-
elapsed,
|
|
26
|
-
logFailure,
|
|
27
|
-
writeLog,
|
|
28
|
-
recordDataOf,
|
|
29
|
-
recordFieldDataOf,
|
|
30
|
-
optionalOf2,
|
|
31
|
-
isVersion,
|
|
32
|
-
pluginsOf2,
|
|
33
|
-
hasPlugin2,
|
|
34
|
-
InvalidEntitySchemaVersion2,
|
|
35
|
-
isEntityClass2,
|
|
36
|
-
isEntity2,
|
|
37
|
-
enqueueLifecycle,
|
|
38
|
-
EntityClock,
|
|
39
|
-
EntityUniqueKeys2,
|
|
40
|
-
EntitySettings2,
|
|
41
|
-
SCHEMA_VERSION_KEY2,
|
|
42
|
-
isVersionedEntityClass,
|
|
43
|
-
identityKeysOf,
|
|
44
|
-
storeEntityVersion,
|
|
45
|
-
InvalidRepositoryQuery2,
|
|
46
|
-
InvalidRepositoryCursor2,
|
|
47
|
-
InvalidRepositoryDeclaration2,
|
|
48
|
-
InvalidRepositoryEntity2,
|
|
49
|
-
InvalidStoredSchemaVersion2,
|
|
50
|
-
InvalidRepositoryData2,
|
|
51
|
-
RepositoryUniqueViolation2,
|
|
52
|
-
RepositoryEntityNotFound2,
|
|
53
|
-
RepositoryConcurrencyConflict2,
|
|
54
|
-
InvalidRepositoryIncrement2,
|
|
55
|
-
frozenJson,
|
|
56
|
-
states,
|
|
57
|
-
findState,
|
|
58
|
-
stateOf,
|
|
59
|
-
contextOf,
|
|
60
|
-
inspectRepository,
|
|
61
|
-
declareRepository,
|
|
62
|
-
relatedRepositoryOf,
|
|
63
|
-
assertUnbound,
|
|
64
|
-
isScalarKey,
|
|
65
|
-
routeDeclarationOf
|
|
66
|
-
} from "./index-xegv6a1p.js";
|
|
67
|
-
|
|
68
|
-
// src/core/event-policy/exceptions.ts
|
|
69
|
-
var exports_exceptions = {};
|
|
70
|
-
__export(exports_exceptions, {
|
|
71
|
-
InvalidEventPolicy: () => InvalidEventPolicy2,
|
|
72
|
-
PublisherNotBound: () => PublisherNotBound2
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
// src/core/use-case/exceptions.ts
|
|
76
|
-
var exports_exceptions2 = {};
|
|
77
|
-
__export(exports_exceptions2, {
|
|
78
|
-
IncompatibleUseCasePlugin: () => IncompatibleUseCasePlugin2,
|
|
79
|
-
InvalidUseCasePlugin: () => InvalidUseCasePlugin2,
|
|
80
|
-
InvalidUseCaseStep: () => InvalidUseCaseStep2,
|
|
81
|
-
UnknownUseCasePlugin: () => UnknownUseCasePlugin2,
|
|
82
|
-
UseCaseEntityConflict: () => UseCaseEntityConflict2,
|
|
83
|
-
UseCaseEntityNotFound: () => UseCaseEntityNotFound2,
|
|
84
|
-
UseCaseInvalidCredentials: () => UseCaseInvalidCredentials2,
|
|
85
|
-
UseCaseUnavailable: () => UseCaseUnavailable2
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
// src/core/use-case/use-case.ts
|
|
89
|
-
var exports_use_case = {};
|
|
90
|
-
__export(exports_use_case, {
|
|
91
|
-
AbstractUseCase: () => AbstractUseCase,
|
|
92
|
-
InvalidUseCaseDeclaration: () => InvalidUseCaseDeclaration2,
|
|
93
|
-
InvalidUseCaseDependencies: () => InvalidUseCaseDependencies2,
|
|
94
|
-
UseCaseAlreadyExecuted: () => UseCaseAlreadyExecuted2,
|
|
95
|
-
isUseCase: () => isUseCase2,
|
|
96
|
-
isUseCaseClass: () => isUseCaseClass2,
|
|
97
|
-
useCaseOf: () => useCaseOf2
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
// src/core/event-policy/exceptions.ts
|
|
101
|
-
var InvalidEventPolicy2 = exceptionOf2({
|
|
102
|
-
layer: "event" /* Event */,
|
|
103
|
-
name: "InvalidEventPolicy",
|
|
104
|
-
message: ({ reason }) => reason
|
|
105
|
-
});
|
|
106
|
-
var PublisherNotBound2 = exceptionOf2({
|
|
107
|
-
layer: "application" /* Application */,
|
|
108
|
-
name: "PublisherNotBound",
|
|
109
|
-
message: ({ publisher }) => `No publisher "${publisher}" is declared: add it to applicationOf({ publishers }).`
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
// src/core/exception/guard.ts
|
|
113
|
-
function guard2(scope, run, resolve) {
|
|
114
|
-
const handle = (error) => {
|
|
115
|
-
const exception = Exception2.from(error, scope);
|
|
116
|
-
const pass = () => {
|
|
117
|
-
throw exception === error ? exception.propagate(scope) : exception;
|
|
118
|
-
};
|
|
119
|
-
return resolve ? resolve(exception, pass) : pass();
|
|
120
|
-
};
|
|
121
|
-
try {
|
|
122
|
-
const result = run();
|
|
123
|
-
return result instanceof Promise ? result.catch(handle) : result;
|
|
124
|
-
} catch (error) {
|
|
125
|
-
return handle(error);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// src/core/use-case/scope.ts
|
|
130
|
-
var ScopeKey = Symbol("afora.use-case.scope");
|
|
131
|
-
function scopedProvider(scope, provider) {
|
|
132
|
-
return scope?.providers.get(provider) ?? provider;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// src/core/repository/cursor.ts
|
|
136
|
-
var VERSION = 1;
|
|
137
|
-
var signatureOf = (orders) => orders.map(({ key, direction = "asc" }) => `${key}:${direction}`);
|
|
138
|
-
function pack(value) {
|
|
139
|
-
if (value === undefined || value === null)
|
|
140
|
-
return null;
|
|
141
|
-
if (value instanceof Date)
|
|
142
|
-
return { d: value.toISOString() };
|
|
143
|
-
if (typeof value === "bigint")
|
|
144
|
-
return { b: value.toString() };
|
|
145
|
-
return value;
|
|
146
|
-
}
|
|
147
|
-
function unpack(value) {
|
|
148
|
-
if (value === null)
|
|
149
|
-
return;
|
|
150
|
-
if (["string", "number", "boolean"].includes(typeof value))
|
|
151
|
-
return value;
|
|
152
|
-
if (typeof value === "object") {
|
|
153
|
-
const { d, b } = value;
|
|
154
|
-
const keys = Object.keys(value);
|
|
155
|
-
if (keys.length === 1 && typeof d === "string") {
|
|
156
|
-
const date = new Date(d);
|
|
157
|
-
if (!Number.isNaN(date.getTime()))
|
|
158
|
-
return date;
|
|
159
|
-
}
|
|
160
|
-
if (keys.length === 1 && typeof b === "string" && /^-?\d+$/.test(b))
|
|
161
|
-
return BigInt(b);
|
|
162
|
-
}
|
|
163
|
-
throw new SyntaxError("value");
|
|
164
|
-
}
|
|
165
|
-
function encodeText(text) {
|
|
166
|
-
let binary = "";
|
|
167
|
-
for (const byte of new TextEncoder().encode(text))
|
|
168
|
-
binary += String.fromCharCode(byte);
|
|
169
|
-
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
170
|
-
}
|
|
171
|
-
function decodeText(encoded) {
|
|
172
|
-
if (!/^[A-Za-z0-9_-]+$/.test(encoded))
|
|
173
|
-
throw new SyntaxError("alphabet");
|
|
174
|
-
const binary = atob(encoded.replaceAll("-", "+").replaceAll("_", "/"));
|
|
175
|
-
return new TextDecoder("utf-8", { fatal: true }).decode(Uint8Array.from(binary, (char) => char.charCodeAt(0)));
|
|
176
|
-
}
|
|
177
|
-
function encodeCursor(orders, row) {
|
|
178
|
-
const payload = {
|
|
179
|
-
v: VERSION,
|
|
180
|
-
k: signatureOf(orders),
|
|
181
|
-
a: orders.map(({ key }) => pack(row[key]))
|
|
182
|
-
};
|
|
183
|
-
return encodeText(JSON.stringify(payload));
|
|
184
|
-
}
|
|
185
|
-
function decodeCursor(source, orders, cursor) {
|
|
186
|
-
const fail = (reason) => {
|
|
187
|
-
throw new InvalidRepositoryCursor2({
|
|
188
|
-
source,
|
|
189
|
-
details: {
|
|
190
|
-
reason,
|
|
191
|
-
issues: [
|
|
192
|
-
{ code: "invalid_value", path: ["after"], message: reason, source }
|
|
193
|
-
]
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
};
|
|
197
|
-
if (typeof cursor !== "string" || cursor === "")
|
|
198
|
-
return fail("Expected a cursor given by findCursor.");
|
|
199
|
-
let payload;
|
|
200
|
-
let values;
|
|
201
|
-
try {
|
|
202
|
-
payload = JSON.parse(decodeText(cursor));
|
|
203
|
-
if (typeof payload !== "object" || payload === null)
|
|
204
|
-
throw new SyntaxError;
|
|
205
|
-
if (!Array.isArray(payload.k) || !Array.isArray(payload.a))
|
|
206
|
-
throw new SyntaxError;
|
|
207
|
-
values = payload.a.map(unpack);
|
|
208
|
-
} catch {
|
|
209
|
-
return fail("The cursor is not one given by findCursor.");
|
|
210
|
-
}
|
|
211
|
-
if (payload.v !== VERSION)
|
|
212
|
-
return fail("The cursor was made by another version.");
|
|
213
|
-
const expected = signatureOf(orders);
|
|
214
|
-
if (payload.k?.length !== expected.length || values.length !== expected.length || expected.some((key, index) => payload.k?.[index] !== key))
|
|
215
|
-
return fail("The cursor was made for another orderBy.");
|
|
216
|
-
return values;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// src/core/repository/provider.ts
|
|
220
|
-
var descriptions = new WeakMap;
|
|
221
|
-
var opaque = Object.freeze({ kind: "opaque" });
|
|
222
|
-
function invalid(reason) {
|
|
223
|
-
throw new InvalidRepositoryDeclaration2({
|
|
224
|
-
source: "providerOf",
|
|
225
|
-
details: { reason }
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
function providerOf2(settings) {
|
|
229
|
-
if (!settings || typeof settings.name !== "string" || settings.name.trim().length === 0 || typeof settings.create !== "function")
|
|
230
|
-
invalid("Expected a provider name and create function.");
|
|
231
|
-
const options = frozenJson(settings.options ?? {}, "Provider options", invalid);
|
|
232
|
-
if (options === null || typeof options !== "object" || Array.isArray(options))
|
|
233
|
-
invalid("Provider options must be an object.");
|
|
234
|
-
const provider = settings.create(options);
|
|
235
|
-
if (!provider || typeof provider !== "object" || ["findOne", "findMany", "insert", "update", "remove", "count"].some((key) => typeof provider[key] !== "function"))
|
|
236
|
-
invalid("Expected a repository provider from create.");
|
|
237
|
-
descriptions.set(provider, Object.freeze({
|
|
238
|
-
kind: "provider",
|
|
239
|
-
name: settings.name,
|
|
240
|
-
options
|
|
241
|
-
}));
|
|
242
|
-
return provider;
|
|
243
|
-
}
|
|
244
|
-
function providerDescriptionOf2(provider) {
|
|
245
|
-
return descriptions.get(provider) ?? opaque;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// src/core/repository/query.ts
|
|
249
|
-
function plain(value) {
|
|
250
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
|
|
251
|
-
}
|
|
252
|
-
function isMembership(value) {
|
|
253
|
-
return plain(value) && Object.keys(value).length === 1 && Array.isArray(value.in);
|
|
254
|
-
}
|
|
255
|
-
var QUERY_OPTIONS = [
|
|
256
|
-
"where",
|
|
257
|
-
"orderBy",
|
|
258
|
-
"limit",
|
|
259
|
-
"offset",
|
|
260
|
-
"withDeleted",
|
|
261
|
-
"withExpired"
|
|
262
|
-
];
|
|
263
|
-
function prepareQuery2(context, input = {}, map = (_key, value) => value, options = QUERY_OPTIONS) {
|
|
264
|
-
const fail = (reason) => {
|
|
265
|
-
throw new InvalidRepositoryQuery2({
|
|
266
|
-
source: context.name,
|
|
267
|
-
details: { reason }
|
|
268
|
-
});
|
|
269
|
-
};
|
|
270
|
-
if (!plain(input))
|
|
271
|
-
return fail("Expected a query object.");
|
|
272
|
-
for (const key of Object.keys(input))
|
|
273
|
-
if (!options.includes(key))
|
|
274
|
-
fail(`Unknown query option "${key}".`);
|
|
275
|
-
const properties = context.entity.blueprint.properties;
|
|
276
|
-
const keyOf = (key) => {
|
|
277
|
-
if (!Object.hasOwn(properties, key))
|
|
278
|
-
fail(`Unknown query field "${key}".`);
|
|
279
|
-
};
|
|
280
|
-
for (const name of ["limit", "offset"]) {
|
|
281
|
-
const value = input[name];
|
|
282
|
-
if (value !== undefined && (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0))
|
|
283
|
-
fail(`Expected a nonnegative integer for ${name}.`);
|
|
284
|
-
}
|
|
285
|
-
if (input.withDeleted !== undefined && typeof input.withDeleted !== "boolean")
|
|
286
|
-
fail("Expected withDeleted to be boolean.");
|
|
287
|
-
if (input.withExpired !== undefined && typeof input.withExpired !== "boolean")
|
|
288
|
-
fail("Expected withExpired to be boolean.");
|
|
289
|
-
let where;
|
|
290
|
-
if (input.where !== undefined) {
|
|
291
|
-
if (!plain(input.where))
|
|
292
|
-
return fail("Expected where to be an object.");
|
|
293
|
-
where = Object.fromEntries(Object.entries(input.where).map(([key, value]) => {
|
|
294
|
-
keyOf(key);
|
|
295
|
-
return [
|
|
296
|
-
key,
|
|
297
|
-
isMembership(value) ? { in: value.in.map((item) => map(key, item)) } : map(key, value)
|
|
298
|
-
];
|
|
299
|
-
}));
|
|
300
|
-
}
|
|
301
|
-
if (input.orderBy !== undefined) {
|
|
302
|
-
if (!Array.isArray(input.orderBy))
|
|
303
|
-
return fail("Expected orderBy to be an array.");
|
|
304
|
-
for (const order of input.orderBy) {
|
|
305
|
-
if (!plain(order) || typeof order.key !== "string" || Object.keys(order).some((key) => key !== "key" && key !== "direction"))
|
|
306
|
-
return fail("Expected an order with key and optional direction.");
|
|
307
|
-
keyOf(order.key);
|
|
308
|
-
if (order.direction !== undefined && order.direction !== "asc" && order.direction !== "desc")
|
|
309
|
-
fail("Expected order direction asc or desc.");
|
|
310
|
-
const field = properties[order.key];
|
|
311
|
-
if (isBlueprint(field) || isBlueprintList(field) || isBlueprintVariant(field) || recordFieldOf(field))
|
|
312
|
-
fail("Only scalar fields may be ordered.");
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
return { ...input, ...where ? { where } : {} };
|
|
316
|
-
}
|
|
317
|
-
function sameValue2(a, b) {
|
|
318
|
-
if (a === b)
|
|
319
|
-
return true;
|
|
320
|
-
if (a instanceof Date || b instanceof Date)
|
|
321
|
-
return a instanceof Date && b instanceof Date && a.getTime() === b.getTime();
|
|
322
|
-
if (Array.isArray(a) || Array.isArray(b))
|
|
323
|
-
return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((value, index) => sameValue2(value, b[index]));
|
|
324
|
-
if (!plain(a) || !plain(b))
|
|
325
|
-
return false;
|
|
326
|
-
const keys = Object.keys(a);
|
|
327
|
-
return keys.length === Object.keys(b).length && keys.every((key) => Object.hasOwn(b, key) && sameValue2(a[key], b[key]));
|
|
328
|
-
}
|
|
329
|
-
function isExpired(value, now) {
|
|
330
|
-
if (value === undefined || value === null)
|
|
331
|
-
return false;
|
|
332
|
-
const at = value instanceof Date ? value.getTime() : typeof value === "string" ? Date.parse(value) : Number.NaN;
|
|
333
|
-
return !Number.isNaN(at) && at <= now;
|
|
334
|
-
}
|
|
335
|
-
function nowOf(query) {
|
|
336
|
-
const now = query.now === undefined ? Number.NaN : Date.parse(query.now);
|
|
337
|
-
return Number.isNaN(now) ? Date.now() : now;
|
|
338
|
-
}
|
|
339
|
-
function matchesQuery2(data, query, expiry, now = expiry === undefined ? 0 : nowOf(query)) {
|
|
340
|
-
return (query.withDeleted === true || data.deletedAt === undefined) && (expiry === undefined || query.withExpired === true || !isExpired(data[expiry], now)) && Object.entries(query.where ?? {}).every(([key, value]) => isMembership(value) ? value.in.some((candidate) => sameValue2(data[key], candidate)) : sameValue2(data[key], value));
|
|
341
|
-
}
|
|
342
|
-
function codePoints(text) {
|
|
343
|
-
return Array.from(text, (char) => char.codePointAt(0));
|
|
344
|
-
}
|
|
345
|
-
function rank(value) {
|
|
346
|
-
return value instanceof Date ? 1 : { number: 0, bigint: 0, string: 2, boolean: 3 }[typeof value] ?? 4;
|
|
347
|
-
}
|
|
348
|
-
function compareScalars(a, b) {
|
|
349
|
-
const [left, right] = [rank(a), rank(b)];
|
|
350
|
-
if (left !== right)
|
|
351
|
-
return left - right;
|
|
352
|
-
if (typeof a === "string" && typeof b === "string") {
|
|
353
|
-
const [x, y] = [codePoints(a), codePoints(b)];
|
|
354
|
-
for (let index = 0;index < Math.min(x.length, y.length); index++) {
|
|
355
|
-
const difference = x[index] - y[index];
|
|
356
|
-
if (difference !== 0)
|
|
357
|
-
return difference;
|
|
358
|
-
}
|
|
359
|
-
return x.length - y.length;
|
|
360
|
-
}
|
|
361
|
-
const [x, y] = [
|
|
362
|
-
a instanceof Date ? a.getTime() : Number(a),
|
|
363
|
-
b instanceof Date ? b.getTime() : Number(b)
|
|
364
|
-
];
|
|
365
|
-
return x < y ? -1 : x > y ? 1 : 0;
|
|
366
|
-
}
|
|
367
|
-
function compareRows(orders, a, b) {
|
|
368
|
-
for (const { key, direction } of orders) {
|
|
369
|
-
const [x, y] = [a[key], b[key]];
|
|
370
|
-
const [missingX, missingY] = [
|
|
371
|
-
x === undefined || x === null,
|
|
372
|
-
y === undefined || y === null
|
|
373
|
-
];
|
|
374
|
-
let result = missingX || missingY ? Number(!missingX) - Number(!missingY) : compareScalars(x, y);
|
|
375
|
-
if (direction === "desc")
|
|
376
|
-
result = -result;
|
|
377
|
-
if (result !== 0)
|
|
378
|
-
return result;
|
|
379
|
-
}
|
|
380
|
-
return 0;
|
|
381
|
-
}
|
|
382
|
-
function orderRows(rows, query) {
|
|
383
|
-
const orders = query.orderBy ?? [];
|
|
384
|
-
return [...rows].sort((a, b) => compareRows(orders, a, b));
|
|
385
|
-
}
|
|
386
|
-
function rowsAfter(rows, orders, after) {
|
|
387
|
-
const position = Object.fromEntries(orders.map(({ key }, index) => [key, after[index]]));
|
|
388
|
-
return rows.filter((row) => compareRows(orders, row, position) > 0);
|
|
389
|
-
}
|
|
390
|
-
function selectRows2(rows, query, expiry) {
|
|
391
|
-
const now = expiry === undefined ? 0 : nowOf(query);
|
|
392
|
-
const ordered = orderRows([...rows].filter((data) => matchesQuery2(data, query, expiry, now)), query);
|
|
393
|
-
const matched = query.after === undefined ? ordered : rowsAfter(ordered, query.orderBy ?? [], query.after);
|
|
394
|
-
const start = query.offset ?? 0;
|
|
395
|
-
return matched.slice(start, query.limit === undefined ? undefined : start + query.limit);
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// src/core/repository/in-memory.ts
|
|
399
|
-
var tableKeys = new WeakMap;
|
|
400
|
-
function tableKeyOf(entity) {
|
|
401
|
-
let key = tableKeys.get(entity);
|
|
402
|
-
if (key === undefined) {
|
|
403
|
-
key = declaredRecordNameOf(entity) ?? entity;
|
|
404
|
-
tableKeys.set(entity, key);
|
|
405
|
-
}
|
|
406
|
-
return key;
|
|
407
|
-
}
|
|
408
|
-
function idOf(data) {
|
|
409
|
-
return data.id;
|
|
410
|
-
}
|
|
411
|
-
function snapshotOf(tables) {
|
|
412
|
-
return new Map([...tables].map(([key, table]) => [key, new Map(table)]));
|
|
413
|
-
}
|
|
414
|
-
var copy = (data) => structuredClone(data);
|
|
415
|
-
function scopeOf(initial, root) {
|
|
416
|
-
let tables = initial;
|
|
417
|
-
let held;
|
|
418
|
-
let closed = false;
|
|
419
|
-
const tableOf = (context) => {
|
|
420
|
-
const key = tableKeyOf(context.entity);
|
|
421
|
-
let table = tables.get(key);
|
|
422
|
-
if (!table) {
|
|
423
|
-
table = new Map;
|
|
424
|
-
tables.set(key, table);
|
|
425
|
-
}
|
|
426
|
-
return table;
|
|
427
|
-
};
|
|
428
|
-
const whenIdle = async (work) => {
|
|
429
|
-
while (held)
|
|
430
|
-
await held;
|
|
431
|
-
if (closed) {
|
|
432
|
-
throw new InvalidRepositoryDeclaration2({
|
|
433
|
-
source: "inMemory",
|
|
434
|
-
details: {
|
|
435
|
-
reason: "The transaction has ended; its provider cannot be used."
|
|
436
|
-
}
|
|
437
|
-
});
|
|
438
|
-
}
|
|
439
|
-
return work();
|
|
440
|
-
};
|
|
441
|
-
const assertUnique = (context, data) => {
|
|
442
|
-
for (const stored of tableOf(context).values()) {
|
|
443
|
-
if (idOf(stored) === idOf(data))
|
|
444
|
-
continue;
|
|
445
|
-
const key = context.unique.find((key) => data[key] !== undefined && sameValue2(stored[key], data[key]));
|
|
446
|
-
if (key !== undefined) {
|
|
447
|
-
throw new RepositoryUniqueViolation2({
|
|
448
|
-
source: context.name,
|
|
449
|
-
details: { key }
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
};
|
|
454
|
-
const provider = {
|
|
455
|
-
capabilities: Object.freeze({
|
|
456
|
-
transactions: true,
|
|
457
|
-
keyset: true,
|
|
458
|
-
versioning: true,
|
|
459
|
-
atomic: true,
|
|
460
|
-
expiry: true
|
|
461
|
-
}),
|
|
462
|
-
findOne: (context, query = {}) => whenIdle(() => {
|
|
463
|
-
const [first] = selectRows2(tableOf(context).values(), { ...query, limit: Math.min(query.limit ?? 1, 1) }, context.expiry?.key);
|
|
464
|
-
return first === undefined ? undefined : copy(first);
|
|
465
|
-
}),
|
|
466
|
-
findMany: (context, query = {}) => whenIdle(() => selectRows2(tableOf(context).values(), query, context.expiry?.key).map(copy)),
|
|
467
|
-
insert: (context, data) => whenIdle(() => {
|
|
468
|
-
const table = tableOf(context);
|
|
469
|
-
if (table.has(idOf(data))) {
|
|
470
|
-
throw new RepositoryUniqueViolation2({
|
|
471
|
-
source: context.name,
|
|
472
|
-
details: { key: "id" }
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
assertUnique(context, data);
|
|
476
|
-
table.set(idOf(data), copy(data));
|
|
477
|
-
}),
|
|
478
|
-
update: (context, data, options = {}) => whenIdle(() => {
|
|
479
|
-
const table = tableOf(context);
|
|
480
|
-
const stored = table.get(idOf(data));
|
|
481
|
-
if (!stored) {
|
|
482
|
-
throw new RepositoryEntityNotFound2({
|
|
483
|
-
source: context.name,
|
|
484
|
-
details: { id: idOf(data) }
|
|
485
|
-
});
|
|
486
|
-
}
|
|
487
|
-
const { expectedVersion: expected } = options;
|
|
488
|
-
if (expected !== undefined && stored.version !== expected) {
|
|
489
|
-
throw new RepositoryConcurrencyConflict2({
|
|
490
|
-
source: context.name,
|
|
491
|
-
details: {
|
|
492
|
-
id: idOf(data),
|
|
493
|
-
expected,
|
|
494
|
-
...typeof stored.version === "number" ? { actual: stored.version } : {}
|
|
495
|
-
}
|
|
496
|
-
});
|
|
497
|
-
}
|
|
498
|
-
assertUnique(context, data);
|
|
499
|
-
table.set(idOf(data), copy(data));
|
|
500
|
-
}),
|
|
501
|
-
increment: (context, id, deltas, options = {}) => whenIdle(() => {
|
|
502
|
-
const table = tableOf(context);
|
|
503
|
-
const found = table.get(id);
|
|
504
|
-
const expiry = context.expiry?.key;
|
|
505
|
-
const expired = found !== undefined && expiry !== undefined && !matchesQuery2(found, { withDeleted: true, now: options.now }, expiry);
|
|
506
|
-
const stored = expired ? undefined : found;
|
|
507
|
-
if (!stored || stored.deletedAt != null) {
|
|
508
|
-
if (!options.create) {
|
|
509
|
-
throw new RepositoryEntityNotFound2({
|
|
510
|
-
source: context.name,
|
|
511
|
-
details: { id }
|
|
512
|
-
});
|
|
513
|
-
}
|
|
514
|
-
if (stored) {
|
|
515
|
-
throw new RepositoryUniqueViolation2({
|
|
516
|
-
source: context.name,
|
|
517
|
-
details: { key: "id" }
|
|
518
|
-
});
|
|
519
|
-
}
|
|
520
|
-
const created = { ...options.create, id };
|
|
521
|
-
assertUnique(context, created);
|
|
522
|
-
table.set(id, copy(created));
|
|
523
|
-
return copy(created);
|
|
524
|
-
}
|
|
525
|
-
const next = { ...stored, ...options.set };
|
|
526
|
-
for (const [key, delta] of Object.entries(deltas)) {
|
|
527
|
-
const value = stored[key];
|
|
528
|
-
next[key] = (typeof value === "number" ? value : 0) + delta;
|
|
529
|
-
}
|
|
530
|
-
table.set(id, copy(next));
|
|
531
|
-
return copy(next);
|
|
532
|
-
}),
|
|
533
|
-
remove: (context, id) => whenIdle(() => {
|
|
534
|
-
if (!tableOf(context).delete(id)) {
|
|
535
|
-
throw new RepositoryEntityNotFound2({
|
|
536
|
-
source: context.name,
|
|
537
|
-
details: { id }
|
|
538
|
-
});
|
|
539
|
-
}
|
|
540
|
-
}),
|
|
541
|
-
count: (context, query = {}) => whenIdle(() => {
|
|
542
|
-
const expiry = context.expiry?.key;
|
|
543
|
-
const now = expiry === undefined ? 0 : nowOf(query);
|
|
544
|
-
let total = 0;
|
|
545
|
-
for (const data of tableOf(context).values()) {
|
|
546
|
-
if (matchesQuery2(data, query, expiry, now))
|
|
547
|
-
total++;
|
|
548
|
-
}
|
|
549
|
-
return total;
|
|
550
|
-
}),
|
|
551
|
-
async transaction(run) {
|
|
552
|
-
let release;
|
|
553
|
-
const child = await whenIdle(() => {
|
|
554
|
-
held = new Promise((resolve) => {
|
|
555
|
-
release = resolve;
|
|
556
|
-
});
|
|
557
|
-
return scopeOf(snapshotOf(tables), false);
|
|
558
|
-
});
|
|
559
|
-
try {
|
|
560
|
-
const result = await run(child.provider);
|
|
561
|
-
tables = child.close();
|
|
562
|
-
return result;
|
|
563
|
-
} finally {
|
|
564
|
-
child.close();
|
|
565
|
-
held = undefined;
|
|
566
|
-
release();
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
};
|
|
570
|
-
return {
|
|
571
|
-
provider,
|
|
572
|
-
close() {
|
|
573
|
-
if (!root)
|
|
574
|
-
closed = true;
|
|
575
|
-
return tables;
|
|
576
|
-
}
|
|
577
|
-
};
|
|
578
|
-
}
|
|
579
|
-
function inMemoryProvider2() {
|
|
580
|
-
return providerOf2({
|
|
581
|
-
name: "inMemory",
|
|
582
|
-
options: {},
|
|
583
|
-
create: () => scopeOf(new Map, true).provider
|
|
584
|
-
});
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
// src/core/repository/plan.ts
|
|
588
|
-
var ID = Object.freeze({ access: "id" });
|
|
589
|
-
var UNIQUE = Object.freeze({ access: "unique" });
|
|
590
|
-
var SCAN = Object.freeze({ access: "scan" });
|
|
591
|
-
function isMembership2(value) {
|
|
592
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 1 && Array.isArray(value.in);
|
|
593
|
-
}
|
|
594
|
-
function planQuery(repository, query = {}) {
|
|
595
|
-
const where = query.where ?? {};
|
|
596
|
-
const keys = Object.keys(where);
|
|
597
|
-
if (keys.includes("id"))
|
|
598
|
-
return ID;
|
|
599
|
-
if (keys.some((key) => repository.unique.includes(key)))
|
|
600
|
-
return UNIQUE;
|
|
601
|
-
const equal = new Set(keys.filter((key) => !isMembership2(where[key])));
|
|
602
|
-
const first = query.orderBy?.[0]?.key;
|
|
603
|
-
let best;
|
|
604
|
-
for (const entry of Object.entries(repository.indexes ?? {})) {
|
|
605
|
-
const [, index] = entry;
|
|
606
|
-
if (!index.where.every((key) => equal.has(key)))
|
|
607
|
-
continue;
|
|
608
|
-
if (first !== undefined && index.orderBy !== first)
|
|
609
|
-
continue;
|
|
610
|
-
if (best === undefined || index.where.length > best[1].where.length)
|
|
611
|
-
best = entry;
|
|
612
|
-
}
|
|
613
|
-
return best === undefined ? SCAN : Object.freeze({ access: "index", index: best[0] });
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
// src/core/repository/upcast.ts
|
|
617
|
-
var isPlain2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
618
|
-
function stepOf(field) {
|
|
619
|
-
if (field === undefined)
|
|
620
|
-
return { kind: "opaque" };
|
|
621
|
-
if (isBlueprintList(field))
|
|
622
|
-
return { kind: "list", item: stepOf(field.item) };
|
|
623
|
-
if (isBlueprint(field)) {
|
|
624
|
-
return { kind: "collection", fields: stepsOf(field.properties) };
|
|
625
|
-
}
|
|
626
|
-
const record = recordFieldOf(field);
|
|
627
|
-
if (record) {
|
|
628
|
-
if (isEntityClass2(record.record))
|
|
629
|
-
return { kind: "opaque" };
|
|
630
|
-
return {
|
|
631
|
-
kind: "collection",
|
|
632
|
-
fields: stepsOf(record.record.blueprint.properties)
|
|
633
|
-
};
|
|
634
|
-
}
|
|
635
|
-
const property = fieldOf(field);
|
|
636
|
-
if (!isProperty(property))
|
|
637
|
-
return { kind: "opaque" };
|
|
638
|
-
const { fill, renamedFrom } = property.type;
|
|
639
|
-
return {
|
|
640
|
-
kind: "property",
|
|
641
|
-
...fill === undefined ? {} : { fill },
|
|
642
|
-
...renamedFrom === undefined ? {} : { renamedFrom }
|
|
643
|
-
};
|
|
644
|
-
}
|
|
645
|
-
function stepsOf(properties) {
|
|
646
|
-
return {
|
|
647
|
-
fields: new Map(Object.entries(properties).map(([key, field]) => [key, stepOf(field)]))
|
|
648
|
-
};
|
|
649
|
-
}
|
|
650
|
-
function declaresSteps(steps) {
|
|
651
|
-
const visit = (step) => {
|
|
652
|
-
switch (step.kind) {
|
|
653
|
-
case "property":
|
|
654
|
-
return step.fill !== undefined || step.renamedFrom !== undefined;
|
|
655
|
-
case "collection":
|
|
656
|
-
return declaresSteps(step.fields);
|
|
657
|
-
case "list":
|
|
658
|
-
return visit(step.item);
|
|
659
|
-
case "opaque":
|
|
660
|
-
return false;
|
|
661
|
-
}
|
|
662
|
-
};
|
|
663
|
-
for (const step of steps.fields.values())
|
|
664
|
-
if (visit(step))
|
|
665
|
-
return true;
|
|
666
|
-
return false;
|
|
667
|
-
}
|
|
668
|
-
var plans = new WeakMap;
|
|
669
|
-
function upcastPlanOf(entity) {
|
|
670
|
-
const cached = plans.get(entity);
|
|
671
|
-
if (cached !== undefined)
|
|
672
|
-
return cached ?? undefined;
|
|
673
|
-
const plan = fullUpcastPlanOf(entity);
|
|
674
|
-
const needed = plan.versioned || plan.renamed || declaresSteps(plan.fields) ? plan : null;
|
|
675
|
-
plans.set(entity, needed);
|
|
676
|
-
return needed ?? undefined;
|
|
677
|
-
}
|
|
678
|
-
function fullUpcastPlanOf(entity) {
|
|
679
|
-
const settings = entity[EntitySettings2];
|
|
680
|
-
return {
|
|
681
|
-
versioned: settings?.schemaVersioned === true,
|
|
682
|
-
version: settings?.schemaVersion ?? 1,
|
|
683
|
-
renamed: (settings?.previousNames.length ?? 0) > 0,
|
|
684
|
-
upgrade: settings?.upgrade ?? {},
|
|
685
|
-
fields: stepsOf(entity.blueprint.properties)
|
|
686
|
-
};
|
|
687
|
-
}
|
|
688
|
-
var join = (path, key) => path ? `${path}.${key}` : key;
|
|
689
|
-
var fillOf = (value) => typeof value === "object" && value !== null ? structuredClone(value) : value;
|
|
690
|
-
function applySteps(steps, value, drop, path, report) {
|
|
691
|
-
const result = { ...value };
|
|
692
|
-
for (const [key, step] of steps.fields) {
|
|
693
|
-
if (step.kind !== "property" || step.renamedFrom === undefined)
|
|
694
|
-
continue;
|
|
695
|
-
const from = step.renamedFrom;
|
|
696
|
-
if (result[key] === undefined && Object.hasOwn(result, from)) {
|
|
697
|
-
result[key] = result[from];
|
|
698
|
-
delete result[from];
|
|
699
|
-
report?.renamed.push(join(path, key));
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
for (const [key, step] of steps.fields) {
|
|
703
|
-
const current = result[key];
|
|
704
|
-
if (current === undefined || current === null) {
|
|
705
|
-
if (step.kind === "property" && step.fill !== undefined && current === undefined) {
|
|
706
|
-
result[key] = fillOf(step.fill);
|
|
707
|
-
report?.filled.push(join(path, key));
|
|
708
|
-
}
|
|
709
|
-
continue;
|
|
710
|
-
}
|
|
711
|
-
result[key] = applyField(step, current, drop, join(path, key), report);
|
|
712
|
-
}
|
|
713
|
-
for (const key of Object.keys(result)) {
|
|
714
|
-
if (steps.fields.has(key))
|
|
715
|
-
continue;
|
|
716
|
-
if (result[key] !== undefined)
|
|
717
|
-
report?.removed.push(join(path, key));
|
|
718
|
-
if (drop)
|
|
719
|
-
delete result[key];
|
|
720
|
-
}
|
|
721
|
-
return result;
|
|
722
|
-
}
|
|
723
|
-
function applyField(step, value, drop, path, report) {
|
|
724
|
-
switch (step.kind) {
|
|
725
|
-
case "collection":
|
|
726
|
-
return isPlain2(value) ? applySteps(step.fields, value, drop, path, report) : value;
|
|
727
|
-
case "list":
|
|
728
|
-
return Array.isArray(value) ? value.map((item) => item === undefined || item === null ? item : applyField(step.item, item, drop, `${path}[]`, report)) : value;
|
|
729
|
-
default:
|
|
730
|
-
return value;
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
function storedVersionOf(data) {
|
|
734
|
-
return Object.hasOwn(data, SCHEMA_VERSION_KEY2) ? data[SCHEMA_VERSION_KEY2] : 1;
|
|
735
|
-
}
|
|
736
|
-
function upcast(source, plan, data, drop = true, report) {
|
|
737
|
-
const stored = storedVersionOf(data);
|
|
738
|
-
if (typeof stored !== "number" || !Number.isSafeInteger(stored) || stored < 1 || stored > plan.version) {
|
|
739
|
-
throw new InvalidStoredSchemaVersion2({
|
|
740
|
-
source,
|
|
741
|
-
details: { id: String(data.id), stored, current: plan.version }
|
|
742
|
-
});
|
|
743
|
-
}
|
|
744
|
-
if (report)
|
|
745
|
-
report.from = stored;
|
|
746
|
-
let value = { ...data };
|
|
747
|
-
delete value[SCHEMA_VERSION_KEY2];
|
|
748
|
-
for (let version = stored + 1;version <= plan.version; version++) {
|
|
749
|
-
const step = plan.upgrade[version];
|
|
750
|
-
if (step === undefined)
|
|
751
|
-
continue;
|
|
752
|
-
const next = step(Object.freeze(value));
|
|
753
|
-
if (!isPlain2(next)) {
|
|
754
|
-
throw new InvalidStoredSchemaVersion2({
|
|
755
|
-
source,
|
|
756
|
-
details: {
|
|
757
|
-
id: String(data.id),
|
|
758
|
-
stored,
|
|
759
|
-
current: plan.version,
|
|
760
|
-
reason: `The upgrade step ${version} did not give an object.`
|
|
761
|
-
}
|
|
762
|
-
});
|
|
763
|
-
}
|
|
764
|
-
value = { ...next };
|
|
765
|
-
delete value[SCHEMA_VERSION_KEY2];
|
|
766
|
-
}
|
|
767
|
-
return applySteps(plan.fields, value, drop, "", report);
|
|
768
|
-
}
|
|
769
|
-
function upcastRows(state, rows) {
|
|
770
|
-
const plan = upcastPlanOf(state.entity);
|
|
771
|
-
if (plan === undefined)
|
|
772
|
-
return rows;
|
|
773
|
-
return rows.map((row) => {
|
|
774
|
-
try {
|
|
775
|
-
return upcast(state.name, plan, row);
|
|
776
|
-
} catch (error) {
|
|
777
|
-
throw new InvalidRepositoryData2({ source: state.name, cause: error });
|
|
778
|
-
}
|
|
779
|
-
});
|
|
780
|
-
}
|
|
781
|
-
function withSchemaVersion(entity, data) {
|
|
782
|
-
const plan = upcastPlanOf(entity);
|
|
783
|
-
return plan?.versioned ? { ...data, [SCHEMA_VERSION_KEY2]: plan.version } : data;
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
// src/core/repository/references.ts
|
|
787
|
-
function isPlain3(value) {
|
|
788
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
789
|
-
}
|
|
790
|
-
function recordClassOf(field) {
|
|
791
|
-
return recordFieldOf(field)?.record;
|
|
792
|
-
}
|
|
793
|
-
function entityClassesOf(properties) {
|
|
794
|
-
const found = new Set;
|
|
795
|
-
function visit(field) {
|
|
796
|
-
if (isBlueprintList(field)) {
|
|
797
|
-
visit(field.item);
|
|
798
|
-
} else if (isBlueprint(field)) {
|
|
799
|
-
walk(field.properties);
|
|
800
|
-
} else if (isBlueprintVariant(field)) {
|
|
801
|
-
for (const variant of Object.values(field.variants)) {
|
|
802
|
-
visit(variant);
|
|
803
|
-
}
|
|
804
|
-
} else {
|
|
805
|
-
const record = recordClassOf(field);
|
|
806
|
-
if (!record)
|
|
807
|
-
return;
|
|
808
|
-
if (isEntityClass2(record))
|
|
809
|
-
found.add(record);
|
|
810
|
-
else
|
|
811
|
-
walk(record.blueprint.properties);
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
function walk(properties) {
|
|
815
|
-
for (const field of Object.values(properties))
|
|
816
|
-
visit(field);
|
|
817
|
-
}
|
|
818
|
-
walk(properties);
|
|
819
|
-
return found;
|
|
820
|
-
}
|
|
821
|
-
function mapReferences(properties, data, visit) {
|
|
822
|
-
function mapField(field, value) {
|
|
823
|
-
if (value === undefined || value === null)
|
|
824
|
-
return value;
|
|
825
|
-
if (isBlueprintList(field)) {
|
|
826
|
-
return Array.isArray(value) ? Object.freeze(value.map((item) => mapField(field.item, item))) : value;
|
|
827
|
-
}
|
|
828
|
-
if (isBlueprint(field)) {
|
|
829
|
-
return isPlain3(value) ? mapCollection(field.properties, value) : value;
|
|
830
|
-
}
|
|
831
|
-
if (isBlueprintVariant(field)) {
|
|
832
|
-
const variant = namedVariantOf(field, value);
|
|
833
|
-
if (!variant || !isPlain3(value))
|
|
834
|
-
return value;
|
|
835
|
-
if (isEntityClass2(variant)) {
|
|
836
|
-
const mapped = visit(variant, value.id);
|
|
837
|
-
return mapped === value.id ? value : mapped;
|
|
838
|
-
}
|
|
839
|
-
return mapCollection(blueprintOfVariant(variant).properties, value);
|
|
840
|
-
}
|
|
841
|
-
const record = recordClassOf(field);
|
|
842
|
-
if (!record)
|
|
843
|
-
return value;
|
|
844
|
-
if (isEntityClass2(record))
|
|
845
|
-
return visit(record, value);
|
|
846
|
-
return isPlain3(value) ? mapCollection(record.blueprint.properties, value) : value;
|
|
847
|
-
}
|
|
848
|
-
function mapCollection(properties, value) {
|
|
849
|
-
return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
850
|
-
key,
|
|
851
|
-
mapField(properties[key], item)
|
|
852
|
-
])));
|
|
853
|
-
}
|
|
854
|
-
return mapCollection(properties, data);
|
|
855
|
-
}
|
|
856
|
-
function relationsOf(name, entity, relations) {
|
|
857
|
-
const invalid = (reason) => {
|
|
858
|
-
throw new InvalidRepositoryDeclaration2({
|
|
859
|
-
source: name,
|
|
860
|
-
details: { reason }
|
|
861
|
-
});
|
|
862
|
-
};
|
|
863
|
-
if (relations === undefined)
|
|
864
|
-
relations = [];
|
|
865
|
-
if (!Array.isArray(relations)) {
|
|
866
|
-
return invalid("Expected relations to be a list of repositories.");
|
|
867
|
-
}
|
|
868
|
-
const related = [];
|
|
869
|
-
for (const relation of relations) {
|
|
870
|
-
const state = findState(relation);
|
|
871
|
-
if (!state)
|
|
872
|
-
return invalid("Expected relations made by repositoryOf.");
|
|
873
|
-
if (related.some((other) => other.entity === state.entity)) {
|
|
874
|
-
return invalid(`Relation "${recordNameOf2(state.entity)}" is duplicated.`);
|
|
875
|
-
}
|
|
876
|
-
if (typeof state.provider.findMany !== "function") {
|
|
877
|
-
return invalid(`The provider of "${state.name}" has no "findMany" to load it.`);
|
|
878
|
-
}
|
|
879
|
-
related.push(state);
|
|
880
|
-
}
|
|
881
|
-
const used = new Set;
|
|
882
|
-
const map = new Map;
|
|
883
|
-
for (const target of entityClassesOf(entity.blueprint.properties)) {
|
|
884
|
-
const matches = related.filter((state) => state.entity === target || state.entity.prototype instanceof target);
|
|
885
|
-
const exact = matches.find((state) => state.entity === target);
|
|
886
|
-
if (!exact && matches.length === 0) {
|
|
887
|
-
return invalid(`Missing relation: no repository of "${recordNameOf2(target)}".`);
|
|
888
|
-
}
|
|
889
|
-
if (!exact && matches.length > 1) {
|
|
890
|
-
return invalid(`Relation "${recordNameOf2(target)}" matches more than one repository.`);
|
|
891
|
-
}
|
|
892
|
-
const state = exact ?? matches[0];
|
|
893
|
-
map.set(target, state);
|
|
894
|
-
used.add(state);
|
|
895
|
-
}
|
|
896
|
-
for (const state of related) {
|
|
897
|
-
if (!used.has(state)) {
|
|
898
|
-
return invalid(`Relation "${recordNameOf2(state.entity)}" is not held by ${recordNameOf2(entity)}.`);
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
return map;
|
|
902
|
-
}
|
|
903
|
-
function parseEntity(state, data) {
|
|
904
|
-
const parsed = state.entity.safeParse(data);
|
|
905
|
-
if (parsed.issues) {
|
|
906
|
-
throw new InvalidRepositoryData2({
|
|
907
|
-
source: state.name,
|
|
908
|
-
cause: parsed.issues.find((issue) => issue.cause)?.cause
|
|
909
|
-
});
|
|
910
|
-
}
|
|
911
|
-
return parsed.value;
|
|
912
|
-
}
|
|
913
|
-
function loadByIds(state, ids, scope) {
|
|
914
|
-
return scopedProvider(scope, state.provider).findMany(contextOf(state), {
|
|
915
|
-
where: { id: { in: ids } },
|
|
916
|
-
withDeleted: true,
|
|
917
|
-
withExpired: true
|
|
918
|
-
});
|
|
919
|
-
}
|
|
920
|
-
async function resolveReferences(state, datas, session, scope) {
|
|
921
|
-
if (state.relations.size === 0)
|
|
922
|
-
return datas;
|
|
923
|
-
const { properties } = state.entity.blueprint;
|
|
924
|
-
const wanted = new Map;
|
|
925
|
-
for (const data of datas) {
|
|
926
|
-
mapReferences(properties, data, (target, value) => {
|
|
927
|
-
const relation = state.relations.get(target);
|
|
928
|
-
if (relation && typeof value === "string") {
|
|
929
|
-
const ids = wanted.get(relation) ?? new Set;
|
|
930
|
-
wanted.set(relation, ids.add(value));
|
|
931
|
-
}
|
|
932
|
-
return value;
|
|
933
|
-
});
|
|
934
|
-
}
|
|
935
|
-
for (const [relation, ids] of wanted) {
|
|
936
|
-
const loaded = session.get(relation) ?? new Map;
|
|
937
|
-
session.set(relation, loaded);
|
|
938
|
-
const missing = [...ids].filter((id) => !loaded.has(id));
|
|
939
|
-
if (missing.length === 0)
|
|
940
|
-
continue;
|
|
941
|
-
const found = upcastRows(relation, await loadByIds(relation, missing, scope));
|
|
942
|
-
const resolved = await resolveReferences(relation, found, session, scope);
|
|
943
|
-
for (const data of resolved) {
|
|
944
|
-
const id = data.id;
|
|
945
|
-
if (!loaded.has(id))
|
|
946
|
-
loaded.set(id, parseEntity(relation, data));
|
|
947
|
-
}
|
|
948
|
-
for (const id of missing) {
|
|
949
|
-
if (loaded.has(id))
|
|
950
|
-
continue;
|
|
951
|
-
throw new InvalidRepositoryData2({
|
|
952
|
-
source: state.name,
|
|
953
|
-
cause: new RepositoryEntityNotFound2({
|
|
954
|
-
source: relation.name,
|
|
955
|
-
details: { id }
|
|
956
|
-
})
|
|
957
|
-
});
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
return datas.map((data) => mapReferences(properties, data, (target, value) => {
|
|
961
|
-
const relation = state.relations.get(target);
|
|
962
|
-
return relation && typeof value === "string" ? session.get(relation)?.get(value) ?? value : value;
|
|
963
|
-
}));
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
// src/core/repository/repository.ts
|
|
967
|
-
var REFERENCES = {
|
|
968
|
-
reference: (record) => isEntity2(record) ? record.id : undefined
|
|
969
|
-
};
|
|
970
|
-
|
|
971
|
-
class AbstractRepository {
|
|
972
|
-
get [Kind2]() {
|
|
973
|
-
return "repository";
|
|
974
|
-
}
|
|
975
|
-
get entity() {
|
|
976
|
-
return stateOf(this).entity;
|
|
977
|
-
}
|
|
978
|
-
get actions() {
|
|
979
|
-
return stateOf(this).actions;
|
|
980
|
-
}
|
|
981
|
-
get provider() {
|
|
982
|
-
return stateOf(this).provider;
|
|
983
|
-
}
|
|
984
|
-
get context() {
|
|
985
|
-
return contextOf(stateOf(this));
|
|
986
|
-
}
|
|
987
|
-
get env() {
|
|
988
|
-
return this.context.env;
|
|
989
|
-
}
|
|
990
|
-
async hydrate(data) {
|
|
991
|
-
return (await this.hydrateMany([data]))[0];
|
|
992
|
-
}
|
|
993
|
-
async hydrateMany(list) {
|
|
994
|
-
const state = stateOf(this);
|
|
995
|
-
const scope = this[ScopeKey];
|
|
996
|
-
const resolved = await resolveReferences(state, upcastRows(state, list), new Map, scope);
|
|
997
|
-
return resolved.map((data) => parseEntity(state, data));
|
|
998
|
-
}
|
|
999
|
-
dataOf(entity) {
|
|
1000
|
-
if (!(entity instanceof stateOf(this).entity)) {
|
|
1001
|
-
throw new InvalidRepositoryEntity2({ source: stateOf(this).name });
|
|
1002
|
-
}
|
|
1003
|
-
return withSchemaVersion(stateOf(this).entity, recordDataOf(entity, REFERENCES));
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
function action(method) {
|
|
1007
|
-
return function(...args) {
|
|
1008
|
-
const state = stateOf(this);
|
|
1009
|
-
return guard2({ layer: "repository" /* Repository */, source: state.name }, async () => {
|
|
1010
|
-
assertUnbound(state);
|
|
1011
|
-
return method(this, ...args);
|
|
1012
|
-
});
|
|
1013
|
-
};
|
|
1014
|
-
}
|
|
1015
|
-
function queryValueOf(context, key, value) {
|
|
1016
|
-
const properties = context.entity.blueprint.properties;
|
|
1017
|
-
return recordFieldDataOf(properties[key], value, REFERENCES);
|
|
1018
|
-
}
|
|
1019
|
-
function expiryOf(self, withExpired) {
|
|
1020
|
-
const { context, provider } = self;
|
|
1021
|
-
if (context.expiry === undefined)
|
|
1022
|
-
return {};
|
|
1023
|
-
if (provider.capabilities?.expiry !== true) {
|
|
1024
|
-
throw new InvalidRepositoryQuery2({
|
|
1025
|
-
source: context.name,
|
|
1026
|
-
details: {
|
|
1027
|
-
reason: "The provider does not leave expired records out (capabilities.expiry), which an entity with expires needs."
|
|
1028
|
-
}
|
|
1029
|
-
});
|
|
1030
|
-
}
|
|
1031
|
-
const clock = context.entity[EntityClock];
|
|
1032
|
-
return { withExpired: withExpired ?? false, now: clock.now() };
|
|
1033
|
-
}
|
|
1034
|
-
function queryOf(self, query) {
|
|
1035
|
-
const prepared = prepareQuery2(self.context, query, (key, value) => queryValueOf(self.context, key, value));
|
|
1036
|
-
return {
|
|
1037
|
-
...prepared,
|
|
1038
|
-
withDeleted: prepared.withDeleted ?? stateOf(self).deletion === "hard",
|
|
1039
|
-
...expiryOf(self, prepared.withExpired)
|
|
1040
|
-
};
|
|
1041
|
-
}
|
|
1042
|
-
var equals = (self, key, value) => queryOf(self, { where: { [key]: value } });
|
|
1043
|
-
function planned(self, method, query, orderBy = query.orderBy) {
|
|
1044
|
-
const state = stateOf(self);
|
|
1045
|
-
const plan = planQuery(self.context, { where: query.where, orderBy });
|
|
1046
|
-
if (plan.access === "scan" && state.scans !== "allow") {
|
|
1047
|
-
const fields = {
|
|
1048
|
-
repository: state.name,
|
|
1049
|
-
method,
|
|
1050
|
-
where: Object.keys(query.where ?? {}),
|
|
1051
|
-
orderBy: (orderBy ?? []).map(({ key }) => key)
|
|
1052
|
-
};
|
|
1053
|
-
if (state.scans === "reject") {
|
|
1054
|
-
throw new InvalidRepositoryQuery2({
|
|
1055
|
-
source: state.name,
|
|
1056
|
-
details: {
|
|
1057
|
-
reason: `"${method}" matches no index of ${state.name} and would read every row (scans: "reject").`,
|
|
1058
|
-
scan: true,
|
|
1059
|
-
...fields
|
|
1060
|
-
}
|
|
1061
|
-
});
|
|
1062
|
-
}
|
|
1063
|
-
const scope = self[ScopeKey];
|
|
1064
|
-
writeLog(resolveLogger(scope?.logger ?? state.logger, undefined), "warn", "repository-scan", fields);
|
|
1065
|
-
}
|
|
1066
|
-
return plan.index === undefined ? query : { ...query, index: plan.index };
|
|
1067
|
-
}
|
|
1068
|
-
var findBy = (key) => action(async (self, value) => {
|
|
1069
|
-
const data = await self.provider.findOne(self.context, equals(self, key, value));
|
|
1070
|
-
return data === undefined ? undefined : await self.hydrate(data);
|
|
1071
|
-
});
|
|
1072
|
-
var findManyBy = (key) => action(async (self, value) => self.hydrateMany(await self.provider.findMany(self.context, planned(self, `findManyBy${capitalize(key)}`, equals(self, key, value)))));
|
|
1073
|
-
var findMany = action(async (self, query) => self.hydrateMany(await self.provider.findMany(self.context, planned(self, "findMany", queryOf(self, query)))));
|
|
1074
|
-
var exists = action(async (self, query) => await self.provider.findOne(self.context, planned(self, "exists", queryOf(self, query))) !== undefined);
|
|
1075
|
-
var EXPLAIN_OPTIONS = [
|
|
1076
|
-
"where",
|
|
1077
|
-
"orderBy",
|
|
1078
|
-
"limit",
|
|
1079
|
-
"offset",
|
|
1080
|
-
"withDeleted",
|
|
1081
|
-
"after"
|
|
1082
|
-
];
|
|
1083
|
-
var explain = action(async (self, query = {}) => {
|
|
1084
|
-
const prepared = prepareQuery2(self.context, query, (key, value) => queryValueOf(self.context, key, value), EXPLAIN_OPTIONS);
|
|
1085
|
-
return planQuery(self.context, prepared);
|
|
1086
|
-
});
|
|
1087
|
-
var findPage = action(async (self, query) => {
|
|
1088
|
-
const {
|
|
1089
|
-
orderBy,
|
|
1090
|
-
where,
|
|
1091
|
-
withDeleted,
|
|
1092
|
-
withExpired,
|
|
1093
|
-
now,
|
|
1094
|
-
limit = 20,
|
|
1095
|
-
offset = 0,
|
|
1096
|
-
index
|
|
1097
|
-
} = planned(self, "findPage", queryOf(self, query));
|
|
1098
|
-
const expiry = withExpired === undefined ? {} : { withExpired, now };
|
|
1099
|
-
const [rows, total] = await Promise.all([
|
|
1100
|
-
self.provider.findMany(self.context, {
|
|
1101
|
-
where,
|
|
1102
|
-
orderBy,
|
|
1103
|
-
withDeleted,
|
|
1104
|
-
...expiry,
|
|
1105
|
-
limit,
|
|
1106
|
-
offset,
|
|
1107
|
-
...index === undefined ? {} : { index }
|
|
1108
|
-
}),
|
|
1109
|
-
self.provider.count(self.context, {
|
|
1110
|
-
where,
|
|
1111
|
-
withDeleted,
|
|
1112
|
-
...expiry,
|
|
1113
|
-
...index === undefined ? {} : { index }
|
|
1114
|
-
})
|
|
1115
|
-
]);
|
|
1116
|
-
return { items: await self.hydrateMany(rows), total, limit, offset };
|
|
1117
|
-
});
|
|
1118
|
-
var CURSOR_OPTIONS = [
|
|
1119
|
-
"where",
|
|
1120
|
-
"orderBy",
|
|
1121
|
-
"limit",
|
|
1122
|
-
"withDeleted",
|
|
1123
|
-
"withExpired",
|
|
1124
|
-
"after"
|
|
1125
|
-
];
|
|
1126
|
-
function cursorReadOf(self, query) {
|
|
1127
|
-
const { context } = self;
|
|
1128
|
-
const fail = (reason) => {
|
|
1129
|
-
throw new InvalidRepositoryQuery2({
|
|
1130
|
-
source: context.name,
|
|
1131
|
-
details: { reason }
|
|
1132
|
-
});
|
|
1133
|
-
};
|
|
1134
|
-
const prepared = prepareQuery2(context, query, (key, value) => queryValueOf(context, key, value), CURSOR_OPTIONS);
|
|
1135
|
-
const { where, limit = 20 } = prepared;
|
|
1136
|
-
if (limit < 1)
|
|
1137
|
-
fail("Expected a positive integer for limit.");
|
|
1138
|
-
const withDeleted = prepared.withDeleted ?? stateOf(self).deletion === "hard";
|
|
1139
|
-
const declared = prepared.orderBy ?? [];
|
|
1140
|
-
const orderBy = declared.some(({ key }) => key === "id") ? declared : [...declared, { key: "id", direction: "asc" }];
|
|
1141
|
-
for (const { key } of orderBy) {
|
|
1142
|
-
if (context.entity.options.sensitive.includes(key))
|
|
1143
|
-
fail(`"${key}" is sensitive and cannot order a cursor.`);
|
|
1144
|
-
}
|
|
1145
|
-
if (self.provider.capabilities?.keyset !== true)
|
|
1146
|
-
fail("The provider does not read by cursor (capabilities.keyset).");
|
|
1147
|
-
const { index } = planned(self, "findCursor", { where }, declared);
|
|
1148
|
-
expiryOf(self, prepared.withExpired);
|
|
1149
|
-
return {
|
|
1150
|
-
where,
|
|
1151
|
-
orderBy,
|
|
1152
|
-
withDeleted,
|
|
1153
|
-
withExpired: prepared.withExpired,
|
|
1154
|
-
limit,
|
|
1155
|
-
...index === undefined ? {} : { index }
|
|
1156
|
-
};
|
|
1157
|
-
}
|
|
1158
|
-
async function readCursor(self, read, after) {
|
|
1159
|
-
const { context } = self;
|
|
1160
|
-
const { where, orderBy, withDeleted, limit, index } = read;
|
|
1161
|
-
const rows = await self.provider.findMany(context, {
|
|
1162
|
-
where,
|
|
1163
|
-
orderBy,
|
|
1164
|
-
withDeleted,
|
|
1165
|
-
...expiryOf(self, read.withExpired),
|
|
1166
|
-
limit: limit + 1,
|
|
1167
|
-
...index === undefined ? {} : { index },
|
|
1168
|
-
...after === undefined ? {} : { after: decodeCursor(context.name, orderBy, after) }
|
|
1169
|
-
});
|
|
1170
|
-
const page = rows.slice(0, limit);
|
|
1171
|
-
const last = page.at(-1);
|
|
1172
|
-
return {
|
|
1173
|
-
items: await self.hydrateMany(page),
|
|
1174
|
-
limit,
|
|
1175
|
-
next: rows.length > limit && last !== undefined ? encodeCursor(orderBy, last) : undefined
|
|
1176
|
-
};
|
|
1177
|
-
}
|
|
1178
|
-
var findCursor = action(async (self, query = {}) => readCursor(self, cursorReadOf(self, query), query.after));
|
|
1179
|
-
function stopperOf(source, until) {
|
|
1180
|
-
if (until === undefined)
|
|
1181
|
-
return () => false;
|
|
1182
|
-
if (typeof until === "function")
|
|
1183
|
-
return () => Boolean(until());
|
|
1184
|
-
const deadline = until instanceof Date ? until.getTime() : typeof until === "number" ? until : Number.NaN;
|
|
1185
|
-
if (Number.isNaN(deadline)) {
|
|
1186
|
-
throw new InvalidRepositoryQuery2({
|
|
1187
|
-
source,
|
|
1188
|
-
details: {
|
|
1189
|
-
reason: "Expected until to be a Date, epoch milliseconds or a function."
|
|
1190
|
-
}
|
|
1191
|
-
});
|
|
1192
|
-
}
|
|
1193
|
-
return () => Date.now() >= deadline;
|
|
1194
|
-
}
|
|
1195
|
-
var forEachPage = action(async (self, query, fn, options) => {
|
|
1196
|
-
const { context } = self;
|
|
1197
|
-
if (typeof fn !== "function") {
|
|
1198
|
-
throw new InvalidRepositoryQuery2({
|
|
1199
|
-
source: context.name,
|
|
1200
|
-
details: { reason: "Expected a function to run on each page." }
|
|
1201
|
-
});
|
|
1202
|
-
}
|
|
1203
|
-
const read = cursorReadOf(self, query ?? {});
|
|
1204
|
-
const stop = stopperOf(context.name, options?.until);
|
|
1205
|
-
let after = query?.after;
|
|
1206
|
-
let pages = 0;
|
|
1207
|
-
let items = 0;
|
|
1208
|
-
while (!stop()) {
|
|
1209
|
-
const page = await readCursor(self, read, after);
|
|
1210
|
-
if (page.items.length > 0) {
|
|
1211
|
-
await fn(page.items, { index: pages, next: page.next });
|
|
1212
|
-
pages += 1;
|
|
1213
|
-
items += page.items.length;
|
|
1214
|
-
}
|
|
1215
|
-
if (page.next === undefined)
|
|
1216
|
-
return { done: true, pages, items };
|
|
1217
|
-
after = page.next;
|
|
1218
|
-
}
|
|
1219
|
-
return { done: false, next: after, pages, items };
|
|
1220
|
-
});
|
|
1221
|
-
var create = action(async (self, input) => {
|
|
1222
|
-
const { entity: Entity } = self.context;
|
|
1223
|
-
const entity = input instanceof Entity ? input : new Entity(input);
|
|
1224
|
-
await self.provider.insert(self.context, self.dataOf(entity));
|
|
1225
|
-
return entity;
|
|
1226
|
-
});
|
|
1227
|
-
function nowOf2(context) {
|
|
1228
|
-
return context.entity[EntityClock].now();
|
|
1229
|
-
}
|
|
1230
|
-
function assertVersioning(self) {
|
|
1231
|
-
if (self.provider.capabilities?.versioning === true)
|
|
1232
|
-
return;
|
|
1233
|
-
invalid2(self.context.name, `${recordNameOf2(self.context.entity)} is versioned, and the provider does not check versions (capabilities.versioning).`);
|
|
1234
|
-
}
|
|
1235
|
-
async function write(self, data) {
|
|
1236
|
-
if (!isVersionedEntityClass(self.context.entity)) {
|
|
1237
|
-
await self.provider.update(self.context, data);
|
|
1238
|
-
return data;
|
|
1239
|
-
}
|
|
1240
|
-
assertVersioning(self);
|
|
1241
|
-
const expected = data.version;
|
|
1242
|
-
const next = { ...data, version: expected + 1 };
|
|
1243
|
-
await self.provider.update(self.context, next, {
|
|
1244
|
-
expectedVersion: expected
|
|
1245
|
-
});
|
|
1246
|
-
return next;
|
|
1247
|
-
}
|
|
1248
|
-
async function writeEntity(self, entity) {
|
|
1249
|
-
const written = await write(self, self.dataOf(entity));
|
|
1250
|
-
if (written.version !== undefined && isEntity2(entity)) {
|
|
1251
|
-
storeEntityVersion(entity, written.version);
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
var update = action(async (self, entity) => {
|
|
1255
|
-
await writeEntity(self, entity);
|
|
1256
|
-
return entity;
|
|
1257
|
-
});
|
|
1258
|
-
function isPlainObject(value) {
|
|
1259
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1260
|
-
}
|
|
1261
|
-
function isNumericField(field) {
|
|
1262
|
-
if (field === undefined)
|
|
1263
|
-
return false;
|
|
1264
|
-
const property = fieldOf(field);
|
|
1265
|
-
if (!isProperty(property))
|
|
1266
|
-
return false;
|
|
1267
|
-
const { type } = property.type.schema.type.Type();
|
|
1268
|
-
return type === "number" || type === "integer";
|
|
1269
|
-
}
|
|
1270
|
-
function deltasOf(context, deltas) {
|
|
1271
|
-
const { name: source } = context;
|
|
1272
|
-
const fail = (reason, issues) => {
|
|
1273
|
-
throw new InvalidRepositoryIncrement2({
|
|
1274
|
-
source,
|
|
1275
|
-
details: { reason, issues }
|
|
1276
|
-
});
|
|
1277
|
-
};
|
|
1278
|
-
if (!isPlainObject(deltas) || Object.keys(deltas).length === 0) {
|
|
1279
|
-
const message = "Expected an object of the numbers to add, by key.";
|
|
1280
|
-
fail(message, [{ code: "invalid_type", path: [], message, source }]);
|
|
1281
|
-
}
|
|
1282
|
-
const { change } = context.entity.options;
|
|
1283
|
-
const properties = context.entity.blueprint.properties;
|
|
1284
|
-
const issues = [];
|
|
1285
|
-
for (const [key, value] of Object.entries(deltas)) {
|
|
1286
|
-
if (!change.includes(key) || !isNumericField(properties[key])) {
|
|
1287
|
-
issues.push({
|
|
1288
|
-
code: "undeclared_key",
|
|
1289
|
-
path: [key],
|
|
1290
|
-
message: `"${key}" is not a number in the change list of ${recordNameOf2(context.entity)}.`,
|
|
1291
|
-
source
|
|
1292
|
-
});
|
|
1293
|
-
} else if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
1294
|
-
issues.push({
|
|
1295
|
-
code: "invalid_value",
|
|
1296
|
-
path: [key],
|
|
1297
|
-
message: "Expected a finite number.",
|
|
1298
|
-
source
|
|
1299
|
-
});
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
if (issues.length > 0) {
|
|
1303
|
-
fail(`"${source}" cannot increment ${issues.map((issue) => `"${issue.path[0]}"`).join(", ")}.`, issues);
|
|
1304
|
-
}
|
|
1305
|
-
return deltas;
|
|
1306
|
-
}
|
|
1307
|
-
function createdOf(context, id, input, deltas) {
|
|
1308
|
-
const identity = identityKeysOf(context.entity);
|
|
1309
|
-
const value = isPlainObject(input) ? Object.fromEntries(Object.entries(input).filter(([key]) => !identity.includes(key))) : {};
|
|
1310
|
-
for (const [key, delta] of Object.entries(deltas)) {
|
|
1311
|
-
const base = value[key];
|
|
1312
|
-
value[key] = (typeof base === "number" ? base : 0) + delta;
|
|
1313
|
-
}
|
|
1314
|
-
const Entity = context.entity;
|
|
1315
|
-
const entity = new Entity({ ...value, id });
|
|
1316
|
-
enqueueLifecycle(entity, "onCreate");
|
|
1317
|
-
return entity;
|
|
1318
|
-
}
|
|
1319
|
-
function renamedFromOf(field) {
|
|
1320
|
-
if (field === undefined)
|
|
1321
|
-
return;
|
|
1322
|
-
const property = fieldOf(field);
|
|
1323
|
-
if (!isProperty(property))
|
|
1324
|
-
return;
|
|
1325
|
-
const { renamedFrom } = property.type;
|
|
1326
|
-
return typeof renamedFrom === "string" ? renamedFrom : undefined;
|
|
1327
|
-
}
|
|
1328
|
-
async function bringUpRenamed(self, id, deltas) {
|
|
1329
|
-
const { context } = self;
|
|
1330
|
-
const properties = context.entity.blueprint.properties;
|
|
1331
|
-
const renamed = [];
|
|
1332
|
-
for (const key of Object.keys(deltas)) {
|
|
1333
|
-
const from = renamedFromOf(properties[key]);
|
|
1334
|
-
if (from !== undefined)
|
|
1335
|
-
renamed.push([key, from]);
|
|
1336
|
-
}
|
|
1337
|
-
if (renamed.length === 0)
|
|
1338
|
-
return;
|
|
1339
|
-
const stored = await self.provider.findOne(context, {
|
|
1340
|
-
where: { id },
|
|
1341
|
-
withDeleted: true,
|
|
1342
|
-
...context.expiry === undefined ? {} : { withExpired: true }
|
|
1343
|
-
});
|
|
1344
|
-
if (stored === undefined || !renamed.some(([key, from]) => stored[key] === undefined && Object.hasOwn(stored, from))) {
|
|
1345
|
-
return;
|
|
1346
|
-
}
|
|
1347
|
-
const data = withSchemaVersion(context.entity, upcast(context.name, fullUpcastPlanOf(context.entity), stored, false));
|
|
1348
|
-
const expected = stored.version;
|
|
1349
|
-
try {
|
|
1350
|
-
await self.provider.update(context, data, isVersionedEntityClass(context.entity) && typeof expected === "number" ? { expectedVersion: expected } : undefined);
|
|
1351
|
-
} catch (error) {
|
|
1352
|
-
if (!(error instanceof Exception2) || !error.is(RepositoryConcurrencyConflict2)) {
|
|
1353
|
-
throw error;
|
|
1354
|
-
}
|
|
1355
|
-
}
|
|
1356
|
-
}
|
|
1357
|
-
var increment = action(async (self, target, deltas, options) => {
|
|
1358
|
-
const { context } = self;
|
|
1359
|
-
const id = typeof target === "string" ? target : self.dataOf(target).id;
|
|
1360
|
-
const checked = deltasOf(context, deltas);
|
|
1361
|
-
if (options !== undefined && !isPlainObject(options)) {
|
|
1362
|
-
invalid2(context.name, "Expected the options of increment as an object.");
|
|
1363
|
-
}
|
|
1364
|
-
const versioned = isVersionedEntityClass(context.entity);
|
|
1365
|
-
if (versioned)
|
|
1366
|
-
assertVersioning(self);
|
|
1367
|
-
const { create } = options ?? {};
|
|
1368
|
-
const created = create === undefined ? undefined : createdOf(context, id, create, checked);
|
|
1369
|
-
await bringUpRenamed(self, id, checked);
|
|
1370
|
-
const stored = await self.provider.increment(context, id, versioned ? { ...checked, version: 1 } : checked, {
|
|
1371
|
-
set: { updatedAt: nowOf2(context) },
|
|
1372
|
-
...created === undefined ? {} : { create: self.dataOf(created) },
|
|
1373
|
-
...context.expiry === undefined ? {} : { now: expiryOf(self, false).now }
|
|
1374
|
-
});
|
|
1375
|
-
if (created !== undefined && stored.updatedAt === undefined)
|
|
1376
|
-
return created;
|
|
1377
|
-
const entity = await self.hydrate(stored);
|
|
1378
|
-
enqueueLifecycle(entity, "onUpdate");
|
|
1379
|
-
return entity;
|
|
1380
|
-
});
|
|
1381
|
-
var remove = action(async (self, target) => {
|
|
1382
|
-
const { context } = self;
|
|
1383
|
-
let entity = target;
|
|
1384
|
-
if (typeof target === "string") {
|
|
1385
|
-
const data = await self.provider.findOne(context, equals(self, "id", target));
|
|
1386
|
-
if (data === undefined) {
|
|
1387
|
-
throw new RepositoryEntityNotFound2({
|
|
1388
|
-
source: context.name,
|
|
1389
|
-
details: { id: target }
|
|
1390
|
-
});
|
|
1391
|
-
}
|
|
1392
|
-
entity = await self.hydrate(data);
|
|
1393
|
-
}
|
|
1394
|
-
const data = self.dataOf(entity);
|
|
1395
|
-
const destroyable = entity;
|
|
1396
|
-
if (!destroyable.isDeleted)
|
|
1397
|
-
destroyable.destroy();
|
|
1398
|
-
if (stateOf(self).deletion === "soft") {
|
|
1399
|
-
await writeEntity(self, entity);
|
|
1400
|
-
} else {
|
|
1401
|
-
await self.provider.remove(context, data.id);
|
|
1402
|
-
}
|
|
1403
|
-
return entity;
|
|
1404
|
-
});
|
|
1405
|
-
var restore = action(async (self, target) => {
|
|
1406
|
-
const { context } = self;
|
|
1407
|
-
const id = typeof target === "string" ? target : self.dataOf(target).id;
|
|
1408
|
-
const data = await self.provider.findOne(context, {
|
|
1409
|
-
...equals(self, "id", id),
|
|
1410
|
-
withDeleted: true
|
|
1411
|
-
});
|
|
1412
|
-
if (data === undefined) {
|
|
1413
|
-
throw new RepositoryEntityNotFound2({
|
|
1414
|
-
source: context.name,
|
|
1415
|
-
details: { id }
|
|
1416
|
-
});
|
|
1417
|
-
}
|
|
1418
|
-
if (data.deletedAt === undefined)
|
|
1419
|
-
return self.hydrate(data);
|
|
1420
|
-
const [current] = upcastRows(stateOf(self), [data]);
|
|
1421
|
-
const { deletedAt: _deletedAt, ...alive } = current;
|
|
1422
|
-
const restored = withSchemaVersion(context.entity, {
|
|
1423
|
-
...alive,
|
|
1424
|
-
updatedAt: nowOf2(context)
|
|
1425
|
-
});
|
|
1426
|
-
return self.hydrate(await write(self, restored));
|
|
1427
|
-
});
|
|
1428
|
-
var count = action(async (self, query) => {
|
|
1429
|
-
const prepared = queryOf(self, query);
|
|
1430
|
-
return self.provider.count(self.context, planned(self, "count", prepared, []));
|
|
1431
|
-
});
|
|
1432
|
-
var NEEDS = {
|
|
1433
|
-
findBy: ["findOne"],
|
|
1434
|
-
findManyBy: ["findMany"],
|
|
1435
|
-
create: ["insert"],
|
|
1436
|
-
update: ["update"],
|
|
1437
|
-
delete: ["findOne", "remove"],
|
|
1438
|
-
restore: ["findOne", "update"],
|
|
1439
|
-
count: ["count"],
|
|
1440
|
-
findMany: ["findMany"],
|
|
1441
|
-
exists: ["findOne"],
|
|
1442
|
-
findPage: ["findMany", "count"],
|
|
1443
|
-
findCursor: ["findMany"],
|
|
1444
|
-
explain: [],
|
|
1445
|
-
increment: ["increment"],
|
|
1446
|
-
forEachPage: ["findMany"]
|
|
1447
|
-
};
|
|
1448
|
-
function capitalize(key) {
|
|
1449
|
-
return `${key.charAt(0).toUpperCase()}${key.slice(1)}`;
|
|
1450
|
-
}
|
|
1451
|
-
function unknownAction(entity, action, methods) {
|
|
1452
|
-
const many = /^findManyBy(.+)$/.exec(action)?.[1];
|
|
1453
|
-
const one = many === undefined ? /^findBy(.+)$/.exec(action)?.[1] : undefined;
|
|
1454
|
-
const suffix = many ?? one;
|
|
1455
|
-
const key = suffix === undefined ? undefined : `${suffix.charAt(0).toLowerCase()}${suffix.slice(1)}`;
|
|
1456
|
-
if (one !== undefined && methods.has(`findManyBy${one}`)) {
|
|
1457
|
-
return `"${action}" requires "${key}" to be a unique key of ${entity}.`;
|
|
1458
|
-
}
|
|
1459
|
-
if (many !== undefined && methods.has(`findBy${many}`)) {
|
|
1460
|
-
return `"${action}" is not available for the unique key "${key}" of ${entity}; use "findBy${many}".`;
|
|
1461
|
-
}
|
|
1462
|
-
return `"${action}" is not an action of ${entity}.`;
|
|
1463
|
-
}
|
|
1464
|
-
function invalid2(source, reason) {
|
|
1465
|
-
throw new InvalidRepositoryDeclaration2({ source, details: { reason } });
|
|
1466
|
-
}
|
|
1467
|
-
function assertPreviousNames(entity) {
|
|
1468
|
-
const own = recordNameOf2(entity);
|
|
1469
|
-
const previous = entity[EntitySettings2]?.previousNames ?? [];
|
|
1470
|
-
if (!previous.includes(own))
|
|
1471
|
-
return;
|
|
1472
|
-
throw new InvalidEntitySchemaVersion2({
|
|
1473
|
-
source: own,
|
|
1474
|
-
details: {
|
|
1475
|
-
reason: `"${own}" cannot be a previous name: expected an identifier other than the name of the entity.`
|
|
1476
|
-
}
|
|
1477
|
-
});
|
|
1478
|
-
}
|
|
1479
|
-
var INDEX_NAME = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
1480
|
-
function indexesOf(name, entity, declared) {
|
|
1481
|
-
if (declared === undefined)
|
|
1482
|
-
return Object.freeze({});
|
|
1483
|
-
if (typeof declared !== "object" || declared === null || Array.isArray(declared)) {
|
|
1484
|
-
invalid2(name, "Expected indexes to be an object of indexes by name.");
|
|
1485
|
-
}
|
|
1486
|
-
const properties = entity.blueprint.properties;
|
|
1487
|
-
const sensitive = entity.options.sensitive;
|
|
1488
|
-
const keyOf = (index, key) => {
|
|
1489
|
-
if (typeof key !== "string" || !Object.hasOwn(properties, key)) {
|
|
1490
|
-
invalid2(name, `The index "${index}" names "${String(key)}", which is not a key of ${recordNameOf2(entity)}.`);
|
|
1491
|
-
}
|
|
1492
|
-
if (sensitive.includes(key)) {
|
|
1493
|
-
invalid2(name, `The index "${index}" names "${key}", which is sensitive and cannot be indexed.`);
|
|
1494
|
-
}
|
|
1495
|
-
return key;
|
|
1496
|
-
};
|
|
1497
|
-
const seen = new Map;
|
|
1498
|
-
const indexes = {};
|
|
1499
|
-
for (const [index, value] of Object.entries(declared)) {
|
|
1500
|
-
if (!INDEX_NAME.test(index) || index === "__proto__") {
|
|
1501
|
-
invalid2(name, `Expected the index "${index}" to be named by an identifier.`);
|
|
1502
|
-
}
|
|
1503
|
-
if (typeof value !== "object" || value === null || Array.isArray(value) || Object.keys(value).some((key) => key !== "where" && key !== "orderBy") || !Array.isArray(value.where) || value.where.length === 0) {
|
|
1504
|
-
invalid2(name, `Expected the index "${index}" to be { where: [keys], orderBy?: key }.`);
|
|
1505
|
-
}
|
|
1506
|
-
const where = value.where.map((key) => keyOf(index, key));
|
|
1507
|
-
if (new Set(where).size !== where.length) {
|
|
1508
|
-
invalid2(name, `The index "${index}" names a key twice in where.`);
|
|
1509
|
-
}
|
|
1510
|
-
const orderBy = value.orderBy === undefined ? undefined : keyOf(index, value.orderBy);
|
|
1511
|
-
if (orderBy !== undefined) {
|
|
1512
|
-
const field = properties[orderBy];
|
|
1513
|
-
if (isBlueprint(field) || isBlueprintList(field) || isBlueprintVariant(field) || recordFieldOf(field)) {
|
|
1514
|
-
invalid2(name, `The index "${index}" orders by "${orderBy}", which is not scalar.`);
|
|
1515
|
-
}
|
|
1516
|
-
}
|
|
1517
|
-
const signature = JSON.stringify([[...where].sort(), orderBy ?? null]);
|
|
1518
|
-
const other = seen.get(signature);
|
|
1519
|
-
if (other !== undefined) {
|
|
1520
|
-
invalid2(name, `The indexes "${other}" and "${index}" are the same.`);
|
|
1521
|
-
}
|
|
1522
|
-
seen.set(signature, index);
|
|
1523
|
-
indexes[index] = Object.freeze({
|
|
1524
|
-
where: Object.freeze(where),
|
|
1525
|
-
...orderBy === undefined ? {} : { orderBy }
|
|
1526
|
-
});
|
|
1527
|
-
}
|
|
1528
|
-
return Object.freeze(indexes);
|
|
1529
|
-
}
|
|
1530
|
-
function repositoryOf2(settings) {
|
|
1531
|
-
const { entity, actions, environmentDependencies } = settings ?? {};
|
|
1532
|
-
if (!isEntityClass2(entity) || !(EntityUniqueKeys2 in entity)) {
|
|
1533
|
-
invalid2("repositoryOf", "Expected an entity class made by entityOf.");
|
|
1534
|
-
}
|
|
1535
|
-
const name = `${recordNameOf2(entity)}Repository`;
|
|
1536
|
-
assertPreviousNames(entity);
|
|
1537
|
-
const deletion = settings.deletion ?? "hard";
|
|
1538
|
-
if (deletion !== "hard" && deletion !== "soft") {
|
|
1539
|
-
invalid2(name, 'Expected deletion to be "hard" or "soft".');
|
|
1540
|
-
}
|
|
1541
|
-
const scans = settings.scans ?? "allow";
|
|
1542
|
-
if (scans !== "allow" && scans !== "warn" && scans !== "reject") {
|
|
1543
|
-
invalid2(name, 'Expected scans to be "allow", "warn" or "reject".');
|
|
1544
|
-
}
|
|
1545
|
-
const provider = settings.provider ?? inMemoryProvider2();
|
|
1546
|
-
if (!Array.isArray(actions) || actions.length === 0) {
|
|
1547
|
-
invalid2(name, "Expected a list of actions.");
|
|
1548
|
-
}
|
|
1549
|
-
if (environmentDependencies !== undefined && !isEnvironment(environmentDependencies) && !(Array.isArray(environmentDependencies) && environmentDependencies.every(isEnvironment))) {
|
|
1550
|
-
invalid2(name, "Expected environments made by environmentOf.");
|
|
1551
|
-
}
|
|
1552
|
-
const relations = relationsOf(name, entity, settings.relations);
|
|
1553
|
-
const indexes = indexesOf(name, entity, settings.indexes);
|
|
1554
|
-
const unique = entity[EntityUniqueKeys2];
|
|
1555
|
-
const methods = new Map;
|
|
1556
|
-
for (const key of Object.keys(entity.blueprint.properties)) {
|
|
1557
|
-
const suffix = capitalize(key);
|
|
1558
|
-
if (unique.includes(key)) {
|
|
1559
|
-
methods.set(`findBy${suffix}`, ["findBy", findBy(key)]);
|
|
1560
|
-
} else {
|
|
1561
|
-
methods.set(`findManyBy${suffix}`, ["findManyBy", findManyBy(key)]);
|
|
1562
|
-
}
|
|
1563
|
-
}
|
|
1564
|
-
methods.set("create", ["create", create]);
|
|
1565
|
-
methods.set("update", ["update", update]);
|
|
1566
|
-
methods.set("delete", ["delete", remove]);
|
|
1567
|
-
methods.set("restore", ["restore", restore]);
|
|
1568
|
-
methods.set("count", ["count", count]);
|
|
1569
|
-
methods.set("findMany", ["findMany", findMany]);
|
|
1570
|
-
methods.set("exists", ["exists", exists]);
|
|
1571
|
-
methods.set("findPage", ["findPage", findPage]);
|
|
1572
|
-
methods.set("findCursor", ["findCursor", findCursor]);
|
|
1573
|
-
methods.set("explain", ["explain", explain]);
|
|
1574
|
-
methods.set("increment", ["increment", increment]);
|
|
1575
|
-
methods.set("forEachPage", ["forEachPage", forEachPage]);
|
|
1576
|
-
|
|
1577
|
-
class DeclaredRepository extends AbstractRepository {
|
|
1578
|
-
}
|
|
1579
|
-
for (const name_ of new Set(actions)) {
|
|
1580
|
-
const method = typeof name_ === "string" ? methods.get(name_) : undefined;
|
|
1581
|
-
if (!method)
|
|
1582
|
-
invalid2(name, unknownAction(recordNameOf2(entity), String(name_), methods));
|
|
1583
|
-
const [kind, implementation] = method;
|
|
1584
|
-
if (kind === "restore" && deletion !== "soft") {
|
|
1585
|
-
invalid2(name, '"restore" needs deletion to be "soft".');
|
|
1586
|
-
}
|
|
1587
|
-
if (kind === "findCursor" && settings.provider !== undefined && provider[Kind2] !== "provider-slot" && provider.capabilities?.keyset !== true) {
|
|
1588
|
-
invalid2(name, '"findCursor" needs a provider that reads by cursor (capabilities.keyset).');
|
|
1589
|
-
}
|
|
1590
|
-
const needs = deletion === "soft" && kind === "delete" ? ["findOne", "update"] : NEEDS[kind] ?? [];
|
|
1591
|
-
for (const need of needs) {
|
|
1592
|
-
if (typeof provider?.[need] !== "function") {
|
|
1593
|
-
invalid2(name, `The provider has no "${need}" for "${name_}".`);
|
|
1594
|
-
}
|
|
1595
|
-
}
|
|
1596
|
-
Object.defineProperty(DeclaredRepository.prototype, name_, {
|
|
1597
|
-
value: implementation,
|
|
1598
|
-
writable: true,
|
|
1599
|
-
configurable: true
|
|
1600
|
-
});
|
|
1601
|
-
}
|
|
1602
|
-
Object.defineProperty(DeclaredRepository, "name", { value: name });
|
|
1603
|
-
const state = {
|
|
1604
|
-
name,
|
|
1605
|
-
entity,
|
|
1606
|
-
actions: Object.freeze([...new Set(actions)]),
|
|
1607
|
-
deletion,
|
|
1608
|
-
provider,
|
|
1609
|
-
explicitProvider: settings.provider !== undefined,
|
|
1610
|
-
environment: environmentDependencies,
|
|
1611
|
-
relations,
|
|
1612
|
-
indexes,
|
|
1613
|
-
scans
|
|
1614
|
-
};
|
|
1615
|
-
states.set(DeclaredRepository, state);
|
|
1616
|
-
const repository = Object.freeze(new DeclaredRepository);
|
|
1617
|
-
declareRepository(state, repository);
|
|
1618
|
-
return repository;
|
|
1619
|
-
}
|
|
1620
|
-
function RepositoryOf2(repository) {
|
|
1621
|
-
if (!isRepository(repository)) {
|
|
1622
|
-
invalid2("RepositoryOf", "Expected a repository made by repositoryOf.");
|
|
1623
|
-
}
|
|
1624
|
-
return repository.constructor;
|
|
1625
|
-
}
|
|
1626
|
-
function isRepository(value) {
|
|
1627
|
-
return value instanceof AbstractRepository;
|
|
1628
|
-
}
|
|
1629
|
-
|
|
1630
|
-
// src/core/use-case/exceptions.ts
|
|
1631
|
-
var InvalidUseCaseStep2 = exceptionOf2({
|
|
1632
|
-
layer: "use-case" /* UseCase */,
|
|
1633
|
-
name: "InvalidUseCaseStep",
|
|
1634
|
-
message: ({
|
|
1635
|
-
index,
|
|
1636
|
-
reason
|
|
1637
|
-
}) => `Invalid step ${index}: ${reason}`
|
|
1638
|
-
});
|
|
1639
|
-
var InvalidUseCasePlugin2 = exceptionOf2({
|
|
1640
|
-
layer: "use-case" /* UseCase */,
|
|
1641
|
-
name: "InvalidUseCasePlugin",
|
|
1642
|
-
message: ({ reason }, source) => `Invalid use case plugin "${source}": ${reason}.`
|
|
1643
|
-
});
|
|
1644
|
-
var UnknownUseCasePlugin2 = exceptionOf2({
|
|
1645
|
-
layer: "use-case" /* UseCase */,
|
|
1646
|
-
name: "UnknownUseCasePlugin",
|
|
1647
|
-
message: ({ name }) => `No use case plugin "${name}" in the catalog.`
|
|
1648
|
-
});
|
|
1649
|
-
var IncompatibleUseCasePlugin2 = exceptionOf2({
|
|
1650
|
-
layer: "use-case" /* UseCase */,
|
|
1651
|
-
name: "IncompatibleUseCasePlugin",
|
|
1652
|
-
message: ({ reason }, source) => `Incompatible use case plugin "${source}": ${reason}.`
|
|
1653
|
-
});
|
|
1654
|
-
var UseCaseUnavailable2 = exceptionOf2({
|
|
1655
|
-
layer: "use-case" /* UseCase */,
|
|
1656
|
-
name: "UseCaseUnavailable",
|
|
1657
|
-
message: ({
|
|
1658
|
-
repository,
|
|
1659
|
-
reason
|
|
1660
|
-
}, source) => `The use case "${source}" is not available for "${repository}": ${reason}.`
|
|
1661
|
-
});
|
|
1662
|
-
var UseCaseEntityNotFound2 = exceptionOf2({
|
|
1663
|
-
layer: "use-case" /* UseCase */,
|
|
1664
|
-
name: "UseCaseEntityNotFound",
|
|
1665
|
-
message: "The entity was not found."
|
|
1666
|
-
});
|
|
1667
|
-
var UseCaseEntityConflict2 = exceptionOf2({
|
|
1668
|
-
layer: "use-case" /* UseCase */,
|
|
1669
|
-
name: "UseCaseEntityConflict",
|
|
1670
|
-
message: "The entity already exists."
|
|
1671
|
-
});
|
|
1672
|
-
var UseCaseInvalidCredentials2 = exceptionOf2({
|
|
1673
|
-
layer: "use-case" /* UseCase */,
|
|
1674
|
-
name: "UseCaseInvalidCredentials",
|
|
1675
|
-
message: "Invalid credentials."
|
|
1676
|
-
});
|
|
1677
|
-
|
|
1678
|
-
// src/core/use-case/steps/references.ts
|
|
1679
|
-
var RESERVED_NAMES = [
|
|
1680
|
-
"input",
|
|
1681
|
-
"context",
|
|
1682
|
-
"env",
|
|
1683
|
-
"literal"
|
|
1684
|
-
];
|
|
1685
|
-
var IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
1686
|
-
var SEGMENT = /^(?:[A-Za-z_][A-Za-z0-9_]*|0|[1-9][0-9]*)$/;
|
|
1687
|
-
var BLOCKED = new Set(["__proto__", "constructor", "prototype"]);
|
|
1688
|
-
function isStepName(value) {
|
|
1689
|
-
return typeof value === "string" && IDENTIFIER.test(value);
|
|
1690
|
-
}
|
|
1691
|
-
var parsed = new Map;
|
|
1692
|
-
function parseReference(text) {
|
|
1693
|
-
const cached = parsed.get(text);
|
|
1694
|
-
if (cached !== undefined)
|
|
1695
|
-
return cached ?? undefined;
|
|
1696
|
-
let result = null;
|
|
1697
|
-
if (text.startsWith("$")) {
|
|
1698
|
-
const [root, ...path] = text.slice(1).split(".");
|
|
1699
|
-
if (root !== undefined && IDENTIFIER.test(root) && path.every((segment) => SEGMENT.test(segment))) {
|
|
1700
|
-
result = Object.freeze({ root, path: Object.freeze(path) });
|
|
1701
|
-
}
|
|
1702
|
-
}
|
|
1703
|
-
if (parsed.size < 4096)
|
|
1704
|
-
parsed.set(text, result);
|
|
1705
|
-
return result ?? undefined;
|
|
1706
|
-
}
|
|
1707
|
-
function isPlain(value) {
|
|
1708
|
-
if (typeof value !== "object" || value === null)
|
|
1709
|
-
return false;
|
|
1710
|
-
const prototype = Object.getPrototypeOf(value);
|
|
1711
|
-
return prototype === Object.prototype || prototype === null;
|
|
1712
|
-
}
|
|
1713
|
-
function isLiteralEscape(value) {
|
|
1714
|
-
return isPlain(value) && Object.keys(value).length === 1 && Object.hasOwn(value, "literal");
|
|
1715
|
-
}
|
|
1716
|
-
function walkValue(value, onReference, onIssue) {
|
|
1717
|
-
if (typeof value === "string") {
|
|
1718
|
-
if (!value.startsWith("$"))
|
|
1719
|
-
return;
|
|
1720
|
-
const reference = parseReference(value);
|
|
1721
|
-
if (!reference)
|
|
1722
|
-
onIssue("invalid_reference", value);
|
|
1723
|
-
else
|
|
1724
|
-
onReference(reference, value);
|
|
1725
|
-
return;
|
|
1726
|
-
}
|
|
1727
|
-
if (value === null || typeof value === "number" || typeof value === "boolean") {
|
|
1728
|
-
return;
|
|
1729
|
-
}
|
|
1730
|
-
if (isLiteralEscape(value)) {
|
|
1731
|
-
return;
|
|
1732
|
-
}
|
|
1733
|
-
if (Array.isArray(value)) {
|
|
1734
|
-
for (const item of value)
|
|
1735
|
-
walkValue(item, onReference, onIssue);
|
|
1736
|
-
return;
|
|
1737
|
-
}
|
|
1738
|
-
if (isPlain(value)) {
|
|
1739
|
-
for (const item of Object.values(value)) {
|
|
1740
|
-
walkValue(item, onReference, onIssue);
|
|
1741
|
-
}
|
|
1742
|
-
return;
|
|
1743
|
-
}
|
|
1744
|
-
onIssue("invalid_value", String(value));
|
|
1745
|
-
}
|
|
1746
|
-
function followPath(from, path) {
|
|
1747
|
-
let current = from;
|
|
1748
|
-
for (const segment of path) {
|
|
1749
|
-
if (typeof current !== "object" && typeof current !== "function" || current === null || BLOCKED.has(segment)) {
|
|
1750
|
-
return;
|
|
1751
|
-
}
|
|
1752
|
-
current = current[segment];
|
|
1753
|
-
}
|
|
1754
|
-
return current;
|
|
1755
|
-
}
|
|
1756
|
-
function resolveValue(value, lookup) {
|
|
1757
|
-
if (typeof value === "string") {
|
|
1758
|
-
if (!value.startsWith("$"))
|
|
1759
|
-
return value;
|
|
1760
|
-
const reference = parseReference(value);
|
|
1761
|
-
return reference ? lookup(reference) : undefined;
|
|
1762
|
-
}
|
|
1763
|
-
if (isLiteralEscape(value))
|
|
1764
|
-
return value.literal;
|
|
1765
|
-
if (Array.isArray(value))
|
|
1766
|
-
return value.map((item) => resolveValue(item, lookup));
|
|
1767
|
-
if (isPlain(value)) {
|
|
1768
|
-
return Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
1769
|
-
key,
|
|
1770
|
-
resolveValue(item, lookup)
|
|
1771
|
-
]));
|
|
1772
|
-
}
|
|
1773
|
-
return value;
|
|
1774
|
-
}
|
|
1775
|
-
function freezeData(value) {
|
|
1776
|
-
if (Array.isArray(value)) {
|
|
1777
|
-
return Object.freeze(value.map(freezeData));
|
|
1778
|
-
}
|
|
1779
|
-
if (isPlain(value)) {
|
|
1780
|
-
return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, item]) => [key, freezeData(item)])));
|
|
1781
|
-
}
|
|
1782
|
-
return value;
|
|
1783
|
-
}
|
|
1784
|
-
|
|
1785
|
-
// src/core/property/input.ts
|
|
1786
|
-
var PropertyInput2 = Symbol("propertyInput");
|
|
1787
|
-
function propertyInputOf2(field) {
|
|
1788
|
-
const property = fieldOf(field);
|
|
1789
|
-
if (!isProperty(property))
|
|
1790
|
-
return;
|
|
1791
|
-
const declared = property[PropertyInput2];
|
|
1792
|
-
return typeof declared === "object" && declared !== null && isProperty(declared.accepts) && typeof declared.convert === "function" ? declared : undefined;
|
|
1793
|
-
}
|
|
1794
|
-
|
|
1795
|
-
// src/core/use-case/target.ts
|
|
1796
|
-
function isOptionalField(field) {
|
|
1797
|
-
if (isProperty(field))
|
|
1798
|
-
return field.type.optional === true;
|
|
1799
|
-
const record = recordFieldOf(field);
|
|
1800
|
-
if (record)
|
|
1801
|
-
return record.optional;
|
|
1802
|
-
return field.optional === true;
|
|
1803
|
-
}
|
|
1804
|
-
function planOf(entity, key, owners) {
|
|
1805
|
-
const field = fieldOf(entity.blueprint.properties[key]);
|
|
1806
|
-
const optional = isOptionalField(field);
|
|
1807
|
-
const record = recordFieldOf(field);
|
|
1808
|
-
const item = isBlueprintList(field) ? recordFieldOf(field.item) : undefined;
|
|
1809
|
-
const related = [record?.record, item?.record].find(isEntityClass2);
|
|
1810
|
-
const kind = record ? related ? "entity" : "record" : isBlueprintList(field) ? related ? "entity-list" : "list" : isBlueprint(field) || isBlueprintVariant(field) ? "blueprint" : "property";
|
|
1811
|
-
let input = { key, field };
|
|
1812
|
-
if (kind === "entity" && related) {
|
|
1813
|
-
const id = related.blueprint.properties.id;
|
|
1814
|
-
input = { key: `${key}Id`, field: optional ? optionalOf2(id) : id };
|
|
1815
|
-
}
|
|
1816
|
-
const declared = kind === "property" ? propertyInputOf2(field) : undefined;
|
|
1817
|
-
if (declared) {
|
|
1818
|
-
input = {
|
|
1819
|
-
key,
|
|
1820
|
-
field: optional ? optionalOf2(declared.accepts) : declared.accepts
|
|
1821
|
-
};
|
|
1822
|
-
}
|
|
1823
|
-
return Object.freeze({
|
|
1824
|
-
key,
|
|
1825
|
-
field,
|
|
1826
|
-
identity: identityKeysOf(entity).includes(key),
|
|
1827
|
-
plugin: owners.get(key),
|
|
1828
|
-
sensitive: entity.options.sensitive.includes(key),
|
|
1829
|
-
unique: entity.options.unique.includes(key),
|
|
1830
|
-
optional,
|
|
1831
|
-
change: entity.options.change.includes(key),
|
|
1832
|
-
kind,
|
|
1833
|
-
related,
|
|
1834
|
-
input: Object.freeze(input),
|
|
1835
|
-
convert: declared ? (value) => value === undefined ? undefined : declared.convert(value) : undefined
|
|
1836
|
-
});
|
|
1837
|
-
}
|
|
1838
|
-
function useCaseTargetOf2(repository, source = "useCaseTargetOf") {
|
|
1839
|
-
const state = findState(repository);
|
|
1840
|
-
if (!state)
|
|
1841
|
-
throw new TypeError("Expected a repository made by repositoryOf");
|
|
1842
|
-
const { entity } = state;
|
|
1843
|
-
const unavailable = (reason) => {
|
|
1844
|
-
throw new UseCaseUnavailable2({
|
|
1845
|
-
source,
|
|
1846
|
-
details: { repository: state.name, reason }
|
|
1847
|
-
});
|
|
1848
|
-
};
|
|
1849
|
-
const owners = new Map;
|
|
1850
|
-
for (const plugin of pluginsOf2(entity)) {
|
|
1851
|
-
for (const key of plugin.keys)
|
|
1852
|
-
owners.set(key, plugin.name);
|
|
1853
|
-
}
|
|
1854
|
-
const fields = Object.freeze(Object.keys(entity.blueprint.properties).map((key) => planOf(entity, key, owners)));
|
|
1855
|
-
const field = (key) => fields.find((plan) => plan.key === key) ?? unavailable(`the entity has no key "${key}"`);
|
|
1856
|
-
return Object.freeze({
|
|
1857
|
-
repository,
|
|
1858
|
-
repositoryName: state.name,
|
|
1859
|
-
entity,
|
|
1860
|
-
entityName: recordNameOf2(entity),
|
|
1861
|
-
actions: state.actions,
|
|
1862
|
-
deletion: state.deletion,
|
|
1863
|
-
fields,
|
|
1864
|
-
changeKeys: entity.options.change,
|
|
1865
|
-
uniqueKeys: entity.options.unique,
|
|
1866
|
-
field,
|
|
1867
|
-
relation(key, actions = []) {
|
|
1868
|
-
const { related } = field(key);
|
|
1869
|
-
const found = related ? relatedRepositoryOf(repository, related) : undefined;
|
|
1870
|
-
if (!found) {
|
|
1871
|
-
return unavailable(`the repository has no relation for the entity of "${key}"`);
|
|
1872
|
-
}
|
|
1873
|
-
const missing = actions.filter((action) => !found.actions.includes(action));
|
|
1874
|
-
if (missing.length > 0) {
|
|
1875
|
-
unavailable(`the repository of "${key}" lacks the action ${missing.map((action) => `"${action}"`).join(", ")}`);
|
|
1876
|
-
}
|
|
1877
|
-
return found;
|
|
1878
|
-
},
|
|
1879
|
-
unavailable
|
|
1880
|
-
});
|
|
1881
|
-
}
|
|
1882
|
-
|
|
1883
|
-
// src/core/use-case/access.ts
|
|
1884
|
-
function isObject(value) {
|
|
1885
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1886
|
-
}
|
|
1887
|
-
var ACCESS_KEYS = ["public", "scopes", "owner", "context"];
|
|
1888
|
-
var PRINCIPAL_VALUE = /^(subject|scopes|claims\.[^.]+)$/;
|
|
1889
|
-
function isPublicAccess(access) {
|
|
1890
|
-
return access.public === true;
|
|
1891
|
-
}
|
|
1892
|
-
function accessDeclarationOf(access, blueprint) {
|
|
1893
|
-
if (!isObject(access)) {
|
|
1894
|
-
return "Expected access to be { public: true } or { scopes?, owner?, context? }.";
|
|
1895
|
-
}
|
|
1896
|
-
for (const key of Object.keys(access)) {
|
|
1897
|
-
if (!ACCESS_KEYS.includes(key))
|
|
1898
|
-
return `access has no option "${key}".`;
|
|
1899
|
-
}
|
|
1900
|
-
const { scopes, owner, context } = access;
|
|
1901
|
-
if (access.public !== undefined && typeof access.public !== "boolean") {
|
|
1902
|
-
return "Expected access.public to be a boolean.";
|
|
1903
|
-
}
|
|
1904
|
-
if (access.public === true) {
|
|
1905
|
-
if (Object.keys(access).length > 1) {
|
|
1906
|
-
return "access.public leaves no room for scopes, owner or context: anyone may call.";
|
|
1907
|
-
}
|
|
1908
|
-
return Object.freeze({ public: true });
|
|
1909
|
-
}
|
|
1910
|
-
if (scopes !== undefined) {
|
|
1911
|
-
if (!Array.isArray(scopes) || scopes.some((scope) => typeof scope !== "string" || scope === "") || new Set(scopes).size !== scopes.length) {
|
|
1912
|
-
return "Expected access.scopes to be distinct, non-empty strings.";
|
|
1913
|
-
}
|
|
1914
|
-
}
|
|
1915
|
-
if (owner !== undefined) {
|
|
1916
|
-
if (!isObject(owner) || Object.keys(owner).some((key) => key !== "input" && key !== "claim") || typeof owner.input !== "string" || owner.claim !== undefined && (typeof owner.claim !== "string" || owner.claim === "")) {
|
|
1917
|
-
return "Expected access.owner to be { input, claim? }.";
|
|
1918
|
-
}
|
|
1919
|
-
if (!isScalarKey(blueprint, owner.input)) {
|
|
1920
|
-
return `access.owner compares "${owner.input}", which is not a scalar key of the input.`;
|
|
1921
|
-
}
|
|
1922
|
-
}
|
|
1923
|
-
if (context !== undefined) {
|
|
1924
|
-
if (!isObject(context)) {
|
|
1925
|
-
return "Expected access.context to map context keys to values of the caller.";
|
|
1926
|
-
}
|
|
1927
|
-
for (const [key, value] of Object.entries(context)) {
|
|
1928
|
-
if (typeof value !== "string" || !PRINCIPAL_VALUE.test(value)) {
|
|
1929
|
-
return `Expected access.context["${key}"] to be "subject", "scopes" or "claims.<name>".`;
|
|
1930
|
-
}
|
|
1931
|
-
}
|
|
1932
|
-
}
|
|
1933
|
-
return Object.freeze({
|
|
1934
|
-
...scopes === undefined ? {} : { scopes: Object.freeze([...scopes]) },
|
|
1935
|
-
...owner === undefined ? {} : { owner: Object.freeze({ ...owner }) },
|
|
1936
|
-
...context === undefined ? {} : { context: Object.freeze({ ...context }) }
|
|
1937
|
-
});
|
|
1938
|
-
}
|
|
1939
|
-
|
|
1940
|
-
// src/core/use-case/steps/run.ts
|
|
1941
|
-
var FORBIDDEN_METHODS = new Set([
|
|
1942
|
-
"set",
|
|
1943
|
-
"emit",
|
|
1944
|
-
"constructor",
|
|
1945
|
-
"__proto__",
|
|
1946
|
-
"hasOwnProperty",
|
|
1947
|
-
"isPrototypeOf",
|
|
1948
|
-
"propertyIsEnumerable",
|
|
1949
|
-
"toLocaleString",
|
|
1950
|
-
"valueOf",
|
|
1951
|
-
"__defineGetter__",
|
|
1952
|
-
"__defineSetter__",
|
|
1953
|
-
"__lookupGetter__",
|
|
1954
|
-
"__lookupSetter__"
|
|
1955
|
-
]);
|
|
1956
|
-
function isRecordInstance(value) {
|
|
1957
|
-
return typeof value === "object" && value !== null && isRecordClass(value.constructor);
|
|
1958
|
-
}
|
|
1959
|
-
async function executeSteps(dependencies = {}) {
|
|
1960
|
-
const { useCaseName, steps = [] } = this.constructor;
|
|
1961
|
-
const named = new Map;
|
|
1962
|
-
const lookup = ({ root, path }) => {
|
|
1963
|
-
if (root === "input")
|
|
1964
|
-
return followPath(this.input, path);
|
|
1965
|
-
if (root === "context")
|
|
1966
|
-
return followPath(this.context, path);
|
|
1967
|
-
if (root === "env")
|
|
1968
|
-
return followPath(dependencies[path[0]], path.slice(1));
|
|
1969
|
-
return followPath(named.get(root), path);
|
|
1970
|
-
};
|
|
1971
|
-
const value = (raw) => resolveValue(raw, lookup);
|
|
1972
|
-
for (const [index, step] of steps.entries()) {
|
|
1973
|
-
const invalid = (reason) => {
|
|
1974
|
-
throw new InvalidUseCaseStep2({
|
|
1975
|
-
source: useCaseName,
|
|
1976
|
-
details: { index, reason, code: "invalid_target" }
|
|
1977
|
-
});
|
|
1978
|
-
};
|
|
1979
|
-
const list = (raw) => {
|
|
1980
|
-
if (raw === undefined)
|
|
1981
|
-
return [];
|
|
1982
|
-
const resolved = value(raw);
|
|
1983
|
-
if (!Array.isArray(resolved))
|
|
1984
|
-
invalid("args must resolve to a list.");
|
|
1985
|
-
return resolved;
|
|
1986
|
-
};
|
|
1987
|
-
const bind = (result) => {
|
|
1988
|
-
if (typeof step.as === "string")
|
|
1989
|
-
named.set(step.as, result);
|
|
1990
|
-
};
|
|
1991
|
-
const target = (field) => {
|
|
1992
|
-
const resolved = value(step[field]);
|
|
1993
|
-
if (!isRecordInstance(resolved)) {
|
|
1994
|
-
invalid(`${field} must reference a record or entity.`);
|
|
1995
|
-
}
|
|
1996
|
-
return resolved;
|
|
1997
|
-
};
|
|
1998
|
-
if (Object.hasOwn(step, "return"))
|
|
1999
|
-
return value(step.return);
|
|
2000
|
-
if (Object.hasOwn(step, "find")) {
|
|
2001
|
-
const [repository, action] = step.find.split(".");
|
|
2002
|
-
const methods = dependencies[repository];
|
|
2003
|
-
bind(await methods[action].call(methods, ...list(step.args)));
|
|
2004
|
-
} else if (Object.hasOwn(step, "ensure")) {
|
|
2005
|
-
const subject = value(step.value);
|
|
2006
|
-
const passed = {
|
|
2007
|
-
exists: subject !== undefined && subject !== null,
|
|
2008
|
-
absent: subject === undefined || subject === null,
|
|
2009
|
-
equals: sameValue2(subject, value(step.other)),
|
|
2010
|
-
notEquals: !sameValue2(subject, value(step.other)),
|
|
2011
|
-
true: subject === true,
|
|
2012
|
-
false: subject === false
|
|
2013
|
-
}[step.ensure];
|
|
2014
|
-
if (!passed) {
|
|
2015
|
-
const Thrown = step.error;
|
|
2016
|
-
throw new Thrown({
|
|
2017
|
-
source: useCaseName,
|
|
2018
|
-
...step.details === undefined ? {} : { details: value(step.details) }
|
|
2019
|
-
});
|
|
2020
|
-
}
|
|
2021
|
-
} else if (Object.hasOwn(step, "build")) {
|
|
2022
|
-
const Built = step.build;
|
|
2023
|
-
bind(new Built(value(step.values)));
|
|
2024
|
-
} else if (Object.hasOwn(step, "change")) {
|
|
2025
|
-
const record = target("change");
|
|
2026
|
-
const namespace = record.change;
|
|
2027
|
-
const key = step.key;
|
|
2028
|
-
if (typeof namespace !== "object" || namespace === null || typeof namespace[key] !== "function") {
|
|
2029
|
-
invalid(`"${key}" is not a change key of the record.`);
|
|
2030
|
-
}
|
|
2031
|
-
const changed = value(step.value);
|
|
2032
|
-
if (step.ifPresent !== true || changed !== undefined) {
|
|
2033
|
-
await namespace[key]?.(changed);
|
|
2034
|
-
}
|
|
2035
|
-
bind(record);
|
|
2036
|
-
} else if (Object.hasOwn(step, "invoke")) {
|
|
2037
|
-
const record = target("invoke");
|
|
2038
|
-
const method = step.method;
|
|
2039
|
-
if (FORBIDDEN_METHODS.has(method) || typeof record[method] !== "function") {
|
|
2040
|
-
invalid(`"${method}" is not a public method of the record.`);
|
|
2041
|
-
}
|
|
2042
|
-
bind(await record[method].apply(record, list(step.args)));
|
|
2043
|
-
} else if (Object.hasOwn(step, "call")) {
|
|
2044
|
-
const child = dependencies[step.call];
|
|
2045
|
-
bind(await child(value(step.input)));
|
|
2046
|
-
} else if (Object.hasOwn(step, "run")) {
|
|
2047
|
-
const fn = step.fn;
|
|
2048
|
-
bind(await fn(...list(step.args), { context: this.context, dependencies }));
|
|
2049
|
-
} else {
|
|
2050
|
-
const action = ["create", "update", "delete", "restore"].find((kind) => Object.hasOwn(step, kind));
|
|
2051
|
-
const methods = dependencies[step[action]];
|
|
2052
|
-
bind(await methods[action].call(methods, value(step.value)));
|
|
2053
|
-
}
|
|
2054
|
-
}
|
|
2055
|
-
return;
|
|
2056
|
-
}
|
|
2057
|
-
function interpretedStepsOf(UseCase) {
|
|
2058
|
-
const Declared = UseCase;
|
|
2059
|
-
return Declared.prototype.execute === executeSteps ? Declared.steps : undefined;
|
|
2060
|
-
}
|
|
2061
|
-
|
|
2062
|
-
// src/core/use-case/steps/validate.ts
|
|
2063
|
-
var SHAPES = {
|
|
2064
|
-
find: { required: ["find"], optional: ["args", "as"] },
|
|
2065
|
-
ensure: {
|
|
2066
|
-
required: ["ensure", "value", "error"],
|
|
2067
|
-
optional: ["other", "details"]
|
|
2068
|
-
},
|
|
2069
|
-
build: { required: ["build", "values", "as"], optional: [] },
|
|
2070
|
-
change: {
|
|
2071
|
-
required: ["change", "key", "value"],
|
|
2072
|
-
optional: ["ifPresent", "as"]
|
|
2073
|
-
},
|
|
2074
|
-
invoke: { required: ["invoke", "method"], optional: ["args", "as"] },
|
|
2075
|
-
create: { required: ["create", "value"], optional: ["as"] },
|
|
2076
|
-
update: { required: ["update", "value"], optional: ["as"] },
|
|
2077
|
-
delete: { required: ["delete", "value"], optional: ["as"] },
|
|
2078
|
-
restore: { required: ["restore", "value"], optional: ["as"] },
|
|
2079
|
-
call: { required: ["call", "input"], optional: ["as"] },
|
|
2080
|
-
run: { required: ["run", "fn"], optional: ["args", "as"] },
|
|
2081
|
-
return: { required: ["return"], optional: [] }
|
|
2082
|
-
};
|
|
2083
|
-
var KINDS = Object.keys(SHAPES);
|
|
2084
|
-
var ENSURE_KINDS = new Set([
|
|
2085
|
-
"exists",
|
|
2086
|
-
"absent",
|
|
2087
|
-
"equals",
|
|
2088
|
-
"notEquals",
|
|
2089
|
-
"true",
|
|
2090
|
-
"false"
|
|
2091
|
-
]);
|
|
2092
|
-
var READ_ACTION = /^(?:findBy.+|findManyBy.+|findMany|exists|findPage|findCursor|count)$/;
|
|
2093
|
-
var WRITE_ACTIONS = new Set([
|
|
2094
|
-
"create",
|
|
2095
|
-
"update",
|
|
2096
|
-
"delete",
|
|
2097
|
-
"restore",
|
|
2098
|
-
"increment"
|
|
2099
|
-
]);
|
|
2100
|
-
var INDEX = /^(?:0|[1-9][0-9]*)$/;
|
|
2101
|
-
var LENGTH = Symbol("length");
|
|
2102
|
-
var PRIMITIVE_SCHEMA_TYPES = new Set([
|
|
2103
|
-
"string",
|
|
2104
|
-
"number",
|
|
2105
|
-
"integer",
|
|
2106
|
-
"boolean",
|
|
2107
|
-
"null"
|
|
2108
|
-
]);
|
|
2109
|
-
function isPrimitiveProperty(field) {
|
|
2110
|
-
const schema = field?.type?.schema;
|
|
2111
|
-
if (typeof field !== "function" || schema === undefined)
|
|
2112
|
-
return false;
|
|
2113
|
-
try {
|
|
2114
|
-
const type = JSON.parse(schema.toString()).type;
|
|
2115
|
-
return typeof type === "string" && PRIMITIVE_SCHEMA_TYPES.has(type);
|
|
2116
|
-
} catch {
|
|
2117
|
-
return false;
|
|
2118
|
-
}
|
|
2119
|
-
}
|
|
2120
|
-
function missingSegment(field, path) {
|
|
2121
|
-
let current = field;
|
|
2122
|
-
for (const segment of path) {
|
|
2123
|
-
if (current === LENGTH)
|
|
2124
|
-
return segment;
|
|
2125
|
-
if (isBlueprint(current)) {
|
|
2126
|
-
if (!Object.hasOwn(current.properties, segment))
|
|
2127
|
-
return segment;
|
|
2128
|
-
current = current.properties[segment];
|
|
2129
|
-
continue;
|
|
2130
|
-
}
|
|
2131
|
-
if (isBlueprintList(current)) {
|
|
2132
|
-
if (segment === "length") {
|
|
2133
|
-
current = LENGTH;
|
|
2134
|
-
continue;
|
|
2135
|
-
}
|
|
2136
|
-
if (!INDEX.test(segment))
|
|
2137
|
-
return segment;
|
|
2138
|
-
current = current.item;
|
|
2139
|
-
continue;
|
|
2140
|
-
}
|
|
2141
|
-
if (isBlueprintVariant(current)) {
|
|
2142
|
-
if (segment === current.key)
|
|
2143
|
-
return;
|
|
2144
|
-
const variants = current.variants;
|
|
2145
|
-
const holder = Object.values(variants).map(blueprintOfVariant).find((variant) => Object.hasOwn(variant.properties, segment));
|
|
2146
|
-
if (!holder)
|
|
2147
|
-
return segment;
|
|
2148
|
-
current = holder.properties[segment];
|
|
2149
|
-
continue;
|
|
2150
|
-
}
|
|
2151
|
-
const record = recordFieldOf(current)?.record;
|
|
2152
|
-
if (record) {
|
|
2153
|
-
const { properties } = record.blueprint;
|
|
2154
|
-
if (Object.hasOwn(properties, segment)) {
|
|
2155
|
-
current = properties[segment];
|
|
2156
|
-
continue;
|
|
2157
|
-
}
|
|
2158
|
-
return segment in record.prototype ? undefined : segment;
|
|
2159
|
-
}
|
|
2160
|
-
return isPrimitiveProperty(current) ? segment : undefined;
|
|
2161
|
-
}
|
|
2162
|
-
return;
|
|
2163
|
-
}
|
|
2164
|
-
function validateSteps(declaration, steps) {
|
|
2165
|
-
const { name } = declaration;
|
|
2166
|
-
const fail = (index, code, reason) => {
|
|
2167
|
-
throw new InvalidUseCaseStep2({
|
|
2168
|
-
source: name,
|
|
2169
|
-
details: { index, reason, code }
|
|
2170
|
-
});
|
|
2171
|
-
};
|
|
2172
|
-
if (!Array.isArray(steps) || steps.length === 0) {
|
|
2173
|
-
fail(0, "missing_return", "Expected steps to be a list ending in return.");
|
|
2174
|
-
}
|
|
2175
|
-
const list = steps;
|
|
2176
|
-
const bound = new Map;
|
|
2177
|
-
const runs = new Set;
|
|
2178
|
-
let returned = false;
|
|
2179
|
-
const useCaseDependency = (key) => typeof key === "string" && Object.hasOwn(declaration.dependencies, key) ? declaration.dependencies[key] : undefined;
|
|
2180
|
-
const checkReference = (index, text) => {
|
|
2181
|
-
const reference = parseReference(text);
|
|
2182
|
-
if (!reference) {
|
|
2183
|
-
fail(index, "invalid_reference", `"${text}" is not a valid reference.`);
|
|
2184
|
-
return;
|
|
2185
|
-
}
|
|
2186
|
-
const { root, path } = reference;
|
|
2187
|
-
const firstKey = path[0];
|
|
2188
|
-
if (root === "input" || root === "context") {
|
|
2189
|
-
const blueprint = root === "input" ? declaration.blueprint : declaration.context;
|
|
2190
|
-
if (blueprint === undefined) {
|
|
2191
|
-
fail(index, "invalid_reference", `"${text}" uses $context, but the use case declares no context.`);
|
|
2192
|
-
return;
|
|
2193
|
-
}
|
|
2194
|
-
const missing = missingSegment(blueprint, path);
|
|
2195
|
-
if (missing !== undefined) {
|
|
2196
|
-
fail(index, "unknown_key", `"${text}" names "${missing}", which the ${root} does not declare.`);
|
|
2197
|
-
}
|
|
2198
|
-
return;
|
|
2199
|
-
}
|
|
2200
|
-
if (root === "env") {
|
|
2201
|
-
if (firstKey === undefined || !isEnvironment(useCaseDependency(firstKey))) {
|
|
2202
|
-
fail(index, "unknown_dependency", `"${text}" must name an environment dependency.`);
|
|
2203
|
-
}
|
|
2204
|
-
return;
|
|
2205
|
-
}
|
|
2206
|
-
if (RESERVED_NAMES.includes(root)) {
|
|
2207
|
-
fail(index, "invalid_reference", `"${root}" is reserved.`);
|
|
2208
|
-
return;
|
|
2209
|
-
}
|
|
2210
|
-
if (!bound.has(root)) {
|
|
2211
|
-
fail(index, "unbound_reference", `"${text}" uses "${root}", which no earlier step binds.`);
|
|
2212
|
-
}
|
|
2213
|
-
};
|
|
2214
|
-
const checkValue = (index, value, field) => {
|
|
2215
|
-
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.`));
|
|
2216
|
-
};
|
|
2217
|
-
const checkArgs = (index, args) => {
|
|
2218
|
-
if (args === undefined)
|
|
2219
|
-
return;
|
|
2220
|
-
if (typeof args === "string" && args.startsWith("$")) {
|
|
2221
|
-
checkReference(index, args);
|
|
2222
|
-
return;
|
|
2223
|
-
}
|
|
2224
|
-
if (!Array.isArray(args)) {
|
|
2225
|
-
fail(index, "invalid_step", "args must be a list of values or a reference to one.");
|
|
2226
|
-
}
|
|
2227
|
-
checkValue(index, args, "args");
|
|
2228
|
-
};
|
|
2229
|
-
const checkTarget = (index, target, kind) => {
|
|
2230
|
-
if (typeof target !== "string" || !target.startsWith("$")) {
|
|
2231
|
-
fail(index, "invalid_reference", `${kind} must be a reference.`);
|
|
2232
|
-
return;
|
|
2233
|
-
}
|
|
2234
|
-
checkReference(index, target);
|
|
2235
|
-
};
|
|
2236
|
-
const repositoryOf = (index, key, action) => {
|
|
2237
|
-
const dependency = useCaseDependency(key);
|
|
2238
|
-
if (!isRepository(dependency)) {
|
|
2239
|
-
return fail(index, "unknown_dependency", `"${String(key)}" is not a repository dependency.`);
|
|
2240
|
-
}
|
|
2241
|
-
const actions = dependency.actions;
|
|
2242
|
-
if (!actions.includes(action)) {
|
|
2243
|
-
fail(index, "unknown_action", `The repository "${String(key)}" has no "${action}" action.`);
|
|
2244
|
-
}
|
|
2245
|
-
return { actions };
|
|
2246
|
-
};
|
|
2247
|
-
for (const [index, step] of list.entries()) {
|
|
2248
|
-
if (!isPlain(step) || Array.isArray(step)) {
|
|
2249
|
-
fail(index, "unknown_step", "A step must be an object.");
|
|
2250
|
-
}
|
|
2251
|
-
const record = step;
|
|
2252
|
-
const kinds = KINDS.filter((kind) => Object.hasOwn(record, kind));
|
|
2253
|
-
if (kinds.length !== 1) {
|
|
2254
|
-
fail(index, "unknown_step", kinds.length === 0 ? `Unknown step; expected one of ${KINDS.join(", ")}.` : `A step has one kind, found ${kinds.join(" and ")}.`);
|
|
2255
|
-
}
|
|
2256
|
-
const kind = kinds[0];
|
|
2257
|
-
const shape = SHAPES[kind];
|
|
2258
|
-
for (const key of shape.required) {
|
|
2259
|
-
if (!Object.hasOwn(record, key) || record[key] === undefined) {
|
|
2260
|
-
fail(index, "invalid_step", `A ${kind} step needs "${key}".`);
|
|
2261
|
-
}
|
|
2262
|
-
}
|
|
2263
|
-
for (const key of Object.keys(record)) {
|
|
2264
|
-
if (!shape.required.includes(key) && !shape.optional.includes(key)) {
|
|
2265
|
-
fail(index, "invalid_step", `A ${kind} step has no "${key}".`);
|
|
2266
|
-
}
|
|
2267
|
-
}
|
|
2268
|
-
const as = record.as;
|
|
2269
|
-
if (as !== undefined) {
|
|
2270
|
-
if (!isStepName(as) || RESERVED_NAMES.includes(as) || bound.has(as)) {
|
|
2271
|
-
fail(index, "invalid_step_name", `"${String(as)}" must be an identifier, not reserved, bound once.`);
|
|
2272
|
-
}
|
|
2273
|
-
}
|
|
2274
|
-
let knownRecord;
|
|
2275
|
-
switch (kind) {
|
|
2276
|
-
case "find": {
|
|
2277
|
-
const target = record.find;
|
|
2278
|
-
const parts = typeof target === "string" ? target.split(".") : [];
|
|
2279
|
-
const [repository, action] = parts;
|
|
2280
|
-
if (parts.length !== 2 || !repository || !action) {
|
|
2281
|
-
fail(index, "invalid_step", 'find must be "<repository>.<action>".');
|
|
2282
|
-
}
|
|
2283
|
-
if (WRITE_ACTIONS.has(action)) {
|
|
2284
|
-
fail(index, "write_in_find", `find cannot call "${action}".`);
|
|
2285
|
-
}
|
|
2286
|
-
if (action === "forEachPage") {
|
|
2287
|
-
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.');
|
|
2288
|
-
}
|
|
2289
|
-
if (!READ_ACTION.test(action)) {
|
|
2290
|
-
fail(index, "unknown_action", `"${action}" is not a read action.`);
|
|
2291
|
-
}
|
|
2292
|
-
repositoryOf(index, repository, action);
|
|
2293
|
-
checkArgs(index, record.args);
|
|
2294
|
-
break;
|
|
2295
|
-
}
|
|
2296
|
-
case "ensure": {
|
|
2297
|
-
if (!ENSURE_KINDS.has(record.ensure)) {
|
|
2298
|
-
fail(index, "invalid_step", `ensure must be one of ${[...ENSURE_KINDS].join(", ")}.`);
|
|
2299
|
-
}
|
|
2300
|
-
const compares = record.ensure === "equals" || record.ensure === "notEquals";
|
|
2301
|
-
if (compares && record.other === undefined) {
|
|
2302
|
-
fail(index, "invalid_step", `ensure "${record.ensure}" needs other.`);
|
|
2303
|
-
}
|
|
2304
|
-
if (!compares && record.other !== undefined) {
|
|
2305
|
-
fail(index, "invalid_step", `ensure "${String(record.ensure)}" has no other.`);
|
|
2306
|
-
}
|
|
2307
|
-
if (!isDeclaredException(record.error) || !declaration.errors.includes(record.error)) {
|
|
2308
|
-
fail(index, "undeclared_error", "ensure.error must be a class made by exceptionOf, listed in errors.");
|
|
2309
|
-
}
|
|
2310
|
-
checkValue(index, record.value, "value");
|
|
2311
|
-
if (record.other !== undefined)
|
|
2312
|
-
checkValue(index, record.other, "other");
|
|
2313
|
-
if (record.details !== undefined) {
|
|
2314
|
-
checkValue(index, record.details, "details");
|
|
2315
|
-
}
|
|
2316
|
-
break;
|
|
2317
|
-
}
|
|
2318
|
-
case "build": {
|
|
2319
|
-
if (!isRecordClass(record.build)) {
|
|
2320
|
-
fail(index, "invalid_class", "build must be a record or entity class.");
|
|
2321
|
-
}
|
|
2322
|
-
const values = record.values;
|
|
2323
|
-
if (!(typeof values === "string" && values.startsWith("$")) && !isPlain(values)) {
|
|
2324
|
-
fail(index, "invalid_step", "build.values must be a mapping or a reference.");
|
|
2325
|
-
}
|
|
2326
|
-
checkValue(index, values, "values");
|
|
2327
|
-
knownRecord = record.build;
|
|
2328
|
-
break;
|
|
2329
|
-
}
|
|
2330
|
-
case "change": {
|
|
2331
|
-
checkTarget(index, record.change, "change");
|
|
2332
|
-
if (typeof record.key !== "string" || record.key === "") {
|
|
2333
|
-
fail(index, "invalid_step", "change.key must be a string.");
|
|
2334
|
-
}
|
|
2335
|
-
if (record.ifPresent !== undefined && typeof record.ifPresent !== "boolean") {
|
|
2336
|
-
fail(index, "invalid_step", "change.ifPresent must be a boolean.");
|
|
2337
|
-
}
|
|
2338
|
-
const reference = parseReference(record.change);
|
|
2339
|
-
const known = reference && reference.path.length === 0 ? bound.get(reference.root)?.record : undefined;
|
|
2340
|
-
if (known) {
|
|
2341
|
-
const keys = known.options?.change ?? [];
|
|
2342
|
-
if (!keys.includes(record.key)) {
|
|
2343
|
-
fail(index, "invalid_change_key", `"${String(record.key)}" is not a change key of ${known.name}.`);
|
|
2344
|
-
}
|
|
2345
|
-
}
|
|
2346
|
-
checkValue(index, record.value, "value");
|
|
2347
|
-
knownRecord = known;
|
|
2348
|
-
break;
|
|
2349
|
-
}
|
|
2350
|
-
case "invoke": {
|
|
2351
|
-
checkTarget(index, record.invoke, "invoke");
|
|
2352
|
-
if (typeof record.method !== "string" || record.method === "") {
|
|
2353
|
-
fail(index, "invalid_step", "invoke.method must be a string.");
|
|
2354
|
-
}
|
|
2355
|
-
checkArgs(index, record.args);
|
|
2356
|
-
break;
|
|
2357
|
-
}
|
|
2358
|
-
case "create":
|
|
2359
|
-
case "update":
|
|
2360
|
-
case "delete":
|
|
2361
|
-
case "restore": {
|
|
2362
|
-
repositoryOf(index, record[kind], kind);
|
|
2363
|
-
checkValue(index, record.value, "value");
|
|
2364
|
-
break;
|
|
2365
|
-
}
|
|
2366
|
-
case "call": {
|
|
2367
|
-
const dependency = useCaseDependency(record.call);
|
|
2368
|
-
if (typeof dependency !== "function" || dependency[Kind2] !== "use-case") {
|
|
2369
|
-
fail(index, "unknown_dependency", `"${String(record.call)}" is not a use case dependency.`);
|
|
2370
|
-
}
|
|
2371
|
-
checkValue(index, record.input, "input");
|
|
2372
|
-
break;
|
|
2373
|
-
}
|
|
2374
|
-
case "run": {
|
|
2375
|
-
const runName = record.run;
|
|
2376
|
-
if (!isStepName(runName) || runs.has(runName)) {
|
|
2377
|
-
fail(index, "invalid_step_name", `run "${String(runName)}" must be an identifier, unique in the use case.`);
|
|
2378
|
-
}
|
|
2379
|
-
if (typeof record.fn !== "function") {
|
|
2380
|
-
fail(index, "invalid_step", "run.fn must be a function.");
|
|
2381
|
-
}
|
|
2382
|
-
runs.add(runName);
|
|
2383
|
-
checkArgs(index, record.args);
|
|
2384
|
-
break;
|
|
2385
|
-
}
|
|
2386
|
-
case "return": {
|
|
2387
|
-
checkValue(index, record.return, "return");
|
|
2388
|
-
returned = true;
|
|
2389
|
-
if (index !== list.length - 1) {
|
|
2390
|
-
fail(index, "misplaced_return", "return must be the last step, exactly once.");
|
|
2391
|
-
}
|
|
2392
|
-
break;
|
|
2393
|
-
}
|
|
2394
|
-
}
|
|
2395
|
-
if (typeof as === "string")
|
|
2396
|
-
bound.set(as, { record: knownRecord });
|
|
2397
|
-
}
|
|
2398
|
-
if (!returned) {
|
|
2399
|
-
fail(list.length, "missing_return", "The steps must end with return.");
|
|
2400
|
-
}
|
|
2401
|
-
}
|
|
2402
|
-
|
|
2403
|
-
// src/core/use-case/use-case.ts
|
|
2404
|
-
var InvalidUseCaseDeclaration2 = exceptionOf2({
|
|
2405
|
-
layer: "use-case" /* UseCase */,
|
|
2406
|
-
name: "InvalidUseCaseDeclaration",
|
|
2407
|
-
message: ({ reason }) => reason
|
|
2408
|
-
});
|
|
2409
|
-
var UseCaseAlreadyExecuted2 = exceptionOf2({
|
|
2410
|
-
layer: "use-case" /* UseCase */,
|
|
2411
|
-
name: "UseCaseAlreadyExecuted",
|
|
2412
|
-
message: "A UseCase instance can execute only once."
|
|
2413
|
-
});
|
|
2414
|
-
var InvalidUseCaseDependencies2 = exceptionOf2({
|
|
2415
|
-
layer: "use-case" /* UseCase */,
|
|
2416
|
-
name: "InvalidUseCaseDependencies",
|
|
2417
|
-
message: "Dependency overrides must contain only declared names: functions for use cases, objects of methods for repositories, and valid variables for environments."
|
|
2418
|
-
});
|
|
2419
|
-
function loggerOfCall(state) {
|
|
2420
|
-
return resolveLogger(state.scope?.logger, state.owner);
|
|
2421
|
-
}
|
|
2422
|
-
var states2 = new WeakMap;
|
|
2423
|
-
function invalidValue(issues, source, phase) {
|
|
2424
|
-
throw new Exception2({
|
|
2425
|
-
layer: "use-case" /* UseCase */,
|
|
2426
|
-
name: phase === "input" ? "InvalidUseCaseInput" : phase === "context" ? "InvalidUseCaseContext" : "InvalidUseCaseResponse",
|
|
2427
|
-
source,
|
|
2428
|
-
message: `Invalid UseCase ${phase}.`,
|
|
2429
|
-
details: { issues },
|
|
2430
|
-
cause: issues.find((issue) => issue.cause)?.cause
|
|
2431
|
-
});
|
|
2432
|
-
}
|
|
2433
|
-
function collectEvents(blueprint, value, events) {
|
|
2434
|
-
const visited = new WeakSet;
|
|
2435
|
-
const visit = (field, current) => {
|
|
2436
|
-
if (typeof current !== "object" || current === null || visited.has(current))
|
|
2437
|
-
return;
|
|
2438
|
-
visited.add(current);
|
|
2439
|
-
if (isEntity2(current))
|
|
2440
|
-
events.push(...current.pullEvents());
|
|
2441
|
-
if (isBlueprintList(field)) {
|
|
2442
|
-
for (const item of current)
|
|
2443
|
-
visit(field.item, item);
|
|
2444
|
-
return;
|
|
2445
|
-
}
|
|
2446
|
-
const shape = isBlueprint(field) ? field : isBlueprintVariant(field) ? isRecordClass(current.constructor) ? current.constructor.blueprint : variantOfValue(field, current) : recordFieldOf(field)?.record.blueprint;
|
|
2447
|
-
if (!shape)
|
|
2448
|
-
return;
|
|
2449
|
-
for (const [key, child] of Object.entries(shape.properties)) {
|
|
2450
|
-
visit(child, current[key]);
|
|
2451
|
-
}
|
|
2452
|
-
};
|
|
2453
|
-
visit(blueprint, value);
|
|
2454
|
-
}
|
|
2455
|
-
function pullEntities(value, Entity, events) {
|
|
2456
|
-
for (const item of Array.isArray(value) ? value : [value]) {
|
|
2457
|
-
if (isEntity2(item) && item instanceof Entity) {
|
|
2458
|
-
events.push(...item.pullEvents());
|
|
2459
|
-
}
|
|
2460
|
-
}
|
|
2461
|
-
}
|
|
2462
|
-
function methodNamesOf(repository) {
|
|
2463
|
-
const names = new Set;
|
|
2464
|
-
for (let current = repository;current !== null && current !== AbstractRepository.prototype && current !== Object.prototype; current = Object.getPrototypeOf(current)) {
|
|
2465
|
-
for (const name of Object.getOwnPropertyNames(current)) {
|
|
2466
|
-
const descriptor = Object.getOwnPropertyDescriptor(current, name);
|
|
2467
|
-
if (name !== "constructor" && typeof descriptor?.value === "function") {
|
|
2468
|
-
names.add(name);
|
|
2469
|
-
}
|
|
2470
|
-
}
|
|
2471
|
-
}
|
|
2472
|
-
return [...names];
|
|
2473
|
-
}
|
|
2474
|
-
function bindRepository(repository, overrides, state, source) {
|
|
2475
|
-
const Entity = repository.entity;
|
|
2476
|
-
const repositoryName = recordNameOf2(Entity);
|
|
2477
|
-
const bound = Object.create(null);
|
|
2478
|
-
const view = new Proxy(Object.create(repository), {
|
|
2479
|
-
get: (_target, key, receiver) => {
|
|
2480
|
-
if (typeof key === "string" && Object.hasOwn(bound, key)) {
|
|
2481
|
-
return bound[key];
|
|
2482
|
-
}
|
|
2483
|
-
if (key === ScopeKey)
|
|
2484
|
-
return state.scope;
|
|
2485
|
-
const value = Reflect.get(repository, key, receiver);
|
|
2486
|
-
return key === "provider" && state.scope ? state.scope.providers.get(value) ?? value : value;
|
|
2487
|
-
},
|
|
2488
|
-
set: (_target, key, value) => Reflect.set(repository, key, value),
|
|
2489
|
-
defineProperty: (_target, key, descriptor) => Reflect.defineProperty(repository, key, descriptor),
|
|
2490
|
-
deleteProperty: (_target, key) => Reflect.deleteProperty(repository, key)
|
|
2491
|
-
});
|
|
2492
|
-
const bind = (target, name, receiver) => {
|
|
2493
|
-
const method = target[name];
|
|
2494
|
-
bound[name] = async (...args) => {
|
|
2495
|
-
if (!state.active)
|
|
2496
|
-
throw new InvalidUseCaseDependencies2({ source });
|
|
2497
|
-
const started = clock();
|
|
2498
|
-
const result = await method.apply(receiver, args);
|
|
2499
|
-
writeLog(loggerOfCall(state), "debug", "repository", {
|
|
2500
|
-
repository: repositoryName,
|
|
2501
|
-
method: name,
|
|
2502
|
-
useCase: source,
|
|
2503
|
-
ms: elapsed(started)
|
|
2504
|
-
});
|
|
2505
|
-
for (const arg of args)
|
|
2506
|
-
pullEntities(arg, Entity, state.events);
|
|
2507
|
-
pullEntities(result, Entity, state.events);
|
|
2508
|
-
return result;
|
|
2509
|
-
};
|
|
2510
|
-
};
|
|
2511
|
-
for (const name of methodNamesOf(repository))
|
|
2512
|
-
bind(repository, name, view);
|
|
2513
|
-
if (overrides) {
|
|
2514
|
-
for (const name of new Set([
|
|
2515
|
-
...methodNamesOf(repository),
|
|
2516
|
-
...methodNamesOf(overrides)
|
|
2517
|
-
])) {
|
|
2518
|
-
if (typeof overrides[name] === "function") {
|
|
2519
|
-
bind(overrides, name, overrides);
|
|
2520
|
-
}
|
|
2521
|
-
}
|
|
2522
|
-
}
|
|
2523
|
-
bound.entity = Entity;
|
|
2524
|
-
bound.actions = repository.actions;
|
|
2525
|
-
return Object.freeze(bound);
|
|
2526
|
-
}
|
|
2527
|
-
function narrowedContext(Child, parent) {
|
|
2528
|
-
if (!Child.context || parent === undefined)
|
|
2529
|
-
return;
|
|
2530
|
-
const picked = {};
|
|
2531
|
-
for (const key of Object.keys(Child.context.properties)) {
|
|
2532
|
-
if (Object.hasOwn(parent, key)) {
|
|
2533
|
-
picked[key] = parent[key];
|
|
2534
|
-
}
|
|
2535
|
-
}
|
|
2536
|
-
return picked;
|
|
2537
|
-
}
|
|
2538
|
-
function isOverrideOf(dependency, value) {
|
|
2539
|
-
if (isLoggerDeclaration(dependency))
|
|
2540
|
-
return isLogger(value);
|
|
2541
|
-
if (isRepository(dependency) || isEnvironment(dependency)) {
|
|
2542
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2543
|
-
}
|
|
2544
|
-
return typeof value === "function";
|
|
2545
|
-
}
|
|
2546
|
-
|
|
2547
|
-
class AbstractUseCase {
|
|
2548
|
-
input;
|
|
2549
|
-
constructor(input, ...args) {
|
|
2550
|
-
const declaration = this.constructor;
|
|
2551
|
-
const source = declaration.useCaseName;
|
|
2552
|
-
const parsed = declaration.blueprint.safeParse(input);
|
|
2553
|
-
if (parsed.issues)
|
|
2554
|
-
invalidValue(parsed.issues, source, "input");
|
|
2555
|
-
this.input = parsed.value;
|
|
2556
|
-
const options = args[0];
|
|
2557
|
-
const scope = options?.[ScopeKey];
|
|
2558
|
-
let context;
|
|
2559
|
-
if (declaration.context) {
|
|
2560
|
-
const checked = declaration.context.safeParse(options?.context);
|
|
2561
|
-
if (checked.issues)
|
|
2562
|
-
invalidValue(checked.issues, source, "context");
|
|
2563
|
-
context = Object.freeze(checked.value);
|
|
2564
|
-
}
|
|
2565
|
-
Object.defineProperty(this, "context", {
|
|
2566
|
-
value: context,
|
|
2567
|
-
enumerable: true
|
|
2568
|
-
});
|
|
2569
|
-
Object.defineProperty(this, "input", {
|
|
2570
|
-
writable: false,
|
|
2571
|
-
configurable: false
|
|
2572
|
-
});
|
|
2573
|
-
const implementation = this.execute;
|
|
2574
|
-
if (typeof implementation !== "function") {
|
|
2575
|
-
throw new InvalidUseCaseDeclaration2({
|
|
2576
|
-
source,
|
|
2577
|
-
details: { reason: "Implement execute as a prototype method." }
|
|
2578
|
-
});
|
|
2579
|
-
}
|
|
2580
|
-
const state = {
|
|
2581
|
-
context,
|
|
2582
|
-
scope,
|
|
2583
|
-
started: false,
|
|
2584
|
-
active: false,
|
|
2585
|
-
events: [],
|
|
2586
|
-
dependencies: Object.create(null),
|
|
2587
|
-
owner: this.constructor
|
|
2588
|
-
};
|
|
2589
|
-
states2.set(this, state);
|
|
2590
|
-
for (const [key, dependency] of Object.entries(declaration.dependencies)) {
|
|
2591
|
-
if (isRepository(dependency)) {
|
|
2592
|
-
state.dependencies[key] = bindRepository(dependency, undefined, state, source);
|
|
2593
|
-
continue;
|
|
2594
|
-
}
|
|
2595
|
-
if (isLoggerDeclaration(dependency)) {
|
|
2596
|
-
Object.defineProperty(state.dependencies, key, {
|
|
2597
|
-
get: () => loggerOfCall(state) ?? silentLogger,
|
|
2598
|
-
enumerable: true
|
|
2599
|
-
});
|
|
2600
|
-
continue;
|
|
2601
|
-
}
|
|
2602
|
-
if (isEnvironment(dependency)) {
|
|
2603
|
-
Object.defineProperty(state.dependencies, key, {
|
|
2604
|
-
get: () => state.scope?.environments?.get(dependency) ?? dependency.load(),
|
|
2605
|
-
enumerable: true
|
|
2606
|
-
});
|
|
2607
|
-
continue;
|
|
2608
|
-
}
|
|
2609
|
-
const Child = dependency;
|
|
2610
|
-
state.dependencies[key] = async (value) => {
|
|
2611
|
-
if (!state.active)
|
|
2612
|
-
throw new InvalidUseCaseDependencies2({ source });
|
|
2613
|
-
const child = new Child(value, {
|
|
2614
|
-
context: narrowedContext(Child, state.context),
|
|
2615
|
-
[ScopeKey]: state.scope
|
|
2616
|
-
});
|
|
2617
|
-
try {
|
|
2618
|
-
return await child.execute();
|
|
2619
|
-
} finally {
|
|
2620
|
-
state.events.push(...child.pullEvents());
|
|
2621
|
-
}
|
|
2622
|
-
};
|
|
2623
|
-
}
|
|
2624
|
-
Object.freeze(state.dependencies);
|
|
2625
|
-
Object.defineProperty(this, "execute", {
|
|
2626
|
-
value: async (overrides = {}) => {
|
|
2627
|
-
if (state.started)
|
|
2628
|
-
throw new UseCaseAlreadyExecuted2({ source });
|
|
2629
|
-
state.started = true;
|
|
2630
|
-
if (typeof overrides !== "object" || overrides === null || Array.isArray(overrides) || Reflect.ownKeys(overrides).some((key) => typeof key !== "string" || !Object.hasOwn(declaration.dependencies, key) || !isOverrideOf(declaration.dependencies[key], overrides[key]))) {
|
|
2631
|
-
throw new InvalidUseCaseDependencies2({ source });
|
|
2632
|
-
}
|
|
2633
|
-
const replaced = overrides;
|
|
2634
|
-
const dependencies = {};
|
|
2635
|
-
for (const key of Object.keys(declaration.dependencies)) {
|
|
2636
|
-
if (!Object.hasOwn(replaced, key)) {
|
|
2637
|
-
dependencies[key] = state.dependencies[key];
|
|
2638
|
-
}
|
|
2639
|
-
}
|
|
2640
|
-
for (const [key, substitute] of Object.entries(replaced)) {
|
|
2641
|
-
const dependency = declaration.dependencies[key];
|
|
2642
|
-
if (isRepository(dependency)) {
|
|
2643
|
-
dependencies[key] = bindRepository(dependency, substitute, state, source);
|
|
2644
|
-
continue;
|
|
2645
|
-
}
|
|
2646
|
-
if (isLoggerDeclaration(dependency)) {
|
|
2647
|
-
dependencies[key] = substitute;
|
|
2648
|
-
continue;
|
|
2649
|
-
}
|
|
2650
|
-
if (isEnvironment(dependency)) {
|
|
2651
|
-
const parsed = dependency.blueprint.safeParse(substitute);
|
|
2652
|
-
if (parsed.issues)
|
|
2653
|
-
throw new InvalidUseCaseDependencies2({ source });
|
|
2654
|
-
dependencies[key] = Object.freeze(parsed.value);
|
|
2655
|
-
continue;
|
|
2656
|
-
}
|
|
2657
|
-
const Child = dependency;
|
|
2658
|
-
dependencies[key] = async (value) => {
|
|
2659
|
-
if (!state.active)
|
|
2660
|
-
throw new InvalidUseCaseDependencies2({ source });
|
|
2661
|
-
const childSource = Child.useCaseName;
|
|
2662
|
-
const input = Child.blueprint.safeParse(value);
|
|
2663
|
-
if (input.issues)
|
|
2664
|
-
invalidValue(input.issues, childSource, "input");
|
|
2665
|
-
const output = Child.response.safeParse(await guard2({ layer: "use-case" /* UseCase */, source: childSource }, () => substitute(input.value)));
|
|
2666
|
-
if (output.issues)
|
|
2667
|
-
invalidValue(output.issues, childSource, "response");
|
|
2668
|
-
collectEvents(Child.response, output.value, state.events);
|
|
2669
|
-
return output.value;
|
|
2670
|
-
};
|
|
2671
|
-
}
|
|
2672
|
-
state.resolved = Object.freeze(dependencies);
|
|
2673
|
-
state.active = true;
|
|
2674
|
-
const started = clock();
|
|
2675
|
-
try {
|
|
2676
|
-
const result = await guard2({ layer: "use-case" /* UseCase */, source }, () => implementation.call(this, state.resolved));
|
|
2677
|
-
const parsed = declaration.response.safeParse(result);
|
|
2678
|
-
if (parsed.issues)
|
|
2679
|
-
invalidValue(parsed.issues, source, "response");
|
|
2680
|
-
collectEvents(declaration.response, parsed.value, state.events);
|
|
2681
|
-
writeLog(loggerOfCall(state), "debug", "use-case", {
|
|
2682
|
-
useCase: source,
|
|
2683
|
-
outcome: "ok",
|
|
2684
|
-
ms: elapsed(started)
|
|
2685
|
-
});
|
|
2686
|
-
return parsed.value;
|
|
2687
|
-
} catch (error) {
|
|
2688
|
-
logFailure(loggerOfCall(state), "use-case-failed", { useCase: source, ms: elapsed(started) }, error);
|
|
2689
|
-
throw error;
|
|
2690
|
-
} finally {
|
|
2691
|
-
state.active = false;
|
|
2692
|
-
state.resolved = undefined;
|
|
2693
|
-
}
|
|
2694
|
-
}
|
|
2695
|
-
});
|
|
2696
|
-
}
|
|
2697
|
-
get dependencies() {
|
|
2698
|
-
const state = states2.get(this);
|
|
2699
|
-
return state?.resolved ?? state?.dependencies;
|
|
2700
|
-
}
|
|
2701
|
-
pullEvents() {
|
|
2702
|
-
return states2.get(this)?.events.splice(0) ?? [];
|
|
2703
|
-
}
|
|
2704
|
-
}
|
|
2705
|
-
function useCaseOf2(settings) {
|
|
2706
|
-
const { name, blueprint, response, context } = settings;
|
|
2707
|
-
const { description, deprecated = false } = settings;
|
|
2708
|
-
const tags = settings.tags === undefined ? [] : settings.tags;
|
|
2709
|
-
const errors = settings.errors === undefined ? [] : settings.errors;
|
|
2710
|
-
const dependencies = settings.dependencies === undefined ? {} : settings.dependencies;
|
|
2711
|
-
if (typeof name !== "string" || !name.trim() || !isBlueprint(blueprint) || blueprint.optional || !isBlueprint(response) || response.optional || typeof dependencies !== "object" || dependencies === null || Array.isArray(dependencies) || Reflect.ownKeys(dependencies).some((key) => typeof key !== "string" || !isDependency(dependencies[key]))) {
|
|
2712
|
-
throw new InvalidUseCaseDeclaration2({
|
|
2713
|
-
source: typeof name === "string" ? name : "useCaseOf",
|
|
2714
|
-
details: {
|
|
2715
|
-
reason: "Expected a name, input and response blueprints, and UseCase classes, repositories, environments or Logger as dependencies."
|
|
2716
|
-
}
|
|
2717
|
-
});
|
|
2718
|
-
}
|
|
2719
|
-
const invalidSetting = (reason) => {
|
|
2720
|
-
throw new InvalidUseCaseDeclaration2({ source: name, details: { reason } });
|
|
2721
|
-
};
|
|
2722
|
-
if (context !== undefined && (!isBlueprint(context) || context.optional)) {
|
|
2723
|
-
invalidSetting("Expected context to be a non-optional blueprint.");
|
|
2724
|
-
}
|
|
2725
|
-
if (description !== undefined && typeof description !== "string") {
|
|
2726
|
-
invalidSetting("Expected description to be a string.");
|
|
2727
|
-
}
|
|
2728
|
-
if (typeof deprecated !== "boolean") {
|
|
2729
|
-
invalidSetting("Expected deprecated to be a boolean.");
|
|
2730
|
-
}
|
|
2731
|
-
if (settings.strictSteps !== undefined && typeof settings.strictSteps !== "boolean") {
|
|
2732
|
-
invalidSetting("Expected strictSteps to be a boolean.");
|
|
2733
|
-
}
|
|
2734
|
-
if (!Array.isArray(tags) || tags.some((tag) => typeof tag !== "string" || tag === "") || new Set(tags).size !== tags.length) {
|
|
2735
|
-
invalidSetting("Expected tags to be distinct, non-empty strings.");
|
|
2736
|
-
}
|
|
2737
|
-
if (!Array.isArray(errors) || !errors.every(isDeclaredException) || new Set(errors).size !== errors.length) {
|
|
2738
|
-
invalidSetting("Expected errors to be distinct classes made by exceptionOf.");
|
|
2739
|
-
}
|
|
2740
|
-
let route;
|
|
2741
|
-
if (settings.route !== undefined) {
|
|
2742
|
-
const declared = routeDeclarationOf(settings.route, blueprint, response);
|
|
2743
|
-
route = typeof declared === "string" ? invalidSetting(declared) : declared;
|
|
2744
|
-
}
|
|
2745
|
-
let access;
|
|
2746
|
-
if (settings.access !== undefined) {
|
|
2747
|
-
const declared = accessDeclarationOf(settings.access, blueprint);
|
|
2748
|
-
access = typeof declared === "string" ? invalidSetting(declared) : declared;
|
|
2749
|
-
}
|
|
2750
|
-
if (settings.steps !== undefined) {
|
|
2751
|
-
validateSteps({ name, blueprint, context, dependencies, errors }, settings.steps);
|
|
2752
|
-
}
|
|
2753
|
-
|
|
2754
|
-
class DeclaredUseCase extends AbstractUseCase {
|
|
2755
|
-
static [Kind2] = "use-case";
|
|
2756
|
-
static useCaseName = name;
|
|
2757
|
-
static blueprint = blueprint;
|
|
2758
|
-
static response = response;
|
|
2759
|
-
static dependencies = Object.freeze({
|
|
2760
|
-
...dependencies
|
|
2761
|
-
});
|
|
2762
|
-
static context = context;
|
|
2763
|
-
static description = description;
|
|
2764
|
-
static tags = Object.freeze([...tags]);
|
|
2765
|
-
static deprecated = deprecated;
|
|
2766
|
-
static errors = Object.freeze([
|
|
2767
|
-
...errors
|
|
2768
|
-
]);
|
|
2769
|
-
static route = route;
|
|
2770
|
-
static access = access;
|
|
2771
|
-
}
|
|
2772
|
-
Object.defineProperty(DeclaredUseCase, "name", { value: name });
|
|
2773
|
-
if (settings.steps !== undefined) {
|
|
2774
|
-
Object.defineProperty(DeclaredUseCase, "steps", {
|
|
2775
|
-
value: freezeData(settings.steps)
|
|
2776
|
-
});
|
|
2777
|
-
Object.defineProperty(DeclaredUseCase.prototype, "execute", {
|
|
2778
|
-
value: executeSteps,
|
|
2779
|
-
writable: true,
|
|
2780
|
-
configurable: true
|
|
2781
|
-
});
|
|
2782
|
-
}
|
|
2783
|
-
return DeclaredUseCase;
|
|
2784
|
-
}
|
|
2785
|
-
function isDependency(value) {
|
|
2786
|
-
return isUseCaseClass2(value) || isRepository(value) || isEnvironment(value) || isLoggerDeclaration(value);
|
|
2787
|
-
}
|
|
2788
|
-
function isUseCaseClass2(value) {
|
|
2789
|
-
return typeof value === "function" && value.prototype instanceof AbstractUseCase && value[Kind2] === "use-case" && typeof value.prototype.execute === "function";
|
|
2790
|
-
}
|
|
2791
|
-
function isUseCase2(value) {
|
|
2792
|
-
return value instanceof AbstractUseCase;
|
|
2793
|
-
}
|
|
2794
|
-
|
|
2795
|
-
// src/core/use-case/plugin.ts
|
|
2796
|
-
var UseCaseOrigin = Symbol("useCaseOrigin");
|
|
2797
|
-
var NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
2798
|
-
var identities = new WeakMap;
|
|
2799
|
-
var nextIdentity = 0;
|
|
2800
|
-
function keyOf(value) {
|
|
2801
|
-
if (typeof value === "function" || typeof value === "object" && value) {
|
|
2802
|
-
if (Array.isArray(value))
|
|
2803
|
-
return `[${value.map(keyOf).join(",")}]`;
|
|
2804
|
-
if (isPlain(value)) {
|
|
2805
|
-
return `{${Object.keys(value).sort().filter((key) => value[key] !== undefined).map((key) => `${JSON.stringify(key)}:${keyOf(value[key])}`).join(",")}}`;
|
|
2806
|
-
}
|
|
2807
|
-
let identity = identities.get(value);
|
|
2808
|
-
if (identity === undefined) {
|
|
2809
|
-
identity = nextIdentity++;
|
|
2810
|
-
identities.set(value, identity);
|
|
2811
|
-
}
|
|
2812
|
-
return `#${identity}`;
|
|
2813
|
-
}
|
|
2814
|
-
return value === undefined ? "undefined" : JSON.stringify(value);
|
|
2815
|
-
}
|
|
2816
|
-
function isUseCasePlugin2(value) {
|
|
2817
|
-
return typeof value === "object" && value !== null && value[Kind2] === "use-case-plugin";
|
|
2818
|
-
}
|
|
2819
|
-
function useCaseOriginOf2(UseCase) {
|
|
2820
|
-
return typeof UseCase === "function" ? UseCase[UseCaseOrigin] : undefined;
|
|
2821
|
-
}
|
|
2822
|
-
function useCasePluginOf2(name, settings) {
|
|
2823
|
-
const source = typeof name === "string" ? name : "useCasePluginOf";
|
|
2824
|
-
const invalid = (reason) => {
|
|
2825
|
-
throw new InvalidUseCasePlugin2({ source, details: { reason } });
|
|
2826
|
-
};
|
|
2827
|
-
if (typeof name !== "string" || !NAME.test(name)) {
|
|
2828
|
-
invalid("its name must be an identifier");
|
|
2829
|
-
}
|
|
2830
|
-
if (!isPlain(settings) || typeof settings.compile !== "function") {
|
|
2831
|
-
invalid("it needs a compile function");
|
|
2832
|
-
}
|
|
2833
|
-
const { version = "1.0.0", description, compile } = settings;
|
|
2834
|
-
if (!isVersion(version)) {
|
|
2835
|
-
invalid(`"${String(version)}" is not a semantic version`);
|
|
2836
|
-
}
|
|
2837
|
-
if (description !== undefined && typeof description !== "string") {
|
|
2838
|
-
invalid("its description must be a string");
|
|
2839
|
-
}
|
|
2840
|
-
const names = (list, what) => {
|
|
2841
|
-
if (list === undefined)
|
|
2842
|
-
return Object.freeze([]);
|
|
2843
|
-
if (!Array.isArray(list) || list.some((item) => typeof item !== "string" || item === "")) {
|
|
2844
|
-
invalid(`requires.${what} must be a list of names`);
|
|
2845
|
-
}
|
|
2846
|
-
return Object.freeze([...new Set(list)]);
|
|
2847
|
-
};
|
|
2848
|
-
if (settings.requires !== undefined && !isPlain(settings.requires)) {
|
|
2849
|
-
invalid("requires must be an object of actions and plugins");
|
|
2850
|
-
}
|
|
2851
|
-
const requires = Object.freeze({
|
|
2852
|
-
actions: names(settings.requires?.actions, "actions"),
|
|
2853
|
-
plugins: names(settings.requires?.plugins, "plugins")
|
|
2854
|
-
});
|
|
2855
|
-
const made = new WeakMap;
|
|
2856
|
-
const make = (repository, given) => {
|
|
2857
|
-
if (!isRepository(repository)) {
|
|
2858
|
-
return invalid("it makes a use case for a repository of repositoryOf");
|
|
2859
|
-
}
|
|
2860
|
-
if (given !== undefined && !isPlain(given)) {
|
|
2861
|
-
return invalid("its options must be a plain object");
|
|
2862
|
-
}
|
|
2863
|
-
const options = Object.freeze({ ...given });
|
|
2864
|
-
const info = inspectRepository(repository);
|
|
2865
|
-
let classes = made.get(info.state);
|
|
2866
|
-
if (!classes) {
|
|
2867
|
-
classes = new Map;
|
|
2868
|
-
made.set(info.state, classes);
|
|
2869
|
-
}
|
|
2870
|
-
const key = keyOf(options);
|
|
2871
|
-
const known = classes.get(key);
|
|
2872
|
-
if (known)
|
|
2873
|
-
return known;
|
|
2874
|
-
const target = useCaseTargetOf2(repository, name);
|
|
2875
|
-
const missing = {
|
|
2876
|
-
actions: requires.actions.filter((action) => !info.actions.includes(action)),
|
|
2877
|
-
plugins: requires.plugins.filter((plugin) => !hasPlugin2(info.entity, plugin))
|
|
2878
|
-
};
|
|
2879
|
-
if (missing.actions.length > 0 || missing.plugins.length > 0) {
|
|
2880
|
-
const quoted = (list) => list.map((item) => `"${item}"`).join(", ");
|
|
2881
|
-
throw new UseCaseUnavailable2({
|
|
2882
|
-
source: name,
|
|
2883
|
-
details: {
|
|
2884
|
-
repository: target.repositoryName,
|
|
2885
|
-
reason: [
|
|
2886
|
-
missing.actions.length > 0 ? `the repository lacks the action ${quoted(missing.actions)}` : undefined,
|
|
2887
|
-
missing.plugins.length > 0 ? `the entity lacks the plugin ${quoted(missing.plugins)}` : undefined
|
|
2888
|
-
].filter(Boolean).join(" and "),
|
|
2889
|
-
missing
|
|
2890
|
-
}
|
|
2891
|
-
});
|
|
2892
|
-
}
|
|
2893
|
-
const UseCase = useCaseOf2(compile(target, options));
|
|
2894
|
-
Object.defineProperty(UseCase, UseCaseOrigin, {
|
|
2895
|
-
value: Object.freeze({
|
|
2896
|
-
plugin,
|
|
2897
|
-
repository,
|
|
2898
|
-
options
|
|
2899
|
-
})
|
|
2900
|
-
});
|
|
2901
|
-
classes.set(key, UseCase);
|
|
2902
|
-
return UseCase;
|
|
2903
|
-
};
|
|
2904
|
-
const plugin = Object.freeze({
|
|
2905
|
-
[Kind2]: "use-case-plugin",
|
|
2906
|
-
name,
|
|
2907
|
-
version,
|
|
2908
|
-
...description === undefined ? {} : { description },
|
|
2909
|
-
requires,
|
|
2910
|
-
make,
|
|
2911
|
-
toJSON: () => ({
|
|
2912
|
-
kind: "use-case-plugin",
|
|
2913
|
-
name,
|
|
2914
|
-
version,
|
|
2915
|
-
...description === undefined ? {} : { description },
|
|
2916
|
-
requires
|
|
2917
|
-
})
|
|
2918
|
-
});
|
|
2919
|
-
return plugin;
|
|
2920
|
-
}
|
|
2921
|
-
|
|
2922
|
-
// src/core/event-policy/event-policy.ts
|
|
2923
|
-
var described = new WeakMap;
|
|
2924
|
-
var factories = new WeakMap;
|
|
2925
|
-
function invalid3(source, reason) {
|
|
2926
|
-
throw new InvalidEventPolicy2({ source, details: { reason } });
|
|
2927
|
-
}
|
|
2928
|
-
function optionsOf(name, given, validate) {
|
|
2929
|
-
const options = frozenJson(given ?? {}, "Event policy options", (reason) => invalid3(name, reason));
|
|
2930
|
-
if (options === null || typeof options !== "object" || Array.isArray(options)) {
|
|
2931
|
-
invalid3(name, "Event policy options must be an object.");
|
|
2932
|
-
}
|
|
2933
|
-
const reason = validate?.(options);
|
|
2934
|
-
if (reason !== undefined)
|
|
2935
|
-
invalid3(name, reason);
|
|
2936
|
-
return options;
|
|
2937
|
-
}
|
|
2938
|
-
function eventPolicyOf2(settings) {
|
|
2939
|
-
const name = settings?.name;
|
|
2940
|
-
if (typeof name !== "string" || !/^[A-Za-z_$][\w$]*$/.test(name)) {
|
|
2941
|
-
invalid3("eventPolicyOf", "Expected the policy name to be an identifier.");
|
|
2942
|
-
}
|
|
2943
|
-
if (typeof settings.handle !== "function") {
|
|
2944
|
-
invalid3(name, "Expected handle to be a function.");
|
|
2945
|
-
}
|
|
2946
|
-
if (settings.publishers !== undefined && typeof settings.publishers !== "function") {
|
|
2947
|
-
invalid3(name, "Expected publishers to be a function.");
|
|
2948
|
-
}
|
|
2949
|
-
const { handle, validate, publishers } = settings;
|
|
2950
|
-
const factory = (given) => {
|
|
2951
|
-
const options = optionsOf(name, given, validate);
|
|
2952
|
-
const handler = (events, info) => handle(events, info, options);
|
|
2953
|
-
Object.defineProperty(handler, "name", { value: name });
|
|
2954
|
-
described.set(handler, {
|
|
2955
|
-
description: Object.freeze({
|
|
2956
|
-
kind: "policy",
|
|
2957
|
-
name,
|
|
2958
|
-
options
|
|
2959
|
-
}),
|
|
2960
|
-
publishers: Object.freeze([...publishers?.(options) ?? []])
|
|
2961
|
-
});
|
|
2962
|
-
return handler;
|
|
2963
|
-
};
|
|
2964
|
-
factories.set(factory, name);
|
|
2965
|
-
return factory;
|
|
2966
|
-
}
|
|
2967
|
-
function eventPolicyDescriptionOf2(handler) {
|
|
2968
|
-
return typeof handler === "function" ? described.get(handler)?.description : undefined;
|
|
2969
|
-
}
|
|
2970
|
-
function eventPolicyPublishersOf(handler) {
|
|
2971
|
-
return typeof handler === "function" ? described.get(handler)?.publishers ?? [] : [];
|
|
2972
|
-
}
|
|
2973
|
-
function eventPolicyCatalogOf2(...policies) {
|
|
2974
|
-
const byName = {};
|
|
2975
|
-
for (const factory of policies) {
|
|
2976
|
-
const name = factories.get(factory);
|
|
2977
|
-
if (name === undefined) {
|
|
2978
|
-
invalid3("catalog", "A catalog takes factories made by `eventPolicyOf`.");
|
|
2979
|
-
}
|
|
2980
|
-
if (Object.hasOwn(byName, name)) {
|
|
2981
|
-
invalid3(name, "The catalog has another event policy with its name.");
|
|
2982
|
-
}
|
|
2983
|
-
byName[name] = factory;
|
|
2984
|
-
}
|
|
2985
|
-
return Object.freeze({
|
|
2986
|
-
[Kind2]: "event-policy-catalog",
|
|
2987
|
-
policies: Object.freeze(byName),
|
|
2988
|
-
resolve(name, options) {
|
|
2989
|
-
const factory = Object.hasOwn(byName, name) ? byName[name] : undefined;
|
|
2990
|
-
if (!factory)
|
|
2991
|
-
invalid3("catalog", `Unknown event policy "${name}".`);
|
|
2992
|
-
return factory(options);
|
|
2993
|
-
}
|
|
2994
|
-
});
|
|
2995
|
-
}
|
|
2996
|
-
|
|
2997
|
-
export { InvalidEventPolicy2, PublisherNotBound2, exports_exceptions, guard2, ScopeKey, providerOf2, providerDescriptionOf2, prepareQuery2, sameValue2, matchesQuery2, selectRows2, inMemoryProvider2, fullUpcastPlanOf, upcast, resolveReferences, repositoryOf2, RepositoryOf2, isRepository, InvalidUseCaseStep2, InvalidUseCasePlugin2, UnknownUseCasePlugin2, IncompatibleUseCasePlugin2, UseCaseUnavailable2, UseCaseEntityNotFound2, UseCaseEntityConflict2, UseCaseInvalidCredentials2, exports_exceptions2, isPlain, PropertyInput2, propertyInputOf2, useCaseTargetOf2, isPublicAccess, accessDeclarationOf, interpretedStepsOf, InvalidUseCaseDeclaration2, UseCaseAlreadyExecuted2, InvalidUseCaseDependencies2, useCaseOf2, isUseCaseClass2, isUseCase2, exports_use_case, isUseCasePlugin2, useCaseOriginOf2, useCasePluginOf2, eventPolicyOf2, eventPolicyDescriptionOf2, eventPolicyPublishersOf, eventPolicyCatalogOf2 };
|