@stacksjs/cache 0.70.44 → 0.70.53

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/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ # MIT License
2
+
3
+ Copyright (c) 2023 Open Web Foundation
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- import{createCache as q}from"@stacksjs/ts-cache";import{CacheManager as X,MemoryDriver as Y,RedisDriver as Z}from"@stacksjs/ts-cache";import{CacheAsidePattern as $,MultiLevelPattern as j,RefreshAheadPattern as P,WriteThroughPattern as M,BatchOperations as O,CacheInvalidation as b,CacheLock as r,CircuitBreaker as S,memoize as K,RateLimiter as u}from"@stacksjs/ts-cache";class f{manager;inflight=new Map;constructor(m){this.manager=m}async get(m){return await this.manager.get(m)}async mget(m){return await this.manager.mget(m)}async set(m,w,x){return await this.manager.set(m,w,x)}async mset(m){return await this.manager.mset(m)}async setForever(m,w){return await this.manager.set(m,w,0)}async getOrSet(m,w,x){let A=await this.manager.get(m);if(A!==void 0)return A;let L=this.inflight.get(m);if(L)return await L;let U=(async()=>{try{return await this.manager.fetch(m,w,x)}finally{this.inflight.delete(m)}})();return this.inflight.set(m,U),await U}async remember(m,w,x){return await this.getOrSet(m,x,w)}async rememberForever(m,w){return this.getOrSet(m,w,0)}async del(m){return await this.manager.del(m)}async has(m){return await this.manager.has(m)}async missing(m){return!await this.manager.has(m)}async remove(m){await this.manager.del(m)}async deleteMany(m){return await this.manager.del(m)}async clear(){await this.manager.flush()}async flush(){await this.manager.flush()}async keys(m){return await this.manager.keys(m)}async getTtl(m){return await this.manager.getTtl(m)}async ttl(m,w){return await this.manager.ttl(m,w)}async take(m){return await this.manager.take(m)}async getStats(){let m=await this.manager.getStats(),w=m.hits+m.misses;return{hits:m.hits,misses:m.misses,keys:m.keys,size:m.ksize+m.vsize,hitRate:w>0?m.hits/w:0}}async close(){await this.manager.close()}async disconnect(){await this.manager.close()}get cacheManager(){return this.manager}tags(m){return new R(this,m)}}class R{cache;tags;static TAG_PREFIX="__stacks_tag__:";static flushLocks=new Map;constructor(m,w){this.cache=m;this.tags=w;if(!w||w.length===0)throw Error("cache.tags(...) requires at least one tag")}tagKey(m){return`${R.TAG_PREFIX}${m}`}async indexKey(m){for(let w of this.tags){let x=this.tagKey(w),A=await this.cache.get(x)??[],L=new Set(A);if(!L.has(m))L.add(m),await this.cache.setForever(x,[...L])}}async put(m,w,x){let A=await this.cache.set(m,w,x);if(A)await this.indexKey(m);return A}async set(m,w,x){return this.put(m,w,x)}async setForever(m,w){let x=await this.cache.setForever(m,w);if(x)await this.indexKey(m);return x}async remember(m,w,x){let A=await this.cache.remember(m,w,x);return await this.indexKey(m),A}async rememberForever(m,w){let x=await this.cache.rememberForever(m,w);return await this.indexKey(m),x}async get(m){return this.cache.get(m)}async has(m){return this.cache.has(m)}async flush(){let m=0;for(let w of this.tags){let x=this.tagKey(w),A=R.flushLocks.get(x);if(A)await A.catch(()=>{});let L=(async()=>{let U=await this.cache.get(x)??[],z=0;if(U.length>0)z=await this.cache.del(U);return await this.cache.del(x),z})();R.flushLocks.set(x,L);try{m+=await L}finally{if(R.flushLocks.get(x)===L)R.flushLocks.delete(x)}}return m}}function B(m={}){let w=q({driver:"memory",stdTTL:m.stdTTL??0,checkPeriod:m.checkPeriod??600,maxKeys:m.maxKeys??-1,useClones:m.useClones??!0,prefix:m.prefix});return new f(w)}function D(m={}){let w=q({driver:"redis",url:m.url,host:m.host??"localhost",port:m.port??6379,password:m.password,database:m.database??0,tls:m.tls??!1,stdTTL:m.stdTTL??0,prefix:m.prefix});return new f(w)}function N(m,w){if(m==="redis")return D(w);return B(w)}var E=q({driver:"memory",stdTTL:0,checkPeriod:600,maxKeys:-1,useClones:!0}),F=new f(E),Q=F;export{F as memory,K as memoize,D as createRedisCache,B as createMemoryCache,N as createCache,Q as cache,M as WriteThroughPattern,R as TaggedCache,f as StacksCache,P as RefreshAheadPattern,Z as RedisDriver,u as RateLimiter,j as MultiLevelPattern,Y as MemoryDriver,S as CircuitBreaker,X as CacheManager,r as CacheLock,b as CacheInvalidation,$ as CacheAsidePattern,O as BatchOperations};
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};
@@ -1,5 +1,9 @@
1
1
  import { CacheManager, MemoryDriver, RedisDriver } from '@stacksjs/ts-cache';
2
+ import { SingleStoreCacheStore } from './singlestore';
2
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';
3
7
  /**
4
8
  * Create a memory cache driver
5
9
  */
@@ -8,11 +12,21 @@ export declare function createMemoryCache(options?: MemoryOptions): CacheDriver;
8
12
  * Create a Redis cache driver
9
13
  */
10
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;
11
24
  /**
12
25
  * Create a cache driver based on the specified type
13
26
  */
14
27
  export declare function createCache(driver: 'memory', options?: MemoryOptions): CacheDriver;
15
28
  export declare function createCache(driver: 'redis', options?: RedisOptions): CacheDriver;
29
+ export declare function createCache(driver: 'singlestore', options?: SingleStoreCacheOptions): CacheDriver;
16
30
  /**
17
31
  * Default memory cache instance
18
32
  */
@@ -52,7 +66,7 @@ export declare interface RedisOptions {
52
66
  * that matches the Stacks CacheDriver interface.
53
67
  */
54
68
  export declare class StacksCache implements CacheDriver {
55
- constructor(manager: CacheManager);
69
+ constructor(manager: CacheManager, opts?: { inflightTimeoutMs?: number });
56
70
  get<T>(key: string): Promise<T | undefined>;
57
71
  mget<T>(keys: string[]): Promise<Record<string, T>>;
58
72
  set<T>(key: string, value: T, ttl?: number): Promise<boolean>;
@@ -104,6 +118,7 @@ export declare class TaggedCache {
104
118
  has(key: string): Promise<boolean>;
105
119
  flush(): Promise<number>;
106
120
  }
121
+ export { SingleStoreCacheStore } from './singlestore';
107
122
  // Re-export ts-cache utilities for advanced usage
108
123
  export {
109
124
  CacheManager,
@@ -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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/cache",
3
3
  "type": "module",
4
- "version": "0.70.44",
4
+ "version": "0.70.53",
5
5
  "description": "Caching the Stacks way.",
6
6
  "author": "Chris Breuer",
7
7
  "contributors": [
@@ -50,7 +50,7 @@
50
50
  "prepublishOnly": "bun run build"
51
51
  },
52
52
  "devDependencies": {
53
- "@stacksjs/config": "^0.70.44",
53
+ "@stacksjs/config": "0.70.53",
54
54
  "@stacksjs/ts-cache": "^0.1.4"
55
55
  }
56
56
  }