@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.
- package/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +1 -0
- package/dist/lib/commands/command-bus.js +4 -2
- package/dist/lib/commands/command-bus.js.map +2 -2
- package/dist/lib/commands/command-interceptor-runner.js +4 -2
- package/dist/lib/commands/command-interceptor-runner.js.map +2 -2
- package/dist/lib/commands/flush.js +3 -4
- package/dist/lib/commands/flush.js.map +2 -2
- package/dist/lib/commands/helpers.js +3 -1
- package/dist/lib/commands/helpers.js.map +2 -2
- package/dist/lib/commands/registry.js +5 -3
- package/dist/lib/commands/registry.js.map +2 -2
- package/dist/lib/commands/scope.js +5 -3
- package/dist/lib/commands/scope.js.map +2 -2
- package/dist/lib/crud/advanced-filter-integration.js +3 -1
- package/dist/lib/crud/advanced-filter-integration.js.map +2 -2
- package/dist/lib/crud/cache.js +3 -1
- package/dist/lib/crud/cache.js.map +2 -2
- package/dist/lib/crud/custom-fields.js +4 -2
- package/dist/lib/crud/custom-fields.js.map +2 -2
- package/dist/lib/crud/enricher-runner.js +6 -10
- package/dist/lib/crud/enricher-runner.js.map +2 -2
- package/dist/lib/crud/factory.js +10 -8
- package/dist/lib/crud/factory.js.map +2 -2
- package/dist/lib/crud/interceptor-registry.js +3 -3
- package/dist/lib/crud/interceptor-registry.js.map +2 -2
- package/dist/lib/crud/mutation-guard.js +4 -2
- package/dist/lib/crud/mutation-guard.js.map +2 -2
- package/dist/lib/crud/optimistic-lock.js +5 -6
- package/dist/lib/crud/optimistic-lock.js.map +2 -2
- package/dist/lib/crud/route-mutation-guard.js +3 -1
- package/dist/lib/crud/route-mutation-guard.js.map +2 -2
- package/dist/lib/crud/sync-event-runner.js +3 -1
- package/dist/lib/crud/sync-event-runner.js.map +2 -2
- package/dist/lib/data/engine.js +5 -5
- package/dist/lib/data/engine.js.map +2 -2
- package/dist/lib/db/mikro.js +5 -3
- package/dist/lib/db/mikro.js.map +2 -2
- package/dist/lib/di/container.js +3 -1
- package/dist/lib/di/container.js.map +2 -2
- package/dist/lib/encryption/aes.js +3 -1
- package/dist/lib/encryption/aes.js.map +2 -2
- package/dist/lib/encryption/entityFields.js +3 -1
- package/dist/lib/encryption/entityFields.js.map +2 -2
- package/dist/lib/encryption/entityIds.js +3 -1
- package/dist/lib/encryption/entityIds.js.map +2 -2
- package/dist/lib/encryption/kms.js +25 -32
- package/dist/lib/encryption/kms.js.map +2 -2
- package/dist/lib/encryption/subscriber.js +3 -1
- package/dist/lib/encryption/subscriber.js.map +2 -2
- package/dist/lib/encryption/tenantDataEncryptionService.js +3 -1
- package/dist/lib/encryption/tenantDataEncryptionService.js.map +2 -2
- package/dist/lib/indexers/error-log.js +4 -2
- package/dist/lib/indexers/error-log.js.map +2 -2
- package/dist/lib/indexers/status-log.js +5 -3
- package/dist/lib/indexers/status-log.js.map +2 -2
- package/dist/lib/logger/index.js +28 -0
- package/dist/lib/logger/index.js.map +7 -0
- package/dist/lib/logger/level.js +43 -0
- package/dist/lib/logger/level.js.map +7 -0
- package/dist/lib/logger/transport.console.js +39 -0
- package/dist/lib/logger/transport.console.js.map +7 -0
- package/dist/lib/logger/transport.js +17 -0
- package/dist/lib/logger/transport.js.map +7 -0
- package/dist/lib/logger/transport.pretty.js +85 -0
- package/dist/lib/logger/transport.pretty.js.map +7 -0
- package/dist/lib/logger/transport.server.js +85 -0
- package/dist/lib/logger/transport.server.js.map +7 -0
- package/dist/lib/middleware/page-executor.js +3 -1
- package/dist/lib/middleware/page-executor.js.map +2 -2
- package/dist/lib/modules/registry.js +3 -1
- package/dist/lib/modules/registry.js.map +2 -2
- package/dist/lib/profiler/index.js +3 -2
- package/dist/lib/profiler/index.js.map +2 -2
- package/dist/lib/query/engine.js +5 -6
- package/dist/lib/query/engine.js.map +2 -2
- package/dist/lib/query/query-extension-runner.js +5 -11
- package/dist/lib/query/query-extension-runner.js.map +2 -2
- package/dist/lib/redis/connection.js +3 -1
- package/dist/lib/redis/connection.js.map +2 -2
- package/dist/lib/url.js +7 -5
- package/dist/lib/url.js.map +2 -2
- package/dist/lib/version.js +1 -1
- package/dist/lib/version.js.map +1 -1
- package/dist/modules/analytics.js +3 -1
- package/dist/modules/analytics.js.map +2 -2
- package/dist/modules/dashboard/widgets.js +3 -1
- package/dist/modules/dashboard/widgets.js.map +2 -2
- package/dist/modules/events/factory.js +5 -3
- package/dist/modules/events/factory.js.map +2 -2
- package/dist/modules/overrides.js +16 -34
- package/dist/modules/overrides.js.map +2 -2
- package/dist/modules/registry.js +3 -1
- package/dist/modules/registry.js.map +2 -2
- package/dist/modules/search.js +3 -1
- package/dist/modules/search.js.map +2 -2
- package/dist/modules/widgets/injection-loader.js +6 -6
- package/dist/modules/widgets/injection-loader.js.map +2 -2
- package/dist/modules/widgets/injection-position.js +3 -1
- package/dist/modules/widgets/injection-position.js.map +2 -2
- package/package.json +7 -2
- package/src/lib/commands/__tests__/command-interceptor-runner.test.ts +19 -6
- package/src/lib/commands/__tests__/flush.test.ts +24 -15
- package/src/lib/commands/__tests__/normalizeAuthorUserId.test.ts +14 -2
- package/src/lib/commands/__tests__/registry.test.ts +17 -2
- package/src/lib/commands/__tests__/scope.test.ts +21 -9
- package/src/lib/commands/command-bus.ts +5 -2
- package/src/lib/commands/command-interceptor-runner.ts +5 -2
- package/src/lib/commands/flush.ts +4 -5
- package/src/lib/commands/helpers.ts +9 -2
- package/src/lib/commands/registry.ts +6 -3
- package/src/lib/commands/scope.ts +6 -3
- package/src/lib/crud/__tests__/optimistic-lock.test.ts +21 -6
- package/src/lib/crud/__tests__/route-mutation-guard.test.ts +17 -2
- package/src/lib/crud/__tests__/sync-event-runner.test.ts +17 -3
- package/src/lib/crud/advanced-filter-integration.ts +4 -1
- package/src/lib/crud/cache.ts +4 -1
- package/src/lib/crud/custom-fields.ts +5 -2
- package/src/lib/crud/enricher-runner.ts +7 -10
- package/src/lib/crud/factory.ts +11 -10
- package/src/lib/crud/interceptor-registry.ts +4 -3
- package/src/lib/crud/mutation-guard.ts +5 -2
- package/src/lib/crud/optimistic-lock.ts +6 -10
- package/src/lib/crud/route-mutation-guard.ts +4 -1
- package/src/lib/crud/sync-event-runner.ts +4 -1
- package/src/lib/data/__tests__/engine.event-validation.test.ts +26 -15
- package/src/lib/data/engine.ts +6 -6
- package/src/lib/db/mikro.ts +6 -3
- package/src/lib/di/container.ts +4 -1
- package/src/lib/encryption/aes.ts +4 -2
- package/src/lib/encryption/entityFields.ts +5 -1
- package/src/lib/encryption/entityIds.ts +4 -1
- package/src/lib/encryption/kms.ts +27 -33
- package/src/lib/encryption/subscriber.ts +4 -2
- package/src/lib/encryption/tenantDataEncryptionService.ts +4 -2
- package/src/lib/indexers/__tests__/status-log.test.ts +22 -10
- package/src/lib/indexers/error-log.ts +5 -2
- package/src/lib/indexers/status-log.ts +6 -3
- package/src/lib/logger/__tests__/logger.test.ts +539 -0
- package/src/lib/logger/index.ts +33 -0
- package/src/lib/logger/level.ts +50 -0
- package/src/lib/logger/transport.console.ts +41 -0
- package/src/lib/logger/transport.pretty.ts +94 -0
- package/src/lib/logger/transport.server.ts +125 -0
- package/src/lib/logger/transport.ts +17 -0
- package/src/lib/middleware/page-executor.ts +4 -1
- package/src/lib/modules/__tests__/registry.test.ts +28 -21
- package/src/lib/modules/registry.ts +4 -1
- package/src/lib/profiler/index.ts +4 -2
- package/src/lib/query/__tests__/query-extension-runner.test.ts +21 -7
- package/src/lib/query/engine.ts +7 -10
- package/src/lib/query/query-extension-runner.ts +6 -11
- package/src/lib/redis/connection.ts +5 -1
- package/src/lib/url.ts +10 -5
- package/src/modules/__tests__/overrides.test.ts +17 -3
- package/src/modules/__tests__/route-overrides.test.ts +29 -20
- package/src/modules/analytics.ts +5 -1
- package/src/modules/dashboard/widgets.ts +4 -1
- package/src/modules/events/factory.ts +6 -3
- package/src/modules/overrides.ts +18 -34
- package/src/modules/registry.ts +4 -1
- package/src/modules/search.ts +4 -1
- package/src/modules/widgets/injection-loader.ts +7 -6
- package/src/modules/widgets/injection-position.ts +5 -1
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { LogBindings, Logger } from './index'
|
|
2
|
+
import { parseBooleanToken } from '../boolean'
|
|
3
|
+
import { isLevelEnabled, type LogLevel } from './level'
|
|
4
|
+
import { formatBindings } from './transport.console'
|
|
5
|
+
import { OM_LOG_DESTINATION_ENV, isStderrDestinationToken } from './transport.server'
|
|
6
|
+
|
|
7
|
+
export const OM_LOG_PRETTY_ENV = 'OM_LOG_PRETTY'
|
|
8
|
+
|
|
9
|
+
type EnvSource = Record<string, string | undefined>
|
|
10
|
+
|
|
11
|
+
function readProcessEnv(): EnvSource {
|
|
12
|
+
return typeof process === 'undefined' ? {} : process.env
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Resolve pretty mode from `OM_LOG_PRETTY`, defaulting to on outside production. */
|
|
16
|
+
export function resolvePrettyMode(env: EnvSource = readProcessEnv()): boolean {
|
|
17
|
+
const parsed = parseBooleanToken(env[OM_LOG_PRETTY_ENV])
|
|
18
|
+
if (parsed !== null) return parsed
|
|
19
|
+
return env.NODE_ENV !== 'production'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let cachedPrettyMode: boolean | null = null
|
|
23
|
+
|
|
24
|
+
export function isPrettyModeEnabled(): boolean {
|
|
25
|
+
if (cachedPrettyMode === null) cachedPrettyMode = resolvePrettyMode()
|
|
26
|
+
return cachedPrettyMode
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Internal: clear the memoized pretty mode so tests can vary `OM_LOG_PRETTY`. */
|
|
30
|
+
export function resetLogPrettyCache(): void {
|
|
31
|
+
cachedPrettyMode = null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const LEVEL_LABELS: Record<LogLevel, string> = {
|
|
35
|
+
debug: 'DEBUG',
|
|
36
|
+
info: 'INFO ',
|
|
37
|
+
warn: 'WARN ',
|
|
38
|
+
error: 'ERROR',
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const ANSI_RESET = '\x1b[0m'
|
|
42
|
+
const ANSI_DIM = '\x1b[2m'
|
|
43
|
+
|
|
44
|
+
const LEVEL_COLORS: Record<LogLevel, string> = {
|
|
45
|
+
debug: ANSI_DIM,
|
|
46
|
+
info: '\x1b[36m',
|
|
47
|
+
warn: '\x1b[33m',
|
|
48
|
+
error: '\x1b[31m',
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
type WritableLogStream = { write(chunk: string): unknown; isTTY?: boolean }
|
|
52
|
+
|
|
53
|
+
function resolveStream(): WritableLogStream {
|
|
54
|
+
const useStderr = isStderrDestinationToken(readProcessEnv()[OM_LOG_DESTINATION_ENV])
|
|
55
|
+
return useStderr ? process.stderr : process.stdout
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function formatTimestamp(date: Date): string {
|
|
59
|
+
const hours = String(date.getHours()).padStart(2, '0')
|
|
60
|
+
const minutes = String(date.getMinutes()).padStart(2, '0')
|
|
61
|
+
const seconds = String(date.getSeconds()).padStart(2, '0')
|
|
62
|
+
const millis = String(date.getMilliseconds()).padStart(3, '0')
|
|
63
|
+
return `${hours}:${minutes}:${seconds}.${millis}`
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function createPrettyLogger(namespace: string, bindings: LogBindings = {}): Logger {
|
|
67
|
+
const emit = (level: LogLevel, msg: string, fields?: LogBindings): void => {
|
|
68
|
+
if (!isLevelEnabled(level)) return
|
|
69
|
+
const stream = resolveStream()
|
|
70
|
+
const { err, ...rest } = { ...bindings, ...fields }
|
|
71
|
+
let scope = namespace
|
|
72
|
+
if (typeof rest.component === 'string' && rest.component) {
|
|
73
|
+
scope = `${namespace}:${rest.component}`
|
|
74
|
+
delete rest.component
|
|
75
|
+
}
|
|
76
|
+
const tail = formatBindings(err instanceof Error || err === undefined ? rest : { ...rest, err })
|
|
77
|
+
const colored = stream.isTTY === true
|
|
78
|
+
const timestamp = formatTimestamp(new Date())
|
|
79
|
+
const timestampPart = colored ? `${ANSI_DIM}${timestamp}${ANSI_RESET}` : timestamp
|
|
80
|
+
const label = LEVEL_LABELS[level]
|
|
81
|
+
const levelPart = colored ? `${LEVEL_COLORS[level]}${label}${ANSI_RESET}` : label
|
|
82
|
+
let line = `${timestampPart} ${levelPart} [${scope}] ${msg}`
|
|
83
|
+
if (tail) line += ` ${tail}`
|
|
84
|
+
if (err instanceof Error) line += `\n${err.stack ?? err.message}`
|
|
85
|
+
stream.write(`${line}\n`)
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
debug: (msg, fields) => emit('debug', msg, fields),
|
|
89
|
+
info: (msg, fields) => emit('info', msg, fields),
|
|
90
|
+
warn: (msg, fields) => emit('warn', msg, fields),
|
|
91
|
+
error: (msg, fields) => emit('error', msg, fields),
|
|
92
|
+
child: (childBindings) => createPrettyLogger(namespace, { ...bindings, ...childBindings }),
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { LogBindings, Logger } from './index'
|
|
2
|
+
import { getLogLevel } from './level'
|
|
3
|
+
import { createConsoleLogger } from './transport.console'
|
|
4
|
+
|
|
5
|
+
const REDACT_PATHS = [
|
|
6
|
+
'password',
|
|
7
|
+
'*.password',
|
|
8
|
+
'token',
|
|
9
|
+
'*.token',
|
|
10
|
+
'secret',
|
|
11
|
+
'*.secret',
|
|
12
|
+
'authorization',
|
|
13
|
+
'*.authorization',
|
|
14
|
+
'headers.authorization',
|
|
15
|
+
'req.headers.authorization',
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
const REDACT_CENSOR = '[Redacted]'
|
|
19
|
+
|
|
20
|
+
export const OM_LOG_DESTINATION_ENV = 'OM_LOG_DESTINATION'
|
|
21
|
+
|
|
22
|
+
type PinoBaseLogger = {
|
|
23
|
+
debug(fields: Record<string, unknown>, msg: string): void
|
|
24
|
+
info(fields: Record<string, unknown>, msg: string): void
|
|
25
|
+
warn(fields: Record<string, unknown>, msg: string): void
|
|
26
|
+
error(fields: Record<string, unknown>, msg: string): void
|
|
27
|
+
child(bindings: Record<string, unknown>): PinoBaseLogger
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type PinoFactory = (options: Record<string, unknown>, destination?: unknown) => PinoBaseLogger
|
|
31
|
+
|
|
32
|
+
type NodeModuleBuiltin = {
|
|
33
|
+
createRequire(basePath: string): (id: string) => unknown
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type ProcessWithBuiltins = {
|
|
37
|
+
cwd(): string
|
|
38
|
+
getBuiltinModule?(id: string): unknown
|
|
39
|
+
env?: Record<string, string | undefined>
|
|
40
|
+
stderr?: unknown
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function isStderrDestinationToken(raw: string | undefined): boolean {
|
|
44
|
+
return typeof raw === 'string' && raw.trim().toLowerCase() === 'stderr'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function resolveDestinationStream(nodeProcess: ProcessWithBuiltins): unknown {
|
|
48
|
+
return isStderrDestinationToken(nodeProcess.env?.[OM_LOG_DESTINATION_ENV])
|
|
49
|
+
? nodeProcess.stderr
|
|
50
|
+
: undefined
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let cachedRoot: PinoBaseLogger | null | undefined
|
|
54
|
+
let rootGeneration = 0
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* pino is loaded lazily via a runtime `require` obtained from
|
|
58
|
+
* `process.getBuiltinModule('node:module')` — never through a static import —
|
|
59
|
+
* so browser/edge bundles that reach this file never resolve pino or
|
|
60
|
+
* `node:module`. Any load failure falls back to the console transport.
|
|
61
|
+
*/
|
|
62
|
+
function loadPinoRoot(): PinoBaseLogger | null {
|
|
63
|
+
if (cachedRoot !== undefined) return cachedRoot
|
|
64
|
+
try {
|
|
65
|
+
const nodeProcess = (typeof process === 'undefined' ? undefined : process) as
|
|
66
|
+
| ProcessWithBuiltins
|
|
67
|
+
| undefined
|
|
68
|
+
const moduleBuiltin = nodeProcess?.getBuiltinModule?.('node:module') as
|
|
69
|
+
| NodeModuleBuiltin
|
|
70
|
+
| undefined
|
|
71
|
+
if (!nodeProcess || !moduleBuiltin) {
|
|
72
|
+
cachedRoot = null
|
|
73
|
+
return cachedRoot
|
|
74
|
+
}
|
|
75
|
+
const requireFromApp = moduleBuiltin.createRequire(`${nodeProcess.cwd()}/package.json`)
|
|
76
|
+
const pinoExport = requireFromApp('pino') as PinoFactory | { default: PinoFactory }
|
|
77
|
+
const pinoFactory = typeof pinoExport === 'function' ? pinoExport : pinoExport.default
|
|
78
|
+
const destination = resolveDestinationStream(nodeProcess)
|
|
79
|
+
const options = {
|
|
80
|
+
level: getLogLevel(),
|
|
81
|
+
redact: { paths: REDACT_PATHS, censor: REDACT_CENSOR },
|
|
82
|
+
}
|
|
83
|
+
cachedRoot = destination ? pinoFactory(options, destination) : pinoFactory(options)
|
|
84
|
+
} catch {
|
|
85
|
+
cachedRoot = null
|
|
86
|
+
}
|
|
87
|
+
return cachedRoot
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function wrapPinoLogger(pinoLogger: PinoBaseLogger): Logger {
|
|
91
|
+
return {
|
|
92
|
+
debug: (msg, fields) => pinoLogger.debug(fields ?? {}, msg),
|
|
93
|
+
info: (msg, fields) => pinoLogger.info(fields ?? {}, msg),
|
|
94
|
+
warn: (msg, fields) => pinoLogger.warn(fields ?? {}, msg),
|
|
95
|
+
error: (msg, fields) => pinoLogger.error(fields ?? {}, msg),
|
|
96
|
+
child: (childBindings) => wrapPinoLogger(pinoLogger.child(childBindings)),
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function createServerLogger(namespace: string, bindings: LogBindings = {}): Logger {
|
|
101
|
+
let delegate: Logger | null = null
|
|
102
|
+
let delegateGeneration = -1
|
|
103
|
+
const resolveDelegate = (): Logger => {
|
|
104
|
+
if (delegate && delegateGeneration === rootGeneration) return delegate
|
|
105
|
+
delegateGeneration = rootGeneration
|
|
106
|
+
const root = loadPinoRoot()
|
|
107
|
+
delegate = root
|
|
108
|
+
? wrapPinoLogger(root.child({ name: namespace, ...bindings }))
|
|
109
|
+
: createConsoleLogger(namespace, bindings)
|
|
110
|
+
return delegate
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
debug: (msg, fields) => resolveDelegate().debug(msg, fields),
|
|
114
|
+
info: (msg, fields) => resolveDelegate().info(msg, fields),
|
|
115
|
+
warn: (msg, fields) => resolveDelegate().warn(msg, fields),
|
|
116
|
+
error: (msg, fields) => resolveDelegate().error(msg, fields),
|
|
117
|
+
child: (childBindings) => createServerLogger(namespace, { ...bindings, ...childBindings }),
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Internal: clear the cached pino root so tests can re-run transport selection. */
|
|
122
|
+
export function resetServerLoggerCache(): void {
|
|
123
|
+
cachedRoot = undefined
|
|
124
|
+
rootGeneration += 1
|
|
125
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Logger } from './index'
|
|
2
|
+
import { createConsoleLogger } from './transport.console'
|
|
3
|
+
import { createPrettyLogger, isPrettyModeEnabled } from './transport.pretty'
|
|
4
|
+
import { createServerLogger } from './transport.server'
|
|
5
|
+
|
|
6
|
+
function isNodeServerRuntime(): boolean {
|
|
7
|
+
if (typeof window !== 'undefined') return false
|
|
8
|
+
if (typeof process === 'undefined') return false
|
|
9
|
+
if (process.env.NEXT_RUNTIME === 'edge') return false
|
|
10
|
+
return true
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Pick the runtime-appropriate transport: pretty or pino on the Node server, console elsewhere. */
|
|
14
|
+
export function selectTransport(namespace: string): Logger {
|
|
15
|
+
if (!isNodeServerRuntime()) return createConsoleLogger(namespace)
|
|
16
|
+
return isPrettyModeEnabled() ? createPrettyLogger(namespace) : createServerLogger(namespace)
|
|
17
|
+
}
|
|
@@ -7,6 +7,9 @@ import type {
|
|
|
7
7
|
} from '@open-mercato/shared/modules/middleware/page'
|
|
8
8
|
import { CONTINUE_PAGE_MIDDLEWARE, matchPageMiddlewareTarget } from '@open-mercato/shared/modules/middleware/page'
|
|
9
9
|
import { applyPageGuardOverridesToEntries } from '@open-mercato/shared/modules/overrides'
|
|
10
|
+
import { createLogger } from '../logger'
|
|
11
|
+
|
|
12
|
+
const logger = createLogger('shared').child({ component: 'middleware' })
|
|
10
13
|
|
|
11
14
|
type ExecutePageMiddlewareArgs = {
|
|
12
15
|
entries: PageMiddlewareRegistryEntry[]
|
|
@@ -49,7 +52,7 @@ export async function executePageMiddleware(args: ExecutePageMiddlewareArgs): Pr
|
|
|
49
52
|
if (onError) {
|
|
50
53
|
onError(error, { id: middleware.id, priority: middleware.priority })
|
|
51
54
|
} else {
|
|
52
|
-
|
|
55
|
+
logger.error('Page middleware execution failed', { id: middleware.id, err: error })
|
|
53
56
|
}
|
|
54
57
|
throw error
|
|
55
58
|
}
|
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
import type { Module } from '@open-mercato/shared/modules/registry'
|
|
2
|
+
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
3
|
+
|
|
4
|
+
jest.mock('@open-mercato/shared/lib/logger', () => {
|
|
5
|
+
const mocked = {
|
|
6
|
+
debug: jest.fn(),
|
|
7
|
+
info: jest.fn(),
|
|
8
|
+
warn: jest.fn(),
|
|
9
|
+
error: jest.fn(),
|
|
10
|
+
child: jest.fn(),
|
|
11
|
+
}
|
|
12
|
+
mocked.child.mockImplementation(() => mocked)
|
|
13
|
+
return { createLogger: jest.fn(() => mocked) }
|
|
14
|
+
})
|
|
15
|
+
const loggerDebug = createLogger('shared').debug as jest.Mock
|
|
16
|
+
|
|
2
17
|
|
|
3
18
|
const GLOBAL_KEY = '__openMercatoModulesRegistry__'
|
|
4
19
|
|
|
@@ -85,31 +100,23 @@ describe('shared modules registry', () => {
|
|
|
85
100
|
|
|
86
101
|
it('emits the HMR debug log on re-registration in development', () => {
|
|
87
102
|
process.env.NODE_ENV = 'development'
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
)
|
|
97
|
-
} finally {
|
|
98
|
-
debugSpy.mockRestore()
|
|
99
|
-
}
|
|
103
|
+
loggerDebug.mockClear()
|
|
104
|
+
const registry = loadRegistry()
|
|
105
|
+
registry.registerModules(sampleModules)
|
|
106
|
+
expect(loggerDebug).not.toHaveBeenCalled()
|
|
107
|
+
registry.registerModules(sampleModules)
|
|
108
|
+
expect(loggerDebug).toHaveBeenCalledWith(
|
|
109
|
+
'Modules re-registered (this may occur during HMR)',
|
|
110
|
+
)
|
|
100
111
|
})
|
|
101
112
|
|
|
102
113
|
it('does not emit the HMR debug log when NODE_ENV is not development', () => {
|
|
103
114
|
process.env.NODE_ENV = 'production'
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
expect(debugSpy).not.toHaveBeenCalled()
|
|
110
|
-
} finally {
|
|
111
|
-
debugSpy.mockRestore()
|
|
112
|
-
}
|
|
115
|
+
loggerDebug.mockClear()
|
|
116
|
+
const registry = loadRegistry()
|
|
117
|
+
registry.registerModules(sampleModules)
|
|
118
|
+
registry.registerModules(sampleModules)
|
|
119
|
+
expect(loggerDebug).not.toHaveBeenCalled()
|
|
113
120
|
})
|
|
114
121
|
|
|
115
122
|
it('does not let i18n-only registrations clobber runtime module contracts', () => {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { Module } from '@open-mercato/shared/modules/registry'
|
|
2
2
|
import { applyModuleOverridesToModules } from '@open-mercato/shared/modules/overrides'
|
|
3
3
|
import { invalidateDictionaryCache } from '../i18n/dictionary-cache'
|
|
4
|
+
import { createLogger } from '../logger'
|
|
5
|
+
|
|
6
|
+
const logger = createLogger('shared').child({ component: 'modules-registry' })
|
|
4
7
|
|
|
5
8
|
// Registration pattern for publishable packages.
|
|
6
9
|
// Use globalThis to survive tsx/esbuild module duplication where the same
|
|
@@ -49,7 +52,7 @@ function mergeI18nModules(existing: Module[], incoming: Module[]): Module[] {
|
|
|
49
52
|
export function registerModules(modules: Module[]) {
|
|
50
53
|
const existing = getGlobalModules()
|
|
51
54
|
if (existing !== null && process.env.NODE_ENV === 'development') {
|
|
52
|
-
|
|
55
|
+
logger.debug('Modules re-registered (this may occur during HMR)')
|
|
53
56
|
}
|
|
54
57
|
const nextModules = applyModuleOverridesToModules(modules)
|
|
55
58
|
const shouldMergeI18nOnly = existing !== null
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { parseBooleanToken } from '../boolean'
|
|
2
|
+
import { createLogger } from '../logger'
|
|
3
|
+
|
|
4
|
+
const logger = createLogger('shared').child({ component: 'profiler' })
|
|
2
5
|
|
|
3
6
|
const ROUND_PRECISION = 1000
|
|
4
7
|
const NS_PER_MS = BigInt(1_000_000)
|
|
@@ -267,9 +270,8 @@ class ProfilerImpl implements Profiler {
|
|
|
267
270
|
if (this.root.meta && Object.keys(this.root.meta).length > 0) {
|
|
268
271
|
snapshot.meta = this.root.meta
|
|
269
272
|
}
|
|
270
|
-
const serialized = JSON.stringify(snapshot, null, 2)
|
|
271
273
|
try {
|
|
272
|
-
|
|
274
|
+
logger.info('Profiler snapshot', { label: this.root.loggerLabel, snapshot })
|
|
273
275
|
} catch {
|
|
274
276
|
// ignore logging failures
|
|
275
277
|
}
|
|
@@ -14,6 +14,22 @@ import {
|
|
|
14
14
|
runAfterQueryPipeline,
|
|
15
15
|
} from '../query-extension-runner'
|
|
16
16
|
import type { SyncQueryEventPayload } from '../sync-query-event-types'
|
|
17
|
+
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
18
|
+
|
|
19
|
+
jest.mock('@open-mercato/shared/lib/logger', () => {
|
|
20
|
+
const mocked = {
|
|
21
|
+
debug: jest.fn(),
|
|
22
|
+
info: jest.fn(),
|
|
23
|
+
warn: jest.fn(),
|
|
24
|
+
error: jest.fn(),
|
|
25
|
+
child: jest.fn(),
|
|
26
|
+
}
|
|
27
|
+
mocked.child.mockImplementation(() => mocked)
|
|
28
|
+
return { createLogger: jest.fn(() => mocked) }
|
|
29
|
+
})
|
|
30
|
+
const loggerError = createLogger('shared').error as jest.Mock
|
|
31
|
+
const loggerWarn = createLogger('shared').warn as jest.Mock
|
|
32
|
+
|
|
17
33
|
|
|
18
34
|
const ctx = { resolve: jest.fn() }
|
|
19
35
|
|
|
@@ -204,7 +220,7 @@ describe('runBeforeQueryEvent', () => {
|
|
|
204
220
|
})
|
|
205
221
|
|
|
206
222
|
it('catches thrown errors and returns blocked', async () => {
|
|
207
|
-
|
|
223
|
+
loggerError.mockClear()
|
|
208
224
|
registerSyncSubscribers([
|
|
209
225
|
makeQuerySub(
|
|
210
226
|
'customers.person.querying',
|
|
@@ -217,7 +233,6 @@ describe('runBeforeQueryEvent', () => {
|
|
|
217
233
|
const result = await runBeforeQueryEvent(payload, ctx)
|
|
218
234
|
expect(result.ok).toBe(false)
|
|
219
235
|
expect(result.errorStatus).toBe(500)
|
|
220
|
-
consoleSpy.mockRestore()
|
|
221
236
|
})
|
|
222
237
|
})
|
|
223
238
|
|
|
@@ -266,7 +281,7 @@ describe('runAfterQueryEvent', () => {
|
|
|
266
281
|
})
|
|
267
282
|
|
|
268
283
|
it('rejects invalid modifiedResult shape', async () => {
|
|
269
|
-
|
|
284
|
+
loggerWarn.mockClear()
|
|
270
285
|
registerSyncSubscribers([
|
|
271
286
|
makeQuerySub(
|
|
272
287
|
'customers.person.queried',
|
|
@@ -278,14 +293,14 @@ describe('runAfterQueryEvent', () => {
|
|
|
278
293
|
const payload = makeQueriedPayload()
|
|
279
294
|
const result = await runAfterQueryEvent(payload, ctx)
|
|
280
295
|
expect(result.modifiedResult).toBeUndefined()
|
|
281
|
-
expect(
|
|
296
|
+
expect(loggerWarn).toHaveBeenCalledWith(
|
|
282
297
|
expect.stringContaining('invalid modifiedResult shape'),
|
|
298
|
+
expect.objectContaining({ subscriberId: expect.any(String) }),
|
|
283
299
|
)
|
|
284
|
-
consoleSpy.mockRestore()
|
|
285
300
|
})
|
|
286
301
|
|
|
287
302
|
it('continues on subscriber error', async () => {
|
|
288
|
-
|
|
303
|
+
loggerError.mockClear()
|
|
289
304
|
const handler2 = jest.fn().mockResolvedValue({
|
|
290
305
|
modifiedResult: {
|
|
291
306
|
items: [{ id: '1', transformed: true }],
|
|
@@ -307,7 +322,6 @@ describe('runAfterQueryEvent', () => {
|
|
|
307
322
|
const result = await runAfterQueryEvent(payload, ctx)
|
|
308
323
|
expect(handler2).toHaveBeenCalled()
|
|
309
324
|
expect(result.modifiedResult).toBeDefined()
|
|
310
|
-
consoleSpy.mockRestore()
|
|
311
325
|
})
|
|
312
326
|
})
|
|
313
327
|
|
package/src/lib/query/engine.ts
CHANGED
|
@@ -22,6 +22,9 @@ import {
|
|
|
22
22
|
} from '../crud/custom-field-definition-index'
|
|
23
23
|
import { resolveEncryptedSortFields, resolveEncryptedSortMaxRows, sortRowsInMemory } from './encrypted-sort'
|
|
24
24
|
import { mapWithConcurrency } from './bounded-decrypt'
|
|
25
|
+
import { createLogger } from '../logger'
|
|
26
|
+
|
|
27
|
+
const logger = createLogger('shared').child({ component: 'query' })
|
|
25
28
|
|
|
26
29
|
const DECRYPT_CONCURRENCY = 8
|
|
27
30
|
|
|
@@ -138,11 +141,7 @@ export function resolveEntityTableName(em: EntityManager | undefined, entity: En
|
|
|
138
141
|
}
|
|
139
142
|
|
|
140
143
|
const fallback = pluralizeBaseName(rawName || '')
|
|
141
|
-
|
|
142
|
-
`[QueryEngine] Could not resolve entity "${entity}" via ORM metadata. ` +
|
|
143
|
-
`Falling back to table name "${fallback}". ` +
|
|
144
|
-
`Ensure the entity ID segment matches the class name convention.`
|
|
145
|
-
)
|
|
144
|
+
logger.warn('Could not resolve entity via ORM metadata; falling back to table name — ensure the entity ID segment matches the class name convention', { entity, fallback })
|
|
146
145
|
entityTableCache.set(entity, fallback)
|
|
147
146
|
return fallback
|
|
148
147
|
}
|
|
@@ -945,7 +944,7 @@ export class BasicQueryEngine implements QueryEngine {
|
|
|
945
944
|
)
|
|
946
945
|
return { ...item, ...decrypted }
|
|
947
946
|
} catch (err) {
|
|
948
|
-
|
|
947
|
+
logger.error('Error decrypting entity payload', { err })
|
|
949
948
|
return item
|
|
950
949
|
}
|
|
951
950
|
}
|
|
@@ -1409,10 +1408,8 @@ export class BasicQueryEngine implements QueryEngine {
|
|
|
1409
1408
|
|
|
1410
1409
|
private logSearchDebug(event: string, payload: Record<string, unknown>) {
|
|
1411
1410
|
try {
|
|
1412
|
-
|
|
1413
|
-
} catch {
|
|
1414
|
-
console.info('[query:search]', event, payload)
|
|
1415
|
-
}
|
|
1411
|
+
logger.debug(event, payload)
|
|
1412
|
+
} catch {}
|
|
1416
1413
|
}
|
|
1417
1414
|
|
|
1418
1415
|
private resolveOrganizationScope(opts: QueryOptions): { ids: string[]; includeNull: boolean } | null {
|
|
@@ -18,6 +18,9 @@ import { getEnrichersForEntity } from '../crud/enricher-registry'
|
|
|
18
18
|
import { applyResponseEnrichers, applyResponseEnricherToRecord } from '../crud/enricher-runner'
|
|
19
19
|
import type { SyncQueryEventPayload, SyncQueryEventResult } from './sync-query-event-types'
|
|
20
20
|
import type { QueryOptions, QueryResult } from './types'
|
|
21
|
+
import { createLogger } from '../logger'
|
|
22
|
+
|
|
23
|
+
const logger = createLogger('shared').child({ component: 'query-extensions' })
|
|
21
24
|
|
|
22
25
|
// ---------------------------------------------------------------------------
|
|
23
26
|
// Entity-to-event-ID helpers
|
|
@@ -96,10 +99,7 @@ export async function runBeforeQueryEvent(
|
|
|
96
99
|
currentQuery = { ...currentQuery, ...queryResult.modifiedQuery }
|
|
97
100
|
}
|
|
98
101
|
} catch (error) {
|
|
99
|
-
|
|
100
|
-
`[query-extension] before-query subscriber failed: ${subscriber.metadata.id}`,
|
|
101
|
-
error,
|
|
102
|
-
)
|
|
102
|
+
logger.error('Before-query subscriber failed', { subscriberId: subscriber.metadata.id, err: error })
|
|
103
103
|
return {
|
|
104
104
|
ok: false,
|
|
105
105
|
errorMessage: `Subscriber ${subscriber.metadata.id} threw unexpectedly`,
|
|
@@ -148,16 +148,11 @@ export async function runAfterQueryEvent(
|
|
|
148
148
|
if (isValidQueryResult(queryResult.modifiedResult)) {
|
|
149
149
|
currentResult = queryResult.modifiedResult
|
|
150
150
|
} else {
|
|
151
|
-
|
|
152
|
-
`[query-extension] after-query subscriber ${subscriber.metadata.id} returned invalid modifiedResult shape — ignored`,
|
|
153
|
-
)
|
|
151
|
+
logger.warn('After-query subscriber returned invalid modifiedResult shape — ignored', { subscriberId: subscriber.metadata.id })
|
|
154
152
|
}
|
|
155
153
|
}
|
|
156
154
|
} catch (error) {
|
|
157
|
-
|
|
158
|
-
`[query-extension] after-query subscriber failed: ${subscriber.metadata.id}`,
|
|
159
|
-
error,
|
|
160
|
-
)
|
|
155
|
+
logger.error('After-query subscriber failed', { subscriberId: subscriber.metadata.id, err: error })
|
|
161
156
|
}
|
|
162
157
|
}
|
|
163
158
|
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { createLogger } from '../logger'
|
|
2
|
+
|
|
3
|
+
const logger = createLogger('shared').child({ component: 'redis' })
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
6
|
* Shared Redis connection utilities.
|
|
3
7
|
*
|
|
@@ -77,7 +81,7 @@ export function parseRedisUrl(url: string): ParsedRedisConnection {
|
|
|
77
81
|
}
|
|
78
82
|
} catch {
|
|
79
83
|
const safeUrl = url.replace(/\/\/[^:]*:[^@]*@/, '//<redacted>@')
|
|
80
|
-
|
|
84
|
+
logger.warn('Failed to parse Redis URL, falling back to localhost:6379', { url: safeUrl })
|
|
81
85
|
return { host: 'localhost', port: 6379 }
|
|
82
86
|
}
|
|
83
87
|
}
|
package/src/lib/url.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { createLogger } from './logger'
|
|
2
|
+
|
|
3
|
+
const logger = createLogger('shared').child({ component: 'origin-check' })
|
|
4
|
+
|
|
1
5
|
const DEFAULT_DEV_APP_URL = 'http://localhost:3000'
|
|
2
6
|
|
|
3
7
|
type EnvLike = Record<string, string | undefined> & {
|
|
@@ -125,10 +129,11 @@ function logOriginDebugContext(
|
|
|
125
129
|
nodeEnv?: string,
|
|
126
130
|
): void {
|
|
127
131
|
if (level === 'warn' && nodeEnv === 'test') return
|
|
128
|
-
const log =
|
|
132
|
+
const log = (msg: string, fields: Record<string, unknown>) =>
|
|
133
|
+
level === 'warn' ? logger.warn(msg, fields) : logger.error(msg, fields)
|
|
129
134
|
|
|
130
135
|
if (typeof input === 'string') {
|
|
131
|
-
log('
|
|
136
|
+
log('Origin check rejected string input', {
|
|
132
137
|
requestUrl: input,
|
|
133
138
|
rejectedOrigin: rejectedOrigin ?? null,
|
|
134
139
|
allowedOrigins: Array.from(allowedOrigins),
|
|
@@ -137,14 +142,14 @@ function logOriginDebugContext(
|
|
|
137
142
|
}
|
|
138
143
|
|
|
139
144
|
if (!input) {
|
|
140
|
-
log('
|
|
145
|
+
log('Origin check rejected empty input', {
|
|
141
146
|
rejectedOrigin: rejectedOrigin ?? null,
|
|
142
147
|
allowedOrigins: Array.from(allowedOrigins),
|
|
143
148
|
})
|
|
144
149
|
return
|
|
145
150
|
}
|
|
146
151
|
|
|
147
|
-
log('
|
|
152
|
+
log('Origin check rejected request', {
|
|
148
153
|
requestUrl: input.url,
|
|
149
154
|
host: input.headers.get('host'),
|
|
150
155
|
forwardedHost: input.headers.get('x-forwarded-host'),
|
|
@@ -270,7 +275,7 @@ export function mapSecurityEmailUrlError(
|
|
|
270
275
|
return { status: 400, body: { error: 'Invalid request origin' } }
|
|
271
276
|
}
|
|
272
277
|
if (error instanceof AppOriginConfigurationError) {
|
|
273
|
-
|
|
278
|
+
logger.error('APP_URL is required in production', { scope: mapping.scope })
|
|
274
279
|
return { status: 500, body: { error: mapping.configMessage } }
|
|
275
280
|
}
|
|
276
281
|
return null
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* Spec: `.ai/specs/implemented/2026-05-04-modules-ts-unified-overrides.md`.
|
|
10
10
|
*/
|
|
11
11
|
import {
|
|
12
|
+
|
|
12
13
|
applyModuleOverridesFromEnabledModules,
|
|
13
14
|
composeInjectionWidgetOverrides,
|
|
14
15
|
composeSubscriberOverrides,
|
|
@@ -18,6 +19,20 @@ import {
|
|
|
18
19
|
type ModuleEntryWithOverrides,
|
|
19
20
|
type ModuleOverrideEntry,
|
|
20
21
|
} from '../overrides'
|
|
22
|
+
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
23
|
+
|
|
24
|
+
jest.mock('@open-mercato/shared/lib/logger', () => {
|
|
25
|
+
const mocked = {
|
|
26
|
+
debug: jest.fn(),
|
|
27
|
+
info: jest.fn(),
|
|
28
|
+
warn: jest.fn(),
|
|
29
|
+
error: jest.fn(),
|
|
30
|
+
child: jest.fn(),
|
|
31
|
+
}
|
|
32
|
+
mocked.child.mockImplementation(() => mocked)
|
|
33
|
+
return { createLogger: jest.fn(() => mocked) }
|
|
34
|
+
})
|
|
35
|
+
const loggerWarn = createLogger('shared').warn as jest.Mock
|
|
21
36
|
|
|
22
37
|
beforeEach(() => {
|
|
23
38
|
resetModuleOverrideAppliersForTests()
|
|
@@ -77,7 +92,7 @@ describe('applyModuleOverridesFromEnabledModules', () => {
|
|
|
77
92
|
})
|
|
78
93
|
|
|
79
94
|
it('routes built-in domains to default appliers without unwired warnings', () => {
|
|
80
|
-
|
|
95
|
+
loggerWarn.mockClear()
|
|
81
96
|
|
|
82
97
|
applyModuleOverridesFromEnabledModules([
|
|
83
98
|
{
|
|
@@ -93,7 +108,7 @@ describe('applyModuleOverridesFromEnabledModules', () => {
|
|
|
93
108
|
},
|
|
94
109
|
])
|
|
95
110
|
|
|
96
|
-
const unwiredCalls =
|
|
111
|
+
const unwiredCalls = loggerWarn.mock.calls.filter((args) =>
|
|
97
112
|
typeof args[0] === 'string' && args[0].includes('not yet wired'),
|
|
98
113
|
)
|
|
99
114
|
expect(unwiredCalls).toHaveLength(0)
|
|
@@ -105,7 +120,6 @@ describe('applyModuleOverridesFromEnabledModules', () => {
|
|
|
105
120
|
'example.todo.created.notify': null,
|
|
106
121
|
})
|
|
107
122
|
|
|
108
|
-
warnSpy.mockRestore()
|
|
109
123
|
})
|
|
110
124
|
|
|
111
125
|
it('does NOT consume the legacy `aiAgentOverrides` / `aiToolOverrides` keys', () => {
|