tezx 1.0.31 → 1.0.33

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/cjs/index.js CHANGED
@@ -7,4 +7,4 @@ var server_1 = require("./core/server");
7
7
  Object.defineProperty(exports, "TezX", { enumerable: true, get: function () { return server_1.TezX; } });
8
8
  var params_1 = require("./utils/params");
9
9
  Object.defineProperty(exports, "useParams", { enumerable: true, get: function () { return params_1.useParams; } });
10
- exports.version = "1.0.31";
10
+ exports.version = "1.0.33";
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.i18nMiddleware = void 0;
4
+ const config_1 = require("../core/config");
5
+ const i18nMiddleware = (options) => {
6
+ const { loadTranslations, defaultCacheDuration = 3600000, isCacheValid = (cached) => cached.expiresAt > Date.now(), detectLanguage = (ctx) => ctx.req.query.lang ||
7
+ ctx.cookies?.get('lang') ||
8
+ ctx.headers.get('accept-language')?.split(',')[0] ||
9
+ options.defaultLanguage ||
10
+ 'en', defaultLanguage = 'en', fallbackChain = [], translationFunctionKey = 't', formatMessage = (message, options = {}) => {
11
+ return Object.entries(options).reduce((msg, [key, value]) => msg.replace(new RegExp(`{{${key}}}`, 'g'), String(value)), message);
12
+ }, cacheTranslations = true } = options;
13
+ const translationCache = {};
14
+ return async (ctx, next) => {
15
+ try {
16
+ const detectedLanguage = detectLanguage(ctx);
17
+ const languageChain = [
18
+ detectedLanguage,
19
+ ...fallbackChain,
20
+ defaultLanguage
21
+ ].filter(Boolean);
22
+ let translations = null;
23
+ let selectedLanguage = defaultLanguage;
24
+ for (const lang of languageChain) {
25
+ const normalizedLang = lang.split('-')[0].toLowerCase();
26
+ if (cacheTranslations && translationCache[normalizedLang]) {
27
+ const cached = translationCache[normalizedLang];
28
+ if (isCacheValid(cached, normalizedLang)) {
29
+ translations = cached.translations;
30
+ selectedLanguage = lang;
31
+ break;
32
+ }
33
+ delete translationCache[normalizedLang];
34
+ }
35
+ try {
36
+ const { translations: loadedTranslations, expiresAt } = await loadTranslations(normalizedLang);
37
+ let expirationTime = Date.now() + defaultCacheDuration;
38
+ if (expiresAt instanceof Date) {
39
+ expirationTime = expiresAt.getTime();
40
+ }
41
+ else if (typeof expiresAt === 'number') {
42
+ expirationTime = expiresAt;
43
+ }
44
+ translations = loadedTranslations;
45
+ selectedLanguage = lang;
46
+ if (cacheTranslations) {
47
+ translationCache[normalizedLang] = {
48
+ translations,
49
+ expiresAt: expirationTime,
50
+ };
51
+ }
52
+ break;
53
+ }
54
+ catch (error) {
55
+ config_1.GlobalConfig.debugging.warn(`Translation load failed for ${lang}:`, error);
56
+ }
57
+ }
58
+ if (!translations) {
59
+ throw new Error('No translations available');
60
+ }
61
+ ctx[translationFunctionKey] = (key, options) => {
62
+ const value = key.split('.').reduce((acc, k) => {
63
+ return (acc && typeof acc === 'object') ? acc[k] : undefined;
64
+ }, translations);
65
+ const message = typeof value === 'string' ? value : key;
66
+ return formatMessage(message, options);
67
+ };
68
+ ctx.language = selectedLanguage;
69
+ ctx.languageChain = languageChain;
70
+ return await next();
71
+ }
72
+ catch (error) {
73
+ config_1.GlobalConfig.debugging.error('i18n processing error:', error);
74
+ ctx.setStatus = 500;
75
+ throw error;
76
+ }
77
+ };
78
+ };
79
+ exports.i18nMiddleware = i18nMiddleware;
@@ -24,3 +24,6 @@ __exportStar(require("./secureHeaders"), exports);
24
24
  __exportStar(require("./xssProtection"), exports);
