@nowarajs/elysia-cache 1.2.9 โ†’ 1.3.1

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/AGENTS.md ADDED
@@ -0,0 +1,44 @@
1
+ ---
2
+ name: Development_Workflow_and_Code_Standards
3
+ description: Guidelines for development workflow and code standards
4
+ applyTo: '**'
5
+ ---
6
+
7
+ ## TypeScript and Project Conventions
8
+ 1. Use underscore prefix for private or non-exported elements (e.g., _privateMethod)
9
+ 2. Always specify visibility modifiers (private, protected, public), plus readonly, override, etc. when applicable
10
+ 3. Naming conventions: camelCase for variables/functions/methods; PascalCase for classes/interfaces/types; SCREAMING_CASE for constants
11
+ 4. Explicit typing: Always specify types for variables, parameters, and return values; never use any; prefer unknown if type cannot be determined; prefer interface over type alias for extendable objects
12
+ 5. Documentation: Use TSDoc style; explain purpose, parameters, return values, and behavior; only document code when requested; for @throws, use format "@throws ({@link Type}) โ€“ description"; for object/interface properties, write comment above each property instead of @param
13
+ 6. Control structures: Omit curly braces for single-statement bodies
14
+ 7. Path Aliases: Use $ for internal imports; do not import barrel files except as entry points
15
+ 8. Export Pattern: Each directory has index.ts re-exporting public items; types exported in types/index.ts
16
+ 9. Function style: Class methods use standard method syntax; helpers/callbacks/HOFs prefer arrow functions unless function syntax is required
17
+
18
+ ## Contribution Principles
19
+ 1. Follow TypeScript best practices and idiomatic patterns
20
+ 2. Maintain existing code structure and modular organization
21
+ 3. Keep developer experience in mind
22
+ 4. Keep pull requests focused and well-documented (with TsDoc if asked)
23
+ 5. Commit different types of changes separately; avoid mixing tests and features in one commit
24
+
25
+ ## Commit Message Convention (Conventional Commits + Emoji)
26
+ Format: <type>(<emoji>): [summary up to 72 chars]
27
+ (blank line, then context or description)
28
+
29
+ <type> is lowercase
30
+ summary is surrounded by brackets `[summary]`
31
+
32
+ Types:
33
+ feat(๐Ÿš€) โ€“ New features
34
+ fix(๐Ÿ”ง) โ€“ Bug fixes
35
+ perf(โšก) โ€“ Performance improvements
36
+ refactor(๐Ÿงน) โ€“ Refactoring
37
+ build(๐Ÿ“ฆ) โ€“ Build tools / dependency changes
38
+ types(๐ŸŒŠ) โ€“ Type definitions
39
+ chore(๐Ÿฆ‰) โ€“ Maintenance, non-code/test changes
40
+ examples(๐Ÿ€) โ€“ Example updates
41
+ docs(๐Ÿ“–) โ€“ Documentation changes
42
+ test(๐Ÿงช) โ€“ Test code updates
43
+ style(๐ŸŽจ) โ€“ Style/formatting only
44
+ ci(๐Ÿค–) โ€“ CI/CD configuration
package/README.md CHANGED
@@ -8,6 +8,11 @@
8
8
  - [โœจ Features](#-features)
9
9
  - [๐Ÿ”ง Installation](#-installation)
10
10
  - [โš™๏ธ Usage](#-usage)
11
+ - [Basic Setup (In-Memory Store)](#basic-setup-in-memory-store)
12
+ - [Cache Configuration](#cache-configuration)
13
+ - [Redis Store Setup (Production)](#redis-store-setup-production)
14
+ - [Route-specific Caching](#route-specific-caching)
15
+ - [Storage Options](#storage-options)
11
16
  - [๐Ÿ“Š Cache Headers](#-cache-headers)
12
17
  - [๐Ÿ“š API Reference](#-api-reference)
13
18
  - [โš–๏ธ License](#-license)
@@ -56,7 +61,9 @@ const app = new Elysia()
56
61
  // This response will be cached automatically
57
62
  return await fetchUsers()
58
63
  }, {
59
- isCached: true // Enable caching for this route
64
+ isCached: {
65
+ ttl: 300 // Cache for 5 minutes
66
+ }
60
67
  })
61
68
  .listen(3000)
62
69
  ```
@@ -66,18 +73,15 @@ const app = new Elysia()
66
73
  ```ts
67
74
  import { cache } from '@nowarajs/elysia-cache'
68
75
 
76
+ // Using in-memory store (default)
69
77
  const app = new Elysia()
70
- .use(cache({
71
- defaultTtl: 300, // Cache for 5 minutes by default
72
- prefix: 'api:', // Add prefix to cache keys
73
- storage: ':memory:' // Use in-memory storage (default)
74
- }))
78
+ .use(cache())
75
79
  ```
76
80
 
77
81
  ### Redis Store Setup (Production)
78
82
 
79
83
  ```ts
80
- import { IoRedisStore } from '@nowarajs/kv-store'
84
+ import { IoRedisStore } from '@nowarajs/kv-store/ioredis' // or you can use BunRedis with /bun-redis
81
85
  import { cache } from '@nowarajs/elysia-cache'
82
86
 
83
87
  // Create Redis store instance
@@ -89,36 +93,25 @@ await redisStore.connect()
89
93
 
90
94
  // Create application with Redis-backed caching
91
95
  const app = new Elysia()
92
- .use(cache({
93
- storage: redisStore,
94
- defaultTtl: 600
95
- }))
96
+ .use(cache(redisStore))
96
97
  ```
97
98
 
98
99
  ### Route-specific Caching
99
100
 
100
101
  ```ts
101
102
  const app = new Elysia()
102
- .use(cache({ defaultTtl: 60 }))
103
+ .use(cache())
103
104
  .get('/fast', () => getData(), {
104
- isCached: 30 // Cache for 30 seconds
105
+ isCached: { ttl: 30 } // Cache for 30 seconds
105
106
  })
106
107
  .get('/slow', () => getSlowData(), {
107
- isCached: 3600 // Cache for 1 hour
108
+ isCached: { ttl: 3600 } // Cache for 1 hour
108
109
  })
109
- .get('/no-cache', () => getRealTimeData(), {
110
- isCached: false // Disable caching
110
+ .get('/prefixed', () => getData(), {
111
+ isCached: { ttl: 60, prefix: 'api:' } // With custom prefix
111
112
  })
112
113
  ```
113
114
 
114
- ### Storage Options
115
-
116
- | Store Type | Usage | Best For |
117
- |------------|-------|----------|
118
- | Default (`:memory:`) | `cache({ defaultTtl: 60 })` | Development, single instance |
119
- | Explicit Memory | `cache({ storage: new MemoryStore(), ... })` | When you need store reference |
120
- | Redis Store | `cache({ storage: redisStore, ... })` | Production, distributed systems |
121
-
122
115
  ## ๐Ÿ“Š Cache Headers
123
116
 
124
117
  The plugin automatically adds these headers to all responses:
@@ -143,36 +136,6 @@ X-Cache: HIT
143
136
 
144
137
  ## ๐Ÿ“š API Reference
145
138
 
146
- ### Cache Options
147
-
148
- ```ts
149
- interface CacheOptions {
150
- /** Default TTL in seconds (default: 60) */
151
- defaultTtl?: number
152
-
153
- /** Cache key prefix (default: '') */
154
- prefix?: string
155
-
156
- /** Storage backend (default: ':memory:') */
157
- storage?: ':memory:' | KvStore
158
- }
159
- ```
160
-
161
- ### Macro: `isCached`
162
-
163
- The `isCached` macro enables caching for specific routes:
164
-
165
- ```ts
166
- // Enable with default TTL
167
- { isCached: true }
168
-
169
- // Enable with custom TTL (in seconds)
170
- { isCached: 300 }
171
-
172
- // Disable caching
173
- { isCached: false }
174
- ```
175
-
176
139
  You can find the complete API reference documentation for `Elysia Cache` at:
177
140
 
178
141
  - [TypeDoc Documentation](https://nowarajs.github.io/elysia-cache)
@@ -183,6 +146,6 @@ Distributed under the MIT License. See [LICENSE](./LICENSE) for more information
183
146
 
184
147
  ## ๐Ÿ“ง Contact
185
148
 
149
+ - Mail: [nowarajs@pm.me](mailto:nowarajs@pm.me)
186
150
  - GitHub: [NowaraJS](https://github.com/NowaraJS)
187
- - Package: [@nowarajs/elysia-cache](https://www.npmjs.com/package/@nowarajs/elysia-cache)
188
151
 
package/dist/cache.d.ts CHANGED
@@ -1,12 +1,9 @@
1
+ import type { KvStore } from '@nowarajs/kv-store/types';
1
2
  import { Elysia } from 'elysia';
2
- import { MemoryStore } from '@nowarajs/kv-store/memory';
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("@nowarajs/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("@nowarajs/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 | 300 | 301 | 302 | 303 | 304 | 307 | 308 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 451 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511 ? {
45
39
  readonly 100: "Continue";
46
40
  readonly 101: "Switching Protocols";
package/dist/index.js CHANGED
@@ -1,66 +1,85 @@
1
1
  // @bun
2
- import {
3
- generateCacheKey
4
- } from "./chunk-kqx0zdcn.js";
5
-
6
2
  // source/cache.ts
7
- import { Elysia } from "elysia";
8
3
  import { MemoryStore } from "@nowarajs/kv-store/memory";
9
- var cache = ({
10
- defaultTtl = 60,
11
- prefix = "",
12
- store = ":memory:"
13
- } = {}) => new Elysia().state({
14
- kvStore: store === ":memory:" ? new MemoryStore : store
15
- }).state({
16
- _cachedRoutes: new Set
17
- }).onRequest(async ({ request, store: store2, set }) => {
18
- const sanitizeUrl = new URL(request.url).pathname;
19
- if (store2._cachedRoutes.has(`${request.method}:${sanitizeUrl}`)) {
20
- const cacheKey = await generateCacheKey(request.clone());
21
- const cachedData = await store2.kvStore.get(`${prefix}${cacheKey}`);
22
- if (cachedData && typeof cachedData === "object" && "response" in cachedData && "metadata" in cachedData) {
23
- const { response, metadata } = cachedData;
24
- set.headers["cache-control"] = `max-age=${metadata.ttl}, public`;
25
- set.headers["x-cache"] = "HIT";
26
- set.headers["etag"] = `"${prefix}${cacheKey}"`;
27
- set.headers["expires"] = new Date(Date.now() + metadata.ttl * 1000).toUTCString();
28
- set.headers["last-modified"] = metadata.createdAt;
29
- if (response instanceof Response)
30
- return response.clone();
31
- return response;
4
+ import { Elysia } from "elysia";
5
+
6
+ // source/utils/generate-cache-key.ts
7
+ var _calculateBodyHash = async (body, hasher) => {
8
+ if (!body)
9
+ return;
10
+ const reader = body.getReader();
11
+ try {
12
+ while (true) {
13
+ const { done, value } = await reader.read();
14
+ if (done)
15
+ break;
16
+ if (value)
17
+ hasher.update(new Uint8Array(value));
32
18
  }
33
- set.headers["x-cache"] = "MISS";
19
+ } finally {
20
+ reader.releaseLock();
34
21
  }
35
- return;
36
- }).macro({
37
- isCached: (enable) => {
38
- const ttl = typeof enable === "number" ? enable : enable ? defaultTtl : 0;
39
- return {
40
- async afterHandle({ set, responseValue, store: store2, request }) {
41
- const sanitizeUrl = new URL(request.url).pathname;
42
- if (!store2._cachedRoutes.has(`${request.method}:${sanitizeUrl}`))
43
- store2._cachedRoutes.add(`${request.method}:${sanitizeUrl}`);
22
+ };
23
+ var generateCacheKey = async (request) => {
24
+ const { method, url, headers } = request;
25
+ const hasher = new Bun.CryptoHasher("sha256");
26
+ hasher.update(method);
27
+ hasher.update(url);
28
+ hasher.update(JSON.stringify(headers));
29
+ await _calculateBodyHash(request.body, hasher);
30
+ return hasher.digest("hex");
31
+ };
32
+
33
+ // source/cache.ts
34
+ var cache = (store = new MemoryStore) => {
35
+ const cachedRoutes = new Map;
36
+ return new Elysia().onRequest(async ({ request, set }) => {
37
+ const route = `${request.method}:${new URL(request.url).pathname}`;
38
+ if (cachedRoutes.has(route)) {
39
+ const { ttl, prefix } = cachedRoutes.get(route);
40
+ const cacheKey = await generateCacheKey(request.clone());
41
+ const cacheItem = await store.get(`${prefix}${cacheKey}`);
42
+ if (cacheItem && typeof cacheItem === "object" && "response" in cacheItem && "metadata" in cacheItem) {
43
+ const createdAt = new Date(cacheItem.metadata.createdAt);
44
+ const expiresAt = new Date(createdAt.getTime() + ttl * 1000);
45
+ const now = Date.now();
46
+ const remaining = Math.max(0, Math.ceil((expiresAt.getTime() - now) / 1000));
47
+ set.headers["cache-control"] = `max-age=${remaining}, public`;
48
+ set.headers["etag"] = `"${prefix}${cacheKey}"`;
49
+ set.headers["last-modified"] = cacheItem.metadata.createdAt;
50
+ set.headers["expires"] = expiresAt.toUTCString();
51
+ set.headers["x-cache"] = "HIT";
52
+ if (cacheItem.response instanceof Response)
53
+ return cacheItem.response.clone();
54
+ return cacheItem.response;
55
+ }
56
+ set.headers["x-cache"] = "MISS";
57
+ }
58
+ return;
59
+ }).macro({
60
+ isCached: ({ ttl, prefix = "" }) => ({
61
+ async afterHandle({ set, responseValue, request }) {
62
+ const route = `${request.method}:${new URL(request.url).pathname}`;
63
+ if (!cachedRoutes.has(route))
64
+ cachedRoutes.set(route, { ttl, prefix });
44
65
  const cacheKey = await generateCacheKey(request.clone());
45
66
  const now = new Date;
46
67
  set.headers["cache-control"] = `max-age=${ttl}, public`;
47
68
  set.headers["etag"] = `"${prefix}${cacheKey}"`;
48
69
  set.headers["last-modified"] = now.toUTCString();
49
70
  set.headers["expires"] = new Date(now.getTime() + ttl * 1000).toUTCString();
50
- if (!set.headers["x-cache"])
51
- set.headers["x-cache"] = "MISS";
52
- const cacheData = {
71
+ set.headers["x-cache"] = "MISS";
72
+ const cacheItem = {
53
73
  response: responseValue instanceof Response ? responseValue.clone() : responseValue,
54
74
  metadata: {
55
- createdAt: now.toUTCString(),
56
- ttl
75
+ createdAt: now.toUTCString()
57
76
  }
58
77
  };
59
- await store2.kvStore.set(`${prefix}${cacheKey}`, cacheData, ttl);
78
+ await store.set(`${prefix}${cacheKey}`, cacheItem, ttl);
60
79
  }
61
- };
62
- }
63
- });
80
+ })
81
+ });
82
+ };
64
83
  export {
65
84
  cache
66
85
  };
@@ -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 '@nowarajs/kv-store/types';
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nowarajs/elysia-cache",
3
- "version": "1.2.9",
3
+ "version": "1.3.1",
4
4
  "author": "NowaraJS",
5
5
  "description": "A template to create a bun package.",
6
6
  "type": "module",
@@ -23,24 +23,24 @@
23
23
  "test": "bun test --coverage"
24
24
  },
25
25
  "devDependencies": {
26
- "@eslint/js": "^9.36.0",
27
- "@nowarajs/error": "^1.3.2",
28
- "@nowarajs/kv-store": "^1.1.6",
29
- "@stylistic/eslint-plugin": "^5.4.0",
30
- "@types/bun": "^1.2.22",
31
- "eslint": "^9.36.0",
32
- "globals": "^16.4.0",
33
- "typescript-eslint": "^8.44.1",
34
- "typescript": "^5.9.2"
26
+ "@eslint/js": "^9.39.2",
27
+ "@nowarajs/error": "^1.3.10",
28
+ "@nowarajs/kv-store": "^1.3.0",
29
+ "@stylistic/eslint-plugin": "^5.6.1",
30
+ "@types/bun": "^1.3.5",
31
+ "elysia": "^1.4.19",
32
+ "eslint": "^9.39.2",
33
+ "globals": "^16.5.0",
34
+ "typescript": "^5.9.3",
35
+ "typescript-eslint": "^8.51.0"
35
36
  },
36
37
  "peerDependencies": {
37
- "@nowarajs/error": "^1.3.2",
38
- "@nowarajs/kv-store": "^1.1.6",
39
- "elysia": "^1.4.7"
38
+ "@nowarajs/error": "^1.3.10",
39
+ "@nowarajs/kv-store": "^1.3.0",
40
+ "elysia": "^1.4.19"
40
41
  },
41
42
  "exports": {
42
43
  "./types": "./dist/types/index.js",
43
- "./utils": "./dist/utils/index.js",
44
44
  ".": "./dist/index.js"
45
45
  },
46
46
  "changelog": {
@@ -74,19 +74,24 @@
74
74
  "semver": "patch"
75
75
  },
76
76
  "chore": {
77
- "title": "๐Ÿฆ‰ Chore"
77
+ "title": "๐Ÿฆ‰ Chore",
78
+ "semver": "patch"
78
79
  },
79
80
  "examples": {
80
- "title": "๐Ÿ€ Examples"
81
+ "title": "๐Ÿ€ Examples",
82
+ "semver": "patch"
81
83
  },
82
84
  "test": {
83
- "title": "๐Ÿงช Tests"
85
+ "title": "๐Ÿงช Tests",
86
+ "semver": "patch"
84
87
  },
85
88
  "style": {
86
- "title": "๐ŸŽจ Styles"
89
+ "title": "๐ŸŽจ Styles",
90
+ "semver": "patch"
87
91
  },
88
92
  "ci": {
89
- "title": "๐Ÿค– CI"
93
+ "title": "๐Ÿค– CI",
94
+ "semver": "patch"
90
95
  }
91
96
  },
92
97
  "templates": {
@@ -98,8 +103,5 @@
98
103
  "repository": {
99
104
  "type": "git",
100
105
  "url": "https://github.com/NowaraJS/elysia-cache.git"
101
- },
102
- "dependencies": {
103
- "ioredis": "^5.8.0"
104
106
  }
105
107
  }
@@ -1,29 +0,0 @@
1
- // @bun
2
- // source/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 +0,0 @@
1
- export { generateCacheKey } from './generate-cache-key';
@@ -1,7 +0,0 @@
1
- // @bun
2
- import {
3
- generateCacheKey
4
- } from "../chunk-kqx0zdcn.js";
5
- export {
6
- generateCacheKey
7
- };