@spfn/core 0.1.0-alpha.86 → 0.2.0-beta.1
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 +1046 -384
- package/dist/boss-D-fGtVgM.d.ts +187 -0
- package/dist/cache/index.d.ts +13 -33
- package/dist/cache/index.js +14 -703
- package/dist/cache/index.js.map +1 -1
- package/dist/codegen/index.d.ts +167 -17
- package/dist/codegen/index.js +76 -1419
- package/dist/codegen/index.js.map +1 -1
- package/dist/config/index.d.ts +1191 -0
- package/dist/config/index.js +264 -0
- package/dist/config/index.js.map +1 -0
- package/dist/db/index.d.ts +728 -59
- package/dist/db/index.js +1028 -1225
- package/dist/db/index.js.map +1 -1
- package/dist/env/index.d.ts +579 -308
- package/dist/env/index.js +438 -930
- package/dist/env/index.js.map +1 -1
- package/dist/errors/index.d.ts +417 -29
- package/dist/errors/index.js +359 -98
- package/dist/errors/index.js.map +1 -1
- package/dist/event/index.d.ts +108 -0
- package/dist/event/index.js +122 -0
- package/dist/event/index.js.map +1 -0
- package/dist/job/index.d.ts +172 -0
- package/dist/job/index.js +361 -0
- package/dist/job/index.js.map +1 -0
- package/dist/logger/index.d.ts +20 -79
- package/dist/logger/index.js +82 -387
- package/dist/logger/index.js.map +1 -1
- package/dist/middleware/index.d.ts +2 -11
- package/dist/middleware/index.js +49 -703
- package/dist/middleware/index.js.map +1 -1
- package/dist/nextjs/index.d.ts +120 -0
- package/dist/nextjs/index.js +416 -0
- package/dist/nextjs/index.js.map +1 -0
- package/dist/{client/nextjs/index.d.ts → nextjs/server.d.ts} +288 -262
- package/dist/nextjs/server.js +568 -0
- package/dist/nextjs/server.js.map +1 -0
- package/dist/route/index.d.ts +667 -25
- package/dist/route/index.js +437 -1287
- package/dist/route/index.js.map +1 -1
- package/dist/route/types.d.ts +38 -0
- package/dist/route/types.js +3 -0
- package/dist/route/types.js.map +1 -0
- package/dist/server/index.d.ts +201 -67
- package/dist/server/index.js +921 -3182
- package/dist/server/index.js.map +1 -1
- package/dist/types-BGl4QL1w.d.ts +77 -0
- package/dist/types-DRG2XMTR.d.ts +157 -0
- package/package.json +56 -48
- package/dist/auto-loader-JFaZ9gON.d.ts +0 -80
- package/dist/client/index.d.ts +0 -358
- package/dist/client/index.js +0 -357
- package/dist/client/index.js.map +0 -1
- package/dist/client/nextjs/index.js +0 -371
- package/dist/client/nextjs/index.js.map +0 -1
- package/dist/codegen/generators/index.d.ts +0 -19
- package/dist/codegen/generators/index.js +0 -1404
- package/dist/codegen/generators/index.js.map +0 -1
- package/dist/database-errors-BNNmLTJE.d.ts +0 -86
- package/dist/events/index.d.ts +0 -183
- package/dist/events/index.js +0 -77
- package/dist/events/index.js.map +0 -1
- package/dist/index-DHiAqhKv.d.ts +0 -101
- package/dist/index.d.ts +0 -8
- package/dist/index.js +0 -3674
- package/dist/index.js.map +0 -1
- package/dist/types/index.d.ts +0 -121
- package/dist/types/index.js +0 -38
- package/dist/types/index.js.map +0 -1
- package/dist/types-BXibIEyj.d.ts +0 -60
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import PgBoss from 'pg-boss';
|
|
2
|
+
import { logger } from '@spfn/core/logger';
|
|
3
|
+
|
|
4
|
+
// src/job/boss.ts
|
|
5
|
+
var jobLogger = logger.child("@spfn/core:job");
|
|
6
|
+
var bossInstance = null;
|
|
7
|
+
var bossConfig = null;
|
|
8
|
+
async function initBoss(config) {
|
|
9
|
+
if (bossInstance) {
|
|
10
|
+
jobLogger.warn("pg-boss already initialized, returning existing instance");
|
|
11
|
+
return bossInstance;
|
|
12
|
+
}
|
|
13
|
+
jobLogger.info("Initializing pg-boss...");
|
|
14
|
+
bossConfig = config;
|
|
15
|
+
bossInstance = new PgBoss({
|
|
16
|
+
connectionString: config.connectionString,
|
|
17
|
+
schema: config.schema ?? "spfn_queue",
|
|
18
|
+
maintenanceIntervalSeconds: config.maintenanceIntervalSeconds ?? 120,
|
|
19
|
+
monitorIntervalSeconds: config.monitorIntervalSeconds
|
|
20
|
+
});
|
|
21
|
+
bossInstance.on("error", (error) => {
|
|
22
|
+
jobLogger.error("pg-boss error:", error);
|
|
23
|
+
});
|
|
24
|
+
await bossInstance.start();
|
|
25
|
+
jobLogger.info("pg-boss started successfully");
|
|
26
|
+
return bossInstance;
|
|
27
|
+
}
|
|
28
|
+
function getBoss() {
|
|
29
|
+
return bossInstance;
|
|
30
|
+
}
|
|
31
|
+
async function stopBoss() {
|
|
32
|
+
if (!bossInstance) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
jobLogger.info("Stopping pg-boss...");
|
|
36
|
+
try {
|
|
37
|
+
await bossInstance.stop({ graceful: true, timeout: 3e4 });
|
|
38
|
+
jobLogger.info("pg-boss stopped gracefully");
|
|
39
|
+
} catch (error) {
|
|
40
|
+
jobLogger.error("Error stopping pg-boss:", error);
|
|
41
|
+
throw error;
|
|
42
|
+
} finally {
|
|
43
|
+
bossInstance = null;
|
|
44
|
+
bossConfig = null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function isBossRunning() {
|
|
48
|
+
return bossInstance !== null;
|
|
49
|
+
}
|
|
50
|
+
function shouldClearOnStart() {
|
|
51
|
+
return bossConfig?.clearOnStart ?? false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/job/job-builder.ts
|
|
55
|
+
function buildPgBossOptions(defaults, sendOptions) {
|
|
56
|
+
const options = {};
|
|
57
|
+
if (sendOptions?.startAfter) {
|
|
58
|
+
options.startAfter = sendOptions.startAfter;
|
|
59
|
+
}
|
|
60
|
+
if (sendOptions?.singletonKey) {
|
|
61
|
+
options.singletonKey = sendOptions.singletonKey;
|
|
62
|
+
}
|
|
63
|
+
if (sendOptions?.priority !== void 0) {
|
|
64
|
+
options.priority = sendOptions.priority;
|
|
65
|
+
}
|
|
66
|
+
if (defaults?.retryLimit !== void 0) {
|
|
67
|
+
options.retryLimit = defaults.retryLimit;
|
|
68
|
+
}
|
|
69
|
+
if (defaults?.retryDelay !== void 0) {
|
|
70
|
+
options.retryDelay = defaults.retryDelay;
|
|
71
|
+
}
|
|
72
|
+
if (defaults?.expireInSeconds !== void 0) {
|
|
73
|
+
options.expireInSeconds = defaults.expireInSeconds;
|
|
74
|
+
}
|
|
75
|
+
if (defaults?.priority !== void 0 && sendOptions?.priority === void 0) {
|
|
76
|
+
options.priority = defaults.priority;
|
|
77
|
+
}
|
|
78
|
+
if (defaults?.singletonKey && !sendOptions?.singletonKey) {
|
|
79
|
+
options.singletonKey = defaults.singletonKey;
|
|
80
|
+
}
|
|
81
|
+
if (defaults?.retentionSeconds !== void 0) {
|
|
82
|
+
options.retentionSeconds = defaults.retentionSeconds;
|
|
83
|
+
}
|
|
84
|
+
return options;
|
|
85
|
+
}
|
|
86
|
+
var JobBuilder = class _JobBuilder {
|
|
87
|
+
_name;
|
|
88
|
+
_inputSchema;
|
|
89
|
+
_cronExpression;
|
|
90
|
+
_runOnce;
|
|
91
|
+
_subscribedEvent;
|
|
92
|
+
_subscribedEventDef;
|
|
93
|
+
_options;
|
|
94
|
+
_handler;
|
|
95
|
+
constructor(name) {
|
|
96
|
+
this._name = name;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Define input schema with TypeBox
|
|
100
|
+
*/
|
|
101
|
+
input(schema) {
|
|
102
|
+
const builder = new _JobBuilder(this._name);
|
|
103
|
+
builder._inputSchema = schema;
|
|
104
|
+
builder._cronExpression = this._cronExpression;
|
|
105
|
+
builder._runOnce = this._runOnce;
|
|
106
|
+
builder._subscribedEvent = this._subscribedEvent;
|
|
107
|
+
builder._options = this._options;
|
|
108
|
+
return builder;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Subscribe to an event (decoupled triggering)
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```typescript
|
|
115
|
+
* const userCreated = defineEvent('user.created', Type.Object({
|
|
116
|
+
* userId: Type.String(),
|
|
117
|
+
* }));
|
|
118
|
+
*
|
|
119
|
+
* const sendWelcomeEmail = job('send-welcome-email')
|
|
120
|
+
* .on(userCreated)
|
|
121
|
+
* .handler(async (payload) => {
|
|
122
|
+
* // payload is typed as { userId: string }
|
|
123
|
+
* });
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
on(event) {
|
|
127
|
+
const builder = new _JobBuilder(this._name);
|
|
128
|
+
builder._inputSchema = event.schema;
|
|
129
|
+
builder._subscribedEvent = event.name;
|
|
130
|
+
builder._subscribedEventDef = event;
|
|
131
|
+
builder._cronExpression = this._cronExpression;
|
|
132
|
+
builder._runOnce = this._runOnce;
|
|
133
|
+
builder._options = this._options;
|
|
134
|
+
return builder;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Set cron expression for scheduled execution
|
|
138
|
+
*/
|
|
139
|
+
cron(expression) {
|
|
140
|
+
this._cronExpression = expression;
|
|
141
|
+
return this;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Mark job to run once on server start
|
|
145
|
+
*/
|
|
146
|
+
runOnce() {
|
|
147
|
+
this._runOnce = true;
|
|
148
|
+
return this;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Set job options (retry, expiration, etc.)
|
|
152
|
+
*/
|
|
153
|
+
options(options) {
|
|
154
|
+
this._options = options;
|
|
155
|
+
return this;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Define the job handler and finalize the job definition
|
|
159
|
+
*/
|
|
160
|
+
handler(fn) {
|
|
161
|
+
this._handler = fn;
|
|
162
|
+
const name = this._name;
|
|
163
|
+
const inputSchema = this._inputSchema;
|
|
164
|
+
const cronExpression = this._cronExpression;
|
|
165
|
+
const runOnce = this._runOnce;
|
|
166
|
+
const subscribedEvent = this._subscribedEvent;
|
|
167
|
+
const subscribedEventDef = this._subscribedEventDef;
|
|
168
|
+
const options = this._options;
|
|
169
|
+
const handler = this._handler;
|
|
170
|
+
const send = async (inputOrOptions, maybeOptions) => {
|
|
171
|
+
const boss = getBoss();
|
|
172
|
+
if (!boss) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
`[Job:${name}] pg-boss not initialized. Ensure jobs are registered with defineServerConfig().jobs()`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
const [input, sendOptions] = inputSchema ? [inputOrOptions, maybeOptions] : [void 0, inputOrOptions];
|
|
178
|
+
return await boss.send(
|
|
179
|
+
name,
|
|
180
|
+
input ?? {},
|
|
181
|
+
buildPgBossOptions(options, sendOptions)
|
|
182
|
+
);
|
|
183
|
+
};
|
|
184
|
+
const run = async (input) => {
|
|
185
|
+
if (inputSchema) {
|
|
186
|
+
await handler(input);
|
|
187
|
+
} else {
|
|
188
|
+
await handler();
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
return {
|
|
192
|
+
name,
|
|
193
|
+
inputSchema,
|
|
194
|
+
cronExpression,
|
|
195
|
+
runOnce,
|
|
196
|
+
subscribedEvent,
|
|
197
|
+
_subscribedEventDef: subscribedEventDef,
|
|
198
|
+
options,
|
|
199
|
+
handler,
|
|
200
|
+
send,
|
|
201
|
+
run,
|
|
202
|
+
_input: void 0
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
function job(name) {
|
|
207
|
+
return new JobBuilder(name);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/job/job-router.ts
|
|
211
|
+
function isJobDef(value) {
|
|
212
|
+
return value !== null && typeof value === "object" && "name" in value && "handler" in value && "send" in value && "run" in value;
|
|
213
|
+
}
|
|
214
|
+
function isJobRouter(value) {
|
|
215
|
+
return value !== null && typeof value === "object" && "jobs" in value && "_jobs" in value;
|
|
216
|
+
}
|
|
217
|
+
function defineJobRouter(jobs) {
|
|
218
|
+
return {
|
|
219
|
+
jobs,
|
|
220
|
+
_jobs: jobs
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function collectJobs(router, prefix = "") {
|
|
224
|
+
const jobs = [];
|
|
225
|
+
for (const [key, value] of Object.entries(router.jobs)) {
|
|
226
|
+
const name = prefix ? `${prefix}.${key}` : key;
|
|
227
|
+
if (isJobRouter(value)) {
|
|
228
|
+
jobs.push(...collectJobs(value, name));
|
|
229
|
+
} else if (isJobDef(value)) {
|
|
230
|
+
jobs.push(value);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return jobs;
|
|
234
|
+
}
|
|
235
|
+
var jobLogger2 = logger.child("@spfn/core:job");
|
|
236
|
+
function getEventQueueName(eventName) {
|
|
237
|
+
return `event:${eventName}`;
|
|
238
|
+
}
|
|
239
|
+
function getDefaultJobOptions(options) {
|
|
240
|
+
return {
|
|
241
|
+
retryLimit: options?.retryLimit ?? 3,
|
|
242
|
+
retryDelay: options?.retryDelay ?? 1e3,
|
|
243
|
+
expireInSeconds: options?.expireInSeconds ?? 300
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
async function registerJobs(router) {
|
|
247
|
+
const boss = getBoss();
|
|
248
|
+
if (!boss) {
|
|
249
|
+
throw new Error(
|
|
250
|
+
"pg-boss not initialized. Call initBoss() before registerJobs()"
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
const jobs = collectJobs(router);
|
|
254
|
+
const clearOnStart = shouldClearOnStart();
|
|
255
|
+
jobLogger2.info(`Registering ${jobs.length} job(s)...`);
|
|
256
|
+
if (clearOnStart) {
|
|
257
|
+
jobLogger2.info("Clearing existing jobs before registration...");
|
|
258
|
+
for (const job2 of jobs) {
|
|
259
|
+
await boss.deleteAllJobs(job2.name);
|
|
260
|
+
if (job2.subscribedEvent) {
|
|
261
|
+
const eventQueue = getEventQueueName(job2.subscribedEvent);
|
|
262
|
+
await boss.deleteAllJobs(eventQueue);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
jobLogger2.info("Existing jobs cleared");
|
|
266
|
+
}
|
|
267
|
+
for (const job2 of jobs) {
|
|
268
|
+
await registerJob(job2);
|
|
269
|
+
}
|
|
270
|
+
jobLogger2.info("All jobs registered successfully");
|
|
271
|
+
}
|
|
272
|
+
async function registerWorker(boss, job2, queueName) {
|
|
273
|
+
await boss.work(
|
|
274
|
+
queueName,
|
|
275
|
+
{ batchSize: 1 },
|
|
276
|
+
async (jobs) => {
|
|
277
|
+
for (const pgBossJob of jobs) {
|
|
278
|
+
jobLogger2.debug(`[Job:${job2.name}] Executing...`, { jobId: pgBossJob.id });
|
|
279
|
+
const startTime = Date.now();
|
|
280
|
+
try {
|
|
281
|
+
if (job2.inputSchema) {
|
|
282
|
+
await job2.handler(pgBossJob.data);
|
|
283
|
+
} else {
|
|
284
|
+
await job2.handler();
|
|
285
|
+
}
|
|
286
|
+
const duration = Date.now() - startTime;
|
|
287
|
+
jobLogger2.info(`[Job:${job2.name}] Completed in ${duration}ms`, {
|
|
288
|
+
jobId: pgBossJob.id,
|
|
289
|
+
duration
|
|
290
|
+
});
|
|
291
|
+
} catch (error) {
|
|
292
|
+
const duration = Date.now() - startTime;
|
|
293
|
+
jobLogger2.error(`[Job:${job2.name}] Failed after ${duration}ms`, {
|
|
294
|
+
jobId: pgBossJob.id,
|
|
295
|
+
duration,
|
|
296
|
+
error: error instanceof Error ? error.message : String(error)
|
|
297
|
+
});
|
|
298
|
+
throw error;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
function connectEventToQueue(boss, job2, queueName) {
|
|
305
|
+
if (!job2._subscribedEventDef) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const eventDef = job2._subscribedEventDef;
|
|
309
|
+
eventDef._registerJobQueue(queueName, async (queue, payload) => {
|
|
310
|
+
await boss.send(queue, payload, getDefaultJobOptions(job2.options));
|
|
311
|
+
});
|
|
312
|
+
jobLogger2.debug(`[Job:${job2.name}] Connected to event: ${job2.subscribedEvent}`);
|
|
313
|
+
}
|
|
314
|
+
async function registerCronSchedule(boss, job2) {
|
|
315
|
+
if (!job2.cronExpression) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
jobLogger2.debug(`[Job:${job2.name}] Scheduling cron: ${job2.cronExpression}`);
|
|
319
|
+
await boss.schedule(
|
|
320
|
+
job2.name,
|
|
321
|
+
job2.cronExpression,
|
|
322
|
+
{},
|
|
323
|
+
getDefaultJobOptions(job2.options)
|
|
324
|
+
);
|
|
325
|
+
jobLogger2.info(`[Job:${job2.name}] Cron scheduled: ${job2.cronExpression}`);
|
|
326
|
+
}
|
|
327
|
+
async function queueRunOnceJob(boss, job2) {
|
|
328
|
+
if (!job2.runOnce) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
jobLogger2.debug(`[Job:${job2.name}] Queuing runOnce job`);
|
|
332
|
+
await boss.send(
|
|
333
|
+
job2.name,
|
|
334
|
+
{},
|
|
335
|
+
{
|
|
336
|
+
...getDefaultJobOptions(job2.options),
|
|
337
|
+
singletonKey: `runOnce:${job2.name}`
|
|
338
|
+
}
|
|
339
|
+
);
|
|
340
|
+
jobLogger2.info(`[Job:${job2.name}] runOnce job queued`);
|
|
341
|
+
}
|
|
342
|
+
async function registerJob(job2) {
|
|
343
|
+
const boss = getBoss();
|
|
344
|
+
if (!boss) {
|
|
345
|
+
throw new Error("pg-boss not initialized");
|
|
346
|
+
}
|
|
347
|
+
const queueName = job2.subscribedEvent ? getEventQueueName(job2.subscribedEvent) : job2.name;
|
|
348
|
+
jobLogger2.debug(`Registering job: ${job2.name}`, {
|
|
349
|
+
queueName,
|
|
350
|
+
subscribedEvent: job2.subscribedEvent
|
|
351
|
+
});
|
|
352
|
+
await registerWorker(boss, job2, queueName);
|
|
353
|
+
connectEventToQueue(boss, job2, queueName);
|
|
354
|
+
await registerCronSchedule(boss, job2);
|
|
355
|
+
await queueRunOnceJob(boss, job2);
|
|
356
|
+
jobLogger2.debug(`Job registered: ${job2.name}`);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export { JobBuilder, collectJobs, defineJobRouter, getBoss, initBoss, isBossRunning, isJobDef, isJobRouter, job, registerJobs, shouldClearOnStart, stopBoss };
|
|
360
|
+
//# sourceMappingURL=index.js.map
|
|
361
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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"]}
|
package/dist/logger/index.d.ts
CHANGED
|
@@ -1,78 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* 로깅 시스템 타입 정의
|
|
5
|
-
*
|
|
6
|
-
* ✅ 구현 완료:
|
|
7
|
-
* - LogLevel 타입 정의
|
|
8
|
-
* - LogMetadata 인터페이스
|
|
9
|
-
* - Transport 인터페이스
|
|
10
|
-
* - 환경별 설정 타입
|
|
11
|
-
*
|
|
12
|
-
* 🔗 관련 파일:
|
|
13
|
-
* - src/logger/logger.ts (Logger 클래스)
|
|
14
|
-
* - src/logger/transports/ (Transport 구현체)
|
|
15
|
-
* - src/logger/config.ts (설정)
|
|
16
|
-
*/
|
|
17
|
-
/**
|
|
18
|
-
* 로그 레벨
|
|
19
|
-
* debug < info < warn < error < fatal
|
|
20
|
-
*/
|
|
21
|
-
type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal';
|
|
22
|
-
/**
|
|
23
|
-
* 로그 메타데이터
|
|
24
|
-
*/
|
|
25
|
-
interface LogMetadata {
|
|
26
|
-
timestamp: Date;
|
|
27
|
-
level: LogLevel;
|
|
28
|
-
message: string;
|
|
29
|
-
module?: string;
|
|
30
|
-
error?: Error;
|
|
31
|
-
context?: Record<string, unknown>;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Transport 인터페이스
|
|
35
|
-
* 모든 Transport는 이 인터페이스를 구현해야 함
|
|
36
|
-
*/
|
|
37
|
-
interface Transport {
|
|
38
|
-
/**
|
|
39
|
-
* Transport 이름
|
|
40
|
-
*/
|
|
41
|
-
name: string;
|
|
42
|
-
/**
|
|
43
|
-
* 최소 로그 레벨 (이 레벨 이상만 처리)
|
|
44
|
-
*/
|
|
45
|
-
level: LogLevel;
|
|
46
|
-
/**
|
|
47
|
-
* 활성화 여부
|
|
48
|
-
*/
|
|
49
|
-
enabled: boolean;
|
|
50
|
-
/**
|
|
51
|
-
* 로그 처리 함수
|
|
52
|
-
*/
|
|
53
|
-
log(metadata: LogMetadata): Promise<void>;
|
|
54
|
-
/**
|
|
55
|
-
* Transport 종료 (리소스 정리)
|
|
56
|
-
*/
|
|
57
|
-
close?(): Promise<void>;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Logger 설정
|
|
61
|
-
*/
|
|
62
|
-
interface LoggerConfig {
|
|
63
|
-
/**
|
|
64
|
-
* 기본 로그 레벨
|
|
65
|
-
*/
|
|
66
|
-
level: LogLevel;
|
|
67
|
-
/**
|
|
68
|
-
* 모듈명 (context)
|
|
69
|
-
*/
|
|
70
|
-
module?: string;
|
|
71
|
-
/**
|
|
72
|
-
* Transport 리스트
|
|
73
|
-
*/
|
|
74
|
-
transports: Transport[];
|
|
75
|
-
}
|
|
1
|
+
import { a as LoggerConfig, L as LogLevel } from '../types-BGl4QL1w.js';
|
|
2
|
+
export { T as Transport } from '../types-BGl4QL1w.js';
|
|
76
3
|
|
|
77
4
|
/**
|
|
78
5
|
* Logger Class
|
|
@@ -87,6 +14,14 @@ declare class Logger {
|
|
|
87
14
|
private readonly config;
|
|
88
15
|
private readonly module?;
|
|
89
16
|
constructor(config: LoggerConfig);
|
|
17
|
+
/**
|
|
18
|
+
* Convert unknown error to Error object
|
|
19
|
+
*/
|
|
20
|
+
private toError;
|
|
21
|
+
/**
|
|
22
|
+
* Check if value is a context object (not an error)
|
|
23
|
+
*/
|
|
24
|
+
private isContext;
|
|
90
25
|
/**
|
|
91
26
|
* Get current log level
|
|
92
27
|
*/
|
|
@@ -95,29 +30,35 @@ declare class Logger {
|
|
|
95
30
|
* Create child logger (per module)
|
|
96
31
|
*/
|
|
97
32
|
child(module: string): Logger;
|
|
33
|
+
/**
|
|
34
|
+
* Common log method with error/context detection
|
|
35
|
+
*/
|
|
36
|
+
private logWithLevel;
|
|
98
37
|
/**
|
|
99
38
|
* Debug log
|
|
100
39
|
*/
|
|
101
40
|
debug(message: string, context?: Record<string, unknown>): void;
|
|
41
|
+
debug(message: string, error: Error | unknown, context?: Record<string, unknown>): void;
|
|
102
42
|
/**
|
|
103
43
|
* Info log
|
|
104
44
|
*/
|
|
105
45
|
info(message: string, context?: Record<string, unknown>): void;
|
|
46
|
+
info(message: string, error: Error | unknown, context?: Record<string, unknown>): void;
|
|
106
47
|
/**
|
|
107
48
|
* Warn log
|
|
108
49
|
*/
|
|
109
50
|
warn(message: string, context?: Record<string, unknown>): void;
|
|
110
|
-
warn(message: string, error: Error, context?: Record<string, unknown>): void;
|
|
51
|
+
warn(message: string, error: Error | unknown, context?: Record<string, unknown>): void;
|
|
111
52
|
/**
|
|
112
53
|
* Error log
|
|
113
54
|
*/
|
|
114
55
|
error(message: string, context?: Record<string, unknown>): void;
|
|
115
|
-
error(message: string, error: Error, context?: Record<string, unknown>): void;
|
|
56
|
+
error(message: string, error: Error | unknown, context?: Record<string, unknown>): void;
|
|
116
57
|
/**
|
|
117
58
|
* Fatal log
|
|
118
59
|
*/
|
|
119
60
|
fatal(message: string, context?: Record<string, unknown>): void;
|
|
120
|
-
fatal(message: string, error: Error, context?: Record<string, unknown>): void;
|
|
61
|
+
fatal(message: string, error: Error | unknown, context?: Record<string, unknown>): void;
|
|
121
62
|
/**
|
|
122
63
|
* Log processing (internal)
|
|
123
64
|
*/
|
|
@@ -147,4 +88,4 @@ declare class Logger {
|
|
|
147
88
|
*/
|
|
148
89
|
declare const logger: Logger;
|
|
149
90
|
|
|
150
|
-
export {
|
|
91
|
+
export { LogLevel, Logger, logger };
|