@pikku/queue-pg-boss 0.9.4 → 0.10.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,5 +1,18 @@
1
1
  # @pikku/queue-pg-boss
2
2
 
3
+ ## 0.10.0
4
+
5
+ This release includes significant improvements across the framework including tree-shaking support, middleware/permission factories, enhanced CLI functionality, improved TypeScript type safety, and comprehensive test strategies.
6
+
7
+ For complete details, see https://pikku.dev/changelogs/0_10_0.md
8
+
9
+ ## 0.9.5-next.0
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies
14
+ - @pikku/core@0.9.12-next.0
15
+
3
16
  ## 0.9.4
4
17
 
5
18
  ### Patch Changes
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @module @pikku/queue-pg-boss
3
+ *
4
+ * PostgreSQL queue service for Pikku using pg-boss
5
+ * Provides job results, retries, priorities, and PostgreSQL-based queuing
6
+ */
7
+ export { PgBossQueueService } from './pg-boss-queue-service.js';
8
+ export { PgBossQueueWorkers } from './pg-boss-queue-worker.js';
9
+ export type { QueueService, PikkuWorkerConfig, PikkuJobConfig, QueueJob, JobOptions, } from '@pikku/core/queue';
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @module @pikku/queue-pg-boss
3
+ *
4
+ * PostgreSQL queue service for Pikku using pg-boss
5
+ * Provides job results, retries, priorities, and PostgreSQL-based queuing
6
+ */
7
+ export { PgBossQueueService } from './pg-boss-queue-service.js';
8
+ export { PgBossQueueWorkers } from './pg-boss-queue-worker.js';
@@ -0,0 +1,29 @@
1
+ import PgBoss from 'pg-boss';
2
+ import type { QueueService, QueueJob, JobOptions } from '@pikku/core/queue';
3
+ export declare const mapPikkuJobToPgBoss: (options?: JobOptions) => PgBoss.JobOptions;
4
+ /**
5
+ * pg-boss/PostgreSQL queue service implementation
6
+ * Supports job results, retries, and PostgreSQL-based queuing
7
+ */
8
+ export declare class PgBossQueueService implements QueueService {
9
+ readonly name = "pg-boss";
10
+ readonly supportsResults = true;
11
+ protected pgBoss: PgBoss;
12
+ constructor(optionsOrUrl: PgBoss.ConstructorOptions | string);
13
+ /**
14
+ * Initialize the pg-boss instance
15
+ */
16
+ init(): Promise<void>;
17
+ /**
18
+ * Add a job to the queue
19
+ */
20
+ add<In>(queueName: string, data: In, options?: JobOptions): Promise<string>;
21
+ /**
22
+ * Get job by ID
23
+ */
24
+ 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
+ }
@@ -0,0 +1,63 @@
1
+ import PgBoss from 'pg-boss';
2
+ import { mapPgBossJobToQueueJob } from './utils.js';
3
+ export const mapPikkuJobToPgBoss = (options) => {
4
+ const pgBossOptions = {};
5
+ if (options?.priority !== undefined) {
6
+ pgBossOptions.priority = options.priority;
7
+ }
8
+ if (options?.delay !== undefined) {
9
+ pgBossOptions.startAfter = new Date(Date.now() + options.delay);
10
+ }
11
+ if (options?.jobId !== undefined) {
12
+ pgBossOptions.singletonKey = options.jobId;
13
+ }
14
+ return pgBossOptions;
15
+ };
16
+ /**
17
+ * pg-boss/PostgreSQL queue service implementation
18
+ * Supports job results, retries, and PostgreSQL-based queuing
19
+ */
20
+ export class PgBossQueueService {
21
+ name = 'pg-boss';
22
+ supportsResults = true;
23
+ 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();
36
+ }
37
+ /**
38
+ * Add a job to the queue
39
+ */
40
+ async add(queueName, data, options) {
41
+ const jobId = await this.pgBoss.send(queueName, data, mapPikkuJobToPgBoss(options));
42
+ if (!jobId) {
43
+ throw new Error('Failed to create job');
44
+ }
45
+ return jobId;
46
+ }
47
+ /**
48
+ * Get job by ID
49
+ */
50
+ async getJob(queueName, jobId) {
51
+ const job = await this.pgBoss.getJobById(queueName, jobId);
52
+ if (!job) {
53
+ return null;
54
+ }
55
+ return mapPgBossJobToQueueJob(job, this.pgBoss);
56
+ }
57
+ /**
58
+ * Close the pg-boss connection
59
+ */
60
+ async close() {
61
+ await this.pgBoss.stop();
62
+ }
63
+ }
@@ -0,0 +1,34 @@
1
+ import PgBoss from 'pg-boss';
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;
5
+ /**
6
+ * pg-boss/PostgreSQL queue worker implementation
7
+ * Supports job results, retries, and PostgreSQL-based queuing
8
+ */
9
+ export declare class PgBossQueueWorkers implements QueueWorkers {
10
+ private singletonServices;
11
+ private createSessionServices?;
12
+ readonly name = "pg-boss";
13
+ readonly supportsResults = true;
14
+ /**
15
+ * Configuration mapping rules for pg-boss
16
+ * This defines how Pikku worker configs map to pg-boss configurations
17
+ */
18
+ readonly configMappings: QueueConfigMapping;
19
+ private pgBoss;
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>;
26
+ /**
27
+ * Scan state and register all compatible processors
28
+ */
29
+ registerQueues(): Promise<Record<string, ConfigValidationResult[]>>;
30
+ /**
31
+ * Close all workers and connections
32
+ */
33
+ close(): Promise<void>;
34
+ }
@@ -0,0 +1,148 @@
1
+ import PgBoss from 'pg-boss';
2
+ import { runQueueJob, registerQueueWorkers, QueueJobFailedError, QueueJobDiscardedError, } from '@pikku/core/queue';
3
+ import { mapPgBossJobToQueueJob } from './utils.js';
4
+ export const mapPikkuWorkerToPgBoss = (workerConfig) => {
5
+ const workerOptions = {};
6
+ if (workerConfig?.batchSize !== undefined) {
7
+ workerOptions.batchSize = workerConfig.batchSize;
8
+ }
9
+ if (workerConfig?.pollInterval !== undefined) {
10
+ workerOptions.pollingIntervalSeconds = Math.round(workerConfig.pollInterval / 1000);
11
+ }
12
+ return workerOptions;
13
+ };
14
+ /**
15
+ * pg-boss/PostgreSQL queue worker implementation
16
+ * Supports job results, retries, and PostgreSQL-based queuing
17
+ */
18
+ export class PgBossQueueWorkers {
19
+ singletonServices;
20
+ createSessionServices;
21
+ name = 'pg-boss';
22
+ supportsResults = true;
23
+ /**
24
+ * Configuration mapping rules for pg-boss
25
+ * This defines how Pikku worker configs map to pg-boss configurations
26
+ */
27
+ configMappings = {
28
+ // Configurations that are directly supported
29
+ supported: {
30
+ batchSize: {
31
+ queueProperty: 'batchSize',
32
+ description: 'Number of jobs to process in a single batch',
33
+ },
34
+ pollInterval: {
35
+ queueProperty: 'pollingIntervalSeconds',
36
+ transform: (value) => Math.round(value / 1000),
37
+ description: 'How often to poll for new jobs (converted from ms to seconds)',
38
+ },
39
+ },
40
+ // Configurations that are not supported but have reasonable explanations
41
+ unsupported: {
42
+ name: {
43
+ reason: 'Worker names are not supported in pg-boss',
44
+ explanation: 'pg-boss identifies workers by their queue name and process, not custom names',
45
+ },
46
+ autorun: {
47
+ reason: 'Autorun is not configurable in pg-boss (always enabled)',
48
+ explanation: 'pg-boss workers automatically start processing when created',
49
+ },
50
+ lockDuration: {
51
+ reason: 'Lock duration is managed differently in pg-boss',
52
+ explanation: 'pg-boss uses job-level expiration instead of worker-level locks',
53
+ },
54
+ drainDelay: {
55
+ reason: 'Drain delay is not configurable in pg-boss',
56
+ explanation: 'pg-boss handles graceful shutdown internally',
57
+ },
58
+ maxStalledCount: {
59
+ reason: 'Max stalled count is not configurable in pg-boss',
60
+ explanation: 'pg-boss handles stalled jobs through its built-in retry mechanism',
61
+ },
62
+ prefetch: {
63
+ reason: 'Prefetch is managed internally by pg-boss',
64
+ explanation: 'pg-boss optimizes job fetching automatically based on batch size',
65
+ },
66
+ visibilityTimeout: {
67
+ reason: "Visibility timeout concept doesn't apply to pg-boss",
68
+ explanation: 'pg-boss uses PostgreSQL locks and job expiration instead of visibility timeout',
69
+ },
70
+ },
71
+ // Configurations that have partial support or workarounds
72
+ fallbacks: {
73
+ removeOnComplete: {
74
+ reason: 'Job retention is managed through pg-boss archival system',
75
+ explanation: 'pg-boss automatically archives completed jobs based on database settings',
76
+ fallbackValue: 'Managed by pg-boss database archival',
77
+ },
78
+ removeOnFail: {
79
+ reason: 'Failed job retention is managed through pg-boss archival system',
80
+ explanation: 'pg-boss automatically archives failed jobs based on database settings',
81
+ fallbackValue: 'Managed by pg-boss database archival',
82
+ },
83
+ },
84
+ };
85
+ pgBoss;
86
+ activeWorkers = new Map();
87
+ constructor(options, singletonServices, createSessionServices) {
88
+ 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();
101
+ }
102
+ /**
103
+ * Scan state and register all compatible processors
104
+ */
105
+ async registerQueues() {
106
+ return await registerQueueWorkers(this.configMappings, this.singletonServices.logger, async (queueName, processor) => {
107
+ await this.pgBoss.createQueue(queueName);
108
+ const workerId = await this.pgBoss.work(processor.queueName, {
109
+ ...mapPikkuWorkerToPgBoss(processor.config),
110
+ includeMetadata: true,
111
+ }, async ([job]) => {
112
+ if (!this.singletonServices) {
113
+ throw new Error('Singleton services not available');
114
+ }
115
+ if (!job) {
116
+ this.singletonServices.logger.warn(`No job received for queue ${queueName}`);
117
+ return;
118
+ }
119
+ try {
120
+ await runQueueJob({
121
+ singletonServices: this.singletonServices,
122
+ createSessionServices: this.createSessionServices,
123
+ job: mapPgBossJobToQueueJob(job, this.pgBoss),
124
+ });
125
+ }
126
+ catch (error) {
127
+ if (error instanceof QueueJobFailedError) {
128
+ // Let pg-boss handle this as a failed job
129
+ throw new Error(error.message);
130
+ }
131
+ else if (error instanceof QueueJobDiscardedError) {
132
+ // For pg-boss, complete the job successfully to discard it
133
+ this.singletonServices.logger.info(`PgBoss job ${job.id} discarded: ${error.message}`);
134
+ return; // Successfully "completed" by discarding
135
+ }
136
+ throw error;
137
+ }
138
+ });
139
+ this.activeWorkers.set(queueName, workerId);
140
+ });
141
+ }
142
+ /**
143
+ * Close all workers and connections
144
+ */
145
+ async close() {
146
+ await this.pgBoss.stop();
147
+ }
148
+ }
@@ -0,0 +1,6 @@
1
+ import { QueueJob } from '@pikku/core/queue';
2
+ import PgBoss from 'pg-boss';
3
+ /**
4
+ * Map pg-boss job to QueueJob interface
5
+ */
6
+ export declare const mapPgBossJobToQueueJob: <In, Out>(pgBossJob: PgBoss.JobWithMetadata<In>, pgBossInstance: PgBoss) => QueueJob<In, Out>;
package/dist/utils.js ADDED
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Map pg-boss job state to our queue job status
3
+ */
4
+ const mapPgBossStateToStatus = (state) => {
5
+ switch (state) {
6
+ case 'created':
7
+ case 'retry':
8
+ return 'waiting';
9
+ case 'active':
10
+ return 'active';
11
+ case 'completed':
12
+ return 'completed';
13
+ case 'failed':
14
+ return 'failed';
15
+ case 'cancelled':
16
+ return 'failed';
17
+ default:
18
+ return 'waiting';
19
+ }
20
+ };
21
+ /**
22
+ * Map pg-boss job to QueueJob interface
23
+ */
24
+ export const mapPgBossJobToQueueJob = (pgBossJob, pgBossInstance) => {
25
+ return {
26
+ queueName: pgBossJob.name,
27
+ id: pgBossJob.id || '',
28
+ data: pgBossJob.data,
29
+ status: async () => {
30
+ const jobWithState = await pgBossInstance.getJobById(pgBossJob.name, pgBossJob.id);
31
+ return mapPgBossStateToStatus(jobWithState?.state || 'created');
32
+ },
33
+ waitForCompletion: async (ttl) => {
34
+ // pg-boss doesn't have a built-in waitForCompletion, so we'll poll
35
+ const startTime = Date.now();
36
+ const timeout = ttl || 30000; // 30 seconds default
37
+ while (Date.now() - startTime < timeout) {
38
+ const job = await pgBossInstance.getJobById(pgBossJob.name, pgBossJob.id);
39
+ if (job?.state === 'completed') {
40
+ return job.output;
41
+ }
42
+ if (job?.state === 'failed' || job?.state === 'cancelled') {
43
+ throw new Error(`Job failed: ${JSON.stringify(job.output)}`);
44
+ }
45
+ // Wait 100ms before polling again
46
+ await new Promise((resolve) => setTimeout(resolve, 100));
47
+ }
48
+ throw new Error('Job did not complete within timeout');
49
+ },
50
+ metadata: () => {
51
+ return {
52
+ progress: 0, // pg-boss doesn't have built-in progress tracking
53
+ attemptsMade: pgBossJob.retryCount || 0,
54
+ maxAttempts: pgBossJob.retryLimit || 1,
55
+ createdAt: pgBossJob.createdOn
56
+ ? new Date(pgBossJob.createdOn)
57
+ : new Date(),
58
+ processedAt: pgBossJob.startedOn
59
+ ? new Date(pgBossJob.startedOn)
60
+ : undefined,
61
+ completedAt: pgBossJob.completedOn
62
+ ? new Date(pgBossJob.completedOn)
63
+ : undefined,
64
+ failedAt: pgBossJob.state === 'failed' && pgBossJob.completedOn
65
+ ? new Date(pgBossJob.completedOn)
66
+ : undefined,
67
+ error: pgBossJob.data.error,
68
+ };
69
+ },
70
+ };
71
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/queue-pg-boss",
3
- "version": "0.9.4",
3
+ "version": "0.10.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.9.9"
16
+ "@pikku/core": "^0.10.0"
17
17
  },
18
18
  "dependencies": {
19
- "pg-boss": "^10.3.2"
19
+ "pg-boss": "^11.1.1"
20
20
  },
21
21
  "devDependencies": {
22
22
  "typescript": "^5.9"
@@ -0,0 +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"}