@lucaapp/service-utils 5.8.0 → 5.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/dist/lib/kafka/events/payment.d.ts +189 -12
- package/dist/lib/kafka/events/payment.js +13 -0
- package/dist/lib/kafka/events.d.ts +2 -1
- package/dist/lib/kafka/events.js +5 -1
- package/dist/lib/kafka/kafkaClient.js +40 -11
- package/dist/lib/metrics/metricsClient.js +17 -7
- package/dist/lib/pgBoss/controller/routes.d.ts +6 -16
- package/dist/lib/pgBoss/controller/routes.js +34 -41
- package/dist/lib/pgBoss/helpers.d.ts +9 -1
- package/dist/lib/pgBoss/helpers.js +28 -1
- package/dist/lib/pgBoss/index.d.ts +17 -4
- package/dist/lib/pgBoss/index.js +29 -9
- package/dist/lib/pgBoss/metrics.js +17 -7
- package/dist/lib/pgBoss/service/manageJob.js +1 -1
- package/dist/lib/phone/phone.js +17 -7
- package/dist/lib/serviceIdentity/service.d.ts +1 -0
- package/dist/lib/serviceIdentity/service.js +1 -0
- package/dist/lib/serviceIdentity/serviceIdentity.js +17 -7
- package/package.json +6 -6
|
@@ -1,28 +1,205 @@
|
|
|
1
|
-
|
|
1
|
+
import { SupportedCurrencies } from '../../money/supportedCurrencies';
|
|
2
|
+
/**
|
|
3
|
+
* JSON-serialisable value tree.
|
|
4
|
+
*
|
|
5
|
+
* Used for opaque payloads carried across the wire. Anything stored here
|
|
6
|
+
* must round-trip through `JSON.stringify` / `JSON.parse` without loss.
|
|
7
|
+
*/
|
|
8
|
+
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
9
|
+
[key: string]: JsonValue;
|
|
10
|
+
};
|
|
11
|
+
/** Legacy 3-letter terminal status code from the original payment provider integration. */
|
|
12
|
+
export type RapydPaymentStatus = 'ACT' | 'CAN' | 'CLO' | 'ERR' | 'EXP' | 'NEW' | 'REV';
|
|
13
|
+
/** Provider-neutral payment lifecycle status. */
|
|
14
|
+
export type PaymentStatus = 'PAID' | 'NEW' | 'ACTIVE' | 'CANCELLED' | 'EXPIRED' | 'REFUNDED' | 'ERROR' | 'UNKNOWN';
|
|
15
|
+
/**
|
|
16
|
+
* Lifecycle timestamps shared by all payment events.
|
|
17
|
+
*
|
|
18
|
+
* Each timestamp marks the first time the payment entered the corresponding
|
|
19
|
+
* state. A null value means the state was never reached.
|
|
20
|
+
*/
|
|
21
|
+
export type PaymentStatusTimings = {
|
|
22
|
+
/** Timestamp when the payment record was created (session start, NOT settlement). */
|
|
23
|
+
paymentCreatedAt: Date;
|
|
24
|
+
/** Payment record created; awaiting consumer action (e.g. order created, link sent, QR displayed). */
|
|
25
|
+
statusNewAt: Date | null;
|
|
26
|
+
/** Consumer engaged with the payment flow (e.g. opened a 3DS challenge, tapped a terminal, clicked the payment link). */
|
|
27
|
+
statusActiveAt: Date | null;
|
|
28
|
+
/** Provider authorised and captured the funds (online auth + capture or terminal sale complete). */
|
|
29
|
+
statusCompleteAt?: Date | null;
|
|
30
|
+
/** Cancelled by consumer or operator before completion. */
|
|
31
|
+
statusCanceledAt: Date | null;
|
|
32
|
+
/** Payment session expired before completion. */
|
|
33
|
+
statusExpiredAt: Date | null;
|
|
34
|
+
/** Provider returned an error or refused the payment. */
|
|
35
|
+
statusErrorAt: Date | null;
|
|
36
|
+
/** Authorised payment was reversed before settlement (e.g. chargeback pre-arbitration). */
|
|
37
|
+
statusReversedAt?: Date | null;
|
|
38
|
+
/** Projected or actual settlement date to the merchant balance. Null until the provider schedules settlement. */
|
|
39
|
+
settleAt?: Date | null;
|
|
40
|
+
};
|
|
41
|
+
/** Fields shared by both the legacy {@link Payment} event and the provider-neutral {@link PaymentEvent}. */
|
|
42
|
+
export type PaymentBase = {
|
|
43
|
+
/** Stable payment id. Uniquely identifies the payment across all systems. */
|
|
2
44
|
uuid: string;
|
|
45
|
+
/** Merchant location id (UUID). */
|
|
3
46
|
locationId: string;
|
|
47
|
+
/** Human-readable location name. Null when not provided. */
|
|
4
48
|
locationName: string | null;
|
|
49
|
+
/** Table or seat identifier from the POS. Null for non-dine-in payments. */
|
|
5
50
|
table: string | null;
|
|
51
|
+
/** Pre-created payment-request id. Null for spontaneous payments. */
|
|
6
52
|
paymentRequestId: string | null;
|
|
7
|
-
|
|
8
|
-
paidAt: Date | null;
|
|
53
|
+
/** Short verifier token used by consumer-facing flows to prove ownership of the payment session. */
|
|
9
54
|
paymentVerifier: string;
|
|
10
|
-
|
|
55
|
+
/** Net goods/services amount before tip, in the smallest currency subunit. */
|
|
11
56
|
invoiceAmount: number;
|
|
57
|
+
/** Consumer-added gratuity, in the smallest currency subunit. Zero when no tip was given. */
|
|
12
58
|
tipAmount: number;
|
|
59
|
+
} & PaymentStatusTimings;
|
|
60
|
+
/**
|
|
61
|
+
* Legacy payment event tied to the original provider integration.
|
|
62
|
+
*
|
|
63
|
+
* Kept for backward compatibility. New consumers should subscribe to
|
|
64
|
+
* {@link PaymentEvent}.
|
|
65
|
+
*/
|
|
66
|
+
export type Payment = PaymentBase & {
|
|
67
|
+
/** Total paid by the consumer (= `invoiceAmount + tipAmount`), in the smallest currency subunit. Kept for legacy consumers. */
|
|
68
|
+
totalAmount: number;
|
|
69
|
+
/** Timestamp when the payment was marked paid. */
|
|
70
|
+
paidAt: Date | null;
|
|
71
|
+
/** @deprecated Legacy terminal "CLO" state. Use `statusCompleteAt`. */
|
|
72
|
+
statusClosedAt: Date | null;
|
|
73
|
+
/** Fixed processing fee, in the smallest currency subunit. */
|
|
13
74
|
fixedFee: number;
|
|
75
|
+
/** Variable fee (commission + markup), in the smallest currency subunit. */
|
|
14
76
|
variableFee: number;
|
|
77
|
+
/** Legacy provider payment id. */
|
|
15
78
|
rapydPaymentId: string | null;
|
|
79
|
+
/** Legacy provider error code on failure. */
|
|
16
80
|
rapydErrorCode: string | null;
|
|
81
|
+
/** Legacy provider error message on failure. */
|
|
17
82
|
rapydErrorMessage: string | null;
|
|
18
|
-
|
|
83
|
+
/** Legacy 3-letter status code from the provider. */
|
|
84
|
+
rapydPaymentStatus: RapydPaymentStatus;
|
|
85
|
+
/** Legacy provider customer / saved-card alias. */
|
|
19
86
|
rapydCustomerId: string | null;
|
|
87
|
+
/** Aggregated payout batch id. Null until the payment is included in a payout. */
|
|
20
88
|
payoutId: string | null;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Provider-neutral payment lifecycle event.
|
|
92
|
+
*
|
|
93
|
+
* Carries the full domain context needed for fiscalisation, receipt
|
|
94
|
+
* rendering, and downstream reconciliation. Wire format is JSON: monetary
|
|
95
|
+
* fields are integers in the smallest currency subunit and `currency`
|
|
96
|
+
* declares the unit.
|
|
97
|
+
*/
|
|
98
|
+
export type PaymentEvent = PaymentBase & {
|
|
99
|
+
/** Checkout session id. Optional — some flows emit payment events without a checkout. */
|
|
100
|
+
checkoutId?: string;
|
|
101
|
+
/** Order id (e.g. POS / fiscalisation order reference). Absent for payments not tied to an order. */
|
|
102
|
+
orderId?: string;
|
|
103
|
+
/** Operator-added service charge / convenience fee, in the smallest currency subunit. Zero if none. */
|
|
104
|
+
surchargeAmount: number;
|
|
105
|
+
/** ISO 4217 code that applies to every monetary field on this event. Single-currency invariant. */
|
|
106
|
+
currency: SupportedCurrencies;
|
|
107
|
+
/** Lifecycle status at event-emission time. */
|
|
108
|
+
status: PaymentStatus;
|
|
109
|
+
/** Method identifier, e.g. `'CARD'`, `'CASH'`, `'WALLET'`. */
|
|
110
|
+
method: string;
|
|
111
|
+
/** True if `method` is a cash variant. Cash flows skip provider webhooks and short-circuit fiscalisation. */
|
|
112
|
+
isCash: boolean;
|
|
113
|
+
/** Origin service identifier (free-form short string). */
|
|
114
|
+
source: string;
|
|
115
|
+
/** Aggregated payout batch id. Null until included in a payout. */
|
|
116
|
+
payoutId?: string | null;
|
|
117
|
+
/** Provider-side identifiers, errors, and raw provider payload. Absent for cash. */
|
|
118
|
+
merchantDetails?: PaymentMerchantDetails;
|
|
119
|
+
/** Card-specific identifiers and terminal context. Absent for non-card methods. */
|
|
120
|
+
cardDetails?: PaymentCardDetails;
|
|
121
|
+
/** Per-item breakdown for fiscalisation and receipt rendering. */
|
|
122
|
+
lineItems?: PaymentEventLineItem[];
|
|
123
|
+
};
|
|
124
|
+
/** Amount the consumer actually pays = `invoiceAmount + tipAmount + surchargeAmount` (smallest currency subunit). */
|
|
125
|
+
export declare const consumerPayAmount: (event: PaymentEvent) => number;
|
|
126
|
+
/**
|
|
127
|
+
* Amount receivable by the operator before Luca / provider fees are
|
|
128
|
+
* deducted = `invoiceAmount + tipAmount` (smallest currency subunit).
|
|
129
|
+
*
|
|
130
|
+
* Surcharge is excluded — it covers the consumer-side fee, not operator
|
|
131
|
+
* revenue. Net payout to the operator is this minus the per-payment fees.
|
|
132
|
+
*/
|
|
133
|
+
export declare const operatorReceivableAmount: (event: PaymentEvent) => number;
|
|
134
|
+
/**
|
|
135
|
+
* Provider-side metadata for a payment.
|
|
136
|
+
*
|
|
137
|
+
* All fields optional — populated by whichever provider handled the
|
|
138
|
+
* payment. Absent on cash and other non-provider flows.
|
|
139
|
+
*/
|
|
140
|
+
export type PaymentMerchantDetails = {
|
|
141
|
+
/** Payment-processor identifier (lowercase short string). Null when not yet routed to a provider. */
|
|
142
|
+
provider?: string | null;
|
|
143
|
+
/** Provider's payment id. Null until the provider authorises. */
|
|
144
|
+
providerIdentifier?: string | null;
|
|
145
|
+
/** Machine-readable error / decline code from the provider. Null on success. */
|
|
146
|
+
providerErrorCode?: string | null;
|
|
147
|
+
/** Human-readable provider error detail. Null on success. */
|
|
148
|
+
providerErrorMessage?: string | null;
|
|
149
|
+
/** Raw provider payload (notification or response body). Used for audits and chargeback evidence. */
|
|
150
|
+
providerData?: {
|
|
151
|
+
[key: string]: JsonValue;
|
|
152
|
+
} | null;
|
|
153
|
+
/** Provider-facing merchant reference used to reconcile internal records against the provider's records. */
|
|
154
|
+
externalReference?: string | null;
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* Card and terminal context.
|
|
158
|
+
*
|
|
159
|
+
* Card fields populated for card payments; terminal/consumer fields
|
|
160
|
+
* populated when applicable, regardless of card vs. wallet.
|
|
161
|
+
*/
|
|
162
|
+
export type PaymentCardDetails = {
|
|
163
|
+
/** Last 4 digits of the card. Null for non-card methods. */
|
|
164
|
+
last4?: string | null;
|
|
165
|
+
/** Provider authorisation code. Null until authorised. */
|
|
166
|
+
authCode?: string | null;
|
|
167
|
+
/** Saved-card alias for tokenised cards. Null for one-time payments. */
|
|
168
|
+
consumerAlias?: string | null;
|
|
169
|
+
/** Physical terminal id. Null for online / QR payments. */
|
|
170
|
+
terminalId?: string | null;
|
|
171
|
+
/** Consumer account id. Null for anonymous payments. */
|
|
172
|
+
consumerId?: string | null;
|
|
173
|
+
};
|
|
174
|
+
/**
|
|
175
|
+
* One billable line in the order, used for fiscalisation and receipt
|
|
176
|
+
* rendering.
|
|
177
|
+
*
|
|
178
|
+
* Sub-items represent components of a parent line (e.g. base drink + paid
|
|
179
|
+
* topping) and follow the same shape recursively. Amounts are integers in
|
|
180
|
+
* the smallest currency subunit.
|
|
181
|
+
*/
|
|
182
|
+
export type PaymentEventLineItem = {
|
|
183
|
+
/** Stable line-item id. Omitted on synthetic sub-items that have no own row. */
|
|
184
|
+
uuid?: string;
|
|
185
|
+
/** POS-system menu item / SKU id. Null when not synced from the POS menu. */
|
|
186
|
+
posItemId?: string | null;
|
|
187
|
+
/** Display name (e.g. `'Espresso'`). May be omitted when the consumer can resolve it from `posItemId`. */
|
|
188
|
+
name?: string;
|
|
189
|
+
/** Number of units, e.g. `2` for two coffees. */
|
|
190
|
+
quantity: number;
|
|
191
|
+
/** Price for one unit, in the smallest currency subunit. */
|
|
192
|
+
pricePerUnit: number;
|
|
193
|
+
/** `quantity × pricePerUnit + Σ subItems.totalPrice`, in the smallest currency subunit. */
|
|
194
|
+
totalPrice: number;
|
|
195
|
+
/**
|
|
196
|
+
* VAT / sales-tax rate as a percentage value (e.g. `19` for 19%, `7` for
|
|
197
|
+
* 7%). NOT a decimal fraction — `0.19` would mean 0.19%. Null if
|
|
198
|
+
* tax-exempt or unknown.
|
|
199
|
+
*/
|
|
200
|
+
taxPercentage?: number | null;
|
|
201
|
+
/** Item currency. Typically equals the parent payment currency. */
|
|
202
|
+
currency: SupportedCurrencies;
|
|
203
|
+
/** Recursive components of this line. Each sub-item follows the same shape. */
|
|
204
|
+
subItems?: PaymentEventLineItem[];
|
|
28
205
|
};
|
|
@@ -1,2 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.operatorReceivableAmount = exports.consumerPayAmount = void 0;
|
|
4
|
+
/** Amount the consumer actually pays = `invoiceAmount + tipAmount + surchargeAmount` (smallest currency subunit). */
|
|
5
|
+
const consumerPayAmount = (event) => event.invoiceAmount + event.tipAmount + event.surchargeAmount;
|
|
6
|
+
exports.consumerPayAmount = consumerPayAmount;
|
|
7
|
+
/**
|
|
8
|
+
* Amount receivable by the operator before Luca / provider fees are
|
|
9
|
+
* deducted = `invoiceAmount + tipAmount` (smallest currency subunit).
|
|
10
|
+
*
|
|
11
|
+
* Surcharge is excluded — it covers the consumer-side fee, not operator
|
|
12
|
+
* revenue. Net payout to the operator is this minus the per-payment fees.
|
|
13
|
+
*/
|
|
14
|
+
const operatorReceivableAmount = (event) => event.invoiceAmount + event.tipAmount;
|
|
15
|
+
exports.operatorReceivableAmount = operatorReceivableAmount;
|
|
@@ -82,5 +82,6 @@ declare const MessageIssuer: {
|
|
|
82
82
|
"wsevent_backend-pos": Service;
|
|
83
83
|
tokenization_job_item: Service;
|
|
84
84
|
};
|
|
85
|
-
|
|
85
|
+
declare const MessageSignatureIssuers: Partial<Record<KafkaTopic, Service[]>>;
|
|
86
|
+
export { KafkaTopic, MessageIssuer, MessageSignatureIssuers };
|
|
86
87
|
export type { MessageFormats };
|
package/dist/lib/kafka/events.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.MessageIssuer = exports.KafkaTopic = void 0;
|
|
3
|
+
exports.MessageSignatureIssuers = exports.MessageIssuer = exports.KafkaTopic = void 0;
|
|
4
4
|
const serviceIdentity_1 = require("../serviceIdentity");
|
|
5
5
|
var KafkaTopic;
|
|
6
6
|
(function (KafkaTopic) {
|
|
@@ -48,3 +48,7 @@ const MessageIssuer = {
|
|
|
48
48
|
[KafkaTopic.TOKENIZATION_JOB_ITEM]: serviceIdentity_1.Service.BACKEND_PAY,
|
|
49
49
|
};
|
|
50
50
|
exports.MessageIssuer = MessageIssuer;
|
|
51
|
+
const MessageSignatureIssuers = {
|
|
52
|
+
[KafkaTopic.PAYMENTS]: [serviceIdentity_1.Service.BACKEND_PAY, serviceIdentity_1.Service.BACKEND_TSE],
|
|
53
|
+
};
|
|
54
|
+
exports.MessageSignatureIssuers = MessageSignatureIssuers;
|
|
@@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|
|
15
15
|
}) : function(o, v) {
|
|
16
16
|
o["default"] = v;
|
|
17
17
|
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
25
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
36
|
exports.KafkaClient = void 0;
|
|
27
37
|
const kafkajs_1 = require("kafkajs");
|
|
@@ -61,6 +71,18 @@ const messageAcknowledgedCounter = new metrics_1.metricsClient.Counter({
|
|
|
61
71
|
const getIssuer = (kafkaTopic) => {
|
|
62
72
|
return events_1.MessageIssuer[kafkaTopic].valueOf();
|
|
63
73
|
};
|
|
74
|
+
const getAllowedSignatureIssuers = (kafkaTopic) => {
|
|
75
|
+
return (events_1.MessageSignatureIssuers[kafkaTopic] ?? [events_1.MessageIssuer[kafkaTopic]]).map(issuer => issuer.valueOf());
|
|
76
|
+
};
|
|
77
|
+
const headerToString = (header) => {
|
|
78
|
+
if (Array.isArray(header)) {
|
|
79
|
+
return headerToString(header[0]);
|
|
80
|
+
}
|
|
81
|
+
if (Buffer.isBuffer(header)) {
|
|
82
|
+
return header.toString();
|
|
83
|
+
}
|
|
84
|
+
return header;
|
|
85
|
+
};
|
|
64
86
|
class KafkaClient {
|
|
65
87
|
constructor(parentLogger, kafkaConfig, topicSecrets, serviceIdentity) {
|
|
66
88
|
this.connect = async () => {
|
|
@@ -119,11 +141,15 @@ class KafkaClient {
|
|
|
119
141
|
this.logger.info('Skipping signature verification (encryption disabled)');
|
|
120
142
|
return;
|
|
121
143
|
}
|
|
122
|
-
|
|
144
|
+
const signature = headerToString(headers?.signature);
|
|
145
|
+
if (!signature) {
|
|
123
146
|
throw (0, utils_1.logAndGetError)(this.logger, 'Unable to verify signature. Expected header not present');
|
|
124
147
|
}
|
|
125
|
-
const issuer = getIssuer(kafkaTopic);
|
|
126
|
-
const
|
|
148
|
+
const issuer = headerToString(headers?.signatureIssuer) ?? getIssuer(kafkaTopic);
|
|
149
|
+
const allowedIssuers = getAllowedSignatureIssuers(kafkaTopic);
|
|
150
|
+
if (!allowedIssuers.includes(issuer)) {
|
|
151
|
+
throw (0, utils_1.logAndGetError)(this.logger, `Unable to verify signature. Issuer ${issuer} is not allowed for topic=${kafkaTopic}`);
|
|
152
|
+
}
|
|
127
153
|
const jwks = await this.serviceIdentity.getRemoteJWKS(issuer);
|
|
128
154
|
if (!jwks) {
|
|
129
155
|
throw (0, utils_1.logAndGetError)(this.logger, `Unable to find jwks for issuer=${issuer}`);
|
|
@@ -230,7 +256,10 @@ class KafkaClient {
|
|
|
230
256
|
{
|
|
231
257
|
key,
|
|
232
258
|
value: encryptedValue,
|
|
233
|
-
headers: {
|
|
259
|
+
headers: {
|
|
260
|
+
signature,
|
|
261
|
+
signatureIssuer: this.serviceIdentity.identityName,
|
|
262
|
+
},
|
|
234
263
|
},
|
|
235
264
|
],
|
|
236
265
|
};
|
|
@@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|
|
15
15
|
}) : function(o, v) {
|
|
16
16
|
o["default"] = v;
|
|
17
17
|
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
25
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
36
|
exports.metricsClient = void 0;
|
|
27
37
|
exports.metricsClient = __importStar(require("prom-client"));
|
|
@@ -1,20 +1,10 @@
|
|
|
1
1
|
import { Api } from '../../api/api';
|
|
2
2
|
import { PgBossService } from '../service';
|
|
3
|
+
import type { Middleware } from '../../api/types/middleware';
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* - GET /queues — list queues with stats
|
|
8
|
-
* - GET /queues/:name/jobs — list jobs in a queue
|
|
9
|
-
* - GET /queues/:queue/jobs/:id — job detail
|
|
10
|
-
* - POST /queues/:queue/jobs/:id/retry — retry a job
|
|
11
|
-
* - POST /queues/:queue/jobs/:id/cancel — cancel a job
|
|
12
|
-
* - DELETE /queues/:queue/jobs/:id — delete a job
|
|
13
|
-
* - GET /schedules — list cron schedules
|
|
14
|
-
* - POST /schedules — create a schedule
|
|
15
|
-
* - PUT /schedules/:name — update a schedule
|
|
16
|
-
* - DELETE /schedules/:name — delete a schedule
|
|
17
|
-
* - POST /queues/:name/jobs — enqueue a new job
|
|
18
|
-
* - GET /warnings — recent warnings
|
|
5
|
+
* Express path prefix for every pg-boss admin route. `Api.child()` only
|
|
6
|
+
* affects OpenAPI documentation, not the underlying express router, so the
|
|
7
|
+
* prefix MUST live on each path string to make routing work end-to-end.
|
|
19
8
|
*/
|
|
20
|
-
export declare const
|
|
9
|
+
export declare const PGBOSS_API_PATH_PREFIX = "/support/pgboss";
|
|
10
|
+
export declare const mountPgBossApiRoutes: (api: Api, service: PgBossService, middlewares?: Middleware<any, any, any, any, any, any>[]) => void;
|
|
@@ -1,37 +1,30 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.mountPgBossApiRoutes = void 0;
|
|
3
|
+
exports.mountPgBossApiRoutes = exports.PGBOSS_API_PATH_PREFIX = void 0;
|
|
4
4
|
const send_1 = require("../../api/send");
|
|
5
5
|
const response_1 = require("../../api/response");
|
|
6
6
|
const routes_schema_1 = require("./routes.schema");
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* - GET /queues — list queues with stats
|
|
12
|
-
* - GET /queues/:name/jobs — list jobs in a queue
|
|
13
|
-
* - GET /queues/:queue/jobs/:id — job detail
|
|
14
|
-
* - POST /queues/:queue/jobs/:id/retry — retry a job
|
|
15
|
-
* - POST /queues/:queue/jobs/:id/cancel — cancel a job
|
|
16
|
-
* - DELETE /queues/:queue/jobs/:id — delete a job
|
|
17
|
-
* - GET /schedules — list cron schedules
|
|
18
|
-
* - POST /schedules — create a schedule
|
|
19
|
-
* - PUT /schedules/:name — update a schedule
|
|
20
|
-
* - DELETE /schedules/:name — delete a schedule
|
|
21
|
-
* - POST /queues/:name/jobs — enqueue a new job
|
|
22
|
-
* - GET /warnings — recent warnings
|
|
8
|
+
* Express path prefix for every pg-boss admin route. `Api.child()` only
|
|
9
|
+
* affects OpenAPI documentation, not the underlying express router, so the
|
|
10
|
+
* prefix MUST live on each path string to make routing work end-to-end.
|
|
23
11
|
*/
|
|
24
|
-
|
|
12
|
+
exports.PGBOSS_API_PATH_PREFIX = '/support/pgboss';
|
|
13
|
+
const mountPgBossApiRoutes = (api, service,
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
15
|
+
middlewares) => {
|
|
25
16
|
const pgBossApi = api.child({ tags: ['PgBoss'] });
|
|
26
|
-
|
|
27
|
-
|
|
17
|
+
const p = (path) => `${exports.PGBOSS_API_PATH_PREFIX}${path}`;
|
|
18
|
+
const mw = middlewares?.length ? { middlewares } : {};
|
|
19
|
+
pgBossApi.get(p('/queues'), 'List all queues with stats', {
|
|
20
|
+
...mw,
|
|
28
21
|
responses: [(0, response_1.okResponse)(routes_schema_1.queueStatsArraySchema)],
|
|
29
22
|
}, async (_request, _context, respond) => {
|
|
30
23
|
const stats = await service.getQueueStats();
|
|
31
24
|
return respond((0, send_1.ok)(stats));
|
|
32
25
|
});
|
|
33
|
-
|
|
34
|
-
|
|
26
|
+
pgBossApi.get(p('/queues/:name/jobs'), 'List jobs in a queue', {
|
|
27
|
+
...mw,
|
|
35
28
|
responses: [(0, response_1.okResponse)(routes_schema_1.jobArraySchema)],
|
|
36
29
|
schemas: {
|
|
37
30
|
params: routes_schema_1.queueNameParamsSchema,
|
|
@@ -40,8 +33,8 @@ const mountPgBossApiRoutes = (api, service) => {
|
|
|
40
33
|
const jobs = await service.listJobs(request.params.name);
|
|
41
34
|
return respond((0, send_1.ok)(jobs));
|
|
42
35
|
});
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
pgBossApi.get(p('/queues/:queue/jobs/:id'), 'Get job details', {
|
|
37
|
+
...mw,
|
|
45
38
|
responses: [(0, response_1.okResponse)(routes_schema_1.jobDetailSchema)],
|
|
46
39
|
schemas: {
|
|
47
40
|
params: routes_schema_1.queueJobParamsSchema,
|
|
@@ -54,8 +47,8 @@ const mountPgBossApiRoutes = (api, service) => {
|
|
|
54
47
|
}
|
|
55
48
|
return respond((0, send_1.ok)(job));
|
|
56
49
|
});
|
|
57
|
-
|
|
58
|
-
|
|
50
|
+
pgBossApi.post(p('/queues/:queue/jobs/:id/retry'), 'Retry a failed job', {
|
|
51
|
+
...mw,
|
|
59
52
|
responses: [(0, response_1.okResponse)(routes_schema_1.successResponseSchema)],
|
|
60
53
|
schemas: {
|
|
61
54
|
params: routes_schema_1.queueJobParamsSchema,
|
|
@@ -65,8 +58,8 @@ const mountPgBossApiRoutes = (api, service) => {
|
|
|
65
58
|
await service.retryJob(queue, id);
|
|
66
59
|
return respond((0, send_1.ok)({ success: true, jobId: id }));
|
|
67
60
|
});
|
|
68
|
-
|
|
69
|
-
|
|
61
|
+
pgBossApi.post(p('/queues/:queue/jobs/:id/cancel'), 'Cancel a job', {
|
|
62
|
+
...mw,
|
|
70
63
|
responses: [(0, response_1.okResponse)(routes_schema_1.successResponseSchema)],
|
|
71
64
|
schemas: {
|
|
72
65
|
params: routes_schema_1.queueJobParamsSchema,
|
|
@@ -76,8 +69,8 @@ const mountPgBossApiRoutes = (api, service) => {
|
|
|
76
69
|
await service.cancelJob(queue, id);
|
|
77
70
|
return respond((0, send_1.ok)({ success: true, jobId: id }));
|
|
78
71
|
});
|
|
79
|
-
|
|
80
|
-
|
|
72
|
+
pgBossApi.delete(p('/queues/:queue/jobs/:id'), 'Delete a job', {
|
|
73
|
+
...mw,
|
|
81
74
|
responses: [(0, response_1.okResponse)(routes_schema_1.successResponseSchema)],
|
|
82
75
|
schemas: {
|
|
83
76
|
params: routes_schema_1.queueJobParamsSchema,
|
|
@@ -87,15 +80,15 @@ const mountPgBossApiRoutes = (api, service) => {
|
|
|
87
80
|
await service.deleteJob(queue, id);
|
|
88
81
|
return respond((0, send_1.ok)({ success: true, deleted: id }));
|
|
89
82
|
});
|
|
90
|
-
|
|
91
|
-
|
|
83
|
+
pgBossApi.get(p('/schedules'), 'List all cron schedules', {
|
|
84
|
+
...mw,
|
|
92
85
|
responses: [(0, response_1.okResponse)(routes_schema_1.scheduleArraySchema)],
|
|
93
86
|
}, async (_request, _context, respond) => {
|
|
94
87
|
const schedules = await service.getSchedules();
|
|
95
88
|
return respond((0, send_1.ok)(schedules));
|
|
96
89
|
});
|
|
97
|
-
|
|
98
|
-
|
|
90
|
+
pgBossApi.post(p('/schedules'), 'Create a cron schedule', {
|
|
91
|
+
...mw,
|
|
99
92
|
responses: [(0, response_1.createdResponse)(routes_schema_1.successResponseSchema)],
|
|
100
93
|
schemas: {
|
|
101
94
|
body: routes_schema_1.scheduleBodySchema,
|
|
@@ -105,8 +98,8 @@ const mountPgBossApiRoutes = (api, service) => {
|
|
|
105
98
|
await service.createSchedule(name, cron, data, options);
|
|
106
99
|
return respond((0, send_1.created)({ success: true, name }));
|
|
107
100
|
});
|
|
108
|
-
|
|
109
|
-
|
|
101
|
+
pgBossApi.put(p('/schedules/:name'), 'Update a cron schedule', {
|
|
102
|
+
...mw,
|
|
110
103
|
responses: [(0, response_1.okResponse)(routes_schema_1.successResponseSchema)],
|
|
111
104
|
schemas: {
|
|
112
105
|
params: routes_schema_1.scheduleNameParamsSchema,
|
|
@@ -117,8 +110,8 @@ const mountPgBossApiRoutes = (api, service) => {
|
|
|
117
110
|
await service.updateSchedule(request.params.name, cron, data, options);
|
|
118
111
|
return respond((0, send_1.ok)({ success: true, name: request.params.name }));
|
|
119
112
|
});
|
|
120
|
-
|
|
121
|
-
|
|
113
|
+
pgBossApi.delete(p('/schedules/:name'), 'Delete a cron schedule', {
|
|
114
|
+
...mw,
|
|
122
115
|
responses: [(0, response_1.okResponse)(routes_schema_1.successResponseSchema)],
|
|
123
116
|
schemas: {
|
|
124
117
|
params: routes_schema_1.scheduleNameParamsSchema,
|
|
@@ -127,8 +120,8 @@ const mountPgBossApiRoutes = (api, service) => {
|
|
|
127
120
|
await service.deleteSchedule(request.params.name);
|
|
128
121
|
return respond((0, send_1.ok)({ success: true, deleted: request.params.name }));
|
|
129
122
|
});
|
|
130
|
-
|
|
131
|
-
|
|
123
|
+
pgBossApi.post(p('/queues/:name/jobs'), 'Enqueue a new job', {
|
|
124
|
+
...mw,
|
|
132
125
|
responses: [(0, response_1.createdResponse)(routes_schema_1.successResponseSchema)],
|
|
133
126
|
schemas: {
|
|
134
127
|
params: routes_schema_1.queueNameParamsSchema,
|
|
@@ -139,8 +132,8 @@ const mountPgBossApiRoutes = (api, service) => {
|
|
|
139
132
|
const jobId = await service.enqueueJob(request.params.name, data, options);
|
|
140
133
|
return respond((0, send_1.created)({ success: true, jobId: jobId ?? undefined }));
|
|
141
134
|
});
|
|
142
|
-
|
|
143
|
-
|
|
135
|
+
pgBossApi.get(p('/warnings'), 'List recent warnings', {
|
|
136
|
+
...mw,
|
|
144
137
|
responses: [(0, response_1.okResponse)(routes_schema_1.warningArraySchema)],
|
|
145
138
|
schemas: {
|
|
146
139
|
query: routes_schema_1.warningsQuerySchema,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PgBoss } from 'pg-boss';
|
|
2
|
-
import type { SendOptions } from 'pg-boss';
|
|
2
|
+
import type { Job, SendOptions } from 'pg-boss';
|
|
3
3
|
import type { Logger } from 'pino';
|
|
4
4
|
import type { Sequelize, Transaction } from 'sequelize';
|
|
5
5
|
/**
|
|
@@ -13,3 +13,11 @@ export declare const enqueueJob: (boss: PgBoss, queueName: string, data: object,
|
|
|
13
13
|
* instead of calling internal SQL functions directly.
|
|
14
14
|
*/
|
|
15
15
|
export declare const enqueueInTransaction: (boss: PgBoss, queueName: string, data: object, options: SendOptions, transaction: Transaction, sequelize: Sequelize) => Promise<string | null>;
|
|
16
|
+
/**
|
|
17
|
+
* Wrap a pg-boss worker handler so that, when the final retry attempt fails,
|
|
18
|
+
* a Mattermost DLQ alert is fired before the error is re-thrown.
|
|
19
|
+
*
|
|
20
|
+
* Requires the worker to be registered with `{ includeMetadata: true }` so
|
|
21
|
+
* `retryCount` and `retryLimit` are populated on each job.
|
|
22
|
+
*/
|
|
23
|
+
export declare const withDlqAlerting: <T = object>(queueName: string, handler: (jobs: Job<T>[]) => Promise<void>, logger: Logger, mattermostWebhookUrl?: string) => ((jobs: Job<T>[]) => Promise<void>);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.enqueueInTransaction = exports.enqueueJob = void 0;
|
|
3
|
+
exports.withDlqAlerting = exports.enqueueInTransaction = exports.enqueueJob = void 0;
|
|
4
|
+
const metrics_1 = require("./metrics");
|
|
4
5
|
/**
|
|
5
6
|
* Enqueue a job via pg-boss with logging.
|
|
6
7
|
*/
|
|
@@ -42,3 +43,29 @@ const enqueueInTransaction = async (boss, queueName, data, options, transaction,
|
|
|
42
43
|
return boss.send(queueName, data, { ...options, db });
|
|
43
44
|
};
|
|
44
45
|
exports.enqueueInTransaction = enqueueInTransaction;
|
|
46
|
+
/**
|
|
47
|
+
* Wrap a pg-boss worker handler so that, when the final retry attempt fails,
|
|
48
|
+
* a Mattermost DLQ alert is fired before the error is re-thrown.
|
|
49
|
+
*
|
|
50
|
+
* Requires the worker to be registered with `{ includeMetadata: true }` so
|
|
51
|
+
* `retryCount` and `retryLimit` are populated on each job.
|
|
52
|
+
*/
|
|
53
|
+
const withDlqAlerting = (queueName, handler, logger, mattermostWebhookUrl) => {
|
|
54
|
+
return async (jobs) => {
|
|
55
|
+
try {
|
|
56
|
+
await handler(jobs);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
const job = jobs[0];
|
|
60
|
+
const retryCount = job?.retryCount ?? 0;
|
|
61
|
+
const retryLimit = job?.retryLimit ?? 0;
|
|
62
|
+
const isFinalFailure = retryLimit > 0 && retryCount >= retryLimit;
|
|
63
|
+
if (isFinalFailure && mattermostWebhookUrl && job) {
|
|
64
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
65
|
+
await (0, metrics_1.sendDlqAlert)(mattermostWebhookUrl, queueName, job.id, message, logger);
|
|
66
|
+
}
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
exports.withDlqAlerting = withDlqAlerting;
|
|
@@ -2,28 +2,41 @@ import { PgBoss } from 'pg-boss';
|
|
|
2
2
|
import type { Logger } from 'pino';
|
|
3
3
|
import type { PgBossConfig } from './types';
|
|
4
4
|
export { PgBoss };
|
|
5
|
+
export type { Job, SendOptions } from 'pg-boss';
|
|
5
6
|
export type { PgBossConfig, QueueDefinition, WorkerRegistration, PgBossInstance, } from './types';
|
|
6
7
|
export { PG_BOSS_DEFAULTS, buildPgBossDefaults } from './config';
|
|
7
8
|
export { mountPgBossApiRoutes } from './controller/routes';
|
|
8
9
|
export { PgBossService } from './service';
|
|
9
10
|
export { registerPgBossMetrics } from './metrics';
|
|
10
|
-
export { enqueueJob, enqueueInTransaction } from './helpers';
|
|
11
|
+
export { enqueueJob, enqueueInTransaction, withDlqAlerting } from './helpers';
|
|
11
12
|
export { sendDlqAlert } from './metrics';
|
|
12
13
|
export interface PgBossContext {
|
|
13
14
|
boss: PgBoss;
|
|
14
15
|
logger: Logger;
|
|
15
16
|
config: Required<PgBossConfig>;
|
|
17
|
+
serviceName: string;
|
|
18
|
+
metricsInterval?: NodeJS.Timeout;
|
|
16
19
|
}
|
|
17
20
|
/**
|
|
18
21
|
* Creates a pg-boss instance with error recovery (auto-restart on connection loss).
|
|
19
22
|
* Issue #510 mitigation: on error, waits 5s then restarts.
|
|
20
23
|
*/
|
|
21
|
-
export declare const createPgBoss: (config: PgBossConfig, logger: Logger) => PgBossContext;
|
|
24
|
+
export declare const createPgBoss: (config: PgBossConfig, logger: Logger, serviceName?: string) => PgBossContext;
|
|
22
25
|
/**
|
|
23
|
-
* Starts the pg-boss instance
|
|
26
|
+
* Starts the pg-boss instance and registers Prometheus metrics polling.
|
|
27
|
+
* Call after database is connected.
|
|
24
28
|
*/
|
|
25
29
|
export declare const startBoss: (ctx: PgBossContext) => Promise<void>;
|
|
26
30
|
/**
|
|
27
|
-
* Gracefully stops the pg-boss instance
|
|
31
|
+
* Gracefully stops the pg-boss instance and clears metrics polling.
|
|
32
|
+
* Suitable as a shutdown handler.
|
|
28
33
|
*/
|
|
29
34
|
export declare const stopBoss: (ctx: PgBossContext) => Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Maps the legacy `deleteAfterDays` setting to pg-boss v12's
|
|
37
|
+
* per-queue `deleteAfterSeconds` option. Use this when calling
|
|
38
|
+
* `boss.createQueue(name, { ...defaultRetention(ctx), policy: 'singleton' })`.
|
|
39
|
+
*/
|
|
40
|
+
export declare const defaultRetention: (ctx: PgBossContext) => {
|
|
41
|
+
deleteAfterSeconds: number;
|
|
42
|
+
};
|
package/dist/lib/pgBoss/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.stopBoss = exports.startBoss = exports.createPgBoss = exports.sendDlqAlert = exports.enqueueInTransaction = exports.enqueueJob = exports.registerPgBossMetrics = exports.PgBossService = exports.mountPgBossApiRoutes = exports.buildPgBossDefaults = exports.PG_BOSS_DEFAULTS = exports.PgBoss = void 0;
|
|
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 = exports.PgBoss = void 0;
|
|
4
4
|
const pg_boss_1 = require("pg-boss");
|
|
5
5
|
Object.defineProperty(exports, "PgBoss", { enumerable: true, get: function () { return pg_boss_1.PgBoss; } });
|
|
6
6
|
const config_1 = require("./config");
|
|
7
|
+
const metrics_1 = require("./metrics");
|
|
7
8
|
var config_2 = require("./config");
|
|
8
9
|
Object.defineProperty(exports, "PG_BOSS_DEFAULTS", { enumerable: true, get: function () { return config_2.PG_BOSS_DEFAULTS; } });
|
|
9
10
|
Object.defineProperty(exports, "buildPgBossDefaults", { enumerable: true, get: function () { return config_2.buildPgBossDefaults; } });
|
|
@@ -11,24 +12,27 @@ var routes_1 = require("./controller/routes");
|
|
|
11
12
|
Object.defineProperty(exports, "mountPgBossApiRoutes", { enumerable: true, get: function () { return routes_1.mountPgBossApiRoutes; } });
|
|
12
13
|
var service_1 = require("./service");
|
|
13
14
|
Object.defineProperty(exports, "PgBossService", { enumerable: true, get: function () { return service_1.PgBossService; } });
|
|
14
|
-
var
|
|
15
|
-
Object.defineProperty(exports, "registerPgBossMetrics", { enumerable: true, get: function () { return
|
|
15
|
+
var metrics_2 = require("./metrics");
|
|
16
|
+
Object.defineProperty(exports, "registerPgBossMetrics", { enumerable: true, get: function () { return metrics_2.registerPgBossMetrics; } });
|
|
16
17
|
var helpers_1 = require("./helpers");
|
|
17
18
|
Object.defineProperty(exports, "enqueueJob", { enumerable: true, get: function () { return helpers_1.enqueueJob; } });
|
|
18
19
|
Object.defineProperty(exports, "enqueueInTransaction", { enumerable: true, get: function () { return helpers_1.enqueueInTransaction; } });
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
Object.defineProperty(exports, "withDlqAlerting", { enumerable: true, get: function () { return helpers_1.withDlqAlerting; } });
|
|
21
|
+
var metrics_3 = require("./metrics");
|
|
22
|
+
Object.defineProperty(exports, "sendDlqAlert", { enumerable: true, get: function () { return metrics_3.sendDlqAlert; } });
|
|
21
23
|
const RESTART_DELAY_MS = 5000;
|
|
24
|
+
const SECONDS_PER_DAY = 86_400;
|
|
22
25
|
/**
|
|
23
26
|
* Creates a pg-boss instance with error recovery (auto-restart on connection loss).
|
|
24
27
|
* Issue #510 mitigation: on error, waits 5s then restarts.
|
|
25
28
|
*/
|
|
26
|
-
const createPgBoss = (config, logger) => {
|
|
29
|
+
const createPgBoss = (config, logger, serviceName = config.schema) => {
|
|
27
30
|
const resolvedConfig = (0, config_1.buildPgBossDefaults)(config);
|
|
28
31
|
const boss = new pg_boss_1.PgBoss({
|
|
29
32
|
connectionString: resolvedConfig.connectionString,
|
|
30
33
|
schema: resolvedConfig.schema,
|
|
31
34
|
application_name: resolvedConfig.applicationName,
|
|
35
|
+
max: resolvedConfig.maxConnectionPoolSize,
|
|
32
36
|
migrate: true,
|
|
33
37
|
});
|
|
34
38
|
boss.on('error', (error) => {
|
|
@@ -38,27 +42,43 @@ const createPgBoss = (config, logger) => {
|
|
|
38
42
|
boss.on('warning', warning => {
|
|
39
43
|
logger.warn({ warning }, 'pg-boss warning');
|
|
40
44
|
});
|
|
41
|
-
return { boss, logger, config: resolvedConfig };
|
|
45
|
+
return { boss, logger, config: resolvedConfig, serviceName };
|
|
42
46
|
};
|
|
43
47
|
exports.createPgBoss = createPgBoss;
|
|
44
48
|
/**
|
|
45
|
-
* Starts the pg-boss instance
|
|
49
|
+
* Starts the pg-boss instance and registers Prometheus metrics polling.
|
|
50
|
+
* Call after database is connected.
|
|
46
51
|
*/
|
|
47
52
|
const startBoss = async (ctx) => {
|
|
48
53
|
ctx.logger.info({ schema: ctx.config.schema }, 'Starting pg-boss');
|
|
49
54
|
await ctx.boss.start();
|
|
55
|
+
ctx.metricsInterval = (0, metrics_1.registerPgBossMetrics)(ctx.boss, ctx.serviceName, ctx.logger, ctx.config.mattermostWebhookUrl || undefined);
|
|
50
56
|
ctx.logger.info({ schema: ctx.config.schema }, 'pg-boss started successfully');
|
|
51
57
|
};
|
|
52
58
|
exports.startBoss = startBoss;
|
|
53
59
|
/**
|
|
54
|
-
* Gracefully stops the pg-boss instance
|
|
60
|
+
* Gracefully stops the pg-boss instance and clears metrics polling.
|
|
61
|
+
* Suitable as a shutdown handler.
|
|
55
62
|
*/
|
|
56
63
|
const stopBoss = async (ctx) => {
|
|
57
64
|
ctx.logger.info({ schema: ctx.config.schema }, 'Stopping pg-boss');
|
|
65
|
+
if (ctx.metricsInterval) {
|
|
66
|
+
clearInterval(ctx.metricsInterval);
|
|
67
|
+
ctx.metricsInterval = undefined;
|
|
68
|
+
}
|
|
58
69
|
await ctx.boss.stop({ graceful: true, timeout: 30_000 });
|
|
59
70
|
ctx.logger.info({ schema: ctx.config.schema }, 'pg-boss stopped');
|
|
60
71
|
};
|
|
61
72
|
exports.stopBoss = stopBoss;
|
|
73
|
+
/**
|
|
74
|
+
* Maps the legacy `deleteAfterDays` setting to pg-boss v12's
|
|
75
|
+
* per-queue `deleteAfterSeconds` option. Use this when calling
|
|
76
|
+
* `boss.createQueue(name, { ...defaultRetention(ctx), policy: 'singleton' })`.
|
|
77
|
+
*/
|
|
78
|
+
const defaultRetention = (ctx) => ({
|
|
79
|
+
deleteAfterSeconds: ctx.config.deleteAfterDays * SECONDS_PER_DAY,
|
|
80
|
+
});
|
|
81
|
+
exports.defaultRetention = defaultRetention;
|
|
62
82
|
const scheduleRestart = (boss, logger) => {
|
|
63
83
|
setTimeout(async () => {
|
|
64
84
|
try {
|
|
@@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|
|
15
15
|
}) : function(o, v) {
|
|
16
16
|
o["default"] = v;
|
|
17
17
|
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
25
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
37
|
};
|
|
@@ -4,7 +4,7 @@ exports.manageJob = void 0;
|
|
|
4
4
|
const manageJob = async (boss, logger, action, queueName, jobId) => {
|
|
5
5
|
switch (action) {
|
|
6
6
|
case 'retry':
|
|
7
|
-
await boss.
|
|
7
|
+
await boss.retry(queueName, jobId);
|
|
8
8
|
break;
|
|
9
9
|
case 'cancel':
|
|
10
10
|
await boss.cancel(queueName, jobId);
|
package/dist/lib/phone/phone.js
CHANGED
|
@@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|
|
15
15
|
}) : function(o, v) {
|
|
16
16
|
o["default"] = v;
|
|
17
17
|
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
25
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
36
|
exports.normalizePhoneNumber = exports.getPhoneType = exports.getPhoneCountry = exports.parsePhone = exports.formatPhoneNumber = exports.tryParsePhoneNumber = exports.getFormattedPhoneNumber = exports.isValidMobilePhoneNumber = exports.isValidPhoneNumber = exports.OutputFormat = void 0;
|
|
27
37
|
const max_1 = __importStar(require("libphonenumber-js/max"));
|
|
@@ -7,6 +7,7 @@ var Service;
|
|
|
7
7
|
Service["BACKEND_PAY"] = "backend-pay";
|
|
8
8
|
Service["BACKEND_POS"] = "backend-pos";
|
|
9
9
|
Service["BACKEND_PUBSUB"] = "backend-pubsub";
|
|
10
|
+
Service["BACKEND_TSE"] = "backend-tse";
|
|
10
11
|
Service["BACKEND_PYTHON"] = "backend-python";
|
|
11
12
|
Service["BACKEND_LINK"] = "backend-link";
|
|
12
13
|
Service["MASTRA"] = "mastra";
|
|
@@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|
|
15
15
|
}) : function(o, v) {
|
|
16
16
|
o["default"] = v;
|
|
17
17
|
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
25
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
37
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lucaapp/service-utils",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.10.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"files": [
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"test": "vitest run",
|
|
16
16
|
"test:coverage": "vitest run --coverage",
|
|
17
17
|
"test:ci": "VITEST_JUNIT_SUITE_NAME=service-utils CI=true vitest run --coverage",
|
|
18
|
-
"audit": "
|
|
18
|
+
"audit:osv": "yarn-osv-audit"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@apidevtools/swagger-parser": "10.0.3",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"@aws-sdk/lib-storage": "^3.1035.0",
|
|
25
25
|
"@hapi/boom": "^10.0.1",
|
|
26
26
|
"@sentry/node": "^9.10.1",
|
|
27
|
-
"@tsconfig/
|
|
27
|
+
"@tsconfig/node24": "24.0.0",
|
|
28
28
|
"@types/express": "4.17.23",
|
|
29
29
|
"@types/express-serve-static-core": "^4.17.43",
|
|
30
30
|
"@types/node": "22.13.11",
|
|
@@ -74,12 +74,12 @@
|
|
|
74
74
|
"eslint": "8.57.0",
|
|
75
75
|
"eslint-plugin-prettier": "4.2.1",
|
|
76
76
|
"eslint-plugin-vitest": "0.4.1",
|
|
77
|
-
"
|
|
77
|
+
"yarn-osv-audit": "0.1.8",
|
|
78
78
|
"prettier": "2.7.1",
|
|
79
79
|
"semantic-release": "^25.0.3",
|
|
80
80
|
"semantic-release-monorepo": "8.0.2",
|
|
81
|
-
"supertest": "
|
|
82
|
-
"typescript": "
|
|
81
|
+
"supertest": "6.3.4",
|
|
82
|
+
"typescript": "6.0.2",
|
|
83
83
|
"vite-tsconfig-paths": "4.3.2",
|
|
84
84
|
"vitest": "4.1.1"
|
|
85
85
|
},
|