@lucaapp/service-utils 5.11.0 → 5.12.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/dist/lib/pgBoss/config.d.ts +0 -2
- package/dist/lib/pgBoss/config.js +14 -9
- package/dist/lib/pgBoss/controller/routes.d.ts +1 -1
- package/dist/lib/pgBoss/controller/routes.js +6 -2
- package/dist/lib/pgBoss/controller/routes.schema.d.ts +64 -9
- package/dist/lib/pgBoss/controller/routes.schema.js +28 -5
- package/dist/lib/pgBoss/helpers.d.ts +12 -4
- package/dist/lib/pgBoss/helpers.js +38 -21
- package/dist/lib/pgBoss/index.d.ts +13 -8
- package/dist/lib/pgBoss/index.js +47 -34
- package/dist/lib/pgBoss/metrics.d.ts +10 -3
- package/dist/lib/pgBoss/metrics.js +33 -6
- package/dist/lib/pgBoss/service/getWarnings.d.ts +2 -0
- package/dist/lib/pgBoss/service/getWarnings.js +6 -0
- package/dist/lib/pgBoss/service/listJobs.d.ts +8 -1
- package/dist/lib/pgBoss/service/listJobs.js +15 -2
- package/dist/lib/pgBoss/service/schedules.js +0 -1
- package/dist/lib/pgBoss/service.d.ts +2 -2
- package/dist/lib/pgBoss/service.js +4 -4
- package/dist/lib/pgBoss/types.d.ts +7 -6
- package/package.json +1 -1
- package/dist/lib/pgBoss/service/enqueue.d.ts +0 -3
- package/dist/lib/pgBoss/service/enqueue.js +0 -9
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type { PgBossConfig } from './types';
|
|
2
2
|
export declare const PG_BOSS_DEFAULTS: {
|
|
3
|
-
readonly maxConnectionPoolSize: 3;
|
|
4
3
|
readonly deleteAfterDays: 30;
|
|
5
|
-
readonly applicationName: "pgboss";
|
|
6
4
|
};
|
|
7
5
|
export declare const buildPgBossDefaults: (config: PgBossConfig) => Required<PgBossConfig>;
|
|
@@ -2,15 +2,20 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.buildPgBossDefaults = exports.PG_BOSS_DEFAULTS = void 0;
|
|
4
4
|
exports.PG_BOSS_DEFAULTS = {
|
|
5
|
-
maxConnectionPoolSize: 3,
|
|
6
5
|
deleteAfterDays: 30,
|
|
7
|
-
applicationName: 'pgboss',
|
|
8
6
|
};
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
const MIN_DELETE_AFTER_DAYS = 1;
|
|
8
|
+
const MAX_DELETE_AFTER_DAYS = 365;
|
|
9
|
+
const buildPgBossDefaults = (config) => {
|
|
10
|
+
const merged = {
|
|
11
|
+
deleteAfterDays: exports.PG_BOSS_DEFAULTS.deleteAfterDays,
|
|
12
|
+
mattermostWebhookUrl: null,
|
|
13
|
+
...config,
|
|
14
|
+
};
|
|
15
|
+
if (merged.deleteAfterDays < MIN_DELETE_AFTER_DAYS ||
|
|
16
|
+
merged.deleteAfterDays > MAX_DELETE_AFTER_DAYS) {
|
|
17
|
+
throw new Error(`pg-boss deleteAfterDays must be between ${MIN_DELETE_AFTER_DAYS} and ${MAX_DELETE_AFTER_DAYS}, got ${merged.deleteAfterDays}`);
|
|
18
|
+
}
|
|
19
|
+
return merged;
|
|
20
|
+
};
|
|
16
21
|
exports.buildPgBossDefaults = buildPgBossDefaults;
|
|
@@ -7,4 +7,4 @@ import type { Middleware } from '../../api/types/middleware';
|
|
|
7
7
|
* prefix MUST live on each path string to make routing work end-to-end.
|
|
8
8
|
*/
|
|
9
9
|
export declare const PGBOSS_API_PATH_PREFIX = "/support/pgboss";
|
|
10
|
-
export declare const mountPgBossApiRoutes: (api: Api, service: PgBossService, middlewares
|
|
10
|
+
export declare const mountPgBossApiRoutes: (api: Api, service: PgBossService, middlewares: Middleware<any, any, any, any, any, any>[]) => void;
|
|
@@ -13,9 +13,12 @@ exports.PGBOSS_API_PATH_PREFIX = '/support/pgboss';
|
|
|
13
13
|
const mountPgBossApiRoutes = (api, service,
|
|
14
14
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
15
15
|
middlewares) => {
|
|
16
|
+
if (!middlewares.length) {
|
|
17
|
+
throw new Error('mountPgBossApiRoutes requires at least one auth middleware — pg-boss admin routes must not be public');
|
|
18
|
+
}
|
|
16
19
|
const pgBossApi = api.child({ tags: ['PgBoss'] });
|
|
17
20
|
const p = (path) => `${exports.PGBOSS_API_PATH_PREFIX}${path}`;
|
|
18
|
-
const mw =
|
|
21
|
+
const mw = { middlewares };
|
|
19
22
|
pgBossApi.get(p('/queues'), 'List all queues with stats', {
|
|
20
23
|
...mw,
|
|
21
24
|
responses: [(0, response_1.okResponse)(routes_schema_1.queueStatsArraySchema)],
|
|
@@ -28,9 +31,10 @@ middlewares) => {
|
|
|
28
31
|
responses: [(0, response_1.okResponse)(routes_schema_1.jobArraySchema)],
|
|
29
32
|
schemas: {
|
|
30
33
|
params: routes_schema_1.queueNameParamsSchema,
|
|
34
|
+
query: routes_schema_1.listJobsQuerySchema,
|
|
31
35
|
},
|
|
32
36
|
}, async (request, _context, respond) => {
|
|
33
|
-
const jobs = await service.listJobs(request.params.name);
|
|
37
|
+
const jobs = await service.listJobs(request.params.name, request.query.limit, request.query.offset);
|
|
34
38
|
return respond((0, send_1.ok)(jobs));
|
|
35
39
|
});
|
|
36
40
|
pgBossApi.get(p('/queues/:queue/jobs/:id'), 'Get job details', {
|
|
@@ -30,43 +30,98 @@ export declare const warningsQuerySchema: z.ZodObject<{
|
|
|
30
30
|
}, {
|
|
31
31
|
limit?: number | undefined;
|
|
32
32
|
}>;
|
|
33
|
+
export declare const listJobsQuerySchema: z.ZodObject<{
|
|
34
|
+
limit: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
35
|
+
offset: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
36
|
+
}, "strip", z.ZodTypeAny, {
|
|
37
|
+
limit: number;
|
|
38
|
+
offset: number;
|
|
39
|
+
}, {
|
|
40
|
+
limit?: number | undefined;
|
|
41
|
+
offset?: number | undefined;
|
|
42
|
+
}>;
|
|
33
43
|
export declare const enqueueBodySchema: z.ZodObject<{
|
|
34
44
|
data: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
35
|
-
options: z.ZodOptional<z.
|
|
45
|
+
options: z.ZodOptional<z.ZodObject<{
|
|
46
|
+
priority: z.ZodOptional<z.ZodNumber>;
|
|
47
|
+
startAfter: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodDate]>>;
|
|
48
|
+
singletonKey: z.ZodOptional<z.ZodString>;
|
|
49
|
+
singletonSeconds: z.ZodOptional<z.ZodNumber>;
|
|
50
|
+
}, "strict", z.ZodTypeAny, {
|
|
51
|
+
priority?: number | undefined;
|
|
52
|
+
startAfter?: string | number | Date | undefined;
|
|
53
|
+
singletonKey?: string | undefined;
|
|
54
|
+
singletonSeconds?: number | undefined;
|
|
55
|
+
}, {
|
|
56
|
+
priority?: number | undefined;
|
|
57
|
+
startAfter?: string | number | Date | undefined;
|
|
58
|
+
singletonKey?: string | undefined;
|
|
59
|
+
singletonSeconds?: number | undefined;
|
|
60
|
+
}>>;
|
|
36
61
|
}, "strip", z.ZodTypeAny, {
|
|
37
|
-
options?:
|
|
62
|
+
options?: {
|
|
63
|
+
priority?: number | undefined;
|
|
64
|
+
startAfter?: string | number | Date | undefined;
|
|
65
|
+
singletonKey?: string | undefined;
|
|
66
|
+
singletonSeconds?: number | undefined;
|
|
67
|
+
} | undefined;
|
|
38
68
|
data?: Record<string, unknown> | undefined;
|
|
39
69
|
}, {
|
|
40
|
-
options?:
|
|
70
|
+
options?: {
|
|
71
|
+
priority?: number | undefined;
|
|
72
|
+
startAfter?: string | number | Date | undefined;
|
|
73
|
+
singletonKey?: string | undefined;
|
|
74
|
+
singletonSeconds?: number | undefined;
|
|
75
|
+
} | undefined;
|
|
41
76
|
data?: Record<string, unknown> | undefined;
|
|
42
77
|
}>;
|
|
43
78
|
export declare const scheduleBodySchema: z.ZodObject<{
|
|
44
79
|
name: z.ZodString;
|
|
45
80
|
cron: z.ZodString;
|
|
46
81
|
data: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
47
|
-
options: z.ZodOptional<z.
|
|
82
|
+
options: z.ZodOptional<z.ZodObject<{
|
|
83
|
+
tz: z.ZodOptional<z.ZodString>;
|
|
84
|
+
}, "strict", z.ZodTypeAny, {
|
|
85
|
+
tz?: string | undefined;
|
|
86
|
+
}, {
|
|
87
|
+
tz?: string | undefined;
|
|
88
|
+
}>>;
|
|
48
89
|
}, "strip", z.ZodTypeAny, {
|
|
49
90
|
name: string;
|
|
50
91
|
cron: string;
|
|
51
|
-
options?:
|
|
92
|
+
options?: {
|
|
93
|
+
tz?: string | undefined;
|
|
94
|
+
} | undefined;
|
|
52
95
|
data?: Record<string, unknown> | undefined;
|
|
53
96
|
}, {
|
|
54
97
|
name: string;
|
|
55
98
|
cron: string;
|
|
56
|
-
options?:
|
|
99
|
+
options?: {
|
|
100
|
+
tz?: string | undefined;
|
|
101
|
+
} | undefined;
|
|
57
102
|
data?: Record<string, unknown> | undefined;
|
|
58
103
|
}>;
|
|
59
104
|
export declare const updateScheduleBodySchema: z.ZodObject<{
|
|
60
105
|
cron: z.ZodString;
|
|
61
106
|
data: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
62
|
-
options: z.ZodOptional<z.
|
|
107
|
+
options: z.ZodOptional<z.ZodObject<{
|
|
108
|
+
tz: z.ZodOptional<z.ZodString>;
|
|
109
|
+
}, "strict", z.ZodTypeAny, {
|
|
110
|
+
tz?: string | undefined;
|
|
111
|
+
}, {
|
|
112
|
+
tz?: string | undefined;
|
|
113
|
+
}>>;
|
|
63
114
|
}, "strip", z.ZodTypeAny, {
|
|
64
115
|
cron: string;
|
|
65
|
-
options?:
|
|
116
|
+
options?: {
|
|
117
|
+
tz?: string | undefined;
|
|
118
|
+
} | undefined;
|
|
66
119
|
data?: Record<string, unknown> | undefined;
|
|
67
120
|
}, {
|
|
68
121
|
cron: string;
|
|
69
|
-
options?:
|
|
122
|
+
options?: {
|
|
123
|
+
tz?: string | undefined;
|
|
124
|
+
} | undefined;
|
|
70
125
|
data?: Record<string, unknown> | undefined;
|
|
71
126
|
}>;
|
|
72
127
|
export declare const queueStatsSchema: z.ZodObject<{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.successResponseSchema = exports.warningArraySchema = exports.warningSchema = exports.scheduleArraySchema = exports.scheduleSchema = exports.jobArraySchema = exports.jobDetailSchema = exports.queueStatsArraySchema = exports.queueStatsSchema = exports.updateScheduleBodySchema = exports.scheduleBodySchema = exports.enqueueBodySchema = exports.warningsQuerySchema = exports.scheduleNameParamsSchema = exports.queueJobParamsSchema = exports.queueNameParamsSchema = void 0;
|
|
3
|
+
exports.successResponseSchema = exports.warningArraySchema = exports.warningSchema = exports.scheduleArraySchema = exports.scheduleSchema = exports.jobArraySchema = exports.jobDetailSchema = exports.queueStatsArraySchema = exports.queueStatsSchema = exports.updateScheduleBodySchema = exports.scheduleBodySchema = exports.enqueueBodySchema = exports.listJobsQuerySchema = exports.warningsQuerySchema = exports.scheduleNameParamsSchema = exports.queueJobParamsSchema = exports.queueNameParamsSchema = void 0;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
5
|
// ── Param schemas ────────────────────────────────────────────────────
|
|
6
6
|
exports.queueNameParamsSchema = zod_1.z.object({
|
|
@@ -15,23 +15,46 @@ exports.scheduleNameParamsSchema = zod_1.z.object({
|
|
|
15
15
|
});
|
|
16
16
|
// ── Query schemas ────────────────────────────────────────────────────
|
|
17
17
|
exports.warningsQuerySchema = zod_1.z.object({
|
|
18
|
-
limit: zod_1.z.coerce.number().int().positive().optional().default(100),
|
|
18
|
+
limit: zod_1.z.coerce.number().int().positive().max(1000).optional().default(100),
|
|
19
|
+
});
|
|
20
|
+
exports.listJobsQuerySchema = zod_1.z.object({
|
|
21
|
+
limit: zod_1.z.coerce.number().int().positive().max(1000).optional().default(100),
|
|
22
|
+
offset: zod_1.z.coerce.number().int().nonnegative().optional().default(0),
|
|
19
23
|
});
|
|
20
24
|
// ── Body schemas ─────────────────────────────────────────────────────
|
|
25
|
+
/**
|
|
26
|
+
* Whitelisted enqueue options. Disallows infrastructure-touching keys
|
|
27
|
+
* (`deadLetter`, explicit `id`, `retentionSeconds`) so the support API
|
|
28
|
+
* cannot reroute jobs or rewrite retention from outside.
|
|
29
|
+
*/
|
|
30
|
+
const enqueueOptionsSchema = zod_1.z
|
|
31
|
+
.object({
|
|
32
|
+
priority: zod_1.z.number().int().optional(),
|
|
33
|
+
startAfter: zod_1.z.union([zod_1.z.string(), zod_1.z.number(), zod_1.z.date()]).optional(),
|
|
34
|
+
singletonKey: zod_1.z.string().optional(),
|
|
35
|
+
singletonSeconds: zod_1.z.number().int().positive().optional(),
|
|
36
|
+
})
|
|
37
|
+
.strict();
|
|
38
|
+
/** Whitelisted cron schedule options. Only timezone is exposed. */
|
|
39
|
+
const scheduleOptionsSchema = zod_1.z
|
|
40
|
+
.object({
|
|
41
|
+
tz: zod_1.z.string().optional(),
|
|
42
|
+
})
|
|
43
|
+
.strict();
|
|
21
44
|
exports.enqueueBodySchema = zod_1.z.object({
|
|
22
45
|
data: zod_1.z.record(zod_1.z.unknown()).optional(),
|
|
23
|
-
options:
|
|
46
|
+
options: enqueueOptionsSchema.optional(),
|
|
24
47
|
});
|
|
25
48
|
exports.scheduleBodySchema = zod_1.z.object({
|
|
26
49
|
name: zod_1.z.string(),
|
|
27
50
|
cron: zod_1.z.string(),
|
|
28
51
|
data: zod_1.z.record(zod_1.z.unknown()).optional(),
|
|
29
|
-
options:
|
|
52
|
+
options: scheduleOptionsSchema.optional(),
|
|
30
53
|
});
|
|
31
54
|
exports.updateScheduleBodySchema = zod_1.z.object({
|
|
32
55
|
cron: zod_1.z.string(),
|
|
33
56
|
data: zod_1.z.record(zod_1.z.unknown()).optional(),
|
|
34
|
-
options:
|
|
57
|
+
options: scheduleOptionsSchema.optional(),
|
|
35
58
|
});
|
|
36
59
|
// ── Response schemas ─────────────────────────────────────────────────
|
|
37
60
|
exports.queueStatsSchema = zod_1.z.object({
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { PgBoss } from 'pg-boss';
|
|
2
|
-
import type { Job, SendOptions } from 'pg-boss';
|
|
2
|
+
import type { Db, Job, SendOptions } from 'pg-boss';
|
|
3
3
|
import type { Logger } from 'pino';
|
|
4
4
|
import type { Sequelize, Transaction } from 'sequelize';
|
|
5
|
+
/**
|
|
6
|
+
* Create a pg-boss database adapter that runs SQL through the given Sequelize
|
|
7
|
+
* instance. Reuses Sequelize's connection pool, dialect options (TLS,
|
|
8
|
+
* fingerprint pinning), retries, and disconnect handling.
|
|
9
|
+
*
|
|
10
|
+
* Used as pg-boss's `db` option so pg-boss does not open its own pool.
|
|
11
|
+
*/
|
|
12
|
+
export declare const createSequelizeDbAdapter: (sequelize: Sequelize) => Db;
|
|
5
13
|
/**
|
|
6
14
|
* Enqueue a job via pg-boss with logging.
|
|
7
15
|
*/
|
|
@@ -9,10 +17,10 @@ export declare const enqueueJob: (boss: PgBoss, queueName: string, data: object,
|
|
|
9
17
|
/**
|
|
10
18
|
* Enqueue a job inside an existing Sequelize transaction.
|
|
11
19
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
20
|
+
* Builds a transactional `Db` adapter from the transaction's own Sequelize
|
|
21
|
+
* instance so the pg-boss insert participates in the surrounding transaction.
|
|
14
22
|
*/
|
|
15
|
-
export declare const enqueueInTransaction: (boss: PgBoss, queueName: string, data: object, options: SendOptions, transaction: Transaction
|
|
23
|
+
export declare const enqueueInTransaction: (boss: PgBoss, queueName: string, data: object, options: SendOptions, transaction: Transaction) => Promise<string | null>;
|
|
16
24
|
/**
|
|
17
25
|
* Wrap a pg-boss worker handler so that, when the final retry attempt fails,
|
|
18
26
|
* a Mattermost DLQ alert is fired before the error is re-thrown.
|
|
@@ -1,28 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.withDlqAlerting = exports.enqueueInTransaction = exports.enqueueJob = void 0;
|
|
3
|
+
exports.withDlqAlerting = exports.enqueueInTransaction = exports.enqueueJob = exports.createSequelizeDbAdapter = void 0;
|
|
4
4
|
const metrics_1 = require("./metrics");
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const jobId = await boss.send(queueName, data, options);
|
|
10
|
-
logger.info({ queueName, jobId }, 'Job enqueued via pg-boss');
|
|
11
|
-
return jobId;
|
|
12
|
-
};
|
|
13
|
-
exports.enqueueJob = enqueueJob;
|
|
14
|
-
/**
|
|
15
|
-
* Create a pg-boss–compatible database adapter backed by a Sequelize transaction.
|
|
6
|
+
* Create a pg-boss database adapter that runs SQL through the given Sequelize
|
|
7
|
+
* instance. Reuses Sequelize's connection pool, dialect options (TLS,
|
|
8
|
+
* fingerprint pinning), retries, and disconnect handling.
|
|
16
9
|
*
|
|
17
|
-
*
|
|
18
|
-
* so that `boss.send()` executes within the same database transaction
|
|
19
|
-
* as surrounding Sequelize operations.
|
|
10
|
+
* Used as pg-boss's `db` option so pg-boss does not open its own pool.
|
|
20
11
|
*/
|
|
21
|
-
const
|
|
12
|
+
const createSequelizeDbAdapter = (sequelize) => ({
|
|
22
13
|
executeSql: async (text, values) => {
|
|
23
14
|
const [results] = await sequelize.query(text, {
|
|
24
15
|
bind: values,
|
|
25
|
-
transaction,
|
|
26
16
|
raw: true,
|
|
27
17
|
});
|
|
28
18
|
return {
|
|
@@ -32,14 +22,39 @@ const createTransactionalDb = (sequelize, transaction) => ({
|
|
|
32
22
|
};
|
|
33
23
|
},
|
|
34
24
|
});
|
|
25
|
+
exports.createSequelizeDbAdapter = createSequelizeDbAdapter;
|
|
26
|
+
/**
|
|
27
|
+
* Enqueue a job via pg-boss with logging.
|
|
28
|
+
*/
|
|
29
|
+
const enqueueJob = async (boss, queueName, data, options = {}, logger) => {
|
|
30
|
+
const jobId = await boss.send(queueName, data, options);
|
|
31
|
+
logger.info({ queueName, jobId }, 'Job enqueued via pg-boss');
|
|
32
|
+
return jobId;
|
|
33
|
+
};
|
|
34
|
+
exports.enqueueJob = enqueueJob;
|
|
35
35
|
/**
|
|
36
36
|
* Enqueue a job inside an existing Sequelize transaction.
|
|
37
37
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
38
|
+
* Builds a transactional `Db` adapter from the transaction's own Sequelize
|
|
39
|
+
* instance so the pg-boss insert participates in the surrounding transaction.
|
|
40
40
|
*/
|
|
41
|
-
const enqueueInTransaction = async (boss, queueName, data, options, transaction
|
|
42
|
-
const
|
|
41
|
+
const enqueueInTransaction = async (boss, queueName, data, options, transaction) => {
|
|
42
|
+
const sequelize = transaction
|
|
43
|
+
.sequelize;
|
|
44
|
+
const db = {
|
|
45
|
+
executeSql: async (text, values) => {
|
|
46
|
+
const [results] = await sequelize.query(text, {
|
|
47
|
+
bind: values,
|
|
48
|
+
transaction,
|
|
49
|
+
raw: true,
|
|
50
|
+
});
|
|
51
|
+
return {
|
|
52
|
+
rows: Array.isArray(results)
|
|
53
|
+
? results
|
|
54
|
+
: [],
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
};
|
|
43
58
|
return boss.send(queueName, data, { ...options, db });
|
|
44
59
|
};
|
|
45
60
|
exports.enqueueInTransaction = enqueueInTransaction;
|
|
@@ -62,7 +77,9 @@ const withDlqAlerting = (queueName, handler, logger, mattermostWebhookUrl) => {
|
|
|
62
77
|
const isFinalFailure = retryLimit > 0 && retryCount >= retryLimit;
|
|
63
78
|
if (isFinalFailure && mattermostWebhookUrl && job) {
|
|
64
79
|
const message = error instanceof Error ? error.message : String(error);
|
|
65
|
-
|
|
80
|
+
// Fire-and-forget: webhook errors are swallowed inside sendDlqAlert,
|
|
81
|
+
// and we must not block the worker on a flaky webhook.
|
|
82
|
+
void (0, metrics_1.sendDlqAlert)(mattermostWebhookUrl, queueName, job.id, message, logger);
|
|
66
83
|
}
|
|
67
84
|
throw error;
|
|
68
85
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { PgBoss } from 'pg-boss';
|
|
2
2
|
import type { Logger } from 'pino';
|
|
3
3
|
import type { PgBossConfig } from './types';
|
|
4
|
-
|
|
4
|
+
import { type PgBossMetricsRegistration } from './metrics';
|
|
5
|
+
export type { PgBoss };
|
|
5
6
|
export type { Job, SendOptions } from 'pg-boss';
|
|
6
7
|
export type { PgBossConfig, QueueDefinition, WorkerRegistration, PgBossInstance, } from './types';
|
|
7
8
|
export { PG_BOSS_DEFAULTS, buildPgBossDefaults } from './config';
|
|
@@ -15,11 +16,13 @@ export interface PgBossContext {
|
|
|
15
16
|
logger: Logger;
|
|
16
17
|
config: Required<PgBossConfig>;
|
|
17
18
|
serviceName: string;
|
|
18
|
-
|
|
19
|
+
metricsRegistration?: PgBossMetricsRegistration;
|
|
19
20
|
}
|
|
20
21
|
/**
|
|
21
|
-
* Creates a pg-boss instance with
|
|
22
|
-
*
|
|
22
|
+
* Creates a pg-boss instance with bounded auto-restart on connection loss.
|
|
23
|
+
* After RESTART_MAX_ATTEMPTS consecutive failures the loop gives up and
|
|
24
|
+
* relies on the orchestrator (k8s) to replace the pod via failing health
|
|
25
|
+
* checks.
|
|
23
26
|
*/
|
|
24
27
|
export declare const createPgBoss: (config: PgBossConfig, logger: Logger, serviceName?: string) => PgBossContext;
|
|
25
28
|
/**
|
|
@@ -28,13 +31,15 @@ export declare const createPgBoss: (config: PgBossConfig, logger: Logger, servic
|
|
|
28
31
|
*/
|
|
29
32
|
export declare const startBoss: (ctx: PgBossContext) => Promise<void>;
|
|
30
33
|
/**
|
|
31
|
-
* Gracefully stops the pg-boss instance
|
|
32
|
-
*
|
|
34
|
+
* Gracefully stops the pg-boss instance:
|
|
35
|
+
* 1. Stop scheduling new metrics ticks.
|
|
36
|
+
* 2. Drain any in-flight metrics tick (so it doesn't race boss.stop).
|
|
37
|
+
* 3. Stop pg-boss with a 30s graceful drain.
|
|
33
38
|
*/
|
|
34
39
|
export declare const stopBoss: (ctx: PgBossContext) => Promise<void>;
|
|
35
40
|
/**
|
|
36
|
-
* Maps the legacy `deleteAfterDays` setting to pg-boss v12's
|
|
37
|
-
*
|
|
41
|
+
* Maps the legacy `deleteAfterDays` setting to pg-boss v12's per-queue
|
|
42
|
+
* `deleteAfterSeconds` option. Use this when calling
|
|
38
43
|
* `boss.createQueue(name, { ...defaultRetention(ctx), policy: 'singleton' })`.
|
|
39
44
|
*/
|
|
40
45
|
export declare const defaultRetention: (ctx: PgBossContext) => {
|
package/dist/lib/pgBoss/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.defaultRetention = exports.stopBoss = exports.startBoss = exports.createPgBoss = exports.sendDlqAlert = exports.withDlqAlerting = exports.enqueueInTransaction = exports.enqueueJob = exports.registerPgBossMetrics = exports.PgBossService = exports.mountPgBossApiRoutes = exports.buildPgBossDefaults = exports.PG_BOSS_DEFAULTS =
|
|
3
|
+
exports.defaultRetention = exports.stopBoss = exports.startBoss = exports.createPgBoss = exports.sendDlqAlert = exports.withDlqAlerting = exports.enqueueInTransaction = exports.enqueueJob = exports.registerPgBossMetrics = exports.PgBossService = exports.mountPgBossApiRoutes = exports.buildPgBossDefaults = exports.PG_BOSS_DEFAULTS = void 0;
|
|
4
4
|
const pg_boss_1 = require("pg-boss");
|
|
5
|
-
Object.defineProperty(exports, "PgBoss", { enumerable: true, get: function () { return pg_boss_1.PgBoss; } });
|
|
6
5
|
const config_1 = require("./config");
|
|
7
6
|
const metrics_1 = require("./metrics");
|
|
7
|
+
const helpers_1 = require("./helpers");
|
|
8
8
|
var config_2 = require("./config");
|
|
9
9
|
Object.defineProperty(exports, "PG_BOSS_DEFAULTS", { enumerable: true, get: function () { return config_2.PG_BOSS_DEFAULTS; } });
|
|
10
10
|
Object.defineProperty(exports, "buildPgBossDefaults", { enumerable: true, get: function () { return config_2.buildPgBossDefaults; } });
|
|
@@ -14,30 +14,53 @@ var service_1 = require("./service");
|
|
|
14
14
|
Object.defineProperty(exports, "PgBossService", { enumerable: true, get: function () { return service_1.PgBossService; } });
|
|
15
15
|
var metrics_2 = require("./metrics");
|
|
16
16
|
Object.defineProperty(exports, "registerPgBossMetrics", { enumerable: true, get: function () { return metrics_2.registerPgBossMetrics; } });
|
|
17
|
-
var
|
|
18
|
-
Object.defineProperty(exports, "enqueueJob", { enumerable: true, get: function () { return
|
|
19
|
-
Object.defineProperty(exports, "enqueueInTransaction", { enumerable: true, get: function () { return
|
|
20
|
-
Object.defineProperty(exports, "withDlqAlerting", { enumerable: true, get: function () { return
|
|
17
|
+
var helpers_2 = require("./helpers");
|
|
18
|
+
Object.defineProperty(exports, "enqueueJob", { enumerable: true, get: function () { return helpers_2.enqueueJob; } });
|
|
19
|
+
Object.defineProperty(exports, "enqueueInTransaction", { enumerable: true, get: function () { return helpers_2.enqueueInTransaction; } });
|
|
20
|
+
Object.defineProperty(exports, "withDlqAlerting", { enumerable: true, get: function () { return helpers_2.withDlqAlerting; } });
|
|
21
21
|
var metrics_3 = require("./metrics");
|
|
22
22
|
Object.defineProperty(exports, "sendDlqAlert", { enumerable: true, get: function () { return metrics_3.sendDlqAlert; } });
|
|
23
|
-
const
|
|
23
|
+
const RESTART_DELAY_MIN_MS = 5_000;
|
|
24
|
+
const RESTART_DELAY_MAX_MS = 60_000;
|
|
25
|
+
const RESTART_MAX_ATTEMPTS = 10;
|
|
24
26
|
const SECONDS_PER_DAY = 86_400;
|
|
25
27
|
/**
|
|
26
|
-
* Creates a pg-boss instance with
|
|
27
|
-
*
|
|
28
|
+
* Creates a pg-boss instance with bounded auto-restart on connection loss.
|
|
29
|
+
* After RESTART_MAX_ATTEMPTS consecutive failures the loop gives up and
|
|
30
|
+
* relies on the orchestrator (k8s) to replace the pod via failing health
|
|
31
|
+
* checks.
|
|
28
32
|
*/
|
|
29
33
|
const createPgBoss = (config, logger, serviceName = config.schema) => {
|
|
30
34
|
const resolvedConfig = (0, config_1.buildPgBossDefaults)(config);
|
|
31
35
|
const boss = new pg_boss_1.PgBoss({
|
|
32
|
-
|
|
36
|
+
db: (0, helpers_1.createSequelizeDbAdapter)(resolvedConfig.sequelize),
|
|
33
37
|
schema: resolvedConfig.schema,
|
|
34
|
-
application_name: resolvedConfig.applicationName,
|
|
35
|
-
max: resolvedConfig.maxConnectionPoolSize,
|
|
36
38
|
migrate: true,
|
|
37
39
|
});
|
|
40
|
+
let restartAttempts = 0;
|
|
41
|
+
const scheduleRestart = () => {
|
|
42
|
+
if (restartAttempts >= RESTART_MAX_ATTEMPTS) {
|
|
43
|
+
logger.fatal({ attempts: restartAttempts }, 'pg-boss restart limit exceeded — giving up');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const delay = Math.min(RESTART_DELAY_MIN_MS * 2 ** restartAttempts, RESTART_DELAY_MAX_MS);
|
|
47
|
+
restartAttempts += 1;
|
|
48
|
+
setTimeout(async () => {
|
|
49
|
+
try {
|
|
50
|
+
logger.info({ attempt: restartAttempts }, 'Attempting pg-boss restart');
|
|
51
|
+
await boss.start();
|
|
52
|
+
restartAttempts = 0;
|
|
53
|
+
logger.info('pg-boss restarted successfully');
|
|
54
|
+
}
|
|
55
|
+
catch (restartError) {
|
|
56
|
+
logger.error({ error: restartError, attempt: restartAttempts }, 'pg-boss restart failed — will retry with backoff');
|
|
57
|
+
scheduleRestart();
|
|
58
|
+
}
|
|
59
|
+
}, delay);
|
|
60
|
+
};
|
|
38
61
|
boss.on('error', (error) => {
|
|
39
62
|
logger.error({ error }, 'pg-boss error — will attempt restart');
|
|
40
|
-
scheduleRestart(
|
|
63
|
+
scheduleRestart();
|
|
41
64
|
});
|
|
42
65
|
boss.on('warning', warning => {
|
|
43
66
|
logger.warn({ warning }, 'pg-boss warning');
|
|
@@ -52,43 +75,33 @@ exports.createPgBoss = createPgBoss;
|
|
|
52
75
|
const startBoss = async (ctx) => {
|
|
53
76
|
ctx.logger.info({ schema: ctx.config.schema }, 'Starting pg-boss');
|
|
54
77
|
await ctx.boss.start();
|
|
55
|
-
ctx.
|
|
78
|
+
ctx.metricsRegistration = (0, metrics_1.registerPgBossMetrics)(ctx.boss, ctx.serviceName, ctx.logger, ctx.config.mattermostWebhookUrl ?? undefined);
|
|
56
79
|
ctx.logger.info({ schema: ctx.config.schema }, 'pg-boss started successfully');
|
|
57
80
|
};
|
|
58
81
|
exports.startBoss = startBoss;
|
|
59
82
|
/**
|
|
60
|
-
* Gracefully stops the pg-boss instance
|
|
61
|
-
*
|
|
83
|
+
* Gracefully stops the pg-boss instance:
|
|
84
|
+
* 1. Stop scheduling new metrics ticks.
|
|
85
|
+
* 2. Drain any in-flight metrics tick (so it doesn't race boss.stop).
|
|
86
|
+
* 3. Stop pg-boss with a 30s graceful drain.
|
|
62
87
|
*/
|
|
63
88
|
const stopBoss = async (ctx) => {
|
|
64
89
|
ctx.logger.info({ schema: ctx.config.schema }, 'Stopping pg-boss');
|
|
65
|
-
if (ctx.
|
|
66
|
-
clearInterval(ctx.
|
|
67
|
-
ctx.
|
|
90
|
+
if (ctx.metricsRegistration) {
|
|
91
|
+
clearInterval(ctx.metricsRegistration.intervalHandle);
|
|
92
|
+
await ctx.metricsRegistration.awaitInFlight();
|
|
93
|
+
ctx.metricsRegistration = undefined;
|
|
68
94
|
}
|
|
69
95
|
await ctx.boss.stop({ graceful: true, timeout: 30_000 });
|
|
70
96
|
ctx.logger.info({ schema: ctx.config.schema }, 'pg-boss stopped');
|
|
71
97
|
};
|
|
72
98
|
exports.stopBoss = stopBoss;
|
|
73
99
|
/**
|
|
74
|
-
* Maps the legacy `deleteAfterDays` setting to pg-boss v12's
|
|
75
|
-
*
|
|
100
|
+
* Maps the legacy `deleteAfterDays` setting to pg-boss v12's per-queue
|
|
101
|
+
* `deleteAfterSeconds` option. Use this when calling
|
|
76
102
|
* `boss.createQueue(name, { ...defaultRetention(ctx), policy: 'singleton' })`.
|
|
77
103
|
*/
|
|
78
104
|
const defaultRetention = (ctx) => ({
|
|
79
105
|
deleteAfterSeconds: ctx.config.deleteAfterDays * SECONDS_PER_DAY,
|
|
80
106
|
});
|
|
81
107
|
exports.defaultRetention = defaultRetention;
|
|
82
|
-
const scheduleRestart = (boss, logger) => {
|
|
83
|
-
setTimeout(async () => {
|
|
84
|
-
try {
|
|
85
|
-
logger.info('Attempting pg-boss restart after error');
|
|
86
|
-
await boss.start();
|
|
87
|
-
logger.info('pg-boss restarted successfully');
|
|
88
|
-
}
|
|
89
|
-
catch (restartError) {
|
|
90
|
-
logger.error({ error: restartError }, 'pg-boss restart failed — will retry');
|
|
91
|
-
scheduleRestart(boss, logger);
|
|
92
|
-
}
|
|
93
|
-
}, RESTART_DELAY_MS);
|
|
94
|
-
};
|
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
import { PgBoss } from 'pg-boss';
|
|
2
2
|
import type { Logger } from 'pino';
|
|
3
|
+
export interface PgBossMetricsRegistration {
|
|
4
|
+
intervalHandle: NodeJS.Timeout;
|
|
5
|
+
/** Resolve once any in-flight collectMetrics tick finishes. */
|
|
6
|
+
awaitInFlight: () => Promise<void>;
|
|
7
|
+
}
|
|
3
8
|
/**
|
|
4
9
|
* Register Prometheus metrics by polling pg-boss queue stats.
|
|
5
10
|
*
|
|
6
11
|
* pg-boss v12 removed the `monitor-states` event, so we poll
|
|
7
12
|
* `getQueues()` + `getQueueStats()` on a timer instead.
|
|
8
13
|
*
|
|
9
|
-
* Returns the interval handle so callers can
|
|
14
|
+
* Returns the interval handle plus an `awaitInFlight` so callers can drain a
|
|
15
|
+
* running tick before stopping pg-boss on shutdown.
|
|
10
16
|
*/
|
|
11
|
-
export declare const registerPgBossMetrics: (boss: PgBoss, serviceName: string, logger: Logger, mattermostWebhookUrl?: string) =>
|
|
17
|
+
export declare const registerPgBossMetrics: (boss: PgBoss, serviceName: string, logger: Logger, mattermostWebhookUrl?: string) => PgBossMetricsRegistration;
|
|
12
18
|
/**
|
|
13
|
-
* Send a Mattermost alert for a dead-letter job.
|
|
19
|
+
* Send a Mattermost alert for a dead-letter job. Errors are swallowed and
|
|
20
|
+
* logged so callers can fire-and-forget without affecting job semantics.
|
|
14
21
|
*/
|
|
15
22
|
export declare const sendDlqAlert: (webhookUrl: string, queueName: string, jobId: string, error: string, logger: Logger) => Promise<void>;
|
|
@@ -55,46 +55,73 @@ const queueTotal = new promClient.Gauge({
|
|
|
55
55
|
labelNames: ['queue', 'service'],
|
|
56
56
|
});
|
|
57
57
|
const POLL_INTERVAL_MS = 30_000;
|
|
58
|
+
const MATTERMOST_TIMEOUT_MS = 5000;
|
|
58
59
|
/**
|
|
59
60
|
* Register Prometheus metrics by polling pg-boss queue stats.
|
|
60
61
|
*
|
|
61
62
|
* pg-boss v12 removed the `monitor-states` event, so we poll
|
|
62
63
|
* `getQueues()` + `getQueueStats()` on a timer instead.
|
|
63
64
|
*
|
|
64
|
-
* Returns the interval handle so callers can
|
|
65
|
+
* Returns the interval handle plus an `awaitInFlight` so callers can drain a
|
|
66
|
+
* running tick before stopping pg-boss on shutdown.
|
|
65
67
|
*/
|
|
66
68
|
const registerPgBossMetrics = (boss, serviceName, logger, mattermostWebhookUrl) => {
|
|
69
|
+
let inflight;
|
|
70
|
+
let knownQueues = new Set();
|
|
67
71
|
const collectMetrics = async () => {
|
|
68
72
|
try {
|
|
69
73
|
const queues = await boss.getQueues();
|
|
74
|
+
const currentQueues = new Set();
|
|
70
75
|
for (const queue of queues) {
|
|
71
76
|
const stats = await boss.getQueueStats(queue.name);
|
|
72
77
|
const labels = { queue: queue.name, service: serviceName };
|
|
78
|
+
currentQueues.add(queue.name);
|
|
73
79
|
queueDepth.set(labels, stats.queuedCount ?? 0);
|
|
74
80
|
queueActive.set(labels, stats.activeCount ?? 0);
|
|
75
81
|
queueTotal.set(labels, stats.totalCount ?? 0);
|
|
76
82
|
}
|
|
83
|
+
for (const stale of knownQueues) {
|
|
84
|
+
if (currentQueues.has(stale))
|
|
85
|
+
continue;
|
|
86
|
+
const labels = { queue: stale, service: serviceName };
|
|
87
|
+
queueDepth.remove(labels);
|
|
88
|
+
queueActive.remove(labels);
|
|
89
|
+
queueTotal.remove(labels);
|
|
90
|
+
}
|
|
91
|
+
knownQueues = currentQueues;
|
|
77
92
|
}
|
|
78
93
|
catch (error) {
|
|
79
94
|
logger.error({ error }, 'Failed to collect pg-boss metrics');
|
|
80
95
|
}
|
|
81
96
|
};
|
|
97
|
+
const tick = () => {
|
|
98
|
+
inflight = collectMetrics().finally(() => {
|
|
99
|
+
inflight = undefined;
|
|
100
|
+
});
|
|
101
|
+
};
|
|
82
102
|
if (mattermostWebhookUrl) {
|
|
83
103
|
logger.info('Mattermost DLQ alerting enabled');
|
|
84
104
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
return
|
|
105
|
+
tick();
|
|
106
|
+
const intervalHandle = setInterval(tick, POLL_INTERVAL_MS);
|
|
107
|
+
return {
|
|
108
|
+
intervalHandle,
|
|
109
|
+
awaitInFlight: async () => {
|
|
110
|
+
if (inflight)
|
|
111
|
+
await inflight;
|
|
112
|
+
},
|
|
113
|
+
};
|
|
88
114
|
};
|
|
89
115
|
exports.registerPgBossMetrics = registerPgBossMetrics;
|
|
90
116
|
/**
|
|
91
|
-
* Send a Mattermost alert for a dead-letter job.
|
|
117
|
+
* Send a Mattermost alert for a dead-letter job. Errors are swallowed and
|
|
118
|
+
* logged so callers can fire-and-forget without affecting job semantics.
|
|
92
119
|
*/
|
|
93
120
|
const sendDlqAlert = async (webhookUrl, queueName, jobId, error, logger) => {
|
|
94
121
|
try {
|
|
95
122
|
await axios_1.default.post(webhookUrl, {
|
|
96
123
|
text: `🚨 **pg-boss DLQ Alert**\n**Queue:** \`${queueName}\`\n**Job ID:** \`${jobId}\`\n**Error:** ${error}`,
|
|
97
|
-
});
|
|
124
|
+
}, { timeout: MATTERMOST_TIMEOUT_MS });
|
|
98
125
|
}
|
|
99
126
|
catch (webhookError) {
|
|
100
127
|
logger.error({ error: webhookError }, 'Failed to send Mattermost DLQ alert');
|
|
@@ -5,5 +5,7 @@ import { PgBoss } from 'pg-boss';
|
|
|
5
5
|
* pg-boss v12 persists warnings (slow queries, queue backlogs, clock skew)
|
|
6
6
|
* when `persistWarnings: true` is set. There is no public class method,
|
|
7
7
|
* so we query the warning table via `getDb()`.
|
|
8
|
+
*
|
|
9
|
+
* The schema name is interpolated into raw SQL — guard against injection.
|
|
8
10
|
*/
|
|
9
11
|
export declare const getWarnings: (boss: PgBoss, schema: string, limit?: number) => Promise<Record<string, unknown>[]>;
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getWarnings = void 0;
|
|
4
|
+
const SCHEMA_NAME_RE = /^[a-z_][a-z0-9_]*$/;
|
|
4
5
|
/**
|
|
5
6
|
* Retrieve persisted warnings from the pg-boss warning table.
|
|
6
7
|
*
|
|
7
8
|
* pg-boss v12 persists warnings (slow queries, queue backlogs, clock skew)
|
|
8
9
|
* when `persistWarnings: true` is set. There is no public class method,
|
|
9
10
|
* so we query the warning table via `getDb()`.
|
|
11
|
+
*
|
|
12
|
+
* The schema name is interpolated into raw SQL — guard against injection.
|
|
10
13
|
*/
|
|
11
14
|
const getWarnings = async (boss, schema, limit = 100) => {
|
|
15
|
+
if (!SCHEMA_NAME_RE.test(schema)) {
|
|
16
|
+
throw new Error(`Invalid pg-boss schema name: ${schema}`);
|
|
17
|
+
}
|
|
12
18
|
const db = boss.getDb();
|
|
13
19
|
const result = await db.executeSql(`SELECT id, type, message, data, created_on
|
|
14
20
|
FROM "${schema}".warning
|
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
import { PgBoss } from 'pg-boss';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* List jobs in a queue with mandatory pagination.
|
|
4
|
+
*
|
|
5
|
+
* pg-boss `findJobs` does not support limit/offset directly, so we slice
|
|
6
|
+
* client-side. Callers must pass `limit` (clamped to MAX_LIMIT) and
|
|
7
|
+
* `offset` to avoid loading 1M rows into memory.
|
|
8
|
+
*/
|
|
9
|
+
export declare const listJobs: (boss: PgBoss, queueName: string, limit?: number, offset?: number) => Promise<Record<string, unknown>[]>;
|
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.listJobs = void 0;
|
|
4
|
-
const
|
|
4
|
+
const DEFAULT_LIMIT = 100;
|
|
5
|
+
const MAX_LIMIT = 1000;
|
|
6
|
+
/**
|
|
7
|
+
* List jobs in a queue with mandatory pagination.
|
|
8
|
+
*
|
|
9
|
+
* pg-boss `findJobs` does not support limit/offset directly, so we slice
|
|
10
|
+
* client-side. Callers must pass `limit` (clamped to MAX_LIMIT) and
|
|
11
|
+
* `offset` to avoid loading 1M rows into memory.
|
|
12
|
+
*/
|
|
13
|
+
const listJobs = async (boss, queueName, limit = DEFAULT_LIMIT, offset = 0) => {
|
|
14
|
+
const cappedLimit = Math.min(Math.max(limit, 1), MAX_LIMIT);
|
|
15
|
+
const safeOffset = Math.max(offset, 0);
|
|
5
16
|
const jobs = await boss.findJobs(queueName);
|
|
6
|
-
return jobs
|
|
17
|
+
return jobs
|
|
18
|
+
.slice(safeOffset, safeOffset + cappedLimit)
|
|
19
|
+
.map(job => ({ ...job }));
|
|
7
20
|
};
|
|
8
21
|
exports.listJobs = listJobs;
|
|
@@ -12,7 +12,6 @@ const createSchedule = async (boss, logger, name, cron, data, options) => {
|
|
|
12
12
|
};
|
|
13
13
|
exports.createSchedule = createSchedule;
|
|
14
14
|
const updateSchedule = async (boss, logger, name, cron, data, options) => {
|
|
15
|
-
await boss.unschedule(name);
|
|
16
15
|
await boss.schedule(name, cron, data || {}, options || {});
|
|
17
16
|
logger.info({ name, cron }, 'Schedule updated');
|
|
18
17
|
};
|
|
@@ -4,9 +4,9 @@ export declare class PgBossService {
|
|
|
4
4
|
private readonly boss;
|
|
5
5
|
private readonly logger;
|
|
6
6
|
private readonly schema;
|
|
7
|
-
constructor(boss: PgBoss, logger: Logger, schema
|
|
7
|
+
constructor(boss: PgBoss, logger: Logger, schema: string);
|
|
8
8
|
getQueueStats: () => Promise<import("./service/getQueueStats").QueueStats[]>;
|
|
9
|
-
listJobs: (queueName: string) => Promise<Record<string, unknown>[]>;
|
|
9
|
+
listJobs: (queueName: string, limit?: number, offset?: number) => Promise<Record<string, unknown>[]>;
|
|
10
10
|
getJob: (queueName: string, jobId: string) => Promise<Record<string, unknown> | null>;
|
|
11
11
|
retryJob: (queueName: string, jobId: string) => Promise<void>;
|
|
12
12
|
cancelJob: (queueName: string, jobId: string) => Promise<void>;
|
|
@@ -6,15 +6,15 @@ const listJobs_1 = require("./service/listJobs");
|
|
|
6
6
|
const getJob_1 = require("./service/getJob");
|
|
7
7
|
const manageJob_1 = require("./service/manageJob");
|
|
8
8
|
const schedules_1 = require("./service/schedules");
|
|
9
|
-
const
|
|
9
|
+
const helpers_1 = require("./helpers");
|
|
10
10
|
const getWarnings_1 = require("./service/getWarnings");
|
|
11
11
|
class PgBossService {
|
|
12
|
-
constructor(boss, logger, schema
|
|
12
|
+
constructor(boss, logger, schema) {
|
|
13
13
|
this.boss = boss;
|
|
14
14
|
this.logger = logger;
|
|
15
15
|
this.schema = schema;
|
|
16
16
|
this.getQueueStats = () => (0, getQueueStats_1.getQueueStats)(this.boss);
|
|
17
|
-
this.listJobs = (queueName) => (0, listJobs_1.listJobs)(this.boss, queueName);
|
|
17
|
+
this.listJobs = (queueName, limit, offset) => (0, listJobs_1.listJobs)(this.boss, queueName, limit, offset);
|
|
18
18
|
this.getJob = (queueName, jobId) => (0, getJob_1.getJob)(this.boss, queueName, jobId);
|
|
19
19
|
this.retryJob = (queueName, jobId) => (0, manageJob_1.manageJob)(this.boss, this.logger, 'retry', queueName, jobId);
|
|
20
20
|
this.cancelJob = (queueName, jobId) => (0, manageJob_1.manageJob)(this.boss, this.logger, 'cancel', queueName, jobId);
|
|
@@ -23,7 +23,7 @@ class PgBossService {
|
|
|
23
23
|
this.createSchedule = (name, cron, data, options) => (0, schedules_1.createSchedule)(this.boss, this.logger, name, cron, data, options);
|
|
24
24
|
this.updateSchedule = (name, cron, data, options) => (0, schedules_1.updateSchedule)(this.boss, this.logger, name, cron, data, options);
|
|
25
25
|
this.deleteSchedule = (name) => (0, schedules_1.deleteSchedule)(this.boss, this.logger, name);
|
|
26
|
-
this.enqueueJob = (queueName, data, options) => (0,
|
|
26
|
+
this.enqueueJob = (queueName, data, options) => (0, helpers_1.enqueueJob)(this.boss, queueName, data ?? {}, options, this.logger);
|
|
27
27
|
this.getWarnings = (limit) => (0, getWarnings_1.getWarnings)(this.boss, this.schema, limit);
|
|
28
28
|
}
|
|
29
29
|
}
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import type { QueueOptions, Job, WorkOptions } from 'pg-boss';
|
|
2
2
|
import type { PgBoss } from 'pg-boss';
|
|
3
|
+
import type { Sequelize } from 'sequelize';
|
|
3
4
|
export type { SendOptions, QueueOptions, WorkOptions, Job } from 'pg-boss';
|
|
4
5
|
export interface PgBossConfig {
|
|
5
6
|
enabled: boolean;
|
|
6
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Existing Sequelize instance. pg-boss reuses its connection pool, dialect
|
|
9
|
+
* options (TLS, fingerprint pinning), and retry config — no separate pool.
|
|
10
|
+
*/
|
|
11
|
+
sequelize: Sequelize;
|
|
7
12
|
schema: string;
|
|
8
|
-
/** Max connections in the pg-boss pool. Default: 3 */
|
|
9
|
-
maxConnectionPoolSize?: number;
|
|
10
13
|
/** Days to keep completed/failed jobs. Default: 30 (GDPR-aligned) */
|
|
11
14
|
deleteAfterDays?: number;
|
|
12
|
-
/** Application name for PG connections. Default: "pgboss" */
|
|
13
|
-
applicationName?: string;
|
|
14
15
|
/** Mattermost webhook URL for DLQ alerts (optional) */
|
|
15
|
-
mattermostWebhookUrl?: string;
|
|
16
|
+
mattermostWebhookUrl?: string | null;
|
|
16
17
|
}
|
|
17
18
|
export interface QueueDefinition {
|
|
18
19
|
name: string;
|
package/package.json
CHANGED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.enqueue = void 0;
|
|
4
|
-
const enqueue = async (boss, logger, queueName, data, options) => {
|
|
5
|
-
const jobId = await boss.send(queueName, data || {}, options || {});
|
|
6
|
-
logger.info({ queue: queueName, jobId }, 'Job enqueued');
|
|
7
|
-
return jobId;
|
|
8
|
-
};
|
|
9
|
-
exports.enqueue = enqueue;
|