@rpcbase/api 0.147.0 → 0.149.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/errors.d.ts +48 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +158 -7
- package/dist/index.js.map +1 -1
- package/dist/initApi.d.ts +5 -2
- package/dist/initApi.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { ErrorRequestHandler, Request, RequestHandler, Response } from 'express';
|
|
2
|
+
export declare const API_REQUEST_ID_HEADER = "x-request-id";
|
|
3
|
+
export type ApiErrorFields = Record<string, string[]>;
|
|
4
|
+
export type ApiErrorResponse = {
|
|
5
|
+
success: false;
|
|
6
|
+
error: string;
|
|
7
|
+
message?: string;
|
|
8
|
+
fields?: ApiErrorFields;
|
|
9
|
+
requestId: string;
|
|
10
|
+
};
|
|
11
|
+
export type ApiHttpErrorOptions = {
|
|
12
|
+
message?: string;
|
|
13
|
+
fields?: ApiErrorFields;
|
|
14
|
+
cause?: unknown;
|
|
15
|
+
};
|
|
16
|
+
export declare const isApiErrorCode: (value: unknown) => value is string;
|
|
17
|
+
export declare class ApiHttpError extends Error {
|
|
18
|
+
readonly __rpcbaseApiHttpError = true;
|
|
19
|
+
readonly status: number;
|
|
20
|
+
readonly code: string;
|
|
21
|
+
readonly publicMessage?: string;
|
|
22
|
+
readonly fields?: ApiErrorFields;
|
|
23
|
+
constructor(status: number, code: string, options?: ApiHttpErrorOptions);
|
|
24
|
+
}
|
|
25
|
+
type ApiHttpErrorLike = Error & {
|
|
26
|
+
__rpcbaseApiHttpError: true;
|
|
27
|
+
status: number;
|
|
28
|
+
code: string;
|
|
29
|
+
publicMessage?: string;
|
|
30
|
+
fields?: ApiErrorFields;
|
|
31
|
+
};
|
|
32
|
+
export declare const isApiHttpError: (error: unknown) => error is ApiHttpErrorLike;
|
|
33
|
+
export declare const getApiRequestId: (res: Response) => string;
|
|
34
|
+
export declare const apiRequestIdMiddleware: RequestHandler;
|
|
35
|
+
export declare const toApiErrorResponse: (error: Pick<ApiHttpErrorLike, "code" | "publicMessage" | "fields">, requestId: string) => ApiErrorResponse;
|
|
36
|
+
export declare const sendApiHttpError: (res: Response, error: ApiHttpErrorLike) => void;
|
|
37
|
+
export type ApiErrorMiddlewareContext = {
|
|
38
|
+
requestId: string;
|
|
39
|
+
req: Request;
|
|
40
|
+
};
|
|
41
|
+
export type ApiErrorMiddlewareOptions = {
|
|
42
|
+
internalMessage?: string;
|
|
43
|
+
onError?: (error: unknown, context: ApiErrorMiddlewareContext) => void | Promise<void>;
|
|
44
|
+
};
|
|
45
|
+
export declare const createApiErrorMiddleware: (options?: ApiErrorMiddlewareOptions) => ErrorRequestHandler;
|
|
46
|
+
export declare const apiErrorMiddleware: ErrorRequestHandler<import('express-serve-static-core').ParamsDictionary, any, any, import('qs').ParsedQs, Record<string, any>>;
|
|
47
|
+
export {};
|
|
48
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAGrF,eAAO,MAAM,qBAAqB,iBAAiB,CAAA;AAKnD,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;AAErD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,KAAK,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,cAAc,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,cAAc,CAAA;IACvB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB,CAAA;AAED,eAAO,MAAM,cAAc,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,MACQ,CAAA;AAEjE,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,qBAAqB,QAAO;IACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAA;gBAEpB,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB;CAgB5E;AAED,KAAK,gBAAgB,GAAG,KAAK,GAAG;IAC9B,qBAAqB,EAAE,IAAI,CAAA;IAC3B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,MAAM,CAAC,EAAE,cAAc,CAAA;CACxB,CAAA;AAED,eAAO,MAAM,cAAc,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,gBAWxD,CAAA;AAqCD,eAAO,MAAM,eAAe,GAAI,KAAK,QAAQ,KAAG,MAuB/C,CAAA;AAED,eAAO,MAAM,sBAAsB,EAAE,cAGpC,CAAA;AAED,eAAO,MAAM,kBAAkB,GAC7B,OAAO,IAAI,CAAC,gBAAgB,EAAE,MAAM,GAAG,eAAe,GAAG,QAAQ,CAAC,EAClE,WAAW,MAAM,KAChB,gBAMD,CAAA;AAEF,eAAO,MAAM,gBAAgB,GAAI,KAAK,QAAQ,EAAE,OAAO,gBAAgB,KAAG,IAGzE,CAAA;AAED,MAAM,MAAM,yBAAyB,GAAG;IACtC,SAAS,EAAE,MAAM,CAAA;IACjB,GAAG,EAAE,OAAO,CAAA;CACb,CAAA;AAED,MAAM,MAAM,yBAAyB,GAAG;IACtC,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,yBAAyB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACvF,CAAA;AA6BD,eAAO,MAAM,wBAAwB,GACnC,UAAS,yBAA8B,KACtC,mBA6BF,CAAA;AAED,eAAO,MAAM,kBAAkB,iIAA6B,CAAA"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAA;AACvB,cAAc,WAAW,CAAA;AACzB,cAAc,cAAc,CAAA;AAC5B,cAAc,mBAAmB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAA;AACvB,cAAc,WAAW,CAAA;AACzB,cAAc,UAAU,CAAA;AACxB,cAAc,cAAc,CAAA;AAC5B,cAAc,mBAAmB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -17,6 +17,7 @@ import * as m$3 from "node:querystring";
|
|
|
17
17
|
import * as m$2 from "node:buffer";
|
|
18
18
|
import * as m$1 from "node:net";
|
|
19
19
|
import * as m from "util";
|
|
20
|
+
import { randomUUID } from "node:crypto";
|
|
20
21
|
//#region \0rolldown/runtime.js
|
|
21
22
|
var __create = Object.create;
|
|
22
23
|
var __defProp = Object.defineProperty;
|
|
@@ -27367,10 +27368,136 @@ var require_lib = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
27367
27368
|
})();
|
|
27368
27369
|
}));
|
|
27369
27370
|
//#endregion
|
|
27370
|
-
//#region src/
|
|
27371
|
+
//#region src/errors.ts
|
|
27371
27372
|
var import_bluebird = /* @__PURE__ */ __toESM(require_bluebird(), 1);
|
|
27372
27373
|
var import_express = require_express();
|
|
27373
27374
|
var import_lib = /* @__PURE__ */ __toESM(require_lib(), 1);
|
|
27375
|
+
var API_REQUEST_ID_HEADER = "x-request-id";
|
|
27376
|
+
var API_REQUEST_ID_LOCAL = "rpcbaseApiRequestId";
|
|
27377
|
+
var API_ERROR_CODE_PATTERN = /^[a-z][A-Za-z0-9_.:-]*$/u;
|
|
27378
|
+
var isApiErrorCode = (value) => typeof value === "string" && API_ERROR_CODE_PATTERN.test(value);
|
|
27379
|
+
var ApiHttpError = class extends Error {
|
|
27380
|
+
__rpcbaseApiHttpError = true;
|
|
27381
|
+
status;
|
|
27382
|
+
code;
|
|
27383
|
+
publicMessage;
|
|
27384
|
+
fields;
|
|
27385
|
+
constructor(status, code, options = {}) {
|
|
27386
|
+
if (!Number.isInteger(status) || status < 400 || status > 599) throw new RangeError("ApiHttpError status must be an integer between 400 and 599");
|
|
27387
|
+
if (!isApiErrorCode(code)) throw new TypeError("ApiHttpError code must start with a lowercase letter and contain only letters, numbers, '.', ':', '_' or '-'");
|
|
27388
|
+
super(options.message ?? code, { cause: options.cause });
|
|
27389
|
+
this.name = "ApiHttpError";
|
|
27390
|
+
this.status = status;
|
|
27391
|
+
this.code = code;
|
|
27392
|
+
this.publicMessage = options.message;
|
|
27393
|
+
this.fields = options.fields;
|
|
27394
|
+
}
|
|
27395
|
+
};
|
|
27396
|
+
var isApiHttpError = (error) => {
|
|
27397
|
+
if (error instanceof ApiHttpError) return true;
|
|
27398
|
+
if (!error || typeof error !== "object") return false;
|
|
27399
|
+
const candidate = error;
|
|
27400
|
+
return candidate.__rpcbaseApiHttpError === true && Number.isInteger(candidate.status) && typeof candidate.status === "number" && candidate.status >= 400 && candidate.status <= 599 && isApiErrorCode(candidate.code);
|
|
27401
|
+
};
|
|
27402
|
+
var getExposedApiHttpError = (error) => {
|
|
27403
|
+
if (!error || typeof error !== "object") return null;
|
|
27404
|
+
const candidate = error;
|
|
27405
|
+
if (candidate.expose !== true) return null;
|
|
27406
|
+
const status = [candidate.status, candidate.statusCode].find((value) => typeof value === "number" && Number.isInteger(value) && value >= 400 && value < 500);
|
|
27407
|
+
if (typeof status !== "number") return null;
|
|
27408
|
+
const code = isApiErrorCode(candidate.type) ? candidate.type : isApiErrorCode(candidate.code) ? candidate.code : "http_error";
|
|
27409
|
+
const message = typeof candidate.message === "string" && candidate.message.trim() ? candidate.message.trim() : void 0;
|
|
27410
|
+
return new ApiHttpError(status, code, {
|
|
27411
|
+
...message ? { message } : {},
|
|
27412
|
+
cause: error
|
|
27413
|
+
});
|
|
27414
|
+
};
|
|
27415
|
+
var getApiRequestId = (res) => {
|
|
27416
|
+
res.locals ??= {};
|
|
27417
|
+
const existing = res.locals[API_REQUEST_ID_LOCAL];
|
|
27418
|
+
if (typeof existing === "string" && existing.trim()) {
|
|
27419
|
+
const requestId = existing.trim();
|
|
27420
|
+
res.locals[API_REQUEST_ID_LOCAL] = requestId;
|
|
27421
|
+
if (!res.headersSent) res.setHeader(API_REQUEST_ID_HEADER, requestId);
|
|
27422
|
+
return requestId;
|
|
27423
|
+
}
|
|
27424
|
+
const existingHeader = res.getHeader(API_REQUEST_ID_HEADER);
|
|
27425
|
+
if (typeof existingHeader === "string" && existingHeader.trim()) {
|
|
27426
|
+
const requestId = existingHeader.trim();
|
|
27427
|
+
res.locals[API_REQUEST_ID_LOCAL] = requestId;
|
|
27428
|
+
if (!res.headersSent) res.setHeader(API_REQUEST_ID_HEADER, requestId);
|
|
27429
|
+
return requestId;
|
|
27430
|
+
}
|
|
27431
|
+
const requestId = randomUUID();
|
|
27432
|
+
res.locals[API_REQUEST_ID_LOCAL] = requestId;
|
|
27433
|
+
if (!res.headersSent) res.setHeader(API_REQUEST_ID_HEADER, requestId);
|
|
27434
|
+
return requestId;
|
|
27435
|
+
};
|
|
27436
|
+
var apiRequestIdMiddleware = (_req, res, next) => {
|
|
27437
|
+
getApiRequestId(res);
|
|
27438
|
+
next();
|
|
27439
|
+
};
|
|
27440
|
+
var toApiErrorResponse = (error, requestId) => ({
|
|
27441
|
+
success: false,
|
|
27442
|
+
error: error.code,
|
|
27443
|
+
...error.publicMessage ? { message: error.publicMessage } : {},
|
|
27444
|
+
...error.fields ? { fields: error.fields } : {},
|
|
27445
|
+
requestId
|
|
27446
|
+
});
|
|
27447
|
+
var sendApiHttpError = (res, error) => {
|
|
27448
|
+
const requestId = getApiRequestId(res);
|
|
27449
|
+
res.status(error.status).json(toApiErrorResponse(error, requestId));
|
|
27450
|
+
};
|
|
27451
|
+
var reportApiError = (error, context, onError) => {
|
|
27452
|
+
if (onError) {
|
|
27453
|
+
try {
|
|
27454
|
+
const reportingResult = onError(error, context);
|
|
27455
|
+
if (reportingResult && typeof reportingResult.then === "function") reportingResult.catch((reportingError) => {
|
|
27456
|
+
console.error("[rpcbase/api] error reporter failed", reportingError);
|
|
27457
|
+
});
|
|
27458
|
+
} catch (reportingError) {
|
|
27459
|
+
console.error("[rpcbase/api] error reporter failed", reportingError);
|
|
27460
|
+
}
|
|
27461
|
+
return;
|
|
27462
|
+
}
|
|
27463
|
+
console.error("[rpcbase/api] API error", {
|
|
27464
|
+
requestId: context.requestId,
|
|
27465
|
+
method: context.req.method,
|
|
27466
|
+
path: context.req.originalUrl,
|
|
27467
|
+
error
|
|
27468
|
+
});
|
|
27469
|
+
};
|
|
27470
|
+
var createApiErrorMiddleware = (options = {}) => (error, req, res, next) => {
|
|
27471
|
+
if (res.headersSent) {
|
|
27472
|
+
next(error);
|
|
27473
|
+
return;
|
|
27474
|
+
}
|
|
27475
|
+
const requestId = getApiRequestId(res);
|
|
27476
|
+
const context = {
|
|
27477
|
+
requestId,
|
|
27478
|
+
req
|
|
27479
|
+
};
|
|
27480
|
+
if (isApiHttpError(error)) {
|
|
27481
|
+
if (error.status >= 500) reportApiError(error, context, options.onError);
|
|
27482
|
+
sendApiHttpError(res, error);
|
|
27483
|
+
return;
|
|
27484
|
+
}
|
|
27485
|
+
const exposedError = getExposedApiHttpError(error);
|
|
27486
|
+
if (exposedError) {
|
|
27487
|
+
sendApiHttpError(res, exposedError);
|
|
27488
|
+
return;
|
|
27489
|
+
}
|
|
27490
|
+
reportApiError(error, context, options.onError);
|
|
27491
|
+
res.status(500).json({
|
|
27492
|
+
success: false,
|
|
27493
|
+
error: "internal_error",
|
|
27494
|
+
...options.internalMessage ? { message: options.internalMessage } : {},
|
|
27495
|
+
requestId
|
|
27496
|
+
});
|
|
27497
|
+
};
|
|
27498
|
+
var apiErrorMiddleware = createApiErrorMiddleware();
|
|
27499
|
+
//#endregion
|
|
27500
|
+
//#region src/initApi.ts
|
|
27374
27501
|
var loadInitApiClient = async () => {
|
|
27375
27502
|
return (await import("@rpcbase/client")).initApiClient;
|
|
27376
27503
|
};
|
|
@@ -27379,12 +27506,21 @@ process.env = {
|
|
|
27379
27506
|
...__rb_env__,
|
|
27380
27507
|
...process.env
|
|
27381
27508
|
};
|
|
27382
|
-
var
|
|
27509
|
+
var withApiRequestIdExposed = (options) => {
|
|
27510
|
+
const configured = options?.exposedHeaders;
|
|
27511
|
+
const exposedHeaders = Array.isArray(configured) ? [...configured] : configured ? [configured] : [];
|
|
27512
|
+
if (!exposedHeaders.some((header) => header.toLowerCase() === "x-request-id")) exposedHeaders.push(API_REQUEST_ID_HEADER);
|
|
27513
|
+
return {
|
|
27514
|
+
...options,
|
|
27515
|
+
exposedHeaders
|
|
27516
|
+
};
|
|
27517
|
+
};
|
|
27518
|
+
var initApi = async ({ app, routes, cors: corsOptions, errors }) => {
|
|
27383
27519
|
await (await loadInitApiClient())({ app });
|
|
27384
27520
|
const registerHandler = (method, path, handler) => {
|
|
27385
27521
|
assert(path.startsWith("/api"), "API routes must start with /api");
|
|
27386
27522
|
app[method](path, async (req, res, next) => {
|
|
27387
|
-
const payload = req.body
|
|
27523
|
+
const payload = method === "get" ? req.query : req.body ?? {};
|
|
27388
27524
|
try {
|
|
27389
27525
|
const result = await handler(payload, {
|
|
27390
27526
|
req,
|
|
@@ -27415,11 +27551,12 @@ var initApi = async ({ app, routes, cors: corsOptions }) => {
|
|
|
27415
27551
|
registerHandler("delete", path, handler);
|
|
27416
27552
|
}
|
|
27417
27553
|
};
|
|
27418
|
-
app.use("/api", (0, import_lib.default)(corsOptions));
|
|
27554
|
+
app.use("/api", (0, import_lib.default)(withApiRequestIdExposed(corsOptions)));
|
|
27419
27555
|
app.use("/api", (0, import_express.json)({ limit: "8mb" }));
|
|
27420
27556
|
app.use("/api", function __FIRST_API_MIDDLEWARE__(_req, _res, next) {
|
|
27421
27557
|
next();
|
|
27422
27558
|
});
|
|
27559
|
+
app.use("/api", apiRequestIdMiddleware);
|
|
27423
27560
|
const routeEntries = Object.entries(routes);
|
|
27424
27561
|
const totalRoutes = routeEntries.length;
|
|
27425
27562
|
const loadedModules = /* @__PURE__ */ new Map();
|
|
@@ -27443,19 +27580,33 @@ var initApi = async ({ app, routes, cors: corsOptions }) => {
|
|
|
27443
27580
|
}
|
|
27444
27581
|
console.log("Done loading routes");
|
|
27445
27582
|
app.use("/api", (req, res) => {
|
|
27446
|
-
|
|
27583
|
+
const requestId = getApiRequestId(res);
|
|
27584
|
+
res.status(404).json(toApiErrorResponse(new ApiHttpError(404, "not_found", { message: `Not Found: ${req.method}:${req.originalUrl}` }), requestId));
|
|
27447
27585
|
});
|
|
27448
27586
|
app.use("/api", (err, _req, res, next) => {
|
|
27449
27587
|
if (res.headersSent) return next(err);
|
|
27588
|
+
if (isApiHttpError(err)) {
|
|
27589
|
+
next(err);
|
|
27590
|
+
return;
|
|
27591
|
+
}
|
|
27450
27592
|
const errLike = err;
|
|
27451
27593
|
const code = typeof errLike?.code === "string" ? errLike.code : null;
|
|
27452
27594
|
const statusCode = typeof errLike?.statusCode === "number" ? errLike.statusCode : typeof errLike?.status === "number" ? errLike.status : null;
|
|
27453
27595
|
if (code === "invalid_pagination") {
|
|
27454
|
-
|
|
27596
|
+
next(new ApiHttpError(statusCode && Number.isInteger(statusCode) && statusCode >= 400 && statusCode <= 599 ? statusCode : 400, code));
|
|
27597
|
+
return;
|
|
27598
|
+
}
|
|
27599
|
+
if (errLike.type === "entity.parse.failed") {
|
|
27600
|
+
next(new ApiHttpError(400, "invalid_json", { message: "Request body must contain valid JSON." }));
|
|
27601
|
+
return;
|
|
27602
|
+
}
|
|
27603
|
+
if (errLike.type === "entity.too.large") {
|
|
27604
|
+
next(new ApiHttpError(413, "payload_too_large", { message: "Request body is too large." }));
|
|
27455
27605
|
return;
|
|
27456
27606
|
}
|
|
27457
27607
|
next(err);
|
|
27458
27608
|
});
|
|
27609
|
+
app.use("/api", createApiErrorMiddleware(errors));
|
|
27459
27610
|
};
|
|
27460
27611
|
//#endregion
|
|
27461
27612
|
//#region src/agentApiCatalog.ts
|
|
@@ -27471,6 +27622,6 @@ var AGENT_API_BLOCKED_REASONS = [
|
|
|
27471
27622
|
"other"
|
|
27472
27623
|
];
|
|
27473
27624
|
//#endregion
|
|
27474
|
-
export { AGENT_API_BLOCKED_REASONS, MOCK_TYPE, initApi };
|
|
27625
|
+
export { AGENT_API_BLOCKED_REASONS, API_REQUEST_ID_HEADER, ApiHttpError, MOCK_TYPE, apiErrorMiddleware, apiRequestIdMiddleware, createApiErrorMiddleware, getApiRequestId, initApi, isApiErrorCode, isApiHttpError, sendApiHttpError, toApiErrorResponse };
|
|
27475
27626
|
|
|
27476
27627
|
//# sourceMappingURL=index.js.map
|