@open-mercato/shared 0.6.6-develop.6460.1.f94c74174c → 0.6.6-develop.6471.1.9ab5bdd79a

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 (164) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +1 -0
  3. package/dist/lib/commands/command-bus.js +4 -2
  4. package/dist/lib/commands/command-bus.js.map +2 -2
  5. package/dist/lib/commands/command-interceptor-runner.js +4 -2
  6. package/dist/lib/commands/command-interceptor-runner.js.map +2 -2
  7. package/dist/lib/commands/flush.js +3 -4
  8. package/dist/lib/commands/flush.js.map +2 -2
  9. package/dist/lib/commands/helpers.js +3 -1
  10. package/dist/lib/commands/helpers.js.map +2 -2
  11. package/dist/lib/commands/registry.js +5 -3
  12. package/dist/lib/commands/registry.js.map +2 -2
  13. package/dist/lib/commands/scope.js +5 -3
  14. package/dist/lib/commands/scope.js.map +2 -2
  15. package/dist/lib/crud/advanced-filter-integration.js +3 -1
  16. package/dist/lib/crud/advanced-filter-integration.js.map +2 -2
  17. package/dist/lib/crud/cache.js +3 -1
  18. package/dist/lib/crud/cache.js.map +2 -2
  19. package/dist/lib/crud/custom-fields.js +4 -2
  20. package/dist/lib/crud/custom-fields.js.map +2 -2
  21. package/dist/lib/crud/enricher-runner.js +6 -10
  22. package/dist/lib/crud/enricher-runner.js.map +2 -2
  23. package/dist/lib/crud/factory.js +10 -8
  24. package/dist/lib/crud/factory.js.map +2 -2
  25. package/dist/lib/crud/interceptor-registry.js +3 -3
  26. package/dist/lib/crud/interceptor-registry.js.map +2 -2
  27. package/dist/lib/crud/mutation-guard.js +4 -2
  28. package/dist/lib/crud/mutation-guard.js.map +2 -2
  29. package/dist/lib/crud/optimistic-lock.js +5 -6
  30. package/dist/lib/crud/optimistic-lock.js.map +2 -2
  31. package/dist/lib/crud/route-mutation-guard.js +3 -1
  32. package/dist/lib/crud/route-mutation-guard.js.map +2 -2
  33. package/dist/lib/crud/sync-event-runner.js +3 -1
  34. package/dist/lib/crud/sync-event-runner.js.map +2 -2
  35. package/dist/lib/data/engine.js +5 -5
  36. package/dist/lib/data/engine.js.map +2 -2
  37. package/dist/lib/db/mikro.js +5 -3
  38. package/dist/lib/db/mikro.js.map +2 -2
  39. package/dist/lib/di/container.js +3 -1
  40. package/dist/lib/di/container.js.map +2 -2
  41. package/dist/lib/encryption/aes.js +3 -1
  42. package/dist/lib/encryption/aes.js.map +2 -2
  43. package/dist/lib/encryption/entityFields.js +3 -1
  44. package/dist/lib/encryption/entityFields.js.map +2 -2
  45. package/dist/lib/encryption/entityIds.js +3 -1
  46. package/dist/lib/encryption/entityIds.js.map +2 -2
  47. package/dist/lib/encryption/kms.js +25 -32
  48. package/dist/lib/encryption/kms.js.map +2 -2
  49. package/dist/lib/encryption/subscriber.js +3 -1
  50. package/dist/lib/encryption/subscriber.js.map +2 -2
  51. package/dist/lib/encryption/tenantDataEncryptionService.js +3 -1
  52. package/dist/lib/encryption/tenantDataEncryptionService.js.map +2 -2
  53. package/dist/lib/indexers/error-log.js +4 -2
  54. package/dist/lib/indexers/error-log.js.map +2 -2
  55. package/dist/lib/indexers/status-log.js +5 -3
  56. package/dist/lib/indexers/status-log.js.map +2 -2
  57. package/dist/lib/logger/index.js +28 -0
  58. package/dist/lib/logger/index.js.map +7 -0
  59. package/dist/lib/logger/level.js +43 -0
  60. package/dist/lib/logger/level.js.map +7 -0
  61. package/dist/lib/logger/transport.console.js +39 -0
  62. package/dist/lib/logger/transport.console.js.map +7 -0
  63. package/dist/lib/logger/transport.js +17 -0
  64. package/dist/lib/logger/transport.js.map +7 -0
  65. package/dist/lib/logger/transport.pretty.js +85 -0
  66. package/dist/lib/logger/transport.pretty.js.map +7 -0
  67. package/dist/lib/logger/transport.server.js +85 -0
  68. package/dist/lib/logger/transport.server.js.map +7 -0
  69. package/dist/lib/middleware/page-executor.js +3 -1
  70. package/dist/lib/middleware/page-executor.js.map +2 -2
  71. package/dist/lib/modules/registry.js +3 -1
  72. package/dist/lib/modules/registry.js.map +2 -2
  73. package/dist/lib/profiler/index.js +3 -2
  74. package/dist/lib/profiler/index.js.map +2 -2
  75. package/dist/lib/query/engine.js +5 -6
  76. package/dist/lib/query/engine.js.map +2 -2
  77. package/dist/lib/query/query-extension-runner.js +5 -11
  78. package/dist/lib/query/query-extension-runner.js.map +2 -2
  79. package/dist/lib/redis/connection.js +3 -1
  80. package/dist/lib/redis/connection.js.map +2 -2
  81. package/dist/lib/url.js +7 -5
  82. package/dist/lib/url.js.map +2 -2
  83. package/dist/lib/version.js +1 -1
  84. package/dist/lib/version.js.map +1 -1
  85. package/dist/modules/analytics.js +3 -1
  86. package/dist/modules/analytics.js.map +2 -2
  87. package/dist/modules/dashboard/widgets.js +3 -1
  88. package/dist/modules/dashboard/widgets.js.map +2 -2
  89. package/dist/modules/events/factory.js +5 -3
  90. package/dist/modules/events/factory.js.map +2 -2
  91. package/dist/modules/overrides.js +16 -34
  92. package/dist/modules/overrides.js.map +2 -2
  93. package/dist/modules/registry.js +3 -1
  94. package/dist/modules/registry.js.map +2 -2
  95. package/dist/modules/search.js +3 -1
  96. package/dist/modules/search.js.map +2 -2
  97. package/dist/modules/widgets/injection-loader.js +6 -6
  98. package/dist/modules/widgets/injection-loader.js.map +2 -2
  99. package/dist/modules/widgets/injection-position.js +3 -1
  100. package/dist/modules/widgets/injection-position.js.map +2 -2
  101. package/package.json +7 -2
  102. package/src/lib/commands/__tests__/command-interceptor-runner.test.ts +19 -6
  103. package/src/lib/commands/__tests__/flush.test.ts +24 -15
  104. package/src/lib/commands/__tests__/normalizeAuthorUserId.test.ts +14 -2
  105. package/src/lib/commands/__tests__/registry.test.ts +17 -2
  106. package/src/lib/commands/__tests__/scope.test.ts +21 -9
  107. package/src/lib/commands/command-bus.ts +5 -2
  108. package/src/lib/commands/command-interceptor-runner.ts +5 -2
  109. package/src/lib/commands/flush.ts +4 -5
  110. package/src/lib/commands/helpers.ts +9 -2
  111. package/src/lib/commands/registry.ts +6 -3
  112. package/src/lib/commands/scope.ts +6 -3
  113. package/src/lib/crud/__tests__/optimistic-lock.test.ts +21 -6
  114. package/src/lib/crud/__tests__/route-mutation-guard.test.ts +17 -2
  115. package/src/lib/crud/__tests__/sync-event-runner.test.ts +17 -3
  116. package/src/lib/crud/advanced-filter-integration.ts +4 -1
  117. package/src/lib/crud/cache.ts +4 -1
  118. package/src/lib/crud/custom-fields.ts +5 -2
  119. package/src/lib/crud/enricher-runner.ts +7 -10
  120. package/src/lib/crud/factory.ts +11 -10
  121. package/src/lib/crud/interceptor-registry.ts +4 -3
  122. package/src/lib/crud/mutation-guard.ts +5 -2
  123. package/src/lib/crud/optimistic-lock.ts +6 -10
  124. package/src/lib/crud/route-mutation-guard.ts +4 -1
  125. package/src/lib/crud/sync-event-runner.ts +4 -1
  126. package/src/lib/data/__tests__/engine.event-validation.test.ts +26 -15
  127. package/src/lib/data/engine.ts +6 -6
  128. package/src/lib/db/mikro.ts +6 -3
  129. package/src/lib/di/container.ts +4 -1
  130. package/src/lib/encryption/aes.ts +4 -2
  131. package/src/lib/encryption/entityFields.ts +5 -1
  132. package/src/lib/encryption/entityIds.ts +4 -1
  133. package/src/lib/encryption/kms.ts +27 -33
  134. package/src/lib/encryption/subscriber.ts +4 -2
  135. package/src/lib/encryption/tenantDataEncryptionService.ts +4 -2
  136. package/src/lib/indexers/__tests__/status-log.test.ts +22 -10
  137. package/src/lib/indexers/error-log.ts +5 -2
  138. package/src/lib/indexers/status-log.ts +6 -3
  139. package/src/lib/logger/__tests__/logger.test.ts +539 -0
  140. package/src/lib/logger/index.ts +33 -0
  141. package/src/lib/logger/level.ts +50 -0
  142. package/src/lib/logger/transport.console.ts +41 -0
  143. package/src/lib/logger/transport.pretty.ts +94 -0
  144. package/src/lib/logger/transport.server.ts +125 -0
  145. package/src/lib/logger/transport.ts +17 -0
  146. package/src/lib/middleware/page-executor.ts +4 -1
  147. package/src/lib/modules/__tests__/registry.test.ts +28 -21
  148. package/src/lib/modules/registry.ts +4 -1
  149. package/src/lib/profiler/index.ts +4 -2
  150. package/src/lib/query/__tests__/query-extension-runner.test.ts +21 -7
  151. package/src/lib/query/engine.ts +7 -10
  152. package/src/lib/query/query-extension-runner.ts +6 -11
  153. package/src/lib/redis/connection.ts +5 -1
  154. package/src/lib/url.ts +10 -5
  155. package/src/modules/__tests__/overrides.test.ts +17 -3
  156. package/src/modules/__tests__/route-overrides.test.ts +29 -20
  157. package/src/modules/analytics.ts +5 -1
  158. package/src/modules/dashboard/widgets.ts +4 -1
  159. package/src/modules/events/factory.ts +6 -3
  160. package/src/modules/overrides.ts +18 -34
  161. package/src/modules/registry.ts +4 -1
  162. package/src/modules/search.ts +4 -1
  163. package/src/modules/widgets/injection-loader.ts +7 -6
  164. package/src/modules/widgets/injection-position.ts +5 -1
