@afora/core 0.0.8 → 0.0.11

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 (226) hide show
  1. package/README.md +149 -23
  2. package/dist/adapters.d.ts +25 -0
  3. package/dist/adapters.js +32 -0
  4. package/dist/core/application/application.d.ts +30 -0
  5. package/dist/core/application/application.js +1291 -0
  6. package/dist/core/application/exceptions.d.ts +17 -0
  7. package/dist/core/application/exceptions.js +85 -0
  8. package/dist/core/application/migrate.d.ts +19 -0
  9. package/dist/core/application/migrate.js +67 -0
  10. package/dist/core/application/providers.js +108 -0
  11. package/dist/core/application/registry.js +23 -0
  12. package/dist/core/application/schema.d.ts +30 -0
  13. package/dist/core/application/schema.js +398 -0
  14. package/dist/core/application/start.js +162 -0
  15. package/dist/core/application/types/application.interface.d.ts +60 -3
  16. package/dist/core/auth/jwt.d.ts +30 -0
  17. package/dist/core/auth/jwt.js +200 -0
  18. package/dist/core/auth/types/jwt.interface.d.ts +74 -0
  19. package/dist/core/blueprint/blueprint-plugin.js +7 -0
  20. package/dist/core/blueprint/blueprint.js +146 -0
  21. package/dist/core/blueprint/callable.js +13 -0
  22. package/dist/core/blueprint/exceptions.d.ts +5 -0
  23. package/dist/core/blueprint/exceptions.js +20 -0
  24. package/dist/core/blueprint/is-blueprint.js +4 -0
  25. package/dist/core/blueprint/list.js +19 -0
  26. package/dist/core/blueprint/optional.d.ts +3 -1
  27. package/dist/core/blueprint/optional.js +69 -0
  28. package/dist/core/blueprint/record-field.js +14 -0
  29. package/dist/core/blueprint/slice.js +127 -0
  30. package/dist/core/blueprint/types/blueprint.interface.d.ts +95 -5
  31. package/dist/core/blueprint/variant.d.ts +32 -0
  32. package/dist/core/blueprint/variant.js +72 -0
  33. package/dist/core/command/command.js +428 -0
  34. package/dist/core/command/exceptions.js +30 -0
  35. package/dist/core/command/types/command.interface.d.ts +10 -1
  36. package/dist/core/entity/abstract-entity.d.ts +24 -0
  37. package/dist/core/entity/abstract-entity.js +181 -0
  38. package/dist/core/entity/entity.d.ts +1 -1
  39. package/dist/core/entity/entity.js +209 -0
  40. package/dist/core/entity/event.d.ts +39 -0
  41. package/dist/core/entity/event.js +80 -0
  42. package/dist/core/entity/exceptions.d.ts +9 -0
  43. package/dist/core/entity/exceptions.js +27 -0
  44. package/dist/core/entity/trust.d.ts +24 -0
  45. package/dist/core/entity/trust.js +7 -0
  46. package/dist/core/entity/types/entity.interface.d.ts +30 -0
  47. package/dist/core/entity/version.js +13 -0
  48. package/dist/core/environment/environment.d.ts +26 -3
  49. package/dist/core/environment/environment.js +71 -0
  50. package/dist/core/event-policy/event-policy.js +76 -0
  51. package/dist/core/event-policy/exceptions.js +12 -0
  52. package/dist/core/event-policy/publish.js +98 -0
  53. package/dist/core/event-policy/publishers.js +18 -0
  54. package/dist/core/event-policy/types/event-policy.interface.d.ts +8 -2
  55. package/dist/core/exception/enums/exception-layers.js +15 -0
  56. package/dist/core/exception/exception.d.ts +15 -0
  57. package/dist/core/exception/exception.js +128 -0
  58. package/dist/core/exception/exported.js +49 -0
  59. package/dist/core/exception/guard.d.ts +20 -1
  60. package/dist/core/exception/guard.js +16 -0
  61. package/dist/core/exception/types/exception.interface.d.ts +13 -0
  62. package/dist/core/json.js +19 -0
  63. package/dist/core/logger/exceptions.js +7 -0
  64. package/dist/core/logger/logger.d.ts +14 -1
  65. package/dist/core/logger/logger.js +178 -0
  66. package/dist/core/openapi/exceptions.js +7 -0
  67. package/dist/core/openapi/openapi.js +132 -0
  68. package/dist/core/openapi/types/openapi.interface.d.ts +7 -1
  69. package/dist/core/output/access.d.ts +16 -0
  70. package/dist/core/output/access.js +66 -0
  71. package/dist/core/output/coerce.js +38 -0
  72. package/dist/core/output/exceptions.d.ts +16 -0
  73. package/dist/core/output/exceptions.js +22 -0
  74. package/dist/core/output/http.d.ts +36 -0
  75. package/dist/core/output/http.js +351 -0
  76. package/dist/core/output/openapi-sink.d.ts +33 -4
  77. package/dist/core/output/openapi-sink.js +571 -0
  78. package/dist/core/output/output.js +192 -0
  79. package/dist/core/output/problem.js +30 -0
  80. package/dist/core/output/select.js +28 -0
  81. package/dist/core/output/stage.d.ts +7 -2
  82. package/dist/core/output/stage.js +111 -0
  83. package/dist/core/output/status.d.ts +1 -1
  84. package/dist/core/output/status.js +52 -0
  85. package/dist/core/output/types/http.interface.d.ts +134 -0
  86. package/dist/core/output/types/output.interface.d.ts +49 -5
  87. package/dist/core/parse/issue.d.ts +28 -3
  88. package/dist/core/parse/issue.js +31 -0
  89. package/dist/core/parse/parse.d.ts +16 -0
  90. package/dist/core/parse/parse.js +323 -0
  91. package/dist/core/parse/strip.js +48 -0
  92. package/dist/core/property/abstract-property.d.ts +9 -0
  93. package/dist/core/property/abstract-property.js +37 -0
  94. package/dist/core/property/define-property.d.ts +34 -6
  95. package/dist/core/property/define-property.js +209 -0
  96. package/dist/core/property/enums/property-states.js +8 -0
  97. package/dist/core/property/exceptions.js +12 -0
  98. package/dist/core/property/input.js +10 -0
  99. package/dist/core/property/is-property.js +3 -0
  100. package/dist/core/property/lineage.js +8 -0
  101. package/dist/core/property/property.d.ts +27 -0
  102. package/dist/core/property/property.js +46 -0
  103. package/dist/core/property/refine.js +103 -0
  104. package/dist/core/property/types/property-options.interface.d.ts +10 -0
  105. package/dist/core/property/types/property-type.interface.d.ts +4 -0
  106. package/dist/core/record/abstract-record.d.ts +8 -0
  107. package/dist/core/record/abstract-record.js +307 -0
  108. package/dist/core/record/exceptions.js +49 -0
  109. package/dist/core/record/marks.js +7 -0
  110. package/dist/core/record/mask.js +60 -0
  111. package/dist/core/record/name.js +23 -0
  112. package/dist/core/record/plugin-catalog.js +115 -0
  113. package/dist/core/record/plugin.js +198 -0
  114. package/dist/core/record/record.js +117 -0
  115. package/dist/core/record/types/record.interface.d.ts +17 -1
  116. package/dist/core/repository/cursor.js +83 -0
  117. package/dist/core/repository/exceptions.d.ts +13 -0
  118. package/dist/core/repository/exceptions.js +72 -0
  119. package/dist/core/repository/in-memory.d.ts +2 -2
  120. package/dist/core/repository/in-memory.js +196 -0
  121. package/dist/core/repository/migrate.d.ts +20 -0
  122. package/dist/core/repository/migrate.js +254 -0
  123. package/dist/core/repository/plan.js +27 -0
  124. package/dist/core/repository/provider.js +29 -0
  125. package/dist/core/repository/query.js +153 -0
  126. package/dist/core/repository/references.js +196 -0
  127. package/dist/core/repository/repository.d.ts +22 -2
  128. package/dist/core/repository/repository.js +720 -0
  129. package/dist/core/repository/state.d.ts +2 -0
  130. package/dist/core/repository/state.js +138 -0
  131. package/dist/core/repository/types/query.interface.d.ts +13 -0
  132. package/dist/core/repository/types/repository.interface.d.ts +50 -5
  133. package/dist/core/repository/types/schema.interface.d.ts +132 -0
  134. package/dist/core/repository/upcast.d.ts +64 -0
  135. package/dist/core/repository/upcast.js +179 -0
  136. package/dist/core/schema/schema.d.ts +37 -0
  137. package/dist/core/schema/schema.js +542 -0
  138. package/dist/core/standard/standard-schema.js +1 -0
  139. package/dist/core/standard/standard.js +23 -0
  140. package/dist/core/symbols/demo.d.ts +4 -0
  141. package/dist/core/symbols/demo.js +1 -0
  142. package/dist/core/symbols/kind.d.ts +1 -1
  143. package/dist/core/symbols/kind.js +1 -0
  144. package/dist/core/symbols/meta.js +1 -0
  145. package/dist/core/symbols/state.d.ts +5 -0
  146. package/dist/core/symbols/state.js +1 -0
  147. package/dist/core/tracing/exceptions.d.ts +4 -0
  148. package/dist/core/tracing/exceptions.js +7 -0
  149. package/dist/core/tracing/tracing.d.ts +72 -0
  150. package/dist/core/tracing/tracing.js +101 -0
  151. package/dist/core/use-case/access.d.ts +12 -0
  152. package/dist/core/use-case/access.js +56 -0
  153. package/dist/core/use-case/decorators.d.ts +40 -0
  154. package/dist/core/use-case/decorators.js +65 -0
  155. package/dist/core/use-case/exceptions.d.ts +5 -1
  156. package/dist/core/use-case/exceptions.js +48 -0
  157. package/dist/core/use-case/plugin-catalog.js +68 -0
  158. package/dist/core/use-case/plugin.js +133 -0
  159. package/dist/core/use-case/route.d.ts +18 -0
  160. package/dist/core/use-case/route.js +200 -0
  161. package/dist/core/use-case/scope.d.ts +5 -0
  162. package/dist/core/use-case/scope.js +4 -0
  163. package/dist/core/use-case/steps/describe.d.ts +5 -1
  164. package/dist/core/use-case/steps/describe.js +24 -0
  165. package/dist/core/use-case/steps/references.js +105 -0
  166. package/dist/core/use-case/steps/run.js +139 -0
  167. package/dist/core/use-case/steps/types/check.interface.d.ts +233 -0
  168. package/dist/core/use-case/steps/types/steps.interface.d.ts +8 -1
  169. package/dist/core/use-case/steps/validate.js +368 -0
  170. package/dist/core/use-case/target.js +100 -0
  171. package/dist/core/use-case/types/access.interface.d.ts +39 -0
  172. package/dist/core/use-case/types/route.interface.d.ts +86 -0
  173. package/dist/core/use-case/use-case.d.ts +127 -2
  174. package/dist/core/use-case/use-case.js +493 -0
  175. package/dist/experimental.d.ts +18 -1
  176. package/dist/experimental.js +18 -123
  177. package/dist/index.d.ts +49 -51
  178. package/dist/index.js +213 -443
  179. package/dist/std/blueprint-plugin.js +42 -0
  180. package/dist/std/blueprint.d.ts +21 -0
  181. package/dist/std/blueprint.js +3 -0
  182. package/dist/std/command.js +3 -0
  183. package/dist/std/entity.d.ts +20 -1
  184. package/dist/std/entity.js +30 -0
  185. package/dist/std/event-policies.js +3 -0
  186. package/dist/std/properties/contact.d.ts +3 -0
  187. package/dist/std/properties/contact.js +29 -0
  188. package/dist/std/properties/enum.js +26 -0
  189. package/dist/std/properties/exceptions.js +14 -0
  190. package/dist/std/properties/headless.js +45 -0
  191. package/dist/std/properties/identity.js +28 -0
  192. package/dist/std/properties/json.js +161 -0
  193. package/dist/std/properties/password.d.ts +0 -2
  194. package/dist/std/properties/password.js +79 -0
  195. package/dist/std/properties/primitives.d.ts +4 -0
  196. package/dist/std/properties/primitives.js +28 -0
  197. package/dist/std/properties/time.js +55 -0
  198. package/dist/std/record-plugins.d.ts +2 -2
  199. package/dist/std/record-plugins.js +73 -0
  200. package/dist/std/record.d.ts +20 -1
  201. package/dist/std/record.js +3 -0
  202. package/dist/std/resource.d.ts +31 -0
  203. package/dist/std/resource.js +39 -0
  204. package/dist/std/runtime.d.ts +6 -0
  205. package/dist/std/runtime.js +193 -0
  206. package/dist/std/stages.js +4 -0
  207. package/dist/std/use-cases/auth.d.ts +5 -1
  208. package/dist/std/use-cases/auth.js +193 -0
  209. package/dist/std/use-cases/crud.js +261 -0
  210. package/dist/std/use-cases/index.d.ts +1 -1
  211. package/dist/std/use-cases/index.js +12 -0
  212. package/dist/std/use-cases/lifecycle.js +97 -0
  213. package/dist/std/use-cases/shared.d.ts +2 -2
  214. package/dist/std/use-cases/shared.js +232 -0
  215. package/dist/std/use-cases/types.d.ts +102 -2
  216. package/dist/std/use-cases/types.js +0 -0
  217. package/dist/testing/http-provider-contract.d.ts +32 -0
  218. package/dist/testing/http-provider-contract.js +344 -0
  219. package/dist/testing/index.d.ts +2 -0
  220. package/dist/testing/index.js +3 -944
  221. package/dist/testing/memory-publisher.js +12 -0
  222. package/dist/testing/provider-contract.d.ts +26 -1
  223. package/dist/testing/provider-contract.js +1071 -0
  224. package/package.json +23 -3
  225. package/dist/index-qxfkyd6k.js +0 -1721
  226. package/dist/index-t5qwtyj5.js +0 -8194
