@pikku/queue-pg-boss 0.12.0 → 0.12.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,5 +1,23 @@
1
1
  ## 0.12.0
2
2
 
3
+ ## 0.12.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 62a8725: Fix pg-boss queue initialisation: create the queue before registering a worker to avoid a race condition on first startup. Also sanitise scheduler names to meet pg-boss naming constraints.
8
+ - Updated dependencies [62a8725]
9
+ - Updated dependencies [a3bdb0d]
10
+ - Updated dependencies [e0349ff]
11
+ - Updated dependencies [62a8725]
12
+ - Updated dependencies [e04531f]
13
+ - Updated dependencies [62a8725]
14
+ - Updated dependencies [a83efb8]
15
+ - Updated dependencies [8eed717]
16
+ - Updated dependencies [62a8725]
17
+ - Updated dependencies [62a8725]
18
+ - Updated dependencies [62a8725]
19
+ - @pikku/core@0.12.1
20
+
3
21
  ### New Features
4
22
 
5
23
  - `setServices()`, `start()`, `stop()` lifecycle methods on scheduler and queue workers
@@ -1,4 +1,4 @@
1
- import { PgBoss, Request as PgBossRequest } from 'pg-boss';
1
+ import type { PgBoss, Request as PgBossRequest } from 'pg-boss';
2
2
  import type { QueueService, QueueJob, JobOptions } from '@pikku/core/queue';
3
3
  export declare const mapPikkuJobToPgBoss: (options?: JobOptions) => PgBossRequest["options"];
4
4
  /**
@@ -1,6 +1,5 @@
1
- import { PgBoss, type WorkOptions } from 'pg-boss';
2
- import type { QueueWorkers, PikkuWorkerConfig, QueueConfigMapping, ConfigValidationResult, QueueJob } from '@pikku/core/queue';
3
- import type { Logger } from '@pikku/core/services';
1
+ import type { PgBoss, WorkOptions } from 'pg-boss';
2
+ import type { QueueWorkers, PikkuWorkerConfig, QueueConfigMapping, ConfigValidationResult } from '@pikku/core/queue';
4
3
  export declare const mapPikkuWorkerToPgBoss: (workerConfig?: PikkuWorkerConfig) => WorkOptions;
5
4
  /**
6
5
  * pg-boss/PostgreSQL queue worker implementation
@@ -16,13 +15,7 @@ export declare class PgBossQueueWorkers implements QueueWorkers {
16
15
  readonly configMappings: QueueConfigMapping;
17
16
  private pgBoss;
18
17
  private activeWorkers;
19
- private runJob?;
20
- private logger?;
21
18
  constructor(pgBoss: PgBoss);
22
- setJobRunner(runJob: (params: {
23
- job: QueueJob;
24
- updateProgress?: (progress: number | string | object) => Promise<void>;
25
- }) => Promise<void>, logger: Logger): void;
26
19
  /**
27
20
  * Scan state and register all compatible processors
28
21
  */
@@ -1,4 +1,5 @@
1
- import { registerQueueWorkers, QueueJobFailedError, QueueJobDiscardedError, } from '@pikku/core/queue';
1
+ import { registerQueueWorkers, runQueueJob, QueueJobFailedError, QueueJobDiscardedError, } from '@pikku/core/queue';
2
+ import { pikkuState } from '@pikku/core/internal';
2
3
  import { mapPgBossJobToQueueJob } from './utils.js';
