@pattern-stack/codegen 0.14.2 → 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 +43 -0
- 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 +1308 -1276
- package/dist/src/cli/index.js.map +1 -1
- package/package.json +1 -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";
|
|
@@ -6856,1384 +6856,1420 @@ async function generateBridgeRegistry(opts) {
|
|
|
6856
6856
|
};
|
|
6857
6857
|
}
|
|
6858
6858
|
|
|
6859
|
-
// src/cli/shared/
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
}
|
|
6865
|
-
function
|
|
6866
|
-
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
|
|
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
|
+
}
|
|
6875
6882
|
}
|
|
6876
|
-
return
|
|
6883
|
+
return issues;
|
|
6877
6884
|
}
|
|
6878
|
-
function
|
|
6879
|
-
const
|
|
6880
|
-
if (
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
const parts = [`backend: 'bullmq'`];
|
|
6884
|
-
if (multiTenant) parts.push(`multiTenant: true`);
|
|
6885
|
-
parts.push(`extensions: { bullmq: ${jsonToTs(bullExt)} }`);
|
|
6886
|
-
return `{ ${parts.join(", ")} }`;
|
|
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;
|
|
6887
6890
|
}
|
|
6888
|
-
function
|
|
6889
|
-
const
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
|
|
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 };
|
|
6893
6906
|
}
|
|
6894
|
-
if (
|
|
6895
|
-
|
|
6896
|
-
|
|
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;
|
|
6923
|
+
}
|
|
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;
|
|
6935
|
+
}
|
|
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;
|
|
6945
|
+
}
|
|
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 };
|
|
6897
6959
|
}
|
|
6898
|
-
|
|
6899
|
-
|
|
6900
|
-
|
|
6901
|
-
|
|
6902
|
-
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
]
|
|
6909
|
-
};
|
|
6910
|
-
},
|
|
6911
|
-
jobs: ({ moduleImport, cfg, bridgeInstalled }) => {
|
|
6912
|
-
const backend = cfg?.backend ?? "drizzle";
|
|
6913
|
-
const multiTenant = Boolean(cfg?.multi_tenant);
|
|
6914
|
-
const workerMode = (cfg?.worker_mode ?? "standalone").trim();
|
|
6915
|
-
const imports = [
|
|
6916
|
-
`import { JobsDomainModule } from '${moduleImport("jobs", "jobs-domain.module")}';`
|
|
6917
|
-
];
|
|
6918
|
-
const bullExt = backend === "bullmq" ? cfg?.extensions?.bullmq : void 0;
|
|
6919
|
-
const domainOpts = quoteBullmqDomainOpts({ backend, multiTenant, bullExt });
|
|
6920
|
-
const calls = [` JobsDomainModule.forRoot(${domainOpts}),`];
|
|
6921
|
-
if (workerMode === "embedded") {
|
|
6922
|
-
imports.push(
|
|
6923
|
-
`import { JobWorkerModule } from '${moduleImport("jobs", "job-worker.module")}';`
|
|
6924
|
-
);
|
|
6925
|
-
const parts = [`mode: 'embedded'`];
|
|
6926
|
-
if (backend === "bullmq") {
|
|
6927
|
-
parts.push(`backend: 'bullmq'`);
|
|
6928
|
-
if (bullExt) {
|
|
6929
|
-
parts.push(`domainModuleExtensions: { bullmq: ${jsonToTs(bullExt)} }`);
|
|
6930
|
-
}
|
|
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("|")}`
|
|
6969
|
+
);
|
|
6931
6970
|
}
|
|
6932
|
-
|
|
6933
|
-
if (poolsClause) parts.push(poolsClause);
|
|
6934
|
-
calls.push(` JobWorkerModule.forRoot({ ${parts.join(", ")} }),`);
|
|
6935
|
-
}
|
|
6936
|
-
return { imports, calls };
|
|
6937
|
-
},
|
|
6938
|
-
bridge: ({ moduleImport, cfg, mode }) => {
|
|
6939
|
-
const backend = cfg?.backend ?? "drizzle";
|
|
6940
|
-
const multiTenant = Boolean(cfg?.multi_tenant);
|
|
6941
|
-
const imports = [
|
|
6942
|
-
`import { BridgeModule } from '${moduleImport("bridge", "bridge.module")}';`
|
|
6943
|
-
];
|
|
6944
|
-
if (mode === "package") {
|
|
6945
|
-
imports.push(`import { bridgeRegistry } from './bridge-registry';`);
|
|
6946
|
-
return {
|
|
6947
|
-
imports,
|
|
6948
|
-
calls: [
|
|
6949
|
-
` BridgeModule.forRoot({ backend: '${backend}', multiTenant: ${multiTenant}, registry: bridgeRegistry }),`
|
|
6950
|
-
]
|
|
6951
|
-
};
|
|
6971
|
+
payload[key] = { type: typeString, nullable: false };
|
|
6952
6972
|
}
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
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
|
|
6958
6982
|
};
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
|
|
6962
|
-
|
|
6983
|
+
return def;
|
|
6984
|
+
});
|
|
6985
|
+
}
|
|
6986
|
+
function isEventFieldType(s) {
|
|
6987
|
+
return EVENT_FIELD_TYPES.includes(s);
|
|
6988
|
+
}
|
|
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") {
|
|
6963
6994
|
return {
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
calls: [
|
|
6968
|
-
` IntegrationModule.forRoot(${quoteOpts({ backend, multiTenant })}),`
|
|
6969
|
-
]
|
|
6995
|
+
protocol: PACKAGE_EVENTS_RUNTIME_IMPORT,
|
|
6996
|
+
tokens: PACKAGE_EVENTS_RUNTIME_IMPORT,
|
|
6997
|
+
errors: PACKAGE_EVENTS_RUNTIME_IMPORT
|
|
6970
6998
|
};
|
|
6971
6999
|
}
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
6975
|
-
|
|
6976
|
-
|
|
6977
|
-
}
|
|
6978
|
-
return (subsystem) => subsystem === "events" ? `${PACKAGE2}/subsystems` : `${PACKAGE2}/runtime/subsystems/${subsystem}/index`;
|
|
7000
|
+
return {
|
|
7001
|
+
protocol: "../event-bus.protocol",
|
|
7002
|
+
tokens: "../events.tokens",
|
|
7003
|
+
errors: "../events-errors"
|
|
7004
|
+
};
|
|
6979
7005
|
}
|
|
6980
|
-
var
|
|
6981
|
-
|
|
6982
|
-
// Subsystem composition barrel \u2014 reflects \`subsystems.install\` in
|
|
6983
|
-
// codegen.config.yaml and the per-subsystem option blocks
|
|
6984
|
-
// (\`events:\`, \`jobs:\`, \`bridge:\`, \`integration:\`).
|
|
6985
|
-
//
|
|
6986
|
-
// Wire into AppModule once:
|
|
6987
|
-
//
|
|
6988
|
-
// import { SUBSYSTEM_MODULES } from './generated/subsystems';
|
|
6989
|
-
// @Module({ imports: [DatabaseModule, ...SUBSYSTEM_MODULES, ...GENERATED_MODULES] })
|
|
6990
|
-
//
|
|
6991
|
-
// Regenerated by every \`codegen entity new\` / \`codegen subsystem install\`.
|
|
6992
|
-
|
|
7006
|
+
var HEADER4 = `// AUTO-GENERATED by @pattern-stack/codegen. Do not edit.
|
|
7007
|
+
// Run \`codegen entity new --all\` to refresh.
|
|
6993
7008
|
`;
|
|
6994
|
-
function
|
|
6995
|
-
const
|
|
6996
|
-
|
|
6997
|
-
const
|
|
6998
|
-
|
|
6999
|
-
|
|
7000
|
-
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
|
|
7006
|
-
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
|
|
7010
|
-
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
|
|
7017
|
-
|
|
7018
|
-
|
|
7019
|
-
|
|
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("");
|
|
7014
|
+
}
|
|
7015
|
+
function toPascalCase2(input) {
|
|
7016
|
+
return input.split("_").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
7017
|
+
}
|
|
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];
|
|
7020
7041
|
}
|
|
7021
|
-
|
|
7022
|
-
` : `export const SUBSYSTEM_MODULES: DynamicModule[] = [
|
|
7023
|
-
${allCalls.join("\n")}
|
|
7024
|
-
];
|
|
7025
|
-
`;
|
|
7026
|
-
const body = allImports.join("\n") + "\n\n" + exportLine;
|
|
7027
|
-
return { content: HEADER4 + body, emitted, skipped };
|
|
7042
|
+
return field.nullable ? `${base} | null` : base;
|
|
7028
7043
|
}
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
|
|
7032
|
-
|
|
7033
|
-
|
|
7034
|
-
|
|
7035
|
-
|
|
7036
|
-
const subsystemsAbs = resolveSubsystemsRoot(ctx);
|
|
7037
|
-
const barrelAbs = path9.resolve(generatedDir, "subsystems.ts");
|
|
7038
|
-
let subsystemsRel = path9.relative(path9.dirname(barrelAbs), subsystemsAbs).split(path9.sep).join("/");
|
|
7039
|
-
if (!subsystemsRel.startsWith(".")) subsystemsRel = "./" + subsystemsRel;
|
|
7040
|
-
const { content, emitted, skipped } = buildSubsystemBarrel(
|
|
7041
|
-
installed,
|
|
7042
|
-
ctx.config,
|
|
7043
|
-
subsystemsRel,
|
|
7044
|
-
mode
|
|
7045
|
-
);
|
|
7046
|
-
let written = false;
|
|
7047
|
-
if (!dryRun) {
|
|
7048
|
-
fs6.mkdirSync(path9.dirname(barrelAbs), { recursive: true });
|
|
7049
|
-
fs6.writeFileSync(barrelAbs, content);
|
|
7050
|
-
written = true;
|
|
7051
|
-
if (mode === "package" && emitted.includes("bridge")) {
|
|
7052
|
-
const registryPath = path9.resolve(generatedDir, "bridge-registry.ts");
|
|
7053
|
-
if (!fs6.existsSync(registryPath)) {
|
|
7054
|
-
fs6.writeFileSync(
|
|
7055
|
-
registryPath,
|
|
7056
|
-
buildBridgeRegistryContent([], PACKAGE_BRIDGE_TYPE_IMPORT)
|
|
7057
|
-
);
|
|
7058
|
-
}
|
|
7059
|
-
}
|
|
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];
|
|
7060
7051
|
}
|
|
7061
|
-
return {
|
|
7062
|
-
subsystemBarrel: barrelAbs,
|
|
7063
|
-
emitted,
|
|
7064
|
-
skipped,
|
|
7065
|
-
content,
|
|
7066
|
-
written
|
|
7067
|
-
};
|
|
7052
|
+
return field.nullable ? `${base}.nullable()` : base;
|
|
7068
7053
|
}
|
|
7069
|
-
|
|
7070
|
-
|
|
7071
|
-
import fs7 from "fs";
|
|
7072
|
-
import path10 from "path";
|
|
7073
|
-
var PACKAGE3 = "@pattern-stack/codegen";
|
|
7074
|
-
var SCHEMA_SYMBOLS = {
|
|
7075
|
-
events: ["domainEvents"],
|
|
7076
|
-
jobs: [
|
|
7077
|
-
"jobs",
|
|
7078
|
-
"jobRuns",
|
|
7079
|
-
"jobSteps",
|
|
7080
|
-
"jobRunStatusEnum",
|
|
7081
|
-
"jobStepKindEnum",
|
|
7082
|
-
"jobStepStatusEnum",
|
|
7083
|
-
"collisionModeEnum",
|
|
7084
|
-
"replayFromEnum",
|
|
7085
|
-
"parentClosePolicyEnum",
|
|
7086
|
-
"waitKindEnum",
|
|
7087
|
-
"triggerSourceEnum"
|
|
7088
|
-
],
|
|
7089
|
-
bridge: ["bridgeDelivery", "bridgeDeliveryStatusEnum"],
|
|
7090
|
-
integration: [
|
|
7091
|
-
"integrationSubscriptions",
|
|
7092
|
-
"integrationRuns",
|
|
7093
|
-
"integrationRunItems",
|
|
7094
|
-
"integrationRunDirectionEnum",
|
|
7095
|
-
"integrationRunActionEnum",
|
|
7096
|
-
"integrationRunStatusEnum",
|
|
7097
|
-
"integrationRunItemOperationEnum",
|
|
7098
|
-
"integrationRunItemStatusEnum"
|
|
7099
|
-
]
|
|
7100
|
-
};
|
|
7101
|
-
var SCHEMA_ORDER = ["events", "jobs", "bridge", "integration"];
|
|
7102
|
-
var HEADER5 = `// AUTO-GENERATED by @pattern-stack/codegen. Do not edit.
|
|
7103
|
-
// Subsystem Drizzle schema barrel \u2014 re-exports the tables + pgEnums of every
|
|
7104
|
-
// installed subsystem so drizzle-kit emits their CREATE TABLE / CREATE TYPE.
|
|
7105
|
-
//
|
|
7106
|
-
// Fold into your drizzle-kit schema entrypoint once, alongside the entity
|
|
7107
|
-
// schema barrel:
|
|
7108
|
-
//
|
|
7109
|
-
// // src/schema.ts
|
|
7110
|
-
// export * from './generated/schema'; // entity tables
|
|
7111
|
-
// export * from './generated/subsystems-schema'; // subsystem tables + enums
|
|
7112
|
-
//
|
|
7113
|
-
// Regenerated by every \`codegen entity new\` / \`codegen subsystem install\`.
|
|
7114
|
-
|
|
7115
|
-
`;
|
|
7116
|
-
function schemaImportFor(mode, subsystem, subsystemsRel) {
|
|
7117
|
-
return mode === "vendored" ? `${subsystemsRel}/${subsystem}` : `${PACKAGE3}/runtime/subsystems/${subsystem}/index`;
|
|
7054
|
+
function aggregateTypeLiteral(ev) {
|
|
7055
|
+
return ev.aggregate ?? ev.source ?? ev.destination ?? ev.type;
|
|
7118
7056
|
}
|
|
7119
|
-
function
|
|
7120
|
-
const
|
|
7121
|
-
const
|
|
7122
|
-
|
|
7123
|
-
|
|
7124
|
-
for (const name of SCHEMA_ORDER) {
|
|
7125
|
-
if (!installedNames.has(name)) continue;
|
|
7126
|
-
const symbols = SCHEMA_SYMBOLS[name];
|
|
7127
|
-
if (!symbols || symbols.length === 0) continue;
|
|
7128
|
-
const importSpec = schemaImportFor(mode, name, subsystemsRel);
|
|
7129
|
-
lines.push(`export { ${symbols.join(", ")} } from '${importSpec}';`);
|
|
7130
|
-
emitted.push(name);
|
|
7057
|
+
function collectEntityEvents(entitiesDir) {
|
|
7058
|
+
const events = [];
|
|
7059
|
+
const issues = [];
|
|
7060
|
+
if (!fs6.existsSync(entitiesDir)) {
|
|
7061
|
+
return { events, issues };
|
|
7131
7062
|
}
|
|
7132
|
-
const
|
|
7133
|
-
|
|
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
|
+
}
|
|
7077
|
+
}
|
|
7078
|
+
return { events, issues };
|
|
7134
7079
|
}
|
|
7135
|
-
|
|
7136
|
-
const
|
|
7137
|
-
const
|
|
7138
|
-
const
|
|
7139
|
-
|
|
7140
|
-
|
|
7141
|
-
|
|
7142
|
-
|
|
7143
|
-
|
|
7144
|
-
|
|
7145
|
-
|
|
7146
|
-
|
|
7147
|
-
|
|
7148
|
-
|
|
7149
|
-
|
|
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);
|
|
7085
|
+
}
|
|
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);
|
|
7095
|
+
}
|
|
7096
|
+
const events = Array.from(byType.values()).sort(
|
|
7097
|
+
(a, b) => a.type.localeCompare(b.type)
|
|
7150
7098
|
);
|
|
7151
|
-
|
|
7152
|
-
|
|
7153
|
-
|
|
7154
|
-
|
|
7155
|
-
|
|
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);
|
|
7109
|
+
}
|
|
7156
7110
|
}
|
|
7157
|
-
|
|
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 };
|
|
7158
7123
|
}
|
|
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}';
|
|
7159
7130
|
|
|
7160
|
-
|
|
7161
|
-
|
|
7162
|
-
|
|
7163
|
-
|
|
7164
|
-
|
|
7165
|
-
|
|
7166
|
-
this.issueType = issueType;
|
|
7167
|
-
this.patternName = patternName;
|
|
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
|
+
`;
|
|
7168
7137
|
}
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
};
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
`;
|
|
7176
|
-
|
|
7177
|
-
|
|
7178
|
-
}
|
|
7179
|
-
function toKebabCase2(str) {
|
|
7180
|
-
return splitWords(str).join("-");
|
|
7181
|
-
}
|
|
7182
|
-
function toPascalCase2(str) {
|
|
7183
|
-
return splitWords(str).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
7184
|
-
}
|
|
7185
|
-
function toCamelCase(str) {
|
|
7186
|
-
const words = splitWords(str);
|
|
7187
|
-
if (words.length === 0) return "";
|
|
7188
|
-
const [first, ...rest] = words;
|
|
7189
|
-
return (first ?? "") + rest.map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
7190
|
-
}
|
|
7191
|
-
function toScreamingSnake(str) {
|
|
7192
|
-
return splitWords(str).join("_").toUpperCase();
|
|
7193
|
-
}
|
|
7194
|
-
var FORBIDDEN_KEY_TYPES = /* @__PURE__ */ new Set([
|
|
7195
|
-
"string",
|
|
7196
|
-
"number",
|
|
7197
|
-
"symbol",
|
|
7198
|
-
"any",
|
|
7199
|
-
"unknown"
|
|
7200
|
-
]);
|
|
7201
|
-
function assertEmittable(pattern) {
|
|
7202
|
-
const allRegistries2 = [
|
|
7203
|
-
pattern.registry,
|
|
7204
|
-
...pattern.coKeyedRegistries ?? []
|
|
7205
|
-
];
|
|
7206
|
-
for (const sibling of pattern.coKeyedRegistries ?? []) {
|
|
7207
|
-
if (typeof sibling.name !== "string" || sibling.name.length === 0) {
|
|
7208
|
-
throw new OrchestrationEmissionError(
|
|
7209
|
-
"pattern_cokeyed_missing_name",
|
|
7210
|
-
pattern.name,
|
|
7211
|
-
`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.`
|
|
7212
|
-
);
|
|
7213
|
-
}
|
|
7214
|
-
}
|
|
7215
|
-
for (const reg of allRegistries2) {
|
|
7216
|
-
if (FORBIDDEN_KEY_TYPES.has(reg.keyType)) {
|
|
7217
|
-
throw new OrchestrationEmissionError(
|
|
7218
|
-
"pattern_keytype_unresolved",
|
|
7219
|
-
pattern.name,
|
|
7220
|
-
`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.`
|
|
7221
|
-
);
|
|
7222
|
-
}
|
|
7223
|
-
if (typeof reg.keyTypeImport !== "string" || reg.keyTypeImport.length === 0) {
|
|
7224
|
-
throw new OrchestrationEmissionError(
|
|
7225
|
-
"pattern_missing_import_path",
|
|
7226
|
-
pattern.name,
|
|
7227
|
-
`registry (keyType '${reg.keyType}') is missing 'keyTypeImport'. Phase 3-2 emission requires every keyType to carry a module specifier.`
|
|
7228
|
-
);
|
|
7229
|
-
}
|
|
7230
|
-
if (typeof reg.valueTypeImport !== "string" || reg.valueTypeImport.length === 0) {
|
|
7231
|
-
throw new OrchestrationEmissionError(
|
|
7232
|
-
"pattern_missing_import_path",
|
|
7233
|
-
pattern.name,
|
|
7234
|
-
`registry (valueType '${reg.valueType}') is missing 'valueTypeImport'.`
|
|
7235
|
-
);
|
|
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} */`);
|
|
7236
7148
|
}
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7240
|
-
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
|
|
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)};`);
|
|
7244
7164
|
}
|
|
7165
|
+
chunks.push(` };`);
|
|
7245
7166
|
}
|
|
7167
|
+
chunks.push(`}`);
|
|
7168
|
+
chunks.push("");
|
|
7246
7169
|
}
|
|
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");
|
|
7247
7183
|
}
|
|
7248
|
-
function
|
|
7249
|
-
const
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
}
|
|
7254
|
-
|
|
7255
|
-
|
|
7256
|
-
|
|
7257
|
-
return {
|
|
7258
|
-
tokenConst: `${screaming}_REGISTRY`,
|
|
7259
|
-
mapType: `${pascal}RegistryMap`,
|
|
7260
|
-
method: "select",
|
|
7261
|
-
overrideField: "overrides",
|
|
7262
|
-
builderFn: `build${pascal}RegistryProviders`
|
|
7263
|
-
};
|
|
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
|
+
`;
|
|
7264
7193
|
}
|
|
7265
|
-
const
|
|
7266
|
-
|
|
7267
|
-
|
|
7268
|
-
|
|
7269
|
-
|
|
7270
|
-
|
|
7271
|
-
|
|
7272
|
-
|
|
7273
|
-
|
|
7274
|
-
|
|
7275
|
-
}
|
|
7276
|
-
|
|
7277
|
-
|
|
7278
|
-
}
|
|
7279
|
-
|
|
7280
|
-
|
|
7281
|
-
|
|
7282
|
-
|
|
7283
|
-
|
|
7284
|
-
}
|
|
7285
|
-
|
|
7286
|
-
|
|
7287
|
-
for (const i of imports) {
|
|
7288
|
-
const set = grouped.get(i.specifier) ?? /* @__PURE__ */ new Set();
|
|
7289
|
-
set.add(i.name);
|
|
7290
|
-
grouped.set(i.specifier, set);
|
|
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;
|
|
7207
|
+
}
|
|
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("");
|
|
7291
7216
|
}
|
|
7292
|
-
const
|
|
7293
|
-
for (const
|
|
7294
|
-
|
|
7295
|
-
out.push(`import type { ${names2.join(", ")} } from '${specifier}';`);
|
|
7217
|
+
chunks.push(`export const eventPayloadSchemas = {`);
|
|
7218
|
+
for (const ev of sorted) {
|
|
7219
|
+
chunks.push(` '${ev.type}': ${toCamelCase(ev.type)}PayloadSchema,`);
|
|
7296
7220
|
}
|
|
7297
|
-
|
|
7221
|
+
chunks.push(`} as const satisfies Record<EventTypeName, z.ZodType>;`);
|
|
7222
|
+
chunks.push("");
|
|
7223
|
+
return chunks.join("\n");
|
|
7298
7224
|
}
|
|
7299
|
-
|
|
7300
|
-
|
|
7301
|
-
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
|
|
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");
|
|
7305
7264
|
}
|
|
7306
|
-
const
|
|
7307
|
-
for (const
|
|
7308
|
-
const
|
|
7309
|
-
|
|
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}' },`
|
|
7290
|
+
);
|
|
7291
|
+
chunks.push(` },`);
|
|
7310
7292
|
}
|
|
7311
|
-
|
|
7293
|
+
chunks.push(
|
|
7294
|
+
`} as const satisfies Record<EventTypeName, EventMetadata>;`
|
|
7295
|
+
);
|
|
7296
|
+
chunks.push("");
|
|
7297
|
+
chunks.push(REGISTRY_GETTER);
|
|
7298
|
+
chunks.push("");
|
|
7299
|
+
return chunks.join("\n");
|
|
7312
7300
|
}
|
|
7313
|
-
|
|
7314
|
-
|
|
7315
|
-
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
|
|
7319
|
-
|
|
7320
|
-
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
|
|
7325
|
-
|
|
7326
|
-
|
|
7327
|
-
|
|
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
|
+
}
|
|
7328
7427
|
}
|
|
7329
|
-
|
|
7330
|
-
|
|
7331
|
-
|
|
7332
|
-
|
|
7333
|
-
|
|
7334
|
-
typeImports.push({ specifier: reg.valueTypeImport, name: reg.valueType });
|
|
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}'`);
|
|
7335
7433
|
}
|
|
7336
|
-
|
|
7337
|
-
|
|
7338
|
-
|
|
7339
|
-
|
|
7340
|
-
|
|
7341
|
-
|
|
7342
|
-
|
|
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;
|
|
7343
7474
|
}
|
|
7344
|
-
|
|
7345
|
-
|
|
7346
|
-
|
|
7347
|
-
|
|
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
|
|
7348
7499
|
);
|
|
7500
|
+
return `{ ${entries.map(([k, v]) => `${k}: ${jsonToTs(v)}`).join(", ")} }`;
|
|
7349
7501
|
}
|
|
7350
|
-
|
|
7351
|
-
return lines.join("\n");
|
|
7502
|
+
return "undefined";
|
|
7352
7503
|
}
|
|
7353
|
-
function
|
|
7354
|
-
const
|
|
7355
|
-
|
|
7356
|
-
|
|
7357
|
-
for (const e of reg.entries) {
|
|
7358
|
-
providerImports.push({ specifier: e.providerImport, name: e.provider });
|
|
7359
|
-
}
|
|
7504
|
+
function quoteBullmqDomainOpts(input) {
|
|
7505
|
+
const { backend, multiTenant, bullExt } = input;
|
|
7506
|
+
if (backend !== "bullmq" || !bullExt) {
|
|
7507
|
+
return quoteOpts({ backend, multiTenant });
|
|
7360
7508
|
}
|
|
7361
|
-
const
|
|
7362
|
-
|
|
7363
|
-
|
|
7364
|
-
|
|
7365
|
-
|
|
7366
|
-
|
|
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}]`;
|
|
7367
7519
|
}
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
7372
|
-
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
|
|
7377
|
-
|
|
7378
|
-
|
|
7379
|
-
|
|
7380
|
-
|
|
7381
|
-
|
|
7382
|
-
|
|
7383
|
-
|
|
7384
|
-
|
|
7385
|
-
|
|
7386
|
-
|
|
7387
|
-
|
|
7388
|
-
|
|
7389
|
-
|
|
7390
|
-
|
|
7391
|
-
|
|
7392
|
-
|
|
7393
|
-
|
|
7394
|
-
|
|
7395
|
-
|
|
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")}';`
|
|
7396
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(", ")} }),`);
|
|
7397
7571
|
}
|
|
7398
|
-
|
|
7399
|
-
|
|
7400
|
-
|
|
7401
|
-
|
|
7402
|
-
|
|
7403
|
-
|
|
7404
|
-
|
|
7405
|
-
|
|
7406
|
-
|
|
7407
|
-
|
|
7408
|
-
|
|
7409
|
-
|
|
7410
|
-
|
|
7411
|
-
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
}
|
|
7415
|
-
function camelArg(provider, index2) {
|
|
7416
|
-
const camel = provider.charAt(0).toLowerCase() + provider.slice(1);
|
|
7417
|
-
return `${camel}_${index2}`;
|
|
7418
|
-
}
|
|
7419
|
-
function buildDispatcherTs(pattern) {
|
|
7420
|
-
const registries = allRegistries(pattern);
|
|
7421
|
-
const className = dispatcherClassName(pattern);
|
|
7422
|
-
const errorClass = `${className}Error`;
|
|
7423
|
-
const typeImports = [];
|
|
7424
|
-
for (const { reg } of registries) {
|
|
7425
|
-
typeImports.push({ specifier: reg.keyTypeImport, name: reg.keyType });
|
|
7426
|
-
typeImports.push({ specifier: reg.valueTypeImport, name: reg.valueType });
|
|
7427
|
-
}
|
|
7428
|
-
const providerImports = [];
|
|
7429
|
-
for (const { reg } of registries) {
|
|
7430
|
-
for (const e of reg.entries) {
|
|
7431
|
-
providerImports.push({ specifier: e.providerImport, name: e.provider });
|
|
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
|
+
};
|
|
7432
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
|
+
};
|
|
7433
7607
|
}
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
lines.push(`import { Inject, Injectable } from '@nestjs/common';`);
|
|
7440
|
-
for (const i of emitTypeImports(typeImports)) lines.push(i);
|
|
7441
|
-
for (const i of emitValueImports(providerImports)) lines.push(i);
|
|
7442
|
-
const tokenLine = [
|
|
7443
|
-
...tokenValues.sort(),
|
|
7444
|
-
...mapTypes.sort().map((m) => `type ${m}`)
|
|
7445
|
-
].join(", ");
|
|
7446
|
-
lines.push(`import { ${tokenLine} } from './tokens.js';`);
|
|
7447
|
-
lines.push("");
|
|
7448
|
-
lines.push(`export class ${errorClass} extends Error {}`);
|
|
7449
|
-
lines.push("");
|
|
7450
|
-
lines.push("@Injectable()");
|
|
7451
|
-
lines.push(`export class ${className} {`);
|
|
7452
|
-
lines.push(" constructor(");
|
|
7453
|
-
for (const { names: names2 } of registries) {
|
|
7454
|
-
const fieldName = names2.method === "select" ? "registry" : `${camelFromMethod(names2.method)}Registry`;
|
|
7455
|
-
lines.push(
|
|
7456
|
-
` @Inject(${names2.tokenConst}) protected readonly ${fieldName}: ${names2.mapType},`
|
|
7457
|
-
);
|
|
7608
|
+
};
|
|
7609
|
+
var PACKAGE2 = "@pattern-stack/codegen";
|
|
7610
|
+
function makeModuleImport(mode, subsystemsRel) {
|
|
7611
|
+
if (mode === "vendored") {
|
|
7612
|
+
return (subsystem, moduleBasename) => `${subsystemsRel}/${subsystem}/${moduleBasename}`;
|
|
7458
7613
|
}
|
|
7459
|
-
|
|
7460
|
-
|
|
7461
|
-
|
|
7462
|
-
|
|
7463
|
-
|
|
7464
|
-
|
|
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;
|
|
7465
7645
|
}
|
|
7466
|
-
|
|
7467
|
-
|
|
7468
|
-
|
|
7469
|
-
|
|
7470
|
-
|
|
7471
|
-
);
|
|
7472
|
-
lines.push(` return entry;`);
|
|
7473
|
-
lines.push(" }");
|
|
7474
|
-
lines.push("");
|
|
7475
|
-
}
|
|
7476
|
-
if (pattern.dispatcher?.assemblySlot) {
|
|
7477
|
-
lines.push(
|
|
7478
|
-
` // ADR-032 Decision 5 \u2014 \`assemblySlot: '${pattern.dispatcher.assemblySlot}'\` is a`
|
|
7479
|
-
);
|
|
7480
|
-
lines.push(
|
|
7481
|
-
` // subclass contract, not emitted here. Consumers subclass and add the`
|
|
7482
|
-
);
|
|
7483
|
-
lines.push(
|
|
7484
|
-
` // \`${pattern.dispatcher.assemblySlot}(...)\` method themselves.`
|
|
7485
|
-
);
|
|
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);
|
|
7486
7651
|
}
|
|
7487
|
-
|
|
7488
|
-
|
|
7489
|
-
|
|
7490
|
-
}
|
|
7491
|
-
function camelFromMethod(method) {
|
|
7492
|
-
if (method.startsWith("select") && method.length > "select".length) {
|
|
7493
|
-
const tail = method.slice("select".length);
|
|
7494
|
-
return tail.charAt(0).toLowerCase() + tail.slice(1);
|
|
7652
|
+
for (const inst of actable) {
|
|
7653
|
+
if (!COMPOSABLE_ORDER.includes(inst.name) && !COMPOSERS[inst.name]) {
|
|
7654
|
+
skipped.push(inst.name);
|
|
7655
|
+
}
|
|
7495
7656
|
}
|
|
7496
|
-
|
|
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 };
|
|
7497
7664
|
}
|
|
7498
|
-
function
|
|
7499
|
-
const
|
|
7500
|
-
const
|
|
7501
|
-
const
|
|
7502
|
-
const
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
|
|
7507
|
-
|
|
7508
|
-
|
|
7509
|
-
const
|
|
7510
|
-
|
|
7511
|
-
|
|
7512
|
-
|
|
7513
|
-
|
|
7514
|
-
for (const i of emitTypeImports(typeImports)) lines.push(i);
|
|
7515
|
-
lines.push(`import { ${className} } from './dispatcher.js';`);
|
|
7516
|
-
lines.push(`import { build${pascal}RegistryProviders } from './registry.providers.js';`);
|
|
7517
|
-
lines.push(`import { ${tokenValues.sort().join(", ")} } from './tokens.js';`);
|
|
7518
|
-
lines.push("");
|
|
7519
|
-
lines.push(`export interface ${optsType} {`);
|
|
7520
|
-
for (const { reg, names: names2 } of registries) {
|
|
7521
|
-
lines.push(
|
|
7522
|
-
` ${names2.overrideField}?: Partial<Record<${reg.keyType}, ${reg.valueType}>>;`
|
|
7523
|
-
);
|
|
7524
|
-
}
|
|
7525
|
-
lines.push("}");
|
|
7526
|
-
lines.push("");
|
|
7527
|
-
lines.push("@Module({})");
|
|
7528
|
-
lines.push(`export class ${moduleName} {`);
|
|
7529
|
-
lines.push(` static forRoot(opts?: ${optsType}): DynamicModule {`);
|
|
7530
|
-
lines.push(" return {");
|
|
7531
|
-
lines.push(` module: ${moduleName},`);
|
|
7532
|
-
lines.push(
|
|
7533
|
-
` providers: [...build${pascal}RegistryProviders(opts), ${className}],`
|
|
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
|
|
7534
7681
|
);
|
|
7535
|
-
|
|
7536
|
-
|
|
7537
|
-
|
|
7538
|
-
|
|
7539
|
-
|
|
7540
|
-
|
|
7541
|
-
|
|
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
|
+
};
|
|
7542
7713
|
}
|
|
7543
|
-
|
|
7544
|
-
|
|
7545
|
-
|
|
7546
|
-
|
|
7547
|
-
|
|
7548
|
-
|
|
7549
|
-
|
|
7550
|
-
|
|
7551
|
-
|
|
7552
|
-
|
|
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`;
|
|
7553
7763
|
}
|
|
7554
|
-
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 = [];
|
|
7555
7768
|
const lines = [];
|
|
7556
|
-
|
|
7557
|
-
|
|
7558
|
-
|
|
7559
|
-
|
|
7560
|
-
|
|
7561
|
-
lines.push("");
|
|
7562
|
-
|
|
7563
|
-
}
|
|
7564
|
-
const slugs = patterns.map((p) => toKebabCase2(p.name)).sort();
|
|
7565
|
-
for (const slug of slugs) {
|
|
7566
|
-
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);
|
|
7567
7776
|
}
|
|
7568
|
-
lines.
|
|
7569
|
-
return
|
|
7570
|
-
}
|
|
7571
|
-
function buildPatternFiles(pattern, outputRoot) {
|
|
7572
|
-
assertEmittable(pattern);
|
|
7573
|
-
const slug = toKebabCase2(pattern.name);
|
|
7574
|
-
const outputDir = path11.join(outputRoot, slug);
|
|
7575
|
-
const make = (name, content) => ({
|
|
7576
|
-
name,
|
|
7577
|
-
outputPath: path11.join(outputDir, name),
|
|
7578
|
-
relativePath: path11.join(slug, name),
|
|
7579
|
-
content
|
|
7580
|
-
});
|
|
7581
|
-
const files = [
|
|
7582
|
-
make("tokens.ts", buildTokensTs(pattern)),
|
|
7583
|
-
make("registry.providers.ts", buildProvidersTs(pattern)),
|
|
7584
|
-
make("dispatcher.ts", buildDispatcherTs(pattern)),
|
|
7585
|
-
make("module.ts", buildModuleTs(pattern)),
|
|
7586
|
-
make("index.ts", buildIndexTs(pattern))
|
|
7587
|
-
];
|
|
7588
|
-
return {
|
|
7589
|
-
patternName: pattern.name,
|
|
7590
|
-
slug,
|
|
7591
|
-
outputDir,
|
|
7592
|
-
files
|
|
7593
|
-
};
|
|
7777
|
+
const body = lines.length === 0 ? "export {};\n" : lines.join("\n") + "\n";
|
|
7778
|
+
return { content: HEADER6 + body, emitted };
|
|
7594
7779
|
}
|
|
7595
|
-
function
|
|
7596
|
-
const {
|
|
7597
|
-
const
|
|
7598
|
-
const
|
|
7599
|
-
|
|
7600
|
-
|
|
7601
|
-
|
|
7602
|
-
|
|
7603
|
-
|
|
7604
|
-
|
|
7605
|
-
|
|
7606
|
-
|
|
7607
|
-
|
|
7608
|
-
|
|
7609
|
-
|
|
7610
|
-
|
|
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
|
+
);
|
|
7611
7796
|
let written = false;
|
|
7612
|
-
if (!dryRun
|
|
7613
|
-
fs8.mkdirSync(
|
|
7614
|
-
|
|
7615
|
-
fs8.mkdirSync(r.outputDir, { recursive: true });
|
|
7616
|
-
for (const f of r.files) {
|
|
7617
|
-
fs8.writeFileSync(f.outputPath, f.content);
|
|
7618
|
-
}
|
|
7619
|
-
}
|
|
7620
|
-
fs8.writeFileSync(rootBarrel.outputPath, rootBarrel.content);
|
|
7797
|
+
if (!dryRun) {
|
|
7798
|
+
fs8.mkdirSync(path11.dirname(barrelAbs), { recursive: true });
|
|
7799
|
+
fs8.writeFileSync(barrelAbs, content);
|
|
7621
7800
|
written = true;
|
|
7622
7801
|
}
|
|
7623
|
-
return {
|
|
7624
|
-
outputRoot,
|
|
7625
|
-
patterns: perPattern,
|
|
7626
|
-
files: allFiles,
|
|
7627
|
-
written
|
|
7628
|
-
};
|
|
7802
|
+
return { schemaBarrel: barrelAbs, emitted, content, written };
|
|
7629
7803
|
}
|
|
7630
7804
|
|
|
7631
|
-
// src/cli/shared/
|
|
7805
|
+
// src/cli/shared/orchestration-generator.ts
|
|
7632
7806
|
import fs9 from "fs";
|
|
7633
7807
|
import path12 from "path";
|
|
7634
|
-
|
|
7635
|
-
|
|
7636
|
-
|
|
7637
|
-
|
|
7638
|
-
|
|
7639
|
-
issues.push({
|
|
7640
|
-
severity: "error",
|
|
7641
|
-
type: "parse_error",
|
|
7642
|
-
message: error.error,
|
|
7643
|
-
path: error.filePath
|
|
7644
|
-
});
|
|
7645
|
-
if (error.details) {
|
|
7646
|
-
for (const detail of error.details) {
|
|
7647
|
-
issues.push({
|
|
7648
|
-
severity: "error",
|
|
7649
|
-
type: "schema_error",
|
|
7650
|
-
message: detail,
|
|
7651
|
-
path: error.filePath
|
|
7652
|
-
});
|
|
7653
|
-
}
|
|
7808
|
+
var OrchestrationEmissionError = class extends Error {
|
|
7809
|
+
constructor(issueType, patternName, message) {
|
|
7810
|
+
super(`[${issueType}] ${patternName}: ${message}`);
|
|
7811
|
+
this.issueType = issueType;
|
|
7812
|
+
this.patternName = patternName;
|
|
7654
7813
|
}
|
|
7655
|
-
|
|
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());
|
|
7656
7823
|
}
|
|
7657
|
-
function
|
|
7658
|
-
|
|
7659
|
-
if (base.endsWith(".yaml")) return base.slice(0, -".yaml".length);
|
|
7660
|
-
if (base.endsWith(".yml")) return base.slice(0, -".yml".length);
|
|
7661
|
-
return base;
|
|
7824
|
+
function toKebabCase2(str) {
|
|
7825
|
+
return splitWords(str).join("-");
|
|
7662
7826
|
}
|
|
7663
|
-
function
|
|
7664
|
-
|
|
7665
|
-
|
|
7666
|
-
|
|
7667
|
-
|
|
7668
|
-
|
|
7669
|
-
|
|
7670
|
-
|
|
7671
|
-
|
|
7672
|
-
|
|
7673
|
-
|
|
7674
|
-
|
|
7675
|
-
|
|
7676
|
-
|
|
7677
|
-
|
|
7678
|
-
|
|
7679
|
-
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
|
|
7685
|
-
|
|
7686
|
-
|
|
7687
|
-
|
|
7688
|
-
|
|
7689
|
-
|
|
7690
|
-
|
|
7691
|
-
|
|
7692
|
-
|
|
7693
|
-
|
|
7694
|
-
continue;
|
|
7695
|
-
}
|
|
7696
|
-
const { definition } = result;
|
|
7697
|
-
const baseName = stripYamlExt(filePath);
|
|
7698
|
-
if (baseName !== definition.type) {
|
|
7699
|
-
issues.push({
|
|
7700
|
-
severity: "error",
|
|
7701
|
-
type: "event_filename_mismatch",
|
|
7702
|
-
message: `Event file '${baseName}' must contain 'type: ${baseName}' (found 'type: ${definition.type}')`,
|
|
7703
|
-
path: filePath,
|
|
7704
|
-
suggestion: `Rename the file to '${definition.type}.yaml' or fix the 'type' field to '${baseName}'`
|
|
7705
|
-
});
|
|
7706
|
-
continue;
|
|
7707
|
-
}
|
|
7708
|
-
if (definition.direction === "change" && definition.aggregate !== void 0 && !entityNameSet.has(definition.aggregate)) {
|
|
7709
|
-
issues.push({
|
|
7710
|
-
severity: "error",
|
|
7711
|
-
type: "unknown_aggregate",
|
|
7712
|
-
message: `change event '${definition.type}' references unknown aggregate entity '${definition.aggregate}'`,
|
|
7713
|
-
path: filePath,
|
|
7714
|
-
suggestion: `Define entities/${definition.aggregate}.yaml or fix the aggregate value`
|
|
7715
|
-
});
|
|
7716
|
-
continue;
|
|
7717
|
-
}
|
|
7718
|
-
if (seenTypes.has(definition.type)) {
|
|
7719
|
-
issues.push({
|
|
7720
|
-
severity: "error",
|
|
7721
|
-
type: "duplicate_event_type",
|
|
7722
|
-
message: `Duplicate event type '${definition.type}' (already declared in ${seenTypes.get(definition.type)})`,
|
|
7723
|
-
path: filePath
|
|
7724
|
-
});
|
|
7725
|
-
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
|
+
);
|
|
7726
7858
|
}
|
|
7727
|
-
seenTypes.set(definition.type, filePath);
|
|
7728
|
-
events.push(definition);
|
|
7729
7859
|
}
|
|
7730
|
-
|
|
7731
|
-
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
|
|
7735
|
-
|
|
7736
|
-
|
|
7737
|
-
|
|
7738
|
-
|
|
7739
|
-
|
|
7740
|
-
|
|
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'.`
|
|
7741
7888
|
);
|
|
7742
7889
|
}
|
|
7743
|
-
payload[key] = { type: typeString, nullable: false };
|
|
7744
7890
|
}
|
|
7745
|
-
|
|
7746
|
-
type: ev.name,
|
|
7747
|
-
tier: "domain",
|
|
7748
|
-
direction: "change",
|
|
7749
|
-
aggregate: entityName,
|
|
7750
|
-
payload,
|
|
7751
|
-
retry: { attempts: 3, backoff: "exponential" },
|
|
7752
|
-
version: 1,
|
|
7753
|
-
pool: DIRECTION_TO_POOL.change
|
|
7754
|
-
};
|
|
7755
|
-
return def;
|
|
7756
|
-
});
|
|
7757
|
-
}
|
|
7758
|
-
function isEventFieldType(s) {
|
|
7759
|
-
return EVENT_FIELD_TYPES.includes(s);
|
|
7760
|
-
}
|
|
7761
|
-
|
|
7762
|
-
// src/cli/shared/event-codegen-generator.ts
|
|
7763
|
-
var HEADER7 = `// AUTO-GENERATED by @pattern-stack/codegen. Do not edit.
|
|
7764
|
-
// Run \`codegen entity new --all\` to refresh.
|
|
7765
|
-
`;
|
|
7766
|
-
function toCamelCase2(input) {
|
|
7767
|
-
const parts = input.split("_").filter(Boolean);
|
|
7768
|
-
if (parts.length === 0) return input;
|
|
7769
|
-
const [first, ...rest] = parts;
|
|
7770
|
-
return (first ?? "") + rest.map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
7891
|
+
}
|
|
7771
7892
|
}
|
|
7772
|
-
function
|
|
7773
|
-
|
|
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 };
|
|
7774
7898
|
}
|
|
7775
|
-
|
|
7776
|
-
|
|
7777
|
-
|
|
7778
|
-
|
|
7779
|
-
|
|
7780
|
-
|
|
7781
|
-
|
|
7782
|
-
|
|
7783
|
-
|
|
7784
|
-
|
|
7785
|
-
string: "z.string()",
|
|
7786
|
-
number: "z.number()",
|
|
7787
|
-
boolean: "z.boolean()",
|
|
7788
|
-
date: "z.coerce.date()",
|
|
7789
|
-
json: "z.record(z.unknown())"
|
|
7790
|
-
};
|
|
7791
|
-
function tsTypeForField(field) {
|
|
7792
|
-
let base;
|
|
7793
|
-
if (field.type === "array") {
|
|
7794
|
-
const itemType = field.items;
|
|
7795
|
-
base = `${TS_TYPE_BY_FIELD[itemType]}[]`;
|
|
7796
|
-
} else {
|
|
7797
|
-
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
|
+
};
|
|
7798
7909
|
}
|
|
7799
|
-
|
|
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
|
+
};
|
|
7800
7920
|
}
|
|
7801
|
-
function
|
|
7802
|
-
|
|
7803
|
-
if (field.type === "array") {
|
|
7804
|
-
const itemType = field.items;
|
|
7805
|
-
base = `z.array(${ZOD_EXPR_BY_FIELD[itemType]})`;
|
|
7806
|
-
} else {
|
|
7807
|
-
base = ZOD_EXPR_BY_FIELD[field.type];
|
|
7808
|
-
}
|
|
7809
|
-
return field.nullable ? `${base}.nullable()` : base;
|
|
7921
|
+
function dispatcherClassName(pattern) {
|
|
7922
|
+
return pattern.dispatcher?.className ?? `${toPascalCase3(pattern.name)}Dispatcher`;
|
|
7810
7923
|
}
|
|
7811
|
-
function
|
|
7812
|
-
return
|
|
7924
|
+
function moduleClassName(pattern) {
|
|
7925
|
+
return `${toPascalCase3(pattern.name)}OrchestrationModule`;
|
|
7813
7926
|
}
|
|
7814
|
-
function
|
|
7815
|
-
|
|
7816
|
-
|
|
7817
|
-
|
|
7818
|
-
|
|
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);
|
|
7819
7936
|
}
|
|
7820
|
-
const
|
|
7821
|
-
for (const
|
|
7822
|
-
const
|
|
7823
|
-
|
|
7824
|
-
try {
|
|
7825
|
-
events.push(...desugarEntityEvents(result.definition));
|
|
7826
|
-
} catch (err) {
|
|
7827
|
-
issues.push({
|
|
7828
|
-
severity: "error",
|
|
7829
|
-
type: "entity_event_desugar_failed",
|
|
7830
|
-
message: err instanceof Error ? err.message : String(err),
|
|
7831
|
-
path: filePath
|
|
7832
|
-
});
|
|
7833
|
-
}
|
|
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}';`);
|
|
7834
7941
|
}
|
|
7835
|
-
return
|
|
7942
|
+
return out;
|
|
7836
7943
|
}
|
|
7837
|
-
function
|
|
7838
|
-
const
|
|
7839
|
-
const
|
|
7840
|
-
|
|
7841
|
-
|
|
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);
|
|
7842
7950
|
}
|
|
7843
|
-
|
|
7844
|
-
|
|
7845
|
-
|
|
7846
|
-
|
|
7847
|
-
type: "event_merge_override",
|
|
7848
|
-
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`
|
|
7849
|
-
});
|
|
7850
|
-
}
|
|
7851
|
-
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}';`);
|
|
7852
7955
|
}
|
|
7853
|
-
|
|
7854
|
-
(a, b) => a.type.localeCompare(b.type)
|
|
7855
|
-
);
|
|
7856
|
-
return { events, issues };
|
|
7956
|
+
return out;
|
|
7857
7957
|
}
|
|
7858
|
-
function
|
|
7859
|
-
const
|
|
7860
|
-
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
7866
|
-
|
|
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
|
+
});
|
|
7867
7971
|
}
|
|
7868
|
-
|
|
7869
|
-
const { events: entitySugar, issues: sugarIssues } = collectEntityEvents(entitiesDir);
|
|
7870
|
-
const { events: merged, issues: mergeIssues } = mergeEvents(
|
|
7871
|
-
topLevelResult.events,
|
|
7872
|
-
entitySugar
|
|
7873
|
-
);
|
|
7874
|
-
const issues = [
|
|
7875
|
-
...topLevelResult.issues,
|
|
7876
|
-
...sugarIssues,
|
|
7877
|
-
...mergeIssues
|
|
7878
|
-
];
|
|
7879
|
-
return { events: merged, issues };
|
|
7972
|
+
return list;
|
|
7880
7973
|
}
|
|
7881
|
-
function
|
|
7882
|
-
const
|
|
7883
|
-
|
|
7884
|
-
|
|
7885
|
-
|
|
7886
|
-
|
|
7887
|
-
export type AppDomainEvent = never;
|
|
7888
|
-
|
|
7889
|
-
export type EventTypeName = string;
|
|
7890
|
-
export type EventOfType<T extends EventTypeName> = DomainEvent;
|
|
7891
|
-
export type PayloadOfType<T extends EventTypeName> = DomainEvent['payload'];
|
|
7892
|
-
`;
|
|
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 });
|
|
7893
7980
|
}
|
|
7894
|
-
const
|
|
7895
|
-
|
|
7896
|
-
|
|
7897
|
-
|
|
7898
|
-
|
|
7899
|
-
for (const
|
|
7900
|
-
const
|
|
7901
|
-
|
|
7902
|
-
|
|
7903
|
-
|
|
7904
|
-
|
|
7905
|
-
|
|
7906
|
-
|
|
7907
|
-
|
|
7908
|
-
|
|
7909
|
-
|
|
7910
|
-
|
|
7911
|
-
|
|
7912
|
-
|
|
7913
|
-
|
|
7914
|
-
|
|
7915
|
-
|
|
7916
|
-
|
|
7917
|
-
chunks.push(` /** ${field.description} */`);
|
|
7918
|
-
}
|
|
7919
|
-
chunks.push(` ${toCamelCase2(key)}: ${tsTypeForField(field)};`);
|
|
7920
|
-
}
|
|
7921
|
-
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 });
|
|
7922
8004
|
}
|
|
7923
|
-
chunks.push(`}`);
|
|
7924
|
-
chunks.push("");
|
|
7925
8005
|
}
|
|
7926
|
-
const
|
|
7927
|
-
|
|
7928
|
-
|
|
7929
|
-
|
|
7930
|
-
|
|
7931
|
-
|
|
7932
|
-
|
|
7933
|
-
);
|
|
7934
|
-
|
|
7935
|
-
|
|
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[] {`
|
|
7936
8030
|
);
|
|
7937
|
-
|
|
7938
|
-
|
|
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");
|
|
7939
8059
|
}
|
|
7940
|
-
function
|
|
7941
|
-
const
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
|
|
7945
|
-
|
|
7946
|
-
|
|
7947
|
-
|
|
7948
|
-
|
|
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 });
|
|
7949
8072
|
}
|
|
7950
|
-
const
|
|
7951
|
-
|
|
7952
|
-
|
|
7953
|
-
|
|
7954
|
-
chunks.push(`import type { EventTypeName } from './types';`);
|
|
7955
|
-
chunks.push("");
|
|
7956
|
-
for (const ev of sorted) {
|
|
7957
|
-
const schemaConst = `${toCamelCase2(ev.type)}PayloadSchema`;
|
|
7958
|
-
const payloadKeys = Object.keys(ev.payload).sort();
|
|
7959
|
-
if (payloadKeys.length === 0) {
|
|
7960
|
-
chunks.push(`export const ${schemaConst} = z.object({}).strict();`);
|
|
7961
|
-
chunks.push("");
|
|
7962
|
-
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 });
|
|
7963
8077
|
}
|
|
7964
|
-
|
|
7965
|
-
|
|
7966
|
-
|
|
7967
|
-
|
|
7968
|
-
|
|
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};`);
|
|
7969
8110
|
}
|
|
7970
|
-
|
|
7971
|
-
|
|
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("");
|
|
7972
8120
|
}
|
|
7973
|
-
|
|
7974
|
-
|
|
7975
|
-
|
|
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
|
+
);
|
|
7976
8131
|
}
|
|
7977
|
-
|
|
7978
|
-
|
|
7979
|
-
return
|
|
8132
|
+
lines.push("}");
|
|
8133
|
+
lines.push("");
|
|
8134
|
+
return lines.join("\n");
|
|
7980
8135
|
}
|
|
7981
|
-
|
|
7982
|
-
"
|
|
7983
|
-
|
|
7984
|
-
|
|
7985
|
-
" direction: 'inbound' | 'change' | 'outbound' | null;",
|
|
7986
|
-
" pool: 'events_inbound' | 'events_change' | 'events_outbound' | null;",
|
|
7987
|
-
" aggregate?: string;",
|
|
7988
|
-
" source?: string;",
|
|
7989
|
-
" destination?: string;",
|
|
7990
|
-
" version: number;",
|
|
7991
|
-
" retry: { attempts: number; backoff: 'linear' | 'exponential' };",
|
|
7992
|
-
"}"
|
|
7993
|
-
].join("\n");
|
|
7994
|
-
var REGISTRY_GETTER = [
|
|
7995
|
-
"export function getEventMetadata<T extends EventTypeName>(type: T): EventMetadata {",
|
|
7996
|
-
" const meta = eventRegistry[type];",
|
|
7997
|
-
" if (!meta) {",
|
|
7998
|
-
" throw new Error(`No registry entry for event type '${String(type)}' \u2014 declare events under events/*.yaml and re-run \\`codegen entity new --all\\`.`);",
|
|
7999
|
-
" }",
|
|
8000
|
-
" return meta;",
|
|
8001
|
-
"}"
|
|
8002
|
-
].join("\n");
|
|
8003
|
-
function buildRegistryContent(events) {
|
|
8004
|
-
const sorted = [...events].sort((a, b) => a.type.localeCompare(b.type));
|
|
8005
|
-
const chunks = [];
|
|
8006
|
-
chunks.push(HEADER7);
|
|
8007
|
-
chunks.push("");
|
|
8008
|
-
chunks.push(`import type { EventTypeName } from './types';`);
|
|
8009
|
-
chunks.push("");
|
|
8010
|
-
chunks.push(REGISTRY_INTERFACE);
|
|
8011
|
-
chunks.push("");
|
|
8012
|
-
if (sorted.length === 0) {
|
|
8013
|
-
chunks.push(
|
|
8014
|
-
`export const eventRegistry = {} as Record<EventTypeName, EventMetadata>;`
|
|
8015
|
-
);
|
|
8016
|
-
chunks.push("");
|
|
8017
|
-
chunks.push(REGISTRY_GETTER);
|
|
8018
|
-
chunks.push("");
|
|
8019
|
-
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);
|
|
8020
8140
|
}
|
|
8021
|
-
|
|
8022
|
-
|
|
8023
|
-
|
|
8024
|
-
|
|
8025
|
-
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
|
|
8029
|
-
|
|
8030
|
-
|
|
8031
|
-
|
|
8032
|
-
|
|
8033
|
-
|
|
8034
|
-
|
|
8035
|
-
|
|
8036
|
-
|
|
8037
|
-
|
|
8038
|
-
|
|
8039
|
-
|
|
8040
|
-
|
|
8041
|
-
|
|
8042
|
-
|
|
8043
|
-
|
|
8044
|
-
|
|
8045
|
-
|
|
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}>>;`
|
|
8046
8168
|
);
|
|
8047
|
-
chunks.push(` },`);
|
|
8048
8169
|
}
|
|
8049
|
-
|
|
8050
|
-
|
|
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}],`
|
|
8051
8179
|
);
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
8056
|
-
}
|
|
8057
|
-
|
|
8058
|
-
|
|
8059
|
-
import { EVENT_BUS, EVENTS_MULTI_TENANT } from '../events.tokens';
|
|
8060
|
-
import { MissingTenantIdError } from '../events-errors';
|
|
8061
|
-
import type { IEventBus, DrizzleTransaction } from '../event-bus.protocol';
|
|
8062
|
-
import { eventPayloadSchemas } from './schemas';
|
|
8063
|
-
import { getEventMetadata } from './registry';
|
|
8064
|
-
import type { EventTypeName, EventOfType, PayloadOfType } from './types';
|
|
8065
|
-
|
|
8066
|
-
/**
|
|
8067
|
-
* Typed facade over IEventBus.
|
|
8068
|
-
*
|
|
8069
|
-
* Stamps \`pool\`, \`direction\`, \`tier\`, and \`version\` into \`event.metadata\`
|
|
8070
|
-
* from the generated \`eventRegistry\` before delegating to
|
|
8071
|
-
* \`IEventBus.publish()\`. Downstream backends (DrizzleEventBus) read those
|
|
8072
|
-
* values to populate the explicit \`domain_events\` columns.
|
|
8073
|
-
*
|
|
8074
|
-
* Tier stamping (AUDIT-3): every event carries \`metadata.tier\`, sourced
|
|
8075
|
-
* from the registry. For \`tier: 'audit'\` events, the bus FORCES
|
|
8076
|
-
* \`metadata.pool = null\` and \`metadata.direction = null\` regardless of
|
|
8077
|
-
* any caller-supplied values in \`opts.metadata\` \u2014 audit routing is
|
|
8078
|
-
* bus-stamped, not caller-controlled. Caller overrides are silently
|
|
8079
|
-
* dropped with a debug-level log (callers should not be specifying these
|
|
8080
|
-
* for audit events; see ai-docs/specs/issue-242/plan.md \xA7AUDIT-3).
|
|
8081
|
-
*
|
|
8082
|
-
* Validation gating (EVT-Q5): \`CODEGEN_EVENT_VALIDATE\` env flag, default on.
|
|
8083
|
-
* Uses \`safeParse\` + \`console.warn\` \u2014 never throws, so a bad publish does
|
|
8084
|
-
* not crash a hot path.
|
|
8085
|
-
*
|
|
8086
|
-
* Multi-tenancy (EVT-6): when the EventsModule is configured with
|
|
8087
|
-
* \`multiTenant: true\`, every publish must supply \`opts.metadata.tenantId\`
|
|
8088
|
-
* \u2014 otherwise \`publish()\` throws \`MissingTenantIdError\`. When \`multiTenant\`
|
|
8089
|
-
* is \`false\` (default), no tenantId is required. If a tenantId IS supplied,
|
|
8090
|
-
* it is preserved on \`event.metadata\` and the Drizzle backend writes it to
|
|
8091
|
-
* \`domain_events.tenant_id\` (EVT-4).
|
|
8092
|
-
*/
|
|
8093
|
-
@Injectable()
|
|
8094
|
-
export class TypedEventBus {
|
|
8095
|
-
constructor(
|
|
8096
|
-
@Inject(EVENT_BUS) private readonly bus: IEventBus,
|
|
8097
|
-
@Inject(EVENTS_MULTI_TENANT) private readonly multiTenant: boolean,
|
|
8098
|
-
) {}
|
|
8099
|
-
|
|
8100
|
-
async publish<T extends EventTypeName>(
|
|
8101
|
-
type: T,
|
|
8102
|
-
aggregateId: string,
|
|
8103
|
-
payload: PayloadOfType<T>,
|
|
8104
|
-
opts?: { tx?: DrizzleTransaction; metadata?: Record<string, unknown> },
|
|
8105
|
-
): Promise<void> {
|
|
8106
|
-
const meta = getEventMetadata(type);
|
|
8107
|
-
|
|
8108
|
-
const flag = process.env['CODEGEN_EVENT_VALIDATE'];
|
|
8109
|
-
const shouldValidate =
|
|
8110
|
-
flag === undefined ? true : flag !== 'false' && flag !== '0';
|
|
8111
|
-
if (shouldValidate) {
|
|
8112
|
-
// \`eventPayloadSchemas\` is typed as \`Record<EventTypeName, z.ZodType>\`,
|
|
8113
|
-
// so under \`noUncheckedIndexedAccess\` the indexed lookup widens
|
|
8114
|
-
// to \`z.ZodType | undefined\`. When no events are registered at
|
|
8115
|
-
// codegen time \`EventTypeName\` degrades to \`string\` and the
|
|
8116
|
-
// schemas object is literally \`{}\` \u2014 the guard below is the
|
|
8117
|
-
// honest handling of that empty-registry case (skip validation;
|
|
8118
|
-
// it's a warn-only best-effort check per the class docblock).
|
|
8119
|
-
const schema = eventPayloadSchemas[type];
|
|
8120
|
-
if (schema) {
|
|
8121
|
-
const check = schema.safeParse(payload);
|
|
8122
|
-
if (!check.success) {
|
|
8123
|
-
console.warn(
|
|
8124
|
-
\`[TypedEventBus] payload validation failed for \${String(type)}:\`,
|
|
8125
|
-
check.error.issues,
|
|
8126
|
-
);
|
|
8127
|
-
}
|
|
8128
|
-
}
|
|
8129
|
-
}
|
|
8130
|
-
|
|
8131
|
-
const tenantId = opts?.metadata?.['tenantId'];
|
|
8132
|
-
if (this.multiTenant && (tenantId === undefined || tenantId === null)) {
|
|
8133
|
-
throw new MissingTenantIdError(type as string);
|
|
8134
|
-
}
|
|
8135
|
-
|
|
8136
|
-
const aggregateType =
|
|
8137
|
-
meta.aggregate ?? meta.source ?? meta.destination ?? (type as string);
|
|
8138
|
-
|
|
8139
|
-
// AUDIT-3: build metadata with tier-aware routing stamping. For
|
|
8140
|
-
// \`tier: 'audit'\` events the bus FORCES pool/direction to null,
|
|
8141
|
-
// even if the caller supplied them in opts.metadata. Audit routing
|
|
8142
|
-
// is bus-stamped, not caller-controlled (see plan \xA7AUDIT-3).
|
|
8143
|
-
const baseMetadata: Record<string, unknown> = { ...(opts?.metadata ?? {}) };
|
|
8144
|
-
if (meta.tier === 'audit') {
|
|
8145
|
-
if (
|
|
8146
|
-
baseMetadata['pool'] !== undefined ||
|
|
8147
|
-
baseMetadata['direction'] !== undefined
|
|
8148
|
-
) {
|
|
8149
|
-
console.debug(
|
|
8150
|
-
\`[TypedEventBus] tier:audit event '\${String(type)}' had pool/direction in opts.metadata; overriding to null.\`,
|
|
8151
|
-
);
|
|
8152
|
-
}
|
|
8153
|
-
baseMetadata['pool'] = null;
|
|
8154
|
-
baseMetadata['direction'] = null;
|
|
8155
|
-
baseMetadata['tier'] = 'audit';
|
|
8156
|
-
} else {
|
|
8157
|
-
baseMetadata['pool'] = meta.pool;
|
|
8158
|
-
baseMetadata['direction'] = meta.direction;
|
|
8159
|
-
baseMetadata['tier'] = 'domain';
|
|
8160
|
-
}
|
|
8161
|
-
baseMetadata['version'] = meta.version;
|
|
8162
|
-
|
|
8163
|
-
await this.bus.publish(
|
|
8164
|
-
{
|
|
8165
|
-
id: randomUUID(),
|
|
8166
|
-
type,
|
|
8167
|
-
aggregateId,
|
|
8168
|
-
aggregateType,
|
|
8169
|
-
payload: payload as Record<string, unknown>,
|
|
8170
|
-
occurredAt: new Date(),
|
|
8171
|
-
metadata: baseMetadata,
|
|
8172
|
-
},
|
|
8173
|
-
opts?.tx,
|
|
8174
|
-
);
|
|
8175
|
-
}
|
|
8176
|
-
|
|
8177
|
-
subscribe<T extends EventTypeName>(
|
|
8178
|
-
type: T,
|
|
8179
|
-
handler: (event: EventOfType<T>) => Promise<void>,
|
|
8180
|
-
): () => void {
|
|
8181
|
-
return this.bus.subscribe<EventOfType<T>>(type, handler as never);
|
|
8182
|
-
}
|
|
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");
|
|
8183
8187
|
}
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
|
|
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");
|
|
8187
8198
|
}
|
|
8188
|
-
function
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
|
|
8192
|
-
|
|
8193
|
-
|
|
8194
|
-
|
|
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");
|
|
8195
8215
|
}
|
|
8196
|
-
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
"index.ts"
|
|
8202
|
-
];
|
|
8203
|
-
async function generateEventCodegen(opts) {
|
|
8204
|
-
const { entitiesDir, eventsDir, outputDir, dryRun = false } = opts;
|
|
8205
|
-
const { events: merged, issues } = collectMergedEvents({
|
|
8206
|
-
entitiesDir,
|
|
8207
|
-
eventsDir
|
|
8208
|
-
});
|
|
8209
|
-
const builders = {
|
|
8210
|
-
"types.ts": buildTypesContent,
|
|
8211
|
-
"schemas.ts": buildSchemasContent,
|
|
8212
|
-
"registry.ts": buildRegistryContent,
|
|
8213
|
-
"bus.ts": buildBusContent,
|
|
8214
|
-
"index.ts": buildIndexContent
|
|
8215
|
-
};
|
|
8216
|
-
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) => ({
|
|
8217
8221
|
name,
|
|
8218
8222
|
outputPath: path12.join(outputDir, name),
|
|
8219
|
-
|
|
8220
|
-
|
|
8221
|
-
|
|
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);
|
|
8222
8256
|
let written = false;
|
|
8223
|
-
if (!dryRun &&
|
|
8224
|
-
fs9.mkdirSync(
|
|
8225
|
-
for (const
|
|
8226
|
-
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
|
+
}
|
|
8227
8264
|
}
|
|
8265
|
+
fs9.writeFileSync(rootBarrel.outputPath, rootBarrel.content);
|
|
8228
8266
|
written = true;
|
|
8229
8267
|
}
|
|
8230
8268
|
return {
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
written,
|
|
8236
|
-
files
|
|
8269
|
+
outputRoot,
|
|
8270
|
+
patterns: perPattern,
|
|
8271
|
+
files: allFiles,
|
|
8272
|
+
written
|
|
8237
8273
|
};
|
|
8238
8274
|
}
|
|
8239
8275
|
|
|
@@ -9779,16 +9815,10 @@ var EntityNewCommand = class extends Command2 {
|
|
|
9779
9815
|
const generatedDir = resolveGeneratedDir(ctx);
|
|
9780
9816
|
const architecture = resolveArchitecture(ctx);
|
|
9781
9817
|
const subsystemsRoot = resolveSubsystemsRoot(ctx);
|
|
9782
|
-
const scopeEntityTypePath = path14.resolve(
|
|
9783
|
-
subsystemsRoot,
|
|
9784
|
-
"jobs/generated/scope-entity-type.ts"
|
|
9785
|
-
);
|
|
9786
|
-
const eventsDir = resolveEventsDir(ctx);
|
|
9787
|
-
const eventCodegenOutputDir = path14.resolve(
|
|
9788
|
-
subsystemsRoot,
|
|
9789
|
-
"events/generated"
|
|
9790
|
-
);
|
|
9791
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");
|
|
9820
|
+
const eventsDir = resolveEventsDir(ctx);
|
|
9821
|
+
const eventCodegenOutputDir = runtimeMode === "package" ? path14.resolve(generatedDir, "events") : path14.resolve(subsystemsRoot, "events/generated");
|
|
9792
9822
|
const bridgeInstalledForRegistry = configuredSubsystemNames(
|
|
9793
9823
|
ctx.config
|
|
9794
9824
|
).includes("bridge");
|
|
@@ -9838,6 +9868,7 @@ var EntityNewCommand = class extends Command2 {
|
|
|
9838
9868
|
entitiesDir,
|
|
9839
9869
|
eventsDir,
|
|
9840
9870
|
outputDir: eventCodegenOutputDir,
|
|
9871
|
+
mode: runtimeMode,
|
|
9841
9872
|
dryRun: true
|
|
9842
9873
|
});
|
|
9843
9874
|
const bridgeRegistryPlan = await generateBridgeRegistry({
|
|
@@ -10018,7 +10049,8 @@ var EntityNewCommand = class extends Command2 {
|
|
|
10018
10049
|
eventCodegenResult = await generateEventCodegen({
|
|
10019
10050
|
entitiesDir,
|
|
10020
10051
|
eventsDir,
|
|
10021
|
-
outputDir: eventCodegenOutputDir
|
|
10052
|
+
outputDir: eventCodegenOutputDir,
|
|
10053
|
+
mode: runtimeMode
|
|
10022
10054
|
});
|
|
10023
10055
|
if (!isJsonMode()) {
|
|
10024
10056
|
for (const issue of eventCodegenResult.issues) {
|