@alveolus/arch 0.1.0 → 0.2.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/README.md +2 -1
- package/dist/bin.mjs +1 -1
- package/dist/{cli-CwPCGjDg.mjs → cli-P5PwH9OE.mjs} +98 -34
- package/dist/cli-P5PwH9OE.mjs.map +1 -0
- package/dist/index.d.mts +46 -34
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
- package/dist/cli-CwPCGjDg.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -13,8 +13,9 @@ pnpm add -D @alveolus/arch
|
|
|
13
13
|
import { defineConfig } from "@alveolus/arch";
|
|
14
14
|
|
|
15
15
|
export default defineConfig({
|
|
16
|
-
boundedContexts: { catalog: "catalog", ordering: "ordering" },
|
|
16
|
+
boundedContexts: { catalog: "catalog", notifications: "notifications", ordering: "ordering" },
|
|
17
17
|
root: "src",
|
|
18
|
+
subdomains: { core: ["catalog", "ordering"], generic: ["notifications"] },
|
|
18
19
|
});
|
|
19
20
|
```
|
|
20
21
|
|
package/dist/bin.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as Cli } from "./cli-
|
|
2
|
+
import { t as Cli } from "./cli-P5PwH9OE.mjs";
|
|
3
3
|
//#region src/bin.ts
|
|
4
4
|
const cli = new Cli(process.stdout, process.stderr, process.cwd(), process.stdout.isTTY);
|
|
5
5
|
process.exitCode = await cli.run(process.argv.slice(2));
|
|
@@ -640,6 +640,7 @@ var LayerShape = class {
|
|
|
640
640
|
var Location = class {
|
|
641
641
|
area;
|
|
642
642
|
context;
|
|
643
|
+
subdomain;
|
|
643
644
|
layer;
|
|
644
645
|
folder;
|
|
645
646
|
foldersInLayer;
|
|
@@ -649,6 +650,7 @@ var Location = class {
|
|
|
649
650
|
constructor(props) {
|
|
650
651
|
this.area = props.area;
|
|
651
652
|
this.context = props.context;
|
|
653
|
+
this.subdomain = props.subdomain;
|
|
652
654
|
this.layer = props.layer;
|
|
653
655
|
this.folder = props.folder;
|
|
654
656
|
this.foldersInLayer = props.foldersInLayer ?? [];
|
|
@@ -659,6 +661,9 @@ var Location = class {
|
|
|
659
661
|
get isInBoundedContext() {
|
|
660
662
|
return this.area === "context";
|
|
661
663
|
}
|
|
664
|
+
get isInCoreDomain() {
|
|
665
|
+
return this.isInBoundedContext && this.subdomain === "core";
|
|
666
|
+
}
|
|
662
667
|
get isInSharedKernel() {
|
|
663
668
|
return this.area === "shared-kernel";
|
|
664
669
|
}
|
|
@@ -693,39 +698,44 @@ var Layout = class {
|
|
|
693
698
|
area: dirname(file) === this.settings.rootDir ? "root" : "outside",
|
|
694
699
|
fileName
|
|
695
700
|
});
|
|
696
|
-
const
|
|
697
|
-
const context = folder.name;
|
|
701
|
+
const inside = this.insideOf(folder);
|
|
698
702
|
const directories = this.directoriesBetween(folder.dir, file);
|
|
699
703
|
if (directories.length === 0) return new Location({
|
|
700
|
-
|
|
701
|
-
context,
|
|
704
|
+
...inside,
|
|
702
705
|
fileName,
|
|
703
706
|
isCompositionRoot: this.isCompositionRoot(fileName)
|
|
704
707
|
});
|
|
705
708
|
const layerIndex = this.layerIndexOf(directories, folder.isSharedKernel);
|
|
706
|
-
if (layerIndex === void 0)
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
709
|
+
if (layerIndex === void 0) {
|
|
710
|
+
const isFeatureRoot = folder.isSharedKernel && directories.length === 1;
|
|
711
|
+
return new Location({
|
|
712
|
+
...inside,
|
|
713
|
+
fileName,
|
|
714
|
+
isCompositionRoot: isFeatureRoot && this.isCompositionRoot(fileName)
|
|
715
|
+
});
|
|
716
|
+
}
|
|
712
717
|
const layer = directories[layerIndex];
|
|
713
718
|
if (layer === void 0 || !this.isLayer(layer)) return new Location({
|
|
714
|
-
|
|
715
|
-
context,
|
|
719
|
+
...inside,
|
|
716
720
|
fileName
|
|
717
721
|
});
|
|
718
722
|
const foldersInLayer = directories.slice(layerIndex + 1);
|
|
719
723
|
const subfolder = foldersInLayer.at(-1);
|
|
720
724
|
return new Location({
|
|
721
|
-
|
|
722
|
-
context,
|
|
725
|
+
...inside,
|
|
723
726
|
fileName,
|
|
724
727
|
foldersInLayer,
|
|
725
728
|
layer,
|
|
726
729
|
...subfolder === void 0 ? {} : { folder: subfolder }
|
|
727
730
|
});
|
|
728
731
|
}
|
|
732
|
+
insideOf(folder) {
|
|
733
|
+
return {
|
|
734
|
+
area: folder.isSharedKernel ? "shared-kernel" : "context",
|
|
735
|
+
context: folder.name,
|
|
736
|
+
...folder.subdomain === void 0 ? {} : { subdomain: folder.subdomain }
|
|
737
|
+
};
|
|
738
|
+
}
|
|
729
739
|
layerIndexOf(directories, isSharedKernel) {
|
|
730
740
|
if (this.isLayer(directories[0] ?? "")) return 0;
|
|
731
741
|
if (isSharedKernel && this.isLayer(directories[1] ?? "")) return 1;
|
|
@@ -901,6 +911,16 @@ var Wording = class {
|
|
|
901
911
|
//#region src/rules/framework/rule.ts
|
|
902
912
|
var Rule = class {
|
|
903
913
|
wording = new Wording();
|
|
914
|
+
filesOf(architecture) {
|
|
915
|
+
const files = [];
|
|
916
|
+
for (const file of architecture.files) if (this.checks(file, architecture)) files.push(file);
|
|
917
|
+
return files;
|
|
918
|
+
}
|
|
919
|
+
checks(file, architecture) {
|
|
920
|
+
if (this.meta.contexts === "every") return true;
|
|
921
|
+
const location = architecture.locationOf(file);
|
|
922
|
+
return !location.isInBoundedContext || location.isInCoreDomain;
|
|
923
|
+
}
|
|
904
924
|
finding(file, line, symbol, messageId, data = {}) {
|
|
905
925
|
return {
|
|
906
926
|
data,
|
|
@@ -916,7 +936,7 @@ var Rule = class {
|
|
|
916
936
|
var ClassRule = class extends Rule {
|
|
917
937
|
check(architecture) {
|
|
918
938
|
const findings = [];
|
|
919
|
-
for (const file of architecture
|
|
939
|
+
for (const file of this.filesOf(architecture)) for (const codeClass of file.classes) findings.push(...this.findingsFor(codeClass, file, architecture));
|
|
920
940
|
return findings;
|
|
921
941
|
}
|
|
922
942
|
};
|
|
@@ -925,7 +945,7 @@ var ClassRule = class extends Rule {
|
|
|
925
945
|
var ImportRule = class extends Rule {
|
|
926
946
|
check(architecture) {
|
|
927
947
|
const findings = [];
|
|
928
|
-
for (const file of architecture
|
|
948
|
+
for (const file of this.filesOf(architecture)) {
|
|
929
949
|
if (!this.appliesTo(file, architecture)) continue;
|
|
930
950
|
for (const dependency of file.dependencies) {
|
|
931
951
|
const finding = this.findingFor(dependency, file, architecture);
|
|
@@ -974,6 +994,7 @@ const shortcuts = [
|
|
|
974
994
|
];
|
|
975
995
|
var NoDrivingShortcutRule = class extends ImportRule {
|
|
976
996
|
meta = {
|
|
997
|
+
contexts: "core",
|
|
977
998
|
description: "A driving adapter importing a port, a repository, an aggregate, an entity or a domain service instead of calling a handler.",
|
|
978
999
|
id: "layers/no-driving-shortcut",
|
|
979
1000
|
messages: { shortcut: "Imports {name}, {kind}: a driving adapter calls the command and query handlers, never the ports, repositories or aggregates of the domain." }
|
|
@@ -1013,6 +1034,7 @@ var NoDrivingShortcutRule = class extends ImportRule {
|
|
|
1013
1034
|
//#region src/rules/layers/no-impure-domain.rule.ts
|
|
1014
1035
|
var NoImpureDomainRule = class extends ImportRule {
|
|
1015
1036
|
meta = {
|
|
1037
|
+
contexts: "core",
|
|
1016
1038
|
description: "The domain importing a framework, a database, another layer or a package not allowed, or using the host, the clock or randomness.",
|
|
1017
1039
|
id: "layers/no-impure-domain",
|
|
1018
1040
|
messages: {
|
|
@@ -1027,7 +1049,7 @@ var NoImpureDomainRule = class extends ImportRule {
|
|
|
1027
1049
|
};
|
|
1028
1050
|
check(architecture) {
|
|
1029
1051
|
const findings = super.check(architecture);
|
|
1030
|
-
for (const file of architecture
|
|
1052
|
+
for (const file of this.filesOf(architecture)) if (this.appliesTo(file, architecture)) findings.push(...this.impureGlobals(file));
|
|
1031
1053
|
return findings;
|
|
1032
1054
|
}
|
|
1033
1055
|
appliesTo(file, architecture) {
|
|
@@ -1074,6 +1096,7 @@ var NoImpureDomainRule = class extends ImportRule {
|
|
|
1074
1096
|
//#region src/rules/layers/no-outward-import.rule.ts
|
|
1075
1097
|
var NoOutwardImportRule = class extends ImportRule {
|
|
1076
1098
|
meta = {
|
|
1099
|
+
contexts: "core",
|
|
1077
1100
|
description: "A dependency pointing away from the domain, a file outside the layers or in the wrong folder of its layer.",
|
|
1078
1101
|
id: "layers/no-outward-import",
|
|
1079
1102
|
messages: {
|
|
@@ -1125,7 +1148,7 @@ var NoOutwardImportRule = class extends ImportRule {
|
|
|
1125
1148
|
}
|
|
1126
1149
|
misplacedFiles(architecture) {
|
|
1127
1150
|
const findings = [];
|
|
1128
|
-
for (const file of architecture
|
|
1151
|
+
for (const file of this.filesOf(architecture)) {
|
|
1129
1152
|
const location = architecture.locationOf(file);
|
|
1130
1153
|
if (location.isOutside) findings.push(this.finding(file, 1, location.fileName, "outsideContexts"));
|
|
1131
1154
|
else if (!location.isAtRoot && !location.isInLayer && !location.isCompositionRoot) findings.push(this.finding(file, 1, location.fileName, "outsideLayers"));
|
|
@@ -1150,7 +1173,7 @@ var NoOutwardImportRule = class extends ImportRule {
|
|
|
1150
1173
|
}
|
|
1151
1174
|
extraCompositionRoots(architecture) {
|
|
1152
1175
|
const byFolder = /* @__PURE__ */ new Map();
|
|
1153
|
-
for (const file of architecture
|
|
1176
|
+
for (const file of this.filesOf(architecture)) if (architecture.locationOf(file).isCompositionRoot) {
|
|
1154
1177
|
const folder = dirname(file.path);
|
|
1155
1178
|
byFolder.set(folder, [...byFolder.get(folder) ?? [], file]);
|
|
1156
1179
|
}
|
|
@@ -1210,6 +1233,7 @@ var NoOutwardImportRule = class extends ImportRule {
|
|
|
1210
1233
|
//#region src/rules/layers/no-portless-adapter.rule.ts
|
|
1211
1234
|
var NoPortlessAdapterRule = class extends ClassRule {
|
|
1212
1235
|
meta = {
|
|
1236
|
+
contexts: "core",
|
|
1213
1237
|
description: "A driven adapter that extends no port, a port declared outside the domain.",
|
|
1214
1238
|
id: "layers/no-portless-adapter",
|
|
1215
1239
|
messages: {
|
|
@@ -1235,11 +1259,12 @@ var NoPortlessAdapterRule = class extends ClassRule {
|
|
|
1235
1259
|
//#region src/rules/strategic/no-cross-context-import.rule.ts
|
|
1236
1260
|
var NoCrossContextImportRule = class extends ImportRule {
|
|
1237
1261
|
meta = {
|
|
1262
|
+
contexts: "every",
|
|
1238
1263
|
description: "An import from another bounded context that is not its open host service, a composition root that re-exports.",
|
|
1239
1264
|
id: "strategic/no-cross-context-import",
|
|
1240
1265
|
messages: {
|
|
1241
1266
|
notOpenHostService: "Imports {target}: only an OpenHostService of another bounded context may be imported.",
|
|
1242
|
-
outsideAntiCorruptionLayer: "Uses the open host service of {context} outside an AntiCorruptionLayer:
|
|
1267
|
+
outsideAntiCorruptionLayer: "Uses the open host service of {context} outside an AntiCorruptionLayer: a core context translates what it consumes in an anti-corruption layer.",
|
|
1243
1268
|
publishedLanguage: "Imports the published language of {context}: redeclare the fields you read in your own published-language/.",
|
|
1244
1269
|
reexport: "The composition root re-exports {names}: it exports its own module only, so that no other context reaches through it.",
|
|
1245
1270
|
sharedKernel: "The shared kernel imports no bounded context, but imports {target}."
|
|
@@ -1260,7 +1285,7 @@ var NoCrossContextImportRule = class extends ImportRule {
|
|
|
1260
1285
|
if (from.isCompositionRoot && to.isCompositionRoot) return;
|
|
1261
1286
|
if (to.layer === "published-language") return this.finding(file, dependency.line, dependency.label, "publishedLanguage", { context: to.context ?? "" });
|
|
1262
1287
|
if (!this.importsOnlyOpenHostServices(dependency, target.path, architecture)) return this.finding(file, dependency.line, dependency.label, "notOpenHostService", { target: this.wording.target(target, architecture) });
|
|
1263
|
-
if (!from.isCompositionRoot && !this.declaresAntiCorruptionLayer(file, architecture)) return this.finding(file, dependency.line, dependency.label, "outsideAntiCorruptionLayer", { context: to.context ?? "" });
|
|
1288
|
+
if (from.isInCoreDomain && !from.isCompositionRoot && !this.declaresAntiCorruptionLayer(file, architecture)) return this.finding(file, dependency.line, dependency.label, "outsideAntiCorruptionLayer", { context: to.context ?? "" });
|
|
1264
1289
|
}
|
|
1265
1290
|
importsOnlyOpenHostServices(dependency, path, architecture) {
|
|
1266
1291
|
const target = architecture.project.file(path);
|
|
@@ -1289,6 +1314,7 @@ const owned = [
|
|
|
1289
1314
|
];
|
|
1290
1315
|
var NoFatSharedKernelRule = class extends ClassRule {
|
|
1291
1316
|
meta = {
|
|
1317
|
+
contexts: "every",
|
|
1292
1318
|
description: "An aggregate, an entity, an event, a domain service, a repository or a handler in the shared kernel.",
|
|
1293
1319
|
id: "strategic/no-fat-shared-kernel",
|
|
1294
1320
|
messages: { owned: "{class} is {kind} in the shared kernel: it belongs to one bounded context; the shared kernel holds value objects, ports and their adapters." }
|
|
@@ -1308,6 +1334,7 @@ var NoFatSharedKernelRule = class extends ClassRule {
|
|
|
1308
1334
|
//#region src/rules/strategic/no-leaky-host-service.rule.ts
|
|
1309
1335
|
var NoLeakyHostServiceRule = class extends ClassRule {
|
|
1310
1336
|
meta = {
|
|
1337
|
+
contexts: "every",
|
|
1311
1338
|
description: "An open host service that exposes a class of its context instead of the published language.",
|
|
1312
1339
|
id: "strategic/no-leaky-host-service",
|
|
1313
1340
|
messages: { exposes: "{member} exposes {type}, {kind}{owner}: an open host service speaks the published language." }
|
|
@@ -1346,6 +1373,7 @@ var NoLeakyHostServiceRule = class extends ClassRule {
|
|
|
1346
1373
|
//#region src/rules/strategic/no-unmapped-context.rule.ts
|
|
1347
1374
|
var NoUnmappedContextRule = class extends Rule {
|
|
1348
1375
|
meta = {
|
|
1376
|
+
contexts: "every",
|
|
1349
1377
|
description: "A bounded context consuming one the context map does not allow, or two contexts that depend on each other.",
|
|
1350
1378
|
id: "strategic/no-unmapped-context",
|
|
1351
1379
|
messages: {
|
|
@@ -1367,7 +1395,7 @@ var NoUnmappedContextRule = class extends Rule {
|
|
|
1367
1395
|
}
|
|
1368
1396
|
consumptionsIn(architecture) {
|
|
1369
1397
|
const consumptions = [];
|
|
1370
|
-
for (const file of architecture
|
|
1398
|
+
for (const file of this.filesOf(architecture)) {
|
|
1371
1399
|
const from = architecture.locationOf(file);
|
|
1372
1400
|
if (!from.isInBoundedContext || from.context === void 0) continue;
|
|
1373
1401
|
for (const dependency of file.dependencies) {
|
|
@@ -1400,6 +1428,7 @@ const holders = [
|
|
|
1400
1428
|
];
|
|
1401
1429
|
var NoAggregateReferenceRule = class extends Rule {
|
|
1402
1430
|
meta = {
|
|
1431
|
+
contexts: "core",
|
|
1403
1432
|
description: "An aggregate holding another aggregate instead of its identifier, an entity held by two aggregates.",
|
|
1404
1433
|
id: "tactical/no-aggregate-reference",
|
|
1405
1434
|
messages: {
|
|
@@ -1410,7 +1439,7 @@ var NoAggregateReferenceRule = class extends Rule {
|
|
|
1410
1439
|
};
|
|
1411
1440
|
check(architecture) {
|
|
1412
1441
|
const findings = [];
|
|
1413
|
-
for (const file of architecture
|
|
1442
|
+
for (const file of this.filesOf(architecture)) for (const codeClass of file.classes) findings.push(...this.heldAggregates(codeClass, file, architecture));
|
|
1414
1443
|
findings.push(...this.sharedEntities(architecture));
|
|
1415
1444
|
return findings;
|
|
1416
1445
|
}
|
|
@@ -1459,7 +1488,7 @@ var NoAggregateReferenceRule = class extends Rule {
|
|
|
1459
1488
|
}
|
|
1460
1489
|
holdingsIn(architecture) {
|
|
1461
1490
|
const holdings = [];
|
|
1462
|
-
for (const file of architecture
|
|
1491
|
+
for (const file of this.filesOf(architecture)) for (const aggregate of file.classes) {
|
|
1463
1492
|
if (!architecture.is(aggregate, "AggregateRoot")) continue;
|
|
1464
1493
|
const visited = /* @__PURE__ */ new Set();
|
|
1465
1494
|
for (const through of this.heldBy(aggregate)) for (const type of through.types) for (const entity of this.entitiesReachedFrom(type, architecture, visited)) holdings.push({
|
|
@@ -1514,6 +1543,7 @@ const statementMessages = {
|
|
|
1514
1543
|
};
|
|
1515
1544
|
var NoLooseCodeRule = class extends Rule {
|
|
1516
1545
|
meta = {
|
|
1546
|
+
contexts: "core",
|
|
1517
1547
|
description: "Code outside a building block in the domain or the application, outside a class in an adapter layer, anything but the module class in a composition root.",
|
|
1518
1548
|
id: "tactical/no-loose-code",
|
|
1519
1549
|
messages: {
|
|
@@ -1535,7 +1565,7 @@ var NoLooseCodeRule = class extends Rule {
|
|
|
1535
1565
|
};
|
|
1536
1566
|
check(architecture) {
|
|
1537
1567
|
const findings = [];
|
|
1538
|
-
for (const file of architecture
|
|
1568
|
+
for (const file of this.filesOf(architecture)) {
|
|
1539
1569
|
const location = architecture.locationOf(file);
|
|
1540
1570
|
if (guarded$2.has(location.layer)) {
|
|
1541
1571
|
findings.push(...this.looseClasses(file, location.layer ?? "domain", architecture));
|
|
@@ -1585,6 +1615,7 @@ const markerWording = {
|
|
|
1585
1615
|
};
|
|
1586
1616
|
var NoMisplacedClassRule = class extends Rule {
|
|
1587
1617
|
meta = {
|
|
1618
|
+
contexts: "core",
|
|
1588
1619
|
description: "A class in the wrong folder or file, two classes in one file.",
|
|
1589
1620
|
id: "tactical/no-misplaced-class",
|
|
1590
1621
|
messages: {
|
|
@@ -1595,7 +1626,7 @@ var NoMisplacedClassRule = class extends Rule {
|
|
|
1595
1626
|
};
|
|
1596
1627
|
check(architecture) {
|
|
1597
1628
|
const findings = [];
|
|
1598
|
-
for (const file of architecture
|
|
1629
|
+
for (const file of this.filesOf(architecture)) {
|
|
1599
1630
|
findings.push(...this.extraClasses(file));
|
|
1600
1631
|
const [first] = file.classes;
|
|
1601
1632
|
const finding = first === void 0 ? void 0 : this.misplacement(first, file, architecture.locationOf(file), architecture);
|
|
@@ -1630,6 +1661,7 @@ var NoMisplacedClassRule = class extends Rule {
|
|
|
1630
1661
|
//#region src/rules/tactical/command-handlers/no-foreign-command-dependency.rule.ts
|
|
1631
1662
|
var NoForeignCommandDependencyRule = class extends InjectionRule {
|
|
1632
1663
|
meta = {
|
|
1664
|
+
contexts: "core",
|
|
1633
1665
|
description: "A command handler receiving a query repository, an event publisher, another handler or a plain class.",
|
|
1634
1666
|
id: "tactical/no-foreign-command-dependency",
|
|
1635
1667
|
messages: { foreign: "The CommandHandler {class} receives {type}, {kind}: a command handler receives command repositories, ports, event translators, domain services and value objects; events leave through the outbox, never a publisher." }
|
|
@@ -1657,6 +1689,7 @@ const exempt = /* @__PURE__ */ new Set([
|
|
|
1657
1689
|
const guarded$1 = /* @__PURE__ */ new Set(["domain", "application"]);
|
|
1658
1690
|
var NoThrownFailureRule = class extends Rule {
|
|
1659
1691
|
meta = {
|
|
1692
|
+
contexts: "core",
|
|
1660
1693
|
description: "A business failure thrown instead of returned, an entity method without a Result, a setter.",
|
|
1661
1694
|
id: "tactical/no-thrown-failure",
|
|
1662
1695
|
messages: {
|
|
@@ -1668,7 +1701,7 @@ var NoThrownFailureRule = class extends Rule {
|
|
|
1668
1701
|
};
|
|
1669
1702
|
check(architecture) {
|
|
1670
1703
|
const findings = [];
|
|
1671
|
-
for (const file of architecture
|
|
1704
|
+
for (const file of this.filesOf(architecture)) {
|
|
1672
1705
|
findings.push(...this.operationsWithoutResult(file, architecture));
|
|
1673
1706
|
if (guarded$1.has(architecture.locationOf(file).layer)) findings.push(...this.raisedFailures(file));
|
|
1674
1707
|
}
|
|
@@ -1703,6 +1736,7 @@ var NoThrownFailureRule = class extends Rule {
|
|
|
1703
1736
|
//#region src/rules/tactical/domain-services/no-stateful-service.rule.ts
|
|
1704
1737
|
var NoStatefulServiceRule = class extends InjectionRule {
|
|
1705
1738
|
meta = {
|
|
1739
|
+
contexts: "core",
|
|
1706
1740
|
description: "A domain service holding a port, a repository or another service.",
|
|
1707
1741
|
id: "tactical/no-stateful-service",
|
|
1708
1742
|
messages: { foreign: "The DomainService {class} holds {type}, {kind}: a domain service holds configuration only; the command handler passes it what it needs." }
|
|
@@ -1720,6 +1754,7 @@ const guarded = [
|
|
|
1720
1754
|
];
|
|
1721
1755
|
var NoPublicFieldRule = class extends ClassRule {
|
|
1722
1756
|
meta = {
|
|
1757
|
+
contexts: "core",
|
|
1723
1758
|
description: "A public field on an aggregate, an entity, a value object or an identifier.",
|
|
1724
1759
|
id: "tactical/no-public-field",
|
|
1725
1760
|
messages: { publicField: "{member} is a public field: keep the state private, and expose what callers need through a getter." }
|
|
@@ -1738,6 +1773,7 @@ var NoPublicFieldRule = class extends ClassRule {
|
|
|
1738
1773
|
//#region src/rules/tactical/query-handlers/no-foreign-query-dependency.rule.ts
|
|
1739
1774
|
var NoForeignQueryDependencyRule = class extends InjectionRule {
|
|
1740
1775
|
meta = {
|
|
1776
|
+
contexts: "core",
|
|
1741
1777
|
description: "A query handler receiving what writes or changes state.",
|
|
1742
1778
|
id: "tactical/no-foreign-query-dependency",
|
|
1743
1779
|
messages: { foreign: "The QueryHandler {class} receives {type}, {kind}: a query handler receives query repositories, ports that do not write, and value objects." }
|
|
@@ -1775,6 +1811,7 @@ var DisableDirective = class {
|
|
|
1775
1811
|
var NoLooseDisableRule = class extends Rule {
|
|
1776
1812
|
knownRules;
|
|
1777
1813
|
meta = {
|
|
1814
|
+
contexts: "every",
|
|
1778
1815
|
description: "A disable comment that names no known rule, gives no reason, or disables nothing.",
|
|
1779
1816
|
id: "tooling/no-loose-disable",
|
|
1780
1817
|
messages: {
|
|
@@ -1790,7 +1827,7 @@ var NoLooseDisableRule = class extends Rule {
|
|
|
1790
1827
|
}
|
|
1791
1828
|
check(architecture) {
|
|
1792
1829
|
const findings = [];
|
|
1793
|
-
for (const file of architecture
|
|
1830
|
+
for (const file of this.filesOf(architecture)) for (const comment of file.disables) {
|
|
1794
1831
|
const finding = this.malformed(file, comment);
|
|
1795
1832
|
if (finding !== void 0) findings.push(finding);
|
|
1796
1833
|
}
|
|
@@ -2082,16 +2119,37 @@ var Config = class {
|
|
|
2082
2119
|
this.contextMap = config.contextMap === void 0 ? void 0 : this.validContextMap(config.contextMap, Object.keys(config.boundedContexts));
|
|
2083
2120
|
this.rules = config.rules;
|
|
2084
2121
|
const sharedKernel = config.sharedKernel ?? "shared-kernel";
|
|
2085
|
-
this.contextFolders = [...
|
|
2086
|
-
dir: resolve(this.rootDir, folder),
|
|
2087
|
-
isSharedKernel: false,
|
|
2088
|
-
name
|
|
2089
|
-
})), {
|
|
2122
|
+
this.contextFolders = [...this.classifiedContexts(config.subdomains ?? {}, config.boundedContexts), {
|
|
2090
2123
|
dir: resolve(this.rootDir, sharedKernel),
|
|
2091
2124
|
isSharedKernel: true,
|
|
2092
2125
|
name: "shared kernel"
|
|
2093
2126
|
}];
|
|
2094
2127
|
}
|
|
2128
|
+
classifiedContexts(subdomains, boundedContexts) {
|
|
2129
|
+
const types = [
|
|
2130
|
+
"core",
|
|
2131
|
+
"supporting",
|
|
2132
|
+
"generic"
|
|
2133
|
+
];
|
|
2134
|
+
const classified = /* @__PURE__ */ new Map();
|
|
2135
|
+
const folders = [];
|
|
2136
|
+
for (const type of types) for (const name of subdomains[type] ?? []) {
|
|
2137
|
+
const folder = boundedContexts[name];
|
|
2138
|
+
if (folder === void 0) throw new Error(`subdomains names ${name}, which boundedContexts does not declare.`);
|
|
2139
|
+
const already = classified.get(name);
|
|
2140
|
+
if (already !== void 0) throw new Error(`subdomains lists ${name} as ${already} and as ${type}: a bounded context implements one subdomain.`);
|
|
2141
|
+
classified.set(name, type);
|
|
2142
|
+
folders.push({
|
|
2143
|
+
dir: resolve(this.rootDir, folder),
|
|
2144
|
+
isSharedKernel: false,
|
|
2145
|
+
name,
|
|
2146
|
+
subdomain: type
|
|
2147
|
+
});
|
|
2148
|
+
}
|
|
2149
|
+
const unclassified = Object.keys(boundedContexts).filter((name) => !classified.has(name));
|
|
2150
|
+
if (unclassified.length > 0) throw new Error(`boundedContexts declares ${unclassified.join(", ")}, which subdomains does not classify: list each context under subdomains.core, subdomains.supporting or subdomains.generic.`);
|
|
2151
|
+
return folders;
|
|
2152
|
+
}
|
|
2095
2153
|
validContextMap(upstreams, contexts) {
|
|
2096
2154
|
const map = new ContextMap(upstreams);
|
|
2097
2155
|
const unknown = map.contexts.filter((name) => !contexts.includes(name));
|
|
@@ -2117,6 +2175,7 @@ const ruleSetting = z.enum([
|
|
|
2117
2175
|
"info",
|
|
2118
2176
|
"off"
|
|
2119
2177
|
]);
|
|
2178
|
+
const contextNames = z.array(z.string()).exactOptional();
|
|
2120
2179
|
const schema = z.strictObject({
|
|
2121
2180
|
applicationDependencies: packageDependencies.exactOptional(),
|
|
2122
2181
|
boundedContexts: z.record(z.string(), z.string()),
|
|
@@ -2131,6 +2190,11 @@ const schema = z.strictObject({
|
|
|
2131
2190
|
root: z.string(),
|
|
2132
2191
|
rules: z.strictObject(Object.fromEntries(new RuleRegistry().ids.map((id) => [id, ruleSetting.exactOptional()]))).exactOptional(),
|
|
2133
2192
|
sharedKernel: z.string().exactOptional(),
|
|
2193
|
+
subdomains: z.strictObject({
|
|
2194
|
+
core: contextNames,
|
|
2195
|
+
generic: contextNames,
|
|
2196
|
+
supporting: contextNames
|
|
2197
|
+
}).exactOptional(),
|
|
2134
2198
|
tsconfig: z.string().exactOptional()
|
|
2135
2199
|
});
|
|
2136
2200
|
var ConfigLoader = class ConfigLoader {
|
|
@@ -2807,4 +2871,4 @@ var Cli = class {
|
|
|
2807
2871
|
//#endregion
|
|
2808
2872
|
export { Config as a, RuleRegistry as c, AllowedPackages as d, Baseline as f, ConfigLoader as i, Rule as l, TsMorphImporter as n, Report as o, Importer as r, Checker as s, Cli as t, Architecture as u };
|
|
2809
2873
|
|
|
2810
|
-
//# sourceMappingURL=cli-
|
|
2874
|
+
//# sourceMappingURL=cli-P5PwH9OE.mjs.map
|