ddd-ts-kit 0.15.0 → 0.16.1

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 (144) hide show
  1. package/README.fr.md +1594 -0
  2. package/README.md +426 -6
  3. package/dist/check/catalogue/architecture-rules.d.ts.map +1 -1
  4. package/dist/check/catalogue/architecture-rules.js +2 -0
  5. package/dist/check/catalogue/architecture-rules.js.map +1 -1
  6. package/dist/check/explain/domain-docs.d.ts +3 -0
  7. package/dist/check/explain/domain-docs.d.ts.map +1 -0
  8. package/dist/check/explain/domain-docs.js +30 -0
  9. package/dist/check/explain/domain-docs.js.map +1 -0
  10. package/dist/check/explain/rule-docs.d.ts.map +1 -1
  11. package/dist/check/explain/rule-docs.js +2 -0
  12. package/dist/check/explain/rule-docs.js.map +1 -1
  13. package/dist/check/fixtures/fixtures.d.ts.map +1 -1
  14. package/dist/check/fixtures/fixtures.js +4 -0
  15. package/dist/check/fixtures/fixtures.js.map +1 -1
  16. package/dist/check/fixtures/model-function-fixtures.d.ts +4 -0
  17. package/dist/check/fixtures/model-function-fixtures.d.ts.map +1 -0
  18. package/dist/check/fixtures/model-function-fixtures.js +52 -0
  19. package/dist/check/fixtures/model-function-fixtures.js.map +1 -0
  20. package/dist/check/fixtures/value-object-file-fixtures.d.ts +4 -0
  21. package/dist/check/fixtures/value-object-file-fixtures.d.ts.map +1 -0
  22. package/dist/check/fixtures/value-object-file-fixtures.js +54 -0
  23. package/dist/check/fixtures/value-object-file-fixtures.js.map +1 -0
  24. package/dist/check/fixtures/value-object-files.d.ts +34 -0
  25. package/dist/check/fixtures/value-object-files.d.ts.map +1 -0
  26. package/dist/check/fixtures/value-object-files.js +46 -0
  27. package/dist/check/fixtures/value-object-files.js.map +1 -0
  28. package/dist/check/rules/domain-value-object-class.d.ts +3 -0
  29. package/dist/check/rules/domain-value-object-class.d.ts.map +1 -0
  30. package/dist/check/rules/domain-value-object-class.js +21 -0
  31. package/dist/check/rules/domain-value-object-class.js.map +1 -0
  32. package/dist/check/structure/class-body-brace.d.ts +6 -0
  33. package/dist/check/structure/class-body-brace.d.ts.map +1 -0
  34. package/dist/check/structure/class-body-brace.js +22 -0
  35. package/dist/check/structure/class-body-brace.js.map +1 -0
  36. package/dist/check/structure/read-structure.d.ts.map +1 -1
  37. package/dist/check/structure/read-structure.js +3 -1
  38. package/dist/check/structure/read-structure.js.map +1 -1
  39. package/dist/check/values/answered-literals.d.ts +8 -0
  40. package/dist/check/values/answered-literals.d.ts.map +1 -0
  41. package/dist/check/values/answered-literals.js +20 -0
  42. package/dist/check/values/answered-literals.js.map +1 -0
  43. package/dist/check/values/arrow-body.d.ts +4 -0
  44. package/dist/check/values/arrow-body.d.ts.map +1 -0
  45. package/dist/check/values/arrow-body.js +52 -0
  46. package/dist/check/values/arrow-body.js.map +1 -0
  47. package/dist/check/values/entry-role.d.ts +10 -0
  48. package/dist/check/values/entry-role.d.ts.map +1 -0
  49. package/dist/check/values/entry-role.js +40 -0
  50. package/dist/check/values/entry-role.js.map +1 -0
  51. package/dist/check/values/exported-function-body.d.ts +9 -0
  52. package/dist/check/values/exported-function-body.d.ts.map +1 -0
  53. package/dist/check/values/exported-function-body.js +29 -0
  54. package/dist/check/values/exported-function-body.js.map +1 -0
  55. package/dist/check/values/exported-values.d.ts +9 -0
  56. package/dist/check/values/exported-values.d.ts.map +1 -0
  57. package/dist/check/values/exported-values.js +48 -0
  58. package/dist/check/values/exported-values.js.map +1 -0
  59. package/dist/check/values/extends-value-object.d.ts +9 -0
  60. package/dist/check/values/extends-value-object.d.ts.map +1 -0
  61. package/dist/check/values/extends-value-object.js +25 -0
  62. package/dist/check/values/extends-value-object.js.map +1 -0
  63. package/dist/check/values/function-body.d.ts +10 -0
  64. package/dist/check/values/function-body.d.ts.map +1 -0
  65. package/dist/check/values/function-body.js +2 -0
  66. package/dist/check/values/function-body.js.map +1 -0
  67. package/dist/check/values/generate-hint.d.ts +4 -0
  68. package/dist/check/values/generate-hint.d.ts.map +1 -0
  69. package/dist/check/values/generate-hint.js +8 -0
  70. package/dist/check/values/generate-hint.js.map +1 -0
  71. package/dist/check/values/initializer-of.d.ts +3 -0
  72. package/dist/check/values/initializer-of.d.ts.map +1 -0
  73. package/dist/check/values/initializer-of.js +15 -0
  74. package/dist/check/values/initializer-of.js.map +1 -0
  75. package/dist/check/values/is-model-file.d.ts +8 -0
  76. package/dist/check/values/is-model-file.d.ts.map +1 -0
  77. package/dist/check/values/is-model-file.js +15 -0
  78. package/dist/check/values/is-model-file.js.map +1 -0
  79. package/dist/check/values/is-value-object-file.d.ts +7 -0
  80. package/dist/check/values/is-value-object-file.d.ts.map +1 -0
  81. package/dist/check/values/is-value-object-file.js +14 -0
  82. package/dist/check/values/is-value-object-file.js.map +1 -0
  83. package/dist/check/values/javascript-globals.d.ts +3 -0
  84. package/dist/check/values/javascript-globals.d.ts.map +1 -0
  85. package/dist/check/values/javascript-globals.js +22 -0
  86. package/dist/check/values/javascript-globals.js.map +1 -0
  87. package/dist/check/values/literal-at.d.ts +3 -0
  88. package/dist/check/values/literal-at.d.ts.map +1 -0
  89. package/dist/check/values/literal-at.js +7 -0
  90. package/dist/check/values/literal-at.js.map +1 -0
  91. package/dist/check/values/local-declaration.d.ts +8 -0
  92. package/dist/check/values/local-declaration.d.ts.map +1 -0
  93. package/dist/check/values/local-declaration.js +31 -0
  94. package/dist/check/values/local-declaration.js.map +1 -0
  95. package/dist/check/values/nested-bodies.d.ts +8 -0
  96. package/dist/check/values/nested-bodies.d.ts.map +1 -0
  97. package/dist/check/values/nested-bodies.js +28 -0
  98. package/dist/check/values/nested-bodies.js.map +1 -0
  99. package/dist/check/values/plain-value-functions.d.ts +10 -0
  100. package/dist/check/values/plain-value-functions.d.ts.map +1 -0
  101. package/dist/check/values/plain-value-functions.js +33 -0
  102. package/dist/check/values/plain-value-functions.js.map +1 -0
  103. package/dist/check/values/returns-package-type.d.ts +9 -0
  104. package/dist/check/values/returns-package-type.d.ts.map +1 -0
  105. package/dist/check/values/returns-package-type.js +12 -0
  106. package/dist/check/values/returns-package-type.js.map +1 -0
  107. package/dist/check/values/stands-for-a-value.d.ts +9 -0
  108. package/dist/check/values/stands-for-a-value.d.ts.map +1 -0
  109. package/dist/check/values/stands-for-a-value.js +28 -0
  110. package/dist/check/values/stands-for-a-value.js.map +1 -0
  111. package/dist/check/values/value-object-file-findings.d.ts +5 -0
  112. package/dist/check/values/value-object-file-findings.d.ts.map +1 -0
  113. package/dist/check/values/value-object-file-findings.js +96 -0
  114. package/dist/check/values/value-object-file-findings.js.map +1 -0
  115. package/dist/commands/init.d.ts.map +1 -1
  116. package/dist/commands/init.js +1 -0
  117. package/dist/commands/init.js.map +1 -1
  118. package/dist/config/index.d.ts +1 -1
  119. package/dist/config/index.d.ts.map +1 -1
  120. package/dist/config/index.js +1 -1
  121. package/dist/config/index.js.map +1 -1
  122. package/dist/config/parse-stack.d.ts.map +1 -1
  123. package/dist/config/parse-stack.js +2 -1
  124. package/dist/config/parse-stack.js.map +1 -1
  125. package/dist/config/stack.d.ts +8 -0
  126. package/dist/config/stack.d.ts.map +1 -1
  127. package/dist/config/stack.js +1 -0
  128. package/dist/config/stack.js.map +1 -1
  129. package/dist/help/usage.d.ts +1 -1
  130. package/dist/help/usage.d.ts.map +1 -1
  131. package/dist/help/usage.js +1 -1
  132. package/dist/init/manifest/versions.js +24 -24
  133. package/dist/init/stack/resolve-stack.d.ts.map +1 -1
  134. package/dist/init/stack/resolve-stack.js +3 -1
  135. package/dist/init/stack/resolve-stack.js.map +1 -1
  136. package/dist/init/stack/stack-options.d.ts +2 -0
  137. package/dist/init/stack/stack-options.d.ts.map +1 -1
  138. package/dist/stack/stack-vars.js +2 -1
  139. package/dist/stack/stack-vars.js.map +1 -1
  140. package/dist/version.d.ts +1 -1
  141. package/dist/version.js +1 -1
  142. package/package.json +2 -2
  143. package/templates/init/rules/domain.md.tpl +1 -1
  144. package/templates/pipeline/package-scripts.json +1 -1
