@open-mercato/core 0.7.1-develop.7194.1.ab4fc81f82 → 0.7.1-develop.7195.1.b5efd1d51a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bootstrap.js +7 -3
- package/dist/bootstrap.js.map +2 -2
- package/package.json +7 -7
- package/src/bootstrap.ts +13 -3
package/dist/bootstrap.js
CHANGED
|
@@ -173,14 +173,18 @@ async function bootstrap(container) {
|
|
|
173
173
|
defaultEncryptionMaps
|
|
174
174
|
});
|
|
175
175
|
container.register({ tenantEncryptionService: asValue(tenantEncryptionService) });
|
|
176
|
-
if (isTenantDataEncryptionEnabled()
|
|
176
|
+
if (isTenantDataEncryptionEnabled()) {
|
|
177
177
|
try {
|
|
178
178
|
registerTenantEncryptionSubscriber(em, tenantEncryptionService);
|
|
179
179
|
} catch (err) {
|
|
180
180
|
logger.warn("Failed to register MikroORM encryption subscriber", { component: "encryption", err });
|
|
181
181
|
}
|
|
182
|
-
|
|
183
|
-
|
|
182
|
+
if (!kmsService.isHealthy()) {
|
|
183
|
+
logger.warn(
|
|
184
|
+
"Vault/KMS unhealthy - tenant data encryption is paused until it recovers; the subscriber is registered and resumes automatically",
|
|
185
|
+
{ component: "encryption" }
|
|
186
|
+
);
|
|
187
|
+
}
|
|
184
188
|
}
|
|
185
189
|
} catch (err) {
|
|
186
190
|
logger.warn("Failed to initialize tenant encryption service", { component: "encryption", err });
|
package/dist/bootstrap.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/bootstrap.ts"],
|
|
4
|
-
"sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport { asValue } from 'awilix'\nimport { getDefaultEncryptionMaps, type ModuleSubscriber } from '@open-mercato/shared/modules/registry'\nimport type { ModuleEncryptionMap } from '@open-mercato/shared/modules/encryption'\nimport { createEventBus } from '@open-mercato/events/index'\nimport { setGlobalEventBus } from '@open-mercato/shared/modules/events'\nimport { createCacheService } from '@open-mercato/cache'\nimport type { CacheStrategy } from '@open-mercato/cache'\nimport { createKmsService } from '@open-mercato/shared/lib/encryption/kms'\nimport { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { registerTenantEncryptionSubscriber } from '@open-mercato/shared/lib/encryption/subscriber'\nimport { isTenantDataEncryptionEnabled } from '@open-mercato/shared/lib/encryption/toggles'\nimport { getSearchModuleConfigs } from '@open-mercato/shared/modules/search'\nimport {\n registerSearchModule,\n createSearchDeleteSubscriber,\n searchDeleteMetadata,\n} from '@open-mercato/search'\nimport { RateLimiterService } from '@open-mercato/shared/lib/ratelimit/service'\nimport { readRateLimitConfig } from '@open-mercato/shared/lib/ratelimit/config'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('core')\n\n// Use globalThis to survive tsx/webpack module duplication (same pattern as container.ts DI registrars)\nconst RL_GLOBAL_KEY = '__openMercatoRateLimiterService__'\nconst RL_SHUTDOWN_KEY = '__openMercatoRateLimiterShutdown__'\n\nconst CACHE_GLOBAL_KEY = '__openMercatoCacheService__'\nconst CACHE_SHUTDOWN_KEY = '__openMercatoCacheShutdown__'\n\n// Escape hatch: set OM_CACHE_SINGLETON=off to fall back to the legacy\n// per-request cache instance (e.g. to isolate a regression). Default ON.\nfunction isCacheSingletonEnabled(): boolean {\n const raw = process.env.OM_CACHE_SINGLETON\n if (raw === undefined) return true\n const normalized = raw.trim().toLowerCase()\n if (!normalized.length) return true\n return !(normalized === '0' || normalized === 'off' || normalized === 'false' || normalized === 'no')\n}\n\n/**\n * Process-wide cache service singleton, mirroring getCachedRateLimiterService.\n *\n * bootstrap() previously built a fresh cache per request container, so under\n * the default memory strategy every cross-request cache was a no-op (each\n * instance is process-local) and under sqlite each request leaked a native\n * handle. Reusing one instance is safe by construction: tenant scope resolves\n * per-call via AsyncLocalStorage (packages/cache/src/tenantContext.ts), so the\n * service holds no request-bound state.\n *\n * Returns null when disabled via the escape hatch or when creation fails, so\n * the caller falls back to a per-request instance.\n */\nexport function getCachedCacheService(): CacheStrategy | null {\n if (!isCacheSingletonEnabled()) return null\n let service = (globalThis as any)[CACHE_GLOBAL_KEY] as CacheStrategy | null ?? null\n if (!service) {\n try {\n try {\n service = createCacheService()\n } catch (err) {\n logger.warn('Cache service initialization failed; falling back to memory strategy', { err })\n service = createCacheService({ strategy: 'memory' })\n }\n ;(globalThis as any)[CACHE_GLOBAL_KEY] = service\n\n // Register shutdown hook once to close persistent handles (sqlite/redis) on process exit\n if (!(globalThis as any)[CACHE_SHUTDOWN_KEY]) {\n const shutdown = () => { service?.close?.().catch(() => {}) }\n process.once('SIGTERM', shutdown)\n process.once('SIGINT', shutdown)\n ;(globalThis as any)[CACHE_SHUTDOWN_KEY] = true\n }\n } catch (err) {\n logger.warn('Failed to create cache service', { component: 'cache', err })\n }\n }\n return service\n}\n\nexport function getCachedRateLimiterService(): RateLimiterService | null {\n let service = (globalThis as any)[RL_GLOBAL_KEY] as RateLimiterService | null ?? null\n if (!service) {\n try {\n const rateLimitConfig = readRateLimitConfig()\n service = new RateLimiterService(rateLimitConfig)\n // Fire-and-forget async init (only needed for Redis strategy;\n // memory strategy works synchronously, and Redis has an in-memory\n // insurance limiter so the first few requests are still protected)\n service.initialize().catch((err) => {\n logger.warn('Async initialization failed', { component: 'ratelimit', err })\n })\n ;(globalThis as any)[RL_GLOBAL_KEY] = service\n\n // Register shutdown hook once to disconnect Redis on process exit\n if (!(globalThis as any)[RL_SHUTDOWN_KEY]) {\n const shutdown = () => { service?.destroy().catch(() => {}) }\n process.once('SIGTERM', shutdown)\n process.once('SIGINT', shutdown)\n ;(globalThis as any)[RL_SHUTDOWN_KEY] = true\n }\n } catch (err) {\n logger.warn('Failed to create rate limiter service', { component: 'ratelimit', err })\n }\n }\n return service\n}\n\nexport async function bootstrap(container: AwilixContainer) {\n // Register the cache service. Prefer the process-wide singleton so caches\n // survive across request containers; fall back to a per-request instance\n // when the singleton is disabled or fails to build.\n let cache: any = getCachedCacheService()\n if (!cache) {\n try {\n cache = createCacheService()\n } catch (err: any) {\n logger.warn('Cache service initialization failed; falling back to memory strategy', { err })\n cache = createCacheService({ strategy: 'memory' })\n }\n }\n container.register({ cache: asValue(cache) })\n\n // Create and register the DI-aware event bus\n let eventBus: any\n try {\n // Support both QUEUE_STRATEGY and legacy EVENTS_STRATEGY env vars\n const strategyEnv = process.env.QUEUE_STRATEGY || process.env.EVENTS_STRATEGY\n const queueStrategy = strategyEnv === 'async' || strategyEnv === 'redis' ? 'async' : 'local'\n eventBus = createEventBus({ resolve: container.resolve.bind(container) as any, queueStrategy })\n } catch (err: any) {\n // Fall back to local strategy to avoid breaking the app on misconfiguration\n logger.warn('Event bus initialization failed; falling back to local strategy', { err })\n try {\n eventBus = createEventBus({ resolve: container.resolve.bind(container) as any, queueStrategy: 'local' })\n } catch {\n // In extreme cases, provide a no-op bus to avoid crashes. It deliberately\n // omits `dispatchQueued` so the events worker fails its job loudly instead\n // of quietly completing it with zero subscribers dispatched.\n eventBus = {\n emit: async () => {},\n on: () => {},\n registerModuleSubscribers: () => {},\n clearQueue: async () => ({ removed: 0 }),\n }\n }\n }\n container.register({ eventBus: asValue(eventBus) })\n // Wire the global event bus so createModuleEvents().emit works outside DI context\n setGlobalEventBus(eventBus)\n // Auto-register discovered module subscribers\n try {\n let loadedModules: any[] = []\n try {\n const { getModules } = await import('@open-mercato/shared/lib/i18n/server')\n loadedModules = getModules()\n } catch (err) {\n // The events worker dispatches persistent subscribers through this bus, so\n // an empty registry here means queued events silently run nothing. Swallowing\n // it made that failure invisible; log it.\n logger.warn('Module registry unavailable; event bus starts with no module subscribers', { err })\n }\n const subs = loadedModules.flatMap((m) =>\n (m.subscribers || []).map((subscriber: ModuleSubscriber) => ({\n ...subscriber,\n moduleId: subscriber.moduleId ?? m.id,\n })),\n )\n if (subs.length) (container.resolve as any)('eventBus').registerModuleSubscribers(subs)\n\n // Extract sync subscribers and register in the sync-subscriber-store\n const syncSubs = subs.filter((s: any) => s.sync === true)\n if (syncSubs.length) {\n try {\n const { registerSyncSubscribers } = await import('@open-mercato/shared/lib/crud/sync-subscriber-store')\n registerSyncSubscribers(\n syncSubs.map((s: any) => ({\n metadata: { event: s.event, sync: true as const, priority: s.priority, id: s.id },\n handler: s.handler,\n })),\n )\n } catch {\n // sync-subscriber-store may not be available\n }\n }\n } catch (err) {\n logger.error(\"Failed to register module subscribers:\", { err });\n }\n\n // KMS + tenant encryption\n const kmsService = createKmsService()\n container.register({ kmsService: asValue(kmsService) })\n let defaultEncryptionMaps: ModuleEncryptionMap[] = []\n if (isTenantDataEncryptionEnabled()) {\n try {\n const { getModules } = await import('@open-mercato/shared/lib/i18n/server')\n defaultEncryptionMaps = getDefaultEncryptionMaps(getModules())\n } catch (err) {\n logger.error('Failed to load default encryption maps', { component: 'encryption', err })\n throw err\n }\n }\n try {\n const em = container.resolve('em') as EntityManager\n const cacheService = (() => {\n try { return container.resolve('cache') as any } catch { return null }\n })()\n const tenantEncryptionService = new TenantDataEncryptionService(em, {\n cache: cacheService,\n kms: kmsService,\n defaultEncryptionMaps,\n })\n container.register({ tenantEncryptionService: asValue(tenantEncryptionService) })\n if (isTenantDataEncryptionEnabled() && kmsService.isHealthy()) {\n try {\n registerTenantEncryptionSubscriber(em, tenantEncryptionService)\n } catch (err) {\n logger.warn('Failed to register MikroORM encryption subscriber', { component: 'encryption', err })\n }\n } else if (isTenantDataEncryptionEnabled() && !kmsService.isHealthy()) {\n logger.warn('Vault/KMS unhealthy - tenant data encryption is disabled until recovery', { component: 'encryption' })\n }\n } catch (err) {\n logger.warn('Failed to initialize tenant encryption service', { component: 'encryption', err })\n }\n\n // Register rate limiter service (singleton via globalThis \u2014 reused across request containers)\n // getCachedRateLimiterService() never throws; returns null on failure\n const rateLimiterService = getCachedRateLimiterService()\n if (rateLimiterService) {\n container.register({ rateLimiterService: asValue(rateLimiterService) })\n }\n\n // Register search module\n try {\n // Get configs from global registry (registered during app bootstrap)\n const searchModuleConfigs = getSearchModuleConfigs()\n registerSearchModule(container as any, { moduleConfigs: searchModuleConfigs })\n\n // Register searchModuleConfigs in container so status API can access vector-enabled entities\n container.register({\n searchModuleConfigs: asValue(searchModuleConfigs),\n })\n\n // Register search delete event subscriber\n // Note: search.index_record is now handled by auto-discovered fulltext_upsert.ts subscriber\n try {\n const searchIndexer = container.resolve('searchIndexer') as any\n if (searchIndexer && eventBus) {\n eventBus.registerModuleSubscribers([\n {\n event: searchDeleteMetadata.event,\n persistent: searchDeleteMetadata.persistent,\n handler: createSearchDeleteSubscriber(searchIndexer),\n },\n ])\n }\n } catch {\n // searchIndexer may not be available\n }\n } catch (err) {\n logger.warn('Failed to register search module', { component: 'search', err })\n }\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport { asValue } from 'awilix'\nimport { getDefaultEncryptionMaps, type ModuleSubscriber } from '@open-mercato/shared/modules/registry'\nimport type { ModuleEncryptionMap } from '@open-mercato/shared/modules/encryption'\nimport { createEventBus } from '@open-mercato/events/index'\nimport { setGlobalEventBus } from '@open-mercato/shared/modules/events'\nimport { createCacheService } from '@open-mercato/cache'\nimport type { CacheStrategy } from '@open-mercato/cache'\nimport { createKmsService } from '@open-mercato/shared/lib/encryption/kms'\nimport { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { registerTenantEncryptionSubscriber } from '@open-mercato/shared/lib/encryption/subscriber'\nimport { isTenantDataEncryptionEnabled } from '@open-mercato/shared/lib/encryption/toggles'\nimport { getSearchModuleConfigs } from '@open-mercato/shared/modules/search'\nimport {\n registerSearchModule,\n createSearchDeleteSubscriber,\n searchDeleteMetadata,\n} from '@open-mercato/search'\nimport { RateLimiterService } from '@open-mercato/shared/lib/ratelimit/service'\nimport { readRateLimitConfig } from '@open-mercato/shared/lib/ratelimit/config'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('core')\n\n// Use globalThis to survive tsx/webpack module duplication (same pattern as container.ts DI registrars)\nconst RL_GLOBAL_KEY = '__openMercatoRateLimiterService__'\nconst RL_SHUTDOWN_KEY = '__openMercatoRateLimiterShutdown__'\n\nconst CACHE_GLOBAL_KEY = '__openMercatoCacheService__'\nconst CACHE_SHUTDOWN_KEY = '__openMercatoCacheShutdown__'\n\n// Escape hatch: set OM_CACHE_SINGLETON=off to fall back to the legacy\n// per-request cache instance (e.g. to isolate a regression). Default ON.\nfunction isCacheSingletonEnabled(): boolean {\n const raw = process.env.OM_CACHE_SINGLETON\n if (raw === undefined) return true\n const normalized = raw.trim().toLowerCase()\n if (!normalized.length) return true\n return !(normalized === '0' || normalized === 'off' || normalized === 'false' || normalized === 'no')\n}\n\n/**\n * Process-wide cache service singleton, mirroring getCachedRateLimiterService.\n *\n * bootstrap() previously built a fresh cache per request container, so under\n * the default memory strategy every cross-request cache was a no-op (each\n * instance is process-local) and under sqlite each request leaked a native\n * handle. Reusing one instance is safe by construction: tenant scope resolves\n * per-call via AsyncLocalStorage (packages/cache/src/tenantContext.ts), so the\n * service holds no request-bound state.\n *\n * Returns null when disabled via the escape hatch or when creation fails, so\n * the caller falls back to a per-request instance.\n */\nexport function getCachedCacheService(): CacheStrategy | null {\n if (!isCacheSingletonEnabled()) return null\n let service = (globalThis as any)[CACHE_GLOBAL_KEY] as CacheStrategy | null ?? null\n if (!service) {\n try {\n try {\n service = createCacheService()\n } catch (err) {\n logger.warn('Cache service initialization failed; falling back to memory strategy', { err })\n service = createCacheService({ strategy: 'memory' })\n }\n ;(globalThis as any)[CACHE_GLOBAL_KEY] = service\n\n // Register shutdown hook once to close persistent handles (sqlite/redis) on process exit\n if (!(globalThis as any)[CACHE_SHUTDOWN_KEY]) {\n const shutdown = () => { service?.close?.().catch(() => {}) }\n process.once('SIGTERM', shutdown)\n process.once('SIGINT', shutdown)\n ;(globalThis as any)[CACHE_SHUTDOWN_KEY] = true\n }\n } catch (err) {\n logger.warn('Failed to create cache service', { component: 'cache', err })\n }\n }\n return service\n}\n\nexport function getCachedRateLimiterService(): RateLimiterService | null {\n let service = (globalThis as any)[RL_GLOBAL_KEY] as RateLimiterService | null ?? null\n if (!service) {\n try {\n const rateLimitConfig = readRateLimitConfig()\n service = new RateLimiterService(rateLimitConfig)\n // Fire-and-forget async init (only needed for Redis strategy;\n // memory strategy works synchronously, and Redis has an in-memory\n // insurance limiter so the first few requests are still protected)\n service.initialize().catch((err) => {\n logger.warn('Async initialization failed', { component: 'ratelimit', err })\n })\n ;(globalThis as any)[RL_GLOBAL_KEY] = service\n\n // Register shutdown hook once to disconnect Redis on process exit\n if (!(globalThis as any)[RL_SHUTDOWN_KEY]) {\n const shutdown = () => { service?.destroy().catch(() => {}) }\n process.once('SIGTERM', shutdown)\n process.once('SIGINT', shutdown)\n ;(globalThis as any)[RL_SHUTDOWN_KEY] = true\n }\n } catch (err) {\n logger.warn('Failed to create rate limiter service', { component: 'ratelimit', err })\n }\n }\n return service\n}\n\nexport async function bootstrap(container: AwilixContainer) {\n // Register the cache service. Prefer the process-wide singleton so caches\n // survive across request containers; fall back to a per-request instance\n // when the singleton is disabled or fails to build.\n let cache: any = getCachedCacheService()\n if (!cache) {\n try {\n cache = createCacheService()\n } catch (err: any) {\n logger.warn('Cache service initialization failed; falling back to memory strategy', { err })\n cache = createCacheService({ strategy: 'memory' })\n }\n }\n container.register({ cache: asValue(cache) })\n\n // Create and register the DI-aware event bus\n let eventBus: any\n try {\n // Support both QUEUE_STRATEGY and legacy EVENTS_STRATEGY env vars\n const strategyEnv = process.env.QUEUE_STRATEGY || process.env.EVENTS_STRATEGY\n const queueStrategy = strategyEnv === 'async' || strategyEnv === 'redis' ? 'async' : 'local'\n eventBus = createEventBus({ resolve: container.resolve.bind(container) as any, queueStrategy })\n } catch (err: any) {\n // Fall back to local strategy to avoid breaking the app on misconfiguration\n logger.warn('Event bus initialization failed; falling back to local strategy', { err })\n try {\n eventBus = createEventBus({ resolve: container.resolve.bind(container) as any, queueStrategy: 'local' })\n } catch {\n // In extreme cases, provide a no-op bus to avoid crashes. It deliberately\n // omits `dispatchQueued` so the events worker fails its job loudly instead\n // of quietly completing it with zero subscribers dispatched.\n eventBus = {\n emit: async () => {},\n on: () => {},\n registerModuleSubscribers: () => {},\n clearQueue: async () => ({ removed: 0 }),\n }\n }\n }\n container.register({ eventBus: asValue(eventBus) })\n // Wire the global event bus so createModuleEvents().emit works outside DI context\n setGlobalEventBus(eventBus)\n // Auto-register discovered module subscribers\n try {\n let loadedModules: any[] = []\n try {\n const { getModules } = await import('@open-mercato/shared/lib/i18n/server')\n loadedModules = getModules()\n } catch (err) {\n // The events worker dispatches persistent subscribers through this bus, so\n // an empty registry here means queued events silently run nothing. Swallowing\n // it made that failure invisible; log it.\n logger.warn('Module registry unavailable; event bus starts with no module subscribers', { err })\n }\n const subs = loadedModules.flatMap((m) =>\n (m.subscribers || []).map((subscriber: ModuleSubscriber) => ({\n ...subscriber,\n moduleId: subscriber.moduleId ?? m.id,\n })),\n )\n if (subs.length) (container.resolve as any)('eventBus').registerModuleSubscribers(subs)\n\n // Extract sync subscribers and register in the sync-subscriber-store\n const syncSubs = subs.filter((s: any) => s.sync === true)\n if (syncSubs.length) {\n try {\n const { registerSyncSubscribers } = await import('@open-mercato/shared/lib/crud/sync-subscriber-store')\n registerSyncSubscribers(\n syncSubs.map((s: any) => ({\n metadata: { event: s.event, sync: true as const, priority: s.priority, id: s.id },\n handler: s.handler,\n })),\n )\n } catch {\n // sync-subscriber-store may not be available\n }\n }\n } catch (err) {\n logger.error(\"Failed to register module subscribers:\", { err });\n }\n\n // KMS + tenant encryption\n const kmsService = createKmsService()\n container.register({ kmsService: asValue(kmsService) })\n let defaultEncryptionMaps: ModuleEncryptionMap[] = []\n if (isTenantDataEncryptionEnabled()) {\n try {\n const { getModules } = await import('@open-mercato/shared/lib/i18n/server')\n defaultEncryptionMaps = getDefaultEncryptionMaps(getModules())\n } catch (err) {\n logger.error('Failed to load default encryption maps', { component: 'encryption', err })\n throw err\n }\n }\n try {\n const em = container.resolve('em') as EntityManager\n const cacheService = (() => {\n try { return container.resolve('cache') as any } catch { return null }\n })()\n const tenantEncryptionService = new TenantDataEncryptionService(em, {\n cache: cacheService,\n kms: kmsService,\n defaultEncryptionMaps,\n })\n container.register({ tenantEncryptionService: asValue(tenantEncryptionService) })\n // Register on the static config toggle only \u2014 never on KMS health. The\n // subscriber re-checks `service.isEnabled()` (which includes KMS health) on\n // every read/write, so registering while Vault is down is a no-op that\n // starts encrypting again the moment KMS recovers. Gating registration on\n // health instead left a boot-time outage fail-open for the whole process\n // lifetime, recoverable only by a restart (#5948).\n if (isTenantDataEncryptionEnabled()) {\n try {\n registerTenantEncryptionSubscriber(em, tenantEncryptionService)\n } catch (err) {\n logger.warn('Failed to register MikroORM encryption subscriber', { component: 'encryption', err })\n }\n if (!kmsService.isHealthy()) {\n logger.warn(\n 'Vault/KMS unhealthy - tenant data encryption is paused until it recovers; the subscriber is registered and resumes automatically',\n { component: 'encryption' },\n )\n }\n }\n } catch (err) {\n logger.warn('Failed to initialize tenant encryption service', { component: 'encryption', err })\n }\n\n // Register rate limiter service (singleton via globalThis \u2014 reused across request containers)\n // getCachedRateLimiterService() never throws; returns null on failure\n const rateLimiterService = getCachedRateLimiterService()\n if (rateLimiterService) {\n container.register({ rateLimiterService: asValue(rateLimiterService) })\n }\n\n // Register search module\n try {\n // Get configs from global registry (registered during app bootstrap)\n const searchModuleConfigs = getSearchModuleConfigs()\n registerSearchModule(container as any, { moduleConfigs: searchModuleConfigs })\n\n // Register searchModuleConfigs in container so status API can access vector-enabled entities\n container.register({\n searchModuleConfigs: asValue(searchModuleConfigs),\n })\n\n // Register search delete event subscriber\n // Note: search.index_record is now handled by auto-discovered fulltext_upsert.ts subscriber\n try {\n const searchIndexer = container.resolve('searchIndexer') as any\n if (searchIndexer && eventBus) {\n eventBus.registerModuleSubscribers([\n {\n event: searchDeleteMetadata.event,\n persistent: searchDeleteMetadata.persistent,\n handler: createSearchDeleteSubscriber(searchIndexer),\n },\n ])\n }\n } catch {\n // searchIndexer may not be available\n }\n } catch (err) {\n logger.warn('Failed to register search module', { component: 'search', err })\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.7.1-develop.
|
|
3
|
+
"version": "0.7.1-develop.7195.1.b5efd1d51a",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -253,16 +253,16 @@
|
|
|
253
253
|
"zod": "^4.4.3"
|
|
254
254
|
},
|
|
255
255
|
"peerDependencies": {
|
|
256
|
-
"@open-mercato/ai-assistant": "0.7.1-develop.
|
|
257
|
-
"@open-mercato/shared": "0.7.1-develop.
|
|
258
|
-
"@open-mercato/ui": "0.7.1-develop.
|
|
256
|
+
"@open-mercato/ai-assistant": "0.7.1-develop.7195.1.b5efd1d51a",
|
|
257
|
+
"@open-mercato/shared": "0.7.1-develop.7195.1.b5efd1d51a",
|
|
258
|
+
"@open-mercato/ui": "0.7.1-develop.7195.1.b5efd1d51a",
|
|
259
259
|
"react": "^19.0.0",
|
|
260
260
|
"react-dom": "^19.0.0"
|
|
261
261
|
},
|
|
262
262
|
"devDependencies": {
|
|
263
|
-
"@open-mercato/ai-assistant": "0.7.1-develop.
|
|
264
|
-
"@open-mercato/shared": "0.7.1-develop.
|
|
265
|
-
"@open-mercato/ui": "0.7.1-develop.
|
|
263
|
+
"@open-mercato/ai-assistant": "0.7.1-develop.7195.1.b5efd1d51a",
|
|
264
|
+
"@open-mercato/shared": "0.7.1-develop.7195.1.b5efd1d51a",
|
|
265
|
+
"@open-mercato/ui": "0.7.1-develop.7195.1.b5efd1d51a",
|
|
266
266
|
"@testing-library/dom": "^10.4.1",
|
|
267
267
|
"@testing-library/jest-dom": "^7.0.1",
|
|
268
268
|
"@testing-library/react": "^16.3.3",
|
package/src/bootstrap.ts
CHANGED
|
@@ -213,14 +213,24 @@ export async function bootstrap(container: AwilixContainer) {
|
|
|
213
213
|
defaultEncryptionMaps,
|
|
214
214
|
})
|
|
215
215
|
container.register({ tenantEncryptionService: asValue(tenantEncryptionService) })
|
|
216
|
-
|
|
216
|
+
// Register on the static config toggle only — never on KMS health. The
|
|
217
|
+
// subscriber re-checks `service.isEnabled()` (which includes KMS health) on
|
|
218
|
+
// every read/write, so registering while Vault is down is a no-op that
|
|
219
|
+
// starts encrypting again the moment KMS recovers. Gating registration on
|
|
220
|
+
// health instead left a boot-time outage fail-open for the whole process
|
|
221
|
+
// lifetime, recoverable only by a restart (#5948).
|
|
222
|
+
if (isTenantDataEncryptionEnabled()) {
|
|
217
223
|
try {
|
|
218
224
|
registerTenantEncryptionSubscriber(em, tenantEncryptionService)
|
|
219
225
|
} catch (err) {
|
|
220
226
|
logger.warn('Failed to register MikroORM encryption subscriber', { component: 'encryption', err })
|
|
221
227
|
}
|
|
222
|
-
|
|
223
|
-
|
|
228
|
+
if (!kmsService.isHealthy()) {
|
|
229
|
+
logger.warn(
|
|
230
|
+
'Vault/KMS unhealthy - tenant data encryption is paused until it recovers; the subscriber is registered and resumes automatically',
|
|
231
|
+
{ component: 'encryption' },
|
|
232
|
+
)
|
|
233
|
+
}
|
|
224
234
|
}
|
|
225
235
|
} catch (err) {
|
|
226
236
|
logger.warn('Failed to initialize tenant encryption service', { component: 'encryption', err })
|