@open-mercato/shared 0.6.2-develop.3461.1.605f31c2c9 → 0.6.2

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.
Files changed (67) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +24 -0
  3. package/dist/lib/bootstrap/factory.js +3 -1
  4. package/dist/lib/bootstrap/factory.js.map +2 -2
  5. package/dist/lib/commands/command-interceptor-store.js +3 -1
  6. package/dist/lib/commands/command-interceptor-store.js.map +2 -2
  7. package/dist/lib/crud/enricher-registry.js +3 -1
  8. package/dist/lib/crud/enricher-registry.js.map +2 -2
  9. package/dist/lib/crud/factory.js.map +1 -1
  10. package/dist/lib/crud/interceptor-registry.js +3 -1
  11. package/dist/lib/crud/interceptor-registry.js.map +2 -2
  12. package/dist/lib/crud/sync-event-runner.js +2 -8
  13. package/dist/lib/crud/sync-event-runner.js.map +2 -2
  14. package/dist/lib/db/mikro.js +27 -3
  15. package/dist/lib/db/mikro.js.map +2 -2
  16. package/dist/lib/di/container.js +16 -0
  17. package/dist/lib/di/container.js.map +2 -2
  18. package/dist/lib/events/patterns.js +2 -10
  19. package/dist/lib/events/patterns.js.map +2 -2
  20. package/dist/lib/middleware/page-executor.js +2 -1
  21. package/dist/lib/middleware/page-executor.js.map +2 -2
  22. package/dist/lib/modules/registry.js +2 -1
  23. package/dist/lib/modules/registry.js.map +2 -2
  24. package/dist/lib/notifications/handler-registry.js +3 -1
  25. package/dist/lib/notifications/handler-registry.js.map +2 -2
  26. package/dist/lib/patterns/wildcard.js +42 -0
  27. package/dist/lib/patterns/wildcard.js.map +7 -0
  28. package/dist/lib/regex/linear.js +31 -0
  29. package/dist/lib/regex/linear.js.map +7 -0
  30. package/dist/lib/version.js +1 -1
  31. package/dist/lib/version.js.map +1 -1
  32. package/dist/modules/entities/validation.js +15 -7
  33. package/dist/modules/entities/validation.js.map +2 -2
  34. package/dist/modules/overrides.js +857 -0
  35. package/dist/modules/overrides.js.map +2 -2
  36. package/dist/modules/registry.js +17 -4
  37. package/dist/modules/registry.js.map +2 -2
  38. package/dist/modules/widgets/injection-loader.js +12 -6
  39. package/dist/modules/widgets/injection-loader.js.map +2 -2
  40. package/jest.config.cjs +3 -1
  41. package/package.json +8 -8
  42. package/src/lib/bootstrap/factory.ts +3 -1
  43. package/src/lib/commands/command-interceptor-store.ts +3 -1
  44. package/src/lib/crud/__tests__/sync-event-runner.test.ts +5 -0
  45. package/src/lib/crud/enricher-registry.ts +3 -1
  46. package/src/lib/crud/factory.ts +2 -2
  47. package/src/lib/crud/interceptor-registry.ts +3 -1
  48. package/src/lib/crud/sync-event-runner.ts +2 -11
  49. package/src/lib/db/mikro.ts +27 -2
  50. package/src/lib/di/container.ts +24 -4
  51. package/src/lib/events/__tests__/patterns.test.ts +5 -0
  52. package/src/lib/events/patterns.ts +3 -16
  53. package/src/lib/middleware/page-executor.ts +2 -1
  54. package/src/lib/modules/registry.ts +2 -1
  55. package/src/lib/notifications/handler-registry.ts +3 -1
  56. package/src/lib/patterns/__tests__/wildcard.test.ts +27 -0
  57. package/src/lib/patterns/wildcard.ts +59 -0
  58. package/src/lib/regex/linear.ts +48 -0
  59. package/src/modules/__tests__/contract-overrides.test.ts +332 -0
  60. package/src/modules/__tests__/overrides.test.ts +23 -22
  61. package/src/modules/__tests__/route-overrides.test.ts +441 -0
  62. package/src/modules/entities/__tests__/validation.test.ts +97 -2
  63. package/src/modules/entities/validation.ts +14 -9
  64. package/src/modules/overrides.ts +1297 -27
  65. package/src/modules/registry.ts +27 -4
  66. package/src/modules/widgets/__tests__/injection-loader.required-modules.test.ts +21 -0
  67. package/src/modules/widgets/injection-loader.ts +12 -6
@@ -12,19 +12,19 @@
12
12
  * from: '@app',
13
13
  * overrides: {
14
14
  * ai: { agents: {...}, tools: {...} }, // Phase 1 — wired
15
- * routes: { api: {...}, pages: {...} }, // Phase 2/3 — stub
16
- * events: { subscribers: {...} }, // Phase 4 — stub
17
- * workers: {...}, // Phase 5 — stub
15
+ * routes: { api: {...}, pages: {...} }, // Phase 2/3 — wired
16
+ * events: { subscribers: {...} }, // Phase 4 — wired
17
+ * workers: {...}, // Phase 5 — wired
18
18
  * ...
19
19
  * },
20
20
  * }
21
21
  *
22
22
  * The umbrella shape is the union of every per-domain sub-shape. Per-
23
- * domain runtime hooks ("wired" domains) own their composers and apply
24
- * the override map against their registry. Until a phase ships, the
25
- * dispatcher emits a one-shot structured warning when it sees an
26
- * override targeting that unwired domain — the runtime never throws on
27
- * unwired domains so app boot stays unaffected during the rollout.
23
+ * domain runtime hooks own their composers and apply the override map
24
+ * against their registry. During the rollout the dispatcher emitted a
25
+ * one-shot warning for unwired domains; all domains in this umbrella are
26
+ * now wired, so warnings only indicate a missing custom applier in tests
27
+ * or future domain additions.
28
28
  *
29
29
  * Resolution order across all domains (highest precedence first):
30
30
  *
@@ -59,33 +59,47 @@ export interface AiOverridesShape {
59
59
  extensions?: unknown[]
60
60
  }
61
61
 
62
- /** Phase 2/3 — routes (api + pages). Stubbed until wired. */
62
+ type LooseOverrideMap = Record<string, unknown>
63
+
64
+ /**
65
+ * Phase 2 (api) / Phase 3 (pages).
66
+ *
67
+ * The `api` sub-shape is keyed by `'METHOD /api/path'` and accepts either
68
+ * a replacement definition (`{ handler, metadata? }`) or `null` to disable.
69
+ * See {@link ApiRouteOverridesMap} for the strongly-typed alias and
70
+ * {@link applyApiOverridesToManifests} for the runtime apply step.
71
+ *
72
+ * The `pages` sub-shape is keyed by `'/backend/path'` or
73
+ * `'/frontend/path'`; `null` disables the page and a definition replaces
74
+ * its loader and/or metadata.
75
+ */
63
76
  export interface RoutesOverridesShape {
64
- api?: Record<string, unknown>
65
- pages?: Record<string, unknown>
77
+ api?: ApiRouteOverridesMap | LooseOverrideMap
78
+ pages?: PageRouteOverridesMap | LooseOverrideMap
66
79
  }
67
80
 
68
- /** Phase 4 — event subscribers. Stubbed until wired. */
81
+ /** Phase 4 — event subscribers. */
69
82
  export interface EventsOverridesShape {
70
- subscribers?: Record<string, unknown>
83
+ subscribers?: SubscriberOverridesMap | LooseOverrideMap
71
84
  }
72
85
 
73
86
  /** Phase 6/7/8 — widget injection, component handles, dashboard widgets. */
74
87
  export interface WidgetsOverridesShape {
75
- injection?: Record<string, unknown>
76
- components?: Record<string, unknown>
77
- dashboard?: Record<string, unknown>
88
+ injection?: InjectionWidgetOverridesMap | LooseOverrideMap
89
+ components?: ComponentOverridesMap | LooseOverrideMap
90
+ dashboard?: DashboardWidgetOverridesMap | LooseOverrideMap
78
91
  }
79
92
 
80
93
  /** Phase 9 — notification types + handlers. */
81
94
  export interface NotificationsOverridesShape {
82
- types?: Record<string, unknown>
83
- handlers?: Record<string, unknown>
95
+ types?: NotificationTypeOverridesMap | LooseOverrideMap
96
+ handlers?: NotificationHandlerOverridesMap | LooseOverrideMap
84
97
  }
85
98
 
86
99
  /** Phase 15 — setup lifecycle hooks. */
