@open-mercato/shared 0.7.1-develop.7137.1.26575786c0 → 0.7.1-develop.7148.1.3076e5ccf7
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/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +4 -2
- package/dist/lib/bootstrap/factory.js +1 -1
- package/dist/lib/bootstrap/factory.js.map +2 -2
- package/dist/lib/query/migration-reindex.js +45 -0
- package/dist/lib/query/migration-reindex.js.map +7 -0
- package/dist/lib/version.js +1 -1
- package/dist/lib/version.js.map +1 -1
- package/dist/modules/overrides.js +69 -9
- package/dist/modules/overrides.js.map +2 -2
- package/dist/modules/widgets/injection-loader.js +2 -2
- package/dist/modules/widgets/injection-loader.js.map +2 -2
- package/package.json +2 -2
- package/src/lib/bootstrap/__tests__/factory.test.ts +4 -1
- package/src/lib/bootstrap/factory.ts +1 -1
- package/src/lib/query/__tests__/migration-reindex.test.ts +80 -0
- package/src/lib/query/migration-reindex.ts +61 -0
- package/src/modules/__tests__/injection-widget-override-ids.test.ts +249 -0
- package/src/modules/overrides.ts +137 -12
- package/src/modules/widgets/injection-loader.ts +11 -2
|
@@ -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 { matchWildcardPattern } from '@open-mercato/shared/lib/patterns/wildcard'\nimport type {\n InjectionAnyWidgetModule,\n InjectionDataWidgetModule,\n InjectionWidgetMetadata,\n InjectionWidgetModule,\n InjectionSpotId,\n ModuleInjectionSlot,\n ModuleInjectionTable,\n InjectionWidgetPlacement,\n} from './injection'\nimport { createLogger } from '../../lib/logger'\nimport {\n applyInjectionWidgetOverridesToEntries,\n applyInjectionWidgetOverridesToTables,\n} from '../overrides'\n\nconst logger = createLogger('widgets').child({ component: 'injection-loader' })\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 logger.debug('Core injection widgets re-registered (this may occur during HMR)')\n }\n const finalEntries = applyInjectionWidgetOverridesToEntries(entries)\n _coreInjectionWidgetEntries = finalEntries\n writeGlobalInjectionWidgets(finalEntries)\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 logger.debug('Core injection tables re-registered (this may occur during HMR)')\n }\n const finalTables = applyInjectionWidgetOverridesToTables(tables)\n _coreInjectionTables = finalTables\n writeGlobalInjectionTables(finalTables)\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 logger.debug('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\nconst injectionRegistryChangeSubscribers = new Set<() => void>()\nlet injectionRegistryDomListenerAttached = false\n\nfunction dispatchInjectionRegistryChangeToSubscribers() {\n // Snapshot so a subscriber that unsubscribes during fan-out does not skip others.\n for (const subscriber of Array.from(injectionRegistryChangeSubscribers)) {\n subscriber()\n }\n}\n\n/**\n * Subscribe to injection-registry version changes.\n *\n * All subscribers share a single browser-level DOM listener: the first\n * subscriber attaches the `window` listener and the last one to unsubscribe\n * detaches it. Registry-change notifications fan out to every subscriber\n * through an internal callback set so mounting many widget surfaces does not\n * register one DOM listener per surface (#3320).\n */\nexport function subscribeToInjectionRegistryChanges(listener: () => void): () => void {\n if (typeof window === 'undefined') {\n return () => {}\n }\n\n injectionRegistryChangeSubscribers.add(listener)\n if (!injectionRegistryDomListenerAttached) {\n window.addEventListener(INJECTION_REGISTRY_CHANGED_EVENT, dispatchInjectionRegistryChangeToSubscribers)\n injectionRegistryDomListenerAttached = true\n }\n\n return () => {\n injectionRegistryChangeSubscribers.delete(listener)\n if (injectionRegistryChangeSubscribers.size === 0 && injectionRegistryDomListenerAttached) {\n window.removeEventListener(INJECTION_REGISTRY_CHANGED_EVENT, dispatchInjectionRegistryChangeToSubscribers)\n injectionRegistryDomListenerAttached = false\n }\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\ntype WidgetLookupIndex = {\n widgetsById: Map<string, LoadedInjectionWidget>\n dataWidgetsById: Map<string, LoadedInjectionDataWidget>\n}\nlet widgetLookupIndexPromise: { version: number; promise: Promise<WidgetLookupIndex> } | 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 widgetLookupIndexPromise = 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 = Promise.resolve()\n .then(() => entry.loader())\n .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 loadWidgetLookupIndex(): Promise<WidgetLookupIndex> {\n const version = getInjectionRegistryVersion()\n if (!widgetLookupIndexPromise || widgetLookupIndexPromise.version !== version) {\n const promise = Promise.resolve().then(async () => {\n const widgetEntries = await loadWidgetEntries()\n const settled = await Promise.allSettled(widgetEntries.map((entry) => loadEntry(entry)))\n const widgetsById = new Map<string, LoadedInjectionWidget>()\n const dataWidgetsById = new Map<string, LoadedInjectionDataWidget>()\n\n settled.forEach((result, index) => {\n if (result.status !== 'fulfilled') return\n const entry = widgetEntries[index]\n const module = result.value\n if (isLoadedInjectionWidget(module)) {\n if (!widgetsById.has(module.metadata.id)) {\n widgetsById.set(module.metadata.id, { ...module, moduleId: entry.moduleId, key: entry.key })\n }\n return\n }\n if (!dataWidgetsById.has(module.metadata.id)) {\n dataWidgetsById.set(module.metadata.id, { ...module, moduleId: entry.moduleId, key: entry.key })\n }\n })\n\n return { widgetsById, dataWidgetsById }\n })\n widgetLookupIndexPromise = { version, promise }\n }\n return widgetLookupIndexPromise.promise\n}\n\nfunction applyRequiredModuleGate<T extends LoadedInjectionWidget | LoadedInjectionDataWidget>(\n widget: T,\n enabledModuleIds: ReadonlySet<string>,\n): T | null {\n const missing = widgetMissingRequiredModules(widget.metadata, enabledModuleIds)\n if (missing.length > 0) {\n warnSkippedWidget(widget.metadata.id, missing)\n return null\n }\n return widget\n}\n\ntype HintedLookupResult<T> =\n | { resolved: true; widget: T | null }\n | { resolved: false }\n\nasync function tryLoadHintedWidgetById<T extends LoadedInjectionWidget | LoadedInjectionDataWidget>(\n widgetId: string,\n isExpectedKind: (module: InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }) => boolean,\n enabledModuleIds: ReadonlySet<string>,\n): Promise<HintedLookupResult<T>> {\n const widgetEntries = await loadWidgetEntries()\n const entry = widgetEntries.find((candidate) => candidate.widgetId === widgetId)\n if (!entry) return { resolved: false }\n\n const module = await loadEntry(entry).catch(() => null)\n if (!module || module.metadata.id !== widgetId || !isExpectedKind(module)) {\n return { resolved: false }\n }\n\n const widget = { ...module, moduleId: entry.moduleId, key: entry.key } as T\n return { resolved: true, widget: applyRequiredModuleGate(widget, enabledModuleIds) }\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 logger.debug('Skipping widget \u2014 required module(s) not enabled', { metadataId, missingModules })\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 if (!matchWildcardPattern(spotId, candidateSpotId)) 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 enabledModuleIds = getEnabledModuleIdsForInjection()\n const hinted = await tryLoadHintedWidgetById<LoadedInjectionWidget>(widgetId, isLoadedInjectionWidget, enabledModuleIds)\n if (hinted.resolved) return hinted.widget\n\n const index = await loadWidgetLookupIndex()\n const widget = index.widgetsById.get(widgetId)\n return widget ? applyRequiredModuleGate(widget, enabledModuleIds) : null\n}\n\nexport async function loadInjectionDataWidgetById(widgetId: string): Promise<LoadedInjectionDataWidget | null> {\n const enabledModuleIds = getEnabledModuleIdsForInjection()\n const hinted = await tryLoadHintedWidgetById<LoadedInjectionDataWidget>(widgetId, isLoadedInjectionDataWidget, enabledModuleIds)\n if (hinted.resolved) return hinted.widget\n\n const index = await loadWidgetLookupIndex()\n const widget = index.dataWidgetsById.get(widgetId)\n return widget ? applyRequiredModuleGate(widget, enabledModuleIds) : 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 { matchWildcardPattern } from '@open-mercato/shared/lib/patterns/wildcard'\nimport type {\n InjectionAnyWidgetModule,\n InjectionDataWidgetModule,\n InjectionWidgetMetadata,\n InjectionWidgetModule,\n InjectionSpotId,\n ModuleInjectionSlot,\n ModuleInjectionTable,\n InjectionWidgetPlacement,\n} from './injection'\nimport { createLogger } from '../../lib/logger'\nimport {\n applyInjectionWidgetOverridesToEntries,\n applyInjectionWidgetOverridesToTables,\n} from '../overrides'\n\nconst logger = createLogger('widgets').child({ component: 'injection-loader' })\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 logger.debug('Core injection widgets re-registered (this may occur during HMR)')\n }\n const finalEntries = applyInjectionWidgetOverridesToEntries(entries)\n _coreInjectionWidgetEntries = finalEntries\n writeGlobalInjectionWidgets(finalEntries)\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\n/**\n * `widgetEntries` is optional and carries the *unfiltered* generated entries so a\n * `key`-spelled injection-widget override can be resolved to the `widgetId` the table\n * slots reference (#5152). Bootstraps that skip core widget registration have no other\n * source for that mapping, since the registered entries are already override-filtered.\n */\nexport function registerCoreInjectionTables(\n tables: Array<{ moduleId: string; table: ModuleInjectionTable }>,\n widgetEntries?: readonly ModuleInjectionWidgetEntry[],\n) {\n if (_coreInjectionTables !== null && process.env.NODE_ENV === 'development') {\n logger.debug('Core injection tables re-registered (this may occur during HMR)')\n }\n const finalTables = applyInjectionWidgetOverridesToTables(tables, undefined, widgetEntries)\n _coreInjectionTables = finalTables\n writeGlobalInjectionTables(finalTables)\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 logger.debug('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\nconst injectionRegistryChangeSubscribers = new Set<() => void>()\nlet injectionRegistryDomListenerAttached = false\n\nfunction dispatchInjectionRegistryChangeToSubscribers() {\n // Snapshot so a subscriber that unsubscribes during fan-out does not skip others.\n for (const subscriber of Array.from(injectionRegistryChangeSubscribers)) {\n subscriber()\n }\n}\n\n/**\n * Subscribe to injection-registry version changes.\n *\n * All subscribers share a single browser-level DOM listener: the first\n * subscriber attaches the `window` listener and the last one to unsubscribe\n * detaches it. Registry-change notifications fan out to every subscriber\n * through an internal callback set so mounting many widget surfaces does not\n * register one DOM listener per surface (#3320).\n */\nexport function subscribeToInjectionRegistryChanges(listener: () => void): () => void {\n if (typeof window === 'undefined') {\n return () => {}\n }\n\n injectionRegistryChangeSubscribers.add(listener)\n if (!injectionRegistryDomListenerAttached) {\n window.addEventListener(INJECTION_REGISTRY_CHANGED_EVENT, dispatchInjectionRegistryChangeToSubscribers)\n injectionRegistryDomListenerAttached = true\n }\n\n return () => {\n injectionRegistryChangeSubscribers.delete(listener)\n if (injectionRegistryChangeSubscribers.size === 0 && injectionRegistryDomListenerAttached) {\n window.removeEventListener(INJECTION_REGISTRY_CHANGED_EVENT, dispatchInjectionRegistryChangeToSubscribers)\n injectionRegistryDomListenerAttached = false\n }\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\ntype WidgetLookupIndex = {\n widgetsById: Map<string, LoadedInjectionWidget>\n dataWidgetsById: Map<string, LoadedInjectionDataWidget>\n}\nlet widgetLookupIndexPromise: { version: number; promise: Promise<WidgetLookupIndex> } | 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 widgetLookupIndexPromise = 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 = Promise.resolve()\n .then(() => entry.loader())\n .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 loadWidgetLookupIndex(): Promise<WidgetLookupIndex> {\n const version = getInjectionRegistryVersion()\n if (!widgetLookupIndexPromise || widgetLookupIndexPromise.version !== version) {\n const promise = Promise.resolve().then(async () => {\n const widgetEntries = await loadWidgetEntries()\n const settled = await Promise.allSettled(widgetEntries.map((entry) => loadEntry(entry)))\n const widgetsById = new Map<string, LoadedInjectionWidget>()\n const dataWidgetsById = new Map<string, LoadedInjectionDataWidget>()\n\n settled.forEach((result, index) => {\n if (result.status !== 'fulfilled') return\n const entry = widgetEntries[index]\n const module = result.value\n if (isLoadedInjectionWidget(module)) {\n if (!widgetsById.has(module.metadata.id)) {\n widgetsById.set(module.metadata.id, { ...module, moduleId: entry.moduleId, key: entry.key })\n }\n return\n }\n if (!dataWidgetsById.has(module.metadata.id)) {\n dataWidgetsById.set(module.metadata.id, { ...module, moduleId: entry.moduleId, key: entry.key })\n }\n })\n\n return { widgetsById, dataWidgetsById }\n })\n widgetLookupIndexPromise = { version, promise }\n }\n return widgetLookupIndexPromise.promise\n}\n\nfunction applyRequiredModuleGate<T extends LoadedInjectionWidget | LoadedInjectionDataWidget>(\n widget: T,\n enabledModuleIds: ReadonlySet<string>,\n): T | null {\n const missing = widgetMissingRequiredModules(widget.metadata, enabledModuleIds)\n if (missing.length > 0) {\n warnSkippedWidget(widget.metadata.id, missing)\n return null\n }\n return widget\n}\n\ntype HintedLookupResult<T> =\n | { resolved: true; widget: T | null }\n | { resolved: false }\n\nasync function tryLoadHintedWidgetById<T extends LoadedInjectionWidget | LoadedInjectionDataWidget>(\n widgetId: string,\n isExpectedKind: (module: InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }) => boolean,\n enabledModuleIds: ReadonlySet<string>,\n): Promise<HintedLookupResult<T>> {\n const widgetEntries = await loadWidgetEntries()\n const entry = widgetEntries.find((candidate) => candidate.widgetId === widgetId)\n if (!entry) return { resolved: false }\n\n const module = await loadEntry(entry).catch(() => null)\n if (!module || module.metadata.id !== widgetId || !isExpectedKind(module)) {\n return { resolved: false }\n }\n\n const widget = { ...module, moduleId: entry.moduleId, key: entry.key } as T\n return { resolved: true, widget: applyRequiredModuleGate(widget, enabledModuleIds) }\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 logger.debug('Skipping widget \u2014 required module(s) not enabled', { metadataId, missingModules })\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 if (!matchWildcardPattern(spotId, candidateSpotId)) 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 enabledModuleIds = getEnabledModuleIdsForInjection()\n const hinted = await tryLoadHintedWidgetById<LoadedInjectionWidget>(widgetId, isLoadedInjectionWidget, enabledModuleIds)\n if (hinted.resolved) return hinted.widget\n\n const index = await loadWidgetLookupIndex()\n const widget = index.widgetsById.get(widgetId)\n return widget ? applyRequiredModuleGate(widget, enabledModuleIds) : null\n}\n\nexport async function loadInjectionDataWidgetById(widgetId: string): Promise<LoadedInjectionDataWidget | null> {\n const enabledModuleIds = getEnabledModuleIdsForInjection()\n const hinted = await tryLoadHintedWidgetById<LoadedInjectionDataWidget>(widgetId, isLoadedInjectionDataWidget, enabledModuleIds)\n if (hinted.resolved) return hinted.widget\n\n const index = await loadWidgetLookupIndex()\n const widget = index.dataWidgetsById.get(widgetId)\n return widget ? applyRequiredModuleGate(widget, enabledModuleIds) : 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": "AACA,SAAS,4BAA4B;AAWrC,SAAS,oBAAoB;AAC7B;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAEP,MAAM,SAAS,aAAa,SAAS,EAAE,MAAM,EAAE,WAAW,mBAAmB,CAAC;AAkC9E,IAAI,8BAAmE;AACvE,IAAI,uBAAwF;AAC5F,IAAI,oBAAgD;AACpD,IAAI,4BAA4B;AAChC,MAAM,+BAA+B;AACrC,MAAM,8BAA8B;AACpC,MAAM,gCAAgC;AACtC,MAAM,wCAAwC;AAC9C,MAAM,mCAAmC;AAEzC,SAAS,6BAAkE;AACzE,MAAI;AACF,UAAM,QAAS,WAAuC,4BAA4B;AAClF,WAAO,MAAM,QAAQ,KAAK,IAAK,QAAyC;AAAA,EAC1E,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,4BAA4B,SAAuC;AAC1E,MAAI;AACF;AAAC,IAAC,WAAuC,4BAA4B,IAAI;AAAA,EAC3E,QAAQ;AAAA,EAER;AACF;AAEA,SAAS,6BAAyD;AAChE,MAAI;AACF,UAAM,QAAS,WAAuC,6BAA6B;AACnF,QAAI,iBAAiB,IAAK,QAAO;AACjC,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,4BAA4B,KAA0B;AAC7D,MAAI;AACF;AAAC,IAAC,WAAuC,6BAA6B,IAAI;AAAA,EAC5E,QAAQ;AAAA,EAER;AACF;AAEA,SAAS,4BAA6F;AACpG,MAAI;AACF,UAAM,QAAS,WAAuC,2BAA2B;AACjF,WAAO,MAAM,QAAQ,KAAK,IAAK,QAAqE;AAAA,EACtG,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,2BAA2B,QAAkE;AACpG,MAAI;AACF;AAAC,IAAC,WAAuC,2BAA2B,IAAI;AAAA,EAC1E,QAAQ;AAAA,EAER;AACF;AAEA,SAAS,qCAAoD;AAC3D,MAAI;AACF,UAAM,QAAS,WAAuC,qCAAqC;AAC3F,WAAO,OAAO,UAAU,WAAW,QAAQ;AAAA,EAC7C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,oCAAoC,SAAiB;AAC5D,MAAI;AACF;AAAC,IAAC,WAAuC,qCAAqC,IAAI;AAAA,EACpF,QAAQ;AAAA,EAER;AACF;AAEA,SAAS,iCAAiC;AACxC,+BAA6B;AAC7B,sCAAoC,yBAAyB;AAC7D,iCAA+B;AAE/B,MAAI,OAAO,WAAW,YAAa;AAEnC,SAAO,cAAc,IAAI,YAAY,kCAAkC;AAAA,IACrE,QAAQ,EAAE,SAAS,0BAA0B;AAAA,EAC/C,CAAC,CAAC;AACJ;AAEO,SAAS,6BAA6B,SAAuC;AAClF,MAAI,gCAAgC,QAAQ,QAAQ,IAAI,aAAa,eAAe;AAClF,WAAO,MAAM,kEAAkE;AAAA,EACjF;AACA,QAAM,eAAe,uCAAuC,OAAO;AACnE,gCAA8B;AAC9B,8BAA4B,YAAY;AACxC,iCAA+B;AACjC;AAEO,SAAS,0BAAwD;AACtE,QAAM,gBAAgB,2BAA2B;AACjD,MAAI,cAAe,QAAO;AAC1B,MAAI,CAAC,6BAA6B;AAEhC,QAAI,OAAO,WAAW,aAAa;AACjC,aAAO,CAAC;AAAA,IACV;AACA,UAAM,IAAI,MAAM,sGAAsG;AAAA,EACxH;AACA,SAAO;AACT;AAQO,SAAS,4BACd,QACA,eACA;AACA,MAAI,yBAAyB,QAAQ,QAAQ,IAAI,aAAa,eAAe;AAC3E,WAAO,MAAM,iEAAiE;AAAA,EAChF;AACA,QAAM,cAAc,sCAAsC,QAAQ,QAAW,aAAa;AAC1F,yBAAuB;AACvB,6BAA2B,WAAW;AACtC,iCAA+B;AACjC;AAWO,SAAS,yBAAyB,WAA6B;AACpE,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,YAAY,WAAW;AAChC,QAAI,OAAO,aAAa,YAAY,SAAS,SAAS,EAAG,MAAK,IAAI,QAAQ;AAAA,EAC5E;AACA,MAAI,sBAAsB,QAAQ,QAAQ,IAAI,aAAa,eAAe;AACxE,WAAO,MAAM,8DAA8D;AAAA,EAC7E;AACA,sBAAoB;AACpB,8BAA4B,IAAI;AAChC,iCAA+B;AACjC;AAEO,SAAS,sBAAkD;AAChE,SAAO,2BAA2B,KAAK;AACzC;AAEO,SAAS,8BAAsC;AACpD,QAAM,gBAAgB,mCAAmC;AACzD,MAAI,kBAAkB,KAAM,QAAO;AACnC,SAAO;AACT;AAEA,MAAM,qCAAqC,oBAAI,IAAgB;AAC/D,IAAI,uCAAuC;AAE3C,SAAS,+CAA+C;AAEtD,aAAW,cAAc,MAAM,KAAK,kCAAkC,GAAG;AACvE,eAAW;AAAA,EACb;AACF;AAWO,SAAS,oCAAoC,UAAkC;AACpF,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO,MAAM;AAAA,IAAC;AAAA,EAChB;AAEA,qCAAmC,IAAI,QAAQ;AAC/C,MAAI,CAAC,sCAAsC;AACzC,WAAO,iBAAiB,kCAAkC,4CAA4C;AACtG,2CAAuC;AAAA,EACzC;AAEA,SAAO,MAAM;AACX,uCAAmC,OAAO,QAAQ;AAClD,QAAI,mCAAmC,SAAS,KAAK,sCAAsC;AACzF,aAAO,oBAAoB,kCAAkC,4CAA4C;AACzG,6CAAuC;AAAA,IACzC;AAAA,EACF;AACF;AAEO,SAAS,yBAAmF;AACjG,QAAM,eAAe,0BAA0B;AAC/C,MAAI,aAAc,QAAO;AACzB,MAAI,CAAC,sBAAsB;AAEzB,QAAI,OAAO,WAAW,aAAa;AACjC,aAAO,CAAC;AAAA,IACV;AACA,UAAM,IAAI,MAAM,oGAAoG;AAAA,EACtH;AACA,SAAO;AACT;AAEA,IAAI,uBAAsD;AAW1D,IAAI,wBAA4E;AAKhF,IAAI,2BAA4F;AAEhG,SAAS,sBAAsB,OAAyG;AACtI,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,cAAc;AACtE;AAMO,SAAS,iCAAiC;AAC/C,yBAAuB;AACvB,0BAAwB;AACxB,6BAA2B;AAC3B,cAAY,MAAM;AAClB,4BAA0B,MAAM;AAClC;AAEA,eAAe,oBAA4C;AACzD,MAAI,CAAC,sBAAsB;AACzB,UAAM,UAAU,QAAQ,QAAQ,EAAE;AAAA,MAAK,MACrC,wBAAwB,EAAE,IAAI,CAAC,WAAW;AAAA,QACxC,GAAG;AAAA,QACH,UAAU,MAAM,YAAY;AAAA,MAC9B,EAAE;AAAA,IACJ;AACA,2BAAuB,QAAQ,MAAM,CAAC,QAAQ;AAC5C,UAAI,yBAAyB,SAAS;AACpC,+BAAuB;AAAA,MACzB;AACA,YAAM;AAAA,IACR,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAEA,eAAe,qBAAkE;AAC/E,MAAI,CAAC,uBAAuB;AAC1B,UAAM,UAAU,QAAQ,QAAQ,EAAE,KAAK,MAAM;AAC3C,YAAM,OAAO,uBAAuB;AACpC,YAAM,QAAQ,oBAAI,IAAmC;AAErD,iBAAW,SAAS,MAAM;AACxB,cAAM,iBAAiB,MAAM,SAAS,CAAC;AACvC,mBAAW,CAAC,QAAQ,SAAS,KAAK,OAAO,QAAQ,cAAc,GAAG;AAChE,gBAAM,UAAU,MAAM,QAAQ,SAAS,IAAI,YAAY,CAAC,SAAS;AACjE,gBAAM,WAAW,MAAM,IAAI,MAAM,KAAK,CAAC;AACvC,qBAAW,eAAe,SAAS;AACjC,gBAAI,OAAO,gBAAgB,UAAU;AACnC,uBAAS,KAAK,EAAE,UAAU,aAAa,UAAU,MAAM,UAAU,UAAU,EAAE,CAAC;AAC9E;AAAA,YACF;AACA,gBAAI,sBAAsB,WAAW,GAAG;AACtC,oBAAM,EAAE,UAAU,WAAW,GAAG,GAAG,UAAU,IAAI;AACjD,uBAAS,KAAK;AAAA,gBACZ;AAAA,gBACA,UAAU,MAAM;AAAA,gBAChB,UAAU,OAAO,aAAa,WAAW,WAAW;AAAA,gBACpD;AAAA,cACF,CAAC;AACD;AAAA,YACF;AAAA,UACF;AACA,gBAAM,IAAI,QAAQ,QAAQ;AAAA,QAC5B;AAAA,MACF;AAEA,iBAAW,CAAC,QAAQ,OAAO,KAAK,MAAM,QAAQ,GAAG;AAC/C,cAAM,IAAI,QAAQ,QAAQ,KAAK,CAAC,GAAG,OAAO,EAAE,YAAY,MAAM,EAAE,YAAY,EAAE,CAAC;AAAA,MACjF;AAEA,aAAO;AAAA,IACT,CAAC;AACD,4BAAwB,QAAQ,MAAM,CAAC,QAAQ;AAC7C,UAAI,0BAA0B,SAAS;AACrC,gCAAwB;AAAA,MAC1B;AACA,YAAM;AAAA,IACR,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAEA,MAAM,cAAc,oBAAI,IAAiG;AAEzH,SAAS,mBAAmB,QAAmE;AAC7F,QAAM,OAAO;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,SAAO,KAAK,KAAK,CAAC,QAAQ,OAAO,MAAM;AACzC;AAEA,SAAS,2BAA2B,KAAc,KAAa,UAAgG;AAC7J,MAAI,CAAC,OAAO,OAAO,QAAQ,UAAU;AACnC,UAAM,IAAI,MAAM,oCAAoC,GAAG,WAAW,QAAQ,4BAA4B;AAAA,EACxG;AACA,QAAM,SAAU,IAAyD,WAAY;AACrF,MAAI,CAAC,UAAU,OAAO,WAAW,UAAU;AACzC,UAAM,IAAI,MAAM,oCAAoC,GAAG,WAAW,QAAQ,4BAA4B;AAAA,EACxG;AACA,MAAI,EAAE,cAAc,WAAW,CAAC,OAAO,YAAY,OAAO,OAAO,aAAa,UAAU;AACtF,UAAM,IAAI,MAAM,qBAAqB,GAAG,WAAW,QAAQ,uBAAuB;AAAA,EACpF;AACA,QAAM,WAAW,OAAO;AACxB,MAAI,OAAO,SAAS,OAAO,YAAY,SAAS,GAAG,WAAW,GAAG;AAC/D,UAAM,IAAI,MAAM,qBAAqB,GAAG,WAAW,QAAQ,0CAA0C;AAAA,EACvG;AACA,QAAM,aAAa;AAAA,IACjB,GAAG;AAAA,IACH;AAAA,EACF;AAEA,MAAI,YAAY,cAAc,OAAO,WAAW,WAAW,YAAY;AACrE,QAAI,OAAO,SAAS,UAAU,YAAY,SAAS,MAAM,WAAW,GAAG;AACrE,YAAM,IAAI,MAAM,qBAAqB,SAAS,EAAE,WAAW,QAAQ,qBAAqB;AAAA,IAC1F;AACA,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,mBAAmB,UAAqC,GAAG;AAC9D,UAAM,IAAI;AAAA,MACR,qBAAqB,SAAS,EAAE,WAAW,QAAQ;AAAA,IACrD;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,wBACP,QAC8B;AAC9B,SAAO,YAAY,UAAU,OAAO,OAAO,WAAW;AACxD;AAEA,SAAS,4BACP,QACkC;AAClC,SAAO,CAAC,wBAAwB,MAAM;AACxC;AAEA,eAAe,UAAU,OAAyG;AAChI,MAAI,CAAC,YAAY,IAAI,MAAM,GAAG,GAAG;AAC/B,UAAM,UAAU,QAAQ,QAAQ,EAC7B,KAAK,MAAM,MAAM,OAAO,CAAC,EACzB,KAAK,CAAC,QAAQ,2BAA2B,KAAK,MAAM,KAAK,MAAM,QAAQ,CAAC;AAC3E,gBAAY,IAAI,MAAM,KAAK,OAAO;AAAA,EACpC;AACA,SAAO,YAAY,IAAI,MAAM,GAAG;AAClC;AAEA,eAAe,wBAAoD;AACjE,QAAM,UAAU,4BAA4B;AAC5C,MAAI,CAAC,4BAA4B,yBAAyB,YAAY,SAAS;AAC7E,UAAM,UAAU,QAAQ,QAAQ,EAAE,KAAK,YAAY;AACjD,YAAM,gBAAgB,MAAM,kBAAkB;AAC9C,YAAM,UAAU,MAAM,QAAQ,WAAW,cAAc,IAAI,CAAC,UAAU,UAAU,KAAK,CAAC,CAAC;AACvF,YAAM,cAAc,oBAAI,IAAmC;AAC3D,YAAM,kBAAkB,oBAAI,IAAuC;AAEnE,cAAQ,QAAQ,CAAC,QAAQ,UAAU;AACjC,YAAI,OAAO,WAAW,YAAa;AACnC,cAAM,QAAQ,cAAc,KAAK;AACjC,cAAM,SAAS,OAAO;AACtB,YAAI,wBAAwB,MAAM,GAAG;AACnC,cAAI,CAAC,YAAY,IAAI,OAAO,SAAS,EAAE,GAAG;AACxC,wBAAY,IAAI,OAAO,SAAS,IAAI,EAAE,GAAG,QAAQ,UAAU,MAAM,UAAU,KAAK,MAAM,IAAI,CAAC;AAAA,UAC7F;AACA;AAAA,QACF;AACA,YAAI,CAAC,gBAAgB,IAAI,OAAO,SAAS,EAAE,GAAG;AAC5C,0BAAgB,IAAI,OAAO,SAAS,IAAI,EAAE,GAAG,QAAQ,UAAU,MAAM,UAAU,KAAK,MAAM,IAAI,CAAC;AAAA,QACjG;AAAA,MACF,CAAC;AAED,aAAO,EAAE,aAAa,gBAAgB;AAAA,IACxC,CAAC;AACD,+BAA2B,EAAE,SAAS,QAAQ;AAAA,EAChD;AACA,SAAO,yBAAyB;AAClC;AAEA,SAAS,wBACP,QACA,kBACU;AACV,QAAM,UAAU,6BAA6B,OAAO,UAAU,gBAAgB;AAC9E,MAAI,QAAQ,SAAS,GAAG;AACtB,sBAAkB,OAAO,SAAS,IAAI,OAAO;AAC7C,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAMA,eAAe,wBACb,UACA,gBACA,kBACgC;AAChC,QAAM,gBAAgB,MAAM,kBAAkB;AAC9C,QAAM,QAAQ,cAAc,KAAK,CAAC,cAAc,UAAU,aAAa,QAAQ;AAC/E,MAAI,CAAC,MAAO,QAAO,EAAE,UAAU,MAAM;AAErC,QAAM,SAAS,MAAM,UAAU,KAAK,EAAE,MAAM,MAAM,IAAI;AACtD,MAAI,CAAC,UAAU,OAAO,SAAS,OAAO,YAAY,CAAC,eAAe,MAAM,GAAG;AACzE,WAAO,EAAE,UAAU,MAAM;AAAA,EAC3B;AAEA,QAAM,SAAS,EAAE,GAAG,QAAQ,UAAU,MAAM,UAAU,KAAK,MAAM,IAAI;AACrE,SAAO,EAAE,UAAU,MAAM,QAAQ,wBAAwB,QAAQ,gBAAgB,EAAE;AACrF;AAEA,SAAS,kCAAuD;AAK9D,QAAM,WAAW,2BAA2B,KAAK;AACjD,MAAI,SAAU,QAAO;AAOrB,QAAM,UAAU,oBAAI,IAAY;AAChC,QAAM,SAAS,0BAA0B,KAAK,wBAAwB,CAAC;AACvE,aAAW,SAAS,QAAQ;AAC1B,QAAI,OAAO,SAAU,SAAQ,IAAI,MAAM,QAAQ;AAAA,EACjD;AACA,QAAM,UAAU,2BAA2B,KAAK,+BAA+B,CAAC;AAChF,aAAW,SAAS,SAAS;AAC3B,QAAI,OAAO,SAAU,SAAQ,IAAI,MAAM,QAAQ;AAAA,EACjD;AACA,SAAO;AACT;AAEA,SAAS,6BACP,UACA,kBACU;AACV,QAAM,WAAW,SAAS;AAC1B,MAAI,CAAC,MAAM,QAAQ,QAAQ,KAAK,SAAS,WAAW,EAAG,QAAO,CAAC;AAC/D,QAAM,UAAoB,CAAC;AAC3B,aAAW,YAAY,UAAU;AAC/B,QAAI,OAAO,aAAa,YAAY,SAAS,WAAW,EAAG;AAC3D,QAAI,CAAC,iBAAiB,IAAI,QAAQ,EAAG,SAAQ,KAAK,QAAQ;AAAA,EAC5D;AACA,SAAO;AACT;AAEA,MAAM,4BAA4B,oBAAI,IAAY;AAElD,SAAS,kBAAkB,YAAoB,gBAA0B;AACvE,QAAM,MAAM,GAAG,UAAU,IAAI,eAAe,KAAK,GAAG,CAAC;AACrD,MAAI,0BAA0B,IAAI,GAAG,EAAG;AACxC,4BAA0B,IAAI,GAAG;AACjC,MAAI,QAAQ,IAAI,aAAa,eAAe;AAC1C,WAAO,MAAM,yDAAoD,EAAE,YAAY,eAAe,CAAC;AAAA,EACjG;AACF;AAEA,eAAe,0BAA0B,QAAgD;AACvF,QAAM,QAAQ,MAAM,mBAAmB;AACvC,QAAM,eAAe,MAAM,IAAI,MAAM,KAAK,CAAC;AAC3C,QAAM,kBAAgC,CAAC;AAEvC,aAAW,CAAC,iBAAiB,gBAAgB,KAAK,MAAM,QAAQ,GAAG;AACjE,QAAI,oBAAoB,OAAQ;AAChC,QAAI,CAAC,gBAAgB,SAAS,GAAG,EAAG;AACpC,QAAI,CAAC,qBAAqB,QAAQ,eAAe,EAAG;AACpD,oBAAgB,KAAK,GAAG,gBAAgB;AAAA,EAC1C;AAEA,QAAM,iBAAiB,oBAAI,IAAwB;AACnD,aAAW,SAAS,CAAC,GAAG,cAAc,GAAG,eAAe,GAAG;AACzD,UAAM,WAAW,GAAG,MAAM,QAAQ,IAAI,MAAM,QAAQ;AACpD,UAAM,WAAW,eAAe,IAAI,QAAQ;AAC5C,QAAI,CAAC,aAAa,MAAM,YAAY,MAAM,SAAS,YAAY,IAAI;AACjE,qBAAe,IAAI,UAAU,KAAK;AAAA,IACpC;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,eAAe,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,OAAO,EAAE,YAAY,MAAM,EAAE,YAAY,EAAE;AACjG;AAEA,eAAsB,0BAA4D;AAChF,QAAM,gBAAgB,MAAM,kBAAkB;AAC9C,QAAM,mBAAmB,gCAAgC;AACzD,QAAM,SAAS,MAAM,QAAQ;AAAA,IAC3B,cAAc,IAAI,OAAO,UAAU;AACjC,YAAM,SAAS,MAAM,UAAU,KAAK;AACpC,UAAI,CAAC,wBAAwB,MAAM,EAAG,QAAO;AAC7C,YAAM,UAAU,6BAA6B,OAAO,UAAU,gBAAgB;AAC9E,UAAI,QAAQ,SAAS,GAAG;AACtB,0BAAkB,OAAO,SAAS,IAAI,OAAO;AAC7C,eAAO;AAAA,MACT;AACA,aAAO,EAAE,GAAG,QAAQ,UAAU,MAAM,UAAU,KAAK,MAAM,IAAI;AAAA,IAC/D,CAAC;AAAA,EACH;AACA,QAAM,OAAO,oBAAI,IAAmC;AACpD,aAAW,UAAU,QAAQ;AAC3B,QAAI,CAAC,OAAQ;AACb,QAAI,CAAC,KAAK,IAAI,OAAO,SAAS,EAAE,GAAG;AACjC,WAAK,IAAI,OAAO,SAAS,IAAI,MAAM;AAAA,IACrC;AAAA,EACF;AACA,SAAO,MAAM,KAAK,KAAK,OAAO,CAAC;AACjC;AAEA,eAAsB,wBAAwB,UAAyD;AACrG,QAAM,mBAAmB,gCAAgC;AACzD,QAAM,SAAS,MAAM,wBAA+C,UAAU,yBAAyB,gBAAgB;AACvH,MAAI,OAAO,SAAU,QAAO,OAAO;AAEnC,QAAM,QAAQ,MAAM,sBAAsB;AAC1C,QAAM,SAAS,MAAM,YAAY,IAAI,QAAQ;AAC7C,SAAO,SAAS,wBAAwB,QAAQ,gBAAgB,IAAI;AACtE;AAEA,eAAsB,4BAA4B,UAA6D;AAC7G,QAAM,mBAAmB,gCAAgC;AACzD,QAAM,SAAS,MAAM,wBAAmD,UAAU,6BAA6B,gBAAgB;AAC/H,MAAI,OAAO,SAAU,QAAO,OAAO;AAEnC,QAAM,QAAQ,MAAM,sBAAsB;AAC1C,QAAM,SAAS,MAAM,gBAAgB,IAAI,QAAQ;AACjD,SAAO,SAAS,wBAAwB,QAAQ,gBAAgB,IAAI;AACtE;AAEA,eAAsB,4BAA4B,QAA2D;AAC3G,QAAM,UAAU,MAAM,0BAA0B,MAAM;AACtD,QAAM,UAAmC,CAAC;AAC1C,aAAW,EAAE,UAAU,WAAW,SAAS,KAAK,SAAS;AACvD,UAAM,SAAS,MAAM,wBAAwB,QAAQ;AACrD,QAAI,CAAC,OAAQ;AACb,UAAM,oBAAoB,YACtB,EAAE,GAAG,WAAW,UAAU,OAAO,aAAa,WAAW,WAAW,EAAE,IACtE,EAAE,UAAU,OAAO,aAAa,WAAW,WAAW,EAAE;AAC5D,YAAQ,KAAK,EAAE,GAAG,QAAQ,WAAW,kBAAkB,CAAC;AAAA,EAC1D;AACA,SAAO;AACT;AAEA,eAAsB,gCAAgC,QAA+D;AACnH,QAAM,UAAU,MAAM,0BAA0B,MAAM;AACtD,QAAM,UAAuC,CAAC;AAC9C,aAAW,EAAE,UAAU,WAAW,SAAS,KAAK,SAAS;AACvD,UAAM,SAAS,MAAM,4BAA4B,QAAQ;AACzD,QAAI,CAAC,OAAQ;AACb,UAAM,oBAAoB,YACtB,EAAE,GAAG,WAAW,UAAU,OAAO,aAAa,WAAW,WAAW,EAAE,IACtE,EAAE,UAAU,OAAO,aAAa,WAAW,WAAW,EAAE;AAC5D,YAAQ,KAAK,EAAE,GAAG,QAAQ,WAAW,kBAAkB,CAAC;AAAA,EAC1D;AACA,SAAO;AACT;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/shared",
|
|
3
|
-
"version": "0.7.1-develop.
|
|
3
|
+
"version": "0.7.1-develop.7148.1.3076e5ccf7",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
"@mikro-orm/core": "^7.1.8",
|
|
114
114
|
"@mikro-orm/decorators": "^7.1.8",
|
|
115
115
|
"@mikro-orm/postgresql": "^7.1.8",
|
|
116
|
-
"@open-mercato/cache": "0.7.1-develop.
|
|
116
|
+
"@open-mercato/cache": "0.7.1-develop.7148.1.3076e5ccf7",
|
|
117
117
|
"@types/html-to-text": "^9.0.4",
|
|
118
118
|
"@types/sanitize-html": "^2.16.1",
|
|
119
119
|
"dotenv": "^17.4.2",
|
|
@@ -72,7 +72,10 @@ describe('partitioned bootstrap registration', () => {
|
|
|
72
72
|
await waitForAsyncRegistration()
|
|
73
73
|
|
|
74
74
|
expect(registerCoreInjectionWidgetsMock).not.toHaveBeenCalled()
|
|
75
|
-
|
|
75
|
+
// The raw widget entries still travel with the tables so a `key`-spelled injection
|
|
76
|
+
// override resolves to the `widgetId` the slots reference (#5152), even though this
|
|
77
|
+
// bootstrap deliberately skips registering the widgets themselves.
|
|
78
|
+
expect(registerCoreInjectionTablesMock).toHaveBeenCalledWith([], [])
|
|
76
79
|
expect(registerEnabledModuleIdsMock).toHaveBeenCalledTimes(1)
|
|
77
80
|
})
|
|
78
81
|
})
|
|
@@ -146,7 +146,7 @@ async function registerWidgetsAndOptionalPackages(data: BootstrapData, options:
|
|
|
146
146
|
if (!options.skipCoreInjectionWidgets) {
|
|
147
147
|
coreInjection.registerCoreInjectionWidgets(data.injectionWidgetEntries)
|
|
148
148
|
}
|
|
149
|
-
coreInjection.registerCoreInjectionTables(data.injectionTables)
|
|
149
|
+
coreInjection.registerCoreInjectionTables(data.injectionTables, data.injectionWidgetEntries)
|
|
150
150
|
coreInjection.registerEnabledModuleIds(
|
|
151
151
|
data.modules.map((module) => module.id).filter((id): id is string => typeof id === 'string' && id.length > 0),
|
|
152
152
|
)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import {
|
|
2
|
+
QUERY_INDEX_REINDEX_EXPORT,
|
|
3
|
+
declareQueryIndexReindex,
|
|
4
|
+
formatQueryIndexRebuildCommands,
|
|
5
|
+
readQueryIndexReindexDeclaration,
|
|
6
|
+
} from '../migration-reindex'
|
|
7
|
+
|
|
8
|
+
describe('declareQueryIndexReindex', () => {
|
|
9
|
+
it('normalizes and freezes the declared entity types', () => {
|
|
10
|
+
const declared = declareQueryIndexReindex([
|
|
11
|
+
'customers:customer_dictionary_entry',
|
|
12
|
+
'customers:customer_dictionary_entry',
|
|
13
|
+
'workflows:workflow_definition',
|
|
14
|
+
])
|
|
15
|
+
|
|
16
|
+
expect(declared).toEqual(['customers:customer_dictionary_entry', 'workflows:workflow_definition'])
|
|
17
|
+
expect(Object.isFrozen(declared)).toBe(true)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('rejects identifiers that are not module:entity', () => {
|
|
21
|
+
expect(() => declareQueryIndexReindex(['customer_dictionary_entries'])).toThrow(/module:entity/)
|
|
22
|
+
expect(() => declareQueryIndexReindex(['customers:Customer-Dictionary-Entry'])).toThrow(/module:entity/)
|
|
23
|
+
expect(() => declareQueryIndexReindex([])).toThrow(/at least one entity type/)
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe('readQueryIndexReindexDeclaration', () => {
|
|
28
|
+
it('reads the declaration from a migration module', () => {
|
|
29
|
+
const moduleExports = {
|
|
30
|
+
[QUERY_INDEX_REINDEX_EXPORT]: ['dictionaries:dictionary_entry', 'dictionaries:dictionary_entry'],
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
expect(readQueryIndexReindexDeclaration(moduleExports)).toEqual(['dictionaries:dictionary_entry'])
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('returns nothing for migrations that declare nothing', () => {
|
|
37
|
+
expect(readQueryIndexReindexDeclaration({})).toEqual([])
|
|
38
|
+
expect(readQueryIndexReindexDeclaration(null)).toEqual([])
|
|
39
|
+
expect(readQueryIndexReindexDeclaration({ [QUERY_INDEX_REINDEX_EXPORT]: 'customers:deal' })).toEqual([])
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('drops malformed entries instead of propagating them into a reindex request', () => {
|
|
43
|
+
const moduleExports = {
|
|
44
|
+
[QUERY_INDEX_REINDEX_EXPORT]: ['customers:deal', 42, 'not-an-entity-type', null],
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
expect(readQueryIndexReindexDeclaration(moduleExports)).toEqual(['customers:deal'])
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('reports every rejected entry so a typo cannot leave a projection stale in silence', () => {
|
|
51
|
+
const rejected: unknown[] = []
|
|
52
|
+
const moduleExports = {
|
|
53
|
+
// camelCase is the natural slip in a codebase whose TS identifiers are all camelCase.
|
|
54
|
+
[QUERY_INDEX_REINDEX_EXPORT]: ['customers:customerDictionaryEntry', 'customers:deal', 42],
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
expect(readQueryIndexReindexDeclaration(moduleExports, (value) => rejected.push(value))).toEqual([
|
|
58
|
+
'customers:deal',
|
|
59
|
+
])
|
|
60
|
+
expect(rejected).toEqual(['customers:customerDictionaryEntry', 42])
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('never reports an accepted entry as rejected', () => {
|
|
64
|
+
const rejected: unknown[] = []
|
|
65
|
+
const moduleExports = { [QUERY_INDEX_REINDEX_EXPORT]: ['customers:deal', 'customers:deal'] }
|
|
66
|
+
|
|
67
|
+
expect(readQueryIndexReindexDeclaration(moduleExports, (value) => rejected.push(value))).toEqual([
|
|
68
|
+
'customers:deal',
|
|
69
|
+
])
|
|
70
|
+
expect(rejected).toEqual([])
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
describe('formatQueryIndexRebuildCommands', () => {
|
|
75
|
+
it('renders the operator fallback command for every entity type', () => {
|
|
76
|
+
expect(formatQueryIndexRebuildCommands(['customers:deal'])).toEqual([
|
|
77
|
+
'mercato query_index rebuild --entity customers:deal --global',
|
|
78
|
+
])
|
|
79
|
+
})
|
|
80
|
+
})
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data migrations rewrite columns in raw SQL, so they bypass every CRUD/indexer helper that
|
|
3
|
+
* would normally emit `query_index.upsert_one`. A migration also cannot emit from where it
|
|
4
|
+
* stands: it runs inside its own transaction with no DI container, and the projection must
|
|
5
|
+
* only be refreshed once the rewrite has committed.
|
|
6
|
+
*
|
|
7
|
+
* A migration therefore *declares* the entity types whose projections it invalidated, and
|
|
8
|
+
* `mercato db migrate` discharges the obligation after the whole run commits.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const QUERY_INDEX_REINDEX_EXPORT = 'queryIndexReindexEntityTypes'
|
|
12
|
+
|
|
13
|
+
const ENTITY_TYPE_PATTERN = /^[a-z0-9_]+:[a-z0-9_]+$/
|
|
14
|
+
|
|
15
|
+
export function isQueryIndexEntityType(value: unknown): value is string {
|
|
16
|
+
return typeof value === 'string' && ENTITY_TYPE_PATTERN.test(value)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function declareQueryIndexReindex(entityTypes: readonly string[]): readonly string[] {
|
|
20
|
+
if (!Array.isArray(entityTypes) || entityTypes.length === 0) {
|
|
21
|
+
throw new Error('[internal] declareQueryIndexReindex requires at least one entity type')
|
|
22
|
+
}
|
|
23
|
+
const normalized: string[] = []
|
|
24
|
+
for (const entityType of entityTypes) {
|
|
25
|
+
if (!isQueryIndexEntityType(entityType)) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
`[internal] declareQueryIndexReindex expects "module:entity" identifiers, received: ${String(entityType)}`,
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
if (!normalized.includes(entityType)) normalized.push(entityType)
|
|
31
|
+
}
|
|
32
|
+
return Object.freeze(normalized)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The reader — not `declareQueryIndexReindex` — is the contract's real boundary: it accepts any
|
|
37
|
+
* `queryIndexReindexEntityTypes` array, including one written as a plain literal. A rejected entry
|
|
38
|
+
* is therefore reported through `onReject` rather than dropped silently, so a typo such as
|
|
39
|
+
* `customers:customerDictionaryEntry` cannot leave a projection stale behind a green migrate run.
|
|
40
|
+
*/
|
|
41
|
+
export function readQueryIndexReindexDeclaration(
|
|
42
|
+
moduleExports: unknown,
|
|
43
|
+
onReject?: (value: unknown) => void,
|
|
44
|
+
): string[] {
|
|
45
|
+
if (!moduleExports || typeof moduleExports !== 'object') return []
|
|
46
|
+
const declared = (moduleExports as Record<string, unknown>)[QUERY_INDEX_REINDEX_EXPORT]
|
|
47
|
+
if (!Array.isArray(declared)) return []
|
|
48
|
+
const collected: string[] = []
|
|
49
|
+
for (const entityType of declared) {
|
|
50
|
+
if (!isQueryIndexEntityType(entityType)) {
|
|
51
|
+
onReject?.(entityType)
|
|
52
|
+
continue
|
|
53
|
+
}
|
|
54
|
+
if (!collected.includes(entityType)) collected.push(entityType)
|
|
55
|
+
}
|
|
56
|
+
return collected
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function formatQueryIndexRebuildCommands(entityTypes: readonly string[]): string[] {
|
|
60
|
+
return entityTypes.map((entityType) => `mercato query_index rebuild --entity ${entityType} --global`)
|
|
61
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import {
|
|
2
|
+
applyInjectionWidgetOverridesToEntries,
|
|
3
|
+
applyInjectionWidgetOverridesToTables,
|
|
4
|
+
applyModuleOverridesFromEnabledModules,
|
|
5
|
+
resetModuleContractOverridesForTests,
|
|
6
|
+
resetModuleOverrideAppliersForTests,
|
|
7
|
+
type ModuleEntryWithOverrides,
|
|
8
|
+
} from '../overrides'
|
|
9
|
+
import type { ModuleInjectionWidgetEntry } from '../registry'
|
|
10
|
+
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
11
|
+
|
|
12
|
+
jest.mock('@open-mercato/shared/lib/logger', () => {
|
|
13
|
+
const mocked = {
|
|
14
|
+
debug: jest.fn(),
|
|
15
|
+
info: jest.fn(),
|
|
16
|
+
warn: jest.fn(),
|
|
17
|
+
error: jest.fn(),
|
|
18
|
+
child: jest.fn(),
|
|
19
|
+
}
|
|
20
|
+
mocked.child.mockImplementation(() => mocked)
|
|
21
|
+
return { createLogger: jest.fn(() => mocked) }
|
|
22
|
+
})
|
|
23
|
+
const loggerWarn = createLogger('shared').warn as jest.Mock
|
|
24
|
+
|
|
25
|
+
const ENTRY_KEY = 'catalog:product-seo:widget'
|
|
26
|
+
const WIDGET_ID = 'catalog.injection.product-seo'
|
|
27
|
+
|
|
28
|
+
function makeEntries(): ModuleInjectionWidgetEntry[] {
|
|
29
|
+
return [
|
|
30
|
+
{ moduleId: 'catalog', key: ENTRY_KEY, source: 'package', widgetId: WIDGET_ID, loader: jest.fn() },
|
|
31
|
+
{ moduleId: 'catalog', key: 'catalog:pricing:widget', source: 'package', widgetId: 'catalog.injection.pricing', loader: jest.fn() },
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function makeTables() {
|
|
36
|
+
return [
|
|
37
|
+
{
|
|
38
|
+
moduleId: 'catalog',
|
|
39
|
+
table: {
|
|
40
|
+
'backend.product.tabs': [
|
|
41
|
+
{ widgetId: WIDGET_ID, priority: 10 },
|
|
42
|
+
{ widgetId: 'catalog.injection.pricing', priority: 20 },
|
|
43
|
+
],
|
|
44
|
+
'backend.product.footer': WIDGET_ID,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function disableWidget(overrideKey: string): void {
|
|
51
|
+
const moduleEntry: ModuleEntryWithOverrides = {
|
|
52
|
+
id: 'app',
|
|
53
|
+
from: '@app',
|
|
54
|
+
overrides: { widgets: { injection: { [overrideKey]: null } } },
|
|
55
|
+
}
|
|
56
|
+
applyModuleOverridesFromEnabledModules([moduleEntry])
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function staleWarnings(): unknown[][] {
|
|
60
|
+
return loggerWarn.mock.calls.filter((args) => String(args[0]).includes('did not match any registered entry'))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
resetModuleOverrideAppliersForTests()
|
|
65
|
+
resetModuleContractOverridesForTests()
|
|
66
|
+
loggerWarn.mockClear()
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
describe('injection widget overrides accept either identifier (#5152)', () => {
|
|
70
|
+
it.each([
|
|
71
|
+
['entry.key', ENTRY_KEY],
|
|
72
|
+
['widgetId', WIDGET_ID],
|
|
73
|
+
])('drops the entry and its table slots when the override is keyed by %s', (_label, overrideKey) => {
|
|
74
|
+
disableWidget(overrideKey)
|
|
75
|
+
|
|
76
|
+
const entries = applyInjectionWidgetOverridesToEntries(makeEntries())
|
|
77
|
+
expect(entries.map((entry) => entry.key)).toEqual(['catalog:pricing:widget'])
|
|
78
|
+
|
|
79
|
+
expect(applyInjectionWidgetOverridesToTables(makeTables())).toEqual([
|
|
80
|
+
{
|
|
81
|
+
moduleId: 'catalog',
|
|
82
|
+
table: {
|
|
83
|
+
'backend.product.tabs': [{ widgetId: 'catalog.injection.pricing', priority: 20 }],
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
])
|
|
87
|
+
|
|
88
|
+
expect(staleWarnings()).toEqual([])
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('resolves the key/widgetId pair from entries handed to the table filter directly', () => {
|
|
92
|
+
disableWidget(ENTRY_KEY)
|
|
93
|
+
|
|
94
|
+
expect(applyInjectionWidgetOverridesToTables(makeTables(), undefined, makeEntries())).toEqual([
|
|
95
|
+
{
|
|
96
|
+
moduleId: 'catalog',
|
|
97
|
+
table: {
|
|
98
|
+
'backend.product.tabs': [{ widgetId: 'catalog.injection.pricing', priority: 20 }],
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
])
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('replaces the entry when the override is keyed by widgetId', () => {
|
|
105
|
+
const replacement: ModuleInjectionWidgetEntry = {
|
|
106
|
+
moduleId: 'app',
|
|
107
|
+
key: ENTRY_KEY,
|
|
108
|
+
source: 'app',
|
|
109
|
+
widgetId: WIDGET_ID,
|
|
110
|
+
loader: jest.fn(),
|
|
111
|
+
}
|
|
112
|
+
applyModuleOverridesFromEnabledModules([{
|
|
113
|
+
id: 'app',
|
|
114
|
+
from: '@app',
|
|
115
|
+
overrides: { widgets: { injection: { [WIDGET_ID]: replacement } } },
|
|
116
|
+
}])
|
|
117
|
+
|
|
118
|
+
expect(applyInjectionWidgetOverridesToEntries(makeEntries())[0]).toBe(replacement)
|
|
119
|
+
expect(staleWarnings()).toEqual([])
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('accepts a map carrying both spellings without reporting either as stale', () => {
|
|
123
|
+
applyModuleOverridesFromEnabledModules([{
|
|
124
|
+
id: 'app',
|
|
125
|
+
from: '@app',
|
|
126
|
+
overrides: { widgets: { injection: { [ENTRY_KEY]: null, [WIDGET_ID]: null } } },
|
|
127
|
+
}])
|
|
128
|
+
|
|
129
|
+
expect(applyInjectionWidgetOverridesToEntries(makeEntries()).map((entry) => entry.key))
|
|
130
|
+
.toEqual(['catalog:pricing:widget'])
|
|
131
|
+
expect(applyInjectionWidgetOverridesToTables(makeTables())).toEqual([
|
|
132
|
+
{
|
|
133
|
+
moduleId: 'catalog',
|
|
134
|
+
table: {
|
|
135
|
+
'backend.product.tabs': [{ widgetId: 'catalog.injection.pricing', priority: 20 }],
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
])
|
|
139
|
+
expect(staleWarnings()).toEqual([])
|
|
140
|
+
expect(loggerWarn.mock.calls.filter((args) => String(args[0]).includes('Conflicting overrides'))).toEqual([])
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('warns when the two spellings of one widget disagree', () => {
|
|
144
|
+
const replacement: ModuleInjectionWidgetEntry = {
|
|
145
|
+
moduleId: 'app',
|
|
146
|
+
key: ENTRY_KEY,
|
|
147
|
+
source: 'app',
|
|
148
|
+
widgetId: WIDGET_ID,
|
|
149
|
+
loader: jest.fn(),
|
|
150
|
+
}
|
|
151
|
+
applyModuleOverridesFromEnabledModules([{
|
|
152
|
+
id: 'app',
|
|
153
|
+
from: '@app',
|
|
154
|
+
overrides: { widgets: { injection: { [ENTRY_KEY]: replacement, [WIDGET_ID]: null } } },
|
|
155
|
+
}])
|
|
156
|
+
|
|
157
|
+
expect(applyInjectionWidgetOverridesToEntries(makeEntries())[0]).toBe(replacement)
|
|
158
|
+
expect(staleWarnings()).toEqual([])
|
|
159
|
+
expect(loggerWarn.mock.calls.filter((args) => String(args[0]).includes('Conflicting overrides'))).toHaveLength(1)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('warns when a replacement quietly changes the identifier it was not matched on', () => {
|
|
163
|
+
// Matched on widgetId, so the foreign `key` passes validation — and then collides
|
|
164
|
+
// with whatever registry slot already owns it. The override still applies, but the
|
|
165
|
+
// author is told which identifier moved.
|
|
166
|
+
const replacement: ModuleInjectionWidgetEntry = {
|
|
167
|
+
moduleId: 'app',
|
|
168
|
+
key: 'app:something-else:widget',
|
|
169
|
+
source: 'app',
|
|
170
|
+
widgetId: WIDGET_ID,
|
|
171
|
+
loader: jest.fn(),
|
|
172
|
+
}
|
|
173
|
+
applyModuleOverridesFromEnabledModules([{
|
|
174
|
+
id: 'app',
|
|
175
|
+
from: '@app',
|
|
176
|
+
overrides: { widgets: { injection: { [WIDGET_ID]: replacement } } },
|
|
177
|
+
}])
|
|
178
|
+
|
|
179
|
+
expect(applyInjectionWidgetOverridesToEntries(makeEntries())[0]).toBe(replacement)
|
|
180
|
+
const warnings = loggerWarn.mock.calls.filter((args) => String(args[0]).includes('changes an identifier it was not matched on'))
|
|
181
|
+
expect(warnings).toHaveLength(1)
|
|
182
|
+
expect((warnings[0][1] as { replaced: string[] }).replaced).toEqual([ENTRY_KEY])
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('reports two equivalent replacements as a duplicate instruction, not a conflict', () => {
|
|
186
|
+
// Both spellings name the same widget with the same intent; they are distinct
|
|
187
|
+
// objects only because the author wrote the literal twice.
|
|
188
|
+
const makeReplacement = (): ModuleInjectionWidgetEntry => ({
|
|
189
|
+
moduleId: 'app',
|
|
190
|
+
key: ENTRY_KEY,
|
|
191
|
+
source: 'app',
|
|
192
|
+
widgetId: WIDGET_ID,
|
|
193
|
+
loader: jest.fn(),
|
|
194
|
+
})
|
|
195
|
+
applyModuleOverridesFromEnabledModules([{
|
|
196
|
+
id: 'app',
|
|
197
|
+
from: '@app',
|
|
198
|
+
overrides: { widgets: { injection: { [ENTRY_KEY]: makeReplacement(), [WIDGET_ID]: makeReplacement() } } },
|
|
199
|
+
}])
|
|
200
|
+
|
|
201
|
+
applyInjectionWidgetOverridesToEntries(makeEntries())
|
|
202
|
+
|
|
203
|
+
expect(loggerWarn.mock.calls.filter((args) => String(args[0]).includes('Conflicting overrides'))).toEqual([])
|
|
204
|
+
expect(loggerWarn.mock.calls.filter((args) => String(args[0]).includes('Duplicate replacement overrides'))).toHaveLength(1)
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
it('keeps the key/widgetId index on globalThis so a duplicated shared instance still resolves it', () => {
|
|
208
|
+
// The index is written by @open-mercato/ui's entries filter and read by
|
|
209
|
+
// @open-mercato/core's table filter; a module-local Map would split in a
|
|
210
|
+
// standalone build that evaluates @open-mercato/shared through two chunks.
|
|
211
|
+
applyInjectionWidgetOverridesToEntries(makeEntries())
|
|
212
|
+
|
|
213
|
+
const index = (globalThis as Record<string, unknown>).__openMercatoInjectionWidgetIdAliases__
|
|
214
|
+
expect(index).toBeInstanceOf(Map)
|
|
215
|
+
expect([...((index as Map<string, Set<string>>).get(ENTRY_KEY) ?? [])]).toEqual([WIDGET_ID])
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
it('still warns for an override key that matches neither identifier', () => {
|
|
219
|
+
disableWidget('catalog.injection.does-not-exist')
|
|
220
|
+
|
|
221
|
+
expect(applyInjectionWidgetOverridesToEntries(makeEntries())).toHaveLength(2)
|
|
222
|
+
expect(staleWarnings()).toHaveLength(1)
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('leaves tables untouched when no widget is disabled', () => {
|
|
226
|
+
const tables = makeTables()
|
|
227
|
+
expect(applyInjectionWidgetOverridesToTables(tables)).toEqual(tables)
|
|
228
|
+
})
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
describe('client-side override dispatch (#5152)', () => {
|
|
232
|
+
it('dispatches only the requested domains so unwired ones are not reported', () => {
|
|
233
|
+
applyModuleOverridesFromEnabledModules(
|
|
234
|
+
[{
|
|
235
|
+
id: 'app',
|
|
236
|
+
from: '@app',
|
|
237
|
+
overrides: {
|
|
238
|
+
widgets: { injection: { [WIDGET_ID]: null } },
|
|
239
|
+
ai: { agents: { 'catalog.catalog_assistant': null } },
|
|
240
|
+
},
|
|
241
|
+
}],
|
|
242
|
+
{ domains: ['widgets'] },
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
expect(applyInjectionWidgetOverridesToEntries(makeEntries()).map((entry) => entry.key))
|
|
246
|
+
.toEqual(['catalog:pricing:widget'])
|
|
247
|
+
expect(loggerWarn.mock.calls.filter((args) => String(args[0]).includes('not yet wired'))).toEqual([])
|
|
248
|
+
})
|
|
249
|
+
})
|