@afora/core 0.0.9 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/README.md +62 -189
  2. package/dist/adapters.js +31 -55
  3. package/dist/core/application/application.d.ts +30 -0
  4. package/dist/core/application/application.js +1291 -0
  5. package/dist/core/application/exceptions.js +85 -0
  6. package/dist/core/application/migrate.js +67 -0
  7. package/dist/core/application/providers.js +108 -0
  8. package/dist/core/application/registry.js +23 -0
  9. package/dist/core/application/schema.js +398 -0
  10. package/dist/core/application/start.js +162 -0
  11. package/dist/core/application/types/application.interface.d.ts +23 -3
  12. package/dist/core/auth/jwt.js +200 -0
  13. package/dist/core/auth/types/jwt.interface.d.ts +10 -2
  14. package/dist/core/blueprint/blueprint-plugin.js +7 -0
  15. package/dist/core/blueprint/blueprint.js +146 -0
  16. package/dist/core/blueprint/callable.js +13 -0
  17. package/dist/core/blueprint/exceptions.js +20 -0
  18. package/dist/core/blueprint/is-blueprint.js +4 -0
  19. package/dist/core/blueprint/list.js +19 -0
  20. package/dist/core/blueprint/optional.js +69 -0
  21. package/dist/core/blueprint/record-field.js +14 -0
  22. package/dist/core/blueprint/slice.js +127 -0
  23. package/dist/core/blueprint/types/blueprint.interface.d.ts +16 -0
  24. package/dist/core/blueprint/variant.js +72 -0
  25. package/dist/core/command/command.js +428 -0
  26. package/dist/core/command/exceptions.js +30 -0
  27. package/dist/core/command/types/command.interface.d.ts +10 -1
  28. package/dist/core/entity/abstract-entity.d.ts +19 -0
  29. package/dist/core/entity/abstract-entity.js +181 -0
  30. package/dist/core/entity/entity.js +209 -0
  31. package/dist/core/entity/event.d.ts +39 -0
  32. package/dist/core/entity/event.js +80 -0
  33. package/dist/core/entity/exceptions.js +27 -0
  34. package/dist/core/entity/trust.js +7 -0
  35. package/dist/core/entity/version.js +13 -0
  36. package/dist/core/environment/environment.d.ts +26 -3
  37. package/dist/core/environment/environment.js +71 -0
  38. package/dist/core/event-policy/event-policy.js +76 -0
  39. package/dist/core/event-policy/exceptions.js +12 -0
  40. package/dist/core/event-policy/publish.js +98 -0
  41. package/dist/core/event-policy/publishers.js +18 -0
  42. package/dist/core/event-policy/types/event-policy.interface.d.ts +8 -2
  43. package/dist/core/exception/enums/exception-layers.js +15 -0
  44. package/dist/core/exception/exception.d.ts +15 -0
  45. package/dist/core/exception/exception.js +128 -0
  46. package/dist/core/exception/exported.js +49 -0
  47. package/dist/core/exception/guard.d.ts +20 -1
  48. package/dist/core/exception/guard.js +16 -0
  49. package/dist/core/exception/types/exception.interface.d.ts +13 -0
  50. package/dist/core/json.js +19 -0
  51. package/dist/core/logger/exceptions.js +7 -0
  52. package/dist/core/logger/logger.d.ts +14 -1
  53. package/dist/core/logger/logger.js +178 -0
  54. package/dist/core/openapi/exceptions.js +7 -0
  55. package/dist/core/openapi/openapi.js +132 -0
  56. package/dist/core/openapi/types/openapi.interface.d.ts +7 -1
  57. package/dist/core/output/access.js +66 -0
  58. package/dist/core/output/coerce.js +38 -0
  59. package/dist/core/output/exceptions.js +22 -0
  60. package/dist/{index-y280ew0k.js → core/output/http.js} +35 -80
  61. package/dist/core/output/openapi-sink.d.ts +10 -2
  62. package/dist/{index-hg1z2s2v.js → core/output/openapi-sink.js} +60 -544
  63. package/dist/core/output/output.js +192 -0
  64. package/dist/core/output/problem.js +30 -0
  65. package/dist/core/output/select.js +28 -0
  66. package/dist/core/output/stage.js +111 -0
  67. package/dist/core/output/status.js +52 -0
  68. package/dist/core/output/types/http.interface.d.ts +5 -1
  69. package/dist/core/output/types/output.interface.d.ts +24 -0
  70. package/dist/core/parse/issue.js +31 -0
  71. package/dist/core/parse/parse.d.ts +8 -1
  72. package/dist/core/parse/parse.js +323 -0
  73. package/dist/core/parse/strip.js +48 -0
  74. package/dist/core/property/abstract-property.js +37 -0
  75. package/dist/core/property/define-property.js +209 -0
  76. package/dist/core/property/enums/property-states.js +8 -0
  77. package/dist/core/property/exceptions.js +12 -0
  78. package/dist/core/property/input.js +10 -0
  79. package/dist/core/property/is-property.js +3 -0
  80. package/dist/core/property/lineage.js +8 -0
  81. package/dist/core/property/property.d.ts +27 -0
  82. package/dist/core/property/property.js +46 -0
  83. package/dist/core/property/refine.js +103 -0
  84. package/dist/core/record/abstract-record.d.ts +2 -1
  85. package/dist/core/record/abstract-record.js +307 -0
  86. package/dist/core/record/exceptions.js +49 -0
  87. package/dist/core/record/marks.js +7 -0
  88. package/dist/core/record/mask.js +60 -0
  89. package/dist/core/record/name.js +23 -0
  90. package/dist/core/record/plugin-catalog.js +115 -0
  91. package/dist/core/record/plugin.js +198 -0
  92. package/dist/core/record/record.js +117 -0
  93. package/dist/core/record/types/record.interface.d.ts +5 -0
  94. package/dist/core/repository/cursor.js +83 -0
  95. package/dist/core/repository/exceptions.js +72 -0
  96. package/dist/core/repository/in-memory.js +196 -0
  97. package/dist/core/repository/migrate.js +254 -0
  98. package/dist/core/repository/plan.js +27 -0
  99. package/dist/core/repository/provider.js +29 -0
  100. package/dist/core/repository/query.js +153 -0
  101. package/dist/core/repository/references.js +196 -0
  102. package/dist/core/repository/repository.d.ts +22 -2
  103. package/dist/core/repository/repository.js +720 -0
  104. package/dist/core/repository/state.js +138 -0
  105. package/dist/core/repository/types/query.interface.d.ts +13 -0
  106. package/dist/core/repository/types/repository.interface.d.ts +20 -8
  107. package/dist/core/repository/types/schema.interface.d.ts +8 -2
  108. package/dist/core/repository/upcast.js +179 -0
  109. package/dist/core/schema/schema.d.ts +9 -0
  110. package/dist/core/schema/schema.js +542 -0
  111. package/dist/core/standard/standard-schema.js +1 -0
  112. package/dist/core/standard/standard.js +23 -0
  113. package/dist/core/symbols/demo.d.ts +4 -0
  114. package/dist/core/symbols/demo.js +1 -0
  115. package/dist/core/symbols/kind.js +1 -0
  116. package/dist/core/symbols/meta.js +1 -0
  117. package/dist/core/symbols/state.d.ts +5 -0
  118. package/dist/core/symbols/state.js +1 -0
  119. package/dist/core/tracing/exceptions.d.ts +4 -0
  120. package/dist/core/tracing/exceptions.js +7 -0
  121. package/dist/core/tracing/tracing.d.ts +72 -0
  122. package/dist/core/tracing/tracing.js +101 -0
  123. package/dist/core/use-case/access.js +56 -0
  124. package/dist/core/use-case/decorators.js +65 -0
  125. package/dist/core/use-case/exceptions.d.ts +5 -1
  126. package/dist/core/use-case/exceptions.js +48 -0
  127. package/dist/core/use-case/plugin-catalog.js +68 -0
  128. package/dist/core/use-case/plugin.js +133 -0
  129. package/dist/core/use-case/route.js +200 -0
  130. package/dist/core/use-case/scope.d.ts +5 -0
  131. package/dist/core/use-case/scope.js +4 -0
  132. package/dist/core/use-case/steps/describe.d.ts +5 -1
  133. package/dist/core/use-case/steps/describe.js +24 -0
  134. package/dist/core/use-case/steps/references.js +105 -0
  135. package/dist/core/use-case/steps/run.js +139 -0
  136. package/dist/core/use-case/steps/types/check.interface.d.ts +27 -8
  137. package/dist/core/use-case/steps/types/steps.interface.d.ts +8 -1
  138. package/dist/core/use-case/steps/validate.js +368 -0
  139. package/dist/core/use-case/target.js +100 -0
  140. package/dist/core/use-case/types/route.interface.d.ts +25 -5
  141. package/dist/core/use-case/use-case.d.ts +77 -2
  142. package/dist/core/use-case/use-case.js +493 -0
  143. package/dist/experimental.d.ts +5 -0
  144. package/dist/experimental.js +18 -161
  145. package/dist/index.d.ts +7 -5
  146. package/dist/index.js +213 -367
  147. package/dist/std/blueprint-plugin.js +42 -0
  148. package/dist/std/blueprint.d.ts +21 -0
  149. package/dist/std/blueprint.js +3 -0
  150. package/dist/std/command.js +3 -0
  151. package/dist/std/entity.d.ts +20 -1
  152. package/dist/std/entity.js +30 -0
  153. package/dist/std/event-policies.js +3 -0
  154. package/dist/std/properties/contact.d.ts +3 -0
  155. package/dist/std/properties/contact.js +29 -0
  156. package/dist/std/properties/enum.js +26 -0
  157. package/dist/std/properties/exceptions.js +14 -0
  158. package/dist/std/properties/headless.js +45 -0
  159. package/dist/std/properties/identity.js +28 -0
  160. package/dist/std/properties/json.js +161 -0
  161. package/dist/std/properties/password.js +79 -0
  162. package/dist/std/properties/primitives.d.ts +4 -0
  163. package/dist/std/properties/primitives.js +28 -0
  164. package/dist/std/properties/time.js +55 -0
  165. package/dist/std/record-plugins.js +73 -0
  166. package/dist/std/record.d.ts +20 -1
  167. package/dist/std/record.js +3 -0
  168. package/dist/std/resource.js +39 -0
  169. package/dist/std/runtime.js +193 -0
  170. package/dist/std/stages.js +4 -0
  171. package/dist/std/use-cases/auth.js +193 -0
  172. package/dist/std/use-cases/crud.js +261 -0
  173. package/dist/std/use-cases/index.js +12 -0
  174. package/dist/std/use-cases/lifecycle.js +97 -0
  175. package/dist/std/use-cases/shared.js +232 -0
  176. package/dist/std/use-cases/types.d.ts +5 -1
  177. package/dist/std/use-cases/types.js +0 -0
  178. package/dist/testing/http-provider-contract.js +344 -0
  179. package/dist/testing/index.js +3 -1435
  180. package/dist/testing/memory-publisher.js +12 -0
  181. package/dist/testing/provider-contract.d.ts +12 -3
  182. package/dist/testing/provider-contract.js +1071 -0
  183. package/package.json +17 -5
  184. package/dist/index-3aae5jfq.js +0 -2997
  185. package/dist/index-ny3as79r.js +0 -4567
  186. package/dist/index-xegv6a1p.js +0 -4291
