@pikku/queue-pg-boss 0.10.0 → 0.11.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/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ ## 0.11.0
2
+
3
+ ## 0.11.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 4b811db: chore: updating all dependencies
8
+ - 06e1a31: breaking: change session services to wire services
9
+ - Updated dependencies [4b811db]
10
+ - Updated dependencies [e12a00c]
11
+ - Updated dependencies [4579434]
12
+ - Updated dependencies [28aeb7f]
13
+ - Updated dependencies [ce902b1]
14
+ - Updated dependencies [06e1a31]
15
+ - @pikku/core@0.11.1
16
+
17
+ ### Minor Changes
18
+
19
+ - Add scheduler service implementation
20
+ - Add service factory for queue and scheduler creation
21
+
1
22
  # @pikku/queue-pg-boss
2
23
 
3
24
  ## 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';
@@ -1,6 +1,6 @@
1
- import PgBoss from 'pg-boss';
1
+ import { PgBoss, Request as PgBossRequest } from 'pg-boss';
2
2
  import type { QueueService, QueueJob, JobOptions } from '@pikku/core/queue';
3
- export declare const mapPikkuJobToPgBoss: (options?: JobOptions) => PgBoss.JobOptions;
3
+ export declare const mapPikkuJobToPgBoss: (options?: JobOptions) => PgBossRequest["options"];
4
4
  /**
5
5
  * pg-boss/PostgreSQL queue service implementation
6
6
  * Supports job results, retries, and PostgreSQL-based queuing
@@ -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
  }
@@ -1,14 +1,14 @@
1
- import PgBoss from 'pg-boss';
1
+ import { PgBoss, type WorkOptions } from 'pg-boss';
2
2
  import type { QueueWorkers, PikkuWorkerConfig, QueueConfigMapping, ConfigValidationResult } from '@pikku/core/queue';
3
- import { CoreServices, CoreSingletonServices, CreateSessionServices } from '@pikku/core';
4
- export declare const mapPikkuWorkerToPgBoss: (workerConfig?: PikkuWorkerConfig) => PgBoss.WorkOptions;
3
+ import { CoreServices, CoreSingletonServices, CreateWireServices } from '@pikku/core';
4
+ export declare const mapPikkuWorkerToPgBoss: (workerConfig?: PikkuWorkerConfig) => WorkOptions;
5
5
  /**
6
6
  * pg-boss/PostgreSQL queue worker implementation
7
7
  * Supports job results, retries, and PostgreSQL-based queuing
8
8
  */
