@lavoro/postgres 0.3.1 → 0.4.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/build/index.d.ts CHANGED
@@ -10,7 +10,6 @@ type PostgresConfig = {
10
10
  };
11
11
  declare class PostgresQueueDriver extends QueueDriver<PostgresConfig> {
12
12
  private boss;
13
- private lockFactory?;
14
13
  private lockKnexInstance?;
15
14
  private lockTableName;
16
15
  constructor(queueConfig: QueueConfig, options: Record<string, WorkerOptions>, config?: PostgresConfig);
package/build/index.js CHANGED
@@ -41,7 +41,6 @@ var PostgresQueueDriver = class _PostgresQueueDriver extends QueueDriver {
41
41
  }
42
42
  super(queueConfig, options, config);
43
43
  __publicField(this, "boss");
44
- __publicField(this, "lockFactory");
45
44
  __publicField(this, "lockKnexInstance");
46
45
  __publicField(this, "lockTableName", "lavoro_locks");
47
46
  this.boss = new PgBoss({
@@ -121,8 +120,9 @@ var PostgresQueueDriver = class _PostgresQueueDriver extends QueueDriver {
121
120
  job: job.name,
122
121
  err: error
123
122
  },
124
- "Job failed"
123
+ "Got error while processing the job"
125
124
  );
125
+ this.emit("error", error);
126
126
  yield this.boss.fail(job.name, job.id, error);
127
127
  }
128
128
  }))
@@ -142,83 +142,11 @@ var PostgresQueueDriver = class _PostgresQueueDriver extends QueueDriver {
142
142
  }
143
143
  processJob(job) {
144
144
  return __async(this, null, function* () {
145
- var _a;
146
- const { queue, name } = Job.parseName(job.name);
147
- if (!queue || !name) {
148
- const error = new Error(`Invalid job class name: ${job.name}`);
149
- this.logger.warn(error);
150
- throw error;
151
- }
152
- this.logger.trace({ job: name }, "Processing job");
153
- try {
154
- this.checkIfJobIsRegistered(name);
155
- } catch (error) {
156
- this.logger.warn(error.message);
157
- throw error;
158
- }
159
- const jobClass = this.registeredJobs.get(name);
160
- if (!jobClass) {
161
- const error = new Error(`Job is not registered: ${name}`);
162
- this.logger.warn(error);
163
- throw error;
164
- }
165
- const jobInstance = new jobClass();
166
- jobInstance.connection = this.connection;
167
- jobInstance.queue = queue;
168
- jobInstance.id = job.id;
169
- this.logger.debug(
170
- {
171
- connection: this.connection,
172
- queue,
173
- job: name,
174
- id: job.id
175
- },
176
- "Processing job"
177
- );
178
- const serializedLock = (_a = job.data) == null ? void 0 : _a._lock;
179
- let lock;
180
- if (serializedLock !== void 0 && this.lockFactory !== void 0) {
181
- try {
182
- lock = this.lockFactory.restoreLock(serializedLock);
183
- yield lock.acquireImmediately();
184
- this.logger.trace(
185
- { job: name, id: job.id, lock: serializedLock },
186
- "Restored lock from scheduler"
187
- );
188
- } catch (error) {
189
- this.logger.warn(
190
- { job: name, id: job.id, error },
191
- "Failed to restore lock"
192
- );
193
- }
194
- }
195
- try {
196
- yield jobInstance.handle(job.data);
197
- } finally {
198
- if (lock) {
199
- try {
200
- yield lock.forceRelease();
201
- this.logger.trace(
202
- { job: name, id: job.id, lock: lock.serialize() },
203
- "Released lock for scheduled job"
204
- );
205
- } catch (error) {
206
- this.logger.warn(
207
- { job: name, id: job.id, error },
208
- "Failed to release lock"
209
- );
210
- }
211
- }
212
- }
213
- this.logger.trace(
214
- {
215
- connection: this.connection,
216
- queue,
217
- job: name,
218
- id: job.id
219
- },
220
- "Job completed"
221
- );
145
+ yield this.process({
146
+ id: job.id,
147
+ fullyQualifiedName: job.name,
148
+ payload: job.data
149
+ });
222
150
  });
223
151
  }
