@spfn/core 0.3.0-beta.2 → 0.3.0-beta.21

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 (94) hide show
  1. package/README.md +343 -16
  2. package/dist/app-config/index.d.ts +134 -0
  3. package/dist/app-config/index.js +58 -0
  4. package/dist/app-config/index.js.map +1 -0
  5. package/dist/authz/index.js +1 -381
  6. package/dist/authz/index.js.map +1 -1
  7. package/dist/{boss-D16fO2oG.d.ts → boss-BiDCbbAQ.d.ts} +60 -7
  8. package/dist/codegen/index.d.ts +93 -15
  9. package/dist/codegen/index.js +626 -165
  10. package/dist/codegen/index.js.map +1 -1
  11. package/dist/config/index.d.ts +0 -63
  12. package/dist/config/index.js +15 -15
  13. package/dist/config/index.js.map +1 -1
  14. package/dist/contract/index.d.ts +40 -17
  15. package/dist/contract/index.js +147 -23
  16. package/dist/contract/index.js.map +1 -1
  17. package/dist/db/index.d.ts +201 -34
  18. package/dist/db/index.js +294 -158
  19. package/dist/db/index.js.map +1 -1
  20. package/dist/{define-middleware-DfDP39Nq.d.ts → define-middleware-CVKgqo8S.d.ts} +1 -1
  21. package/dist/env/index.d.ts +6 -442
  22. package/dist/env/index.js +13 -16
  23. package/dist/env/index.js.map +1 -1
  24. package/dist/env/loader.js +24 -1
  25. package/dist/env/loader.js.map +1 -1
  26. package/dist/error-TsUIjp_x.d.ts +157 -0
  27. package/dist/errors/index.js +1 -381
  28. package/dist/errors/index.js.map +1 -1
  29. package/dist/event/index.d.ts +5 -5
  30. package/dist/event/index.js +32 -1
  31. package/dist/event/index.js.map +1 -1
  32. package/dist/event/sse/client.d.ts +18 -4
  33. package/dist/event/sse/client.js +38 -2
  34. package/dist/event/sse/client.js.map +1 -1
  35. package/dist/event/sse/index.d.ts +5 -4
  36. package/dist/event/ws/client.d.ts +3 -2
  37. package/dist/event/ws/index.d.ts +4 -3
  38. package/dist/job/index.d.ts +48 -3
  39. package/dist/job/index.js +131 -52
  40. package/dist/job/index.js.map +1 -1
  41. package/dist/logger/index.js +0 -12
  42. package/dist/logger/index.js.map +1 -1
  43. package/dist/middleware/index.d.ts +6 -2
  44. package/dist/middleware/index.js +7 -388
  45. package/dist/middleware/index.js.map +1 -1
  46. package/dist/nextjs/index.d.ts +88 -8
  47. package/dist/nextjs/index.js +138 -62
  48. package/dist/nextjs/index.js.map +1 -1
  49. package/dist/nextjs/server.d.ts +45 -7
  50. package/dist/nextjs/server.js +14 -0
  51. package/dist/nextjs/server.js.map +1 -1
  52. package/dist/ops/index.d.ts +62 -43
  53. package/dist/ops/index.js +638 -97
  54. package/dist/ops/index.js.map +1 -1
  55. package/dist/registry-C2KQCh0m.d.ts +473 -0
  56. package/dist/route/index.d.ts +6 -3
  57. package/dist/route/index.js +97 -28
  58. package/dist/route/index.js.map +1 -1
  59. package/dist/route/types.d.ts +7 -1
  60. package/dist/{router-Qbssr11H.d.ts → route-builder-DRW-MSCJ.d.ts} +52 -127
  61. package/dist/router-4YoIy5fN.d.ts +172 -0
  62. package/dist/{token-manager-BT5EnUAR.d.ts → router-CysBmgv3.d.ts} +81 -127
  63. package/dist/server/index.d.ts +185 -14
  64. package/dist/server/index.js +883 -78
  65. package/dist/server/index.js.map +1 -1
  66. package/dist/surface/index.d.ts +115 -0
  67. package/dist/surface/index.js +1003 -0
  68. package/dist/surface/index.js.map +1 -0
  69. package/dist/token-manager-Ckjt2-qq.d.ts +122 -0
  70. package/dist/{types-D1c57Ko-.d.ts → types-5Rl11_Nb.d.ts} +43 -4
  71. package/dist/{types-ZQODsBft.d.ts → types-B8zAxFsE.d.ts} +2 -1
  72. package/dist/types-CxhPH4s_.d.ts +375 -0
  73. package/dist/{types-2AbaW4Ie.d.ts → types-W4u_S0lF.d.ts} +4 -3
  74. package/docs/file-upload.md +195 -333
  75. package/package.json +20 -9
  76. package/src/cache/README.md +330 -0
  77. package/src/codegen/README.md +634 -0
  78. package/src/config/README.md +333 -0
  79. package/src/contract/README.md +440 -0
  80. package/src/db/README.md +589 -0
  81. package/src/db/manager/README.md +500 -0
  82. package/src/db/schema/README.md +354 -0
  83. package/src/db/transaction/README.md +822 -0
  84. package/src/env/README.md +787 -0
  85. package/src/errors/README.md +429 -0
  86. package/src/event/README.md +789 -0
  87. package/src/job/README.md +613 -0
  88. package/src/logger/README.md +321 -0
  89. package/src/middleware/README.md +634 -0
  90. package/src/nextjs/README.md +688 -0
  91. package/src/route/README.md +763 -0
  92. package/src/security/README.md +100 -0
  93. package/src/server/README.md +704 -0
  94. package/dist/types-9oszaJqp.d.ts +0 -250
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/job/boss.ts","../../src/job/job-builder.ts","../../src/job/job-router.ts","../../src/job/register-jobs.ts"],"names":["jobLogger","logger","job"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * pg-boss Wrapper\n *\n * Manages pg-boss instance lifecycle\n */\n\nimport PgBoss from 'pg-boss';\nimport { logger } from '@spfn/core/logger';\n\nconst jobLogger = logger.child('@spfn/core:job');\n\n/**\n * Check if connection string uses SSL without certificate verification\n *\n * pg library verifies certificates by default even with sslmode=require.\n * For require/prefer modes (no explicit verification), we disable cert checking\n * to support self-signed certificates.\n */\nfunction requiresSSLWithoutVerification(connectionString: string): boolean\n{\n try\n {\n const url = new URL(connectionString);\n const sslmode = url.searchParams.get('sslmode');\n\n return sslmode === 'require' || sslmode === 'prefer';\n }\n catch\n {\n return false;\n }\n}\n\n/**\n * Remove sslmode parameter from connection string URL\n *\n * pg driver interprets sslmode=require as verify-full, which overrides\n * the ssl option object. Stripping it lets us control SSL via the ssl option only.\n */\nfunction stripSslModeFromUrl(connectionString: string): string\n{\n const url = new URL(connectionString);\n url.searchParams.delete('sslmode');\n\n return url.toString();\n}\n\n/**\n * globalThis keys for cross-module-cache singleton (ESM/CJS share same instance)\n */\nconst BOSS_KEY = Symbol.for('spfn:boss-instance');\nconst CONFIG_KEY = Symbol.for('spfn:boss-config');\n\nconst g = globalThis as any;\n\nfunction getBossInstance(): PgBoss | null\n{\n return g[BOSS_KEY] ?? null;\n}\n\nfunction setBossInstance(instance: PgBoss | null): void\n{\n g[BOSS_KEY] = instance;\n}\n\nfunction getBossConfig(): BossConfig | null\n{\n return g[CONFIG_KEY] ?? null;\n}\n\nfunction setBossConfig(config: BossConfig | null): void\n{\n g[CONFIG_KEY] = config;\n}\n\n/**\n * Options for pg-boss initialization\n *\n * @example\n * ```typescript\n * await initBoss({\n * connectionString: process.env.DATABASE_URL,\n * schema: 'spfn_queue',\n * clearOnStart: process.env.NODE_ENV === 'development',\n * });\n * ```\n */\nexport interface BossOptions\n{\n /**\n * PostgreSQL connection string\n *\n * @example 'postgresql://user:password@localhost:5432/mydb'\n */\n connectionString: string;\n\n /**\n * Schema name for pg-boss tables\n *\n * pg-boss creates its own tables in this schema.\n *\n * @default 'spfn_queue'\n */\n schema?: string;\n\n /**\n * Maintenance interval in seconds\n *\n * pg-boss runs maintenance tasks (cleanup, archiving) at this interval.\n *\n * @default 120\n */\n maintenanceIntervalSeconds?: number;\n\n /**\n * Monitor state changes interval in seconds\n *\n * When set, pg-boss emits state change events at this interval.\n *\n * @default undefined (disabled)\n */\n monitorIntervalSeconds?: number;\n\n /**\n * Clear all pending/scheduled jobs on startup\n *\n * Useful for development mode to start with a clean queue.\n * Should be false in production.\n *\n * @default false\n */\n clearOnStart?: boolean;\n\n /**\n * Unschedule cron schedules that are no longer declared on any registered\n * job router, once at startup after job registration. Queues and job rows\n * are never deleted — only the schedule row is removed.\n *\n * Leave disabled when multiple apps share the same pg-boss schema, when\n * schedules are created directly via getBoss(), or during rolling deploys\n * that mix router versions — the sweep only knows the routers registered\n * in this process and would unschedule everything else.\n *\n * @default false\n */\n sweepOrphanSchedules?: boolean;\n}\n\n/**\n * @deprecated Use BossOptions instead\n */\nexport type BossConfig = BossOptions;\n\n/**\n * Initialize pg-boss with the given configuration\n *\n * Must be called before registerJobs(). Typically handled by defineServerConfig().\n *\n * @param options - pg-boss configuration options\n * @returns The pg-boss instance\n *\n * @example\n * ```typescript\n * const boss = await initBoss({\n * connectionString: process.env.DATABASE_URL!,\n * schema: 'spfn_queue',\n * });\n * ```\n */\nexport async function initBoss(options: BossOptions): Promise<PgBoss>\n{\n const existing = getBossInstance();\n if (existing)\n {\n jobLogger.warn('pg-boss already initialized, returning existing instance');\n\n return existing;\n }\n\n jobLogger.info('Initializing pg-boss...');\n\n setBossConfig(options);\n\n const needsSSL = requiresSSLWithoutVerification(options.connectionString);\n\n const pgBossOptions: PgBoss.ConstructorOptions = {\n // pg 드라이버가 URL의 sslmode=require를 verify-full로 해석해서\n // ssl 옵션을 무시하므로, URL에서 sslmode를 빼고 ssl 객체만 전달\n connectionString: needsSSL\n ? stripSslModeFromUrl(options.connectionString)\n : options.connectionString,\n schema: options.schema ?? 'spfn_queue',\n maintenanceIntervalSeconds: options.maintenanceIntervalSeconds ?? 120,\n };\n\n if (needsSSL)\n {\n pgBossOptions.ssl = { rejectUnauthorized: false };\n }\n\n // Only set monitorIntervalSeconds if explicitly provided (must be >= 1)\n if (options.monitorIntervalSeconds !== undefined && options.monitorIntervalSeconds >= 1)\n {\n pgBossOptions.monitorIntervalSeconds = options.monitorIntervalSeconds;\n }\n\n const boss = new PgBoss(pgBossOptions);\n\n // Event handlers\n boss.on('error', (error) =>\n {\n jobLogger.error('pg-boss error:', error);\n });\n\n await boss.start();\n\n setBossInstance(boss);\n\n jobLogger.info('pg-boss started successfully');\n\n return boss;\n}\n\n/**\n * Get the current pg-boss instance\n */\nexport function getBoss(): PgBoss | null\n{\n return getBossInstance();\n}\n\n/**\n * Stop pg-boss gracefully\n */\nexport async function stopBoss(): Promise<void>\n{\n const boss = getBossInstance();\n if (!boss)\n {\n return;\n }\n\n jobLogger.info('Stopping pg-boss...');\n\n try\n {\n await boss.stop({ graceful: true, timeout: 30000 });\n jobLogger.info('pg-boss stopped gracefully');\n }\n catch (error)\n {\n jobLogger.error('Error stopping pg-boss:', error);\n throw error;\n }\n finally\n {\n setBossInstance(null);\n setBossConfig(null);\n }\n}\n\n/**\n * Check if pg-boss is initialized and running\n */\nexport function isBossRunning(): boolean\n{\n return getBossInstance() !== null;\n}\n\n/**\n * Check if jobs should be cleared on start\n */\nexport function shouldClearOnStart(): boolean\n{\n return getBossConfig()?.clearOnStart ?? false;\n}\n\n/**\n * Check if orphan cron schedules should be swept after registration\n */\nexport function shouldSweepOrphanSchedules(): boolean\n{\n return getBossConfig()?.sweepOrphanSchedules ?? false;\n}\n","/**\n * Job Builder\n *\n * Fluent API for defining jobs, similar to route builder pattern\n */\n\nimport type { Static, TSchema } from '@sinclair/typebox';\nimport type { CompensateHandler, JobDef, JobHandler, JobOptions, JobSendOptions } from './types';\nimport type { EventDef, InferEventPayload } from '@spfn/core/event';\nimport { getBoss } from './boss';\n\n/**\n * Build pg-boss options from job defaults and send options\n */\nfunction buildPgBossOptions(\n defaults?: JobOptions,\n sendOptions?: JobSendOptions,\n): Record<string, unknown>\n{\n const options: Record<string, unknown> = {};\n\n // Send options (per-job invocation)\n if (sendOptions?.startAfter)\n {\n options.startAfter = sendOptions.startAfter;\n }\n if (sendOptions?.singletonKey)\n {\n options.singletonKey = sendOptions.singletonKey;\n }\n if (sendOptions?.priority !== undefined)\n {\n options.priority = sendOptions.priority;\n }\n\n // Default options (from job definition)\n if (defaults?.retryLimit !== undefined)\n {\n options.retryLimit = defaults.retryLimit;\n }\n if (defaults?.retryDelay !== undefined)\n {\n options.retryDelay = defaults.retryDelay;\n }\n if (defaults?.expireInSeconds !== undefined)\n {\n options.expireInSeconds = defaults.expireInSeconds;\n }\n if (defaults?.priority !== undefined && sendOptions?.priority === undefined)\n {\n options.priority = defaults.priority;\n }\n if (defaults?.singletonKey && !sendOptions?.singletonKey)\n {\n options.singletonKey = defaults.singletonKey;\n }\n if (defaults?.retentionSeconds !== undefined)\n {\n options.retentionSeconds = defaults.retentionSeconds;\n }\n\n return options;\n}\n\n/**\n * Job builder class with fluent API\n */\nexport class JobBuilder<TInput = void, TOutput = void>\n{\n private readonly _name: string;\n private _inputSchema?: TSchema;\n private _outputSchema?: TSchema;\n private _cronExpression?: string;\n private _runOnce?: boolean;\n private _subscribedEvent?: string;\n private _subscribedEventDef?: EventDef<any>;\n private _options?: JobOptions;\n private _handler?: JobHandler<TInput, TOutput>;\n private _compensate?: CompensateHandler<TInput, TOutput>;\n\n constructor(name: string)\n {\n this._name = name;\n }\n\n /**\n * Define input schema with TypeBox\n */\n input<TSchema extends import('@sinclair/typebox').TSchema>(\n schema: TSchema,\n ): JobBuilder<Static<TSchema>, TOutput>\n {\n const builder = new JobBuilder<Static<TSchema>, TOutput>(this._name);\n builder._inputSchema = schema;\n builder._outputSchema = this._outputSchema;\n builder._cronExpression = this._cronExpression;\n builder._runOnce = this._runOnce;\n builder._subscribedEvent = this._subscribedEvent;\n builder._options = this._options;\n\n return builder;\n }\n\n /**\n * Define output schema with TypeBox (for workflow integration)\n */\n output<TSchema extends import('@sinclair/typebox').TSchema>(\n schema: TSchema,\n ): JobBuilder<TInput, Static<TSchema>>\n {\n const builder = new JobBuilder<TInput, Static<TSchema>>(this._name);\n builder._inputSchema = this._inputSchema;\n builder._outputSchema = schema;\n builder._cronExpression = this._cronExpression;\n builder._runOnce = this._runOnce;\n builder._subscribedEvent = this._subscribedEvent;\n builder._options = this._options;\n\n return builder;\n }\n\n /**\n * Subscribe to an event (decoupled triggering)\n *\n * @example\n * ```typescript\n * const userCreated = defineEvent('user.created', Type.Object({\n * userId: Type.String(),\n * }));\n *\n * const sendWelcomeEmail = job('send-welcome-email')\n * .on(userCreated)\n * .handler(async (payload) => {\n * // payload is typed as { userId: string }\n * });\n * ```\n */\n on<TEvent extends EventDef<any>>(\n event: TEvent,\n ): JobBuilder<InferEventPayload<TEvent>, TOutput>\n {\n const builder = new JobBuilder<InferEventPayload<TEvent>, TOutput>(this._name);\n builder._inputSchema = event.schema;\n builder._outputSchema = this._outputSchema;\n builder._subscribedEvent = event.name;\n builder._subscribedEventDef = event;\n builder._cronExpression = this._cronExpression;\n builder._runOnce = this._runOnce;\n builder._options = this._options;\n\n return builder;\n }\n\n /**\n * Set cron expression for scheduled execution\n */\n cron(expression: string): this\n {\n this._cronExpression = expression;\n\n return this;\n }\n\n /**\n * Mark job to run once on server start\n */\n runOnce(): this\n {\n this._runOnce = true;\n\n return this;\n }\n\n /**\n * Set job options (retry, expiration, etc.)\n */\n options(options: JobOptions): this\n {\n this._options = options;\n\n return this;\n }\n\n /**\n * Set job timeout in milliseconds\n * (Converts to expireInSeconds for pg-boss)\n */\n timeout(ms: number): this\n {\n this._options = {\n ...this._options,\n expireInSeconds: Math.ceil(ms / 1000),\n };\n\n return this;\n }\n\n /**\n * Define compensate handler for rollback (workflow integration)\n */\n compensate(fn: CompensateHandler<TInput, TOutput>): this\n {\n this._compensate = fn;\n\n return this;\n }\n\n /**\n * Define the job handler and finalize the job definition\n */\n handler(fn: JobHandler<TInput, TOutput>): JobDef<TInput, TOutput>\n {\n this._handler = fn;\n\n const name = this._name;\n const inputSchema = this._inputSchema;\n const outputSchema = this._outputSchema;\n const cronExpression = this._cronExpression;\n const runOnce = this._runOnce;\n const subscribedEvent = this._subscribedEvent;\n const subscribedEventDef = this._subscribedEventDef;\n const options = this._options;\n const handler = this._handler;\n const compensate = this._compensate;\n\n // Create send function\n const send = async (\n inputOrOptions?: TInput | JobSendOptions,\n maybeOptions?: JobSendOptions,\n ): Promise<string | null> =>\n {\n const boss = getBoss();\n if (!boss)\n {\n throw new Error(\n `[Job:${name}] pg-boss not initialized. ` +\n 'Ensure jobs are registered with defineServerConfig().jobs()',\n );\n }\n\n // Determine input and options based on whether job has input schema\n const [input, sendOptions] = inputSchema\n ? [inputOrOptions as TInput, maybeOptions]\n : [undefined, inputOrOptions as JobSendOptions | undefined];\n\n return await boss.send(\n name,\n input ?? {},\n buildPgBossOptions(options, sendOptions),\n );\n };\n\n // Create run function (synchronous execution)\n const run = async (input?: TInput): Promise<TOutput> =>\n {\n if (inputSchema)\n {\n return await (handler as (input: TInput) => Promise<TOutput>)(input as TInput);\n }\n else\n {\n return await (handler as () => Promise<TOutput>)();\n }\n };\n\n // Create sendBatch function (bulk insert via pg-boss)\n const sendBatch = async (\n inputsOrOptions?: TInput[] | JobSendOptions,\n maybeOptions?: JobSendOptions,\n ): Promise<void> =>\n {\n const boss = getBoss();\n if (!boss)\n {\n throw new Error(\n `[Job:${name}] pg-boss not initialized. ` +\n 'Ensure jobs are registered with defineServerConfig().jobs()',\n );\n }\n\n const [inputs, sendOptions] = inputSchema\n ? [inputsOrOptions as TInput[], maybeOptions]\n : [undefined, inputsOrOptions as JobSendOptions | undefined];\n\n const pgBossOptions = buildPgBossOptions(options, sendOptions);\n\n const jobs = (inputs ?? [{}]).map((data) => ({\n name,\n ...pgBossOptions,\n data: data as object,\n }));\n\n await boss.insert(name, jobs);\n };\n\n return {\n name,\n inputSchema,\n outputSchema,\n cronExpression,\n runOnce,\n subscribedEvent,\n _subscribedEventDef: subscribedEventDef,\n options,\n handler,\n compensate,\n send: send as JobDef<TInput, TOutput>['send'],\n sendBatch: sendBatch as JobDef<TInput, TOutput>['sendBatch'],\n run: run as JobDef<TInput, TOutput>['run'],\n _input: undefined as unknown as TInput,\n _output: undefined as unknown as TOutput,\n };\n }\n}\n\n/**\n * Create a new job definition\n *\n * @example\n * ```typescript\n * // Simple job without input\n * export const cleanupJob = job('cleanup')\n * .handler(async () => {\n * await db.cleanup();\n * });\n *\n * // Job with typed input\n * export const sendEmailJob = job('send-email')\n * .input(Type.Object({\n * to: Type.String(),\n * subject: Type.String(),\n * body: Type.String(),\n * }))\n * .handler(async (input) => {\n * await emailService.send(input.to, input.subject, input.body);\n * });\n *\n * // Cron job\n * export const dailyReportJob = job('daily-report')\n * .cron('0 9 * * *')\n * .handler(async () => {\n * await reportService.generateDaily();\n * });\n *\n * // Run once on server start\n * export const initCacheJob = job('init-cache')\n * .runOnce()\n * .handler(async () => {\n * await cache.warmup();\n * });\n *\n * // With options\n * export const importantJob = job('important-task')\n * .input(Type.Object({ id: Type.String() }))\n * .options({\n * retryLimit: 5,\n * retryDelay: 5000,\n * priority: 10,\n * })\n * .handler(async (input) => {\n * await processImportant(input.id);\n * });\n * ```\n */\nexport function job(name: string): JobBuilder<void>\n{\n return new JobBuilder(name);\n}\n","/**\n * Job Router\n *\n * Groups job definitions for registration with the server\n */\n\nimport type { JobDef, JobRouter, JobRouterEntry } from './types';\n\n/**\n * Type guard to check if value is a JobDef\n */\nexport function isJobDef(value: unknown): value is JobDef<any>\n{\n return (\n value !== null &&\n typeof value === 'object' &&\n 'name' in value &&\n 'handler' in value &&\n 'send' in value &&\n 'run' in value\n );\n}\n\n/**\n * Type guard to check if value is a JobRouter\n */\nexport function isJobRouter(value: unknown): value is JobRouter<any>\n{\n return (\n value !== null &&\n typeof value === 'object' &&\n 'jobs' in value &&\n '_jobs' in value\n );\n}\n\n/**\n * Define a job router to group jobs together\n *\n * @example\n * ```typescript\n * // Flat structure\n * export const jobRouter = defineJobRouter({\n * sendWelcomeEmail,\n * dailyReport,\n * initCache,\n * });\n *\n * // Nested structure\n * export const jobRouter = defineJobRouter({\n * email: defineJobRouter({\n * sendWelcome: sendWelcomeEmailJob,\n * sendReset: sendResetPasswordJob,\n * }),\n * reports: defineJobRouter({\n * daily: dailyReportJob,\n * weekly: weeklyReportJob,\n * }),\n * });\n *\n * // Mixed\n * export const jobRouter = defineJobRouter({\n * initCache, // flat\n * email: defineJobRouter({ ... }), // nested\n * });\n * ```\n */\nexport function defineJobRouter<\n TJobs extends Record<string, JobRouterEntry>,\n>(jobs: TJobs): JobRouter<TJobs>\n{\n return {\n jobs,\n _jobs: jobs,\n };\n}\n\n/**\n * Collect all JobDefs from a JobRouter (including nested)\n */\nexport function collectJobs(\n router: JobRouter<any>,\n prefix = '',\n): JobDef<any>[]\n{\n const jobs: JobDef<any>[] = [];\n\n for (const [key, value] of Object.entries(router.jobs))\n {\n const name = prefix ? `${prefix}.${key}` : key;\n\n if (isJobRouter(value))\n {\n // Nested router - recurse\n jobs.push(...collectJobs(value, name));\n }\n else if (isJobDef(value))\n {\n jobs.push(value);\n }\n }\n\n return jobs;\n}\n","/**\n * Job Registration\n *\n * Registers jobs with pg-boss\n */\n\nimport type PgBoss from 'pg-boss';\nimport { logger } from '@spfn/core/logger';\nimport { env } from '@spfn/core/config';\nimport type { JobDef, JobOptions, JobRouter } from './types';\nimport type { EventDef } from '@spfn/core/event';\nimport { collectJobs } from './job-router';\nimport { getBoss, shouldClearOnStart, shouldSweepOrphanSchedules } from './boss';\n\nconst jobLogger = logger.child('@spfn/core:job');\n\n/**\n * Get the pg-boss queue name for an event\n */\nexport function getEventQueueName(eventName: string): string\n{\n return `event:${eventName}`;\n}\n\n/**\n * Build default pg-boss options for a job\n */\nfunction getDefaultJobOptions(options?: JobOptions): PgBoss.SendOptions\n{\n return {\n retryLimit: options?.retryLimit ?? 3,\n retryDelay: options?.retryDelay ?? 1000,\n // Exponential backoff by default — a failed cohort (e.g. a provider 429)\n // would otherwise all retry at the same fixed offset (thundering herd).\n retryBackoff: options?.retryBackoff ?? true,\n expireInSeconds: options?.expireInSeconds ?? 300,\n };\n}\n\n/**\n * Register all jobs from a JobRouter with pg-boss\n *\n * This function:\n * 1. Collects all jobs from the router (including nested routers)\n * 2. Optionally clears existing jobs (if clearOnStart is enabled)\n * 3. Registers each job's worker handler with pg-boss\n * 4. Sets up cron schedules for scheduled jobs\n * 5. Queues runOnce jobs\n * 6. Connects event subscriptions to job queues\n *\n * @param router - JobRouter containing job definitions\n *\n * @example\n * ```typescript\n * // Define jobs\n * const sendEmail = job('send-email')\n * .input(Type.Object({ to: Type.String() }))\n * .handler(async (input) => { ... });\n *\n * const dailyReport = job('daily-report')\n * .cron('0 9 * * *')\n * .handler(async () => { ... });\n *\n * // Create router\n * const jobRouter = defineJobRouter({ sendEmail, dailyReport });\n *\n * // Initialize pg-boss first\n * await initBoss({ connectionString: process.env.DATABASE_URL! });\n *\n * // Register jobs\n * await registerJobs(jobRouter);\n * ```\n */\nexport async function registerJobs(router: JobRouter<any>): Promise<void>\n{\n const boss = getBoss();\n if (!boss)\n {\n throw new Error(\n 'pg-boss not initialized. Call initBoss() before registerJobs()',\n );\n }\n\n const jobs = collectJobs(router);\n const clearOnStart = shouldClearOnStart();\n\n jobLogger.info(`Registering ${jobs.length} job(s)...`);\n\n // Clear existing jobs if requested (useful for development)\n if (clearOnStart)\n {\n jobLogger.info('Clearing existing jobs before registration...');\n for (const job of jobs)\n {\n // Clear job queue\n await boss.deleteAllJobs(job.name);\n\n // Also clear event queue if subscribed\n if (job.subscribedEvent)\n {\n const eventQueue = getEventQueueName(job.subscribedEvent);\n await boss.deleteAllJobs(eventQueue);\n }\n }\n jobLogger.info('Existing jobs cleared');\n }\n\n // Each job registers an independent pg-boss queue/worker, so register them\n // concurrently — sequential awaits made startup scale linearly with job count.\n await Promise.all(jobs.map((job) => registerJob(job)));\n\n for (const job of jobs)\n {\n if (job.cronExpression)\n {\n registeredCronNames.add(job.name);\n }\n }\n\n if (shouldSweepOrphanSchedules())\n {\n await sweepOrphanSchedules(boss);\n }\n\n jobLogger.info('All jobs registered successfully');\n}\n\n// Cron names accumulate across registerJobs() calls so a sweep in one call\n// never treats another router's schedules (registered earlier in the same\n// process) as orphans.\nconst registeredCronNames = new Set<string>();\n\n/**\n * Reset sweep state accumulated by registerJobs()\n *\n * @internal test-only\n */\nexport function resetOrphanSweepState(): void\n{\n registeredCronNames.clear();\n}\n\n/**\n * Unschedule pg-boss schedules that are no longer declared on any router\n * registered in this process (opt-in via `sweepOrphanSchedules`)\n *\n * The router is the only sanctioned way to create cron schedules, so any\n * schedule whose name is not a declared cron job is an orphan: pg-boss keeps\n * creating jobs for it on every cron tick, and without a worker they\n * accumulate forever. Unscheduling stops the pile-up; the queue and its\n * existing job rows are left untouched so nothing is destroyed if the name\n * belongs to another app, an ad-hoc getBoss() schedule, or a newer deploy.\n *\n * Skipped entirely when no cron job has been declared — an empty router says\n * nothing about which schedules are orphans. Runs once after registration;\n * failures are logged per orphan and never block startup.\n */\nasync function sweepOrphanSchedules(boss: PgBoss): Promise<void>\n{\n if (registeredCronNames.size === 0)\n {\n jobLogger.debug('No cron jobs declared; skipping orphan schedule sweep');\n\n return;\n }\n\n try\n {\n const schedules = await boss.getSchedules();\n const orphans = schedules.filter((schedule) => !registeredCronNames.has(schedule.name));\n\n if (orphans.length === 0)\n {\n jobLogger.debug('No orphan schedules found');\n\n return;\n }\n\n await Promise.all(orphans.map(async (orphan) =>\n {\n try\n {\n await boss.unschedule(orphan.name, orphan.key);\n jobLogger.info(`Removed orphan schedule: ${orphan.name}`);\n }\n catch (error)\n {\n jobLogger.error(`Failed to remove orphan schedule: ${orphan.name}`, {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }));\n }\n catch (error)\n {\n jobLogger.error('Orphan schedule sweep failed', {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n}\n\n/**\n * Create queue if not exists (required for pg-boss v11+)\n */\nasync function ensureQueue(boss: PgBoss, queueName: string): Promise<void>\n{\n await boss.createQueue(queueName);\n}\n\n/**\n * Execute a single job handler with logging\n */\nasync function executeJobHandler(\n job: JobDef<any>,\n pgBossJob: PgBoss.Job<any>,\n): Promise<void>\n{\n jobLogger.debug(`[Job:${job.name}] Executing...`, { jobId: pgBossJob.id });\n\n const startTime = Date.now();\n\n try\n {\n if (job.inputSchema)\n {\n await (job.handler as (input: unknown) => Promise<void>)(pgBossJob.data);\n }\n else\n {\n await (job.handler as () => Promise<void>)();\n }\n\n const duration = Date.now() - startTime;\n jobLogger.info(`[Job:${job.name}] Completed in ${duration}ms`, {\n jobId: pgBossJob.id,\n duration,\n });\n }\n catch (error)\n {\n const duration = Date.now() - startTime;\n jobLogger.error(`[Job:${job.name}] Failed after ${duration}ms`, {\n jobId: pgBossJob.id,\n duration,\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n}\n\n/**\n * Register worker handler for a job\n *\n * When batchSize > 1, jobs are processed in parallel.\n * Failed jobs are individually marked via boss.fail() so pg-boss can retry them.\n * Successful jobs are auto-completed when the handler callback resolves.\n */\nasync function registerWorker(\n boss: PgBoss,\n job: JobDef<any>,\n queueName: string,\n): Promise<void>\n{\n // Ensure queue exists before registering worker\n await ensureQueue(boss, queueName);\n\n const batchSize = job.options?.batchSize ?? 1;\n const pollingIntervalSeconds = job.options?.pollingIntervalSeconds ?? env.JOB_POLLING_INTERVAL_SECONDS;\n\n await boss.work(\n queueName,\n { batchSize, pollingIntervalSeconds },\n async (pgBossJobs) =>\n {\n if (batchSize <= 1)\n {\n // Single job — throw on error for pg-boss retry\n await executeJobHandler(job, pgBossJobs[0]);\n\n return;\n }\n\n // Batch — parallel execution with individual failure handling\n const results = await Promise.allSettled(\n pgBossJobs.map((pgBossJob) => executeJobHandler(job, pgBossJob)),\n );\n\n // Collect failed job IDs and mark them individually.\n // boss.fail() sets state = 'failed'; the subsequent auto-complete\n // from work() only affects jobs still in 'active' state, so\n // already-failed jobs are not overwritten.\n const failedIds: string[] = [];\n\n for (let i = 0; i < results.length; i++)\n {\n if (results[i].status === 'rejected')\n {\n failedIds.push(pgBossJobs[i].id);\n }\n }\n\n if (failedIds.length > 0)\n {\n await boss.fail(queueName, failedIds);\n }\n\n // Callback resolves → pg-boss auto-completes remaining 'active' jobs\n },\n );\n}\n\n/**\n * Connect event to pg-boss queue\n */\nfunction connectEventToQueue(\n boss: PgBoss,\n job: JobDef<any>,\n queueName: string,\n): void\n{\n if (!job._subscribedEventDef)\n {\n return;\n }\n\n const eventDef = job._subscribedEventDef as EventDef<any>;\n eventDef._registerJobQueue(queueName, async (queue, payload) =>\n {\n await boss.send(queue, payload as object, getDefaultJobOptions(job.options));\n });\n\n jobLogger.debug(`[Job:${job.name}] Connected to event: ${job.subscribedEvent}`);\n}\n\n/**\n * Register cron schedule for a job\n */\nasync function registerCronSchedule(boss: PgBoss, job: JobDef<any>): Promise<void>\n{\n if (!job.cronExpression)\n {\n return;\n }\n\n jobLogger.debug(`[Job:${job.name}] Scheduling cron: ${job.cronExpression}`);\n\n // Ensure queue exists for cron jobs (uses job.name as queue)\n await ensureQueue(boss, job.name);\n\n await boss.schedule(\n job.name,\n job.cronExpression,\n {},\n getDefaultJobOptions(job.options),\n );\n\n jobLogger.info(`[Job:${job.name}] Cron scheduled: ${job.cronExpression}`);\n}\n\n/**\n * Queue a runOnce job\n */\nasync function queueRunOnceJob(boss: PgBoss, job: JobDef<any>): Promise<void>\n{\n if (!job.runOnce)\n {\n return;\n }\n\n jobLogger.debug(`[Job:${job.name}] Queuing runOnce job`);\n\n // Ensure queue exists for runOnce jobs (uses job.name as queue)\n await ensureQueue(boss, job.name);\n\n await boss.send(\n job.name,\n {},\n {\n ...getDefaultJobOptions(job.options),\n singletonKey: `runOnce:${job.name}`,\n },\n );\n\n jobLogger.info(`[Job:${job.name}] runOnce job queued`);\n}\n\n/**\n * Register a single job with pg-boss\n */\nasync function registerJob(job: JobDef<any>): Promise<void>\n{\n const boss = getBoss();\n if (!boss)\n {\n throw new Error('pg-boss not initialized');\n }\n\n const queueName = job.subscribedEvent\n ? getEventQueueName(job.subscribedEvent)\n : job.name;\n\n jobLogger.debug(`Registering job: ${job.name}`, {\n queueName,\n subscribedEvent: job.subscribedEvent,\n });\n\n await registerWorker(boss, job, queueName);\n connectEventToQueue(boss, job, queueName);\n await registerCronSchedule(boss, job);\n await queueRunOnceJob(boss, job);\n\n jobLogger.debug(`Job registered: ${job.name}`);\n}\n"]}
1
+ {"version":3,"sources":["../../src/job/boss.ts","../../src/job/queue-policy.ts","../../src/job/retry-options.ts","../../src/job/job-builder.ts","../../src/job/job-router.ts","../../src/job/register-jobs.ts"],"names":["job","jobLogger","logger"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * pg-boss Wrapper\n *\n * Manages pg-boss instance lifecycle\n */\n\nimport PgBoss from 'pg-boss';\nimport { logger } from '@spfn/core/logger';\n\nconst jobLogger = logger.child('@spfn/core:job');\n\n/**\n * Check if connection string uses SSL without certificate verification\n *\n * pg library verifies certificates by default even with sslmode=require.\n * For require/prefer modes (no explicit verification), we disable cert checking\n * to support self-signed certificates.\n */\nfunction requiresSSLWithoutVerification(connectionString: string): boolean\n{\n try\n {\n const url = new URL(connectionString);\n const sslmode = url.searchParams.get('sslmode');\n\n return sslmode === 'require' || sslmode === 'prefer';\n }\n catch\n {\n return false;\n }\n}\n\n/**\n * Remove sslmode parameter from connection string URL\n *\n * pg driver interprets sslmode=require as verify-full, which overrides\n * the ssl option object. Stripping it lets us control SSL via the ssl option only.\n */\nfunction stripSslModeFromUrl(connectionString: string): string\n{\n const url = new URL(connectionString);\n url.searchParams.delete('sslmode');\n\n return url.toString();\n}\n\n/**\n * globalThis keys for cross-module-cache singleton (ESM/CJS share same instance)\n */\nconst BOSS_KEY = Symbol.for('spfn:boss-instance');\nconst CONFIG_KEY = Symbol.for('spfn:boss-config');\n\nconst g = globalThis as any;\n\nfunction getBossInstance(): PgBoss | null\n{\n return g[BOSS_KEY] ?? null;\n}\n\nfunction setBossInstance(instance: PgBoss | null): void\n{\n g[BOSS_KEY] = instance;\n}\n\nfunction getBossConfig(): BossConfig | null\n{\n return g[CONFIG_KEY] ?? null;\n}\n\nfunction setBossConfig(config: BossConfig | null): void\n{\n g[CONFIG_KEY] = config;\n}\n\n/**\n * Options for pg-boss initialization\n *\n * @example\n * ```typescript\n * await initBoss({\n * connectionString: process.env.DATABASE_URL,\n * schema: 'spfn_queue',\n * clearOnStart: process.env.NODE_ENV === 'development',\n * });\n * ```\n */\nexport interface BossOptions\n{\n /**\n * PostgreSQL connection string\n *\n * @example 'postgresql://user:password@localhost:5432/mydb'\n */\n connectionString: string;\n\n /**\n * Schema name for pg-boss tables\n *\n * pg-boss creates its own tables in this schema.\n *\n * @default 'spfn_queue'\n */\n schema?: string;\n\n /**\n * Maintenance interval in seconds\n *\n * pg-boss runs maintenance tasks (cleanup, archiving) at this interval.\n *\n * @default 120\n */\n maintenanceIntervalSeconds?: number;\n\n /**\n * Monitor state changes interval in seconds\n *\n * When set, pg-boss emits state change events at this interval.\n *\n * @default undefined (disabled)\n */\n monitorIntervalSeconds?: number;\n\n /**\n * Clear all pending/scheduled jobs on startup\n *\n * Useful for development mode to start with a clean queue.\n * Should be false in production.\n *\n * @default false\n */\n clearOnStart?: boolean;\n\n /**\n * Unschedule cron schedules that are no longer declared on any registered\n * job router, once at startup after job registration. Queues and job rows\n * are never deleted — only the schedule row is removed.\n *\n * Leave disabled when multiple apps share the same pg-boss schema, when\n * schedules are created directly via getBoss(), or during rolling deploys\n * that mix router versions — the sweep only knows the routers registered\n * in this process and would unschedule everything else.\n *\n * @default false\n */\n sweepOrphanSchedules?: boolean;\n}\n\n/**\n * @deprecated Use BossOptions instead\n */\nexport type BossConfig = BossOptions;\n\n/**\n * Initialize pg-boss with the given configuration\n *\n * Must be called before registerJobs(). Typically handled by defineServerConfig().\n *\n * @param options - pg-boss configuration options\n * @returns The pg-boss instance\n *\n * @example\n * ```typescript\n * const boss = await initBoss({\n * connectionString: process.env.DATABASE_URL!,\n * schema: 'spfn_queue',\n * });\n * ```\n */\nexport async function initBoss(options: BossOptions): Promise<PgBoss>\n{\n const existing = getBossInstance();\n if (existing)\n {\n jobLogger.warn('pg-boss already initialized, returning existing instance');\n\n return existing;\n }\n\n jobLogger.info('Initializing pg-boss...');\n\n setBossConfig(options);\n\n const needsSSL = requiresSSLWithoutVerification(options.connectionString);\n\n const pgBossOptions: PgBoss.ConstructorOptions = {\n // pg 드라이버가 URL의 sslmode=require를 verify-full로 해석해서\n // ssl 옵션을 무시하므로, URL에서 sslmode를 빼고 ssl 객체만 전달\n connectionString: needsSSL\n ? stripSslModeFromUrl(options.connectionString)\n : options.connectionString,\n schema: options.schema ?? 'spfn_queue',\n maintenanceIntervalSeconds: options.maintenanceIntervalSeconds ?? 120,\n };\n\n if (needsSSL)\n {\n pgBossOptions.ssl = { rejectUnauthorized: false };\n }\n\n // Only set monitorIntervalSeconds if explicitly provided (must be >= 1)\n if (options.monitorIntervalSeconds !== undefined && options.monitorIntervalSeconds >= 1)\n {\n pgBossOptions.monitorIntervalSeconds = options.monitorIntervalSeconds;\n }\n\n const boss = new PgBoss(pgBossOptions);\n\n // Event handlers\n boss.on('error', (error) =>\n {\n jobLogger.error('pg-boss error:', error);\n });\n\n await boss.start();\n\n setBossInstance(boss);\n\n jobLogger.info('pg-boss started successfully');\n\n return boss;\n}\n\n/**\n * Get the current pg-boss instance\n */\nexport function getBoss(): PgBoss | null\n{\n return getBossInstance();\n}\n\n/**\n * Stop pg-boss gracefully\n */\nexport async function stopBoss(): Promise<void>\n{\n const boss = getBossInstance();\n if (!boss)\n {\n return;\n }\n\n jobLogger.info('Stopping pg-boss...');\n\n try\n {\n await boss.stop({ graceful: true, timeout: 30000 });\n jobLogger.info('pg-boss stopped gracefully');\n }\n catch (error)\n {\n jobLogger.error('Error stopping pg-boss:', error);\n throw error;\n }\n finally\n {\n setBossInstance(null);\n setBossConfig(null);\n }\n}\n\n/**\n * Check if pg-boss is initialized and running\n */\nexport function isBossRunning(): boolean\n{\n return getBossInstance() !== null;\n}\n\n/**\n * Check if jobs should be cleared on start\n */\nexport function shouldClearOnStart(): boolean\n{\n return getBossConfig()?.clearOnStart ?? false;\n}\n\n/**\n * Check if orphan cron schedules should be swept after registration\n */\nexport function shouldSweepOrphanSchedules(): boolean\n{\n return getBossConfig()?.sweepOrphanSchedules ?? false;\n}\n","/**\n * Queue Policy Resolution\n *\n * Lives apart from register-jobs.ts and job-builder.ts so both can read the\n * policy of a job definition without importing each other.\n */\n\nimport type { JobOptions, JobQueuePolicy } from './types';\n\n/**\n * The part of a job definition that decides its queue policy\n *\n * `JobDef` satisfies this; the builder can also pass its own fields before the\n * definition object exists.\n */\nexport interface QueuePolicySource\n{\n readonly runOnce?: boolean;\n readonly options?: JobOptions;\n}\n\n/**\n * Resolve the pg-boss queue policy for a job definition\n *\n * An explicit `.options({ policy })` always wins. Otherwise a job that asks\n * for deduplication — `.runOnce()` or an `options.singletonKey` — gets\n * `exclusive`, the only policy under which a second enqueue is dropped while\n * the first is still pending or running.\n *\n * Everything else stays `standard`: `exclusive` without a `singletonKey`\n * deduplicates on `COALESCE(singleton_key, '')` and would collapse a plain\n * queue to a single pending job.\n */\nexport function resolveQueuePolicy(job: QueuePolicySource): JobQueuePolicy\n{\n if (job.options?.policy)\n {\n return job.options.policy;\n }\n\n if (job.runOnce || job.options?.singletonKey)\n {\n return 'exclusive';\n }\n\n return 'standard';\n}\n","/**\n * Retry options, translated from the job API's units to pg-boss's\n */\n\nimport type { JobOptions } from './types';\n\nexport interface PgBossRetryOptions\n{\n retryLimit: number;\n retryDelay: number;\n retryBackoff: boolean;\n}\n\n/**\n * The retry options every enqueue path hands to pg-boss\n *\n * `JobOptions.retryDelay` is in milliseconds; pg-boss reads `retryDelay` in\n * seconds. Passing it through unchanged turned a documented 5-second retry\n * into 5000 seconds (83 minutes). A delay under one second rounds up to one,\n * so a non-zero delay never becomes an immediate retry.\n */\nexport function toPgBossRetryOptions(options?: JobOptions): PgBossRetryOptions\n{\n return {\n retryLimit: options?.retryLimit ?? 3,\n retryDelay: Math.ceil((options?.retryDelay ?? 1000) / 1000),\n // Exponential backoff by default — a failed cohort (e.g. a provider 429)\n // would otherwise all retry at the same fixed offset (thundering herd).\n retryBackoff: options?.retryBackoff ?? true,\n };\n}\n","/**\n * Job Builder\n *\n * Fluent API for defining jobs, similar to route builder pattern\n */\n\nimport type { Static, TSchema } from '@sinclair/typebox';\nimport type { CompensateHandler, JobDef, JobHandler, JobOptions, JobSendOptions } from './types';\nimport type { EventDef, InferEventPayload } from '@spfn/core/event';\nimport { logger } from '@spfn/core/logger';\nimport { getBoss } from './boss';\nimport { resolveQueuePolicy, type QueuePolicySource } from './queue-policy';\nimport { toPgBossRetryOptions } from './retry-options';\n\nconst jobLogger = logger.child('@spfn/core:job');\n\n// Job names whose ignored singletonKey has already been reported. A send-time\n// key is usually inside a loop, so warning per call would flood the log.\nconst warnedIgnoredSingletonKeys = new Set<string>();\n\n/**\n * Reset the singletonKey warn-once state\n *\n * @internal test-only\n */\nexport function resetSingletonKeyWarnings(): void\n{\n warnedIgnoredSingletonKeys.clear();\n}\n\n/**\n * Warn once when a send-time singletonKey lands on a `standard` queue\n *\n * pg-boss stores the key and never conflicts on it there, so the send is\n * accepted and the deduplication the caller asked for silently does not\n * happen. The policy lives on the definition, not on the send, so the fix is\n * on the definition too.\n */\nfunction warnIfSingletonKeyIgnored(name: string, source: QueuePolicySource): void\n{\n if (resolveQueuePolicy(source) !== 'standard' || warnedIgnoredSingletonKeys.has(name))\n {\n return;\n }\n\n warnedIgnoredSingletonKeys.add(name);\n\n jobLogger.warn(\n `[Job:${name}] singletonKey ignored: queue policy is \"standard\"; ` +\n \"set .options({ policy: 'exclusive' }) (or another non-standard policy) to deduplicate\",\n );\n}\n\n/**\n * Build pg-boss options from job defaults and send options\n */\nfunction buildPgBossOptions(\n defaults?: JobOptions,\n sendOptions?: JobSendOptions,\n): Record<string, unknown>\n{\n const options: Record<string, unknown> = { ...toPgBossRetryOptions(defaults) };\n\n // Send options (per-job invocation)\n if (sendOptions?.startAfter)\n {\n options.startAfter = sendOptions.startAfter;\n }\n if (sendOptions?.singletonKey)\n {\n options.singletonKey = sendOptions.singletonKey;\n }\n if (sendOptions?.priority !== undefined)\n {\n options.priority = sendOptions.priority;\n }\n\n // Default options (from job definition)\n if (defaults?.expireInSeconds !== undefined)\n {\n options.expireInSeconds = defaults.expireInSeconds;\n }\n if (defaults?.priority !== undefined && sendOptions?.priority === undefined)\n {\n options.priority = defaults.priority;\n }\n if (defaults?.singletonKey && !sendOptions?.singletonKey)\n {\n options.singletonKey = defaults.singletonKey;\n }\n if (defaults?.retentionSeconds !== undefined)\n {\n options.retentionSeconds = defaults.retentionSeconds;\n }\n\n return options;\n}\n\n/**\n * Job builder class with fluent API\n */\nexport class JobBuilder<TInput = void, TOutput = void>\n{\n private readonly _name: string;\n private _inputSchema?: TSchema;\n private _outputSchema?: TSchema;\n private _cronExpression?: string;\n private _runOnce?: boolean;\n private _subscribedEvent?: string;\n private _subscribedEventDef?: EventDef<any>;\n private _options?: JobOptions;\n private _handler?: JobHandler<TInput, TOutput>;\n private _compensate?: CompensateHandler<TInput, TOutput>;\n\n constructor(name: string)\n {\n this._name = name;\n }\n\n /**\n * Define input schema with TypeBox\n */\n input<TSchema extends import('@sinclair/typebox').TSchema>(\n schema: TSchema,\n ): JobBuilder<Static<TSchema>, TOutput>\n {\n const builder = new JobBuilder<Static<TSchema>, TOutput>(this._name);\n builder._inputSchema = schema;\n builder._outputSchema = this._outputSchema;\n builder._cronExpression = this._cronExpression;\n builder._runOnce = this._runOnce;\n builder._subscribedEvent = this._subscribedEvent;\n builder._options = this._options;\n\n return builder;\n }\n\n /**\n * Define output schema with TypeBox (for workflow integration)\n */\n output<TSchema extends import('@sinclair/typebox').TSchema>(\n schema: TSchema,\n ): JobBuilder<TInput, Static<TSchema>>\n {\n const builder = new JobBuilder<TInput, Static<TSchema>>(this._name);\n builder._inputSchema = this._inputSchema;\n builder._outputSchema = schema;\n builder._cronExpression = this._cronExpression;\n builder._runOnce = this._runOnce;\n builder._subscribedEvent = this._subscribedEvent;\n builder._options = this._options;\n\n return builder;\n }\n\n /**\n * Subscribe to an event (decoupled triggering)\n *\n * @example\n * ```typescript\n * const userCreated = defineEvent('user.created', Type.Object({\n * userId: Type.String(),\n * }));\n *\n * const sendWelcomeEmail = job('send-welcome-email')\n * .on(userCreated)\n * .handler(async (payload) => {\n * // payload is typed as { userId: string }\n * });\n * ```\n */\n on<TEvent extends EventDef<any>>(\n event: TEvent,\n ): JobBuilder<InferEventPayload<TEvent>, TOutput>\n {\n const builder = new JobBuilder<InferEventPayload<TEvent>, TOutput>(this._name);\n builder._inputSchema = event.schema;\n builder._outputSchema = this._outputSchema;\n builder._subscribedEvent = event.name;\n builder._subscribedEventDef = event;\n builder._cronExpression = this._cronExpression;\n builder._runOnce = this._runOnce;\n builder._options = this._options;\n\n return builder;\n }\n\n /**\n * Set cron expression for scheduled execution\n */\n cron(expression: string): this\n {\n this._cronExpression = expression;\n\n return this;\n }\n\n /**\n * Mark job to run once on server start\n */\n runOnce(): this\n {\n this._runOnce = true;\n\n return this;\n }\n\n /**\n * Set job options (retry, expiration, etc.)\n */\n options(options: JobOptions): this\n {\n this._options = options;\n\n return this;\n }\n\n /**\n * Set job timeout in milliseconds\n * (Converts to expireInSeconds for pg-boss)\n */\n timeout(ms: number): this\n {\n this._options = {\n ...this._options,\n expireInSeconds: Math.ceil(ms / 1000),\n };\n\n return this;\n }\n\n /**\n * Define compensate handler for rollback (workflow integration)\n */\n compensate(fn: CompensateHandler<TInput, TOutput>): this\n {\n this._compensate = fn;\n\n return this;\n }\n\n /**\n * Define the job handler and finalize the job definition\n */\n handler(fn: JobHandler<TInput, TOutput>): JobDef<TInput, TOutput>\n {\n this._handler = fn;\n\n const name = this._name;\n const inputSchema = this._inputSchema;\n const outputSchema = this._outputSchema;\n const cronExpression = this._cronExpression;\n const runOnce = this._runOnce;\n const subscribedEvent = this._subscribedEvent;\n const subscribedEventDef = this._subscribedEventDef;\n const options = this._options;\n const handler = this._handler;\n const compensate = this._compensate;\n\n // Create send function\n const send = async (\n inputOrOptions?: TInput | JobSendOptions,\n maybeOptions?: JobSendOptions,\n ): Promise<string | null> =>\n {\n const boss = getBoss();\n if (!boss)\n {\n throw new Error(\n `[Job:${name}] pg-boss not initialized. ` +\n 'Ensure jobs are registered with defineServerConfig().jobs()',\n );\n }\n\n // Determine input and options based on whether job has input schema\n const [input, sendOptions] = inputSchema\n ? [inputOrOptions as TInput, maybeOptions]\n : [undefined, inputOrOptions as JobSendOptions | undefined];\n\n if (sendOptions?.singletonKey)\n {\n warnIfSingletonKeyIgnored(name, { runOnce, options });\n }\n\n return await boss.send(\n name,\n input ?? {},\n buildPgBossOptions(options, sendOptions),\n );\n };\n\n // Create run function (synchronous execution)\n const run = async (input?: TInput): Promise<TOutput> =>\n {\n if (inputSchema)\n {\n return await (handler as (input: TInput) => Promise<TOutput>)(input as TInput);\n }\n else\n {\n return await (handler as () => Promise<TOutput>)();\n }\n };\n\n // Create sendBatch function (bulk insert via pg-boss)\n const sendBatch = async (\n inputsOrOptions?: TInput[] | JobSendOptions,\n maybeOptions?: JobSendOptions,\n ): Promise<void> =>\n {\n const boss = getBoss();\n if (!boss)\n {\n throw new Error(\n `[Job:${name}] pg-boss not initialized. ` +\n 'Ensure jobs are registered with defineServerConfig().jobs()',\n );\n }\n\n const [inputs, sendOptions] = inputSchema\n ? [inputsOrOptions as TInput[], maybeOptions]\n : [undefined, inputsOrOptions as JobSendOptions | undefined];\n\n if (sendOptions?.singletonKey)\n {\n warnIfSingletonKeyIgnored(name, { runOnce, options });\n }\n\n const pgBossOptions = buildPgBossOptions(options, sendOptions);\n\n const jobs = (inputs ?? [{}]).map((data) => ({\n name,\n ...pgBossOptions,\n data: data as object,\n }));\n\n await boss.insert(name, jobs);\n };\n\n return {\n name,\n inputSchema,\n outputSchema,\n cronExpression,\n runOnce,\n subscribedEvent,\n _subscribedEventDef: subscribedEventDef,\n options,\n handler,\n compensate,\n send: send as JobDef<TInput, TOutput>['send'],\n sendBatch: sendBatch as JobDef<TInput, TOutput>['sendBatch'],\n run: run as JobDef<TInput, TOutput>['run'],\n _input: undefined as unknown as TInput,\n _output: undefined as unknown as TOutput,\n };\n }\n}\n\n/**\n * Create a new job definition\n *\n * @example\n * ```typescript\n * // Simple job without input\n * export const cleanupJob = job('cleanup')\n * .handler(async () => {\n * await db.cleanup();\n * });\n *\n * // Job with typed input\n * export const sendEmailJob = job('send-email')\n * .input(Type.Object({\n * to: Type.String(),\n * subject: Type.String(),\n * body: Type.String(),\n * }))\n * .handler(async (input) => {\n * await emailService.send(input.to, input.subject, input.body);\n * });\n *\n * // Cron job\n * export const dailyReportJob = job('daily-report')\n * .cron('0 9 * * *')\n * .handler(async () => {\n * await reportService.generateDaily();\n * });\n *\n * // Run once on server start\n * export const initCacheJob = job('init-cache')\n * .runOnce()\n * .handler(async () => {\n * await cache.warmup();\n * });\n *\n * // With options\n * export const importantJob = job('important-task')\n * .input(Type.Object({ id: Type.String() }))\n * .options({\n * retryLimit: 5,\n * retryDelay: 5000,\n * priority: 10,\n * })\n * .handler(async (input) => {\n * await processImportant(input.id);\n * });\n * ```\n */\nexport function job(name: string): JobBuilder<void>\n{\n return new JobBuilder(name);\n}\n","/**\n * Job Router\n *\n * Groups job definitions for registration with the server\n */\n\nimport type { JobDef, JobRouter, JobRouterEntry } from './types';\n\n/**\n * Type guard to check if value is a JobDef\n */\nexport function isJobDef(value: unknown): value is JobDef<any>\n{\n return (\n value !== null &&\n typeof value === 'object' &&\n 'name' in value &&\n 'handler' in value &&\n 'send' in value &&\n 'run' in value\n );\n}\n\n/**\n * Type guard to check if value is a JobRouter\n */\nexport function isJobRouter(value: unknown): value is JobRouter<any>\n{\n return (\n value !== null &&\n typeof value === 'object' &&\n 'jobs' in value &&\n '_jobs' in value\n );\n}\n\n/**\n * Define a job router to group jobs together\n *\n * @example\n * ```typescript\n * // Flat structure\n * export const jobRouter = defineJobRouter({\n * sendWelcomeEmail,\n * dailyReport,\n * initCache,\n * });\n *\n * // Nested structure\n * export const jobRouter = defineJobRouter({\n * email: defineJobRouter({\n * sendWelcome: sendWelcomeEmailJob,\n * sendReset: sendResetPasswordJob,\n * }),\n * reports: defineJobRouter({\n * daily: dailyReportJob,\n * weekly: weeklyReportJob,\n * }),\n * });\n *\n * // Mixed\n * export const jobRouter = defineJobRouter({\n * initCache, // flat\n * email: defineJobRouter({ ... }), // nested\n * });\n * ```\n */\nexport function defineJobRouter<\n TJobs extends Record<string, JobRouterEntry>,\n>(jobs: TJobs): JobRouter<TJobs>\n{\n return {\n jobs,\n _jobs: jobs,\n };\n}\n\n/**\n * Merge two job routers into a new one\n *\n * `defineServerConfig().jobs()` may be called once per domain router, the way\n * `.routes()` is; each call merges into what is already registered instead of\n * replacing it. Keys are the router's own namespace, so a key present in both\n * routers is a mistake the app cannot recover from silently — one of the two\n * would disappear.\n *\n * Neither input is mutated: a fresh router is returned.\n *\n * @throws if a top-level key exists in both routers\n */\nexport function mergeJobRouters(base: JobRouter<any>, extra: JobRouter<any>): JobRouter<any>\n{\n for (const key of Object.keys(extra.jobs))\n {\n if (key in base.jobs)\n {\n throw new Error(\n `jobs(): key \"${key}\" is already registered by an earlier .jobs() call; ` +\n 'rename it or nest it under a domain router',\n );\n }\n }\n\n return defineJobRouter({ ...base.jobs, ...extra.jobs });\n}\n\n/**\n * A collected job together with the router key path that reached it\n *\n * The key path (`email.sendWelcome`) is what an app author recognises, so it\n * is what error messages about a job name should quote.\n */\nexport interface JobEntry\n{\n readonly key: string;\n readonly job: JobDef<any>;\n}\n\n/**\n * Collect all JobDefs from a JobRouter (including nested) with their key paths\n */\nexport function collectJobEntries(\n router: JobRouter<any>,\n prefix = '',\n): JobEntry[]\n{\n const entries: JobEntry[] = [];\n\n for (const [key, value] of Object.entries(router.jobs))\n {\n const path = prefix ? `${prefix}.${key}` : key;\n\n if (isJobRouter(value))\n {\n // Nested router - recurse\n entries.push(...collectJobEntries(value, path));\n }\n else if (isJobDef(value))\n {\n entries.push({ key: path, job: value });\n }\n }\n\n return entries;\n}\n\n/**\n * Collect all JobDefs from a JobRouter (including nested)\n */\nexport function collectJobs(\n router: JobRouter<any>,\n prefix = '',\n): JobDef<any>[]\n{\n return collectJobEntries(router, prefix).map((entry) => entry.job);\n}\n","/**\n * Job Registration\n *\n * Registers jobs with pg-boss\n */\n\nimport type PgBoss from 'pg-boss';\nimport { logger } from '@spfn/core/logger';\nimport { env } from '@spfn/core/config';\nimport type { JobDef, JobOptions, JobQueuePolicy, JobRouter } from './types';\nimport type { EventDef } from '@spfn/core/event';\nimport { collectJobEntries, collectJobs } from './job-router';\nimport { resolveQueuePolicy } from './queue-policy';\nimport { toPgBossRetryOptions } from './retry-options';\nimport { getBoss, shouldClearOnStart, shouldSweepOrphanSchedules } from './boss';\n\nconst jobLogger = logger.child('@spfn/core:job');\n\n/**\n * Get the pg-boss queue name for an event\n */\nexport function getEventQueueName(eventName: string): string\n{\n return `event:${eventName}`;\n}\n\n/**\n * Build default pg-boss options for a job\n */\nfunction getDefaultJobOptions(options?: JobOptions): PgBoss.SendOptions\n{\n return {\n ...toPgBossRetryOptions(options),\n expireInSeconds: options?.expireInSeconds ?? 300,\n };\n}\n\n/**\n * Register all jobs from a JobRouter with pg-boss\n *\n * This function:\n * 1. Collects all jobs from the router (including nested routers)\n * 2. Optionally clears existing jobs (if clearOnStart is enabled)\n * 3. Registers each job's worker handler with pg-boss\n * 4. Sets up cron schedules for scheduled jobs\n * 5. Queues runOnce jobs\n * 6. Connects event subscriptions to job queues\n *\n * @param router - JobRouter containing job definitions\n *\n * @example\n * ```typescript\n * // Define jobs\n * const sendEmail = job('send-email')\n * .input(Type.Object({ to: Type.String() }))\n * .handler(async (input) => { ... });\n *\n * const dailyReport = job('daily-report')\n * .cron('0 9 * * *')\n * .handler(async () => { ... });\n *\n * // Create router\n * const jobRouter = defineJobRouter({ sendEmail, dailyReport });\n *\n * // Initialize pg-boss first\n * await initBoss({ connectionString: process.env.DATABASE_URL! });\n *\n * // Register jobs\n * await registerJobs(jobRouter);\n * ```\n */\nexport async function registerJobs(router: JobRouter<any>): Promise<void>\n{\n const boss = getBoss();\n if (!boss)\n {\n throw new Error(\n 'pg-boss not initialized. Call initBoss() before registerJobs()',\n );\n }\n\n assertUniqueJobNames(router);\n\n const jobs = collectJobs(router);\n const clearOnStart = shouldClearOnStart();\n\n jobLogger.info(`Registering ${jobs.length} job(s)...`);\n\n // Clear existing jobs if requested (useful for development)\n if (clearOnStart)\n {\n jobLogger.info('Clearing existing jobs before registration...');\n for (const job of jobs)\n {\n // Clear job queue\n await boss.deleteAllJobs(job.name);\n\n // Also clear event queue if subscribed\n if (job.subscribedEvent)\n {\n const eventQueue = getEventQueueName(job.subscribedEvent);\n await boss.deleteAllJobs(eventQueue);\n }\n }\n jobLogger.info('Existing jobs cleared');\n }\n\n // Each job registers an independent pg-boss queue/worker, so register them\n // concurrently — sequential awaits made startup scale linearly with job count.\n await Promise.all(jobs.map((job) => registerJob(job)));\n\n for (const job of jobs)\n {\n if (job.cronExpression)\n {\n registeredCronNames.add(job.name);\n }\n }\n\n if (shouldSweepOrphanSchedules())\n {\n await sweepOrphanSchedules(boss);\n }\n\n jobLogger.info('All jobs registered successfully');\n}\n\n/**\n * Reject two job definitions that share a `name`\n *\n * A job name *is* its pg-boss queue name, so two jobs with the same name share\n * one queue and one worker: whichever registered last silently handles both.\n * Merged routers make this easy to hit, and it must fail before any queue or\n * worker exists.\n *\n * @throws if two collected jobs share a name\n */\nfunction assertUniqueJobNames(router: JobRouter<any>): void\n{\n const keysByName = new Map<string, string>();\n\n for (const { key, job } of collectJobEntries(router))\n {\n const existingKey = keysByName.get(job.name);\n\n if (existingKey)\n {\n throw new Error(\n `registerJobs(): job name \"${job.name}\" is defined twice ` +\n `(keys \"${existingKey}\" and \"${key}\"); ` +\n 'job names are pg-boss queue names and must be unique',\n );\n }\n\n keysByName.set(job.name, key);\n }\n}\n\n// Cron names accumulate across registerJobs() calls so a sweep in one call\n// never treats another router's schedules (registered earlier in the same\n// process) as orphans.\nconst registeredCronNames = new Set<string>();\n\n/**\n * Reset sweep state accumulated by registerJobs()\n *\n * @internal test-only\n */\nexport function resetOrphanSweepState(): void\n{\n registeredCronNames.clear();\n}\n\n/**\n * Unschedule pg-boss schedules that are no longer declared on any router\n * registered in this process (opt-in via `sweepOrphanSchedules`)\n *\n * The router is the only sanctioned way to create cron schedules, so any\n * schedule whose name is not a declared cron job is an orphan: pg-boss keeps\n * creating jobs for it on every cron tick, and without a worker they\n * accumulate forever. Unscheduling stops the pile-up; the queue and its\n * existing job rows are left untouched so nothing is destroyed if the name\n * belongs to another app, an ad-hoc getBoss() schedule, or a newer deploy.\n *\n * Skipped entirely when no cron job has been declared — an empty router says\n * nothing about which schedules are orphans. Runs once after registration;\n * failures are logged per orphan and never block startup.\n */\nasync function sweepOrphanSchedules(boss: PgBoss): Promise<void>\n{\n if (registeredCronNames.size === 0)\n {\n jobLogger.debug('No cron jobs declared; skipping orphan schedule sweep');\n\n return;\n }\n\n try\n {\n const schedules = await boss.getSchedules();\n const orphans = schedules.filter((schedule) => !registeredCronNames.has(schedule.name));\n\n if (orphans.length === 0)\n {\n jobLogger.debug('No orphan schedules found');\n\n return;\n }\n\n await Promise.all(orphans.map(async (orphan) =>\n {\n try\n {\n await boss.unschedule(orphan.name, orphan.key);\n jobLogger.info(`Removed orphan schedule: ${orphan.name}`);\n }\n catch (error)\n {\n jobLogger.error(`Failed to remove orphan schedule: ${orphan.name}`, {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }));\n }\n catch (error)\n {\n jobLogger.error('Orphan schedule sweep failed', {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n}\n\n/**\n * Create the queue with the wanted policy, or report that it already differs\n *\n * pg-boss `create_queue` is `INSERT … ON CONFLICT DO NOTHING`, so a queue\n * created by an earlier version keeps its old policy and `createQueue` is a\n * silent no-op. `updateQueue` cannot change a policy either. Comparing against\n * `getQueue` is the only way an already-deployed app ever gets the fix.\n *\n * Recreating the queue drops its job rows, so that only happens under\n * `clearOnStart` (development). Otherwise the mismatch is logged and boot\n * continues — a queue that does not deduplicate is a degraded queue, not a\n * broken one.\n */\nasync function ensureQueue(boss: PgBoss, queueName: string, policy: JobQueuePolicy): Promise<void>\n{\n await boss.createQueue(queueName, { policy });\n\n // null = queue missing (nothing to compare); pg-boss defaults it to 'standard'\n const existing = await boss.getQueue(queueName);\n if (!existing || existing.policy === policy)\n {\n return;\n }\n\n if (shouldClearOnStart())\n {\n await boss.deleteQueue(queueName);\n await boss.createQueue(queueName, { policy });\n jobLogger.info(`[Queue:${queueName}] policy ${existing.policy} → ${policy} (recreated: clearOnStart)`);\n\n return;\n }\n\n jobLogger.error(\n `[Queue:${queueName}] policy mismatch: the queue exists as \"${existing.policy}\" but its jobs ` +\n `require \"${policy}\". Delete the queue in a quiet window ` +\n `(getBoss().deleteQueue('${queueName}') drops its job rows) or set clearOnStart in development; ` +\n 'until then singletonKey/runOnce on this queue do not deduplicate',\n );\n}\n\n/**\n * Build the once-per-queue ensure used by a single registerJob() call\n *\n * registerJob touches its queue up to three times (worker, cron, runOnce) and\n * all three want the same policy, so the queue is ensured on first use only —\n * otherwise the same mismatch would be created and logged three times over.\n */\nfunction createQueueEnsurer(boss: PgBoss, policy: JobQueuePolicy): (queueName: string) => Promise<void>\n{\n const ensured = new Set<string>();\n\n return async (queueName: string) =>\n {\n if (ensured.has(queueName))\n {\n return;\n }\n\n ensured.add(queueName);\n\n await ensureQueue(boss, queueName, policy);\n };\n}\n\n/**\n * Execute a single job handler with logging\n */\nasync function executeJobHandler(\n job: JobDef<any>,\n pgBossJob: PgBoss.Job<any>,\n): Promise<void>\n{\n jobLogger.debug(`[Job:${job.name}] Executing...`, { jobId: pgBossJob.id });\n\n const startTime = Date.now();\n\n try\n {\n if (job.inputSchema)\n {\n await (job.handler as (input: unknown) => Promise<void>)(pgBossJob.data);\n }\n else\n {\n await (job.handler as () => Promise<void>)();\n }\n\n const duration = Date.now() - startTime;\n jobLogger.info(`[Job:${job.name}] Completed in ${duration}ms`, {\n jobId: pgBossJob.id,\n duration,\n });\n }\n catch (error)\n {\n const duration = Date.now() - startTime;\n jobLogger.error(`[Job:${job.name}] Failed after ${duration}ms`, {\n jobId: pgBossJob.id,\n duration,\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n}\n\n/**\n * Register worker handler for a job\n *\n * When batchSize > 1, jobs are processed in parallel.\n * Failed jobs are individually marked via boss.fail() so pg-boss can retry them.\n * Successful jobs are auto-completed when the handler callback resolves.\n */\nasync function registerWorker(\n boss: PgBoss,\n job: JobDef<any>,\n queueName: string,\n ensureQueueOnce: (queueName: string) => Promise<void>,\n): Promise<void>\n{\n // Ensure queue exists before registering worker\n await ensureQueueOnce(queueName);\n\n const batchSize = job.options?.batchSize ?? 1;\n const pollingIntervalSeconds = job.options?.pollingIntervalSeconds ?? env.JOB_POLLING_INTERVAL_SECONDS;\n\n await boss.work(\n queueName,\n { batchSize, pollingIntervalSeconds },\n async (pgBossJobs) =>\n {\n if (batchSize <= 1)\n {\n // Single job — throw on error for pg-boss retry\n await executeJobHandler(job, pgBossJobs[0]);\n\n return;\n }\n\n // Batch — parallel execution with individual failure handling\n const results = await Promise.allSettled(\n pgBossJobs.map((pgBossJob) => executeJobHandler(job, pgBossJob)),\n );\n\n // Collect failed job IDs and mark them individually.\n // boss.fail() sets state = 'failed'; the subsequent auto-complete\n // from work() only affects jobs still in 'active' state, so\n // already-failed jobs are not overwritten.\n const failedIds: string[] = [];\n\n for (let i = 0; i < results.length; i++)\n {\n if (results[i].status === 'rejected')\n {\n failedIds.push(pgBossJobs[i].id);\n }\n }\n\n if (failedIds.length > 0)\n {\n await boss.fail(queueName, failedIds);\n }\n\n // Callback resolves → pg-boss auto-completes remaining 'active' jobs\n },\n );\n}\n\n/**\n * Connect event to pg-boss queue\n */\nfunction connectEventToQueue(\n boss: PgBoss,\n job: JobDef<any>,\n queueName: string,\n): void\n{\n if (!job._subscribedEventDef)\n {\n return;\n }\n\n const eventDef = job._subscribedEventDef as EventDef<any>;\n eventDef._registerJobQueue(queueName, async (queue, payload) =>\n {\n await boss.send(queue, payload as object, getDefaultJobOptions(job.options));\n });\n\n jobLogger.debug(`[Job:${job.name}] Connected to event: ${job.subscribedEvent}`);\n}\n\n/**\n * Register cron schedule for a job\n */\nasync function registerCronSchedule(\n boss: PgBoss,\n job: JobDef<any>,\n ensureQueueOnce: (queueName: string) => Promise<void>,\n): Promise<void>\n{\n if (!job.cronExpression)\n {\n return;\n }\n\n jobLogger.debug(`[Job:${job.name}] Scheduling cron: ${job.cronExpression}`);\n\n // Ensure queue exists for cron jobs (uses job.name as queue)\n await ensureQueueOnce(job.name);\n\n await boss.schedule(\n job.name,\n job.cronExpression,\n {},\n getDefaultJobOptions(job.options),\n );\n\n jobLogger.info(`[Job:${job.name}] Cron scheduled: ${job.cronExpression}`);\n}\n\n/**\n * Queue a runOnce job\n */\nasync function queueRunOnceJob(\n boss: PgBoss,\n job: JobDef<any>,\n ensureQueueOnce: (queueName: string) => Promise<void>,\n): Promise<void>\n{\n if (!job.runOnce)\n {\n return;\n }\n\n jobLogger.debug(`[Job:${job.name}] Queuing runOnce job`);\n\n // Ensure queue exists for runOnce jobs (uses job.name as queue)\n await ensureQueueOnce(job.name);\n\n await boss.send(\n job.name,\n {},\n {\n ...getDefaultJobOptions(job.options),\n singletonKey: `runOnce:${job.name}`,\n },\n );\n\n jobLogger.info(`[Job:${job.name}] runOnce job queued`);\n}\n\n/**\n * Register a single job with pg-boss\n */\nasync function registerJob(job: JobDef<any>): Promise<void>\n{\n const boss = getBoss();\n if (!boss)\n {\n throw new Error('pg-boss not initialized');\n }\n\n const queueName = job.subscribedEvent\n ? getEventQueueName(job.subscribedEvent)\n : job.name;\n\n jobLogger.debug(`Registering job: ${job.name}`, {\n queueName,\n subscribedEvent: job.subscribedEvent,\n });\n\n const ensureQueueOnce = createQueueEnsurer(boss, resolveQueuePolicy(job));\n\n await registerWorker(boss, job, queueName, ensureQueueOnce);\n connectEventToQueue(boss, job, queueName);\n await registerCronSchedule(boss, job, ensureQueueOnce);\n await queueRunOnceJob(boss, job, ensureQueueOnce);\n\n jobLogger.debug(`Job registered: ${job.name}`);\n}\n"]}
@@ -341,17 +341,6 @@ function getConsoleConfig() {
341
341
  // Dev: colored output, Production: plain text
342
342
  };
343
343
  }
344
- function validateEnvironment() {
345
- const nodeEnv = process.env.NODE_ENV;
346
- if (!nodeEnv) {
347
- process.stderr.write(
348
- "[Logger] Warning: NODE_ENV is not set. Defaulting to test environment.\n"
349
- );
350
- }
351
- }
352
- function validateConfig() {
353
- validateEnvironment();
354
- }
355
344
 
356
345
  // src/logger/factory.ts
357
346
  function initializeTransports() {
@@ -372,7 +361,6 @@ function getLogLevel() {
372
361
  return "info";
373
362
  }
374
363
  function initializeLogger() {
375
- validateConfig();
376
364
  return new Logger({
377
365
  level: getLogLevel(),
378
366
  transports: initializeTransports()
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/logger/types.ts","../../src/logger/formatters.ts","../../src/logger/logger.ts","../../src/logger/transports/console.ts","../../src/logger/config.ts","../../src/logger/factory.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Logger Type Definitions\n *\n * 로깅 시스템 타입 정의\n *\n * ✅ 구현 완료:\n * - LogLevel 타입 정의\n * - LogMetadata 인터페이스\n * - Transport 인터페이스\n * - 환경별 설정 타입\n *\n * 🔗 관련 파일:\n * - src/logger/logger.ts (Logger 클래스)\n * - src/logger/transports/ (Transport 구현체)\n * - src/logger/config.ts (설정)\n */\n\n/**\n * 로그 레벨\n * debug < info < warn < error < fatal\n */\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal';\n\n/**\n * 로그 레벨 우선순위\n */\nexport const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n fatal: 4,\n};\n\n/**\n * 로그 메타데이터\n */\nexport interface LogMetadata\n{\n timestamp: Date;\n level: LogLevel;\n message: string;\n module?: string;\n error?: Error;\n context?: Record<string, unknown>;\n}\n\n/**\n * Transport 인터페이스\n * 모든 Transport는 이 인터페이스를 구현해야 함\n */\nexport interface Transport\n{\n /**\n * Transport 이름\n */\n name: string;\n\n /**\n * 최소 로그 레벨 (이 레벨 이상만 처리)\n */\n level: LogLevel;\n\n /**\n * 활성화 여부\n */\n enabled: boolean;\n\n /**\n * 로그 처리 함수\n */\n log(metadata: LogMetadata): Promise<void>;\n\n /**\n * Transport 종료 (리소스 정리)\n */\n close?(): Promise<void>;\n}\n\n/**\n * Logger 설정\n */\nexport interface LoggerConfig\n{\n /**\n * 기본 로그 레벨\n */\n level: LogLevel;\n\n /**\n * 모듈명 (context)\n */\n module?: string;\n\n /**\n * Transport 리스트\n */\n transports: Transport[];\n}\n\n/**\n * Transport 설정 (공통)\n */\nexport interface TransportConfig\n{\n level: LogLevel;\n enabled: boolean;\n}\n\n/**\n * Console Transport 설정\n */\nexport interface ConsoleTransportConfig extends TransportConfig\n{\n colorize?: boolean;\n}\n","/**\n * Logger Formatters\n *\n * Log formatting utilities for console and JSON outputs with sensitive data masking.\n */\n\nimport type { LogLevel, LogMetadata } from './types';\n\n/**\n * 민감 정보로 간주되는 키 목록\n * 이 키들을 포함하는 필드는 자동으로 마스킹됨\n */\nconst SENSITIVE_KEYS = [\n 'password',\n 'passwd',\n 'pwd',\n 'secret',\n 'token',\n 'apikey',\n 'api_key',\n 'accesstoken',\n 'access_token',\n 'refreshtoken',\n 'refresh_token',\n 'authorization',\n 'auth',\n 'cookie',\n 'session',\n 'sessionid',\n 'session_id',\n 'privatekey',\n 'private_key',\n 'creditcard',\n 'credit_card',\n 'cardnumber',\n 'card_number',\n 'cvv',\n 'ssn',\n 'pin',\n];\n\n/**\n * 마스킹된 값\n */\nconst MASKED_VALUE = '***MASKED***';\n\n/**\n * 키가 민감 정보를 포함하는지 확인\n */\nfunction isSensitiveKey(key: string): boolean\n{\n const lowerKey = key.toLowerCase();\n\n return SENSITIVE_KEYS.some(sensitive => lowerKey.includes(sensitive));\n}\n\n/**\n * 민감 정보 마스킹\n * Context 객체에서 민감한 정보(비밀번호, 토큰 등)를 마스킹\n * Circular reference를 안전하게 처리\n *\n * @param data - 원본 데이터\n * @param seen - 순환 참조 감지용 WeakSet (내부 사용)\n * @returns 마스킹된 데이터\n */\nexport function maskSensitiveData(data: unknown, seen = new WeakSet<object>()): unknown\n{\n // null, undefined 처리\n if (data === null || data === undefined)\n {\n return data;\n }\n\n // 기본 타입은 그대로 반환\n if (typeof data !== 'object')\n {\n return data;\n }\n\n // Circular reference 감지\n if (seen.has(data as object))\n {\n return '[Circular]';\n }\n seen.add(data as object);\n\n // 배열 처리\n if (Array.isArray(data))\n {\n return data.map(item => maskSensitiveData(item, seen));\n }\n\n // 객체 처리\n const masked: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(data))\n {\n if (isSensitiveKey(key))\n {\n // 민감 정보 키는 마스킹\n masked[key] = MASKED_VALUE;\n }\n else if (typeof value === 'object' && value !== null)\n {\n // 중첩된 객체는 재귀 처리 (seen 전달)\n masked[key] = maskSensitiveData(value, seen);\n }\n else\n {\n // 일반 값은 그대로 유지\n masked[key] = value;\n }\n }\n\n return masked;\n}\n\n/**\n * ANSI 컬러 코드\n */\nconst COLORS = {\n reset: '\\x1b[0m',\n bright: '\\x1b[1m',\n dim: '\\x1b[2m',\n\n // 로그 레벨 컬러\n debug: '\\x1b[36m', // cyan\n info: '\\x1b[32m', // green\n warn: '\\x1b[33m', // yellow\n error: '\\x1b[31m', // red\n fatal: '\\x1b[35m', // magenta\n\n // 추가 컬러\n gray: '\\x1b[90m',\n};\n\n/**\n * 로그 레벨을 컬러 문자열로 변환\n */\nexport function colorizeLevel(level: LogLevel): string\n{\n const color = COLORS[level];\n const levelStr = level.toUpperCase().padEnd(5);\n\n return `${color}${levelStr}${COLORS.reset}`;\n}\n\n/**\n * 타임스탬프 포맷 (ISO 8601)\n */\nexport function formatTimestamp(date: Date): string\n{\n return date.toISOString();\n}\n\n/**\n * 타임스탬프 포맷 (사람이 읽기 쉬운 형식)\n */\nexport function formatTimestampHuman(date: Date): string\n{\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, '0');\n const day = String(date.getDate()).padStart(2, '0');\n const hours = String(date.getHours()).padStart(2, '0');\n const minutes = String(date.getMinutes()).padStart(2, '0');\n const seconds = String(date.getSeconds()).padStart(2, '0');\n const ms = String(date.getMilliseconds()).padStart(3, '0');\n\n return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${ms}`;\n}\n\n/**\n * 에러 객체를 문자열로 변환 (스택 트레이스 포함)\n */\nexport function formatError(error: Error): string\n{\n const lines: string[] = [];\n\n lines.push(`${error.name}: ${error.message}`);\n\n if (error.stack)\n {\n const stackLines = error.stack.split('\\n').slice(1);\n lines.push(...stackLines);\n }\n\n // Cause chain — surfaces original PG errors hidden by DrizzleQueryError\n if (error.cause instanceof Error)\n {\n lines.push(`Caused by: ${formatError(error.cause)}`);\n }\n else if (error.cause !== undefined)\n {\n lines.push(`Caused by: ${String(error.cause)}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Context 객체를 문자열로 변환\n */\nexport function formatContext(context: Record<string, unknown>): string\n{\n try\n {\n return JSON.stringify(context, null, 2);\n }\n catch (error)\n {\n return '[Context serialization failed]';\n }\n}\n\n/**\n * 콘솔용 컬러 포맷\n */\nexport function formatConsole(metadata: LogMetadata, colorize = true): string\n{\n const parts: string[] = [];\n\n // [타임스탬프]\n const timestamp = formatTimestampHuman(metadata.timestamp);\n if (colorize)\n {\n parts.push(`${COLORS.gray}[${timestamp}]${COLORS.reset}`);\n }\n else\n {\n parts.push(`[${timestamp}]`);\n }\n\n // [pid=12345]\n const pid = process.pid;\n if (colorize)\n {\n parts.push(`${COLORS.dim}[pid=${pid}]${COLORS.reset}`);\n }\n else\n {\n parts.push(`[pid=${pid}]`);\n }\n\n // [module=value]\n if (metadata.module)\n {\n if (colorize)\n {\n parts.push(`${COLORS.dim}[module=${metadata.module}]${COLORS.reset}`);\n }\n else\n {\n parts.push(`[module=${metadata.module}]`);\n }\n }\n\n // Context를 각각 [key=value] 형태로 추가\n if (metadata.context && Object.keys(metadata.context).length > 0)\n {\n Object.entries(metadata.context).forEach(([key, value]) =>\n {\n let valueStr: string;\n if (typeof value === 'string')\n {\n valueStr = value;\n }\n else if (typeof value === 'object' && value !== null)\n {\n try\n {\n valueStr = JSON.stringify(value);\n }\n catch (error)\n {\n valueStr = '[circular]';\n }\n }\n else\n {\n valueStr = String(value);\n }\n\n if (colorize)\n {\n parts.push(`${COLORS.dim}[${key}=${valueStr}]${COLORS.reset}`);\n }\n else\n {\n parts.push(`[${key}=${valueStr}]`);\n }\n });\n }\n\n // (LEVEL):\n const levelStr = metadata.level.toUpperCase();\n if (colorize)\n {\n const color = COLORS[metadata.level];\n parts.push(`${color}(${levelStr})${COLORS.reset}:`);\n }\n else\n {\n parts.push(`(${levelStr}):`);\n }\n\n // 메시지\n if (colorize)\n {\n parts.push(`${COLORS.bright}${metadata.message}${COLORS.reset}`);\n }\n else\n {\n parts.push(metadata.message);\n }\n\n let output = parts.join(' ');\n\n // 에러는 별도 줄로 추가\n if (metadata.error)\n {\n output += '\\n' + formatError(metadata.error);\n }\n\n return output;\n}\n\n/**\n * Error 객체를 cause 체인까지 포함한 직렬화 가능 객체로 변환\n */\nfunction formatErrorCauseChain(error: Error): Record<string, unknown>\n{\n const result: Record<string, unknown> = {\n name: error.name,\n message: error.message,\n stack: error.stack,\n };\n\n if (error.cause instanceof Error)\n {\n result.cause = formatErrorCauseChain(error.cause);\n }\n else if (error.cause !== undefined)\n {\n result.cause = String(error.cause);\n }\n\n return result;\n}\n\n/**\n * JSON 포맷 (파일 저장 및 전송용)\n */\nexport function formatJSON(metadata: LogMetadata): string\n{\n const obj: Record<string, unknown> = {\n timestamp: formatTimestamp(metadata.timestamp),\n level: metadata.level,\n message: metadata.message,\n };\n\n if (metadata.module)\n {\n obj.module = metadata.module;\n }\n\n if (metadata.context)\n {\n obj.context = metadata.context;\n }\n\n if (metadata.error)\n {\n obj.error = formatErrorCauseChain(metadata.error);\n }\n\n return JSON.stringify(obj);\n}\n\n/**\n * Drizzle ORM 에러에서 쿼리 정보 추출\n *\n * Drizzle ORM은 에러 메시지에 다음 형식으로 정보를 포함:\n * - \"Failed query: <QUERY>\\nparams: <PARAMS>\"\n * - \"Query: <QUERY>\"\n *\n * @param error - Error 객체\n * @returns 쿼리 정보 (query, params, table)\n */\nexport function extractQueryInfo(error: Error): {\n query?: string;\n params?: unknown;\n table?: string;\n} | null\n{\n const message = error.message;\n\n if (!message) return null;\n\n const result: {\n query?: string;\n params?: unknown;\n table?: string;\n } = {};\n\n // Extract query from \"Failed query: ...\" or \"Query: ...\"\n const queryMatch = message.match(/(?:Failed query:|Query:)\\s*([^\\n]+)/);\n if (queryMatch)\n {\n result.query = queryMatch[1].trim();\n\n // Extract table name from query (e.g., UPDATE \"table_name\" or INSERT INTO \"table_name\")\n const tableMatch = result.query.match(/(?:UPDATE|INSERT INTO|DELETE FROM|FROM)\\s+\"?([a-zA-Z_][a-zA-Z0-9_]*)\"?\\.\"?([a-zA-Z_][a-zA-Z0-9_]*)\"?|(?:UPDATE|INSERT INTO|DELETE FROM|FROM)\\s+\"?([a-zA-Z_][a-zA-Z0-9_]*)\"?/i);\n if (tableMatch)\n {\n // Schema.Table or just Table\n result.table = tableMatch[2] || tableMatch[3] || tableMatch[1];\n }\n }\n\n // Extract params from \"params: ...\"\n const paramsMatch = message.match(/params:\\s*(.+?)(?:\\n|$)/);\n if (paramsMatch)\n {\n const paramsStr = paramsMatch[1].trim();\n try\n {\n // Try to parse as comma-separated values\n result.params = paramsStr.split(',').map(p => p.trim());\n }\n catch (e)\n {\n result.params = paramsStr;\n }\n }\n\n return Object.keys(result).length > 0 ? result : null;\n}\n\n/**\n * Promise rejection의 호출 스택에서 실제 발생 위치 추출\n *\n * 스택 트레이스에서 다음 정보를 추출:\n * - 실제 에러 발생 파일 경로\n * - 라인 번호\n * - 함수명/메서드명\n * - Repository 정보 (있는 경우)\n *\n * @param error - Error 객체\n * @returns Promise context 정보\n */\nexport function extractPromiseContext(error: Error): Record<string, unknown>\n{\n const context: Record<string, unknown> = {};\n\n if (!error.stack) return context;\n\n const stackLines = error.stack.split('\\n');\n\n // Skip first line (error message) and find first meaningful stack frame\n // Ignore node_modules and internal Node.js paths\n for (let i = 1; i < stackLines.length; i++)\n {\n const line = stackLines[i].trim();\n\n // Skip node_modules and node internals\n if (line.includes('node_modules') || line.includes('node:internal')) continue;\n\n // Extract file, line number, and function name\n // Format: \"at ClassName.methodName (file.ts:line:col)\"\n // or: \"at functionName (file.ts:line:col)\"\n // or: \"at file.ts:line:col\"\n\n const match = line.match(/at\\s+(?:([a-zA-Z_$][\\w$]*(?:\\.[a-zA-Z_$][\\w$]*)*)\\s+)?\\(?([^)]+):(\\d+):(\\d+)\\)?/);\n\n if (match)\n {\n const [, functionName, filePath, lineNumber, columnNumber] = match;\n\n // Extract just the filename from the full path\n const fileNameMatch = filePath.match(/([^/\\\\]+)$/);\n const fileName = fileNameMatch ? fileNameMatch[1] : filePath;\n\n context.file = fileName;\n context.line = parseInt(lineNumber, 10);\n context.column = parseInt(columnNumber, 10);\n\n if (functionName)\n {\n // Check if it's a class method (e.g., \"ClassName.methodName\")\n const methodMatch = functionName.match(/^(.+)\\.([^.]+)$/);\n if (methodMatch)\n {\n const [, className, methodName] = methodMatch;\n\n context.class = className;\n context.method = methodName;\n\n // Check if it's a Repository\n if (className.includes('Repository'))\n {\n context.repository = className;\n }\n }\n else\n {\n context.function = functionName;\n }\n }\n\n // Found first relevant frame, stop here\n break;\n }\n }\n\n return context;\n}\n\n/**\n * Unhandled rejection 에러를 상세하게 포맷팅\n *\n * Promise context와 DB 쿼리 정보를 자동으로 추출하여\n * 에러 발생 위치와 원인을 명확하게 파악할 수 있도록 함\n *\n * @param reason - Rejection 원인 (Error 또는 기타)\n * @param promise - Promise 객체\n * @returns 상세 context 정보\n */\nexport function formatUnhandledRejection(reason: unknown, promise: Promise<unknown>): {\n error: Error;\n context: Record<string, unknown>;\n}\n{\n // Convert reason to Error if not already\n let error: Error;\n if (reason instanceof Error)\n {\n error = reason;\n }\n else if (typeof reason === 'string')\n {\n error = new Error(reason);\n }\n else\n {\n error = new Error(JSON.stringify(reason));\n }\n\n const context: Record<string, unknown> = {\n promise: String(promise),\n };\n\n // Extract promise context (file, line, function, etc.)\n const promiseContext = extractPromiseContext(error);\n if (Object.keys(promiseContext).length > 0)\n {\n context.promiseContext = promiseContext;\n }\n\n // Extract DB query info if available\n const queryInfo = extractQueryInfo(error);\n if (queryInfo)\n {\n context.queryInfo = queryInfo;\n }\n\n return { error, context };\n}\n","/**\n * Logger Class\n *\n * Central logging class with multiple transports, child loggers, and sensitive data masking.\n */\n\nimport { format } from 'node:util';\nimport type { LogLevel, LogMetadata, LoggerConfig, Transport } from './types';\nimport { LOG_LEVEL_PRIORITY } from './types';\nimport { maskSensitiveData } from './formatters';\n\nconst FORMAT_PATTERN = /%[sdifjoOc%]/;\n\n/**\n * Logger class\n */\nexport class Logger\n{\n private readonly config: LoggerConfig;\n private readonly module?: string;\n\n constructor(config: LoggerConfig)\n {\n this.config = config;\n this.module = config.module;\n }\n\n /**\n * Convert unknown error to Error object\n */\n private toError(error: unknown): Error\n {\n if (error instanceof Error) return error;\n if (typeof error === 'string') return new Error(error);\n if (typeof error === 'object' && error !== null)\n {\n return new Error(JSON.stringify(error));\n }\n\n return new Error(String(error));\n }\n\n /**\n * Check if value is a context object (not an error)\n */\n private isContext(value: unknown): value is Record<string, unknown>\n {\n if (typeof value !== 'object' || value === null) return false;\n if (value instanceof Error) return false;\n\n // Use stack as the primary indicator for error detection\n // - Almost all Error objects have stack (auto-generated by JS engines)\n // - Regular objects rarely use 'stack' as a property name\n // - This avoids false positives for objects with 'name' or 'message' properties\n const hasStack = 'stack' in value && typeof (value as any).stack === 'string';\n\n if (hasStack)\n {\n return false; // Likely an error object\n }\n\n // No stack -> treat as context\n // This allows {name: \"...\", method: \"...\"} to be treated as context\n return true;\n }\n\n /**\n * Get current log level\n */\n get level(): LogLevel\n {\n return this.config.level;\n }\n\n /**\n * Create child logger (per module)\n */\n child(module: string): Logger\n {\n return new Logger({\n ...this.config,\n module,\n });\n }\n\n /**\n * Common log method with error/context detection\n */\n private logWithLevel(\n level: LogLevel,\n message: string,\n errorOrContext?: Error | unknown | Record<string, unknown>,\n context?: Record<string, unknown>,\n ): void\n {\n // printf-style format string: logger.info('url: %s', value)\n if (errorOrContext !== undefined && FORMAT_PATTERN.test(message))\n {\n this.log(level, format(message, errorOrContext), undefined, context);\n\n return;\n }\n\n if (errorOrContext instanceof Error)\n {\n this.log(level, message, errorOrContext, context);\n }\n else if (errorOrContext !== undefined && typeof errorOrContext === 'object' && !this.isContext(errorOrContext))\n {\n this.log(level, message, this.toError(errorOrContext), context);\n }\n else if (typeof errorOrContext === 'string' || typeof errorOrContext === 'number' || typeof errorOrContext === 'boolean')\n {\n this.log(level, message, this.toError(errorOrContext), context);\n }\n else\n {\n this.log(level, message, undefined, errorOrContext as Record<string, unknown>);\n }\n }\n\n /**\n * Debug log\n */\n debug(message: string, context?: Record<string, unknown>): void;\n debug(message: string, formatArg: string | number | boolean): void;\n debug(message: string, error: Error | unknown, context?: Record<string, unknown>): void;\n debug(message: string, errorOrContext?: unknown, context?: Record<string, unknown>): void\n {\n this.logWithLevel('debug', message, errorOrContext, context);\n }\n\n /**\n * Info log\n */\n info(message: string, context?: Record<string, unknown>): void;\n info(message: string, formatArg: string | number | boolean): void;\n info(message: string, error: Error | unknown, context?: Record<string, unknown>): void;\n info(message: string, errorOrContext?: unknown, context?: Record<string, unknown>): void\n {\n this.logWithLevel('info', message, errorOrContext, context);\n }\n\n /**\n * Warn log\n */\n warn(message: string, context?: Record<string, unknown>): void;\n warn(message: string, formatArg: string | number | boolean): void;\n warn(message: string, error: Error | unknown, context?: Record<string, unknown>): void;\n warn(message: string, errorOrContext?: unknown, context?: Record<string, unknown>): void\n {\n this.logWithLevel('warn', message, errorOrContext, context);\n }\n\n /**\n * Error log\n */\n error(message: string, context?: Record<string, unknown>): void;\n error(message: string, formatArg: string | number | boolean): void;\n error(message: string, error: Error | unknown, context?: Record<string, unknown>): void;\n error(message: string, errorOrContext?: unknown, context?: Record<string, unknown>): void\n {\n this.logWithLevel('error', message, errorOrContext, context);\n }\n\n /**\n * Fatal log\n */\n fatal(message: string, context?: Record<string, unknown>): void;\n fatal(message: string, formatArg: string | number | boolean): void;\n fatal(message: string, error: Error | unknown, context?: Record<string, unknown>): void;\n fatal(message: string, errorOrContext?: unknown, context?: Record<string, unknown>): void\n {\n this.logWithLevel('fatal', message, errorOrContext, context);\n }\n\n /**\n * Log processing (internal)\n */\n private log(level: LogLevel, message: string, error?: Error, context?: Record<string, unknown>): void\n {\n // Early return if log level is below configured level\n // This prevents unnecessary metadata creation and processing\n if (LOG_LEVEL_PRIORITY[level] < LOG_LEVEL_PRIORITY[this.config.level])\n {\n return;\n }\n\n const metadata: LogMetadata = {\n timestamp: new Date(),\n level,\n message,\n module: this.module,\n error,\n // Mask sensitive information in context to prevent credential leaks\n context: context ? maskSensitiveData(context) as Record<string, unknown> : undefined,\n };\n\n // Pass to all enabled Transports\n this.processTransports(metadata);\n }\n\n /**\n * Process Transports\n */\n private processTransports(metadata: LogMetadata): void\n {\n const promises = this.config.transports\n .filter(transport => transport.enabled)\n .map(transport => this.safeTransportLog(transport, metadata));\n\n // Async processing to prevent Transport errors from blocking logs\n Promise.all(promises).catch(error =>\n {\n // Use stderr directly to avoid circular logging\n const errorMessage = error instanceof Error ? error.message : String(error);\n process.stderr.write(`[Logger] Transport error: ${errorMessage}\\n`);\n });\n }\n\n /**\n * Transport log (error-safe)\n */\n private async safeTransportLog(transport: Transport, metadata: LogMetadata): Promise<void>\n {\n try\n {\n await transport.log(metadata);\n }\n catch (error)\n {\n // Use stderr directly to avoid circular logging\n const errorMessage = error instanceof Error ? error.message : String(error);\n process.stderr.write(`[Logger] Transport \"${transport.name}\" failed: ${errorMessage}\\n`);\n }\n }\n\n /**\n * Close all Transports\n */\n async close(): Promise<void>\n {\n const closePromises = this.config.transports\n .filter(transport => transport.close)\n .map(transport => transport.close!());\n\n await Promise.all(closePromises);\n }\n}\n","/**\n * Console Transport\n *\n * 콘솔 출력 Transport\n *\n * ✅ 구현 완료:\n * - 콘솔 출력 (stdout/stderr)\n * - 컬러 출력 지원\n * - 로그 레벨별 스트림 분리 (warn/error/fatal → stderr)\n *\n * 🔗 관련 파일:\n * - src/logger/types.ts (Transport 인터페이스)\n * - src/logger/formatters.ts (포맷터)\n * - src/logger/config.ts (설정)\n */\n\nimport type { Transport, LogMetadata, LogLevel, ConsoleTransportConfig } from '../types';\nimport { LOG_LEVEL_PRIORITY } from '../types';\nimport { formatConsole } from '../formatters';\n\n/**\n * Console Transport\n */\nexport class ConsoleTransport implements Transport\n{\n public readonly name = 'console';\n public readonly level: LogLevel;\n public readonly enabled: boolean;\n private readonly colorize: boolean;\n\n constructor(config: ConsoleTransportConfig)\n {\n this.level = config.level;\n this.enabled = config.enabled;\n this.colorize = config.colorize ?? true;\n }\n\n async log(metadata: LogMetadata): Promise<void>\n {\n // Enabled 상태 체크\n if (!this.enabled)\n {\n return;\n }\n\n // 로그 레벨 체크\n if (LOG_LEVEL_PRIORITY[metadata.level] < LOG_LEVEL_PRIORITY[this.level])\n {\n return;\n }\n\n // 포맷팅\n const message = formatConsole(metadata, this.colorize);\n\n // warn/error/fatal은 stderr로, 나머지는 stdout으로\n if (metadata.level === 'warn' || metadata.level === 'error' || metadata.level === 'fatal')\n {\n console.error(message);\n }\n else\n {\n console.log(message);\n }\n }\n}\n","/**\n * Logger Configuration\n *\n * Environment-based logger configuration with validation for console transport.\n */\n\nimport type {\n ConsoleTransportConfig,\n} from './types';\n\n/**\n * Console Transport configuration\n */\nexport function getConsoleConfig(): ConsoleTransportConfig\n{\n const isProduction = process.env.NODE_ENV === 'production';\n\n return {\n level: 'debug',\n enabled: true,\n colorize: !isProduction, // Dev: colored output, Production: plain text\n };\n}\n\n/**\n * Validate environment variables\n */\nfunction validateEnvironment(): void\n{\n const nodeEnv = process.env.NODE_ENV;\n\n if (!nodeEnv)\n {\n process.stderr.write(\n '[Logger] Warning: NODE_ENV is not set. Defaulting to test environment.\\n',\n );\n }\n // Allow any NODE_ENV value (development, production, test, staging, local, etc.)\n // No validation needed - users can use custom environments\n}\n\n/**\n * Validate all logger configuration\n */\nexport function validateConfig(): void\n{\n validateEnvironment();\n}\n","/**\n * Logger Factory\n *\n * Creates and initializes the logger instance with configured transports\n */\n\nimport { Logger } from './logger';\nimport { ConsoleTransport } from './transports/console';\nimport { getConsoleConfig, validateConfig } from './config';\nimport type { LogLevel, Transport } from './types';\nimport { LOG_LEVEL_PRIORITY } from './types';\n\n/**\n * Initialize transports based on environment and configuration\n */\nfunction initializeTransports(): Transport[]\n{\n const transports: Transport[] = [];\n\n // Console Transport (always enabled)\n const consoleConfig = getConsoleConfig();\n transports.push(new ConsoleTransport(consoleConfig));\n\n // Future: Add more transports (Slack, Email, etc.)\n // if (config.slack?.enabled) {\n // transports.push(new SlackTransport(config.slack));\n // }\n\n return transports;\n}\n\n/**\n * Get validated log level from environment variables\n */\nfunction getLogLevel(): LogLevel\n{\n const envLevel = process.env.SPFN_LOG_LEVEL\n || process.env.NEXT_PUBLIC_SPFN_LOG_LEVEL\n || 'info';\n\n if (envLevel in LOG_LEVEL_PRIORITY)\n {\n return envLevel as LogLevel;\n }\n\n process.stderr.write(\n `[Logger] Invalid log level \"${envLevel}\", defaulting to \"info\"\\n`,\n );\n\n return 'info';\n}\n\n/**\n * Initialize logger with configuration validation\n */\nfunction initializeLogger(): Logger\n{\n // Validate configuration before creating logger\n validateConfig();\n\n // Create logger with configured transports\n return new Logger({\n level: getLogLevel(),\n transports: initializeTransports(),\n });\n}\n\n/**\n * Singleton Logger instance\n */\nexport const logger: Logger = initializeLogger();\n"]}
1
+ {"version":3,"sources":["../../src/logger/types.ts","../../src/logger/formatters.ts","../../src/logger/logger.ts","../../src/logger/transports/console.ts","../../src/logger/config.ts","../../src/logger/factory.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Logger Type Definitions\n *\n * 로깅 시스템 타입 정의\n *\n * ✅ 구현 완료:\n * - LogLevel 타입 정의\n * - LogMetadata 인터페이스\n * - Transport 인터페이스\n * - 환경별 설정 타입\n *\n * 🔗 관련 파일:\n * - src/logger/logger.ts (Logger 클래스)\n * - src/logger/transports/ (Transport 구현체)\n * - src/logger/config.ts (설정)\n */\n\n/**\n * 로그 레벨\n * debug < info < warn < error < fatal\n */\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal';\n\n/**\n * 로그 레벨 우선순위\n */\nexport const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n fatal: 4,\n};\n\n/**\n * 로그 메타데이터\n */\nexport interface LogMetadata\n{\n timestamp: Date;\n level: LogLevel;\n message: string;\n module?: string;\n error?: Error;\n context?: Record<string, unknown>;\n}\n\n/**\n * Transport 인터페이스\n * 모든 Transport는 이 인터페이스를 구현해야 함\n */\nexport interface Transport\n{\n /**\n * Transport 이름\n */\n name: string;\n\n /**\n * 최소 로그 레벨 (이 레벨 이상만 처리)\n */\n level: LogLevel;\n\n /**\n * 활성화 여부\n */\n enabled: boolean;\n\n /**\n * 로그 처리 함수\n */\n log(metadata: LogMetadata): Promise<void>;\n\n /**\n * Transport 종료 (리소스 정리)\n */\n close?(): Promise<void>;\n}\n\n/**\n * Logger 설정\n */\nexport interface LoggerConfig\n{\n /**\n * 기본 로그 레벨\n */\n level: LogLevel;\n\n /**\n * 모듈명 (context)\n */\n module?: string;\n\n /**\n * Transport 리스트\n */\n transports: Transport[];\n}\n\n/**\n * Transport 설정 (공통)\n */\nexport interface TransportConfig\n{\n level: LogLevel;\n enabled: boolean;\n}\n\n/**\n * Console Transport 설정\n */\nexport interface ConsoleTransportConfig extends TransportConfig\n{\n colorize?: boolean;\n}\n","/**\n * Logger Formatters\n *\n * Log formatting utilities for console and JSON outputs with sensitive data masking.\n */\n\nimport type { LogLevel, LogMetadata } from './types';\n\n/**\n * 민감 정보로 간주되는 키 목록\n * 이 키들을 포함하는 필드는 자동으로 마스킹됨\n */\nconst SENSITIVE_KEYS = [\n 'password',\n 'passwd',\n 'pwd',\n 'secret',\n 'token',\n 'apikey',\n 'api_key',\n 'accesstoken',\n 'access_token',\n 'refreshtoken',\n 'refresh_token',\n 'authorization',\n 'auth',\n 'cookie',\n 'session',\n 'sessionid',\n 'session_id',\n 'privatekey',\n 'private_key',\n 'creditcard',\n 'credit_card',\n 'cardnumber',\n 'card_number',\n 'cvv',\n 'ssn',\n 'pin',\n];\n\n/**\n * 마스킹된 값\n */\nconst MASKED_VALUE = '***MASKED***';\n\n/**\n * 키가 민감 정보를 포함하는지 확인\n */\nfunction isSensitiveKey(key: string): boolean\n{\n const lowerKey = key.toLowerCase();\n\n return SENSITIVE_KEYS.some(sensitive => lowerKey.includes(sensitive));\n}\n\n/**\n * 민감 정보 마스킹\n * Context 객체에서 민감한 정보(비밀번호, 토큰 등)를 마스킹\n * Circular reference를 안전하게 처리\n *\n * @param data - 원본 데이터\n * @param seen - 순환 참조 감지용 WeakSet (내부 사용)\n * @returns 마스킹된 데이터\n */\nexport function maskSensitiveData(data: unknown, seen = new WeakSet<object>()): unknown\n{\n // null, undefined 처리\n if (data === null || data === undefined)\n {\n return data;\n }\n\n // 기본 타입은 그대로 반환\n if (typeof data !== 'object')\n {\n return data;\n }\n\n // Circular reference 감지\n if (seen.has(data as object))\n {\n return '[Circular]';\n }\n seen.add(data as object);\n\n // 배열 처리\n if (Array.isArray(data))\n {\n return data.map(item => maskSensitiveData(item, seen));\n }\n\n // 객체 처리\n const masked: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(data))\n {\n if (isSensitiveKey(key))\n {\n // 민감 정보 키는 마스킹\n masked[key] = MASKED_VALUE;\n }\n else if (typeof value === 'object' && value !== null)\n {\n // 중첩된 객체는 재귀 처리 (seen 전달)\n masked[key] = maskSensitiveData(value, seen);\n }\n else\n {\n // 일반 값은 그대로 유지\n masked[key] = value;\n }\n }\n\n return masked;\n}\n\n/**\n * ANSI 컬러 코드\n */\nconst COLORS = {\n reset: '\\x1b[0m',\n bright: '\\x1b[1m',\n dim: '\\x1b[2m',\n\n // 로그 레벨 컬러\n debug: '\\x1b[36m', // cyan\n info: '\\x1b[32m', // green\n warn: '\\x1b[33m', // yellow\n error: '\\x1b[31m', // red\n fatal: '\\x1b[35m', // magenta\n\n // 추가 컬러\n gray: '\\x1b[90m',\n};\n\n/**\n * 로그 레벨을 컬러 문자열로 변환\n */\nexport function colorizeLevel(level: LogLevel): string\n{\n const color = COLORS[level];\n const levelStr = level.toUpperCase().padEnd(5);\n\n return `${color}${levelStr}${COLORS.reset}`;\n}\n\n/**\n * 타임스탬프 포맷 (ISO 8601)\n */\nexport function formatTimestamp(date: Date): string\n{\n return date.toISOString();\n}\n\n/**\n * 타임스탬프 포맷 (사람이 읽기 쉬운 형식)\n */\nexport function formatTimestampHuman(date: Date): string\n{\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, '0');\n const day = String(date.getDate()).padStart(2, '0');\n const hours = String(date.getHours()).padStart(2, '0');\n const minutes = String(date.getMinutes()).padStart(2, '0');\n const seconds = String(date.getSeconds()).padStart(2, '0');\n const ms = String(date.getMilliseconds()).padStart(3, '0');\n\n return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${ms}`;\n}\n\n/**\n * 에러 객체를 문자열로 변환 (스택 트레이스 포함)\n */\nexport function formatError(error: Error): string\n{\n const lines: string[] = [];\n\n lines.push(`${error.name}: ${error.message}`);\n\n if (error.stack)\n {\n const stackLines = error.stack.split('\\n').slice(1);\n lines.push(...stackLines);\n }\n\n // Cause chain — surfaces original PG errors hidden by DrizzleQueryError\n if (error.cause instanceof Error)\n {\n lines.push(`Caused by: ${formatError(error.cause)}`);\n }\n else if (error.cause !== undefined)\n {\n lines.push(`Caused by: ${String(error.cause)}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Context 객체를 문자열로 변환\n */\nexport function formatContext(context: Record<string, unknown>): string\n{\n try\n {\n return JSON.stringify(context, null, 2);\n }\n catch (error)\n {\n return '[Context serialization failed]';\n }\n}\n\n/**\n * 콘솔용 컬러 포맷\n */\nexport function formatConsole(metadata: LogMetadata, colorize = true): string\n{\n const parts: string[] = [];\n\n // [타임스탬프]\n const timestamp = formatTimestampHuman(metadata.timestamp);\n if (colorize)\n {\n parts.push(`${COLORS.gray}[${timestamp}]${COLORS.reset}`);\n }\n else\n {\n parts.push(`[${timestamp}]`);\n }\n\n // [pid=12345]\n const pid = process.pid;\n if (colorize)\n {\n parts.push(`${COLORS.dim}[pid=${pid}]${COLORS.reset}`);\n }\n else\n {\n parts.push(`[pid=${pid}]`);\n }\n\n // [module=value]\n if (metadata.module)\n {\n if (colorize)\n {\n parts.push(`${COLORS.dim}[module=${metadata.module}]${COLORS.reset}`);\n }\n else\n {\n parts.push(`[module=${metadata.module}]`);\n }\n }\n\n // Context를 각각 [key=value] 형태로 추가\n if (metadata.context && Object.keys(metadata.context).length > 0)\n {\n Object.entries(metadata.context).forEach(([key, value]) =>\n {\n let valueStr: string;\n if (typeof value === 'string')\n {\n valueStr = value;\n }\n else if (typeof value === 'object' && value !== null)\n {\n try\n {\n valueStr = JSON.stringify(value);\n }\n catch (error)\n {\n valueStr = '[circular]';\n }\n }\n else\n {\n valueStr = String(value);\n }\n\n if (colorize)\n {\n parts.push(`${COLORS.dim}[${key}=${valueStr}]${COLORS.reset}`);\n }\n else\n {\n parts.push(`[${key}=${valueStr}]`);\n }\n });\n }\n\n // (LEVEL):\n const levelStr = metadata.level.toUpperCase();\n if (colorize)\n {\n const color = COLORS[metadata.level];\n parts.push(`${color}(${levelStr})${COLORS.reset}:`);\n }\n else\n {\n parts.push(`(${levelStr}):`);\n }\n\n // 메시지\n if (colorize)\n {\n parts.push(`${COLORS.bright}${metadata.message}${COLORS.reset}`);\n }\n else\n {\n parts.push(metadata.message);\n }\n\n let output = parts.join(' ');\n\n // 에러는 별도 줄로 추가\n if (metadata.error)\n {\n output += '\\n' + formatError(metadata.error);\n }\n\n return output;\n}\n\n/**\n * Error 객체를 cause 체인까지 포함한 직렬화 가능 객체로 변환\n */\nfunction formatErrorCauseChain(error: Error): Record<string, unknown>\n{\n const result: Record<string, unknown> = {\n name: error.name,\n message: error.message,\n stack: error.stack,\n };\n\n if (error.cause instanceof Error)\n {\n result.cause = formatErrorCauseChain(error.cause);\n }\n else if (error.cause !== undefined)\n {\n result.cause = String(error.cause);\n }\n\n return result;\n}\n\n/**\n * JSON 포맷 (파일 저장 및 전송용)\n */\nexport function formatJSON(metadata: LogMetadata): string\n{\n const obj: Record<string, unknown> = {\n timestamp: formatTimestamp(metadata.timestamp),\n level: metadata.level,\n message: metadata.message,\n };\n\n if (metadata.module)\n {\n obj.module = metadata.module;\n }\n\n if (metadata.context)\n {\n obj.context = metadata.context;\n }\n\n if (metadata.error)\n {\n obj.error = formatErrorCauseChain(metadata.error);\n }\n\n return JSON.stringify(obj);\n}\n\n/**\n * Drizzle ORM 에러에서 쿼리 정보 추출\n *\n * Drizzle ORM은 에러 메시지에 다음 형식으로 정보를 포함:\n * - \"Failed query: <QUERY>\\nparams: <PARAMS>\"\n * - \"Query: <QUERY>\"\n *\n * @param error - Error 객체\n * @returns 쿼리 정보 (query, params, table)\n */\nexport function extractQueryInfo(error: Error): {\n query?: string;\n params?: unknown;\n table?: string;\n} | null\n{\n const message = error.message;\n\n if (!message) return null;\n\n const result: {\n query?: string;\n params?: unknown;\n table?: string;\n } = {};\n\n // Extract query from \"Failed query: ...\" or \"Query: ...\"\n const queryMatch = message.match(/(?:Failed query:|Query:)\\s*([^\\n]+)/);\n if (queryMatch)\n {\n result.query = queryMatch[1].trim();\n\n // Extract table name from query (e.g., UPDATE \"table_name\" or INSERT INTO \"table_name\")\n const tableMatch = result.query.match(/(?:UPDATE|INSERT INTO|DELETE FROM|FROM)\\s+\"?([a-zA-Z_][a-zA-Z0-9_]*)\"?\\.\"?([a-zA-Z_][a-zA-Z0-9_]*)\"?|(?:UPDATE|INSERT INTO|DELETE FROM|FROM)\\s+\"?([a-zA-Z_][a-zA-Z0-9_]*)\"?/i);\n if (tableMatch)\n {\n // Schema.Table or just Table\n result.table = tableMatch[2] || tableMatch[3] || tableMatch[1];\n }\n }\n\n // Extract params from \"params: ...\"\n const paramsMatch = message.match(/params:\\s*(.+?)(?:\\n|$)/);\n if (paramsMatch)\n {\n const paramsStr = paramsMatch[1].trim();\n try\n {\n // Try to parse as comma-separated values\n result.params = paramsStr.split(',').map(p => p.trim());\n }\n catch (e)\n {\n result.params = paramsStr;\n }\n }\n\n return Object.keys(result).length > 0 ? result : null;\n}\n\n/**\n * Promise rejection의 호출 스택에서 실제 발생 위치 추출\n *\n * 스택 트레이스에서 다음 정보를 추출:\n * - 실제 에러 발생 파일 경로\n * - 라인 번호\n * - 함수명/메서드명\n * - Repository 정보 (있는 경우)\n *\n * @param error - Error 객체\n * @returns Promise context 정보\n */\nexport function extractPromiseContext(error: Error): Record<string, unknown>\n{\n const context: Record<string, unknown> = {};\n\n if (!error.stack) return context;\n\n const stackLines = error.stack.split('\\n');\n\n // Skip first line (error message) and find first meaningful stack frame\n // Ignore node_modules and internal Node.js paths\n for (let i = 1; i < stackLines.length; i++)\n {\n const line = stackLines[i].trim();\n\n // Skip node_modules and node internals\n if (line.includes('node_modules') || line.includes('node:internal')) continue;\n\n // Extract file, line number, and function name\n // Format: \"at ClassName.methodName (file.ts:line:col)\"\n // or: \"at functionName (file.ts:line:col)\"\n // or: \"at file.ts:line:col\"\n\n const match = line.match(/at\\s+(?:([a-zA-Z_$][\\w$]*(?:\\.[a-zA-Z_$][\\w$]*)*)\\s+)?\\(?([^)]+):(\\d+):(\\d+)\\)?/);\n\n if (match)\n {\n const [, functionName, filePath, lineNumber, columnNumber] = match;\n\n // Extract just the filename from the full path\n const fileNameMatch = filePath.match(/([^/\\\\]+)$/);\n const fileName = fileNameMatch ? fileNameMatch[1] : filePath;\n\n context.file = fileName;\n context.line = parseInt(lineNumber, 10);\n context.column = parseInt(columnNumber, 10);\n\n if (functionName)\n {\n // Check if it's a class method (e.g., \"ClassName.methodName\")\n const methodMatch = functionName.match(/^(.+)\\.([^.]+)$/);\n if (methodMatch)\n {\n const [, className, methodName] = methodMatch;\n\n context.class = className;\n context.method = methodName;\n\n // Check if it's a Repository\n if (className.includes('Repository'))\n {\n context.repository = className;\n }\n }\n else\n {\n context.function = functionName;\n }\n }\n\n // Found first relevant frame, stop here\n break;\n }\n }\n\n return context;\n}\n\n/**\n * Unhandled rejection 에러를 상세하게 포맷팅\n *\n * Promise context와 DB 쿼리 정보를 자동으로 추출하여\n * 에러 발생 위치와 원인을 명확하게 파악할 수 있도록 함\n *\n * @param reason - Rejection 원인 (Error 또는 기타)\n * @param promise - Promise 객체\n * @returns 상세 context 정보\n */\nexport function formatUnhandledRejection(reason: unknown, promise: Promise<unknown>): {\n error: Error;\n context: Record<string, unknown>;\n}\n{\n // Convert reason to Error if not already\n let error: Error;\n if (reason instanceof Error)\n {\n error = reason;\n }\n else if (typeof reason === 'string')\n {\n error = new Error(reason);\n }\n else\n {\n error = new Error(JSON.stringify(reason));\n }\n\n const context: Record<string, unknown> = {\n promise: String(promise),\n };\n\n // Extract promise context (file, line, function, etc.)\n const promiseContext = extractPromiseContext(error);\n if (Object.keys(promiseContext).length > 0)\n {\n context.promiseContext = promiseContext;\n }\n\n // Extract DB query info if available\n const queryInfo = extractQueryInfo(error);\n if (queryInfo)\n {\n context.queryInfo = queryInfo;\n }\n\n return { error, context };\n}\n","/**\n * Logger Class\n *\n * Central logging class with multiple transports, child loggers, and sensitive data masking.\n */\n\nimport { format } from 'node:util';\nimport type { LogLevel, LogMetadata, LoggerConfig, Transport } from './types';\nimport { LOG_LEVEL_PRIORITY } from './types';\nimport { maskSensitiveData } from './formatters';\n\nconst FORMAT_PATTERN = /%[sdifjoOc%]/;\n\n/**\n * Logger class\n */\nexport class Logger\n{\n private readonly config: LoggerConfig;\n private readonly module?: string;\n\n constructor(config: LoggerConfig)\n {\n this.config = config;\n this.module = config.module;\n }\n\n /**\n * Convert unknown error to Error object\n */\n private toError(error: unknown): Error\n {\n if (error instanceof Error) return error;\n if (typeof error === 'string') return new Error(error);\n if (typeof error === 'object' && error !== null)\n {\n return new Error(JSON.stringify(error));\n }\n\n return new Error(String(error));\n }\n\n /**\n * Check if value is a context object (not an error)\n */\n private isContext(value: unknown): value is Record<string, unknown>\n {\n if (typeof value !== 'object' || value === null) return false;\n if (value instanceof Error) return false;\n\n // Use stack as the primary indicator for error detection\n // - Almost all Error objects have stack (auto-generated by JS engines)\n // - Regular objects rarely use 'stack' as a property name\n // - This avoids false positives for objects with 'name' or 'message' properties\n const hasStack = 'stack' in value && typeof (value as any).stack === 'string';\n\n if (hasStack)\n {\n return false; // Likely an error object\n }\n\n // No stack -> treat as context\n // This allows {name: \"...\", method: \"...\"} to be treated as context\n return true;\n }\n\n /**\n * Get current log level\n */\n get level(): LogLevel\n {\n return this.config.level;\n }\n\n /**\n * Create child logger (per module)\n */\n child(module: string): Logger\n {\n return new Logger({\n ...this.config,\n module,\n });\n }\n\n /**\n * Common log method with error/context detection\n */\n private logWithLevel(\n level: LogLevel,\n message: string,\n errorOrContext?: Error | unknown | Record<string, unknown>,\n context?: Record<string, unknown>,\n ): void\n {\n // printf-style format string: logger.info('url: %s', value)\n if (errorOrContext !== undefined && FORMAT_PATTERN.test(message))\n {\n this.log(level, format(message, errorOrContext), undefined, context);\n\n return;\n }\n\n if (errorOrContext instanceof Error)\n {\n this.log(level, message, errorOrContext, context);\n }\n else if (errorOrContext !== undefined && typeof errorOrContext === 'object' && !this.isContext(errorOrContext))\n {\n this.log(level, message, this.toError(errorOrContext), context);\n }\n else if (typeof errorOrContext === 'string' || typeof errorOrContext === 'number' || typeof errorOrContext === 'boolean')\n {\n this.log(level, message, this.toError(errorOrContext), context);\n }\n else\n {\n this.log(level, message, undefined, errorOrContext as Record<string, unknown>);\n }\n }\n\n /**\n * Debug log\n */\n debug(message: string, context?: Record<string, unknown>): void;\n debug(message: string, formatArg: string | number | boolean): void;\n debug(message: string, error: Error | unknown, context?: Record<string, unknown>): void;\n debug(message: string, errorOrContext?: unknown, context?: Record<string, unknown>): void\n {\n this.logWithLevel('debug', message, errorOrContext, context);\n }\n\n /**\n * Info log\n */\n info(message: string, context?: Record<string, unknown>): void;\n info(message: string, formatArg: string | number | boolean): void;\n info(message: string, error: Error | unknown, context?: Record<string, unknown>): void;\n info(message: string, errorOrContext?: unknown, context?: Record<string, unknown>): void\n {\n this.logWithLevel('info', message, errorOrContext, context);\n }\n\n /**\n * Warn log\n */\n warn(message: string, context?: Record<string, unknown>): void;\n warn(message: string, formatArg: string | number | boolean): void;\n warn(message: string, error: Error | unknown, context?: Record<string, unknown>): void;\n warn(message: string, errorOrContext?: unknown, context?: Record<string, unknown>): void\n {\n this.logWithLevel('warn', message, errorOrContext, context);\n }\n\n /**\n * Error log\n */\n error(message: string, context?: Record<string, unknown>): void;\n error(message: string, formatArg: string | number | boolean): void;\n error(message: string, error: Error | unknown, context?: Record<string, unknown>): void;\n error(message: string, errorOrContext?: unknown, context?: Record<string, unknown>): void\n {\n this.logWithLevel('error', message, errorOrContext, context);\n }\n\n /**\n * Fatal log\n */\n fatal(message: string, context?: Record<string, unknown>): void;\n fatal(message: string, formatArg: string | number | boolean): void;\n fatal(message: string, error: Error | unknown, context?: Record<string, unknown>): void;\n fatal(message: string, errorOrContext?: unknown, context?: Record<string, unknown>): void\n {\n this.logWithLevel('fatal', message, errorOrContext, context);\n }\n\n /**\n * Log processing (internal)\n */\n private log(level: LogLevel, message: string, error?: Error, context?: Record<string, unknown>): void\n {\n // Early return if log level is below configured level\n // This prevents unnecessary metadata creation and processing\n if (LOG_LEVEL_PRIORITY[level] < LOG_LEVEL_PRIORITY[this.config.level])\n {\n return;\n }\n\n const metadata: LogMetadata = {\n timestamp: new Date(),\n level,\n message,\n module: this.module,\n error,\n // Mask sensitive information in context to prevent credential leaks\n context: context ? maskSensitiveData(context) as Record<string, unknown> : undefined,\n };\n\n // Pass to all enabled Transports\n this.processTransports(metadata);\n }\n\n /**\n * Process Transports\n */\n private processTransports(metadata: LogMetadata): void\n {\n const promises = this.config.transports\n .filter(transport => transport.enabled)\n .map(transport => this.safeTransportLog(transport, metadata));\n\n // Async processing to prevent Transport errors from blocking logs\n Promise.all(promises).catch(error =>\n {\n // Use stderr directly to avoid circular logging\n const errorMessage = error instanceof Error ? error.message : String(error);\n process.stderr.write(`[Logger] Transport error: ${errorMessage}\\n`);\n });\n }\n\n /**\n * Transport log (error-safe)\n */\n private async safeTransportLog(transport: Transport, metadata: LogMetadata): Promise<void>\n {\n try\n {\n await transport.log(metadata);\n }\n catch (error)\n {\n // Use stderr directly to avoid circular logging\n const errorMessage = error instanceof Error ? error.message : String(error);\n process.stderr.write(`[Logger] Transport \"${transport.name}\" failed: ${errorMessage}\\n`);\n }\n }\n\n /**\n * Close all Transports\n */\n async close(): Promise<void>\n {\n const closePromises = this.config.transports\n .filter(transport => transport.close)\n .map(transport => transport.close!());\n\n await Promise.all(closePromises);\n }\n}\n","/**\n * Console Transport\n *\n * 콘솔 출력 Transport\n *\n * ✅ 구현 완료:\n * - 콘솔 출력 (stdout/stderr)\n * - 컬러 출력 지원\n * - 로그 레벨별 스트림 분리 (warn/error/fatal → stderr)\n *\n * 🔗 관련 파일:\n * - src/logger/types.ts (Transport 인터페이스)\n * - src/logger/formatters.ts (포맷터)\n * - src/logger/config.ts (설정)\n */\n\nimport type { Transport, LogMetadata, LogLevel, ConsoleTransportConfig } from '../types';\nimport { LOG_LEVEL_PRIORITY } from '../types';\nimport { formatConsole } from '../formatters';\n\n/**\n * Console Transport\n */\nexport class ConsoleTransport implements Transport\n{\n public readonly name = 'console';\n public readonly level: LogLevel;\n public readonly enabled: boolean;\n private readonly colorize: boolean;\n\n constructor(config: ConsoleTransportConfig)\n {\n this.level = config.level;\n this.enabled = config.enabled;\n this.colorize = config.colorize ?? true;\n }\n\n async log(metadata: LogMetadata): Promise<void>\n {\n // Enabled 상태 체크\n if (!this.enabled)\n {\n return;\n }\n\n // 로그 레벨 체크\n if (LOG_LEVEL_PRIORITY[metadata.level] < LOG_LEVEL_PRIORITY[this.level])\n {\n return;\n }\n\n // 포맷팅\n const message = formatConsole(metadata, this.colorize);\n\n // warn/error/fatal은 stderr로, 나머지는 stdout으로\n if (metadata.level === 'warn' || metadata.level === 'error' || metadata.level === 'fatal')\n {\n console.error(message);\n }\n else\n {\n console.log(message);\n }\n }\n}\n","/**\n * Logger Configuration\n *\n * Environment-based logger configuration for the console transport.\n *\n * NODE_ENV is deliberately NOT checked here. The logger is a module-level\n * singleton, so anything this module runs happens at import time — and the env\n * loader imports the logger, which puts every logger-side check strictly before\n * any .env file is read. A NODE_ENV warning raised from here therefore fires\n * even when .env.server sets NODE_ENV, which is what issue #136 reported. The\n * check now lives in `loadEnv` (src/env/loader.ts), where the environment is\n * settled.\n */\n\nimport type {\n ConsoleTransportConfig,\n} from './types';\n\n/**\n * Console Transport configuration\n */\nexport function getConsoleConfig(): ConsoleTransportConfig\n{\n const isProduction = process.env.NODE_ENV === 'production';\n\n return {\n level: 'debug',\n enabled: true,\n colorize: !isProduction, // Dev: colored output, Production: plain text\n };\n}\n","/**\n * Logger Factory\n *\n * Creates and initializes the logger instance with configured transports\n */\n\nimport { Logger } from './logger';\nimport { ConsoleTransport } from './transports/console';\nimport { getConsoleConfig } from './config';\nimport type { LogLevel, Transport } from './types';\nimport { LOG_LEVEL_PRIORITY } from './types';\n\n/**\n * Initialize transports based on environment and configuration\n */\nfunction initializeTransports(): Transport[]\n{\n const transports: Transport[] = [];\n\n // Console Transport (always enabled)\n const consoleConfig = getConsoleConfig();\n transports.push(new ConsoleTransport(consoleConfig));\n\n // Future: Add more transports (Slack, Email, etc.)\n // if (config.slack?.enabled) {\n // transports.push(new SlackTransport(config.slack));\n // }\n\n return transports;\n}\n\n/**\n * Get validated log level from environment variables\n */\nfunction getLogLevel(): LogLevel\n{\n const envLevel = process.env.SPFN_LOG_LEVEL\n || process.env.NEXT_PUBLIC_SPFN_LOG_LEVEL\n || 'info';\n\n if (envLevel in LOG_LEVEL_PRIORITY)\n {\n return envLevel as LogLevel;\n }\n\n process.stderr.write(\n `[Logger] Invalid log level \"${envLevel}\", defaulting to \"info\"\\n`,\n );\n\n return 'info';\n}\n\n/**\n * Initialize logger\n *\n * Runs at import time, so it must not inspect anything a .env file can supply —\n * see the note in ./config.\n */\nfunction initializeLogger(): Logger\n{\n return new Logger({\n level: getLogLevel(),\n transports: initializeTransports(),\n });\n}\n\n/**\n * Singleton Logger instance\n */\nexport const logger: Logger = initializeLogger();\n"]}
@@ -1,5 +1,5 @@
1
1
  import { Context, Next, MiddlewareHandler } from 'hono';
2
- import { N as NamedMiddlewareFactory, a as NamedMiddleware } from '../define-middleware-DfDP39Nq.js';
2
+ import { N as NamedMiddlewareFactory, a as NamedMiddleware } from '../define-middleware-CVKgqo8S.js';
3
3
  import { Redis, Cluster } from 'ioredis';
4
4
 
5
5
  /**
@@ -106,7 +106,11 @@ interface RequestLoggerOptions {
106
106
  *
107
107
  * Supports exact match and prefix match (e.g., '/health' excludes '/health/db').
108
108
  *
109
- * @default ['/health', '/ping', '/favicon.ico']
109
+ * `/_core/health` is where the built-in health endpoint always answers, so a
110
+ * readiness probe usually points there rather than at `/health`. Leaving it
111
+ * out of the default fills the log with one line per probe interval.
112
+ *
113
+ * @default ['/_core/health', '/health', '/ping', '/favicon.ico']
110
114
  *
111
115
  * @example
112
116
  * ```typescript