@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.
Files changed (50) hide show
  1. package/README.md +4 -1
  2. package/dist/core/application/types/application.interface.d.ts +56 -11
  3. package/dist/core/blueprint/types/slice.interface.d.ts +5 -2
  4. package/dist/core/command/command.d.ts +11 -4
  5. package/dist/core/command/types/command.interface.d.ts +19 -0
  6. package/dist/core/entity/abstract-entity.d.ts +1 -1
  7. package/dist/core/entity/types/entity.interface.d.ts +13 -7
  8. package/dist/core/exception/exception.d.ts +10 -1
  9. package/dist/core/output/status.d.ts +4 -3
  10. package/dist/core/property/input.d.ts +20 -0
  11. package/dist/core/property/property.d.ts +4 -4
  12. package/dist/core/property/types/optional-property.type.d.ts +2 -2
  13. package/dist/core/property/types/property-type.interface.d.ts +17 -0
  14. package/dist/core/record/abstract-record.d.ts +4 -4
  15. package/dist/core/record/exceptions.d.ts +2 -0
  16. package/dist/core/record/name.d.ts +16 -0
  17. package/dist/core/record/plugin.d.ts +6 -2
  18. package/dist/core/record/record.d.ts +4 -3
  19. package/dist/core/record/types/plugin.interface.d.ts +11 -0
  20. package/dist/core/record/types/record.interface.d.ts +71 -10
  21. package/dist/core/repository/cursor.d.ts +13 -0
  22. package/dist/core/repository/exceptions.d.ts +10 -0
  23. package/dist/core/repository/query.d.ts +4 -4
  24. package/dist/core/repository/repository.d.ts +2 -2
  25. package/dist/core/repository/state.d.ts +9 -0
  26. package/dist/core/repository/types/query.interface.d.ts +37 -0
  27. package/dist/core/repository/types/repository.interface.d.ts +17 -6
  28. package/dist/core/use-case/exceptions.d.ts +36 -0
  29. package/dist/core/use-case/plugin-catalog.d.ts +11 -0
  30. package/dist/core/use-case/plugin.d.ts +25 -0
  31. package/dist/core/use-case/steps/types/steps.interface.d.ts +24 -7
  32. package/dist/core/use-case/target.d.ts +9 -0
  33. package/dist/core/use-case/types/plugin.interface.d.ts +185 -0
  34. package/dist/experimental.js +4 -3
  35. package/dist/{index-8m80wjzf.js → index-q6hggpzw.js} +1210 -85
  36. package/dist/{index-hn82hm31.js → index-r78dsvtc.js} +136 -66
  37. package/dist/index.d.ts +22 -12
  38. package/dist/index.js +47 -5
  39. package/dist/std/command.d.ts +11 -0
  40. package/dist/std/properties/password.d.ts +21 -2
  41. package/dist/std/record-plugins.d.ts +5 -5
  42. package/dist/std/use-cases/auth.d.ts +5 -0
  43. package/dist/std/use-cases/crud.d.ts +23 -0
  44. package/dist/std/use-cases/index.d.ts +11 -0
  45. package/dist/std/use-cases/lifecycle.d.ts +13 -0
  46. package/dist/std/use-cases/shared.d.ts +60 -0
  47. package/dist/std/use-cases/types.d.ts +212 -0
  48. package/dist/testing/index.js +99 -2
  49. package/dist/testing/provider-contract.d.ts +5 -0
  50. 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
- versionParts,
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-hn82hm31.js";
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.name,
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.name,
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
- function prepareQuery(context, input = {}, map = (_key, value) => value) {
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 (!["where", "orderBy", "limit", "offset", "withDeleted"].includes(key))
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
- for (const { key, direction } of orders) {
897
- const [x, y] = [a[key], b[key]];
898
- const [missingX, missingY] = [
899
- x === undefined || x === null,
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 matched = orderRows([...rows].filter((data) => matchesQuery(data, query)), query);
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.name}" is duplicated.`);
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.name}".`);
1328
+ return invalid(`Missing relation: no repository of "${recordNameOf2(target)}".`);
1210
1329
  }
1211
1330
  if (!exact && matches.length > 1) {
1212
- return invalid(`Relation "${target.name}" matches more than one repository.`);
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.name}" is not held by ${entity.name}.`);
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.name}Repository`;
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.name, String(name_), methods));
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
- states.set(DeclaredRepository, {
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
- return Object.freeze(new DeclaredRepository);
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/use-case/exceptions.ts
1622
- var InvalidUseCaseStep2 = exceptionOf2({
1623
- layer: "use-case" /* UseCase */,
1624
- name: "InvalidUseCaseStep",
1625
- message: ({
1626
- index,
1627
- reason
1628
- }) => `Invalid step ${index}: ${reason}`
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
- await namespace[key]?.(value(step.value));
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: { required: ["change", "key", "value"], optional: ["as"] },
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.name;
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 commandOf2(settings) {
2530
- const { name, description, context, useCases, onEventError } = settings;
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 keyOf(collection, value) {
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: keyOf(collection, value),
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.name}"`
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.name}"`);
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.name}"`
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.name}"`
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.name,
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 : commandOf2({
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 === "RepositoryUniqueViolation")
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.name;
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, commandOf2, 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, Password2, StringProperty2, Text2, NumberProperty2, Integer2, BooleanProperty2, DateTime2, DateProperty2, CreatedAt2, UpdatedAt2, DeletedAt2, Std2, blueprintOf2, Publishable2, Archivable2, Taggable2, Ownable2, Authenticatable2, StdRecordPlugins2, entityOf2, recordOf2, StdStages2 };
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 };