@agendajs/postgres-backend 1.0.0-alpha.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/LICENSE.md +25 -0
- package/README.md +215 -0
- package/dist/PostgresBackend.d.ts +75 -0
- package/dist/PostgresBackend.js +123 -0
- package/dist/PostgresBackend.js.map +1 -0
- package/dist/PostgresJobRepository.d.ts +53 -0
- package/dist/PostgresJobRepository.js +559 -0
- package/dist/PostgresJobRepository.js.map +1 -0
- package/dist/PostgresNotificationChannel.d.ts +41 -0
- package/dist/PostgresNotificationChannel.js +170 -0
- package/dist/PostgresNotificationChannel.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/schema.d.ts +10 -0
- package/dist/schema.js +72 -0
- package/dist/schema.js.map +1 -0
- package/dist/types.d.ts +52 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +71 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import debug from 'debug';
|
|
2
|
+
import { Pool } from 'pg';
|
|
3
|
+
import { BaseNotificationChannel, toJobId } from 'agenda';
|
|
4
|
+
const log = debug('agenda:postgres:notification');
|
|
5
|
+
/**
|
|
6
|
+
* PostgreSQL notification channel using LISTEN/NOTIFY
|
|
7
|
+
*
|
|
8
|
+
* This implementation uses PostgreSQL's built-in pub/sub mechanism
|
|
9
|
+
* for real-time job notifications across multiple processes.
|
|
10
|
+
*/
|
|
11
|
+
export class PostgresNotificationChannel extends BaseNotificationChannel {
|
|
12
|
+
pool;
|
|
13
|
+
ownPool = false;
|
|
14
|
+
connectionString;
|
|
15
|
+
listenClient;
|
|
16
|
+
constructor(config = {}) {
|
|
17
|
+
super(config);
|
|
18
|
+
if (config.pool) {
|
|
19
|
+
this.pool = config.pool;
|
|
20
|
+
this.ownPool = false;
|
|
21
|
+
}
|
|
22
|
+
else if (config.connectionString) {
|
|
23
|
+
this.connectionString = config.connectionString;
|
|
24
|
+
this.ownPool = true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Set the pool (used when created by PostgresBackend)
|
|
29
|
+
*/
|
|
30
|
+
setPool(pool) {
|
|
31
|
+
if (this.pool && this.ownPool) {
|
|
32
|
+
throw new Error('Cannot set pool on channel with own pool');
|
|
33
|
+
}
|
|
34
|
+
this.pool = pool;
|
|
35
|
+
this.ownPool = false;
|
|
36
|
+
}
|
|
37
|
+
async connect() {
|
|
38
|
+
log('connecting notification channel');
|
|
39
|
+
this.clearReconnect();
|
|
40
|
+
try {
|
|
41
|
+
// Create own pool if needed
|
|
42
|
+
if (!this.pool && this.connectionString) {
|
|
43
|
+
this.pool = new Pool({ connectionString: this.connectionString });
|
|
44
|
+
this.ownPool = true;
|
|
45
|
+
}
|
|
46
|
+
if (!this.pool) {
|
|
47
|
+
throw new Error('PostgresNotificationChannel requires pool or connectionString');
|
|
48
|
+
}
|
|
49
|
+
// Get a dedicated connection for LISTEN
|
|
50
|
+
this.listenClient = await this.pool.connect();
|
|
51
|
+
// Set up notification handler
|
|
52
|
+
this.listenClient.on('notification', msg => {
|
|
53
|
+
if (msg.channel === this.config.channelName && msg.payload) {
|
|
54
|
+
try {
|
|
55
|
+
const notification = this.parseNotification(msg.payload);
|
|
56
|
+
log('received notification: %O', notification);
|
|
57
|
+
this.notifyHandlers(notification).catch(err => {
|
|
58
|
+
this.emit('error', err);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
log('error parsing notification: %O', error);
|
|
63
|
+
this.emit('error', error);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
// Handle connection errors
|
|
68
|
+
this.listenClient.on('error', error => {
|
|
69
|
+
log('listen client error: %O', error);
|
|
70
|
+
this.emit('error', error);
|
|
71
|
+
// Try to reconnect if the connection was lost
|
|
72
|
+
if (this._state === 'connected') {
|
|
73
|
+
this.handleConnectionLoss();
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
// Start listening
|
|
77
|
+
await this.listenClient.query(`LISTEN "${this.config.channelName}"`);
|
|
78
|
+
log('listening on channel: %s', this.config.channelName);
|
|
79
|
+
this.setState('connected');
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
log('connection failed: %O', error);
|
|
83
|
+
this.emit('error', error);
|
|
84
|
+
this.scheduleReconnect();
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
handleConnectionLoss() {
|
|
89
|
+
log('handling connection loss');
|
|
90
|
+
// Clean up the listen client
|
|
91
|
+
if (this.listenClient) {
|
|
92
|
+
try {
|
|
93
|
+
this.listenClient.release(true); // Destroy the client
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// Ignore release errors
|
|
97
|
+
}
|
|
98
|
+
this.listenClient = undefined;
|
|
99
|
+
}
|
|
100
|
+
this.scheduleReconnect();
|
|
101
|
+
}
|
|
102
|
+
async disconnect() {
|
|
103
|
+
log('disconnecting notification channel');
|
|
104
|
+
this.clearReconnect();
|
|
105
|
+
if (this.listenClient) {
|
|
106
|
+
try {
|
|
107
|
+
await this.listenClient.query(`UNLISTEN "${this.config.channelName}"`);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// Ignore UNLISTEN errors (connection might be dead)
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
this.listenClient.release();
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// Ignore release errors
|
|
117
|
+
}
|
|
118
|
+
this.listenClient = undefined;
|
|
119
|
+
}
|
|
120
|
+
// Only close pool if we created it
|
|
121
|
+
if (this.ownPool && this.pool) {
|
|
122
|
+
await this.pool.end();
|
|
123
|
+
this.pool = undefined;
|
|
124
|
+
}
|
|
125
|
+
this.handlers.clear();
|
|
126
|
+
this.setState('disconnected');
|
|
127
|
+
}
|
|
128
|
+
async publish(notification) {
|
|
129
|
+
if (this._state !== 'connected') {
|
|
130
|
+
throw new Error('Cannot publish: channel not connected');
|
|
131
|
+
}
|
|
132
|
+
if (!this.pool) {
|
|
133
|
+
throw new Error('Cannot publish: no pool available');
|
|
134
|
+
}
|
|
135
|
+
const payload = this.serializeNotification(notification);
|
|
136
|
+
log('publishing notification: %s', payload);
|
|
137
|
+
// NOTIFY doesn't support parameterized queries, so we must escape the payload
|
|
138
|
+
// PostgreSQL escapes single quotes by doubling them
|
|
139
|
+
const escapedPayload = payload.replace(/'/g, "''");
|
|
140
|
+
await this.pool.query(`NOTIFY "${this.config.channelName}", '${escapedPayload}'`);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Serialize notification to JSON string for NOTIFY payload
|
|
144
|
+
*/
|
|
145
|
+
serializeNotification(notification) {
|
|
146
|
+
return JSON.stringify({
|
|
147
|
+
jobId: notification.jobId,
|
|
148
|
+
jobName: notification.jobName,
|
|
149
|
+
nextRunAt: notification.nextRunAt?.toISOString() || null,
|
|
150
|
+
priority: notification.priority,
|
|
151
|
+
timestamp: notification.timestamp.toISOString(),
|
|
152
|
+
source: notification.source
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Parse notification from JSON string received via LISTEN
|
|
157
|
+
*/
|
|
158
|
+
parseNotification(payload) {
|
|
159
|
+
const data = JSON.parse(payload);
|
|
160
|
+
return {
|
|
161
|
+
jobId: toJobId(data.jobId),
|
|
162
|
+
jobName: data.jobName,
|
|
163
|
+
nextRunAt: data.nextRunAt ? new Date(data.nextRunAt) : null,
|
|
164
|
+
priority: data.priority,
|
|
165
|
+
timestamp: new Date(data.timestamp),
|
|
166
|
+
source: data.source
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=PostgresNotificationChannel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PostgresNotificationChannel.js","sourceRoot":"","sources":["../src/PostgresNotificationChannel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,IAAI,EAAc,MAAM,IAAI,CAAC;AAEtC,OAAO,EAAE,uBAAuB,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAE1D,MAAM,GAAG,GAAG,KAAK,CAAC,8BAA8B,CAAC,CAAC;AAYlD;;;;;GAKG;AACH,MAAM,OAAO,2BAA4B,SAAQ,uBAAuB;IAC/D,IAAI,CAAQ;IACZ,OAAO,GAAY,KAAK,CAAC;IACzB,gBAAgB,CAAU;IAC1B,YAAY,CAAc;IAElC,YAAY,SAA4C,EAAE;QACzD,KAAK,CAAC,MAAM,CAAC,CAAC;QAEd,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;YACjB,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;YACxB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACtB,CAAC;aAAM,IAAI,MAAM,CAAC,gBAAgB,EAAE,CAAC;YACpC,IAAI,CAAC,gBAAgB,GAAG,MAAM,CAAC,gBAAgB,CAAC;YAChD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACrB,CAAC;IACF,CAAC;IAED;;OAEG;IACH,OAAO,CAAC,IAAU;QACjB,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;QAC7D,CAAC;QACD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,OAAO;QACZ,GAAG,CAAC,iCAAiC,CAAC,CAAC;QACvC,IAAI,CAAC,cAAc,EAAE,CAAC;QAEtB,IAAI,CAAC;YACJ,4BAA4B;YAC5B,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBACzC,IAAI,CAAC,IAAI,GAAG,IAAI,IAAI,CAAC,EAAE,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC;gBAClE,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACrB,CAAC;YAED,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;gBAChB,MAAM,IAAI,KAAK,CAAC,+DAA+D,CAAC,CAAC;YAClF,CAAC;YAED,wCAAwC;YACxC,IAAI,CAAC,YAAY,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAE9C,8BAA8B;YAC9B,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,cAAc,EAAE,GAAG,CAAC,EAAE;gBAC1C,IAAI,GAAG,CAAC,OAAO,KAAK,IAAI,CAAC,MAAM,CAAC,WAAW,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;oBAC5D,IAAI,CAAC;wBACJ,MAAM,YAAY,GAAG,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;wBACzD,GAAG,CAAC,2BAA2B,EAAE,YAAY,CAAC,CAAC;wBAC/C,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;4BAC7C,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;wBACzB,CAAC,CAAC,CAAC;oBACJ,CAAC;oBAAC,OAAO,KAAK,EAAE,CAAC;wBAChB,GAAG,CAAC,gCAAgC,EAAE,KAAK,CAAC,CAAC;wBAC7C,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,KAAc,CAAC,CAAC;oBACpC,CAAC;gBACF,CAAC;YACF,CAAC,CAAC,CAAC;YAEH,2BAA2B;YAC3B,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE;gBACrC,GAAG,CAAC,yBAAyB,EAAE,KAAK,CAAC,CAAC;gBACtC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;gBAE1B,8CAA8C;gBAC9C,IAAI,IAAI,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;oBACjC,IAAI,CAAC,oBAAoB,EAAE,CAAC;gBAC7B,CAAC;YACF,CAAC,CAAC,CAAC;YAEH,kBAAkB;YAClB,MAAM,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,WAAW,IAAI,CAAC,MAAM,CAAC,WAAW,GAAG,CAAC,CAAC;YACrE,GAAG,CAAC,0BAA0B,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;YAEzD,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QAC5B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,GAAG,CAAC,uBAAuB,EAAE,KAAK,CAAC,CAAC;YACpC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,KAAc,CAAC,CAAC;YACnC,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACzB,MAAM,KAAK,CAAC;QACb,CAAC;IACF,CAAC;IAEO,oBAAoB;QAC3B,GAAG,CAAC,0BAA0B,CAAC,CAAC;QAEhC,6BAA6B;QAC7B,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACvB,IAAI,CAAC;gBACJ,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,qBAAqB;YACvD,CAAC;YAAC,MAAM,CAAC;gBACR,wBAAwB;YACzB,CAAC;YACD,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC;QAC/B,CAAC;QAED,IAAI,CAAC,iBAAiB,EAAE,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,UAAU;QACf,GAAG,CAAC,oCAAoC,CAAC,CAAC;QAC1C,IAAI,CAAC,cAAc,EAAE,CAAC;QAEtB,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACvB,IAAI,CAAC;gBACJ,MAAM,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,aAAa,IAAI,CAAC,MAAM,CAAC,WAAW,GAAG,CAAC,CAAC;YACxE,CAAC;YAAC,MAAM,CAAC;gBACR,oDAAoD;YACrD,CAAC;YAED,IAAI,CAAC;gBACJ,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC;YAC7B,CAAC;YAAC,MAAM,CAAC;gBACR,wBAAwB;YACzB,CAAC;YACD,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC;QAC/B,CAAC;QAED,mCAAmC;QACnC,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YAC/B,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;YACtB,IAAI,CAAC,IAAI,GAAG,SAAS,CAAC;QACvB,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,YAA6B;QAC1C,IAAI,IAAI,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;YACjC,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;QAC1D,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;QACtD,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,qBAAqB,CAAC,YAAY,CAAC,CAAC;QACzD,GAAG,CAAC,6BAA6B,EAAE,OAAO,CAAC,CAAC;QAE5C,8EAA8E;QAC9E,oDAAoD;QACpD,MAAM,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACnD,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,IAAI,CAAC,MAAM,CAAC,WAAW,OAAO,cAAc,GAAG,CAAC,CAAC;IACnF,CAAC;IAED;;OAEG;IACK,qBAAqB,CAAC,YAA6B;QAC1D,OAAO,IAAI,CAAC,SAAS,CAAC;YACrB,KAAK,EAAE,YAAY,CAAC,KAAK;YACzB,OAAO,EAAE,YAAY,CAAC,OAAO;YAC7B,SAAS,EAAE,YAAY,CAAC,SAAS,EAAE,WAAW,EAAE,IAAI,IAAI;YACxD,QAAQ,EAAE,YAAY,CAAC,QAAQ;YAC/B,SAAS,EAAE,YAAY,CAAC,SAAS,CAAC,WAAW,EAAE;YAC/C,MAAM,EAAE,YAAY,CAAC,MAAM;SAC3B,CAAC,CAAC;IACJ,CAAC;IAED;;OAEG;IACK,iBAAiB,CAAC,OAAe;QACxC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAEjC,OAAO;YACN,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAU;YACnC,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI;YAC3D,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC;YACnC,MAAM,EAAE,IAAI,CAAC,MAAM;SACnB,CAAC;IACH,CAAC;CACD"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @agendajs/postgres-backend
|
|
3
|
+
*
|
|
4
|
+
* PostgreSQL backend for Agenda job scheduler with LISTEN/NOTIFY support.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { Agenda } from 'agenda';
|
|
9
|
+
* import { PostgresBackend } from '@agendajs/postgres-backend';
|
|
10
|
+
*
|
|
11
|
+
* // Create agenda with PostgreSQL backend
|
|
12
|
+
* const agenda = new Agenda({
|
|
13
|
+
* backend: new PostgresBackend({
|
|
14
|
+
* connectionString: 'postgresql://user:pass@localhost:5432/mydb'
|
|
15
|
+
* })
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* // Define and run jobs
|
|
19
|
+
* agenda.define('myJob', async (job) => {
|
|
20
|
+
* console.log('Running job:', job.attrs.name);
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* await agenda.start();
|
|
24
|
+
* await agenda.every('5 minutes', 'myJob');
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export { PostgresBackend } from './PostgresBackend.js';
|
|
28
|
+
export { PostgresJobRepository } from './PostgresJobRepository.js';
|
|
29
|
+
export { PostgresNotificationChannel } from './PostgresNotificationChannel.js';
|
|
30
|
+
export type { PostgresBackendConfig, PostgresJobRow } from './types.js';
|
|
31
|
+
export type { PostgresNotificationChannelConfig } from './PostgresNotificationChannel.js';
|
|
32
|
+
export { getCreateTableSQL, getCreateIndexesSQL, getDropTableSQL, getUpdateTimestampTriggerSQL } from './schema.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @agendajs/postgres-backend
|
|
3
|
+
*
|
|
4
|
+
* PostgreSQL backend for Agenda job scheduler with LISTEN/NOTIFY support.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { Agenda } from 'agenda';
|
|
9
|
+
* import { PostgresBackend } from '@agendajs/postgres-backend';
|
|
10
|
+
*
|
|
11
|
+
* // Create agenda with PostgreSQL backend
|
|
12
|
+
* const agenda = new Agenda({
|
|
13
|
+
* backend: new PostgresBackend({
|
|
14
|
+
* connectionString: 'postgresql://user:pass@localhost:5432/mydb'
|
|
15
|
+
* })
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* // Define and run jobs
|
|
19
|
+
* agenda.define('myJob', async (job) => {
|
|
20
|
+
* console.log('Running job:', job.attrs.name);
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* await agenda.start();
|
|
24
|
+
* await agenda.every('5 minutes', 'myJob');
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export { PostgresBackend } from './PostgresBackend.js';
|
|
28
|
+
export { PostgresJobRepository } from './PostgresJobRepository.js';
|
|
29
|
+
export { PostgresNotificationChannel } from './PostgresNotificationChannel.js';
|
|
30
|
+
// Re-export schema utilities for advanced use cases
|
|
31
|
+
export { getCreateTableSQL, getCreateIndexesSQL, getDropTableSQL, getUpdateTimestampTriggerSQL } from './schema.js';
|
|
32
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAC;AAK/E,oDAAoD;AACpD,OAAO,EACN,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,4BAA4B,EAC5B,MAAM,aAAa,CAAC"}
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQL schema for PostgreSQL Agenda jobs table
|
|
3
|
+
*/
|
|
4
|
+
export declare function getCreateTableSQL(tableName: string): string;
|
|
5
|
+
export declare function getCreateIndexesSQL(tableName: string): string[];
|
|
6
|
+
export declare function getDropTableSQL(tableName: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Function and trigger for automatic updated_at timestamp
|
|
9
|
+
*/
|
|
10
|
+
export declare function getUpdateTimestampTriggerSQL(tableName: string): string;
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQL schema for PostgreSQL Agenda jobs table
|
|
3
|
+
*/
|
|
4
|
+
export function getCreateTableSQL(tableName) {
|
|
5
|
+
return `
|
|
6
|
+
CREATE TABLE IF NOT EXISTS "${tableName}" (
|
|
7
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
8
|
+
name VARCHAR(255) NOT NULL,
|
|
9
|
+
priority INTEGER NOT NULL DEFAULT 0,
|
|
10
|
+
next_run_at TIMESTAMPTZ,
|
|
11
|
+
type VARCHAR(10) NOT NULL DEFAULT 'normal' CHECK (type IN ('normal', 'single')),
|
|
12
|
+
locked_at TIMESTAMPTZ,
|
|
13
|
+
last_finished_at TIMESTAMPTZ,
|
|
14
|
+
failed_at TIMESTAMPTZ,
|
|
15
|
+
fail_count INTEGER DEFAULT NULL,
|
|
16
|
+
fail_reason TEXT,
|
|
17
|
+
repeat_timezone VARCHAR(100),
|
|
18
|
+
last_run_at TIMESTAMPTZ,
|
|
19
|
+
repeat_interval VARCHAR(255),
|
|
20
|
+
data JSONB DEFAULT '{}'::jsonb,
|
|
21
|
+
repeat_at VARCHAR(255),
|
|
22
|
+
disabled BOOLEAN DEFAULT FALSE,
|
|
23
|
+
progress REAL,
|
|
24
|
+
fork BOOLEAN DEFAULT FALSE,
|
|
25
|
+
last_modified_by VARCHAR(255),
|
|
26
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
27
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
28
|
+
);
|
|
29
|
+
`;
|
|
30
|
+
}
|
|
31
|
+
export function getCreateIndexesSQL(tableName) {
|
|
32
|
+
return [
|
|
33
|
+
// Main index for finding and locking next job
|
|
34
|
+
`CREATE INDEX IF NOT EXISTS "${tableName}_find_and_lock_idx"
|
|
35
|
+
ON "${tableName}" (name, next_run_at, priority DESC, locked_at, disabled)
|
|
36
|
+
WHERE disabled = FALSE`,
|
|
37
|
+
// Index for single jobs (upsert operations)
|
|
38
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS "${tableName}_single_job_idx"
|
|
39
|
+
ON "${tableName}" (name) WHERE type = 'single'`,
|
|
40
|
+
// Index for querying by locked_at (for stale lock detection)
|
|
41
|
+
`CREATE INDEX IF NOT EXISTS "${tableName}_locked_at_idx"
|
|
42
|
+
ON "${tableName}" (locked_at) WHERE locked_at IS NOT NULL`,
|
|
43
|
+
// Index for next_run_at queries (queue size, scheduled jobs)
|
|
44
|
+
`CREATE INDEX IF NOT EXISTS "${tableName}_next_run_at_idx"
|
|
45
|
+
ON "${tableName}" (next_run_at) WHERE next_run_at IS NOT NULL`
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
export function getDropTableSQL(tableName) {
|
|
49
|
+
return `DROP TABLE IF EXISTS "${tableName}" CASCADE;`;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Function and trigger for automatic updated_at timestamp
|
|
53
|
+
*/
|
|
54
|
+
export function getUpdateTimestampTriggerSQL(tableName) {
|
|
55
|
+
return `
|
|
56
|
+
CREATE OR REPLACE FUNCTION update_${tableName}_updated_at()
|
|
57
|
+
RETURNS TRIGGER AS $$
|
|
58
|
+
BEGIN
|
|
59
|
+
NEW.updated_at = NOW();
|
|
60
|
+
RETURN NEW;
|
|
61
|
+
END;
|
|
62
|
+
$$ LANGUAGE plpgsql;
|
|
63
|
+
|
|
64
|
+
DROP TRIGGER IF EXISTS "${tableName}_updated_at_trigger" ON "${tableName}";
|
|
65
|
+
|
|
66
|
+
CREATE TRIGGER "${tableName}_updated_at_trigger"
|
|
67
|
+
BEFORE UPDATE ON "${tableName}"
|
|
68
|
+
FOR EACH ROW
|
|
69
|
+
EXECUTE FUNCTION update_${tableName}_updated_at();
|
|
70
|
+
`;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,UAAU,iBAAiB,CAAC,SAAiB;IAClD,OAAO;gCACwB,SAAS;;;;;;;;;;;;;;;;;;;;;;;EAuBvC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,SAAiB;IACpD,OAAO;QACN,8CAA8C;QAC9C,+BAA+B,SAAS;SACjC,SAAS;0BACQ;QAExB,4CAA4C;QAC5C,sCAAsC,SAAS;SACxC,SAAS,gCAAgC;QAEhD,6DAA6D;QAC7D,+BAA+B,SAAS;SACjC,SAAS,2CAA2C;QAE3D,6DAA6D;QAC7D,+BAA+B,SAAS;SACjC,SAAS,+CAA+C;KAC/D,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,SAAiB;IAChD,OAAO,yBAAyB,SAAS,YAAY,CAAC;AACvD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,4BAA4B,CAAC,SAAiB;IAC7D,OAAO;sCAC8B,SAAS;;;;;;;;4BAQnB,SAAS,4BAA4B,SAAS;;oBAEtD,SAAS;sBACP,SAAS;;4BAEH,SAAS;EACnC,CAAC;AACH,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Pool, PoolConfig } from 'pg';
|
|
2
|
+
import type { SortDirection } from 'agenda';
|
|
3
|
+
/**
|
|
4
|
+
* Configuration options for PostgresBackend
|
|
5
|
+
*/
|
|
6
|
+
export interface PostgresBackendConfig {
|
|
7
|
+
/** PostgreSQL connection string (e.g., 'postgresql://user:pass@host:5432/db') */
|
|
8
|
+
connectionString?: string;
|
|
9
|
+
/** PostgreSQL pool configuration (creates a new pool) */
|
|
10
|
+
poolConfig?: PoolConfig;
|
|
11
|
+
/** Existing PostgreSQL pool instance (will not be closed on disconnect) */
|
|
12
|
+
pool?: Pool;
|
|
13
|
+
/** Table name for jobs (default: 'agenda_jobs') */
|
|
14
|
+
tableName?: string;
|
|
15
|
+
/** Channel name for LISTEN/NOTIFY (default: 'agenda_jobs') */
|
|
16
|
+
channelName?: string;
|
|
17
|
+
/** Whether to create the table and indexes on connect (default: true) */
|
|
18
|
+
ensureSchema?: boolean;
|
|
19
|
+
/** Sort order for job queries (default: { nextRunAt: 'asc', priority: 'desc' }) */
|
|
20
|
+
sort?: {
|
|
21
|
+
nextRunAt?: SortDirection;
|
|
22
|
+
priority?: SortDirection;
|
|
23
|
+
};
|
|
24
|
+
/** Disable LISTEN/NOTIFY notification channel (default: false) */
|
|
25
|
+
disableNotifications?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Internal row type matching PostgreSQL column names
|
|
29
|
+
*/
|
|
30
|
+
export interface PostgresJobRow {
|
|
31
|
+
id: string;
|
|
32
|
+
name: string;
|
|
33
|
+
priority: number;
|
|
34
|
+
next_run_at: Date | null;
|
|
35
|
+
type: 'normal' | 'single';
|
|
36
|
+
locked_at: Date | null;
|
|
37
|
+
last_finished_at: Date | null;
|
|
38
|
+
failed_at: Date | null;
|
|
39
|
+
fail_count: number | null;
|
|
40
|
+
fail_reason: string | null;
|
|
41
|
+
repeat_timezone: string | null;
|
|
42
|
+
last_run_at: Date | null;
|
|
43
|
+
repeat_interval: string | null;
|
|
44
|
+
data: unknown;
|
|
45
|
+
repeat_at: string | null;
|
|
46
|
+
disabled: boolean;
|
|
47
|
+
progress: number | null;
|
|
48
|
+
fork: boolean;
|
|
49
|
+
last_modified_by: string | null;
|
|
50
|
+
created_at: Date;
|
|
51
|
+
updated_at: Date;
|
|
52
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agendajs/postgres-backend",
|
|
3
|
+
"version": "1.0.0-alpha.0",
|
|
4
|
+
"description": "PostgreSQL backend for Agenda job scheduler with LISTEN/NOTIFY support",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18.0.0"
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git://github.com/agenda/agenda"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"agenda",
|
|
29
|
+
"job",
|
|
30
|
+
"scheduler",
|
|
31
|
+
"postgresql",
|
|
32
|
+
"postgres",
|
|
33
|
+
"pg",
|
|
34
|
+
"listen",
|
|
35
|
+
"notify"
|
|
36
|
+
],
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/agenda/agenda/issues"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"debug": "^4.4.0",
|
|
43
|
+
"pg": "^8.13.0"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"agenda": "6.0.0-alpha.0"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/debug": "^4.1.12",
|
|
50
|
+
"@types/node": "^22.0.0",
|
|
51
|
+
"@types/pg": "^8.11.0",
|
|
52
|
+
"@vitest/coverage-v8": "^3.1.0",
|
|
53
|
+
"tsx": "^4.21.0",
|
|
54
|
+
"vitest": "^3.1.0",
|
|
55
|
+
"agenda": "6.0.0-alpha.0"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "tsc -b",
|
|
59
|
+
"typecheck": "tsc -b && tsc -p tsconfig.test.json",
|
|
60
|
+
"test": "vitest run",
|
|
61
|
+
"test:watch": "vitest",
|
|
62
|
+
"test:coverage": "vitest run --coverage",
|
|
63
|
+
"test:debug": "DEBUG=agenda:**,-agenda:internal:** vitest run",
|
|
64
|
+
"test:postgres": "POSTGRES_TEST_URL=postgresql://agenda:agenda@localhost:5432/agenda_test vitest run",
|
|
65
|
+
"test:postgres:watch": "POSTGRES_TEST_URL=postgresql://agenda:agenda@localhost:5432/agenda_test vitest",
|
|
66
|
+
"docker:up": "docker compose up -d --wait",
|
|
67
|
+
"docker:down": "docker compose down",
|
|
68
|
+
"docker:logs": "docker compose logs -f",
|
|
69
|
+
"test:docker": "npm run docker:up && npm run test:postgres; npm run docker:down"
|
|
70
|
+
}
|
|
71
|
+
}
|