@increase21/simplenodejs 1.0.15 → 1.0.16

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 CHANGED
@@ -190,9 +190,20 @@ app.use(async (req, res, next) => {
190
190
  Registers a global error handler.
191
191
 
192
192
  ### Signature
193
+ app.useError() registers global error-handling middleware.
194
+ It catches all errors thrown anywhere in the request lifecycle — including:
195
+ • Errors thrown inside middlewares
196
+ • Errors thrown inside controllers / route handlers
197
+ • Async errors (throw or rejected promises)
198
+ • Validation errors
199
+ • Custom application errors
200
+
201
+ This gives you a single, centralized place to log, format, and return consistent error responses across your entire system.
193
202
 
194
203
  ```ts
195
- (err: any, req: RequestObject, res: ResponseObject, next: () => void) => void
204
+ app.useError((err, req, res, next) => {
205
+ // handle error
206
+ });
196
207
  ```
197
208
 
198
209
  ---
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { SimpleNodeJsController } from "./utils/simpleController";
2
2
  export { CreateSimpleJsHttpServer } from "./server";
3
- export { SetRequestCORS, SetRateLimiter, SetBodyParser } from "./utils/helpers";
3
+ export { SetRequestCORS, SetRateLimiter, SetBodyParser } from "./utils/simpleMiddleware";
4
4
  export * from "./utils/simplePlugins";
5
5
  export type { SimpleJsPrivateMethodProps } from "./typings/general";
package/dist/index.js CHANGED
@@ -19,8 +19,8 @@ var simpleController_1 = require("./utils/simpleController");
19
19
  Object.defineProperty(exports, "SimpleNodeJsController", { enumerable: true, get: function () { return simpleController_1.SimpleNodeJsController; } });
20
20
  var server_1 = require("./server");
21
21
  Object.defineProperty(exports, "CreateSimpleJsHttpServer", { enumerable: true, get: function () { return server_1.CreateSimpleJsHttpServer; } });
22
- var helpers_1 = require("./utils/helpers");
23
- Object.defineProperty(exports, "SetRequestCORS", { enumerable: true, get: function () { return helpers_1.SetRequestCORS; } });
24
- Object.defineProperty(exports, "SetRateLimiter", { enumerable: true, get: function () { return helpers_1.SetRateLimiter; } });
25
- Object.defineProperty(exports, "SetBodyParser", { enumerable: true, get: function () { return helpers_1.SetBodyParser; } });
22
+ var simpleMiddleware_1 = require("./utils/simpleMiddleware");
23
+ Object.defineProperty(exports, "SetRequestCORS", { enumerable: true, get: function () { return simpleMiddleware_1.SetRequestCORS; } });
24
+ Object.defineProperty(exports, "SetRateLimiter", { enumerable: true, get: function () { return simpleMiddleware_1.SetRateLimiter; } });
25
+ Object.defineProperty(exports, "SetBodyParser", { enumerable: true, get: function () { return simpleMiddleware_1.SetBodyParser; } });
26
26
  __exportStar(require("./utils/simplePlugins"), exports);
package/dist/server.js CHANGED
@@ -42,10 +42,10 @@ const CreateSimpleJsHttpServer = (handler, opts) => {
42
42
  if (opts?.controllersDir)
43
43
  (0, router_1.setControllersDir)(opts.controllersDir);
44
44
  const server = http_1.default.createServer(async (req, res) => {
45
- extension(req, res);
46
- const run = (0, helpers_1.composeWithError)(middlewares, errorMiddlewares);
47
45
  try {
48
- handler && await handler(req, res);
46
+ extension(req, res);
47
+ const run = (0, helpers_1.composeWithError)(middlewares);
48
+ // handler && await handler(req, res)
49
49
  if (res.writableEnded)
50
50
  return;
51
51
  await run(req, res);
@@ -62,7 +62,7 @@ const CreateSimpleJsHttpServer = (handler, opts) => {
62
62
  // 2. Safe HTTP response fallback
63
63
  if (!res.headersSent) {
64
64
  res.statusCode = 503;
65
- res.end("Service unavailable at the moment");
65
+ res.end("No Error Handlers: Service unavailable at the moment");
66
66
  }
67
67
  }
68
68
  });
@@ -1,16 +1,16 @@
1
1
  import http, { IncomingMessage, ServerResponse } from "http";
2
- export type Next = () => Promise<void> | void;
3
- export type Plugin = (app: SimpleJsServer, opts?: any) => Promise<void> | void;
2
+ export type Next = () => Promise<any> | void;
3
+ export type Plugin = (app: SimpleJsServer, opts?: any) => Promise<any> | void;
4
4
  export type HttpMethod = "get" | "post" | "put" | "patch" | "delete";
5
5
  export type ObjectPayload = {
6
6
  [key: string]: any;
7
7
  };
8
- export type Middleware = (req: RequestObject, res: ResponseObject, next: () => Promise<void> | void) => Promise<void> | void;
9
- export type ErrorMiddleware = (err: any, req: RequestObject, res: ResponseObject, next: Next) => Promise<void> | void;
8
+ export type Middleware = (req: RequestObject, res: ResponseObject, next: () => Promise<any> | void, errorHandler?: () => void) => Promise<any> | void;
9
+ export type ErrorMiddleware = (err: any, req: RequestObject, res: ResponseObject, next: Next) => Promise<boolean> | void;
10
10
  export interface SimpleJsServer extends http.Server {
11
11
  use(mw: Middleware): Promise<any> | void;
12
12
  useError: (mw: ErrorMiddleware) => void;
13
- registerPlugin: (plugin: Plugin) => Promise<void>;
13
+ registerPlugin: (plugin: Plugin) => Promise<any> | void;
14
14
  _environment: 'dev' | 'stag' | 'live';
15
15
  }
16
16
  export interface RequestObject extends IncomingMessage {
@@ -1,10 +1,3 @@
1
- import { RequestObject, ResponseObject } from "../typings/general";
2
- import { ErrorMiddleware, Middleware } from "../typings/general";
3
- import { SimpleJSBodyParseType, SimpleJSRateLimitType } from "../typings/simpletypes";
4
- export declare function SetRequestCORS(opts: {
5
- name: string;
6
- value: string;
7
- }[]): (req: RequestObject, res: ResponseObject, next: any) => Promise<void>;
8
- export declare function SetRateLimiter(opts: SimpleJSRateLimitType): (req: RequestObject, res: ResponseObject, next: () => Promise<any> | void) => Promise<void>;
9
- export declare function SetBodyParser(opts: SimpleJSBodyParseType): (req: RequestObject, res: ResponseObject, next: () => Promise<any> | void) => Promise<void>;
10
- export declare function composeWithError(middlewares: Middleware[], errorMiddlewares: ErrorMiddleware[]): (req: RequestObject, res: ResponseObject) => Promise<void>;
1
+ import { Middleware, RequestObject, ResponseObject } from "../typings/general";
2
+ export declare function composeWithError(middlewares: Middleware[]): (req: RequestObject, res: ResponseObject) => Promise<void>;
3
+ export declare function throwHttpError(code: number, message: string): void;
@@ -1,169 +1,34 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.SetRequestCORS = SetRequestCORS;
7
- exports.SetRateLimiter = SetRateLimiter;
8
- exports.SetBodyParser = SetBodyParser;
9
3
  exports.composeWithError = composeWithError;
10
- const node_querystring_1 = __importDefault(require("node:querystring"));
11
- // core/cors.ts
12
- function SetRequestCORS(opts) {
13
- return async (req, res, next) => {
14
- const defaults = {
15
- "Access-Control-Allow-Origin": "*",
16
- "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept, Authorization",
17
- "Access-Control-Allow-Methods": "GET, POST, DELETE, PUT, PATCH",
18
- "X-Content-Type-Options": "nosniff",
19
- "X-Frame-Options": "DENY",
20
- "Referrer-Policy": "no-referrer",
21
- "Content-Security-Policy": "default-src 'none'",
22
- };
23
- let merged = { ...defaults };
24
- for (const { name, value } of opts) {
25
- merged[name] = value; // overrides defaults if same key
26
- }
27
- //allow credentials only when origin specified
28
- if (merged["Access-Control-Allow-Credentials"] === "true") {
29
- const origin = req.headers.origin;
30
- res.status(403).end();
31
- if (!origin)
32
- return;
33
- merged["Access-Control-Allow-Origin"] = origin;
34
- }
35
- for (const [key, value] of Object.entries(merged)) {
36
- res.setHeader(key, value);
37
- }
38
- if (req.method === "OPTIONS") {
39
- res.status(204).end();
40
- return;
41
- }
42
- await next();
43
- };
44
- }
45
- // core/rateLimit.ts
46
- function SetRateLimiter(opts) {
47
- const store = new Map();
48
- const timer = setInterval(() => {
49
- const now = Date.now();
50
- for (const [k, v] of store) {
51
- if (now - v.ts > opts.windowMs * 2)
52
- store.delete(k);
53
- }
54
- }, opts.windowMs);
55
- timer.unref();
56
- return async (req, res, next) => {
57
- const rawIp = req.headers["x-forwarded-for"] || req.socket.remoteAddress || "unknown";
58
- const ip = Array.isArray(rawIp) ? rawIp[0] : String(rawIp).split(",")[0].trim();
59
- const key = String(opts.keyGenerator?.(req) || ip || "unknown");
60
- const now = Date.now();
61
- const entry = store.get(key) || { count: 0, ts: now };
62
- if (now - entry.ts > opts.windowMs) {
63
- entry.count = 0;
64
- entry.ts = now;
65
- }
66
- entry.count++;
67
- store.set(key, entry);
68
- if (entry.count > opts.max) {
69
- res.setHeader("Retry-After", Math.ceil(opts.windowMs / 1000));
70
- res.status(429).json({ error: "Too Many Requests" });
71
- return;
72
- }
73
- await next();
74
- };
75
- }
76
- // core/body.ts
77
- function SetBodyLimit(limit = "1mb") {
78
- if (typeof limit === "number")
79
- return limit;
80
- const match = /^(\d+)(kb|mb)?$/i.exec(limit);
81
- if (!match)
82
- return 1024 * 1024;
83
- const n = parseInt(match[1], 10);
84
- const unit = match[2]?.toLowerCase();
85
- if (unit === "kb")
86
- return n * 1024;
87
- if (unit === "mb")
88
- return n * 1024 * 1024;
89
- return n;
90
- }
91
- function SetBodyParser(opts) {
92
- const maxSize = SetBodyLimit(opts.limit);
93
- return async (req, res, next) => {
94
- return new Promise(resolve => {
95
- let size = 0;
96
- let body = "";
97
- req.on("data", chunk => {
98
- size += chunk.length;
99
- if (maxSize && size > maxSize) {
100
- if (!res.writableEnded) {
101
- res.status(413).json({ error: "Payload too large" });
102
- }
103
- req.destroy();
104
- req.socket.destroy();
105
- return;
106
- }
107
- //add the body
108
- body += chunk;
109
- });
110
- req.on("end", () => {
111
- if (res.writableEnded)
112
- return resolve();
113
- try {
114
- if (body && !["application/text", "application/media"].includes(req.headers.accept)) {
115
- req.body = JSON.parse(body);
116
- }
117
- else {
118
- req.body = body;
119
- }
120
- //parse query
121
- if (req.query) {
122
- req.query = JSON.parse(JSON.stringify(node_querystring_1.default.parse(req.query)));
123
- }
124
- resolve(next());
125
- }
126
- catch (e) {
127
- res.status(400).json({ error: "Invalid request body" });
128
- return resolve();
129
- }
130
- });
131
- req.on("error", () => {
132
- if (!res.writableEnded) {
133
- res.status(400).json({ error: "Request stream error" });
134
- }
135
- resolve();
136
- });
137
- });
138
- };
139
- }
140
- function composeWithError(middlewares, errorMiddlewares) {
4
+ exports.throwHttpError = throwHttpError;
5
+ function composeWithError(middlewares) {
141
6
  return async function (req, res) {
142
7
  let idx = -1;
143
8
  async function dispatch(i) {
144
9
  if (i <= idx)
145
10
  throw new Error("next() called twice");
146
11
  idx = i;
12
+ if (res.writableEnded)
13
+ return;
147
14
  const fn = middlewares[i];
148
15
  if (!fn)
149
16
  return;
150
- try {
151
- await fn(req, res, () => dispatch(i + 1));
152
- }
153
- catch (err) {
154
- await dispatchError(err, 0);
155
- }
156
- }
157
- async function dispatchError(err, i) {
158
- const fn = errorMiddlewares[i];
159
- if (!fn) {
160
- if (!res.writableEnded) {
161
- res.status(500).json({ error: "Unhandled error", detail: String(err) });
162
- }
163
- return;
164
- }
165
- await fn(err, req, res, () => dispatchError(err, i + 1));
166
- }
167
- await dispatch(0);
17
+ let called = false;
18
+ const next = async () => {
19
+ if (called)
20
+ throw new Error("next() called multiple times in the same middleware");
21
+ called = true;
22
+ return dispatch(i + 1);
23
+ };
24
+ return await fn(req, res, next);
25
+ }
26
+ return await dispatch(0);
168
27
  };
169
28
  }
29
+ function throwHttpError(code, message) {
30
+ const error = new Error(message);
31
+ error.statusCode = code;
32
+ error.toJSON = () => ({ code, error: message });
33
+ throw error;
34
+ }
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SimpleNodeJsController = void 0;
4
+ const helpers_1 = require("./helpers");
4
5
  class SimpleNodeJsController {
5
6
  /** framework-internal method */
6
7
  __bindContext(ctx) {
@@ -16,17 +17,15 @@ class SimpleNodeJsController {
16
17
  RunRequest(handlers, params) {
17
18
  const method = this.req.method?.toLowerCase();
18
19
  if (!method)
19
- return this.res.status(400).json({ code: 400, error: "Invalid HTTP Method" });
20
+ return (0, helpers_1.throwHttpError)(400, "Invalid HTTP Method");
20
21
  const runFn = handlers[method];
21
22
  if (typeof runFn !== "function")
22
- return this.res.status(405).json({ code: 405, error: "Method Not Allowed" });
23
+ return (0, helpers_1.throwHttpError)(405, "Method Not Allowed");
23
24
  // ID validation rules
24
- if (params && params.id && (!params.idMethod || !params.idMethod[method])) {
25
- return this.res.status(404).json({ code: 404, error: "Resource not found" });
26
- }
27
- if (params && params.idMethod?.[method] === "required" && !params.id) {
28
- return this.res.status(404).json({ code: 404, error: "Resource not found" });
29
- }
25
+ if (params && params.id && (!params.idMethod || !params.idMethod[method]))
26
+ return (0, helpers_1.throwHttpError)(404, "Resource not found");
27
+ if (params && params.idMethod?.[method] === "required" && !params.id)
28
+ return (0, helpers_1.throwHttpError)(404, "Resource not found");
30
29
  return runFn({
31
30
  ...(params || {}), req: this.req,
32
31
  res: this.res, query: this.query,
@@ -0,0 +1,8 @@
1
+ import { RequestObject, ResponseObject } from "../typings/general";
2
+ import { SimpleJSBodyParseType, SimpleJSRateLimitType } from "../typings/simpletypes";
3
+ export declare function SetRequestCORS(opts: {
4
+ name: string;
5
+ value: string;
6
+ }[]): (req: RequestObject, res: ResponseObject, next: any) => Promise<void>;
7
+ export declare function SetRateLimiter(opts: SimpleJSRateLimitType): (req: RequestObject, res: ResponseObject, next: () => Promise<any> | void) => Promise<void>;
8
+ export declare function SetBodyParser(opts: SimpleJSBodyParseType): (req: RequestObject, res: ResponseObject, next: () => Promise<any> | void) => Promise<void>;
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SetRequestCORS = SetRequestCORS;
7
+ exports.SetRateLimiter = SetRateLimiter;
8
+ exports.SetBodyParser = SetBodyParser;
9
+ const node_querystring_1 = __importDefault(require("node:querystring"));
10
+ const helpers_1 = require("./helpers");
11
+ // core/cors.ts
12
+ function SetRequestCORS(opts) {
13
+ return async (req, res, next) => {
14
+ const defaults = {
15
+ "Access-Control-Allow-Origin": "*",
16
+ "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept, Authorization",
17
+ "Access-Control-Allow-Methods": "GET, POST, DELETE, PUT, PATCH",
18
+ "X-Content-Type-Options": "nosniff",
19
+ "X-Frame-Options": "DENY",
20
+ "Referrer-Policy": "no-referrer",
21
+ "Content-Security-Policy": "default-src 'none'",
22
+ };
23
+ let merged = { ...defaults };
24
+ for (const { name, value } of opts) {
25
+ merged[name] = value; // overrides defaults if same key
26
+ }
27
+ //allow credentials only when origin specified
28
+ if (merged["Access-Control-Allow-Credentials"] === "true") {
29
+ const origin = req.headers.origin;
30
+ if (!origin) {
31
+ (0, helpers_1.throwHttpError)(403, "CORS Error: Origin header is required when Access-Control-Allow-Credentials is true");
32
+ return;
33
+ }
34
+ merged["Access-Control-Allow-Origin"] = origin;
35
+ }
36
+ for (const [key, value] of Object.entries(merged)) {
37
+ res.setHeader(key, value);
38
+ }
39
+ if (req.method === "OPTIONS") {
40
+ res.status(204).end();
41
+ return;
42
+ }
43
+ await next();
44
+ };
45
+ }
46
+ // core/rateLimit.ts
47
+ function SetRateLimiter(opts) {
48
+ const store = new Map();
49
+ const timer = setInterval(() => {
50
+ const now = Date.now();
51
+ for (const [k, v] of store) {
52
+ if (now - v.ts > opts.windowMs * 2)
53
+ store.delete(k);
54
+ }
55
+ }, opts.windowMs);
56
+ timer.unref();
57
+ return async (req, res, next) => {
58
+ const rawIp = req.headers["x-forwarded-for"] || req.socket.remoteAddress || "unknown";
59
+ const ip = Array.isArray(rawIp) ? rawIp[0] : String(rawIp).split(",")[0].trim();
60
+ const key = String(opts.keyGenerator?.(req) || ip || "unknown");
61
+ const now = Date.now();
62
+ const entry = store.get(key) || { count: 0, ts: now };
63
+ if (now - entry.ts > opts.windowMs) {
64
+ entry.count = 0;
65
+ entry.ts = now;
66
+ }
67
+ entry.count++;
68
+ store.set(key, entry);
69
+ if (entry.count > opts.max) {
70
+ res.setHeader("Retry-After", Math.ceil(opts.windowMs / 1000));
71
+ if (!res.writableEnded)
72
+ (0, helpers_1.throwHttpError)(429, "Too Many Requests");
73
+ return;
74
+ }
75
+ await next();
76
+ };
77
+ }
78
+ // core/body.ts
79
+ function SetBodyLimit(limit = "1mb") {
80
+ if (typeof limit === "number")
81
+ return limit;
82
+ const match = /^(\d+)(kb|mb)?$/i.exec(limit);
83
+ if (!match)
84
+ return 1024 * 1024;
85
+ const n = parseInt(match[1], 10);
86
+ const unit = match[2]?.toLowerCase();
87
+ if (unit === "kb")
88
+ return n * 1024;
89
+ if (unit === "mb")
90
+ return n * 1024 * 1024;
91
+ return n;
92
+ }
93
+ //For
94
+ function SetBodyParser(opts) {
95
+ const maxSize = SetBodyLimit(opts.limit);
96
+ return (req, res, next) => new Promise((resolve, reject) => {
97
+ //get the content type of the request
98
+ const contentType = req.headers["content-type"] || "";
99
+ // Skip multipart/form-data (file uploads)
100
+ if (contentType.includes("multipart/form-data"))
101
+ return resolve(next());
102
+ let size = 0;
103
+ let body = "";
104
+ req.on("data", chunk => {
105
+ size += chunk.length;
106
+ if (maxSize && size > maxSize) {
107
+ reject({ code: 413, error: "Payload Too Large" });
108
+ if (!res.writableEnded)
109
+ res.status(413).end("Payload Too Large");
110
+ req.destroy();
111
+ req.socket.destroy();
112
+ return;
113
+ }
114
+ //add the body
115
+ body += chunk;
116
+ });
117
+ req.on("end", () => {
118
+ if (res.writableEnded)
119
+ return resolve();
120
+ try {
121
+ if (body && contentType.includes("application/json")) {
122
+ req.body = JSON.parse(body);
123
+ }
124
+ else {
125
+ req.body = body;
126
+ }
127
+ //parse query
128
+ if (req.query) {
129
+ req.query = JSON.parse(JSON.stringify(node_querystring_1.default.parse(req.query)));
130
+ }
131
+ resolve(next());
132
+ }
133
+ catch (e) {
134
+ if (!res.writableEnded)
135
+ reject({ code: 400, error: "Invalid request body" });
136
+ }
137
+ });
138
+ req.on("error", () => {
139
+ if (!res.writableEnded)
140
+ reject({ code: 400, error: "Request stream error" });
141
+ });
142
+ });
143
+ }
144
+ ;
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SimpleJsSecurityPlugin = SimpleJsSecurityPlugin;
4
- const helpers_1 = require("./helpers");
4
+ const simpleMiddleware_1 = require("./simpleMiddleware");
5
5
  function SimpleJsSecurityPlugin(app, opts) {
6
- opts.cors && app.use((0, helpers_1.SetRequestCORS)(opts.cors || []));
7
- opts.rateLimit && app.use((0, helpers_1.SetRateLimiter)(opts.rateLimit || { windowMs: 1000, max: 100 }));
6
+ opts.cors && app.use((0, simpleMiddleware_1.SetRequestCORS)(opts.cors || []));
7
+ opts.rateLimit && app.use((0, simpleMiddleware_1.SetRateLimiter)(opts.rateLimit || { windowMs: 1000, max: 100 }));
8
8
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@increase21/simplenodejs",
3
- "version": "1.0.15",
3
+ "version": "1.0.16",
4
4
  "description": "Lightweight Node.js HTTP framework with middleware and plugins",
5
5
  "dev": "dist/index.js",
6
6
  "bugs": "https://github.com/increase21/simplenodejs/issues",