@@ -1,5 +1,6 @@
1
1
  import type { ILogger } from "../logger/logger.js";
2
2
  import type { IRepositoryProvider } from "../repository/types/repository.interface.js";
3
+ import type { ITracer, ITraceSpan } from "../tracing/tracing.js";
3
4
  /**
4
5
  * What a call carries to its repositories, internal: the providers that
5
6
  * replace the ones the repositories declared (e.g. one inside a transaction).
@@ -14,6 +15,10 @@ export interface ExecutionScope {
14
15
  readonly environments?: ReadonlyMap<object, Readonly<Record<string, unknown>>>;
15
16
  /** The logger of the application running the call, given by its command. */
16
17
  readonly logger?: ILogger;
18
+ /** The tracer of the application running the call, given by its command. */
19
+ readonly tracer?: ITracer;
20
+ /** The span of the caller (a command, a use case): the parent of the next. */
21
+ readonly span?: ITraceSpan;
17
22
  }
18
23
  /**
19
24
  * Key of the constructor options (and of the repository view) that carries
@@ -0,0 +1,4 @@
1
+ export const ScopeKey = Symbol("afora.use-case.scope");
2
+ export function scopedProvider(scope, provider) {
3
+ return scope?.providers.get(provider) ?? provider;
4
+ }
@@ -1,3 +1,4 @@
1
+ import type { FieldsDescription } from "../../application/types/application.interface.js";
1
2
  import type { StepErrorDescription, UseCaseStepDescription } from "./types/steps.interface.js";
2
3
  /** How a class of a step is named in the descriptor. */