@@ -71,11 +71,14 @@ import { mergeAdvancedFilters } from './advanced-filter-integration'
71
71
  import { parseExtensionHeaders } from '../umes/extension-headers'
72
72
  import { createGenericOptimisticLockReader } from './optimistic-lock'
73
73
  import { registerOptimisticLockReaderIfAbsent } from './optimistic-lock-store'
74
+ import { createLogger } from '../logger'
74
75
 
75
76
  type RbacServiceLike = {
76
77
  getGrantedFeatures: (userId: string, opts: { tenantId: string | null; organizationId: string | null }) => Promise<string[]>
77
78
  }
78
79
 
80
+ const logger = createLogger('shared').child({ component: 'crud' })
81
+
79
82
  function resolveSortParams(queryParams: Record<string, unknown>) {
80
83
  const rawSortField = queryParams.sortField ?? queryParams.sort ?? 'id'
81
84
  const rawSortDir = queryParams.sortDir ?? queryParams.order ?? 'asc'
@@ -544,8 +547,7 @@ function handleError(err: unknown): Response {
544
547
 
545
548
  const message = err instanceof Error ? err.message : undefined
546
549
  const stack = err instanceof Error ? err.stack : undefined
547
- // eslint-disable-next-line no-console
548
- console.error('[crud] unexpected error', { message, stack, err })
550
+ logger.error('Unexpected CRUD error', { message, stack, err })
549
551
  const body: Record<string, unknown> = {
550
552
  error: 'Internal server error',
551
553
  message: 'Something went wrong. Please try again later.',
@@ -615,7 +617,7 @@ async function runGuardAfterSuccessCallbacks(
615
617
  try {
616
618
  await guard.afterSuccess!({ ...base, metadata: guardMeta })
617
619
  } catch (error) {
618
- console.error(`[mutation-guard] afterSuccess failed for guard ${guard.id}`, error)
620
+ logger.error('Mutation guard afterSuccess failed', { guardId: guard.id, err: error })
619
621
  }
620
622
  }
621
623
  }
@@ -705,8 +707,7 @@ export async function flushPendingCrudAccessLogs(): Promise<void> {
705
707
 
706
708
  function logForbidden(details: Record<string, unknown>) {
707
709
  try {
708
- // eslint-disable-next-line no-console
709
- console.warn('[crud] Forbidden request', details)
710
+ logger.warn('Forbidden request', details)
710
711
  } catch {}
711
712
  }
712
713
 
@@ -824,7 +825,7 @@ export async function logCrudAccess(options: LogCrudAccessOptions): Promise<LogC
824
825
  payloads.map((payload) =>
825
826
  Promise.resolve(service.log(payload)).catch((err) => {
826
827
  try {
827
- console.error('[crud] failed to record access log', { err, payload })
828
+ logger.error('Failed to record access log', { err, payload })
828
829
  } catch {}
829
830
  return undefined
830
831
  }),
@@ -833,7 +834,7 @@ export async function logCrudAccess(options: LogCrudAccessOptions): Promise<LogC
833
834
  }
834
835
  } catch (err) {
835
836
  try {
836
- console.error('[crud] failed to record access logs (batch)', { err, count: payloads.length })
837
+ logger.error('Failed to record access logs (batch)', { err, count: payloads.length })
837
838
  } catch {}
838
839
  }
839
840
  })()
