@pgpm/jobs 0.4.0 → 0.5.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/Makefile +1 -1
- package/README.md +368 -1
- package/launchql-jobs.control +1 -1
- package/package.json +6 -6
- package/sql/{launchql-jobs--0.4.6.sql → launchql-jobs--0.4.0.sql} +450 -450
- /package/{launchql.plan → pgpm.plan} +0 -0
package/Makefile
CHANGED
package/README.md
CHANGED
|
@@ -2,4 +2,371 @@
|
|
|
2
2
|
|
|
3
3
|
Core job system for background task processing in PostgreSQL.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`@pgpm/jobs` provides the core abstractions and interfaces for a PostgreSQL-based background job processing system. This package defines the schema, tables, and procedures for job queue management, scheduled jobs, and worker coordination. It serves as the foundation for building reliable background task processing systems entirely within PostgreSQL.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Job Queue Schema**: Core `app_jobs` schema with jobs, scheduled_jobs, and job_queues tables
|
|
12
|
+
- **Job Management Procedures**: Functions for adding, retrieving, completing, and failing jobs
|
|
13
|
+
- **Scheduled Jobs**: Support for cron-style and rule-based job scheduling
|
|
14
|
+
- **Worker Coordination**: Job locking and worker management with expiry
|
|
15
|
+
- **Priority Queue**: Process jobs by priority, run time, and insertion order
|
|
16
|
+
- **Automatic Retries**: Configurable retry attempts with failure tracking
|
|
17
|
+
- **Job Keys**: Upsert semantics for idempotent job creation
|
|
18
|
+
- **Trigger Functions**: Automatic job creation from table changes
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
If you have `pgpm` installed:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pgpm install @pgpm/jobs
|
|
26
|
+
pgpm deploy
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
This is a quick way to get started. The sections below provide more detailed installation options.
|
|
30
|
+
|
|
31
|
+
### Prerequisites
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Install pgpm globally
|
|
35
|
+
npm install -g pgpm
|
|
36
|
+
|
|
37
|
+
# Start PostgreSQL
|
|
38
|
+
pgpm docker start
|
|
39
|
+
|
|
40
|
+
# Set environment variables
|
|
41
|
+
eval "$(pgpm env)"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Deploy
|
|
45
|
+
|
|
46
|
+
#### Option 1: Deploy by installing with pgpm
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pgpm install @pgpm/jobs
|
|
50
|
+
pgpm deploy
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
#### Option 2: Deploy from Package Directory
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
cd packages/jobs/jobs
|
|
57
|
+
pgpm deploy --createdb
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
#### Option 3: Deploy from Workspace Root
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# Install workspace dependencies
|
|
64
|
+
pgpm install
|
|
65
|
+
|
|
66
|
+
# Deploy with dependencies
|
|
67
|
+
pgpm deploy mydb1 --yes --createdb
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Core Schema
|
|
71
|
+
|
|
72
|
+
The `app_jobs` schema provides three main tables:
|
|
73
|
+
|
|
74
|
+
### jobs Table
|
|
75
|
+
Stores active jobs awaiting processing:
|
|
76
|
+
- `id`: Unique job identifier
|
|
77
|
+
- `database_id`: Database/tenant identifier
|
|
78
|
+
- `task_identifier`: Job type/handler name
|
|
79
|
+
- `payload`: JSON data for the job
|
|
80
|
+
- `priority`: Lower numbers = higher priority
|
|
81
|
+
- `run_at`: Scheduled execution time
|
|
82
|
+
- `attempts`: Current attempt count
|
|
83
|
+
- `max_attempts`: Maximum retry attempts
|
|
84
|
+
- `locked_by`: Worker ID holding the lock
|
|
85
|
+
- `locked_at`: Lock timestamp
|
|
86
|
+
- `key`: Optional unique key for upsert
|
|
87
|
+
|
|
88
|
+
### scheduled_jobs Table
|
|
89
|
+
Stores recurring job definitions:
|
|
90
|
+
- `id`: Unique identifier
|
|
91
|
+
- `database_id`: Database/tenant identifier
|
|
92
|
+
- `task_identifier`: Job type/handler name
|
|
93
|
+
- `payload`: JSON data template
|
|
94
|
+
- `schedule_info`: Cron or rule-based schedule
|
|
95
|
+
- `priority`: Job priority
|
|
96
|
+
- `max_attempts`: Maximum retries
|
|
97
|
+
|
|
98
|
+
### job_queues Table
|
|
99
|
+
Tracks queue statistics and locking:
|
|
100
|
+
- `queue_name`: Queue identifier
|
|
101
|
+
- `job_count`: Number of jobs in queue
|
|
102
|
+
- `locked_by`: Worker ID holding queue lock
|
|
103
|
+
- `locked_at`: Lock timestamp
|
|
104
|
+
|
|
105
|
+
## Usage
|
|
106
|
+
|
|
107
|
+
### Adding Jobs
|
|
108
|
+
|
|
109
|
+
```sql
|
|
110
|
+
-- Add a simple job
|
|
111
|
+
SELECT app_jobs.add_job(
|
|
112
|
+
db_id := '5b720132-17d5-424d-9bcb-ee7b17c13d43'::uuid,
|
|
113
|
+
identifier := 'send_email',
|
|
114
|
+
payload := '{"to": "user@example.com"}'::json
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
-- Add a delayed job with priority
|
|
118
|
+
SELECT app_jobs.add_job(
|
|
119
|
+
db_id := '5b720132-17d5-424d-9bcb-ee7b17c13d43'::uuid,
|
|
120
|
+
identifier := 'generate_report',
|
|
121
|
+
payload := '{"report_id": 123}'::json,
|
|
122
|
+
run_at := now() + interval '1 hour',
|
|
123
|
+
priority := 10
|
|
124
|
+
);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Retrieving Jobs
|
|
128
|
+
|
|
129
|
+
```sql
|
|
130
|
+
-- Worker fetches next job
|
|
131
|
+
SELECT * FROM app_jobs.get_job(
|
|
132
|
+
worker_id := 'worker-1',
|
|
133
|
+
task_identifiers := ARRAY['send_email', 'generate_report']
|
|
134
|
+
);
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Completing Jobs
|
|
138
|
+
|
|
139
|
+
```sql
|
|
140
|
+
-- Mark job as successfully completed
|
|
141
|
+
SELECT app_jobs.complete_job(
|
|
142
|
+
worker_id := 'worker-1',
|
|
143
|
+
job_id := 123
|
|
144
|
+
);
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Failing Jobs
|
|
148
|
+
|
|
149
|
+
```sql
|
|
150
|
+
-- Mark job as failed (will retry if attempts remain)
|
|
151
|
+
SELECT app_jobs.fail_job(
|
|
152
|
+
worker_id := 'worker-1',
|
|
153
|
+
job_id := 123,
|
|
154
|
+
error_message := 'Connection timeout'
|
|
155
|
+
);
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Scheduled Jobs
|
|
159
|
+
|
|
160
|
+
```sql
|
|
161
|
+
-- Create a scheduled job
|
|
162
|
+
INSERT INTO app_jobs.scheduled_jobs (
|
|
163
|
+
database_id,
|
|
164
|
+
task_identifier,
|
|
165
|
+
schedule_info
|
|
166
|
+
) VALUES (
|
|
167
|
+
'5b720132-17d5-424d-9bcb-ee7b17c13d43'::uuid,
|
|
168
|
+
'cleanup_task',
|
|
169
|
+
'{"hour": [2], "minute": [0]}'::json
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
-- Execute a scheduled job
|
|
173
|
+
SELECT * FROM app_jobs.run_scheduled_job(1);
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Trigger Functions
|
|
177
|
+
|
|
178
|
+
The package includes trigger functions for automatic job creation:
|
|
179
|
+
|
|
180
|
+
### tg_add_job_with_row_id
|
|
181
|
+
Creates a job when a row is inserted, using the row's ID in the payload.
|
|
182
|
+
|
|
183
|
+
```sql
|
|
184
|
+
CREATE TRIGGER auto_process
|
|
185
|
+
AFTER INSERT ON my_table
|
|
186
|
+
FOR EACH ROW
|
|
187
|
+
EXECUTE FUNCTION app_jobs.tg_add_job_with_row_id(
|
|
188
|
+
'database-uuid',
|
|
189
|
+
'process_record',
|
|
190
|
+
'id'
|
|
191
|
+
);
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### tg_add_job_with_row
|
|
195
|
+
Creates a job with the entire row as JSON payload.
|
|
196
|
+
|
|
197
|
+
```sql
|
|
198
|
+
CREATE TRIGGER auto_process
|
|
199
|
+
AFTER INSERT ON my_table
|
|
200
|
+
FOR EACH ROW
|
|
201
|
+
EXECUTE FUNCTION app_jobs.tg_add_job_with_row(
|
|
202
|
+
'database-uuid',
|
|
203
|
+
'process_record'
|
|
204
|
+
);
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### tg_add_job_with_fields
|
|
208
|
+
Creates a job with specific fields from the row.
|
|
209
|
+
|
|
210
|
+
```sql
|
|
211
|
+
CREATE TRIGGER auto_process
|
|
212
|
+
AFTER INSERT ON my_table
|
|
213
|
+
FOR EACH ROW
|
|
214
|
+
EXECUTE FUNCTION app_jobs.tg_add_job_with_fields(
|
|
215
|
+
'database-uuid',
|
|
216
|
+
'process_record',
|
|
217
|
+
'field1',
|
|
218
|
+
'field2',
|
|
219
|
+
'field3'
|
|
220
|
+
);
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Functions Reference
|
|
224
|
+
|
|
225
|
+
### app_jobs.add_job(...)
|
|
226
|
+
Adds a new job to the queue.
|
|
227
|
+
|
|
228
|
+
**Parameters:**
|
|
229
|
+
- `db_id` (uuid): Database identifier
|
|
230
|
+
- `identifier` (text): Job type
|
|
231
|
+
- `payload` (json): Job data
|
|
232
|
+
- `job_key` (text): Optional unique key
|
|
233
|
+
- `queue_name` (text): Optional queue name
|
|
234
|
+
- `run_at` (timestamptz): Execution time
|
|
235
|
+
- `max_attempts` (integer): Max retries
|
|
236
|
+
- `priority` (integer): Job priority
|
|
237
|
+
|
|
238
|
+
### app_jobs.get_job(...)
|
|
239
|
+
Retrieves and locks the next available job.
|
|
240
|
+
|
|
241
|
+
**Parameters:**
|
|
242
|
+
- `worker_id` (text): Worker identifier
|
|
243
|
+
- `task_identifiers` (text[]): Job types to fetch
|
|
244
|
+
- `job_expiry` (interval): Lock expiry duration
|
|
245
|
+
|
|
246
|
+
### app_jobs.complete_job(...)
|
|
247
|
+
Marks a job as completed.
|
|
248
|
+
|
|
249
|
+
**Parameters:**
|
|
250
|
+
- `worker_id` (text): Worker identifier
|
|
251
|
+
- `job_id` (bigint): Job identifier
|
|
252
|
+
|
|
253
|
+
### app_jobs.fail_job(...)
|
|
254
|
+
Marks a job as failed.
|
|
255
|
+
|
|
256
|
+
**Parameters:**
|
|
257
|
+
- `worker_id` (text): Worker identifier
|
|
258
|
+
- `job_id` (bigint): Job identifier
|
|
259
|
+
- `error_message` (text): Error description
|
|
260
|
+
|
|
261
|
+
### app_jobs.add_scheduled_job(...)
|
|
262
|
+
Creates a scheduled job.
|
|
263
|
+
|
|
264
|
+
**Parameters:**
|
|
265
|
+
- `db_id` (uuid): Database identifier
|
|
266
|
+
- `identifier` (text): Job type
|
|
267
|
+
- `payload` (json): Job data
|
|
268
|
+
- `schedule_info` (json): Schedule configuration
|
|
269
|
+
- Additional optional parameters
|
|
270
|
+
|
|
271
|
+
### app_jobs.run_scheduled_job(...)
|
|
272
|
+
Executes a scheduled job.
|
|
273
|
+
|
|
274
|
+
**Parameters:**
|
|
275
|
+
- `scheduled_job_id` (bigint): Scheduled job identifier
|
|
276
|
+
|
|
277
|
+
## Dependencies
|
|
278
|
+
|
|
279
|
+
- `@pgpm/default-roles`: Role-based access control
|
|
280
|
+
- `@pgpm/verify`: Verification utilities
|
|
281
|
+
|
|
282
|
+
## Testing
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
pnpm test
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Development
|
|
289
|
+
|
|
290
|
+
See the [Development](#development) section below for information on working with this package.
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## Development
|
|
295
|
+
|
|
296
|
+
### **Before You Begin**
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
# 1. Install pgpm
|
|
300
|
+
npm install -g pgpm
|
|
301
|
+
|
|
302
|
+
# 2. Start Postgres (Docker or local)
|
|
303
|
+
pgpm docker start
|
|
304
|
+
|
|
305
|
+
# 3. Load PG* environment variables (PGHOST, PGUSER, ...)
|
|
306
|
+
eval "$(pgpm env)"
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
### **Starting a New Project**
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
# 1. Create a workspace
|
|
315
|
+
pgpm init --workspace
|
|
316
|
+
cd my-app
|
|
317
|
+
|
|
318
|
+
# 2. Create your first module
|
|
319
|
+
pgpm init
|
|
320
|
+
|
|
321
|
+
# 3. Add a migration
|
|
322
|
+
pgpm add some_change
|
|
323
|
+
|
|
324
|
+
# 4. Deploy (auto-creates database)
|
|
325
|
+
pgpm deploy --createdb
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
### **Working With an Existing Project**
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
# 1. Clone and enter the project
|
|
334
|
+
git clone <repo> && cd <project>
|
|
335
|
+
|
|
336
|
+
# 2. Install dependencies
|
|
337
|
+
pnpm install
|
|
338
|
+
|
|
339
|
+
# 3. Deploy locally
|
|
340
|
+
pgpm deploy --createdb
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
### **Testing a Module Inside a Workspace**
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
# 1. Install workspace deps
|
|
349
|
+
pnpm install
|
|
350
|
+
|
|
351
|
+
# 2. Enter the module directory
|
|
352
|
+
cd packages/<some-module>
|
|
353
|
+
|
|
354
|
+
# 3. Run tests in watch mode
|
|
355
|
+
pnpm test:watch
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## Related Tooling
|
|
359
|
+
|
|
360
|
+
* [pgpm](https://github.com/launchql/launchql/tree/main/packages/pgpm): **🖥️ PostgreSQL Package Manager** for modular Postgres development. Works with database workspaces, scaffolding, migrations, seeding, and installing database packages.
|
|
361
|
+
* [pgsql-test](https://github.com/launchql/launchql/tree/main/packages/pgsql-test): **📊 Isolated testing environments** with per-test transaction rollbacks—ideal for integration tests, complex migrations, and RLS simulation.
|
|
362
|
+
* [supabase-test](https://github.com/launchql/launchql/tree/main/packages/supabase-test): **🧪 Supabase-native test harness** preconfigured for the local Supabase stack—per-test rollbacks, JWT/role context helpers, and CI/GitHub Actions ready.
|
|
363
|
+
* [graphile-test](https://github.com/launchql/launchql/tree/main/packages/graphile-test): **🔐 Authentication mocking** for Graphile-focused test helpers and emulating row-level security contexts.
|
|
364
|
+
* [pgsql-parser](https://github.com/launchql/pgsql-parser): **🔄 SQL conversion engine** that interprets and converts PostgreSQL syntax.
|
|
365
|
+
* [libpg-query-node](https://github.com/launchql/libpg-query-node): **🌉 Node.js bindings** for `libpg_query`, converting SQL into parse trees.
|
|
366
|
+
* [pg-proto-parser](https://github.com/launchql/pg-proto-parser): **📦 Protobuf parser** for parsing PostgreSQL Protocol Buffers definitions to generate TypeScript interfaces, utility functions, and JSON mappings for enums.
|
|
367
|
+
|
|
368
|
+
## Disclaimer
|
|
369
|
+
|
|
370
|
+
AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED "AS IS", AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND.
|
|
371
|
+
|
|
372
|
+
No developer or entity involved in creating this software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the code, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value.
|
package/launchql-jobs.control
CHANGED
package/package.json
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pgpm/jobs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Core job system for background task processing in PostgreSQL",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
|
-
"bundle": "
|
|
9
|
+
"bundle": "pgpm package",
|
|
10
10
|
"test": "jest",
|
|
11
11
|
"test:watch": "jest --watch"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@pgpm/default-roles": "0.
|
|
15
|
-
"@pgpm/verify": "0.
|
|
14
|
+
"@pgpm/default-roles": "0.5.0",
|
|
15
|
+
"@pgpm/verify": "0.5.0"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
|
-
"
|
|
18
|
+
"pgpm": "^0.2.0"
|
|
19
19
|
},
|
|
20
20
|
"repository": {
|
|
21
21
|
"type": "git",
|
|
@@ -25,5 +25,5 @@
|
|
|
25
25
|
"bugs": {
|
|
26
26
|
"url": "https://github.com/launchql/extensions/issues"
|
|
27
27
|
},
|
|
28
|
-
"gitHead": "
|
|
28
|
+
"gitHead": "d8eedbb24ad22a106634bc3b919bfb8d41976c16"
|
|
29
29
|
}
|