(
services: Record<string, { dispatchName?: string }> = SERVICES,
)
| 2273 | * Accepts an injected map for testing; defaults to the real SERVICES map. |
| 2274 | */ |
| 2275 | export function assertDispatchNamesUnique( |
| 2276 | services: Record<string, { dispatchName?: string }> = SERVICES, |
| 2277 | ): void { |
| 2278 | const seen = new Map<string, string>(); // dispatchName -> first ssotKey |
| 2279 | const problems: string[] = []; |
| 2280 | for (const [key, entry] of Object.entries(services)) { |
| 2281 | const dn = entry.dispatchName; |
| 2282 | if (typeof dn !== "string" || dn.length === 0) continue; |
| 2283 | const prior = seen.get(dn); |
| 2284 | if (prior !== undefined) { |
| 2285 | problems.push( |
| 2286 | ` - duplicate dispatchName "${dn}" on SSOT keys: ${prior}, ${key}`, |
| 2287 | ); |
| 2288 | } else { |
| 2289 | seen.set(dn, key); |
| 2290 | } |
| 2291 | // Own-property lookup (same rationale as elsewhere in this file): an |
| 2292 | // inherited Object.prototype key must not register as a collision. |
| 2293 | if (dn !== key && Object.hasOwn(services, dn)) { |
| 2294 | problems.push( |
| 2295 | ` - dispatchName "${dn}" on SSOT key "${key}" equals a DIFFERENT entry's SSOT key — resolveTargetServices resolves SSOT keys first, so this dispatch_name would silently misroute to "${dn}"`, |
| 2296 | ); |
| 2297 | } |
| 2298 | } |
| 2299 | if (problems.length > 0) { |
| 2300 | throw new Error( |
| 2301 | `railway-envs SSOT invariant violated:\n${problems.join("\n")}\n` + |
| 2302 | `Fix: each Railway service must have a unique dispatchName that ` + |
| 2303 | `does not collide with another entry's SSOT key ` + |
| 2304 | `(or no dispatchName at all for out-of-band services).`, |
| 2305 | ); |
| 2306 | } |
| 2307 | } |
| 2308 | |
| 2309 | /** |
| 2310 | * Throw on SSOT load if any `imageOf` is mis-wired. A dangling target, |
no test coverage detected
searching dependent graphs…