@alveolus/arch 0.4.0 → 0.6.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/dist/bin.mjs +1 -1
- package/dist/{docs-DcFgskuN.mjs → docs-B6M9DxTM.mjs} +732 -15
- package/dist/docs-B6M9DxTM.mjs.map +1 -0
- package/dist/index.d.mts +68 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/docs/core/strategic/open-host-services.md +1 -1
- package/docs/guide/project-layout.md +3 -1
- package/docs/rules/index.md +17 -4
- package/docs/rules/layers/no-outward-import.md +7 -3
- package/docs/rules/strategic/no-cross-context-import.md +64 -2
- package/docs/rules/strategic/no-fat-shared-kernel.md +1 -0
- package/docs/rules/strategic/no-leaky-host-service.md +59 -6
- package/docs/rules/strategic/no-shared-state.md +126 -0
- package/docs/rules/strategic/no-unmapped-context.md +46 -7
- package/package.json +1 -1
- package/dist/docs-DcFgskuN.mjs.map +0 -1
package/dist/index.d.mts
CHANGED
|
@@ -69,11 +69,16 @@ interface MemberProps {
|
|
|
69
69
|
readonly kind: MemberKind;
|
|
70
70
|
readonly visibility: Visibility;
|
|
71
71
|
readonly isStatic: boolean;
|
|
72
|
+
readonly isReadonly: boolean;
|
|
72
73
|
readonly isParameterProperty: boolean;
|
|
73
74
|
readonly isCallable: boolean;
|
|
74
75
|
readonly valueTypes: readonly ClassType[];
|
|
75
76
|
readonly parameterTypes: readonly ClassType[];
|
|
76
77
|
readonly returns: ReturnShape | undefined;
|
|
78
|
+
readonly holdsCollection: boolean;
|
|
79
|
+
readonly receivesFunction: boolean;
|
|
80
|
+
readonly erasedType: string | undefined;
|
|
81
|
+
readonly opaqueValue: string | undefined;
|
|
77
82
|
}
|
|
78
83
|
declare class Member {
|
|
79
84
|
readonly name: string;
|
|
@@ -81,11 +86,16 @@ declare class Member {
|
|
|
81
86
|
readonly kind: MemberKind;
|
|
82
87
|
readonly visibility: Visibility;
|
|
83
88
|
readonly isStatic: boolean;
|
|
89
|
+
readonly isReadonly: boolean;
|
|
84
90
|
readonly isParameterProperty: boolean;
|
|
85
91
|
readonly isCallable: boolean;
|
|
86
92
|
readonly valueTypes: readonly ClassType[];
|
|
87
93
|
readonly parameterTypes: readonly ClassType[];
|
|
88
94
|
readonly returns: ReturnShape | undefined;
|
|
95
|
+
readonly holdsCollection: boolean;
|
|
96
|
+
readonly receivesFunction: boolean;
|
|
97
|
+
readonly erasedType: string | undefined;
|
|
98
|
+
readonly opaqueValue: string | undefined;
|
|
89
99
|
constructor(props: MemberProps);
|
|
90
100
|
get isPublicInstance(): boolean;
|
|
91
101
|
get isField(): boolean;
|
|
@@ -119,8 +129,8 @@ declare class ClassDeclaration {
|
|
|
119
129
|
}
|
|
120
130
|
//#endregion
|
|
121
131
|
//#region src/model/dependencies/dependency.d.ts
|
|
122
|
-
type DependencyForm = "import" | "re-export" | "inline type" | "dynamic import" | "require" | "global";
|
|
123
|
-
type TargetVisibility = "analysed" | "ignored" | "unresolved";
|
|
132
|
+
type DependencyForm = "import" | "re-export" | "inline type" | "dynamic import" | "require" | "reference" | "augmentation" | "dynamic load" | "global";
|
|
133
|
+
type TargetVisibility = "analysed" | "ignored" | "unresolved" | "dynamic";
|
|
124
134
|
type DependencyTarget = {
|
|
125
135
|
readonly kind: "file";
|
|
126
136
|
readonly path: string;
|
|
@@ -140,6 +150,15 @@ declare class Dependency {
|
|
|
140
150
|
isOnPackage(name: string): boolean;
|
|
141
151
|
}
|
|
142
152
|
//#endregion
|
|
153
|
+
//#region src/model/dependencies/global-channel.d.ts
|
|
154
|
+
type GlobalAccess = "reads" | "writes";
|
|
155
|
+
declare class GlobalChannel {
|
|
156
|
+
readonly line: number;
|
|
157
|
+
readonly name: string;
|
|
158
|
+
readonly access: GlobalAccess;
|
|
159
|
+
constructor(line: number, name: string, access: GlobalAccess);
|
|
160
|
+
}
|
|
161
|
+
//#endregion
|
|
143
162
|
//#region src/model/dependencies/global-use.d.ts
|
|
144
163
|
type GlobalOrigin = "ecmascript" | "host";
|
|
145
164
|
type GlobalEffect = "clock" | "randomness";
|
|
@@ -151,6 +170,28 @@ declare class GlobalUse {
|
|
|
151
170
|
constructor(line: number, name: string, origin: GlobalOrigin, effect: GlobalEffect | undefined);
|
|
152
171
|
}
|
|
153
172
|
//#endregion
|
|
173
|
+
//#region src/model/dependencies/module-reach.d.ts
|
|
174
|
+
declare class ModuleReach {
|
|
175
|
+
readonly line: number;
|
|
176
|
+
readonly expression: string;
|
|
177
|
+
readonly means: string;
|
|
178
|
+
readonly module: ClassType;
|
|
179
|
+
constructor(line: number, expression: string, means: string, module: ClassType);
|
|
180
|
+
}
|
|
181
|
+
//#endregion
|
|
182
|
+
//#region src/model/dependencies/wiring.d.ts
|
|
183
|
+
interface WiringLink {
|
|
184
|
+
readonly text: string;
|
|
185
|
+
readonly declaredIn: string | undefined;
|
|
186
|
+
readonly types: readonly ClassType[];
|
|
187
|
+
}
|
|
188
|
+
declare class Wiring {
|
|
189
|
+
readonly line: number;
|
|
190
|
+
readonly receiver: string;
|
|
191
|
+
readonly links: readonly WiringLink[];
|
|
192
|
+
constructor(line: number, receiver: string, links: readonly WiringLink[]);
|
|
193
|
+
}
|
|
194
|
+
//#endregion
|
|
154
195
|
//#region src/model/statements/disable-comment.d.ts
|
|
155
196
|
declare class DisableComment {
|
|
156
197
|
readonly line: number;
|
|
@@ -185,6 +226,9 @@ interface SourceFileProps {
|
|
|
185
226
|
readonly statements: readonly TopLevelStatement[];
|
|
186
227
|
readonly throws: readonly Throw[];
|
|
187
228
|
readonly globals: readonly GlobalUse[];
|
|
229
|
+
readonly globalChannels: readonly GlobalChannel[];
|
|
230
|
+
readonly wirings: readonly Wiring[];
|
|
231
|
+
readonly moduleReaches: readonly ModuleReach[];
|
|
188
232
|
readonly disables: readonly DisableComment[];
|
|
189
233
|
}
|
|
190
234
|
declare class SourceFile {
|
|
@@ -194,6 +238,9 @@ declare class SourceFile {
|
|
|
194
238
|
readonly statements: readonly TopLevelStatement[];
|
|
195
239
|
readonly throws: readonly Throw[];
|
|
196
240
|
readonly globals: readonly GlobalUse[];
|
|
241
|
+
readonly globalChannels: readonly GlobalChannel[];
|
|
242
|
+
readonly wirings: readonly Wiring[];
|
|
243
|
+
readonly moduleReaches: readonly ModuleReach[];
|
|
197
244
|
readonly disables: readonly DisableComment[];
|
|
198
245
|
private readonly lines;
|
|
199
246
|
constructor(props: SourceFileProps);
|
|
@@ -261,7 +308,7 @@ interface Settings {
|
|
|
261
308
|
//#endregion
|
|
262
309
|
//#region src/architecture/paths/location.d.ts
|
|
263
310
|
type Area = "context" | "shared-kernel" | "root" | "outside";
|
|
264
|
-
type Unseen = "ignored" | "unresolved";
|
|
311
|
+
type Unseen = "ignored" | "unresolved" | "dynamic";
|
|
265
312
|
interface LocationProps {
|
|
266
313
|
readonly area: Area;
|
|
267
314
|
readonly context?: string;
|
|
@@ -316,6 +363,15 @@ declare class BuildingBlocks {
|
|
|
316
363
|
foldersOf(layer: "domain" | "application"): readonly string[];
|
|
317
364
|
}
|
|
318
365
|
//#endregion
|
|
366
|
+
//#region src/architecture/crossing.d.ts
|
|
367
|
+
interface Crossing {
|
|
368
|
+
readonly line: number;
|
|
369
|
+
readonly from: string;
|
|
370
|
+
readonly to: string;
|
|
371
|
+
readonly expression: string;
|
|
372
|
+
readonly isOpenHostService: boolean;
|
|
373
|
+
}
|
|
374
|
+
//#endregion
|
|
319
375
|
//#region src/architecture/layers.d.ts
|
|
320
376
|
declare class Layers {
|
|
321
377
|
isLayer(name: string): name is Layer;
|
|
@@ -349,12 +405,15 @@ export declare class Architecture {
|
|
|
349
405
|
private readonly shape;
|
|
350
406
|
private readonly layout;
|
|
351
407
|
private readonly locations;
|
|
408
|
+
private readonly crossings;
|
|
352
409
|
constructor(project: Project$1, settings: Settings);
|
|
353
410
|
get files(): readonly SourceFile[];
|
|
354
411
|
get domainDependencies(): AllowedPackages;
|
|
355
412
|
get applicationDependencies(): AllowedPackages;
|
|
356
413
|
get contextMap(): ContextMap;
|
|
357
414
|
locationOf(file: SourceFile): Location;
|
|
415
|
+
locationOfPath(path: string): Location;
|
|
416
|
+
crossingsIn(file: SourceFile): Crossing[];
|
|
358
417
|
locationOfTarget(target: FileTarget$1): Location;
|
|
359
418
|
shapeIssueOf(location: Location): ShapeIssue | undefined;
|
|
360
419
|
relativePath(path: string): string;
|
|
@@ -418,7 +477,7 @@ declare class NoLooseDisableRule extends Rule<"tooling/no-loose-disable", Messag
|
|
|
418
477
|
}
|
|
419
478
|
//#endregion
|
|
420
479
|
//#region src/rules/registry.d.ts
|
|
421
|
-
declare const ruleIds: readonly ["strategic/no-cross-context-import", "strategic/no-leaky-host-service", "strategic/no-unmapped-context", "strategic/no-fat-shared-kernel", "layers/no-impure-domain", "layers/no-outward-import", "layers/no-portless-adapter", "layers/no-driving-shortcut", "tactical/no-aggregate-reference", "tactical/no-public-field", "tactical/no-foreign-command-dependency", "tactical/no-foreign-query-dependency", "tactical/no-stateful-service", "tactical/no-thrown-failure", "tactical/no-misplaced-class", "tactical/no-loose-code", "tooling/no-loose-disable"];
|
|
480
|
+
declare const ruleIds: readonly ["strategic/no-cross-context-import", "strategic/no-leaky-host-service", "strategic/no-unmapped-context", "strategic/no-fat-shared-kernel", "strategic/no-shared-state", "layers/no-impure-domain", "layers/no-outward-import", "layers/no-portless-adapter", "layers/no-driving-shortcut", "tactical/no-aggregate-reference", "tactical/no-public-field", "tactical/no-foreign-command-dependency", "tactical/no-foreign-query-dependency", "tactical/no-stateful-service", "tactical/no-thrown-failure", "tactical/no-misplaced-class", "tactical/no-loose-code", "tooling/no-loose-disable"];
|
|
422
481
|
type RuleId = (typeof ruleIds)[number];
|
|
423
482
|
export declare class RuleRegistry {
|
|
424
483
|
readonly rules: readonly Rule<RuleId>[];
|
|
@@ -459,6 +518,7 @@ interface ImportScope {
|
|
|
459
518
|
readonly projectDir: string;
|
|
460
519
|
readonly rootDir: string;
|
|
461
520
|
isIgnored(path: string): boolean;
|
|
521
|
+
readsWiring(path: string): boolean;
|
|
462
522
|
}
|
|
463
523
|
export declare abstract class Importer {
|
|
464
524
|
abstract read(scope: ImportScope): Project$1;
|
|
@@ -469,9 +529,11 @@ export declare abstract class Importer {
|
|
|
469
529
|
export declare class TsMorphImporter extends Importer {
|
|
470
530
|
private readonly sources;
|
|
471
531
|
private readonly classes;
|
|
532
|
+
private readonly types;
|
|
472
533
|
private readonly statements;
|
|
473
534
|
private readonly disables;
|
|
474
535
|
private readonly throws;
|
|
536
|
+
private readonly globalChannels;
|
|
475
537
|
constructor(sources: Project);
|
|
476
538
|
static fromTsConfig(tsConfigFilePath: string): TsMorphImporter;
|
|
477
539
|
resolves(packageName: string, scope: ImportScope): boolean;
|
|
@@ -617,10 +679,12 @@ export declare class Config implements CheckSettings {
|
|
|
617
679
|
readonly contextMap: ContextMap;
|
|
618
680
|
private readonly ignored;
|
|
619
681
|
private readonly rules;
|
|
682
|
+
private readonly layout;
|
|
620
683
|
constructor(config: AlveolusConfig, projectDir: string);
|
|
621
684
|
private classifiedContexts;
|
|
622
685
|
private validContextMap;
|
|
623
686
|
isIgnored(path: string): boolean;
|
|
687
|
+
readsWiring(path: string): boolean;
|
|
624
688
|
severityOf(rule: RuleId): Severity | "off";
|
|
625
689
|
}
|
|
626
690
|
//#endregion
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":["Project","FileTarget","Project","Project","MorphProject","Project"],"sources":["../src/architecture/allowed-packages.ts","../src/conventions/core-api.ts","../src/conventions/layers.ts","../src/conventions/building-blocks.ts","../src/model/classes/named-type.ts","../src/model/classes/class-type.ts","../src/model/classes/heritage.ts","../src/model/classes/return-shape.ts","../src/model/classes/member.ts","../src/model/classes/class-declaration.ts","../src/model/dependencies/dependency.ts","../src/model/dependencies/global-use.ts","../src/model/statements/disable-comment.ts","../src/model/statements/throw.ts","../src/model/statements/top-level-statement.ts","../src/model/source-file.ts","../src/model/project.ts","../src/architecture/core-api.ts","../src/architecture/context-map.ts","../src/architecture/settings.ts","../src/architecture/paths/location.ts","../src/architecture/paths/place.ts","../src/architecture/building-blocks.ts","../src/architecture/layers.ts","../src/architecture/paths/layer-shape.ts","../src/architecture/architecture.ts","../src/rules/framework/finding.ts","../src/rules/framework/wording.ts","../src/rules/framework/rule.ts","../src/rules/tooling/no-loose-disable.rule.ts","../src/rules/registry.ts","../src/check/violation.ts","../src/check/baseline.ts","../src/importer/importer.ts","../src/importer/ts-morph/ts-morph-importer.ts","../src/check/checker.ts","../src/check/report.ts","../src/docs/page.ts","../src/docs/docs.ts","../src/cli/cli.ts","../src/config/alveolus-config.ts","../src/config/config.ts","../src/config/config-loader.ts","../src/config/define-config.ts","../src/init/init.ts"],"mappings":";;KAAY,sBAAsB,SAAS;qBAE9B;mBACO;EAAnB,YAAmB,WAA2B;EAE9C,IAAW;EAIX,eAAsB,qBAAqB;EAW3C,aAAoB;EAQpB,KAAY,OAAO,kBAAkB;;;;cC1BzB;KAmBD,mBAAmB;cAElB;KAED,qBAAqB;;;cCzBpB;KAED,gBAAgB;;;UCCX;WACP,OAAO;WACP;WACA;;;;UCNO;WACP;WACA;;;;cCAG;WAEX;WACA;WACA,kBAAkC;EAHnC,YACC,cACA,gCACA,kBAAkC;MAGxB;MAIA;;;;UCXK;WACP;WACA;WACA,gBAAgB;;UAGT;WACP;WACA,wBAAwB;;;;UCRjB;WACP,OAAO;WACP,gBAAgB;;;;KCDd;KAEA;UAEK;WACP;WACA;WACA,MAAM;WACN,YAAY;WACZ;WACA;WACA;WACA,qBAAqB;WACrB,yBAAyB;WACzB,SAAS;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":["Project","FileTarget","Project","Project","MorphProject","Project"],"sources":["../src/architecture/allowed-packages.ts","../src/conventions/core-api.ts","../src/conventions/layers.ts","../src/conventions/building-blocks.ts","../src/model/classes/named-type.ts","../src/model/classes/class-type.ts","../src/model/classes/heritage.ts","../src/model/classes/return-shape.ts","../src/model/classes/member.ts","../src/model/classes/class-declaration.ts","../src/model/dependencies/dependency.ts","../src/model/dependencies/global-channel.ts","../src/model/dependencies/global-use.ts","../src/model/dependencies/module-reach.ts","../src/model/dependencies/wiring.ts","../src/model/statements/disable-comment.ts","../src/model/statements/throw.ts","../src/model/statements/top-level-statement.ts","../src/model/source-file.ts","../src/model/project.ts","../src/architecture/core-api.ts","../src/architecture/context-map.ts","../src/architecture/settings.ts","../src/architecture/paths/location.ts","../src/architecture/paths/place.ts","../src/architecture/building-blocks.ts","../src/architecture/crossing.ts","../src/architecture/layers.ts","../src/architecture/paths/layer-shape.ts","../src/architecture/architecture.ts","../src/rules/framework/finding.ts","../src/rules/framework/wording.ts","../src/rules/framework/rule.ts","../src/rules/tooling/no-loose-disable.rule.ts","../src/rules/registry.ts","../src/check/violation.ts","../src/check/baseline.ts","../src/importer/importer.ts","../src/importer/ts-morph/ts-morph-importer.ts","../src/check/checker.ts","../src/check/report.ts","../src/docs/page.ts","../src/docs/docs.ts","../src/cli/cli.ts","../src/config/alveolus-config.ts","../src/config/config.ts","../src/config/config-loader.ts","../src/config/define-config.ts","../src/init/init.ts"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as TsMorphImporter, c as Config, d as RuleRegistry, f as Rule, h as Baseline, i as Init, l as Report, m as AllowedPackages, n as Page, o as Importer, p as Architecture, r as Cli, s as ConfigLoader, t as Docs, u as Checker } from "./docs-
|
|
1
|
+
import { a as TsMorphImporter, c as Config, d as RuleRegistry, f as Rule, h as Baseline, i as Init, l as Report, m as AllowedPackages, n as Page, o as Importer, p as Architecture, r as Cli, s as ConfigLoader, t as Docs, u as Checker } from "./docs-B6M9DxTM.mjs";
|
|
2
2
|
//#region src/config/define-config.ts
|
|
3
3
|
function defineConfig(config) {
|
|
4
4
|
return config;
|
|
@@ -265,7 +265,7 @@ npx alveolus arch check
|
|
|
265
265
|
|
|
266
266
|
<div class="al-cards">
|
|
267
267
|
<div class="al-card"><span class="al-card-title"><a href="../../rules/strategic/no-cross-context-import"><code>strategic/no-cross-context-import</code></a></span>Another context imports this class, and nothing else of the catalog.</div>
|
|
268
|
-
<div class="al-card"><span class="al-card-title"><a href="../../rules/strategic/no-leaky-host-service"><code>strategic/no-leaky-host-service</code></a></span>It answers in the published language, never with a class of the catalog.</div>
|
|
268
|
+
<div class="al-card"><span class="al-card-title"><a href="../../rules/strategic/no-leaky-host-service"><code>strategic/no-leaky-host-service</code></a></span>It answers in the published language, never with a class of the catalog or an untyped result, and it receives data, never a callback.</div>
|
|
269
269
|
<div class="al-card"><span class="al-card-title"><a href="../../rules/tactical/no-misplaced-class"><code>tactical/no-misplaced-class</code></a></span>It stays under <code>driving/</code>.</div>
|
|
270
270
|
</div>
|
|
271
271
|
|
|
@@ -313,7 +313,9 @@ may import it; it imports none of them.
|
|
|
313
313
|
Each change to the shared kernel reaches every context. `Clock` and `IdGenerator` already come
|
|
314
314
|
with `@alveolus/core`; only their adapters live here. An aggregate, a repository or a handler in
|
|
315
315
|
the shared kernel is reported by
|
|
316
|
-
[`strategic/no-fat-shared-kernel`](../rules/strategic/no-fat-shared-kernel.md)
|
|
316
|
+
[`strategic/no-fat-shared-kernel`](../rules/strategic/no-fat-shared-kernel.md), and a static field
|
|
317
|
+
that holds state, such as a registry of services, by
|
|
318
|
+
[`strategic/no-shared-state`](../rules/strategic/no-shared-state.md).
|
|
317
319
|
:::
|
|
318
320
|
|
|
319
321
|
## See also
|
package/docs/rules/index.md
CHANGED
|
@@ -51,9 +51,10 @@ its own business. The "Applies to" line of each rule page says which it is.
|
|
|
51
51
|
|
|
52
52
|
| Rule | Reports |
|
|
53
53
|
| --- | --- |
|
|
54
|
-
| [`strategic/no-cross-context-import`](./strategic/no-cross-context-import.md) | An import from another bounded context that is not its open host service, a composition root that re-exports. |
|
|
54
|
+
| [`strategic/no-cross-context-import`](./strategic/no-cross-context-import.md) | An import from another bounded context that is not its open host service, a file the analysis does not see, a composition root that re-exports. |
|
|
55
55
|
| [`strategic/no-fat-shared-kernel`](./strategic/no-fat-shared-kernel.md) | An aggregate, a repository or a handler in the shared kernel. |
|
|
56
|
-
| [`strategic/no-
|
|
56
|
+
| [`strategic/no-shared-state`](./strategic/no-shared-state.md) | A static field of the shared kernel that holds state. |
|
|
57
|
+
| [`strategic/no-leaky-host-service`](./strategic/no-leaky-host-service.md) | An open host service that exposes a class of its context, receives a function or returns an erased type, instead of speaking the published language. |
|
|
57
58
|
| [`strategic/no-unmapped-context`](./strategic/no-unmapped-context.md) | A context consuming one the context map does not allow, or two contexts that depend on each other. |
|
|
58
59
|
|
|
59
60
|
## Layers
|
|
@@ -120,12 +121,15 @@ The rules that check imports read every way a file can depend on another one, no
|
|
|
120
121
|
`import … from`:
|
|
121
122
|
|
|
122
123
|
```ts
|
|
124
|
+
/// <reference path="../legacy/pool.ts" />
|
|
125
|
+
/// <reference types="pg" />
|
|
123
126
|
import { Pool } from "pg";
|
|
124
127
|
export { Pool } from "pg";
|
|
125
128
|
type Pool = import("pg").Pool;
|
|
126
129
|
const pg = await import("pg");
|
|
127
130
|
const pg = require("pg");
|
|
128
131
|
import pg = require("pg");
|
|
132
|
+
declare module "pg" { interface Pool { tenant: string } }
|
|
129
133
|
```
|
|
130
134
|
|
|
131
135
|
A global declared by the project, in a `declare global` block, counts as an import of the file that
|
|
@@ -133,8 +137,17 @@ declares it.
|
|
|
133
137
|
|
|
134
138
|
An import the analysis cannot see through counts as a file outside the project: one that does not
|
|
135
139
|
resolve, such as a `.js` file without types, one whose path is computed at runtime, or one that is
|
|
136
|
-
ignored, such as a test file. No
|
|
137
|
-
root of `src
|
|
140
|
+
ignored, such as a test file. No file imports it, not even a composition root or a file at the
|
|
141
|
+
root of `src/`: a test file that re-exports another context would otherwise carry the import past
|
|
142
|
+
every rule.
|
|
143
|
+
|
|
144
|
+
Code loaded at runtime counts the same way, wherever it is: `node:module` (`createRequire`),
|
|
145
|
+
`node:vm`, `module.require`, `process.getBuiltinModule`, `eval` and the `Function` constructor.
|
|
146
|
+
They are recognised by their type as well as by their name: an alias of `Function` or `eval`, the
|
|
147
|
+
`constructor` of a function (`(() => 0).constructor`, the `AsyncFunction` constructor),
|
|
148
|
+
`Reflect.construct(Function, …)`, and a lookup on `globalThis` whose key is computed, such as
|
|
149
|
+
`Reflect.get(globalThis, name)`. To read a JSON file, import it:
|
|
150
|
+
`import pkg from "../package.json" with { type: "json" }`.
|
|
138
151
|
|
|
139
152
|
```
|
|
140
153
|
src/ordering/domain/services/pricing.service.ts
|
|
@@ -40,8 +40,11 @@ Within the same context, or towards the shared kernel:
|
|
|
40
40
|
| `published-language/` | Its own `published-language/`. From `@alveolus/core`, only `PublishedLanguage`, `IntegrationEvent` and `JsonValue`; other packages, such as a schema library, are fine. |
|
|
41
41
|
| `driven/` | `domain/`, `application/`, `published-language/`, `driven/`, any package. |
|
|
42
42
|
| `driving/` | `domain/`, `application/`, `published-language/`, `driving/`, any package. |
|
|
43
|
-
| the composition root | Anything in its context and in the shared kernel. |
|
|
44
|
-
| files at the root of `src/` | Composition roots,
|
|
43
|
+
| the composition root | Anything in its context and in the shared kernel, any package. |
|
|
44
|
+
| files at the root of `src/` | Composition roots, each other, any package. |
|
|
45
|
+
|
|
46
|
+
No file imports a file the analysis does not see: ignored, unresolved, computed at runtime, or
|
|
47
|
+
outside the declared contexts and the shared kernel.
|
|
45
48
|
|
|
46
49
|
No layer imports a composition root. The domain has its own rule,
|
|
47
50
|
[`layers/no-impure-domain`](./no-impure-domain.md); imports from another context are checked by
|
|
@@ -100,7 +103,8 @@ The rule also reports:
|
|
|
100
103
|
| Case | Message |
|
|
101
104
|
| --- | --- |
|
|
102
105
|
| A layer imports a composition root | `Imports … : only the composition root wires the layers.` |
|
|
103
|
-
| A file at the root imports inside a context | `Files at the root import composition roots only, not ….` |
|
|
106
|
+
| A file at the root imports inside a context, or a file the analysis does not see | `Files at the root import composition roots only, not ….` |
|
|
107
|
+
| A composition root imports a file the analysis does not see | `The composition root imports src/ordering/wiring.fixture.ts (ignored by the analysis): it wires its own context and the shared kernel only.` |
|
|
104
108
|
| Two composition roots in a context | `ordering has 2 composition roots (ordering.module.ts, pricing.module.ts): keep one, and move the rest into the layers.` |
|
|
105
109
|
| The published language imports another name from core | `The published language imports … from @alveolus/core: only published-language types are allowed.` |
|
|
106
110
|
| A name not allowed from a restricted package | `The application imports Controller from @nestjs/common: applicationDependencies only allows Injectable.` |
|
|
@@ -10,7 +10,7 @@ an anti-corruption layer or its composition root.
|
|
|
10
10
|
<dl class="al-glance">
|
|
11
11
|
<dt>Rule</dt><dd><code>strategic/no-cross-context-import</code></dd>
|
|
12
12
|
<dt>Category</dt><dd><a href="/rules/#strategic">Strategic</a>: what crosses a bounded context</dd>
|
|
13
|
-
<dt>Reports</dt><dd>An import from another bounded context that is not its open host service, used where it may be</dd>
|
|
13
|
+
<dt>Reports</dt><dd>An import from another bounded context that is not its open host service, used where it may be; a value of another context given in the wiring that is not its open host service; an import of a file the analysis does not see</dd>
|
|
14
14
|
<dt>Applies to</dt><dd>Every file of every bounded context, whatever its subdomain, and of the shared kernel</dd>
|
|
15
15
|
<dt>Turn off</dt><dd><a href="#turn-it-off"><code>"strategic/no-cross-context-import": "off"</code></a></dd>
|
|
16
16
|
</dl>
|
|
@@ -41,8 +41,36 @@ When a file of one bounded context imports a file of another one:
|
|
|
41
41
|
|
|
42
42
|
The shared kernel imports no bounded context at all. Every context may import the shared kernel.
|
|
43
43
|
|
|
44
|
+
No file of a context or of the shared kernel imports a file the analysis does not see: ignored,
|
|
45
|
+
unresolved, computed at runtime, or outside the declared contexts and the shared kernel. Such a
|
|
46
|
+
file could re-export another context, and the rule could not tell which one. Code loaded at
|
|
47
|
+
runtime, with `createRequire`, `eval`, the `Function` constructor or `node:vm`, is reported the
|
|
48
|
+
same way, whether it is named or reached through its type, see
|
|
49
|
+
[Every import counts](../index.md#every-import-counts).
|
|
50
|
+
|
|
44
51
|
Every form of import counts, see [Every import counts](../index.md#every-import-counts).
|
|
45
52
|
|
|
53
|
+
The global object is no channel either. Writing or reading a name of `globalThis`, `global`,
|
|
54
|
+
`window` or `self` that no file declares is reported, wherever it is written: an assignment,
|
|
55
|
+
`Reflect.set`, `Reflect.get`, `Object.assign`, `Object.defineProperty`, or `(globalThis as any).x`.
|
|
56
|
+
A name the library declares, such as a polyfill of `globalThis.crypto` or a read of `fetch`, is
|
|
57
|
+
fine, and a global the project declares with `declare global` counts as an import of the file that
|
|
58
|
+
declares it.
|
|
59
|
+
|
|
60
|
+
The wiring follows the same contract. In the composition roots and the files at the root of
|
|
61
|
+
`src/`, a value of one context given to another one, such as
|
|
62
|
+
`new OrderingModule({ prices: () => this.catalog.commands.changePrice })`, is an
|
|
63
|
+
`OpenHostService` of the giving context, or one of its methods. A handler, a repository or any
|
|
64
|
+
other class of its model is reported, even when the receiving side declares a type of the same
|
|
65
|
+
shape. Whether the receiving context may consume the giving one at all is checked by
|
|
66
|
+
[`strategic/no-unmapped-context`](./no-unmapped-context.md).
|
|
67
|
+
|
|
68
|
+
So that what crosses can be seen, a module of another context is read by its properties
|
|
69
|
+
(`this.catalog.api`), or handed whole to the constructor of another module
|
|
70
|
+
(`new OrderingModule(this.catalog)`). Any other way into it is reported: brackets
|
|
71
|
+
(`this.catalog["commands"]`), `Reflect.get`, a spread, destructuring, or passing it to a function,
|
|
72
|
+
such as a helper of `src/` or `get` from lodash, that could hand back anything.
|
|
73
|
+
|
|
46
74
|
## What it reports
|
|
47
75
|
|
|
48
76
|
```
|
|
@@ -67,6 +95,32 @@ src/shared-kernel/domain/value-objects/money.value-object.ts
|
|
|
67
95
|
bounded context, but imports
|
|
68
96
|
src/catalog/domain/value-objects/currency.value-object.ts
|
|
69
97
|
(catalog domain).
|
|
98
|
+
|
|
99
|
+
src/ordering/domain/services/pricing.service.ts
|
|
100
|
+
1 error strategic/no-cross-context-import: Imports
|
|
101
|
+
src/ordering/domain/value-objects/product-id.fixture.ts (ignored by
|
|
102
|
+
the analysis): the analysis cannot tell which bounded context it
|
|
103
|
+
reaches; move the file into a bounded context or the shared kernel.
|
|
104
|
+
|
|
105
|
+
src/ordering/driven/memory/adapters/memory-prices.adapter.ts
|
|
106
|
+
1 error strategic/no-cross-context-import: Loads code at runtime with
|
|
107
|
+
node:module: the analysis cannot tell which bounded context it
|
|
108
|
+
reaches; use a static import.
|
|
109
|
+
|
|
110
|
+
src/app.module.ts
|
|
111
|
+
14 error strategic/no-cross-context-import: Gives
|
|
112
|
+
this.catalog.commands.changePrice, from catalog, to ordering: only an
|
|
113
|
+
OpenHostService of another bounded context may cross, in an import or
|
|
114
|
+
in the wiring.
|
|
115
|
+
21 error strategic/no-cross-context-import: Reaches into this.catalog
|
|
116
|
+
with Reflect.get: in the wiring, a module of another context is read
|
|
117
|
+
by its properties, so that what crosses can be seen.
|
|
118
|
+
|
|
119
|
+
src/catalog/catalog.module.ts
|
|
120
|
+
18 error strategic/no-cross-context-import: Writes
|
|
121
|
+
globalThis.catalog:prices, which no file declares: two contexts can
|
|
122
|
+
meet there without the context map showing it. Integrate through an
|
|
123
|
+
open host service, or publish an integration event.
|
|
70
124
|
```
|
|
71
125
|
|
|
72
126
|
## Fix it
|
|
@@ -119,7 +173,15 @@ bounded context. Move what it needs into the shared kernel, or keep it in the co
|
|
|
119
173
|
- The rule checks what an anti-corruption layer imports, not what it does with it: an adapter that
|
|
120
174
|
returns the open host service's answer as is, untranslated, is accepted. In review, the ACL
|
|
121
175
|
should build values of its own context.
|
|
122
|
-
- A file that matches `ignore` in `alveolus.config.ts` is not analysed at all
|
|
176
|
+
- A file that matches `ignore` in `alveolus.config.ts` is not analysed at all, and no file of a
|
|
177
|
+
context may import it. Review a change to `ignore` as you would review a rule turned off.
|
|
178
|
+
- A loader reached through a value typed `any` that is neither a `constructor` nor a lookup on
|
|
179
|
+
`globalThis`, such as `(loaders as any).run(code)`, is not recognised. In review, `any` around a
|
|
180
|
+
call is a question to ask.
|
|
181
|
+
- In the wiring, a module chosen by a condition (`flag ? this.catalog : this.ordering`) and then
|
|
182
|
+
reached into is not recognised as a module: its type is a union.
|
|
183
|
+
- In the wiring, a value whose type is erased on the way, by a cast or a container token written
|
|
184
|
+
as a string, is not seen. In review, the composition root holds no cast.
|
|
123
185
|
:::
|
|
124
186
|
|
|
125
187
|
## Turn it off
|
|
@@ -77,5 +77,6 @@ rules: { "strategic/no-fat-shared-kernel": "off" },
|
|
|
77
77
|
## See also
|
|
78
78
|
|
|
79
79
|
- [Project layout: shared kernel](../../guide/project-layout.md#shared-kernel)
|
|
80
|
+
- [`strategic/no-shared-state`](./no-shared-state.md), no state in the shared kernel either
|
|
80
81
|
- [`strategic/no-unmapped-context`](./no-unmapped-context.md), the other way contexts get tied
|
|
81
82
|
- [Rules](../index.md), every rule by category
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "Architecture rule: an open host service speaks the published language
|
|
2
|
+
description: "Architecture rule: an open host service speaks the published language: it never exposes a class of its bounded context, never receives a function and never returns an erased type."
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# no-leaky-host-service
|
|
6
6
|
|
|
7
|
-
An open host service speaks the published language:
|
|
8
|
-
offers.
|
|
7
|
+
An open host service speaks the published language: data in, data out. No class of its context
|
|
8
|
+
appears in what it offers, it receives no function, and what it returns has a type.
|
|
9
9
|
|
|
10
10
|
<dl class="al-glance">
|
|
11
11
|
<dt>Rule</dt><dd><code>strategic/no-leaky-host-service</code></dd>
|
|
12
12
|
<dt>Category</dt><dd><a href="/rules/#strategic">Strategic</a>: what crosses a bounded context</dd>
|
|
13
|
-
<dt>Reports</dt><dd>A class of the project in the parameters, results, properties or getters of an open host service</dd>
|
|
13
|
+
<dt>Reports</dt><dd>A class of the project in the parameters, results, properties or getters of an open host service; a function among its parameters; <code>unknown</code>, <code>any</code> or <code>object</code> as a result</dd>
|
|
14
14
|
<dt>Applies to</dt><dd>Every class that implements <code>OpenHostService</code></dd>
|
|
15
15
|
<dt>Turn off</dt><dd><a href="#turn-it-off"><code>"strategic/no-leaky-host-service": "off"</code></a></dd>
|
|
16
16
|
</dl>
|
|
@@ -39,6 +39,13 @@ parameters and its result, followed into generics, `Promise`, arrays and object
|
|
|
39
39
|
| A class of an installed package | ✅ |
|
|
40
40
|
| Any other class of the project: an aggregate, an entity, a value object, an identifier, a domain event | ❌ |
|
|
41
41
|
|
|
42
|
+
Its parameters carry data, never behaviour: a function is reported wherever it sits, as a
|
|
43
|
+
parameter, a property of an object parameter (`{ onSettled: () => void }`), an item of an array or
|
|
44
|
+
a method of an interface it receives. A class, such as `Date` or `AbortSignal`, is not a function.
|
|
45
|
+
|
|
46
|
+
Its results have a type: `unknown`, `any` and `object`, alone or in a `Promise`, are reported. A
|
|
47
|
+
parameter may be `unknown`, for a payload the service validates.
|
|
48
|
+
|
|
42
49
|
The constructor and private members are left out: they wire the service, other contexts never see
|
|
43
50
|
them.
|
|
44
51
|
|
|
@@ -49,6 +56,13 @@ src/catalog/driving/in-process/catalog-api.ts
|
|
|
49
56
|
6 error strategic/no-leaky-host-service: CatalogApi.product exposes
|
|
50
57
|
Product, an AggregateRoot of catalog: an open host service speaks
|
|
51
58
|
the published language.
|
|
59
|
+
9 error strategic/no-leaky-host-service: CatalogApi.onRestockNeeded
|
|
60
|
+
receives a function: an open host service receives data, never
|
|
61
|
+
behaviour, or the upstream context ends up running code of another
|
|
62
|
+
one. To let another context react, publish an integration event.
|
|
63
|
+
12 error strategic/no-leaky-host-service: CatalogApi.inspect returns
|
|
64
|
+
unknown: an open host service returns a type of its published
|
|
65
|
+
language, so that what leaves the context can be seen.
|
|
52
66
|
```
|
|
53
67
|
|
|
54
68
|
## Fix it
|
|
@@ -79,12 +93,51 @@ export class CatalogApi implements OpenHostService {
|
|
|
79
93
|
|
|
80
94
|
</div>
|
|
81
95
|
|
|
96
|
+
### Publish an event instead of taking a callback
|
|
97
|
+
|
|
98
|
+
So that the upstream context never runs code of another one, it does not accept a callback to call
|
|
99
|
+
when something happens: it publishes an integration event, and the downstream context subscribes
|
|
100
|
+
to it. The context map then says the truth: the downstream context consumes the upstream one, not
|
|
101
|
+
the other way round.
|
|
102
|
+
|
|
103
|
+
<div class="al-compare">
|
|
104
|
+
|
|
105
|
+
```ts [❌ Avoid: src/catalog/driving/in-process/catalog-api.ts]
|
|
106
|
+
export class CatalogApi implements OpenHostService {
|
|
107
|
+
onRestockNeeded(callback: RestockCallback): void {
|
|
108
|
+
this.handlers.connect(callback);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
```ts [✅ Prefer: src/catalog/application/translators/catalog-events.translator.ts]
|
|
114
|
+
export class CatalogEventsTranslator extends EventTranslator<
|
|
115
|
+
RestockNeeded,
|
|
116
|
+
RestockNeededRepresentation
|
|
117
|
+
> {
|
|
118
|
+
protected readonly source = "catalog";
|
|
119
|
+
|
|
120
|
+
translate(event: RestockNeeded, context: IntegrationEventContext) {
|
|
121
|
+
return this.wrap(event, context, {
|
|
122
|
+
payload: { productId: event.productId.value, quantity: event.quantity },
|
|
123
|
+
type: "catalog.restock-needed",
|
|
124
|
+
version: 1,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
</div>
|
|
131
|
+
|
|
82
132
|
## Limits
|
|
83
133
|
|
|
84
134
|
::: warning What the rule cannot see
|
|
85
|
-
-
|
|
86
|
-
|
|
135
|
+
- A plain object typed by hand with the fields of the aggregate: the rule follows classes and
|
|
136
|
+
erased types, not shapes. In review, a method of an open host service returns a type of
|
|
87
137
|
`published-language/`.
|
|
138
|
+
- A function smuggled through a parameter typed `unknown`: parameters may be `unknown`, for a
|
|
139
|
+
payload to validate, so the rule cannot tell. In review, an `unknown` parameter is parsed, never
|
|
140
|
+
called.
|
|
88
141
|
- A DTO class of the context is a leak too, on purpose: the published language is made of types,
|
|
89
142
|
not classes.
|
|
90
143
|
:::
|