@@ -1085,7 +1086,7 @@ export function makeCrudRoute<TCreate = any, TUpdate = any, TList = any>(opts: C
1085
1086
  })
1086
1087
  return decoratedItems
1087
1088
  } catch (err) {
1088
- console.warn('[crud] failed to decorate custom fields', err)
1089
+ logger.warn('Failed to decorate custom fields', { err })
1089
1090
  endProfile({
1090
1091
  result: 'error',
1091
1092
  entityIds: entityIds.length,
@@ -1709,7 +1710,7 @@ export function makeCrudRoute<TCreate = any, TUpdate = any, TList = any>(opts: C
1709
1710
  }
1710
1711
  }
1711
1712
  } catch (err) {
1712
- console.warn('[CRUD] Translation overlay failed:', err)
1713
+ logger.warn('Translation overlay failed', { err })
1713
1714
  }
1714
1715
  profiler.mark('translation_overlays_complete', { itemCount: transformedItems.length })
1715
1716
  }
@@ -1940,7 +1941,7 @@ export function makeCrudRoute<TCreate = any, TUpdate = any, TList = any>(opts: C
1940
1941
  }
1941
1942
  }
1942
1943
  } catch (err) {
1943
- console.warn('[CRUD] Translation overlay (fallback) failed:', err)
1944
+ logger.warn('Translation overlay (fallback) failed', { err })
1944
1945
  }
1945
1946
  profiler.mark('fallback_translation_overlays_complete', { itemCount: Array.isArray(list) ? list.length : 0 })
1946
1947
  }
@@ -1,5 +1,8 @@
1
1
  import type { ApiInterceptor, ApiInterceptorMethod, ApiInterceptorRegistryEntry } from './api-interceptor'
2
2
  import { applyApiInterceptorOverridesToEntries } from '../../modules/overrides'
3
+ import { createLogger } from '../logger'
4
+
5
+ const logger = createLogger('shared').child({ component: 'umes' })
3
6
 
4
7
  let _interceptorEntries: ApiInterceptorRegistryEntry[] | null = null
5
8
  const GLOBAL_INTERCEPTOR_KEY = '__openMercatoApiInterceptors__'
@@ -80,9 +83,7 @@ export function getApiInterceptorsForRoute(routePath: string, method: ApiInterce
80
83
  const warningKey = `${routePath}:${method}:${prev.interceptor.id}:${current.interceptor.id}:${currentPriority}`
81
84
  if (collisionWarnings.has(warningKey)) continue
82
85
  collisionWarnings.add(warningKey)
83
- console.warn(
84
- `[UMES] Interceptors "${prev.interceptor.id}" and "${current.interceptor.id}" have the same priority (${currentPriority}) for route "${routePath}". Execution order is based on module registration order.`
85
- )
86
+ logger.warn('Interceptors share the same priority for route — execution order is based on module registration order', { firstInterceptorId: prev.interceptor.id, secondInterceptorId: current.interceptor.id, priority: currentPriority, routePath })
86
87
  }
