@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 +419 -0
- package/dist/auth.d.ts +5 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +14 -0
- package/dist/contracts.d.ts +288 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +424 -0
- package/dist/home.d.ts +13 -0
- package/dist/home.d.ts.map +1 -0
- package/dist/home.js +145 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +270 -0
- package/dist/node.d.ts +31 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +78 -0
- package/dist/routes.d.ts +22 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +128 -0
- package/dist/shutdown.d.ts +12 -0
- package/dist/shutdown.d.ts.map +1 -0
- package/dist/shutdown.js +37 -0
- package/dist/types.d.ts +234 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/package.json +63 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { version } from './version.js';
|
|
2
|
+
import { OpenAPIHono, createRoute } from '@hono/zod-openapi';
|
|
3
|
+
import { swaggerUI } from '@hono/swagger-ui';
|
|
4
|
+
import { logger } from 'hono/logger';
|
|
5
|
+
import { PgBoss, events, policies, states } from 'pg-boss';
|
|
6
|
+
import { errorResultSchema, htmlResponseSchema, metaResponseSchema } from './contracts.js';
|
|
7
|
+
import { renderHome } from './home.js';
|
|
8
|
+
import { allRoutes } from './routes.js';
|
|
9
|
+
import { configureAuth } from './auth.js';
|
|
10
|
+
const normalizePrefix = (prefix) => {
|
|
11
|
+
const normalized = prefix.startsWith('/') ? prefix : `/${prefix}`;
|
|
12
|
+
return normalized === '/' ? '' : normalized.replace(/\/$/, '');
|
|
13
|
+
};
|
|
14
|
+
const resolvePrefix = (prefix) => {
|
|
15
|
+
return normalizePrefix(prefix ?? '/api');
|
|
16
|
+
};
|
|
17
|
+
const errorResponse = (context, status, message) => {
|
|
18
|
+
return context.json({ ok: false, error: { message } }, status);
|
|
19
|
+
};
|
|
20
|
+
const resultResponse = (context, result) => {
|
|
21
|
+
return context.json({ ok: true, result: result ?? null }, 200);
|
|
22
|
+
};
|
|
23
|
+
export const createProxyApp = (options) => {
|
|
24
|
+
const envOptions = options.env?.DATABASE_URL
|
|
25
|
+
? { connectionString: options.env.DATABASE_URL }
|
|
26
|
+
: undefined;
|
|
27
|
+
const providedOptions = options.options ?? envOptions ?? {};
|
|
28
|
+
const exposeErrors = options.exposeErrors ?? false;
|
|
29
|
+
const resolvedOptions = { ...providedOptions };
|
|
30
|
+
if (!('supervise' in providedOptions)) {
|
|
31
|
+
resolvedOptions.supervise = false;
|
|
32
|
+
}
|
|
33
|
+
if (!('migrate' in providedOptions)) {
|
|
34
|
+
resolvedOptions.migrate = false;
|
|
35
|
+
}
|
|
36
|
+
if (!('schedule' in providedOptions)) {
|
|
37
|
+
resolvedOptions.schedule = false;
|
|
38
|
+
}
|
|
39
|
+
let boss;
|
|
40
|
+
if (options.bossFactory) {
|
|
41
|
+
boss = options.bossFactory(resolvedOptions);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
if (Object.keys(providedOptions).length === 0) {
|
|
45
|
+
throw new Error('Proxy requires PgBoss constructor options.');
|
|
46
|
+
}
|
|
47
|
+
boss = new PgBoss(resolvedOptions);
|
|
48
|
+
}
|
|
49
|
+
const prefix = resolvePrefix(options.prefix);
|
|
50
|
+
const app = new OpenAPIHono({
|
|
51
|
+
defaultHook: (result, context) => {
|
|
52
|
+
if (!result.success) {
|
|
53
|
+
const message = result.error instanceof Error ? result.error.message : 'Validation error';
|
|
54
|
+
return context.json({ ok: false, error: { message } }, 400);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
app.use('*', logger());
|
|
59
|
+
configureAuth(app, options.env ?? process.env, prefix);
|
|
60
|
+
const base = prefix || '/';
|
|
61
|
+
const openapiPath = '/openapi.json';
|
|
62
|
+
const docsPath = '/docs';
|
|
63
|
+
const pages = options.pages ?? {};
|
|
64
|
+
if (pages.openapi !== false) {
|
|
65
|
+
app.doc31(openapiPath, {
|
|
66
|
+
openapi: '3.1.0',
|
|
67
|
+
info: {
|
|
68
|
+
title: 'pg-boss proxy',
|
|
69
|
+
version,
|
|
70
|
+
description: 'HTTP proxy for pg-boss methods.'
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
if (pages.docs !== false) {
|
|
75
|
+
app.get(docsPath, swaggerUI({ url: openapiPath }));
|
|
76
|
+
}
|
|
77
|
+
app.onError((error, context) => {
|
|
78
|
+
const message = exposeErrors && error instanceof Error
|
|
79
|
+
? error.message
|
|
80
|
+
: 'Internal server error';
|
|
81
|
+
return errorResponse(context, 500, message);
|
|
82
|
+
});
|
|
83
|
+
const homeRoute = createRoute({
|
|
84
|
+
method: 'get',
|
|
85
|
+
path: '/',
|
|
86
|
+
responses: {
|
|
87
|
+
200: {
|
|
88
|
+
description: 'Home page',
|
|
89
|
+
content: {
|
|
90
|
+
'text/html': {
|
|
91
|
+
schema: htmlResponseSchema
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
const applyRouteFilter = (routes) => {
|
|
98
|
+
let result = routes;
|
|
99
|
+
const allow = options.routes?.allow;
|
|
100
|
+
const deny = options.routes?.deny;
|
|
101
|
+
if (allow && allow.length > 0) {
|
|
102
|
+
result = result.filter((entry) => allow.includes(entry.method));
|
|
103
|
+
}
|
|
104
|
+
if (deny && deny.length > 0) {
|
|
105
|
+
result = result.filter((entry) => !deny.includes(entry.method));
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
};
|
|
109
|
+
const enabledRoutes = applyRouteFilter(allRoutes);
|
|
110
|
+
const enabledMethodInfos = enabledRoutes.map(({ method, httpMethod }) => ({ method, httpMethod }));
|
|
111
|
+
const homeHtml = renderHome({ base, openapiPath, docsPath, methods: enabledMethodInfos });
|
|
112
|
+
if (pages.root !== false) {
|
|
113
|
+
app.openapi(homeRoute, (context) => {
|
|
114
|
+
return context.html(homeHtml);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
const maxBodySize = options.bodyLimit ?? 1024 * 1024;
|
|
118
|
+
app.use(`${prefix}/*`, async (context, next) => {
|
|
119
|
+
if (context.req.method === 'POST') {
|
|
120
|
+
const contentLength = context.req.header('content-length');
|
|
121
|
+
if (contentLength && parseInt(contentLength, 10) > maxBodySize) {
|
|
122
|
+
return context.json({ ok: false, error: { message: `Request body too large (max ${maxBodySize} bytes)` } }, 413);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
await next();
|
|
126
|
+
});
|
|
127
|
+
if (options.middleware) {
|
|
128
|
+
const middlewares = Array.isArray(options.middleware) ? options.middleware : [options.middleware];
|
|
129
|
+
for (const mw of middlewares) {
|
|
130
|
+
app.use(`${prefix}/*`, mw);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const metaRoute = createRoute({
|
|
134
|
+
method: 'get',
|
|
135
|
+
path: `${prefix}/meta`,
|
|
136
|
+
responses: {
|
|
137
|
+
200: {
|
|
138
|
+
description: 'Metadata for pg-boss enumerations',
|
|
139
|
+
content: {
|
|
140
|
+
'application/json': {
|
|
141
|
+
schema: metaResponseSchema
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
app.openapi(metaRoute, (context) => {
|
|
148
|
+
return context.json({ ok: true, result: { states, policies, events } }, 200);
|
|
149
|
+
});
|
|
150
|
+
const registerRoute = (entry) => {
|
|
151
|
+
const { method, httpMethod, tag, request, querySchema, response, args: buildArgs } = entry;
|
|
152
|
+
const responses = {
|
|
153
|
+
200: {
|
|
154
|
+
description: 'Method response',
|
|
155
|
+
content: {
|
|
156
|
+
'application/json': {
|
|
157
|
+
schema: response
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
500: {
|
|
162
|
+
description: 'Server error',
|
|
163
|
+
content: {
|
|
164
|
+
'application/json': {
|
|
165
|
+
schema: errorResultSchema
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
if (httpMethod === 'post' && request) {
|
|
171
|
+
responses[400] = {
|
|
172
|
+
description: 'Invalid request',
|
|
173
|
+
content: {
|
|
174
|
+
'application/json': {
|
|
175
|
+
schema: errorResultSchema
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
if (httpMethod === 'get' && querySchema) {
|
|
181
|
+
responses[400] = {
|
|
182
|
+
description: 'Invalid query parameters',
|
|
183
|
+
content: {
|
|
184
|
+
'application/json': {
|
|
185
|
+
schema: errorResultSchema
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
const routeDef = {
|
|
191
|
+
method: httpMethod,
|
|
192
|
+
path: `${prefix}/${method}`,
|
|
193
|
+
tags: [tag],
|
|
194
|
+
operationId: method,
|
|
195
|
+
responses
|
|
196
|
+
};
|
|
197
|
+
if (httpMethod === 'post' && request) {
|
|
198
|
+
routeDef.request = {
|
|
199
|
+
body: {
|
|
200
|
+
content: {
|
|
201
|
+
'application/json': {
|
|
202
|
+
schema: request
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
description: 'Arguments to pass through to the pg-boss method'
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
if (httpMethod === 'get' && querySchema) {
|
|
210
|
+
routeDef.request = {
|
|
211
|
+
query: querySchema
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
const route = createRoute(routeDef);
|
|
215
|
+
app.openapi(route, async (context) => {
|
|
216
|
+
let args;
|
|
217
|
+
if (httpMethod === 'post' && request) {
|
|
218
|
+
const input = context.req.valid('json');
|
|
219
|
+
args = buildArgs(input);
|
|
220
|
+
}
|
|
221
|
+
else if (httpMethod === 'get' && querySchema) {
|
|
222
|
+
try {
|
|
223
|
+
const raw = context.req.queries();
|
|
224
|
+
const flat = {};
|
|
225
|
+
for (const [key, values] of Object.entries(raw)) {
|
|
226
|
+
flat[key] = values.length === 1 ? values[0] : values;
|
|
227
|
+
}
|
|
228
|
+
const input = querySchema.parse(flat);
|
|
229
|
+
args = buildArgs(input);
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
const message = error instanceof Error ? error.message : 'Invalid query parameters';
|
|
233
|
+
return errorResponse(context, 400, message);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
args = buildArgs();
|
|
238
|
+
}
|
|
239
|
+
try {
|
|
240
|
+
const result = await boss[method](...args);
|
|
241
|
+
return resultResponse(context, result);
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
const message = exposeErrors && error instanceof Error
|
|
245
|
+
? error.message
|
|
246
|
+
: 'Internal server error';
|
|
247
|
+
return errorResponse(context, 500, message);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
};
|
|
251
|
+
for (const entry of enabledRoutes) {
|
|
252
|
+
registerRoute(entry);
|
|
253
|
+
}
|
|
254
|
+
return { app, boss, prefix };
|
|
255
|
+
};
|
|
256
|
+
export const createProxyService = (options) => {
|
|
257
|
+
const proxy = createProxyApp(options);
|
|
258
|
+
return {
|
|
259
|
+
...proxy,
|
|
260
|
+
start: async () => {
|
|
261
|
+
await proxy.boss.start();
|
|
262
|
+
},
|
|
263
|
+
stop: async () => {
|
|
264
|
+
await proxy.boss.stop();
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
};
|
|
268
|
+
export { bossMethodNames, bossMethodInfos } from './routes.js';
|
|
269
|
+
export { configureAuth } from './auth.js';
|
|
270
|
+
export { attachShutdownListeners, nodeShutdownAdapter, bunShutdownAdapter, createDenoShutdownAdapter } from './shutdown.js';
|
package/dist/node.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { serve } from '@hono/node-server';
|
|
2
|
+
import type { ConstructorOptions } from 'pg-boss';
|
|
3
|
+
import { type ProxyApp, type ProxyOptions, type ProxyService } from './index.js';
|
|
4
|
+
type ProxyNodeOptions = Omit<ProxyOptions, 'options' | 'env'> & {
|
|
5
|
+
options?: ConstructorOptions;
|
|
6
|
+
env?: Record<string, string | undefined>;
|
|
7
|
+
};
|
|
8
|
+
type ProxyServerNodeOptions = ProxyNodeOptions & {
|
|
9
|
+
port?: number;
|
|
10
|
+
hostname?: string;
|
|
11
|
+
shutdownSignals?: NodeJS.Signals[];
|
|
12
|
+
attachSignals?: boolean;
|
|
13
|
+
onListen?: (info: {
|
|
14
|
+
port: number;
|
|
15
|
+
}) => void;
|
|
16
|
+
};
|
|
17
|
+
type ProxyServerNode = Omit<ProxyService, 'start' | 'stop'> & {
|
|
18
|
+
hostname: string;
|
|
19
|
+
port: number;
|
|
20
|
+
server: ReturnType<typeof serve> | null;
|
|
21
|
+
start: () => Promise<{
|
|
22
|
+
port: number;
|
|
23
|
+
}>;
|
|
24
|
+
stop: () => Promise<void>;
|
|
25
|
+
detachSignals?: () => void;
|
|
26
|
+
};
|
|
27
|
+
export declare const createProxyAppNode: (options?: ProxyNodeOptions) => ProxyApp;
|
|
28
|
+
export declare const createProxyServiceNode: (options?: ProxyNodeOptions) => ProxyService;
|
|
29
|
+
export declare const createProxyServerNode: (options?: ProxyServerNodeOptions) => ProxyServerNode;
|
|
30
|
+
export type { ProxyNodeOptions, ProxyServerNodeOptions, ProxyServerNode };
|
|
31
|
+
//# sourceMappingURL=node.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AACzC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAA;AACjD,OAAO,EAKL,KAAK,QAAQ,EACb,KAAK,YAAY,EACjB,KAAK,YAAY,EAClB,MAAM,YAAY,CAAA;AAEnB,KAAK,gBAAgB,GAAG,IAAI,CAAC,YAAY,EAAE,SAAS,GAAG,KAAK,CAAC,GAAG;IAC9D,OAAO,CAAC,EAAE,kBAAkB,CAAA;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;CACzC,CAAA;AAED,KAAK,sBAAsB,GAAG,gBAAgB,GAAG;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC,OAAO,EAAE,CAAA;IAClC,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAA;CAC5C,CAAA;AAED,KAAK,eAAe,GAAG,IAAI,CAAC,YAAY,EAAE,OAAO,GAAG,MAAM,CAAC,GAAG;IAC5D,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,UAAU,CAAC,OAAO,KAAK,CAAC,GAAG,IAAI,CAAA;IACvC,KAAK,EAAE,MAAM,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACtC,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACzB,aAAa,CAAC,EAAE,MAAM,IAAI,CAAA;CAC3B,CAAA;AAcD,eAAO,MAAM,kBAAkB,GAAI,UAAS,gBAAqB,KAAG,QAInE,CAAA;AAED,eAAO,MAAM,sBAAsB,GAAI,UAAS,gBAAqB,KAAG,YAIvE,CAAA;AAED,eAAO,MAAM,qBAAqB,GAAI,UAAS,sBAA2B,KAAG,eAgE5E,CAAA;AAED,YAAY,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,eAAe,EAAE,CAAA"}
|
package/dist/node.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { serve } from '@hono/node-server';
|
|
2
|
+
import { attachShutdownListeners, createProxyApp, createProxyService, nodeShutdownAdapter } from './index.js';
|
|
3
|
+
const resolveOptions = (options) => {
|
|
4
|
+
if (options.options) {
|
|
5
|
+
return options.options;
|
|
6
|
+
}
|
|
7
|
+
if (options.env?.DATABASE_URL) {
|
|
8
|
+
return { connectionString: options.env.DATABASE_URL };
|
|
9
|
+
}
|
|
10
|
+
throw new Error('Proxy requires PgBoss constructor options or DATABASE_URL.');
|
|
11
|
+
};
|
|
12
|
+
export const createProxyAppNode = (options = {}) => {
|
|
13
|
+
const { options: __, ...rest } = options;
|
|
14
|
+
const env = options.env ?? process.env;
|
|
15
|
+
return createProxyApp({ ...rest, options: resolveOptions({ ...options, env }), env });
|
|
16
|
+
};
|
|
17
|
+
export const createProxyServiceNode = (options = {}) => {
|
|
18
|
+
const { options: __, ...rest } = options;
|
|
19
|
+
const env = options.env ?? process.env;
|
|
20
|
+
return createProxyService({ ...rest, options: resolveOptions({ ...options, env }), env });
|
|
21
|
+
};
|
|
22
|
+
export const createProxyServerNode = (options = {}) => {
|
|
23
|
+
const { options: __, ...rest } = options;
|
|
24
|
+
const env = options.env ?? process.env;
|
|
25
|
+
const service = createProxyService({ ...rest, options: resolveOptions({ ...options, env }), env });
|
|
26
|
+
const hostname = options.hostname ?? env.HOST ?? 'localhost';
|
|
27
|
+
const port = options.port ?? Number(env.PORT ?? 3000);
|
|
28
|
+
const signals = options.shutdownSignals ?? ['SIGINT', 'SIGTERM'];
|
|
29
|
+
let server = null;
|
|
30
|
+
let detachSignals;
|
|
31
|
+
const proxy = {};
|
|
32
|
+
const stop = async () => {
|
|
33
|
+
try {
|
|
34
|
+
await service.stop();
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
if (server) {
|
|
38
|
+
await new Promise((resolve) => server?.close(() => resolve()));
|
|
39
|
+
server = null;
|
|
40
|
+
proxy.server = null;
|
|
41
|
+
}
|
|
42
|
+
if (detachSignals) {
|
|
43
|
+
detachSignals();
|
|
44
|
+
detachSignals = undefined;
|
|
45
|
+
proxy.detachSignals = undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const start = async () => {
|
|
50
|
+
await service.start();
|
|
51
|
+
if (!server) {
|
|
52
|
+
const info = await new Promise((resolve) => {
|
|
53
|
+
server = serve({
|
|
54
|
+
fetch: service.app.fetch,
|
|
55
|
+
port,
|
|
56
|
+
hostname
|
|
57
|
+
}, resolve);
|
|
58
|
+
});
|
|
59
|
+
proxy.server = server;
|
|
60
|
+
options.onListen?.(info);
|
|
61
|
+
}
|
|
62
|
+
if (options.attachSignals ?? true) {
|
|
63
|
+
detachSignals = attachShutdownListeners(signals, nodeShutdownAdapter, stop);
|
|
64
|
+
proxy.detachSignals = detachSignals;
|
|
65
|
+
}
|
|
66
|
+
return { port };
|
|
67
|
+
};
|
|
68
|
+
Object.assign(proxy, {
|
|
69
|
+
...service,
|
|
70
|
+
hostname,
|
|
71
|
+
port,
|
|
72
|
+
server,
|
|
73
|
+
start,
|
|
74
|
+
stop,
|
|
75
|
+
detachSignals
|
|
76
|
+
});
|
|
77
|
+
return proxy;
|
|
78
|
+
};
|
package/dist/routes.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from '@hono/zod-openapi';
|
|
2
|
+
export declare const withOptionalDataOptions: (args: unknown[], data?: unknown, options?: unknown) => unknown[];
|
|
3
|
+
export declare const withFixedDataOptions: (args: unknown[], data?: unknown, options?: unknown, tail?: unknown[]) => unknown[];
|
|
4
|
+
export declare const withOptionalOptions: (args: unknown[], options?: unknown) => unknown[];
|
|
5
|
+
export type RouteEntry = {
|
|
6
|
+
method: string;
|
|
7
|
+
httpMethod: 'get' | 'post';
|
|
8
|
+
tag: string;
|
|
9
|
+
request?: z.ZodTypeAny;
|
|
10
|
+
querySchema?: z.ZodTypeAny;
|
|
11
|
+
response: z.ZodTypeAny;
|
|
12
|
+
args: (input?: unknown) => unknown[];
|
|
13
|
+
};
|
|
14
|
+
export declare const postMethods: RouteEntry[];
|
|
15
|
+
export declare const getMethods: RouteEntry[];
|
|
16
|
+
export declare const allRoutes: RouteEntry[];
|
|
17
|
+
export declare const bossMethodNames: string[];
|
|
18
|
+
export declare const bossMethodInfos: {
|
|
19
|
+
method: string;
|
|
20
|
+
httpMethod: 'get' | 'post';
|
|
21
|
+
}[];
|
|
22
|
+
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,mBAAmB,CAAA;AA4DrC,eAAO,MAAM,uBAAuB,GAAI,MAAM,OAAO,EAAE,EAAE,OAAO,OAAO,EAAE,UAAU,OAAO,cAQzF,CAAA;AAED,eAAO,MAAM,oBAAoB,GAAI,MAAM,OAAO,EAAE,EAAE,OAAO,OAAO,EAAE,UAAU,OAAO,EAAE,OAAM,OAAO,EAAO,cAM5G,CAAA;AAED,eAAO,MAAM,mBAAmB,GAAI,MAAM,OAAO,EAAE,EAAE,UAAU,OAAO,cAKrE,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,KAAK,GAAG,MAAM,CAAA;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,CAAC,EAAE,CAAC,CAAC,UAAU,CAAA;IACtB,WAAW,CAAC,EAAE,CAAC,CAAC,UAAU,CAAA;IAC1B,QAAQ,EAAE,CAAC,CAAC,UAAU,CAAA;IACtB,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,OAAO,EAAE,CAAA;CACrC,CAAA;AA+DD,eAAO,MAAM,WAAW,EAAE,UAAU,EAyBnC,CAAA;AAED,eAAO,MAAM,UAAU,EAAE,UAAU,EAsBlC,CAAA;AAED,eAAO,MAAM,SAAS,EAAE,UAAU,EAAoC,CAAA;AAEtE,eAAO,MAAM,eAAe,EAAE,MAAM,EAA2C,CAAA;AAE/E,eAAO,MAAM,eAAe,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,KAAK,GAAG,MAAM,CAAA;CAAE,EAGzE,CAAA"}
|
package/dist/routes.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { z } from '@hono/zod-openapi';
|
|
2
|
+
import { cancelRequestSchema, cancelResponseSchema, completeRequestSchema, completeResponseSchema, createQueueRequestSchema, createQueueResponseSchema, deleteAllJobsRequestSchema, deleteAllJobsResponseSchema, deleteJobRequestSchema, deleteJobResponseSchema, deleteQueueRequestSchema, deleteQueueResponseSchema, deleteQueuedJobsRequestSchema, deleteQueuedJobsResponseSchema, deleteStoredJobsRequestSchema, deleteStoredJobsResponseSchema, failRequestSchema, failResponseSchema, fetchRequestSchema, fetchResponseSchema, findJobsResponseSchema, getBamStatusResponseSchema, getBlockedKeysResponseSchema, getQueueResponseSchema, getQueuesResponseSchema, getSchedulesResponseSchema, insertRequestSchema, insertResponseSchema, isInstalledResponseSchema, publishRequestSchema, publishResponseSchema, resumeRequestSchema, resumeResponseSchema, retryRequestSchema, retryResponseSchema, scheduleRequestSchema, scheduleResponseSchema, schemaVersionResponseSchema, sendAfterRequestSchema, sendAfterResponseSchema, sendDebouncedRequestSchema, sendDebouncedResponseSchema, sendRequestSchema, sendResponseSchema, sendThrottledRequestSchema, sendThrottledResponseSchema, subscribeRequestSchema, subscribeResponseSchema, superviseRequestSchema, superviseResponseSchema, unsubscribeRequestSchema, unsubscribeResponseSchema, unscheduleRequestSchema, unscheduleResponseSchema, updateQueueRequestSchema, updateQueueResponseSchema } from './contracts.js';
|
|
3
|
+
export const withOptionalDataOptions = (args, data, options) => {
|
|
4
|
+
if (data !== undefined || options !== undefined) {
|
|
5
|
+
args.push(data ?? null);
|
|
6
|
+
}
|
|
7
|
+
if (options !== undefined) {
|
|
8
|
+
args.push(options);
|
|
9
|
+
}
|
|
10
|
+
return args;
|
|
11
|
+
};
|
|
12
|
+
export const withFixedDataOptions = (args, data, options, tail = []) => {
|
|
13
|
+
const result = [...args, data ?? null, options ?? null, ...tail];
|
|
14
|
+
while (result.length > 0 && result[result.length - 1] === undefined) {
|
|
15
|
+
result.pop();
|
|
16
|
+
}
|
|
17
|
+
return result;
|
|
18
|
+
};
|
|
19
|
+
export const withOptionalOptions = (args, options) => {
|
|
20
|
+
if (options !== undefined) {
|
|
21
|
+
args.push(options);
|
|
22
|
+
}
|
|
23
|
+
return args;
|
|
24
|
+
};
|
|
25
|
+
const post = (tag, method, request, response, args) => ({
|
|
26
|
+
method,
|
|
27
|
+
httpMethod: 'post',
|
|
28
|
+
tag,
|
|
29
|
+
request,
|
|
30
|
+
response,
|
|
31
|
+
args: (body) => args(body)
|
|
32
|
+
});
|
|
33
|
+
const get = (tag, method, response, querySchema, args) => ({
|
|
34
|
+
method,
|
|
35
|
+
httpMethod: 'get',
|
|
36
|
+
tag,
|
|
37
|
+
querySchema,
|
|
38
|
+
response,
|
|
39
|
+
args: args
|
|
40
|
+
? (query) => args(query)
|
|
41
|
+
: () => []
|
|
42
|
+
});
|
|
43
|
+
// Query schemas for GET endpoints (transport-layer schemas with string coercion)
|
|
44
|
+
const nameQuerySchema = z.object({
|
|
45
|
+
name: z.string().min(1)
|
|
46
|
+
});
|
|
47
|
+
const namesQuerySchema = z.object({
|
|
48
|
+
names: z.union([z.array(z.string()), z.string().transform((s) => [s])]).optional()
|
|
49
|
+
});
|
|
50
|
+
const schedulesQuerySchema = z.object({
|
|
51
|
+
name: z.string().optional(),
|
|
52
|
+
key: z.string().optional()
|
|
53
|
+
});
|
|
54
|
+
const findJobsQuerySchema = z.object({
|
|
55
|
+
name: z.string().min(1),
|
|
56
|
+
id: z.string().optional(),
|
|
57
|
+
key: z.string().optional(),
|
|
58
|
+
queued: z.enum(['true', 'false']).transform((v) => v === 'true').optional(),
|
|
59
|
+
dataKey: z.string().optional(),
|
|
60
|
+
dataValue: z.string().optional()
|
|
61
|
+
}).refine((q) => !q.dataValue || q.dataKey, {
|
|
62
|
+
message: 'dataKey is required when dataValue is provided'
|
|
63
|
+
});
|
|
64
|
+
const blockedKeysQuerySchema = z.object({
|
|
65
|
+
name: z.string().min(1)
|
|
66
|
+
});
|
|
67
|
+
export const postMethods = [
|
|
68
|
+
post('jobs', 'send', sendRequestSchema, sendResponseSchema, (body) => withOptionalDataOptions([body.name], body.data, body.options)),
|
|
69
|
+
post('jobs', 'sendAfter', sendAfterRequestSchema, sendAfterResponseSchema, (body) => withFixedDataOptions([body.name], body.data, body.options, [body.after])),
|
|
70
|
+
post('jobs', 'sendThrottled', sendThrottledRequestSchema, sendThrottledResponseSchema, (body) => withFixedDataOptions([body.name], body.data, body.options, [body.seconds, body.key])),
|
|
71
|
+
post('jobs', 'sendDebounced', sendDebouncedRequestSchema, sendDebouncedResponseSchema, (body) => withFixedDataOptions([body.name], body.data, body.options, [body.seconds, body.key])),
|
|
72
|
+
post('jobs', 'insert', insertRequestSchema, insertResponseSchema, (body) => withOptionalOptions([body.name, body.jobs], body.options)),
|
|
73
|
+
post('jobs', 'fetch', fetchRequestSchema, fetchResponseSchema, (body) => withOptionalOptions([body.name], body.options)),
|
|
74
|
+
post('jobs', 'complete', completeRequestSchema, completeResponseSchema, (body) => withOptionalDataOptions([body.name, body.id], body.data, body.options)),
|
|
75
|
+
post('jobs', 'fail', failRequestSchema, failResponseSchema, (body) => withOptionalDataOptions([body.name, body.id], body.data)),
|
|
76
|
+
post('jobs', 'cancel', cancelRequestSchema, cancelResponseSchema, (body) => [body.name, body.id]),
|
|
77
|
+
post('jobs', 'resume', resumeRequestSchema, resumeResponseSchema, (body) => [body.name, body.id]),
|
|
78
|
+
post('jobs', 'retry', retryRequestSchema, retryResponseSchema, (body) => [body.name, body.id]),
|
|
79
|
+
post('jobs', 'deleteJob', deleteJobRequestSchema, deleteJobResponseSchema, (body) => [body.name, body.id]),
|
|
80
|
+
post('jobs', 'deleteQueuedJobs', deleteQueuedJobsRequestSchema, deleteQueuedJobsResponseSchema, (body) => [body.name]),
|
|
81
|
+
post('jobs', 'deleteStoredJobs', deleteStoredJobsRequestSchema, deleteStoredJobsResponseSchema, (body) => [body.name]),
|
|
82
|
+
post('jobs', 'deleteAllJobs', deleteAllJobsRequestSchema, deleteAllJobsResponseSchema, (body) => (body.name ? [body.name] : [])),
|
|
83
|
+
post('queues', 'createQueue', createQueueRequestSchema, createQueueResponseSchema, (body) => withOptionalOptions([body.name], body.options)),
|
|
84
|
+
post('queues', 'updateQueue', updateQueueRequestSchema, updateQueueResponseSchema, (body) => withOptionalOptions([body.name], body.options)),
|
|
85
|
+
post('queues', 'deleteQueue', deleteQueueRequestSchema, deleteQueueResponseSchema, (body) => [body.name]),
|
|
86
|
+
post('events', 'subscribe', subscribeRequestSchema, subscribeResponseSchema, (body) => [body.event, body.name]),
|
|
87
|
+
post('events', 'unsubscribe', unsubscribeRequestSchema, unsubscribeResponseSchema, (body) => [body.event, body.name]),
|
|
88
|
+
post('events', 'publish', publishRequestSchema, publishResponseSchema, (body) => withOptionalDataOptions([body.event], body.data, body.options)),
|
|
89
|
+
post('schedules', 'schedule', scheduleRequestSchema, scheduleResponseSchema, (body) => withOptionalDataOptions([body.name, body.cron], body.data, body.options)),
|
|
90
|
+
post('schedules', 'unschedule', unscheduleRequestSchema, unscheduleResponseSchema, (body) => (body.key ? [body.name, body.key] : [body.name])),
|
|
91
|
+
post('system', 'supervise', superviseRequestSchema, superviseResponseSchema, (body) => (body.name ? [body.name] : []))
|
|
92
|
+
];
|
|
93
|
+
export const getMethods = [
|
|
94
|
+
get('system', 'isInstalled', isInstalledResponseSchema),
|
|
95
|
+
get('system', 'schemaVersion', schemaVersionResponseSchema),
|
|
96
|
+
get('system', 'getBamStatus', getBamStatusResponseSchema),
|
|
97
|
+
get('queues', 'getQueue', getQueueResponseSchema, nameQuerySchema, (q) => [q.name]),
|
|
98
|
+
get('queues', 'getBlockedKeys', getBlockedKeysResponseSchema, blockedKeysQuerySchema, (q) => [q.name]),
|
|
99
|
+
get('queues', 'getQueues', getQueuesResponseSchema, namesQuerySchema, (q) => (q.names ? [q.names] : [])),
|
|
100
|
+
get('schedules', 'getSchedules', getSchedulesResponseSchema, schedulesQuerySchema, (q) => {
|
|
101
|
+
if (q.name && q.key)
|
|
102
|
+
return [q.name, q.key];
|
|
103
|
+
if (q.name)
|
|
104
|
+
return [q.name];
|
|
105
|
+
return [];
|
|
106
|
+
}),
|
|
107
|
+
get('jobs', 'findJobs', findJobsResponseSchema, findJobsQuerySchema, (q) => {
|
|
108
|
+
const args = [q.name];
|
|
109
|
+
const options = {};
|
|
110
|
+
if (q.id)
|
|
111
|
+
options.id = q.id;
|
|
112
|
+
if (q.key)
|
|
113
|
+
options.key = q.key;
|
|
114
|
+
if (q.queued !== undefined)
|
|
115
|
+
options.queued = q.queued;
|
|
116
|
+
if (q.dataKey)
|
|
117
|
+
options.data = { [q.dataKey]: q.dataValue ?? null };
|
|
118
|
+
if (Object.keys(options).length > 0)
|
|
119
|
+
args.push(options);
|
|
120
|
+
return args;
|
|
121
|
+
})
|
|
122
|
+
];
|
|
123
|
+
export const allRoutes = [...postMethods, ...getMethods];
|
|
124
|
+
export const bossMethodNames = allRoutes.map((entry) => entry.method);
|
|
125
|
+
export const bossMethodInfos = allRoutes.map((entry) => ({
|
|
126
|
+
method: entry.method,
|
|
127
|
+
httpMethod: entry.httpMethod
|
|
128
|
+
}));
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type ShutdownHandler = () => void | Promise<void>;
|
|
2
|
+
export type ShutdownAdapter<Signal> = {
|
|
3
|
+
on: (signal: Signal, handler: () => void) => void;
|
|
4
|
+
off?: (signal: Signal, handler: () => void) => void;
|
|
5
|
+
};
|
|
6
|
+
type DenoSignal = 'SIGINT' | 'SIGTERM' | string;
|
|
7
|
+
export declare const nodeShutdownAdapter: ShutdownAdapter<NodeJS.Signals>;
|
|
8
|
+
export declare const bunShutdownAdapter: ShutdownAdapter<NodeJS.Signals>;
|
|
9
|
+
export declare const createDenoShutdownAdapter: () => ShutdownAdapter<DenoSignal>;
|
|
10
|
+
export declare const attachShutdownListeners: <Signal>(signals: Signal[], adapter: ShutdownAdapter<Signal>, handler: ShutdownHandler) => () => void;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=shutdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shutdown.d.ts","sourceRoot":"","sources":["../src/shutdown.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;AAExD,MAAM,MAAM,eAAe,CAAC,MAAM,IAAI;IACpC,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,IAAI,KAAK,IAAI,CAAA;IACjD,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,IAAI,KAAK,IAAI,CAAA;CACpD,CAAA;AAED,KAAK,UAAU,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAA;AAO/C,eAAO,MAAM,mBAAmB,EAAE,eAAe,CAAC,MAAM,CAAC,OAAO,CAG/D,CAAA;AAED,eAAO,MAAM,kBAAkB,iCAAsB,CAAA;AAErD,eAAO,MAAM,yBAAyB,QAAO,eAAe,CAAC,UAAU,CAStE,CAAA;AAED,eAAO,MAAM,uBAAuB,GAAI,MAAM,EAC5C,SAAS,MAAM,EAAE,EACjB,SAAS,eAAe,CAAC,MAAM,CAAC,EAChC,SAAS,eAAe,eAuBzB,CAAA"}
|
package/dist/shutdown.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export const nodeShutdownAdapter = {
|
|
2
|
+
on: (signal, handler) => process.on(signal, handler),
|
|
3
|
+
off: (signal, handler) => process.off(signal, handler)
|
|
4
|
+
};
|
|
5
|
+
export const bunShutdownAdapter = nodeShutdownAdapter;
|
|
6
|
+
export const createDenoShutdownAdapter = () => {
|
|
7
|
+
const deno = globalThis.Deno;
|
|
8
|
+
if (!deno) {
|
|
9
|
+
throw new Error('Deno global is not available in this runtime.');
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
on: (signal, handler) => deno.addSignalListener(signal, handler),
|
|
13
|
+
off: (signal, handler) => deno.removeSignalListener(signal, handler)
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
export const attachShutdownListeners = (signals, adapter, handler) => {
|
|
17
|
+
let called = false;
|
|
18
|
+
const wrapped = () => {
|
|
19
|
+
if (called)
|
|
20
|
+
return;
|
|
21
|
+
called = true;
|
|
22
|
+
Promise.resolve(handler()).catch((err) => {
|
|
23
|
+
console.error('Shutdown handler error:', err);
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
for (const signal of signals) {
|
|
27
|
+
adapter.on(signal, wrapped);
|
|
28
|
+
}
|
|
29
|
+
return () => {
|
|
30
|
+
if (!adapter.off) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
for (const signal of signals) {
|
|
34
|
+
adapter.off(signal, wrapped);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
};
|