25
25
  __exportStar(require("./sanitizeHeader"), exports);
26
26
  __exportStar(require("./rateLimiter"), exports);
27
+ __exportStar(require("./pagination"), exports);
28
+ __exportStar(require("./i18nMiddleware"), exports);
29
+ __exportStar(require("./lazyLoadModules"), exports);
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.lazyLoadModules = void 0;
4
+ const config_1 = require("../core/config");
5
+ ;
6
+ const lazyLoadModules = (options) => {
7
+ const { moduleKey = (ctx) => ctx.req.params[queryKeyModule] || ctx.req.query[queryKeyModule], getModuleLoader, queryKeyModule = "module", moduleContextKey = "module", cacheTTL = 3600000, dependencies = {}, enableCache = true, cacheStorage = new Map(), lifecycleHooks = {}, validateModule } = options;
8
+ return async (ctx, next) => {
9
+ let moduleName = moduleKey(ctx) || ctx.req.params[queryKeyModule] || ctx.req.query[queryKeyModule];
10
+ if (!moduleName) {
11
+ config_1.GlobalConfig.debugging.warn("No module specified for lazy loading.");
12
+ return await next();
13
+ }
14
+ try {
15
+ if (enableCache) {
16
+ const cached = cacheStorage.get(moduleName);
17
+ if (cached) {
18
+ if (cached.expiresAt > Date.now()) {
19
+ cacheStorage.delete(moduleName);
20
+ }
21
+ else {
22
+ config_1.GlobalConfig.debugging.info(`Using cached module: ${moduleName}`);
23
+ ctx[moduleContextKey] = cached?.module;
24
+ lifecycleHooks.onCacheHit?.(moduleName, ctx[moduleContextKey], ctx);
25
+ lifecycleHooks.onComplete?.(moduleName, ctx[moduleContextKey], ctx);
26
+ return await next();
27
+ }
28
+ }
29
+ }
30
+ if (!getModuleLoader) {
31
+ throw new Error(`No module loader found for module: ${moduleName}`);
32
+ }
33
+ const moduleLoader = getModuleLoader(ctx);
34
+ if (!moduleLoader) {
35
+ throw new Error(`No loader found for module: ${moduleName}`);
36
+ }
37
+ lifecycleHooks.onLoad?.(moduleName, ctx);
38
+ const module = await moduleLoader();
39
+ if (validateModule && !validateModule(module)) {
40
+ throw new Error(`Module validation failed for: ${moduleName}`);
41
+ }
42
+ ctx.dependencies = dependencies;
43
+ if (module.init && typeof module.init === "function") {
44
+ module.init(dependencies, ctx);
45
+ }
46
+ if (enableCache) {
47
+ cacheStorage.set(moduleName, {
48
+ module,
49
+ expiresAt: Date.now() + cacheTTL
50
+ });
51
+ lifecycleHooks.onCacheSet?.(moduleName, module, ctx);
52
+ }
53
+ ctx[moduleContextKey] = module;
54
+ lifecycleHooks.onComplete?.(moduleName, module, ctx);
55
+ config_1.GlobalConfig.debugging.success(`Successfully loaded module: ${moduleName}`);
56
+ }
57
+ catch (error) {
58
+ config_1.GlobalConfig.debugging.error(`Error loading module: ${moduleName}`, error);
59
+ lifecycleHooks.onError?.(moduleName, error, ctx);
60
+ ctx.setStatus = 500;
61
+ throw error;
62
+ }
63
+ return await next();
64
+ };
65
+ };
66
+ exports.lazyLoadModules = lazyLoadModules;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.paginationHandler = void 0;
4
+ const paginationHandler = (options = {}) => {
5
+ const { defaultPage = 1, defaultLimit = 10, maxLimit = 100, queryKeyPage = "page", queryKeyLimit = "limit", countKey = "total", dataKey = "data", getDataSource, } = options;
6
+ return async (ctx, next) => {
7
+ const rawPage = ctx.req.query[queryKeyPage];
8
+ const rawLimit = ctx.req.query[queryKeyLimit];
9
+ const page = Math.max(parseInt(rawPage || `${defaultPage}`, 10), 1);
10
+ const limit = Math.min(Math.max(parseInt(rawLimit || `${defaultLimit}`, 10), 1), maxLimit);
11
+ const offset = (page - 1) * limit;
12
+ ctx.pagination = {
13
+ page,
14
+ limit,
15
+ offset: offset,
16
+ queryKeyPage,
17
+ queryKeyLimit
18
+ };
19
+ if (getDataSource) {
20
+ try {
21
+ const dataSourceResponse = await getDataSource(ctx, { page, limit, offset });
22
+ const total = dataSourceResponse?.[countKey];
23
+ const data = dataSourceResponse?.[dataKey];
24
+ if (typeof total !== "number" || !Array.isArray(data)) {
25
+ throw new Error("Invalid data structure returned by getDataSource.");
26
+ }
27
+ ctx.body = {
28
+ [dataKey]: data,
29
+ [countKey]: total,
30
+ pagination: {
31
+ page,
32
+ limit,
33
+ totalItems: total,
34
+ totalPages: Math.ceil(total / limit),
35
+ hasNextPage: page < Math.ceil(total / limit),
36
+ hasPrevPage: page > 1,
37
+ nextPage: page < Math.ceil(total / limit) ? page + 1 : null,
38
+ prevPage: page > 1 ? page - 1 : null,
39
+ },
40
+ };
41
+ }
42
+ catch (error) {
43
+ ctx.setStatus = 500;
44
+ ctx.body = { error: "Internal Server Error", };
45
+ throw new Error("Error fetching or processing data:", error?.message);
46
+ }
47
+ }
48
+ return await next();
49
+ };
50
+ };
51
+ exports.paginationHandler = paginationHandler;
@@ -2,12 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.requestID = void 0;
4
4
  const helper_1 = require("../helper");
