@afora/core 0.0.5 → 0.0.7
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 +4 -1
- package/dist/core/application/types/application.interface.d.ts +56 -11
- package/dist/core/blueprint/types/slice.interface.d.ts +5 -2
- package/dist/core/command/command.d.ts +11 -4
- package/dist/core/command/types/command.interface.d.ts +19 -0
- package/dist/core/entity/abstract-entity.d.ts +1 -1
- package/dist/core/entity/types/entity.interface.d.ts +13 -7
- package/dist/core/exception/exception.d.ts +10 -1
- package/dist/core/output/status.d.ts +4 -3
- package/dist/core/property/input.d.ts +20 -0
- package/dist/core/property/property.d.ts +4 -4
- package/dist/core/property/types/optional-property.type.d.ts +2 -2
- package/dist/core/property/types/property-type.interface.d.ts +17 -0
- package/dist/core/record/abstract-record.d.ts +4 -4
- package/dist/core/record/exceptions.d.ts +2 -0
- package/dist/core/record/name.d.ts +16 -0
- package/dist/core/record/plugin.d.ts +6 -2
- package/dist/core/record/record.d.ts +4 -3
- package/dist/core/record/types/plugin.interface.d.ts +11 -0
- package/dist/core/record/types/record.interface.d.ts +71 -10
- package/dist/core/repository/cursor.d.ts +13 -0
- package/dist/core/repository/exceptions.d.ts +10 -0
- package/dist/core/repository/query.d.ts +4 -4
- package/dist/core/repository/repository.d.ts +2 -2
- package/dist/core/repository/state.d.ts +9 -0
- package/dist/core/repository/types/query.interface.d.ts +37 -0
- package/dist/core/repository/types/repository.interface.d.ts +17 -6
- 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 +24 -7
- 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 +4 -3
- package/dist/{index-8m80wjzf.js → index-q6hggpzw.js} +1210 -85
- package/dist/{index-hn82hm31.js → index-r78dsvtc.js} +136 -66
- package/dist/index.d.ts +22 -12
- package/dist/index.js +47 -5
- package/dist/std/command.d.ts +11 -0
- package/dist/std/properties/password.d.ts +21 -2
- package/dist/std/record-plugins.d.ts +5 -5
- package/dist/std/use-cases/auth.d.ts +5 -0
- package/dist/std/use-cases/crud.d.ts +23 -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 +60 -0
- package/dist/std/use-cases/types.d.ts +212 -0
- package/dist/testing/index.js +99 -2
- package/dist/testing/provider-contract.d.ts +5 -0
- package/package.json +1 -1
|
@@ -9,6 +9,9 @@ import {
|
|
|
9
9
|
isBlueprint2,
|
|
10
10
|
isRecordClass2,
|
|
11
11
|
recordFieldOf2,
|
|
12
|
+
recordNameOf2,
|
|
13
|
+
declaredRecordNameOf,
|
|
14
|
+
listOf2,
|
|
12
15
|
isBlueprintList2,
|
|
13
16
|
UNEXPECTED_ERROR2,
|
|
14
17
|
Exception2,
|
|
@@ -27,20 +30,22 @@ import {
|
|
|
27
30
|
AbstractRecord2,
|
|
28
31
|
IdentifiedRecord,
|
|
29
32
|
isVersion,
|
|
30
|
-
|
|
33
|
+
satisfies,
|
|
31
34
|
pluginsOf2,
|
|
32
35
|
isRecordPlugin2,
|
|
36
|
+
hasPlugin2,
|
|
33
37
|
recordPluginOf2,
|
|
34
38
|
recordBuilderOf2,
|
|
35
39
|
pluginContextOf,
|
|
36
40
|
recordOf3,
|
|
37
41
|
declareRecord,
|
|
42
|
+
optionalOf2,
|
|
38
43
|
exports_exceptions2,
|
|
39
44
|
InvalidPropertyValue2,
|
|
40
45
|
exports_exceptions3,
|
|
41
46
|
propertyOf2,
|
|
42
47
|
blueprintBuilderOf2
|
|
43
|
-
} from "./index-
|
|
48
|
+
} from "./index-r78dsvtc.js";
|
|
44
49
|
|
|
45
50
|
// src/core/environment/environment.ts
|
|
46
51
|
var exports_environment = {};
|
|
@@ -83,6 +88,7 @@ __export(exports_exceptions6, {
|
|
|
83
88
|
// src/core/repository/exceptions.ts
|
|
84
89
|
var exports_exceptions7 = {};
|
|
85
90
|
__export(exports_exceptions7, {
|
|
91
|
+
InvalidRepositoryCursor: () => InvalidRepositoryCursor2,
|
|
86
92
|
InvalidRepositoryData: () => InvalidRepositoryData2,
|
|
87
93
|
InvalidRepositoryDeclaration: () => InvalidRepositoryDeclaration2,
|
|
88
94
|
InvalidRepositoryEntity: () => InvalidRepositoryEntity2,
|
|
@@ -91,6 +97,19 @@ __export(exports_exceptions7, {
|
|
|
91
97
|
RepositoryUniqueViolation: () => RepositoryUniqueViolation2
|
|
92
98
|
});
|
|
93
99
|
|
|
100
|
+
// src/core/use-case/exceptions.ts
|
|
101
|
+
var exports_exceptions8 = {};
|
|
102
|
+
__export(exports_exceptions8, {
|
|
103
|
+
IncompatibleUseCasePlugin: () => IncompatibleUseCasePlugin2,
|
|
104
|
+
InvalidUseCasePlugin: () => InvalidUseCasePlugin2,
|
|
105
|
+
InvalidUseCaseStep: () => InvalidUseCaseStep2,
|
|
106
|
+
UnknownUseCasePlugin: () => UnknownUseCasePlugin2,
|
|
107
|
+
UseCaseEntityConflict: () => UseCaseEntityConflict2,
|
|
108
|
+
UseCaseEntityNotFound: () => UseCaseEntityNotFound2,
|
|
109
|
+
UseCaseInvalidCredentials: () => UseCaseInvalidCredentials2,
|
|
110
|
+
UseCaseUnavailable: () => UseCaseUnavailable2
|
|
111
|
+
});
|
|
112
|
+
|
|
94
113
|
// src/core/use-case/use-case.ts
|
|
95
114
|
var exports_use_case = {};
|
|
96
115
|
__export(exports_use_case, {
|
|
@@ -103,12 +122,6 @@ __export(exports_use_case, {
|
|
|
103
122
|
useCaseOf: () => useCaseOf2
|
|
104
123
|
});
|
|
105
124
|
|
|
106
|
-
// src/core/use-case/exceptions.ts
|
|
107
|
-
var exports_exceptions8 = {};
|
|
108
|
-
__export(exports_exceptions8, {
|
|
109
|
-
InvalidUseCaseStep: () => InvalidUseCaseStep2
|
|
110
|
-
});
|
|
111
|
-
|
|
112
125
|
// src/core/command/exceptions.ts
|
|
113
126
|
var exports_exceptions9 = {};
|
|
114
127
|
__export(exports_exceptions9, {
|
|
@@ -640,7 +653,7 @@ function clockOf(entity) {
|
|
|
640
653
|
function assertAlive(entity) {
|
|
641
654
|
if (entity.isDeleted) {
|
|
642
655
|
throw new DeletedEntity2({
|
|
643
|
-
source: entity.constructor
|
|
656
|
+
source: recordNameOf2(entity.constructor),
|
|
644
657
|
details: { id: identityOf(entity).id }
|
|
645
658
|
});
|
|
646
659
|
}
|
|
@@ -690,7 +703,7 @@ class AbstractEntity2 extends AbstractRecord2 {
|
|
|
690
703
|
const name = String(key);
|
|
691
704
|
if (ENTITY_IDENTITY_KEYS2.includes(name)) {
|
|
692
705
|
throw new ImmutableEntityKey2({
|
|
693
|
-
source: this.constructor
|
|
706
|
+
source: recordNameOf2(this.constructor),
|
|
694
707
|
details: { key: name }
|
|
695
708
|
});
|
|
696
709
|
}
|
|
@@ -715,6 +728,13 @@ var InvalidRepositoryQuery2 = exceptionOf2({
|
|
|
715
728
|
name: "InvalidRepositoryQuery",
|
|
716
729
|
message: ({ reason }) => reason
|
|
717
730
|
});
|
|
731
|
+
var InvalidRepositoryCursor2 = exceptionOf2({
|
|
732
|
+
layer: "repository" /* Repository */,
|
|
733
|
+
name: "InvalidRepositoryCursor",
|
|
734
|
+
message: ({
|
|
735
|
+
reason
|
|
736
|
+
}) => reason
|
|
737
|
+
});
|
|
718
738
|
var InvalidRepositoryDeclaration2 = exceptionOf2({
|
|
719
739
|
layer: "repository" /* Repository */,
|
|
720
740
|
name: "InvalidRepositoryDeclaration",
|
|
@@ -741,6 +761,90 @@ var RepositoryEntityNotFound2 = exceptionOf2({
|
|
|
741
761
|
message: ({ id }, source) => `"${source}" holds no entity of id "${id}".`
|
|
742
762
|
});
|
|
743
763
|
|
|
764
|
+
// src/core/repository/cursor.ts
|
|
765
|
+
var VERSION = 1;
|
|
766
|
+
var signatureOf = (orders) => orders.map(({ key, direction = "asc" }) => `${key}:${direction}`);
|
|
767
|
+
function pack(value) {
|
|
768
|
+
if (value === undefined || value === null)
|
|
769
|
+
return null;
|
|
770
|
+
if (value instanceof Date)
|
|
771
|
+
return { d: value.toISOString() };
|
|
772
|
+
if (typeof value === "bigint")
|
|
773
|
+
return { b: value.toString() };
|
|
774
|
+
return value;
|
|
775
|
+
}
|
|
776
|
+
function unpack(value) {
|
|
777
|
+
if (value === null)
|
|
778
|
+
return;
|
|
779
|
+
if (["string", "number", "boolean"].includes(typeof value))
|
|
780
|
+
return value;
|
|
781
|
+
if (typeof value === "object") {
|
|
782
|
+
const { d, b } = value;
|
|
783
|
+
const keys = Object.keys(value);
|
|
784
|
+
if (keys.length === 1 && typeof d === "string") {
|
|
785
|
+
const date = new Date(d);
|
|
786
|
+
if (!Number.isNaN(date.getTime()))
|
|
787
|
+
return date;
|
|
788
|
+
}
|
|
789
|
+
if (keys.length === 1 && typeof b === "string" && /^-?\d+$/.test(b))
|
|
790
|
+
return BigInt(b);
|
|
791
|
+
}
|
|
792
|
+
throw new SyntaxError("value");
|
|
793
|
+
}
|
|
794
|
+
function encodeText(text) {
|
|
795
|
+
let binary = "";
|
|
796
|
+
for (const byte of new TextEncoder().encode(text))
|
|
797
|
+
binary += String.fromCharCode(byte);
|
|
798
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
799
|
+
}
|
|
800
|
+
function decodeText(encoded) {
|
|
801
|
+
if (!/^[A-Za-z0-9_-]+$/.test(encoded))
|
|
802
|
+
throw new SyntaxError("alphabet");
|
|
803
|
+
const binary = atob(encoded.replaceAll("-", "+").replaceAll("_", "/"));
|
|
804
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(Uint8Array.from(binary, (char) => char.charCodeAt(0)));
|
|
805
|
+
}
|
|
806
|
+
function encodeCursor(orders, row) {
|
|
807
|
+
const payload = {
|
|
808
|
+
v: VERSION,
|
|
809
|
+
k: signatureOf(orders),
|
|
810
|
+
a: orders.map(({ key }) => pack(row[key]))
|
|
811
|
+
};
|
|
812
|
+
return encodeText(JSON.stringify(payload));
|
|
813
|
+
}
|
|
814
|
+
function decodeCursor(source, orders, cursor) {
|
|
815
|
+
const fail = (reason) => {
|
|
816
|
+
throw new InvalidRepositoryCursor2({
|
|
817
|
+
source,
|
|
818
|
+
details: {
|
|
819
|
+
reason,
|
|
820
|
+
issues: [
|
|
821
|
+
{ code: "invalid_value", path: ["after"], message: reason, source }
|
|
822
|
+
]
|
|
823
|
+
}
|
|
824
|
+
});
|
|
825
|
+
};
|
|
826
|
+
if (typeof cursor !== "string" || cursor === "")
|
|
827
|
+
return fail("Expected a cursor given by findCursor.");
|
|
828
|
+
let payload;
|
|
829
|
+
let values;
|
|
830
|
+
try {
|
|
831
|
+
payload = JSON.parse(decodeText(cursor));
|
|
832
|
+
if (typeof payload !== "object" || payload === null)
|
|
833
|
+
throw new SyntaxError;
|
|
834
|
+
if (!Array.isArray(payload.k) || !Array.isArray(payload.a))
|
|
835
|
+
throw new SyntaxError;
|
|
836
|
+
values = payload.a.map(unpack);
|
|
837
|
+
} catch {
|
|
838
|
+
return fail("The cursor is not one given by findCursor.");
|
|
839
|
+
}
|
|
840
|
+
if (payload.v !== VERSION)
|
|
841
|
+
return fail("The cursor was made by another version.");
|
|
842
|
+
const expected = signatureOf(orders);
|
|
843
|
+
if (payload.k?.length !== expected.length || values.length !== expected.length || expected.some((key, index) => payload.k?.[index] !== key))
|
|
844
|
+
return fail("The cursor was made for another orderBy.");
|
|
845
|
+
return values;
|
|
846
|
+
}
|
|
847
|
+
|
|
744
848
|
// src/core/json.ts
|
|
745
849
|
function frozenJson(value, subject, fail, seen = new Set) {
|
|
746
850
|
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
@@ -798,7 +902,8 @@ function plain(value) {
|
|
|
798
902
|
function isMembership(value) {
|
|
799
903
|
return plain(value) && Object.keys(value).length === 1 && Array.isArray(value.in);
|
|
800
904
|
}
|
|
801
|
-
|
|
905
|
+
var QUERY_OPTIONS = ["where", "orderBy", "limit", "offset", "withDeleted"];
|
|
906
|
+
function prepareQuery(context, input = {}, map = (_key, value) => value, options = QUERY_OPTIONS) {
|
|
802
907
|
const fail = (reason) => {
|
|
803
908
|
throw new InvalidRepositoryQuery2({
|
|
804
909
|
source: context.name,
|
|
@@ -808,7 +913,7 @@ function prepareQuery(context, input = {}, map = (_key, value) => value) {
|
|
|
808
913
|
if (!plain(input))
|
|
809
914
|
return fail("Expected a query object.");
|
|
810
915
|
for (const key of Object.keys(input))
|
|
811
|
-
if (!
|
|
916
|
+
if (!options.includes(key))
|
|
812
917
|
fail(`Unknown query option "${key}".`);
|
|
813
918
|
const properties = context.entity.blueprint.properties;
|
|
814
919
|
const keyOf = (key) => {
|
|
@@ -890,26 +995,32 @@ function compareScalars(a, b) {
|
|
|
890
995
|
];
|
|
891
996
|
return x < y ? -1 : x > y ? 1 : 0;
|
|
892
997
|
}
|
|
998
|
+
function compareRows(orders, a, b) {
|
|
999
|
+
for (const { key, direction } of orders) {
|
|
1000
|
+
const [x, y] = [a[key], b[key]];
|
|
1001
|
+
const [missingX, missingY] = [
|
|
1002
|
+
x === undefined || x === null,
|
|
1003
|
+
y === undefined || y === null
|
|
1004
|
+
];
|
|
1005
|
+
let result = missingX || missingY ? Number(!missingX) - Number(!missingY) : compareScalars(x, y);
|
|
1006
|
+
if (direction === "desc")
|
|
1007
|
+
result = -result;
|
|
1008
|
+
if (result !== 0)
|
|
1009
|
+
return result;
|
|
1010
|
+
}
|
|
1011
|
+
return 0;
|
|
1012
|
+
}
|
|
893
1013
|
function orderRows(rows, query) {
|
|
894
1014
|
const orders = query.orderBy ?? [];
|
|
895
|
-
return [...rows].sort((a, b) =>
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
y === undefined || y === null
|
|
901
|
-
];
|
|
902
|
-
let result = missingX || missingY ? Number(!missingX) - Number(!missingY) : compareScalars(x, y);
|
|
903
|
-
if (direction === "desc")
|
|
904
|
-
result = -result;
|
|
905
|
-
if (result !== 0)
|
|
906
|
-
return result;
|
|
907
|
-
}
|
|
908
|
-
return 0;
|
|
909
|
-
});
|
|
1015
|
+
return [...rows].sort((a, b) => compareRows(orders, a, b));
|
|
1016
|
+
}
|
|
1017
|
+
function rowsAfter(rows, orders, after) {
|
|
1018
|
+
const position = Object.fromEntries(orders.map(({ key }, index) => [key, after[index]]));
|
|
1019
|
+
return rows.filter((row) => compareRows(orders, row, position) > 0);
|
|
910
1020
|
}
|
|
911
1021
|
function selectRows(rows, query) {
|
|
912
|
-
const
|
|
1022
|
+
const ordered = orderRows([...rows].filter((data) => matchesQuery(data, query)), query);
|
|
1023
|
+
const matched = query.after === undefined ? ordered : rowsAfter(ordered, query.orderBy ?? [], query.after);
|
|
913
1024
|
const start = query.offset ?? 0;
|
|
914
1025
|
return matched.slice(start, query.limit === undefined ? undefined : start + query.limit);
|
|
915
1026
|
}
|
|
@@ -961,7 +1072,7 @@ function scopeOf(initial, root) {
|
|
|
961
1072
|
}
|
|
962
1073
|
};
|
|
963
1074
|
const provider = {
|
|
964
|
-
capabilities: Object.freeze({ transactions: true }),
|
|
1075
|
+
capabilities: Object.freeze({ transactions: true, keyset: true }),
|
|
965
1076
|
findOne: (context, query = {}) => whenIdle(() => {
|
|
966
1077
|
const [first] = selectRows(tableOf(context).values(), {
|
|
967
1078
|
...query,
|
|
@@ -1091,6 +1202,14 @@ var origins = new WeakMap;
|
|
|
1091
1202
|
function declarationOf(state) {
|
|
1092
1203
|
return origins.get(state) ?? state;
|
|
1093
1204
|
}
|
|
1205
|
+
var declared = new WeakMap;
|
|
1206
|
+
function declareRepository(state, repository) {
|
|
1207
|
+
declared.set(state, repository);
|
|
1208
|
+
}
|
|
1209
|
+
function relatedRepositoryOf(repository, Entity) {
|
|
1210
|
+
const related = findState(repository)?.relations.get(Entity);
|
|
1211
|
+
return related ? declared.get(declarationOf(related)) : undefined;
|
|
1212
|
+
}
|
|
1094
1213
|
function bindState(state, provider, instances, bound = new Map) {
|
|
1095
1214
|
const done = bound.get(state);
|
|
1096
1215
|
if (done)
|
|
@@ -1193,7 +1312,7 @@ function relationsOf(name, entity, relations) {
|
|
|
1193
1312
|
if (!state)
|
|
1194
1313
|
return invalid("Expected relations made by repositoryOf.");
|
|
1195
1314
|
if (related.some((other) => other.entity === state.entity)) {
|
|
1196
|
-
return invalid(`Relation "${state.entity
|
|
1315
|
+
return invalid(`Relation "${recordNameOf2(state.entity)}" is duplicated.`);
|
|
1197
1316
|
}
|
|
1198
1317
|
if (typeof state.provider.findMany !== "function") {
|
|
1199
1318
|
return invalid(`The provider of "${state.name}" has no "findMany" to load it.`);
|
|
@@ -1206,10 +1325,10 @@ function relationsOf(name, entity, relations) {
|
|
|
1206
1325
|
const matches = related.filter((state) => state.entity === target || state.entity.prototype instanceof target);
|
|
1207
1326
|
const exact = matches.find((state) => state.entity === target);
|
|
1208
1327
|
if (!exact && matches.length === 0) {
|
|
1209
|
-
return invalid(`Missing relation: no repository of "${target
|
|
1328
|
+
return invalid(`Missing relation: no repository of "${recordNameOf2(target)}".`);
|
|
1210
1329
|
}
|
|
1211
1330
|
if (!exact && matches.length > 1) {
|
|
1212
|
-
return invalid(`Relation "${target
|
|
1331
|
+
return invalid(`Relation "${recordNameOf2(target)}" matches more than one repository.`);
|
|
1213
1332
|
}
|
|
1214
1333
|
const state = exact ?? matches[0];
|
|
1215
1334
|
map.set(target, state);
|
|
@@ -1217,7 +1336,7 @@ function relationsOf(name, entity, relations) {
|
|
|
1217
1336
|
}
|
|
1218
1337
|
for (const state of related) {
|
|
1219
1338
|
if (!used.has(state)) {
|
|
1220
|
-
return invalid(`Relation "${state.entity
|
|
1339
|
+
return invalid(`Relation "${recordNameOf2(state.entity)}" is not held by ${recordNameOf2(entity)}.`);
|
|
1221
1340
|
}
|
|
1222
1341
|
}
|
|
1223
1342
|
return map;
|
|
@@ -1368,6 +1487,44 @@ var findPage = action(async (self, query) => {
|
|
|
1368
1487
|
]);
|
|
1369
1488
|
return { items: await self.hydrateMany(rows), total, limit, offset };
|
|
1370
1489
|
});
|
|
1490
|
+
var CURSOR_OPTIONS = ["where", "orderBy", "limit", "withDeleted", "after"];
|
|
1491
|
+
var findCursor = action(async (self, query = {}) => {
|
|
1492
|
+
const { context } = self;
|
|
1493
|
+
const fail = (reason) => {
|
|
1494
|
+
throw new InvalidRepositoryQuery2({
|
|
1495
|
+
source: context.name,
|
|
1496
|
+
details: { reason }
|
|
1497
|
+
});
|
|
1498
|
+
};
|
|
1499
|
+
const prepared = prepareQuery(context, query, (key, value) => queryValueOf(context, key, value), CURSOR_OPTIONS);
|
|
1500
|
+
const { after } = query;
|
|
1501
|
+
const { where, limit = 20 } = prepared;
|
|
1502
|
+
if (limit < 1)
|
|
1503
|
+
fail("Expected a positive integer for limit.");
|
|
1504
|
+
const withDeleted = prepared.withDeleted ?? stateOf(self).deletion === "hard";
|
|
1505
|
+
const declared = prepared.orderBy ?? [];
|
|
1506
|
+
const orderBy = declared.some(({ key }) => key === "id") ? declared : [...declared, { key: "id", direction: "asc" }];
|
|
1507
|
+
for (const { key } of orderBy) {
|
|
1508
|
+
if (context.entity.options.sensitive.includes(key))
|
|
1509
|
+
fail(`"${key}" is sensitive and cannot order a cursor.`);
|
|
1510
|
+
}
|
|
1511
|
+
if (self.provider.capabilities?.keyset !== true)
|
|
1512
|
+
fail("The provider does not read by cursor (capabilities.keyset).");
|
|
1513
|
+
const rows = await self.provider.findMany(context, {
|
|
1514
|
+
where,
|
|
1515
|
+
orderBy,
|
|
1516
|
+
withDeleted,
|
|
1517
|
+
limit: limit + 1,
|
|
1518
|
+
...after === undefined ? {} : { after: decodeCursor(context.name, orderBy, after) }
|
|
1519
|
+
});
|
|
1520
|
+
const page = rows.slice(0, limit);
|
|
1521
|
+
const last = page.at(-1);
|
|
1522
|
+
return {
|
|
1523
|
+
items: await self.hydrateMany(page),
|
|
1524
|
+
limit,
|
|
1525
|
+
next: rows.length > limit && last !== undefined ? encodeCursor(orderBy, last) : undefined
|
|
1526
|
+
};
|
|
1527
|
+
});
|
|
1371
1528
|
var create = action(async (self, input) => {
|
|
1372
1529
|
const { entity: Entity } = self.context;
|
|
1373
1530
|
const entity = input instanceof Entity ? input : new Entity(input);
|
|
@@ -1402,6 +1559,29 @@ var remove = action(async (self, target) => {
|
|
|
1402
1559
|
}
|
|
1403
1560
|
return entity;
|
|
1404
1561
|
});
|
|
1562
|
+
var restore = action(async (self, target) => {
|
|
1563
|
+
const { context } = self;
|
|
1564
|
+
const id = typeof target === "string" ? target : self.dataOf(target).id;
|
|
1565
|
+
const data = await self.provider.findOne(context, {
|
|
1566
|
+
...equals(self, "id", id),
|
|
1567
|
+
withDeleted: true
|
|
1568
|
+
});
|
|
1569
|
+
if (data === undefined) {
|
|
1570
|
+
throw new RepositoryEntityNotFound2({
|
|
1571
|
+
source: context.name,
|
|
1572
|
+
details: { id }
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
if (data.deletedAt === undefined)
|
|
1576
|
+
return self.hydrate(data);
|
|
1577
|
+
const { deletedAt: _deletedAt, ...alive } = data;
|
|
1578
|
+
const restored = {
|
|
1579
|
+
...alive,
|
|
1580
|
+
updatedAt: context.entity[EntityClock].now()
|
|
1581
|
+
};
|
|
1582
|
+
await self.provider.update(context, restored);
|
|
1583
|
+
return self.hydrate(restored);
|
|
1584
|
+
});
|
|
1405
1585
|
var count = action(async (self, query) => self.provider.count(self.context, queryOf(self, query)));
|
|
1406
1586
|
var NEEDS = {
|
|
1407
1587
|
findBy: ["findOne"],
|
|
@@ -1409,10 +1589,12 @@ var NEEDS = {
|
|
|
1409
1589
|
create: ["insert"],
|
|
1410
1590
|
update: ["update"],
|
|
1411
1591
|
delete: ["findOne", "remove"],
|
|
1592
|
+
restore: ["findOne", "update"],
|
|
1412
1593
|
count: ["count"],
|
|
1413
1594
|
findMany: ["findMany"],
|
|
1414
1595
|
exists: ["findOne"],
|
|
1415
|
-
findPage: ["findMany", "count"]
|
|
1596
|
+
findPage: ["findMany", "count"],
|
|
1597
|
+
findCursor: ["findMany"]
|
|
1416
1598
|
};
|
|
1417
1599
|
function capitalize(key) {
|
|
1418
1600
|
return `${key.charAt(0).toUpperCase()}${key.slice(1)}`;
|
|
@@ -1438,7 +1620,7 @@ function repositoryOf2(settings) {
|
|
|
1438
1620
|
if (!isEntityClass2(entity) || !(EntityUniqueKeys2 in entity)) {
|
|
1439
1621
|
invalid3("repositoryOf", "Expected an entity class made by entityOf.");
|
|
1440
1622
|
}
|
|
1441
|
-
const name = `${entity
|
|
1623
|
+
const name = `${recordNameOf2(entity)}Repository`;
|
|
1442
1624
|
const deletion = settings.deletion ?? "hard";
|
|
1443
1625
|
if (deletion !== "hard" && deletion !== "soft") {
|
|
1444
1626
|
invalid3(name, 'Expected deletion to be "hard" or "soft".');
|
|
@@ -1464,18 +1646,23 @@ function repositoryOf2(settings) {
|
|
|
1464
1646
|
methods.set("create", ["create", create]);
|
|
1465
1647
|
methods.set("update", ["update", update]);
|
|
1466
1648
|
methods.set("delete", ["delete", remove]);
|
|
1649
|
+
methods.set("restore", ["restore", restore]);
|
|
1467
1650
|
methods.set("count", ["count", count]);
|
|
1468
1651
|
methods.set("findMany", ["findMany", findMany]);
|
|
1469
1652
|
methods.set("exists", ["exists", exists]);
|
|
1470
1653
|
methods.set("findPage", ["findPage", findPage]);
|
|
1654
|
+
methods.set("findCursor", ["findCursor", findCursor]);
|
|
1471
1655
|
|
|
1472
1656
|
class DeclaredRepository extends AbstractRepository2 {
|
|
1473
1657
|
}
|
|
1474
1658
|
for (const name_ of new Set(actions)) {
|
|
1475
1659
|
const method = typeof name_ === "string" ? methods.get(name_) : undefined;
|
|
1476
1660
|
if (!method)
|
|
1477
|
-
invalid3(name, unknownAction(entity
|
|
1661
|
+
invalid3(name, unknownAction(recordNameOf2(entity), String(name_), methods));
|
|
1478
1662
|
const [kind, implementation] = method;
|
|
1663
|
+
if (kind === "restore" && deletion !== "soft") {
|
|
1664
|
+
invalid3(name, '"restore" needs deletion to be "soft".');
|
|
1665
|
+
}
|
|
1479
1666
|
const needs = deletion === "soft" && kind === "delete" ? ["findOne", "update"] : NEEDS[kind] ?? [];
|
|
1480
1667
|
for (const need of needs) {
|
|
1481
1668
|
if (typeof provider?.[need] !== "function") {
|
|
@@ -1489,7 +1676,7 @@ function repositoryOf2(settings) {
|
|
|
1489
1676
|
});
|
|
1490
1677
|
}
|
|
1491
1678
|
Object.defineProperty(DeclaredRepository, "name", { value: name });
|
|
1492
|
-
|
|
1679
|
+
const state = {
|
|
1493
1680
|
name,
|
|
1494
1681
|
entity,
|
|
1495
1682
|
actions: Object.freeze([...new Set(actions)]),
|
|
@@ -1498,8 +1685,11 @@ function repositoryOf2(settings) {
|
|
|
1498
1685
|
explicitProvider: settings.provider !== undefined,
|
|
1499
1686
|
environment: environmentDependencies,
|
|
1500
1687
|
relations
|
|
1501
|
-
}
|
|
1502
|
-
|
|
1688
|
+
};
|
|
1689
|
+
states.set(DeclaredRepository, state);
|
|
1690
|
+
const repository = Object.freeze(new DeclaredRepository);
|
|
1691
|
+
declareRepository(state, repository);
|
|
1692
|
+
return repository;
|
|
1503
1693
|
}
|
|
1504
1694
|
function RepositoryOf2(repository) {
|
|
1505
1695
|
if (!isRepository2(repository)) {
|
|
@@ -1511,6 +1701,54 @@ function isRepository2(value) {
|
|
|
1511
1701
|
return value instanceof AbstractRepository2;
|
|
1512
1702
|
}
|
|
1513
1703
|
|
|
1704
|
+
// src/core/use-case/exceptions.ts
|
|
1705
|
+
var InvalidUseCaseStep2 = exceptionOf2({
|
|
1706
|
+
layer: "use-case" /* UseCase */,
|
|
1707
|
+
name: "InvalidUseCaseStep",
|
|
1708
|
+
message: ({
|
|
1709
|
+
index,
|
|
1710
|
+
reason
|
|
1711
|
+
}) => `Invalid step ${index}: ${reason}`
|
|
1712
|
+
});
|
|
1713
|
+
var InvalidUseCasePlugin2 = exceptionOf2({
|
|
1714
|
+
layer: "use-case" /* UseCase */,
|
|
1715
|
+
name: "InvalidUseCasePlugin",
|
|
1716
|
+
message: ({ reason }, source) => `Invalid use case plugin "${source}": ${reason}.`
|
|
1717
|
+
});
|
|
1718
|
+
var UnknownUseCasePlugin2 = exceptionOf2({
|
|
1719
|
+
layer: "use-case" /* UseCase */,
|
|
1720
|
+
name: "UnknownUseCasePlugin",
|
|
1721
|
+
message: ({ name }) => `No use case plugin "${name}" in the catalog.`
|
|
1722
|
+
});
|
|
1723
|
+
var IncompatibleUseCasePlugin2 = exceptionOf2({
|
|
1724
|
+
layer: "use-case" /* UseCase */,
|
|
1725
|
+
name: "IncompatibleUseCasePlugin",
|
|
1726
|
+
message: ({ reason }, source) => `Incompatible use case plugin "${source}": ${reason}.`
|
|
1727
|
+
});
|
|
1728
|
+
var UseCaseUnavailable2 = exceptionOf2({
|
|
1729
|
+
layer: "use-case" /* UseCase */,
|
|
1730
|
+
name: "UseCaseUnavailable",
|
|
1731
|
+
message: ({
|
|
1732
|
+
repository,
|
|
1733
|
+
reason
|
|
1734
|
+
}, source) => `The use case "${source}" is not available for "${repository}": ${reason}.`
|
|
1735
|
+
});
|
|
1736
|
+
var UseCaseEntityNotFound2 = exceptionOf2({
|
|
1737
|
+
layer: "use-case" /* UseCase */,
|
|
1738
|
+
name: "UseCaseEntityNotFound",
|
|
1739
|
+
message: "The entity was not found."
|
|
1740
|
+
});
|
|
1741
|
+
var UseCaseEntityConflict2 = exceptionOf2({
|
|
1742
|
+
layer: "use-case" /* UseCase */,
|
|
1743
|
+
name: "UseCaseEntityConflict",
|
|
1744
|
+
message: "The entity already exists."
|
|
1745
|
+
});
|
|
1746
|
+
var UseCaseInvalidCredentials2 = exceptionOf2({
|
|
1747
|
+
layer: "use-case" /* UseCase */,
|
|
1748
|
+
name: "UseCaseInvalidCredentials",
|
|
1749
|
+
message: "Invalid credentials."
|
|
1750
|
+
});
|
|
1751
|
+
|
|
1514
1752
|
// src/core/use-case/steps/references.ts
|
|
1515
1753
|
var RESERVED_NAMES = [
|
|
1516
1754
|
"input",
|
|
@@ -1618,15 +1856,104 @@ function freezeData(value) {
|
|
|
1618
1856
|
return value;
|
|
1619
1857
|
}
|
|
1620
1858
|
|
|
1621
|
-
// src/core/
|
|
1622
|
-
var
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
}
|
|
1859
|
+
// src/core/property/input.ts
|
|
1860
|
+
var PropertyInput2 = Symbol("propertyInput");
|
|
1861
|
+
function propertyInputOf2(field) {
|
|
1862
|
+
const property = fieldOf(field);
|
|
1863
|
+
if (!isProperty2(property))
|
|
1864
|
+
return;
|
|
1865
|
+
const declared = property[PropertyInput2];
|
|
1866
|
+
return typeof declared === "object" && declared !== null && isProperty2(declared.accepts) && typeof declared.convert === "function" ? declared : undefined;
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
// src/core/use-case/target.ts
|
|
1870
|
+
var IDENTITY = ENTITY_IDENTITY_KEYS2;
|
|
1871
|
+
function isOptionalField(field) {
|
|
1872
|
+
if (isProperty2(field))
|
|
1873
|
+
return field.type.optional === true;
|
|
1874
|
+
const record = recordFieldOf2(field);
|
|
1875
|
+
if (record)
|
|
1876
|
+
return record.optional;
|
|
1877
|
+
return field.optional === true;
|
|
1878
|
+
}
|
|
1879
|
+
function planOf(entity, key, owners) {
|
|
1880
|
+
const field = fieldOf(entity.blueprint.properties[key]);
|
|
1881
|
+
const optional = isOptionalField(field);
|
|
1882
|
+
const record = recordFieldOf2(field);
|
|
1883
|
+
const item = isBlueprintList2(field) ? recordFieldOf2(field.item) : undefined;
|
|
1884
|
+
const related = [record?.record, item?.record].find(isEntityClass2);
|
|
1885
|
+
const kind = record ? related ? "entity" : "record" : isBlueprintList2(field) ? related ? "entity-list" : "list" : isBlueprint2(field) ? "blueprint" : "property";
|
|
1886
|
+
let input = { key, field };
|
|
1887
|
+
if (kind === "entity" && related) {
|
|
1888
|
+
const id = related.blueprint.properties.id;
|
|
1889
|
+
input = { key: `${key}Id`, field: optional ? optionalOf2(id) : id };
|
|
1890
|
+
}
|
|
1891
|
+
const declared = kind === "property" ? propertyInputOf2(field) : undefined;
|
|
1892
|
+
if (declared) {
|
|
1893
|
+
input = {
|
|
1894
|
+
key,
|
|
1895
|
+
field: optional ? optionalOf2(declared.accepts) : declared.accepts
|
|
1896
|
+
};
|
|
1897
|
+
}
|
|
1898
|
+
return Object.freeze({
|
|
1899
|
+
key,
|
|
1900
|
+
field,
|
|
1901
|
+
identity: IDENTITY.includes(key),
|
|
1902
|
+
plugin: owners.get(key),
|
|
1903
|
+
sensitive: entity.options.sensitive.includes(key),
|
|
1904
|
+
unique: entity.options.unique.includes(key),
|
|
1905
|
+
optional,
|
|
1906
|
+
change: entity.options.change.includes(key),
|
|
1907
|
+
kind,
|
|
1908
|
+
related,
|
|
1909
|
+
input: Object.freeze(input),
|
|
1910
|
+
convert: declared ? (value) => value === undefined ? undefined : declared.convert(value) : undefined
|
|
1911
|
+
});
|
|
1912
|
+
}
|
|
1913
|
+
function useCaseTargetOf2(repository, source = "useCaseTargetOf") {
|
|
1914
|
+
const state = findState(repository);
|
|
1915
|
+
if (!state)
|
|
1916
|
+
throw new TypeError("Expected a repository made by repositoryOf");
|
|
1917
|
+
const { entity } = state;
|
|
1918
|
+
const unavailable = (reason) => {
|
|
1919
|
+
throw new UseCaseUnavailable2({
|
|
1920
|
+
source,
|
|
1921
|
+
details: { repository: state.name, reason }
|
|
1922
|
+
});
|
|
1923
|
+
};
|
|
1924
|
+
const owners = new Map;
|
|
1925
|
+
for (const plugin of pluginsOf2(entity)) {
|
|
1926
|
+
for (const key of plugin.keys)
|
|
1927
|
+
owners.set(key, plugin.name);
|
|
1928
|
+
}
|
|
1929
|
+
const fields = Object.freeze(Object.keys(entity.blueprint.properties).map((key) => planOf(entity, key, owners)));
|
|
1930
|
+
const field = (key) => fields.find((plan) => plan.key === key) ?? unavailable(`the entity has no key "${key}"`);
|
|
1931
|
+
return Object.freeze({
|
|
1932
|
+
repository,
|
|
1933
|
+
repositoryName: state.name,
|
|
1934
|
+
entity,
|
|
1935
|
+
entityName: recordNameOf2(entity),
|
|
1936
|
+
actions: state.actions,
|
|
1937
|
+
deletion: state.deletion,
|
|
1938
|
+
fields,
|
|
1939
|
+
changeKeys: entity.options.change,
|
|
1940
|
+
uniqueKeys: entity.options.unique,
|
|
1941
|
+
field,
|
|
1942
|
+
relation(key, actions = []) {
|
|
1943
|
+
const { related } = field(key);
|
|
1944
|
+
const found = related ? relatedRepositoryOf(repository, related) : undefined;
|
|
1945
|
+
if (!found) {
|
|
1946
|
+
return unavailable(`the repository has no relation for the entity of "${key}"`);
|
|
1947
|
+
}
|
|
1948
|
+
const missing = actions.filter((action) => !found.actions.includes(action));
|
|
1949
|
+
if (missing.length > 0) {
|
|
1950
|
+
unavailable(`the repository of "${key}" lacks the action ${missing.map((action) => `"${action}"`).join(", ")}`);
|
|
1951
|
+
}
|
|
1952
|
+
return found;
|
|
1953
|
+
},
|
|
1954
|
+
unavailable
|
|
1955
|
+
});
|
|
1956
|
+
}
|
|
1630
1957
|
|
|
1631
1958
|
// src/core/use-case/steps/run.ts
|
|
1632
1959
|
var FORBIDDEN_METHODS = new Set([
|
|
@@ -1660,7 +1987,6 @@ async function executeSteps(dependencies = {}) {
|
|
|
1660
1987
|
return followPath(named.get(root), path);
|
|
1661
1988
|
};
|
|
1662
1989
|
const value = (raw) => resolveValue(raw, lookup);
|
|
1663
|
-
const list = (raw) => raw === undefined ? [] : value(raw);
|
|
1664
1990
|
for (const [index, step] of steps.entries()) {
|
|
1665
1991
|
const invalid = (reason) => {
|
|
1666
1992
|
throw new InvalidUseCaseStep2({
|
|
@@ -1668,6 +1994,14 @@ async function executeSteps(dependencies = {}) {
|
|
|
1668
1994
|
details: { index, reason, code: "invalid_target" }
|
|
1669
1995
|
});
|
|
1670
1996
|
};
|
|
1997
|
+
const list = (raw) => {
|
|
1998
|
+
if (raw === undefined)
|
|
1999
|
+
return [];
|
|
2000
|
+
const resolved = value(raw);
|
|
2001
|
+
if (!Array.isArray(resolved))
|
|
2002
|
+
invalid("args must resolve to a list.");
|
|
2003
|
+
return resolved;
|
|
2004
|
+
};
|
|
1671
2005
|
const bind = (result) => {
|
|
1672
2006
|
if (typeof step.as === "string")
|
|
1673
2007
|
named.set(step.as, result);
|
|
@@ -1712,7 +2046,10 @@ async function executeSteps(dependencies = {}) {
|
|
|
1712
2046
|
if (typeof namespace !== "object" || namespace === null || typeof namespace[key] !== "function") {
|
|
1713
2047
|
invalid(`"${key}" is not a change key of the record.`);
|
|
1714
2048
|
}
|
|
1715
|
-
|
|
2049
|
+
const changed = value(step.value);
|
|
2050
|
+
if (step.ifPresent !== true || changed !== undefined) {
|
|
2051
|
+
await namespace[key]?.(changed);
|
|
2052
|
+
}
|
|
1716
2053
|
bind(record);
|
|
1717
2054
|
} else if (Object.hasOwn(step, "invoke")) {
|
|
1718
2055
|
const record = target("invoke");
|
|
@@ -1728,7 +2065,7 @@ async function executeSteps(dependencies = {}) {
|
|
|
1728
2065
|
const fn = step.fn;
|
|
1729
2066
|
bind(await fn(...list(step.args), { context: this.context }));
|
|
1730
2067
|
} else {
|
|
1731
|
-
const action = ["create", "update", "delete"].find((kind) => Object.hasOwn(step, kind));
|
|
2068
|
+
const action = ["create", "update", "delete", "restore"].find((kind) => Object.hasOwn(step, kind));
|
|
1732
2069
|
const methods = dependencies[step[action]];
|
|
1733
2070
|
bind(await methods[action].call(methods, value(step.value)));
|
|
1734
2071
|
}
|
|
@@ -1748,11 +2085,15 @@ var SHAPES = {
|
|
|
1748
2085
|
optional: ["other", "details"]
|
|
1749
2086
|
},
|
|
1750
2087
|
build: { required: ["build", "values", "as"], optional: [] },
|
|
1751
|
-
change: {
|
|
2088
|
+
change: {
|
|
2089
|
+
required: ["change", "key", "value"],
|
|
2090
|
+
optional: ["ifPresent", "as"]
|
|
2091
|
+
},
|
|
1752
2092
|
invoke: { required: ["invoke", "method"], optional: ["args", "as"] },
|
|
1753
2093
|
create: { required: ["create", "value"], optional: ["as"] },
|
|
1754
2094
|
update: { required: ["update", "value"], optional: ["as"] },
|
|
1755
2095
|
delete: { required: ["delete", "value"], optional: ["as"] },
|
|
2096
|
+
restore: { required: ["restore", "value"], optional: ["as"] },
|
|
1756
2097
|
call: { required: ["call", "input"], optional: ["as"] },
|
|
1757
2098
|
run: { required: ["run", "fn"], optional: ["args", "as"] },
|
|
1758
2099
|
return: { required: ["return"], optional: [] }
|
|
@@ -1766,8 +2107,8 @@ var ENSURE_KINDS = new Set([
|
|
|
1766
2107
|
"true",
|
|
1767
2108
|
"false"
|
|
1768
2109
|
]);
|
|
1769
|
-
var READ_ACTION = /^(?:findBy.+|findManyBy.+|findMany|exists|findPage|count)$/;
|
|
1770
|
-
var WRITE_ACTIONS = new Set(["create", "update", "delete"]);
|
|
2110
|
+
var READ_ACTION = /^(?:findBy.+|findManyBy.+|findMany|exists|findPage|findCursor|count)$/;
|
|
2111
|
+
var WRITE_ACTIONS = new Set(["create", "update", "delete", "restore"]);
|
|
1771
2112
|
function validateSteps(declaration, steps) {
|
|
1772
2113
|
const { name } = declaration;
|
|
1773
2114
|
const fail = (index, code, reason) => {
|
|
@@ -1823,8 +2164,12 @@ function validateSteps(declaration, steps) {
|
|
|
1823
2164
|
const checkArgs = (index, args) => {
|
|
1824
2165
|
if (args === undefined)
|
|
1825
2166
|
return;
|
|
2167
|
+
if (typeof args === "string" && args.startsWith("$")) {
|
|
2168
|
+
checkReference(index, args);
|
|
2169
|
+
return;
|
|
2170
|
+
}
|
|
1826
2171
|
if (!Array.isArray(args)) {
|
|
1827
|
-
fail(index, "invalid_step", "args must be a list of values.");
|
|
2172
|
+
fail(index, "invalid_step", "args must be a list of values or a reference to one.");
|
|
1828
2173
|
}
|
|
1829
2174
|
checkValue(index, args, "args");
|
|
1830
2175
|
};
|
|
@@ -1931,6 +2276,9 @@ function validateSteps(declaration, steps) {
|
|
|
1931
2276
|
if (typeof record.key !== "string" || record.key === "") {
|
|
1932
2277
|
fail(index, "invalid_step", "change.key must be a string.");
|
|
1933
2278
|
}
|
|
2279
|
+
if (record.ifPresent !== undefined && typeof record.ifPresent !== "boolean") {
|
|
2280
|
+
fail(index, "invalid_step", "change.ifPresent must be a boolean.");
|
|
2281
|
+
}
|
|
1934
2282
|
const reference = parseReference(record.change);
|
|
1935
2283
|
const known = reference && reference.path.length === 0 ? bound.get(reference.root)?.record : undefined;
|
|
1936
2284
|
if (known) {
|
|
@@ -1953,7 +2301,8 @@ function validateSteps(declaration, steps) {
|
|
|
1953
2301
|
}
|
|
1954
2302
|
case "create":
|
|
1955
2303
|
case "update":
|
|
1956
|
-
case "delete":
|
|
2304
|
+
case "delete":
|
|
2305
|
+
case "restore": {
|
|
1957
2306
|
repositoryOf(index, record[kind], kind);
|
|
1958
2307
|
checkValue(index, record.value, "value");
|
|
1959
2308
|
break;
|
|
@@ -2068,7 +2417,7 @@ function methodNamesOf(repository) {
|
|
|
2068
2417
|
}
|
|
2069
2418
|
function bindRepository2(repository, overrides, state, source) {
|
|
2070
2419
|
const Entity = repository.entity;
|
|
2071
|
-
const repositoryName = Entity
|
|
2420
|
+
const repositoryName = recordNameOf2(Entity);
|
|
2072
2421
|
const bound = Object.create(null);
|
|
2073
2422
|
const view = new Proxy(Object.create(repository), {
|
|
2074
2423
|
get: (_target, key, receiver) => {
|
|
@@ -2372,6 +2721,195 @@ function isUseCase2(value) {
|
|
|
2372
2721
|
return value instanceof AbstractUseCase2;
|
|
2373
2722
|
}
|
|
2374
2723
|
|
|
2724
|
+
// src/core/use-case/plugin.ts
|
|
2725
|
+
var UseCaseOrigin = Symbol("useCaseOrigin");
|
|
2726
|
+
var NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
2727
|
+
var identities = new WeakMap;
|
|
2728
|
+
var nextIdentity = 0;
|
|
2729
|
+
function keyOf(value) {
|
|
2730
|
+
if (typeof value === "function" || typeof value === "object" && value) {
|
|
2731
|
+
if (Array.isArray(value))
|
|
2732
|
+
return `[${value.map(keyOf).join(",")}]`;
|
|
2733
|
+
if (isPlain2(value)) {
|
|
2734
|
+
return `{${Object.keys(value).sort().filter((key) => value[key] !== undefined).map((key) => `${JSON.stringify(key)}:${keyOf(value[key])}`).join(",")}}`;
|
|
2735
|
+
}
|
|
2736
|
+
let identity = identities.get(value);
|
|
2737
|
+
if (identity === undefined) {
|
|
2738
|
+
identity = nextIdentity++;
|
|
2739
|
+
identities.set(value, identity);
|
|
2740
|
+
}
|
|
2741
|
+
return `#${identity}`;
|
|
2742
|
+
}
|
|
2743
|
+
return value === undefined ? "undefined" : JSON.stringify(value);
|
|
2744
|
+
}
|
|
2745
|
+
function isUseCasePlugin2(value) {
|
|
2746
|
+
return typeof value === "object" && value !== null && value[Kind2] === "use-case-plugin";
|
|
2747
|
+
}
|
|
2748
|
+
function useCaseOriginOf2(UseCase) {
|
|
2749
|
+
return typeof UseCase === "function" ? UseCase[UseCaseOrigin] : undefined;
|
|
2750
|
+
}
|
|
2751
|
+
function useCasePluginOf2(name, settings) {
|
|
2752
|
+
const source = typeof name === "string" ? name : "useCasePluginOf";
|
|
2753
|
+
const invalid = (reason) => {
|
|
2754
|
+
throw new InvalidUseCasePlugin2({ source, details: { reason } });
|
|
2755
|
+
};
|
|
2756
|
+
if (typeof name !== "string" || !NAME.test(name)) {
|
|
2757
|
+
invalid("its name must be an identifier");
|
|
2758
|
+
}
|
|
2759
|
+
if (!isPlain2(settings) || typeof settings.compile !== "function") {
|
|
2760
|
+
invalid("it needs a compile function");
|
|
2761
|
+
}
|
|
2762
|
+
const { version = "1.0.0", description, compile } = settings;
|
|
2763
|
+
if (!isVersion(version)) {
|
|
2764
|
+
invalid(`"${String(version)}" is not a semantic version`);
|
|
2765
|
+
}
|
|
2766
|
+
if (description !== undefined && typeof description !== "string") {
|
|
2767
|
+
invalid("its description must be a string");
|
|
2768
|
+
}
|
|
2769
|
+
const names = (list, what) => {
|
|
2770
|
+
if (list === undefined)
|
|
2771
|
+
return Object.freeze([]);
|
|
2772
|
+
if (!Array.isArray(list) || list.some((item) => typeof item !== "string" || item === "")) {
|
|
2773
|
+
invalid(`requires.${what} must be a list of names`);
|
|
2774
|
+
}
|
|
2775
|
+
return Object.freeze([...new Set(list)]);
|
|
2776
|
+
};
|
|
2777
|
+
if (settings.requires !== undefined && !isPlain2(settings.requires)) {
|
|
2778
|
+
invalid("requires must be an object of actions and plugins");
|
|
2779
|
+
}
|
|
2780
|
+
const requires = Object.freeze({
|
|
2781
|
+
actions: names(settings.requires?.actions, "actions"),
|
|
2782
|
+
plugins: names(settings.requires?.plugins, "plugins")
|
|
2783
|
+
});
|
|
2784
|
+
const made = new WeakMap;
|
|
2785
|
+
const make = (repository, given) => {
|
|
2786
|
+
if (!isRepository2(repository)) {
|
|
2787
|
+
return invalid("it makes a use case for a repository of repositoryOf");
|
|
2788
|
+
}
|
|
2789
|
+
if (given !== undefined && !isPlain2(given)) {
|
|
2790
|
+
return invalid("its options must be a plain object");
|
|
2791
|
+
}
|
|
2792
|
+
const options = Object.freeze({ ...given });
|
|
2793
|
+
const info = inspectRepository(repository);
|
|
2794
|
+
let classes = made.get(info.state);
|
|
2795
|
+
if (!classes) {
|
|
2796
|
+
classes = new Map;
|
|
2797
|
+
made.set(info.state, classes);
|
|
2798
|
+
}
|
|
2799
|
+
const key = keyOf(options);
|
|
2800
|
+
const known = classes.get(key);
|
|
2801
|
+
if (known)
|
|
2802
|
+
return known;
|
|
2803
|
+
const target = useCaseTargetOf2(repository, name);
|
|
2804
|
+
const missing = {
|
|
2805
|
+
actions: requires.actions.filter((action) => !info.actions.includes(action)),
|
|
2806
|
+
plugins: requires.plugins.filter((plugin) => !hasPlugin2(info.entity, plugin))
|
|
2807
|
+
};
|
|
2808
|
+
if (missing.actions.length > 0 || missing.plugins.length > 0) {
|
|
2809
|
+
const quoted = (list) => list.map((item) => `"${item}"`).join(", ");
|
|
2810
|
+
throw new UseCaseUnavailable2({
|
|
2811
|
+
source: name,
|
|
2812
|
+
details: {
|
|
2813
|
+
repository: target.repositoryName,
|
|
2814
|
+
reason: [
|
|
2815
|
+
missing.actions.length > 0 ? `the repository lacks the action ${quoted(missing.actions)}` : undefined,
|
|
2816
|
+
missing.plugins.length > 0 ? `the entity lacks the plugin ${quoted(missing.plugins)}` : undefined
|
|
2817
|
+
].filter(Boolean).join(" and "),
|
|
2818
|
+
missing
|
|
2819
|
+
}
|
|
2820
|
+
});
|
|
2821
|
+
}
|
|
2822
|
+
const UseCase = useCaseOf2(compile(target, options));
|
|
2823
|
+
Object.defineProperty(UseCase, UseCaseOrigin, {
|
|
2824
|
+
value: Object.freeze({
|
|
2825
|
+
plugin,
|
|
2826
|
+
repository,
|
|
2827
|
+
options
|
|
2828
|
+
})
|
|
2829
|
+
});
|
|
2830
|
+
classes.set(key, UseCase);
|
|
2831
|
+
return UseCase;
|
|
2832
|
+
};
|
|
2833
|
+
const plugin = Object.freeze({
|
|
2834
|
+
[Kind2]: "use-case-plugin",
|
|
2835
|
+
name,
|
|
2836
|
+
version,
|
|
2837
|
+
...description === undefined ? {} : { description },
|
|
2838
|
+
requires,
|
|
2839
|
+
make,
|
|
2840
|
+
toJSON: () => ({
|
|
2841
|
+
kind: "use-case-plugin",
|
|
2842
|
+
name,
|
|
2843
|
+
version,
|
|
2844
|
+
...description === undefined ? {} : { description },
|
|
2845
|
+
requires
|
|
2846
|
+
})
|
|
2847
|
+
});
|
|
2848
|
+
return plugin;
|
|
2849
|
+
}
|
|
2850
|
+
|
|
2851
|
+
// src/core/use-case/plugin-catalog.ts
|
|
2852
|
+
function isUseCaseCatalog2(value) {
|
|
2853
|
+
return typeof value === "object" && value !== null && value[Kind2] === "use-case-catalog";
|
|
2854
|
+
}
|
|
2855
|
+
function useCaseCatalogOf2(...plugins) {
|
|
2856
|
+
const byName = {};
|
|
2857
|
+
const context = {};
|
|
2858
|
+
for (const plugin of plugins) {
|
|
2859
|
+
if (!isUseCasePlugin2(plugin)) {
|
|
2860
|
+
throw new InvalidUseCasePlugin2({
|
|
2861
|
+
source: "catalog",
|
|
2862
|
+
details: {
|
|
2863
|
+
reason: "a catalog takes plugins made by `useCasePluginOf`"
|
|
2864
|
+
}
|
|
2865
|
+
});
|
|
2866
|
+
}
|
|
2867
|
+
if (Object.hasOwn(byName, plugin.name)) {
|
|
2868
|
+
throw new InvalidUseCasePlugin2({
|
|
2869
|
+
source: plugin.name,
|
|
2870
|
+
details: { reason: "the catalog has another plugin with its name" }
|
|
2871
|
+
});
|
|
2872
|
+
}
|
|
2873
|
+
byName[plugin.name] = plugin;
|
|
2874
|
+
context[plugin.name] = plugin.make;
|
|
2875
|
+
}
|
|
2876
|
+
const find = (name) => {
|
|
2877
|
+
const plugin = Object.hasOwn(byName, name) ? byName[name] : undefined;
|
|
2878
|
+
if (!plugin) {
|
|
2879
|
+
throw new UnknownUseCasePlugin2({ source: "catalog", details: { name } });
|
|
2880
|
+
}
|
|
2881
|
+
return plugin;
|
|
2882
|
+
};
|
|
2883
|
+
return Object.freeze({
|
|
2884
|
+
[Kind2]: "use-case-catalog",
|
|
2885
|
+
plugins: Object.freeze(byName),
|
|
2886
|
+
list: Object.freeze([...plugins]),
|
|
2887
|
+
context: Object.freeze(context),
|
|
2888
|
+
resolve(reference) {
|
|
2889
|
+
const at = typeof reference === "string" ? reference.lastIndexOf("@") : -1;
|
|
2890
|
+
if (at <= 0)
|
|
2891
|
+
return find(String(reference));
|
|
2892
|
+
const plugin = find(reference.slice(0, at));
|
|
2893
|
+
const version = reference.slice(at + 1);
|
|
2894
|
+
if (!isVersion(version)) {
|
|
2895
|
+
throw new IncompatibleUseCasePlugin2({
|
|
2896
|
+
source: plugin.name,
|
|
2897
|
+
details: { reason: `"${version}" is not a semantic version` }
|
|
2898
|
+
});
|
|
2899
|
+
}
|
|
2900
|
+
if (!satisfies(plugin.version, version)) {
|
|
2901
|
+
throw new IncompatibleUseCasePlugin2({
|
|
2902
|
+
source: plugin.name,
|
|
2903
|
+
details: {
|
|
2904
|
+
reason: `version ${plugin.version} does not satisfy ${version}`
|
|
2905
|
+
}
|
|
2906
|
+
});
|
|
2907
|
+
}
|
|
2908
|
+
return plugin;
|
|
2909
|
+
}
|
|
2910
|
+
});
|
|
2911
|
+
}
|
|
2912
|
+
|
|
2375
2913
|
// src/core/command/exceptions.ts
|
|
2376
2914
|
var InvalidCommandDeclaration2 = exceptionOf2({
|
|
2377
2915
|
layer: "command" /* Command */,
|
|
@@ -2526,9 +3064,12 @@ function callEnvironment(source, environments, given) {
|
|
|
2526
3064
|
}
|
|
2527
3065
|
return { byEnvironment, merged: Object.freeze(merged) };
|
|
2528
3066
|
}
|
|
2529
|
-
function
|
|
2530
|
-
const { name, description, context,
|
|
3067
|
+
function declareCommand(given, generics) {
|
|
3068
|
+
const { name, description, context, onEventError } = given;
|
|
2531
3069
|
const source = typeof name === "string" ? name : "commandOf";
|
|
3070
|
+
const picked = given.useCases;
|
|
3071
|
+
const useCases = typeof picked === "function" && !isUseCaseClass2(picked) ? guard2({ layer: "command" /* Command */, source }, () => picked(generics)) : picked;
|
|
3072
|
+
const settings = given;
|
|
2532
3073
|
const invalid = (reason) => {
|
|
2533
3074
|
throw new InvalidCommandDeclaration2({ source, details: { reason } });
|
|
2534
3075
|
};
|
|
@@ -2546,7 +3087,7 @@ function commandOf2(settings) {
|
|
|
2546
3087
|
invalid("Expected context to be a non-optional blueprint.");
|
|
2547
3088
|
}
|
|
2548
3089
|
if (typeof useCases !== "object" || useCases === null || Array.isArray(useCases) || Reflect.ownKeys(useCases).length === 0) {
|
|
2549
|
-
invalid("Expected useCases to be a non-empty object of use case classes.");
|
|
3090
|
+
invalid("Expected useCases to be a non-empty object of use case classes, or a function returning one.");
|
|
2550
3091
|
}
|
|
2551
3092
|
for (const key of Reflect.ownKeys(useCases)) {
|
|
2552
3093
|
if (typeof key !== "string" || !IDENTIFIER2.test(key) || !isUseCaseClass2(useCases[key])) {
|
|
@@ -2684,6 +3225,19 @@ function commandOf2(settings) {
|
|
|
2684
3225
|
verifiers.set(command, providerOfCall);
|
|
2685
3226
|
return command;
|
|
2686
3227
|
}
|
|
3228
|
+
function commandBuilderOf2(...catalogs) {
|
|
3229
|
+
if (!catalogs.every(isUseCaseCatalog2)) {
|
|
3230
|
+
throw new InvalidCommandDeclaration2({
|
|
3231
|
+
source: "commandBuilderOf",
|
|
3232
|
+
details: {
|
|
3233
|
+
reason: "Expected catalogs made by useCaseCatalogOf."
|
|
3234
|
+
}
|
|
3235
|
+
});
|
|
3236
|
+
}
|
|
3237
|
+
const generics = Object.freeze(Object.assign({}, ...catalogs.map((catalog) => catalog.context)));
|
|
3238
|
+
return (settings) => declareCommand(settings, generics);
|
|
3239
|
+
}
|
|
3240
|
+
var commandOf = commandBuilderOf2();
|
|
2687
3241
|
async function applyPolicy(command, policy, onEventError, call) {
|
|
2688
3242
|
const { operation, raised } = call;
|
|
2689
3243
|
const source = `${command.commandName}.${operation}`;
|
|
@@ -3522,7 +4076,7 @@ function isOfKind(collection, value) {
|
|
|
3522
4076
|
return isDeclaredException(value);
|
|
3523
4077
|
}
|
|
3524
4078
|
}
|
|
3525
|
-
function
|
|
4079
|
+
function keyOf2(collection, value) {
|
|
3526
4080
|
switch (collection) {
|
|
3527
4081
|
case "properties":
|
|
3528
4082
|
return fieldOf(value);
|
|
@@ -3597,7 +4151,7 @@ function resolve(settings) {
|
|
|
3597
4151
|
if (!isOfKind(collection, value)) {
|
|
3598
4152
|
invalid7(source, `${collection}.${name} is not a ${KIND_OF[collection]} (${collection} takes ${KIND_OF[collection]} declarations).`);
|
|
3599
4153
|
}
|
|
3600
|
-
const declaredName = collection === "useCases" ? value.useCaseName : collection === "commands" ? value.commandName : collection === "outputs" ? value.outputName : collection === "exceptions" ? value.name : name;
|
|
4154
|
+
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;
|
|
3601
4155
|
if (declaredName !== name) {
|
|
3602
4156
|
invalid7(source, `${collection}.${name} must be keyed by its name, "${declaredName}".`);
|
|
3603
4157
|
}
|
|
@@ -3605,11 +4159,25 @@ function resolve(settings) {
|
|
|
3605
4159
|
collection,
|
|
3606
4160
|
name,
|
|
3607
4161
|
value,
|
|
3608
|
-
key:
|
|
4162
|
+
key: keyOf2(collection, value),
|
|
3609
4163
|
from: source
|
|
3610
4164
|
});
|
|
3611
4165
|
}
|
|
3612
4166
|
}
|
|
4167
|
+
for (const entry of [...registry.commands.values()]) {
|
|
4168
|
+
for (const UseCase of Object.values(entry.value.useCases)) {
|
|
4169
|
+
if (useCaseOriginOf2(UseCase) === undefined || byKey.has(UseCase)) {
|
|
4170
|
+
continue;
|
|
4171
|
+
}
|
|
4172
|
+
add({
|
|
4173
|
+
collection: "useCases",
|
|
4174
|
+
name: UseCase.useCaseName,
|
|
4175
|
+
value: UseCase,
|
|
4176
|
+
key: UseCase,
|
|
4177
|
+
from: entry.from
|
|
4178
|
+
});
|
|
4179
|
+
}
|
|
4180
|
+
}
|
|
3613
4181
|
return registry;
|
|
3614
4182
|
}
|
|
3615
4183
|
function recordsIn(properties) {
|
|
@@ -3652,7 +4220,7 @@ function validate(source, registry) {
|
|
|
3652
4220
|
source,
|
|
3653
4221
|
details: {
|
|
3654
4222
|
holder: `${KIND_OF[collection]} "${entry.name}"`,
|
|
3655
|
-
reference: `${isEntityClass2(record) ? "entity" : "record"} "${record
|
|
4223
|
+
reference: `${isEntityClass2(record) ? "entity" : "record"} "${recordNameOf2(record)}"`
|
|
3656
4224
|
}
|
|
3657
4225
|
});
|
|
3658
4226
|
}
|
|
@@ -3673,7 +4241,7 @@ function validate(source, registry) {
|
|
|
3673
4241
|
}
|
|
3674
4242
|
for (const relation of info.relations) {
|
|
3675
4243
|
if (!repositories.has(relation)) {
|
|
3676
|
-
unregistered(entry.name, `the repository of "${relation.entity
|
|
4244
|
+
unregistered(entry.name, `the repository of "${recordNameOf2(relation.entity)}"`);
|
|
3677
4245
|
}
|
|
3678
4246
|
}
|
|
3679
4247
|
for (const environment of info.environments) {
|
|
@@ -3772,7 +4340,7 @@ function validateUseCases(source, registry) {
|
|
|
3772
4340
|
source,
|
|
3773
4341
|
details: {
|
|
3774
4342
|
holder: `the ${part} of use case "${entry.name}"`,
|
|
3775
|
-
reference: `${isEntityClass2(record) ? "entity" : "record"} "${record
|
|
4343
|
+
reference: `${isEntityClass2(record) ? "entity" : "record"} "${recordNameOf2(record)}"`
|
|
3776
4344
|
}
|
|
3777
4345
|
});
|
|
3778
4346
|
}
|
|
@@ -3795,7 +4363,7 @@ function validateUseCases(source, registry) {
|
|
|
3795
4363
|
source,
|
|
3796
4364
|
details: {
|
|
3797
4365
|
holder,
|
|
3798
|
-
reference: `${isEntityClass2(step.build) ? "entity" : "record"} "${step.build
|
|
4366
|
+
reference: `${isEntityClass2(step.build) ? "entity" : "record"} "${recordNameOf2(step.build)}"`
|
|
3799
4367
|
}
|
|
3800
4368
|
});
|
|
3801
4369
|
}
|
|
@@ -3881,7 +4449,7 @@ function fieldDescriptionOf(field, scope) {
|
|
|
3881
4449
|
const ref = entity ?? scope.records.get(record.record);
|
|
3882
4450
|
return {
|
|
3883
4451
|
kind: entity === undefined ? "record" : "entity",
|
|
3884
|
-
ref: ref ?? record.record
|
|
4452
|
+
ref: ref ?? recordNameOf2(record.record),
|
|
3885
4453
|
optional: record.optional
|
|
3886
4454
|
};
|
|
3887
4455
|
}
|
|
@@ -3946,6 +4514,36 @@ function describeRegistry(name, extended, registry, provider, named, log) {
|
|
|
3946
4514
|
})
|
|
3947
4515
|
};
|
|
3948
4516
|
};
|
|
4517
|
+
const describeOption = (value) => {
|
|
4518
|
+
if (typeof value === "function") {
|
|
4519
|
+
if (isDeclaredException(value)) {
|
|
4520
|
+
return exceptionRefs.get(value) ?? { core: value.name };
|
|
4521
|
+
}
|
|
4522
|
+
return scope.entities.get(value) ?? scope.records.get(value) ?? { kind: "opaque" };
|
|
4523
|
+
}
|
|
4524
|
+
if (Array.isArray(value))
|
|
4525
|
+
return value.map(describeOption);
|
|
4526
|
+
if (typeof value === "object" && value !== null) {
|
|
4527
|
+
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined).map(([key, item]) => [key, describeOption(item)]));
|
|
4528
|
+
}
|
|
4529
|
+
return value;
|
|
4530
|
+
};
|
|
4531
|
+
const describeGeneric = (UseCase) => {
|
|
4532
|
+
const origin = useCaseOriginOf2(UseCase);
|
|
4533
|
+
if (!origin || interpretedStepsOf(UseCase) === undefined)
|
|
4534
|
+
return {};
|
|
4535
|
+
const repository = repositories.get(inspectRepository(origin.repository).state);
|
|
4536
|
+
if (repository === undefined)
|
|
4537
|
+
return {};
|
|
4538
|
+
return {
|
|
4539
|
+
generic: {
|
|
4540
|
+
name: origin.plugin.name,
|
|
4541
|
+
version: origin.plugin.version,
|
|
4542
|
+
repository,
|
|
4543
|
+
options: describeOption(origin.options)
|
|
4544
|
+
}
|
|
4545
|
+
};
|
|
4546
|
+
};
|
|
3949
4547
|
const refsOf = (blueprints) => {
|
|
3950
4548
|
const refs = {};
|
|
3951
4549
|
for (const [key, blueprint] of Object.entries(blueprints)) {
|
|
@@ -4047,7 +4645,8 @@ function describeRegistry(name, extended, registry, provider, named, log) {
|
|
|
4047
4645
|
ref: repositories.get(inspectRepository(dependency).state)
|
|
4048
4646
|
}
|
|
4049
4647
|
])),
|
|
4050
|
-
implementation: describeImplementation(UseCase)
|
|
4648
|
+
implementation: describeImplementation(UseCase),
|
|
4649
|
+
...describeGeneric(UseCase)
|
|
4051
4650
|
};
|
|
4052
4651
|
}),
|
|
4053
4652
|
commands: each("commands", (entry) => {
|
|
@@ -4246,7 +4845,7 @@ function applicationOf2(settings) {
|
|
|
4246
4845
|
const boundCommandOf = new Map;
|
|
4247
4846
|
for (const entry of registry.commands.values()) {
|
|
4248
4847
|
const declared = entry.value;
|
|
4249
|
-
const rebuilt = !rebinds ? declared :
|
|
4848
|
+
const rebuilt = !rebinds ? declared : commandOf({
|
|
4250
4849
|
name: declared.commandName,
|
|
4251
4850
|
description: declared.description,
|
|
4252
4851
|
tags: declared.tags,
|
|
@@ -4353,6 +4952,14 @@ function applicationOf2(settings) {
|
|
|
4353
4952
|
}
|
|
4354
4953
|
return boundUseCaseOf.get(useCase);
|
|
4355
4954
|
},
|
|
4955
|
+
async run(useCase, input = {}, options) {
|
|
4956
|
+
if (!boundUseCaseOf.has(useCase)) {
|
|
4957
|
+
invalid7(name, "Expected a use case registered in the application.");
|
|
4958
|
+
}
|
|
4959
|
+
const Bound = boundUseCaseOf.get(useCase);
|
|
4960
|
+
const instance = options?.context === undefined ? new Bound(input) : new Bound(input, { context: options.context });
|
|
4961
|
+
return instance.execute(options?.dependencies);
|
|
4962
|
+
},
|
|
4356
4963
|
command(command) {
|
|
4357
4964
|
if (!boundCommandOf.has(command)) {
|
|
4358
4965
|
invalid7(name, "Expected a command registered in the application.");
|
|
@@ -4480,7 +5087,12 @@ var INVALID_INPUT = new Set([
|
|
|
4480
5087
|
var CONFIGURATION = new Set(["InvalidCommandEnvironment"]);
|
|
4481
5088
|
var NOT_FOUND = new Set([
|
|
4482
5089
|
"RepositoryEntityNotFound",
|
|
4483
|
-
"UnknownCommandOperation"
|
|
5090
|
+
"UnknownCommandOperation",
|
|
5091
|
+
"UseCaseEntityNotFound"
|
|
5092
|
+
]);
|
|
5093
|
+
var CONFLICT = new Set([
|
|
5094
|
+
"RepositoryUniqueViolation",
|
|
5095
|
+
"UseCaseEntityConflict"
|
|
4484
5096
|
]);
|
|
4485
5097
|
function raisedOf(error) {
|
|
4486
5098
|
let raised = error;
|
|
@@ -4505,8 +5117,10 @@ function statusOf2(error, operation, statuses) {
|
|
|
4505
5117
|
}
|
|
4506
5118
|
if (NOT_FOUND.has(name))
|
|
4507
5119
|
return 404;
|
|
4508
|
-
if (name
|
|
5120
|
+
if (CONFLICT.has(name))
|
|
4509
5121
|
return 409;
|
|
5122
|
+
if (name === "UseCaseInvalidCredentials")
|
|
5123
|
+
return 401;
|
|
4510
5124
|
if (operation?.errors.some((Declared) => error.is(Declared) || Declared.name === name)) {
|
|
4511
5125
|
return 422;
|
|
4512
5126
|
}
|
|
@@ -4763,7 +5377,7 @@ var openapi2 = sinkOf2({
|
|
|
4763
5377
|
const models = {};
|
|
4764
5378
|
const classes = new Map;
|
|
4765
5379
|
for (const record of records) {
|
|
4766
|
-
const name = record
|
|
5380
|
+
const name = recordNameOf2(record);
|
|
4767
5381
|
if (!/^[A-Za-z0-9._-]+$/.test(name) || name === "Problem") {
|
|
4768
5382
|
invalid8(`The record class "${name}" cannot be a component name; give the class a name that is not "Problem".`);
|
|
4769
5383
|
}
|
|
@@ -4851,17 +5465,6 @@ var select2 = transformOf2({
|
|
|
4851
5465
|
}
|
|
4852
5466
|
});
|
|
4853
5467
|
// src/core/record/plugin-catalog.ts
|
|
4854
|
-
function satisfies(available, requested) {
|
|
4855
|
-
const [major, minor, patch] = versionParts(available);
|
|
4856
|
-
const [wantedMajor, wantedMinor, wantedPatch] = versionParts(requested);
|
|
4857
|
-
if (major !== wantedMajor)
|
|
4858
|
-
return false;
|
|
4859
|
-
if (major === 0 && minor !== wantedMinor)
|
|
4860
|
-
return false;
|
|
4861
|
-
if (minor !== wantedMinor)
|
|
4862
|
-
return minor > wantedMinor;
|
|
4863
|
-
return patch >= wantedPatch;
|
|
4864
|
-
}
|
|
4865
5468
|
var LISTS2 = ["keys", "sensitive", "unique", "change", "members"];
|
|
4866
5469
|
function isDescriptor(value) {
|
|
4867
5470
|
if (typeof value !== "object" || value === null)
|
|
@@ -5318,6 +5921,19 @@ function passwordIssues2(value) {
|
|
|
5318
5921
|
}
|
|
5319
5922
|
return issues;
|
|
5320
5923
|
}
|
|
5924
|
+
var escaped = PASSWORD_SPECIAL_CHARACTERS2.replace(/[\\\]^-]/g, "\\$&");
|
|
5925
|
+
|
|
5926
|
+
class PlainPassword2 extends propertyOf2({
|
|
5927
|
+
name: "plainPassword",
|
|
5928
|
+
schema: Type5.String({
|
|
5929
|
+
minLength: PASSWORD_MIN_LENGTH2,
|
|
5930
|
+
maxLength: PASSWORD_MAX_LENGTH2,
|
|
5931
|
+
pattern: `^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[${escaped}])`
|
|
5932
|
+
}),
|
|
5933
|
+
default: "Demo@123",
|
|
5934
|
+
sensitive: true
|
|
5935
|
+
}) {
|
|
5936
|
+
}
|
|
5321
5937
|
|
|
5322
5938
|
class Password2 extends propertyOf2({
|
|
5323
5939
|
name: "password",
|
|
@@ -5325,6 +5941,10 @@ class Password2 extends propertyOf2({
|
|
|
5325
5941
|
default: DEMO_HASH,
|
|
5326
5942
|
sensitive: true
|
|
5327
5943
|
}) {
|
|
5944
|
+
static [PropertyInput2] = {
|
|
5945
|
+
accepts: PlainPassword2,
|
|
5946
|
+
convert: async (plain) => (await Password2.hash(plain)).value
|
|
5947
|
+
};
|
|
5328
5948
|
static async hash(plain) {
|
|
5329
5949
|
const issues = passwordIssues2(plain);
|
|
5330
5950
|
if (issues.length > 0) {
|
|
@@ -5452,6 +6072,511 @@ var Std2 = blueprintPluginOf2("std", {
|
|
|
5452
6072
|
|
|
5453
6073
|
// src/std/blueprint.ts
|
|
5454
6074
|
var blueprintOf2 = blueprintBuilderOf2(Std2);
|
|
6075
|
+
// src/std/use-cases/shared.ts
|
|
6076
|
+
var fields = blueprintBuilderOf2();
|
|
6077
|
+
var IDENTIFIER3 = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
6078
|
+
var capitalize2 = (key) => `${key.charAt(0).toUpperCase()}${key.slice(1)}`;
|
|
6079
|
+
function literal(value) {
|
|
6080
|
+
if (typeof value === "string") {
|
|
6081
|
+
return value.startsWith("$") ? { literal: value } : value;
|
|
6082
|
+
}
|
|
6083
|
+
if (Array.isArray(value))
|
|
6084
|
+
return value.map(literal);
|
|
6085
|
+
if (isPlain2(value)) {
|
|
6086
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, literal(item)]));
|
|
6087
|
+
}
|
|
6088
|
+
return value;
|
|
6089
|
+
}
|
|
6090
|
+
function checkOptions(target, options, allowed) {
|
|
6091
|
+
const known = [...COMMON, ...allowed];
|
|
6092
|
+
for (const key of Object.keys(options)) {
|
|
6093
|
+
if (!known.includes(key)) {
|
|
6094
|
+
target.unavailable(`it has no option "${key}" (options: ${known.join(", ")})`);
|
|
6095
|
+
}
|
|
6096
|
+
}
|
|
6097
|
+
}
|
|
6098
|
+
var COMMON = ["name", "description", "tags", "deprecated", "errors"];
|
|
6099
|
+
function metaOf(target, options, verb) {
|
|
6100
|
+
const { name, description, tags, deprecated } = options;
|
|
6101
|
+
if (name !== undefined && (typeof name !== "string" || name === "")) {
|
|
6102
|
+
target.unavailable("the option name must be a non-empty string");
|
|
6103
|
+
}
|
|
6104
|
+
return {
|
|
6105
|
+
name: name ?? `${verb}${target.entityName}`,
|
|
6106
|
+
...description === undefined ? {} : { description },
|
|
6107
|
+
...tags === undefined ? {} : { tags },
|
|
6108
|
+
...deprecated === undefined ? {} : { deprecated }
|
|
6109
|
+
};
|
|
6110
|
+
}
|
|
6111
|
+
function asOf(target, options) {
|
|
6112
|
+
const { as = "item" } = options;
|
|
6113
|
+
if (typeof as !== "string" || !IDENTIFIER3.test(as)) {
|
|
6114
|
+
target.unavailable("the option as must be an identifier");
|
|
6115
|
+
}
|
|
6116
|
+
return as;
|
|
6117
|
+
}
|
|
6118
|
+
function errorOf(target, options, kind, fallback) {
|
|
6119
|
+
const { errors } = options;
|
|
6120
|
+
if (errors === undefined)
|
|
6121
|
+
return fallback;
|
|
6122
|
+
if (!isPlain2(errors)) {
|
|
6123
|
+
return target.unavailable("the option errors must be an object of exception classes");
|
|
6124
|
+
}
|
|
6125
|
+
const given = errors[kind];
|
|
6126
|
+
if (given === undefined)
|
|
6127
|
+
return fallback;
|
|
6128
|
+
if (!isDeclaredException(given)) {
|
|
6129
|
+
return target.unavailable(`errors.${kind} must be a class made by exceptionOf`);
|
|
6130
|
+
}
|
|
6131
|
+
return given;
|
|
6132
|
+
}
|
|
6133
|
+
function keysOf(target, options, option) {
|
|
6134
|
+
const given = options[option];
|
|
6135
|
+
if (given === undefined)
|
|
6136
|
+
return [];
|
|
6137
|
+
if (!Array.isArray(given) || given.some((key) => typeof key !== "string")) {
|
|
6138
|
+
return target.unavailable(`the option ${option} must be a list of keys`);
|
|
6139
|
+
}
|
|
6140
|
+
return given.map((key) => target.field(key));
|
|
6141
|
+
}
|
|
6142
|
+
function valuesOf(target, options, option) {
|
|
6143
|
+
const given = options[option];
|
|
6144
|
+
if (given === undefined)
|
|
6145
|
+
return {};
|
|
6146
|
+
if (!isPlain2(given)) {
|
|
6147
|
+
return target.unavailable(`the option ${option} must be an object`);
|
|
6148
|
+
}
|
|
6149
|
+
for (const key of Object.keys(given)) {
|
|
6150
|
+
if (target.field(key).identity) {
|
|
6151
|
+
target.unavailable(`${option}.${key} is an identity key`);
|
|
6152
|
+
}
|
|
6153
|
+
}
|
|
6154
|
+
return given;
|
|
6155
|
+
}
|
|
6156
|
+
function ownerOf(target, options) {
|
|
6157
|
+
const { owner } = options;
|
|
6158
|
+
if (owner === undefined)
|
|
6159
|
+
return;
|
|
6160
|
+
if (!isPlain2(owner) || typeof owner.context !== "string" || !IDENTIFIER3.test(owner.context) || owner.key !== undefined && typeof owner.key !== "string") {
|
|
6161
|
+
return target.unavailable("the option owner must be { key?, context }, context naming a context key");
|
|
6162
|
+
}
|
|
6163
|
+
const plan = target.field(owner.key ?? "ownerId");
|
|
6164
|
+
if (plan.identity || plan.optional) {
|
|
6165
|
+
target.unavailable(`owner.key "${plan.key}" must be a required key`);
|
|
6166
|
+
}
|
|
6167
|
+
if (plan.kind === "entity" && plan.related) {
|
|
6168
|
+
return {
|
|
6169
|
+
plan,
|
|
6170
|
+
context: owner.context,
|
|
6171
|
+
field: plan.related.blueprint.properties.id,
|
|
6172
|
+
reference: `$item.${plan.key}.id`
|
|
6173
|
+
};
|
|
6174
|
+
}
|
|
6175
|
+
if (plan.kind !== "property") {
|
|
6176
|
+
target.unavailable(`owner.key "${plan.key}" must hold an id or an entity`);
|
|
6177
|
+
}
|
|
6178
|
+
return {
|
|
6179
|
+
plan,
|
|
6180
|
+
context: owner.context,
|
|
6181
|
+
field: plan.field,
|
|
6182
|
+
reference: `$item.${plan.key}`
|
|
6183
|
+
};
|
|
6184
|
+
}
|
|
6185
|
+
function contextOf2(owner) {
|
|
6186
|
+
return owner ? { context: fields(() => ({ [owner.context]: owner.field })) } : {};
|
|
6187
|
+
}
|
|
6188
|
+
function loadSteps(owner, notFound) {
|
|
6189
|
+
return [
|
|
6190
|
+
{ find: "repository.findById", args: ["$input.id"], as: "item" },
|
|
6191
|
+
{ ensure: "exists", value: "$item", error: notFound },
|
|
6192
|
+
...owner ? [
|
|
6193
|
+
{
|
|
6194
|
+
ensure: "equals",
|
|
6195
|
+
value: owner.reference,
|
|
6196
|
+
other: `$context.${owner.context}`,
|
|
6197
|
+
error: notFound
|
|
6198
|
+
}
|
|
6199
|
+
] : []
|
|
6200
|
+
];
|
|
6201
|
+
}
|
|
6202
|
+
var idOf2 = (target) => target.field("id").field;
|
|
6203
|
+
var distinct = (...errors) => [...new Set(errors)];
|
|
6204
|
+
function entityUseCase(target, options, verb, allowed, steps) {
|
|
6205
|
+
checkOptions(target, options, ["as", "owner", ...allowed]);
|
|
6206
|
+
const owner = ownerOf(target, options);
|
|
6207
|
+
const notFound = errorOf(target, options, "notFound", UseCaseEntityNotFound2);
|
|
6208
|
+
const body = steps({ owner });
|
|
6209
|
+
return {
|
|
6210
|
+
...metaOf(target, options, verb),
|
|
6211
|
+
blueprint: fields(() => ({ id: idOf2(target) })),
|
|
6212
|
+
response: fields(() => ({ [asOf(target, options)]: target.entity })),
|
|
6213
|
+
...contextOf2(owner),
|
|
6214
|
+
dependencies: { repository: target.repository },
|
|
6215
|
+
errors: [notFound],
|
|
6216
|
+
steps: [
|
|
6217
|
+
...loadSteps(owner, notFound),
|
|
6218
|
+
...body.steps,
|
|
6219
|
+
{ return: { [asOf(target, options)]: body.result } }
|
|
6220
|
+
]
|
|
6221
|
+
};
|
|
6222
|
+
}
|
|
6223
|
+
|
|
6224
|
+
// src/std/use-cases/crud.ts
|
|
6225
|
+
function createCompilation(target, options, verb, before = [], errors = []) {
|
|
6226
|
+
checkOptions(target, options, ["as", "owner", "omit", "values"]);
|
|
6227
|
+
const owner = ownerOf(target, options);
|
|
6228
|
+
const omitted = keysOf(target, options, "omit");
|
|
6229
|
+
const constants = valuesOf(target, options, "values");
|
|
6230
|
+
const notFound = errorOf(target, options, "notFound", UseCaseEntityNotFound2);
|
|
6231
|
+
const ownerError = errorOf(target, options, "owner", notFound);
|
|
6232
|
+
const input = {};
|
|
6233
|
+
const values = {};
|
|
6234
|
+
const dependencies = {
|
|
6235
|
+
repository: target.repository
|
|
6236
|
+
};
|
|
6237
|
+
const steps = [];
|
|
6238
|
+
const used = new Set(errors);
|
|
6239
|
+
const take = (key, field) => {
|
|
6240
|
+
if (Object.hasOwn(input, key)) {
|
|
6241
|
+
target.unavailable(`two keys of the entity take the input "${key}"`);
|
|
6242
|
+
}
|
|
6243
|
+
input[key] = field;
|
|
6244
|
+
};
|
|
6245
|
+
const load = (key, reference, error) => {
|
|
6246
|
+
const dependency = `${key}Repository`;
|
|
6247
|
+
dependencies[dependency] = target.relation(key, ["findById"]);
|
|
6248
|
+
used.add(error);
|
|
6249
|
+
steps.push({
|
|
6250
|
+
find: `${dependency}.findById`,
|
|
6251
|
+
args: [reference],
|
|
6252
|
+
as: `${key}Found`
|
|
6253
|
+
}, { ensure: "exists", value: `$${key}Found`, error });
|
|
6254
|
+
return `$${key}Found`;
|
|
6255
|
+
};
|
|
6256
|
+
for (const plan of target.fields) {
|
|
6257
|
+
const { key } = plan;
|
|
6258
|
+
if (plan.identity)
|
|
6259
|
+
continue;
|
|
6260
|
+
if (Object.hasOwn(constants, key)) {
|
|
6261
|
+
values[key] = literal(constants[key]);
|
|
6262
|
+
continue;
|
|
6263
|
+
}
|
|
6264
|
+
if (owner?.plan.key === key) {
|
|
6265
|
+
values[key] = plan.kind === "entity" ? load(key, `$context.${owner.context}`, ownerError) : `$context.${owner.context}`;
|
|
6266
|
+
continue;
|
|
6267
|
+
}
|
|
6268
|
+
const unloadable = plan.kind === "entity-list" || plan.kind === "entity" && plan.optional;
|
|
6269
|
+
if (omitted.includes(plan) || unloadable) {
|
|
6270
|
+
if (!plan.optional) {
|
|
6271
|
+
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`);
|
|
6272
|
+
}
|
|
6273
|
+
continue;
|
|
6274
|
+
}
|
|
6275
|
+
take(plan.input.key, plan.input.field);
|
|
6276
|
+
const given = `$input.${plan.input.key}`;
|
|
6277
|
+
if (plan.kind === "entity") {
|
|
6278
|
+
values[key] = load(key, given, notFound);
|
|
6279
|
+
} else if (plan.convert) {
|
|
6280
|
+
steps.push({
|
|
6281
|
+
run: `convert${capitalize2(key)}`,
|
|
6282
|
+
fn: plan.convert,
|
|
6283
|
+
args: [given],
|
|
6284
|
+
as: `${key}Converted`
|
|
6285
|
+
});
|
|
6286
|
+
values[key] = `$${key}Converted`;
|
|
6287
|
+
} else {
|
|
6288
|
+
values[key] = given;
|
|
6289
|
+
}
|
|
6290
|
+
}
|
|
6291
|
+
return {
|
|
6292
|
+
...metaOf(target, options, verb),
|
|
6293
|
+
blueprint: fields(() => input),
|
|
6294
|
+
response: fields(() => ({ [asOf(target, options)]: target.entity })),
|
|
6295
|
+
...contextOf2(owner),
|
|
6296
|
+
dependencies,
|
|
6297
|
+
errors: distinct(...used),
|
|
6298
|
+
steps: [
|
|
6299
|
+
...before,
|
|
6300
|
+
...steps,
|
|
6301
|
+
{ build: target.entity, values, as: "built" },
|
|
6302
|
+
{ create: "repository", value: "$built", as: "item" },
|
|
6303
|
+
{ return: { [asOf(target, options)]: "$item" } }
|
|
6304
|
+
]
|
|
6305
|
+
};
|
|
6306
|
+
}
|
|
6307
|
+
var create2 = useCasePluginOf2("create", {
|
|
6308
|
+
description: "Builds an entity from the input (its keys, less the identity; an entity it holds by id) and stores it.",
|
|
6309
|
+
requires: { actions: ["create"] },
|
|
6310
|
+
compile: (target, options) => createCompilation(target, options, "Create")
|
|
6311
|
+
});
|
|
6312
|
+
var get = useCasePluginOf2("get", {
|
|
6313
|
+
description: "Gives the entity of an id.",
|
|
6314
|
+
requires: { actions: ["findById"] },
|
|
6315
|
+
compile: (target, options) => entityUseCase(target, options, "Get", [], () => ({
|
|
6316
|
+
steps: [],
|
|
6317
|
+
result: "$item"
|
|
6318
|
+
}))
|
|
6319
|
+
});
|
|
6320
|
+
var list = useCasePluginOf2("list", {
|
|
6321
|
+
description: "Gives a page of entities: { items, total, limit, offset }.",
|
|
6322
|
+
requires: { actions: ["findPage"] },
|
|
6323
|
+
compile: (target, options) => {
|
|
6324
|
+
checkOptions(target, options, ["owner", "orderBy"]);
|
|
6325
|
+
const owner = ownerOf(target, options);
|
|
6326
|
+
const { orderBy = [{ key: "createdAt", direction: "desc" }] } = options;
|
|
6327
|
+
if (!Array.isArray(orderBy)) {
|
|
6328
|
+
target.unavailable("the option orderBy must be a list of orders");
|
|
6329
|
+
}
|
|
6330
|
+
for (const order of orderBy) {
|
|
6331
|
+
target.field(String(order?.key));
|
|
6332
|
+
}
|
|
6333
|
+
return {
|
|
6334
|
+
...metaOf(target, options, "List"),
|
|
6335
|
+
blueprint: fields(() => ({
|
|
6336
|
+
limit: optionalOf2(Integer2),
|
|
6337
|
+
offset: optionalOf2(Integer2)
|
|
6338
|
+
})),
|
|
6339
|
+
response: fields(() => ({
|
|
6340
|
+
items: listOf2(target.entity),
|
|
6341
|
+
total: Integer2,
|
|
6342
|
+
limit: Integer2,
|
|
6343
|
+
offset: Integer2
|
|
6344
|
+
})),
|
|
6345
|
+
...contextOf2(owner),
|
|
6346
|
+
dependencies: { repository: target.repository },
|
|
6347
|
+
steps: [
|
|
6348
|
+
{
|
|
6349
|
+
find: "repository.findPage",
|
|
6350
|
+
args: [
|
|
6351
|
+
{
|
|
6352
|
+
...owner ? {
|
|
6353
|
+
where: {
|
|
6354
|
+
[owner.plan.key]: `$context.${owner.context}`
|
|
6355
|
+
}
|
|
6356
|
+
} : {},
|
|
6357
|
+
orderBy: literal(orderBy),
|
|
6358
|
+
limit: "$input.limit",
|
|
6359
|
+
offset: "$input.offset"
|
|
6360
|
+
}
|
|
6361
|
+
],
|
|
6362
|
+
as: "page"
|
|
6363
|
+
},
|
|
6364
|
+
{
|
|
6365
|
+
return: {
|
|
6366
|
+
items: "$page.items",
|
|
6367
|
+
total: "$page.total",
|
|
6368
|
+
limit: "$page.limit",
|
|
6369
|
+
offset: "$page.offset"
|
|
6370
|
+
}
|
|
6371
|
+
}
|
|
6372
|
+
]
|
|
6373
|
+
};
|
|
6374
|
+
}
|
|
6375
|
+
});
|
|
6376
|
+
var update2 = useCasePluginOf2("update", {
|
|
6377
|
+
description: "Changes the change keys the input gives, leaving the others as they are, and stores the entity.",
|
|
6378
|
+
requires: { actions: ["findById", "update"] },
|
|
6379
|
+
compile: (target, options) => {
|
|
6380
|
+
checkOptions(target, options, ["as", "owner"]);
|
|
6381
|
+
const owner = ownerOf(target, options);
|
|
6382
|
+
const notFound = errorOf(target, options, "notFound", UseCaseEntityNotFound2);
|
|
6383
|
+
const plans = target.changeKeys.map((key) => target.field(key)).filter((plan) => plan.kind !== "entity" && plan.kind !== "entity-list");
|
|
6384
|
+
if (plans.length === 0) {
|
|
6385
|
+
target.unavailable("the entity has no change key an input can take");
|
|
6386
|
+
}
|
|
6387
|
+
if (plans.some((plan) => plan.key === "id")) {
|
|
6388
|
+
target.unavailable('"id" cannot be a change key');
|
|
6389
|
+
}
|
|
6390
|
+
const steps = [];
|
|
6391
|
+
for (const plan of plans) {
|
|
6392
|
+
const given = `$input.${plan.input.key}`;
|
|
6393
|
+
let value = given;
|
|
6394
|
+
if (plan.convert) {
|
|
6395
|
+
steps.push({
|
|
6396
|
+
run: `convert${capitalize2(plan.key)}`,
|
|
6397
|
+
fn: plan.convert,
|
|
6398
|
+
args: [given],
|
|
6399
|
+
as: `${plan.key}Converted`
|
|
6400
|
+
});
|
|
6401
|
+
value = `$${plan.key}Converted`;
|
|
6402
|
+
}
|
|
6403
|
+
steps.push({ change: "$item", key: plan.key, value, ifPresent: true });
|
|
6404
|
+
}
|
|
6405
|
+
return {
|
|
6406
|
+
...metaOf(target, options, "Update"),
|
|
6407
|
+
blueprint: fields(() => ({
|
|
6408
|
+
id: idOf2(target),
|
|
6409
|
+
...Object.fromEntries(plans.map((plan) => [plan.input.key, optionalOf2(plan.input.field)]))
|
|
6410
|
+
})),
|
|
6411
|
+
response: fields(() => ({ [asOf(target, options)]: target.entity })),
|
|
6412
|
+
...contextOf2(owner),
|
|
6413
|
+
dependencies: { repository: target.repository },
|
|
6414
|
+
errors: [notFound],
|
|
6415
|
+
steps: [
|
|
6416
|
+
...loadSteps(owner, notFound),
|
|
6417
|
+
...steps,
|
|
6418
|
+
{ update: "repository", value: "$item", as: "saved" },
|
|
6419
|
+
{ return: { [asOf(target, options)]: "$saved" } }
|
|
6420
|
+
]
|
|
6421
|
+
};
|
|
6422
|
+
}
|
|
6423
|
+
});
|
|
6424
|
+
var remove2 = useCasePluginOf2("delete", {
|
|
6425
|
+
description: "Deletes the entity of an id, hard or soft as the repository declares, and gives it.",
|
|
6426
|
+
requires: { actions: ["findById", "delete"] },
|
|
6427
|
+
compile: (target, options) => entityUseCase(target, options, "Delete", [], () => ({
|
|
6428
|
+
steps: [{ delete: "repository", value: "$item", as: "deleted" }],
|
|
6429
|
+
result: "$deleted"
|
|
6430
|
+
}))
|
|
6431
|
+
});
|
|
6432
|
+
var restore2 = useCasePluginOf2("restore", {
|
|
6433
|
+
description: "Brings back the soft-deleted entity of an id; for a repository with soft deletion and the restore action.",
|
|
6434
|
+
requires: { actions: ["restore"] },
|
|
6435
|
+
compile: (target, options) => {
|
|
6436
|
+
checkOptions(target, options, ["as"]);
|
|
6437
|
+
return {
|
|
6438
|
+
...metaOf(target, options, "Restore"),
|
|
6439
|
+
blueprint: fields(() => ({ id: idOf2(target) })),
|
|
6440
|
+
response: fields(() => ({ [asOf(target, options)]: target.entity })),
|
|
6441
|
+
dependencies: { repository: target.repository },
|
|
6442
|
+
steps: [
|
|
6443
|
+
{ restore: "repository", value: "$input.id", as: "item" },
|
|
6444
|
+
{ return: { [asOf(target, options)]: "$item" } }
|
|
6445
|
+
]
|
|
6446
|
+
};
|
|
6447
|
+
}
|
|
6448
|
+
});
|
|
6449
|
+
|
|
6450
|
+
// src/std/use-cases/auth.ts
|
|
6451
|
+
var authenticatable = (actions) => ({
|
|
6452
|
+
requires: { actions, plugins: ["authenticatable"] }
|
|
6453
|
+
});
|
|
6454
|
+
var register = useCasePluginOf2("register", {
|
|
6455
|
+
description: "Stores an authenticatable entity built from the input, its password given plain and stored as a hash, unless the email is taken.",
|
|
6456
|
+
...authenticatable(["findByEmail", "create"]),
|
|
6457
|
+
compile: (target, options) => {
|
|
6458
|
+
const conflict = errorOf(target, options, "conflict", UseCaseEntityConflict2);
|
|
6459
|
+
return createCompilation(target, options, "Register", [
|
|
6460
|
+
{
|
|
6461
|
+
find: "repository.findByEmail",
|
|
6462
|
+
args: ["$input.email"],
|
|
6463
|
+
as: "existing"
|
|
6464
|
+
},
|
|
6465
|
+
{ ensure: "absent", value: "$existing", error: conflict }
|
|
6466
|
+
], [conflict]);
|
|
6467
|
+
}
|
|
6468
|
+
});
|
|
6469
|
+
var login = useCasePluginOf2("login", {
|
|
6470
|
+
description: "Gives the authenticatable entity of an email when the password matches; issues no token.",
|
|
6471
|
+
...authenticatable(["findByEmail"]),
|
|
6472
|
+
compile: (target, options) => {
|
|
6473
|
+
checkOptions(target, options, ["as"]);
|
|
6474
|
+
const invalid = errorOf(target, options, "invalid", UseCaseInvalidCredentials2);
|
|
6475
|
+
return {
|
|
6476
|
+
...metaOf(target, options, "Login"),
|
|
6477
|
+
blueprint: fields(() => ({
|
|
6478
|
+
email: target.field("email").field,
|
|
6479
|
+
password: StringProperty2
|
|
6480
|
+
})),
|
|
6481
|
+
response: fields(() => ({ [asOf(target, options)]: target.entity })),
|
|
6482
|
+
dependencies: { repository: target.repository },
|
|
6483
|
+
errors: [invalid],
|
|
6484
|
+
steps: [
|
|
6485
|
+
{ find: "repository.findByEmail", args: ["$input.email"], as: "item" },
|
|
6486
|
+
{ ensure: "exists", value: "$item", error: invalid },
|
|
6487
|
+
{
|
|
6488
|
+
invoke: "$item",
|
|
6489
|
+
method: "verifyPassword",
|
|
6490
|
+
args: ["$input.password"],
|
|
6491
|
+
as: "matches"
|
|
6492
|
+
},
|
|
6493
|
+
{ ensure: "true", value: "$matches", error: invalid },
|
|
6494
|
+
{ return: { [asOf(target, options)]: "$item" } }
|
|
6495
|
+
]
|
|
6496
|
+
};
|
|
6497
|
+
}
|
|
6498
|
+
});
|
|
6499
|
+
|
|
6500
|
+
// src/std/use-cases/lifecycle.ts
|
|
6501
|
+
function invoking(target, options, verb, method) {
|
|
6502
|
+
const set = valuesOf(target, options, "set");
|
|
6503
|
+
const changes = Object.entries(set).map(([key, value]) => {
|
|
6504
|
+
if (!target.changeKeys.includes(key)) {
|
|
6505
|
+
target.unavailable(`set.${key} is not a change key of the entity`);
|
|
6506
|
+
}
|
|
6507
|
+
return { change: "$item", key, value: literal(value) };
|
|
6508
|
+
});
|
|
6509
|
+
return entityUseCase(target, options, verb, ["set"], () => ({
|
|
6510
|
+
steps: [
|
|
6511
|
+
...changes,
|
|
6512
|
+
{ invoke: "$item", method },
|
|
6513
|
+
{ update: "repository", value: "$item", as: "saved" }
|
|
6514
|
+
],
|
|
6515
|
+
result: "$saved"
|
|
6516
|
+
}));
|
|
6517
|
+
}
|
|
6518
|
+
var lifecycle = (plugin) => ({
|
|
6519
|
+
requires: { actions: ["findById", "update"], plugins: [plugin] }
|
|
6520
|
+
});
|
|
6521
|
+
var publish = useCasePluginOf2("publish", {
|
|
6522
|
+
description: "Publishes the publishable entity of an id and stores it.",
|
|
6523
|
+
...lifecycle("publishable"),
|
|
6524
|
+
compile: (target, options) => invoking(target, options, "Publish", "publish")
|
|
6525
|
+
});
|
|
6526
|
+
var unpublish = useCasePluginOf2("unpublish", {
|
|
6527
|
+
description: "Makes the publishable entity of an id a draft again and stores it.",
|
|
6528
|
+
...lifecycle("publishable"),
|
|
6529
|
+
compile: (target, options) => invoking(target, options, "Unpublish", "unpublish")
|
|
6530
|
+
});
|
|
6531
|
+
var archive = useCasePluginOf2("archive", {
|
|
6532
|
+
description: "Archives the archivable entity of an id and stores it.",
|
|
6533
|
+
...lifecycle("archivable"),
|
|
6534
|
+
compile: (target, options) => invoking(target, options, "Archive", "archive")
|
|
6535
|
+
});
|
|
6536
|
+
var unarchive = useCasePluginOf2("unarchive", {
|
|
6537
|
+
description: "Makes the archived entity of an id active again and stores it.",
|
|
6538
|
+
...lifecycle("archivable"),
|
|
6539
|
+
compile: (target, options) => invoking(target, options, "Unarchive", "restore")
|
|
6540
|
+
});
|
|
6541
|
+
function tagging(target, options, verb, method) {
|
|
6542
|
+
checkOptions(target, options, ["as", "owner"]);
|
|
6543
|
+
const owner = ownerOf(target, options);
|
|
6544
|
+
const notFound = errorOf(target, options, "notFound", UseCaseEntityNotFound2);
|
|
6545
|
+
const { item } = target.field("tags").field;
|
|
6546
|
+
return {
|
|
6547
|
+
...metaOf(target, options, verb),
|
|
6548
|
+
blueprint: fields(() => ({
|
|
6549
|
+
id: idOf2(target),
|
|
6550
|
+
tags: listOf2(item, { minItems: 1 })
|
|
6551
|
+
})),
|
|
6552
|
+
response: fields(() => ({ [asOf(target, options)]: target.entity })),
|
|
6553
|
+
...contextOf2(owner),
|
|
6554
|
+
dependencies: { repository: target.repository },
|
|
6555
|
+
errors: [notFound],
|
|
6556
|
+
steps: [
|
|
6557
|
+
...loadSteps(owner, notFound),
|
|
6558
|
+
{ invoke: "$item", method, args: "$input.tags" },
|
|
6559
|
+
{ update: "repository", value: "$item", as: "saved" },
|
|
6560
|
+
{ return: { [asOf(target, options)]: "$saved" } }
|
|
6561
|
+
]
|
|
6562
|
+
};
|
|
6563
|
+
}
|
|
6564
|
+
var tag = useCasePluginOf2("tag", {
|
|
6565
|
+
description: "Adds tags to the taggable entity of an id and stores it.",
|
|
6566
|
+
...lifecycle("taggable"),
|
|
6567
|
+
compile: (target, options) => tagging(target, options, "Tag", "tag")
|
|
6568
|
+
});
|
|
6569
|
+
var untag = useCasePluginOf2("untag", {
|
|
6570
|
+
description: "Removes tags from the taggable entity of an id and stores it.",
|
|
6571
|
+
...lifecycle("taggable"),
|
|
6572
|
+
compile: (target, options) => tagging(target, options, "Untag", "untag")
|
|
6573
|
+
});
|
|
6574
|
+
|
|
6575
|
+
// src/std/use-cases/index.ts
|
|
6576
|
+
var StdUseCases2 = useCaseCatalogOf2(create2, get, list, update2, remove2, restore2, publish, unpublish, archive, unarchive, tag, untag, register, login);
|
|
6577
|
+
|
|
6578
|
+
// src/std/command.ts
|
|
6579
|
+
var commandOf2 = commandBuilderOf2(StdUseCases2);
|
|
5455
6580
|
// src/std/record-plugins.ts
|
|
5456
6581
|
var now = () => new Date().toISOString();
|
|
5457
6582
|
|
|
@@ -5539,4 +6664,4 @@ var entityOf2 = entityBuilderOf2({
|
|
|
5539
6664
|
var recordOf2 = recordBuilderOf2(StdRecordPlugins2);
|
|
5540
6665
|
// src/std/stages.ts
|
|
5541
6666
|
var StdStages2 = stageCatalogOf2(select2, openapi2);
|
|
5542
|
-
export { InvalidEnvironment2, environmentOf2, isEnvironment2, guard2, InvalidApplicationDeclaration2, DuplicateApplicationDeclaration2, ApplicationConflict2, UnregisteredApplicationReference2, UnregisteredRepositoryDependency2, UnregisteredUseCaseDependency2, ApplicationNotStarted2, ProviderNotBound2, ApplicationAlreadyStarted2, InvalidApplicationEnvironment2, useApplication2, resetApplication2, InvalidLogDeclaration2, LogLevels2, logProviderOf2, consoleLogProvider2, jsonLogProvider2, memoryLogProvider2, Logger2, ReservedEntityKey2, ImmutableEntityKey2, DeletedEntity2, entityBuilderOf2, isEntityClass2, isEntity2, eventOf2, ENTITY_IDENTITY_KEYS2, RESERVED_ENTITY_KEYS2, EntityUniqueKeys2, AbstractEntity2, InvalidRepositoryQuery2, InvalidRepositoryDeclaration2, InvalidRepositoryEntity2, InvalidRepositoryData2, RepositoryUniqueViolation2, RepositoryEntityNotFound2, providerOf2, providerDescriptionOf2, inMemoryProvider2, AbstractRepository2, repositoryOf2, RepositoryOf2, isRepository2, InvalidUseCaseStep2, InvalidUseCaseDeclaration2, UseCaseAlreadyExecuted2, InvalidUseCaseDependencies2, AbstractUseCase2, useCaseOf2, isUseCaseClass2, isUseCase2, InvalidCommandDeclaration2, UnknownCommandOperation2, InvalidCommandContext2, InvalidCommandEnvironment2, CommandEventFailed2,
|
|
6667
|
+
export { InvalidEnvironment2, environmentOf2, isEnvironment2, guard2, InvalidApplicationDeclaration2, DuplicateApplicationDeclaration2, ApplicationConflict2, UnregisteredApplicationReference2, UnregisteredRepositoryDependency2, UnregisteredUseCaseDependency2, ApplicationNotStarted2, ProviderNotBound2, ApplicationAlreadyStarted2, InvalidApplicationEnvironment2, useApplication2, resetApplication2, InvalidLogDeclaration2, LogLevels2, logProviderOf2, consoleLogProvider2, jsonLogProvider2, memoryLogProvider2, Logger2, ReservedEntityKey2, ImmutableEntityKey2, DeletedEntity2, entityBuilderOf2, isEntityClass2, isEntity2, eventOf2, ENTITY_IDENTITY_KEYS2, RESERVED_ENTITY_KEYS2, EntityUniqueKeys2, AbstractEntity2, InvalidRepositoryQuery2, InvalidRepositoryCursor2, InvalidRepositoryDeclaration2, InvalidRepositoryEntity2, InvalidRepositoryData2, RepositoryUniqueViolation2, RepositoryEntityNotFound2, 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, InvalidOpenAPIModel2, InvalidOutputStage2, DuplicateOperation2, toOpenAPI2, transformOf2, sinkOf2, stageDescriptionOf2, stageKindOf2, stageCatalogOf2, operationOf2, outputOf2, isOutput2, providerFrom2, logFrom2, isLogFactory2, isProviderFactory2, providersOf2, isProviderSlot2, isProviderStorage2, isApplication2, applicationOf2, blueprintPluginOf2, statusOf2, PROBLEM_MEDIA_TYPE2, problemOf2, outputValueOf2, routeOptionsIssue2, operationRoutes2, openapi2, select2, recordPluginCatalogOf2, Email2, Phone2, Url2, Ip2, enumOf2, SchemaProperty2, PasswordHasherRequired2, SyncVerifyUnsupported2, configure2, resetRuntime2, Id2, Slug2, PASSWORD_MIN_LENGTH2, PASSWORD_MAX_LENGTH2, PASSWORD_SPECIAL_CHARACTERS2, isPasswordHash2, passwordIssues2, PlainPassword2, Password2, StringProperty2, Text2, NumberProperty2, Integer2, BooleanProperty2, DateTime2, DateProperty2, CreatedAt2, UpdatedAt2, DeletedAt2, Std2, blueprintOf2, StdUseCases2, commandOf2, Publishable2, Archivable2, Taggable2, Ownable2, Authenticatable2, StdRecordPlugins2, entityOf2, recordOf2, StdStages2 };
|