ddd-ts-kit 0.17.0 → 0.19.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.
- package/CHANGELOG.md +1273 -0
- package/README.fr.md +11 -12
- package/README.md +10 -10
- package/dist/affected/reach-of.js +1 -1
- package/dist/affected/reach-of.js.map +1 -1
- package/dist/check/catalogue/architecture-rules.d.ts.map +1 -1
- package/dist/check/catalogue/architecture-rules.js +4 -0
- package/dist/check/catalogue/architecture-rules.js.map +1 -1
- package/dist/check/declarations/declared-event-handlers.d.ts +4 -9
- package/dist/check/declarations/declared-event-handlers.d.ts.map +1 -1
- package/dist/check/declarations/declared-event-handlers.js +5 -5
- package/dist/check/declarations/declared-event-handlers.js.map +1 -1
- package/dist/check/declarations/option-of.d.ts +0 -2
- package/dist/check/declarations/option-of.d.ts.map +1 -1
- package/dist/check/declarations/option-of.js +1 -7
- package/dist/check/declarations/option-of.js.map +1 -1
- package/dist/check/explain/authz-docs.d.ts.map +1 -1
- package/dist/check/explain/authz-docs.js +14 -1
- package/dist/check/explain/authz-docs.js.map +1 -1
- package/dist/check/explain/events-docs.d.ts.map +1 -1
- package/dist/check/explain/events-docs.js +44 -13
- package/dist/check/explain/events-docs.js.map +1 -1
- package/dist/check/explain/ports-docs.d.ts.map +1 -1
- package/dist/check/explain/ports-docs.js +11 -9
- package/dist/check/explain/ports-docs.js.map +1 -1
- package/dist/check/explain/scheduling-docs.d.ts.map +1 -1
- package/dist/check/explain/scheduling-docs.js +10 -5
- package/dist/check/explain/scheduling-docs.js.map +1 -1
- package/dist/check/explain/wiring-docs.js +4 -4
- package/dist/check/explain/wiring-docs.js.map +1 -1
- package/dist/check/fixtures/authz-policy-fixtures.d.ts +3 -0
- package/dist/check/fixtures/authz-policy-fixtures.d.ts.map +1 -0
- package/dist/check/fixtures/authz-policy-fixtures.js +97 -0
- package/dist/check/fixtures/authz-policy-fixtures.js.map +1 -0
- package/dist/check/fixtures/declarations-fixtures.d.ts.map +1 -1
- package/dist/check/fixtures/declarations-fixtures.js +2 -42
- package/dist/check/fixtures/declarations-fixtures.js.map +1 -1
- package/dist/check/fixtures/declared.d.ts.map +1 -1
- package/dist/check/fixtures/declared.js +3 -3
- package/dist/check/fixtures/declared.js.map +1 -1
- package/dist/check/fixtures/domain-events-fixtures.js +1 -1
- package/dist/check/fixtures/domain-events-fixtures.js.map +1 -1
- package/dist/check/fixtures/event-sourcing-fixtures.js +3 -3
- package/dist/check/fixtures/event-sourcing-fixtures.js.map +1 -1
- package/dist/check/fixtures/events-fixtures.d.ts.map +1 -1
- package/dist/check/fixtures/events-fixtures.js +30 -1
- package/dist/check/fixtures/events-fixtures.js.map +1 -1
- package/dist/check/fixtures/fixtures.d.ts.map +1 -1
- package/dist/check/fixtures/fixtures.js +2 -0
- package/dist/check/fixtures/fixtures.js.map +1 -1
- package/dist/check/fixtures/language-fixtures.d.ts.map +1 -1
- package/dist/check/fixtures/language-fixtures.js +1 -1
- package/dist/check/fixtures/language-fixtures.js.map +1 -1
- package/dist/check/rules/authz-policy-asked.d.ts +3 -0
- package/dist/check/rules/authz-policy-asked.d.ts.map +1 -0
- package/dist/check/rules/authz-policy-asked.js +184 -0
- package/dist/check/rules/authz-policy-asked.js.map +1 -0
- package/dist/check/rules/declarations-role-declared.d.ts.map +1 -1
- package/dist/check/rules/declarations-role-declared.js +4 -35
- package/dist/check/rules/declarations-role-declared.js.map +1 -1
- package/dist/check/rules/events-consumer-idempotent.js +3 -3
- package/dist/check/rules/events-consumer-idempotent.js.map +1 -1
- package/dist/check/rules/events-name-declared-once.d.ts +3 -0
- package/dist/check/rules/events-name-declared-once.d.ts.map +1 -0
- package/dist/check/rules/events-name-declared-once.js +51 -0
- package/dist/check/rules/events-name-declared-once.js.map +1 -0
- package/dist/generate/emit.d.ts.map +1 -1
- package/dist/generate/emit.js +2 -14
- package/dist/generate/emit.js.map +1 -1
- package/dist/generate/kinds/ask-policy-in-generated-use-case.d.ts +9 -0
- package/dist/generate/kinds/ask-policy-in-generated-use-case.d.ts.map +1 -0
- package/dist/generate/kinds/ask-policy-in-generated-use-case.js +102 -0
- package/dist/generate/kinds/ask-policy-in-generated-use-case.js.map +1 -0
- package/dist/generate/kinds/assert-declares-its-name.d.ts +9 -0
- package/dist/generate/kinds/assert-declares-its-name.d.ts.map +1 -0
- package/dist/generate/kinds/assert-declares-its-name.js +16 -0
- package/dist/generate/kinds/assert-declares-its-name.js.map +1 -0
- package/dist/generate/kinds/command-context.d.ts +12 -0
- package/dist/generate/kinds/command-context.d.ts.map +1 -0
- package/dist/generate/kinds/command-context.js +18 -0
- package/dist/generate/kinds/command-context.js.map +1 -0
- package/dist/generate/kinds/command-kind.d.ts.map +1 -1
- package/dist/generate/kinds/command-kind.js +7 -24
- package/dist/generate/kinds/command-kind.js.map +1 -1
- package/dist/generate/kinds/command-outputs.d.ts +6 -0
- package/dist/generate/kinds/command-outputs.d.ts.map +1 -0
- package/dist/generate/kinds/command-outputs.js +13 -0
- package/dist/generate/kinds/command-outputs.js.map +1 -0
- package/dist/generate/kinds/declares-its-name.d.ts +5 -0
- package/dist/generate/kinds/declares-its-name.d.ts.map +1 -0
- package/dist/generate/kinds/declares-its-name.js +7 -0
- package/dist/generate/kinds/declares-its-name.js.map +1 -0
- package/dist/generate/kinds/event-file-of.d.ts +5 -0
- package/dist/generate/kinds/event-file-of.d.ts.map +1 -0
- package/dist/generate/kinds/event-file-of.js +5 -0
- package/dist/generate/kinds/event-file-of.js.map +1 -0
- package/dist/generate/kinds/event-handler-kind.d.ts.map +1 -1
- package/dist/generate/kinds/event-handler-kind.js +2 -0
- package/dist/generate/kinds/event-handler-kind.js.map +1 -1
- package/dist/generate/kinds/event-name-reference.d.ts +4 -3
- package/dist/generate/kinds/event-name-reference.d.ts.map +1 -1
- package/dist/generate/kinds/event-name-reference.js +6 -6
- package/dist/generate/kinds/event-name-reference.js.map +1 -1
- package/dist/generate/kinds/integration-event-kind.d.ts.map +1 -1
- package/dist/generate/kinds/integration-event-kind.js +2 -0
- package/dist/generate/kinds/integration-event-kind.js.map +1 -1
- package/dist/generate/kinds/policy-kind.d.ts.map +1 -1
- package/dist/generate/kinds/policy-kind.js +2 -0
- package/dist/generate/kinds/policy-kind.js.map +1 -1
- package/dist/generate/kinds/query-context.d.ts +9 -0
- package/dist/generate/kinds/query-context.d.ts.map +1 -0
- package/dist/generate/kinds/query-context.js +27 -0
- package/dist/generate/kinds/query-context.js.map +1 -0
- package/dist/generate/kinds/query-kind.d.ts.map +1 -1
- package/dist/generate/kinds/query-kind.js +16 -52
- package/dist/generate/kinds/query-kind.js.map +1 -1
- package/dist/generate/kinds/query-outputs.d.ts +8 -0
- package/dist/generate/kinds/query-outputs.d.ts.map +1 -0
- package/dist/generate/kinds/query-outputs.js +20 -0
- package/dist/generate/kinds/query-outputs.js.map +1 -0
- package/dist/generate/kinds/reader-file.d.ts +8 -0
- package/dist/generate/kinds/reader-file.d.ts.map +1 -0
- package/dist/generate/kinds/reader-file.js +9 -0
- package/dist/generate/kinds/reader-file.js.map +1 -0
- package/dist/generate/mark-policy-questions.d.ts +10 -0
- package/dist/generate/mark-policy-questions.d.ts.map +1 -0
- package/dist/generate/mark-policy-questions.js +76 -0
- package/dist/generate/mark-policy-questions.js.map +1 -0
- package/dist/generate/permission-pattern.d.ts +4 -0
- package/dist/generate/permission-pattern.d.ts.map +1 -0
- package/dist/generate/permission-pattern.js +7 -0
- package/dist/generate/permission-pattern.js.map +1 -0
- package/dist/generate/refined-by-policy.d.ts +4 -0
- package/dist/generate/refined-by-policy.d.ts.map +1 -0
- package/dist/generate/refined-by-policy.js +9 -0
- package/dist/generate/refined-by-policy.js.map +1 -0
- package/dist/generate/rendered.d.ts +5 -0
- package/dist/generate/rendered.d.ts.map +1 -0
- package/dist/generate/rendered.js +18 -0
- package/dist/generate/rendered.js.map +1 -0
- package/dist/generate/wire.d.ts.map +1 -1
- package/dist/generate/wire.js +3 -25
- package/dist/generate/wire.js.map +1 -1
- package/dist/generate/wirings.d.ts +8 -0
- package/dist/generate/wirings.d.ts.map +1 -0
- package/dist/generate/wirings.js +28 -0
- package/dist/generate/wirings.js.map +1 -0
- package/dist/init/copilot-instruction.d.ts +6 -0
- package/dist/init/copilot-instruction.d.ts.map +1 -0
- package/dist/init/copilot-instruction.js +15 -0
- package/dist/init/copilot-instruction.js.map +1 -0
- package/dist/init/files/guidance-files.d.ts +1 -0
- package/dist/init/files/guidance-files.d.ts.map +1 -1
- package/dist/init/files/guidance-files.js +4 -3
- package/dist/init/files/guidance-files.js.map +1 -1
- package/dist/init/files/layer-rules.d.ts +3 -0
- package/dist/init/files/layer-rules.d.ts.map +1 -0
- package/dist/init/files/layer-rules.js +11 -0
- package/dist/init/files/layer-rules.js.map +1 -0
- package/dist/init/files/pipeline-files.d.ts.map +1 -1
- package/dist/init/files/pipeline-files.js +2 -0
- package/dist/init/files/pipeline-files.js.map +1 -1
- package/dist/init/json-object.d.ts +3 -0
- package/dist/init/json-object.d.ts.map +1 -0
- package/dist/init/json-object.js +12 -0
- package/dist/init/json-object.js.map +1 -0
- package/dist/init/manifest/versions.js +24 -24
- package/dist/init/run-init.d.ts.map +1 -1
- package/dist/init/run-init.js +4 -0
- package/dist/init/run-init.js.map +1 -1
- package/dist/init/write-agent-settings.d.ts.map +1 -1
- package/dist/init/write-agent-settings.js +3 -10
- package/dist/init/write-agent-settings.js.map +1 -1
- package/dist/init/write-copilot-instructions.d.ts +7 -0
- package/dist/init/write-copilot-instructions.d.ts.map +1 -0
- package/dist/init/write-copilot-instructions.js +16 -0
- package/dist/init/write-copilot-instructions.js.map +1 -0
- package/dist/init/write-copilot-settings.d.ts +8 -0
- package/dist/init/write-copilot-settings.d.ts.map +1 -0
- package/dist/init/write-copilot-settings.js +36 -0
- package/dist/init/write-copilot-settings.js.map +1 -0
- package/dist/markers/index.d.ts +1 -0
- package/dist/markers/index.d.ts.map +1 -1
- package/dist/markers/index.js +1 -0
- package/dist/markers/index.js.map +1 -1
- package/dist/markers/replace-in-region.d.ts +10 -0
- package/dist/markers/replace-in-region.d.ts.map +1 -0
- package/dist/markers/replace-in-region.js +30 -0
- package/dist/markers/replace-in-region.js.map +1 -0
- package/dist/progress/format-progress.js +4 -0
- package/dist/progress/format-progress.js.map +1 -1
- package/dist/progress/progress.d.ts +2 -0
- package/dist/progress/progress.d.ts.map +1 -1
- package/dist/progress/read-progress.js +1 -0
- package/dist/progress/read-progress.js.map +1 -1
- package/dist/progress/read-slices.d.ts +5 -1
- package/dist/progress/read-slices.d.ts.map +1 -1
- package/dist/progress/read-slices.js +17 -7
- package/dist/progress/read-slices.js.map +1 -1
- package/dist/sdk/agent-instruction.d.ts +1 -1
- package/dist/sdk/agent-instruction.js +1 -1
- package/dist/sdk/instruct-agents.d.ts +1 -1
- package/dist/sdk/instruct-agents.d.ts.map +1 -1
- package/dist/sdk/instruct-agents.js +12 -8
- package/dist/sdk/instruct-agents.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +27 -4
- package/templates/command/handler.declared.ts.tpl +29 -0
- package/templates/command/handler.spec.buses.ts.tpl +24 -1
- package/templates/command/handler.ts.tpl +29 -0
- package/templates/command/wiring.manual.tpl +2 -2
- package/templates/command/wiring.tsyringe.tpl +5 -2
- package/templates/event-handler/event-handler.declared.ts.tpl +1 -1
- package/templates/init/AGENTS.md.tpl +46 -0
- package/templates/init/CLAUDE.md.tpl +2 -45
- package/templates/init/README.md.tpl +1 -1
- package/templates/init/rules/application.md.tpl +1 -1
- package/templates/init/rules/domain.md.tpl +1 -1
- package/templates/integration-event/publisher.declared.ts.tpl +1 -1
- package/templates/pipeline/github/workflows/copilot-setup-steps.yml +26 -0
- package/templates/pipeline/package-scripts.json +1 -1
- package/templates/presets/identity/context/application/integration/publishers/account-deactivated.publisher.ts.tpl +1 -1
- package/templates/presets/identity/context/application/integration/publishers/delegation-granted.publisher.ts.tpl +1 -1
- package/templates/presets/identity/context/application/integration/publishers/delegation-revoked.publisher.ts.tpl +1 -1
- package/templates/presets/identity/context/application/integration/publishers/member-joined.publisher.ts.tpl +1 -1
- package/templates/presets/identity/context/application/integration/publishers/member-removed.publisher.ts.tpl +1 -1
- package/templates/presets/identity/context/application/integration/publishers/member-role-changed.publisher.ts.tpl +1 -1
- package/templates/presets/identity/context/application/integration/publishers/organisation-opened.publisher.ts.tpl +1 -1
- package/templates/query/handler.declared.ts.tpl +19 -0
- package/templates/query/handler.spec.buses.ts.tpl +21 -0
- package/templates/query/handler.ts.tpl +19 -0
- package/templates/query/wiring.manual.tpl +1 -1
- package/templates/query/wiring.tsyringe.tpl +4 -1
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { describe, expect, it } from '{{tests.module}}';
|
|
2
2
|
{{#if access.protected}}
|
|
3
|
+
{{#if access.policy}}
|
|
4
|
+
import { AuthorizationMap, PolicyAccess, RoleBasedAuthorizer, authorizationMiddleware, requires } from '@ddd-ts-kit/auth';
|
|
5
|
+
{{/if}}
|
|
6
|
+
{{#unless access.policy}}
|
|
3
7
|
import { AuthorizationMap, RoleBasedAuthorizer, authorizationMiddleware, requires } from '@ddd-ts-kit/auth';
|
|
8
|
+
{{/unless}}
|
|
4
9
|
import { anActor } from '@ddd-ts-kit/auth/testing';
|
|
5
10
|
{{/if}}
|
|
6
11
|
import { createTestBuses } from '@ddd-ts-kit/testing';
|
|
@@ -14,12 +19,22 @@ import { Fake{{aggregate.pascal}}Repository } from '{{alias.context}}/test/fakes
|
|
|
14
19
|
{{/if}}
|
|
15
20
|
{{#if access.protected}}
|
|
16
21
|
|
|
22
|
+
{{#if access.policy}}
|
|
23
|
+
const authorizer = new RoleBasedAuthorizer({ roles: { holder: [{{context.camel}}Permissions['{{access.permission}}']] } });
|
|
24
|
+
|
|
25
|
+
const holders = authorizationMiddleware({
|
|
26
|
+
authorizer,
|
|
27
|
+
map: AuthorizationMap.of(requires({{name.pascal}}Command, {{context.camel}}Permissions['{{access.permission}}'])),
|
|
28
|
+
});
|
|
29
|
+
{{/if}}
|
|
30
|
+
{{#unless access.policy}}
|
|
17
31
|
const holders = authorizationMiddleware({
|
|
18
32
|
authorizer: new RoleBasedAuthorizer({
|
|
19
33
|
roles: { holder: [{{context.camel}}Permissions['{{access.permission}}']] },
|
|
20
34
|
}),
|
|
21
35
|
map: AuthorizationMap.of(requires({{name.pascal}}Command, {{context.camel}}Permissions['{{access.permission}}'])),
|
|
22
36
|
});
|
|
37
|
+
{{/unless}}
|
|
23
38
|
|
|
24
39
|
const holder = anActor().withRole('holder').build();
|
|
25
40
|
{{/if}}
|
|
@@ -71,16 +86,24 @@ function arrange() {
|
|
|
71
86
|
// As the application hands it to the handler: the save announces the events of the {{aggregate.words}} to `domainEvents`.
|
|
72
87
|
const {{aggregate.camel}}Repository = dispatching(new Fake{{aggregate.pascal}}Repository());
|
|
73
88
|
|
|
74
|
-
const handler = new {{name.pascal}}Handler({{aggregate.camel}}Repository, ids, clock);
|
|
89
|
+
const handler = new {{name.pascal}}Handler({{aggregate.camel}}Repository, ids, clock{{#if access.policy}}, new PolicyAccess({ authorizer, clock }){{/if}});
|
|
75
90
|
|
|
76
91
|
commandBus{{spec.guard}}.register({{name.pascal}}Command, handler);
|
|
77
92
|
|
|
78
93
|
return { commandBus, {{aggregate.camel}}Repository, domainEvents };
|
|
79
94
|
{{/if}}
|
|
80
95
|
{{#unless command.hasAggregate}}
|
|
96
|
+
{{#if access.policy}}
|
|
97
|
+
const { commandBus, clock } = createTestBuses();
|
|
98
|
+
const handler = new {{name.pascal}}Handler(new PolicyAccess({ authorizer, clock }));
|
|
99
|
+
|
|
100
|
+
commandBus{{spec.guard}}.register({{name.pascal}}Command, handler);
|
|
101
|
+
{{/if}}
|
|
102
|
+
{{#unless access.policy}}
|
|
81
103
|
const { commandBus } = createTestBuses();
|
|
82
104
|
|
|
83
105
|
commandBus{{spec.guard}}.register({{name.pascal}}Command, new {{name.pascal}}Handler());
|
|
106
|
+
{{/unless}}
|
|
84
107
|
|
|
85
108
|
return { commandBus };
|
|
86
109
|
{{/unless}}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
{{#if command.hasAggregate}}
|
|
2
|
+
{{#if access.policy}}
|
|
3
|
+
import type { Access } from '@ddd-ts-kit/auth';
|
|
4
|
+
{{/if}}
|
|
2
5
|
import type { Clock, CommandHandler, IdGenerator } from '@ddd-ts-kit/kernel';
|
|
3
6
|
import { {{aggregate.pascal}} } from '{{alias.context}}/domain/aggregates/{{aggregate.kebab}}.aggregate{{alias.ext}}';
|
|
4
7
|
import type { {{aggregate.pascal}}Repository } from '{{alias.context}}/domain/repositories/{{aggregate.kebab}}.repository{{alias.ext}}';
|
|
8
|
+
{{#if access.policy}}
|
|
9
|
+
{{access.useCasePermissionsImport}}
|
|
10
|
+
{{/if}}
|
|
5
11
|
import type { {{name.pascal}}Command } from '{{alias.context}}/application/commands/{{name.kebab}}/{{name.kebab}}.command{{alias.ext}}';
|
|
6
12
|
|
|
7
13
|
export class {{name.pascal}}Handler implements CommandHandler<{{name.pascal}}Command> {
|
|
@@ -9,9 +15,17 @@ export class {{name.pascal}}Handler implements CommandHandler<{{name.pascal}}Com
|
|
|
9
15
|
private readonly {{aggregate.camel}}Repository: {{aggregate.pascal}}Repository,
|
|
10
16
|
private readonly ids: IdGenerator,
|
|
11
17
|
private readonly clock: Clock,
|
|
18
|
+
{{#if access.policy}}
|
|
19
|
+
private readonly access: Access,
|
|
20
|
+
{{/if}}
|
|
12
21
|
) {}
|
|
13
22
|
|
|
14
23
|
async execute(command: {{name.pascal}}Command): Promise<string> {
|
|
24
|
+
{{#if access.policy}}
|
|
25
|
+
// TODO(domain): a policy refines {{access.reference}}: ask it about the element "{{name.words}}" acts on once it is loaded, in place of `command`.
|
|
26
|
+
await this.access.authorize(command.actor, {{access.reference}}, command);
|
|
27
|
+
|
|
28
|
+
{{/if}}
|
|
15
29
|
{{#if refusals.factories}}
|
|
16
30
|
// TODO(domain): decide what "{{name.words}}" does to a {{aggregate.pascal}}: either it gives birth to one (keep `create`, pass the facts of `command` as value objects), or it changes an existing one (load it with `findById`, throw its not-found refusal, `{{aggregate.pascal}}Error.notFound(id)` (`ddd-ts-kit generate error {{context.kebab}} {{aggregate.pascal}} notFound`), call ONE intention method). The events it records leave with the save: nothing else to do here.
|
|
17
31
|
{{/if}}
|
|
@@ -27,11 +41,26 @@ export class {{name.pascal}}Handler implements CommandHandler<{{name.pascal}}Com
|
|
|
27
41
|
}
|
|
28
42
|
{{/if}}
|
|
29
43
|
{{#unless command.hasAggregate}}
|
|
44
|
+
{{#if access.policy}}
|
|
45
|
+
import type { Access } from '@ddd-ts-kit/auth';
|
|
46
|
+
{{/if}}
|
|
30
47
|
import type { CommandHandler } from '@ddd-ts-kit/kernel';
|
|
48
|
+
{{#if access.policy}}
|
|
49
|
+
{{access.useCasePermissionsImport}}
|
|
50
|
+
{{/if}}
|
|
31
51
|
import type { {{name.pascal}}Command } from '{{alias.context}}/application/commands/{{name.kebab}}/{{name.kebab}}.command{{alias.ext}}';
|
|
32
52
|
|
|
33
53
|
export class {{name.pascal}}Handler implements CommandHandler<{{name.pascal}}Command> {
|
|
54
|
+
{{#if access.policy}}
|
|
55
|
+
constructor(private readonly access: Access) {}
|
|
56
|
+
|
|
57
|
+
{{/if}}
|
|
34
58
|
async execute(command: {{name.pascal}}Command): Promise<void> {
|
|
59
|
+
{{#if access.policy}}
|
|
60
|
+
// TODO(domain): a policy refines {{access.reference}}: ask it about the element "{{name.words}}" acts on once it is loaded, in place of `command`.
|
|
61
|
+
await this.access.authorize(command.actor, {{access.reference}}, command);
|
|
62
|
+
|
|
63
|
+
{{/if}}
|
|
35
64
|
// TODO(domain): receive the repository of the aggregate "{{name.words}}" acts on in the constructor (rerun with --aggregate <Name> --force to get it wired), load or create the aggregate from `command`, call its intention method and save it (its events leave with the save); the unit of work is opened by the command bus.
|
|
36
65
|
}
|
|
37
66
|
}
|
|
@@ -5,10 +5,10 @@ import { {{name.pascal}}Command } from '{{alias.context}}/application/commands/{
|
|
|
5
5
|
import { {{name.pascal}}Handler } from '{{alias.context}}/application/commands/{{name.kebab}}/{{name.kebab}}.handler{{alias.ext}}';
|
|
6
6
|
@@registrations
|
|
7
7
|
{{#if command.hasAggregate}}
|
|
8
|
-
platform.commands.register({{name.pascal}}Command, new {{name.pascal}}Handler({{aggregate.camel}}Repository, platform.ids, platform.clock), { context: '{{context.kebab}}' });
|
|
8
|
+
platform.commands.register({{name.pascal}}Command, new {{name.pascal}}Handler({{aggregate.camel}}Repository, platform.ids, platform.clock{{#if access.policy}}, platform.access{{/if}}), { context: '{{context.kebab}}' });
|
|
9
9
|
{{/if}}
|
|
10
10
|
{{#unless command.hasAggregate}}
|
|
11
|
-
platform.commands.register({{name.pascal}}Command, new {{name.pascal}}Handler(), { context: '{{context.kebab}}' });
|
|
11
|
+
platform.commands.register({{name.pascal}}Command, new {{name.pascal}}Handler({{#if access.policy}}platform.access{{/if}}), { context: '{{context.kebab}}' });
|
|
12
12
|
{{/unless}}
|
|
13
13
|
@@authorization
|
|
14
14
|
{{access.manualDeclaration}}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
@@imports
|
|
2
|
+
{{#if access.policy}}
|
|
3
|
+
import { Access } from '@ddd-ts-kit/auth';
|
|
4
|
+
{{/if}}
|
|
2
5
|
{{#if command.hasAggregate}}
|
|
3
6
|
import { Clock, IdGenerator } from '@ddd-ts-kit/kernel';
|
|
4
7
|
import { {{aggregate.pascal}}Repository } from '{{alias.context}}/domain/repositories/{{aggregate.kebab}}.repository{{alias.ext}}';
|
|
@@ -8,8 +11,8 @@ import { {{name.pascal}}Command } from '{{alias.context}}/application/commands/{
|
|
|
8
11
|
import { {{name.pascal}}Handler } from '{{alias.context}}/application/commands/{{name.kebab}}/{{name.kebab}}.handler{{alias.ext}}';
|
|
9
12
|
@@registrations
|
|
10
13
|
{{#if command.hasAggregate}}
|
|
11
|
-
builder.command({{name.pascal}}Command){{access.declaration}}.handledBy({{name.pascal}}Handler, [{{aggregate.pascal}}Repository, IdGenerator, Clock]);
|
|
14
|
+
builder.command({{name.pascal}}Command){{access.declaration}}.handledBy({{name.pascal}}Handler, [{{aggregate.pascal}}Repository, IdGenerator, Clock{{#if access.policy}}, Access{{/if}}]);
|
|
12
15
|
{{/if}}
|
|
13
16
|
{{#unless command.hasAggregate}}
|
|
14
|
-
builder.command({{name.pascal}}Command){{access.declaration}}.handledBy({{name.pascal}}Handler);
|
|
17
|
+
builder.command({{name.pascal}}Command){{access.declaration}}.handledBy({{name.pascal}}Handler{{#if access.policy}}, [Access]{{/if}});
|
|
15
18
|
{{/unless}}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DomainEventHandler } from '@ddd-ts-kit/kernel/declarations';
|
|
2
2
|
import { {{event.pascal}} } from '{{alias.context}}/domain/events/{{event.kebab}}.event{{alias.ext}}';
|
|
3
3
|
|
|
4
|
-
@DomainEventHandler({{event.pascal}}
|
|
4
|
+
@DomainEventHandler({{event.pascal}}{{#if handler.afterCommit}}, { phase: 'after-commit' }{{/if}})
|
|
5
5
|
export class {{event.pascal}}EventHandler implements DomainEventHandler<{{event.pascal}}> {
|
|
6
6
|
async handle(event: {{event.pascal}}): Promise<void> {
|
|
7
7
|
{{#unless handler.afterCommit}}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# {{project.name}}
|
|
2
|
+
|
|
3
|
+
A ddd-ts-kit project: one bounded context per directory of `{{config.contextsDir}}/`, each described by its `CONTEXT.md`. The plan and what is next: `docs/architecture/plan.md`, when the project follows the method.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
- `npm run verify`: types, lint, format, tests with the coverage thresholds of `ddd-ts-kit.config.json` (per context and layer), architecture rules, import graph, dead code, documentation drift. Once, before a commit that closes a slice. A layer under its threshold gets the missing test, never an exclusion.
|
|
8
|
+
- `npx ddd-ts-kit check --changed-since <ref>`: the architecture rules on what changed; `npx ddd-ts-kit explain <rule-id>` gives the reason and an example.
|
|
9
|
+
- `npx ddd-ts-kit list --json`: the map of the contexts. `npx ddd-ts-kit describe <context> --json`: one context as data (use cases, routes, events, ports, dependencies).
|
|
10
|
+
- `npx ddd-ts-kit status`: the next step of the plan, the slices by status, the markers left per context. `npx ddd-ts-kit todo [<context>]`: each remaining `TODO(domain)` and `it.todo` as `file:line`.
|
|
11
|
+
- `npx ddd-ts-kit docs`: regenerates the context map and the generated sections of each `CONTEXT.md`; `npm run docs:generate` the OpenAPI contract, the forms and the event catalog.
|
|
12
|
+
|
|
13
|
+
## Read less
|
|
14
|
+
|
|
15
|
+
1. `describe <context> --json` and the `CONTEXT.md` of the context before the tree: do not browse `{{config.sourceRoot}}/` to learn what exists.
|
|
16
|
+
2. Open only what the `next` list of a generator (`file:line`) or a violation names.
|
|
17
|
+
3. `check --changed-since <ref>` and the tests of the context while iterating; `npm run verify` once at the end.
|
|
18
|
+
|
|
19
|
+
## Rules by layer
|
|
20
|
+
|
|
21
|
+
One file per layer in `.claude/rules/`: `domain.md` (`domain/`, the shared kernel), `application.md`, `published-language.md`, `infrastructure.md`, `presentation.md` (and the OpenAPI contract), `platform.md` (`{{config.platformDir}}/`, `main.ts`, `apps/`{{#if style.explicit}}, module files{{/if}}), `tests.md` (specs, fakes, builders, `test/`). The same text is in `.github/instructions/<layer>.instructions.md`. Claude Code and GitHub Copilot load it with the files it applies to; an agent that does not (Codex) reads the file of a layer before it edits a file of that layer.
|
|
22
|
+
|
|
23
|
+
## Generated files
|
|
24
|
+
|
|
25
|
+
Create every file of a role with `npx ddd-ts-kit generate <kind> ... --json`, then fill the `TODO(domain)` places it lists; never write one by hand, never restructure what a generator wrote.
|
|
26
|
+
{{#if assembly.barrel}}
|
|
27
|
+
Never edit `<context>.declarations.ts`: `generate`, `docs` and `npm run dev` rewrite it, from the role files and from the constructor signatures (it registers what each constructor is injected). After changing a constructor by hand, `npx ddd-ts-kit docs`.
|
|
28
|
+
{{/if}}
|
|
29
|
+
{{#if assembly.discovery}}
|
|
30
|
+
Never edit `<context>.dependencies.ts`: `generate`, `docs` and `npm run dev` rewrite it from the constructor signatures (it registers what each constructor is injected). After changing a constructor by hand, `npx ddd-ts-kit docs`.
|
|
31
|
+
{{/if}}
|
|
32
|
+
Never edit the contracts under `openapi/` (`openapi/openapi.yaml`, one file per document when `openapi.documents` splits it, the forms of `openapi/forms/`), `docs/integration/asyncapi.yaml`, `docs/integration/event-catalog.md`, `{{config.platformDir}}/http/openapi-documents.ts`, `.dependency-cruiser.cjs` or the generated sections of a `CONTEXT.md`: change the code or the config, then `npm run docs:generate`, `npx ddd-ts-kit docs` and `npx ddd-ts-kit depcruise-config --write`. `stored-events.lock.json` is written by `npx ddd-ts-kit docs --stored-events` once the shape of a version is final (`docs --check` fails while one is unrecorded), never by hand: a stored shape changes only by a new version with its upcaster (`event-sourcing/upcaster-required`).
|
|
33
|
+
{{#if sdk.on}}
|
|
34
|
+
{{sdk.instruction}}
|
|
35
|
+
{{/if}}
|
|
36
|
+
Imports: a file of the project is imported by its alias from the project root, never by a relative path, the same directory included: `{{alias.source}}/...` for `{{config.sourceRoot}}/`, `{{alias.test}}/...` for `test/`, `{{alias.scripts}}/...` for `scripts/`, {{#if alias.extensionless}}never ending in `.js` or `.ts`, and naming a file, never a directory{{/if}}{{#unless alias.extensionless}}always ending in `.js`{{/unless}} (`imports/alias-only`, `imports/extension`; `npm run lint:fix` rewrites a relative import and a wrong ending). The alias is declared once, under `paths` in `tsconfig.json`, from `importAlias` of `ddd-ts-kit.config.json`, and the ending follows `importExtension`: never add a second entry or mapping, never change `module` or `moduleResolution` in `tsconfig.json`, and never compile without `npm run build`, which rewrites the aliases of `dist/` into relative paths ending in `.js` for Node (`imports/alias-declared`).
|
|
37
|
+
Everywhere: one exported class, interface or function per file, named after it in kebab-case; at most 350 lines a file, 30 lines and 3 parameters a function; `private` members, never `#name`; a blank line before a `return` and around `if`, `for`, `try` (`npm run lint:fix`); braces around the body of every `if`, `else`, `for`, `while` and `do`, on its own lines: a branch is visible at a glance, and a statement added to it later cannot silently fall outside it. `npm run format` (Biome, line width 120) lays out every file: do not spend tokens on line breaks.
|
|
38
|
+
|
|
39
|
+
## TypeScript
|
|
40
|
+
|
|
41
|
+
The compiler is the native TypeScript 7, installed as `@typescript/native`; `typescript` 6 is there for the tools that need its API (ESLint, dependency-cruiser). Use `npm run typecheck` and `npm run build`, never `npx tsc`, which would run version 6.
|
|
42
|
+
|
|
43
|
+
## Workflow
|
|
44
|
+
|
|
45
|
+
Follow the recommended steps in order, the supported path (`README.md`, "Recommended steps"): discover the domain, bounded contexts, glossaries, context map, bootstrap, plan the slices, build slice by slice, quality gate, document. `npx ddd-ts-kit status` says where the project stands. When asked to skip a step, say which one and what it costs, and record it in `docs/architecture/plan.md`.
|
|
46
|
+
With the ddd-ts-kit plugin: `/ddd-ts-kit:start` for a new project or to resume one, `/ddd-ts-kit:glossary` before a new name, `/ddd-ts-kit:use-case` for a feature, `/ddd-ts-kit:document` for the contracts, `/ddd-ts-kit:quality-gate` before a pull or merge request. The plugin installs in Claude Code, Codex and the Copilot CLI from the marketplace `gbourgeat/ddd-ts-kit`; Codex calls a skill by its name, `$start`, `$use-case`.
|
|
@@ -1,46 +1,3 @@
|
|
|
1
|
-
|
|
1
|
+
@AGENTS.md
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Commands
|
|
6
|
-
|
|
7
|
-
- `npm run verify`: types, lint, format, tests with the coverage thresholds of `ddd-ts-kit.config.json` (per context and layer), architecture rules, import graph, dead code, documentation drift. Once, before a commit that closes a slice. A layer under its threshold gets the missing test, never an exclusion.
|
|
8
|
-
- `npx ddd-ts-kit check --changed-since <ref>`: the architecture rules on what changed; `npx ddd-ts-kit explain <rule-id>` gives the reason and an example.
|
|
9
|
-
- `npx ddd-ts-kit list --json`: the map of the contexts. `npx ddd-ts-kit describe <context> --json`: one context as data (use cases, routes, events, ports, dependencies).
|
|
10
|
-
- `npx ddd-ts-kit status`: the next step of the plan, the slices by status, the markers left per context. `npx ddd-ts-kit todo [<context>]`: each remaining `TODO(domain)` and `it.todo` as `file:line`.
|
|
11
|
-
- `npx ddd-ts-kit docs`: regenerates the context map and the generated sections of each `CONTEXT.md`; `npm run docs:generate` the OpenAPI contract, the forms and the event catalog.
|
|
12
|
-
|
|
13
|
-
## Read less
|
|
14
|
-
|
|
15
|
-
1. `describe <context> --json` and the `CONTEXT.md` of the context before the tree: do not browse `{{config.sourceRoot}}/` to learn what exists.
|
|
16
|
-
2. Open only what the `next` list of a generator (`file:line`) or a violation names.
|
|
17
|
-
3. `check --changed-since <ref>` and the tests of the context while iterating; `npm run verify` once at the end.
|
|
18
|
-
|
|
19
|
-
## Rules by layer
|
|
20
|
-
|
|
21
|
-
Loaded with the files they apply to, from `.claude/rules/`: `domain.md` (`domain/`, the shared kernel), `application.md`, `published-language.md`, `infrastructure.md`, `presentation.md` (and the OpenAPI contract), `platform.md` (`{{config.platformDir}}/`, `main.ts`, `apps/`{{#if style.explicit}}, module files{{/if}}), `tests.md` (specs, fakes, builders, `test/`).
|
|
22
|
-
|
|
23
|
-
## Generated files
|
|
24
|
-
|
|
25
|
-
Create every file of a role with `npx ddd-ts-kit generate <kind> ... --json`, then fill the `TODO(domain)` places it lists; never write one by hand, never restructure what a generator wrote.
|
|
26
|
-
{{#if assembly.barrel}}
|
|
27
|
-
Never edit `<context>.declarations.ts`: `generate`, `docs` and `npm run dev` rewrite it, from the role files and from the constructor signatures (it registers what each constructor is injected). After changing a constructor by hand, `npx ddd-ts-kit docs`.
|
|
28
|
-
{{/if}}
|
|
29
|
-
{{#if assembly.discovery}}
|
|
30
|
-
Never edit `<context>.dependencies.ts`: `generate`, `docs` and `npm run dev` rewrite it from the constructor signatures (it registers what each constructor is injected). After changing a constructor by hand, `npx ddd-ts-kit docs`.
|
|
31
|
-
{{/if}}
|
|
32
|
-
Never edit the contracts under `openapi/` (`openapi/openapi.yaml`, one file per document when `openapi.documents` splits it, the forms of `openapi/forms/`), `docs/integration/asyncapi.yaml`, `docs/integration/event-catalog.md`, `{{config.platformDir}}/http/openapi-documents.ts`, `.dependency-cruiser.cjs` or the generated sections of a `CONTEXT.md`: change the code or the config, then `npm run docs:generate`, `npx ddd-ts-kit docs` and `npx ddd-ts-kit depcruise-config --write`. `stored-events.lock.json` is written by `npx ddd-ts-kit docs --stored-events` once the shape of a version is final (`docs --check` fails while one is unrecorded), never by hand: a stored shape changes only by a new version with its upcaster (`event-sourcing/upcaster-required`).
|
|
33
|
-
{{#if sdk.on}}
|
|
34
|
-
{{sdk.instruction}}
|
|
35
|
-
{{/if}}
|
|
36
|
-
Imports: a file of the project is imported by its alias from the project root, never by a relative path, the same directory included: `{{alias.source}}/...` for `{{config.sourceRoot}}/`, `{{alias.test}}/...` for `test/`, `{{alias.scripts}}/...` for `scripts/`, {{#if alias.extensionless}}never ending in `.js` or `.ts`, and naming a file, never a directory{{/if}}{{#unless alias.extensionless}}always ending in `.js`{{/unless}} (`imports/alias-only`, `imports/extension`; `npm run lint:fix` rewrites a relative import and a wrong ending). The alias is declared once, under `paths` in `tsconfig.json`, from `importAlias` of `ddd-ts-kit.config.json`, and the ending follows `importExtension`: never add a second entry or mapping, never change `module` or `moduleResolution` in `tsconfig.json`, and never compile without `npm run build`, which rewrites the aliases of `dist/` into relative paths ending in `.js` for Node (`imports/alias-declared`).
|
|
37
|
-
Everywhere: one exported class, interface or function per file, named after it in kebab-case; at most 350 lines a file, 30 lines and 3 parameters a function; `private` members, never `#name`; a blank line before a `return` and around `if`, `for`, `try` (`npm run lint:fix`); braces around the body of every `if`, `else`, `for`, `while` and `do`, on its own lines: a branch is visible at a glance, and a statement added to it later cannot silently fall outside it. `npm run format` (Biome, line width 120) lays out every file: do not spend tokens on line breaks.
|
|
38
|
-
|
|
39
|
-
## TypeScript
|
|
40
|
-
|
|
41
|
-
The compiler is the native TypeScript 7, installed as `@typescript/native`; `typescript` 6 is there for the tools that need its API (ESLint, dependency-cruiser). Use `npm run typecheck` and `npm run build`, never `npx tsc`, which would run version 6.
|
|
42
|
-
|
|
43
|
-
## Workflow
|
|
44
|
-
|
|
45
|
-
Follow the recommended steps in order, the supported path (`README.md`, "Recommended steps"): discover the domain, bounded contexts, glossaries, context map, bootstrap, plan the slices, build slice by slice, quality gate, document. `npx ddd-ts-kit status` says where the project stands. When asked to skip a step, say which one and what it costs, and record it in `docs/architecture/plan.md`.
|
|
46
|
-
With the ddd-ts-kit plugin: `/ddd-ts-kit:start` for a new project or to resume one, `/ddd-ts-kit:glossary` before a new name, `/ddd-ts-kit:use-case` for a feature, `/ddd-ts-kit:document` for the contracts, `/ddd-ts-kit:quality-gate` before a pull or merge request.
|
|
3
|
+
The rules by layer of `.claude/rules/` load with the files they apply to.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# {{project.name}}
|
|
2
2
|
|
|
3
3
|
Domain-Driven Design and Clean Architecture on [ddd-ts-kit](https://www.npmjs.com/package/ddd-ts-kit).
|
|
4
|
-
The rules are in `
|
|
4
|
+
The rules are in `AGENTS.md`, which every coding agent reads, and by layer in `.claude/rules/` and `.github/instructions/`; `npx ddd-ts-kit explain <rule-id>` explains each one.
|
|
5
5
|
|
|
6
6
|
## Run it
|
|
7
7
|
|
|
@@ -11,7 +11,7 @@ paths:
|
|
|
11
11
|
- Nothing here reads the event store: an event-sourced aggregate is loaded and saved through its repository port like any other (`event-sourcing/store-behind-repository`). A projection writes its read model and never a stream (`event-sourcing/projection-never-appends`); one fed by the store is declared `@Projection(name, { feed: 'event-store' })`.
|
|
12
12
|
{{#if style.declared}}
|
|
13
13
|
- Every command and query carries `@Requires(permission)` or `@Public()` (`authz/declared`), and the actor is in the message (`authz/actor-explicit`). Permissions live in `authorization/<context>.permissions.ts`, rules that need loaded state in `authorization/*.policy.ts`.
|
|
14
|
-
- The decorator is the registration (`declarations/role-declared`): `@CommandHandler(Message)`, `@QueryHandler(Message)`, `@DomainEventHandler(Event
|
|
14
|
+
- The decorator is the registration (`declarations/role-declared`): `@CommandHandler(Message)`, `@QueryHandler(Message)`, `@DomainEventHandler(Event)` (the name is the static `eventName` of the event class), `@IntegrationEventHandler(definition, { consumer })`, `@Scheduled('<context>.<job>', schedule)`, `@Background(options)` (the name is the static `jobName`), `@Projection('<context>.<name>')`, `@Policy(permission, { name })`. Names are string literals.
|
|
15
15
|
- The constructor is the one statement of what a class depends on (`declarations/constructor-injection`): each parameter is typed by one port class, with no `?`, default value, rest or destructuring, and no list repeats it in the decorator. To add a dependency, add the parameter: `npx ddd-ts-kit docs` reads the signature into the generated file of the context (`npm run dev` does it on every change), never edit that file.
|
|
16
16
|
{{/if}}
|
|
17
17
|
{{#if style.explicit}}
|
|
@@ -28,7 +28,7 @@ paths:
|
|
|
28
28
|
{{#if valueObjects.result}}
|
|
29
29
|
- A value object returns its refusals in the `Result` of `create`{{#if refusals.factories}}: `err(PersonError.invalidEmail(raw))`{{/if}}; the caller unwraps it with `unwrap(..)` of the kernel. `npx ddd-ts-kit value-objects` moves the project to factories that throw.
|
|
30
30
|
{{/if}}
|
|
31
|
-
- Domain events are named in the past tense, `<context>.<aggregate>.<fact>` (`events/name-format`), and recorded by the aggregate (`this.record(...)`). They leave with the save: nothing here dispatches them.
|
|
31
|
+
- Domain events are named in the past tense, `<context>.<aggregate>.<fact>` (`events/name-format`), declared once on the class, `static readonly eventName`, which `readonly name` reads (`events/name-declared-once`), and recorded by the aggregate (`this.record(...)`). They leave with the save: nothing here dispatches them.
|
|
32
32
|
- An aggregate whose history is the business can be event-sourced, chosen per aggregate (`generate aggregate <context> <Name> --event-sourced`): it extends `EventSourcedAggregate`, a command checks the invariants then `this.record(...)`, and only its `apply<Fact>` methods (and `restore`, for a snapshot) change the state (`event-sourcing/state-in-apply`). Its events are named in the past tense (`event-sourcing/past-tense`). The other aggregates keep their state in tables.
|
|
33
33
|
- A repository here is a port: an abstract class extending the kernel `Repository<Aggregate>`, abstract members only (`ports/abstract-class`). A domain service is stateless.
|
|
34
34
|
- Time and identifiers come in as parameters (`purity/*`); nothing here knows the acting user or its rights (`authz/in-application`).
|
|
@@ -3,7 +3,7 @@ import { DomainEventHandler } from '@ddd-ts-kit/kernel/declarations';
|
|
|
3
3
|
import { {{event.pascal}} } from '{{alias.context}}/domain/events/{{event.kebab}}.event{{alias.ext}}';
|
|
4
4
|
import { {{event.camel}}V{{event.version}} } from '{{alias.context}}/published-language/{{event.kebab}}.v{{event.version}}.integration-event{{alias.ext}}';
|
|
5
5
|
|
|
6
|
-
@DomainEventHandler({{event.pascal}}
|
|
6
|
+
@DomainEventHandler({{event.pascal}})
|
|
7
7
|
export class {{publisher.pascal}}Publisher implements DomainEventHandler<{{event.pascal}}> {
|
|
8
8
|
constructor(
|
|
9
9
|
private readonly publisher: IntegrationEventPublisher,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: Copilot setup steps
|
|
2
|
+
|
|
3
|
+
# The environment the Copilot cloud agent starts in: Node and the dependencies of the lockfile,
|
|
4
|
+
# so that the checks and the generators of the kit answer at once. Copilot reads this file from
|
|
5
|
+
# the default branch, and only a job of this name; the runs below check it when it changes.
|
|
6
|
+
on:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
push:
|
|
9
|
+
paths:
|
|
10
|
+
- .github/workflows/copilot-setup-steps.yml
|
|
11
|
+
pull_request:
|
|
12
|
+
paths:
|
|
13
|
+
- .github/workflows/copilot-setup-steps.yml
|
|
14
|
+
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
copilot-setup-steps:
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
timeout-minutes: 15
|
|
22
|
+
permissions:
|
|
23
|
+
contents: read
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v7
|
|
26
|
+
- uses: ./.github/actions/setup
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"@biomejs/biome": "2.5.15",
|
|
24
24
|
"@commitlint/cli": "^21.2.3",
|
|
25
25
|
"@commitlint/config-conventional": "^21.2.3",
|
|
26
|
-
"@ddd-ts-kit/eslint-plugin": "^0.
|
|
26
|
+
"@ddd-ts-kit/eslint-plugin": "^0.19.0",
|
|
27
27
|
"@eslint/js": "^10.0.1",
|
|
28
28
|
"@typescript/native": "npm:typescript@~7.0.2",
|
|
29
29
|
"@vitest/coverage-v8": "5.0.3",
|
|
@@ -4,7 +4,7 @@ import { AccountDeactivated } from '{{alias.context}}/domain/events/account-deac
|
|
|
4
4
|
import { accountDeactivatedV1 } from '{{alias.context}}/published-language/account-deactivated.v1.integration-event{{alias.ext}}';
|
|
5
5
|
|
|
6
6
|
/** Tells the other contexts, through the outbox, in the transaction of the fact. */
|
|
7
|
-
@DomainEventHandler(AccountDeactivated
|
|
7
|
+
@DomainEventHandler(AccountDeactivated)
|
|
8
8
|
export class AccountDeactivatedPublisher implements DomainEventHandler<AccountDeactivated> {
|
|
9
9
|
constructor(
|
|
10
10
|
private readonly publisher: IntegrationEventPublisher,
|
|
@@ -4,7 +4,7 @@ import { DelegationGranted } from '{{alias.context}}/domain/events/delegation-gr
|
|
|
4
4
|
import { delegationGrantedV1 } from '{{alias.context}}/published-language/delegation-granted.v1.integration-event{{alias.ext}}';
|
|
5
5
|
|
|
6
6
|
/** Tells the other contexts, through the outbox, in the transaction of the fact. */
|
|
7
|
-
@DomainEventHandler(DelegationGranted
|
|
7
|
+
@DomainEventHandler(DelegationGranted)
|
|
8
8
|
export class DelegationGrantedPublisher implements DomainEventHandler<DelegationGranted> {
|
|
9
9
|
constructor(
|
|
10
10
|
private readonly publisher: IntegrationEventPublisher,
|
|
@@ -4,7 +4,7 @@ import { DelegationRevoked } from '{{alias.context}}/domain/events/delegation-re
|
|
|
4
4
|
import { delegationRevokedV1 } from '{{alias.context}}/published-language/delegation-revoked.v1.integration-event{{alias.ext}}';
|
|
5
5
|
|
|
6
6
|
/** Tells the other contexts, through the outbox, in the transaction of the fact. */
|
|
7
|
-
@DomainEventHandler(DelegationRevoked
|
|
7
|
+
@DomainEventHandler(DelegationRevoked)
|
|
8
8
|
export class DelegationRevokedPublisher implements DomainEventHandler<DelegationRevoked> {
|
|
9
9
|
constructor(
|
|
10
10
|
private readonly publisher: IntegrationEventPublisher,
|
|
@@ -4,7 +4,7 @@ import { MemberJoined } from '{{alias.context}}/domain/events/member-joined.even
|
|
|
4
4
|
import { memberJoinedV1 } from '{{alias.context}}/published-language/member-joined.v1.integration-event{{alias.ext}}';
|
|
5
5
|
|
|
6
6
|
/** Tells the other contexts, through the outbox, in the transaction of the fact. */
|
|
7
|
-
@DomainEventHandler(MemberJoined
|
|
7
|
+
@DomainEventHandler(MemberJoined)
|
|
8
8
|
export class MemberJoinedPublisher implements DomainEventHandler<MemberJoined> {
|
|
9
9
|
constructor(
|
|
10
10
|
private readonly publisher: IntegrationEventPublisher,
|
|
@@ -4,7 +4,7 @@ import { MemberRemoved } from '{{alias.context}}/domain/events/member-removed.ev
|
|
|
4
4
|
import { memberRemovedV1 } from '{{alias.context}}/published-language/member-removed.v1.integration-event{{alias.ext}}';
|
|
5
5
|
|
|
6
6
|
/** Tells the other contexts, through the outbox, in the transaction of the fact. */
|
|
7
|
-
@DomainEventHandler(MemberRemoved
|
|
7
|
+
@DomainEventHandler(MemberRemoved)
|
|
8
8
|
export class MemberRemovedPublisher implements DomainEventHandler<MemberRemoved> {
|
|
9
9
|
constructor(
|
|
10
10
|
private readonly publisher: IntegrationEventPublisher,
|
|
@@ -4,7 +4,7 @@ import { MemberRoleChanged } from '{{alias.context}}/domain/events/member-role-c
|
|
|
4
4
|
import { memberRoleChangedV1 } from '{{alias.context}}/published-language/member-role-changed.v1.integration-event{{alias.ext}}';
|
|
5
5
|
|
|
6
6
|
/** Tells the other contexts, through the outbox, in the transaction of the fact. */
|
|
7
|
-
@DomainEventHandler(MemberRoleChanged
|
|
7
|
+
@DomainEventHandler(MemberRoleChanged)
|
|
8
8
|
export class MemberRoleChangedPublisher implements DomainEventHandler<MemberRoleChanged> {
|
|
9
9
|
constructor(
|
|
10
10
|
private readonly publisher: IntegrationEventPublisher,
|
|
@@ -4,7 +4,7 @@ import { OrganisationOpened } from '{{alias.context}}/domain/events/organisation
|
|
|
4
4
|
import { organisationOpenedV1 } from '{{alias.context}}/published-language/organisation-opened.v1.integration-event{{alias.ext}}';
|
|
5
5
|
|
|
6
6
|
/** Tells the other contexts, through the outbox, in the transaction of the fact. */
|
|
7
|
-
@DomainEventHandler(OrganisationOpened
|
|
7
|
+
@DomainEventHandler(OrganisationOpened)
|
|
8
8
|
export class OrganisationOpenedPublisher implements DomainEventHandler<OrganisationOpened> {
|
|
9
9
|
constructor(
|
|
10
10
|
private readonly publisher: IntegrationEventPublisher,
|
|
@@ -1,13 +1,32 @@
|
|
|
1
|
+
{{#if access.policy}}
|
|
2
|
+
import type { Access } from '@ddd-ts-kit/auth';
|
|
3
|
+
{{/if}}
|
|
1
4
|
import { QueryHandler } from '@ddd-ts-kit/kernel/declarations';
|
|
5
|
+
{{#if access.policy}}
|
|
6
|
+
{{access.useCasePermissionsImport}}
|
|
7
|
+
{{/if}}
|
|
2
8
|
import type { {{name.pascal}}Reader } from '{{alias.context}}/application/ports/{{name.kebab}}-reader.port{{alias.ext}}';
|
|
3
9
|
import { {{name.pascal}}Query } from '{{alias.context}}/application/queries/{{name.kebab}}/{{name.kebab}}.query{{alias.ext}}';
|
|
4
10
|
import type { {{name.pascal}}Result } from '{{alias.context}}/application/queries/{{name.kebab}}/{{name.kebab}}.result{{alias.ext}}';
|
|
5
11
|
|
|
6
12
|
@QueryHandler({{name.pascal}}Query)
|
|
7
13
|
export class {{name.pascal}}Handler implements QueryHandler<{{name.pascal}}Query> {
|
|
14
|
+
{{#if access.policy}}
|
|
15
|
+
constructor(
|
|
16
|
+
private readonly reader: {{name.pascal}}Reader,
|
|
17
|
+
private readonly access: Access,
|
|
18
|
+
) {}
|
|
19
|
+
{{/if}}
|
|
20
|
+
{{#unless access.policy}}
|
|
8
21
|
constructor(private readonly reader: {{name.pascal}}Reader) {}
|
|
22
|
+
{{/unless}}
|
|
9
23
|
|
|
10
24
|
async execute(query: {{name.pascal}}Query): Promise<{{name.pascal}}Result> {
|
|
25
|
+
{{#if access.policy}}
|
|
26
|
+
// TODO(domain): a policy refines {{access.reference}}: ask it about the element "{{name.words}}" reads once it is read, in place of `query`.
|
|
27
|
+
await this.access.authorize(query.actor, {{access.reference}}, query);
|
|
28
|
+
|
|
29
|
+
{{/if}}
|
|
11
30
|
// TODO(domain): narrow what is read to what the asker may see: ask `Access.scope(query.actor, permission, '<type>')` for the scope and hand it to the reader, never filter in memory after loading; a query never changes state.
|
|
12
31
|
return this.reader.read(query);
|
|
13
32
|
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { describe, expect, it } from '{{tests.module}}';
|
|
2
2
|
{{#if access.protected}}
|
|
3
|
+
{{#if access.policy}}
|
|
4
|
+
import { AuthorizationMap, PolicyAccess, RoleBasedAuthorizer, authorizationMiddleware, requires } from '@ddd-ts-kit/auth';
|
|
5
|
+
{{/if}}
|
|
6
|
+
{{#unless access.policy}}
|
|
3
7
|
import { AuthorizationMap, RoleBasedAuthorizer, authorizationMiddleware, requires } from '@ddd-ts-kit/auth';
|
|
8
|
+
{{/unless}}
|
|
4
9
|
import { anActor } from '@ddd-ts-kit/auth/testing';
|
|
5
10
|
{{/if}}
|
|
6
11
|
import { createTestBuses } from '@ddd-ts-kit/testing';
|
|
@@ -13,12 +18,22 @@ import type { {{name.pascal}}Result } from '{{alias.context}}/application/querie
|
|
|
13
18
|
import { Fake{{name.pascal}}Reader } from '{{alias.context}}/test/fakes/{{name.kebab}}-reader.fake{{alias.ext}}';
|
|
14
19
|
{{#if access.protected}}
|
|
15
20
|
|
|
21
|
+
{{#if access.policy}}
|
|
22
|
+
const authorizer = new RoleBasedAuthorizer({ roles: { holder: [{{context.camel}}Permissions['{{access.permission}}']] } });
|
|
23
|
+
|
|
24
|
+
const holders = authorizationMiddleware({
|
|
25
|
+
authorizer,
|
|
26
|
+
map: AuthorizationMap.of(requires({{name.pascal}}Query, {{context.camel}}Permissions['{{access.permission}}'])),
|
|
27
|
+
});
|
|
28
|
+
{{/if}}
|
|
29
|
+
{{#unless access.policy}}
|
|
16
30
|
const holders = authorizationMiddleware({
|
|
17
31
|
authorizer: new RoleBasedAuthorizer({
|
|
18
32
|
roles: { holder: [{{context.camel}}Permissions['{{access.permission}}']] },
|
|
19
33
|
}),
|
|
20
34
|
map: AuthorizationMap.of(requires({{name.pascal}}Query, {{context.camel}}Permissions['{{access.permission}}'])),
|
|
21
35
|
});
|
|
36
|
+
{{/unless}}
|
|
22
37
|
|
|
23
38
|
const holder = anActor().withRole('holder').build();
|
|
24
39
|
{{/if}}
|
|
@@ -47,8 +62,14 @@ describe('{{name.title}}', () => {
|
|
|
47
62
|
});
|
|
48
63
|
|
|
49
64
|
function arrange(answer: {{name.pascal}}Result) {
|
|
65
|
+
{{#if access.policy}}
|
|
66
|
+
const { queryBus, clock } = createTestBuses();
|
|
67
|
+
const handler = new {{name.pascal}}Handler(new Fake{{name.pascal}}Reader(answer), new PolicyAccess({ authorizer, clock }));
|
|
68
|
+
{{/if}}
|
|
69
|
+
{{#unless access.policy}}
|
|
50
70
|
const { queryBus } = createTestBuses();
|
|
51
71
|
const handler = new {{name.pascal}}Handler(new Fake{{name.pascal}}Reader(answer));
|
|
72
|
+
{{/unless}}
|
|
52
73
|
|
|
53
74
|
return queryBus{{spec.guard}}.register({{name.pascal}}Query, handler);
|
|
54
75
|
}
|
|
@@ -1,12 +1,31 @@
|
|
|
1
|
+
{{#if access.policy}}
|
|
2
|
+
import type { Access } from '@ddd-ts-kit/auth';
|
|
3
|
+
{{/if}}
|
|
1
4
|
import type { QueryHandler } from '@ddd-ts-kit/kernel';
|
|
5
|
+
{{#if access.policy}}
|
|
6
|
+
{{access.useCasePermissionsImport}}
|
|
7
|
+
{{/if}}
|
|
2
8
|
import type { {{name.pascal}}Reader } from '{{alias.context}}/application/ports/{{name.kebab}}-reader.port{{alias.ext}}';
|
|
3
9
|
import type { {{name.pascal}}Query } from '{{alias.context}}/application/queries/{{name.kebab}}/{{name.kebab}}.query{{alias.ext}}';
|
|
4
10
|
import type { {{name.pascal}}Result } from '{{alias.context}}/application/queries/{{name.kebab}}/{{name.kebab}}.result{{alias.ext}}';
|
|
5
11
|
|
|
6
12
|
export class {{name.pascal}}Handler implements QueryHandler<{{name.pascal}}Query> {
|
|
13
|
+
{{#if access.policy}}
|
|
14
|
+
constructor(
|
|
15
|
+
private readonly reader: {{name.pascal}}Reader,
|
|
16
|
+
private readonly access: Access,
|
|
17
|
+
) {}
|
|
18
|
+
{{/if}}
|
|
19
|
+
{{#unless access.policy}}
|
|
7
20
|
constructor(private readonly reader: {{name.pascal}}Reader) {}
|
|
21
|
+
{{/unless}}
|
|
8
22
|
|
|
9
23
|
async execute(query: {{name.pascal}}Query): Promise<{{name.pascal}}Result> {
|
|
24
|
+
{{#if access.policy}}
|
|
25
|
+
// TODO(domain): a policy refines {{access.reference}}: ask it about the element "{{name.words}}" reads once it is read, in place of `query`.
|
|
26
|
+
await this.access.authorize(query.actor, {{access.reference}}, query);
|
|
27
|
+
|
|
28
|
+
{{/if}}
|
|
10
29
|
// TODO(domain): narrow what is read to what the asker may see: ask `Access.scope(query.actor, permission, '<type>')` for the scope and hand it to the reader, never filter in memory after loading; a query never changes state.
|
|
11
30
|
return this.reader.read(query);
|
|
12
31
|
}
|
|
@@ -7,6 +7,6 @@ import { InMemory{{name.pascal}}Reader } from '{{alias.context}}/infrastructure/
|
|
|
7
7
|
@@registrations
|
|
8
8
|
const {{name.camel}}Reader = platform.adapter(new InMemory{{name.pascal}}Reader(), { kind: 'repository', context: '{{context.kebab}}' });
|
|
9
9
|
|
|
10
|
-
platform.queries.register({{name.pascal}}Query, new {{name.pascal}}Handler({{name.camel}}Reader), { context: '{{context.kebab}}' });
|
|
10
|
+
platform.queries.register({{name.pascal}}Query, new {{name.pascal}}Handler({{name.camel}}Reader{{#if access.policy}}, platform.access{{/if}}), { context: '{{context.kebab}}' });
|
|
11
11
|
@@authorization
|
|
12
12
|
{{access.manualDeclaration}}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
@@imports
|
|
2
|
+
{{#if access.policy}}
|
|
3
|
+
import { Access } from '@ddd-ts-kit/auth';
|
|
4
|
+
{{/if}}
|
|
2
5
|
{{access.permissionsImport}}
|
|
3
6
|
import { {{name.pascal}}Reader } from '{{alias.context}}/application/ports/{{name.kebab}}-reader.port{{alias.ext}}';
|
|
4
7
|
import { {{name.pascal}}Handler } from '{{alias.context}}/application/queries/{{name.kebab}}/{{name.kebab}}.handler{{alias.ext}}';
|
|
@@ -6,4 +9,4 @@ import { {{name.pascal}}Query } from '{{alias.context}}/application/queries/{{na
|
|
|
6
9
|
import { InMemory{{name.pascal}}Reader } from '{{alias.context}}/infrastructure/persistence/{{reader.file}}{{alias.ext}}';
|
|
7
10
|
@@registrations
|
|
8
11
|
builder.bind({{name.pascal}}Reader).toClass(InMemory{{name.pascal}}Reader).as('repository');
|
|
9
|
-
builder.query({{name.pascal}}Query){{access.declaration}}.handledBy({{name.pascal}}Handler, [{{name.pascal}}Reader]);
|
|
12
|
+
builder.query({{name.pascal}}Query){{access.declaration}}.handledBy({{name.pascal}}Handler, [{{name.pascal}}Reader{{#if access.policy}}, Access{{/if}}]);
|