@afora/core 0.0.6 → 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.
@@ -11,6 +11,7 @@ import {
11
11
  recordFieldOf2,
12
12
  recordNameOf2,
13
13
  declaredRecordNameOf,
14
+ listOf2,
14
15
  isBlueprintList2,
15
16
  UNEXPECTED_ERROR2,
16
17
  Exception2,
@@ -29,20 +30,22 @@ import {
29
30
  AbstractRecord2,
30
31
  IdentifiedRecord,
31
32
  isVersion,
32
- versionParts,
33
+ satisfies,
33
34
  pluginsOf2,
34
35
  isRecordPlugin2,
36
+ hasPlugin2,
35
37
  recordPluginOf2,
36
38
  recordBuilderOf2,
37
39
  pluginContextOf,
38
40
  recordOf3,
39
41
  declareRecord,
42
+ optionalOf2,
40
43
  exports_exceptions2,
41
44
  InvalidPropertyValue2,
42
45
  exports_exceptions3,
43
46
  propertyOf2,
44
47
  blueprintBuilderOf2
45
- } from "./index-3nd9bjnc.js";
48
+ } from "./index-r78dsvtc.js";
46
49
 
47
50
  // src/core/environment/environment.ts
48
51
  var exports_environment = {};
@@ -94,6 +97,19 @@ __export(exports_exceptions7, {
94
97
  RepositoryUniqueViolation: () => RepositoryUniqueViolation2
95
98
  });
96
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
+
97
113
  // src/core/use-case/use-case.ts
98
114
  var exports_use_case = {};