3
4
  export interface StepNames {
@@ -5,9 +6,12 @@ export interface StepNames {
5
6
  readonly record: (Class: unknown) => string;
6
7
  /** The registered name of an exception, or `{ core }` for one the package exports. */
7
8
  readonly error: (Class: unknown) => StepErrorDescription;
9
+ /** The fields of the `params` blueprint of a `run`. */
10
+ readonly params: (blueprint: unknown) => FieldsDescription;
8
11
  }
9
12
  /**
10
13
  * The steps as `describe()` gives them: the same data, with the classes of
11
- * `build` and `ensure.error` by name and the function of `run` as `opaque`.
14
+ * `build` and `ensure.error` by name, the function of `run` as `opaque` and
15
+ * its `params` by their fields.
12
16
  */
13
17
  export declare function describeSteps(steps: readonly Readonly<Record<string, unknown>>[], names: StepNames): UseCaseStepDescription[];
@@ -0,0 +1,24 @@
1
+ import { isPlain } from "./references.js";
2
+ function cloneData(value) {
3
+ if (Array.isArray(value))
4
+ return value.map(cloneData);
5
+ if (isPlain(value)) {
6
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, cloneData(item)]));
7
+ }
8
+ return value;
9
+ }
10
+ export function describeSteps(steps, names) {
11
+ return steps.map((step) => {
12
+ const copy = cloneData({ ...step });
13
+ if (Object.hasOwn(step, "build"))
14
+ copy.build = names.record(step.build);
15
+ if (Object.hasOwn(step, "ensure"))
16
+ copy.error = names.error(step.error);
17
+ if (Object.hasOwn(step, "run")) {
18
+ copy.fn = { kind: "opaque" };
19
+ if (step.params !== undefined)
20
+ copy.params = names.params(step.params);
21
+ }
22
+ return copy;
23
+ });
24
+ }
@@ -0,0 +1,105 @@
1
+ export const RESERVED_NAMES = [
2
+ "input",
3
+ "context",
4
+ "env",
5
+ "literal"
6
+ ];
7
+ const IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
8
+ const SEGMENT = /^(?:[A-Za-z_][A-Za-z0-9_]*|0|[1-9][0-9]*)$/;
9
+ const BLOCKED = new Set(["__proto__", "constructor", "prototype"]);
10
+ export function isStepName(value) {
11
+ return typeof value === "string" && IDENTIFIER.test(value);
12
+ }
13
+ const parsed = new Map;
14
+ export function parseReference(text) {
15
+ const cached = parsed.get(text);
16
+ if (cached !== undefined)
17
+ return cached ?? undefined;
18
+ let result = null;
19
+ if (text.startsWith("$")) {
20
+ const [root, ...path] = text.slice(1).split(".");
21
+ if (root !== undefined && IDENTIFIER.test(root) && path.every((segment) => SEGMENT.test(segment))) {
22
+ result = Object.freeze({ root, path: Object.freeze(path) });
23
+ }
24
+ }
25
+ if (parsed.size < 4096)
26
+ parsed.set(text, result);
27
+ return result ?? undefined;
28
+ }
29
+ export function isPlain(value) {
30
+ if (typeof value !== "object" || value === null)
31
+ return false;
32
+ const prototype = Object.getPrototypeOf(value);
33
+ return prototype === Object.prototype || prototype === null;
34
+ }
35
+ function isLiteralEscape(value) {
36
+ return isPlain(value) && Object.keys(value).length === 1 && Object.hasOwn(value, "literal");
37
+ }
38
+ export function walkValue(value, onReference, onIssue) {
39
+ if (typeof value === "string") {
40
+ if (!value.startsWith("$"))
41
+ return;
42
+ const reference = parseReference(value);
43
+ if (!reference)
44
+ onIssue("invalid_reference", value);
45
+ else
46
+ onReference(reference, value);
47
+ return;
48
+ }
49
+ if (value === null || typeof value === "number" || typeof value === "boolean") {
50
+ return;
51
+ }
52
+ if (isLiteralEscape(value)) {
53
+ return;
54
+ }
55
+ if (Array.isArray(value)) {
56
+ for (const item of value)
57
+ walkValue(item, onReference, onIssue);
58
+ return;
59
+ }
60
+ if (isPlain(value)) {
61
+ for (const item of Object.values(value)) {
62
+ walkValue(item, onReference, onIssue);
63
+ }
64
+ return;
65
+ }
66
+ onIssue("invalid_value", String(value));
67
+ }
68
+ export function followPath(from, path) {
69
+ let current = from;
70
+ for (const segment of path) {
71
+ if (typeof current !== "object" && typeof current !== "function" || current === null || BLOCKED.has(segment)) {
72
+ return;
73
+ }
74
+ current = current[segment];
75
+ }
76
+ return current;
77
+ }
78
+ export function resolveValue(value, lookup) {
79
+ if (typeof value === "string") {
80
+ if (!value.startsWith("$"))
81
+ return value;
82
+ const reference = parseReference(value);
83
+ return reference ? lookup(reference) : undefined;
84
+ }
85
+ if (isLiteralEscape(value))
86
+ return value.literal;
87
+ if (Array.isArray(value))
88
+ return value.map((item) => resolveValue(item, lookup));
89
+ if (isPlain(value)) {
90
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
91
+ key,
92
+ resolveValue(item, lookup)
93
+ ]));
94
+ }
95
+ return value;
96
+ }
97
+ export function freezeData(value) {
98
+ if (Array.isArray(value)) {
99
+ return Object.freeze(value.map(freezeData));
100
+ }
101
+ if (isPlain(value)) {
102
+ return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, item]) => [key, freezeData(item)])));
103
+ }
104
+ return value;
105
+ }
@@ -0,0 +1,139 @@
1
+ import { isRecordClass } from "../../blueprint/record-field.js";
2
+ import { sameValue } from "../../repository/query.js";
3
+ import { InvalidUseCaseStep } from "../exceptions.js";
4
+ import { followPath, resolveValue } from "./references.js";
5
+ const FORBIDDEN_METHODS = new Set([
6
+ "set",
7
+ "emit",
8
+ "constructor",
9
+ "__proto__",
10
+ "hasOwnProperty",
11
+ "isPrototypeOf",
12
+ "propertyIsEnumerable",
13
+ "toLocaleString",
14
+ "valueOf",
15
+ "__defineGetter__",
16
+ "__defineSetter__",
17
+ "__lookupGetter__",
18
+ "__lookupSetter__"
19
+ ]);
20
+ function isRecordInstance(value) {
21
+ return typeof value === "object" && value !== null && isRecordClass(value.constructor);
22
+ }
23
+ function parsedArgs(params, args, invalid) {
24
+ const keys = Object.keys(params.properties);
25
+ if (args.length !== keys.length) {
26
+ invalid(`run.params declares ${keys.length} parameter(s), args gave ${args.length}.`, "invalid_args");
27
+ }
28
+ const result = params.safeParse(Object.fromEntries(keys.map((key, at) => [key, args[at]])));
29
+ if (result.issues) {
30
+ const [first] = result.issues;
31
+ const more = result.issues.length > 1 ? ` (and ${result.issues.length - 1} more)` : "";
32
+ return invalid(`the argument "${first?.path.join(".")}" ${first?.message}${more}.`, "invalid_args", result.issues);
33
+ }
34
+ const value = result.value;
35
+ return keys.map((key) => value[key]);
36
+ }
37
+ export async function executeSteps(dependencies = {}) {
38
+ const { useCaseName, steps = [] } = this.constructor;
39
+ const named = new Map;
40
+ const lookup = ({ root, path }) => {
41
+ if (root === "input")
42
+ return followPath(this.input, path);
43
+ if (root === "context")
44
+ return followPath(this.context, path);
45
+ if (root === "env")
46
+ return followPath(dependencies[path[0]], path.slice(1));
47
+ return followPath(named.get(root), path);
48
+ };
49
+ const value = (raw) => resolveValue(raw, lookup);
50
+ for (const [index, step] of steps.entries()) {
51
+ const invalid = (reason, code = "invalid_target", issues) => {
52
+ throw new InvalidUseCaseStep({
53
+ source: useCaseName,
54
+ details: { index, reason, code, ...issues ? { issues } : {} }
55
+ });
56
+ };
57
+ const list = (raw) => {
58
+ if (raw === undefined)
59
+ return [];
60
+ const resolved = value(raw);
61
+ if (!Array.isArray(resolved))
62
+ invalid("args must resolve to a list.");
63
+ return resolved;
64
+ };
65
+ const bind = (result) => {
66
+ if (typeof step.as === "string")
67
+ named.set(step.as, result);
68
+ };
69
+ const target = (field) => {
70
+ const resolved = value(step[field]);
71
+ if (!isRecordInstance(resolved)) {
72
+ invalid(`${field} must reference a record or entity.`);
73
+ }
74
+ return resolved;
75
+ };
76
+ if (Object.hasOwn(step, "return"))
77
+ return value(step.return);
78
+ if (Object.hasOwn(step, "find")) {
79
+ const [repository, action] = step.find.split(".");
80
+ const methods = dependencies[repository];
81
+ bind(await methods[action].call(methods, ...list(step.args)));
82
+ } else if (Object.hasOwn(step, "ensure")) {
83
+ const subject = value(step.value);
84
+ const passed = {
85
+ exists: subject !== undefined && subject !== null,
86
+ absent: subject === undefined || subject === null,
87
+ equals: sameValue(subject, value(step.other)),
88
+ notEquals: !sameValue(subject, value(step.other)),
89
+ true: subject === true,
90
+ false: subject === false
91
+ }[step.ensure];
92
+ if (!passed) {
93
+ const Thrown = step.error;
94
+ throw new Thrown({
95
+ source: useCaseName,
96
+ ...step.details === undefined ? {} : { details: value(step.details) }
97
+ });
98
+ }
99
+ } else if (Object.hasOwn(step, "build")) {
100
+ const Built = step.build;
101
+ bind(new Built(value(step.values)));
102
+ } else if (Object.hasOwn(step, "change")) {
103
+ const record = target("change");
104
+ const namespace = record.change;
105
+ const key = step.key;
106
+ if (typeof namespace !== "object" || namespace === null || typeof namespace[key] !== "function") {
107
+ invalid(`"${key}" is not a change key of the record.`);
108
+ }
109
+ const changed = value(step.value);
110
+ if (step.ifPresent !== true || changed !== undefined) {
111
+ await namespace[key]?.(changed);
112
+ }
113
+ bind(record);
114
+ } else if (Object.hasOwn(step, "invoke")) {
115
+ const record = target("invoke");
116
+ const method = step.method;
117
+ if (FORBIDDEN_METHODS.has(method) || typeof record[method] !== "function") {
118
+ invalid(`"${method}" is not a public method of the record.`);
119
+ }
120
+ bind(await record[method].apply(record, list(step.args)));
121
+ } else if (Object.hasOwn(step, "call")) {
122
+ const child = dependencies[step.call];
123
+ bind(await child(value(step.input)));
124
+ } else if (Object.hasOwn(step, "run")) {
125
+ const fn = step.fn;
126
+ const args = list(step.args);
127
+ bind(await fn(...step.params === undefined ? args : parsedArgs(step.params, args, invalid), { context: this.context, dependencies }));
128
+ } else {
129
+ const action = ["create", "update", "delete", "restore"].find((kind) => Object.hasOwn(step, kind));
130
+ const methods = dependencies[step[action]];
131
+ bind(await methods[action].call(methods, value(step.value)));
132
+ }
133
+ }
134
+ return;
135
+ }
136
+ export function interpretedStepsOf(UseCase) {
137
+ const Declared = UseCase;
138
+ return Declared.prototype.execute === executeSteps ? Declared.steps : undefined;
139
+ }
@@ -13,7 +13,7 @@
13
13
  * the steps are not distributive (`[Step] extends [...]`): TypeScript then
