@spfn/core 0.3.0-beta.14 → 0.3.0-beta.15
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/dist/job/index.js +13 -12
- package/dist/job/index.js.map +1 -1
- package/dist/server/index.js +12 -5
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
package/dist/job/index.js
CHANGED
|
@@ -105,6 +105,17 @@ function resolveQueuePolicy(job2) {
|
|
|
105
105
|
return "standard";
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
// src/job/retry-options.ts
|
|
109
|
+
function toPgBossRetryOptions(options) {
|
|
110
|
+
return {
|
|
111
|
+
retryLimit: options?.retryLimit ?? 3,
|
|
112
|
+
retryDelay: Math.ceil((options?.retryDelay ?? 1e3) / 1e3),
|
|
113
|
+
// Exponential backoff by default — a failed cohort (e.g. a provider 429)
|
|
114
|
+
// would otherwise all retry at the same fixed offset (thundering herd).
|
|
115
|
+
retryBackoff: options?.retryBackoff ?? true
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
108
119
|
// src/job/job-builder.ts
|
|
109
120
|
var jobLogger2 = logger.child("@spfn/core:job");
|
|
110
121
|
var warnedIgnoredSingletonKeys = /* @__PURE__ */ new Set();
|
|
@@ -118,7 +129,7 @@ function warnIfSingletonKeyIgnored(name, source) {
|
|
|
118
129
|
);
|
|
119
130
|
}
|
|
120
131
|
function buildPgBossOptions(defaults, sendOptions) {
|
|
121
|
-
const options = {};
|
|
132
|
+
const options = { ...toPgBossRetryOptions(defaults) };
|
|
122
133
|
if (sendOptions?.startAfter) {
|
|
123
134
|
options.startAfter = sendOptions.startAfter;
|
|
124
135
|
}
|
|
@@ -128,12 +139,6 @@ function buildPgBossOptions(defaults, sendOptions) {
|
|
|
128
139
|
if (sendOptions?.priority !== void 0) {
|
|
129
140
|
options.priority = sendOptions.priority;
|
|
130
141
|
}
|
|
131
|
-
if (defaults?.retryLimit !== void 0) {
|
|
132
|
-
options.retryLimit = defaults.retryLimit;
|
|
133
|
-
}
|
|
134
|
-
if (defaults?.retryDelay !== void 0) {
|
|
135
|
-
options.retryDelay = defaults.retryDelay;
|
|
136
|
-
}
|
|
137
142
|
if (defaults?.expireInSeconds !== void 0) {
|
|
138
143
|
options.expireInSeconds = defaults.expireInSeconds;
|
|
139
144
|
}
|
|
@@ -379,11 +384,7 @@ function getEventQueueName(eventName) {
|
|
|
379
384
|
}
|
|
380
385
|
function getDefaultJobOptions(options) {
|
|
381
386
|
return {
|
|
382
|
-
|
|
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,
|
|
387
|
+
...toPgBossRetryOptions(options),
|
|
387
388
|
expireInSeconds: options?.expireInSeconds ?? 300
|
|
388
389
|
};
|
|
389
390
|
}
|
package/dist/job/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/job/boss.ts","../../src/job/queue-policy.ts","../../src/job/job-builder.ts","../../src/job/job-router.ts","../../src/job/register-jobs.ts"],"names":["job","jobLogger","logger"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * pg-boss Wrapper\n *\n * Manages pg-boss instance lifecycle\n */\n\nimport PgBoss from 'pg-boss';\nimport { logger } from '@spfn/core/logger';\n\nconst jobLogger = logger.child('@spfn/core:job');\n\n/**\n * Check if connection string uses SSL without certificate verification\n *\n * pg library verifies certificates by default even with sslmode=require.\n * For require/prefer modes (no explicit verification), we disable cert checking\n * to support self-signed certificates.\n */\nfunction requiresSSLWithoutVerification(connectionString: string): boolean\n{\n try\n {\n const url = new URL(connectionString);\n const sslmode = url.searchParams.get('sslmode');\n\n return sslmode === 'require' || sslmode === 'prefer';\n }\n catch\n {\n return false;\n }\n}\n\n/**\n * Remove sslmode parameter from connection string URL\n *\n * pg driver interprets sslmode=require as verify-full, which overrides\n * the ssl option object. Stripping it lets us control SSL via the ssl option only.\n */\nfunction stripSslModeFromUrl(connectionString: string): string\n{\n const url = new URL(connectionString);\n url.searchParams.delete('sslmode');\n\n return url.toString();\n}\n\n/**\n * globalThis keys for cross-module-cache singleton (ESM/CJS share same instance)\n */\nconst BOSS_KEY = Symbol.for('spfn:boss-instance');\nconst CONFIG_KEY = Symbol.for('spfn:boss-config');\n\nconst g = globalThis as any;\n\nfunction getBossInstance(): PgBoss | null\n{\n return g[BOSS_KEY] ?? null;\n}\n\nfunction setBossInstance(instance: PgBoss | null): void\n{\n g[BOSS_KEY] = instance;\n}\n\nfunction getBossConfig(): BossConfig | null\n{\n return g[CONFIG_KEY] ?? null;\n}\n\nfunction setBossConfig(config: BossConfig | null): void\n{\n g[CONFIG_KEY] = config;\n}\n\n/**\n * Options for pg-boss initialization\n *\n * @example\n * ```typescript\n * await initBoss({\n * connectionString: process.env.DATABASE_URL,\n * schema: 'spfn_queue',\n * clearOnStart: process.env.NODE_ENV === 'development',\n * });\n * ```\n */\nexport interface BossOptions\n{\n /**\n * PostgreSQL connection string\n *\n * @example 'postgresql://user:password@localhost:5432/mydb'\n */\n connectionString: string;\n\n /**\n * Schema name for pg-boss tables\n *\n * pg-boss creates its own tables in this schema.\n *\n * @default 'spfn_queue'\n */\n schema?: string;\n\n /**\n * Maintenance interval in seconds\n *\n * pg-boss runs maintenance tasks (cleanup, archiving) at this interval.\n *\n * @default 120\n */\n maintenanceIntervalSeconds?: number;\n\n /**\n * Monitor state changes interval in seconds\n *\n * When set, pg-boss emits state change events at this interval.\n *\n * @default undefined (disabled)\n */\n monitorIntervalSeconds?: number;\n\n /**\n * Clear all pending/scheduled jobs on startup\n *\n * Useful for development mode to start with a clean queue.\n * Should be false in production.\n *\n * @default false\n */\n clearOnStart?: boolean;\n\n /**\n * Unschedule cron schedules that are no longer declared on any registered\n * job router, once at startup after job registration. Queues and job rows\n * are never deleted — only the schedule row is removed.\n *\n * Leave disabled when multiple apps share the same pg-boss schema, when\n * schedules are created directly via getBoss(), or during rolling deploys\n * that mix router versions — the sweep only knows the routers registered\n * in this process and would unschedule everything else.\n *\n * @default false\n */\n sweepOrphanSchedules?: boolean;\n}\n\n/**\n * @deprecated Use BossOptions instead\n */\nexport type BossConfig = BossOptions;\n\n/**\n * Initialize pg-boss with the given configuration\n *\n * Must be called before registerJobs(). Typically handled by defineServerConfig().\n *\n * @param options - pg-boss configuration options\n * @returns The pg-boss instance\n *\n * @example\n * ```typescript\n * const boss = await initBoss({\n * connectionString: process.env.DATABASE_URL!,\n * schema: 'spfn_queue',\n * });\n * ```\n */\nexport async function initBoss(options: BossOptions): Promise<PgBoss>\n{\n const existing = getBossInstance();\n if (existing)\n {\n jobLogger.warn('pg-boss already initialized, returning existing instance');\n\n return existing;\n }\n\n jobLogger.info('Initializing pg-boss...');\n\n setBossConfig(options);\n\n const needsSSL = requiresSSLWithoutVerification(options.connectionString);\n\n const pgBossOptions: PgBoss.ConstructorOptions = {\n // pg 드라이버가 URL의 sslmode=require를 verify-full로 해석해서\n // ssl 옵션을 무시하므로, URL에서 sslmode를 빼고 ssl 객체만 전달\n connectionString: needsSSL\n ? stripSslModeFromUrl(options.connectionString)\n : options.connectionString,\n schema: options.schema ?? 'spfn_queue',\n maintenanceIntervalSeconds: options.maintenanceIntervalSeconds ?? 120,\n };\n\n if (needsSSL)\n {\n pgBossOptions.ssl = { rejectUnauthorized: false };\n }\n\n // Only set monitorIntervalSeconds if explicitly provided (must be >= 1)\n if (options.monitorIntervalSeconds !== undefined && options.monitorIntervalSeconds >= 1)\n {\n pgBossOptions.monitorIntervalSeconds = options.monitorIntervalSeconds;\n }\n\n const boss = new PgBoss(pgBossOptions);\n\n // Event handlers\n boss.on('error', (error) =>\n {\n jobLogger.error('pg-boss error:', error);\n });\n\n await boss.start();\n\n setBossInstance(boss);\n\n jobLogger.info('pg-boss started successfully');\n\n return boss;\n}\n\n/**\n * Get the current pg-boss instance\n */\nexport function getBoss(): PgBoss | null\n{\n return getBossInstance();\n}\n\n/**\n * Stop pg-boss gracefully\n */\nexport async function stopBoss(): Promise<void>\n{\n const boss = getBossInstance();\n if (!boss)\n {\n return;\n }\n\n jobLogger.info('Stopping pg-boss...');\n\n try\n {\n await boss.stop({ graceful: true, timeout: 30000 });\n jobLogger.info('pg-boss stopped gracefully');\n }\n catch (error)\n {\n jobLogger.error('Error stopping pg-boss:', error);\n throw error;\n }\n finally\n {\n setBossInstance(null);\n setBossConfig(null);\n }\n}\n\n/**\n * Check if pg-boss is initialized and running\n */\nexport function isBossRunning(): boolean\n{\n return getBossInstance() !== null;\n}\n\n/**\n * Check if jobs should be cleared on start\n */\nexport function shouldClearOnStart(): boolean\n{\n return getBossConfig()?.clearOnStart ?? false;\n}\n\n/**\n * Check if orphan cron schedules should be swept after registration\n */\nexport function shouldSweepOrphanSchedules(): boolean\n{\n return getBossConfig()?.sweepOrphanSchedules ?? false;\n}\n","/**\n * Queue Policy Resolution\n *\n * Lives apart from register-jobs.ts and job-builder.ts so both can read the\n * policy of a job definition without importing each other.\n */\n\nimport type { JobOptions, JobQueuePolicy } from './types';\n\n/**\n * The part of a job definition that decides its queue policy\n *\n * `JobDef` satisfies this; the builder can also pass its own fields before the\n * definition object exists.\n */\nexport interface QueuePolicySource\n{\n readonly runOnce?: boolean;\n readonly options?: JobOptions;\n}\n\n/**\n * Resolve the pg-boss queue policy for a job definition\n *\n * An explicit `.options({ policy })` always wins. Otherwise a job that asks\n * for deduplication — `.runOnce()` or an `options.singletonKey` — gets\n * `exclusive`, the only policy under which a second enqueue is dropped while\n * the first is still pending or running.\n *\n * Everything else stays `standard`: `exclusive` without a `singletonKey`\n * deduplicates on `COALESCE(singleton_key, '')` and would collapse a plain\n * queue to a single pending job.\n */\nexport function resolveQueuePolicy(job: QueuePolicySource): JobQueuePolicy\n{\n if (job.options?.policy)\n {\n return job.options.policy;\n }\n\n if (job.runOnce || job.options?.singletonKey)\n {\n return 'exclusive';\n }\n\n return 'standard';\n}\n","/**\n * Job Builder\n *\n * Fluent API for defining jobs, similar to route builder pattern\n */\n\nimport type { Static, TSchema } from '@sinclair/typebox';\nimport type { CompensateHandler, JobDef, JobHandler, JobOptions, JobSendOptions } from './types';\nimport type { EventDef, InferEventPayload } from '@spfn/core/event';\nimport { logger } from '@spfn/core/logger';\nimport { getBoss } from './boss';\nimport { resolveQueuePolicy, type QueuePolicySource } from './queue-policy';\n\nconst jobLogger = logger.child('@spfn/core:job');\n\n// Job names whose ignored singletonKey has already been reported. A send-time\n// key is usually inside a loop, so warning per call would flood the log.\nconst warnedIgnoredSingletonKeys = new Set<string>();\n\n/**\n * Reset the singletonKey warn-once state\n *\n * @internal test-only\n */\nexport function resetSingletonKeyWarnings(): void\n{\n warnedIgnoredSingletonKeys.clear();\n}\n\n/**\n * Warn once when a send-time singletonKey lands on a `standard` queue\n *\n * pg-boss stores the key and never conflicts on it there, so the send is\n * accepted and the deduplication the caller asked for silently does not\n * happen. The policy lives on the definition, not on the send, so the fix is\n * on the definition too.\n */\nfunction warnIfSingletonKeyIgnored(name: string, source: QueuePolicySource): void\n{\n if (resolveQueuePolicy(source) !== 'standard' || warnedIgnoredSingletonKeys.has(name))\n {\n return;\n }\n\n warnedIgnoredSingletonKeys.add(name);\n\n jobLogger.warn(\n `[Job:${name}] singletonKey ignored: queue policy is \"standard\"; ` +\n \"set .options({ policy: 'exclusive' }) (or another non-standard policy) to deduplicate\",\n );\n}\n\n/**\n * Build pg-boss options from job defaults and send options\n */\nfunction buildPgBossOptions(\n defaults?: JobOptions,\n sendOptions?: JobSendOptions,\n): Record<string, unknown>\n{\n const options: Record<string, unknown> = {};\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 if (sendOptions?.singletonKey)\n {\n warnIfSingletonKeyIgnored(name, { runOnce, options });\n }\n\n return await boss.send(\n name,\n input ?? {},\n buildPgBossOptions(options, sendOptions),\n );\n };\n\n // Create run function (synchronous execution)\n const run = async (input?: TInput): Promise<TOutput> =>\n {\n if (inputSchema)\n {\n return await (handler as (input: TInput) => Promise<TOutput>)(input as TInput);\n }\n else\n {\n return await (handler as () => Promise<TOutput>)();\n }\n };\n\n // Create sendBatch function (bulk insert via pg-boss)\n const sendBatch = async (\n inputsOrOptions?: TInput[] | JobSendOptions,\n maybeOptions?: JobSendOptions,\n ): Promise<void> =>\n {\n const boss = getBoss();\n if (!boss)\n {\n throw new Error(\n `[Job:${name}] pg-boss not initialized. ` +\n 'Ensure jobs are registered with defineServerConfig().jobs()',\n );\n }\n\n const [inputs, sendOptions] = inputSchema\n ? [inputsOrOptions as TInput[], maybeOptions]\n : [undefined, inputsOrOptions as JobSendOptions | undefined];\n\n if (sendOptions?.singletonKey)\n {\n warnIfSingletonKeyIgnored(name, { runOnce, options });\n }\n\n const pgBossOptions = buildPgBossOptions(options, sendOptions);\n\n const jobs = (inputs ?? [{}]).map((data) => ({\n name,\n ...pgBossOptions,\n data: data as object,\n }));\n\n await boss.insert(name, jobs);\n };\n\n return {\n name,\n inputSchema,\n outputSchema,\n cronExpression,\n runOnce,\n subscribedEvent,\n _subscribedEventDef: subscribedEventDef,\n options,\n handler,\n compensate,\n send: send as JobDef<TInput, TOutput>['send'],\n sendBatch: sendBatch as JobDef<TInput, TOutput>['sendBatch'],\n run: run as JobDef<TInput, TOutput>['run'],\n _input: undefined as unknown as TInput,\n _output: undefined as unknown as TOutput,\n };\n }\n}\n\n/**\n * Create a new job definition\n *\n * @example\n * ```typescript\n * // Simple job without input\n * export const cleanupJob = job('cleanup')\n * .handler(async () => {\n * await db.cleanup();\n * });\n *\n * // Job with typed input\n * export const sendEmailJob = job('send-email')\n * .input(Type.Object({\n * to: Type.String(),\n * subject: Type.String(),\n * body: Type.String(),\n * }))\n * .handler(async (input) => {\n * await emailService.send(input.to, input.subject, input.body);\n * });\n *\n * // Cron job\n * export const dailyReportJob = job('daily-report')\n * .cron('0 9 * * *')\n * .handler(async () => {\n * await reportService.generateDaily();\n * });\n *\n * // Run once on server start\n * export const initCacheJob = job('init-cache')\n * .runOnce()\n * .handler(async () => {\n * await cache.warmup();\n * });\n *\n * // With options\n * export const importantJob = job('important-task')\n * .input(Type.Object({ id: Type.String() }))\n * .options({\n * retryLimit: 5,\n * retryDelay: 5000,\n * priority: 10,\n * })\n * .handler(async (input) => {\n * await processImportant(input.id);\n * });\n * ```\n */\nexport function job(name: string): JobBuilder<void>\n{\n return new JobBuilder(name);\n}\n","/**\n * Job Router\n *\n * Groups job definitions for registration with the server\n */\n\nimport type { JobDef, JobRouter, JobRouterEntry } from './types';\n\n/**\n * Type guard to check if value is a JobDef\n */\nexport function isJobDef(value: unknown): value is JobDef<any>\n{\n return (\n value !== null &&\n typeof value === 'object' &&\n 'name' in value &&\n 'handler' in value &&\n 'send' in value &&\n 'run' in value\n );\n}\n\n/**\n * Type guard to check if value is a JobRouter\n */\nexport function isJobRouter(value: unknown): value is JobRouter<any>\n{\n return (\n value !== null &&\n typeof value === 'object' &&\n 'jobs' in value &&\n '_jobs' in value\n );\n}\n\n/**\n * Define a job router to group jobs together\n *\n * @example\n * ```typescript\n * // Flat structure\n * export const jobRouter = defineJobRouter({\n * sendWelcomeEmail,\n * dailyReport,\n * initCache,\n * });\n *\n * // Nested structure\n * export const jobRouter = defineJobRouter({\n * email: defineJobRouter({\n * sendWelcome: sendWelcomeEmailJob,\n * sendReset: sendResetPasswordJob,\n * }),\n * reports: defineJobRouter({\n * daily: dailyReportJob,\n * weekly: weeklyReportJob,\n * }),\n * });\n *\n * // Mixed\n * export const jobRouter = defineJobRouter({\n * initCache, // flat\n * email: defineJobRouter({ ... }), // nested\n * });\n * ```\n */\nexport function defineJobRouter<\n TJobs extends Record<string, JobRouterEntry>,\n>(jobs: TJobs): JobRouter<TJobs>\n{\n return {\n jobs,\n _jobs: jobs,\n };\n}\n\n/**\n * Merge two job routers into a new one\n *\n * `defineServerConfig().jobs()` may be called once per domain router, the way\n * `.routes()` is; each call merges into what is already registered instead of\n * replacing it. Keys are the router's own namespace, so a key present in both\n * routers is a mistake the app cannot recover from silently — one of the two\n * would disappear.\n *\n * Neither input is mutated: a fresh router is returned.\n *\n * @throws if a top-level key exists in both routers\n */\nexport function mergeJobRouters(base: JobRouter<any>, extra: JobRouter<any>): JobRouter<any>\n{\n for (const key of Object.keys(extra.jobs))\n {\n if (key in base.jobs)\n {\n throw new Error(\n `jobs(): key \"${key}\" is already registered by an earlier .jobs() call; ` +\n 'rename it or nest it under a domain router',\n );\n }\n }\n\n return defineJobRouter({ ...base.jobs, ...extra.jobs });\n}\n\n/**\n * A collected job together with the router key path that reached it\n *\n * The key path (`email.sendWelcome`) is what an app author recognises, so it\n * is what error messages about a job name should quote.\n */\nexport interface JobEntry\n{\n readonly key: string;\n readonly job: JobDef<any>;\n}\n\n/**\n * Collect all JobDefs from a JobRouter (including nested) with their key paths\n */\nexport function collectJobEntries(\n router: JobRouter<any>,\n prefix = '',\n): JobEntry[]\n{\n const entries: JobEntry[] = [];\n\n for (const [key, value] of Object.entries(router.jobs))\n {\n const path = prefix ? `${prefix}.${key}` : key;\n\n if (isJobRouter(value))\n {\n // Nested router - recurse\n entries.push(...collectJobEntries(value, path));\n }\n else if (isJobDef(value))\n {\n entries.push({ key: path, job: value });\n }\n }\n\n return entries;\n}\n\n/**\n * Collect all JobDefs from a JobRouter (including nested)\n */\nexport function collectJobs(\n router: JobRouter<any>,\n prefix = '',\n): JobDef<any>[]\n{\n return collectJobEntries(router, prefix).map((entry) => entry.job);\n}\n","/**\n * Job Registration\n *\n * Registers jobs with pg-boss\n */\n\nimport type PgBoss from 'pg-boss';\nimport { logger } from '@spfn/core/logger';\nimport { env } from '@spfn/core/config';\nimport type { JobDef, JobOptions, JobQueuePolicy, JobRouter } from './types';\nimport type { EventDef } from '@spfn/core/event';\nimport { collectJobEntries, collectJobs } from './job-router';\nimport { resolveQueuePolicy } from './queue-policy';\nimport { 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 assertUniqueJobNames(router);\n\n const jobs = collectJobs(router);\n const clearOnStart = shouldClearOnStart();\n\n jobLogger.info(`Registering ${jobs.length} job(s)...`);\n\n // Clear existing jobs if requested (useful for development)\n if (clearOnStart)\n {\n jobLogger.info('Clearing existing jobs before registration...');\n for (const job of jobs)\n {\n // Clear job queue\n await boss.deleteAllJobs(job.name);\n\n // Also clear event queue if subscribed\n if (job.subscribedEvent)\n {\n const eventQueue = getEventQueueName(job.subscribedEvent);\n await boss.deleteAllJobs(eventQueue);\n }\n }\n jobLogger.info('Existing jobs cleared');\n }\n\n // Each job registers an independent pg-boss queue/worker, so register them\n // concurrently — sequential awaits made startup scale linearly with job count.\n await Promise.all(jobs.map((job) => registerJob(job)));\n\n for (const job of jobs)\n {\n if (job.cronExpression)\n {\n registeredCronNames.add(job.name);\n }\n }\n\n if (shouldSweepOrphanSchedules())\n {\n await sweepOrphanSchedules(boss);\n }\n\n jobLogger.info('All jobs registered successfully');\n}\n\n/**\n * Reject two job definitions that share a `name`\n *\n * A job name *is* its pg-boss queue name, so two jobs with the same name share\n * one queue and one worker: whichever registered last silently handles both.\n * Merged routers make this easy to hit, and it must fail before any queue or\n * worker exists.\n *\n * @throws if two collected jobs share a name\n */\nfunction assertUniqueJobNames(router: JobRouter<any>): void\n{\n const keysByName = new Map<string, string>();\n\n for (const { key, job } of collectJobEntries(router))\n {\n const existingKey = keysByName.get(job.name);\n\n if (existingKey)\n {\n throw new Error(\n `registerJobs(): job name \"${job.name}\" is defined twice ` +\n `(keys \"${existingKey}\" and \"${key}\"); ` +\n 'job names are pg-boss queue names and must be unique',\n );\n }\n\n keysByName.set(job.name, key);\n }\n}\n\n// Cron names accumulate across registerJobs() calls so a sweep in one call\n// never treats another router's schedules (registered earlier in the same\n// process) as orphans.\nconst registeredCronNames = new Set<string>();\n\n/**\n * Reset sweep state accumulated by registerJobs()\n *\n * @internal test-only\n */\nexport function resetOrphanSweepState(): void\n{\n registeredCronNames.clear();\n}\n\n/**\n * Unschedule pg-boss schedules that are no longer declared on any router\n * registered in this process (opt-in via `sweepOrphanSchedules`)\n *\n * The router is the only sanctioned way to create cron schedules, so any\n * schedule whose name is not a declared cron job is an orphan: pg-boss keeps\n * creating jobs for it on every cron tick, and without a worker they\n * accumulate forever. Unscheduling stops the pile-up; the queue and its\n * existing job rows are left untouched so nothing is destroyed if the name\n * belongs to another app, an ad-hoc getBoss() schedule, or a newer deploy.\n *\n * Skipped entirely when no cron job has been declared — an empty router says\n * nothing about which schedules are orphans. Runs once after registration;\n * failures are logged per orphan and never block startup.\n */\nasync function sweepOrphanSchedules(boss: PgBoss): Promise<void>\n{\n if (registeredCronNames.size === 0)\n {\n jobLogger.debug('No cron jobs declared; skipping orphan schedule sweep');\n\n return;\n }\n\n try\n {\n const schedules = await boss.getSchedules();\n const orphans = schedules.filter((schedule) => !registeredCronNames.has(schedule.name));\n\n if (orphans.length === 0)\n {\n jobLogger.debug('No orphan schedules found');\n\n return;\n }\n\n await Promise.all(orphans.map(async (orphan) =>\n {\n try\n {\n await boss.unschedule(orphan.name, orphan.key);\n jobLogger.info(`Removed orphan schedule: ${orphan.name}`);\n }\n catch (error)\n {\n jobLogger.error(`Failed to remove orphan schedule: ${orphan.name}`, {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }));\n }\n catch (error)\n {\n jobLogger.error('Orphan schedule sweep failed', {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n}\n\n/**\n * Create the queue with the wanted policy, or report that it already differs\n *\n * pg-boss `create_queue` is `INSERT … ON CONFLICT DO NOTHING`, so a queue\n * created by an earlier version keeps its old policy and `createQueue` is a\n * silent no-op. `updateQueue` cannot change a policy either. Comparing against\n * `getQueue` is the only way an already-deployed app ever gets the fix.\n *\n * Recreating the queue drops its job rows, so that only happens under\n * `clearOnStart` (development). Otherwise the mismatch is logged and boot\n * continues — a queue that does not deduplicate is a degraded queue, not a\n * broken one.\n */\nasync function ensureQueue(boss: PgBoss, queueName: string, policy: JobQueuePolicy): Promise<void>\n{\n await boss.createQueue(queueName, { policy });\n\n // null = queue missing (nothing to compare); pg-boss defaults it to 'standard'\n const existing = await boss.getQueue(queueName);\n if (!existing || existing.policy === policy)\n {\n return;\n }\n\n if (shouldClearOnStart())\n {\n await boss.deleteQueue(queueName);\n await boss.createQueue(queueName, { policy });\n jobLogger.info(`[Queue:${queueName}] policy ${existing.policy} → ${policy} (recreated: clearOnStart)`);\n\n return;\n }\n\n jobLogger.error(\n `[Queue:${queueName}] policy mismatch: the queue exists as \"${existing.policy}\" but its jobs ` +\n `require \"${policy}\". Delete the queue in a quiet window ` +\n `(getBoss().deleteQueue('${queueName}') drops its job rows) or set clearOnStart in development; ` +\n 'until then singletonKey/runOnce on this queue do not deduplicate',\n );\n}\n\n/**\n * Build the once-per-queue ensure used by a single registerJob() call\n *\n * registerJob touches its queue up to three times (worker, cron, runOnce) and\n * all three want the same policy, so the queue is ensured on first use only —\n * otherwise the same mismatch would be created and logged three times over.\n */\nfunction createQueueEnsurer(boss: PgBoss, policy: JobQueuePolicy): (queueName: string) => Promise<void>\n{\n const ensured = new Set<string>();\n\n return async (queueName: string) =>\n {\n if (ensured.has(queueName))\n {\n return;\n }\n\n ensured.add(queueName);\n\n await ensureQueue(boss, queueName, policy);\n };\n}\n\n/**\n * Execute a single job handler with logging\n */\nasync function executeJobHandler(\n job: JobDef<any>,\n pgBossJob: PgBoss.Job<any>,\n): Promise<void>\n{\n jobLogger.debug(`[Job:${job.name}] Executing...`, { jobId: pgBossJob.id });\n\n const startTime = Date.now();\n\n try\n {\n if (job.inputSchema)\n {\n await (job.handler as (input: unknown) => Promise<void>)(pgBossJob.data);\n }\n else\n {\n await (job.handler as () => Promise<void>)();\n }\n\n const duration = Date.now() - startTime;\n jobLogger.info(`[Job:${job.name}] Completed in ${duration}ms`, {\n jobId: pgBossJob.id,\n duration,\n });\n }\n catch (error)\n {\n const duration = Date.now() - startTime;\n jobLogger.error(`[Job:${job.name}] Failed after ${duration}ms`, {\n jobId: pgBossJob.id,\n duration,\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n}\n\n/**\n * Register worker handler for a job\n *\n * When batchSize > 1, jobs are processed in parallel.\n * Failed jobs are individually marked via boss.fail() so pg-boss can retry them.\n * Successful jobs are auto-completed when the handler callback resolves.\n */\nasync function registerWorker(\n boss: PgBoss,\n job: JobDef<any>,\n queueName: string,\n ensureQueueOnce: (queueName: string) => Promise<void>,\n): Promise<void>\n{\n // Ensure queue exists before registering worker\n await ensureQueueOnce(queueName);\n\n const batchSize = job.options?.batchSize ?? 1;\n const pollingIntervalSeconds = job.options?.pollingIntervalSeconds ?? env.JOB_POLLING_INTERVAL_SECONDS;\n\n await boss.work(\n queueName,\n { batchSize, pollingIntervalSeconds },\n async (pgBossJobs) =>\n {\n if (batchSize <= 1)\n {\n // Single job — throw on error for pg-boss retry\n await executeJobHandler(job, pgBossJobs[0]);\n\n return;\n }\n\n // Batch — parallel execution with individual failure handling\n const results = await Promise.allSettled(\n pgBossJobs.map((pgBossJob) => executeJobHandler(job, pgBossJob)),\n );\n\n // Collect failed job IDs and mark them individually.\n // boss.fail() sets state = 'failed'; the subsequent auto-complete\n // from work() only affects jobs still in 'active' state, so\n // already-failed jobs are not overwritten.\n const failedIds: string[] = [];\n\n for (let i = 0; i < results.length; i++)\n {\n if (results[i].status === 'rejected')\n {\n failedIds.push(pgBossJobs[i].id);\n }\n }\n\n if (failedIds.length > 0)\n {\n await boss.fail(queueName, failedIds);\n }\n\n // Callback resolves → pg-boss auto-completes remaining 'active' jobs\n },\n );\n}\n\n/**\n * Connect event to pg-boss queue\n */\nfunction connectEventToQueue(\n boss: PgBoss,\n job: JobDef<any>,\n queueName: string,\n): void\n{\n if (!job._subscribedEventDef)\n {\n return;\n }\n\n const eventDef = job._subscribedEventDef as EventDef<any>;\n eventDef._registerJobQueue(queueName, async (queue, payload) =>\n {\n await boss.send(queue, payload as object, getDefaultJobOptions(job.options));\n });\n\n jobLogger.debug(`[Job:${job.name}] Connected to event: ${job.subscribedEvent}`);\n}\n\n/**\n * Register cron schedule for a job\n */\nasync function registerCronSchedule(\n boss: PgBoss,\n job: JobDef<any>,\n ensureQueueOnce: (queueName: string) => Promise<void>,\n): Promise<void>\n{\n if (!job.cronExpression)\n {\n return;\n }\n\n jobLogger.debug(`[Job:${job.name}] Scheduling cron: ${job.cronExpression}`);\n\n // Ensure queue exists for cron jobs (uses job.name as queue)\n await ensureQueueOnce(job.name);\n\n await boss.schedule(\n job.name,\n job.cronExpression,\n {},\n getDefaultJobOptions(job.options),\n );\n\n jobLogger.info(`[Job:${job.name}] Cron scheduled: ${job.cronExpression}`);\n}\n\n/**\n * Queue a runOnce job\n */\nasync function queueRunOnceJob(\n boss: PgBoss,\n job: JobDef<any>,\n ensureQueueOnce: (queueName: string) => Promise<void>,\n): Promise<void>\n{\n if (!job.runOnce)\n {\n return;\n }\n\n jobLogger.debug(`[Job:${job.name}] Queuing runOnce job`);\n\n // Ensure queue exists for runOnce jobs (uses job.name as queue)\n await ensureQueueOnce(job.name);\n\n await boss.send(\n job.name,\n {},\n {\n ...getDefaultJobOptions(job.options),\n singletonKey: `runOnce:${job.name}`,\n },\n );\n\n jobLogger.info(`[Job:${job.name}] runOnce job queued`);\n}\n\n/**\n * Register a single job with pg-boss\n */\nasync function registerJob(job: JobDef<any>): Promise<void>\n{\n const boss = getBoss();\n if (!boss)\n {\n throw new Error('pg-boss not initialized');\n }\n\n const queueName = job.subscribedEvent\n ? getEventQueueName(job.subscribedEvent)\n : job.name;\n\n jobLogger.debug(`Registering job: ${job.name}`, {\n queueName,\n subscribedEvent: job.subscribedEvent,\n });\n\n const ensureQueueOnce = createQueueEnsurer(boss, resolveQueuePolicy(job));\n\n await registerWorker(boss, job, queueName, ensureQueueOnce);\n connectEventToQueue(boss, job, queueName);\n await registerCronSchedule(boss, job, ensureQueueOnce);\n await queueRunOnceJob(boss, job, ensureQueueOnce);\n\n jobLogger.debug(`Job registered: ${job.name}`);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/job/boss.ts","../../src/job/queue-policy.ts","../../src/job/retry-options.ts","../../src/job/job-builder.ts","../../src/job/job-router.ts","../../src/job/register-jobs.ts"],"names":["job","jobLogger","logger"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * pg-boss Wrapper\n *\n * Manages pg-boss instance lifecycle\n */\n\nimport PgBoss from 'pg-boss';\nimport { logger } from '@spfn/core/logger';\n\nconst jobLogger = logger.child('@spfn/core:job');\n\n/**\n * Check if connection string uses SSL without certificate verification\n *\n * pg library verifies certificates by default even with sslmode=require.\n * For require/prefer modes (no explicit verification), we disable cert checking\n * to support self-signed certificates.\n */\nfunction requiresSSLWithoutVerification(connectionString: string): boolean\n{\n try\n {\n const url = new URL(connectionString);\n const sslmode = url.searchParams.get('sslmode');\n\n return sslmode === 'require' || sslmode === 'prefer';\n }\n catch\n {\n return false;\n }\n}\n\n/**\n * Remove sslmode parameter from connection string URL\n *\n * pg driver interprets sslmode=require as verify-full, which overrides\n * the ssl option object. Stripping it lets us control SSL via the ssl option only.\n */\nfunction stripSslModeFromUrl(connectionString: string): string\n{\n const url = new URL(connectionString);\n url.searchParams.delete('sslmode');\n\n return url.toString();\n}\n\n/**\n * globalThis keys for cross-module-cache singleton (ESM/CJS share same instance)\n */\nconst BOSS_KEY = Symbol.for('spfn:boss-instance');\nconst CONFIG_KEY = Symbol.for('spfn:boss-config');\n\nconst g = globalThis as any;\n\nfunction getBossInstance(): PgBoss | null\n{\n return g[BOSS_KEY] ?? null;\n}\n\nfunction setBossInstance(instance: PgBoss | null): void\n{\n g[BOSS_KEY] = instance;\n}\n\nfunction getBossConfig(): BossConfig | null\n{\n return g[CONFIG_KEY] ?? null;\n}\n\nfunction setBossConfig(config: BossConfig | null): void\n{\n g[CONFIG_KEY] = config;\n}\n\n/**\n * Options for pg-boss initialization\n *\n * @example\n * ```typescript\n * await initBoss({\n * connectionString: process.env.DATABASE_URL,\n * schema: 'spfn_queue',\n * clearOnStart: process.env.NODE_ENV === 'development',\n * });\n * ```\n */\nexport interface BossOptions\n{\n /**\n * PostgreSQL connection string\n *\n * @example 'postgresql://user:password@localhost:5432/mydb'\n */\n connectionString: string;\n\n /**\n * Schema name for pg-boss tables\n *\n * pg-boss creates its own tables in this schema.\n *\n * @default 'spfn_queue'\n */\n schema?: string;\n\n /**\n * Maintenance interval in seconds\n *\n * pg-boss runs maintenance tasks (cleanup, archiving) at this interval.\n *\n * @default 120\n */\n maintenanceIntervalSeconds?: number;\n\n /**\n * Monitor state changes interval in seconds\n *\n * When set, pg-boss emits state change events at this interval.\n *\n * @default undefined (disabled)\n */\n monitorIntervalSeconds?: number;\n\n /**\n * Clear all pending/scheduled jobs on startup\n *\n * Useful for development mode to start with a clean queue.\n * Should be false in production.\n *\n * @default false\n */\n clearOnStart?: boolean;\n\n /**\n * Unschedule cron schedules that are no longer declared on any registered\n * job router, once at startup after job registration. Queues and job rows\n * are never deleted — only the schedule row is removed.\n *\n * Leave disabled when multiple apps share the same pg-boss schema, when\n * schedules are created directly via getBoss(), or during rolling deploys\n * that mix router versions — the sweep only knows the routers registered\n * in this process and would unschedule everything else.\n *\n * @default false\n */\n sweepOrphanSchedules?: boolean;\n}\n\n/**\n * @deprecated Use BossOptions instead\n */\nexport type BossConfig = BossOptions;\n\n/**\n * Initialize pg-boss with the given configuration\n *\n * Must be called before registerJobs(). Typically handled by defineServerConfig().\n *\n * @param options - pg-boss configuration options\n * @returns The pg-boss instance\n *\n * @example\n * ```typescript\n * const boss = await initBoss({\n * connectionString: process.env.DATABASE_URL!,\n * schema: 'spfn_queue',\n * });\n * ```\n */\nexport async function initBoss(options: BossOptions): Promise<PgBoss>\n{\n const existing = getBossInstance();\n if (existing)\n {\n jobLogger.warn('pg-boss already initialized, returning existing instance');\n\n return existing;\n }\n\n jobLogger.info('Initializing pg-boss...');\n\n setBossConfig(options);\n\n const needsSSL = requiresSSLWithoutVerification(options.connectionString);\n\n const pgBossOptions: PgBoss.ConstructorOptions = {\n // pg 드라이버가 URL의 sslmode=require를 verify-full로 해석해서\n // ssl 옵션을 무시하므로, URL에서 sslmode를 빼고 ssl 객체만 전달\n connectionString: needsSSL\n ? stripSslModeFromUrl(options.connectionString)\n : options.connectionString,\n schema: options.schema ?? 'spfn_queue',\n maintenanceIntervalSeconds: options.maintenanceIntervalSeconds ?? 120,\n };\n\n if (needsSSL)\n {\n pgBossOptions.ssl = { rejectUnauthorized: false };\n }\n\n // Only set monitorIntervalSeconds if explicitly provided (must be >= 1)\n if (options.monitorIntervalSeconds !== undefined && options.monitorIntervalSeconds >= 1)\n {\n pgBossOptions.monitorIntervalSeconds = options.monitorIntervalSeconds;\n }\n\n const boss = new PgBoss(pgBossOptions);\n\n // Event handlers\n boss.on('error', (error) =>\n {\n jobLogger.error('pg-boss error:', error);\n });\n\n await boss.start();\n\n setBossInstance(boss);\n\n jobLogger.info('pg-boss started successfully');\n\n return boss;\n}\n\n/**\n * Get the current pg-boss instance\n */\nexport function getBoss(): PgBoss | null\n{\n return getBossInstance();\n}\n\n/**\n * Stop pg-boss gracefully\n */\nexport async function stopBoss(): Promise<void>\n{\n const boss = getBossInstance();\n if (!boss)\n {\n return;\n }\n\n jobLogger.info('Stopping pg-boss...');\n\n try\n {\n await boss.stop({ graceful: true, timeout: 30000 });\n jobLogger.info('pg-boss stopped gracefully');\n }\n catch (error)\n {\n jobLogger.error('Error stopping pg-boss:', error);\n throw error;\n }\n finally\n {\n setBossInstance(null);\n setBossConfig(null);\n }\n}\n\n/**\n * Check if pg-boss is initialized and running\n */\nexport function isBossRunning(): boolean\n{\n return getBossInstance() !== null;\n}\n\n/**\n * Check if jobs should be cleared on start\n */\nexport function shouldClearOnStart(): boolean\n{\n return getBossConfig()?.clearOnStart ?? false;\n}\n\n/**\n * Check if orphan cron schedules should be swept after registration\n */\nexport function shouldSweepOrphanSchedules(): boolean\n{\n return getBossConfig()?.sweepOrphanSchedules ?? false;\n}\n","/**\n * Queue Policy Resolution\n *\n * Lives apart from register-jobs.ts and job-builder.ts so both can read the\n * policy of a job definition without importing each other.\n */\n\nimport type { JobOptions, JobQueuePolicy } from './types';\n\n/**\n * The part of a job definition that decides its queue policy\n *\n * `JobDef` satisfies this; the builder can also pass its own fields before the\n * definition object exists.\n */\nexport interface QueuePolicySource\n{\n readonly runOnce?: boolean;\n readonly options?: JobOptions;\n}\n\n/**\n * Resolve the pg-boss queue policy for a job definition\n *\n * An explicit `.options({ policy })` always wins. Otherwise a job that asks\n * for deduplication — `.runOnce()` or an `options.singletonKey` — gets\n * `exclusive`, the only policy under which a second enqueue is dropped while\n * the first is still pending or running.\n *\n * Everything else stays `standard`: `exclusive` without a `singletonKey`\n * deduplicates on `COALESCE(singleton_key, '')` and would collapse a plain\n * queue to a single pending job.\n */\nexport function resolveQueuePolicy(job: QueuePolicySource): JobQueuePolicy\n{\n if (job.options?.policy)\n {\n return job.options.policy;\n }\n\n if (job.runOnce || job.options?.singletonKey)\n {\n return 'exclusive';\n }\n\n return 'standard';\n}\n","/**\n * Retry options, translated from the job API's units to pg-boss's\n */\n\nimport type { JobOptions } from './types';\n\nexport interface PgBossRetryOptions\n{\n retryLimit: number;\n retryDelay: number;\n retryBackoff: boolean;\n}\n\n/**\n * The retry options every enqueue path hands to pg-boss\n *\n * `JobOptions.retryDelay` is in milliseconds; pg-boss reads `retryDelay` in\n * seconds. Passing it through unchanged turned a documented 5-second retry\n * into 5000 seconds (83 minutes). A delay under one second rounds up to one,\n * so a non-zero delay never becomes an immediate retry.\n */\nexport function toPgBossRetryOptions(options?: JobOptions): PgBossRetryOptions\n{\n return {\n retryLimit: options?.retryLimit ?? 3,\n retryDelay: Math.ceil((options?.retryDelay ?? 1000) / 1000),\n // Exponential backoff by default — a failed cohort (e.g. a provider 429)\n // would otherwise all retry at the same fixed offset (thundering herd).\n retryBackoff: options?.retryBackoff ?? true,\n };\n}\n","/**\n * Job Builder\n *\n * Fluent API for defining jobs, similar to route builder pattern\n */\n\nimport type { Static, TSchema } from '@sinclair/typebox';\nimport type { CompensateHandler, JobDef, JobHandler, JobOptions, JobSendOptions } from './types';\nimport type { EventDef, InferEventPayload } from '@spfn/core/event';\nimport { logger } from '@spfn/core/logger';\nimport { getBoss } from './boss';\nimport { resolveQueuePolicy, type QueuePolicySource } from './queue-policy';\nimport { toPgBossRetryOptions } from './retry-options';\n\nconst jobLogger = logger.child('@spfn/core:job');\n\n// Job names whose ignored singletonKey has already been reported. A send-time\n// key is usually inside a loop, so warning per call would flood the log.\nconst warnedIgnoredSingletonKeys = new Set<string>();\n\n/**\n * Reset the singletonKey warn-once state\n *\n * @internal test-only\n */\nexport function resetSingletonKeyWarnings(): void\n{\n warnedIgnoredSingletonKeys.clear();\n}\n\n/**\n * Warn once when a send-time singletonKey lands on a `standard` queue\n *\n * pg-boss stores the key and never conflicts on it there, so the send is\n * accepted and the deduplication the caller asked for silently does not\n * happen. The policy lives on the definition, not on the send, so the fix is\n * on the definition too.\n */\nfunction warnIfSingletonKeyIgnored(name: string, source: QueuePolicySource): void\n{\n if (resolveQueuePolicy(source) !== 'standard' || warnedIgnoredSingletonKeys.has(name))\n {\n return;\n }\n\n warnedIgnoredSingletonKeys.add(name);\n\n jobLogger.warn(\n `[Job:${name}] singletonKey ignored: queue policy is \"standard\"; ` +\n \"set .options({ policy: 'exclusive' }) (or another non-standard policy) to deduplicate\",\n );\n}\n\n/**\n * Build pg-boss options from job defaults and send options\n */\nfunction buildPgBossOptions(\n defaults?: JobOptions,\n sendOptions?: JobSendOptions,\n): Record<string, unknown>\n{\n const options: Record<string, unknown> = { ...toPgBossRetryOptions(defaults) };\n\n // Send options (per-job invocation)\n if (sendOptions?.startAfter)\n {\n options.startAfter = sendOptions.startAfter;\n }\n if (sendOptions?.singletonKey)\n {\n options.singletonKey = sendOptions.singletonKey;\n }\n if (sendOptions?.priority !== undefined)\n {\n options.priority = sendOptions.priority;\n }\n\n // Default options (from job definition)\n if (defaults?.expireInSeconds !== undefined)\n {\n options.expireInSeconds = defaults.expireInSeconds;\n }\n if (defaults?.priority !== undefined && sendOptions?.priority === undefined)\n {\n options.priority = defaults.priority;\n }\n if (defaults?.singletonKey && !sendOptions?.singletonKey)\n {\n options.singletonKey = defaults.singletonKey;\n }\n if (defaults?.retentionSeconds !== undefined)\n {\n options.retentionSeconds = defaults.retentionSeconds;\n }\n\n return options;\n}\n\n/**\n * Job builder class with fluent API\n */\nexport class JobBuilder<TInput = void, TOutput = void>\n{\n private readonly _name: string;\n private _inputSchema?: TSchema;\n private _outputSchema?: TSchema;\n private _cronExpression?: string;\n private _runOnce?: boolean;\n private _subscribedEvent?: string;\n private _subscribedEventDef?: EventDef<any>;\n private _options?: JobOptions;\n private _handler?: JobHandler<TInput, TOutput>;\n private _compensate?: CompensateHandler<TInput, TOutput>;\n\n constructor(name: string)\n {\n this._name = name;\n }\n\n /**\n * Define input schema with TypeBox\n */\n input<TSchema extends import('@sinclair/typebox').TSchema>(\n schema: TSchema,\n ): JobBuilder<Static<TSchema>, TOutput>\n {\n const builder = new JobBuilder<Static<TSchema>, TOutput>(this._name);\n builder._inputSchema = schema;\n builder._outputSchema = this._outputSchema;\n builder._cronExpression = this._cronExpression;\n builder._runOnce = this._runOnce;\n builder._subscribedEvent = this._subscribedEvent;\n builder._options = this._options;\n\n return builder;\n }\n\n /**\n * Define output schema with TypeBox (for workflow integration)\n */\n output<TSchema extends import('@sinclair/typebox').TSchema>(\n schema: TSchema,\n ): JobBuilder<TInput, Static<TSchema>>\n {\n const builder = new JobBuilder<TInput, Static<TSchema>>(this._name);\n builder._inputSchema = this._inputSchema;\n builder._outputSchema = schema;\n builder._cronExpression = this._cronExpression;\n builder._runOnce = this._runOnce;\n builder._subscribedEvent = this._subscribedEvent;\n builder._options = this._options;\n\n return builder;\n }\n\n /**\n * Subscribe to an event (decoupled triggering)\n *\n * @example\n * ```typescript\n * const userCreated = defineEvent('user.created', Type.Object({\n * userId: Type.String(),\n * }));\n *\n * const sendWelcomeEmail = job('send-welcome-email')\n * .on(userCreated)\n * .handler(async (payload) => {\n * // payload is typed as { userId: string }\n * });\n * ```\n */\n on<TEvent extends EventDef<any>>(\n event: TEvent,\n ): JobBuilder<InferEventPayload<TEvent>, TOutput>\n {\n const builder = new JobBuilder<InferEventPayload<TEvent>, TOutput>(this._name);\n builder._inputSchema = event.schema;\n builder._outputSchema = this._outputSchema;\n builder._subscribedEvent = event.name;\n builder._subscribedEventDef = event;\n builder._cronExpression = this._cronExpression;\n builder._runOnce = this._runOnce;\n builder._options = this._options;\n\n return builder;\n }\n\n /**\n * Set cron expression for scheduled execution\n */\n cron(expression: string): this\n {\n this._cronExpression = expression;\n\n return this;\n }\n\n /**\n * Mark job to run once on server start\n */\n runOnce(): this\n {\n this._runOnce = true;\n\n return this;\n }\n\n /**\n * Set job options (retry, expiration, etc.)\n */\n options(options: JobOptions): this\n {\n this._options = options;\n\n return this;\n }\n\n /**\n * Set job timeout in milliseconds\n * (Converts to expireInSeconds for pg-boss)\n */\n timeout(ms: number): this\n {\n this._options = {\n ...this._options,\n expireInSeconds: Math.ceil(ms / 1000),\n };\n\n return this;\n }\n\n /**\n * Define compensate handler for rollback (workflow integration)\n */\n compensate(fn: CompensateHandler<TInput, TOutput>): this\n {\n this._compensate = fn;\n\n return this;\n }\n\n /**\n * Define the job handler and finalize the job definition\n */\n handler(fn: JobHandler<TInput, TOutput>): JobDef<TInput, TOutput>\n {\n this._handler = fn;\n\n const name = this._name;\n const inputSchema = this._inputSchema;\n const outputSchema = this._outputSchema;\n const cronExpression = this._cronExpression;\n const runOnce = this._runOnce;\n const subscribedEvent = this._subscribedEvent;\n const subscribedEventDef = this._subscribedEventDef;\n const options = this._options;\n const handler = this._handler;\n const compensate = this._compensate;\n\n // Create send function\n const send = async (\n inputOrOptions?: TInput | JobSendOptions,\n maybeOptions?: JobSendOptions,\n ): Promise<string | null> =>\n {\n const boss = getBoss();\n if (!boss)\n {\n throw new Error(\n `[Job:${name}] pg-boss not initialized. ` +\n 'Ensure jobs are registered with defineServerConfig().jobs()',\n );\n }\n\n // Determine input and options based on whether job has input schema\n const [input, sendOptions] = inputSchema\n ? [inputOrOptions as TInput, maybeOptions]\n : [undefined, inputOrOptions as JobSendOptions | undefined];\n\n if (sendOptions?.singletonKey)\n {\n warnIfSingletonKeyIgnored(name, { runOnce, options });\n }\n\n return await boss.send(\n name,\n input ?? {},\n buildPgBossOptions(options, sendOptions),\n );\n };\n\n // Create run function (synchronous execution)\n const run = async (input?: TInput): Promise<TOutput> =>\n {\n if (inputSchema)\n {\n return await (handler as (input: TInput) => Promise<TOutput>)(input as TInput);\n }\n else\n {\n return await (handler as () => Promise<TOutput>)();\n }\n };\n\n // Create sendBatch function (bulk insert via pg-boss)\n const sendBatch = async (\n inputsOrOptions?: TInput[] | JobSendOptions,\n maybeOptions?: JobSendOptions,\n ): Promise<void> =>\n {\n const boss = getBoss();\n if (!boss)\n {\n throw new Error(\n `[Job:${name}] pg-boss not initialized. ` +\n 'Ensure jobs are registered with defineServerConfig().jobs()',\n );\n }\n\n const [inputs, sendOptions] = inputSchema\n ? [inputsOrOptions as TInput[], maybeOptions]\n : [undefined, inputsOrOptions as JobSendOptions | undefined];\n\n if (sendOptions?.singletonKey)\n {\n warnIfSingletonKeyIgnored(name, { runOnce, options });\n }\n\n const pgBossOptions = buildPgBossOptions(options, sendOptions);\n\n const jobs = (inputs ?? [{}]).map((data) => ({\n name,\n ...pgBossOptions,\n data: data as object,\n }));\n\n await boss.insert(name, jobs);\n };\n\n return {\n name,\n inputSchema,\n outputSchema,\n cronExpression,\n runOnce,\n subscribedEvent,\n _subscribedEventDef: subscribedEventDef,\n options,\n handler,\n compensate,\n send: send as JobDef<TInput, TOutput>['send'],\n sendBatch: sendBatch as JobDef<TInput, TOutput>['sendBatch'],\n run: run as JobDef<TInput, TOutput>['run'],\n _input: undefined as unknown as TInput,\n _output: undefined as unknown as TOutput,\n };\n }\n}\n\n/**\n * Create a new job definition\n *\n * @example\n * ```typescript\n * // Simple job without input\n * export const cleanupJob = job('cleanup')\n * .handler(async () => {\n * await db.cleanup();\n * });\n *\n * // Job with typed input\n * export const sendEmailJob = job('send-email')\n * .input(Type.Object({\n * to: Type.String(),\n * subject: Type.String(),\n * body: Type.String(),\n * }))\n * .handler(async (input) => {\n * await emailService.send(input.to, input.subject, input.body);\n * });\n *\n * // Cron job\n * export const dailyReportJob = job('daily-report')\n * .cron('0 9 * * *')\n * .handler(async () => {\n * await reportService.generateDaily();\n * });\n *\n * // Run once on server start\n * export const initCacheJob = job('init-cache')\n * .runOnce()\n * .handler(async () => {\n * await cache.warmup();\n * });\n *\n * // With options\n * export const importantJob = job('important-task')\n * .input(Type.Object({ id: Type.String() }))\n * .options({\n * retryLimit: 5,\n * retryDelay: 5000,\n * priority: 10,\n * })\n * .handler(async (input) => {\n * await processImportant(input.id);\n * });\n * ```\n */\nexport function job(name: string): JobBuilder<void>\n{\n return new JobBuilder(name);\n}\n","/**\n * Job Router\n *\n * Groups job definitions for registration with the server\n */\n\nimport type { JobDef, JobRouter, JobRouterEntry } from './types';\n\n/**\n * Type guard to check if value is a JobDef\n */\nexport function isJobDef(value: unknown): value is JobDef<any>\n{\n return (\n value !== null &&\n typeof value === 'object' &&\n 'name' in value &&\n 'handler' in value &&\n 'send' in value &&\n 'run' in value\n );\n}\n\n/**\n * Type guard to check if value is a JobRouter\n */\nexport function isJobRouter(value: unknown): value is JobRouter<any>\n{\n return (\n value !== null &&\n typeof value === 'object' &&\n 'jobs' in value &&\n '_jobs' in value\n );\n}\n\n/**\n * Define a job router to group jobs together\n *\n * @example\n * ```typescript\n * // Flat structure\n * export const jobRouter = defineJobRouter({\n * sendWelcomeEmail,\n * dailyReport,\n * initCache,\n * });\n *\n * // Nested structure\n * export const jobRouter = defineJobRouter({\n * email: defineJobRouter({\n * sendWelcome: sendWelcomeEmailJob,\n * sendReset: sendResetPasswordJob,\n * }),\n * reports: defineJobRouter({\n * daily: dailyReportJob,\n * weekly: weeklyReportJob,\n * }),\n * });\n *\n * // Mixed\n * export const jobRouter = defineJobRouter({\n * initCache, // flat\n * email: defineJobRouter({ ... }), // nested\n * });\n * ```\n */\nexport function defineJobRouter<\n TJobs extends Record<string, JobRouterEntry>,\n>(jobs: TJobs): JobRouter<TJobs>\n{\n return {\n jobs,\n _jobs: jobs,\n };\n}\n\n/**\n * Merge two job routers into a new one\n *\n * `defineServerConfig().jobs()` may be called once per domain router, the way\n * `.routes()` is; each call merges into what is already registered instead of\n * replacing it. Keys are the router's own namespace, so a key present in both\n * routers is a mistake the app cannot recover from silently — one of the two\n * would disappear.\n *\n * Neither input is mutated: a fresh router is returned.\n *\n * @throws if a top-level key exists in both routers\n */\nexport function mergeJobRouters(base: JobRouter<any>, extra: JobRouter<any>): JobRouter<any>\n{\n for (const key of Object.keys(extra.jobs))\n {\n if (key in base.jobs)\n {\n throw new Error(\n `jobs(): key \"${key}\" is already registered by an earlier .jobs() call; ` +\n 'rename it or nest it under a domain router',\n );\n }\n }\n\n return defineJobRouter({ ...base.jobs, ...extra.jobs });\n}\n\n/**\n * A collected job together with the router key path that reached it\n *\n * The key path (`email.sendWelcome`) is what an app author recognises, so it\n * is what error messages about a job name should quote.\n */\nexport interface JobEntry\n{\n readonly key: string;\n readonly job: JobDef<any>;\n}\n\n/**\n * Collect all JobDefs from a JobRouter (including nested) with their key paths\n */\nexport function collectJobEntries(\n router: JobRouter<any>,\n prefix = '',\n): JobEntry[]\n{\n const entries: JobEntry[] = [];\n\n for (const [key, value] of Object.entries(router.jobs))\n {\n const path = prefix ? `${prefix}.${key}` : key;\n\n if (isJobRouter(value))\n {\n // Nested router - recurse\n entries.push(...collectJobEntries(value, path));\n }\n else if (isJobDef(value))\n {\n entries.push({ key: path, job: value });\n }\n }\n\n return entries;\n}\n\n/**\n * Collect all JobDefs from a JobRouter (including nested)\n */\nexport function collectJobs(\n router: JobRouter<any>,\n prefix = '',\n): JobDef<any>[]\n{\n return collectJobEntries(router, prefix).map((entry) => entry.job);\n}\n","/**\n * Job Registration\n *\n * Registers jobs with pg-boss\n */\n\nimport type PgBoss from 'pg-boss';\nimport { logger } from '@spfn/core/logger';\nimport { env } from '@spfn/core/config';\nimport type { JobDef, JobOptions, JobQueuePolicy, JobRouter } from './types';\nimport type { EventDef } from '@spfn/core/event';\nimport { collectJobEntries, collectJobs } from './job-router';\nimport { resolveQueuePolicy } from './queue-policy';\nimport { toPgBossRetryOptions } from './retry-options';\nimport { getBoss, shouldClearOnStart, shouldSweepOrphanSchedules } from './boss';\n\nconst jobLogger = logger.child('@spfn/core:job');\n\n/**\n * Get the pg-boss queue name for an event\n */\nexport function getEventQueueName(eventName: string): string\n{\n return `event:${eventName}`;\n}\n\n/**\n * Build default pg-boss options for a job\n */\nfunction getDefaultJobOptions(options?: JobOptions): PgBoss.SendOptions\n{\n return {\n ...toPgBossRetryOptions(options),\n expireInSeconds: options?.expireInSeconds ?? 300,\n };\n}\n\n/**\n * Register all jobs from a JobRouter with pg-boss\n *\n * This function:\n * 1. Collects all jobs from the router (including nested routers)\n * 2. Optionally clears existing jobs (if clearOnStart is enabled)\n * 3. Registers each job's worker handler with pg-boss\n * 4. Sets up cron schedules for scheduled jobs\n * 5. Queues runOnce jobs\n * 6. Connects event subscriptions to job queues\n *\n * @param router - JobRouter containing job definitions\n *\n * @example\n * ```typescript\n * // Define jobs\n * const sendEmail = job('send-email')\n * .input(Type.Object({ to: Type.String() }))\n * .handler(async (input) => { ... });\n *\n * const dailyReport = job('daily-report')\n * .cron('0 9 * * *')\n * .handler(async () => { ... });\n *\n * // Create router\n * const jobRouter = defineJobRouter({ sendEmail, dailyReport });\n *\n * // Initialize pg-boss first\n * await initBoss({ connectionString: process.env.DATABASE_URL! });\n *\n * // Register jobs\n * await registerJobs(jobRouter);\n * ```\n */\nexport async function registerJobs(router: JobRouter<any>): Promise<void>\n{\n const boss = getBoss();\n if (!boss)\n {\n throw new Error(\n 'pg-boss not initialized. Call initBoss() before registerJobs()',\n );\n }\n\n assertUniqueJobNames(router);\n\n const jobs = collectJobs(router);\n const clearOnStart = shouldClearOnStart();\n\n jobLogger.info(`Registering ${jobs.length} job(s)...`);\n\n // Clear existing jobs if requested (useful for development)\n if (clearOnStart)\n {\n jobLogger.info('Clearing existing jobs before registration...');\n for (const job of jobs)\n {\n // Clear job queue\n await boss.deleteAllJobs(job.name);\n\n // Also clear event queue if subscribed\n if (job.subscribedEvent)\n {\n const eventQueue = getEventQueueName(job.subscribedEvent);\n await boss.deleteAllJobs(eventQueue);\n }\n }\n jobLogger.info('Existing jobs cleared');\n }\n\n // Each job registers an independent pg-boss queue/worker, so register them\n // concurrently — sequential awaits made startup scale linearly with job count.\n await Promise.all(jobs.map((job) => registerJob(job)));\n\n for (const job of jobs)\n {\n if (job.cronExpression)\n {\n registeredCronNames.add(job.name);\n }\n }\n\n if (shouldSweepOrphanSchedules())\n {\n await sweepOrphanSchedules(boss);\n }\n\n jobLogger.info('All jobs registered successfully');\n}\n\n/**\n * Reject two job definitions that share a `name`\n *\n * A job name *is* its pg-boss queue name, so two jobs with the same name share\n * one queue and one worker: whichever registered last silently handles both.\n * Merged routers make this easy to hit, and it must fail before any queue or\n * worker exists.\n *\n * @throws if two collected jobs share a name\n */\nfunction assertUniqueJobNames(router: JobRouter<any>): void\n{\n const keysByName = new Map<string, string>();\n\n for (const { key, job } of collectJobEntries(router))\n {\n const existingKey = keysByName.get(job.name);\n\n if (existingKey)\n {\n throw new Error(\n `registerJobs(): job name \"${job.name}\" is defined twice ` +\n `(keys \"${existingKey}\" and \"${key}\"); ` +\n 'job names are pg-boss queue names and must be unique',\n );\n }\n\n keysByName.set(job.name, key);\n }\n}\n\n// Cron names accumulate across registerJobs() calls so a sweep in one call\n// never treats another router's schedules (registered earlier in the same\n// process) as orphans.\nconst registeredCronNames = new Set<string>();\n\n/**\n * Reset sweep state accumulated by registerJobs()\n *\n * @internal test-only\n */\nexport function resetOrphanSweepState(): void\n{\n registeredCronNames.clear();\n}\n\n/**\n * Unschedule pg-boss schedules that are no longer declared on any router\n * registered in this process (opt-in via `sweepOrphanSchedules`)\n *\n * The router is the only sanctioned way to create cron schedules, so any\n * schedule whose name is not a declared cron job is an orphan: pg-boss keeps\n * creating jobs for it on every cron tick, and without a worker they\n * accumulate forever. Unscheduling stops the pile-up; the queue and its\n * existing job rows are left untouched so nothing is destroyed if the name\n * belongs to another app, an ad-hoc getBoss() schedule, or a newer deploy.\n *\n * Skipped entirely when no cron job has been declared — an empty router says\n * nothing about which schedules are orphans. Runs once after registration;\n * failures are logged per orphan and never block startup.\n */\nasync function sweepOrphanSchedules(boss: PgBoss): Promise<void>\n{\n if (registeredCronNames.size === 0)\n {\n jobLogger.debug('No cron jobs declared; skipping orphan schedule sweep');\n\n return;\n }\n\n try\n {\n const schedules = await boss.getSchedules();\n const orphans = schedules.filter((schedule) => !registeredCronNames.has(schedule.name));\n\n if (orphans.length === 0)\n {\n jobLogger.debug('No orphan schedules found');\n\n return;\n }\n\n await Promise.all(orphans.map(async (orphan) =>\n {\n try\n {\n await boss.unschedule(orphan.name, orphan.key);\n jobLogger.info(`Removed orphan schedule: ${orphan.name}`);\n }\n catch (error)\n {\n jobLogger.error(`Failed to remove orphan schedule: ${orphan.name}`, {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }));\n }\n catch (error)\n {\n jobLogger.error('Orphan schedule sweep failed', {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n}\n\n/**\n * Create the queue with the wanted policy, or report that it already differs\n *\n * pg-boss `create_queue` is `INSERT … ON CONFLICT DO NOTHING`, so a queue\n * created by an earlier version keeps its old policy and `createQueue` is a\n * silent no-op. `updateQueue` cannot change a policy either. Comparing against\n * `getQueue` is the only way an already-deployed app ever gets the fix.\n *\n * Recreating the queue drops its job rows, so that only happens under\n * `clearOnStart` (development). Otherwise the mismatch is logged and boot\n * continues — a queue that does not deduplicate is a degraded queue, not a\n * broken one.\n */\nasync function ensureQueue(boss: PgBoss, queueName: string, policy: JobQueuePolicy): Promise<void>\n{\n await boss.createQueue(queueName, { policy });\n\n // null = queue missing (nothing to compare); pg-boss defaults it to 'standard'\n const existing = await boss.getQueue(queueName);\n if (!existing || existing.policy === policy)\n {\n return;\n }\n\n if (shouldClearOnStart())\n {\n await boss.deleteQueue(queueName);\n await boss.createQueue(queueName, { policy });\n jobLogger.info(`[Queue:${queueName}] policy ${existing.policy} → ${policy} (recreated: clearOnStart)`);\n\n return;\n }\n\n jobLogger.error(\n `[Queue:${queueName}] policy mismatch: the queue exists as \"${existing.policy}\" but its jobs ` +\n `require \"${policy}\". Delete the queue in a quiet window ` +\n `(getBoss().deleteQueue('${queueName}') drops its job rows) or set clearOnStart in development; ` +\n 'until then singletonKey/runOnce on this queue do not deduplicate',\n );\n}\n\n/**\n * Build the once-per-queue ensure used by a single registerJob() call\n *\n * registerJob touches its queue up to three times (worker, cron, runOnce) and\n * all three want the same policy, so the queue is ensured on first use only —\n * otherwise the same mismatch would be created and logged three times over.\n */\nfunction createQueueEnsurer(boss: PgBoss, policy: JobQueuePolicy): (queueName: string) => Promise<void>\n{\n const ensured = new Set<string>();\n\n return async (queueName: string) =>\n {\n if (ensured.has(queueName))\n {\n return;\n }\n\n ensured.add(queueName);\n\n await ensureQueue(boss, queueName, policy);\n };\n}\n\n/**\n * Execute a single job handler with logging\n */\nasync function executeJobHandler(\n job: JobDef<any>,\n pgBossJob: PgBoss.Job<any>,\n): Promise<void>\n{\n jobLogger.debug(`[Job:${job.name}] Executing...`, { jobId: pgBossJob.id });\n\n const startTime = Date.now();\n\n try\n {\n if (job.inputSchema)\n {\n await (job.handler as (input: unknown) => Promise<void>)(pgBossJob.data);\n }\n else\n {\n await (job.handler as () => Promise<void>)();\n }\n\n const duration = Date.now() - startTime;\n jobLogger.info(`[Job:${job.name}] Completed in ${duration}ms`, {\n jobId: pgBossJob.id,\n duration,\n });\n }\n catch (error)\n {\n const duration = Date.now() - startTime;\n jobLogger.error(`[Job:${job.name}] Failed after ${duration}ms`, {\n jobId: pgBossJob.id,\n duration,\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n}\n\n/**\n * Register worker handler for a job\n *\n * When batchSize > 1, jobs are processed in parallel.\n * Failed jobs are individually marked via boss.fail() so pg-boss can retry them.\n * Successful jobs are auto-completed when the handler callback resolves.\n */\nasync function registerWorker(\n boss: PgBoss,\n job: JobDef<any>,\n queueName: string,\n ensureQueueOnce: (queueName: string) => Promise<void>,\n): Promise<void>\n{\n // Ensure queue exists before registering worker\n await ensureQueueOnce(queueName);\n\n const batchSize = job.options?.batchSize ?? 1;\n const pollingIntervalSeconds = job.options?.pollingIntervalSeconds ?? env.JOB_POLLING_INTERVAL_SECONDS;\n\n await boss.work(\n queueName,\n { batchSize, pollingIntervalSeconds },\n async (pgBossJobs) =>\n {\n if (batchSize <= 1)\n {\n // Single job — throw on error for pg-boss retry\n await executeJobHandler(job, pgBossJobs[0]);\n\n return;\n }\n\n // Batch — parallel execution with individual failure handling\n const results = await Promise.allSettled(\n pgBossJobs.map((pgBossJob) => executeJobHandler(job, pgBossJob)),\n );\n\n // Collect failed job IDs and mark them individually.\n // boss.fail() sets state = 'failed'; the subsequent auto-complete\n // from work() only affects jobs still in 'active' state, so\n // already-failed jobs are not overwritten.\n const failedIds: string[] = [];\n\n for (let i = 0; i < results.length; i++)\n {\n if (results[i].status === 'rejected')\n {\n failedIds.push(pgBossJobs[i].id);\n }\n }\n\n if (failedIds.length > 0)\n {\n await boss.fail(queueName, failedIds);\n }\n\n // Callback resolves → pg-boss auto-completes remaining 'active' jobs\n },\n );\n}\n\n/**\n * Connect event to pg-boss queue\n */\nfunction connectEventToQueue(\n boss: PgBoss,\n job: JobDef<any>,\n queueName: string,\n): void\n{\n if (!job._subscribedEventDef)\n {\n return;\n }\n\n const eventDef = job._subscribedEventDef as EventDef<any>;\n eventDef._registerJobQueue(queueName, async (queue, payload) =>\n {\n await boss.send(queue, payload as object, getDefaultJobOptions(job.options));\n });\n\n jobLogger.debug(`[Job:${job.name}] Connected to event: ${job.subscribedEvent}`);\n}\n\n/**\n * Register cron schedule for a job\n */\nasync function registerCronSchedule(\n boss: PgBoss,\n job: JobDef<any>,\n ensureQueueOnce: (queueName: string) => Promise<void>,\n): Promise<void>\n{\n if (!job.cronExpression)\n {\n return;\n }\n\n jobLogger.debug(`[Job:${job.name}] Scheduling cron: ${job.cronExpression}`);\n\n // Ensure queue exists for cron jobs (uses job.name as queue)\n await ensureQueueOnce(job.name);\n\n await boss.schedule(\n job.name,\n job.cronExpression,\n {},\n getDefaultJobOptions(job.options),\n );\n\n jobLogger.info(`[Job:${job.name}] Cron scheduled: ${job.cronExpression}`);\n}\n\n/**\n * Queue a runOnce job\n */\nasync function queueRunOnceJob(\n boss: PgBoss,\n job: JobDef<any>,\n ensureQueueOnce: (queueName: string) => Promise<void>,\n): Promise<void>\n{\n if (!job.runOnce)\n {\n return;\n }\n\n jobLogger.debug(`[Job:${job.name}] Queuing runOnce job`);\n\n // Ensure queue exists for runOnce jobs (uses job.name as queue)\n await ensureQueueOnce(job.name);\n\n await boss.send(\n job.name,\n {},\n {\n ...getDefaultJobOptions(job.options),\n singletonKey: `runOnce:${job.name}`,\n },\n );\n\n jobLogger.info(`[Job:${job.name}] runOnce job queued`);\n}\n\n/**\n * Register a single job with pg-boss\n */\nasync function registerJob(job: JobDef<any>): Promise<void>\n{\n const boss = getBoss();\n if (!boss)\n {\n throw new Error('pg-boss not initialized');\n }\n\n const queueName = job.subscribedEvent\n ? getEventQueueName(job.subscribedEvent)\n : job.name;\n\n jobLogger.debug(`Registering job: ${job.name}`, {\n queueName,\n subscribedEvent: job.subscribedEvent,\n });\n\n const ensureQueueOnce = createQueueEnsurer(boss, resolveQueuePolicy(job));\n\n await registerWorker(boss, job, queueName, ensureQueueOnce);\n connectEventToQueue(boss, job, queueName);\n await registerCronSchedule(boss, job, ensureQueueOnce);\n await queueRunOnceJob(boss, job, ensureQueueOnce);\n\n jobLogger.debug(`Job registered: ${job.name}`);\n}\n"]}
|
package/dist/server/index.js
CHANGED
|
@@ -2281,6 +2281,17 @@ function resolveQueuePolicy(job2) {
|
|
|
2281
2281
|
return "standard";
|
|
2282
2282
|
}
|
|
2283
2283
|
|
|
2284
|
+
// src/job/retry-options.ts
|
|
2285
|
+
function toPgBossRetryOptions(options) {
|
|
2286
|
+
return {
|
|
2287
|
+
retryLimit: options?.retryLimit ?? 3,
|
|
2288
|
+
retryDelay: Math.ceil((options?.retryDelay ?? 1e3) / 1e3),
|
|
2289
|
+
// Exponential backoff by default — a failed cohort (e.g. a provider 429)
|
|
2290
|
+
// would otherwise all retry at the same fixed offset (thundering herd).
|
|
2291
|
+
retryBackoff: options?.retryBackoff ?? true
|
|
2292
|
+
};
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2284
2295
|
// src/job/job-builder.ts
|
|
2285
2296
|
logger.child("@spfn/core:job");
|
|
2286
2297
|
|
|
@@ -2328,11 +2339,7 @@ function getEventQueueName(eventName) {
|
|
|
2328
2339
|
}
|
|
2329
2340
|
function getDefaultJobOptions(options) {
|
|
2330
2341
|
return {
|
|
2331
|
-
|
|
2332
|
-
retryDelay: options?.retryDelay ?? 1e3,
|
|
2333
|
-
// Exponential backoff by default — a failed cohort (e.g. a provider 429)
|
|
2334
|
-
// would otherwise all retry at the same fixed offset (thundering herd).
|
|
2335
|
-
retryBackoff: options?.retryBackoff ?? true,
|
|
2342
|
+
...toPgBossRetryOptions(options),
|
|
2336
2343
|
expireInSeconds: options?.expireInSeconds ?? 300
|
|
2337
2344
|
};
|
|
2338
2345
|
}
|