@pikku/queue-pg-boss 0.10.0 → 0.11.0

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/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 0.11.0
2
+
3
+ ### Minor Changes
4
+
5
+ - Add scheduler service implementation
6
+ - Add service factory for queue and scheduler creation
7
+
8
+
1
9
  # @pikku/queue-pg-boss
2
10
 
3
11
  ## 0.10.0
package/dist/index.d.ts CHANGED
@@ -4,6 +4,8 @@
4
4
  * PostgreSQL queue service for Pikku using pg-boss
5
5
  * Provides job results, retries, priorities, and PostgreSQL-based queuing
6
6
  */
7
+ export { PgBossServiceFactory } from './pg-boss-service-factory.js';
7
8
  export { PgBossQueueService } from './pg-boss-queue-service.js';
8
9
  export { PgBossQueueWorkers } from './pg-boss-queue-worker.js';
10
+ export { PgBossSchedulerService } from './pg-boss-scheduler-service.js';
9
11
  export type { QueueService, PikkuWorkerConfig, PikkuJobConfig, QueueJob, JobOptions, } from '@pikku/core/queue';
package/dist/index.js CHANGED
@@ -4,5 +4,7 @@
4
4
  * PostgreSQL queue service for Pikku using pg-boss
5
5
  * Provides job results, retries, priorities, and PostgreSQL-based queuing
6
6
  */
7
+ export { PgBossServiceFactory } from './pg-boss-service-factory.js';
7
8
  export { PgBossQueueService } from './pg-boss-queue-service.js';
8
9
  export { PgBossQueueWorkers } from './pg-boss-queue-worker.js';
