@ddd-ts-kit/eslint-plugin 0.9.0 → 0.10.0

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 (66) hide show
  1. package/README.md +6 -1
  2. package/dist/rules/event-sourcing-past-tense.d.ts +2 -0
  3. package/dist/rules/event-sourcing-past-tense.d.ts.map +1 -0
  4. package/dist/rules/event-sourcing-past-tense.js +52 -0
  5. package/dist/rules/event-sourcing-past-tense.js.map +1 -0
  6. package/dist/rules/event-sourcing-projection-never-appends.d.ts +3 -0
  7. package/dist/rules/event-sourcing-projection-never-appends.d.ts.map +1 -0
  8. package/dist/rules/event-sourcing-projection-never-appends.js +72 -0
  9. package/dist/rules/event-sourcing-projection-never-appends.js.map +1 -0
  10. package/dist/rules/event-sourcing-state-in-apply.d.ts +4 -0
  11. package/dist/rules/event-sourcing-state-in-apply.d.ts.map +1 -0
  12. package/dist/rules/event-sourcing-state-in-apply.js +81 -0
  13. package/dist/rules/event-sourcing-state-in-apply.js.map +1 -0
  14. package/dist/rules/event-sourcing-store-behind-repository.d.ts +2 -0
  15. package/dist/rules/event-sourcing-store-behind-repository.d.ts.map +1 -0
  16. package/dist/rules/event-sourcing-store-behind-repository.js +32 -0
  17. package/dist/rules/event-sourcing-store-behind-repository.js.map +1 -0
  18. package/dist/rules/event-sourcing-upcaster-required.d.ts +4 -0
  19. package/dist/rules/event-sourcing-upcaster-required.d.ts.map +1 -0
  20. package/dist/rules/event-sourcing-upcaster-required.js +96 -0
  21. package/dist/rules/event-sourcing-upcaster-required.js.map +1 -0
  22. package/dist/rules/rules.d.ts.map +1 -1
  23. package/dist/rules/rules.js +10 -0
  24. package/dist/rules/rules.js.map +1 -1
  25. package/dist/support/event-sourcing/fact-words.d.ts +6 -0
  26. package/dist/support/event-sourcing/fact-words.d.ts.map +1 -0
  27. package/dist/support/event-sourcing/fact-words.js +10 -0
  28. package/dist/support/event-sourcing/fact-words.js.map +1 -0
  29. package/dist/support/event-sourcing/is-aggregate-file.d.ts +4 -0
  30. package/dist/support/event-sourcing/is-aggregate-file.d.ts.map +1 -0
  31. package/dist/support/event-sourcing/is-aggregate-file.js +6 -0
  32. package/dist/support/event-sourcing/is-aggregate-file.js.map +1 -0
  33. package/dist/support/event-sourcing/is-event-sourced-class.d.ts +4 -0
  34. package/dist/support/event-sourcing/is-event-sourced-class.d.ts.map +1 -0
  35. package/dist/support/event-sourcing/is-event-sourced-class.js +8 -0
  36. package/dist/support/event-sourcing/is-event-sourced-class.js.map +1 -0
  37. package/dist/support/event-sourcing/is-past-fact.d.ts +3 -0
  38. package/dist/support/event-sourcing/is-past-fact.d.ts.map +1 -0
  39. package/dist/support/event-sourcing/is-past-fact.js +15 -0
  40. package/dist/support/event-sourcing/is-past-fact.js.map +1 -0
  41. package/dist/support/event-sourcing/is-past-participle.d.ts +3 -0
  42. package/dist/support/event-sourcing/is-past-participle.d.ts.map +1 -0
  43. package/dist/support/event-sourcing/is-past-participle.js +113 -0
  44. package/dist/support/event-sourcing/is-past-participle.js.map +1 -0
  45. package/dist/support/event-sourcing/kernel-names-listener.d.ts +6 -0
  46. package/dist/support/event-sourcing/kernel-names-listener.d.ts.map +1 -0
  47. package/dist/support/event-sourcing/kernel-names-listener.js +10 -0
  48. package/dist/support/event-sourcing/kernel-names-listener.js.map +1 -0
  49. package/dist/support/event-sourcing/unresolved-types.d.ts +7 -0
  50. package/dist/support/event-sourcing/unresolved-types.d.ts.map +1 -0
  51. package/dist/support/event-sourcing/unresolved-types.js +49 -0
  52. package/dist/support/event-sourcing/unresolved-types.js.map +1 -0
  53. package/dist/support/event-sourcing/words-of.d.ts +3 -0
  54. package/dist/support/event-sourcing/words-of.d.ts.map +1 -0
  55. package/dist/support/event-sourcing/words-of.js +9 -0
  56. package/dist/support/event-sourcing/words-of.js.map +1 -0
  57. package/dist/version.d.ts +1 -1
  58. package/dist/version.d.ts.map +1 -1
  59. package/dist/version.js +1 -1
  60. package/dist/version.js.map +1 -1
  61. package/docs/rules/event-sourcing-past-tense.md +40 -0
  62. package/docs/rules/event-sourcing-projection-never-appends.md +49 -0
  63. package/docs/rules/event-sourcing-state-in-apply.md +51 -0
  64. package/docs/rules/event-sourcing-store-behind-repository.md +39 -0
  65. package/docs/rules/event-sourcing-upcaster-required.md +55 -0
  66. package/package.json +1 -1
package/README.md CHANGED
@@ -116,6 +116,11 @@ is an error whatever the configured limit). `strict` sets every rule to `error`.
116
116
  | [`application/command-query-separation`](docs/rules/application-command-query-separation.md) | `ddd-ts-kit/application-command-query-separation` | nothing under `application/queries` calls `save`, `delete`, `publish` on a dependency | `warn` | custom |
117
117
  | [`domain-events/dispatched-on-save`](docs/rules/domain-events-dispatched-on-save.md) | `ddd-ts-kit/domain-events-dispatched-on-save` | no `pullEvents()` in `application/` and `infrastructure/`; no `DomainEventDispatcher` imported, listed as a dependency or received by a constructor in `application/commands`, `application/queries` and a repository adapter | `error` | custom |
118
118
  | [`domain-events/transactional-before-commit`](docs/rules/domain-events-transactional-before-commit.md) | `ddd-ts-kit/domain-events-transactional-before-commit` | a class carrying `@DomainEventHandler` without `phase: 'after-commit'` depends on no `Cache`, `Lock`, `RateLimiter`, `JobScheduler` or `EventBus` of the kernel and on nothing imported from `infrastructure/`, another context, the platform or a package other than `@ddd-ts-kit/kernel` and `@ddd-ts-kit/auth`; an after-commit handler does not depend on `IntegrationEventPublisher`. The adapters of an `application/ports/` port and the phase of an explicit-style handler are held by the CLI checker | `error` | custom |
119
+ | [`event-sourcing/state-in-apply`](docs/rules/event-sourcing-state-in-apply.md) | `ddd-ts-kit/event-sourcing-state-in-apply` | an event-sourced aggregate changes its state only in its apply methods (`apply<Fact>`, `restore`), and an apply method records no event | `error` | custom |
120
+ | [`event-sourcing/past-tense`](docs/rules/event-sourcing-past-tense.md) | `ddd-ts-kit/event-sourcing-past-tense` | the events an event-sourced aggregate imports from `domain/events/` are named in the past tense | `error` | custom |
121
+ | [`event-sourcing/store-behind-repository`](docs/rules/event-sourcing-store-behind-repository.md) | `ddd-ts-kit/event-sourcing-store-behind-repository` | no `EventStore`, `SnapshotStore`, `CheckpointStore`, `EventSourcedRepository`, `EventSerializer`, `CatchUpSubscription` in `domain/` and `application/`: the aggregate goes through its repository | `error` | custom |
122
+ | [`event-sourcing/projection-never-appends`](docs/rules/event-sourcing-projection-never-appends.md) | `ddd-ts-kit/event-sourcing-projection-never-appends` | a projection names no event store, and one fed by the store no repository: it never appends to a stream | `error` | custom |
123
+ | [`event-sourcing/upcaster-required`](docs/rules/event-sourcing-upcaster-required.md) | `ddd-ts-kit/event-sourcing-upcaster-required` | `defineStoredEvent` names the type of its data, declared in the file, and a version above 1 has an upcaster from each version below (the lock of recorded shapes: `ddd-ts-kit check`) | `error` | custom |
119
124
  | [`test/no-mocks`](docs/rules/test-no-mocks.md) | `ddd-ts-kit/test-no-mocks` | no `vi.mock`, `vi.fn`, `vi.spyOn`, their `jest` twins or a mocking library in a context's tests | `warn` | custom |
120
125
  | [`events/name-format`](docs/rules/events-name-format.md) | `ddd-ts-kit/events-name-format` | event names follow `<context>.<aggregate>.<fact>` in kebab-case | `error` | custom |