5
- const requestID = (headerName = "X-Request-ID") => {
5
+ const requestID = (headerName = "X-Request-ID", contextKey = "requestID") => {
6
6
  return (ctx, next) => {
7
7
  const existingID = ctx.headers?.get(headerName.toLowerCase()) ||
8
8
  ctx.headers?.get(headerName);
9
9
  const requestId = existingID || `req-${(0, helper_1.generateID)()}`;
10
- ctx.state.set("requestID", requestId);
10
+ ctx.set(contextKey, requestId);
11
11
  ctx.header(headerName, requestId);
12
12
  return next();
13
13
  };
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  export { Router } from "./core/router";
2
2
  export { TezX } from "./core/server";
3
3
  export { useParams } from "./utils/params";
4
- export let version = "1.0.31";
4
+ export let version = "1.0.33";
@@ -0,0 +1,101 @@
1
+ import { Context, Middleware } from "..";
2
+ export type TranslationMap = {
3
+ [key: string]: string | TranslationMap;
4
+ };
5
+ export type loadTranslations = (language: string) => Promise<{
6
+ translations: TranslationMap;
7
+ expiresAt?: Date | number;
8
+ }>;
9
+ export type I18nOptions = {
10
+ /**
11
+ * 🌐 Function to load translations dynamically
12
+ * @param language - Language code to load (e.g., "en-US")
13
+ * @returns Promise with translations map and optional expiration
14
+ */
15
+ loadTranslations: (language: string) => Promise<{
16
+ translations: TranslationMap;
17
+ expiresAt?: Date | number;
18
+ }>;
19
+ /**
20
+ * ⏱️ Default cache duration in milliseconds
21
+ * @default 3600000 (1 hour)
22
+ */
23
+ defaultCacheDuration?: number;
24
+ /**
25
+ * 🔄 Function to check if cached translations are stale
26
+ * @default Checks expiration time
27
+ * @example
28
+ * isCacheValid: (cached, lang) => {
29
+ * return cached.expiresAt > Date.now() &&
30
+ * cached.version === getCurrentVersion(lang);
31
+ * }
32
+ */
33
+ isCacheValid?: (cached: {
34
+ translations: TranslationMap;
35
+ expiresAt: number;
36
+ }, language: string) => boolean;
37
+ /**
38
+ * 🔍 Custom language detection function
39
+ * @default Checks query param → cookie → Accept-Language header → default
40
+ * @example
41
+ * detectLanguage: (ctx) => ctx.cookies.get('user_lang') || 'en'
42
+ */
43
+ detectLanguage?: (ctx: Context) => string;
44
+ /**
45
+ * 🏠 Default fallback language
46
+ * @default "en"
47
+ */
48
+ defaultLanguage?: string;
49
+ /**
50
+ * 🔀 Language fallback chain (most specific to least)
51
+ * @example ["fr-CA", "fr", "en"] // Try Canadian French → French → English
52
+ */
53
+ fallbackChain?: string[];
54
+ /**
55
+ * 🗝️ Context property name for translation function
56
+ * @default "t"
57
+ */
58
+ translationFunctionKey?: string;
59
+ /**
60
+ * 💬 Message formatting function
61
+ * @default Basic template replacement ({{key}})
62
+ * @example
63
+ * formatMessage: (msg, vars) => {
64
+ * return msg.replace(/\{(\w+)\}/g, (_, k) => vars[k]);
65
+ * }
66
+ */
67
+ formatMessage?: (message: string, options?: Record<string, any>) => string;
68
+ /**
69
+ * 🗃️ Cache loaded translations
70
+ * @default true
71
+ */
72
+ cacheTranslations?: boolean;
73
+ };
74
+ /**
75
+ * 🌍 Advanced i18n middleware with dynamic loading and fallback support
76
+ *
77
+ * Features:
78
+ * - Hierarchical language resolution
79
+ * - Nested translation keys
80
+ * - Template interpolation
81
+ * - Optional caching
82
+ * - Custom language detection
83
+ *
84
+ * @param {I18nOptions} options - Configuration options
85
+ * @returns {Middleware} Configured middleware
86
+ *
87
+ * @example
88
+ * // Basic usage
89
+ * app.use(i18nMiddleware({
90
+ * loadTranslations: lang => import(`./locales/${lang}.json`),
91
+ * defaultLanguage: 'en'
92
+ * }));
93
+ *
94
+ * // With caching and custom detection
95
+ * app.use(i18nMiddleware({
96
+ * loadTranslations: fetchTranslations,
97
+ * detectLanguage: ctx => ctx.get('X-Language'),
98
+ * cacheTranslations: true
99
+ * }));
100
+ */
101
+ export declare const i18nMiddleware: (options: I18nOptions) => Middleware;
@@ -0,0 +1,75 @@
1
+ import { GlobalConfig } from "../core/config";
2
+ export const i18nMiddleware = (options) => {
3
+ const { loadTranslations, defaultCacheDuration = 3600000, isCacheValid = (cached) => cached.expiresAt > Date.now(), detectLanguage = (ctx) => ctx.req.query.lang ||
4
+ ctx.cookies?.get('lang') ||
5
+ ctx.headers.get('accept-language')?.split(',')[0] ||
6
+ options.defaultLanguage ||
7
+ 'en', defaultLanguage = 'en', fallbackChain = [], translationFunctionKey = 't', formatMessage = (message, options = {}) => {
8
+ return Object.entries(options).reduce((msg, [key, value]) => msg.replace(new RegExp(`{{${key}}}`, 'g'), String(value)), message);
9
+ }, cacheTranslations = true } = options;
10
+ const translationCache = {};
11
+ return async (ctx, next) => {
12
+ try {
13
+ const detectedLanguage = detectLanguage(ctx);
14
+ const languageChain = [
15
+ detectedLanguage,
16
+ ...fallbackChain,
17
+ defaultLanguage
18
+ ].filter(Boolean);
19
+ let translations = null;
20
+ let selectedLanguage = defaultLanguage;
21
+ for (const lang of languageChain) {
22
+ const normalizedLang = lang.split('-')[0].toLowerCase();
23
+ if (cacheTranslations && translationCache[normalizedLang]) {
24
+ const cached = translationCache[normalizedLang];
25
+ if (isCacheValid(cached, normalizedLang)) {
26
+ translations = cached.translations;
27
+ selectedLanguage = lang;
28
+ break;
29
+ }
30
+ delete translationCache[normalizedLang];
31
+ }
32
+ try {
33
+ const { translations: loadedTranslations, expiresAt } = await loadTranslations(normalizedLang);
34
+ let expirationTime = Date.now() + defaultCacheDuration;
35
+ if (expiresAt instanceof Date) {
36
+ expirationTime = expiresAt.getTime();
37
+ }
38
+ else if (typeof expiresAt === 'number') {
39
+ expirationTime = expiresAt;
40
+ }
41
+ translations = loadedTranslations;
42
+ selectedLanguage = lang;
43
+ if (cacheTranslations) {
44
+ translationCache[normalizedLang] = {
45
+ translations,
46
+ expiresAt: expirationTime,
47
+ };
48
+ }
49
+ break;
50
+ }
51
+ catch (error) {
52
+ GlobalConfig.debugging.warn(`Translation load failed for ${lang}:`, error);
53
+ }
54
+ }
55
+ if (!translations) {
56
+ throw new Error('No translations available');
57
+ }
58
+ ctx[translationFunctionKey] = (key, options) => {
59
+ const value = key.split('.').reduce((acc, k) => {
60
+ return (acc && typeof acc === 'object') ? acc[k] : undefined;
61
+ }, translations);
62
+ const message = typeof value === 'string' ? value : key;
63
+ return formatMessage(message, options);
64
+ };
65
+ ctx.language = selectedLanguage;
66
+ ctx.languageChain = languageChain;
67
+ return await next();
68
+ }
69
+ catch (error) {
70
+ GlobalConfig.debugging.error('i18n processing error:', error);
71
+ ctx.setStatus = 500;
72
+ throw error;
73
+ }
74
+ };
75
+ };
@@ -7,3 +7,6 @@ export * from "./secureHeaders";
7
7
  export * from "./xssProtection";
8
8
  export * from "./sanitizeHeader";
9
9
  export * from "./rateLimiter";
10
+ export * from "./pagination";
11
+ export * from "./i18nMiddleware";
12
+ export * from './lazyLoadModules';
@@ -6,3 +6,6 @@ export * from "./secureHeaders";
6
6
  export * from "./xssProtection";
7
7
  export * from "./sanitizeHeader";
8
8
  export * from "./rateLimiter";
9
+ export * from "./pagination";
10
+ export * from "./i18nMiddleware";
11
+ export * from './lazyLoadModules';
@@ -0,0 +1,78 @@
1
+ import { Context, Middleware } from "..";
2
+ export type LazyModuleLoader<T> = () => Promise<T>;
3
+ export interface CacheItem<T = any> {
4
+ module: T;
5
+ expiresAt: number;
6
+ }
7
+ interface LazyLoadOptions<T> {
8
+ /**
9
+ * 🗺️ Key to identify the module to load. This can be a function that extracts the module name from the request context.
10
+ * @default (ctx) => ctx.req.params[queryKeyModule] || ctx.req.query[queryKeyModule]
11
+ */
12
+ moduleKey?: (ctx: Context) => string;
13
+ /**
14
+ * 🛠️ Function that returns a loader function for the specified module, used to dynamically load the module.
15
+ * If this function returns null, it indicates no loader is available for the module.
16
+ */
17
+ getModuleLoader: (ctx: Context) => LazyModuleLoader<T> | null;
18
+ /**
19
+ * 🔍 Query parameter name to select which module to load (e.g., "module").
20
+ * @default "module"
21
+ */
22
+ queryKeyModule?: string;
23
+ /**
24
+ * 📦 Key to attach the loaded module to the context object.
25
+ * @default "module"
26
+ */
27
+ moduleContextKey?: string;
28
+ /**
29
+ * ⚙️ Dependencies to inject into the module during initialization.
30
+ */
31
+ dependencies?: Record<string, any>;
32
+ /**
33
+ * 🔄 Enable caching of loaded modules to avoid re-loading them repeatedly.
34
+ * @default true
35
+ */
36
+ enableCache?: boolean;
37
+ /**
38
+ * 🔄 Custom cache storage implementation (e.g., using `Map`, `Redis`, etc.).
39
+ * By default, it uses a `Map<string, CacheItem<T>>`.
40
+ */
41
+ cacheStorage?: {
42
+ get: (key: string) => CacheItem<T> | undefined;
43
+ set: (key: string, value: CacheItem<T>) => void;
44
+ delete: (key: string) => void;
45
+ clear?: () => void;
46
+ };
47
+ /**
48
+ * ⏳ Cache Time-To-Live (TTL) in milliseconds. This determines how long cached modules are valid.
49
+ * @default 3600000 (1 hour)
50
+ */
51
+ cacheTTL?: number;
52
+ /**
53
+ * 🌟 Lifecycle hooks for the module loading process.
54
+ * These hooks allow for custom actions at various stages of loading the module (e.g., when a module is loaded or when cache is hit).
55
+ */
56
+ lifecycleHooks?: {
57
+ onLoad?: (moduleName: string, ctx: Context) => void;
58
+ onError?: (moduleName: string, error: Error, ctx: Context) => void;
59
+ onComplete?: (moduleName: string, module: T, ctx: Context) => void;
60
+ onCacheHit?: (moduleName: string, module: T, ctx: Context) => void;
61
+ onCacheSet?: (moduleName: string, module: T, ctx: Context) => void;
62
+ };
63
+ /**
64
+ * 🛡️ Module validation function to ensure the module meets specific criteria before use.
65
+ * This function will be called after the module is loaded to verify its structure or behavior.
66
+ * If validation fails, an error is thrown.
67
+ */
68
+ validateModule?: (module: T) => boolean;
69
+ }
70
+ /**
71
+ * Middleware for handling lazy loading of modules. This middleware allows dynamically loading modules based on route or query parameters.
72
+ * It supports caching, lifecycle hooks, and module validation.
73
+ *
74
+ * @param options - Custom options for lazy loading, including caching, hooks, and module validation.
75
+ * @returns A middleware function to use in your application.
76
+ */
77
+ export declare const lazyLoadModules: <T = any>(options: LazyLoadOptions<T>) => Middleware;
78
+ export {};
@@ -0,0 +1,62 @@
1
+ import { GlobalConfig } from "../core/config";
2
+ ;
3
+ export const lazyLoadModules = (options) => {
4
+ const { moduleKey = (ctx) => ctx.req.params[queryKeyModule] || ctx.req.query[queryKeyModule], getModuleLoader, queryKeyModule = "module", moduleContextKey = "module", cacheTTL = 3600000, dependencies = {}, enableCache = true, cacheStorage = new Map(), lifecycleHooks = {}, validateModule } = options;
5
+ return async (ctx, next) => {
6
+ let moduleName = moduleKey(ctx) || ctx.req.params[queryKeyModule] || ctx.req.query[queryKeyModule];
7
+ if (!moduleName) {
8
+ GlobalConfig.debugging.warn("No module specified for lazy loading.");
9
+ return await next();
10
+ }
11
+ try {
12
+ if (enableCache) {
13
+ const cached = cacheStorage.get(moduleName);
14
+ if (cached) {
15
+ if (cached.expiresAt > Date.now()) {
16
+ cacheStorage.delete(moduleName);
17
+ }
18
+ else {
19
+ GlobalConfig.debugging.info(`Using cached module: ${moduleName}`);
20
+ ctx[moduleContextKey] = cached?.module;
21
+ lifecycleHooks.onCacheHit?.(moduleName, ctx[moduleContextKey], ctx);
22
+ lifecycleHooks.onComplete?.(moduleName, ctx[moduleContextKey], ctx);
23
+ return await next();
24
+ }
25
+ }
26
+ }
27
+ if (!getModuleLoader) {
28
+ throw new Error(`No module loader found for module: ${moduleName}`);
29
+ }
30
+ const moduleLoader = getModuleLoader(ctx);
31
+ if (!moduleLoader) {
32
+ throw new Error(`No loader found for module: ${moduleName}`);
33
+ }
34
+ lifecycleHooks.onLoad?.(moduleName, ctx);
35
+ const module = await moduleLoader();
36
+ if (validateModule && !validateModule(module)) {
37
+ throw new Error(`Module validation failed for: ${moduleName}`);
38
+ }
39
+ ctx.dependencies = dependencies;
40
+ if (module.init && typeof module.init === "function") {
41
+ module.init(dependencies, ctx);
42
+ }
43
+ if (enableCache) {
44
+ cacheStorage.set(moduleName, {
45
+ module,
46
+ expiresAt: Date.now() + cacheTTL
47
+ });
48
+ lifecycleHooks.onCacheSet?.(moduleName, module, ctx);
49
+ }
50
+ ctx[moduleContextKey] = module;
51
+ lifecycleHooks.onComplete?.(moduleName, module, ctx);
52
+ GlobalConfig.debugging.success(`Successfully loaded module: ${moduleName}`);
53
+ }
54
+ catch (error) {
55
+ GlobalConfig.debugging.error(`Error loading module: ${moduleName}`, error);
56
+ lifecycleHooks.onError?.(moduleName, error, ctx);
57
+ ctx.setStatus = 500;
58
+ throw error;
59
+ }
60
+ return await next();
61
+ };
62
+ };
@@ -0,0 +1,89 @@
1
+ import { Context, Middleware } from "..";
2
+ export type PaginationOptions = {
3
+ /**
4
+ * 🔢 Default page number when not specified
5
+ * @default 1
6
+ * @example 1 // Start from first page
7
+ */
8
+ defaultPage?: number;
9
+ /**
10
+ * 📏 Default items per page
11
+ * @default 10
12
+ * @example 25 // Show 25 items by default
13
+ */
14
+ defaultLimit?: number;
15
+ /**
16
+ * ⚠️ Maximum allowed items per page
17
+ * @default 100
18
+ * @example 50 // Never return more than 50 items
19
+ */
20
+ maxLimit?: number;
21
+ /**
22
+ * 🔍 Query parameter name for page number
23
+ * @default "page"
24
+ * @example "p" // Use ?p=2 instead of ?page=2
25
+ */
26
+ queryKeyPage?: string;
27
+ /**
28
+ * 🔍 Query parameter name for items limit
29
+ * @default "limit"
30
+ * @example "size" // Use ?size=20
31
+ */
32
+ queryKeyLimit?: string;
33
+ /**
34
+ * 📊 Key to read total count from response
35
+ * @default "total"
36
+ * @example "totalCount" // Read from response.totalCount
37
+ */
38
+ countKey?: string;
39
+ /**
40
+ * 📦 Key containing the data array in response
41
+ * @default "data"
42
+ * @example "items" // Process response.items array
43
+ */
44
+ dataKey?: string;
45
+ /**
46
+ * 🛠️ Function to fetch data dynamically
47
+ * @param ctx - Request context
48
+ * @param pagination - Pagination details
49
+ * @returns Promise with data and total count
50
+ * @example
51
+ * getDataSource: async (ctx, { page, limit }) => {
52
+ * return db.find().skip((page-1)*limit).limit(limit);
53
+ * }
54
+ */
55
+ getDataSource?: (ctx: Context, pagination: {
56
+ page: number;
57
+ limit: number;
58
+ offset: number;
59
+ }) => Promise<{
60
+ [key: string]: any;
61
+ }>;
62
+ };
63
+ /**
64
+ * 🗂️ Advanced pagination middleware with dynamic data fetching
65
+ *
66
+ * Features:
67
+ * - Automatic pagination parameter handling
68
+ * - Dynamic data source integration
69
+ * - Comprehensive pagination metadata
70
+ * - Built-in error handling
71
+ *
72
+ * @param {PaginationOptions} [options={}] - Configuration options
73
+ * @returns {Middleware} Configured middleware
74
+ *
75
+ * @example
76
+ * // Basic usage
77
+ * app.get('/users', paginationHandler(), getUsers);
78
+ *
79
+ * // With dynamic data source
80
+ * app.get('/products', paginationHandler({
81
+ * getDataSource: async (ctx, { page, limit }) => {
82
+ * return await Product.findAndCountAll({
83
+ * offset: (page-1)*limit,
84
+ * limit
85
+ * });
86
+ * }
87
+ * }));
88
+ */
89
+ export declare const paginationHandler: (options?: PaginationOptions) => Middleware;
@@ -0,0 +1,47 @@
1
+ export const paginationHandler = (options = {}) => {
2
+ const { defaultPage = 1, defaultLimit = 10, maxLimit = 100, queryKeyPage = "page", queryKeyLimit = "limit", countKey = "total", dataKey = "data", getDataSource, } = options;
3
+ return async (ctx, next) => {
4
+ const rawPage = ctx.req.query[queryKeyPage];
5
+ const rawLimit = ctx.req.query[queryKeyLimit];
6
+ const page = Math.max(parseInt(rawPage || `${defaultPage}`, 10), 1);
7
+ const limit = Math.min(Math.max(parseInt(rawLimit || `${defaultLimit}`, 10), 1), maxLimit);
8
+ const offset = (page - 1) * limit;
9
+ ctx.pagination = {
10
+ page,
11
+ limit,
12
+ offset: offset,
13
+ queryKeyPage,
14
+ queryKeyLimit
15
+ };
16
+ if (getDataSource) {
17
+ try {
18
+ const dataSourceResponse = await getDataSource(ctx, { page, limit, offset });
19
+ const total = dataSourceResponse?.[countKey];
20
+ const data = dataSourceResponse?.[dataKey];
21
+ if (typeof total !== "number" || !Array.isArray(data)) {
22
+ throw new Error("Invalid data structure returned by getDataSource.");
23
+ }
24
+ ctx.body = {
25
+ [dataKey]: data,
26
+ [countKey]: total,
27
+ pagination: {
28
+ page,
29
+ limit,
30
+ totalItems: total,
31
+ totalPages: Math.ceil(total / limit),
32
+ hasNextPage: page < Math.ceil(total / limit),
33
+ hasPrevPage: page > 1,
34
+ nextPage: page < Math.ceil(total / limit) ? page + 1 : null,
35
+ prevPage: page > 1 ? page - 1 : null,
36
+ },
37
+ };
38
+ }
39
+ catch (error) {
40
+ ctx.setStatus = 500;
41
+ ctx.body = { error: "Internal Server Error", };
42
+ throw new Error("Error fetching or processing data:", error?.message);
43
+ }
44
+ }
45
+ return await next();
46
+ };
47
+ };
@@ -13,4 +13,4 @@ import { Middleware } from "../core/router";
13
13
  * app.use(requestID());
14
14
  * ```
15
15
  */
16
- export declare const requestID: (headerName?: string) => Middleware;
16
+ export declare const requestID: (headerName?: string, contextKey?: string) => Middleware;
@@ -1,10 +1,10 @@
1
1
  import { generateID } from "../helper";
2
- export const requestID = (headerName = "X-Request-ID") => {
2
+ export const requestID = (headerName = "X-Request-ID", contextKey = "requestID") => {
3
3
  return (ctx, next) => {
4
4
  const existingID = ctx.headers?.get(headerName.toLowerCase()) ||
5
5
  ctx.headers?.get(headerName);
6
6
  const requestId = existingID || `req-${generateID()}`;
7
- ctx.state.set("requestID", requestId);
7
+ ctx.set(contextKey, requestId);
8
8
  ctx.header(headerName, requestId);
9
9
  return next();
10
10
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tezx",
3
- "version": "1.0.31",
3
+ "version": "1.0.33",
4
4
  "description": "TezX is a high-performance, lightweight JavaScript framework designed for speed, scalability, and flexibility. It enables efficient routing, middleware management, and static file serving with minimal configuration. Fully compatible with Node.js, Deno, and Bun.",
5
5
  "main": "cjs/index.js",
6
6
  "module": "index.js",