@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,428 @@
1
+ import { isBlueprint } from "../blueprint/is-blueprint.js";
2
+ import {
3
+ isEnvironment,
4
+ loadEnvironments
5
+ } from "../environment/environment.js";
6
+ import { resolvePublisher } from "../event-policy/publishers.js";
7
+ import { ExceptionLayers } from "../exception/enums/exception-layers.js";
8
+ import { Exception } from "../exception/exception.js";
9
+ import { guard } from "../exception/guard.js";
10
+ import {
11
+ clock,
12
+ elapsed,
13
+ logFailure,
14
+ resolveLogger,
15
+ silentLogger,
16
+ writeLog
17
+ } from "../logger/logger.js";
18
+ import { isRepository } from "../repository/repository.js";
19
+ import { findState } from "../repository/state.js";
20
+ import { Kind } from "../symbols/kind.js";
21
+ import {
22
+ endSpan,
23
+ failSpan,
24
+ resolveTracer,
25
+ runInSpan,
26
+ startSpan
27
+ } from "../tracing/tracing.js";
28
+ import { isUseCaseCatalog } from "../use-case/plugin-catalog.js";
29
+ import { ScopeKey } from "../use-case/scope.js";
30
+ import { isUseCaseClass } from "../use-case/use-case.js";
31
+ import {
32
+ CommandEventFailed,
33
+ InvalidCommandContext,
34
+ InvalidCommandDeclaration,
35
+ InvalidCommandEnvironment,
36
+ UnknownCommandOperation
37
+ } from "./exceptions.js";
38
+ const verifiers = new WeakMap;
39
+ export function verifyCommand(command) {
40
+ verifiers.get(command)?.();
41
+ }
42
+ export function commandTransactionProvider(command) {
43
+ return verifiers.get(command)?.();
44
+ }
45
+ const IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
46
+ function repositoryProvidersOf(useCases, declared) {
47
+ const providers = new Map;
48
+ const seen = new Set;
49
+ const add = (state) => {
50
+ if (declared && !state.explicitProvider)
51
+ return;
52
+ const { provider, name } = state;
53
+ const names = providers.get(provider) ?? [];
54
+ if (!names.includes(name))
55
+ names.push(name);
56
+ providers.set(provider, names);
57
+ };
58
+ const visit = (UseCase) => {
59
+ if (seen.has(UseCase))
60
+ return;
61
+ seen.add(UseCase);
62
+ for (const dependency of Object.values(UseCase.dependencies)) {
63
+ if (isUseCaseClass(dependency)) {
64
+ visit(dependency);
65
+ } else if (isRepository(dependency)) {
66
+ const state = findState(dependency);
67
+ if (!state)
68
+ continue;
69
+ add(state);
70
+ for (const related of state.relations.values())
71
+ add(related);
72
+ }
73
+ }
74
+ };
75
+ for (const UseCase of Object.values(useCases))
76
+ visit(UseCase);
77
+ return providers;
78
+ }
79
+ function transactionProviderOf(name, useCases, declared = false) {
80
+ const providers = repositoryProvidersOf(useCases, declared);
81
+ const invalid = (reason, repositories) => {
82
+ throw new InvalidCommandDeclaration({
83
+ source: name,
84
+ details: { reason, repositories }
85
+ });
86
+ };
87
+ if (providers.size > 1) {
88
+ const repositories = [...providers.values()].flat();
89
+ invalid(`A transaction needs every repository to share one provider; found ${providers.size} (${repositories.join(", ")}).`, repositories);
90
+ }
91
+ const [entry] = providers;
92
+ if (!entry)
93
+ return;
94
+ const [provider, repositories] = entry;
95
+ if (typeof provider.transaction !== "function" || provider.capabilities?.transactions === false) {
96
+ invalid(`The provider of ${repositories.join(", ")} does not support transactions.`, repositories);
97
+ }
98
+ return provider;
99
+ }
100
+ function narrowed(UseCase, context) {
101
+ if (!UseCase.context || typeof context !== "object" || context === null) {
102
+ return;
103
+ }
104
+ const picked = {};
105
+ for (const key of Object.keys(UseCase.context.properties)) {
106
+ if (Object.hasOwn(context, key)) {
107
+ picked[key] = context[key];
108
+ }
109
+ }
110
+ return picked;
111
+ }
112
+ function callEnvironment(source, environments, given) {
113
+ if (typeof given !== "object" || given === null || Array.isArray(given)) {
114
+ throw new InvalidCommandEnvironment({
115
+ source,
116
+ details: { keys: [], issues: [] }
117
+ });
118
+ }
119
+ const values = given;
120
+ const unknownKeys = Object.keys(values).filter((key) => !environments.some((environment) => Object.hasOwn(environment.blueprint.properties, key)));
121
+ if (unknownKeys.length > 0) {
122
+ throw new InvalidCommandEnvironment({
123
+ source,
124
+ details: { keys: unknownKeys, issues: [] }
125
+ });
126
+ }
127
+ const byEnvironment = new Map;
128
+ const merged = {};
129
+ for (const environment of environments) {
130
+ const keys = Object.keys(environment.blueprint.properties);
131
+ const picked = {};
132
+ for (const key of keys) {
133
+ if (Object.hasOwn(values, key))
134
+ picked[key] = values[key];
135
+ }
136
+ const complete = keys.every((key) => Object.hasOwn(picked, key));
137
+ const base = Object.keys(picked).length === 0 || !complete ? environment.load() : {};
138
+ if (Object.keys(picked).length === 0) {
139
+ Object.assign(merged, base);
140
+ continue;
141
+ }
142
+ const parsed = environment.blueprint.safeParse({ ...base, ...picked });
143
+ if (parsed.issues) {
144
+ throw new InvalidCommandEnvironment({
145
+ source,
146
+ details: {
147
+ keys: [
148
+ ...new Set(parsed.issues.map((issue) => String(issue.path[0] ?? "")))
149
+ ],
150
+ issues: parsed.issues
151
+ },
152
+ cause: parsed.issues.find((issue) => issue.cause)?.cause
153
+ });
154
+ }
155
+ const value = Object.freeze(parsed.value);
156
+ byEnvironment.set(environment, value);
157
+ Object.assign(merged, value);
158
+ }
159
+ return { byEnvironment, merged: Object.freeze(merged) };
160
+ }
161
+ function declareCommand(given, generics) {
162
+ const { name, description, context, onEventError } = given;
163
+ const source = typeof name === "string" ? name : "commandOf";
164
+ const picked = given.useCases;
165
+ const useCases = typeof picked === "function" && !isUseCaseClass(picked) ? guard({ layer: ExceptionLayers.Command, source }, () => picked(generics)) : picked;
166
+ const settings = given;
167
+ const invalid = (reason) => {
168
+ throw new InvalidCommandDeclaration({ source, details: { reason } });
169
+ };
170
+ if (typeof name !== "string" || !name.trim()) {
171
+ invalid("Expected name to be a non-empty string.");
172
+ }
173
+ if (description !== undefined && typeof description !== "string") {
174
+ invalid("Expected description to be a string.");
175
+ }
176
+ const tags = settings.tags === undefined ? [] : settings.tags;
177
+ if (!Array.isArray(tags) || tags.some((tag) => typeof tag !== "string") || new Set(tags).size !== tags.length) {
178
+ invalid("Expected tags to be distinct strings.");
179
+ }
180
+ if (context !== undefined && (!isBlueprint(context) || context.optional)) {
181
+ invalid("Expected context to be a non-optional blueprint.");
182
+ }
183
+ if (typeof useCases !== "object" || useCases === null || Array.isArray(useCases) || Reflect.ownKeys(useCases).length === 0) {
184
+ invalid("Expected useCases to be a non-empty object of use case classes, or a function returning one.");
185
+ }
186
+ for (const key of Reflect.ownKeys(useCases)) {
187
+ if (typeof key !== "string" || !IDENTIFIER.test(key) || !isUseCaseClass(useCases[key])) {
188
+ invalid("Expected the operations to be identifiers holding use case classes.");
189
+ }
190
+ }
191
+ const transaction = settings.transaction === undefined ? false : settings.transaction;
192
+ if (typeof transaction !== "boolean") {
193
+ invalid("Expected transaction to be a boolean.");
194
+ }
195
+ const declaredEnvironments = settings.environmentDependencies;
196
+ const environments = Object.freeze(declaredEnvironments === undefined ? [] : isEnvironment(declaredEnvironments) ? [declaredEnvironments] : Array.isArray(declaredEnvironments) ? [...declaredEnvironments] : [null]);
197
+ if (!environments.every(isEnvironment) || new Set(environments).size !== environments.length) {
198
+ invalid("Expected environmentDependencies to be distinct environments made by environmentOf.");
199
+ }
200
+ const events = settings.events === undefined ? "after-commit" : settings.events;
201
+ if (events !== "after-commit" && events !== "manual" && typeof events !== "function") {
202
+ invalid('Expected events to be "after-commit", "manual" or a function.');
203
+ }
204
+ if (onEventError !== undefined) {
205
+ if (typeof onEventError !== "function") {
206
+ invalid("Expected onEventError to be a function.");
207
+ }
208
+ if (events !== "after-commit") {
209
+ invalid('onEventError only applies to the "after-commit" policy.');
210
+ }
211
+ }
212
+ if (transaction)
213
+ transactionProviderOf(source, useCases, true);
214
+ let resolved;
215
+ const providerOfCall = () => {
216
+ if (!transaction)
217
+ return;
218
+ resolved ??= { provider: transactionProviderOf(source, useCases) };
219
+ return resolved.provider;
220
+ };
221
+ const operations = Object.freeze(Object.keys(useCases));
222
+ const frozenUseCases = Object.freeze({ ...useCases });
223
+ const perform = async (op, input, options, keepSpan) => {
224
+ if (typeof op !== "string" || !Object.hasOwn(frozenUseCases, op)) {
225
+ throw new UnknownCommandOperation({
226
+ source,
227
+ details: { operation: String(op) }
228
+ });
229
+ }
230
+ const where = `${source}.${op}`;
231
+ const UseCase = frozenUseCases[op];
232
+ const tracer = resolveTracer(undefined, UseCase);
233
+ const span = tracer === undefined ? undefined : startSpan(tracer, `command ${where}`, { "afora.command": where, "afora.operation": op }, undefined);
234
+ let callContext = options?.context;
235
+ let given = callContext;
236
+ let overrides;
237
+ try {
238
+ if (context) {
239
+ const checked = context.safeParse(callContext);
240
+ if (checked.issues) {
241
+ throw new InvalidCommandContext({
242
+ source: where,
243
+ details: { issues: checked.issues },
244
+ cause: checked.issues.find((issue) => issue.cause)?.cause
245
+ });
246
+ }
247
+ callContext = checked.value;
248
+ given = narrowed(UseCase, callContext);
249
+ }
250
+ overrides = options?.env === undefined ? undefined : callEnvironment(where, environments, options.env);
251
+ } catch (error) {
252
+ failSpan(span, error, ExceptionLayers.Command);
253
+ throw error;
254
+ }
255
+ const logger = resolveLogger(undefined, UseCase);
256
+ const withEnvironments = (scope) => {
257
+ const environmentsOfCall = overrides === undefined || overrides.byEnvironment.size === 0 ? scope?.environments : overrides.byEnvironment;
258
+ if (!environmentsOfCall && !logger && !tracer)
259
+ return scope;
260
+ return {
261
+ ...scope,
262
+ providers: scope?.providers ?? new Map,
263
+ ...environmentsOfCall ? { environments: environmentsOfCall } : {},
264
+ ...logger ? { logger } : {},
265
+ ...tracer ? { tracer, ...span ? { span } : {} } : {}
266
+ };
267
+ };
268
+ const run = async (declared) => {
269
+ const scope = withEnvironments(declared);
270
+ const useCase = new UseCase(input, { context: given, [ScopeKey]: scope });
271
+ const value = await useCase.execute();
272
+ return { value, events: useCase.pullEvents() };
273
+ };
274
+ const provider = providerOfCall();
275
+ const started = clock();
276
+ const outcome = await guard({ layer: ExceptionLayers.Command, source: where }, span === undefined || tracer === undefined ? () => provider?.transaction ? provider.transaction((scoped) => run({ providers: new Map([[provider, scoped]]) })) : run(undefined) : () => runInSpan(tracer, span, () => provider?.transaction ? provider.transaction((scoped) => run({ providers: new Map([[provider, scoped]]) })) : run(undefined))).catch((error) => {
277
+ logFailure(logger, "command-failed", { command: where, ms: elapsed(started) }, error);
278
+ failSpan(span, error, ExceptionLayers.Command);
279
+ throw error;
280
+ });
281
+ writeLog(logger, "debug", "command", {
282
+ command: where,
283
+ ms: elapsed(started)
284
+ });
285
+ if (!keepSpan)
286
+ endSpan(span);
287
+ return {
288
+ ...outcome,
289
+ context: callContext,
290
+ env: () => overrides?.merged ?? loadEnvironments(environments),
291
+ logger: logger ?? silentLogger,
292
+ tracer,
293
+ span
294
+ };
295
+ };
296
+ const command = {
297
+ [Kind]: "command",
298
+ commandName: name,
299
+ description,
300
+ tags: Object.freeze([...tags]),
301
+ context,
302
+ useCases: frozenUseCases,
303
+ transaction,
304
+ environments,
305
+ get env() {
306
+ return loadEnvironments(environments);
307
+ },
308
+ events,
309
+ onEventError,
310
+ operations,
311
+ async execute(op, input, options) {
312
+ const { value, events } = await perform(op, input, options, false);
313
+ return { value, events };
314
+ },
315
+ async run(op, input, options) {
316
+ const {
317
+ value,
318
+ events: raised,
319
+ context: callContext,
320
+ env,
321
+ logger,
322
+ tracer,
323
+ span
324
+ } = await perform(op, input, options, true);
325
+ const call = {
326
+ operation: op,
327
+ raised,
328
+ context: callContext,
329
+ env,
330
+ logger
331
+ };
332
+ if (span === undefined || tracer === undefined) {
333
+ await applyPolicy(command, events, onEventError, call);
334
+ return value;
335
+ }
336
+ try {
337
+ await runInSpan(tracer, span, () => applyPolicy(command, events, onEventError, call));
338
+ } catch (error) {
339
+ failSpan(span, error, ExceptionLayers.Command);
340
+ throw error;
341
+ }
342
+ endSpan(span);
343
+ return value;
344
+ }
345
+ };
346
+ Object.freeze(command);
347
+ verifiers.set(command, providerOfCall);
348
+ return command;
349
+ }
350
+ export function commandBuilderOf(...catalogs) {
351
+ if (!catalogs.every(isUseCaseCatalog)) {
352
+ throw new InvalidCommandDeclaration({
353
+ source: "commandBuilderOf",
354
+ details: {
355
+ reason: "Expected catalogs made by useCaseCatalogOf."
356
+ }
357
+ });
358
+ }
359
+ const generics = Object.freeze(Object.assign({}, ...catalogs.map((catalog) => catalog.context)));
360
+ return (settings) => declareCommand(settings, generics);
361
+ }
362
+ export const commandOf = commandBuilderOf();
363
+ async function applyPolicy(command, policy, onEventError, call) {
364
+ const { operation, raised } = call;
365
+ const source = `${command.commandName}.${operation}`;
366
+ if (policy === "manual")
367
+ return;
368
+ const info = {
369
+ command,
370
+ operation,
371
+ context: call.context,
372
+ get env() {
373
+ return call.env();
374
+ },
375
+ logger: call.logger,
376
+ publisher: (name) => resolvePublisher(command, name, source)
377
+ };
378
+ if (typeof policy === "function") {
379
+ try {
380
+ await policy(raised, info);
381
+ } catch (cause) {
382
+ if (cause instanceof Exception && cause.name === "CommandEventFailed") {
383
+ throw cause;
384
+ }
385
+ throw new CommandEventFailed({
386
+ source,
387
+ cause,
388
+ details: { operation, handled: 0, remaining: raised.length }
389
+ });
390
+ }
391
+ return;
392
+ }
393
+ for (const [index, event] of raised.entries()) {
394
+ try {
395
+ await event.handle({ logger: call.logger });
396
+ } catch (cause) {
397
+ if (!onEventError) {
398
+ throw new CommandEventFailed({
399
+ source,
400
+ cause,
401
+ details: {
402
+ operation,
403
+ event: event.name,
404
+ handled: index,
405
+ remaining: raised.length - index - 1
406
+ }
407
+ });
408
+ }
409
+ try {
410
+ await onEventError(cause, event, info);
411
+ } catch (failure) {
412
+ throw new CommandEventFailed({
413
+ source,
414
+ cause: failure,
415
+ details: {
416
+ operation,
417
+ event: event.name,
418
+ handled: index,
419
+ remaining: raised.length - index - 1
420
+ }
421
+ });
422
+ }
423
+ }
424
+ }
425
+ }
426
+ export function isCommand(value) {
427
+ return typeof value === "object" && value !== null && value[Kind] === "command";
428
+ }
@@ -0,0 +1,30 @@
1
+ import { ExceptionLayers } from "../exception/enums/exception-layers.js";
2
+ import { exceptionOf } from "../exception/exception.js";
3
+ export const InvalidCommandDeclaration = exceptionOf({
4
+ layer: ExceptionLayers.Command,
5
+ name: "InvalidCommandDeclaration",
6
+ message: ({ reason }) => reason
7
+ });
8
+ export const UnknownCommandOperation = exceptionOf({
9
+ layer: ExceptionLayers.Command,
10
+ name: "UnknownCommandOperation",
11
+ message: ({ operation }) => `Unknown command operation "${operation}".`
12
+ });
13
+ export const InvalidCommandContext = exceptionOf({
14
+ layer: ExceptionLayers.Command,
15
+ name: "InvalidCommandContext",
16
+ message: (_details) => "Invalid Command context."
17
+ });
18
+ export const InvalidCommandEnvironment = exceptionOf({
19
+ layer: ExceptionLayers.Command,
20
+ name: "InvalidCommandEnvironment",
21
+ message: (_details) => "Invalid Command environment."
22
+ });
23
+ export const CommandEventFailed = exceptionOf({
24
+ layer: ExceptionLayers.Command,
25
+ name: "CommandEventFailed",
26
+ message: ({
27
+ operation,
28
+ event
29
+ }) => event === undefined ? `The event handler of "${operation}" failed after the call succeeded.` : `The event "${event}" of "${operation}" failed after the call succeeded.`
30
+ });
@@ -37,6 +37,11 @@ export type CommandEventHandler = (events: readonly EntityEvent[], info: Command
37
37
  export type CommandEvents = "after-commit" | "manual" | CommandEventHandler;
38
38
  /** Called when an event fails with the `"after-commit"` policy; the next event still runs. */
39
39
  export type CommandEventErrorHandler = (error: unknown, event: EntityEvent, info: CommandEventInfo) => void | Promise<void>;
40
+ /**
41
+ * What {@link commandOf} takes: the `name` and the `useCases` of the command,
42
+ * the `context` a call supplies, and how a call runs (`transaction`,
43
+ * `environmentDependencies`, `events` and `onEventError`).
44
+ */
40
45
  export interface CommandSettings<UseCases extends CommandUseCases, Context extends IBlueprintCollection | undefined = undefined, Environment extends EnvironmentDependencies | undefined = undefined> {
41
46
  readonly name: string;
42
47
  readonly description?: string;
@@ -44,7 +49,11 @@ export interface CommandSettings<UseCases extends CommandUseCases, Context exten
44
49
  /** What the caller supplies with each call; validated once by this blueprint. */
45
50
  readonly context?: IBlueprint<Exclude<Context, undefined>>;
46
51
  readonly useCases: UseCases;
47
- /** Runs every call in a transaction of the provider of the repositories. Default `false`. */
52
+ /**
53
+ * Runs every call in a transaction of the provider of the repositories.
54
+ *
55
+ * @default false
56
+ */
48
57
  readonly transaction?: boolean;
49
58
  /**
50
59
  * One environment, or a list merged in order: read on first use as
@@ -1,4 +1,5 @@
1
1
  import type { BlueprintInput, BlueprintValue, IBlueprintCollection } from "../blueprint/types/blueprint.interface.js";
2
+ import type { Complete } from "../parse/parse.js";
2
3
  import { AbstractRecord } from "../record/abstract-record.js";
3
4
  import { type EntityEvent, type EventDefinition } from "./event.js";
4
5
  import type { EntityIdentityKey } from "./types/entity.interface.js";
@@ -25,6 +26,11 @@ export declare const EntityLifetime: unique symbol;
25
26
  export declare const EXPIRES_AT = "expiresAt";
26
27
  /** The key a versioned entity adds to its identity. */
27
28
  export declare const ENTITY_VERSION_KEY = "version";
29
+ /**
30
+ * The key of each stored row holding its schema version, for entities
31
+ * declared with `schemaVersion`; it is never a key of the entity.
32
+ */
33
+ export declare const SCHEMA_VERSION_KEY = "$v";
28
34
  /** Whether `entityClass` was declared with `versioned: true`. */
29
35
  export declare function isVersionedEntityClass(entityClass: unknown): boolean;
30
36
  /**
@@ -38,6 +44,24 @@ export declare function identityKeysOf(entityClass: unknown): readonly string[];
38
44
  * a write succeeded. @internal
39
45
  */
40
46
  export declare function storeEntityVersion(entity: object, version: number): void;
47
+ /**
48
+ * Under this key, a class made by `entityOf` holds what makes its id, its
49
+ * `createdAt` and its `expiresAt` once its value is known to be valid.
50
+ *
51
+ * @internal
52
+ */
53
+ export declare const EntityComplete: unique symbol;
54
+ /**
55
+ * What an entity class does with the values its builder vouched for and left
56
+ * out, after the rest of its value is known to be valid: `createdAt` first,
57
+ * then `id`, as they were made before; and, for a class that expires, an
58
+ * `expiresAt` `lifetime` milliseconds after `createdAt`, which is still
59
+ * checked.
60
+ */
61
+ export declare function completeOf(clock: {
62
+ readonly generateId: () => string;
63
+ readonly now: () => string;
64
+ }, lifetime: number | undefined): Complete;
41
65
  /**
42
66
  * A record with identity: an `id` and a `createdAt`, made when it is built
43
67
  * and never changed after, an `updatedAt` set by each change, and a