@alveolus/arch 0.3.0 → 0.4.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 CHANGED
@@ -14,6 +14,11 @@ import { defineConfig } from "@alveolus/arch";
14
14
 
15
15
  export default defineConfig({
16
16
  boundedContexts: { catalog: "catalog", notifications: "notifications", ordering: "ordering" },
17
+ contextMap: {
18
+ catalog: { consumes: [] },
19
+ notifications: { consumes: ["ordering"] },
20
+ ordering: { consumes: ["catalog"] },
21
+ },
17
22
  root: "src",
18
23
  subdomains: { core: ["catalog", "ordering"], generic: ["notifications"] },
19
24
  });
package/dist/bin.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { r as Cli, t as Docs } from "./docs-DsQHpTtV.mjs";
2
+ import { r as Cli, t as Docs } from "./docs-DcFgskuN.mjs";
3
3
  import { fileURLToPath } from "node:url";
4
4
  //#region src/bin.ts
5
5
  const docs = new Docs(fileURLToPath(new URL("../docs/", import.meta.url)));
@@ -839,24 +839,22 @@ var Architecture = class {
839
839
  };
840
840
  //#endregion
841
841
  //#region src/architecture/context-map.ts
842
- var ContextMap = class ContextMap {
842
+ var ContextMap = class {
843
843
  upstreams;
844
844
  constructor(upstreams) {
845
845
  this.upstreams = upstreams;
846
846
  }
847
- static ofEdges(edges) {
848
- const upstreams = {};
849
- for (const { from, to } of edges) {
850
- const known = upstreams[from] ?? [];
851
- if (!known.includes(to)) upstreams[from] = [...known, to];
852
- }
853
- return new ContextMap(upstreams);
854
- }
855
847
  get contexts() {
856
- const names = new Set(Object.keys(this.upstreams));
848
+ return Object.keys(this.upstreams);
849
+ }
850
+ get consumed() {
851
+ const names = /* @__PURE__ */ new Set();
857
852
  for (const targets of Object.values(this.upstreams)) for (const target of targets) names.add(target);
858
853
  return [...names];
859
854
  }
855
+ selfConsumers() {
856
+ return this.contexts.filter((context) => this.allows(context, context));
857
+ }
860
858
  allows(from, to) {
861
859
  return (this.upstreams[from] ?? []).includes(to);
862
860
  }
@@ -868,9 +866,6 @@ var ContextMap = class ContextMap {
868
866
  if (found !== void 0) return found;
869
867
  }
870
868
  }
871
- cycleThrough(from, to) {
872
- return this.reaches(to, from, /* @__PURE__ */ new Set());
873
- }
874
869
  cycleFrom(context, path, visiting, done) {
875
870
  if (done.has(context)) return;
876
871
  if (visiting.has(context)) return [...path.slice(path.indexOf(context)), context];
@@ -882,12 +877,6 @@ var ContextMap = class ContextMap {
882
877
  visiting.delete(context);
883
878
  done.add(context);
884
879
  }
885
- reaches(from, target, seen) {
886
- if (from === target) return true;
887
- if (seen.has(from)) return false;
888
- seen.add(from);
889
- return (this.upstreams[from] ?? []).some((upstream) => this.reaches(upstream, target, seen));
890
- }
891
880
  };
892
881
  //#endregion
893
882
  //#region src/rules/framework/wording.ts
@@ -1374,23 +1363,13 @@ var NoLeakyHostServiceRule = class extends ClassRule {
1374
1363
  var NoUnmappedContextRule = class extends Rule {
1375
1364
  meta = {
1376
1365
  contexts: "every",
1377
- description: "A bounded context consuming one the context map does not allow, or two contexts that depend on each other.",
1366
+ description: "A bounded context consuming one the context map does not allow.",
1378
1367
  id: "strategic/no-unmapped-context",
1379
- messages: {
1380
- cycle: "{from} consumes {to}, which consumes {from} back: two contexts that depend on each other can no longer change alone; declare a contextMap and reverse one dependency.",
1381
- unmapped: "{from} consumes {to}, which the context map does not allow: add {to} to contextMap.{from}, or reverse the dependency."
1382
- }
1368
+ messages: { unmapped: "{from} consumes {to}, which the context map does not allow: reverse the dependency, or if {from} really is downstream of {to}, add {to} to contextMap.{from}.consumes." }
1383
1369
  };
1384
1370
  check(architecture) {
1385
- const consumptions = this.consumptionsIn(architecture);
1386
- const declared = architecture.contextMap;
1387
1371
  const findings = [];
1388
- if (declared !== void 0) {
1389
- for (const consumption of consumptions) if (!declared.allows(consumption.from, consumption.to)) findings.push(this.consumptionFinding(consumption, "unmapped"));
1390
- return findings;
1391
- }
1392
- const observed = ContextMap.ofEdges(consumptions);
1393
- for (const consumption of consumptions) if (observed.cycleThrough(consumption.from, consumption.to)) findings.push(this.consumptionFinding(consumption, "cycle"));
1372
+ for (const consumption of this.consumptionsIn(architecture)) if (!architecture.contextMap.allows(consumption.from, consumption.to)) findings.push(this.consumptionFinding(consumption));
1394
1373
  return findings;
1395
1374
  }
1396
1375
  consumptionsIn(architecture) {
@@ -1411,9 +1390,9 @@ var NoUnmappedContextRule = class extends Rule {
1411
1390
  }
1412
1391
  return consumptions;
1413
1392
  }
1414
- consumptionFinding(consumption, messageId) {
1393
+ consumptionFinding(consumption) {
1415
1394
  const { dependency, file, from, to } = consumption;
1416
- return this.finding(file, dependency.line, dependency.label, messageId, {
1395
+ return this.finding(file, dependency.line, dependency.label, "unmapped", {
1417
1396
  from,
1418
1397
  to
1419
1398
  });
@@ -2118,7 +2097,7 @@ var Config = class {
2118
2097
  this.applicationDependencies = this.domainDependencies.with(new AllowedPackages(config.applicationDependencies));
2119
2098
  this.ignored = [...testFiles, ...config.ignore ?? []];
2120
2099
  this.extraFolders = config.layout?.extraFolders ?? {};
2121
- this.contextMap = config.contextMap === void 0 ? void 0 : this.validContextMap(config.contextMap, Object.keys(config.boundedContexts));
2100
+ this.contextMap = this.validContextMap(config.contextMap, Object.keys(config.boundedContexts));
2122
2101
  this.rules = config.rules;
2123
2102
  const sharedKernel = config.sharedKernel ?? "shared-kernel";
2124
2103
  this.contextFolders = [...this.classifiedContexts(config.subdomains ?? {}, config.boundedContexts), {
@@ -2152,10 +2131,16 @@ var Config = class {
2152
2131
  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.`);
2153
2132
  return folders;
2154
2133
  }
2155
- validContextMap(upstreams, contexts) {
2134
+ validContextMap(relations, contexts) {
2135
+ const upstreams = {};
2136
+ for (const [name, { consumes }] of Object.entries(relations)) upstreams[name] = consumes;
2156
2137
  const map = new ContextMap(upstreams);
2157
- const unknown = map.contexts.filter((name) => !contexts.includes(name));
2138
+ const unknown = [.../* @__PURE__ */ new Set([...map.contexts, ...map.consumed])].filter((name) => !contexts.includes(name));
2158
2139
  if (unknown.length > 0) throw new Error(`contextMap names ${unknown.join(", ")}, which boundedContexts does not declare.`);
2140
+ const unlisted = contexts.filter((name) => !map.contexts.includes(name));
2141
+ if (unlisted.length > 0) throw new Error(`contextMap does not list ${unlisted.join(", ")}: every bounded context lists the contexts it consumes, consumes: [] when none.`);
2142
+ const selfConsumers = map.selfConsumers();
2143
+ if (selfConsumers.length > 0) throw new Error(`contextMap lists ${selfConsumers.join(", ")} as consuming itself: a context consumes other contexts only.`);
2159
2144
  const cycle = map.cycle();
2160
2145
  if (cycle !== void 0) throw new Error(`contextMap has a cycle: ${cycle.join(" → ")}. Two contexts that depend on each other can no longer change alone: reverse one dependency.`);
2161
2146
  return map;
@@ -2182,7 +2167,7 @@ const schema = z.strictObject({
2182
2167
  applicationDependencies: packageDependencies.exactOptional(),
2183
2168
  boundedContexts: z.record(z.string(), z.string()),
2184
2169
  compositionRoot: z.string().exactOptional(),
2185
- contextMap: z.record(z.string(), z.array(z.string())).exactOptional(),
2170
+ contextMap: z.record(z.string(), z.strictObject({ consumes: z.array(z.string()) })),
2186
2171
  domainDependencies: packageDependencies.exactOptional(),
2187
2172
  ignore: z.array(z.string()).exactOptional(),
2188
2173
  layout: z.strictObject({ extraFolders: z.strictObject({
@@ -2781,6 +2766,7 @@ const scaffolds = [
2781
2766
 
2782
2767
  export default defineConfig({
2783
2768
  boundedContexts: {},
2769
+ contextMap: {},
2784
2770
  root: "src",
2785
2771
  });
2786
2772
  `,
@@ -2799,7 +2785,7 @@ The documentation is installed with the package: read it with \`npx alveolus exp
2799
2785
  1. Before writing a class, read its building block: \`npx alveolus explain aggregates\`, \`entities\`, \`value-objects\`, \`domain-events\`, \`domain-errors\`, \`ports\`, \`repositories\`, \`command-handlers\`, \`query-handlers\`, \`result\`.
2800
2786
  2. Where a file goes and what each layer may import: \`npx alveolus explain project-layout\`.
2801
2787
  3. After each change, run \`npx alveolus arch check\`.
2802
- 4. On a violation, read the rule before changing the code: \`npx alveolus explain <rule>\`, with the rule id of the report, such as \`layers/no-impure-domain\`. Fix the cause: never turn a rule off, add a disable comment or edit \`alveolus.baseline.json\` by hand without asking.
2788
+ 4. On a violation, read the rule before changing the code: \`npx alveolus explain <rule>\`, with the rule id of the report, such as \`layers/no-impure-domain\`. Fix the cause: never turn a rule off, add a disable comment, add a context to \`contextMap\` or edit \`alveolus.baseline.json\` by hand without asking.
2803
2789
  `,
2804
2790
  path: ".claude/skills/alveolus/SKILL.md"
2805
2791
  },
@@ -3056,4 +3042,4 @@ var Docs = class Docs {
3056
3042
  //#endregion
3057
3043
  export { TsMorphImporter as a, Config as c, RuleRegistry as d, Rule as f, Baseline as h, Init as i, Report as l, AllowedPackages as m, Page as n, Importer as o, Architecture as p, Cli as r, ConfigLoader as s, Docs as t, Checker as u };
3058
3044
 
3059
- //# sourceMappingURL=docs-DsQHpTtV.mjs.map
3045
+ //# sourceMappingURL=docs-DcFgskuN.mjs.map