@open-mercato/shared 0.6.1-develop.3187.1.4c96d17341 → 0.6.1-develop.3188.1.499d530779
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/dist/lib/bootstrap/factory.js +3 -0
- package/dist/lib/bootstrap/factory.js.map +2 -2
- package/dist/lib/version.js +1 -1
- package/dist/lib/version.js.map +1 -1
- package/dist/modules/widgets/injection-loader.js +89 -0
- package/dist/modules/widgets/injection-loader.js.map +2 -2
- package/package.json +2 -2
- package/src/lib/bootstrap/factory.ts +3 -0
- package/src/modules/widgets/__tests__/injection-loader.required-modules.test.ts +176 -0
- package/src/modules/widgets/injection-loader.ts +117 -0
- package/src/modules/widgets/injection.ts +11 -0
|
@@ -80,6 +80,9 @@ async function registerWidgetsAndOptionalPackages(data, options) {
|
|
|
80
80
|
injectionRegistry.registerInjectionWidgets(data.injectionWidgetEntries);
|
|
81
81
|
coreInjection.registerCoreInjectionWidgets(data.injectionWidgetEntries);
|
|
82
82
|
coreInjection.registerCoreInjectionTables(data.injectionTables);
|
|
83
|
+
coreInjection.registerEnabledModuleIds(
|
|
84
|
+
data.modules.map((module) => module.id).filter((id) => typeof id === "string" && id.length > 0)
|
|
85
|
+
);
|
|
83
86
|
} catch {
|
|
84
87
|
}
|
|
85
88
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/bootstrap/factory.ts"],
|
|
4
|
-
"sourcesContent": ["import type { BootstrapData, BootstrapOptions } from './types'\nimport { registerOrmEntities } from '../db/mikro'\nimport { registerDiRegistrars } from '../di/container'\nimport { registerModules } from '../modules/registry'\nimport { registerEntityIds } from '../encryption/entityIds'\nimport { registerEntityFields } from '../encryption/entityFields'\nimport { registerSearchModuleConfigs } from '../../modules/search'\nimport { registerAnalyticsModuleConfigs } from '../../modules/analytics'\nimport { registerResponseEnrichers } from '../crud/enricher-registry'\nimport { registerApiInterceptors } from '../crud/interceptor-registry'\nimport { registerComponentOverrides } from '../../modules/widgets/component-registry'\nimport { registerMutationGuards } from '../crud/mutation-guard-store'\nimport { registerCommandInterceptors } from '../commands/command-interceptor-store'\nimport { registerNotificationHandlers } from '../notifications/handler-registry'\nimport { clearRegisteredIntegrations, registerBundles, registerIntegrations } from '../../modules/integrations/types'\n\nlet _bootstrapped = false\n\n// Store the async registration promise so callers can await it if needed\nlet _asyncRegistrationPromise: Promise<void> | null = null\n\n/**\n * Creates a bootstrap function that registers all application dependencies.\n *\n * The returned function should be called once at application startup.\n * In development mode, it can be called multiple times (for HMR).\n *\n * @param data - All generated registry data from .mercato/generated/\n * @param options - Optional configuration\n * @returns A bootstrap function to call at app startup\n */\nexport function createBootstrap(data: BootstrapData, options: BootstrapOptions = {}) {\n return function bootstrap(): void {\n // In development, always re-run registrations to handle HMR\n // (Module state may be reset when Turbopack reloads packages)\n if (_bootstrapped && process.env.NODE_ENV !== 'development') return\n _bootstrapped = true\n\n // === 1. Foundation: ORM entities and DI registrars ===\n registerOrmEntities(data.entities)\n registerDiRegistrars(data.diRegistrars.filter((r): r is NonNullable<typeof r> => r != null))\n\n // === 2. Modules registry (required by i18n, query engine, dashboards, CLI) ===\n registerModules(data.modules)\n clearRegisteredIntegrations()\n for (const module of data.modules) {\n if (module.integrations?.length) {\n registerIntegrations(module.integrations)\n }\n if (module.bundles?.length) {\n registerBundles(module.bundles)\n }\n }\n\n // === 3. Entity IDs (required by encryption, indexing, entity links) ===\n registerEntityIds(data.entityIds)\n\n // === 4. Entity fields registry (for encryption manager, Turbopack compatibility) ===\n if (data.entityFieldsRegistry) {\n registerEntityFields(data.entityFieldsRegistry)\n }\n\n // === 5. Search module configs (for search service registration in DI) ===\n if (data.searchModuleConfigs) {\n registerSearchModuleConfigs(data.searchModuleConfigs)\n }\n\n // === 6. Analytics module configs (for dashboard widgets and analytics API) ===\n if (data.analyticsModuleConfigs) {\n registerAnalyticsModuleConfigs(data.analyticsModuleConfigs)\n }\n\n // === 6b. Response enrichers (for CRUD response enrichment) ===\n if (data.enricherEntries) {\n registerResponseEnrichers(data.enricherEntries)\n }\n\n // === 6c. API interceptors (for CRUD route interception) ===\n if (data.interceptorEntries) {\n registerApiInterceptors(data.interceptorEntries)\n }\n\n // === 6d. Component overrides (for page/component replacement) ===\n if (data.componentOverrideEntries) {\n const allOverrides = data.componentOverrideEntries.flatMap((entry) => entry.componentOverrides ?? [])\n registerComponentOverrides(allOverrides)\n }\n\n // === 6e. Mutation guards (for CRUD mutation lifecycle) ===\n if (data.guardEntries) {\n registerMutationGuards(data.guardEntries)\n }\n\n // === 6f. Command interceptors (for command bus lifecycle) ===\n if (data.commandInterceptorEntries) {\n registerCommandInterceptors(data.commandInterceptorEntries)\n }\n\n // === 6g. Notification handlers (reactive notification side-effects) ===\n if (data.notificationHandlerEntries) {\n registerNotificationHandlers(data.notificationHandlerEntries)\n }\n\n // === 7-8. UI Widgets and Optional packages (async to avoid circular deps) ===\n // Store the promise so CLI context can await it\n _asyncRegistrationPromise = registerWidgetsAndOptionalPackages(data, options)\n void _asyncRegistrationPromise\n\n options.onRegistrationComplete?.()\n }\n}\n\n/**\n * Wait for async registrations (CLI modules, widgets, etc.) to complete.\n * Call this after bootstrap() in CLI context where you need modules immediately.\n */\nexport async function waitForAsyncRegistration(): Promise<void> {\n if (_asyncRegistrationPromise) {\n await _asyncRegistrationPromise\n }\n}\n\nasync function registerWidgetsAndOptionalPackages(data: BootstrapData, options: BootstrapOptions): Promise<void> {\n // Register UI widgets (dynamic imports to avoid circular deps with ui/core packages)\n try {\n const [dashboardRegistry, injectionRegistry, coreInjection] = await Promise.all([\n import('@open-mercato/ui/backend/dashboard/widgetRegistry'),\n import('@open-mercato/ui/backend/injection/widgetRegistry'),\n import('@open-mercato/core/modules/widgets/lib/injection'),\n ])\n\n dashboardRegistry.registerDashboardWidgets(data.dashboardWidgetEntries)\n injectionRegistry.registerInjectionWidgets(data.injectionWidgetEntries)\n coreInjection.registerCoreInjectionWidgets(data.injectionWidgetEntries)\n coreInjection.registerCoreInjectionTables(data.injectionTables)\n } catch {\n // UI packages may not be available in all contexts\n }\n\n // Note: Search module configs are registered synchronously in the main bootstrap.\n // The actual registerSearchModule() call happens in core/bootstrap.ts when the\n // DI container is created, using getSearchModuleConfigs() from the global registry.\n\n // Note: CLI module registration is handled separately in CLI context\n // via bootstrapFromAppRoot in dynamicLoader. We don't import CLI here\n // to avoid Turbopack tracing through the CLI package in Next.js context.\n}\n\n/**\n * Check if bootstrap has been called.\n */\nexport function isBootstrapped(): boolean {\n return _bootstrapped\n}\n\n/**\n * Reset bootstrap state. Useful for testing.\n */\nexport function resetBootstrapState(): void {\n _bootstrapped = false\n}\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,2BAA2B;AACpC,SAAS,4BAA4B;AACrC,SAAS,uBAAuB;AAChC,SAAS,yBAAyB;AAClC,SAAS,4BAA4B;AACrC,SAAS,mCAAmC;AAC5C,SAAS,sCAAsC;AAC/C,SAAS,iCAAiC;AAC1C,SAAS,+BAA+B;AACxC,SAAS,kCAAkC;AAC3C,SAAS,8BAA8B;AACvC,SAAS,mCAAmC;AAC5C,SAAS,oCAAoC;AAC7C,SAAS,6BAA6B,iBAAiB,4BAA4B;AAEnF,IAAI,gBAAgB;AAGpB,IAAI,4BAAkD;AAY/C,SAAS,gBAAgB,MAAqB,UAA4B,CAAC,GAAG;AACnF,SAAO,SAAS,YAAkB;AAGhC,QAAI,iBAAiB,QAAQ,IAAI,aAAa,cAAe;AAC7D,oBAAgB;AAGhB,wBAAoB,KAAK,QAAQ;AACjC,yBAAqB,KAAK,aAAa,OAAO,CAAC,MAAkC,KAAK,IAAI,CAAC;AAG3F,oBAAgB,KAAK,OAAO;AAC5B,gCAA4B;AAC5B,eAAW,UAAU,KAAK,SAAS;AACjC,UAAI,OAAO,cAAc,QAAQ;AAC/B,6BAAqB,OAAO,YAAY;AAAA,MAC1C;AACA,UAAI,OAAO,SAAS,QAAQ;AAC1B,wBAAgB,OAAO,OAAO;AAAA,MAChC;AAAA,IACF;AAGA,sBAAkB,KAAK,SAAS;AAGhC,QAAI,KAAK,sBAAsB;AAC7B,2BAAqB,KAAK,oBAAoB;AAAA,IAChD;AAGA,QAAI,KAAK,qBAAqB;AAC5B,kCAA4B,KAAK,mBAAmB;AAAA,IACtD;AAGA,QAAI,KAAK,wBAAwB;AAC/B,qCAA+B,KAAK,sBAAsB;AAAA,IAC5D;AAGA,QAAI,KAAK,iBAAiB;AACxB,gCAA0B,KAAK,eAAe;AAAA,IAChD;AAGA,QAAI,KAAK,oBAAoB;AAC3B,8BAAwB,KAAK,kBAAkB;AAAA,IACjD;AAGA,QAAI,KAAK,0BAA0B;AACjC,YAAM,eAAe,KAAK,yBAAyB,QAAQ,CAAC,UAAU,MAAM,sBAAsB,CAAC,CAAC;AACpG,iCAA2B,YAAY;AAAA,IACzC;AAGA,QAAI,KAAK,cAAc;AACrB,6BAAuB,KAAK,YAAY;AAAA,IAC1C;AAGA,QAAI,KAAK,2BAA2B;AAClC,kCAA4B,KAAK,yBAAyB;AAAA,IAC5D;AAGA,QAAI,KAAK,4BAA4B;AACnC,mCAA6B,KAAK,0BAA0B;AAAA,IAC9D;AAIA,gCAA4B,mCAAmC,MAAM,OAAO;AAC5E,SAAK;AAEL,YAAQ,yBAAyB;AAAA,EACnC;AACF;AAMA,eAAsB,2BAA0C;AAC9D,MAAI,2BAA2B;AAC7B,UAAM;AAAA,EACR;AACF;AAEA,eAAe,mCAAmC,MAAqB,SAA0C;AAE/G,MAAI;AACF,UAAM,CAAC,mBAAmB,mBAAmB,aAAa,IAAI,MAAM,QAAQ,IAAI;AAAA,MAC9E,OAAO,mDAAmD;AAAA,MAC1D,OAAO,mDAAmD;AAAA,MAC1D,OAAO,kDAAkD;AAAA,IAC3D,CAAC;AAED,sBAAkB,yBAAyB,KAAK,sBAAsB;AACtE,sBAAkB,yBAAyB,KAAK,sBAAsB;AACtE,kBAAc,6BAA6B,KAAK,sBAAsB;AACtE,kBAAc,4BAA4B,KAAK,eAAe;AAAA,
|
|
4
|
+
"sourcesContent": ["import type { BootstrapData, BootstrapOptions } from './types'\nimport { registerOrmEntities } from '../db/mikro'\nimport { registerDiRegistrars } from '../di/container'\nimport { registerModules } from '../modules/registry'\nimport { registerEntityIds } from '../encryption/entityIds'\nimport { registerEntityFields } from '../encryption/entityFields'\nimport { registerSearchModuleConfigs } from '../../modules/search'\nimport { registerAnalyticsModuleConfigs } from '../../modules/analytics'\nimport { registerResponseEnrichers } from '../crud/enricher-registry'\nimport { registerApiInterceptors } from '../crud/interceptor-registry'\nimport { registerComponentOverrides } from '../../modules/widgets/component-registry'\nimport { registerMutationGuards } from '../crud/mutation-guard-store'\nimport { registerCommandInterceptors } from '../commands/command-interceptor-store'\nimport { registerNotificationHandlers } from '../notifications/handler-registry'\nimport { clearRegisteredIntegrations, registerBundles, registerIntegrations } from '../../modules/integrations/types'\n\nlet _bootstrapped = false\n\n// Store the async registration promise so callers can await it if needed\nlet _asyncRegistrationPromise: Promise<void> | null = null\n\n/**\n * Creates a bootstrap function that registers all application dependencies.\n *\n * The returned function should be called once at application startup.\n * In development mode, it can be called multiple times (for HMR).\n *\n * @param data - All generated registry data from .mercato/generated/\n * @param options - Optional configuration\n * @returns A bootstrap function to call at app startup\n */\nexport function createBootstrap(data: BootstrapData, options: BootstrapOptions = {}) {\n return function bootstrap(): void {\n // In development, always re-run registrations to handle HMR\n // (Module state may be reset when Turbopack reloads packages)\n if (_bootstrapped && process.env.NODE_ENV !== 'development') return\n _bootstrapped = true\n\n // === 1. Foundation: ORM entities and DI registrars ===\n registerOrmEntities(data.entities)\n registerDiRegistrars(data.diRegistrars.filter((r): r is NonNullable<typeof r> => r != null))\n\n // === 2. Modules registry (required by i18n, query engine, dashboards, CLI) ===\n registerModules(data.modules)\n clearRegisteredIntegrations()\n for (const module of data.modules) {\n if (module.integrations?.length) {\n registerIntegrations(module.integrations)\n }\n if (module.bundles?.length) {\n registerBundles(module.bundles)\n }\n }\n\n // === 3. Entity IDs (required by encryption, indexing, entity links) ===\n registerEntityIds(data.entityIds)\n\n // === 4. Entity fields registry (for encryption manager, Turbopack compatibility) ===\n if (data.entityFieldsRegistry) {\n registerEntityFields(data.entityFieldsRegistry)\n }\n\n // === 5. Search module configs (for search service registration in DI) ===\n if (data.searchModuleConfigs) {\n registerSearchModuleConfigs(data.searchModuleConfigs)\n }\n\n // === 6. Analytics module configs (for dashboard widgets and analytics API) ===\n if (data.analyticsModuleConfigs) {\n registerAnalyticsModuleConfigs(data.analyticsModuleConfigs)\n }\n\n // === 6b. Response enrichers (for CRUD response enrichment) ===\n if (data.enricherEntries) {\n registerResponseEnrichers(data.enricherEntries)\n }\n\n // === 6c. API interceptors (for CRUD route interception) ===\n if (data.interceptorEntries) {\n registerApiInterceptors(data.interceptorEntries)\n }\n\n // === 6d. Component overrides (for page/component replacement) ===\n if (data.componentOverrideEntries) {\n const allOverrides = data.componentOverrideEntries.flatMap((entry) => entry.componentOverrides ?? [])\n registerComponentOverrides(allOverrides)\n }\n\n // === 6e. Mutation guards (for CRUD mutation lifecycle) ===\n if (data.guardEntries) {\n registerMutationGuards(data.guardEntries)\n }\n\n // === 6f. Command interceptors (for command bus lifecycle) ===\n if (data.commandInterceptorEntries) {\n registerCommandInterceptors(data.commandInterceptorEntries)\n }\n\n // === 6g. Notification handlers (reactive notification side-effects) ===\n if (data.notificationHandlerEntries) {\n registerNotificationHandlers(data.notificationHandlerEntries)\n }\n\n // === 7-8. UI Widgets and Optional packages (async to avoid circular deps) ===\n // Store the promise so CLI context can await it\n _asyncRegistrationPromise = registerWidgetsAndOptionalPackages(data, options)\n void _asyncRegistrationPromise\n\n options.onRegistrationComplete?.()\n }\n}\n\n/**\n * Wait for async registrations (CLI modules, widgets, etc.) to complete.\n * Call this after bootstrap() in CLI context where you need modules immediately.\n */\nexport async function waitForAsyncRegistration(): Promise<void> {\n if (_asyncRegistrationPromise) {\n await _asyncRegistrationPromise\n }\n}\n\nasync function registerWidgetsAndOptionalPackages(data: BootstrapData, options: BootstrapOptions): Promise<void> {\n // Register UI widgets (dynamic imports to avoid circular deps with ui/core packages)\n try {\n const [dashboardRegistry, injectionRegistry, coreInjection] = await Promise.all([\n import('@open-mercato/ui/backend/dashboard/widgetRegistry'),\n import('@open-mercato/ui/backend/injection/widgetRegistry'),\n import('@open-mercato/core/modules/widgets/lib/injection'),\n ])\n\n dashboardRegistry.registerDashboardWidgets(data.dashboardWidgetEntries)\n injectionRegistry.registerInjectionWidgets(data.injectionWidgetEntries)\n coreInjection.registerCoreInjectionWidgets(data.injectionWidgetEntries)\n coreInjection.registerCoreInjectionTables(data.injectionTables)\n coreInjection.registerEnabledModuleIds(\n data.modules.map((module) => module.id).filter((id): id is string => typeof id === 'string' && id.length > 0),\n )\n } catch {\n // UI packages may not be available in all contexts\n }\n\n // Note: Search module configs are registered synchronously in the main bootstrap.\n // The actual registerSearchModule() call happens in core/bootstrap.ts when the\n // DI container is created, using getSearchModuleConfigs() from the global registry.\n\n // Note: CLI module registration is handled separately in CLI context\n // via bootstrapFromAppRoot in dynamicLoader. We don't import CLI here\n // to avoid Turbopack tracing through the CLI package in Next.js context.\n}\n\n/**\n * Check if bootstrap has been called.\n */\nexport function isBootstrapped(): boolean {\n return _bootstrapped\n}\n\n/**\n * Reset bootstrap state. Useful for testing.\n */\nexport function resetBootstrapState(): void {\n _bootstrapped = false\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,2BAA2B;AACpC,SAAS,4BAA4B;AACrC,SAAS,uBAAuB;AAChC,SAAS,yBAAyB;AAClC,SAAS,4BAA4B;AACrC,SAAS,mCAAmC;AAC5C,SAAS,sCAAsC;AAC/C,SAAS,iCAAiC;AAC1C,SAAS,+BAA+B;AACxC,SAAS,kCAAkC;AAC3C,SAAS,8BAA8B;AACvC,SAAS,mCAAmC;AAC5C,SAAS,oCAAoC;AAC7C,SAAS,6BAA6B,iBAAiB,4BAA4B;AAEnF,IAAI,gBAAgB;AAGpB,IAAI,4BAAkD;AAY/C,SAAS,gBAAgB,MAAqB,UAA4B,CAAC,GAAG;AACnF,SAAO,SAAS,YAAkB;AAGhC,QAAI,iBAAiB,QAAQ,IAAI,aAAa,cAAe;AAC7D,oBAAgB;AAGhB,wBAAoB,KAAK,QAAQ;AACjC,yBAAqB,KAAK,aAAa,OAAO,CAAC,MAAkC,KAAK,IAAI,CAAC;AAG3F,oBAAgB,KAAK,OAAO;AAC5B,gCAA4B;AAC5B,eAAW,UAAU,KAAK,SAAS;AACjC,UAAI,OAAO,cAAc,QAAQ;AAC/B,6BAAqB,OAAO,YAAY;AAAA,MAC1C;AACA,UAAI,OAAO,SAAS,QAAQ;AAC1B,wBAAgB,OAAO,OAAO;AAAA,MAChC;AAAA,IACF;AAGA,sBAAkB,KAAK,SAAS;AAGhC,QAAI,KAAK,sBAAsB;AAC7B,2BAAqB,KAAK,oBAAoB;AAAA,IAChD;AAGA,QAAI,KAAK,qBAAqB;AAC5B,kCAA4B,KAAK,mBAAmB;AAAA,IACtD;AAGA,QAAI,KAAK,wBAAwB;AAC/B,qCAA+B,KAAK,sBAAsB;AAAA,IAC5D;AAGA,QAAI,KAAK,iBAAiB;AACxB,gCAA0B,KAAK,eAAe;AAAA,IAChD;AAGA,QAAI,KAAK,oBAAoB;AAC3B,8BAAwB,KAAK,kBAAkB;AAAA,IACjD;AAGA,QAAI,KAAK,0BAA0B;AACjC,YAAM,eAAe,KAAK,yBAAyB,QAAQ,CAAC,UAAU,MAAM,sBAAsB,CAAC,CAAC;AACpG,iCAA2B,YAAY;AAAA,IACzC;AAGA,QAAI,KAAK,cAAc;AACrB,6BAAuB,KAAK,YAAY;AAAA,IAC1C;AAGA,QAAI,KAAK,2BAA2B;AAClC,kCAA4B,KAAK,yBAAyB;AAAA,IAC5D;AAGA,QAAI,KAAK,4BAA4B;AACnC,mCAA6B,KAAK,0BAA0B;AAAA,IAC9D;AAIA,gCAA4B,mCAAmC,MAAM,OAAO;AAC5E,SAAK;AAEL,YAAQ,yBAAyB;AAAA,EACnC;AACF;AAMA,eAAsB,2BAA0C;AAC9D,MAAI,2BAA2B;AAC7B,UAAM;AAAA,EACR;AACF;AAEA,eAAe,mCAAmC,MAAqB,SAA0C;AAE/G,MAAI;AACF,UAAM,CAAC,mBAAmB,mBAAmB,aAAa,IAAI,MAAM,QAAQ,IAAI;AAAA,MAC9E,OAAO,mDAAmD;AAAA,MAC1D,OAAO,mDAAmD;AAAA,MAC1D,OAAO,kDAAkD;AAAA,IAC3D,CAAC;AAED,sBAAkB,yBAAyB,KAAK,sBAAsB;AACtE,sBAAkB,yBAAyB,KAAK,sBAAsB;AACtE,kBAAc,6BAA6B,KAAK,sBAAsB;AACtE,kBAAc,4BAA4B,KAAK,eAAe;AAC9D,kBAAc;AAAA,MACZ,KAAK,QAAQ,IAAI,CAAC,WAAW,OAAO,EAAE,EAAE,OAAO,CAAC,OAAqB,OAAO,OAAO,YAAY,GAAG,SAAS,CAAC;AAAA,IAC9G;AAAA,EACF,QAAQ;AAAA,EAER;AASF;AAKO,SAAS,iBAA0B;AACxC,SAAO;AACT;AAKO,SAAS,sBAA4B;AAC1C,kBAAgB;AAClB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/lib/version.js
CHANGED
package/dist/lib/version.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/version.ts"],
|
|
4
|
-
"sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.6.1-develop.
|
|
4
|
+
"sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.6.1-develop.3188.1.499d530779'\nexport const appVersion = APP_VERSION\n"],
|
|
5
5
|
"mappings": "AACO,MAAM,cAAc;AACpB,MAAM,aAAa;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
let _coreInjectionWidgetEntries = null;
|
|
2
2
|
let _coreInjectionTables = null;
|
|
3
|
+
let _enabledModuleIds = null;
|
|
3
4
|
let _injectionRegistryVersion = 0;
|
|
4
5
|
const GLOBAL_INJECTION_WIDGETS_KEY = "__openMercatoCoreInjectionWidgetEntries__";
|
|
5
6
|
const GLOBAL_INJECTION_TABLES_KEY = "__openMercatoCoreInjectionTables__";
|
|
7
|
+
const GLOBAL_ENABLED_MODULE_IDS_KEY = "__openMercatoEnabledModuleIds__";
|
|
6
8
|
const GLOBAL_INJECTION_REGISTRY_VERSION_KEY = "__openMercatoCoreInjectionRegistryVersion__";
|
|
7
9
|
const INJECTION_REGISTRY_CHANGED_EVENT = "__openMercatoInjectionRegistryChanged__";
|
|
8
10
|
function readGlobalInjectionWidgets() {
|
|
@@ -20,6 +22,22 @@ function writeGlobalInjectionWidgets(entries) {
|
|
|
20
22
|
} catch {
|
|
21
23
|
}
|
|
22
24
|
}
|
|
25
|
+
function readGlobalEnabledModuleIds() {
|
|
26
|
+
try {
|
|
27
|
+
const value = globalThis[GLOBAL_ENABLED_MODULE_IDS_KEY];
|
|
28
|
+
if (value instanceof Set) return value;
|
|
29
|
+
return null;
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function writeGlobalEnabledModuleIds(ids) {
|
|
35
|
+
try {
|
|
36
|
+
;
|
|
37
|
+
globalThis[GLOBAL_ENABLED_MODULE_IDS_KEY] = ids;
|
|
38
|
+
} catch {
|
|
39
|
+
}
|
|
40
|
+
}
|
|
23
41
|
function readGlobalInjectionTables() {
|
|
24
42
|
try {
|
|
25
43
|
const value = globalThis[GLOBAL_INJECTION_TABLES_KEY];
|
|
@@ -86,6 +104,21 @@ function registerCoreInjectionTables(tables) {
|
|
|
86
104
|
writeGlobalInjectionTables(tables);
|
|
87
105
|
notifyInjectionRegistryChanged();
|
|
88
106
|
}
|
|
107
|
+
function registerEnabledModuleIds(moduleIds) {
|
|
108
|
+
const next = /* @__PURE__ */ new Set();
|
|
109
|
+
for (const moduleId of moduleIds) {
|
|
110
|
+
if (typeof moduleId === "string" && moduleId.length > 0) next.add(moduleId);
|
|
111
|
+
}
|
|
112
|
+
if (_enabledModuleIds !== null && process.env.NODE_ENV === "development") {
|
|
113
|
+
console.debug("[Bootstrap] Enabled module IDs re-registered (this may occur during HMR)");
|
|
114
|
+
}
|
|
115
|
+
_enabledModuleIds = next;
|
|
116
|
+
writeGlobalEnabledModuleIds(next);
|
|
117
|
+
notifyInjectionRegistryChanged();
|
|
118
|
+
}
|
|
119
|
+
function getEnabledModuleIds() {
|
|
120
|
+
return readGlobalEnabledModuleIds() ?? _enabledModuleIds;
|
|
121
|
+
}
|
|
89
122
|
function getInjectionRegistryVersion() {
|
|
90
123
|
const globalVersion = readGlobalInjectionRegistryVersion();
|
|
91
124
|
if (globalVersion !== null) return globalVersion;
|
|
@@ -122,6 +155,7 @@ function invalidateInjectionWidgetCache() {
|
|
|
122
155
|
widgetEntriesPromise = null;
|
|
123
156
|
injectionTablePromise = null;
|
|
124
157
|
widgetCache.clear();
|
|
158
|
+
warnedRequiredModuleSkips.clear();
|
|
125
159
|
}
|
|
126
160
|
async function loadWidgetEntries() {
|
|
127
161
|
if (!widgetEntriesPromise) {
|
|
@@ -242,6 +276,41 @@ async function loadEntry(entry) {
|
|
|
242
276
|
}
|
|
243
277
|
return widgetCache.get(entry.key);
|
|
244
278
|
}
|
|
279
|
+
function getEnabledModuleIdsForInjection() {
|
|
280
|
+
const explicit = readGlobalEnabledModuleIds() ?? _enabledModuleIds;
|
|
281
|
+
if (explicit) return explicit;
|
|
282
|
+
const enabled = /* @__PURE__ */ new Set();
|
|
283
|
+
const tables = readGlobalInjectionTables() ?? _coreInjectionTables ?? [];
|
|
284
|
+
for (const entry of tables) {
|
|
285
|
+
if (entry?.moduleId) enabled.add(entry.moduleId);
|
|
286
|
+
}
|
|
287
|
+
const entries = readGlobalInjectionWidgets() ?? _coreInjectionWidgetEntries ?? [];
|
|
288
|
+
for (const entry of entries) {
|
|
289
|
+
if (entry?.moduleId) enabled.add(entry.moduleId);
|
|
290
|
+
}
|
|
291
|
+
return enabled;
|
|
292
|
+
}
|
|
293
|
+
function widgetMissingRequiredModules(metadata, enabledModuleIds) {
|
|
294
|
+
const required = metadata.requiredModules;
|
|
295
|
+
if (!Array.isArray(required) || required.length === 0) return [];
|
|
296
|
+
const missing = [];
|
|
297
|
+
for (const moduleId of required) {
|
|
298
|
+
if (typeof moduleId !== "string" || moduleId.length === 0) continue;
|
|
299
|
+
if (!enabledModuleIds.has(moduleId)) missing.push(moduleId);
|
|
300
|
+
}
|
|
301
|
+
return missing;
|
|
302
|
+
}
|
|
303
|
+
const warnedRequiredModuleSkips = /* @__PURE__ */ new Set();
|
|
304
|
+
function warnSkippedWidget(metadataId, missingModules) {
|
|
305
|
+
const key = `${metadataId}:${missingModules.join(",")}`;
|
|
306
|
+
if (warnedRequiredModuleSkips.has(key)) return;
|
|
307
|
+
warnedRequiredModuleSkips.add(key);
|
|
308
|
+
if (process.env.NODE_ENV === "development") {
|
|
309
|
+
console.debug(
|
|
310
|
+
`[InjectionLoader] Skipping widget "${metadataId}" \u2014 required module(s) not enabled: ${missingModules.join(", ")}`
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
245
314
|
async function getResolvedEntriesForSpot(spotId) {
|
|
246
315
|
const table = await loadInjectionTable();
|
|
247
316
|
const exactEntries = table.get(spotId) ?? [];
|
|
@@ -265,10 +334,16 @@ async function getResolvedEntriesForSpot(spotId) {
|
|
|
265
334
|
}
|
|
266
335
|
async function loadAllInjectionWidgets() {
|
|
267
336
|
const widgetEntries = await loadWidgetEntries();
|
|
337
|
+
const enabledModuleIds = getEnabledModuleIdsForInjection();
|
|
268
338
|
const loaded = await Promise.all(
|
|
269
339
|
widgetEntries.map(async (entry) => {
|
|
270
340
|
const module = await loadEntry(entry);
|
|
271
341
|
if (!isLoadedInjectionWidget(module)) return null;
|
|
342
|
+
const missing = widgetMissingRequiredModules(module.metadata, enabledModuleIds);
|
|
343
|
+
if (missing.length > 0) {
|
|
344
|
+
warnSkippedWidget(module.metadata.id, missing);
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
272
347
|
return { ...module, moduleId: entry.moduleId, key: entry.key };
|
|
273
348
|
})
|
|
274
349
|
);
|
|
@@ -283,10 +358,16 @@ async function loadAllInjectionWidgets() {
|
|
|
283
358
|
}
|
|
284
359
|
async function loadInjectionWidgetById(widgetId) {
|
|
285
360
|
const widgetEntries = await loadWidgetEntries();
|
|
361
|
+
const enabledModuleIds = getEnabledModuleIdsForInjection();
|
|
286
362
|
for (const entry of widgetEntries) {
|
|
287
363
|
const module = await loadEntry(entry);
|
|
288
364
|
if (!isLoadedInjectionWidget(module)) continue;
|
|
289
365
|
if (module.metadata.id === widgetId) {
|
|
366
|
+
const missing = widgetMissingRequiredModules(module.metadata, enabledModuleIds);
|
|
367
|
+
if (missing.length > 0) {
|
|
368
|
+
warnSkippedWidget(module.metadata.id, missing);
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
290
371
|
return { ...module, moduleId: entry.moduleId, key: entry.key };
|
|
291
372
|
}
|
|
292
373
|
}
|
|
@@ -294,10 +375,16 @@ async function loadInjectionWidgetById(widgetId) {
|
|
|
294
375
|
}
|
|
295
376
|
async function loadInjectionDataWidgetById(widgetId) {
|
|
296
377
|
const widgetEntries = await loadWidgetEntries();
|
|
378
|
+
const enabledModuleIds = getEnabledModuleIdsForInjection();
|
|
297
379
|
for (const entry of widgetEntries) {
|
|
298
380
|
const module = await loadEntry(entry);
|
|
299
381
|
if (!isLoadedInjectionDataWidget(module)) continue;
|
|
300
382
|
if (module.metadata.id === widgetId) {
|
|
383
|
+
const missing = widgetMissingRequiredModules(module.metadata, enabledModuleIds);
|
|
384
|
+
if (missing.length > 0) {
|
|
385
|
+
warnSkippedWidget(module.metadata.id, missing);
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
301
388
|
return { ...module, moduleId: entry.moduleId, key: entry.key };
|
|
302
389
|
}
|
|
303
390
|
}
|
|
@@ -328,6 +415,7 @@ async function loadInjectionDataWidgetsForSpot(spotId) {
|
|
|
328
415
|
export {
|
|
329
416
|
getCoreInjectionTables,
|
|
330
417
|
getCoreInjectionWidgets,
|
|
418
|
+
getEnabledModuleIds,
|
|
331
419
|
getInjectionRegistryVersion,
|
|
332
420
|
invalidateInjectionWidgetCache,
|
|
333
421
|
loadAllInjectionWidgets,
|
|
@@ -337,6 +425,7 @@ export {
|
|
|
337
425
|
loadInjectionWidgetsForSpot,
|
|
338
426
|
registerCoreInjectionTables,
|
|
339
427
|
registerCoreInjectionWidgets,
|
|
428
|
+
registerEnabledModuleIds,
|
|
340
429
|
subscribeToInjectionRegistryChanges
|
|
341
430
|
};
|
|
342
431
|
//# sourceMappingURL=injection-loader.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/widgets/injection-loader.ts"],
|
|
4
|
-
"sourcesContent": ["import type { ModuleInjectionWidgetEntry } from '../registry'\nimport type {\n InjectionAnyWidgetModule,\n InjectionDataWidgetModule,\n InjectionWidgetMetadata,\n InjectionWidgetModule,\n InjectionSpotId,\n ModuleInjectionSlot,\n ModuleInjectionTable,\n InjectionWidgetPlacement,\n} from './injection'\n\ntype LoadedWidgetModule = InjectionWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }\ntype LoadedDataWidgetModule = InjectionDataWidgetModule & { metadata: InjectionWidgetMetadata }\n\nexport type LoadedInjectionWidget = LoadedWidgetModule & {\n moduleId: string\n key: string\n placement?: {\n groupId?: string\n groupLabel?: string\n groupDescription?: string\n column?: 1 | 2\n kind?: 'tab' | 'group' | 'stack'\n [k: string]: unknown\n }\n}\n\nexport type LoadedInjectionDataWidget = LoadedDataWidgetModule & {\n moduleId: string\n key: string\n placement?: {\n groupId?: string\n groupLabel?: string\n groupDescription?: string\n column?: 1 | 2\n kind?: 'tab' | 'group' | 'stack'\n [k: string]: unknown\n }\n}\n\ntype WidgetEntry = ModuleInjectionWidgetEntry & { moduleId: string }\n\n// Registration pattern for publishable packages\nlet _coreInjectionWidgetEntries: ModuleInjectionWidgetEntry[] | null = null\nlet _coreInjectionTables: Array<{ moduleId: string; table: ModuleInjectionTable }> | null = null\nlet _injectionRegistryVersion = 0\nconst GLOBAL_INJECTION_WIDGETS_KEY = '__openMercatoCoreInjectionWidgetEntries__'\nconst GLOBAL_INJECTION_TABLES_KEY = '__openMercatoCoreInjectionTables__'\nconst GLOBAL_INJECTION_REGISTRY_VERSION_KEY = '__openMercatoCoreInjectionRegistryVersion__'\nconst INJECTION_REGISTRY_CHANGED_EVENT = '__openMercatoInjectionRegistryChanged__'\n\nfunction readGlobalInjectionWidgets(): ModuleInjectionWidgetEntry[] | null {\n try {\n const value = (globalThis as Record<string, unknown>)[GLOBAL_INJECTION_WIDGETS_KEY]\n return Array.isArray(value) ? (value as ModuleInjectionWidgetEntry[]) : null\n } catch {\n return null\n }\n}\n\nfunction writeGlobalInjectionWidgets(entries: ModuleInjectionWidgetEntry[]) {\n try {\n ;(globalThis as Record<string, unknown>)[GLOBAL_INJECTION_WIDGETS_KEY] = entries\n } catch {\n // ignore global assignment failures\n }\n}\n\nfunction readGlobalInjectionTables(): Array<{ moduleId: string; table: ModuleInjectionTable }> | null {\n try {\n const value = (globalThis as Record<string, unknown>)[GLOBAL_INJECTION_TABLES_KEY]\n return Array.isArray(value) ? (value as Array<{ moduleId: string; table: ModuleInjectionTable }>) : null\n } catch {\n return null\n }\n}\n\nfunction writeGlobalInjectionTables(tables: Array<{ moduleId: string; table: ModuleInjectionTable }>) {\n try {\n ;(globalThis as Record<string, unknown>)[GLOBAL_INJECTION_TABLES_KEY] = tables\n } catch {\n // ignore global assignment failures\n }\n}\n\nfunction readGlobalInjectionRegistryVersion(): number | null {\n try {\n const value = (globalThis as Record<string, unknown>)[GLOBAL_INJECTION_REGISTRY_VERSION_KEY]\n return typeof value === 'number' ? value : null\n } catch {\n return null\n }\n}\n\nfunction writeGlobalInjectionRegistryVersion(version: number) {\n try {\n ;(globalThis as Record<string, unknown>)[GLOBAL_INJECTION_REGISTRY_VERSION_KEY] = version\n } catch {\n // ignore global assignment failures\n }\n}\n\nfunction notifyInjectionRegistryChanged() {\n _injectionRegistryVersion += 1\n writeGlobalInjectionRegistryVersion(_injectionRegistryVersion)\n invalidateInjectionWidgetCache()\n\n if (typeof window === 'undefined') return\n\n window.dispatchEvent(new CustomEvent(INJECTION_REGISTRY_CHANGED_EVENT, {\n detail: { version: _injectionRegistryVersion },\n }))\n}\n\nexport function registerCoreInjectionWidgets(entries: ModuleInjectionWidgetEntry[]) {\n if (_coreInjectionWidgetEntries !== null && process.env.NODE_ENV === 'development') {\n console.debug('[Bootstrap] Core injection widgets re-registered (this may occur during HMR)')\n }\n _coreInjectionWidgetEntries = entries\n writeGlobalInjectionWidgets(entries)\n notifyInjectionRegistryChanged()\n}\n\nexport function getCoreInjectionWidgets(): ModuleInjectionWidgetEntry[] {\n const globalEntries = readGlobalInjectionWidgets()\n if (globalEntries) return globalEntries\n if (!_coreInjectionWidgetEntries) {\n // On client-side, bootstrap doesn't run - return empty array gracefully\n if (typeof window !== 'undefined') {\n return []\n }\n throw new Error('[Bootstrap] Core injection widgets not registered. Call registerCoreInjectionWidgets() at bootstrap.')\n }\n return _coreInjectionWidgetEntries\n}\n\nexport function registerCoreInjectionTables(tables: Array<{ moduleId: string; table: ModuleInjectionTable }>) {\n if (_coreInjectionTables !== null && process.env.NODE_ENV === 'development') {\n console.debug('[Bootstrap] Core injection tables re-registered (this may occur during HMR)')\n }\n _coreInjectionTables = tables\n writeGlobalInjectionTables(tables)\n notifyInjectionRegistryChanged()\n}\n\nexport function getInjectionRegistryVersion(): number {\n const globalVersion = readGlobalInjectionRegistryVersion()\n if (globalVersion !== null) return globalVersion\n return _injectionRegistryVersion\n}\n\nexport function subscribeToInjectionRegistryChanges(listener: () => void): () => void {\n if (typeof window === 'undefined') {\n return () => {}\n }\n\n const handler = () => listener()\n window.addEventListener(INJECTION_REGISTRY_CHANGED_EVENT, handler)\n return () => {\n window.removeEventListener(INJECTION_REGISTRY_CHANGED_EVENT, handler)\n }\n}\n\nexport function getCoreInjectionTables(): Array<{ moduleId: string; table: ModuleInjectionTable }> {\n const globalTables = readGlobalInjectionTables()\n if (globalTables) return globalTables\n if (!_coreInjectionTables) {\n // On client-side, bootstrap doesn't run - return empty array gracefully\n if (typeof window !== 'undefined') {\n return []\n }\n throw new Error('[Bootstrap] Core injection tables not registered. Call registerCoreInjectionTables() at bootstrap.')\n }\n return _coreInjectionTables\n}\n\nlet widgetEntriesPromise: Promise<WidgetEntry[]> | null = null\ntype TableEntry = {\n widgetId: string\n moduleId: string\n priority: number\n placement?: ModuleInjectionSlot extends infer S\n ? S extends { widgetId: string }\n ? Omit<S, 'widgetId' | 'priority'>\n : never\n : never\n}\nlet injectionTablePromise: Promise<Map<InjectionSpotId, TableEntry[]>> | null = null\n\nfunction isInjectionSlotObject(value: ModuleInjectionSlot): value is InjectionWidgetPlacement & { widgetId: string; priority?: number } {\n return typeof value === 'object' && value !== null && 'widgetId' in value\n}\n\n/**\n * Invalidate the widget entries and widget module cache.\n * Call this when the generated registry is updated or modules are reloaded.\n */\nexport function invalidateInjectionWidgetCache() {\n widgetEntriesPromise = null\n injectionTablePromise = null\n widgetCache.clear()\n}\n\nasync function loadWidgetEntries(): Promise<WidgetEntry[]> {\n if (!widgetEntriesPromise) {\n const promise = Promise.resolve().then(() =>\n getCoreInjectionWidgets().map((entry) => ({\n ...entry,\n moduleId: entry.moduleId || 'unknown',\n }))\n )\n widgetEntriesPromise = promise.catch((err) => {\n if (widgetEntriesPromise === promise) {\n widgetEntriesPromise = null\n }\n throw err\n })\n }\n return widgetEntriesPromise\n}\n\nasync function loadInjectionTable(): Promise<Map<InjectionSpotId, TableEntry[]>> {\n if (!injectionTablePromise) {\n const promise = Promise.resolve().then(() => {\n const list = getCoreInjectionTables()\n const table = new Map<InjectionSpotId, TableEntry[]>()\n\n for (const entry of list) {\n const injectionTable = entry.table ?? {}\n for (const [spotId, widgetIds] of Object.entries(injectionTable)) {\n const widgets = Array.isArray(widgetIds) ? widgetIds : [widgetIds]\n const existing = table.get(spotId) ?? []\n for (const widgetEntry of widgets) {\n if (typeof widgetEntry === 'string') {\n existing.push({ widgetId: widgetEntry, moduleId: entry.moduleId, priority: 0 })\n continue\n }\n if (isInjectionSlotObject(widgetEntry)) {\n const { widgetId, priority = 0, ...placement } = widgetEntry\n existing.push({\n widgetId,\n moduleId: entry.moduleId,\n priority: typeof priority === 'number' ? priority : 0,\n placement,\n })\n continue\n }\n }\n table.set(spotId, existing)\n }\n }\n\n for (const [spotId, widgets] of table.entries()) {\n table.set(spotId, widgets.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)))\n }\n\n return table\n })\n injectionTablePromise = promise.catch((err) => {\n if (injectionTablePromise === promise) {\n injectionTablePromise = null\n }\n throw err\n })\n }\n return injectionTablePromise\n}\n\nconst widgetCache = new Map<string, Promise<InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }>>()\n\nfunction isDataWidgetModule(widget: Record<string, unknown>): widget is LoadedDataWidgetModule {\n const keys = [\n 'columns',\n 'rowActions',\n 'bulkActions',\n 'filters',\n 'fields',\n 'steps',\n 'badge',\n 'menuItems',\n ]\n return keys.some((key) => key in widget)\n}\n\nfunction ensureValidInjectionModule(mod: unknown, key: string, moduleId: string): (InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }) {\n if (!mod || typeof mod !== 'object') {\n throw new Error(`Invalid injection widget module \"${key}\" from \"${moduleId}\" (expected object export)`)\n }\n const widget = (mod as { default?: InjectionAnyWidgetModule<any, any> }).default ?? (mod as InjectionAnyWidgetModule<any, any>)\n if (!widget || typeof widget !== 'object') {\n throw new Error(`Invalid injection widget export \"${key}\" from \"${moduleId}\" (missing default export)`) \n }\n if (!('metadata' in widget) || !widget.metadata || typeof widget.metadata !== 'object') {\n throw new Error(`Injection widget \"${key}\" from \"${moduleId}\" is missing metadata`)\n }\n const metadata = widget.metadata\n if (typeof metadata.id !== 'string' || metadata.id.length === 0) {\n throw new Error(`Injection widget \"${key}\" from \"${moduleId}\" metadata.id must be a non-empty string`)\n }\n const normalized = {\n ...widget,\n metadata,\n }\n\n if ('Widget' in normalized && typeof normalized.Widget === 'function') {\n if (typeof metadata.title !== 'string' || metadata.title.length === 0) {\n throw new Error(`Injection widget \"${metadata.id}\" from \"${moduleId}\" must have a title`)\n }\n return normalized\n }\n\n if (!isDataWidgetModule(normalized as Record<string, unknown>)) {\n throw new Error(\n `Injection widget \"${metadata.id}\" from \"${moduleId}\" must export either Widget component or a declarative data payload`\n )\n }\n\n return normalized\n}\n\nfunction isLoadedInjectionWidget(\n module: InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }\n): module is LoadedWidgetModule {\n return 'Widget' in module && typeof module.Widget === 'function'\n}\n\nfunction isLoadedInjectionDataWidget(\n module: InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }\n): module is LoadedDataWidgetModule {\n return !isLoadedInjectionWidget(module)\n}\n\nasync function loadEntry(entry: WidgetEntry): Promise<InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }> {\n if (!widgetCache.has(entry.key)) {\n const promise = entry.loader().then((mod) => ensureValidInjectionModule(mod, entry.key, entry.moduleId))\n widgetCache.set(entry.key, promise)\n }\n return widgetCache.get(entry.key)!\n}\n\nasync function getResolvedEntriesForSpot(spotId: InjectionSpotId): Promise<TableEntry[]> {\n const table = await loadInjectionTable()\n const exactEntries = table.get(spotId) ?? []\n const wildcardEntries: TableEntry[] = []\n\n for (const [candidateSpotId, candidateEntries] of table.entries()) {\n if (candidateSpotId === spotId) continue\n if (!candidateSpotId.includes('*')) continue\n const pattern = new RegExp(`^${candidateSpotId.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&').replace(/\\*/g, '.*')}$`)\n if (!pattern.test(spotId)) continue\n wildcardEntries.push(...candidateEntries)\n }\n\n const dedupedEntries = new Map<string, TableEntry>()\n for (const entry of [...exactEntries, ...wildcardEntries]) {\n const cacheKey = `${entry.moduleId}:${entry.widgetId}`\n const previous = dedupedEntries.get(cacheKey)\n if (!previous || (entry.priority ?? 0) > (previous.priority ?? 0)) {\n dedupedEntries.set(cacheKey, entry)\n }\n }\n\n return Array.from(dedupedEntries.values()).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0))\n}\n\nexport async function loadAllInjectionWidgets(): Promise<LoadedInjectionWidget[]> {\n const widgetEntries = await loadWidgetEntries()\n const loaded = await Promise.all(\n widgetEntries.map(async (entry) => {\n const module = await loadEntry(entry)\n if (!isLoadedInjectionWidget(module)) return null\n return { ...module, moduleId: entry.moduleId, key: entry.key }\n })\n )\n const byId = new Map<string, LoadedInjectionWidget>()\n for (const widget of loaded) {\n if (!widget) continue\n if (!byId.has(widget.metadata.id)) {\n byId.set(widget.metadata.id, widget)\n }\n }\n return Array.from(byId.values())\n}\n\nexport async function loadInjectionWidgetById(widgetId: string): Promise<LoadedInjectionWidget | null> {\n const widgetEntries = await loadWidgetEntries()\n for (const entry of widgetEntries) {\n const module = await loadEntry(entry)\n if (!isLoadedInjectionWidget(module)) continue\n if (module.metadata.id === widgetId) {\n return { ...module, moduleId: entry.moduleId, key: entry.key }\n }\n }\n return null\n}\n\nexport async function loadInjectionDataWidgetById(widgetId: string): Promise<LoadedInjectionDataWidget | null> {\n const widgetEntries = await loadWidgetEntries()\n for (const entry of widgetEntries) {\n const module = await loadEntry(entry)\n if (!isLoadedInjectionDataWidget(module)) continue\n if (module.metadata.id === widgetId) {\n return { ...module, moduleId: entry.moduleId, key: entry.key }\n }\n }\n return null\n}\n\nexport async function loadInjectionWidgetsForSpot(spotId: InjectionSpotId): Promise<LoadedInjectionWidget[]> {\n const entries = await getResolvedEntriesForSpot(spotId)\n const widgets: LoadedInjectionWidget[] = []\n for (const { widgetId, placement, priority } of entries) {\n const widget = await loadInjectionWidgetById(widgetId)\n if (!widget) continue\n const combinedPlacement = placement\n ? { ...placement, priority: typeof priority === 'number' ? priority : 0 }\n : { priority: typeof priority === 'number' ? priority : 0 }\n widgets.push({ ...widget, placement: combinedPlacement })\n }\n return widgets\n}\n\nexport async function loadInjectionDataWidgetsForSpot(spotId: InjectionSpotId): Promise<LoadedInjectionDataWidget[]> {\n const entries = await getResolvedEntriesForSpot(spotId)\n const widgets: LoadedInjectionDataWidget[] = []\n for (const { widgetId, placement, priority } of entries) {\n const widget = await loadInjectionDataWidgetById(widgetId)\n if (!widget) continue\n const combinedPlacement = placement\n ? { ...placement, priority: typeof priority === 'number' ? priority : 0 }\n : { priority: typeof priority === 'number' ? priority : 0 }\n widgets.push({ ...widget, placement: combinedPlacement })\n }\n return widgets\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import type { ModuleInjectionWidgetEntry } from '../registry'\nimport type {\n InjectionAnyWidgetModule,\n InjectionDataWidgetModule,\n InjectionWidgetMetadata,\n InjectionWidgetModule,\n InjectionSpotId,\n ModuleInjectionSlot,\n ModuleInjectionTable,\n InjectionWidgetPlacement,\n} from './injection'\n\ntype LoadedWidgetModule = InjectionWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }\ntype LoadedDataWidgetModule = InjectionDataWidgetModule & { metadata: InjectionWidgetMetadata }\n\nexport type LoadedInjectionWidget = LoadedWidgetModule & {\n moduleId: string\n key: string\n placement?: {\n groupId?: string\n groupLabel?: string\n groupDescription?: string\n column?: 1 | 2\n kind?: 'tab' | 'group' | 'stack'\n [k: string]: unknown\n }\n}\n\nexport type LoadedInjectionDataWidget = LoadedDataWidgetModule & {\n moduleId: string\n key: string\n placement?: {\n groupId?: string\n groupLabel?: string\n groupDescription?: string\n column?: 1 | 2\n kind?: 'tab' | 'group' | 'stack'\n [k: string]: unknown\n }\n}\n\ntype WidgetEntry = ModuleInjectionWidgetEntry & { moduleId: string }\n\n// Registration pattern for publishable packages\nlet _coreInjectionWidgetEntries: ModuleInjectionWidgetEntry[] | null = null\nlet _coreInjectionTables: Array<{ moduleId: string; table: ModuleInjectionTable }> | null = null\nlet _enabledModuleIds: ReadonlySet<string> | null = null\nlet _injectionRegistryVersion = 0\nconst GLOBAL_INJECTION_WIDGETS_KEY = '__openMercatoCoreInjectionWidgetEntries__'\nconst GLOBAL_INJECTION_TABLES_KEY = '__openMercatoCoreInjectionTables__'\nconst GLOBAL_ENABLED_MODULE_IDS_KEY = '__openMercatoEnabledModuleIds__'\nconst GLOBAL_INJECTION_REGISTRY_VERSION_KEY = '__openMercatoCoreInjectionRegistryVersion__'\nconst INJECTION_REGISTRY_CHANGED_EVENT = '__openMercatoInjectionRegistryChanged__'\n\nfunction readGlobalInjectionWidgets(): ModuleInjectionWidgetEntry[] | null {\n try {\n const value = (globalThis as Record<string, unknown>)[GLOBAL_INJECTION_WIDGETS_KEY]\n return Array.isArray(value) ? (value as ModuleInjectionWidgetEntry[]) : null\n } catch {\n return null\n }\n}\n\nfunction writeGlobalInjectionWidgets(entries: ModuleInjectionWidgetEntry[]) {\n try {\n ;(globalThis as Record<string, unknown>)[GLOBAL_INJECTION_WIDGETS_KEY] = entries\n } catch {\n // ignore global assignment failures\n }\n}\n\nfunction readGlobalEnabledModuleIds(): ReadonlySet<string> | null {\n try {\n const value = (globalThis as Record<string, unknown>)[GLOBAL_ENABLED_MODULE_IDS_KEY]\n if (value instanceof Set) return value as ReadonlySet<string>\n return null\n } catch {\n return null\n }\n}\n\nfunction writeGlobalEnabledModuleIds(ids: ReadonlySet<string>) {\n try {\n ;(globalThis as Record<string, unknown>)[GLOBAL_ENABLED_MODULE_IDS_KEY] = ids\n } catch {\n // ignore global assignment failures\n }\n}\n\nfunction readGlobalInjectionTables(): Array<{ moduleId: string; table: ModuleInjectionTable }> | null {\n try {\n const value = (globalThis as Record<string, unknown>)[GLOBAL_INJECTION_TABLES_KEY]\n return Array.isArray(value) ? (value as Array<{ moduleId: string; table: ModuleInjectionTable }>) : null\n } catch {\n return null\n }\n}\n\nfunction writeGlobalInjectionTables(tables: Array<{ moduleId: string; table: ModuleInjectionTable }>) {\n try {\n ;(globalThis as Record<string, unknown>)[GLOBAL_INJECTION_TABLES_KEY] = tables\n } catch {\n // ignore global assignment failures\n }\n}\n\nfunction readGlobalInjectionRegistryVersion(): number | null {\n try {\n const value = (globalThis as Record<string, unknown>)[GLOBAL_INJECTION_REGISTRY_VERSION_KEY]\n return typeof value === 'number' ? value : null\n } catch {\n return null\n }\n}\n\nfunction writeGlobalInjectionRegistryVersion(version: number) {\n try {\n ;(globalThis as Record<string, unknown>)[GLOBAL_INJECTION_REGISTRY_VERSION_KEY] = version\n } catch {\n // ignore global assignment failures\n }\n}\n\nfunction notifyInjectionRegistryChanged() {\n _injectionRegistryVersion += 1\n writeGlobalInjectionRegistryVersion(_injectionRegistryVersion)\n invalidateInjectionWidgetCache()\n\n if (typeof window === 'undefined') return\n\n window.dispatchEvent(new CustomEvent(INJECTION_REGISTRY_CHANGED_EVENT, {\n detail: { version: _injectionRegistryVersion },\n }))\n}\n\nexport function registerCoreInjectionWidgets(entries: ModuleInjectionWidgetEntry[]) {\n if (_coreInjectionWidgetEntries !== null && process.env.NODE_ENV === 'development') {\n console.debug('[Bootstrap] Core injection widgets re-registered (this may occur during HMR)')\n }\n _coreInjectionWidgetEntries = entries\n writeGlobalInjectionWidgets(entries)\n notifyInjectionRegistryChanged()\n}\n\nexport function getCoreInjectionWidgets(): ModuleInjectionWidgetEntry[] {\n const globalEntries = readGlobalInjectionWidgets()\n if (globalEntries) return globalEntries\n if (!_coreInjectionWidgetEntries) {\n // On client-side, bootstrap doesn't run - return empty array gracefully\n if (typeof window !== 'undefined') {\n return []\n }\n throw new Error('[Bootstrap] Core injection widgets not registered. Call registerCoreInjectionWidgets() at bootstrap.')\n }\n return _coreInjectionWidgetEntries\n}\n\nexport function registerCoreInjectionTables(tables: Array<{ moduleId: string; table: ModuleInjectionTable }>) {\n if (_coreInjectionTables !== null && process.env.NODE_ENV === 'development') {\n console.debug('[Bootstrap] Core injection tables re-registered (this may occur during HMR)')\n }\n _coreInjectionTables = tables\n writeGlobalInjectionTables(tables)\n notifyInjectionRegistryChanged()\n}\n\n/**\n * Register the canonical set of enabled module IDs for the running app.\n *\n * This is the authoritative signal used by `requiredModules` widget gating \u2014\n * deriving \"enabled\" from injection tables or widget entries is unreliable\n * because modules without injection widgets (for example `ai_assistant`) do\n * not contribute entries to either source. Bootstrap callers should pass\n * every module ID present in the app's module registry.\n */\nexport function registerEnabledModuleIds(moduleIds: Iterable<string>) {\n const next = new Set<string>()\n for (const moduleId of moduleIds) {\n if (typeof moduleId === 'string' && moduleId.length > 0) next.add(moduleId)\n }\n if (_enabledModuleIds !== null && process.env.NODE_ENV === 'development') {\n console.debug('[Bootstrap] Enabled module IDs re-registered (this may occur during HMR)')\n }\n _enabledModuleIds = next\n writeGlobalEnabledModuleIds(next)\n notifyInjectionRegistryChanged()\n}\n\nexport function getEnabledModuleIds(): ReadonlySet<string> | null {\n return readGlobalEnabledModuleIds() ?? _enabledModuleIds\n}\n\nexport function getInjectionRegistryVersion(): number {\n const globalVersion = readGlobalInjectionRegistryVersion()\n if (globalVersion !== null) return globalVersion\n return _injectionRegistryVersion\n}\n\nexport function subscribeToInjectionRegistryChanges(listener: () => void): () => void {\n if (typeof window === 'undefined') {\n return () => {}\n }\n\n const handler = () => listener()\n window.addEventListener(INJECTION_REGISTRY_CHANGED_EVENT, handler)\n return () => {\n window.removeEventListener(INJECTION_REGISTRY_CHANGED_EVENT, handler)\n }\n}\n\nexport function getCoreInjectionTables(): Array<{ moduleId: string; table: ModuleInjectionTable }> {\n const globalTables = readGlobalInjectionTables()\n if (globalTables) return globalTables\n if (!_coreInjectionTables) {\n // On client-side, bootstrap doesn't run - return empty array gracefully\n if (typeof window !== 'undefined') {\n return []\n }\n throw new Error('[Bootstrap] Core injection tables not registered. Call registerCoreInjectionTables() at bootstrap.')\n }\n return _coreInjectionTables\n}\n\nlet widgetEntriesPromise: Promise<WidgetEntry[]> | null = null\ntype TableEntry = {\n widgetId: string\n moduleId: string\n priority: number\n placement?: ModuleInjectionSlot extends infer S\n ? S extends { widgetId: string }\n ? Omit<S, 'widgetId' | 'priority'>\n : never\n : never\n}\nlet injectionTablePromise: Promise<Map<InjectionSpotId, TableEntry[]>> | null = null\n\nfunction isInjectionSlotObject(value: ModuleInjectionSlot): value is InjectionWidgetPlacement & { widgetId: string; priority?: number } {\n return typeof value === 'object' && value !== null && 'widgetId' in value\n}\n\n/**\n * Invalidate the widget entries and widget module cache.\n * Call this when the generated registry is updated or modules are reloaded.\n */\nexport function invalidateInjectionWidgetCache() {\n widgetEntriesPromise = null\n injectionTablePromise = null\n widgetCache.clear()\n warnedRequiredModuleSkips.clear()\n}\n\nasync function loadWidgetEntries(): Promise<WidgetEntry[]> {\n if (!widgetEntriesPromise) {\n const promise = Promise.resolve().then(() =>\n getCoreInjectionWidgets().map((entry) => ({\n ...entry,\n moduleId: entry.moduleId || 'unknown',\n }))\n )\n widgetEntriesPromise = promise.catch((err) => {\n if (widgetEntriesPromise === promise) {\n widgetEntriesPromise = null\n }\n throw err\n })\n }\n return widgetEntriesPromise\n}\n\nasync function loadInjectionTable(): Promise<Map<InjectionSpotId, TableEntry[]>> {\n if (!injectionTablePromise) {\n const promise = Promise.resolve().then(() => {\n const list = getCoreInjectionTables()\n const table = new Map<InjectionSpotId, TableEntry[]>()\n\n for (const entry of list) {\n const injectionTable = entry.table ?? {}\n for (const [spotId, widgetIds] of Object.entries(injectionTable)) {\n const widgets = Array.isArray(widgetIds) ? widgetIds : [widgetIds]\n const existing = table.get(spotId) ?? []\n for (const widgetEntry of widgets) {\n if (typeof widgetEntry === 'string') {\n existing.push({ widgetId: widgetEntry, moduleId: entry.moduleId, priority: 0 })\n continue\n }\n if (isInjectionSlotObject(widgetEntry)) {\n const { widgetId, priority = 0, ...placement } = widgetEntry\n existing.push({\n widgetId,\n moduleId: entry.moduleId,\n priority: typeof priority === 'number' ? priority : 0,\n placement,\n })\n continue\n }\n }\n table.set(spotId, existing)\n }\n }\n\n for (const [spotId, widgets] of table.entries()) {\n table.set(spotId, widgets.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)))\n }\n\n return table\n })\n injectionTablePromise = promise.catch((err) => {\n if (injectionTablePromise === promise) {\n injectionTablePromise = null\n }\n throw err\n })\n }\n return injectionTablePromise\n}\n\nconst widgetCache = new Map<string, Promise<InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }>>()\n\nfunction isDataWidgetModule(widget: Record<string, unknown>): widget is LoadedDataWidgetModule {\n const keys = [\n 'columns',\n 'rowActions',\n 'bulkActions',\n 'filters',\n 'fields',\n 'steps',\n 'badge',\n 'menuItems',\n ]\n return keys.some((key) => key in widget)\n}\n\nfunction ensureValidInjectionModule(mod: unknown, key: string, moduleId: string): (InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }) {\n if (!mod || typeof mod !== 'object') {\n throw new Error(`Invalid injection widget module \"${key}\" from \"${moduleId}\" (expected object export)`)\n }\n const widget = (mod as { default?: InjectionAnyWidgetModule<any, any> }).default ?? (mod as InjectionAnyWidgetModule<any, any>)\n if (!widget || typeof widget !== 'object') {\n throw new Error(`Invalid injection widget export \"${key}\" from \"${moduleId}\" (missing default export)`) \n }\n if (!('metadata' in widget) || !widget.metadata || typeof widget.metadata !== 'object') {\n throw new Error(`Injection widget \"${key}\" from \"${moduleId}\" is missing metadata`)\n }\n const metadata = widget.metadata\n if (typeof metadata.id !== 'string' || metadata.id.length === 0) {\n throw new Error(`Injection widget \"${key}\" from \"${moduleId}\" metadata.id must be a non-empty string`)\n }\n const normalized = {\n ...widget,\n metadata,\n }\n\n if ('Widget' in normalized && typeof normalized.Widget === 'function') {\n if (typeof metadata.title !== 'string' || metadata.title.length === 0) {\n throw new Error(`Injection widget \"${metadata.id}\" from \"${moduleId}\" must have a title`)\n }\n return normalized\n }\n\n if (!isDataWidgetModule(normalized as Record<string, unknown>)) {\n throw new Error(\n `Injection widget \"${metadata.id}\" from \"${moduleId}\" must export either Widget component or a declarative data payload`\n )\n }\n\n return normalized\n}\n\nfunction isLoadedInjectionWidget(\n module: InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }\n): module is LoadedWidgetModule {\n return 'Widget' in module && typeof module.Widget === 'function'\n}\n\nfunction isLoadedInjectionDataWidget(\n module: InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }\n): module is LoadedDataWidgetModule {\n return !isLoadedInjectionWidget(module)\n}\n\nasync function loadEntry(entry: WidgetEntry): Promise<InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }> {\n if (!widgetCache.has(entry.key)) {\n const promise = entry.loader().then((mod) => ensureValidInjectionModule(mod, entry.key, entry.moduleId))\n widgetCache.set(entry.key, promise)\n }\n return widgetCache.get(entry.key)!\n}\n\nfunction getEnabledModuleIdsForInjection(): ReadonlySet<string> {\n // Prefer the explicit enabled-modules registry populated by bootstrap.\n // This is the only signal that includes modules without injection widgets\n // (for example `ai_assistant`), so it is required for `requiredModules`\n // gating to be sound.\n const explicit = readGlobalEnabledModuleIds() ?? _enabledModuleIds\n if (explicit) return explicit\n\n // Fallback: derive from injection tables and widget entries. This keeps\n // older bootstrap paths (and callers that have not yet wired\n // `registerEnabledModuleIds`) working \u2014 at the cost of mis-classifying\n // dependency modules that ship no widgets. New apps MUST call\n // `registerEnabledModuleIds` to get accurate gating.\n const enabled = new Set<string>()\n const tables = readGlobalInjectionTables() ?? _coreInjectionTables ?? []\n for (const entry of tables) {\n if (entry?.moduleId) enabled.add(entry.moduleId)\n }\n const entries = readGlobalInjectionWidgets() ?? _coreInjectionWidgetEntries ?? []\n for (const entry of entries) {\n if (entry?.moduleId) enabled.add(entry.moduleId)\n }\n return enabled\n}\n\nfunction widgetMissingRequiredModules(\n metadata: InjectionWidgetMetadata,\n enabledModuleIds: ReadonlySet<string>,\n): string[] {\n const required = metadata.requiredModules\n if (!Array.isArray(required) || required.length === 0) return []\n const missing: string[] = []\n for (const moduleId of required) {\n if (typeof moduleId !== 'string' || moduleId.length === 0) continue\n if (!enabledModuleIds.has(moduleId)) missing.push(moduleId)\n }\n return missing\n}\n\nconst warnedRequiredModuleSkips = new Set<string>()\n\nfunction warnSkippedWidget(metadataId: string, missingModules: string[]) {\n const key = `${metadataId}:${missingModules.join(',')}`\n if (warnedRequiredModuleSkips.has(key)) return\n warnedRequiredModuleSkips.add(key)\n if (process.env.NODE_ENV === 'development') {\n console.debug(\n `[InjectionLoader] Skipping widget \"${metadataId}\" \u2014 required module(s) not enabled: ${missingModules.join(', ')}`,\n )\n }\n}\n\nasync function getResolvedEntriesForSpot(spotId: InjectionSpotId): Promise<TableEntry[]> {\n const table = await loadInjectionTable()\n const exactEntries = table.get(spotId) ?? []\n const wildcardEntries: TableEntry[] = []\n\n for (const [candidateSpotId, candidateEntries] of table.entries()) {\n if (candidateSpotId === spotId) continue\n if (!candidateSpotId.includes('*')) continue\n const pattern = new RegExp(`^${candidateSpotId.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&').replace(/\\*/g, '.*')}$`)\n if (!pattern.test(spotId)) continue\n wildcardEntries.push(...candidateEntries)\n }\n\n const dedupedEntries = new Map<string, TableEntry>()\n for (const entry of [...exactEntries, ...wildcardEntries]) {\n const cacheKey = `${entry.moduleId}:${entry.widgetId}`\n const previous = dedupedEntries.get(cacheKey)\n if (!previous || (entry.priority ?? 0) > (previous.priority ?? 0)) {\n dedupedEntries.set(cacheKey, entry)\n }\n }\n\n return Array.from(dedupedEntries.values()).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0))\n}\n\nexport async function loadAllInjectionWidgets(): Promise<LoadedInjectionWidget[]> {\n const widgetEntries = await loadWidgetEntries()\n const enabledModuleIds = getEnabledModuleIdsForInjection()\n const loaded = await Promise.all(\n widgetEntries.map(async (entry) => {\n const module = await loadEntry(entry)\n if (!isLoadedInjectionWidget(module)) return null\n const missing = widgetMissingRequiredModules(module.metadata, enabledModuleIds)\n if (missing.length > 0) {\n warnSkippedWidget(module.metadata.id, missing)\n return null\n }\n return { ...module, moduleId: entry.moduleId, key: entry.key }\n })\n )\n const byId = new Map<string, LoadedInjectionWidget>()\n for (const widget of loaded) {\n if (!widget) continue\n if (!byId.has(widget.metadata.id)) {\n byId.set(widget.metadata.id, widget)\n }\n }\n return Array.from(byId.values())\n}\n\nexport async function loadInjectionWidgetById(widgetId: string): Promise<LoadedInjectionWidget | null> {\n const widgetEntries = await loadWidgetEntries()\n const enabledModuleIds = getEnabledModuleIdsForInjection()\n for (const entry of widgetEntries) {\n const module = await loadEntry(entry)\n if (!isLoadedInjectionWidget(module)) continue\n if (module.metadata.id === widgetId) {\n const missing = widgetMissingRequiredModules(module.metadata, enabledModuleIds)\n if (missing.length > 0) {\n warnSkippedWidget(module.metadata.id, missing)\n return null\n }\n return { ...module, moduleId: entry.moduleId, key: entry.key }\n }\n }\n return null\n}\n\nexport async function loadInjectionDataWidgetById(widgetId: string): Promise<LoadedInjectionDataWidget | null> {\n const widgetEntries = await loadWidgetEntries()\n const enabledModuleIds = getEnabledModuleIdsForInjection()\n for (const entry of widgetEntries) {\n const module = await loadEntry(entry)\n if (!isLoadedInjectionDataWidget(module)) continue\n if (module.metadata.id === widgetId) {\n const missing = widgetMissingRequiredModules(module.metadata, enabledModuleIds)\n if (missing.length > 0) {\n warnSkippedWidget(module.metadata.id, missing)\n return null\n }\n return { ...module, moduleId: entry.moduleId, key: entry.key }\n }\n }\n return null\n}\n\nexport async function loadInjectionWidgetsForSpot(spotId: InjectionSpotId): Promise<LoadedInjectionWidget[]> {\n const entries = await getResolvedEntriesForSpot(spotId)\n const widgets: LoadedInjectionWidget[] = []\n for (const { widgetId, placement, priority } of entries) {\n const widget = await loadInjectionWidgetById(widgetId)\n if (!widget) continue\n const combinedPlacement = placement\n ? { ...placement, priority: typeof priority === 'number' ? priority : 0 }\n : { priority: typeof priority === 'number' ? priority : 0 }\n widgets.push({ ...widget, placement: combinedPlacement })\n }\n return widgets\n}\n\nexport async function loadInjectionDataWidgetsForSpot(spotId: InjectionSpotId): Promise<LoadedInjectionDataWidget[]> {\n const entries = await getResolvedEntriesForSpot(spotId)\n const widgets: LoadedInjectionDataWidget[] = []\n for (const { widgetId, placement, priority } of entries) {\n const widget = await loadInjectionDataWidgetById(widgetId)\n if (!widget) continue\n const combinedPlacement = placement\n ? { ...placement, priority: typeof priority === 'number' ? priority : 0 }\n : { priority: typeof priority === 'number' ? priority : 0 }\n widgets.push({ ...widget, placement: combinedPlacement })\n }\n return widgets\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/shared",
|
|
3
|
-
"version": "0.6.1-develop.
|
|
3
|
+
"version": "0.6.1-develop.3188.1.499d530779",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"@mikro-orm/core": "^7.0.14",
|
|
93
93
|
"@mikro-orm/decorators": "^7.0.14",
|
|
94
94
|
"@mikro-orm/postgresql": "^7.0.14",
|
|
95
|
-
"@open-mercato/cache": "0.6.1-develop.
|
|
95
|
+
"@open-mercato/cache": "0.6.1-develop.3188.1.499d530779",
|
|
96
96
|
"dotenv": "^17.4.2",
|
|
97
97
|
"rate-limiter-flexible": "^11.0.1",
|
|
98
98
|
"reflect-metadata": "^0.2.2",
|
|
@@ -133,6 +133,9 @@ async function registerWidgetsAndOptionalPackages(data: BootstrapData, options:
|
|
|
133
133
|
injectionRegistry.registerInjectionWidgets(data.injectionWidgetEntries)
|
|
134
134
|
coreInjection.registerCoreInjectionWidgets(data.injectionWidgetEntries)
|
|
135
135
|
coreInjection.registerCoreInjectionTables(data.injectionTables)
|
|
136
|
+
coreInjection.registerEnabledModuleIds(
|
|
137
|
+
data.modules.map((module) => module.id).filter((id): id is string => typeof id === 'string' && id.length > 0),
|
|
138
|
+
)
|
|
136
139
|
} catch {
|
|
137
140
|
// UI packages may not be available in all contexts
|
|
138
141
|
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*
|
|
4
|
+
* Regression coverage for issue #1849 — widgets that integrate with another
|
|
5
|
+
* module (for example a customers AI trigger that calls
|
|
6
|
+
* `/api/ai_assistant/...`) must be skipped at load time when the dependency
|
|
7
|
+
* module is not enabled in `modules.ts`. Feature gates alone are insufficient
|
|
8
|
+
* because superadmin / wildcard grants pass them even when the dependency
|
|
9
|
+
* module is absent.
|
|
10
|
+
*/
|
|
11
|
+
import { describe, it, expect, beforeEach } from '@jest/globals'
|
|
12
|
+
import type {
|
|
13
|
+
InjectionWidgetModule,
|
|
14
|
+
ModuleInjectionTable,
|
|
15
|
+
} from '@open-mercato/shared/modules/widgets/injection'
|
|
16
|
+
import type { ModuleInjectionWidgetEntry } from '@open-mercato/shared/modules/registry'
|
|
17
|
+
import {
|
|
18
|
+
invalidateInjectionWidgetCache,
|
|
19
|
+
loadInjectionWidgetById,
|
|
20
|
+
loadInjectionWidgetsForSpot,
|
|
21
|
+
registerCoreInjectionTables,
|
|
22
|
+
registerCoreInjectionWidgets,
|
|
23
|
+
registerEnabledModuleIds,
|
|
24
|
+
} from '@open-mercato/shared/modules/widgets/injection-loader'
|
|
25
|
+
|
|
26
|
+
const HOST_SPOT_ID = 'data-table:host.list:search-trailing'
|
|
27
|
+
const ALWAYS_AVAILABLE_WIDGET_ID = 'host.injection.always-available'
|
|
28
|
+
const REQUIRES_AI_WIDGET_ID = 'host.injection.requires-ai-assistant'
|
|
29
|
+
|
|
30
|
+
const PlaceholderComponent = () => null
|
|
31
|
+
|
|
32
|
+
const alwaysAvailableWidget: InjectionWidgetModule<Record<string, unknown>, Record<string, unknown>> = {
|
|
33
|
+
metadata: {
|
|
34
|
+
id: ALWAYS_AVAILABLE_WIDGET_ID,
|
|
35
|
+
title: 'Always Available',
|
|
36
|
+
description: 'Has no module dependencies and must always load.',
|
|
37
|
+
priority: 50,
|
|
38
|
+
enabled: true,
|
|
39
|
+
},
|
|
40
|
+
Widget: PlaceholderComponent,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const requiresAiAssistantWidget: InjectionWidgetModule<Record<string, unknown>, Record<string, unknown>> = {
|
|
44
|
+
metadata: {
|
|
45
|
+
id: REQUIRES_AI_WIDGET_ID,
|
|
46
|
+
title: 'Requires AI Assistant',
|
|
47
|
+
description: 'Calls /api/ai_assistant — must be skipped when ai_assistant is disabled.',
|
|
48
|
+
requiredModules: ['ai_assistant'],
|
|
49
|
+
priority: 100,
|
|
50
|
+
enabled: true,
|
|
51
|
+
},
|
|
52
|
+
Widget: PlaceholderComponent,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function makeWidgetEntry(
|
|
56
|
+
moduleId: string,
|
|
57
|
+
key: string,
|
|
58
|
+
loader: () => Promise<InjectionWidgetModule<any, any>>,
|
|
59
|
+
): ModuleInjectionWidgetEntry {
|
|
60
|
+
return {
|
|
61
|
+
moduleId,
|
|
62
|
+
key,
|
|
63
|
+
source: 'package',
|
|
64
|
+
loader,
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function registerHostFixtures(includeAiAssistantModule: boolean) {
|
|
69
|
+
invalidateInjectionWidgetCache()
|
|
70
|
+
const widgetEntries: ModuleInjectionWidgetEntry[] = [
|
|
71
|
+
makeWidgetEntry('host', 'host/widgets/injection/always-available/widget.ts', async () => alwaysAvailableWidget),
|
|
72
|
+
makeWidgetEntry('host', 'host/widgets/injection/requires-ai/widget.ts', async () => requiresAiAssistantWidget),
|
|
73
|
+
]
|
|
74
|
+
registerCoreInjectionWidgets(widgetEntries)
|
|
75
|
+
|
|
76
|
+
const tables: Array<{ moduleId: string; table: ModuleInjectionTable }> = [
|
|
77
|
+
{
|
|
78
|
+
moduleId: 'host',
|
|
79
|
+
table: {
|
|
80
|
+
[HOST_SPOT_ID]: [
|
|
81
|
+
{ widgetId: ALWAYS_AVAILABLE_WIDGET_ID, priority: 50 },
|
|
82
|
+
{ widgetId: REQUIRES_AI_WIDGET_ID, priority: 100 },
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
]
|
|
87
|
+
if (includeAiAssistantModule) {
|
|
88
|
+
tables.push({ moduleId: 'ai_assistant', table: {} })
|
|
89
|
+
}
|
|
90
|
+
registerCoreInjectionTables(tables)
|
|
91
|
+
|
|
92
|
+
const enabledIds = ['host']
|
|
93
|
+
if (includeAiAssistantModule) enabledIds.push('ai_assistant')
|
|
94
|
+
registerEnabledModuleIds(enabledIds)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function registerHostFixturesUsingExplicitRegistryOnly(includeAiAssistantModule: boolean) {
|
|
98
|
+
// Simulates the real generator output: a dependency module (ai_assistant)
|
|
99
|
+
// is enabled in modules.ts but ships NO widgets/injection-table.ts, so it
|
|
100
|
+
// does not appear in injectionTables. The explicit enabled-modules registry
|
|
101
|
+
// is the only signal that proves it is enabled.
|
|
102
|
+
invalidateInjectionWidgetCache()
|
|
103
|
+
const widgetEntries: ModuleInjectionWidgetEntry[] = [
|
|
104
|
+
makeWidgetEntry('host', 'host/widgets/injection/always-available/widget.ts', async () => alwaysAvailableWidget),
|
|
105
|
+
makeWidgetEntry('host', 'host/widgets/injection/requires-ai/widget.ts', async () => requiresAiAssistantWidget),
|
|
106
|
+
]
|
|
107
|
+
registerCoreInjectionWidgets(widgetEntries)
|
|
108
|
+
|
|
109
|
+
registerCoreInjectionTables([
|
|
110
|
+
{
|
|
111
|
+
moduleId: 'host',
|
|
112
|
+
table: {
|
|
113
|
+
[HOST_SPOT_ID]: [
|
|
114
|
+
{ widgetId: ALWAYS_AVAILABLE_WIDGET_ID, priority: 50 },
|
|
115
|
+
{ widgetId: REQUIRES_AI_WIDGET_ID, priority: 100 },
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
])
|
|
120
|
+
|
|
121
|
+
const enabledIds = ['host']
|
|
122
|
+
if (includeAiAssistantModule) enabledIds.push('ai_assistant')
|
|
123
|
+
registerEnabledModuleIds(enabledIds)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
describe('Injection loader — requiredModules gating (#1849)', () => {
|
|
127
|
+
beforeEach(() => {
|
|
128
|
+
invalidateInjectionWidgetCache()
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('loads a widget that requires a module that IS enabled', async () => {
|
|
132
|
+
registerHostFixtures(true)
|
|
133
|
+
const loaded = await loadInjectionWidgetsForSpot(HOST_SPOT_ID)
|
|
134
|
+
const ids = loaded.map((widget) => widget.metadata.id).sort()
|
|
135
|
+
expect(ids).toEqual([REQUIRES_AI_WIDGET_ID, ALWAYS_AVAILABLE_WIDGET_ID].sort())
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('skips a widget whose required module is NOT enabled', async () => {
|
|
139
|
+
registerHostFixtures(false)
|
|
140
|
+
const loaded = await loadInjectionWidgetsForSpot(HOST_SPOT_ID)
|
|
141
|
+
const ids = loaded.map((widget) => widget.metadata.id)
|
|
142
|
+
expect(ids).toContain(ALWAYS_AVAILABLE_WIDGET_ID)
|
|
143
|
+
expect(ids).not.toContain(REQUIRES_AI_WIDGET_ID)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('returns null from loadInjectionWidgetById when required module is missing', async () => {
|
|
147
|
+
registerHostFixtures(false)
|
|
148
|
+
const widget = await loadInjectionWidgetById(REQUIRES_AI_WIDGET_ID)
|
|
149
|
+
expect(widget).toBeNull()
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('returns the widget from loadInjectionWidgetById when required module is enabled', async () => {
|
|
153
|
+
registerHostFixtures(true)
|
|
154
|
+
const widget = await loadInjectionWidgetById(REQUIRES_AI_WIDGET_ID)
|
|
155
|
+
expect(widget).not.toBeNull()
|
|
156
|
+
expect(widget?.metadata.id).toBe(REQUIRES_AI_WIDGET_ID)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it('loads a widget when its required module ships NO injection table but is in the enabled-modules registry', async () => {
|
|
160
|
+
// Regression: dependency modules without widgets/injection-table.ts
|
|
161
|
+
// (for example `ai_assistant`) must still satisfy `requiredModules`
|
|
162
|
+
// checks once they are registered as enabled at bootstrap.
|
|
163
|
+
registerHostFixturesUsingExplicitRegistryOnly(true)
|
|
164
|
+
const loaded = await loadInjectionWidgetsForSpot(HOST_SPOT_ID)
|
|
165
|
+
const ids = loaded.map((widget) => widget.metadata.id).sort()
|
|
166
|
+
expect(ids).toEqual([REQUIRES_AI_WIDGET_ID, ALWAYS_AVAILABLE_WIDGET_ID].sort())
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('skips a widget when its required module is absent from the explicit enabled-modules registry', async () => {
|
|
170
|
+
registerHostFixturesUsingExplicitRegistryOnly(false)
|
|
171
|
+
const loaded = await loadInjectionWidgetsForSpot(HOST_SPOT_ID)
|
|
172
|
+
const ids = loaded.map((widget) => widget.metadata.id)
|
|
173
|
+
expect(ids).toContain(ALWAYS_AVAILABLE_WIDGET_ID)
|
|
174
|
+
expect(ids).not.toContain(REQUIRES_AI_WIDGET_ID)
|
|
175
|
+
})
|
|
176
|
+
})
|
|
@@ -44,9 +44,11 @@ type WidgetEntry = ModuleInjectionWidgetEntry & { moduleId: string }
|
|
|
44
44
|
// Registration pattern for publishable packages
|
|
45
45
|
let _coreInjectionWidgetEntries: ModuleInjectionWidgetEntry[] | null = null
|
|
46
46
|
let _coreInjectionTables: Array<{ moduleId: string; table: ModuleInjectionTable }> | null = null
|
|
47
|
+
let _enabledModuleIds: ReadonlySet<string> | null = null
|
|
47
48
|
let _injectionRegistryVersion = 0
|
|
48
49
|
const GLOBAL_INJECTION_WIDGETS_KEY = '__openMercatoCoreInjectionWidgetEntries__'
|
|
49
50
|
const GLOBAL_INJECTION_TABLES_KEY = '__openMercatoCoreInjectionTables__'
|
|
51
|
+
const GLOBAL_ENABLED_MODULE_IDS_KEY = '__openMercatoEnabledModuleIds__'
|
|
50
52
|
const GLOBAL_INJECTION_REGISTRY_VERSION_KEY = '__openMercatoCoreInjectionRegistryVersion__'
|
|
51
53
|
const INJECTION_REGISTRY_CHANGED_EVENT = '__openMercatoInjectionRegistryChanged__'
|
|
52
54
|
|
|
@@ -67,6 +69,24 @@ function writeGlobalInjectionWidgets(entries: ModuleInjectionWidgetEntry[]) {
|
|
|
67
69
|
}
|
|
68
70
|
}
|
|
69
71
|
|
|
72
|
+
function readGlobalEnabledModuleIds(): ReadonlySet<string> | null {
|
|
73
|
+
try {
|
|
74
|
+
const value = (globalThis as Record<string, unknown>)[GLOBAL_ENABLED_MODULE_IDS_KEY]
|
|
75
|
+
if (value instanceof Set) return value as ReadonlySet<string>
|
|
76
|
+
return null
|
|
77
|
+
} catch {
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function writeGlobalEnabledModuleIds(ids: ReadonlySet<string>) {
|
|
83
|
+
try {
|
|
84
|
+
;(globalThis as Record<string, unknown>)[GLOBAL_ENABLED_MODULE_IDS_KEY] = ids
|
|
85
|
+
} catch {
|
|
86
|
+
// ignore global assignment failures
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
70
90
|
function readGlobalInjectionTables(): Array<{ moduleId: string; table: ModuleInjectionTable }> | null {
|
|
71
91
|
try {
|
|
72
92
|
const value = (globalThis as Record<string, unknown>)[GLOBAL_INJECTION_TABLES_KEY]
|
|
@@ -144,6 +164,32 @@ export function registerCoreInjectionTables(tables: Array<{ moduleId: string; ta
|
|
|
144
164
|
notifyInjectionRegistryChanged()
|
|
145
165
|
}
|
|
146
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Register the canonical set of enabled module IDs for the running app.
|
|
169
|
+
*
|
|
170
|
+
* This is the authoritative signal used by `requiredModules` widget gating —
|
|
171
|
+
* deriving "enabled" from injection tables or widget entries is unreliable
|
|
172
|
+
* because modules without injection widgets (for example `ai_assistant`) do
|
|
173
|
+
* not contribute entries to either source. Bootstrap callers should pass
|
|
174
|
+
* every module ID present in the app's module registry.
|
|
175
|
+
*/
|
|
176
|
+
export function registerEnabledModuleIds(moduleIds: Iterable<string>) {
|
|
177
|
+
const next = new Set<string>()
|
|
178
|
+
for (const moduleId of moduleIds) {
|
|
179
|
+
if (typeof moduleId === 'string' && moduleId.length > 0) next.add(moduleId)
|
|
180
|
+
}
|
|
181
|
+
if (_enabledModuleIds !== null && process.env.NODE_ENV === 'development') {
|
|
182
|
+
console.debug('[Bootstrap] Enabled module IDs re-registered (this may occur during HMR)')
|
|
183
|
+
}
|
|
184
|
+
_enabledModuleIds = next
|
|
185
|
+
writeGlobalEnabledModuleIds(next)
|
|
186
|
+
notifyInjectionRegistryChanged()
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function getEnabledModuleIds(): ReadonlySet<string> | null {
|
|
190
|
+
return readGlobalEnabledModuleIds() ?? _enabledModuleIds
|
|
191
|
+
}
|
|
192
|
+
|
|
147
193
|
export function getInjectionRegistryVersion(): number {
|
|
148
194
|
const globalVersion = readGlobalInjectionRegistryVersion()
|
|
149
195
|
if (globalVersion !== null) return globalVersion
|
|
@@ -200,6 +246,7 @@ export function invalidateInjectionWidgetCache() {
|
|
|
200
246
|
widgetEntriesPromise = null
|
|
201
247
|
injectionTablePromise = null
|
|
202
248
|
widgetCache.clear()
|
|
249
|
+
warnedRequiredModuleSkips.clear()
|
|
203
250
|
}
|
|
204
251
|
|
|
205
252
|
async function loadWidgetEntries(): Promise<WidgetEntry[]> {
|
|
@@ -339,6 +386,58 @@ async function loadEntry(entry: WidgetEntry): Promise<InjectionAnyWidgetModule<a
|
|
|
339
386
|
return widgetCache.get(entry.key)!
|
|
340
387
|
}
|
|
341
388
|
|
|
389
|
+
function getEnabledModuleIdsForInjection(): ReadonlySet<string> {
|
|
390
|
+
// Prefer the explicit enabled-modules registry populated by bootstrap.
|
|
391
|
+
// This is the only signal that includes modules without injection widgets
|
|
392
|
+
// (for example `ai_assistant`), so it is required for `requiredModules`
|
|
393
|
+
// gating to be sound.
|
|
394
|
+
const explicit = readGlobalEnabledModuleIds() ?? _enabledModuleIds
|
|
395
|
+
if (explicit) return explicit
|
|
396
|
+
|
|
397
|
+
// Fallback: derive from injection tables and widget entries. This keeps
|
|
398
|
+
// older bootstrap paths (and callers that have not yet wired
|
|
399
|
+
// `registerEnabledModuleIds`) working — at the cost of mis-classifying
|
|
400
|
+
// dependency modules that ship no widgets. New apps MUST call
|
|
401
|
+
// `registerEnabledModuleIds` to get accurate gating.
|
|
402
|
+
const enabled = new Set<string>()
|
|
403
|
+
const tables = readGlobalInjectionTables() ?? _coreInjectionTables ?? []
|
|
404
|
+
for (const entry of tables) {
|
|
405
|
+
if (entry?.moduleId) enabled.add(entry.moduleId)
|
|
406
|
+
}
|
|
407
|
+
const entries = readGlobalInjectionWidgets() ?? _coreInjectionWidgetEntries ?? []
|
|
408
|
+
for (const entry of entries) {
|
|
409
|
+
if (entry?.moduleId) enabled.add(entry.moduleId)
|
|
410
|
+
}
|
|
411
|
+
return enabled
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function widgetMissingRequiredModules(
|
|
415
|
+
metadata: InjectionWidgetMetadata,
|
|
416
|
+
enabledModuleIds: ReadonlySet<string>,
|
|
417
|
+
): string[] {
|
|
418
|
+
const required = metadata.requiredModules
|
|
419
|
+
if (!Array.isArray(required) || required.length === 0) return []
|
|
420
|
+
const missing: string[] = []
|
|
421
|
+
for (const moduleId of required) {
|
|
422
|
+
if (typeof moduleId !== 'string' || moduleId.length === 0) continue
|
|
423
|
+
if (!enabledModuleIds.has(moduleId)) missing.push(moduleId)
|
|
424
|
+
}
|
|
425
|
+
return missing
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const warnedRequiredModuleSkips = new Set<string>()
|
|
429
|
+
|
|
430
|
+
function warnSkippedWidget(metadataId: string, missingModules: string[]) {
|
|
431
|
+
const key = `${metadataId}:${missingModules.join(',')}`
|
|
432
|
+
if (warnedRequiredModuleSkips.has(key)) return
|
|
433
|
+
warnedRequiredModuleSkips.add(key)
|
|
434
|
+
if (process.env.NODE_ENV === 'development') {
|
|
435
|
+
console.debug(
|
|
436
|
+
`[InjectionLoader] Skipping widget "${metadataId}" — required module(s) not enabled: ${missingModules.join(', ')}`,
|
|
437
|
+
)
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
342
441
|
async function getResolvedEntriesForSpot(spotId: InjectionSpotId): Promise<TableEntry[]> {
|
|
343
442
|
const table = await loadInjectionTable()
|
|
344
443
|
const exactEntries = table.get(spotId) ?? []
|
|
@@ -366,10 +465,16 @@ async function getResolvedEntriesForSpot(spotId: InjectionSpotId): Promise<Table
|
|
|
366
465
|
|
|
367
466
|
export async function loadAllInjectionWidgets(): Promise<LoadedInjectionWidget[]> {
|
|
368
467
|
const widgetEntries = await loadWidgetEntries()
|
|
468
|
+
const enabledModuleIds = getEnabledModuleIdsForInjection()
|
|
369
469
|
const loaded = await Promise.all(
|
|
370
470
|
widgetEntries.map(async (entry) => {
|
|
371
471
|
const module = await loadEntry(entry)
|
|
372
472
|
if (!isLoadedInjectionWidget(module)) return null
|
|
473
|
+
const missing = widgetMissingRequiredModules(module.metadata, enabledModuleIds)
|
|
474
|
+
if (missing.length > 0) {
|
|
475
|
+
warnSkippedWidget(module.metadata.id, missing)
|
|
476
|
+
return null
|
|
477
|
+
}
|
|
373
478
|
return { ...module, moduleId: entry.moduleId, key: entry.key }
|
|
374
479
|
})
|
|
375
480
|
)
|
|
@@ -385,10 +490,16 @@ export async function loadAllInjectionWidgets(): Promise<LoadedInjectionWidget[]
|
|
|
385
490
|
|
|
386
491
|
export async function loadInjectionWidgetById(widgetId: string): Promise<LoadedInjectionWidget | null> {
|
|
387
492
|
const widgetEntries = await loadWidgetEntries()
|
|
493
|
+
const enabledModuleIds = getEnabledModuleIdsForInjection()
|
|
388
494
|
for (const entry of widgetEntries) {
|
|
389
495
|
const module = await loadEntry(entry)
|
|
390
496
|
if (!isLoadedInjectionWidget(module)) continue
|
|
391
497
|
if (module.metadata.id === widgetId) {
|
|
498
|
+
const missing = widgetMissingRequiredModules(module.metadata, enabledModuleIds)
|
|
499
|
+
if (missing.length > 0) {
|
|
500
|
+
warnSkippedWidget(module.metadata.id, missing)
|
|
501
|
+
return null
|
|
502
|
+
}
|
|
392
503
|
return { ...module, moduleId: entry.moduleId, key: entry.key }
|
|
393
504
|
}
|
|
394
505
|
}
|
|
@@ -397,10 +508,16 @@ export async function loadInjectionWidgetById(widgetId: string): Promise<LoadedI
|
|
|
397
508
|
|
|
398
509
|
export async function loadInjectionDataWidgetById(widgetId: string): Promise<LoadedInjectionDataWidget | null> {
|
|
399
510
|
const widgetEntries = await loadWidgetEntries()
|
|
511
|
+
const enabledModuleIds = getEnabledModuleIdsForInjection()
|
|
400
512
|
for (const entry of widgetEntries) {
|
|
401
513
|
const module = await loadEntry(entry)
|
|
402
514
|
if (!isLoadedInjectionDataWidget(module)) continue
|
|
403
515
|
if (module.metadata.id === widgetId) {
|
|
516
|
+
const missing = widgetMissingRequiredModules(module.metadata, enabledModuleIds)
|
|
517
|
+
if (missing.length > 0) {
|
|
518
|
+
warnSkippedWidget(module.metadata.id, missing)
|
|
519
|
+
return null
|
|
520
|
+
}
|
|
404
521
|
return { ...module, moduleId: entry.moduleId, key: entry.key }
|
|
405
522
|
}
|
|
406
523
|
}
|
|
@@ -164,6 +164,17 @@ export type InjectionWidgetMetadata = {
|
|
|
164
164
|
features?: string[]
|
|
165
165
|
priority?: number
|
|
166
166
|
enabled?: boolean
|
|
167
|
+
/**
|
|
168
|
+
* Module IDs that MUST be enabled in `modules.ts` for this widget to be loaded.
|
|
169
|
+
* When any listed module is missing from the enabled module set, the widget is
|
|
170
|
+
* skipped at load time — its component is never imported and never rendered.
|
|
171
|
+
*
|
|
172
|
+
* Use this when a widget integrates with another module's runtime surface
|
|
173
|
+
* (for example a customers AI trigger that calls `/api/ai_assistant/...`).
|
|
174
|
+
* Feature flags alone are insufficient because a superadmin / wildcard grant
|
|
175
|
+
* may still pass the feature gate even when the dependency module is absent.
|
|
176
|
+
*/
|
|
177
|
+
requiredModules?: string[]
|
|
167
178
|
}
|
|
168
179
|
|
|
169
180
|
/**
|