87
88
  }
88
89
 
@@ -1,4 +1,7 @@
1
1
  import type { AwilixContainer } from 'awilix'
2
+ import { createLogger } from '../logger'
3
+
4
+ const logger = createLogger('shared').child({ component: 'crud' })
2
5
 
3
6
  export type CrudMutationGuardValidationSuccess = {
4
7
  ok: true
@@ -94,12 +97,12 @@ export async function runCrudMutationGuardAfterSuccess(
94
97
  try {
95
98
  await service.afterMutationSuccess(input)
96
99
  } catch (error) {
97
- console.error('[crud-mutation-guard] after-success hook failed', {
100
+ logger.error('Mutation guard after-success hook failed', {
98
101
  resourceKind: input.resourceKind,
99
102
  resourceId: input.resourceId,
100
103
  operation: input.operation,
101
104
  requestMethod: input.requestMethod,
102
- error: error instanceof Error ? error.message : String(error),
105
+ err: error,
103
106
  })
104
107
  }
105
108
  }
@@ -44,6 +44,9 @@ import {
44
44
  type OptimisticLockConflictBody,
45
45
  } from './optimistic-lock-headers'
46
46
  import { getAllOptimisticLockReaders } from './optimistic-lock-store'
47
+ import { createLogger } from '../logger'
48
+
49
+ const logger = createLogger('shared').child({ component: 'optimistic-lock' })
47
50
 
48
51
  export type OptimisticLockConfig =
49
52
  | { mode: 'off' }
@@ -194,12 +197,7 @@ export function createGenericOptimisticLockReader(
194
197
  // misconfig filtering on a column the table lacks), which silently disables
195
198
  // locking for `resourceKind`. Log loudly so the misconfig is visible.
196
199
  // Control flow stays fail-open (return null) to honor the no-500 contract.
197
- // eslint-disable-next-line no-console
198
- console.error(
199
- `[optimistic-lock] reader query failed for resourceKind="${resourceKind}" — optimistic locking is DISABLED for this entity until fixed. ` +
200
- `Most likely a softDeleteField/tenantField/orgField filters on a column the table does not have.`,
201
- err,
202
- )
200
+ logger.error('Reader query failed — optimistic locking is DISABLED for this entity until fixed; most likely a softDeleteField/tenantField/orgField filters on a column the table does not have', { resourceKind, err })
203
201
  return null
204
202
  }
205
203
  }
@@ -350,8 +348,7 @@ export function createOptimisticLockGuardService(
350
348
 
351
349
  if (currentIso === expectedIso) {
352
350
  if (debugEnabled) {
353
- // eslint-disable-next-line no-console
354
- console.log('[optimistic-lock] match', {
351
+ logger.info('Optimistic lock match', {
355
352
  resourceKind: input.resourceKind,
356
353
  resourceId: input.resourceId,
357
354
  operation: input.operation,
@@ -363,8 +360,7 @@ export function createOptimisticLockGuardService(
363
360
  }
364
361
 
365
362
  if (debugEnabled) {
366
- // eslint-disable-next-line no-console
367
- console.log('[optimistic-lock] CONFLICT', {
363
+ logger.info('Optimistic lock conflict', {
368
364
  resourceKind: input.resourceKind,
369
365
  resourceId: input.resourceId,
370
366
  operation: input.operation,
@@ -5,6 +5,9 @@ import {
5
5
  type MutationGuard,
6
6
  } from './mutation-guard-registry'
7
7
  import { getAllMutationGuardInstances } from './mutation-guard-store'
8
+ import { createLogger } from '../logger'
9
+
10
+ const logger = createLogger('shared').child({ component: 'crud' })
8
11
 
9
12
  /**
10
13
  * Shared registry-based mutation-guard wrapper for custom write routes that do
@@ -173,7 +176,7 @@ export async function runRouteMutationGuards(params: {
173
176
  metadata,
174
177
  })
175
178
  } catch (error) {
176
- console.error(`[mutation-guard] afterSuccess failed for guard ${guard.id}`, error)
179
+ logger.error('Mutation guard afterSuccess failed', { guardId: guard.id, err: error })
177
180
  }
178
181
  }
179
182
  },
@@ -1,6 +1,9 @@
1
1
  import type { SyncCrudEventPayload } from './sync-event-types'
2
2
  import type { SyncSubscriberEntry } from './sync-subscriber-store'
3
3
  import { matchWildcardPattern } from '@open-mercato/shared/lib/patterns/wildcard'
4
+ import { createLogger } from '../logger'
5
+
6
+ const logger = createLogger('shared').child({ component: 'crud' })
4
7
 
5
8
  // ---------------------------------------------------------------------------
6
9
  // Event pattern matching
@@ -63,7 +66,7 @@ export async function runSyncAfterEvent(
63
66
  try {
64
67
  await subscriber.handler(payload, ctx)
65
68
  } catch (error) {
66
- console.error(`[sync-event] after-subscriber failed: ${subscriber.metadata.id}`, error)
69
+ logger.error('Sync-event after-subscriber failed', { subscriberId: subscriber.metadata.id, err: error })
67
70
  }
68
71
  }
69
72
  }
@@ -2,6 +2,21 @@ import type { AwilixContainer } from 'awilix'
2
2
  import type { EntityManager } from '@mikro-orm/postgresql'
3
3
  import { DefaultDataEngine, __resetUndeclaredEventWarningsForTests } from '../engine'
4
4
  import { createModuleEvents, registerEventModuleConfigs } from '../../../modules/events'
5
+ import { createLogger } from '@open-mercato/shared/lib/logger'
6
+
7
+ jest.mock('@open-mercato/shared/lib/logger', () => {
8
+ const mocked = {
9
+ debug: jest.fn(),
10
+ info: jest.fn(),
11
+ warn: jest.fn(),
12
+ error: jest.fn(),
13
+ child: jest.fn(),
14
+ }
15
+ mocked.child.mockImplementation(() => mocked)
16
+ return { createLogger: jest.fn(() => mocked) }
17
+ })
18
+ const loggerWarn = createLogger('shared').warn as jest.Mock
19
+
5
20
 
6
21
  const testEvents = [
7
22
  { id: 'issue1421_test.widget.created', label: 'Widget Created', entity: 'widget', category: 'crud' as const },
@@ -41,7 +56,7 @@ describe('DataEngine event contract validation (issue #1421)', () => {
41
56
  })
42
57
 
43
58
  it('emits declared events without warning', async () => {
44
- const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {})
59
+ loggerWarn.mockClear()
45
60
  try {
46
61
  const { engine, emitted } = makeFixture()
47
62
 
@@ -55,9 +70,8 @@ describe('DataEngine event contract validation (issue #1421)', () => {
55
70
  expect(emitted).toEqual([
56
71
  expect.objectContaining({ name: 'issue1421_test.widget.created' }),
57
72
  ])
58
- expect(warnSpy).not.toHaveBeenCalled()
73
+ expect(loggerWarn).not.toHaveBeenCalled()
59
74
  } finally {
60
- warnSpy.mockRestore()
61
75
  }
62
76
  })
63
77
 
@@ -78,7 +92,7 @@ describe('DataEngine event contract validation (issue #1421)', () => {
78
92
  },
79
93
  ])
80
94
 
81
- const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {})
95
+ loggerWarn.mockClear()
82
96
  try {
83
97
  const { engine, emitted } = makeFixture()
84
98
 
@@ -92,14 +106,13 @@ describe('DataEngine event contract validation (issue #1421)', () => {
92
106
  expect(emitted).toEqual([
93
107
  expect.objectContaining({ name: 'issue1421_bootstrap.widget.deleted' }),
94
108
  ])
95
- expect(warnSpy).not.toHaveBeenCalled()
109
+ expect(loggerWarn).not.toHaveBeenCalled()
96
110
  } finally {
97
- warnSpy.mockRestore()
98
111
  }
99
112
  })
100
113
 
101
114
  it('warns when emitting an event that is not registered', async () => {
102
- const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {})
115
+ loggerWarn.mockClear()
103
116
  try {
104
117
  const { engine, emitted } = makeFixture()
105
118
 
@@ -110,22 +123,21 @@ describe('DataEngine event contract validation (issue #1421)', () => {
110
123
  events: { module: 'issue1421_unregistered', entity: 'ghost' },
111
124
  })
112
125
 
113
- expect(warnSpy).toHaveBeenCalledTimes(1)
114
- const warningMessage = warnSpy.mock.calls[0]?.[0] ?? ''
115
- expect(warningMessage).toContain('issue1421_unregistered.ghost.created')
116
- expect(warningMessage).toContain('events.ts')
126
+ expect(loggerWarn).toHaveBeenCalledTimes(1)
127
+ const [warningMessage, warningFields] = loggerWarn.mock.calls[0] ?? []
128
+ expect(String(warningMessage)).toContain('events.ts')
129
+ expect(warningFields).toEqual(expect.objectContaining({ eventName: 'issue1421_unregistered.ghost.created' }))
117
130
 
118
131
  // Emission is still attempted (non-strict), matching the factory's default behavior
119
132
  expect(emitted).toEqual([
120
133
  expect.objectContaining({ name: 'issue1421_unregistered.ghost.created' }),
121
134
  ])
122
135
  } finally {
123
- warnSpy.mockRestore()
124
136
  }
125
137
  })
126
138
 
127
139
  it('deduplicates repeated warnings for the same undeclared event', async () => {
128
- const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {})
140
+ loggerWarn.mockClear()
129
141
  try {
130
142
  const { engine } = makeFixture()
131
143
 
@@ -138,9 +150,8 @@ describe('DataEngine event contract validation (issue #1421)', () => {
138
150
  })
139
151
  }
140
152
 
141
- expect(warnSpy).toHaveBeenCalledTimes(1)
153
+ expect(loggerWarn).toHaveBeenCalledTimes(1)
142
154
  } finally {
143
- warnSpy.mockRestore()
144
155
  }
145
156
  })
146
157
  })
@@ -18,6 +18,9 @@ import { getEntityIds } from '../encryption/entityIds'
18
18
  import { normalizeCustomFieldValues } from '../custom-fields/normalize'
19
19
  import { parseBooleanToken } from '../boolean'
20
20
  import { isEventDeclared } from '../../modules/events'
21
+ import { createLogger } from '../logger'
22
+
23
+ const logger = createLogger('shared').child({ component: 'data-engine' })
21
24
 
22
25
  const undeclaredEventWarned = new Set<string>()
23
26
 
@@ -25,10 +28,7 @@ function warnIfUndeclaredEvent(eventName: string, context: string): void {
25
28
  if (isEventDeclared(eventName)) return
26
29
  if (undeclaredEventWarned.has(eventName)) return
27
30
  undeclaredEventWarned.add(eventName)
28
- console.warn(
29
- `[data-engine] ${context} is emitting undeclared event "${eventName}". ` +
30
- `Declare it in the owning module's events.ts (createModuleEvents) so the event registry stays authoritative.`,
31
- )
31
+ logger.warn('Emitting undeclared event — declare it in the owning module events.ts (createModuleEvents) so the event registry stays authoritative', { context, eventName })
32
32
  }
33
33
 
34
34
  /** Internal: clear the undeclared-event warning cache. Exposed for tests. */
@@ -618,7 +618,7 @@ export class DefaultDataEngine implements DataEngine {
618
618
  // defers the coverage recompute + fulltext delete, so this stays bounded.
619
619
  // Errors are logged, not thrown — index drift never fails the originating write.
620
620
  await bus.emitEvent('query_index.delete_one', enrichedPayload).catch((err: unknown) => {
621
- console.error('[data-engine] query_index.delete_one emit failed', err)
621
+ logger.error('query_index.delete_one emit failed', { err })
622
622
  })
623
623
  } else {
624
624
  const payload = indexer.buildUpsertPayload
@@ -638,7 +638,7 @@ export class DefaultDataEngine implements DataEngine {
638
638
  // and defers the heavy token-reindex pipeline (build doc + encrypt + decrypt +
639
639
  // tokenize + DELETE + chunked INSERT) so write latency stays bounded.
640
640
  await bus.emitEvent('query_index.upsert_one', enrichedPayload).catch((err: unknown) => {
641
- console.error('[data-engine] query_index.upsert_one emit failed', err)
641
+ logger.error('query_index.upsert_one emit failed', { err })
642
642
  })
643
643
  }
644
644
 
@@ -4,6 +4,9 @@ import { MikroORM } from '@mikro-orm/core'
4
4
  import { ReflectMetadataProvider } from '@mikro-orm/decorators/legacy'
5
5
  import { PostgreSqlDriver, type EntityManager as PostgreSqlEntityManager } from '@mikro-orm/postgresql'
6
6
  import { getSslConfig } from './ssl'
7
+ import { createLogger } from '../logger'
8
+
9
+ const logger = createLogger('shared').child({ component: 'orm' })
7
10
 
8
11
  export type AppMikroORM = MikroORM<PostgreSqlDriver, PostgreSqlEntityManager<PostgreSqlDriver>>
9
12
 
@@ -22,7 +25,7 @@ function setRegisteredEntities(entities: any[]): void {
22
25
 
23
26
  export function registerOrmEntities(entities: any[]) {
24
27
  if (getRegisteredEntities() !== null && process.env.NODE_ENV === 'development') {
25
- console.debug('[Bootstrap] ORM entities re-registered (this may occur during HMR)')
28
+ logger.debug('ORM entities re-registered (this may occur during HMR)')
26
29
  }
27
30
  setRegisteredEntities(entities)
28
31
  }
@@ -106,7 +109,7 @@ export async function getOrm() {
106
109
  const sslConfig = getSslConfig()
107
110
 
108
111
  if (process.env.OM_DB_POOL_DEBUG === '1' || process.env.OM_INTEGRATION_TEST === 'true') {
109
- console.log('[orm] pool config', {
112
+ logger.info('Pool config', {
110
113
  poolMin,
111
114
  poolMax,
112
115
  poolIdleTimeout,
@@ -150,7 +153,7 @@ export async function getOrm() {
150
153
  ssl: sslConfig,
151
154
  onPoolCreated: (pool: any) => {
152
155
  if (process.env.OM_DB_POOL_DEBUG === '1' || process.env.OM_INTEGRATION_TEST === 'true') {
153
- console.log('[orm] pg pool created with options', {
156
+ logger.info('pg pool created with options', {
154
157
  max: pool.options?.max,
155
158
  min: pool.options?.min,
156
159
  idleTimeoutMillis: pool.options?.idleTimeoutMillis,
@@ -9,6 +9,9 @@ import { applyDiOverridesToContainer } from '@open-mercato/shared/modules/overri
9
9
  import { createOptimisticLockGuardService } from '@open-mercato/shared/lib/crud/optimistic-lock'
10
10
  import { getAllOptimisticLockReaders } from '@open-mercato/shared/lib/crud/optimistic-lock-store'
11
11
  import { createCommandOptimisticLockGuardService } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
12
+ import { createLogger } from '../logger'
13
+
14
+ const logger = createLogger('shared').child({ component: 'di' })
12
15
 
13
16
  type DynamicCradle = Record<string, any>
14
17
 
@@ -107,7 +110,7 @@ function setGlobalRegistrars(registrars: DiRegistrar[]): void {
107
110
  export function registerDiRegistrars(registrars: DiRegistrar[]) {
108
111
  const existing = getGlobalRegistrars()
109
112
  if (existing !== null && process.env.NODE_ENV === 'development') {
110
- console.debug('[Bootstrap] DI registrars re-registered (this may occur during HMR)')
113
+ logger.debug('DI registrars re-registered (this may occur during HMR)')
111
114
  }
112
115
  setGlobalRegistrars(registrars)
113
116
  // Force re-bootstrap on HMR — module subscribers may have changed.
@@ -1,5 +1,8 @@
1
1
  import crypto from 'node:crypto'
2
2
  import { isEncryptionDebugEnabled } from './toggles'
3
+ import { createLogger } from '../logger'
4
+
5
+ const logger = createLogger('shared').child({ component: 'encryption' })
3
6
 
4
7
  export type EncryptionPayload = {
5
8
  value: string | null
@@ -31,8 +34,7 @@ export function generateDek(): string {
31
34
  function logDebug(event: string, payload: Record<string, unknown>) {
32
35
  if (!isEncryptionDebugEnabled()) return
33
36
  try {
34
- // eslint-disable-next-line no-console
35
- console.debug('[encryption]', event, payload)
37
+ logger.debug(event, payload)
36
38
  } catch {
37
39
  // ignore
38
40
  }
@@ -1,3 +1,7 @@
1
+ import { createLogger } from '../logger'
2
+
3
+ const logger = createLogger('shared').child({ component: 'encryption' })
4
+
1
5
  // Registration pattern for entity fields (for Turbopack compatibility)
2
6
  export type EntityFieldsRegistry = Record<string, Record<string, string>>
3
7
 
@@ -5,7 +9,7 @@ let _entityFieldsRegistry: EntityFieldsRegistry | null = null
5
9
 
6
10
  export function registerEntityFields(registry: EntityFieldsRegistry) {
7
11
  if (_entityFieldsRegistry !== null && process.env.NODE_ENV === 'development') {
8
- console.debug('[Bootstrap] Entity fields re-registered (this may occur during HMR)')
12
+ logger.debug('Entity fields re-registered (this may occur during HMR)')
9
13
  }
10
14
  _entityFieldsRegistry = registry
11
15
  }
@@ -1,4 +1,7 @@
1
1
  import type { EntityMetadata } from '@mikro-orm/core'
2
+ import { createLogger } from '../logger'
3
+
4
+ const logger = createLogger('shared').child({ component: 'encryption' })
2
5
 
3
6
  // Registration pattern for publishable packages
4
7
  export type EntityIds = Record<string, Record<string, string>>
@@ -8,7 +11,7 @@ let _entityIdLookup: Map<string, string> | null = null
8
11
 
9
12
  export function registerEntityIds(E: EntityIds) {
10
13
  if (_entityIds !== null && process.env.NODE_ENV === 'development') {
11
- console.debug('[Bootstrap] Entity IDs re-registered (this may occur during HMR)')
14
+ logger.debug('Entity IDs re-registered (this may occur during HMR)')
12
15
  }
13
16
  _entityIds = E
14
17
  _entityIdLookup = null // Reset cache on re-registration
@@ -2,8 +2,11 @@ import crypto from 'node:crypto'
2
2
  import { generateDek, hashForLookup } from './aes'
3
3
  import { isEncryptionDebugEnabled, isTenantDataEncryptionEnabled } from './toggles'
4
4
  import { parseBooleanToken } from '../boolean'
5
+ import { createLogger } from '../logger'
5
6
  import { fetchWithTimeout, resolveTimeoutMs } from '../http/fetchWithTimeout'
6
7
 
8
+ const logger = createLogger('shared').child({ component: 'kms' })
9
+
7
10
  const DEFAULT_VAULT_REQUEST_TIMEOUT_MS = 1_000
8
11
  const DEFAULT_VAULT_RECOVERY_COOLDOWN_MS = 30_000
9
12
 
@@ -54,9 +57,7 @@ class FallbackKmsService implements KmsService {
54
57
  try {
55
58
  return await op()
56
59
  } catch (err) {
57
- console.warn('⚠️ [encryption][kms] Primary KMS failed, will try fallback', {
58
- error: (err as Error)?.message || String(err),
59
- })
60
+ logger.warn('Primary KMS failed, will try fallback', { err })
60
61
  return null
61
62
  }
62
63
  }
@@ -199,13 +200,13 @@ export class HashicorpVaultKmsService implements KmsService {
199
200
  this.healthy = false
200
201
  this.misconfigured = true
201
202
  if (this.debugEnabled) {
202
- console.warn('⚠️ [encryption][kms] Vault misconfigured (missing VAULT_ADDR or VAULT_TOKEN)')
203
+ logger.warn('Vault misconfigured (missing VAULT_ADDR or VAULT_TOKEN)')
203
204
  }
204
205
  }
205
206
  if (this.healthy && !HashicorpVaultKmsService.loggedInit && this.debugEnabled) {
206
207
  HashicorpVaultKmsService.loggedInit = true
207
- if(this.debugEnabled) {
208
- console.info('🔐 [encryption][kms] Hashicorp Vault KMS enabled')
208
+ if (this.debugEnabled) {
209
+ logger.info('Hashicorp Vault KMS enabled')
209
210
  }
210
211
  }
211
212
  }
@@ -267,21 +268,17 @@ export class HashicorpVaultKmsService implements KmsService {
267
268
  // not-yet-created tenant DEK is the normal read-before-write path.
268
269
  if (res.status >= 500) this.markTransientFailure()
269
270
  else this.markHealthy()
270
- console.warn('⚠️ [encryption][kms] Vault read failed', { path, status: res.status })
271
+ logger.warn('Vault read failed', { path, status: res.status })
271
272
  return null
272
273
  }
273
274
  this.markHealthy()
274
275
  if (this.debugEnabled) {
275
- console.info('🔍 [encryption][kms] Vault read ok', { path })
276
+ logger.info('Vault read ok', { path })
276
277
  }
277
278
  return (await res.json()) as VaultReadResponse
278
279
  } catch (err) {
279
280
  this.markTransientFailure()
280
- console.warn('⚠️ [encryption][kms] Vault read error', {
281
- path,
282
- error: (err as Error)?.message || String(err),
283
- timeoutMs: this.requestTimeoutMs,
284
- })
281
+ logger.warn('Vault read error', { path, err, timeoutMs: this.requestTimeoutMs })
285
282
  return null
286
283
  }
287
284
  }
@@ -313,19 +310,15 @@ export class HashicorpVaultKmsService implements KmsService {
313
310
  // not an unhealthy Vault — Vault is reachable, so close the breaker.
314
311
  if (typeof opts?.cas === 'number' && res.status === 400) {
315
312
  this.markHealthy()
316
- console.warn('⚠️ [encryption][kms] Vault write CAS conflict (concurrent DEK create)', { path, status: res.status })
313
+ logger.warn('Vault write CAS conflict (concurrent DEK create)', { path, status: res.status })
317
314
  return 'conflict'
318
315
  }
319
316
  this.markTransientFailure()
320
- console.warn('⚠️ [encryption][kms] Vault write failed', { path, status: res.status })
317
+ logger.warn('Vault write failed', { path, status: res.status })
321
318
  return 'error'
322
319
  } catch (err) {
323
320
  this.markTransientFailure()
324
- console.warn('⚠️ [encryption][kms] Vault write error', {
325
- path,
326
- error: (err as Error)?.message || String(err),
327
- timeoutMs: this.requestTimeoutMs,
328
- })
321
+ logger.warn('Vault write error', { path, err, timeoutMs: this.requestTimeoutMs })
329
322
  return 'error'
330
323
  }
331
324
  }
@@ -348,7 +341,7 @@ export class HashicorpVaultKmsService implements KmsService {
348
341
  const res = await this.readVault(path)
349
342
  const key = res?.data?.data?.key
350
343
  if (!key) {
351
- console.warn('⚠️ [encryption][kms] No tenant DEK found in Vault', { tenantId, path })
344
+ logger.warn('No tenant DEK found in Vault', { tenantId, path })
352
345
  return null
353
346
  }
354
347
  const dek: TenantDek = { tenantId, key, fetchedAt: this.now() }
@@ -371,7 +364,7 @@ export class HashicorpVaultKmsService implements KmsService {
371
364
  const key = generateDek()
372
365
  const outcome = await this.writeVault(path, key, { cas: 0 })
373
366
  if (outcome === 'ok') {
374
- console.info('🔑 [encryption][kms] Stored tenant DEK in Vault', { tenantId, path })
367
+ logger.info('Stored tenant DEK in Vault', { tenantId, path })
375
368
  return this.remember({ tenantId, key, fetchedAt: this.now() })
376
369
  }
377
370
  if (outcome === 'conflict') {
@@ -380,11 +373,11 @@ export class HashicorpVaultKmsService implements KmsService {
380
373
  const winner = await this.readVault(path)
381
374
  const winnerKey = winner?.data?.data?.key
382
375
  if (winnerKey) {
383
- console.info('🔑 [encryption][kms] Adopted concurrently-created tenant DEK', { tenantId, path })
376
+ logger.info('Adopted concurrently-created tenant DEK', { tenantId, path })
384
377
  return this.remember({ tenantId, key: winnerKey, fetchedAt: this.now() })
385
378
  }
386
379
  }
387
- console.warn('⚠️ [encryption][kms] Failed to store tenant DEK in Vault', { tenantId, path })
380
+ logger.warn('Failed to store tenant DEK in Vault', { tenantId, path })
388
381
  return null
389
382
  }
390
383
 
@@ -419,12 +412,15 @@ function logDerivedKeyFallbackBanner(opts: DerivedSecret): void {
419
412
  const width = 110
420
413
  const border = `${redBg}${white}${'━'.repeat(width)}${reset}`
421
414
  const body = buildDerivedKeyFallbackBannerLines(opts)
422
- console.warn(border)
423
- for (const line of body) {
424
- const padded = line.padEnd(width - 2, ' ')
425
- console.warn(`${redBg}${white} ${padded} ${reset}`)
426
- }
427
- console.warn(border)
415
+ const bannerLines = [
416
+ border,
417
+ ...body.map((line) => `${redBg}${white} ${line.padEnd(width - 2, ' ')} ${reset}`),
418
+ border,
419
+ ]
420
+ process.stderr.write(bannerLines.join('\n') + '\n')
421
+ logger.warn('Using derived tenant encryption keys (Vault unavailable / no DEK)', {
422
+ secretFingerprint: fingerprintSecret(opts.secret),
423
+ })
428
424
  }
429
425
 
430
426
  export function createKmsService(): KmsService {
@@ -444,9 +440,7 @@ export function createKmsService(): KmsService {
444
440
  notifyFallback?.()
445
441
  return fallback
446
442
  }
447
- console.warn(
448
- '⚠️ [encryption][kms] Vault not healthy or misconfigured (missing VAULT_ADDR/VAULT_TOKEN) and no fallback secret provided; falling back to noop KMS',
449
- )
443
+ logger.warn('Vault not healthy or misconfigured (missing VAULT_ADDR/VAULT_TOKEN) and no fallback secret provided; falling back to noop KMS')
450
444
  return new NoopKmsService()
451
445
  }
452
446
 
@@ -5,6 +5,9 @@ import { TenantDataEncryptionService, parseDecryptedFieldValue } from './tenantD
5
5
  import { isTenantDataEncryptionEnabled } from './toggles'
6
6
  import { isEncryptionDebugEnabled } from './toggles'
7
7
  import { resolveTenantEncryptionService } from './customFieldValues'
8
+ import { createLogger } from '../logger'
9
+
10
+ const logger = createLogger('shared').child({ component: 'encryption' })
8
11
 
9
12
  type Scoped = {
10
13
  tenantId?: string | null
@@ -29,8 +32,7 @@ function resolveScope(entity: Scoped): Scope {
29
32
  function debug(event: string, payload: Record<string, unknown>) {
30
33
  if (!isEncryptionDebugEnabled()) return
31
34
  try {
32
- // eslint-disable-next-line no-console
33
- console.debug(event, payload)
35
+ logger.debug(event, payload)
34
36
  } catch {
35
37
  // ignore
36
38
  }