@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,108 @@
|
|
|
1
|
+
import { TSchema, Static } from '@sinclair/typebox';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Event System Types
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Pub/Sub capable cache interface for multi-instance events
|
|
9
|
+
*/
|
|
10
|
+
interface PubSubCache {
|
|
11
|
+
/**
|
|
12
|
+
* Publish a message to a channel
|
|
13
|
+
*/
|
|
14
|
+
publish(channel: string, message: unknown): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Subscribe to a channel
|
|
17
|
+
*/
|
|
18
|
+
subscribe(channel: string, handler: (message: unknown) => void | Promise<void>): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Event handler function type
|
|
22
|
+
*/
|
|
23
|
+
type EventHandler<TPayload> = (payload: TPayload) => void | Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Job queue sender function type (used by job module)
|
|
26
|
+
*/
|
|
27
|
+
type JobQueueSender = (queueName: string, payload: unknown) => Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Event definition interface
|
|
30
|
+
*/
|
|
31
|
+
interface EventDef<TPayload = void> {
|
|
32
|
+
/**
|
|
33
|
+
* Unique event name
|
|
34
|
+
*/
|
|
35
|
+
readonly name: string;
|
|
36
|
+
/**
|
|
37
|
+
* TypeBox payload schema (optional)
|
|
38
|
+
*/
|
|
39
|
+
readonly schema?: TSchema;
|
|
40
|
+
/**
|
|
41
|
+
* Subscribe to this event (in-memory handler)
|
|
42
|
+
*/
|
|
43
|
+
subscribe: (handler: EventHandler<TPayload>) => () => void;
|
|
44
|
+
/**
|
|
45
|
+
* Unsubscribe all handlers
|
|
46
|
+
*/
|
|
47
|
+
unsubscribeAll: () => void;
|
|
48
|
+
/**
|
|
49
|
+
* Emit the event (triggers all subscribers and queued jobs)
|
|
50
|
+
*/
|
|
51
|
+
emit: TPayload extends void ? () => Promise<void> : (payload: TPayload) => Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Enable cache-based pub/sub for multi-instance support
|
|
54
|
+
* Must await before emitting events to ensure subscription is ready
|
|
55
|
+
*/
|
|
56
|
+
useCache: (cache: PubSubCache) => Promise<EventDef<TPayload>>;
|
|
57
|
+
/**
|
|
58
|
+
* Internal: Register a job queue to receive this event
|
|
59
|
+
* Called by job registration system
|
|
60
|
+
*/
|
|
61
|
+
_registerJobQueue: (queueName: string, sender: JobQueueSender) => void;
|
|
62
|
+
/**
|
|
63
|
+
* Type inference helper
|
|
64
|
+
*/
|
|
65
|
+
_payload: TPayload;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Infer payload type from EventDef
|
|
69
|
+
*/
|
|
70
|
+
type InferEventPayload<TEvent> = TEvent extends EventDef<infer TPayload> ? TPayload : never;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Event System
|
|
74
|
+
*
|
|
75
|
+
* Decoupled pub/sub event system with optional cache integration for multi-instance support
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
79
|
+
* // Define event
|
|
80
|
+
* const userCreated = defineEvent('user.created', Type.Object({
|
|
81
|
+
* userId: Type.String(),
|
|
82
|
+
* }));
|
|
83
|
+
*
|
|
84
|
+
* // Subscribe (in-memory)
|
|
85
|
+
* userCreated.subscribe((payload) => {
|
|
86
|
+
* console.log('User created:', payload.userId);
|
|
87
|
+
* });
|
|
88
|
+
*
|
|
89
|
+
* // Emit
|
|
90
|
+
* await userCreated.emit({ userId: '123' });
|
|
91
|
+
*
|
|
92
|
+
* // With cache for multi-instance
|
|
93
|
+
* const event = defineEvent('user.created', schema);
|
|
94
|
+
* await event.useCache(cache); // Must await before emitting
|
|
95
|
+
* await event.emit({ userId: '123' }); // Broadcast to all instances
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Define an event without payload
|
|
101
|
+
*/
|
|
102
|
+
declare function defineEvent(name: string): EventDef<void>;
|
|
103
|
+
/**
|
|
104
|
+
* Define an event with typed payload
|
|
105
|
+
*/
|
|
106
|
+
declare function defineEvent<T extends TSchema>(name: string, schema: T): EventDef<Static<T>>;
|
|
107
|
+
|
|
108
|
+
export { type EventDef, type EventHandler, type InferEventPayload, type JobQueueSender, type PubSubCache, defineEvent };
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { logger } from '@spfn/core/logger';
|
|
2
|
+
|
|
3
|
+
// src/event/event.ts
|
|
4
|
+
var eventLogger = logger.child("@spfn/core:event");
|
|
5
|
+
function logHandlerError(eventName, error) {
|
|
6
|
+
eventLogger.error(`Event handler error: ${eventName}`, {
|
|
7
|
+
error: error instanceof Error ? error.message : String(error)
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
function logJobQueueError(queueName, error) {
|
|
11
|
+
eventLogger.error(`Failed to send event to job queue: ${queueName}`, {
|
|
12
|
+
error: error instanceof Error ? error.message : String(error)
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function createHandlerManager(name) {
|
|
16
|
+
const handlers = /* @__PURE__ */ new Set();
|
|
17
|
+
return {
|
|
18
|
+
add: (handler) => {
|
|
19
|
+
handlers.add(handler);
|
|
20
|
+
eventLogger.debug(`Subscribed to event: ${name}`, { handlerCount: handlers.size });
|
|
21
|
+
return () => {
|
|
22
|
+
handlers.delete(handler);
|
|
23
|
+
eventLogger.debug(`Unsubscribed from event: ${name}`, { handlerCount: handlers.size });
|
|
24
|
+
};
|
|
25
|
+
},
|
|
26
|
+
clear: () => {
|
|
27
|
+
handlers.clear();
|
|
28
|
+
eventLogger.debug(`Unsubscribed all from event: ${name}`);
|
|
29
|
+
},
|
|
30
|
+
trigger: async (payload) => {
|
|
31
|
+
const results = await Promise.allSettled(
|
|
32
|
+
[...handlers].map((handler) => handler(payload))
|
|
33
|
+
);
|
|
34
|
+
for (const result of results) {
|
|
35
|
+
if (result.status === "rejected") {
|
|
36
|
+
logHandlerError(name, result.reason);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function createJobQueueManager(name) {
|
|
43
|
+
const jobQueues = /* @__PURE__ */ new Map();
|
|
44
|
+
return {
|
|
45
|
+
register: (queueName, sender) => {
|
|
46
|
+
jobQueues.set(queueName, sender);
|
|
47
|
+
eventLogger.debug(`Registered job queue for event: ${name}`, { queueName });
|
|
48
|
+
},
|
|
49
|
+
send: async (payload) => {
|
|
50
|
+
if (jobQueues.size === 0) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const entries = [...jobQueues.entries()];
|
|
54
|
+
const results = await Promise.allSettled(
|
|
55
|
+
entries.map(([queueName, sender]) => sender(queueName, payload))
|
|
56
|
+
);
|
|
57
|
+
for (const [i, result] of results.entries()) {
|
|
58
|
+
if (result.status === "rejected") {
|
|
59
|
+
logJobQueueError(entries[i][0], result.reason);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
get size() {
|
|
64
|
+
return jobQueues.size;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function createEventImpl(name, schema) {
|
|
69
|
+
const handlerManager = createHandlerManager(name);
|
|
70
|
+
const jobQueueManager = createJobQueueManager(name);
|
|
71
|
+
let cache;
|
|
72
|
+
let cacheSubscribed = false;
|
|
73
|
+
const emit = async (payload) => {
|
|
74
|
+
eventLogger.debug(`Emitting event: ${name}`, {
|
|
75
|
+
payload,
|
|
76
|
+
hasCache: !!cache,
|
|
77
|
+
jobQueueCount: jobQueueManager.size
|
|
78
|
+
});
|
|
79
|
+
if (cache) {
|
|
80
|
+
await cache.publish(name, payload);
|
|
81
|
+
} else {
|
|
82
|
+
await handlerManager.trigger(payload);
|
|
83
|
+
}
|
|
84
|
+
await jobQueueManager.send(payload);
|
|
85
|
+
eventLogger.debug(`Event emitted: ${name}`);
|
|
86
|
+
};
|
|
87
|
+
const useCache = async (newCache) => {
|
|
88
|
+
if (cacheSubscribed) {
|
|
89
|
+
eventLogger.warn(`Cache already configured for event: ${name}`);
|
|
90
|
+
return self;
|
|
91
|
+
}
|
|
92
|
+
cache = newCache;
|
|
93
|
+
cacheSubscribed = true;
|
|
94
|
+
await newCache.subscribe(name, async (message) => {
|
|
95
|
+
eventLogger.debug(`Received event from cache: ${name}`);
|
|
96
|
+
await handlerManager.trigger(message);
|
|
97
|
+
});
|
|
98
|
+
eventLogger.debug(`Cache subscription ready for event: ${name}`);
|
|
99
|
+
return self;
|
|
100
|
+
};
|
|
101
|
+
const self = {
|
|
102
|
+
name,
|
|
103
|
+
schema,
|
|
104
|
+
subscribe: handlerManager.add,
|
|
105
|
+
unsubscribeAll: handlerManager.clear,
|
|
106
|
+
emit,
|
|
107
|
+
useCache,
|
|
108
|
+
_registerJobQueue: jobQueueManager.register,
|
|
109
|
+
_payload: void 0
|
|
110
|
+
};
|
|
111
|
+
return self;
|
|
112
|
+
}
|
|
113
|
+
function defineEvent(name, schema) {
|
|
114
|
+
if (schema) {
|
|
115
|
+
return createEventImpl(name, schema);
|
|
116
|
+
}
|
|
117
|
+
return createEventImpl(name);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export { defineEvent };
|
|
121
|
+
//# sourceMappingURL=index.js.map
|
|
122
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/event/event.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Event System\n *\n * Decoupled pub/sub event system with optional cache integration for multi-instance support\n *\n * @example\n * ```typescript\n * // Define event\n * const userCreated = defineEvent('user.created', Type.Object({\n * userId: Type.String(),\n * }));\n *\n * // Subscribe (in-memory)\n * userCreated.subscribe((payload) => {\n * console.log('User created:', payload.userId);\n * });\n *\n * // Emit\n * await userCreated.emit({ userId: '123' });\n *\n * // With cache for multi-instance\n * const event = defineEvent('user.created', schema);\n * await event.useCache(cache); // Must await before emitting\n * await event.emit({ userId: '123' }); // Broadcast to all instances\n * ```\n */\n\nimport type { TSchema, Static } from '@sinclair/typebox';\nimport { logger } from '@spfn/core/logger';\nimport type { EventDef, EventHandler, JobQueueSender, PubSubCache } from './types';\n\nconst eventLogger = logger.child('@spfn/core:event');\n\n/**\n * Log handler error with consistent format\n */\nfunction logHandlerError(eventName: string, error: unknown): void\n{\n eventLogger.error(`Event handler error: ${eventName}`, {\n error: error instanceof Error ? error.message : String(error),\n });\n}\n\n/**\n * Log job queue error with consistent format\n */\nfunction logJobQueueError(queueName: string, error: unknown): void\n{\n eventLogger.error(`Failed to send event to job queue: ${queueName}`, {\n error: error instanceof Error ? error.message : String(error),\n });\n}\n\n/**\n * Create handler subscription manager\n */\nfunction createHandlerManager<TPayload>(name: string)\n{\n const handlers: Set<EventHandler<TPayload>> = new Set();\n\n return {\n add: (handler: EventHandler<TPayload>): (() => void) =>\n {\n handlers.add(handler);\n eventLogger.debug(`Subscribed to event: ${name}`, { handlerCount: handlers.size });\n\n return () =>\n {\n handlers.delete(handler);\n eventLogger.debug(`Unsubscribed from event: ${name}`, { handlerCount: handlers.size });\n };\n },\n\n clear: (): void =>\n {\n handlers.clear();\n eventLogger.debug(`Unsubscribed all from event: ${name}`);\n },\n\n trigger: async (payload: TPayload): Promise<void> =>\n {\n const results = await Promise.allSettled(\n [...handlers].map((handler) => handler(payload))\n );\n\n for (const result of results)\n {\n if (result.status === 'rejected')\n {\n logHandlerError(name, result.reason);\n }\n }\n },\n };\n}\n\n/**\n * Create job queue manager\n */\nfunction createJobQueueManager(name: string)\n{\n const jobQueues: Map<string, JobQueueSender> = new Map();\n\n return {\n register: (queueName: string, sender: JobQueueSender): void =>\n {\n jobQueues.set(queueName, sender);\n eventLogger.debug(`Registered job queue for event: ${name}`, { queueName });\n },\n\n send: async (payload: unknown): Promise<void> =>\n {\n if (jobQueues.size === 0)\n {\n return;\n }\n\n const entries = [...jobQueues.entries()];\n const results = await Promise.allSettled(\n entries.map(([queueName, sender]) => sender(queueName, payload))\n );\n\n for (const [i, result] of results.entries())\n {\n if (result.status === 'rejected')\n {\n logJobQueueError(entries[i][0], result.reason);\n }\n }\n },\n\n get size(): number\n {\n return jobQueues.size;\n },\n };\n}\n\n/**\n * Internal: Create event implementation\n */\nfunction createEventImpl<TPayload>(\n name: string,\n schema?: TSchema\n): EventDef<TPayload>\n{\n const handlerManager = createHandlerManager<TPayload>(name);\n const jobQueueManager = createJobQueueManager(name);\n let cache: PubSubCache | undefined;\n let cacheSubscribed = false;\n\n const emit = async (payload?: TPayload): Promise<void> =>\n {\n eventLogger.debug(`Emitting event: ${name}`, {\n payload,\n hasCache: !!cache,\n jobQueueCount: jobQueueManager.size,\n });\n\n if (cache)\n {\n await cache.publish(name, payload);\n }\n else\n {\n await handlerManager.trigger(payload as TPayload);\n }\n\n await jobQueueManager.send(payload);\n eventLogger.debug(`Event emitted: ${name}`);\n };\n\n const useCache = async (newCache: PubSubCache): Promise<EventDef<TPayload>> =>\n {\n if (cacheSubscribed)\n {\n eventLogger.warn(`Cache already configured for event: ${name}`);\n return self;\n }\n\n cache = newCache;\n cacheSubscribed = true;\n\n await newCache.subscribe(name, async (message: unknown) =>\n {\n eventLogger.debug(`Received event from cache: ${name}`);\n await handlerManager.trigger(message as TPayload);\n });\n\n eventLogger.debug(`Cache subscription ready for event: ${name}`);\n return self;\n };\n\n const self: EventDef<TPayload> = {\n name,\n schema,\n subscribe: handlerManager.add,\n unsubscribeAll: handlerManager.clear,\n emit: emit as EventDef<TPayload>['emit'],\n useCache,\n _registerJobQueue: jobQueueManager.register,\n _payload: undefined as unknown as TPayload,\n };\n\n return self;\n}\n\n/**\n * Define an event without payload\n */\nexport function defineEvent(name: string): EventDef<void>;\n\n/**\n * Define an event with typed payload\n */\nexport function defineEvent<T extends TSchema>(\n name: string,\n schema: T\n): EventDef<Static<T>>;\n\n/**\n * Define an event for decoupled pub/sub\n *\n * @example\n * ```typescript\n * // Define event with payload\n * export const userCreated = defineEvent('user.created', Type.Object({\n * userId: Type.String(),\n * }));\n *\n * // Subscribe to event (in-memory)\n * const unsubscribe = userCreated.subscribe((payload) => {\n * console.log('User created:', payload.userId);\n * });\n *\n * // Emit event\n * await userCreated.emit({ userId: '123' });\n *\n * // Unsubscribe when done\n * unsubscribe();\n *\n * // Multi-instance with cache\n * await userCreated.useCache(cache);\n * await userCreated.emit({ userId: '123' }); // Broadcast to all instances\n * ```\n */\nexport function defineEvent<T extends TSchema>(\n name: string,\n schema?: T\n): EventDef<Static<T>> | EventDef\n{\n if (schema)\n {\n return createEventImpl<Static<T>>(name, schema);\n }\n\n return createEventImpl<void>(name);\n}\n"]}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { a as JobOptions, b as JobHandler, c as JobDef, d as JobRouterEntry, J as JobRouter } from '../boss-D-fGtVgM.js';
|
|
2
|
+
export { B as BossConfig, I as InferJobInput, e as JobSendOptions, g as getBoss, i as initBoss, f as isBossRunning, h as shouldClearOnStart, s as stopBoss } from '../boss-D-fGtVgM.js';
|
|
3
|
+
import * as _sinclair_typebox from '@sinclair/typebox';
|
|
4
|
+
import { Static } from '@sinclair/typebox';
|
|
5
|
+
import { EventDef, InferEventPayload } from '@spfn/core/event';
|
|
6
|
+
import 'pg-boss';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Job builder class with fluent API
|
|
10
|
+
*/
|
|
11
|
+
declare class JobBuilder<TInput = void> {
|
|
12
|
+
private _name;
|
|
13
|
+
private _inputSchema?;
|
|
14
|
+
private _cronExpression?;
|
|
15
|
+
private _runOnce?;
|
|
16
|
+
private _subscribedEvent?;
|
|
17
|
+
private _subscribedEventDef?;
|
|
18
|
+
private _options?;
|
|
19
|
+
private _handler?;
|
|
20
|
+
constructor(name: string);
|
|
21
|
+
/**
|
|
22
|
+
* Define input schema with TypeBox
|
|
23
|
+
*/
|
|
24
|
+
input<TSchema extends _sinclair_typebox.TSchema>(schema: TSchema): JobBuilder<Static<TSchema>>;
|
|
25
|
+
/**
|
|
26
|
+
* Subscribe to an event (decoupled triggering)
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* const userCreated = defineEvent('user.created', Type.Object({
|
|
31
|
+
* userId: Type.String(),
|
|
32
|
+
* }));
|
|
33
|
+
*
|
|
34
|
+
* const sendWelcomeEmail = job('send-welcome-email')
|
|
35
|
+
* .on(userCreated)
|
|
36
|
+
* .handler(async (payload) => {
|
|
37
|
+
* // payload is typed as { userId: string }
|
|
38
|
+
* });
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
on<TEvent extends EventDef<any>>(event: TEvent): JobBuilder<InferEventPayload<TEvent>>;
|
|
42
|
+
/**
|
|
43
|
+
* Set cron expression for scheduled execution
|
|
44
|
+
*/
|
|
45
|
+
cron(expression: string): this;
|
|
46
|
+
/**
|
|
47
|
+
* Mark job to run once on server start
|
|
48
|
+
*/
|
|
49
|
+
runOnce(): this;
|
|
50
|
+
/**
|
|
51
|
+
* Set job options (retry, expiration, etc.)
|
|
52
|
+
*/
|
|
53
|
+
options(options: JobOptions): this;
|
|
54
|
+
/**
|
|
55
|
+
* Define the job handler and finalize the job definition
|
|
56
|
+
*/
|
|
57
|
+
handler(fn: JobHandler<TInput>): JobDef<TInput>;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Create a new job definition
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* // Simple job without input
|
|
65
|
+
* export const cleanupJob = job('cleanup')
|
|
66
|
+
* .handler(async () => {
|
|
67
|
+
* await db.cleanup();
|
|
68
|
+
* });
|
|
69
|
+
*
|
|
70
|
+
* // Job with typed input
|
|
71
|
+
* export const sendEmailJob = job('send-email')
|
|
72
|
+
* .input(Type.Object({
|
|
73
|
+
* to: Type.String(),
|
|
74
|
+
* subject: Type.String(),
|
|
75
|
+
* body: Type.String(),
|
|
76
|
+
* }))
|
|
77
|
+
* .handler(async (input) => {
|
|
78
|
+
* await emailService.send(input.to, input.subject, input.body);
|
|
79
|
+
* });
|
|
80
|
+
*
|
|
81
|
+
* // Cron job
|
|
82
|
+
* export const dailyReportJob = job('daily-report')
|
|
83
|
+
* .cron('0 9 * * *')
|
|
84
|
+
* .handler(async () => {
|
|
85
|
+
* await reportService.generateDaily();
|
|
86
|
+
* });
|
|
87
|
+
*
|
|
88
|
+
* // Run once on server start
|
|
89
|
+
* export const initCacheJob = job('init-cache')
|
|
90
|
+
* .runOnce()
|
|
91
|
+
* .handler(async () => {
|
|
92
|
+
* await cache.warmup();
|
|
93
|
+
* });
|
|
94
|
+
*
|
|
95
|
+
* // With options
|
|
96
|
+
* export const importantJob = job('important-task')
|
|
97
|
+
* .input(Type.Object({ id: Type.String() }))
|
|
98
|
+
* .options({
|
|
99
|
+
* retryLimit: 5,
|
|
100
|
+
* retryDelay: 5000,
|
|
101
|
+
* priority: 10,
|
|
102
|
+
* })
|
|
103
|
+
* .handler(async (input) => {
|
|
104
|
+
* await processImportant(input.id);
|
|
105
|
+
* });
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
declare function job(name: string): JobBuilder<void>;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Job Router
|
|
112
|
+
*
|
|
113
|
+
* Groups job definitions for registration with the server
|
|
114
|
+
*/
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Type guard to check if value is a JobDef
|
|
118
|
+
*/
|
|
119
|
+
declare function isJobDef(value: unknown): value is JobDef<any>;
|
|
120
|
+
/**
|
|
121
|
+
* Type guard to check if value is a JobRouter
|
|
122
|
+
*/
|
|
123
|
+
declare function isJobRouter(value: unknown): value is JobRouter<any>;
|
|
124
|
+
/**
|
|
125
|
+
* Define a job router to group jobs together
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```typescript
|
|
129
|
+
* // Flat structure
|
|
130
|
+
* export const jobRouter = defineJobRouter({
|
|
131
|
+
* sendWelcomeEmail,
|
|
132
|
+
* dailyReport,
|
|
133
|
+
* initCache,
|
|
134
|
+
* });
|
|
135
|
+
*
|
|
136
|
+
* // Nested structure
|
|
137
|
+
* export const jobRouter = defineJobRouter({
|
|
138
|
+
* email: defineJobRouter({
|
|
139
|
+
* sendWelcome: sendWelcomeEmailJob,
|
|
140
|
+
* sendReset: sendResetPasswordJob,
|
|
141
|
+
* }),
|
|
142
|
+
* reports: defineJobRouter({
|
|
143
|
+
* daily: dailyReportJob,
|
|
144
|
+
* weekly: weeklyReportJob,
|
|
145
|
+
* }),
|
|
146
|
+
* });
|
|
147
|
+
*
|
|
148
|
+
* // Mixed
|
|
149
|
+
* export const jobRouter = defineJobRouter({
|
|
150
|
+
* initCache, // flat
|
|
151
|
+
* email: defineJobRouter({ ... }), // nested
|
|
152
|
+
* });
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
declare function defineJobRouter<TJobs extends Record<string, JobRouterEntry>>(jobs: TJobs): JobRouter<TJobs>;
|
|
156
|
+
/**
|
|
157
|
+
* Collect all JobDefs from a JobRouter (including nested)
|
|
158
|
+
*/
|
|
159
|
+
declare function collectJobs(router: JobRouter<any>, prefix?: string): JobDef<any>[];
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Job Registration
|
|
163
|
+
*
|
|
164
|
+
* Registers jobs with pg-boss
|
|
165
|
+
*/
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Register all jobs from a JobRouter with pg-boss
|
|
169
|
+
*/
|
|
170
|
+
declare function registerJobs(router: JobRouter<any>): Promise<void>;
|
|
171
|
+
|
|
172
|
+
export { JobBuilder, JobDef, JobHandler, JobOptions, JobRouter, JobRouterEntry, collectJobs, defineJobRouter, isJobDef, isJobRouter, job, registerJobs };
|