@constructive-io/knative-job-worker 0.7.14 → 0.8.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
@@ -3,6 +3,18 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [0.8.0](https://github.com/constructive-io/jobs/compare/@constructive-io/knative-job-worker@0.7.16...@constructive-io/knative-job-worker@0.8.0) (2026-01-18)
7
+
8
+ **Note:** Version bump only for package @constructive-io/knative-job-worker
9
+
10
+ ## [0.7.16](https://github.com/constructive-io/jobs/compare/@constructive-io/knative-job-worker@0.7.15...@constructive-io/knative-job-worker@0.7.16) (2026-01-18)
11
+
12
+ **Note:** Version bump only for package @constructive-io/knative-job-worker
13
+
14
+ ## [0.7.15](https://github.com/constructive-io/jobs/compare/@constructive-io/knative-job-worker@0.7.14...@constructive-io/knative-job-worker@0.7.15) (2026-01-14)
15
+
16
+ **Note:** Version bump only for package @constructive-io/knative-job-worker
17
+
6
18
  ## [0.7.14](https://github.com/constructive-io/jobs/compare/@constructive-io/knative-job-worker@0.7.13...@constructive-io/knative-job-worker@0.7.14) (2026-01-14)
7
19
 
8
20
  **Note:** Version bump only for package @constructive-io/knative-job-worker
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { PgClientLike } from '@constructive-io/job-utils';
2
- import type { Pool } from 'pg';
2
+ import type { Pool, PoolClient } from 'pg';
3
3
  export interface JobRow {
4
4
  id: number | string;
5
5
  task_identifier: string;
@@ -13,6 +13,9 @@ export default class Worker {
13
13
  doNextTimer?: NodeJS.Timeout;
14
14
  pgPool: Pool;
15
15
  _initialized?: boolean;
16
+ listenClient?: PoolClient;
17
+ listenRelease?: () => void;
18
+ stopped?: boolean;
16
19
  constructor({ tasks, idleDelay, pgPool, workerId }: {
17
20
  tasks: string[];
18
21
  idleDelay?: number;
@@ -37,5 +40,6 @@ export default class Worker {
37
40
  doWork(job: JobRow): Promise<void>;
38
41
  doNext(client: PgClientLike): Promise<void>;
39
42
  listen(): void;
43
+ stop(): Promise<void>;
40
44
  }
41
45
  export { Worker };
package/dist/index.js CHANGED
@@ -49,6 +49,9 @@ class Worker {
49
49
  doNextTimer;
50
50
  pgPool;
51
51
  _initialized;
52
+ listenClient;
53
+ listenRelease;
54
+ stopped;
52
55
  constructor({ tasks, idleDelay = 15000, pgPool = job_pg_1.default.getPool(), workerId = 'worker-0' }) {
53
56
  /*
54
57
  * idleDelay: This is how long to wait between polling for jobs.
@@ -114,6 +117,8 @@ class Worker {
114
117
  });
115
118
  }
116
119
  async doNext(client) {
120
+ if (this.stopped)
121
+ return;
117
122
  if (!this._initialized) {
118
123
  return await this.initialize(client);
119
124
  }
@@ -130,7 +135,9 @@ class Worker {
130
135
  : this.supportedTaskNames
131
136
  }));
132
137
  if (!job || !job.id) {
133
- this.doNextTimer = setTimeout(() => this.doNext(client), this.idleDelay);
138
+ if (!this.stopped) {
139
+ this.doNextTimer = setTimeout(() => this.doNext(client), this.idleDelay);
140
+ }
134
141
  return;
135
142
  }
136
143
  const start = process.hrtime();
@@ -155,13 +162,20 @@ class Worker {
155
162
  catch (fatalError) {
156
163
  await this.handleFatalError(client, { err, fatalError, jobId });
157
164
  }
158
- return this.doNext(client);
165
+ if (!this.stopped) {
166
+ return this.doNext(client);
167
+ }
168
+ return;
159
169
  }
160
170
  catch (err) {
161
- this.doNextTimer = setTimeout(() => this.doNext(client), this.idleDelay);
171
+ if (!this.stopped) {
172
+ this.doNextTimer = setTimeout(() => this.doNext(client), this.idleDelay);
173
+ }
162
174
  }
163
175
  }
164
176
  listen() {
177
+ if (this.stopped)
178
+ return;
165
179
  const listenForChanges = (err, client, release) => {
166
180
  if (err) {
167
181
  log.error('Error connecting with notify listener', err);
@@ -170,9 +184,17 @@ class Worker {
170
184
  }
171
185
  // Try again in 5 seconds
172
186
  // should this really be done in the node process?
173
- setTimeout(this.listen, 5000);
187
+ if (!this.stopped) {
188
+ setTimeout(this.listen, 5000);
189
+ }
190
+ return;
191
+ }
192
+ if (this.stopped) {
193
+ release();
174
194
  return;
175
195
  }
196
+ this.listenClient = client;
197
+ this.listenRelease = release;
176
198
  client.on('notification', () => {
177
199
  if (this.doNextTimer) {
178
200
  // Must be idle, do something!
@@ -181,18 +203,46 @@ class Worker {
181
203
  });
182
204
  client.query('LISTEN "jobs:insert"');
183
205
  client.on('error', (e) => {
206
+ if (this.stopped) {
207
+ release();
208
+ return;
209
+ }
184
210
  log.error('Error with database notify listener', e);
185
211
  if (e instanceof Error && e.stack) {
186
212
  log.debug(e.stack);
187
213
  }
188
214
  release();
189
- this.listen();
215
+ if (!this.stopped) {
216
+ this.listen();
217
+ }
190
218
  });
191
219
  log.info(`${this.workerId} connected and looking for jobs...`);
192
220
  this.doNext(client);
193
221
  };
194
222
  this.pgPool.connect(listenForChanges);
195
223
  }
224
+ async stop() {
225
+ this.stopped = true;
226
+ if (this.doNextTimer) {
227
+ clearTimeout(this.doNextTimer);
228
+ this.doNextTimer = undefined;
229
+ }
230
+ const client = this.listenClient;
231
+ const release = this.listenRelease;
232
+ this.listenClient = undefined;
233
+ this.listenRelease = undefined;
234
+ if (client && release) {
235
+ client.removeAllListeners('notification');
236
+ client.removeAllListeners('error');
237
+ try {
238
+ await client.query('UNLISTEN "jobs:insert"');
239
+ }
240
+ catch {
241
+ // Ignore listener cleanup errors during shutdown.
242
+ }
243
+ release();
244
+ }
245
+ }
196
246
  }
197
247
  exports.default = Worker;
198
248
  exports.Worker = Worker;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constructive-io/knative-job-worker",
3
- "version": "0.7.14",
3
+ "version": "0.8.0",
4
4
  "description": "knative job worker",
5
5
  "author": "Constructive <developers@constructive.io>",
6
6
  "homepage": "https://github.com/constructive-io/jobs/tree/master/packages/knative-job-worker#readme",
@@ -32,14 +32,14 @@
32
32
  "url": "https://github.com/constructive-io/jobs/issues"
33
33
  },
34
34
  "dependencies": {
35
- "@constructive-io/job-pg": "^0.3.20",
36
- "@constructive-io/job-utils": "^0.5.15",
37
- "@pgpmjs/logger": "^1.3.7",
35
+ "@constructive-io/job-pg": "^0.4.0",
36
+ "@constructive-io/job-utils": "^0.6.0",
37
+ "@pgpmjs/logger": "^1.4.0",
38
38
  "pg": "8.16.3",
39
39
  "request": "2.88.2"
40
40
  },
41
41
  "devDependencies": {
42
- "pgsql-test": "^2.24.14"
42
+ "pgsql-test": "^2.25.0"
43
43
  },
44
- "gitHead": "15e8f92ee6ec7bfebd6647dec743e4235b0362ac"
44
+ "gitHead": "481b3a50b4eec2da6b376c4cd1868065e1e28edb"
45
45
  }
package/src/index.ts CHANGED
@@ -21,6 +21,9 @@ export default class Worker {
21
21
  doNextTimer?: NodeJS.Timeout;
22
22
  pgPool: Pool;
23
23
  _initialized?: boolean;
24
+ listenClient?: PoolClient;
25
+ listenRelease?: () => void;
26
+ stopped?: boolean;
24
27
 
25
28
  constructor({
26
29
  tasks,
@@ -118,6 +121,7 @@ export default class Worker {
118
121
  });
119
122
  }
120
123
  async doNext(client: PgClientLike): Promise<void> {
124
+ if (this.stopped) return;
121
125
  if (!this._initialized) {
122
126
  return await this.initialize(client);
123
127
  }
@@ -136,10 +140,12 @@ export default class Worker {
136
140
  })) as JobRow | undefined;
137
141
 
138
142
  if (!job || !job.id) {
139
- this.doNextTimer = setTimeout(
140
- () => this.doNext(client),
141
- this.idleDelay
142
- );
143
+ if (!this.stopped) {
144
+ this.doNextTimer = setTimeout(
145
+ () => this.doNext(client),
146
+ this.idleDelay
147
+ );
148
+ }
143
149
  return;
144
150
  }
145
151
  const start = process.hrtime();
@@ -164,12 +170,21 @@ export default class Worker {
164
170
  } catch (fatalError: unknown) {
165
171
  await this.handleFatalError(client, { err, fatalError, jobId });
166
172
  }
167
- return this.doNext(client);
173
+ if (!this.stopped) {
174
+ return this.doNext(client);
175
+ }
176
+ return;
168
177
  } catch (err: unknown) {
169
- this.doNextTimer = setTimeout(() => this.doNext(client), this.idleDelay);
178
+ if (!this.stopped) {
179
+ this.doNextTimer = setTimeout(
180
+ () => this.doNext(client),
181
+ this.idleDelay
182
+ );
183
+ }
170
184
  }
171
185
  }
172
186
  listen() {
187
+ if (this.stopped) return;
173
188
  const listenForChanges = (
174
189
  err: Error | null,
175
190
  client: PoolClient,
@@ -182,9 +197,17 @@ export default class Worker {
182
197
  }
183
198
  // Try again in 5 seconds
184
199
  // should this really be done in the node process?
185
- setTimeout(this.listen, 5000);
200
+ if (!this.stopped) {
201
+ setTimeout(this.listen, 5000);
202
+ }
186
203
  return;
187
204
  }
205
+ if (this.stopped) {
206
+ release();
207
+ return;
208
+ }
209
+ this.listenClient = client;
210
+ this.listenRelease = release;
188
211
  client.on('notification', () => {
189
212
  if (this.doNextTimer) {
190
213
  // Must be idle, do something!
@@ -193,18 +216,47 @@ export default class Worker {
193
216
  });
194
217
  client.query('LISTEN "jobs:insert"');
195
218
  client.on('error', (e: unknown) => {
219
+ if (this.stopped) {
220
+ release();
221
+ return;
222
+ }
196
223
  log.error('Error with database notify listener', e);
197
224
  if (e instanceof Error && e.stack) {
198
225
  log.debug(e.stack);
199
226
  }
200
227
  release();
201
- this.listen();
228
+ if (!this.stopped) {
229
+ this.listen();
230
+ }
202
231
  });
203
232
  log.info(`${this.workerId} connected and looking for jobs...`);
204
233
  this.doNext(client);
205
234
  };
206
235
  this.pgPool.connect(listenForChanges);
207
236
  }
237
+
238
+ async stop(): Promise<void> {
239
+ this.stopped = true;
240
+ if (this.doNextTimer) {
241
+ clearTimeout(this.doNextTimer);
242
+ this.doNextTimer = undefined;
243
+ }
244
+ const client = this.listenClient;
245
+ const release = this.listenRelease;
246
+ this.listenClient = undefined;
247
+ this.listenRelease = undefined;
248
+
249
+ if (client && release) {
250
+ client.removeAllListeners('notification');
251
+ client.removeAllListeners('error');
252
+ try {
253
+ await client.query('UNLISTEN "jobs:insert"');
254
+ } catch {
255
+ // Ignore listener cleanup errors during shutdown.
256
+ }
257
+ release();
258
+ }
259
+ }
208
260
  }
209
261
 
210
262
  export { Worker };