@afora/core 0.0.6 → 0.0.8
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 +36 -7
- package/dist/core/application/start.d.ts +7 -0
- package/dist/core/application/types/application.interface.d.ts +47 -2
- package/dist/core/command/command.d.ts +11 -4
- package/dist/core/command/types/command.interface.d.ts +26 -0
- package/dist/core/entity/abstract-entity.d.ts +28 -1
- package/dist/core/entity/entity.d.ts +1 -1
- package/dist/core/entity/event.d.ts +13 -2
- package/dist/core/entity/exceptions.d.ts +8 -0
- package/dist/core/entity/types/entity.interface.d.ts +66 -2
- package/dist/core/entity/version.d.ts +13 -0
- package/dist/core/event-policy/event-policy.d.ts +25 -0
- package/dist/core/event-policy/exceptions.d.ts +11 -0
- package/dist/core/event-policy/publish.d.ts +17 -0
- package/dist/core/event-policy/publishers.d.ts +18 -0
- package/dist/core/event-policy/types/event-policy.interface.d.ts +72 -0
- package/dist/core/output/coerce.d.ts +9 -0
- package/dist/core/output/openapi-sink.d.ts +138 -38
- package/dist/core/output/status.d.ts +2 -1
- package/dist/core/parse/parse.d.ts +10 -0
- package/dist/core/parse/strip.d.ts +13 -0
- package/dist/core/property/input.d.ts +20 -0
- package/dist/core/record/plugin.d.ts +6 -2
- package/dist/core/repository/exceptions.d.ts +30 -0
- package/dist/core/repository/in-memory.d.ts +14 -1
- package/dist/core/repository/plan.d.ts +20 -0
- package/dist/core/repository/provider.d.ts +1 -1
- package/dist/core/repository/query.d.ts +9 -2
- package/dist/core/repository/repository.d.ts +40 -8
- package/dist/core/repository/state.d.ts +39 -2
- package/dist/core/repository/types/query.interface.d.ts +106 -5
- package/dist/core/repository/types/repository.interface.d.ts +113 -7
- package/dist/core/use-case/exceptions.d.ts +36 -0
- package/dist/core/use-case/plugin-catalog.d.ts +11 -0
- package/dist/core/use-case/plugin.d.ts +25 -0
- package/dist/core/use-case/steps/types/steps.interface.d.ts +23 -6
- package/dist/core/use-case/target.d.ts +9 -0
- package/dist/core/use-case/types/plugin.interface.d.ts +185 -0
- package/dist/experimental.js +3 -3
- package/dist/{index-3nd9bjnc.js → index-qxfkyd6k.js} +646 -635
- package/dist/{index-gbgc40xe.js → index-t5qwtyj5.js} +2798 -295
- package/dist/index.d.ts +35 -15
- package/dist/index.js +99 -15
- package/dist/std/blueprint-plugin.d.ts +6 -2
- package/dist/std/blueprint.d.ts +3 -0
- package/dist/std/command.d.ts +11 -0
- package/dist/std/entity.d.ts +2 -1
- package/dist/std/event-policies.d.ts +2 -0
- package/dist/std/properties/exceptions.d.ts +11 -0
- package/dist/std/properties/json.d.ts +54 -0
- package/dist/std/properties/password.d.ts +16 -1
- package/dist/std/properties/time.d.ts +4 -0
- package/dist/std/use-cases/auth.d.ts +5 -0
- package/dist/std/use-cases/crud.d.ts +25 -0
- package/dist/std/use-cases/index.d.ts +11 -0
- package/dist/std/use-cases/lifecycle.d.ts +13 -0
- package/dist/std/use-cases/shared.d.ts +88 -0
- package/dist/std/use-cases/types.d.ts +291 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +355 -2
- package/dist/testing/memory-publisher.d.ts +16 -0
- package/dist/testing/provider-contract.d.ts +14 -0
- package/package.json +3 -2
|
@@ -11,6 +11,9 @@ import {
|
|
|
11
11
|
recordFieldOf2,
|
|
12
12
|
recordNameOf2,
|
|
13
13
|
declaredRecordNameOf,
|
|
14
|
+
fieldsOf,
|
|
15
|
+
isPlainObject,
|
|
16
|
+
listOf2,
|
|
14
17
|
isBlueprintList2,
|
|
15
18
|
UNEXPECTED_ERROR2,
|
|
16
19
|
Exception2,
|
|
@@ -27,22 +30,25 @@ import {
|
|
|
27
30
|
storeRecordValue,
|
|
28
31
|
runRecordEffects,
|
|
29
32
|
AbstractRecord2,
|
|
33
|
+
InvalidPropertyValue2,
|
|
34
|
+
exports_exceptions2,
|
|
35
|
+
propertyOf2,
|
|
36
|
+
exports_exceptions3,
|
|
37
|
+
optionalOf2,
|
|
38
|
+
blueprintBuilderOf2,
|
|
39
|
+
blueprintFromProperties,
|
|
30
40
|
IdentifiedRecord,
|
|
31
41
|
isVersion,
|
|
32
|
-
|
|
42
|
+
satisfies,
|
|
33
43
|
pluginsOf2,
|
|
34
44
|
isRecordPlugin2,
|
|
45
|
+
hasPlugin2,
|
|
35
46
|
recordPluginOf2,
|
|
36
47
|
recordBuilderOf2,
|
|
37
48
|
pluginContextOf,
|
|
38
49
|
recordOf3,
|
|
39
|
-
declareRecord
|
|
40
|
-
|
|
41
|
-
InvalidPropertyValue2,
|
|
42
|
-
exports_exceptions3,
|
|
43
|
-
propertyOf2,
|
|
44
|
-
blueprintBuilderOf2
|
|
45
|
-
} from "./index-3nd9bjnc.js";
|
|
50
|
+
declareRecord
|
|
51
|
+
} from "./index-qxfkyd6k.js";
|
|
46
52
|
|
|
47
53
|
// src/core/environment/environment.ts
|
|
48
54
|
var exports_environment = {};
|
|
@@ -68,30 +74,54 @@ __export(exports_exceptions4, {
|
|
|
68
74
|
UnregisteredUseCaseDependency: () => UnregisteredUseCaseDependency2
|
|
69
75
|
});
|
|
70
76
|
|
|
71
|
-
// src/core/
|
|
77
|
+
// src/core/event-policy/exceptions.ts
|
|
72
78
|
var exports_exceptions5 = {};
|
|
73
79
|
__export(exports_exceptions5, {
|
|
74
|
-
|
|
80
|
+
InvalidEventPolicy: () => InvalidEventPolicy2,
|
|
81
|
+
PublisherNotBound: () => PublisherNotBound2
|
|
75
82
|
});
|
|
76
83
|
|
|
77
|
-
// src/core/
|
|
84
|
+
// src/core/logger/exceptions.ts
|
|
78
85
|
var exports_exceptions6 = {};
|
|
79
86
|
__export(exports_exceptions6, {
|
|
87
|
+
InvalidLogDeclaration: () => InvalidLogDeclaration2
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// src/core/entity/exceptions.ts
|
|
91
|
+
var exports_exceptions7 = {};
|
|
92
|
+
__export(exports_exceptions7, {
|
|
80
93
|
DeletedEntity: () => DeletedEntity2,
|
|
81
94
|
ImmutableEntityKey: () => ImmutableEntityKey2,
|
|
95
|
+
InvalidEntityExpiry: () => InvalidEntityExpiry2,
|
|
82
96
|
ReservedEntityKey: () => ReservedEntityKey2
|
|
83
97
|
});
|
|
84
98
|
|
|
85
99
|
// src/core/repository/exceptions.ts
|
|
86
|
-
var
|
|
87
|
-
__export(
|
|
100
|
+
var exports_exceptions8 = {};
|
|
101
|
+
__export(exports_exceptions8, {
|
|
88
102
|
InvalidRepositoryCursor: () => InvalidRepositoryCursor2,
|
|
89
103
|
InvalidRepositoryData: () => InvalidRepositoryData2,
|
|
90
104
|
InvalidRepositoryDeclaration: () => InvalidRepositoryDeclaration2,
|
|
91
105
|
InvalidRepositoryEntity: () => InvalidRepositoryEntity2,
|
|
106
|
+
InvalidRepositoryIncrement: () => InvalidRepositoryIncrement2,
|
|
92
107
|
InvalidRepositoryQuery: () => InvalidRepositoryQuery2,
|
|
108
|
+
RepositoryConcurrencyConflict: () => RepositoryConcurrencyConflict2,
|
|
93
109
|
RepositoryEntityNotFound: () => RepositoryEntityNotFound2,
|
|
94
|
-
RepositoryUniqueViolation: () => RepositoryUniqueViolation2
|
|
110
|
+
RepositoryUniqueViolation: () => RepositoryUniqueViolation2,
|
|
111
|
+
RepositoryUsedOutsideApplication: () => RepositoryUsedOutsideApplication2
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// src/core/use-case/exceptions.ts
|
|
115
|
+
var exports_exceptions9 = {};
|
|
116
|
+
__export(exports_exceptions9, {
|
|
117
|
+
IncompatibleUseCasePlugin: () => IncompatibleUseCasePlugin2,
|
|
118
|
+
InvalidUseCasePlugin: () => InvalidUseCasePlugin2,
|
|
119
|
+
InvalidUseCaseStep: () => InvalidUseCaseStep2,
|
|
120
|
+
UnknownUseCasePlugin: () => UnknownUseCasePlugin2,
|
|
121
|
+
UseCaseEntityConflict: () => UseCaseEntityConflict2,
|
|
122
|
+
UseCaseEntityNotFound: () => UseCaseEntityNotFound2,
|
|
123
|
+
UseCaseInvalidCredentials: () => UseCaseInvalidCredentials2,
|
|
124
|
+
UseCaseUnavailable: () => UseCaseUnavailable2
|
|
95
125
|
});
|
|
96
126
|
|
|
97
127
|
// src/core/use-case/use-case.ts
|
|
@@ -106,15 +136,9 @@ __export(exports_use_case, {
|
|
|
106
136
|
useCaseOf: () => useCaseOf2
|
|
107
137
|
});
|
|
108
138
|
|
|
109
|
-
// src/core/use-case/exceptions.ts
|
|
110
|
-
var exports_exceptions8 = {};
|
|
111
|
-
__export(exports_exceptions8, {
|
|
112
|
-
InvalidUseCaseStep: () => InvalidUseCaseStep2
|
|
113
|
-
});
|
|
114
|
-
|
|
115
139
|
// src/core/command/exceptions.ts
|
|
116
|
-
var
|
|
117
|
-
__export(
|
|
140
|
+
var exports_exceptions10 = {};
|
|
141
|
+
__export(exports_exceptions10, {
|
|
118
142
|
CommandEventFailed: () => CommandEventFailed2,
|
|
119
143
|
InvalidCommandContext: () => InvalidCommandContext2,
|
|
120
144
|
InvalidCommandDeclaration: () => InvalidCommandDeclaration2,
|
|
@@ -123,14 +147,14 @@ __export(exports_exceptions9, {
|
|
|
123
147
|
});
|
|
124
148
|
|
|
125
149
|
// src/core/openapi/exceptions.ts
|
|
126
|
-
var
|
|
127
|
-
__export(
|
|
150
|
+
var exports_exceptions11 = {};
|
|
151
|
+
__export(exports_exceptions11, {
|
|
128
152
|
InvalidOpenAPIModel: () => InvalidOpenAPIModel2
|
|
129
153
|
});
|
|
130
154
|
|
|
131
155
|
// src/core/output/exceptions.ts
|
|
132
|
-
var
|
|
133
|
-
__export(
|
|
156
|
+
var exports_exceptions12 = {};
|
|
157
|
+
__export(exports_exceptions12, {
|
|
134
158
|
DuplicateOperation: () => DuplicateOperation2,
|
|
135
159
|
InvalidOutputStage: () => InvalidOutputStage2
|
|
136
160
|
});
|
|
@@ -203,23 +227,6 @@ function isEnvironment2(value) {
|
|
|
203
227
|
return typeof value === "object" && value !== null && value[Kind2] === "environment";
|
|
204
228
|
}
|
|
205
229
|
|
|
206
|
-
// src/core/exception/guard.ts
|
|
207
|
-
function guard2(scope, run, resolve) {
|
|
208
|
-
const handle = (error) => {
|
|
209
|
-
const exception = Exception2.from(error, scope);
|
|
210
|
-
const pass = () => {
|
|
211
|
-
throw exception === error ? exception.propagate(scope) : exception;
|
|
212
|
-
};
|
|
213
|
-
return resolve ? resolve(exception, pass) : pass();
|
|
214
|
-
};
|
|
215
|
-
try {
|
|
216
|
-
const result = run();
|
|
217
|
-
return result instanceof Promise ? result.catch(handle) : result;
|
|
218
|
-
} catch (error) {
|
|
219
|
-
return handle(error);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
230
|
// src/core/application/exceptions.ts
|
|
224
231
|
var InvalidApplicationDeclaration2 = exceptionOf2({
|
|
225
232
|
layer: "application" /* Application */,
|
|
@@ -319,6 +326,53 @@ function setStartedApplication(application) {
|
|
|
319
326
|
slot().current = application;
|
|
320
327
|
}
|
|
321
328
|
|
|
329
|
+
// src/core/event-policy/exceptions.ts
|
|
330
|
+
var InvalidEventPolicy2 = exceptionOf2({
|
|
331
|
+
layer: "event" /* Event */,
|
|
332
|
+
name: "InvalidEventPolicy",
|
|
333
|
+
message: ({ reason }) => reason
|
|
334
|
+
});
|
|
335
|
+
var PublisherNotBound2 = exceptionOf2({
|
|
336
|
+
layer: "application" /* Application */,
|
|
337
|
+
name: "PublisherNotBound",
|
|
338
|
+
message: ({ publisher }) => `No publisher "${publisher}" is declared: add it to applicationOf({ publishers }).`
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// src/core/event-policy/publishers.ts
|
|
342
|
+
var ApplicationPublishersKey = Symbol("afora.application.publishers");
|
|
343
|
+
var boundPublishers = new WeakMap;
|
|
344
|
+
function bindPublishers(command, publishers) {
|
|
345
|
+
boundPublishers.set(command, publishers);
|
|
346
|
+
}
|
|
347
|
+
function isEventPublisher(value) {
|
|
348
|
+
return typeof value === "object" && value !== null && typeof value.publish === "function";
|
|
349
|
+
}
|
|
350
|
+
function resolvePublisher(command, name, source) {
|
|
351
|
+
const publishers = boundPublishers.get(command) ?? startedApplication()?.[ApplicationPublishersKey];
|
|
352
|
+
const publisher = publishers && Object.hasOwn(publishers, name) ? publishers[name] : undefined;
|
|
353
|
+
if (!publisher) {
|
|
354
|
+
throw new PublisherNotBound2({ source, details: { publisher: name } });
|
|
355
|
+
}
|
|
356
|
+
return publisher;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// src/core/exception/guard.ts
|
|
360
|
+
function guard2(scope, run, resolve) {
|
|
361
|
+
const handle = (error) => {
|
|
362
|
+
const exception = Exception2.from(error, scope);
|
|
363
|
+
const pass = () => {
|
|
364
|
+
throw exception === error ? exception.propagate(scope) : exception;
|
|
365
|
+
};
|
|
366
|
+
return resolve ? resolve(exception, pass) : pass();
|
|
367
|
+
};
|
|
368
|
+
try {
|
|
369
|
+
const result = run();
|
|
370
|
+
return result instanceof Promise ? result.catch(handle) : result;
|
|
371
|
+
} catch (error) {
|
|
372
|
+
return handle(error);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
322
376
|
// src/core/logger/exceptions.ts
|
|
323
377
|
var InvalidLogDeclaration2 = exceptionOf2({
|
|
324
378
|
layer: "application" /* Application */,
|
|
@@ -518,34 +572,81 @@ var DeletedEntity2 = exceptionOf2({
|
|
|
518
572
|
name: "DeletedEntity",
|
|
519
573
|
message: ({ id }, source) => `Entity "${source}" of id "${id}" was destroyed and cannot change.`
|
|
520
574
|
});
|
|
575
|
+
var InvalidEntityExpiry2 = exceptionOf2({
|
|
576
|
+
layer: "entity" /* Entity */,
|
|
577
|
+
name: "InvalidEntityExpiry",
|
|
578
|
+
message: ({ reason }, source) => `Invalid expires of entity "${source}": ${reason}`
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
// src/core/entity/version.ts
|
|
582
|
+
import { Type } from "typebox";
|
|
583
|
+
class EntityVersion2 extends propertyOf2({
|
|
584
|
+
name: "version",
|
|
585
|
+
schema: Type.Integer({ minimum: 1 }),
|
|
586
|
+
default: 1
|
|
587
|
+
}) {
|
|
588
|
+
}
|
|
589
|
+
var VersionIdentity = blueprintBuilderOf2()(() => ({
|
|
590
|
+
version: EntityVersion2
|
|
591
|
+
}));
|
|
521
592
|
|
|
522
593
|
// src/core/entity/entity.ts
|
|
523
594
|
function entityBuilderOf2({
|
|
524
595
|
identity,
|
|
525
596
|
generateId,
|
|
526
597
|
now,
|
|
598
|
+
expiresAt,
|
|
527
599
|
plugins: catalogs
|
|
528
600
|
}) {
|
|
529
601
|
const clock = Object.freeze({ generateId, now });
|
|
530
602
|
const context = pluginContextOf(catalogs ?? []);
|
|
531
603
|
return (blueprint, options = {}) => {
|
|
604
|
+
const lifetime = lifetimeOf(blueprint, options);
|
|
605
|
+
const expiring = lifetime !== undefined;
|
|
606
|
+
const versioned = options.versioned === true;
|
|
607
|
+
const added = [
|
|
608
|
+
...versioned ? [ENTITY_VERSION_KEY] : [],
|
|
609
|
+
...expiring ? [EXPIRES_AT] : []
|
|
610
|
+
];
|
|
611
|
+
if (expiring && expiresAt === undefined) {
|
|
612
|
+
throw new InvalidEntityExpiry2({
|
|
613
|
+
source: options.name ?? "entity",
|
|
614
|
+
details: {
|
|
615
|
+
reason: "the entity builder has no expiresAt field, which expires: { after } needs."
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
}
|
|
532
619
|
for (const key of Object.keys(blueprint.properties)) {
|
|
533
|
-
if (RESERVED_ENTITY_KEYS2.includes(key)) {
|
|
620
|
+
if (RESERVED_ENTITY_KEYS2.includes(key) || added.includes(key)) {
|
|
534
621
|
throw new ReservedEntityKey2({ source: "entity", details: { key } });
|
|
535
622
|
}
|
|
536
623
|
}
|
|
537
624
|
for (const key of options.change ?? []) {
|
|
538
|
-
if (ENTITY_IDENTITY_KEYS2.includes(key)) {
|
|
625
|
+
if (ENTITY_IDENTITY_KEYS2.includes(key) || added.includes(key)) {
|
|
539
626
|
throw new ImmutableEntityKey2({ source: "entity", details: { key } });
|
|
540
627
|
}
|
|
541
628
|
}
|
|
542
|
-
const DeclaredEntity = declareRecord(AbstractEntity2, identity.join(
|
|
629
|
+
const DeclaredEntity = declareRecord(AbstractEntity2, identity.join(...versioned ? [VersionIdentity] : [], ...expiring ? [
|
|
630
|
+
blueprintFromProperties([], {
|
|
631
|
+
[EXPIRES_AT]: expiresAt
|
|
632
|
+
})
|
|
633
|
+
] : [], blueprint), {
|
|
634
|
+
...withoutStorage(options),
|
|
635
|
+
unique: ["id", ...options.unique ?? []]
|
|
636
|
+
}, "entity", ENTITY_MEMBERS, context);
|
|
543
637
|
Object.defineProperty(DeclaredEntity, IdentifiedRecord, { value: true });
|
|
544
638
|
Object.defineProperty(DeclaredEntity, EntityClock, { value: clock });
|
|
545
639
|
Object.defineProperty(DeclaredEntity, EntityClassBrand, { value: true });
|
|
546
640
|
Object.defineProperty(DeclaredEntity, EntityUniqueKeys2, {
|
|
547
641
|
value: DeclaredEntity.options.unique
|
|
548
642
|
});
|
|
643
|
+
Object.defineProperty(DeclaredEntity, EntityLifetime, { value: lifetime });
|
|
644
|
+
Object.defineProperty(DeclaredEntity, EntitySettings2, {
|
|
645
|
+
value: Object.freeze({
|
|
646
|
+
versioned,
|
|
647
|
+
expires: options.expires
|
|
648
|
+
})
|
|
649
|
+
});
|
|
549
650
|
Object.defineProperty(DeclaredEntity, EntityEventSettings, {
|
|
550
651
|
value: Object.freeze({
|
|
551
652
|
onCreate: options.onCreate,
|
|
@@ -562,6 +663,59 @@ function isEntityClass2(value) {
|
|
|
562
663
|
function isEntity2(value) {
|
|
563
664
|
return value instanceof AbstractEntity2;
|
|
564
665
|
}
|
|
666
|
+
function withoutStorage(options) {
|
|
667
|
+
const { versioned: _versioned, expires: _expires, ...rest } = options;
|
|
668
|
+
return rest;
|
|
669
|
+
}
|
|
670
|
+
var DURATION_UNITS = {
|
|
671
|
+
days: 86400000,
|
|
672
|
+
hours: 3600000,
|
|
673
|
+
minutes: 60000,
|
|
674
|
+
seconds: 1000
|
|
675
|
+
};
|
|
676
|
+
function isDateField(field) {
|
|
677
|
+
if (!isProperty2(field))
|
|
678
|
+
return false;
|
|
679
|
+
const schema = field.type.schema.type.Type();
|
|
680
|
+
return schema.type === "string" && (schema.format === "date-time" || schema.format === "date");
|
|
681
|
+
}
|
|
682
|
+
function lifetimeOf(blueprint, options) {
|
|
683
|
+
const { expires } = options;
|
|
684
|
+
if (expires === undefined)
|
|
685
|
+
return;
|
|
686
|
+
const fail = (reason) => {
|
|
687
|
+
throw new InvalidEntityExpiry2({
|
|
688
|
+
source: options.name ?? "entity",
|
|
689
|
+
details: { reason }
|
|
690
|
+
});
|
|
691
|
+
};
|
|
692
|
+
const plain = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
693
|
+
if (!plain(expires) || Object.keys(expires).length !== 1)
|
|
694
|
+
return fail("expected { after: duration } or { at: key }.");
|
|
695
|
+
if ("at" in expires) {
|
|
696
|
+
const { at } = expires;
|
|
697
|
+
if (typeof at !== "string" || !Object.hasOwn(blueprint.properties, at))
|
|
698
|
+
return fail(`"${String(at)}" is not a key of the blueprint.`);
|
|
699
|
+
if (!isDateField(blueprint.properties[at]))
|
|
700
|
+
return fail(`"${at}" does not hold a date.`);
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
if (!("after" in expires)) {
|
|
704
|
+
return fail("expected { after: duration } or { at: key }.");
|
|
705
|
+
}
|
|
706
|
+
const { after } = expires;
|
|
707
|
+
if (!plain(after) || Object.keys(after).length === 0)
|
|
708
|
+
return fail("after must be a non-empty duration, e.g. { days: 400 }.");
|
|
709
|
+
let total = 0;
|
|
710
|
+
for (const [unit, amount] of Object.entries(after)) {
|
|
711
|
+
if (!Object.hasOwn(DURATION_UNITS, unit))
|
|
712
|
+
return fail(`"${unit}" is not a unit of a duration (days, hours, minutes, seconds).`);
|
|
713
|
+
if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0)
|
|
714
|
+
return fail(`${unit} must be a positive finite number.`);
|
|
715
|
+
total += amount * DURATION_UNITS[unit];
|
|
716
|
+
}
|
|
717
|
+
return total;
|
|
718
|
+
}
|
|
565
719
|
|
|
566
720
|
// src/core/entity/event.ts
|
|
567
721
|
var EntityClassBrand = Symbol("entityClass");
|
|
@@ -569,14 +723,20 @@ var EventBrand = Symbol("event");
|
|
|
569
723
|
var EntityEventSettings = Symbol("entityEvents");
|
|
570
724
|
var definitions = new WeakMap;
|
|
571
725
|
var queues = new WeakMap;
|
|
726
|
+
var withHandler = new WeakSet;
|
|
572
727
|
function eventOf2(name, entityClass, handler) {
|
|
573
728
|
if (!isEntityClass2(entityClass)) {
|
|
574
729
|
throw new TypeError("An event requires an entity class");
|
|
575
730
|
}
|
|
731
|
+
if (handler !== undefined && typeof handler !== "function") {
|
|
732
|
+
throw new TypeError(`The handler of event ${name} must be a function`);
|
|
733
|
+
}
|
|
576
734
|
const event = Object.freeze({
|
|
577
735
|
name,
|
|
578
736
|
[EventBrand]: true
|
|
579
737
|
});
|
|
738
|
+
if (handler)
|
|
739
|
+
withHandler.add(event);
|
|
580
740
|
definitions.set(event, (entity) => {
|
|
581
741
|
if (!(entity instanceof entityClass)) {
|
|
582
742
|
throw new TypeError(`Incompatible entity for event ${name}`);
|
|
@@ -585,6 +745,8 @@ function eventOf2(name, entityClass, handler) {
|
|
|
585
745
|
name,
|
|
586
746
|
entity,
|
|
587
747
|
async handle(context) {
|
|
748
|
+
if (!handler)
|
|
749
|
+
return;
|
|
588
750
|
await handler(entity, {
|
|
589
751
|
logger: context?.logger ?? resolveLogger(undefined, undefined) ?? silentLogger
|
|
590
752
|
});
|
|
@@ -593,6 +755,23 @@ function eventOf2(name, entityClass, handler) {
|
|
|
593
755
|
});
|
|
594
756
|
return event;
|
|
595
757
|
}
|
|
758
|
+
function eventHasHandler(event) {
|
|
759
|
+
return withHandler.has(event);
|
|
760
|
+
}
|
|
761
|
+
function lifecycleEventsOf(entityClass) {
|
|
762
|
+
const settings = entityClass[EntityEventSettings];
|
|
763
|
+
const found = {};
|
|
764
|
+
for (const cycle of ["onCreate", "onUpdate", "onDelete"]) {
|
|
765
|
+
const reference = settings?.[cycle];
|
|
766
|
+
if (reference === undefined)
|
|
767
|
+
continue;
|
|
768
|
+
const events = typeof reference === "function" ? reference() : reference;
|
|
769
|
+
const list = Array.isArray(events) ? events : [events];
|
|
770
|
+
if (list.length > 0)
|
|
771
|
+
found[cycle] = list;
|
|
772
|
+
}
|
|
773
|
+
return found;
|
|
774
|
+
}
|
|
596
775
|
function enqueueEvent(entity, event) {
|
|
597
776
|
const bind = definitions.get(event);
|
|
598
777
|
if (!bind)
|
|
@@ -634,6 +813,30 @@ var RESERVED_ENTITY_KEYS2 = Object.freeze([
|
|
|
634
813
|
]);
|
|
635
814
|
var EntityClock = Symbol("entityClock");
|
|
636
815
|
var EntityUniqueKeys2 = Symbol("entityUniqueKeys");
|
|
816
|
+
var EntitySettings2 = Symbol("entitySettings");
|
|
817
|
+
var EntityLifetime = Symbol("entityLifetime");
|
|
818
|
+
var EXPIRES_AT = "expiresAt";
|
|
819
|
+
var ENTITY_VERSION_KEY = "version";
|
|
820
|
+
function isVersionedEntityClass(entityClass) {
|
|
821
|
+
if (typeof entityClass !== "function")
|
|
822
|
+
return false;
|
|
823
|
+
return entityClass[EntitySettings2]?.versioned === true;
|
|
824
|
+
}
|
|
825
|
+
function identityKeysOf(entityClass) {
|
|
826
|
+
const keys = [...ENTITY_IDENTITY_KEYS2];
|
|
827
|
+
if (isVersionedEntityClass(entityClass))
|
|
828
|
+
keys.push(ENTITY_VERSION_KEY);
|
|
829
|
+
if (typeof entityClass === "function" && entityClass[EntityLifetime] !== undefined) {
|
|
830
|
+
keys.push(EXPIRES_AT);
|
|
831
|
+
}
|
|
832
|
+
return keys;
|
|
833
|
+
}
|
|
834
|
+
function isIdentityKey(Entity, key) {
|
|
835
|
+
return identityKeysOf(Entity).includes(key);
|
|
836
|
+
}
|
|
837
|
+
function storeEntityVersion(entity, version) {
|
|
838
|
+
storeRecordValue(entity, ENTITY_VERSION_KEY, version);
|
|
839
|
+
}
|
|
637
840
|
function identityOf(entity) {
|
|
638
841
|
return entity;
|
|
639
842
|
}
|
|
@@ -648,23 +851,34 @@ function assertAlive(entity) {
|
|
|
648
851
|
});
|
|
649
852
|
}
|
|
650
853
|
}
|
|
651
|
-
function withIdentity(value, clock) {
|
|
854
|
+
function withIdentity(value, clock, lifetime, versioned) {
|
|
652
855
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
653
856
|
return value;
|
|
654
857
|
}
|
|
655
|
-
const { id, createdAt } = value;
|
|
656
|
-
|
|
858
|
+
const { id, createdAt, expiresAt, version } = value;
|
|
859
|
+
const expiring = lifetime !== undefined && expiresAt === undefined;
|
|
860
|
+
const unversioned = versioned && version === undefined;
|
|
861
|
+
if (id !== undefined && createdAt !== undefined && !expiring && !unversioned)
|
|
657
862
|
return value;
|
|
658
|
-
|
|
863
|
+
const created = createdAt === undefined ? clock.now() : createdAt;
|
|
864
|
+
const identity = {
|
|
659
865
|
...value,
|
|
660
866
|
id: id === undefined ? clock.generateId() : id,
|
|
661
|
-
createdAt:
|
|
867
|
+
createdAt: created,
|
|
868
|
+
...unversioned ? { [ENTITY_VERSION_KEY]: 1 } : {}
|
|
662
869
|
};
|
|
870
|
+
if (expiring) {
|
|
871
|
+
const start = typeof created === "string" ? Date.parse(created) : NaN;
|
|
872
|
+
if (!Number.isNaN(start)) {
|
|
873
|
+
identity[EXPIRES_AT] = new Date(start + lifetime).toISOString();
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
return identity;
|
|
663
877
|
}
|
|
664
878
|
|
|
665
879
|
class AbstractEntity2 extends AbstractRecord2 {
|
|
666
880
|
constructor(value) {
|
|
667
|
-
super(withIdentity(value, new.target[EntityClock]));
|
|
881
|
+
super(withIdentity(value, new.target[EntityClock], new.target[EntityLifetime], isVersionedEntityClass(new.target)));
|
|
668
882
|
const input = value;
|
|
669
883
|
if (input.id === undefined && input.createdAt === undefined) {
|
|
670
884
|
enqueueLifecycle(this, "onCreate");
|
|
@@ -691,7 +905,7 @@ class AbstractEntity2 extends AbstractRecord2 {
|
|
|
691
905
|
}
|
|
692
906
|
set(key, value) {
|
|
693
907
|
const name = String(key);
|
|
694
|
-
if (
|
|
908
|
+
if (isIdentityKey(this.constructor, name)) {
|
|
695
909
|
throw new ImmutableEntityKey2({
|
|
696
910
|
source: recordNameOf2(this.constructor),
|
|
697
911
|
details: { key: name }
|
|
@@ -745,11 +959,34 @@ var RepositoryUniqueViolation2 = exceptionOf2({
|
|
|
745
959
|
name: "RepositoryUniqueViolation",
|
|
746
960
|
message: ({ key }, source) => `"${source}" already holds an entity with this "${key}".`
|
|
747
961
|
});
|
|
962
|
+
var RepositoryUsedOutsideApplication2 = exceptionOf2({
|
|
963
|
+
layer: "repository" /* Repository */,
|
|
964
|
+
name: "RepositoryUsedOutsideApplication",
|
|
965
|
+
message: ({
|
|
966
|
+
application,
|
|
967
|
+
repository
|
|
968
|
+
}, source) => `"${source}" is bound to the application "${application}", and its declaration no longer reads or writes: use app.repositories.${repository}, run its use cases with app.run, or give the repository of the application to execute.`
|
|
969
|
+
});
|
|
748
970
|
var RepositoryEntityNotFound2 = exceptionOf2({
|
|
749
971
|
layer: "repository" /* Repository */,
|
|
750
972
|
name: "RepositoryEntityNotFound",
|
|
751
973
|
message: ({ id }, source) => `"${source}" holds no entity of id "${id}".`
|
|
752
974
|
});
|
|
975
|
+
var RepositoryConcurrencyConflict2 = exceptionOf2({
|
|
976
|
+
layer: "repository" /* Repository */,
|
|
977
|
+
name: "RepositoryConcurrencyConflict",
|
|
978
|
+
message: ({
|
|
979
|
+
id,
|
|
980
|
+
expected
|
|
981
|
+
}, source) => `"${source}" holds another version of "${id}" than ${expected}: it changed since it was read.`
|
|
982
|
+
});
|
|
983
|
+
var InvalidRepositoryIncrement2 = exceptionOf2({
|
|
984
|
+
layer: "repository" /* Repository */,
|
|
985
|
+
name: "InvalidRepositoryIncrement",
|
|
986
|
+
message: ({
|
|
987
|
+
reason
|
|
988
|
+
}) => reason
|
|
989
|
+
});
|
|
753
990
|
|
|
754
991
|
// src/core/repository/cursor.ts
|
|
755
992
|
var VERSION = 1;
|
|
@@ -892,7 +1129,14 @@ function plain(value) {
|
|
|
892
1129
|
function isMembership(value) {
|
|
893
1130
|
return plain(value) && Object.keys(value).length === 1 && Array.isArray(value.in);
|
|
894
1131
|
}
|
|
895
|
-
var QUERY_OPTIONS = [
|
|
1132
|
+
var QUERY_OPTIONS = [
|
|
1133
|
+
"where",
|
|
1134
|
+
"orderBy",
|
|
1135
|
+
"limit",
|
|
1136
|
+
"offset",
|
|
1137
|
+
"withDeleted",
|
|
1138
|
+
"withExpired"
|
|
1139
|
+
];
|
|
896
1140
|
function prepareQuery(context, input = {}, map = (_key, value) => value, options = QUERY_OPTIONS) {
|
|
897
1141
|
const fail = (reason) => {
|
|
898
1142
|
throw new InvalidRepositoryQuery2({
|
|
@@ -917,6 +1161,8 @@ function prepareQuery(context, input = {}, map = (_key, value) => value, options
|
|
|
917
1161
|
}
|
|
918
1162
|
if (input.withDeleted !== undefined && typeof input.withDeleted !== "boolean")
|
|
919
1163
|
fail("Expected withDeleted to be boolean.");
|
|
1164
|
+
if (input.withExpired !== undefined && typeof input.withExpired !== "boolean")
|
|
1165
|
+
fail("Expected withExpired to be boolean.");
|
|
920
1166
|
let where;
|
|
921
1167
|
if (input.where !== undefined) {
|
|
922
1168
|
if (!plain(input.where))
|
|
@@ -957,8 +1203,18 @@ function sameValue(a, b) {
|
|
|
957
1203
|
const keys = Object.keys(a);
|
|
958
1204
|
return keys.length === Object.keys(b).length && keys.every((key) => Object.hasOwn(b, key) && sameValue(a[key], b[key]));
|
|
959
1205
|
}
|
|
960
|
-
function
|
|
961
|
-
|
|
1206
|
+
function isExpired(value, now) {
|
|
1207
|
+
if (value === undefined || value === null)
|
|
1208
|
+
return false;
|
|
1209
|
+
const at = value instanceof Date ? value.getTime() : typeof value === "string" ? Date.parse(value) : Number.NaN;
|
|
1210
|
+
return !Number.isNaN(at) && at <= now;
|
|
1211
|
+
}
|
|
1212
|
+
function nowOf(query) {
|
|
1213
|
+
const now = query.now === undefined ? Number.NaN : Date.parse(query.now);
|
|
1214
|
+
return Number.isNaN(now) ? Date.now() : now;
|
|
1215
|
+
}
|
|
1216
|
+
function matchesQuery(data, query, expiry, now = expiry === undefined ? 0 : nowOf(query)) {
|
|
1217
|
+
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) => sameValue(data[key], candidate)) : sameValue(data[key], value));
|
|
962
1218
|
}
|
|
963
1219
|
function codePoints(text) {
|
|
964
1220
|
return Array.from(text, (char) => char.codePointAt(0));
|
|
@@ -1008,8 +1264,9 @@ function rowsAfter(rows, orders, after) {
|
|
|
1008
1264
|
const position = Object.fromEntries(orders.map(({ key }, index) => [key, after[index]]));
|
|
1009
1265
|
return rows.filter((row) => compareRows(orders, row, position) > 0);
|
|
1010
1266
|
}
|
|
1011
|
-
function selectRows(rows, query) {
|
|
1012
|
-
const
|
|
1267
|
+
function selectRows(rows, query, expiry) {
|
|
1268
|
+
const now = expiry === undefined ? 0 : nowOf(query);
|
|
1269
|
+
const ordered = orderRows([...rows].filter((data) => matchesQuery(data, query, expiry, now)), query);
|
|
1013
1270
|
const matched = query.after === undefined ? ordered : rowsAfter(ordered, query.orderBy ?? [], query.after);
|
|
1014
1271
|
const start = query.offset ?? 0;
|
|
1015
1272
|
return matched.slice(start, query.limit === undefined ? undefined : start + query.limit);
|
|
@@ -1062,15 +1319,18 @@ function scopeOf(initial, root) {
|
|
|
1062
1319
|
}
|
|
1063
1320
|
};
|
|
1064
1321
|
const provider = {
|
|
1065
|
-
capabilities: Object.freeze({
|
|
1322
|
+
capabilities: Object.freeze({
|
|
1323
|
+
transactions: true,
|
|
1324
|
+
keyset: true,
|
|
1325
|
+
versioning: true,
|
|
1326
|
+
atomic: true,
|
|
1327
|
+
expiry: true
|
|
1328
|
+
}),
|
|
1066
1329
|
findOne: (context, query = {}) => whenIdle(() => {
|
|
1067
|
-
const [first] = selectRows(tableOf(context).values(), {
|
|
1068
|
-
...query,
|
|
1069
|
-
limit: Math.min(query.limit ?? 1, 1)
|
|
1070
|
-
});
|
|
1330
|
+
const [first] = selectRows(tableOf(context).values(), { ...query, limit: Math.min(query.limit ?? 1, 1) }, context.expiry?.key);
|
|
1071
1331
|
return first === undefined ? undefined : copy(first);
|
|
1072
1332
|
}),
|
|
1073
|
-
findMany: (context, query = {}) => whenIdle(() => selectRows(tableOf(context).values(), query).map(copy)),
|
|
1333
|
+
findMany: (context, query = {}) => whenIdle(() => selectRows(tableOf(context).values(), query, context.expiry?.key).map(copy)),
|
|
1074
1334
|
insert: (context, data) => whenIdle(() => {
|
|
1075
1335
|
const table = tableOf(context);
|
|
1076
1336
|
if (table.has(idOf(data))) {
|
|
@@ -1082,17 +1342,61 @@ function scopeOf(initial, root) {
|
|
|
1082
1342
|
assertUnique(context, data);
|
|
1083
1343
|
table.set(idOf(data), copy(data));
|
|
1084
1344
|
}),
|
|
1085
|
-
update: (context, data) => whenIdle(() => {
|
|
1345
|
+
update: (context, data, options = {}) => whenIdle(() => {
|
|
1086
1346
|
const table = tableOf(context);
|
|
1087
|
-
|
|
1347
|
+
const stored = table.get(idOf(data));
|
|
1348
|
+
if (!stored) {
|
|
1088
1349
|
throw new RepositoryEntityNotFound2({
|
|
1089
1350
|
source: context.name,
|
|
1090
1351
|
details: { id: idOf(data) }
|
|
1091
1352
|
});
|
|
1092
1353
|
}
|
|
1354
|
+
const { expectedVersion: expected } = options;
|
|
1355
|
+
if (expected !== undefined && stored.version !== expected) {
|
|
1356
|
+
throw new RepositoryConcurrencyConflict2({
|
|
1357
|
+
source: context.name,
|
|
1358
|
+
details: {
|
|
1359
|
+
id: idOf(data),
|
|
1360
|
+
expected,
|
|
1361
|
+
...typeof stored.version === "number" ? { actual: stored.version } : {}
|
|
1362
|
+
}
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1093
1365
|
assertUnique(context, data);
|
|
1094
1366
|
table.set(idOf(data), copy(data));
|
|
1095
1367
|
}),
|
|
1368
|
+
increment: (context, id, deltas, options = {}) => whenIdle(() => {
|
|
1369
|
+
const table = tableOf(context);
|
|
1370
|
+
const found = table.get(id);
|
|
1371
|
+
const expiry = context.expiry?.key;
|
|
1372
|
+
const expired = found !== undefined && expiry !== undefined && !matchesQuery(found, { withDeleted: true, now: options.now }, expiry);
|
|
1373
|
+
const stored = expired ? undefined : found;
|
|
1374
|
+
if (!stored || stored.deletedAt != null) {
|
|
1375
|
+
if (!options.create) {
|
|
1376
|
+
throw new RepositoryEntityNotFound2({
|
|
1377
|
+
source: context.name,
|
|
1378
|
+
details: { id }
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
1381
|
+
if (stored) {
|
|
1382
|
+
throw new RepositoryUniqueViolation2({
|
|
1383
|
+
source: context.name,
|
|
1384
|
+
details: { key: "id" }
|
|
1385
|
+
});
|
|
1386
|
+
}
|
|
1387
|
+
const created = { ...options.create, id };
|
|
1388
|
+
assertUnique(context, created);
|
|
1389
|
+
table.set(id, copy(created));
|
|
1390
|
+
return copy(created);
|
|
1391
|
+
}
|
|
1392
|
+
const next = { ...stored, ...options.set };
|
|
1393
|
+
for (const [key, delta] of Object.entries(deltas)) {
|
|
1394
|
+
const value = stored[key];
|
|
1395
|
+
next[key] = (typeof value === "number" ? value : 0) + delta;
|
|
1396
|
+
}
|
|
1397
|
+
table.set(id, copy(next));
|
|
1398
|
+
return copy(next);
|
|
1399
|
+
}),
|
|
1096
1400
|
remove: (context, id) => whenIdle(() => {
|
|
1097
1401
|
if (!tableOf(context).delete(id)) {
|
|
1098
1402
|
throw new RepositoryEntityNotFound2({
|
|
@@ -1102,9 +1406,11 @@ function scopeOf(initial, root) {
|
|
|
1102
1406
|
}
|
|
1103
1407
|
}),
|
|
1104
1408
|
count: (context, query = {}) => whenIdle(() => {
|
|
1409
|
+
const expiry = context.expiry?.key;
|
|
1410
|
+
const now = expiry === undefined ? 0 : nowOf(query);
|
|
1105
1411
|
let total = 0;
|
|
1106
1412
|
for (const data of tableOf(context).values()) {
|
|
1107
|
-
if (matchesQuery(data, query))
|
|
1413
|
+
if (matchesQuery(data, query, expiry, now))
|
|
1108
1414
|
total++;
|
|
1109
1415
|
}
|
|
1110
1416
|
return total;
|
|
@@ -1145,6 +1451,35 @@ function inMemoryProvider2() {
|
|
|
1145
1451
|
});
|
|
1146
1452
|
}
|
|
1147
1453
|
|
|
1454
|
+
// src/core/repository/plan.ts
|
|
1455
|
+
var ID = Object.freeze({ access: "id" });
|
|
1456
|
+
var UNIQUE = Object.freeze({ access: "unique" });
|
|
1457
|
+
var SCAN = Object.freeze({ access: "scan" });
|
|
1458
|
+
function isMembership2(value) {
|
|
1459
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 1 && Array.isArray(value.in);
|
|
1460
|
+
}
|
|
1461
|
+
function planQuery(repository, query = {}) {
|
|
1462
|
+
const where = query.where ?? {};
|
|
1463
|
+
const keys = Object.keys(where);
|
|
1464
|
+
if (keys.includes("id"))
|
|
1465
|
+
return ID;
|
|
1466
|
+
if (keys.some((key) => repository.unique.includes(key)))
|
|
1467
|
+
return UNIQUE;
|
|
1468
|
+
const equal = new Set(keys.filter((key) => !isMembership2(where[key])));
|
|
1469
|
+
const first = query.orderBy?.[0]?.key;
|
|
1470
|
+
let best;
|
|
1471
|
+
for (const entry of Object.entries(repository.indexes ?? {})) {
|
|
1472
|
+
const [, index] = entry;
|
|
1473
|
+
if (!index.where.every((key) => equal.has(key)))
|
|
1474
|
+
continue;
|
|
1475
|
+
if (first !== undefined && index.orderBy !== first)
|
|
1476
|
+
continue;
|
|
1477
|
+
if (best === undefined || index.where.length > best[1].where.length)
|
|
1478
|
+
best = entry;
|
|
1479
|
+
}
|
|
1480
|
+
return best === undefined ? SCAN : Object.freeze({ access: "index", index: best[0] });
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1148
1483
|
// src/core/repository/state.ts
|
|
1149
1484
|
var states = new WeakMap;
|
|
1150
1485
|
function findState(repository) {
|
|
@@ -1163,11 +1498,22 @@ function stateOf(repository) {
|
|
|
1163
1498
|
throw new TypeError("Expected a repository made by repositoryOf");
|
|
1164
1499
|
return state;
|
|
1165
1500
|
}
|
|
1501
|
+
function expiryKeyOf(entity) {
|
|
1502
|
+
const expires = entity[EntitySettings2]?.expires;
|
|
1503
|
+
if (expires === undefined)
|
|
1504
|
+
return;
|
|
1505
|
+
return "at" in expires ? expires.at : EXPIRES_AT;
|
|
1506
|
+
}
|
|
1166
1507
|
function contextOf(state) {
|
|
1167
|
-
state.context
|
|
1508
|
+
if (state.context)
|
|
1509
|
+
return state.context;
|
|
1510
|
+
const key = expiryKeyOf(state.entity);
|
|
1511
|
+
state.context = Object.freeze({
|
|
1168
1512
|
entity: state.entity,
|
|
1169
1513
|
name: state.name,
|
|
1170
1514
|
unique: state.entity[EntityUniqueKeys2],
|
|
1515
|
+
indexes: state.indexes,
|
|
1516
|
+
...key === undefined ? {} : { expiry: Object.freeze({ key }) },
|
|
1171
1517
|
env: loadEnvironments(state.environment)
|
|
1172
1518
|
});
|
|
1173
1519
|
return state.context;
|
|
@@ -1181,6 +1527,8 @@ function inspectRepository(repository) {
|
|
|
1181
1527
|
entity: state.entity,
|
|
1182
1528
|
actions: state.actions,
|
|
1183
1529
|
deletion: state.deletion,
|
|
1530
|
+
indexes: state.indexes,
|
|
1531
|
+
scans: state.scans,
|
|
1184
1532
|
explicitProvider: state.explicitProvider,
|
|
1185
1533
|
provider: state.explicitProvider ? state.provider : undefined,
|
|
1186
1534
|
environments: environment === undefined ? [] : Array.isArray(environment) ? environment : [environment],
|
|
@@ -1192,33 +1540,61 @@ var origins = new WeakMap;
|
|
|
1192
1540
|
function declarationOf(state) {
|
|
1193
1541
|
return origins.get(state) ?? state;
|
|
1194
1542
|
}
|
|
1195
|
-
|
|
1543
|
+
var declared = new WeakMap;
|
|
1544
|
+
function declareRepository(state, repository) {
|
|
1545
|
+
declared.set(state, repository);
|
|
1546
|
+
}
|
|
1547
|
+
function relatedRepositoryOf(repository, Entity) {
|
|
1548
|
+
const related = findState(repository)?.relations.get(Entity);
|
|
1549
|
+
return related ? declared.get(declarationOf(related)) : undefined;
|
|
1550
|
+
}
|
|
1551
|
+
function bindState(state, provider, instances, logger, bound = new Map) {
|
|
1196
1552
|
const done = bound.get(state);
|
|
1197
1553
|
if (done)
|
|
1198
1554
|
return done;
|
|
1199
1555
|
const copy = {
|
|
1200
1556
|
...state,
|
|
1557
|
+
logger,
|
|
1201
1558
|
provider: state.explicitProvider ? instances?.get(state.provider) ?? state.provider : provider ?? state.provider,
|
|
1202
1559
|
context: undefined,
|
|
1203
1560
|
relations: new Map([...state.relations].map(([target, related]) => [
|
|
1204
1561
|
target,
|
|
1205
|
-
bindState(related, provider, instances, bound)
|
|
1562
|
+
bindState(related, provider, instances, logger, bound)
|
|
1206
1563
|
]))
|
|
1207
1564
|
};
|
|
1208
1565
|
origins.set(copy, declarationOf(state));
|
|
1209
1566
|
bound.set(state, copy);
|
|
1210
1567
|
return copy;
|
|
1211
1568
|
}
|
|
1212
|
-
|
|
1569
|
+
var owners = new WeakMap;
|
|
1570
|
+
function assertUnbound(state) {
|
|
1571
|
+
const owner = owners.get(state);
|
|
1572
|
+
if (owner) {
|
|
1573
|
+
throw new RepositoryUsedOutsideApplication2({
|
|
1574
|
+
source: state.name,
|
|
1575
|
+
details: owner
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
function bindRepository(repository, provider, instances, binding) {
|
|
1213
1580
|
const state = stateOf(repository);
|
|
1214
1581
|
const Declared = repository.constructor;
|
|
1215
1582
|
|
|
1216
1583
|
class Bound extends Declared {
|
|
1217
1584
|
}
|
|
1218
1585
|
Object.defineProperty(Bound, "name", { value: Declared.name });
|
|
1219
|
-
states.set(Bound, bindState(state, provider, instances));
|
|
1586
|
+
states.set(Bound, bindState(state, provider, instances, binding?.logger));
|
|
1587
|
+
if (binding) {
|
|
1588
|
+
owners.set(declarationOf(state), {
|
|
1589
|
+
application: binding.application,
|
|
1590
|
+
repository: binding.key
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1220
1593
|
return Object.freeze(new Bound);
|
|
1221
1594
|
}
|
|
1595
|
+
function providerOfRepository(repository) {
|
|
1596
|
+
return findState(repository)?.provider;
|
|
1597
|
+
}
|
|
1222
1598
|
|
|
1223
1599
|
// src/core/repository/references.ts
|
|
1224
1600
|
function isPlain(value) {
|
|
@@ -1336,7 +1712,8 @@ function parseEntity(state, data) {
|
|
|
1336
1712
|
function loadByIds(state, ids, scope) {
|
|
1337
1713
|
return scopedProvider(scope, state.provider).findMany(contextOf(state), {
|
|
1338
1714
|
where: { id: { in: ids } },
|
|
1339
|
-
withDeleted: true
|
|
1715
|
+
withDeleted: true,
|
|
1716
|
+
withExpired: true
|
|
1340
1717
|
});
|
|
1341
1718
|
}
|
|
1342
1719
|
async function resolveReferences(state, datas, session, scope) {
|
|
@@ -1427,50 +1804,125 @@ class AbstractRepository2 {
|
|
|
1427
1804
|
}
|
|
1428
1805
|
function action(method) {
|
|
1429
1806
|
return function(...args) {
|
|
1430
|
-
|
|
1807
|
+
const state = stateOf(this);
|
|
1808
|
+
return guard2({ layer: "repository" /* Repository */, source: state.name }, async () => {
|
|
1809
|
+
assertUnbound(state);
|
|
1810
|
+
return method(this, ...args);
|
|
1811
|
+
});
|
|
1431
1812
|
};
|
|
1432
1813
|
}
|
|
1433
1814
|
function queryValueOf(context, key, value) {
|
|
1434
1815
|
const properties = context.entity.blueprint.properties;
|
|
1435
1816
|
return recordFieldDataOf(properties[key], value, REFERENCES);
|
|
1436
1817
|
}
|
|
1818
|
+
function expiryOf(self, withExpired) {
|
|
1819
|
+
const { context, provider } = self;
|
|
1820
|
+
if (context.expiry === undefined)
|
|
1821
|
+
return {};
|
|
1822
|
+
if (provider.capabilities?.expiry !== true) {
|
|
1823
|
+
throw new InvalidRepositoryQuery2({
|
|
1824
|
+
source: context.name,
|
|
1825
|
+
details: {
|
|
1826
|
+
reason: "The provider does not leave expired records out (capabilities.expiry), which an entity with expires needs."
|
|
1827
|
+
}
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
const clock = context.entity[EntityClock];
|
|
1831
|
+
return { withExpired: withExpired ?? false, now: clock.now() };
|
|
1832
|
+
}
|
|
1437
1833
|
function queryOf(self, query) {
|
|
1438
1834
|
const prepared = prepareQuery(self.context, query, (key, value) => queryValueOf(self.context, key, value));
|
|
1439
1835
|
return {
|
|
1440
1836
|
...prepared,
|
|
1441
|
-
withDeleted: prepared.withDeleted ?? stateOf(self).deletion === "hard"
|
|
1837
|
+
withDeleted: prepared.withDeleted ?? stateOf(self).deletion === "hard",
|
|
1838
|
+
...expiryOf(self, prepared.withExpired)
|
|
1442
1839
|
};
|
|
1443
1840
|
}
|
|
1444
1841
|
var equals = (self, key, value) => queryOf(self, { where: { [key]: value } });
|
|
1842
|
+
function planned(self, method, query, orderBy = query.orderBy) {
|
|
1843
|
+
const state = stateOf(self);
|
|
1844
|
+
const plan = planQuery(self.context, { where: query.where, orderBy });
|
|
1845
|
+
if (plan.access === "scan" && state.scans !== "allow") {
|
|
1846
|
+
const fields = {
|
|
1847
|
+
repository: state.name,
|
|
1848
|
+
method,
|
|
1849
|
+
where: Object.keys(query.where ?? {}),
|
|
1850
|
+
orderBy: (orderBy ?? []).map(({ key }) => key)
|
|
1851
|
+
};
|
|
1852
|
+
if (state.scans === "reject") {
|
|
1853
|
+
throw new InvalidRepositoryQuery2({
|
|
1854
|
+
source: state.name,
|
|
1855
|
+
details: {
|
|
1856
|
+
reason: `"${method}" matches no index of ${state.name} and would read every row (scans: "reject").`,
|
|
1857
|
+
scan: true,
|
|
1858
|
+
...fields
|
|
1859
|
+
}
|
|
1860
|
+
});
|
|
1861
|
+
}
|
|
1862
|
+
const scope = self[ScopeKey];
|
|
1863
|
+
writeLog(resolveLogger(scope?.logger ?? state.logger, undefined), "warn", "repository-scan", fields);
|
|
1864
|
+
}
|
|
1865
|
+
return plan.index === undefined ? query : { ...query, index: plan.index };
|
|
1866
|
+
}
|
|
1445
1867
|
var findBy = (key) => action(async (self, value) => {
|
|
1446
1868
|
const data = await self.provider.findOne(self.context, equals(self, key, value));
|
|
1447
1869
|
return data === undefined ? undefined : await self.hydrate(data);
|
|
1448
1870
|
});
|
|
1449
|
-
var findManyBy = (key) => action(async (self, value) => self.hydrateMany(await self.provider.findMany(self.context, equals(self, key, value))));
|
|
1450
|
-
var findMany = action(async (self, query) => self.hydrateMany(await self.provider.findMany(self.context, queryOf(self, query))));
|
|
1451
|
-
var exists = action(async (self, query) => await self.provider.findOne(self.context, queryOf(self, query)) !== undefined);
|
|
1871
|
+
var findManyBy = (key) => action(async (self, value) => self.hydrateMany(await self.provider.findMany(self.context, planned(self, `findManyBy${capitalize(key)}`, equals(self, key, value)))));
|
|
1872
|
+
var findMany = action(async (self, query) => self.hydrateMany(await self.provider.findMany(self.context, planned(self, "findMany", queryOf(self, query)))));
|
|
1873
|
+
var exists = action(async (self, query) => await self.provider.findOne(self.context, planned(self, "exists", queryOf(self, query))) !== undefined);
|
|
1874
|
+
var EXPLAIN_OPTIONS = [
|
|
1875
|
+
"where",
|
|
1876
|
+
"orderBy",
|
|
1877
|
+
"limit",
|
|
1878
|
+
"offset",
|
|
1879
|
+
"withDeleted",
|
|
1880
|
+
"after"
|
|
1881
|
+
];
|
|
1882
|
+
var explain = action(async (self, query = {}) => {
|
|
1883
|
+
const prepared = prepareQuery(self.context, query, (key, value) => queryValueOf(self.context, key, value), EXPLAIN_OPTIONS);
|
|
1884
|
+
return planQuery(self.context, prepared);
|
|
1885
|
+
});
|
|
1452
1886
|
var findPage = action(async (self, query) => {
|
|
1453
1887
|
const {
|
|
1454
1888
|
orderBy,
|
|
1455
1889
|
where,
|
|
1456
1890
|
withDeleted,
|
|
1891
|
+
withExpired,
|
|
1892
|
+
now,
|
|
1457
1893
|
limit = 20,
|
|
1458
|
-
offset = 0
|
|
1459
|
-
|
|
1894
|
+
offset = 0,
|
|
1895
|
+
index
|
|
1896
|
+
} = planned(self, "findPage", queryOf(self, query));
|
|
1897
|
+
const expiry = withExpired === undefined ? {} : { withExpired, now };
|
|
1460
1898
|
const [rows, total] = await Promise.all([
|
|
1461
1899
|
self.provider.findMany(self.context, {
|
|
1462
1900
|
where,
|
|
1463
1901
|
orderBy,
|
|
1464
1902
|
withDeleted,
|
|
1903
|
+
...expiry,
|
|
1465
1904
|
limit,
|
|
1466
|
-
offset
|
|
1905
|
+
offset,
|
|
1906
|
+
...index === undefined ? {} : { index }
|
|
1467
1907
|
}),
|
|
1468
|
-
self.provider.count(self.context, {
|
|
1908
|
+
self.provider.count(self.context, {
|
|
1909
|
+
where,
|
|
1910
|
+
withDeleted,
|
|
1911
|
+
...expiry,
|
|
1912
|
+
...index === undefined ? {} : { index }
|
|
1913
|
+
})
|
|
1469
1914
|
]);
|
|
1470
1915
|
return { items: await self.hydrateMany(rows), total, limit, offset };
|
|
1471
1916
|
});
|
|
1472
|
-
var CURSOR_OPTIONS = [
|
|
1473
|
-
|
|
1917
|
+
var CURSOR_OPTIONS = [
|
|
1918
|
+
"where",
|
|
1919
|
+
"orderBy",
|
|
1920
|
+
"limit",
|
|
1921
|
+
"withDeleted",
|
|
1922
|
+
"withExpired",
|
|
1923
|
+
"after"
|
|
1924
|
+
];
|
|
1925
|
+
function cursorReadOf(self, query) {
|
|
1474
1926
|
const { context } = self;
|
|
1475
1927
|
const fail = (reason) => {
|
|
1476
1928
|
throw new InvalidRepositoryQuery2({
|
|
@@ -1479,7 +1931,6 @@ var findCursor = action(async (self, query = {}) => {
|
|
|
1479
1931
|
});
|
|
1480
1932
|
};
|
|
1481
1933
|
const prepared = prepareQuery(context, query, (key, value) => queryValueOf(context, key, value), CURSOR_OPTIONS);
|
|
1482
|
-
const { after } = query;
|
|
1483
1934
|
const { where, limit = 20 } = prepared;
|
|
1484
1935
|
if (limit < 1)
|
|
1485
1936
|
fail("Expected a positive integer for limit.");
|
|
@@ -1492,11 +1943,27 @@ var findCursor = action(async (self, query = {}) => {
|
|
|
1492
1943
|
}
|
|
1493
1944
|
if (self.provider.capabilities?.keyset !== true)
|
|
1494
1945
|
fail("The provider does not read by cursor (capabilities.keyset).");
|
|
1946
|
+
const { index } = planned(self, "findCursor", { where }, declared);
|
|
1947
|
+
expiryOf(self, prepared.withExpired);
|
|
1948
|
+
return {
|
|
1949
|
+
where,
|
|
1950
|
+
orderBy,
|
|
1951
|
+
withDeleted,
|
|
1952
|
+
withExpired: prepared.withExpired,
|
|
1953
|
+
limit,
|
|
1954
|
+
...index === undefined ? {} : { index }
|
|
1955
|
+
};
|
|
1956
|
+
}
|
|
1957
|
+
async function readCursor(self, read, after) {
|
|
1958
|
+
const { context } = self;
|
|
1959
|
+
const { where, orderBy, withDeleted, limit, index } = read;
|
|
1495
1960
|
const rows = await self.provider.findMany(context, {
|
|
1496
1961
|
where,
|
|
1497
1962
|
orderBy,
|
|
1498
1963
|
withDeleted,
|
|
1964
|
+
...expiryOf(self, read.withExpired),
|
|
1499
1965
|
limit: limit + 1,
|
|
1966
|
+
...index === undefined ? {} : { index },
|
|
1500
1967
|
...after === undefined ? {} : { after: decodeCursor(context.name, orderBy, after) }
|
|
1501
1968
|
});
|
|
1502
1969
|
const page = rows.slice(0, limit);
|
|
@@ -1506,6 +1973,49 @@ var findCursor = action(async (self, query = {}) => {
|
|
|
1506
1973
|
limit,
|
|
1507
1974
|
next: rows.length > limit && last !== undefined ? encodeCursor(orderBy, last) : undefined
|
|
1508
1975
|
};
|
|
1976
|
+
}
|
|
1977
|
+
var findCursor = action(async (self, query = {}) => readCursor(self, cursorReadOf(self, query), query.after));
|
|
1978
|
+
function stopperOf(source, until) {
|
|
1979
|
+
if (until === undefined)
|
|
1980
|
+
return () => false;
|
|
1981
|
+
if (typeof until === "function")
|
|
1982
|
+
return () => Boolean(until());
|
|
1983
|
+
const deadline = until instanceof Date ? until.getTime() : typeof until === "number" ? until : Number.NaN;
|
|
1984
|
+
if (Number.isNaN(deadline)) {
|
|
1985
|
+
throw new InvalidRepositoryQuery2({
|
|
1986
|
+
source,
|
|
1987
|
+
details: {
|
|
1988
|
+
reason: "Expected until to be a Date, epoch milliseconds or a function."
|
|
1989
|
+
}
|
|
1990
|
+
});
|
|
1991
|
+
}
|
|
1992
|
+
return () => Date.now() >= deadline;
|
|
1993
|
+
}
|
|
1994
|
+
var forEachPage = action(async (self, query, fn, options) => {
|
|
1995
|
+
const { context } = self;
|
|
1996
|
+
if (typeof fn !== "function") {
|
|
1997
|
+
throw new InvalidRepositoryQuery2({
|
|
1998
|
+
source: context.name,
|
|
1999
|
+
details: { reason: "Expected a function to run on each page." }
|
|
2000
|
+
});
|
|
2001
|
+
}
|
|
2002
|
+
const read = cursorReadOf(self, query ?? {});
|
|
2003
|
+
const stop = stopperOf(context.name, options?.until);
|
|
2004
|
+
let after = query?.after;
|
|
2005
|
+
let pages = 0;
|
|
2006
|
+
let items = 0;
|
|
2007
|
+
while (!stop()) {
|
|
2008
|
+
const page = await readCursor(self, read, after);
|
|
2009
|
+
if (page.items.length > 0) {
|
|
2010
|
+
await fn(page.items, { index: pages, next: page.next });
|
|
2011
|
+
pages += 1;
|
|
2012
|
+
items += page.items.length;
|
|
2013
|
+
}
|
|
2014
|
+
if (page.next === undefined)
|
|
2015
|
+
return { done: true, pages, items };
|
|
2016
|
+
after = page.next;
|
|
2017
|
+
}
|
|
2018
|
+
return { done: false, next: after, pages, items };
|
|
1509
2019
|
});
|
|
1510
2020
|
var create = action(async (self, input) => {
|
|
1511
2021
|
const { entity: Entity } = self.context;
|
|
@@ -1513,46 +2023,183 @@ var create = action(async (self, input) => {
|
|
|
1513
2023
|
await self.provider.insert(self.context, self.dataOf(entity));
|
|
1514
2024
|
return entity;
|
|
1515
2025
|
});
|
|
2026
|
+
function nowOf2(context) {
|
|
2027
|
+
return context.entity[EntityClock].now();
|
|
2028
|
+
}
|
|
2029
|
+
function assertVersioning(self) {
|
|
2030
|
+
if (self.provider.capabilities?.versioning === true)
|
|
2031
|
+
return;
|
|
2032
|
+
invalid3(self.context.name, `${recordNameOf2(self.context.entity)} is versioned, and the provider does not check versions (capabilities.versioning).`);
|
|
2033
|
+
}
|
|
2034
|
+
async function write(self, data) {
|
|
2035
|
+
if (!isVersionedEntityClass(self.context.entity)) {
|
|
2036
|
+
await self.provider.update(self.context, data);
|
|
2037
|
+
return data;
|
|
2038
|
+
}
|
|
2039
|
+
assertVersioning(self);
|
|
2040
|
+
const expected = data.version;
|
|
2041
|
+
const next = { ...data, version: expected + 1 };
|
|
2042
|
+
await self.provider.update(self.context, next, {
|
|
2043
|
+
expectedVersion: expected
|
|
2044
|
+
});
|
|
2045
|
+
return next;
|
|
2046
|
+
}
|
|
2047
|
+
async function writeEntity(self, entity) {
|
|
2048
|
+
const written = await write(self, self.dataOf(entity));
|
|
2049
|
+
if (written.version !== undefined && isEntity2(entity)) {
|
|
2050
|
+
storeEntityVersion(entity, written.version);
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
1516
2053
|
var update = action(async (self, entity) => {
|
|
1517
|
-
await
|
|
2054
|
+
await writeEntity(self, entity);
|
|
1518
2055
|
return entity;
|
|
1519
2056
|
});
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
2057
|
+
function isPlainObject2(value) {
|
|
2058
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2059
|
+
}
|
|
2060
|
+
function isNumericField(field) {
|
|
2061
|
+
if (field === undefined)
|
|
2062
|
+
return false;
|
|
2063
|
+
const property = fieldOf(field);
|
|
2064
|
+
if (!isProperty2(property))
|
|
2065
|
+
return false;
|
|
2066
|
+
const { type } = property.type.schema.type.Type();
|
|
2067
|
+
return type === "number" || type === "integer";
|
|
2068
|
+
}
|
|
2069
|
+
function deltasOf(context, deltas) {
|
|
2070
|
+
const { name: source } = context;
|
|
2071
|
+
const fail = (reason, issues) => {
|
|
2072
|
+
throw new InvalidRepositoryIncrement2({
|
|
2073
|
+
source,
|
|
2074
|
+
details: { reason, issues }
|
|
2075
|
+
});
|
|
2076
|
+
};
|
|
2077
|
+
if (!isPlainObject2(deltas) || Object.keys(deltas).length === 0) {
|
|
2078
|
+
const message = "Expected an object of the numbers to add, by key.";
|
|
2079
|
+
fail(message, [{ code: "invalid_type", path: [], message, source }]);
|
|
2080
|
+
}
|
|
2081
|
+
const { change } = context.entity.options;
|
|
2082
|
+
const properties = context.entity.blueprint.properties;
|
|
2083
|
+
const issues = [];
|
|
2084
|
+
for (const [key, value] of Object.entries(deltas)) {
|
|
2085
|
+
if (!change.includes(key) || !isNumericField(properties[key])) {
|
|
2086
|
+
issues.push({
|
|
2087
|
+
code: "undeclared_key",
|
|
2088
|
+
path: [key],
|
|
2089
|
+
message: `"${key}" is not a number in the change list of ${recordNameOf2(context.entity)}.`,
|
|
2090
|
+
source
|
|
2091
|
+
});
|
|
2092
|
+
} else if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
2093
|
+
issues.push({
|
|
2094
|
+
code: "invalid_value",
|
|
2095
|
+
path: [key],
|
|
2096
|
+
message: "Expected a finite number.",
|
|
2097
|
+
source
|
|
1529
2098
|
});
|
|
1530
2099
|
}
|
|
1531
|
-
entity = await self.hydrate(data);
|
|
1532
2100
|
}
|
|
1533
|
-
|
|
2101
|
+
if (issues.length > 0) {
|
|
2102
|
+
fail(`"${source}" cannot increment ${issues.map((issue) => `"${issue.path[0]}"`).join(", ")}.`, issues);
|
|
2103
|
+
}
|
|
2104
|
+
return deltas;
|
|
2105
|
+
}
|
|
2106
|
+
function createdOf(context, id, input, deltas) {
|
|
2107
|
+
const identity = identityKeysOf(context.entity);
|
|
2108
|
+
const value = isPlainObject2(input) ? Object.fromEntries(Object.entries(input).filter(([key]) => !identity.includes(key))) : {};
|
|
2109
|
+
for (const [key, delta] of Object.entries(deltas)) {
|
|
2110
|
+
const base = value[key];
|
|
2111
|
+
value[key] = (typeof base === "number" ? base : 0) + delta;
|
|
2112
|
+
}
|
|
2113
|
+
const Entity = context.entity;
|
|
2114
|
+
const entity = new Entity({ ...value, id });
|
|
2115
|
+
enqueueLifecycle(entity, "onCreate");
|
|
2116
|
+
return entity;
|
|
2117
|
+
}
|
|
2118
|
+
var increment = action(async (self, target, deltas, options) => {
|
|
2119
|
+
const { context } = self;
|
|
2120
|
+
const id = typeof target === "string" ? target : self.dataOf(target).id;
|
|
2121
|
+
const checked = deltasOf(context, deltas);
|
|
2122
|
+
if (options !== undefined && !isPlainObject2(options)) {
|
|
2123
|
+
invalid3(context.name, "Expected the options of increment as an object.");
|
|
2124
|
+
}
|
|
2125
|
+
const versioned = isVersionedEntityClass(context.entity);
|
|
2126
|
+
if (versioned)
|
|
2127
|
+
assertVersioning(self);
|
|
2128
|
+
const { create } = options ?? {};
|
|
2129
|
+
const created = create === undefined ? undefined : createdOf(context, id, create, checked);
|
|
2130
|
+
const stored = await self.provider.increment(context, id, versioned ? { ...checked, version: 1 } : checked, {
|
|
2131
|
+
set: { updatedAt: nowOf2(context) },
|
|
2132
|
+
...created === undefined ? {} : { create: self.dataOf(created) },
|
|
2133
|
+
...context.expiry === undefined ? {} : { now: expiryOf(self, false).now }
|
|
2134
|
+
});
|
|
2135
|
+
if (created !== undefined && stored.updatedAt === undefined)
|
|
2136
|
+
return created;
|
|
2137
|
+
const entity = await self.hydrate(stored);
|
|
2138
|
+
enqueueLifecycle(entity, "onUpdate");
|
|
2139
|
+
return entity;
|
|
2140
|
+
});
|
|
2141
|
+
var remove = action(async (self, target) => {
|
|
2142
|
+
const { context } = self;
|
|
2143
|
+
let entity = target;
|
|
2144
|
+
if (typeof target === "string") {
|
|
2145
|
+
const data = await self.provider.findOne(context, equals(self, "id", target));
|
|
2146
|
+
if (data === undefined) {
|
|
2147
|
+
throw new RepositoryEntityNotFound2({
|
|
2148
|
+
source: context.name,
|
|
2149
|
+
details: { id: target }
|
|
2150
|
+
});
|
|
2151
|
+
}
|
|
2152
|
+
entity = await self.hydrate(data);
|
|
2153
|
+
}
|
|
2154
|
+
const data = self.dataOf(entity);
|
|
1534
2155
|
const destroyable = entity;
|
|
1535
2156
|
if (!destroyable.isDeleted)
|
|
1536
2157
|
destroyable.destroy();
|
|
1537
2158
|
if (stateOf(self).deletion === "soft") {
|
|
1538
|
-
await self
|
|
2159
|
+
await writeEntity(self, entity);
|
|
1539
2160
|
} else {
|
|
1540
2161
|
await self.provider.remove(context, data.id);
|
|
1541
2162
|
}
|
|
1542
2163
|
return entity;
|
|
1543
2164
|
});
|
|
1544
|
-
var
|
|
2165
|
+
var restore = action(async (self, target) => {
|
|
2166
|
+
const { context } = self;
|
|
2167
|
+
const id = typeof target === "string" ? target : self.dataOf(target).id;
|
|
2168
|
+
const data = await self.provider.findOne(context, {
|
|
2169
|
+
...equals(self, "id", id),
|
|
2170
|
+
withDeleted: true
|
|
2171
|
+
});
|
|
2172
|
+
if (data === undefined) {
|
|
2173
|
+
throw new RepositoryEntityNotFound2({
|
|
2174
|
+
source: context.name,
|
|
2175
|
+
details: { id }
|
|
2176
|
+
});
|
|
2177
|
+
}
|
|
2178
|
+
if (data.deletedAt === undefined)
|
|
2179
|
+
return self.hydrate(data);
|
|
2180
|
+
const { deletedAt: _deletedAt, ...alive } = data;
|
|
2181
|
+
const restored = { ...alive, updatedAt: nowOf2(context) };
|
|
2182
|
+
return self.hydrate(await write(self, restored));
|
|
2183
|
+
});
|
|
2184
|
+
var count = action(async (self, query) => {
|
|
2185
|
+
const prepared = queryOf(self, query);
|
|
2186
|
+
return self.provider.count(self.context, planned(self, "count", prepared, []));
|
|
2187
|
+
});
|
|
1545
2188
|
var NEEDS = {
|
|
1546
2189
|
findBy: ["findOne"],
|
|
1547
2190
|
findManyBy: ["findMany"],
|
|
1548
2191
|
create: ["insert"],
|
|
1549
2192
|
update: ["update"],
|
|
1550
2193
|
delete: ["findOne", "remove"],
|
|
2194
|
+
restore: ["findOne", "update"],
|
|
1551
2195
|
count: ["count"],
|
|
1552
2196
|
findMany: ["findMany"],
|
|
1553
2197
|
exists: ["findOne"],
|
|
1554
2198
|
findPage: ["findMany", "count"],
|
|
1555
|
-
findCursor: ["findMany"]
|
|
2199
|
+
findCursor: ["findMany"],
|
|
2200
|
+
explain: [],
|
|
2201
|
+
increment: ["increment"],
|
|
2202
|
+
forEachPage: ["findMany"]
|
|
1556
2203
|
};
|
|
1557
2204
|
function capitalize(key) {
|
|
1558
2205
|
return `${key.charAt(0).toUpperCase()}${key.slice(1)}`;
|
|
@@ -1573,6 +2220,57 @@ function unknownAction(entity, action, methods) {
|
|
|
1573
2220
|
function invalid3(source, reason) {
|
|
1574
2221
|
throw new InvalidRepositoryDeclaration2({ source, details: { reason } });
|
|
1575
2222
|
}
|
|
2223
|
+
var INDEX_NAME = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
2224
|
+
function indexesOf(name, entity, declared) {
|
|
2225
|
+
if (declared === undefined)
|
|
2226
|
+
return Object.freeze({});
|
|
2227
|
+
if (typeof declared !== "object" || declared === null || Array.isArray(declared)) {
|
|
2228
|
+
invalid3(name, "Expected indexes to be an object of indexes by name.");
|
|
2229
|
+
}
|
|
2230
|
+
const properties = entity.blueprint.properties;
|
|
2231
|
+
const sensitive = entity.options.sensitive;
|
|
2232
|
+
const keyOf = (index, key) => {
|
|
2233
|
+
if (typeof key !== "string" || !Object.hasOwn(properties, key)) {
|
|
2234
|
+
invalid3(name, `The index "${index}" names "${String(key)}", which is not a key of ${recordNameOf2(entity)}.`);
|
|
2235
|
+
}
|
|
2236
|
+
if (sensitive.includes(key)) {
|
|
2237
|
+
invalid3(name, `The index "${index}" names "${key}", which is sensitive and cannot be indexed.`);
|
|
2238
|
+
}
|
|
2239
|
+
return key;
|
|
2240
|
+
};
|
|
2241
|
+
const seen = new Map;
|
|
2242
|
+
const indexes = {};
|
|
2243
|
+
for (const [index, value] of Object.entries(declared)) {
|
|
2244
|
+
if (!INDEX_NAME.test(index) || index === "__proto__") {
|
|
2245
|
+
invalid3(name, `Expected the index "${index}" to be named by an identifier.`);
|
|
2246
|
+
}
|
|
2247
|
+
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) {
|
|
2248
|
+
invalid3(name, `Expected the index "${index}" to be { where: [keys], orderBy?: key }.`);
|
|
2249
|
+
}
|
|
2250
|
+
const where = value.where.map((key) => keyOf(index, key));
|
|
2251
|
+
if (new Set(where).size !== where.length) {
|
|
2252
|
+
invalid3(name, `The index "${index}" names a key twice in where.`);
|
|
2253
|
+
}
|
|
2254
|
+
const orderBy = value.orderBy === undefined ? undefined : keyOf(index, value.orderBy);
|
|
2255
|
+
if (orderBy !== undefined) {
|
|
2256
|
+
const field = properties[orderBy];
|
|
2257
|
+
if (isBlueprint2(field) || isBlueprintList2(field) || recordFieldOf2(field)) {
|
|
2258
|
+
invalid3(name, `The index "${index}" orders by "${orderBy}", which is not scalar.`);
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
const signature = JSON.stringify([[...where].sort(), orderBy ?? null]);
|
|
2262
|
+
const other = seen.get(signature);
|
|
2263
|
+
if (other !== undefined) {
|
|
2264
|
+
invalid3(name, `The indexes "${other}" and "${index}" are the same.`);
|
|
2265
|
+
}
|
|
2266
|
+
seen.set(signature, index);
|
|
2267
|
+
indexes[index] = Object.freeze({
|
|
2268
|
+
where: Object.freeze(where),
|
|
2269
|
+
...orderBy === undefined ? {} : { orderBy }
|
|
2270
|
+
});
|
|
2271
|
+
}
|
|
2272
|
+
return Object.freeze(indexes);
|
|
2273
|
+
}
|
|
1576
2274
|
function repositoryOf2(settings) {
|
|
1577
2275
|
const { entity, actions, environmentDependencies } = settings ?? {};
|
|
1578
2276
|
if (!isEntityClass2(entity) || !(EntityUniqueKeys2 in entity)) {
|
|
@@ -1583,6 +2281,10 @@ function repositoryOf2(settings) {
|
|
|
1583
2281
|
if (deletion !== "hard" && deletion !== "soft") {
|
|
1584
2282
|
invalid3(name, 'Expected deletion to be "hard" or "soft".');
|
|
1585
2283
|
}
|
|
2284
|
+
const scans = settings.scans ?? "allow";
|
|
2285
|
+
if (scans !== "allow" && scans !== "warn" && scans !== "reject") {
|
|
2286
|
+
invalid3(name, 'Expected scans to be "allow", "warn" or "reject".');
|
|
2287
|
+
}
|
|
1586
2288
|
const provider = settings.provider ?? inMemoryProvider2();
|
|
1587
2289
|
if (!Array.isArray(actions) || actions.length === 0) {
|
|
1588
2290
|
invalid3(name, "Expected a list of actions.");
|
|
@@ -1591,6 +2293,7 @@ function repositoryOf2(settings) {
|
|
|
1591
2293
|
invalid3(name, "Expected environments made by environmentOf.");
|
|
1592
2294
|
}
|
|
1593
2295
|
const relations = relationsOf(name, entity, settings.relations);
|
|
2296
|
+
const indexes = indexesOf(name, entity, settings.indexes);
|
|
1594
2297
|
const unique = entity[EntityUniqueKeys2];
|
|
1595
2298
|
const methods = new Map;
|
|
1596
2299
|
for (const key of Object.keys(entity.blueprint.properties)) {
|
|
@@ -1604,11 +2307,15 @@ function repositoryOf2(settings) {
|
|
|
1604
2307
|
methods.set("create", ["create", create]);
|
|
1605
2308
|
methods.set("update", ["update", update]);
|
|
1606
2309
|
methods.set("delete", ["delete", remove]);
|
|
2310
|
+
methods.set("restore", ["restore", restore]);
|
|
1607
2311
|
methods.set("count", ["count", count]);
|
|
1608
2312
|
methods.set("findMany", ["findMany", findMany]);
|
|
1609
2313
|
methods.set("exists", ["exists", exists]);
|
|
1610
2314
|
methods.set("findPage", ["findPage", findPage]);
|
|
1611
2315
|
methods.set("findCursor", ["findCursor", findCursor]);
|
|
2316
|
+
methods.set("explain", ["explain", explain]);
|
|
2317
|
+
methods.set("increment", ["increment", increment]);
|
|
2318
|
+
methods.set("forEachPage", ["forEachPage", forEachPage]);
|
|
1612
2319
|
|
|
1613
2320
|
class DeclaredRepository extends AbstractRepository2 {
|
|
1614
2321
|
}
|
|
@@ -1617,6 +2324,12 @@ function repositoryOf2(settings) {
|
|
|
1617
2324
|
if (!method)
|
|
1618
2325
|
invalid3(name, unknownAction(recordNameOf2(entity), String(name_), methods));
|
|
1619
2326
|
const [kind, implementation] = method;
|
|
2327
|
+
if (kind === "restore" && deletion !== "soft") {
|
|
2328
|
+
invalid3(name, '"restore" needs deletion to be "soft".');
|
|
2329
|
+
}
|
|
2330
|
+
if (kind === "findCursor" && settings.provider !== undefined && provider[Kind2] !== "provider-slot" && provider.capabilities?.keyset !== true) {
|
|
2331
|
+
invalid3(name, '"findCursor" needs a provider that reads by cursor (capabilities.keyset).');
|
|
2332
|
+
}
|
|
1620
2333
|
const needs = deletion === "soft" && kind === "delete" ? ["findOne", "update"] : NEEDS[kind] ?? [];
|
|
1621
2334
|
for (const need of needs) {
|
|
1622
2335
|
if (typeof provider?.[need] !== "function") {
|
|
@@ -1630,7 +2343,7 @@ function repositoryOf2(settings) {
|
|
|
1630
2343
|
});
|
|
1631
2344
|
}
|
|
1632
2345
|
Object.defineProperty(DeclaredRepository, "name", { value: name });
|
|
1633
|
-
|
|
2346
|
+
const state = {
|
|
1634
2347
|
name,
|
|
1635
2348
|
entity,
|
|
1636
2349
|
actions: Object.freeze([...new Set(actions)]),
|
|
@@ -1638,9 +2351,14 @@ function repositoryOf2(settings) {
|
|
|
1638
2351
|
provider,
|
|
1639
2352
|
explicitProvider: settings.provider !== undefined,
|
|
1640
2353
|
environment: environmentDependencies,
|
|
1641
|
-
relations
|
|
1642
|
-
|
|
1643
|
-
|
|
2354
|
+
relations,
|
|
2355
|
+
indexes,
|
|
2356
|
+
scans
|
|
2357
|
+
};
|
|
2358
|
+
states.set(DeclaredRepository, state);
|
|
2359
|
+
const repository = Object.freeze(new DeclaredRepository);
|
|
2360
|
+
declareRepository(state, repository);
|
|
2361
|
+
return repository;
|
|
1644
2362
|
}
|
|
1645
2363
|
function RepositoryOf2(repository) {
|
|
1646
2364
|
if (!isRepository2(repository)) {
|
|
@@ -1652,6 +2370,54 @@ function isRepository2(value) {
|
|
|
1652
2370
|
return value instanceof AbstractRepository2;
|
|
1653
2371
|
}
|
|
1654
2372
|
|
|
2373
|
+
// src/core/use-case/exceptions.ts
|
|
2374
|
+
var InvalidUseCaseStep2 = exceptionOf2({
|
|
2375
|
+
layer: "use-case" /* UseCase */,
|
|
2376
|
+
name: "InvalidUseCaseStep",
|
|
2377
|
+
message: ({
|
|
2378
|
+
index,
|
|
2379
|
+
reason
|
|
2380
|
+
}) => `Invalid step ${index}: ${reason}`
|
|
2381
|
+
});
|
|
2382
|
+
var InvalidUseCasePlugin2 = exceptionOf2({
|
|
2383
|
+
layer: "use-case" /* UseCase */,
|
|
2384
|
+
name: "InvalidUseCasePlugin",
|
|
2385
|
+
message: ({ reason }, source) => `Invalid use case plugin "${source}": ${reason}.`
|
|
2386
|
+
});
|
|
2387
|
+
var UnknownUseCasePlugin2 = exceptionOf2({
|
|
2388
|
+
layer: "use-case" /* UseCase */,
|
|
2389
|
+
name: "UnknownUseCasePlugin",
|
|
2390
|
+
message: ({ name }) => `No use case plugin "${name}" in the catalog.`
|
|
2391
|
+
});
|
|
2392
|
+
var IncompatibleUseCasePlugin2 = exceptionOf2({
|
|
2393
|
+
layer: "use-case" /* UseCase */,
|
|
2394
|
+
name: "IncompatibleUseCasePlugin",
|
|
2395
|
+
message: ({ reason }, source) => `Incompatible use case plugin "${source}": ${reason}.`
|
|
2396
|
+
});
|
|
2397
|
+
var UseCaseUnavailable2 = exceptionOf2({
|
|
2398
|
+
layer: "use-case" /* UseCase */,
|
|
2399
|
+
name: "UseCaseUnavailable",
|
|
2400
|
+
message: ({
|
|
2401
|
+
repository,
|
|
2402
|
+
reason
|
|
2403
|
+
}, source) => `The use case "${source}" is not available for "${repository}": ${reason}.`
|
|
2404
|
+
});
|
|
2405
|
+
var UseCaseEntityNotFound2 = exceptionOf2({
|
|
2406
|
+
layer: "use-case" /* UseCase */,
|
|
2407
|
+
name: "UseCaseEntityNotFound",
|
|
2408
|
+
message: "The entity was not found."
|
|
2409
|
+
});
|
|
2410
|
+
var UseCaseEntityConflict2 = exceptionOf2({
|
|
2411
|
+
layer: "use-case" /* UseCase */,
|
|
2412
|
+
name: "UseCaseEntityConflict",
|
|
2413
|
+
message: "The entity already exists."
|
|
2414
|
+
});
|
|
2415
|
+
var UseCaseInvalidCredentials2 = exceptionOf2({
|
|
2416
|
+
layer: "use-case" /* UseCase */,
|
|
2417
|
+
name: "UseCaseInvalidCredentials",
|
|
2418
|
+
message: "Invalid credentials."
|
|
2419
|
+
});
|
|
2420
|
+
|
|
1655
2421
|
// src/core/use-case/steps/references.ts
|
|
1656
2422
|
var RESERVED_NAMES = [
|
|
1657
2423
|
"input",
|
|
@@ -1759,15 +2525,103 @@ function freezeData(value) {
|
|
|
1759
2525
|
return value;
|
|
1760
2526
|
}
|
|
1761
2527
|
|
|
1762
|
-
// src/core/
|
|
1763
|
-
var
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
}
|
|
2528
|
+
// src/core/property/input.ts
|
|
2529
|
+
var PropertyInput2 = Symbol("propertyInput");
|
|
2530
|
+
function propertyInputOf2(field) {
|
|
2531
|
+
const property = fieldOf(field);
|
|
2532
|
+
if (!isProperty2(property))
|
|
2533
|
+
return;
|
|
2534
|
+
const declared = property[PropertyInput2];
|
|
2535
|
+
return typeof declared === "object" && declared !== null && isProperty2(declared.accepts) && typeof declared.convert === "function" ? declared : undefined;
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
// src/core/use-case/target.ts
|
|
2539
|
+
function isOptionalField(field) {
|
|
2540
|
+
if (isProperty2(field))
|
|
2541
|
+
return field.type.optional === true;
|
|
2542
|
+
const record = recordFieldOf2(field);
|
|
2543
|
+
if (record)
|
|
2544
|
+
return record.optional;
|
|
2545
|
+
return field.optional === true;
|
|
2546
|
+
}
|
|
2547
|
+
function planOf(entity, key, owners) {
|
|
2548
|
+
const field = fieldOf(entity.blueprint.properties[key]);
|
|
2549
|
+
const optional = isOptionalField(field);
|
|
2550
|
+
const record = recordFieldOf2(field);
|
|
2551
|
+
const item = isBlueprintList2(field) ? recordFieldOf2(field.item) : undefined;
|
|
2552
|
+
const related = [record?.record, item?.record].find(isEntityClass2);
|
|
2553
|
+
const kind = record ? related ? "entity" : "record" : isBlueprintList2(field) ? related ? "entity-list" : "list" : isBlueprint2(field) ? "blueprint" : "property";
|
|
2554
|
+
let input = { key, field };
|
|
2555
|
+
if (kind === "entity" && related) {
|
|
2556
|
+
const id = related.blueprint.properties.id;
|
|
2557
|
+
input = { key: `${key}Id`, field: optional ? optionalOf2(id) : id };
|
|
2558
|
+
}
|
|
2559
|
+
const declared = kind === "property" ? propertyInputOf2(field) : undefined;
|
|
2560
|
+
if (declared) {
|
|
2561
|
+
input = {
|
|
2562
|
+
key,
|
|
2563
|
+
field: optional ? optionalOf2(declared.accepts) : declared.accepts
|
|
2564
|
+
};
|
|
2565
|
+
}
|
|
2566
|
+
return Object.freeze({
|
|
2567
|
+
key,
|
|
2568
|
+
field,
|
|
2569
|
+
identity: identityKeysOf(entity).includes(key),
|
|
2570
|
+
plugin: owners.get(key),
|
|
2571
|
+
sensitive: entity.options.sensitive.includes(key),
|
|
2572
|
+
unique: entity.options.unique.includes(key),
|
|
2573
|
+
optional,
|
|
2574
|
+
change: entity.options.change.includes(key),
|
|
2575
|
+
kind,
|
|
2576
|
+
related,
|
|
2577
|
+
input: Object.freeze(input),
|
|
2578
|
+
convert: declared ? (value) => value === undefined ? undefined : declared.convert(value) : undefined
|
|
2579
|
+
});
|
|
2580
|
+
}
|
|
2581
|
+
function useCaseTargetOf2(repository, source = "useCaseTargetOf") {
|
|
2582
|
+
const state = findState(repository);
|
|
2583
|
+
if (!state)
|
|
2584
|
+
throw new TypeError("Expected a repository made by repositoryOf");
|
|
2585
|
+
const { entity } = state;
|
|
2586
|
+
const unavailable = (reason) => {
|
|
2587
|
+
throw new UseCaseUnavailable2({
|
|
2588
|
+
source,
|
|
2589
|
+
details: { repository: state.name, reason }
|
|
2590
|
+
});
|
|
2591
|
+
};
|
|
2592
|
+
const owners = new Map;
|
|
2593
|
+
for (const plugin of pluginsOf2(entity)) {
|
|
2594
|
+
for (const key of plugin.keys)
|
|
2595
|
+
owners.set(key, plugin.name);
|
|
2596
|
+
}
|
|
2597
|
+
const fields = Object.freeze(Object.keys(entity.blueprint.properties).map((key) => planOf(entity, key, owners)));
|
|
2598
|
+
const field = (key) => fields.find((plan) => plan.key === key) ?? unavailable(`the entity has no key "${key}"`);
|
|
2599
|
+
return Object.freeze({
|
|
2600
|
+
repository,
|
|
2601
|
+
repositoryName: state.name,
|
|
2602
|
+
entity,
|
|
2603
|
+
entityName: recordNameOf2(entity),
|
|
2604
|
+
actions: state.actions,
|
|
2605
|
+
deletion: state.deletion,
|
|
2606
|
+
fields,
|
|
2607
|
+
changeKeys: entity.options.change,
|
|
2608
|
+
uniqueKeys: entity.options.unique,
|
|
2609
|
+
field,
|
|
2610
|
+
relation(key, actions = []) {
|
|
2611
|
+
const { related } = field(key);
|
|
2612
|
+
const found = related ? relatedRepositoryOf(repository, related) : undefined;
|
|
2613
|
+
if (!found) {
|
|
2614
|
+
return unavailable(`the repository has no relation for the entity of "${key}"`);
|
|
2615
|
+
}
|
|
2616
|
+
const missing = actions.filter((action) => !found.actions.includes(action));
|
|
2617
|
+
if (missing.length > 0) {
|
|
2618
|
+
unavailable(`the repository of "${key}" lacks the action ${missing.map((action) => `"${action}"`).join(", ")}`);
|
|
2619
|
+
}
|
|
2620
|
+
return found;
|
|
2621
|
+
},
|
|
2622
|
+
unavailable
|
|
2623
|
+
});
|
|
2624
|
+
}
|
|
1771
2625
|
|
|
1772
2626
|
// src/core/use-case/steps/run.ts
|
|
1773
2627
|
var FORBIDDEN_METHODS = new Set([
|
|
@@ -1801,7 +2655,6 @@ async function executeSteps(dependencies = {}) {
|
|
|
1801
2655
|
return followPath(named.get(root), path);
|
|
1802
2656
|
};
|
|
1803
2657
|
const value = (raw) => resolveValue(raw, lookup);
|
|
1804
|
-
const list = (raw) => raw === undefined ? [] : value(raw);
|
|
1805
2658
|
for (const [index, step] of steps.entries()) {
|
|
1806
2659
|
const invalid = (reason) => {
|
|
1807
2660
|
throw new InvalidUseCaseStep2({
|
|
@@ -1809,6 +2662,14 @@ async function executeSteps(dependencies = {}) {
|
|
|
1809
2662
|
details: { index, reason, code: "invalid_target" }
|
|
1810
2663
|
});
|
|
1811
2664
|
};
|
|
2665
|
+
const list = (raw) => {
|
|
2666
|
+
if (raw === undefined)
|
|
2667
|
+
return [];
|
|
2668
|
+
const resolved = value(raw);
|
|
2669
|
+
if (!Array.isArray(resolved))
|
|
2670
|
+
invalid("args must resolve to a list.");
|
|
2671
|
+
return resolved;
|
|
2672
|
+
};
|
|
1812
2673
|
const bind = (result) => {
|
|
1813
2674
|
if (typeof step.as === "string")
|
|
1814
2675
|
named.set(step.as, result);
|
|
@@ -1853,7 +2714,10 @@ async function executeSteps(dependencies = {}) {
|
|
|
1853
2714
|
if (typeof namespace !== "object" || namespace === null || typeof namespace[key] !== "function") {
|
|
1854
2715
|
invalid(`"${key}" is not a change key of the record.`);
|
|
1855
2716
|
}
|
|
1856
|
-
|
|
2717
|
+
const changed = value(step.value);
|
|
2718
|
+
if (step.ifPresent !== true || changed !== undefined) {
|
|
2719
|
+
await namespace[key]?.(changed);
|
|
2720
|
+
}
|
|
1857
2721
|
bind(record);
|
|
1858
2722
|
} else if (Object.hasOwn(step, "invoke")) {
|
|
1859
2723
|
const record = target("invoke");
|
|
@@ -1867,9 +2731,9 @@ async function executeSteps(dependencies = {}) {
|
|
|
1867
2731
|
bind(await child(value(step.input)));
|
|
1868
2732
|
} else if (Object.hasOwn(step, "run")) {
|
|
1869
2733
|
const fn = step.fn;
|
|
1870
|
-
bind(await fn(...list(step.args), { context: this.context }));
|
|
2734
|
+
bind(await fn(...list(step.args), { context: this.context, dependencies }));
|
|
1871
2735
|
} else {
|
|
1872
|
-
const action = ["create", "update", "delete"].find((kind) => Object.hasOwn(step, kind));
|
|
2736
|
+
const action = ["create", "update", "delete", "restore"].find((kind) => Object.hasOwn(step, kind));
|
|
1873
2737
|
const methods = dependencies[step[action]];
|
|
1874
2738
|
bind(await methods[action].call(methods, value(step.value)));
|
|
1875
2739
|
}
|
|
@@ -1889,11 +2753,15 @@ var SHAPES = {
|
|
|
1889
2753
|
optional: ["other", "details"]
|
|
1890
2754
|
},
|
|
1891
2755
|
build: { required: ["build", "values", "as"], optional: [] },
|
|
1892
|
-
change: {
|
|
2756
|
+
change: {
|
|
2757
|
+
required: ["change", "key", "value"],
|
|
2758
|
+
optional: ["ifPresent", "as"]
|
|
2759
|
+
},
|
|
1893
2760
|
invoke: { required: ["invoke", "method"], optional: ["args", "as"] },
|
|
1894
2761
|
create: { required: ["create", "value"], optional: ["as"] },
|
|
1895
2762
|
update: { required: ["update", "value"], optional: ["as"] },
|
|
1896
2763
|
delete: { required: ["delete", "value"], optional: ["as"] },
|
|
2764
|
+
restore: { required: ["restore", "value"], optional: ["as"] },
|
|
1897
2765
|
call: { required: ["call", "input"], optional: ["as"] },
|
|
1898
2766
|
run: { required: ["run", "fn"], optional: ["args", "as"] },
|
|
1899
2767
|
return: { required: ["return"], optional: [] }
|
|
@@ -1908,7 +2776,13 @@ var ENSURE_KINDS = new Set([
|
|
|
1908
2776
|
"false"
|
|
1909
2777
|
]);
|
|
1910
2778
|
var READ_ACTION = /^(?:findBy.+|findManyBy.+|findMany|exists|findPage|findCursor|count)$/;
|
|
1911
|
-
var WRITE_ACTIONS = new Set([
|
|
2779
|
+
var WRITE_ACTIONS = new Set([
|
|
2780
|
+
"create",
|
|
2781
|
+
"update",
|
|
2782
|
+
"delete",
|
|
2783
|
+
"restore",
|
|
2784
|
+
"increment"
|
|
2785
|
+
]);
|
|
1912
2786
|
function validateSteps(declaration, steps) {
|
|
1913
2787
|
const { name } = declaration;
|
|
1914
2788
|
const fail = (index, code, reason) => {
|
|
@@ -1964,8 +2838,12 @@ function validateSteps(declaration, steps) {
|
|
|
1964
2838
|
const checkArgs = (index, args) => {
|
|
1965
2839
|
if (args === undefined)
|
|
1966
2840
|
return;
|
|
2841
|
+
if (typeof args === "string" && args.startsWith("$")) {
|
|
2842
|
+
checkReference(index, args);
|
|
2843
|
+
return;
|
|
2844
|
+
}
|
|
1967
2845
|
if (!Array.isArray(args)) {
|
|
1968
|
-
fail(index, "invalid_step", "args must be a list of values.");
|
|
2846
|
+
fail(index, "invalid_step", "args must be a list of values or a reference to one.");
|
|
1969
2847
|
}
|
|
1970
2848
|
checkValue(index, args, "args");
|
|
1971
2849
|
};
|
|
@@ -2026,6 +2904,9 @@ function validateSteps(declaration, steps) {
|
|
|
2026
2904
|
if (WRITE_ACTIONS.has(action)) {
|
|
2027
2905
|
fail(index, "write_in_find", `find cannot call "${action}".`);
|
|
2028
2906
|
}
|
|
2907
|
+
if (action === "forEachPage") {
|
|
2908
|
+
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.');
|
|
2909
|
+
}
|
|
2029
2910
|
if (!READ_ACTION.test(action)) {
|
|
2030
2911
|
fail(index, "unknown_action", `"${action}" is not a read action.`);
|
|
2031
2912
|
}
|
|
@@ -2072,6 +2953,9 @@ function validateSteps(declaration, steps) {
|
|
|
2072
2953
|
if (typeof record.key !== "string" || record.key === "") {
|
|
2073
2954
|
fail(index, "invalid_step", "change.key must be a string.");
|
|
2074
2955
|
}
|
|
2956
|
+
if (record.ifPresent !== undefined && typeof record.ifPresent !== "boolean") {
|
|
2957
|
+
fail(index, "invalid_step", "change.ifPresent must be a boolean.");
|
|
2958
|
+
}
|
|
2075
2959
|
const reference = parseReference(record.change);
|
|
2076
2960
|
const known = reference && reference.path.length === 0 ? bound.get(reference.root)?.record : undefined;
|
|
2077
2961
|
if (known) {
|
|
@@ -2094,7 +2978,8 @@ function validateSteps(declaration, steps) {
|
|
|
2094
2978
|
}
|
|
2095
2979
|
case "create":
|
|
2096
2980
|
case "update":
|
|
2097
|
-
case "delete":
|
|
2981
|
+
case "delete":
|
|
2982
|
+
case "restore": {
|
|
2098
2983
|
repositoryOf(index, record[kind], kind);
|
|
2099
2984
|
checkValue(index, record.value, "value");
|
|
2100
2985
|
break;
|
|
@@ -2513,6 +3398,195 @@ function isUseCase2(value) {
|
|
|
2513
3398
|
return value instanceof AbstractUseCase2;
|
|
2514
3399
|
}
|
|
2515
3400
|
|
|
3401
|
+
// src/core/use-case/plugin.ts
|
|
3402
|
+
var UseCaseOrigin = Symbol("useCaseOrigin");
|
|
3403
|
+
var NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
3404
|
+
var identities = new WeakMap;
|
|
3405
|
+
var nextIdentity = 0;
|
|
3406
|
+
function keyOf(value) {
|
|
3407
|
+
if (typeof value === "function" || typeof value === "object" && value) {
|
|
3408
|
+
if (Array.isArray(value))
|
|
3409
|
+
return `[${value.map(keyOf).join(",")}]`;
|
|
3410
|
+
if (isPlain2(value)) {
|
|
3411
|
+
return `{${Object.keys(value).sort().filter((key) => value[key] !== undefined).map((key) => `${JSON.stringify(key)}:${keyOf(value[key])}`).join(",")}}`;
|
|
3412
|
+
}
|
|
3413
|
+
let identity = identities.get(value);
|
|
3414
|
+
if (identity === undefined) {
|
|
3415
|
+
identity = nextIdentity++;
|
|
3416
|
+
identities.set(value, identity);
|
|
3417
|
+
}
|
|
3418
|
+
return `#${identity}`;
|
|
3419
|
+
}
|
|
3420
|
+
return value === undefined ? "undefined" : JSON.stringify(value);
|
|
3421
|
+
}
|
|
3422
|
+
function isUseCasePlugin2(value) {
|
|
3423
|
+
return typeof value === "object" && value !== null && value[Kind2] === "use-case-plugin";
|
|
3424
|
+
}
|
|
3425
|
+
function useCaseOriginOf2(UseCase) {
|
|
3426
|
+
return typeof UseCase === "function" ? UseCase[UseCaseOrigin] : undefined;
|
|
3427
|
+
}
|
|
3428
|
+
function useCasePluginOf2(name, settings) {
|
|
3429
|
+
const source = typeof name === "string" ? name : "useCasePluginOf";
|
|
3430
|
+
const invalid = (reason) => {
|
|
3431
|
+
throw new InvalidUseCasePlugin2({ source, details: { reason } });
|
|
3432
|
+
};
|
|
3433
|
+
if (typeof name !== "string" || !NAME.test(name)) {
|
|
3434
|
+
invalid("its name must be an identifier");
|
|
3435
|
+
}
|
|
3436
|
+
if (!isPlain2(settings) || typeof settings.compile !== "function") {
|
|
3437
|
+
invalid("it needs a compile function");
|
|
3438
|
+
}
|
|
3439
|
+
const { version = "1.0.0", description, compile } = settings;
|
|
3440
|
+
if (!isVersion(version)) {
|
|
3441
|
+
invalid(`"${String(version)}" is not a semantic version`);
|
|
3442
|
+
}
|
|
3443
|
+
if (description !== undefined && typeof description !== "string") {
|
|
3444
|
+
invalid("its description must be a string");
|
|
3445
|
+
}
|
|
3446
|
+
const names = (list, what) => {
|
|
3447
|
+
if (list === undefined)
|
|
3448
|
+
return Object.freeze([]);
|
|
3449
|
+
if (!Array.isArray(list) || list.some((item) => typeof item !== "string" || item === "")) {
|
|
3450
|
+
invalid(`requires.${what} must be a list of names`);
|
|
3451
|
+
}
|
|
3452
|
+
return Object.freeze([...new Set(list)]);
|
|
3453
|
+
};
|
|
3454
|
+
if (settings.requires !== undefined && !isPlain2(settings.requires)) {
|
|
3455
|
+
invalid("requires must be an object of actions and plugins");
|
|
3456
|
+
}
|
|
3457
|
+
const requires = Object.freeze({
|
|
3458
|
+
actions: names(settings.requires?.actions, "actions"),
|
|
3459
|
+
plugins: names(settings.requires?.plugins, "plugins")
|
|
3460
|
+
});
|
|
3461
|
+
const made = new WeakMap;
|
|
3462
|
+
const make = (repository, given) => {
|
|
3463
|
+
if (!isRepository2(repository)) {
|
|
3464
|
+
return invalid("it makes a use case for a repository of repositoryOf");
|
|
3465
|
+
}
|
|
3466
|
+
if (given !== undefined && !isPlain2(given)) {
|
|
3467
|
+
return invalid("its options must be a plain object");
|
|
3468
|
+
}
|
|
3469
|
+
const options = Object.freeze({ ...given });
|
|
3470
|
+
const info = inspectRepository(repository);
|
|
3471
|
+
let classes = made.get(info.state);
|
|
3472
|
+
if (!classes) {
|
|
3473
|
+
classes = new Map;
|
|
3474
|
+
made.set(info.state, classes);
|
|
3475
|
+
}
|
|
3476
|
+
const key = keyOf(options);
|
|
3477
|
+
const known = classes.get(key);
|
|
3478
|
+
if (known)
|
|
3479
|
+
return known;
|
|
3480
|
+
const target = useCaseTargetOf2(repository, name);
|
|
3481
|
+
const missing = {
|
|
3482
|
+
actions: requires.actions.filter((action) => !info.actions.includes(action)),
|
|
3483
|
+
plugins: requires.plugins.filter((plugin) => !hasPlugin2(info.entity, plugin))
|
|
3484
|
+
};
|
|
3485
|
+
if (missing.actions.length > 0 || missing.plugins.length > 0) {
|
|
3486
|
+
const quoted = (list) => list.map((item) => `"${item}"`).join(", ");
|
|
3487
|
+
throw new UseCaseUnavailable2({
|
|
3488
|
+
source: name,
|
|
3489
|
+
details: {
|
|
3490
|
+
repository: target.repositoryName,
|
|
3491
|
+
reason: [
|
|
3492
|
+
missing.actions.length > 0 ? `the repository lacks the action ${quoted(missing.actions)}` : undefined,
|
|
3493
|
+
missing.plugins.length > 0 ? `the entity lacks the plugin ${quoted(missing.plugins)}` : undefined
|
|
3494
|
+
].filter(Boolean).join(" and "),
|
|
3495
|
+
missing
|
|
3496
|
+
}
|
|
3497
|
+
});
|
|
3498
|
+
}
|
|
3499
|
+
const UseCase = useCaseOf2(compile(target, options));
|
|
3500
|
+
Object.defineProperty(UseCase, UseCaseOrigin, {
|
|
3501
|
+
value: Object.freeze({
|
|
3502
|
+
plugin,
|
|
3503
|
+
repository,
|
|
3504
|
+
options
|
|
3505
|
+
})
|
|
3506
|
+
});
|
|
3507
|
+
classes.set(key, UseCase);
|
|
3508
|
+
return UseCase;
|
|
3509
|
+
};
|
|
3510
|
+
const plugin = Object.freeze({
|
|
3511
|
+
[Kind2]: "use-case-plugin",
|
|
3512
|
+
name,
|
|
3513
|
+
version,
|
|
3514
|
+
...description === undefined ? {} : { description },
|
|
3515
|
+
requires,
|
|
3516
|
+
make,
|
|
3517
|
+
toJSON: () => ({
|
|
3518
|
+
kind: "use-case-plugin",
|
|
3519
|
+
name,
|
|
3520
|
+
version,
|
|
3521
|
+
...description === undefined ? {} : { description },
|
|
3522
|
+
requires
|
|
3523
|
+
})
|
|
3524
|
+
});
|
|
3525
|
+
return plugin;
|
|
3526
|
+
}
|
|
3527
|
+
|
|
3528
|
+
// src/core/use-case/plugin-catalog.ts
|
|
3529
|
+
function isUseCaseCatalog2(value) {
|
|
3530
|
+
return typeof value === "object" && value !== null && value[Kind2] === "use-case-catalog";
|
|
3531
|
+
}
|
|
3532
|
+
function useCaseCatalogOf2(...plugins) {
|
|
3533
|
+
const byName = {};
|
|
3534
|
+
const context = {};
|
|
3535
|
+
for (const plugin of plugins) {
|
|
3536
|
+
if (!isUseCasePlugin2(plugin)) {
|
|
3537
|
+
throw new InvalidUseCasePlugin2({
|
|
3538
|
+
source: "catalog",
|
|
3539
|
+
details: {
|
|
3540
|
+
reason: "a catalog takes plugins made by `useCasePluginOf`"
|
|
3541
|
+
}
|
|
3542
|
+
});
|
|
3543
|
+
}
|
|
3544
|
+
if (Object.hasOwn(byName, plugin.name)) {
|
|
3545
|
+
throw new InvalidUseCasePlugin2({
|
|
3546
|
+
source: plugin.name,
|
|
3547
|
+
details: { reason: "the catalog has another plugin with its name" }
|
|
3548
|
+
});
|
|
3549
|
+
}
|
|
3550
|
+
byName[plugin.name] = plugin;
|
|
3551
|
+
context[plugin.name] = plugin.make;
|
|
3552
|
+
}
|
|
3553
|
+
const find = (name) => {
|
|
3554
|
+
const plugin = Object.hasOwn(byName, name) ? byName[name] : undefined;
|
|
3555
|
+
if (!plugin) {
|
|
3556
|
+
throw new UnknownUseCasePlugin2({ source: "catalog", details: { name } });
|
|
3557
|
+
}
|
|
3558
|
+
return plugin;
|
|
3559
|
+
};
|
|
3560
|
+
return Object.freeze({
|
|
3561
|
+
[Kind2]: "use-case-catalog",
|
|
3562
|
+
plugins: Object.freeze(byName),
|
|
3563
|
+
list: Object.freeze([...plugins]),
|
|
3564
|
+
context: Object.freeze(context),
|
|
3565
|
+
resolve(reference) {
|
|
3566
|
+
const at = typeof reference === "string" ? reference.lastIndexOf("@") : -1;
|
|
3567
|
+
if (at <= 0)
|
|
3568
|
+
return find(String(reference));
|
|
3569
|
+
const plugin = find(reference.slice(0, at));
|
|
3570
|
+
const version = reference.slice(at + 1);
|
|
3571
|
+
if (!isVersion(version)) {
|
|
3572
|
+
throw new IncompatibleUseCasePlugin2({
|
|
3573
|
+
source: plugin.name,
|
|
3574
|
+
details: { reason: `"${version}" is not a semantic version` }
|
|
3575
|
+
});
|
|
3576
|
+
}
|
|
3577
|
+
if (!satisfies(plugin.version, version)) {
|
|
3578
|
+
throw new IncompatibleUseCasePlugin2({
|
|
3579
|
+
source: plugin.name,
|
|
3580
|
+
details: {
|
|
3581
|
+
reason: `version ${plugin.version} does not satisfy ${version}`
|
|
3582
|
+
}
|
|
3583
|
+
});
|
|
3584
|
+
}
|
|
3585
|
+
return plugin;
|
|
3586
|
+
}
|
|
3587
|
+
});
|
|
3588
|
+
}
|
|
3589
|
+
|
|
2516
3590
|
// src/core/command/exceptions.ts
|
|
2517
3591
|
var InvalidCommandDeclaration2 = exceptionOf2({
|
|
2518
3592
|
layer: "command" /* Command */,
|
|
@@ -2667,9 +3741,12 @@ function callEnvironment(source, environments, given) {
|
|
|
2667
3741
|
}
|
|
2668
3742
|
return { byEnvironment, merged: Object.freeze(merged) };
|
|
2669
3743
|
}
|
|
2670
|
-
function
|
|
2671
|
-
const { name, description, context,
|
|
3744
|
+
function declareCommand(given, generics) {
|
|
3745
|
+
const { name, description, context, onEventError } = given;
|
|
2672
3746
|
const source = typeof name === "string" ? name : "commandOf";
|
|
3747
|
+
const picked = given.useCases;
|
|
3748
|
+
const useCases = typeof picked === "function" && !isUseCaseClass2(picked) ? guard2({ layer: "command" /* Command */, source }, () => picked(generics)) : picked;
|
|
3749
|
+
const settings = given;
|
|
2673
3750
|
const invalid = (reason) => {
|
|
2674
3751
|
throw new InvalidCommandDeclaration2({ source, details: { reason } });
|
|
2675
3752
|
};
|
|
@@ -2687,7 +3764,7 @@ function commandOf2(settings) {
|
|
|
2687
3764
|
invalid("Expected context to be a non-optional blueprint.");
|
|
2688
3765
|
}
|
|
2689
3766
|
if (typeof useCases !== "object" || useCases === null || Array.isArray(useCases) || Reflect.ownKeys(useCases).length === 0) {
|
|
2690
|
-
invalid("Expected useCases to be a non-empty object of use case classes.");
|
|
3767
|
+
invalid("Expected useCases to be a non-empty object of use case classes, or a function returning one.");
|
|
2691
3768
|
}
|
|
2692
3769
|
for (const key of Reflect.ownKeys(useCases)) {
|
|
2693
3770
|
if (typeof key !== "string" || !IDENTIFIER2.test(key) || !isUseCaseClass2(useCases[key])) {
|
|
@@ -2825,6 +3902,19 @@ function commandOf2(settings) {
|
|
|
2825
3902
|
verifiers.set(command, providerOfCall);
|
|
2826
3903
|
return command;
|
|
2827
3904
|
}
|
|
3905
|
+
function commandBuilderOf2(...catalogs) {
|
|
3906
|
+
if (!catalogs.every(isUseCaseCatalog2)) {
|
|
3907
|
+
throw new InvalidCommandDeclaration2({
|
|
3908
|
+
source: "commandBuilderOf",
|
|
3909
|
+
details: {
|
|
3910
|
+
reason: "Expected catalogs made by useCaseCatalogOf."
|
|
3911
|
+
}
|
|
3912
|
+
});
|
|
3913
|
+
}
|
|
3914
|
+
const generics = Object.freeze(Object.assign({}, ...catalogs.map((catalog) => catalog.context)));
|
|
3915
|
+
return (settings) => declareCommand(settings, generics);
|
|
3916
|
+
}
|
|
3917
|
+
var commandOf = commandBuilderOf2();
|
|
2828
3918
|
async function applyPolicy(command, policy, onEventError, call) {
|
|
2829
3919
|
const { operation, raised } = call;
|
|
2830
3920
|
const source = `${command.commandName}.${operation}`;
|
|
@@ -2837,12 +3927,16 @@ async function applyPolicy(command, policy, onEventError, call) {
|
|
|
2837
3927
|
get env() {
|
|
2838
3928
|
return call.env();
|
|
2839
3929
|
},
|
|
2840
|
-
logger: call.logger
|
|
3930
|
+
logger: call.logger,
|
|
3931
|
+
publisher: (name) => resolvePublisher(command, name, source)
|
|
2841
3932
|
};
|
|
2842
3933
|
if (typeof policy === "function") {
|
|
2843
3934
|
try {
|
|
2844
3935
|
await policy(raised, info);
|
|
2845
3936
|
} catch (cause) {
|
|
3937
|
+
if (cause instanceof Exception2 && cause.name === "CommandEventFailed") {
|
|
3938
|
+
throw cause;
|
|
3939
|
+
}
|
|
2846
3940
|
throw new CommandEventFailed2({
|
|
2847
3941
|
source,
|
|
2848
3942
|
cause,
|
|
@@ -2888,6 +3982,81 @@ function isCommand2(value) {
|
|
|
2888
3982
|
return typeof value === "object" && value !== null && value[Kind2] === "command";
|
|
2889
3983
|
}
|
|
2890
3984
|
|
|
3985
|
+
// src/core/event-policy/event-policy.ts
|
|
3986
|
+
var described = new WeakMap;
|
|
3987
|
+
var factories = new WeakMap;
|
|
3988
|
+
function invalid4(source, reason) {
|
|
3989
|
+
throw new InvalidEventPolicy2({ source, details: { reason } });
|
|
3990
|
+
}
|
|
3991
|
+
function optionsOf(name, given, validate) {
|
|
3992
|
+
const options = frozenJson(given ?? {}, "Event policy options", (reason) => invalid4(name, reason));
|
|
3993
|
+
if (options === null || typeof options !== "object" || Array.isArray(options)) {
|
|
3994
|
+
invalid4(name, "Event policy options must be an object.");
|
|
3995
|
+
}
|
|
3996
|
+
const reason = validate?.(options);
|
|
3997
|
+
if (reason !== undefined)
|
|
3998
|
+
invalid4(name, reason);
|
|
3999
|
+
return options;
|
|
4000
|
+
}
|
|
4001
|
+
function eventPolicyOf2(settings) {
|
|
4002
|
+
const name = settings?.name;
|
|
4003
|
+
if (typeof name !== "string" || !/^[A-Za-z_$][\w$]*$/.test(name)) {
|
|
4004
|
+
invalid4("eventPolicyOf", "Expected the policy name to be an identifier.");
|
|
4005
|
+
}
|
|
4006
|
+
if (typeof settings.handle !== "function") {
|
|
4007
|
+
invalid4(name, "Expected handle to be a function.");
|
|
4008
|
+
}
|
|
4009
|
+
if (settings.publishers !== undefined && typeof settings.publishers !== "function") {
|
|
4010
|
+
invalid4(name, "Expected publishers to be a function.");
|
|
4011
|
+
}
|
|
4012
|
+
const { handle, validate, publishers } = settings;
|
|
4013
|
+
const factory = (given) => {
|
|
4014
|
+
const options = optionsOf(name, given, validate);
|
|
4015
|
+
const handler = (events, info) => handle(events, info, options);
|
|
4016
|
+
Object.defineProperty(handler, "name", { value: name });
|
|
4017
|
+
described.set(handler, {
|
|
4018
|
+
description: Object.freeze({
|
|
4019
|
+
kind: "policy",
|
|
4020
|
+
name,
|
|
4021
|
+
options
|
|
4022
|
+
}),
|
|
4023
|
+
publishers: Object.freeze([...publishers?.(options) ?? []])
|
|
4024
|
+
});
|
|
4025
|
+
return handler;
|
|
4026
|
+
};
|
|
4027
|
+
factories.set(factory, name);
|
|
4028
|
+
return factory;
|
|
4029
|
+
}
|
|
4030
|
+
function eventPolicyDescriptionOf2(handler) {
|
|
4031
|
+
return typeof handler === "function" ? described.get(handler)?.description : undefined;
|
|
4032
|
+
}
|
|
4033
|
+
function eventPolicyPublishersOf(handler) {
|
|
4034
|
+
return typeof handler === "function" ? described.get(handler)?.publishers ?? [] : [];
|
|
4035
|
+
}
|
|
4036
|
+
function eventPolicyCatalogOf2(...policies) {
|
|
4037
|
+
const byName = {};
|
|
4038
|
+
for (const factory of policies) {
|
|
4039
|
+
const name = factories.get(factory);
|
|
4040
|
+
if (name === undefined) {
|
|
4041
|
+
invalid4("catalog", "A catalog takes factories made by `eventPolicyOf`.");
|
|
4042
|
+
}
|
|
4043
|
+
if (Object.hasOwn(byName, name)) {
|
|
4044
|
+
invalid4(name, "The catalog has another event policy with its name.");
|
|
4045
|
+
}
|
|
4046
|
+
byName[name] = factory;
|
|
4047
|
+
}
|
|
4048
|
+
return Object.freeze({
|
|
4049
|
+
[Kind2]: "event-policy-catalog",
|
|
4050
|
+
policies: Object.freeze(byName),
|
|
4051
|
+
resolve(name, options) {
|
|
4052
|
+
const factory = Object.hasOwn(byName, name) ? byName[name] : undefined;
|
|
4053
|
+
if (!factory)
|
|
4054
|
+
invalid4("catalog", `Unknown event policy "${name}".`);
|
|
4055
|
+
return factory(options);
|
|
4056
|
+
}
|
|
4057
|
+
});
|
|
4058
|
+
}
|
|
4059
|
+
|
|
2891
4060
|
// src/core/openapi/exceptions.ts
|
|
2892
4061
|
var InvalidOpenAPIModel2 = exceptionOf2({
|
|
2893
4062
|
layer: "output" /* Output */,
|
|
@@ -2921,18 +4090,19 @@ function isExportedException(Declared) {
|
|
|
2921
4090
|
collected = true;
|
|
2922
4091
|
for (const module of [
|
|
2923
4092
|
exports_exceptions4,
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
4093
|
+
exports_exceptions3,
|
|
4094
|
+
exports_exceptions10,
|
|
4095
|
+
exports_exceptions7,
|
|
2927
4096
|
exports_environment,
|
|
2928
4097
|
exports_exceptions5,
|
|
2929
|
-
|
|
4098
|
+
exports_exceptions6,
|
|
2930
4099
|
exports_exceptions11,
|
|
2931
|
-
|
|
4100
|
+
exports_exceptions12,
|
|
4101
|
+
exports_exceptions2,
|
|
2932
4102
|
exports_exceptions,
|
|
2933
|
-
|
|
4103
|
+
exports_exceptions8,
|
|
2934
4104
|
exports_use_case,
|
|
2935
|
-
|
|
4105
|
+
exports_exceptions9
|
|
2936
4106
|
]) {
|
|
2937
4107
|
markExported(...Object.values(module));
|
|
2938
4108
|
}
|
|
@@ -2991,7 +4161,7 @@ function recordSchemaOf(record, scope) {
|
|
|
2991
4161
|
const { sensitive = [] } = record.options ?? {};
|
|
2992
4162
|
return objectOf(record.blueprint.properties, scope, {
|
|
2993
4163
|
writeOnly: sensitive,
|
|
2994
|
-
readOnly: isEntityClass2(record) ?
|
|
4164
|
+
readOnly: isEntityClass2(record) ? identityKeysOf(record) : []
|
|
2995
4165
|
});
|
|
2996
4166
|
}
|
|
2997
4167
|
function schemaOf(field, scope) {
|
|
@@ -3052,34 +4222,34 @@ function toOpenAPI2(modelOrModels, { refPrefix = "#/components/schemas/" } = {})
|
|
|
3052
4222
|
|
|
3053
4223
|
// src/core/output/stage.ts
|
|
3054
4224
|
var descriptions2 = new WeakMap;
|
|
3055
|
-
var
|
|
4225
|
+
var factories2 = new WeakMap;
|
|
3056
4226
|
var opaque2 = Object.freeze({ kind: "opaque" });
|
|
3057
|
-
function
|
|
4227
|
+
function invalid5(source, reason) {
|
|
3058
4228
|
throw new InvalidOutputStage2({ source, details: { reason } });
|
|
3059
4229
|
}
|
|
3060
|
-
function
|
|
3061
|
-
const options = frozenJson(given ?? {}, "Stage options", (reason) =>
|
|
4230
|
+
function optionsOf2(name, given, validate) {
|
|
4231
|
+
const options = frozenJson(given ?? {}, "Stage options", (reason) => invalid5(name, reason));
|
|
3062
4232
|
if (options === null || typeof options !== "object" || Array.isArray(options)) {
|
|
3063
|
-
|
|
4233
|
+
invalid5(name, "Stage options must be an object.");
|
|
3064
4234
|
}
|
|
3065
4235
|
const reason = validate?.(options);
|
|
3066
4236
|
if (reason !== undefined)
|
|
3067
|
-
|
|
4237
|
+
invalid5(name, reason);
|
|
3068
4238
|
return options;
|
|
3069
4239
|
}
|
|
3070
4240
|
function checkName(source, name) {
|
|
3071
4241
|
if (typeof name !== "string" || !name.trim()) {
|
|
3072
|
-
|
|
4242
|
+
invalid5(source, "Expected the stage name to be a non-empty string.");
|
|
3073
4243
|
}
|
|
3074
4244
|
}
|
|
3075
4245
|
function transformOf2(settings) {
|
|
3076
4246
|
checkName("transformOf", settings?.name);
|
|
3077
4247
|
if (typeof settings.apply !== "function") {
|
|
3078
|
-
|
|
4248
|
+
invalid5(settings.name, "Expected apply to be a function.");
|
|
3079
4249
|
}
|
|
3080
4250
|
const { name, apply, validate } = settings;
|
|
3081
4251
|
const factory = (given) => {
|
|
3082
|
-
const options =
|
|
4252
|
+
const options = optionsOf2(name, given, validate);
|
|
3083
4253
|
const stage = Object.freeze({
|
|
3084
4254
|
kind: "transform",
|
|
3085
4255
|
name,
|
|
@@ -3093,17 +4263,17 @@ function transformOf2(settings) {
|
|
|
3093
4263
|
}));
|
|
3094
4264
|
return stage;
|
|
3095
4265
|
};
|
|
3096
|
-
|
|
4266
|
+
factories2.set(factory, { kind: "transform", name });
|
|
3097
4267
|
return factory;
|
|
3098
4268
|
}
|
|
3099
4269
|
function sinkOf2(settings) {
|
|
3100
4270
|
checkName("sinkOf", settings?.name);
|
|
3101
4271
|
if (typeof settings.build !== "function") {
|
|
3102
|
-
|
|
4272
|
+
invalid5(settings.name, "Expected build to be a function.");
|
|
3103
4273
|
}
|
|
3104
4274
|
const { name, build, validate } = settings;
|
|
3105
4275
|
const factory = (given) => {
|
|
3106
|
-
const options =
|
|
4276
|
+
const options = optionsOf2(name, given, validate);
|
|
3107
4277
|
const stage = Object.freeze({
|
|
3108
4278
|
kind: "sink",
|
|
3109
4279
|
name,
|
|
@@ -3117,14 +4287,14 @@ function sinkOf2(settings) {
|
|
|
3117
4287
|
}));
|
|
3118
4288
|
return stage;
|
|
3119
4289
|
};
|
|
3120
|
-
|
|
4290
|
+
factories2.set(factory, { kind: "sink", name });
|
|
3121
4291
|
return factory;
|
|
3122
4292
|
}
|
|
3123
4293
|
function stageDescriptionOf2(stage) {
|
|
3124
4294
|
return descriptions2.get(stage) ?? opaque2;
|
|
3125
4295
|
}
|
|
3126
4296
|
function stageKindOf2(factory) {
|
|
3127
|
-
return
|
|
4297
|
+
return factories2.get(factory)?.kind;
|
|
3128
4298
|
}
|
|
3129
4299
|
function isStage(value) {
|
|
3130
4300
|
if (typeof value !== "object" || value === null)
|
|
@@ -3135,12 +4305,12 @@ function isStage(value) {
|
|
|
3135
4305
|
function stageCatalogOf2(...stages) {
|
|
3136
4306
|
const byName = {};
|
|
3137
4307
|
for (const factory of stages) {
|
|
3138
|
-
const known =
|
|
4308
|
+
const known = factories2.get(factory);
|
|
3139
4309
|
if (!known) {
|
|
3140
|
-
|
|
4310
|
+
invalid5("catalog", "A catalog takes factories made by `transformOf` or `sinkOf`.");
|
|
3141
4311
|
}
|
|
3142
4312
|
if (Object.hasOwn(byName, known.name)) {
|
|
3143
|
-
|
|
4313
|
+
invalid5(known.name, "The catalog has another stage with its name.");
|
|
3144
4314
|
}
|
|
3145
4315
|
byName[known.name] = factory;
|
|
3146
4316
|
}
|
|
@@ -3151,7 +4321,7 @@ function stageCatalogOf2(...stages) {
|
|
|
3151
4321
|
resolve(name, options) {
|
|
3152
4322
|
const factory = Object.hasOwn(byName, name) ? byName[name] : undefined;
|
|
3153
4323
|
if (!factory)
|
|
3154
|
-
|
|
4324
|
+
invalid5("catalog", `Unknown stage "${name}".`);
|
|
3155
4325
|
return factory(options);
|
|
3156
4326
|
}
|
|
3157
4327
|
});
|
|
@@ -3161,38 +4331,38 @@ function stageCatalogOf2(...stages) {
|
|
|
3161
4331
|
function isApplicationLike(value) {
|
|
3162
4332
|
return typeof value === "object" && value !== null && value[Kind2] === "application";
|
|
3163
4333
|
}
|
|
3164
|
-
function
|
|
4334
|
+
function invalid6(source, reason) {
|
|
3165
4335
|
throw new InvalidOutputStage2({ source, details: { reason } });
|
|
3166
4336
|
}
|
|
3167
4337
|
var PATCHABLE = ["meta", "tags", "description", "deprecated", "invoke"];
|
|
3168
4338
|
function operationOf2(operation, patch) {
|
|
3169
4339
|
const source = operation?.id ?? "operationOf";
|
|
3170
4340
|
if (operation?.kind !== "operation" || operation.version !== 1) {
|
|
3171
|
-
|
|
4341
|
+
invalid6(source, "Expected an operation descriptor.");
|
|
3172
4342
|
}
|
|
3173
4343
|
if (typeof patch !== "object" || patch === null || Array.isArray(patch)) {
|
|
3174
|
-
|
|
4344
|
+
invalid6(source, "Expected the patch to be an object.");
|
|
3175
4345
|
}
|
|
3176
4346
|
for (const key of Reflect.ownKeys(patch)) {
|
|
3177
4347
|
if (typeof key !== "string" || !PATCHABLE.includes(key)) {
|
|
3178
|
-
|
|
4348
|
+
invalid6(source, `"${String(key)}" cannot be patched; only ${PATCHABLE.join(", ")} can.`);
|
|
3179
4349
|
}
|
|
3180
4350
|
}
|
|
3181
4351
|
const { meta, tags, description, deprecated, invoke } = patch;
|
|
3182
4352
|
if ("meta" in patch && (typeof meta !== "object" || meta === null || Array.isArray(meta))) {
|
|
3183
|
-
|
|
4353
|
+
invalid6(source, "Expected meta to be an object.");
|
|
3184
4354
|
}
|
|
3185
4355
|
if ("tags" in patch && (!Array.isArray(tags) || tags.some((tag) => typeof tag !== "string"))) {
|
|
3186
|
-
|
|
4356
|
+
invalid6(source, "Expected tags to be a list of strings.");
|
|
3187
4357
|
}
|
|
3188
4358
|
if ("description" in patch && description !== undefined && typeof description !== "string") {
|
|
3189
|
-
|
|
4359
|
+
invalid6(source, "Expected description to be a string.");
|
|
3190
4360
|
}
|
|
3191
4361
|
if ("deprecated" in patch && typeof deprecated !== "boolean") {
|
|
3192
|
-
|
|
4362
|
+
invalid6(source, "Expected deprecated to be a boolean.");
|
|
3193
4363
|
}
|
|
3194
4364
|
if ("invoke" in patch && typeof invoke !== "function") {
|
|
3195
|
-
|
|
4365
|
+
invalid6(source, "Expected invoke to be a function.");
|
|
3196
4366
|
}
|
|
3197
4367
|
return Object.freeze({
|
|
3198
4368
|
...operation,
|
|
@@ -3233,25 +4403,25 @@ function statusesOf(name, statuses) {
|
|
|
3233
4403
|
if (statuses === undefined)
|
|
3234
4404
|
return Object.freeze({});
|
|
3235
4405
|
if (typeof statuses !== "object" || statuses === null || Array.isArray(statuses)) {
|
|
3236
|
-
|
|
4406
|
+
invalid6(name, "Expected statuses to be a map of exception names to statuses.");
|
|
3237
4407
|
}
|
|
3238
4408
|
for (const [exception, status] of Object.entries(statuses)) {
|
|
3239
4409
|
if (!exception || typeof status !== "number" || !Number.isInteger(status) || status < 400 || status > 599) {
|
|
3240
|
-
|
|
4410
|
+
invalid6(name, `Expected the status of "${exception}" to be an integer between 400 and 599.`);
|
|
3241
4411
|
}
|
|
3242
4412
|
}
|
|
3243
4413
|
return Object.freeze({ ...statuses });
|
|
3244
4414
|
}
|
|
3245
4415
|
function checkPipe(name, pipe) {
|
|
3246
4416
|
if (!Array.isArray(pipe))
|
|
3247
|
-
|
|
4417
|
+
invalid6(name, "Expected pipe to be a list of stages.");
|
|
3248
4418
|
const stages = pipe;
|
|
3249
4419
|
stages.forEach((stage, index) => {
|
|
3250
4420
|
if (!isStage(stage)) {
|
|
3251
|
-
|
|
4421
|
+
invalid6(name, `Expected pipe[${index}] to be a transform or sink stage.`);
|
|
3252
4422
|
}
|
|
3253
4423
|
if (stage.kind === "sink" && index !== stages.length - 1) {
|
|
3254
|
-
|
|
4424
|
+
invalid6(name, `The sink at pipe[${index}] must be the last stage.`);
|
|
3255
4425
|
}
|
|
3256
4426
|
});
|
|
3257
4427
|
return Object.freeze([...stages]);
|
|
@@ -3267,7 +4437,7 @@ function transformed(name, operations, transforms, context) {
|
|
|
3267
4437
|
if (result === null)
|
|
3268
4438
|
continue;
|
|
3269
4439
|
if (typeof result !== "object" || result?.kind !== "operation" || result.id !== operation.id) {
|
|
3270
|
-
|
|
4440
|
+
invalid6(name, `A transform must return the operation "${operation.id}" or null.`);
|
|
3271
4441
|
}
|
|
3272
4442
|
next.push(result);
|
|
3273
4443
|
}
|
|
@@ -3292,7 +4462,7 @@ function make(name, commands, operations, stages, statuses) {
|
|
|
3292
4462
|
return built.value;
|
|
3293
4463
|
const sink = stages.at(-1);
|
|
3294
4464
|
if (sink?.kind !== "sink") {
|
|
3295
|
-
|
|
4465
|
+
invalid6(name || "output", "The pipe has no sink to build.");
|
|
3296
4466
|
}
|
|
3297
4467
|
built = { value: sink.build(operations, context) };
|
|
3298
4468
|
return built.value;
|
|
@@ -3301,7 +4471,7 @@ function make(name, commands, operations, stages, statuses) {
|
|
|
3301
4471
|
const next = checkPipe(name || "output", more);
|
|
3302
4472
|
const last = next.at(-1);
|
|
3303
4473
|
if (stages.at(-1)?.kind === "sink") {
|
|
3304
|
-
|
|
4474
|
+
invalid6(name || "output", "Cannot add stages after the sink.");
|
|
3305
4475
|
}
|
|
3306
4476
|
const ops = transformed(name, operations, next, context);
|
|
3307
4477
|
if (last?.kind === "sink") {
|
|
@@ -3315,11 +4485,11 @@ function make(name, commands, operations, stages, statuses) {
|
|
|
3315
4485
|
function outputOf2(settings) {
|
|
3316
4486
|
const { name, commands } = settings ?? {};
|
|
3317
4487
|
if (typeof name !== "string" || !name.trim()) {
|
|
3318
|
-
|
|
4488
|
+
invalid6("outputOf", "Expected name to be a non-empty string.");
|
|
3319
4489
|
}
|
|
3320
4490
|
const sources = Array.isArray(commands) ? commands : [commands];
|
|
3321
4491
|
if (!Array.isArray(commands) && isCommand2(commands) || sources.some((entry) => !isCommand2(entry) && !isApplicationLike(entry))) {
|
|
3322
|
-
|
|
4492
|
+
invalid6(name, "Expected commands to be a list of commands made by commandOf or applications.");
|
|
3323
4493
|
}
|
|
3324
4494
|
const flat = [
|
|
3325
4495
|
...new Set(sources.flatMap((entry) => isCommand2(entry) ? [entry] : Object.values(entry.commands)))
|
|
@@ -3439,7 +4609,7 @@ function deferredProvider(factory, application) {
|
|
|
3439
4609
|
return provider;
|
|
3440
4610
|
};
|
|
3441
4611
|
const proxy = {};
|
|
3442
|
-
for (const method of METHODS) {
|
|
4612
|
+
for (const method of [...METHODS, "increment"]) {
|
|
3443
4613
|
proxy[method] = (...args) => real(method)[method]?.(...args);
|
|
3444
4614
|
}
|
|
3445
4615
|
proxy.transaction = (run) => real("transaction").transaction(run);
|
|
@@ -3500,6 +4670,16 @@ function checkTransactions(source, provider, commands) {
|
|
|
3500
4670
|
});
|
|
3501
4671
|
}
|
|
3502
4672
|
}
|
|
4673
|
+
function checkCapabilities(source, provider, repositories) {
|
|
4674
|
+
if (repositories.length === 0 || provider.capabilities?.keyset === true)
|
|
4675
|
+
return;
|
|
4676
|
+
throw new InvalidApplicationDeclaration2({
|
|
4677
|
+
source,
|
|
4678
|
+
details: {
|
|
4679
|
+
reason: `The provider made on start does not read by cursor (capabilities.keyset), which the findCursor of ${repositories.join(", ")} needs.`
|
|
4680
|
+
}
|
|
4681
|
+
});
|
|
4682
|
+
}
|
|
3503
4683
|
|
|
3504
4684
|
// src/core/application/providers.ts
|
|
3505
4685
|
var slots = new WeakMap;
|
|
@@ -3514,7 +4694,7 @@ var METHODS2 = [
|
|
|
3514
4694
|
];
|
|
3515
4695
|
var KEY = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
3516
4696
|
var RESERVED = new Set(["inMemory", "__proto__", "constructor", "prototype"]);
|
|
3517
|
-
function
|
|
4697
|
+
function invalid7(reason) {
|
|
3518
4698
|
throw new InvalidApplicationDeclaration2({
|
|
3519
4699
|
source: "providersOf",
|
|
3520
4700
|
details: { reason }
|
|
@@ -3535,6 +4715,7 @@ function slotOf(key, storage, value) {
|
|
|
3535
4715
|
for (const method of METHODS2)
|
|
3536
4716
|
slot[method] = unbound;
|
|
3537
4717
|
slot.transaction = unbound;
|
|
4718
|
+
slot.increment = unbound;
|
|
3538
4719
|
Object.defineProperty(slot, "capabilities", {
|
|
3539
4720
|
get: () => {
|
|
3540
4721
|
return;
|
|
@@ -3543,7 +4724,7 @@ function slotOf(key, storage, value) {
|
|
|
3543
4724
|
});
|
|
3544
4725
|
} else {
|
|
3545
4726
|
const target = value;
|
|
3546
|
-
for (const method of [...METHODS2, "transaction", "close"]) {
|
|
4727
|
+
for (const method of [...METHODS2, "increment", "transaction", "close"]) {
|
|
3547
4728
|
if (typeof target[method] === "function") {
|
|
3548
4729
|
slot[method] = (...args) => target[method]?.(...args);
|
|
3549
4730
|
}
|
|
@@ -3559,7 +4740,7 @@ function slotOf(key, storage, value) {
|
|
|
3559
4740
|
}
|
|
3560
4741
|
function providersOf2(entries) {
|
|
3561
4742
|
if (typeof entries !== "object" || entries === null || Array.isArray(entries) || Object.keys(entries).length === 0) {
|
|
3562
|
-
|
|
4743
|
+
invalid7("Expected an object of providers by key.");
|
|
3563
4744
|
}
|
|
3564
4745
|
const storage = Object.create(null);
|
|
3565
4746
|
storage[Kind2] = "provider-storage";
|
|
@@ -3567,17 +4748,17 @@ function providersOf2(entries) {
|
|
|
3567
4748
|
const list = [];
|
|
3568
4749
|
for (const [key, value] of Object.entries(entries)) {
|
|
3569
4750
|
if (!KEY.test(key) || RESERVED.has(key)) {
|
|
3570
|
-
|
|
4751
|
+
invalid7(key === "inMemory" ? `"inMemory" is the built-in provider; choose another key.` : `Expected "${key}" to be an identifier that is not reserved.`);
|
|
3571
4752
|
}
|
|
3572
4753
|
if (isProviderSlot2(value)) {
|
|
3573
|
-
|
|
4754
|
+
invalid7(`"${key}" holds a provider of another storage; a storage holds providers, not slots.`);
|
|
3574
4755
|
}
|
|
3575
4756
|
if (!isProviderFactory2(value) && !isProviderObject(value)) {
|
|
3576
|
-
|
|
4757
|
+
invalid7(`Expected "${key}" to be a repository provider or a factory of providerFrom.`);
|
|
3577
4758
|
}
|
|
3578
4759
|
const other = seen.get(value);
|
|
3579
4760
|
if (other !== undefined) {
|
|
3580
|
-
|
|
4761
|
+
invalid7(`"${other}" and "${key}" hold the same provider.`);
|
|
3581
4762
|
}
|
|
3582
4763
|
seen.set(value, key);
|
|
3583
4764
|
const slot = slotOf(key, storage, value);
|
|
@@ -3636,9 +4817,34 @@ var KIND_OF = {
|
|
|
3636
4817
|
function isApplication2(value) {
|
|
3637
4818
|
return typeof value === "object" && value !== null && value[Kind2] === "application";
|
|
3638
4819
|
}
|
|
3639
|
-
function
|
|
4820
|
+
function invalid8(source, reason) {
|
|
3640
4821
|
throw new InvalidApplicationDeclaration2({ source, details: { reason } });
|
|
3641
4822
|
}
|
|
4823
|
+
var publisherOrigins = new WeakMap;
|
|
4824
|
+
function publishersOf(name, declared, extended) {
|
|
4825
|
+
const merged = {};
|
|
4826
|
+
const origins = {};
|
|
4827
|
+
for (const other of extended) {
|
|
4828
|
+
Object.assign(merged, other[ApplicationPublishersKey]);
|
|
4829
|
+
Object.assign(origins, publisherOrigins.get(other));
|
|
4830
|
+
}
|
|
4831
|
+
if (declared !== undefined) {
|
|
4832
|
+
if (typeof declared !== "object" || declared === null || Array.isArray(declared)) {
|
|
4833
|
+
invalid8(name, "Expected `publishers` to be an object of publishers by key.");
|
|
4834
|
+
}
|
|
4835
|
+
for (const [key, publisher] of Object.entries(declared)) {
|
|
4836
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(key)) {
|
|
4837
|
+
invalid8(name, `Expected the publisher key "${key}" to be an identifier.`);
|
|
4838
|
+
}
|
|
4839
|
+
if (!isEventPublisher(publisher)) {
|
|
4840
|
+
invalid8(name, `Expected the publisher "${key}" to be an object with a publish(topic, event) method.`);
|
|
4841
|
+
}
|
|
4842
|
+
merged[key] = publisher;
|
|
4843
|
+
origins[key] = name;
|
|
4844
|
+
}
|
|
4845
|
+
}
|
|
4846
|
+
return { publishers: Object.freeze(merged), origins };
|
|
4847
|
+
}
|
|
3642
4848
|
function isOfKind(collection, value) {
|
|
3643
4849
|
switch (collection) {
|
|
3644
4850
|
case "properties":
|
|
@@ -3663,7 +4869,7 @@ function isOfKind(collection, value) {
|
|
|
3663
4869
|
return isDeclaredException(value);
|
|
3664
4870
|
}
|
|
3665
4871
|
}
|
|
3666
|
-
function
|
|
4872
|
+
function keyOf2(collection, value) {
|
|
3667
4873
|
switch (collection) {
|
|
3668
4874
|
case "properties":
|
|
3669
4875
|
return fieldOf(value);
|
|
@@ -3721,7 +4927,7 @@ function resolve(settings) {
|
|
|
3721
4927
|
for (const other of settings.extends ?? []) {
|
|
3722
4928
|
const resolved = internals.get(other);
|
|
3723
4929
|
if (!resolved)
|
|
3724
|
-
|
|
4930
|
+
invalid8(source, "Expected `extends` to hold applications.");
|
|
3725
4931
|
for (const key of KEYS) {
|
|
3726
4932
|
for (const entry of resolved[key].values())
|
|
3727
4933
|
add(entry);
|
|
@@ -3732,25 +4938,39 @@ function resolve(settings) {
|
|
|
3732
4938
|
if (declared === undefined)
|
|
3733
4939
|
continue;
|
|
3734
4940
|
if (typeof declared !== "object" || declared === null || Array.isArray(declared)) {
|
|
3735
|
-
|
|
4941
|
+
invalid8(source, `Expected ${collection} to be an object of declarations.`);
|
|
3736
4942
|
}
|
|
3737
4943
|
for (const [name, value] of Object.entries(declared)) {
|
|
3738
4944
|
if (!isOfKind(collection, value)) {
|
|
3739
|
-
|
|
4945
|
+
invalid8(source, `${collection}.${name} is not a ${KIND_OF[collection]} (${collection} takes ${KIND_OF[collection]} declarations).`);
|
|
3740
4946
|
}
|
|
3741
4947
|
const declaredName = collection === "useCases" ? value.useCaseName : collection === "commands" ? value.commandName : collection === "outputs" ? value.outputName : collection === "exceptions" ? value.name : collection === "records" || collection === "entities" ? declaredRecordNameOf(value) ?? name : name;
|
|
3742
4948
|
if (declaredName !== name) {
|
|
3743
|
-
|
|
4949
|
+
invalid8(source, `${collection}.${name} must be keyed by its name, "${declaredName}".`);
|
|
3744
4950
|
}
|
|
3745
4951
|
add({
|
|
3746
4952
|
collection,
|
|
3747
4953
|
name,
|
|
3748
4954
|
value,
|
|
3749
|
-
key:
|
|
4955
|
+
key: keyOf2(collection, value),
|
|
3750
4956
|
from: source
|
|
3751
4957
|
});
|
|
3752
4958
|
}
|
|
3753
4959
|
}
|
|
4960
|
+
for (const entry of [...registry.commands.values()]) {
|
|
4961
|
+
for (const UseCase of Object.values(entry.value.useCases)) {
|
|
4962
|
+
if (useCaseOriginOf2(UseCase) === undefined || byKey.has(UseCase)) {
|
|
4963
|
+
continue;
|
|
4964
|
+
}
|
|
4965
|
+
add({
|
|
4966
|
+
collection: "useCases",
|
|
4967
|
+
name: UseCase.useCaseName,
|
|
4968
|
+
value: UseCase,
|
|
4969
|
+
key: UseCase,
|
|
4970
|
+
from: entry.from
|
|
4971
|
+
});
|
|
4972
|
+
}
|
|
4973
|
+
}
|
|
3754
4974
|
return registry;
|
|
3755
4975
|
}
|
|
3756
4976
|
function recordsIn(properties) {
|
|
@@ -3861,7 +5081,7 @@ function validateOutputs(source, registry) {
|
|
|
3861
5081
|
for (const entry of registry.outputs.values()) {
|
|
3862
5082
|
const output = entry.value;
|
|
3863
5083
|
if (!output.outputName) {
|
|
3864
|
-
|
|
5084
|
+
invalid8(source, `outputs.${entry.name} has no name (an output made by pipe cannot be registered).`);
|
|
3865
5085
|
}
|
|
3866
5086
|
for (const command of output.commands) {
|
|
3867
5087
|
if (commands.has(command))
|
|
@@ -4042,8 +5262,9 @@ function recordDescriptionOf(entry, scope) {
|
|
|
4042
5262
|
const Record = entry.value;
|
|
4043
5263
|
const plugins = pluginsOf2(Record).map((plugin) => plugin.toJSON());
|
|
4044
5264
|
const skip = new Set(plugins.flatMap((plugin) => plugin.keys));
|
|
5265
|
+
const expires = entry.collection === "entities" ? Record[EntitySettings2]?.expires : undefined;
|
|
4045
5266
|
if (entry.collection === "entities") {
|
|
4046
|
-
for (const key of
|
|
5267
|
+
for (const key of identityKeysOf(Record))
|
|
4047
5268
|
skip.add(key);
|
|
4048
5269
|
}
|
|
4049
5270
|
const own = (keys, flag) => keys.filter((key) => {
|
|
@@ -4052,6 +5273,14 @@ function recordDescriptionOf(entry, scope) {
|
|
|
4052
5273
|
const field = Record.blueprint.properties[key];
|
|
4053
5274
|
return !(flag && isProperty2(field) && field.type[flag] === true);
|
|
4054
5275
|
});
|
|
5276
|
+
const lifecycle = entry.collection === "entities" ? lifecycleEventsOf(Record) : {};
|
|
5277
|
+
const events = Object.fromEntries(Object.entries(lifecycle).map(([cycle, list]) => [
|
|
5278
|
+
cycle,
|
|
5279
|
+
list.map((event) => ({
|
|
5280
|
+
name: event.name,
|
|
5281
|
+
handler: eventHasHandler(event)
|
|
5282
|
+
}))
|
|
5283
|
+
]));
|
|
4055
5284
|
return {
|
|
4056
5285
|
from: entry.from,
|
|
4057
5286
|
fields: fieldsDescriptionOf(Record.blueprint.properties, scope, skip),
|
|
@@ -4060,10 +5289,13 @@ function recordDescriptionOf(entry, scope) {
|
|
|
4060
5289
|
unique: own(Record.options.unique, "unique"),
|
|
4061
5290
|
change: own(Record.options.change)
|
|
4062
5291
|
},
|
|
4063
|
-
plugins
|
|
5292
|
+
plugins,
|
|
5293
|
+
...Object.keys(events).length > 0 ? { events } : {},
|
|
5294
|
+
...expires === undefined ? {} : { expires },
|
|
5295
|
+
...entry.collection === "entities" && isVersionedEntityClass(Record) ? { versioned: true } : {}
|
|
4064
5296
|
};
|
|
4065
5297
|
}
|
|
4066
|
-
function describeRegistry(name, extended, registry, provider, named, log) {
|
|
5298
|
+
function describeRegistry(name, extended, registry, provider, named, log, publishers) {
|
|
4067
5299
|
const scope = {
|
|
4068
5300
|
properties: referencesOf(registry, "properties"),
|
|
4069
5301
|
blueprints: referencesOf(registry, "blueprints"),
|
|
@@ -4087,6 +5319,45 @@ function describeRegistry(name, extended, registry, provider, named, log) {
|
|
|
4087
5319
|
})
|
|
4088
5320
|
};
|
|
4089
5321
|
};
|
|
5322
|
+
const describeOption = (value) => {
|
|
5323
|
+
if (isLoggerDeclaration(value))
|
|
5324
|
+
return "Logger";
|
|
5325
|
+
if (isUseCaseClass2(value))
|
|
5326
|
+
return useCaseRefs.get(value);
|
|
5327
|
+
if (isEnvironment2(value))
|
|
5328
|
+
return environmentRefs.get(value);
|
|
5329
|
+
if (isRepository2(value)) {
|
|
5330
|
+
return repositories.get(inspectRepository(value).state);
|
|
5331
|
+
}
|
|
5332
|
+
if (typeof value === "function") {
|
|
5333
|
+
if (isDeclaredException(value)) {
|
|
5334
|
+
return exceptionRefs.get(value) ?? { core: value.name };
|
|
5335
|
+
}
|
|
5336
|
+
return scope.entities.get(value) ?? scope.records.get(value) ?? { kind: "opaque" };
|
|
5337
|
+
}
|
|
5338
|
+
if (Array.isArray(value))
|
|
5339
|
+
return value.map(describeOption);
|
|
5340
|
+
if (typeof value === "object" && value !== null) {
|
|
5341
|
+
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined).map(([key, item]) => [key, describeOption(item)]));
|
|
5342
|
+
}
|
|
5343
|
+
return value;
|
|
5344
|
+
};
|
|
5345
|
+
const describeGeneric = (UseCase) => {
|
|
5346
|
+
const origin = useCaseOriginOf2(UseCase);
|
|
5347
|
+
if (!origin || interpretedStepsOf(UseCase) === undefined)
|
|
5348
|
+
return {};
|
|
5349
|
+
const repository = repositories.get(inspectRepository(origin.repository).state);
|
|
5350
|
+
if (repository === undefined)
|
|
5351
|
+
return {};
|
|
5352
|
+
return {
|
|
5353
|
+
generic: {
|
|
5354
|
+
name: origin.plugin.name,
|
|
5355
|
+
version: origin.plugin.version,
|
|
5356
|
+
repository,
|
|
5357
|
+
options: describeOption(origin.options)
|
|
5358
|
+
}
|
|
5359
|
+
};
|
|
5360
|
+
};
|
|
4090
5361
|
const refsOf = (blueprints) => {
|
|
4091
5362
|
const refs = {};
|
|
4092
5363
|
for (const [key, blueprint] of Object.entries(blueprints)) {
|
|
@@ -4125,6 +5396,9 @@ function describeRegistry(name, extended, registry, provider, named, log) {
|
|
|
4125
5396
|
})
|
|
4126
5397
|
}
|
|
4127
5398
|
},
|
|
5399
|
+
...Object.keys(publishers).length === 0 ? {} : {
|
|
5400
|
+
publishers: Object.fromEntries(Object.entries(publishers).map(([key, from]) => [key, { from }]))
|
|
5401
|
+
},
|
|
4128
5402
|
properties: each("properties", (entry) => ({
|
|
4129
5403
|
from: entry.from,
|
|
4130
5404
|
...propertyDescriptionOf(entry.value, scope)
|
|
@@ -4146,6 +5420,8 @@ function describeRegistry(name, extended, registry, provider, named, log) {
|
|
|
4146
5420
|
entity: scope.entities.get(info.entity),
|
|
4147
5421
|
actions: info.actions,
|
|
4148
5422
|
deletion: info.deletion,
|
|
5423
|
+
indexes: info.indexes,
|
|
5424
|
+
scans: info.scans,
|
|
4149
5425
|
relations: info.relations.map((relation) => repositories.get(relation)),
|
|
4150
5426
|
environments: info.environments.map((environment) => environmentRefs.get(environment)),
|
|
4151
5427
|
...info.provider === undefined ? {} : {
|
|
@@ -4188,7 +5464,8 @@ function describeRegistry(name, extended, registry, provider, named, log) {
|
|
|
4188
5464
|
ref: repositories.get(inspectRepository(dependency).state)
|
|
4189
5465
|
}
|
|
4190
5466
|
])),
|
|
4191
|
-
implementation: describeImplementation(UseCase)
|
|
5467
|
+
implementation: describeImplementation(UseCase),
|
|
5468
|
+
...describeGeneric(UseCase)
|
|
4192
5469
|
};
|
|
4193
5470
|
}),
|
|
4194
5471
|
commands: each("commands", (entry) => {
|
|
@@ -4210,7 +5487,7 @@ function describeRegistry(name, extended, registry, provider, named, log) {
|
|
|
4210
5487
|
...command.environments.length === 0 ? {} : {
|
|
4211
5488
|
environments: command.environments.map((environment) => environmentRefs.get(environment))
|
|
4212
5489
|
},
|
|
4213
|
-
events: typeof command.events === "function" ? { kind: "opaque" } : command.events,
|
|
5490
|
+
events: typeof command.events === "function" ? eventPolicyDescriptionOf2(command.events) ?? { kind: "opaque" } : command.events,
|
|
4214
5491
|
...command.onEventError === undefined ? {} : { onEventError: { kind: "opaque" } }
|
|
4215
5492
|
};
|
|
4216
5493
|
}),
|
|
@@ -4238,11 +5515,11 @@ function describeRegistry(name, extended, registry, provider, named, log) {
|
|
|
4238
5515
|
function applicationOf2(settings) {
|
|
4239
5516
|
const { name } = settings ?? {};
|
|
4240
5517
|
if (typeof name !== "string" || name === "") {
|
|
4241
|
-
|
|
5518
|
+
invalid8("applicationOf", "Expected a name.");
|
|
4242
5519
|
}
|
|
4243
5520
|
const extended = settings.extends ?? [];
|
|
4244
5521
|
if (!Array.isArray(extended) || !extended.every(isApplication2)) {
|
|
4245
|
-
|
|
5522
|
+
invalid8(name, "Expected `extends` to be a list of applications.");
|
|
4246
5523
|
}
|
|
4247
5524
|
const registry = resolve(settings);
|
|
4248
5525
|
validate(name, registry);
|
|
@@ -4250,7 +5527,7 @@ function applicationOf2(settings) {
|
|
|
4250
5527
|
validateCommands(name, registry);
|
|
4251
5528
|
validateOutputs(name, registry);
|
|
4252
5529
|
if (settings.providers !== undefined && !isProviderStorage2(settings.providers)) {
|
|
4253
|
-
|
|
5530
|
+
invalid8(name, "Expected `providers` to be a storage made by providersOf.");
|
|
4254
5531
|
}
|
|
4255
5532
|
const named = new Map;
|
|
4256
5533
|
const addNamed = (entry) => {
|
|
@@ -4307,7 +5584,7 @@ function applicationOf2(settings) {
|
|
|
4307
5584
|
const inheritedFrom = settings.provider === undefined ? [...extended].reverse().find((other) => other.provider) : undefined;
|
|
4308
5585
|
const declaredProvider = settings.provider ?? inheritedFrom?.provider;
|
|
4309
5586
|
if (declaredProvider !== undefined && typeof declaredProvider !== "object") {
|
|
4310
|
-
|
|
5587
|
+
invalid8(name, "Expected `provider` to be a repository provider.");
|
|
4311
5588
|
}
|
|
4312
5589
|
const defaultSlot = isProviderSlot2(declaredProvider) ? declaredProvider : inheritedFrom === undefined ? undefined : defaultSlots.get(inheritedFrom);
|
|
4313
5590
|
const provider = defaultSlot !== undefined ? instanceOf(defaultSlot, "the application") : isProviderFactory2(declaredProvider) ? deferredProvider(declaredProvider, name) : declaredProvider;
|
|
@@ -4340,14 +5617,33 @@ function applicationOf2(settings) {
|
|
|
4340
5617
|
log?.set(checkLogSettings(declaredLog));
|
|
4341
5618
|
}
|
|
4342
5619
|
const logger = log?.logger;
|
|
5620
|
+
const { publishers, origins } = publishersOf(name, settings.publishers, extended);
|
|
5621
|
+
const hasPublishers = Object.keys(publishers).length > 0;
|
|
5622
|
+
for (const entry of registry.commands.values()) {
|
|
5623
|
+
for (const key of eventPolicyPublishersOf(entry.value.events)) {
|
|
5624
|
+
if (!Object.hasOwn(publishers, key)) {
|
|
5625
|
+
throw new UnregisteredApplicationReference2({
|
|
5626
|
+
source: name,
|
|
5627
|
+
details: {
|
|
5628
|
+
holder: `command "${entry.name}"`,
|
|
5629
|
+
reference: `publisher "${key}"`
|
|
5630
|
+
}
|
|
5631
|
+
});
|
|
5632
|
+
}
|
|
5633
|
+
}
|
|
5634
|
+
}
|
|
4343
5635
|
const bindsProviders = provider !== undefined || instances.size > 0;
|
|
4344
|
-
const rebinds = bindsProviders || logger !== undefined;
|
|
5636
|
+
const rebinds = bindsProviders || logger !== undefined || hasPublishers;
|
|
4345
5637
|
const collection = (key) => Object.freeze(Object.fromEntries([...registry[key].values()].map((entry) => [entry.name, entry.value])));
|
|
4346
5638
|
const bound = new Map;
|
|
4347
5639
|
const boundByState = new Map;
|
|
4348
5640
|
const repositories = {};
|
|
4349
5641
|
for (const entry of registry.repositories.values()) {
|
|
4350
|
-
const repository = bindsProviders ? bindRepository(entry.value, provider, instances
|
|
5642
|
+
const repository = bindsProviders ? bindRepository(entry.value, provider, instances, {
|
|
5643
|
+
application: name,
|
|
5644
|
+
key: entry.name,
|
|
5645
|
+
logger
|
|
5646
|
+
}) : entry.value;
|
|
4351
5647
|
bound.set(entry.value, repository);
|
|
4352
5648
|
boundByState.set(entry.key, repository);
|
|
4353
5649
|
repositories[entry.name] = repository;
|
|
@@ -4387,7 +5683,7 @@ function applicationOf2(settings) {
|
|
|
4387
5683
|
const boundCommandOf = new Map;
|
|
4388
5684
|
for (const entry of registry.commands.values()) {
|
|
4389
5685
|
const declared = entry.value;
|
|
4390
|
-
const rebuilt = !rebinds ? declared :
|
|
5686
|
+
const rebuilt = !rebinds ? declared : commandOf({
|
|
4391
5687
|
name: declared.commandName,
|
|
4392
5688
|
description: declared.description,
|
|
4393
5689
|
tags: declared.tags,
|
|
@@ -4403,6 +5699,8 @@ function applicationOf2(settings) {
|
|
|
4403
5699
|
});
|
|
4404
5700
|
if (bindsProviders)
|
|
4405
5701
|
verifyCommand(rebuilt);
|
|
5702
|
+
if (hasPublishers)
|
|
5703
|
+
bindPublishers(rebuilt, publishers);
|
|
4406
5704
|
boundCommandOf.set(entry.value, rebuilt);
|
|
4407
5705
|
commands[entry.name] = rebuilt;
|
|
4408
5706
|
}
|
|
@@ -4467,6 +5765,7 @@ function applicationOf2(settings) {
|
|
|
4467
5765
|
continue;
|
|
4468
5766
|
const made = await startDeferred(instance, context);
|
|
4469
5767
|
checkTransactions(name, made, Object.entries(commands).filter(([, command]) => command.transaction && commandTransactionProvider(command) === instance).map(([key]) => key));
|
|
5768
|
+
checkCapabilities(name, made, Object.entries(repositories).filter(([, repository]) => providerOfRepository(repository) === instance && inspectRepository(repository).actions.includes("findCursor")).map(([key]) => key));
|
|
4470
5769
|
}
|
|
4471
5770
|
started = true;
|
|
4472
5771
|
};
|
|
@@ -4478,6 +5777,7 @@ function applicationOf2(settings) {
|
|
|
4478
5777
|
logger: logger ?? silentLogger,
|
|
4479
5778
|
context,
|
|
4480
5779
|
...logger ? { [ApplicationLoggerKey]: logger } : {},
|
|
5780
|
+
...hasPublishers ? { [ApplicationPublishersKey]: publishers } : {},
|
|
4481
5781
|
properties: collection("properties"),
|
|
4482
5782
|
blueprints: collection("blueprints"),
|
|
4483
5783
|
records: collection("records"),
|
|
@@ -4490,13 +5790,13 @@ function applicationOf2(settings) {
|
|
|
4490
5790
|
exceptions: collection("exceptions"),
|
|
4491
5791
|
useCase(useCase) {
|
|
4492
5792
|
if (!boundUseCaseOf.has(useCase)) {
|
|
4493
|
-
|
|
5793
|
+
invalid8(name, "Expected a use case registered in the application.");
|
|
4494
5794
|
}
|
|
4495
5795
|
return boundUseCaseOf.get(useCase);
|
|
4496
5796
|
},
|
|
4497
5797
|
async run(useCase, input = {}, options) {
|
|
4498
5798
|
if (!boundUseCaseOf.has(useCase)) {
|
|
4499
|
-
|
|
5799
|
+
invalid8(name, "Expected a use case registered in the application.");
|
|
4500
5800
|
}
|
|
4501
5801
|
const Bound = boundUseCaseOf.get(useCase);
|
|
4502
5802
|
const instance = options?.context === undefined ? new Bound(input) : new Bound(input, { context: options.context });
|
|
@@ -4504,19 +5804,19 @@ function applicationOf2(settings) {
|
|
|
4504
5804
|
},
|
|
4505
5805
|
command(command) {
|
|
4506
5806
|
if (!boundCommandOf.has(command)) {
|
|
4507
|
-
|
|
5807
|
+
invalid8(name, "Expected a command registered in the application.");
|
|
4508
5808
|
}
|
|
4509
5809
|
return boundCommandOf.get(command);
|
|
4510
5810
|
},
|
|
4511
5811
|
output(output) {
|
|
4512
5812
|
if (!boundOutputOf.has(output)) {
|
|
4513
|
-
|
|
5813
|
+
invalid8(name, "Expected an output registered in the application.");
|
|
4514
5814
|
}
|
|
4515
5815
|
return boundOutputOf.get(output);
|
|
4516
5816
|
},
|
|
4517
5817
|
repository(repository) {
|
|
4518
5818
|
if (!bound.has(repository)) {
|
|
4519
|
-
|
|
5819
|
+
invalid8(name, "Expected a repository registered in the application.");
|
|
4520
5820
|
}
|
|
4521
5821
|
return bound.get(repository);
|
|
4522
5822
|
},
|
|
@@ -4528,7 +5828,7 @@ function applicationOf2(settings) {
|
|
|
4528
5828
|
...declaredLog === undefined ? {} : { log: declaredLog }
|
|
4529
5829
|
});
|
|
4530
5830
|
},
|
|
4531
|
-
describe: () => describeRegistry(name, extended, registry, defaultSlot !== undefined ? referenceOf(defaultSlot) : provider === undefined ? undefined : providerDescriptionOf2(provider), named, isLogFactory2(declaredLog) ? "opaque" : log?.settings),
|
|
5831
|
+
describe: () => describeRegistry(name, extended, registry, defaultSlot !== undefined ? referenceOf(defaultSlot) : provider === undefined ? undefined : providerDescriptionOf2(provider), named, isLogFactory2(declaredLog) ? "opaque" : log?.settings, origins),
|
|
4532
5832
|
get started() {
|
|
4533
5833
|
return started;
|
|
4534
5834
|
},
|
|
@@ -4581,7 +5881,7 @@ function applicationOf2(settings) {
|
|
|
4581
5881
|
get(declaration) {
|
|
4582
5882
|
if (isEnvironment2(declaration)) {
|
|
4583
5883
|
if (!registeredEnvironments.has(declaration)) {
|
|
4584
|
-
|
|
5884
|
+
invalid8(name, "Expected an environment registered in the application.");
|
|
4585
5885
|
}
|
|
4586
5886
|
return declaration.load();
|
|
4587
5887
|
}
|
|
@@ -4597,11 +5897,11 @@ function applicationOf2(settings) {
|
|
|
4597
5897
|
if (isProviderSlot2(declaration)) {
|
|
4598
5898
|
const instance = instances.get(declaration);
|
|
4599
5899
|
if (instance === undefined) {
|
|
4600
|
-
|
|
5900
|
+
invalid8(name, "Expected a provider of a storage registered in the application.");
|
|
4601
5901
|
}
|
|
4602
5902
|
return instance;
|
|
4603
5903
|
}
|
|
4604
|
-
return
|
|
5904
|
+
return invalid8(name, "Expected a repository, use case, command, output, environment or provider registered in the application.");
|
|
4605
5905
|
}
|
|
4606
5906
|
});
|
|
4607
5907
|
internals.set(application, registry);
|
|
@@ -4610,6 +5910,7 @@ function applicationOf2(settings) {
|
|
|
4610
5910
|
defaultSlots.set(application, defaultSlot);
|
|
4611
5911
|
if (declaredLog !== undefined)
|
|
4612
5912
|
logSettings.set(application, declaredLog);
|
|
5913
|
+
publisherOrigins.set(application, Object.freeze(origins));
|
|
4613
5914
|
return application;
|
|
4614
5915
|
}
|
|
4615
5916
|
// src/core/blueprint/blueprint-plugin.ts
|
|
@@ -4620,6 +5921,140 @@ function blueprintPluginOf2(name, properties, factories = {}) {
|
|
|
4620
5921
|
factories: Object.freeze({ ...factories })
|
|
4621
5922
|
});
|
|
4622
5923
|
}
|
|
5924
|
+
// src/core/event-policy/publish.ts
|
|
5925
|
+
var KEYS2 = ["publisher", "topic", "events", "handle", "onPublishError"];
|
|
5926
|
+
var IDENTIFIER3 = /^[A-Za-z_$][\w$]*$/;
|
|
5927
|
+
var PLACEHOLDER = /\{(event|command|operation|id)\}/g;
|
|
5928
|
+
function validate2(options) {
|
|
5929
|
+
const given = options;
|
|
5930
|
+
for (const key of Object.keys(given)) {
|
|
5931
|
+
if (!KEYS2.includes(key))
|
|
5932
|
+
return `publishEvents has no option "${key}".`;
|
|
5933
|
+
}
|
|
5934
|
+
const { publisher, topic, events, handle, onPublishError } = given;
|
|
5935
|
+
if (typeof publisher !== "string" || !IDENTIFIER3.test(publisher)) {
|
|
5936
|
+
return "Expected publishEvents.publisher to be the key of a publisher of the application.";
|
|
5937
|
+
}
|
|
5938
|
+
if (typeof topic !== "string" || topic === "") {
|
|
5939
|
+
return 'Expected publishEvents.topic to be a non-empty string, such as "posts/{id}".';
|
|
5940
|
+
}
|
|
5941
|
+
const unknown = topic.replaceAll(PLACEHOLDER, "").match(/\{[^}]*\}/);
|
|
5942
|
+
if (unknown) {
|
|
5943
|
+
return `publishEvents.topic has an unknown placeholder ${unknown[0]}: use {event}, {command}, {operation} or {id}.`;
|
|
5944
|
+
}
|
|
5945
|
+
if (events !== undefined && (!Array.isArray(events) || events.some((name) => typeof name !== "string" || name === ""))) {
|
|
5946
|
+
return "Expected publishEvents.events to be a list of event names.";
|
|
5947
|
+
}
|
|
5948
|
+
if (handle !== undefined && typeof handle !== "boolean") {
|
|
5949
|
+
return "Expected publishEvents.handle to be a boolean.";
|
|
5950
|
+
}
|
|
5951
|
+
if (onPublishError !== undefined && onPublishError !== "log" && onPublishError !== "throw") {
|
|
5952
|
+
return 'Expected publishEvents.onPublishError to be "log" or "throw".';
|
|
5953
|
+
}
|
|
5954
|
+
return;
|
|
5955
|
+
}
|
|
5956
|
+
function entityJson(event) {
|
|
5957
|
+
const text = JSON.stringify(event.entity);
|
|
5958
|
+
return text === undefined ? null : JSON.parse(text);
|
|
5959
|
+
}
|
|
5960
|
+
var publishEvents2 = eventPolicyOf2({
|
|
5961
|
+
name: "publishEvents",
|
|
5962
|
+
validate: validate2,
|
|
5963
|
+
publishers: (options) => [options.publisher],
|
|
5964
|
+
handle: async (events, info, options) => {
|
|
5965
|
+
const { command, operation, logger } = info;
|
|
5966
|
+
const commandName = command.commandName;
|
|
5967
|
+
const source = `${commandName}.${operation}`;
|
|
5968
|
+
const only = options.events ? new Set(options.events) : undefined;
|
|
5969
|
+
const failed = (cause, event, index) => new CommandEventFailed2({
|
|
5970
|
+
source,
|
|
5971
|
+
cause,
|
|
5972
|
+
details: {
|
|
5973
|
+
operation,
|
|
5974
|
+
event: event.name,
|
|
5975
|
+
handled: index,
|
|
5976
|
+
remaining: events.length - index - 1
|
|
5977
|
+
}
|
|
5978
|
+
});
|
|
5979
|
+
for (const [index, event] of events.entries()) {
|
|
5980
|
+
if (options.handle !== false) {
|
|
5981
|
+
try {
|
|
5982
|
+
await event.handle({ logger });
|
|
5983
|
+
} catch (cause) {
|
|
5984
|
+
throw failed(cause, event, index);
|
|
5985
|
+
}
|
|
5986
|
+
}
|
|
5987
|
+
if (only && !only.has(event.name))
|
|
5988
|
+
continue;
|
|
5989
|
+
let publisher;
|
|
5990
|
+
try {
|
|
5991
|
+
publisher = info.publisher(options.publisher);
|
|
5992
|
+
} catch (cause) {
|
|
5993
|
+
throw failed(cause, event, index);
|
|
5994
|
+
}
|
|
5995
|
+
const entity = entityJson(event);
|
|
5996
|
+
const id = entity?.id;
|
|
5997
|
+
const topic = options.topic.replaceAll(PLACEHOLDER, (_, key) => key === "event" ? event.name : key === "command" ? commandName : key === "operation" ? operation : id === undefined ? "" : String(id));
|
|
5998
|
+
const published = Object.freeze({
|
|
5999
|
+
name: event.name,
|
|
6000
|
+
command: commandName,
|
|
6001
|
+
operation,
|
|
6002
|
+
entity
|
|
6003
|
+
});
|
|
6004
|
+
try {
|
|
6005
|
+
await publisher.publish(topic, published);
|
|
6006
|
+
} catch (cause) {
|
|
6007
|
+
if (options.onPublishError === "throw")
|
|
6008
|
+
throw failed(cause, event, index);
|
|
6009
|
+
logFailure(logger, "event-publish-failed", {
|
|
6010
|
+
command: commandName,
|
|
6011
|
+
operation,
|
|
6012
|
+
event: event.name,
|
|
6013
|
+
publisher: options.publisher,
|
|
6014
|
+
topic
|
|
6015
|
+
}, cause);
|
|
6016
|
+
}
|
|
6017
|
+
}
|
|
6018
|
+
}
|
|
6019
|
+
});
|
|
6020
|
+
// src/core/output/coerce.ts
|
|
6021
|
+
var scalarTypes = new WeakMap;
|
|
6022
|
+
function scalarTypesOf(blueprint) {
|
|
6023
|
+
let types = scalarTypes.get(blueprint);
|
|
6024
|
+
if (!types) {
|
|
6025
|
+
const schema = toOpenAPI2(blueprint);
|
|
6026
|
+
const found = {};
|
|
6027
|
+
for (const [key, property] of Object.entries(schema.properties ?? {})) {
|
|
6028
|
+
found[key] = [property.type ?? []].flat();
|
|
6029
|
+
}
|
|
6030
|
+
types = Object.freeze(found);
|
|
6031
|
+
scalarTypes.set(blueprint, types);
|
|
6032
|
+
}
|
|
6033
|
+
return types;
|
|
6034
|
+
}
|
|
6035
|
+
function coerce(text, types) {
|
|
6036
|
+
if (!types || types.includes("string"))
|
|
6037
|
+
return text;
|
|
6038
|
+
const trimmed = text.trim();
|
|
6039
|
+
if (types.includes("boolean") && (trimmed === "true" || trimmed === "false")) {
|
|
6040
|
+
return trimmed === "true";
|
|
6041
|
+
}
|
|
6042
|
+
if (types.includes("integer") || types.includes("number")) {
|
|
6043
|
+
const number = trimmed === "" ? Number.NaN : Number(trimmed);
|
|
6044
|
+
if (Number.isFinite(number))
|
|
6045
|
+
return number;
|
|
6046
|
+
}
|
|
6047
|
+
return text;
|
|
6048
|
+
}
|
|
6049
|
+
function coerceTextValues2(blueprint, texts) {
|
|
6050
|
+
const types = scalarTypesOf(blueprint);
|
|
6051
|
+
const out = {};
|
|
6052
|
+
for (const [key, value] of Object.entries(texts)) {
|
|
6053
|
+
if (value !== undefined)
|
|
6054
|
+
out[key] = coerce(value, types[key]);
|
|
6055
|
+
}
|
|
6056
|
+
return out;
|
|
6057
|
+
}
|
|
4623
6058
|
// src/core/output/status.ts
|
|
4624
6059
|
var INVALID_INPUT = new Set([
|
|
4625
6060
|
"InvalidUseCaseInput",
|
|
@@ -4629,7 +6064,13 @@ var INVALID_INPUT = new Set([
|
|
|
4629
6064
|
var CONFIGURATION = new Set(["InvalidCommandEnvironment"]);
|
|
4630
6065
|
var NOT_FOUND = new Set([
|
|
4631
6066
|
"RepositoryEntityNotFound",
|
|
4632
|
-
"UnknownCommandOperation"
|
|
6067
|
+
"UnknownCommandOperation",
|
|
6068
|
+
"UseCaseEntityNotFound"
|
|
6069
|
+
]);
|
|
6070
|
+
var CONFLICT = new Set([
|
|
6071
|
+
"RepositoryUniqueViolation",
|
|
6072
|
+
"RepositoryConcurrencyConflict",
|
|
6073
|
+
"UseCaseEntityConflict"
|
|
4633
6074
|
]);
|
|
4634
6075
|
function raisedOf(error) {
|
|
4635
6076
|
let raised = error;
|
|
@@ -4654,8 +6095,10 @@ function statusOf2(error, operation, statuses) {
|
|
|
4654
6095
|
}
|
|
4655
6096
|
if (NOT_FOUND.has(name))
|
|
4656
6097
|
return 404;
|
|
4657
|
-
if (name
|
|
6098
|
+
if (CONFLICT.has(name))
|
|
4658
6099
|
return 409;
|
|
6100
|
+
if (name === "UseCaseInvalidCredentials")
|
|
6101
|
+
return 401;
|
|
4659
6102
|
if (operation?.errors.some((Declared) => error.is(Declared) || Declared.name === name)) {
|
|
4660
6103
|
return 422;
|
|
4661
6104
|
}
|
|
@@ -4694,6 +6137,8 @@ function outputValueOf2(value) {
|
|
|
4694
6137
|
|
|
4695
6138
|
// src/core/output/openapi-sink.ts
|
|
4696
6139
|
var METHODS3 = ["get", "post", "put", "patch", "delete"];
|
|
6140
|
+
var SUCCESS_STATUSES = [200, 201, 202, 204];
|
|
6141
|
+
var REQUEST_ID_HEADER = "x-request-id";
|
|
4697
6142
|
function isObject(value) {
|
|
4698
6143
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4699
6144
|
}
|
|
@@ -4704,36 +6149,199 @@ function onlyKeys(value, allowed, where) {
|
|
|
4704
6149
|
}
|
|
4705
6150
|
return;
|
|
4706
6151
|
}
|
|
4707
|
-
|
|
4708
|
-
|
|
6152
|
+
var PARAMETER = /^\{([A-Za-z_][A-Za-z0-9_]*)\}$/;
|
|
6153
|
+
var HEADER_NAME = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/;
|
|
6154
|
+
var COMPONENT_NAME = /^[A-Za-z0-9._-]+$/;
|
|
6155
|
+
function isPath(value, parameters = true) {
|
|
6156
|
+
if (typeof value !== "string" || !value.startsWith("/"))
|
|
6157
|
+
return false;
|
|
6158
|
+
return value.split("/").every((segment) => parameters && PARAMETER.test(segment) || !/[{}?#\s:*]/.test(segment));
|
|
4709
6159
|
}
|
|
6160
|
+
function parametersOfPath(path) {
|
|
6161
|
+
const names = [];
|
|
6162
|
+
for (const segment of path.split("/")) {
|
|
6163
|
+
const name = PARAMETER.exec(segment)?.[1];
|
|
6164
|
+
if (name)
|
|
6165
|
+
names.push(name);
|
|
6166
|
+
}
|
|
6167
|
+
return names;
|
|
6168
|
+
}
|
|
6169
|
+
function isHeaderValue(value) {
|
|
6170
|
+
if (typeof value !== "string")
|
|
6171
|
+
return false;
|
|
6172
|
+
for (let i = 0;i < value.length; i++) {
|
|
6173
|
+
const code = value.charCodeAt(i);
|
|
6174
|
+
if (code < 32 && code !== 9 || code === 127)
|
|
6175
|
+
return false;
|
|
6176
|
+
}
|
|
6177
|
+
return true;
|
|
6178
|
+
}
|
|
6179
|
+
function isScopes(value) {
|
|
6180
|
+
return value === undefined || Array.isArray(value) && value.every((scope) => typeof scope === "string" && scope !== "");
|
|
6181
|
+
}
|
|
6182
|
+
var SCHEME_KEYS = {
|
|
6183
|
+
http: ["scheme", "bearerFormat"],
|
|
6184
|
+
apiKey: ["name", "in"],
|
|
6185
|
+
oauth2: ["flows"],
|
|
6186
|
+
openIdConnect: ["openIdConnectUrl"],
|
|
6187
|
+
mutualTLS: []
|
|
6188
|
+
};
|
|
6189
|
+
function schemeIssue(scheme, where) {
|
|
6190
|
+
if (!isObject(scheme) || typeof scheme.type !== "string") {
|
|
6191
|
+
return `Expected ${where} to be a security scheme with a "type".`;
|
|
6192
|
+
}
|
|
6193
|
+
const keys = Object.hasOwn(SCHEME_KEYS, scheme.type) ? SCHEME_KEYS[scheme.type] : undefined;
|
|
6194
|
+
if (!keys) {
|
|
6195
|
+
return `Expected ${where}.type to be one of ${Object.keys(SCHEME_KEYS).join(", ")}.`;
|
|
6196
|
+
}
|
|
6197
|
+
const bad = onlyKeys(scheme, ["type", "description", ...keys], where);
|
|
6198
|
+
if (bad)
|
|
6199
|
+
return bad;
|
|
6200
|
+
if (scheme.description !== undefined && typeof scheme.description !== "string") {
|
|
6201
|
+
return `Expected ${where}.description to be a string.`;
|
|
6202
|
+
}
|
|
6203
|
+
switch (scheme.type) {
|
|
6204
|
+
case "http":
|
|
6205
|
+
if (typeof scheme.scheme !== "string" || !scheme.scheme) {
|
|
6206
|
+
return `Expected ${where}.scheme to be a scheme such as "bearer".`;
|
|
6207
|
+
}
|
|
6208
|
+
if (scheme.bearerFormat !== undefined && typeof scheme.bearerFormat !== "string") {
|
|
6209
|
+
return `Expected ${where}.bearerFormat to be a string.`;
|
|
6210
|
+
}
|
|
6211
|
+
return;
|
|
6212
|
+
case "apiKey":
|
|
6213
|
+
if (typeof scheme.name !== "string" || !scheme.name) {
|
|
6214
|
+
return `Expected ${where}.name to be a non-empty string.`;
|
|
6215
|
+
}
|
|
6216
|
+
if (!["header", "query", "cookie"].includes(scheme.in)) {
|
|
6217
|
+
return `Expected ${where}.in to be "header", "query" or "cookie".`;
|
|
6218
|
+
}
|
|
6219
|
+
return;
|
|
6220
|
+
case "oauth2":
|
|
6221
|
+
return isObject(scheme.flows) ? undefined : `Expected ${where}.flows to be an object.`;
|
|
6222
|
+
case "openIdConnect":
|
|
6223
|
+
return typeof scheme.openIdConnectUrl === "string" && scheme.openIdConnectUrl ? undefined : `Expected ${where}.openIdConnectUrl to be a URL.`;
|
|
6224
|
+
default:
|
|
6225
|
+
return;
|
|
6226
|
+
}
|
|
6227
|
+
}
|
|
6228
|
+
function requirementIssue(requirement, schemes, where) {
|
|
6229
|
+
if (!isObject(requirement) || onlyKeys(requirement, ["scheme", "scopes"], where) || typeof requirement.scheme !== "string" || !isScopes(requirement.scopes)) {
|
|
6230
|
+
return `Expected ${where} to be { scheme, scopes? }.`;
|
|
6231
|
+
}
|
|
6232
|
+
if (!Object.hasOwn(schemes, requirement.scheme)) {
|
|
6233
|
+
return `${where}.scheme names the unknown security scheme "${requirement.scheme}".`;
|
|
6234
|
+
}
|
|
6235
|
+
return;
|
|
6236
|
+
}
|
|
6237
|
+
function securityIssue(security, where) {
|
|
6238
|
+
if (!isObject(security))
|
|
6239
|
+
return `Expected ${where} to be an object.`;
|
|
6240
|
+
const bad = onlyKeys(security, ["schemes", "operations", "default"], where);
|
|
6241
|
+
if (bad)
|
|
6242
|
+
return bad;
|
|
6243
|
+
const { schemes, operations } = security;
|
|
6244
|
+
if (!isObject(schemes))
|
|
6245
|
+
return `Expected ${where}.schemes to be an object.`;
|
|
6246
|
+
for (const [name, scheme] of Object.entries(schemes)) {
|
|
6247
|
+
if (!COMPONENT_NAME.test(name)) {
|
|
6248
|
+
return `Expected the security scheme "${name}" to be named with letters, digits, ".", "_" or "-".`;
|
|
6249
|
+
}
|
|
6250
|
+
const schemeBad = schemeIssue(scheme, `${where}.schemes["${name}"]`);
|
|
6251
|
+
if (schemeBad)
|
|
6252
|
+
return schemeBad;
|
|
6253
|
+
}
|
|
6254
|
+
if (operations !== undefined) {
|
|
6255
|
+
if (!isObject(operations)) {
|
|
6256
|
+
return `Expected ${where}.operations to be an object.`;
|
|
6257
|
+
}
|
|
6258
|
+
for (const [id, requirement] of Object.entries(operations)) {
|
|
6259
|
+
if (requirement === false)
|
|
6260
|
+
continue;
|
|
6261
|
+
const requirementBad = requirementIssue(requirement, schemes, `${where}.operations["${id}"]`);
|
|
6262
|
+
if (requirementBad)
|
|
6263
|
+
return requirementBad;
|
|
6264
|
+
}
|
|
6265
|
+
}
|
|
6266
|
+
if (security.default !== undefined) {
|
|
6267
|
+
return requirementIssue(security.default, schemes, `${where}.default`);
|
|
6268
|
+
}
|
|
6269
|
+
return;
|
|
6270
|
+
}
|
|
6271
|
+
var ROUTE_OPTION_KEYS2 = [
|
|
6272
|
+
"basePath",
|
|
6273
|
+
"route",
|
|
6274
|
+
"responses",
|
|
6275
|
+
"requestId",
|
|
6276
|
+
"security"
|
|
6277
|
+
];
|
|
4710
6278
|
function routeOptionsIssue2(options, where) {
|
|
4711
|
-
const { basePath, route } = options;
|
|
4712
|
-
if (basePath !== undefined && (typeof basePath !== "string" || basePath !== "" && !isPath(basePath))) {
|
|
6279
|
+
const { basePath, route, responses, requestId, security } = options;
|
|
6280
|
+
if (basePath !== undefined && (typeof basePath !== "string" || basePath !== "" && !isPath(basePath, false))) {
|
|
4713
6281
|
return `Expected ${where}.basePath to be "" or a path such as "/api".`;
|
|
4714
6282
|
}
|
|
4715
6283
|
if (route !== undefined) {
|
|
4716
6284
|
if (!isObject(route))
|
|
4717
6285
|
return `Expected ${where}.route to be an object.`;
|
|
4718
6286
|
for (const [id, entry] of Object.entries(route)) {
|
|
4719
|
-
if (!isObject(entry) || onlyKeys(entry, ["method", "path"], "route")) {
|
|
4720
|
-
return `Expected ${where}.route["${id}"] to be { method?, path? }.`;
|
|
6287
|
+
if (!isObject(entry) || onlyKeys(entry, ["method", "path", "status"], "route")) {
|
|
6288
|
+
return `Expected ${where}.route["${id}"] to be { method?, path?, status? }.`;
|
|
4721
6289
|
}
|
|
4722
6290
|
if (entry.method !== undefined && !METHODS3.includes(entry.method)) {
|
|
4723
6291
|
return `Expected ${where}.route["${id}"].method to be one of ${METHODS3.join(", ")}.`;
|
|
4724
6292
|
}
|
|
4725
|
-
if (entry.path !== undefined
|
|
4726
|
-
|
|
6293
|
+
if (entry.path !== undefined) {
|
|
6294
|
+
if (!isPath(entry.path)) {
|
|
6295
|
+
return `Expected ${where}.route["${id}"].path to start with "/", with parameters only as whole "{name}" segments and no query.`;
|
|
6296
|
+
}
|
|
6297
|
+
const names = parametersOfPath(entry.path);
|
|
6298
|
+
if (new Set(names).size !== names.length) {
|
|
6299
|
+
return `Expected ${where}.route["${id}"].path to name each parameter once.`;
|
|
6300
|
+
}
|
|
6301
|
+
}
|
|
6302
|
+
if (entry.status !== undefined && !SUCCESS_STATUSES.includes(entry.status)) {
|
|
6303
|
+
return `Expected ${where}.route["${id}"].status to be one of ${SUCCESS_STATUSES.join(", ")}.`;
|
|
6304
|
+
}
|
|
6305
|
+
}
|
|
6306
|
+
}
|
|
6307
|
+
if (responses !== undefined) {
|
|
6308
|
+
if (!isObject(responses)) {
|
|
6309
|
+
return `Expected ${where}.responses to be an object.`;
|
|
6310
|
+
}
|
|
6311
|
+
for (const [id, entry] of Object.entries(responses)) {
|
|
6312
|
+
const at = `${where}.responses["${id}"]`;
|
|
6313
|
+
if (!isObject(entry) || onlyKeys(entry, ["headers"], at) || entry.headers !== undefined && !isObject(entry.headers)) {
|
|
6314
|
+
return `Expected ${at} to be { headers?: { name: value } }.`;
|
|
6315
|
+
}
|
|
6316
|
+
for (const [name, value] of Object.entries(entry.headers ?? {})) {
|
|
6317
|
+
if (!HEADER_NAME.test(name)) {
|
|
6318
|
+
return `Expected ${at}.headers to be named by header names, not "${name}".`;
|
|
6319
|
+
}
|
|
6320
|
+
if (name.toLowerCase() === "content-type") {
|
|
6321
|
+
return `${at}.headers cannot set "content-type": the sink sets it.`;
|
|
6322
|
+
}
|
|
6323
|
+
if (!isHeaderValue(value)) {
|
|
6324
|
+
return `Expected ${at}.headers["${name}"] to be text without line breaks.`;
|
|
6325
|
+
}
|
|
4727
6326
|
}
|
|
4728
6327
|
}
|
|
4729
6328
|
}
|
|
6329
|
+
if (requestId !== undefined && requestId !== true && (typeof requestId !== "string" || !HEADER_NAME.test(requestId))) {
|
|
6330
|
+
return `Expected ${where}.requestId to be true or a header name.`;
|
|
6331
|
+
}
|
|
6332
|
+
if (security !== undefined) {
|
|
6333
|
+
return securityIssue(security, `${where}.security`);
|
|
6334
|
+
}
|
|
4730
6335
|
return;
|
|
4731
6336
|
}
|
|
4732
|
-
function
|
|
4733
|
-
|
|
6337
|
+
function requestIdHeaderOf2(options) {
|
|
6338
|
+
return options.requestId === true ? REQUEST_ID_HEADER : options.requestId ?? undefined;
|
|
6339
|
+
}
|
|
6340
|
+
function validate3(options) {
|
|
6341
|
+
const bad = onlyKeys(options, ["info", "servers", ...ROUTE_OPTION_KEYS2], "openapi");
|
|
4734
6342
|
if (bad)
|
|
4735
6343
|
return bad;
|
|
4736
|
-
const { info, servers
|
|
6344
|
+
const { info, servers } = options;
|
|
4737
6345
|
if (!isObject(info))
|
|
4738
6346
|
return "Expected openapi.info to be an object.";
|
|
4739
6347
|
const infoBad = onlyKeys(info, ["title", "version", "description"], "info");
|
|
@@ -4756,21 +6364,38 @@ function validate2(options) {
|
|
|
4756
6364
|
}
|
|
4757
6365
|
}
|
|
4758
6366
|
}
|
|
4759
|
-
return routeOptionsIssue2(
|
|
6367
|
+
return routeOptionsIssue2(options, "openapi");
|
|
4760
6368
|
}
|
|
4761
|
-
function
|
|
6369
|
+
function invalid9(reason, source = "openapi") {
|
|
4762
6370
|
throw new InvalidOutputStage2({ source, details: { reason } });
|
|
4763
6371
|
}
|
|
4764
6372
|
function kebab(text) {
|
|
4765
6373
|
return text.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
4766
6374
|
}
|
|
6375
|
+
function isScalarSchema(schema) {
|
|
6376
|
+
const types = [schema.type ?? []].flat();
|
|
6377
|
+
return !types.some((type) => type === "object" || type === "array");
|
|
6378
|
+
}
|
|
6379
|
+
function isScalarKey(operation, name) {
|
|
6380
|
+
if (!Object.hasOwn(operation.input.properties, name))
|
|
6381
|
+
return false;
|
|
6382
|
+
const field = operation.input.properties[name];
|
|
6383
|
+
return isProperty2(field) && isScalarSchema(toOpenAPI2(field));
|
|
6384
|
+
}
|
|
4767
6385
|
function operationRoutes2(operations, options, source = "openapi") {
|
|
4768
6386
|
const base = options.basePath ?? "";
|
|
4769
6387
|
const route = options.route ?? {};
|
|
6388
|
+
const responses = options.responses ?? {};
|
|
4770
6389
|
const ids = new Set(operations.map(({ id }) => id));
|
|
4771
|
-
for (const
|
|
4772
|
-
|
|
4773
|
-
|
|
6390
|
+
for (const [where, entries] of [
|
|
6391
|
+
["route", route],
|
|
6392
|
+
["responses", responses],
|
|
6393
|
+
["security.operations", options.security?.operations ?? {}]
|
|
6394
|
+
]) {
|
|
6395
|
+
for (const id of Object.keys(entries)) {
|
|
6396
|
+
if (!ids.has(id)) {
|
|
6397
|
+
invalid9(`${source}.${where} names the unknown operation "${id}".`, source);
|
|
6398
|
+
}
|
|
4774
6399
|
}
|
|
4775
6400
|
}
|
|
4776
6401
|
const routes = new Map;
|
|
@@ -4779,12 +6404,24 @@ function operationRoutes2(operations, options, source = "openapi") {
|
|
|
4779
6404
|
const chosen = route[operation.id] ?? {};
|
|
4780
6405
|
const method = chosen.method ?? "post";
|
|
4781
6406
|
const path = base + (chosen.path ?? `/${kebab(operation.command)}/${kebab(operation.name)}`);
|
|
4782
|
-
const
|
|
6407
|
+
const parameters = parametersOfPath(path);
|
|
6408
|
+
for (const name of parameters) {
|
|
6409
|
+
if (!isScalarKey(operation, name)) {
|
|
6410
|
+
invalid9(`${source}.route["${operation.id}"].path has the parameter "{${name}}", which is not a scalar key of the input of ${operation.id}.`, source);
|
|
6411
|
+
}
|
|
6412
|
+
}
|
|
6413
|
+
const key = `${method} ${path.replace(/\{[^}]*\}/g, "{}")}`;
|
|
4783
6414
|
if (taken.has(key)) {
|
|
4784
|
-
|
|
6415
|
+
invalid9(`Two operations are routed to ${method.toUpperCase()} ${path}.`, source);
|
|
4785
6416
|
}
|
|
4786
6417
|
taken.add(key);
|
|
4787
|
-
routes.set(operation.id, {
|
|
6418
|
+
routes.set(operation.id, {
|
|
6419
|
+
method,
|
|
6420
|
+
path,
|
|
6421
|
+
parameters,
|
|
6422
|
+
status: chosen.status ?? 200,
|
|
6423
|
+
headers: { ...responses[operation.id]?.headers ?? {} }
|
|
6424
|
+
});
|
|
4788
6425
|
}
|
|
4789
6426
|
return routes;
|
|
4790
6427
|
}
|
|
@@ -4847,7 +6484,13 @@ var STATUS_TEXT = {
|
|
|
4847
6484
|
422: "Declared error of the operation.",
|
|
4848
6485
|
500: "Internal error."
|
|
4849
6486
|
};
|
|
4850
|
-
|
|
6487
|
+
var SUCCESS_TEXT = {
|
|
6488
|
+
200: "Success.",
|
|
6489
|
+
201: "Created.",
|
|
6490
|
+
202: "Accepted.",
|
|
6491
|
+
204: "No content."
|
|
6492
|
+
};
|
|
6493
|
+
function responsesOf(operation, statuses, response, route, requestIdHeader) {
|
|
4851
6494
|
const names = new Map([
|
|
4852
6495
|
[400, []],
|
|
4853
6496
|
[500, []]
|
|
@@ -4865,10 +6508,22 @@ function responsesOf(operation, statuses, response) {
|
|
|
4865
6508
|
list.push(Declared.name);
|
|
4866
6509
|
names.set(status, list);
|
|
4867
6510
|
}
|
|
6511
|
+
const idHeaders = requestIdHeader ? {
|
|
6512
|
+
[requestIdHeader]: {
|
|
6513
|
+
description: "The id of the request: the one it sent in this header, else a new one.",
|
|
6514
|
+
schema: { type: "string" }
|
|
6515
|
+
}
|
|
6516
|
+
} : {};
|
|
6517
|
+
const successHeaders = { ...idHeaders };
|
|
6518
|
+
for (const [name, value] of Object.entries(route.headers)) {
|
|
6519
|
+
successHeaders[name] = { schema: { type: "string", const: value } };
|
|
6520
|
+
}
|
|
6521
|
+
const withHeaders = (headers) => Object.keys(headers).length > 0 ? { headers } : {};
|
|
4868
6522
|
const responses = {
|
|
4869
|
-
|
|
4870
|
-
description:
|
|
4871
|
-
|
|
6523
|
+
[String(route.status)]: {
|
|
6524
|
+
description: SUCCESS_TEXT[route.status],
|
|
6525
|
+
...withHeaders(successHeaders),
|
|
6526
|
+
...route.status === 204 ? {} : { content: { "application/json": { schema: response } } }
|
|
4872
6527
|
}
|
|
4873
6528
|
};
|
|
4874
6529
|
for (const status of [...names.keys()].sort((a, b) => a - b)) {
|
|
@@ -4876,34 +6531,55 @@ function responsesOf(operation, statuses, response) {
|
|
|
4876
6531
|
const text = STATUS_TEXT[status] ?? `Error ${status}.`;
|
|
4877
6532
|
responses[String(status)] = {
|
|
4878
6533
|
description: declared.length > 0 ? `${text} Declared: ${declared.join(", ")}.` : text,
|
|
6534
|
+
...withHeaders(idHeaders),
|
|
4879
6535
|
content: problemContent
|
|
4880
6536
|
};
|
|
4881
6537
|
}
|
|
4882
6538
|
return responses;
|
|
4883
6539
|
}
|
|
4884
|
-
function
|
|
6540
|
+
function parameterOf(name, property, where, required) {
|
|
6541
|
+
const { description, ...rest } = property;
|
|
6542
|
+
return {
|
|
6543
|
+
name,
|
|
6544
|
+
in: where,
|
|
6545
|
+
required: where === "path" || required,
|
|
6546
|
+
...typeof description === "string" ? { description } : {},
|
|
6547
|
+
schema: rest
|
|
6548
|
+
};
|
|
6549
|
+
}
|
|
6550
|
+
function queryParametersOf(operation, schema) {
|
|
4885
6551
|
const required = new Set(schema.required ?? []);
|
|
4886
6552
|
return Object.entries(schema.properties ?? {}).map(([name, property]) => {
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
if (!isProperty2(field) || type === "object" || type === "array" || Array.isArray(type) && type.some((t) => t === "object" || t === "array")) {
|
|
4890
|
-
invalid8(`${operation.id} uses a ${"get/delete"} route, whose input becomes query parameters, but "${name}" is not a scalar; route it with method "post".`);
|
|
6553
|
+
if (!isProperty2(operation.input.properties[name]) || !isScalarSchema(property)) {
|
|
6554
|
+
invalid9(`${operation.id} uses a get/delete route, whose input becomes query parameters, but "${name}" is not a scalar; route it with method "post".`);
|
|
4891
6555
|
}
|
|
4892
|
-
|
|
4893
|
-
return {
|
|
4894
|
-
name,
|
|
4895
|
-
in: "query",
|
|
4896
|
-
required: required.has(name),
|
|
4897
|
-
...typeof description === "string" ? { description } : {},
|
|
4898
|
-
schema: rest
|
|
4899
|
-
};
|
|
6556
|
+
return parameterOf(name, property, "query", required.has(name));
|
|
4900
6557
|
});
|
|
4901
6558
|
}
|
|
6559
|
+
function withoutKeys(schema, keys) {
|
|
6560
|
+
if (keys.length === 0)
|
|
6561
|
+
return schema;
|
|
6562
|
+
const properties = { ...schema.properties ?? {} };
|
|
6563
|
+
for (const key of keys)
|
|
6564
|
+
delete properties[key];
|
|
6565
|
+
const required = (schema.required ?? []).filter((key) => !keys.includes(key));
|
|
6566
|
+
const { required: _, ...rest } = schema;
|
|
6567
|
+
return {
|
|
6568
|
+
...rest,
|
|
6569
|
+
properties,
|
|
6570
|
+
...required.length > 0 ? { required } : {}
|
|
6571
|
+
};
|
|
6572
|
+
}
|
|
6573
|
+
function requirementOf(requirement) {
|
|
6574
|
+
return { [requirement.scheme]: [...requirement.scopes ?? []] };
|
|
6575
|
+
}
|
|
4902
6576
|
var openapi2 = sinkOf2({
|
|
4903
6577
|
name: "openapi",
|
|
4904
|
-
validate:
|
|
6578
|
+
validate: validate3,
|
|
4905
6579
|
build: (operations, options, { output }) => {
|
|
4906
6580
|
const routes = operationRoutes2(operations, options);
|
|
6581
|
+
const requestIdHeader = requestIdHeaderOf2(options);
|
|
6582
|
+
const security = options.security;
|
|
4907
6583
|
const records = new Set;
|
|
4908
6584
|
for (const operation of operations) {
|
|
4909
6585
|
collectRecords(operation.input.properties, records);
|
|
@@ -4913,11 +6589,11 @@ var openapi2 = sinkOf2({
|
|
|
4913
6589
|
const classes = new Map;
|
|
4914
6590
|
for (const record of records) {
|
|
4915
6591
|
const name = recordNameOf2(record);
|
|
4916
|
-
if (
|
|
4917
|
-
|
|
6592
|
+
if (!COMPONENT_NAME.test(name) || name === "Problem") {
|
|
6593
|
+
invalid9(`The record class "${name}" cannot be a component name; give the class a name that is not "Problem".`);
|
|
4918
6594
|
}
|
|
4919
6595
|
if (classes.has(name)) {
|
|
4920
|
-
|
|
6596
|
+
invalid9(`Two record classes are named "${name}"; rename one.`);
|
|
4921
6597
|
}
|
|
4922
6598
|
classes.set(name, record);
|
|
4923
6599
|
models[name] = record;
|
|
@@ -4934,40 +6610,52 @@ var openapi2 = sinkOf2({
|
|
|
4934
6610
|
components[name] = schemas[name];
|
|
4935
6611
|
components.Problem = problemSchema();
|
|
4936
6612
|
for (const operation of operations) {
|
|
4937
|
-
const
|
|
6613
|
+
const route = routes.get(operation.id);
|
|
6614
|
+
const { method, path } = route;
|
|
4938
6615
|
const operationId = operation.id.replaceAll(".", "_");
|
|
4939
6616
|
if (operationIds.has(operationId)) {
|
|
4940
|
-
|
|
6617
|
+
invalid9(`The operationId "${operationId}" is produced twice.`);
|
|
4941
6618
|
}
|
|
4942
6619
|
operationIds.add(operationId);
|
|
4943
6620
|
const slot = paths[path] ?? {};
|
|
4944
6621
|
paths[path] = slot;
|
|
4945
|
-
const
|
|
6622
|
+
const full = schemas[`${operation.id}#input`];
|
|
6623
|
+
const input = withoutKeys(full, route.parameters);
|
|
4946
6624
|
const response = schemas[`${operation.id}#response`];
|
|
4947
6625
|
const hasBody = method !== "get" && method !== "delete";
|
|
4948
6626
|
const hasInput = Object.keys(input.properties ?? {}).length > 0;
|
|
6627
|
+
const parameters = [
|
|
6628
|
+
...route.parameters.map((name) => parameterOf(name, full.properties?.[name], "path", true)),
|
|
6629
|
+
...hasBody ? [] : queryParametersOf(operation, input)
|
|
6630
|
+
];
|
|
6631
|
+
const requirement = security?.operations?.[operation.id];
|
|
4949
6632
|
slot[method] = {
|
|
4950
6633
|
operationId,
|
|
4951
6634
|
summary: operation.id,
|
|
4952
6635
|
...operation.description ? { description: operation.description } : {},
|
|
4953
6636
|
...operation.tags.length > 0 ? { tags: [...operation.tags] } : {},
|
|
4954
6637
|
...operation.deprecated ? { deprecated: true } : {},
|
|
4955
|
-
...!hasBody ? { parameters
|
|
6638
|
+
...!hasBody || parameters.length > 0 ? { parameters } : {},
|
|
4956
6639
|
...hasBody && hasInput ? {
|
|
4957
6640
|
requestBody: {
|
|
4958
6641
|
required: true,
|
|
4959
6642
|
content: { "application/json": { schema: input } }
|
|
4960
6643
|
}
|
|
4961
6644
|
} : {},
|
|
4962
|
-
responses: responsesOf(operation, output.statuses, response)
|
|
6645
|
+
responses: responsesOf(operation, output.statuses, response, route, requestIdHeader),
|
|
6646
|
+
...requirement === false ? { security: [] } : requirement ? { security: [requirementOf(requirement)] } : {}
|
|
4963
6647
|
};
|
|
4964
6648
|
}
|
|
4965
6649
|
return {
|
|
4966
6650
|
openapi: "3.1.0",
|
|
4967
6651
|
info: options.info,
|
|
4968
6652
|
...options.servers ? { servers: options.servers } : {},
|
|
6653
|
+
...security?.default ? { security: [requirementOf(security.default)] } : {},
|
|
4969
6654
|
paths,
|
|
4970
|
-
components: {
|
|
6655
|
+
components: {
|
|
6656
|
+
schemas: components,
|
|
6657
|
+
...security ? { securitySchemes: security.schemes } : {}
|
|
6658
|
+
}
|
|
4971
6659
|
};
|
|
4972
6660
|
}
|
|
4973
6661
|
});
|
|
@@ -4999,18 +6687,42 @@ var select2 = transformOf2({
|
|
|
4999
6687
|
return operation;
|
|
5000
6688
|
}
|
|
5001
6689
|
});
|
|
5002
|
-
// src/core/
|
|
5003
|
-
function
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
6690
|
+
// src/core/parse/strip.ts
|
|
6691
|
+
function collectionOf(properties, value) {
|
|
6692
|
+
if (!isPlainObject(value))
|
|
6693
|
+
return value;
|
|
6694
|
+
const kept = {};
|
|
6695
|
+
for (const [name, field] of fieldsOf(properties)) {
|
|
6696
|
+
if (!Object.hasOwn(value, name))
|
|
6697
|
+
continue;
|
|
6698
|
+
const item = fieldOf2(field, value[name]);
|
|
6699
|
+
if (item === undefined)
|
|
6700
|
+
continue;
|
|
6701
|
+
Object.defineProperty(kept, name, {
|
|
6702
|
+
value: item,
|
|
6703
|
+
enumerable: true,
|
|
6704
|
+
writable: true,
|
|
6705
|
+
configurable: true
|
|
6706
|
+
});
|
|
6707
|
+
}
|
|
6708
|
+
return kept;
|
|
5013
6709
|
}
|
|
6710
|
+
function fieldOf2(field, value) {
|
|
6711
|
+
if (isBlueprintList2(field)) {
|
|
6712
|
+
return Array.isArray(value) ? value.map((item) => fieldOf2(field.item, item)) : value;
|
|
6713
|
+
}
|
|
6714
|
+
if (isBlueprint2(field))
|
|
6715
|
+
return collectionOf(field.properties, value);
|
|
6716
|
+
const record = recordFieldOf2(field);
|
|
6717
|
+
if (record) {
|
|
6718
|
+
return collectionOf(record.record.blueprint.properties, value);
|
|
6719
|
+
}
|
|
6720
|
+
return value;
|
|
6721
|
+
}
|
|
6722
|
+
function stripUndeclared2(shape, value) {
|
|
6723
|
+
return fieldOf2(shape, value);
|
|
6724
|
+
}
|
|
6725
|
+
// src/core/record/plugin-catalog.ts
|
|
5014
6726
|
var LISTS2 = ["keys", "sensitive", "unique", "change", "members"];
|
|
5015
6727
|
function isDescriptor(value) {
|
|
5016
6728
|
if (typeof value !== "object" || value === null)
|
|
@@ -5120,37 +6832,37 @@ function recordPluginCatalogOf2(...plugins) {
|
|
|
5120
6832
|
});
|
|
5121
6833
|
}
|
|
5122
6834
|
// src/std/properties/contact.ts
|
|
5123
|
-
import { Type } from "typebox";
|
|
6835
|
+
import { Type as Type2 } from "typebox";
|
|
5124
6836
|
var Email2 = propertyOf2({
|
|
5125
6837
|
name: "email",
|
|
5126
|
-
schema:
|
|
6838
|
+
schema: Type2.String({ format: "email", maxLength: 254 }),
|
|
5127
6839
|
default: "demo@example.com",
|
|
5128
6840
|
preHandle: (value) => value.trim().toLowerCase()
|
|
5129
6841
|
});
|
|
5130
6842
|
var Phone2 = propertyOf2({
|
|
5131
6843
|
name: "phone",
|
|
5132
|
-
schema:
|
|
6844
|
+
schema: Type2.String({ pattern: "^\\+[1-9]\\d{1,14}$" }),
|
|
5133
6845
|
default: "+5511999999999",
|
|
5134
6846
|
preHandle: (value) => value.replace(/[\s().-]/g, "")
|
|
5135
6847
|
});
|
|
5136
6848
|
var Url2 = propertyOf2({
|
|
5137
6849
|
name: "url",
|
|
5138
|
-
schema:
|
|
6850
|
+
schema: Type2.String({ format: "url", maxLength: 2048 }),
|
|
5139
6851
|
default: "https://example.com",
|
|
5140
6852
|
preHandle: (value) => value.trim()
|
|
5141
6853
|
});
|
|
5142
6854
|
var Ip2 = propertyOf2({
|
|
5143
6855
|
name: "ip",
|
|
5144
|
-
schema:
|
|
5145
|
-
|
|
5146
|
-
|
|
6856
|
+
schema: Type2.Union([
|
|
6857
|
+
Type2.String({ format: "ipv4" }),
|
|
6858
|
+
Type2.String({ format: "ipv6" })
|
|
5147
6859
|
]),
|
|
5148
6860
|
default: "127.0.0.1",
|
|
5149
6861
|
preHandle: (value) => value.trim()
|
|
5150
6862
|
});
|
|
5151
6863
|
|
|
5152
6864
|
// src/std/properties/enum.ts
|
|
5153
|
-
import { Type as
|
|
6865
|
+
import { Type as Type3 } from "typebox";
|
|
5154
6866
|
function enumOf2(values, {
|
|
5155
6867
|
name = "enum",
|
|
5156
6868
|
optional,
|
|
@@ -5158,7 +6870,7 @@ function enumOf2(values, {
|
|
|
5158
6870
|
} = {}) {
|
|
5159
6871
|
const Enum = propertyOf2({
|
|
5160
6872
|
name,
|
|
5161
|
-
schema:
|
|
6873
|
+
schema: Type3.Enum(values),
|
|
5162
6874
|
optional,
|
|
5163
6875
|
default: _default
|
|
5164
6876
|
});
|
|
@@ -5176,9 +6888,9 @@ function enumOf2(values, {
|
|
|
5176
6888
|
}
|
|
5177
6889
|
|
|
5178
6890
|
// src/std/properties/headless.ts
|
|
5179
|
-
import { Type as
|
|
6891
|
+
import { Type as Type4 } from "typebox";
|
|
5180
6892
|
import { Meta } from "typebox/schema";
|
|
5181
|
-
var JSON_SCHEMA =
|
|
6893
|
+
var JSON_SCHEMA = Type4.Unsafe({
|
|
5182
6894
|
type: "object",
|
|
5183
6895
|
allOf: [Meta["https://json-schema.org/draft/2020-12/schema"]]
|
|
5184
6896
|
});
|
|
@@ -5186,7 +6898,7 @@ var JSON_SCHEMA = Type3.Unsafe({
|
|
|
5186
6898
|
class SchemaProperty2 extends propertyOf2({
|
|
5187
6899
|
name: "schema",
|
|
5188
6900
|
schema: JSON_SCHEMA,
|
|
5189
|
-
default:
|
|
6901
|
+
default: Type4.String()
|
|
5190
6902
|
}) {
|
|
5191
6903
|
compiled;
|
|
5192
6904
|
static deserialize(json) {
|
|
@@ -5221,7 +6933,7 @@ class SchemaProperty2 extends propertyOf2({
|
|
|
5221
6933
|
|
|
5222
6934
|
// src/std/properties/identity.ts
|
|
5223
6935
|
import slugify from "slugify";
|
|
5224
|
-
import { Type as
|
|
6936
|
+
import { Type as Type5 } from "typebox";
|
|
5225
6937
|
|
|
5226
6938
|
// src/std/runtime.ts
|
|
5227
6939
|
var PasswordHasherRequired2 = exceptionOf2({
|
|
@@ -5417,7 +7129,7 @@ var UUID_V7 = "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{
|
|
|
5417
7129
|
|
|
5418
7130
|
class Id2 extends propertyOf2({
|
|
5419
7131
|
name: "id",
|
|
5420
|
-
schema:
|
|
7132
|
+
schema: Type5.String({ pattern: UUID_V7 }),
|
|
5421
7133
|
default: "01a173d2-051c-77bb-8166-a371e999573e",
|
|
5422
7134
|
preHandle: (value) => value.toLowerCase()
|
|
5423
7135
|
}) {
|
|
@@ -5428,7 +7140,7 @@ class Id2 extends propertyOf2({
|
|
|
5428
7140
|
|
|
5429
7141
|
class Slug2 extends propertyOf2({
|
|
5430
7142
|
name: "slug",
|
|
5431
|
-
schema:
|
|
7143
|
+
schema: Type5.String({
|
|
5432
7144
|
pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$",
|
|
5433
7145
|
maxLength: 255
|
|
5434
7146
|
}),
|
|
@@ -5438,8 +7150,183 @@ class Slug2 extends propertyOf2({
|
|
|
5438
7150
|
}) {
|
|
5439
7151
|
}
|
|
5440
7152
|
|
|
7153
|
+
// src/std/properties/json.ts
|
|
7154
|
+
import { Type as Type6 } from "typebox";
|
|
7155
|
+
import { Meta as Meta3 } from "typebox/schema";
|
|
7156
|
+
import Value from "typebox/value";
|
|
7157
|
+
|
|
7158
|
+
// src/std/properties/exceptions.ts
|
|
7159
|
+
var InvalidJsonData2 = exceptionOf2({
|
|
7160
|
+
layer: "property" /* Property */,
|
|
7161
|
+
name: "InvalidJsonData",
|
|
7162
|
+
message: ({ issues }, source) => `Invalid JSON data for "${source}": ${issues.map((issue) => `${issue.path.length === 0 ? "/" : `/${issue.path.join("/")}`}: ${issue.message}`).join("; ")}`
|
|
7163
|
+
});
|
|
7164
|
+
var InvalidJsonSchema2 = exceptionOf2({
|
|
7165
|
+
layer: "schema" /* Schema */,
|
|
7166
|
+
name: "InvalidJsonSchema",
|
|
7167
|
+
message: (_, source) => `The schema of "${source}" is not a JSON Schema.`
|
|
7168
|
+
});
|
|
7169
|
+
markExported(InvalidJsonData2, InvalidJsonSchema2);
|
|
7170
|
+
|
|
7171
|
+
// src/std/properties/json.ts
|
|
7172
|
+
function notJson(value, path = [], ancestors = []) {
|
|
7173
|
+
if (value === null)
|
|
7174
|
+
return;
|
|
7175
|
+
switch (typeof value) {
|
|
7176
|
+
case "string":
|
|
7177
|
+
case "boolean":
|
|
7178
|
+
return;
|
|
7179
|
+
case "number":
|
|
7180
|
+
return Number.isFinite(value) ? undefined : { path, message: `is not JSON (${value})` };
|
|
7181
|
+
case "object":
|
|
7182
|
+
break;
|
|
7183
|
+
default:
|
|
7184
|
+
return { path, message: `is not JSON (${typeof value})` };
|
|
7185
|
+
}
|
|
7186
|
+
if (ancestors.includes(value))
|
|
7187
|
+
return { path, message: "is circular" };
|
|
7188
|
+
const inner = [...ancestors, value];
|
|
7189
|
+
if (Array.isArray(value)) {
|
|
7190
|
+
for (let index = 0;index < value.length; index++) {
|
|
7191
|
+
const found = notJson(value[index], [...path, index], inner);
|
|
7192
|
+
if (found)
|
|
7193
|
+
return found;
|
|
7194
|
+
}
|
|
7195
|
+
return;
|
|
7196
|
+
}
|
|
7197
|
+
const prototype = Object.getPrototypeOf(value);
|
|
7198
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
7199
|
+
return {
|
|
7200
|
+
path,
|
|
7201
|
+
message: `is not JSON (an instance of ${prototype?.constructor?.name ?? "a class"})`
|
|
7202
|
+
};
|
|
7203
|
+
}
|
|
7204
|
+
for (const [key, item] of Object.entries(value)) {
|
|
7205
|
+
const found = notJson(item, [...path, key], inner);
|
|
7206
|
+
if (found)
|
|
7207
|
+
return found;
|
|
7208
|
+
}
|
|
7209
|
+
return;
|
|
7210
|
+
}
|
|
7211
|
+
var JSON_SCHEMA2 = Type6.Unsafe({
|
|
7212
|
+
type: "object",
|
|
7213
|
+
allOf: [Meta3["https://json-schema.org/draft/2020-12/schema"]]
|
|
7214
|
+
});
|
|
7215
|
+
var checked = new WeakSet;
|
|
7216
|
+
function assertJsonSchema(schema, source) {
|
|
7217
|
+
if (typeof schema === "object" && schema !== null && checked.has(schema)) {
|
|
7218
|
+
return schema;
|
|
7219
|
+
}
|
|
7220
|
+
if (!Value.Check(JSON_SCHEMA2, schema)) {
|
|
7221
|
+
throw new InvalidJsonSchema2({ source });
|
|
7222
|
+
}
|
|
7223
|
+
checked.add(schema);
|
|
7224
|
+
return schema;
|
|
7225
|
+
}
|
|
7226
|
+
function pathOf(pointer) {
|
|
7227
|
+
if (pointer === "")
|
|
7228
|
+
return [];
|
|
7229
|
+
return pointer.slice(1).split("/").map((segment) => segment.replaceAll("~1", "/").replaceAll("~0", "~")).map((segment) => /^(?:0|[1-9][0-9]*)$/.test(segment) ? +segment : segment);
|
|
7230
|
+
}
|
|
7231
|
+
function issuesOfErrors(errors, source) {
|
|
7232
|
+
const issues = [];
|
|
7233
|
+
const add = (path, code, message) => {
|
|
7234
|
+
issues.push({ path, code, message, source });
|
|
7235
|
+
};
|
|
7236
|
+
for (const error of errors) {
|
|
7237
|
+
const path = pathOf(error.instancePath);
|
|
7238
|
+
const keys = (name) => Array.isArray(error.params[name]) ? error.params[name] : [];
|
|
7239
|
+
if (error.keyword === "required") {
|
|
7240
|
+
for (const key of keys("requiredProperties")) {
|
|
7241
|
+
add([...path, key], "required", "is required");
|
|
7242
|
+
}
|
|
7243
|
+
} else if (error.keyword === "additionalProperties") {
|
|
7244
|
+
for (const key of keys("additionalProperties")) {
|
|
7245
|
+
add([...path, key], "undeclared_key", "is not declared");
|
|
7246
|
+
}
|
|
7247
|
+
} else if (!(error.keyword === "boolean" && error.schemaPath.endsWith("/additionalProperties"))) {
|
|
7248
|
+
add(path, error.keyword === "type" ? "invalid_type" : "invalid_value", error.message);
|
|
7249
|
+
}
|
|
7250
|
+
}
|
|
7251
|
+
return issues;
|
|
7252
|
+
}
|
|
7253
|
+
function jsonSchemaIssues2(schema, value, { source = "json" } = {}) {
|
|
7254
|
+
const checkedSchema = assertJsonSchema(schema, source);
|
|
7255
|
+
const found = notJson(value);
|
|
7256
|
+
if (found) {
|
|
7257
|
+
return [
|
|
7258
|
+
{
|
|
7259
|
+
path: found.path,
|
|
7260
|
+
code: "invalid_value",
|
|
7261
|
+
message: found.message,
|
|
7262
|
+
source
|
|
7263
|
+
}
|
|
7264
|
+
];
|
|
7265
|
+
}
|
|
7266
|
+
if (Value.Check(checkedSchema, value))
|
|
7267
|
+
return [];
|
|
7268
|
+
return issuesOfErrors(Value.Errors(checkedSchema, value), source);
|
|
7269
|
+
}
|
|
7270
|
+
function checkJsonSchema2(schema, value, options = {}) {
|
|
7271
|
+
const issues = jsonSchemaIssues2(schema, value, options);
|
|
7272
|
+
if (issues.length > 0) {
|
|
7273
|
+
throw new InvalidJsonData2({
|
|
7274
|
+
source: options.source ?? "json",
|
|
7275
|
+
details: { issues }
|
|
7276
|
+
});
|
|
7277
|
+
}
|
|
7278
|
+
return value;
|
|
7279
|
+
}
|
|
7280
|
+
function demoOf(schema) {
|
|
7281
|
+
try {
|
|
7282
|
+
return Value.Create(schema);
|
|
7283
|
+
} catch {
|
|
7284
|
+
return;
|
|
7285
|
+
}
|
|
7286
|
+
}
|
|
7287
|
+
function assertJson(value) {
|
|
7288
|
+
const found = notJson(value);
|
|
7289
|
+
if (found) {
|
|
7290
|
+
throw new Error(`${found.path.length === 0 ? "" : `/${found.path.join("/")}: `}${found.message}`);
|
|
7291
|
+
}
|
|
7292
|
+
return value;
|
|
7293
|
+
}
|
|
7294
|
+
var JsonProperty2 = propertyOf2({
|
|
7295
|
+
name: "json",
|
|
7296
|
+
schema: Type6.Unsafe({}),
|
|
7297
|
+
default: {},
|
|
7298
|
+
preHandle: assertJson
|
|
7299
|
+
});
|
|
7300
|
+
function jsonOf2({
|
|
7301
|
+
schema,
|
|
7302
|
+
name = "json",
|
|
7303
|
+
optional,
|
|
7304
|
+
default: _default
|
|
7305
|
+
}) {
|
|
7306
|
+
assertJsonSchema(schema, name);
|
|
7307
|
+
const made = _default ?? demoOf(schema);
|
|
7308
|
+
const Json = propertyOf2({
|
|
7309
|
+
name,
|
|
7310
|
+
schema,
|
|
7311
|
+
optional,
|
|
7312
|
+
...made !== undefined && Value.Check(schema, made) ? { default: made } : {},
|
|
7313
|
+
preHandle: assertJson
|
|
7314
|
+
});
|
|
7315
|
+
recordPropertyLineage(Json, {
|
|
7316
|
+
kind: "factory",
|
|
7317
|
+
factory: "jsonOf",
|
|
7318
|
+
args: Object.freeze({
|
|
7319
|
+
schema,
|
|
7320
|
+
...name === "json" ? {} : { name },
|
|
7321
|
+
...optional ? { optional: true } : {},
|
|
7322
|
+
..._default === undefined ? {} : { default: _default }
|
|
7323
|
+
})
|
|
7324
|
+
});
|
|
7325
|
+
return Json;
|
|
7326
|
+
}
|
|
7327
|
+
|
|
5441
7328
|
// src/std/properties/password.ts
|
|
5442
|
-
import { Type as
|
|
7329
|
+
import { Type as Type7 } from "typebox";
|
|
5443
7330
|
var PASSWORD_MIN_LENGTH2 = 7;
|
|
5444
7331
|
var PASSWORD_MAX_LENGTH2 = 128;
|
|
5445
7332
|
var PASSWORD_SPECIAL_CHARACTERS2 = "!@#$%^&*()-_=+[]{}|\\;:'\",.<>/?`~";
|
|
@@ -5467,13 +7354,30 @@ function passwordIssues2(value) {
|
|
|
5467
7354
|
}
|
|
5468
7355
|
return issues;
|
|
5469
7356
|
}
|
|
7357
|
+
var escaped = PASSWORD_SPECIAL_CHARACTERS2.replace(/[\\\]^-]/g, "\\$&");
|
|
7358
|
+
|
|
7359
|
+
class PlainPassword2 extends propertyOf2({
|
|
7360
|
+
name: "plainPassword",
|
|
7361
|
+
schema: Type7.String({
|
|
7362
|
+
minLength: PASSWORD_MIN_LENGTH2,
|
|
7363
|
+
maxLength: PASSWORD_MAX_LENGTH2,
|
|
7364
|
+
pattern: `^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[${escaped}])`
|
|
7365
|
+
}),
|
|
7366
|
+
default: "Demo@123",
|
|
7367
|
+
sensitive: true
|
|
7368
|
+
}) {
|
|
7369
|
+
}
|
|
5470
7370
|
|
|
5471
7371
|
class Password2 extends propertyOf2({
|
|
5472
7372
|
name: "password",
|
|
5473
|
-
schema:
|
|
7373
|
+
schema: Type7.String({ pattern: ARGON2_HASH }),
|
|
5474
7374
|
default: DEMO_HASH,
|
|
5475
7375
|
sensitive: true
|
|
5476
7376
|
}) {
|
|
7377
|
+
static [PropertyInput2] = {
|
|
7378
|
+
accepts: PlainPassword2,
|
|
7379
|
+
convert: async (plain) => (await Password2.hash(plain)).value
|
|
7380
|
+
};
|
|
5477
7381
|
static async hash(plain) {
|
|
5478
7382
|
const issues = passwordIssues2(plain);
|
|
5479
7383
|
if (issues.length > 0) {
|
|
@@ -5499,36 +7403,36 @@ class Password2 extends propertyOf2({
|
|
|
5499
7403
|
}
|
|
5500
7404
|
|
|
5501
7405
|
// src/std/properties/primitives.ts
|
|
5502
|
-
import { Type as
|
|
7406
|
+
import { Type as Type8 } from "typebox";
|
|
5503
7407
|
var StringProperty2 = propertyOf2({
|
|
5504
7408
|
name: "string",
|
|
5505
|
-
schema:
|
|
7409
|
+
schema: Type8.String(),
|
|
5506
7410
|
default: "demo"
|
|
5507
7411
|
});
|
|
5508
7412
|
var Text2 = propertyOf2({
|
|
5509
7413
|
name: "text",
|
|
5510
|
-
schema:
|
|
7414
|
+
schema: Type8.String({ minLength: 1 }),
|
|
5511
7415
|
default: "Demo",
|
|
5512
7416
|
preHandle: (value) => value.trim()
|
|
5513
7417
|
});
|
|
5514
7418
|
var NumberProperty2 = propertyOf2({
|
|
5515
7419
|
name: "number",
|
|
5516
|
-
schema:
|
|
7420
|
+
schema: Type8.Number(),
|
|
5517
7421
|
default: 0
|
|
5518
7422
|
});
|
|
5519
7423
|
var Integer2 = propertyOf2({
|
|
5520
7424
|
name: "integer",
|
|
5521
|
-
schema:
|
|
7425
|
+
schema: Type8.Integer(),
|
|
5522
7426
|
default: 0
|
|
5523
7427
|
});
|
|
5524
7428
|
var BooleanProperty2 = propertyOf2({
|
|
5525
7429
|
name: "boolean",
|
|
5526
|
-
schema:
|
|
7430
|
+
schema: Type8.Boolean(),
|
|
5527
7431
|
default: false
|
|
5528
7432
|
});
|
|
5529
7433
|
|
|
5530
7434
|
// src/std/properties/time.ts
|
|
5531
|
-
import { Type as
|
|
7435
|
+
import { Type as Type9 } from "typebox";
|
|
5532
7436
|
var DEMO_DATE_TIME = "2026-01-01T00:00:00.000Z";
|
|
5533
7437
|
function dateTimeOf({
|
|
5534
7438
|
name,
|
|
@@ -5537,7 +7441,7 @@ function dateTimeOf({
|
|
|
5537
7441
|
}) {
|
|
5538
7442
|
return class extends propertyOf2({
|
|
5539
7443
|
name,
|
|
5540
|
-
schema:
|
|
7444
|
+
schema: Type9.String({ format: "date-time" }),
|
|
5541
7445
|
optional,
|
|
5542
7446
|
default: _default
|
|
5543
7447
|
}) {
|
|
@@ -5554,7 +7458,7 @@ class DateTime2 extends dateTimeOf({
|
|
|
5554
7458
|
}
|
|
5555
7459
|
var DateProperty2 = propertyOf2({
|
|
5556
7460
|
name: "date",
|
|
5557
|
-
schema:
|
|
7461
|
+
schema: Type9.String({ format: "date" }),
|
|
5558
7462
|
default: "2026-01-01"
|
|
5559
7463
|
});
|
|
5560
7464
|
|
|
@@ -5577,6 +7481,12 @@ class DeletedAt2 extends dateTimeOf({
|
|
|
5577
7481
|
}) {
|
|
5578
7482
|
}
|
|
5579
7483
|
|
|
7484
|
+
class ExpiresAt2 extends dateTimeOf({
|
|
7485
|
+
name: "expiresAt",
|
|
7486
|
+
default: DEMO_DATE_TIME
|
|
7487
|
+
}) {
|
|
7488
|
+
}
|
|
7489
|
+
|
|
5580
7490
|
// src/std/blueprint-plugin.ts
|
|
5581
7491
|
var Std2 = blueprintPluginOf2("std", {
|
|
5582
7492
|
id: Id2,
|
|
@@ -5596,11 +7506,601 @@ var Std2 = blueprintPluginOf2("std", {
|
|
|
5596
7506
|
createdAt: CreatedAt2,
|
|
5597
7507
|
updatedAt: UpdatedAt2,
|
|
5598
7508
|
deletedAt: DeletedAt2,
|
|
5599
|
-
schema: SchemaProperty2
|
|
5600
|
-
|
|
7509
|
+
schema: SchemaProperty2,
|
|
7510
|
+
json: JsonProperty2
|
|
7511
|
+
}, { enumOf: enumOf2, jsonOf: jsonOf2 });
|
|
5601
7512
|
|
|
5602
7513
|
// src/std/blueprint.ts
|
|
5603
7514
|
var blueprintOf2 = blueprintBuilderOf2(Std2);
|
|
7515
|
+
// src/std/use-cases/shared.ts
|
|
7516
|
+
var fields = blueprintBuilderOf2();
|
|
7517
|
+
var IDENTIFIER4 = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
7518
|
+
var capitalize2 = (key) => `${key.charAt(0).toUpperCase()}${key.slice(1)}`;
|
|
7519
|
+
function literal(value) {
|
|
7520
|
+
if (typeof value === "string") {
|
|
7521
|
+
return value.startsWith("$") ? { literal: value } : value;
|
|
7522
|
+
}
|
|
7523
|
+
if (Array.isArray(value))
|
|
7524
|
+
return value.map(literal);
|
|
7525
|
+
if (isPlain2(value)) {
|
|
7526
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, literal(item)]));
|
|
7527
|
+
}
|
|
7528
|
+
return value;
|
|
7529
|
+
}
|
|
7530
|
+
function checkOptions(target, options, allowed) {
|
|
7531
|
+
const known = [...COMMON, ...allowed];
|
|
7532
|
+
for (const key of Object.keys(options)) {
|
|
7533
|
+
if (!known.includes(key)) {
|
|
7534
|
+
target.unavailable(`it has no option "${key}" (options: ${known.join(", ")})`);
|
|
7535
|
+
}
|
|
7536
|
+
}
|
|
7537
|
+
}
|
|
7538
|
+
var COMMON = ["name", "description", "tags", "deprecated", "errors"];
|
|
7539
|
+
var HOOKS = ["before", "after", "dependencies", "throws"];
|
|
7540
|
+
function metaOf(target, options, verb) {
|
|
7541
|
+
const { name, description, tags, deprecated } = options;
|
|
7542
|
+
if (name !== undefined && (typeof name !== "string" || name === "")) {
|
|
7543
|
+
target.unavailable("the option name must be a non-empty string");
|
|
7544
|
+
}
|
|
7545
|
+
return {
|
|
7546
|
+
name: name ?? `${verb}${target.entityName}`,
|
|
7547
|
+
...description === undefined ? {} : { description },
|
|
7548
|
+
...tags === undefined ? {} : { tags },
|
|
7549
|
+
...deprecated === undefined ? {} : { deprecated }
|
|
7550
|
+
};
|
|
7551
|
+
}
|
|
7552
|
+
function asOf(target, options) {
|
|
7553
|
+
const { as = "item" } = options;
|
|
7554
|
+
if (typeof as !== "string" || !IDENTIFIER4.test(as)) {
|
|
7555
|
+
target.unavailable("the option as must be an identifier");
|
|
7556
|
+
}
|
|
7557
|
+
return as;
|
|
7558
|
+
}
|
|
7559
|
+
function errorOf(target, options, kind, fallback) {
|
|
7560
|
+
const { errors } = options;
|
|
7561
|
+
if (errors === undefined)
|
|
7562
|
+
return fallback;
|
|
7563
|
+
if (!isPlain2(errors)) {
|
|
7564
|
+
return target.unavailable("the option errors must be an object of exception classes");
|
|
7565
|
+
}
|
|
7566
|
+
const given = errors[kind];
|
|
7567
|
+
if (given === undefined)
|
|
7568
|
+
return fallback;
|
|
7569
|
+
if (!isDeclaredException(given)) {
|
|
7570
|
+
return target.unavailable(`errors.${kind} must be a class made by exceptionOf`);
|
|
7571
|
+
}
|
|
7572
|
+
return given;
|
|
7573
|
+
}
|
|
7574
|
+
function keysOf(target, options, option) {
|
|
7575
|
+
const given = options[option];
|
|
7576
|
+
if (given === undefined)
|
|
7577
|
+
return [];
|
|
7578
|
+
if (!Array.isArray(given) || given.some((key) => typeof key !== "string")) {
|
|
7579
|
+
return target.unavailable(`the option ${option} must be a list of keys`);
|
|
7580
|
+
}
|
|
7581
|
+
return given.map((key) => target.field(key));
|
|
7582
|
+
}
|
|
7583
|
+
function valuesOf(target, options, option) {
|
|
7584
|
+
const given = options[option];
|
|
7585
|
+
if (given === undefined)
|
|
7586
|
+
return {};
|
|
7587
|
+
if (!isPlain2(given)) {
|
|
7588
|
+
return target.unavailable(`the option ${option} must be an object`);
|
|
7589
|
+
}
|
|
7590
|
+
for (const key of Object.keys(given)) {
|
|
7591
|
+
if (target.field(key).identity) {
|
|
7592
|
+
target.unavailable(`${option}.${key} is an identity key`);
|
|
7593
|
+
}
|
|
7594
|
+
}
|
|
7595
|
+
return given;
|
|
7596
|
+
}
|
|
7597
|
+
function ownerOf(target, options) {
|
|
7598
|
+
const { owner } = options;
|
|
7599
|
+
if (owner === undefined)
|
|
7600
|
+
return;
|
|
7601
|
+
if (!isPlain2(owner) || typeof owner.context !== "string" || !IDENTIFIER4.test(owner.context) || owner.key !== undefined && typeof owner.key !== "string") {
|
|
7602
|
+
return target.unavailable("the option owner must be { key?, context }, context naming a context key");
|
|
7603
|
+
}
|
|
7604
|
+
const plan = target.field(owner.key ?? "ownerId");
|
|
7605
|
+
if (plan.identity || plan.optional) {
|
|
7606
|
+
target.unavailable(`owner.key "${plan.key}" must be a required key`);
|
|
7607
|
+
}
|
|
7608
|
+
if (plan.kind === "entity" && plan.related) {
|
|
7609
|
+
return {
|
|
7610
|
+
plan,
|
|
7611
|
+
context: owner.context,
|
|
7612
|
+
field: plan.related.blueprint.properties.id,
|
|
7613
|
+
reference: `$item.${plan.key}.id`
|
|
7614
|
+
};
|
|
7615
|
+
}
|
|
7616
|
+
if (plan.kind !== "property") {
|
|
7617
|
+
target.unavailable(`owner.key "${plan.key}" must hold an id or an entity`);
|
|
7618
|
+
}
|
|
7619
|
+
return {
|
|
7620
|
+
plan,
|
|
7621
|
+
context: owner.context,
|
|
7622
|
+
field: plan.field,
|
|
7623
|
+
reference: `$item.${plan.key}`
|
|
7624
|
+
};
|
|
7625
|
+
}
|
|
7626
|
+
function contextOf2(owner) {
|
|
7627
|
+
return owner ? { context: fields(() => ({ [owner.context]: owner.field })) } : {};
|
|
7628
|
+
}
|
|
7629
|
+
function loadSteps(owner, notFound) {
|
|
7630
|
+
return [
|
|
7631
|
+
{ find: "repository.findById", args: ["$input.id"], as: "item" },
|
|
7632
|
+
{ ensure: "exists", value: "$item", error: notFound },
|
|
7633
|
+
...owner ? [
|
|
7634
|
+
{
|
|
7635
|
+
ensure: "equals",
|
|
7636
|
+
value: owner.reference,
|
|
7637
|
+
other: `$context.${owner.context}`,
|
|
7638
|
+
error: notFound
|
|
7639
|
+
}
|
|
7640
|
+
] : []
|
|
7641
|
+
];
|
|
7642
|
+
}
|
|
7643
|
+
function hooksOf(target, options) {
|
|
7644
|
+
const { before, after, dependencies = {}, throws = [] } = options;
|
|
7645
|
+
for (const [key, hook] of [
|
|
7646
|
+
["before", before],
|
|
7647
|
+
["after", after]
|
|
7648
|
+
]) {
|
|
7649
|
+
if (hook !== undefined && typeof hook !== "function") {
|
|
7650
|
+
target.unavailable(`the option ${key} must be a function`);
|
|
7651
|
+
}
|
|
7652
|
+
}
|
|
7653
|
+
if (!isPlain2(dependencies) || Object.keys(dependencies).some((key) => !IDENTIFIER4.test(key))) {
|
|
7654
|
+
target.unavailable("the option dependencies must be an object of repositories, use cases or environments by name");
|
|
7655
|
+
}
|
|
7656
|
+
if (!Array.isArray(throws) || throws.some((error) => !isDeclaredException(error))) {
|
|
7657
|
+
target.unavailable("the option throws must be a list of classes made by exceptionOf");
|
|
7658
|
+
}
|
|
7659
|
+
const input = before === undefined ? "$input" : "$prepared";
|
|
7660
|
+
return {
|
|
7661
|
+
input,
|
|
7662
|
+
dependencies,
|
|
7663
|
+
throws,
|
|
7664
|
+
before: (entity) => before === undefined ? [] : [
|
|
7665
|
+
{
|
|
7666
|
+
run: "before",
|
|
7667
|
+
fn: async (given, ...rest) => {
|
|
7668
|
+
const { context, dependencies } = rest.at(-1);
|
|
7669
|
+
const replaced = await before({
|
|
7670
|
+
input: given,
|
|
7671
|
+
...entity === undefined ? {} : { entity: rest[0] },
|
|
7672
|
+
dependencies,
|
|
7673
|
+
context
|
|
7674
|
+
});
|
|
7675
|
+
return replaced === undefined ? given : replaced;
|
|
7676
|
+
},
|
|
7677
|
+
args: entity === undefined ? ["$input"] : ["$input", entity],
|
|
7678
|
+
as: "prepared"
|
|
7679
|
+
}
|
|
7680
|
+
],
|
|
7681
|
+
after: (entity) => after === undefined ? [] : [
|
|
7682
|
+
{
|
|
7683
|
+
run: "after",
|
|
7684
|
+
fn: async (written, given, { context, dependencies }) => {
|
|
7685
|
+
await after({
|
|
7686
|
+
entity: written,
|
|
7687
|
+
input: given,
|
|
7688
|
+
dependencies,
|
|
7689
|
+
context
|
|
7690
|
+
});
|
|
7691
|
+
},
|
|
7692
|
+
args: [entity, input]
|
|
7693
|
+
}
|
|
7694
|
+
]
|
|
7695
|
+
};
|
|
7696
|
+
}
|
|
7697
|
+
function joinDependencies(target, made, hooks) {
|
|
7698
|
+
for (const key of Object.keys(hooks.dependencies)) {
|
|
7699
|
+
if (Object.hasOwn(made, key)) {
|
|
7700
|
+
target.unavailable(`dependencies.${key} is a name the use case gives its own dependency`);
|
|
7701
|
+
}
|
|
7702
|
+
}
|
|
7703
|
+
return { ...made, ...hooks.dependencies };
|
|
7704
|
+
}
|
|
7705
|
+
var idOf2 = (target) => target.field("id").field;
|
|
7706
|
+
var distinct = (...errors) => [...new Set(errors)];
|
|
7707
|
+
function entityUseCase(target, options, verb, allowed, steps, hooks = false) {
|
|
7708
|
+
checkOptions(target, options, [
|
|
7709
|
+
"as",
|
|
7710
|
+
"owner",
|
|
7711
|
+
...allowed,
|
|
7712
|
+
...hooks ? HOOKS : []
|
|
7713
|
+
]);
|
|
7714
|
+
const owner = ownerOf(target, options);
|
|
7715
|
+
const notFound = errorOf(target, options, "notFound", UseCaseEntityNotFound2);
|
|
7716
|
+
const hooked = hooksOf(target, options);
|
|
7717
|
+
const body = steps({ owner, input: hooked.input });
|
|
7718
|
+
return {
|
|
7719
|
+
...metaOf(target, options, verb),
|
|
7720
|
+
blueprint: fields(() => ({ id: idOf2(target) })),
|
|
7721
|
+
response: fields(() => ({ [asOf(target, options)]: target.entity })),
|
|
7722
|
+
...contextOf2(owner),
|
|
7723
|
+
dependencies: joinDependencies(target, { repository: target.repository }, hooked),
|
|
7724
|
+
errors: distinct(notFound, ...hooked.throws),
|
|
7725
|
+
steps: [
|
|
7726
|
+
...loadSteps(owner, notFound),
|
|
7727
|
+
...hooked.before("$item"),
|
|
7728
|
+
...body.steps,
|
|
7729
|
+
...hooked.after(body.result),
|
|
7730
|
+
{ return: { [asOf(target, options)]: body.result } }
|
|
7731
|
+
]
|
|
7732
|
+
};
|
|
7733
|
+
}
|
|
7734
|
+
|
|
7735
|
+
// src/std/use-cases/crud.ts
|
|
7736
|
+
function createCompilation(target, options, verb, checks = () => [], errors = []) {
|
|
7737
|
+
checkOptions(target, options, ["as", "owner", "omit", "values", ...HOOKS]);
|
|
7738
|
+
const owner = ownerOf(target, options);
|
|
7739
|
+
const hooks = hooksOf(target, options);
|
|
7740
|
+
const omitted = keysOf(target, options, "omit");
|
|
7741
|
+
const constants = valuesOf(target, options, "values");
|
|
7742
|
+
const notFound = errorOf(target, options, "notFound", UseCaseEntityNotFound2);
|
|
7743
|
+
const ownerError = errorOf(target, options, "owner", notFound);
|
|
7744
|
+
const input = {};
|
|
7745
|
+
const values = {};
|
|
7746
|
+
const dependencies = {
|
|
7747
|
+
repository: target.repository
|
|
7748
|
+
};
|
|
7749
|
+
const steps = [];
|
|
7750
|
+
const used = new Set([...errors, ...hooks.throws]);
|
|
7751
|
+
const take = (key, field) => {
|
|
7752
|
+
if (Object.hasOwn(input, key)) {
|
|
7753
|
+
target.unavailable(`two keys of the entity take the input "${key}"`);
|
|
7754
|
+
}
|
|
7755
|
+
input[key] = field;
|
|
7756
|
+
};
|
|
7757
|
+
const load = (key, reference, error) => {
|
|
7758
|
+
const dependency = `${key}Repository`;
|
|
7759
|
+
dependencies[dependency] = target.relation(key, ["findById"]);
|
|
7760
|
+
used.add(error);
|
|
7761
|
+
steps.push({
|
|
7762
|
+
find: `${dependency}.findById`,
|
|
7763
|
+
args: [reference],
|
|
7764
|
+
as: `${key}Found`
|
|
7765
|
+
}, { ensure: "exists", value: `$${key}Found`, error });
|
|
7766
|
+
return `$${key}Found`;
|
|
7767
|
+
};
|
|
7768
|
+
for (const plan of target.fields) {
|
|
7769
|
+
const { key } = plan;
|
|
7770
|
+
if (plan.identity)
|
|
7771
|
+
continue;
|
|
7772
|
+
if (Object.hasOwn(constants, key)) {
|
|
7773
|
+
values[key] = literal(constants[key]);
|
|
7774
|
+
continue;
|
|
7775
|
+
}
|
|
7776
|
+
if (owner?.plan.key === key) {
|
|
7777
|
+
values[key] = plan.kind === "entity" ? load(key, `$context.${owner.context}`, ownerError) : `$context.${owner.context}`;
|
|
7778
|
+
continue;
|
|
7779
|
+
}
|
|
7780
|
+
const unloadable = plan.kind === "entity-list" || plan.kind === "entity" && plan.optional;
|
|
7781
|
+
if (omitted.includes(plan) || unloadable) {
|
|
7782
|
+
if (!plan.optional) {
|
|
7783
|
+
target.unavailable(unloadable ? `"${key}" holds a list of entities, which the input cannot take; give it in values` : `"${key}" is required and cannot be omitted`);
|
|
7784
|
+
}
|
|
7785
|
+
continue;
|
|
7786
|
+
}
|
|
7787
|
+
take(plan.input.key, plan.input.field);
|
|
7788
|
+
const given = `${hooks.input}.${plan.input.key}`;
|
|
7789
|
+
if (plan.kind === "entity") {
|
|
7790
|
+
values[key] = load(key, given, notFound);
|
|
7791
|
+
} else if (plan.convert) {
|
|
7792
|
+
steps.push({
|
|
7793
|
+
run: `convert${capitalize2(key)}`,
|
|
7794
|
+
fn: plan.convert,
|
|
7795
|
+
args: [given],
|
|
7796
|
+
as: `${key}Converted`
|
|
7797
|
+
});
|
|
7798
|
+
values[key] = `$${key}Converted`;
|
|
7799
|
+
} else {
|
|
7800
|
+
values[key] = given;
|
|
7801
|
+
}
|
|
7802
|
+
}
|
|
7803
|
+
return {
|
|
7804
|
+
...metaOf(target, options, verb),
|
|
7805
|
+
blueprint: fields(() => input),
|
|
7806
|
+
response: fields(() => ({ [asOf(target, options)]: target.entity })),
|
|
7807
|
+
...contextOf2(owner),
|
|
7808
|
+
dependencies: joinDependencies(target, dependencies, hooks),
|
|
7809
|
+
errors: distinct(...used),
|
|
7810
|
+
steps: [
|
|
7811
|
+
...hooks.before(),
|
|
7812
|
+
...checks(hooks.input),
|
|
7813
|
+
...steps,
|
|
7814
|
+
{ build: target.entity, values, as: "built" },
|
|
7815
|
+
{ create: "repository", value: "$built", as: "item" },
|
|
7816
|
+
...hooks.after("$item"),
|
|
7817
|
+
{ return: { [asOf(target, options)]: "$item" } }
|
|
7818
|
+
]
|
|
7819
|
+
};
|
|
7820
|
+
}
|
|
7821
|
+
var create2 = useCasePluginOf2("create", {
|
|
7822
|
+
description: "Builds an entity from the input (its keys, less the identity; an entity it holds by id) and stores it.",
|
|
7823
|
+
requires: { actions: ["create"] },
|
|
7824
|
+
compile: (target, options) => createCompilation(target, options, "Create")
|
|
7825
|
+
});
|
|
7826
|
+
var get = useCasePluginOf2("get", {
|
|
7827
|
+
description: "Gives the entity of an id.",
|
|
7828
|
+
requires: { actions: ["findById"] },
|
|
7829
|
+
compile: (target, options) => entityUseCase(target, options, "Get", [], () => ({
|
|
7830
|
+
steps: [],
|
|
7831
|
+
result: "$item"
|
|
7832
|
+
}))
|
|
7833
|
+
});
|
|
7834
|
+
var list = useCasePluginOf2("list", {
|
|
7835
|
+
description: "Gives a page of entities: { items, total, limit, offset }.",
|
|
7836
|
+
requires: { actions: ["findPage"] },
|
|
7837
|
+
compile: (target, options) => {
|
|
7838
|
+
checkOptions(target, options, ["owner", "orderBy"]);
|
|
7839
|
+
const owner = ownerOf(target, options);
|
|
7840
|
+
const { orderBy = [{ key: "createdAt", direction: "desc" }] } = options;
|
|
7841
|
+
if (!Array.isArray(orderBy)) {
|
|
7842
|
+
target.unavailable("the option orderBy must be a list of orders");
|
|
7843
|
+
}
|
|
7844
|
+
for (const order of orderBy) {
|
|
7845
|
+
target.field(String(order?.key));
|
|
7846
|
+
}
|
|
7847
|
+
return {
|
|
7848
|
+
...metaOf(target, options, "List"),
|
|
7849
|
+
blueprint: fields(() => ({
|
|
7850
|
+
limit: optionalOf2(Integer2),
|
|
7851
|
+
offset: optionalOf2(Integer2)
|
|
7852
|
+
})),
|
|
7853
|
+
response: fields(() => ({
|
|
7854
|
+
items: listOf2(target.entity),
|
|
7855
|
+
total: Integer2,
|
|
7856
|
+
limit: Integer2,
|
|
7857
|
+
offset: Integer2
|
|
7858
|
+
})),
|
|
7859
|
+
...contextOf2(owner),
|
|
7860
|
+
dependencies: { repository: target.repository },
|
|
7861
|
+
steps: [
|
|
7862
|
+
{
|
|
7863
|
+
find: "repository.findPage",
|
|
7864
|
+
args: [
|
|
7865
|
+
{
|
|
7866
|
+
...owner ? {
|
|
7867
|
+
where: {
|
|
7868
|
+
[owner.plan.key]: `$context.${owner.context}`
|
|
7869
|
+
}
|
|
7870
|
+
} : {},
|
|
7871
|
+
orderBy: literal(orderBy),
|
|
7872
|
+
limit: "$input.limit",
|
|
7873
|
+
offset: "$input.offset"
|
|
7874
|
+
}
|
|
7875
|
+
],
|
|
7876
|
+
as: "page"
|
|
7877
|
+
},
|
|
7878
|
+
{
|
|
7879
|
+
return: {
|
|
7880
|
+
items: "$page.items",
|
|
7881
|
+
total: "$page.total",
|
|
7882
|
+
limit: "$page.limit",
|
|
7883
|
+
offset: "$page.offset"
|
|
7884
|
+
}
|
|
7885
|
+
}
|
|
7886
|
+
]
|
|
7887
|
+
};
|
|
7888
|
+
}
|
|
7889
|
+
});
|
|
7890
|
+
var update2 = useCasePluginOf2("update", {
|
|
7891
|
+
description: "Changes the change keys the input gives, leaving the others as they are, and stores the entity.",
|
|
7892
|
+
requires: { actions: ["findById", "update"] },
|
|
7893
|
+
compile: (target, options) => {
|
|
7894
|
+
checkOptions(target, options, ["as", "owner", ...HOOKS]);
|
|
7895
|
+
const owner = ownerOf(target, options);
|
|
7896
|
+
const hooks = hooksOf(target, options);
|
|
7897
|
+
const notFound = errorOf(target, options, "notFound", UseCaseEntityNotFound2);
|
|
7898
|
+
const plans = target.changeKeys.map((key) => target.field(key)).filter((plan) => plan.kind !== "entity" && plan.kind !== "entity-list");
|
|
7899
|
+
if (plans.length === 0) {
|
|
7900
|
+
target.unavailable("the entity has no change key an input can take");
|
|
7901
|
+
}
|
|
7902
|
+
if (plans.some((plan) => plan.key === "id")) {
|
|
7903
|
+
target.unavailable('"id" cannot be a change key');
|
|
7904
|
+
}
|
|
7905
|
+
const steps = [];
|
|
7906
|
+
for (const plan of plans) {
|
|
7907
|
+
const given = `${hooks.input}.${plan.input.key}`;
|
|
7908
|
+
let value = given;
|
|
7909
|
+
if (plan.convert) {
|
|
7910
|
+
steps.push({
|
|
7911
|
+
run: `convert${capitalize2(plan.key)}`,
|
|
7912
|
+
fn: plan.convert,
|
|
7913
|
+
args: [given],
|
|
7914
|
+
as: `${plan.key}Converted`
|
|
7915
|
+
});
|
|
7916
|
+
value = `$${plan.key}Converted`;
|
|
7917
|
+
}
|
|
7918
|
+
steps.push({ change: "$item", key: plan.key, value, ifPresent: true });
|
|
7919
|
+
}
|
|
7920
|
+
return {
|
|
7921
|
+
...metaOf(target, options, "Update"),
|
|
7922
|
+
blueprint: fields(() => ({
|
|
7923
|
+
id: idOf2(target),
|
|
7924
|
+
...Object.fromEntries(plans.map((plan) => [plan.input.key, optionalOf2(plan.input.field)]))
|
|
7925
|
+
})),
|
|
7926
|
+
response: fields(() => ({ [asOf(target, options)]: target.entity })),
|
|
7927
|
+
...contextOf2(owner),
|
|
7928
|
+
dependencies: joinDependencies(target, { repository: target.repository }, hooks),
|
|
7929
|
+
errors: distinct(notFound, ...hooks.throws),
|
|
7930
|
+
steps: [
|
|
7931
|
+
...loadSteps(owner, notFound),
|
|
7932
|
+
...hooks.before("$item"),
|
|
7933
|
+
...steps,
|
|
7934
|
+
{ update: "repository", value: "$item", as: "saved" },
|
|
7935
|
+
...hooks.after("$saved"),
|
|
7936
|
+
{ return: { [asOf(target, options)]: "$saved" } }
|
|
7937
|
+
]
|
|
7938
|
+
};
|
|
7939
|
+
}
|
|
7940
|
+
});
|
|
7941
|
+
var remove2 = useCasePluginOf2("delete", {
|
|
7942
|
+
description: "Deletes the entity of an id, hard or soft as the repository declares, and gives it.",
|
|
7943
|
+
requires: { actions: ["findById", "delete"] },
|
|
7944
|
+
compile: (target, options) => entityUseCase(target, options, "Delete", [], () => ({
|
|
7945
|
+
steps: [{ delete: "repository", value: "$item", as: "deleted" }],
|
|
7946
|
+
result: "$deleted"
|
|
7947
|
+
}), true)
|
|
7948
|
+
});
|
|
7949
|
+
var restore2 = useCasePluginOf2("restore", {
|
|
7950
|
+
description: "Brings back the soft-deleted entity of an id; for a repository with soft deletion and the restore action.",
|
|
7951
|
+
requires: { actions: ["restore"] },
|
|
7952
|
+
compile: (target, options) => {
|
|
7953
|
+
checkOptions(target, options, ["as", ...HOOKS]);
|
|
7954
|
+
const hooks = hooksOf(target, options);
|
|
7955
|
+
return {
|
|
7956
|
+
...metaOf(target, options, "Restore"),
|
|
7957
|
+
blueprint: fields(() => ({ id: idOf2(target) })),
|
|
7958
|
+
response: fields(() => ({ [asOf(target, options)]: target.entity })),
|
|
7959
|
+
dependencies: joinDependencies(target, { repository: target.repository }, hooks),
|
|
7960
|
+
errors: distinct(...hooks.throws),
|
|
7961
|
+
steps: [
|
|
7962
|
+
...hooks.before(),
|
|
7963
|
+
{ restore: "repository", value: `${hooks.input}.id`, as: "item" },
|
|
7964
|
+
...hooks.after("$item"),
|
|
7965
|
+
{ return: { [asOf(target, options)]: "$item" } }
|
|
7966
|
+
]
|
|
7967
|
+
};
|
|
7968
|
+
}
|
|
7969
|
+
});
|
|
7970
|
+
|
|
7971
|
+
// src/std/use-cases/auth.ts
|
|
7972
|
+
var authenticatable = (actions) => ({
|
|
7973
|
+
requires: { actions, plugins: ["authenticatable"] }
|
|
7974
|
+
});
|
|
7975
|
+
var register = useCasePluginOf2("register", {
|
|
7976
|
+
description: "Stores an authenticatable entity built from the input, its password given plain and stored as a hash, unless the email is taken.",
|
|
7977
|
+
...authenticatable(["findByEmail", "create"]),
|
|
7978
|
+
compile: (target, options) => {
|
|
7979
|
+
const conflict = errorOf(target, options, "conflict", UseCaseEntityConflict2);
|
|
7980
|
+
return createCompilation(target, options, "Register", (input) => [
|
|
7981
|
+
{
|
|
7982
|
+
find: "repository.findByEmail",
|
|
7983
|
+
args: [`${input}.email`],
|
|
7984
|
+
as: "existing"
|
|
7985
|
+
},
|
|
7986
|
+
{ ensure: "absent", value: "$existing", error: conflict }
|
|
7987
|
+
], [conflict]);
|
|
7988
|
+
}
|
|
7989
|
+
});
|
|
7990
|
+
var login = useCasePluginOf2("login", {
|
|
7991
|
+
description: "Gives the authenticatable entity of an email when the password matches; issues no token.",
|
|
7992
|
+
...authenticatable(["findByEmail"]),
|
|
7993
|
+
compile: (target, options) => {
|
|
7994
|
+
checkOptions(target, options, ["as"]);
|
|
7995
|
+
const invalid = errorOf(target, options, "invalid", UseCaseInvalidCredentials2);
|
|
7996
|
+
return {
|
|
7997
|
+
...metaOf(target, options, "Login"),
|
|
7998
|
+
blueprint: fields(() => ({
|
|
7999
|
+
email: target.field("email").field,
|
|
8000
|
+
password: StringProperty2
|
|
8001
|
+
})),
|
|
8002
|
+
response: fields(() => ({ [asOf(target, options)]: target.entity })),
|
|
8003
|
+
dependencies: { repository: target.repository },
|
|
8004
|
+
errors: [invalid],
|
|
8005
|
+
steps: [
|
|
8006
|
+
{ find: "repository.findByEmail", args: ["$input.email"], as: "item" },
|
|
8007
|
+
{ ensure: "exists", value: "$item", error: invalid },
|
|
8008
|
+
{
|
|
8009
|
+
invoke: "$item",
|
|
8010
|
+
method: "verifyPassword",
|
|
8011
|
+
args: ["$input.password"],
|
|
8012
|
+
as: "matches"
|
|
8013
|
+
},
|
|
8014
|
+
{ ensure: "true", value: "$matches", error: invalid },
|
|
8015
|
+
{ return: { [asOf(target, options)]: "$item" } }
|
|
8016
|
+
]
|
|
8017
|
+
};
|
|
8018
|
+
}
|
|
8019
|
+
});
|
|
8020
|
+
|
|
8021
|
+
// src/std/use-cases/lifecycle.ts
|
|
8022
|
+
function invoking(target, options, verb, method) {
|
|
8023
|
+
const set = valuesOf(target, options, "set");
|
|
8024
|
+
const changes = Object.entries(set).map(([key, value]) => {
|
|
8025
|
+
if (!target.changeKeys.includes(key)) {
|
|
8026
|
+
target.unavailable(`set.${key} is not a change key of the entity`);
|
|
8027
|
+
}
|
|
8028
|
+
return { change: "$item", key, value: literal(value) };
|
|
8029
|
+
});
|
|
8030
|
+
return entityUseCase(target, options, verb, ["set"], () => ({
|
|
8031
|
+
steps: [
|
|
8032
|
+
...changes,
|
|
8033
|
+
{ invoke: "$item", method },
|
|
8034
|
+
{ update: "repository", value: "$item", as: "saved" }
|
|
8035
|
+
],
|
|
8036
|
+
result: "$saved"
|
|
8037
|
+
}), true);
|
|
8038
|
+
}
|
|
8039
|
+
var lifecycle = (plugin) => ({
|
|
8040
|
+
requires: { actions: ["findById", "update"], plugins: [plugin] }
|
|
8041
|
+
});
|
|
8042
|
+
var publish = useCasePluginOf2("publish", {
|
|
8043
|
+
description: "Publishes the publishable entity of an id and stores it.",
|
|
8044
|
+
...lifecycle("publishable"),
|
|
8045
|
+
compile: (target, options) => invoking(target, options, "Publish", "publish")
|
|
8046
|
+
});
|
|
8047
|
+
var unpublish = useCasePluginOf2("unpublish", {
|
|
8048
|
+
description: "Makes the publishable entity of an id a draft again and stores it.",
|
|
8049
|
+
...lifecycle("publishable"),
|
|
8050
|
+
compile: (target, options) => invoking(target, options, "Unpublish", "unpublish")
|
|
8051
|
+
});
|
|
8052
|
+
var archive = useCasePluginOf2("archive", {
|
|
8053
|
+
description: "Archives the archivable entity of an id and stores it.",
|
|
8054
|
+
...lifecycle("archivable"),
|
|
8055
|
+
compile: (target, options) => invoking(target, options, "Archive", "archive")
|
|
8056
|
+
});
|
|
8057
|
+
var unarchive = useCasePluginOf2("unarchive", {
|
|
8058
|
+
description: "Makes the archived entity of an id active again and stores it.",
|
|
8059
|
+
...lifecycle("archivable"),
|
|
8060
|
+
compile: (target, options) => invoking(target, options, "Unarchive", "restore")
|
|
8061
|
+
});
|
|
8062
|
+
function tagging(target, options, verb, method) {
|
|
8063
|
+
checkOptions(target, options, ["as", "owner", ...HOOKS]);
|
|
8064
|
+
const owner = ownerOf(target, options);
|
|
8065
|
+
const hooks = hooksOf(target, options);
|
|
8066
|
+
const notFound = errorOf(target, options, "notFound", UseCaseEntityNotFound2);
|
|
8067
|
+
const { item } = target.field("tags").field;
|
|
8068
|
+
return {
|
|
8069
|
+
...metaOf(target, options, verb),
|
|
8070
|
+
blueprint: fields(() => ({
|
|
8071
|
+
id: idOf2(target),
|
|
8072
|
+
tags: listOf2(item, { minItems: 1 })
|
|
8073
|
+
})),
|
|
8074
|
+
response: fields(() => ({ [asOf(target, options)]: target.entity })),
|
|
8075
|
+
...contextOf2(owner),
|
|
8076
|
+
dependencies: joinDependencies(target, { repository: target.repository }, hooks),
|
|
8077
|
+
errors: distinct(notFound, ...hooks.throws),
|
|
8078
|
+
steps: [
|
|
8079
|
+
...loadSteps(owner, notFound),
|
|
8080
|
+
...hooks.before("$item"),
|
|
8081
|
+
{ invoke: "$item", method, args: `${hooks.input}.tags` },
|
|
8082
|
+
{ update: "repository", value: "$item", as: "saved" },
|
|
8083
|
+
...hooks.after("$saved"),
|
|
8084
|
+
{ return: { [asOf(target, options)]: "$saved" } }
|
|
8085
|
+
]
|
|
8086
|
+
};
|
|
8087
|
+
}
|
|
8088
|
+
var tag = useCasePluginOf2("tag", {
|
|
8089
|
+
description: "Adds tags to the taggable entity of an id and stores it.",
|
|
8090
|
+
...lifecycle("taggable"),
|
|
8091
|
+
compile: (target, options) => tagging(target, options, "Tag", "tag")
|
|
8092
|
+
});
|
|
8093
|
+
var untag = useCasePluginOf2("untag", {
|
|
8094
|
+
description: "Removes tags from the taggable entity of an id and stores it.",
|
|
8095
|
+
...lifecycle("taggable"),
|
|
8096
|
+
compile: (target, options) => tagging(target, options, "Untag", "untag")
|
|
8097
|
+
});
|
|
8098
|
+
|
|
8099
|
+
// src/std/use-cases/index.ts
|
|
8100
|
+
var StdUseCases2 = useCaseCatalogOf2(create2, get, list, update2, remove2, restore2, publish, unpublish, archive, unarchive, tag, untag, register, login);
|
|
8101
|
+
|
|
8102
|
+
// src/std/command.ts
|
|
8103
|
+
var commandOf2 = commandBuilderOf2(StdUseCases2);
|
|
5604
8104
|
// src/std/record-plugins.ts
|
|
5605
8105
|
var now = () => new Date().toISOString();
|
|
5606
8106
|
|
|
@@ -5682,10 +8182,13 @@ var entityOf2 = entityBuilderOf2({
|
|
|
5682
8182
|
identity: Identity,
|
|
5683
8183
|
generateId,
|
|
5684
8184
|
now: () => new Date().toISOString(),
|
|
8185
|
+
expiresAt: ExpiresAt2,
|
|
5685
8186
|
plugins: [StdRecordPlugins2]
|
|
5686
8187
|
});
|
|
8188
|
+
// src/std/event-policies.ts
|
|
8189
|
+
var StdEventPolicies2 = eventPolicyCatalogOf2(publishEvents2);
|
|
5687
8190
|
// src/std/record.ts
|
|
5688
8191
|
var recordOf2 = recordBuilderOf2(StdRecordPlugins2);
|
|
5689
8192
|
// src/std/stages.ts
|
|
5690
8193
|
var StdStages2 = stageCatalogOf2(select2, openapi2);
|
|
5691
|
-
export { InvalidEnvironment2, environmentOf2, isEnvironment2,
|
|
8194
|
+
export { InvalidEnvironment2, environmentOf2, isEnvironment2, InvalidApplicationDeclaration2, DuplicateApplicationDeclaration2, ApplicationConflict2, UnregisteredApplicationReference2, UnregisteredRepositoryDependency2, UnregisteredUseCaseDependency2, ApplicationNotStarted2, ProviderNotBound2, ApplicationAlreadyStarted2, InvalidApplicationEnvironment2, useApplication2, resetApplication2, InvalidEventPolicy2, PublisherNotBound2, guard2, InvalidLogDeclaration2, LogLevels2, logProviderOf2, consoleLogProvider2, jsonLogProvider2, memoryLogProvider2, Logger2, ReservedEntityKey2, ImmutableEntityKey2, DeletedEntity2, InvalidEntityExpiry2, EntityVersion2, entityBuilderOf2, isEntityClass2, isEntity2, eventOf2, ENTITY_IDENTITY_KEYS2, RESERVED_ENTITY_KEYS2, EntityUniqueKeys2, EntitySettings2, AbstractEntity2, InvalidRepositoryQuery2, InvalidRepositoryCursor2, InvalidRepositoryDeclaration2, InvalidRepositoryEntity2, InvalidRepositoryData2, RepositoryUniqueViolation2, RepositoryUsedOutsideApplication2, RepositoryEntityNotFound2, RepositoryConcurrencyConflict2, InvalidRepositoryIncrement2, providerOf2, providerDescriptionOf2, inMemoryProvider2, AbstractRepository2, repositoryOf2, RepositoryOf2, isRepository2, InvalidUseCaseStep2, InvalidUseCasePlugin2, UnknownUseCasePlugin2, IncompatibleUseCasePlugin2, UseCaseUnavailable2, UseCaseEntityNotFound2, UseCaseEntityConflict2, UseCaseInvalidCredentials2, PropertyInput2, propertyInputOf2, useCaseTargetOf2, InvalidUseCaseDeclaration2, UseCaseAlreadyExecuted2, InvalidUseCaseDependencies2, AbstractUseCase2, useCaseOf2, isUseCaseClass2, isUseCase2, isUseCasePlugin2, useCaseOriginOf2, useCasePluginOf2, isUseCaseCatalog2, useCaseCatalogOf2, InvalidCommandDeclaration2, UnknownCommandOperation2, InvalidCommandContext2, InvalidCommandEnvironment2, CommandEventFailed2, commandBuilderOf2, isCommand2, eventPolicyOf2, eventPolicyDescriptionOf2, eventPolicyCatalogOf2, InvalidOpenAPIModel2, InvalidOutputStage2, DuplicateOperation2, toOpenAPI2, transformOf2, sinkOf2, stageDescriptionOf2, stageKindOf2, stageCatalogOf2, operationOf2, outputOf2, isOutput2, providerFrom2, logFrom2, isLogFactory2, isProviderFactory2, providersOf2, isProviderSlot2, isProviderStorage2, isApplication2, applicationOf2, blueprintPluginOf2, publishEvents2, coerceTextValues2, statusOf2, PROBLEM_MEDIA_TYPE2, problemOf2, outputValueOf2, ROUTE_OPTION_KEYS2, routeOptionsIssue2, requestIdHeaderOf2, operationRoutes2, openapi2, select2, stripUndeclared2, recordPluginCatalogOf2, Email2, Phone2, Url2, Ip2, enumOf2, SchemaProperty2, PasswordHasherRequired2, SyncVerifyUnsupported2, configure2, resetRuntime2, Id2, Slug2, InvalidJsonData2, InvalidJsonSchema2, jsonSchemaIssues2, checkJsonSchema2, JsonProperty2, jsonOf2, PASSWORD_MIN_LENGTH2, PASSWORD_MAX_LENGTH2, PASSWORD_SPECIAL_CHARACTERS2, isPasswordHash2, passwordIssues2, PlainPassword2, Password2, StringProperty2, Text2, NumberProperty2, Integer2, BooleanProperty2, DateTime2, DateProperty2, CreatedAt2, UpdatedAt2, DeletedAt2, ExpiresAt2, Std2, blueprintOf2, StdUseCases2, commandOf2, Publishable2, Archivable2, Taggable2, Ownable2, Authenticatable2, StdRecordPlugins2, entityOf2, StdEventPolicies2, recordOf2, StdStages2 };
|