224
152
  listen(queue, options) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n Job,\n Payload,\n QueueDriver,\n QueueDriverStopOptions,\n QueueName,\n ConfiguredDriver,\n QueueConfig,\n WorkerOptions,\n} from '@lavoro/core'\n\nimport { Lock, LockFactory } from '@verrou/core'\nimport { knexStore } from '@verrou/core/drivers/knex'\nimport type { SerializedLock } from '@verrou/core/types'\nimport knex from 'knex'\nimport { PgBoss, Job as PgBossJob } from 'pg-boss'\n\nexport type PostgresConfig = {\n host: string\n port: string | number\n user: string\n password: string\n database: string\n}\n\nexport class PostgresQueueDriver extends QueueDriver<PostgresConfig> {\n private boss: PgBoss\n private lockFactory?: LockFactory\n private lockKnexInstance?: ReturnType<typeof knex>\n private lockTableName: string = 'lavoro_locks'\n\n constructor(\n queueConfig: QueueConfig,\n options: Record<string, WorkerOptions>,\n config?: PostgresConfig,\n ) {\n /**\n * Since the config is marked optional and it is required for this driver,\n * we check it during runtime before creating the driver.\n */\n if (!config) {\n throw new Error('PostgresQueueDriver requires a config object')\n }\n\n super(queueConfig, options, config)\n\n this.boss = new PgBoss({\n connectionString: `postgresql://${config.user}:${config.password}@${config.host}:${config.port}/${config.database}`,\n })\n }\n\n public createLockProvider() {\n const knexInstance = knex({\n client: 'pg',\n connection: {\n host: this.driverConfig.host,\n port: Number(this.driverConfig.port),\n user: this.driverConfig.user,\n password: this.driverConfig.password,\n database: this.driverConfig.database,\n },\n })\n\n this.lockKnexInstance = knexInstance\n\n this.lockFactory = new LockFactory(\n knexStore({\n connection: this.lockKnexInstance,\n autoCreateTable: true,\n tableName: this.lockTableName,\n }).factory(),\n )\n\n return this.lockFactory\n }\n\n public async destroyLockProvider(): Promise<void> {\n if (this.lockKnexInstance) {\n await this.lockKnexInstance.destroy()\n this.lockKnexInstance = undefined\n }\n }\n\n /**\n *\n * @param fullyQualifiedJobName\n */\n private async createWorker(\n fullyQualifiedJobName: string,\n options: WorkerOptions,\n ): Promise<void> {\n const { concurrency = 1 } = options\n\n const { queue, name } = Job.parseName(fullyQualifiedJobName)\n\n await this.boss.createQueue(fullyQualifiedJobName, {\n // partition: true,\n // deadLetter: undefined,\n // retryLimit: 3,\n // retryDelay: 1,\n // retryBackoff: true,\n // retentionSeconds: 0, // Default: 14 days. How many seconds a job may be in created or retry state before it's deleted.\n // deleteAfterSeconds: 0, // Default: 7 days. How long a job should be retained in the database after it's completed.\n })\n\n if (!this.config?.worker || concurrency === 0) {\n this.logger.trace(\n { connection: this.connection, queue, job: name },\n 'Queue worker is disabled - skipping',\n )\n\n return\n }\n\n await this.boss.work(\n fullyQualifiedJobName,\n {\n batchSize: concurrency,\n // pollingIntervalSeconds: 2, // 2 seconds by default\n },\n async (jobs) => {\n Promise.allSettled(\n jobs.map(async (job) => {\n try {\n await this.processJob(job)\n } catch (error) {\n // TODO: Add a way to signal about the error\n this.logger.warn(\n {\n connection: this.connection,\n queue,\n job: job.name,\n err: error,\n },\n 'Job failed',\n )\n\n await this.boss.fail(job.name, job.id, error)\n }\n }),\n )\n },\n )\n\n this.logger.trace(\n {\n connection: this.connection,\n queue,\n job: name,\n options,\n },\n 'Started worker',\n )\n }\n\n private async processJob(job: PgBossJob<unknown>): Promise<void> {\n const { queue, name } = Job.parseName(job.name)\n\n if (!queue || !name) {\n const error = new Error(`Invalid job class name: ${job.name}`)\n this.logger.warn(error)\n throw error\n }\n\n this.logger.trace({ job: name }, 'Processing job')\n\n try {\n this.checkIfJobIsRegistered(name)\n } catch (error) {\n this.logger.warn(error.message)\n throw error\n }\n\n const jobClass = this.registeredJobs.get(name)\n\n if (!jobClass) {\n const error = new Error(`Job is not registered: ${name}`)\n this.logger.warn(error)\n throw error\n }\n\n const jobInstance = new jobClass()\n\n jobInstance.connection = this.connection\n jobInstance.queue = queue\n jobInstance.id = job.id\n\n // TODO: Make this pretty\n\n this.logger.debug(\n {\n connection: this.connection,\n queue,\n job: name,\n id: job.id,\n },\n 'Processing job',\n )\n\n /**\n * A job might have been scheduled by the scheduler,\n * in which case we need to restore the lock from the payload.\n *\n * This will prevent the job from being scheduled\n * while it is being processed.\n */\n const serializedLock = (job.data as any)?._lock as\n | SerializedLock\n | undefined\n\n let lock: Lock | undefined\n\n if (serializedLock !== undefined && this.lockFactory !== undefined) {\n try {\n lock = this.lockFactory.restoreLock(serializedLock)\n await lock.acquireImmediately()\n\n this.logger.trace(\n { job: name, id: job.id, lock: serializedLock },\n 'Restored lock from scheduler',\n )\n } catch (error) {\n this.logger.warn(\n { job: name, id: job.id, error },\n 'Failed to restore lock',\n )\n }\n }\n\n /**\n * Next, we process the actual job.\n */\n try {\n await jobInstance.handle(job.data)\n } finally {\n /**\n * If we previously acquired a lock for\n * this job, we need to release it here.\n */\n if (lock) {\n try {\n await lock.forceRelease()\n\n this.logger.trace(\n { job: name, id: job.id, lock: lock.serialize() },\n 'Released lock for scheduled job',\n )\n } catch (error) {\n this.logger.warn(\n { job: name, id: job.id, error },\n 'Failed to release lock',\n )\n }\n }\n }\n\n this.logger.trace(\n {\n connection: this.connection,\n queue,\n job: name,\n id: job.id,\n },\n 'Job completed',\n )\n }\n\n public async listen(\n queue: QueueName,\n options?: WorkerOptions,\n ): Promise<void> {\n const mergedOptions = this.getMergedWorkerOptions(queue, options)\n await super.listen(queue, options)\n for (const job of this.registeredJobs.values()) {\n await this.createWorker(Job.compileName(queue, job.name), mergedOptions)\n }\n }\n\n public async start(): Promise<void> {\n // TODO: Add error handling\n this.boss.on('error', async (error) => {\n this.logger.error({ error }, 'Error in Postgres queue driver')\n await this.stop({ graceful: false })\n })\n\n await this.boss.start()\n\n await super.start()\n\n this.logger.trace(\n { connection: this.connection, driver: 'postgres' },\n 'Queue driver started',\n )\n }\n\n public async stop(options?: QueueDriverStopOptions): Promise<void> {\n const { graceful = true, timeout = 30000 } = options || {}\n\n this.logger.trace(\n { connection: this.connection, driver: 'postgres', graceful, timeout },\n `Waiting for pg-boss to stop...`,\n )\n\n await this.boss.stop({ graceful, timeout })\n\n this.logger.trace(\n { connection: this.connection, driver: 'postgres' },\n 'Pg-boss stopped',\n )\n\n await super.stop()\n\n this.logger.trace(\n { connection: this.connection, driver: 'postgres' },\n 'Queue driver stopped',\n )\n }\n\n public async enqueue<T extends Job, P extends Payload<T>>(\n job: T,\n payload: P,\n ): Promise<void> {\n await super.enqueue(job, payload)\n\n this.logger.trace(\n {\n connection: this.connection,\n queue: job.options.queue,\n job: job.name,\n },\n 'Enqueuing job',\n )\n\n await this.boss.send(job.fullyQualifiedName, payload as object, {\n // priority: 2,\n // retryLimit: 3,\n // startAfter: 10,\n })\n }\n\n // public async schedule<T extends Job, P extends Payload<T>>(\n // job: T,\n // payload: P,\n // ): Promise<void> {\n // await super.schedule(job, payload)\n\n // this.logger.trace(\n // { connection: this.connection, queue: job.options.queue, job: job.name },\n // 'Scheduling job',\n // )\n\n // const scheduleExpression = job.options.schedule || '* * * * *' // every second\n\n // await this.boss.schedule(\n // job.fullyQualifiedName,\n // scheduleExpression,\n // payload as object,\n // {\n // // tz: 'UTC',\n // // key: 'unique_key',\n // },\n // )\n // }\n}\n\n/**\n * Builder function for PostgresQueueDriver.\n * Creates a driver descriptor with type-safe config.\n */\nexport function postgres(\n config: PostgresConfig,\n): ConfiguredDriver<PostgresQueueDriver, PostgresConfig> {\n return {\n constructor: PostgresQueueDriver,\n config: config,\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n ConfiguredDriver,\n Job,\n Payload,\n QueueConfig,\n QueueDriver,\n QueueDriverStopOptions,\n QueueName,\n WorkerOptions,\n} from '@lavoro/core'\nimport { LockFactory } from '@verrou/core'\nimport { knexStore } from '@verrou/core/drivers/knex'\nimport knex from 'knex'\nimport { PgBoss, Job as PgBossJob } from 'pg-boss'\n\nexport type PostgresConfig = {\n host: string\n port: string | number\n user: string\n password: string\n database: string\n}\n\nexport class PostgresQueueDriver extends QueueDriver<PostgresConfig> {\n private boss: PgBoss\n private lockKnexInstance?: ReturnType<typeof knex>\n private lockTableName: string = 'lavoro_locks'\n\n constructor(\n queueConfig: QueueConfig,\n options: Record<string, WorkerOptions>,\n config?: PostgresConfig,\n ) {\n /**\n * Since the config is marked optional and it is required for this driver,\n * we check it during runtime before creating the driver.\n */\n if (!config) {\n throw new Error('PostgresQueueDriver requires a config object')\n }\n\n super(queueConfig, options, config)\n\n this.boss = new PgBoss({\n connectionString: `postgresql://${config.user}:${config.password}@${config.host}:${config.port}/${config.database}`,\n })\n }\n\n public createLockProvider() {\n const knexInstance = knex({\n client: 'pg',\n connection: {\n host: this.driverConfig.host,\n port: Number(this.driverConfig.port),\n user: this.driverConfig.user,\n password: this.driverConfig.password,\n database: this.driverConfig.database,\n },\n })\n\n this.lockKnexInstance = knexInstance\n\n this.lockFactory = new LockFactory(\n knexStore({\n connection: this.lockKnexInstance,\n autoCreateTable: true,\n tableName: this.lockTableName,\n }).factory(),\n )\n\n return this.lockFactory\n }\n\n public async destroyLockProvider(): Promise<void> {\n if (this.lockKnexInstance) {\n await this.lockKnexInstance.destroy()\n this.lockKnexInstance = undefined\n }\n }\n\n /**\n *\n * @param fullyQualifiedJobName\n */\n private async createWorker(\n fullyQualifiedJobName: string,\n options: WorkerOptions,\n ): Promise<void> {\n const { concurrency = 1 } = options\n\n const { queue, name } = Job.parseName(fullyQualifiedJobName)\n\n await this.boss.createQueue(fullyQualifiedJobName, {\n // partition: true,\n // deadLetter: undefined,\n // retryLimit: 3,\n // retryDelay: 1,\n // retryBackoff: true,\n // retentionSeconds: 0, // Default: 14 days. How many seconds a job may be in created or retry state before it's deleted.\n // deleteAfterSeconds: 0, // Default: 7 days. How long a job should be retained in the database after it's completed.\n })\n\n if (!this.config?.worker || concurrency === 0) {\n this.logger.trace(\n { connection: this.connection, queue, job: name },\n 'Queue worker is disabled - skipping',\n )\n\n return\n }\n\n await this.boss.work(\n fullyQualifiedJobName,\n {\n batchSize: concurrency,\n // pollingIntervalSeconds: 2, // 2 seconds by default\n },\n async (jobs) => {\n Promise.allSettled(\n jobs.map(async (job) => {\n try {\n await this.processJob(job)\n } catch (error) {\n this.logger.warn(\n {\n connection: this.connection,\n queue,\n job: job.name,\n err: error,\n },\n 'Got error while processing the job',\n )\n\n this.emit('error', error)\n\n await this.boss.fail(job.name, job.id, error)\n }\n }),\n )\n },\n )\n\n this.logger.trace(\n {\n connection: this.connection,\n queue,\n job: name,\n options,\n },\n 'Started worker',\n )\n }\n\n private async processJob(job: PgBossJob<unknown>): Promise<void> {\n await this.process({\n id: job.id,\n fullyQualifiedName: job.name,\n payload: job.data,\n })\n }\n\n public async listen(\n queue: QueueName,\n options?: WorkerOptions,\n ): Promise<void> {\n const mergedOptions = this.getMergedWorkerOptions(queue, options)\n await super.listen(queue, options)\n for (const job of this.registeredJobs.values()) {\n await this.createWorker(Job.compileName(queue, job.name), mergedOptions)\n }\n }\n\n public async start(): Promise<void> {\n // TODO: Add error handling\n this.boss.on('error', async (error) => {\n this.logger.error({ error }, 'Error in Postgres queue driver')\n await this.stop({ graceful: false })\n })\n\n await this.boss.start()\n\n await super.start()\n\n this.logger.trace(\n { connection: this.connection, driver: 'postgres' },\n 'Queue driver started',\n )\n }\n\n public async stop(options?: QueueDriverStopOptions): Promise<void> {\n const { graceful = true, timeout = 30000 } = options || {}\n\n this.logger.trace(\n { connection: this.connection, driver: 'postgres', graceful, timeout },\n `Waiting for pg-boss to stop...`,\n )\n\n await this.boss.stop({ graceful, timeout })\n\n this.logger.trace(\n { connection: this.connection, driver: 'postgres' },\n 'Pg-boss stopped',\n )\n\n await super.stop()\n\n this.logger.trace(\n { connection: this.connection, driver: 'postgres' },\n 'Queue driver stopped',\n )\n }\n\n public async enqueue<T extends Job, P extends Payload<T>>(\n job: T,\n payload: P,\n ): Promise<void> {\n await super.enqueue(job, payload)\n\n this.logger.trace(\n {\n connection: this.connection,\n queue: job.options.queue,\n job: job.name,\n },\n 'Enqueuing job',\n )\n\n await this.boss.send(job.fullyQualifiedName, payload as object, {\n // priority: 2,\n // retryLimit: 3,\n // startAfter: 10,\n })\n }\n\n // public async schedule<T extends Job, P extends Payload<T>>(\n // job: T,\n // payload: P,\n // ): Promise<void> {\n // await super.schedule(job, payload)\n\n // this.logger.trace(\n // { connection: this.connection, queue: job.options.queue, job: job.name },\n // 'Scheduling job',\n // )\n\n // const scheduleExpression = job.options.schedule || '* * * * *' // every second\n\n // await this.boss.schedule(\n // job.fullyQualifiedName,\n // scheduleExpression,\n // payload as object,\n // {\n // // tz: 'UTC',\n // // key: 'unique_key',\n // },\n // )\n // }\n}\n\n/**\n * Builder function for PostgresQueueDriver.\n * Creates a driver descriptor with type-safe config.\n */\nexport function postgres(\n config: PostgresConfig,\n): ConfiguredDriver<PostgresQueueDriver, PostgresConfig> {\n return {\n constructor: PostgresQueueDriver,\n config: config,\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lavoro/postgres",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "description": "PostgreSQL queue driver for Lavoro with support for distributed locking",
6
6
  "author": "Aleksei Ivanov <contact@aleksei.dev>",