@spfn/core 0.2.0-beta.9 → 0.3.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +693 -279
- package/dist/app-config/index.d.ts +96 -0
- package/dist/app-config/index.js +58 -0
- package/dist/app-config/index.js.map +1 -0
- package/dist/authz/index.d.ts +34 -0
- package/dist/authz/index.js +430 -0
- package/dist/authz/index.js.map +1 -0
- package/dist/boss-BiDCbbAQ.d.ts +337 -0
- package/dist/cache/index.js +42 -30
- package/dist/cache/index.js.map +1 -1
- package/dist/codegen/index.d.ts +121 -13
- package/dist/codegen/index.js +212 -15
- package/dist/codegen/index.js.map +1 -1
- package/dist/config/index.d.ts +576 -30
- package/dist/config/index.js +136 -17
- package/dist/config/index.js.map +1 -1
- package/dist/contract/index.d.ts +221 -0
- package/dist/contract/index.js +558 -0
- package/dist/contract/index.js.map +1 -0
- package/dist/db/index.d.ts +704 -94
- package/dist/db/index.js +1105 -260
- package/dist/db/index.js.map +1 -1
- package/dist/define-middleware-CVKgqo8S.d.ts +167 -0
- package/dist/env/index.d.ts +26 -2
- package/dist/env/index.js +15 -5
- package/dist/env/index.js.map +1 -1
- package/dist/env/loader.d.ts +26 -19
- package/dist/env/loader.js +55 -25
- package/dist/env/loader.js.map +1 -1
- package/dist/errors/index.d.ts +10 -0
- package/dist/errors/index.js +40 -7
- package/dist/errors/index.js.map +1 -1
- package/dist/event/index.d.ts +33 -3
- package/dist/event/index.js +24 -3
- package/dist/event/index.js.map +1 -1
- package/dist/event/sse/client.d.ts +55 -3
- package/dist/event/sse/client.js +164 -45
- package/dist/event/sse/client.js.map +1 -1
- package/dist/event/sse/index.d.ts +12 -5
- package/dist/event/sse/index.js +280 -32
- package/dist/event/sse/index.js.map +1 -1
- package/dist/event/ws/client.d.ts +59 -0
- package/dist/event/ws/client.js +273 -0
- package/dist/event/ws/client.js.map +1 -0
- package/dist/event/ws/index.d.ts +94 -0
- package/dist/event/ws/index.js +272 -0
- package/dist/event/ws/index.js.map +1 -0
- package/dist/job/index.d.ts +48 -3
- package/dist/job/index.js +265 -74
- package/dist/job/index.js.map +1 -1
- package/dist/logger/index.d.ts +5 -0
- package/dist/logger/index.js +14 -12
- package/dist/logger/index.js.map +1 -1
- package/dist/middleware/index.d.ts +352 -10
- package/dist/middleware/index.js +1079 -13
- package/dist/middleware/index.js.map +1 -1
- package/dist/nextjs/index.d.ts +39 -3
- package/dist/nextjs/index.js +82 -29
- package/dist/nextjs/index.js.map +1 -1
- package/dist/nextjs/server.d.ts +69 -52
- package/dist/nextjs/server.js +140 -60
- package/dist/nextjs/server.js.map +1 -1
- package/dist/ops/index.d.ts +208 -0
- package/dist/ops/index.js +800 -0
- package/dist/ops/index.js.map +1 -0
- package/dist/route/index.d.ts +10 -694
- package/dist/route/index.js +112 -23
- package/dist/route/index.js.map +1 -1
- package/dist/route-builder-2ani2jEI.d.ts +553 -0
- package/dist/router-DJdpwuB6.d.ts +127 -0
- package/dist/security/index.d.ts +83 -0
- package/dist/security/index.js +173 -0
- package/dist/security/index.js.map +1 -0
- package/dist/server/index.d.ts +619 -31
- package/dist/server/index.js +2583 -360
- package/dist/server/index.js.map +1 -1
- package/dist/token-manager-BT5EnUAR.d.ts +278 -0
- package/dist/types-2AbaW4Ie.d.ts +205 -0
- package/dist/{types-BOPTApC2.d.ts → types-9oszaJqp.d.ts} +7 -2
- package/dist/types-ClQVomgV.d.ts +115 -0
- package/dist/types-ZQODsBft.d.ts +282 -0
- package/docs/file-upload.md +195 -333
- package/package.json +250 -208
- package/src/cache/README.md +330 -0
- package/src/codegen/README.md +516 -0
- package/src/config/README.md +326 -0
- package/src/contract/README.md +326 -0
- package/src/db/README.md +589 -0
- package/src/db/manager/README.md +500 -0
- package/src/db/schema/README.md +354 -0
- package/src/db/transaction/README.md +822 -0
- package/src/env/README.md +651 -0
- package/src/errors/README.md +429 -0
- package/src/event/README.md +741 -0
- package/src/job/README.md +613 -0
- package/src/logger/README.md +321 -0
- package/src/middleware/README.md +634 -0
- package/src/nextjs/README.md +608 -0
- package/src/route/README.md +738 -0
- package/src/security/README.md +100 -0
- package/src/server/README.md +704 -0
- package/dist/boss-DI1r4kTS.d.ts +0 -244
- package/dist/router-Di7ENoah.d.ts +0 -151
- package/dist/types-B-e_f2dQ.d.ts +0 -121
- package/docs/cache.md +0 -133
- package/docs/codegen.md +0 -74
- package/docs/database.md +0 -346
- package/docs/entity.md +0 -539
- package/docs/env.md +0 -477
- package/docs/errors.md +0 -319
- package/docs/event.md +0 -116
- package/docs/job.md +0 -131
- package/docs/logger.md +0 -108
- package/docs/middleware.md +0 -337
- package/docs/nextjs.md +0 -241
- package/docs/repository.md +0 -496
- package/docs/route.md +0 -497
- package/docs/server.md +0 -307
|
@@ -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"]}
|
package/dist/job/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
1
|
+
import { J as JobOptions, C as CompensateHandler, a as JobHandler, b as JobDef, c as JobRouter, d as JobRouterEntry, e as JobQueuePolicy } from '../boss-BiDCbbAQ.js';
|
|
2
|
+
export { B as BossConfig, f as BossOptions, I as InferJobInput, g as InferJobOutput, h as JobSendOptions, i as getBoss, j as initBoss, k as isBossRunning, s as shouldClearOnStart, l as stopBoss } from '../boss-BiDCbbAQ.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';
|
|
@@ -168,11 +168,56 @@ declare function isJobRouter(value: unknown): value is JobRouter<any>;
|
|
|
168
168
|
* ```
|
|
169
169
|
*/
|
|
170
170
|
declare function defineJobRouter<TJobs extends Record<string, JobRouterEntry>>(jobs: TJobs): JobRouter<TJobs>;
|
|
171
|
+
/**
|
|
172
|
+
* Merge two job routers into a new one
|
|
173
|
+
*
|
|
174
|
+
* `defineServerConfig().jobs()` may be called once per domain router, the way
|
|
175
|
+
* `.routes()` is; each call merges into what is already registered instead of
|
|
176
|
+
* replacing it. Keys are the router's own namespace, so a key present in both
|
|
177
|
+
* routers is a mistake the app cannot recover from silently — one of the two
|
|
178
|
+
* would disappear.
|
|
179
|
+
*
|
|
180
|
+
* Neither input is mutated: a fresh router is returned.
|
|
181
|
+
*
|
|
182
|
+
* @throws if a top-level key exists in both routers
|
|
183
|
+
*/
|
|
184
|
+
declare function mergeJobRouters(base: JobRouter<any>, extra: JobRouter<any>): JobRouter<any>;
|
|
171
185
|
/**
|
|
172
186
|
* Collect all JobDefs from a JobRouter (including nested)
|
|
173
187
|
*/
|
|
174
188
|
declare function collectJobs(router: JobRouter<any>, prefix?: string): JobDef<any>[];
|
|
175
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Queue Policy Resolution
|
|
192
|
+
*
|
|
193
|
+
* Lives apart from register-jobs.ts and job-builder.ts so both can read the
|
|
194
|
+
* policy of a job definition without importing each other.
|
|
195
|
+
*/
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* The part of a job definition that decides its queue policy
|
|
199
|
+
*
|
|
200
|
+
* `JobDef` satisfies this; the builder can also pass its own fields before the
|
|
201
|
+
* definition object exists.
|
|
202
|
+
*/
|
|
203
|
+
interface QueuePolicySource {
|
|
204
|
+
readonly runOnce?: boolean;
|
|
205
|
+
readonly options?: JobOptions;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Resolve the pg-boss queue policy for a job definition
|
|
209
|
+
*
|
|
210
|
+
* An explicit `.options({ policy })` always wins. Otherwise a job that asks
|
|
211
|
+
* for deduplication — `.runOnce()` or an `options.singletonKey` — gets
|
|
212
|
+
* `exclusive`, the only policy under which a second enqueue is dropped while
|
|
213
|
+
* the first is still pending or running.
|
|
214
|
+
*
|
|
215
|
+
* Everything else stays `standard`: `exclusive` without a `singletonKey`
|
|
216
|
+
* deduplicates on `COALESCE(singleton_key, '')` and would collapse a plain
|
|
217
|
+
* queue to a single pending job.
|
|
218
|
+
*/
|
|
219
|
+
declare function resolveQueuePolicy(job: QueuePolicySource): JobQueuePolicy;
|
|
220
|
+
|
|
176
221
|
/**
|
|
177
222
|
* Job Registration
|
|
178
223
|
*
|
|
@@ -215,4 +260,4 @@ declare function collectJobs(router: JobRouter<any>, prefix?: string): JobDef<an
|
|
|
215
260
|
*/
|
|
216
261
|
declare function registerJobs(router: JobRouter<any>): Promise<void>;
|
|
217
262
|
|
|
218
|
-
export { CompensateHandler, JobDef, JobHandler, JobOptions, JobRouter, JobRouterEntry, collectJobs, defineJobRouter, isJobDef, isJobRouter, job, registerJobs };
|
|
263
|
+
export { CompensateHandler, JobDef, JobHandler, JobOptions, JobQueuePolicy, JobRouter, JobRouterEntry, collectJobs, defineJobRouter, isJobDef, isJobRouter, job, mergeJobRouters, registerJobs, resolveQueuePolicy };
|
package/dist/job/index.js
CHANGED
|
@@ -1,60 +1,122 @@
|
|
|
1
|
-
import PgBoss from 'pg-boss';
|
|
2
1
|
import { logger } from '@spfn/core/logger';
|
|
2
|
+
import PgBoss from 'pg-boss';
|
|
3
|
+
import { env } from '@spfn/core/config';
|
|
3
4
|
|
|
4
|
-
// src/job/
|
|
5
|
+
// src/job/job-builder.ts
|
|
5
6
|
var jobLogger = logger.child("@spfn/core:job");
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
37
|
+
const existing = getBossInstance();
|
|
38
|
+
if (existing) {
|
|
10
39
|
jobLogger.warn("pg-boss already initialized, returning existing instance");
|
|
11
|
-
return
|
|
40
|
+
return existing;
|
|
12
41
|
}
|
|
13
42
|
jobLogger.info("Initializing pg-boss...");
|
|
14
|
-
|
|
43
|
+
setBossConfig(options);
|
|
44
|
+
const needsSSL = requiresSSLWithoutVerification(options.connectionString);
|
|
15
45
|
const pgBossOptions = {
|
|
16
|
-
|
|
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
|
-
|
|
24
|
-
|
|
58
|
+
const boss = new PgBoss(pgBossOptions);
|
|
59
|
+
boss.on("error", (error) => {
|
|
25
60
|
jobLogger.error("pg-boss error:", error);
|
|
26
61
|
});
|
|
27
|
-
await
|
|
62
|
+
await boss.start();
|
|
63
|
+
setBossInstance(boss);
|
|
28
64
|
jobLogger.info("pg-boss started successfully");
|
|
29
|
-
return
|
|
65
|
+
return boss;
|
|
30
66
|
}
|
|
31
67
|
function getBoss() {
|
|
32
|
-
return
|
|
68
|
+
return getBossInstance();
|
|
33
69
|
}
|
|
34
70
|
async function stopBoss() {
|
|
35
|
-
|
|
71
|
+
const boss = getBossInstance();
|
|
72
|
+
if (!boss) {
|
|
36
73
|
return;
|
|
37
74
|
}
|
|
38
75
|
jobLogger.info("Stopping pg-boss...");
|
|
39
76
|
try {
|
|
40
|
-
await
|
|
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
|
-
|
|
47
|
-
|
|
83
|
+
setBossInstance(null);
|
|
84
|
+
setBossConfig(null);
|
|
48
85
|
}
|
|
49
86
|
}
|
|
50
87
|
function isBossRunning() {
|
|
51
|
-
return
|
|
88
|
+
return getBossInstance() !== null;
|
|
52
89
|
}
|
|
53
90
|
function shouldClearOnStart() {
|
|
54
|
-
return
|
|
91
|
+
return getBossConfig()?.clearOnStart ?? false;
|
|
92
|
+
}
|
|
93
|
+
function shouldSweepOrphanSchedules() {
|
|
94
|
+
return getBossConfig()?.sweepOrphanSchedules ?? false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/job/queue-policy.ts
|
|
98
|
+
function resolveQueuePolicy(job2) {
|
|
99
|
+
if (job2.options?.policy) {
|
|
100
|
+
return job2.options.policy;
|
|
101
|
+
}
|
|
102
|
+
if (job2.runOnce || job2.options?.singletonKey) {
|
|
103
|
+
return "exclusive";
|
|
104
|
+
}
|
|
105
|
+
return "standard";
|
|
55
106
|
}
|
|
56
107
|
|
|
57
108
|
// src/job/job-builder.ts
|
|
109
|
+
var jobLogger2 = logger.child("@spfn/core:job");
|
|
110
|
+
var warnedIgnoredSingletonKeys = /* @__PURE__ */ new Set();
|
|
111
|
+
function warnIfSingletonKeyIgnored(name, source) {
|
|
112
|
+
if (resolveQueuePolicy(source) !== "standard" || warnedIgnoredSingletonKeys.has(name)) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
warnedIgnoredSingletonKeys.add(name);
|
|
116
|
+
jobLogger2.warn(
|
|
117
|
+
`[Job:${name}] singletonKey ignored: queue policy is "standard"; set .options({ policy: 'exclusive' }) (or another non-standard policy) to deduplicate`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
58
120
|
function buildPgBossOptions(defaults, sendOptions) {
|
|
59
121
|
const options = {};
|
|
60
122
|
if (sendOptions?.startAfter) {
|
|
@@ -215,6 +277,9 @@ var JobBuilder = class _JobBuilder {
|
|
|
215
277
|
);
|
|
216
278
|
}
|
|
217
279
|
const [input, sendOptions] = inputSchema ? [inputOrOptions, maybeOptions] : [void 0, inputOrOptions];
|
|
280
|
+
if (sendOptions?.singletonKey) {
|
|
281
|
+
warnIfSingletonKeyIgnored(name, { runOnce, options });
|
|
282
|
+
}
|
|
218
283
|
return await boss.send(
|
|
219
284
|
name,
|
|
220
285
|
input ?? {},
|
|
@@ -228,6 +293,25 @@ var JobBuilder = class _JobBuilder {
|
|
|
228
293
|
return await handler();
|
|
229
294
|
}
|
|
230
295
|
};
|
|
296
|
+
const sendBatch = async (inputsOrOptions, maybeOptions) => {
|
|
297
|
+
const boss = getBoss();
|
|
298
|
+
if (!boss) {
|
|
299
|
+
throw new Error(
|
|
300
|
+
`[Job:${name}] pg-boss not initialized. Ensure jobs are registered with defineServerConfig().jobs()`
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
const [inputs, sendOptions] = inputSchema ? [inputsOrOptions, maybeOptions] : [void 0, inputsOrOptions];
|
|
304
|
+
if (sendOptions?.singletonKey) {
|
|
305
|
+
warnIfSingletonKeyIgnored(name, { runOnce, options });
|
|
306
|
+
}
|
|
307
|
+
const pgBossOptions = buildPgBossOptions(options, sendOptions);
|
|
308
|
+
const jobs = (inputs ?? [{}]).map((data) => ({
|
|
309
|
+
name,
|
|
310
|
+
...pgBossOptions,
|
|
311
|
+
data
|
|
312
|
+
}));
|
|
313
|
+
await boss.insert(name, jobs);
|
|
314
|
+
};
|
|
231
315
|
return {
|
|
232
316
|
name,
|
|
233
317
|
inputSchema,
|
|
@@ -240,6 +324,7 @@ var JobBuilder = class _JobBuilder {
|
|
|
240
324
|
handler,
|
|
241
325
|
compensate,
|
|
242
326
|
send,
|
|
327
|
+
sendBatch,
|
|
243
328
|
run,
|
|
244
329
|
_input: void 0,
|
|
245
330
|
_output: void 0
|
|
@@ -263,19 +348,32 @@ function defineJobRouter(jobs) {
|
|
|
263
348
|
_jobs: jobs
|
|
264
349
|
};
|
|
265
350
|
}
|
|
266
|
-
function
|
|
267
|
-
const jobs
|
|
351
|
+
function mergeJobRouters(base, extra) {
|
|
352
|
+
for (const key of Object.keys(extra.jobs)) {
|
|
353
|
+
if (key in base.jobs) {
|
|
354
|
+
throw new Error(
|
|
355
|
+
`jobs(): key "${key}" is already registered by an earlier .jobs() call; rename it or nest it under a domain router`
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return defineJobRouter({ ...base.jobs, ...extra.jobs });
|
|
360
|
+
}
|
|
361
|
+
function collectJobEntries(router, prefix = "") {
|
|
362
|
+
const entries = [];
|
|
268
363
|
for (const [key, value] of Object.entries(router.jobs)) {
|
|
269
|
-
const
|
|
364
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
270
365
|
if (isJobRouter(value)) {
|
|
271
|
-
|
|
366
|
+
entries.push(...collectJobEntries(value, path));
|
|
272
367
|
} else if (isJobDef(value)) {
|
|
273
|
-
|
|
368
|
+
entries.push({ key: path, job: value });
|
|
274
369
|
}
|
|
275
370
|
}
|
|
276
|
-
return
|
|
371
|
+
return entries;
|
|
277
372
|
}
|
|
278
|
-
|
|
373
|
+
function collectJobs(router, prefix = "") {
|
|
374
|
+
return collectJobEntries(router, prefix).map((entry) => entry.job);
|
|
375
|
+
}
|
|
376
|
+
var jobLogger3 = logger.child("@spfn/core:job");
|
|
279
377
|
function getEventQueueName(eventName) {
|
|
280
378
|
return `event:${eventName}`;
|
|
281
379
|
}
|
|
@@ -283,6 +381,9 @@ function getDefaultJobOptions(options) {
|
|
|
283
381
|
return {
|
|
284
382
|
retryLimit: options?.retryLimit ?? 3,
|
|
285
383
|
retryDelay: options?.retryDelay ?? 1e3,
|
|
384
|
+
// Exponential backoff by default — a failed cohort (e.g. a provider 429)
|
|
385
|
+
// would otherwise all retry at the same fixed offset (thundering herd).
|
|
386
|
+
retryBackoff: options?.retryBackoff ?? true,
|
|
286
387
|
expireInSeconds: options?.expireInSeconds ?? 300
|
|
287
388
|
};
|
|
288
389
|
}
|
|
@@ -293,11 +394,12 @@ async function registerJobs(router) {
|
|
|
293
394
|
"pg-boss not initialized. Call initBoss() before registerJobs()"
|
|
294
395
|
);
|
|
295
396
|
}
|
|
397
|
+
assertUniqueJobNames(router);
|
|
296
398
|
const jobs = collectJobs(router);
|
|
297
399
|
const clearOnStart = shouldClearOnStart();
|
|
298
|
-
|
|
400
|
+
jobLogger3.info(`Registering ${jobs.length} job(s)...`);
|
|
299
401
|
if (clearOnStart) {
|
|
300
|
-
|
|
402
|
+
jobLogger3.info("Clearing existing jobs before registration...");
|
|
301
403
|
for (const job2 of jobs) {
|
|
302
404
|
await boss.deleteAllJobs(job2.name);
|
|
303
405
|
if (job2.subscribedEvent) {
|
|
@@ -305,46 +407,134 @@ async function registerJobs(router) {
|
|
|
305
407
|
await boss.deleteAllJobs(eventQueue);
|
|
306
408
|
}
|
|
307
409
|
}
|
|
308
|
-
|
|
410
|
+
jobLogger3.info("Existing jobs cleared");
|
|
309
411
|
}
|
|
412
|
+
await Promise.all(jobs.map((job2) => registerJob(job2)));
|
|
310
413
|
for (const job2 of jobs) {
|
|
311
|
-
|
|
414
|
+
if (job2.cronExpression) {
|
|
415
|
+
registeredCronNames.add(job2.name);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (shouldSweepOrphanSchedules()) {
|
|
419
|
+
await sweepOrphanSchedules(boss);
|
|
420
|
+
}
|
|
421
|
+
jobLogger3.info("All jobs registered successfully");
|
|
422
|
+
}
|
|
423
|
+
function assertUniqueJobNames(router) {
|
|
424
|
+
const keysByName = /* @__PURE__ */ new Map();
|
|
425
|
+
for (const { key, job: job2 } of collectJobEntries(router)) {
|
|
426
|
+
const existingKey = keysByName.get(job2.name);
|
|
427
|
+
if (existingKey) {
|
|
428
|
+
throw new Error(
|
|
429
|
+
`registerJobs(): job name "${job2.name}" is defined twice (keys "${existingKey}" and "${key}"); job names are pg-boss queue names and must be unique`
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
keysByName.set(job2.name, key);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
var registeredCronNames = /* @__PURE__ */ new Set();
|
|
436
|
+
async function sweepOrphanSchedules(boss) {
|
|
437
|
+
if (registeredCronNames.size === 0) {
|
|
438
|
+
jobLogger3.debug("No cron jobs declared; skipping orphan schedule sweep");
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
try {
|
|
442
|
+
const schedules = await boss.getSchedules();
|
|
443
|
+
const orphans = schedules.filter((schedule) => !registeredCronNames.has(schedule.name));
|
|
444
|
+
if (orphans.length === 0) {
|
|
445
|
+
jobLogger3.debug("No orphan schedules found");
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
await Promise.all(orphans.map(async (orphan) => {
|
|
449
|
+
try {
|
|
450
|
+
await boss.unschedule(orphan.name, orphan.key);
|
|
451
|
+
jobLogger3.info(`Removed orphan schedule: ${orphan.name}`);
|
|
452
|
+
} catch (error) {
|
|
453
|
+
jobLogger3.error(`Failed to remove orphan schedule: ${orphan.name}`, {
|
|
454
|
+
error: error instanceof Error ? error.message : String(error)
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
}));
|
|
458
|
+
} catch (error) {
|
|
459
|
+
jobLogger3.error("Orphan schedule sweep failed", {
|
|
460
|
+
error: error instanceof Error ? error.message : String(error)
|
|
461
|
+
});
|
|
312
462
|
}
|
|
313
|
-
jobLogger2.info("All jobs registered successfully");
|
|
314
463
|
}
|
|
315
|
-
async function ensureQueue(boss, queueName) {
|
|
316
|
-
await boss.createQueue(queueName);
|
|
464
|
+
async function ensureQueue(boss, queueName, policy) {
|
|
465
|
+
await boss.createQueue(queueName, { policy });
|
|
466
|
+
const existing = await boss.getQueue(queueName);
|
|
467
|
+
if (!existing || existing.policy === policy) {
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
if (shouldClearOnStart()) {
|
|
471
|
+
await boss.deleteQueue(queueName);
|
|
472
|
+
await boss.createQueue(queueName, { policy });
|
|
473
|
+
jobLogger3.info(`[Queue:${queueName}] policy ${existing.policy} \u2192 ${policy} (recreated: clearOnStart)`);
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
jobLogger3.error(
|
|
477
|
+
`[Queue:${queueName}] policy mismatch: the queue exists as "${existing.policy}" but its jobs require "${policy}". Delete the queue in a quiet window (getBoss().deleteQueue('${queueName}') drops its job rows) or set clearOnStart in development; until then singletonKey/runOnce on this queue do not deduplicate`
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
function createQueueEnsurer(boss, policy) {
|
|
481
|
+
const ensured = /* @__PURE__ */ new Set();
|
|
482
|
+
return async (queueName) => {
|
|
483
|
+
if (ensured.has(queueName)) {
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
ensured.add(queueName);
|
|
487
|
+
await ensureQueue(boss, queueName, policy);
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
async function executeJobHandler(job2, pgBossJob) {
|
|
491
|
+
jobLogger3.debug(`[Job:${job2.name}] Executing...`, { jobId: pgBossJob.id });
|
|
492
|
+
const startTime = Date.now();
|
|
493
|
+
try {
|
|
494
|
+
if (job2.inputSchema) {
|
|
495
|
+
await job2.handler(pgBossJob.data);
|
|
496
|
+
} else {
|
|
497
|
+
await job2.handler();
|
|
498
|
+
}
|
|
499
|
+
const duration = Date.now() - startTime;
|
|
500
|
+
jobLogger3.info(`[Job:${job2.name}] Completed in ${duration}ms`, {
|
|
501
|
+
jobId: pgBossJob.id,
|
|
502
|
+
duration
|
|
503
|
+
});
|
|
504
|
+
} catch (error) {
|
|
505
|
+
const duration = Date.now() - startTime;
|
|
506
|
+
jobLogger3.error(`[Job:${job2.name}] Failed after ${duration}ms`, {
|
|
507
|
+
jobId: pgBossJob.id,
|
|
508
|
+
duration,
|
|
509
|
+
error: error instanceof Error ? error.message : String(error)
|
|
510
|
+
});
|
|
511
|
+
throw error;
|
|
512
|
+
}
|
|
317
513
|
}
|
|
318
|
-
async function registerWorker(boss, job2, queueName) {
|
|
319
|
-
await
|
|
514
|
+
async function registerWorker(boss, job2, queueName, ensureQueueOnce) {
|
|
515
|
+
await ensureQueueOnce(queueName);
|
|
516
|
+
const batchSize = job2.options?.batchSize ?? 1;
|
|
517
|
+
const pollingIntervalSeconds = job2.options?.pollingIntervalSeconds ?? env.JOB_POLLING_INTERVAL_SECONDS;
|
|
320
518
|
await boss.work(
|
|
321
519
|
queueName,
|
|
322
|
-
{ batchSize
|
|
323
|
-
async (
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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;
|
|
520
|
+
{ batchSize, pollingIntervalSeconds },
|
|
521
|
+
async (pgBossJobs) => {
|
|
522
|
+
if (batchSize <= 1) {
|
|
523
|
+
await executeJobHandler(job2, pgBossJobs[0]);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
const results = await Promise.allSettled(
|
|
527
|
+
pgBossJobs.map((pgBossJob) => executeJobHandler(job2, pgBossJob))
|
|
528
|
+
);
|
|
529
|
+
const failedIds = [];
|
|
530
|
+
for (let i = 0; i < results.length; i++) {
|
|
531
|
+
if (results[i].status === "rejected") {
|
|
532
|
+
failedIds.push(pgBossJobs[i].id);
|
|
346
533
|
}
|
|
347
534
|
}
|
|
535
|
+
if (failedIds.length > 0) {
|
|
536
|
+
await boss.fail(queueName, failedIds);
|
|
537
|
+
}
|
|
348
538
|
}
|
|
349
539
|
);
|
|
350
540
|
}
|
|
@@ -356,28 +546,28 @@ function connectEventToQueue(boss, job2, queueName) {
|
|
|
356
546
|
eventDef._registerJobQueue(queueName, async (queue, payload) => {
|
|
357
547
|
await boss.send(queue, payload, getDefaultJobOptions(job2.options));
|
|
358
548
|
});
|
|
359
|
-
|
|
549
|
+
jobLogger3.debug(`[Job:${job2.name}] Connected to event: ${job2.subscribedEvent}`);
|
|
360
550
|
}
|
|
361
|
-
async function registerCronSchedule(boss, job2) {
|
|
551
|
+
async function registerCronSchedule(boss, job2, ensureQueueOnce) {
|
|
362
552
|
if (!job2.cronExpression) {
|
|
363
553
|
return;
|
|
364
554
|
}
|
|
365
|
-
|
|
366
|
-
await
|
|
555
|
+
jobLogger3.debug(`[Job:${job2.name}] Scheduling cron: ${job2.cronExpression}`);
|
|
556
|
+
await ensureQueueOnce(job2.name);
|
|
367
557
|
await boss.schedule(
|
|
368
558
|
job2.name,
|
|
369
559
|
job2.cronExpression,
|
|
370
560
|
{},
|
|
371
561
|
getDefaultJobOptions(job2.options)
|
|
372
562
|
);
|
|
373
|
-
|
|
563
|
+
jobLogger3.info(`[Job:${job2.name}] Cron scheduled: ${job2.cronExpression}`);
|
|
374
564
|
}
|
|
375
|
-
async function queueRunOnceJob(boss, job2) {
|
|
565
|
+
async function queueRunOnceJob(boss, job2, ensureQueueOnce) {
|
|
376
566
|
if (!job2.runOnce) {
|
|
377
567
|
return;
|
|
378
568
|
}
|
|
379
|
-
|
|
380
|
-
await
|
|
569
|
+
jobLogger3.debug(`[Job:${job2.name}] Queuing runOnce job`);
|
|
570
|
+
await ensureQueueOnce(job2.name);
|
|
381
571
|
await boss.send(
|
|
382
572
|
job2.name,
|
|
383
573
|
{},
|
|
@@ -386,7 +576,7 @@ async function queueRunOnceJob(boss, job2) {
|
|
|
386
576
|
singletonKey: `runOnce:${job2.name}`
|
|
387
577
|
}
|
|
388
578
|
);
|
|
389
|
-
|
|
579
|
+
jobLogger3.info(`[Job:${job2.name}] runOnce job queued`);
|
|
390
580
|
}
|
|
391
581
|
async function registerJob(job2) {
|
|
392
582
|
const boss = getBoss();
|
|
@@ -394,17 +584,18 @@ async function registerJob(job2) {
|
|
|
394
584
|
throw new Error("pg-boss not initialized");
|
|
395
585
|
}
|
|
396
586
|
const queueName = job2.subscribedEvent ? getEventQueueName(job2.subscribedEvent) : job2.name;
|
|
397
|
-
|
|
587
|
+
jobLogger3.debug(`Registering job: ${job2.name}`, {
|
|
398
588
|
queueName,
|
|
399
589
|
subscribedEvent: job2.subscribedEvent
|
|
400
590
|
});
|
|
401
|
-
|
|
591
|
+
const ensureQueueOnce = createQueueEnsurer(boss, resolveQueuePolicy(job2));
|
|
592
|
+
await registerWorker(boss, job2, queueName, ensureQueueOnce);
|
|
402
593
|
connectEventToQueue(boss, job2, queueName);
|
|
403
|
-
await registerCronSchedule(boss, job2);
|
|
404
|
-
await queueRunOnceJob(boss, job2);
|
|
405
|
-
|
|
594
|
+
await registerCronSchedule(boss, job2, ensureQueueOnce);
|
|
595
|
+
await queueRunOnceJob(boss, job2, ensureQueueOnce);
|
|
596
|
+
jobLogger3.debug(`Job registered: ${job2.name}`);
|
|
406
597
|
}
|
|
407
598
|
|
|
408
|
-
export { collectJobs, defineJobRouter, getBoss, initBoss, isBossRunning, isJobDef, isJobRouter, job, registerJobs, shouldClearOnStart, stopBoss };
|
|
599
|
+
export { collectJobs, defineJobRouter, getBoss, initBoss, isBossRunning, isJobDef, isJobRouter, job, mergeJobRouters, registerJobs, resolveQueuePolicy, shouldClearOnStart, stopBoss };
|
|
409
600
|
//# sourceMappingURL=index.js.map
|
|
410
601
|
//# sourceMappingURL=index.js.map
|