@flink-app/flink 2.0.0-alpha.89 → 2.0.0-alpha.91
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 +27 -0
- package/dist/src/FlinkApp.js +18 -8
- package/dist/src/FlinkErrors.d.ts +19 -6
- package/dist/src/FlinkErrors.js +36 -42
- package/dist/src/FlinkResponse.d.ts +6 -0
- package/package.json +1 -1
- package/src/FlinkApp.ts +9 -0
- package/src/FlinkErrors.ts +32 -12
- package/src/FlinkResponse.ts +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @flink-app/flink
|
|
2
2
|
|
|
3
|
+
## 2.0.0-alpha.91
|
|
4
|
+
|
|
5
|
+
## 2.0.0-alpha.90
|
|
6
|
+
|
|
7
|
+
### Minor Changes
|
|
8
|
+
|
|
9
|
+
- 0d84b5f: Add optional `meta` field to error responses for structured context
|
|
10
|
+
|
|
11
|
+
The `error` object in `FlinkResponse` now supports an optional `meta?: unknown`
|
|
12
|
+
field for passing structured payloads alongside an error (e.g. payment decline
|
|
13
|
+
codes, retry hints, gateway metadata). All six error helpers (`notFound`,
|
|
14
|
+
`conflict`, `badRequest`, `unauthorized`, `forbidden`, `internalServerError`)
|
|
15
|
+
accept it as an optional third argument.
|
|
16
|
+
|
|
17
|
+
The value must be JSON-serializable. If serialization fails (e.g. `BigInt`,
|
|
18
|
+
circular references), the framework silently strips `meta` and logs a warning
|
|
19
|
+
before sending the response.
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
return badRequest("Payment declined", "paymentDeclined", {
|
|
23
|
+
gatewayCode: "insufficient_funds",
|
|
24
|
+
attemptId: "att_123",
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Fully additive — existing callers are unaffected.
|
|
29
|
+
|
|
3
30
|
## 2.0.0-alpha.89
|
|
4
31
|
|
|
5
32
|
## 2.0.0-alpha.88
|
package/dist/src/FlinkApp.js
CHANGED
|
@@ -451,16 +451,17 @@ var FlinkApp = /** @class */ (function () {
|
|
|
451
451
|
this.expressApp[method](routeProps.path, function (req, res) { return __awaiter(_this, void 0, void 0, function () {
|
|
452
452
|
var valid, formattedErrors, data, normalizedQuery, _i, _a, _b, key, value, stream, handlerRes, flinkReq_1, err_1, errorResponse, result, detail, valid, formattedErrors;
|
|
453
453
|
var _this = this;
|
|
454
|
-
|
|
455
|
-
|
|
454
|
+
var _c;
|
|
455
|
+
return __generator(this, function (_d) {
|
|
456
|
+
switch (_d.label) {
|
|
456
457
|
case 0:
|
|
457
458
|
if (!routeProps.permissions) return [3 /*break*/, 2];
|
|
458
459
|
return [4 /*yield*/, this.authenticate(req, routeProps.permissions)];
|
|
459
460
|
case 1:
|
|
460
|
-
if (!(
|
|
461
|
+
if (!(_d.sent())) {
|
|
461
462
|
return [2 /*return*/, res.status(401).json((0, FlinkErrors_1.unauthorized)())];
|
|
462
463
|
}
|
|
463
|
-
|
|
464
|
+
_d.label = 2;
|
|
464
465
|
case 2:
|
|
465
466
|
if (validateReq_1) {
|
|
466
467
|
valid = validateReq_1(req.body);
|
|
@@ -507,9 +508,9 @@ var FlinkApp = /** @class */ (function () {
|
|
|
507
508
|
req.query = normalizedQuery;
|
|
508
509
|
}
|
|
509
510
|
stream = streamFormat ? StreamWriterFactory_1.StreamWriterFactory.create(res, streamFormat) : undefined;
|
|
510
|
-
|
|
511
|
+
_d.label = 3;
|
|
511
512
|
case 3:
|
|
512
|
-
|
|
513
|
+
_d.trys.push([3, 5, , 6]);
|
|
513
514
|
flinkReq_1 = req;
|
|
514
515
|
return [4 /*yield*/, FlinkRequestContext_1.requestContext.run({
|
|
515
516
|
reqId: flinkReq_1.reqId,
|
|
@@ -533,10 +534,10 @@ var FlinkApp = /** @class */ (function () {
|
|
|
533
534
|
});
|
|
534
535
|
}); })];
|
|
535
536
|
case 4:
|
|
536
|
-
handlerRes =
|
|
537
|
+
handlerRes = _d.sent();
|
|
537
538
|
return [3 /*break*/, 6];
|
|
538
539
|
case 5:
|
|
539
|
-
err_1 =
|
|
540
|
+
err_1 = _d.sent();
|
|
540
541
|
// Handle errors for streaming handlers
|
|
541
542
|
if (streamFormat && stream) {
|
|
542
543
|
FlinkLog_1.log.error("Streaming handler error on ".concat(req.method.toUpperCase(), " ").concat(req.path, ": ").concat(err_1.message), {
|
|
@@ -629,6 +630,15 @@ var FlinkApp = /** @class */ (function () {
|
|
|
629
630
|
.type(routeProps.responseType)
|
|
630
631
|
.send(handlerRes.data)];
|
|
631
632
|
}
|
|
633
|
+
if (((_c = handlerRes.error) === null || _c === void 0 ? void 0 : _c.meta) !== undefined) {
|
|
634
|
+
try {
|
|
635
|
+
JSON.stringify(handlerRes.error.meta);
|
|
636
|
+
}
|
|
637
|
+
catch (e) {
|
|
638
|
+
FlinkLog_1.log.warn("[".concat(handlerRes.reqId, "] error.meta stripped from error ").concat(handlerRes.error.id, ": not JSON-serializable (").concat(e.message, ")"));
|
|
639
|
+
delete handlerRes.error.meta;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
632
642
|
res.status(handlerRes.status || 200).json(handlerRes);
|
|
633
643
|
return [2 /*return*/];
|
|
634
644
|
}
|
|
@@ -6,36 +6,46 @@ export type FlinkError = undefined;
|
|
|
6
6
|
*
|
|
7
7
|
* @param detail - Optional custom error message
|
|
8
8
|
* @param code - Optional custom error code
|
|
9
|
+
* @param meta - Optional structured payload (must be JSON-serializable)
|
|
9
10
|
* @example
|
|
10
11
|
* ```ts
|
|
11
12
|
* if (!user) return notFound("User not found");
|
|
13
|
+
* if (!user) return notFound("User not found", "userNotFound", { userId });
|
|
12
14
|
* ```
|
|
13
15
|
*/
|
|
14
|
-
export declare function notFound(detail?: string, code?: string): FlinkResponse<FlinkError>;
|
|
16
|
+
export declare function notFound(detail?: string, code?: string, meta?: unknown): FlinkResponse<FlinkError>;
|
|
15
17
|
/**
|
|
16
18
|
* Returns a 409 Conflict error response.
|
|
17
19
|
* Use when a request conflicts with existing data (e.g., duplicate username/email).
|
|
18
20
|
*
|
|
19
21
|
* @param detail - Optional custom error message
|
|
20
22
|
* @param code - Optional custom error code
|
|
23
|
+
* @param meta - Optional structured payload (must be JSON-serializable)
|
|
21
24
|
* @example
|
|
22
25
|
* ```ts
|
|
23
26
|
* if (existingUser) return conflict("Email already registered");
|
|
24
27
|
* ```
|
|
25
28
|
*/
|
|
26
|
-
export declare function conflict(detail?: string, code?: string): FlinkResponse<FlinkError>;
|
|
29
|
+
export declare function conflict(detail?: string, code?: string, meta?: unknown): FlinkResponse<FlinkError>;
|
|
27
30
|
/**
|
|
28
31
|
* Returns a 400 Bad Request error response.
|
|
29
32
|
* Use when the request is malformed or contains invalid data (e.g., validation errors).
|
|
30
33
|
*
|
|
31
34
|
* @param detail - Optional custom error message
|
|
32
35
|
* @param code - Optional custom error code
|
|
36
|
+
* @param meta - Optional structured payload (must be JSON-serializable).
|
|
37
|
+
* Useful for domain-specific context like payment decline codes,
|
|
38
|
+
* retry hints, or structured field errors.
|
|
33
39
|
* @example
|
|
34
40
|
* ```ts
|
|
35
41
|
* if (!email || !password) return badRequest("Email and password are required");
|
|
42
|
+
* return badRequest("Payment declined", "paymentDeclined", {
|
|
43
|
+
* gatewayCode: "insufficient_funds",
|
|
44
|
+
* attemptId: "att_123",
|
|
45
|
+
* });
|
|
36
46
|
* ```
|
|
37
47
|
*/
|
|
38
|
-
export declare function badRequest(detail?: string, code?: string): FlinkResponse<FlinkError>;
|
|
48
|
+
export declare function badRequest(detail?: string, code?: string, meta?: unknown): FlinkResponse<FlinkError>;
|
|
39
49
|
/**
|
|
40
50
|
* Returns a 401 Unauthorized error response.
|
|
41
51
|
* Use when authentication is required but missing or invalid (e.g., no token, expired token).
|
|
@@ -43,12 +53,13 @@ export declare function badRequest(detail?: string, code?: string): FlinkRespons
|
|
|
43
53
|
*
|
|
44
54
|
* @param detail - Optional custom error message
|
|
45
55
|
* @param code - Optional custom error code
|
|
56
|
+
* @param meta - Optional structured payload (must be JSON-serializable)
|
|
46
57
|
* @example
|
|
47
58
|
* ```ts
|
|
48
59
|
* if (!ctx.auth?.user) return unauthorized("Authentication required");
|
|
49
60
|
* ```
|
|
50
61
|
*/
|
|
51
|
-
export declare function unauthorized(detail?: string, code?: string): FlinkResponse<FlinkError>;
|
|
62
|
+
export declare function unauthorized(detail?: string, code?: string, meta?: unknown): FlinkResponse<FlinkError>;
|
|
52
63
|
/**
|
|
53
64
|
* Returns a 403 Forbidden error response.
|
|
54
65
|
* Use when the user is authenticated but lacks permission to access the resource.
|
|
@@ -56,21 +67,23 @@ export declare function unauthorized(detail?: string, code?: string): FlinkRespo
|
|
|
56
67
|
*
|
|
57
68
|
* @param detail - Optional custom error message
|
|
58
69
|
* @param code - Optional custom error code
|
|
70
|
+
* @param meta - Optional structured payload (must be JSON-serializable)
|
|
59
71
|
* @example
|
|
60
72
|
* ```ts
|
|
61
73
|
* if (ctx.auth?.user?.role !== "admin") return forbidden("Admin access required");
|
|
62
74
|
* ```
|
|
63
75
|
*/
|
|
64
|
-
export declare function forbidden(detail?: string, code?: string): FlinkResponse<FlinkError>;
|
|
76
|
+
export declare function forbidden(detail?: string, code?: string, meta?: unknown): FlinkResponse<FlinkError>;
|
|
65
77
|
/**
|
|
66
78
|
* Returns a 500 Internal Server Error response.
|
|
67
79
|
* Use when an unexpected error occurs on the server side.
|
|
68
80
|
*
|
|
69
81
|
* @param detail - Optional custom error message
|
|
70
82
|
* @param code - Optional custom error code
|
|
83
|
+
* @param meta - Optional structured payload (must be JSON-serializable)
|
|
71
84
|
* @example
|
|
72
85
|
* ```ts
|
|
73
86
|
* try { ... } catch (error) { return internalServerError("Failed to process request"); }
|
|
74
87
|
* ```
|
|
75
88
|
*/
|
|
76
|
-
export declare function internalServerError(detail?: string, code?: string): FlinkResponse<FlinkError>;
|
|
89
|
+
export declare function internalServerError(detail?: string, code?: string, meta?: unknown): FlinkResponse<FlinkError>;
|
package/dist/src/FlinkErrors.js
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __assign = (this && this.__assign) || function () {
|
|
3
|
+
__assign = Object.assign || function(t) {
|
|
4
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
+
s = arguments[i];
|
|
6
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
+
t[p] = s[p];
|
|
8
|
+
}
|
|
9
|
+
return t;
|
|
10
|
+
};
|
|
11
|
+
return __assign.apply(this, arguments);
|
|
12
|
+
};
|
|
2
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
14
|
exports.notFound = notFound;
|
|
4
15
|
exports.conflict = conflict;
|
|
@@ -13,20 +24,17 @@ var uuid_1 = require("uuid");
|
|
|
13
24
|
*
|
|
14
25
|
* @param detail - Optional custom error message
|
|
15
26
|
* @param code - Optional custom error code
|
|
27
|
+
* @param meta - Optional structured payload (must be JSON-serializable)
|
|
16
28
|
* @example
|
|
17
29
|
* ```ts
|
|
18
30
|
* if (!user) return notFound("User not found");
|
|
31
|
+
* if (!user) return notFound("User not found", "userNotFound", { userId });
|
|
19
32
|
* ```
|
|
20
33
|
*/
|
|
21
|
-
function notFound(detail, code) {
|
|
34
|
+
function notFound(detail, code, meta) {
|
|
22
35
|
return {
|
|
23
36
|
status: 404,
|
|
24
|
-
error: {
|
|
25
|
-
id: (0, uuid_1.v4)(),
|
|
26
|
-
title: "Not Found",
|
|
27
|
-
detail: detail || "The requested resource does not exist",
|
|
28
|
-
code: code || "notFound"
|
|
29
|
-
},
|
|
37
|
+
error: __assign({ id: (0, uuid_1.v4)(), title: "Not Found", detail: detail || "The requested resource does not exist", code: code || "notFound" }, (meta !== undefined && { meta: meta })),
|
|
30
38
|
};
|
|
31
39
|
}
|
|
32
40
|
/**
|
|
@@ -35,20 +43,16 @@ function notFound(detail, code) {
|
|
|
35
43
|
*
|
|
36
44
|
* @param detail - Optional custom error message
|
|
37
45
|
* @param code - Optional custom error code
|
|
46
|
+
* @param meta - Optional structured payload (must be JSON-serializable)
|
|
38
47
|
* @example
|
|
39
48
|
* ```ts
|
|
40
49
|
* if (existingUser) return conflict("Email already registered");
|
|
41
50
|
* ```
|
|
42
51
|
*/
|
|
43
|
-
function conflict(detail, code) {
|
|
52
|
+
function conflict(detail, code, meta) {
|
|
44
53
|
return {
|
|
45
54
|
status: 409,
|
|
46
|
-
error: {
|
|
47
|
-
id: (0, uuid_1.v4)(),
|
|
48
|
-
title: "Conflict",
|
|
49
|
-
detail: detail || "An identical entity exits",
|
|
50
|
-
code: code || "conflict"
|
|
51
|
-
},
|
|
55
|
+
error: __assign({ id: (0, uuid_1.v4)(), title: "Conflict", detail: detail || "An identical entity exits", code: code || "conflict" }, (meta !== undefined && { meta: meta })),
|
|
52
56
|
};
|
|
53
57
|
}
|
|
54
58
|
/**
|
|
@@ -57,20 +61,22 @@ function conflict(detail, code) {
|
|
|
57
61
|
*
|
|
58
62
|
* @param detail - Optional custom error message
|
|
59
63
|
* @param code - Optional custom error code
|
|
64
|
+
* @param meta - Optional structured payload (must be JSON-serializable).
|
|
65
|
+
* Useful for domain-specific context like payment decline codes,
|
|
66
|
+
* retry hints, or structured field errors.
|
|
60
67
|
* @example
|
|
61
68
|
* ```ts
|
|
62
69
|
* if (!email || !password) return badRequest("Email and password are required");
|
|
70
|
+
* return badRequest("Payment declined", "paymentDeclined", {
|
|
71
|
+
* gatewayCode: "insufficient_funds",
|
|
72
|
+
* attemptId: "att_123",
|
|
73
|
+
* });
|
|
63
74
|
* ```
|
|
64
75
|
*/
|
|
65
|
-
function badRequest(detail, code) {
|
|
76
|
+
function badRequest(detail, code, meta) {
|
|
66
77
|
return {
|
|
67
78
|
status: 400,
|
|
68
|
-
error: {
|
|
69
|
-
id: (0, uuid_1.v4)(),
|
|
70
|
-
title: "Bad Request",
|
|
71
|
-
detail: detail || "Invalid request",
|
|
72
|
-
code: code || "badRequest"
|
|
73
|
-
},
|
|
79
|
+
error: __assign({ id: (0, uuid_1.v4)(), title: "Bad Request", detail: detail || "Invalid request", code: code || "badRequest" }, (meta !== undefined && { meta: meta })),
|
|
74
80
|
};
|
|
75
81
|
}
|
|
76
82
|
/**
|
|
@@ -80,20 +86,16 @@ function badRequest(detail, code) {
|
|
|
80
86
|
*
|
|
81
87
|
* @param detail - Optional custom error message
|
|
82
88
|
* @param code - Optional custom error code
|
|
89
|
+
* @param meta - Optional structured payload (must be JSON-serializable)
|
|
83
90
|
* @example
|
|
84
91
|
* ```ts
|
|
85
92
|
* if (!ctx.auth?.user) return unauthorized("Authentication required");
|
|
86
93
|
* ```
|
|
87
94
|
*/
|
|
88
|
-
function unauthorized(detail, code) {
|
|
95
|
+
function unauthorized(detail, code, meta) {
|
|
89
96
|
return {
|
|
90
97
|
status: 401,
|
|
91
|
-
error: {
|
|
92
|
-
id: (0, uuid_1.v4)(),
|
|
93
|
-
title: "Unauthorized",
|
|
94
|
-
detail: detail || "Authentication required",
|
|
95
|
-
code: code || "unauthorized"
|
|
96
|
-
},
|
|
98
|
+
error: __assign({ id: (0, uuid_1.v4)(), title: "Unauthorized", detail: detail || "Authentication required", code: code || "unauthorized" }, (meta !== undefined && { meta: meta })),
|
|
97
99
|
};
|
|
98
100
|
}
|
|
99
101
|
/**
|
|
@@ -103,20 +105,16 @@ function unauthorized(detail, code) {
|
|
|
103
105
|
*
|
|
104
106
|
* @param detail - Optional custom error message
|
|
105
107
|
* @param code - Optional custom error code
|
|
108
|
+
* @param meta - Optional structured payload (must be JSON-serializable)
|
|
106
109
|
* @example
|
|
107
110
|
* ```ts
|
|
108
111
|
* if (ctx.auth?.user?.role !== "admin") return forbidden("Admin access required");
|
|
109
112
|
* ```
|
|
110
113
|
*/
|
|
111
|
-
function forbidden(detail, code) {
|
|
114
|
+
function forbidden(detail, code, meta) {
|
|
112
115
|
return {
|
|
113
116
|
status: 403,
|
|
114
|
-
error: {
|
|
115
|
-
id: (0, uuid_1.v4)(),
|
|
116
|
-
title: "Forbidden",
|
|
117
|
-
detail: detail || "You do not have permission to access this resource",
|
|
118
|
-
code: code || "forbidden"
|
|
119
|
-
},
|
|
117
|
+
error: __assign({ id: (0, uuid_1.v4)(), title: "Forbidden", detail: detail || "You do not have permission to access this resource", code: code || "forbidden" }, (meta !== undefined && { meta: meta })),
|
|
120
118
|
};
|
|
121
119
|
}
|
|
122
120
|
/**
|
|
@@ -125,19 +123,15 @@ function forbidden(detail, code) {
|
|
|
125
123
|
*
|
|
126
124
|
* @param detail - Optional custom error message
|
|
127
125
|
* @param code - Optional custom error code
|
|
126
|
+
* @param meta - Optional structured payload (must be JSON-serializable)
|
|
128
127
|
* @example
|
|
129
128
|
* ```ts
|
|
130
129
|
* try { ... } catch (error) { return internalServerError("Failed to process request"); }
|
|
131
130
|
* ```
|
|
132
131
|
*/
|
|
133
|
-
function internalServerError(detail, code) {
|
|
132
|
+
function internalServerError(detail, code, meta) {
|
|
134
133
|
return {
|
|
135
134
|
status: 500,
|
|
136
|
-
error: {
|
|
137
|
-
id: (0, uuid_1.v4)(),
|
|
138
|
-
title: "Internal Server Error",
|
|
139
|
-
detail: detail || "Something unexpected went wrong",
|
|
140
|
-
code: code || "internalServerError"
|
|
141
|
-
},
|
|
135
|
+
error: __assign({ id: (0, uuid_1.v4)(), title: "Internal Server Error", detail: detail || "Something unexpected went wrong", code: code || "internalServerError" }, (meta !== undefined && { meta: meta })),
|
|
142
136
|
};
|
|
143
137
|
}
|
|
@@ -28,6 +28,12 @@ export interface FlinkResponse<T = any> {
|
|
|
28
28
|
title: string;
|
|
29
29
|
detail?: string;
|
|
30
30
|
code?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Optional structured payload with additional error context.
|
|
33
|
+
* Must be JSON-serializable; non-serializable values are stripped
|
|
34
|
+
* with a warning before sending the response.
|
|
35
|
+
*/
|
|
36
|
+
meta?: unknown;
|
|
31
37
|
};
|
|
32
38
|
/**
|
|
33
39
|
* HTTP headers, names are lower cased
|
package/package.json
CHANGED
package/src/FlinkApp.ts
CHANGED
|
@@ -865,6 +865,15 @@ export class FlinkApp<C extends FlinkContext> {
|
|
|
865
865
|
.send(handlerRes.data);
|
|
866
866
|
}
|
|
867
867
|
|
|
868
|
+
if (handlerRes.error?.meta !== undefined) {
|
|
869
|
+
try {
|
|
870
|
+
JSON.stringify(handlerRes.error.meta);
|
|
871
|
+
} catch (e) {
|
|
872
|
+
log.warn(`[${handlerRes.reqId}] error.meta stripped from error ${handlerRes.error.id}: not JSON-serializable (${(e as Error).message})`);
|
|
873
|
+
delete handlerRes.error.meta;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
868
877
|
res.status(handlerRes.status || 200).json(handlerRes);
|
|
869
878
|
});
|
|
870
879
|
|
package/src/FlinkErrors.ts
CHANGED
|
@@ -10,19 +10,22 @@ export type FlinkError = undefined;
|
|
|
10
10
|
*
|
|
11
11
|
* @param detail - Optional custom error message
|
|
12
12
|
* @param code - Optional custom error code
|
|
13
|
+
* @param meta - Optional structured payload (must be JSON-serializable)
|
|
13
14
|
* @example
|
|
14
15
|
* ```ts
|
|
15
16
|
* if (!user) return notFound("User not found");
|
|
17
|
+
* if (!user) return notFound("User not found", "userNotFound", { userId });
|
|
16
18
|
* ```
|
|
17
19
|
*/
|
|
18
|
-
export function notFound(detail?: string, code?: string ): FlinkResponse<FlinkError> {
|
|
20
|
+
export function notFound(detail?: string, code?: string, meta?: unknown): FlinkResponse<FlinkError> {
|
|
19
21
|
return {
|
|
20
22
|
status: 404,
|
|
21
23
|
error: {
|
|
22
24
|
id: v4(),
|
|
23
25
|
title: "Not Found",
|
|
24
26
|
detail: detail || "The requested resource does not exist",
|
|
25
|
-
code : code || "notFound"
|
|
27
|
+
code : code || "notFound",
|
|
28
|
+
...(meta !== undefined && { meta }),
|
|
26
29
|
},
|
|
27
30
|
};
|
|
28
31
|
}
|
|
@@ -33,19 +36,21 @@ export function notFound(detail?: string, code?: string ): FlinkResponse<FlinkEr
|
|
|
33
36
|
*
|
|
34
37
|
* @param detail - Optional custom error message
|
|
35
38
|
* @param code - Optional custom error code
|
|
39
|
+
* @param meta - Optional structured payload (must be JSON-serializable)
|
|
36
40
|
* @example
|
|
37
41
|
* ```ts
|
|
38
42
|
* if (existingUser) return conflict("Email already registered");
|
|
39
43
|
* ```
|
|
40
44
|
*/
|
|
41
|
-
export function conflict(detail?: string, code?: string ): FlinkResponse<FlinkError> {
|
|
45
|
+
export function conflict(detail?: string, code?: string, meta?: unknown): FlinkResponse<FlinkError> {
|
|
42
46
|
return {
|
|
43
47
|
status: 409,
|
|
44
48
|
error: {
|
|
45
49
|
id: v4(),
|
|
46
50
|
title: "Conflict",
|
|
47
51
|
detail: detail || "An identical entity exits",
|
|
48
|
-
code : code || "conflict"
|
|
52
|
+
code : code || "conflict",
|
|
53
|
+
...(meta !== undefined && { meta }),
|
|
49
54
|
},
|
|
50
55
|
};
|
|
51
56
|
}
|
|
@@ -58,19 +63,27 @@ export function conflict(detail?: string, code?: string ): FlinkResponse<FlinkEr
|
|
|
58
63
|
*
|
|
59
64
|
* @param detail - Optional custom error message
|
|
60
65
|
* @param code - Optional custom error code
|
|
66
|
+
* @param meta - Optional structured payload (must be JSON-serializable).
|
|
67
|
+
* Useful for domain-specific context like payment decline codes,
|
|
68
|
+
* retry hints, or structured field errors.
|
|
61
69
|
* @example
|
|
62
70
|
* ```ts
|
|
63
71
|
* if (!email || !password) return badRequest("Email and password are required");
|
|
72
|
+
* return badRequest("Payment declined", "paymentDeclined", {
|
|
73
|
+
* gatewayCode: "insufficient_funds",
|
|
74
|
+
* attemptId: "att_123",
|
|
75
|
+
* });
|
|
64
76
|
* ```
|
|
65
77
|
*/
|
|
66
|
-
export function badRequest(detail?: string, code?: string): FlinkResponse<FlinkError> {
|
|
78
|
+
export function badRequest(detail?: string, code?: string, meta?: unknown): FlinkResponse<FlinkError> {
|
|
67
79
|
return {
|
|
68
80
|
status: 400,
|
|
69
81
|
error: {
|
|
70
82
|
id: v4(),
|
|
71
83
|
title: "Bad Request",
|
|
72
84
|
detail: detail || "Invalid request",
|
|
73
|
-
code : code || "badRequest"
|
|
85
|
+
code : code || "badRequest",
|
|
86
|
+
...(meta !== undefined && { meta }),
|
|
74
87
|
},
|
|
75
88
|
};
|
|
76
89
|
}
|
|
@@ -82,12 +95,13 @@ export function badRequest(detail?: string, code?: string): FlinkResponse<FlinkE
|
|
|
82
95
|
*
|
|
83
96
|
* @param detail - Optional custom error message
|
|
84
97
|
* @param code - Optional custom error code
|
|
98
|
+
* @param meta - Optional structured payload (must be JSON-serializable)
|
|
85
99
|
* @example
|
|
86
100
|
* ```ts
|
|
87
101
|
* if (!ctx.auth?.user) return unauthorized("Authentication required");
|
|
88
102
|
* ```
|
|
89
103
|
*/
|
|
90
|
-
export function unauthorized(detail?: string, code?: string): FlinkResponse<FlinkError> {
|
|
104
|
+
export function unauthorized(detail?: string, code?: string, meta?: unknown): FlinkResponse<FlinkError> {
|
|
91
105
|
return {
|
|
92
106
|
status: 401,
|
|
93
107
|
error: {
|
|
@@ -95,7 +109,8 @@ export function unauthorized(detail?: string, code?: string): FlinkResponse<Flin
|
|
|
95
109
|
title: "Unauthorized",
|
|
96
110
|
detail:
|
|
97
111
|
detail || "Authentication required",
|
|
98
|
-
code : code || "unauthorized"
|
|
112
|
+
code : code || "unauthorized",
|
|
113
|
+
...(meta !== undefined && { meta }),
|
|
99
114
|
},
|
|
100
115
|
};
|
|
101
116
|
}
|
|
@@ -107,19 +122,21 @@ export function unauthorized(detail?: string, code?: string): FlinkResponse<Flin
|
|
|
107
122
|
*
|
|
108
123
|
* @param detail - Optional custom error message
|
|
109
124
|
* @param code - Optional custom error code
|
|
125
|
+
* @param meta - Optional structured payload (must be JSON-serializable)
|
|
110
126
|
* @example
|
|
111
127
|
* ```ts
|
|
112
128
|
* if (ctx.auth?.user?.role !== "admin") return forbidden("Admin access required");
|
|
113
129
|
* ```
|
|
114
130
|
*/
|
|
115
|
-
export function forbidden(detail?: string, code?: string): FlinkResponse<FlinkError> {
|
|
131
|
+
export function forbidden(detail?: string, code?: string, meta?: unknown): FlinkResponse<FlinkError> {
|
|
116
132
|
return {
|
|
117
133
|
status: 403,
|
|
118
134
|
error: {
|
|
119
135
|
id: v4(),
|
|
120
136
|
title: "Forbidden",
|
|
121
137
|
detail: detail || "You do not have permission to access this resource",
|
|
122
|
-
code : code || "forbidden"
|
|
138
|
+
code : code || "forbidden",
|
|
139
|
+
...(meta !== undefined && { meta }),
|
|
123
140
|
},
|
|
124
141
|
};
|
|
125
142
|
}
|
|
@@ -130,6 +147,7 @@ export function forbidden(detail?: string, code?: string): FlinkResponse<FlinkEr
|
|
|
130
147
|
*
|
|
131
148
|
* @param detail - Optional custom error message
|
|
132
149
|
* @param code - Optional custom error code
|
|
150
|
+
* @param meta - Optional structured payload (must be JSON-serializable)
|
|
133
151
|
* @example
|
|
134
152
|
* ```ts
|
|
135
153
|
* try { ... } catch (error) { return internalServerError("Failed to process request"); }
|
|
@@ -137,7 +155,8 @@ export function forbidden(detail?: string, code?: string): FlinkResponse<FlinkEr
|
|
|
137
155
|
*/
|
|
138
156
|
export function internalServerError(
|
|
139
157
|
detail?: string,
|
|
140
|
-
code?: string
|
|
158
|
+
code?: string,
|
|
159
|
+
meta?: unknown
|
|
141
160
|
): FlinkResponse<FlinkError> {
|
|
142
161
|
return {
|
|
143
162
|
status: 500,
|
|
@@ -145,7 +164,8 @@ export function internalServerError(
|
|
|
145
164
|
id: v4(),
|
|
146
165
|
title: "Internal Server Error",
|
|
147
166
|
detail: detail || "Something unexpected went wrong",
|
|
148
|
-
code : code || "internalServerError"
|
|
167
|
+
code : code || "internalServerError",
|
|
168
|
+
...(meta !== undefined && { meta }),
|
|
149
169
|
},
|
|
150
170
|
};
|
|
151
171
|
}
|
package/src/FlinkResponse.ts
CHANGED
|
@@ -34,6 +34,12 @@ export interface FlinkResponse<T = any> {
|
|
|
34
34
|
title: string;
|
|
35
35
|
detail?: string;
|
|
36
36
|
code?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Optional structured payload with additional error context.
|
|
39
|
+
* Must be JSON-serializable; non-serializable values are stripped
|
|
40
|
+
* with a warning before sending the response.
|
|
41
|
+
*/
|
|
42
|
+
meta?: unknown;
|
|
37
43
|
};
|
|
38
44
|
|
|
39
45
|
/**
|