10
+ export { PgBossSchedulerService } from './pg-boss-scheduler-service.js';
@@ -9,11 +9,7 @@ export declare class PgBossQueueService implements QueueService {
9
9
  readonly name = "pg-boss";
10
10
  readonly supportsResults = true;
11
11
  protected pgBoss: PgBoss;
12
- constructor(optionsOrUrl: PgBoss.ConstructorOptions | string);
13
- /**
14
- * Initialize the pg-boss instance
15
- */
16
- init(): Promise<void>;
12
+ constructor(pgBoss: PgBoss);
17
13
  /**
18
14
  * Add a job to the queue
19
15
  */
@@ -22,8 +18,4 @@ export declare class PgBossQueueService implements QueueService {
22
18
  * Get job by ID
23
19
  */
24
20
  getJob<T, R>(queueName: string, jobId: string): Promise<QueueJob<T, R> | null>;
25
- /**
26
- * Close the pg-boss connection
27
- */
28
- close(): Promise<void>;
29
21
  }
@@ -1,4 +1,3 @@
1
- import PgBoss from 'pg-boss';
2
1
  import { mapPgBossJobToQueueJob } from './utils.js';
3
2
  export const mapPikkuJobToPgBoss = (options) => {
4
3
  const pgBossOptions = {};
@@ -11,6 +10,30 @@ export const mapPikkuJobToPgBoss = (options) => {
11
10
  if (options?.jobId !== undefined) {
12
11
  pgBossOptions.singletonKey = options.jobId;
13
12
  }
13
+ // Map retry options
14
+ if (options?.attempts !== undefined) {
15
+ pgBossOptions.retryLimit = options.attempts - 1;
16
+ }
17
+ if (options?.backoff !== undefined) {
18
+ if (typeof options.backoff === 'string') {
19
+ // If backoff is a string, assume it's 'exponential' or 'fixed'
20
+ pgBossOptions.retryBackoff = options.backoff === 'exponential';
21
+ }
22
+ else if (typeof options.backoff === 'object') {
23
+ pgBossOptions.retryBackoff = options.backoff.type === 'exponential';
24
+ if (options.backoff.delay !== undefined) {
25
+ // pg-boss uses seconds, we use milliseconds
26
+ pgBossOptions.retryDelay = Math.floor(options.backoff.delay / 1000);
27
+ }
28
+ }
29
+ }
30
+ if (options?.removeOnComplete !== undefined) {
31
+ pgBossOptions.onComplete = options.removeOnComplete === 0;
32
+ }
33
+ if (options?.removeOnFail !== undefined) {
34
+ // pg-boss doesn't have a direct equivalent, but we can track this for consistency
35
+ // Note: pg-boss keeps failed jobs by default
36
+ }
14
37
  return pgBossOptions;
15
38
  };
16
39
  /**
@@ -21,23 +44,14 @@ export class PgBossQueueService {
21
44
  name = 'pg-boss';
22
45
  supportsResults = true;
23
46
  pgBoss;
24
- constructor(optionsOrUrl) {
25
- if (typeof optionsOrUrl === 'string') {
26
- // If a string is provided, treat it as the connection URL
27
- optionsOrUrl = { connectionString: optionsOrUrl };
28
- }
29
- this.pgBoss = new PgBoss(optionsOrUrl);
30
- }
31
- /**
32
- * Initialize the pg-boss instance
33
- */
34
- async init() {
35
- await this.pgBoss.start();
47
+ constructor(pgBoss) {
48
+ this.pgBoss = pgBoss;
36
49
  }
37
50
  /**
38
51
  * Add a job to the queue
39
52
  */
40
53
  async add(queueName, data, options) {
54
+ await this.pgBoss.createQueue(queueName);
41
55
  const jobId = await this.pgBoss.send(queueName, data, mapPikkuJobToPgBoss(options));
42
56
  if (!jobId) {
43
57
  throw new Error('Failed to create job');
@@ -54,10 +68,4 @@ export class PgBossQueueService {
54
68
  }
55
69
  return mapPgBossJobToQueueJob(job, this.pgBoss);
56
70
  }
57
- /**
58
- * Close the pg-boss connection
59
- */
60
- async close() {
61
- await this.pgBoss.stop();
62
- }
63
71
  }
@@ -18,17 +18,9 @@ export declare class PgBossQueueWorkers implements QueueWorkers {
18
18
  readonly configMappings: QueueConfigMapping;
19
19
  private pgBoss;
20
20
  private activeWorkers;
21
- constructor(options: PgBoss.ConstructorOptions | string, singletonServices: CoreSingletonServices, createSessionServices?: CreateSessionServices<CoreSingletonServices, CoreServices, any> | undefined);
22
- /**
23
- * Initialize pg-boss
24
- */
25
- init(): Promise<void>;
21
+ constructor(pgBoss: PgBoss, singletonServices: CoreSingletonServices, createSessionServices?: CreateSessionServices<CoreSingletonServices, CoreServices, any> | undefined);
26
22
  /**
27
23
  * Scan state and register all compatible processors
28
24
  */
29
25
  registerQueues(): Promise<Record<string, ConfigValidationResult[]>>;
30
- /**
31
- * Close all workers and connections
32
- */
33
- close(): Promise<void>;
34
26
  }
@@ -1,4 +1,3 @@
1
- import PgBoss from 'pg-boss';
2
1
  import { runQueueJob, registerQueueWorkers, QueueJobFailedError, QueueJobDiscardedError, } from '@pikku/core/queue';
3
2
  import { mapPgBossJobToQueueJob } from './utils.js';
4
3
  export const mapPikkuWorkerToPgBoss = (workerConfig) => {
@@ -84,20 +83,10 @@ export class PgBossQueueWorkers {
84
83
  };
85
84
  pgBoss;
86
85
  activeWorkers = new Map();
87
- constructor(options, singletonServices, createSessionServices) {
86
+ constructor(pgBoss, singletonServices, createSessionServices) {
88
87
  this.singletonServices = singletonServices;
89
88
  this.createSessionServices = createSessionServices;
90
- if (typeof options === 'string') {
91
- // If a string is provided, treat it as the connection string
92
- options = { connectionString: options };
93
- }
94
- this.pgBoss = new PgBoss(options);
95
- }
96
- /**
97
- * Initialize pg-boss
98
- */
99
- async init() {
100
- await this.pgBoss.start();
89
+ this.pgBoss = pgBoss;
101
90
  }
102
91
  /**
103
92
  * Scan state and register all compatible processors
@@ -139,10 +128,4 @@ export class PgBossQueueWorkers {
139
128
  this.activeWorkers.set(queueName, workerId);
140
129
  });
141
130
  }
142
- /**
143
- * Close all workers and connections
144
- */
145
- async close() {
146
- await this.pgBoss.stop();
147
- }
148
131
  }
@@ -0,0 +1,35 @@
1
+ import PgBoss from 'pg-boss';
2
+ import { SchedulerService, ScheduledTaskInfo, ScheduledTaskSummary, CoreUserSession } from '@pikku/core';
3
+ /**
4
+ * pg-boss scheduler service implementation
5
+ * Uses pg-boss's schedule() API to store recurring jobs in PostgreSQL
6
+ */
7
+ export declare class PgBossSchedulerService extends SchedulerService {
8
+ private pgBoss;
9
+ constructor(pgBoss: PgBoss);
10
+ /**
11
+ * Initialize - no-op as pg-boss is already initialized by factory
12
+ */
13
+ init(): Promise<void>;
14
+ /**
15
+ * Schedule a one-off delayed RPC call
16
+ * Uses pg-boss send() with startAfter for one-time execution
17
+ */
18
+ scheduleRPC(delay: number | string, rpcName: string, data?: any, session?: CoreUserSession): Promise<string>;
19
+ /**
20
+ * Unschedule (cancel) a task by ID
21
+ */
22
+ unschedule(taskId: string): Promise<boolean>;
23
+ /**
24
+ * Get a scheduled task by ID with full details
25
+ */
26
+ getTask(taskId: string): Promise<ScheduledTaskInfo | null>;
27
+ /**
28
+ * Get all scheduled tasks with minimal info
29
+ */
30
+ getAllTasks(): Promise<ScheduledTaskSummary[]>;
31
+ /**
32
+ * Close - no-op as pg-boss lifecycle is managed by factory
33
+ */
34
+ close(): Promise<void>;
35
+ }
@@ -0,0 +1,80 @@
1
+ import { SchedulerService, parseDurationString, } from '@pikku/core';
2
+ /**
3
+ * pg-boss scheduler service implementation
4
+ * Uses pg-boss's schedule() API to store recurring jobs in PostgreSQL
5
+ */
6
+ export class PgBossSchedulerService extends SchedulerService {
7
+ pgBoss;
8
+ constructor(pgBoss) {
9
+ super();
10
+ this.pgBoss = pgBoss;
11
+ }
12
+ /**
13
+ * Initialize - no-op as pg-boss is already initialized by factory
14
+ */
15
+ async init() {
16
+ // No-op - pg-boss is already initialized by factory
17
+ }
18
+ /**
19
+ * Schedule a one-off delayed RPC call
20
+ * Uses pg-boss send() with startAfter for one-time execution
21
+ */
22
+ async scheduleRPC(delay, rpcName, data, session) {
23
+ // Parse delay if it's a string
24
+ const delayMs = typeof delay === 'string' ? parseDurationString(delay) : delay;
25
+ const jobData = {
26
+ rpcName,
27
+ data,
28
+ session,
29
+ };
30
+ // Use send() with startAfter for one-off delayed execution
31
+ const taskId = await this.pgBoss.send('pikku-remote-internal-rpc', jobData, {
32
+ startAfter: new Date(Date.now() + delayMs),
33
+ singletonKey: `${rpcName}-${Date.now()}`, // Ensure uniqueness
34
+ });
35
+ if (!taskId) {
36
+ throw new Error('Failed to schedule RPC');
37
+ }
38
+ return taskId;
39
+ }
40
+ /**
41
+ * Unschedule (cancel) a task by ID
42
+ */
43
+ async unschedule(taskId) {
44
+ await this.pgBoss.cancel('pikku-remote-internal-rpc', taskId);
45
+ return true;
46
+ }
47
+ /**
48
+ * Get a scheduled task by ID with full details
49
+ */
50
+ async getTask(taskId) {
51
+ const job = await this.pgBoss.getJobById('pikku-remote-internal-rpc', taskId);
52
+ if (!job) {
53
+ return null;
54
+ }
55
+ const jobData = job.data;
56
+ return {
57
+ taskId: job.id,
58
+ rpcName: jobData.rpcName,
59
+ scheduledFor: job.startAfter || job.createdOn,
60
+ data: jobData.data,
61
+ session: jobData.session,
62
+ status: job.state,
63
+ };
64
+ }
65
+ /**
66
+ * Get all scheduled tasks with minimal info
67
+ */
68
+ async getAllTasks() {
69
+ // pg-boss doesn't have a simple way to list all pending jobs
70
+ // This would require querying the database directly or maintaining a separate index
71
+ // For now, return empty array - can be implemented if needed
72
+ return [];
73
+ }
74
+ /**
75
+ * Close - no-op as pg-boss lifecycle is managed by factory
76
+ */
77
+ async close() {
78
+ // No-op - pg-boss lifecycle is managed by factory
79
+ }
80
+ }
@@ -0,0 +1,37 @@
1
+ import PgBoss from 'pg-boss';
2
+ import { PgBossQueueService } from './pg-boss-queue-service.js';
3
+ import { PgBossQueueWorkers } from './pg-boss-queue-worker.js';
4
+ import { PgBossSchedulerService } from './pg-boss-scheduler-service.js';
5
+ import type { CoreServices, CoreSingletonServices, CreateSessionServices } from '@pikku/core';
6
+ /**
7
+ * Factory class for pg-boss services
8
+ * Manages a single PgBoss instance shared across queue, worker, and scheduler services
9
+ */
10
+ export declare class PgBossServiceFactory {
11
+ private pgBoss;
12
+ private queueService?;
13
+ private queueWorkers?;
14
+ private schedulerService?;
15
+ private initialized;
16
+ constructor(optionsOrInstance: PgBoss.ConstructorOptions | string | PgBoss);
17
+ /**
18
+ * Initialize the pg-boss instance
19
+ */
20
+ init(): Promise<void>;
21
+ /**
22
+ * Get the queue service for publishing jobs
23
+ */
24
+ getQueueService(): PgBossQueueService;
25
+ /**
26
+ * Get the queue workers for processing jobs
27
+ */
28
+ getQueueWorkers(singletonServices: CoreSingletonServices, createSessionServices?: CreateSessionServices<CoreSingletonServices, CoreServices, any>): PgBossQueueWorkers;
29
+ /**
30
+ * Get the scheduler service for managing recurring tasks
31
+ */
32
+ getSchedulerService(): PgBossSchedulerService;
33
+ /**
34
+ * Close all services and the pg-boss connection
35
+ */
36
+ close(): Promise<void>;
37
+ }
@@ -0,0 +1,71 @@
1
+ import PgBoss from 'pg-boss';
2
+ import { PgBossQueueService } from './pg-boss-queue-service.js';
3
+ import { PgBossQueueWorkers } from './pg-boss-queue-worker.js';
4
+ import { PgBossSchedulerService } from './pg-boss-scheduler-service.js';
5
+ /**
6
+ * Factory class for pg-boss services
7
+ * Manages a single PgBoss instance shared across queue, worker, and scheduler services
8
+ */
9
+ export class PgBossServiceFactory {
10
+ pgBoss;
11
+ queueService;
12
+ queueWorkers;
13
+ schedulerService;
14
+ initialized = false;
15
+ constructor(optionsOrInstance) {
16
+ if (optionsOrInstance instanceof PgBoss) {
17
+ this.pgBoss = optionsOrInstance;
18
+ }
19
+ else {
20
+ const options = typeof optionsOrInstance === 'string'
21
+ ? { connectionString: optionsOrInstance }
22
+ : optionsOrInstance;
23
+ this.pgBoss = new PgBoss(options);
24
+ }
25
+ }
26
+ /**
27
+ * Initialize the pg-boss instance
28
+ */
29
+ async init() {
30
+ if (!this.initialized) {
31
+ await this.pgBoss.start();
32
+ this.initialized = true;
33
+ }
34
+ }
35
+ /**
36
+ * Get the queue service for publishing jobs
37
+ */
38
+ getQueueService() {
39
+ if (!this.queueService) {
40
+ this.queueService = new PgBossQueueService(this.pgBoss);
41
+ }
42
+ return this.queueService;
43
+ }
44
+ /**
45
+ * Get the queue workers for processing jobs
46
+ */
47
+ getQueueWorkers(singletonServices, createSessionServices) {
48
+ if (!this.queueWorkers) {
49
+ this.queueWorkers = new PgBossQueueWorkers(this.pgBoss, singletonServices, createSessionServices);
50
+ }
51
+ return this.queueWorkers;
52
+ }
53
+ /**
54
+ * Get the scheduler service for managing recurring tasks
55
+ */
56
+ getSchedulerService() {
57
+ if (!this.schedulerService) {
58
+ this.schedulerService = new PgBossSchedulerService(this.pgBoss);
59
+ }
60
+ return this.schedulerService;
61
+ }
62
+ /**
63
+ * Close all services and the pg-boss connection
64
+ */
65
+ async close() {
66
+ if (this.initialized) {
67
+ await this.pgBoss.stop();
68
+ this.initialized = false;
69
+ }
70
+ }
71
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/queue-pg-boss",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "author": "yasser.fadl@gmail.com",
5
5
  "license": "MIT",
6
6
  "module": "dist/index.js",
@@ -13,10 +13,10 @@
13
13
  "release": "npm run build && npm test"
14
14
  },
15
15
  "peerDependencies": {
16
- "@pikku/core": "^0.10.0"
16
+ "@pikku/core": "^0.11.0"
17
17
  },
18
18
  "dependencies": {
19
- "pg-boss": "^11.1.1"
19
+ "pg-boss": "^11.1.2"
20
20
  },
21
21
  "devDependencies": {
22
22
  "typescript": "^5.9"
package/src/index.ts CHANGED
@@ -5,8 +5,10 @@
5
5
  * Provides job results, retries, priorities, and PostgreSQL-based queuing
6
6
  */
7
7
 
8
+ export { PgBossServiceFactory } from './pg-boss-service-factory.js'
8
9
  export { PgBossQueueService } from './pg-boss-queue-service.js'
9
10
  export { PgBossQueueWorkers } from './pg-boss-queue-worker.js'
11
+ export { PgBossSchedulerService } from './pg-boss-scheduler-service.js'
10
12
 
11
13
  // Re-export core queue types for convenience
12
14
  export type {
@@ -5,7 +5,7 @@ import { mapPgBossJobToQueueJob } from './utils.js'
5
5
  export const mapPikkuJobToPgBoss = (
6
6
  options?: JobOptions
7
7
  ): PgBoss.JobOptions => {
8
- const pgBossOptions: PgBoss.JobOptions = {}
8
+ const pgBossOptions: any = {}
9
9
 
10
10
  if (options?.priority !== undefined) {
11
11
  pgBossOptions.priority = options.priority
@@ -19,7 +19,34 @@ export const mapPikkuJobToPgBoss = (
19
19
  pgBossOptions.singletonKey = options.jobId
20
20
  }
21
21
 
22
- return pgBossOptions
22
+ // Map retry options
23
+ if (options?.attempts !== undefined) {
24
+ pgBossOptions.retryLimit = options.attempts - 1
25
+ }
26
+
27
+ if (options?.backoff !== undefined) {
28
+ if (typeof options.backoff === 'string') {
29
+ // If backoff is a string, assume it's 'exponential' or 'fixed'
30
+ pgBossOptions.retryBackoff = options.backoff === 'exponential'
31
+ } else if (typeof options.backoff === 'object') {
32
+ pgBossOptions.retryBackoff = options.backoff.type === 'exponential'
33
+ if (options.backoff.delay !== undefined) {
34
+ // pg-boss uses seconds, we use milliseconds
35
+ pgBossOptions.retryDelay = Math.floor(options.backoff.delay / 1000)
36
+ }
37
+ }
38
+ }
39
+
40
+ if (options?.removeOnComplete !== undefined) {
41
+ pgBossOptions.onComplete = options.removeOnComplete === 0
42
+ }
43
+
44
+ if (options?.removeOnFail !== undefined) {
45
+ // pg-boss doesn't have a direct equivalent, but we can track this for consistency
46
+ // Note: pg-boss keeps failed jobs by default
47
+ }
48
+
49
+ return pgBossOptions as PgBoss.JobOptions
23
50
  }
24
51
 
25
52
  /**
@@ -31,19 +58,8 @@ export class PgBossQueueService implements QueueService {
31
58
  readonly supportsResults = true
32
59
  protected pgBoss: PgBoss
33
60
 
34
- constructor(optionsOrUrl: PgBoss.ConstructorOptions | string) {
35
- if (typeof optionsOrUrl === 'string') {
36
- // If a string is provided, treat it as the connection URL
37
- optionsOrUrl = { connectionString: optionsOrUrl }
38
- }
39
- this.pgBoss = new PgBoss(optionsOrUrl)
40
- }
41
-
42
- /**
43
- * Initialize the pg-boss instance
44
- */
45
- async init(): Promise<void> {
46
- await this.pgBoss.start()
61
+ constructor(pgBoss: PgBoss) {
62
+ this.pgBoss = pgBoss
47
63
  }
48
64
 
49
65
  /**
@@ -54,6 +70,7 @@ export class PgBossQueueService implements QueueService {
54
70
  data: In,
55
71
  options?: JobOptions
56
72
  ): Promise<string> {
73
+ await this.pgBoss.createQueue(queueName)
57
74
  const jobId = await this.pgBoss.send(
58
75
  queueName,
59
76
  data as any,
@@ -78,11 +95,4 @@ export class PgBossQueueService implements QueueService {
78
95
  }
79
96
  return mapPgBossJobToQueueJob<T, R>(job, this.pgBoss)
80
97
  }
81
-
82
- /**
83
- * Close the pg-boss connection
84
- */
85
- async close(): Promise<void> {
86
- await this.pgBoss.stop()
87
- }
88
98
  }
@@ -123,7 +123,7 @@ export class PgBossQueueWorkers implements QueueWorkers {
123
123
  private activeWorkers = new Map<string, string>()
124
124
 
125
125
  constructor(
126
- options: PgBoss.ConstructorOptions | string,
126
+ pgBoss: PgBoss,
127
127
  private singletonServices: CoreSingletonServices,
128
128
  private createSessionServices?: CreateSessionServices<
129
129
  CoreSingletonServices,
@@ -131,18 +131,7 @@ export class PgBossQueueWorkers implements QueueWorkers {
131
131
  any
132
132
  >
133
133
  ) {
134
- if (typeof options === 'string') {
135
- // If a string is provided, treat it as the connection string
136
- options = { connectionString: options }
137
- }
138
- this.pgBoss = new PgBoss(options)
139
- }
140
-
141
- /**
142
- * Initialize pg-boss
143
- */
144
- async init(): Promise<void> {
145
- await this.pgBoss.start()
134
+ this.pgBoss = pgBoss
146
135
  }
147
136
 
148
137
  /**
@@ -195,11 +184,4 @@ export class PgBossQueueWorkers implements QueueWorkers {
195
184
  }
196
185
  )
197
186
  }
198
-
199
- /**
200
- * Close all workers and connections
201
- */
202
- async close(): Promise<void> {
203
- await this.pgBoss.stop()
204
- }
205
187
  }
@@ -0,0 +1,121 @@
1
+ import PgBoss from 'pg-boss'
2
+ import {
3
+ SchedulerService,
4
+ ScheduledTaskInfo,
5
+ ScheduledTaskSummary,
6
+ CoreUserSession,
7
+ parseDurationString,
8
+ } from '@pikku/core'
9
+
10
+ /**
11
+ * Data stored in scheduled job
12
+ */
13
+ interface ScheduledJobData {
14
+ rpcName: string
15
+ data?: any
16
+ session?: CoreUserSession
17
+ }
18
+
19
+ /**
20
+ * pg-boss scheduler service implementation
21
+ * Uses pg-boss's schedule() API to store recurring jobs in PostgreSQL
22
+ */
23
+ export class PgBossSchedulerService extends SchedulerService {
24
+ constructor(private pgBoss: PgBoss) {
25
+ super()
26
+ }
27
+
28
+ /**
29
+ * Initialize - no-op as pg-boss is already initialized by factory
30
+ */
31
+ async init(): Promise<void> {
32
+ // No-op - pg-boss is already initialized by factory
33
+ }
34
+
35
+ /**
36
+ * Schedule a one-off delayed RPC call
37
+ * Uses pg-boss send() with startAfter for one-time execution
38
+ */
39
+ async scheduleRPC(
40
+ delay: number | string,
41
+ rpcName: string,
42
+ data?: any,
43
+ session?: CoreUserSession
44
+ ): Promise<string> {
45
+ // Parse delay if it's a string
46
+ const delayMs =
47
+ typeof delay === 'string' ? parseDurationString(delay) : delay
48
+
49
+ const jobData: ScheduledJobData = {
50
+ rpcName,
51
+ data,
52
+ session,
53
+ }
54
+
55
+ // Use send() with startAfter for one-off delayed execution
56
+ const taskId = await this.pgBoss.send(
57
+ 'pikku-remote-internal-rpc',
58
+ jobData,
59
+ {
60
+ startAfter: new Date(Date.now() + delayMs),
61
+ singletonKey: `${rpcName}-${Date.now()}`, // Ensure uniqueness
62
+ }
63
+ )
64
+
65
+ if (!taskId) {
66
+ throw new Error('Failed to schedule RPC')
67
+ }
68
+
69
+ return taskId
70
+ }
71
+
72
+ /**
73
+ * Unschedule (cancel) a task by ID
74
+ */
75
+ async unschedule(taskId: string): Promise<boolean> {
76
+ await this.pgBoss.cancel('pikku-remote-internal-rpc', taskId)
77
+ return true
78
+ }
79
+
80
+ /**
81
+ * Get a scheduled task by ID with full details
82
+ */
83
+ async getTask(taskId: string): Promise<ScheduledTaskInfo | null> {
84
+ const job = await this.pgBoss.getJobById(
85
+ 'pikku-remote-internal-rpc',
86
+ taskId
87
+ )
88
+
89
+ if (!job) {
90
+ return null
91
+ }
92
+
93
+ const jobData = job.data as ScheduledJobData
94
+
95
+ return {
96
+ taskId: job.id,
97
+ rpcName: jobData.rpcName,
98
+ scheduledFor: job.startAfter || job.createdOn,
99
+ data: jobData.data,
100
+ session: jobData.session,
101
+ status: job.state as any,
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Get all scheduled tasks with minimal info
107
+ */
108
+ async getAllTasks(): Promise<ScheduledTaskSummary[]> {
109
+ // pg-boss doesn't have a simple way to list all pending jobs
110
+ // This would require querying the database directly or maintaining a separate index
111
+ // For now, return empty array - can be implemented if needed
112
+ return []
113
+ }
114
+
115
+ /**
116
+ * Close - no-op as pg-boss lifecycle is managed by factory
117
+ */
118
+ async close(): Promise<void> {
119
+ // No-op - pg-boss lifecycle is managed by factory
120
+ }
121
+ }
@@ -0,0 +1,94 @@
1
+ import PgBoss from 'pg-boss'
2
+ import { PgBossQueueService } from './pg-boss-queue-service.js'
3
+ import { PgBossQueueWorkers } from './pg-boss-queue-worker.js'
4
+ import { PgBossSchedulerService } from './pg-boss-scheduler-service.js'
5
+ import type {
6
+ CoreServices,
7
+ CoreSingletonServices,
8
+ CreateSessionServices,
9
+ } from '@pikku/core'
10
+
11
+ /**
12
+ * Factory class for pg-boss services
13
+ * Manages a single PgBoss instance shared across queue, worker, and scheduler services
14
+ */
15
+ export class PgBossServiceFactory {
16
+ private pgBoss: PgBoss
17
+ private queueService?: PgBossQueueService
18
+ private queueWorkers?: PgBossQueueWorkers
19
+ private schedulerService?: PgBossSchedulerService
20
+ private initialized = false
21
+
22
+ constructor(optionsOrInstance: PgBoss.ConstructorOptions | string | PgBoss) {
23
+ if (optionsOrInstance instanceof PgBoss) {
24
+ this.pgBoss = optionsOrInstance
25
+ } else {
26
+ const options =
27
+ typeof optionsOrInstance === 'string'
28
+ ? { connectionString: optionsOrInstance }
29
+ : optionsOrInstance
30
+ this.pgBoss = new PgBoss(options)
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Initialize the pg-boss instance
36
+ */
37
+ async init(): Promise<void> {
38
+ if (!this.initialized) {
39
+ await this.pgBoss.start()
40
+ this.initialized = true
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Get the queue service for publishing jobs
46
+ */
47
+ getQueueService(): PgBossQueueService {
48
+ if (!this.queueService) {
49
+ this.queueService = new PgBossQueueService(this.pgBoss)
50
+ }
51
+ return this.queueService
52
+ }
53
+
54
+ /**
55
+ * Get the queue workers for processing jobs
56
+ */
57
+ getQueueWorkers(
58
+ singletonServices: CoreSingletonServices,
59
+ createSessionServices?: CreateSessionServices<
60
+ CoreSingletonServices,
61
+ CoreServices,
62
+ any
63
+ >
64
+ ): PgBossQueueWorkers {
65
+ if (!this.queueWorkers) {
66
+ this.queueWorkers = new PgBossQueueWorkers(
67
+ this.pgBoss,
68
+ singletonServices,
69
+ createSessionServices
70
+ )
71
+ }
72
+ return this.queueWorkers
73
+ }
74
+
75
+ /**
76
+ * Get the scheduler service for managing recurring tasks
77
+ */
78
+ getSchedulerService(): PgBossSchedulerService {
79
+ if (!this.schedulerService) {
80
+ this.schedulerService = new PgBossSchedulerService(this.pgBoss)
81
+ }
82
+ return this.schedulerService
83
+ }
84
+
85
+ /**
86
+ * Close all services and the pg-boss connection
87
+ */
88
+ async close(): Promise<void> {
89
+ if (this.initialized) {
90
+ await this.pgBoss.stop()
91
+ this.initialized = false
92
+ }
93
+ }
94
+ }
@@ -1 +1 @@
1
- {"root":["./src/index.ts","./src/pg-boss-queue-service.ts","./src/pg-boss-queue-worker.ts","./src/utils.ts"],"version":"5.9.3"}
1
+ {"root":["./src/index.ts","./src/pg-boss-queue-service.ts","./src/pg-boss-queue-worker.ts","./src/pg-boss-scheduler-service.ts","./src/pg-boss-service-factory.ts","./src/utils.ts"],"version":"5.9.3"}