@mrxsys/mrx-core 2.11.0-1-and-273-20251029 → 2.11.0-1-and-275-20251029

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.
@@ -1,12 +1,9 @@
1
1
  import { Elysia } from 'elysia';
2
- import { MemoryStore } from '../../../modules/kv-store/memory/memory-store';
2
+ import type { KvStore } from '../../../modules/kv-store/types';
3
3
  import type { CacheOptions } from './types/cache-options';
4
- export declare const cache: ({ defaultTtl, prefix, store }?: CacheOptions) => Elysia<"", {
4
+ export declare const cache: (store?: KvStore) => Elysia<"", {
5
5
  decorator: {};
6
- store: {
7
- kvStore: import("../../kv-store/types").KvStore | MemoryStore;
8
- _cachedRoutes: Set<string>;
9
- };
6
+ store: {};
10
7
  derive: {};
11
8
  resolve: {};
12
9
  }, {
@@ -16,11 +13,11 @@ export declare const cache: ({ defaultTtl, prefix, store }?: CacheOptions) => El
16
13
  schema: {};
17
14
  standaloneSchema: {};
18
15
  macro: Partial<{
19
- readonly isCached: number | boolean;
16
+ readonly isCached: CacheOptions;
20
17
  }>;
21
18
  macroFn: {
22
- readonly isCached: (enable: boolean | number) => {
23
- readonly afterHandle: ({ set, responseValue, store, request }: {
19
+ readonly isCached: ({ ttl, prefix }: CacheOptions) => {
20
+ readonly afterHandle: ({ set, responseValue, request }: {
24
21
  body: unknown;
25
22
  query: Record<string, string>;
26
23
  params: {};
@@ -37,10 +34,7 @@ export declare const cache: ({ defaultTtl, prefix, store }?: CacheOptions) => El
37
34
  path: string;
38
35
  route: string;
39
36
  request: Request;
40
- store: {
41
- kvStore: import("../../kv-store/types").KvStore | MemoryStore;
42
- _cachedRoutes: Set<string>;
43
- };
37
+ store: {};
44
38
  status: <const Code extends number | keyof import("elysia").StatusMap, const T = Code extends 100 | 101 | 102 | 103 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 421 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 451 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511 | 300 | 301 | 302 | 303 | 304 | 307 | 308 | 420 ? {
45
39
  readonly 100: "Continue";
46
40
  readonly 101: "Switching Protocols";
@@ -1,70 +1,90 @@
1
1
  // @bun
2
2
  import {
3
3
  MemoryStore
4
- } from "../../../chunk-e30paw8a.js";
4
+ } from "../../kv-store/memory/index.js";
5
5
  import"../../../chunk-xhhj1gvj.js";
6
- import {
7
- generateCacheKey
8
- } from "../../../chunk-b23dvm2d.js";
9
6
  import"../../../chunk-9cgzhc50.js";
10
7
 
11
8
  // source/modules/elysia/cache/cache.ts
12
9
  import { Elysia } from "elysia";
13
- var cache = ({
14
- defaultTtl = 60,
15
- prefix = "",
16
- store = ":memory:"
17
- } = {}) => new Elysia().state({
18
- kvStore: store === ":memory:" ? new MemoryStore : store
19
- }).state({
20
- _cachedRoutes: new Set
21
- }).onRequest(async ({ request, store: store2, set }) => {
22
- const sanitizeUrl = new URL(request.url).pathname;
23
- if (store2._cachedRoutes.has(`${request.method}:${sanitizeUrl}`)) {
24
- const cacheKey = await generateCacheKey(request.clone());
25
- const cachedData = await store2.kvStore.get(`${prefix}${cacheKey}`);
26
- if (cachedData && typeof cachedData === "object" && "response" in cachedData && "metadata" in cachedData) {
27
- const { response, metadata } = cachedData;
28
- set.headers["cache-control"] = `max-age=${metadata.ttl}, public`;
29
- set.headers["x-cache"] = "HIT";
30
- set.headers["etag"] = `"${prefix}${cacheKey}"`;
31
- set.headers["expires"] = new Date(Date.now() + metadata.ttl * 1000).toUTCString();
32
- set.headers["last-modified"] = metadata.createdAt;
33
- if (response instanceof Response)
34
- return response.clone();
35
- return response;
10
+
11
+ // source/modules/elysia/cache/utils/generate-cache-key.ts
12
+ var _calculateBodyHash = async (body, hasher) => {
13
+ if (!body)
14
+ return;
15
+ const reader = body.getReader();
16
+ try {
17
+ while (true) {
18
+ const { done, value } = await reader.read();
19
+ if (done)
20
+ break;
21
+ if (value)
22
+ hasher.update(new Uint8Array(value));
36
23
  }
37
- set.headers["x-cache"] = "MISS";
24
+ } finally {
25
+ reader.releaseLock();
38
26
  }
39
- return;
40
- }).macro({
41
- isCached: (enable) => {
42
- const ttl = typeof enable === "number" ? enable : enable ? defaultTtl : 0;
43
- return {
44
- async afterHandle({ set, responseValue, store: store2, request }) {
45
- const sanitizeUrl = new URL(request.url).pathname;
46
- if (!store2._cachedRoutes.has(`${request.method}:${sanitizeUrl}`))
47
- store2._cachedRoutes.add(`${request.method}:${sanitizeUrl}`);
27
+ };
28
+ var generateCacheKey = async (request) => {
29
+ const { method, url, headers } = request;
30
+ const hasher = new Bun.CryptoHasher("sha256");
31
+ hasher.update(method);
32
+ hasher.update(url);
33
+ hasher.update(JSON.stringify(headers));
34
+ await _calculateBodyHash(request.body, hasher);
35
+ return hasher.digest("hex");
36
+ };
37
+
38
+ // source/modules/elysia/cache/cache.ts
39
+ var cache = (store = new MemoryStore) => {
40
+ const cachedRoutes = new Map;
41
+ return new Elysia().onRequest(async ({ request, set }) => {
42
+ const route = `${request.method}:${new URL(request.url).pathname}`;
43
+ if (cachedRoutes.has(route)) {
44
+ const { ttl, prefix } = cachedRoutes.get(route);
45
+ const cacheKey = await generateCacheKey(request.clone());
46
+ const cacheItem = await store.get(`${prefix}${cacheKey}`);
47
+ if (cacheItem && typeof cacheItem === "object" && "response" in cacheItem && "metadata" in cacheItem) {
48
+ const createdAt = new Date(cacheItem.metadata.createdAt);
49
+ const expiresAt = new Date(createdAt.getTime() + ttl * 1000);
50
+ const now = Date.now();
51
+ const remaining = Math.max(0, Math.ceil((expiresAt.getTime() - now) / 1000));
52
+ set.headers["cache-control"] = `max-age=${remaining}, public`;
53
+ set.headers["etag"] = `"${prefix}${cacheKey}"`;
54
+ set.headers["last-modified"] = cacheItem.metadata.createdAt;
55
+ set.headers["expires"] = expiresAt.toUTCString();
56
+ set.headers["x-cache"] = "HIT";
57
+ if (cacheItem.response instanceof Response)
58
+ return cacheItem.response.clone();
59
+ return cacheItem.response;
60
+ }
61
+ set.headers["x-cache"] = "MISS";
62
+ }
63
+ return;
64
+ }).macro({
65
+ isCached: ({ ttl, prefix = "" }) => ({
66
+ async afterHandle({ set, responseValue, request }) {
67
+ const route = `${request.method}:${new URL(request.url).pathname}`;
68
+ if (!cachedRoutes.has(route))
69
+ cachedRoutes.set(route, { ttl, prefix });
48
70
  const cacheKey = await generateCacheKey(request.clone());
49
71
  const now = new Date;
50
72
  set.headers["cache-control"] = `max-age=${ttl}, public`;
51
73
  set.headers["etag"] = `"${prefix}${cacheKey}"`;
52
74
  set.headers["last-modified"] = now.toUTCString();
53
75
  set.headers["expires"] = new Date(now.getTime() + ttl * 1000).toUTCString();
54
- if (!set.headers["x-cache"])
55
- set.headers["x-cache"] = "MISS";
56
- const cacheData = {
76
+ set.headers["x-cache"] = "MISS";
77
+ const cacheItem = {
57
78
  response: responseValue instanceof Response ? responseValue.clone() : responseValue,
58
79
  metadata: {
59
- createdAt: now.toUTCString(),
60
- ttl
80
+ createdAt: now.toUTCString()
61
81
  }
62
82
  };
63
- await store2.kvStore.set(`${prefix}${cacheKey}`, cacheData, ttl);
83
+ await store.set(`${prefix}${cacheKey}`, cacheItem, ttl);
64
84
  }
65
- };
66
- }
67
- });
85
+ })
86
+ });
87
+ };
68
88
  export {
69
89
  cache
70
90
  };
@@ -2,6 +2,5 @@ export interface CacheItem {
2
2
  response: unknown;
3
3
  metadata: {
4
4
  createdAt: string;
5
- ttl: number;
6
5
  };
7
6
  }
@@ -1,21 +1,12 @@
1
- import type { KvStore } from '../../../../modules/kv-store/types/kv-store';
2
1
  export interface CacheOptions {
3
2
  /**
4
- * Default TTL in seconds
5
- *
6
- * @defaultValue 60
3
+ * TTL in seconds
7
4
  */
8
- defaultTtl?: number;
5
+ ttl: number;
9
6
  /**
10
7
  * Cache key prefix
11
8
  *
12
9
  * @defaultValue ''
13
10
  */
14
11
  prefix?: string;
15
- /**
16
- * Storage backend
17
- *
18
- * @defaultValue ':memory:'
19
- */
20
- store?: ':memory:' | KvStore;
21
12
  }
@@ -1,14 +1,4 @@
1
1
  // @bun
2
- import {
3
- batchDelete,
4
- count,
5
- deleteOne,
6
- find,
7
- findOne,
8
- insert,
9
- update,
10
- updateOne
11
- } from "../../../chunk-pjv1ekwr.js";
12
2
  import {
13
3
  createCountResponse200Schema,
14
4
  createCountSchema,
@@ -21,6 +11,16 @@ import {
21
11
  createUpdateSchema
22
12
  } from "../../../chunk-p14h6jfs.js";
23
13
  import"../../../chunk-9dzsj7f2.js";
14
+ import {
15
+ batchDelete,
16
+ count,
17
+ deleteOne,
18
+ find,
19
+ findOne,
20
+ insert,
21
+ update,
22
+ updateOne
23
+ } from "../../../chunk-pjv1ekwr.js";
24
24
  import"../../../chunk-441xs5k1.js";
25
25
  import"../../../chunk-fs3wm3p4.js";
26
26
  import"../../../chunk-z0ct35ft.js";
@@ -4,7 +4,7 @@ import {
4
4
  } from "../../../chunk-dre2fgj0.js";
5
5
  import {
6
6
  MemoryStore
7
- } from "../../../chunk-e30paw8a.js";
7
+ } from "../../kv-store/memory/index.js";
8
8
  import"../../../chunk-xhhj1gvj.js";
9
9
  import {
10
10
  HttpError
@@ -14,40 +14,65 @@ import"../../../chunk-9cgzhc50.js";
14
14
 
15
15
  // source/modules/elysia/rate-limit/rate-limit.ts
16
16
  import { Elysia } from "elysia";
17
- var rateLimit = ({ store, limit, window }) => {
18
- const storeInstance = store === ":memory:" || !store ? new MemoryStore : store;
19
- return new Elysia({
20
- name: "rateLimit",
21
- seed: {
22
- store,
23
- limit,
24
- window
25
- }
26
- }).onRequest(async ({ set, request, server }) => {
27
- const ip = request.headers.get("x-forwarded-for") || request.headers.get("x-real-ip") || server?.requestIP(request)?.address || "127.0.0.1";
28
- const key = `ratelimit:${ip}`;
29
- const count = await storeInstance.get(key);
30
- let newCount;
31
- if (count === null) {
32
- await storeInstance.set(key, 1, window);
33
- newCount = 1;
17
+ var rateLimit = (store = new MemoryStore) => {
18
+ const restrictedRoutes = new Map;
19
+ const rateLimitCheck = async (key, limit, window, set) => {
20
+ if (set.headers["X-RateLimit-Limit"])
21
+ return;
22
+ let count = await store.get(key) ?? 0;
23
+ if (count === 0) {
24
+ await store.set(key, 1, window);
25
+ count = 1;
34
26
  } else {
35
- newCount = await storeInstance.increment(key);
27
+ count = await store.increment(key);
36
28
  }
37
- if (newCount > limit) {
29
+ const remaining = Math.max(0, limit - count);
30
+ const resetTime = await store.ttl(key);
31
+ set.headers = {
32
+ "X-RateLimit-Limit": limit.toString(),
33
+ "X-RateLimit-Remaining": remaining.toString(),
34
+ "X-RateLimit-Reset": resetTime.toString()
35
+ };
36
+ if (count > limit) {
38
37
  set.status = 429;
39
38
  throw new HttpError(RATE_LIMIT_ERROR_KEYS.RATE_LIMIT_EXCEEDED, "TOO_MANY_REQUESTS", {
40
39
  limit,
41
40
  window,
42
41
  remaining: 0,
43
- reset: await storeInstance.ttl(key)
42
+ reset: resetTime
44
43
  });
45
44
  }
46
- set.headers = {
47
- "X-RateLimit-Limit": limit.toString(),
48
- "X-RateLimit-Remaining": Math.max(0, limit - newCount).toString(),
49
- "X-RateLimit-Reset": (await storeInstance.ttl(key)).toString()
50
- };
45
+ };
46
+ return new Elysia({
47
+ name: "rateLimit",
48
+ seed: {
49
+ store
50
+ }
51
+ }).macro({
52
+ rateLimit: ({ limit, window }) => ({
53
+ transform: ({ request }) => {
54
+ const route = `${request.method}:${new URL(request.url).pathname}`;
55
+ if (!restrictedRoutes.has(route)) {
56
+ restrictedRoutes.set(route, { limit, window });
57
+ } else if (restrictedRoutes.has(route)) {
58
+ const existing = restrictedRoutes.get(route);
59
+ if (limit != existing.limit || window != existing.window)
60
+ restrictedRoutes.set(route, {
61
+ limit,
62
+ window
63
+ });
64
+ }
65
+ },
66
+ beforeHandle: async ({ set, request, server }) => {
67
+ const route = `${request.method}:${new URL(request.url).pathname}`;
68
+ if (restrictedRoutes.has(route)) {
69
+ const { limit: limit2, window: window2 } = restrictedRoutes.get(route);
70
+ const ip = request.headers.get("x-forwarded-for") || request.headers.get("x-real-ip") || server?.requestIP(request)?.address || "127.0.0.1";
71
+ const key = `ratelimit:${route}:${ip}`;
72
+ await rateLimitCheck(key, limit2, window2, set);
73
+ }
74
+ }
75
+ })
51
76
  }).as("global");
52
77
  };
53
78
  export {
@@ -1,42 +1,55 @@
1
- import { Elysia } from 'elysia';
1
+ import type { KvStore } from '../../../modules/kv-store/types';
2
+ import { Elysia, type HTTPHeaders, type StatusMap } from 'elysia';
2
3
  import type { RateLimitOptions } from './types/rate-limit-options';
3
4
  /**
4
- * The `rateLimitPlugin` provides rate limiting capabilities for Elysia applications,
5
- * protecting APIs from excessive use and potential abuse. It tracks request rates by client IP
6
- * and enforces configurable limits based on a sliding time window.
5
+ * Rate limiting plugin for Elysia applications that protects APIs from excessive use and potential abuse.
7
6
  *
8
- * ### Rate Limit Headers:
9
- * The plugin adds the following headers to all responses:
7
+ * The plugin tracks request rates by client IP address and enforces configurable limits based on a fixed time window.
8
+ * It supports multiple storage backends through the `@mrxsys/mrx-core/modules/kv-store` ecosystem for both single-instance and distributed deployments.
9
+ *
10
+ * ### How It Works
11
+ * - Rate limiting is applied per IP address and per route
12
+ * - Each route can have its own limit and time window configuration
13
+ * - Route-level rate limits override global rate limits
14
+ * - Headers are added to all responses indicating the current rate limit status
15
+ *
16
+ * ### Rate Limit Headers
17
+ * The plugin adds the following headers to all responses (including 429 errors):
10
18
  * - `X-RateLimit-Limit`: The maximum number of requests allowed in the window
11
19
  * - `X-RateLimit-Remaining`: The number of requests remaining in the current window
12
- * - `X-RateLimit-Reset`: The time in seconds until the rate limit resets
20
+ * - `X-RateLimit-Reset`: The time in seconds until the rate limit resets (Unix timestamp)
21
+ *
22
+ * When a rate limit is exceeded, the response includes a 429 (Too Many Requests) status code
23
+ * and the error key `elysia.rate-limit.error.exceeded`, along with the rate limit headers
24
+ * to help clients implement exponential backoff retry strategies.
13
25
  *
14
- * @param options - The configuration options for the rate limit plugin
26
+ * @param store - The KV store instance for tracking rate limits. Defaults to an in-memory store.
27
+ * Supports any `KvStore` implementation from `@mrxsys/mrx-core/modules/kv-store` (e.g., `MemoryStore`, `IoRedisStore`)
15
28
  *
16
- * @returns An {@link Elysia} plugin that adds rate limiting functionality
29
+ * @returns An {@link Elysia} plugin that adds rate limiting functionality via the `rateLimit` macro
17
30
  *
18
31
  * @example
19
- * Basic usage with default in-memory store
32
+ * Basic usage with default in-memory store (development)
20
33
  * ```ts
21
- * import { rateLimit } from '@nowarajs/elysia-ratelimit';
34
+ * import { rateLimit } from '@mrxsys/mrx-core/modules/elysia/elysia-ratelimit';
22
35
  * import { Elysia } from 'elysia';
23
36
  *
24
37
  * const app = new Elysia()
25
- * .use(rateLimit({
26
- * store: ':memory:', // Use in-memory store
27
- * limit: 100, // 100 requests
28
- * window: 60, // per minute
29
- * }))
30
- * .get('/api/endpoint', () => ({ message: 'Hello World' }));
31
- *
32
- * app.listen(3000);
38
+ * .use(rateLimit())
39
+ * .get('/api/data', () => ({ message: 'Hello World' }), {
40
+ * rateLimit: {
41
+ * limit: 100, // 100 requests
42
+ * window: 60 // per minute
43
+ * }
44
+ * })
45
+ * .listen(3000);
33
46
  * ```
34
47
  *
35
48
  * @example
36
- * Using Redis store for distributed rate limiting
49
+ * Using Redis store for distributed rate limiting (production)
37
50
  * ```ts
38
- * import { IoRedisStore } from '@nowarajs/kv-store';
39
- * import { rateLimit } from '@nowarajs/elysia-ratelimit';
51
+ * import { IoRedisStore } from '@mrxsys/mrx-core/modules/kv-store/ioredis'; // or use bun-redis
52
+ * import { rateLimit } from '@mrxsys/mrx-core/modules/elysia/elysia-ratelimit';
40
53
  * import { Elysia } from 'elysia';
41
54
  *
42
55
  * const redisStore = new IoRedisStore({
@@ -46,17 +59,40 @@ import type { RateLimitOptions } from './types/rate-limit-options';
46
59
  * await redisStore.connect();
47
60
  *
48
61
  * const app = new Elysia()
49
- * .use(rateLimit({
50
- * store: redisStore,
51
- * limit: 1000, // 1000 requests
52
- * window: 3600, // per hour
53
- * }))
54
- * .get('/api/endpoint', () => ({ message: 'Hello World' }));
62
+ * .use(rateLimit(redisStore))
63
+ * .get('/api/endpoint', () => ({ data: 'content' }), {
64
+ * rateLimit: {
65
+ * limit: 1000, // 1000 requests
66
+ * window: 3600 // per hour
67
+ * }
68
+ * })
69
+ * .listen(3000);
70
+ * ```
55
71
  *
56
- * app.listen(3000);
72
+ * @example
73
+ * Global rate limit with route-level overrides
74
+ * ```ts
75
+ * const app = new Elysia()
76
+ * .use(rateLimit())
77
+ * .guard({
78
+ * rateLimit: {
79
+ * limit: 100,
80
+ * window: 60
81
+ * }
82
+ * })
83
+ * .get('/api/slow', () => 'content', {
84
+ * rateLimit: {
85
+ * limit: 10, // Override global: stricter limit
86
+ * window: 60
87
+ * }
88
+ * })
89
+ * .get('/api/fast', () => 'content') // Uses global limit
90
+ * .listen(3000);
57
91
  * ```
92
+ *
93
+ * @throws ({@link HttpError}) – `elysia.rate-limit.error.exceeded` when the rate limit is exceeded (HTTP 429)
58
94
  */
59
- export declare const rateLimit: ({ store, limit, window }: RateLimitOptions) => Elysia<"", {
95
+ export declare const rateLimit: (store?: KvStore) => Elysia<"", {
60
96
  decorator: {};
61
97
  store: {};
62
98
  derive: {};
@@ -67,8 +103,301 @@ export declare const rateLimit: ({ store, limit, window }: RateLimitOptions) =>
67
103
  }, {
68
104
  schema: {};
69
105
  standaloneSchema: {};
70
- macro: {};
71
- macroFn: {};
106
+ macro: Partial<{
107
+ readonly rateLimit: RateLimitOptions;
108
+ }>;
109
+ macroFn: {
110
+ readonly rateLimit: ({ limit, window }: RateLimitOptions) => {
111
+ readonly transform: ({ request }: {
112
+ body: unknown;
113
+ query: Record<string, string>;
114
+ params: {};
115
+ headers: Record<string, string | undefined>;
116
+ cookie: Record<string, import("elysia").Cookie<unknown>>;
117
+ server: import("elysia/universal/server").Server | null;
118
+ redirect: import("elysia").redirect;
119
+ set: {
120
+ headers: HTTPHeaders;
121
+ status?: number | keyof StatusMap;
122
+ redirect?: string;
123
+ cookie?: Record<string, import("elysia/cookies").ElysiaCookie>;
124
+ };
125
+ path: string;
126
+ route: string;
127
+ request: Request;
128
+ store: {};
129
+ status: <const Code extends number | keyof StatusMap, const T = Code extends 100 | 101 | 102 | 103 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 421 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 451 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511 | 300 | 301 | 302 | 303 | 304 | 307 | 308 | 420 ? {
130
+ readonly 100: "Continue";
131
+ readonly 101: "Switching Protocols";
132
+ readonly 102: "Processing";
133
+ readonly 103: "Early Hints";
134
+ readonly 200: "OK";
135
+ readonly 201: "Created";
136
+ readonly 202: "Accepted";
137
+ readonly 203: "Non-Authoritative Information";
138
+ readonly 204: "No Content";
139
+ readonly 205: "Reset Content";
140
+ readonly 206: "Partial Content";
141
+ readonly 207: "Multi-Status";
142
+ readonly 208: "Already Reported";
143
+ readonly 300: "Multiple Choices";
144
+ readonly 301: "Moved Permanently";
145
+ readonly 302: "Found";
146
+ readonly 303: "See Other";
147
+ readonly 304: "Not Modified";
148
+ readonly 307: "Temporary Redirect";
149
+ readonly 308: "Permanent Redirect";
150
+ readonly 400: "Bad Request";
151
+ readonly 401: "Unauthorized";
152
+ readonly 402: "Payment Required";
153
+ readonly 403: "Forbidden";
154
+ readonly 404: "Not Found";
155
+ readonly 405: "Method Not Allowed";
156
+ readonly 406: "Not Acceptable";
157
+ readonly 407: "Proxy Authentication Required";
158
+ readonly 408: "Request Timeout";
159
+ readonly 409: "Conflict";
160
+ readonly 410: "Gone";
161
+ readonly 411: "Length Required";
162
+ readonly 412: "Precondition Failed";
163
+ readonly 413: "Payload Too Large";
164
+ readonly 414: "URI Too Long";
165
+ readonly 415: "Unsupported Media Type";
166
+ readonly 416: "Range Not Satisfiable";
167
+ readonly 417: "Expectation Failed";
168
+ readonly 418: "I'm a teapot";
169
+ readonly 420: "Enhance Your Calm";
170
+ readonly 421: "Misdirected Request";
171
+ readonly 422: "Unprocessable Content";
172
+ readonly 423: "Locked";
173
+ readonly 424: "Failed Dependency";
174
+ readonly 425: "Too Early";
175
+ readonly 426: "Upgrade Required";
176
+ readonly 428: "Precondition Required";
177
+ readonly 429: "Too Many Requests";
178
+ readonly 431: "Request Header Fields Too Large";
179
+ readonly 451: "Unavailable For Legal Reasons";
180
+ readonly 500: "Internal Server Error";
181
+ readonly 501: "Not Implemented";
182
+ readonly 502: "Bad Gateway";
183
+ readonly 503: "Service Unavailable";
184
+ readonly 504: "Gateway Timeout";
185
+ readonly 505: "HTTP Version Not Supported";
186
+ readonly 506: "Variant Also Negotiates";
187
+ readonly 507: "Insufficient Storage";
188
+ readonly 508: "Loop Detected";
189
+ readonly 510: "Not Extended";
190
+ readonly 511: "Network Authentication Required";
191
+ }[Code] : Code>(code: Code, response?: T) => import("elysia").ElysiaCustomStatusResponse<Code, T, Code extends "Continue" | "Switching Protocols" | "Processing" | "Early Hints" | "OK" | "Created" | "Accepted" | "Non-Authoritative Information" | "No Content" | "Reset Content" | "Partial Content" | "Multi-Status" | "Already Reported" | "Multiple Choices" | "Moved Permanently" | "Found" | "See Other" | "Not Modified" | "Temporary Redirect" | "Permanent Redirect" | "Bad Request" | "Unauthorized" | "Payment Required" | "Forbidden" | "Not Found" | "Method Not Allowed" | "Not Acceptable" | "Proxy Authentication Required" | "Request Timeout" | "Conflict" | "Gone" | "Length Required" | "Precondition Failed" | "Payload Too Large" | "URI Too Long" | "Unsupported Media Type" | "Range Not Satisfiable" | "Expectation Failed" | "I'm a teapot" | "Enhance Your Calm" | "Misdirected Request" | "Unprocessable Content" | "Locked" | "Failed Dependency" | "Too Early" | "Upgrade Required" | "Precondition Required" | "Too Many Requests" | "Request Header Fields Too Large" | "Unavailable For Legal Reasons" | "Internal Server Error" | "Not Implemented" | "Bad Gateway" | "Service Unavailable" | "Gateway Timeout" | "HTTP Version Not Supported" | "Variant Also Negotiates" | "Insufficient Storage" | "Loop Detected" | "Not Extended" | "Network Authentication Required" ? {
192
+ readonly Continue: 100;
193
+ readonly "Switching Protocols": 101;
194
+ readonly Processing: 102;
195
+ readonly "Early Hints": 103;
196
+ readonly OK: 200;
197
+ readonly Created: 201;
198
+ readonly Accepted: 202;
199
+ readonly "Non-Authoritative Information": 203;
200
+ readonly "No Content": 204;
201
+ readonly "Reset Content": 205;
202
+ readonly "Partial Content": 206;
203
+ readonly "Multi-Status": 207;
204
+ readonly "Already Reported": 208;
205
+ readonly "Multiple Choices": 300;
206
+ readonly "Moved Permanently": 301;
207
+ readonly Found: 302;
208
+ readonly "See Other": 303;
209
+ readonly "Not Modified": 304;
210
+ readonly "Temporary Redirect": 307;
211
+ readonly "Permanent Redirect": 308;
212
+ readonly "Bad Request": 400;
213
+ readonly Unauthorized: 401;
214
+ readonly "Payment Required": 402;
215
+ readonly Forbidden: 403;
216
+ readonly "Not Found": 404;
217
+ readonly "Method Not Allowed": 405;
218
+ readonly "Not Acceptable": 406;
219
+ readonly "Proxy Authentication Required": 407;
220
+ readonly "Request Timeout": 408;
221
+ readonly Conflict: 409;
222
+ readonly Gone: 410;
223
+ readonly "Length Required": 411;
224
+ readonly "Precondition Failed": 412;
225
+ readonly "Payload Too Large": 413;
226
+ readonly "URI Too Long": 414;
227
+ readonly "Unsupported Media Type": 415;
228
+ readonly "Range Not Satisfiable": 416;
229
+ readonly "Expectation Failed": 417;
230
+ readonly "I'm a teapot": 418;
231
+ readonly "Enhance Your Calm": 420;
232
+ readonly "Misdirected Request": 421;
233
+ readonly "Unprocessable Content": 422;
234
+ readonly Locked: 423;
235
+ readonly "Failed Dependency": 424;
236
+ readonly "Too Early": 425;
237
+ readonly "Upgrade Required": 426;
238
+ readonly "Precondition Required": 428;
239
+ readonly "Too Many Requests": 429;
240
+ readonly "Request Header Fields Too Large": 431;
241
+ readonly "Unavailable For Legal Reasons": 451;
242
+ readonly "Internal Server Error": 500;
243
+ readonly "Not Implemented": 501;
244
+ readonly "Bad Gateway": 502;
245
+ readonly "Service Unavailable": 503;
246
+ readonly "Gateway Timeout": 504;
247
+ readonly "HTTP Version Not Supported": 505;
248
+ readonly "Variant Also Negotiates": 506;
249
+ readonly "Insufficient Storage": 507;
250
+ readonly "Loop Detected": 508;
251
+ readonly "Not Extended": 510;
252
+ readonly "Network Authentication Required": 511;
253
+ }[Code] : Code>;
254
+ }) => void;
255
+ readonly beforeHandle: ({ set, request, server }: {
256
+ body: unknown;
257
+ query: Record<string, string>;
258
+ params: {};
259
+ headers: Record<string, string | undefined>;
260
+ cookie: Record<string, import("elysia").Cookie<unknown>>;
261
+ server: import("elysia/universal/server").Server | null;
262
+ redirect: import("elysia").redirect;
263
+ set: {
264
+ headers: HTTPHeaders;
265
+ status?: number | keyof StatusMap;
266
+ redirect?: string;
267
+ cookie?: Record<string, import("elysia/cookies").ElysiaCookie>;
268
+ };
269
+ path: string;
270
+ route: string;
271
+ request: Request;
272
+ store: {};
273
+ status: <const Code extends number | keyof StatusMap, const T = Code extends 100 | 101 | 102 | 103 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 421 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 451 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511 | 300 | 301 | 302 | 303 | 304 | 307 | 308 | 420 ? {
274
+ readonly 100: "Continue";
275
+ readonly 101: "Switching Protocols";
276
+ readonly 102: "Processing";
277
+ readonly 103: "Early Hints";
278
+ readonly 200: "OK";
279
+ readonly 201: "Created";
280
+ readonly 202: "Accepted";
281
+ readonly 203: "Non-Authoritative Information";
282
+ readonly 204: "No Content";
283
+ readonly 205: "Reset Content";
284
+ readonly 206: "Partial Content";
285
+ readonly 207: "Multi-Status";
286
+ readonly 208: "Already Reported";
287
+ readonly 300: "Multiple Choices";
288
+ readonly 301: "Moved Permanently";
289
+ readonly 302: "Found";
290
+ readonly 303: "See Other";
291
+ readonly 304: "Not Modified";
292
+ readonly 307: "Temporary Redirect";
293
+ readonly 308: "Permanent Redirect";
294
+ readonly 400: "Bad Request";
295
+ readonly 401: "Unauthorized";
296
+ readonly 402: "Payment Required";
297
+ readonly 403: "Forbidden";
298
+ readonly 404: "Not Found";
299
+ readonly 405: "Method Not Allowed";
300
+ readonly 406: "Not Acceptable";
301
+ readonly 407: "Proxy Authentication Required";
302
+ readonly 408: "Request Timeout";
303
+ readonly 409: "Conflict";
304
+ readonly 410: "Gone";
305
+ readonly 411: "Length Required";
306
+ readonly 412: "Precondition Failed";
307
+ readonly 413: "Payload Too Large";
308
+ readonly 414: "URI Too Long";
309
+ readonly 415: "Unsupported Media Type";
310
+ readonly 416: "Range Not Satisfiable";
311
+ readonly 417: "Expectation Failed";
312
+ readonly 418: "I'm a teapot";
313
+ readonly 420: "Enhance Your Calm";
314
+ readonly 421: "Misdirected Request";
315
+ readonly 422: "Unprocessable Content";
316
+ readonly 423: "Locked";
317
+ readonly 424: "Failed Dependency";
318
+ readonly 425: "Too Early";
319
+ readonly 426: "Upgrade Required";
320
+ readonly 428: "Precondition Required";
321
+ readonly 429: "Too Many Requests";
322
+ readonly 431: "Request Header Fields Too Large";
323
+ readonly 451: "Unavailable For Legal Reasons";
324
+ readonly 500: "Internal Server Error";
325
+ readonly 501: "Not Implemented";
326
+ readonly 502: "Bad Gateway";
327
+ readonly 503: "Service Unavailable";
328
+ readonly 504: "Gateway Timeout";
329
+ readonly 505: "HTTP Version Not Supported";
330
+ readonly 506: "Variant Also Negotiates";
331
+ readonly 507: "Insufficient Storage";
332
+ readonly 508: "Loop Detected";
333
+ readonly 510: "Not Extended";
334
+ readonly 511: "Network Authentication Required";
335
+ }[Code] : Code>(code: Code, response?: T) => import("elysia").ElysiaCustomStatusResponse<Code, T, Code extends "Continue" | "Switching Protocols" | "Processing" | "Early Hints" | "OK" | "Created" | "Accepted" | "Non-Authoritative Information" | "No Content" | "Reset Content" | "Partial Content" | "Multi-Status" | "Already Reported" | "Multiple Choices" | "Moved Permanently" | "Found" | "See Other" | "Not Modified" | "Temporary Redirect" | "Permanent Redirect" | "Bad Request" | "Unauthorized" | "Payment Required" | "Forbidden" | "Not Found" | "Method Not Allowed" | "Not Acceptable" | "Proxy Authentication Required" | "Request Timeout" | "Conflict" | "Gone" | "Length Required" | "Precondition Failed" | "Payload Too Large" | "URI Too Long" | "Unsupported Media Type" | "Range Not Satisfiable" | "Expectation Failed" | "I'm a teapot" | "Enhance Your Calm" | "Misdirected Request" | "Unprocessable Content" | "Locked" | "Failed Dependency" | "Too Early" | "Upgrade Required" | "Precondition Required" | "Too Many Requests" | "Request Header Fields Too Large" | "Unavailable For Legal Reasons" | "Internal Server Error" | "Not Implemented" | "Bad Gateway" | "Service Unavailable" | "Gateway Timeout" | "HTTP Version Not Supported" | "Variant Also Negotiates" | "Insufficient Storage" | "Loop Detected" | "Not Extended" | "Network Authentication Required" ? {
336
+ readonly Continue: 100;
337
+ readonly "Switching Protocols": 101;
338
+ readonly Processing: 102;
339
+ readonly "Early Hints": 103;
340
+ readonly OK: 200;
341
+ readonly Created: 201;
342
+ readonly Accepted: 202;
343
+ readonly "Non-Authoritative Information": 203;
344
+ readonly "No Content": 204;
345
+ readonly "Reset Content": 205;
346
+ readonly "Partial Content": 206;
347
+ readonly "Multi-Status": 207;
348
+ readonly "Already Reported": 208;
349
+ readonly "Multiple Choices": 300;
350
+ readonly "Moved Permanently": 301;
351
+ readonly Found: 302;
352
+ readonly "See Other": 303;
353
+ readonly "Not Modified": 304;
354
+ readonly "Temporary Redirect": 307;
355
+ readonly "Permanent Redirect": 308;
356
+ readonly "Bad Request": 400;
357
+ readonly Unauthorized: 401;
358
+ readonly "Payment Required": 402;
359
+ readonly Forbidden: 403;
360
+ readonly "Not Found": 404;
361
+ readonly "Method Not Allowed": 405;
362
+ readonly "Not Acceptable": 406;
363
+ readonly "Proxy Authentication Required": 407;
364
+ readonly "Request Timeout": 408;
365
+ readonly Conflict: 409;
366
+ readonly Gone: 410;
367
+ readonly "Length Required": 411;
368
+ readonly "Precondition Failed": 412;
369
+ readonly "Payload Too Large": 413;
370
+ readonly "URI Too Long": 414;
371
+ readonly "Unsupported Media Type": 415;
372
+ readonly "Range Not Satisfiable": 416;
373
+ readonly "Expectation Failed": 417;
374
+ readonly "I'm a teapot": 418;
375
+ readonly "Enhance Your Calm": 420;
376
+ readonly "Misdirected Request": 421;
377
+ readonly "Unprocessable Content": 422;
378
+ readonly Locked: 423;
379
+ readonly "Failed Dependency": 424;
380
+ readonly "Too Early": 425;
381
+ readonly "Upgrade Required": 426;
382
+ readonly "Precondition Required": 428;
383
+ readonly "Too Many Requests": 429;
384
+ readonly "Request Header Fields Too Large": 431;
385
+ readonly "Unavailable For Legal Reasons": 451;
386
+ readonly "Internal Server Error": 500;
387
+ readonly "Not Implemented": 501;
388
+ readonly "Bad Gateway": 502;
389
+ readonly "Service Unavailable": 503;
390
+ readonly "Gateway Timeout": 504;
391
+ readonly "HTTP Version Not Supported": 505;
392
+ readonly "Variant Also Negotiates": 506;
393
+ readonly "Insufficient Storage": 507;
394
+ readonly "Loop Detected": 508;
395
+ readonly "Not Extended": 510;
396
+ readonly "Network Authentication Required": 511;
397
+ }[Code] : Code>;
398
+ }) => Promise<void>;
399
+ };
400
+ };
72
401
  parser: {};
73
402
  response: {};
74
403
  }, {}, {
@@ -1,13 +1,4 @@
1
- import type { KvStore } from '../../../../modules/kv-store/types/kv-store';
2
1
  export interface RateLimitOptions {
3
- /**
4
- * Storage backend for rate limit data.
5
- *
6
- * - If not specified, defaults to in-memory storage
7
- * - Use ':memory:' to explicitly specify in-memory storage
8
- * - Provide a KvStore instance for persistent distributed storage
9
- */
10
- readonly store?: ':memory:' | KvStore;
11
2
  /**
12
3
  * Maximum number of requests allowed in the time window.
13
4
  *
@@ -1,9 +1,103 @@
1
1
  // @bun
2
2
  import {
3
- MemoryStore
4
- } from "../../../chunk-e30paw8a.js";
5
- import"../../../chunk-xhhj1gvj.js";
6
- import"../../../chunk-9cgzhc50.js";
3
+ KV_STORE_ERROR_KEYS
4
+ } from "../../../chunk-xhhj1gvj.js";
5
+ import {
6
+ BaseError
7
+ } from "../../../chunk-9cgzhc50.js";
8
+
9
+ // source/modules/kv-store/memory/memory-store.ts
10
+ class MemoryStore {
11
+ _store = new Map;
12
+ _cleanupInterval;
13
+ _cleanupTimer = null;
14
+ constructor(cleanupIntervalMs) {
15
+ this._cleanupInterval = cleanupIntervalMs ?? 300000;
16
+ this._startCleanup();
17
+ }
18
+ get(key) {
19
+ const entry = this._store.get(key);
20
+ if (!entry)
21
+ return null;
22
+ const now = Date.now();
23
+ if (now > entry.expiresAt && entry.expiresAt !== -1) {
24
+ this._store.delete(key);
25
+ return null;
26
+ }
27
+ return entry.value;
28
+ }
29
+ set(key, value, ttlSec) {
30
+ const expiresAt = ttlSec ? Date.now() + ttlSec * 1000 : -1;
31
+ this._store.set(key, { value, expiresAt });
32
+ }
33
+ increment(key, amount = 1) {
34
+ const current = this.get(key);
35
+ const entry = this._store.get(key);
36
+ if (current !== null && typeof current !== "number")
37
+ throw new BaseError(KV_STORE_ERROR_KEYS.NOT_INTEGER);
38
+ const currentValue = current ?? 0;
39
+ const newValue = currentValue + amount;
40
+ const expiresAt = entry ? entry.expiresAt : -1;
41
+ this._store.set(key, { value: newValue, expiresAt });
42
+ return newValue;
43
+ }
44
+ decrement(key, amount = 1) {
45
+ const current = this.get(key);
46
+ const entry = this._store.get(key);
47
+ if (current !== null && typeof current !== "number")
48
+ throw new BaseError(KV_STORE_ERROR_KEYS.NOT_INTEGER);
49
+ const currentValue = current ?? 0;
50
+ const newValue = currentValue - amount;
51
+ const expiresAt = entry ? entry.expiresAt : -1;
52
+ this._store.set(key, { value: newValue, expiresAt });
53
+ return newValue;
54
+ }
55
+ del(key) {
56
+ return this._store.delete(key);
57
+ }
58
+ expire(key, ttlSec) {
59
+ const entry = this._store.get(key);
60
+ if (!entry)
61
+ return false;
62
+ entry.expiresAt = Date.now() + ttlSec * 1000;
63
+ return true;
64
+ }
65
+ ttl(key) {
66
+ const entry = this._store.get(key);
67
+ if (!entry)
68
+ return -1;
69
+ if (entry.expiresAt === -1)
70
+ return -1;
71
+ const remaining = entry.expiresAt - Date.now();
72
+ return remaining > 0 ? Math.ceil(remaining / 1000) : -1;
73
+ }
74
+ clean() {
75
+ const sizeBefore = this._store.size;
76
+ this._store.clear();
77
+ return sizeBefore;
78
+ }
79
+ _startCleanup() {
80
+ if (this._cleanupTimer)
81
+ return;
82
+ this._cleanupTimer = setInterval(() => {
83
+ this._removeExpiredEntries();
84
+ }, this._cleanupInterval);
85
+ }
86
+ _removeExpiredEntries() {
87
+ const now = Date.now();
88
+ for (const [key, entry] of this._store.entries())
89
+ if (entry.expiresAt !== -1 && now > entry.expiresAt)
90
+ this._store.delete(key);
91
+ }
92
+ destroy() {
93
+ if (this._cleanupTimer) {
94
+ clearInterval(this._cleanupTimer);
95
+ this._cleanupTimer = null;
96
+ }
97
+ this._store.clear();
98
+ }
99
+ }
7
100
  export {
8
101
  MemoryStore
9
102
  };
103
+ export { MemoryStore };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrxsys/mrx-core",
3
- "version": "2.11.0-1-and-273-20251029",
3
+ "version": "2.11.0-1-and-275-20251029",
4
4
  "author": "Ruby",
5
5
  "devDependencies": {
6
6
  "@eslint/js": "^9.38.0",
@@ -8,14 +8,14 @@
8
8
  "@stylistic/eslint-plugin": "^5.5.0",
9
9
  "@types/bun": "^1.3.0",
10
10
  "@types/nodemailer": "^7.0.2",
11
- "elysia": "1.4.12",
11
+ "elysia": "1.4.13",
12
12
  "eslint": "^9.38.0",
13
13
  "globals": "^16.4.0",
14
14
  "ioredis": "^5.8.2",
15
15
  "jose": "^6.1.0",
16
16
  "knex": "^3.1.0",
17
17
  "mssql": "^12.0.0",
18
- "nodemailer": "^7.0.9",
18
+ "nodemailer": "^7.0.10",
19
19
  "typescript-eslint": "^8.46.2"
20
20
  },
21
21
  "peerDependencies": {
@@ -42,7 +42,6 @@
42
42
  "./modules/database/types": "./dist/modules/database/types/index.js",
43
43
  "./modules/elysia/cache": "./dist/modules/elysia/cache/index.js",
44
44
  "./modules/elysia/cache/types": "./dist/modules/elysia/cache/types/index.js",
45
- "./modules/elysia/cache/utils": "./dist/modules/elysia/cache/utils/index.js",
46
45
  "./modules/elysia/crud": "./dist/modules/elysia/crud/index.js",
47
46
  "./modules/elysia/crud/enums": "./dist/modules/elysia/crud/enums/index.js",
48
47
  "./modules/elysia/crud/operations": "./dist/modules/elysia/crud/operations/index.js",
@@ -180,9 +179,9 @@
180
179
  "fix-lint": "eslint --fix ./source",
181
180
  "lint": "eslint ./source",
182
181
  "start": "bun build/index.js",
183
- "test:integration": "bun test --timeout 6000 $(find test/integration -name '*.spec.ts')",
184
- "test:unit": "bun test --timeout 6000 --coverage $(find test/unit -name '*.spec.ts')",
185
- "test": "bun test --coverage --timeout 6000"
182
+ "test:integration": "bun test --timeout 10000 $(find test/integration -name '*.spec.ts')",
183
+ "test:unit": "bun test --timeout 10000 --coverage $(find test/unit -name '*.spec.ts')",
184
+ "test": "bun test --coverage --timeout 10000"
186
185
  },
187
186
  "type": "module"
188
187
  }
@@ -1,29 +0,0 @@
1
- // @bun
2
- // source/modules/elysia/cache/utils/generate-cache-key.ts
3
- var _calculateBodyHash = async (body, hasher) => {
4
- if (!body)
5
- return;
6
- const reader = body.getReader();
7
- try {
8
- while (true) {
9
- const { done, value } = await reader.read();
10
- if (done)
11
- break;
12
- if (value)
13
- hasher.update(new Uint8Array(value));
14
- }
15
- } finally {
16
- reader.releaseLock();
17
- }
18
- };
19
- var generateCacheKey = async (request) => {
20
- const { method, url, headers } = request;
21
- const hasher = new Bun.CryptoHasher("sha256");
22
- hasher.update(method);
23
- hasher.update(url);
24
- hasher.update(JSON.stringify(headers));
25
- await _calculateBodyHash(request.body, hasher);
26
- return hasher.digest("hex");
27
- };
28
-
29
- export { generateCacheKey };
@@ -1,101 +0,0 @@
1
- // @bun
2
- import {
3
- KV_STORE_ERROR_KEYS
4
- } from "./chunk-xhhj1gvj.js";
5
- import {
6
- BaseError
7
- } from "./chunk-9cgzhc50.js";
8
-
9
- // source/modules/kv-store/memory/memory-store.ts
10
- class MemoryStore {
11
- _store = new Map;
12
- _cleanupInterval;
13
- _cleanupTimer = null;
14
- constructor(cleanupIntervalMs) {
15
- this._cleanupInterval = cleanupIntervalMs ?? 300000;
16
- this._startCleanup();
17
- }
18
- get(key) {
19
- const entry = this._store.get(key);
20
- if (!entry)
21
- return null;
22
- const now = Date.now();
23
- if (now > entry.expiresAt && entry.expiresAt !== -1) {
24
- this._store.delete(key);
25
- return null;
26
- }
27
- return entry.value;
28
- }
29
- set(key, value, ttlSec) {
30
- const expiresAt = ttlSec ? Date.now() + ttlSec * 1000 : -1;
31
- this._store.set(key, { value, expiresAt });
32
- }
33
- increment(key, amount = 1) {
34
- const current = this.get(key);
35
- const entry = this._store.get(key);
36
- if (current !== null && typeof current !== "number")
37
- throw new BaseError(KV_STORE_ERROR_KEYS.NOT_INTEGER);
38
- const currentValue = current ?? 0;
39
- const newValue = currentValue + amount;
40
- const expiresAt = entry ? entry.expiresAt : -1;
41
- this._store.set(key, { value: newValue, expiresAt });
42
- return newValue;
43
- }
44
- decrement(key, amount = 1) {
45
- const current = this.get(key);
46
- const entry = this._store.get(key);
47
- if (current !== null && typeof current !== "number")
48
- throw new BaseError(KV_STORE_ERROR_KEYS.NOT_INTEGER);
49
- const currentValue = current ?? 0;
50
- const newValue = currentValue - amount;
51
- const expiresAt = entry ? entry.expiresAt : -1;
52
- this._store.set(key, { value: newValue, expiresAt });
53
- return newValue;
54
- }
55
- del(key) {
56
- return this._store.delete(key);
57
- }
58
- expire(key, ttlSec) {
59
- const entry = this._store.get(key);
60
- if (!entry)
61
- return false;
62
- entry.expiresAt = Date.now() + ttlSec * 1000;
63
- return true;
64
- }
65
- ttl(key) {
66
- const entry = this._store.get(key);
67
- if (!entry)
68
- return -1;
69
- if (entry.expiresAt === -1)
70
- return -1;
71
- const remaining = entry.expiresAt - Date.now();
72
- return remaining > 0 ? Math.ceil(remaining / 1000) : -1;
73
- }
74
- clean() {
75
- const sizeBefore = this._store.size;
76
- this._store.clear();
77
- return sizeBefore;
78
- }
79
- _startCleanup() {
80
- if (this._cleanupTimer)
81
- return;
82
- this._cleanupTimer = setInterval(() => {
83
- this._removeExpiredEntries();
84
- }, this._cleanupInterval);
85
- }
86
- _removeExpiredEntries() {
87
- const now = Date.now();
88
- for (const [key, entry] of this._store.entries())
89
- if (entry.expiresAt !== -1 && now > entry.expiresAt)
90
- this._store.delete(key);
91
- }
92
- destroy() {
93
- if (this._cleanupTimer) {
94
- clearInterval(this._cleanupTimer);
95
- this._cleanupTimer = null;
96
- }
97
- this._store.clear();
98
- }
99
- }
100
-
101
- export { MemoryStore };
@@ -1 +0,0 @@
1
- export { generateCacheKey } from './generate-cache-key';
@@ -1,7 +0,0 @@
1
- // @bun
2
- import {
3
- generateCacheKey
4
- } from "../../../../chunk-b23dvm2d.js";
5
- export {
6
- generateCacheKey
7
- };