veryfront 0.0.82 → 0.0.84
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -17
- package/esm/deno.js +1 -1
- package/esm/proxy/cache/index.d.ts +41 -0
- package/esm/proxy/cache/index.d.ts.map +1 -0
- package/esm/proxy/cache/index.js +75 -0
- package/esm/proxy/cache/memory-cache.d.ts +18 -0
- package/esm/proxy/cache/memory-cache.d.ts.map +1 -0
- package/esm/proxy/cache/memory-cache.js +100 -0
- package/esm/proxy/cache/redis-cache.d.ts +27 -0
- package/esm/proxy/cache/redis-cache.d.ts.map +1 -0
- package/esm/proxy/cache/redis-cache.js +183 -0
- package/esm/proxy/cache/resilient-cache.d.ts +44 -0
- package/esm/proxy/cache/resilient-cache.d.ts.map +1 -0
- package/esm/proxy/cache/resilient-cache.js +178 -0
- package/esm/proxy/cache/types.d.ts +65 -0
- package/esm/proxy/cache/types.d.ts.map +1 -0
- package/esm/proxy/cache/types.js +7 -0
- package/esm/proxy/handler.d.ts +81 -0
- package/esm/proxy/handler.d.ts.map +1 -0
- package/esm/proxy/handler.js +417 -0
- package/esm/proxy/logger.d.ts +29 -0
- package/esm/proxy/logger.d.ts.map +1 -0
- package/esm/proxy/logger.js +258 -0
- package/esm/proxy/oauth-client.d.ts +15 -0
- package/esm/proxy/oauth-client.d.ts.map +1 -0
- package/esm/proxy/oauth-client.js +52 -0
- package/esm/proxy/token-manager.d.ts +59 -0
- package/esm/proxy/token-manager.d.ts.map +1 -0
- package/esm/proxy/token-manager.js +125 -0
- package/esm/proxy/tracing.d.ts +39 -0
- package/esm/proxy/tracing.d.ts.map +1 -0
- package/esm/proxy/tracing.js +194 -0
- package/esm/src/cache/backend.d.ts +2 -0
- package/esm/src/cache/backend.d.ts.map +1 -1
- package/esm/src/cache/backend.js +2 -0
- package/esm/src/cache/cache-key-builder.d.ts +0 -4
- package/esm/src/cache/cache-key-builder.d.ts.map +1 -1
- package/esm/src/cache/cache-key-builder.js +0 -6
- package/esm/src/cache/multi-tier.d.ts +0 -29
- package/esm/src/cache/multi-tier.d.ts.map +1 -1
- package/esm/src/cache/multi-tier.js +0 -26
- package/esm/src/cli/app/actions.d.ts +26 -0
- package/esm/src/cli/app/actions.d.ts.map +1 -0
- package/esm/src/cli/app/actions.js +152 -0
- package/esm/src/cli/app/components/inline-input.d.ts +35 -0
- package/esm/src/cli/app/components/inline-input.d.ts.map +1 -0
- package/esm/src/cli/app/components/inline-input.js +220 -0
- package/esm/src/cli/app/components/list-select.d.ts +69 -0
- package/esm/src/cli/app/components/list-select.d.ts.map +1 -0
- package/esm/src/cli/app/components/list-select.js +137 -0
- package/esm/src/cli/app/index.d.ts +45 -0
- package/esm/src/cli/app/index.d.ts.map +1 -0
- package/esm/src/cli/app/index.js +1252 -0
- package/esm/src/cli/app/state.d.ts +122 -0
- package/esm/src/cli/app/state.d.ts.map +1 -0
- package/esm/src/cli/app/state.js +232 -0
- package/esm/src/cli/app/views/dashboard.d.ts +19 -0
- package/esm/src/cli/app/views/dashboard.d.ts.map +1 -0
- package/esm/src/cli/app/views/dashboard.js +178 -0
- package/esm/src/cli/commands/dev.js +2 -2
- package/esm/src/cli/commands/new.js +1 -1
- package/esm/src/cli/index/command-router.d.ts.map +1 -1
- package/esm/src/cli/index/command-router.js +9 -39
- package/esm/src/cli/index/start-handler.d.ts +3 -0
- package/esm/src/cli/index/start-handler.d.ts.map +1 -0
- package/esm/src/cli/index/start-handler.js +145 -0
- package/esm/src/cli/mcp/index.d.ts +11 -0
- package/esm/src/cli/mcp/index.d.ts.map +1 -0
- package/esm/src/cli/mcp/index.js +10 -0
- package/esm/src/cli/ui/tui.js +1 -1
- package/esm/src/middleware/builtin/security/redis-rate-limit.d.ts +2 -0
- package/esm/src/middleware/builtin/security/redis-rate-limit.d.ts.map +1 -1
- package/esm/src/middleware/builtin/security/redis-rate-limit.js +23 -9
- package/esm/src/modules/react-loader/ssr-module-loader/cache/redis.d.ts +10 -0
- package/esm/src/modules/react-loader/ssr-module-loader/cache/redis.d.ts.map +1 -1
- package/esm/src/modules/react-loader/ssr-module-loader/cache/redis.js +30 -42
- package/esm/src/modules/react-loader/ssr-module-loader/loader.d.ts.map +1 -1
- package/esm/src/modules/react-loader/ssr-module-loader/loader.js +34 -13
- package/esm/src/platform/adapters/fs/cache/file-cache.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/cache/file-cache.js +9 -3
- package/esm/src/server/context/cache-invalidation.d.ts.map +1 -1
- package/esm/src/server/context/cache-invalidation.js +4 -0
- package/esm/src/server/handlers/dev/dashboard/api.js +4 -0
- package/esm/src/server/handlers/dev/projects/ui-handler.d.ts.map +1 -1
- package/esm/src/server/handlers/dev/projects/ui-handler.js +6 -0
- package/esm/src/transforms/esm/http-cache.d.ts.map +1 -1
- package/esm/src/transforms/esm/http-cache.js +139 -64
- package/esm/src/utils/index.d.ts +1 -1
- package/esm/src/utils/index.d.ts.map +1 -1
- package/esm/src/utils/index.js +1 -1
- package/package.json +2 -1
- package/src/deno.js +1 -1
- package/src/proxy/cache/index.ts +93 -0
- package/src/proxy/cache/memory-cache.ts +120 -0
- package/src/proxy/cache/redis-cache.ts +203 -0
- package/src/proxy/cache/resilient-cache.ts +205 -0
- package/src/proxy/cache/types.ts +72 -0
- package/src/proxy/handler.ts +593 -0
- package/src/proxy/logger.ts +329 -0
- package/src/proxy/oauth-client.ts +91 -0
- package/src/proxy/token-manager.ts +174 -0
- package/src/proxy/tracing.ts +237 -0
- package/src/src/cache/backend.ts +3 -0
- package/src/src/cache/cache-key-builder.ts +0 -9
- package/src/src/cache/multi-tier.ts +0 -41
- package/src/src/cli/app/actions.ts +190 -0
- package/src/src/cli/app/components/inline-input.ts +255 -0
- package/src/src/cli/app/components/list-select.ts +215 -0
- package/src/src/cli/app/index.ts +1471 -0
- package/src/src/cli/app/state.ts +385 -0
- package/src/src/cli/app/views/dashboard.ts +212 -0
- package/src/src/cli/commands/dev.ts +2 -2
- package/src/src/cli/commands/new.ts +1 -1
- package/src/src/cli/index/command-router.ts +9 -40
- package/src/src/cli/index/start-handler.ts +195 -0
- package/src/src/cli/mcp/index.ts +11 -0
- package/src/src/cli/ui/tui.ts +1 -1
- package/src/src/middleware/builtin/security/redis-rate-limit.ts +24 -11
- package/src/src/modules/react-loader/ssr-module-loader/cache/redis.ts +36 -50
- package/src/src/modules/react-loader/ssr-module-loader/loader.ts +38 -14
- package/src/src/platform/adapters/fs/cache/file-cache.ts +9 -3
- package/src/src/server/context/cache-invalidation.ts +4 -0
- package/src/src/server/handlers/dev/dashboard/api.ts +2 -0
- package/src/src/server/handlers/dev/projects/ui-handler.ts +6 -0
- package/src/src/transforms/esm/http-cache.ts +148 -73
- package/src/src/utils/index.ts +0 -1
package/README.md
CHANGED
|
@@ -1,25 +1,26 @@
|
|
|
1
|
-
# Veryfront
|
|
1
|
+
# Veryfront Code
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[](https://github.com/veryfront/veryfront-renderer/actions/workflows/ci.yml)
|
|
5
|
-
[](https://opensource.org/licenses/MIT)
|
|
6
|
-
|
|
7
|
-
The all-in-one React framework for building AI-powered applications and agents.
|
|
8
|
-
|
|
9
|
-
## Features
|
|
10
|
-
|
|
11
|
-
- **Zero config** — Auto-discovery from file structure
|
|
12
|
-
- **Multi-runtime** — Deno, Node.js, Bun, Cloudflare Workers
|
|
13
|
-
- **Full-stack React** — SSR, SSG, ISR, streaming
|
|
14
|
-
- **MCP built-in** — Model Context Protocol server
|
|
15
|
-
- **Production-ready** — Rate limiting, caching, observability
|
|
16
|
-
|
|
17
|
-
## Quick Start
|
|
3
|
+
The simplest way to build AI-powered apps.
|
|
18
4
|
|
|
19
5
|
```bash
|
|
20
|
-
npx veryfront
|
|
6
|
+
npx veryfront
|
|
21
7
|
```
|
|
22
8
|
|
|
9
|
+
```
|
|
10
|
+
○ ○ ○ ○ ○ ○ ○
|
|
11
|
+
○ ● ● ● ○ ○ ○ Veryfront Code is running
|
|
12
|
+
○ ● ● ● ○ ○ ○
|
|
13
|
+
○ ● ● ○ ● ● ○ Url http://veryfront.me:3000
|
|
14
|
+
○ ○ ○ ● ● ● ○ Mcp http://veryfront.me:3002/mcp
|
|
15
|
+
○ ○ ○ ● ● ● ○
|
|
16
|
+
○ ○ ○ ○ ○ ○ ○
|
|
17
|
+
|
|
18
|
+
✓ Server ready
|
|
19
|
+
✓ MCP ready
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
One command. Zero config. Just build.
|
|
23
|
+
|
|
23
24
|
## Project Structure
|
|
24
25
|
|
|
25
26
|
```
|
package/esm/deno.js
CHANGED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token Cache Module
|
|
3
|
+
*
|
|
4
|
+
* Provides configurable caching for OAuth tokens.
|
|
5
|
+
* Supports in-memory (single instance) and Redis (distributed) backends.
|
|
6
|
+
* Redis cache includes automatic fallback to memory when Redis is unavailable.
|
|
7
|
+
*/
|
|
8
|
+
export type { CacheOptions, CacheStats, MemoryCacheOptions, RedisCacheOptions, TokenCache, TokenCacheEntry, } from "./types.js";
|
|
9
|
+
export { MemoryCache } from "./memory-cache.js";
|
|
10
|
+
export { RedisCache } from "./redis-cache.js";
|
|
11
|
+
export { ResilientCache } from "./resilient-cache.js";
|
|
12
|
+
import type { CacheOptions, TokenCache } from "./types.js";
|
|
13
|
+
/**
|
|
14
|
+
* Create a token cache based on configuration.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* // In-memory cache (default)
|
|
19
|
+
* const cache = createCache({ type: "memory" });
|
|
20
|
+
*
|
|
21
|
+
* // Redis cache for distributed deployments
|
|
22
|
+
* const cache = createCache({
|
|
23
|
+
* type: "redis",
|
|
24
|
+
* options: { url: "redis://localhost:6379" }
|
|
25
|
+
* });
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare function createCache(options: CacheOptions): Promise<TokenCache>;
|
|
29
|
+
/**
|
|
30
|
+
* Create cache from environment variables.
|
|
31
|
+
*
|
|
32
|
+
* Environment variables:
|
|
33
|
+
* - CACHE_TYPE: "memory" or "redis" (default: "memory")
|
|
34
|
+
* - REDIS_URL: Redis connection URL (required if CACHE_TYPE=redis)
|
|
35
|
+
* - REDIS_PREFIX: Key prefix (default: "vf:token:")
|
|
36
|
+
*
|
|
37
|
+
* When CACHE_TYPE=redis, automatically wraps with ResilientCache for
|
|
38
|
+
* graceful fallback to memory when Redis is unavailable.
|
|
39
|
+
*/
|
|
40
|
+
export declare function createCacheFromEnv(): Promise<TokenCache>;
|
|
41
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/proxy/cache/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,YAAY,EACV,YAAY,EACZ,UAAU,EACV,kBAAkB,EAClB,iBAAiB,EACjB,UAAU,EACV,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAU3D;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CAU5E;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,UAAU,CAAC,CAwB9D"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token Cache Module
|
|
3
|
+
*
|
|
4
|
+
* Provides configurable caching for OAuth tokens.
|
|
5
|
+
* Supports in-memory (single instance) and Redis (distributed) backends.
|
|
6
|
+
* Redis cache includes automatic fallback to memory when Redis is unavailable.
|
|
7
|
+
*/
|
|
8
|
+
export { MemoryCache } from "./memory-cache.js";
|
|
9
|
+
export { RedisCache } from "./redis-cache.js";
|
|
10
|
+
export { ResilientCache } from "./resilient-cache.js";
|
|
11
|
+
import { MemoryCache } from "./memory-cache.js";
|
|
12
|
+
import { RedisCache } from "./redis-cache.js";
|
|
13
|
+
import { ResilientCache } from "./resilient-cache.js";
|
|
14
|
+
import { getEnv } from "../../src/platform/compat/process.js";
|
|
15
|
+
import { proxyLogger } from "../logger.js";
|
|
16
|
+
import { withSpan } from "../tracing.js";
|
|
17
|
+
const logger = proxyLogger.child({ module: "cache" });
|
|
18
|
+
/**
|
|
19
|
+
* Create a token cache based on configuration.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* // In-memory cache (default)
|
|
24
|
+
* const cache = createCache({ type: "memory" });
|
|
25
|
+
*
|
|
26
|
+
* // Redis cache for distributed deployments
|
|
27
|
+
* const cache = createCache({
|
|
28
|
+
* type: "redis",
|
|
29
|
+
* options: { url: "redis://localhost:6379" }
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export async function createCache(options) {
|
|
34
|
+
return withSpan("cache.create", async () => {
|
|
35
|
+
switch (options.type) {
|
|
36
|
+
case "redis":
|
|
37
|
+
return new RedisCache(options.options);
|
|
38
|
+
case "memory":
|
|
39
|
+
default:
|
|
40
|
+
return new MemoryCache(options.options);
|
|
41
|
+
}
|
|
42
|
+
}, { "cache.type": options.type });
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Create cache from environment variables.
|
|
46
|
+
*
|
|
47
|
+
* Environment variables:
|
|
48
|
+
* - CACHE_TYPE: "memory" or "redis" (default: "memory")
|
|
49
|
+
* - REDIS_URL: Redis connection URL (required if CACHE_TYPE=redis)
|
|
50
|
+
* - REDIS_PREFIX: Key prefix (default: "vf:token:")
|
|
51
|
+
*
|
|
52
|
+
* When CACHE_TYPE=redis, automatically wraps with ResilientCache for
|
|
53
|
+
* graceful fallback to memory when Redis is unavailable.
|
|
54
|
+
*/
|
|
55
|
+
export async function createCacheFromEnv() {
|
|
56
|
+
return withSpan("cache.createFromEnv", async () => {
|
|
57
|
+
const cacheType = getEnv("CACHE_TYPE") || "memory";
|
|
58
|
+
if (cacheType === "redis") {
|
|
59
|
+
const url = getEnv("REDIS_URL");
|
|
60
|
+
if (!url) {
|
|
61
|
+
logger.warn("[Cache] CACHE_TYPE=redis but REDIS_URL not set, falling back to memory");
|
|
62
|
+
return new MemoryCache();
|
|
63
|
+
}
|
|
64
|
+
const redisCache = new RedisCache({
|
|
65
|
+
url,
|
|
66
|
+
prefix: getEnv("REDIS_PREFIX") || "vf:token:",
|
|
67
|
+
});
|
|
68
|
+
// Wrap Redis with resilient fallback to memory cache
|
|
69
|
+
// This ensures the proxy continues to function when Redis is unavailable
|
|
70
|
+
logger.info("[Cache] Using Redis with memory fallback (ResilientCache)");
|
|
71
|
+
return new ResilientCache(redisCache, new MemoryCache());
|
|
72
|
+
}
|
|
73
|
+
return new MemoryCache();
|
|
74
|
+
}, { "cache.type": getEnv("CACHE_TYPE") || "memory" });
|
|
75
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { CacheStats, MemoryCacheOptions, TokenCache, TokenCacheEntry } from "./types.js";
|
|
2
|
+
export declare class MemoryCache implements TokenCache {
|
|
3
|
+
private cache;
|
|
4
|
+
private hits;
|
|
5
|
+
private misses;
|
|
6
|
+
private maxSize;
|
|
7
|
+
private cleanupTimer;
|
|
8
|
+
constructor(options?: MemoryCacheOptions);
|
|
9
|
+
get(key: string): Promise<TokenCacheEntry | null>;
|
|
10
|
+
set(key: string, entry: TokenCacheEntry): Promise<void>;
|
|
11
|
+
delete(key: string): Promise<void>;
|
|
12
|
+
clear(): Promise<void>;
|
|
13
|
+
has(key: string): Promise<boolean>;
|
|
14
|
+
stats(): Promise<CacheStats>;
|
|
15
|
+
close(): Promise<void>;
|
|
16
|
+
private cleanup;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=memory-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-cache.d.ts","sourceRoot":"","sources":["../../../src/proxy/cache/memory-cache.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAM9F,qBAAa,WAAY,YAAW,UAAU;IAC5C,OAAO,CAAC,KAAK,CAAsC;IACnD,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,YAAY,CAAuD;gBAE/D,OAAO,GAAE,kBAAuB;IAM5C,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAoBjD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAYvD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMlC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQtB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAclC,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC;IAS5B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAUtB,OAAO,CAAC,OAAO;CAehB"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-Memory Token Cache - single-instance deployments.
|
|
3
|
+
*/
|
|
4
|
+
import * as dntShim from "../../_dnt.shims.js";
|
|
5
|
+
import { withSpan } from "../tracing.js";
|
|
6
|
+
const DEFAULT_MAX_SIZE = 1000;
|
|
7
|
+
const DEFAULT_CLEANUP_INTERVAL = 60_000;
|
|
8
|
+
export class MemoryCache {
|
|
9
|
+
cache = new Map();
|
|
10
|
+
hits = 0;
|
|
11
|
+
misses = 0;
|
|
12
|
+
maxSize;
|
|
13
|
+
cleanupTimer = null;
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
this.maxSize = options.maxSize ?? DEFAULT_MAX_SIZE;
|
|
16
|
+
const interval = options.cleanupInterval ?? DEFAULT_CLEANUP_INTERVAL;
|
|
17
|
+
this.cleanupTimer = dntShim.setInterval(() => this.cleanup(), interval);
|
|
18
|
+
}
|
|
19
|
+
get(key) {
|
|
20
|
+
return withSpan("cache.memory.get", async () => {
|
|
21
|
+
const entry = this.cache.get(key);
|
|
22
|
+
if (!entry) {
|
|
23
|
+
this.misses++;
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
if (Date.now() >= entry.expiresAt) {
|
|
27
|
+
this.cache.delete(key);
|
|
28
|
+
this.misses++;
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
this.hits++;
|
|
32
|
+
return entry;
|
|
33
|
+
}, { "cache.key": key });
|
|
34
|
+
}
|
|
35
|
+
set(key, entry) {
|
|
36
|
+
return withSpan("cache.memory.set", async () => {
|
|
37
|
+
if (this.cache.size >= this.maxSize) {
|
|
38
|
+
const firstKey = this.cache.keys().next().value;
|
|
39
|
+
if (firstKey) {
|
|
40
|
+
this.cache.delete(firstKey);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
this.cache.set(key, entry);
|
|
44
|
+
}, { "cache.key": key });
|
|
45
|
+
}
|
|
46
|
+
delete(key) {
|
|
47
|
+
return withSpan("cache.memory.delete", async () => {
|
|
48
|
+
this.cache.delete(key);
|
|
49
|
+
}, { "cache.key": key });
|
|
50
|
+
}
|
|
51
|
+
clear() {
|
|
52
|
+
return withSpan("cache.memory.clear", async () => {
|
|
53
|
+
this.cache.clear();
|
|
54
|
+
this.hits = 0;
|
|
55
|
+
this.misses = 0;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
has(key) {
|
|
59
|
+
return withSpan("cache.memory.has", async () => {
|
|
60
|
+
const entry = this.cache.get(key);
|
|
61
|
+
if (!entry)
|
|
62
|
+
return false;
|
|
63
|
+
if (Date.now() >= entry.expiresAt) {
|
|
64
|
+
this.cache.delete(key);
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
return true;
|
|
68
|
+
}, { "cache.key": key });
|
|
69
|
+
}
|
|
70
|
+
stats() {
|
|
71
|
+
return withSpan("cache.memory.stats", async () => ({
|
|
72
|
+
hits: this.hits,
|
|
73
|
+
misses: this.misses,
|
|
74
|
+
size: this.cache.size,
|
|
75
|
+
type: "memory",
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
close() {
|
|
79
|
+
return withSpan("cache.memory.close", async () => {
|
|
80
|
+
if (this.cleanupTimer) {
|
|
81
|
+
clearInterval(this.cleanupTimer);
|
|
82
|
+
this.cleanupTimer = null;
|
|
83
|
+
}
|
|
84
|
+
this.cache.clear();
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
cleanup() {
|
|
88
|
+
const now = Date.now();
|
|
89
|
+
let cleaned = 0;
|
|
90
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
91
|
+
if (now >= entry.expiresAt) {
|
|
92
|
+
this.cache.delete(key);
|
|
93
|
+
cleaned++;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (cleaned > 0) {
|
|
97
|
+
console.log(`[MemoryCache] Cleaned ${cleaned} expired entries`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis Token Cache
|
|
3
|
+
*
|
|
4
|
+
* Uses the standard `redis` package for cross-runtime compatibility.
|
|
5
|
+
* Works in Deno, Node.js, and Bun.
|
|
6
|
+
*/
|
|
7
|
+
import type { CacheStats, RedisCacheOptions, TokenCache, TokenCacheEntry } from "./types.js";
|
|
8
|
+
export declare class RedisCache implements TokenCache {
|
|
9
|
+
private client;
|
|
10
|
+
private prefix;
|
|
11
|
+
private url;
|
|
12
|
+
private connectTimeout;
|
|
13
|
+
private hits;
|
|
14
|
+
private misses;
|
|
15
|
+
private connected;
|
|
16
|
+
constructor(options: RedisCacheOptions);
|
|
17
|
+
private key;
|
|
18
|
+
get(key: string): Promise<TokenCacheEntry | null>;
|
|
19
|
+
set(key: string, entry: TokenCacheEntry): Promise<void>;
|
|
20
|
+
delete(key: string): Promise<void>;
|
|
21
|
+
clear(): Promise<void>;
|
|
22
|
+
has(key: string): Promise<boolean>;
|
|
23
|
+
stats(): Promise<CacheStats>;
|
|
24
|
+
close(): Promise<void>;
|
|
25
|
+
private ensureConnected;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=redis-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-cache.d.ts","sourceRoot":"","sources":["../../../src/proxy/cache/redis-cache.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAO7F,qBAAa,UAAW,YAAW,UAAU;IAC3C,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,SAAS,CAAS;gBAEd,OAAO,EAAE,iBAAiB;IAMtC,OAAO,CAAC,GAAG;IAIL,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IA8BjD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAevD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAalC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAmCtB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAclC,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC;IAe5B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAcd,eAAe;CAgC9B"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis Token Cache
|
|
3
|
+
*
|
|
4
|
+
* Uses the standard `redis` package for cross-runtime compatibility.
|
|
5
|
+
* Works in Deno, Node.js, and Bun.
|
|
6
|
+
*/
|
|
7
|
+
import { createClient } from "redis";
|
|
8
|
+
import { withSpan } from "../tracing.js";
|
|
9
|
+
const DEFAULT_PREFIX = "vf:token:";
|
|
10
|
+
const DEFAULT_CONNECT_TIMEOUT = 5000;
|
|
11
|
+
const DEFAULT_SCAN_COUNT = 100;
|
|
12
|
+
export class RedisCache {
|
|
13
|
+
client = null;
|
|
14
|
+
prefix;
|
|
15
|
+
url;
|
|
16
|
+
connectTimeout;
|
|
17
|
+
hits = 0;
|
|
18
|
+
misses = 0;
|
|
19
|
+
connected = false;
|
|
20
|
+
constructor(options) {
|
|
21
|
+
this.url = options.url;
|
|
22
|
+
this.prefix = options.prefix ?? DEFAULT_PREFIX;
|
|
23
|
+
this.connectTimeout = options.connectTimeout ?? DEFAULT_CONNECT_TIMEOUT;
|
|
24
|
+
}
|
|
25
|
+
key(k) {
|
|
26
|
+
return `${this.prefix}${k}`;
|
|
27
|
+
}
|
|
28
|
+
async get(key) {
|
|
29
|
+
return withSpan("cache.redis.get", async () => {
|
|
30
|
+
try {
|
|
31
|
+
await this.ensureConnected();
|
|
32
|
+
const data = await this.client.get(this.key(key));
|
|
33
|
+
if (!data) {
|
|
34
|
+
this.misses++;
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const entry = JSON.parse(data);
|
|
38
|
+
if (Date.now() >= entry.expiresAt) {
|
|
39
|
+
await this.client.del(this.key(key));
|
|
40
|
+
this.misses++;
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
this.hits++;
|
|
44
|
+
return entry;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
console.error("[RedisCache] Get error:", error);
|
|
48
|
+
this.connected = false;
|
|
49
|
+
this.misses++;
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
}, { "cache.key": key });
|
|
53
|
+
}
|
|
54
|
+
async set(key, entry) {
|
|
55
|
+
return withSpan("cache.redis.set", async () => {
|
|
56
|
+
try {
|
|
57
|
+
await this.ensureConnected();
|
|
58
|
+
const ttlMs = entry.expiresAt - Date.now();
|
|
59
|
+
const ttlSeconds = Math.max(1, Math.floor(ttlMs / 1000));
|
|
60
|
+
await this.client.setEx(this.key(key), ttlSeconds, JSON.stringify(entry));
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
console.error("[RedisCache] Set error:", error);
|
|
64
|
+
this.connected = false;
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}, { "cache.key": key });
|
|
68
|
+
}
|
|
69
|
+
async delete(key) {
|
|
70
|
+
return withSpan("cache.redis.delete", async () => {
|
|
71
|
+
try {
|
|
72
|
+
await this.ensureConnected();
|
|
73
|
+
await this.client.del(this.key(key));
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
console.error("[RedisCache] Delete error:", error);
|
|
77
|
+
this.connected = false;
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}, { "cache.key": key });
|
|
81
|
+
}
|
|
82
|
+
async clear() {
|
|
83
|
+
return withSpan("cache.redis.clear", async () => {
|
|
84
|
+
try {
|
|
85
|
+
await this.ensureConnected();
|
|
86
|
+
const pattern = `${this.prefix}*`;
|
|
87
|
+
let cursor = "0";
|
|
88
|
+
let totalDeleted = 0;
|
|
89
|
+
do {
|
|
90
|
+
const result = await this.client.scan(cursor, {
|
|
91
|
+
MATCH: pattern,
|
|
92
|
+
COUNT: DEFAULT_SCAN_COUNT,
|
|
93
|
+
});
|
|
94
|
+
cursor = String(result.cursor);
|
|
95
|
+
if (result.keys.length > 0) {
|
|
96
|
+
totalDeleted += await this.client.del(result.keys);
|
|
97
|
+
}
|
|
98
|
+
} while (cursor !== "0");
|
|
99
|
+
if (totalDeleted > 0) {
|
|
100
|
+
console.log(`[RedisCache] Cleared ${totalDeleted} keys`);
|
|
101
|
+
}
|
|
102
|
+
this.hits = 0;
|
|
103
|
+
this.misses = 0;
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
console.error("[RedisCache] Clear error:", error);
|
|
107
|
+
this.connected = false;
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
async has(key) {
|
|
113
|
+
return withSpan("cache.redis.has", async () => {
|
|
114
|
+
try {
|
|
115
|
+
await this.ensureConnected();
|
|
116
|
+
const exists = await this.client.exists(this.key(key));
|
|
117
|
+
return exists === 1;
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
console.error("[RedisCache] Has error:", error);
|
|
121
|
+
this.connected = false;
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
}, { "cache.key": key });
|
|
125
|
+
}
|
|
126
|
+
async stats() {
|
|
127
|
+
return withSpan("cache.redis.stats", async () => {
|
|
128
|
+
let size = 0;
|
|
129
|
+
try {
|
|
130
|
+
await this.ensureConnected();
|
|
131
|
+
size = await this.client.dbSize();
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
this.connected = false;
|
|
135
|
+
console.warn("[RedisCache] Stats error:", error);
|
|
136
|
+
}
|
|
137
|
+
return { hits: this.hits, misses: this.misses, size, type: "redis" };
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
async close() {
|
|
141
|
+
return withSpan("cache.redis.close", async () => {
|
|
142
|
+
if (this.client) {
|
|
143
|
+
try {
|
|
144
|
+
await this.client.quit();
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// Ignore close errors
|
|
148
|
+
}
|
|
149
|
+
this.client = null;
|
|
150
|
+
}
|
|
151
|
+
this.connected = false;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
async ensureConnected() {
|
|
155
|
+
return withSpan("cache.redis.connect", async () => {
|
|
156
|
+
if (this.connected && this.client) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
// Create client with connection options
|
|
160
|
+
this.client = createClient({
|
|
161
|
+
url: this.url,
|
|
162
|
+
socket: {
|
|
163
|
+
connectTimeout: this.connectTimeout,
|
|
164
|
+
reconnectStrategy: (retries) => {
|
|
165
|
+
// Exponential backoff with max 3 retries
|
|
166
|
+
if (retries > 3) {
|
|
167
|
+
return new Error("Max reconnection attempts reached");
|
|
168
|
+
}
|
|
169
|
+
return Math.min(retries * 100, 3000);
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
// Handle connection errors
|
|
174
|
+
this.client.on("error", (err) => {
|
|
175
|
+
console.error("[RedisCache] Client error:", err);
|
|
176
|
+
this.connected = false;
|
|
177
|
+
});
|
|
178
|
+
await this.client.connect();
|
|
179
|
+
this.connected = true;
|
|
180
|
+
console.log("[RedisCache] Connected");
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resilient Token Cache
|
|
3
|
+
*
|
|
4
|
+
* Wraps a primary cache (Redis) with a fallback cache (Memory).
|
|
5
|
+
* Automatically falls back to memory cache when Redis operations fail.
|
|
6
|
+
* Provides graceful degradation instead of hard failures.
|
|
7
|
+
*/
|
|
8
|
+
import type { CacheStats, TokenCache, TokenCacheEntry } from "./types.js";
|
|
9
|
+
export declare class ResilientCache implements TokenCache {
|
|
10
|
+
private primary;
|
|
11
|
+
private fallback;
|
|
12
|
+
private usingFallback;
|
|
13
|
+
private failureCount;
|
|
14
|
+
private circuitOpenedAt;
|
|
15
|
+
constructor(primary: TokenCache, fallback: TokenCache);
|
|
16
|
+
/**
|
|
17
|
+
* Check if we should try primary again after circuit was opened.
|
|
18
|
+
*/
|
|
19
|
+
private shouldTryPrimary;
|
|
20
|
+
/**
|
|
21
|
+
* Record a successful primary operation - reset failure state.
|
|
22
|
+
*/
|
|
23
|
+
private recordSuccess;
|
|
24
|
+
/**
|
|
25
|
+
* Record a primary failure - may trigger fallback.
|
|
26
|
+
*/
|
|
27
|
+
private recordFailure;
|
|
28
|
+
get(key: string): Promise<TokenCacheEntry | null>;
|
|
29
|
+
set(key: string, entry: TokenCacheEntry): Promise<void>;
|
|
30
|
+
delete(key: string): Promise<void>;
|
|
31
|
+
clear(): Promise<void>;
|
|
32
|
+
has(key: string): Promise<boolean>;
|
|
33
|
+
stats(): Promise<CacheStats>;
|
|
34
|
+
close(): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Get current resilience status for debugging/health checks.
|
|
37
|
+
*/
|
|
38
|
+
getStatus(): {
|
|
39
|
+
usingFallback: boolean;
|
|
40
|
+
failureCount: number;
|
|
41
|
+
circuitOpenedAt: number | null;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=resilient-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resilient-cache.d.ts","sourceRoot":"","sources":["../../../src/proxy/cache/resilient-cache.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAS1E,qBAAa,cAAe,YAAW,UAAU;IAC/C,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,eAAe,CAAuB;gBAElC,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU;IAKrD;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAexB;;OAEG;IACH,OAAO,CAAC,aAAa;IASrB;;OAEG;IACH,OAAO,CAAC,aAAa;IAcf,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAmBjD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBvD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBlC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAetB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAgBlC,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC;IAwB5B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAM5B;;OAEG;IACH,SAAS,IAAI;QACX,aAAa,EAAE,OAAO,CAAC;QACvB,YAAY,EAAE,MAAM,CAAC;QACrB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;KAChC;CAOF"}
|