package/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ **English** · [Français](README.fr.md)
2
+
1
3
  # ddd-ts-kit
2
4
 
3
5
  Bootstrap, scaffold, document and verify Domain-Driven Design and Clean Architecture projects
@@ -11,6 +13,15 @@ dependency-cruiser, knip, coverage thresholds, git hooks and the CI of GitHub or
11
13
  The generator and the checker share one definition of the canonical tree, and a test generates
12
14
  every kind for every stack and requires zero violation: they cannot disagree.
13
15
 
16
+ The main commands fall into two families. The first writes the project: `init` once, `generate` for
17
+ each piece, `docs`, `sdk` and `depcruise-config` for what is derived from the code and the
18
+ config, and three migrations that move a project from an older form to the current one. The
19
+ second reads the project back without changing it: `check` judges it, `list` and `describe` state
20
+ what it holds, `status` and `todo` say what is left to do, `affected` says what a pull request has
21
+ to run.
22
+
23
+ ![The commands of the CLI around a project: on the left the ones that write it (init, generate, docs, sdk and depcruise-config, the three migrations), in the middle the project (ddd-ts-kit.config.json, the bounded contexts and the platform under src/, the generated documents), on the right the ones that read it back (check and check-file, list and describe, status and todo, affected)](https://raw.githubusercontent.com/gbourgeat/ddd-ts-kit/main/docs/assets/diagrams/cli-commands-and-project.svg)
24
+
14
25
  No runtime dependency. Node 22.12 or later.
15
26
 
16
27
  ```
@@ -52,7 +63,8 @@ ddd-ts-kit init [--http express|fastify|hono|nest|none]
52
63
  [--style declared|explicit] [--assembly barrel|discovery]
53
64
  [--dependencies signature|listed] [--refusals factories|classes]
54
65
  [--routes controllers|functions] [--value-objects throwing|result]
55
- [--literals named|free] [--tenancy none|shared-table] [--queue database|redis|memory]
66
+ [--literals named|free] [--values classes|free] [--tenancy none|shared-table]
67
+ [--queue database|redis|memory]
56
68
  [--schemas shared|per-route]
57
69
  [--package-manager npm|pnpm]
58
70
  [--allow-no-outbox "<reason>"] [--import-alias '$<name>/']
@@ -97,7 +109,7 @@ existed keeps its spelling. See [docs/CONFIG.md](../../docs/CONFIG.md).
97
109
 
98
110
  Defaults: `express`, `zod`, `tsyringe` (`nest` with `--http nest`, and only then), `vitest`,
99
111
  `drizzle` on `postgres`, `outbox` when there is an ORM, `swagger`, `github`, `jwt`, `rbac`, no broker,
100
- the cache in the process, the audit trail `on` when there is an ORM, unexpected failures reported
112
+ the cache in the process, files in a local directory (`--storage local`), the audit trail `on` when there is an ORM, unexpected failures reported
101
113
  nowhere (`--errors none`), JSON lines on the standard output (`--logger stdout`), and the
102
114
  `declared` style with a `barrel` wherever the declarations are read: with `--di tsyringe`.
103
115
 
@@ -109,7 +121,7 @@ proven by a real install of a generated project, its build and its `verify`. The
109
121
  from the registry either way.
110
122
 
111
123
  **Pipelines.** A pull or merge request runs the unit specs, the integration specs and the lint of
112
- the contexts its change reaches through the context map (`ddd-ts-kit affected`); types,
124
+ the contexts its change reaches through the context map ([`ddd-ts-kit affected`](#affected)); types,
113
125
  architecture, import graph, dead code, end to end, build and documents always run on the whole
114
126
  project, and the default branch and the nightly schedule run everything.
115
127
 
@@ -131,6 +143,104 @@ files it imports itself are compiled by the runner. Discovery is accepted behind
131
143
  context listens on the subscriber `EventSubscribers.forContext(name)` hands it, in either assembly.
132
144
  A config written before `stack.style` existed is explicit.
133
145
 
146
+ The same use case in both styles. In the declared style, the command and its handler carry their
147
+ decorator, and the handler's constructor says what it needs. The body of the handler is the one
148
+ `generate command billing IssueInvoice --aggregate Invoice --permission invoice.issue` writes,
149
+ before its `TODO(domain)` is answered; the input of the command is filled in:
150
+
151
+ <!-- checked sample -->
152
+ ```ts
153
+ import { definePermissions } from '@ddd-ts-kit/auth';
154
+ import { type Actor, AggregateRoot, AuthorizedCommand, type Clock, type IdGenerator, Repository } from '@ddd-ts-kit/kernel';
155
+ import { CommandHandler, Requires } from '@ddd-ts-kit/kernel/declarations';
156
+
157
+ declare class Invoice extends AggregateRoot {
158
+ static create(id: string, now: Date): Invoice;
159
+ }
160
+
161
+ // domain/repositories/invoice.repository.ts
162
+ export abstract class InvoiceRepository extends Repository<Invoice> {}
163
+
164
+ // application/authorization/billing.permissions.ts
165
+ export const billingPermissions = definePermissions('billing', {
166
+ 'invoice.issue': { description: 'Issue invoice' },
167
+ });
168
+
169
+ // application/commands/issue-invoice/issue-invoice.command.ts
170
+ @Requires(billingPermissions['invoice.issue'])
171
+ export class IssueInvoiceCommand extends AuthorizedCommand<string> {
172
+ constructor(
173
+ actor: Actor,
174
+ readonly customerId: string,
175
+ ) {
176
+ super(actor);
177
+ }
178
+ }
179
+
180
+ // application/commands/issue-invoice/issue-invoice.handler.ts
181
+ @CommandHandler(IssueInvoiceCommand)
182
+ export class IssueInvoiceHandler implements CommandHandler<IssueInvoiceCommand> {
183
+ // each parameter is typed by one class, which is also its injection token
184
+ constructor(
185
+ private readonly invoiceRepository: InvoiceRepository,
186
+ private readonly ids: IdGenerator,
187
+ private readonly clock: Clock,
188
+ ) {}
189
+
190
+ async execute(command: IssueInvoiceCommand): Promise<string> {
191
+ const target = Invoice.create(this.ids.next(), this.clock.now());
192
+
193
+ await this.invoiceRepository.save(target);
194
+
195
+ return target.id;
196
+ }
197
+ }
198
+ ```
199
+
200
+ In the explicit style, the same classes carry no decorator, and the module file of the context
201
+ binds each of them, with its permission and the ports it is given:
202
+
203
+ <!-- checked sample -->
204
+ ```ts
205
+ import { definePermissions } from '@ddd-ts-kit/auth';
206
+ import { defineContextModule } from '@ddd-ts-kit/di';
207
+ import { AggregateRoot, AuthorizedCommand, Clock, type CommandHandler, IdGenerator, Repository } from '@ddd-ts-kit/kernel';
208
+
209
+ // the classes of the declared sample, without their decorators
210
+ declare class Invoice extends AggregateRoot {}
211
+ declare class IssueInvoiceCommand extends AuthorizedCommand<string> {}
212
+ declare class IssueInvoiceHandler implements CommandHandler<IssueInvoiceCommand> {
213
+ constructor(invoiceRepository: InvoiceRepository, ids: IdGenerator, clock: Clock);
214
+ execute(command: IssueInvoiceCommand): Promise<string>;
215
+ }
216
+ declare class InMemoryInvoiceRepository implements InvoiceRepository {
217
+ findById(id: string): Promise<Invoice | undefined>;
218
+ save(invoice: Invoice): Promise<void>;
219
+ }
220
+
221
+ export abstract class InvoiceRepository extends Repository<Invoice> {}
222
+
223
+ export const billingPermissions = definePermissions('billing', {
224
+ 'invoice.issue': { description: 'Issue invoice' },
225
+ });
226
+
227
+ // billing.module.ts: every binding of the context, one line each
228
+ export const billingModule = defineContextModule({
229
+ name: 'billing',
230
+ register(builder) {
231
+ builder.bind(InvoiceRepository).toClass(InMemoryInvoiceRepository).as('repository');
232
+ builder
233
+ .command(IssueInvoiceCommand)
234
+ .requires(billingPermissions['invoice.issue'])
235
+ .handledBy(IssueInvoiceHandler, [InvoiceRepository, IdGenerator, Clock]);
236
+ },
237
+ });
238
+ ```
239
+
240
+ `check` holds both forms: `authz/declared` reports a command that says neither who may send it
241
+ nor that it is public, `declarations/role-declared` a role file of the declared style without its
242
+ decorator.
243
+
134
244
  **A port is an abstract class and its own injection token.** `application/ports/<name>.port.ts`
135
245
  and `domain/repositories/<name>.repository.ts` hold one abstract class each. Generated code holds
136
246
  no `token()`, no UPPER_SNAKE injection constant, no `<context>.ports.ts` and no
@@ -139,6 +249,50 @@ no `token()`, no UPPER_SNAKE injection constant, no `<context>.ports.ts` and no
139
249
  list or a binding and type imports where they only type a parameter, an adapter `implements`
140
250
  its port.
141
251
 
252
+ Instead of an interface, a token constant and an injection by name:
253
+
254
+ ```ts
255
+ export interface IPaymentGateway {
256
+ charge(invoiceId: string, amountInCents: number): Promise<void>;
257
+ }
258
+
259
+ export const PAYMENT_GATEWAY = Symbol('PaymentGateway'); // ports/no-token-constant
260
+
261
+ constructor(@inject(PAYMENT_GATEWAY) private readonly payments: IPaymentGateway) {}
262
+ ```
263
+
264
+ write one abstract class, which is both the contract and the token:
265
+
266
+ <!-- checked sample -->
267
+ ```ts
268
+ import { Adapter } from '@ddd-ts-kit/kernel/declarations';
269
+
270
+ // application/ports/payment-gateway.port.ts
271
+ export abstract class PaymentGateway {
272
+ abstract charge(invoiceId: string, amountInCents: number): Promise<void>;
273
+ }
274
+
275
+ // infrastructure/external/remote-payment-gateway.adapter.ts: implements the port, never extends it
276
+ @Adapter(PaymentGateway, { kind: 'external' })
277
+ export class RemotePaymentGateway implements PaymentGateway {
278
+ async charge(invoiceId: string, amountInCents: number): Promise<void> {
279
+ // TODO(platform): call the payment provider with invoiceId and amountInCents
280
+ }
281
+ }
282
+
283
+ // a handler names the port by the type of its parameter: nothing else says what is injected
284
+ export class SettleInvoiceHandler {
285
+ constructor(private readonly payments: PaymentGateway) {}
286
+
287
+ async settle(invoiceId: string, amountInCents: number): Promise<void> {
288
+ await this.payments.charge(invoiceId, amountInCents);
289
+ }
290
+ }
291
+ ```
292
+
293
+ `ports/abstract-class`, `ports/implements-never-extends`, `ports/no-token-constant` and
294
+ `ports/value-import` hold this form, in `check` and in ESLint.
295
+
142
296
  **The constructor states the dependencies.** In the declared style a generated class lists
143
297
  nothing in its decorator (`@CommandHandler(IssueInvoiceCommand)`): each constructor parameter
144
298
  is typed by one class, its token (`declarations/constructor-injection`). `generate`, `docs` and
@@ -153,6 +307,31 @@ is absent with standard decorators and under `tsx`. `init` writes
153
307
  what each class is given. Every generated file exports one class, interface or function and is named
154
308
  after it.
155
309
 
310
+ ![How a declared context reaches the composition root: the role files carry their decorator and type their constructor parameters by classes; generate, docs and the dev script read those signatures and write the barrel, one export per role file and one injected statement per class that takes something; the composition root includes the barrel with contextModule and @ddd-ts-kit/di injects from it; check and docs --check report a barrel that no longer matches the role files](https://raw.githubusercontent.com/gbourgeat/ddd-ts-kit/main/docs/assets/diagrams/cli-declared-barrel.svg)
311
+
312
+ An excerpt of the barrel of the `notification` context of the example application, as the CLI
313
+ writes it (never by hand):
314
+
315
+ ```ts
316
+ // src/contexts/notification/notification.declarations.ts
317
+ import { injected } from '@ddd-ts-kit/kernel/declarations';
318
+ import { WithdrawReminderHandler } from '@/contexts/notification/application/commands/withdraw-reminder/withdraw-reminder.handler';
319
+ import { ReminderRepository } from '@/contexts/notification/domain/repositories/reminder.repository';
320
+ import { KnexReminderRepository } from '@/contexts/notification/infrastructure/persistence/knex-reminder.repository';
321
+ import { Clock, ExecutionScope } from '@ddd-ts-kit/kernel';
322
+ import { KnexContext } from '@ddd-ts-kit/knex';
323
+
324
+ export * from '@/contexts/notification/application/commands/withdraw-reminder/withdraw-reminder.command';
325
+ export * from '@/contexts/notification/application/commands/withdraw-reminder/withdraw-reminder.handler';
326
+ export * from '@/contexts/notification/infrastructure/persistence/knex-reminder.repository';
327
+
328
+ injected(WithdrawReminderHandler, [ReminderRepository]);
329
+ injected(KnexReminderRepository, [KnexContext, ExecutionScope, Clock]);
330
+ ```
331
+
332
+ and the one line of the composition root that adds the whole context:
333
+ `contextModule('notification', notificationDeclarations)` in `src/platform/compose-application.ts`.
334
+
156
335
  **Domain errors are built by factories.** `init` writes `"stack.refusals": "factories"`: a
157
336
  context has one error class per concept, extending `ConceptError` of the kernel, and
158
337
  `generate error <context> <Concept> <refusal>` adds a static factory to it. The `errors/*`
@@ -160,6 +339,46 @@ rules hold that form. `--refusals classes`, or a config without the key, keeps o
160
339
  error built with `new`, as 0.1 and 0.2 wrote them; `error-factories` moves a project from one
161
340
  to the other.
162
341
 
342
+ The older form, under `"stack.refusals": "classes"`: one class per error.
343
+
344
+ <!-- checked sample -->
345
+ ```ts
346
+ import { DomainError } from '@ddd-ts-kit/kernel';
347
+
348
+ // domain/errors/slot-taken.error.ts
349
+ export class SlotTakenError extends DomainError {
350
+ readonly code = 'SLOT_TAKEN';
351
+
352
+ constructor(readonly calendarId: string) {
353
+ super(`The slot of calendar ${calendarId} is already held.`);
354
+ }
355
+ }
356
+
357
+ const refusal = new SlotTakenError('calendar-1');
358
+ ```
359
+
360
+ The form `init` writes: one class per concept of the glossary, one factory per refusal, which
361
+ `generate error scheduling Appointment slotTaken` adds.
362
+
363
+ <!-- checked sample -->
364
+ ```ts
365
+ import { ConceptError } from '@ddd-ts-kit/kernel';
366
+
367
+ // domain/errors/appointment.error.ts
368
+ export class AppointmentError extends ConceptError {
369
+ static slotTaken(calendarId: string) {
370
+ return AppointmentError.refusal('SLOT_TAKEN', `The slot of calendar ${calendarId} is already held.`, { calendarId });
371
+ }
372
+ }
373
+
374
+ const refusal = AppointmentError.slotTaken('calendar-1');
375
+
376
+ // what catches it tells the refusal by its class and its code, and reads its facts
377
+ if (AppointmentError.is(refusal, 'SLOT_TAKEN')) {
378
+ console.log(refusal.facts.calendarId);
379
+ }
380
+ ```
381
+
163
382
  **A route is a controller class.** `init` writes `"stack.routes": "controllers"`: each route is
164
383
  one class with one method, in a folder of its own, behind the guards of its route group, and a
165
384
  declared context lists no route: its controllers reach the application through its generated
@@ -187,6 +406,65 @@ the aggregate recorded are dispatched once the save resolves, inside the unit of
187
406
  command: by the kernel module on tsyringe, by `dispatchingOnSave` around each repository in
188
407
  `platform/create-platform.ts` with hand wiring (`domain-events/dispatched-on-save`).
189
408
 
409
+ Instead of pulling the events and dispatching them in the handler:
410
+
411
+ ```ts
412
+ async execute(command: IssueInvoiceCommand): Promise<void> {
413
+ const invoice = await this.invoiceRepository.findById(command.invoiceId);
414
+
415
+ invoice.issue(this.clock.now());
416
+ await this.invoiceRepository.save(invoice);
417
+ await this.dispatcher.dispatch(invoice.pullEvents()); // domain-events/dispatched-on-save
418
+ }
419
+ ```
420
+
421
+ write the save, and nothing after it:
422
+
423
+ <!-- checked sample -->
424
+ ```ts
425
+ import { type Actor, AggregateRoot, AuthorizedCommand, type Clock, type CommandHandler, ConceptError, Repository } from '@ddd-ts-kit/kernel';
426
+
427
+ declare class Invoice extends AggregateRoot {
428
+ /** Makes the invoice final and records InvoiceIssued, which waits in the aggregate. */
429
+ issue(now: Date): void;
430
+ }
431
+
432
+ declare class InvoiceError extends ConceptError {
433
+ static notFound(invoiceId: string): InvoiceError;
434
+ }
435
+
436
+ export abstract class InvoiceRepository extends Repository<Invoice> {}
437
+
438
+ export class IssueInvoiceCommand extends AuthorizedCommand<void> {
439
+ constructor(
440
+ actor: Actor,
441
+ readonly invoiceId: string,
442
+ ) {
443
+ super(actor);
444
+ }
445
+ }
446
+
447
+ export class IssueInvoiceHandler implements CommandHandler<IssueInvoiceCommand> {
448
+ constructor(
449
+ private readonly invoiceRepository: InvoiceRepository,
450
+ private readonly clock: Clock,
451
+ ) {}
452
+
453
+ async execute(command: IssueInvoiceCommand): Promise<void> {
454
+ const invoice = await this.invoiceRepository.findById(command.invoiceId);
455
+
456
+ if (invoice === undefined) {
457
+ throw InvoiceError.notFound(command.invoiceId);
458
+ }
459
+
460
+ invoice.issue(this.clock.now());
461
+
462
+ // InvoiceIssued is dispatched once this save resolves, inside the unit of work of the command
463
+ await this.invoiceRepository.save(invoice);
464
+ }
465
+ }
466
+ ```
467
+
190
468
  `--events in-memory` together with an ORM is refused unless `--allow-no-outbox "<reason>"` is given:
191
469
  it records a `deviations` entry (`stack.events`) and writes the decision record it points to. The generated
192
470
  persistence of the four ORMs is written for PostgreSQL; `custom` starts on in-memory adapters
@@ -211,6 +489,7 @@ config, `docsUi`, `ci` and `logger` included.
211
489
  | `--http-cache varnish` | a compose service `varnish` (Varnish 9.1.0, reaching the API on `host.docker.internal`) with its VCL in `docker/varnish/default.vcl` (only answers whose `Cache-Control` allows them by `max-age` or `s-maxage` are kept, filed under their `xkey` tags, purged by `PURGE` from the machine of Varnish or with `VARNISH_PURGE_KEY`, `x-cache: HIT\|MISS`), `VARNISH_URL` and `VARNISH_PURGE_KEY` validated, `cacheTags: { header: CacheTagHeader.xkey }` in the HTTP boundary, and the outbox relay of `main.ts` and of every `generate service` entry point publishing through `PurgingPublisher` with `VarnishCachePurger` (the end-to-end application records the purges in `purges`); on `--di manual` and `nest` the platform gains `cacheInvalidation`; with `--tenancy`, the tags of the answers and the purges are filed under the tenant (`perTenant`) and the tenant of a request is read from `x-tenant-id`. Needs an HTTP framework and `--events outbox`: a purge is a row of the outbox |
212
490
  | `--errors sentry` | unexpected failures reported to Sentry or GlitchTip (one protocol, only the DSN differs) through the kernel `ErrorReporter`, bound to `@ddd-ts-kit/sentry`. `src/platform/errors.ts` (`initSentry` on the validated configuration, then `reportProcessFailures`), the second import of `main.ts`, of `apps/<context>/main.ts` and of `apps/worker/main.ts`, right after `tracing.ts`; `errors` handed to `composeApplication`, which gives it to the kernel module on tsyringe (`errorReporter`, flushed by `container.stop()` before the logger, `errorReportingMiddleware` first on both buses, the default scheduler observing with `reportFailedRuns`) or to `createPlatform` by hand (the same middleware, `onRun: reportFailedRuns(errors)`, `platform.errors`, flushed two seconds at most when the application stops); `reportHttpErrors` in `auth/http-boundary.ts` for every request answered 5xx; `DeadLetterReports` on the `onFailure` hook of a provided broker (`createEventProvider(config, errors)`, with `observing` around each consumer) and of the outbox relay. `SENTRY_DSN`, `SENTRY_ENVIRONMENT` (default: `NODE_ENV`), `SENTRY_RELEASE` and `SENTRY_SCRUB_MESSAGES` validated in `config/load-config.ts` as `errorReporting`: without a DSN the application starts and reports nothing; a DSN that is not a URL refuses the start. Dependencies `@ddd-ts-kit/sentry` and `@sentry/node`. A compose profile `errors` running GlitchTip 6.2.6 with its own PostgreSQL and Valkey (`GLITCHTIP_PORT`, loopback only, development bootstrap), and a section of the generated README saying how to read its DSN. The tools and the tests compose the application without a reporter. It goes with every other choice of the stack; with `--errors none` nothing of this is written |
213
491
  | `--authn keycloak` | a compose service `keycloak` (Keycloak 26.8.0, `start-dev --import-realm`, profile `identity`) and `docker/keycloak/<project>-realm.json`: the API client, a public client `<project>-local` with the direct grant, its audience and `tenant_id` mappers, the realm role `admin`, and the demo users `demo-admin` and `demo-user`, for local development only |
492
+ | `--storage local\|s3` | recorded as `stack.storage`: where the content of files lives, never in the database. `src/platform/config/read-files.ts` and `src/platform/files/open-files.ts`, one per choice: `local`, a directory in development (`FILES_DIRECTORY`, `.data/files`) and the signed links answered by the application under `/files` (`FILES_PUBLIC_URL` and `FILES_SIGNING_SECRET` required in production); `s3`, a bucket of any provider of the S3 protocol through `@ddd-ts-kit/s3` (the `S3_*` variables, MinIO in the compose stack); the memory of the process in the tests and the tools, either way. With `--di tsyringe`: `files/bind-files.ts`, `files/files.module.ts` and `files/staging-uploads.ts`; with hand wiring `files/create-files.ts`; with an HTTP stack `files/file-routes.ts`. They bind `FileStorage`, `FileKeys`, `FileScanner` and `FileStaging`, register the hourly purge of staged files and stage the file a command carries before its transaction; `generate file-upload` needs them. `--storage none` writes none of this ([File storage](../../docs/concepts/file-storage.md)) |
214
493
  | `src/platform/events/` | `event-provider.ts` (`bus`, `forConsumer(name)`, `start()`, `close()`), `in-process-events.ts`, and for `--broker` `create-event-provider.ts`: `RabbitMqEventBus` (`AMQP_URL`), `KafkaEventBus` (`KAFKA_BROKERS`, with `aggregate-key-of.ts` for per-aggregate order), `NatsEventBus` (`NATS_URL`) or `RedisStreamsEventBus` of `@ddd-ts-kit/redis/event-bus` (`REDIS_URL`: Redis, Dragonfly or Valkey), each with one subscriber per consuming context (`forConsumer`), `assertTopology` at start, its address validated and a compose service with a healthcheck; or `custom-event-bus.ts` with its `eventBusContract` spec. `start-outbox-relay.ts` with the outbox. `--broker redis` with `--cache redis` is one server and one compose service, `cache`; alone, the service is `streams` |
215
494
  | `src/platform/database/` | `persistence.ts` (`interface Persistence`: unit of work, outbox and processed-event stores of the ORM package, the storage handle), `open-persistence.ts`, `persistence-over.ts`, `inert-persistence.ts`, the connection (`connect-to-database.ts`, `create-client.ts` or `create-data-source.ts`), `schema.ts`, `open-test-database.ts` (PostgreSQL in process, or the server named by a URL). The storage class a repository adapter names in its dependency list: `database.ts` with drizzle and prisma (`abstract class Database extends` the context type of the ORM package), `KnexContext` of `@ddd-ts-kit/knex`, `TypeOrmContext` of `@ddd-ts-kit/typeorm` |
216
495
  | migration tooling | `drizzle.config.ts`, `knexfile.js` and `migrations/00000000000000_initial.js`, `prisma/schema.prisma` and `prisma.config.ts`, or `data-source.ts` |
@@ -296,10 +575,42 @@ ddd-ts-kit generate <kind> ... [--term "<Term>=<meaning>"]... [--allow-name] [--
296
575
  ddd-ts-kit generate --help --json
297
576
  ```
