@constructive-io/knative-job-fn 0.2.6 → 0.3.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/CHANGELOG.md CHANGED
@@ -3,6 +3,18 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [0.3.0](https://github.com/constructive-io/jobs/compare/@constructive-io/knative-job-fn@0.2.8...@constructive-io/knative-job-fn@0.3.0) (2026-01-18)
7
+
8
+ **Note:** Version bump only for package @constructive-io/knative-job-fn
9
+
10
+ ## [0.2.8](https://github.com/constructive-io/jobs/compare/@constructive-io/knative-job-fn@0.2.7...@constructive-io/knative-job-fn@0.2.8) (2026-01-18)
11
+
12
+ **Note:** Version bump only for package @constructive-io/knative-job-fn
13
+
14
+ ## [0.2.7](https://github.com/constructive-io/jobs/compare/@constructive-io/knative-job-fn@0.2.6...@constructive-io/knative-job-fn@0.2.7) (2025-12-25)
15
+
16
+ **Note:** Version bump only for package @constructive-io/knative-job-fn
17
+
6
18
  ## [0.2.6](https://github.com/constructive-io/jobs/compare/@constructive-io/knative-job-fn@0.2.5...@constructive-io/knative-job-fn@0.2.6) (2025-12-24)
7
19
 
8
20
  **Note:** Version bump only for package @constructive-io/knative-job-fn
package/dist/index.d.ts CHANGED
@@ -1,5 +1,11 @@
1
- declare const _default: {
1
+ import type { Server as HttpServer } from 'http';
2
+ declare const createJobApp: () => {
2
3
  post: (...args: any[]) => any;
3
- listen: (port: any, cb?: () => void) => void;
4
+ listen: (port: any, hostOrCb?: string | (() => void), cb?: () => void) => HttpServer;
4
5
  };
5
- export default _default;
6
+ declare const defaultApp: {
7
+ post: (...args: any[]) => any;
8
+ listen: (port: any, hostOrCb?: string | (() => void), cb?: () => void) => HttpServer;
9
+ };
10
+ export { createJobApp };
11
+ export default defaultApp;
package/dist/index.js CHANGED
@@ -3,23 +3,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createJobApp = void 0;
6
7
  const express_1 = __importDefault(require("express"));
7
8
  const body_parser_1 = __importDefault(require("body-parser"));
8
9
  const node_http_1 = __importDefault(require("node:http"));
9
10
  const node_https_1 = __importDefault(require("node:https"));
10
11
  const node_url_1 = require("node:url");
11
- const app = (0, express_1.default)();
12
- app.use(body_parser_1.default.json());
13
- // Echo job headers back on responses for debugging/traceability.
14
- app.use((req, res, next) => {
15
- res.set({
16
- 'Content-Type': 'application/json',
17
- 'X-Worker-Id': req.get('X-Worker-Id'),
18
- 'X-Database-Id': req.get('X-Database-Id'),
19
- 'X-Job-Id': req.get('X-Job-Id')
20
- });
21
- next();
22
- });
12
+ const logger_1 = require("@pgpmjs/logger");
13
+ function getHeaders(req) {
14
+ return {
15
+ 'x-worker-id': req.get('X-Worker-Id'),
16
+ 'x-job-id': req.get('X-Job-Id'),
17
+ 'x-database-id': req.get('X-Database-Id'),
18
+ 'x-callback-url': req.get('X-Callback-Url')
19
+ };
20
+ }
23
21
  // Normalize callback URL so it always points at the /callback endpoint.
24
22
  const normalizeCallbackUrl = (rawUrl) => {
25
23
  try {
@@ -85,8 +83,7 @@ const sendJobCallback = async (ctx, status, errorMessage) => {
85
83
  body.error = errorMessage || 'ERROR';
86
84
  }
87
85
  try {
88
- // eslint-disable-next-line no-console
89
- console.log('[knative-job-fn] Sending job callback', {
86
+ logger.info('Sending job callback', {
90
87
  status,
91
88
  target: normalizeCallbackUrl(callbackUrl),
92
89
  workerId,
@@ -96,65 +93,150 @@ const sendJobCallback = async (ctx, status, errorMessage) => {
96
93
  await postJson(target, headers, body);
97
94
  }
98
95
  catch (err) {
99
- // eslint-disable-next-line no-console
100
- console.error('[knative-job-fn] Failed to POST job callback', {
96
+ logger.error('Failed to POST job callback', {
101
97
  target,
102
98
  status,
103
99
  err
104
100
  });
105
101
  }
106
102
  };
107
- // Attach per-request context and a finish hook to send success callbacks.
108
- app.use((req, res, next) => {
109
- const ctx = {
110
- callbackUrl: req.get('X-Callback-Url'),
111
- workerId: req.get('X-Worker-Id'),
112
- jobId: req.get('X-Job-Id'),
113
- databaseId: req.get('X-Database-Id')
114
- };
115
- // Store on res.locals so the error middleware can also mark callbacks as sent.
116
- res.locals = res.locals || {};
117
- res.locals.jobContext = ctx;
118
- res.locals.jobCallbackSent = false;
119
- if (ctx.callbackUrl && ctx.workerId && ctx.jobId) {
120
- res.on('finish', () => {
121
- // If an error handler already sent a callback, skip.
122
- if (res.locals.jobCallbackSent)
123
- return;
124
- res.locals.jobCallbackSent = true;
125
- void sendJobCallback(ctx, 'success');
126
- });
127
- }
128
- next();
129
- });
130
- exports.default = {
131
- post: function (...args) {
132
- return app.post.apply(app, args);
133
- },
134
- listen: (port, cb = () => { }) => {
135
- // NOTE Remember that Express middleware executes in order.
136
- // You should define error handlers last, after all other middleware.
137
- // Otherwise, your error handler won't get called
138
- // eslint-disable-next-line no-unused-vars
139
- app.use(async (error, req, res, next) => {
140
- res.set({
141
- 'Content-Type': 'application/json',
142
- 'X-Job-Error': true
143
- });
144
- // Mark job as having errored via callback, if available.
145
- try {
146
- const ctx = res.locals?.jobContext;
147
- if (ctx && !res.locals.jobCallbackSent) {
148
- res.locals.jobCallbackSent = true;
149
- await sendJobCallback(ctx, 'error', error?.message);
150
- }
103
+ const logger = (0, logger_1.createLogger)('knative-job-fn');
104
+ const createJobApp = () => {
105
+ const app = (0, express_1.default)();
106
+ app.use(body_parser_1.default.json());
107
+ // Basic request logging for all incoming job invocations.
108
+ app.use((req, res, next) => {
109
+ try {
110
+ // Log only the headers we care about plus a shallow body snapshot
111
+ const headers = getHeaders(req);
112
+ let body;
113
+ if (req.body && typeof req.body === 'object') {
114
+ // Only log top-level keys to avoid exposing sensitive body contents.
115
+ body = { keys: Object.keys(req.body) };
116
+ }
117
+ else if (typeof req.body === 'string') {
118
+ // For string bodies, log only the length.
119
+ body = { length: req.body.length };
151
120
  }
152
- catch (err) {
153
- // eslint-disable-next-line no-console
154
- console.error('[knative-job-fn] Failed to send error callback', err);
121
+ else {
122
+ body = undefined;
155
123
  }
156
- res.status(200).json({ message: error.message });
124
+ logger.info('Incoming job request', {
125
+ method: req.method,
126
+ path: req.originalUrl || req.url,
127
+ headers,
128
+ body
129
+ });
130
+ }
131
+ catch {
132
+ // best-effort logging; never block the request
133
+ }
134
+ next();
135
+ });
136
+ // Echo job headers back on responses for debugging/traceability.
137
+ app.use((req, res, next) => {
138
+ res.set({
139
+ 'Content-Type': 'application/json',
140
+ 'X-Worker-Id': req.get('X-Worker-Id'),
141
+ 'X-Database-Id': req.get('X-Database-Id'),
142
+ 'X-Job-Id': req.get('X-Job-Id')
157
143
  });
158
- app.listen(port, cb);
159
- }
144
+ next();
145
+ });
146
+ // Attach per-request context and a finish hook to send success callbacks.
147
+ app.use((req, res, next) => {
148
+ const ctx = {
149
+ callbackUrl: req.get('X-Callback-Url'),
150
+ workerId: req.get('X-Worker-Id'),
151
+ jobId: req.get('X-Job-Id'),
152
+ databaseId: req.get('X-Database-Id')
153
+ };
154
+ // Store on res.locals so the error middleware can also mark callbacks as sent.
155
+ res.locals = res.locals || {};
156
+ res.locals.jobContext = ctx;
157
+ res.locals.jobCallbackSent = false;
158
+ if (ctx.callbackUrl && ctx.workerId && ctx.jobId) {
159
+ res.on('finish', () => {
160
+ // If an error handler already sent a callback, skip.
161
+ if (res.locals.jobCallbackSent)
162
+ return;
163
+ res.locals.jobCallbackSent = true;
164
+ logger.info('Function completed', {
165
+ workerId: ctx.workerId,
166
+ jobId: ctx.jobId,
167
+ databaseId: ctx.databaseId,
168
+ statusCode: res.statusCode
169
+ });
170
+ void sendJobCallback(ctx, 'success');
171
+ });
172
+ }
173
+ next();
174
+ });
175
+ return {
176
+ post: function (...args) {
177
+ return app.post.apply(app, args);
178
+ },
179
+ listen: (port, hostOrCb, cb = () => { }) => {
180
+ // NOTE Remember that Express middleware executes in order.
181
+ // You should define error handlers last, after all other middleware.
182
+ // Otherwise, your error handler won't get called
183
+ // eslint-disable-next-line no-unused-vars
184
+ app.use(async (error, req, res, next) => {
185
+ res.set({
186
+ 'Content-Type': 'application/json',
187
+ 'X-Job-Error': true
188
+ });
189
+ // Mark job as having errored via callback, if available.
190
+ try {
191
+ const ctx = res.locals?.jobContext;
192
+ if (ctx && !res.locals.jobCallbackSent) {
193
+ res.locals.jobCallbackSent = true;
194
+ await sendJobCallback(ctx, 'error', error?.message);
195
+ }
196
+ }
197
+ catch (err) {
198
+ logger.error('Failed to send error callback', err);
199
+ }
200
+ // Log the full error context for debugging.
201
+ try {
202
+ const headers = getHeaders(req);
203
+ // Some error types (e.g. GraphQL ClientError) expose response info.
204
+ const errorDetails = {
205
+ message: error?.message,
206
+ name: error?.name,
207
+ stack: error?.stack
208
+ };
209
+ if (error?.response) {
210
+ errorDetails.response = {
211
+ status: error.response.status,
212
+ statusText: error.response.statusText,
213
+ errors: error.response.errors,
214
+ data: error.response.data
215
+ };
216
+ }
217
+ logger.error('Function error', {
218
+ headers,
219
+ path: req.originalUrl || req.url,
220
+ error: errorDetails
221
+ });
222
+ }
223
+ catch {
224
+ // never throw from the error logger
225
+ }
226
+ res.status(200).json({ message: error.message });
227
+ });
228
+ const host = typeof hostOrCb === 'string' ? hostOrCb : undefined;
229
+ const callback = typeof hostOrCb === 'function' ? hostOrCb : cb;
230
+ const onListen = () => {
231
+ callback();
232
+ };
233
+ const server = host
234
+ ? app.listen(port, host, onListen)
235
+ : app.listen(port, onListen);
236
+ return server;
237
+ }
238
+ };
160
239
  };
240
+ exports.createJobApp = createJobApp;
241
+ const defaultApp = createJobApp();
242
+ exports.default = defaultApp;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constructive-io/knative-job-fn",
3
- "version": "0.2.6",
3
+ "version": "0.3.0",
4
4
  "description": "knative job fn",
5
5
  "author": "Constructive <developers@constructive.io>",
6
6
  "homepage": "https://github.com/constructive-io/jobs/tree/master/packages/knative-job-fn#readme",
@@ -28,8 +28,9 @@
28
28
  "url": "https://github.com/constructive-io/jobs/issues"
29
29
  },
30
30
  "dependencies": {
31
+ "@pgpmjs/logger": "^1.4.0",
31
32
  "body-parser": "1.19.0",
32
- "express": "4.17.1"
33
+ "express": "5.2.1"
33
34
  },
34
- "gitHead": "fc182ff9e4c4745a3e86eda6d58e3b0061f36564"
35
+ "gitHead": "481b3a50b4eec2da6b376c4cd1868065e1e28edb"
35
36
  }
package/src/index.ts CHANGED
@@ -3,6 +3,8 @@ import bodyParser from 'body-parser';
3
3
  import http from 'node:http';
4
4
  import https from 'node:https';
5
5
  import { URL } from 'node:url';
6
+ import type { Server as HttpServer } from 'http';
7
+ import { createLogger } from '@pgpmjs/logger';
6
8
 
7
9
  type JobCallbackStatus = 'success' | 'error';
8
10
 
@@ -22,51 +24,6 @@ function getHeaders(req: any) {
22
24
  };
23
25
  }
24
26
 
25
- const app: any = express();
26
-
27
- app.use(bodyParser.json());
28
-
29
- // Basic request logging for all incoming job invocations.
30
- app.use((req: any, res: any, next: any) => {
31
- try {
32
- // Log only the headers we care about plus a shallow body snapshot
33
- const headers = getHeaders(req);
34
-
35
- let body: any;
36
- if (req.body && typeof req.body === 'object') {
37
- // Only log top-level keys to avoid exposing sensitive body contents.
38
- body = { keys: Object.keys(req.body) };
39
- } else if (typeof req.body === 'string') {
40
- // For string bodies, log only the length.
41
- body = { length: req.body.length };
42
- } else {
43
- body = undefined;
44
- }
45
-
46
- // eslint-disable-next-line no-console
47
- console.log('[knative-job-fn] Incoming job request', {
48
- method: req.method,
49
- path: req.originalUrl || req.url,
50
- headers,
51
- body
52
- });
53
- } catch {
54
- // best-effort logging; never block the request
55
- }
56
- next();
57
- });
58
-
59
- // Echo job headers back on responses for debugging/traceability.
60
- app.use((req: any, res: any, next: any) => {
61
- res.set({
62
- 'Content-Type': 'application/json',
63
- 'X-Worker-Id': req.get('X-Worker-Id'),
64
- 'X-Database-Id': req.get('X-Database-Id'),
65
- 'X-Job-Id': req.get('X-Job-Id')
66
- });
67
- next();
68
- });
69
-
70
27
  // Normalize callback URL so it always points at the /callback endpoint.
71
28
  const normalizeCallbackUrl = (rawUrl: string): string => {
72
29
  try {
@@ -152,8 +109,7 @@ const sendJobCallback = async (
152
109
  }
153
110
 
154
111
  try {
155
- // eslint-disable-next-line no-console
156
- console.log('[knative-job-fn] Sending job callback', {
112
+ logger.info('Sending job callback', {
157
113
  status,
158
114
  target: normalizeCallbackUrl(callbackUrl),
159
115
  workerId,
@@ -162,8 +118,7 @@ const sendJobCallback = async (
162
118
  });
163
119
  await postJson(target, headers, body);
164
120
  } catch (err) {
165
- // eslint-disable-next-line no-console
166
- console.error('[knative-job-fn] Failed to POST job callback', {
121
+ logger.error('Failed to POST job callback', {
167
122
  target,
168
123
  status,
169
124
  err
@@ -171,98 +126,162 @@ const sendJobCallback = async (
171
126
  }
172
127
  };
173
128
 
174
- // Attach per-request context and a finish hook to send success callbacks.
175
- app.use((req: any, res: any, next: any) => {
176
- const ctx: JobContext = {
177
- callbackUrl: req.get('X-Callback-Url'),
178
- workerId: req.get('X-Worker-Id'),
179
- jobId: req.get('X-Job-Id'),
180
- databaseId: req.get('X-Database-Id')
181
- };
129
+ const logger = createLogger('knative-job-fn');
182
130
 
183
- // Store on res.locals so the error middleware can also mark callbacks as sent.
184
- res.locals = res.locals || {};
185
- res.locals.jobContext = ctx;
186
- res.locals.jobCallbackSent = false;
187
-
188
- if (ctx.callbackUrl && ctx.workerId && ctx.jobId) {
189
- res.on('finish', () => {
190
- // If an error handler already sent a callback, skip.
191
- if (res.locals.jobCallbackSent) return;
192
- res.locals.jobCallbackSent = true;
193
- // eslint-disable-next-line no-console
194
- console.log('[knative-job-fn] Function completed', {
195
- workerId: ctx.workerId,
196
- jobId: ctx.jobId,
197
- databaseId: ctx.databaseId,
198
- statusCode: res.statusCode
131
+ const createJobApp = () => {
132
+ const app: any = express();
133
+
134
+ app.use(bodyParser.json());
135
+
136
+ // Basic request logging for all incoming job invocations.
137
+ app.use((req: any, res: any, next: any) => {
138
+ try {
139
+ // Log only the headers we care about plus a shallow body snapshot
140
+ const headers = getHeaders(req);
141
+
142
+ let body: any;
143
+ if (req.body && typeof req.body === 'object') {
144
+ // Only log top-level keys to avoid exposing sensitive body contents.
145
+ body = { keys: Object.keys(req.body) };
146
+ } else if (typeof req.body === 'string') {
147
+ // For string bodies, log only the length.
148
+ body = { length: req.body.length };
149
+ } else {
150
+ body = undefined;
151
+ }
152
+
153
+ logger.info('Incoming job request', {
154
+ method: req.method,
155
+ path: req.originalUrl || req.url,
156
+ headers,
157
+ body
199
158
  });
200
- void sendJobCallback(ctx, 'success');
159
+ } catch {
160
+ // best-effort logging; never block the request
161
+ }
162
+ next();
163
+ });
164
+
165
+ // Echo job headers back on responses for debugging/traceability.
166
+ app.use((req: any, res: any, next: any) => {
167
+ res.set({
168
+ 'Content-Type': 'application/json',
169
+ 'X-Worker-Id': req.get('X-Worker-Id'),
170
+ 'X-Database-Id': req.get('X-Database-Id'),
171
+ 'X-Job-Id': req.get('X-Job-Id')
201
172
  });
202
- }
173
+ next();
174
+ });
203
175
 
204
- next();
205
- });
206
-
207
- export default {
208
- post: function (...args: any[]) {
209
- return app.post.apply(app, args as any);
210
- },
211
- listen: (port: any, cb: () => void = () => {}) => {
212
- // NOTE Remember that Express middleware executes in order.
213
- // You should define error handlers last, after all other middleware.
214
- // Otherwise, your error handler won't get called
215
- // eslint-disable-next-line no-unused-vars
216
- app.use(async (error: any, req: any, res: any, next: any) => {
217
- res.set({
218
- 'Content-Type': 'application/json',
219
- 'X-Job-Error': true
176
+ // Attach per-request context and a finish hook to send success callbacks.
177
+ app.use((req: any, res: any, next: any) => {
178
+ const ctx: JobContext = {
179
+ callbackUrl: req.get('X-Callback-Url'),
180
+ workerId: req.get('X-Worker-Id'),
181
+ jobId: req.get('X-Job-Id'),
182
+ databaseId: req.get('X-Database-Id')
183
+ };
184
+
185
+ // Store on res.locals so the error middleware can also mark callbacks as sent.
186
+ res.locals = res.locals || {};
187
+ res.locals.jobContext = ctx;
188
+ res.locals.jobCallbackSent = false;
189
+
190
+ if (ctx.callbackUrl && ctx.workerId && ctx.jobId) {
191
+ res.on('finish', () => {
192
+ // If an error handler already sent a callback, skip.
193
+ if (res.locals.jobCallbackSent) return;
194
+ res.locals.jobCallbackSent = true;
195
+ logger.info('Function completed', {
196
+ workerId: ctx.workerId,
197
+ jobId: ctx.jobId,
198
+ databaseId: ctx.databaseId,
199
+ statusCode: res.statusCode
200
+ });
201
+ void sendJobCallback(ctx, 'success');
220
202
  });
203
+ }
204
+
205
+ next();
206
+ });
221
207
 
222
- // Mark job as having errored via callback, if available.
223
- try {
224
- const ctx: JobContext | undefined = res.locals?.jobContext;
225
- if (ctx && !res.locals.jobCallbackSent) {
226
- res.locals.jobCallbackSent = true;
227
- await sendJobCallback(ctx, 'error', error?.message);
208
+ return {
209
+ post: function (...args: any[]) {
210
+ return app.post.apply(app, args as any);
211
+ },
212
+ listen: (
213
+ port: any,
214
+ hostOrCb?: string | (() => void),
215
+ cb: () => void = () => {}
216
+ ): HttpServer => {
217
+ // NOTE Remember that Express middleware executes in order.
218
+ // You should define error handlers last, after all other middleware.
219
+ // Otherwise, your error handler won't get called
220
+ // eslint-disable-next-line no-unused-vars
221
+ app.use(async (error: any, req: any, res: any, next: any) => {
222
+ res.set({
223
+ 'Content-Type': 'application/json',
224
+ 'X-Job-Error': true
225
+ });
226
+
227
+ // Mark job as having errored via callback, if available.
228
+ try {
229
+ const ctx: JobContext | undefined = res.locals?.jobContext;
230
+ if (ctx && !res.locals.jobCallbackSent) {
231
+ res.locals.jobCallbackSent = true;
232
+ await sendJobCallback(ctx, 'error', error?.message);
233
+ }
234
+ } catch (err) {
235
+ logger.error('Failed to send error callback', err);
228
236
  }
229
- } catch (err) {
230
- // eslint-disable-next-line no-console
231
- console.error('[knative-job-fn] Failed to send error callback', err);
232
- }
233
237
 
234
- // Log the full error context for debugging.
235
- try {
236
- const headers = getHeaders(req);
237
-
238
- // Some error types (e.g. GraphQL ClientError) expose response info.
239
- const errorDetails: any = {
240
- message: error?.message,
241
- name: error?.name,
242
- stack: error?.stack
243
- };
244
-
245
- if (error?.response) {
246
- errorDetails.response = {
247
- status: error.response.status,
248
- statusText: error.response.statusText,
249
- errors: error.response.errors,
250
- data: error.response.data
238
+ // Log the full error context for debugging.
239
+ try {
240
+ const headers = getHeaders(req);
241
+
242
+ // Some error types (e.g. GraphQL ClientError) expose response info.
243
+ const errorDetails: any = {
244
+ message: error?.message,
245
+ name: error?.name,
246
+ stack: error?.stack
251
247
  };
248
+
249
+ if (error?.response) {
250
+ errorDetails.response = {
251
+ status: error.response.status,
252
+ statusText: error.response.statusText,
253
+ errors: error.response.errors,
254
+ data: error.response.data
255
+ };
256
+ }
257
+
258
+ logger.error('Function error', {
259
+ headers,
260
+ path: req.originalUrl || req.url,
261
+ error: errorDetails
262
+ });
263
+ } catch {
264
+ // never throw from the error logger
252
265
  }
253
266
 
254
- // eslint-disable-next-line no-console
255
- console.error('[knative-job-fn] Function error', {
256
- headers,
257
- path: req.originalUrl || req.url,
258
- error: errorDetails
259
- });
260
- } catch {
261
- // never throw from the error logger
262
- }
267
+ res.status(200).json({ message: error.message });
268
+ });
263
269
 
264
- res.status(200).json({ message: error.message });
265
- });
266
- app.listen(port, cb);
267
- }
270
+ const host = typeof hostOrCb === 'string' ? hostOrCb : undefined;
271
+ const callback = typeof hostOrCb === 'function' ? hostOrCb : cb;
272
+ const onListen = () => {
273
+ callback();
274
+ };
275
+ const server = host
276
+ ? app.listen(port, host, onListen)
277
+ : app.listen(port, onListen);
278
+
279
+ return server;
280
+ }
281
+ };
268
282
  };
283
+
284
+ const defaultApp = createJobApp();
285
+
286
+ export { createJobApp };
287
+ export default defaultApp;