@visualbravo/zenstack-cache 0.0.0 → 1.0.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/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
  <div align="center">
2
2
  <h1>
3
3
  ZenStack Cache
4
- <small>(beta)</small>
5
4
  </h1>
6
5
 
7
6
  Reduce response times and database load with query-level caching integrated with the ZenStack ORM.
@@ -11,13 +10,16 @@
11
10
  <a href="https://www.npmjs.com/package/@visualbravo/zenstack-cache?activeTab=versions">
12
11
  <img alt="NPM Version" src="https://img.shields.io/npm/v/%40visualbravo%2Fzenstack-cache/latest">
13
12
  </a>
14
- <a>
15
- <img alt="GitHub Actions Workflow Status" src="https://img.shields.io/github/actions/workflow/status/visualbravo/zenstack-cache/build-and-test.yaml">
13
+ <a href="https://www.npmjs.com/package/@visualbravo/zenstack-cache">
14
+ <img alt="NPM Downloads" src="https://img.shields.io/npm/dm/%40visualbravo%2Fzenstack-cache">
16
15
  </a>
17
- <a href="https://discord.gg/Ykhr738dUe">
18
- <img alt="Join the ZenStack server" src="https://img.shields.io/discord/1035538056146595961">
16
+ <a href="https://github.com/visualbravo/zenstack-cache/actions/workflows/build-and-test.yaml?query=branch%3Adev++">
17
+ <img alt="Build Status" src="https://img.shields.io/github/actions/workflow/status/visualbravo/zenstack-cache/build-and-test.yaml">
19
18
  </a>
20
- <a href="https://github.com/visualbravo/zenstack-cache/blob/main/LICENSE">
19
+ <a href="https://discord.gg/2PaRSu7X">
20
+ <img alt="Join the ZenStack Cache channel" src="https://img.shields.io/discord/1035538056146595961">
21
+ </a>
22
+ <a href="https://github.com/visualbravo/zenstack-cache/blob/76a2de03245c26841b04525dd8b424a8799d654c/LICENSE">
21
23
  <img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-green">
22
24
  </a>
23
25
 
@@ -27,13 +29,14 @@
27
29
  </div>
28
30
 
29
31
  ## Features
30
- * 🌐 **Redis Caching:** Centralizes your caching to scale across different systems.
31
- * đŸ–Ĩī¸ **Memory Caching:** Simplifies caching when scale is not a concern.
32
- * 🛟 **Type-safe:** The caching options appear in the intellisense for all read queries.
32
+ * 🌐 **Redis Cache:** A central cache to scale across different systems.
33
+ * đŸ–Ĩī¸ **Memory Cache:** A simple cache when scale is not a concern.
34
+ * 🛟 **Type-safety:** The caching options appear in the intellisense for all read queries.
35
+ * đŸˇī¸ **Tag-based Invalidation:** Easily invalidate multiple related cache entries.
33
36
 
34
37
  ## Requirements
35
38
 
36
- * ZenStack (version >= `canary`)
39
+ * ZenStack (version >= `3.3.0`)
37
40
  * Node.js (version >= `20.0.0`)
38
41
  * Redis (version >= `7.0.0`)
39
42
  * â„šī¸ Only if you intend to use the `RedisCacheProvider`
@@ -16,10 +16,12 @@ var MemoryCacheProvider = class {
16
16
  for (const [key, entry] of this.entryStore) if (require_utils.entryIsExpired(entry)) {
17
17
  this.entryStore.delete(key);
18
18
  this.options?.onIntervalExpiration?.(entry);
19
- }
20
- for (const [tag, keys] of this.tagStore) {
21
- for (const key of keys) if (!this.entryStore.has(key)) keys.delete(key);
22
- if (keys.size === 0) this.tagStore.delete(tag);
19
+ if (entry.options.tags) for (const tag of entry.options.tags) {
20
+ const keys = this.tagStore.get(tag);
21
+ if (!keys) continue;
22
+ keys.delete(key);
23
+ if (keys.size === 0) this.tagStore.delete(tag);
24
+ }
23
25
  }
24
26
  }