3
4
  export const mapPikkuWorkerToPgBoss = (workerConfig) => {
4
5
  const workerOptions = {
@@ -83,36 +84,28 @@ export class PgBossQueueWorkers {
83
84
  };
84
85
  pgBoss;
85
86
  activeWorkers = new Map();
86
- runJob;
87
- logger;
88
87
  constructor(pgBoss) {
89
88
  this.pgBoss = pgBoss;
90
89
  }
91
- setJobRunner(runJob, logger) {
92
- this.runJob = runJob;
93
- this.logger = logger;
94
- }
95
90
  /**
96
91
  * Scan state and register all compatible processors
97
92
  */
98
93
  async registerQueues() {
99
- if (!this.runJob || !this.logger) {
100
- throw new Error('PgBossQueueWorkers requires setJobRunner() before registerQueues()');
101
- }
102
- return await registerQueueWorkers(this.configMappings, this.logger, async (queueName, processor) => {
94
+ const logger = pikkuState(null, 'package', 'singletonServices').logger;
95
+ return await registerQueueWorkers(this.configMappings, logger, async (queueName, processor) => {
103
96
  await this.pgBoss.createQueue(queueName);
104
97
  const workerId = await this.pgBoss.work(processor.name, {
105
98
  ...mapPikkuWorkerToPgBoss(processor.config),
106
99
  includeMetadata: true,
107
100
  }, async (jobs) => {
108
101
  if (!jobs || jobs.length === 0) {
109
- this.logger.warn(`No jobs received for queue ${queueName}`);
102
+ logger.warn(`No jobs received for queue ${queueName}`);
110
103
  return;
111
104
  }
112
105
  // Process all jobs in parallel
113
106
  await Promise.all(jobs.map(async (job) => {
114
107
  try {
115
- await this.runJob({
108
+ await runQueueJob({
116
109
  job: mapPgBossJobToQueueJob(job, this.pgBoss),
117
110
  });
118
111
  }
@@ -125,7 +118,7 @@ export class PgBossQueueWorkers {
125
118
  }
126
119
  else if (error instanceof QueueJobDiscardedError) {
127
120
  // For pg-boss, complete the job successfully to discard it
128
- this.logger.info(`PgBoss job ${job.id} discarded: ${error.message}`);
121
+ logger.info(`PgBoss job ${job.id} discarded: ${error.message}`);
129
122
  await this.pgBoss.complete(queueName, job.id);
130
123
  }
131
124
  else {
@@ -1,5 +1,6 @@
1
- import { PgBoss } from 'pg-boss';
2
- import { SchedulerService, SchedulerRuntimeHandlers, ScheduledTaskInfo, ScheduledTaskSummary, CoreUserSession } from '@pikku/core';
1
+ import type { PgBoss } from 'pg-boss';
2
+ import type { ScheduledTaskInfo, ScheduledTaskSummary, CoreUserSession } from '@pikku/core';
3
+ import { SchedulerService } from '@pikku/core';
3
4
  /**
4
5
  * pg-boss scheduler service implementation
5
6
  * Uses pg-boss's schedule() API to store recurring jobs in PostgreSQL.
@@ -8,14 +9,7 @@ import { SchedulerService, SchedulerRuntimeHandlers, ScheduledTaskInfo, Schedule
8
9
  export declare class PgBossSchedulerService extends SchedulerService {
9
10
  private pgBoss;
10
11
  private scheduledCronNames;
11
- private handlers?;
12
12
  constructor(pgBoss: PgBoss);
13
- /**
14
- * Set services needed for processing recurring tasks.
15
- * Must be called before start() since the scheduler is typically
16
- * created before singletonServices are available.
17
- */
18
- setServices(handlers: SchedulerRuntimeHandlers): void;
19
13
  /**
20
14
  * Initialize - no-op as pg-boss is already initialized by factory
21
15
  */
@@ -1,5 +1,6 @@
1
- import { SchedulerService, parseDurationString, } from '@pikku/core';
2
- import { getScheduledTasks } from '@pikku/core/scheduler';
1
+ import { SchedulerService, parseDurationString } from '@pikku/core';
2
+ import { pikkuState } from '@pikku/core/internal';
3
+ import { runScheduledTask, getScheduledTasks } from '@pikku/core/scheduler';
3
4
  /**
4
5
  * pg-boss scheduler service implementation
5
6
  * Uses pg-boss's schedule() API to store recurring jobs in PostgreSQL.
@@ -8,19 +9,10 @@ import { getScheduledTasks } from '@pikku/core/scheduler';
8
9
  export class PgBossSchedulerService extends SchedulerService {
9
10
  pgBoss;
10
11
  scheduledCronNames = [];
11
- handlers;
12
12
  constructor(pgBoss) {
13
13
  super();
14
14
  this.pgBoss = pgBoss;
15
15
  }
16
- /**
17
- * Set services needed for processing recurring tasks.
18
- * Must be called before start() since the scheduler is typically
19
- * created before singletonServices are available.
20
- */
21
- setServices(handlers) {
22
- this.handlers = handlers;
23
- }
24
16
  /**
25
17
  * Initialize - no-op as pg-boss is already initialized by factory
26
18
  */
@@ -94,12 +86,11 @@ export class PgBossSchedulerService extends SchedulerService {
94
86
  * Registers a pg-boss worker to process recurring jobs via runScheduledTask.
95
87
  */
96
88
  async start() {
97
- if (!this.handlers) {
98
- throw new Error('PgBossSchedulerService requires setServices() before start()');
99
- }
89
+ const logger = pikkuState(null, 'package', 'singletonServices').logger;
100
90
  const scheduledTasks = getScheduledTasks();
101
91
  for (const [name, task] of scheduledTasks) {
102
- const cronName = `pikku-recurring-scheduled-task:${name}`;
92
+ const cronName = `pikku-recurring-scheduled-task_${name.replace(/[^a-zA-Z0-9_\-.]/g, '_')}`;
93
+ await this.pgBoss.createQueue(cronName);
103
94
  await this.pgBoss.schedule(cronName, task.schedule, {
104
95
  rpcName: name,
105
96
  });
@@ -108,8 +99,8 @@ export class PgBossSchedulerService extends SchedulerService {
108
99
  await this.pgBoss.work(cronName, async (jobs) => {
109
100
  for (const job of jobs) {
110
101
  const { rpcName } = job.data;
111
- this.handlers.logger.info(`Running scheduled task: ${rpcName}`);
112
- await this.handlers.runScheduledTask(rpcName);
102
+ logger.info(`Running scheduled task: ${rpcName}`);
103
+ await runScheduledTask({ name: rpcName });
113
104
  }
114
105
  });
115
106
  }
@@ -2,7 +2,6 @@ import { PgBoss, type ConstructorOptions } from 'pg-boss';
2
2
  import { PgBossQueueService } from './pg-boss-queue-service.js';
3
3
  import { PgBossQueueWorkers } from './pg-boss-queue-worker.js';
4
4
  import { PgBossSchedulerService } from './pg-boss-scheduler-service.js';
5
- import type { CoreSingletonServices, CreateWireServices } from '@pikku/core';
6
5
  /**
7
6
  * Factory class for pg-boss services
8
7
  * Manages a single PgBoss instance shared across queue, worker, and scheduler services
@@ -25,12 +24,11 @@ export declare class PgBossServiceFactory {
25
24
  /**
26
25
  * Get the queue workers for processing jobs
27
26
  */
28
- getQueueWorkers(singletonServices: CoreSingletonServices, createWireServices?: CreateWireServices): PgBossQueueWorkers;
27
+ getQueueWorkers(): PgBossQueueWorkers;
29
28
  /**
30
29
  * Get the scheduler service for managing recurring tasks
31
30
  */
32
31
  getSchedulerService(): PgBossSchedulerService;
33
- setSchedulerRuntime(singletonServices: CoreSingletonServices, createWireServices?: CreateWireServices): void;
34
32
  /**
35
33
  * Close all services and the pg-boss connection
36
34
  */
@@ -2,8 +2,6 @@ import { PgBoss } from 'pg-boss';
2
2
  import { PgBossQueueService } from './pg-boss-queue-service.js';
3
3
  import { PgBossQueueWorkers } from './pg-boss-queue-worker.js';
4
4
  import { PgBossSchedulerService } from './pg-boss-scheduler-service.js';
5
- import { createQueueJobRunner } from '@pikku/core/queue';
6
- import { createSchedulerRuntimeHandlers } from '@pikku/core/scheduler';
7
5
  /**
8
6
  * Factory class for pg-boss services
9
7
  * Manages a single PgBoss instance shared across queue, worker, and scheduler services
@@ -46,11 +44,10 @@ export class PgBossServiceFactory {
46
44
  /**
47
45
  * Get the queue workers for processing jobs
48
46
  */
49
- getQueueWorkers(singletonServices, createWireServices) {
47
+ getQueueWorkers() {
50
48
  if (!this.queueWorkers) {
51
49
  this.queueWorkers = new PgBossQueueWorkers(this.pgBoss);
52
50
  }
53
- this.queueWorkers.setJobRunner(createQueueJobRunner({ singletonServices, createWireServices }), singletonServices.logger);
54
51
  return this.queueWorkers;
55
52
  }
56
53
  /**
@@ -62,12 +59,6 @@ export class PgBossServiceFactory {
62
59
  }
63
60
  return this.schedulerService;
64
61
  }
65
- setSchedulerRuntime(singletonServices, createWireServices) {
66
- this.getSchedulerService().setServices(createSchedulerRuntimeHandlers({
67
- singletonServices,
68
- createWireServices,
69
- }));
70
- }
71
62
  /**
72
63
  * Close all services and the pg-boss connection
73
64
  */
package/dist/utils.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { QueueJob } from '@pikku/core/queue';
2
- import { PgBoss, type JobWithMetadata } from 'pg-boss';
1
+ import type { QueueJob } from '@pikku/core/queue';
2
+ import type { PgBoss, JobWithMetadata } from 'pg-boss';
3
3
  /**
4
4
  * Map pg-boss job to QueueJob interface
5
5
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/queue-pg-boss",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "author": "yasser.fadl@gmail.com",
5
5
  "license": "MIT",
6
6
  "module": "dist/index.js",
@@ -13,7 +13,7 @@
13
13
  "release": "npm run build && npm test"
14
14
  },
15
15
  "peerDependencies": {
16
- "@pikku/core": "^0.12.0"
16
+ "@pikku/core": "^0.12.1"
17
17
  },
18
18
  "dependencies": {
19
19
  "pg-boss": "^12.9.0"
@@ -1,4 +1,4 @@
1
- import { PgBoss, Request as PgBossRequest } from 'pg-boss'
1
+ import type { 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
 
@@ -1,17 +1,17 @@
1
- import { PgBoss, type WorkOptions } from 'pg-boss'
1
+ import type { PgBoss, WorkOptions } from 'pg-boss'
2
2
  import type {
3
3
  QueueWorkers,
4
4
  PikkuWorkerConfig,
5
5
  QueueConfigMapping,
6
6
  ConfigValidationResult,
7
- QueueJob,
8
7
  } from '@pikku/core/queue'
9
8
  import {
10
9
  registerQueueWorkers,
10
+ runQueueJob,
11
11
  QueueJobFailedError,
12
12
  QueueJobDiscardedError,
13
13
  } from '@pikku/core/queue'
14
- import type { Logger } from '@pikku/core/services'
14
+ import { pikkuState } from '@pikku/core/internal'
15
15
  import { mapPgBossJobToQueueJob } from './utils.js'
16
16
 
17
17
  export const mapPikkuWorkerToPgBoss = (
@@ -120,42 +120,20 @@ export class PgBossQueueWorkers implements QueueWorkers {
120
120
 
121
121
  private pgBoss: PgBoss
122
122
  private activeWorkers = new Map<string, string>()
123
- private runJob?:
124
- | ((params: {
125
- job: QueueJob
126
- updateProgress?: (progress: number | string | object) => Promise<void>
127
- }) => Promise<void>)
128
- | undefined
129
- private logger?: Logger
130
123
 
131
124
  constructor(pgBoss: PgBoss) {
132
125
  this.pgBoss = pgBoss
133
126
  }
134
127
 
135
- setJobRunner(
136
- runJob: (params: {
137
- job: QueueJob
138
- updateProgress?: (progress: number | string | object) => Promise<void>
139
- }) => Promise<void>,
140
- logger: Logger
141
- ): void {
142
- this.runJob = runJob
143
- this.logger = logger
144
- }
145
-
146
128
  /**
147
129
  * Scan state and register all compatible processors
148
130
  */
149
131
  async registerQueues(): Promise<Record<string, ConfigValidationResult[]>> {
150
- if (!this.runJob || !this.logger) {
151
- throw new Error(
152
- 'PgBossQueueWorkers requires setJobRunner() before registerQueues()'
153
- )
154
- }
132
+ const logger = pikkuState(null, 'package', 'singletonServices')!.logger
155
133
 
156
134
  return await registerQueueWorkers(
157
135
  this.configMappings,
158
- this.logger,
136
+ logger,
159
137
  async (queueName, processor) => {
160
138
  await this.pgBoss.createQueue(queueName)
161
139
  const workerId = await this.pgBoss.work<any>(
@@ -166,14 +144,14 @@ export class PgBossQueueWorkers implements QueueWorkers {
166
144
  },
167
145
  async (jobs) => {
168
146
  if (!jobs || jobs.length === 0) {
169
- this.logger!.warn(`No jobs received for queue ${queueName}`)
147
+ logger.warn(`No jobs received for queue ${queueName}`)
170
148
  return
171
149
  }
172
150
  // Process all jobs in parallel
173
151
  await Promise.all(
174
152
  jobs.map(async (job) => {
175
153
  try {
176
- await this.runJob!({
154
+ await runQueueJob({
177
155
  job: mapPgBossJobToQueueJob(job, this.pgBoss),
178
156
  })
179
157
  } catch (error: unknown) {
@@ -184,7 +162,7 @@ export class PgBossQueueWorkers implements QueueWorkers {
184
162
  })
185
163
  } else if (error instanceof QueueJobDiscardedError) {
186
164
  // For pg-boss, complete the job successfully to discard it
187
- this.logger!.info(
165
+ logger.info(
188
166
  `PgBoss job ${job.id} discarded: ${error.message}`
189
167
  )
190
168
  await this.pgBoss.complete(queueName, job.id)
@@ -1,13 +1,12 @@
1
- import { PgBoss } from 'pg-boss'
2
- import {
3
- SchedulerService,
4
- SchedulerRuntimeHandlers,
1
+ import type { PgBoss } from 'pg-boss'
2
+ import type {
5
3
  ScheduledTaskInfo,
6
4
  ScheduledTaskSummary,
7
5
  CoreUserSession,
8
- parseDurationString,
9
6
  } from '@pikku/core'
10
- import { getScheduledTasks } from '@pikku/core/scheduler'
7
+ import { SchedulerService, parseDurationString } from '@pikku/core'
8
+ import { pikkuState } from '@pikku/core/internal'
9
+ import { runScheduledTask, getScheduledTasks } from '@pikku/core/scheduler'
11
10
 
12
11
  /**
13
12
  * Data stored in scheduled job
@@ -25,21 +24,11 @@ interface ScheduledJobData {
25
24
  */
26
25
  export class PgBossSchedulerService extends SchedulerService {
27
26
  private scheduledCronNames: string[] = []
28
- private handlers?: SchedulerRuntimeHandlers
29
27
 
30
28
  constructor(private pgBoss: PgBoss) {
31
29
  super()
32
30
  }
33
31
 
34
- /**
35
- * Set services needed for processing recurring tasks.
36
- * Must be called before start() since the scheduler is typically
37
- * created before singletonServices are available.
38
- */
39
- setServices(handlers: SchedulerRuntimeHandlers): void {
40
- this.handlers = handlers
41
- }
42
-
43
32
  /**
44
33
  * Initialize - no-op as pg-boss is already initialized by factory
45
34
  */
@@ -139,16 +128,12 @@ export class PgBossSchedulerService extends SchedulerService {
139
128
  * Registers a pg-boss worker to process recurring jobs via runScheduledTask.
140
129
  */
141
130
  async start(): Promise<void> {
142
- if (!this.handlers) {
143
- throw new Error(
144
- 'PgBossSchedulerService requires setServices() before start()'
145
- )
146
- }
147
-
131
+ const logger = pikkuState(null, 'package', 'singletonServices')!.logger
148
132
  const scheduledTasks = getScheduledTasks()
149
133
 
150
134
  for (const [name, task] of scheduledTasks) {
151
- const cronName = `pikku-recurring-scheduled-task:${name}`
135
+ const cronName = `pikku-recurring-scheduled-task_${name.replace(/[^a-zA-Z0-9_\-.]/g, '_')}`
136
+ await this.pgBoss.createQueue(cronName)
152
137
  await this.pgBoss.schedule(cronName, task.schedule, {
153
138
  rpcName: name,
154
139
  } as ScheduledJobData)
@@ -158,8 +143,8 @@ export class PgBossSchedulerService extends SchedulerService {
158
143
  await this.pgBoss.work<ScheduledJobData>(cronName, async (jobs) => {
159
144
  for (const job of jobs) {
160
145
  const { rpcName } = job.data
161
- this.handlers!.logger.info(`Running scheduled task: ${rpcName}`)
162
- await this.handlers!.runScheduledTask(rpcName)
146
+ logger.info(`Running scheduled task: ${rpcName}`)
147
+ await runScheduledTask({ name: rpcName })
163
148
  }
164
149
  })
165
150
  }
@@ -2,9 +2,6 @@ import { PgBoss, type ConstructorOptions } from 'pg-boss'
2
2
  import { PgBossQueueService } from './pg-boss-queue-service.js'
3
3
  import { PgBossQueueWorkers } from './pg-boss-queue-worker.js'
4
4
  import { PgBossSchedulerService } from './pg-boss-scheduler-service.js'
5
- import type { CoreSingletonServices, CreateWireServices } from '@pikku/core'
6
- import { createQueueJobRunner } from '@pikku/core/queue'
7
- import { createSchedulerRuntimeHandlers } from '@pikku/core/scheduler'
8
5
 
9
6
  /**
10
7
  * Factory class for pg-boss services
@@ -52,17 +49,10 @@ export class PgBossServiceFactory {
52
49
  /**
53
50
  * Get the queue workers for processing jobs
54
51
  */
55
- getQueueWorkers(
56
- singletonServices: CoreSingletonServices,
57
- createWireServices?: CreateWireServices
58
- ): PgBossQueueWorkers {
52
+ getQueueWorkers(): PgBossQueueWorkers {
59
53
  if (!this.queueWorkers) {
60
54
  this.queueWorkers = new PgBossQueueWorkers(this.pgBoss)
61
55
  }
62
- this.queueWorkers.setJobRunner(
63
- createQueueJobRunner({ singletonServices, createWireServices }),
64
- singletonServices.logger
65
- )
66
56
  return this.queueWorkers
67
57
  }
68
58
 
@@ -76,18 +66,6 @@ export class PgBossServiceFactory {
76
66
  return this.schedulerService
77
67
  }
78
68
 
79
- setSchedulerRuntime(
80
- singletonServices: CoreSingletonServices,
81
- createWireServices?: CreateWireServices
82
- ): void {
83
- this.getSchedulerService().setServices(
84
- createSchedulerRuntimeHandlers({
85
- singletonServices,
86
- createWireServices,
87
- })
88
- )
89
- }
90
-
91
69
  /**
92
70
  * Close all services and the pg-boss connection
93
71
  */
package/src/utils.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { QueueJob, QueueJobStatus } from '@pikku/core/queue'
2
- import { PgBoss, type JobWithMetadata } from 'pg-boss'
1
+ import type { QueueJob, QueueJobStatus } from '@pikku/core/queue'
2
+ import type { PgBoss, JobWithMetadata } from 'pg-boss'
3
3
 
4
4
  /**
5
5
  * Map pg-boss job state to our queue job status