@pg-boss/proxy 0.1.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/README.md ADDED
@@ -0,0 +1,419 @@
1
+ # @pg-boss/proxy
2
+
3
+ A HTTP proxy for pg-boss methods, to support use cases such as platform compatibility and connection pooling or scalability.
4
+
5
+ All background processing is disabled by default (the opposite of how pg-boss normally works). A pg-boss instance is started via `start()`, which opens the database connection.
6
+
7
+ ## Quick Start
8
+
9
+ **As a library** (import into your own Node app):
10
+
11
+ ```ts
12
+ import { createProxyServerNode } from '@pg-boss/proxy/node'
13
+
14
+ const proxy = createProxyServerNode()
15
+ await proxy.start()
16
+ // Reads DATABASE_URL from process.env, listens on PORT (default 3000)
17
+ ```
18
+
19
+ **From source** (clone the repo and run the built-in dev server):
20
+
21
+ ```bash
22
+ DATABASE_URL=postgres://user:pass@host/database npm run dev
23
+ ```
24
+
25
+ Then visit:
26
+ - http://localhost:3000 - Proxy home page with links to all endpoints
27
+ - http://localhost:3000/docs - Interactive Swagger documentation
28
+ - http://localhost:3000/openapi.json - OpenAPI spec
29
+
30
+
31
+ ## API Usage Examples
32
+
33
+ Once the proxy is running, you can interact with it using any HTTP client:
34
+
35
+ ```bash
36
+ # Send a job to a queue
37
+ curl -X POST http://localhost:3000/api/send \
38
+ -H "Content-Type: application/json" \
39
+ -d '{"name": "my-queue", "data": {"key": "value"}}'
40
+
41
+ # Fetch jobs from a queue
42
+ curl -X POST http://localhost:3000/api/fetch \
43
+ -H "Content-Type: application/json" \
44
+ -d '{"name": "my-queue"}'
45
+
46
+ # Get queue information
47
+ curl "http://localhost:3000/api/getQueue?name=my-queue"
48
+
49
+ # Get all queues
50
+ curl "http://localhost:3000/api/getQueues"
51
+ ```
52
+
53
+ ## Response Format
54
+
55
+ All endpoints return a consistent JSON envelope:
56
+
57
+ ```json
58
+ // Success
59
+ { "ok": true, "result": <value | null> }
60
+
61
+ // Error
62
+ { "ok": false, "error": { "message": "..." } }
63
+ ```
64
+
65
+ The `result` field contains the direct return value of the underlying pg-boss method. HTTP status codes used: `200` for success, `400` for invalid input, `413` for body too large, and `500` for server errors.
66
+
67
+ ## Entry Points
68
+
69
+ This package ships a runtime-neutral entry point and a Node-only entry point.
70
+
71
+ ### Runtime-neutral (default)
72
+
73
+ Use this when you want a runtime-neutral entry point:
74
+
75
+ ```ts
76
+ import { createProxyService } from '@pg-boss/proxy'
77
+
78
+ const { app, start, stop } = createProxyService({
79
+ options: {
80
+ connectionString: 'postgres://user:pass@host/database'
81
+ }
82
+ })
83
+
84
+ await start()
85
+ // later
86
+ await stop()
87
+ ```
88
+
89
+ If you only need the Hono app and will manage lifecycle yourself:
90
+
91
+ ```ts
92
+ import { createProxyApp } from '@pg-boss/proxy'
93
+
94
+ const { app, boss } = createProxyApp({
95
+ options: {
96
+ connectionString: 'postgres://user:pass@host/database'
97
+ }
98
+ })
99
+
100
+ await boss.start()
101
+ // Use with any Hono-compatible server
102
+ serve({ fetch: app.fetch, port: 3000 })
103
+ ```
104
+
105
+ ### Node Convenience Entry Point
106
+
107
+ ```ts
108
+ import { createProxyServiceNode } from '@pg-boss/proxy/node'
109
+
110
+ const { app, start, stop } = createProxyServiceNode()
111
+
112
+ await start()
113
+ // later
114
+ await stop()
115
+ ```
116
+
117
+ If you want a ready-to-listen Node server with automatic shutdown signal wiring:
118
+
119
+ ```ts
120
+ import { createProxyServerNode } from '@pg-boss/proxy/node'
121
+
122
+ const proxy = createProxyServerNode()
123
+ await proxy.start()
124
+ ```
125
+
126
+ `createProxyServerNode` accepts all `ProxyOptions` plus the following Node-specific options:
127
+
128
+ | Option | Type | Default | Description |
129
+ |---|---|---|---|
130
+ | `port` | `number` | `PORT` env or `3000` | Port to listen on |
131
+ | `hostname` | `string` | `HOST` env or `localhost` | Hostname to bind |
132
+ | `shutdownSignals` | `NodeJS.Signals[]` | `['SIGINT', 'SIGTERM']` | Signals that trigger graceful shutdown |
133
+ | `attachSignals` | `boolean` | `true` | Auto-attach shutdown signal handlers |
134
+ | `onListen` | `(info: { port: number }) => void` | - | Called after the server starts listening |
135
+
136
+ You can override environment lookups by passing an `env` object:
137
+
138
+ ```ts
139
+ const proxy = createProxyServerNode({
140
+ env: {
141
+ DATABASE_URL: 'postgres://user:pass@host/database',
142
+ HOST: '0.0.0.0',
143
+ PORT: '8080'
144
+ }
145
+ })
146
+ ```
147
+
148
+ ## Lifecycle Wiring by Runtime
149
+
150
+ `createProxyServerNode` automatically attaches `SIGINT` and `SIGTERM` handlers. Set `attachSignals: false` to opt out and manage shutdown yourself.
151
+
152
+ For `createProxyService` and `createProxyApp` (runtime-neutral), or for non-Node runtimes, wire shutdown manually using `attachShutdownListeners` and the appropriate adapter:
153
+
154
+ ### Node
155
+
156
+ ```ts
157
+ import { attachShutdownListeners, createProxyService, nodeShutdownAdapter } from '@pg-boss/proxy'
158
+
159
+ const { app, start, stop } = createProxyService({
160
+ options: { connectionString: process.env.DATABASE_URL }
161
+ })
162
+
163
+ await start()
164
+
165
+ attachShutdownListeners(['SIGINT', 'SIGTERM'], nodeShutdownAdapter, stop)
166
+ ```
167
+
168
+ ### Deno
169
+
170
+ ```ts
171
+ import { attachShutdownListeners, createDenoShutdownAdapter, createProxyService } from '@pg-boss/proxy'
172
+
173
+ const { start, stop } = createProxyService({
174
+ options: {
175
+ connectionString: Deno.env.get('DATABASE_URL')
176
+ }
177
+ })
178
+
179
+ await start()
180
+
181
+ attachShutdownListeners(['SIGINT', 'SIGTERM'], createDenoShutdownAdapter(), stop)
182
+ ```
183
+
184
+ ### Bun
185
+
186
+ ```ts
187
+ import { attachShutdownListeners, createProxyService, bunShutdownAdapter } from '@pg-boss/proxy'
188
+
189
+ const { start, stop } = createProxyService({
190
+ options: { connectionString: process.env.DATABASE_URL }
191
+ })
192
+
193
+ await start()
194
+
195
+ attachShutdownListeners(['SIGINT', 'SIGTERM'], bunShutdownAdapter, stop)
196
+ ```
197
+
198
+ ## Configuration
199
+
200
+ The proxy accepts the following options:
201
+
202
+ | Option | Type | Default | Description |
203
+ |--------|------|---------|-------------|
204
+ | `options` | `ConstructorOptions` | - | PgBoss constructor options (see below) |
205
+ | `prefix` | `string` | `/api` | URL prefix for all API routes |
206
+ | `env` | `Record<string, string>` | `process.env` | Environment variables |
207
+ | `middleware` | `MiddlewareHandler \| MiddlewareHandler[]` | - | Hono middleware to apply to API routes |
208
+ | `exposeErrors` | `boolean` | `false` | Return actual error messages to clients |
209
+ | `bodyLimit` | `number` | `1048576` (1MB) | Max request body size in bytes |
210
+ | `routes.allow` | `string[]` | all | List of pg-boss methods to expose |
211
+ | `routes.deny` | `string[]` | none | List of pg-boss methods to exclude |
212
+ | `pages.root` | `boolean` | `true` | Enable/disable the root page (`/`) |
213
+ | `pages.docs` | `boolean` | `true` | Enable/disable Swagger docs (`/docs`) |
214
+ | `pages.openapi` | `boolean` | `true` | Enable/disable OpenAPI spec (`/openapi.json`) |
215
+
216
+ ### Environment Variables
217
+
218
+ | Variable | Default | Description |
219
+ |---|---|---|
220
+ | `DATABASE_URL` | - | PostgreSQL connection string |
221
+ | `PORT` | `3000` | Listening port (Node entry point only) |
222
+ | `HOST` | `localhost` | Listening hostname (Node entry point only) |
223
+ | `PGBOSS_PROXY_AUTH_USERNAME` | - | Basic auth username (must be set with password) |
224
+ | `PGBOSS_PROXY_AUTH_PASSWORD` | - | Basic auth password (must be set with username) |
225
+
226
+ ### PgBoss Constructor Options
227
+
228
+ You can pass any PgBoss constructor options via the `options` object:
229
+
230
+ ```ts
231
+ const { app, boss } = createProxyApp({
232
+ options: {
233
+ connectionString: 'postgres://user:pass@host/database',
234
+ schema: 'custom',
235
+ supervise: true, // enable job supervision (disabled by default)
236
+ schedule: true, // enable job scheduling (disabled by default)
237
+ migrate: true // run migrations on startup (disabled by default)
238
+ }
239
+ })
240
+ ```
241
+
242
+ By default, `supervise`, `schedule`, and `migrate` are set to `false` to run the proxy in a stateless manner. Set any of these to `true` to enable that functionality.
243
+
244
+ ### Request Logging
245
+
246
+ All requests are logged to stdout via the built-in `hono/logger` middleware.
247
+
248
+ ### Authentication
249
+
250
+ Basic auth can be enabled via environment variables:
251
+
252
+ ```bash
253
+ PGBOSS_PROXY_AUTH_USERNAME=admin
254
+ PGBOSS_PROXY_AUTH_PASSWORD=secret
255
+ ```
256
+
257
+ Both variables must be set together. When enabled, auth is applied to all routes under the prefix (e.g., `/api/*`). The root page (`/`), Swagger docs (`/docs`), and OpenAPI spec (`/openapi.json`) sit outside the prefix and remain publicly accessible.
258
+
259
+ ### Custom Middleware
260
+
261
+ You can add custom Hono middleware to the API routes:
262
+
263
+ ```ts
264
+ import { cors } from 'hono/cors'
265
+
266
+ const { app, boss } = createProxyApp({
267
+ options: { connectionString: 'postgres://user:pass@host/database' },
268
+ middleware: [
269
+ // Add CORS headers
270
+ cors({
271
+ origin: ['https://myapp.com'],
272
+ credentials: true
273
+ }),
274
+ // Add any other Hono-compatible middleware here
275
+ ]
276
+ })
277
+ ```
278
+
279
+ ### Custom PgBoss Factory
280
+
281
+ For advanced customization, you can provide a custom `bossFactory` function to wrap or modify pg-boss behavior:
282
+
283
+ ```ts
284
+ import { PgBoss } from 'pg-boss'
285
+
286
+ const { app, boss } = createProxyApp({
287
+ bossFactory: (options) => {
288
+ const instance = new PgBoss({
289
+ ...options,
290
+ // Custom configuration
291
+ })
292
+
293
+ // Wrap methods with logging
294
+ const originalSend = instance.send.bind(instance)
295
+ instance.send = async (...args) => {
296
+ console.log('send called with:', args)
297
+ return originalSend(...args)
298
+ }
299
+
300
+ return instance
301
+ }
302
+ })
303
+
304
+ await boss.start()
305
+ ```
306
+
307
+ ### Route Filtering
308
+
309
+ You can allowlist or denylist pg-boss methods to control which API routes are exposed:
310
+
311
+ ```ts
312
+ const { app, boss } = createProxyApp({
313
+ options: { connectionString: 'postgres://user:pass@host/database' },
314
+ routes: {
315
+ // Only expose safe operations (default: all methods are exposed)
316
+ allow: ['send', 'fetch', 'complete', 'fail', 'getQueue', 'getQueues']
317
+ }
318
+ })
319
+ ```
320
+
321
+ Or deny specific methods:
322
+
323
+ ```ts
324
+ const { app, boss } = createProxyApp({
325
+ options: { connectionString: 'postgres://user:pass@host/database' },
326
+ routes: {
327
+ // Exclude destructive operations
328
+ deny: ['deleteQueue', 'deleteAllJobs', 'deleteStoredJobs']
329
+ }
330
+ })
331
+ ```
332
+
333
+ ### Disabling Pages
334
+
335
+ You can disable the root page, docs, or OpenAPI spec:
336
+
337
+ ```ts
338
+ const { app, boss } = createProxyApp({
339
+ options: { connectionString: 'postgres://user:pass@host/database' },
340
+ pages: {
341
+ root: false, // Disable the home page
342
+ docs: false, // Disable Swagger UI
343
+ openapi: false // Disable OpenAPI JSON endpoint
344
+ }
345
+ })
346
+ ```
347
+
348
+ ## Complete Production Example
349
+
350
+ Here's a production-ready setup with authentication, CORS, and restricted routes:
351
+
352
+ ```ts
353
+ import { createProxyServerNode } from '@pg-boss/proxy/node'
354
+ import { cors } from 'hono/cors'
355
+
356
+ const proxy = createProxyServerNode({
357
+ options: {
358
+ connectionString: process.env.DATABASE_URL,
359
+ schema: 'pgboss'
360
+ },
361
+ prefix: '/api',
362
+ middleware: [
363
+ cors({ origin: process.env.ALLOWED_ORIGINS?.split(',') ?? [] })
364
+ ],
365
+ routes: {
366
+ // Only expose safe operations
367
+ allow: [
368
+ 'send',
369
+ 'sendAfter',
370
+ 'sendDebounced',
371
+ 'sendThrottled',
372
+ 'fetch',
373
+ 'complete',
374
+ 'fail',
375
+ 'cancel',
376
+ 'retry',
377
+ 'getQueue',
378
+ 'getQueues',
379
+ 'getSchedules',
380
+ 'findJobs'
381
+ ]
382
+ },
383
+ bodyLimit: 1024 * 1024, // 1MB
384
+ exposeErrors: false
385
+ })
386
+
387
+ // Server will use HOST and PORT from env (defaults: localhost:3000)
388
+ await proxy.start()
389
+
390
+ console.log(`pg-boss proxy running at http://${proxy.hostname}:${proxy.port}`)
391
+ ```
392
+
393
+ ## Running from Source
394
+
395
+ ```bash
396
+ # Start dev server
397
+ DATABASE_URL=postgres://user:pass@host/database npm run dev
398
+
399
+ # With custom port
400
+ PORT=8080 DATABASE_URL=postgres://user:pass@host/database npm run dev
401
+
402
+ # With authentication
403
+ DATABASE_URL=postgres://user:pass@host/database \
404
+ PGBOSS_PROXY_AUTH_USERNAME=admin \
405
+ PGBOSS_PROXY_AUTH_PASSWORD=secret \
406
+ npm run dev
407
+
408
+ # Build for production
409
+ npm run build
410
+
411
+ # Start production server
412
+ npm start
413
+ ```
414
+
415
+ ## API Reference
416
+
417
+ - [Interactive API Docs](http://localhost:3000/docs) - Swagger UI for exploring all endpoints
418
+ - [OpenAPI Spec](http://localhost:3000/openapi.json) - Machine-readable API specification
419
+ - [pg-boss Docs](https://timgit.github.io/pg-boss) - pg-boss documentation
package/dist/auth.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import type { OpenAPIHono } from '@hono/zod-openapi';
2
+ type ProxyEnv = Record<string, string | undefined>;
3
+ export declare function configureAuth(app: OpenAPIHono, env: ProxyEnv, prefix: string): void;
4
+ export {};
5
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAGpD,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;AAElD,wBAAgB,aAAa,CAAE,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAepF"}
package/dist/auth.js ADDED
@@ -0,0 +1,14 @@
1
+ import { basicAuth } from 'hono/basic-auth';
2
+ export function configureAuth(app, env, prefix) {
3
+ const username = env.PGBOSS_PROXY_AUTH_USERNAME;
4
+ const password = env.PGBOSS_PROXY_AUTH_PASSWORD;
5
+ if (username && !password) {
6
+ throw new Error('PGBOSS_PROXY_AUTH_PASSWORD is required when PGBOSS_PROXY_AUTH_USERNAME is set');
7
+ }
8
+ if (!username && password) {
9
+ throw new Error('PGBOSS_PROXY_AUTH_USERNAME is required when PGBOSS_PROXY_AUTH_PASSWORD is set');
10
+ }
11
+ if (username && password) {
12
+ app.use(`${prefix}/*`, basicAuth({ username, password }));
13
+ }
14
+ }