@open-mercato/shared 0.6.5-develop.5337.1.534b781eac → 0.6.5
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 +1 -1
- package/dist/lib/ai/llm-provider-registry.js.map +1 -1
- package/dist/lib/crud/custom-fields.js +23 -15
- package/dist/lib/crud/custom-fields.js.map +2 -2
- package/dist/lib/crud/factory.js.map +1 -1
- package/dist/lib/crud/optimistic-lock-command.js.map +1 -1
- package/dist/lib/crud/optimistic-lock-headers.js.map +1 -1
- package/dist/lib/crud/optimistic-lock-store.js.map +1 -1
- package/dist/lib/crud/optimistic-lock.js.map +1 -1
- package/dist/lib/data/engine.js +25 -1
- package/dist/lib/data/engine.js.map +2 -2
- package/dist/lib/db/buildIlikeTerm.js +17 -0
- package/dist/lib/db/buildIlikeTerm.js.map +7 -0
- package/dist/lib/db/mikro.js +38 -9
- package/dist/lib/db/mikro.js.map +2 -2
- package/dist/lib/di/container.js +1 -1
- package/dist/lib/di/container.js.map +1 -1
- package/dist/lib/encryption/kms.js +41 -6
- package/dist/lib/encryption/kms.js.map +2 -2
- package/dist/lib/query/advanced-filter-tree.js +5 -5
- package/dist/lib/query/advanced-filter-tree.js.map +2 -2
- package/dist/lib/query/advanced-filter.js +5 -5
- package/dist/lib/query/advanced-filter.js.map +2 -2
- package/dist/lib/query/engine.js +3 -1
- package/dist/lib/query/engine.js.map +2 -2
- package/dist/lib/query/types.js.map +1 -1
- package/dist/lib/version.js +1 -1
- package/dist/lib/version.js.map +1 -1
- package/dist/modules/overrides.js +1 -1
- package/dist/modules/overrides.js.map +1 -1
- package/dist/modules/search.js.map +1 -1
- package/package.json +4 -5
- package/src/lib/ai/llm-provider-registry.ts +1 -1
- package/src/lib/ai/llm-provider.ts +1 -1
- package/src/lib/crud/__tests__/custom-fields.test.ts +91 -0
- package/src/lib/crud/custom-fields.ts +30 -17
- package/src/lib/crud/factory.ts +1 -1
- package/src/lib/crud/optimistic-lock-command.ts +1 -1
- package/src/lib/crud/optimistic-lock-headers.ts +1 -1
- package/src/lib/crud/optimistic-lock-store.ts +1 -1
- package/src/lib/crud/optimistic-lock.ts +1 -1
- package/src/lib/data/__tests__/engine.custom-entity-storage-guard.test.ts +78 -0
- package/src/lib/data/engine.ts +40 -0
- package/src/lib/db/__tests__/buildIlikeTerm.test.ts +40 -0
- package/src/lib/db/__tests__/escapeLikePattern.test.ts +123 -0
- package/src/lib/db/__tests__/mikro.test.ts +82 -0
- package/src/lib/db/buildIlikeTerm.ts +16 -0
- package/src/lib/db/mikro.ts +55 -16
- package/src/lib/di/container.ts +1 -1
- package/src/lib/encryption/__tests__/kms.test.ts +80 -0
- package/src/lib/encryption/kms.ts +55 -7
- package/src/lib/query/__tests__/engine.count-distinct.test.ts +229 -0
- package/src/lib/query/advanced-filter-tree.ts +5 -5
- package/src/lib/query/advanced-filter.ts +5 -5
- package/src/lib/query/engine.ts +13 -2
- package/src/lib/query/types.ts +10 -0
- package/src/modules/__tests__/overrides.test.ts +1 -1
- package/src/modules/__tests__/route-overrides.test.ts +1 -1
- package/src/modules/navigation/backendChrome.ts +9 -0
- package/src/modules/overrides.ts +3 -3
- package/src/modules/search.ts +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/query/types.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityId } from '@open-mercato/shared/modules/entities'\nimport type { Profiler } from '../profiler'\nimport type { ResolvedCustomFieldDefinitions } from '../crud/custom-field-definition-index'\n\nexport type FilterOp = 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'nin' | 'like' | 'ilike' | 'exists'\n\nexport enum SortDir {\n Asc = 'asc',\n Desc = 'desc',\n}\n\nexport type FieldSelector = string // base field or custom field key (prefixed with 'cf:')\n\nexport type Filter = {\n field: FieldSelector\n op: FilterOp\n value?: any\n}\n\nexport type Sort = { field: FieldSelector; dir?: SortDir }\n\nexport type Page = { page?: number; pageSize?: number }\n\n// Mongo/Medusa-style filter operators (typed)\nexport type WhereOps<T> = {\n $eq?: T\n $ne?: T | null\n $gt?: T extends number | Date ? T : never\n $gte?: T extends number | Date ? T : never\n $lt?: T extends number | Date ? T : never\n $lte?: T extends number | Date ? T : never\n $in?: T[]\n $nin?: T[]\n $like?: T extends string ? string : never\n $ilike?: T extends string ? string : never\n $exists?: boolean\n}\n\n// A field filter can be a direct value (equals) or ops object\nexport type WhereValue<T = any> = T | WhereOps<T>\n\n// Generic shape for object filters. If you have a typed map of field\u2192type,\n// pass it as the generic to get end-to-end typing.\n// Example: Where<{\n// id: string; title: string; created_at: Date; 'cf:severity': number\n// }>\nexport type Where<Fields extends Record<string, any> = Record<string, any>> =\n Partial<{ [K in keyof Fields]: WhereValue<Fields[K]> }> & Record<string, WhereValue>\n\nexport type QueryCustomFieldJoin = {\n fromField: string\n toField: string\n type?: 'left' | 'inner'\n}\n\nexport type QueryCustomFieldSource = {\n entityId: EntityId\n table?: string\n alias?: string\n recordIdColumn?: string\n join?: QueryCustomFieldJoin\n tenantField?: string\n organizationField?: string\n}\n\nexport type QueryJoinEdge = {\n alias: string\n table?: string\n entityId?: EntityId\n from: {\n alias?: string\n field: string\n }\n to: {\n field: string\n }\n type?: 'left' | 'inner'\n}\n\n/**\n * Optional context for query-level UMES extensions.\n * When provided, the query engine will execute sync lifecycle events\n * (querying/queried) and apply query-enabled enrichers.\n */\nexport type QueryExtensionsConfig = {\n userId?: string\n container?: unknown\n userFeatures?: string[]\n resolve?: <T = unknown>(name: string) => T\n}\n\nexport type QueryOptions = {\n fields?: FieldSelector[] // base fields and/or 'cf:<key>' for custom fields\n includeExtensions?: boolean | string[] // include all registered extensions or only specific ones by entity id\n includeCustomFields?: boolean | string[] // include all CFs or specific keys\n // Accept classic array syntax or Mongo-style object syntax\n filters?: Filter[] | Where\n sort?: Sort[]\n page?: Page\n organizationId?: string // enforce multi-tenant scope\n tenantId?: string // enforce tenant scope\n // Optional list of organization ids to scope results. Takes precedence over organizationId.\n organizationIds?: string[]\n /**\n * When true, the engine does not apply default `organization_id` / `tenant_id` equality guards.\n *\n * Callers MUST encode full visibility in `filters` (for example with `$or` of scoped branches)\n * and MUST fail closed when the authenticated principal lacks a resolvable tenant/org, otherwise\n * queries return cross-tenant rows.\n *\n * When this flag is set, the hybrid query engine delegates to the basic engine, which means\n * custom-field (`cf:*`) filters/sorts, `search_tokens` fulltext filtering, and vector-search\n * branches are BYPASSED. Only use this on entities whose scoping does not match the standard\n * `organization_id = X AND tenant_id = Y` shape and which do not rely on custom-field/search\n * features.\n */\n omitAutomaticTenantOrgScope?: boolean\n // Soft-delete behavior: when false (default), rows with non-null deleted_at\n // are excluded if the base table has that column. Set true to include them.\n withDeleted?: boolean\n customFieldSources?: QueryCustomFieldSource[]\n joins?: QueryJoinEdge[]\n profiler?: Profiler\n // When true, suppress automatic reindex scheduling triggered by coverage gap detection.\n // Used by the search indexing pipeline to prevent feedback loops where indexing triggers\n // re-indexing indefinitely.\n skipAutoReindex?: boolean\n // Optional UMES query extensions context. When provided, the engine will\n // emit sync lifecycle events and apply query-level enrichers.\n extensions?: QueryExtensionsConfig\n}\n\nexport type PartialIndexWarning = {\n entity: EntityId\n entityLabel?: string | null\n baseCount?: number | null\n indexedCount?: number | null\n scope?: 'scoped' | 'global'\n}\n\nexport type QueryResultMeta = {\n partialIndexWarning?: PartialIndexWarning\n}\n\nexport type QueryResult<T = any> = {\n items: T[]\n page: number\n pageSize: number\n total: number\n meta?: QueryResultMeta\n /**\n * Custom-field definitions the engine resolved while building this result\n * (only present when `includeCustomFields: true`). Lets the CRUD factory\n * decorate list rows without reloading definitions from the DB (issue #2133).\n * Internal contract \u2014 additive and optional; callers must treat absence as a\n * cue to load definitions themselves.\n */\n customFieldDefinitions?: ResolvedCustomFieldDefinitions\n}\n\nexport interface QueryEngine {\n query<T = any>(entity: EntityId, opts?: QueryOptions): Promise<QueryResult<T>>\n}\n"],
|
|
4
|
+
"sourcesContent": ["import type { EntityId } from '@open-mercato/shared/modules/entities'\nimport type { Profiler } from '../profiler'\nimport type { ResolvedCustomFieldDefinitions } from '../crud/custom-field-definition-index'\n\nexport type FilterOp = 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'nin' | 'like' | 'ilike' | 'exists'\n\nexport enum SortDir {\n Asc = 'asc',\n Desc = 'desc',\n}\n\nexport type FieldSelector = string // base field or custom field key (prefixed with 'cf:')\n\nexport type Filter = {\n field: FieldSelector\n op: FilterOp\n value?: any\n}\n\nexport type Sort = { field: FieldSelector; dir?: SortDir }\n\nexport type Page = { page?: number; pageSize?: number }\n\n// Mongo/Medusa-style filter operators (typed)\nexport type WhereOps<T> = {\n $eq?: T\n $ne?: T | null\n $gt?: T extends number | Date ? T : never\n $gte?: T extends number | Date ? T : never\n $lt?: T extends number | Date ? T : never\n $lte?: T extends number | Date ? T : never\n $in?: T[]\n $nin?: T[]\n $like?: T extends string ? string : never\n $ilike?: T extends string ? string : never\n $exists?: boolean\n}\n\n// A field filter can be a direct value (equals) or ops object\nexport type WhereValue<T = any> = T | WhereOps<T>\n\n// Generic shape for object filters. If you have a typed map of field\u2192type,\n// pass it as the generic to get end-to-end typing.\n// Example: Where<{\n// id: string; title: string; created_at: Date; 'cf:severity': number\n// }>\nexport type Where<Fields extends Record<string, any> = Record<string, any>> =\n Partial<{ [K in keyof Fields]: WhereValue<Fields[K]> }> & Record<string, WhereValue>\n\nexport type QueryCustomFieldJoin = {\n fromField: string\n toField: string\n type?: 'left' | 'inner'\n}\n\nexport type QueryCustomFieldSource = {\n entityId: EntityId\n table?: string\n alias?: string\n recordIdColumn?: string\n join?: QueryCustomFieldJoin\n tenantField?: string\n organizationField?: string\n}\n\nexport type QueryJoinEdge = {\n alias: string\n table?: string\n entityId?: EntityId\n from: {\n alias?: string\n field: string\n }\n to: {\n field: string\n }\n type?: 'left' | 'inner'\n}\n\n/**\n * Optional context for query-level UMES extensions.\n * When provided, the query engine will execute sync lifecycle events\n * (querying/queried) and apply query-enabled enrichers.\n */\nexport type QueryExtensionsConfig = {\n userId?: string\n container?: unknown\n userFeatures?: string[]\n resolve?: <T = unknown>(name: string) => T\n}\n\nexport type QueryOptions = {\n fields?: FieldSelector[] // base fields and/or 'cf:<key>' for custom fields\n includeExtensions?: boolean | string[] // include all registered extensions or only specific ones by entity id\n includeCustomFields?: boolean | string[] // include all CFs or specific keys\n // Accept classic array syntax or Mongo-style object syntax\n filters?: Filter[] | Where\n sort?: Sort[]\n page?: Page\n organizationId?: string // enforce multi-tenant scope\n tenantId?: string // enforce tenant scope\n // Optional list of organization ids to scope results. Takes precedence over organizationId.\n organizationIds?: string[]\n /**\n * When true, the engine does not apply default `organization_id` / `tenant_id` equality guards.\n *\n * Callers MUST encode full visibility in `filters` (for example with `$or` of scoped branches)\n * and MUST fail closed when the authenticated principal lacks a resolvable tenant/org, otherwise\n * queries return cross-tenant rows.\n *\n * When this flag is set, the hybrid query engine delegates to the basic engine, which means\n * custom-field (`cf:*`) filters/sorts, `search_tokens` fulltext filtering, and vector-search\n * branches are BYPASSED. Only use this on entities whose scoping does not match the standard\n * `organization_id = X AND tenant_id = Y` shape and which do not rely on custom-field/search\n * features.\n */\n omitAutomaticTenantOrgScope?: boolean\n // Soft-delete behavior: when false (default), rows with non-null deleted_at\n // are excluded if the base table has that column. Set true to include them.\n withDeleted?: boolean\n customFieldSources?: QueryCustomFieldSource[]\n joins?: QueryJoinEdge[]\n profiler?: Profiler\n // When true, suppress automatic reindex scheduling triggered by coverage gap detection.\n // Used by the search indexing pipeline to prevent feedback loops where indexing triggers\n // re-indexing indefinitely.\n skipAutoReindex?: boolean\n /**\n * Force routing this query to custom-entity doc storage (`custom_entities_storage`)\n * instead of classifying the entity automatically. Automatic classification routes\n * ids backed by a registered ORM table to that base table, so surfaces that manage\n * doc records for ids that are ALSO table-backed (e.g. the entities records browser\n * reading a module-declared custom entity such as `example:todo`) must set this flag.\n * Honored by the hybrid query engine only; `BasicQueryEngine` has no doc-storage\n * reader and ignores it.\n */\n forceCustomEntityStorage?: boolean\n // Optional UMES query extensions context. When provided, the engine will\n // emit sync lifecycle events and apply query-level enrichers.\n extensions?: QueryExtensionsConfig\n}\n\nexport type PartialIndexWarning = {\n entity: EntityId\n entityLabel?: string | null\n baseCount?: number | null\n indexedCount?: number | null\n scope?: 'scoped' | 'global'\n}\n\nexport type QueryResultMeta = {\n partialIndexWarning?: PartialIndexWarning\n}\n\nexport type QueryResult<T = any> = {\n items: T[]\n page: number\n pageSize: number\n total: number\n meta?: QueryResultMeta\n /**\n * Custom-field definitions the engine resolved while building this result\n * (only present when `includeCustomFields: true`). Lets the CRUD factory\n * decorate list rows without reloading definitions from the DB (issue #2133).\n * Internal contract \u2014 additive and optional; callers must treat absence as a\n * cue to load definitions themselves.\n */\n customFieldDefinitions?: ResolvedCustomFieldDefinitions\n}\n\nexport interface QueryEngine {\n query<T = any>(entity: EntityId, opts?: QueryOptions): Promise<QueryResult<T>>\n}\n"],
|
|
5
5
|
"mappings": "AAMO,IAAK,UAAL,kBAAKA,aAAL;AACL,EAAAA,SAAA,SAAM;AACN,EAAAA,SAAA,UAAO;AAFG,SAAAA;AAAA,GAAA;",
|
|
6
6
|
"names": ["SortDir"]
|
|
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.5
|
|
4
|
+
"sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.6.5'\nexport const appVersion = APP_VERSION\n"],
|
|
5
5
|
"mappings": "AACO,MAAM,cAAc;AACpB,MAAM,aAAa;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -25,7 +25,7 @@ const DOMAIN_KEYS = [
|
|
|
25
25
|
"di",
|
|
26
26
|
"encryption"
|
|
27
27
|
];
|
|
28
|
-
const TRACKING_ISSUE_HINT = "See `.ai/specs/2026-05-04-modules-ts-unified-overrides.md` and tracking issue https://github.com/open-mercato/open-mercato/issues/1787.";
|
|
28
|
+
const TRACKING_ISSUE_HINT = "See `.ai/specs/implemented/2026-05-04-modules-ts-unified-overrides.md` and tracking issue https://github.com/open-mercato/open-mercato/issues/1787.";
|
|
29
29
|
function applyModuleOverridesFromEnabledModules(modules) {
|
|
30
30
|
if (!Array.isArray(modules) || modules.length === 0) return;
|
|
31
31
|
const buckets = /* @__PURE__ */ new Map();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/modules/overrides.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Unified `modules.ts` override surface \u2014 one place for downstream apps to\n * replace or disable any contract a module presents.\n *\n * Spec: `.ai/specs/2026-05-04-modules-ts-unified-overrides.md`\n *\n * Each `ModuleEntry` in `apps/<app>/src/modules.ts` may carry an\n * `overrides` field whose sub-keys address one domain at a time:\n *\n * {\n * id: 'example',\n * from: '@app',\n * overrides: {\n * ai: { agents: {...}, tools: {...} }, // Phase 1 \u2014 wired\n * routes: { api: {...}, pages: {...} }, // Phase 2/3 \u2014 wired\n * events: { subscribers: {...} }, // Phase 4 \u2014 wired\n * workers: {...}, // Phase 5 \u2014 wired\n * ...\n * },\n * }\n *\n * The umbrella shape is the union of every per-domain sub-shape. Per-\n * domain runtime hooks own their composers and apply the override map\n * against their registry. During the rollout the dispatcher emitted a\n * one-shot warning for unwired domains; all domains in this umbrella are\n * now wired, so warnings only indicate a missing custom applier in tests\n * or future domain additions.\n *\n * Resolution order across all domains (highest precedence first):\n *\n * 1. Programmatic \u2014 direct calls into the per-domain `apply*Overrides()` API.\n * 2. `modules.ts` inline \u2014 `entry.overrides.<domain>` here.\n * 3. File-based \u2014 overrides exported from a contributing module's own files.\n * 4. Base \u2014 the module's own registrations.\n *\n * `null` always means \"disable\"; a definition replaces.\n */\n\n// ---------------------------------------------------------------------------\n// Domain sub-shapes\n// ---------------------------------------------------------------------------\n\n/**\n * AI domain \u2014 agents and tools. Re-exports the canonical maps from\n * `@open-mercato/ai-assistant` so consumers do not need to import that\n * package directly when they only want to declare overrides.\n *\n * Imported lazily as `unknown` here because `@open-mercato/shared` must\n * NOT take a runtime dependency on `@open-mercato/ai-assistant` (the\n * dependency direction is the other way around). Apps that author\n * `entry.overrides.ai` should import the strongly-typed\n * `AiAgentOverridesMap` / `AiToolOverridesMap` from `@open-mercato/ai-assistant`\n * directly \u2014 TypeScript structurally compatible types make the loose\n * shape here a no-op annotation cost.\n */\nexport interface AiOverridesShape {\n agents?: Record<string, unknown>\n tools?: Record<string, unknown>\n extensions?: unknown[]\n}\n\ntype LooseOverrideMap = Record<string, unknown>\n\n/**\n * Phase 2 (api) / Phase 3 (pages).\n *\n * The `api` sub-shape is keyed by `'METHOD /api/path'` and accepts either\n * a replacement definition (`{ handler, metadata? }`) or `null` to disable.\n * See {@link ApiRouteOverridesMap} for the strongly-typed alias and\n * {@link applyApiOverridesToManifests} for the runtime apply step.\n *\n * The `pages` sub-shape is keyed by `'/backend/path'` or\n * `'/frontend/path'`; `null` disables the page and a definition replaces\n * its loader and/or metadata.\n */\nexport interface RoutesOverridesShape {\n api?: ApiRouteOverridesMap | LooseOverrideMap\n pages?: PageRouteOverridesMap | LooseOverrideMap\n}\n\n/** Phase 4 \u2014 event subscribers. */\nexport interface EventsOverridesShape {\n subscribers?: SubscriberOverridesMap | LooseOverrideMap\n}\n\n/** Phase 6/7/8 \u2014 widget injection, component handles, dashboard widgets. */\nexport interface WidgetsOverridesShape {\n injection?: InjectionWidgetOverridesMap | LooseOverrideMap\n components?: ComponentOverridesMap | LooseOverrideMap\n dashboard?: DashboardWidgetOverridesMap | LooseOverrideMap\n}\n\n/** Phase 9 \u2014 notification types + handlers. */\nexport interface NotificationsOverridesShape {\n types?: NotificationTypeOverridesMap | LooseOverrideMap\n handlers?: NotificationHandlerOverridesMap | LooseOverrideMap\n}\n\n/** Phase 15 \u2014 setup lifecycle hooks. */\nexport interface SetupOverridesShape {\n defaultRoleFeatures?: Record<string, readonly string[]>\n defaultCustomerRoleFeatures?: Record<string, readonly string[]>\n seedDefaults?: false\n seedExamples?: false\n onTenantCreated?: false\n}\n\n/** Phase 16 \u2014 ACL features (per-feature override). */\nexport interface AclOverridesShape {\n features?: AclFeatureOverridesMap | LooseOverrideMap\n}\n\n/** Phase 18 \u2014 encryption maps per entity id. */\nexport interface EncryptionOverridesShape {\n maps?: EncryptionMapOverridesMap | LooseOverrideMap\n}\n\n/**\n * Umbrella shape for `entry.overrides`. Every key is optional; a\n * downstream app sets only the domains it cares about.\n */\nexport interface ModuleOverrides {\n ai?: AiOverridesShape\n routes?: RoutesOverridesShape\n events?: EventsOverridesShape\n workers?: WorkerOverridesMap | LooseOverrideMap\n widgets?: WidgetsOverridesShape\n notifications?: NotificationsOverridesShape\n interceptors?: ApiInterceptorOverridesMap | LooseOverrideMap\n commandInterceptors?: CommandInterceptorOverridesMap | LooseOverrideMap\n enrichers?: ResponseEnricherOverridesMap | LooseOverrideMap\n guards?: PageGuardOverridesMap | LooseOverrideMap\n cli?: CliOverridesMap | LooseOverrideMap\n setup?: SetupOverridesShape\n acl?: AclOverridesShape\n di?: DiOverridesMap | LooseOverrideMap\n encryption?: EncryptionOverridesShape\n}\n\n/**\n * Public shape consumed by the dispatcher. Mirrors the `ModuleEntry`\n * defined in each app's `modules.ts` \u2014 the dispatcher only needs `id`\n * and `overrides`.\n */\nexport interface ModuleEntryWithOverrides {\n id: string\n from?: string\n overrides?: ModuleOverrides\n}\n\n// ---------------------------------------------------------------------------\n// Per-domain runtime hook registry\n// ---------------------------------------------------------------------------\n\n/**\n * Each wired domain registers an applier that receives the list of\n * `(moduleId, overrides)` pairs in module-load order and forwards them\n * to its own runtime hook. Unwired domains do not register an applier\n * and instead trigger the dispatcher's one-shot warning.\n */\nexport type ModuleOverrideDomain =\n | 'ai'\n | 'routes'\n | 'events'\n | 'workers'\n | 'widgets'\n | 'notifications'\n | 'interceptors'\n | 'commandInterceptors'\n | 'enrichers'\n | 'guards'\n | 'cli'\n | 'setup'\n | 'acl'\n | 'di'\n | 'encryption'\n\nexport interface ModuleOverrideEntry<TShape> {\n moduleId: string\n overrides: TShape\n}\n\nexport type ModuleOverrideApplier<TShape> = (\n entries: ReadonlyArray<ModuleOverrideEntry<TShape>>,\n) => void\n\nconst appliers = new Map<ModuleOverrideDomain, ModuleOverrideApplier<unknown>>()\nconst warnedUnwiredDomains = new Set<ModuleOverrideDomain>()\n\n/**\n * Register a per-domain runtime hook. Called once at module-load time\n * by each wired domain (e.g. the AI subsystem registers `'ai'` from\n * `@open-mercato/ai-assistant`).\n */\nexport function registerModuleOverrideApplier<TShape>(\n domain: ModuleOverrideDomain,\n applier: ModuleOverrideApplier<TShape>,\n): void {\n appliers.set(domain, applier as ModuleOverrideApplier<unknown>)\n}\n\n/** @__internal Test-only hook \u2014 clear all registered appliers + warnings. */\nexport function resetModuleOverrideAppliersForTests(): void {\n appliers.clear()\n warnedUnwiredDomains.clear()\n registerBuiltInModuleOverrideAppliers()\n}\n\n// ---------------------------------------------------------------------------\n// Dispatcher\n// ---------------------------------------------------------------------------\n\nconst DOMAIN_KEYS: ModuleOverrideDomain[] = [\n 'ai',\n 'routes',\n 'events',\n 'workers',\n 'widgets',\n 'notifications',\n 'interceptors',\n 'commandInterceptors',\n 'enrichers',\n 'guards',\n 'cli',\n 'setup',\n 'acl',\n 'di',\n 'encryption',\n]\n\nconst TRACKING_ISSUE_HINT =\n 'See `.ai/specs/2026-05-04-modules-ts-unified-overrides.md` and tracking issue https://github.com/open-mercato/open-mercato/issues/1787.'\n\n/**\n * Walk every `ModuleEntry` and dispatch its `overrides.<domain>` shape\n * to the matching wired applier. Unwired domains emit a one-shot\n * structured warning.\n *\n * Call this exactly once from `apps/<app>/src/bootstrap.ts` BEFORE any\n * registry first-loads. Calling it more than once is safe but\n * accumulates per-domain entries each time.\n */\nexport function applyModuleOverridesFromEnabledModules(\n modules: ReadonlyArray<ModuleEntryWithOverrides>,\n): void {\n if (!Array.isArray(modules) || modules.length === 0) return\n\n // Bucket entries by domain in module-load order.\n const buckets = new Map<ModuleOverrideDomain, Array<ModuleOverrideEntry<unknown>>>()\n\n for (const entry of modules) {\n if (!entry || typeof entry.id !== 'string' || !entry.id) continue\n const overrides = entry.overrides\n if (!overrides || typeof overrides !== 'object') continue\n\n for (const domain of DOMAIN_KEYS) {\n const value = (overrides as Record<string, unknown>)[domain]\n if (value === undefined || value === null) continue\n if (typeof value !== 'object') continue\n const list = buckets.get(domain) ?? []\n list.push({ moduleId: entry.id, overrides: value })\n buckets.set(domain, list)\n }\n }\n\n // Dispatch each domain to its wired applier; warn on unwired domains.\n for (const [domain, entries] of buckets) {\n const applier = appliers.get(domain)\n if (!applier) {\n if (!warnedUnwiredDomains.has(domain)) {\n warnedUnwiredDomains.add(domain)\n const moduleIds = Array.from(new Set(entries.map((e) => e.moduleId))).join(', ')\n console.warn(\n `[Module Overrides] Domain \"${domain}\" not yet wired \u2014 entry.overrides.${domain} for module(s) [${moduleIds}] was ignored. ${TRACKING_ISSUE_HINT}`,\n )\n }\n continue\n }\n applier(entries)\n }\n}\n\n// ---------------------------------------------------------------------------\n// Phase 2/3 \u2014 Routes (API + pages) applier\n// ---------------------------------------------------------------------------\n//\n// Wires `entry.overrides.routes.api` so a downstream module can disable or\n// replace any API route declared by another module. The override map is\n// keyed by `'METHOD /api/path'` (case-insensitive method, leading slash on\n// the path), and each value is either:\n//\n// - `null` \u2014 disable the matching method on the route.\n// - `{ handler, metadata? }` \u2014 replace the matching method's handler\n// (and optionally merge override metadata into the route's metadata).\n//\n// Resolution order (lowest precedence first):\n//\n// 1. `modules.ts` inline \u2014 every `entry.overrides.routes.api` on a\n// `ModuleEntry` (last entry per key wins).\n// 2. Programmatic \u2014 `applyApiRouteOverrides({...})` from anywhere at\n// boot or test scaffolds (last call per key wins).\n//\n// A file-based tier (per-module `apiRouteOverrides` exports) is **not**\n// part of Phase 2 \u2014 modules that ship a route override do so via\n// `modules.ts` or programmatically. The umbrella spec keeps the file-based\n// tier \"where applicable\".\n//\n// The applied overrides are consumed by `registerApiRouteManifests` (see\n// `./registry.ts`) which rewrites the manifest array once before storing\n// it. Calls to `applyApiRouteOverrides` made AFTER manifests are already\n// registered do not retro-actively update the stored manifest \u2014 for\n// runtime override scenarios call the dispatcher / programmatic API\n// before `bootstrap.ts` triggers manifest registration.\n//\n// Spec: `.ai/specs/2026-05-04-modules-ts-unified-overrides.md` (Phases 2/3).\n\n// ApiRouteManifestEntry / ApiHandler / HttpMethod live in ./registry. The\n// type-only import is erased at runtime, so there is no cycle even though\n// ./registry imports the runtime helpers below.\nimport type {\n ApiHandler,\n ApiRouteManifestEntry,\n BackendRouteManifestEntry,\n FrontendRouteManifestEntry,\n HttpMethod,\n Module,\n ModuleCli,\n ModuleDashboardWidgetEntry,\n ModuleInjectionWidgetEntry,\n ModuleSubscriber,\n ModuleWorker,\n} from './registry'\nimport type { ModuleInjectionTable } from './widgets/injection'\nimport type { ComponentOverride } from './widgets/component-registry'\nimport type { NotificationHandler } from './notifications/handler'\nimport type { NotificationTypeDefinition } from './notifications/types'\nimport type { ModuleEncryptionMap } from './encryption'\nimport type { ModuleSetupConfig } from './setup'\nimport type { ApiInterceptor } from '../lib/crud/api-interceptor'\nimport type { ResponseEnricher } from '../lib/crud/response-enricher'\nimport type { CommandInterceptor } from '../lib/commands/command-interceptor'\nimport type { PageMiddlewareRegistryEntry, PageRouteMiddleware } from './middleware/page'\n\n/** Override for a single API route entry: replace handler/metadata, or `null` to disable. */\nexport interface ApiRouteOverrideDefinition {\n /** Replacement handler. Same signature as the original API route handler. */\n handler: ApiHandler\n /** Optional metadata override \u2014 replaces the per-method metadata on the route. */\n metadata?: unknown\n}\n\n/** Override value per `'METHOD /api/path'` key \u2014 definition or `null` to disable. */\nexport type ApiRouteOverride = ApiRouteOverrideDefinition | null\n\n/** Map of `'METHOD /api/path'` \u2192 override. */\nexport type ApiRouteOverridesMap = Record<string, ApiRouteOverride>\n\ntype OverrideStore<T> = {\n modules: Record<string, T | null>\n programmatic: Record<string, T | null>\n}\n\ntype OverrideValue<T> = T | null\ntype OverrideMap<T> = Record<string, OverrideValue<T>>\n\n/** Override for a backend/frontend page route: replace loader/metadata, or `null` to disable. */\nexport interface PageRouteOverrideDefinition {\n load?: BackendRouteManifestEntry['load']\n Component?: Awaited<ReturnType<BackendRouteManifestEntry['load']>>\n metadata?: Partial<Omit<BackendRouteManifestEntry | FrontendRouteManifestEntry, 'moduleId' | 'load'>>\n}\n\nexport type PageRouteOverride = PageRouteOverrideDefinition | null\nexport type PageRouteOverridesMap = Record<string, PageRouteOverride>\n\nexport type SubscriberOverride = ModuleSubscriber | null\nexport type SubscriberOverridesMap = Record<string, SubscriberOverride>\n\nexport type WorkerOverride = ModuleWorker | null\nexport type WorkerOverridesMap = Record<string, WorkerOverride>\n\nexport type CliOverride = ModuleCli | null\nexport type CliOverridesMap = Record<string, CliOverride>\n\nexport type InjectionWidgetOverride = ModuleInjectionWidgetEntry | null\nexport type InjectionWidgetOverridesMap = Record<string, InjectionWidgetOverride>\n\nexport type DashboardWidgetOverride = ModuleDashboardWidgetEntry | null\nexport type DashboardWidgetOverridesMap = Record<string, DashboardWidgetOverride>\n\nexport type ComponentOverrideValue = ComponentOverride | ComponentOverride[] | null\nexport type ComponentOverridesMap = Record<string, ComponentOverrideValue>\n\nexport type NotificationTypeOverride = NotificationTypeDefinition | null\nexport type NotificationTypeOverridesMap = Record<string, NotificationTypeOverride>\n\nexport type NotificationHandlerOverride = NotificationHandler | null\nexport type NotificationHandlerOverridesMap = Record<string, NotificationHandlerOverride>\n\nexport type ApiInterceptorOverride = ApiInterceptor | null\nexport type ApiInterceptorOverridesMap = Record<string, ApiInterceptorOverride>\n\nexport type CommandInterceptorOverride = CommandInterceptor | null\nexport type CommandInterceptorOverridesMap = Record<string, CommandInterceptorOverride>\n\nexport type ResponseEnricherOverride = ResponseEnricher | null\nexport type ResponseEnricherOverridesMap = Record<string, ResponseEnricherOverride>\n\nexport type PageGuardOverride = PageRouteMiddleware | null\nexport type PageGuardOverridesMap = Record<string, PageGuardOverride>\n\nexport type AclFeatureOverride = string | { id: string; [key: string]: unknown } | null\nexport type AclFeatureOverridesMap = Record<string, AclFeatureOverride>\n\nexport type EncryptionMapOverride = ModuleEncryptionMap | null\nexport type EncryptionMapOverridesMap = Record<string, EncryptionMapOverride>\n\nexport type DiBindingOverrideDefinition = {\n register: (container: { register: (registrations: Record<string, unknown>) => unknown }, key: string) => void\n}\nexport type DiBindingOverride = DiBindingOverrideDefinition | unknown | null\nexport type DiOverridesMap = Record<string, DiBindingOverride>\n\nconst pageRouteOverrideStore: OverrideStore<PageRouteOverrideDefinition> = { modules: {}, programmatic: {} }\nconst subscriberOverrideStore: OverrideStore<ModuleSubscriber> = { modules: {}, programmatic: {} }\nconst workerOverrideStore: OverrideStore<ModuleWorker> = { modules: {}, programmatic: {} }\nconst cliOverrideStore: OverrideStore<ModuleCli> = { modules: {}, programmatic: {} }\nconst injectionWidgetOverrideStore: OverrideStore<ModuleInjectionWidgetEntry> = { modules: {}, programmatic: {} }\nconst dashboardWidgetOverrideStore: OverrideStore<ModuleDashboardWidgetEntry> = { modules: {}, programmatic: {} }\nconst componentOverrideStore: OverrideStore<ComponentOverride | ComponentOverride[]> = { modules: {}, programmatic: {} }\nconst notificationTypeOverrideStore: OverrideStore<NotificationTypeDefinition> = { modules: {}, programmatic: {} }\nconst notificationHandlerOverrideStore: OverrideStore<NotificationHandler> = { modules: {}, programmatic: {} }\nconst apiInterceptorOverrideStore: OverrideStore<ApiInterceptor> = { modules: {}, programmatic: {} }\nconst commandInterceptorOverrideStore: OverrideStore<CommandInterceptor> = { modules: {}, programmatic: {} }\nconst responseEnricherOverrideStore: OverrideStore<ResponseEnricher> = { modules: {}, programmatic: {} }\nconst pageGuardOverrideStore: OverrideStore<PageRouteMiddleware> = { modules: {}, programmatic: {} }\nconst aclFeatureOverrideStore: OverrideStore<Exclude<AclFeatureOverride, null>> = { modules: {}, programmatic: {} }\nconst encryptionMapOverrideStore: OverrideStore<ModuleEncryptionMap> = { modules: {}, programmatic: {} }\nconst diOverrideStore: OverrideStore<Exclude<DiBindingOverride, null>> = { modules: {}, programmatic: {} }\nconst setupOverridesByModule: Record<string, SetupOverridesShape> = {}\n\nfunction normalizeIdOverrideKey(key: string, label: string): string | null {\n if (typeof key !== 'string') return null\n const trimmed = key.trim()\n if (trimmed) return trimmed\n console.warn(`[Module Overrides] Skipping malformed ${label} key \"${key}\" \u2014 expected a non-empty string.`)\n return null\n}\n\nfunction clearStore<T>(store: OverrideStore<T>): void {\n for (const key of Object.keys(store.modules)) delete store.modules[key]\n for (const key of Object.keys(store.programmatic)) delete store.programmatic[key]\n}\n\n// Shared frozen empty override map. Returned from compose* helpers when both\n// the modules-tier and programmatic-tier stores are empty so per-request hot\n// paths (page guards, etc.) reuse one object instead of allocating fresh `{}`\n// per call. Consumers MUST NOT mutate \u2014 internal applier loops iterate keys\n// only, and the public API hands out shallow copies through composeApiRouteOverrides.\nconst EMPTY_OVERRIDE_MAP: Readonly<Record<string, never>> = Object.freeze({})\n\nfunction applyStoreOverrides<T>(\n store: OverrideStore<T>,\n target: 'modules' | 'programmatic',\n overrides: OverrideMap<T> | undefined,\n options: {\n label: string\n normalizeKey?: (key: string) => string | null\n },\n): void {\n if (!overrides || typeof overrides !== 'object') return\n const normalize = options.normalizeKey ?? ((key: string) => normalizeIdOverrideKey(key, options.label))\n for (const [rawKey, value] of Object.entries(overrides)) {\n const key = normalize(rawKey)\n if (!key) continue\n store[target][key] = value\n }\n}\n\nfunction composeStore<T>(store: OverrideStore<T>): OverrideMap<T> {\n const modulesKeys = Object.keys(store.modules)\n const programmaticKeys = Object.keys(store.programmatic)\n if (modulesKeys.length === 0 && programmaticKeys.length === 0) {\n return EMPTY_OVERRIDE_MAP as OverrideMap<T>\n }\n return { ...store.modules, ...store.programmatic }\n}\n\nfunction applyArrayOverrides<T>(\n items: readonly T[] | undefined,\n overrides: Readonly<OverrideMap<T>>,\n options: {\n label: string\n getId: (value: T) => string | null\n isReplacement?: (value: unknown) => value is T\n },\n): { items: T[] | undefined; consumed: Set<string>; changed: boolean } {\n if (!items || Object.keys(overrides).length === 0) {\n return { items: items ? Array.from(items) : items, consumed: new Set(), changed: false }\n }\n\n const consumed = new Set<string>()\n const result: T[] = []\n let changed = false\n\n for (const item of items) {\n const id = options.getId(item)\n if (!id || !Object.prototype.hasOwnProperty.call(overrides, id)) {\n result.push(item)\n continue\n }\n consumed.add(id)\n changed = true\n const replacement = overrides[id]\n if (replacement === null) continue\n if (options.isReplacement && !options.isReplacement(replacement)) {\n console.warn(\n `[Module Overrides] Skipping malformed ${options.label} override for \"${id}\" \u2014 replacement has the wrong shape.`,\n )\n result.push(item)\n continue\n }\n const replacementId = options.getId(replacement)\n if (replacementId !== id) {\n console.warn(\n `[Module Overrides] Skipping malformed ${options.label} override for \"${id}\" \u2014 replacement id must match the override key.`,\n )\n result.push(item)\n continue\n }\n result.push(replacement)\n }\n\n return { items: result, consumed, changed }\n}\n\nfunction warnStaleOverrides(label: string, overrides: Readonly<Record<string, unknown>>, consumed: ReadonlySet<string>): void {\n for (const key of Object.keys(overrides)) {\n if (!consumed.has(key)) {\n console.warn(`[Module Overrides] ${label} override \"${key}\" did not match any registered entry \u2014 override skipped.`)\n }\n }\n}\n\nfunction isObjectWithId(value: unknown): value is { id: string } {\n return !!value && typeof value === 'object' && typeof (value as { id?: unknown }).id === 'string'\n}\n\nfunction isModuleSubscriber(value: unknown): value is ModuleSubscriber {\n return isObjectWithId(value)\n && typeof (value as ModuleSubscriber).event === 'string'\n && typeof (value as ModuleSubscriber).handler === 'function'\n}\n\nfunction isModuleWorker(value: unknown): value is ModuleWorker {\n return isObjectWithId(value)\n && typeof (value as ModuleWorker).queue === 'string'\n && typeof (value as ModuleWorker).handler === 'function'\n}\n\nfunction isModuleCli(value: unknown): value is ModuleCli {\n return !!value\n && typeof value === 'object'\n && typeof (value as ModuleCli).command === 'string'\n && typeof (value as ModuleCli).run === 'function'\n}\n\nfunction isInjectionWidgetEntry(value: unknown): value is ModuleInjectionWidgetEntry {\n return !!value\n && typeof value === 'object'\n && typeof (value as ModuleInjectionWidgetEntry).key === 'string'\n && typeof (value as ModuleInjectionWidgetEntry).loader === 'function'\n}\n\nfunction isDashboardWidgetEntry(value: unknown): value is ModuleDashboardWidgetEntry {\n return !!value\n && typeof value === 'object'\n && typeof (value as ModuleDashboardWidgetEntry).key === 'string'\n && typeof (value as ModuleDashboardWidgetEntry).loader === 'function'\n}\n\nfunction isComponentOverrideValue(value: unknown): value is ComponentOverride | ComponentOverride[] {\n const items = Array.isArray(value) ? value : [value]\n return items.every((item) =>\n !!item\n && typeof item === 'object'\n && !!(item as ComponentOverride).target\n && typeof (item as ComponentOverride).target.componentId === 'string',\n )\n}\n\nfunction isNotificationType(value: unknown): value is NotificationTypeDefinition {\n return !!value && typeof value === 'object' && typeof (value as NotificationTypeDefinition).type === 'string'\n}\n\nfunction isNotificationHandler(value: unknown): value is NotificationHandler {\n return isObjectWithId(value) && typeof (value as NotificationHandler).handle === 'function'\n}\n\nfunction isApiInterceptor(value: unknown): value is ApiInterceptor {\n return isObjectWithId(value) && typeof (value as ApiInterceptor).targetRoute === 'string'\n}\n\nfunction isCommandInterceptor(value: unknown): value is CommandInterceptor {\n return isObjectWithId(value) && typeof (value as CommandInterceptor).targetCommand === 'string'\n}\n\nfunction isResponseEnricher(value: unknown): value is ResponseEnricher {\n return isObjectWithId(value)\n && typeof (value as ResponseEnricher).targetEntity === 'string'\n && typeof (value as ResponseEnricher).enrichOne === 'function'\n}\n\nfunction isPageRouteMiddleware(value: unknown): value is PageRouteMiddleware {\n return isObjectWithId(value) && typeof (value as PageRouteMiddleware).run === 'function'\n}\n\nfunction isEncryptionMap(value: unknown): value is ModuleEncryptionMap {\n return !!value\n && typeof value === 'object'\n && typeof (value as ModuleEncryptionMap).entityId === 'string'\n && Array.isArray((value as ModuleEncryptionMap).fields)\n}\n\nfunction getFeatureId(value: unknown): string | null {\n if (typeof value === 'string' && value.trim()) return value.trim()\n if (value && typeof value === 'object' && typeof (value as { id?: unknown }).id === 'string') {\n return (value as { id: string }).id\n }\n return null\n}\n\nconst VALID_HTTP_METHODS: readonly HttpMethod[] = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']\n\nconst programmaticApiRouteOverrides: ApiRouteOverridesMap = {}\nconst modulesConfigApiRouteOverrides: ApiRouteOverridesMap = {}\n\n/**\n * Normalize an override key to the canonical `'METHOD /api/path'` form.\n * Returns `null` if the key is malformed (missing method, unknown method,\n * empty path). Trailing slashes on the path are stripped.\n */\nfunction normalizeApiRouteOverrideKey(key: string): string | null {\n if (typeof key !== 'string') return null\n const trimmed = key.trim()\n if (!trimmed) return null\n const parts = trimmed.split(/\\s+/)\n if (parts.length < 2) return null\n const method = parts[0].toUpperCase() as HttpMethod\n if (!VALID_HTTP_METHODS.includes(method)) return null\n const rawPath = parts.slice(1).join(' ').trim()\n if (!rawPath) return null\n const withLead = rawPath.startsWith('/') ? rawPath : `/${rawPath}`\n const normalized = withLead.replace(/\\/+$/, '') || '/'\n return `${method} ${normalized}`\n}\n\nfunction normalizePageRouteOverrideKey(key: string): string | null {\n if (typeof key !== 'string') return null\n const trimmed = key.trim()\n if (!trimmed) return null\n if (trimmed.startsWith('backend:') || trimmed.startsWith('frontend:')) return trimmed\n const withLead = trimmed.startsWith('/') ? trimmed : `/${trimmed}`\n const stripped = withLead.replace(/\\/+$/, '') || '/'\n if (stripped === '/frontend') return 'frontend:/'\n if (stripped.startsWith('/frontend/')) return `frontend:/${stripped.slice('/frontend/'.length)}`\n if (stripped === '/backend' || stripped.startsWith('/backend/')) return `backend:${stripped}`\n return `frontend:${stripped}`\n}\n\n/**\n * Programmatic API: apply API-route overrides. Supersedes any matching\n * `modules.ts` inline overrides for the same key. Call before\n * `registerApiRouteManifests` (i.e. before generated `apiRoutes` lands in\n * the registry); calls after registration do not retro-actively update\n * the stored manifest.\n *\n * @example\n * applyApiRouteOverrides({\n * 'GET /api/example/items': null, // disable\n * 'POST /api/example/items': { handler: customHandler }, // replace\n * })\n */\nexport function applyApiRouteOverrides(overrides: ApiRouteOverridesMap): void {\n if (!overrides) return\n for (const [rawKey, value] of Object.entries(overrides)) {\n const key = normalizeApiRouteOverrideKey(rawKey)\n if (!key) {\n console.warn(\n `[Module Overrides] Skipping malformed routes.api key \"${rawKey}\" \u2014 expected \"METHOD /api/path\".`,\n )\n continue\n }\n programmaticApiRouteOverrides[key] = value\n }\n}\n\n/** @__internal Test-only hook \u2014 clear programmatic + modules.ts route override state. */\nexport function resetApiRouteOverridesForTests(): void {\n for (const key of Object.keys(programmaticApiRouteOverrides)) {\n delete programmaticApiRouteOverrides[key]\n }\n for (const key of Object.keys(modulesConfigApiRouteOverrides)) {\n delete modulesConfigApiRouteOverrides[key]\n }\n}\n\n/** @__internal Test-only hook \u2014 clear every per-domain override store. */\nexport function resetModuleContractOverridesForTests(): void {\n resetApiRouteOverridesForTests()\n clearStore(pageRouteOverrideStore)\n clearStore(subscriberOverrideStore)\n clearStore(workerOverrideStore)\n clearStore(cliOverrideStore)\n clearStore(injectionWidgetOverrideStore)\n clearStore(dashboardWidgetOverrideStore)\n clearStore(componentOverrideStore)\n clearStore(notificationTypeOverrideStore)\n clearStore(notificationHandlerOverrideStore)\n clearStore(apiInterceptorOverrideStore)\n clearStore(commandInterceptorOverrideStore)\n clearStore(responseEnricherOverrideStore)\n clearStore(pageGuardOverrideStore)\n clearStore(aclFeatureOverrideStore)\n clearStore(encryptionMapOverrideStore)\n clearStore(diOverrideStore)\n for (const key of Object.keys(setupOverridesByModule)) delete setupOverridesByModule[key]\n}\n\n/**\n * Resolve the final API-route override map. Resolution order (lowest \u2192\n * highest precedence):\n *\n * 1. `modules.ts` inline (applied via the dispatcher).\n * 2. Programmatic (`applyApiRouteOverrides`).\n */\nexport function composeApiRouteOverrides(): ApiRouteOverridesMap {\n const modulesKeys = Object.keys(modulesConfigApiRouteOverrides)\n const programmaticKeys = Object.keys(programmaticApiRouteOverrides)\n if (modulesKeys.length === 0 && programmaticKeys.length === 0) {\n return EMPTY_OVERRIDE_MAP as ApiRouteOverridesMap\n }\n const out: ApiRouteOverridesMap = {}\n for (const k of modulesKeys) out[k] = modulesConfigApiRouteOverrides[k]\n for (const k of programmaticKeys) out[k] = programmaticApiRouteOverrides[k]\n return out\n}\n\nexport function applyPageRouteOverrides(overrides: PageRouteOverridesMap): void {\n applyStoreOverrides(pageRouteOverrideStore, 'programmatic', overrides as OverrideMap<PageRouteOverrideDefinition>, {\n label: 'routes.pages',\n normalizeKey: normalizePageRouteOverrideKey,\n })\n}\n\nexport function composePageRouteOverrides(): PageRouteOverridesMap {\n return composeStore(pageRouteOverrideStore) as PageRouteOverridesMap\n}\n\nexport function applySubscriberOverrides(overrides: SubscriberOverridesMap): void {\n applyStoreOverrides(subscriberOverrideStore, 'programmatic', overrides as OverrideMap<ModuleSubscriber>, { label: 'events.subscribers' })\n}\n\nexport function composeSubscriberOverrides(): SubscriberOverridesMap {\n return composeStore(subscriberOverrideStore) as SubscriberOverridesMap\n}\n\nexport function applyWorkerOverrides(overrides: WorkerOverridesMap): void {\n applyStoreOverrides(workerOverrideStore, 'programmatic', overrides as OverrideMap<ModuleWorker>, { label: 'workers' })\n}\n\nexport function composeWorkerOverrides(): WorkerOverridesMap {\n return composeStore(workerOverrideStore) as WorkerOverridesMap\n}\n\nexport function applyCliOverrides(overrides: CliOverridesMap): void {\n applyStoreOverrides(cliOverrideStore, 'programmatic', overrides as OverrideMap<ModuleCli>, { label: 'cli' })\n}\n\nexport function composeCliOverrides(): CliOverridesMap {\n return composeStore(cliOverrideStore) as CliOverridesMap\n}\n\nexport function applyInjectionWidgetOverrides(overrides: InjectionWidgetOverridesMap): void {\n applyStoreOverrides(injectionWidgetOverrideStore, 'programmatic', overrides as OverrideMap<ModuleInjectionWidgetEntry>, { label: 'widgets.injection' })\n}\n\nexport function composeInjectionWidgetOverrides(): InjectionWidgetOverridesMap {\n return composeStore(injectionWidgetOverrideStore) as InjectionWidgetOverridesMap\n}\n\nexport function applyDashboardWidgetOverrides(overrides: DashboardWidgetOverridesMap): void {\n applyStoreOverrides(dashboardWidgetOverrideStore, 'programmatic', overrides as OverrideMap<ModuleDashboardWidgetEntry>, { label: 'widgets.dashboard' })\n}\n\nexport function composeDashboardWidgetOverrides(): DashboardWidgetOverridesMap {\n return composeStore(dashboardWidgetOverrideStore) as DashboardWidgetOverridesMap\n}\n\nexport function applyComponentOverrides(overrides: ComponentOverridesMap): void {\n applyStoreOverrides(componentOverrideStore, 'programmatic', overrides as OverrideMap<ComponentOverride | ComponentOverride[]>, { label: 'widgets.components' })\n}\n\nexport function composeComponentOverrides(): ComponentOverridesMap {\n return composeStore(componentOverrideStore) as ComponentOverridesMap\n}\n\nexport function applyNotificationTypeOverrides(overrides: NotificationTypeOverridesMap): void {\n applyStoreOverrides(notificationTypeOverrideStore, 'programmatic', overrides as OverrideMap<NotificationTypeDefinition>, { label: 'notifications.types' })\n}\n\nexport function composeNotificationTypeOverrides(): NotificationTypeOverridesMap {\n return composeStore(notificationTypeOverrideStore) as NotificationTypeOverridesMap\n}\n\nexport function applyNotificationHandlerOverrides(overrides: NotificationHandlerOverridesMap): void {\n applyStoreOverrides(notificationHandlerOverrideStore, 'programmatic', overrides as OverrideMap<NotificationHandler>, { label: 'notifications.handlers' })\n}\n\nexport function composeNotificationHandlerOverrides(): NotificationHandlerOverridesMap {\n return composeStore(notificationHandlerOverrideStore) as NotificationHandlerOverridesMap\n}\n\nexport function applyApiInterceptorOverrides(overrides: ApiInterceptorOverridesMap): void {\n applyStoreOverrides(apiInterceptorOverrideStore, 'programmatic', overrides as OverrideMap<ApiInterceptor>, { label: 'interceptors' })\n}\n\nexport function composeApiInterceptorOverrides(): ApiInterceptorOverridesMap {\n return composeStore(apiInterceptorOverrideStore) as ApiInterceptorOverridesMap\n}\n\nexport function applyCommandInterceptorOverrides(overrides: CommandInterceptorOverridesMap): void {\n applyStoreOverrides(commandInterceptorOverrideStore, 'programmatic', overrides as OverrideMap<CommandInterceptor>, { label: 'commandInterceptors' })\n}\n\nexport function composeCommandInterceptorOverrides(): CommandInterceptorOverridesMap {\n return composeStore(commandInterceptorOverrideStore) as CommandInterceptorOverridesMap\n}\n\nexport function applyResponseEnricherOverrides(overrides: ResponseEnricherOverridesMap): void {\n applyStoreOverrides(responseEnricherOverrideStore, 'programmatic', overrides as OverrideMap<ResponseEnricher>, { label: 'enrichers' })\n}\n\nexport function composeResponseEnricherOverrides(): ResponseEnricherOverridesMap {\n return composeStore(responseEnricherOverrideStore) as ResponseEnricherOverridesMap\n}\n\nexport function applyPageGuardOverrides(overrides: PageGuardOverridesMap): void {\n applyStoreOverrides(pageGuardOverrideStore, 'programmatic', overrides as OverrideMap<PageRouteMiddleware>, { label: 'guards' })\n}\n\nexport function composePageGuardOverrides(): PageGuardOverridesMap {\n return composeStore(pageGuardOverrideStore) as PageGuardOverridesMap\n}\n\nexport function applyAclFeatureOverrides(overrides: AclFeatureOverridesMap): void {\n applyStoreOverrides(aclFeatureOverrideStore, 'programmatic', overrides as OverrideMap<Exclude<AclFeatureOverride, null>>, { label: 'acl.features' })\n}\n\nexport function composeAclFeatureOverrides(): AclFeatureOverridesMap {\n return composeStore(aclFeatureOverrideStore) as AclFeatureOverridesMap\n}\n\nexport function applyEncryptionMapOverrides(overrides: EncryptionMapOverridesMap): void {\n applyStoreOverrides(encryptionMapOverrideStore, 'programmatic', overrides as OverrideMap<ModuleEncryptionMap>, { label: 'encryption.maps' })\n}\n\nexport function composeEncryptionMapOverrides(): EncryptionMapOverridesMap {\n return composeStore(encryptionMapOverrideStore) as EncryptionMapOverridesMap\n}\n\nexport function applyDiOverrides(overrides: DiOverridesMap): void {\n applyStoreOverrides(diOverrideStore, 'programmatic', overrides as OverrideMap<Exclude<DiBindingOverride, null>>, { label: 'di' })\n}\n\nexport function composeDiOverrides(): DiOverridesMap {\n return composeStore(diOverrideStore) as DiOverridesMap\n}\n\n/**\n * Apply an API-route override map to a list of registered manifest entries.\n * Returns a NEW array \u2014 never mutates input.\n *\n * Behavior:\n * - For each entry, walk every method in `entry.methods`.\n * - If the override key `'METHOD path'` is `null`, drop that method from\n * the entry's `methods` array.\n * - If the override key is a definition, wrap `entry.load()` to return a\n * module that exposes the override handler at `module[METHOD]` (top-level)\n * and the override metadata at `module.metadata[METHOD]`.\n * - If every method on the entry is disabled, the entry is dropped.\n * - Overrides naming a key that does not match any entry log a single\n * warning so an operator notices a stale override.\n */\nexport function applyApiOverridesToManifests<T extends ApiRouteManifestEntry>(\n routes: readonly T[],\n overrides: Readonly<ApiRouteOverridesMap>,\n): T[] {\n if (!routes || routes.length === 0) return Array.from(routes)\n const overrideKeys = Object.keys(overrides)\n if (overrideKeys.length === 0) return Array.from(routes)\n\n const consumedKeys = new Set<string>()\n const result: T[] = []\n\n for (const entry of routes) {\n const path = entry.path\n const remainingMethods: HttpMethod[] = []\n const methodOverrides = new Map<HttpMethod, ApiRouteOverrideDefinition>()\n\n for (const method of entry.methods) {\n const manifestKey = `${method} ${path}`\n const publicKey = path.startsWith('/api/')\n ? manifestKey\n : `${method} /api${path === '/' ? '' : path}`\n const key = Object.prototype.hasOwnProperty.call(overrides, manifestKey)\n ? manifestKey\n : Object.prototype.hasOwnProperty.call(overrides, publicKey)\n ? publicKey\n : null\n if (!key) {\n remainingMethods.push(method)\n continue\n }\n consumedKeys.add(key)\n const value = overrides[key]\n if (value === null) continue\n if (value && typeof value === 'object' && typeof value.handler === 'function') {\n methodOverrides.set(method, value)\n remainingMethods.push(method)\n } else {\n console.warn(\n `[Module Overrides] Skipping malformed routes.api override for \"${key}\" \u2014 expected { handler, metadata? } or null.`,\n )\n remainingMethods.push(method)\n }\n }\n\n if (remainingMethods.length === 0) continue\n\n const methodsChanged = remainingMethods.length !== entry.methods.length\n const hasReplacements = methodOverrides.size > 0\n\n if (!methodsChanged && !hasReplacements) {\n result.push(entry)\n continue\n }\n\n const originalLoad = entry.load\n const wrappedLoad: T['load'] = hasReplacements\n ? async () => {\n const original = await originalLoad()\n const out: Record<string, unknown> = { ...original }\n const baseMetadata = original && typeof original === 'object' && original.metadata !== undefined\n ? original.metadata\n : null\n const mergedMetadata: Record<string, unknown> = baseMetadata && typeof baseMetadata === 'object'\n ? { ...(baseMetadata as Record<string, unknown>) }\n : {}\n for (const [method, def] of methodOverrides) {\n out[method] = def.handler\n if (def.metadata !== undefined) mergedMetadata[method] = def.metadata\n }\n if (Object.keys(mergedMetadata).length > 0) out.metadata = mergedMetadata\n return out\n }\n : originalLoad\n\n result.push({ ...entry, methods: remainingMethods, load: wrappedLoad })\n }\n\n for (const key of overrideKeys) {\n if (!consumedKeys.has(key)) {\n console.warn(\n `[Module Overrides] routes.api override \"${key}\" did not match any registered API route \u2014 override skipped.`,\n )\n }\n }\n\n return result\n}\n\nexport function applyPageOverridesToManifests<T extends BackendRouteManifestEntry | FrontendRouteManifestEntry>(\n routes: readonly T[],\n overrides: Readonly<PageRouteOverridesMap>,\n kind: 'backend' | 'frontend',\n): T[] {\n if (!routes || routes.length === 0) return Array.from(routes)\n const normalizedOverrides: PageRouteOverridesMap = {}\n for (const [rawKey, value] of Object.entries(overrides)) {\n const key = normalizePageRouteOverrideKey(rawKey)\n if (!key) {\n console.warn(\n `[Module Overrides] Skipping malformed routes.pages key \"${rawKey}\" \u2014 expected \"/backend/path\" or \"/frontend/path\".`,\n )\n continue\n }\n normalizedOverrides[key] = value\n }\n const overrideKeys = Object.keys(normalizedOverrides).filter((key) => key.startsWith(`${kind}:`))\n if (overrideKeys.length === 0) return Array.from(routes)\n\n const consumed = new Set<string>()\n const result: T[] = []\n\n for (const entry of routes) {\n const path = entry.pattern ?? entry.path ?? '/'\n const key = `${kind}:${path.replace(/\\/+$/, '') || '/'}`\n if (!Object.prototype.hasOwnProperty.call(normalizedOverrides, key)) {\n result.push(entry)\n continue\n }\n\n consumed.add(key)\n const override = normalizedOverrides[key]\n if (override === null) continue\n if (!override || typeof override !== 'object') {\n console.warn(\n `[Module Overrides] Skipping malformed routes.pages override for \"${key}\" \u2014 expected { load?, Component?, metadata? } or null.`,\n )\n result.push(entry)\n continue\n }\n\n const hasLoad = typeof override.load === 'function'\n const hasComponent = typeof override.Component === 'function'\n if (!hasLoad && !hasComponent && override.metadata === undefined) {\n console.warn(\n `[Module Overrides] Skipping malformed routes.pages override for \"${key}\" \u2014 expected a loader, component, metadata, or null.`,\n )\n result.push(entry)\n continue\n }\n\n const metadata = override.metadata && typeof override.metadata === 'object'\n ? override.metadata\n : {}\n const load = hasLoad\n ? override.load!\n : hasComponent\n ? async () => override.Component!\n : entry.load\n\n result.push({ ...entry, ...metadata, load })\n }\n\n warnStaleOverrides('routes.pages', Object.fromEntries(\n overrideKeys.map((key) => [key, normalizedOverrides[key]]),\n ), consumed)\n return result\n}\n\nfunction applyEntryListOverrides<TEntry extends { moduleId: string }, TValue>(\n entries: readonly TEntry[] | undefined,\n overrides: Readonly<OverrideMap<TValue>>,\n options: {\n listKey: keyof TEntry\n label: string\n getId: (value: TValue) => string | null\n isReplacement: (value: unknown) => value is TValue\n },\n): TEntry[] | undefined {\n // Fast path: when no overrides are registered, return the input directly.\n // All known callers iterate the result (.forEach/.flatMap/.map/storage),\n // never mutate it. This avoids a per-request Array.from copy on hot paths\n // like executePageMiddleware \u2192 applyPageGuardOverridesToEntries.\n if (!entries || Object.keys(overrides).length === 0) return entries as TEntry[] | undefined\n const consumed = new Set<string>()\n let changed = false\n const result = entries.map((entry) => {\n const list = entry[options.listKey]\n if (!Array.isArray(list)) return entry\n const applied = applyArrayOverrides(list as TValue[], overrides, {\n label: options.label,\n getId: options.getId,\n isReplacement: options.isReplacement,\n })\n for (const key of applied.consumed) consumed.add(key)\n if (!applied.changed) return entry\n changed = true\n return { ...entry, [options.listKey]: applied.items ?? [] }\n })\n warnStaleOverrides(options.label, overrides, consumed)\n return changed ? result : Array.from(entries)\n}\n\nexport function applyInjectionWidgetOverridesToEntries(\n entries: readonly ModuleInjectionWidgetEntry[],\n overrides: Readonly<InjectionWidgetOverridesMap> = composeInjectionWidgetOverrides(),\n): ModuleInjectionWidgetEntry[] {\n const applied = applyArrayOverrides(entries, overrides as OverrideMap<ModuleInjectionWidgetEntry>, {\n label: 'widgets.injection',\n getId: (entry) => entry.key,\n isReplacement: isInjectionWidgetEntry,\n })\n warnStaleOverrides('widgets.injection', overrides, applied.consumed)\n return applied.items ?? []\n}\n\nexport function applyDashboardWidgetOverridesToEntries(\n entries: readonly ModuleDashboardWidgetEntry[],\n overrides: Readonly<DashboardWidgetOverridesMap> = composeDashboardWidgetOverrides(),\n): ModuleDashboardWidgetEntry[] {\n const applied = applyArrayOverrides(entries, overrides as OverrideMap<ModuleDashboardWidgetEntry>, {\n label: 'widgets.dashboard',\n getId: (entry) => entry.key,\n isReplacement: isDashboardWidgetEntry,\n })\n warnStaleOverrides('widgets.dashboard', overrides, applied.consumed)\n return applied.items ?? []\n}\n\nexport function applyWorkerOverridesToDescriptors<T extends { id: string; queue: string; concurrency: number; handler: unknown }>(\n entries: readonly T[],\n overrides: Readonly<WorkerOverridesMap> = composeWorkerOverrides(),\n): T[] {\n const applied = applyArrayOverrides(entries, overrides as OverrideMap<T>, {\n label: 'workers',\n getId: (entry) => entry.id,\n isReplacement: (value): value is T =>\n !!value\n && typeof value === 'object'\n && typeof (value as { id?: unknown }).id === 'string'\n && typeof (value as { queue?: unknown }).queue === 'string'\n && typeof (value as { concurrency?: unknown }).concurrency === 'number'\n && typeof (value as { handler?: unknown }).handler === 'function',\n })\n warnStaleOverrides('workers', overrides, applied.consumed)\n return applied.items ?? []\n}\n\nexport function applyInjectionWidgetOverridesToTables(\n tables: readonly { moduleId: string; table: ModuleInjectionTable }[],\n overrides: Readonly<InjectionWidgetOverridesMap> = composeInjectionWidgetOverrides(),\n): Array<{ moduleId: string; table: ModuleInjectionTable }> {\n const disabled = new Set(Object.entries(overrides).filter(([, value]) => value === null).map(([key]) => key))\n if (disabled.size === 0) return Array.from(tables)\n\n const filterSlot = (slot: unknown): unknown => {\n if (typeof slot === 'string') return disabled.has(slot) ? null : slot\n if (slot && typeof slot === 'object') {\n const widgetId = (slot as { widgetId?: unknown }).widgetId\n return typeof widgetId === 'string' && disabled.has(widgetId) ? null : slot\n }\n return slot\n }\n\n return tables.map((entry) => {\n const nextTable: ModuleInjectionTable = {}\n for (const [spotId, rawSlot] of Object.entries(entry.table)) {\n const filtered = Array.isArray(rawSlot)\n ? rawSlot.map(filterSlot).filter((slot): slot is NonNullable<typeof slot> => slot !== null)\n : filterSlot(rawSlot)\n if (Array.isArray(filtered)) {\n if (filtered.length > 0) nextTable[spotId] = filtered as ModuleInjectionTable[string]\n } else if (filtered !== null) {\n nextTable[spotId] = filtered as ModuleInjectionTable[string]\n }\n }\n return { ...entry, table: nextTable }\n })\n}\n\nexport function applyComponentOverridesToEntries(\n entries: readonly { moduleId: string; componentOverrides: ComponentOverride[] }[],\n overrides: Readonly<ComponentOverridesMap> = composeComponentOverrides(),\n): Array<{ moduleId: string; componentOverrides: ComponentOverride[] }> {\n const overrideKeys = Object.keys(overrides)\n if (overrideKeys.length === 0) return Array.from(entries)\n const consumed = new Set<string>()\n const additions: ComponentOverride[] = []\n const result = entries.map((entry) => {\n const next = entry.componentOverrides.filter((override) => {\n const id = override.target.componentId\n if (!Object.prototype.hasOwnProperty.call(overrides, id)) return true\n consumed.add(id)\n return false\n })\n return { ...entry, componentOverrides: next }\n })\n for (const key of overrideKeys) {\n const value = overrides[key]\n if (value === null) continue\n if (!isComponentOverrideValue(value)) {\n console.warn(`[Module Overrides] Skipping malformed widgets.components override for \"${key}\" \u2014 expected ComponentOverride, ComponentOverride[], or null.`)\n continue\n }\n const values = Array.isArray(value) ? value : [value]\n for (const override of values) {\n if (override.target.componentId !== key) {\n console.warn(`[Module Overrides] Skipping malformed widgets.components override for \"${key}\" \u2014 target.componentId must match the override key.`)\n continue\n }\n additions.push(override)\n consumed.add(key)\n }\n }\n if (additions.length > 0) {\n result.push({ moduleId: 'overrides', componentOverrides: additions })\n }\n warnStaleOverrides('widgets.components', overrides, consumed)\n return result\n}\n\nexport function applyNotificationTypeOverridesToEntries(\n entries: readonly { moduleId: string; types: NotificationTypeDefinition[] }[],\n overrides: Readonly<NotificationTypeOverridesMap> = composeNotificationTypeOverrides(),\n): Array<{ moduleId: string; types: NotificationTypeDefinition[] }> {\n return applyEntryListOverrides(entries, overrides as OverrideMap<NotificationTypeDefinition>, {\n listKey: 'types',\n label: 'notifications.types',\n getId: (entry) => entry.type,\n isReplacement: isNotificationType,\n }) ?? []\n}\n\nexport function applyNotificationHandlerOverridesToEntries(\n entries: readonly { moduleId: string; handlers: NotificationHandler[] }[],\n overrides: Readonly<NotificationHandlerOverridesMap> = composeNotificationHandlerOverrides(),\n): Array<{ moduleId: string; handlers: NotificationHandler[] }> {\n return applyEntryListOverrides(entries, overrides as OverrideMap<NotificationHandler>, {\n listKey: 'handlers',\n label: 'notifications.handlers',\n getId: (entry) => entry.id,\n isReplacement: isNotificationHandler,\n }) ?? []\n}\n\nexport function applyApiInterceptorOverridesToEntries(\n entries: readonly { moduleId: string; interceptors: ApiInterceptor[] }[],\n overrides: Readonly<ApiInterceptorOverridesMap> = composeApiInterceptorOverrides(),\n): Array<{ moduleId: string; interceptors: ApiInterceptor[] }> {\n return applyEntryListOverrides(entries, overrides as OverrideMap<ApiInterceptor>, {\n listKey: 'interceptors',\n label: 'interceptors',\n getId: (entry) => entry.id,\n isReplacement: isApiInterceptor,\n }) ?? []\n}\n\nexport function applyCommandInterceptorOverridesToEntries(\n entries: readonly { moduleId: string; interceptors: CommandInterceptor[] }[],\n overrides: Readonly<CommandInterceptorOverridesMap> = composeCommandInterceptorOverrides(),\n): Array<{ moduleId: string; interceptors: CommandInterceptor[] }> {\n return applyEntryListOverrides(entries, overrides as OverrideMap<CommandInterceptor>, {\n listKey: 'interceptors',\n label: 'commandInterceptors',\n getId: (entry) => entry.id,\n isReplacement: isCommandInterceptor,\n }) ?? []\n}\n\nexport function applyResponseEnricherOverridesToEntries(\n entries: readonly { moduleId: string; enrichers: ResponseEnricher[] }[],\n overrides: Readonly<ResponseEnricherOverridesMap> = composeResponseEnricherOverrides(),\n): Array<{ moduleId: string; enrichers: ResponseEnricher[] }> {\n return applyEntryListOverrides(entries, overrides as OverrideMap<ResponseEnricher>, {\n listKey: 'enrichers',\n label: 'enrichers',\n getId: (entry) => entry.id,\n isReplacement: isResponseEnricher,\n }) ?? []\n}\n\nexport function applyPageGuardOverridesToEntries(\n entries: readonly PageMiddlewareRegistryEntry[],\n overrides: Readonly<PageGuardOverridesMap> = composePageGuardOverrides(),\n): PageMiddlewareRegistryEntry[] {\n return applyEntryListOverrides(entries, overrides as OverrideMap<PageRouteMiddleware>, {\n listKey: 'middleware',\n label: 'guards',\n getId: (entry) => entry.id,\n isReplacement: isPageRouteMiddleware,\n }) ?? []\n}\n\nfunction applySetupOverride(setup: ModuleSetupConfig | undefined, override: SetupOverridesShape | undefined): ModuleSetupConfig | undefined {\n if (!override) return setup\n const next: ModuleSetupConfig = { ...(setup ?? {}) }\n if (override.defaultRoleFeatures) {\n next.defaultRoleFeatures = Object.fromEntries(\n Object.entries(override.defaultRoleFeatures).map(([role, features]) => [role, Array.from(features)]),\n )\n }\n if (override.defaultCustomerRoleFeatures) {\n next.defaultCustomerRoleFeatures = Object.fromEntries(\n Object.entries(override.defaultCustomerRoleFeatures).map(([role, features]) => [role, Array.from(features)]),\n )\n }\n if (override.seedDefaults === false) delete next.seedDefaults\n if (override.seedExamples === false) delete next.seedExamples\n if (override.onTenantCreated === false) delete next.onTenantCreated\n return next\n}\n\nexport function applyModuleOverridesToModules(modules: readonly Module[]): Module[] {\n const subscriberOverrides = composeSubscriberOverrides()\n const workerOverrides = composeWorkerOverrides()\n const cliOverrides = composeCliOverrides()\n const aclOverrides = composeAclFeatureOverrides()\n const encryptionOverrides = composeEncryptionMapOverrides()\n const hasSetupOverrides = Object.keys(setupOverridesByModule).length > 0\n const hasAny =\n Object.keys(subscriberOverrides).length > 0\n || Object.keys(workerOverrides).length > 0\n || Object.keys(cliOverrides).length > 0\n || Object.keys(aclOverrides).length > 0\n || Object.keys(encryptionOverrides).length > 0\n || hasSetupOverrides\n\n if (!hasAny) return modules as Module[]\n\n const subscriberConsumed = new Set<string>()\n const workerConsumed = new Set<string>()\n const cliConsumed = new Set<string>()\n const aclConsumed = new Set<string>()\n const encryptionConsumed = new Set<string>()\n\n const result = modules.map((module) => {\n let next = module\n\n const subscribers = applyArrayOverrides(module.subscribers, subscriberOverrides as OverrideMap<ModuleSubscriber>, {\n label: 'events.subscribers',\n getId: (entry) => entry.id,\n isReplacement: isModuleSubscriber,\n })\n for (const key of subscribers.consumed) subscriberConsumed.add(key)\n if (subscribers.changed) next = { ...next, subscribers: subscribers.items }\n\n const workers = applyArrayOverrides(module.workers, workerOverrides as OverrideMap<ModuleWorker>, {\n label: 'workers',\n getId: (entry) => entry.id,\n isReplacement: isModuleWorker,\n })\n for (const key of workers.consumed) workerConsumed.add(key)\n if (workers.changed) next = { ...next, workers: workers.items }\n\n const cli = applyArrayOverrides(module.cli, cliOverrides as OverrideMap<ModuleCli>, {\n label: 'cli',\n getId: (entry) => entry.command,\n isReplacement: isModuleCli,\n })\n for (const key of cli.consumed) cliConsumed.add(key)\n if (cli.changed) next = { ...next, cli: cli.items }\n\n const features = applyArrayOverrides(module.features, aclOverrides as OverrideMap<NonNullable<Module['features']>[number]>, {\n label: 'acl.features',\n getId: getFeatureId,\n isReplacement: (value): value is NonNullable<Module['features']>[number] => getFeatureId(value) !== null,\n })\n for (const key of features.consumed) aclConsumed.add(key)\n if (features.changed) next = { ...next, features: features.items }\n\n const maps = applyArrayOverrides(module.defaultEncryptionMaps, encryptionOverrides as OverrideMap<ModuleEncryptionMap>, {\n label: 'encryption.maps',\n getId: (entry) => entry.entityId,\n isReplacement: isEncryptionMap,\n })\n for (const key of maps.consumed) encryptionConsumed.add(key)\n if (maps.changed) next = { ...next, defaultEncryptionMaps: maps.items }\n\n const setupOverride = setupOverridesByModule[module.id]\n if (setupOverride) {\n next = { ...next, setup: applySetupOverride(module.setup, setupOverride) }\n }\n\n return next\n })\n\n warnStaleOverrides('events.subscribers', subscriberOverrides, subscriberConsumed)\n warnStaleOverrides('workers', workerOverrides, workerConsumed)\n warnStaleOverrides('cli', cliOverrides, cliConsumed)\n warnStaleOverrides('acl.features', aclOverrides, aclConsumed)\n warnStaleOverrides('encryption.maps', encryptionOverrides, encryptionConsumed)\n\n return result\n}\n\nexport function applyDiOverridesToContainer(container: {\n register: (registrations: Record<string, unknown>) => unknown\n unregister?: (name: string) => unknown\n registrations?: Record<string, unknown>\n}): void {\n const overrides = composeDiOverrides()\n for (const [key, value] of Object.entries(overrides)) {\n if (value === null) {\n if (typeof container.unregister === 'function') {\n container.unregister(key)\n } else if (container.registrations && key in container.registrations) {\n delete container.registrations[key]\n }\n continue\n }\n if (value && typeof value === 'object' && typeof (value as DiBindingOverrideDefinition).register === 'function') {\n ;(value as DiBindingOverrideDefinition).register(container, key)\n continue\n }\n container.register({ [key]: value })\n }\n}\n\n/**\n * Dispatcher applier for the `'routes'` domain. Buckets every entry's\n * `overrides.routes.api` and `overrides.routes.pages` into the\n * `modules.ts` tier.\n */\nfunction routesOverridesApplier(\n entries: ReadonlyArray<ModuleOverrideEntry<RoutesOverridesShape>>,\n): void {\n for (const entry of entries) {\n const shape = entry.overrides\n if (!shape || typeof shape !== 'object') continue\n const api = shape.api\n if (api && typeof api === 'object') {\n for (const [rawKey, value] of Object.entries(api)) {\n const key = normalizeApiRouteOverrideKey(rawKey)\n if (!key) {\n console.warn(\n `[Module Overrides] Skipping malformed routes.api key \"${rawKey}\" \u2014 expected \"METHOD /api/path\".`,\n )\n continue\n }\n modulesConfigApiRouteOverrides[key] = value as ApiRouteOverride\n }\n }\n const pages = shape.pages\n if (pages && typeof pages === 'object' && Object.keys(pages).length > 0) {\n applyStoreOverrides(pageRouteOverrideStore, 'modules', pages as OverrideMap<PageRouteOverrideDefinition>, {\n label: 'routes.pages',\n normalizeKey: normalizePageRouteOverrideKey,\n })\n }\n }\n}\n\nfunction eventsOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<EventsOverridesShape>>): void {\n for (const entry of entries) {\n const subscribers = entry.overrides?.subscribers\n applyStoreOverrides(subscriberOverrideStore, 'modules', subscribers as OverrideMap<ModuleSubscriber>, { label: 'events.subscribers' })\n }\n}\n\nfunction workersOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<WorkerOverridesMap>>): void {\n for (const entry of entries) {\n applyStoreOverrides(workerOverrideStore, 'modules', entry.overrides as OverrideMap<ModuleWorker>, { label: 'workers' })\n }\n}\n\nfunction widgetsOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<WidgetsOverridesShape>>): void {\n for (const entry of entries) {\n applyStoreOverrides(injectionWidgetOverrideStore, 'modules', entry.overrides?.injection as OverrideMap<ModuleInjectionWidgetEntry>, { label: 'widgets.injection' })\n applyStoreOverrides(componentOverrideStore, 'modules', entry.overrides?.components as OverrideMap<ComponentOverride | ComponentOverride[]>, { label: 'widgets.components' })\n applyStoreOverrides(dashboardWidgetOverrideStore, 'modules', entry.overrides?.dashboard as OverrideMap<ModuleDashboardWidgetEntry>, { label: 'widgets.dashboard' })\n }\n}\n\nfunction notificationsOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<NotificationsOverridesShape>>): void {\n for (const entry of entries) {\n applyStoreOverrides(notificationTypeOverrideStore, 'modules', entry.overrides?.types as OverrideMap<NotificationTypeDefinition>, { label: 'notifications.types' })\n applyStoreOverrides(notificationHandlerOverrideStore, 'modules', entry.overrides?.handlers as OverrideMap<NotificationHandler>, { label: 'notifications.handlers' })\n }\n}\n\nfunction interceptorsOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<ApiInterceptorOverridesMap>>): void {\n for (const entry of entries) {\n applyStoreOverrides(apiInterceptorOverrideStore, 'modules', entry.overrides as OverrideMap<ApiInterceptor>, { label: 'interceptors' })\n }\n}\n\nfunction commandInterceptorsOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<CommandInterceptorOverridesMap>>): void {\n for (const entry of entries) {\n applyStoreOverrides(commandInterceptorOverrideStore, 'modules', entry.overrides as OverrideMap<CommandInterceptor>, { label: 'commandInterceptors' })\n }\n}\n\nfunction enrichersOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<ResponseEnricherOverridesMap>>): void {\n for (const entry of entries) {\n applyStoreOverrides(responseEnricherOverrideStore, 'modules', entry.overrides as OverrideMap<ResponseEnricher>, { label: 'enrichers' })\n }\n}\n\nfunction guardsOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<PageGuardOverridesMap>>): void {\n for (const entry of entries) {\n applyStoreOverrides(pageGuardOverrideStore, 'modules', entry.overrides as OverrideMap<PageRouteMiddleware>, { label: 'guards' })\n }\n}\n\nfunction cliOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<CliOverridesMap>>): void {\n for (const entry of entries) {\n applyStoreOverrides(cliOverrideStore, 'modules', entry.overrides as OverrideMap<ModuleCli>, { label: 'cli' })\n }\n}\n\nfunction setupOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<SetupOverridesShape>>): void {\n for (const entry of entries) {\n if (!entry.overrides || typeof entry.overrides !== 'object') continue\n setupOverridesByModule[entry.moduleId] = entry.overrides\n }\n}\n\nfunction aclOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<AclOverridesShape>>): void {\n for (const entry of entries) {\n applyStoreOverrides(aclFeatureOverrideStore, 'modules', entry.overrides?.features as OverrideMap<Exclude<AclFeatureOverride, null>>, { label: 'acl.features' })\n }\n}\n\nfunction diOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<DiOverridesMap>>): void {\n for (const entry of entries) {\n applyStoreOverrides(diOverrideStore, 'modules', entry.overrides as OverrideMap<Exclude<DiBindingOverride, null>>, { label: 'di' })\n }\n}\n\nfunction encryptionOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<EncryptionOverridesShape>>): void {\n for (const entry of entries) {\n applyStoreOverrides(encryptionMapOverrideStore, 'modules', entry.overrides?.maps as OverrideMap<ModuleEncryptionMap>, { label: 'encryption.maps' })\n }\n}\n\nfunction registerBuiltInModuleOverrideAppliers(): void {\n registerModuleOverrideApplier<RoutesOverridesShape>('routes', routesOverridesApplier)\n registerModuleOverrideApplier<EventsOverridesShape>('events', eventsOverridesApplier)\n registerModuleOverrideApplier<WorkerOverridesMap>('workers', workersOverridesApplier)\n registerModuleOverrideApplier<WidgetsOverridesShape>('widgets', widgetsOverridesApplier)\n registerModuleOverrideApplier<NotificationsOverridesShape>('notifications', notificationsOverridesApplier)\n registerModuleOverrideApplier<ApiInterceptorOverridesMap>('interceptors', interceptorsOverridesApplier)\n registerModuleOverrideApplier<CommandInterceptorOverridesMap>('commandInterceptors', commandInterceptorsOverridesApplier)\n registerModuleOverrideApplier<ResponseEnricherOverridesMap>('enrichers', enrichersOverridesApplier)\n registerModuleOverrideApplier<PageGuardOverridesMap>('guards', guardsOverridesApplier)\n registerModuleOverrideApplier<CliOverridesMap>('cli', cliOverridesApplier)\n registerModuleOverrideApplier<SetupOverridesShape>('setup', setupOverridesApplier)\n registerModuleOverrideApplier<AclOverridesShape>('acl', aclOverridesApplier)\n registerModuleOverrideApplier<DiOverridesMap>('di', diOverridesApplier)\n registerModuleOverrideApplier<EncryptionOverridesShape>('encryption', encryptionOverridesApplier)\n}\n\nregisterBuiltInModuleOverrideAppliers()\n"],
|
|
4
|
+
"sourcesContent": ["/**\n * Unified `modules.ts` override surface \u2014 one place for downstream apps to\n * replace or disable any contract a module presents.\n *\n * Spec: `.ai/specs/implemented/2026-05-04-modules-ts-unified-overrides.md`\n *\n * Each `ModuleEntry` in `apps/<app>/src/modules.ts` may carry an\n * `overrides` field whose sub-keys address one domain at a time:\n *\n * {\n * id: 'example',\n * from: '@app',\n * overrides: {\n * ai: { agents: {...}, tools: {...} }, // Phase 1 \u2014 wired\n * routes: { api: {...}, pages: {...} }, // Phase 2/3 \u2014 wired\n * events: { subscribers: {...} }, // Phase 4 \u2014 wired\n * workers: {...}, // Phase 5 \u2014 wired\n * ...\n * },\n * }\n *\n * The umbrella shape is the union of every per-domain sub-shape. Per-\n * domain runtime hooks own their composers and apply the override map\n * against their registry. During the rollout the dispatcher emitted a\n * one-shot warning for unwired domains; all domains in this umbrella are\n * now wired, so warnings only indicate a missing custom applier in tests\n * or future domain additions.\n *\n * Resolution order across all domains (highest precedence first):\n *\n * 1. Programmatic \u2014 direct calls into the per-domain `apply*Overrides()` API.\n * 2. `modules.ts` inline \u2014 `entry.overrides.<domain>` here.\n * 3. File-based \u2014 overrides exported from a contributing module's own files.\n * 4. Base \u2014 the module's own registrations.\n *\n * `null` always means \"disable\"; a definition replaces.\n */\n\n// ---------------------------------------------------------------------------\n// Domain sub-shapes\n// ---------------------------------------------------------------------------\n\n/**\n * AI domain \u2014 agents and tools. Re-exports the canonical maps from\n * `@open-mercato/ai-assistant` so consumers do not need to import that\n * package directly when they only want to declare overrides.\n *\n * Imported lazily as `unknown` here because `@open-mercato/shared` must\n * NOT take a runtime dependency on `@open-mercato/ai-assistant` (the\n * dependency direction is the other way around). Apps that author\n * `entry.overrides.ai` should import the strongly-typed\n * `AiAgentOverridesMap` / `AiToolOverridesMap` from `@open-mercato/ai-assistant`\n * directly \u2014 TypeScript structurally compatible types make the loose\n * shape here a no-op annotation cost.\n */\nexport interface AiOverridesShape {\n agents?: Record<string, unknown>\n tools?: Record<string, unknown>\n extensions?: unknown[]\n}\n\ntype LooseOverrideMap = Record<string, unknown>\n\n/**\n * Phase 2 (api) / Phase 3 (pages).\n *\n * The `api` sub-shape is keyed by `'METHOD /api/path'` and accepts either\n * a replacement definition (`{ handler, metadata? }`) or `null` to disable.\n * See {@link ApiRouteOverridesMap} for the strongly-typed alias and\n * {@link applyApiOverridesToManifests} for the runtime apply step.\n *\n * The `pages` sub-shape is keyed by `'/backend/path'` or\n * `'/frontend/path'`; `null` disables the page and a definition replaces\n * its loader and/or metadata.\n */\nexport interface RoutesOverridesShape {\n api?: ApiRouteOverridesMap | LooseOverrideMap\n pages?: PageRouteOverridesMap | LooseOverrideMap\n}\n\n/** Phase 4 \u2014 event subscribers. */\nexport interface EventsOverridesShape {\n subscribers?: SubscriberOverridesMap | LooseOverrideMap\n}\n\n/** Phase 6/7/8 \u2014 widget injection, component handles, dashboard widgets. */\nexport interface WidgetsOverridesShape {\n injection?: InjectionWidgetOverridesMap | LooseOverrideMap\n components?: ComponentOverridesMap | LooseOverrideMap\n dashboard?: DashboardWidgetOverridesMap | LooseOverrideMap\n}\n\n/** Phase 9 \u2014 notification types + handlers. */\nexport interface NotificationsOverridesShape {\n types?: NotificationTypeOverridesMap | LooseOverrideMap\n handlers?: NotificationHandlerOverridesMap | LooseOverrideMap\n}\n\n/** Phase 15 \u2014 setup lifecycle hooks. */\nexport interface SetupOverridesShape {\n defaultRoleFeatures?: Record<string, readonly string[]>\n defaultCustomerRoleFeatures?: Record<string, readonly string[]>\n seedDefaults?: false\n seedExamples?: false\n onTenantCreated?: false\n}\n\n/** Phase 16 \u2014 ACL features (per-feature override). */\nexport interface AclOverridesShape {\n features?: AclFeatureOverridesMap | LooseOverrideMap\n}\n\n/** Phase 18 \u2014 encryption maps per entity id. */\nexport interface EncryptionOverridesShape {\n maps?: EncryptionMapOverridesMap | LooseOverrideMap\n}\n\n/**\n * Umbrella shape for `entry.overrides`. Every key is optional; a\n * downstream app sets only the domains it cares about.\n */\nexport interface ModuleOverrides {\n ai?: AiOverridesShape\n routes?: RoutesOverridesShape\n events?: EventsOverridesShape\n workers?: WorkerOverridesMap | LooseOverrideMap\n widgets?: WidgetsOverridesShape\n notifications?: NotificationsOverridesShape\n interceptors?: ApiInterceptorOverridesMap | LooseOverrideMap\n commandInterceptors?: CommandInterceptorOverridesMap | LooseOverrideMap\n enrichers?: ResponseEnricherOverridesMap | LooseOverrideMap\n guards?: PageGuardOverridesMap | LooseOverrideMap\n cli?: CliOverridesMap | LooseOverrideMap\n setup?: SetupOverridesShape\n acl?: AclOverridesShape\n di?: DiOverridesMap | LooseOverrideMap\n encryption?: EncryptionOverridesShape\n}\n\n/**\n * Public shape consumed by the dispatcher. Mirrors the `ModuleEntry`\n * defined in each app's `modules.ts` \u2014 the dispatcher only needs `id`\n * and `overrides`.\n */\nexport interface ModuleEntryWithOverrides {\n id: string\n from?: string\n overrides?: ModuleOverrides\n}\n\n// ---------------------------------------------------------------------------\n// Per-domain runtime hook registry\n// ---------------------------------------------------------------------------\n\n/**\n * Each wired domain registers an applier that receives the list of\n * `(moduleId, overrides)` pairs in module-load order and forwards them\n * to its own runtime hook. Unwired domains do not register an applier\n * and instead trigger the dispatcher's one-shot warning.\n */\nexport type ModuleOverrideDomain =\n | 'ai'\n | 'routes'\n | 'events'\n | 'workers'\n | 'widgets'\n | 'notifications'\n | 'interceptors'\n | 'commandInterceptors'\n | 'enrichers'\n | 'guards'\n | 'cli'\n | 'setup'\n | 'acl'\n | 'di'\n | 'encryption'\n\nexport interface ModuleOverrideEntry<TShape> {\n moduleId: string\n overrides: TShape\n}\n\nexport type ModuleOverrideApplier<TShape> = (\n entries: ReadonlyArray<ModuleOverrideEntry<TShape>>,\n) => void\n\nconst appliers = new Map<ModuleOverrideDomain, ModuleOverrideApplier<unknown>>()\nconst warnedUnwiredDomains = new Set<ModuleOverrideDomain>()\n\n/**\n * Register a per-domain runtime hook. Called once at module-load time\n * by each wired domain (e.g. the AI subsystem registers `'ai'` from\n * `@open-mercato/ai-assistant`).\n */\nexport function registerModuleOverrideApplier<TShape>(\n domain: ModuleOverrideDomain,\n applier: ModuleOverrideApplier<TShape>,\n): void {\n appliers.set(domain, applier as ModuleOverrideApplier<unknown>)\n}\n\n/** @__internal Test-only hook \u2014 clear all registered appliers + warnings. */\nexport function resetModuleOverrideAppliersForTests(): void {\n appliers.clear()\n warnedUnwiredDomains.clear()\n registerBuiltInModuleOverrideAppliers()\n}\n\n// ---------------------------------------------------------------------------\n// Dispatcher\n// ---------------------------------------------------------------------------\n\nconst DOMAIN_KEYS: ModuleOverrideDomain[] = [\n 'ai',\n 'routes',\n 'events',\n 'workers',\n 'widgets',\n 'notifications',\n 'interceptors',\n 'commandInterceptors',\n 'enrichers',\n 'guards',\n 'cli',\n 'setup',\n 'acl',\n 'di',\n 'encryption',\n]\n\nconst TRACKING_ISSUE_HINT =\n 'See `.ai/specs/implemented/2026-05-04-modules-ts-unified-overrides.md` and tracking issue https://github.com/open-mercato/open-mercato/issues/1787.'\n\n/**\n * Walk every `ModuleEntry` and dispatch its `overrides.<domain>` shape\n * to the matching wired applier. Unwired domains emit a one-shot\n * structured warning.\n *\n * Call this exactly once from `apps/<app>/src/bootstrap.ts` BEFORE any\n * registry first-loads. Calling it more than once is safe but\n * accumulates per-domain entries each time.\n */\nexport function applyModuleOverridesFromEnabledModules(\n modules: ReadonlyArray<ModuleEntryWithOverrides>,\n): void {\n if (!Array.isArray(modules) || modules.length === 0) return\n\n // Bucket entries by domain in module-load order.\n const buckets = new Map<ModuleOverrideDomain, Array<ModuleOverrideEntry<unknown>>>()\n\n for (const entry of modules) {\n if (!entry || typeof entry.id !== 'string' || !entry.id) continue\n const overrides = entry.overrides\n if (!overrides || typeof overrides !== 'object') continue\n\n for (const domain of DOMAIN_KEYS) {\n const value = (overrides as Record<string, unknown>)[domain]\n if (value === undefined || value === null) continue\n if (typeof value !== 'object') continue\n const list = buckets.get(domain) ?? []\n list.push({ moduleId: entry.id, overrides: value })\n buckets.set(domain, list)\n }\n }\n\n // Dispatch each domain to its wired applier; warn on unwired domains.\n for (const [domain, entries] of buckets) {\n const applier = appliers.get(domain)\n if (!applier) {\n if (!warnedUnwiredDomains.has(domain)) {\n warnedUnwiredDomains.add(domain)\n const moduleIds = Array.from(new Set(entries.map((e) => e.moduleId))).join(', ')\n console.warn(\n `[Module Overrides] Domain \"${domain}\" not yet wired \u2014 entry.overrides.${domain} for module(s) [${moduleIds}] was ignored. ${TRACKING_ISSUE_HINT}`,\n )\n }\n continue\n }\n applier(entries)\n }\n}\n\n// ---------------------------------------------------------------------------\n// Phase 2/3 \u2014 Routes (API + pages) applier\n// ---------------------------------------------------------------------------\n//\n// Wires `entry.overrides.routes.api` so a downstream module can disable or\n// replace any API route declared by another module. The override map is\n// keyed by `'METHOD /api/path'` (case-insensitive method, leading slash on\n// the path), and each value is either:\n//\n// - `null` \u2014 disable the matching method on the route.\n// - `{ handler, metadata? }` \u2014 replace the matching method's handler\n// (and optionally merge override metadata into the route's metadata).\n//\n// Resolution order (lowest precedence first):\n//\n// 1. `modules.ts` inline \u2014 every `entry.overrides.routes.api` on a\n// `ModuleEntry` (last entry per key wins).\n// 2. Programmatic \u2014 `applyApiRouteOverrides({...})` from anywhere at\n// boot or test scaffolds (last call per key wins).\n//\n// A file-based tier (per-module `apiRouteOverrides` exports) is **not**\n// part of Phase 2 \u2014 modules that ship a route override do so via\n// `modules.ts` or programmatically. The umbrella spec keeps the file-based\n// tier \"where applicable\".\n//\n// The applied overrides are consumed by `registerApiRouteManifests` (see\n// `./registry.ts`) which rewrites the manifest array once before storing\n// it. Calls to `applyApiRouteOverrides` made AFTER manifests are already\n// registered do not retro-actively update the stored manifest \u2014 for\n// runtime override scenarios call the dispatcher / programmatic API\n// before `bootstrap.ts` triggers manifest registration.\n//\n// Spec: `.ai/specs/implemented/2026-05-04-modules-ts-unified-overrides.md` (Phases 2/3).\n\n// ApiRouteManifestEntry / ApiHandler / HttpMethod live in ./registry. The\n// type-only import is erased at runtime, so there is no cycle even though\n// ./registry imports the runtime helpers below.\nimport type {\n ApiHandler,\n ApiRouteManifestEntry,\n BackendRouteManifestEntry,\n FrontendRouteManifestEntry,\n HttpMethod,\n Module,\n ModuleCli,\n ModuleDashboardWidgetEntry,\n ModuleInjectionWidgetEntry,\n ModuleSubscriber,\n ModuleWorker,\n} from './registry'\nimport type { ModuleInjectionTable } from './widgets/injection'\nimport type { ComponentOverride } from './widgets/component-registry'\nimport type { NotificationHandler } from './notifications/handler'\nimport type { NotificationTypeDefinition } from './notifications/types'\nimport type { ModuleEncryptionMap } from './encryption'\nimport type { ModuleSetupConfig } from './setup'\nimport type { ApiInterceptor } from '../lib/crud/api-interceptor'\nimport type { ResponseEnricher } from '../lib/crud/response-enricher'\nimport type { CommandInterceptor } from '../lib/commands/command-interceptor'\nimport type { PageMiddlewareRegistryEntry, PageRouteMiddleware } from './middleware/page'\n\n/** Override for a single API route entry: replace handler/metadata, or `null` to disable. */\nexport interface ApiRouteOverrideDefinition {\n /** Replacement handler. Same signature as the original API route handler. */\n handler: ApiHandler\n /** Optional metadata override \u2014 replaces the per-method metadata on the route. */\n metadata?: unknown\n}\n\n/** Override value per `'METHOD /api/path'` key \u2014 definition or `null` to disable. */\nexport type ApiRouteOverride = ApiRouteOverrideDefinition | null\n\n/** Map of `'METHOD /api/path'` \u2192 override. */\nexport type ApiRouteOverridesMap = Record<string, ApiRouteOverride>\n\ntype OverrideStore<T> = {\n modules: Record<string, T | null>\n programmatic: Record<string, T | null>\n}\n\ntype OverrideValue<T> = T | null\ntype OverrideMap<T> = Record<string, OverrideValue<T>>\n\n/** Override for a backend/frontend page route: replace loader/metadata, or `null` to disable. */\nexport interface PageRouteOverrideDefinition {\n load?: BackendRouteManifestEntry['load']\n Component?: Awaited<ReturnType<BackendRouteManifestEntry['load']>>\n metadata?: Partial<Omit<BackendRouteManifestEntry | FrontendRouteManifestEntry, 'moduleId' | 'load'>>\n}\n\nexport type PageRouteOverride = PageRouteOverrideDefinition | null\nexport type PageRouteOverridesMap = Record<string, PageRouteOverride>\n\nexport type SubscriberOverride = ModuleSubscriber | null\nexport type SubscriberOverridesMap = Record<string, SubscriberOverride>\n\nexport type WorkerOverride = ModuleWorker | null\nexport type WorkerOverridesMap = Record<string, WorkerOverride>\n\nexport type CliOverride = ModuleCli | null\nexport type CliOverridesMap = Record<string, CliOverride>\n\nexport type InjectionWidgetOverride = ModuleInjectionWidgetEntry | null\nexport type InjectionWidgetOverridesMap = Record<string, InjectionWidgetOverride>\n\nexport type DashboardWidgetOverride = ModuleDashboardWidgetEntry | null\nexport type DashboardWidgetOverridesMap = Record<string, DashboardWidgetOverride>\n\nexport type ComponentOverrideValue = ComponentOverride | ComponentOverride[] | null\nexport type ComponentOverridesMap = Record<string, ComponentOverrideValue>\n\nexport type NotificationTypeOverride = NotificationTypeDefinition | null\nexport type NotificationTypeOverridesMap = Record<string, NotificationTypeOverride>\n\nexport type NotificationHandlerOverride = NotificationHandler | null\nexport type NotificationHandlerOverridesMap = Record<string, NotificationHandlerOverride>\n\nexport type ApiInterceptorOverride = ApiInterceptor | null\nexport type ApiInterceptorOverridesMap = Record<string, ApiInterceptorOverride>\n\nexport type CommandInterceptorOverride = CommandInterceptor | null\nexport type CommandInterceptorOverridesMap = Record<string, CommandInterceptorOverride>\n\nexport type ResponseEnricherOverride = ResponseEnricher | null\nexport type ResponseEnricherOverridesMap = Record<string, ResponseEnricherOverride>\n\nexport type PageGuardOverride = PageRouteMiddleware | null\nexport type PageGuardOverridesMap = Record<string, PageGuardOverride>\n\nexport type AclFeatureOverride = string | { id: string; [key: string]: unknown } | null\nexport type AclFeatureOverridesMap = Record<string, AclFeatureOverride>\n\nexport type EncryptionMapOverride = ModuleEncryptionMap | null\nexport type EncryptionMapOverridesMap = Record<string, EncryptionMapOverride>\n\nexport type DiBindingOverrideDefinition = {\n register: (container: { register: (registrations: Record<string, unknown>) => unknown }, key: string) => void\n}\nexport type DiBindingOverride = DiBindingOverrideDefinition | unknown | null\nexport type DiOverridesMap = Record<string, DiBindingOverride>\n\nconst pageRouteOverrideStore: OverrideStore<PageRouteOverrideDefinition> = { modules: {}, programmatic: {} }\nconst subscriberOverrideStore: OverrideStore<ModuleSubscriber> = { modules: {}, programmatic: {} }\nconst workerOverrideStore: OverrideStore<ModuleWorker> = { modules: {}, programmatic: {} }\nconst cliOverrideStore: OverrideStore<ModuleCli> = { modules: {}, programmatic: {} }\nconst injectionWidgetOverrideStore: OverrideStore<ModuleInjectionWidgetEntry> = { modules: {}, programmatic: {} }\nconst dashboardWidgetOverrideStore: OverrideStore<ModuleDashboardWidgetEntry> = { modules: {}, programmatic: {} }\nconst componentOverrideStore: OverrideStore<ComponentOverride | ComponentOverride[]> = { modules: {}, programmatic: {} }\nconst notificationTypeOverrideStore: OverrideStore<NotificationTypeDefinition> = { modules: {}, programmatic: {} }\nconst notificationHandlerOverrideStore: OverrideStore<NotificationHandler> = { modules: {}, programmatic: {} }\nconst apiInterceptorOverrideStore: OverrideStore<ApiInterceptor> = { modules: {}, programmatic: {} }\nconst commandInterceptorOverrideStore: OverrideStore<CommandInterceptor> = { modules: {}, programmatic: {} }\nconst responseEnricherOverrideStore: OverrideStore<ResponseEnricher> = { modules: {}, programmatic: {} }\nconst pageGuardOverrideStore: OverrideStore<PageRouteMiddleware> = { modules: {}, programmatic: {} }\nconst aclFeatureOverrideStore: OverrideStore<Exclude<AclFeatureOverride, null>> = { modules: {}, programmatic: {} }\nconst encryptionMapOverrideStore: OverrideStore<ModuleEncryptionMap> = { modules: {}, programmatic: {} }\nconst diOverrideStore: OverrideStore<Exclude<DiBindingOverride, null>> = { modules: {}, programmatic: {} }\nconst setupOverridesByModule: Record<string, SetupOverridesShape> = {}\n\nfunction normalizeIdOverrideKey(key: string, label: string): string | null {\n if (typeof key !== 'string') return null\n const trimmed = key.trim()\n if (trimmed) return trimmed\n console.warn(`[Module Overrides] Skipping malformed ${label} key \"${key}\" \u2014 expected a non-empty string.`)\n return null\n}\n\nfunction clearStore<T>(store: OverrideStore<T>): void {\n for (const key of Object.keys(store.modules)) delete store.modules[key]\n for (const key of Object.keys(store.programmatic)) delete store.programmatic[key]\n}\n\n// Shared frozen empty override map. Returned from compose* helpers when both\n// the modules-tier and programmatic-tier stores are empty so per-request hot\n// paths (page guards, etc.) reuse one object instead of allocating fresh `{}`\n// per call. Consumers MUST NOT mutate \u2014 internal applier loops iterate keys\n// only, and the public API hands out shallow copies through composeApiRouteOverrides.\nconst EMPTY_OVERRIDE_MAP: Readonly<Record<string, never>> = Object.freeze({})\n\nfunction applyStoreOverrides<T>(\n store: OverrideStore<T>,\n target: 'modules' | 'programmatic',\n overrides: OverrideMap<T> | undefined,\n options: {\n label: string\n normalizeKey?: (key: string) => string | null\n },\n): void {\n if (!overrides || typeof overrides !== 'object') return\n const normalize = options.normalizeKey ?? ((key: string) => normalizeIdOverrideKey(key, options.label))\n for (const [rawKey, value] of Object.entries(overrides)) {\n const key = normalize(rawKey)\n if (!key) continue\n store[target][key] = value\n }\n}\n\nfunction composeStore<T>(store: OverrideStore<T>): OverrideMap<T> {\n const modulesKeys = Object.keys(store.modules)\n const programmaticKeys = Object.keys(store.programmatic)\n if (modulesKeys.length === 0 && programmaticKeys.length === 0) {\n return EMPTY_OVERRIDE_MAP as OverrideMap<T>\n }\n return { ...store.modules, ...store.programmatic }\n}\n\nfunction applyArrayOverrides<T>(\n items: readonly T[] | undefined,\n overrides: Readonly<OverrideMap<T>>,\n options: {\n label: string\n getId: (value: T) => string | null\n isReplacement?: (value: unknown) => value is T\n },\n): { items: T[] | undefined; consumed: Set<string>; changed: boolean } {\n if (!items || Object.keys(overrides).length === 0) {\n return { items: items ? Array.from(items) : items, consumed: new Set(), changed: false }\n }\n\n const consumed = new Set<string>()\n const result: T[] = []\n let changed = false\n\n for (const item of items) {\n const id = options.getId(item)\n if (!id || !Object.prototype.hasOwnProperty.call(overrides, id)) {\n result.push(item)\n continue\n }\n consumed.add(id)\n changed = true\n const replacement = overrides[id]\n if (replacement === null) continue\n if (options.isReplacement && !options.isReplacement(replacement)) {\n console.warn(\n `[Module Overrides] Skipping malformed ${options.label} override for \"${id}\" \u2014 replacement has the wrong shape.`,\n )\n result.push(item)\n continue\n }\n const replacementId = options.getId(replacement)\n if (replacementId !== id) {\n console.warn(\n `[Module Overrides] Skipping malformed ${options.label} override for \"${id}\" \u2014 replacement id must match the override key.`,\n )\n result.push(item)\n continue\n }\n result.push(replacement)\n }\n\n return { items: result, consumed, changed }\n}\n\nfunction warnStaleOverrides(label: string, overrides: Readonly<Record<string, unknown>>, consumed: ReadonlySet<string>): void {\n for (const key of Object.keys(overrides)) {\n if (!consumed.has(key)) {\n console.warn(`[Module Overrides] ${label} override \"${key}\" did not match any registered entry \u2014 override skipped.`)\n }\n }\n}\n\nfunction isObjectWithId(value: unknown): value is { id: string } {\n return !!value && typeof value === 'object' && typeof (value as { id?: unknown }).id === 'string'\n}\n\nfunction isModuleSubscriber(value: unknown): value is ModuleSubscriber {\n return isObjectWithId(value)\n && typeof (value as ModuleSubscriber).event === 'string'\n && typeof (value as ModuleSubscriber).handler === 'function'\n}\n\nfunction isModuleWorker(value: unknown): value is ModuleWorker {\n return isObjectWithId(value)\n && typeof (value as ModuleWorker).queue === 'string'\n && typeof (value as ModuleWorker).handler === 'function'\n}\n\nfunction isModuleCli(value: unknown): value is ModuleCli {\n return !!value\n && typeof value === 'object'\n && typeof (value as ModuleCli).command === 'string'\n && typeof (value as ModuleCli).run === 'function'\n}\n\nfunction isInjectionWidgetEntry(value: unknown): value is ModuleInjectionWidgetEntry {\n return !!value\n && typeof value === 'object'\n && typeof (value as ModuleInjectionWidgetEntry).key === 'string'\n && typeof (value as ModuleInjectionWidgetEntry).loader === 'function'\n}\n\nfunction isDashboardWidgetEntry(value: unknown): value is ModuleDashboardWidgetEntry {\n return !!value\n && typeof value === 'object'\n && typeof (value as ModuleDashboardWidgetEntry).key === 'string'\n && typeof (value as ModuleDashboardWidgetEntry).loader === 'function'\n}\n\nfunction isComponentOverrideValue(value: unknown): value is ComponentOverride | ComponentOverride[] {\n const items = Array.isArray(value) ? value : [value]\n return items.every((item) =>\n !!item\n && typeof item === 'object'\n && !!(item as ComponentOverride).target\n && typeof (item as ComponentOverride).target.componentId === 'string',\n )\n}\n\nfunction isNotificationType(value: unknown): value is NotificationTypeDefinition {\n return !!value && typeof value === 'object' && typeof (value as NotificationTypeDefinition).type === 'string'\n}\n\nfunction isNotificationHandler(value: unknown): value is NotificationHandler {\n return isObjectWithId(value) && typeof (value as NotificationHandler).handle === 'function'\n}\n\nfunction isApiInterceptor(value: unknown): value is ApiInterceptor {\n return isObjectWithId(value) && typeof (value as ApiInterceptor).targetRoute === 'string'\n}\n\nfunction isCommandInterceptor(value: unknown): value is CommandInterceptor {\n return isObjectWithId(value) && typeof (value as CommandInterceptor).targetCommand === 'string'\n}\n\nfunction isResponseEnricher(value: unknown): value is ResponseEnricher {\n return isObjectWithId(value)\n && typeof (value as ResponseEnricher).targetEntity === 'string'\n && typeof (value as ResponseEnricher).enrichOne === 'function'\n}\n\nfunction isPageRouteMiddleware(value: unknown): value is PageRouteMiddleware {\n return isObjectWithId(value) && typeof (value as PageRouteMiddleware).run === 'function'\n}\n\nfunction isEncryptionMap(value: unknown): value is ModuleEncryptionMap {\n return !!value\n && typeof value === 'object'\n && typeof (value as ModuleEncryptionMap).entityId === 'string'\n && Array.isArray((value as ModuleEncryptionMap).fields)\n}\n\nfunction getFeatureId(value: unknown): string | null {\n if (typeof value === 'string' && value.trim()) return value.trim()\n if (value && typeof value === 'object' && typeof (value as { id?: unknown }).id === 'string') {\n return (value as { id: string }).id\n }\n return null\n}\n\nconst VALID_HTTP_METHODS: readonly HttpMethod[] = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']\n\nconst programmaticApiRouteOverrides: ApiRouteOverridesMap = {}\nconst modulesConfigApiRouteOverrides: ApiRouteOverridesMap = {}\n\n/**\n * Normalize an override key to the canonical `'METHOD /api/path'` form.\n * Returns `null` if the key is malformed (missing method, unknown method,\n * empty path). Trailing slashes on the path are stripped.\n */\nfunction normalizeApiRouteOverrideKey(key: string): string | null {\n if (typeof key !== 'string') return null\n const trimmed = key.trim()\n if (!trimmed) return null\n const parts = trimmed.split(/\\s+/)\n if (parts.length < 2) return null\n const method = parts[0].toUpperCase() as HttpMethod\n if (!VALID_HTTP_METHODS.includes(method)) return null\n const rawPath = parts.slice(1).join(' ').trim()\n if (!rawPath) return null\n const withLead = rawPath.startsWith('/') ? rawPath : `/${rawPath}`\n const normalized = withLead.replace(/\\/+$/, '') || '/'\n return `${method} ${normalized}`\n}\n\nfunction normalizePageRouteOverrideKey(key: string): string | null {\n if (typeof key !== 'string') return null\n const trimmed = key.trim()\n if (!trimmed) return null\n if (trimmed.startsWith('backend:') || trimmed.startsWith('frontend:')) return trimmed\n const withLead = trimmed.startsWith('/') ? trimmed : `/${trimmed}`\n const stripped = withLead.replace(/\\/+$/, '') || '/'\n if (stripped === '/frontend') return 'frontend:/'\n if (stripped.startsWith('/frontend/')) return `frontend:/${stripped.slice('/frontend/'.length)}`\n if (stripped === '/backend' || stripped.startsWith('/backend/')) return `backend:${stripped}`\n return `frontend:${stripped}`\n}\n\n/**\n * Programmatic API: apply API-route overrides. Supersedes any matching\n * `modules.ts` inline overrides for the same key. Call before\n * `registerApiRouteManifests` (i.e. before generated `apiRoutes` lands in\n * the registry); calls after registration do not retro-actively update\n * the stored manifest.\n *\n * @example\n * applyApiRouteOverrides({\n * 'GET /api/example/items': null, // disable\n * 'POST /api/example/items': { handler: customHandler }, // replace\n * })\n */\nexport function applyApiRouteOverrides(overrides: ApiRouteOverridesMap): void {\n if (!overrides) return\n for (const [rawKey, value] of Object.entries(overrides)) {\n const key = normalizeApiRouteOverrideKey(rawKey)\n if (!key) {\n console.warn(\n `[Module Overrides] Skipping malformed routes.api key \"${rawKey}\" \u2014 expected \"METHOD /api/path\".`,\n )\n continue\n }\n programmaticApiRouteOverrides[key] = value\n }\n}\n\n/** @__internal Test-only hook \u2014 clear programmatic + modules.ts route override state. */\nexport function resetApiRouteOverridesForTests(): void {\n for (const key of Object.keys(programmaticApiRouteOverrides)) {\n delete programmaticApiRouteOverrides[key]\n }\n for (const key of Object.keys(modulesConfigApiRouteOverrides)) {\n delete modulesConfigApiRouteOverrides[key]\n }\n}\n\n/** @__internal Test-only hook \u2014 clear every per-domain override store. */\nexport function resetModuleContractOverridesForTests(): void {\n resetApiRouteOverridesForTests()\n clearStore(pageRouteOverrideStore)\n clearStore(subscriberOverrideStore)\n clearStore(workerOverrideStore)\n clearStore(cliOverrideStore)\n clearStore(injectionWidgetOverrideStore)\n clearStore(dashboardWidgetOverrideStore)\n clearStore(componentOverrideStore)\n clearStore(notificationTypeOverrideStore)\n clearStore(notificationHandlerOverrideStore)\n clearStore(apiInterceptorOverrideStore)\n clearStore(commandInterceptorOverrideStore)\n clearStore(responseEnricherOverrideStore)\n clearStore(pageGuardOverrideStore)\n clearStore(aclFeatureOverrideStore)\n clearStore(encryptionMapOverrideStore)\n clearStore(diOverrideStore)\n for (const key of Object.keys(setupOverridesByModule)) delete setupOverridesByModule[key]\n}\n\n/**\n * Resolve the final API-route override map. Resolution order (lowest \u2192\n * highest precedence):\n *\n * 1. `modules.ts` inline (applied via the dispatcher).\n * 2. Programmatic (`applyApiRouteOverrides`).\n */\nexport function composeApiRouteOverrides(): ApiRouteOverridesMap {\n const modulesKeys = Object.keys(modulesConfigApiRouteOverrides)\n const programmaticKeys = Object.keys(programmaticApiRouteOverrides)\n if (modulesKeys.length === 0 && programmaticKeys.length === 0) {\n return EMPTY_OVERRIDE_MAP as ApiRouteOverridesMap\n }\n const out: ApiRouteOverridesMap = {}\n for (const k of modulesKeys) out[k] = modulesConfigApiRouteOverrides[k]\n for (const k of programmaticKeys) out[k] = programmaticApiRouteOverrides[k]\n return out\n}\n\nexport function applyPageRouteOverrides(overrides: PageRouteOverridesMap): void {\n applyStoreOverrides(pageRouteOverrideStore, 'programmatic', overrides as OverrideMap<PageRouteOverrideDefinition>, {\n label: 'routes.pages',\n normalizeKey: normalizePageRouteOverrideKey,\n })\n}\n\nexport function composePageRouteOverrides(): PageRouteOverridesMap {\n return composeStore(pageRouteOverrideStore) as PageRouteOverridesMap\n}\n\nexport function applySubscriberOverrides(overrides: SubscriberOverridesMap): void {\n applyStoreOverrides(subscriberOverrideStore, 'programmatic', overrides as OverrideMap<ModuleSubscriber>, { label: 'events.subscribers' })\n}\n\nexport function composeSubscriberOverrides(): SubscriberOverridesMap {\n return composeStore(subscriberOverrideStore) as SubscriberOverridesMap\n}\n\nexport function applyWorkerOverrides(overrides: WorkerOverridesMap): void {\n applyStoreOverrides(workerOverrideStore, 'programmatic', overrides as OverrideMap<ModuleWorker>, { label: 'workers' })\n}\n\nexport function composeWorkerOverrides(): WorkerOverridesMap {\n return composeStore(workerOverrideStore) as WorkerOverridesMap\n}\n\nexport function applyCliOverrides(overrides: CliOverridesMap): void {\n applyStoreOverrides(cliOverrideStore, 'programmatic', overrides as OverrideMap<ModuleCli>, { label: 'cli' })\n}\n\nexport function composeCliOverrides(): CliOverridesMap {\n return composeStore(cliOverrideStore) as CliOverridesMap\n}\n\nexport function applyInjectionWidgetOverrides(overrides: InjectionWidgetOverridesMap): void {\n applyStoreOverrides(injectionWidgetOverrideStore, 'programmatic', overrides as OverrideMap<ModuleInjectionWidgetEntry>, { label: 'widgets.injection' })\n}\n\nexport function composeInjectionWidgetOverrides(): InjectionWidgetOverridesMap {\n return composeStore(injectionWidgetOverrideStore) as InjectionWidgetOverridesMap\n}\n\nexport function applyDashboardWidgetOverrides(overrides: DashboardWidgetOverridesMap): void {\n applyStoreOverrides(dashboardWidgetOverrideStore, 'programmatic', overrides as OverrideMap<ModuleDashboardWidgetEntry>, { label: 'widgets.dashboard' })\n}\n\nexport function composeDashboardWidgetOverrides(): DashboardWidgetOverridesMap {\n return composeStore(dashboardWidgetOverrideStore) as DashboardWidgetOverridesMap\n}\n\nexport function applyComponentOverrides(overrides: ComponentOverridesMap): void {\n applyStoreOverrides(componentOverrideStore, 'programmatic', overrides as OverrideMap<ComponentOverride | ComponentOverride[]>, { label: 'widgets.components' })\n}\n\nexport function composeComponentOverrides(): ComponentOverridesMap {\n return composeStore(componentOverrideStore) as ComponentOverridesMap\n}\n\nexport function applyNotificationTypeOverrides(overrides: NotificationTypeOverridesMap): void {\n applyStoreOverrides(notificationTypeOverrideStore, 'programmatic', overrides as OverrideMap<NotificationTypeDefinition>, { label: 'notifications.types' })\n}\n\nexport function composeNotificationTypeOverrides(): NotificationTypeOverridesMap {\n return composeStore(notificationTypeOverrideStore) as NotificationTypeOverridesMap\n}\n\nexport function applyNotificationHandlerOverrides(overrides: NotificationHandlerOverridesMap): void {\n applyStoreOverrides(notificationHandlerOverrideStore, 'programmatic', overrides as OverrideMap<NotificationHandler>, { label: 'notifications.handlers' })\n}\n\nexport function composeNotificationHandlerOverrides(): NotificationHandlerOverridesMap {\n return composeStore(notificationHandlerOverrideStore) as NotificationHandlerOverridesMap\n}\n\nexport function applyApiInterceptorOverrides(overrides: ApiInterceptorOverridesMap): void {\n applyStoreOverrides(apiInterceptorOverrideStore, 'programmatic', overrides as OverrideMap<ApiInterceptor>, { label: 'interceptors' })\n}\n\nexport function composeApiInterceptorOverrides(): ApiInterceptorOverridesMap {\n return composeStore(apiInterceptorOverrideStore) as ApiInterceptorOverridesMap\n}\n\nexport function applyCommandInterceptorOverrides(overrides: CommandInterceptorOverridesMap): void {\n applyStoreOverrides(commandInterceptorOverrideStore, 'programmatic', overrides as OverrideMap<CommandInterceptor>, { label: 'commandInterceptors' })\n}\n\nexport function composeCommandInterceptorOverrides(): CommandInterceptorOverridesMap {\n return composeStore(commandInterceptorOverrideStore) as CommandInterceptorOverridesMap\n}\n\nexport function applyResponseEnricherOverrides(overrides: ResponseEnricherOverridesMap): void {\n applyStoreOverrides(responseEnricherOverrideStore, 'programmatic', overrides as OverrideMap<ResponseEnricher>, { label: 'enrichers' })\n}\n\nexport function composeResponseEnricherOverrides(): ResponseEnricherOverridesMap {\n return composeStore(responseEnricherOverrideStore) as ResponseEnricherOverridesMap\n}\n\nexport function applyPageGuardOverrides(overrides: PageGuardOverridesMap): void {\n applyStoreOverrides(pageGuardOverrideStore, 'programmatic', overrides as OverrideMap<PageRouteMiddleware>, { label: 'guards' })\n}\n\nexport function composePageGuardOverrides(): PageGuardOverridesMap {\n return composeStore(pageGuardOverrideStore) as PageGuardOverridesMap\n}\n\nexport function applyAclFeatureOverrides(overrides: AclFeatureOverridesMap): void {\n applyStoreOverrides(aclFeatureOverrideStore, 'programmatic', overrides as OverrideMap<Exclude<AclFeatureOverride, null>>, { label: 'acl.features' })\n}\n\nexport function composeAclFeatureOverrides(): AclFeatureOverridesMap {\n return composeStore(aclFeatureOverrideStore) as AclFeatureOverridesMap\n}\n\nexport function applyEncryptionMapOverrides(overrides: EncryptionMapOverridesMap): void {\n applyStoreOverrides(encryptionMapOverrideStore, 'programmatic', overrides as OverrideMap<ModuleEncryptionMap>, { label: 'encryption.maps' })\n}\n\nexport function composeEncryptionMapOverrides(): EncryptionMapOverridesMap {\n return composeStore(encryptionMapOverrideStore) as EncryptionMapOverridesMap\n}\n\nexport function applyDiOverrides(overrides: DiOverridesMap): void {\n applyStoreOverrides(diOverrideStore, 'programmatic', overrides as OverrideMap<Exclude<DiBindingOverride, null>>, { label: 'di' })\n}\n\nexport function composeDiOverrides(): DiOverridesMap {\n return composeStore(diOverrideStore) as DiOverridesMap\n}\n\n/**\n * Apply an API-route override map to a list of registered manifest entries.\n * Returns a NEW array \u2014 never mutates input.\n *\n * Behavior:\n * - For each entry, walk every method in `entry.methods`.\n * - If the override key `'METHOD path'` is `null`, drop that method from\n * the entry's `methods` array.\n * - If the override key is a definition, wrap `entry.load()` to return a\n * module that exposes the override handler at `module[METHOD]` (top-level)\n * and the override metadata at `module.metadata[METHOD]`.\n * - If every method on the entry is disabled, the entry is dropped.\n * - Overrides naming a key that does not match any entry log a single\n * warning so an operator notices a stale override.\n */\nexport function applyApiOverridesToManifests<T extends ApiRouteManifestEntry>(\n routes: readonly T[],\n overrides: Readonly<ApiRouteOverridesMap>,\n): T[] {\n if (!routes || routes.length === 0) return Array.from(routes)\n const overrideKeys = Object.keys(overrides)\n if (overrideKeys.length === 0) return Array.from(routes)\n\n const consumedKeys = new Set<string>()\n const result: T[] = []\n\n for (const entry of routes) {\n const path = entry.path\n const remainingMethods: HttpMethod[] = []\n const methodOverrides = new Map<HttpMethod, ApiRouteOverrideDefinition>()\n\n for (const method of entry.methods) {\n const manifestKey = `${method} ${path}`\n const publicKey = path.startsWith('/api/')\n ? manifestKey\n : `${method} /api${path === '/' ? '' : path}`\n const key = Object.prototype.hasOwnProperty.call(overrides, manifestKey)\n ? manifestKey\n : Object.prototype.hasOwnProperty.call(overrides, publicKey)\n ? publicKey\n : null\n if (!key) {\n remainingMethods.push(method)\n continue\n }\n consumedKeys.add(key)\n const value = overrides[key]\n if (value === null) continue\n if (value && typeof value === 'object' && typeof value.handler === 'function') {\n methodOverrides.set(method, value)\n remainingMethods.push(method)\n } else {\n console.warn(\n `[Module Overrides] Skipping malformed routes.api override for \"${key}\" \u2014 expected { handler, metadata? } or null.`,\n )\n remainingMethods.push(method)\n }\n }\n\n if (remainingMethods.length === 0) continue\n\n const methodsChanged = remainingMethods.length !== entry.methods.length\n const hasReplacements = methodOverrides.size > 0\n\n if (!methodsChanged && !hasReplacements) {\n result.push(entry)\n continue\n }\n\n const originalLoad = entry.load\n const wrappedLoad: T['load'] = hasReplacements\n ? async () => {\n const original = await originalLoad()\n const out: Record<string, unknown> = { ...original }\n const baseMetadata = original && typeof original === 'object' && original.metadata !== undefined\n ? original.metadata\n : null\n const mergedMetadata: Record<string, unknown> = baseMetadata && typeof baseMetadata === 'object'\n ? { ...(baseMetadata as Record<string, unknown>) }\n : {}\n for (const [method, def] of methodOverrides) {\n out[method] = def.handler\n if (def.metadata !== undefined) mergedMetadata[method] = def.metadata\n }\n if (Object.keys(mergedMetadata).length > 0) out.metadata = mergedMetadata\n return out\n }\n : originalLoad\n\n result.push({ ...entry, methods: remainingMethods, load: wrappedLoad })\n }\n\n for (const key of overrideKeys) {\n if (!consumedKeys.has(key)) {\n console.warn(\n `[Module Overrides] routes.api override \"${key}\" did not match any registered API route \u2014 override skipped.`,\n )\n }\n }\n\n return result\n}\n\nexport function applyPageOverridesToManifests<T extends BackendRouteManifestEntry | FrontendRouteManifestEntry>(\n routes: readonly T[],\n overrides: Readonly<PageRouteOverridesMap>,\n kind: 'backend' | 'frontend',\n): T[] {\n if (!routes || routes.length === 0) return Array.from(routes)\n const normalizedOverrides: PageRouteOverridesMap = {}\n for (const [rawKey, value] of Object.entries(overrides)) {\n const key = normalizePageRouteOverrideKey(rawKey)\n if (!key) {\n console.warn(\n `[Module Overrides] Skipping malformed routes.pages key \"${rawKey}\" \u2014 expected \"/backend/path\" or \"/frontend/path\".`,\n )\n continue\n }\n normalizedOverrides[key] = value\n }\n const overrideKeys = Object.keys(normalizedOverrides).filter((key) => key.startsWith(`${kind}:`))\n if (overrideKeys.length === 0) return Array.from(routes)\n\n const consumed = new Set<string>()\n const result: T[] = []\n\n for (const entry of routes) {\n const path = entry.pattern ?? entry.path ?? '/'\n const key = `${kind}:${path.replace(/\\/+$/, '') || '/'}`\n if (!Object.prototype.hasOwnProperty.call(normalizedOverrides, key)) {\n result.push(entry)\n continue\n }\n\n consumed.add(key)\n const override = normalizedOverrides[key]\n if (override === null) continue\n if (!override || typeof override !== 'object') {\n console.warn(\n `[Module Overrides] Skipping malformed routes.pages override for \"${key}\" \u2014 expected { load?, Component?, metadata? } or null.`,\n )\n result.push(entry)\n continue\n }\n\n const hasLoad = typeof override.load === 'function'\n const hasComponent = typeof override.Component === 'function'\n if (!hasLoad && !hasComponent && override.metadata === undefined) {\n console.warn(\n `[Module Overrides] Skipping malformed routes.pages override for \"${key}\" \u2014 expected a loader, component, metadata, or null.`,\n )\n result.push(entry)\n continue\n }\n\n const metadata = override.metadata && typeof override.metadata === 'object'\n ? override.metadata\n : {}\n const load = hasLoad\n ? override.load!\n : hasComponent\n ? async () => override.Component!\n : entry.load\n\n result.push({ ...entry, ...metadata, load })\n }\n\n warnStaleOverrides('routes.pages', Object.fromEntries(\n overrideKeys.map((key) => [key, normalizedOverrides[key]]),\n ), consumed)\n return result\n}\n\nfunction applyEntryListOverrides<TEntry extends { moduleId: string }, TValue>(\n entries: readonly TEntry[] | undefined,\n overrides: Readonly<OverrideMap<TValue>>,\n options: {\n listKey: keyof TEntry\n label: string\n getId: (value: TValue) => string | null\n isReplacement: (value: unknown) => value is TValue\n },\n): TEntry[] | undefined {\n // Fast path: when no overrides are registered, return the input directly.\n // All known callers iterate the result (.forEach/.flatMap/.map/storage),\n // never mutate it. This avoids a per-request Array.from copy on hot paths\n // like executePageMiddleware \u2192 applyPageGuardOverridesToEntries.\n if (!entries || Object.keys(overrides).length === 0) return entries as TEntry[] | undefined\n const consumed = new Set<string>()\n let changed = false\n const result = entries.map((entry) => {\n const list = entry[options.listKey]\n if (!Array.isArray(list)) return entry\n const applied = applyArrayOverrides(list as TValue[], overrides, {\n label: options.label,\n getId: options.getId,\n isReplacement: options.isReplacement,\n })\n for (const key of applied.consumed) consumed.add(key)\n if (!applied.changed) return entry\n changed = true\n return { ...entry, [options.listKey]: applied.items ?? [] }\n })\n warnStaleOverrides(options.label, overrides, consumed)\n return changed ? result : Array.from(entries)\n}\n\nexport function applyInjectionWidgetOverridesToEntries(\n entries: readonly ModuleInjectionWidgetEntry[],\n overrides: Readonly<InjectionWidgetOverridesMap> = composeInjectionWidgetOverrides(),\n): ModuleInjectionWidgetEntry[] {\n const applied = applyArrayOverrides(entries, overrides as OverrideMap<ModuleInjectionWidgetEntry>, {\n label: 'widgets.injection',\n getId: (entry) => entry.key,\n isReplacement: isInjectionWidgetEntry,\n })\n warnStaleOverrides('widgets.injection', overrides, applied.consumed)\n return applied.items ?? []\n}\n\nexport function applyDashboardWidgetOverridesToEntries(\n entries: readonly ModuleDashboardWidgetEntry[],\n overrides: Readonly<DashboardWidgetOverridesMap> = composeDashboardWidgetOverrides(),\n): ModuleDashboardWidgetEntry[] {\n const applied = applyArrayOverrides(entries, overrides as OverrideMap<ModuleDashboardWidgetEntry>, {\n label: 'widgets.dashboard',\n getId: (entry) => entry.key,\n isReplacement: isDashboardWidgetEntry,\n })\n warnStaleOverrides('widgets.dashboard', overrides, applied.consumed)\n return applied.items ?? []\n}\n\nexport function applyWorkerOverridesToDescriptors<T extends { id: string; queue: string; concurrency: number; handler: unknown }>(\n entries: readonly T[],\n overrides: Readonly<WorkerOverridesMap> = composeWorkerOverrides(),\n): T[] {\n const applied = applyArrayOverrides(entries, overrides as OverrideMap<T>, {\n label: 'workers',\n getId: (entry) => entry.id,\n isReplacement: (value): value is T =>\n !!value\n && typeof value === 'object'\n && typeof (value as { id?: unknown }).id === 'string'\n && typeof (value as { queue?: unknown }).queue === 'string'\n && typeof (value as { concurrency?: unknown }).concurrency === 'number'\n && typeof (value as { handler?: unknown }).handler === 'function',\n })\n warnStaleOverrides('workers', overrides, applied.consumed)\n return applied.items ?? []\n}\n\nexport function applyInjectionWidgetOverridesToTables(\n tables: readonly { moduleId: string; table: ModuleInjectionTable }[],\n overrides: Readonly<InjectionWidgetOverridesMap> = composeInjectionWidgetOverrides(),\n): Array<{ moduleId: string; table: ModuleInjectionTable }> {\n const disabled = new Set(Object.entries(overrides).filter(([, value]) => value === null).map(([key]) => key))\n if (disabled.size === 0) return Array.from(tables)\n\n const filterSlot = (slot: unknown): unknown => {\n if (typeof slot === 'string') return disabled.has(slot) ? null : slot\n if (slot && typeof slot === 'object') {\n const widgetId = (slot as { widgetId?: unknown }).widgetId\n return typeof widgetId === 'string' && disabled.has(widgetId) ? null : slot\n }\n return slot\n }\n\n return tables.map((entry) => {\n const nextTable: ModuleInjectionTable = {}\n for (const [spotId, rawSlot] of Object.entries(entry.table)) {\n const filtered = Array.isArray(rawSlot)\n ? rawSlot.map(filterSlot).filter((slot): slot is NonNullable<typeof slot> => slot !== null)\n : filterSlot(rawSlot)\n if (Array.isArray(filtered)) {\n if (filtered.length > 0) nextTable[spotId] = filtered as ModuleInjectionTable[string]\n } else if (filtered !== null) {\n nextTable[spotId] = filtered as ModuleInjectionTable[string]\n }\n }\n return { ...entry, table: nextTable }\n })\n}\n\nexport function applyComponentOverridesToEntries(\n entries: readonly { moduleId: string; componentOverrides: ComponentOverride[] }[],\n overrides: Readonly<ComponentOverridesMap> = composeComponentOverrides(),\n): Array<{ moduleId: string; componentOverrides: ComponentOverride[] }> {\n const overrideKeys = Object.keys(overrides)\n if (overrideKeys.length === 0) return Array.from(entries)\n const consumed = new Set<string>()\n const additions: ComponentOverride[] = []\n const result = entries.map((entry) => {\n const next = entry.componentOverrides.filter((override) => {\n const id = override.target.componentId\n if (!Object.prototype.hasOwnProperty.call(overrides, id)) return true\n consumed.add(id)\n return false\n })\n return { ...entry, componentOverrides: next }\n })\n for (const key of overrideKeys) {\n const value = overrides[key]\n if (value === null) continue\n if (!isComponentOverrideValue(value)) {\n console.warn(`[Module Overrides] Skipping malformed widgets.components override for \"${key}\" \u2014 expected ComponentOverride, ComponentOverride[], or null.`)\n continue\n }\n const values = Array.isArray(value) ? value : [value]\n for (const override of values) {\n if (override.target.componentId !== key) {\n console.warn(`[Module Overrides] Skipping malformed widgets.components override for \"${key}\" \u2014 target.componentId must match the override key.`)\n continue\n }\n additions.push(override)\n consumed.add(key)\n }\n }\n if (additions.length > 0) {\n result.push({ moduleId: 'overrides', componentOverrides: additions })\n }\n warnStaleOverrides('widgets.components', overrides, consumed)\n return result\n}\n\nexport function applyNotificationTypeOverridesToEntries(\n entries: readonly { moduleId: string; types: NotificationTypeDefinition[] }[],\n overrides: Readonly<NotificationTypeOverridesMap> = composeNotificationTypeOverrides(),\n): Array<{ moduleId: string; types: NotificationTypeDefinition[] }> {\n return applyEntryListOverrides(entries, overrides as OverrideMap<NotificationTypeDefinition>, {\n listKey: 'types',\n label: 'notifications.types',\n getId: (entry) => entry.type,\n isReplacement: isNotificationType,\n }) ?? []\n}\n\nexport function applyNotificationHandlerOverridesToEntries(\n entries: readonly { moduleId: string; handlers: NotificationHandler[] }[],\n overrides: Readonly<NotificationHandlerOverridesMap> = composeNotificationHandlerOverrides(),\n): Array<{ moduleId: string; handlers: NotificationHandler[] }> {\n return applyEntryListOverrides(entries, overrides as OverrideMap<NotificationHandler>, {\n listKey: 'handlers',\n label: 'notifications.handlers',\n getId: (entry) => entry.id,\n isReplacement: isNotificationHandler,\n }) ?? []\n}\n\nexport function applyApiInterceptorOverridesToEntries(\n entries: readonly { moduleId: string; interceptors: ApiInterceptor[] }[],\n overrides: Readonly<ApiInterceptorOverridesMap> = composeApiInterceptorOverrides(),\n): Array<{ moduleId: string; interceptors: ApiInterceptor[] }> {\n return applyEntryListOverrides(entries, overrides as OverrideMap<ApiInterceptor>, {\n listKey: 'interceptors',\n label: 'interceptors',\n getId: (entry) => entry.id,\n isReplacement: isApiInterceptor,\n }) ?? []\n}\n\nexport function applyCommandInterceptorOverridesToEntries(\n entries: readonly { moduleId: string; interceptors: CommandInterceptor[] }[],\n overrides: Readonly<CommandInterceptorOverridesMap> = composeCommandInterceptorOverrides(),\n): Array<{ moduleId: string; interceptors: CommandInterceptor[] }> {\n return applyEntryListOverrides(entries, overrides as OverrideMap<CommandInterceptor>, {\n listKey: 'interceptors',\n label: 'commandInterceptors',\n getId: (entry) => entry.id,\n isReplacement: isCommandInterceptor,\n }) ?? []\n}\n\nexport function applyResponseEnricherOverridesToEntries(\n entries: readonly { moduleId: string; enrichers: ResponseEnricher[] }[],\n overrides: Readonly<ResponseEnricherOverridesMap> = composeResponseEnricherOverrides(),\n): Array<{ moduleId: string; enrichers: ResponseEnricher[] }> {\n return applyEntryListOverrides(entries, overrides as OverrideMap<ResponseEnricher>, {\n listKey: 'enrichers',\n label: 'enrichers',\n getId: (entry) => entry.id,\n isReplacement: isResponseEnricher,\n }) ?? []\n}\n\nexport function applyPageGuardOverridesToEntries(\n entries: readonly PageMiddlewareRegistryEntry[],\n overrides: Readonly<PageGuardOverridesMap> = composePageGuardOverrides(),\n): PageMiddlewareRegistryEntry[] {\n return applyEntryListOverrides(entries, overrides as OverrideMap<PageRouteMiddleware>, {\n listKey: 'middleware',\n label: 'guards',\n getId: (entry) => entry.id,\n isReplacement: isPageRouteMiddleware,\n }) ?? []\n}\n\nfunction applySetupOverride(setup: ModuleSetupConfig | undefined, override: SetupOverridesShape | undefined): ModuleSetupConfig | undefined {\n if (!override) return setup\n const next: ModuleSetupConfig = { ...(setup ?? {}) }\n if (override.defaultRoleFeatures) {\n next.defaultRoleFeatures = Object.fromEntries(\n Object.entries(override.defaultRoleFeatures).map(([role, features]) => [role, Array.from(features)]),\n )\n }\n if (override.defaultCustomerRoleFeatures) {\n next.defaultCustomerRoleFeatures = Object.fromEntries(\n Object.entries(override.defaultCustomerRoleFeatures).map(([role, features]) => [role, Array.from(features)]),\n )\n }\n if (override.seedDefaults === false) delete next.seedDefaults\n if (override.seedExamples === false) delete next.seedExamples\n if (override.onTenantCreated === false) delete next.onTenantCreated\n return next\n}\n\nexport function applyModuleOverridesToModules(modules: readonly Module[]): Module[] {\n const subscriberOverrides = composeSubscriberOverrides()\n const workerOverrides = composeWorkerOverrides()\n const cliOverrides = composeCliOverrides()\n const aclOverrides = composeAclFeatureOverrides()\n const encryptionOverrides = composeEncryptionMapOverrides()\n const hasSetupOverrides = Object.keys(setupOverridesByModule).length > 0\n const hasAny =\n Object.keys(subscriberOverrides).length > 0\n || Object.keys(workerOverrides).length > 0\n || Object.keys(cliOverrides).length > 0\n || Object.keys(aclOverrides).length > 0\n || Object.keys(encryptionOverrides).length > 0\n || hasSetupOverrides\n\n if (!hasAny) return modules as Module[]\n\n const subscriberConsumed = new Set<string>()\n const workerConsumed = new Set<string>()\n const cliConsumed = new Set<string>()\n const aclConsumed = new Set<string>()\n const encryptionConsumed = new Set<string>()\n\n const result = modules.map((module) => {\n let next = module\n\n const subscribers = applyArrayOverrides(module.subscribers, subscriberOverrides as OverrideMap<ModuleSubscriber>, {\n label: 'events.subscribers',\n getId: (entry) => entry.id,\n isReplacement: isModuleSubscriber,\n })\n for (const key of subscribers.consumed) subscriberConsumed.add(key)\n if (subscribers.changed) next = { ...next, subscribers: subscribers.items }\n\n const workers = applyArrayOverrides(module.workers, workerOverrides as OverrideMap<ModuleWorker>, {\n label: 'workers',\n getId: (entry) => entry.id,\n isReplacement: isModuleWorker,\n })\n for (const key of workers.consumed) workerConsumed.add(key)\n if (workers.changed) next = { ...next, workers: workers.items }\n\n const cli = applyArrayOverrides(module.cli, cliOverrides as OverrideMap<ModuleCli>, {\n label: 'cli',\n getId: (entry) => entry.command,\n isReplacement: isModuleCli,\n })\n for (const key of cli.consumed) cliConsumed.add(key)\n if (cli.changed) next = { ...next, cli: cli.items }\n\n const features = applyArrayOverrides(module.features, aclOverrides as OverrideMap<NonNullable<Module['features']>[number]>, {\n label: 'acl.features',\n getId: getFeatureId,\n isReplacement: (value): value is NonNullable<Module['features']>[number] => getFeatureId(value) !== null,\n })\n for (const key of features.consumed) aclConsumed.add(key)\n if (features.changed) next = { ...next, features: features.items }\n\n const maps = applyArrayOverrides(module.defaultEncryptionMaps, encryptionOverrides as OverrideMap<ModuleEncryptionMap>, {\n label: 'encryption.maps',\n getId: (entry) => entry.entityId,\n isReplacement: isEncryptionMap,\n })\n for (const key of maps.consumed) encryptionConsumed.add(key)\n if (maps.changed) next = { ...next, defaultEncryptionMaps: maps.items }\n\n const setupOverride = setupOverridesByModule[module.id]\n if (setupOverride) {\n next = { ...next, setup: applySetupOverride(module.setup, setupOverride) }\n }\n\n return next\n })\n\n warnStaleOverrides('events.subscribers', subscriberOverrides, subscriberConsumed)\n warnStaleOverrides('workers', workerOverrides, workerConsumed)\n warnStaleOverrides('cli', cliOverrides, cliConsumed)\n warnStaleOverrides('acl.features', aclOverrides, aclConsumed)\n warnStaleOverrides('encryption.maps', encryptionOverrides, encryptionConsumed)\n\n return result\n}\n\nexport function applyDiOverridesToContainer(container: {\n register: (registrations: Record<string, unknown>) => unknown\n unregister?: (name: string) => unknown\n registrations?: Record<string, unknown>\n}): void {\n const overrides = composeDiOverrides()\n for (const [key, value] of Object.entries(overrides)) {\n if (value === null) {\n if (typeof container.unregister === 'function') {\n container.unregister(key)\n } else if (container.registrations && key in container.registrations) {\n delete container.registrations[key]\n }\n continue\n }\n if (value && typeof value === 'object' && typeof (value as DiBindingOverrideDefinition).register === 'function') {\n ;(value as DiBindingOverrideDefinition).register(container, key)\n continue\n }\n container.register({ [key]: value })\n }\n}\n\n/**\n * Dispatcher applier for the `'routes'` domain. Buckets every entry's\n * `overrides.routes.api` and `overrides.routes.pages` into the\n * `modules.ts` tier.\n */\nfunction routesOverridesApplier(\n entries: ReadonlyArray<ModuleOverrideEntry<RoutesOverridesShape>>,\n): void {\n for (const entry of entries) {\n const shape = entry.overrides\n if (!shape || typeof shape !== 'object') continue\n const api = shape.api\n if (api && typeof api === 'object') {\n for (const [rawKey, value] of Object.entries(api)) {\n const key = normalizeApiRouteOverrideKey(rawKey)\n if (!key) {\n console.warn(\n `[Module Overrides] Skipping malformed routes.api key \"${rawKey}\" \u2014 expected \"METHOD /api/path\".`,\n )\n continue\n }\n modulesConfigApiRouteOverrides[key] = value as ApiRouteOverride\n }\n }\n const pages = shape.pages\n if (pages && typeof pages === 'object' && Object.keys(pages).length > 0) {\n applyStoreOverrides(pageRouteOverrideStore, 'modules', pages as OverrideMap<PageRouteOverrideDefinition>, {\n label: 'routes.pages',\n normalizeKey: normalizePageRouteOverrideKey,\n })\n }\n }\n}\n\nfunction eventsOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<EventsOverridesShape>>): void {\n for (const entry of entries) {\n const subscribers = entry.overrides?.subscribers\n applyStoreOverrides(subscriberOverrideStore, 'modules', subscribers as OverrideMap<ModuleSubscriber>, { label: 'events.subscribers' })\n }\n}\n\nfunction workersOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<WorkerOverridesMap>>): void {\n for (const entry of entries) {\n applyStoreOverrides(workerOverrideStore, 'modules', entry.overrides as OverrideMap<ModuleWorker>, { label: 'workers' })\n }\n}\n\nfunction widgetsOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<WidgetsOverridesShape>>): void {\n for (const entry of entries) {\n applyStoreOverrides(injectionWidgetOverrideStore, 'modules', entry.overrides?.injection as OverrideMap<ModuleInjectionWidgetEntry>, { label: 'widgets.injection' })\n applyStoreOverrides(componentOverrideStore, 'modules', entry.overrides?.components as OverrideMap<ComponentOverride | ComponentOverride[]>, { label: 'widgets.components' })\n applyStoreOverrides(dashboardWidgetOverrideStore, 'modules', entry.overrides?.dashboard as OverrideMap<ModuleDashboardWidgetEntry>, { label: 'widgets.dashboard' })\n }\n}\n\nfunction notificationsOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<NotificationsOverridesShape>>): void {\n for (const entry of entries) {\n applyStoreOverrides(notificationTypeOverrideStore, 'modules', entry.overrides?.types as OverrideMap<NotificationTypeDefinition>, { label: 'notifications.types' })\n applyStoreOverrides(notificationHandlerOverrideStore, 'modules', entry.overrides?.handlers as OverrideMap<NotificationHandler>, { label: 'notifications.handlers' })\n }\n}\n\nfunction interceptorsOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<ApiInterceptorOverridesMap>>): void {\n for (const entry of entries) {\n applyStoreOverrides(apiInterceptorOverrideStore, 'modules', entry.overrides as OverrideMap<ApiInterceptor>, { label: 'interceptors' })\n }\n}\n\nfunction commandInterceptorsOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<CommandInterceptorOverridesMap>>): void {\n for (const entry of entries) {\n applyStoreOverrides(commandInterceptorOverrideStore, 'modules', entry.overrides as OverrideMap<CommandInterceptor>, { label: 'commandInterceptors' })\n }\n}\n\nfunction enrichersOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<ResponseEnricherOverridesMap>>): void {\n for (const entry of entries) {\n applyStoreOverrides(responseEnricherOverrideStore, 'modules', entry.overrides as OverrideMap<ResponseEnricher>, { label: 'enrichers' })\n }\n}\n\nfunction guardsOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<PageGuardOverridesMap>>): void {\n for (const entry of entries) {\n applyStoreOverrides(pageGuardOverrideStore, 'modules', entry.overrides as OverrideMap<PageRouteMiddleware>, { label: 'guards' })\n }\n}\n\nfunction cliOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<CliOverridesMap>>): void {\n for (const entry of entries) {\n applyStoreOverrides(cliOverrideStore, 'modules', entry.overrides as OverrideMap<ModuleCli>, { label: 'cli' })\n }\n}\n\nfunction setupOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<SetupOverridesShape>>): void {\n for (const entry of entries) {\n if (!entry.overrides || typeof entry.overrides !== 'object') continue\n setupOverridesByModule[entry.moduleId] = entry.overrides\n }\n}\n\nfunction aclOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<AclOverridesShape>>): void {\n for (const entry of entries) {\n applyStoreOverrides(aclFeatureOverrideStore, 'modules', entry.overrides?.features as OverrideMap<Exclude<AclFeatureOverride, null>>, { label: 'acl.features' })\n }\n}\n\nfunction diOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<DiOverridesMap>>): void {\n for (const entry of entries) {\n applyStoreOverrides(diOverrideStore, 'modules', entry.overrides as OverrideMap<Exclude<DiBindingOverride, null>>, { label: 'di' })\n }\n}\n\nfunction encryptionOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<EncryptionOverridesShape>>): void {\n for (const entry of entries) {\n applyStoreOverrides(encryptionMapOverrideStore, 'modules', entry.overrides?.maps as OverrideMap<ModuleEncryptionMap>, { label: 'encryption.maps' })\n }\n}\n\nfunction registerBuiltInModuleOverrideAppliers(): void {\n registerModuleOverrideApplier<RoutesOverridesShape>('routes', routesOverridesApplier)\n registerModuleOverrideApplier<EventsOverridesShape>('events', eventsOverridesApplier)\n registerModuleOverrideApplier<WorkerOverridesMap>('workers', workersOverridesApplier)\n registerModuleOverrideApplier<WidgetsOverridesShape>('widgets', widgetsOverridesApplier)\n registerModuleOverrideApplier<NotificationsOverridesShape>('notifications', notificationsOverridesApplier)\n registerModuleOverrideApplier<ApiInterceptorOverridesMap>('interceptors', interceptorsOverridesApplier)\n registerModuleOverrideApplier<CommandInterceptorOverridesMap>('commandInterceptors', commandInterceptorsOverridesApplier)\n registerModuleOverrideApplier<ResponseEnricherOverridesMap>('enrichers', enrichersOverridesApplier)\n registerModuleOverrideApplier<PageGuardOverridesMap>('guards', guardsOverridesApplier)\n registerModuleOverrideApplier<CliOverridesMap>('cli', cliOverridesApplier)\n registerModuleOverrideApplier<SetupOverridesShape>('setup', setupOverridesApplier)\n registerModuleOverrideApplier<AclOverridesShape>('acl', aclOverridesApplier)\n registerModuleOverrideApplier<DiOverridesMap>('di', diOverridesApplier)\n registerModuleOverrideApplier<EncryptionOverridesShape>('encryption', encryptionOverridesApplier)\n}\n\nregisterBuiltInModuleOverrideAppliers()\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/modules/search.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityId } from './entities'\n\n// =============================================================================\n// Strategy Identifiers\n// =============================================================================\n\n/**\n * Built-in strategy identifiers plus extensible string for third-party strategies.\n */\nexport type SearchStrategyId = 'tokens' | 'vector' | 'fulltext' | (string & Record<string, never>)\n\n// =============================================================================\n// Result Types\n// =============================================================================\n\n/**\n * Presenter metadata for displaying search results in UI (Cmd+K, global search).\n */\nexport type SearchResultPresenter = {\n title: string\n subtitle?: string\n icon?: string\n badge?: string\n}\n\n/**\n * Deep link rendered next to a search result.\n */\nexport type SearchResultLink = {\n href: string\n label: string\n kind?: 'primary' | 'secondary'\n}\n\n/**\n * A single search result returned by a strategy.\n */\nexport type SearchResult = {\n /** Entity type identifier, e.g., 'customers:customer_person_profile' */\n entityId: EntityId\n /** Record primary key */\n recordId: string\n /** Relevance score (normalized 0-1 range preferred, but RRF scores may exceed 1) */\n score: number\n /** Which strategy produced this result */\n source: SearchStrategyId\n /** Optional presenter for quick display */\n presenter?: SearchResultPresenter\n /** Primary URL when result is clicked */\n url?: string\n /** Additional action links */\n links?: SearchResultLink[]\n /** Extra metadata from the strategy */\n metadata?: Record<string, unknown>\n /** Organization scope of the result, when known by the strategy. */\n organizationId?: string | null\n}\n\n// =============================================================================\n// Search Options\n// =============================================================================\n\n/**\n * Options passed to SearchService.search()\n */\nexport type SearchOptions = {\n /** Tenant isolation - required */\n tenantId: string\n /**\n * Optional organization filter.\n * - `string` restricts results to that organization only.\n * - `undefined` or `null` means no organization filter (tenant-wide).\n */\n organizationId?: string | null\n /**\n * Optional organization allowlist.\n * - Non-empty array restricts results to one of those organizations.\n * - Empty array means no organizations are visible and should return no results.\n * - `undefined` or `null` means no organization filter (tenant-wide).\n *\n * `organizationId` takes precedence when both are provided.\n */\n organizationIds?: string[] | null\n /** Filter to specific entity types */\n entityTypes?: EntityId[]\n /** Use only specific strategies (defaults to all available) */\n strategies?: SearchStrategyId[]\n /** Maximum results per strategy before merging */\n limit?: number\n /** Offset for pagination */\n offset?: number\n /** How to combine results: 'or' merges all, 'and' requires match in all strategies */\n combineMode?: 'or' | 'and'\n}\n\n// =============================================================================\n// Indexable Record\n// =============================================================================\n\n/**\n * A record prepared for indexing across all strategies.\n */\nexport type IndexableRecord = {\n /** Entity type identifier */\n entityId: EntityId\n /** Record primary key */\n recordId: string\n /** Tenant for isolation */\n tenantId: string\n /** Optional organization for additional filtering */\n organizationId?: string | null\n /** All fields from the record (strategies will filter based on their needs) */\n fields: Record<string, unknown>\n /** Optional presenter for result display */\n presenter?: SearchResultPresenter\n /** Primary URL for the record */\n url?: string\n /** Additional action links */\n links?: SearchResultLink[]\n /** Text content for embedding (from buildSource, used by vector strategy) */\n text?: string | string[]\n /** Source object for checksum calculation (change detection) */\n checksumSource?: unknown\n}\n\n// =============================================================================\n// Strategy Interface\n// =============================================================================\n\n/**\n * Interface that all search strategies must implement.\n * Following the cache module's strategy pattern.\n */\nexport interface SearchStrategy {\n /** Unique strategy identifier */\n readonly id: SearchStrategyId\n\n /** Human-readable name for debugging/logging */\n readonly name: string\n\n /** Priority for result merging (higher = more prominent in results) */\n readonly priority: number\n\n /** Check if strategy is available and configured */\n isAvailable(): Promise<boolean>\n\n /** Initialize strategy resources (lazy, called on first use) */\n ensureReady(): Promise<void>\n\n /** Execute a search query */\n search(query: string, options: SearchOptions): Promise<SearchResult[]>\n\n /** Index a record */\n index(record: IndexableRecord): Promise<void>\n\n /** Delete a record from the index */\n delete(entityId: EntityId, recordId: string, tenantId: string): Promise<void>\n\n /** Bulk index multiple records (optional optimization) */\n bulkIndex?(records: IndexableRecord[]): Promise<void>\n\n /** Purge all records for an entity type (optional) */\n purge?(entityId: EntityId, tenantId: string): Promise<void>\n}\n\n// =============================================================================\n// Service Configuration\n// =============================================================================\n\n/**\n * Configuration for result merging across strategies.\n */\nexport type ResultMergeConfig = {\n /** How to handle duplicate results: 'highest_score' | 'first' | 'merge_scores' */\n duplicateHandling: 'highest_score' | 'first' | 'merge_scores'\n /** Weight multipliers per strategy (e.g., { meilisearch: 1.2, tokens: 0.8 }) */\n strategyWeights?: Record<SearchStrategyId, number>\n /** Minimum score threshold to include in results */\n minScore?: number\n}\n\n/**\n * Callback function to enrich search results with presenter data.\n * Used to load presenter from database when not available from search strategy.\n */\nexport type PresenterEnricherFn = (\n results: SearchResult[],\n tenantId: string,\n organizationId?: string | null,\n) => Promise<SearchResult[]>\n\n/**\n * Options for creating a SearchService instance.\n */\nexport type SearchServiceOptions = {\n /** Array of strategy instances */\n strategies?: SearchStrategy[]\n /** Default strategies to use when not specified in search options */\n defaultStrategies?: SearchStrategyId[]\n /** Fallback strategy when others fail */\n fallbackStrategy?: SearchStrategyId\n /** Configuration for merging results from multiple strategies */\n mergeConfig?: ResultMergeConfig\n /** Callback to enrich results with presenter data from database */\n presenterEnricher?: PresenterEnricherFn\n /** TTL (ms) for the per-strategy availability cache. Defaults to 2_000. */\n availabilityCacheTtlMs?: number\n}\n\n// =============================================================================\n// Module Configuration (for modules defining searchable entities)\n// =============================================================================\n\n/**\n * Context passed to buildSource, formatResult, resolveUrl, and resolveLinks.\n */\nexport type SearchBuildContext = {\n /** The record being indexed */\n record: Record<string, unknown>\n /** Custom fields for the record */\n customFields: Record<string, unknown>\n /** Organization ID if applicable */\n organizationId?: string | null\n /** Tenant ID */\n tenantId?: string | null\n /** DI container for resolving dependencies */\n container?: unknown\n /** Query engine for loading related records (optional, used by buildSource for entity hydration) */\n queryEngine?: unknown\n}\n\n/**\n * Source data for indexing a record.\n */\nexport type SearchIndexSource = {\n /** Text content for keyword/fuzzy search (single string or array of chunks) */\n text: string | string[]\n /** Optional structured fields for filtering */\n fields?: Record<string, unknown>\n /** Presenter for quick display in search results */\n presenter?: SearchResultPresenter\n /** Deep links for the result */\n links?: SearchResultLink[]\n /** Source object used for checksum calculation (change detection) */\n checksumSource?: unknown\n}\n\n/**\n * Policy defining how fields should be handled for search indexing.\n */\nexport type SearchFieldPolicy = {\n /** Fields safe to send to external providers (fuzzy searchable) */\n searchable?: string[]\n /** Fields for hash-based search only (encrypted/sensitive) */\n hashOnly?: string[]\n /** Fields to exclude from all search */\n excluded?: string[]\n}\n\n/**\n * Configuration for a single searchable entity within a module.\n */\nexport type SearchEntityConfig = {\n /** Entity identifier, e.g., 'customers:customer_person_profile' */\n entityId: EntityId\n /** Enable/disable search for this entity (default: true) */\n enabled?: boolean\n /** Override strategies for this specific entity */\n strategies?: SearchStrategyId[]\n /** Priority for result ordering (higher = more prominent) */\n priority?: number\n /** Build searchable content from record */\n buildSource?: (ctx: SearchBuildContext) => Promise<SearchIndexSource | null> | SearchIndexSource | null\n /** Format result for display in Cmd+K */\n formatResult?: (ctx: SearchBuildContext) => Promise<SearchResultPresenter | null> | SearchResultPresenter | null\n /** Resolve primary URL when result is clicked */\n resolveUrl?: (ctx: SearchBuildContext) => Promise<string | null> | string | null\n /** Resolve additional action links */\n resolveLinks?: (ctx: SearchBuildContext) => Promise<SearchResultLink[] | null> | SearchResultLink[] | null\n /** Define which fields are searchable vs hash-only */\n fieldPolicy?: SearchFieldPolicy\n /**\n * Per-entity view feature(s) required to read this entity's records through\n * data-returning surfaces (e.g. the `search_get` / `search_aggregate` AI tools).\n * These tools must NOT rely on the search-administration `search.view` feature\n * to gate record reads \u2014 callers must additionally hold the owning module's\n * `<entity>.view` feature(s) declared here. When omitted, those tools fail\n * closed (deny) so an entity is never exposed without an explicit grant.\n */\n aclFeatures?: string[]\n}\n\n/**\n * Module-level search configuration (defined in search.ts files).\n */\nexport type SearchModuleConfig = {\n /** Default strategies for all entities in this module */\n defaultStrategies?: SearchStrategyId[]\n /** Entity configurations */\n entities: SearchEntityConfig[]\n}\n\n// =============================================================================\n// Event Payloads (for indexer events)\n// =============================================================================\n\n/**\n * Payload for search.index_record events.\n */\nexport type SearchIndexPayload = {\n entityId: EntityId\n recordId: string\n tenantId: string\n organizationId?: string | null\n record: Record<string, unknown>\n customFields?: Record<string, unknown>\n}\n\n/**\n * Payload for search.delete_record events.\n */\nexport type SearchDeletePayload = {\n entityId: EntityId\n recordId: string\n tenantId: string\n}\n\n// =============================================================================\n// Global Registry for Search Module Configs\n// =============================================================================\n\nlet _searchModuleConfigs: SearchModuleConfig[] | null = null\n\n/**\n * Register search module configurations globally.\n * Called during app bootstrap with configs from search.generated.ts.\n */\nexport function registerSearchModuleConfigs(configs: SearchModuleConfig[]): void {\n if (_searchModuleConfigs !== null && process.env.NODE_ENV === 'development') {\n console.debug('[Bootstrap] Search module configs re-registered (this may occur during HMR)')\n }\n _searchModuleConfigs = configs\n}\n\n/**\n * Get registered search module configurations.\n * Returns empty array if not registered (search module may not be enabled).\n */\nexport function getSearchModuleConfigs(): SearchModuleConfig[] {\n return _searchModuleConfigs ?? []\n}\n"],
|
|
4
|
+
"sourcesContent": ["import type { EntityId } from './entities'\n\n// =============================================================================\n// Strategy Identifiers\n// =============================================================================\n\n/**\n * Built-in strategy identifiers plus extensible string for third-party strategies.\n */\nexport type SearchStrategyId = 'tokens' | 'vector' | 'fulltext' | (string & Record<string, never>)\n\n// =============================================================================\n// Result Types\n// =============================================================================\n\n/**\n * Presenter metadata for displaying search results in UI (Cmd+K, global search).\n */\nexport type SearchResultPresenter = {\n title: string\n subtitle?: string\n icon?: string\n badge?: string\n}\n\n/**\n * Deep link rendered next to a search result.\n */\nexport type SearchResultLink = {\n href: string\n label: string\n kind?: 'primary' | 'secondary'\n}\n\n/**\n * A single search result returned by a strategy.\n */\nexport type SearchResult = {\n /** Entity type identifier, e.g., 'customers:customer_person_profile' */\n entityId: EntityId\n /** Record primary key */\n recordId: string\n /** Relevance score (normalized 0-1 range preferred, but RRF scores may exceed 1) */\n score: number\n /** Which strategy produced this result */\n source: SearchStrategyId\n /** Optional presenter for quick display */\n presenter?: SearchResultPresenter\n /** Primary URL when result is clicked */\n url?: string\n /** Additional action links */\n links?: SearchResultLink[]\n /** Extra metadata from the strategy */\n metadata?: Record<string, unknown>\n /** Organization scope of the result, when known by the strategy. */\n organizationId?: string | null\n}\n\n// =============================================================================\n// Search Options\n// =============================================================================\n\n/**\n * Options passed to SearchService.search()\n */\nexport type SearchOptions = {\n /** Tenant isolation - required */\n tenantId: string\n /**\n * Optional organization filter.\n * - `string` restricts results to that organization only.\n * - `undefined` or `null` means no organization filter (tenant-wide).\n */\n organizationId?: string | null\n /**\n * Optional organization allowlist.\n * - Non-empty array restricts results to one of those organizations.\n * - Empty array means no organizations are visible and should return no results.\n * - `undefined` or `null` means no organization filter (tenant-wide).\n *\n * `organizationId` takes precedence when both are provided.\n */\n organizationIds?: string[] | null\n /** Filter to specific entity types */\n entityTypes?: EntityId[]\n /** Use only specific strategies (defaults to all available) */\n strategies?: SearchStrategyId[]\n /** Maximum results per strategy before merging */\n limit?: number\n /** Offset for pagination */\n offset?: number\n /** How to combine results: 'or' merges all, 'and' requires match in all strategies */\n combineMode?: 'or' | 'and'\n}\n\n// =============================================================================\n// Indexable Record\n// =============================================================================\n\n/**\n * A record prepared for indexing across all strategies.\n */\nexport type IndexableRecord = {\n /** Entity type identifier */\n entityId: EntityId\n /** Record primary key */\n recordId: string\n /** Tenant for isolation */\n tenantId: string\n /** Optional organization for additional filtering */\n organizationId?: string | null\n /** All fields from the record (strategies will filter based on their needs) */\n fields: Record<string, unknown>\n /** Optional presenter for result display */\n presenter?: SearchResultPresenter\n /** Primary URL for the record */\n url?: string\n /** Additional action links */\n links?: SearchResultLink[]\n /** Text content for embedding (from buildSource, used by vector strategy) */\n text?: string | string[]\n /** Source object for checksum calculation (change detection) */\n checksumSource?: unknown\n}\n\n// =============================================================================\n// Strategy Interface\n// =============================================================================\n\n/**\n * Interface that all search strategies must implement.\n * Following the cache module's strategy pattern.\n */\nexport interface SearchStrategy {\n /** Unique strategy identifier */\n readonly id: SearchStrategyId\n\n /** Human-readable name for debugging/logging */\n readonly name: string\n\n /** Priority for result merging (higher = more prominent in results) */\n readonly priority: number\n\n /** Check if strategy is available and configured */\n isAvailable(): Promise<boolean>\n\n /** Initialize strategy resources (lazy, called on first use) */\n ensureReady(): Promise<void>\n\n /** Execute a search query */\n search(query: string, options: SearchOptions): Promise<SearchResult[]>\n\n /** Index a record */\n index(record: IndexableRecord): Promise<void>\n\n /** Delete a record from the index */\n delete(entityId: EntityId, recordId: string, tenantId: string): Promise<void>\n\n /** Bulk index multiple records (optional optimization) */\n bulkIndex?(records: IndexableRecord[]): Promise<void>\n\n /** Purge all records for an entity type (optional) */\n purge?(entityId: EntityId, tenantId: string, organizationId?: string | null): Promise<void>\n}\n\n// =============================================================================\n// Service Configuration\n// =============================================================================\n\n/**\n * Configuration for result merging across strategies.\n */\nexport type ResultMergeConfig = {\n /** How to handle duplicate results: 'highest_score' | 'first' | 'merge_scores' */\n duplicateHandling: 'highest_score' | 'first' | 'merge_scores'\n /** Weight multipliers per strategy (e.g., { meilisearch: 1.2, tokens: 0.8 }) */\n strategyWeights?: Record<SearchStrategyId, number>\n /** Minimum score threshold to include in results */\n minScore?: number\n}\n\n/**\n * Callback function to enrich search results with presenter data.\n * Used to load presenter from database when not available from search strategy.\n */\nexport type PresenterEnricherFn = (\n results: SearchResult[],\n tenantId: string,\n organizationId?: string | null,\n) => Promise<SearchResult[]>\n\n/**\n * Options for creating a SearchService instance.\n */\nexport type SearchServiceOptions = {\n /** Array of strategy instances */\n strategies?: SearchStrategy[]\n /** Default strategies to use when not specified in search options */\n defaultStrategies?: SearchStrategyId[]\n /** Fallback strategy when others fail */\n fallbackStrategy?: SearchStrategyId\n /** Configuration for merging results from multiple strategies */\n mergeConfig?: ResultMergeConfig\n /** Callback to enrich results with presenter data from database */\n presenterEnricher?: PresenterEnricherFn\n /** TTL (ms) for the per-strategy availability cache. Defaults to 2_000. */\n availabilityCacheTtlMs?: number\n}\n\n// =============================================================================\n// Module Configuration (for modules defining searchable entities)\n// =============================================================================\n\n/**\n * Context passed to buildSource, formatResult, resolveUrl, and resolveLinks.\n */\nexport type SearchBuildContext = {\n /** The record being indexed */\n record: Record<string, unknown>\n /** Custom fields for the record */\n customFields: Record<string, unknown>\n /** Organization ID if applicable */\n organizationId?: string | null\n /** Tenant ID */\n tenantId?: string | null\n /** DI container for resolving dependencies */\n container?: unknown\n /** Query engine for loading related records (optional, used by buildSource for entity hydration) */\n queryEngine?: unknown\n}\n\n/**\n * Source data for indexing a record.\n */\nexport type SearchIndexSource = {\n /** Text content for keyword/fuzzy search (single string or array of chunks) */\n text: string | string[]\n /** Optional structured fields for filtering */\n fields?: Record<string, unknown>\n /** Presenter for quick display in search results */\n presenter?: SearchResultPresenter\n /** Deep links for the result */\n links?: SearchResultLink[]\n /** Source object used for checksum calculation (change detection) */\n checksumSource?: unknown\n}\n\n/**\n * Policy defining how fields should be handled for search indexing.\n */\nexport type SearchFieldPolicy = {\n /** Fields safe to send to external providers (fuzzy searchable) */\n searchable?: string[]\n /** Fields for hash-based search only (encrypted/sensitive) */\n hashOnly?: string[]\n /** Fields to exclude from all search */\n excluded?: string[]\n}\n\n/**\n * Configuration for a single searchable entity within a module.\n */\nexport type SearchEntityConfig = {\n /** Entity identifier, e.g., 'customers:customer_person_profile' */\n entityId: EntityId\n /** Enable/disable search for this entity (default: true) */\n enabled?: boolean\n /** Override strategies for this specific entity */\n strategies?: SearchStrategyId[]\n /** Priority for result ordering (higher = more prominent) */\n priority?: number\n /** Build searchable content from record */\n buildSource?: (ctx: SearchBuildContext) => Promise<SearchIndexSource | null> | SearchIndexSource | null\n /** Format result for display in Cmd+K */\n formatResult?: (ctx: SearchBuildContext) => Promise<SearchResultPresenter | null> | SearchResultPresenter | null\n /** Resolve primary URL when result is clicked */\n resolveUrl?: (ctx: SearchBuildContext) => Promise<string | null> | string | null\n /** Resolve additional action links */\n resolveLinks?: (ctx: SearchBuildContext) => Promise<SearchResultLink[] | null> | SearchResultLink[] | null\n /** Define which fields are searchable vs hash-only */\n fieldPolicy?: SearchFieldPolicy\n /**\n * Per-entity view feature(s) required to read this entity's records through\n * data-returning surfaces (e.g. the `search_get` / `search_aggregate` AI tools).\n * These tools must NOT rely on the search-administration `search.view` feature\n * to gate record reads \u2014 callers must additionally hold the owning module's\n * `<entity>.view` feature(s) declared here. When omitted, those tools fail\n * closed (deny) so an entity is never exposed without an explicit grant.\n */\n aclFeatures?: string[]\n}\n\n/**\n * Module-level search configuration (defined in search.ts files).\n */\nexport type SearchModuleConfig = {\n /** Default strategies for all entities in this module */\n defaultStrategies?: SearchStrategyId[]\n /** Entity configurations */\n entities: SearchEntityConfig[]\n}\n\n// =============================================================================\n// Event Payloads (for indexer events)\n// =============================================================================\n\n/**\n * Payload for search.index_record events.\n */\nexport type SearchIndexPayload = {\n entityId: EntityId\n recordId: string\n tenantId: string\n organizationId?: string | null\n record: Record<string, unknown>\n customFields?: Record<string, unknown>\n}\n\n/**\n * Payload for search.delete_record events.\n */\nexport type SearchDeletePayload = {\n entityId: EntityId\n recordId: string\n tenantId: string\n}\n\n// =============================================================================\n// Global Registry for Search Module Configs\n// =============================================================================\n\nlet _searchModuleConfigs: SearchModuleConfig[] | null = null\n\n/**\n * Register search module configurations globally.\n * Called during app bootstrap with configs from search.generated.ts.\n */\nexport function registerSearchModuleConfigs(configs: SearchModuleConfig[]): void {\n if (_searchModuleConfigs !== null && process.env.NODE_ENV === 'development') {\n console.debug('[Bootstrap] Search module configs re-registered (this may occur during HMR)')\n }\n _searchModuleConfigs = configs\n}\n\n/**\n * Get registered search module configurations.\n * Returns empty array if not registered (search module may not be enabled).\n */\nexport function getSearchModuleConfigs(): SearchModuleConfig[] {\n return _searchModuleConfigs ?? []\n}\n"],
|
|
5
5
|
"mappings": "AA2UA,IAAI,uBAAoD;AAMjD,SAAS,4BAA4B,SAAqC;AAC/E,MAAI,yBAAyB,QAAQ,QAAQ,IAAI,aAAa,eAAe;AAC3E,YAAQ,MAAM,6EAA6E;AAAA,EAC7F;AACA,yBAAuB;AACzB;AAMO,SAAS,yBAA+C;AAC7D,SAAO,wBAAwB,CAAC;AAClC;",
|
|
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.5
|
|
3
|
+
"version": "0.6.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -92,12 +92,12 @@
|
|
|
92
92
|
"@mikro-orm/core": "^7.1.4",
|
|
93
93
|
"@mikro-orm/decorators": "^7.1.4",
|
|
94
94
|
"@mikro-orm/postgresql": "^7.1.4",
|
|
95
|
-
"@open-mercato/cache": "0.6.5
|
|
95
|
+
"@open-mercato/cache": "0.6.5",
|
|
96
96
|
"dotenv": "^17.4.2",
|
|
97
97
|
"rate-limiter-flexible": "^11.2.0",
|
|
98
98
|
"re2js": "2.8.3",
|
|
99
99
|
"reflect-metadata": "^0.2.2",
|
|
100
|
-
"sanitize-html": "^2.17.
|
|
100
|
+
"sanitize-html": "^2.17.5",
|
|
101
101
|
"undici": "^8.4.1"
|
|
102
102
|
},
|
|
103
103
|
"devDependencies": {
|
|
@@ -113,6 +113,5 @@
|
|
|
113
113
|
"type": "git",
|
|
114
114
|
"url": "https://github.com/open-mercato/open-mercato",
|
|
115
115
|
"directory": "packages/shared"
|
|
116
|
-
}
|
|
117
|
-
"stableVersion": "0.6.4"
|
|
116
|
+
}
|
|
118
117
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* "first configured" resolver used by the routing layer.
|
|
5
5
|
*
|
|
6
6
|
* @see ./llm-provider.ts
|
|
7
|
-
* @see .ai/specs/2026-04-14-llm-provider-ports-and-adapters.md
|
|
7
|
+
* @see .ai/specs/implemented/2026-04-14-llm-provider-ports-and-adapters.md
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { EnvLookup, LlmProvider } from './llm-provider'
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* plain data, not code.
|
|
13
13
|
*
|
|
14
14
|
* @see packages/shared/src/lib/ai/llm-provider-registry.ts
|
|
15
|
-
* @see .ai/specs/2026-04-14-llm-provider-ports-and-adapters.md
|
|
15
|
+
* @see .ai/specs/implemented/2026-04-14-llm-provider-ports-and-adapters.md
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
export type EnvLookup = Record<string, string | undefined>
|