@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,100 @@
1
+ import { fieldOf } from "../blueprint/callable.js";
2
+ import { isBlueprint } from "../blueprint/is-blueprint.js";
3
+ import { isBlueprintList } from "../blueprint/list.js";
4
+ import { optionalOf } from "../blueprint/optional.js";
5
+ import { recordFieldOf } from "../blueprint/record-field.js";
6
+ import { isBlueprintVariant } from "../blueprint/variant.js";
7
+ import { identityKeysOf } from "../entity/abstract-entity.js";
8
+ import { isEntityClass } from "../entity/entity.js";
9
+ import { propertyInputOf } from "../property/input.js";
10
+ import { isProperty } from "../property/is-property.js";
11
+ import { recordNameOf } from "../record/name.js";
12
+ import { pluginsOf } from "../record/plugin.js";
13
+ import { findState, relatedRepositoryOf } from "../repository/state.js";
14
+ import { UseCaseUnavailable } from "./exceptions.js";
15
+ function isOptionalField(field) {
16
+ if (isProperty(field))
17
+ return field.type.optional === true;
18
+ const record = recordFieldOf(field);
19
+ if (record)
20
+ return record.optional;
21
+ return field.optional === true;
22
+ }
23
+ function planOf(entity, key, owners) {
24
+ const field = fieldOf(entity.blueprint.properties[key]);
25
+ const optional = isOptionalField(field);
26
+ const record = recordFieldOf(field);
27
+ const item = isBlueprintList(field) ? recordFieldOf(field.item) : undefined;
28
+ const related = [record?.record, item?.record].find(isEntityClass);
29
+ const kind = record ? related ? "entity" : "record" : isBlueprintList(field) ? related ? "entity-list" : "list" : isBlueprint(field) || isBlueprintVariant(field) ? "blueprint" : "property";
30
+ let input = { key, field };
31
+ if (kind === "entity" && related) {
32
+ const id = related.blueprint.properties.id;
33
+ input = { key: `${key}Id`, field: optional ? optionalOf(id) : id };
34
+ }
35
+ const declared = kind === "property" ? propertyInputOf(field) : undefined;
36
+ if (declared) {
37
+ input = {
38
+ key,
39
+ field: optional ? optionalOf(declared.accepts) : declared.accepts
40
+ };
41
+ }
42
+ return Object.freeze({
43
+ key,
44
+ field,
45
+ identity: identityKeysOf(entity).includes(key),
46
+ plugin: owners.get(key),
47
+ sensitive: entity.options.sensitive.includes(key),
48
+ unique: entity.options.unique.includes(key),
49
+ optional,
50
+ change: entity.options.change.includes(key),
51
+ kind,
52
+ related,
53
+ input: Object.freeze(input),
54
+ convert: declared ? (value) => value === undefined ? undefined : declared.convert(value) : undefined
55
+ });
56
+ }
57
+ export function useCaseTargetOf(repository, source = "useCaseTargetOf") {
58
+ const state = findState(repository);
59
+ if (!state)
60
+ throw new TypeError("Expected a repository made by repositoryOf");
61
+ const { entity } = state;
62
+ const unavailable = (reason) => {
63
+ throw new UseCaseUnavailable({
64
+ source,
65
+ details: { repository: state.name, reason }
66
+ });
67
+ };
68
+ const owners = new Map;
69
+ for (const plugin of pluginsOf(entity)) {
70
+ for (const key of plugin.keys)
71
+ owners.set(key, plugin.name);
72
+ }
73
+ const fields = Object.freeze(Object.keys(entity.blueprint.properties).map((key) => planOf(entity, key, owners)));
74
+ const field = (key) => fields.find((plan) => plan.key === key) ?? unavailable(`the entity has no key "${key}"`);
75
+ return Object.freeze({
76
+ repository,
77
+ repositoryName: state.name,
78
+ entity,
79
+ entityName: recordNameOf(entity),
80
+ actions: state.actions,
81
+ deletion: state.deletion,
82
+ fields,
83
+ changeKeys: entity.options.change,
84
+ uniqueKeys: entity.options.unique,
85
+ field,
86
+ relation(key, actions = []) {
87
+ const { related } = field(key);
88
+ const found = related ? relatedRepositoryOf(repository, related) : undefined;
89
+ if (!found) {
90
+ return unavailable(`the repository has no relation for the entity of "${key}"`);
91
+ }
92
+ const missing = actions.filter((action) => !found.actions.includes(action));
93
+ if (missing.length > 0) {
94
+ unavailable(`the repository of "${key}" lacks the action ${missing.map((action) => `"${action}"`).join(", ")}`);
95
+ }
96
+ return found;
97
+ },
98
+ unavailable
99
+ });
100
+ }
@@ -0,0 +1,39 @@
1
+ import type { JsonValue } from "../../json.js";
2
+ /**
3
+ * Who is calling: what an authenticator made of the credentials of a request.
4
+ * `subject` identifies the caller (a user id), `scopes` is what it may do.
5
+ */
6
+ export interface Principal {
7
+ readonly subject: string;
8
+ readonly scopes: readonly string[];
9
+ /** Anything else the credentials say (a tenant, a role). */
10
+ readonly claims?: Readonly<Record<string, JsonValue>>;
11
+ }
12
+ /** A value of the principal: its subject, its scopes or one of its claims. */
13
+ type PrincipalValue = "subject" | "scopes" | `claims.${string}`;
14
+ /**
15
+ * Who may call a use case, as data: declared with the `access` setting of
16
+ * `useCaseOf` or with `@Access`, and enforced by every output before the use
17
+ * case runs. `{ public: true }` lets anyone in. Any other declaration needs
18
+ * an authenticated caller (`{}` asks for nothing more), and may add:
19
+ *
20
+ * - `scopes`: the caller has all of them;
21
+ * - `owner`: the input key `input` equals the subject of the caller, or its
22
+ * claim `claim`;
23
+ * - `context`: keys of the context of the call filled from the caller, over
24
+ * whatever the request sent.
25
+ */
26
+ export type AccessDeclaration = {
27
+ readonly public: true;
28
+ } | {
29
+ readonly public?: false;
30
+ readonly scopes?: readonly string[];
31
+ readonly owner?: {
32
+ readonly input: string;
33
+ readonly claim?: string;
34
+ };
35
+ readonly context?: {
36
+ readonly [contextKey: string]: PrincipalValue;
37
+ };
38
+ };
39
+ export {};
@@ -0,0 +1,86 @@
1
+ /** An HTTP method an operation can be routed to. */
2
+ export type RouteMethod = "get" | "post" | "put" | "patch" | "delete";
3
+ /** The status of a successful call of a route; `204` has no body. */
4
+ export type RouteSuccessStatus = 200 | 201 | 202 | 204;
5
+ /** How a cookie a route sets is sent. The defaults are the safe ones for a session. */
6
+ export interface RouteCookie {
7
+ /** The name of the cookie. */
8
+ readonly name: string;
9
+ /**
10
+ * Not readable by scripts of the page.
11
+ *
12
+ * @default true
13
+ */
14
+ readonly httpOnly?: boolean;
15
+ /**
16
+ * Sent over HTTPS only.
17
+ *
18
+ * @default true
19
+ */
20
+ readonly secure?: boolean;
21
+ /**
22
+ * The `SameSite` attribute of the cookie.
23
+ *
24
+ * @default "lax"
25
+ */
26
+ readonly sameSite?: "strict" | "lax" | "none";
27
+ /**
28
+ * The `Path` attribute of the cookie.
29
+ *
30
+ * @default "/"
31
+ */
32
+ readonly path?: string;
33
+ readonly domain?: string;
34
+ /** The seconds the cookie lives; without it, until the browser closes. */
35
+ readonly maxAge?: number;
36
+ }
37
+ /**
38
+ * Where a use case is served over HTTP, as data: declared with the `route`
39
+ * setting of `useCaseOf` or with `@Route`, and read by the sinks that serve
40
+ * or document HTTP. The blueprint of the use case stays the only description
41
+ * of its input: a route only says where each key comes from.
42
+ */
43
+ export interface RouteDeclaration {
44
+ readonly method: RouteMethod;
45
+ /**
46
+ * The path, after the `basePath` of the sink, e.g. `"/posts/{slug}"`. A
47
+ * `{name}` segment fills the scalar input key `name`. Without it, the path
48
+ * is the default of the sink: `/{kebab(command)}/{kebab(operation)}`.
49
+ */
50
+ readonly path?: string;
51
+ /**
52
+ * The status of a successful response.
53
+ *
54
+ * @default 200
55
+ */
56
+ readonly status?: RouteSuccessStatus;
57
+ /**
58
+ * Scalar input keys read from the query string. A `get` or `delete` route
59
+ * reads from the query every key that has no other place, so this is for
60
+ * the routes with a body.
61
+ */
62
+ readonly query?: readonly string[];
63
+ /** Scalar input keys read from request headers: `{ locale: "accept-language" }`. */
64
+ readonly headers?: {
65
+ readonly [inputKey: string]: string;
66
+ };
67
+ /** Scalar input keys read from cookies: `{ session: "sid" }`. */
68
+ readonly cookie?: {
69
+ readonly [inputKey: string]: string;
70
+ };
71
+ /**
72
+ * Scalar keys of the response sent as cookies instead of in the body:
73
+ * `{ token: { name: "sid" } }`. An empty text removes the cookie, and an
74
+ * absent value sets none.
75
+ */
76
+ readonly setCookie?: {
77
+ readonly [responseKey: string]: RouteCookie;
78
+ };
79
+ }
80
+ /**
81
+ * What the `route` setting and `@Route` take: a {@link RouteDeclaration}
82
+ * whose method may be written in upper case.
83
+ */
84
+ export interface RouteSettings extends Omit<RouteDeclaration, "method"> {
85
+ readonly method: RouteMethod | Uppercase<RouteMethod>;
86
+ }
@@ -5,7 +5,10 @@ import { type DeclaredExceptionClass } from "../exception/exception.js";
5
5
  import { type ILogger, type LoggerDeclaration } from "../logger/logger.js";