25
27
  get(key) {
@@ -40,7 +42,9 @@ var MemoryCacheProvider = class {
40
42
  invalidate(options) {
41
43
  if (options.tags) for (const tag of options.tags) {
42
44
  const keys = this.tagStore.get(tag);
43
- if (keys) for (const key of keys) this.entryStore.delete(key);
45
+ if (!keys) continue;
46
+ for (const key of keys) this.entryStore.delete(key);
47
+ this.tagStore.delete(tag);
44
48
  }
45
49
  return Promise.resolve();
46
50
  }
@@ -16,10 +16,12 @@ var MemoryCacheProvider = class {
16
16
  for (const [key, entry] of this.entryStore) if (entryIsExpired(entry)) {
17
17
  this.entryStore.delete(key);
18
18
  this.options?.onIntervalExpiration?.(entry);
19
- }
20
- for (const [tag, keys] of this.tagStore) {
21
- for (const key of keys) if (!this.entryStore.has(key)) keys.delete(key);
22
- if (keys.size === 0) this.tagStore.delete(tag);
19
+ if (entry.options.tags) for (const tag of entry.options.tags) {
20
+ const keys = this.tagStore.get(tag);
21
+ if (!keys) continue;
22
+ keys.delete(key);
23
+ if (keys.size === 0) this.tagStore.delete(tag);
24
+ }
23
25
  }
24
26
  }
25
27
  get(key) {
@@ -40,7 +42,9 @@ var MemoryCacheProvider = class {
40
42
  invalidate(options) {
41
43
  if (options.tags) for (const tag of options.tags) {
42
44
  const keys = this.tagStore.get(tag);
43
- if (keys) for (const key of keys) this.entryStore.delete(key);
45
+ if (!keys) continue;
46
+ for (const key of keys) this.entryStore.delete(key);
47
+ this.tagStore.delete(tag);
44
48
  }
45
49
  return Promise.resolve();
46
50
  }
@@ -11,30 +11,30 @@ var RedisCacheProvider = class {
11
11
  this.redis = new ioredis.Redis(options.url);
12
12
  }
13
13
  async get(key) {
14
- const entryJson = await this.redis.get(formatQueryKey(key));
14
+ const entryJson = await this.redis.get(makeQueryKey(key));
15
15
  if (!entryJson) return;
16
16
  return superjson.default.parse(entryJson);
17
17
  }
18
18
  async set(key, entry) {
19
19
  const multi = this.redis.multi();
20
- const formattedKey = formatQueryKey(key);
21
- multi.set(formattedKey, superjson.default.stringify(entry));
20
+ const queryKey = makeQueryKey(key);
21
+ multi.set(queryKey, superjson.default.stringify(entry));
22
22
  const totalTtl = require_utils.getTotalTtl(entry);
23
- if (totalTtl > 0) multi.expire(formattedKey, totalTtl);
23
+ if (totalTtl > 0) multi.expire(queryKey, totalTtl);
24
24
  if (entry.options.tags) for (const tag of entry.options.tags) {
25
- const formattedTagKey = formatTagKey(tag);
26
- multi.sadd(formattedTagKey, formattedKey);
25
+ const tagKey = makeTagKey(tag);
26
+ multi.sadd(tagKey, queryKey);
27
27
  if (totalTtl > 0) {
28
- multi.expire(formattedTagKey, totalTtl, "GT");
29
- multi.expire(formattedTagKey, totalTtl, "NX");
30
- }
28
+ multi.expire(tagKey, totalTtl, "GT");
29
+ multi.expire(tagKey, totalTtl, "NX");
30
+ } else multi.persist(tagKey);
31
31
  }
32
32
  await multi.exec();
33
33
  }
34
34
  async invalidate(options) {
35
35
  if (options.tags && options.tags.length > 0) await Promise.all(options.tags.map((tag) => {
36
36
  return new Promise((resolve, reject) => {
37
- const stream = this.redis.sscanStream(formatTagKey(tag), { count: 100 });
37
+ const stream = this.redis.sscanStream(makeTagKey(tag), { count: 100 });
38
38
  stream.on("data", async (keys) => {
39
39
  if (keys.length > 1) await this.redis.del(...keys);
40
40
  });
@@ -57,10 +57,10 @@ var RedisCacheProvider = class {
57
57
  });
58
58
  }
59
59
  };
60
- function formatQueryKey(key) {
60
+ function makeQueryKey(key) {
61
61
  return `zenstack:cache:query:${key}`;
62
62
  }
63
- function formatTagKey(key) {
63
+ function makeTagKey(key) {
64
64
  return `zenstack:cache:tag:${key}`;
65
65
  }
66
66
 
@@ -9,30 +9,30 @@ var RedisCacheProvider = class {
9
9
  this.redis = new Redis(options.url);
10
10
  }
11
11
  async get(key) {
12
- const entryJson = await this.redis.get(formatQueryKey(key));
12
+ const entryJson = await this.redis.get(makeQueryKey(key));
13
13
  if (!entryJson) return;
14
14
  return superjson.parse(entryJson);
15
15
  }
16
16
  async set(key, entry) {
17
17
  const multi = this.redis.multi();
18
- const formattedKey = formatQueryKey(key);
19
- multi.set(formattedKey, superjson.stringify(entry));
18
+ const queryKey = makeQueryKey(key);
19
+ multi.set(queryKey, superjson.stringify(entry));
20
20
  const totalTtl = getTotalTtl(entry);
21
- if (totalTtl > 0) multi.expire(formattedKey, totalTtl);
21
+ if (totalTtl > 0) multi.expire(queryKey, totalTtl);
22
22
  if (entry.options.tags) for (const tag of entry.options.tags) {
23
- const formattedTagKey = formatTagKey(tag);
24
- multi.sadd(formattedTagKey, formattedKey);
23
+ const tagKey = makeTagKey(tag);
24
+ multi.sadd(tagKey, queryKey);
25
25
  if (totalTtl > 0) {
26
- multi.expire(formattedTagKey, totalTtl, "GT");
27
- multi.expire(formattedTagKey, totalTtl, "NX");
28
- }
26
+ multi.expire(tagKey, totalTtl, "GT");
27
+ multi.expire(tagKey, totalTtl, "NX");
28
+ } else multi.persist(tagKey);
29
29
  }
30
30
  await multi.exec();
31
31
  }
32
32
  async invalidate(options) {
33
33
  if (options.tags && options.tags.length > 0) await Promise.all(options.tags.map((tag) => {
34
34
  return new Promise((resolve, reject) => {
35
- const stream = this.redis.sscanStream(formatTagKey(tag), { count: 100 });
35
+ const stream = this.redis.sscanStream(makeTagKey(tag), { count: 100 });
36
36
  stream.on("data", async (keys) => {
37
37
  if (keys.length > 1) await this.redis.del(...keys);
38
38
  });
@@ -55,10 +55,10 @@ var RedisCacheProvider = class {
55
55
  });
56
56
  }
57
57
  };
58
- function formatQueryKey(key) {
58
+ function makeQueryKey(key) {
59
59
  return `zenstack:cache:query:${key}`;
60
60
  }
61
- function formatTagKey(key) {
61
+ function makeTagKey(key) {
62
62
  return `zenstack:cache:tag:${key}`;
63
63
  }
64
64
 
package/dist/schemas.cjs CHANGED
@@ -4,8 +4,8 @@ zod = require_rolldown_runtime.__toESM(zod);
4
4
 
5
5
  //#region src/schemas.ts
6
6
  const cacheOptionsSchema = zod.default.strictObject({
7
- ttl: zod.default.number().min(1).optional(),
8
- swr: zod.default.number().min(1).optional(),
7
+ ttl: zod.default.int().positive().optional(),
8
+ swr: zod.default.int().positive().optional(),
9
9
  tags: zod.default.string().array().optional()
10
10
  });
11
11
  const cacheEnvelopeSchema = zod.default.object({ cache: cacheOptionsSchema.optional() });
@@ -2,14 +2,14 @@ import z from "zod";
2
2
 
3
3
  //#region src/schemas.d.ts
4
4
  declare const cacheOptionsSchema: z.ZodObject<{
5
- ttl: z.ZodOptional<z.ZodNumber>;
6
- swr: z.ZodOptional<z.ZodNumber>;
5
+ ttl: z.ZodOptional<z.ZodInt>;
6
+ swr: z.ZodOptional<z.ZodInt>;
7
7
  tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
8
8
  }, z.core.$strict>;
9
9
  declare const cacheEnvelopeSchema: z.ZodObject<{
10
10
  cache: z.ZodOptional<z.ZodObject<{
11
- ttl: z.ZodOptional<z.ZodNumber>;
12
- swr: z.ZodOptional<z.ZodNumber>;
11
+ ttl: z.ZodOptional<z.ZodInt>;
12
+ swr: z.ZodOptional<z.ZodInt>;
13
13
  tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
14
14
  }, z.core.$strict>>;
15
15
  }, z.core.$strip>;
@@ -2,14 +2,14 @@ import z from "zod";
2
2
 
3
3
  //#region src/schemas.d.ts
4
4
  declare const cacheOptionsSchema: z.ZodObject<{
5
- ttl: z.ZodOptional<z.ZodNumber>;
6
- swr: z.ZodOptional<z.ZodNumber>;
5
+ ttl: z.ZodOptional<z.ZodInt>;
6
+ swr: z.ZodOptional<z.ZodInt>;
7
7
  tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
8
8
  }, z.core.$strict>;
9
9
  declare const cacheEnvelopeSchema: z.ZodObject<{
10
10
  cache: z.ZodOptional<z.ZodObject<{
11
- ttl: z.ZodOptional<z.ZodNumber>;
12
- swr: z.ZodOptional<z.ZodNumber>;
11
+ ttl: z.ZodOptional<z.ZodInt>;
12
+ swr: z.ZodOptional<z.ZodInt>;
13
13
  tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
14
14
  }, z.core.$strict>>;
15
15
  }, z.core.$strip>;
package/dist/schemas.mjs CHANGED
@@ -2,8 +2,8 @@ import z from "zod";
2
2
 
3
3
  //#region src/schemas.ts
4
4
  const cacheOptionsSchema = z.strictObject({
5
- ttl: z.number().min(1).optional(),
6
- swr: z.number().min(1).optional(),
5
+ ttl: z.int().positive().optional(),
6
+ swr: z.int().positive().optional(),
7
7
  tags: z.string().array().optional()
8
8
  });
9
9
  const cacheEnvelopeSchema = z.object({ cache: cacheOptionsSchema.optional() });
package/dist/utils.cjs CHANGED
@@ -4,13 +4,15 @@ function getTotalTtl(entry) {
4
4
  return (entry.options.ttl ?? 0) + (entry.options.swr ?? 0);
5
5
  }
6
6
  function entryIsFresh(entry) {
7
- return entry.options.ttl ? Date.now() <= entry.createdAt + (entry.options.ttl ?? 0) * 1e3 : false;
7
+ return entry.options.ttl ? Date.now() <= entry.createdAt + (entry.options.ttl ?? 0) * 1e3 : !entry.options.swr;
8
8
  }
9
9
  function entryIsStale(entry) {
10
10
  return entry.options.swr ? Date.now() <= entry.createdAt + getTotalTtl(entry) * 1e3 : false;
11
11
  }
12
12
  function entryIsExpired(entry) {
13
- return Date.now() > entry.createdAt + getTotalTtl(entry) * 1e3;
13
+ const totalTtl = getTotalTtl(entry);
14
+ if (totalTtl === 0) return false;
15
+ return Date.now() > entry.createdAt + totalTtl * 1e3;
14
16
  }
15
17
 
16
18
  //#endregion
package/dist/utils.mjs CHANGED
@@ -3,13 +3,15 @@ function getTotalTtl(entry) {
3
3
  return (entry.options.ttl ?? 0) + (entry.options.swr ?? 0);
4
4
  }
5
5
  function entryIsFresh(entry) {
6
- return entry.options.ttl ? Date.now() <= entry.createdAt + (entry.options.ttl ?? 0) * 1e3 : false;
6
+ return entry.options.ttl ? Date.now() <= entry.createdAt + (entry.options.ttl ?? 0) * 1e3 : !entry.options.swr;
7
7
  }
8
8
  function entryIsStale(entry) {
9
9
  return entry.options.swr ? Date.now() <= entry.createdAt + getTotalTtl(entry) * 1e3 : false;
10
10
  }
11
11
  function entryIsExpired(entry) {
12
- return Date.now() > entry.createdAt + getTotalTtl(entry) * 1e3;
12
+ const totalTtl = getTotalTtl(entry);
13
+ if (totalTtl === 0) return false;
14
+ return Date.now() > entry.createdAt + totalTtl * 1e3;
13
15
  }
14
16
 
15
17
  //#endregion
package/package.json CHANGED
@@ -1,10 +1,20 @@
1
1
  {
2
2
  "name": "@visualbravo/zenstack-cache",
3
- "version": "0.0.0",
3
+ "version": "1.0.1",
4
+ "license": "MIT",
5
+ "repository": "github:visualbravo/zenstack-cache",
4
6
  "type": "module",
5
7
  "main": "./dist/index.cjs",
6
8
  "module": "./dist/index.mjs",
7
9
  "types": "./dist/index.d.cts",
10
+ "keywords": [
11
+ "zenstack",
12
+ "cache",
13
+ "caching",
14
+ "prisma",
15
+ "accelerate",
16
+ "orm"
17
+ ],
8
18
  "exports": {
9
19
  ".": {
10
20
  "@zenstack-cache/source": "./src/index.ts",
@@ -103,13 +113,13 @@
103
113
  "zod": "^4.1.0"
104
114
  },
105
115
  "devDependencies": {
116
+ "@types/better-sqlite3": "7.6.13",
106
117
  "@zenstack-cache/config": "0.0.0",
107
- "kysely": "~0.28.8",
108
118
  "better-sqlite3": "12.6.2",
109
- "@types/better-sqlite3": "7.6.13"
119
+ "kysely": "~0.28.8"
110
120
  },
111
121
  "peerDependencies": {
112
122
  "@zenstackhq/orm": "canary"
113
123
  },
114
- "packageManager": "bun@1.3.6"
124
+ "packageManager": "bun@1.3.8"
115
125
  }