@spfn/core 0.2.0-beta.2 → 0.2.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +262 -1092
- package/dist/{boss-D-fGtVgM.d.ts → boss-DI1r4kTS.d.ts} +68 -11
- package/dist/codegen/index.d.ts +55 -8
- package/dist/codegen/index.js +179 -5
- package/dist/codegen/index.js.map +1 -1
- package/dist/config/index.d.ts +204 -6
- package/dist/config/index.js +44 -11
- package/dist/config/index.js.map +1 -1
- package/dist/db/index.d.ts +13 -0
- package/dist/db/index.js +92 -33
- package/dist/db/index.js.map +1 -1
- package/dist/env/index.d.ts +83 -3
- package/dist/env/index.js +83 -15
- package/dist/env/index.js.map +1 -1
- package/dist/env/loader.d.ts +95 -0
- package/dist/env/loader.js +78 -0
- package/dist/env/loader.js.map +1 -0
- package/dist/event/index.d.ts +29 -70
- package/dist/event/index.js +15 -1
- package/dist/event/index.js.map +1 -1
- package/dist/event/sse/client.d.ts +157 -0
- package/dist/event/sse/client.js +169 -0
- package/dist/event/sse/client.js.map +1 -0
- package/dist/event/sse/index.d.ts +46 -0
- package/dist/event/sse/index.js +205 -0
- package/dist/event/sse/index.js.map +1 -0
- package/dist/job/index.d.ts +54 -8
- package/dist/job/index.js +61 -12
- package/dist/job/index.js.map +1 -1
- package/dist/middleware/index.d.ts +124 -11
- package/dist/middleware/index.js +41 -7
- package/dist/middleware/index.js.map +1 -1
- package/dist/nextjs/index.d.ts +2 -2
- package/dist/nextjs/index.js +37 -5
- package/dist/nextjs/index.js.map +1 -1
- package/dist/nextjs/server.d.ts +45 -24
- package/dist/nextjs/server.js +87 -66
- package/dist/nextjs/server.js.map +1 -1
- package/dist/route/index.d.ts +207 -14
- package/dist/route/index.js +304 -31
- package/dist/route/index.js.map +1 -1
- package/dist/route/types.d.ts +2 -31
- package/dist/router-Di7ENoah.d.ts +151 -0
- package/dist/server/index.d.ts +321 -10
- package/dist/server/index.js +798 -189
- package/dist/server/index.js.map +1 -1
- package/dist/{types-DRG2XMTR.d.ts → types-7Mhoxnnt.d.ts} +97 -4
- package/dist/types-DHQMQlcb.d.ts +305 -0
- package/docs/cache.md +133 -0
- package/docs/codegen.md +74 -0
- package/docs/database.md +346 -0
- package/docs/entity.md +539 -0
- package/docs/env.md +499 -0
- package/docs/errors.md +319 -0
- package/docs/event.md +432 -0
- package/docs/file-upload.md +717 -0
- package/docs/job.md +131 -0
- package/docs/logger.md +108 -0
- package/docs/middleware.md +337 -0
- package/docs/nextjs.md +247 -0
- package/docs/repository.md +496 -0
- package/docs/route.md +497 -0
- package/docs/server.md +429 -0
- package/package.json +19 -3
package/dist/job/index.js
CHANGED
|
@@ -5,19 +5,22 @@ import { logger } from '@spfn/core/logger';
|
|
|
5
5
|
var jobLogger = logger.child("@spfn/core:job");
|
|
6
6
|
var bossInstance = null;
|
|
7
7
|
var bossConfig = null;
|
|
8
|
-
async function initBoss(
|
|
8
|
+
async function initBoss(options) {
|
|
9
9
|
if (bossInstance) {
|
|
10
10
|
jobLogger.warn("pg-boss already initialized, returning existing instance");
|
|
11
11
|
return bossInstance;
|
|
12
12
|
}
|
|
13
13
|
jobLogger.info("Initializing pg-boss...");
|
|
14
|
-
bossConfig =
|
|
15
|
-
|
|
16
|
-
connectionString:
|
|
17
|
-
schema:
|
|
18
|
-
maintenanceIntervalSeconds:
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
bossConfig = options;
|
|
15
|
+
const pgBossOptions = {
|
|
16
|
+
connectionString: options.connectionString,
|
|
17
|
+
schema: options.schema ?? "spfn_queue",
|
|
18
|
+
maintenanceIntervalSeconds: options.maintenanceIntervalSeconds ?? 120
|
|
19
|
+
};
|
|
20
|
+
if (options.monitorIntervalSeconds !== void 0 && options.monitorIntervalSeconds >= 1) {
|
|
21
|
+
pgBossOptions.monitorIntervalSeconds = options.monitorIntervalSeconds;
|
|
22
|
+
}
|
|
23
|
+
bossInstance = new PgBoss(pgBossOptions);
|
|
21
24
|
bossInstance.on("error", (error) => {
|
|
22
25
|
jobLogger.error("pg-boss error:", error);
|
|
23
26
|
});
|
|
@@ -86,12 +89,14 @@ function buildPgBossOptions(defaults, sendOptions) {
|
|
|
86
89
|
var JobBuilder = class _JobBuilder {
|
|
87
90
|
_name;
|
|
88
91
|
_inputSchema;
|
|
92
|
+
_outputSchema;
|
|
89
93
|
_cronExpression;
|
|
90
94
|
_runOnce;
|
|
91
95
|
_subscribedEvent;
|
|
92
96
|
_subscribedEventDef;
|
|
93
97
|
_options;
|
|
94
98
|
_handler;
|
|
99
|
+
_compensate;
|
|
95
100
|
constructor(name) {
|
|
96
101
|
this._name = name;
|
|
97
102
|
}
|
|
@@ -101,6 +106,20 @@ var JobBuilder = class _JobBuilder {
|
|
|
101
106
|
input(schema) {
|
|
102
107
|
const builder = new _JobBuilder(this._name);
|
|
103
108
|
builder._inputSchema = schema;
|
|
109
|
+
builder._outputSchema = this._outputSchema;
|
|
110
|
+
builder._cronExpression = this._cronExpression;
|
|
111
|
+
builder._runOnce = this._runOnce;
|
|
112
|
+
builder._subscribedEvent = this._subscribedEvent;
|
|
113
|
+
builder._options = this._options;
|
|
114
|
+
return builder;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Define output schema with TypeBox (for workflow integration)
|
|
118
|
+
*/
|
|
119
|
+
output(schema) {
|
|
120
|
+
const builder = new _JobBuilder(this._name);
|
|
121
|
+
builder._inputSchema = this._inputSchema;
|
|
122
|
+
builder._outputSchema = schema;
|
|
104
123
|
builder._cronExpression = this._cronExpression;
|
|
105
124
|
builder._runOnce = this._runOnce;
|
|
106
125
|
builder._subscribedEvent = this._subscribedEvent;
|
|
@@ -126,6 +145,7 @@ var JobBuilder = class _JobBuilder {
|
|
|
126
145
|
on(event) {
|
|
127
146
|
const builder = new _JobBuilder(this._name);
|
|
128
147
|
builder._inputSchema = event.schema;
|
|
148
|
+
builder._outputSchema = this._outputSchema;
|
|
129
149
|
builder._subscribedEvent = event.name;
|
|
130
150
|
builder._subscribedEventDef = event;
|
|
131
151
|
builder._cronExpression = this._cronExpression;
|
|
@@ -154,6 +174,24 @@ var JobBuilder = class _JobBuilder {
|
|
|
154
174
|
this._options = options;
|
|
155
175
|
return this;
|
|
156
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Set job timeout in milliseconds
|
|
179
|
+
* (Converts to expireInSeconds for pg-boss)
|
|
180
|
+
*/
|
|
181
|
+
timeout(ms) {
|
|
182
|
+
this._options = {
|
|
183
|
+
...this._options,
|
|
184
|
+
expireInSeconds: Math.ceil(ms / 1e3)
|
|
185
|
+
};
|
|
186
|
+
return this;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Define compensate handler for rollback (workflow integration)
|
|
190
|
+
*/
|
|
191
|
+
compensate(fn) {
|
|
192
|
+
this._compensate = fn;
|
|
193
|
+
return this;
|
|
194
|
+
}
|
|
157
195
|
/**
|
|
158
196
|
* Define the job handler and finalize the job definition
|
|
159
197
|
*/
|
|
@@ -161,12 +199,14 @@ var JobBuilder = class _JobBuilder {
|
|
|
161
199
|
this._handler = fn;
|
|
162
200
|
const name = this._name;
|
|
163
201
|
const inputSchema = this._inputSchema;
|
|
202
|
+
const outputSchema = this._outputSchema;
|
|
164
203
|
const cronExpression = this._cronExpression;
|
|
165
204
|
const runOnce = this._runOnce;
|
|
166
205
|
const subscribedEvent = this._subscribedEvent;
|
|
167
206
|
const subscribedEventDef = this._subscribedEventDef;
|
|
168
207
|
const options = this._options;
|
|
169
208
|
const handler = this._handler;
|
|
209
|
+
const compensate = this._compensate;
|
|
170
210
|
const send = async (inputOrOptions, maybeOptions) => {
|
|
171
211
|
const boss = getBoss();
|
|
172
212
|
if (!boss) {
|
|
@@ -183,23 +223,26 @@ var JobBuilder = class _JobBuilder {
|
|
|
183
223
|
};
|
|
184
224
|
const run = async (input) => {
|
|
185
225
|
if (inputSchema) {
|
|
186
|
-
await handler(input);
|
|
226
|
+
return await handler(input);
|
|
187
227
|
} else {
|
|
188
|
-
await handler();
|
|
228
|
+
return await handler();
|
|
189
229
|
}
|
|
190
230
|
};
|
|
191
231
|
return {
|
|
192
232
|
name,
|
|
193
233
|
inputSchema,
|
|
234
|
+
outputSchema,
|
|
194
235
|
cronExpression,
|
|
195
236
|
runOnce,
|
|
196
237
|
subscribedEvent,
|
|
197
238
|
_subscribedEventDef: subscribedEventDef,
|
|
198
239
|
options,
|
|
199
240
|
handler,
|
|
241
|
+
compensate,
|
|
200
242
|
send,
|
|
201
243
|
run,
|
|
202
|
-
_input: void 0
|
|
244
|
+
_input: void 0,
|
|
245
|
+
_output: void 0
|
|
203
246
|
};
|
|
204
247
|
}
|
|
205
248
|
};
|
|
@@ -269,7 +312,11 @@ async function registerJobs(router) {
|
|
|
269
312
|
}
|
|
270
313
|
jobLogger2.info("All jobs registered successfully");
|
|
271
314
|
}
|
|
315
|
+
async function ensureQueue(boss, queueName) {
|
|
316
|
+
await boss.createQueue(queueName);
|
|
317
|
+
}
|
|
272
318
|
async function registerWorker(boss, job2, queueName) {
|
|
319
|
+
await ensureQueue(boss, queueName);
|
|
273
320
|
await boss.work(
|
|
274
321
|
queueName,
|
|
275
322
|
{ batchSize: 1 },
|
|
@@ -316,6 +363,7 @@ async function registerCronSchedule(boss, job2) {
|
|
|
316
363
|
return;
|
|
317
364
|
}
|
|
318
365
|
jobLogger2.debug(`[Job:${job2.name}] Scheduling cron: ${job2.cronExpression}`);
|
|
366
|
+
await ensureQueue(boss, job2.name);
|
|
319
367
|
await boss.schedule(
|
|
320
368
|
job2.name,
|
|
321
369
|
job2.cronExpression,
|
|
@@ -329,6 +377,7 @@ async function queueRunOnceJob(boss, job2) {
|
|
|
329
377
|
return;
|
|
330
378
|
}
|
|
331
379
|
jobLogger2.debug(`[Job:${job2.name}] Queuing runOnce job`);
|
|
380
|
+
await ensureQueue(boss, job2.name);
|
|
332
381
|
await boss.send(
|
|
333
382
|
job2.name,
|
|
334
383
|
{},
|
|
@@ -356,6 +405,6 @@ async function registerJob(job2) {
|
|
|
356
405
|
jobLogger2.debug(`Job registered: ${job2.name}`);
|
|
357
406
|
}
|
|
358
407
|
|
|
359
|
-
export {
|
|
408
|
+
export { collectJobs, defineJobRouter, getBoss, initBoss, isBossRunning, isJobDef, isJobRouter, job, registerJobs, shouldClearOnStart, stopBoss };
|
|
360
409
|
//# sourceMappingURL=index.js.map
|
|
361
410
|
//# sourceMappingURL=index.js.map
|
package/dist/job/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/job/boss.ts","../../src/job/job-builder.ts","../../src/job/job-router.ts","../../src/job/register-jobs.ts"],"names":["jobLogger","logger","job"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * pg-boss Wrapper\n *\n * Manages pg-boss instance lifecycle\n */\n\nimport PgBoss from 'pg-boss';\nimport { logger } from '@spfn/core/logger';\n\nconst jobLogger = logger.child('@spfn/core:job');\n\n/**\n * Global pg-boss instance\n */\nlet bossInstance: PgBoss | null = null;\n\n/**\n * Stored config for access during registration\n */\nlet bossConfig: BossConfig | null = null;\n\n/**\n * pg-boss configuration options\n */\nexport interface BossConfig\n{\n /**\n * PostgreSQL connection string\n */\n connectionString: string;\n\n /**\n * Schema name for pg-boss tables\n * @default 'spfn_queue'\n */\n schema?: string;\n\n /**\n * Maintenance interval in seconds\n * @default 120\n */\n maintenanceIntervalSeconds?: number;\n\n /**\n * Monitor state changes interval in seconds\n * @default undefined (disabled)\n */\n monitorIntervalSeconds?: number;\n\n /**\n * Clear all pending/scheduled jobs on startup\n * Useful for development mode\n * @default false\n */\n clearOnStart?: boolean;\n}\n\n/**\n * Initialize pg-boss with the given configuration\n */\nexport async function initBoss(config: BossConfig): Promise<PgBoss>\n{\n if (bossInstance)\n {\n jobLogger.warn('pg-boss already initialized, returning existing instance');\n return bossInstance;\n }\n\n jobLogger.info('Initializing pg-boss...');\n\n bossConfig = config;\n bossInstance = new PgBoss({\n connectionString: config.connectionString,\n schema: config.schema ?? 'spfn_queue',\n maintenanceIntervalSeconds: config.maintenanceIntervalSeconds ?? 120,\n monitorIntervalSeconds: config.monitorIntervalSeconds,\n });\n\n // Event handlers\n bossInstance.on('error', (error) =>\n {\n jobLogger.error('pg-boss error:', error);\n });\n\n await bossInstance.start();\n\n jobLogger.info('pg-boss started successfully');\n\n return bossInstance;\n}\n\n/**\n * Get the current pg-boss instance\n */\nexport function getBoss(): PgBoss | null\n{\n return bossInstance;\n}\n\n/**\n * Stop pg-boss gracefully\n */\nexport async function stopBoss(): Promise<void>\n{\n if (!bossInstance)\n {\n return;\n }\n\n jobLogger.info('Stopping pg-boss...');\n\n try\n {\n await bossInstance.stop({ graceful: true, timeout: 30000 });\n jobLogger.info('pg-boss stopped gracefully');\n }\n catch (error)\n {\n jobLogger.error('Error stopping pg-boss:', error);\n throw error;\n }\n finally\n {\n bossInstance = null;\n bossConfig = null;\n }\n}\n\n/**\n * Check if pg-boss is initialized and running\n */\nexport function isBossRunning(): boolean\n{\n return bossInstance !== null;\n}\n\n/**\n * Check if jobs should be cleared on start\n */\nexport function shouldClearOnStart(): boolean\n{\n return bossConfig?.clearOnStart ?? false;\n}\n","/**\n * Job Builder\n *\n * Fluent API for defining jobs, similar to route builder pattern\n */\n\nimport type { Static, TSchema } from '@sinclair/typebox';\nimport type { JobDef, JobHandler, JobOptions, JobSendOptions } from './types';\nimport type { EventDef, InferEventPayload } from '@spfn/core/event';\nimport { getBoss } from './boss';\n\n/**\n * Build pg-boss options from job defaults and send options\n */\nfunction buildPgBossOptions(\n defaults?: JobOptions,\n sendOptions?: JobSendOptions\n): Record<string, unknown>\n{\n const options: Record<string, unknown> = {};\n\n // Send options (per-job invocation)\n if (sendOptions?.startAfter)\n {\n options.startAfter = sendOptions.startAfter;\n }\n if (sendOptions?.singletonKey)\n {\n options.singletonKey = sendOptions.singletonKey;\n }\n if (sendOptions?.priority !== undefined)\n {\n options.priority = sendOptions.priority;\n }\n\n // Default options (from job definition)\n if (defaults?.retryLimit !== undefined)\n {\n options.retryLimit = defaults.retryLimit;\n }\n if (defaults?.retryDelay !== undefined)\n {\n options.retryDelay = defaults.retryDelay;\n }\n if (defaults?.expireInSeconds !== undefined)\n {\n options.expireInSeconds = defaults.expireInSeconds;\n }\n if (defaults?.priority !== undefined && sendOptions?.priority === undefined)\n {\n options.priority = defaults.priority;\n }\n if (defaults?.singletonKey && !sendOptions?.singletonKey)\n {\n options.singletonKey = defaults.singletonKey;\n }\n if (defaults?.retentionSeconds !== undefined)\n {\n options.retentionSeconds = defaults.retentionSeconds;\n }\n\n return options;\n}\n\n/**\n * Job builder class with fluent API\n */\nexport class JobBuilder<TInput = void>\n{\n private _name: string;\n private _inputSchema?: 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>;\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>>\n {\n const builder = new JobBuilder<Static<TSchema>>(this._name);\n builder._inputSchema = schema;\n builder._cronExpression = this._cronExpression;\n builder._runOnce = this._runOnce;\n builder._subscribedEvent = this._subscribedEvent;\n builder._options = this._options;\n return builder;\n }\n\n /**\n * Subscribe to an event (decoupled triggering)\n *\n * @example\n * ```typescript\n * const userCreated = defineEvent('user.created', Type.Object({\n * userId: Type.String(),\n * }));\n *\n * const sendWelcomeEmail = job('send-welcome-email')\n * .on(userCreated)\n * .handler(async (payload) => {\n * // payload is typed as { userId: string }\n * });\n * ```\n */\n on<TEvent extends EventDef<any>>(\n event: TEvent\n ): JobBuilder<InferEventPayload<TEvent>>\n {\n const builder = new JobBuilder<InferEventPayload<TEvent>>(this._name);\n builder._inputSchema = event.schema;\n builder._subscribedEvent = event.name;\n builder._subscribedEventDef = event;\n builder._cronExpression = this._cronExpression;\n builder._runOnce = this._runOnce;\n builder._options = this._options;\n return builder;\n }\n\n /**\n * Set cron expression for scheduled execution\n */\n cron(expression: string): this\n {\n this._cronExpression = expression;\n return this;\n }\n\n /**\n * Mark job to run once on server start\n */\n runOnce(): this\n {\n this._runOnce = true;\n return this;\n }\n\n /**\n * Set job options (retry, expiration, etc.)\n */\n options(options: JobOptions): this\n {\n this._options = options;\n return this;\n }\n\n /**\n * Define the job handler and finalize the job definition\n */\n handler(fn: JobHandler<TInput>): JobDef<TInput>\n {\n this._handler = fn;\n\n const name = this._name;\n const inputSchema = this._inputSchema;\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\n // Create send function\n const send = async (\n inputOrOptions?: TInput | JobSendOptions,\n maybeOptions?: JobSendOptions\n ): Promise<string | null> =>\n {\n const boss = getBoss();\n if (!boss)\n {\n throw new Error(\n `[Job:${name}] pg-boss not initialized. ` +\n 'Ensure jobs are registered with defineServerConfig().jobs()'\n );\n }\n\n // Determine input and options based on whether job has input schema\n const [input, sendOptions] = inputSchema\n ? [inputOrOptions as TInput, maybeOptions]\n : [undefined, inputOrOptions as JobSendOptions | undefined];\n\n return await boss.send(\n name,\n input ?? {},\n buildPgBossOptions(options, sendOptions)\n );\n };\n\n // Create run function (synchronous execution)\n const run = async (input?: TInput): Promise<void> =>\n {\n if (inputSchema)\n {\n await (handler as (input: TInput) => Promise<void>)(input as TInput);\n }\n else\n {\n await (handler as () => Promise<void>)();\n }\n };\n\n return {\n name,\n inputSchema,\n cronExpression,\n runOnce,\n subscribedEvent,\n _subscribedEventDef: subscribedEventDef,\n options,\n handler,\n send: send as JobDef<TInput>['send'],\n run: run as JobDef<TInput>['run'],\n _input: undefined as unknown as TInput,\n };\n }\n}\n\n/**\n * Create a new job definition\n *\n * @example\n * ```typescript\n * // Simple job without input\n * export const cleanupJob = job('cleanup')\n * .handler(async () => {\n * await db.cleanup();\n * });\n *\n * // Job with typed input\n * export const sendEmailJob = job('send-email')\n * .input(Type.Object({\n * to: Type.String(),\n * subject: Type.String(),\n * body: Type.String(),\n * }))\n * .handler(async (input) => {\n * await emailService.send(input.to, input.subject, input.body);\n * });\n *\n * // Cron job\n * export const dailyReportJob = job('daily-report')\n * .cron('0 9 * * *')\n * .handler(async () => {\n * await reportService.generateDaily();\n * });\n *\n * // Run once on server start\n * export const initCacheJob = job('init-cache')\n * .runOnce()\n * .handler(async () => {\n * await cache.warmup();\n * });\n *\n * // With options\n * export const importantJob = job('important-task')\n * .input(Type.Object({ id: Type.String() }))\n * .options({\n * retryLimit: 5,\n * retryDelay: 5000,\n * priority: 10,\n * })\n * .handler(async (input) => {\n * await processImportant(input.id);\n * });\n * ```\n */\nexport function job(name: string): JobBuilder<void>\n{\n return new JobBuilder(name);\n}\n","/**\n * Job Router\n *\n * Groups job definitions for registration with the server\n */\n\nimport type { JobDef, JobRouter, JobRouterEntry } from './types';\n\n/**\n * Type guard to check if value is a JobDef\n */\nexport function isJobDef(value: unknown): value is JobDef<any>\n{\n return (\n value !== null &&\n typeof value === 'object' &&\n 'name' in value &&\n 'handler' in value &&\n 'send' in value &&\n 'run' in value\n );\n}\n\n/**\n * Type guard to check if value is a JobRouter\n */\nexport function isJobRouter(value: unknown): value is JobRouter<any>\n{\n return (\n value !== null &&\n typeof value === 'object' &&\n 'jobs' in value &&\n '_jobs' in value\n );\n}\n\n/**\n * Define a job router to group jobs together\n *\n * @example\n * ```typescript\n * // Flat structure\n * export const jobRouter = defineJobRouter({\n * sendWelcomeEmail,\n * dailyReport,\n * initCache,\n * });\n *\n * // Nested structure\n * export const jobRouter = defineJobRouter({\n * email: defineJobRouter({\n * sendWelcome: sendWelcomeEmailJob,\n * sendReset: sendResetPasswordJob,\n * }),\n * reports: defineJobRouter({\n * daily: dailyReportJob,\n * weekly: weeklyReportJob,\n * }),\n * });\n *\n * // Mixed\n * export const jobRouter = defineJobRouter({\n * initCache, // flat\n * email: defineJobRouter({ ... }), // nested\n * });\n * ```\n */\nexport function defineJobRouter<\n TJobs extends Record<string, JobRouterEntry>\n>(jobs: TJobs): JobRouter<TJobs>\n{\n return {\n jobs,\n _jobs: jobs,\n };\n}\n\n/**\n * Collect all JobDefs from a JobRouter (including nested)\n */\nexport function collectJobs(\n router: JobRouter<any>,\n prefix = ''\n): JobDef<any>[]\n{\n const jobs: JobDef<any>[] = [];\n\n for (const [key, value] of Object.entries(router.jobs))\n {\n const name = prefix ? `${prefix}.${key}` : key;\n\n if (isJobRouter(value))\n {\n // Nested router - recurse\n jobs.push(...collectJobs(value, name));\n }\n else if (isJobDef(value))\n {\n jobs.push(value);\n }\n }\n\n return jobs;\n}\n","/**\n * Job Registration\n *\n * Registers jobs with pg-boss\n */\n\nimport type PgBoss from 'pg-boss';\nimport { logger } from '@spfn/core/logger';\nimport type { JobDef, JobOptions, JobRouter } from './types';\nimport type { EventDef } from '@spfn/core/event';\nimport { collectJobs } from './job-router';\nimport { getBoss, shouldClearOnStart } from './boss';\n\nconst jobLogger = logger.child('@spfn/core:job');\n\n/**\n * Get the pg-boss queue name for an event\n */\nexport function getEventQueueName(eventName: string): string\n{\n return `event:${eventName}`;\n}\n\n/**\n * Build default pg-boss options for a job\n */\nfunction getDefaultJobOptions(options?: JobOptions): PgBoss.SendOptions\n{\n return {\n retryLimit: options?.retryLimit ?? 3,\n retryDelay: options?.retryDelay ?? 1000,\n expireInSeconds: options?.expireInSeconds ?? 300,\n };\n}\n\n/**\n * Register all jobs from a JobRouter with pg-boss\n */\nexport async function registerJobs(router: JobRouter<any>): Promise<void>\n{\n const boss = getBoss();\n if (!boss)\n {\n throw new Error(\n 'pg-boss not initialized. Call initBoss() before registerJobs()'\n );\n }\n\n const jobs = collectJobs(router);\n const clearOnStart = shouldClearOnStart();\n\n jobLogger.info(`Registering ${jobs.length} job(s)...`);\n\n // Clear existing jobs if requested (useful for development)\n if (clearOnStart)\n {\n jobLogger.info('Clearing existing jobs before registration...');\n for (const job of jobs)\n {\n // Clear job queue\n await boss.deleteAllJobs(job.name);\n\n // Also clear event queue if subscribed\n if (job.subscribedEvent)\n {\n const eventQueue = getEventQueueName(job.subscribedEvent);\n await boss.deleteAllJobs(eventQueue);\n }\n }\n jobLogger.info('Existing jobs cleared');\n }\n\n for (const job of jobs)\n {\n await registerJob(job);\n }\n\n jobLogger.info('All jobs registered successfully');\n}\n\n/**\n * Register worker handler for a job\n */\nasync function registerWorker(\n boss: PgBoss,\n job: JobDef<any>,\n queueName: string\n): Promise<void>\n{\n await boss.work(\n queueName,\n { batchSize: 1 },\n async (jobs) =>\n {\n for (const pgBossJob of jobs)\n {\n jobLogger.debug(`[Job:${job.name}] Executing...`, { jobId: pgBossJob.id });\n\n const startTime = Date.now();\n\n try\n {\n if (job.inputSchema)\n {\n await (job.handler as (input: unknown) => Promise<void>)(pgBossJob.data);\n }\n else\n {\n await (job.handler as () => Promise<void>)();\n }\n\n const duration = Date.now() - startTime;\n jobLogger.info(`[Job:${job.name}] Completed in ${duration}ms`, {\n jobId: pgBossJob.id,\n duration,\n });\n }\n catch (error)\n {\n const duration = Date.now() - startTime;\n jobLogger.error(`[Job:${job.name}] Failed after ${duration}ms`, {\n jobId: pgBossJob.id,\n duration,\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n }\n }\n );\n}\n\n/**\n * Connect event to pg-boss queue\n */\nfunction connectEventToQueue(\n boss: PgBoss,\n job: JobDef<any>,\n queueName: string\n): void\n{\n if (!job._subscribedEventDef)\n {\n return;\n }\n\n const eventDef = job._subscribedEventDef as EventDef<any>;\n eventDef._registerJobQueue(queueName, async (queue, payload) =>\n {\n await boss.send(queue, payload as object, getDefaultJobOptions(job.options));\n });\n\n jobLogger.debug(`[Job:${job.name}] Connected to event: ${job.subscribedEvent}`);\n}\n\n/**\n * Register cron schedule for a job\n */\nasync function registerCronSchedule(boss: PgBoss, job: JobDef<any>): Promise<void>\n{\n if (!job.cronExpression)\n {\n return;\n }\n\n jobLogger.debug(`[Job:${job.name}] Scheduling cron: ${job.cronExpression}`);\n\n await boss.schedule(\n job.name,\n job.cronExpression,\n {},\n getDefaultJobOptions(job.options)\n );\n\n jobLogger.info(`[Job:${job.name}] Cron scheduled: ${job.cronExpression}`);\n}\n\n/**\n * Queue a runOnce job\n */\nasync function queueRunOnceJob(boss: PgBoss, job: JobDef<any>): Promise<void>\n{\n if (!job.runOnce)\n {\n return;\n }\n\n jobLogger.debug(`[Job:${job.name}] Queuing runOnce job`);\n\n await boss.send(\n job.name,\n {},\n {\n ...getDefaultJobOptions(job.options),\n singletonKey: `runOnce:${job.name}`,\n }\n );\n\n jobLogger.info(`[Job:${job.name}] runOnce job queued`);\n}\n\n/**\n * Register a single job with pg-boss\n */\nasync function registerJob(job: JobDef<any>): Promise<void>\n{\n const boss = getBoss();\n if (!boss)\n {\n throw new Error('pg-boss not initialized');\n }\n\n const queueName = job.subscribedEvent\n ? getEventQueueName(job.subscribedEvent)\n : job.name;\n\n jobLogger.debug(`Registering job: ${job.name}`, {\n queueName,\n subscribedEvent: job.subscribedEvent,\n });\n\n await registerWorker(boss, job, queueName);\n connectEventToQueue(boss, job, queueName);\n await registerCronSchedule(boss, job);\n await queueRunOnceJob(boss, job);\n\n jobLogger.debug(`Job registered: ${job.name}`);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/job/boss.ts","../../src/job/job-builder.ts","../../src/job/job-router.ts","../../src/job/register-jobs.ts"],"names":["jobLogger","logger","job"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * pg-boss Wrapper\n *\n * Manages pg-boss instance lifecycle\n */\n\nimport PgBoss from 'pg-boss';\nimport { logger } from '@spfn/core/logger';\n\nconst jobLogger = logger.child('@spfn/core:job');\n\n/**\n * Global pg-boss instance\n */\nlet bossInstance: PgBoss | null = null;\n\n/**\n * Stored config for access during registration\n */\nlet bossConfig: BossConfig | null = null;\n\n/**\n * Options for pg-boss initialization\n *\n * @example\n * ```typescript\n * await initBoss({\n * connectionString: process.env.DATABASE_URL,\n * schema: 'spfn_queue',\n * clearOnStart: process.env.NODE_ENV === 'development',\n * });\n * ```\n */\nexport interface BossOptions\n{\n /**\n * PostgreSQL connection string\n *\n * @example 'postgresql://user:password@localhost:5432/mydb'\n */\n connectionString: string;\n\n /**\n * Schema name for pg-boss tables\n *\n * pg-boss creates its own tables in this schema.\n *\n * @default 'spfn_queue'\n */\n schema?: string;\n\n /**\n * Maintenance interval in seconds\n *\n * pg-boss runs maintenance tasks (cleanup, archiving) at this interval.\n *\n * @default 120\n */\n maintenanceIntervalSeconds?: number;\n\n /**\n * Monitor state changes interval in seconds\n *\n * When set, pg-boss emits state change events at this interval.\n *\n * @default undefined (disabled)\n */\n monitorIntervalSeconds?: number;\n\n /**\n * Clear all pending/scheduled jobs on startup\n *\n * Useful for development mode to start with a clean queue.\n * Should be false in production.\n *\n * @default false\n */\n clearOnStart?: boolean;\n}\n\n/**\n * @deprecated Use BossOptions instead\n */\nexport type BossConfig = BossOptions;\n\n/**\n * Initialize pg-boss with the given configuration\n *\n * Must be called before registerJobs(). Typically handled by defineServerConfig().\n *\n * @param options - pg-boss configuration options\n * @returns The pg-boss instance\n *\n * @example\n * ```typescript\n * const boss = await initBoss({\n * connectionString: process.env.DATABASE_URL!,\n * schema: 'spfn_queue',\n * });\n * ```\n */\nexport async function initBoss(options: BossOptions): Promise<PgBoss>\n{\n if (bossInstance)\n {\n jobLogger.warn('pg-boss already initialized, returning existing instance');\n return bossInstance;\n }\n\n jobLogger.info('Initializing pg-boss...');\n\n bossConfig = options;\n\n const pgBossOptions: PgBoss.ConstructorOptions = {\n connectionString: options.connectionString,\n schema: options.schema ?? 'spfn_queue',\n maintenanceIntervalSeconds: options.maintenanceIntervalSeconds ?? 120,\n };\n\n // Only set monitorIntervalSeconds if explicitly provided (must be >= 1)\n if (options.monitorIntervalSeconds !== undefined && options.monitorIntervalSeconds >= 1)\n {\n pgBossOptions.monitorIntervalSeconds = options.monitorIntervalSeconds;\n }\n\n bossInstance = new PgBoss(pgBossOptions);\n\n // Event handlers\n bossInstance.on('error', (error) =>\n {\n jobLogger.error('pg-boss error:', error);\n });\n\n await bossInstance.start();\n\n jobLogger.info('pg-boss started successfully');\n\n return bossInstance;\n}\n\n/**\n * Get the current pg-boss instance\n */\nexport function getBoss(): PgBoss | null\n{\n return bossInstance;\n}\n\n/**\n * Stop pg-boss gracefully\n */\nexport async function stopBoss(): Promise<void>\n{\n if (!bossInstance)\n {\n return;\n }\n\n jobLogger.info('Stopping pg-boss...');\n\n try\n {\n await bossInstance.stop({ graceful: true, timeout: 30000 });\n jobLogger.info('pg-boss stopped gracefully');\n }\n catch (error)\n {\n jobLogger.error('Error stopping pg-boss:', error);\n throw error;\n }\n finally\n {\n bossInstance = null;\n bossConfig = null;\n }\n}\n\n/**\n * Check if pg-boss is initialized and running\n */\nexport function isBossRunning(): boolean\n{\n return bossInstance !== null;\n}\n\n/**\n * Check if jobs should be cleared on start\n */\nexport function shouldClearOnStart(): boolean\n{\n return bossConfig?.clearOnStart ?? false;\n}\n","/**\n * Job Builder\n *\n * Fluent API for defining jobs, similar to route builder pattern\n */\n\nimport type { Static, TSchema } from '@sinclair/typebox';\nimport type { CompensateHandler, JobDef, JobHandler, JobOptions, JobSendOptions } from './types';\nimport type { EventDef, InferEventPayload } from '@spfn/core/event';\nimport { getBoss } from './boss';\n\n/**\n * Build pg-boss options from job defaults and send options\n */\nfunction buildPgBossOptions(\n defaults?: JobOptions,\n sendOptions?: JobSendOptions\n): Record<string, unknown>\n{\n const options: Record<string, unknown> = {};\n\n // Send options (per-job invocation)\n if (sendOptions?.startAfter)\n {\n options.startAfter = sendOptions.startAfter;\n }\n if (sendOptions?.singletonKey)\n {\n options.singletonKey = sendOptions.singletonKey;\n }\n if (sendOptions?.priority !== undefined)\n {\n options.priority = sendOptions.priority;\n }\n\n // Default options (from job definition)\n if (defaults?.retryLimit !== undefined)\n {\n options.retryLimit = defaults.retryLimit;\n }\n if (defaults?.retryDelay !== undefined)\n {\n options.retryDelay = defaults.retryDelay;\n }\n if (defaults?.expireInSeconds !== undefined)\n {\n options.expireInSeconds = defaults.expireInSeconds;\n }\n if (defaults?.priority !== undefined && sendOptions?.priority === undefined)\n {\n options.priority = defaults.priority;\n }\n if (defaults?.singletonKey && !sendOptions?.singletonKey)\n {\n options.singletonKey = defaults.singletonKey;\n }\n if (defaults?.retentionSeconds !== undefined)\n {\n options.retentionSeconds = defaults.retentionSeconds;\n }\n\n return options;\n}\n\n/**\n * Job builder class with fluent API\n */\nexport class JobBuilder<TInput = void, TOutput = void>\n{\n private readonly _name: string;\n private _inputSchema?: TSchema;\n private _outputSchema?: TSchema;\n private _cronExpression?: string;\n private _runOnce?: boolean;\n private _subscribedEvent?: string;\n private _subscribedEventDef?: EventDef<any>;\n private _options?: JobOptions;\n private _handler?: JobHandler<TInput, TOutput>;\n private _compensate?: CompensateHandler<TInput, TOutput>;\n\n constructor(name: string)\n {\n this._name = name;\n }\n\n /**\n * Define input schema with TypeBox\n */\n input<TSchema extends import('@sinclair/typebox').TSchema>(\n schema: TSchema\n ): JobBuilder<Static<TSchema>, TOutput>\n {\n const builder = new JobBuilder<Static<TSchema>, TOutput>(this._name);\n builder._inputSchema = schema;\n builder._outputSchema = this._outputSchema;\n builder._cronExpression = this._cronExpression;\n builder._runOnce = this._runOnce;\n builder._subscribedEvent = this._subscribedEvent;\n builder._options = this._options;\n return builder;\n }\n\n /**\n * Define output schema with TypeBox (for workflow integration)\n */\n output<TSchema extends import('@sinclair/typebox').TSchema>(\n schema: TSchema\n ): JobBuilder<TInput, Static<TSchema>>\n {\n const builder = new JobBuilder<TInput, Static<TSchema>>(this._name);\n builder._inputSchema = this._inputSchema;\n builder._outputSchema = schema;\n builder._cronExpression = this._cronExpression;\n builder._runOnce = this._runOnce;\n builder._subscribedEvent = this._subscribedEvent;\n builder._options = this._options;\n return builder;\n }\n\n /**\n * Subscribe to an event (decoupled triggering)\n *\n * @example\n * ```typescript\n * const userCreated = defineEvent('user.created', Type.Object({\n * userId: Type.String(),\n * }));\n *\n * const sendWelcomeEmail = job('send-welcome-email')\n * .on(userCreated)\n * .handler(async (payload) => {\n * // payload is typed as { userId: string }\n * });\n * ```\n */\n on<TEvent extends EventDef<any>>(\n event: TEvent\n ): JobBuilder<InferEventPayload<TEvent>, TOutput>\n {\n const builder = new JobBuilder<InferEventPayload<TEvent>, TOutput>(this._name);\n builder._inputSchema = event.schema;\n builder._outputSchema = this._outputSchema;\n builder._subscribedEvent = event.name;\n builder._subscribedEventDef = event;\n builder._cronExpression = this._cronExpression;\n builder._runOnce = this._runOnce;\n builder._options = this._options;\n return builder;\n }\n\n /**\n * Set cron expression for scheduled execution\n */\n cron(expression: string): this\n {\n this._cronExpression = expression;\n return this;\n }\n\n /**\n * Mark job to run once on server start\n */\n runOnce(): this\n {\n this._runOnce = true;\n return this;\n }\n\n /**\n * Set job options (retry, expiration, etc.)\n */\n options(options: JobOptions): this\n {\n this._options = options;\n return this;\n }\n\n /**\n * Set job timeout in milliseconds\n * (Converts to expireInSeconds for pg-boss)\n */\n timeout(ms: number): this\n {\n this._options = {\n ...this._options,\n expireInSeconds: Math.ceil(ms / 1000),\n };\n return this;\n }\n\n /**\n * Define compensate handler for rollback (workflow integration)\n */\n compensate(fn: CompensateHandler<TInput, TOutput>): this\n {\n this._compensate = fn;\n return this;\n }\n\n /**\n * Define the job handler and finalize the job definition\n */\n handler(fn: JobHandler<TInput, TOutput>): JobDef<TInput, TOutput>\n {\n this._handler = fn;\n\n const name = this._name;\n const inputSchema = this._inputSchema;\n const outputSchema = this._outputSchema;\n const cronExpression = this._cronExpression;\n const runOnce = this._runOnce;\n const subscribedEvent = this._subscribedEvent;\n const subscribedEventDef = this._subscribedEventDef;\n const options = this._options;\n const handler = this._handler;\n const compensate = this._compensate;\n\n // Create send function\n const send = async (\n inputOrOptions?: TInput | JobSendOptions,\n maybeOptions?: JobSendOptions\n ): Promise<string | null> =>\n {\n const boss = getBoss();\n if (!boss)\n {\n throw new Error(\n `[Job:${name}] pg-boss not initialized. ` +\n 'Ensure jobs are registered with defineServerConfig().jobs()'\n );\n }\n\n // Determine input and options based on whether job has input schema\n const [input, sendOptions] = inputSchema\n ? [inputOrOptions as TInput, maybeOptions]\n : [undefined, inputOrOptions as JobSendOptions | undefined];\n\n return await boss.send(\n name,\n input ?? {},\n buildPgBossOptions(options, sendOptions)\n );\n };\n\n // Create run function (synchronous execution)\n const run = async (input?: TInput): Promise<TOutput> =>\n {\n if (inputSchema)\n {\n return await (handler as (input: TInput) => Promise<TOutput>)(input as TInput);\n }\n else\n {\n return await (handler as () => Promise<TOutput>)();\n }\n };\n\n return {\n name,\n inputSchema,\n outputSchema,\n cronExpression,\n runOnce,\n subscribedEvent,\n _subscribedEventDef: subscribedEventDef,\n options,\n handler,\n compensate,\n send: send as JobDef<TInput, TOutput>['send'],\n run: run as JobDef<TInput, TOutput>['run'],\n _input: undefined as unknown as TInput,\n _output: undefined as unknown as TOutput,\n };\n }\n}\n\n/**\n * Create a new job definition\n *\n * @example\n * ```typescript\n * // Simple job without input\n * export const cleanupJob = job('cleanup')\n * .handler(async () => {\n * await db.cleanup();\n * });\n *\n * // Job with typed input\n * export const sendEmailJob = job('send-email')\n * .input(Type.Object({\n * to: Type.String(),\n * subject: Type.String(),\n * body: Type.String(),\n * }))\n * .handler(async (input) => {\n * await emailService.send(input.to, input.subject, input.body);\n * });\n *\n * // Cron job\n * export const dailyReportJob = job('daily-report')\n * .cron('0 9 * * *')\n * .handler(async () => {\n * await reportService.generateDaily();\n * });\n *\n * // Run once on server start\n * export const initCacheJob = job('init-cache')\n * .runOnce()\n * .handler(async () => {\n * await cache.warmup();\n * });\n *\n * // With options\n * export const importantJob = job('important-task')\n * .input(Type.Object({ id: Type.String() }))\n * .options({\n * retryLimit: 5,\n * retryDelay: 5000,\n * priority: 10,\n * })\n * .handler(async (input) => {\n * await processImportant(input.id);\n * });\n * ```\n */\nexport function job(name: string): JobBuilder<void>\n{\n return new JobBuilder(name);\n}\n","/**\n * Job Router\n *\n * Groups job definitions for registration with the server\n */\n\nimport type { JobDef, JobRouter, JobRouterEntry } from './types';\n\n/**\n * Type guard to check if value is a JobDef\n */\nexport function isJobDef(value: unknown): value is JobDef<any>\n{\n return (\n value !== null &&\n typeof value === 'object' &&\n 'name' in value &&\n 'handler' in value &&\n 'send' in value &&\n 'run' in value\n );\n}\n\n/**\n * Type guard to check if value is a JobRouter\n */\nexport function isJobRouter(value: unknown): value is JobRouter<any>\n{\n return (\n value !== null &&\n typeof value === 'object' &&\n 'jobs' in value &&\n '_jobs' in value\n );\n}\n\n/**\n * Define a job router to group jobs together\n *\n * @example\n * ```typescript\n * // Flat structure\n * export const jobRouter = defineJobRouter({\n * sendWelcomeEmail,\n * dailyReport,\n * initCache,\n * });\n *\n * // Nested structure\n * export const jobRouter = defineJobRouter({\n * email: defineJobRouter({\n * sendWelcome: sendWelcomeEmailJob,\n * sendReset: sendResetPasswordJob,\n * }),\n * reports: defineJobRouter({\n * daily: dailyReportJob,\n * weekly: weeklyReportJob,\n * }),\n * });\n *\n * // Mixed\n * export const jobRouter = defineJobRouter({\n * initCache, // flat\n * email: defineJobRouter({ ... }), // nested\n * });\n * ```\n */\nexport function defineJobRouter<\n TJobs extends Record<string, JobRouterEntry>\n>(jobs: TJobs): JobRouter<TJobs>\n{\n return {\n jobs,\n _jobs: jobs,\n };\n}\n\n/**\n * Collect all JobDefs from a JobRouter (including nested)\n */\nexport function collectJobs(\n router: JobRouter<any>,\n prefix = ''\n): JobDef<any>[]\n{\n const jobs: JobDef<any>[] = [];\n\n for (const [key, value] of Object.entries(router.jobs))\n {\n const name = prefix ? `${prefix}.${key}` : key;\n\n if (isJobRouter(value))\n {\n // Nested router - recurse\n jobs.push(...collectJobs(value, name));\n }\n else if (isJobDef(value))\n {\n jobs.push(value);\n }\n }\n\n return jobs;\n}\n","/**\n * Job Registration\n *\n * Registers jobs with pg-boss\n */\n\nimport type PgBoss from 'pg-boss';\nimport { logger } from '@spfn/core/logger';\nimport type { JobDef, JobOptions, JobRouter } from './types';\nimport type { EventDef } from '@spfn/core/event';\nimport { collectJobs } from './job-router';\nimport { getBoss, shouldClearOnStart } from './boss';\n\nconst jobLogger = logger.child('@spfn/core:job');\n\n/**\n * Get the pg-boss queue name for an event\n */\nexport function getEventQueueName(eventName: string): string\n{\n return `event:${eventName}`;\n}\n\n/**\n * Build default pg-boss options for a job\n */\nfunction getDefaultJobOptions(options?: JobOptions): PgBoss.SendOptions\n{\n return {\n retryLimit: options?.retryLimit ?? 3,\n retryDelay: options?.retryDelay ?? 1000,\n expireInSeconds: options?.expireInSeconds ?? 300,\n };\n}\n\n/**\n * Register all jobs from a JobRouter with pg-boss\n *\n * This function:\n * 1. Collects all jobs from the router (including nested routers)\n * 2. Optionally clears existing jobs (if clearOnStart is enabled)\n * 3. Registers each job's worker handler with pg-boss\n * 4. Sets up cron schedules for scheduled jobs\n * 5. Queues runOnce jobs\n * 6. Connects event subscriptions to job queues\n *\n * @param router - JobRouter containing job definitions\n *\n * @example\n * ```typescript\n * // Define jobs\n * const sendEmail = job('send-email')\n * .input(Type.Object({ to: Type.String() }))\n * .handler(async (input) => { ... });\n *\n * const dailyReport = job('daily-report')\n * .cron('0 9 * * *')\n * .handler(async () => { ... });\n *\n * // Create router\n * const jobRouter = defineJobRouter({ sendEmail, dailyReport });\n *\n * // Initialize pg-boss first\n * await initBoss({ connectionString: process.env.DATABASE_URL! });\n *\n * // Register jobs\n * await registerJobs(jobRouter);\n * ```\n */\nexport async function registerJobs(router: JobRouter<any>): Promise<void>\n{\n const boss = getBoss();\n if (!boss)\n {\n throw new Error(\n 'pg-boss not initialized. Call initBoss() before registerJobs()'\n );\n }\n\n const jobs = collectJobs(router);\n const clearOnStart = shouldClearOnStart();\n\n jobLogger.info(`Registering ${jobs.length} job(s)...`);\n\n // Clear existing jobs if requested (useful for development)\n if (clearOnStart)\n {\n jobLogger.info('Clearing existing jobs before registration...');\n for (const job of jobs)\n {\n // Clear job queue\n await boss.deleteAllJobs(job.name);\n\n // Also clear event queue if subscribed\n if (job.subscribedEvent)\n {\n const eventQueue = getEventQueueName(job.subscribedEvent);\n await boss.deleteAllJobs(eventQueue);\n }\n }\n jobLogger.info('Existing jobs cleared');\n }\n\n for (const job of jobs)\n {\n await registerJob(job);\n }\n\n jobLogger.info('All jobs registered successfully');\n}\n\n/**\n * Create queue if not exists (required for pg-boss v11+)\n */\nasync function ensureQueue(boss: PgBoss, queueName: string): Promise<void>\n{\n await boss.createQueue(queueName);\n}\n\n/**\n * Register worker handler for a job\n */\nasync function registerWorker(\n boss: PgBoss,\n job: JobDef<any>,\n queueName: string\n): Promise<void>\n{\n // Ensure queue exists before registering worker\n await ensureQueue(boss, queueName);\n\n await boss.work(\n queueName,\n { batchSize: 1 },\n async (jobs) =>\n {\n for (const pgBossJob of jobs)\n {\n jobLogger.debug(`[Job:${job.name}] Executing...`, { jobId: pgBossJob.id });\n\n const startTime = Date.now();\n\n try\n {\n if (job.inputSchema)\n {\n await (job.handler as (input: unknown) => Promise<void>)(pgBossJob.data);\n }\n else\n {\n await (job.handler as () => Promise<void>)();\n }\n\n const duration = Date.now() - startTime;\n jobLogger.info(`[Job:${job.name}] Completed in ${duration}ms`, {\n jobId: pgBossJob.id,\n duration,\n });\n }\n catch (error)\n {\n const duration = Date.now() - startTime;\n jobLogger.error(`[Job:${job.name}] Failed after ${duration}ms`, {\n jobId: pgBossJob.id,\n duration,\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n }\n }\n );\n}\n\n/**\n * Connect event to pg-boss queue\n */\nfunction connectEventToQueue(\n boss: PgBoss,\n job: JobDef<any>,\n queueName: string\n): void\n{\n if (!job._subscribedEventDef)\n {\n return;\n }\n\n const eventDef = job._subscribedEventDef as EventDef<any>;\n eventDef._registerJobQueue(queueName, async (queue, payload) =>\n {\n await boss.send(queue, payload as object, getDefaultJobOptions(job.options));\n });\n\n jobLogger.debug(`[Job:${job.name}] Connected to event: ${job.subscribedEvent}`);\n}\n\n/**\n * Register cron schedule for a job\n */\nasync function registerCronSchedule(boss: PgBoss, job: JobDef<any>): Promise<void>\n{\n if (!job.cronExpression)\n {\n return;\n }\n\n jobLogger.debug(`[Job:${job.name}] Scheduling cron: ${job.cronExpression}`);\n\n // Ensure queue exists for cron jobs (uses job.name as queue)\n await ensureQueue(boss, job.name);\n\n await boss.schedule(\n job.name,\n job.cronExpression,\n {},\n getDefaultJobOptions(job.options)\n );\n\n jobLogger.info(`[Job:${job.name}] Cron scheduled: ${job.cronExpression}`);\n}\n\n/**\n * Queue a runOnce job\n */\nasync function queueRunOnceJob(boss: PgBoss, job: JobDef<any>): Promise<void>\n{\n if (!job.runOnce)\n {\n return;\n }\n\n jobLogger.debug(`[Job:${job.name}] Queuing runOnce job`);\n\n // Ensure queue exists for runOnce jobs (uses job.name as queue)\n await ensureQueue(boss, job.name);\n\n await boss.send(\n job.name,\n {},\n {\n ...getDefaultJobOptions(job.options),\n singletonKey: `runOnce:${job.name}`,\n }\n );\n\n jobLogger.info(`[Job:${job.name}] runOnce job queued`);\n}\n\n/**\n * Register a single job with pg-boss\n */\nasync function registerJob(job: JobDef<any>): Promise<void>\n{\n const boss = getBoss();\n if (!boss)\n {\n throw new Error('pg-boss not initialized');\n }\n\n const queueName = job.subscribedEvent\n ? getEventQueueName(job.subscribedEvent)\n : job.name;\n\n jobLogger.debug(`Registering job: ${job.name}`, {\n queueName,\n subscribedEvent: job.subscribedEvent,\n });\n\n await registerWorker(boss, job, queueName);\n connectEventToQueue(boss, job, queueName);\n await registerCronSchedule(boss, job);\n await queueRunOnceJob(boss, job);\n\n jobLogger.debug(`Job registered: ${job.name}`);\n}\n"]}
|
|
@@ -6,46 +6,159 @@ import { Context, Next } from 'hono';
|
|
|
6
6
|
* Handles SerializableError with automatic serialization and standard errors
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Options for ErrorHandler middleware
|
|
11
|
+
*/
|
|
9
12
|
interface ErrorHandlerOptions {
|
|
10
13
|
/**
|
|
11
|
-
* Include stack trace in response
|
|
14
|
+
* Include stack trace in error response
|
|
15
|
+
*
|
|
16
|
+
* Useful for debugging in development, should be disabled in production.
|
|
17
|
+
*
|
|
12
18
|
* @default env.NODE_ENV !== 'production'
|
|
13
19
|
*/
|
|
14
20
|
includeStack?: boolean;
|
|
15
21
|
/**
|
|
16
|
-
* Enable error logging
|
|
22
|
+
* Enable error logging to console
|
|
23
|
+
*
|
|
24
|
+
* Logs errors with appropriate level (warn for 4xx, error for 5xx).
|
|
25
|
+
*
|
|
17
26
|
* @default true
|
|
18
27
|
*/
|
|
19
28
|
enableLogging?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Callback invoked when an error occurs
|
|
31
|
+
*
|
|
32
|
+
* Called asynchronously without blocking the response.
|
|
33
|
+
* Useful for external error notifications (Slack, PagerDuty, etc.)
|
|
34
|
+
*/
|
|
35
|
+
onError?: (err: Error, context: OnErrorContext) => Promise<void> | void;
|
|
20
36
|
}
|
|
21
37
|
/**
|
|
22
|
-
*
|
|
38
|
+
* Context passed to onError callback
|
|
39
|
+
*/
|
|
40
|
+
interface OnErrorContext {
|
|
41
|
+
statusCode: number;
|
|
42
|
+
path: string;
|
|
43
|
+
method: string;
|
|
44
|
+
requestId?: string;
|
|
45
|
+
timestamp: string;
|
|
46
|
+
userId?: string;
|
|
47
|
+
request: {
|
|
48
|
+
headers: Record<string, string>;
|
|
49
|
+
query: Record<string, string>;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Error handler middleware for Hono
|
|
54
|
+
*
|
|
55
|
+
* Handles SerializableError with automatic serialization and standard errors.
|
|
56
|
+
* SerializableError instances are serialized using their toJSON() method,
|
|
57
|
+
* preserving custom fields like `resource`, `fields`, etc.
|
|
58
|
+
*
|
|
59
|
+
* @param options - Configuration options
|
|
60
|
+
* @returns Error handler function for Hono's onError hook
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* import { Hono } from 'hono';
|
|
65
|
+
* import { ErrorHandler } from '@spfn/core/middleware';
|
|
66
|
+
*
|
|
67
|
+
* const app = new Hono();
|
|
23
68
|
*
|
|
24
|
-
*
|
|
69
|
+
* // Register error handler
|
|
70
|
+
* app.onError(ErrorHandler({
|
|
71
|
+
* includeStack: process.env.NODE_ENV !== 'production',
|
|
72
|
+
* enableLogging: true,
|
|
73
|
+
* }));
|
|
74
|
+
*
|
|
75
|
+
* // Throw SerializableError in routes
|
|
76
|
+
* app.get('/users/:id', (c) => {
|
|
77
|
+
* throw new NotFoundError({ message: 'User not found', resource: 'User' });
|
|
78
|
+
* // Response: { __type: 'NotFoundError', message: 'User not found', resource: 'User' }
|
|
79
|
+
* });
|
|
80
|
+
* ```
|
|
25
81
|
*/
|
|
26
82
|
declare function ErrorHandler(options?: ErrorHandlerOptions): (err: Error, c: Context) => Response | Promise<Response>;
|
|
27
83
|
|
|
28
|
-
|
|
84
|
+
/**
|
|
85
|
+
* Options for RequestLogger middleware
|
|
86
|
+
*/
|
|
87
|
+
interface RequestLoggerOptions {
|
|
29
88
|
/**
|
|
30
|
-
* Paths to exclude from logging
|
|
89
|
+
* Paths to exclude from logging
|
|
90
|
+
*
|
|
91
|
+
* Supports exact match and prefix match (e.g., '/health' excludes '/health/db').
|
|
92
|
+
*
|
|
93
|
+
* @default ['/health', '/ping', '/favicon.ico']
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```typescript
|
|
97
|
+
* excludePaths: ['/health', '/metrics', '/_next']
|
|
98
|
+
* ```
|
|
31
99
|
*/
|
|
32
100
|
excludePaths?: string[];
|
|
33
101
|
/**
|
|
34
|
-
* Field names to mask
|
|
102
|
+
* Field names to mask in logged request bodies
|
|
103
|
+
*
|
|
104
|
+
* Case-insensitive partial matching (e.g., 'password' masks 'userPassword').
|
|
105
|
+
*
|
|
106
|
+
* @default ['password', 'token', 'apiKey', 'secret', 'authorization']
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```typescript
|
|
110
|
+
* sensitiveFields: ['password', 'creditCard', 'ssn']
|
|
111
|
+
* ```
|
|
35
112
|
*/
|
|
36
113
|
sensitiveFields?: string[];
|
|
37
114
|
/**
|
|
38
|
-
*
|
|
115
|
+
* Threshold in milliseconds for marking requests as slow
|
|
116
|
+
*
|
|
117
|
+
* Slow requests are logged with `slow: true` flag.
|
|
118
|
+
*
|
|
119
|
+
* @default 1000
|
|
39
120
|
*/
|
|
40
121
|
slowRequestThreshold?: number;
|
|
41
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* @deprecated Use RequestLoggerOptions instead
|
|
125
|
+
*/
|
|
126
|
+
type RequestLoggerConfig = RequestLoggerOptions;
|
|
42
127
|
/**
|
|
43
128
|
* Mask sensitive data with circular reference handling
|
|
44
129
|
*/
|
|
45
130
|
declare function maskSensitiveData(obj: any, sensitiveFields: string[], seen?: WeakSet<object>): any;
|
|
46
131
|
/**
|
|
47
|
-
* Request
|
|
132
|
+
* Request logger middleware for Hono
|
|
133
|
+
*
|
|
134
|
+
* Logs incoming requests with method, path, IP, and user agent.
|
|
135
|
+
* Logs completed requests with status code and duration.
|
|
136
|
+
* Automatically generates unique request IDs and masks sensitive data.
|
|
137
|
+
*
|
|
138
|
+
* @param options - Configuration options
|
|
139
|
+
* @returns Hono middleware function
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```typescript
|
|
143
|
+
* import { Hono } from 'hono';
|
|
144
|
+
* import { RequestLogger } from '@spfn/core/middleware';
|
|
145
|
+
*
|
|
146
|
+
* const app = new Hono();
|
|
147
|
+
*
|
|
148
|
+
* // Add request logging
|
|
149
|
+
* app.use(RequestLogger({
|
|
150
|
+
* excludePaths: ['/health', '/metrics'],
|
|
151
|
+
* sensitiveFields: ['password', 'token'],
|
|
152
|
+
* slowRequestThreshold: 2000,
|
|
153
|
+
* }));
|
|
154
|
+
*
|
|
155
|
+
* // Access request ID in handlers
|
|
156
|
+
* app.get('/users', (c) => {
|
|
157
|
+
* const requestId = c.get('requestId');
|
|
158
|
+
* return c.json({ requestId });
|
|
159
|
+
* });
|
|
160
|
+
* ```
|
|
48
161
|
*/
|
|
49
|
-
declare function RequestLogger(
|
|
162
|
+
declare function RequestLogger(options?: RequestLoggerOptions): (c: Context, next: Next) => Promise<void>;
|
|
50
163
|
|
|
51
|
-
export { ErrorHandler, type ErrorHandlerOptions, RequestLogger, type RequestLoggerConfig, maskSensitiveData };
|
|
164
|
+
export { ErrorHandler, type ErrorHandlerOptions, type OnErrorContext, RequestLogger, type RequestLoggerConfig, type RequestLoggerOptions, maskSensitiveData };
|
package/dist/middleware/index.js
CHANGED
|
@@ -5,6 +5,29 @@ import { randomBytes } from 'crypto';
|
|
|
5
5
|
|
|
6
6
|
// src/middleware/error-handler.ts
|
|
7
7
|
var errorLogger = logger.child("@spfn/core:error-handler");
|
|
8
|
+
var SENSITIVE_HEADERS = /* @__PURE__ */ new Set(["authorization", "cookie", "x-api-key", "x-auth-token"]);
|
|
9
|
+
function extractHeaders(c) {
|
|
10
|
+
const headers = {};
|
|
11
|
+
c.req.raw.headers.forEach((value, key) => {
|
|
12
|
+
headers[key] = SENSITIVE_HEADERS.has(key.toLowerCase()) ? "***" : value;
|
|
13
|
+
});
|
|
14
|
+
return headers;
|
|
15
|
+
}
|
|
16
|
+
function buildOnErrorContext(c, statusCode) {
|
|
17
|
+
const auth = c.get("auth");
|
|
18
|
+
return {
|
|
19
|
+
statusCode,
|
|
20
|
+
path: c.req.path,
|
|
21
|
+
method: c.req.method,
|
|
22
|
+
requestId: c.get("requestId"),
|
|
23
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
24
|
+
userId: auth?.userId,
|
|
25
|
+
request: {
|
|
26
|
+
headers: extractHeaders(c),
|
|
27
|
+
query: c.req.query()
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
8
31
|
function logError(err, logData, includeStack) {
|
|
9
32
|
const logLevel = logData.statusCode >= 500 ? "error" : "warn";
|
|
10
33
|
if (includeStack) {
|
|
@@ -19,9 +42,12 @@ function isSerializableError(err) {
|
|
|
19
42
|
function ErrorHandler(options = {}) {
|
|
20
43
|
const {
|
|
21
44
|
includeStack = env.NODE_ENV !== "production",
|
|
22
|
-
enableLogging = true
|
|
45
|
+
enableLogging = true,
|
|
46
|
+
onError
|
|
23
47
|
} = options;
|
|
24
48
|
return (err, c) => {
|
|
49
|
+
const path = c.req.path;
|
|
50
|
+
const method = c.req.method;
|
|
25
51
|
if (isSerializableError(err)) {
|
|
26
52
|
const { statusCode: statusCode2 } = err;
|
|
27
53
|
if (enableLogging) {
|
|
@@ -29,10 +55,14 @@ function ErrorHandler(options = {}) {
|
|
|
29
55
|
type: err.constructor.name,
|
|
30
56
|
message: err.message,
|
|
31
57
|
statusCode: statusCode2,
|
|
32
|
-
path
|
|
33
|
-
method
|
|
58
|
+
path,
|
|
59
|
+
method
|
|
34
60
|
}, includeStack);
|
|
35
61
|
}
|
|
62
|
+
if (onError) {
|
|
63
|
+
const ctx = buildOnErrorContext(c, statusCode2);
|
|
64
|
+
Promise.resolve(onError(err, ctx)).catch((e) => errorLogger.warn("onError callback failed", e));
|
|
65
|
+
}
|
|
36
66
|
const serialized = err.toJSON();
|
|
37
67
|
if (includeStack && err.stack) {
|
|
38
68
|
serialized.stack = err.stack;
|
|
@@ -46,10 +76,14 @@ function ErrorHandler(options = {}) {
|
|
|
46
76
|
type: err.name || "Error",
|
|
47
77
|
message: err.message,
|
|
48
78
|
statusCode,
|
|
49
|
-
path
|
|
50
|
-
method
|
|
79
|
+
path,
|
|
80
|
+
method
|
|
51
81
|
}, includeStack);
|
|
52
82
|
}
|
|
83
|
+
if (onError) {
|
|
84
|
+
const ctx = buildOnErrorContext(c, statusCode);
|
|
85
|
+
Promise.resolve(onError(err, ctx)).catch((e) => errorLogger.warn("onError callback failed", e));
|
|
86
|
+
}
|
|
53
87
|
const response = {
|
|
54
88
|
__type: "Error",
|
|
55
89
|
message: err.message || "Internal Server Error"
|
|
@@ -86,8 +120,8 @@ function maskSensitiveData(obj, sensitiveFields, seen = /* @__PURE__ */ new Weak
|
|
|
86
120
|
}
|
|
87
121
|
return masked;
|
|
88
122
|
}
|
|
89
|
-
function RequestLogger(
|
|
90
|
-
const cfg = { ...DEFAULT_CONFIG, ...
|
|
123
|
+
function RequestLogger(options) {
|
|
124
|
+
const cfg = { ...DEFAULT_CONFIG, ...options };
|
|
91
125
|
const apiLogger = logger.child("@spfn/core:api");
|
|
92
126
|
return async (c, next) => {
|
|
93
127
|
const path = new URL(c.req.url).pathname;
|