298
577
 
578
+ One run of `generate` does five things, in this order, and writes nothing when the first one
579
+ refuses:
580
+
581
+ ![One run of generate command: the name is checked against the glossary of the context (refused with exit code 2 and the --term to add when a word is not defined), the files are written from the templates, the new pieces are registered between marker comments, the derived documents are refreshed, and the places that need a business decision are printed as file:line](https://raw.githubusercontent.com/gbourgeat/ddd-ts-kit/main/docs/assets/diagrams/cli-generate-one-run.svg)
582
+
583
+ In the example application, a use case named with a word its glossary does not define is refused,
584
+ and the message says how to go on:
585
+
586
+ ```
587
+ $ npx ddd-ts-kit generate command notification ArchiveNotification --permission notification.archive --aggregate Notification
588
+ error: "ArchiveNotification" uses "archive", which the glossary of src/contexts/notification/CONTEXT.md does not define.
589
+ fix: Either use an existing term (Notification, Send, Recipient, Contact, Confirmation, Reminder, Plan, Due, Withdraw, Mailer, Kind, Request, Rescheduling, Cancellation, Read, Search), or add the term in the same command: --term "Archive=<what it means for the business>". The glossary is written before the code (rule language/glossary-term).
590
+ ```
591
+
592
+ With the term given in the same command (and `--dry-run`, which says what it would write):
593
+
594
+ ```
595
+ $ npx ddd-ts-kit generate command notification ArchiveNotification --permission notification.archive --aggregate Notification \
596
+ --term "Archive=To set a sent notification aside, out of the default search" --dry-run
597
+ would create src/contexts/notification/application/commands/archive-notification/archive-notification.command.ts
598
+ would create src/contexts/notification/application/commands/archive-notification/archive-notification.handler.ts
599
+ would create src/contexts/notification/test/unit/archive-notification.handler.spec.ts
600
+ would update src/contexts/notification/CONTEXT.md
601
+ would update src/contexts/notification/application/authorization/notification.permissions.ts
602
+ would update src/contexts/notification/notification.declarations.ts
603
+
604
+ Next, in this order: the language of the context first, then the business decisions only you can make
605
+ src/contexts/notification/application/commands/archive-notification/archive-notification.command.ts:9 declare the input of "archive notification" as a constructor with the actor first, ...
606
+ src/contexts/notification/application/commands/archive-notification/archive-notification.handler.ts:16 decide what "archive notification" does to a Notification: ...
607
+ src/contexts/notification/test/unit/archive-notification.handler.spec.ts:38 replace each entry below by a test named after the business behaviour of "archive notification": ...
608
+ ```
609
+
299
610
  | Kind | Arguments | Writes |
300
611
  |---|---|---|
301
612
  | `context` | `<name> --classification <c> [--purpose "<two sentences>"] [--term ...]... [--upstream <ctx>[:relationship]]... [--preset identity]` | `CONTEXT.md` with its glossary, `published-language/index.ts`, config entry, registration in `platform/compose-application.ts` and in `test/architecture/extractable.spec.ts`; on tsyringe, with HTTP, `presentation/http/<name>.routes.ts` (a function of the two buses, in both styles); declared: the barrel `<name>.declarations.ts`; explicit and hand wiring: `<name>.module.ts`. `application/authorization/<name>.permissions.ts` is born with the first permission, or at once when `platform/auth/permissions.ts` lists the permission sets (rebac, custom). With `--preset`, the context is then written whole from that preset, its classification and purpose those of the preset unless the flags say otherwise ([Presets](#presets)) |
302
- | `aggregate` | `<context> <Name>` | aggregate, builder, spec in given / when / then |
613
+ | `aggregate` | `<context> <Name> [--event-sourced]` | aggregate, builder, spec in given / when / then; with `--event-sourced`, an aggregate on `EventSourcedAggregate` whose state is the replay of its events, born with its `Created` domain event, the codec of its stored events in `infrastructure/persistence/<name>-events.mapper.ts`, a builder that replays a history and a spec given events, and a note naming the `EventSourcedRepository` its repository adapter delegates to |
303
614
  | `builder` | `<context> <Aggregate>` | `test/builders/<name>.builder.ts`, a class extending `Builder` |
304
615
  | `entity` | `<context> <Name>` | entity, spec |
305
616
  | `value-object` (`vo`) | `<context> <Name>` | value object, spec |
@@ -315,6 +626,7 @@ ddd-ts-kit generate --help --json
315
626
  | `port` | `<context> <Name>` | `application/ports/<name>.port.ts`, one abstract class, and its fake |
316
627
  | `route` | `<context> <UseCaseName> --method <GET\|POST\|PUT\|PATCH\|DELETE> --path <p> [--command\|--query] [--group <Name>] [--mapper] [--serializer] [--cache public\|private\|no-store]` | under `"stack.routes": "controllers"`: a controller class in `presentation/http/<resource>/<use-case>/` (the resource is the first segment of the path), depending on `CommandBus` or `QueryBus`, behind the route group of its resource, written with it when it is new (`<resource>.routes.ts`: prefix, tag, `Authenticated`); a public use case stands in no group and says `guards: [Public]`; with class-validator one class per file (`<use-case>.params.ts`, `.body.ts`, `.answer.ts`, `.accepted.ts`, `.line.ts`); `--mapper` and `--serializer` add a request mapper and a serializer the controller is given; a spec; no registration in a declared project, a controller built by hand in `<context>.routes.ts` (the module file with hand wiring) in an explicit one. Under `"functions"`: a `defineRoute` function, its spec, its registration in `<context>.routes.ts`, and `<use-case>-<role>.dto.ts` classes |
317
628
  | `controller` | the same arguments as `route` | the controller class, whatever `stack.routes` says: for a project of function routes that writes its next route as a class. `--cache` writes its cache policy: `private` (a `GET` of a caller, `max-age=0`, `must-revalidate`, strong `ETag`), `public` (a `GET` of a public use case, `max-age` 1m, `s-maxage` 5m, tagged with the resources of its path), `no-store`; refused on a write, and `public` behind a route group |
629
+ | `file-upload` | `<context> <UseCaseName> --path <p> (--permission <aggregate>.<action> \| --public) [--method POST\|PUT] [--group <Name>]` | an upload: a controller that declares the file it receives (its size and media types in the contract, checked as it streams), the command that carries it as a `FileUpload`, staged by the command bus before the transaction, the handler that records it and promotes it with the commit, and their specs. Refused without `stack.storage` or without an HTTP stack, with the `init --force --storage local` to run |
318
630
  | `route-group` (`group`) | `<context> <Name> [--prefix <path>] [--group <ParentName>] [--public \| --role <role>]` | `presentation/http/<name>.routes.ts`: an empty class carrying `@RouteGroup`, behind `Authenticated` unless `--public` or `--role` says otherwise (`HasRole(Role.Admin)`, the role read from the vocabulary of the platform, `<platformDir>/auth/role.ts`, which gets it when it does not hold it); with `--group` it refines that group and inherits from it |
319
631
  | `guard` | `<context> <Name>` | `presentation/http/guards/<name>.guard.ts`: a declared guard class to fill, and its spec |
320
632
  | `representation` | `<context> <UseCase> <xml\|csv\|media/type>[,...]` | adds `produces` to the `@Controller` of the use case, and `consumes: ['application/xml']` to one that receives a body when XML is asked; says when CSV needs a page or a streamed list, and when `stack.representations` is not `negotiated` |
@@ -322,7 +634,7 @@ ddd-ts-kit generate --help --json
322
634
  | `schema` | `<context\|shared-kernel> <Name>` | `presentation/http/shared/<name>.schema.ts` of the context, or `presentation/<name>.schema.ts` of the shared kernel: one shape of the contract declared once (a zod schema with `.meta({ id })`, a class-validator class, or a schema wrapped by `sharedSchema`), to fill; the routes use it whole, nested or in part |
323
635
  | `integration-event` | `<context> <Aggregate> <Fact> [--version n]` | `defineIntegrationEvent` declaration in the published language (its payload in `<event>.v<n>.payload.ts` with class-validator or a schema of your own), index export, the publisher `application/integration/publishers/<event>.publisher.ts`, spec, registration, a line of `platform/audit/audited-events.ts` |
324
636
  | `integration-handler` (`consumer`) | `<context> <upstreamContext> <eventType>` | `application/integration/handlers/<name>.integration-handler.ts`, a class `<Name>IntegrationHandler` depending on `CommandBus`, its spec, its idempotent subscription under the consumer name `<context>.<name>`; refused unless the upstream is declared |
325
- | `event-handler` | `<context> <Aggregate> <Fact> [--phase after-commit]` | reaction to a domain event of the context, spec, subscription; before the commit of the unit of work by default, after it with `--phase after-commit` (an effect no transaction covers, at most once), and the spec runs the reaction in that phase |
637
+ | `event-handler` | `<context> <Aggregate> <Fact> [--phase before-commit\|after-commit]` | reaction to a domain event of the context, spec, subscription; before the commit of the unit of work by default, after it with `--phase after-commit` (an effect no transaction covers, at most once), and the spec runs the reaction in that phase |
326
638
  | `projection` (`projector`) | `<context> <Name> <event-type>` | a projector in `application/projections/<name>.projection.ts`, declared with `@Projection('<context>.<name>')` and one `@On(definition)` method for the integration event, which the context itself or a declared upstream publishes, and its spec; declared style only. It keeps one read model: its port is received in the constructor |
327
639
  | `job` | `<context> <Name> (--every <duration> \| --cron "<expression>" [--time-zone <zone>]) --command <UseCaseName>` | `application/scheduling/<name>.job.ts`, a class `<Name>Job implements ScheduledJob` that sends the command, as `systemActor('<context>.<name>')` when it is protected, with `execution.scheduledAt` when the command takes it; `test/unit/<name>.job.spec.ts`, which runs the job (by name on tsyringe, `application.jobs.runNow`; `job.run(occurrence)` with hand wiring) and asserts the command it sent; its registration; its switch in `platform/job-settings.ts`; the grant of its system actor (below) |
328
640
  | `background-job` | `<context> <Name> --command <UseCaseName>` | `application/background/<name>.background-job.ts`, a class `<Name>BackgroundJob implements BackgroundJob<<Name>Payload>` named by its static `jobName` `<context>.<name>`, that sends the command as `execution.actor` when it is protected, followed by one field of `<Name>Payload` per other parameter of the constructor of the command (strings, numbers, booleans, literals and lists of them as written, a `Date` as its ISO text; anything else is refused); `test/unit/<name>.background-job.spec.ts`, which enqueues it with a payload of every field as a user holding the permission and runs what is due; its registration (`@Background()`, or `builder.backgroundJob(..)` in the module file). `--di tsyringe` only |
@@ -621,6 +933,15 @@ this command rewrites, in every `.js`, `.mjs`, `.cjs`, `.d.ts`, `.d.mts` and `.d
621
933
  output directory of the tsconfig (`tsconfig.build.json` by default), each alias specifier into
622
934
  the relative path of the emitted file it names, with its `.js` extension
623
935
  (`../../platform/errors.js`), so that `node dist/src/main.js` runs with no loader and no flag.
936
+
937
+ ```js
938
+ // dist/src/contexts/billing/domain/aggregates/invoice.aggregate.js, as the compiler emits it
939
+ import { InvoiceError } from '@/contexts/billing/domain/errors/invoice.error';
940
+
941
+ // the same line after ddd-ts-kit build-aliases
942
+ import { InvoiceError } from '../errors/invoice.error.js';
943
+ ```
944
+
624
945
  It reads `importAlias` and `importExtension` of the config: `@/platform/errors` names
625
946
  `platform/errors.js` under `none`, `@/platform/errors.js` names it under `js`, and no other
626
947
  spelling is guessed.
@@ -721,6 +1042,53 @@ from `docs/architecture/slices.md`, the published languages it consumes. An exis
721
1042
  kept and only the file is written again; `--dry-run` creates and writes nothing. It needs a git
722
1043
  repository with one commit at least. The worker then generates with `--local-only`.
723
1044
 
1045
+ ## affected
1046
+
1047
+ ```
1048
+ ddd-ts-kit affected [--base <git-ref>] [--head <git-ref>] [--format text|json|github|env]
1049
+ ```
1050
+
1051
+ What a pull or merge request has to run, read from the context map and the paths that differ
1052
+ between `--base` and `--head` (`HEAD` by default; the merge base is used when the clone has it).
1053
+ Each changed path reaches something:
1054
+
1055
+ - a path under a declared context reaches that context and every context downstream of it, since
1056
+ their code may read its published language; a path under its `test/` alone reaches that
1057
+ context alone, since no other context may import its tests;
1058
+ - a document reaches nothing that runs: a Markdown file at the root, anything under `docs/`,
1059
+ `openapi/` or `.claude/` (the documents gate always runs);
1060
+ - anything else reaches everything: the platform, the shared kernel, `main.ts`, the config, the
1061
+ lockfile, the pipeline, a directory of `src/contexts/` the config does not declare.
1062
+
1063
+ Without `--base`, or when git cannot tell what changed (an unknown ref, a shallow clone without
1064
+ the base), everything is selected: that is what the default branch and the nightly run get. The
1065
+ selection is never narrower than the declared relationships, because `context/declared-relationship`
1066
+ keeps every import between contexts inside them, and that rule runs on the whole project in every
1067
+ pipeline.
1068
+
1069
+ ![A change in the scheduling context of the example application: scheduling is selected, and so are notification and analytics, which are downstream of it in the context map; identity, upstream, is left to the default branch](https://raw.githubusercontent.com/gbourgeat/ddd-ts-kit/main/docs/assets/diagrams/cli-affected-selection.svg)
1070
+
1071
+ In the example application, a branch that changes one aggregate of `scheduling`:
1072
+
1073
+ ```
1074
+ $ npx ddd-ts-kit affected --base main
1075
+ Affected contexts: scheduling, notification, analytics (the changed contexts and the contexts downstream of them in the context map).
1076
+ Specs: src/contexts/scheduling/**/*.spec.ts src/contexts/notification/**/*.spec.ts src/contexts/analytics/**/*.spec.ts !src/contexts/**
1077
+ Left to the default branch: src/contexts/identity/
1078
+
1079
+ $ npx ddd-ts-kit affected --base main --format github
1080
+ scope=contexts
1081
+ contexts=scheduling,notification,analytics
1082
+ args=--passWithNoTests
1083
+ lint=--ignore-pattern src/contexts/identity/
1084
+ ```
1085
+
1086
+ `--format github` writes `key=value` lines for `$GITHUB_OUTPUT`, `--format env` the same keys in
1087
+ upper case behind `AFFECTED_` for a GitLab `dotenv` report, `--json` the selection as data
1088
+ (`{ scope, reason, contexts, specs, leftOut }`). `contexts` is what the test runner reads as
1089
+ `DDD_TS_KIT_AFFECTED`, `args` what a partial run adds to it, `lint` what it adds to ESLint; the
1090
+ generated pipelines wire all three.
1091
+
724
1092
  ## check
725
1093
 
726
1094
  ```
@@ -731,7 +1099,58 @@ ddd-ts-kit check --commit-size [--staged] [--strict]
731
1099
  ```
732
1100
 
733
1101
  Each violation has a rule id, a severity, a file, a line, a message and a `fix`: one sentence
734
- saying what to do instead. Exit code 1 when there is at least one error.
1102
+ saying what to do instead. Exit code 1 when there is at least one error. Suppose that the
1103
+ `Reminder` aggregate of the example application read the system clock in its domain: an `isDue()`
1104
+ at line 63 that calls `Date.now()`, where the real one receives the instant as `isDue(asOf: Date)`.
1105
+ `check` would report:
1106
+
1107
+ ```
1108
+ $ npx ddd-ts-kit check src/contexts/notification/domain
1109
+ src/contexts/notification/domain/aggregates/reminder.aggregate.ts:64 error purity/no-system-clock
1110
+ Date.now() is used in the domain layer.
1111
+ fix: Receive the instant as a parameter in the domain, and take it from the Clock port (`this.clock.now()`) in a handler, so that a test fixes time with FixedClock.
1112
+
1113
+ 1 error, 0 warnings, 0 suppressions in 772 files.
1114
+ ```
1115
+
1116
+ The `fix` is the change to make. Instead of that hypothetical method:
1117
+
1118
+ ```ts
1119
+ isDue(): boolean {
1120
+ return this.state.status === ReminderStatus.Planned && this.state.dueAt.getTime() <= Date.now();
1121
+ }
1122
+ ```
1123
+
1124
+ write:
1125
+
1126
+ <!-- checked sample -->
1127
+ ```ts
1128
+ import { Clock, FixedClock } from '@ddd-ts-kit/kernel';
1129
+
1130
+ // domain: the instant is a parameter, as in the Reminder aggregate of the example
1131
+ export class Reminder {
1132
+ constructor(private readonly dueAt: Date) {}
1133
+
1134
+ isDue(asOf: Date): boolean {
1135
+ return this.dueAt.getTime() <= asOf.getTime();
1136
+ }
1137
+ }
1138
+
1139
+ // application: the handler takes the instant from the Clock port
1140
+ export class SendDueRemindersHandler {
1141
+ constructor(private readonly clock: Clock) {}
1142
+
1143
+ isDueNow(reminder: Reminder): boolean {
1144
+ return reminder.isDue(this.clock.now());
1145
+ }
1146
+ }
1147
+
1148
+ // a test fixes the time
1149
+ const handler = new SendDueRemindersHandler(new FixedClock(new Date('2026-03-02T09:00:00Z')));
1150
+ const due = handler.isDueNow(new Reminder(new Date('2026-03-01T09:00:00Z'))); // true
1151
+ ```
1152
+
1153
+ `ddd-ts-kit explain <rule-id>` prints the same kind of bad and good example for every rule.
735
1154
 
736
1155
  - `paths` and `--changed-since` limit what is reported, not what is analysed.
737
1156
  `--changed-since` runs `git diff --name-only --relative <ref>...` in the project.
@@ -843,6 +1262,7 @@ saying what to do instead. Exit code 1 when there is at least one error.
843
1262
  | `schema/presentation-only` | under `"stack.schemas": "shared"`: in the shared kernel outside `presentation/`, an import of a validation library; in `domain/`, `application/`, `infrastructure/`, `published-language/` and the shared kernel outside `presentation/`, an import of a file of `presentation/` of the shared kernel |
844
1263
  | `schema/no-duplicate-shape` | under `"stack.schemas": "shared"`, in the presentation layer of one context, or in it and `presentation/` of the shared kernel: a DTO class or an object schema (`z.object`, `v.object`, `Type.Object`) declaring the shape of another one, with the place of the original (the shared kernel, then `presentation/http/shared/`); two contexts are not compared, and request parameters and shapes still carrying a `TODO(domain)` of the generator are not judged |
845
1264
  | `errors/known-code` | under `"stack.refusals": "factories"`, in a `*.route.ts`: a code of the `errors` list that no refusal of the context of the route sets, that no class outside the contexts sets, and that is not a code of the kit |
1265
+ | `domain/value-object-class` | under `"stack.values": "classes"`: in `domain/value-objects/` of a context or a `*.vo.ts` of the shared kernel outside `presentation/`, no exported class or a second one, a class that extends neither `ValueObject` nor another value object of the project, a public constructor, a concrete class without a static method, an exported function, arrow constant or object literal (inline, listed or by default); in `domain/` and in the shared kernel outside `presentation/`, an exported function that answers a record of data: data entries, no spread, nothing a call of the model built |
846
1266
 
847
1267
  More ids belong to `@ddd-ts-kit/eslint-plugin`, which reads the syntax tree where this
848
1268
  checker reads text: `domain/aggregate-encapsulation`, `domain/value-object-immutability`,
@@ -1 +1 @@
1
- {"version":3,"file":"architecture-rules.d.ts","sourceRoot":"","sources":["../../../src/check/catalogue/architecture-rules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAgF7C,gHAAgH;AAChH,eAAO,MAAM,kBAAkB,EAAE,SAAS,IAAI,EA+E7C,CAAC"}
1
+ {"version":3,"file":"architecture-rules.d.ts","sourceRoot":"","sources":["../../../src/check/catalogue/architecture-rules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAiF7C,gHAAgH;AAChH,eAAO,MAAM,kBAAkB,EAAE,SAAS,IAAI,EAgF7C,CAAC"}
@@ -25,6 +25,7 @@ import { declarationsConstructorInjection } from '../rules/declarations-construc
25
25
  import { declarationsRoleDeclared } from '../rules/declarations-role-declared.js';
26
26
  import { domainEventsDispatchedOnSave } from '../rules/domain-events-dispatched-on-save.js';
27
27
  import { domainEventsTransactionalBeforeCommit } from '../rules/domain-events-transactional-before-commit.js';
28
+ import { domainValueObjectClass } from '../rules/domain-value-object-class.js';
28
29
  import { errorsCodeUnique } from '../rules/errors-code-unique.js';
29
30
  import { eventSourcingPastTense } from '../rules/event-sourcing-past-tense.js';
30
31
  import { eventSourcingProjectionNeverAppends } from '../rules/event-sourcing-projection-never-appends.js';
@@ -116,6 +117,7 @@ export const ARCHITECTURE_RULES = [
116
117
  cleanBraces,
117
118
  cleanOneExportPerFile,
118
119
  cleanFileSize,
120
+ domainValueObjectClass,
119
121
  eventsOutboxRequired,
120
122
  eventsConsumerIdempotent,
121
123
  domainEventsDispatchedOnSave,
@@ -1 +1 @@
1
- {"version":3,"file":"architecture-rules.js","sourceRoot":"","sources":["../../../src/check/catalogue/architecture-rules.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"architecture-rules.js","sourceRoot":"","sources":["../../../src/check/catalogue/architecture-rules.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { RuleDoc } from '../model/rule-doc.js';
2
+ export declare const DOMAIN_DOCS: Readonly<Record<string, RuleDoc>>;
3
+ //# sourceMappingURL=domain-docs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domain-docs.d.ts","sourceRoot":"","sources":["../../../src/check/explain/domain-docs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAEpD,eAAO,MAAM,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA4BzD,CAAC"}