14
14
  * never evaluates them with the constraint of the steps while it infers them.
15
15
  */
16
- import type { BlueprintValue, IBlueprint, IBlueprintCollection } from "../../../blueprint/types/blueprint.interface.js";
16
+ import type { BlueprintInput, BlueprintValue, IBlueprint, IBlueprintCollection } from "../../../blueprint/types/blueprint.interface.js";
17
17
  import type { AnyEnvironment, EnvironmentValue } from "../../../environment/environment.js";
18
18
  import type { Meta } from "../../../symbols/meta.js";
19
19
  import type { AnyUseCaseClass, UseCaseInput, UseCaseResponse } from "../../use-case.js";
@@ -64,10 +64,21 @@ type PathsFrom<Value, Path extends string, Expected, Depth extends unknown[]> =
64
64
  export type StepReferencesOf<Scope, Expected = unknown> = Scope extends unknown ? {
65
65
  [Root in keyof Scope & string]-?: Root extends "env" ? Below<Scope[Root], `$${Root}`, Expected, [0]> : PathsFrom<Scope[Root], `$${Root}`, Expected, []>;
66
66
  }[keyof Scope & string] : never;
67
- /** `Reference` when it names a value of `Scope` that fits `Expected`; else the references that would. */
67
+ /**
68
+ * The references of `Scope` that fit `Expected`, or `Expected` itself when
69
+ * none does, so that the error names the type that is taken rather than `never`.
70
+ */
71
+ type Suggested<Expected, Scope> = [StepReferencesOf<Scope, Expected>] extends [
72
+ infer References
73
+ ] ? [References] extends [never] ? Expected : References : never;
74
+ /**
75
+ * `Reference` when it names a value of `Scope` that fits `Expected`; else the
76
+ * references that would, and, when it only fails because it may be
77
+ * `undefined`, a hint to ensure it exists first.
78
+ */
68
79
  type CheckReference<Reference, Expected, Scope> = [
69
80
  StepReferenceType<Scope, Reference>
70
- ] extends [infer Type] ? [Type] extends [never] ? StepReferencesOf<Scope, Expected> : [Type] extends [Expected] ? Reference : StepReferencesOf<Scope, Expected> : never;
81
+ ] extends [infer Type] ? [Type] extends [never] ? Suggested<Expected, Scope> : [Type] extends [Expected] ? Reference : [NonNullable<Type>] extends [Expected] ? Suggested<Expected, Scope> | `${Reference & string} may be undefined: ensure it exists first` : Suggested<Expected, Scope> : never;
71
82
  type Primitive = string | number | boolean | null;