6
6
  import type { AnyRepository } from "../repository/types/repository.interface.js";
7
7
  import { Kind } from "../symbols/kind.js";
8
+ import type { CheckedSteps } from "./steps/types/check.interface.js";
8
9
  import type { UseCaseStep } from "./steps/types/steps.interface.js";
10
+ import type { AccessDeclaration } from "./types/access.interface.js";
11
+ import type { RouteDeclaration, RouteSettings } from "./types/route.interface.js";
9
12
  /** A concrete UseCase declaration, usable as a dependency. */
10
13
  export interface AnyUseCaseClass {
11
14
  readonly [Kind]: "use-case";
@@ -18,6 +21,10 @@ export interface AnyUseCaseClass {
18
21
  readonly tags: readonly string[];
19
22
  readonly deprecated: boolean;
20
23
  readonly errors: readonly AnyExceptionClass[];
24
+ /** Where it is served over HTTP, if it says so (see `RouteDeclaration`). */
25
+ readonly route: RouteDeclaration | undefined;
26
+ /** Who may call it, if it says so (see `AccessDeclaration`). */
27
+ readonly access: AccessDeclaration | undefined;
21
28
  new (input: any, ...options: any[]): {
22
29
  execute(): Promise<unknown>;
23
30
  pullEvents(): EntityEvent[];
@@ -30,14 +37,31 @@ export type AnyExceptionClass = DeclaredExceptionClass<any>;
30
37
  * `repositoryOf`, an environment made by `environmentOf`, or `Logger`.
31
38
  */
32
39
  export type UseCaseDependency = AnyUseCaseClass | AnyRepository | AnyEnvironment | LoggerDeclaration;
40
+ /**
41
+ * The `dependencies` of a use case, by name: use case classes, repositories,
42
+ * environments or the {@link Logger}.
43
+ */
33
44
  export type UseCaseCollection = Readonly<Record<string, UseCaseDependency>>;
34
45
  type Declaration = {
35
46
  readonly blueprint: AnyBlueprint;
36
47
  readonly response: AnyBlueprint;
37
48
  readonly dependencies: UseCaseCollection;
38
49
  };
50
+ /**
51
+ * The input of a use case class, as its constructor takes it: the input of its
52
+ * `blueprint`.
53
+ */
39
54
  export type UseCaseInput<Class extends Declaration> = BlueprintInput<Class["blueprint"]["properties"]>;
55
+ /**
56
+ * The value `execute` of a use case class resolves with: the value of its
57
+ * `response` blueprint.
58
+ */
40
59
  export type UseCaseResponse<Class extends Declaration> = BlueprintValue<Class["response"]["properties"]>;
60
+ /**
61
+ * The dependencies of a use case class as `this.dependencies` gives them: a
62
+ * function for a use case, the repository itself, the variables of an
63
+ * environment or an {@link ILogger} (see {@link DependencyOf}).
64
+ */
41
65
  export type UseCaseDependencies<Class extends Declaration> = DependencyFunctions<Class["dependencies"]>;
42
66
  /** The context of a use case class, as `this.context` gives it; `undefined` without one. */
43
67
  export type UseCaseContext<Class extends {
@@ -59,6 +83,10 @@ type OptionsArgs<Context extends IBlueprintCollection | undefined> = Context ext
59
83
  * repository itself, the variables of an environment, or an `ILogger`.
60
84
  */
61
85
  export type DependencyOf<Dependency> = Dependency extends AnyUseCaseClass ? (input: UseCaseInput<Dependency>) => Promise<UseCaseResponse<Dependency>> : Dependency extends AnyEnvironment ? EnvironmentValue<Dependency> : Dependency extends LoggerDeclaration ? ILogger : Dependency;
86
+ /**
87
+ * A collection of dependencies as a use case receives them: each one as {@link
88
+ * DependencyOf} gives it.
89
+ */
62
90
  export type DependencyFunctions<Dependencies extends UseCaseCollection> = {
63
91
  readonly [Key in keyof Dependencies]: DependencyOf<Dependencies[Key]>;
64
92
  };
@@ -69,6 +97,12 @@ export type DependencyFunctions<Dependencies extends UseCaseCollection> = {
69
97
  export type DependencyOverrides<Dependencies extends UseCaseCollection> = {
70
98
  readonly [Key in keyof Dependencies]?: Dependencies[Key] extends AnyRepository ? Partial<Dependencies[Key]> : DependencyOf<Dependencies[Key]>;
71
99
  };
100
+ /**
101
+ * What {@link useCaseOf} takes: the `name`, the `blueprint` of the input, the
102
+ * `response` blueprint, the `dependencies` and the `context`, with the
103
+ * documentation of the operation (`description`, `tags`, `deprecated`,
104
+ * `errors`) and where it is served and who may call it (`route`, `access`).
105
+ */
72
106
  export interface UseCaseSettings<Input extends IBlueprintCollection, Output extends IBlueprintCollection, Dependencies extends UseCaseCollection, Context extends IBlueprintCollection | undefined = undefined> {
73
107
  readonly name: string;
74
108
  readonly blueprint: IBlueprint<Input>;
@@ -81,6 +115,22 @@ export interface UseCaseSettings<Input extends IBlueprintCollection, Output exte
81
115
  readonly deprecated?: boolean;
82
116
  /** The exceptions (made by `exceptionOf`) a caller should expect; documentation only. */
83
117
  readonly errors?: readonly AnyExceptionClass[];
118
+ /**
119
+ * Where the use case is served over HTTP: method, path and where each
120
+ * input key is read from (see `RouteDeclaration`). A decorated class says
121
+ * the same with `@Route`. Read by the sinks that serve or document HTTP;
122
+ * the `route` option of a sink overrides the method, path and status.
123
+ */
124
+ readonly route?: RouteSettings;
125
+ /**
126
+ * Who may call the use case through an output: anyone (`{ public: true }`),
127
+ * or an authenticated caller with these `scopes`, that is the `owner`
128
+ * named by an input key, and whose values fill the `context` (see
129
+ * `AccessDeclaration`). A decorated class says the same with `@Access`.
130
+ * Enforced before the use case runs; a call made in process, with
131
+ * `execute` or `command.run`, is trusted and not checked.
132
+ */
133
+ readonly access?: AccessDeclaration;
84
134
  }
85
135
  /** `UseCaseSettings` with the `steps` that are its `execute`. */
86
136
  export interface UseCaseStepsSettings<Input extends IBlueprintCollection, Output extends IBlueprintCollection, Dependencies extends UseCaseCollection, Context extends IBlueprintCollection | undefined = undefined> extends UseCaseSettings<Input, Output, Dependencies, Context> {
@@ -89,11 +139,46 @@ export interface UseCaseStepsSettings<Input extends IBlueprintCollection, Output
89
139
  * interpreted by its `execute`, and described by `describe()`.
90
140
  */
91
141
  readonly steps: readonly UseCaseStep<NoInfer<Dependencies>>[];
142
+ /** Checks the references and values of the steps at compile time (see `CheckedSteps`). */
143
+ readonly strictSteps?: boolean;
92
144
  }
145
+ /**
146
+ * What `useCaseOf` takes: `UseCaseSettings`, and the `steps` that make the
147
+ * class concrete, if any.
148
+ */
149
+ export interface UseCaseDeclaration<Input extends IBlueprintCollection, Output extends IBlueprintCollection, Dependencies extends UseCaseCollection, Context extends IBlueprintCollection | undefined = undefined, Steps extends readonly UseCaseStep<Dependencies>[] | undefined = undefined, Strict extends boolean = false> extends UseCaseSettings<Input, Output, Dependencies, Context> {
150
+ /**
151
+ * What the use case does, as data (see `UseCaseStep`), which makes the
152
+ * class concrete: validated here, interpreted by its `execute`, and
153
+ * described by `describe()`. TypeScript checks the kinds of the steps and
154
+ * the names of dependencies and actions; with `strictSteps`, also their
155
+ * references and values, and the `return` against the response (see
156
+ * `CheckedSteps`).
157
+ */
158
+ readonly steps?: [Strict] extends [true] ? CheckedSteps<Steps, NoInfer<Input>, NoInfer<Context>, NoInfer<Dependencies>, NoInfer<Output>> : Steps;
159
+ /**
160
+ * `true` checks the steps, written inline, at compile time: each reference
161
+ * must name a value in scope (the input, the context, an environment or the
162
+ * result of an earlier step, at any depth), and the `args`, `values`,
163
+ * `value` and `input` of a step must fit what it calls or builds. It costs
164
+ * the editor more than the default checks. Without effect at run time.
165
+ */
166
+ readonly strictSteps?: Strict;
167
+ }
168
+ /**
169
+ * A use case, or a decorator of one, is declared with what it cannot use;
170
+ * `reason` says what.
171
+ */
93
172
  export declare const InvalidUseCaseDeclaration: DeclaredExceptionClass<{
94
173
  reason: string;
95
174
  }>;
175
+ /** `execute` was called twice on one instance of a use case, which runs only once. */
96
176
  export declare const UseCaseAlreadyExecuted: DeclaredExceptionClass<undefined>;
177
+ /**
178
+ * The overrides given to `execute` name a dependency the use case does not
179
+ * declare, or give the wrong kind of value for one; or a dependency was called
180
+ * after `execute` ended.
181
+ */
97
182
  export declare const InvalidUseCaseDependencies: DeclaredExceptionClass<undefined>;
98
183
  /** An operation with validated input, dependency functions and a drainable event queue. */
99
184
  export declare abstract class AbstractUseCase<Input extends IBlueprintCollection, Output extends IBlueprintCollection, Dependencies extends UseCaseCollection> {
@@ -101,6 +186,14 @@ export declare abstract class AbstractUseCase<Input extends IBlueprintCollection
101
186
  constructor(input: BlueprintInput<Input>, ...args: unknown[]);
102
187
  /** Default parameter for implementations: `execute(deps = this.dependencies)`. */
103
188
  get dependencies(): DependencyFunctions<Dependencies>;
189
+ /**
190
+ * Runs the operation and gives its response. `dependencies` replace the
191
+ * declared ones for this call: a function for a use case, some methods of a
192
+ * repository, the variables of an environment.
193
+ *
194
+ * @throws {@link UseCaseAlreadyExecuted} when this instance already executed.
195
+ * @throws {@link InvalidUseCaseDependencies} when an override names an undeclared dependency or is of the wrong kind.
196
+ */
104
197
  abstract execute(dependencies?: DependencyOverrides<Dependencies>): Promise<BlueprintValue<Output>>;
105
198
  /** Immediately drains collected events; never executes their handlers. */
106
199
  pullEvents(): EntityEvent[];
@@ -119,12 +212,21 @@ type UseCaseStatics<Input extends IBlueprintCollection, Output extends IBlueprin
119
212
  readonly deprecated: boolean;
120
213
  /** The exceptions a caller should expect. */
121
214
  readonly errors: readonly AnyExceptionClass[];
215
+ /** Where it is served over HTTP, from the `route` setting or `@Route`. */
216
+ readonly route: RouteDeclaration | undefined;
217
+ /** Who may call it, from the `access` setting or `@Access`. */
218
+ readonly access: AccessDeclaration | undefined;
122
219
  };
123
220
  /** What a use case instance carries beyond `AbstractUseCase`. */
124
221
  type UseCaseInstanceContext<Context extends IBlueprintCollection | undefined> = {
125
222
  /** What the caller supplied with the call, parsed and frozen; `undefined` without a declared context. */
126
223
  readonly context: Context extends IBlueprintCollection ? Readonly<BlueprintValue<Context>> : undefined;
127
224
  };
225
+ /**
226
+ * What {@link useCaseOf} gives without `steps`: an abstract class to extend,
227
+ * implementing an async `execute`. Its constructor takes the input and, when
228
+ * the use case declares a context, `{ context }`.
229
+ */
128
230
  export type UseCaseConstructor<Input extends IBlueprintCollection, Output extends IBlueprintCollection, Dependencies extends UseCaseCollection, Context extends IBlueprintCollection | undefined = undefined> = (abstract new (input: BlueprintInput<Input>, ...options: OptionsArgs<Context>) => AbstractUseCase<Input, Output, Dependencies> & UseCaseInstanceContext<Context>) & UseCaseStatics<Input, Output, Dependencies, Context>;
129
231
  /**
130
232
  * What `useCaseOf` gives with `steps`: concrete, its `execute` interprets the
@@ -137,9 +239,32 @@ export type StepsUseCaseConstructor<Input extends IBlueprintCollection, Output e
137
239
  * Declares an operation. With `steps` the class is concrete: its `execute`
138
240
  * interprets them. Without, it is abstract: extend it and implement an async
139
241
  * execute method.
242
+ *
243
+ * @throws {@link InvalidUseCaseDeclaration} when the name, the blueprints or the dependencies are missing or of the wrong kind, or a setting is not valid.
244
+ *
245
+ * @example
246
+ * ```ts
247
+ * import { blueprintOf, useCaseOf } from "@afora/core";
248
+ *
249
+ * class Greet extends useCaseOf({
250
+ * name: "Greet",
251
+ * blueprint: blueprintOf((ctx) => ({ name: ctx.text })),
252
+ * response: blueprintOf((ctx) => ({ message: ctx.string })),
253
+ * }) {
254
+ * override async execute() {
255
+ * return { message: `Hello, ${this.input.name}!` };
256
+ * }
257
+ * }
258
+ *
259
+ * const { message } = await new Greet({ name: "Ana" }).execute();
260
+ * ```
261
+ */
262
+ export declare function useCaseOf<Input extends IBlueprintCollection, Output extends IBlueprintCollection, const Dependencies extends UseCaseCollection = Record<never, never>, Context extends IBlueprintCollection | undefined = undefined, const Steps extends readonly UseCaseStep<NoInfer<Dependencies>>[] | undefined = undefined, Strict extends boolean = false>(settings: UseCaseDeclaration<Input, Output, Dependencies, Context, Steps, Strict>): [Steps] extends [undefined] ? UseCaseConstructor<Input, Output, Dependencies, Context> : StepsUseCaseConstructor<Input, Output, Dependencies, Context>;
263
+ /**
264
+ * Whether `value` is a use case class made by {@link useCaseOf}, abstract or
265
+ * concrete, or a subclass of one.
140
266
  */
141
- export declare function useCaseOf<Input extends IBlueprintCollection, Output extends IBlueprintCollection, const Dependencies extends UseCaseCollection = Record<never, never>, Context extends IBlueprintCollection | undefined = undefined>(settings: UseCaseStepsSettings<Input, Output, Dependencies, Context>): StepsUseCaseConstructor<Input, Output, Dependencies, Context>;
142
- export declare function useCaseOf<Input extends IBlueprintCollection, Output extends IBlueprintCollection, const Dependencies extends UseCaseCollection = Record<never, never>, Context extends IBlueprintCollection | undefined = undefined>(settings: UseCaseSettings<Input, Output, Dependencies, Context>): UseCaseConstructor<Input, Output, Dependencies, Context>;
143
267
  export declare function isUseCaseClass(value: unknown): value is AnyUseCaseClass;
268
+ /** Whether `value` is an instance of a use case class. */
144
269
  export declare function isUseCase(value: unknown): value is AbstractUseCase<IBlueprintCollection, IBlueprintCollection, UseCaseCollection>;
145
270
  export {};