@open-mercato/shared 0.7.1-develop.7154.1.981330c924 → 0.7.1-develop.7171.1.9b31dbba45
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/AGENTS.md +1 -1
- package/dist/lib/bootstrap/dynamicLoader.js +62 -4
- package/dist/lib/bootstrap/dynamicLoader.js.map +2 -2
- package/dist/lib/crud/enricher-registry.js +1 -1
- package/dist/lib/crud/enricher-registry.js.map +2 -2
- package/dist/lib/crud/enricher-runner.js +98 -29
- package/dist/lib/crud/enricher-runner.js.map +2 -2
- package/dist/lib/crud/factory.js +22 -1
- package/dist/lib/crud/factory.js.map +3 -3
- package/dist/lib/db/pg-errors.js +38 -0
- package/dist/lib/db/pg-errors.js.map +2 -2
- package/dist/lib/number.js +67 -1
- package/dist/lib/number.js.map +2 -2
- package/dist/lib/version.js +1 -1
- package/dist/lib/version.js.map +1 -1
- package/package.json +2 -2
- package/src/lib/__tests__/number.test.ts +91 -1
- package/src/lib/bootstrap/__tests__/dynamicLoader.appModuleOverrides.test.ts +279 -0
- package/src/lib/bootstrap/dynamicLoader.ts +105 -5
- package/src/lib/crud/__tests__/crud-factory.test.ts +23 -0
- package/src/lib/crud/__tests__/enricher-registry.test.ts +16 -0
- package/src/lib/crud/__tests__/enricher-runner.read-through-cache.test.ts +291 -0
- package/src/lib/crud/__tests__/enricher-runner.test.ts +59 -0
- package/src/lib/crud/enricher-registry.ts +2 -1
- package/src/lib/crud/enricher-runner.ts +171 -30
- package/src/lib/crud/factory.ts +30 -1
- package/src/lib/crud/response-enricher.ts +26 -2
- package/src/lib/db/__tests__/pg-errors.test.ts +59 -1
- package/src/lib/db/pg-errors.ts +57 -0
- package/src/lib/number.ts +103 -0
package/AGENTS.md
CHANGED
|
@@ -52,7 +52,7 @@ yarn workspace @open-mercato/shared build
|
|
|
52
52
|
| `indexers/` | When building query index helpers | `@open-mercato/shared/lib/indexers` |
|
|
53
53
|
| `logger/` | When emitting diagnostics — `createLogger(namespace)` instead of raw `console.*` (migrate incrementally, Boy Scout rule). Message-first with structured fields (`logger.warn('Payload too large', { event, maxBytes })`), errors under `err`, `child(bindings)` for context, `getLogLevel()`/`isLevelEnabled()` to gate expensive fields; level via `OM_LOG_LEVEL`. Never log credentials, PII, or payload bodies | `@open-mercato/shared/lib/logger` |
|
|
54
54
|
| `modules/` | When registering or listing modules; `onModulesRegistered(listener)` subscribes to (re-)registrations so a cache derived from the module list can drop what it built from an incomplete one — bootstrap may register an i18n-only set before the full module list merges in, and listeners fire only when the registered set actually changed, so nothing is added to the request path. Its governing contract — notification timing, fail-soft handling of a throwing or rejecting listener, snapshot-based change detection, listener lifetime under HMR, and the globals a test MUST clear — is [`.ai/specs/2026-08-12-module-registry-registration-listeners.md`](../../.ai/specs/2026-08-12-module-registry-registration-listeners.md); `surfaceFingerprint` gives a deploy-time hash of the enabled modules, their declared ACL features, and the backend route manifest — mix it into any cache key whose payload is derived from those (no DB write exists to tag-invalidate on, so an omitted fingerprint serves the pre-deploy payload forever). It cannot see React-element fields such as a route `icon`, so callers MUST still pass a `ttl` | `@open-mercato/shared/lib/modules/registry`, `@open-mercato/shared/lib/modules/surfaceFingerprint` |
|
|
55
|
-
| `number.ts` | When parsing numeric strings from env/query params with a fallback and optional min/integer constraint | `@open-mercato/shared/lib/number` |
|
|
55
|
+
| `number.ts` | When parsing numeric strings from env/query params with a fallback and optional min/integer constraint (`parseNumberWithDefault`), or when parsing a number a USER TYPED, which carries the application locale's decimal/group separators (`parseLocaleNumber`, returns `null` — never a silent `0` — on unparseable input). MUST NOT run API/DB values through `parseLocaleNumber`; those are already numbers | `@open-mercato/shared/lib/number` |
|
|
56
56
|
| `openapi/` | When generating CRUD OpenAPI specs | `@open-mercato/shared/lib/openapi/crud` |
|
|
57
57
|
| `profiler/` | When profiling with `OM_PROFILE` env flag | `@open-mercato/shared/lib/profiler` |
|
|
58
58
|
| `search/` | When resolving record ids from the `search_tokens` index — MUST use instead of hand-rolling the Kysely lookup, and MUST be unioned into (or replace) any `$ilike` filter on a column an encryption map covers | `@open-mercato/shared/lib/search/tokenLookup` |
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { findAppRoot } from "./appResolver.js";
|
|
2
2
|
import { registerEntityIds } from "../encryption/entityIds.js";
|
|
3
3
|
import { createLogger } from "../logger/index.js";
|
|
4
|
+
import {
|
|
5
|
+
applyModuleOverridesFromEnabledModules
|
|
6
|
+
} from "../../modules/overrides.js";
|
|
4
7
|
import {
|
|
5
8
|
ensureMikroOrmV7GeneratedCacheCompatibility,
|
|
6
9
|
recoverMikroOrmV7GeneratedCacheFromImportError
|
|
@@ -360,6 +363,58 @@ async function loadAppDiRegistrar(appDir) {
|
|
|
360
363
|
return null;
|
|
361
364
|
}
|
|
362
365
|
}
|
|
366
|
+
const OPTIONAL_OVERRIDE_APPLIER_MODULES = {
|
|
367
|
+
ai: "@open-mercato/ai-assistant/modules/ai_assistant/lib/ai-overrides"
|
|
368
|
+
};
|
|
369
|
+
async function ensureOptionalOverrideAppliers(enabledModules) {
|
|
370
|
+
for (const [domain, specifier] of Object.entries(OPTIONAL_OVERRIDE_APPLIER_MODULES)) {
|
|
371
|
+
const declared = enabledModules.some((entry) => {
|
|
372
|
+
const overrides = entry?.overrides;
|
|
373
|
+
return Boolean(overrides && overrides[domain]);
|
|
374
|
+
});
|
|
375
|
+
if (!declared) continue;
|
|
376
|
+
try {
|
|
377
|
+
await import(
|
|
378
|
+
/* webpackIgnore: true */
|
|
379
|
+
/* turbopackIgnore: true */
|
|
380
|
+
specifier
|
|
381
|
+
);
|
|
382
|
+
} catch (error) {
|
|
383
|
+
logger.debug("Optional override applier module is not installed; the domain has nothing to apply to", {
|
|
384
|
+
domain,
|
|
385
|
+
specifier,
|
|
386
|
+
err: error
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
async function loadAppModuleOverrides(appDir) {
|
|
392
|
+
const tsPath = path.join(appDir, "src", "modules.ts");
|
|
393
|
+
if (!fs.existsSync(tsPath)) {
|
|
394
|
+
logger.debug("App-level modules file not present, skipping entry.overrides dispatch", { filePath: tsPath });
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
let enabledModules;
|
|
398
|
+
try {
|
|
399
|
+
const appModulesModule = await compileAndImport(tsPath, {
|
|
400
|
+
appRoot: appDir,
|
|
401
|
+
outFile: path.join(appDir, ".mercato", "generated", "app-modules-overrides.compiled.mjs")
|
|
402
|
+
});
|
|
403
|
+
enabledModules = appModulesModule.enabledModules;
|
|
404
|
+
} catch (error) {
|
|
405
|
+
throw new Error(
|
|
406
|
+
`[internal] Failed to load the app-level modules file (${tsPath}); entry.overrides cannot be applied. Refusing to bootstrap with a partial override set.`,
|
|
407
|
+
{ cause: error }
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
if (!Array.isArray(enabledModules)) {
|
|
411
|
+
throw new Error(
|
|
412
|
+
`[internal] The app-level modules file (${tsPath}) exports no enabledModules array; entry.overrides cannot be applied. Refusing to bootstrap with a partial override set.`
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
await ensureOptionalOverrideAppliers(enabledModules);
|
|
416
|
+
applyModuleOverridesFromEnabledModules(enabledModules);
|
|
417
|
+
}
|
|
363
418
|
async function loadBootstrapDataWithActiveEsbuild(appRoot) {
|
|
364
419
|
const resolved = resolveAppRootOrThrow(appRoot);
|
|
365
420
|
const { generatedDir } = resolved;
|
|
@@ -414,10 +469,13 @@ async function loadBootstrapData(appRoot) {
|
|
|
414
469
|
async function bootstrapFromAppRoot(appRoot) {
|
|
415
470
|
const { createBootstrap, waitForAsyncRegistration } = await import("./factory.js");
|
|
416
471
|
const resolved = resolveAppRootOrThrow(appRoot);
|
|
417
|
-
const { data, appDiRegistrar } = await withEsbuildLifecycle(async () =>
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
472
|
+
const { data, appDiRegistrar } = await withEsbuildLifecycle(async () => {
|
|
473
|
+
await loadAppModuleOverrides(resolved.appDir);
|
|
474
|
+
return {
|
|
475
|
+
data: await loadBootstrapData(resolved.appDir),
|
|
476
|
+
appDiRegistrar: await loadAppDiRegistrar(resolved.appDir)
|
|
477
|
+
};
|
|
478
|
+
});
|
|
421
479
|
const bootstrap = createBootstrap(data, appDiRegistrar ? { appDiRegistrar } : {});
|
|
422
480
|
bootstrap();
|
|
423
481
|
await waitForAsyncRegistration();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/bootstrap/dynamicLoader.ts"],
|
|
4
|
-
"sourcesContent": ["import type { BootstrapData } from './types'\nimport type { AppDiRegistrar } from '../di/container'\nimport { findAppRoot, type AppRoot } from './appResolver'\nimport { registerEntityIds } from '../encryption/entityIds'\nimport { createLogger } from '../logger'\nimport {\n ensureMikroOrmV7GeneratedCacheCompatibility,\n recoverMikroOrmV7GeneratedCacheFromImportError,\n} from './generatedCacheRecovery'\nimport { CLIENT_ONLY_STUB_NAMESPACE, createClientOnlyStubPlugin } from './clientOnlyModules'\nimport path from 'node:path'\nimport fs from 'node:fs'\nimport crypto from 'node:crypto'\nimport { createRequire } from 'node:module'\nimport { pathToFileURL } from 'node:url'\n\nlet activeBootstrapLoads = 0\nlet esbuildRuntime: typeof import('esbuild') | null = null\nlet esbuildStopPromise: Promise<void> | null = null\n\nconst logger = createLogger('shared').child({ component: 'bootstrap' })\n\nasync function getEsbuildRuntime(): Promise<typeof import('esbuild')> {\n if (esbuildStopPromise) await esbuildStopPromise\n if (esbuildRuntime) return esbuildRuntime\n\n const loadedRuntime = await import('esbuild')\n esbuildRuntime ??= loadedRuntime\n return esbuildRuntime\n}\n\nasync function withEsbuildLifecycle<T>(load: () => Promise<T>): Promise<T> {\n activeBootstrapLoads += 1\n\n try {\n return await load()\n } finally {\n activeBootstrapLoads -= 1\n if (activeBootstrapLoads === 0 && esbuildRuntime) {\n // esbuild keeps a helper process alive after build(). Bootstrap compilation\n // is a bounded phase, so release it once every concurrent loader is done.\n // A later build() call transparently starts a fresh helper process.\n const runtimeToStop = esbuildRuntime\n esbuildRuntime = null\n const stopPromise = runtimeToStop.stop().catch((err) => {\n logger.warn('Failed to stop the bootstrap compiler service', { err })\n })\n esbuildStopPromise = stopPromise\n try {\n await stopPromise\n } finally {\n if (esbuildStopPromise === stopPromise) esbuildStopPromise = null\n }\n }\n }\n}\n\n/**\n * Thrown when an expected generated source file is absent.\n *\n * Optional registries treat this as the supported compatibility case (an app\n * that never generated the file), which is what makes it distinguishable from\n * a file that exists but fails to compile or import.\n */\nclass GeneratedFileNotFoundError extends Error {\n readonly filePath: string\n\n constructor(filePath: string) {\n super(`Generated file not found: ${filePath}`)\n this.name = 'GeneratedFileNotFoundError'\n this.filePath = filePath\n }\n}\n\n/**\n * esbuild plugins for the CLI bundle, in resolution order. The client-only stub must come\n * first so it wins over the alias and external plugins for `*.client` dynamic imports.\n *\n * Exported so the wiring itself is testable: a test that only exercises\n * `createClientOnlyStubPlugin` in isolation stays green if the plugin is dropped from this\n * list, which would silently reintroduce #4623.\n */\nexport function createCliBundlePlugins(appRoot: string): import('esbuild').Plugin[] {\n // Plugin to resolve the @/ alias the way the app tsconfig maps it:\n // `@/.mercato/*` to the app root, every other `@/*` to the app's src/ directory.\n const aliasPlugin: import('esbuild').Plugin = {\n name: 'alias-resolver',\n setup(build) {\n build.onResolve({ filter: /^@\\// }, (args) => {\n const rest = args.path.slice('@/'.length)\n const bases = rest.startsWith('.mercato/')\n ? [path.join(appRoot, rest)]\n : [path.join(appRoot, 'src', rest), path.join(appRoot, rest)]\n for (const base of bases) {\n if (fs.existsSync(base) && fs.statSync(base).isFile()) {\n return { path: base }\n }\n for (const suffix of ['.ts', '.tsx', '/index.ts', '/index.tsx']) {\n if (fs.existsSync(base + suffix)) {\n return { path: base + suffix }\n }\n }\n }\n // Nothing matched \u2014 hand esbuild the literal mapping so it reports the\n // missing file against the path the app author actually wrote.\n return { path: path.join(appRoot, rest) }\n })\n },\n }\n\n // Plugin to mark non-JSON package imports as external\n const externalNonJsonPlugin: import('esbuild').Plugin = {\n name: 'external-non-json',\n setup(build) {\n // Mark all package imports as external EXCEPT JSON files\n // Filter matches paths that don't start with . or / (package imports like @open-mercato/shared)\n build.onResolve({ filter: /^[^./]/ }, (args) => {\n // Skip Windows absolute paths (e.g., C:\\...) - they're local files, not packages\n if (/^[a-zA-Z]:/.test(args.path)) {\n return null // Let esbuild handle it\n }\n // If it's a JSON file, let esbuild bundle it\n if (args.path.endsWith('.json')) {\n return null // Let esbuild handle it\n }\n // Otherwise mark as external\n return { path: args.path, external: true }\n })\n },\n }\n\n return [createClientOnlyStubPlugin(), aliasPlugin, externalNonJsonPlugin]\n}\n\nconst DYNAMIC_LOADER_CACHE_VERSION = 4\n\ntype DynamicLoaderCacheMetadata = {\n version: number\n inputHash: string\n outputHash: string\n dependencies: Record<string, string>\n}\n\nfunction cacheMetadataPath(jsPath: string): string {\n return `${jsPath}.cache.json`\n}\n\nfunction contentHash(content: Buffer | string): string {\n return crypto.createHash('sha256').update(content).digest('hex')\n}\n\nfunction parseJsonConfig(content: string): unknown {\n let normalized = ''\n let inString = false\n let escaped = false\n\n for (let index = 0; index < content.length; index += 1) {\n const character = content[index]\n const nextCharacter = content[index + 1]\n\n if (inString) {\n normalized += character\n if (escaped) {\n escaped = false\n } else if (character === '\\\\') {\n escaped = true\n } else if (character === '\"') {\n inString = false\n }\n continue\n }\n\n if (character === '\"') {\n inString = true\n normalized += character\n continue\n }\n\n if (character === '/' && nextCharacter === '/') {\n while (index < content.length && content[index] !== '\\n') index += 1\n normalized += '\\n'\n continue\n }\n\n if (character === '/' && nextCharacter === '*') {\n index += 2\n while (index < content.length && !(content[index] === '*' && content[index + 1] === '/')) {\n index += 1\n }\n index += 1\n continue\n }\n\n if (character === ',') {\n let lookahead = index + 1\n while (lookahead < content.length && /\\s/.test(content[lookahead])) lookahead += 1\n if (content[lookahead] === '}' || content[lookahead] === ']') continue\n }\n\n normalized += character\n }\n\n return JSON.parse(normalized)\n}\n\nfunction resolveExistingConfigPath(candidate: string): string | null {\n for (const configPath of [candidate, `${candidate}.json`, path.join(candidate, 'tsconfig.json')]) {\n if (fs.existsSync(configPath) && fs.statSync(configPath).isFile()) return configPath\n }\n return null\n}\n\nfunction resolvePackageConfig(configPath: string, reference: string): string | null {\n try {\n const resolved = createRequire(pathToFileURL(configPath)).resolve(reference)\n return path.extname(resolved) === '.json' ? resolved : null\n } catch {\n return null\n }\n}\n\nfunction resolveExtendedConfig(configPath: string, reference: string): string {\n if (path.isAbsolute(reference) || reference.startsWith('.')) {\n const resolved = resolveExistingConfigPath(path.resolve(path.dirname(configPath), reference))\n if (resolved) return resolved\n } else {\n for (const packageReference of [reference, `${reference}/tsconfig.json`]) {\n const resolved = resolvePackageConfig(configPath, packageReference)\n if (resolved) return resolved\n }\n }\n\n throw new Error(`[internal] TypeScript config extends target not found: ${reference}`)\n}\n\nfunction collectTsconfigPaths(entryPath: string, visited: Set<string> = new Set()): string[] {\n const configPath = path.resolve(entryPath)\n if (visited.has(configPath)) return []\n visited.add(configPath)\n\n const parsed = parseJsonConfig(fs.readFileSync(configPath, 'utf8'))\n if (typeof parsed !== 'object' || parsed === null || !('extends' in parsed)) return [configPath]\n\n const extendsValue = parsed.extends\n const references = typeof extendsValue === 'string'\n ? [extendsValue]\n : Array.isArray(extendsValue) && extendsValue.every((value) => typeof value === 'string')\n ? extendsValue\n : []\n\n return [\n ...references.flatMap((reference) => collectTsconfigPaths(\n resolveExtendedConfig(configPath, reference),\n visited,\n )),\n configPath,\n ]\n}\n\nfunction hashFilesRelativeTo(appRoot: string, filePaths: string[]): Record<string, string> {\n return Object.fromEntries(filePaths.map((filePath) => [\n path.relative(appRoot, filePath).split(path.sep).join('/'),\n contentHash(fs.readFileSync(filePath)),\n ]))\n}\n\nfunction cacheInputHash(tsPath: string, appRoot: string, tsconfigPaths: string[]): string {\n const hash = crypto.createHash('sha256')\n hash.update(JSON.stringify({\n version: DYNAMIC_LOADER_CACHE_VERSION,\n sourceHash: contentHash(fs.readFileSync(tsPath)),\n tsconfigHashes: hashFilesRelativeTo(appRoot, tsconfigPaths),\n }))\n return hash.digest('hex')\n}\n\nfunction dependenciesAreValid(appRoot: string, dependencies: Record<string, string>): boolean {\n return Object.entries(dependencies).every(([relativePath, expectedHash]) => {\n const dependencyPath = path.resolve(appRoot, relativePath)\n return fs.existsSync(dependencyPath)\n && contentHash(fs.readFileSync(dependencyPath)) === expectedHash\n })\n}\n\nfunction collectDependencyHashes(\n appRoot: string,\n inputs: Record<string, unknown>,\n): Record<string, string> {\n return Object.fromEntries(\n Object.keys(inputs)\n .filter((inputPath) => !inputPath.startsWith(`${CLIENT_ONLY_STUB_NAMESPACE}:`))\n .map((inputPath) => {\n const absolutePath = path.isAbsolute(inputPath)\n ? inputPath\n : path.resolve(appRoot, inputPath)\n const relativePath = path.relative(appRoot, absolutePath).split(path.sep).join('/')\n return [relativePath, contentHash(fs.readFileSync(absolutePath))]\n })\n .sort(([left], [right]) => left.localeCompare(right)),\n )\n}\n\nfunction readCacheMetadata(metadataPath: string): DynamicLoaderCacheMetadata | null {\n try {\n const parsed: unknown = JSON.parse(fs.readFileSync(metadataPath, 'utf8'))\n if (\n typeof parsed === 'object'\n && parsed !== null\n && 'version' in parsed\n && parsed.version === DYNAMIC_LOADER_CACHE_VERSION\n && 'inputHash' in parsed\n && typeof parsed.inputHash === 'string'\n && 'outputHash' in parsed\n && typeof parsed.outputHash === 'string'\n && 'dependencies' in parsed\n && typeof parsed.dependencies === 'object'\n && parsed.dependencies !== null\n && Object.values(parsed.dependencies).every((hash) => typeof hash === 'string')\n ) {\n return {\n version: parsed.version,\n inputHash: parsed.inputHash,\n outputHash: parsed.outputHash,\n dependencies: parsed.dependencies as Record<string, string>,\n }\n }\n } catch {\n return null\n }\n return null\n}\n\nfunction cacheIsValid(\n appRoot: string,\n jsPath: string,\n metadataPath: string,\n expectedInputHash: string,\n): boolean {\n if (!fs.existsSync(jsPath)) return false\n const metadata = readCacheMetadata(metadataPath)\n if (!metadata || metadata.inputHash !== expectedInputHash) return false\n return contentHash(fs.readFileSync(jsPath)) === metadata.outputHash\n && dependenciesAreValid(appRoot, metadata.dependencies)\n}\n\n/**\n * Options for `compileAndImport`.\n *\n * Both paths default to the generated-registry layout (`<appRoot>/.mercato/generated/<file>.ts`\n * compiled to a `.mjs` sibling). Sources that live elsewhere in the app \u2014 `src/di.ts` \u2014 MUST pass\n * both explicitly: the default app root is derived by walking three directories up from the source,\n * which only holds inside `.mercato/generated`.\n */\ntype CompileAndImportOptions = {\n appRoot?: string\n outFile?: string\n allowRecovery?: boolean\n}\n\n/**\n * Options for `compileAppSourceFile`.\n *\n * `appRoot` anchors the tsconfig, the `@/` alias resolution and the dependency\n * cache; `outFile` is the absolute path of the artifact to write. `format`\n * selects the module system of that artifact \u2014 `'cjs'` exists for the Jest\n * runtime, which cannot `import()` an ESM sibling.\n */\nexport type CompileAppSourceOptions = {\n appRoot: string\n outFile: string\n format?: 'esm' | 'cjs'\n}\n\n/**\n * Compile one app-owned TypeScript source and its relative import graph into a\n * single JavaScript artifact, leaving every package import external.\n *\n * This is the only supported way to load app source (`apps/<app>/src/**`,\n * `.mercato/generated/**`) from a plain Node process. Those files are never\n * compiled to `dist`, and Node's own type stripping cannot load them: it\n * requires explicit file extensions on relative specifiers and rejects the\n * decorator and enum syntax the entities and DI files use.\n *\n * The artifact is cached against the content of the entry, its whole bundled\n * dependency graph, and the tsconfig chain, so an edit anywhere in the graph\n * invalidates it.\n *\n * The build runs inside the shared esbuild lifecycle. Callers outside a\n * bootstrap load \u2014 the generated-registry loader compiling an `@app` module \u2014\n * would otherwise hold a build on a service another scope is entitled to\n * `stop()`, and would leave the helper process running afterwards. Nesting is\n * safe: the scope only releases the service when the last participant exits.\n */\nexport async function compileAppSourceFile(\n tsPath: string,\n options: CompileAppSourceOptions,\n): Promise<string> {\n return withEsbuildLifecycle(() => compileAppSourceFileWithActiveEsbuild(tsPath, options))\n}\n\nasync function compileAppSourceFileWithActiveEsbuild(\n tsPath: string,\n options: CompileAppSourceOptions,\n): Promise<string> {\n const { appRoot, outFile } = options\n const format = options.format ?? 'esm'\n const appTsconfig = path.join(appRoot, 'tsconfig.json')\n const metadataPath = cacheMetadataPath(outFile)\n\n const tsExists = fs.existsSync(tsPath)\n const tsconfigExists = fs.existsSync(appTsconfig)\n\n if (!tsExists) {\n throw new GeneratedFileNotFoundError(tsPath)\n }\n if (!tsconfigExists) {\n throw new Error(`App TypeScript config not found: ${appTsconfig}`)\n }\n\n const tsconfigPaths = collectTsconfigPaths(appTsconfig)\n const expectedInputHash = cacheInputHash(tsPath, appRoot, tsconfigPaths)\n\n if (cacheIsValid(appRoot, outFile, metadataPath, expectedInputHash)) {\n return outFile\n }\n\n fs.mkdirSync(path.dirname(outFile), { recursive: true })\n // Dynamically import esbuild only when needed\n const esbuild = await getEsbuildRuntime()\n\n // Use esbuild.build with bundling to handle JSON imports\n const result = await esbuild.build({\n entryPoints: [tsPath],\n outfile: outFile,\n absWorkingDir: appRoot,\n bundle: true,\n metafile: true,\n format,\n platform: 'node',\n target: 'node18',\n tsconfig: appTsconfig,\n plugins: createCliBundlePlugins(appRoot),\n // Allow JSON imports\n loader: { '.json': 'json' },\n })\n const metadata: DynamicLoaderCacheMetadata = {\n version: DYNAMIC_LOADER_CACHE_VERSION,\n inputHash: expectedInputHash,\n outputHash: contentHash(fs.readFileSync(outFile)),\n dependencies: {\n ...collectDependencyHashes(appRoot, result.metafile.inputs),\n ...hashFilesRelativeTo(appRoot, tsconfigPaths),\n },\n }\n fs.writeFileSync(metadataPath, JSON.stringify(metadata))\n\n return outFile\n}\n\n/**\n * Compile a TypeScript file to JavaScript using esbuild bundler.\n * This bundles the file and all its dependencies, handling JSON imports properly.\n * The compiled file is written next to the source file with a .mjs extension unless\n * `outFile` says otherwise.\n */\nasync function compileAndImport(\n tsPath: string,\n options: CompileAndImportOptions = {},\n): Promise<Record<string, unknown>> {\n const allowRecovery = options.allowRecovery ?? true\n const jsPath = options.outFile ?? tsPath.replace(/\\.ts$/, '.mjs')\n const appRoot = options.appRoot ?? path.dirname(path.dirname(path.dirname(tsPath)))\n\n await compileAppSourceFile(tsPath, { appRoot, outFile: jsPath })\n\n // Import the compiled JavaScript\n try {\n const outputHash = contentHash(fs.readFileSync(jsPath))\n const fileUrl = `${pathToFileURL(jsPath).href}?cache=${outputHash}`\n return await import(fileUrl)\n } catch (error) {\n if (!allowRecovery) {\n throw error\n }\n\n const recovered = recoverMikroOrmV7GeneratedCacheFromImportError(appRoot, error)\n if (!recovered.applied) {\n throw error\n }\n\n return compileAndImport(tsPath, { ...options, allowRecovery: false })\n }\n}\n\n\n/**\n * Load a generated registry that older apps may not have generated yet.\n *\n * An absent source file is the supported compatibility case and resolves to\n * `fallback` quietly. Any other failure \u2014 a compile error, a broken import, a\n * runtime throw at module scope \u2014 still resolves to `fallback` so bootstrap\n * keeps working, but is reported at error level: a registry that silently\n * degrades to nothing is exactly how command interceptors stopped applying in\n * worker/CLI processes (#4327, #4491).\n */\nasync function loadOptionalGeneratedModule(\n tsPath: string,\n fallback: Record<string, unknown>,\n): Promise<Record<string, unknown>> {\n try {\n return await compileAndImport(tsPath)\n } catch (error) {\n if (error instanceof GeneratedFileNotFoundError) {\n logger.debug('Optional generated registry not present, using empty fallback', {\n file: path.basename(tsPath),\n })\n return fallback\n }\n\n logger.error('Failed to load generated registry, continuing without its entries', {\n file: path.basename(tsPath),\n filePath: tsPath,\n err: error,\n })\n return fallback\n }\n}\n\nfunction resolveAppRootOrThrow(appRoot?: string): AppRoot {\n const resolved: AppRoot | null = appRoot\n ? {\n generatedDir: path.join(appRoot, '.mercato', 'generated'),\n appDir: appRoot,\n mercatoDir: path.join(appRoot, '.mercato'),\n }\n : findAppRoot()\n\n if (!resolved) {\n throw new Error(\n 'Could not find app root with .mercato/generated directory. ' +\n 'Make sure you run this command from within a Next.js app directory, ' +\n 'or run \"yarn mercato generate\" first to create the generated files.',\n )\n }\n\n return resolved\n}\n\n/**\n * Load the app-level DI registrar (`src/di.ts`) for the dynamic bootstrap path.\n *\n * The Next.js runtime imports `@/di` statically from its own `src/bootstrap.ts` and hands the\n * registrar to `createBootstrap`. Worker, scheduler and CLI processes bootstrap through\n * `bootstrapFromAppRoot` instead, where the `@/` alias does not exist \u2014 so without this the app's\n * DI registrations silently never ran there, and every request container paid a failed\n * `import('@/di')` resolution (the compatibility fallback in `lib/di/container.ts`).\n *\n * An absent `src/di.ts` is the supported case and resolves to `null` quietly. A file that exists\n * but cannot be compiled, imported, or does not export `register` is reported at error level and\n * still resolves to `null`, so a broken app DI module degrades the same way a broken generated\n * registry does (#4327, #4491) instead of taking the whole process down.\n */\nasync function loadAppDiRegistrar(appDir: string): Promise<AppDiRegistrar | null> {\n const tsPath = path.join(appDir, 'src', 'di.ts')\n if (!fs.existsSync(tsPath)) {\n logger.debug('App-level DI module not present, skipping its registrations', { filePath: tsPath })\n return null\n }\n\n try {\n const appDiModule = await compileAndImport(tsPath, {\n appRoot: appDir,\n outFile: path.join(appDir, '.mercato', 'generated', 'app-di.compiled.mjs'),\n })\n const register = appDiModule.register\n if (typeof register !== 'function') {\n logger.error('App-level DI module exports no register(); its registrations are skipped', {\n filePath: tsPath,\n })\n return null\n }\n return register as AppDiRegistrar\n } catch (error) {\n logger.error('Failed to load the app-level DI module; its registrations are skipped', {\n filePath: tsPath,\n err: error,\n })\n return null\n }\n}\n\n/**\n * Dynamically load bootstrap data from a resolved app directory.\n *\n * IMPORTANT: This only works in unbundled contexts (CLI, tsx).\n * Do NOT use this in Next.js bundled code - use static imports instead.\n *\n * For CLI context, we skip loading modules.generated.ts which has Next.js dependencies.\n * CLI commands are discovered separately via the CLI module system.\n *\n * @param appRoot - Optional explicit app root path. If not provided, will search from cwd.\n * @returns The loaded bootstrap data\n * @throws Error if app root cannot be found or generated files are missing\n */\nasync function loadBootstrapDataWithActiveEsbuild(appRoot?: string): Promise<BootstrapData> {\n const resolved = resolveAppRootOrThrow(appRoot)\n\n const { generatedDir } = resolved\n\n ensureMikroOrmV7GeneratedCacheCompatibility(resolved.appDir)\n\n // IMPORTANT: Load entity IDs FIRST and register them before loading modules.\n // This is because modules (e.g., ce.ts files) use E.xxx.xxx at module scope,\n // and they need entity IDs to be available when they're imported.\n const entityIdsModule = await compileAndImport(path.join(generatedDir, 'entities.ids.generated.ts'))\n registerEntityIds(entityIdsModule.E as BootstrapData['entityIds'])\n\n // Now load the rest of the generated files.\n // modules.cli.generated.ts excludes Next.js-dependent code (routes, APIs, widgets)\n const [\n modulesModule,\n entitiesModule,\n diModule,\n searchModule,\n commandLoadersModule,\n commandInterceptorsModule,\n workflowsModule,\n ] = await Promise.all([\n compileAndImport(path.join(generatedDir, 'modules.cli.generated.ts')),\n compileAndImport(path.join(generatedDir, 'entities.generated.ts')),\n compileAndImport(path.join(generatedDir, 'di.generated.ts')),\n loadOptionalGeneratedModule(path.join(generatedDir, 'search.generated.ts'), { searchModuleConfigs: [] }),\n loadOptionalGeneratedModule(path.join(generatedDir, 'command-loaders.generated.ts'), { commandLoaderEntries: [] }),\n loadOptionalGeneratedModule(path.join(generatedDir, 'command-interceptors.generated.ts'), {\n commandInterceptorEntries: [],\n }),\n loadOptionalGeneratedModule(path.join(generatedDir, 'workflows.generated.ts'), { allCodeWorkflows: [] }),\n ])\n\n return {\n modules: modulesModule.modules as BootstrapData['modules'],\n entities: entitiesModule.entities as BootstrapData['entities'],\n diRegistrars: diModule.diRegistrars as BootstrapData['diRegistrars'],\n entityIds: entityIdsModule.E as BootstrapData['entityIds'],\n // Search configs are needed by workers for indexing\n searchModuleConfigs: (searchModule.searchModuleConfigs ?? []) as BootstrapData['searchModuleConfigs'],\n commandLoaderEntries: (commandLoadersModule.commandLoaderEntries ?? []) as BootstrapData['commandLoaderEntries'],\n // Command interceptors must apply in worker/CLI processes too \u2014 the\n // interceptor registry is per-process, so relying on the Next.js runtime's\n // registration silently no-ops every interceptor for queued/CLI commands\n // (#4327).\n commandInterceptorEntries: (commandInterceptorsModule.commandInterceptorEntries ??\n []) as BootstrapData['commandInterceptorEntries'],\n // Code workflow definitions are needed by workers to resume code-defined instances\n codeWorkflows: (workflowsModule.allCodeWorkflows ?? []) as BootstrapData['codeWorkflows'],\n // Empty UI-related data - not needed for CLI\n dashboardWidgetEntries: [],\n injectionWidgetEntries: [],\n injectionTables: [],\n interceptorEntries: [],\n componentOverrideEntries: [],\n }\n}\n\nexport async function loadBootstrapData(appRoot?: string): Promise<BootstrapData> {\n return withEsbuildLifecycle(() => loadBootstrapDataWithActiveEsbuild(appRoot))\n}\n\n/**\n * Create and execute bootstrap in CLI context.\n *\n * This is a convenience function that finds the app root, loads the generated\n * data dynamically, and runs bootstrap. Use this in CLI entry points.\n *\n * Returns the loaded bootstrap data so the CLI can register modules directly\n * (avoids module resolution issues when importing @open-mercato/cli/mercato).\n *\n * @param appRoot - Optional explicit app root path\n * @returns The loaded bootstrap data (modules, entities, etc.)\n */\nexport async function bootstrapFromAppRoot(appRoot?: string): Promise<BootstrapData> {\n const { createBootstrap, waitForAsyncRegistration } = await import('./factory.js')\n const resolved = resolveAppRootOrThrow(appRoot)\n // Both loads compile through esbuild, so they share one lifecycle scope: without it\n // `loadBootstrapData` releases the esbuild helper process and `loadAppDiRegistrar`\n // silently starts a second one that nothing ever stops.\n const { data, appDiRegistrar } = await withEsbuildLifecycle(async () => ({\n data: await loadBootstrapData(resolved.appDir),\n appDiRegistrar: await loadAppDiRegistrar(resolved.appDir),\n }))\n const bootstrap = createBootstrap(data, appDiRegistrar ? { appDiRegistrar } : {})\n bootstrap()\n // In CLI context, wait for async registrations (UI widgets, search configs, etc.)\n await waitForAsyncRegistration()\n\n return data\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import type { BootstrapData } from './types'\nimport type { AppDiRegistrar } from '../di/container'\nimport { findAppRoot, type AppRoot } from './appResolver'\nimport { registerEntityIds } from '../encryption/entityIds'\nimport { createLogger } from '../logger'\nimport {\n applyModuleOverridesFromEnabledModules,\n type ModuleEntryWithOverrides,\n} from '../../modules/overrides'\nimport {\n ensureMikroOrmV7GeneratedCacheCompatibility,\n recoverMikroOrmV7GeneratedCacheFromImportError,\n} from './generatedCacheRecovery'\nimport { CLIENT_ONLY_STUB_NAMESPACE, createClientOnlyStubPlugin } from './clientOnlyModules'\nimport path from 'node:path'\nimport fs from 'node:fs'\nimport crypto from 'node:crypto'\nimport { createRequire } from 'node:module'\nimport { pathToFileURL } from 'node:url'\n\nlet activeBootstrapLoads = 0\nlet esbuildRuntime: typeof import('esbuild') | null = null\nlet esbuildStopPromise: Promise<void> | null = null\n\nconst logger = createLogger('shared').child({ component: 'bootstrap' })\n\nasync function getEsbuildRuntime(): Promise<typeof import('esbuild')> {\n if (esbuildStopPromise) await esbuildStopPromise\n if (esbuildRuntime) return esbuildRuntime\n\n const loadedRuntime = await import('esbuild')\n esbuildRuntime ??= loadedRuntime\n return esbuildRuntime\n}\n\nasync function withEsbuildLifecycle<T>(load: () => Promise<T>): Promise<T> {\n activeBootstrapLoads += 1\n\n try {\n return await load()\n } finally {\n activeBootstrapLoads -= 1\n if (activeBootstrapLoads === 0 && esbuildRuntime) {\n // esbuild keeps a helper process alive after build(). Bootstrap compilation\n // is a bounded phase, so release it once every concurrent loader is done.\n // A later build() call transparently starts a fresh helper process.\n const runtimeToStop = esbuildRuntime\n esbuildRuntime = null\n const stopPromise = runtimeToStop.stop().catch((err) => {\n logger.warn('Failed to stop the bootstrap compiler service', { err })\n })\n esbuildStopPromise = stopPromise\n try {\n await stopPromise\n } finally {\n if (esbuildStopPromise === stopPromise) esbuildStopPromise = null\n }\n }\n }\n}\n\n/**\n * Thrown when an expected generated source file is absent.\n *\n * Optional registries treat this as the supported compatibility case (an app\n * that never generated the file), which is what makes it distinguishable from\n * a file that exists but fails to compile or import.\n */\nclass GeneratedFileNotFoundError extends Error {\n readonly filePath: string\n\n constructor(filePath: string) {\n super(`Generated file not found: ${filePath}`)\n this.name = 'GeneratedFileNotFoundError'\n this.filePath = filePath\n }\n}\n\n/**\n * esbuild plugins for the CLI bundle, in resolution order. The client-only stub must come\n * first so it wins over the alias and external plugins for `*.client` dynamic imports.\n *\n * Exported so the wiring itself is testable: a test that only exercises\n * `createClientOnlyStubPlugin` in isolation stays green if the plugin is dropped from this\n * list, which would silently reintroduce #4623.\n */\nexport function createCliBundlePlugins(appRoot: string): import('esbuild').Plugin[] {\n // Plugin to resolve the @/ alias the way the app tsconfig maps it:\n // `@/.mercato/*` to the app root, every other `@/*` to the app's src/ directory.\n const aliasPlugin: import('esbuild').Plugin = {\n name: 'alias-resolver',\n setup(build) {\n build.onResolve({ filter: /^@\\// }, (args) => {\n const rest = args.path.slice('@/'.length)\n const bases = rest.startsWith('.mercato/')\n ? [path.join(appRoot, rest)]\n : [path.join(appRoot, 'src', rest), path.join(appRoot, rest)]\n for (const base of bases) {\n if (fs.existsSync(base) && fs.statSync(base).isFile()) {\n return { path: base }\n }\n for (const suffix of ['.ts', '.tsx', '/index.ts', '/index.tsx']) {\n if (fs.existsSync(base + suffix)) {\n return { path: base + suffix }\n }\n }\n }\n // Nothing matched \u2014 hand esbuild the literal mapping so it reports the\n // missing file against the path the app author actually wrote.\n return { path: path.join(appRoot, rest) }\n })\n },\n }\n\n // Plugin to mark non-JSON package imports as external\n const externalNonJsonPlugin: import('esbuild').Plugin = {\n name: 'external-non-json',\n setup(build) {\n // Mark all package imports as external EXCEPT JSON files\n // Filter matches paths that don't start with . or / (package imports like @open-mercato/shared)\n build.onResolve({ filter: /^[^./]/ }, (args) => {\n // Skip Windows absolute paths (e.g., C:\\...) - they're local files, not packages\n if (/^[a-zA-Z]:/.test(args.path)) {\n return null // Let esbuild handle it\n }\n // If it's a JSON file, let esbuild bundle it\n if (args.path.endsWith('.json')) {\n return null // Let esbuild handle it\n }\n // Otherwise mark as external\n return { path: args.path, external: true }\n })\n },\n }\n\n return [createClientOnlyStubPlugin(), aliasPlugin, externalNonJsonPlugin]\n}\n\nconst DYNAMIC_LOADER_CACHE_VERSION = 4\n\ntype DynamicLoaderCacheMetadata = {\n version: number\n inputHash: string\n outputHash: string\n dependencies: Record<string, string>\n}\n\nfunction cacheMetadataPath(jsPath: string): string {\n return `${jsPath}.cache.json`\n}\n\nfunction contentHash(content: Buffer | string): string {\n return crypto.createHash('sha256').update(content).digest('hex')\n}\n\nfunction parseJsonConfig(content: string): unknown {\n let normalized = ''\n let inString = false\n let escaped = false\n\n for (let index = 0; index < content.length; index += 1) {\n const character = content[index]\n const nextCharacter = content[index + 1]\n\n if (inString) {\n normalized += character\n if (escaped) {\n escaped = false\n } else if (character === '\\\\') {\n escaped = true\n } else if (character === '\"') {\n inString = false\n }\n continue\n }\n\n if (character === '\"') {\n inString = true\n normalized += character\n continue\n }\n\n if (character === '/' && nextCharacter === '/') {\n while (index < content.length && content[index] !== '\\n') index += 1\n normalized += '\\n'\n continue\n }\n\n if (character === '/' && nextCharacter === '*') {\n index += 2\n while (index < content.length && !(content[index] === '*' && content[index + 1] === '/')) {\n index += 1\n }\n index += 1\n continue\n }\n\n if (character === ',') {\n let lookahead = index + 1\n while (lookahead < content.length && /\\s/.test(content[lookahead])) lookahead += 1\n if (content[lookahead] === '}' || content[lookahead] === ']') continue\n }\n\n normalized += character\n }\n\n return JSON.parse(normalized)\n}\n\nfunction resolveExistingConfigPath(candidate: string): string | null {\n for (const configPath of [candidate, `${candidate}.json`, path.join(candidate, 'tsconfig.json')]) {\n if (fs.existsSync(configPath) && fs.statSync(configPath).isFile()) return configPath\n }\n return null\n}\n\nfunction resolvePackageConfig(configPath: string, reference: string): string | null {\n try {\n const resolved = createRequire(pathToFileURL(configPath)).resolve(reference)\n return path.extname(resolved) === '.json' ? resolved : null\n } catch {\n return null\n }\n}\n\nfunction resolveExtendedConfig(configPath: string, reference: string): string {\n if (path.isAbsolute(reference) || reference.startsWith('.')) {\n const resolved = resolveExistingConfigPath(path.resolve(path.dirname(configPath), reference))\n if (resolved) return resolved\n } else {\n for (const packageReference of [reference, `${reference}/tsconfig.json`]) {\n const resolved = resolvePackageConfig(configPath, packageReference)\n if (resolved) return resolved\n }\n }\n\n throw new Error(`[internal] TypeScript config extends target not found: ${reference}`)\n}\n\nfunction collectTsconfigPaths(entryPath: string, visited: Set<string> = new Set()): string[] {\n const configPath = path.resolve(entryPath)\n if (visited.has(configPath)) return []\n visited.add(configPath)\n\n const parsed = parseJsonConfig(fs.readFileSync(configPath, 'utf8'))\n if (typeof parsed !== 'object' || parsed === null || !('extends' in parsed)) return [configPath]\n\n const extendsValue = parsed.extends\n const references = typeof extendsValue === 'string'\n ? [extendsValue]\n : Array.isArray(extendsValue) && extendsValue.every((value) => typeof value === 'string')\n ? extendsValue\n : []\n\n return [\n ...references.flatMap((reference) => collectTsconfigPaths(\n resolveExtendedConfig(configPath, reference),\n visited,\n )),\n configPath,\n ]\n}\n\nfunction hashFilesRelativeTo(appRoot: string, filePaths: string[]): Record<string, string> {\n return Object.fromEntries(filePaths.map((filePath) => [\n path.relative(appRoot, filePath).split(path.sep).join('/'),\n contentHash(fs.readFileSync(filePath)),\n ]))\n}\n\nfunction cacheInputHash(tsPath: string, appRoot: string, tsconfigPaths: string[]): string {\n const hash = crypto.createHash('sha256')\n hash.update(JSON.stringify({\n version: DYNAMIC_LOADER_CACHE_VERSION,\n sourceHash: contentHash(fs.readFileSync(tsPath)),\n tsconfigHashes: hashFilesRelativeTo(appRoot, tsconfigPaths),\n }))\n return hash.digest('hex')\n}\n\nfunction dependenciesAreValid(appRoot: string, dependencies: Record<string, string>): boolean {\n return Object.entries(dependencies).every(([relativePath, expectedHash]) => {\n const dependencyPath = path.resolve(appRoot, relativePath)\n return fs.existsSync(dependencyPath)\n && contentHash(fs.readFileSync(dependencyPath)) === expectedHash\n })\n}\n\nfunction collectDependencyHashes(\n appRoot: string,\n inputs: Record<string, unknown>,\n): Record<string, string> {\n return Object.fromEntries(\n Object.keys(inputs)\n .filter((inputPath) => !inputPath.startsWith(`${CLIENT_ONLY_STUB_NAMESPACE}:`))\n .map((inputPath) => {\n const absolutePath = path.isAbsolute(inputPath)\n ? inputPath\n : path.resolve(appRoot, inputPath)\n const relativePath = path.relative(appRoot, absolutePath).split(path.sep).join('/')\n return [relativePath, contentHash(fs.readFileSync(absolutePath))]\n })\n .sort(([left], [right]) => left.localeCompare(right)),\n )\n}\n\nfunction readCacheMetadata(metadataPath: string): DynamicLoaderCacheMetadata | null {\n try {\n const parsed: unknown = JSON.parse(fs.readFileSync(metadataPath, 'utf8'))\n if (\n typeof parsed === 'object'\n && parsed !== null\n && 'version' in parsed\n && parsed.version === DYNAMIC_LOADER_CACHE_VERSION\n && 'inputHash' in parsed\n && typeof parsed.inputHash === 'string'\n && 'outputHash' in parsed\n && typeof parsed.outputHash === 'string'\n && 'dependencies' in parsed\n && typeof parsed.dependencies === 'object'\n && parsed.dependencies !== null\n && Object.values(parsed.dependencies).every((hash) => typeof hash === 'string')\n ) {\n return {\n version: parsed.version,\n inputHash: parsed.inputHash,\n outputHash: parsed.outputHash,\n dependencies: parsed.dependencies as Record<string, string>,\n }\n }\n } catch {\n return null\n }\n return null\n}\n\nfunction cacheIsValid(\n appRoot: string,\n jsPath: string,\n metadataPath: string,\n expectedInputHash: string,\n): boolean {\n if (!fs.existsSync(jsPath)) return false\n const metadata = readCacheMetadata(metadataPath)\n if (!metadata || metadata.inputHash !== expectedInputHash) return false\n return contentHash(fs.readFileSync(jsPath)) === metadata.outputHash\n && dependenciesAreValid(appRoot, metadata.dependencies)\n}\n\n/**\n * Options for `compileAndImport`.\n *\n * Both paths default to the generated-registry layout (`<appRoot>/.mercato/generated/<file>.ts`\n * compiled to a `.mjs` sibling). Sources that live elsewhere in the app \u2014 `src/di.ts` \u2014 MUST pass\n * both explicitly: the default app root is derived by walking three directories up from the source,\n * which only holds inside `.mercato/generated`.\n */\ntype CompileAndImportOptions = {\n appRoot?: string\n outFile?: string\n allowRecovery?: boolean\n}\n\n/**\n * Options for `compileAppSourceFile`.\n *\n * `appRoot` anchors the tsconfig, the `@/` alias resolution and the dependency\n * cache; `outFile` is the absolute path of the artifact to write. `format`\n * selects the module system of that artifact \u2014 `'cjs'` exists for the Jest\n * runtime, which cannot `import()` an ESM sibling.\n */\nexport type CompileAppSourceOptions = {\n appRoot: string\n outFile: string\n format?: 'esm' | 'cjs'\n}\n\n/**\n * Compile one app-owned TypeScript source and its relative import graph into a\n * single JavaScript artifact, leaving every package import external.\n *\n * This is the only supported way to load app source (`apps/<app>/src/**`,\n * `.mercato/generated/**`) from a plain Node process. Those files are never\n * compiled to `dist`, and Node's own type stripping cannot load them: it\n * requires explicit file extensions on relative specifiers and rejects the\n * decorator and enum syntax the entities and DI files use.\n *\n * The artifact is cached against the content of the entry, its whole bundled\n * dependency graph, and the tsconfig chain, so an edit anywhere in the graph\n * invalidates it.\n *\n * The build runs inside the shared esbuild lifecycle. Callers outside a\n * bootstrap load \u2014 the generated-registry loader compiling an `@app` module \u2014\n * would otherwise hold a build on a service another scope is entitled to\n * `stop()`, and would leave the helper process running afterwards. Nesting is\n * safe: the scope only releases the service when the last participant exits.\n */\nexport async function compileAppSourceFile(\n tsPath: string,\n options: CompileAppSourceOptions,\n): Promise<string> {\n return withEsbuildLifecycle(() => compileAppSourceFileWithActiveEsbuild(tsPath, options))\n}\n\nasync function compileAppSourceFileWithActiveEsbuild(\n tsPath: string,\n options: CompileAppSourceOptions,\n): Promise<string> {\n const { appRoot, outFile } = options\n const format = options.format ?? 'esm'\n const appTsconfig = path.join(appRoot, 'tsconfig.json')\n const metadataPath = cacheMetadataPath(outFile)\n\n const tsExists = fs.existsSync(tsPath)\n const tsconfigExists = fs.existsSync(appTsconfig)\n\n if (!tsExists) {\n throw new GeneratedFileNotFoundError(tsPath)\n }\n if (!tsconfigExists) {\n throw new Error(`App TypeScript config not found: ${appTsconfig}`)\n }\n\n const tsconfigPaths = collectTsconfigPaths(appTsconfig)\n const expectedInputHash = cacheInputHash(tsPath, appRoot, tsconfigPaths)\n\n if (cacheIsValid(appRoot, outFile, metadataPath, expectedInputHash)) {\n return outFile\n }\n\n fs.mkdirSync(path.dirname(outFile), { recursive: true })\n // Dynamically import esbuild only when needed\n const esbuild = await getEsbuildRuntime()\n\n // Use esbuild.build with bundling to handle JSON imports\n const result = await esbuild.build({\n entryPoints: [tsPath],\n outfile: outFile,\n absWorkingDir: appRoot,\n bundle: true,\n metafile: true,\n format,\n platform: 'node',\n target: 'node18',\n tsconfig: appTsconfig,\n plugins: createCliBundlePlugins(appRoot),\n // Allow JSON imports\n loader: { '.json': 'json' },\n })\n const metadata: DynamicLoaderCacheMetadata = {\n version: DYNAMIC_LOADER_CACHE_VERSION,\n inputHash: expectedInputHash,\n outputHash: contentHash(fs.readFileSync(outFile)),\n dependencies: {\n ...collectDependencyHashes(appRoot, result.metafile.inputs),\n ...hashFilesRelativeTo(appRoot, tsconfigPaths),\n },\n }\n fs.writeFileSync(metadataPath, JSON.stringify(metadata))\n\n return outFile\n}\n\n/**\n * Compile a TypeScript file to JavaScript using esbuild bundler.\n * This bundles the file and all its dependencies, handling JSON imports properly.\n * The compiled file is written next to the source file with a .mjs extension unless\n * `outFile` says otherwise.\n */\nasync function compileAndImport(\n tsPath: string,\n options: CompileAndImportOptions = {},\n): Promise<Record<string, unknown>> {\n const allowRecovery = options.allowRecovery ?? true\n const jsPath = options.outFile ?? tsPath.replace(/\\.ts$/, '.mjs')\n const appRoot = options.appRoot ?? path.dirname(path.dirname(path.dirname(tsPath)))\n\n await compileAppSourceFile(tsPath, { appRoot, outFile: jsPath })\n\n // Import the compiled JavaScript\n try {\n const outputHash = contentHash(fs.readFileSync(jsPath))\n const fileUrl = `${pathToFileURL(jsPath).href}?cache=${outputHash}`\n return await import(fileUrl)\n } catch (error) {\n if (!allowRecovery) {\n throw error\n }\n\n const recovered = recoverMikroOrmV7GeneratedCacheFromImportError(appRoot, error)\n if (!recovered.applied) {\n throw error\n }\n\n return compileAndImport(tsPath, { ...options, allowRecovery: false })\n }\n}\n\n\n/**\n * Load a generated registry that older apps may not have generated yet.\n *\n * An absent source file is the supported compatibility case and resolves to\n * `fallback` quietly. Any other failure \u2014 a compile error, a broken import, a\n * runtime throw at module scope \u2014 still resolves to `fallback` so bootstrap\n * keeps working, but is reported at error level: a registry that silently\n * degrades to nothing is exactly how command interceptors stopped applying in\n * worker/CLI processes (#4327, #4491).\n */\nasync function loadOptionalGeneratedModule(\n tsPath: string,\n fallback: Record<string, unknown>,\n): Promise<Record<string, unknown>> {\n try {\n return await compileAndImport(tsPath)\n } catch (error) {\n if (error instanceof GeneratedFileNotFoundError) {\n logger.debug('Optional generated registry not present, using empty fallback', {\n file: path.basename(tsPath),\n })\n return fallback\n }\n\n logger.error('Failed to load generated registry, continuing without its entries', {\n file: path.basename(tsPath),\n filePath: tsPath,\n err: error,\n })\n return fallback\n }\n}\n\nfunction resolveAppRootOrThrow(appRoot?: string): AppRoot {\n const resolved: AppRoot | null = appRoot\n ? {\n generatedDir: path.join(appRoot, '.mercato', 'generated'),\n appDir: appRoot,\n mercatoDir: path.join(appRoot, '.mercato'),\n }\n : findAppRoot()\n\n if (!resolved) {\n throw new Error(\n 'Could not find app root with .mercato/generated directory. ' +\n 'Make sure you run this command from within a Next.js app directory, ' +\n 'or run \"yarn mercato generate\" first to create the generated files.',\n )\n }\n\n return resolved\n}\n\n/**\n * Load the app-level DI registrar (`src/di.ts`) for the dynamic bootstrap path.\n *\n * The Next.js runtime imports `@/di` statically from its own `src/bootstrap.ts` and hands the\n * registrar to `createBootstrap`. Worker, scheduler and CLI processes bootstrap through\n * `bootstrapFromAppRoot` instead, where the `@/` alias does not exist \u2014 so without this the app's\n * DI registrations silently never ran there, and every request container paid a failed\n * `import('@/di')` resolution (the compatibility fallback in `lib/di/container.ts`).\n *\n * An absent `src/di.ts` is the supported case and resolves to `null` quietly. A file that exists\n * but cannot be compiled, imported, or does not export `register` is reported at error level and\n * still resolves to `null`, so a broken app DI module degrades the same way a broken generated\n * registry does (#4327, #4491) instead of taking the whole process down.\n */\nasync function loadAppDiRegistrar(appDir: string): Promise<AppDiRegistrar | null> {\n const tsPath = path.join(appDir, 'src', 'di.ts')\n if (!fs.existsSync(tsPath)) {\n logger.debug('App-level DI module not present, skipping its registrations', { filePath: tsPath })\n return null\n }\n\n try {\n const appDiModule = await compileAndImport(tsPath, {\n appRoot: appDir,\n outFile: path.join(appDir, '.mercato', 'generated', 'app-di.compiled.mjs'),\n })\n const register = appDiModule.register\n if (typeof register !== 'function') {\n logger.error('App-level DI module exports no register(); its registrations are skipped', {\n filePath: tsPath,\n })\n return null\n }\n return register as AppDiRegistrar\n } catch (error) {\n logger.error('Failed to load the app-level DI module; its registrations are skipped', {\n filePath: tsPath,\n err: error,\n })\n return null\n }\n}\n\n/**\n * Override domains whose applier is not registered by `registerBuiltInModuleOverrideAppliers()`\n * but by importing a domain package for its side effect. `bootstrap-common.ts` does this with a\n * static import right before it dispatches; the dynamic bootstrap path has no bundler to lean on,\n * so it resolves the same modules here \u2014 lazily, and only when an app actually declares the\n * domain, so `@open-mercato/shared` keeps its rule of never taking a runtime dependency on a\n * domain package (soft-optional coupling, `packages/core/AGENTS.md` \u2192 Cross-Module Coupling).\n */\nconst OPTIONAL_OVERRIDE_APPLIER_MODULES: Record<string, string> = {\n ai: '@open-mercato/ai-assistant/modules/ai_assistant/lib/ai-overrides',\n}\n\n/**\n * Import the side-effect module that registers the applier for every declared override domain\n * that has no built-in one. A domain package the app does not install is not an error \u2014 there\n * is nothing for that domain to apply to \u2014 so a failed resolution is logged and skipped, and the\n * dispatcher's own \"domain not yet wired\" warning still fires behind it.\n */\nasync function ensureOptionalOverrideAppliers(enabledModules: ModuleEntryWithOverrides[]): Promise<void> {\n for (const [domain, specifier] of Object.entries(OPTIONAL_OVERRIDE_APPLIER_MODULES)) {\n const declared = enabledModules.some((entry) => {\n const overrides = entry?.overrides as Record<string, unknown> | undefined\n return Boolean(overrides && overrides[domain])\n })\n if (!declared) continue\n try {\n await import(/* webpackIgnore: true */ /* turbopackIgnore: true */ specifier)\n } catch (error) {\n logger.debug('Optional override applier module is not installed; the domain has nothing to apply to', {\n domain,\n specifier,\n err: error,\n })\n }\n }\n}\n\n/**\n * Dispatch `entry.overrides` declared in the app's `src/modules.ts` for the dynamic\n * bootstrap path.\n *\n * The Next.js runtime imports `enabledModules` statically from its own `src/modules.ts` and\n * calls `applyModuleOverridesFromEnabledModules` from `bootstrap-common.ts` before any registry\n * first-loads. Worker, scheduler and CLI processes bootstrap through `bootstrapFromAppRoot`\n * instead, which only ever compiled the generated `modules.cli.generated.ts` \u2014 so an app's\n * `entry.overrides` (encryption maps, ACL features, CLI commands, workers, event subscribers,\n * setup, \u2026) silently never applied there. `seed-encryption` seeding the base encryption maps\n * instead of the app's `overrides.encryption.maps` was the concrete symptom (#5582).\n *\n * An app layout with no `src/modules.ts` at all is logged and skipped \u2014 that is a real\n * compatibility case, handled the same way an absent `src/di.ts` is. A file that is *present*\n * but fails to compile or import is not: it throws, matching how this same function treats\n * every other mandatory input and how the Next.js runtime treats this same file (a static\n * import in `bootstrap-common.ts`). Degrading there would put `seed-encryption` back on the\n * base encryption maps while still printing success \u2014 #5582's outcome, only quieter.\n */\nasync function loadAppModuleOverrides(appDir: string): Promise<void> {\n const tsPath = path.join(appDir, 'src', 'modules.ts')\n if (!fs.existsSync(tsPath)) {\n logger.debug('App-level modules file not present, skipping entry.overrides dispatch', { filePath: tsPath })\n return\n }\n\n let enabledModules: unknown\n try {\n const appModulesModule = await compileAndImport(tsPath, {\n appRoot: appDir,\n outFile: path.join(appDir, '.mercato', 'generated', 'app-modules-overrides.compiled.mjs'),\n })\n enabledModules = appModulesModule.enabledModules\n } catch (error) {\n throw new Error(\n `[internal] Failed to load the app-level modules file (${tsPath}); entry.overrides cannot be applied. ` +\n 'Refusing to bootstrap with a partial override set.',\n { cause: error },\n )\n }\n\n if (!Array.isArray(enabledModules)) {\n throw new Error(\n `[internal] The app-level modules file (${tsPath}) exports no enabledModules array; ` +\n 'entry.overrides cannot be applied. Refusing to bootstrap with a partial override set.',\n )\n }\n\n await ensureOptionalOverrideAppliers(enabledModules as ModuleEntryWithOverrides[])\n applyModuleOverridesFromEnabledModules(enabledModules as ModuleEntryWithOverrides[])\n}\n\n/**\n * Dynamically load bootstrap data from a resolved app directory.\n *\n * IMPORTANT: This only works in unbundled contexts (CLI, tsx).\n * Do NOT use this in Next.js bundled code - use static imports instead.\n *\n * For CLI context, we skip loading modules.generated.ts which has Next.js dependencies.\n * CLI commands are discovered separately via the CLI module system.\n *\n * @param appRoot - Optional explicit app root path. If not provided, will search from cwd.\n * @returns The loaded bootstrap data\n * @throws Error if app root cannot be found or generated files are missing\n */\nasync function loadBootstrapDataWithActiveEsbuild(appRoot?: string): Promise<BootstrapData> {\n const resolved = resolveAppRootOrThrow(appRoot)\n\n const { generatedDir } = resolved\n\n ensureMikroOrmV7GeneratedCacheCompatibility(resolved.appDir)\n\n // IMPORTANT: Load entity IDs FIRST and register them before loading modules.\n // This is because modules (e.g., ce.ts files) use E.xxx.xxx at module scope,\n // and they need entity IDs to be available when they're imported.\n const entityIdsModule = await compileAndImport(path.join(generatedDir, 'entities.ids.generated.ts'))\n registerEntityIds(entityIdsModule.E as BootstrapData['entityIds'])\n\n // Now load the rest of the generated files.\n // modules.cli.generated.ts excludes Next.js-dependent code (routes, APIs, widgets)\n const [\n modulesModule,\n entitiesModule,\n diModule,\n searchModule,\n commandLoadersModule,\n commandInterceptorsModule,\n workflowsModule,\n ] = await Promise.all([\n compileAndImport(path.join(generatedDir, 'modules.cli.generated.ts')),\n compileAndImport(path.join(generatedDir, 'entities.generated.ts')),\n compileAndImport(path.join(generatedDir, 'di.generated.ts')),\n loadOptionalGeneratedModule(path.join(generatedDir, 'search.generated.ts'), { searchModuleConfigs: [] }),\n loadOptionalGeneratedModule(path.join(generatedDir, 'command-loaders.generated.ts'), { commandLoaderEntries: [] }),\n loadOptionalGeneratedModule(path.join(generatedDir, 'command-interceptors.generated.ts'), {\n commandInterceptorEntries: [],\n }),\n loadOptionalGeneratedModule(path.join(generatedDir, 'workflows.generated.ts'), { allCodeWorkflows: [] }),\n ])\n\n return {\n modules: modulesModule.modules as BootstrapData['modules'],\n entities: entitiesModule.entities as BootstrapData['entities'],\n diRegistrars: diModule.diRegistrars as BootstrapData['diRegistrars'],\n entityIds: entityIdsModule.E as BootstrapData['entityIds'],\n // Search configs are needed by workers for indexing\n searchModuleConfigs: (searchModule.searchModuleConfigs ?? []) as BootstrapData['searchModuleConfigs'],\n commandLoaderEntries: (commandLoadersModule.commandLoaderEntries ?? []) as BootstrapData['commandLoaderEntries'],\n // Command interceptors must apply in worker/CLI processes too \u2014 the\n // interceptor registry is per-process, so relying on the Next.js runtime's\n // registration silently no-ops every interceptor for queued/CLI commands\n // (#4327).\n commandInterceptorEntries: (commandInterceptorsModule.commandInterceptorEntries ??\n []) as BootstrapData['commandInterceptorEntries'],\n // Code workflow definitions are needed by workers to resume code-defined instances\n codeWorkflows: (workflowsModule.allCodeWorkflows ?? []) as BootstrapData['codeWorkflows'],\n // Empty UI-related data - not needed for CLI\n dashboardWidgetEntries: [],\n injectionWidgetEntries: [],\n injectionTables: [],\n interceptorEntries: [],\n componentOverrideEntries: [],\n }\n}\n\nexport async function loadBootstrapData(appRoot?: string): Promise<BootstrapData> {\n return withEsbuildLifecycle(() => loadBootstrapDataWithActiveEsbuild(appRoot))\n}\n\n/**\n * Create and execute bootstrap in CLI context.\n *\n * This is a convenience function that finds the app root, loads the generated\n * data dynamically, and runs bootstrap. Use this in CLI entry points.\n *\n * Returns the loaded bootstrap data so the CLI can register modules directly\n * (avoids module resolution issues when importing @open-mercato/cli/mercato).\n *\n * @param appRoot - Optional explicit app root path\n * @returns The loaded bootstrap data (modules, entities, etc.)\n */\nexport async function bootstrapFromAppRoot(appRoot?: string): Promise<BootstrapData> {\n const { createBootstrap, waitForAsyncRegistration } = await import('./factory.js')\n const resolved = resolveAppRootOrThrow(appRoot)\n // All three loads compile through esbuild, so they share one lifecycle scope: without it\n // `loadBootstrapData` releases the esbuild helper process and `loadAppDiRegistrar`\n // silently starts a second one that nothing ever stops.\n const { data, appDiRegistrar } = await withEsbuildLifecycle(async () => {\n // Dispatch the app's `entry.overrides` (src/modules.ts) BEFORE any registry\n // first-loads \u2014 the `bootstrap()` call below runs `registerModules(data.modules)`,\n // and `registerCliModules` in the mercato bin right after this function returns;\n // both read the override side-registry this populates.\n await loadAppModuleOverrides(resolved.appDir)\n return {\n data: await loadBootstrapData(resolved.appDir),\n appDiRegistrar: await loadAppDiRegistrar(resolved.appDir),\n }\n })\n const bootstrap = createBootstrap(data, appDiRegistrar ? { appDiRegistrar } : {})\n bootstrap()\n // In CLI context, wait for async registrations (UI widgets, search configs, etc.)\n await waitForAsyncRegistration()\n\n return data\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -38,7 +38,7 @@ function getResponseEnrichers() {
|
|
|
38
38
|
}
|
|
39
39
|
function getEnrichersForEntity(targetEntity, selector) {
|
|
40
40
|
const entityEntries = getResponseEnrichers().filter(
|
|
41
|
-
(entry) => entry.enricher.targetEntity === targetEntity
|
|
41
|
+
(entry) => entry.enricher.targetEntity === targetEntity || entry.enricher.targetEntity === "*"
|
|
42
42
|
);
|
|
43
43
|
if (!selector || selector.surface === "api-response") {
|
|
44
44
|
return entityEntries;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/crud/enricher-registry.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Response Enricher Registry\n *\n * Global registry for response enrichers using the same globalThis pattern\n * as injection widgets for HMR-safe storage.\n */\n\nimport type { EnricherRegistryEntry, EnricherQueryEngineConfig, ResponseEnricher } from './response-enricher'\nimport { applyResponseEnricherOverridesToEntries } from '../../modules/overrides'\n\n/**\n * Selector for filtering enrichers by execution surface.\n *\n * - `'api-response'`: returns all enrichers matching the entity (existing behavior).\n * - `'query-engine'`: returns only enrichers with `queryEngine.enabled === true`\n * and matching the specified engine type.\n */\nexport interface EnricherSurfaceSelector {\n surface: 'api-response' | 'query-engine'\n engine?: 'basic' | 'hybrid'\n}\n\nconst GLOBAL_ENRICHERS_KEY = '__openMercatoResponseEnrichers__'\n\nlet _enricherEntries: EnricherRegistryEntry[] | null = null\n\nfunction readGlobalEnrichers(): EnricherRegistryEntry[] | null {\n try {\n const value = (globalThis as Record<string, unknown>)[GLOBAL_ENRICHERS_KEY]\n return Array.isArray(value) ? (value as EnricherRegistryEntry[]) : null\n } catch {\n return null\n }\n}\n\nfunction writeGlobalEnrichers(entries: EnricherRegistryEntry[]) {\n try {\n ;(globalThis as Record<string, unknown>)[GLOBAL_ENRICHERS_KEY] = entries\n } catch {\n // ignore global assignment failures\n }\n}\n\n/**\n * Register response enrichers from all modules.\n * Called during bootstrap after generated enrichers are imported.\n */\nexport function registerResponseEnrichers(\n entries: Array<{ moduleId: string; enrichers: ResponseEnricher[] }>,\n) {\n const finalEntries = applyResponseEnricherOverridesToEntries(entries)\n const flat: EnricherRegistryEntry[] = []\n for (const entry of finalEntries) {\n for (const enricher of entry.enrichers) {\n flat.push({ moduleId: entry.moduleId, enricher })\n }\n }\n flat.sort((a, b) => (b.enricher.priority ?? 0) - (a.enricher.priority ?? 0))\n _enricherEntries = flat\n writeGlobalEnrichers(flat)\n}\n\n/**\n * Get all registered response enrichers.\n */\nexport function getResponseEnrichers(): EnricherRegistryEntry[] {\n const globalEntries = readGlobalEnrichers()\n if (globalEntries) return globalEntries\n if (!_enricherEntries) {\n return []\n }\n return _enricherEntries\n}\n\n/**\n * Get enrichers targeting a specific entity, sorted by priority (higher first).\n *\n * When `selector` is omitted the function returns all enrichers for the entity\n * (backward compatible with existing callers).\n *\n * When `selector.surface === 'api-response'` \u2014 same as omitted (all enrichers).\n * When `selector.surface === 'query-engine'` \u2014 returns only enrichers with\n * `queryEngine.enabled === true` and matching `engines` (defaults to both).\n */\nexport function getEnrichersForEntity(\n targetEntity: string,\n selector?: EnricherSurfaceSelector,\n): EnricherRegistryEntry[] {\n const entityEntries = getResponseEnrichers().filter(\n (entry)
|
|
5
|
-
"mappings": "AAQA,SAAS,+CAA+C;AAcxD,MAAM,uBAAuB;AAE7B,IAAI,mBAAmD;AAEvD,SAAS,sBAAsD;AAC7D,MAAI;AACF,UAAM,QAAS,WAAuC,oBAAoB;AAC1E,WAAO,MAAM,QAAQ,KAAK,IAAK,QAAoC;AAAA,EACrE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,qBAAqB,SAAkC;AAC9D,MAAI;AACF;AAAC,IAAC,WAAuC,oBAAoB,IAAI;AAAA,EACnE,QAAQ;AAAA,EAER;AACF;AAMO,SAAS,0BACd,SACA;AACA,QAAM,eAAe,wCAAwC,OAAO;AACpE,QAAM,OAAgC,CAAC;AACvC,aAAW,SAAS,cAAc;AAChC,eAAW,YAAY,MAAM,WAAW;AACtC,WAAK,KAAK,EAAE,UAAU,MAAM,UAAU,SAAS,CAAC;AAAA,IAClD;AAAA,EACF;AACA,OAAK,KAAK,CAAC,GAAG,OAAO,EAAE,SAAS,YAAY,MAAM,EAAE,SAAS,YAAY,EAAE;AAC3E,qBAAmB;AACnB,uBAAqB,IAAI;AAC3B;AAKO,SAAS,uBAAgD;AAC9D,QAAM,gBAAgB,oBAAoB;AAC1C,MAAI,cAAe,QAAO;AAC1B,MAAI,CAAC,kBAAkB;AACrB,WAAO,CAAC;AAAA,EACV;AACA,SAAO;AACT;AAYO,SAAS,sBACd,cACA,UACyB;AACzB,QAAM,gBAAgB,qBAAqB,EAAE;AAAA,IAC3C,CAAC,
|
|
4
|
+
"sourcesContent": ["/**\n * Response Enricher Registry\n *\n * Global registry for response enrichers using the same globalThis pattern\n * as injection widgets for HMR-safe storage.\n */\n\nimport type { EnricherRegistryEntry, EnricherQueryEngineConfig, ResponseEnricher } from './response-enricher'\nimport { applyResponseEnricherOverridesToEntries } from '../../modules/overrides'\n\n/**\n * Selector for filtering enrichers by execution surface.\n *\n * - `'api-response'`: returns all enrichers matching the entity (existing behavior).\n * - `'query-engine'`: returns only enrichers with `queryEngine.enabled === true`\n * and matching the specified engine type.\n */\nexport interface EnricherSurfaceSelector {\n surface: 'api-response' | 'query-engine'\n engine?: 'basic' | 'hybrid'\n}\n\nconst GLOBAL_ENRICHERS_KEY = '__openMercatoResponseEnrichers__'\n\nlet _enricherEntries: EnricherRegistryEntry[] | null = null\n\nfunction readGlobalEnrichers(): EnricherRegistryEntry[] | null {\n try {\n const value = (globalThis as Record<string, unknown>)[GLOBAL_ENRICHERS_KEY]\n return Array.isArray(value) ? (value as EnricherRegistryEntry[]) : null\n } catch {\n return null\n }\n}\n\nfunction writeGlobalEnrichers(entries: EnricherRegistryEntry[]) {\n try {\n ;(globalThis as Record<string, unknown>)[GLOBAL_ENRICHERS_KEY] = entries\n } catch {\n // ignore global assignment failures\n }\n}\n\n/**\n * Register response enrichers from all modules.\n * Called during bootstrap after generated enrichers are imported.\n */\nexport function registerResponseEnrichers(\n entries: Array<{ moduleId: string; enrichers: ResponseEnricher[] }>,\n) {\n const finalEntries = applyResponseEnricherOverridesToEntries(entries)\n const flat: EnricherRegistryEntry[] = []\n for (const entry of finalEntries) {\n for (const enricher of entry.enrichers) {\n flat.push({ moduleId: entry.moduleId, enricher })\n }\n }\n flat.sort((a, b) => (b.enricher.priority ?? 0) - (a.enricher.priority ?? 0))\n _enricherEntries = flat\n writeGlobalEnrichers(flat)\n}\n\n/**\n * Get all registered response enrichers.\n */\nexport function getResponseEnrichers(): EnricherRegistryEntry[] {\n const globalEntries = readGlobalEnrichers()\n if (globalEntries) return globalEntries\n if (!_enricherEntries) {\n return []\n }\n return _enricherEntries\n}\n\n/**\n * Get enrichers targeting a specific entity, sorted by priority (higher first).\n *\n * When `selector` is omitted the function returns all enrichers for the entity\n * (backward compatible with existing callers).\n *\n * When `selector.surface === 'api-response'` \u2014 same as omitted (all enrichers).\n * When `selector.surface === 'query-engine'` \u2014 returns only enrichers with\n * `queryEngine.enabled === true` and matching `engines` (defaults to both).\n */\nexport function getEnrichersForEntity(\n targetEntity: string,\n selector?: EnricherSurfaceSelector,\n): EnricherRegistryEntry[] {\n const entityEntries = getResponseEnrichers().filter(\n (entry) =>\n entry.enricher.targetEntity === targetEntity || entry.enricher.targetEntity === '*',\n )\n\n if (!selector || selector.surface === 'api-response') {\n return entityEntries\n }\n\n return entityEntries.filter((entry) => {\n const qeConfig: EnricherQueryEngineConfig | undefined = entry.enricher.queryEngine\n if (!qeConfig || !qeConfig.enabled) return false\n if (selector.engine && qeConfig.engines && qeConfig.engines.length > 0) {\n return qeConfig.engines.includes(selector.engine)\n }\n return true\n })\n}\n"],
|
|
5
|
+
"mappings": "AAQA,SAAS,+CAA+C;AAcxD,MAAM,uBAAuB;AAE7B,IAAI,mBAAmD;AAEvD,SAAS,sBAAsD;AAC7D,MAAI;AACF,UAAM,QAAS,WAAuC,oBAAoB;AAC1E,WAAO,MAAM,QAAQ,KAAK,IAAK,QAAoC;AAAA,EACrE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,qBAAqB,SAAkC;AAC9D,MAAI;AACF;AAAC,IAAC,WAAuC,oBAAoB,IAAI;AAAA,EACnE,QAAQ;AAAA,EAER;AACF;AAMO,SAAS,0BACd,SACA;AACA,QAAM,eAAe,wCAAwC,OAAO;AACpE,QAAM,OAAgC,CAAC;AACvC,aAAW,SAAS,cAAc;AAChC,eAAW,YAAY,MAAM,WAAW;AACtC,WAAK,KAAK,EAAE,UAAU,MAAM,UAAU,SAAS,CAAC;AAAA,IAClD;AAAA,EACF;AACA,OAAK,KAAK,CAAC,GAAG,OAAO,EAAE,SAAS,YAAY,MAAM,EAAE,SAAS,YAAY,EAAE;AAC3E,qBAAmB;AACnB,uBAAqB,IAAI;AAC3B;AAKO,SAAS,uBAAgD;AAC9D,QAAM,gBAAgB,oBAAoB;AAC1C,MAAI,cAAe,QAAO;AAC1B,MAAI,CAAC,kBAAkB;AACrB,WAAO,CAAC;AAAA,EACV;AACA,SAAO;AACT;AAYO,SAAS,sBACd,cACA,UACyB;AACzB,QAAM,gBAAgB,qBAAqB,EAAE;AAAA,IAC3C,CAAC,UACC,MAAM,SAAS,iBAAiB,gBAAgB,MAAM,SAAS,iBAAiB;AAAA,EACpF;AAEA,MAAI,CAAC,YAAY,SAAS,YAAY,gBAAgB;AACpD,WAAO;AAAA,EACT;AAEA,SAAO,cAAc,OAAO,CAAC,UAAU;AACrC,UAAM,WAAkD,MAAM,SAAS;AACvE,QAAI,CAAC,YAAY,CAAC,SAAS,QAAS,QAAO;AAC3C,QAAI,SAAS,UAAU,SAAS,WAAW,SAAS,QAAQ,SAAS,GAAG;AACtE,aAAO,SAAS,QAAQ,SAAS,SAAS,MAAM;AAAA,IAClD;AACA,WAAO;AAAA,EACT,CAAC;AACH;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -58,15 +58,16 @@ function resolveCache(context) {
|
|
|
58
58
|
}
|
|
59
59
|
return null;
|
|
60
60
|
}
|
|
61
|
-
function buildCacheKey(enricher, context, mode, recordIds) {
|
|
61
|
+
function buildCacheKey(enricher, context, targetEntity, mode, recordIds) {
|
|
62
62
|
const sortedIds = [...recordIds].sort((a, b) => a.localeCompare(b));
|
|
63
|
-
return `umes:enricher:${enricher.id}:tenant:${context.tenantId}:org:${context.organizationId}:mode:${mode}:ids:${JSON.stringify(sortedIds)}`;
|
|
63
|
+
return `umes:enricher:${enricher.id}:entity:${targetEntity}:tenant:${context.tenantId}:org:${context.organizationId}:mode:${mode}:ids:${JSON.stringify(sortedIds)}`;
|
|
64
64
|
}
|
|
65
|
+
const UNKNOWN_RECORD_ID = "unknown";
|
|
65
66
|
function extractRecordId(record) {
|
|
66
67
|
const idValue = record.id;
|
|
67
68
|
if (typeof idValue === "string" && idValue.trim().length > 0) return idValue.trim();
|
|
68
69
|
if (typeof idValue === "number") return String(idValue);
|
|
69
|
-
return
|
|
70
|
+
return UNKNOWN_RECORD_ID;
|
|
70
71
|
}
|
|
71
72
|
function getEnricherCacheTtl(enricher) {
|
|
72
73
|
const ttl = enricher.cache?.ttl;
|
|
@@ -96,6 +97,11 @@ async function readEnricherCache(cache, key) {
|
|
|
96
97
|
return null;
|
|
97
98
|
}
|
|
98
99
|
}
|
|
100
|
+
function logSkippedCacheWrite(enricher) {
|
|
101
|
+
logger.debug("Skipped enricher cache write \u2014 enrichment is not purely additive or lacks usable record ids", {
|
|
102
|
+
enricherId: enricher.id
|
|
103
|
+
});
|
|
104
|
+
}
|
|
99
105
|
async function writeEnricherCache(cache, key, value, ttl, tags) {
|
|
100
106
|
if (!cache) return;
|
|
101
107
|
try {
|
|
@@ -103,7 +109,51 @@ async function writeEnricherCache(cache, key, value, ttl, tags) {
|
|
|
103
109
|
} catch {
|
|
104
110
|
}
|
|
105
111
|
}
|
|
112
|
+
const ENRICHER_CACHE_VERSION = 1;
|
|
113
|
+
function isEnricherCacheEnvelope(value) {
|
|
114
|
+
if (typeof value !== "object" || value === null) return false;
|
|
115
|
+
const candidate = value;
|
|
116
|
+
if (candidate.version !== ENRICHER_CACHE_VERSION) return false;
|
|
117
|
+
return typeof candidate.deltas === "object" && candidate.deltas !== null;
|
|
118
|
+
}
|
|
119
|
+
function computeAdditiveDelta(input, output) {
|
|
120
|
+
const delta = {};
|
|
121
|
+
for (const key of Object.keys(input)) {
|
|
122
|
+
if (!Object.prototype.hasOwnProperty.call(output, key)) return null;
|
|
123
|
+
if (output[key] !== input[key]) return null;
|
|
124
|
+
}
|
|
125
|
+
for (const key of Object.keys(output)) {
|
|
126
|
+
if (Object.prototype.hasOwnProperty.call(input, key)) continue;
|
|
127
|
+
delta[key] = output[key];
|
|
128
|
+
}
|
|
129
|
+
return delta;
|
|
130
|
+
}
|
|
131
|
+
function buildCacheEnvelope(inputs, outputs) {
|
|
132
|
+
if (inputs.length !== outputs.length) return null;
|
|
133
|
+
const deltas = {};
|
|
134
|
+
for (let index = 0; index < inputs.length; index += 1) {
|
|
135
|
+
const recordId = extractRecordId(inputs[index]);
|
|
136
|
+
if (recordId === UNKNOWN_RECORD_ID) return null;
|
|
137
|
+
if (Object.prototype.hasOwnProperty.call(deltas, recordId)) return null;
|
|
138
|
+
const delta = computeAdditiveDelta(inputs[index], outputs[index]);
|
|
139
|
+
if (!delta) return null;
|
|
140
|
+
deltas[recordId] = delta;
|
|
141
|
+
}
|
|
142
|
+
return { version: ENRICHER_CACHE_VERSION, deltas };
|
|
143
|
+
}
|
|
144
|
+
function applyCacheEnvelope(envelope, records) {
|
|
145
|
+
const merged = [];
|
|
146
|
+
for (const record of records) {
|
|
147
|
+
const recordId = extractRecordId(record);
|
|
148
|
+
if (recordId === UNKNOWN_RECORD_ID) return null;
|
|
149
|
+
const delta = envelope.deltas[recordId];
|
|
150
|
+
if (!delta || typeof delta !== "object") return null;
|
|
151
|
+
merged.push({ ...record, ...delta });
|
|
152
|
+
}
|
|
153
|
+
return merged;
|
|
154
|
+
}
|
|
106
155
|
async function applyResponseEnrichers(items, targetEntity, context, preFilteredEntries) {
|
|
156
|
+
const enricherContext = { ...context, targetEntity };
|
|
107
157
|
const activeEntries = preFilteredEntries ? filterByACLAndTenant(preFilteredEntries, context) : getActiveEnrichers(targetEntity, context);
|
|
108
158
|
if (activeEntries.length === 0) {
|
|
109
159
|
return { items, _meta: { enrichedBy: [] } };
|
|
@@ -120,18 +170,22 @@ async function applyResponseEnrichers(items, targetEntity, context, preFilteredE
|
|
|
120
170
|
let result;
|
|
121
171
|
const recordIds = currentItems.map((item) => extractRecordId(item));
|
|
122
172
|
const shouldUseCache = enricher.cache?.strategy === "read-through";
|
|
123
|
-
const cacheKey = shouldUseCache ? buildCacheKey(enricher, context, "many", recordIds) : null;
|
|
173
|
+
const cacheKey = shouldUseCache ? buildCacheKey(enricher, context, targetEntity, "many", recordIds) : null;
|
|
174
|
+
const inputItems = shouldUseCache ? currentItems.map((item) => ({ ...item })) : currentItems;
|
|
124
175
|
if (shouldUseCache && cacheKey) {
|
|
125
176
|
const cached = await readEnricherCache(cache, cacheKey);
|
|
126
|
-
if (cached) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
177
|
+
if (isEnricherCacheEnvelope(cached)) {
|
|
178
|
+
const merged = applyCacheEnvelope(cached, currentItems);
|
|
179
|
+
if (merged) {
|
|
180
|
+
currentItems = merged;
|
|
181
|
+
enrichedBy.push(enricher.id);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
130
184
|
}
|
|
131
185
|
}
|
|
132
186
|
if (enricher.enrichMany) {
|
|
133
187
|
result = await Promise.race([
|
|
134
|
-
enricher.enrichMany(currentItems,
|
|
188
|
+
enricher.enrichMany(currentItems, enricherContext),
|
|
135
189
|
timeoutPromise(timeout)
|
|
136
190
|
]);
|
|
137
191
|
} else {
|
|
@@ -148,13 +202,18 @@ async function applyResponseEnrichers(items, targetEntity, context, preFilteredE
|
|
|
148
202
|
logEnricherTiming(enricher.id, entry.moduleId, targetEntity, elapsedMs);
|
|
149
203
|
currentItems = result;
|
|
150
204
|
if (shouldUseCache && cacheKey) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
205
|
+
const envelope = buildCacheEnvelope(inputItems, result);
|
|
206
|
+
if (envelope) {
|
|
207
|
+
await writeEnricherCache(
|
|
208
|
+
cache,
|
|
209
|
+
cacheKey,
|
|
210
|
+
envelope,
|
|
211
|
+
getEnricherCacheTtl(enricher),
|
|
212
|
+
getEnricherCacheTags(enricher, context)
|
|
213
|
+
);
|
|
214
|
+
} else {
|
|
215
|
+
logSkippedCacheWrite(enricher);
|
|
216
|
+
}
|
|
158
217
|
}
|
|
159
218
|
enrichedBy.push(enricher.id);
|
|
160
219
|
} catch (err) {
|
|
@@ -180,6 +239,7 @@ async function applyResponseEnrichers(items, targetEntity, context, preFilteredE
|
|
|
180
239
|
};
|
|
181
240
|
}
|
|
182
241
|
async function applyResponseEnricherToRecord(record, targetEntity, context, preFilteredEntries) {
|
|
242
|
+
const enricherContext = { ...context, targetEntity };
|
|
183
243
|
const activeEntries = preFilteredEntries ? filterByACLAndTenant(preFilteredEntries, context) : getActiveEnrichers(targetEntity, context);
|
|
184
244
|
if (activeEntries.length === 0) {
|
|
185
245
|
return { record, _meta: { enrichedBy: [] } };
|
|
@@ -195,30 +255,39 @@ async function applyResponseEnricherToRecord(record, targetEntity, context, preF
|
|
|
195
255
|
try {
|
|
196
256
|
const recordId = extractRecordId(currentRecord);
|
|
197
257
|
const shouldUseCache = enricher.cache?.strategy === "read-through";
|
|
198
|
-
const cacheKey = shouldUseCache ? buildCacheKey(enricher, context, "one", [recordId]) : null;
|
|
258
|
+
const cacheKey = shouldUseCache ? buildCacheKey(enricher, context, targetEntity, "one", [recordId]) : null;
|
|
259
|
+
const inputRecord = shouldUseCache ? { ...currentRecord } : currentRecord;
|
|
199
260
|
if (shouldUseCache && cacheKey) {
|
|
200
261
|
const cached = await readEnricherCache(cache, cacheKey);
|
|
201
|
-
if (cached) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
262
|
+
if (isEnricherCacheEnvelope(cached)) {
|
|
263
|
+
const merged = applyCacheEnvelope(cached, [currentRecord]);
|
|
264
|
+
if (merged) {
|
|
265
|
+
currentRecord = merged[0];
|
|
266
|
+
enrichedBy.push(enricher.id);
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
205
269
|
}
|
|
206
270
|
}
|
|
207
271
|
const result = await Promise.race([
|
|
208
|
-
enricher.enrichOne(currentRecord,
|
|
272
|
+
enricher.enrichOne(currentRecord, enricherContext),
|
|
209
273
|
timeoutPromise(timeout)
|
|
210
274
|
]);
|
|
211
275
|
const elapsedMs = Date.now() - startTime;
|
|
212
276
|
logEnricherTiming(enricher.id, entry.moduleId, targetEntity, elapsedMs);
|
|
213
277
|
currentRecord = result;
|
|
214
278
|
if (shouldUseCache && cacheKey) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
279
|
+
const envelope = buildCacheEnvelope([inputRecord], [result]);
|
|
280
|
+
if (envelope) {
|
|
281
|
+
await writeEnricherCache(
|
|
282
|
+
cache,
|
|
283
|
+
cacheKey,
|
|
284
|
+
envelope,
|
|
285
|
+
getEnricherCacheTtl(enricher),
|
|
286
|
+
getEnricherCacheTags(enricher, context)
|
|
287
|
+
);
|
|
288
|
+
} else {
|
|
289
|
+
logSkippedCacheWrite(enricher);
|
|
290
|
+
}
|
|
222
291
|
}
|
|
223
292
|
enrichedBy.push(enricher.id);
|
|
224
293
|
} catch (err) {
|