99
115
  __export(exports_use_case, {
@@ -106,12 +122,6 @@ __export(exports_use_case, {
106
122
  useCaseOf: () => useCaseOf2
107
123
  });
108
124
 
109
- // src/core/use-case/exceptions.ts
110
- var exports_exceptions8 = {};
111
- __export(exports_exceptions8, {
112
- InvalidUseCaseStep: () => InvalidUseCaseStep2
113
- });
114
-
115
125
  // src/core/command/exceptions.ts
116
126
  var exports_exceptions9 = {};
117
127
  __export(exports_exceptions9, {
@@ -1192,6 +1202,14 @@ var origins = new WeakMap;
1192
1202
  function declarationOf(state) {
1193
1203
  return origins.get(state) ?? state;
1194
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
+ }
1195
1213
  function bindState(state, provider, instances, bound = new Map) {
1196
1214
  const done = bound.get(state);
1197
1215
  if (done)
@@ -1541,6 +1559,29 @@ var remove = action(async (self, target) => {
1541
1559
  }
1542
1560
  return entity;
1543
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
+ });
1544
1585
  var count = action(async (self, query) => self.provider.count(self.context, queryOf(self, query)));
1545
1586
  var NEEDS = {
1546
1587
  findBy: ["findOne"],
@@ -1548,6 +1589,7 @@ var NEEDS = {
1548
1589
  create: ["insert"],
1549
1590
  update: ["update"],
1550
1591
  delete: ["findOne", "remove"],
1592
+ restore: ["findOne", "update"],
1551
1593
  count: ["count"],
1552
1594
  findMany: ["findMany"],
1553
1595
  exists: ["findOne"],
@@ -1604,6 +1646,7 @@ function repositoryOf2(settings) {
1604
1646
  methods.set("create", ["create", create]);
1605
1647
  methods.set("update", ["update", update]);
1606
1648
  methods.set("delete", ["delete", remove]);
1649
+ methods.set("restore", ["restore", restore]);
1607
1650
  methods.set("count", ["count", count]);
1608
1651
  methods.set("findMany", ["findMany", findMany]);
1609
1652
  methods.set("exists", ["exists", exists]);
@@ -1617,6 +1660,9 @@ function repositoryOf2(settings) {
1617
1660
  if (!method)
1618
1661
  invalid3(name, unknownAction(recordNameOf2(entity), String(name_), methods));
1619
1662
  const [kind, implementation] = method;
1663
+ if (kind === "restore" && deletion !== "soft") {
1664
+ invalid3(name, '"restore" needs deletion to be "soft".');
1665
+ }
1620
1666
  const needs = deletion === "soft" && kind === "delete" ? ["findOne", "update"] : NEEDS[kind] ?? [];
1621
1667
  for (const need of needs) {
1622
1668
  if (typeof provider?.[need] !== "function") {
@@ -1630,7 +1676,7 @@ function repositoryOf2(settings) {
1630
1676
  });
1631
1677
  }
1632
1678
  Object.defineProperty(DeclaredRepository, "name", { value: name });
1633
- states.set(DeclaredRepository, {
1679
+ const state = {
1634
1680
  name,
1635
1681
  entity,
1636
1682
  actions: Object.freeze([...new Set(actions)]),
@@ -1639,8 +1685,11 @@ function repositoryOf2(settings) {
1639
1685
  explicitProvider: settings.provider !== undefined,
1640
1686
  environment: environmentDependencies,
1641
1687
  relations
1642
- });
1643
- 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;
1644
1693
  }
1645
1694
  function RepositoryOf2(repository) {
1646
1695
  if (!isRepository2(repository)) {
@@ -1652,6 +1701,54 @@ function isRepository2(value) {
1652
1701
  return value instanceof AbstractRepository2;
1653
1702
  }
1654
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
+
1655
1752
  // src/core/use-case/steps/references.ts
1656
1753
  var RESERVED_NAMES = [
1657
1754
  "input",
@@ -1759,15 +1856,104 @@ function freezeData(value) {
1759
1856
  return value;
1760
1857
  }
1761
1858
 
1762
- // src/core/use-case/exceptions.ts
1763
- var InvalidUseCaseStep2 = exceptionOf2({
1764
- layer: "use-case" /* UseCase */,
1765
- name: "InvalidUseCaseStep",
1766
- message: ({
1767
- index,
1768
- reason
1769
- }) => `Invalid step ${index}: ${reason}`
1770
- });
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
+ }
1771
1957
 
1772
1958
  // src/core/use-case/steps/run.ts
1773
1959
  var FORBIDDEN_METHODS = new Set([
@@ -1801,7 +1987,6 @@ async function executeSteps(dependencies = {}) {
1801
1987
  return followPath(named.get(root), path);
1802
1988
  };
1803
1989
  const value = (raw) => resolveValue(raw, lookup);
1804
- const list = (raw) => raw === undefined ? [] : value(raw);
1805
1990
  for (const [index, step] of steps.entries()) {
1806
1991
  const invalid = (reason) => {
1807
1992
  throw new InvalidUseCaseStep2({
@@ -1809,6 +1994,14 @@ async function executeSteps(dependencies = {}) {
1809
1994
  details: { index, reason, code: "invalid_target" }
1810
1995
  });
1811
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
+ };
1812
2005
  const bind = (result) => {
1813
2006
  if (typeof step.as === "string")
1814
2007
  named.set(step.as, result);
@@ -1853,7 +2046,10 @@ async function executeSteps(dependencies = {}) {
1853
2046
  if (typeof namespace !== "object" || namespace === null || typeof namespace[key] !== "function") {
1854
2047
  invalid(`"${key}" is not a change key of the record.`);
1855
2048
  }
1856
- 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
+ }
1857
2053
  bind(record);
1858
2054
  } else if (Object.hasOwn(step, "invoke")) {
1859
2055
  const record = target("invoke");
@@ -1869,7 +2065,7 @@ async function executeSteps(dependencies = {}) {
1869
2065
  const fn = step.fn;
1870
2066
  bind(await fn(...list(step.args), { context: this.context }));
1871
2067
  } else {
1872
- const action = ["create", "update", "delete"].find((kind) => Object.hasOwn(step, kind));
2068
+ const action = ["create", "update", "delete", "restore"].find((kind) => Object.hasOwn(step, kind));
1873
2069
  const methods = dependencies[step[action]];
1874
2070
  bind(await methods[action].call(methods, value(step.value)));
1875
2071
  }
@@ -1889,11 +2085,15 @@ var SHAPES = {
1889
2085
  optional: ["other", "details"]
1890
2086
  },
1891
2087
  build: { required: ["build", "values", "as"], optional: [] },
1892
- change: { required: ["change", "key", "value"], optional: ["as"] },
2088
+ change: {
2089
+ required: ["change", "key", "value"],
2090
+ optional: ["ifPresent", "as"]
2091
+ },
1893
2092
  invoke: { required: ["invoke", "method"], optional: ["args", "as"] },
1894
2093
  create: { required: ["create", "value"], optional: ["as"] },
1895
2094
  update: { required: ["update", "value"], optional: ["as"] },
1896
2095
  delete: { required: ["delete", "value"], optional: ["as"] },
2096
+ restore: { required: ["restore", "value"], optional: ["as"] },
1897
2097
  call: { required: ["call", "input"], optional: ["as"] },
1898
2098
  run: { required: ["run", "fn"], optional: ["args", "as"] },
1899
2099
  return: { required: ["return"], optional: [] }
@@ -1908,7 +2108,7 @@ var ENSURE_KINDS = new Set([
1908
2108
  "false"
1909
2109
  ]);
1910
2110
  var READ_ACTION = /^(?:findBy.+|findManyBy.+|findMany|exists|findPage|findCursor|count)$/;
1911
- var WRITE_ACTIONS = new Set(["create", "update", "delete"]);
2111
+ var WRITE_ACTIONS = new Set(["create", "update", "delete", "restore"]);
1912
2112
  function validateSteps(declaration, steps) {
1913
2113
  const { name } = declaration;
1914
2114
  const fail = (index, code, reason) => {
@@ -1964,8 +2164,12 @@ function validateSteps(declaration, steps) {
1964
2164
  const checkArgs = (index, args) => {
1965
2165
  if (args === undefined)
1966
2166
  return;
2167
+ if (typeof args === "string" && args.startsWith("$")) {
2168
+ checkReference(index, args);
2169
+ return;
2170
+ }
1967
2171
  if (!Array.isArray(args)) {
1968
- 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.");
1969
2173
  }
1970
2174
  checkValue(index, args, "args");
1971
2175
  };
@@ -2072,6 +2276,9 @@ function validateSteps(declaration, steps) {
2072
2276
  if (typeof record.key !== "string" || record.key === "") {
2073
2277
  fail(index, "invalid_step", "change.key must be a string.");
2074
2278
  }
2279
+ if (record.ifPresent !== undefined && typeof record.ifPresent !== "boolean") {
2280
+ fail(index, "invalid_step", "change.ifPresent must be a boolean.");
2281
+ }
2075
2282
  const reference = parseReference(record.change);
2076
2283
  const known = reference && reference.path.length === 0 ? bound.get(reference.root)?.record : undefined;
2077
2284
  if (known) {
@@ -2094,7 +2301,8 @@ function validateSteps(declaration, steps) {
2094
2301
  }
2095
2302
  case "create":
2096
2303
  case "update":
2097
- case "delete": {
2304
+ case "delete":
2305
+ case "restore": {
2098
2306
  repositoryOf(index, record[kind], kind);
2099
2307
  checkValue(index, record.value, "value");
2100
2308
  break;
@@ -2513,6 +2721,195 @@ function isUseCase2(value) {
2513
2721
  return value instanceof AbstractUseCase2;
2514
2722
  }
2515
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
+
2516
2913
  // src/core/command/exceptions.ts
2517
2914
  var InvalidCommandDeclaration2 = exceptionOf2({
2518
2915
  layer: "command" /* Command */,
@@ -2667,9 +3064,12 @@ function callEnvironment(source, environments, given) {
2667
3064
  }
2668
3065
  return { byEnvironment, merged: Object.freeze(merged) };
2669
3066
  }
2670
- function commandOf2(settings) {
2671
- const { name, description, context, useCases, onEventError } = settings;
3067
+ function declareCommand(given, generics) {
3068
+ const { name, description, context, onEventError } = given;
2672
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;
2673
3073
  const invalid = (reason) => {
2674
3074
  throw new InvalidCommandDeclaration2({ source, details: { reason } });
2675
3075
  };
@@ -2687,7 +3087,7 @@ function commandOf2(settings) {
2687
3087
  invalid("Expected context to be a non-optional blueprint.");
2688
3088
  }
2689
3089
  if (typeof useCases !== "object" || useCases === null || Array.isArray(useCases) || Reflect.ownKeys(useCases).length === 0) {
2690
- invalid("Expected useCases to be a non-empty object of use case classes.");
3090
+ invalid("Expected useCases to be a non-empty object of use case classes, or a function returning one.");
2691
3091
  }
2692
3092
  for (const key of Reflect.ownKeys(useCases)) {
2693
3093
  if (typeof key !== "string" || !IDENTIFIER2.test(key) || !isUseCaseClass2(useCases[key])) {
@@ -2825,6 +3225,19 @@ function commandOf2(settings) {
2825
3225
  verifiers.set(command, providerOfCall);
2826
3226
  return command;
2827
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();
2828
3241
  async function applyPolicy(command, policy, onEventError, call) {
2829
3242
  const { operation, raised } = call;
2830
3243
  const source = `${command.commandName}.${operation}`;
@@ -3663,7 +4076,7 @@ function isOfKind(collection, value) {
3663
4076
  return isDeclaredException(value);
3664
4077
  }
3665
4078
  }
3666
- function keyOf(collection, value) {
4079
+ function keyOf2(collection, value) {
3667
4080
  switch (collection) {
3668
4081
  case "properties":
3669
4082
  return fieldOf(value);
@@ -3746,11 +4159,25 @@ function resolve(settings) {
3746
4159
  collection,
3747
4160
  name,
3748
4161
  value,
3749
- key: keyOf(collection, value),
4162
+ key: keyOf2(collection, value),
3750
4163
  from: source
3751
4164
  });
3752
4165
  }
3753
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
+ }
3754
4181
  return registry;
3755
4182
  }
3756
4183
  function recordsIn(properties) {
@@ -4087,6 +4514,36 @@ function describeRegistry(name, extended, registry, provider, named, log) {
4087
4514
  })
4088
4515
  };
4089
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
+ };
4090
4547
  const refsOf = (blueprints) => {
4091
4548
  const refs = {};
4092
4549
  for (const [key, blueprint] of Object.entries(blueprints)) {
@@ -4188,7 +4645,8 @@ function describeRegistry(name, extended, registry, provider, named, log) {
4188
4645
  ref: repositories.get(inspectRepository(dependency).state)
4189
4646
  }
4190
4647
  ])),
4191
- implementation: describeImplementation(UseCase)
4648
+ implementation: describeImplementation(UseCase),
4649
+ ...describeGeneric(UseCase)
4192
4650
  };
4193
4651
  }),
4194
4652
  commands: each("commands", (entry) => {
@@ -4387,7 +4845,7 @@ function applicationOf2(settings) {
4387
4845
  const boundCommandOf = new Map;
4388
4846
  for (const entry of registry.commands.values()) {
4389
4847
  const declared = entry.value;
4390
- const rebuilt = !rebinds ? declared : commandOf2({
4848
+ const rebuilt = !rebinds ? declared : commandOf({
4391
4849
  name: declared.commandName,
4392
4850
  description: declared.description,
4393
4851
  tags: declared.tags,
@@ -4629,7 +5087,12 @@ var INVALID_INPUT = new Set([
4629
5087
  var CONFIGURATION = new Set(["InvalidCommandEnvironment"]);
4630
5088
  var NOT_FOUND = new Set([
4631
5089
  "RepositoryEntityNotFound",
4632
- "UnknownCommandOperation"
5090
+ "UnknownCommandOperation",
5091
+ "UseCaseEntityNotFound"
5092
+ ]);
5093
+ var CONFLICT = new Set([
5094
+ "RepositoryUniqueViolation",
5095
+ "UseCaseEntityConflict"
4633
5096
  ]);
4634
5097
  function raisedOf(error) {
4635
5098
  let raised = error;
@@ -4654,8 +5117,10 @@ function statusOf2(error, operation, statuses) {
4654
5117
  }
4655
5118
  if (NOT_FOUND.has(name))
4656
5119
  return 404;
4657
- if (name === "RepositoryUniqueViolation")
5120
+ if (CONFLICT.has(name))
4658
5121
  return 409;
5122
+ if (name === "UseCaseInvalidCredentials")
5123
+ return 401;
4659
5124
  if (operation?.errors.some((Declared) => error.is(Declared) || Declared.name === name)) {
4660
5125
  return 422;
4661
5126
  }
@@ -5000,17 +5465,6 @@ var select2 = transformOf2({
5000
5465
  }
5001
5466
  });
5002
5467
  // src/core/record/plugin-catalog.ts
5003
- function satisfies(available, requested) {
5004
- const [major, minor, patch] = versionParts(available);
5005
- const [wantedMajor, wantedMinor, wantedPatch] = versionParts(requested);
5006
- if (major !== wantedMajor)
5007
- return false;
5008
- if (major === 0 && minor !== wantedMinor)
5009
- return false;
5010
- if (minor !== wantedMinor)
5011
- return minor > wantedMinor;
5012
- return patch >= wantedPatch;
5013
- }
5014
5468
  var LISTS2 = ["keys", "sensitive", "unique", "change", "members"];
5015
5469
  function isDescriptor(value) {
5016
5470
  if (typeof value !== "object" || value === null)
@@ -5467,6 +5921,19 @@ function passwordIssues2(value) {
5467
5921
  }
5468
5922
  return issues;
5469
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
+ }
5470
5937
 
5471
5938
  class Password2 extends propertyOf2({
5472
5939
  name: "password",
@@ -5474,6 +5941,10 @@ class Password2 extends propertyOf2({
5474
5941
  default: DEMO_HASH,
5475
5942
  sensitive: true
5476
5943
  }) {
5944
+ static [PropertyInput2] = {
5945
+ accepts: PlainPassword2,
5946
+ convert: async (plain) => (await Password2.hash(plain)).value
5947
+ };
5477
5948
  static async hash(plain) {
5478
5949
  const issues = passwordIssues2(plain);
5479
5950
  if (issues.length > 0) {
@@ -5601,6 +6072,511 @@ var Std2 = blueprintPluginOf2("std", {
5601
6072
 
5602
6073
  // src/std/blueprint.ts
5603
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);
5604
6580
  // src/std/record-plugins.ts
5605
6581
  var now = () => new Date().toISOString();
5606
6582
 
@@ -5688,4 +6664,4 @@ var entityOf2 = entityBuilderOf2({
5688
6664
  var recordOf2 = recordBuilderOf2(StdRecordPlugins2);
5689
6665
  // src/std/stages.ts
5690
6666
  var StdStages2 = stageCatalogOf2(select2, openapi2);
5691
- 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, 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 };