72
83
  /** The type of the key `Key` in what `Expected` takes, over each object of it. */
73
84
  type ExpectedAt<Expected, Key> = Expected extends object ? Key extends keyof Expected ? Expected[Key] : never : never;
@@ -158,7 +169,7 @@ type ChangeStep<Step, Record, Scope> = [Record] extends [never] ? ValueStep<Step
158
169
  Record
159
170
  ] : [Step, Record];
160
171
  /** `[what Step must be, what it gives to its as]`, read against `Scope`. */
161
- type CheckStep<Step, Dependencies, Scope> = [Step] extends [
172
+ type CheckStep<Step, Dependencies, Scope, Response> = [Step] extends [
162
173
  {
163
174
  readonly find: infer Target;
164
175
  }
@@ -183,7 +194,11 @@ type CheckStep<Step, Dependencies, Scope> = [Step] extends [
183
194
  readonly delete: infer Repository;
184
195
  }] ? WriteStep<Step, MemberOf<Dependencies, Repository, "delete">, Scope> : [Step] extends [{
185
196
  readonly restore: infer Repository;
186
- }] ? WriteStep<Step, MemberOf<Dependencies, Repository, "restore">, Scope> : ValueStep<Step, "value" | "other" | "details" | "return", unknown, unknown, Scope>;
197
+ }] ? WriteStep<Step, MemberOf<Dependencies, Repository, "restore">, Scope> : [Step] extends [{
198
+ readonly return: unknown;
199
+ }] ? ValueStep<Step, "return", ResponseInput<Response>, unknown, Scope> : ValueStep<Step, "value" | "other" | "details", unknown, unknown, Scope>;
200
+ /** What the response blueprint `Response` takes, or anything when it is not known. */
201
+ type ResponseInput<Response> = [Response] extends [IBlueprintCollection] ? BlueprintInput<Response> : unknown;
187
202
  type WriteStep<Step, Method, Scope> = ValueStep<Step, "value", FirstParameter<Method>, ResultOf<Method>, Scope>;
