@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 +12 -0
- package/dist/index.d.ts +9 -3
- package/dist/index.js +148 -66
- package/package.json +4 -3
- package/src/index.ts +150 -131
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
|
-
|
|
1
|
+
import type { Server as HttpServer } from 'http';
|
|
2
|
+
declare const createJobApp: () => {
|
|
2
3
|
post: (...args: any[]) => any;
|
|
3
|
-
listen: (port: any, cb?: () => void) =>
|
|
4
|
+
listen: (port: any, hostOrCb?: string | (() => void), cb?: () => void) => HttpServer;
|
|
4
5
|
};
|
|
5
|
-
|
|
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
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
'
|
|
17
|
-
'
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
console.error('[knative-job-fn] Failed to send error callback', err);
|
|
121
|
+
else {
|
|
122
|
+
body = undefined;
|
|
155
123
|
}
|
|
156
|
-
|
|
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
|
-
|
|
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.
|
|
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": "
|
|
33
|
+
"express": "5.2.1"
|
|
33
34
|
},
|
|
34
|
-
"gitHead": "
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
|
|
255
|
-
|
|
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
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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;
|