@pafi-dev/issuer 0.8.0 → 0.9.1
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/dist/chunk-R4FYJZ2N.js +1 -0
- package/dist/chunk-R4FYJZ2N.js.map +1 -0
- package/dist/chunk-U3WMORJG.js +230 -0
- package/dist/chunk-U3WMORJG.js.map +1 -0
- package/dist/http/index.cjs +169 -0
- package/dist/http/index.cjs.map +1 -0
- package/dist/http/index.d.cts +112 -0
- package/dist/http/index.d.ts +112 -0
- package/dist/http/index.js +14 -0
- package/dist/http/index.js.map +1 -0
- package/dist/index.cjs +161 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -5
- package/dist/index.d.ts +35 -5
- package/dist/index.js +21 -41
- package/dist/index.js.map +1 -1
- package/dist/nestjs/index.cjs +314 -0
- package/dist/nestjs/index.cjs.map +1 -0
- package/dist/nestjs/index.d.cts +54 -0
- package/dist/nestjs/index.d.ts +54 -0
- package/dist/nestjs/index.js +124 -0
- package/dist/nestjs/index.js.map +1 -0
- package/package.json +39 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=chunk-R4FYJZ2N.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
|
|
5
|
+
var __typeError = (msg) => {
|
|
6
|
+
throw TypeError(msg);
|
|
7
|
+
};
|
|
8
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
9
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
10
|
+
var __decoratorStart = (base) => [, , , __create(base?.[__knownSymbol("metadata")] ?? null)];
|
|
11
|
+
var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
|
|
12
|
+
var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
|
|
13
|
+
var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) });
|
|
14
|
+
var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
|
|
15
|
+
var __runInitializers = (array, flags, self, value) => {
|
|
16
|
+
for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
|
|
17
|
+
return value;
|
|
18
|
+
};
|
|
19
|
+
var __decorateElement = (array, flags, name, decorators, target, extra) => {
|
|
20
|
+
var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
|
|
21
|
+
var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
|
|
22
|
+
var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
|
|
23
|
+
var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : { get [name]() {
|
|
24
|
+
return __privateGet(this, extra);
|
|
25
|
+
}, set [name](x) {
|
|
26
|
+
return __privateSet(this, extra, x);
|
|
27
|
+
} }, name));
|
|
28
|
+
k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
|
|
29
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
30
|
+
ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
|
|
31
|
+
if (k) {
|
|
32
|
+
ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => name in x };
|
|
33
|
+
if (k ^ 3) access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
|
|
34
|
+
if (k > 2) access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
|
|
35
|
+
}
|
|
36
|
+
it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? void 0 : { get: desc.get, set: desc.set } : target, ctx), done._ = 1;
|
|
37
|
+
if (k ^ 4 || it === void 0) __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
|
|
38
|
+
else if (typeof it !== "object" || it === null) __typeError("Object expected");
|
|
39
|
+
else __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
|
|
40
|
+
}
|
|
41
|
+
return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
|
|
42
|
+
};
|
|
43
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
44
|
+
var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
|
|
45
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
46
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
47
|
+
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
48
|
+
|
|
49
|
+
// src/errors.ts
|
|
50
|
+
import {
|
|
51
|
+
PafiSdkError,
|
|
52
|
+
SDK_ERROR_HTTP_STATUS_CODE,
|
|
53
|
+
defaultErrorTypeForStatus
|
|
54
|
+
} from "@pafi-dev/core";
|
|
55
|
+
import { PafiSdkError as PafiSdkError2 } from "@pafi-dev/core";
|
|
56
|
+
import { ValidationError } from "@pafi-dev/core";
|
|
57
|
+
var ConfigurationError = class extends PafiSdkError2 {
|
|
58
|
+
httpStatus = "service_unavailable";
|
|
59
|
+
code;
|
|
60
|
+
details;
|
|
61
|
+
constructor(code, message, details) {
|
|
62
|
+
super(message);
|
|
63
|
+
this.code = code;
|
|
64
|
+
this.details = details;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// src/api/errorMapper.ts
|
|
69
|
+
function buildSdkErrorBody(err) {
|
|
70
|
+
const type = err.type ?? defaultErrorTypeForStatus(SDK_ERROR_HTTP_STATUS_CODE[err.httpStatus]);
|
|
71
|
+
const body = {
|
|
72
|
+
type,
|
|
73
|
+
code: err.code,
|
|
74
|
+
message: err.message,
|
|
75
|
+
safeToRetry: err.safeToRetry
|
|
76
|
+
};
|
|
77
|
+
if (err.param) body.param = err.param;
|
|
78
|
+
if (err.metadata) body.metadata = err.metadata;
|
|
79
|
+
if (err.details !== void 0) body.details = err.details;
|
|
80
|
+
return body;
|
|
81
|
+
}
|
|
82
|
+
function createSdkErrorMapper(factories) {
|
|
83
|
+
return (err) => {
|
|
84
|
+
if (!(err instanceof PafiSdkError)) {
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
87
|
+
const body = buildSdkErrorBody(err);
|
|
88
|
+
switch (err.httpStatus) {
|
|
89
|
+
case "not_found":
|
|
90
|
+
throw factories.notFound(body);
|
|
91
|
+
case "forbidden":
|
|
92
|
+
throw factories.forbidden(body);
|
|
93
|
+
case "unprocessable":
|
|
94
|
+
throw factories.unprocessable(body);
|
|
95
|
+
case "service_unavailable":
|
|
96
|
+
throw factories.serviceUnavailable(body);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/http/errorEnvelope.ts
|
|
102
|
+
function isValidationPipeBody(body) {
|
|
103
|
+
return Array.isArray(body["message"]) && typeof body["error"] === "string" && body["error"] === "Bad Request";
|
|
104
|
+
}
|
|
105
|
+
function extractParamFromValidatorMessage(message) {
|
|
106
|
+
const idx = message.indexOf(" ");
|
|
107
|
+
if (idx <= 0) return void 0;
|
|
108
|
+
const candidate = message.slice(0, idx);
|
|
109
|
+
return /^[A-Za-z_][\w.]*$/.test(candidate) ? candidate : void 0;
|
|
110
|
+
}
|
|
111
|
+
function normalizeValidationPipeBody(body) {
|
|
112
|
+
const fields = {};
|
|
113
|
+
for (const msg of body.message) {
|
|
114
|
+
const param2 = extractParamFromValidatorMessage(msg);
|
|
115
|
+
const key = param2 ?? "_";
|
|
116
|
+
(fields[key] ??= []).push(msg);
|
|
117
|
+
}
|
|
118
|
+
const fieldKeys = Object.keys(fields).filter((k) => k !== "_");
|
|
119
|
+
const param = fieldKeys.length === 1 ? fieldKeys[0] : void 0;
|
|
120
|
+
const payload = {
|
|
121
|
+
type: "validation_error",
|
|
122
|
+
code: "VALIDATION_FAILED",
|
|
123
|
+
message: body.message.join("; "),
|
|
124
|
+
safeToRetry: false,
|
|
125
|
+
metadata: { fieldErrors: fields }
|
|
126
|
+
};
|
|
127
|
+
if (param) payload.param = param;
|
|
128
|
+
return payload;
|
|
129
|
+
}
|
|
130
|
+
function normalizeHttpExceptionBody(desc) {
|
|
131
|
+
const { statusCode, responseBody, exceptionName, fallbackMessage } = desc;
|
|
132
|
+
const defaultType = defaultErrorTypeForStatus(statusCode);
|
|
133
|
+
if (typeof responseBody === "string") {
|
|
134
|
+
return {
|
|
135
|
+
type: defaultType,
|
|
136
|
+
code: exceptionName,
|
|
137
|
+
message: responseBody,
|
|
138
|
+
safeToRetry: false
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
if (responseBody && typeof responseBody === "object") {
|
|
142
|
+
const body = responseBody;
|
|
143
|
+
if (isValidationPipeBody(body)) {
|
|
144
|
+
return normalizeValidationPipeBody(body);
|
|
145
|
+
}
|
|
146
|
+
const code = typeof body["code"] === "string" && body["code"] || typeof body["error"] === "string" && body["error"] || exceptionName;
|
|
147
|
+
const message = typeof body["message"] === "string" && body["message"] || (Array.isArray(body["message"]) ? body["message"].join("; ") : "") || fallbackMessage;
|
|
148
|
+
const type = typeof body["type"] === "string" && body["type"] || defaultType;
|
|
149
|
+
const safeToRetry = typeof body["safeToRetry"] === "boolean" ? body["safeToRetry"] : false;
|
|
150
|
+
const payload = {
|
|
151
|
+
type,
|
|
152
|
+
code,
|
|
153
|
+
message,
|
|
154
|
+
safeToRetry
|
|
155
|
+
};
|
|
156
|
+
if (typeof body["param"] === "string") payload.param = body["param"];
|
|
157
|
+
if (body["metadata"] && typeof body["metadata"] === "object") {
|
|
158
|
+
payload.metadata = body["metadata"];
|
|
159
|
+
}
|
|
160
|
+
if (body["details"] !== void 0) payload.details = body["details"];
|
|
161
|
+
return payload;
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
type: defaultType,
|
|
165
|
+
code: exceptionName || "INTERNAL_SERVER_ERROR",
|
|
166
|
+
message: fallbackMessage || "An unexpected error occurred",
|
|
167
|
+
safeToRetry: false
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function payloadFromPafiSdkError(err) {
|
|
171
|
+
const body = buildSdkErrorBody(err);
|
|
172
|
+
const payload = {
|
|
173
|
+
type: body.type,
|
|
174
|
+
code: body.code,
|
|
175
|
+
message: body.message,
|
|
176
|
+
safeToRetry: body.safeToRetry
|
|
177
|
+
};
|
|
178
|
+
if (body.param) payload.param = body.param;
|
|
179
|
+
if (body.metadata) payload.metadata = body.metadata;
|
|
180
|
+
if (body.details !== void 0) payload.details = body.details;
|
|
181
|
+
return payload;
|
|
182
|
+
}
|
|
183
|
+
function sanitizeDbErrorMessage(message) {
|
|
184
|
+
if (/^[A-Z_]+: /.test(message) && message.length < 256) return message;
|
|
185
|
+
return "Internal database error";
|
|
186
|
+
}
|
|
187
|
+
function buildErrorEnvelope(input) {
|
|
188
|
+
const now = (input.ctx.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
189
|
+
return {
|
|
190
|
+
success: false,
|
|
191
|
+
statusCode: input.status,
|
|
192
|
+
error: input.payload,
|
|
193
|
+
meta: {
|
|
194
|
+
timestamp: now.toISOString(),
|
|
195
|
+
requestId: input.ctx.requestId,
|
|
196
|
+
path: input.ctx.path
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function payloadFromHttpException(desc) {
|
|
201
|
+
return normalizeHttpExceptionBody(desc);
|
|
202
|
+
}
|
|
203
|
+
function payloadFromGenericError(err) {
|
|
204
|
+
const name = err.name || "INTERNAL_SERVER_ERROR";
|
|
205
|
+
const isDbError = name === "QueryFailedError" || name === "EntityNotFoundError";
|
|
206
|
+
return {
|
|
207
|
+
type: "server_error",
|
|
208
|
+
code: name,
|
|
209
|
+
message: isDbError ? sanitizeDbErrorMessage(err.message) : err.message || "An unexpected error occurred",
|
|
210
|
+
safeToRetry: false
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export {
|
|
215
|
+
__decoratorStart,
|
|
216
|
+
__runInitializers,
|
|
217
|
+
__decorateElement,
|
|
218
|
+
ConfigurationError,
|
|
219
|
+
PafiSdkError,
|
|
220
|
+
SDK_ERROR_HTTP_STATUS_CODE,
|
|
221
|
+
defaultErrorTypeForStatus,
|
|
222
|
+
ValidationError,
|
|
223
|
+
buildSdkErrorBody,
|
|
224
|
+
createSdkErrorMapper,
|
|
225
|
+
payloadFromPafiSdkError,
|
|
226
|
+
buildErrorEnvelope,
|
|
227
|
+
payloadFromHttpException,
|
|
228
|
+
payloadFromGenericError
|
|
229
|
+
};
|
|
230
|
+
//# sourceMappingURL=chunk-U3WMORJG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/api/errorMapper.ts","../src/http/errorEnvelope.ts"],"sourcesContent":["/**\n * `PafiSdkError` + `SdkErrorHttpStatus` are exported from\n * `@pafi-dev/core/errors` so core-level errors (e.g. `OracleStaleError`)\n * can extend the same base. Issuer re-exports the canonical types here\n * for back-compat — `instanceof PafiSdkError` from EITHER package\n * catches errors thrown from EITHER package.\n */\nexport {\n PafiSdkError,\n SDK_ERROR_HTTP_STATUS_CODE,\n defaultErrorTypeForStatus,\n type SdkErrorHttpStatus,\n type PafiErrorType,\n} from \"@pafi-dev/core\";\nimport { PafiSdkError } from \"@pafi-dev/core\";\n\n/**\n * `ValidationError` lives in `@pafi-dev/core` so core/trading helpers\n * throw the same typed class. Re-exported here for back-compat.\n */\nexport { ValidationError } from \"@pafi-dev/core\";\n\n/**\n * Issuer wired the SDK without a dependency the requested endpoint\n * needs (e.g. `/gas-fee` called but `feeManager` not configured;\n * `/pools` called but `poolsProvider` not configured). 503 because\n * the endpoint genuinely can't serve the request — caller's payload\n * is fine, the issuer's deployment is incomplete.\n */\nexport class ConfigurationError extends PafiSdkError {\n readonly httpStatus = \"service_unavailable\" as const;\n readonly code: string;\n readonly details?: Record<string, unknown>;\n\n constructor(\n code: string,\n message: string,\n details?: Record<string, unknown>,\n ) {\n super(message);\n this.code = code;\n this.details = details;\n }\n}\n","import {\n PafiSdkError,\n SDK_ERROR_HTTP_STATUS_CODE,\n defaultErrorTypeForStatus,\n type PafiErrorType,\n type SdkErrorHttpStatus,\n} from \"../errors\";\n\n/**\n * Normalized HTTP status the issuer controller should surface for a\n * given SDK error. Mirrors `SdkErrorHttpStatus` on `PafiSdkError`.\n */\nexport type SdkErrorStatus = SdkErrorHttpStatus;\n\n/**\n * Structured body the issuer controller passes to its\n * framework-specific exception class. Stripe-style envelope:\n *\n * ```json\n * {\n * \"type\": \"business_logic_error\",\n * \"code\": \"REDEMPTION_POLICY_DENIED\",\n * \"message\": \"...\",\n * \"param\": null,\n * \"metadata\": { \"policyDenialCode\": \"PER_TX_MIN\" },\n * \"safeToRetry\": false\n * }\n * ```\n *\n * Carries enough fields for the global HTTP filter to emit the final\n * envelope without losing any SDK-side context.\n */\nexport interface SdkErrorBody {\n /** Stripe-style taxonomy slot — drives UI branching. */\n type: PafiErrorType;\n /** Machine-readable code, e.g. `\"REDEMPTION_POLICY_DENIED\"`. */\n code: string;\n /** Human-readable message. */\n message: string;\n /** Field name that triggered the error, when applicable. */\n param?: string;\n /** UI-facing structured context. */\n metadata?: Record<string, unknown>;\n /** Raw debug context. */\n details?: unknown;\n /** True when retry is safe (no side effects yet). */\n safeToRetry: boolean;\n}\n\n/**\n * Per-status exception factories. The issuer's controller wires one\n * factory per status to its preferred framework's exception class\n * (NestJS `UnprocessableEntityException`, Fastify `httpErrors.badData`,\n * etc). The factory must return an Error — `createSdkErrorMapper`\n * uses `throw factory(body)`.\n */\nexport interface SdkErrorMapperFactories {\n notFound: (body: SdkErrorBody) => Error;\n forbidden: (body: SdkErrorBody) => Error;\n unprocessable: (body: SdkErrorBody) => Error;\n serviceUnavailable: (body: SdkErrorBody) => Error;\n}\n\n/**\n * Build the Stripe-style body from any `PafiSdkError`. Exposed for\n * frameworks that don't fit the four-factory shape (e.g. a Hono error\n * handler that builds its own response object).\n */\nexport function buildSdkErrorBody(err: PafiSdkError): SdkErrorBody {\n const type =\n err.type ??\n defaultErrorTypeForStatus(SDK_ERROR_HTTP_STATUS_CODE[err.httpStatus]);\n const body: SdkErrorBody = {\n type,\n code: err.code,\n message: err.message,\n safeToRetry: err.safeToRetry,\n };\n if (err.param) body.param = err.param;\n if (err.metadata) body.metadata = err.metadata;\n if (err.details !== undefined) body.details = err.details;\n return body;\n}\n\n/**\n * Build a single error-mapping function that converts any `PafiSdkError`\n * into the issuer's framework-specific HTTP exception. Status, code,\n * `safeToRetry`, and `details` come straight off the error class —\n * the mapper is a dumb funnel, no per-error business logic.\n *\n * Any non-`PafiSdkError` is re-thrown unchanged so unexpected runtime\n * errors propagate to the framework's default 500 handler.\n *\n * Usage (NestJS):\n *\n * ```ts\n * const mapSdkError = createSdkErrorMapper({\n * notFound: (body) => new NotFoundException(body),\n * forbidden: (body) => new ForbiddenException(body),\n * unprocessable: (body) => new UnprocessableEntityException(body),\n * serviceUnavailable: (body) => new ServiceUnavailableException(body),\n * });\n *\n * try { ... } catch (err) { mapSdkError(err); }\n * ```\n *\n * Returns `never` so call sites in `try/catch` propagate the throw\n * without a redundant `throw` keyword.\n */\nexport function createSdkErrorMapper(\n factories: SdkErrorMapperFactories,\n): (err: unknown) => never {\n return (err: unknown): never => {\n if (!(err instanceof PafiSdkError)) {\n throw err;\n }\n const body = buildSdkErrorBody(err);\n switch (err.httpStatus) {\n case \"not_found\":\n throw factories.notFound(body);\n case \"forbidden\":\n throw factories.forbidden(body);\n case \"unprocessable\":\n throw factories.unprocessable(body);\n case \"service_unavailable\":\n throw factories.serviceUnavailable(body);\n }\n };\n}\n","/**\n * Stripe-style HTTP error envelope shared by every PAFI service and\n * issuer backend. The framework-agnostic `normalizeErrorToEnvelope`\n * helper produces this from any thrown value; framework-specific\n * filters (`@pafi-dev/issuer/nestjs`) call into it and write the\n * result to their response object.\n *\n * Wire format:\n *\n * ```json\n * {\n * \"success\": false,\n * \"statusCode\": 422,\n * \"error\": {\n * \"type\": \"business_logic_error\",\n * \"code\": \"REDEMPTION_POLICY_DENIED\",\n * \"message\": \"redemption denied: amount 1 below per-tx minimum\",\n * \"param\": null,\n * \"metadata\": { \"policyDenialCode\": \"PER_TX_MIN\" },\n * \"safeToRetry\": false,\n * \"details\": null\n * },\n * \"meta\": {\n * \"timestamp\": \"2026-05-07T...\",\n * \"requestId\": \"...\",\n * \"path\": \"/pt/redeem\"\n * }\n * }\n * ```\n */\n\nimport {\n PafiSdkError,\n SDK_ERROR_HTTP_STATUS_CODE,\n defaultErrorTypeForStatus,\n type PafiErrorType,\n} from \"../errors\";\nimport { buildSdkErrorBody, type SdkErrorBody } from \"../api/errorMapper\";\n\n/** Inner `error` block of the envelope. */\nexport interface PafiErrorPayload {\n type: PafiErrorType;\n code: string;\n message: string;\n param?: string;\n metadata?: Record<string, unknown>;\n details?: unknown;\n safeToRetry: boolean;\n}\n\n/** Outer envelope returned for any non-2xx response. */\nexport interface PafiErrorEnvelope {\n success: false;\n statusCode: number;\n error: PafiErrorPayload;\n meta: {\n timestamp: string;\n requestId: string;\n path: string;\n };\n}\n\n/** Per-call request context the filter must collect from the host framework. */\nexport interface NormalizeContext {\n /** `req.url` or equivalent. */\n path: string;\n /** Resolved request id (header or generated). */\n requestId: string;\n /** Optional `now()` injection for deterministic tests. */\n now?: () => Date;\n}\n\n/**\n * Generic error duck-type. The filter passes a small descriptor for\n * any framework exception so the normalizer doesn't depend on\n * `@nestjs/common` or any specific HTTP library.\n */\nexport interface GenericHttpExceptionDescriptor {\n /** HTTP status code carried by the exception. */\n statusCode: number;\n /**\n * The body the framework would have serialized. For NestJS this is\n * `exception.getResponse()`. May be a string or a record with\n * `code`/`error`/`message`/`details`/`safeToRetry`/`type`/etc.\n */\n responseBody: unknown;\n /** Exception class name, used as a last-resort `code` fallback. */\n exceptionName: string;\n /** Original `error.message`, used as a last-resort `message` fallback. */\n fallbackMessage: string;\n}\n\n/** True when `value` looks like a NestJS `BadRequestException` from `ValidationPipe`. */\nfunction isValidationPipeBody(\n body: Record<string, unknown>,\n): body is { message: string[]; error?: string; statusCode?: number } {\n return (\n Array.isArray(body[\"message\"]) &&\n typeof body[\"error\"] === \"string\" &&\n body[\"error\"] === \"Bad Request\"\n );\n}\n\n/** Pull `param` out of a `class-validator` message like `\"amount must be a number string\"`. */\nfunction extractParamFromValidatorMessage(message: string): string | undefined {\n const idx = message.indexOf(\" \");\n if (idx <= 0) return undefined;\n const candidate = message.slice(0, idx);\n return /^[A-Za-z_][\\w.]*$/.test(candidate) ? candidate : undefined;\n}\n\nfunction normalizeValidationPipeBody(\n body: { message: string[]; statusCode?: number },\n): PafiErrorPayload {\n const fields: Record<string, string[]> = {};\n for (const msg of body.message) {\n const param = extractParamFromValidatorMessage(msg);\n const key = param ?? \"_\";\n (fields[key] ??= []).push(msg);\n }\n const fieldKeys = Object.keys(fields).filter((k) => k !== \"_\");\n const param = fieldKeys.length === 1 ? fieldKeys[0] : undefined;\n\n const payload: PafiErrorPayload = {\n type: \"validation_error\",\n code: \"VALIDATION_FAILED\",\n message: body.message.join(\"; \"),\n safeToRetry: false,\n metadata: { fieldErrors: fields },\n };\n if (param) payload.param = param;\n return payload;\n}\n\n/**\n * Pull a Stripe-style payload out of a structured exception body. The\n * filter has already determined this is an `HttpException` (or\n * duck-typed equivalent). Order:\n *\n * 1. SDK-shaped body (has `type` + `code`) — pass through verbatim.\n * 2. `ValidationPipe` body (`message: string[]`, `error: \"Bad Request\"`)\n * — collapse into `validation_error` with `metadata.fieldErrors`.\n * 3. Plain object with `code` or `error` — best-effort extract.\n * 4. String body — just a message.\n */\nfunction normalizeHttpExceptionBody(\n desc: GenericHttpExceptionDescriptor,\n): PafiErrorPayload {\n const { statusCode, responseBody, exceptionName, fallbackMessage } = desc;\n const defaultType = defaultErrorTypeForStatus(statusCode);\n\n if (typeof responseBody === \"string\") {\n return {\n type: defaultType,\n code: exceptionName,\n message: responseBody,\n safeToRetry: false,\n };\n }\n\n if (responseBody && typeof responseBody === \"object\") {\n const body = responseBody as Record<string, unknown>;\n\n if (isValidationPipeBody(body)) {\n return normalizeValidationPipeBody(body);\n }\n\n const code =\n (typeof body[\"code\"] === \"string\" && body[\"code\"]) ||\n (typeof body[\"error\"] === \"string\" && body[\"error\"]) ||\n exceptionName;\n const message =\n (typeof body[\"message\"] === \"string\" && body[\"message\"]) ||\n (Array.isArray(body[\"message\"])\n ? (body[\"message\"] as string[]).join(\"; \")\n : \"\") ||\n fallbackMessage;\n const type =\n (typeof body[\"type\"] === \"string\" && (body[\"type\"] as PafiErrorType)) ||\n defaultType;\n const safeToRetry =\n typeof body[\"safeToRetry\"] === \"boolean\" ? body[\"safeToRetry\"] : false;\n\n const payload: PafiErrorPayload = {\n type,\n code,\n message,\n safeToRetry,\n };\n if (typeof body[\"param\"] === \"string\") payload.param = body[\"param\"];\n if (body[\"metadata\"] && typeof body[\"metadata\"] === \"object\") {\n payload.metadata = body[\"metadata\"] as Record<string, unknown>;\n }\n if (body[\"details\"] !== undefined) payload.details = body[\"details\"];\n return payload;\n }\n\n return {\n type: defaultType,\n code: exceptionName || \"INTERNAL_SERVER_ERROR\",\n message: fallbackMessage || \"An unexpected error occurred\",\n safeToRetry: false,\n };\n}\n\n/**\n * Convert a `PafiSdkError` directly to the inner `error` payload —\n * skipping any framework exception wrapper. Used by frameworks that\n * surface SDK errors without going through `createSdkErrorMapper`.\n */\nexport function payloadFromPafiSdkError(err: PafiSdkError): PafiErrorPayload {\n const body: SdkErrorBody = buildSdkErrorBody(err);\n const payload: PafiErrorPayload = {\n type: body.type,\n code: body.code,\n message: body.message,\n safeToRetry: body.safeToRetry,\n };\n if (body.param) payload.param = body.param;\n if (body.metadata) payload.metadata = body.metadata;\n if (body.details !== undefined) payload.details = body.details;\n return payload;\n}\n\n/** Strip SQL fragments from raw DB driver errors before exposing them. */\nfunction sanitizeDbErrorMessage(message: string): string {\n if (/^[A-Z_]+: /.test(message) && message.length < 256) return message;\n return \"Internal database error\";\n}\n\n/**\n * Build the full envelope for any thrown value. The caller resolves\n * `HttpException`-shaped exceptions and passes a descriptor; plain\n * `Error` instances and unknown throws are handled directly.\n */\nexport function buildErrorEnvelope(input: {\n status: number;\n payload: PafiErrorPayload;\n ctx: NormalizeContext;\n}): PafiErrorEnvelope {\n const now = (input.ctx.now ?? (() => new Date()))();\n return {\n success: false,\n statusCode: input.status,\n error: input.payload,\n meta: {\n timestamp: now.toISOString(),\n requestId: input.ctx.requestId,\n path: input.ctx.path,\n },\n };\n}\n\n/**\n * Normalize a known `HttpException`-shaped exception into a payload.\n * Framework filters supply the descriptor; the rest is shape-agnostic.\n */\nexport function payloadFromHttpException(\n desc: GenericHttpExceptionDescriptor,\n): PafiErrorPayload {\n return normalizeHttpExceptionBody(desc);\n}\n\n/**\n * Normalize a generic `Error` (not an HttpException) — TypeORM\n * `QueryFailedError`, viem revert errors, etc. Returns a redacted\n * 500-class payload with no message bleed-through unless the error\n * is recognizably benign.\n */\nexport function payloadFromGenericError(err: Error): PafiErrorPayload {\n const name = err.name || \"INTERNAL_SERVER_ERROR\";\n const isDbError = name === \"QueryFailedError\" || name === \"EntityNotFoundError\";\n return {\n type: \"server_error\",\n code: name,\n message: isDbError\n ? sanitizeDbErrorMessage(err.message)\n : err.message || \"An unexpected error occurred\",\n safeToRetry: false,\n };\n}\n"],"mappings":"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","names":["PafiSdkError","param"]}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/http/index.ts
|
|
21
|
+
var http_exports = {};
|
|
22
|
+
__export(http_exports, {
|
|
23
|
+
buildErrorEnvelope: () => buildErrorEnvelope,
|
|
24
|
+
payloadFromGenericError: () => payloadFromGenericError,
|
|
25
|
+
payloadFromHttpException: () => payloadFromHttpException,
|
|
26
|
+
payloadFromPafiSdkError: () => payloadFromPafiSdkError
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(http_exports);
|
|
29
|
+
|
|
30
|
+
// src/errors.ts
|
|
31
|
+
var import_core = require("@pafi-dev/core");
|
|
32
|
+
var import_core2 = require("@pafi-dev/core");
|
|
33
|
+
var import_core3 = require("@pafi-dev/core");
|
|
34
|
+
|
|
35
|
+
// src/api/errorMapper.ts
|
|
36
|
+
function buildSdkErrorBody(err) {
|
|
37
|
+
const type = err.type ?? (0, import_core.defaultErrorTypeForStatus)(import_core.SDK_ERROR_HTTP_STATUS_CODE[err.httpStatus]);
|
|
38
|
+
const body = {
|
|
39
|
+
type,
|
|
40
|
+
code: err.code,
|
|
41
|
+
message: err.message,
|
|
42
|
+
safeToRetry: err.safeToRetry
|
|
43
|
+
};
|
|
44
|
+
if (err.param) body.param = err.param;
|
|
45
|
+
if (err.metadata) body.metadata = err.metadata;
|
|
46
|
+
if (err.details !== void 0) body.details = err.details;
|
|
47
|
+
return body;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/http/errorEnvelope.ts
|
|
51
|
+
function isValidationPipeBody(body) {
|
|
52
|
+
return Array.isArray(body["message"]) && typeof body["error"] === "string" && body["error"] === "Bad Request";
|
|
53
|
+
}
|
|
54
|
+
function extractParamFromValidatorMessage(message) {
|
|
55
|
+
const idx = message.indexOf(" ");
|
|
56
|
+
if (idx <= 0) return void 0;
|
|
57
|
+
const candidate = message.slice(0, idx);
|
|
58
|
+
return /^[A-Za-z_][\w.]*$/.test(candidate) ? candidate : void 0;
|
|
59
|
+
}
|
|
60
|
+
function normalizeValidationPipeBody(body) {
|
|
61
|
+
const fields = {};
|
|
62
|
+
for (const msg of body.message) {
|
|
63
|
+
const param2 = extractParamFromValidatorMessage(msg);
|
|
64
|
+
const key = param2 ?? "_";
|
|
65
|
+
(fields[key] ??= []).push(msg);
|
|
66
|
+
}
|
|
67
|
+
const fieldKeys = Object.keys(fields).filter((k) => k !== "_");
|
|
68
|
+
const param = fieldKeys.length === 1 ? fieldKeys[0] : void 0;
|
|
69
|
+
const payload = {
|
|
70
|
+
type: "validation_error",
|
|
71
|
+
code: "VALIDATION_FAILED",
|
|
72
|
+
message: body.message.join("; "),
|
|
73
|
+
safeToRetry: false,
|
|
74
|
+
metadata: { fieldErrors: fields }
|
|
75
|
+
};
|
|
76
|
+
if (param) payload.param = param;
|
|
77
|
+
return payload;
|
|
78
|
+
}
|
|
79
|
+
function normalizeHttpExceptionBody(desc) {
|
|
80
|
+
const { statusCode, responseBody, exceptionName, fallbackMessage } = desc;
|
|
81
|
+
const defaultType = (0, import_core.defaultErrorTypeForStatus)(statusCode);
|
|
82
|
+
if (typeof responseBody === "string") {
|
|
83
|
+
return {
|
|
84
|
+
type: defaultType,
|
|
85
|
+
code: exceptionName,
|
|
86
|
+
message: responseBody,
|
|
87
|
+
safeToRetry: false
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (responseBody && typeof responseBody === "object") {
|
|
91
|
+
const body = responseBody;
|
|
92
|
+
if (isValidationPipeBody(body)) {
|
|
93
|
+
return normalizeValidationPipeBody(body);
|
|
94
|
+
}
|
|
95
|
+
const code = typeof body["code"] === "string" && body["code"] || typeof body["error"] === "string" && body["error"] || exceptionName;
|
|
96
|
+
const message = typeof body["message"] === "string" && body["message"] || (Array.isArray(body["message"]) ? body["message"].join("; ") : "") || fallbackMessage;
|
|
97
|
+
const type = typeof body["type"] === "string" && body["type"] || defaultType;
|
|
98
|
+
const safeToRetry = typeof body["safeToRetry"] === "boolean" ? body["safeToRetry"] : false;
|
|
99
|
+
const payload = {
|
|
100
|
+
type,
|
|
101
|
+
code,
|
|
102
|
+
message,
|
|
103
|
+
safeToRetry
|
|
104
|
+
};
|
|
105
|
+
if (typeof body["param"] === "string") payload.param = body["param"];
|
|
106
|
+
if (body["metadata"] && typeof body["metadata"] === "object") {
|
|
107
|
+
payload.metadata = body["metadata"];
|
|
108
|
+
}
|
|
109
|
+
if (body["details"] !== void 0) payload.details = body["details"];
|
|
110
|
+
return payload;
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
type: defaultType,
|
|
114
|
+
code: exceptionName || "INTERNAL_SERVER_ERROR",
|
|
115
|
+
message: fallbackMessage || "An unexpected error occurred",
|
|
116
|
+
safeToRetry: false
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function payloadFromPafiSdkError(err) {
|
|
120
|
+
const body = buildSdkErrorBody(err);
|
|
121
|
+
const payload = {
|
|
122
|
+
type: body.type,
|
|
123
|
+
code: body.code,
|
|
124
|
+
message: body.message,
|
|
125
|
+
safeToRetry: body.safeToRetry
|
|
126
|
+
};
|
|
127
|
+
if (body.param) payload.param = body.param;
|
|
128
|
+
if (body.metadata) payload.metadata = body.metadata;
|
|
129
|
+
if (body.details !== void 0) payload.details = body.details;
|
|
130
|
+
return payload;
|
|
131
|
+
}
|
|
132
|
+
function sanitizeDbErrorMessage(message) {
|
|
133
|
+
if (/^[A-Z_]+: /.test(message) && message.length < 256) return message;
|
|
134
|
+
return "Internal database error";
|
|
135
|
+
}
|
|
136
|
+
function buildErrorEnvelope(input) {
|
|
137
|
+
const now = (input.ctx.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
138
|
+
return {
|
|
139
|
+
success: false,
|
|
140
|
+
statusCode: input.status,
|
|
141
|
+
error: input.payload,
|
|
142
|
+
meta: {
|
|
143
|
+
timestamp: now.toISOString(),
|
|
144
|
+
requestId: input.ctx.requestId,
|
|
145
|
+
path: input.ctx.path
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function payloadFromHttpException(desc) {
|
|
150
|
+
return normalizeHttpExceptionBody(desc);
|
|
151
|
+
}
|
|
152
|
+
function payloadFromGenericError(err) {
|
|
153
|
+
const name = err.name || "INTERNAL_SERVER_ERROR";
|
|
154
|
+
const isDbError = name === "QueryFailedError" || name === "EntityNotFoundError";
|
|
155
|
+
return {
|
|
156
|
+
type: "server_error",
|
|
157
|
+
code: name,
|
|
158
|
+
message: isDbError ? sanitizeDbErrorMessage(err.message) : err.message || "An unexpected error occurred",
|
|
159
|
+
safeToRetry: false
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
163
|
+
0 && (module.exports = {
|
|
164
|
+
buildErrorEnvelope,
|
|
165
|
+
payloadFromGenericError,
|
|
166
|
+
payloadFromHttpException,
|
|
167
|
+
payloadFromPafiSdkError
|
|
168
|
+
});
|
|
169
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/http/index.ts","../../src/errors.ts","../../src/api/errorMapper.ts","../../src/http/errorEnvelope.ts"],"sourcesContent":["export {\n buildErrorEnvelope,\n payloadFromHttpException,\n payloadFromGenericError,\n payloadFromPafiSdkError,\n type PafiErrorEnvelope,\n type PafiErrorPayload,\n type GenericHttpExceptionDescriptor,\n type NormalizeContext,\n} from \"./errorEnvelope\";\n","/**\n * `PafiSdkError` + `SdkErrorHttpStatus` are exported from\n * `@pafi-dev/core/errors` so core-level errors (e.g. `OracleStaleError`)\n * can extend the same base. Issuer re-exports the canonical types here\n * for back-compat — `instanceof PafiSdkError` from EITHER package\n * catches errors thrown from EITHER package.\n */\nexport {\n PafiSdkError,\n SDK_ERROR_HTTP_STATUS_CODE,\n defaultErrorTypeForStatus,\n type SdkErrorHttpStatus,\n type PafiErrorType,\n} from \"@pafi-dev/core\";\nimport { PafiSdkError } from \"@pafi-dev/core\";\n\n/**\n * `ValidationError` lives in `@pafi-dev/core` so core/trading helpers\n * throw the same typed class. Re-exported here for back-compat.\n */\nexport { ValidationError } from \"@pafi-dev/core\";\n\n/**\n * Issuer wired the SDK without a dependency the requested endpoint\n * needs (e.g. `/gas-fee` called but `feeManager` not configured;\n * `/pools` called but `poolsProvider` not configured). 503 because\n * the endpoint genuinely can't serve the request — caller's payload\n * is fine, the issuer's deployment is incomplete.\n */\nexport class ConfigurationError extends PafiSdkError {\n readonly httpStatus = \"service_unavailable\" as const;\n readonly code: string;\n readonly details?: Record<string, unknown>;\n\n constructor(\n code: string,\n message: string,\n details?: Record<string, unknown>,\n ) {\n super(message);\n this.code = code;\n this.details = details;\n }\n}\n","import {\n PafiSdkError,\n SDK_ERROR_HTTP_STATUS_CODE,\n defaultErrorTypeForStatus,\n type PafiErrorType,\n type SdkErrorHttpStatus,\n} from \"../errors\";\n\n/**\n * Normalized HTTP status the issuer controller should surface for a\n * given SDK error. Mirrors `SdkErrorHttpStatus` on `PafiSdkError`.\n */\nexport type SdkErrorStatus = SdkErrorHttpStatus;\n\n/**\n * Structured body the issuer controller passes to its\n * framework-specific exception class. Stripe-style envelope:\n *\n * ```json\n * {\n * \"type\": \"business_logic_error\",\n * \"code\": \"REDEMPTION_POLICY_DENIED\",\n * \"message\": \"...\",\n * \"param\": null,\n * \"metadata\": { \"policyDenialCode\": \"PER_TX_MIN\" },\n * \"safeToRetry\": false\n * }\n * ```\n *\n * Carries enough fields for the global HTTP filter to emit the final\n * envelope without losing any SDK-side context.\n */\nexport interface SdkErrorBody {\n /** Stripe-style taxonomy slot — drives UI branching. */\n type: PafiErrorType;\n /** Machine-readable code, e.g. `\"REDEMPTION_POLICY_DENIED\"`. */\n code: string;\n /** Human-readable message. */\n message: string;\n /** Field name that triggered the error, when applicable. */\n param?: string;\n /** UI-facing structured context. */\n metadata?: Record<string, unknown>;\n /** Raw debug context. */\n details?: unknown;\n /** True when retry is safe (no side effects yet). */\n safeToRetry: boolean;\n}\n\n/**\n * Per-status exception factories. The issuer's controller wires one\n * factory per status to its preferred framework's exception class\n * (NestJS `UnprocessableEntityException`, Fastify `httpErrors.badData`,\n * etc). The factory must return an Error — `createSdkErrorMapper`\n * uses `throw factory(body)`.\n */\nexport interface SdkErrorMapperFactories {\n notFound: (body: SdkErrorBody) => Error;\n forbidden: (body: SdkErrorBody) => Error;\n unprocessable: (body: SdkErrorBody) => Error;\n serviceUnavailable: (body: SdkErrorBody) => Error;\n}\n\n/**\n * Build the Stripe-style body from any `PafiSdkError`. Exposed for\n * frameworks that don't fit the four-factory shape (e.g. a Hono error\n * handler that builds its own response object).\n */\nexport function buildSdkErrorBody(err: PafiSdkError): SdkErrorBody {\n const type =\n err.type ??\n defaultErrorTypeForStatus(SDK_ERROR_HTTP_STATUS_CODE[err.httpStatus]);\n const body: SdkErrorBody = {\n type,\n code: err.code,\n message: err.message,\n safeToRetry: err.safeToRetry,\n };\n if (err.param) body.param = err.param;\n if (err.metadata) body.metadata = err.metadata;\n if (err.details !== undefined) body.details = err.details;\n return body;\n}\n\n/**\n * Build a single error-mapping function that converts any `PafiSdkError`\n * into the issuer's framework-specific HTTP exception. Status, code,\n * `safeToRetry`, and `details` come straight off the error class —\n * the mapper is a dumb funnel, no per-error business logic.\n *\n * Any non-`PafiSdkError` is re-thrown unchanged so unexpected runtime\n * errors propagate to the framework's default 500 handler.\n *\n * Usage (NestJS):\n *\n * ```ts\n * const mapSdkError = createSdkErrorMapper({\n * notFound: (body) => new NotFoundException(body),\n * forbidden: (body) => new ForbiddenException(body),\n * unprocessable: (body) => new UnprocessableEntityException(body),\n * serviceUnavailable: (body) => new ServiceUnavailableException(body),\n * });\n *\n * try { ... } catch (err) { mapSdkError(err); }\n * ```\n *\n * Returns `never` so call sites in `try/catch` propagate the throw\n * without a redundant `throw` keyword.\n */\nexport function createSdkErrorMapper(\n factories: SdkErrorMapperFactories,\n): (err: unknown) => never {\n return (err: unknown): never => {\n if (!(err instanceof PafiSdkError)) {\n throw err;\n }\n const body = buildSdkErrorBody(err);\n switch (err.httpStatus) {\n case \"not_found\":\n throw factories.notFound(body);\n case \"forbidden\":\n throw factories.forbidden(body);\n case \"unprocessable\":\n throw factories.unprocessable(body);\n case \"service_unavailable\":\n throw factories.serviceUnavailable(body);\n }\n };\n}\n","/**\n * Stripe-style HTTP error envelope shared by every PAFI service and\n * issuer backend. The framework-agnostic `normalizeErrorToEnvelope`\n * helper produces this from any thrown value; framework-specific\n * filters (`@pafi-dev/issuer/nestjs`) call into it and write the\n * result to their response object.\n *\n * Wire format:\n *\n * ```json\n * {\n * \"success\": false,\n * \"statusCode\": 422,\n * \"error\": {\n * \"type\": \"business_logic_error\",\n * \"code\": \"REDEMPTION_POLICY_DENIED\",\n * \"message\": \"redemption denied: amount 1 below per-tx minimum\",\n * \"param\": null,\n * \"metadata\": { \"policyDenialCode\": \"PER_TX_MIN\" },\n * \"safeToRetry\": false,\n * \"details\": null\n * },\n * \"meta\": {\n * \"timestamp\": \"2026-05-07T...\",\n * \"requestId\": \"...\",\n * \"path\": \"/pt/redeem\"\n * }\n * }\n * ```\n */\n\nimport {\n PafiSdkError,\n SDK_ERROR_HTTP_STATUS_CODE,\n defaultErrorTypeForStatus,\n type PafiErrorType,\n} from \"../errors\";\nimport { buildSdkErrorBody, type SdkErrorBody } from \"../api/errorMapper\";\n\n/** Inner `error` block of the envelope. */\nexport interface PafiErrorPayload {\n type: PafiErrorType;\n code: string;\n message: string;\n param?: string;\n metadata?: Record<string, unknown>;\n details?: unknown;\n safeToRetry: boolean;\n}\n\n/** Outer envelope returned for any non-2xx response. */\nexport interface PafiErrorEnvelope {\n success: false;\n statusCode: number;\n error: PafiErrorPayload;\n meta: {\n timestamp: string;\n requestId: string;\n path: string;\n };\n}\n\n/** Per-call request context the filter must collect from the host framework. */\nexport interface NormalizeContext {\n /** `req.url` or equivalent. */\n path: string;\n /** Resolved request id (header or generated). */\n requestId: string;\n /** Optional `now()` injection for deterministic tests. */\n now?: () => Date;\n}\n\n/**\n * Generic error duck-type. The filter passes a small descriptor for\n * any framework exception so the normalizer doesn't depend on\n * `@nestjs/common` or any specific HTTP library.\n */\nexport interface GenericHttpExceptionDescriptor {\n /** HTTP status code carried by the exception. */\n statusCode: number;\n /**\n * The body the framework would have serialized. For NestJS this is\n * `exception.getResponse()`. May be a string or a record with\n * `code`/`error`/`message`/`details`/`safeToRetry`/`type`/etc.\n */\n responseBody: unknown;\n /** Exception class name, used as a last-resort `code` fallback. */\n exceptionName: string;\n /** Original `error.message`, used as a last-resort `message` fallback. */\n fallbackMessage: string;\n}\n\n/** True when `value` looks like a NestJS `BadRequestException` from `ValidationPipe`. */\nfunction isValidationPipeBody(\n body: Record<string, unknown>,\n): body is { message: string[]; error?: string; statusCode?: number } {\n return (\n Array.isArray(body[\"message\"]) &&\n typeof body[\"error\"] === \"string\" &&\n body[\"error\"] === \"Bad Request\"\n );\n}\n\n/** Pull `param` out of a `class-validator` message like `\"amount must be a number string\"`. */\nfunction extractParamFromValidatorMessage(message: string): string | undefined {\n const idx = message.indexOf(\" \");\n if (idx <= 0) return undefined;\n const candidate = message.slice(0, idx);\n return /^[A-Za-z_][\\w.]*$/.test(candidate) ? candidate : undefined;\n}\n\nfunction normalizeValidationPipeBody(\n body: { message: string[]; statusCode?: number },\n): PafiErrorPayload {\n const fields: Record<string, string[]> = {};\n for (const msg of body.message) {\n const param = extractParamFromValidatorMessage(msg);\n const key = param ?? \"_\";\n (fields[key] ??= []).push(msg);\n }\n const fieldKeys = Object.keys(fields).filter((k) => k !== \"_\");\n const param = fieldKeys.length === 1 ? fieldKeys[0] : undefined;\n\n const payload: PafiErrorPayload = {\n type: \"validation_error\",\n code: \"VALIDATION_FAILED\",\n message: body.message.join(\"; \"),\n safeToRetry: false,\n metadata: { fieldErrors: fields },\n };\n if (param) payload.param = param;\n return payload;\n}\n\n/**\n * Pull a Stripe-style payload out of a structured exception body. The\n * filter has already determined this is an `HttpException` (or\n * duck-typed equivalent). Order:\n *\n * 1. SDK-shaped body (has `type` + `code`) — pass through verbatim.\n * 2. `ValidationPipe` body (`message: string[]`, `error: \"Bad Request\"`)\n * — collapse into `validation_error` with `metadata.fieldErrors`.\n * 3. Plain object with `code` or `error` — best-effort extract.\n * 4. String body — just a message.\n */\nfunction normalizeHttpExceptionBody(\n desc: GenericHttpExceptionDescriptor,\n): PafiErrorPayload {\n const { statusCode, responseBody, exceptionName, fallbackMessage } = desc;\n const defaultType = defaultErrorTypeForStatus(statusCode);\n\n if (typeof responseBody === \"string\") {\n return {\n type: defaultType,\n code: exceptionName,\n message: responseBody,\n safeToRetry: false,\n };\n }\n\n if (responseBody && typeof responseBody === \"object\") {\n const body = responseBody as Record<string, unknown>;\n\n if (isValidationPipeBody(body)) {\n return normalizeValidationPipeBody(body);\n }\n\n const code =\n (typeof body[\"code\"] === \"string\" && body[\"code\"]) ||\n (typeof body[\"error\"] === \"string\" && body[\"error\"]) ||\n exceptionName;\n const message =\n (typeof body[\"message\"] === \"string\" && body[\"message\"]) ||\n (Array.isArray(body[\"message\"])\n ? (body[\"message\"] as string[]).join(\"; \")\n : \"\") ||\n fallbackMessage;\n const type =\n (typeof body[\"type\"] === \"string\" && (body[\"type\"] as PafiErrorType)) ||\n defaultType;\n const safeToRetry =\n typeof body[\"safeToRetry\"] === \"boolean\" ? body[\"safeToRetry\"] : false;\n\n const payload: PafiErrorPayload = {\n type,\n code,\n message,\n safeToRetry,\n };\n if (typeof body[\"param\"] === \"string\") payload.param = body[\"param\"];\n if (body[\"metadata\"] && typeof body[\"metadata\"] === \"object\") {\n payload.metadata = body[\"metadata\"] as Record<string, unknown>;\n }\n if (body[\"details\"] !== undefined) payload.details = body[\"details\"];\n return payload;\n }\n\n return {\n type: defaultType,\n code: exceptionName || \"INTERNAL_SERVER_ERROR\",\n message: fallbackMessage || \"An unexpected error occurred\",\n safeToRetry: false,\n };\n}\n\n/**\n * Convert a `PafiSdkError` directly to the inner `error` payload —\n * skipping any framework exception wrapper. Used by frameworks that\n * surface SDK errors without going through `createSdkErrorMapper`.\n */\nexport function payloadFromPafiSdkError(err: PafiSdkError): PafiErrorPayload {\n const body: SdkErrorBody = buildSdkErrorBody(err);\n const payload: PafiErrorPayload = {\n type: body.type,\n code: body.code,\n message: body.message,\n safeToRetry: body.safeToRetry,\n };\n if (body.param) payload.param = body.param;\n if (body.metadata) payload.metadata = body.metadata;\n if (body.details !== undefined) payload.details = body.details;\n return payload;\n}\n\n/** Strip SQL fragments from raw DB driver errors before exposing them. */\nfunction sanitizeDbErrorMessage(message: string): string {\n if (/^[A-Z_]+: /.test(message) && message.length < 256) return message;\n return \"Internal database error\";\n}\n\n/**\n * Build the full envelope for any thrown value. The caller resolves\n * `HttpException`-shaped exceptions and passes a descriptor; plain\n * `Error` instances and unknown throws are handled directly.\n */\nexport function buildErrorEnvelope(input: {\n status: number;\n payload: PafiErrorPayload;\n ctx: NormalizeContext;\n}): PafiErrorEnvelope {\n const now = (input.ctx.now ?? (() => new Date()))();\n return {\n success: false,\n statusCode: input.status,\n error: input.payload,\n meta: {\n timestamp: now.toISOString(),\n requestId: input.ctx.requestId,\n path: input.ctx.path,\n },\n };\n}\n\n/**\n * Normalize a known `HttpException`-shaped exception into a payload.\n * Framework filters supply the descriptor; the rest is shape-agnostic.\n */\nexport function payloadFromHttpException(\n desc: GenericHttpExceptionDescriptor,\n): PafiErrorPayload {\n return normalizeHttpExceptionBody(desc);\n}\n\n/**\n * Normalize a generic `Error` (not an HttpException) — TypeORM\n * `QueryFailedError`, viem revert errors, etc. Returns a redacted\n * 500-class payload with no message bleed-through unless the error\n * is recognizably benign.\n */\nexport function payloadFromGenericError(err: Error): PafiErrorPayload {\n const name = err.name || \"INTERNAL_SERVER_ERROR\";\n const isDbError = name === \"QueryFailedError\" || name === \"EntityNotFoundError\";\n return {\n type: \"server_error\",\n code: name,\n message: isDbError\n ? sanitizeDbErrorMessage(err.message)\n : err.message || \"An unexpected error occurred\",\n safeToRetry: false,\n };\n}\n"],"mappings":"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","names":["import_core","param"]}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { PafiErrorType, PafiSdkError } from '@pafi-dev/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stripe-style HTTP error envelope shared by every PAFI service and
|
|
5
|
+
* issuer backend. The framework-agnostic `normalizeErrorToEnvelope`
|
|
6
|
+
* helper produces this from any thrown value; framework-specific
|
|
7
|
+
* filters (`@pafi-dev/issuer/nestjs`) call into it and write the
|
|
8
|
+
* result to their response object.
|
|
9
|
+
*
|
|
10
|
+
* Wire format:
|
|
11
|
+
*
|
|
12
|
+
* ```json
|
|
13
|
+
* {
|
|
14
|
+
* "success": false,
|
|
15
|
+
* "statusCode": 422,
|
|
16
|
+
* "error": {
|
|
17
|
+
* "type": "business_logic_error",
|
|
18
|
+
* "code": "REDEMPTION_POLICY_DENIED",
|
|
19
|
+
* "message": "redemption denied: amount 1 below per-tx minimum",
|
|
20
|
+
* "param": null,
|
|
21
|
+
* "metadata": { "policyDenialCode": "PER_TX_MIN" },
|
|
22
|
+
* "safeToRetry": false,
|
|
23
|
+
* "details": null
|
|
24
|
+
* },
|
|
25
|
+
* "meta": {
|
|
26
|
+
* "timestamp": "2026-05-07T...",
|
|
27
|
+
* "requestId": "...",
|
|
28
|
+
* "path": "/pt/redeem"
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/** Inner `error` block of the envelope. */
|
|
35
|
+
interface PafiErrorPayload {
|
|
36
|
+
type: PafiErrorType;
|
|
37
|
+
code: string;
|
|
38
|
+
message: string;
|
|
39
|
+
param?: string;
|
|
40
|
+
metadata?: Record<string, unknown>;
|
|
41
|
+
details?: unknown;
|
|
42
|
+
safeToRetry: boolean;
|
|
43
|
+
}
|
|
44
|
+
/** Outer envelope returned for any non-2xx response. */
|
|
45
|
+
interface PafiErrorEnvelope {
|
|
46
|
+
success: false;
|
|
47
|
+
statusCode: number;
|
|
48
|
+
error: PafiErrorPayload;
|
|
49
|
+
meta: {
|
|
50
|
+
timestamp: string;
|
|
51
|
+
requestId: string;
|
|
52
|
+
path: string;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** Per-call request context the filter must collect from the host framework. */
|
|
56
|
+
interface NormalizeContext {
|
|
57
|
+
/** `req.url` or equivalent. */
|
|
58
|
+
path: string;
|
|
59
|
+
/** Resolved request id (header or generated). */
|
|
60
|
+
requestId: string;
|
|
61
|
+
/** Optional `now()` injection for deterministic tests. */
|
|
62
|
+
now?: () => Date;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Generic error duck-type. The filter passes a small descriptor for
|
|
66
|
+
* any framework exception so the normalizer doesn't depend on
|
|
67
|
+
* `@nestjs/common` or any specific HTTP library.
|
|
68
|
+
*/
|
|
69
|
+
interface GenericHttpExceptionDescriptor {
|
|
70
|
+
/** HTTP status code carried by the exception. */
|
|
71
|
+
statusCode: number;
|
|
72
|
+
/**
|
|
73
|
+
* The body the framework would have serialized. For NestJS this is
|
|
74
|
+
* `exception.getResponse()`. May be a string or a record with
|
|
75
|
+
* `code`/`error`/`message`/`details`/`safeToRetry`/`type`/etc.
|
|
76
|
+
*/
|
|
77
|
+
responseBody: unknown;
|
|
78
|
+
/** Exception class name, used as a last-resort `code` fallback. */
|
|
79
|
+
exceptionName: string;
|
|
80
|
+
/** Original `error.message`, used as a last-resort `message` fallback. */
|
|
81
|
+
fallbackMessage: string;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Convert a `PafiSdkError` directly to the inner `error` payload —
|
|
85
|
+
* skipping any framework exception wrapper. Used by frameworks that
|
|
86
|
+
* surface SDK errors without going through `createSdkErrorMapper`.
|
|
87
|
+
*/
|
|
88
|
+
declare function payloadFromPafiSdkError(err: PafiSdkError): PafiErrorPayload;
|
|
89
|
+
/**
|
|
90
|
+
* Build the full envelope for any thrown value. The caller resolves
|
|
91
|
+
* `HttpException`-shaped exceptions and passes a descriptor; plain
|
|
92
|
+
* `Error` instances and unknown throws are handled directly.
|
|
93
|
+
*/
|
|
94
|
+
declare function buildErrorEnvelope(input: {
|
|
95
|
+
status: number;
|
|
96
|
+
payload: PafiErrorPayload;
|
|
97
|
+
ctx: NormalizeContext;
|
|
98
|
+
}): PafiErrorEnvelope;
|
|
99
|
+
/**
|
|
100
|
+
* Normalize a known `HttpException`-shaped exception into a payload.
|
|
101
|
+
* Framework filters supply the descriptor; the rest is shape-agnostic.
|
|
102
|
+
*/
|
|
103
|
+
declare function payloadFromHttpException(desc: GenericHttpExceptionDescriptor): PafiErrorPayload;
|
|
104
|
+
/**
|
|
105
|
+
* Normalize a generic `Error` (not an HttpException) — TypeORM
|
|
106
|
+
* `QueryFailedError`, viem revert errors, etc. Returns a redacted
|
|
107
|
+
* 500-class payload with no message bleed-through unless the error
|
|
108
|
+
* is recognizably benign.
|
|
109
|
+
*/
|
|
110
|
+
declare function payloadFromGenericError(err: Error): PafiErrorPayload;
|
|
111
|
+
|
|
112
|
+
export { type GenericHttpExceptionDescriptor, type NormalizeContext, type PafiErrorEnvelope, type PafiErrorPayload, buildErrorEnvelope, payloadFromGenericError, payloadFromHttpException, payloadFromPafiSdkError };
|