121
126
  | [`events/outbox-required`](docs/rules/events-outbox-required.md) | `ddd-ts-kit/events-outbox-required` | no broker client and no concrete bus in `domain/` and `application/`; a publisher depends on the `IntegrationEventPublisher` port | `error` | custom |
@@ -258,7 +263,7 @@ Two tools, one set of ids, no overlap of authority.
258
263
 
259
264
  | Concern | Authority | Why |
260
265
  |---|---|---|
261
- | imports and boundaries (`layer/*`, `context/published-language-only`, `context/declared-relationship`, `context/published-language-is-a-contract`), `purity/*`, `clean/*`, `test/no-mocks`, `events/name-format`, `domain/*`, `application/*`, the code side of `events/outbox-required`, `events/consumer-idempotent`, `context/extractable`, `authz/*`, `aspects/outside-in`, `tracing/platform-only`, `domain-events/dispatched-on-save`, `domain-events/transactional-before-commit` on a declared class, `standard/no-parallel-interface`, `declarations/role-declared`, `ports/*`, `scheduling/*` (but the uniqueness of job names) and `governance/recorded-deviation` | this plugin | it reads the syntax tree: no false positive from a string or a comment, shadowing and arrow functions are understood |
266
+ | imports and boundaries (`layer/*`, `context/published-language-only`, `context/declared-relationship`, `context/published-language-is-a-contract`), `purity/*`, `clean/*`, `test/no-mocks`, `events/name-format`, `domain/*`, `application/*`, the code side of `events/outbox-required`, `events/consumer-idempotent`, `context/extractable`, `authz/*`, `aspects/outside-in`, `tracing/platform-only`, `domain-events/dispatched-on-save`, `domain-events/transactional-before-commit` on a declared class, `event-sourcing/*` (but the lock of `event-sourcing/upcaster-required`), `standard/no-parallel-interface`, `declarations/role-declared`, `ports/*`, `scheduling/*` (but the uniqueness of job names) and `governance/recorded-deviation` | this plugin | it reads the syntax tree: no false positive from a string or a comment, shadowing and arrow functions are understood |
262
267
  | the tree (`tree/closed`, `tree/context-doc`), names and files (`naming/*`, `test/spec-required`, `test/builder-required`), the language (`language/*`), the config (`config/*`, `context/no-cycle`), `events/defined-in-published-language`, `composition/only-in-module`, `declarations/barrel-current`, unique consumer names and job names, one declared class per role file, of `domain-events/transactional-before-commit` the adapters of an `application/ports/` port and the phase of an explicit-style handler, the config side of the ids above (`stack.events`, lowered severities, that a decision record exists) | `ddd-ts-kit check` | they judge the project, the glossary and files that are not sources; ESLint judges one source at a time |
263
268
  | the real dependency graph (barrels, cycles, orphans, dev dependencies in production code) | dependency-cruiser, configured by the `.dependency-cruiser.cjs` that `ddd-ts-kit depcruise-config --write` generates from the config | it resolves modules instead of classifying paths |
264
269
 
