@spfn/core 0.2.0-beta.6 → 0.2.0-beta.64

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 (73) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +181 -1281
  3. package/dist/authz/index.d.ts +34 -0
  4. package/dist/authz/index.js +415 -0
  5. package/dist/authz/index.js.map +1 -0
  6. package/dist/{boss-DI1r4kTS.d.ts → boss-gXhgctn6.d.ts} +40 -0
  7. package/dist/cache/index.js +42 -30
  8. package/dist/cache/index.js.map +1 -1
  9. package/dist/codegen/index.d.ts +55 -8
  10. package/dist/codegen/index.js +183 -7
  11. package/dist/codegen/index.js.map +1 -1
  12. package/dist/config/index.d.ts +585 -6
  13. package/dist/config/index.js +116 -5
  14. package/dist/config/index.js.map +1 -1
  15. package/dist/db/index.d.ts +270 -4
  16. package/dist/db/index.js +404 -60
  17. package/dist/db/index.js.map +1 -1
  18. package/dist/define-middleware-DuXD8Hvu.d.ts +167 -0
  19. package/dist/env/index.d.ts +26 -2
  20. package/dist/env/index.js +15 -5
  21. package/dist/env/index.js.map +1 -1
  22. package/dist/env/loader.d.ts +26 -19
  23. package/dist/env/loader.js +32 -25
  24. package/dist/env/loader.js.map +1 -1
  25. package/dist/errors/index.d.ts +10 -0
  26. package/dist/errors/index.js +20 -2
  27. package/dist/errors/index.js.map +1 -1
  28. package/dist/event/index.d.ts +33 -3
  29. package/dist/event/index.js +24 -3
  30. package/dist/event/index.js.map +1 -1
  31. package/dist/event/sse/client.d.ts +42 -3
  32. package/dist/event/sse/client.js +128 -45
  33. package/dist/event/sse/client.js.map +1 -1
  34. package/dist/event/sse/index.d.ts +12 -5
  35. package/dist/event/sse/index.js +271 -32
  36. package/dist/event/sse/index.js.map +1 -1
  37. package/dist/event/ws/client.d.ts +59 -0
  38. package/dist/event/ws/client.js +273 -0
  39. package/dist/event/ws/client.js.map +1 -0
  40. package/dist/event/ws/index.d.ts +94 -0
  41. package/dist/event/ws/index.js +272 -0
  42. package/dist/event/ws/index.js.map +1 -0
  43. package/dist/job/index.d.ts +2 -2
  44. package/dist/job/index.js +155 -42
  45. package/dist/job/index.js.map +1 -1
  46. package/dist/logger/index.d.ts +5 -0
  47. package/dist/logger/index.js +14 -0
  48. package/dist/logger/index.js.map +1 -1
  49. package/dist/middleware/index.d.ts +243 -2
  50. package/dist/middleware/index.js +1323 -13
  51. package/dist/middleware/index.js.map +1 -1
  52. package/dist/nextjs/index.d.ts +2 -2
  53. package/dist/nextjs/index.js +77 -31
  54. package/dist/nextjs/index.js.map +1 -1
  55. package/dist/nextjs/server.d.ts +53 -23
  56. package/dist/nextjs/server.js +197 -66
  57. package/dist/nextjs/server.js.map +1 -1
  58. package/dist/route/index.d.ts +138 -146
  59. package/dist/route/index.js +238 -22
  60. package/dist/route/index.js.map +1 -1
  61. package/dist/security/index.d.ts +83 -0
  62. package/dist/security/index.js +173 -0
  63. package/dist/security/index.js.map +1 -0
  64. package/dist/server/index.d.ts +450 -17
  65. package/dist/server/index.js +1756 -277
  66. package/dist/server/index.js.map +1 -1
  67. package/dist/{router-Di7ENoah.d.ts → token-manager-jKD_EsSE.d.ts} +121 -1
  68. package/dist/{types-D_N_U-Py.d.ts → types-7Mhoxnnt.d.ts} +21 -1
  69. package/dist/types-BFB72jbM.d.ts +282 -0
  70. package/dist/types-DVjf37yO.d.ts +205 -0
  71. package/docs/file-upload.md +717 -0
  72. package/package.json +235 -208
  73. package/dist/types-B-e_f2dQ.d.ts +0 -121
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/event/ws/handler.ts","../../../src/event/ws/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * WebSocket Handler\n *\n * Attaches a WebSocket server to an existing Node.js http.Server.\n * Handles authentication, event subscription, and client message routing.\n */\n\nimport type { Server } from 'node:http';\nimport type { EventDef } from '../types';\nimport type {\n WSRouterDef,\n WSHandlerConfig,\n WSHandlerAuthConfig,\n WSMessageHandlers,\n WSRawConnection,\n} from './types';\nimport type { SSETokenManager } from '../sse/token-manager';\nimport { logger } from '@spfn/core/logger';\n\nconst wsLogger = logger.child('@spfn/core:ws');\n\n/**\n * Decide whether a WebSocket upgrade's Origin is permitted.\n *\n * - no allow-list configured → allowed (check disabled)\n * - no Origin header (native/non-browser client) → allowed\n * - Origin present → must be in the allow-list\n */\nexport function isOriginAllowed(\n origin: string | undefined,\n allowList: Set<string> | null,\n): boolean\n{\n if (!allowList)\n {\n return true;\n }\n\n if (!origin)\n {\n return true;\n }\n\n return allowList.has(origin);\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Attach a WebSocket server to a Node.js http.Server\n *\n * @returns cleanup function that closes the WebSocket server\n */\nexport async function attachWSHandler<\n TEvents extends Record<string, EventDef<any>>,\n TMessages extends WSMessageHandlers,\n>(\n server: Server,\n router: WSRouterDef<TEvents, TMessages>,\n config: WSHandlerConfig & { path?: string } = {},\n tokenManager?: SSETokenManager,\n): Promise<() => Promise<void>>\n{\n const WebSocketServer = await loadWSServer();\n\n const {\n pingInterval = 30000,\n path = '/ws',\n maxPayload = 1_048_576,\n maxBufferedBytes = 1_048_576,\n maxConnections = 10_000,\n maxConnectionsPerSubject = 0,\n allowedOrigins,\n auth: authConfig,\n } = config;\n\n const originAllowList = allowedOrigins && allowedOrigins.length > 0\n ? new Set(allowedOrigins)\n : null;\n\n if (authConfig?.enabled && !tokenManager)\n {\n throw new Error(\n 'WebSocket auth.enabled=true requires a tokenManager. ' +\n 'Pass tokenManager or use .websockets(router, { auth: { enabled: true } }) via startServer.',\n );\n }\n\n const wss = new WebSocketServer({ server, path, maxPayload });\n\n // Track live connections for graceful shutdown\n const clients = new Set<any>();\n // Live connection count per authenticated subject (for the per-subject cap)\n const subjectCounts = new Map<string, number>();\n\n wss.on('connection', (ws: any, req: any) =>\n {\n // Origin allow-list (cross-site WebSocket hijacking protection). Browsers\n // always send Origin on the upgrade; reject one that isn't allowed before\n // doing any work. A missing Origin (native/non-browser client, which can't\n // be CSRF'd into using ambient cookies) passes through.\n if (!isOriginAllowed(req.headers?.origin, originAllowList))\n {\n wsLogger.warn('WebSocket rejected: disallowed origin', { origin: req.headers?.origin });\n ws.close(1008, 'Origin not allowed');\n\n return;\n }\n\n // Global connection cap — reject before doing any work\n if (clients.size >= maxConnections)\n {\n ws.close(1013, 'Server at capacity');\n\n return;\n }\n\n clients.add(ws);\n ws.on('close', () => clients.delete(ws));\n handleConnection(ws, req, router, authConfig, tokenManager, {\n pingInterval,\n maxBufferedBytes,\n maxConnectionsPerSubject,\n subjectCounts,\n })\n .catch((err: Error) =>\n {\n wsLogger.error('WebSocket connection handler error', err);\n if (ws.readyState === 1) ws.close(1011, 'Internal server error');\n });\n });\n\n wss.on('error', (err: Error) =>\n {\n wsLogger.error('WebSocket server error', err);\n });\n\n wsLogger.info(`✓ WebSocket endpoint registered at ${path}`, {\n events: router.eventNames,\n auth: !!authConfig?.enabled,\n });\n\n return () => new Promise<void>((resolve, reject) =>\n {\n // Close all existing connections with 1001 Going Away\n for (const client of clients)\n {\n client.close(1001, 'Server shutting down');\n }\n clients.clear();\n\n wss.close((err?: Error) =>\n {\n if (err) reject(err);\n else resolve();\n });\n });\n}\n\n// ============================================================================\n// Connection Handler\n// ============================================================================\n\ninterface ConnectionOptions\n{\n pingInterval: number;\n maxBufferedBytes: number;\n maxConnectionsPerSubject: number;\n subjectCounts: Map<string, number>;\n}\n\nasync function handleConnection(\n ws: any,\n req: any,\n router: WSRouterDef<any, any>,\n authConfig: WSHandlerAuthConfig | undefined,\n tokenManager: SSETokenManager | undefined,\n opts: ConnectionOptions,\n): Promise<void>\n{\n const { pingInterval, maxBufferedBytes, maxConnectionsPerSubject, subjectCounts } = opts;\n // Register close handler before any await — ensures we never miss the event even during auth\n let pingTimer: ReturnType<typeof setInterval> | undefined;\n let connectionUnsubscribes: (() => void)[] = [];\n let subscribedEvents: string[] = [];\n ws.on('close', () =>\n {\n clearInterval(pingTimer);\n connectionUnsubscribes.forEach(fn => fn());\n if (subscribedEvents.length > 0)\n wsLogger.info('WebSocket connection closed', { events: subscribedEvents });\n });\n\n const url = parseURL(req);\n if (!url)\n {\n ws.close(1002, 'Invalid request URL');\n\n return;\n }\n\n // ── 1. Authenticate ──\n const subject = await resolveSubject(url, authConfig?.enabled ? tokenManager : undefined);\n if (subject === false)\n {\n ws.close(4001, 'Missing token');\n\n return;\n }\n if (subject === null)\n {\n ws.close(4001, 'Invalid or expired token');\n\n return;\n }\n\n // ── 2. Resolve subscribed events ──\n const requestedEvents = parseRequestedEvents(url, router.eventNames as string[]);\n if (requestedEvents.length === 0)\n {\n ws.close(4000, 'No valid event names specified');\n\n return;\n }\n\n // ── 3. Authorize ──\n const allowedEvents = await resolveAllowedEvents(subject, requestedEvents, authConfig);\n if (allowedEvents === null)\n {\n ws.close(4003, 'Not authorized for any requested events');\n\n return;\n }\n\n // ── Per-subject connection cap (authenticated subjects only) ──\n if (maxConnectionsPerSubject > 0 && typeof subject === 'string')\n {\n const current = subjectCounts.get(subject) ?? 0;\n if (current >= maxConnectionsPerSubject)\n {\n ws.close(1013, 'Too many connections for this subject');\n\n return;\n }\n\n subjectCounts.set(subject, current + 1);\n ws.on('close', () =>\n {\n const remaining = (subjectCounts.get(subject) ?? 1) - 1;\n if (remaining <= 0) subjectCounts.delete(subject);\n else subjectCounts.set(subject, remaining);\n });\n }\n\n subscribedEvents = allowedEvents;\n wsLogger.info('WebSocket connection established', {\n events: allowedEvents,\n subject: subject ?? undefined,\n });\n\n // ── 4. Build connection wrapper ──\n const connection = createConnection(ws, maxBufferedBytes);\n\n // ── 5. Subscribe to server-push events ──\n connectionUnsubscribes = subscribeEvents(ws, router, allowedEvents, subject, authConfig, maxBufferedBytes);\n\n // If socket closed during auth awaits, clean up and bail\n if (ws.readyState !== 1)\n {\n connectionUnsubscribes.forEach(fn => fn());\n connectionUnsubscribes = [];\n\n return;\n }\n\n // ── 6. Handle incoming messages ──\n ws.on('message', (data: Buffer | string) =>\n {\n onClientMessage(data, router, connection, subject)\n .catch((err: Error) => wsLogger.error('Unhandled message error', err));\n });\n\n // ── 7. Keep-alive ping with liveness (pong) tracking ──\n // Without reaping un-ponged sockets, a half-open connection (sleeping device,\n // NAT drop with no FIN) lingers with its subscriptions, timer, and buffers.\n if (pingInterval > 0)\n {\n ws.isAlive = true;\n ws.on('pong', () =>\n {\n ws.isAlive = true;\n });\n\n pingTimer = setInterval(() =>\n {\n if (ws.readyState !== 1) return;\n\n if (ws.isAlive === false)\n {\n // No pong since the previous tick — drop the dead/half-open socket\n ws.terminate();\n\n return;\n }\n\n ws.isAlive = false;\n ws.ping();\n }, pingInterval);\n }\n\n // ── 9. Send connected ack ──\n connection.send('__connected', {\n subscribedEvents: allowedEvents,\n timestamp: Date.now(),\n });\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction parseURL(req: any): URL | null\n{\n try\n {\n return new URL(req.url ?? '/', 'ws://localhost');\n }\n catch\n {\n return null;\n }\n}\n\n/**\n * Resolve subject from token\n * - undefined: no auth required\n * - false: token param missing (when required)\n * - null: token invalid/expired\n * - string: authenticated subject\n */\nasync function resolveSubject(\n url: URL,\n tokenManager?: SSETokenManager,\n): Promise<string | undefined | false | null>\n{\n if (!tokenManager)\n {\n return undefined;\n }\n\n const token = url.searchParams.get('token');\n if (!token)\n {\n return false;\n }\n\n return await tokenManager.verify(token);\n}\n\nfunction parseRequestedEvents(url: URL, validEventNames: string[]): string[]\n{\n const eventsParam = url.searchParams.get('events');\n if (!eventsParam)\n {\n return [];\n }\n\n return eventsParam\n .split(',')\n .map(e => e.trim())\n .filter(e => validEventNames.includes(e));\n}\n\nasync function resolveAllowedEvents(\n subject: string | undefined,\n requestedEvents: string[],\n authConfig?: WSHandlerAuthConfig,\n): Promise<string[] | null>\n{\n if (!subject || !authConfig?.authorize)\n {\n return requestedEvents;\n }\n\n const allowed = await authConfig.authorize(subject, requestedEvents);\n\n return allowed.length === 0 ? null : allowed;\n}\n\n/**\n * Send a JSON frame with backpressure protection. If the socket's outbound\n * buffer is already past the cap, the consumer is too slow — close the\n * connection (1013) instead of buffering more and risking OOM. The client\n * reconnects and re-subscribes.\n */\nexport function safeSend(ws: any, frame: unknown, maxBufferedBytes: number): void\n{\n if (ws.readyState !== 1) return;\n\n if (ws.bufferedAmount > maxBufferedBytes)\n {\n ws.close(1013, 'Send buffer overflow');\n\n return;\n }\n\n try\n {\n ws.send(JSON.stringify(frame));\n }\n catch\n {\n // Socket closed between the readyState check and send — ignore\n }\n}\n\nfunction createConnection(ws: any, maxBufferedBytes: number): WSRawConnection\n{\n return {\n send: (type, payload) => safeSend(ws, { type, data: payload }, maxBufferedBytes),\n close: (code, reason) => ws.close(code, reason),\n };\n}\n\nfunction subscribeEvents(\n ws: any,\n router: WSRouterDef<any, any>,\n allowedEvents: string[],\n subject: string | undefined,\n authConfig: WSHandlerAuthConfig | undefined,\n maxBufferedBytes: number,\n): (() => void)[]\n{\n const unsubscribes: (() => void)[] = [];\n\n for (const eventName of allowedEvents)\n {\n const eventDef = router.events[eventName];\n if (!eventDef) continue;\n\n const unsubscribe = eventDef.subscribe((payload: unknown) =>\n {\n if (ws.readyState !== 1) return;\n\n if (subject && authConfig?.filter?.[eventName])\n {\n if (!authConfig.filter[eventName](subject, payload)) return;\n }\n\n safeSend(ws, { type: eventName, data: payload }, maxBufferedBytes);\n });\n\n unsubscribes.push(unsubscribe);\n }\n\n return unsubscribes;\n}\n\nasync function onClientMessage(\n data: Buffer | string,\n router: WSRouterDef<any, any>,\n connection: WSRawConnection,\n subject: string | undefined,\n): Promise<void>\n{\n let message: { type?: string; data?: unknown };\n\n try\n {\n message = JSON.parse(data.toString());\n }\n catch\n {\n return;\n }\n\n const { type, data: payload } = message;\n if (!type) return;\n\n const handler = router.messages[type];\n if (!handler) return;\n\n try\n {\n await handler({ payload, subject, ws: connection });\n }\n catch (err)\n {\n wsLogger.error(`WebSocket message handler error: ${type}`, err as Error);\n }\n}\n\n// ============================================================================\n// Dynamic import for optional 'ws' dependency\n// ============================================================================\n\n/**\n * Resolve the WebSocketServer constructor from the 'ws' module object.\n *\n * ws is a dual package, so the module shape depends on the loader path:\n * - ESM (exports.import → wrapper.mjs): WebSocketServer is a named export;\n * the default export is the bare WebSocket class with no properties on it.\n * - CJS (require interop): module.exports = WebSocket with\n * WebSocket.WebSocketServer attached, surfaced under .default.\n * Named exports must win — checking .default first crashes on the ESM path.\n */\nexport function resolveWSServerCtor(mod: any): any\n{\n return mod.WebSocketServer ?? mod.Server\n ?? mod.default?.WebSocketServer ?? mod.default?.Server;\n}\n\nasync function loadWSServer(): Promise<any>\n{\n let mod: any;\n\n try\n {\n mod = await import('ws');\n }\n catch\n {\n throw new Error(\n '@spfn/core WebSocket support requires the \"ws\" package.\\n' +\n 'Install it with: pnpm add ws',\n );\n }\n\n const WSS = resolveWSServerCtor(mod);\n\n if (typeof WSS !== 'function')\n {\n throw new Error(\n 'WebSocketServer not found in ws module. ' +\n 'Ensure ws@^8 is installed: pnpm add ws',\n );\n }\n\n return WSS;\n}\n","/**\n * WebSocket Module\n *\n * Type-safe WebSocket server with event-based pub/sub and bidirectional messaging.\n *\n * @example Server setup\n * ```typescript\n * // src/server/ws.ts\n * import { defineWSRouter } from '@spfn/core/event/ws';\n * import { defineEvent } from '@spfn/core/event';\n * import { Type } from '@sinclair/typebox';\n *\n * const userUpdated = defineEvent('user.updated', Type.Object({ userId: Type.String() }));\n * const notification = defineEvent('notification', Type.Object({ message: Type.String() }));\n *\n * export const wsRouter = defineWSRouter({\n * events: { userUpdated, notification },\n * messages: {\n * ping: ({ ws }) => ws.send('pong', {}),\n * },\n * });\n *\n * export type WSRouter = typeof wsRouter;\n *\n * // server.config.ts\n * defineServerConfig()\n * .websockets(wsRouter)\n * .build();\n * ```\n *\n * @example Client usage\n * ```typescript\n * import { createWSClient } from '@spfn/core/event/ws/client';\n * import type { WSRouter } from '@/server/ws';\n *\n * const client = createWSClient<WSRouter>();\n *\n * client.subscribe({\n * events: ['userUpdated', 'notification'],\n * handlers: {\n * userUpdated: ({ userId }) => console.log(userId),\n * notification: ({ message }) => console.log(message),\n * },\n * });\n *\n * client.send('ping', {});\n * ```\n */\n\nimport type { EventDef } from '../types';\nimport type { WSRouterDef, WSMessageHandlers } from './types';\n\nexport { attachWSHandler } from './handler';\nexport type {\n WSRouterDef,\n WSHandlerConfig,\n WSAuthConfig,\n WSHandlerAuthConfig,\n WSMessageContext,\n WSMessageHandlerFn,\n WSMessageHandlers,\n WSRawConnection,\n WSClientConfig,\n WSConnectionState,\n WSEventHandlers,\n WSSubscribeOptions,\n WSUnsubscribe,\n} from './types';\n\n/**\n * Define a WebSocket router\n *\n * Combines server→client event push with client→server message handlers.\n *\n * @example\n * ```typescript\n * export const wsRouter = defineWSRouter({\n * events: { userUpdated, notification },\n * messages: {\n * ping: ({ ws }) => ws.send('pong', {}),\n * 'chat.send': ({ payload, subject }) => handleChat(payload, subject),\n * },\n * });\n * ```\n */\nexport function defineWSRouter<\n TEvents extends Record<string, EventDef<any>>,\n TMessages extends WSMessageHandlers = WSMessageHandlers,\n>(def: {\n events: TEvents;\n messages?: TMessages;\n}): WSRouterDef<TEvents, TMessages>\n{\n return {\n events: def.events,\n eventNames: Object.keys(def.events) as (keyof TEvents)[],\n messages: (def.messages ?? {}) as TMessages,\n _types: {} as WSRouterDef<TEvents, TMessages>['_types'],\n };\n}\n"]}
@@ -1,5 +1,5 @@
1
- import { a as JobOptions, C as CompensateHandler, b as JobHandler, c as JobDef, d as JobRouterEntry, J as JobRouter } from '../boss-DI1r4kTS.js';
2
- export { k as BossConfig, B as BossOptions, I as InferJobInput, f as InferJobOutput, e as JobSendOptions, g as getBoss, i as initBoss, h as isBossRunning, j as shouldClearOnStart, s as stopBoss } from '../boss-DI1r4kTS.js';
1
+ import { a as JobOptions, C as CompensateHandler, b as JobHandler, c as JobDef, d as JobRouterEntry, J as JobRouter } from '../boss-gXhgctn6.js';
2
+ export { k as BossConfig, B as BossOptions, I as InferJobInput, f as InferJobOutput, e as JobSendOptions, g as getBoss, i as initBoss, h as isBossRunning, j as shouldClearOnStart, s as stopBoss } from '../boss-gXhgctn6.js';
3
3
  import * as _sinclair_typebox from '@sinclair/typebox';
4
4
  import { Static } from '@sinclair/typebox';
5
5
  import { EventDef, InferEventPayload } from '@spfn/core/event';
package/dist/job/index.js CHANGED
@@ -1,57 +1,97 @@
1
1
  import PgBoss from 'pg-boss';
2
2
  import { logger } from '@spfn/core/logger';
3
+ import { env } from '@spfn/core/config';
3
4
 
4
5
  // src/job/boss.ts
5
6
  var jobLogger = logger.child("@spfn/core:job");
6
- var bossInstance = null;
7
- var bossConfig = null;
7
+ function requiresSSLWithoutVerification(connectionString) {
8
+ try {
9
+ const url = new URL(connectionString);
10
+ const sslmode = url.searchParams.get("sslmode");
11
+ return sslmode === "require" || sslmode === "prefer";
12
+ } catch {
13
+ return false;
14
+ }
15
+ }
16
+ function stripSslModeFromUrl(connectionString) {
17
+ const url = new URL(connectionString);
18
+ url.searchParams.delete("sslmode");
19
+ return url.toString();
20
+ }
21
+ var BOSS_KEY = Symbol.for("spfn:boss-instance");
22
+ var CONFIG_KEY = Symbol.for("spfn:boss-config");
23
+ var g = globalThis;
24
+ function getBossInstance() {
25
+ return g[BOSS_KEY] ?? null;
26
+ }
27
+ function setBossInstance(instance) {
28
+ g[BOSS_KEY] = instance;
29
+ }
30
+ function getBossConfig() {
31
+ return g[CONFIG_KEY] ?? null;
32
+ }
33
+ function setBossConfig(config) {
34
+ g[CONFIG_KEY] = config;
35
+ }
8
36
  async function initBoss(options) {
9
- if (bossInstance) {
37
+ const existing = getBossInstance();
38
+ if (existing) {
10
39
  jobLogger.warn("pg-boss already initialized, returning existing instance");
11
- return bossInstance;
40
+ return existing;
12
41
  }
13
42
  jobLogger.info("Initializing pg-boss...");
14
- bossConfig = options;
43
+ setBossConfig(options);
44
+ const needsSSL = requiresSSLWithoutVerification(options.connectionString);
15
45
  const pgBossOptions = {
16
- connectionString: options.connectionString,
46
+ // pg 드라이버가 URL의 sslmode=require를 verify-full로 해석해서
47
+ // ssl 옵션을 무시하므로, URL에서 sslmode를 빼고 ssl 객체만 전달
48
+ connectionString: needsSSL ? stripSslModeFromUrl(options.connectionString) : options.connectionString,
17
49
  schema: options.schema ?? "spfn_queue",
18
50
  maintenanceIntervalSeconds: options.maintenanceIntervalSeconds ?? 120
19
51
  };
52
+ if (needsSSL) {
53
+ pgBossOptions.ssl = { rejectUnauthorized: false };
54
+ }
20
55
  if (options.monitorIntervalSeconds !== void 0 && options.monitorIntervalSeconds >= 1) {
21
56
  pgBossOptions.monitorIntervalSeconds = options.monitorIntervalSeconds;
22
57
  }
23
- bossInstance = new PgBoss(pgBossOptions);
24
- bossInstance.on("error", (error) => {
58
+ const boss = new PgBoss(pgBossOptions);
59
+ boss.on("error", (error) => {
25
60
  jobLogger.error("pg-boss error:", error);
26
61
  });
27
- await bossInstance.start();
62
+ await boss.start();
63
+ setBossInstance(boss);
28
64
  jobLogger.info("pg-boss started successfully");
29
- return bossInstance;
65
+ return boss;
30
66
  }
31
67
  function getBoss() {
32
- return bossInstance;
68
+ return getBossInstance();
33
69
  }
34
70
  async function stopBoss() {
35
- if (!bossInstance) {
71
+ const boss = getBossInstance();
72
+ if (!boss) {
36
73
  return;
37
74
  }
38
75
  jobLogger.info("Stopping pg-boss...");
39
76
  try {
40
- await bossInstance.stop({ graceful: true, timeout: 3e4 });
77
+ await boss.stop({ graceful: true, timeout: 3e4 });
41
78
  jobLogger.info("pg-boss stopped gracefully");
42
79
  } catch (error) {
43
80
  jobLogger.error("Error stopping pg-boss:", error);
44
81
  throw error;
45
82
  } finally {
46
- bossInstance = null;
47
- bossConfig = null;
83
+ setBossInstance(null);
84
+ setBossConfig(null);
48
85
  }
49
86
  }
50
87
  function isBossRunning() {
51
- return bossInstance !== null;
88
+ return getBossInstance() !== null;
52
89
  }
53
90
  function shouldClearOnStart() {
54
- return bossConfig?.clearOnStart ?? false;
91
+ return getBossConfig()?.clearOnStart ?? false;
92
+ }
93
+ function shouldSweepOrphanSchedules() {
94
+ return getBossConfig()?.sweepOrphanSchedules ?? false;
55
95
  }
56
96
 
57
97
  // src/job/job-builder.ts
@@ -228,6 +268,22 @@ var JobBuilder = class _JobBuilder {
228
268
  return await handler();
229
269
  }
230
270
  };
271
+ const sendBatch = async (inputsOrOptions, maybeOptions) => {
272
+ const boss = getBoss();
273
+ if (!boss) {
274
+ throw new Error(
275
+ `[Job:${name}] pg-boss not initialized. Ensure jobs are registered with defineServerConfig().jobs()`
276
+ );
277
+ }
278
+ const [inputs, sendOptions] = inputSchema ? [inputsOrOptions, maybeOptions] : [void 0, inputsOrOptions];
279
+ const pgBossOptions = buildPgBossOptions(options, sendOptions);
280
+ const jobs = (inputs ?? [{}]).map((data) => ({
281
+ name,
282
+ ...pgBossOptions,
283
+ data
284
+ }));
285
+ await boss.insert(name, jobs);
286
+ };
231
287
  return {
232
288
  name,
233
289
  inputSchema,
@@ -240,6 +296,7 @@ var JobBuilder = class _JobBuilder {
240
296
  handler,
241
297
  compensate,
242
298
  send,
299
+ sendBatch,
243
300
  run,
244
301
  _input: void 0,
245
302
  _output: void 0
@@ -283,6 +340,9 @@ function getDefaultJobOptions(options) {
283
340
  return {
284
341
  retryLimit: options?.retryLimit ?? 3,
285
342
  retryDelay: options?.retryDelay ?? 1e3,
343
+ // Exponential backoff by default — a failed cohort (e.g. a provider 429)
344
+ // would otherwise all retry at the same fixed offset (thundering herd).
345
+ retryBackoff: options?.retryBackoff ?? true,
286
346
  expireInSeconds: options?.expireInSeconds ?? 300
287
347
  };
288
348
  }
@@ -307,44 +367,97 @@ async function registerJobs(router) {
307
367
  }
308
368
  jobLogger2.info("Existing jobs cleared");
309
369
  }
370
+ await Promise.all(jobs.map((job2) => registerJob(job2)));
310
371
  for (const job2 of jobs) {
311
- await registerJob(job2);
372
+ if (job2.cronExpression) {
373
+ registeredCronNames.add(job2.name);
374
+ }
375
+ }
376
+ if (shouldSweepOrphanSchedules()) {
377
+ await sweepOrphanSchedules(boss);
312
378
  }
313
379
  jobLogger2.info("All jobs registered successfully");
314
380
  }
381
+ var registeredCronNames = /* @__PURE__ */ new Set();
382
+ async function sweepOrphanSchedules(boss) {
383
+ if (registeredCronNames.size === 0) {
384
+ jobLogger2.debug("No cron jobs declared; skipping orphan schedule sweep");
385
+ return;
386
+ }
387
+ try {
388
+ const schedules = await boss.getSchedules();
389
+ const orphans = schedules.filter((schedule) => !registeredCronNames.has(schedule.name));
390
+ if (orphans.length === 0) {
391
+ jobLogger2.debug("No orphan schedules found");
392
+ return;
393
+ }
394
+ await Promise.all(orphans.map(async (orphan) => {
395
+ try {
396
+ await boss.unschedule(orphan.name, orphan.key);
397
+ jobLogger2.info(`Removed orphan schedule: ${orphan.name}`);
398
+ } catch (error) {
399
+ jobLogger2.error(`Failed to remove orphan schedule: ${orphan.name}`, {
400
+ error: error instanceof Error ? error.message : String(error)
401
+ });
402
+ }
403
+ }));
404
+ } catch (error) {
405
+ jobLogger2.error("Orphan schedule sweep failed", {
406
+ error: error instanceof Error ? error.message : String(error)
407
+ });
408
+ }
409
+ }
315
410
  async function ensureQueue(boss, queueName) {
316
411
  await boss.createQueue(queueName);
317
412
  }
413
+ async function executeJobHandler(job2, pgBossJob) {
414
+ jobLogger2.debug(`[Job:${job2.name}] Executing...`, { jobId: pgBossJob.id });
415
+ const startTime = Date.now();
416
+ try {
417
+ if (job2.inputSchema) {
418
+ await job2.handler(pgBossJob.data);
419
+ } else {
420
+ await job2.handler();
421
+ }
422
+ const duration = Date.now() - startTime;
423
+ jobLogger2.info(`[Job:${job2.name}] Completed in ${duration}ms`, {
424
+ jobId: pgBossJob.id,
425
+ duration
426
+ });
427
+ } catch (error) {
428
+ const duration = Date.now() - startTime;
429
+ jobLogger2.error(`[Job:${job2.name}] Failed after ${duration}ms`, {
430
+ jobId: pgBossJob.id,
431
+ duration,
432
+ error: error instanceof Error ? error.message : String(error)
433
+ });
434
+ throw error;
435
+ }
436
+ }
318
437
  async function registerWorker(boss, job2, queueName) {
319
438
  await ensureQueue(boss, queueName);
439
+ const batchSize = job2.options?.batchSize ?? 1;
440
+ const pollingIntervalSeconds = job2.options?.pollingIntervalSeconds ?? env.JOB_POLLING_INTERVAL_SECONDS;
320
441
  await boss.work(
321
442
  queueName,
322
- { batchSize: 1 },
323
- async (jobs) => {
324
- for (const pgBossJob of jobs) {
325
- jobLogger2.debug(`[Job:${job2.name}] Executing...`, { jobId: pgBossJob.id });
326
- const startTime = Date.now();
327
- try {
328
- if (job2.inputSchema) {
329
- await job2.handler(pgBossJob.data);
330
- } else {
331
- await job2.handler();
332
- }
333
- const duration = Date.now() - startTime;
334
- jobLogger2.info(`[Job:${job2.name}] Completed in ${duration}ms`, {
335
- jobId: pgBossJob.id,
336
- duration
337
- });
338
- } catch (error) {
339
- const duration = Date.now() - startTime;
340
- jobLogger2.error(`[Job:${job2.name}] Failed after ${duration}ms`, {
341
- jobId: pgBossJob.id,
342
- duration,
343
- error: error instanceof Error ? error.message : String(error)
344
- });
345
- throw error;
443
+ { batchSize, pollingIntervalSeconds },
444
+ async (pgBossJobs) => {
445
+ if (batchSize <= 1) {
446
+ await executeJobHandler(job2, pgBossJobs[0]);
447
+ return;
448
+ }
449
+ const results = await Promise.allSettled(
450
+ pgBossJobs.map((pgBossJob) => executeJobHandler(job2, pgBossJob))
451
+ );
452
+ const failedIds = [];
453
+ for (let i = 0; i < results.length; i++) {
454
+ if (results[i].status === "rejected") {
455
+ failedIds.push(pgBossJobs[i].id);
346
456
  }
347
457
  }
458
+ if (failedIds.length > 0) {
459
+ await boss.fail(queueName, failedIds);
460
+ }
348
461
  }
349
462
  );
350
463
  }
@@ -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 * Global pg-boss instance\n */\nlet bossInstance: PgBoss | null = null;\n\n/**\n * Stored config for access during registration\n */\nlet bossConfig: BossConfig | null = null;\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/**\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 if (bossInstance)\n {\n jobLogger.warn('pg-boss already initialized, returning existing instance');\n return bossInstance;\n }\n\n jobLogger.info('Initializing pg-boss...');\n\n bossConfig = options;\n\n const pgBossOptions: PgBoss.ConstructorOptions = {\n connectionString: options.connectionString,\n schema: options.schema ?? 'spfn_queue',\n maintenanceIntervalSeconds: options.maintenanceIntervalSeconds ?? 120,\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 bossInstance = new PgBoss(pgBossOptions);\n\n // Event handlers\n bossInstance.on('error', (error) =>\n {\n jobLogger.error('pg-boss error:', error);\n });\n\n await bossInstance.start();\n\n jobLogger.info('pg-boss started successfully');\n\n return bossInstance;\n}\n\n/**\n * Get the current pg-boss instance\n */\nexport function getBoss(): PgBoss | null\n{\n return bossInstance;\n}\n\n/**\n * Stop pg-boss gracefully\n */\nexport async function stopBoss(): Promise<void>\n{\n if (!bossInstance)\n {\n return;\n }\n\n jobLogger.info('Stopping pg-boss...');\n\n try\n {\n await bossInstance.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 bossInstance = null;\n bossConfig = null;\n }\n}\n\n/**\n * Check if pg-boss is initialized and running\n */\nexport function isBossRunning(): boolean\n{\n return bossInstance !== null;\n}\n\n/**\n * Check if jobs should be cleared on start\n */\nexport function shouldClearOnStart(): boolean\n{\n return bossConfig?.clearOnStart ?? 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 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 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 return builder;\n }\n\n /**\n * Set cron expression for scheduled execution\n */\n cron(expression: string): this\n {\n this._cronExpression = expression;\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 return this;\n }\n\n /**\n * Set job options (retry, expiration, etc.)\n */\n options(options: JobOptions): this\n {\n this._options = options;\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 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 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 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 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 type { JobDef, JobOptions, JobRouter } from './types';\nimport type { EventDef } from '@spfn/core/event';\nimport { collectJobs } from './job-router';\nimport { getBoss, shouldClearOnStart } 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 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 for (const job of jobs)\n {\n await registerJob(job);\n }\n\n jobLogger.info('All jobs registered successfully');\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 * Register worker handler for a job\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 await boss.work(\n queueName,\n { batchSize: 1 },\n async (jobs) =>\n {\n for (const pgBossJob of jobs)\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}\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/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"]}
@@ -38,26 +38,31 @@ declare class Logger {
38
38
  * Debug log
39
39
  */
40
40
  debug(message: string, context?: Record<string, unknown>): void;
41
+ debug(message: string, formatArg: string | number | boolean): void;
41
42
  debug(message: string, error: Error | unknown, context?: Record<string, unknown>): void;
42
43
  /**
43
44
  * Info log
44
45
  */
45
46
  info(message: string, context?: Record<string, unknown>): void;
47
+ info(message: string, formatArg: string | number | boolean): void;
46
48
  info(message: string, error: Error | unknown, context?: Record<string, unknown>): void;
47
49
  /**
48
50
  * Warn log
49
51
  */
50
52
  warn(message: string, context?: Record<string, unknown>): void;
53
+ warn(message: string, formatArg: string | number | boolean): void;
51
54
  warn(message: string, error: Error | unknown, context?: Record<string, unknown>): void;
52
55
  /**
53
56
  * Error log
54
57
  */
55
58
  error(message: string, context?: Record<string, unknown>): void;
59
+ error(message: string, formatArg: string | number | boolean): void;
56
60
  error(message: string, error: Error | unknown, context?: Record<string, unknown>): void;
57
61
  /**
58
62
  * Fatal log
59
63
  */
60
64
  fatal(message: string, context?: Record<string, unknown>): void;
65
+ fatal(message: string, formatArg: string | number | boolean): void;
61
66
  fatal(message: string, error: Error | unknown, context?: Record<string, unknown>): void;
62
67
  /**
63
68
  * Log processing (internal)
@@ -1,3 +1,7 @@
1
+ import { format } from 'util';
2
+
3
+ // src/logger/logger.ts
4
+
1
5
  // src/logger/types.ts
2
6
  var LOG_LEVEL_PRIORITY = {
3
7
  debug: 0,
@@ -102,6 +106,11 @@ function formatError(error) {
102
106
  const stackLines = error.stack.split("\n").slice(1);
103
107
  lines.push(...stackLines);
104
108
  }
109
+ if (error.cause instanceof Error) {
110
+ lines.push(`Caused by: ${formatError(error.cause)}`);
111
+ } else if (error.cause !== void 0) {
112
+ lines.push(`Caused by: ${String(error.cause)}`);
113
+ }
105
114
  return lines.join("\n");
106
115
  }
107
116
  function formatConsole(metadata, colorize = true) {
@@ -166,6 +175,7 @@ function formatConsole(metadata, colorize = true) {
166
175
  }
167
176
 
168
177
  // src/logger/logger.ts
178
+ var FORMAT_PATTERN = /%[sdifjoOc%]/;
169
179
  var Logger = class _Logger {
170
180
  config;
171
181
  module;
@@ -215,6 +225,10 @@ var Logger = class _Logger {
215
225
  * Common log method with error/context detection
216
226
  */
217
227
  logWithLevel(level, message, errorOrContext, context) {
228
+ if (errorOrContext !== void 0 && FORMAT_PATTERN.test(message)) {
229
+ this.log(level, format(message, errorOrContext), void 0, context);
230
+ return;
231
+ }
218
232
  if (errorOrContext instanceof Error) {
219
233
  this.log(level, message, errorOrContext, context);
220
234
  } else if (errorOrContext !== void 0 && typeof errorOrContext === "object" && !this.isContext(errorOrContext)) {