@stacksjs/cache 0.70.88 → 0.70.91
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/drivers/index.d.ts +142 -0
- package/dist/drivers/singlestore.d.ts +35 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +2 -0
- package/package.json +2 -2
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { CacheManager, MemoryDriver, RedisDriver } from '@stacksjs/ts-cache';
|
|
2
|
+
import { SingleStoreCacheStore } from './singlestore';
|
|
3
|
+
import type { CacheDriver, CacheStats } from '@stacksjs/types';
|
|
4
|
+
import type { SingleStoreCacheOptions } from './singlestore';
|
|
5
|
+
// Re-export the SingleStore store + its options for advanced usage
|
|
6
|
+
export type { SingleStoreCacheOptions } from './singlestore';
|
|
7
|
+
/**
|
|
8
|
+
* Create a memory cache driver
|
|
9
|
+
*/
|
|
10
|
+
export declare function createMemoryCache(options?: MemoryOptions): CacheDriver;
|
|
11
|
+
/**
|
|
12
|
+
* Create a Redis cache driver
|
|
13
|
+
*/
|
|
14
|
+
export declare function createRedisCache(options?: RedisOptions): CacheDriver;
|
|
15
|
+
/**
|
|
16
|
+
* Create a SingleStore cache driver.
|
|
17
|
+
*
|
|
18
|
+
* Wraps a SingleStore-backed store in the same `StacksCache` façade the memory
|
|
19
|
+
* and Redis drivers use, so it inherits the stampede-protection + tag logic.
|
|
20
|
+
* The store implements the `CacheManager` surface `StacksCache` consumes; the
|
|
21
|
+
* cast bridges the structural gap (it is not a literal `CacheManager` subclass).
|
|
22
|
+
*/
|
|
23
|
+
export declare function createSingleStoreCache(options?: SingleStoreCacheOptions): CacheDriver;
|
|
24
|
+
/**
|
|
25
|
+
* Create a cache driver based on the specified type
|
|
26
|
+
*/
|
|
27
|
+
export declare function createCache(driver: 'memory', options?: MemoryOptions): CacheDriver;
|
|
28
|
+
export declare function createCache(driver: 'redis', options?: RedisOptions): CacheDriver;
|
|
29
|
+
export declare function createCache(driver: 'singlestore', options?: SingleStoreCacheOptions): CacheDriver;
|
|
30
|
+
/**
|
|
31
|
+
* Default memory cache instance
|
|
32
|
+
*/
|
|
33
|
+
export declare const memory: CacheDriver;
|
|
34
|
+
/**
|
|
35
|
+
* Default cache instance (memory)
|
|
36
|
+
*/
|
|
37
|
+
export declare const cache: CacheDriver;
|
|
38
|
+
/**
|
|
39
|
+
* Memory cache options
|
|
40
|
+
*/
|
|
41
|
+
export declare interface MemoryOptions {
|
|
42
|
+
stdTTL?: number
|
|
43
|
+
checkPeriod?: number
|
|
44
|
+
maxKeys?: number
|
|
45
|
+
useClones?: boolean
|
|
46
|
+
prefix?: string
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Redis cache options
|
|
50
|
+
*/
|
|
51
|
+
export declare interface RedisOptions {
|
|
52
|
+
url?: string
|
|
53
|
+
host?: string
|
|
54
|
+
port?: number
|
|
55
|
+
username?: string
|
|
56
|
+
password?: string
|
|
57
|
+
database?: number
|
|
58
|
+
tls?: boolean
|
|
59
|
+
stdTTL?: number
|
|
60
|
+
prefix?: string
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Stacks Cache Wrapper
|
|
64
|
+
*
|
|
65
|
+
* Wraps ts-cache's CacheManager to provide a consistent API
|
|
66
|
+
* that matches the Stacks CacheDriver interface.
|
|
67
|
+
*/
|
|
68
|
+
export declare class StacksCache implements CacheDriver {
|
|
69
|
+
constructor(manager: CacheManager, opts?: { inflightTimeoutMs?: number });
|
|
70
|
+
get<T>(key: string): Promise<T | undefined>;
|
|
71
|
+
mget<T>(keys: string[]): Promise<Record<string, T>>;
|
|
72
|
+
set<T>(key: string, value: T, ttl?: number): Promise<boolean>;
|
|
73
|
+
mset<T>(entries: Array<{ key: string, value: T, ttl?: number }>): Promise<boolean>;
|
|
74
|
+
setForever<T>(key: string, value: T): Promise<boolean>;
|
|
75
|
+
getOrSet<T>(key: string, fetcher: () => T | Promise<T>, ttl?: number): Promise<T>;
|
|
76
|
+
remember<T>(key: string, ttl: number, callback: () => T | Promise<T>): Promise<T>;
|
|
77
|
+
rememberForever<T>(key: string, callback: () => T | Promise<T>): Promise<T>;
|
|
78
|
+
del(keys: string | string[]): Promise<number>;
|
|
79
|
+
has(key: string): Promise<boolean>;
|
|
80
|
+
missing(key: string): Promise<boolean>;
|
|
81
|
+
remove(key: string): Promise<void>;
|
|
82
|
+
deleteMany(keys: string[]): Promise<number>;
|
|
83
|
+
clear(): Promise<void>;
|
|
84
|
+
flush(): Promise<void>;
|
|
85
|
+
keys(pattern?: string): Promise<string[]>;
|
|
86
|
+
getTtl(key: string): Promise<number | undefined>;
|
|
87
|
+
ttl(key: string, ttl: number): Promise<boolean>;
|
|
88
|
+
take<T>(key: string): Promise<T | undefined>;
|
|
89
|
+
getStats(): Promise<CacheStats>;
|
|
90
|
+
close(): Promise<void>;
|
|
91
|
+
disconnect(): Promise<void>;
|
|
92
|
+
get cacheManager(): CacheManager;
|
|
93
|
+
tags(tags: readonly string[]): TaggedCache;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Tag-scoped wrapper around `StacksCache`. Records every write under
|
|
97
|
+
* the named tag(s) so `flush()` can invalidate cascade-style without the
|
|
98
|
+
* caller having to track keys themselves.
|
|
99
|
+
*
|
|
100
|
+
* The tag index is stored as `string[]` on disk (so it survives the
|
|
101
|
+
* Redis driver's serializer), but operated on in memory as a `Set` so
|
|
102
|
+
* each write is `O(1)` instead of `O(N)` over the existing index.
|
|
103
|
+
*
|
|
104
|
+
* `flush()` uses a per-tag in-process mutex so concurrent flushes don't
|
|
105
|
+
* race-and-lose keys: without it, two parallel flushes could both read
|
|
106
|
+
* the index, both delete, and the second flush would succeed against
|
|
107
|
+
* the now-empty index — leaving any keys added between the two reads
|
|
108
|
+
* orphaned.
|
|
109
|
+
*/
|
|
110
|
+
export declare class TaggedCache {
|
|
111
|
+
constructor(cache: StacksCache, tags: readonly string[]);
|
|
112
|
+
put<T>(key: string, value: T, ttl?: number): Promise<boolean>;
|
|
113
|
+
set<T>(key: string, value: T, ttl?: number): Promise<boolean>;
|
|
114
|
+
setForever<T>(key: string, value: T): Promise<boolean>;
|
|
115
|
+
remember<T>(key: string, ttl: number, callback: () => T | Promise<T>): Promise<T>;
|
|
116
|
+
rememberForever<T>(key: string, callback: () => T | Promise<T>): Promise<T>;
|
|
117
|
+
get<T>(key: string): Promise<T | undefined>;
|
|
118
|
+
has(key: string): Promise<boolean>;
|
|
119
|
+
flush(): Promise<number>;
|
|
120
|
+
}
|
|
121
|
+
export { SingleStoreCacheStore } from './singlestore';
|
|
122
|
+
// Re-export ts-cache utilities for advanced usage
|
|
123
|
+
export {
|
|
124
|
+
CacheManager,
|
|
125
|
+
MemoryDriver,
|
|
126
|
+
RedisDriver,
|
|
127
|
+
} from '@stacksjs/ts-cache';
|
|
128
|
+
// Re-export patterns and utilities
|
|
129
|
+
export {
|
|
130
|
+
// Patterns
|
|
131
|
+
CacheAsidePattern,
|
|
132
|
+
MultiLevelPattern,
|
|
133
|
+
RefreshAheadPattern,
|
|
134
|
+
WriteThroughPattern,
|
|
135
|
+
// Utilities
|
|
136
|
+
BatchOperations,
|
|
137
|
+
CacheInvalidation,
|
|
138
|
+
CacheLock,
|
|
139
|
+
CircuitBreaker,
|
|
140
|
+
memoize,
|
|
141
|
+
RateLimiter,
|
|
142
|
+
} from '@stacksjs/ts-cache';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export declare interface SingleStoreCacheOptions {
|
|
2
|
+
host?: string
|
|
3
|
+
port?: number
|
|
4
|
+
username?: string
|
|
5
|
+
password?: string
|
|
6
|
+
database?: string
|
|
7
|
+
table?: string
|
|
8
|
+
ssl?: boolean
|
|
9
|
+
prefix?: string
|
|
10
|
+
stdTTL?: number
|
|
11
|
+
}
|
|
12
|
+
declare interface CacheStatsShape {
|
|
13
|
+
hits: number
|
|
14
|
+
misses: number
|
|
15
|
+
keys: number
|
|
16
|
+
ksize: number
|
|
17
|
+
vsize: number
|
|
18
|
+
}
|
|
19
|
+
export declare class SingleStoreCacheStore {
|
|
20
|
+
constructor(options?: SingleStoreCacheOptions);
|
|
21
|
+
get<T>(key: string): Promise<T | undefined>;
|
|
22
|
+
mget<T>(keys: string[]): Promise<Record<string, T>>;
|
|
23
|
+
set<T>(key: string, value: T, ttl?: number): Promise<boolean>;
|
|
24
|
+
mset<T>(entries: Array<{ key: string, value: T, ttl?: number }>): Promise<boolean>;
|
|
25
|
+
fetch<T>(key: string, fn: () => T | Promise<T>, ttl?: number): Promise<T>;
|
|
26
|
+
del(keys: string | string[]): Promise<number>;
|
|
27
|
+
has(key: string): Promise<boolean>;
|
|
28
|
+
flush(): Promise<void>;
|
|
29
|
+
keys(pattern?: string): Promise<string[]>;
|
|
30
|
+
getTtl(key: string): Promise<number | undefined>;
|
|
31
|
+
ttl(key: string, ttl: number): Promise<boolean>;
|
|
32
|
+
take<T>(key: string): Promise<T | undefined>;
|
|
33
|
+
getStats(): Promise<CacheStatsShape>;
|
|
34
|
+
close(): Promise<void>;
|
|
35
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @stacksjs/cache
|
|
3
|
+
*
|
|
4
|
+
* A high-performance, type-safe caching library powered by ts-cache.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { cache, createCache, createMemoryCache, createRedisCache } from '@stacksjs/cache'
|
|
9
|
+
*
|
|
10
|
+
* // Use the default memory cache
|
|
11
|
+
* await cache.set('key', 'value', 60) // 60 second TTL
|
|
12
|
+
* const value = await cache.get('key')
|
|
13
|
+
*
|
|
14
|
+
* // Create a custom Redis cache
|
|
15
|
+
* const redisCache = createRedisCache({
|
|
16
|
+
* host: 'localhost',
|
|
17
|
+
* port: 6379,
|
|
18
|
+
* prefix: 'myapp',
|
|
19
|
+
* })
|
|
20
|
+
*
|
|
21
|
+
* // Use the factory function
|
|
22
|
+
* const customCache = createCache('memory', { maxKeys: 1000 })
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export * from './drivers/index';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import{createCache as V}from"@stacksjs/ts-cache";var{SQL:Y}=globalThis.Bun;function Z(R){return`\`${R.replace(/`/g,"``")}\``}class L{sql;table;qualified;prefix;stdTTL;ready=null;hits=0;misses=0;constructor(R={}){let{host:U="127.0.0.1",port:z=3306,username:B="root",password:D="",database:E="stacks",table:H="stacks_cache",ssl:N=!1}=R;this.table=H,this.qualified=Z(H),this.prefix=R.prefix??"",this.stdTTL=R.stdTTL??0;let W=D?`${B}:${encodeURIComponent(D)}`:B,O=N?"?ssl=true":"";this.sql=new Y(`mysql://${W}@${U}:${z}/${E}${O}`)}async ensureReady(){if(!this.ready)this.ready=this.sql.unsafe(`CREATE ROWSTORE TABLE IF NOT EXISTS ${this.qualified} ( cache_key VARCHAR(255) NOT NULL, value LONGTEXT NOT NULL, expires_at BIGINT NULL, PRIMARY KEY (cache_key) )`).then(()=>{return});await this.ready}k(R){return this.prefix?`${this.prefix}:${R}`:R}nowMs(){return Date.now()}expiryFor(R){let U=R??this.stdTTL;if(!U||U<=0)return null;return this.nowMs()+U*1000}async get(R){await this.ensureReady();let z=(await this.sql.unsafe(`SELECT value, expires_at FROM ${this.qualified} WHERE cache_key = ? LIMIT 1`,[this.k(R)]))[0];if(!z){this.misses++;return}if(z.expires_at!=null&&z.expires_at<=this.nowMs()){await this.del(R),this.misses++;return}return this.hits++,JSON.parse(z.value)}async mget(R){let U={};for(let z of R){let B=await this.get(z);if(B!==void 0)U[z]=B}return U}async set(R,U,z){await this.ensureReady();let B=this.expiryFor(z);return await this.sql.unsafe(`INSERT INTO ${this.qualified} (cache_key, value, expires_at) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE value = VALUES(value), expires_at = VALUES(expires_at)`,[this.k(R),JSON.stringify(U),B]),!0}async mset(R){for(let U of R)await this.set(U.key,U.value,U.ttl);return!0}async fetch(R,U,z){let B=await this.get(R);if(B!==void 0)return B;let D=await U();return await this.set(R,D,z),D}async del(R){await this.ensureReady();let U=Array.isArray(R)?R:[R];if(U.length===0)return 0;let z=U.map(()=>"?").join(", "),D=(await this.sql.unsafe(`DELETE FROM ${this.qualified} WHERE cache_key IN (${z})`,U.map((E)=>this.k(E)))).affectedRows;return typeof D==="number"?D:U.length}async has(R){return await this.get(R)!==void 0}async flush(){if(await this.ensureReady(),this.prefix)await this.sql.unsafe(`DELETE FROM ${this.qualified} WHERE cache_key LIKE ?`,[`${this.prefix}:%`]);else await this.sql.unsafe(`TRUNCATE TABLE ${this.qualified}`)}async keys(R){await this.ensureReady();let U=`${this.prefix?`${this.prefix}:`:""}${R?R.replace(/\*/g,"%"):"%"}`,z=await this.sql.unsafe(`SELECT cache_key FROM ${this.qualified} WHERE cache_key LIKE ? AND (expires_at IS NULL OR expires_at > ?)`,[U,this.nowMs()]),B=this.prefix?this.prefix.length+1:0;return z.map((D)=>B?D.cache_key.slice(B):D.cache_key)}async getTtl(R){await this.ensureReady();let z=(await this.sql.unsafe(`SELECT expires_at FROM ${this.qualified} WHERE cache_key = ? LIMIT 1`,[this.k(R)]))[0];if(!z)return;return z.expires_at==null?0:z.expires_at}async ttl(R,U){await this.ensureReady();let z=this.expiryFor(U);return((await this.sql.unsafe(`UPDATE ${this.qualified} SET expires_at = ? WHERE cache_key = ?`,[z,this.k(R)])).affectedRows??0)>0}async take(R){let U=await this.get(R);if(U!==void 0)await this.del(R);return U}async getStats(){await this.ensureReady();let R=await this.sql.unsafe(`SELECT COUNT(*) AS n FROM ${this.qualified} WHERE expires_at IS NULL OR expires_at > ?`,[this.nowMs()]);return{hits:this.hits,misses:this.misses,keys:Number(R[0]?.n??0),ksize:0,vsize:0}}async close(){await this.sql.end()}}import{CacheManager as m,MemoryDriver as h,RedisDriver as v}from"@stacksjs/ts-cache";import{CacheAsidePattern as d,MultiLevelPattern as u,RefreshAheadPattern as c,WriteThroughPattern as p,BatchOperations as g,CacheInvalidation as r,CacheLock as l,CircuitBreaker as n,memoize as i,RateLimiter as y}from"@stacksjs/ts-cache";class J{manager;inflight=new Map;inflightTimeoutMs;constructor(R,U={}){this.manager=R,this.inflightTimeoutMs=U.inflightTimeoutMs??30000}async get(R){return await this.manager.get(R)}async mget(R){return await this.manager.mget(R)}async set(R,U,z){return await this.manager.set(R,U,z)}async mset(R){return await this.manager.mset(R)}async setForever(R,U){return await this.manager.set(R,U,0)}async getOrSet(R,U,z){let B=await this.manager.get(R);if(B!==void 0)return B;let D=this.inflight.get(R);if(D)return await D;let E=(async()=>{try{return await this.manager.fetch(R,U,z)}finally{this.inflight.delete(R)}})(),H=this.inflightTimeoutMs,N=new Promise((W,O)=>{let X=setTimeout(()=>{if(this.inflight.get(R)===E)this.inflight.delete(R);O(Error(`[cache] getOrSet('${R}') timed out after ${H}ms`))},H);E.then(()=>clearTimeout(X),()=>clearTimeout(X))});return this.inflight.set(R,E),await Promise.race([E,N])}async remember(R,U,z){return await this.getOrSet(R,z,U)}async rememberForever(R,U){return this.getOrSet(R,U,0)}async del(R){return await this.manager.del(R)}async has(R){return await this.manager.has(R)}async missing(R){return!await this.manager.has(R)}async remove(R){await this.manager.del(R)}async deleteMany(R){return await this.manager.del(R)}async clear(){await this.manager.flush()}async flush(){await this.manager.flush()}async keys(R){return await this.manager.keys(R)}async getTtl(R){return await this.manager.getTtl(R)}async ttl(R,U){return await this.manager.ttl(R,U)}async take(R){return await this.manager.take(R)}async getStats(){let R=await this.manager.getStats(),U=R.hits+R.misses;return{hits:R.hits,misses:R.misses,keys:R.keys,size:R.ksize+R.vsize,hitRate:U>0?R.hits/U:0}}async close(){await this.manager.close()}async disconnect(){await this.manager.close()}get cacheManager(){return this.manager}tags(R){return new G(this,R)}}class G{cache;tags;static TAG_PREFIX="__stacks_tag__:";static flushLocks=new Map;constructor(R,U){this.cache=R;this.tags=U;if(!U||U.length===0)throw Error("cache.tags(...) requires at least one tag")}tagKey(R){return`${G.TAG_PREFIX}${R}`}async indexKey(R){for(let U of this.tags){let z=this.tagKey(U),B=await this.cache.get(z)??[],D=new Set(B);if(!D.has(R))D.add(R),await this.cache.setForever(z,[...D])}}async put(R,U,z){let B=await this.cache.set(R,U,z);if(B)await this.indexKey(R);return B}async set(R,U,z){return this.put(R,U,z)}async setForever(R,U){let z=await this.cache.setForever(R,U);if(z)await this.indexKey(R);return z}async remember(R,U,z){let B=await this.cache.remember(R,U,z);return await this.indexKey(R),B}async rememberForever(R,U){let z=await this.cache.rememberForever(R,U);return await this.indexKey(R),z}async get(R){return this.cache.get(R)}async has(R){return this.cache.has(R)}async flush(){let R=0;for(let U of this.tags){let z=this.tagKey(U),B=G.flushLocks.get(z),D=B?B.catch(()=>0).then(()=>this.doFlushOne(z)):this.doFlushOne(z);G.flushLocks.set(z,D);try{R+=await D}finally{if(G.flushLocks.get(z)===D)G.flushLocks.delete(z)}}return R}async doFlushOne(R){let U=await this.cache.get(R)??[],z=0;if(U.length>0)z=await this.cache.del(U);return await this.cache.del(R),z}}function _(R={}){let U=V({driver:"memory",stdTTL:R.stdTTL??0,checkPeriod:R.checkPeriod??600,maxKeys:R.maxKeys??-1,useClones:R.useClones??!0,prefix:R.prefix});return new J(U)}function $(R={}){let U=V({driver:"redis",url:R.url,host:R.host??"localhost",port:R.port??6379,password:R.password,database:R.database??0,tls:R.tls??!1,stdTTL:R.stdTTL??0,prefix:R.prefix});return new J(U)}function f(R={}){let U=new L(R);return new J(U)}function w(R,U){if(R==="redis")return $(U);if(R==="singlestore")return f(U);return _(U)}var A=V({driver:"memory",stdTTL:0,checkPeriod:600,maxKeys:-1,useClones:!0}),I=new J(A),b=I;export{I as memory,i as memoize,f as createSingleStoreCache,$ as createRedisCache,_ as createMemoryCache,w as createCache,b as cache,p as WriteThroughPattern,G as TaggedCache,J as StacksCache,L as SingleStoreCacheStore,c as RefreshAheadPattern,v as RedisDriver,y as RateLimiter,u as MultiLevelPattern,h as MemoryDriver,n as CircuitBreaker,m as CacheManager,l as CacheLock,r as CacheInvalidation,d as CacheAsidePattern,g as BatchOperations};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacksjs/cache",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.70.
|
|
4
|
+
"version": "0.70.91",
|
|
5
5
|
"description": "Caching the Stacks way.",
|
|
6
6
|
"author": "Chris Breuer",
|
|
7
7
|
"contributors": [
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"prepublishOnly": "bun run build"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@stacksjs/config": "0.70.
|
|
57
|
+
"@stacksjs/config": "0.70.91",
|
|
58
58
|
"@stacksjs/ts-cache": "^0.1.4"
|
|
59
59
|
}
|
|
60
60
|
}
|