9
9
  export declare class PgBossQueueWorkers implements QueueWorkers {
10
10
  private singletonServices;
11
- private createSessionServices?;
11
+ private createWireServices?;
12
12
  readonly name = "pg-boss";
13
13
  readonly supportsResults = true;
14
14
  /**
@@ -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, createWireServices?: CreateWireServices<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) => {
@@ -17,7 +16,7 @@ export const mapPikkuWorkerToPgBoss = (workerConfig) => {
17
16
  */
18
17
  export class PgBossQueueWorkers {
19
18
  singletonServices;
20
- createSessionServices;
19
+ createWireServices;
21
20
  name = 'pg-boss';
22
21
  supportsResults = true;
23
22
  /**
@@ -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, createWireServices) {
88
87
  this.singletonServices = singletonServices;
89
- 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();
88
+ this.createWireServices = createWireServices;
89
+ this.pgBoss = pgBoss;
101
90
  }
102
91
  /**
103
92
  * Scan state and register all compatible processors
@@ -119,7 +108,7 @@ export class PgBossQueueWorkers {
119
108
  try {
120
109
  await runQueueJob({
121
110
  singletonServices: this.singletonServices,
122
- createSessionServices: this.createSessionServices,
111
+ createWireServices: this.createWireServices,
123
112
  job: mapPgBossJobToQueueJob(job, this.pgBoss),
124
113
  });
125
114
  }
@@ -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, type ConstructorOptions } 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, CreateWireServices } 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: 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, createWireServices?: CreateWireServices<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, createWireServices) {
48
+ if (!this.queueWorkers) {
49
+ this.queueWorkers = new PgBossQueueWorkers(this.pgBoss, singletonServices, createWireServices);
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/dist/utils.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { QueueJob } from '@pikku/core/queue';
2
- import PgBoss from 'pg-boss';
2
+ import { PgBoss, type JobWithMetadata } from 'pg-boss';
3
3
  /**
4
4
  * Map pg-boss job to QueueJob interface
5
5
  */
6
- export declare const mapPgBossJobToQueueJob: <In, Out>(pgBossJob: PgBoss.JobWithMetadata<In>, pgBossInstance: PgBoss) => QueueJob<In, Out>;
6
+ export declare const mapPgBossJobToQueueJob: <In, Out>(pgBossJob: JobWithMetadata<In>, pgBossInstance: PgBoss) => QueueJob<In, Out>;
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.1",
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.1"
17
17
  },
18
18
  "dependencies": {
19
- "pg-boss": "^11.1.1"
19
+ "pg-boss": "^12.2.0"
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 {
@@ -1,11 +1,11 @@
1
- import PgBoss from 'pg-boss'
1
+ import { PgBoss, Request as PgBossRequest } from 'pg-boss'
2
2
  import type { QueueService, QueueJob, JobOptions } from '@pikku/core/queue'
3
3
  import { mapPgBossJobToQueueJob } from './utils.js'
4
4
 
5
5
  export const mapPikkuJobToPgBoss = (
6
6
  options?: JobOptions
7
- ): PgBoss.JobOptions => {
8
- const pgBossOptions: PgBoss.JobOptions = {}
7
+ ): PgBossRequest['options'] => {
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 PgBossRequest['options']
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
  }
@@ -1,4 +1,4 @@
1
- import PgBoss from 'pg-boss'
1
+ import { PgBoss, type WorkOptions } from 'pg-boss'
2
2
  import type {
3
3
  QueueWorkers,
4
4
  PikkuWorkerConfig,
@@ -14,14 +14,14 @@ import {
14
14
  import {
15
15
  CoreServices,
16
16
  CoreSingletonServices,
17
- CreateSessionServices,
17
+ CreateWireServices,
18
18
  } from '@pikku/core'
19
19
  import { mapPgBossJobToQueueJob } from './utils.js'
20
20
 
21
21
  export const mapPikkuWorkerToPgBoss = (
22
22
  workerConfig?: PikkuWorkerConfig
23
- ): PgBoss.WorkOptions => {
24
- const workerOptions: PgBoss.WorkOptions = {}
23
+ ): WorkOptions => {
24
+ const workerOptions: WorkOptions = {}
25
25
 
26
26
  if (workerConfig?.batchSize !== undefined) {
27
27
  workerOptions.batchSize = workerConfig.batchSize
@@ -123,26 +123,15 @@ 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
- private createSessionServices?: CreateSessionServices<
128
+ private createWireServices?: CreateWireServices<
129
129
  CoreSingletonServices,
130
130
  CoreServices,
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
  /**
@@ -173,7 +162,7 @@ export class PgBossQueueWorkers implements QueueWorkers {
173
162
  try {
174
163
  await runQueueJob({
175
164
  singletonServices: this.singletonServices,
176
- createSessionServices: this.createSessionServices,
165
+ createWireServices: this.createWireServices,
177
166
  job: mapPgBossJobToQueueJob(job, this.pgBoss),
178
167
  })
179
168
  } catch (error: unknown) {
@@ -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, type ConstructorOptions } 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
+ CreateWireServices,
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: 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
+ createWireServices?: CreateWireServices<
60
+ CoreSingletonServices,
61
+ CoreServices,
62
+ any
63
+ >
64
+ ): PgBossQueueWorkers {
65
+ if (!this.queueWorkers) {
66
+ this.queueWorkers = new PgBossQueueWorkers(
67
+ this.pgBoss,
68
+ singletonServices,
69
+ createWireServices
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
+ }
package/src/utils.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { QueueJob, QueueJobStatus } from '@pikku/core/queue'
2
- import PgBoss from 'pg-boss'
2
+ import { PgBoss, type JobWithMetadata } from 'pg-boss'
3
3
 
4
4
  /**
5
5
  * Map pg-boss job state to our queue job status
@@ -26,7 +26,7 @@ const mapPgBossStateToStatus = (state: string): QueueJobStatus => {
26
26
  * Map pg-boss job to QueueJob interface
27
27
  */
28
28
  export const mapPgBossJobToQueueJob = <In, Out>(
29
- pgBossJob: PgBoss.JobWithMetadata<In>,
29
+ pgBossJob: JobWithMetadata<In>,
30
30
  pgBossInstance: PgBoss
31
31
  ): QueueJob<In, Out> => {
32
32
  return {
@@ -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"}