87
100
  export interface SetupOverridesShape {
88
101
  defaultRoleFeatures?: Record<string, readonly string[]>
102
+ defaultCustomerRoleFeatures?: Record<string, readonly string[]>
89
103
  seedDefaults?: false
90
104
  seedExamples?: false
91
105
  onTenantCreated?: false
@@ -93,12 +107,12 @@ export interface SetupOverridesShape {
93
107
 
94
108
  /** Phase 16 — ACL features (per-feature override). */
95
109
  export interface AclOverridesShape {
96
- features?: Record<string, unknown>
110
+ features?: AclFeatureOverridesMap | LooseOverrideMap
97
111
  }
98
112
 
99
113
  /** Phase 18 — encryption maps per entity id. */
100
114
  export interface EncryptionOverridesShape {
101
- maps?: Record<string, unknown>
115
+ maps?: EncryptionMapOverridesMap | LooseOverrideMap
102
116
  }
103
117
 
104
118
  /**
@@ -109,17 +123,17 @@ export interface ModuleOverrides {
109
123
  ai?: AiOverridesShape
110
124
  routes?: RoutesOverridesShape
111
125
  events?: EventsOverridesShape
112
- workers?: Record<string, unknown>
126
+ workers?: WorkerOverridesMap | LooseOverrideMap
113
127
  widgets?: WidgetsOverridesShape
114
128
  notifications?: NotificationsOverridesShape
115
- interceptors?: Record<string, unknown>
116
- commandInterceptors?: Record<string, unknown>
117
- enrichers?: Record<string, unknown>
118
- guards?: Record<string, unknown>
119
- cli?: Record<string, unknown>
129
+ interceptors?: ApiInterceptorOverridesMap | LooseOverrideMap
130
+ commandInterceptors?: CommandInterceptorOverridesMap | LooseOverrideMap
131
+ enrichers?: ResponseEnricherOverridesMap | LooseOverrideMap
132
+ guards?: PageGuardOverridesMap | LooseOverrideMap
133
+ cli?: CliOverridesMap | LooseOverrideMap
120
134
  setup?: SetupOverridesShape
121
135
  acl?: AclOverridesShape
122
- di?: Record<string, unknown>
136
+ di?: DiOverridesMap | LooseOverrideMap
123
137
  encryption?: EncryptionOverridesShape
124
138
  }
125
139
 
@@ -189,6 +203,7 @@ export function registerModuleOverrideApplier<TShape>(
189
203
  export function resetModuleOverrideAppliersForTests(): void {
190
204
  appliers.clear()
191
205
  warnedUnwiredDomains.clear()
206
+ registerBuiltInModuleOverrideAppliers()
192
207
  }
193
208
 
194
209
  // ---------------------------------------------------------------------------
@@ -264,3 +279,1258 @@ export function applyModuleOverridesFromEnabledModules(
264
279
  applier(entries)
265
280
  }
266
281
  }
282
+
283
+ // ---------------------------------------------------------------------------
284
+ // Phase 2/3 — Routes (API + pages) applier
285
+ // ---------------------------------------------------------------------------
286
+ //
287
+ // Wires `entry.overrides.routes.api` so a downstream module can disable or
288
+ // replace any API route declared by another module. The override map is
289
+ // keyed by `'METHOD /api/path'` (case-insensitive method, leading slash on
290
+ // the path), and each value is either:
291
+ //
292
+ // - `null` — disable the matching method on the route.
293
+ // - `{ handler, metadata? }` — replace the matching method's handler
294
+ // (and optionally merge override metadata into the route's metadata).
295
+ //
296
+ // Resolution order (lowest precedence first):
297
+ //
298
+ // 1. `modules.ts` inline — every `entry.overrides.routes.api` on a
299
+ // `ModuleEntry` (last entry per key wins).
300
+ // 2. Programmatic — `applyApiRouteOverrides({...})` from anywhere at
301
+ // boot or test scaffolds (last call per key wins).
302
+ //
303
+ // A file-based tier (per-module `apiRouteOverrides` exports) is **not**
304
+ // part of Phase 2 — modules that ship a route override do so via
305
+ // `modules.ts` or programmatically. The umbrella spec keeps the file-based
306
+ // tier "where applicable".
307
+ //
308
+ // The applied overrides are consumed by `registerApiRouteManifests` (see
309
+ // `./registry.ts`) which rewrites the manifest array once before storing
310
+ // it. Calls to `applyApiRouteOverrides` made AFTER manifests are already
311
+ // registered do not retro-actively update the stored manifest — for
312
+ // runtime override scenarios call the dispatcher / programmatic API
313
+ // before `bootstrap.ts` triggers manifest registration.
314
+ //
315
+ // Spec: `.ai/specs/2026-05-04-modules-ts-unified-overrides.md` (Phases 2/3).
316
+
317
+ // ApiRouteManifestEntry / ApiHandler / HttpMethod live in ./registry. The
318
+ // type-only import is erased at runtime, so there is no cycle even though
319
+ // ./registry imports the runtime helpers below.
320
+ import type {
321
+ ApiHandler,
322
+ ApiRouteManifestEntry,
323
+ BackendRouteManifestEntry,
324
+ FrontendRouteManifestEntry,
325
+ HttpMethod,
326
+ Module,
327
+ ModuleCli,
328
+ ModuleDashboardWidgetEntry,
329
+ ModuleInjectionWidgetEntry,
330
+ ModuleSubscriber,
331
+ ModuleWorker,
332
+ } from './registry'
333
+ import type { ModuleInjectionTable } from './widgets/injection'
334
+ import type { ComponentOverride } from './widgets/component-registry'
335
+ import type { NotificationHandler } from './notifications/handler'
336
+ import type { NotificationTypeDefinition } from './notifications/types'
337
+ import type { ModuleEncryptionMap } from './encryption'
338
+ import type { ModuleSetupConfig } from './setup'
339
+ import type { ApiInterceptor } from '../lib/crud/api-interceptor'
340
+ import type { ResponseEnricher } from '../lib/crud/response-enricher'
341
+ import type { CommandInterceptor } from '../lib/commands/command-interceptor'
342
+ import type { PageMiddlewareRegistryEntry, PageRouteMiddleware } from './middleware/page'
343
+
344
+ /** Override for a single API route entry: replace handler/metadata, or `null` to disable. */
345
+ export interface ApiRouteOverrideDefinition {
346
+ /** Replacement handler. Same signature as the original API route handler. */
347
+ handler: ApiHandler
348
+ /** Optional metadata override — replaces the per-method metadata on the route. */
349
+ metadata?: unknown
350
+ }
351
+
352
+ /** Override value per `'METHOD /api/path'` key — definition or `null` to disable. */
353
+ export type ApiRouteOverride = ApiRouteOverrideDefinition | null
354
+
355
+ /** Map of `'METHOD /api/path'` → override. */
356
+ export type ApiRouteOverridesMap = Record<string, ApiRouteOverride>
357
+
358
+ type OverrideStore<T> = {
359
+ modules: Record<string, T | null>
360
+ programmatic: Record<string, T | null>
361
+ }
362
+
363
+ type OverrideValue<T> = T | null
364
+ type OverrideMap<T> = Record<string, OverrideValue<T>>
365
+
366
+ /** Override for a backend/frontend page route: replace loader/metadata, or `null` to disable. */
367
+ export interface PageRouteOverrideDefinition {
368
+ load?: BackendRouteManifestEntry['load']
369
+ Component?: Awaited<ReturnType<BackendRouteManifestEntry['load']>>
370
+ metadata?: Partial<Omit<BackendRouteManifestEntry | FrontendRouteManifestEntry, 'moduleId' | 'load'>>
371
+ }
372
+
373
+ export type PageRouteOverride = PageRouteOverrideDefinition | null
374
+ export type PageRouteOverridesMap = Record<string, PageRouteOverride>
375
+
376
+ export type SubscriberOverride = ModuleSubscriber | null
377
+ export type SubscriberOverridesMap = Record<string, SubscriberOverride>
378
+
379
+ export type WorkerOverride = ModuleWorker | null
380
+ export type WorkerOverridesMap = Record<string, WorkerOverride>
381
+
382
+ export type CliOverride = ModuleCli | null
383
+ export type CliOverridesMap = Record<string, CliOverride>
384
+
385
+ export type InjectionWidgetOverride = ModuleInjectionWidgetEntry | null
386
+ export type InjectionWidgetOverridesMap = Record<string, InjectionWidgetOverride>
387
+
388
+ export type DashboardWidgetOverride = ModuleDashboardWidgetEntry | null
389
+ export type DashboardWidgetOverridesMap = Record<string, DashboardWidgetOverride>
390
+
391
+ export type ComponentOverrideValue = ComponentOverride | ComponentOverride[] | null
392
+ export type ComponentOverridesMap = Record<string, ComponentOverrideValue>
393
+
394
+ export type NotificationTypeOverride = NotificationTypeDefinition | null
395
+ export type NotificationTypeOverridesMap = Record<string, NotificationTypeOverride>
396
+
397
+ export type NotificationHandlerOverride = NotificationHandler | null
398
+ export type NotificationHandlerOverridesMap = Record<string, NotificationHandlerOverride>
399
+
400
+ export type ApiInterceptorOverride = ApiInterceptor | null
401
+ export type ApiInterceptorOverridesMap = Record<string, ApiInterceptorOverride>
402
+
403
+ export type CommandInterceptorOverride = CommandInterceptor | null
404
+ export type CommandInterceptorOverridesMap = Record<string, CommandInterceptorOverride>
405
+
406
+ export type ResponseEnricherOverride = ResponseEnricher | null
407
+ export type ResponseEnricherOverridesMap = Record<string, ResponseEnricherOverride>
408
+
409
+ export type PageGuardOverride = PageRouteMiddleware | null
410
+ export type PageGuardOverridesMap = Record<string, PageGuardOverride>
411
+
412
+ export type AclFeatureOverride = string | { id: string; [key: string]: unknown } | null
413
+ export type AclFeatureOverridesMap = Record<string, AclFeatureOverride>
414
+
415
+ export type EncryptionMapOverride = ModuleEncryptionMap | null
416
+ export type EncryptionMapOverridesMap = Record<string, EncryptionMapOverride>
417
+
418
+ export type DiBindingOverrideDefinition = {
419
+ register: (container: { register: (registrations: Record<string, unknown>) => unknown }, key: string) => void
420
+ }
421
+ export type DiBindingOverride = DiBindingOverrideDefinition | unknown | null
422
+ export type DiOverridesMap = Record<string, DiBindingOverride>
423
+
424
+ const pageRouteOverrideStore: OverrideStore<PageRouteOverrideDefinition> = { modules: {}, programmatic: {} }
425
+ const subscriberOverrideStore: OverrideStore<ModuleSubscriber> = { modules: {}, programmatic: {} }
426
+ const workerOverrideStore: OverrideStore<ModuleWorker> = { modules: {}, programmatic: {} }
427
+ const cliOverrideStore: OverrideStore<ModuleCli> = { modules: {}, programmatic: {} }
428
+ const injectionWidgetOverrideStore: OverrideStore<ModuleInjectionWidgetEntry> = { modules: {}, programmatic: {} }
429
+ const dashboardWidgetOverrideStore: OverrideStore<ModuleDashboardWidgetEntry> = { modules: {}, programmatic: {} }
430
+ const componentOverrideStore: OverrideStore<ComponentOverride | ComponentOverride[]> = { modules: {}, programmatic: {} }
431
+ const notificationTypeOverrideStore: OverrideStore<NotificationTypeDefinition> = { modules: {}, programmatic: {} }
432
+ const notificationHandlerOverrideStore: OverrideStore<NotificationHandler> = { modules: {}, programmatic: {} }
433
+ const apiInterceptorOverrideStore: OverrideStore<ApiInterceptor> = { modules: {}, programmatic: {} }
434
+ const commandInterceptorOverrideStore: OverrideStore<CommandInterceptor> = { modules: {}, programmatic: {} }
435
+ const responseEnricherOverrideStore: OverrideStore<ResponseEnricher> = { modules: {}, programmatic: {} }
436
+ const pageGuardOverrideStore: OverrideStore<PageRouteMiddleware> = { modules: {}, programmatic: {} }
437
+ const aclFeatureOverrideStore: OverrideStore<Exclude<AclFeatureOverride, null>> = { modules: {}, programmatic: {} }
438
+ const encryptionMapOverrideStore: OverrideStore<ModuleEncryptionMap> = { modules: {}, programmatic: {} }
439
+ const diOverrideStore: OverrideStore<Exclude<DiBindingOverride, null>> = { modules: {}, programmatic: {} }
440
+ const setupOverridesByModule: Record<string, SetupOverridesShape> = {}
441
+
442
+ function normalizeIdOverrideKey(key: string, label: string): string | null {
443
+ if (typeof key !== 'string') return null
444
+ const trimmed = key.trim()
445
+ if (trimmed) return trimmed
446
+ console.warn(`[Module Overrides] Skipping malformed ${label} key "${key}" — expected a non-empty string.`)
447
+ return null
448
+ }
449
+
450
+ function clearStore<T>(store: OverrideStore<T>): void {
451
+ for (const key of Object.keys(store.modules)) delete store.modules[key]
452
+ for (const key of Object.keys(store.programmatic)) delete store.programmatic[key]
453
+ }
454
+
455
+ // Shared frozen empty override map. Returned from compose* helpers when both
456
+ // the modules-tier and programmatic-tier stores are empty so per-request hot
457
+ // paths (page guards, etc.) reuse one object instead of allocating fresh `{}`
458
+ // per call. Consumers MUST NOT mutate — internal applier loops iterate keys
459
+ // only, and the public API hands out shallow copies through composeApiRouteOverrides.
460
+ const EMPTY_OVERRIDE_MAP: Readonly<Record<string, never>> = Object.freeze({})
461
+
462
+ function applyStoreOverrides<T>(
463
+ store: OverrideStore<T>,
464
+ target: 'modules' | 'programmatic',
465
+ overrides: OverrideMap<T> | undefined,
466
+ options: {
467
+ label: string
468
+ normalizeKey?: (key: string) => string | null
469
+ },
470
+ ): void {
471
+ if (!overrides || typeof overrides !== 'object') return
472
+ const normalize = options.normalizeKey ?? ((key: string) => normalizeIdOverrideKey(key, options.label))
473
+ for (const [rawKey, value] of Object.entries(overrides)) {
474
+ const key = normalize(rawKey)
475
+ if (!key) continue
476
+ store[target][key] = value
477
+ }
478
+ }
479
+
480
+ function composeStore<T>(store: OverrideStore<T>): OverrideMap<T> {
481
+ const modulesKeys = Object.keys(store.modules)
482
+ const programmaticKeys = Object.keys(store.programmatic)
483
+ if (modulesKeys.length === 0 && programmaticKeys.length === 0) {
484
+ return EMPTY_OVERRIDE_MAP as OverrideMap<T>
485
+ }
486
+ return { ...store.modules, ...store.programmatic }
487
+ }
488
+
489
+ function applyArrayOverrides<T>(
490
+ items: readonly T[] | undefined,
491
+ overrides: Readonly<OverrideMap<T>>,
492
+ options: {
493
+ label: string
494
+ getId: (value: T) => string | null
495
+ isReplacement?: (value: unknown) => value is T
496
+ },
497
+ ): { items: T[] | undefined; consumed: Set<string>; changed: boolean } {
498
+ if (!items || Object.keys(overrides).length === 0) {
499
+ return { items: items ? Array.from(items) : items, consumed: new Set(), changed: false }
500
+ }
501
+
502
+ const consumed = new Set<string>()
503
+ const result: T[] = []
504
+ let changed = false
505
+
506
+ for (const item of items) {
507
+ const id = options.getId(item)
508
+ if (!id || !Object.prototype.hasOwnProperty.call(overrides, id)) {
509
+ result.push(item)
510
+ continue
511
+ }
512
+ consumed.add(id)
513
+ changed = true
514
+ const replacement = overrides[id]
515
+ if (replacement === null) continue
516
+ if (options.isReplacement && !options.isReplacement(replacement)) {
517
+ console.warn(
518
+ `[Module Overrides] Skipping malformed ${options.label} override for "${id}" — replacement has the wrong shape.`,
519
+ )
520
+ result.push(item)
521
+ continue
522
+ }
523
+ const replacementId = options.getId(replacement)
524
+ if (replacementId !== id) {
525
+ console.warn(
526
+ `[Module Overrides] Skipping malformed ${options.label} override for "${id}" — replacement id must match the override key.`,
527
+ )
528
+ result.push(item)
529
+ continue
530
+ }
531
+ result.push(replacement)
532
+ }
533
+
534
+ return { items: result, consumed, changed }
535
+ }
536
+
537
+ function warnStaleOverrides(label: string, overrides: Readonly<Record<string, unknown>>, consumed: ReadonlySet<string>): void {
538
+ for (const key of Object.keys(overrides)) {
539
+ if (!consumed.has(key)) {
540
+ console.warn(`[Module Overrides] ${label} override "${key}" did not match any registered entry — override skipped.`)
541
+ }
542
+ }
543
+ }
544
+
545
+ function isObjectWithId(value: unknown): value is { id: string } {
546
+ return !!value && typeof value === 'object' && typeof (value as { id?: unknown }).id === 'string'
547
+ }
548
+
549
+ function isModuleSubscriber(value: unknown): value is ModuleSubscriber {
550
+ return isObjectWithId(value)
551
+ && typeof (value as ModuleSubscriber).event === 'string'
552
+ && typeof (value as ModuleSubscriber).handler === 'function'
553
+ }
554
+
555
+ function isModuleWorker(value: unknown): value is ModuleWorker {
556
+ return isObjectWithId(value)
557
+ && typeof (value as ModuleWorker).queue === 'string'
558
+ && typeof (value as ModuleWorker).handler === 'function'
559
+ }
560
+
561
+ function isModuleCli(value: unknown): value is ModuleCli {
562
+ return !!value
563
+ && typeof value === 'object'
564
+ && typeof (value as ModuleCli).command === 'string'
565
+ && typeof (value as ModuleCli).run === 'function'
566
+ }
567
+
568
+ function isInjectionWidgetEntry(value: unknown): value is ModuleInjectionWidgetEntry {
569
+ return !!value
570
+ && typeof value === 'object'
571
+ && typeof (value as ModuleInjectionWidgetEntry).key === 'string'
572
+ && typeof (value as ModuleInjectionWidgetEntry).loader === 'function'
573
+ }
574
+
575
+ function isDashboardWidgetEntry(value: unknown): value is ModuleDashboardWidgetEntry {
576
+ return !!value
577
+ && typeof value === 'object'
578
+ && typeof (value as ModuleDashboardWidgetEntry).key === 'string'
579
+ && typeof (value as ModuleDashboardWidgetEntry).loader === 'function'
580
+ }
581
+
582
+ function isComponentOverrideValue(value: unknown): value is ComponentOverride | ComponentOverride[] {
583
+ const items = Array.isArray(value) ? value : [value]
584
+ return items.every((item) =>
585
+ !!item
586
+ && typeof item === 'object'
587
+ && !!(item as ComponentOverride).target
588
+ && typeof (item as ComponentOverride).target.componentId === 'string',
589
+ )
590
+ }
591
+
592
+ function isNotificationType(value: unknown): value is NotificationTypeDefinition {
593
+ return !!value && typeof value === 'object' && typeof (value as NotificationTypeDefinition).type === 'string'
594
+ }
595
+
596
+ function isNotificationHandler(value: unknown): value is NotificationHandler {
597
+ return isObjectWithId(value) && typeof (value as NotificationHandler).handle === 'function'
598
+ }
599
+
600
+ function isApiInterceptor(value: unknown): value is ApiInterceptor {
601
+ return isObjectWithId(value) && typeof (value as ApiInterceptor).targetRoute === 'string'
602
+ }
603
+
604
+ function isCommandInterceptor(value: unknown): value is CommandInterceptor {
605
+ return isObjectWithId(value) && typeof (value as CommandInterceptor).targetCommand === 'string'
606
+ }
607
+
608
+ function isResponseEnricher(value: unknown): value is ResponseEnricher {
609
+ return isObjectWithId(value)
610
+ && typeof (value as ResponseEnricher).targetEntity === 'string'
611
+ && typeof (value as ResponseEnricher).enrichOne === 'function'
612
+ }
613
+
614
+ function isPageRouteMiddleware(value: unknown): value is PageRouteMiddleware {
615
+ return isObjectWithId(value) && typeof (value as PageRouteMiddleware).run === 'function'
616
+ }
617
+
618
+ function isEncryptionMap(value: unknown): value is ModuleEncryptionMap {
619
+ return !!value
620
+ && typeof value === 'object'
621
+ && typeof (value as ModuleEncryptionMap).entityId === 'string'
622
+ && Array.isArray((value as ModuleEncryptionMap).fields)
623
+ }
624
+
625
+ function getFeatureId(value: unknown): string | null {
626
+ if (typeof value === 'string' && value.trim()) return value.trim()
627
+ if (value && typeof value === 'object' && typeof (value as { id?: unknown }).id === 'string') {
628
+ return (value as { id: string }).id
629
+ }
630
+ return null
631
+ }
632
+
633
+ const VALID_HTTP_METHODS: readonly HttpMethod[] = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']
634
+
635
+ const programmaticApiRouteOverrides: ApiRouteOverridesMap = {}
636
+ const modulesConfigApiRouteOverrides: ApiRouteOverridesMap = {}
637
+
638
+ /**
639
+ * Normalize an override key to the canonical `'METHOD /api/path'` form.
640
+ * Returns `null` if the key is malformed (missing method, unknown method,
641
+ * empty path). Trailing slashes on the path are stripped.
642
+ */
643
+ function normalizeApiRouteOverrideKey(key: string): string | null {
644
+ if (typeof key !== 'string') return null
645
+ const trimmed = key.trim()
646
+ if (!trimmed) return null
647
+ const parts = trimmed.split(/\s+/)
648
+ if (parts.length < 2) return null
649
+ const method = parts[0].toUpperCase() as HttpMethod
650
+ if (!VALID_HTTP_METHODS.includes(method)) return null
651
+ const rawPath = parts.slice(1).join(' ').trim()
652
+ if (!rawPath) return null
653
+ const withLead = rawPath.startsWith('/') ? rawPath : `/${rawPath}`
654
+ const normalized = withLead.replace(/\/+$/, '') || '/'
655
+ return `${method} ${normalized}`
656
+ }
657
+
658
+ function normalizePageRouteOverrideKey(key: string): string | null {
659
+ if (typeof key !== 'string') return null
660
+ const trimmed = key.trim()
661
+ if (!trimmed) return null
662
+ if (trimmed.startsWith('backend:') || trimmed.startsWith('frontend:')) return trimmed
663
+ const withLead = trimmed.startsWith('/') ? trimmed : `/${trimmed}`
664
+ const stripped = withLead.replace(/\/+$/, '') || '/'
665
+ if (stripped === '/frontend') return 'frontend:/'
666
+ if (stripped.startsWith('/frontend/')) return `frontend:/${stripped.slice('/frontend/'.length)}`
667
+ if (stripped === '/backend' || stripped.startsWith('/backend/')) return `backend:${stripped}`
668
+ return `frontend:${stripped}`
669
+ }
670
+
671
+ /**
672
+ * Programmatic API: apply API-route overrides. Supersedes any matching
673
+ * `modules.ts` inline overrides for the same key. Call before
674
+ * `registerApiRouteManifests` (i.e. before generated `apiRoutes` lands in
675
+ * the registry); calls after registration do not retro-actively update
676
+ * the stored manifest.
677
+ *
678
+ * @example
679
+ * applyApiRouteOverrides({
680
+ * 'GET /api/example/items': null, // disable
681
+ * 'POST /api/example/items': { handler: customHandler }, // replace
682
+ * })
683
+ */
684
+ export function applyApiRouteOverrides(overrides: ApiRouteOverridesMap): void {
685
+ if (!overrides) return
686
+ for (const [rawKey, value] of Object.entries(overrides)) {
687
+ const key = normalizeApiRouteOverrideKey(rawKey)
688
+ if (!key) {
689
+ console.warn(
690
+ `[Module Overrides] Skipping malformed routes.api key "${rawKey}" — expected "METHOD /api/path".`,
691
+ )
692
+ continue
693
+ }
694
+ programmaticApiRouteOverrides[key] = value
695
+ }
696
+ }
697
+
698
+ /** @__internal Test-only hook — clear programmatic + modules.ts route override state. */
699
+ export function resetApiRouteOverridesForTests(): void {
700
+ for (const key of Object.keys(programmaticApiRouteOverrides)) {
701
+ delete programmaticApiRouteOverrides[key]
702
+ }
703
+ for (const key of Object.keys(modulesConfigApiRouteOverrides)) {
704
+ delete modulesConfigApiRouteOverrides[key]
705
+ }
706
+ }
707
+
708
+ /** @__internal Test-only hook — clear every per-domain override store. */
709
+ export function resetModuleContractOverridesForTests(): void {
710
+ resetApiRouteOverridesForTests()
711
+ clearStore(pageRouteOverrideStore)
712
+ clearStore(subscriberOverrideStore)
713
+ clearStore(workerOverrideStore)
714
+ clearStore(cliOverrideStore)
715
+ clearStore(injectionWidgetOverrideStore)
716
+ clearStore(dashboardWidgetOverrideStore)
717
+ clearStore(componentOverrideStore)
718
+ clearStore(notificationTypeOverrideStore)
719
+ clearStore(notificationHandlerOverrideStore)
720
+ clearStore(apiInterceptorOverrideStore)
721
+ clearStore(commandInterceptorOverrideStore)
722
+ clearStore(responseEnricherOverrideStore)
723
+ clearStore(pageGuardOverrideStore)
724
+ clearStore(aclFeatureOverrideStore)
725
+ clearStore(encryptionMapOverrideStore)
726
+ clearStore(diOverrideStore)
727
+ for (const key of Object.keys(setupOverridesByModule)) delete setupOverridesByModule[key]
728
+ }
729
+
730
+ /**
731
+ * Resolve the final API-route override map. Resolution order (lowest →
732
+ * highest precedence):
733
+ *
734
+ * 1. `modules.ts` inline (applied via the dispatcher).
735
+ * 2. Programmatic (`applyApiRouteOverrides`).
736
+ */
737
+ export function composeApiRouteOverrides(): ApiRouteOverridesMap {
738
+ const modulesKeys = Object.keys(modulesConfigApiRouteOverrides)
739
+ const programmaticKeys = Object.keys(programmaticApiRouteOverrides)
740
+ if (modulesKeys.length === 0 && programmaticKeys.length === 0) {
741
+ return EMPTY_OVERRIDE_MAP as ApiRouteOverridesMap
742
+ }
743
+ const out: ApiRouteOverridesMap = {}
744
+ for (const k of modulesKeys) out[k] = modulesConfigApiRouteOverrides[k]
745
+ for (const k of programmaticKeys) out[k] = programmaticApiRouteOverrides[k]
746
+ return out
747
+ }
748
+
749
+ export function applyPageRouteOverrides(overrides: PageRouteOverridesMap): void {
750
+ applyStoreOverrides(pageRouteOverrideStore, 'programmatic', overrides as OverrideMap<PageRouteOverrideDefinition>, {
751
+ label: 'routes.pages',
752
+ normalizeKey: normalizePageRouteOverrideKey,
753
+ })
754
+ }
755
+
756
+ export function composePageRouteOverrides(): PageRouteOverridesMap {
757
+ return composeStore(pageRouteOverrideStore) as PageRouteOverridesMap
758
+ }
759
+
760
+ export function applySubscriberOverrides(overrides: SubscriberOverridesMap): void {
761
+ applyStoreOverrides(subscriberOverrideStore, 'programmatic', overrides as OverrideMap<ModuleSubscriber>, { label: 'events.subscribers' })
762
+ }
763
+
764
+ export function composeSubscriberOverrides(): SubscriberOverridesMap {
765
+ return composeStore(subscriberOverrideStore) as SubscriberOverridesMap
766
+ }
767
+
768
+ export function applyWorkerOverrides(overrides: WorkerOverridesMap): void {
769
+ applyStoreOverrides(workerOverrideStore, 'programmatic', overrides as OverrideMap<ModuleWorker>, { label: 'workers' })
770
+ }
771
+
772
+ export function composeWorkerOverrides(): WorkerOverridesMap {
773
+ return composeStore(workerOverrideStore) as WorkerOverridesMap
774
+ }
775
+
776
+ export function applyCliOverrides(overrides: CliOverridesMap): void {
777
+ applyStoreOverrides(cliOverrideStore, 'programmatic', overrides as OverrideMap<ModuleCli>, { label: 'cli' })
778
+ }
779
+
780
+ export function composeCliOverrides(): CliOverridesMap {
781
+ return composeStore(cliOverrideStore) as CliOverridesMap
782
+ }
783
+
784
+ export function applyInjectionWidgetOverrides(overrides: InjectionWidgetOverridesMap): void {
785
+ applyStoreOverrides(injectionWidgetOverrideStore, 'programmatic', overrides as OverrideMap<ModuleInjectionWidgetEntry>, { label: 'widgets.injection' })
786
+ }
787
+
788
+ export function composeInjectionWidgetOverrides(): InjectionWidgetOverridesMap {
789
+ return composeStore(injectionWidgetOverrideStore) as InjectionWidgetOverridesMap
790
+ }
791
+
792
+ export function applyDashboardWidgetOverrides(overrides: DashboardWidgetOverridesMap): void {
793
+ applyStoreOverrides(dashboardWidgetOverrideStore, 'programmatic', overrides as OverrideMap<ModuleDashboardWidgetEntry>, { label: 'widgets.dashboard' })
794
+ }
795
+
796
+ export function composeDashboardWidgetOverrides(): DashboardWidgetOverridesMap {
797
+ return composeStore(dashboardWidgetOverrideStore) as DashboardWidgetOverridesMap
798
+ }
799
+
800
+ export function applyComponentOverrides(overrides: ComponentOverridesMap): void {
801
+ applyStoreOverrides(componentOverrideStore, 'programmatic', overrides as OverrideMap<ComponentOverride | ComponentOverride[]>, { label: 'widgets.components' })
802
+ }
803
+
804
+ export function composeComponentOverrides(): ComponentOverridesMap {
805
+ return composeStore(componentOverrideStore) as ComponentOverridesMap
806
+ }
807
+
808
+ export function applyNotificationTypeOverrides(overrides: NotificationTypeOverridesMap): void {
809
+ applyStoreOverrides(notificationTypeOverrideStore, 'programmatic', overrides as OverrideMap<NotificationTypeDefinition>, { label: 'notifications.types' })
810
+ }
811
+
812
+ export function composeNotificationTypeOverrides(): NotificationTypeOverridesMap {
813
+ return composeStore(notificationTypeOverrideStore) as NotificationTypeOverridesMap
814
+ }
815
+
816
+ export function applyNotificationHandlerOverrides(overrides: NotificationHandlerOverridesMap): void {
817
+ applyStoreOverrides(notificationHandlerOverrideStore, 'programmatic', overrides as OverrideMap<NotificationHandler>, { label: 'notifications.handlers' })
818
+ }
819
+
820
+ export function composeNotificationHandlerOverrides(): NotificationHandlerOverridesMap {
821
+ return composeStore(notificationHandlerOverrideStore) as NotificationHandlerOverridesMap
822
+ }
823
+
824
+ export function applyApiInterceptorOverrides(overrides: ApiInterceptorOverridesMap): void {
825
+ applyStoreOverrides(apiInterceptorOverrideStore, 'programmatic', overrides as OverrideMap<ApiInterceptor>, { label: 'interceptors' })
826
+ }
827
+
828
+ export function composeApiInterceptorOverrides(): ApiInterceptorOverridesMap {
829
+ return composeStore(apiInterceptorOverrideStore) as ApiInterceptorOverridesMap
830
+ }
831
+
832
+ export function applyCommandInterceptorOverrides(overrides: CommandInterceptorOverridesMap): void {
833
+ applyStoreOverrides(commandInterceptorOverrideStore, 'programmatic', overrides as OverrideMap<CommandInterceptor>, { label: 'commandInterceptors' })
834
+ }
835
+
836
+ export function composeCommandInterceptorOverrides(): CommandInterceptorOverridesMap {
837
+ return composeStore(commandInterceptorOverrideStore) as CommandInterceptorOverridesMap
838
+ }
839
+
840
+ export function applyResponseEnricherOverrides(overrides: ResponseEnricherOverridesMap): void {
841
+ applyStoreOverrides(responseEnricherOverrideStore, 'programmatic', overrides as OverrideMap<ResponseEnricher>, { label: 'enrichers' })
842
+ }
843
+
844
+ export function composeResponseEnricherOverrides(): ResponseEnricherOverridesMap {
845
+ return composeStore(responseEnricherOverrideStore) as ResponseEnricherOverridesMap
846
+ }
847
+
848
+ export function applyPageGuardOverrides(overrides: PageGuardOverridesMap): void {
849
+ applyStoreOverrides(pageGuardOverrideStore, 'programmatic', overrides as OverrideMap<PageRouteMiddleware>, { label: 'guards' })
850
+ }
851
+
852
+ export function composePageGuardOverrides(): PageGuardOverridesMap {
853
+ return composeStore(pageGuardOverrideStore) as PageGuardOverridesMap
854
+ }
855
+
856
+ export function applyAclFeatureOverrides(overrides: AclFeatureOverridesMap): void {
857
+ applyStoreOverrides(aclFeatureOverrideStore, 'programmatic', overrides as OverrideMap<Exclude<AclFeatureOverride, null>>, { label: 'acl.features' })
858
+ }
859
+
860
+ export function composeAclFeatureOverrides(): AclFeatureOverridesMap {
861
+ return composeStore(aclFeatureOverrideStore) as AclFeatureOverridesMap
862
+ }
863
+
864
+ export function applyEncryptionMapOverrides(overrides: EncryptionMapOverridesMap): void {
865
+ applyStoreOverrides(encryptionMapOverrideStore, 'programmatic', overrides as OverrideMap<ModuleEncryptionMap>, { label: 'encryption.maps' })
866
+ }
867
+
868
+ export function composeEncryptionMapOverrides(): EncryptionMapOverridesMap {
869
+ return composeStore(encryptionMapOverrideStore) as EncryptionMapOverridesMap
870
+ }
871
+
872
+ export function applyDiOverrides(overrides: DiOverridesMap): void {
873
+ applyStoreOverrides(diOverrideStore, 'programmatic', overrides as OverrideMap<Exclude<DiBindingOverride, null>>, { label: 'di' })
874
+ }
875
+
876
+ export function composeDiOverrides(): DiOverridesMap {
877
+ return composeStore(diOverrideStore) as DiOverridesMap
878
+ }
879
+
880
+ /**
881
+ * Apply an API-route override map to a list of registered manifest entries.
882
+ * Returns a NEW array — never mutates input.
883
+ *
884
+ * Behavior:
885
+ * - For each entry, walk every method in `entry.methods`.
886
+ * - If the override key `'METHOD path'` is `null`, drop that method from
887
+ * the entry's `methods` array.
888
+ * - If the override key is a definition, wrap `entry.load()` to return a
889
+ * module that exposes the override handler at `module[METHOD]` (top-level)
890
+ * and the override metadata at `module.metadata[METHOD]`.
891
+ * - If every method on the entry is disabled, the entry is dropped.
892
+ * - Overrides naming a key that does not match any entry log a single
893
+ * warning so an operator notices a stale override.
894
+ */
895
+ export function applyApiOverridesToManifests<T extends ApiRouteManifestEntry>(
896
+ routes: readonly T[],
897
+ overrides: Readonly<ApiRouteOverridesMap>,
898
+ ): T[] {
899
+ if (!routes || routes.length === 0) return Array.from(routes)
900
+ const overrideKeys = Object.keys(overrides)
901
+ if (overrideKeys.length === 0) return Array.from(routes)
902
+
903
+ const consumedKeys = new Set<string>()
904
+ const result: T[] = []
905
+
906
+ for (const entry of routes) {
907
+ const path = entry.path
908
+ const remainingMethods: HttpMethod[] = []
909
+ const methodOverrides = new Map<HttpMethod, ApiRouteOverrideDefinition>()
910
+
911
+ for (const method of entry.methods) {
912
+ const manifestKey = `${method} ${path}`
913
+ const publicKey = path.startsWith('/api/')
914
+ ? manifestKey
915
+ : `${method} /api${path === '/' ? '' : path}`
916
+ const key = Object.prototype.hasOwnProperty.call(overrides, manifestKey)
917
+ ? manifestKey
918
+ : Object.prototype.hasOwnProperty.call(overrides, publicKey)
919
+ ? publicKey
920
+ : null
921
+ if (!key) {
922
+ remainingMethods.push(method)
923
+ continue
924
+ }
925
+ consumedKeys.add(key)
926
+ const value = overrides[key]
927
+ if (value === null) continue
928
+ if (value && typeof value === 'object' && typeof value.handler === 'function') {
929
+ methodOverrides.set(method, value)
930
+ remainingMethods.push(method)
931
+ } else {
932
+ console.warn(
933
+ `[Module Overrides] Skipping malformed routes.api override for "${key}" — expected { handler, metadata? } or null.`,
934
+ )
935
+ remainingMethods.push(method)
936
+ }
937
+ }
938
+
939
+ if (remainingMethods.length === 0) continue
940
+
941
+ const methodsChanged = remainingMethods.length !== entry.methods.length
942
+ const hasReplacements = methodOverrides.size > 0
943
+
944
+ if (!methodsChanged && !hasReplacements) {
945
+ result.push(entry)
946
+ continue
947
+ }
948
+
949
+ const originalLoad = entry.load
950
+ const wrappedLoad: T['load'] = hasReplacements
951
+ ? async () => {
952
+ const original = await originalLoad()
953
+ const out: Record<string, unknown> = { ...original }
954
+ const baseMetadata = original && typeof original === 'object' && original.metadata !== undefined
955
+ ? original.metadata
956
+ : null
957
+ const mergedMetadata: Record<string, unknown> = baseMetadata && typeof baseMetadata === 'object'
958
+ ? { ...(baseMetadata as Record<string, unknown>) }
959
+ : {}
960
+ for (const [method, def] of methodOverrides) {
961
+ out[method] = def.handler
962
+ if (def.metadata !== undefined) mergedMetadata[method] = def.metadata
963
+ }
964
+ if (Object.keys(mergedMetadata).length > 0) out.metadata = mergedMetadata
965
+ return out
966
+ }
967
+ : originalLoad
968
+
969
+ result.push({ ...entry, methods: remainingMethods, load: wrappedLoad })
970
+ }
971
+
972
+ for (const key of overrideKeys) {
973
+ if (!consumedKeys.has(key)) {
974
+ console.warn(
975
+ `[Module Overrides] routes.api override "${key}" did not match any registered API route — override skipped.`,
976
+ )
977
+ }
978
+ }
979
+
980
+ return result
981
+ }
982
+
983
+ export function applyPageOverridesToManifests<T extends BackendRouteManifestEntry | FrontendRouteManifestEntry>(
984
+ routes: readonly T[],
985
+ overrides: Readonly<PageRouteOverridesMap>,
986
+ kind: 'backend' | 'frontend',
987
+ ): T[] {
988
+ if (!routes || routes.length === 0) return Array.from(routes)
989
+ const normalizedOverrides: PageRouteOverridesMap = {}
990
+ for (const [rawKey, value] of Object.entries(overrides)) {
991
+ const key = normalizePageRouteOverrideKey(rawKey)
992
+ if (!key) {
993
+ console.warn(
994
+ `[Module Overrides] Skipping malformed routes.pages key "${rawKey}" — expected "/backend/path" or "/frontend/path".`,
995
+ )
996
+ continue
997
+ }
998
+ normalizedOverrides[key] = value
999
+ }
1000
+ const overrideKeys = Object.keys(normalizedOverrides).filter((key) => key.startsWith(`${kind}:`))
1001
+ if (overrideKeys.length === 0) return Array.from(routes)
1002
+
1003
+ const consumed = new Set<string>()
1004
+ const result: T[] = []
1005
+
1006
+ for (const entry of routes) {
1007
+ const path = entry.pattern ?? entry.path ?? '/'
1008
+ const key = `${kind}:${path.replace(/\/+$/, '') || '/'}`
1009
+ if (!Object.prototype.hasOwnProperty.call(normalizedOverrides, key)) {
1010
+ result.push(entry)
1011
+ continue
1012
+ }
1013
+
1014
+ consumed.add(key)
1015
+ const override = normalizedOverrides[key]
1016
+ if (override === null) continue
1017
+ if (!override || typeof override !== 'object') {
1018
+ console.warn(
1019
+ `[Module Overrides] Skipping malformed routes.pages override for "${key}" — expected { load?, Component?, metadata? } or null.`,
1020
+ )
1021
+ result.push(entry)
1022
+ continue
1023
+ }
1024
+
1025
+ const hasLoad = typeof override.load === 'function'
1026
+ const hasComponent = typeof override.Component === 'function'
1027
+ if (!hasLoad && !hasComponent && override.metadata === undefined) {
1028
+ console.warn(
1029
+ `[Module Overrides] Skipping malformed routes.pages override for "${key}" — expected a loader, component, metadata, or null.`,
1030
+ )
1031
+ result.push(entry)
1032
+ continue
1033
+ }
1034
+
1035
+ const metadata = override.metadata && typeof override.metadata === 'object'
1036
+ ? override.metadata
1037
+ : {}
1038
+ const load = hasLoad
1039
+ ? override.load!
1040
+ : hasComponent
1041
+ ? async () => override.Component!
1042
+ : entry.load
1043
+
1044
+ result.push({ ...entry, ...metadata, load })
1045
+ }
1046
+
1047
+ warnStaleOverrides('routes.pages', Object.fromEntries(
1048
+ overrideKeys.map((key) => [key, normalizedOverrides[key]]),
1049
+ ), consumed)
1050
+ return result
1051
+ }
1052
+
1053
+ function applyEntryListOverrides<TEntry extends { moduleId: string }, TValue>(
1054
+ entries: readonly TEntry[] | undefined,
1055
+ overrides: Readonly<OverrideMap<TValue>>,
1056
+ options: {
1057
+ listKey: keyof TEntry
1058
+ label: string
1059
+ getId: (value: TValue) => string | null
1060
+ isReplacement: (value: unknown) => value is TValue
1061
+ },
1062
+ ): TEntry[] | undefined {
1063
+ // Fast path: when no overrides are registered, return the input directly.
1064
+ // All known callers iterate the result (.forEach/.flatMap/.map/storage),
1065
+ // never mutate it. This avoids a per-request Array.from copy on hot paths
1066
+ // like executePageMiddleware → applyPageGuardOverridesToEntries.
1067
+ if (!entries || Object.keys(overrides).length === 0) return entries as TEntry[] | undefined
1068
+ const consumed = new Set<string>()
1069
+ let changed = false
1070
+ const result = entries.map((entry) => {
1071
+ const list = entry[options.listKey]
1072
+ if (!Array.isArray(list)) return entry
1073
+ const applied = applyArrayOverrides(list as TValue[], overrides, {
1074
+ label: options.label,
1075
+ getId: options.getId,
1076
+ isReplacement: options.isReplacement,
1077
+ })
1078
+ for (const key of applied.consumed) consumed.add(key)
1079
+ if (!applied.changed) return entry
1080
+ changed = true
1081
+ return { ...entry, [options.listKey]: applied.items ?? [] }
1082
+ })
1083
+ warnStaleOverrides(options.label, overrides, consumed)
1084
+ return changed ? result : Array.from(entries)
1085
+ }
1086
+
1087
+ export function applyInjectionWidgetOverridesToEntries(
1088
+ entries: readonly ModuleInjectionWidgetEntry[],
1089
+ overrides: Readonly<InjectionWidgetOverridesMap> = composeInjectionWidgetOverrides(),
1090
+ ): ModuleInjectionWidgetEntry[] {
1091
+ const applied = applyArrayOverrides(entries, overrides as OverrideMap<ModuleInjectionWidgetEntry>, {
1092
+ label: 'widgets.injection',
1093
+ getId: (entry) => entry.key,
1094
+ isReplacement: isInjectionWidgetEntry,
1095
+ })
1096
+ warnStaleOverrides('widgets.injection', overrides, applied.consumed)
1097
+ return applied.items ?? []
1098
+ }
1099
+
1100
+ export function applyDashboardWidgetOverridesToEntries(
1101
+ entries: readonly ModuleDashboardWidgetEntry[],
1102
+ overrides: Readonly<DashboardWidgetOverridesMap> = composeDashboardWidgetOverrides(),
1103
+ ): ModuleDashboardWidgetEntry[] {
1104
+ const applied = applyArrayOverrides(entries, overrides as OverrideMap<ModuleDashboardWidgetEntry>, {
1105
+ label: 'widgets.dashboard',
1106
+ getId: (entry) => entry.key,
1107
+ isReplacement: isDashboardWidgetEntry,
1108
+ })
1109
+ warnStaleOverrides('widgets.dashboard', overrides, applied.consumed)
1110
+ return applied.items ?? []
1111
+ }
1112
+
1113
+ export function applyWorkerOverridesToDescriptors<T extends { id: string; queue: string; concurrency: number; handler: unknown }>(
1114
+ entries: readonly T[],
1115
+ overrides: Readonly<WorkerOverridesMap> = composeWorkerOverrides(),
1116
+ ): T[] {
1117
+ const applied = applyArrayOverrides(entries, overrides as OverrideMap<T>, {
1118
+ label: 'workers',
1119
+ getId: (entry) => entry.id,
1120
+ isReplacement: (value): value is T =>
1121
+ !!value
1122
+ && typeof value === 'object'
1123
+ && typeof (value as { id?: unknown }).id === 'string'
1124
+ && typeof (value as { queue?: unknown }).queue === 'string'
1125
+ && typeof (value as { concurrency?: unknown }).concurrency === 'number'
1126
+ && typeof (value as { handler?: unknown }).handler === 'function',
1127
+ })
1128
+ warnStaleOverrides('workers', overrides, applied.consumed)
1129
+ return applied.items ?? []
1130
+ }
1131
+
1132
+ export function applyInjectionWidgetOverridesToTables(
1133
+ tables: readonly { moduleId: string; table: ModuleInjectionTable }[],
1134
+ overrides: Readonly<InjectionWidgetOverridesMap> = composeInjectionWidgetOverrides(),
1135
+ ): Array<{ moduleId: string; table: ModuleInjectionTable }> {
1136
+ const disabled = new Set(Object.entries(overrides).filter(([, value]) => value === null).map(([key]) => key))
1137
+ if (disabled.size === 0) return Array.from(tables)
1138
+
1139
+ const filterSlot = (slot: unknown): unknown => {
1140
+ if (typeof slot === 'string') return disabled.has(slot) ? null : slot
1141
+ if (slot && typeof slot === 'object') {
1142
+ const widgetId = (slot as { widgetId?: unknown }).widgetId
1143
+ return typeof widgetId === 'string' && disabled.has(widgetId) ? null : slot
1144
+ }
1145
+ return slot
1146
+ }
1147
+
1148
+ return tables.map((entry) => {
1149
+ const nextTable: ModuleInjectionTable = {}
1150
+ for (const [spotId, rawSlot] of Object.entries(entry.table)) {
1151
+ const filtered = Array.isArray(rawSlot)
1152
+ ? rawSlot.map(filterSlot).filter((slot): slot is NonNullable<typeof slot> => slot !== null)
1153
+ : filterSlot(rawSlot)
1154
+ if (Array.isArray(filtered)) {
1155
+ if (filtered.length > 0) nextTable[spotId] = filtered as ModuleInjectionTable[string]
1156
+ } else if (filtered !== null) {
1157
+ nextTable[spotId] = filtered as ModuleInjectionTable[string]
1158
+ }
1159
+ }
1160
+ return { ...entry, table: nextTable }
1161
+ })
1162
+ }
1163
+
1164
+ export function applyComponentOverridesToEntries(
1165
+ entries: readonly { moduleId: string; componentOverrides: ComponentOverride[] }[],
1166
+ overrides: Readonly<ComponentOverridesMap> = composeComponentOverrides(),
1167
+ ): Array<{ moduleId: string; componentOverrides: ComponentOverride[] }> {
1168
+ const overrideKeys = Object.keys(overrides)
1169
+ if (overrideKeys.length === 0) return Array.from(entries)
1170
+ const consumed = new Set<string>()
1171
+ const additions: ComponentOverride[] = []
1172
+ const result = entries.map((entry) => {
1173
+ const next = entry.componentOverrides.filter((override) => {
1174
+ const id = override.target.componentId
1175
+ if (!Object.prototype.hasOwnProperty.call(overrides, id)) return true
1176
+ consumed.add(id)
1177
+ return false
1178
+ })
1179
+ return { ...entry, componentOverrides: next }
1180
+ })
1181
+ for (const key of overrideKeys) {
1182
+ const value = overrides[key]
1183
+ if (value === null) continue
1184
+ if (!isComponentOverrideValue(value)) {
1185
+ console.warn(`[Module Overrides] Skipping malformed widgets.components override for "${key}" — expected ComponentOverride, ComponentOverride[], or null.`)
1186
+ continue
1187
+ }
1188
+ const values = Array.isArray(value) ? value : [value]
1189
+ for (const override of values) {
1190
+ if (override.target.componentId !== key) {
1191
+ console.warn(`[Module Overrides] Skipping malformed widgets.components override for "${key}" — target.componentId must match the override key.`)
1192
+ continue
1193
+ }
1194
+ additions.push(override)
1195
+ consumed.add(key)
1196
+ }
1197
+ }
1198
+ if (additions.length > 0) {
1199
+ result.push({ moduleId: 'overrides', componentOverrides: additions })
1200
+ }
1201
+ warnStaleOverrides('widgets.components', overrides, consumed)
1202
+ return result
1203
+ }
1204
+
1205
+ export function applyNotificationTypeOverridesToEntries(
1206
+ entries: readonly { moduleId: string; types: NotificationTypeDefinition[] }[],
1207
+ overrides: Readonly<NotificationTypeOverridesMap> = composeNotificationTypeOverrides(),
1208
+ ): Array<{ moduleId: string; types: NotificationTypeDefinition[] }> {
1209
+ return applyEntryListOverrides(entries, overrides as OverrideMap<NotificationTypeDefinition>, {
1210
+ listKey: 'types',
1211
+ label: 'notifications.types',
1212
+ getId: (entry) => entry.type,
1213
+ isReplacement: isNotificationType,
1214
+ }) ?? []
1215
+ }
1216
+
1217
+ export function applyNotificationHandlerOverridesToEntries(
1218
+ entries: readonly { moduleId: string; handlers: NotificationHandler[] }[],
1219
+ overrides: Readonly<NotificationHandlerOverridesMap> = composeNotificationHandlerOverrides(),
1220
+ ): Array<{ moduleId: string; handlers: NotificationHandler[] }> {
1221
+ return applyEntryListOverrides(entries, overrides as OverrideMap<NotificationHandler>, {
1222
+ listKey: 'handlers',
1223
+ label: 'notifications.handlers',
1224
+ getId: (entry) => entry.id,
1225
+ isReplacement: isNotificationHandler,
1226
+ }) ?? []
1227
+ }
1228
+
1229
+ export function applyApiInterceptorOverridesToEntries(
1230
+ entries: readonly { moduleId: string; interceptors: ApiInterceptor[] }[],
1231
+ overrides: Readonly<ApiInterceptorOverridesMap> = composeApiInterceptorOverrides(),
1232
+ ): Array<{ moduleId: string; interceptors: ApiInterceptor[] }> {
1233
+ return applyEntryListOverrides(entries, overrides as OverrideMap<ApiInterceptor>, {
1234
+ listKey: 'interceptors',
1235
+ label: 'interceptors',
1236
+ getId: (entry) => entry.id,
1237
+ isReplacement: isApiInterceptor,
1238
+ }) ?? []
1239
+ }
1240
+
1241
+ export function applyCommandInterceptorOverridesToEntries(
1242
+ entries: readonly { moduleId: string; interceptors: CommandInterceptor[] }[],
1243
+ overrides: Readonly<CommandInterceptorOverridesMap> = composeCommandInterceptorOverrides(),
1244
+ ): Array<{ moduleId: string; interceptors: CommandInterceptor[] }> {
1245
+ return applyEntryListOverrides(entries, overrides as OverrideMap<CommandInterceptor>, {
1246
+ listKey: 'interceptors',
1247
+ label: 'commandInterceptors',
1248
+ getId: (entry) => entry.id,
1249
+ isReplacement: isCommandInterceptor,
1250
+ }) ?? []
1251
+ }
1252
+
1253
+ export function applyResponseEnricherOverridesToEntries(
1254
+ entries: readonly { moduleId: string; enrichers: ResponseEnricher[] }[],
1255
+ overrides: Readonly<ResponseEnricherOverridesMap> = composeResponseEnricherOverrides(),
1256
+ ): Array<{ moduleId: string; enrichers: ResponseEnricher[] }> {
1257
+ return applyEntryListOverrides(entries, overrides as OverrideMap<ResponseEnricher>, {
1258
+ listKey: 'enrichers',
1259
+ label: 'enrichers',
1260
+ getId: (entry) => entry.id,
1261
+ isReplacement: isResponseEnricher,
1262
+ }) ?? []
1263
+ }
1264
+
1265
+ export function applyPageGuardOverridesToEntries(
1266
+ entries: readonly PageMiddlewareRegistryEntry[],
1267
+ overrides: Readonly<PageGuardOverridesMap> = composePageGuardOverrides(),
1268
+ ): PageMiddlewareRegistryEntry[] {
1269
+ return applyEntryListOverrides(entries, overrides as OverrideMap<PageRouteMiddleware>, {
1270
+ listKey: 'middleware',
1271
+ label: 'guards',
1272
+ getId: (entry) => entry.id,
1273
+ isReplacement: isPageRouteMiddleware,
1274
+ }) ?? []
1275
+ }
1276
+
1277
+ function applySetupOverride(setup: ModuleSetupConfig | undefined, override: SetupOverridesShape | undefined): ModuleSetupConfig | undefined {
1278
+ if (!override) return setup
1279
+ const next: ModuleSetupConfig = { ...(setup ?? {}) }
1280
+ if (override.defaultRoleFeatures) {
1281
+ next.defaultRoleFeatures = Object.fromEntries(
1282
+ Object.entries(override.defaultRoleFeatures).map(([role, features]) => [role, Array.from(features)]),
1283
+ )
1284
+ }
1285
+ if (override.defaultCustomerRoleFeatures) {
1286
+ next.defaultCustomerRoleFeatures = Object.fromEntries(
1287
+ Object.entries(override.defaultCustomerRoleFeatures).map(([role, features]) => [role, Array.from(features)]),
1288
+ )
1289
+ }
1290
+ if (override.seedDefaults === false) delete next.seedDefaults
1291
+ if (override.seedExamples === false) delete next.seedExamples
1292
+ if (override.onTenantCreated === false) delete next.onTenantCreated
1293
+ return next
1294
+ }
1295
+
1296
+ export function applyModuleOverridesToModules(modules: readonly Module[]): Module[] {
1297
+ const subscriberOverrides = composeSubscriberOverrides()
1298
+ const workerOverrides = composeWorkerOverrides()
1299
+ const cliOverrides = composeCliOverrides()
1300
+ const aclOverrides = composeAclFeatureOverrides()
1301
+ const encryptionOverrides = composeEncryptionMapOverrides()
1302
+ const hasSetupOverrides = Object.keys(setupOverridesByModule).length > 0
1303
+ const hasAny =
1304
+ Object.keys(subscriberOverrides).length > 0
1305
+ || Object.keys(workerOverrides).length > 0
1306
+ || Object.keys(cliOverrides).length > 0
1307
+ || Object.keys(aclOverrides).length > 0
1308
+ || Object.keys(encryptionOverrides).length > 0
1309
+ || hasSetupOverrides
1310
+
1311
+ if (!hasAny) return modules as Module[]
1312
+
1313
+ const subscriberConsumed = new Set<string>()
1314
+ const workerConsumed = new Set<string>()
1315
+ const cliConsumed = new Set<string>()
1316
+ const aclConsumed = new Set<string>()
1317
+ const encryptionConsumed = new Set<string>()
1318
+
1319
+ const result = modules.map((module) => {
1320
+ let next = module
1321
+
1322
+ const subscribers = applyArrayOverrides(module.subscribers, subscriberOverrides as OverrideMap<ModuleSubscriber>, {
1323
+ label: 'events.subscribers',
1324
+ getId: (entry) => entry.id,
1325
+ isReplacement: isModuleSubscriber,
1326
+ })
1327
+ for (const key of subscribers.consumed) subscriberConsumed.add(key)
1328
+ if (subscribers.changed) next = { ...next, subscribers: subscribers.items }
1329
+
1330
+ const workers = applyArrayOverrides(module.workers, workerOverrides as OverrideMap<ModuleWorker>, {
1331
+ label: 'workers',
1332
+ getId: (entry) => entry.id,
1333
+ isReplacement: isModuleWorker,
1334
+ })
1335
+ for (const key of workers.consumed) workerConsumed.add(key)
1336
+ if (workers.changed) next = { ...next, workers: workers.items }
1337
+
1338
+ const cli = applyArrayOverrides(module.cli, cliOverrides as OverrideMap<ModuleCli>, {
1339
+ label: 'cli',
1340
+ getId: (entry) => entry.command,
1341
+ isReplacement: isModuleCli,
1342
+ })
1343
+ for (const key of cli.consumed) cliConsumed.add(key)
1344
+ if (cli.changed) next = { ...next, cli: cli.items }
1345
+
1346
+ const features = applyArrayOverrides(module.features, aclOverrides as OverrideMap<NonNullable<Module['features']>[number]>, {
1347
+ label: 'acl.features',
1348
+ getId: getFeatureId,
1349
+ isReplacement: (value): value is NonNullable<Module['features']>[number] => getFeatureId(value) !== null,
1350
+ })
1351
+ for (const key of features.consumed) aclConsumed.add(key)
1352
+ if (features.changed) next = { ...next, features: features.items }
1353
+
1354
+ const maps = applyArrayOverrides(module.defaultEncryptionMaps, encryptionOverrides as OverrideMap<ModuleEncryptionMap>, {
1355
+ label: 'encryption.maps',
1356
+ getId: (entry) => entry.entityId,
1357
+ isReplacement: isEncryptionMap,
1358
+ })
1359
+ for (const key of maps.consumed) encryptionConsumed.add(key)
1360
+ if (maps.changed) next = { ...next, defaultEncryptionMaps: maps.items }
1361
+
1362
+ const setupOverride = setupOverridesByModule[module.id]
1363
+ if (setupOverride) {
1364
+ next = { ...next, setup: applySetupOverride(module.setup, setupOverride) }
1365
+ }
1366
+
1367
+ return next
1368
+ })
1369
+
1370
+ warnStaleOverrides('events.subscribers', subscriberOverrides, subscriberConsumed)
1371
+ warnStaleOverrides('workers', workerOverrides, workerConsumed)
1372
+ warnStaleOverrides('cli', cliOverrides, cliConsumed)
1373
+ warnStaleOverrides('acl.features', aclOverrides, aclConsumed)
1374
+ warnStaleOverrides('encryption.maps', encryptionOverrides, encryptionConsumed)
1375
+
1376
+ return result
1377
+ }
1378
+
1379
+ export function applyDiOverridesToContainer(container: {
1380
+ register: (registrations: Record<string, unknown>) => unknown
1381
+ unregister?: (name: string) => unknown
1382
+ registrations?: Record<string, unknown>
1383
+ }): void {
1384
+ const overrides = composeDiOverrides()
1385
+ for (const [key, value] of Object.entries(overrides)) {
1386
+ if (value === null) {
1387
+ if (typeof container.unregister === 'function') {
1388
+ container.unregister(key)
1389
+ } else if (container.registrations && key in container.registrations) {
1390
+ delete container.registrations[key]
1391
+ }
1392
+ continue
1393
+ }
1394
+ if (value && typeof value === 'object' && typeof (value as DiBindingOverrideDefinition).register === 'function') {
1395
+ ;(value as DiBindingOverrideDefinition).register(container, key)
1396
+ continue
1397
+ }
1398
+ container.register({ [key]: value })
1399
+ }
1400
+ }
1401
+
1402
+ /**
1403
+ * Dispatcher applier for the `'routes'` domain. Buckets every entry's
1404
+ * `overrides.routes.api` and `overrides.routes.pages` into the
1405
+ * `modules.ts` tier.
1406
+ */
1407
+ function routesOverridesApplier(
1408
+ entries: ReadonlyArray<ModuleOverrideEntry<RoutesOverridesShape>>,
1409
+ ): void {
1410
+ for (const entry of entries) {
1411
+ const shape = entry.overrides
1412
+ if (!shape || typeof shape !== 'object') continue
1413
+ const api = shape.api
1414
+ if (api && typeof api === 'object') {
1415
+ for (const [rawKey, value] of Object.entries(api)) {
1416
+ const key = normalizeApiRouteOverrideKey(rawKey)
1417
+ if (!key) {
1418
+ console.warn(
1419
+ `[Module Overrides] Skipping malformed routes.api key "${rawKey}" — expected "METHOD /api/path".`,
1420
+ )
1421
+ continue
1422
+ }
1423
+ modulesConfigApiRouteOverrides[key] = value as ApiRouteOverride
1424
+ }
1425
+ }
1426
+ const pages = shape.pages
1427
+ if (pages && typeof pages === 'object' && Object.keys(pages).length > 0) {
1428
+ applyStoreOverrides(pageRouteOverrideStore, 'modules', pages as OverrideMap<PageRouteOverrideDefinition>, {
1429
+ label: 'routes.pages',
1430
+ normalizeKey: normalizePageRouteOverrideKey,
1431
+ })
1432
+ }
1433
+ }
1434
+ }
1435
+
1436
+ function eventsOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<EventsOverridesShape>>): void {
1437
+ for (const entry of entries) {
1438
+ const subscribers = entry.overrides?.subscribers
1439
+ applyStoreOverrides(subscriberOverrideStore, 'modules', subscribers as OverrideMap<ModuleSubscriber>, { label: 'events.subscribers' })
1440
+ }
1441
+ }
1442
+
1443
+ function workersOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<WorkerOverridesMap>>): void {
1444
+ for (const entry of entries) {
1445
+ applyStoreOverrides(workerOverrideStore, 'modules', entry.overrides as OverrideMap<ModuleWorker>, { label: 'workers' })
1446
+ }
1447
+ }
1448
+
1449
+ function widgetsOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<WidgetsOverridesShape>>): void {
1450
+ for (const entry of entries) {
1451
+ applyStoreOverrides(injectionWidgetOverrideStore, 'modules', entry.overrides?.injection as OverrideMap<ModuleInjectionWidgetEntry>, { label: 'widgets.injection' })
1452
+ applyStoreOverrides(componentOverrideStore, 'modules', entry.overrides?.components as OverrideMap<ComponentOverride | ComponentOverride[]>, { label: 'widgets.components' })
1453
+ applyStoreOverrides(dashboardWidgetOverrideStore, 'modules', entry.overrides?.dashboard as OverrideMap<ModuleDashboardWidgetEntry>, { label: 'widgets.dashboard' })
1454
+ }
1455
+ }
1456
+
1457
+ function notificationsOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<NotificationsOverridesShape>>): void {
1458
+ for (const entry of entries) {
1459
+ applyStoreOverrides(notificationTypeOverrideStore, 'modules', entry.overrides?.types as OverrideMap<NotificationTypeDefinition>, { label: 'notifications.types' })
1460
+ applyStoreOverrides(notificationHandlerOverrideStore, 'modules', entry.overrides?.handlers as OverrideMap<NotificationHandler>, { label: 'notifications.handlers' })
1461
+ }
1462
+ }
1463
+
1464
+ function interceptorsOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<ApiInterceptorOverridesMap>>): void {
1465
+ for (const entry of entries) {
1466
+ applyStoreOverrides(apiInterceptorOverrideStore, 'modules', entry.overrides as OverrideMap<ApiInterceptor>, { label: 'interceptors' })
1467
+ }
1468
+ }
1469
+
1470
+ function commandInterceptorsOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<CommandInterceptorOverridesMap>>): void {
1471
+ for (const entry of entries) {
1472
+ applyStoreOverrides(commandInterceptorOverrideStore, 'modules', entry.overrides as OverrideMap<CommandInterceptor>, { label: 'commandInterceptors' })
1473
+ }
1474
+ }
1475
+
1476
+ function enrichersOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<ResponseEnricherOverridesMap>>): void {
1477
+ for (const entry of entries) {
1478
+ applyStoreOverrides(responseEnricherOverrideStore, 'modules', entry.overrides as OverrideMap<ResponseEnricher>, { label: 'enrichers' })
1479
+ }
1480
+ }
1481
+
1482
+ function guardsOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<PageGuardOverridesMap>>): void {
1483
+ for (const entry of entries) {
1484
+ applyStoreOverrides(pageGuardOverrideStore, 'modules', entry.overrides as OverrideMap<PageRouteMiddleware>, { label: 'guards' })
1485
+ }
1486
+ }
1487
+
1488
+ function cliOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<CliOverridesMap>>): void {
1489
+ for (const entry of entries) {
1490
+ applyStoreOverrides(cliOverrideStore, 'modules', entry.overrides as OverrideMap<ModuleCli>, { label: 'cli' })
1491
+ }
1492
+ }
1493
+
1494
+ function setupOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<SetupOverridesShape>>): void {
1495
+ for (const entry of entries) {
1496
+ if (!entry.overrides || typeof entry.overrides !== 'object') continue
1497
+ setupOverridesByModule[entry.moduleId] = entry.overrides
1498
+ }
1499
+ }
1500
+
1501
+ function aclOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<AclOverridesShape>>): void {
1502
+ for (const entry of entries) {
1503
+ applyStoreOverrides(aclFeatureOverrideStore, 'modules', entry.overrides?.features as OverrideMap<Exclude<AclFeatureOverride, null>>, { label: 'acl.features' })
1504
+ }
1505
+ }
1506
+
1507
+ function diOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<DiOverridesMap>>): void {
1508
+ for (const entry of entries) {
1509
+ applyStoreOverrides(diOverrideStore, 'modules', entry.overrides as OverrideMap<Exclude<DiBindingOverride, null>>, { label: 'di' })
1510
+ }
1511
+ }
1512
+
1513
+ function encryptionOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<EncryptionOverridesShape>>): void {
1514
+ for (const entry of entries) {
1515
+ applyStoreOverrides(encryptionMapOverrideStore, 'modules', entry.overrides?.maps as OverrideMap<ModuleEncryptionMap>, { label: 'encryption.maps' })
1516
+ }
1517
+ }
1518
+
1519
+ function registerBuiltInModuleOverrideAppliers(): void {
1520
+ registerModuleOverrideApplier<RoutesOverridesShape>('routes', routesOverridesApplier)
1521
+ registerModuleOverrideApplier<EventsOverridesShape>('events', eventsOverridesApplier)
1522
+ registerModuleOverrideApplier<WorkerOverridesMap>('workers', workersOverridesApplier)
1523
+ registerModuleOverrideApplier<WidgetsOverridesShape>('widgets', widgetsOverridesApplier)
1524
+ registerModuleOverrideApplier<NotificationsOverridesShape>('notifications', notificationsOverridesApplier)
1525
+ registerModuleOverrideApplier<ApiInterceptorOverridesMap>('interceptors', interceptorsOverridesApplier)
1526
+ registerModuleOverrideApplier<CommandInterceptorOverridesMap>('commandInterceptors', commandInterceptorsOverridesApplier)
1527
+ registerModuleOverrideApplier<ResponseEnricherOverridesMap>('enrichers', enrichersOverridesApplier)
1528
+ registerModuleOverrideApplier<PageGuardOverridesMap>('guards', guardsOverridesApplier)
1529
+ registerModuleOverrideApplier<CliOverridesMap>('cli', cliOverridesApplier)
1530
+ registerModuleOverrideApplier<SetupOverridesShape>('setup', setupOverridesApplier)
1531
+ registerModuleOverrideApplier<AclOverridesShape>('acl', aclOverridesApplier)
1532
+ registerModuleOverrideApplier<DiOverridesMap>('di', diOverridesApplier)
1533
+ registerModuleOverrideApplier<EncryptionOverridesShape>('encryption', encryptionOverridesApplier)
1534
+ }
1535
+
1536
+ registerBuiltInModuleOverrideAppliers()