@critical-path/server 0.13.4 → 0.14.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 +46 -0
- package/README.md +37 -0
- package/dist/adapters/next.d.ts +18 -8
- package/dist/adapters/next.d.ts.map +1 -1
- package/dist/adapters/next.js +11 -4
- package/dist/adapters/next.js.map +1 -1
- package/dist/adapters/sveltekit.d.ts +2 -2
- package/dist/adapters/sveltekit.d.ts.map +1 -1
- package/dist/adapters/sveltekit.js +2 -2
- package/dist/adapters/sveltekit.js.map +1 -1
- package/dist/index.test.js +163 -0
- package/dist/index.test.js.map +1 -1
- package/dist/router.d.ts +18 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +132 -52
- package/dist/router.js.map +1 -1
- package/package.json +2 -2
- package/src/adapters/next.ts +27 -6
- package/src/adapters/sveltekit.ts +6 -3
- package/src/index.test.ts +199 -0
- package/src/router.ts +141 -52
package/src/router.ts
CHANGED
|
@@ -1,14 +1,49 @@
|
|
|
1
1
|
import { CriticalPathEngine, type CriticalPathConfig } from '@critical-path/core';
|
|
2
2
|
|
|
3
|
+
const CORS_HEADERS = {
|
|
4
|
+
'Access-Control-Allow-Origin': '*',
|
|
5
|
+
'Access-Control-Allow-Methods': 'GET, POST, PUT, PATCH, DELETE, OPTIONS',
|
|
6
|
+
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
class BadRequestError extends Error {
|
|
10
|
+
constructor(message: string) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = 'BadRequestError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface CriticalPathRouterOptions {
|
|
17
|
+
/**
|
|
18
|
+
* Called for unexpected errors (those that would produce a 500), e.g. to report to Sentry.
|
|
19
|
+
* Return a `Response` to replace the default 500 response. When omitted, errors are logged
|
|
20
|
+
* with `console.error`.
|
|
21
|
+
*/
|
|
22
|
+
onError?: (error: unknown, request: Request) => void | Response | Promise<void | Response>;
|
|
23
|
+
/**
|
|
24
|
+
* Include the real error message in 500 responses. Defaults to `true` only when
|
|
25
|
+
* `NODE_ENV === 'development'`, so production deployments never leak internals.
|
|
26
|
+
*/
|
|
27
|
+
exposeErrors?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isDevelopment(): boolean {
|
|
31
|
+
return (globalThis as any).process?.env?.NODE_ENV === 'development';
|
|
32
|
+
}
|
|
33
|
+
|
|
3
34
|
export class CriticalPathRouter {
|
|
4
35
|
public engine: CriticalPathEngine;
|
|
36
|
+
private readonly onError?: CriticalPathRouterOptions['onError'];
|
|
37
|
+
private readonly exposeErrors: boolean;
|
|
5
38
|
|
|
6
|
-
constructor(configOrEngine?: CriticalPathConfig | CriticalPathEngine) {
|
|
39
|
+
constructor(configOrEngine?: CriticalPathConfig | CriticalPathEngine, options: CriticalPathRouterOptions = {}) {
|
|
7
40
|
if (configOrEngine instanceof CriticalPathEngine) {
|
|
8
41
|
this.engine = configOrEngine;
|
|
9
42
|
} else {
|
|
10
43
|
this.engine = new CriticalPathEngine(configOrEngine);
|
|
11
44
|
}
|
|
45
|
+
this.onError = options.onError;
|
|
46
|
+
this.exposeErrors = options.exposeErrors ?? isDevelopment();
|
|
12
47
|
}
|
|
13
48
|
|
|
14
49
|
async handleRequest(request: Request): Promise<Response> {
|
|
@@ -20,6 +55,11 @@ export class CriticalPathRouter {
|
|
|
20
55
|
const subpath = pathname.replace(/^.*?\/critical-path\/?/, '').replace(/^\/+/, '');
|
|
21
56
|
const segments = subpath.split('/').filter(Boolean);
|
|
22
57
|
|
|
58
|
+
// CORS preflight
|
|
59
|
+
if (method === 'OPTIONS') {
|
|
60
|
+
return new Response(null, { status: 204, headers: CORS_HEADERS });
|
|
61
|
+
}
|
|
62
|
+
|
|
23
63
|
try {
|
|
24
64
|
// Workflows API
|
|
25
65
|
if (segments[0] === 'workflows') {
|
|
@@ -30,7 +70,7 @@ export class CriticalPathRouter {
|
|
|
30
70
|
return this.jsonResponse({ workflows });
|
|
31
71
|
}
|
|
32
72
|
if (method === 'POST') {
|
|
33
|
-
const body = await
|
|
73
|
+
const body = await this.readJson(request);
|
|
34
74
|
const workflow = await this.engine.createWorkflow(body);
|
|
35
75
|
return this.jsonResponse({ workflow }, 201);
|
|
36
76
|
}
|
|
@@ -41,14 +81,15 @@ export class CriticalPathRouter {
|
|
|
41
81
|
return this.jsonResponse({ workflow });
|
|
42
82
|
}
|
|
43
83
|
if (method === 'PATCH' || method === 'PUT') {
|
|
44
|
-
const body = await
|
|
84
|
+
const body = await this.readJson(request);
|
|
45
85
|
const updated = await this.engine.updateWorkflow(workflowId, body);
|
|
46
86
|
if (!updated) return this.jsonResponse({ error: 'Workflow not found' }, 404);
|
|
47
87
|
return this.jsonResponse({ workflow: updated });
|
|
48
88
|
}
|
|
49
89
|
if (method === 'DELETE') {
|
|
50
90
|
const deleted = await this.engine.deleteWorkflow(workflowId);
|
|
51
|
-
return this.jsonResponse({
|
|
91
|
+
if (!deleted) return this.jsonResponse({ error: 'Not found' }, 404);
|
|
92
|
+
return this.jsonResponse({ success: true });
|
|
52
93
|
}
|
|
53
94
|
}
|
|
54
95
|
}
|
|
@@ -63,7 +104,7 @@ export class CriticalPathRouter {
|
|
|
63
104
|
return this.jsonResponse({ projects });
|
|
64
105
|
}
|
|
65
106
|
if (method === 'POST') {
|
|
66
|
-
const body = await
|
|
107
|
+
const body = await this.readJson(request);
|
|
67
108
|
const project = await this.engine.createProject(body);
|
|
68
109
|
return this.jsonResponse({ project }, 201);
|
|
69
110
|
}
|
|
@@ -107,9 +148,16 @@ export class CriticalPathRouter {
|
|
|
107
148
|
if (!project) return this.jsonResponse({ error: 'Project not found' }, 404);
|
|
108
149
|
return this.jsonResponse({ project });
|
|
109
150
|
}
|
|
151
|
+
if (method === 'PATCH' || method === 'PUT') {
|
|
152
|
+
const { id: _id, createdAt: _createdAt, updatedAt: _updatedAt, ...updates } = await this.readJson(request);
|
|
153
|
+
const project = await this.engine.updateProject(projectId, updates);
|
|
154
|
+
if (!project) return this.jsonResponse({ error: 'Project not found' }, 404);
|
|
155
|
+
return this.jsonResponse({ project });
|
|
156
|
+
}
|
|
110
157
|
if (method === 'DELETE') {
|
|
111
|
-
const deleted = await this.engine.
|
|
112
|
-
return this.jsonResponse({
|
|
158
|
+
const deleted = await this.engine.deleteProject(projectId);
|
|
159
|
+
if (!deleted) return this.jsonResponse({ error: 'Not found' }, 404);
|
|
160
|
+
return this.jsonResponse({ success: true });
|
|
113
161
|
}
|
|
114
162
|
}
|
|
115
163
|
}
|
|
@@ -126,7 +174,7 @@ export class CriticalPathRouter {
|
|
|
126
174
|
return this.jsonResponse({ tasks });
|
|
127
175
|
}
|
|
128
176
|
if (method === 'POST') {
|
|
129
|
-
const body = await
|
|
177
|
+
const body = await this.readJson(request);
|
|
130
178
|
const task = await this.engine.createTask(body);
|
|
131
179
|
return this.jsonResponse({ task }, 201);
|
|
132
180
|
}
|
|
@@ -136,7 +184,7 @@ export class CriticalPathRouter {
|
|
|
136
184
|
return this.jsonResponse({ comments });
|
|
137
185
|
}
|
|
138
186
|
if (method === 'POST') {
|
|
139
|
-
const body = await
|
|
187
|
+
const body = await this.readJson(request);
|
|
140
188
|
const comment = await this.engine.addComment({ ...body, taskId });
|
|
141
189
|
return this.jsonResponse({ comment }, 201);
|
|
142
190
|
}
|
|
@@ -146,7 +194,7 @@ export class CriticalPathRouter {
|
|
|
146
194
|
return this.jsonResponse({ attachments });
|
|
147
195
|
}
|
|
148
196
|
if (method === 'POST') {
|
|
149
|
-
const body = await
|
|
197
|
+
const body = await this.readJson(request);
|
|
150
198
|
const attachment = await this.engine.createAttachment({ ...body, taskId });
|
|
151
199
|
return this.jsonResponse({ attachment }, 201);
|
|
152
200
|
}
|
|
@@ -156,8 +204,11 @@ export class CriticalPathRouter {
|
|
|
156
204
|
return this.jsonResponse({ graph });
|
|
157
205
|
}
|
|
158
206
|
if (method === 'POST') {
|
|
159
|
-
const body = await
|
|
160
|
-
|
|
207
|
+
const body = await this.readJson(request);
|
|
208
|
+
if (typeof body.dependsOnTaskId !== 'string' || !body.dependsOnTaskId) {
|
|
209
|
+
return this.jsonResponse({ error: 'dependsOnTaskId is required' }, 400);
|
|
210
|
+
}
|
|
211
|
+
const dep = await this.engine.addDependency({
|
|
161
212
|
taskId,
|
|
162
213
|
dependsOnTaskId: body.dependsOnTaskId,
|
|
163
214
|
type: body.type || 'blocking'
|
|
@@ -201,14 +252,15 @@ export class CriticalPathRouter {
|
|
|
201
252
|
return this.jsonResponse({ task });
|
|
202
253
|
}
|
|
203
254
|
if (method === 'PATCH' || method === 'PUT') {
|
|
204
|
-
const body = await
|
|
255
|
+
const body = await this.readJson(request);
|
|
205
256
|
const updated = await this.engine.updateTask(taskId, body);
|
|
206
257
|
if (!updated) return this.jsonResponse({ error: 'Task not found' }, 404);
|
|
207
258
|
return this.jsonResponse({ task: updated });
|
|
208
259
|
}
|
|
209
260
|
if (method === 'DELETE') {
|
|
210
261
|
const deleted = await this.engine.deleteTask(taskId);
|
|
211
|
-
return this.jsonResponse({
|
|
262
|
+
if (!deleted) return this.jsonResponse({ error: 'Not found' }, 404);
|
|
263
|
+
return this.jsonResponse({ success: true });
|
|
212
264
|
}
|
|
213
265
|
}
|
|
214
266
|
}
|
|
@@ -222,7 +274,7 @@ export class CriticalPathRouter {
|
|
|
222
274
|
return this.jsonResponse({ teams });
|
|
223
275
|
}
|
|
224
276
|
if (method === 'POST') {
|
|
225
|
-
const body = await
|
|
277
|
+
const body = await this.readJson(request);
|
|
226
278
|
const team = await this.engine.createTeam(body);
|
|
227
279
|
return this.jsonResponse({ team }, 201);
|
|
228
280
|
}
|
|
@@ -233,14 +285,15 @@ export class CriticalPathRouter {
|
|
|
233
285
|
return this.jsonResponse({ team });
|
|
234
286
|
}
|
|
235
287
|
if (method === 'PATCH' || method === 'PUT') {
|
|
236
|
-
const body = await
|
|
288
|
+
const body = await this.readJson(request);
|
|
237
289
|
const updated = await this.engine.updateTeam(teamId, body);
|
|
238
290
|
if (!updated) return this.jsonResponse({ error: 'Team not found' }, 404);
|
|
239
291
|
return this.jsonResponse({ team: updated });
|
|
240
292
|
}
|
|
241
293
|
if (method === 'DELETE') {
|
|
242
294
|
const deleted = await this.engine.deleteTeam(teamId);
|
|
243
|
-
return this.jsonResponse({
|
|
295
|
+
if (!deleted) return this.jsonResponse({ error: 'Not found' }, 404);
|
|
296
|
+
return this.jsonResponse({ success: true });
|
|
244
297
|
}
|
|
245
298
|
}
|
|
246
299
|
}
|
|
@@ -256,7 +309,7 @@ export class CriticalPathRouter {
|
|
|
256
309
|
return this.jsonResponse({ containers });
|
|
257
310
|
}
|
|
258
311
|
if (method === 'POST') {
|
|
259
|
-
const body = await
|
|
312
|
+
const body = await this.readJson(request);
|
|
260
313
|
const container = await this.engine.createContainer(body);
|
|
261
314
|
return this.jsonResponse({ container }, 201);
|
|
262
315
|
}
|
|
@@ -267,14 +320,15 @@ export class CriticalPathRouter {
|
|
|
267
320
|
return this.jsonResponse({ container });
|
|
268
321
|
}
|
|
269
322
|
if (method === 'PATCH' || method === 'PUT') {
|
|
270
|
-
const body = await
|
|
323
|
+
const body = await this.readJson(request);
|
|
271
324
|
const updated = await this.engine.updateContainer(containerId, body);
|
|
272
325
|
if (!updated) return this.jsonResponse({ error: 'Container not found' }, 404);
|
|
273
326
|
return this.jsonResponse({ container: updated });
|
|
274
327
|
}
|
|
275
328
|
if (method === 'DELETE') {
|
|
276
329
|
const deleted = await this.engine.deleteContainer(containerId);
|
|
277
|
-
return this.jsonResponse({
|
|
330
|
+
if (!deleted) return this.jsonResponse({ error: 'Not found' }, 404);
|
|
331
|
+
return this.jsonResponse({ success: true });
|
|
278
332
|
}
|
|
279
333
|
}
|
|
280
334
|
}
|
|
@@ -290,7 +344,7 @@ export class CriticalPathRouter {
|
|
|
290
344
|
return this.jsonResponse({ deliverables });
|
|
291
345
|
}
|
|
292
346
|
if (method === 'POST') {
|
|
293
|
-
const body = await
|
|
347
|
+
const body = await this.readJson(request);
|
|
294
348
|
const deliverable = await this.engine.createDeliverable(body);
|
|
295
349
|
return this.jsonResponse({ deliverable }, 201);
|
|
296
350
|
}
|
|
@@ -307,14 +361,15 @@ export class CriticalPathRouter {
|
|
|
307
361
|
return this.jsonResponse({ deliverable });
|
|
308
362
|
}
|
|
309
363
|
if (method === 'PATCH' || method === 'PUT') {
|
|
310
|
-
const body = await
|
|
364
|
+
const body = await this.readJson(request);
|
|
311
365
|
const updated = await this.engine.updateDeliverable(deliverableId, body);
|
|
312
366
|
if (!updated) return this.jsonResponse({ error: 'Deliverable not found' }, 404);
|
|
313
367
|
return this.jsonResponse({ deliverable: updated });
|
|
314
368
|
}
|
|
315
369
|
if (method === 'DELETE') {
|
|
316
370
|
const deleted = await this.engine.deleteDeliverable(deliverableId);
|
|
317
|
-
return this.jsonResponse({
|
|
371
|
+
if (!deleted) return this.jsonResponse({ error: 'Not found' }, 404);
|
|
372
|
+
return this.jsonResponse({ success: true });
|
|
318
373
|
}
|
|
319
374
|
}
|
|
320
375
|
}
|
|
@@ -330,7 +385,7 @@ export class CriticalPathRouter {
|
|
|
330
385
|
return this.jsonResponse({ iterations });
|
|
331
386
|
}
|
|
332
387
|
if (method === 'POST') {
|
|
333
|
-
const body = await
|
|
388
|
+
const body = await this.readJson(request);
|
|
334
389
|
const iteration = await this.engine.createIteration(body);
|
|
335
390
|
return this.jsonResponse({ iteration }, 201);
|
|
336
391
|
}
|
|
@@ -341,14 +396,15 @@ export class CriticalPathRouter {
|
|
|
341
396
|
return this.jsonResponse({ iteration });
|
|
342
397
|
}
|
|
343
398
|
if (method === 'PATCH' || method === 'PUT') {
|
|
344
|
-
const body = await
|
|
399
|
+
const body = await this.readJson(request);
|
|
345
400
|
const updated = await this.engine.updateIteration(iterationId, body);
|
|
346
401
|
if (!updated) return this.jsonResponse({ error: 'Iteration not found' }, 404);
|
|
347
402
|
return this.jsonResponse({ iteration: updated });
|
|
348
403
|
}
|
|
349
404
|
if (method === 'DELETE') {
|
|
350
405
|
const deleted = await this.engine.deleteIteration(iterationId);
|
|
351
|
-
return this.jsonResponse({
|
|
406
|
+
if (!deleted) return this.jsonResponse({ error: 'Not found' }, 404);
|
|
407
|
+
return this.jsonResponse({ success: true });
|
|
352
408
|
}
|
|
353
409
|
}
|
|
354
410
|
}
|
|
@@ -369,7 +425,7 @@ export class CriticalPathRouter {
|
|
|
369
425
|
const subResource = segments[2];
|
|
370
426
|
if (commentId && subResource === 'reactions') {
|
|
371
427
|
if (method === 'POST') {
|
|
372
|
-
const body = await
|
|
428
|
+
const body = await this.readJson(request);
|
|
373
429
|
if (!body.emoji || !body.userId) {
|
|
374
430
|
return this.jsonResponse({ error: 'emoji and userId are required' }, 400);
|
|
375
431
|
}
|
|
@@ -380,7 +436,7 @@ export class CriticalPathRouter {
|
|
|
380
436
|
if (method === 'DELETE') {
|
|
381
437
|
let body: any = {};
|
|
382
438
|
try {
|
|
383
|
-
body = await
|
|
439
|
+
body = await this.readJson(request);
|
|
384
440
|
} catch {
|
|
385
441
|
// Body may be empty on DELETE, fallback to searchParams
|
|
386
442
|
}
|
|
@@ -401,7 +457,7 @@ export class CriticalPathRouter {
|
|
|
401
457
|
return this.jsonResponse({ comments });
|
|
402
458
|
}
|
|
403
459
|
if (method === 'POST') {
|
|
404
|
-
const body = await
|
|
460
|
+
const body = await this.readJson(request);
|
|
405
461
|
const comment = await this.engine.addComment(body);
|
|
406
462
|
return this.jsonResponse({ comment }, 201);
|
|
407
463
|
}
|
|
@@ -412,14 +468,15 @@ export class CriticalPathRouter {
|
|
|
412
468
|
return this.jsonResponse({ comment });
|
|
413
469
|
}
|
|
414
470
|
if (method === 'PATCH' || method === 'PUT') {
|
|
415
|
-
const body = await
|
|
471
|
+
const body = await this.readJson(request);
|
|
416
472
|
const updated = await this.engine.updateComment(commentId, body);
|
|
417
473
|
if (!updated) return this.jsonResponse({ error: 'Comment not found' }, 404);
|
|
418
474
|
return this.jsonResponse({ comment: updated });
|
|
419
475
|
}
|
|
420
476
|
if (method === 'DELETE') {
|
|
421
477
|
const deleted = await this.engine.deleteComment(commentId);
|
|
422
|
-
return this.jsonResponse({
|
|
478
|
+
if (!deleted) return this.jsonResponse({ error: 'Not found' }, 404);
|
|
479
|
+
return this.jsonResponse({ success: true });
|
|
423
480
|
}
|
|
424
481
|
}
|
|
425
482
|
}
|
|
@@ -429,13 +486,13 @@ export class CriticalPathRouter {
|
|
|
429
486
|
const attachmentId = segments[1];
|
|
430
487
|
if (attachmentId === 'presign') {
|
|
431
488
|
if (method === 'POST') {
|
|
432
|
-
const body = await
|
|
489
|
+
const body = await this.readJson(request);
|
|
433
490
|
const presigned = await this.engine.getPresignedAttachmentUploadUrl(body);
|
|
434
491
|
return this.jsonResponse({ presigned });
|
|
435
492
|
}
|
|
436
493
|
} else if (attachmentId === 'upload') {
|
|
437
494
|
if (method === 'POST') {
|
|
438
|
-
const body = await
|
|
495
|
+
const body = await this.readJson(request);
|
|
439
496
|
const attachment = await this.engine.uploadAttachmentFile(body);
|
|
440
497
|
return this.jsonResponse({ attachment }, 201);
|
|
441
498
|
}
|
|
@@ -448,7 +505,7 @@ export class CriticalPathRouter {
|
|
|
448
505
|
return this.jsonResponse({ attachments });
|
|
449
506
|
}
|
|
450
507
|
if (method === 'POST') {
|
|
451
|
-
const body = await
|
|
508
|
+
const body = await this.readJson(request);
|
|
452
509
|
const attachment = await this.engine.createAttachment(body);
|
|
453
510
|
return this.jsonResponse({ attachment }, 201);
|
|
454
511
|
}
|
|
@@ -460,7 +517,8 @@ export class CriticalPathRouter {
|
|
|
460
517
|
}
|
|
461
518
|
if (method === 'DELETE') {
|
|
462
519
|
const deleted = await this.engine.deleteAttachment(attachmentId);
|
|
463
|
-
return this.jsonResponse({
|
|
520
|
+
if (!deleted) return this.jsonResponse({ error: 'Not found' }, 404);
|
|
521
|
+
return this.jsonResponse({ success: true });
|
|
464
522
|
}
|
|
465
523
|
}
|
|
466
524
|
}
|
|
@@ -474,8 +532,8 @@ export class CriticalPathRouter {
|
|
|
474
532
|
return this.jsonResponse({ timeEntries: entries });
|
|
475
533
|
}
|
|
476
534
|
if (method === 'POST') {
|
|
477
|
-
const body = await
|
|
478
|
-
const entry = await this.engine.
|
|
535
|
+
const body = await this.readJson(request);
|
|
536
|
+
const entry = await this.engine.logTime(body);
|
|
479
537
|
return this.jsonResponse({ timeEntry: entry }, 201);
|
|
480
538
|
}
|
|
481
539
|
}
|
|
@@ -508,7 +566,7 @@ export class CriticalPathRouter {
|
|
|
508
566
|
// Agent Status / Telemetry API
|
|
509
567
|
if (segments[0] === 'status') {
|
|
510
568
|
if (method === 'POST') {
|
|
511
|
-
const body = await
|
|
569
|
+
const body = await this.readJson(request);
|
|
512
570
|
const status = typeof body.status === 'string' ? body.status : 'active';
|
|
513
571
|
if (this.engine.events) {
|
|
514
572
|
this.engine.events.publish({
|
|
@@ -534,19 +592,52 @@ export class CriticalPathRouter {
|
|
|
534
592
|
|
|
535
593
|
return this.jsonResponse({ error: `Route not found: ${method} ${pathname}` }, 404);
|
|
536
594
|
} catch (err: unknown) {
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
595
|
+
return this.errorResponse(err, request);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
private async readJson(request: Request): Promise<any> {
|
|
600
|
+
try {
|
|
601
|
+
return await request.json();
|
|
602
|
+
} catch {
|
|
603
|
+
throw new BadRequestError('Request body must be valid JSON.');
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
private async errorResponse(err: unknown, request: Request): Promise<Response> {
|
|
608
|
+
const name = err && typeof err === 'object' && 'name' in err ? (err as Error).name : undefined;
|
|
609
|
+
const e = err as Record<string, any>;
|
|
610
|
+
|
|
611
|
+
switch (name) {
|
|
612
|
+
case 'BadRequestError':
|
|
613
|
+
case 'ValidationError':
|
|
614
|
+
case 'AttachmentValidationError':
|
|
615
|
+
return this.jsonResponse({ error: e.message }, 400);
|
|
616
|
+
case 'WorkflowValidationError':
|
|
617
|
+
return this.jsonResponse({ error: e.message, fromStatus: e.fromStatus, toStatus: e.toStatus }, 400);
|
|
618
|
+
case 'CustomFieldValidationError':
|
|
619
|
+
return this.jsonResponse({ error: e.message, fieldKey: e.fieldKey }, 400);
|
|
620
|
+
case 'CircularDependencyError':
|
|
621
|
+
return this.jsonResponse({ error: e.message, cyclePath: e.cyclePath }, 409);
|
|
622
|
+
case 'NotFoundError':
|
|
623
|
+
return this.jsonResponse({ error: e.message }, 404);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// Unexpected errors may carry internals (SQL, URLs, config details), so they are only
|
|
627
|
+
// exposed to callers when explicitly enabled.
|
|
628
|
+
if (this.onError) {
|
|
629
|
+
try {
|
|
630
|
+
const custom = await this.onError(err, request);
|
|
631
|
+
if (custom instanceof Response) return custom;
|
|
632
|
+
} catch (hookErr) {
|
|
633
|
+
console.error('[CriticalPathRouter] onError hook threw:', hookErr);
|
|
546
634
|
}
|
|
547
|
-
|
|
548
|
-
|
|
635
|
+
} else {
|
|
636
|
+
console.error('[CriticalPathRouter] Unhandled error:', err);
|
|
549
637
|
}
|
|
638
|
+
|
|
639
|
+
const message = this.exposeErrors && err instanceof Error ? err.message : 'Internal Server Error';
|
|
640
|
+
return this.jsonResponse({ error: message }, 500);
|
|
550
641
|
}
|
|
551
642
|
|
|
552
643
|
private jsonResponse(data: unknown, status = 200): Response {
|
|
@@ -554,9 +645,7 @@ export class CriticalPathRouter {
|
|
|
554
645
|
status,
|
|
555
646
|
headers: {
|
|
556
647
|
'Content-Type': 'application/json',
|
|
557
|
-
|
|
558
|
-
'Access-Control-Allow-Methods': 'GET, POST, PUT, PATCH, DELETE, OPTIONS',
|
|
559
|
-
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
|
|
648
|
+
...CORS_HEADERS
|
|
560
649
|
}
|
|
561
650
|
});
|
|
562
651
|
}
|