@devbro/pashmak 0.1.39 → 0.1.41
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/bin/index.cjs +0 -1
- package/dist/bin/middlewares.cjs +2 -1
- package/dist/facades.mjs +1 -2
- package/dist/facades.mjs.map +1 -1
- package/dist/middlewares.mjs +2 -1
- package/dist/middlewares.mjs.map +1 -1
- package/package.json +1 -1
package/dist/bin/index.cjs
CHANGED
|
@@ -761,7 +761,6 @@ var init_facades = __esm({
|
|
|
761
761
|
import_neko_config2 = require("@devbro/neko-config");
|
|
762
762
|
import_clipanion = require("clipanion");
|
|
763
763
|
init_http();
|
|
764
|
-
init_http();
|
|
765
764
|
yup = __toESM(require("yup"), 1);
|
|
766
765
|
import_neko_logger = require("@devbro/neko-logger");
|
|
767
766
|
init_factories();
|
package/dist/bin/middlewares.cjs
CHANGED
|
@@ -782,8 +782,9 @@ async function dbTransaction(req, res, next) {
|
|
|
782
782
|
await db().beginTransaction();
|
|
783
783
|
await next();
|
|
784
784
|
await db().commit();
|
|
785
|
-
} catch {
|
|
785
|
+
} catch (err) {
|
|
786
786
|
await db().rollback();
|
|
787
|
+
throw err;
|
|
787
788
|
}
|
|
788
789
|
}
|
|
789
790
|
__name(dbTransaction, "dbTransaction");
|
package/dist/facades.mjs
CHANGED
|
@@ -11,8 +11,7 @@ import {
|
|
|
11
11
|
} from "@devbro/neko-mailer";
|
|
12
12
|
import { config } from "@devbro/neko-config";
|
|
13
13
|
import { Cli } from "clipanion";
|
|
14
|
-
import { HttpServer } from "./http.mjs";
|
|
15
|
-
import { HttpError } from "./http.mjs";
|
|
14
|
+
import { HttpServer, HttpError } from "./http.mjs";
|
|
16
15
|
import * as yup from "yup";
|
|
17
16
|
import { Logger } from "@devbro/neko-logger";
|
|
18
17
|
import { CacheProviderFactory } from "./factories.mjs";
|
package/dist/facades.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/facades.mts"],"sourcesContent":["import { Router } from \"./router.mjs\";\nimport { Schedule, Scheduler } from \"@devbro/neko-scheduler\";\nimport { createSingleton } from \"@devbro/neko-helper\";\nimport { ctx, ctxSafe } from \"@devbro/neko-context\";\nimport { Connection } from \"@devbro/neko-sql\";\nimport { Storage, StorageProviderFactory } from \"@devbro/neko-storage\";\nimport {\n Mailer,\n MailerProvider,\n MailerProviderFactory,\n} from \"@devbro/neko-mailer\";\nimport { config } from \"@devbro/neko-config\";\nimport { Cli } from \"clipanion\";\nimport { HttpServer
|
|
1
|
+
{"version":3,"sources":["../src/facades.mts"],"sourcesContent":["import { Router } from \"./router.mjs\";\nimport { Schedule, Scheduler } from \"@devbro/neko-scheduler\";\nimport { createSingleton } from \"@devbro/neko-helper\";\nimport { ctx, ctxSafe } from \"@devbro/neko-context\";\nimport { Connection } from \"@devbro/neko-sql\";\nimport { Storage, StorageProviderFactory } from \"@devbro/neko-storage\";\nimport {\n Mailer,\n MailerProvider,\n MailerProviderFactory,\n} from \"@devbro/neko-mailer\";\nimport { config } from \"@devbro/neko-config\";\nimport { Cli } from \"clipanion\";\nimport { HttpServer, HttpError } from \"./http.mjs\";\nimport * as yup from \"yup\";\nimport { Logger } from \"@devbro/neko-logger\";\nimport { CacheProviderFactory } from \"./factories.mjs\";\nimport { Cache } from \"@devbro/neko-cache\";\nimport { QueueConnection, QueueTransportFactory } from \"@devbro/neko-queue\";\n\nexport const router = createSingleton<Router>(() => new Router());\nexport const scheduler = createSingleton<Scheduler>(() => {\n const rc = new Scheduler();\n rc.setErrorHandler((err: any, job: Schedule) => {\n logger().error({\n msg: \"Scheduled job error\",\n err,\n job_name: job.getName(),\n });\n });\n return rc;\n});\nexport const db = (label = \"default\") =>\n ctx().getOrThrow<Connection>([\"database\", label]);\n\nexport const storage = createSingleton<Storage>((label: string = \"default\") => {\n let storage_config: any = config.get([\"storages\", label].join(\".\"));\n\n const provider = StorageProviderFactory.create(\n storage_config.provider,\n storage_config.config,\n );\n\n return new Storage(provider);\n});\n\nexport const cli = createSingleton<Cli>(() => {\n const [node, app, ...args] = process.argv;\n return new Cli({\n binaryLabel: `My Application`,\n binaryName: `${node} ${app}`,\n binaryVersion: `1.0.0`,\n });\n});\n\nexport const httpServer = createSingleton<HttpServer>(() => {\n const server = new HttpServer();\n\n server.setErrorHandler(async (err: Error, req: any, res: any) => {\n if (err instanceof HttpError) {\n res.writeHead(err.statusCode, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ message: err.message, error: err.code }));\n logger().warn({ msg: \"HttpError: \" + err.message, err });\n return;\n } else if (err instanceof yup.ValidationError) {\n res.writeHead(422, { \"Content-Type\": \"application/json\" });\n const errs: any = {};\n err.inner.forEach((e: yup.ValidationError) => {\n // Sanitize sensitive fields\n const sanitizedParams = { ...e.params };\n if (/passw/i.test(e.path!)) {\n sanitizedParams.value = \"******\";\n sanitizedParams.originalValue = \"******\";\n }\n\n errs[e.path!] = {\n type: e.type,\n message: e.message,\n params: sanitizedParams,\n };\n });\n\n res.end(JSON.stringify({ message: \"validation error\", errors: errs }));\n logger().warn({ msg: \"ValidationError: \" + err.message, err });\n return;\n } else {\n logger().error({ msg: \"Error: \" + err.message, err });\n }\n res.writeHead(500, { \"Content-Type\": \"\" });\n res.end(JSON.stringify({ error: \"Internal Server Error\" }));\n });\n server.setRouter(router());\n\n return server;\n});\n\nexport const logger = createSingleton<Logger>((label) => {\n const logger_config: any = config.get([\"loggers\", label].join(\".\"));\n const rc = new Logger(logger_config);\n rc.setExtrasFunction((message: any) => {\n message.requestId = ctxSafe()?.get(\"requestId\") || \"N/A\";\n return message;\n });\n\n return rc;\n});\n\nexport const mailer = createSingleton((label) => {\n const mailer_config: any = config.get([\"mailer\", label].join(\".\"));\n\n const provider: MailerProvider = MailerProviderFactory.create(\n mailer_config.provider,\n mailer_config.config,\n );\n\n const rc = new Mailer(provider);\n return rc;\n});\n\nexport const queue = createSingleton((label) => {\n const queue_config: any = config.get([\"queues\", label].join(\".\"));\n if (!queue_config) {\n throw new Error(`Queue configuration for '${label}' not found`);\n }\n const provider = QueueTransportFactory.create(\n queue_config.provider,\n queue_config.config,\n );\n const rc = new QueueConnection(provider);\n return rc;\n});\n\nexport const cache = createSingleton((label) => {\n const cache_config: any = config.get([\"caches\", label].join(\".\"));\n if (!cache_config) {\n throw new Error(`Cache configuration for '${label}' not found`);\n }\n const provider = CacheProviderFactory.create(\n cache_config.provider,\n cache_config.config,\n );\n\n return new Cache(provider);\n});\n"],"mappings":"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","names":[]}
|
package/dist/middlewares.mjs
CHANGED
package/dist/middlewares.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/middlewares.mts"],"sourcesContent":["import { Middleware, Request, Response } from \"@devbro/neko-router\";\nimport { logger, db, cache } from \"./facades.mjs\";\nimport { HttpTooManyRequestsError } from \"@devbro/neko-http\";\n\nexport function cors(\n options: { allowedOrigins?: (string | RegExp)[] } = {},\n): (req: Request, res: Response, next: () => Promise<void>) => Promise<void> {\n return async (\n req: Request,\n res: Response,\n next: () => Promise<void>,\n ): Promise<void> => {\n const allowedOrigins = options.allowedOrigins || [\"*\"];\n const origin = req.headers.origin || \"*\";\n\n for (const allowedOrigin of allowedOrigins) {\n if (typeof allowedOrigin === \"string\" && allowedOrigin === origin) {\n res.setHeader(\"Access-Control-Allow-Origin\", allowedOrigin);\n break;\n } else if (\n allowedOrigin instanceof RegExp &&\n allowedOrigin.test(origin)\n ) {\n res.setHeader(\"Access-Control-Allow-Origin\", origin);\n break;\n } else if (allowedOrigin === \"*\") {\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n break;\n }\n }\n\n res.setHeader(\"Access-Control-Allow-Headers\", \"*\");\n await next();\n };\n}\n\nexport async function dbTransaction(\n req: Request,\n res: Response,\n next: () => Promise<void>,\n): Promise<void> {\n try {\n await db().beginTransaction();\n await next();\n await db().commit();\n } catch {\n await db().rollback();\n }\n}\n\nexport type RateLimiterMiddlewareParams = {\n generateIdentifier: (req: Request) => string;\n maxRequests: number;\n windowTimeSize: number;\n windowCount: number;\n};\n\n/**\n * Rate limiter middleware using a sliding window algorithm.\n *\n * This middleware tracks request counts across multiple time windows to prevent abuse.\n * It uses atomic cache increments to ensure accurate counting in concurrent environments.\n *\n * @example\n * ```typescript\n * // Basic usage with defaults (200 requests per 2 minutes)\n * authnedRouter.addGlobalMiddleware(RateLimiterMiddleware);\n *\n * // Custom configuration\n * authnedRouter.addGlobalMiddleware(RateLimiterMiddleware.getInstance({\n * generateIdentifier: (req) => req.headers['x-api-key'] || req.socket.remoteAddress,\n * maxRequests: 200, // Allow 200 requests\n * windowTimeSize: 30, // in 30 second windows\n * windowCount: 4 // looking back 4 windows (total: 120 seconds)\n * }));\n * ```\n *\n * @param params.generateIdentifier - Function to generate unique identifier for rate limiting (default: IP address)\n * @param params.maxRequests - Maximum number of requests allowed across all windows (default: 200)\n * @param params.windowTimeSize - Size of each time window in seconds (default: 30)\n * @param params.windowCount - Number of previous windows to check (default: 4)\n *\n * The total time period checked is `windowTimeSize * windowCount` seconds.\n * Default configuration: 200 requests per 120 seconds (30s × 4 windows).\n *\n * @throws {HttpTooManyRequestsError} When rate limit is exceeded\n */\nexport class RateLimiterMiddleware extends Middleware {\n static singletonInstance: Middleware | undefined = undefined;\n static getInstance(\n params: Partial<RateLimiterMiddlewareParams> = {},\n ): Middleware {\n if (RateLimiterMiddleware.singletonInstance) {\n return RateLimiterMiddleware.singletonInstance;\n }\n let default_params: RateLimiterMiddlewareParams = {\n generateIdentifier: (req: Request) =>\n req.socket.remoteAddress || \"unknown\",\n maxRequests: 200,\n windowTimeSize: 30,\n windowCount: 4,\n };\n const merged_params = { ...default_params, ...params };\n return (RateLimiterMiddleware.singletonInstance = new RateLimiterMiddleware(\n merged_params,\n ));\n }\n\n constructor(private params: RateLimiterMiddlewareParams) {\n super();\n }\n\n async call(\n req: Request,\n res: Response,\n next: () => Promise<void>,\n ): Promise<void> {\n let window = parseInt(\n (Date.now() / 1000 / this.params.windowTimeSize).toString(),\n );\n let key = `rate_limiter:${this.params.generateIdentifier(req)}:${window}`;\n let count: number = (await cache().get(key)) || 0;\n if (!count) {\n await cache().put(\n key,\n 1,\n this.params.windowTimeSize * (this.params.windowCount + 1),\n );\n count = 1;\n } else {\n count = await cache().increment(key, 1);\n }\n\n for (let i = 1; i < this.params.windowCount; i++) {\n count += parseInt(\n (await cache().get(\n `rate_limiter:${this.params.generateIdentifier(req)}:${window - i}`,\n )) || \"0\",\n );\n }\n\n if (count > this.params.maxRequests) {\n throw new HttpTooManyRequestsError(\n \"Too many requests. Please try again later.\",\n );\n }\n\n await next();\n return;\n }\n}\n"],"mappings":";;AAAA,SAAS,kBAAqC;AAC9C,SAAiB,IAAI,aAAa;AAClC,SAAS,gCAAgC;AAElC,SAAS,KACd,UAAoD,CAAC,GACsB;AAC3E,SAAO,OACL,KACA,KACA,SACkB;AAClB,UAAM,iBAAiB,QAAQ,kBAAkB,CAAC,GAAG;AACrD,UAAM,SAAS,IAAI,QAAQ,UAAU;AAErC,eAAW,iBAAiB,gBAAgB;AAC1C,UAAI,OAAO,kBAAkB,YAAY,kBAAkB,QAAQ;AACjE,YAAI,UAAU,+BAA+B,aAAa;AAC1D;AAAA,MACF,WACE,yBAAyB,UACzB,cAAc,KAAK,MAAM,GACzB;AACA,YAAI,UAAU,+BAA+B,MAAM;AACnD;AAAA,MACF,WAAW,kBAAkB,KAAK;AAChC,YAAI,UAAU,+BAA+B,GAAG;AAChD;AAAA,MACF;AAAA,IACF;AAEA,QAAI,UAAU,gCAAgC,GAAG;AACjD,UAAM,KAAK;AAAA,EACb;AACF;AA9BgB;AAgChB,eAAsB,cACpB,KACA,KACA,MACe;AACf,MAAI;AACF,UAAM,GAAG,EAAE,iBAAiB;AAC5B,UAAM,KAAK;AACX,UAAM,GAAG,EAAE,OAAO;AAAA,EACpB,
|
|
1
|
+
{"version":3,"sources":["../src/middlewares.mts"],"sourcesContent":["import { Middleware, Request, Response } from \"@devbro/neko-router\";\nimport { logger, db, cache } from \"./facades.mjs\";\nimport { HttpTooManyRequestsError } from \"@devbro/neko-http\";\n\nexport function cors(\n options: { allowedOrigins?: (string | RegExp)[] } = {},\n): (req: Request, res: Response, next: () => Promise<void>) => Promise<void> {\n return async (\n req: Request,\n res: Response,\n next: () => Promise<void>,\n ): Promise<void> => {\n const allowedOrigins = options.allowedOrigins || [\"*\"];\n const origin = req.headers.origin || \"*\";\n\n for (const allowedOrigin of allowedOrigins) {\n if (typeof allowedOrigin === \"string\" && allowedOrigin === origin) {\n res.setHeader(\"Access-Control-Allow-Origin\", allowedOrigin);\n break;\n } else if (\n allowedOrigin instanceof RegExp &&\n allowedOrigin.test(origin)\n ) {\n res.setHeader(\"Access-Control-Allow-Origin\", origin);\n break;\n } else if (allowedOrigin === \"*\") {\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n break;\n }\n }\n\n res.setHeader(\"Access-Control-Allow-Headers\", \"*\");\n await next();\n };\n}\n\nexport async function dbTransaction(\n req: Request,\n res: Response,\n next: () => Promise<void>,\n): Promise<void> {\n try {\n await db().beginTransaction();\n await next();\n await db().commit();\n } catch (err) {\n await db().rollback();\n throw err;\n }\n}\n\nexport type RateLimiterMiddlewareParams = {\n generateIdentifier: (req: Request) => string;\n maxRequests: number;\n windowTimeSize: number;\n windowCount: number;\n};\n\n/**\n * Rate limiter middleware using a sliding window algorithm.\n *\n * This middleware tracks request counts across multiple time windows to prevent abuse.\n * It uses atomic cache increments to ensure accurate counting in concurrent environments.\n *\n * @example\n * ```typescript\n * // Basic usage with defaults (200 requests per 2 minutes)\n * authnedRouter.addGlobalMiddleware(RateLimiterMiddleware);\n *\n * // Custom configuration\n * authnedRouter.addGlobalMiddleware(RateLimiterMiddleware.getInstance({\n * generateIdentifier: (req) => req.headers['x-api-key'] || req.socket.remoteAddress,\n * maxRequests: 200, // Allow 200 requests\n * windowTimeSize: 30, // in 30 second windows\n * windowCount: 4 // looking back 4 windows (total: 120 seconds)\n * }));\n * ```\n *\n * @param params.generateIdentifier - Function to generate unique identifier for rate limiting (default: IP address)\n * @param params.maxRequests - Maximum number of requests allowed across all windows (default: 200)\n * @param params.windowTimeSize - Size of each time window in seconds (default: 30)\n * @param params.windowCount - Number of previous windows to check (default: 4)\n *\n * The total time period checked is `windowTimeSize * windowCount` seconds.\n * Default configuration: 200 requests per 120 seconds (30s × 4 windows).\n *\n * @throws {HttpTooManyRequestsError} When rate limit is exceeded\n */\nexport class RateLimiterMiddleware extends Middleware {\n static singletonInstance: Middleware | undefined = undefined;\n static getInstance(\n params: Partial<RateLimiterMiddlewareParams> = {},\n ): Middleware {\n if (RateLimiterMiddleware.singletonInstance) {\n return RateLimiterMiddleware.singletonInstance;\n }\n let default_params: RateLimiterMiddlewareParams = {\n generateIdentifier: (req: Request) =>\n req.socket.remoteAddress || \"unknown\",\n maxRequests: 200,\n windowTimeSize: 30,\n windowCount: 4,\n };\n const merged_params = { ...default_params, ...params };\n return (RateLimiterMiddleware.singletonInstance = new RateLimiterMiddleware(\n merged_params,\n ));\n }\n\n constructor(private params: RateLimiterMiddlewareParams) {\n super();\n }\n\n async call(\n req: Request,\n res: Response,\n next: () => Promise<void>,\n ): Promise<void> {\n let window = parseInt(\n (Date.now() / 1000 / this.params.windowTimeSize).toString(),\n );\n let key = `rate_limiter:${this.params.generateIdentifier(req)}:${window}`;\n let count: number = (await cache().get(key)) || 0;\n if (!count) {\n await cache().put(\n key,\n 1,\n this.params.windowTimeSize * (this.params.windowCount + 1),\n );\n count = 1;\n } else {\n count = await cache().increment(key, 1);\n }\n\n for (let i = 1; i < this.params.windowCount; i++) {\n count += parseInt(\n (await cache().get(\n `rate_limiter:${this.params.generateIdentifier(req)}:${window - i}`,\n )) || \"0\",\n );\n }\n\n if (count > this.params.maxRequests) {\n throw new HttpTooManyRequestsError(\n \"Too many requests. Please try again later.\",\n );\n }\n\n await next();\n return;\n }\n}\n"],"mappings":";;AAAA,SAAS,kBAAqC;AAC9C,SAAiB,IAAI,aAAa;AAClC,SAAS,gCAAgC;AAElC,SAAS,KACd,UAAoD,CAAC,GACsB;AAC3E,SAAO,OACL,KACA,KACA,SACkB;AAClB,UAAM,iBAAiB,QAAQ,kBAAkB,CAAC,GAAG;AACrD,UAAM,SAAS,IAAI,QAAQ,UAAU;AAErC,eAAW,iBAAiB,gBAAgB;AAC1C,UAAI,OAAO,kBAAkB,YAAY,kBAAkB,QAAQ;AACjE,YAAI,UAAU,+BAA+B,aAAa;AAC1D;AAAA,MACF,WACE,yBAAyB,UACzB,cAAc,KAAK,MAAM,GACzB;AACA,YAAI,UAAU,+BAA+B,MAAM;AACnD;AAAA,MACF,WAAW,kBAAkB,KAAK;AAChC,YAAI,UAAU,+BAA+B,GAAG;AAChD;AAAA,MACF;AAAA,IACF;AAEA,QAAI,UAAU,gCAAgC,GAAG;AACjD,UAAM,KAAK;AAAA,EACb;AACF;AA9BgB;AAgChB,eAAsB,cACpB,KACA,KACA,MACe;AACf,MAAI;AACF,UAAM,GAAG,EAAE,iBAAiB;AAC5B,UAAM,KAAK;AACX,UAAM,GAAG,EAAE,OAAO;AAAA,EACpB,SAAS,KAAK;AACZ,UAAM,GAAG,EAAE,SAAS;AACpB,UAAM;AAAA,EACR;AACF;AAbsB;AAoDf,MAAM,8BAA8B,WAAW;AAAA,EAqBpD,YAAoB,QAAqC;AACvD,UAAM;AADY;AAAA,EAEpB;AAAA,EA/GF,OAwFsD;AAAA;AAAA;AAAA,EACpD,OAAO,oBAA4C;AAAA,EACnD,OAAO,YACL,SAA+C,CAAC,GACpC;AACZ,QAAI,sBAAsB,mBAAmB;AAC3C,aAAO,sBAAsB;AAAA,IAC/B;AACA,QAAI,iBAA8C;AAAA,MAChD,oBAAoB,wBAAC,QACnB,IAAI,OAAO,iBAAiB,WADV;AAAA,MAEpB,aAAa;AAAA,MACb,gBAAgB;AAAA,MAChB,aAAa;AAAA,IACf;AACA,UAAM,gBAAgB,EAAE,GAAG,gBAAgB,GAAG,OAAO;AACrD,WAAQ,sBAAsB,oBAAoB,IAAI;AAAA,MACpD;AAAA,IACF;AAAA,EACF;AAAA,EAMA,MAAM,KACJ,KACA,KACA,MACe;AACf,QAAI,SAAS;AAAA,OACV,KAAK,IAAI,IAAI,MAAO,KAAK,OAAO,gBAAgB,SAAS;AAAA,IAC5D;AACA,QAAI,MAAM,gBAAgB,KAAK,OAAO,mBAAmB,GAAG,CAAC,IAAI,MAAM;AACvE,QAAI,QAAiB,MAAM,MAAM,EAAE,IAAI,GAAG,KAAM;AAChD,QAAI,CAAC,OAAO;AACV,YAAM,MAAM,EAAE;AAAA,QACZ;AAAA,QACA;AAAA,QACA,KAAK,OAAO,kBAAkB,KAAK,OAAO,cAAc;AAAA,MAC1D;AACA,cAAQ;AAAA,IACV,OAAO;AACL,cAAQ,MAAM,MAAM,EAAE,UAAU,KAAK,CAAC;AAAA,IACxC;AAEA,aAAS,IAAI,GAAG,IAAI,KAAK,OAAO,aAAa,KAAK;AAChD,eAAS;AAAA,QACN,MAAM,MAAM,EAAE;AAAA,UACb,gBAAgB,KAAK,OAAO,mBAAmB,GAAG,CAAC,IAAI,SAAS,CAAC;AAAA,QACnE,KAAM;AAAA,MACR;AAAA,IACF;AAEA,QAAI,QAAQ,KAAK,OAAO,aAAa;AACnC,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,UAAM,KAAK;AACX;AAAA,EACF;AACF;","names":[]}
|