@@ -0,0 +1,2 @@
1
+ export declare const eventSourcingPastTense: import("@typescript-eslint/utils/ts-eslint").RuleModule<"presentTense", [], import("../index.js").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
2
+ //# sourceMappingURL=event-sourcing-past-tense.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-sourcing-past-tense.d.ts","sourceRoot":"","sources":["../../src/rules/event-sourcing-past-tense.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,sBAAsB,wKA8CjC,CAAC"}
@@ -0,0 +1,52 @@
1
+ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
2
+ import { isAggregateFile } from '../support/event-sourcing/is-aggregate-file.js';
3
+ import { isEventSourcedClass } from '../support/event-sourcing/is-event-sourced-class.js';
4
+ import { factWords } from '../support/event-sourcing/fact-words.js';
5
+ import { isPastFact } from '../support/event-sourcing/is-past-fact.js';
6
+ import { importListener } from '../support/imports/import-listener.js';
7
+ import { defineRule } from '../support/rule/define-rule.js';
8
+ export const eventSourcingPastTense = defineRule({
9
+ id: 'event-sourcing/past-tense',
10
+ type: 'suggestion',
11
+ recommended: 'error',
12
+ description: 'The events an event-sourced aggregate imports from domain/events/ are named in the past tense: one word of the fact, the name after the aggregate, is a past participle that does not read as a command at its head.',
13
+ messages: {
14
+ presentTense: 'The event {{name}} is not named in the past tense. Name it after what happened (`InvoiceIssued`, `NoShowNoted`, `AmountWithdrawn`): a stored event is a fact replayed for years, a name in the present reads as a command.',
15
+ },
16
+ create({ file, report }) {
17
+ if (!isAggregateFile(file)) {
18
+ return {};
19
+ }
20
+ const events = [];
21
+ const aggregates = [];
22
+ return {
23
+ ...importListener(file, ({ target, names }) => {
24
+ const fromEvents = target.kind === 'file' &&
25
+ target.location.area === 'context' &&
26
+ target.location.layer === 'domain' &&
27
+ target.location.inside[0] === 'events';
28
+ if (fromEvents) {
29
+ events.push(...names);
30
+ }
31
+ }),
32
+ ClassDeclaration(node) {
33
+ if (isEventSourcedClass(node) && node.id !== null) {
34
+ aggregates.push(node.id.name);
35
+ }
36
+ },
37
+ 'Program:exit'() {
38
+ for (const imported of aggregates.length > 0 ? events : []) {
39
+ const name = localName(imported);
40
+ if (!isPastFact(factWords(name, aggregates))) {
41
+ report({ node: imported.node, messageId: 'presentTense', data: { name } });
42
+ }
43
+ }
44
+ },
45
+ };
46
+ },
47
+ });
48
+ /** The name the aggregate writes, as the checker reads it: the local one of a renamed import. */
49
+ function localName({ name, node }) {
50
+ return node.type === AST_NODE_TYPES.ImportSpecifier ? node.local.name : name;
51
+ }
52
+ //# sourceMappingURL=event-sourcing-past-tense.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-sourcing-past-tense.js","sourceRoot":"","sources":["../../src/rules/event-sourcing-past-tense.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,gDAAgD,CAAC;AACjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,qDAAqD,CAAC;AAC1F,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,MAAM,2CAA2C,CAAC;AACvE,OAAO,EAAE,cAAc,EAAqB,MAAM,uCAAuC,CAAC;AAC1F,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAE5D,MAAM,CAAC,MAAM,sBAAsB,GAAG,UAAU,CAAiB;IAC/D,EAAE,EAAE,2BAA2B;IAC/B,IAAI,EAAE,YAAY;IAClB,WAAW,EAAE,OAAO;IACpB,WAAW,EACT,sNAAsN;IACxN,QAAQ,EAAE;QACR,YAAY,EACV,4NAA4N;KAC/N;IACD,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE;QACrB,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3B,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,MAAM,GAAmB,EAAE,CAAC;QAClC,MAAM,UAAU,GAAa,EAAE,CAAC;QAEhC,OAAO;YACL,GAAG,cAAc,CAAC,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE;gBAC5C,MAAM,UAAU,GACd,MAAM,CAAC,IAAI,KAAK,MAAM;oBACtB,MAAM,CAAC,QAAQ,CAAC,IAAI,KAAK,SAAS;oBAClC,MAAM,CAAC,QAAQ,CAAC,KAAK,KAAK,QAAQ;oBAClC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC;gBAEzC,IAAI,UAAU,EAAE,CAAC;oBACf,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC;gBACxB,CAAC;YACH,CAAC,CAAC;YACF,gBAAgB,CAAC,IAAI;gBACnB,IAAI,mBAAmB,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC;oBAClD,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;gBAChC,CAAC;YACH,CAAC;YACD,cAAc;gBACZ,KAAK,MAAM,QAAQ,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;oBAC3D,MAAM,IAAI,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;oBAEjC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC;wBAC7C,MAAM,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,EAAE,cAAc,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;oBAC7E,CAAC;gBACH,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC;AAEH,iGAAiG;AACjG,SAAS,SAAS,CAAC,EAAE,IAAI,EAAE,IAAI,EAAgB;IAC7C,OAAO,IAAI,CAAC,IAAI,KAAK,cAAc,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;AAC/E,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { type TSESLint } from '@typescript-eslint/utils';
2
+ export declare const eventSourcingProjectionNeverAppends: TSESLint.RuleModule<"appends", [], import("../index.js").RuleDocs, TSESLint.RuleListener>;
3
+ //# sourceMappingURL=event-sourcing-projection-never-appends.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-sourcing-projection-never-appends.d.ts","sourceRoot":"","sources":["../../src/rules/event-sourcing-projection-never-appends.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,QAAQ,EAAiB,MAAM,0BAA0B,CAAC;AAQxF,eAAO,MAAM,mCAAmC,2FA6C9C,CAAC"}
@@ -0,0 +1,72 @@
1
+ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
2
+ import { kernelNamesListener } from '../support/event-sourcing/kernel-names-listener.js';
3
+ import { importListener } from '../support/imports/import-listener.js';
4
+ import { defineRule } from '../support/rule/define-rule.js';
5
+ const APPENDING = new Set(['EventStore', 'EventSourcedRepository']);
6
+ export const eventSourcingProjectionNeverAppends = defineRule({
7
+ id: 'event-sourcing/projection-never-appends',
8
+ type: 'problem',
9
+ recommended: 'error',
10
+ description: 'A projection never appends to a stream: it names no event store, and a projection fed by the event store names no repository of domain/repositories/ either.',
11
+ messages: {
12
+ appends: 'The projection names {{name}}: it could append to a stream, and would append again at every rebuild. A projection writes its read model through its port; a fact worth keeping is decided by a command.',
13
+ },
14
+ create({ file, report }) {
15
+ const { location } = file;
16
+ if (location.area !== 'context' || file.isTest) {
17
+ return {};
18
+ }
19
+ const named = [];
20
+ const repositoryNames = [];
21
+ const listeners = [
22
+ kernelNamesListener(file, APPENDING, (imported) => named.push(imported)),
23
+ repositories(file, repositoryNames),
24
+ ];
25
+ let declared = location.layer === 'application' && location.inside[0] === 'projections';
26
+ let storeFed = false;
27
+ return {
28
+ ImportDeclaration(node) {
29
+ listeners.forEach((listener) => listener.ImportDeclaration?.(node));
30
+ },
31
+ Decorator(node) {
32
+ declared ||= isProjectionDecorator(node);
33
+ storeFed ||= isProjectionDecorator(node) && feedsFromStore(node);
34
+ },
35
+ 'Program:exit'() {
36
+ const appending = [...named, ...(storeFed ? repositoryNames : [])];
37
+ for (const { name, node } of declared ? appending : []) {
38
+ report({ node, messageId: 'appends', data: { name } });
39
+ }
40
+ },
41
+ };
42
+ },
43
+ });
44
+ /** Notes what the file imports from `domain/repositories/`. */
45
+ function repositories(file, named) {
46
+ return importListener(file, ({ target, names }) => {
47
+ const isRepository = target.kind === 'file' &&
48
+ target.location.area === 'context' &&
49
+ target.location.layer === 'domain' &&
50
+ target.location.inside[0] === 'repositories';
51
+ if (isRepository) {
52
+ named.push(...names);
53
+ }
54
+ });
55
+ }
56
+ /** `@Projection(name, { feed: 'event-store' })`: replayed from position zero at every rebuild. */
57
+ function feedsFromStore({ expression }) {
58
+ const options = expression.type === AST_NODE_TYPES.CallExpression ? expression.arguments[1] : undefined;
59
+ return (options?.type === AST_NODE_TYPES.ObjectExpression &&
60
+ options.properties.some((property) => property.type === AST_NODE_TYPES.Property &&
61
+ property.key.type === AST_NODE_TYPES.Identifier &&
62
+ property.key.name === 'feed' &&
63
+ property.value.type === AST_NODE_TYPES.Literal &&
64
+ property.value.value === 'event-store'));
65
+ }
66
+ function isProjectionDecorator(node) {
67
+ const { expression } = node;
68
+ return (expression.type === AST_NODE_TYPES.CallExpression &&
69
+ expression.callee.type === AST_NODE_TYPES.Identifier &&
70
+ expression.callee.name === 'Projection');
71
+ }
72
+ //# sourceMappingURL=event-sourcing-projection-never-appends.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-sourcing-projection-never-appends.js","sourceRoot":"","sources":["../../src/rules/event-sourcing-projection-never-appends.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ type MessageId = 'outsideApply' | 'recordedInApply';
2
+ export declare const eventSourcingStateInApply: import("@typescript-eslint/utils/ts-eslint").RuleModule<MessageId, [], import("../index.js").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
3
+ export {};
4
+ //# sourceMappingURL=event-sourcing-state-in-apply.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-sourcing-state-in-apply.d.ts","sourceRoot":"","sources":["../../src/rules/event-sourcing-state-in-apply.ts"],"names":[],"mappings":"AAkCA,KAAK,SAAS,GAAG,cAAc,GAAG,iBAAiB,CAAC;AAEpD,eAAO,MAAM,yBAAyB,mKAiCpC,CAAC"}
@@ -0,0 +1,81 @@
1
+ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
2
+ import { className } from '../support/classes/class-name.js';
3
+ import { enclosingMember } from '../support/classes/enclosing-member.js';
4
+ import { memberName } from '../support/classes/member-name.js';
5
+ import { isAggregateFile } from '../support/event-sourcing/is-aggregate-file.js';
6
+ import { isEventSourcedClass } from '../support/event-sourcing/is-event-sourced-class.js';
7
+ import { propertyName } from '../support/globals/property-name.js';
8
+ import { startsAtThis } from '../support/members/starts-at-this.js';
9
+ import { defineRule } from '../support/rule/define-rule.js';
10
+ const MUTATORS = new Set([
11
+ 'push',
12
+ 'pop',
13
+ 'shift',
14
+ 'unshift',
15
+ 'splice',
16
+ 'sort',
17
+ 'reverse',
18
+ 'fill',
19
+ 'copyWithin',
20
+ 'set',
21
+ 'add',
22
+ 'delete',
23
+ 'clear',
24
+ ]);
25
+ const APPLY = /^apply[A-Z]/;
26
+ export const eventSourcingStateInApply = defineRule({
27
+ id: 'event-sourcing/state-in-apply',
28
+ type: 'problem',
29
+ recommended: 'error',
30
+ description: 'An event-sourced aggregate changes its state only in its apply methods (`apply<Fact>`, `restore` for a snapshot), and an apply method records no event.',
31
+ messages: {
32
+ outsideApply: '{{method}} of the event-sourced aggregate {{className}} writes {{target}} outside an apply method. Record the event of what the command decided, `this.record(new <Fact>(...))`, and change the state in `apply<Fact>(event)`: a replay rebuilds only what the apply methods write.',
33
+ recordedInApply: 'The apply method {{method}} of {{className}} records an event: a replay would record it again. Record the follow-up fact from the command that decides it.',
34
+ },
35
+ create({ file, sourceCode, report }) {
36
+ if (!isAggregateFile(file)) {
37
+ return {};
38
+ }
39
+ const written = (node, target) => {
40
+ const judged = judgedAt(node);
41
+ if (judged !== undefined && !judged.applies && startsAtThis(target)) {
42
+ const data = { method: judged.method, className: judged.owner, target: sourceCode.getText(target) };
43
+ report({ node, messageId: 'outsideApply', data });
44
+ }
45
+ };
46
+ return {
47
+ AssignmentExpression: (node) => written(node, node.left),
48
+ UpdateExpression: (node) => written(node, node.argument),
49
+ CallExpression: (node) => called(node, written, report),
50
+ };
51
+ },
52
+ });
53
+ /** A mutating call writes what it is called on; `this.record(..)` in an apply method records again at each replay. */
54
+ function called(node, written, report) {
55
+ if (node.callee.type !== AST_NODE_TYPES.MemberExpression) {
56
+ return;
57
+ }
58
+ const name = propertyName(node.callee) ?? '';
59
+ if (MUTATORS.has(name)) {
60
+ written(node, node.callee.object);
61
+ }
62
+ const judged = judgedAt(node);
63
+ if (name === 'record' && node.callee.object.type === AST_NODE_TYPES.ThisExpression && judged?.applies === true) {
64
+ report({ node, messageId: 'recordedInApply', data: { method: judged.method, className: judged.owner } });
65
+ }
66
+ }
67
+ /** The method of an event-sourced class around the node, unless it is the constructor or a static method. */
68
+ function judgedAt(node) {
69
+ const site = enclosingMember(node);
70
+ if (site === undefined || !isEventSourcedClass(site.body.parent)) {
71
+ return undefined;
72
+ }
73
+ const { member } = site;
74
+ const isMethod = member.type === AST_NODE_TYPES.MethodDefinition;
75
+ if (!isMethod || member.static || member.kind === 'constructor') {
76
+ return undefined;
77
+ }
78
+ const method = memberName(member) ?? '';
79
+ return { method, owner: className(site.body.parent), applies: APPLY.test(method) || method === 'restore' };
80
+ }
81
+ //# sourceMappingURL=event-sourcing-state-in-apply.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-sourcing-state-in-apply.js","sourceRoot":"","sources":["../../src/rules/event-sourcing-state-in-apply.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare const eventSourcingStoreBehindRepository: import("@typescript-eslint/utils/ts-eslint").RuleModule<"store", [], import("../index.js").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
2
+ //# sourceMappingURL=event-sourcing-store-behind-repository.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-sourcing-store-behind-repository.d.ts","sourceRoot":"","sources":["../../src/rules/event-sourcing-store-behind-repository.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,kCAAkC,iKAyB7C,CAAC"}
@@ -0,0 +1,32 @@
1
+ import { kernelNamesListener } from '../support/event-sourcing/kernel-names-listener.js';
2
+ import { defineRule } from '../support/rule/define-rule.js';
3
+ const STORE_NAMES = new Set([
4
+ 'EventStore',
5
+ 'SnapshotStore',
6
+ 'CheckpointStore',
7
+ 'EventSourcedRepository',
8
+ 'EventSerializer',
9
+ 'CatchUpSubscription',
10
+ 'InMemoryEventStore',
11
+ ]);
12
+ export const eventSourcingStoreBehindRepository = defineRule({
13
+ id: 'event-sourcing/store-behind-repository',
14
+ type: 'problem',
15
+ recommended: 'error',
16
+ description: 'domain/ and application/ never name the event store, its snapshots, checkpoints, serializer or subscriptions: an event-sourced aggregate is loaded and saved through its repository port.',
17
+ messages: {
18
+ store: '{{name}} is named in {{layer}}/: the domain and the use cases never read the event store. Load and save the aggregate through its repository port; its adapter in infrastructure/persistence/ delegates to EventSourcedRepository, and a read across streams is a projection fed by the store.',
19
+ },
20
+ create({ file, report }) {
21
+ const { location } = file;
22
+ if (location.area !== 'context' ||
23
+ file.isTest ||
24
+ (location.layer !== 'domain' && location.layer !== 'application')) {
25
+ return {};
26
+ }
27
+ return kernelNamesListener(file, STORE_NAMES, ({ name, node }) => {
28
+ report({ node, messageId: 'store', data: { name, layer: location.layer } });
29
+ });
30
+ },
31
+ });
32
+ //# sourceMappingURL=event-sourcing-store-behind-repository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-sourcing-store-behind-repository.js","sourceRoot":"","sources":["../../src/rules/event-sourcing-store-behind-repository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,oDAAoD,CAAC;AACzF,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAE5D,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC;IAC1B,YAAY;IACZ,eAAe;IACf,iBAAiB;IACjB,wBAAwB;IACxB,iBAAiB;IACjB,qBAAqB;IACrB,oBAAoB;CACrB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,kCAAkC,GAAG,UAAU,CAAU;IACpE,EAAE,EAAE,wCAAwC;IAC5C,IAAI,EAAE,SAAS;IACf,WAAW,EAAE,OAAO;IACpB,WAAW,EACT,2LAA2L;IAC7L,QAAQ,EAAE;QACR,KAAK,EACH,gSAAgS;KACnS;IACD,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE;QACrB,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;QAE1B,IACE,QAAQ,CAAC,IAAI,KAAK,SAAS;YAC3B,IAAI,CAAC,MAAM;YACX,CAAC,QAAQ,CAAC,KAAK,KAAK,QAAQ,IAAI,QAAQ,CAAC,KAAK,KAAK,aAAa,CAAC,EACjE,CAAC;YACD,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,OAAO,mBAAmB,CAAC,IAAI,EAAE,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE;YAC/D,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC9E,CAAC,CAAC,CAAC;IACL,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,4 @@
1
+ type MessageId = 'unnamed' | 'undeclared' | 'unresolved' | 'missingUpcaster';
2
+ export declare const eventSourcingUpcasterRequired: import("@typescript-eslint/utils/ts-eslint").RuleModule<MessageId, [], import("../index.js").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
3
+ export {};
4
+ //# sourceMappingURL=event-sourcing-upcaster-required.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-sourcing-upcaster-required.d.ts","sourceRoot":"","sources":["../../src/rules/event-sourcing-upcaster-required.ts"],"names":[],"mappings":"AAIA,KAAK,SAAS,GAAG,SAAS,GAAG,YAAY,GAAG,YAAY,GAAG,iBAAiB,CAAC;AAQ7E,eAAO,MAAM,6BAA6B,mKA2CxC,CAAC"}
@@ -0,0 +1,96 @@
1
+ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
2
+ import { unresolvedTypes } from '../support/event-sourcing/unresolved-types.js';
3
+ import { defineRule } from '../support/rule/define-rule.js';
4
+ export const eventSourcingUpcasterRequired = defineRule({
5
+ id: 'event-sourcing/upcaster-required',
6
+ type: 'problem',
7
+ recommended: 'error',
8
+ description: 'A stored event names the type of its data in `encode` and declares it, with every named type it reaches, in its codec file, and a version above 1 has an upcaster from each version below. The lock of recorded shapes is read by `ddd-ts-kit check`.',
9
+ messages: {
10
+ unnamed: 'The encode of {{eventClass}} does not name the type of the data it stores. Write the stored shape as a named type in this file and answer it: `encode: (event): InvoiceIssuedData => ({ ... })`.',
11
+ undeclared: '{{type}}, the data of {{eventClass}}, is not declared in this file. Declare it next to the events it stores: it is the stored shape, kept even when the domain event changes.',
12
+ unresolved: '{{name}}, reached by {{type}} of {{eventClass}}, is not declared in this file: a change of it would change the stored shape unseen. Declare its stored shape in the codec file too, mapped from the domain type in encode.',
13
+ missingUpcaster: '{{eventClass}} is stored in version {{version}} and has no upcaster from version {{from}}. Add `upcasters: { {{from}}: (data) => ({ ...data }) }`: events stored in version {{from}} are read through it.',
14
+ },
15
+ create({ report }) {
16
+ const declared = new Map();
17
+ const stored = [];
18
+ return {
19
+ TSInterfaceDeclaration: (node) => declared.set(node.id.name, node),
20
+ TSTypeAliasDeclaration: (node) => declared.set(node.id.name, node),
21
+ TSEnumDeclaration: (node) => declared.set(node.id.name, node),
22
+ CallExpression(node) {
23
+ const [type, spec] = node.arguments;
24
+ if (node.callee.type === AST_NODE_TYPES.Identifier &&
25
+ node.callee.name === 'defineStoredEvent' &&
26
+ type?.type === AST_NODE_TYPES.Identifier &&
27
+ spec?.type === AST_NODE_TYPES.ObjectExpression) {
28
+ stored.push({ node, eventClass: type.name, spec });
29
+ }
30
+ },
31
+ 'Program:exit'() {
32
+ for (const event of stored) {
33
+ judge(event, declared, report);
34
+ }
35
+ },
36
+ };
37
+ },
38
+ });
39
+ function judge(event, declared, report) {
40
+ const { node, eventClass, spec } = event;
41
+ judgeShape(event, declared, report);
42
+ const version = numberOf(property(spec, 'version'));
43
+ const upcasters = upcasterKeys(property(spec, 'upcasters'));
44
+ for (let from = 1; from < (version ?? 1); from += 1) {
45
+ if (!upcasters.includes(from)) {
46
+ report({
47
+ node,
48
+ messageId: 'missingUpcaster',
49
+ data: { eventClass, version: String(version), from: String(from) },
50
+ });
51
+ }
52
+ }
53
+ }
54
+ /** The data type is named, declared in the file, and so is every named type it reaches. */
55
+ function judgeShape({ node, eventClass, spec }, declared, report) {
56
+ const type = payloadType(property(spec, 'encode'));
57
+ if (type === undefined) {
58
+ report({ node, messageId: 'unnamed', data: { eventClass } });
59
+ }
60
+ else if (!declared.has(type)) {
61
+ report({ node, messageId: 'undeclared', data: { eventClass, type } });
62
+ }
63
+ else {
64
+ for (const name of unresolvedTypes(type, declared)) {
65
+ report({ node, messageId: 'unresolved', data: { eventClass, type, name } });
66
+ }
67
+ }
68
+ }
69
+ function property(spec, name) {
70
+ const found = spec.properties.find((candidate) => candidate.type === AST_NODE_TYPES.Property && keyOf(candidate.key) === name);
71
+ return found?.value;
72
+ }
73
+ function keyOf(key) {
74
+ if (key.type === AST_NODE_TYPES.Identifier) {
75
+ return key.name;
76
+ }
77
+ return key.type === AST_NODE_TYPES.Literal ? String(key.value) : undefined;
78
+ }
79
+ /** The name of the type an `encode` arrow, function or method declares as its return type. */
80
+ function payloadType(encode) {
81
+ const isFunction = encode?.type === AST_NODE_TYPES.ArrowFunctionExpression || encode?.type === AST_NODE_TYPES.FunctionExpression;
82
+ const annotation = isFunction ? encode.returnType?.typeAnnotation : undefined;
83
+ return annotation?.type === AST_NODE_TYPES.TSTypeReference && annotation.typeName.type === AST_NODE_TYPES.Identifier
84
+ ? annotation.typeName.name
85
+ : undefined;
86
+ }
87
+ function numberOf(value) {
88
+ return value?.type === AST_NODE_TYPES.Literal && typeof value.value === 'number' ? value.value : undefined;
89
+ }
90
+ function upcasterKeys(value) {
91
+ if (value?.type !== AST_NODE_TYPES.ObjectExpression) {
92
+ return [];
93
+ }
94
+ return value.properties.flatMap((candidate) => candidate.type === AST_NODE_TYPES.Property ? [Number(keyOf(candidate.key))] : []);
95
+ }
96
+ //# sourceMappingURL=event-sourcing-upcaster-required.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-sourcing-upcaster-required.js","sourceRoot":"","sources":["../../src/rules/event-sourcing-upcaster-required.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../src/rules/rules.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAwHlE,+HAA+H;AAC/H,eAAO,MAAM,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEnD,CAAC"}
1
+ {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../src/rules/rules.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAkIlE,+HAA+H;AAC/H,eAAO,MAAM,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEnD,CAAC"}
@@ -23,6 +23,11 @@ import { domainAggregateEncapsulation } from './domain-aggregate-encapsulation.j
23
23
  import { domainEventsDispatchedOnSave } from './domain-events-dispatched-on-save.js';
24
24
  import { domainEventsTransactionalBeforeCommit } from './domain-events-transactional-before-commit.js';
25
25
  import { domainNoGenericError } from './domain-no-generic-error.js';
26
+ import { eventSourcingPastTense } from './event-sourcing-past-tense.js';
27
+ import { eventSourcingProjectionNeverAppends } from './event-sourcing-projection-never-appends.js';
28
+ import { eventSourcingStateInApply } from './event-sourcing-state-in-apply.js';
29
+ import { eventSourcingStoreBehindRepository } from './event-sourcing-store-behind-repository.js';
30
+ import { eventSourcingUpcasterRequired } from './event-sourcing-upcaster-required.js';
26
31
  import { errorsConstructorNotPublic } from './errors-constructor-not-public.js';
27
32
  import { errorsFactoryName } from './errors-factory-name.js';
28
33
  import { errorsThroughFactory } from './errors-through-factory.js';
@@ -85,6 +90,11 @@ const ALL = [
85
90
  applicationCommandQuerySeparation,
86
91
  domainEventsDispatchedOnSave,
87
92
  domainEventsTransactionalBeforeCommit,
93
+ eventSourcingStateInApply,
94
+ eventSourcingPastTense,
95
+ eventSourcingStoreBehindRepository,
96
+ eventSourcingProjectionNeverAppends,
97
+ eventSourcingUpcasterRequired,
88
98
  testNoMocks,
89
99
  eventsNameFormat,
90
100
  eventsOutboxRequired,
@@ -1 +1 @@
1
- {"version":3,"file":"rules.js","sourceRoot":"","sources":["../../src/rules/rules.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"rules.js","sourceRoot":"","sources":["../../src/rules/rules.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * The words of the fact an event names, after the name of its aggregate: `InvoiceIssued` of
3
+ * `Invoice` is `issued`. The whole name when it does not start with the name of an aggregate.
4
+ */
5
+ export declare function factWords(eventName: string, aggregates: readonly string[]): string[];
6
+ //# sourceMappingURL=fact-words.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fact-words.d.ts","sourceRoot":"","sources":["../../../src/support/event-sourcing/fact-words.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAMpF"}
@@ -0,0 +1,10 @@
1
+ import { wordsOf } from './words-of.js';
2
+ /**
3
+ * The words of the fact an event names, after the name of its aggregate: `InvoiceIssued` of
4
+ * `Invoice` is `issued`. The whole name when it does not start with the name of an aggregate.
5
+ */
6
+ export function factWords(eventName, aggregates) {
7
+ const owner = aggregates.find((aggregate) => eventName.startsWith(aggregate) && /^[A-Z]/.test(eventName.slice(aggregate.length)));
8
+ return wordsOf(owner === undefined ? eventName : eventName.slice(owner.length));
9
+ }
10
+ //# sourceMappingURL=fact-words.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fact-words.js","sourceRoot":"","sources":["../../../src/support/event-sourcing/fact-words.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAExC;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,SAAiB,EAAE,UAA6B;IACxE,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAC3B,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CACnG,CAAC;IAEF,OAAO,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;AAClF,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { LintedFile } from '../../project/linted-file.js';
2
+ /** A production file of `domain/aggregates/` of a context. */
3
+ export declare function isAggregateFile(file: LintedFile): boolean;
4
+ //# sourceMappingURL=is-aggregate-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"is-aggregate-file.d.ts","sourceRoot":"","sources":["../../../src/support/event-sourcing/is-aggregate-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAE/D,8DAA8D;AAC9D,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAMzD"}
@@ -0,0 +1,6 @@
1
+ /** A production file of `domain/aggregates/` of a context. */
2
+ export function isAggregateFile(file) {
3
+ const { location } = file;
4
+ return (location.area === 'context' && !file.isTest && location.layer === 'domain' && location.inside[0] === 'aggregates');
5
+ }
6
+ //# sourceMappingURL=is-aggregate-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"is-aggregate-file.js","sourceRoot":"","sources":["../../../src/support/event-sourcing/is-aggregate-file.ts"],"names":[],"mappings":"AAEA,8DAA8D;AAC9D,MAAM,UAAU,eAAe,CAAC,IAAgB;IAC9C,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;IAE1B,OAAO,CACL,QAAQ,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,QAAQ,CAAC,KAAK,KAAK,QAAQ,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,YAAY,CAClH,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { type TSESTree } from '@typescript-eslint/utils';
2
+ /** Whether a class extends `EventSourcedAggregate` of the kernel, with or without type arguments. */
3
+ export declare function isEventSourcedClass(node: TSESTree.Node | undefined): boolean;
4
+ //# sourceMappingURL=is-event-sourced-class.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"is-event-sourced-class.d.ts","sourceRoot":"","sources":["../../../src/support/event-sourcing/is-event-sourced-class.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEzE,qGAAqG;AACrG,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,GAAG,SAAS,GAAG,OAAO,CAM5E"}
@@ -0,0 +1,8 @@
1
+ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
2
+ /** Whether a class extends `EventSourcedAggregate` of the kernel, with or without type arguments. */
3
+ export function isEventSourcedClass(node) {
4
+ return ((node?.type === AST_NODE_TYPES.ClassDeclaration || node?.type === AST_NODE_TYPES.ClassExpression) &&
5
+ node.superClass?.type === AST_NODE_TYPES.Identifier &&
6
+ node.superClass.name === 'EventSourcedAggregate');
7
+ }
8
+ //# sourceMappingURL=is-event-sourced-class.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"is-event-sourced-class.js","sourceRoot":"","sources":["../../../src/support/event-sourcing/is-event-sourced-class.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAiB,MAAM,0BAA0B,CAAC;AAEzE,qGAAqG;AACrG,MAAM,UAAU,mBAAmB,CAAC,IAA+B;IACjE,OAAO,CACL,CAAC,IAAI,EAAE,IAAI,KAAK,cAAc,CAAC,gBAAgB,IAAI,IAAI,EAAE,IAAI,KAAK,cAAc,CAAC,eAAe,CAAC;QACjG,IAAI,CAAC,UAAU,EAAE,IAAI,KAAK,cAAc,CAAC,UAAU;QACnD,IAAI,CAAC,UAAU,CAAC,IAAI,KAAK,uBAAuB,CACjD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ /** Whether the words of the fact of an event, its name after its aggregate, read as something that happened. */
2
+ export declare function isPastFact(words: readonly string[]): boolean;
3
+ //# sourceMappingURL=is-past-fact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"is-past-fact.d.ts","sourceRoot":"","sources":["../../../src/support/event-sourcing/is-past-fact.ts"],"names":[],"mappings":"AAYA,gHAAgH;AAChH,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAI5D"}
@@ -0,0 +1,15 @@
1
+ import { isPastParticiple } from './is-past-participle.js';
2
+ /**
3
+ * Past participles spelled like the imperative of their verb: at the head of a name they read as
4
+ * a command (`SetPrice`, `ReadInvoice`, `RunReport`), after a noun as a fact (`PriceSet`). The
5
+ * same list as the generator and the checker of the CLI, held equal by a test of the CLI.
6
+ */
7
+ const READS_AS_COMMAND = new Set([
8
+ ...['bid', 'bound', 'cast', 'come', 'become', 'cut', 'forecast', 'found', 'hit', 'left', 'put'],
9
+ ...['quit', 'read', 'rerun', 'reset', 'run', 'set', 'shed', 'shut', 'split', 'spread'],
10
+ ]);
11
+ /** Whether the words of the fact of an event, its name after its aggregate, read as something that happened. */
12
+ export function isPastFact(words) {
13
+ return words.some((word, index) => isPastParticiple(word) && !(index === 0 && READS_AS_COMMAND.has(word.toLowerCase())));
14
+ }
15
+ //# sourceMappingURL=is-past-fact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"is-past-fact.js","sourceRoot":"","sources":["../../../src/support/event-sourcing/is-past-fact.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D;;;;GAIG;AACH,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC;IAC/B,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC;IAC/F,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC;CACvF,CAAC,CAAC;AAEH,gHAAgH;AAChH,MAAM,UAAU,UAAU,CAAC,KAAwB;IACjD,OAAO,KAAK,CAAC,IAAI,CACf,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,KAAK,CAAC,IAAI,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CACtG,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ /** Whether the word reads as a past participle: `issued`, `taken`, `withdrawn`, `paid`, `held`. */
2
+ export declare function isPastParticiple(word: string): boolean;
3
+ //# sourceMappingURL=is-past-participle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"is-past-participle.d.ts","sourceRoot":"","sources":["../../../src/support/event-sourcing/is-past-participle.ts"],"names":[],"mappings":"AA4GA,mGAAmG;AACnG,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAItD"}
@@ -0,0 +1,113 @@
1
+ /** Same table as `isPastWord` of the CLI, which this plugin does not depend on: past participles that do not end in -ed. */
2
+ const IRREGULAR_PAST = new Set([
3
+ 'awoken',
4
+ 'became',
5
+ 'become',
6
+ 'begun',
7
+ 'bid',
8
+ 'bought',
9
+ 'bound',
10
+ 'broken',
11
+ 'brought',
12
+ 'built',
13
+ 'burnt',
14
+ 'cast',
15
+ 'caught',
16
+ 'chosen',
17
+ 'come',
18
+ 'cut',
19
+ 'dealt',
20
+ 'done',
21
+ 'drawn',
22
+ 'dreamt',
23
+ 'fallen',
24
+ 'fed',
25
+ 'felt',
26
+ 'fled',
27
+ 'forecast',
28
+ 'forgotten',
29
+ 'found',
30
+ 'frozen',
31
+ 'given',
32
+ 'gone',
33
+ 'got',
34
+ 'grown',
35
+ 'had',
36
+ 'heard',
37
+ 'held',
38
+ 'hidden',
39
+ 'hit',
40
+ 'hung',
41
+ 'hurt',
42
+ 'kept',
43
+ 'known',
44
+ 'laid',
45
+ 'learnt',
46
+ 'led',
47
+ 'left',
48
+ 'lent',
49
+ 'let',
50
+ 'lit',
51
+ 'lost',
52
+ 'made',
53
+ 'meant',
54
+ 'met',
55
+ 'overdue',
56
+ 'paid',
57
+ 'put',
58
+ 'quit',
59
+ 'read',
60
+ 'rebuilt',
61
+ 'redone',
62
+ 'rerun',
63
+ 'resent',
64
+ 'reset',
65
+ 'risen',
66
+ 'run',
67
+ 'said',
68
+ 'sat',
69
+ 'seen',
70
+ 'sent',
71
+ 'set',
72
+ 'shed',
73
+ 'shot',
74
+ 'shown',
75
+ 'shut',
76
+ 'sold',
77
+ 'sought',
78
+ 'sped',
79
+ 'spent',
80
+ 'split',
81
+ 'spread',
82
+ 'spun',
83
+ 'stolen',
84
+ 'stood',
85
+ 'struck',
86
+ 'stuck',
87
+ 'sung',
88
+ 'sunk',
89
+ 'swept',
90
+ 'sworn',
91
+ 'swung',
92
+ 'taken',
93
+ 'taught',
94
+ 'thought',
95
+ 'thrown',
96
+ 'told',
97
+ 'torn',
98
+ 'understood',
99
+ 'undone',
100
+ 'upheld',
101
+ 'withdrawn',
102
+ 'withheld',
103
+ 'won',
104
+ 'worn',
105
+ 'wound',
106
+ 'written',
107
+ ]);
108
+ /** Whether the word reads as a past participle: `issued`, `taken`, `withdrawn`, `paid`, `held`. */
109
+ export function isPastParticiple(word) {
110
+ const lower = word.toLowerCase();
111
+ return /[a-z]{2,}ed$/.test(lower) || IRREGULAR_PAST.has(lower);
112
+ }
113
+ //# sourceMappingURL=is-past-participle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"is-past-participle.js","sourceRoot":"","sources":["../../../src/support/event-sourcing/is-past-participle.ts"],"names":[],"mappings":"AAAA,4HAA4H;AAC5H,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC;IAC7B,QAAQ;IACR,QAAQ;IACR,QAAQ;IACR,OAAO;IACP,KAAK;IACL,QAAQ;IACR,OAAO;IACP,QAAQ;IACR,SAAS;IACT,OAAO;IACP,OAAO;IACP,MAAM;IACN,QAAQ;IACR,QAAQ;IACR,MAAM;IACN,KAAK;IACL,OAAO;IACP,MAAM;IACN,OAAO;IACP,QAAQ;IACR,QAAQ;IACR,KAAK;IACL,MAAM;IACN,MAAM;IACN,UAAU;IACV,WAAW;IACX,OAAO;IACP,QAAQ;IACR,OAAO;IACP,MAAM;IACN,KAAK;IACL,OAAO;IACP,KAAK;IACL,OAAO;IACP,MAAM;IACN,QAAQ;IACR,KAAK;IACL,MAAM;IACN,MAAM;IACN,MAAM;IACN,OAAO;IACP,MAAM;IACN,QAAQ;IACR,KAAK;IACL,MAAM;IACN,MAAM;IACN,KAAK;IACL,KAAK;IACL,MAAM;IACN,MAAM;IACN,OAAO;IACP,KAAK;IACL,SAAS;IACT,MAAM;IACN,KAAK;IACL,MAAM;IACN,MAAM;IACN,SAAS;IACT,QAAQ;IACR,OAAO;IACP,QAAQ;IACR,OAAO;IACP,OAAO;IACP,KAAK;IACL,MAAM;IACN,KAAK;IACL,MAAM;IACN,MAAM;IACN,KAAK;IACL,MAAM;IACN,MAAM;IACN,OAAO;IACP,MAAM;IACN,MAAM;IACN,QAAQ;IACR,MAAM;IACN,OAAO;IACP,OAAO;IACP,QAAQ;IACR,MAAM;IACN,QAAQ;IACR,OAAO;IACP,QAAQ;IACR,OAAO;IACP,MAAM;IACN,MAAM;IACN,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,QAAQ;IACR,SAAS;IACT,QAAQ;IACR,MAAM;IACN,MAAM;IACN,YAAY;IACZ,QAAQ;IACR,QAAQ;IACR,WAAW;IACX,UAAU;IACV,KAAK;IACL,MAAM;IACN,OAAO;IACP,SAAS;CACV,CAAC,CAAC;AAEH,mGAAmG;AACnG,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IAEjC,OAAO,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AACjE,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { TSESLint } from '@typescript-eslint/utils';
2
+ import type { LintedFile } from '../../project/linted-file.js';
3
+ import { type ImportedName } from '../imports/import-listener.js';
4
+ /** Hands each name among `wanted` that the file imports from `@ddd-ts-kit/kernel`, any entry. */
5
+ export declare function kernelNamesListener(file: LintedFile, wanted: ReadonlySet<string>, visit: (imported: ImportedName) => void): TSESLint.RuleListener;
6
+ //# sourceMappingURL=kernel-names-listener.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kernel-names-listener.d.ts","sourceRoot":"","sources":["../../../src/support/event-sourcing/kernel-names-listener.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAElF,iGAAiG;AACjG,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,EAC3B,KAAK,EAAE,CAAC,QAAQ,EAAE,YAAY,KAAK,IAAI,GACtC,QAAQ,CAAC,YAAY,CAMvB"}
@@ -0,0 +1,10 @@
1
+ import { importListener } from '../imports/import-listener.js';
2
+ /** Hands each name among `wanted` that the file imports from `@ddd-ts-kit/kernel`, any entry. */
3
+ export function kernelNamesListener(file, wanted, visit) {
4
+ return importListener(file, ({ target, names }) => {
5
+ if (target.kind === 'package' && target.name.startsWith('@ddd-ts-kit/kernel')) {
6
+ names.filter(({ name }) => wanted.has(name)).forEach(visit);
7
+ }
8
+ });
9
+ }
10
+ //# sourceMappingURL=kernel-names-listener.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kernel-names-listener.js","sourceRoot":"","sources":["../../../src/support/event-sourcing/kernel-names-listener.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAqB,MAAM,+BAA+B,CAAC;AAElF,iGAAiG;AACjG,MAAM,UAAU,mBAAmB,CACjC,IAAgB,EAChB,MAA2B,EAC3B,KAAuC;IAEvC,OAAO,cAAc,CAAC,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE;QAChD,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,oBAAoB,CAAC,EAAE,CAAC;YAC9E,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { type TSESTree } from '@typescript-eslint/utils';
2
+ /**
3
+ * The named types the data type `root` reaches, through the declarations of the file, that the
4
+ * file does not declare: a change of theirs would change the stored shape and not its fingerprint.
5
+ */
6
+ export declare function unresolvedTypes(root: string, declared: ReadonlyMap<string, TSESTree.Node>): string[];
7
+ //# sourceMappingURL=unresolved-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unresolved-types.d.ts","sourceRoot":"","sources":["../../../src/support/event-sourcing/unresolved-types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AASzE;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,MAAM,EAAE,CAsBpG"}
@@ -0,0 +1,49 @@
1
+ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
2
+ import { descendants } from '../descendants.js';
3
+ /** Generic types of the language, and the stored shapes the kit keeps readable across its releases. */
4
+ const NOT_DECLARED = new Set([
5
+ ...['Array', 'ReadonlyArray', 'Readonly', 'Record', 'Partial', 'Required', 'Pick', 'Omit', 'NonNullable', 'Date'],
6
+ 'PersonalValue',
7
+ ]);
8
+ /**
9
+ * The named types the data type `root` reaches, through the declarations of the file, that the
10
+ * file does not declare: a change of theirs would change the stored shape and not its fingerprint.
11
+ */
12
+ export function unresolvedTypes(root, declared) {
13
+ const unresolved = [];
14
+ const seen = new Set([root]);
15
+ const pending = referencesOf(declared.get(root));
16
+ for (let name = pending.shift(); name !== undefined; name = pending.shift()) {
17
+ if (seen.has(name) || NOT_DECLARED.has(name)) {
18
+ continue;
19
+ }
20
+ seen.add(name);
21
+ const declaration = declared.get(name);
22
+ if (declaration === undefined) {
23
+ unresolved.push(name);
24
+ }
25
+ else {
26
+ pending.push(...referencesOf(declaration));
27
+ }
28
+ }
29
+ return unresolved;
30
+ }
31
+ /** The names of the types a declaration uses, its own type parameters aside; the members of an enum are values, never types. */
32
+ function referencesOf(declaration) {
33
+ if (declaration === undefined || declaration.type === AST_NODE_TYPES.TSEnumDeclaration) {
34
+ return [];
35
+ }
36
+ const parameters = new Set([...descendants(declaration)].flatMap((node) => node.type === AST_NODE_TYPES.TSTypeParameter ? [node.name.name] : []));
37
+ return namedIn(declaration).filter((name) => !parameters.has(name));
38
+ }
39
+ function namedIn(declaration) {
40
+ return [...descendants(declaration)].flatMap((node) => {
41
+ if (node.type === AST_NODE_TYPES.TSTypeReference && node.typeName.type === AST_NODE_TYPES.Identifier) {
42
+ return [node.typeName.name];
43
+ }
44
+ return node.type === AST_NODE_TYPES.TSInterfaceHeritage && node.expression.type === AST_NODE_TYPES.Identifier
45
+ ? [node.expression.name]
46
+ : [];
47
+ });
48
+ }
49
+ //# sourceMappingURL=unresolved-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unresolved-types.js","sourceRoot":"","sources":["../../../src/support/event-sourcing/unresolved-types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ /** The words of a PascalCase or kebab-case name, in lower case. */
2
+ export declare function wordsOf(name: string): string[];
3
+ //# sourceMappingURL=words-of.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"words-of.d.ts","sourceRoot":"","sources":["../../../src/support/event-sourcing/words-of.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAM9C"}
@@ -0,0 +1,9 @@
1
+ /** The words of a PascalCase or kebab-case name, in lower case. */
2
+ export function wordsOf(name) {
3
+ return name
4
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
5
+ .split(/[\s\-_.]+/)
6
+ .filter((word) => word !== '')
7
+ .map((word) => word.toLowerCase());
8
+ }
9
+ //# sourceMappingURL=words-of.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"words-of.js","sourceRoot":"","sources":["../../../src/support/event-sourcing/words-of.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,OAAO,IAAI;SACR,OAAO,CAAC,oBAAoB,EAAE,OAAO,CAAC;SACtC,KAAK,CAAC,WAAW,CAAC;SAClB,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC;SAC7B,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;AACvC,CAAC"}
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.9.0";
1
+ export declare const VERSION = "0.10.0";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,UAAU,CAAC"}
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,WAAW,CAAC"}
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
- export const VERSION = '0.9.0';
1
+ export const VERSION = '0.10.0';
2
2
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC"}
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,OAAO,GAAG,QAAQ,CAAC"}
@@ -0,0 +1,40 @@
1
+ # ddd-ts-kit/event-sourcing-past-tense
2
+
3
+ Architecture id: `event-sourcing/past-tense`. Default severity: `error` in `recommended`, `error` in `strict`.
4
+
5
+ Applies to: `domain/aggregates/` of a context, tests excluded: the files that declare a class extending `EventSourcedAggregate`.
6
+
7
+ ## Why
8
+
9
+ A stored event is a fact, kept and replayed for as long as the aggregate lives, read by people and by projections that were not written yet. A name in the present or the imperative reads as a request that may still be refused.
10
+
11
+ ## Reported
12
+
13
+ - a name imported from `domain/events/`, as the file writes it (the local name of a renamed import), whose fact holds no past participle. The fact is the name after the name of the aggregate (`ScheduledVisitCancel` of `ScheduledVisit`: `Cancel`), or the whole name when it does not start with it. A past participle is a word ending in `-ed`, or a common irregular one (`paid`, `held`, `taken`, `withdrawn`, `set`, `made`, `done`, `lost`, `won`...); one spelled like its imperative (`set`, `put`, `read`, `run`, `cut`, `found`, `left`...) does not count at the head of the fact, where it reads as a command (`SetPrice`, `ReadInvoice`), and counts after a noun (`InvoicePriceSet`)
14
+
15
+ Incorrect:
16
+
17
+ ```ts
18
+ import { NoteNoShow } from '@/contexts/scheduling/domain/events/note-no-show.event';
19
+ ```
20
+
21
+ Correct:
22
+
23
+ ```ts
24
+ import { NoShowNoted } from '@/contexts/scheduling/domain/events/no-show-noted.event';
25
+ ```
26
+
27
+ ## Options
28
+
29
+ None.
30
+
31
+ `rules."event-sourcing/past-tense"` in `ddd-ts-kit.config.json` set to `off` silences the rule, and `// ddd-ts-kit-disable-next-line event-sourcing/past-tense -- <reason>` silences one line, exactly as for `ddd-ts-kit check`.
32
+
33
+ ## Limits
34
+
35
+ - A word that ends in `-ed` without being a participle (`Need`, `Seed`) passes.
36
+ - An irregular participle missing from the list is reported: silence that line with a reason.
37
+
38
+ ## With the CLI checker
39
+
40
+ `ddd-ts-kit check` reports the same id, from the names the import statements bind. Both hold the same lists of participles, and so does `generate domain-event`; a test of the CLI holds the copies equal.
@@ -0,0 +1,49 @@
1
+ # ddd-ts-kit/event-sourcing-projection-never-appends
2
+
3
+ Architecture id: `event-sourcing/projection-never-appends`. Default severity: `error` in `recommended`, `error` in `strict`.
4
+
5
+ Applies to: `application/projections/` of a context, and any file of a context that declares a class with `@Projection`, tests excluded.
6
+
7
+ ## Why
8
+
9
+ A projection turns facts into a read model and is replayed from position zero at every rebuild. One that appends to a stream would append again at each rebuild, and makes the read side decide facts.
10
+
11
+ ## Reported
12
+
13
+ - an import of `EventStore` or `EventSourcedRepository` from `@ddd-ts-kit/kernel`, in any projection
14
+ - an import of anything of `domain/repositories/`, in a projection declared `@Projection(name, { feed: 'event-store' })`
15
+
16
+ A projection fed by the dispatcher may still name a repository port, as it could before event sourcing: it is not replayed from position zero, and a project that does not opt into event sourcing sees no new finding.
17
+
18
+ Incorrect:
19
+
20
+ ```ts
21
+ @Projection('scheduling.attendance-history', { feed: 'event-store' })
22
+ export class AttendanceHistoryProjection {
23
+ constructor(private readonly history: AttendanceHistory, private readonly records: AttendanceRecordRepository) {}
24
+ }
25
+ ```
26
+
27
+ Correct:
28
+
29
+ ```ts
30
+ @Projection('scheduling.attendance-history', { feed: 'event-store' })
31
+ export class AttendanceHistoryProjection {
32
+ constructor(private readonly history: AttendanceHistory) {}
33
+ }
34
+ ```
35
+
36
+ ## Options
37
+
38
+ None.
39
+
40
+ `rules."event-sourcing/projection-never-appends"` in `ddd-ts-kit.config.json` set to `off` silences the rule, and `// ddd-ts-kit-disable-next-line event-sourcing/projection-never-appends -- <reason>` silences one line, exactly as for `ddd-ts-kit check`.
41
+
42
+ ## Limits
43
+
44
+ - An aggregate reached through a port of `application/ports/` is not recognised.
45
+ - A projection is known to be fed by the store only by the literal `feed: 'event-store'` of its `@Projection`; one wired to a `CatchUpSubscription` without it is judged as fed by the dispatcher.
46
+
47
+ ## With the CLI checker
48
+
49
+ `ddd-ts-kit check` reports the same id from the import statements and the text `@Projection(`.
@@ -0,0 +1,51 @@
1
+ # ddd-ts-kit/event-sourcing-state-in-apply
2
+
3
+ Architecture id: `event-sourcing/state-in-apply`. Default severity: `error` in `recommended`, `error` in `strict`.
4
+
5
+ Applies to: `domain/aggregates/` of a context, tests excluded: the classes that extend `EventSourcedAggregate`.
6
+
7
+ ## Why
8
+
9
+ An event-sourced aggregate is rebuilt by replaying its events through its apply methods. A command that also writes the state itself leaves a state no replay rebuilds: the aggregate behaves one way just after the command and another way once loaded.
10
+
11
+ ## Reported
12
+
13
+ - outside the constructor, the static methods and the apply methods (`apply<Fact>`, and `restore` for a snapshot), an assignment or an increment that starts at `this`
14
+ - there, a mutating call on something of `this` (`push`, `splice`, `sort`, `set`, `add`, `delete`, `clear`...)
15
+ - in an apply method, `this.record(..)`: a replay would record the event again
16
+
17
+ Incorrect:
18
+
19
+ ```ts
20
+ noteNoShow(appointmentId: string, now: Date): void {
21
+ this.state = { noShows: this.state.noShows + 1 };
22
+ this.record(new NoShowNoted(this.id, now, appointmentId));
23
+ }
24
+ ```
25
+
26
+ Correct:
27
+
28
+ ```ts
29
+ noteNoShow(appointmentId: string, now: Date): void {
30
+ this.record(new NoShowNoted(this.id, now, appointmentId));
31
+ }
32
+
33
+ private applyNoShowNoted(event: NoShowNoted): void {
34
+ this.state = { noShows: this.state.noShows + 1 };
35
+ }
36
+ ```
37
+
38
+ ## Options
39
+
40
+ None.
41
+
42
+ `rules."event-sourcing/state-in-apply"` in `ddd-ts-kit.config.json` set to `off` silences the rule, and `// ddd-ts-kit-disable-next-line event-sourcing/state-in-apply -- <reason>` silences one line, exactly as for `ddd-ts-kit check`.
43
+
44
+ ## Limits
45
+
46
+ - A write through a local alias (`const state = this.state; state.items.push(x)`) is not followed.
47
+ - A class that extends `EventSourcedAggregate` under another name (an alias, a base class of the project) is not recognised.
48
+
49
+ ## With the CLI checker
50
+
51
+ `ddd-ts-kit check` reports the same id from the text of the files: the writes it finds in the bodies of the methods of a class whose superclass is written `EventSourcedAggregate`. This rule is the authority.
@@ -0,0 +1,39 @@
1
+ # ddd-ts-kit/event-sourcing-store-behind-repository
2
+
3
+ Architecture id: `event-sourcing/store-behind-repository`. Default severity: `error` in `recommended`, `error` in `strict`.
4
+
5
+ Applies to: `domain/` and `application/` of a context, tests excluded.
6
+
7
+ ## Why
8
+
9
+ The domain and the use cases see an event-sourced aggregate as they see any other: loaded by its repository port, changed by its methods, saved by the same port. A handler that reads a stream itself decides how events become state, which is the aggregate's job, and ties the use case to a storage technique.
10
+
11
+ ## Reported
12
+
13
+ - an import of `EventStore`, `SnapshotStore`, `CheckpointStore`, `EventSourcedRepository`, `EventSerializer`, `CatchUpSubscription` or `InMemoryEventStore` from `@ddd-ts-kit/kernel`
14
+
15
+ Incorrect:
16
+
17
+ ```ts
18
+ constructor(private readonly store: EventStore) {}
19
+ ```
20
+
21
+ Correct:
22
+
23
+ ```ts
24
+ constructor(private readonly records: AttendanceRecordRepository) {}
25
+ ```
26
+
27
+ ## Options
28
+
29
+ None.
30
+
31
+ `rules."event-sourcing/store-behind-repository"` in `ddd-ts-kit.config.json` set to `off` silences the rule, and `// ddd-ts-kit-disable-next-line event-sourcing/store-behind-repository -- <reason>` silences one line, exactly as for `ddd-ts-kit check`.
32
+
33
+ ## Limits
34
+
35
+ - The store reached through a port of the project that wraps it is not recognised: the port is then the place to look at.
36
+
37
+ ## With the CLI checker
38
+
39
+ `ddd-ts-kit check` reports the same id from the import statements.
@@ -0,0 +1,55 @@
1
+ # ddd-ts-kit/event-sourcing-upcaster-required
2
+
3
+ Architecture id: `event-sourcing/upcaster-required`. Default severity: `error` in `recommended`, `error` in `strict`.
4
+
5
+ Applies to: every source of a context that calls `defineStoredEvent(Event, { ... })`, tests excluded.
6
+
7
+ ## Why
8
+
9
+ Stored events are never rewritten: an event stored years ago is read by today's code. The type of the data of a stored event is its contract with the past, so it is named and declared next to the codec, and a change of it is a new version read through an upcaster.
10
+
11
+ ## Reported
12
+
13
+ - an `encode` whose return type is not a named type
14
+ - a named type not declared in the file
15
+ - a named type the data reaches (a field typed `SlotData`, an `extends`, a type argument) that the file does not declare, the kit's own `PersonalValue` aside: a change of it would change the stored shape and not its fingerprint
16
+ - a `version` above 1 without an upcaster from each version below it
17
+
18
+ Incorrect:
19
+
20
+ ```ts
21
+ 'scheduling.attendance-record.no-show-noted': defineStoredEvent(NoShowNoted, {
22
+ version: 2,
23
+ encode: (event) => ({ appointmentId: event.appointmentId, reason: event.reason }),
24
+ decode,
25
+ }),
26
+ ```
27
+
28
+ Correct:
29
+
30
+ ```ts
31
+ interface NoShowNotedData { readonly appointmentId: string; readonly reason: string }
32
+
33
+ 'scheduling.attendance-record.no-show-noted': defineStoredEvent(NoShowNoted, {
34
+ version: 2,
35
+ encode: (event): NoShowNotedData => ({ appointmentId: event.appointmentId, reason: event.reason }),
36
+ decode,
37
+ upcasters: { 1: (data) => ({ ...data, reason: '' }) },
38
+ }),
39
+ ```
40
+
41
+ ## Options
42
+
43
+ None.
44
+
45
+ `rules."event-sourcing/upcaster-required"` in `ddd-ts-kit.config.json` set to `off` silences the rule, and `// ddd-ts-kit-disable-next-line event-sourcing/upcaster-required -- <reason>` silences one line, exactly as for `ddd-ts-kit check`.
46
+
47
+ ## Limits
48
+
49
+ - A shape changed in place without raising the version is seen by `ddd-ts-kit check` only, against `stored-events.lock.json`: this rule reads one file.
50
+
51
+ ## With the CLI checker
52
+
53
+ `ddd-ts-kit check` holds the same checks and, alone, the lock: the fingerprint of each version recorded by `ddd-ts-kit docs --stored-events` in `stored-events.lock.json` (the data type and every type of the file it reaches), a recorded shape that changed, a version below the last recorded.
54
+
55
+ Which gate holds what: ESLint and `ddd-ts-kit check` judge each codec as written; `ddd-ts-kit docs --check` (`npm run docs:check`, run by the generated pipeline) fails while a version declared in a codec is not recorded in the lock, so a version cannot reach the main branch unrecorded; and `ddd-ts-kit check` then refuses any change of a recorded shape.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ddd-ts-kit/eslint-plugin",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "AST-accurate ESLint rules for DDD and Clean Architecture in TypeScript: layer and context isolation, domain purity, clean code ordering.",
5
5
  "license": "MIT",
6
6
  "repository": {