@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 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
@@ -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
- return __generator(this, function (_c) {
455
- switch (_c.label) {
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 (!(_c.sent())) {
461
+ if (!(_d.sent())) {
461
462
  return [2 /*return*/, res.status(401).json((0, FlinkErrors_1.unauthorized)())];
462
463
  }
463
- _c.label = 2;
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
- _c.label = 3;
511
+ _d.label = 3;
511
512
  case 3:
512
- _c.trys.push([3, 5, , 6]);
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 = _c.sent();
537
+ handlerRes = _d.sent();
537
538
  return [3 /*break*/, 6];
538
539
  case 5:
539
- err_1 = _c.sent();
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>;
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flink-app/flink",
3
- "version": "2.0.0-alpha.89",
3
+ "version": "2.0.0-alpha.91",
4
4
  "description": "Typescript only framework for creating REST-like APIs on top of Express and mongodb",
5
5
  "types": "dist/src/index.d.ts",
6
6
  "main": "dist/src/index.js",
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
 
@@ -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
  }
@@ -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
  /**