@pattern-stack/codegen 0.14.1 → 0.15.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 +88 -0
- package/dist/runtime/subsystems/bridge/bridge.module.d.ts +22 -0
- package/dist/runtime/subsystems/bridge/bridge.module.js +5 -1
- package/dist/runtime/subsystems/bridge/bridge.module.js.map +1 -1
- package/dist/runtime/subsystems/bridge/index.js +5 -1
- package/dist/runtime/subsystems/bridge/index.js.map +1 -1
- package/dist/runtime/subsystems/events/events.module.d.ts +26 -1
- package/dist/runtime/subsystems/events/events.module.js +6 -5
- package/dist/runtime/subsystems/events/events.module.js.map +1 -1
- package/dist/runtime/subsystems/events/index.js +6 -5
- package/dist/runtime/subsystems/events/index.js.map +1 -1
- package/dist/runtime/subsystems/index.js +6 -5
- package/dist/runtime/subsystems/index.js.map +1 -1
- package/dist/src/cli/index.js +1371 -1284
- package/dist/src/cli/index.js.map +1 -1
- package/package.json +1 -1
- package/runtime/subsystems/bridge/bridge.module.ts +26 -1
- package/runtime/subsystems/events/events.module.ts +39 -6
package/dist/src/cli/index.js
CHANGED
|
@@ -6273,8 +6273,8 @@ async function generateScopeEntityType(opts) {
|
|
|
6273
6273
|
}
|
|
6274
6274
|
|
|
6275
6275
|
// src/cli/shared/subsystem-barrel-generator.ts
|
|
6276
|
-
import
|
|
6277
|
-
import
|
|
6276
|
+
import fs7 from "fs";
|
|
6277
|
+
import path10 from "path";
|
|
6278
6278
|
|
|
6279
6279
|
// src/cli/shared/subsystem-detect.ts
|
|
6280
6280
|
import fs4 from "fs";
|
|
@@ -6541,274 +6541,16 @@ function resolveSubsystemsRoot(ctx, overrideTarget) {
|
|
|
6541
6541
|
return resolveSubsystemsRootFromConfig(ctx.cwd, ctx.config);
|
|
6542
6542
|
}
|
|
6543
6543
|
|
|
6544
|
-
// src/cli/shared/subsystem-barrel-generator.ts
|
|
6545
|
-
function quoteOpts(opts) {
|
|
6546
|
-
const entries = Object.entries(opts).filter(([, v]) => v !== void 0);
|
|
6547
|
-
if (entries.length === 0) return "";
|
|
6548
|
-
return "{ " + entries.map(([k, v]) => `${k}: ${typeof v === "string" ? `'${v}'` : String(v)}`).join(", ") + " }";
|
|
6549
|
-
}
|
|
6550
|
-
function jsonToTs(value) {
|
|
6551
|
-
if (value === null || value === void 0) return "undefined";
|
|
6552
|
-
if (typeof value === "string") return `'${value.replace(/'/g, "\\'")}'`;
|
|
6553
|
-
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
6554
|
-
if (Array.isArray(value)) return `[${value.map(jsonToTs).join(", ")}]`;
|
|
6555
|
-
if (typeof value === "object") {
|
|
6556
|
-
const entries = Object.entries(value).filter(
|
|
6557
|
-
([, v]) => v !== void 0
|
|
6558
|
-
);
|
|
6559
|
-
return `{ ${entries.map(([k, v]) => `${k}: ${jsonToTs(v)}`).join(", ")} }`;
|
|
6560
|
-
}
|
|
6561
|
-
return "undefined";
|
|
6562
|
-
}
|
|
6563
|
-
function quoteBullmqDomainOpts(input) {
|
|
6564
|
-
const { backend, multiTenant, bullExt } = input;
|
|
6565
|
-
if (backend !== "bullmq" || !bullExt) {
|
|
6566
|
-
return quoteOpts({ backend, multiTenant });
|
|
6567
|
-
}
|
|
6568
|
-
const parts = [`backend: 'bullmq'`];
|
|
6569
|
-
if (multiTenant) parts.push(`multiTenant: true`);
|
|
6570
|
-
parts.push(`extensions: { bullmq: ${jsonToTs(bullExt)} }`);
|
|
6571
|
-
return `{ ${parts.join(", ")} }`;
|
|
6572
|
-
}
|
|
6573
|
-
var COMPOSERS = {
|
|
6574
|
-
events: ({ moduleImport, cfg }) => {
|
|
6575
|
-
const backend = cfg?.backend ?? "drizzle";
|
|
6576
|
-
const multiTenant = Boolean(cfg?.multi_tenant);
|
|
6577
|
-
return {
|
|
6578
|
-
imports: [
|
|
6579
|
-
`import { EventsModule } from '${moduleImport("events", "events.module")}';`
|
|
6580
|
-
],
|
|
6581
|
-
calls: [
|
|
6582
|
-
` EventsModule.forRoot(${quoteOpts({ backend, multiTenant })}),`
|
|
6583
|
-
]
|
|
6584
|
-
};
|
|
6585
|
-
},
|
|
6586
|
-
jobs: ({ moduleImport, cfg }) => {
|
|
6587
|
-
const backend = cfg?.backend ?? "drizzle";
|
|
6588
|
-
const multiTenant = Boolean(cfg?.multi_tenant);
|
|
6589
|
-
const workerMode = (cfg?.worker_mode ?? "standalone").trim();
|
|
6590
|
-
const imports = [
|
|
6591
|
-
`import { JobsDomainModule } from '${moduleImport("jobs", "jobs-domain.module")}';`
|
|
6592
|
-
];
|
|
6593
|
-
const bullExt = backend === "bullmq" ? cfg?.extensions?.bullmq : void 0;
|
|
6594
|
-
const domainOpts = quoteBullmqDomainOpts({ backend, multiTenant, bullExt });
|
|
6595
|
-
const calls = [` JobsDomainModule.forRoot(${domainOpts}),`];
|
|
6596
|
-
if (workerMode === "embedded") {
|
|
6597
|
-
imports.push(
|
|
6598
|
-
`import { JobWorkerModule } from '${moduleImport("jobs", "job-worker.module")}';`
|
|
6599
|
-
);
|
|
6600
|
-
const workerOpts = backend === "bullmq" ? `{ mode: 'embedded', backend: 'bullmq'${bullExt ? `, domainModuleExtensions: { bullmq: ${jsonToTs(bullExt)} }` : ""} }` : `{ mode: 'embedded' }`;
|
|
6601
|
-
calls.push(` JobWorkerModule.forRoot(${workerOpts}),`);
|
|
6602
|
-
}
|
|
6603
|
-
return { imports, calls };
|
|
6604
|
-
},
|
|
6605
|
-
bridge: ({ moduleImport, cfg }) => {
|
|
6606
|
-
const backend = cfg?.backend ?? "drizzle";
|
|
6607
|
-
const multiTenant = Boolean(cfg?.multi_tenant);
|
|
6608
|
-
return {
|
|
6609
|
-
imports: [
|
|
6610
|
-
`import { BridgeModule } from '${moduleImport("bridge", "bridge.module")}';`
|
|
6611
|
-
],
|
|
6612
|
-
calls: [
|
|
6613
|
-
` BridgeModule.forRoot(${quoteOpts({ backend, multiTenant })}),`
|
|
6614
|
-
]
|
|
6615
|
-
};
|
|
6616
|
-
},
|
|
6617
|
-
integration: ({ moduleImport, cfg }) => {
|
|
6618
|
-
const backend = cfg?.backend ?? "drizzle";
|
|
6619
|
-
const multiTenant = Boolean(cfg?.multi_tenant);
|
|
6620
|
-
return {
|
|
6621
|
-
imports: [
|
|
6622
|
-
`import { IntegrationModule } from '${moduleImport("integration", "integration.module")}';`
|
|
6623
|
-
],
|
|
6624
|
-
calls: [
|
|
6625
|
-
` IntegrationModule.forRoot(${quoteOpts({ backend, multiTenant })}),`
|
|
6626
|
-
]
|
|
6627
|
-
};
|
|
6628
|
-
}
|
|
6629
|
-
};
|
|
6630
|
-
var PACKAGE2 = "@pattern-stack/codegen";
|
|
6631
|
-
function makeModuleImport(mode, subsystemsRel) {
|
|
6632
|
-
if (mode === "vendored") {
|
|
6633
|
-
return (subsystem, moduleBasename) => `${subsystemsRel}/${subsystem}/${moduleBasename}`;
|
|
6634
|
-
}
|
|
6635
|
-
return (subsystem) => subsystem === "events" ? `${PACKAGE2}/subsystems` : `${PACKAGE2}/runtime/subsystems/${subsystem}/index`;
|
|
6636
|
-
}
|
|
6637
|
-
var COMPOSABLE_ORDER = ["events", "jobs", "bridge", "integration"];
|
|
6638
|
-
var HEADER3 = `// AUTO-GENERATED by @pattern-stack/codegen. DO NOT EDIT.
|
|
6639
|
-
// Subsystem composition barrel \u2014 reflects \`subsystems.install\` in
|
|
6640
|
-
// codegen.config.yaml and the per-subsystem option blocks
|
|
6641
|
-
// (\`events:\`, \`jobs:\`, \`bridge:\`, \`integration:\`).
|
|
6642
|
-
//
|
|
6643
|
-
// Wire into AppModule once:
|
|
6644
|
-
//
|
|
6645
|
-
// import { SUBSYSTEM_MODULES } from './generated/subsystems';
|
|
6646
|
-
// @Module({ imports: [DatabaseModule, ...SUBSYSTEM_MODULES, ...GENERATED_MODULES] })
|
|
6647
|
-
//
|
|
6648
|
-
// Regenerated by every \`codegen entity new\` / \`codegen subsystem install\`.
|
|
6649
|
-
|
|
6650
|
-
`;
|
|
6651
|
-
function buildSubsystemBarrel(installed, config, subsystemsRel, mode = "vendored") {
|
|
6652
|
-
const moduleImport = makeModuleImport(mode, subsystemsRel);
|
|
6653
|
-
const actable = installed.filter((i) => i.status !== "incomplete");
|
|
6654
|
-
const installedNames = new Set(actable.map((i) => i.name));
|
|
6655
|
-
const emitted = [];
|
|
6656
|
-
const skipped = [];
|
|
6657
|
-
const allImports = [`import type { DynamicModule } from '@nestjs/common';`];
|
|
6658
|
-
const allCalls = [];
|
|
6659
|
-
for (const name of COMPOSABLE_ORDER) {
|
|
6660
|
-
if (!installedNames.has(name)) continue;
|
|
6661
|
-
const composer = COMPOSERS[name];
|
|
6662
|
-
if (!composer) {
|
|
6663
|
-
skipped.push(name);
|
|
6664
|
-
continue;
|
|
6665
|
-
}
|
|
6666
|
-
const cfg = config?.[name] ?? void 0;
|
|
6667
|
-
const out = composer({ moduleImport, cfg });
|
|
6668
|
-
allImports.push(...out.imports);
|
|
6669
|
-
allCalls.push(...out.calls);
|
|
6670
|
-
emitted.push(name);
|
|
6671
|
-
}
|
|
6672
|
-
for (const inst of actable) {
|
|
6673
|
-
if (!COMPOSABLE_ORDER.includes(inst.name) && !COMPOSERS[inst.name]) {
|
|
6674
|
-
skipped.push(inst.name);
|
|
6675
|
-
}
|
|
6676
|
-
}
|
|
6677
|
-
const exportLine = allCalls.length === 0 ? `export const SUBSYSTEM_MODULES: DynamicModule[] = [];
|
|
6678
|
-
` : `export const SUBSYSTEM_MODULES: DynamicModule[] = [
|
|
6679
|
-
${allCalls.join("\n")}
|
|
6680
|
-
];
|
|
6681
|
-
`;
|
|
6682
|
-
const body = allImports.join("\n") + "\n\n" + exportLine;
|
|
6683
|
-
return { content: HEADER3 + body, emitted, skipped };
|
|
6684
|
-
}
|
|
6685
|
-
async function regenerateSubsystemBarrel(opts) {
|
|
6686
|
-
const { ctx, dryRun = false } = opts;
|
|
6687
|
-
const generatedDir = opts.generatedDir ?? resolveGeneratedDir(ctx);
|
|
6688
|
-
const mode = resolveRuntimeMode(ctx.config);
|
|
6689
|
-
const installed = mode === "package" ? configuredInstalledSubsystems(
|
|
6690
|
-
ctx.config
|
|
6691
|
-
) : await detectInstalledSubsystems(ctx);
|
|
6692
|
-
const subsystemsAbs = resolveSubsystemsRoot(ctx);
|
|
6693
|
-
const barrelAbs = path8.resolve(generatedDir, "subsystems.ts");
|
|
6694
|
-
let subsystemsRel = path8.relative(path8.dirname(barrelAbs), subsystemsAbs).split(path8.sep).join("/");
|
|
6695
|
-
if (!subsystemsRel.startsWith(".")) subsystemsRel = "./" + subsystemsRel;
|
|
6696
|
-
const { content, emitted, skipped } = buildSubsystemBarrel(
|
|
6697
|
-
installed,
|
|
6698
|
-
ctx.config,
|
|
6699
|
-
subsystemsRel,
|
|
6700
|
-
mode
|
|
6701
|
-
);
|
|
6702
|
-
let written = false;
|
|
6703
|
-
if (!dryRun) {
|
|
6704
|
-
fs5.mkdirSync(path8.dirname(barrelAbs), { recursive: true });
|
|
6705
|
-
fs5.writeFileSync(barrelAbs, content);
|
|
6706
|
-
written = true;
|
|
6707
|
-
}
|
|
6708
|
-
return {
|
|
6709
|
-
subsystemBarrel: barrelAbs,
|
|
6710
|
-
emitted,
|
|
6711
|
-
skipped,
|
|
6712
|
-
content,
|
|
6713
|
-
written
|
|
6714
|
-
};
|
|
6715
|
-
}
|
|
6716
|
-
|
|
6717
|
-
// src/cli/shared/subsystem-schema-generator.ts
|
|
6718
|
-
import fs6 from "fs";
|
|
6719
|
-
import path9 from "path";
|
|
6720
|
-
var PACKAGE3 = "@pattern-stack/codegen";
|
|
6721
|
-
var SCHEMA_SYMBOLS = {
|
|
6722
|
-
events: ["domainEvents"],
|
|
6723
|
-
jobs: [
|
|
6724
|
-
"jobs",
|
|
6725
|
-
"jobRuns",
|
|
6726
|
-
"jobSteps",
|
|
6727
|
-
"jobRunStatusEnum",
|
|
6728
|
-
"jobStepKindEnum",
|
|
6729
|
-
"jobStepStatusEnum",
|
|
6730
|
-
"collisionModeEnum",
|
|
6731
|
-
"replayFromEnum",
|
|
6732
|
-
"parentClosePolicyEnum",
|
|
6733
|
-
"waitKindEnum",
|
|
6734
|
-
"triggerSourceEnum"
|
|
6735
|
-
],
|
|
6736
|
-
bridge: ["bridgeDelivery", "bridgeDeliveryStatusEnum"],
|
|
6737
|
-
integration: [
|
|
6738
|
-
"integrationSubscriptions",
|
|
6739
|
-
"integrationRuns",
|
|
6740
|
-
"integrationRunItems",
|
|
6741
|
-
"integrationRunDirectionEnum",
|
|
6742
|
-
"integrationRunActionEnum",
|
|
6743
|
-
"integrationRunStatusEnum",
|
|
6744
|
-
"integrationRunItemOperationEnum",
|
|
6745
|
-
"integrationRunItemStatusEnum"
|
|
6746
|
-
]
|
|
6747
|
-
};
|
|
6748
|
-
var SCHEMA_ORDER = ["events", "jobs", "bridge", "integration"];
|
|
6749
|
-
var HEADER4 = `// AUTO-GENERATED by @pattern-stack/codegen. Do not edit.
|
|
6750
|
-
// Subsystem Drizzle schema barrel \u2014 re-exports the tables + pgEnums of every
|
|
6751
|
-
// installed subsystem so drizzle-kit emits their CREATE TABLE / CREATE TYPE.
|
|
6752
|
-
//
|
|
6753
|
-
// Fold into your drizzle-kit schema entrypoint once, alongside the entity
|
|
6754
|
-
// schema barrel:
|
|
6755
|
-
//
|
|
6756
|
-
// // src/schema.ts
|
|
6757
|
-
// export * from './generated/schema'; // entity tables
|
|
6758
|
-
// export * from './generated/subsystems-schema'; // subsystem tables + enums
|
|
6759
|
-
//
|
|
6760
|
-
// Regenerated by every \`codegen entity new\` / \`codegen subsystem install\`.
|
|
6761
|
-
|
|
6762
|
-
`;
|
|
6763
|
-
function schemaImportFor(mode, subsystem, subsystemsRel) {
|
|
6764
|
-
return mode === "vendored" ? `${subsystemsRel}/${subsystem}` : `${PACKAGE3}/runtime/subsystems/${subsystem}/index`;
|
|
6765
|
-
}
|
|
6766
|
-
function buildSubsystemSchemaBarrel(installed, subsystemsRel, mode) {
|
|
6767
|
-
const actable = installed.filter((i) => i.status !== "incomplete");
|
|
6768
|
-
const installedNames = new Set(actable.map((i) => i.name));
|
|
6769
|
-
const emitted = [];
|
|
6770
|
-
const lines = [];
|
|
6771
|
-
for (const name of SCHEMA_ORDER) {
|
|
6772
|
-
if (!installedNames.has(name)) continue;
|
|
6773
|
-
const symbols = SCHEMA_SYMBOLS[name];
|
|
6774
|
-
if (!symbols || symbols.length === 0) continue;
|
|
6775
|
-
const importSpec = schemaImportFor(mode, name, subsystemsRel);
|
|
6776
|
-
lines.push(`export { ${symbols.join(", ")} } from '${importSpec}';`);
|
|
6777
|
-
emitted.push(name);
|
|
6778
|
-
}
|
|
6779
|
-
const body = lines.length === 0 ? "export {};\n" : lines.join("\n") + "\n";
|
|
6780
|
-
return { content: HEADER4 + body, emitted };
|
|
6781
|
-
}
|
|
6782
|
-
async function regenerateSubsystemSchemaBarrel(opts) {
|
|
6783
|
-
const { ctx, dryRun = false } = opts;
|
|
6784
|
-
const generatedDir = opts.generatedDir ?? resolveGeneratedDir(ctx);
|
|
6785
|
-
const mode = resolveRuntimeMode(ctx.config);
|
|
6786
|
-
const installed = mode === "package" ? configuredInstalledSubsystems(
|
|
6787
|
-
ctx.config
|
|
6788
|
-
) : await detectInstalledSubsystems(ctx);
|
|
6789
|
-
const subsystemsAbs = resolveSubsystemsRoot(ctx);
|
|
6790
|
-
const barrelAbs = path9.resolve(generatedDir, "subsystems-schema.ts");
|
|
6791
|
-
let subsystemsRel = path9.relative(path9.dirname(barrelAbs), subsystemsAbs).split(path9.sep).join("/");
|
|
6792
|
-
if (!subsystemsRel.startsWith(".")) subsystemsRel = "./" + subsystemsRel;
|
|
6793
|
-
const { content, emitted } = buildSubsystemSchemaBarrel(
|
|
6794
|
-
installed,
|
|
6795
|
-
subsystemsRel,
|
|
6796
|
-
mode
|
|
6797
|
-
);
|
|
6798
|
-
let written = false;
|
|
6799
|
-
if (!dryRun) {
|
|
6800
|
-
fs6.mkdirSync(path9.dirname(barrelAbs), { recursive: true });
|
|
6801
|
-
fs6.writeFileSync(barrelAbs, content);
|
|
6802
|
-
written = true;
|
|
6803
|
-
}
|
|
6804
|
-
return { schemaBarrel: barrelAbs, emitted, content, written };
|
|
6805
|
-
}
|
|
6806
|
-
|
|
6807
6544
|
// src/cli/shared/bridge-registry-generator.ts
|
|
6808
|
-
import
|
|
6809
|
-
import
|
|
6545
|
+
import fs5 from "fs";
|
|
6546
|
+
import path8 from "path";
|
|
6810
6547
|
import ts2 from "typescript";
|
|
6811
|
-
var
|
|
6548
|
+
var PACKAGE_BRIDGE_TYPE_IMPORT = "@pattern-stack/codegen/runtime/subsystems/bridge/index";
|
|
6549
|
+
var OUTPUT_FILE_BY_MODE = {
|
|
6550
|
+
vendored: "registry.ts",
|
|
6551
|
+
package: "bridge-registry.ts"
|
|
6552
|
+
};
|
|
6553
|
+
var HEADER3 = `// AUTO-GENERATED by @pattern-stack/codegen. Do not edit.
|
|
6812
6554
|
// Run \`codegen entity new --all\` to refresh.
|
|
6813
6555
|
`;
|
|
6814
6556
|
var DuplicateTriggerError = class extends Error {
|
|
@@ -6866,12 +6608,12 @@ var UnknownTriggerEventError = class extends Error {
|
|
|
6866
6608
|
name = "UnknownTriggerEventError";
|
|
6867
6609
|
};
|
|
6868
6610
|
function findHandlerFiles(dir) {
|
|
6869
|
-
if (!
|
|
6611
|
+
if (!fs5.existsSync(dir)) return [];
|
|
6870
6612
|
const out = [];
|
|
6871
|
-
for (const entry of
|
|
6613
|
+
for (const entry of fs5.readdirSync(dir, { withFileTypes: true })) {
|
|
6872
6614
|
if (entry.name.startsWith(".")) continue;
|
|
6873
6615
|
if (entry.name === "node_modules" || entry.name === "generated") continue;
|
|
6874
|
-
const full =
|
|
6616
|
+
const full = path8.join(dir, entry.name);
|
|
6875
6617
|
if (entry.isDirectory()) {
|
|
6876
6618
|
out.push(...findHandlerFiles(full));
|
|
6877
6619
|
} else if (entry.isFile() && entry.name.endsWith(".ts") && !entry.name.endsWith(".d.ts")) {
|
|
@@ -6934,7 +6676,7 @@ function scanHandlerFiles(handlersDir) {
|
|
|
6934
6676
|
const files = findHandlerFiles(handlersDir);
|
|
6935
6677
|
const out = [];
|
|
6936
6678
|
for (const filePath of files) {
|
|
6937
|
-
const text2 =
|
|
6679
|
+
const text2 = fs5.readFileSync(filePath, "utf8");
|
|
6938
6680
|
const sourceFile = ts2.createSourceFile(
|
|
6939
6681
|
filePath,
|
|
6940
6682
|
text2,
|
|
@@ -6949,9 +6691,9 @@ function scanHandlerFiles(handlersDir) {
|
|
|
6949
6691
|
}
|
|
6950
6692
|
function readKnownEventTypes(eventsGeneratedDir) {
|
|
6951
6693
|
if (!eventsGeneratedDir) return [];
|
|
6952
|
-
const registryPath =
|
|
6953
|
-
if (!
|
|
6954
|
-
const text2 =
|
|
6694
|
+
const registryPath = path8.join(eventsGeneratedDir, "registry.ts");
|
|
6695
|
+
if (!fs5.existsSync(registryPath)) return [];
|
|
6696
|
+
const text2 = fs5.readFileSync(registryPath, "utf8");
|
|
6955
6697
|
const out = /* @__PURE__ */ new Set();
|
|
6956
6698
|
const re = /^\s*'([a-zA-Z0-9_.-]+)':\s*\{/gm;
|
|
6957
6699
|
let m;
|
|
@@ -6963,9 +6705,9 @@ function readKnownEventTypes(eventsGeneratedDir) {
|
|
|
6963
6705
|
function readEventTiers(eventsGeneratedDir) {
|
|
6964
6706
|
const out = /* @__PURE__ */ new Map();
|
|
6965
6707
|
if (!eventsGeneratedDir) return out;
|
|
6966
|
-
const registryPath =
|
|
6967
|
-
if (!
|
|
6968
|
-
const text2 =
|
|
6708
|
+
const registryPath = path8.join(eventsGeneratedDir, "registry.ts");
|
|
6709
|
+
if (!fs5.existsSync(registryPath)) return out;
|
|
6710
|
+
const text2 = fs5.readFileSync(registryPath, "utf8");
|
|
6969
6711
|
const re = /'([a-zA-Z0-9_.-]+)':\s*\{[^}]*?tier:\s*'(domain|audit)'/g;
|
|
6970
6712
|
let m;
|
|
6971
6713
|
while ((m = re.exec(text2)) !== null) {
|
|
@@ -7020,11 +6762,11 @@ function validateAgainstEventRegistry(triggers, knownEventTypes) {
|
|
|
7020
6762
|
}
|
|
7021
6763
|
}
|
|
7022
6764
|
}
|
|
7023
|
-
function buildBridgeRegistryContent(triggers) {
|
|
6765
|
+
function buildBridgeRegistryContent(triggers, typeImport = "../bridge.protocol") {
|
|
7024
6766
|
const chunks = [];
|
|
7025
|
-
chunks.push(
|
|
6767
|
+
chunks.push(HEADER3);
|
|
7026
6768
|
chunks.push("");
|
|
7027
|
-
chunks.push(`import type { BridgeRegistry } from '
|
|
6769
|
+
chunks.push(`import type { BridgeRegistry } from '${typeImport}';`);
|
|
7028
6770
|
chunks.push("");
|
|
7029
6771
|
if (triggers.length === 0) {
|
|
7030
6772
|
chunks.push(`export const bridgeRegistry: BridgeRegistry = {};`);
|
|
@@ -7057,17 +6799,25 @@ function buildBridgeRegistryContent(triggers) {
|
|
|
7057
6799
|
chunks.push("");
|
|
7058
6800
|
return chunks.join("\n");
|
|
7059
6801
|
}
|
|
7060
|
-
var OUTPUT_FILE_NAME = "registry.ts";
|
|
7061
6802
|
async function generateBridgeRegistry(opts) {
|
|
7062
|
-
const {
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
|
|
7069
|
-
|
|
7070
|
-
|
|
6803
|
+
const {
|
|
6804
|
+
handlersDir,
|
|
6805
|
+
eventsGeneratedDir,
|
|
6806
|
+
outputDir,
|
|
6807
|
+
mode = "vendored",
|
|
6808
|
+
bridgeInstalled = false,
|
|
6809
|
+
dryRun = false
|
|
6810
|
+
} = opts;
|
|
6811
|
+
const outputFileName = OUTPUT_FILE_BY_MODE[mode];
|
|
6812
|
+
const typeImport = mode === "package" ? PACKAGE_BRIDGE_TYPE_IMPORT : "../bridge.protocol";
|
|
6813
|
+
const installed = mode === "package" ? bridgeInstalled : fs5.existsSync(path8.resolve(outputDir, "..", "bridge.protocol.ts"));
|
|
6814
|
+
if (!installed) {
|
|
6815
|
+
const strayPath = path8.join(outputDir, outputFileName);
|
|
6816
|
+
if (!dryRun && fs5.existsSync(strayPath)) {
|
|
6817
|
+
fs5.rmSync(strayPath);
|
|
6818
|
+
}
|
|
6819
|
+
return {
|
|
6820
|
+
outputDir,
|
|
7071
6821
|
triggerCount: 0,
|
|
7072
6822
|
triggers: [],
|
|
7073
6823
|
eventTypeCount: 0,
|
|
@@ -7082,16 +6832,16 @@ async function generateBridgeRegistry(opts) {
|
|
|
7082
6832
|
validateAgainstEventRegistry(triggers, knownEventTypes);
|
|
7083
6833
|
const eventTiers = readEventTiers(eventsGeneratedDir);
|
|
7084
6834
|
validateNoAuditTriggers(triggers, eventTiers);
|
|
7085
|
-
const content = buildBridgeRegistryContent(triggers);
|
|
6835
|
+
const content = buildBridgeRegistryContent(triggers, typeImport);
|
|
7086
6836
|
const file = {
|
|
7087
|
-
name:
|
|
7088
|
-
outputPath:
|
|
6837
|
+
name: outputFileName,
|
|
6838
|
+
outputPath: path8.join(outputDir, outputFileName),
|
|
7089
6839
|
content
|
|
7090
6840
|
};
|
|
7091
6841
|
let written = false;
|
|
7092
6842
|
if (!dryRun) {
|
|
7093
|
-
|
|
7094
|
-
|
|
6843
|
+
fs5.mkdirSync(outputDir, { recursive: true });
|
|
6844
|
+
fs5.writeFileSync(file.outputPath, file.content);
|
|
7095
6845
|
written = true;
|
|
7096
6846
|
}
|
|
7097
6847
|
const eventTypeCount = new Set(triggers.map((t) => t.event)).size;
|
|
@@ -7106,1083 +6856,1420 @@ async function generateBridgeRegistry(opts) {
|
|
|
7106
6856
|
};
|
|
7107
6857
|
}
|
|
7108
6858
|
|
|
7109
|
-
// src/cli/shared/
|
|
7110
|
-
import
|
|
7111
|
-
import
|
|
7112
|
-
|
|
7113
|
-
|
|
7114
|
-
|
|
7115
|
-
|
|
7116
|
-
|
|
6859
|
+
// src/cli/shared/event-codegen-generator.ts
|
|
6860
|
+
import fs6 from "fs";
|
|
6861
|
+
import path9 from "path";
|
|
6862
|
+
|
|
6863
|
+
// src/parser/load-events.ts
|
|
6864
|
+
import { basename as basename2, resolve as resolve4 } from "path";
|
|
6865
|
+
function loadErrorToIssue2(error) {
|
|
6866
|
+
const issues = [];
|
|
6867
|
+
issues.push({
|
|
6868
|
+
severity: "error",
|
|
6869
|
+
type: "parse_error",
|
|
6870
|
+
message: error.error,
|
|
6871
|
+
path: error.filePath
|
|
6872
|
+
});
|
|
6873
|
+
if (error.details) {
|
|
6874
|
+
for (const detail of error.details) {
|
|
6875
|
+
issues.push({
|
|
6876
|
+
severity: "error",
|
|
6877
|
+
type: "schema_error",
|
|
6878
|
+
message: detail,
|
|
6879
|
+
path: error.filePath
|
|
6880
|
+
});
|
|
6881
|
+
}
|
|
7117
6882
|
}
|
|
7118
|
-
|
|
7119
|
-
patternName;
|
|
7120
|
-
name = "OrchestrationEmissionError";
|
|
7121
|
-
};
|
|
7122
|
-
var HEADER6 = `// AUTO-GENERATED by @pattern-stack/codegen. Do not edit.
|
|
7123
|
-
// See ADR-032 \u2014 Orchestration Patterns.
|
|
7124
|
-
`;
|
|
7125
|
-
function splitWords(str) {
|
|
7126
|
-
return str.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").split(/\s+/).filter((w) => w.length > 0).map((w) => w.toLowerCase());
|
|
7127
|
-
}
|
|
7128
|
-
function toKebabCase2(str) {
|
|
7129
|
-
return splitWords(str).join("-");
|
|
7130
|
-
}
|
|
7131
|
-
function toPascalCase2(str) {
|
|
7132
|
-
return splitWords(str).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
7133
|
-
}
|
|
7134
|
-
function toCamelCase(str) {
|
|
7135
|
-
const words = splitWords(str);
|
|
7136
|
-
if (words.length === 0) return "";
|
|
7137
|
-
const [first, ...rest] = words;
|
|
7138
|
-
return (first ?? "") + rest.map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
6883
|
+
return issues;
|
|
7139
6884
|
}
|
|
7140
|
-
function
|
|
7141
|
-
|
|
6885
|
+
function stripYamlExt(file) {
|
|
6886
|
+
const base = basename2(file);
|
|
6887
|
+
if (base.endsWith(".yaml")) return base.slice(0, -".yaml".length);
|
|
6888
|
+
if (base.endsWith(".yml")) return base.slice(0, -".yml".length);
|
|
6889
|
+
return base;
|
|
7142
6890
|
}
|
|
7143
|
-
|
|
7144
|
-
|
|
7145
|
-
|
|
7146
|
-
|
|
7147
|
-
|
|
7148
|
-
|
|
7149
|
-
|
|
7150
|
-
|
|
7151
|
-
|
|
7152
|
-
|
|
7153
|
-
|
|
7154
|
-
|
|
7155
|
-
|
|
7156
|
-
|
|
7157
|
-
|
|
7158
|
-
"pattern_cokeyed_missing_name",
|
|
7159
|
-
pattern.name,
|
|
7160
|
-
`Co-keyed registry (valueType '${sibling.valueType}') is missing the required 'name' field. Each co-keyed sibling must declare an explicit name; the emitter uppercases it for the token suffix and PascalCases it for the dispatcher method (e.g. name: 'auth' \u21D2 '\${PATTERN_CONST}_AUTH_REGISTRY' + 'selectAuth(...)'). ADR-032 O-1.`
|
|
7161
|
-
);
|
|
7162
|
-
}
|
|
6891
|
+
function loadEvents(eventsDir, entityNames) {
|
|
6892
|
+
const events = [];
|
|
6893
|
+
const issues = [];
|
|
6894
|
+
const resolvedDir = resolve4(eventsDir);
|
|
6895
|
+
let files;
|
|
6896
|
+
try {
|
|
6897
|
+
files = findYamlFiles(resolvedDir);
|
|
6898
|
+
} catch {
|
|
6899
|
+
issues.push({
|
|
6900
|
+
severity: "warning",
|
|
6901
|
+
type: "no_events_dir",
|
|
6902
|
+
message: `No events directory found at: ${resolvedDir}`,
|
|
6903
|
+
path: resolvedDir
|
|
6904
|
+
});
|
|
6905
|
+
return { events, issues };
|
|
7163
6906
|
}
|
|
7164
|
-
|
|
7165
|
-
|
|
7166
|
-
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
6907
|
+
if (files.length === 0) {
|
|
6908
|
+
issues.push({
|
|
6909
|
+
severity: "warning",
|
|
6910
|
+
type: "no_files",
|
|
6911
|
+
message: `No event YAML files found in directory: ${resolvedDir}`,
|
|
6912
|
+
path: resolvedDir
|
|
6913
|
+
});
|
|
6914
|
+
return { events, issues };
|
|
6915
|
+
}
|
|
6916
|
+
const entityNameSet = new Set(entityNames);
|
|
6917
|
+
const seenTypes = /* @__PURE__ */ new Map();
|
|
6918
|
+
for (const filePath of files) {
|
|
6919
|
+
const result = loadEventFromYaml(filePath);
|
|
6920
|
+
if (!result.success) {
|
|
6921
|
+
issues.push(...loadErrorToIssue2(result));
|
|
6922
|
+
continue;
|
|
7171
6923
|
}
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
|
|
7177
|
-
|
|
6924
|
+
const { definition } = result;
|
|
6925
|
+
const baseName = stripYamlExt(filePath);
|
|
6926
|
+
if (baseName !== definition.type) {
|
|
6927
|
+
issues.push({
|
|
6928
|
+
severity: "error",
|
|
6929
|
+
type: "event_filename_mismatch",
|
|
6930
|
+
message: `Event file '${baseName}' must contain 'type: ${baseName}' (found 'type: ${definition.type}')`,
|
|
6931
|
+
path: filePath,
|
|
6932
|
+
suggestion: `Rename the file to '${definition.type}.yaml' or fix the 'type' field to '${baseName}'`
|
|
6933
|
+
});
|
|
6934
|
+
continue;
|
|
7178
6935
|
}
|
|
7179
|
-
if (
|
|
7180
|
-
|
|
7181
|
-
"
|
|
7182
|
-
|
|
7183
|
-
`
|
|
7184
|
-
|
|
6936
|
+
if (definition.direction === "change" && definition.aggregate !== void 0 && !entityNameSet.has(definition.aggregate)) {
|
|
6937
|
+
issues.push({
|
|
6938
|
+
severity: "error",
|
|
6939
|
+
type: "unknown_aggregate",
|
|
6940
|
+
message: `change event '${definition.type}' references unknown aggregate entity '${definition.aggregate}'`,
|
|
6941
|
+
path: filePath,
|
|
6942
|
+
suggestion: `Define entities/${definition.aggregate}.yaml or fix the aggregate value`
|
|
6943
|
+
});
|
|
6944
|
+
continue;
|
|
7185
6945
|
}
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
6946
|
+
if (seenTypes.has(definition.type)) {
|
|
6947
|
+
issues.push({
|
|
6948
|
+
severity: "error",
|
|
6949
|
+
type: "duplicate_event_type",
|
|
6950
|
+
message: `Duplicate event type '${definition.type}' (already declared in ${seenTypes.get(definition.type)})`,
|
|
6951
|
+
path: filePath
|
|
6952
|
+
});
|
|
6953
|
+
continue;
|
|
6954
|
+
}
|
|
6955
|
+
seenTypes.set(definition.type, filePath);
|
|
6956
|
+
events.push(definition);
|
|
6957
|
+
}
|
|
6958
|
+
return { events, issues };
|
|
6959
|
+
}
|
|
6960
|
+
function desugarEntityEvents(entity) {
|
|
6961
|
+
const entityName = entity.entity.name;
|
|
6962
|
+
const entityEvents = entity.events ?? [];
|
|
6963
|
+
return entityEvents.map((ev) => {
|
|
6964
|
+
const payload = {};
|
|
6965
|
+
for (const [key, typeString] of Object.entries(ev.body)) {
|
|
6966
|
+
if (!isEventFieldType(typeString)) {
|
|
6967
|
+
throw new Error(
|
|
6968
|
+
`Entity '${entityName}' event '${ev.name}' field '${key}' has unknown type '${typeString}' \u2014 expected one of ${EVENT_FIELD_TYPES.join("|")}`
|
|
7192
6969
|
);
|
|
7193
6970
|
}
|
|
6971
|
+
payload[key] = { type: typeString, nullable: false };
|
|
7194
6972
|
}
|
|
7195
|
-
|
|
6973
|
+
const def = {
|
|
6974
|
+
type: ev.name,
|
|
6975
|
+
tier: "domain",
|
|
6976
|
+
direction: "change",
|
|
6977
|
+
aggregate: entityName,
|
|
6978
|
+
payload,
|
|
6979
|
+
retry: { attempts: 3, backoff: "exponential" },
|
|
6980
|
+
version: 1,
|
|
6981
|
+
pool: DIRECTION_TO_POOL.change
|
|
6982
|
+
};
|
|
6983
|
+
return def;
|
|
6984
|
+
});
|
|
7196
6985
|
}
|
|
7197
|
-
function
|
|
7198
|
-
|
|
7199
|
-
const pascal = toPascalCase2(pattern.name);
|
|
7200
|
-
const screaming = toScreamingSnake(pattern.name);
|
|
7201
|
-
return { slug, pascal, screaming };
|
|
6986
|
+
function isEventFieldType(s) {
|
|
6987
|
+
return EVENT_FIELD_TYPES.includes(s);
|
|
7202
6988
|
}
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
6989
|
+
|
|
6990
|
+
// src/cli/shared/event-codegen-generator.ts
|
|
6991
|
+
var PACKAGE_EVENTS_RUNTIME_IMPORT = "@pattern-stack/codegen/runtime/subsystems/events/index";
|
|
6992
|
+
function eventsRuntimeImports(mode) {
|
|
6993
|
+
if (mode === "package") {
|
|
7206
6994
|
return {
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
overrideField: "overrides",
|
|
7211
|
-
builderFn: `build${pascal}RegistryProviders`
|
|
6995
|
+
protocol: PACKAGE_EVENTS_RUNTIME_IMPORT,
|
|
6996
|
+
tokens: PACKAGE_EVENTS_RUNTIME_IMPORT,
|
|
6997
|
+
errors: PACKAGE_EVENTS_RUNTIME_IMPORT
|
|
7212
6998
|
};
|
|
7213
6999
|
}
|
|
7214
|
-
const namePascal = toPascalCase2(reg.name);
|
|
7215
|
-
const nameScreaming = toScreamingSnake(reg.name);
|
|
7216
|
-
const nameCamel = toCamelCase(reg.name);
|
|
7217
7000
|
return {
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
overrideField: `${nameCamel}Overrides`,
|
|
7222
|
-
builderFn: `build${pascal}RegistryProviders`
|
|
7001
|
+
protocol: "../event-bus.protocol",
|
|
7002
|
+
tokens: "../events.tokens",
|
|
7003
|
+
errors: "../events-errors"
|
|
7223
7004
|
};
|
|
7224
7005
|
}
|
|
7225
|
-
|
|
7226
|
-
|
|
7006
|
+
var HEADER4 = `// AUTO-GENERATED by @pattern-stack/codegen. Do not edit.
|
|
7007
|
+
// Run \`codegen entity new --all\` to refresh.
|
|
7008
|
+
`;
|
|
7009
|
+
function toCamelCase(input) {
|
|
7010
|
+
const parts = input.split("_").filter(Boolean);
|
|
7011
|
+
if (parts.length === 0) return input;
|
|
7012
|
+
const [first, ...rest] = parts;
|
|
7013
|
+
return (first ?? "") + rest.map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
7227
7014
|
}
|
|
7228
|
-
function
|
|
7229
|
-
return
|
|
7015
|
+
function toPascalCase2(input) {
|
|
7016
|
+
return input.split("_").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
7230
7017
|
}
|
|
7231
|
-
|
|
7232
|
-
|
|
7018
|
+
var TS_TYPE_BY_FIELD = {
|
|
7019
|
+
uuid: "string",
|
|
7020
|
+
string: "string",
|
|
7021
|
+
number: "number",
|
|
7022
|
+
boolean: "boolean",
|
|
7023
|
+
date: "Date",
|
|
7024
|
+
json: "Record<string, unknown>"
|
|
7025
|
+
};
|
|
7026
|
+
var ZOD_EXPR_BY_FIELD = {
|
|
7027
|
+
uuid: "z.string().uuid()",
|
|
7028
|
+
string: "z.string()",
|
|
7029
|
+
number: "z.number()",
|
|
7030
|
+
boolean: "z.boolean()",
|
|
7031
|
+
date: "z.coerce.date()",
|
|
7032
|
+
json: "z.record(z.unknown())"
|
|
7033
|
+
};
|
|
7034
|
+
function tsTypeForField(field) {
|
|
7035
|
+
let base;
|
|
7036
|
+
if (field.type === "array") {
|
|
7037
|
+
const itemType = field.items;
|
|
7038
|
+
base = `${TS_TYPE_BY_FIELD[itemType]}[]`;
|
|
7039
|
+
} else {
|
|
7040
|
+
base = TS_TYPE_BY_FIELD[field.type];
|
|
7041
|
+
}
|
|
7042
|
+
return field.nullable ? `${base} | null` : base;
|
|
7233
7043
|
}
|
|
7234
|
-
function
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
const
|
|
7238
|
-
|
|
7239
|
-
|
|
7240
|
-
|
|
7241
|
-
const out = [];
|
|
7242
|
-
for (const specifier of [...grouped.keys()].sort()) {
|
|
7243
|
-
const names2 = [...grouped.get(specifier)].sort();
|
|
7244
|
-
out.push(`import type { ${names2.join(", ")} } from '${specifier}';`);
|
|
7245
|
-
}
|
|
7246
|
-
return out;
|
|
7247
|
-
}
|
|
7248
|
-
function emitValueImports(imports) {
|
|
7249
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
7250
|
-
for (const i of imports) {
|
|
7251
|
-
const set = grouped.get(i.specifier) ?? /* @__PURE__ */ new Set();
|
|
7252
|
-
set.add(i.name);
|
|
7253
|
-
grouped.set(i.specifier, set);
|
|
7254
|
-
}
|
|
7255
|
-
const out = [];
|
|
7256
|
-
for (const specifier of [...grouped.keys()].sort()) {
|
|
7257
|
-
const names2 = [...grouped.get(specifier)].sort();
|
|
7258
|
-
out.push(`import { ${names2.join(", ")} } from '${specifier}';`);
|
|
7044
|
+
function zodExprForField(field) {
|
|
7045
|
+
let base;
|
|
7046
|
+
if (field.type === "array") {
|
|
7047
|
+
const itemType = field.items;
|
|
7048
|
+
base = `z.array(${ZOD_EXPR_BY_FIELD[itemType]})`;
|
|
7049
|
+
} else {
|
|
7050
|
+
base = ZOD_EXPR_BY_FIELD[field.type];
|
|
7259
7051
|
}
|
|
7260
|
-
return
|
|
7052
|
+
return field.nullable ? `${base}.nullable()` : base;
|
|
7261
7053
|
}
|
|
7262
|
-
function
|
|
7263
|
-
|
|
7264
|
-
list.push({
|
|
7265
|
-
reg: pattern.registry,
|
|
7266
|
-
names: registryNames(pattern, pattern.registry, true),
|
|
7267
|
-
isPrimary: true
|
|
7268
|
-
});
|
|
7269
|
-
for (const sibling of pattern.coKeyedRegistries ?? []) {
|
|
7270
|
-
list.push({
|
|
7271
|
-
reg: sibling,
|
|
7272
|
-
names: registryNames(pattern, sibling, false),
|
|
7273
|
-
isPrimary: false
|
|
7274
|
-
});
|
|
7275
|
-
}
|
|
7276
|
-
return list;
|
|
7054
|
+
function aggregateTypeLiteral(ev) {
|
|
7055
|
+
return ev.aggregate ?? ev.source ?? ev.destination ?? ev.type;
|
|
7277
7056
|
}
|
|
7278
|
-
function
|
|
7279
|
-
const
|
|
7280
|
-
const
|
|
7281
|
-
|
|
7282
|
-
|
|
7283
|
-
typeImports.push({ specifier: reg.valueTypeImport, name: reg.valueType });
|
|
7284
|
-
}
|
|
7285
|
-
const lines = [];
|
|
7286
|
-
lines.push(HEADER6.trimEnd());
|
|
7287
|
-
lines.push("");
|
|
7288
|
-
for (const i of emitTypeImports(typeImports)) lines.push(i);
|
|
7289
|
-
lines.push("");
|
|
7290
|
-
for (const { names: names2 } of registries) {
|
|
7291
|
-
lines.push(`export const ${names2.tokenConst} = Symbol('${names2.tokenConst}');`);
|
|
7057
|
+
function collectEntityEvents(entitiesDir) {
|
|
7058
|
+
const events = [];
|
|
7059
|
+
const issues = [];
|
|
7060
|
+
if (!fs6.existsSync(entitiesDir)) {
|
|
7061
|
+
return { events, issues };
|
|
7292
7062
|
}
|
|
7293
|
-
|
|
7294
|
-
for (const
|
|
7295
|
-
|
|
7296
|
-
|
|
7297
|
-
|
|
7063
|
+
const files = findYamlFiles(entitiesDir);
|
|
7064
|
+
for (const filePath of files) {
|
|
7065
|
+
const result = loadEntityFromYaml(filePath);
|
|
7066
|
+
if (!result.success) continue;
|
|
7067
|
+
try {
|
|
7068
|
+
events.push(...desugarEntityEvents(result.definition));
|
|
7069
|
+
} catch (err) {
|
|
7070
|
+
issues.push({
|
|
7071
|
+
severity: "error",
|
|
7072
|
+
type: "entity_event_desugar_failed",
|
|
7073
|
+
message: err instanceof Error ? err.message : String(err),
|
|
7074
|
+
path: filePath
|
|
7075
|
+
});
|
|
7076
|
+
}
|
|
7298
7077
|
}
|
|
7299
|
-
|
|
7300
|
-
return lines.join("\n");
|
|
7078
|
+
return { events, issues };
|
|
7301
7079
|
}
|
|
7302
|
-
function
|
|
7303
|
-
const
|
|
7304
|
-
const
|
|
7305
|
-
for (const
|
|
7306
|
-
|
|
7307
|
-
providerImports.push({ specifier: e.providerImport, name: e.provider });
|
|
7308
|
-
}
|
|
7080
|
+
function mergeEvents(topLevel, entitySugar) {
|
|
7081
|
+
const issues = [];
|
|
7082
|
+
const byType = /* @__PURE__ */ new Map();
|
|
7083
|
+
for (const ev of entitySugar) {
|
|
7084
|
+
byType.set(ev.type, ev);
|
|
7309
7085
|
}
|
|
7310
|
-
|
|
7311
|
-
|
|
7312
|
-
|
|
7313
|
-
|
|
7314
|
-
|
|
7315
|
-
|
|
7086
|
+
for (const ev of topLevel) {
|
|
7087
|
+
if (byType.has(ev.type)) {
|
|
7088
|
+
issues.push({
|
|
7089
|
+
severity: "warning",
|
|
7090
|
+
type: "event_merge_override",
|
|
7091
|
+
message: `event '${ev.type}' is declared both in an entity \`events:\` block and a top-level \`events/${ev.type}.yaml\` \u2014 top-level definition wins`
|
|
7092
|
+
});
|
|
7093
|
+
}
|
|
7094
|
+
byType.set(ev.type, ev);
|
|
7316
7095
|
}
|
|
7317
|
-
const
|
|
7318
|
-
|
|
7319
|
-
lines.push(HEADER6.trimEnd());
|
|
7320
|
-
lines.push("");
|
|
7321
|
-
lines.push(`import type { Provider } from '@nestjs/common';`);
|
|
7322
|
-
for (const i of emitTypeImports(typeImports)) lines.push(i);
|
|
7323
|
-
for (const i of emitValueImports(providerImports)) lines.push(i);
|
|
7324
|
-
const tokenLine = [
|
|
7325
|
-
...tokenValueImports.sort(),
|
|
7326
|
-
...mapTypeImports.sort().map((m) => `type ${m}`)
|
|
7327
|
-
].join(", ");
|
|
7328
|
-
lines.push(`import { ${tokenLine} } from './tokens.js';`);
|
|
7329
|
-
lines.push(`import type { ${optsType} } from './module.js';`);
|
|
7330
|
-
lines.push("");
|
|
7331
|
-
const { pascal } = patternNames(pattern);
|
|
7332
|
-
lines.push(
|
|
7333
|
-
`export function build${pascal}RegistryProviders(opts?: ${optsType}): Provider[] {`
|
|
7096
|
+
const events = Array.from(byType.values()).sort(
|
|
7097
|
+
(a, b) => a.type.localeCompare(b.type)
|
|
7334
7098
|
);
|
|
7335
|
-
|
|
7336
|
-
|
|
7337
|
-
|
|
7338
|
-
|
|
7339
|
-
|
|
7340
|
-
|
|
7341
|
-
|
|
7342
|
-
for (const
|
|
7343
|
-
|
|
7344
|
-
|
|
7345
|
-
);
|
|
7099
|
+
return { events, issues };
|
|
7100
|
+
}
|
|
7101
|
+
function collectMergedEvents(opts) {
|
|
7102
|
+
const { entitiesDir, eventsDir } = opts;
|
|
7103
|
+
const entityNames = [];
|
|
7104
|
+
if (fs6.existsSync(entitiesDir)) {
|
|
7105
|
+
const entityFiles = findYamlFiles(entitiesDir);
|
|
7106
|
+
for (const f of entityFiles) {
|
|
7107
|
+
const result = loadEntityFromYaml(f);
|
|
7108
|
+
if (result.success) entityNames.push(result.definition.entity.name);
|
|
7346
7109
|
}
|
|
7347
|
-
lines.push(" ]);");
|
|
7348
|
-
lines.push(` if (opts?.${names2.overrideField}) {`);
|
|
7349
|
-
lines.push(` for (const [k, v] of Object.entries(opts.${names2.overrideField})) {`);
|
|
7350
|
-
lines.push(` if (v !== undefined) base.set(k as ${reg.keyType}, v as ${reg.valueType});`);
|
|
7351
|
-
lines.push(" }");
|
|
7352
|
-
lines.push(" }");
|
|
7353
|
-
lines.push(` return base as ${names2.mapType};`);
|
|
7354
|
-
lines.push(" },");
|
|
7355
|
-
const injectList = reg.entries.map((e) => e.provider).join(", ");
|
|
7356
|
-
lines.push(` inject: [${injectList}],`);
|
|
7357
|
-
lines.push(" },");
|
|
7358
7110
|
}
|
|
7359
|
-
|
|
7360
|
-
|
|
7361
|
-
|
|
7362
|
-
|
|
7363
|
-
|
|
7364
|
-
|
|
7365
|
-
const
|
|
7366
|
-
|
|
7111
|
+
const topLevelResult = loadEvents(eventsDir, entityNames);
|
|
7112
|
+
const { events: entitySugar, issues: sugarIssues } = collectEntityEvents(entitiesDir);
|
|
7113
|
+
const { events: merged, issues: mergeIssues } = mergeEvents(
|
|
7114
|
+
topLevelResult.events,
|
|
7115
|
+
entitySugar
|
|
7116
|
+
);
|
|
7117
|
+
const issues = [
|
|
7118
|
+
...topLevelResult.issues,
|
|
7119
|
+
...sugarIssues,
|
|
7120
|
+
...mergeIssues
|
|
7121
|
+
];
|
|
7122
|
+
return { events: merged, issues };
|
|
7367
7123
|
}
|
|
7368
|
-
function
|
|
7369
|
-
const
|
|
7370
|
-
const
|
|
7371
|
-
|
|
7372
|
-
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7124
|
+
function buildTypesContent(events, mode = "vendored") {
|
|
7125
|
+
const sorted = [...events].sort((a, b) => a.type.localeCompare(b.type));
|
|
7126
|
+
const protocolImport = eventsRuntimeImports(mode).protocol;
|
|
7127
|
+
if (sorted.length === 0) {
|
|
7128
|
+
return HEADER4 + `
|
|
7129
|
+
import type { DomainEvent } from '${protocolImport}';
|
|
7130
|
+
|
|
7131
|
+
export type AppDomainEvent = never;
|
|
7132
|
+
|
|
7133
|
+
export type EventTypeName = string;
|
|
7134
|
+
export type EventOfType<T extends EventTypeName> = DomainEvent;
|
|
7135
|
+
export type PayloadOfType<T extends EventTypeName> = DomainEvent['payload'];
|
|
7136
|
+
`;
|
|
7376
7137
|
}
|
|
7377
|
-
const
|
|
7378
|
-
|
|
7379
|
-
|
|
7380
|
-
|
|
7138
|
+
const chunks = [];
|
|
7139
|
+
chunks.push(HEADER4);
|
|
7140
|
+
chunks.push("");
|
|
7141
|
+
chunks.push(`import type { DomainEvent } from '${protocolImport}';`);
|
|
7142
|
+
chunks.push("");
|
|
7143
|
+
for (const ev of sorted) {
|
|
7144
|
+
const interfaceName = `${toPascalCase2(ev.type)}Event`;
|
|
7145
|
+
const aggregateLit = aggregateTypeLiteral(ev);
|
|
7146
|
+
if (ev.description) {
|
|
7147
|
+
chunks.push(`/** ${ev.description} */`);
|
|
7148
|
+
}
|
|
7149
|
+
chunks.push(`export interface ${interfaceName} extends DomainEvent {`);
|
|
7150
|
+
chunks.push(` readonly type: '${ev.type}';`);
|
|
7151
|
+
chunks.push(` readonly aggregateType: '${aggregateLit}';`);
|
|
7152
|
+
const payloadKeys = Object.keys(ev.payload).sort();
|
|
7153
|
+
if (payloadKeys.length === 0) {
|
|
7154
|
+
chunks.push(` readonly payload: Record<string, never>;`);
|
|
7155
|
+
} else {
|
|
7156
|
+
chunks.push(` readonly payload: {`);
|
|
7157
|
+
for (const key of payloadKeys) {
|
|
7158
|
+
const field = ev.payload[key];
|
|
7159
|
+
if (!field) continue;
|
|
7160
|
+
if (field.description) {
|
|
7161
|
+
chunks.push(` /** ${field.description} */`);
|
|
7162
|
+
}
|
|
7163
|
+
chunks.push(` ${toCamelCase(key)}: ${tsTypeForField(field)};`);
|
|
7164
|
+
}
|
|
7165
|
+
chunks.push(` };`);
|
|
7381
7166
|
}
|
|
7167
|
+
chunks.push(`}`);
|
|
7168
|
+
chunks.push("");
|
|
7382
7169
|
}
|
|
7383
|
-
const
|
|
7384
|
-
|
|
7385
|
-
|
|
7386
|
-
|
|
7387
|
-
|
|
7388
|
-
|
|
7389
|
-
|
|
7390
|
-
|
|
7391
|
-
|
|
7392
|
-
|
|
7393
|
-
|
|
7394
|
-
|
|
7395
|
-
|
|
7396
|
-
|
|
7397
|
-
|
|
7398
|
-
|
|
7399
|
-
|
|
7400
|
-
|
|
7401
|
-
|
|
7402
|
-
|
|
7403
|
-
|
|
7404
|
-
|
|
7405
|
-
|
|
7406
|
-
);
|
|
7170
|
+
const unionMembers = sorted.map((ev) => `${toPascalCase2(ev.type)}Event`);
|
|
7171
|
+
chunks.push(`export type AppDomainEvent =`);
|
|
7172
|
+
chunks.push(` | ${unionMembers.join("\n | ")};`);
|
|
7173
|
+
chunks.push("");
|
|
7174
|
+
chunks.push(`export type EventTypeName = AppDomainEvent['type'];`);
|
|
7175
|
+
chunks.push(
|
|
7176
|
+
`export type EventOfType<T extends EventTypeName> = Extract<AppDomainEvent, { type: T }>;`
|
|
7177
|
+
);
|
|
7178
|
+
chunks.push(
|
|
7179
|
+
`export type PayloadOfType<T extends EventTypeName> = EventOfType<T>['payload'];`
|
|
7180
|
+
);
|
|
7181
|
+
chunks.push("");
|
|
7182
|
+
return chunks.join("\n");
|
|
7183
|
+
}
|
|
7184
|
+
function buildSchemasContent(events) {
|
|
7185
|
+
const sorted = [...events].sort((a, b) => a.type.localeCompare(b.type));
|
|
7186
|
+
if (sorted.length === 0) {
|
|
7187
|
+
return HEADER4 + `
|
|
7188
|
+
import { z } from 'zod';
|
|
7189
|
+
import type { EventTypeName } from './types';
|
|
7190
|
+
|
|
7191
|
+
export const eventPayloadSchemas = {} as Record<EventTypeName, z.ZodType>;
|
|
7192
|
+
`;
|
|
7407
7193
|
}
|
|
7408
|
-
|
|
7409
|
-
|
|
7410
|
-
|
|
7411
|
-
|
|
7412
|
-
|
|
7413
|
-
|
|
7194
|
+
const chunks = [];
|
|
7195
|
+
chunks.push(HEADER4);
|
|
7196
|
+
chunks.push("");
|
|
7197
|
+
chunks.push(`import { z } from 'zod';`);
|
|
7198
|
+
chunks.push(`import type { EventTypeName } from './types';`);
|
|
7199
|
+
chunks.push("");
|
|
7200
|
+
for (const ev of sorted) {
|
|
7201
|
+
const schemaConst = `${toCamelCase(ev.type)}PayloadSchema`;
|
|
7202
|
+
const payloadKeys = Object.keys(ev.payload).sort();
|
|
7203
|
+
if (payloadKeys.length === 0) {
|
|
7204
|
+
chunks.push(`export const ${schemaConst} = z.object({}).strict();`);
|
|
7205
|
+
chunks.push("");
|
|
7206
|
+
continue;
|
|
7414
7207
|
}
|
|
7415
|
-
|
|
7416
|
-
|
|
7417
|
-
|
|
7418
|
-
|
|
7419
|
-
`
|
|
7420
|
-
|
|
7421
|
-
|
|
7422
|
-
|
|
7423
|
-
lines.push("");
|
|
7424
|
-
}
|
|
7425
|
-
if (pattern.dispatcher?.assemblySlot) {
|
|
7426
|
-
lines.push(
|
|
7427
|
-
` // ADR-032 Decision 5 \u2014 \`assemblySlot: '${pattern.dispatcher.assemblySlot}'\` is a`
|
|
7428
|
-
);
|
|
7429
|
-
lines.push(
|
|
7430
|
-
` // subclass contract, not emitted here. Consumers subclass and add the`
|
|
7431
|
-
);
|
|
7432
|
-
lines.push(
|
|
7433
|
-
` // \`${pattern.dispatcher.assemblySlot}(...)\` method themselves.`
|
|
7434
|
-
);
|
|
7208
|
+
chunks.push(`export const ${schemaConst} = z.object({`);
|
|
7209
|
+
for (const key of payloadKeys) {
|
|
7210
|
+
const field = ev.payload[key];
|
|
7211
|
+
if (!field) continue;
|
|
7212
|
+
chunks.push(` ${toCamelCase(key)}: ${zodExprForField(field)},`);
|
|
7213
|
+
}
|
|
7214
|
+
chunks.push(`}).strict();`);
|
|
7215
|
+
chunks.push("");
|
|
7435
7216
|
}
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
}
|
|
7440
|
-
function camelFromMethod(method) {
|
|
7441
|
-
if (method.startsWith("select") && method.length > "select".length) {
|
|
7442
|
-
const tail = method.slice("select".length);
|
|
7443
|
-
return tail.charAt(0).toLowerCase() + tail.slice(1);
|
|
7217
|
+
chunks.push(`export const eventPayloadSchemas = {`);
|
|
7218
|
+
for (const ev of sorted) {
|
|
7219
|
+
chunks.push(` '${ev.type}': ${toCamelCase(ev.type)}PayloadSchema,`);
|
|
7444
7220
|
}
|
|
7445
|
-
|
|
7221
|
+
chunks.push(`} as const satisfies Record<EventTypeName, z.ZodType>;`);
|
|
7222
|
+
chunks.push("");
|
|
7223
|
+
return chunks.join("\n");
|
|
7446
7224
|
}
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
|
|
7450
|
-
|
|
7451
|
-
|
|
7452
|
-
|
|
7453
|
-
|
|
7454
|
-
|
|
7455
|
-
|
|
7456
|
-
|
|
7225
|
+
var REGISTRY_INTERFACE = [
|
|
7226
|
+
"export interface EventMetadata {",
|
|
7227
|
+
" type: EventTypeName;",
|
|
7228
|
+
" tier: 'domain' | 'audit';",
|
|
7229
|
+
" direction: 'inbound' | 'change' | 'outbound' | null;",
|
|
7230
|
+
" pool: 'events_inbound' | 'events_change' | 'events_outbound' | null;",
|
|
7231
|
+
" aggregate?: string;",
|
|
7232
|
+
" source?: string;",
|
|
7233
|
+
" destination?: string;",
|
|
7234
|
+
" version: number;",
|
|
7235
|
+
" retry: { attempts: number; backoff: 'linear' | 'exponential' };",
|
|
7236
|
+
"}"
|
|
7237
|
+
].join("\n");
|
|
7238
|
+
var REGISTRY_GETTER = [
|
|
7239
|
+
"export function getEventMetadata<T extends EventTypeName>(type: T): EventMetadata {",
|
|
7240
|
+
" const meta = eventRegistry[type];",
|
|
7241
|
+
" if (!meta) {",
|
|
7242
|
+
" throw new Error(`No registry entry for event type '${String(type)}' \u2014 declare events under events/*.yaml and re-run \\`codegen entity new --all\\`.`);",
|
|
7243
|
+
" }",
|
|
7244
|
+
" return meta;",
|
|
7245
|
+
"}"
|
|
7246
|
+
].join("\n");
|
|
7247
|
+
function buildRegistryContent(events) {
|
|
7248
|
+
const sorted = [...events].sort((a, b) => a.type.localeCompare(b.type));
|
|
7249
|
+
const chunks = [];
|
|
7250
|
+
chunks.push(HEADER4);
|
|
7251
|
+
chunks.push("");
|
|
7252
|
+
chunks.push(`import type { EventTypeName } from './types';`);
|
|
7253
|
+
chunks.push("");
|
|
7254
|
+
chunks.push(REGISTRY_INTERFACE);
|
|
7255
|
+
chunks.push("");
|
|
7256
|
+
if (sorted.length === 0) {
|
|
7257
|
+
chunks.push(
|
|
7258
|
+
`export const eventRegistry = {} as Record<EventTypeName, EventMetadata>;`
|
|
7259
|
+
);
|
|
7260
|
+
chunks.push("");
|
|
7261
|
+
chunks.push(REGISTRY_GETTER);
|
|
7262
|
+
chunks.push("");
|
|
7263
|
+
return chunks.join("\n");
|
|
7457
7264
|
}
|
|
7458
|
-
const
|
|
7459
|
-
const
|
|
7460
|
-
|
|
7461
|
-
|
|
7462
|
-
|
|
7463
|
-
|
|
7464
|
-
|
|
7465
|
-
|
|
7466
|
-
|
|
7467
|
-
|
|
7468
|
-
|
|
7469
|
-
|
|
7470
|
-
|
|
7471
|
-
|
|
7265
|
+
chunks.push(`export const eventRegistry = {`);
|
|
7266
|
+
for (const ev of sorted) {
|
|
7267
|
+
const tier = ev.tier ?? "domain";
|
|
7268
|
+
chunks.push(` '${ev.type}': {`);
|
|
7269
|
+
chunks.push(` type: '${ev.type}',`);
|
|
7270
|
+
chunks.push(` tier: '${tier}',`);
|
|
7271
|
+
if (tier === "audit") {
|
|
7272
|
+
chunks.push(` direction: null,`);
|
|
7273
|
+
chunks.push(` pool: null,`);
|
|
7274
|
+
} else {
|
|
7275
|
+
chunks.push(` direction: '${ev.direction}',`);
|
|
7276
|
+
chunks.push(` pool: '${ev.pool}',`);
|
|
7277
|
+
}
|
|
7278
|
+
if (ev.aggregate !== void 0) {
|
|
7279
|
+
chunks.push(` aggregate: '${ev.aggregate}',`);
|
|
7280
|
+
}
|
|
7281
|
+
if (ev.source !== void 0) {
|
|
7282
|
+
chunks.push(` source: '${ev.source}',`);
|
|
7283
|
+
}
|
|
7284
|
+
if (ev.destination !== void 0) {
|
|
7285
|
+
chunks.push(` destination: '${ev.destination}',`);
|
|
7286
|
+
}
|
|
7287
|
+
chunks.push(` version: ${ev.version},`);
|
|
7288
|
+
chunks.push(
|
|
7289
|
+
` retry: { attempts: ${ev.retry.attempts}, backoff: '${ev.retry.backoff}' },`
|
|
7472
7290
|
);
|
|
7291
|
+
chunks.push(` },`);
|
|
7473
7292
|
}
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
lines.push("@Module({})");
|
|
7477
|
-
lines.push(`export class ${moduleName} {`);
|
|
7478
|
-
lines.push(` static forRoot(opts?: ${optsType}): DynamicModule {`);
|
|
7479
|
-
lines.push(" return {");
|
|
7480
|
-
lines.push(` module: ${moduleName},`);
|
|
7481
|
-
lines.push(
|
|
7482
|
-
` providers: [...build${pascal}RegistryProviders(opts), ${className}],`
|
|
7293
|
+
chunks.push(
|
|
7294
|
+
`} as const satisfies Record<EventTypeName, EventMetadata>;`
|
|
7483
7295
|
);
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
|
|
7487
|
-
|
|
7488
|
-
lines.push("}");
|
|
7489
|
-
lines.push("");
|
|
7490
|
-
return lines.join("\n");
|
|
7296
|
+
chunks.push("");
|
|
7297
|
+
chunks.push(REGISTRY_GETTER);
|
|
7298
|
+
chunks.push("");
|
|
7299
|
+
return chunks.join("\n");
|
|
7491
7300
|
}
|
|
7492
|
-
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
|
|
7496
|
-
|
|
7497
|
-
|
|
7498
|
-
|
|
7499
|
-
|
|
7500
|
-
|
|
7501
|
-
|
|
7301
|
+
var BUS_BODY = `import { Injectable, Inject } from '@nestjs/common';
|
|
7302
|
+
import { randomUUID } from 'crypto';
|
|
7303
|
+
import { EVENT_BUS, EVENTS_MULTI_TENANT } from '../events.tokens';
|
|
7304
|
+
import { MissingTenantIdError } from '../events-errors';
|
|
7305
|
+
import type { IEventBus, DrizzleTransaction } from '../event-bus.protocol';
|
|
7306
|
+
import { eventPayloadSchemas } from './schemas';
|
|
7307
|
+
import { getEventMetadata } from './registry';
|
|
7308
|
+
import type { EventTypeName, EventOfType, PayloadOfType } from './types';
|
|
7309
|
+
|
|
7310
|
+
/**
|
|
7311
|
+
* Typed facade over IEventBus.
|
|
7312
|
+
*
|
|
7313
|
+
* Stamps \`pool\`, \`direction\`, \`tier\`, and \`version\` into \`event.metadata\`
|
|
7314
|
+
* from the generated \`eventRegistry\` before delegating to
|
|
7315
|
+
* \`IEventBus.publish()\`. Downstream backends (DrizzleEventBus) read those
|
|
7316
|
+
* values to populate the explicit \`domain_events\` columns.
|
|
7317
|
+
*
|
|
7318
|
+
* Tier stamping (AUDIT-3): every event carries \`metadata.tier\`, sourced
|
|
7319
|
+
* from the registry. For \`tier: 'audit'\` events, the bus FORCES
|
|
7320
|
+
* \`metadata.pool = null\` and \`metadata.direction = null\` regardless of
|
|
7321
|
+
* any caller-supplied values in \`opts.metadata\` \u2014 audit routing is
|
|
7322
|
+
* bus-stamped, not caller-controlled. Caller overrides are silently
|
|
7323
|
+
* dropped with a debug-level log (callers should not be specifying these
|
|
7324
|
+
* for audit events; see ai-docs/specs/issue-242/plan.md \xA7AUDIT-3).
|
|
7325
|
+
*
|
|
7326
|
+
* Validation gating (EVT-Q5): \`CODEGEN_EVENT_VALIDATE\` env flag, default on.
|
|
7327
|
+
* Uses \`safeParse\` + \`console.warn\` \u2014 never throws, so a bad publish does
|
|
7328
|
+
* not crash a hot path.
|
|
7329
|
+
*
|
|
7330
|
+
* Multi-tenancy (EVT-6): when the EventsModule is configured with
|
|
7331
|
+
* \`multiTenant: true\`, every publish must supply \`opts.metadata.tenantId\`
|
|
7332
|
+
* \u2014 otherwise \`publish()\` throws \`MissingTenantIdError\`. When \`multiTenant\`
|
|
7333
|
+
* is \`false\` (default), no tenantId is required. If a tenantId IS supplied,
|
|
7334
|
+
* it is preserved on \`event.metadata\` and the Drizzle backend writes it to
|
|
7335
|
+
* \`domain_events.tenant_id\` (EVT-4).
|
|
7336
|
+
*/
|
|
7337
|
+
@Injectable()
|
|
7338
|
+
export class TypedEventBus {
|
|
7339
|
+
constructor(
|
|
7340
|
+
@Inject(EVENT_BUS) private readonly bus: IEventBus,
|
|
7341
|
+
@Inject(EVENTS_MULTI_TENANT) private readonly multiTenant: boolean,
|
|
7342
|
+
) {}
|
|
7343
|
+
|
|
7344
|
+
async publish<T extends EventTypeName>(
|
|
7345
|
+
type: T,
|
|
7346
|
+
aggregateId: string,
|
|
7347
|
+
payload: PayloadOfType<T>,
|
|
7348
|
+
opts?: { tx?: DrizzleTransaction; metadata?: Record<string, unknown> },
|
|
7349
|
+
): Promise<void> {
|
|
7350
|
+
const meta = getEventMetadata(type);
|
|
7351
|
+
|
|
7352
|
+
const flag = process.env['CODEGEN_EVENT_VALIDATE'];
|
|
7353
|
+
const shouldValidate =
|
|
7354
|
+
flag === undefined ? true : flag !== 'false' && flag !== '0';
|
|
7355
|
+
if (shouldValidate) {
|
|
7356
|
+
// \`eventPayloadSchemas\` is typed as \`Record<EventTypeName, z.ZodType>\`,
|
|
7357
|
+
// so under \`noUncheckedIndexedAccess\` the indexed lookup widens
|
|
7358
|
+
// to \`z.ZodType | undefined\`. When no events are registered at
|
|
7359
|
+
// codegen time \`EventTypeName\` degrades to \`string\` and the
|
|
7360
|
+
// schemas object is literally \`{}\` \u2014 the guard below is the
|
|
7361
|
+
// honest handling of that empty-registry case (skip validation;
|
|
7362
|
+
// it's a warn-only best-effort check per the class docblock).
|
|
7363
|
+
const schema = eventPayloadSchemas[type];
|
|
7364
|
+
if (schema) {
|
|
7365
|
+
const check = schema.safeParse(payload);
|
|
7366
|
+
if (!check.success) {
|
|
7367
|
+
console.warn(
|
|
7368
|
+
\`[TypedEventBus] payload validation failed for \${String(type)}:\`,
|
|
7369
|
+
check.error.issues,
|
|
7370
|
+
);
|
|
7371
|
+
}
|
|
7372
|
+
}
|
|
7373
|
+
}
|
|
7374
|
+
|
|
7375
|
+
const tenantId = opts?.metadata?.['tenantId'];
|
|
7376
|
+
if (this.multiTenant && (tenantId === undefined || tenantId === null)) {
|
|
7377
|
+
throw new MissingTenantIdError(type as string);
|
|
7378
|
+
}
|
|
7379
|
+
|
|
7380
|
+
const aggregateType =
|
|
7381
|
+
meta.aggregate ?? meta.source ?? meta.destination ?? (type as string);
|
|
7382
|
+
|
|
7383
|
+
// AUDIT-3: build metadata with tier-aware routing stamping. For
|
|
7384
|
+
// \`tier: 'audit'\` events the bus FORCES pool/direction to null,
|
|
7385
|
+
// even if the caller supplied them in opts.metadata. Audit routing
|
|
7386
|
+
// is bus-stamped, not caller-controlled (see plan \xA7AUDIT-3).
|
|
7387
|
+
const baseMetadata: Record<string, unknown> = { ...(opts?.metadata ?? {}) };
|
|
7388
|
+
if (meta.tier === 'audit') {
|
|
7389
|
+
if (
|
|
7390
|
+
baseMetadata['pool'] !== undefined ||
|
|
7391
|
+
baseMetadata['direction'] !== undefined
|
|
7392
|
+
) {
|
|
7393
|
+
console.debug(
|
|
7394
|
+
\`[TypedEventBus] tier:audit event '\${String(type)}' had pool/direction in opts.metadata; overriding to null.\`,
|
|
7395
|
+
);
|
|
7396
|
+
}
|
|
7397
|
+
baseMetadata['pool'] = null;
|
|
7398
|
+
baseMetadata['direction'] = null;
|
|
7399
|
+
baseMetadata['tier'] = 'audit';
|
|
7400
|
+
} else {
|
|
7401
|
+
baseMetadata['pool'] = meta.pool;
|
|
7402
|
+
baseMetadata['direction'] = meta.direction;
|
|
7403
|
+
baseMetadata['tier'] = 'domain';
|
|
7404
|
+
}
|
|
7405
|
+
baseMetadata['version'] = meta.version;
|
|
7406
|
+
|
|
7407
|
+
await this.bus.publish(
|
|
7408
|
+
{
|
|
7409
|
+
id: randomUUID(),
|
|
7410
|
+
type,
|
|
7411
|
+
aggregateId,
|
|
7412
|
+
aggregateType,
|
|
7413
|
+
payload: payload as Record<string, unknown>,
|
|
7414
|
+
occurredAt: new Date(),
|
|
7415
|
+
metadata: baseMetadata,
|
|
7416
|
+
},
|
|
7417
|
+
opts?.tx,
|
|
7418
|
+
);
|
|
7419
|
+
}
|
|
7420
|
+
|
|
7421
|
+
subscribe<T extends EventTypeName>(
|
|
7422
|
+
type: T,
|
|
7423
|
+
handler: (event: EventOfType<T>) => Promise<void>,
|
|
7424
|
+
): () => void {
|
|
7425
|
+
return this.bus.subscribe<EventOfType<T>>(type, handler as never);
|
|
7426
|
+
}
|
|
7427
|
+
}
|
|
7428
|
+
`;
|
|
7429
|
+
function buildBusContent(_events, mode = "vendored") {
|
|
7430
|
+
let body = BUS_BODY;
|
|
7431
|
+
if (mode === "package") {
|
|
7432
|
+
body = body.replace(`from '../events.tokens'`, `from '${PACKAGE_EVENTS_RUNTIME_IMPORT}'`).replace(`from '../events-errors'`, `from '${PACKAGE_EVENTS_RUNTIME_IMPORT}'`).replace(`from '../event-bus.protocol'`, `from '${PACKAGE_EVENTS_RUNTIME_IMPORT}'`);
|
|
7433
|
+
}
|
|
7434
|
+
return HEADER4 + "\n" + body;
|
|
7435
|
+
}
|
|
7436
|
+
function buildIndexContent(_events) {
|
|
7437
|
+
return HEADER4 + `
|
|
7438
|
+
export * from './types';
|
|
7439
|
+
export * from './schemas';
|
|
7440
|
+
export * from './registry';
|
|
7441
|
+
export * from './bus';
|
|
7442
|
+
`;
|
|
7443
|
+
}
|
|
7444
|
+
function buildEventCodegenContents(events, mode = "vendored") {
|
|
7445
|
+
return [
|
|
7446
|
+
{ name: "types.ts", content: buildTypesContent(events, mode) },
|
|
7447
|
+
{ name: "schemas.ts", content: buildSchemasContent(events) },
|
|
7448
|
+
{ name: "registry.ts", content: buildRegistryContent(events) },
|
|
7449
|
+
{ name: "bus.ts", content: buildBusContent(events, mode) },
|
|
7450
|
+
{ name: "index.ts", content: buildIndexContent(events) }
|
|
7451
|
+
];
|
|
7452
|
+
}
|
|
7453
|
+
async function generateEventCodegen(opts) {
|
|
7454
|
+
const { entitiesDir, eventsDir, outputDir, mode = "vendored", dryRun = false } = opts;
|
|
7455
|
+
const { events: merged, issues } = collectMergedEvents({
|
|
7456
|
+
entitiesDir,
|
|
7457
|
+
eventsDir
|
|
7458
|
+
});
|
|
7459
|
+
const files = buildEventCodegenContents(merged, mode).map(
|
|
7460
|
+
({ name, content }) => ({
|
|
7461
|
+
name,
|
|
7462
|
+
outputPath: path9.join(outputDir, name),
|
|
7463
|
+
content
|
|
7464
|
+
})
|
|
7465
|
+
);
|
|
7466
|
+
const hasError = issues.some((i) => i.severity === "error");
|
|
7467
|
+
let written = false;
|
|
7468
|
+
if (!dryRun && !hasError) {
|
|
7469
|
+
fs6.mkdirSync(outputDir, { recursive: true });
|
|
7470
|
+
for (const file of files) {
|
|
7471
|
+
fs6.writeFileSync(file.outputPath, file.content);
|
|
7472
|
+
}
|
|
7473
|
+
written = true;
|
|
7474
|
+
}
|
|
7475
|
+
return {
|
|
7476
|
+
outputDir,
|
|
7477
|
+
eventCount: merged.length,
|
|
7478
|
+
events: merged,
|
|
7479
|
+
issues,
|
|
7480
|
+
written,
|
|
7481
|
+
files
|
|
7482
|
+
};
|
|
7483
|
+
}
|
|
7484
|
+
|
|
7485
|
+
// src/cli/shared/subsystem-barrel-generator.ts
|
|
7486
|
+
function quoteOpts(opts) {
|
|
7487
|
+
const entries = Object.entries(opts).filter(([, v]) => v !== void 0);
|
|
7488
|
+
if (entries.length === 0) return "";
|
|
7489
|
+
return "{ " + entries.map(([k, v]) => `${k}: ${typeof v === "string" ? `'${v}'` : String(v)}`).join(", ") + " }";
|
|
7490
|
+
}
|
|
7491
|
+
function jsonToTs(value) {
|
|
7492
|
+
if (value === null || value === void 0) return "undefined";
|
|
7493
|
+
if (typeof value === "string") return `'${value.replace(/'/g, "\\'")}'`;
|
|
7494
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
7495
|
+
if (Array.isArray(value)) return `[${value.map(jsonToTs).join(", ")}]`;
|
|
7496
|
+
if (typeof value === "object") {
|
|
7497
|
+
const entries = Object.entries(value).filter(
|
|
7498
|
+
([, v]) => v !== void 0
|
|
7499
|
+
);
|
|
7500
|
+
return `{ ${entries.map(([k, v]) => `${k}: ${jsonToTs(v)}`).join(", ")} }`;
|
|
7501
|
+
}
|
|
7502
|
+
return "undefined";
|
|
7503
|
+
}
|
|
7504
|
+
function quoteBullmqDomainOpts(input) {
|
|
7505
|
+
const { backend, multiTenant, bullExt } = input;
|
|
7506
|
+
if (backend !== "bullmq" || !bullExt) {
|
|
7507
|
+
return quoteOpts({ backend, multiTenant });
|
|
7508
|
+
}
|
|
7509
|
+
const parts = [`backend: 'bullmq'`];
|
|
7510
|
+
if (multiTenant) parts.push(`multiTenant: true`);
|
|
7511
|
+
parts.push(`extensions: { bullmq: ${jsonToTs(bullExt)} }`);
|
|
7512
|
+
return `{ ${parts.join(", ")} }`;
|
|
7513
|
+
}
|
|
7514
|
+
function workerPoolsClause(cfg, bridgeInstalled) {
|
|
7515
|
+
const explicit = cfg?.worker_pools;
|
|
7516
|
+
if (Array.isArray(explicit) && explicit.length > 0) {
|
|
7517
|
+
const list = explicit.filter((p) => typeof p === "string").map((p) => `'${p}'`).join(", ");
|
|
7518
|
+
if (list.length > 0) return `pools: [${list}]`;
|
|
7519
|
+
}
|
|
7520
|
+
if (cfg?.all_pools === true) return "allPools: true";
|
|
7521
|
+
if (bridgeInstalled) return "allPools: true";
|
|
7522
|
+
return "";
|
|
7523
|
+
}
|
|
7524
|
+
var COMPOSERS = {
|
|
7525
|
+
events: ({ moduleImport, cfg, mode }) => {
|
|
7526
|
+
const backend = cfg?.backend ?? "drizzle";
|
|
7527
|
+
const multiTenant = Boolean(cfg?.multi_tenant);
|
|
7528
|
+
const imports = [
|
|
7529
|
+
`import { EventsModule } from '${moduleImport("events", "events.module")}';`
|
|
7530
|
+
];
|
|
7531
|
+
if (mode === "package") {
|
|
7532
|
+
imports.push(`import { TypedEventBus } from './events/bus';`);
|
|
7533
|
+
return {
|
|
7534
|
+
imports,
|
|
7535
|
+
calls: [
|
|
7536
|
+
` EventsModule.forRoot({ backend: '${backend}', multiTenant: ${multiTenant}, typedBus: TypedEventBus }),`
|
|
7537
|
+
]
|
|
7538
|
+
};
|
|
7539
|
+
}
|
|
7540
|
+
return {
|
|
7541
|
+
imports,
|
|
7542
|
+
calls: [
|
|
7543
|
+
` EventsModule.forRoot(${quoteOpts({ backend, multiTenant })}),`
|
|
7544
|
+
]
|
|
7545
|
+
};
|
|
7546
|
+
},
|
|
7547
|
+
jobs: ({ moduleImport, cfg, bridgeInstalled }) => {
|
|
7548
|
+
const backend = cfg?.backend ?? "drizzle";
|
|
7549
|
+
const multiTenant = Boolean(cfg?.multi_tenant);
|
|
7550
|
+
const workerMode = (cfg?.worker_mode ?? "standalone").trim();
|
|
7551
|
+
const imports = [
|
|
7552
|
+
`import { JobsDomainModule } from '${moduleImport("jobs", "jobs-domain.module")}';`
|
|
7553
|
+
];
|
|
7554
|
+
const bullExt = backend === "bullmq" ? cfg?.extensions?.bullmq : void 0;
|
|
7555
|
+
const domainOpts = quoteBullmqDomainOpts({ backend, multiTenant, bullExt });
|
|
7556
|
+
const calls = [` JobsDomainModule.forRoot(${domainOpts}),`];
|
|
7557
|
+
if (workerMode === "embedded") {
|
|
7558
|
+
imports.push(
|
|
7559
|
+
`import { JobWorkerModule } from '${moduleImport("jobs", "job-worker.module")}';`
|
|
7560
|
+
);
|
|
7561
|
+
const parts = [`mode: 'embedded'`];
|
|
7562
|
+
if (backend === "bullmq") {
|
|
7563
|
+
parts.push(`backend: 'bullmq'`);
|
|
7564
|
+
if (bullExt) {
|
|
7565
|
+
parts.push(`domainModuleExtensions: { bullmq: ${jsonToTs(bullExt)} }`);
|
|
7566
|
+
}
|
|
7567
|
+
}
|
|
7568
|
+
const poolsClause = workerPoolsClause(cfg, bridgeInstalled);
|
|
7569
|
+
if (poolsClause) parts.push(poolsClause);
|
|
7570
|
+
calls.push(` JobWorkerModule.forRoot({ ${parts.join(", ")} }),`);
|
|
7571
|
+
}
|
|
7572
|
+
return { imports, calls };
|
|
7573
|
+
},
|
|
7574
|
+
bridge: ({ moduleImport, cfg, mode }) => {
|
|
7575
|
+
const backend = cfg?.backend ?? "drizzle";
|
|
7576
|
+
const multiTenant = Boolean(cfg?.multi_tenant);
|
|
7577
|
+
const imports = [
|
|
7578
|
+
`import { BridgeModule } from '${moduleImport("bridge", "bridge.module")}';`
|
|
7579
|
+
];
|
|
7580
|
+
if (mode === "package") {
|
|
7581
|
+
imports.push(`import { bridgeRegistry } from './bridge-registry';`);
|
|
7582
|
+
return {
|
|
7583
|
+
imports,
|
|
7584
|
+
calls: [
|
|
7585
|
+
` BridgeModule.forRoot({ backend: '${backend}', multiTenant: ${multiTenant}, registry: bridgeRegistry }),`
|
|
7586
|
+
]
|
|
7587
|
+
};
|
|
7588
|
+
}
|
|
7589
|
+
return {
|
|
7590
|
+
imports,
|
|
7591
|
+
calls: [
|
|
7592
|
+
` BridgeModule.forRoot(${quoteOpts({ backend, multiTenant })}),`
|
|
7593
|
+
]
|
|
7594
|
+
};
|
|
7595
|
+
},
|
|
7596
|
+
integration: ({ moduleImport, cfg }) => {
|
|
7597
|
+
const backend = cfg?.backend ?? "drizzle";
|
|
7598
|
+
const multiTenant = Boolean(cfg?.multi_tenant);
|
|
7599
|
+
return {
|
|
7600
|
+
imports: [
|
|
7601
|
+
`import { IntegrationModule } from '${moduleImport("integration", "integration.module")}';`
|
|
7602
|
+
],
|
|
7603
|
+
calls: [
|
|
7604
|
+
` IntegrationModule.forRoot(${quoteOpts({ backend, multiTenant })}),`
|
|
7605
|
+
]
|
|
7606
|
+
};
|
|
7607
|
+
}
|
|
7608
|
+
};
|
|
7609
|
+
var PACKAGE2 = "@pattern-stack/codegen";
|
|
7610
|
+
function makeModuleImport(mode, subsystemsRel) {
|
|
7611
|
+
if (mode === "vendored") {
|
|
7612
|
+
return (subsystem, moduleBasename) => `${subsystemsRel}/${subsystem}/${moduleBasename}`;
|
|
7613
|
+
}
|
|
7614
|
+
return (subsystem) => subsystem === "events" ? `${PACKAGE2}/subsystems` : `${PACKAGE2}/runtime/subsystems/${subsystem}/index`;
|
|
7615
|
+
}
|
|
7616
|
+
var COMPOSABLE_ORDER = ["events", "jobs", "bridge", "integration"];
|
|
7617
|
+
var HEADER5 = `// AUTO-GENERATED by @pattern-stack/codegen. DO NOT EDIT.
|
|
7618
|
+
// Subsystem composition barrel \u2014 reflects \`subsystems.install\` in
|
|
7619
|
+
// codegen.config.yaml and the per-subsystem option blocks
|
|
7620
|
+
// (\`events:\`, \`jobs:\`, \`bridge:\`, \`integration:\`).
|
|
7621
|
+
//
|
|
7622
|
+
// Wire into AppModule once:
|
|
7623
|
+
//
|
|
7624
|
+
// import { SUBSYSTEM_MODULES } from './generated/subsystems';
|
|
7625
|
+
// @Module({ imports: [DatabaseModule, ...SUBSYSTEM_MODULES, ...GENERATED_MODULES] })
|
|
7626
|
+
//
|
|
7627
|
+
// Regenerated by every \`codegen entity new\` / \`codegen subsystem install\`.
|
|
7628
|
+
|
|
7629
|
+
`;
|
|
7630
|
+
function buildSubsystemBarrel(installed, config, subsystemsRel, mode = "vendored") {
|
|
7631
|
+
const moduleImport = makeModuleImport(mode, subsystemsRel);
|
|
7632
|
+
const actable = installed.filter((i) => i.status !== "incomplete");
|
|
7633
|
+
const installedNames = new Set(actable.map((i) => i.name));
|
|
7634
|
+
const bridgeInstalled = installedNames.has("bridge");
|
|
7635
|
+
const emitted = [];
|
|
7636
|
+
const skipped = [];
|
|
7637
|
+
const allImports = [`import type { DynamicModule } from '@nestjs/common';`];
|
|
7638
|
+
const allCalls = [];
|
|
7639
|
+
for (const name of COMPOSABLE_ORDER) {
|
|
7640
|
+
if (!installedNames.has(name)) continue;
|
|
7641
|
+
const composer = COMPOSERS[name];
|
|
7642
|
+
if (!composer) {
|
|
7643
|
+
skipped.push(name);
|
|
7644
|
+
continue;
|
|
7645
|
+
}
|
|
7646
|
+
const cfg = config?.[name] ?? void 0;
|
|
7647
|
+
const out = composer({ moduleImport, cfg, mode, bridgeInstalled });
|
|
7648
|
+
allImports.push(...out.imports);
|
|
7649
|
+
allCalls.push(...out.calls);
|
|
7650
|
+
emitted.push(name);
|
|
7651
|
+
}
|
|
7652
|
+
for (const inst of actable) {
|
|
7653
|
+
if (!COMPOSABLE_ORDER.includes(inst.name) && !COMPOSERS[inst.name]) {
|
|
7654
|
+
skipped.push(inst.name);
|
|
7655
|
+
}
|
|
7656
|
+
}
|
|
7657
|
+
const exportLine = allCalls.length === 0 ? `export const SUBSYSTEM_MODULES: DynamicModule[] = [];
|
|
7658
|
+
` : `export const SUBSYSTEM_MODULES: DynamicModule[] = [
|
|
7659
|
+
${allCalls.join("\n")}
|
|
7660
|
+
];
|
|
7661
|
+
`;
|
|
7662
|
+
const body = allImports.join("\n") + "\n\n" + exportLine;
|
|
7663
|
+
return { content: HEADER5 + body, emitted, skipped };
|
|
7664
|
+
}
|
|
7665
|
+
async function regenerateSubsystemBarrel(opts) {
|
|
7666
|
+
const { ctx, dryRun = false } = opts;
|
|
7667
|
+
const generatedDir = opts.generatedDir ?? resolveGeneratedDir(ctx);
|
|
7668
|
+
const mode = resolveRuntimeMode(ctx.config);
|
|
7669
|
+
const installed = mode === "package" ? configuredInstalledSubsystems(
|
|
7670
|
+
ctx.config
|
|
7671
|
+
) : await detectInstalledSubsystems(ctx);
|
|
7672
|
+
const subsystemsAbs = resolveSubsystemsRoot(ctx);
|
|
7673
|
+
const barrelAbs = path10.resolve(generatedDir, "subsystems.ts");
|
|
7674
|
+
let subsystemsRel = path10.relative(path10.dirname(barrelAbs), subsystemsAbs).split(path10.sep).join("/");
|
|
7675
|
+
if (!subsystemsRel.startsWith(".")) subsystemsRel = "./" + subsystemsRel;
|
|
7676
|
+
const { content, emitted, skipped } = buildSubsystemBarrel(
|
|
7677
|
+
installed,
|
|
7678
|
+
ctx.config,
|
|
7679
|
+
subsystemsRel,
|
|
7680
|
+
mode
|
|
7681
|
+
);
|
|
7682
|
+
let written = false;
|
|
7683
|
+
if (!dryRun) {
|
|
7684
|
+
fs7.mkdirSync(path10.dirname(barrelAbs), { recursive: true });
|
|
7685
|
+
fs7.writeFileSync(barrelAbs, content);
|
|
7686
|
+
written = true;
|
|
7687
|
+
if (mode === "package" && emitted.includes("bridge")) {
|
|
7688
|
+
const registryPath = path10.resolve(generatedDir, "bridge-registry.ts");
|
|
7689
|
+
if (!fs7.existsSync(registryPath)) {
|
|
7690
|
+
fs7.writeFileSync(
|
|
7691
|
+
registryPath,
|
|
7692
|
+
buildBridgeRegistryContent([], PACKAGE_BRIDGE_TYPE_IMPORT)
|
|
7693
|
+
);
|
|
7694
|
+
}
|
|
7695
|
+
}
|
|
7696
|
+
if (mode === "package" && emitted.includes("events")) {
|
|
7697
|
+
const eventsDir = path10.resolve(generatedDir, "events");
|
|
7698
|
+
if (!fs7.existsSync(path10.resolve(eventsDir, "bus.ts"))) {
|
|
7699
|
+
fs7.mkdirSync(eventsDir, { recursive: true });
|
|
7700
|
+
for (const { name, content: content2 } of buildEventCodegenContents([], "package")) {
|
|
7701
|
+
fs7.writeFileSync(path10.resolve(eventsDir, name), content2);
|
|
7702
|
+
}
|
|
7703
|
+
}
|
|
7704
|
+
}
|
|
7705
|
+
}
|
|
7706
|
+
return {
|
|
7707
|
+
subsystemBarrel: barrelAbs,
|
|
7708
|
+
emitted,
|
|
7709
|
+
skipped,
|
|
7710
|
+
content,
|
|
7711
|
+
written
|
|
7712
|
+
};
|
|
7713
|
+
}
|
|
7714
|
+
|
|
7715
|
+
// src/cli/shared/subsystem-schema-generator.ts
|
|
7716
|
+
import fs8 from "fs";
|
|
7717
|
+
import path11 from "path";
|
|
7718
|
+
var PACKAGE3 = "@pattern-stack/codegen";
|
|
7719
|
+
var SCHEMA_SYMBOLS = {
|
|
7720
|
+
events: ["domainEvents"],
|
|
7721
|
+
jobs: [
|
|
7722
|
+
"jobs",
|
|
7723
|
+
"jobRuns",
|
|
7724
|
+
"jobSteps",
|
|
7725
|
+
"jobRunStatusEnum",
|
|
7726
|
+
"jobStepKindEnum",
|
|
7727
|
+
"jobStepStatusEnum",
|
|
7728
|
+
"collisionModeEnum",
|
|
7729
|
+
"replayFromEnum",
|
|
7730
|
+
"parentClosePolicyEnum",
|
|
7731
|
+
"waitKindEnum",
|
|
7732
|
+
"triggerSourceEnum"
|
|
7733
|
+
],
|
|
7734
|
+
bridge: ["bridgeDelivery", "bridgeDeliveryStatusEnum"],
|
|
7735
|
+
integration: [
|
|
7736
|
+
"integrationSubscriptions",
|
|
7737
|
+
"integrationRuns",
|
|
7738
|
+
"integrationRunItems",
|
|
7739
|
+
"integrationRunDirectionEnum",
|
|
7740
|
+
"integrationRunActionEnum",
|
|
7741
|
+
"integrationRunStatusEnum",
|
|
7742
|
+
"integrationRunItemOperationEnum",
|
|
7743
|
+
"integrationRunItemStatusEnum"
|
|
7744
|
+
]
|
|
7745
|
+
};
|
|
7746
|
+
var SCHEMA_ORDER = ["events", "jobs", "bridge", "integration"];
|
|
7747
|
+
var HEADER6 = `// AUTO-GENERATED by @pattern-stack/codegen. Do not edit.
|
|
7748
|
+
// Subsystem Drizzle schema barrel \u2014 re-exports the tables + pgEnums of every
|
|
7749
|
+
// installed subsystem so drizzle-kit emits their CREATE TABLE / CREATE TYPE.
|
|
7750
|
+
//
|
|
7751
|
+
// Fold into your drizzle-kit schema entrypoint once, alongside the entity
|
|
7752
|
+
// schema barrel:
|
|
7753
|
+
//
|
|
7754
|
+
// // src/schema.ts
|
|
7755
|
+
// export * from './generated/schema'; // entity tables
|
|
7756
|
+
// export * from './generated/subsystems-schema'; // subsystem tables + enums
|
|
7757
|
+
//
|
|
7758
|
+
// Regenerated by every \`codegen entity new\` / \`codegen subsystem install\`.
|
|
7759
|
+
|
|
7760
|
+
`;
|
|
7761
|
+
function schemaImportFor(mode, subsystem, subsystemsRel) {
|
|
7762
|
+
return mode === "vendored" ? `${subsystemsRel}/${subsystem}` : `${PACKAGE3}/runtime/subsystems/${subsystem}/index`;
|
|
7502
7763
|
}
|
|
7503
|
-
function
|
|
7764
|
+
function buildSubsystemSchemaBarrel(installed, subsystemsRel, mode) {
|
|
7765
|
+
const actable = installed.filter((i) => i.status !== "incomplete");
|
|
7766
|
+
const installedNames = new Set(actable.map((i) => i.name));
|
|
7767
|
+
const emitted = [];
|
|
7504
7768
|
const lines = [];
|
|
7505
|
-
|
|
7506
|
-
|
|
7507
|
-
|
|
7508
|
-
|
|
7509
|
-
|
|
7510
|
-
lines.push("");
|
|
7511
|
-
|
|
7512
|
-
}
|
|
7513
|
-
const slugs = patterns.map((p) => toKebabCase2(p.name)).sort();
|
|
7514
|
-
for (const slug of slugs) {
|
|
7515
|
-
lines.push(`export * from './${slug}/index.js';`);
|
|
7769
|
+
for (const name of SCHEMA_ORDER) {
|
|
7770
|
+
if (!installedNames.has(name)) continue;
|
|
7771
|
+
const symbols = SCHEMA_SYMBOLS[name];
|
|
7772
|
+
if (!symbols || symbols.length === 0) continue;
|
|
7773
|
+
const importSpec = schemaImportFor(mode, name, subsystemsRel);
|
|
7774
|
+
lines.push(`export { ${symbols.join(", ")} } from '${importSpec}';`);
|
|
7775
|
+
emitted.push(name);
|
|
7516
7776
|
}
|
|
7517
|
-
lines.
|
|
7518
|
-
return
|
|
7519
|
-
}
|
|
7520
|
-
function buildPatternFiles(pattern, outputRoot) {
|
|
7521
|
-
assertEmittable(pattern);
|
|
7522
|
-
const slug = toKebabCase2(pattern.name);
|
|
7523
|
-
const outputDir = path11.join(outputRoot, slug);
|
|
7524
|
-
const make = (name, content) => ({
|
|
7525
|
-
name,
|
|
7526
|
-
outputPath: path11.join(outputDir, name),
|
|
7527
|
-
relativePath: path11.join(slug, name),
|
|
7528
|
-
content
|
|
7529
|
-
});
|
|
7530
|
-
const files = [
|
|
7531
|
-
make("tokens.ts", buildTokensTs(pattern)),
|
|
7532
|
-
make("registry.providers.ts", buildProvidersTs(pattern)),
|
|
7533
|
-
make("dispatcher.ts", buildDispatcherTs(pattern)),
|
|
7534
|
-
make("module.ts", buildModuleTs(pattern)),
|
|
7535
|
-
make("index.ts", buildIndexTs(pattern))
|
|
7536
|
-
];
|
|
7537
|
-
return {
|
|
7538
|
-
patternName: pattern.name,
|
|
7539
|
-
slug,
|
|
7540
|
-
outputDir,
|
|
7541
|
-
files
|
|
7542
|
-
};
|
|
7777
|
+
const body = lines.length === 0 ? "export {};\n" : lines.join("\n") + "\n";
|
|
7778
|
+
return { content: HEADER6 + body, emitted };
|
|
7543
7779
|
}
|
|
7544
|
-
function
|
|
7545
|
-
const {
|
|
7546
|
-
const
|
|
7547
|
-
const
|
|
7548
|
-
|
|
7549
|
-
|
|
7550
|
-
|
|
7551
|
-
|
|
7552
|
-
|
|
7553
|
-
|
|
7554
|
-
|
|
7555
|
-
|
|
7556
|
-
|
|
7557
|
-
|
|
7558
|
-
|
|
7559
|
-
|
|
7780
|
+
async function regenerateSubsystemSchemaBarrel(opts) {
|
|
7781
|
+
const { ctx, dryRun = false } = opts;
|
|
7782
|
+
const generatedDir = opts.generatedDir ?? resolveGeneratedDir(ctx);
|
|
7783
|
+
const mode = resolveRuntimeMode(ctx.config);
|
|
7784
|
+
const installed = mode === "package" ? configuredInstalledSubsystems(
|
|
7785
|
+
ctx.config
|
|
7786
|
+
) : await detectInstalledSubsystems(ctx);
|
|
7787
|
+
const subsystemsAbs = resolveSubsystemsRoot(ctx);
|
|
7788
|
+
const barrelAbs = path11.resolve(generatedDir, "subsystems-schema.ts");
|
|
7789
|
+
let subsystemsRel = path11.relative(path11.dirname(barrelAbs), subsystemsAbs).split(path11.sep).join("/");
|
|
7790
|
+
if (!subsystemsRel.startsWith(".")) subsystemsRel = "./" + subsystemsRel;
|
|
7791
|
+
const { content, emitted } = buildSubsystemSchemaBarrel(
|
|
7792
|
+
installed,
|
|
7793
|
+
subsystemsRel,
|
|
7794
|
+
mode
|
|
7795
|
+
);
|
|
7560
7796
|
let written = false;
|
|
7561
|
-
if (!dryRun
|
|
7562
|
-
fs8.mkdirSync(
|
|
7563
|
-
|
|
7564
|
-
fs8.mkdirSync(r.outputDir, { recursive: true });
|
|
7565
|
-
for (const f of r.files) {
|
|
7566
|
-
fs8.writeFileSync(f.outputPath, f.content);
|
|
7567
|
-
}
|
|
7568
|
-
}
|
|
7569
|
-
fs8.writeFileSync(rootBarrel.outputPath, rootBarrel.content);
|
|
7797
|
+
if (!dryRun) {
|
|
7798
|
+
fs8.mkdirSync(path11.dirname(barrelAbs), { recursive: true });
|
|
7799
|
+
fs8.writeFileSync(barrelAbs, content);
|
|
7570
7800
|
written = true;
|
|
7571
7801
|
}
|
|
7572
|
-
return {
|
|
7573
|
-
outputRoot,
|
|
7574
|
-
patterns: perPattern,
|
|
7575
|
-
files: allFiles,
|
|
7576
|
-
written
|
|
7577
|
-
};
|
|
7802
|
+
return { schemaBarrel: barrelAbs, emitted, content, written };
|
|
7578
7803
|
}
|
|
7579
7804
|
|
|
7580
|
-
// src/cli/shared/
|
|
7805
|
+
// src/cli/shared/orchestration-generator.ts
|
|
7581
7806
|
import fs9 from "fs";
|
|
7582
7807
|
import path12 from "path";
|
|
7583
|
-
|
|
7584
|
-
|
|
7585
|
-
|
|
7586
|
-
|
|
7587
|
-
|
|
7588
|
-
issues.push({
|
|
7589
|
-
severity: "error",
|
|
7590
|
-
type: "parse_error",
|
|
7591
|
-
message: error.error,
|
|
7592
|
-
path: error.filePath
|
|
7593
|
-
});
|
|
7594
|
-
if (error.details) {
|
|
7595
|
-
for (const detail of error.details) {
|
|
7596
|
-
issues.push({
|
|
7597
|
-
severity: "error",
|
|
7598
|
-
type: "schema_error",
|
|
7599
|
-
message: detail,
|
|
7600
|
-
path: error.filePath
|
|
7601
|
-
});
|
|
7602
|
-
}
|
|
7808
|
+
var OrchestrationEmissionError = class extends Error {
|
|
7809
|
+
constructor(issueType, patternName, message) {
|
|
7810
|
+
super(`[${issueType}] ${patternName}: ${message}`);
|
|
7811
|
+
this.issueType = issueType;
|
|
7812
|
+
this.patternName = patternName;
|
|
7603
7813
|
}
|
|
7604
|
-
|
|
7814
|
+
issueType;
|
|
7815
|
+
patternName;
|
|
7816
|
+
name = "OrchestrationEmissionError";
|
|
7817
|
+
};
|
|
7818
|
+
var HEADER7 = `// AUTO-GENERATED by @pattern-stack/codegen. Do not edit.
|
|
7819
|
+
// See ADR-032 \u2014 Orchestration Patterns.
|
|
7820
|
+
`;
|
|
7821
|
+
function splitWords(str) {
|
|
7822
|
+
return str.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").split(/\s+/).filter((w) => w.length > 0).map((w) => w.toLowerCase());
|
|
7605
7823
|
}
|
|
7606
|
-
function
|
|
7607
|
-
|
|
7608
|
-
if (base.endsWith(".yaml")) return base.slice(0, -".yaml".length);
|
|
7609
|
-
if (base.endsWith(".yml")) return base.slice(0, -".yml".length);
|
|
7610
|
-
return base;
|
|
7824
|
+
function toKebabCase2(str) {
|
|
7825
|
+
return splitWords(str).join("-");
|
|
7611
7826
|
}
|
|
7612
|
-
function
|
|
7613
|
-
|
|
7614
|
-
|
|
7615
|
-
|
|
7616
|
-
|
|
7617
|
-
|
|
7618
|
-
|
|
7619
|
-
|
|
7620
|
-
|
|
7621
|
-
|
|
7622
|
-
|
|
7623
|
-
|
|
7624
|
-
|
|
7625
|
-
|
|
7626
|
-
|
|
7627
|
-
|
|
7628
|
-
|
|
7629
|
-
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
|
|
7633
|
-
|
|
7634
|
-
|
|
7635
|
-
|
|
7636
|
-
|
|
7637
|
-
|
|
7638
|
-
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
|
|
7642
|
-
|
|
7643
|
-
continue;
|
|
7644
|
-
}
|
|
7645
|
-
const { definition } = result;
|
|
7646
|
-
const baseName = stripYamlExt(filePath);
|
|
7647
|
-
if (baseName !== definition.type) {
|
|
7648
|
-
issues.push({
|
|
7649
|
-
severity: "error",
|
|
7650
|
-
type: "event_filename_mismatch",
|
|
7651
|
-
message: `Event file '${baseName}' must contain 'type: ${baseName}' (found 'type: ${definition.type}')`,
|
|
7652
|
-
path: filePath,
|
|
7653
|
-
suggestion: `Rename the file to '${definition.type}.yaml' or fix the 'type' field to '${baseName}'`
|
|
7654
|
-
});
|
|
7655
|
-
continue;
|
|
7656
|
-
}
|
|
7657
|
-
if (definition.direction === "change" && definition.aggregate !== void 0 && !entityNameSet.has(definition.aggregate)) {
|
|
7658
|
-
issues.push({
|
|
7659
|
-
severity: "error",
|
|
7660
|
-
type: "unknown_aggregate",
|
|
7661
|
-
message: `change event '${definition.type}' references unknown aggregate entity '${definition.aggregate}'`,
|
|
7662
|
-
path: filePath,
|
|
7663
|
-
suggestion: `Define entities/${definition.aggregate}.yaml or fix the aggregate value`
|
|
7664
|
-
});
|
|
7665
|
-
continue;
|
|
7666
|
-
}
|
|
7667
|
-
if (seenTypes.has(definition.type)) {
|
|
7668
|
-
issues.push({
|
|
7669
|
-
severity: "error",
|
|
7670
|
-
type: "duplicate_event_type",
|
|
7671
|
-
message: `Duplicate event type '${definition.type}' (already declared in ${seenTypes.get(definition.type)})`,
|
|
7672
|
-
path: filePath
|
|
7673
|
-
});
|
|
7674
|
-
continue;
|
|
7827
|
+
function toPascalCase3(str) {
|
|
7828
|
+
return splitWords(str).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
7829
|
+
}
|
|
7830
|
+
function toCamelCase2(str) {
|
|
7831
|
+
const words = splitWords(str);
|
|
7832
|
+
if (words.length === 0) return "";
|
|
7833
|
+
const [first, ...rest] = words;
|
|
7834
|
+
return (first ?? "") + rest.map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
7835
|
+
}
|
|
7836
|
+
function toScreamingSnake(str) {
|
|
7837
|
+
return splitWords(str).join("_").toUpperCase();
|
|
7838
|
+
}
|
|
7839
|
+
var FORBIDDEN_KEY_TYPES = /* @__PURE__ */ new Set([
|
|
7840
|
+
"string",
|
|
7841
|
+
"number",
|
|
7842
|
+
"symbol",
|
|
7843
|
+
"any",
|
|
7844
|
+
"unknown"
|
|
7845
|
+
]);
|
|
7846
|
+
function assertEmittable(pattern) {
|
|
7847
|
+
const allRegistries2 = [
|
|
7848
|
+
pattern.registry,
|
|
7849
|
+
...pattern.coKeyedRegistries ?? []
|
|
7850
|
+
];
|
|
7851
|
+
for (const sibling of pattern.coKeyedRegistries ?? []) {
|
|
7852
|
+
if (typeof sibling.name !== "string" || sibling.name.length === 0) {
|
|
7853
|
+
throw new OrchestrationEmissionError(
|
|
7854
|
+
"pattern_cokeyed_missing_name",
|
|
7855
|
+
pattern.name,
|
|
7856
|
+
`Co-keyed registry (valueType '${sibling.valueType}') is missing the required 'name' field. Each co-keyed sibling must declare an explicit name; the emitter uppercases it for the token suffix and PascalCases it for the dispatcher method (e.g. name: 'auth' \u21D2 '\${PATTERN_CONST}_AUTH_REGISTRY' + 'selectAuth(...)'). ADR-032 O-1.`
|
|
7857
|
+
);
|
|
7675
7858
|
}
|
|
7676
|
-
seenTypes.set(definition.type, filePath);
|
|
7677
|
-
events.push(definition);
|
|
7678
7859
|
}
|
|
7679
|
-
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
|
|
7685
|
-
|
|
7686
|
-
|
|
7687
|
-
|
|
7688
|
-
|
|
7689
|
-
|
|
7860
|
+
for (const reg of allRegistries2) {
|
|
7861
|
+
if (FORBIDDEN_KEY_TYPES.has(reg.keyType)) {
|
|
7862
|
+
throw new OrchestrationEmissionError(
|
|
7863
|
+
"pattern_keytype_unresolved",
|
|
7864
|
+
pattern.name,
|
|
7865
|
+
`registry keyType '${reg.keyType}' is a primitive \u2014 Partial<Record<K, V>> requires a literal-union K, and per-overload narrowing requires a closed enum/union. Use a string-literal union or a TypeScript enum.`
|
|
7866
|
+
);
|
|
7867
|
+
}
|
|
7868
|
+
if (typeof reg.keyTypeImport !== "string" || reg.keyTypeImport.length === 0) {
|
|
7869
|
+
throw new OrchestrationEmissionError(
|
|
7870
|
+
"pattern_missing_import_path",
|
|
7871
|
+
pattern.name,
|
|
7872
|
+
`registry (keyType '${reg.keyType}') is missing 'keyTypeImport'. Phase 3-2 emission requires every keyType to carry a module specifier.`
|
|
7873
|
+
);
|
|
7874
|
+
}
|
|
7875
|
+
if (typeof reg.valueTypeImport !== "string" || reg.valueTypeImport.length === 0) {
|
|
7876
|
+
throw new OrchestrationEmissionError(
|
|
7877
|
+
"pattern_missing_import_path",
|
|
7878
|
+
pattern.name,
|
|
7879
|
+
`registry (valueType '${reg.valueType}') is missing 'valueTypeImport'.`
|
|
7880
|
+
);
|
|
7881
|
+
}
|
|
7882
|
+
for (const e of reg.entries) {
|
|
7883
|
+
if (typeof e.providerImport !== "string" || e.providerImport.length === 0) {
|
|
7884
|
+
throw new OrchestrationEmissionError(
|
|
7885
|
+
"pattern_missing_import_path",
|
|
7886
|
+
pattern.name,
|
|
7887
|
+
`entry '${e.key}' provider '${e.provider}' is missing 'providerImport'.`
|
|
7690
7888
|
);
|
|
7691
7889
|
}
|
|
7692
|
-
payload[key] = { type: typeString, nullable: false };
|
|
7693
7890
|
}
|
|
7694
|
-
|
|
7695
|
-
type: ev.name,
|
|
7696
|
-
tier: "domain",
|
|
7697
|
-
direction: "change",
|
|
7698
|
-
aggregate: entityName,
|
|
7699
|
-
payload,
|
|
7700
|
-
retry: { attempts: 3, backoff: "exponential" },
|
|
7701
|
-
version: 1,
|
|
7702
|
-
pool: DIRECTION_TO_POOL.change
|
|
7703
|
-
};
|
|
7704
|
-
return def;
|
|
7705
|
-
});
|
|
7706
|
-
}
|
|
7707
|
-
function isEventFieldType(s) {
|
|
7708
|
-
return EVENT_FIELD_TYPES.includes(s);
|
|
7709
|
-
}
|
|
7710
|
-
|
|
7711
|
-
// src/cli/shared/event-codegen-generator.ts
|
|
7712
|
-
var HEADER7 = `// AUTO-GENERATED by @pattern-stack/codegen. Do not edit.
|
|
7713
|
-
// Run \`codegen entity new --all\` to refresh.
|
|
7714
|
-
`;
|
|
7715
|
-
function toCamelCase2(input) {
|
|
7716
|
-
const parts = input.split("_").filter(Boolean);
|
|
7717
|
-
if (parts.length === 0) return input;
|
|
7718
|
-
const [first, ...rest] = parts;
|
|
7719
|
-
return (first ?? "") + rest.map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
7891
|
+
}
|
|
7720
7892
|
}
|
|
7721
|
-
function
|
|
7722
|
-
|
|
7893
|
+
function patternNames(pattern) {
|
|
7894
|
+
const slug = toKebabCase2(pattern.name);
|
|
7895
|
+
const pascal = toPascalCase3(pattern.name);
|
|
7896
|
+
const screaming = toScreamingSnake(pattern.name);
|
|
7897
|
+
return { slug, pascal, screaming };
|
|
7723
7898
|
}
|
|
7724
|
-
|
|
7725
|
-
|
|
7726
|
-
|
|
7727
|
-
|
|
7728
|
-
|
|
7729
|
-
|
|
7730
|
-
|
|
7731
|
-
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
string: "z.string()",
|
|
7735
|
-
number: "z.number()",
|
|
7736
|
-
boolean: "z.boolean()",
|
|
7737
|
-
date: "z.coerce.date()",
|
|
7738
|
-
json: "z.record(z.unknown())"
|
|
7739
|
-
};
|
|
7740
|
-
function tsTypeForField(field) {
|
|
7741
|
-
let base;
|
|
7742
|
-
if (field.type === "array") {
|
|
7743
|
-
const itemType = field.items;
|
|
7744
|
-
base = `${TS_TYPE_BY_FIELD[itemType]}[]`;
|
|
7745
|
-
} else {
|
|
7746
|
-
base = TS_TYPE_BY_FIELD[field.type];
|
|
7899
|
+
function registryNames(pattern, reg, isPrimary) {
|
|
7900
|
+
const { pascal, screaming } = patternNames(pattern);
|
|
7901
|
+
if (isPrimary) {
|
|
7902
|
+
return {
|
|
7903
|
+
tokenConst: `${screaming}_REGISTRY`,
|
|
7904
|
+
mapType: `${pascal}RegistryMap`,
|
|
7905
|
+
method: "select",
|
|
7906
|
+
overrideField: "overrides",
|
|
7907
|
+
builderFn: `build${pascal}RegistryProviders`
|
|
7908
|
+
};
|
|
7747
7909
|
}
|
|
7748
|
-
|
|
7910
|
+
const namePascal = toPascalCase3(reg.name);
|
|
7911
|
+
const nameScreaming = toScreamingSnake(reg.name);
|
|
7912
|
+
const nameCamel = toCamelCase2(reg.name);
|
|
7913
|
+
return {
|
|
7914
|
+
tokenConst: `${screaming}_${nameScreaming}_REGISTRY`,
|
|
7915
|
+
mapType: `${pascal}${namePascal}RegistryMap`,
|
|
7916
|
+
method: `select${namePascal}`,
|
|
7917
|
+
overrideField: `${nameCamel}Overrides`,
|
|
7918
|
+
builderFn: `build${pascal}RegistryProviders`
|
|
7919
|
+
};
|
|
7749
7920
|
}
|
|
7750
|
-
function
|
|
7751
|
-
|
|
7752
|
-
if (field.type === "array") {
|
|
7753
|
-
const itemType = field.items;
|
|
7754
|
-
base = `z.array(${ZOD_EXPR_BY_FIELD[itemType]})`;
|
|
7755
|
-
} else {
|
|
7756
|
-
base = ZOD_EXPR_BY_FIELD[field.type];
|
|
7757
|
-
}
|
|
7758
|
-
return field.nullable ? `${base}.nullable()` : base;
|
|
7921
|
+
function dispatcherClassName(pattern) {
|
|
7922
|
+
return pattern.dispatcher?.className ?? `${toPascalCase3(pattern.name)}Dispatcher`;
|
|
7759
7923
|
}
|
|
7760
|
-
function
|
|
7761
|
-
return
|
|
7924
|
+
function moduleClassName(pattern) {
|
|
7925
|
+
return `${toPascalCase3(pattern.name)}OrchestrationModule`;
|
|
7762
7926
|
}
|
|
7763
|
-
function
|
|
7764
|
-
|
|
7765
|
-
|
|
7766
|
-
|
|
7767
|
-
|
|
7927
|
+
function forRootOptionsTypeName(pattern) {
|
|
7928
|
+
return `${toPascalCase3(pattern.name)}ForRootOptions`;
|
|
7929
|
+
}
|
|
7930
|
+
function emitTypeImports(imports) {
|
|
7931
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
7932
|
+
for (const i of imports) {
|
|
7933
|
+
const set = grouped.get(i.specifier) ?? /* @__PURE__ */ new Set();
|
|
7934
|
+
set.add(i.name);
|
|
7935
|
+
grouped.set(i.specifier, set);
|
|
7768
7936
|
}
|
|
7769
|
-
const
|
|
7770
|
-
for (const
|
|
7771
|
-
const
|
|
7772
|
-
|
|
7773
|
-
try {
|
|
7774
|
-
events.push(...desugarEntityEvents(result.definition));
|
|
7775
|
-
} catch (err) {
|
|
7776
|
-
issues.push({
|
|
7777
|
-
severity: "error",
|
|
7778
|
-
type: "entity_event_desugar_failed",
|
|
7779
|
-
message: err instanceof Error ? err.message : String(err),
|
|
7780
|
-
path: filePath
|
|
7781
|
-
});
|
|
7782
|
-
}
|
|
7937
|
+
const out = [];
|
|
7938
|
+
for (const specifier of [...grouped.keys()].sort()) {
|
|
7939
|
+
const names2 = [...grouped.get(specifier)].sort();
|
|
7940
|
+
out.push(`import type { ${names2.join(", ")} } from '${specifier}';`);
|
|
7783
7941
|
}
|
|
7784
|
-
return
|
|
7942
|
+
return out;
|
|
7785
7943
|
}
|
|
7786
|
-
function
|
|
7787
|
-
const
|
|
7788
|
-
const
|
|
7789
|
-
|
|
7790
|
-
|
|
7944
|
+
function emitValueImports(imports) {
|
|
7945
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
7946
|
+
for (const i of imports) {
|
|
7947
|
+
const set = grouped.get(i.specifier) ?? /* @__PURE__ */ new Set();
|
|
7948
|
+
set.add(i.name);
|
|
7949
|
+
grouped.set(i.specifier, set);
|
|
7791
7950
|
}
|
|
7792
|
-
|
|
7793
|
-
|
|
7794
|
-
|
|
7795
|
-
|
|
7796
|
-
type: "event_merge_override",
|
|
7797
|
-
message: `event '${ev.type}' is declared both in an entity \`events:\` block and a top-level \`events/${ev.type}.yaml\` \u2014 top-level definition wins`
|
|
7798
|
-
});
|
|
7799
|
-
}
|
|
7800
|
-
byType.set(ev.type, ev);
|
|
7951
|
+
const out = [];
|
|
7952
|
+
for (const specifier of [...grouped.keys()].sort()) {
|
|
7953
|
+
const names2 = [...grouped.get(specifier)].sort();
|
|
7954
|
+
out.push(`import { ${names2.join(", ")} } from '${specifier}';`);
|
|
7801
7955
|
}
|
|
7802
|
-
|
|
7803
|
-
(a, b) => a.type.localeCompare(b.type)
|
|
7804
|
-
);
|
|
7805
|
-
return { events, issues };
|
|
7956
|
+
return out;
|
|
7806
7957
|
}
|
|
7807
|
-
function
|
|
7808
|
-
const
|
|
7809
|
-
|
|
7810
|
-
|
|
7811
|
-
|
|
7812
|
-
|
|
7813
|
-
|
|
7814
|
-
|
|
7815
|
-
|
|
7958
|
+
function allRegistries(pattern) {
|
|
7959
|
+
const list = [];
|
|
7960
|
+
list.push({
|
|
7961
|
+
reg: pattern.registry,
|
|
7962
|
+
names: registryNames(pattern, pattern.registry, true),
|
|
7963
|
+
isPrimary: true
|
|
7964
|
+
});
|
|
7965
|
+
for (const sibling of pattern.coKeyedRegistries ?? []) {
|
|
7966
|
+
list.push({
|
|
7967
|
+
reg: sibling,
|
|
7968
|
+
names: registryNames(pattern, sibling, false),
|
|
7969
|
+
isPrimary: false
|
|
7970
|
+
});
|
|
7816
7971
|
}
|
|
7817
|
-
|
|
7818
|
-
const { events: entitySugar, issues: sugarIssues } = collectEntityEvents(entitiesDir);
|
|
7819
|
-
const { events: merged, issues: mergeIssues } = mergeEvents(
|
|
7820
|
-
topLevelResult.events,
|
|
7821
|
-
entitySugar
|
|
7822
|
-
);
|
|
7823
|
-
const issues = [
|
|
7824
|
-
...topLevelResult.issues,
|
|
7825
|
-
...sugarIssues,
|
|
7826
|
-
...mergeIssues
|
|
7827
|
-
];
|
|
7828
|
-
return { events: merged, issues };
|
|
7972
|
+
return list;
|
|
7829
7973
|
}
|
|
7830
|
-
function
|
|
7831
|
-
const
|
|
7832
|
-
|
|
7833
|
-
|
|
7834
|
-
|
|
7835
|
-
|
|
7836
|
-
export type AppDomainEvent = never;
|
|
7837
|
-
|
|
7838
|
-
export type EventTypeName = string;
|
|
7839
|
-
export type EventOfType<T extends EventTypeName> = DomainEvent;
|
|
7840
|
-
export type PayloadOfType<T extends EventTypeName> = DomainEvent['payload'];
|
|
7841
|
-
`;
|
|
7974
|
+
function buildTokensTs(pattern) {
|
|
7975
|
+
const registries = allRegistries(pattern);
|
|
7976
|
+
const typeImports = [];
|
|
7977
|
+
for (const { reg } of registries) {
|
|
7978
|
+
typeImports.push({ specifier: reg.keyTypeImport, name: reg.keyType });
|
|
7979
|
+
typeImports.push({ specifier: reg.valueTypeImport, name: reg.valueType });
|
|
7842
7980
|
}
|
|
7843
|
-
const
|
|
7844
|
-
|
|
7845
|
-
|
|
7846
|
-
|
|
7847
|
-
|
|
7848
|
-
for (const
|
|
7849
|
-
const
|
|
7850
|
-
|
|
7851
|
-
|
|
7852
|
-
|
|
7853
|
-
|
|
7854
|
-
|
|
7855
|
-
|
|
7856
|
-
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
|
|
7860
|
-
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
7866
|
-
chunks.push(` /** ${field.description} */`);
|
|
7867
|
-
}
|
|
7868
|
-
chunks.push(` ${toCamelCase2(key)}: ${tsTypeForField(field)};`);
|
|
7869
|
-
}
|
|
7870
|
-
chunks.push(` };`);
|
|
7981
|
+
const lines = [];
|
|
7982
|
+
lines.push(HEADER7.trimEnd());
|
|
7983
|
+
lines.push("");
|
|
7984
|
+
for (const i of emitTypeImports(typeImports)) lines.push(i);
|
|
7985
|
+
lines.push("");
|
|
7986
|
+
for (const { names: names2 } of registries) {
|
|
7987
|
+
lines.push(`export const ${names2.tokenConst} = Symbol('${names2.tokenConst}');`);
|
|
7988
|
+
}
|
|
7989
|
+
lines.push("");
|
|
7990
|
+
for (const { reg, names: names2 } of registries) {
|
|
7991
|
+
lines.push(
|
|
7992
|
+
`export type ${names2.mapType} = Map<${reg.keyType}, ${reg.valueType}>;`
|
|
7993
|
+
);
|
|
7994
|
+
}
|
|
7995
|
+
lines.push("");
|
|
7996
|
+
return lines.join("\n");
|
|
7997
|
+
}
|
|
7998
|
+
function buildProvidersTs(pattern) {
|
|
7999
|
+
const registries = allRegistries(pattern);
|
|
8000
|
+
const providerImports = [];
|
|
8001
|
+
for (const { reg } of registries) {
|
|
8002
|
+
for (const e of reg.entries) {
|
|
8003
|
+
providerImports.push({ specifier: e.providerImport, name: e.provider });
|
|
7871
8004
|
}
|
|
7872
|
-
chunks.push(`}`);
|
|
7873
|
-
chunks.push("");
|
|
7874
8005
|
}
|
|
7875
|
-
const
|
|
7876
|
-
|
|
7877
|
-
|
|
7878
|
-
|
|
7879
|
-
|
|
7880
|
-
|
|
7881
|
-
|
|
7882
|
-
);
|
|
7883
|
-
|
|
7884
|
-
|
|
8006
|
+
const tokenValueImports = registries.map(({ names: names2 }) => names2.tokenConst);
|
|
8007
|
+
const mapTypeImports = registries.map(({ names: names2 }) => names2.mapType);
|
|
8008
|
+
const typeImports = [];
|
|
8009
|
+
for (const { reg } of registries) {
|
|
8010
|
+
typeImports.push({ specifier: reg.keyTypeImport, name: reg.keyType });
|
|
8011
|
+
typeImports.push({ specifier: reg.valueTypeImport, name: reg.valueType });
|
|
8012
|
+
}
|
|
8013
|
+
const optsType = forRootOptionsTypeName(pattern);
|
|
8014
|
+
const lines = [];
|
|
8015
|
+
lines.push(HEADER7.trimEnd());
|
|
8016
|
+
lines.push("");
|
|
8017
|
+
lines.push(`import type { Provider } from '@nestjs/common';`);
|
|
8018
|
+
for (const i of emitTypeImports(typeImports)) lines.push(i);
|
|
8019
|
+
for (const i of emitValueImports(providerImports)) lines.push(i);
|
|
8020
|
+
const tokenLine = [
|
|
8021
|
+
...tokenValueImports.sort(),
|
|
8022
|
+
...mapTypeImports.sort().map((m) => `type ${m}`)
|
|
8023
|
+
].join(", ");
|
|
8024
|
+
lines.push(`import { ${tokenLine} } from './tokens.js';`);
|
|
8025
|
+
lines.push(`import type { ${optsType} } from './module.js';`);
|
|
8026
|
+
lines.push("");
|
|
8027
|
+
const { pascal } = patternNames(pattern);
|
|
8028
|
+
lines.push(
|
|
8029
|
+
`export function build${pascal}RegistryProviders(opts?: ${optsType}): Provider[] {`
|
|
7885
8030
|
);
|
|
7886
|
-
|
|
7887
|
-
|
|
8031
|
+
lines.push(" return [");
|
|
8032
|
+
for (const { reg, names: names2 } of registries) {
|
|
8033
|
+
const factoryArgs = reg.entries.map((e, i) => `${camelArg(e.provider, i)}: ${e.provider}`).join(", ");
|
|
8034
|
+
lines.push(" {");
|
|
8035
|
+
lines.push(` provide: ${names2.tokenConst},`);
|
|
8036
|
+
lines.push(` useFactory: (${factoryArgs}) => {`);
|
|
8037
|
+
lines.push(` const base: ${names2.mapType} = new Map<${reg.keyType}, ${reg.valueType}>([`);
|
|
8038
|
+
for (const [i, e] of reg.entries.entries()) {
|
|
8039
|
+
lines.push(
|
|
8040
|
+
` ['${e.key}' as ${reg.keyType}, ${camelArg(e.provider, i)}],`
|
|
8041
|
+
);
|
|
8042
|
+
}
|
|
8043
|
+
lines.push(" ]);");
|
|
8044
|
+
lines.push(` if (opts?.${names2.overrideField}) {`);
|
|
8045
|
+
lines.push(` for (const [k, v] of Object.entries(opts.${names2.overrideField})) {`);
|
|
8046
|
+
lines.push(` if (v !== undefined) base.set(k as ${reg.keyType}, v as ${reg.valueType});`);
|
|
8047
|
+
lines.push(" }");
|
|
8048
|
+
lines.push(" }");
|
|
8049
|
+
lines.push(` return base as ${names2.mapType};`);
|
|
8050
|
+
lines.push(" },");
|
|
8051
|
+
const injectList = reg.entries.map((e) => e.provider).join(", ");
|
|
8052
|
+
lines.push(` inject: [${injectList}],`);
|
|
8053
|
+
lines.push(" },");
|
|
8054
|
+
}
|
|
8055
|
+
lines.push(" ];");
|
|
8056
|
+
lines.push("}");
|
|
8057
|
+
lines.push("");
|
|
8058
|
+
return lines.join("\n");
|
|
7888
8059
|
}
|
|
7889
|
-
function
|
|
7890
|
-
const
|
|
7891
|
-
|
|
7892
|
-
|
|
7893
|
-
|
|
7894
|
-
|
|
7895
|
-
|
|
7896
|
-
|
|
7897
|
-
|
|
8060
|
+
function camelArg(provider, index2) {
|
|
8061
|
+
const camel = provider.charAt(0).toLowerCase() + provider.slice(1);
|
|
8062
|
+
return `${camel}_${index2}`;
|
|
8063
|
+
}
|
|
8064
|
+
function buildDispatcherTs(pattern) {
|
|
8065
|
+
const registries = allRegistries(pattern);
|
|
8066
|
+
const className = dispatcherClassName(pattern);
|
|
8067
|
+
const errorClass = `${className}Error`;
|
|
8068
|
+
const typeImports = [];
|
|
8069
|
+
for (const { reg } of registries) {
|
|
8070
|
+
typeImports.push({ specifier: reg.keyTypeImport, name: reg.keyType });
|
|
8071
|
+
typeImports.push({ specifier: reg.valueTypeImport, name: reg.valueType });
|
|
7898
8072
|
}
|
|
7899
|
-
const
|
|
7900
|
-
|
|
7901
|
-
|
|
7902
|
-
|
|
7903
|
-
chunks.push(`import type { EventTypeName } from './types';`);
|
|
7904
|
-
chunks.push("");
|
|
7905
|
-
for (const ev of sorted) {
|
|
7906
|
-
const schemaConst = `${toCamelCase2(ev.type)}PayloadSchema`;
|
|
7907
|
-
const payloadKeys = Object.keys(ev.payload).sort();
|
|
7908
|
-
if (payloadKeys.length === 0) {
|
|
7909
|
-
chunks.push(`export const ${schemaConst} = z.object({}).strict();`);
|
|
7910
|
-
chunks.push("");
|
|
7911
|
-
continue;
|
|
8073
|
+
const providerImports = [];
|
|
8074
|
+
for (const { reg } of registries) {
|
|
8075
|
+
for (const e of reg.entries) {
|
|
8076
|
+
providerImports.push({ specifier: e.providerImport, name: e.provider });
|
|
7912
8077
|
}
|
|
7913
|
-
|
|
7914
|
-
|
|
7915
|
-
|
|
7916
|
-
|
|
7917
|
-
|
|
8078
|
+
}
|
|
8079
|
+
const tokenValues = registries.map(({ names: names2 }) => names2.tokenConst);
|
|
8080
|
+
const mapTypes = registries.map(({ names: names2 }) => names2.mapType);
|
|
8081
|
+
const lines = [];
|
|
8082
|
+
lines.push(HEADER7.trimEnd());
|
|
8083
|
+
lines.push("");
|
|
8084
|
+
lines.push(`import { Inject, Injectable } from '@nestjs/common';`);
|
|
8085
|
+
for (const i of emitTypeImports(typeImports)) lines.push(i);
|
|
8086
|
+
for (const i of emitValueImports(providerImports)) lines.push(i);
|
|
8087
|
+
const tokenLine = [
|
|
8088
|
+
...tokenValues.sort(),
|
|
8089
|
+
...mapTypes.sort().map((m) => `type ${m}`)
|
|
8090
|
+
].join(", ");
|
|
8091
|
+
lines.push(`import { ${tokenLine} } from './tokens.js';`);
|
|
8092
|
+
lines.push("");
|
|
8093
|
+
lines.push(`export class ${errorClass} extends Error {}`);
|
|
8094
|
+
lines.push("");
|
|
8095
|
+
lines.push("@Injectable()");
|
|
8096
|
+
lines.push(`export class ${className} {`);
|
|
8097
|
+
lines.push(" constructor(");
|
|
8098
|
+
for (const { names: names2 } of registries) {
|
|
8099
|
+
const fieldName = names2.method === "select" ? "registry" : `${camelFromMethod(names2.method)}Registry`;
|
|
8100
|
+
lines.push(
|
|
8101
|
+
` @Inject(${names2.tokenConst}) protected readonly ${fieldName}: ${names2.mapType},`
|
|
8102
|
+
);
|
|
8103
|
+
}
|
|
8104
|
+
lines.push(" ) {}");
|
|
8105
|
+
lines.push("");
|
|
8106
|
+
for (const { reg, names: names2 } of registries) {
|
|
8107
|
+
const fieldName = names2.method === "select" ? "registry" : `${camelFromMethod(names2.method)}Registry`;
|
|
8108
|
+
for (const e of reg.entries) {
|
|
8109
|
+
lines.push(` ${names2.method}(key: '${e.key}'): ${e.provider};`);
|
|
7918
8110
|
}
|
|
7919
|
-
|
|
7920
|
-
|
|
8111
|
+
lines.push(` ${names2.method}(key: ${reg.keyType}): ${reg.valueType};`);
|
|
8112
|
+
lines.push(` ${names2.method}(key: ${reg.keyType}): ${reg.valueType} {`);
|
|
8113
|
+
lines.push(` const entry = this.${fieldName}.get(key);`);
|
|
8114
|
+
lines.push(
|
|
8115
|
+
` if (!entry) throw new ${errorClass}(\`Unknown ${reg.keyType}: \${String(key)}\`);`
|
|
8116
|
+
);
|
|
8117
|
+
lines.push(` return entry;`);
|
|
8118
|
+
lines.push(" }");
|
|
8119
|
+
lines.push("");
|
|
7921
8120
|
}
|
|
7922
|
-
|
|
7923
|
-
|
|
7924
|
-
|
|
8121
|
+
if (pattern.dispatcher?.assemblySlot) {
|
|
8122
|
+
lines.push(
|
|
8123
|
+
` // ADR-032 Decision 5 \u2014 \`assemblySlot: '${pattern.dispatcher.assemblySlot}'\` is a`
|
|
8124
|
+
);
|
|
8125
|
+
lines.push(
|
|
8126
|
+
` // subclass contract, not emitted here. Consumers subclass and add the`
|
|
8127
|
+
);
|
|
8128
|
+
lines.push(
|
|
8129
|
+
` // \`${pattern.dispatcher.assemblySlot}(...)\` method themselves.`
|
|
8130
|
+
);
|
|
7925
8131
|
}
|
|
7926
|
-
|
|
7927
|
-
|
|
7928
|
-
return
|
|
8132
|
+
lines.push("}");
|
|
8133
|
+
lines.push("");
|
|
8134
|
+
return lines.join("\n");
|
|
7929
8135
|
}
|
|
7930
|
-
|
|
7931
|
-
"
|
|
7932
|
-
|
|
7933
|
-
|
|
7934
|
-
" direction: 'inbound' | 'change' | 'outbound' | null;",
|
|
7935
|
-
" pool: 'events_inbound' | 'events_change' | 'events_outbound' | null;",
|
|
7936
|
-
" aggregate?: string;",
|
|
7937
|
-
" source?: string;",
|
|
7938
|
-
" destination?: string;",
|
|
7939
|
-
" version: number;",
|
|
7940
|
-
" retry: { attempts: number; backoff: 'linear' | 'exponential' };",
|
|
7941
|
-
"}"
|
|
7942
|
-
].join("\n");
|
|
7943
|
-
var REGISTRY_GETTER = [
|
|
7944
|
-
"export function getEventMetadata<T extends EventTypeName>(type: T): EventMetadata {",
|
|
7945
|
-
" const meta = eventRegistry[type];",
|
|
7946
|
-
" if (!meta) {",
|
|
7947
|
-
" throw new Error(`No registry entry for event type '${String(type)}' \u2014 declare events under events/*.yaml and re-run \\`codegen entity new --all\\`.`);",
|
|
7948
|
-
" }",
|
|
7949
|
-
" return meta;",
|
|
7950
|
-
"}"
|
|
7951
|
-
].join("\n");
|
|
7952
|
-
function buildRegistryContent(events) {
|
|
7953
|
-
const sorted = [...events].sort((a, b) => a.type.localeCompare(b.type));
|
|
7954
|
-
const chunks = [];
|
|
7955
|
-
chunks.push(HEADER7);
|
|
7956
|
-
chunks.push("");
|
|
7957
|
-
chunks.push(`import type { EventTypeName } from './types';`);
|
|
7958
|
-
chunks.push("");
|
|
7959
|
-
chunks.push(REGISTRY_INTERFACE);
|
|
7960
|
-
chunks.push("");
|
|
7961
|
-
if (sorted.length === 0) {
|
|
7962
|
-
chunks.push(
|
|
7963
|
-
`export const eventRegistry = {} as Record<EventTypeName, EventMetadata>;`
|
|
7964
|
-
);
|
|
7965
|
-
chunks.push("");
|
|
7966
|
-
chunks.push(REGISTRY_GETTER);
|
|
7967
|
-
chunks.push("");
|
|
7968
|
-
return chunks.join("\n");
|
|
8136
|
+
function camelFromMethod(method) {
|
|
8137
|
+
if (method.startsWith("select") && method.length > "select".length) {
|
|
8138
|
+
const tail = method.slice("select".length);
|
|
8139
|
+
return tail.charAt(0).toLowerCase() + tail.slice(1);
|
|
7969
8140
|
}
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
|
|
7973
|
-
|
|
7974
|
-
|
|
7975
|
-
|
|
7976
|
-
|
|
7977
|
-
|
|
7978
|
-
|
|
7979
|
-
|
|
7980
|
-
|
|
7981
|
-
|
|
7982
|
-
|
|
7983
|
-
|
|
7984
|
-
|
|
7985
|
-
|
|
7986
|
-
|
|
7987
|
-
|
|
7988
|
-
|
|
7989
|
-
|
|
7990
|
-
|
|
7991
|
-
|
|
7992
|
-
|
|
7993
|
-
|
|
7994
|
-
|
|
8141
|
+
return method;
|
|
8142
|
+
}
|
|
8143
|
+
function buildModuleTs(pattern) {
|
|
8144
|
+
const registries = allRegistries(pattern);
|
|
8145
|
+
const className = dispatcherClassName(pattern);
|
|
8146
|
+
const moduleName = moduleClassName(pattern);
|
|
8147
|
+
const optsType = forRootOptionsTypeName(pattern);
|
|
8148
|
+
const { pascal } = patternNames(pattern);
|
|
8149
|
+
const typeImports = [];
|
|
8150
|
+
for (const { reg } of registries) {
|
|
8151
|
+
typeImports.push({ specifier: reg.keyTypeImport, name: reg.keyType });
|
|
8152
|
+
typeImports.push({ specifier: reg.valueTypeImport, name: reg.valueType });
|
|
8153
|
+
}
|
|
8154
|
+
const tokenValues = registries.map(({ names: names2 }) => names2.tokenConst);
|
|
8155
|
+
const lines = [];
|
|
8156
|
+
lines.push(HEADER7.trimEnd());
|
|
8157
|
+
lines.push("");
|
|
8158
|
+
lines.push(`import { type DynamicModule, Module } from '@nestjs/common';`);
|
|
8159
|
+
for (const i of emitTypeImports(typeImports)) lines.push(i);
|
|
8160
|
+
lines.push(`import { ${className} } from './dispatcher.js';`);
|
|
8161
|
+
lines.push(`import { build${pascal}RegistryProviders } from './registry.providers.js';`);
|
|
8162
|
+
lines.push(`import { ${tokenValues.sort().join(", ")} } from './tokens.js';`);
|
|
8163
|
+
lines.push("");
|
|
8164
|
+
lines.push(`export interface ${optsType} {`);
|
|
8165
|
+
for (const { reg, names: names2 } of registries) {
|
|
8166
|
+
lines.push(
|
|
8167
|
+
` ${names2.overrideField}?: Partial<Record<${reg.keyType}, ${reg.valueType}>>;`
|
|
7995
8168
|
);
|
|
7996
|
-
chunks.push(` },`);
|
|
7997
8169
|
}
|
|
7998
|
-
|
|
7999
|
-
|
|
8170
|
+
lines.push("}");
|
|
8171
|
+
lines.push("");
|
|
8172
|
+
lines.push("@Module({})");
|
|
8173
|
+
lines.push(`export class ${moduleName} {`);
|
|
8174
|
+
lines.push(` static forRoot(opts?: ${optsType}): DynamicModule {`);
|
|
8175
|
+
lines.push(" return {");
|
|
8176
|
+
lines.push(` module: ${moduleName},`);
|
|
8177
|
+
lines.push(
|
|
8178
|
+
` providers: [...build${pascal}RegistryProviders(opts), ${className}],`
|
|
8000
8179
|
);
|
|
8001
|
-
|
|
8002
|
-
|
|
8003
|
-
|
|
8004
|
-
|
|
8005
|
-
}
|
|
8006
|
-
|
|
8007
|
-
|
|
8008
|
-
import { EVENT_BUS, EVENTS_MULTI_TENANT } from '../events.tokens';
|
|
8009
|
-
import { MissingTenantIdError } from '../events-errors';
|
|
8010
|
-
import type { IEventBus, DrizzleTransaction } from '../event-bus.protocol';
|
|
8011
|
-
import { eventPayloadSchemas } from './schemas';
|
|
8012
|
-
import { getEventMetadata } from './registry';
|
|
8013
|
-
import type { EventTypeName, EventOfType, PayloadOfType } from './types';
|
|
8014
|
-
|
|
8015
|
-
/**
|
|
8016
|
-
* Typed facade over IEventBus.
|
|
8017
|
-
*
|
|
8018
|
-
* Stamps \`pool\`, \`direction\`, \`tier\`, and \`version\` into \`event.metadata\`
|
|
8019
|
-
* from the generated \`eventRegistry\` before delegating to
|
|
8020
|
-
* \`IEventBus.publish()\`. Downstream backends (DrizzleEventBus) read those
|
|
8021
|
-
* values to populate the explicit \`domain_events\` columns.
|
|
8022
|
-
*
|
|
8023
|
-
* Tier stamping (AUDIT-3): every event carries \`metadata.tier\`, sourced
|
|
8024
|
-
* from the registry. For \`tier: 'audit'\` events, the bus FORCES
|
|
8025
|
-
* \`metadata.pool = null\` and \`metadata.direction = null\` regardless of
|
|
8026
|
-
* any caller-supplied values in \`opts.metadata\` \u2014 audit routing is
|
|
8027
|
-
* bus-stamped, not caller-controlled. Caller overrides are silently
|
|
8028
|
-
* dropped with a debug-level log (callers should not be specifying these
|
|
8029
|
-
* for audit events; see ai-docs/specs/issue-242/plan.md \xA7AUDIT-3).
|
|
8030
|
-
*
|
|
8031
|
-
* Validation gating (EVT-Q5): \`CODEGEN_EVENT_VALIDATE\` env flag, default on.
|
|
8032
|
-
* Uses \`safeParse\` + \`console.warn\` \u2014 never throws, so a bad publish does
|
|
8033
|
-
* not crash a hot path.
|
|
8034
|
-
*
|
|
8035
|
-
* Multi-tenancy (EVT-6): when the EventsModule is configured with
|
|
8036
|
-
* \`multiTenant: true\`, every publish must supply \`opts.metadata.tenantId\`
|
|
8037
|
-
* \u2014 otherwise \`publish()\` throws \`MissingTenantIdError\`. When \`multiTenant\`
|
|
8038
|
-
* is \`false\` (default), no tenantId is required. If a tenantId IS supplied,
|
|
8039
|
-
* it is preserved on \`event.metadata\` and the Drizzle backend writes it to
|
|
8040
|
-
* \`domain_events.tenant_id\` (EVT-4).
|
|
8041
|
-
*/
|
|
8042
|
-
@Injectable()
|
|
8043
|
-
export class TypedEventBus {
|
|
8044
|
-
constructor(
|
|
8045
|
-
@Inject(EVENT_BUS) private readonly bus: IEventBus,
|
|
8046
|
-
@Inject(EVENTS_MULTI_TENANT) private readonly multiTenant: boolean,
|
|
8047
|
-
) {}
|
|
8048
|
-
|
|
8049
|
-
async publish<T extends EventTypeName>(
|
|
8050
|
-
type: T,
|
|
8051
|
-
aggregateId: string,
|
|
8052
|
-
payload: PayloadOfType<T>,
|
|
8053
|
-
opts?: { tx?: DrizzleTransaction; metadata?: Record<string, unknown> },
|
|
8054
|
-
): Promise<void> {
|
|
8055
|
-
const meta = getEventMetadata(type);
|
|
8056
|
-
|
|
8057
|
-
const flag = process.env['CODEGEN_EVENT_VALIDATE'];
|
|
8058
|
-
const shouldValidate =
|
|
8059
|
-
flag === undefined ? true : flag !== 'false' && flag !== '0';
|
|
8060
|
-
if (shouldValidate) {
|
|
8061
|
-
// \`eventPayloadSchemas\` is typed as \`Record<EventTypeName, z.ZodType>\`,
|
|
8062
|
-
// so under \`noUncheckedIndexedAccess\` the indexed lookup widens
|
|
8063
|
-
// to \`z.ZodType | undefined\`. When no events are registered at
|
|
8064
|
-
// codegen time \`EventTypeName\` degrades to \`string\` and the
|
|
8065
|
-
// schemas object is literally \`{}\` \u2014 the guard below is the
|
|
8066
|
-
// honest handling of that empty-registry case (skip validation;
|
|
8067
|
-
// it's a warn-only best-effort check per the class docblock).
|
|
8068
|
-
const schema = eventPayloadSchemas[type];
|
|
8069
|
-
if (schema) {
|
|
8070
|
-
const check = schema.safeParse(payload);
|
|
8071
|
-
if (!check.success) {
|
|
8072
|
-
console.warn(
|
|
8073
|
-
\`[TypedEventBus] payload validation failed for \${String(type)}:\`,
|
|
8074
|
-
check.error.issues,
|
|
8075
|
-
);
|
|
8076
|
-
}
|
|
8077
|
-
}
|
|
8078
|
-
}
|
|
8079
|
-
|
|
8080
|
-
const tenantId = opts?.metadata?.['tenantId'];
|
|
8081
|
-
if (this.multiTenant && (tenantId === undefined || tenantId === null)) {
|
|
8082
|
-
throw new MissingTenantIdError(type as string);
|
|
8083
|
-
}
|
|
8084
|
-
|
|
8085
|
-
const aggregateType =
|
|
8086
|
-
meta.aggregate ?? meta.source ?? meta.destination ?? (type as string);
|
|
8087
|
-
|
|
8088
|
-
// AUDIT-3: build metadata with tier-aware routing stamping. For
|
|
8089
|
-
// \`tier: 'audit'\` events the bus FORCES pool/direction to null,
|
|
8090
|
-
// even if the caller supplied them in opts.metadata. Audit routing
|
|
8091
|
-
// is bus-stamped, not caller-controlled (see plan \xA7AUDIT-3).
|
|
8092
|
-
const baseMetadata: Record<string, unknown> = { ...(opts?.metadata ?? {}) };
|
|
8093
|
-
if (meta.tier === 'audit') {
|
|
8094
|
-
if (
|
|
8095
|
-
baseMetadata['pool'] !== undefined ||
|
|
8096
|
-
baseMetadata['direction'] !== undefined
|
|
8097
|
-
) {
|
|
8098
|
-
console.debug(
|
|
8099
|
-
\`[TypedEventBus] tier:audit event '\${String(type)}' had pool/direction in opts.metadata; overriding to null.\`,
|
|
8100
|
-
);
|
|
8101
|
-
}
|
|
8102
|
-
baseMetadata['pool'] = null;
|
|
8103
|
-
baseMetadata['direction'] = null;
|
|
8104
|
-
baseMetadata['tier'] = 'audit';
|
|
8105
|
-
} else {
|
|
8106
|
-
baseMetadata['pool'] = meta.pool;
|
|
8107
|
-
baseMetadata['direction'] = meta.direction;
|
|
8108
|
-
baseMetadata['tier'] = 'domain';
|
|
8109
|
-
}
|
|
8110
|
-
baseMetadata['version'] = meta.version;
|
|
8111
|
-
|
|
8112
|
-
await this.bus.publish(
|
|
8113
|
-
{
|
|
8114
|
-
id: randomUUID(),
|
|
8115
|
-
type,
|
|
8116
|
-
aggregateId,
|
|
8117
|
-
aggregateType,
|
|
8118
|
-
payload: payload as Record<string, unknown>,
|
|
8119
|
-
occurredAt: new Date(),
|
|
8120
|
-
metadata: baseMetadata,
|
|
8121
|
-
},
|
|
8122
|
-
opts?.tx,
|
|
8123
|
-
);
|
|
8124
|
-
}
|
|
8125
|
-
|
|
8126
|
-
subscribe<T extends EventTypeName>(
|
|
8127
|
-
type: T,
|
|
8128
|
-
handler: (event: EventOfType<T>) => Promise<void>,
|
|
8129
|
-
): () => void {
|
|
8130
|
-
return this.bus.subscribe<EventOfType<T>>(type, handler as never);
|
|
8131
|
-
}
|
|
8180
|
+
const exportItems = [...tokenValues.sort(), className];
|
|
8181
|
+
lines.push(` exports: [${exportItems.join(", ")}],`);
|
|
8182
|
+
lines.push(" };");
|
|
8183
|
+
lines.push(" }");
|
|
8184
|
+
lines.push("}");
|
|
8185
|
+
lines.push("");
|
|
8186
|
+
return lines.join("\n");
|
|
8132
8187
|
}
|
|
8133
|
-
|
|
8134
|
-
|
|
8135
|
-
|
|
8188
|
+
function buildIndexTs(pattern) {
|
|
8189
|
+
const lines = [];
|
|
8190
|
+
lines.push(HEADER7.trimEnd());
|
|
8191
|
+
lines.push("");
|
|
8192
|
+
lines.push(`export * from './tokens.js';`);
|
|
8193
|
+
lines.push(`export * from './dispatcher.js';`);
|
|
8194
|
+
lines.push(`export * from './module.js';`);
|
|
8195
|
+
lines.push(`export * from './registry.providers.js';`);
|
|
8196
|
+
lines.push("");
|
|
8197
|
+
return lines.join("\n");
|
|
8136
8198
|
}
|
|
8137
|
-
function
|
|
8138
|
-
|
|
8139
|
-
|
|
8140
|
-
|
|
8141
|
-
|
|
8142
|
-
|
|
8143
|
-
|
|
8199
|
+
function buildRootBarrelTs(patterns) {
|
|
8200
|
+
const lines = [];
|
|
8201
|
+
lines.push(HEADER7.trimEnd());
|
|
8202
|
+
lines.push("");
|
|
8203
|
+
if (patterns.length === 0) {
|
|
8204
|
+
lines.push("// No orchestration patterns registered.");
|
|
8205
|
+
lines.push("export {};");
|
|
8206
|
+
lines.push("");
|
|
8207
|
+
return lines.join("\n");
|
|
8208
|
+
}
|
|
8209
|
+
const slugs = patterns.map((p) => toKebabCase2(p.name)).sort();
|
|
8210
|
+
for (const slug of slugs) {
|
|
8211
|
+
lines.push(`export * from './${slug}/index.js';`);
|
|
8212
|
+
}
|
|
8213
|
+
lines.push("");
|
|
8214
|
+
return lines.join("\n");
|
|
8144
8215
|
}
|
|
8145
|
-
|
|
8146
|
-
|
|
8147
|
-
|
|
8148
|
-
|
|
8149
|
-
|
|
8150
|
-
"index.ts"
|
|
8151
|
-
];
|
|
8152
|
-
async function generateEventCodegen(opts) {
|
|
8153
|
-
const { entitiesDir, eventsDir, outputDir, dryRun = false } = opts;
|
|
8154
|
-
const { events: merged, issues } = collectMergedEvents({
|
|
8155
|
-
entitiesDir,
|
|
8156
|
-
eventsDir
|
|
8157
|
-
});
|
|
8158
|
-
const builders = {
|
|
8159
|
-
"types.ts": buildTypesContent,
|
|
8160
|
-
"schemas.ts": buildSchemasContent,
|
|
8161
|
-
"registry.ts": buildRegistryContent,
|
|
8162
|
-
"bus.ts": buildBusContent,
|
|
8163
|
-
"index.ts": buildIndexContent
|
|
8164
|
-
};
|
|
8165
|
-
const files = OUTPUT_FILE_NAMES.map((name) => ({
|
|
8216
|
+
function buildPatternFiles(pattern, outputRoot) {
|
|
8217
|
+
assertEmittable(pattern);
|
|
8218
|
+
const slug = toKebabCase2(pattern.name);
|
|
8219
|
+
const outputDir = path12.join(outputRoot, slug);
|
|
8220
|
+
const make = (name, content) => ({
|
|
8166
8221
|
name,
|
|
8167
8222
|
outputPath: path12.join(outputDir, name),
|
|
8168
|
-
|
|
8169
|
-
|
|
8170
|
-
|
|
8223
|
+
relativePath: path12.join(slug, name),
|
|
8224
|
+
content
|
|
8225
|
+
});
|
|
8226
|
+
const files = [
|
|
8227
|
+
make("tokens.ts", buildTokensTs(pattern)),
|
|
8228
|
+
make("registry.providers.ts", buildProvidersTs(pattern)),
|
|
8229
|
+
make("dispatcher.ts", buildDispatcherTs(pattern)),
|
|
8230
|
+
make("module.ts", buildModuleTs(pattern)),
|
|
8231
|
+
make("index.ts", buildIndexTs(pattern))
|
|
8232
|
+
];
|
|
8233
|
+
return {
|
|
8234
|
+
patternName: pattern.name,
|
|
8235
|
+
slug,
|
|
8236
|
+
outputDir,
|
|
8237
|
+
files
|
|
8238
|
+
};
|
|
8239
|
+
}
|
|
8240
|
+
function generateOrchestrationModules(opts) {
|
|
8241
|
+
const { patterns, outputRoot, dryRun = false } = opts;
|
|
8242
|
+
const perPattern = [];
|
|
8243
|
+
const allFiles = [];
|
|
8244
|
+
for (const pattern of patterns) {
|
|
8245
|
+
const result = buildPatternFiles(pattern, outputRoot);
|
|
8246
|
+
perPattern.push(result);
|
|
8247
|
+
allFiles.push(...result.files);
|
|
8248
|
+
}
|
|
8249
|
+
const rootBarrel = {
|
|
8250
|
+
name: "index.ts",
|
|
8251
|
+
outputPath: path12.join(outputRoot, "index.ts"),
|
|
8252
|
+
relativePath: "index.ts",
|
|
8253
|
+
content: buildRootBarrelTs(patterns)
|
|
8254
|
+
};
|
|
8255
|
+
allFiles.push(rootBarrel);
|
|
8171
8256
|
let written = false;
|
|
8172
|
-
if (!dryRun &&
|
|
8173
|
-
fs9.mkdirSync(
|
|
8174
|
-
for (const
|
|
8175
|
-
fs9.
|
|
8257
|
+
if (!dryRun && patterns.length > 0) {
|
|
8258
|
+
fs9.mkdirSync(outputRoot, { recursive: true });
|
|
8259
|
+
for (const r of perPattern) {
|
|
8260
|
+
fs9.mkdirSync(r.outputDir, { recursive: true });
|
|
8261
|
+
for (const f of r.files) {
|
|
8262
|
+
fs9.writeFileSync(f.outputPath, f.content);
|
|
8263
|
+
}
|
|
8176
8264
|
}
|
|
8265
|
+
fs9.writeFileSync(rootBarrel.outputPath, rootBarrel.content);
|
|
8177
8266
|
written = true;
|
|
8178
8267
|
}
|
|
8179
8268
|
return {
|
|
8180
|
-
|
|
8181
|
-
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
written,
|
|
8185
|
-
files
|
|
8269
|
+
outputRoot,
|
|
8270
|
+
patterns: perPattern,
|
|
8271
|
+
files: allFiles,
|
|
8272
|
+
written
|
|
8186
8273
|
};
|
|
8187
8274
|
}
|
|
8188
8275
|
|
|
@@ -9728,21 +9815,15 @@ var EntityNewCommand = class extends Command2 {
|
|
|
9728
9815
|
const generatedDir = resolveGeneratedDir(ctx);
|
|
9729
9816
|
const architecture = resolveArchitecture(ctx);
|
|
9730
9817
|
const subsystemsRoot = resolveSubsystemsRoot(ctx);
|
|
9731
|
-
const
|
|
9732
|
-
|
|
9733
|
-
"jobs/generated/scope-entity-type.ts"
|
|
9734
|
-
);
|
|
9818
|
+
const runtimeMode = resolveRuntimeMode(ctx.config);
|
|
9819
|
+
const scopeEntityTypePath = runtimeMode === "package" ? path14.resolve(generatedDir, "scope-entity-type.ts") : path14.resolve(subsystemsRoot, "jobs/generated/scope-entity-type.ts");
|
|
9735
9820
|
const eventsDir = resolveEventsDir(ctx);
|
|
9736
|
-
const eventCodegenOutputDir = path14.resolve(
|
|
9737
|
-
|
|
9738
|
-
|
|
9739
|
-
);
|
|
9740
|
-
const bridgeRegistryOutputDir = path14.resolve(
|
|
9741
|
-
subsystemsRoot,
|
|
9742
|
-
"bridge/generated"
|
|
9743
|
-
);
|
|
9821
|
+
const eventCodegenOutputDir = runtimeMode === "package" ? path14.resolve(generatedDir, "events") : path14.resolve(subsystemsRoot, "events/generated");
|
|
9822
|
+
const bridgeInstalledForRegistry = configuredSubsystemNames(
|
|
9823
|
+
ctx.config
|
|
9824
|
+
).includes("bridge");
|
|
9825
|
+
const bridgeRegistryOutputDir = runtimeMode === "package" ? generatedDir : path14.resolve(subsystemsRoot, "bridge/generated");
|
|
9744
9826
|
const backendSrcForHandlers = ctx.config?.paths?.backend_src ?? "src";
|
|
9745
|
-
const runtimeMode = resolveRuntimeMode(ctx.config);
|
|
9746
9827
|
const bridgeHandlersDir = path14.resolve(
|
|
9747
9828
|
ctx.cwd,
|
|
9748
9829
|
backendSrcForHandlers,
|
|
@@ -9787,12 +9868,15 @@ var EntityNewCommand = class extends Command2 {
|
|
|
9787
9868
|
entitiesDir,
|
|
9788
9869
|
eventsDir,
|
|
9789
9870
|
outputDir: eventCodegenOutputDir,
|
|
9871
|
+
mode: runtimeMode,
|
|
9790
9872
|
dryRun: true
|
|
9791
9873
|
});
|
|
9792
9874
|
const bridgeRegistryPlan = await generateBridgeRegistry({
|
|
9793
9875
|
handlersDir: bridgeHandlersDir,
|
|
9794
9876
|
eventsGeneratedDir: eventCodegenOutputDir,
|
|
9795
9877
|
outputDir: bridgeRegistryOutputDir,
|
|
9878
|
+
mode: runtimeMode,
|
|
9879
|
+
bridgeInstalled: bridgeInstalledForRegistry,
|
|
9796
9880
|
dryRun: true
|
|
9797
9881
|
});
|
|
9798
9882
|
const orchestrationPatterns = await loadOrchestrationPatterns();
|
|
@@ -9965,7 +10049,8 @@ var EntityNewCommand = class extends Command2 {
|
|
|
9965
10049
|
eventCodegenResult = await generateEventCodegen({
|
|
9966
10050
|
entitiesDir,
|
|
9967
10051
|
eventsDir,
|
|
9968
|
-
outputDir: eventCodegenOutputDir
|
|
10052
|
+
outputDir: eventCodegenOutputDir,
|
|
10053
|
+
mode: runtimeMode
|
|
9969
10054
|
});
|
|
9970
10055
|
if (!isJsonMode()) {
|
|
9971
10056
|
for (const issue of eventCodegenResult.issues) {
|
|
@@ -9987,7 +10072,9 @@ var EntityNewCommand = class extends Command2 {
|
|
|
9987
10072
|
bridgeRegistryResult = await generateBridgeRegistry({
|
|
9988
10073
|
handlersDir: bridgeHandlersDir,
|
|
9989
10074
|
eventsGeneratedDir: eventCodegenOutputDir,
|
|
9990
|
-
outputDir: bridgeRegistryOutputDir
|
|
10075
|
+
outputDir: bridgeRegistryOutputDir,
|
|
10076
|
+
mode: runtimeMode,
|
|
10077
|
+
bridgeInstalled: bridgeInstalledForRegistry
|
|
9991
10078
|
});
|
|
9992
10079
|
if (bridgeRegistryResult.skipped && !isJsonMode()) {
|
|
9993
10080
|
printInfo(
|