188
203
  /** The scope after `Step`: its result bound to its `as`, or a value it ensures exists. */
189
204
  type ScopeAfter<Step, Result, Scope> = [Step] extends [
@@ -201,14 +216,18 @@ type ScopeAfter<Step, Result, Scope> = [Step] extends [
201
216
  readonly [Key in Name]: NonNullable<Scope[Name]>;
202
217
  } : Scope : Scope;
203
218
  /** The steps folded in order, each checked against the scope the earlier ones built. */
204
- type CheckSteps<Steps extends readonly unknown[], Dependencies, Scope, Checked extends readonly unknown[] = []> = [Steps] extends [readonly [infer Step, ...infer Rest]] ? CheckStep<Step, Dependencies, Scope> extends [infer Current, infer Result] ? CheckSteps<Rest, Dependencies, ScopeAfter<Step, Result, Scope>, [
219
+ type CheckSteps<Steps extends readonly unknown[], Dependencies, Scope, Response, Checked extends readonly unknown[] = []> = [Steps] extends [readonly [infer Step, ...infer Rest]] ? CheckStep<Step, Dependencies, Scope, Response> extends [
220
+ infer Current,
221
+ infer Result
222
+ ] ? CheckSteps<Rest, Dependencies, ScopeAfter<Step, Result, Scope>, Response, [
205
223
  ...Checked,
206
224
  Current
207
225
  ]> : never : readonly [...Checked];
208
226
  /**
209
227
  * What `useCaseOf` takes as `steps`: the steps, inferred as a tuple, each
210
- * checked against the values in scope. Steps that are not a tuple (built at
228
+ * checked against the values in scope, and the `return` against what the
229
+ * response blueprint (the collection `Response`) takes. Steps that are not a tuple (built at
211
230
  * run time) are checked only when the use case is declared.
212
231
  */
213
- export type CheckedSteps<Steps, Input extends IBlueprintCollection, Context extends IBlueprintCollection | undefined, Dependencies> = [Steps] extends [readonly [unknown, ...unknown[]]] ? CheckSteps<Steps, Dependencies, StepsInitialScope<Input, Context, Dependencies>> : Steps;
232
+ export type CheckedSteps<Steps, Input extends IBlueprintCollection, Context extends IBlueprintCollection | undefined, Dependencies, Response extends IBlueprintCollection | undefined = undefined> = [Steps] extends [readonly [unknown, ...unknown[]]] ? CheckSteps<Steps, Dependencies, StepsInitialScope<Input, Context, Dependencies>, Response> : Steps;
214
233
  export {};
@@ -1,4 +1,5 @@
1
- import type { AnyRecordClass } from "../../../blueprint/types/blueprint.interface.js";
1
+ import type { FieldsDescription } from "../../../application/types/application.interface.js";
2
+ import type { AnyBlueprint, AnyRecordClass } from "../../../blueprint/types/blueprint.interface.js";
2
3
  import type { AnyRepository } from "../../../repository/types/repository.interface.js";
3
4
  import type { AnyExceptionClass, AnyUseCaseClass, UseCaseCollection } from "../../use-case.js";
4
5
  /** A reference to a value: `$input.email`, `$context.userId`, `$env.mail`, `$saved.tags.0`. */
@@ -103,6 +104,11 @@ export type UseCaseStep<Dependencies extends UseCaseCollection = UseCaseCollecti
103
104
  readonly run: string;
104
105
  readonly fn: (...args: any[]) => unknown;
105
106
  readonly args?: StepArgs;
107
+ /**
108
+ * Checks the `args` at execution: each against the key of the blueprint
109
+ * at its position; `fn` gets the values as the keys handled them.
110
+ */
111
+ readonly params?: AnyBlueprint;
106
112
  readonly as?: string;
107
113
  } | {
108
114
  readonly return: StepValue;
@@ -163,6 +169,7 @@ export type UseCaseStepDescription = {
163
169
  readonly kind: "opaque";
164
170
  };
165
171
  readonly args?: StepArgs;
172
+ readonly params?: FieldsDescription;
166
173
  readonly as?: string;
167
174
  } | {
168
175
  readonly return: StepValue;