@@ -0,0 +1,233 @@
1
+ /**
2
+ * The compile-time checking of `useCaseOf({ steps })`: the steps are inferred
3
+ * as a tuple and folded in order. Each step sees the values in scope (the
4
+ * input, the context, the environments and the names bound by the earlier
5
+ * steps with `as`); its references must name a path of one of them, and its
6
+ * `args`, `values`, `value` or `input` must fit what the called function,
7
+ * record, repository or use case takes. A reference that does not fit is
8
+ * replaced by the references that would, which is the error TypeScript shows
9
+ * and what the editor offers inside a `"$…"` string.
10
+ *
11
+ * The list of the paths of the scope is computed only for a reference that
12
+ * does not fit, so that right steps cost little to check. The conditionals on
13
+ * the steps are not distributive (`[Step] extends [...]`): TypeScript then
14
+ * never evaluates them with the constraint of the steps while it infers them.
15
+ */
16
+ import type { BlueprintInput, BlueprintValue, IBlueprint, IBlueprintCollection } from "../../../blueprint/types/blueprint.interface.js";
17
+ import type { AnyEnvironment, EnvironmentValue } from "../../../environment/environment.js";
18
+ import type { Meta } from "../../../symbols/meta.js";
19
+ import type { AnyUseCaseClass, UseCaseInput, UseCaseResponse } from "../../use-case.js";
20
+ /** The values the first step can reference: the input, the context (if declared) and the environments. */
21
+ export type StepsInitialScope<Input extends IBlueprintCollection, Context extends IBlueprintCollection | undefined, Dependencies> = {
22
+ readonly input: BlueprintValue<Input>;
23
+ readonly env: {
24
+ readonly [Key in keyof Dependencies as Dependencies[Key] extends AnyEnvironment ? Key : never]: Dependencies[Key] extends AnyEnvironment ? EnvironmentValue<Dependencies[Key]> : never;
25
+ };
26
+ } & (Context extends IBlueprintCollection ? {
27
+ readonly context: BlueprintValue<Context>;
28
+ } : unknown);
29
+ type Split<Path extends string> = Path extends `${infer Head}.${infer Tail}` ? [Head, ...Split<Tail>] : [Path];
30
+ /** `Value[Key]` for a value that is not `null` or `undefined`: by index (or `length`) in a list; `never` when absent. */
31
+ type GetDefined<Value, Key extends string> = Value extends readonly unknown[] ? Key extends `${number}` ? Key extends keyof Value ? Value[Key] : Value[number] : Key extends "length" ? number : never : Key extends keyof Value ? Value[Key] : never;
32
+ /**
33
+ * `Value[Key]` as `followPath` reads it: `undefined` through a value that
34
+ * may be `undefined` or `null`, and `never` when no value of the type has
35
+ * the key; `unknown` stays `unknown`.
36
+ */
37
+ type Get<Value, Key extends string> = unknown extends Value ? unknown : [GetDefined<NonNullable<Value>, Key>] extends [infer Found] ? [Found] extends [never] ? never : Found | (Value extends null | undefined ? undefined : never) : never;
38
+ type Walk<Value, Path extends readonly string[]> = Path extends readonly [
39
+ infer Key extends string,
40
+ ...infer Rest extends string[]
41
+ ] ? Walk<Get<Value, Key>, Rest> : Value;
42
+ /** The type of the value the reference `$root.a.b` stands for in `Scope`; `never` when it names nothing. */
43
+ export type StepReferenceType<Scope, Reference> = [Reference] extends [
44
+ `$${infer Path}`
45
+ ] ? Split<Path> extends [
46
+ infer Root extends string,
47
+ ...infer Rest extends string[]
48
+ ] ? Root extends "env" ? Rest extends [string, ...string[]] ? Walk<Get<Scope, Root>, Rest> : never : Walk<Get<Scope, Root>, Rest> : never : never;
49
+ /** What a path walks into: the value of a record, not its methods. */
50
+ type DataOf<Value> = Value extends {
51
+ readonly [Meta]: {
52
+ readonly blueprint: IBlueprint<infer Properties>;
53
+ };
54
+ } ? BlueprintValue<Properties> : Value;
55
+ type Leaf = string | number | boolean | bigint | symbol | null | undefined | Date | readonly unknown[] | ((...args: any[]) => unknown);
56
+ type Below<Value, Prefix extends string, Expected, Depth extends unknown[]> = Depth["length"] extends 3 ? never : DataOf<NonNullable<Value>> extends infer Data ? Data extends Leaf ? never : {
57
+ [Key in keyof Data & string]-?: PathsFrom<Data[Key] | (Value extends null | undefined ? undefined : never), `${Prefix}.${Key}`, Expected, [
58
+ ...Depth,
59
+ 0
60
+ ]>;
61
+ }[keyof Data & string] : never;
62
+ type PathsFrom<Value, Path extends string, Expected, Depth extends unknown[]> = Path extends unknown ? ([Value] extends [Expected] ? Path : never) | Below<Value, Path, Expected, Depth> : never;
63
+ /** Every reference of `Scope` whose value fits `Expected` (three keys deep at most). */
64
+ export type StepReferencesOf<Scope, Expected = unknown> = Scope extends unknown ? {
65
+ [Root in keyof Scope & string]-?: Root extends "env" ? Below<Scope[Root], `$${Root}`, Expected, [0]> : PathsFrom<Scope[Root], `$${Root}`, Expected, []>;
66
+ }[keyof Scope & string] : never;
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
+ */
79
+ type CheckReference<Reference, Expected, Scope> = [
80
+ StepReferenceType<Scope, Reference>
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;
82
+ type Primitive = string | number | boolean | null;
83
+ /** The type of the key `Key` in what `Expected` takes, over each object of it. */
84
+ type ExpectedAt<Expected, Key> = Expected extends object ? Key extends keyof Expected ? Expected[Key] : never : never;
85
+ /** The keys `Expected` takes in a mapping. */
86
+ type KnownKeys<Expected> = Expected extends object ? keyof Expected : never;
87
+ /** The keys the plain objects of `Expected` require (a record instance is not built from a mapping). */
88
+ type RequiredKeys<Expected> = Expected extends {
89
+ readonly [Meta]: unknown;
90
+ } ? never : Expected extends object ? {
91
+ [Key in keyof Expected]-?: undefined extends Expected[Key] ? never : Key;
92
+ }[keyof Expected] : never;
93
+ /** The keys of `Value` that `Expected` takes, each checked; the others are excess properties. */
94
+ type CheckKeys<Value, Expected, Scope> = Value extends unknown ? {
95
+ readonly [Key in keyof Value as Key extends KnownKeys<Expected> ? Key : never]: StepValueCheck<Value[Key], ExpectedAt<Expected, Key>, Scope>;
96
+ } : never;
97
+ type CheckMapping<Value, Expected, Scope> = unknown extends Expected ? {
98
+ readonly [Key in keyof Value]: StepValueCheck<Value[Key], unknown, Scope>;
99
+ } : [Exclude<RequiredKeys<Expected>, keyof Value>] extends [never] ? CheckKeys<Value, Expected, Scope> : CheckKeys<Value, Expected, Scope> & {
100
+ readonly [Key in Exclude<RequiredKeys<Expected>, keyof Value>]: ExpectedAt<Expected, Key>;
101
+ };
102
+ type ElementAt<Expected, Index> = unknown extends Expected ? unknown : Expected extends readonly unknown[] ? Index extends keyof Expected ? Expected[Index] : Expected[number] : never;
103
+ /**
104
+ * What the step value `Value` must be where `Expected` is taken: itself when
105
+ * it fits; else, for a reference, the references that would fit, and for a
106
+ * literal, `Expected`. A mapping is checked key by key, a list item by item.
107
+ */
108
+ export type StepValueCheck<Value, Expected, Scope> = [Value] extends [
109
+ `$${string}`
110
+ ] ? CheckReference<Value, Expected, Scope> : [Value] extends [Primitive] ? [Value] extends [Expected] ? Value : Expected : [Value] extends [{
111
+ readonly literal: unknown;
112
+ }] ? Value : [Value] extends [readonly unknown[]] ? {
113
+ readonly [Index in keyof Value]: StepValueCheck<Value[Index], ElementAt<Expected, Index>, Scope>;
114
+ } : [Value] extends [object] ? CheckMapping<Value, Expected, Scope> : Value;
115
+ /** The parameter `Index` of a function whose parameters are `Parameters`. */
116
+ type ParameterAt<Parameters extends readonly unknown[], Index> = Index extends keyof Parameters ? Parameters[Index] : number extends Parameters["length"] ? Parameters[number] : undefined;
117
+ /** What the `args` of a call must be: each against its parameter, or one reference to a list. */
118
+ type CheckArgs<Args, Parameters extends readonly unknown[], Scope> = [
119
+ Args
120
+ ] extends [`$${string}`] ? CheckReference<Args, readonly unknown[], Scope> : {
121
+ readonly [Index in keyof Args]: StepValueCheck<Args[Index], ParameterAt<Parameters, Index>, Scope>;
122
+ };
123
+ type Fn = (...args: any[]) => unknown;
124
+ type ParametersOf<Value> = [Value] extends [Fn] ? Parameters<Value> : unknown[];
125
+ type FirstParameter<Value> = [Value] extends [never] ? unknown : [Value] extends [Fn] ? Parameters<Value>[0] : unknown;
126
+ type ResultOf<Value> = [Value] extends [Fn] ? Awaited<ReturnType<Value>> : unknown;
127
+ /** `Dependencies[Key][Member]`, or `unknown`. */
128
+ type MemberOf<Dependencies, Key, Member> = Key extends keyof Dependencies ? Member extends keyof Dependencies[Key] ? Dependencies[Key][Member] : unknown : unknown;
129
+ /** The method `"users.findByEmail"` names among `Dependencies`. */
130
+ type FindMethod<Dependencies, Target> = Target extends `${infer Repository}.${infer Action}` ? MemberOf<Dependencies, Repository, Action> : unknown;
131
+ /** The members of a record no `invoke` may call. */
132
+ type ForbiddenMethod = "set" | "emit" | "constructor" | "toLocaleString" | "valueOf" | "hasOwnProperty" | "isPrototypeOf" | "propertyIsEnumerable";
133
+ /** The public methods of the record `Record`: what `invoke.method` names. */
134
+ type StepInvokeMethod<Record> = Record extends unknown ? {
135
+ [Key in keyof Record & string]-?: Record[Key] extends Fn ? Key : never;
136
+ }[Exclude<keyof Record & string, ForbiddenMethod>] : never;
137
+ type ChangeOf<Record> = Record extends {
138
+ readonly change: infer Change;
139
+ } ? Change : never;
140
+ type UseCaseOf<Dependencies, Key> = Key extends keyof Dependencies ? Dependencies[Key] extends AnyUseCaseClass ? Dependencies[Key] : never : never;
141
+ /** A step that calls `Method` with its `args`: `[checked step, result]`. */
142
+ type CallStep<Step, Method, Scope> = [
143
+ {
144
+ readonly [Key in keyof Step]: Key extends "args" ? CheckArgs<Step[Key], ParametersOf<Method>, Scope> : Step[Key];
145
+ },
146
+ ResultOf<Method>
147
+ ];
148
+ /** A step whose `Field` is checked against `Expected`: `[checked step, result]`. */
149
+ type ValueStep<Step, Field, Expected, Result, Scope> = [
150
+ {
151
+ readonly [Key in keyof Step]: Key extends Field ? StepValueCheck<Step[Key], Expected, Scope> : Step[Key];
152
+ },
153
+ Result
154
+ ];
155
+ type InvokeStep<Step, Record, Scope> = [Record] extends [never] ? ValueStep<Step, "invoke", object, unknown, Scope> : [Step] extends [{
156
+ readonly method: infer Method;
157
+ }] ? Method extends keyof Record ? CallStep<Step, Record[Method], Scope> : [
158
+ {
159
+ readonly [Key in keyof Step]: Key extends "method" ? StepInvokeMethod<Record> : Step[Key];
160
+ },
161
+ unknown
162
+ ] : [Step, unknown];
163
+ type ChangeStep<Step, Record, Scope> = [Record] extends [never] ? ValueStep<Step, "change", object, unknown, Scope> : [Step] extends [{
164
+ readonly key: infer Name;
165
+ }] ? Name extends keyof ChangeOf<Record> ? ValueStep<Step, "value", FirstParameter<ChangeOf<Record>[Name]>, Record, Scope> : [
166
+ {
167
+ readonly [Key in keyof Step]: Key extends "key" ? keyof ChangeOf<Record> & string : Step[Key];
168
+ },
169
+ Record
170
+ ] : [Step, Record];
171
+ /** `[what Step must be, what it gives to its as]`, read against `Scope`. */
172
+ type CheckStep<Step, Dependencies, Scope, Response> = [Step] extends [
173
+ {
174
+ readonly find: infer Target;
175
+ }
176
+ ] ? CallStep<Step, FindMethod<Dependencies, Target>, Scope> : [Step] extends [{
177
+ readonly run: unknown;
178
+ readonly fn: infer Run;
179
+ }] ? CallStep<Step, Run, Scope> : [Step] extends [{
180
+ readonly invoke: infer Reference;
181
+ }] ? InvokeStep<Step, NonNullable<StepReferenceType<Scope, Reference>>, Scope> : [Step] extends [{
182
+ readonly change: infer Reference;
183
+ }] ? ChangeStep<Step, NonNullable<StepReferenceType<Scope, Reference>>, Scope> : [Step] extends [
184
+ {
185
+ readonly build: abstract new (value: infer Input) => infer Built;
186
+ }
187
+ ] ? ValueStep<Step, "values", Input, Built, Scope> : [Step] extends [{
188
+ readonly call: infer Name;
189
+ }] ? ValueStep<Step, "input", UseCaseInput<UseCaseOf<Dependencies, Name>>, UseCaseResponse<UseCaseOf<Dependencies, Name>>, Scope> : [Step] extends [{
190
+ readonly create: infer Repository;
191
+ }] ? WriteStep<Step, MemberOf<Dependencies, Repository, "create">, Scope> : [Step] extends [{
192
+ readonly update: infer Repository;
193
+ }] ? WriteStep<Step, MemberOf<Dependencies, Repository, "update">, Scope> : [Step] extends [{
194
+ readonly delete: infer Repository;
195
+ }] ? WriteStep<Step, MemberOf<Dependencies, Repository, "delete">, Scope> : [Step] extends [{
196
+ readonly restore: infer Repository;
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;
202
+ type WriteStep<Step, Method, Scope> = ValueStep<Step, "value", FirstParameter<Method>, ResultOf<Method>, Scope>;
203
+ /** The scope after `Step`: its result bound to its `as`, or a value it ensures exists. */
204
+ type ScopeAfter<Step, Result, Scope> = [Step] extends [
205
+ {
206
+ readonly as: infer Name extends string;
207
+ }
208
+ ] ? Scope & {
209
+ readonly [Key in Name]: Result;
210
+ } : [Step] extends [
211
+ {
212
+ readonly ensure: "exists";
213
+ readonly value: `$${infer Name}`;
214
+ }
215
+ ] ? Name extends keyof Scope ? Scope & {
216
+ readonly [Key in Name]: NonNullable<Scope[Name]>;
217
+ } : Scope : Scope;
218
+ /** The steps folded in order, each checked against the scope the earlier ones built. */
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, [
223
+ ...Checked,
224
+ Current
225
+ ]> : never : readonly [...Checked];
226
+ /**
227
+ * What `useCaseOf` takes as `steps`: the steps, inferred as a tuple, each
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
230
+ * run time) are checked only when the use case is declared.
231
+ */
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;
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;
@@ -0,0 +1,368 @@
1
+ import { isBlueprint } from "../../blueprint/is-blueprint.js";
2
+ import { isBlueprintList } from "../../blueprint/list.js";
3
+ import { isRecordClass, recordFieldOf } from "../../blueprint/record-field.js";
4
+ import {
5
+ blueprintOfVariant,
6
+ isBlueprintVariant
7
+ } from "../../blueprint/variant.js";
8
+ import { isEnvironment } from "../../environment/environment.js";
9
+ import { isDeclaredException } from "../../exception/exception.js";
10
+ import { isRepository } from "../../repository/repository.js";
11
+ import { Kind } from "../../symbols/kind.js";
12
+ import { InvalidUseCaseStep } from "../exceptions.js";
13
+ import {
14
+ isPlain,
15
+ isStepName,
16
+ parseReference,
17
+ RESERVED_NAMES,
18
+ walkValue
19
+ } from "./references.js";
20
+ const SHAPES = {
21
+ find: { required: ["find"], optional: ["args", "as"] },
22
+ ensure: {
23
+ required: ["ensure", "value", "error"],
24
+ optional: ["other", "details"]
25
+ },
26
+ build: { required: ["build", "values", "as"], optional: [] },
27
+ change: {
28
+ required: ["change", "key", "value"],
29
+ optional: ["ifPresent", "as"]
30
+ },
31
+ invoke: { required: ["invoke", "method"], optional: ["args", "as"] },
32
+ create: { required: ["create", "value"], optional: ["as"] },
33
+ update: { required: ["update", "value"], optional: ["as"] },
34
+ delete: { required: ["delete", "value"], optional: ["as"] },
35
+ restore: { required: ["restore", "value"], optional: ["as"] },
36
+ call: { required: ["call", "input"], optional: ["as"] },
37
+ run: { required: ["run", "fn"], optional: ["args", "params", "as"] },
38
+ return: { required: ["return"], optional: [] }
39
+ };
40
+ const KINDS = Object.keys(SHAPES);
41
+ const ENSURE_KINDS = new Set([
42
+ "exists",
43
+ "absent",
44
+ "equals",
45
+ "notEquals",
46
+ "true",
47
+ "false"
48
+ ]);
49
+ const READ_ACTION = /^(?:findBy.+|findManyBy.+|findMany|exists|findPage|findCursor|count)$/;
50
+ const WRITE_ACTIONS = new Set([
51
+ "create",
52
+ "update",
53
+ "delete",
54
+ "restore",
55
+ "increment"
56
+ ]);
57
+ const INDEX = /^(?:0|[1-9][0-9]*)$/;
58
+ const LENGTH = Symbol("length");
59
+ const PRIMITIVE_SCHEMA_TYPES = new Set([
60
+ "string",
61
+ "number",
62
+ "integer",
63
+ "boolean",
64
+ "null"
65
+ ]);
66
+ function isPrimitiveProperty(field) {
67
+ const schema = field?.type?.schema;
68
+ if (typeof field !== "function" || schema === undefined)
69
+ return false;
70
+ try {
71
+ const type = JSON.parse(schema.toString()).type;
72
+ return typeof type === "string" && PRIMITIVE_SCHEMA_TYPES.has(type);
73
+ } catch {
74
+ return false;
75
+ }
76
+ }
77
+ function missingSegment(field, path) {
78
+ let current = field;
79
+ for (const segment of path) {
80
+ if (current === LENGTH)
81
+ return segment;
82
+ if (isBlueprint(current)) {
83
+ if (!Object.hasOwn(current.properties, segment))
84
+ return segment;
85
+ current = current.properties[segment];
86
+ continue;
87
+ }
88
+ if (isBlueprintList(current)) {
89
+ if (segment === "length") {
90
+ current = LENGTH;
91
+ continue;
92
+ }
93
+ if (!INDEX.test(segment))
94
+ return segment;
95
+ current = current.item;
96
+ continue;
97
+ }
98
+ if (isBlueprintVariant(current)) {
99
+ if (segment === current.key)
100
+ return;
101
+ const variants = current.variants;
102
+ const holder = Object.values(variants).map(blueprintOfVariant).find((variant) => Object.hasOwn(variant.properties, segment));
103
+ if (!holder)
104
+ return segment;
105
+ current = holder.properties[segment];
106
+ continue;
107
+ }
108
+ const record = recordFieldOf(current)?.record;
109
+ if (record) {
110
+ const { properties } = record.blueprint;
111
+ if (Object.hasOwn(properties, segment)) {
112
+ current = properties[segment];
113
+ continue;
114
+ }
115
+ return segment in record.prototype ? undefined : segment;
116
+ }
117
+ return isPrimitiveProperty(current) ? segment : undefined;
118
+ }
119
+ return;
120
+ }
121
+ export function validateSteps(declaration, steps) {
122
+ const { name } = declaration;
123
+ const fail = (index, code, reason) => {
124
+ throw new InvalidUseCaseStep({
125
+ source: name,
126
+ details: { index, reason, code }
127
+ });
128
+ };
129
+ if (!Array.isArray(steps) || steps.length === 0) {
130
+ fail(0, "missing_return", "Expected steps to be a list ending in return.");
131
+ }
132
+ const list = steps;
133
+ const bound = new Map;
134
+ const runs = new Set;
135
+ let returned = false;
136
+ const useCaseDependency = (key) => typeof key === "string" && Object.hasOwn(declaration.dependencies, key) ? declaration.dependencies[key] : undefined;
137
+ const checkReference = (index, text) => {
138
+ const reference = parseReference(text);
139
+ if (!reference) {
140
+ fail(index, "invalid_reference", `"${text}" is not a valid reference.`);
141
+ return;
142
+ }
143
+ const { root, path } = reference;
144
+ const firstKey = path[0];
145
+ if (root === "input" || root === "context") {
146
+ const blueprint = root === "input" ? declaration.blueprint : declaration.context;
147
+ if (blueprint === undefined) {
148
+ fail(index, "invalid_reference", `"${text}" uses $context, but the use case declares no context.`);
149
+ return;
150
+ }
151
+ const missing = missingSegment(blueprint, path);
152
+ if (missing !== undefined) {
153
+ fail(index, "unknown_key", `"${text}" names "${missing}", which the ${root} does not declare.`);
154
+ }
155
+ return;
156
+ }
157
+ if (root === "env") {
158
+ if (firstKey === undefined || !isEnvironment(useCaseDependency(firstKey))) {
159
+ fail(index, "unknown_dependency", `"${text}" must name an environment dependency.`);
160
+ }
161
+ return;
162
+ }
163
+ if (RESERVED_NAMES.includes(root)) {
164
+ fail(index, "invalid_reference", `"${root}" is reserved.`);
165
+ return;
166
+ }
167
+ if (!bound.has(root)) {
168
+ fail(index, "unbound_reference", `"${text}" uses "${root}", which no earlier step binds.`);
169
+ }
170
+ };
171
+ const checkValue = (index, value, field) => {
172
+ walkValue(value, (_reference, text) => checkReference(index, text), (issue, text) => fail(index, issue, issue === "invalid_reference" ? `"${text}" is not a valid reference.` : `${field} must hold only JSON values and references.`));
173
+ };
174
+ const checkArgs = (index, args) => {
175
+ if (args === undefined)
176
+ return;
177
+ if (typeof args === "string" && args.startsWith("$")) {
178
+ checkReference(index, args);
179
+ return;
180
+ }
181
+ if (!Array.isArray(args)) {
182
+ fail(index, "invalid_step", "args must be a list of values or a reference to one.");
183
+ }
184
+ checkValue(index, args, "args");
185
+ };
186
+ const checkTarget = (index, target, kind) => {
187
+ if (typeof target !== "string" || !target.startsWith("$")) {
188
+ fail(index, "invalid_reference", `${kind} must be a reference.`);
189
+ return;
190
+ }
191
+ checkReference(index, target);
192
+ };
193
+ const repositoryOf = (index, key, action) => {
194
+ const dependency = useCaseDependency(key);
195
+ if (!isRepository(dependency)) {
196
+ return fail(index, "unknown_dependency", `"${String(key)}" is not a repository dependency.`);
197
+ }
198
+ const actions = dependency.actions;
199
+ if (!actions.includes(action)) {
200
+ fail(index, "unknown_action", `The repository "${String(key)}" has no "${action}" action.`);
201
+ }
202
+ return { actions };
203
+ };
204
+ for (const [index, step] of list.entries()) {
205
+ if (!isPlain(step) || Array.isArray(step)) {
206
+ fail(index, "unknown_step", "A step must be an object.");
207
+ }
208
+ const record = step;
209
+ const kinds = KINDS.filter((kind) => Object.hasOwn(record, kind));
210
+ if (kinds.length !== 1) {
211
+ fail(index, "unknown_step", kinds.length === 0 ? `Unknown step; expected one of ${KINDS.join(", ")}.` : `A step has one kind, found ${kinds.join(" and ")}.`);
212
+ }
213
+ const kind = kinds[0];
214
+ const shape = SHAPES[kind];
215
+ for (const key of shape.required) {
216
+ if (!Object.hasOwn(record, key) || record[key] === undefined) {
217
+ fail(index, "invalid_step", `A ${kind} step needs "${key}".`);
218
+ }
219
+ }
220
+ for (const key of Object.keys(record)) {
221
+ if (!shape.required.includes(key) && !shape.optional.includes(key)) {
222
+ fail(index, "invalid_step", `A ${kind} step has no "${key}".`);
223
+ }
224
+ }
225
+ const as = record.as;
226
+ if (as !== undefined) {
227
+ if (!isStepName(as) || RESERVED_NAMES.includes(as) || bound.has(as)) {
228
+ fail(index, "invalid_step_name", `"${String(as)}" must be an identifier, not reserved, bound once.`);
229
+ }
230
+ }
231
+ let knownRecord;
232
+ switch (kind) {
233
+ case "find": {
234
+ const target = record.find;
235
+ const parts = typeof target === "string" ? target.split(".") : [];
236
+ const [repository, action] = parts;
237
+ if (parts.length !== 2 || !repository || !action) {
238
+ fail(index, "invalid_step", 'find must be "<repository>.<action>".');
239
+ }
240
+ if (WRITE_ACTIONS.has(action)) {
241
+ fail(index, "write_in_find", `find cannot call "${action}".`);
242
+ }
243
+ if (action === "forEachPage") {
244
+ fail(index, "unknown_action", '"forEachPage" runs a function on each page and cannot be a step; call it from a use case written in TypeScript.');
245
+ }
246
+ if (!READ_ACTION.test(action)) {
247
+ fail(index, "unknown_action", `"${action}" is not a read action.`);
248
+ }
249
+ repositoryOf(index, repository, action);
250
+ checkArgs(index, record.args);
251
+ break;
252
+ }
253
+ case "ensure": {
254
+ if (!ENSURE_KINDS.has(record.ensure)) {
255
+ fail(index, "invalid_step", `ensure must be one of ${[...ENSURE_KINDS].join(", ")}.`);
256
+ }
257
+ const compares = record.ensure === "equals" || record.ensure === "notEquals";
258
+ if (compares && record.other === undefined) {
259
+ fail(index, "invalid_step", `ensure "${record.ensure}" needs other.`);
260
+ }
261
+ if (!compares && record.other !== undefined) {
262
+ fail(index, "invalid_step", `ensure "${String(record.ensure)}" has no other.`);
263
+ }
264
+ if (!isDeclaredException(record.error) || !declaration.errors.includes(record.error)) {
265
+ fail(index, "undeclared_error", "ensure.error must be a class made by exceptionOf, listed in errors.");
266
+ }
267
+ checkValue(index, record.value, "value");
268
+ if (record.other !== undefined)
269
+ checkValue(index, record.other, "other");
270
+ if (record.details !== undefined) {
271
+ checkValue(index, record.details, "details");
272
+ }
273
+ break;
274
+ }
275
+ case "build": {
276
+ if (!isRecordClass(record.build)) {
277
+ fail(index, "invalid_class", "build must be a record or entity class.");
278
+ }
279
+ const values = record.values;
280
+ if (!(typeof values === "string" && values.startsWith("$")) && !isPlain(values)) {
281
+ fail(index, "invalid_step", "build.values must be a mapping or a reference.");
282
+ }
283
+ checkValue(index, values, "values");
284
+ knownRecord = record.build;
285
+ break;
286
+ }
287
+ case "change": {
288
+ checkTarget(index, record.change, "change");
289
+ if (typeof record.key !== "string" || record.key === "") {
290
+ fail(index, "invalid_step", "change.key must be a string.");
291
+ }
292
+ if (record.ifPresent !== undefined && typeof record.ifPresent !== "boolean") {
293
+ fail(index, "invalid_step", "change.ifPresent must be a boolean.");
294
+ }
295
+ const reference = parseReference(record.change);
296
+ const known = reference && reference.path.length === 0 ? bound.get(reference.root)?.record : undefined;
297
+ if (known) {
298
+ const keys = known.options?.change ?? [];
299
+ if (!keys.includes(record.key)) {
300
+ fail(index, "invalid_change_key", `"${String(record.key)}" is not a change key of ${known.name}.`);
301
+ }
302
+ }
303
+ checkValue(index, record.value, "value");
304
+ knownRecord = known;
305
+ break;
306
+ }
307
+ case "invoke": {
308
+ checkTarget(index, record.invoke, "invoke");
309
+ if (typeof record.method !== "string" || record.method === "") {
310
+ fail(index, "invalid_step", "invoke.method must be a string.");
311
+ }
312
+ checkArgs(index, record.args);
313
+ break;
314
+ }
315
+ case "create":
316
+ case "update":
317
+ case "delete":
318
+ case "restore": {
319
+ repositoryOf(index, record[kind], kind);
320
+ checkValue(index, record.value, "value");
321
+ break;
322
+ }
323
+ case "call": {
324
+ const dependency = useCaseDependency(record.call);
325
+ if (typeof dependency !== "function" || dependency[Kind] !== "use-case") {
326
+ fail(index, "unknown_dependency", `"${String(record.call)}" is not a use case dependency.`);
327
+ }
328
+ checkValue(index, record.input, "input");
329
+ break;
330
+ }
331
+ case "run": {
332
+ const runName = record.run;
333
+ if (!isStepName(runName) || runs.has(runName)) {
334
+ fail(index, "invalid_step_name", `run "${String(runName)}" must be an identifier, unique in the use case.`);
335
+ }
336
+ if (typeof record.fn !== "function") {
337
+ fail(index, "invalid_step", "run.fn must be a function.");
338
+ }
339
+ runs.add(runName);
340
+ checkArgs(index, record.args);
341
+ if (record.params !== undefined) {
342
+ if (!isBlueprint(record.params)) {
343
+ fail(index, "invalid_step", "run.params must be a blueprint.");
344
+ }
345
+ const declared = Object.keys(record.params.properties).length;
346
+ const given = record.args === undefined ? 0 : Array.isArray(record.args) ? record.args.length : undefined;
347
+ if (given !== undefined && given !== declared) {
348
+ fail(index, "invalid_args", `run.params declares ${declared} parameter(s), args gives ${given}.`);
349
+ }
350
+ }
351
+ break;
352
+ }
353
+ case "return": {
354
+ checkValue(index, record.return, "return");
355
+ returned = true;
356
+ if (index !== list.length - 1) {
357
+ fail(index, "misplaced_return", "return must be the last step, exactly once.");
358
+ }
359
+ break;
360
+ }
361
+ }
362
+ if (typeof as === "string")
363
+ bound.set(as, { record: knownRecord });
364
+ }
365
+ if (!returned) {
366
+ fail(list.length, "missing_return", "The steps must end with return.");
367
+ }
368
+ }