@salespark/route-utils 1.0.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/README.md ADDED
@@ -0,0 +1,217 @@
1
+ # SalesPark Route Utils v1 - Documentation
2
+
3
+ ## @salespark/route-utils
4
+
5
+ Vendor-agnostic helpers for **Express.js** routes.
6
+ Provides a unified way to wrap async route handlers, normalize responses, and handle logging without binding to a specific vendor (e.g., Rollbar, Sentry, Console).
7
+
8
+ By default, if no logger is provided, **no logs are emitted** (silent mode).
9
+
10
+ ---
11
+
12
+ ## 📦 Installation
13
+
14
+ ```bash
15
+ npm install @salespark/route-utils
16
+ # or
17
+ yarn add @salespark/route-utils
18
+ ```
19
+
20
+ Supports both **CommonJS** and **ESM** imports.
21
+
22
+ ```ts
23
+ // ESM
24
+ import { wrapRoute, createResponder, makeRouteUtils, resolveRouteResponse } from "@salespark/route-utils";
25
+
26
+ // CommonJS
27
+ const { wrapRoute, createResponder, makeRouteUtils, resolveRouteResponse } = require("@salespark/route-utils");
28
+ ```
29
+
30
+ ---
31
+
32
+ ## 🚀 Introduction
33
+
34
+ This utility reduces boilerplate in Express.js routes by enforcing:
35
+
36
+ - Consistent response shape across all endpoints
37
+ - Safe error handling with `try/catch` wrappers
38
+ - Prevention of double responses (`res.headersSent`)
39
+ - Configurable vendor-agnostic logging (console, Rollbar, Sentry, etc.)
40
+ - Predictable HTTP status codes for both success and error cases
41
+
42
+ ---
43
+
44
+ ## 📐 Response Shape Specification
45
+
46
+ All route handlers must return an object with the following structure:
47
+
48
+ ```ts
49
+ {
50
+ status: boolean; // required: true for success, false for failure
51
+ data?: any; // optional: payload for success or error
52
+ http?: number; // optional: explicit HTTP status code
53
+ headers?: Record<string,any>;// optional: extra HTTP headers to set
54
+ meta?: any; // optional: metadata (e.g., pagination info)
55
+ }
56
+ ```
57
+
58
+ ### Status Rules
59
+
60
+ - ✅ `status: true` → Success
61
+ - ❌ `status: false` → Failure
62
+ - 🚨 Missing `status` → treated as malformed response (`500` with `MissingStatus`)
63
+
64
+ ### Default HTTP Mapping
65
+
66
+ - Success with data → `200 OK`
67
+ - Success without data (`null`, `undefined`, `""`) → `204 No Content`
68
+ - Failure → `400 Bad Request`
69
+ - Explicit `http` value (100–599) always overrides
70
+
71
+ ---
72
+
73
+ ## 🏗️ Factory: `makeRouteUtils`
74
+
75
+ ```ts
76
+ import { makeRouteUtils } from "@salespark/route-utils";
77
+
78
+ const { wrapRoute, createResponder, resolveRouteResponse } = makeRouteUtils({
79
+ logger: console.error, // or rollbar.error, sentry.captureException, etc.
80
+ tagPrefix: "/routes/producers", // optional prefix for logs
81
+ });
82
+ ```
83
+
84
+ ### Parameters
85
+
86
+ - **`logger`**: `(payload, tag) => void`
87
+ - **`tagPrefix`**: string (optional)
88
+
89
+ ### Returns
90
+
91
+ - `wrapRoute`
92
+ - `createResponder`
93
+ - `resolveRouteResponse`
94
+
95
+ ---
96
+
97
+ ## ⚡ Functions
98
+
99
+ ### `wrapRoute`
100
+
101
+ ```ts
102
+ router.get(
103
+ "/achievements",
104
+ validateAuth,
105
+ wrapRoute(
106
+ async (req, res) => {
107
+ const producerId = res.locals.auth.producer_id;
108
+ return ops.getProducerAchievements(producerId);
109
+ },
110
+ { tag: "GET /achievements" }
111
+ )
112
+ );
113
+ ```
114
+
115
+ ---
116
+
117
+ ### `createResponder`
118
+
119
+ ```ts
120
+ router.get("/achievements", validateAuth, async (req, res) => {
121
+ const respond = createResponder(res, { tag: "GET /achievements" });
122
+ await respond(() => ops.getProducerAchievements(res.locals.auth.producer_id));
123
+ });
124
+ ```
125
+
126
+ ---
127
+
128
+ ### `resolveRouteResponse`
129
+
130
+ ```ts
131
+ const response = await ops.doSomething();
132
+ if (!resolveRouteResponse(res, response)) {
133
+ res.status(500).json({ status: false, error: "No response sent" });
134
+ }
135
+ ```
136
+
137
+ ---
138
+
139
+ ## 🧪 Usage Examples
140
+
141
+ ### Example 1: Silent Mode (default)
142
+
143
+ ```ts
144
+ app.get(
145
+ "/ping",
146
+ wrapRoute(async () => ({ status: true, data: "pong" }))
147
+ );
148
+ ```
149
+
150
+ ### Example 2: With Console Logger
151
+
152
+ ```ts
153
+ const { wrapRoute } = makeRouteUtils({
154
+ logger: (payload, tag) => console.log("[LOG]", tag, payload),
155
+ });
156
+ ```
157
+
158
+ ### Example 3: With Rollbar
159
+
160
+ ```ts
161
+ const rollbar = require("./rollbar");
162
+
163
+ const { wrapRoute } = makeRouteUtils({
164
+ logger: rollbar.error.bind(rollbar),
165
+ tagPrefix: "[API] ",
166
+ });
167
+ ```
168
+
169
+ ### Example 4: POST with Validation
170
+
171
+ ```ts
172
+ app.post(
173
+ "/items",
174
+ wrapRoute(async (req) => {
175
+ if (!req.body.name) {
176
+ return { status: false, data: { error: "ValidationError", message: "Name required" }, http: 422 };
177
+ }
178
+ return { status: true, data: { id: 1, name: req.body.name }, http: 201 };
179
+ })
180
+ );
181
+ ```
182
+
183
+ ### Example 5: Using Sentry
184
+
185
+ ```ts
186
+ import * as Sentry from "@sentry/node";
187
+
188
+ Sentry.init({ dsn: process.env.SENTRY_DSN });
189
+
190
+ const { wrapRoute } = makeRouteUtils({
191
+ logger: (payload, tag) => {
192
+ Sentry.captureException(payload instanceof Error ? payload : new Error(JSON.stringify(payload)));
193
+ },
194
+ });
195
+ ```
196
+
197
+ ---
198
+
199
+ ## 📦 NPM Package
200
+
201
+ This module is published as:
202
+ 👉 [`@salespark/route-utils`](https://www.npmjs.com/package/@salespark/route-utils)
203
+
204
+ ```bash
205
+ npm install @salespark/route-utils
206
+ ```
207
+
208
+ ---
209
+
210
+ ## 📄 License
211
+
212
+ MIT © [SalesPark](https://salespark.io)
213
+
214
+ ---
215
+
216
+ _Document version: 1_
217
+ _Last update: 16-08-2025_
@@ -0,0 +1,127 @@
1
+ /****************************************************************************************************************
2
+ * ##: Route Utils (TypeScript)
3
+ * Vendor-agnostic helpers for Express-like route handlers.
4
+ *
5
+ * What you get:
6
+ * - Normalized responses: { status:boolean, data?, http?, headers?, meta? }
7
+ * - Safe guards against double responses (res.headersSent)
8
+ * - No vendor lock-in for logging (optional logger function, silent by default)
9
+ * - Two ergonomics:
10
+ * a) wrapRoute(handler, { tag?, logger? }) -> middleware
11
+ * b) createResponder(res, { tag?, logger? }) -> in-route responder
12
+ * - Core primitive: resolveRouteResponse(res, response) -> boolean
13
+ *
14
+ * Logging:
15
+ * - Provide a function logger(payload, tag?) if you want logs (e.g. rollbar.error.bind(rollbar))
16
+ * - If omitted, nothing is logged (silent mode)
17
+ *
18
+ * History:
19
+ * 16-08-2025: Initial version
20
+ ****************************************************************************************************************/
21
+ export type LoggerFn = (payload: unknown, tag?: unknown) => void;
22
+ /**
23
+ * Minimal response interface to avoid a hard dependency on Express types.
24
+ * Any Express-like object with these methods/properties is supported.
25
+ */
26
+ export interface ResLike {
27
+ status: (code: number) => ResLike;
28
+ json: (body?: unknown) => ResLike;
29
+ send: (body?: unknown) => ResLike;
30
+ setHeader?: (name: string, value: string) => void;
31
+ headersSent?: boolean;
32
+ [key: string]: any;
33
+ }
34
+ /**
35
+ * Normalized API response contract used throughout the utilities.
36
+ * - `status` is mandatory and must be boolean.
37
+ * - `http` is optional and, when valid, overrides default status code mapping.
38
+ * - `headers` are optional additional response headers to apply.
39
+ */
40
+ export interface ApiResponse<T = unknown, M = unknown> {
41
+ status: boolean;
42
+ data?: T;
43
+ http?: number;
44
+ headers?: Record<string, string>;
45
+ meta?: M;
46
+ }
47
+ /****************************************************************************************************************
48
+ * ##: Factory
49
+ * makeRouteUtils({ logger?, tagPrefix? })
50
+ *
51
+ * Creates helpers bound to provided defaults, so you can avoid passing the same logger/tag repeatedly.
52
+ *
53
+ * @param logger Optional function `(payload, tag?) => void`. If omitted, no logs are emitted.
54
+ * @param tagPrefix Optional prefix applied to all generated tags (e.g., a file or module path).
55
+ *
56
+ * @returns { wrapRoute, createResponder, resolveRouteResponse }
57
+ * History:
58
+ * 16-08-2025: Created
59
+ ****************************************************************************************************************/
60
+ export declare const makeRouteUtils: ({ logger, tagPrefix, }?: {
61
+ logger?: LoggerFn | undefined;
62
+ tagPrefix?: string | undefined;
63
+ }) => {
64
+ wrapRoute: (handler: (req: any, res: ResLike) => Promise<ApiResponse> | ApiResponse, options?: {
65
+ tag?: string | undefined;
66
+ logger?: LoggerFn | undefined;
67
+ }) => (req: any, res: ResLike, _next?: any) => Promise<void>;
68
+ createResponder: (res: ResLike, options?: {
69
+ tag?: string | undefined;
70
+ logger?: LoggerFn | undefined;
71
+ }) => (workFn: () => Promise<ApiResponse> | ApiResponse) => Promise<boolean>;
72
+ resolveRouteResponse: (res: ResLike, response: any) => boolean;
73
+ };
74
+ /****************************************************************************************************************
75
+ * ##: Core Primitive - Resolve Route Response
76
+ * resolveRouteResponse(res, response) => boolean
77
+ *
78
+ * Normalizes and sends the HTTP response based on the ApiResponse contract:
79
+ * - Validates the `res` object
80
+ * - Prevents double responses (`headersSent`)
81
+ * - Applies optional headers
82
+ * - Chooses HTTP status code (custom `http` if valid; else 200/204/400 defaults)
83
+ * - Serializes success/error shapes predictably
84
+ *
85
+ * @param res Express-like response object
86
+ * @param response Expected ApiResponse (must include boolean `status`)
87
+ * @returns true if a response was sent; false if `res` looked invalid (no send)
88
+ * History:
89
+ * 16-08-2025: Created
90
+ ****************************************************************************************************************/
91
+ export declare const resolveRouteResponse: (res: ResLike, response: any) => boolean;
92
+ export declare const wrapRoute: (handler: (req: any, res: ResLike) => Promise<ApiResponse> | ApiResponse, options?: {
93
+ tag?: string | undefined;
94
+ logger?: LoggerFn | undefined;
95
+ }) => (req: any, res: ResLike, _next?: any) => Promise<void>;
96
+ export declare const createResponder: (res: ResLike, options?: {
97
+ tag?: string | undefined;
98
+ logger?: LoggerFn | undefined;
99
+ }) => (workFn: () => Promise<ApiResponse> | ApiResponse) => Promise<boolean>;
100
+ /** Default export bundle (optional convenience) */
101
+ declare const _default: {
102
+ makeRouteUtils: ({ logger, tagPrefix, }?: {
103
+ logger?: LoggerFn | undefined;
104
+ tagPrefix?: string | undefined;
105
+ }) => {
106
+ wrapRoute: (handler: (req: any, res: ResLike) => ApiResponse<unknown, unknown> | Promise<ApiResponse<unknown, unknown>>, options?: {
107
+ tag?: string | undefined;
108
+ logger?: LoggerFn | undefined;
109
+ }) => (req: any, res: ResLike, _next?: any) => Promise<void>;
110
+ createResponder: (res: ResLike, options?: {
111
+ tag?: string | undefined;
112
+ logger?: LoggerFn | undefined;
113
+ }) => (workFn: () => ApiResponse<unknown, unknown> | Promise<ApiResponse<unknown, unknown>>) => Promise<boolean>;
114
+ resolveRouteResponse: (res: ResLike, response: any) => boolean;
115
+ };
116
+ wrapRoute: (handler: (req: any, res: ResLike) => ApiResponse<unknown, unknown> | Promise<ApiResponse<unknown, unknown>>, options?: {
117
+ tag?: string | undefined;
118
+ logger?: LoggerFn | undefined;
119
+ }) => (req: any, res: ResLike, _next?: any) => Promise<void>;
120
+ createResponder: (res: ResLike, options?: {
121
+ tag?: string | undefined;
122
+ logger?: LoggerFn | undefined;
123
+ }) => (workFn: () => ApiResponse<unknown, unknown> | Promise<ApiResponse<unknown, unknown>>) => Promise<boolean>;
124
+ resolveRouteResponse: (res: ResLike, response: any) => boolean;
125
+ };
126
+ export default _default;
127
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;kHAmBkH;AAElH,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;AAEjE;;;GAGG;AACH,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAClC,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC;IAClC,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC;IAClC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClD,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO;IACnD,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,CAAC,CAAC;CACV;AAKD;;;;;;;;;;;;kHAYkH;AAClH,eAAO,MAAM,cAAc;;;;+BAoBS,GAAG,OAAO,OAAO,KAAK,QAAQ,WAAW,CAAC,GAAG,WAAW;;;gBAGrE,GAAG,OAAO,OAAO,UAAU,GAAG;2BAuCrB,OAAO;;;mBAIb,MAAM,QAAQ,WAAW,CAAC,GAAG,WAAW;gCA4CxB,OAAO,YAAY,GAAG,KAAG,OAAO;CAnBzE,CAAC;AAEF;;;;;;;;;;;;;;;;kHAgBkH;AAClH,eAAO,MAAM,oBAAoB,QAAS,OAAO,YAAY,GAAG,KAAG,OAiFlE,CAAC;AAOF,eAAO,MAAM,SAAS,kBAlLc,GAAG,OAAO,OAAO,KAAK,QAAQ,WAAW,CAAC,GAAG,WAAW;;;YAGrE,GAAG,OAAO,OAAO,UAAU,GAAG,kBA+KZ,CAAC;AAC1C,eAAO,MAAM,eAAe,QAzII,OAAO;;;eAIb,MAAM,QAAQ,WAAW,CAAC,GAAG,WAAW,qBAqIb,CAAC;AAEtD,mDAAmD;;;;;;;;;;;;;;;;;;;;;;;;;;AACnD,wBAKE"}
package/dist/index.js ADDED
@@ -0,0 +1,232 @@
1
+ "use strict";
2
+ /****************************************************************************************************************
3
+ * ##: Route Utils (TypeScript)
4
+ * Vendor-agnostic helpers for Express-like route handlers.
5
+ *
6
+ * What you get:
7
+ * - Normalized responses: { status:boolean, data?, http?, headers?, meta? }
8
+ * - Safe guards against double responses (res.headersSent)
9
+ * - No vendor lock-in for logging (optional logger function, silent by default)
10
+ * - Two ergonomics:
11
+ * a) wrapRoute(handler, { tag?, logger? }) -> middleware
12
+ * b) createResponder(res, { tag?, logger? }) -> in-route responder
13
+ * - Core primitive: resolveRouteResponse(res, response) -> boolean
14
+ *
15
+ * Logging:
16
+ * - Provide a function logger(payload, tag?) if you want logs (e.g. rollbar.error.bind(rollbar))
17
+ * - If omitted, nothing is logged (silent mode)
18
+ *
19
+ * History:
20
+ * 16-08-2025: Initial version
21
+ ****************************************************************************************************************/
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.createResponder = exports.wrapRoute = exports.resolveRouteResponse = exports.makeRouteUtils = void 0;
24
+ /** No-op logger (silent). Used when no logger function is provided. */
25
+ const noopLogger = () => { };
26
+ /****************************************************************************************************************
27
+ * ##: Factory
28
+ * makeRouteUtils({ logger?, tagPrefix? })
29
+ *
30
+ * Creates helpers bound to provided defaults, so you can avoid passing the same logger/tag repeatedly.
31
+ *
32
+ * @param logger Optional function `(payload, tag?) => void`. If omitted, no logs are emitted.
33
+ * @param tagPrefix Optional prefix applied to all generated tags (e.g., a file or module path).
34
+ *
35
+ * @returns { wrapRoute, createResponder, resolveRouteResponse }
36
+ * History:
37
+ * 16-08-2025: Created
38
+ ****************************************************************************************************************/
39
+ const makeRouteUtils = ({ logger = noopLogger, tagPrefix = "", } = {}) => {
40
+ /****************************************************************************************************************
41
+ * ##: wrapRoute(handler, options?)
42
+ * Express-style middleware wrapper:
43
+ * - Executes your async handler and expects an ApiResponse
44
+ * - Uses resolveRouteResponse to send output
45
+ * - Catches and logs unexpected shapes/errors (only if a logger is provided)
46
+ *
47
+ * @param handler Async route handler: (req, res) => ApiResponse | Promise<ApiResponse>
48
+ * @param options { tag?: string; logger?: LoggerFn }
49
+ * @returns (req, res, next) => Promise<void>
50
+ * History:
51
+ * 16-08-2025: Created
52
+ ****************************************************************************************************************/
53
+ const wrapRoute = (handler, options = {}) => {
54
+ const routeLogger = typeof options.logger === "function" ? options.logger : logger;
55
+ return async (req, res, _next) => {
56
+ // Build a tag for logging; default to METHOD + originalUrl
57
+ const baseTag = options.tag || `${req?.method ?? "?"} ${req?.originalUrl ?? "?"}`;
58
+ const tag = tagPrefix ? `${tagPrefix}${baseTag}` : baseTag;
59
+ try {
60
+ const response = await handler(req, res);
61
+ if ((0, exports.resolveRouteResponse)(res, response))
62
+ return; // Early exit: a response has been sent
63
+ // If we got here, the handler returned an unexpected shape
64
+ routeLogger({ message: "Unexpected response shape", response, route: tag }, tag);
65
+ if (!res.headersSent) {
66
+ res.status(500).json({ status: false, error: "UnexpectedResponseShape" });
67
+ }
68
+ }
69
+ catch (err) {
70
+ // Unhandled error in the handler
71
+ routeLogger(err, `${tag}/catch`);
72
+ if (!res.headersSent) {
73
+ res.status(500).json({ status: false, error: err?.message || "Unexpected error" });
74
+ }
75
+ }
76
+ };
77
+ };
78
+ /****************************************************************************************************************
79
+ * ##: createResponder(res, options?)
80
+ * In-route responder:
81
+ * - Lets you keep the route signature untouched
82
+ * - You call it with a work function returning an ApiResponse
83
+ * - Ensures normalized output and consistent error handling
84
+ *
85
+ * @param res Express-like response object
86
+ * @param options { tag?: string; logger?: LoggerFn }
87
+ * @returns (workFn) => Promise<true>
88
+ * History:
89
+ * 16-08-2025: Created
90
+ ****************************************************************************************************************/
91
+ const createResponder = (res, options = {}) => {
92
+ const tagBase = options.tag;
93
+ const routeLogger = typeof options.logger === "function" ? options.logger : logger;
94
+ return async (workFn) => {
95
+ try {
96
+ const response = await workFn();
97
+ if ((0, exports.resolveRouteResponse)(res, response))
98
+ return true; // Responded successfully
99
+ // Unexpected shape
100
+ routeLogger({ message: "Unexpected response shape", response }, tagBase || "createResponder");
101
+ if (!res.headersSent) {
102
+ res.status(500).json({ status: false, error: "UnexpectedResponseShape" });
103
+ }
104
+ return true;
105
+ }
106
+ catch (error) {
107
+ // Unhandled error inside workFn
108
+ routeLogger(error, `${tagBase || "createResponder"}/catch`);
109
+ if (!res.headersSent) {
110
+ res.status(500).json({ status: false, error: error?.message || "Unexpected error" });
111
+ }
112
+ return true;
113
+ }
114
+ };
115
+ };
116
+ return { wrapRoute, createResponder, resolveRouteResponse: exports.resolveRouteResponse };
117
+ };
118
+ exports.makeRouteUtils = makeRouteUtils;
119
+ /****************************************************************************************************************
120
+ * ##: Core Primitive - Resolve Route Response
121
+ * resolveRouteResponse(res, response) => boolean
122
+ *
123
+ * Normalizes and sends the HTTP response based on the ApiResponse contract:
124
+ * - Validates the `res` object
125
+ * - Prevents double responses (`headersSent`)
126
+ * - Applies optional headers
127
+ * - Chooses HTTP status code (custom `http` if valid; else 200/204/400 defaults)
128
+ * - Serializes success/error shapes predictably
129
+ *
130
+ * @param res Express-like response object
131
+ * @param response Expected ApiResponse (must include boolean `status`)
132
+ * @returns true if a response was sent; false if `res` looked invalid (no send)
133
+ * History:
134
+ * 16-08-2025: Created
135
+ ****************************************************************************************************************/
136
+ const resolveRouteResponse = (res, response) => {
137
+ try {
138
+ // Guard: ensure `res` looks like a valid Express-like response object
139
+ if (!res || typeof res.status !== "function" || typeof res.send !== "function")
140
+ return false;
141
+ // Prevent double responses
142
+ if (res.headersSent)
143
+ return true;
144
+ // Validate presence of `status`
145
+ const hasStatus = response && typeof response === "object" && Object.prototype.hasOwnProperty.call(response, "status");
146
+ if (!hasStatus) {
147
+ res.status(500).json({ status: false, error: "MissingStatus", message: "Missing `status` on response object" });
148
+ return true;
149
+ }
150
+ const isOk = response.status === true;
151
+ const isFail = response.status === false;
152
+ // Compute HTTP code:
153
+ // - If a valid custom `http` is provided (100–599), use it.
154
+ // - Otherwise:
155
+ // - Success with undefined/null/empty-string data => 204
156
+ // - Success with data => 200
157
+ // - Failure => 400
158
+ const explicit = Number.isInteger(response?.http) && response.http >= 100 && response.http <= 599;
159
+ const http = explicit ? response.http : isOk ? (response.data === undefined || response.data === null || response.data === "" ? 204 : 200) : 400;
160
+ // Apply optional headers
161
+ if (response?.headers && typeof response.headers === "object") {
162
+ for (const [k, v] of Object.entries(response.headers)) {
163
+ try {
164
+ res.setHeader?.(k, String(v));
165
+ }
166
+ catch {
167
+ /* ignore header errors */
168
+ }
169
+ }
170
+ }
171
+ // Success path
172
+ if (isOk) {
173
+ if (http === 204) {
174
+ res.status(http).send();
175
+ }
176
+ else {
177
+ const out = { status: true };
178
+ if (response.data !== undefined)
179
+ out.data = response.data;
180
+ if (response.meta !== undefined)
181
+ out.meta = response.meta;
182
+ res.status(http).json(out);
183
+ }
184
+ return true;
185
+ }
186
+ // Error path
187
+ if (isFail) {
188
+ const raw = response.data;
189
+ const errBody = raw instanceof Error
190
+ ? { error: raw.name || "Error", message: raw.message }
191
+ : raw && typeof raw === "object"
192
+ ? {
193
+ ...(raw.error ? { error: raw.error } : {}),
194
+ ...(raw.message ? { message: raw.message } : {}),
195
+ }
196
+ : { error: "RequestFailed", message: typeof raw === "string" ? raw : "Request failed" };
197
+ res.status(http).json({ status: false, ...errBody, ...(response.meta !== undefined ? { meta: response.meta } : {}) });
198
+ return true;
199
+ }
200
+ // Fallback: `status` is neither true nor false
201
+ res.status(500).json({ status: false, error: "InvalidStatusField", message: "`status` must be boolean" });
202
+ return true;
203
+ }
204
+ catch (error) {
205
+ // Absolute last resort: catch unexpected errors inside the responder
206
+ try {
207
+ if (!res.headersSent)
208
+ res.status(500).json({ status: false, error: "UnhandledResponderError", message: error?.message || "Unexpected error" });
209
+ return true;
210
+ }
211
+ catch {
212
+ // If even this fails, we assume there's nothing else we can safely do
213
+ return true;
214
+ }
215
+ }
216
+ };
217
+ exports.resolveRouteResponse = resolveRouteResponse;
218
+ /****************************************************************************************************************
219
+ * Pre-bound (silent) helpers
220
+ * These export a default, no-logger configuration so consumers can import directly without a factory.
221
+ ****************************************************************************************************************/
222
+ const silent = (0, exports.makeRouteUtils)(); // logger: noop (silent)
223
+ exports.wrapRoute = silent.wrapRoute;
224
+ exports.createResponder = silent.createResponder;
225
+ /** Default export bundle (optional convenience) */
226
+ exports.default = {
227
+ makeRouteUtils: exports.makeRouteUtils,
228
+ wrapRoute: exports.wrapRoute,
229
+ createResponder: exports.createResponder,
230
+ resolveRouteResponse: exports.resolveRouteResponse,
231
+ };
232
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@salespark/route-utils",
3
+ "version": "1.0.0",
4
+ "description": "Vendor-agnostic helpers for Express route handlers with normalized responses and optional logging.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "scripts": {
11
+ "build": "tsc -p tsconfig.build.json",
12
+ "clean": "rm -rf dist",
13
+ "prepublishOnly": "yarn run clean && yarn run build"
14
+ },
15
+ "keywords": [
16
+ "express",
17
+ "middleware",
18
+ "http",
19
+ "routes",
20
+ "api",
21
+ "response",
22
+ "logger",
23
+ "salespark"
24
+ ],
25
+ "author": "SalesPark",
26
+ "license": "MIT",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/salespark/route-utils.git"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "devDependencies": {
35
+ "typescript": "^5.4.0",
36
+ "@types/express": "^4.17.21"
37
+ },
38
+ "peerDependencies": {
39
+ "express": ">=4"
40
+ }
41
+ }