@visualbravo/zenstack-cache 0.0.0
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 +21 -0
- package/README.md +136 -0
- package/dist/index.cjs +3 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +3 -0
- package/dist/plugin.cjs +84 -0
- package/dist/plugin.d.cts +31 -0
- package/dist/plugin.d.mts +31 -0
- package/dist/plugin.mjs +82 -0
- package/dist/providers/memory.cjs +55 -0
- package/dist/providers/memory.d.cts +28 -0
- package/dist/providers/memory.d.mts +28 -0
- package/dist/providers/memory.mjs +55 -0
- package/dist/providers/redis.cjs +68 -0
- package/dist/providers/redis.d.cts +16 -0
- package/dist/providers/redis.d.mts +16 -0
- package/dist/providers/redis.mjs +66 -0
- package/dist/schemas.cjs +15 -0
- package/dist/schemas.d.cts +17 -0
- package/dist/schemas.d.mts +17 -0
- package/dist/schemas.mjs +12 -0
- package/dist/superjson.cjs +14 -0
- package/dist/superjson.d.cts +2 -0
- package/dist/superjson.d.mts +2 -0
- package/dist/superjson.mjs +12 -0
- package/dist/types.cjs +0 -0
- package/dist/types.d.cts +35 -0
- package/dist/types.d.mts +35 -0
- package/dist/types.mjs +1 -0
- package/dist/utils.cjs +20 -0
- package/dist/utils.d.cts +9 -0
- package/dist/utils.d.mts +9 -0
- package/dist/utils.mjs +16 -0
- package/package.json +115 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Visual Bravo LLC
|
|
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/README.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<h1>
|
|
3
|
+
ZenStack Cache
|
|
4
|
+
<small>(beta)</small>
|
|
5
|
+
</h1>
|
|
6
|
+
|
|
7
|
+
Reduce response times and database load with query-level caching integrated with the ZenStack ORM.
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div align="center">
|
|
11
|
+
<a href="https://www.npmjs.com/package/@visualbravo/zenstack-cache?activeTab=versions">
|
|
12
|
+
<img alt="NPM Version" src="https://img.shields.io/npm/v/%40visualbravo%2Fzenstack-cache/latest">
|
|
13
|
+
</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">
|
|
16
|
+
</a>
|
|
17
|
+
<a href="https://discord.gg/Ykhr738dUe">
|
|
18
|
+
<img alt="Join the ZenStack server" src="https://img.shields.io/discord/1035538056146595961">
|
|
19
|
+
</a>
|
|
20
|
+
<a href="https://github.com/visualbravo/zenstack-cache/blob/main/LICENSE">
|
|
21
|
+
<img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-green">
|
|
22
|
+
</a>
|
|
23
|
+
|
|
24
|
+
<p>
|
|
25
|
+
ℹ️ This project is not affiliated with or endorsed by the ZenStack team.
|
|
26
|
+
</p>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
## 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.
|
|
33
|
+
|
|
34
|
+
## Requirements
|
|
35
|
+
|
|
36
|
+
* ZenStack (version >= `canary`)
|
|
37
|
+
* Node.js (version >= `20.0.0`)
|
|
38
|
+
* Redis (version >= `7.0.0`)
|
|
39
|
+
* ℹ️ Only if you intend to use the `RedisCacheProvider`
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install @visualbravo/zenstack-cache
|
|
45
|
+
pnpm add @visualbravo/zenstack-cache
|
|
46
|
+
bun add @visualbravo/zenstack-cache
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Sample Usage
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { schema } from './zenstack/schema'
|
|
53
|
+
import { ZenStackClient } from '@zenstackhq/orm'
|
|
54
|
+
import { defineCachePlugin } from '@visualbravo/zenstack-cache'
|
|
55
|
+
import { RedisCacheProvider } from '@visualbravo/zenstack-cache/providers/redis'
|
|
56
|
+
import { MemoryCacheProvider } from '@visualbravo/zenstack-cache/providers/memory'
|
|
57
|
+
|
|
58
|
+
const client = new ZenStackClient(schema, {
|
|
59
|
+
dialect: ...,
|
|
60
|
+
}).$use(
|
|
61
|
+
defineCachePlugin({
|
|
62
|
+
// Choose only one provider.
|
|
63
|
+
|
|
64
|
+
// 1️⃣
|
|
65
|
+
provider: new RedisCacheProvider({
|
|
66
|
+
url: process.env['REDIS_URL'],
|
|
67
|
+
}),
|
|
68
|
+
|
|
69
|
+
// 2️⃣
|
|
70
|
+
provider: new MemoryCacheProvider(),
|
|
71
|
+
}),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
async function getPostsPublishedByUser(userId: string) {
|
|
75
|
+
const publishedPosts = await client.post.findMany({
|
|
76
|
+
where: {
|
|
77
|
+
published: true,
|
|
78
|
+
authorId: userId,
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
// All of these are optional.
|
|
82
|
+
cache: {
|
|
83
|
+
ttl: 60,
|
|
84
|
+
swr: 120,
|
|
85
|
+
tags: [`user:${userId}`],
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
return publishedPosts
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Invalidation
|
|
94
|
+
|
|
95
|
+
You can easily invalidate multiple cache entries.
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
// Invalidate specific tags.
|
|
99
|
+
await client.$cache.invalidate({
|
|
100
|
+
tags: ['user:1'],
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
// Invalidate everything.
|
|
104
|
+
await client.$cache.invalidateAll()
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Cache Status
|
|
108
|
+
|
|
109
|
+
After performing a query, you can check where the result came from.
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
const publishedPostsStatus = client.$cache.status // 'hit' | 'miss' | 'stale'
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
* `hit` - a cache entry in the `ttl` window was found, and the database was not queried.
|
|
116
|
+
* `miss` - a cache entry was not found, and the database was queried.
|
|
117
|
+
* `stale` - a cache entry in the `swr` window was found, and the database was queried in the background to revalidate it.
|
|
118
|
+
|
|
119
|
+
## Revalidation
|
|
120
|
+
|
|
121
|
+
If the result was stale, you can choose to await its revalidation.
|
|
122
|
+
```typescript
|
|
123
|
+
const revalidatedPublishedPosts = await client.$cache.revalidation as Post[]
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Cache Options
|
|
127
|
+
|
|
128
|
+
* `ttl` reduces response times and database load by serving cached results.
|
|
129
|
+
* `swr` reduces response times by serving cached results, but does not reduce database load because it performs a revalidation in the background after each request.
|
|
130
|
+
|
|
131
|
+
> [!NOTE]
|
|
132
|
+
> The total TTL of a cache entry is equal to its `ttl` + `swr`. The `ttl` window comes first, followed by the `swr` window. You can combine the two options to best suit the needs of your application.
|
|
133
|
+
|
|
134
|
+
## License
|
|
135
|
+
|
|
136
|
+
MIT
|
package/dist/index.cjs
ADDED
package/dist/index.d.cts
ADDED
package/dist/index.d.mts
ADDED
package/dist/index.mjs
ADDED
package/dist/plugin.cjs
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
|
|
2
|
+
const require_schemas = require('./schemas.cjs');
|
|
3
|
+
const require_utils = require('./utils.cjs');
|
|
4
|
+
let _zenstackhq_orm = require("@zenstackhq/orm");
|
|
5
|
+
let stable_hash = require("stable-hash");
|
|
6
|
+
let murmurhash = require("murmurhash");
|
|
7
|
+
murmurhash = require_rolldown_runtime.__toESM(murmurhash);
|
|
8
|
+
|
|
9
|
+
//#region src/plugin.ts
|
|
10
|
+
function lowerCaseFirst(input) {
|
|
11
|
+
return input.charAt(0).toLowerCase() + input.slice(1);
|
|
12
|
+
}
|
|
13
|
+
function defineCachePlugin(pluginOptions) {
|
|
14
|
+
let status = null;
|
|
15
|
+
let revalidation = null;
|
|
16
|
+
return (0, _zenstackhq_orm.definePlugin)({
|
|
17
|
+
id: "cache",
|
|
18
|
+
name: "Cache",
|
|
19
|
+
description: "Optionally caches read queries.",
|
|
20
|
+
queryArgs: { $read: require_schemas.cacheEnvelopeSchema },
|
|
21
|
+
client: { $cache: {
|
|
22
|
+
invalidate: (options) => {
|
|
23
|
+
return pluginOptions.provider.invalidate(options);
|
|
24
|
+
},
|
|
25
|
+
invalidateAll() {
|
|
26
|
+
return pluginOptions.provider.invalidateAll();
|
|
27
|
+
},
|
|
28
|
+
get status() {
|
|
29
|
+
return status;
|
|
30
|
+
},
|
|
31
|
+
get revalidation() {
|
|
32
|
+
return revalidation;
|
|
33
|
+
}
|
|
34
|
+
} },
|
|
35
|
+
onQuery: async ({ args, model, operation, proceed }) => {
|
|
36
|
+
if (args && "cache" in args) {
|
|
37
|
+
const json = (0, stable_hash.stableHash)({
|
|
38
|
+
args,
|
|
39
|
+
model,
|
|
40
|
+
operation
|
|
41
|
+
});
|
|
42
|
+
if (!json) throw new Error(`Failed to serialize cache entry for ${lowerCaseFirst(model)}.${operation}`);
|
|
43
|
+
const cache = pluginOptions.provider;
|
|
44
|
+
const options = args.cache;
|
|
45
|
+
const key = murmurhash.default.v3(json).toString();
|
|
46
|
+
const entry = await cache.get(key);
|
|
47
|
+
if (entry) {
|
|
48
|
+
if (require_utils.entryIsFresh(entry)) {
|
|
49
|
+
status = "hit";
|
|
50
|
+
return entry.result;
|
|
51
|
+
} else if (require_utils.entryIsStale(entry)) {
|
|
52
|
+
revalidation = proceed(args).then(async (result$1) => {
|
|
53
|
+
try {
|
|
54
|
+
await cache.set(key, {
|
|
55
|
+
createdAt: Date.now(),
|
|
56
|
+
options,
|
|
57
|
+
result: result$1
|
|
58
|
+
});
|
|
59
|
+
return result$1;
|
|
60
|
+
} catch (err) {
|
|
61
|
+
console.error(`Failed to cache query result: ${err}`);
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
status = "stale";
|
|
66
|
+
return entry.result;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const result = await proceed(args);
|
|
70
|
+
cache.set(key, {
|
|
71
|
+
createdAt: Date.now(),
|
|
72
|
+
options,
|
|
73
|
+
result
|
|
74
|
+
}).catch((err) => console.error(`Failed to cache query result: ${err}`));
|
|
75
|
+
status = "miss";
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
return proceed(args);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
//#endregion
|
|
84
|
+
exports.defineCachePlugin = defineCachePlugin;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { CacheInvalidationOptions, CachePluginOptions, CacheStatus } from "./types.cjs";
|
|
2
|
+
import * as node_modules__zenstackhq_orm_dist0 from "node_modules/@zenstackhq/orm/dist";
|
|
3
|
+
|
|
4
|
+
//#region src/plugin.d.ts
|
|
5
|
+
declare function defineCachePlugin(pluginOptions: CachePluginOptions): node_modules__zenstackhq_orm_dist0.RuntimePlugin<any, {
|
|
6
|
+
readonly $read: {
|
|
7
|
+
cache?: {
|
|
8
|
+
ttl?: number | undefined;
|
|
9
|
+
swr?: number | undefined;
|
|
10
|
+
tags?: string[] | undefined;
|
|
11
|
+
} | undefined;
|
|
12
|
+
};
|
|
13
|
+
}, {
|
|
14
|
+
readonly $cache: {
|
|
15
|
+
readonly invalidate: (options: CacheInvalidationOptions) => Promise<void>;
|
|
16
|
+
readonly invalidateAll: () => Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Returns the status of the last result returned, or `null`
|
|
19
|
+
* if a result has yet to be returned.
|
|
20
|
+
*/
|
|
21
|
+
readonly status: CacheStatus | null;
|
|
22
|
+
/**
|
|
23
|
+
* Returns a `Promise` that fulfills when the last stale result
|
|
24
|
+
* returned has been revalidated, or `null` if a stale result has
|
|
25
|
+
* yet to be returned.
|
|
26
|
+
*/
|
|
27
|
+
readonly revalidation: Promise<unknown> | null;
|
|
28
|
+
};
|
|
29
|
+
}>;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { defineCachePlugin };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { CacheInvalidationOptions, CachePluginOptions, CacheStatus } from "./types.mjs";
|
|
2
|
+
import * as node_modules__zenstackhq_orm_dist0 from "node_modules/@zenstackhq/orm/dist";
|
|
3
|
+
|
|
4
|
+
//#region src/plugin.d.ts
|
|
5
|
+
declare function defineCachePlugin(pluginOptions: CachePluginOptions): node_modules__zenstackhq_orm_dist0.RuntimePlugin<any, {
|
|
6
|
+
readonly $read: {
|
|
7
|
+
cache?: {
|
|
8
|
+
ttl?: number | undefined;
|
|
9
|
+
swr?: number | undefined;
|
|
10
|
+
tags?: string[] | undefined;
|
|
11
|
+
} | undefined;
|
|
12
|
+
};
|
|
13
|
+
}, {
|
|
14
|
+
readonly $cache: {
|
|
15
|
+
readonly invalidate: (options: CacheInvalidationOptions) => Promise<void>;
|
|
16
|
+
readonly invalidateAll: () => Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Returns the status of the last result returned, or `null`
|
|
19
|
+
* if a result has yet to be returned.
|
|
20
|
+
*/
|
|
21
|
+
readonly status: CacheStatus | null;
|
|
22
|
+
/**
|
|
23
|
+
* Returns a `Promise` that fulfills when the last stale result
|
|
24
|
+
* returned has been revalidated, or `null` if a stale result has
|
|
25
|
+
* yet to be returned.
|
|
26
|
+
*/
|
|
27
|
+
readonly revalidation: Promise<unknown> | null;
|
|
28
|
+
};
|
|
29
|
+
}>;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { defineCachePlugin };
|
package/dist/plugin.mjs
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { cacheEnvelopeSchema } from "./schemas.mjs";
|
|
2
|
+
import { entryIsFresh, entryIsStale } from "./utils.mjs";
|
|
3
|
+
import { definePlugin } from "@zenstackhq/orm";
|
|
4
|
+
import { stableHash } from "stable-hash";
|
|
5
|
+
import murmurhash from "murmurhash";
|
|
6
|
+
|
|
7
|
+
//#region src/plugin.ts
|
|
8
|
+
function lowerCaseFirst(input) {
|
|
9
|
+
return input.charAt(0).toLowerCase() + input.slice(1);
|
|
10
|
+
}
|
|
11
|
+
function defineCachePlugin(pluginOptions) {
|
|
12
|
+
let status = null;
|
|
13
|
+
let revalidation = null;
|
|
14
|
+
return definePlugin({
|
|
15
|
+
id: "cache",
|
|
16
|
+
name: "Cache",
|
|
17
|
+
description: "Optionally caches read queries.",
|
|
18
|
+
queryArgs: { $read: cacheEnvelopeSchema },
|
|
19
|
+
client: { $cache: {
|
|
20
|
+
invalidate: (options) => {
|
|
21
|
+
return pluginOptions.provider.invalidate(options);
|
|
22
|
+
},
|
|
23
|
+
invalidateAll() {
|
|
24
|
+
return pluginOptions.provider.invalidateAll();
|
|
25
|
+
},
|
|
26
|
+
get status() {
|
|
27
|
+
return status;
|
|
28
|
+
},
|
|
29
|
+
get revalidation() {
|
|
30
|
+
return revalidation;
|
|
31
|
+
}
|
|
32
|
+
} },
|
|
33
|
+
onQuery: async ({ args, model, operation, proceed }) => {
|
|
34
|
+
if (args && "cache" in args) {
|
|
35
|
+
const json = stableHash({
|
|
36
|
+
args,
|
|
37
|
+
model,
|
|
38
|
+
operation
|
|
39
|
+
});
|
|
40
|
+
if (!json) throw new Error(`Failed to serialize cache entry for ${lowerCaseFirst(model)}.${operation}`);
|
|
41
|
+
const cache = pluginOptions.provider;
|
|
42
|
+
const options = args.cache;
|
|
43
|
+
const key = murmurhash.v3(json).toString();
|
|
44
|
+
const entry = await cache.get(key);
|
|
45
|
+
if (entry) {
|
|
46
|
+
if (entryIsFresh(entry)) {
|
|
47
|
+
status = "hit";
|
|
48
|
+
return entry.result;
|
|
49
|
+
} else if (entryIsStale(entry)) {
|
|
50
|
+
revalidation = proceed(args).then(async (result$1) => {
|
|
51
|
+
try {
|
|
52
|
+
await cache.set(key, {
|
|
53
|
+
createdAt: Date.now(),
|
|
54
|
+
options,
|
|
55
|
+
result: result$1
|
|
56
|
+
});
|
|
57
|
+
return result$1;
|
|
58
|
+
} catch (err) {
|
|
59
|
+
console.error(`Failed to cache query result: ${err}`);
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
status = "stale";
|
|
64
|
+
return entry.result;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const result = await proceed(args);
|
|
68
|
+
cache.set(key, {
|
|
69
|
+
createdAt: Date.now(),
|
|
70
|
+
options,
|
|
71
|
+
result
|
|
72
|
+
}).catch((err) => console.error(`Failed to cache query result: ${err}`));
|
|
73
|
+
status = "miss";
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
return proceed(args);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
//#endregion
|
|
82
|
+
export { defineCachePlugin };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const require_utils = require('../utils.cjs');
|
|
2
|
+
|
|
3
|
+
//#region src/providers/memory.ts
|
|
4
|
+
var MemoryCacheProvider = class {
|
|
5
|
+
entryStore;
|
|
6
|
+
tagStore;
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.options = options;
|
|
9
|
+
this.entryStore = /* @__PURE__ */ new Map();
|
|
10
|
+
this.tagStore = /* @__PURE__ */ new Map();
|
|
11
|
+
setInterval(() => {
|
|
12
|
+
this.checkExpiration();
|
|
13
|
+
}, (this.options?.checkInterval ?? 60) * 1e3).unref();
|
|
14
|
+
}
|
|
15
|
+
checkExpiration() {
|
|
16
|
+
for (const [key, entry] of this.entryStore) if (require_utils.entryIsExpired(entry)) {
|
|
17
|
+
this.entryStore.delete(key);
|
|
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);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
get(key) {
|
|
26
|
+
return Promise.resolve(this.entryStore.get(key));
|
|
27
|
+
}
|
|
28
|
+
set(key, entry) {
|
|
29
|
+
this.entryStore.set(key, entry);
|
|
30
|
+
if (entry.options.tags) for (const tag of entry.options.tags) {
|
|
31
|
+
let keys = this.tagStore.get(tag);
|
|
32
|
+
if (!keys) {
|
|
33
|
+
keys = /* @__PURE__ */ new Set();
|
|
34
|
+
this.tagStore.set(tag, keys);
|
|
35
|
+
}
|
|
36
|
+
keys.add(key);
|
|
37
|
+
}
|
|
38
|
+
return Promise.resolve();
|
|
39
|
+
}
|
|
40
|
+
invalidate(options) {
|
|
41
|
+
if (options.tags) for (const tag of options.tags) {
|
|
42
|
+
const keys = this.tagStore.get(tag);
|
|
43
|
+
if (keys) for (const key of keys) this.entryStore.delete(key);
|
|
44
|
+
}
|
|
45
|
+
return Promise.resolve();
|
|
46
|
+
}
|
|
47
|
+
invalidateAll() {
|
|
48
|
+
this.entryStore.clear();
|
|
49
|
+
this.tagStore.clear();
|
|
50
|
+
return Promise.resolve();
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
//#endregion
|
|
55
|
+
exports.MemoryCacheProvider = MemoryCacheProvider;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { CacheEntry, CacheInvalidationOptions, CacheProvider } from "../types.cjs";
|
|
2
|
+
|
|
3
|
+
//#region src/providers/memory.d.ts
|
|
4
|
+
declare class MemoryCacheProvider implements CacheProvider {
|
|
5
|
+
private readonly options?;
|
|
6
|
+
private readonly entryStore;
|
|
7
|
+
private readonly tagStore;
|
|
8
|
+
constructor(options?: MemoryCacheOptions | undefined);
|
|
9
|
+
private checkExpiration;
|
|
10
|
+
get(key: string): Promise<CacheEntry | undefined>;
|
|
11
|
+
set(key: string, entry: CacheEntry): Promise<void>;
|
|
12
|
+
invalidate(options: CacheInvalidationOptions): Promise<void>;
|
|
13
|
+
invalidateAll(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
type MemoryCacheOptions = {
|
|
16
|
+
/**
|
|
17
|
+
* How often, in seconds, entries will be checked for expiration.
|
|
18
|
+
*
|
|
19
|
+
* @default 60
|
|
20
|
+
*/
|
|
21
|
+
checkInterval?: number;
|
|
22
|
+
/**
|
|
23
|
+
* Called when an entry has expired via the interval check.
|
|
24
|
+
*/
|
|
25
|
+
onIntervalExpiration?: (entry: CacheEntry) => void;
|
|
26
|
+
};
|
|
27
|
+
//#endregion
|
|
28
|
+
export { MemoryCacheOptions, MemoryCacheProvider };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { CacheEntry, CacheInvalidationOptions, CacheProvider } from "../types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/providers/memory.d.ts
|
|
4
|
+
declare class MemoryCacheProvider implements CacheProvider {
|
|
5
|
+
private readonly options?;
|
|
6
|
+
private readonly entryStore;
|
|
7
|
+
private readonly tagStore;
|
|
8
|
+
constructor(options?: MemoryCacheOptions | undefined);
|
|
9
|
+
private checkExpiration;
|
|
10
|
+
get(key: string): Promise<CacheEntry | undefined>;
|
|
11
|
+
set(key: string, entry: CacheEntry): Promise<void>;
|
|
12
|
+
invalidate(options: CacheInvalidationOptions): Promise<void>;
|
|
13
|
+
invalidateAll(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
type MemoryCacheOptions = {
|
|
16
|
+
/**
|
|
17
|
+
* How often, in seconds, entries will be checked for expiration.
|
|
18
|
+
*
|
|
19
|
+
* @default 60
|
|
20
|
+
*/
|
|
21
|
+
checkInterval?: number;
|
|
22
|
+
/**
|
|
23
|
+
* Called when an entry has expired via the interval check.
|
|
24
|
+
*/
|
|
25
|
+
onIntervalExpiration?: (entry: CacheEntry) => void;
|
|
26
|
+
};
|
|
27
|
+
//#endregion
|
|
28
|
+
export { MemoryCacheOptions, MemoryCacheProvider };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { entryIsExpired } from "../utils.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/providers/memory.ts
|
|
4
|
+
var MemoryCacheProvider = class {
|
|
5
|
+
entryStore;
|
|
6
|
+
tagStore;
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.options = options;
|
|
9
|
+
this.entryStore = /* @__PURE__ */ new Map();
|
|
10
|
+
this.tagStore = /* @__PURE__ */ new Map();
|
|
11
|
+
setInterval(() => {
|
|
12
|
+
this.checkExpiration();
|
|
13
|
+
}, (this.options?.checkInterval ?? 60) * 1e3).unref();
|
|
14
|
+
}
|
|
15
|
+
checkExpiration() {
|
|
16
|
+
for (const [key, entry] of this.entryStore) if (entryIsExpired(entry)) {
|
|
17
|
+
this.entryStore.delete(key);
|
|
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);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
get(key) {
|
|
26
|
+
return Promise.resolve(this.entryStore.get(key));
|
|
27
|
+
}
|
|
28
|
+
set(key, entry) {
|
|
29
|
+
this.entryStore.set(key, entry);
|
|
30
|
+
if (entry.options.tags) for (const tag of entry.options.tags) {
|
|
31
|
+
let keys = this.tagStore.get(tag);
|
|
32
|
+
if (!keys) {
|
|
33
|
+
keys = /* @__PURE__ */ new Set();
|
|
34
|
+
this.tagStore.set(tag, keys);
|
|
35
|
+
}
|
|
36
|
+
keys.add(key);
|
|
37
|
+
}
|
|
38
|
+
return Promise.resolve();
|
|
39
|
+
}
|
|
40
|
+
invalidate(options) {
|
|
41
|
+
if (options.tags) for (const tag of options.tags) {
|
|
42
|
+
const keys = this.tagStore.get(tag);
|
|
43
|
+
if (keys) for (const key of keys) this.entryStore.delete(key);
|
|
44
|
+
}
|
|
45
|
+
return Promise.resolve();
|
|
46
|
+
}
|
|
47
|
+
invalidateAll() {
|
|
48
|
+
this.entryStore.clear();
|
|
49
|
+
this.tagStore.clear();
|
|
50
|
+
return Promise.resolve();
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
//#endregion
|
|
55
|
+
export { MemoryCacheProvider };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
|
|
2
|
+
const require_utils = require('../utils.cjs');
|
|
3
|
+
require('../superjson.cjs');
|
|
4
|
+
let ioredis = require("ioredis");
|
|
5
|
+
let superjson = require("superjson");
|
|
6
|
+
|
|
7
|
+
//#region src/providers/redis.ts
|
|
8
|
+
var RedisCacheProvider = class {
|
|
9
|
+
redis;
|
|
10
|
+
constructor(options) {
|
|
11
|
+
this.redis = new ioredis.Redis(options.url);
|
|
12
|
+
}
|
|
13
|
+
async get(key) {
|
|
14
|
+
const entryJson = await this.redis.get(formatQueryKey(key));
|
|
15
|
+
if (!entryJson) return;
|
|
16
|
+
return superjson.default.parse(entryJson);
|
|
17
|
+
}
|
|
18
|
+
async set(key, entry) {
|
|
19
|
+
const multi = this.redis.multi();
|
|
20
|
+
const formattedKey = formatQueryKey(key);
|
|
21
|
+
multi.set(formattedKey, superjson.default.stringify(entry));
|
|
22
|
+
const totalTtl = require_utils.getTotalTtl(entry);
|
|
23
|
+
if (totalTtl > 0) multi.expire(formattedKey, totalTtl);
|
|
24
|
+
if (entry.options.tags) for (const tag of entry.options.tags) {
|
|
25
|
+
const formattedTagKey = formatTagKey(tag);
|
|
26
|
+
multi.sadd(formattedTagKey, formattedKey);
|
|
27
|
+
if (totalTtl > 0) {
|
|
28
|
+
multi.expire(formattedTagKey, totalTtl, "GT");
|
|
29
|
+
multi.expire(formattedTagKey, totalTtl, "NX");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
await multi.exec();
|
|
33
|
+
}
|
|
34
|
+
async invalidate(options) {
|
|
35
|
+
if (options.tags && options.tags.length > 0) await Promise.all(options.tags.map((tag) => {
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
const stream = this.redis.sscanStream(formatTagKey(tag), { count: 100 });
|
|
38
|
+
stream.on("data", async (keys) => {
|
|
39
|
+
if (keys.length > 1) await this.redis.del(...keys);
|
|
40
|
+
});
|
|
41
|
+
stream.on("error", reject);
|
|
42
|
+
stream.on("end", resolve);
|
|
43
|
+
});
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
async invalidateAll() {
|
|
47
|
+
await new Promise((resolve, reject) => {
|
|
48
|
+
const stream = this.redis.scanStream({
|
|
49
|
+
count: 100,
|
|
50
|
+
match: "zenstack:cache:*"
|
|
51
|
+
});
|
|
52
|
+
stream.on("data", async (keys) => {
|
|
53
|
+
if (keys.length > 1) await this.redis.del(...keys);
|
|
54
|
+
});
|
|
55
|
+
stream.on("error", reject);
|
|
56
|
+
stream.on("end", resolve);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
function formatQueryKey(key) {
|
|
61
|
+
return `zenstack:cache:query:${key}`;
|
|
62
|
+
}
|
|
63
|
+
function formatTagKey(key) {
|
|
64
|
+
return `zenstack:cache:tag:${key}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
//#endregion
|
|
68
|
+
exports.RedisCacheProvider = RedisCacheProvider;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { CacheEntry, CacheInvalidationOptions, CacheProvider } from "../types.cjs";
|
|
2
|
+
|
|
3
|
+
//#region src/providers/redis.d.ts
|
|
4
|
+
declare class RedisCacheProvider implements CacheProvider {
|
|
5
|
+
private readonly redis;
|
|
6
|
+
constructor(options: RedisCacheProviderOptions);
|
|
7
|
+
get(key: string): Promise<CacheEntry | undefined>;
|
|
8
|
+
set(key: string, entry: CacheEntry): Promise<void>;
|
|
9
|
+
invalidate(options: CacheInvalidationOptions): Promise<void>;
|
|
10
|
+
invalidateAll(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
type RedisCacheProviderOptions = {
|
|
13
|
+
url: string;
|
|
14
|
+
};
|
|
15
|
+
//#endregion
|
|
16
|
+
export { RedisCacheProvider, RedisCacheProviderOptions };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { CacheEntry, CacheInvalidationOptions, CacheProvider } from "../types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/providers/redis.d.ts
|
|
4
|
+
declare class RedisCacheProvider implements CacheProvider {
|
|
5
|
+
private readonly redis;
|
|
6
|
+
constructor(options: RedisCacheProviderOptions);
|
|
7
|
+
get(key: string): Promise<CacheEntry | undefined>;
|
|
8
|
+
set(key: string, entry: CacheEntry): Promise<void>;
|
|
9
|
+
invalidate(options: CacheInvalidationOptions): Promise<void>;
|
|
10
|
+
invalidateAll(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
type RedisCacheProviderOptions = {
|
|
13
|
+
url: string;
|
|
14
|
+
};
|
|
15
|
+
//#endregion
|
|
16
|
+
export { RedisCacheProvider, RedisCacheProviderOptions };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { getTotalTtl } from "../utils.mjs";
|
|
2
|
+
import { superjson } from "../superjson.mjs";
|
|
3
|
+
import { Redis } from "ioredis";
|
|
4
|
+
|
|
5
|
+
//#region src/providers/redis.ts
|
|
6
|
+
var RedisCacheProvider = class {
|
|
7
|
+
redis;
|
|
8
|
+
constructor(options) {
|
|
9
|
+
this.redis = new Redis(options.url);
|
|
10
|
+
}
|
|
11
|
+
async get(key) {
|
|
12
|
+
const entryJson = await this.redis.get(formatQueryKey(key));
|
|
13
|
+
if (!entryJson) return;
|
|
14
|
+
return superjson.parse(entryJson);
|
|
15
|
+
}
|
|
16
|
+
async set(key, entry) {
|
|
17
|
+
const multi = this.redis.multi();
|
|
18
|
+
const formattedKey = formatQueryKey(key);
|
|
19
|
+
multi.set(formattedKey, superjson.stringify(entry));
|
|
20
|
+
const totalTtl = getTotalTtl(entry);
|
|
21
|
+
if (totalTtl > 0) multi.expire(formattedKey, totalTtl);
|
|
22
|
+
if (entry.options.tags) for (const tag of entry.options.tags) {
|
|
23
|
+
const formattedTagKey = formatTagKey(tag);
|
|
24
|
+
multi.sadd(formattedTagKey, formattedKey);
|
|
25
|
+
if (totalTtl > 0) {
|
|
26
|
+
multi.expire(formattedTagKey, totalTtl, "GT");
|
|
27
|
+
multi.expire(formattedTagKey, totalTtl, "NX");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
await multi.exec();
|
|
31
|
+
}
|
|
32
|
+
async invalidate(options) {
|
|
33
|
+
if (options.tags && options.tags.length > 0) await Promise.all(options.tags.map((tag) => {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const stream = this.redis.sscanStream(formatTagKey(tag), { count: 100 });
|
|
36
|
+
stream.on("data", async (keys) => {
|
|
37
|
+
if (keys.length > 1) await this.redis.del(...keys);
|
|
38
|
+
});
|
|
39
|
+
stream.on("error", reject);
|
|
40
|
+
stream.on("end", resolve);
|
|
41
|
+
});
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
async invalidateAll() {
|
|
45
|
+
await new Promise((resolve, reject) => {
|
|
46
|
+
const stream = this.redis.scanStream({
|
|
47
|
+
count: 100,
|
|
48
|
+
match: "zenstack:cache:*"
|
|
49
|
+
});
|
|
50
|
+
stream.on("data", async (keys) => {
|
|
51
|
+
if (keys.length > 1) await this.redis.del(...keys);
|
|
52
|
+
});
|
|
53
|
+
stream.on("error", reject);
|
|
54
|
+
stream.on("end", resolve);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
function formatQueryKey(key) {
|
|
59
|
+
return `zenstack:cache:query:${key}`;
|
|
60
|
+
}
|
|
61
|
+
function formatTagKey(key) {
|
|
62
|
+
return `zenstack:cache:tag:${key}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
//#endregion
|
|
66
|
+
export { RedisCacheProvider };
|
package/dist/schemas.cjs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
|
|
2
|
+
let zod = require("zod");
|
|
3
|
+
zod = require_rolldown_runtime.__toESM(zod);
|
|
4
|
+
|
|
5
|
+
//#region src/schemas.ts
|
|
6
|
+
const cacheOptionsSchema = zod.default.strictObject({
|
|
7
|
+
ttl: zod.default.number().min(1).optional(),
|
|
8
|
+
swr: zod.default.number().min(1).optional(),
|
|
9
|
+
tags: zod.default.string().array().optional()
|
|
10
|
+
});
|
|
11
|
+
const cacheEnvelopeSchema = zod.default.object({ cache: cacheOptionsSchema.optional() });
|
|
12
|
+
|
|
13
|
+
//#endregion
|
|
14
|
+
exports.cacheEnvelopeSchema = cacheEnvelopeSchema;
|
|
15
|
+
exports.cacheOptionsSchema = cacheOptionsSchema;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
|
|
3
|
+
//#region src/schemas.d.ts
|
|
4
|
+
declare const cacheOptionsSchema: z.ZodObject<{
|
|
5
|
+
ttl: z.ZodOptional<z.ZodNumber>;
|
|
6
|
+
swr: z.ZodOptional<z.ZodNumber>;
|
|
7
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
8
|
+
}, z.core.$strict>;
|
|
9
|
+
declare const cacheEnvelopeSchema: z.ZodObject<{
|
|
10
|
+
cache: z.ZodOptional<z.ZodObject<{
|
|
11
|
+
ttl: z.ZodOptional<z.ZodNumber>;
|
|
12
|
+
swr: z.ZodOptional<z.ZodNumber>;
|
|
13
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
14
|
+
}, z.core.$strict>>;
|
|
15
|
+
}, z.core.$strip>;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { cacheEnvelopeSchema, cacheOptionsSchema };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
|
|
3
|
+
//#region src/schemas.d.ts
|
|
4
|
+
declare const cacheOptionsSchema: z.ZodObject<{
|
|
5
|
+
ttl: z.ZodOptional<z.ZodNumber>;
|
|
6
|
+
swr: z.ZodOptional<z.ZodNumber>;
|
|
7
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
8
|
+
}, z.core.$strict>;
|
|
9
|
+
declare const cacheEnvelopeSchema: z.ZodObject<{
|
|
10
|
+
cache: z.ZodOptional<z.ZodObject<{
|
|
11
|
+
ttl: z.ZodOptional<z.ZodNumber>;
|
|
12
|
+
swr: z.ZodOptional<z.ZodNumber>;
|
|
13
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
14
|
+
}, z.core.$strict>>;
|
|
15
|
+
}, z.core.$strip>;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { cacheEnvelopeSchema, cacheOptionsSchema };
|
package/dist/schemas.mjs
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
|
|
3
|
+
//#region src/schemas.ts
|
|
4
|
+
const cacheOptionsSchema = z.strictObject({
|
|
5
|
+
ttl: z.number().min(1).optional(),
|
|
6
|
+
swr: z.number().min(1).optional(),
|
|
7
|
+
tags: z.string().array().optional()
|
|
8
|
+
});
|
|
9
|
+
const cacheEnvelopeSchema = z.object({ cache: cacheOptionsSchema.optional() });
|
|
10
|
+
|
|
11
|
+
//#endregion
|
|
12
|
+
export { cacheEnvelopeSchema, cacheOptionsSchema };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
|
|
2
|
+
let decimal_js = require("decimal.js");
|
|
3
|
+
let superjson = require("superjson");
|
|
4
|
+
superjson = require_rolldown_runtime.__toESM(superjson);
|
|
5
|
+
|
|
6
|
+
//#region src/superjson.ts
|
|
7
|
+
superjson.default.registerCustom({
|
|
8
|
+
isApplicable: (v) => decimal_js.Decimal.isDecimal(v),
|
|
9
|
+
serialize: (v) => v.toJSON(),
|
|
10
|
+
deserialize: (v) => new decimal_js.Decimal(v)
|
|
11
|
+
}, "decimal.js");
|
|
12
|
+
|
|
13
|
+
//#endregion
|
|
14
|
+
exports.superjson = superjson.default;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Decimal } from "decimal.js";
|
|
2
|
+
import superjson from "superjson";
|
|
3
|
+
|
|
4
|
+
//#region src/superjson.ts
|
|
5
|
+
superjson.registerCustom({
|
|
6
|
+
isApplicable: (v) => Decimal.isDecimal(v),
|
|
7
|
+
serialize: (v) => v.toJSON(),
|
|
8
|
+
deserialize: (v) => new Decimal(v)
|
|
9
|
+
}, "decimal.js");
|
|
10
|
+
|
|
11
|
+
//#endregion
|
|
12
|
+
export { superjson };
|
package/dist/types.cjs
ADDED
|
File without changes
|
package/dist/types.d.cts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { cacheEnvelopeSchema, cacheOptionsSchema } from "./schemas.cjs";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
|
|
4
|
+
//#region src/types.d.ts
|
|
5
|
+
type CacheEnvelope = z.infer<typeof cacheEnvelopeSchema>;
|
|
6
|
+
type CacheOptions = z.infer<typeof cacheOptionsSchema>;
|
|
7
|
+
interface CacheProvider {
|
|
8
|
+
get: (key: string) => Promise<CacheEntry | undefined>;
|
|
9
|
+
set: (key: string, entry: CacheEntry) => Promise<void>;
|
|
10
|
+
invalidate: (options: CacheInvalidationOptions) => Promise<void>;
|
|
11
|
+
invalidateAll: () => Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
type CacheInvalidationOptions = {
|
|
14
|
+
tags?: string[];
|
|
15
|
+
};
|
|
16
|
+
type CacheEntry = {
|
|
17
|
+
/**
|
|
18
|
+
* In unix epoch milliseconds.
|
|
19
|
+
*/
|
|
20
|
+
createdAt: number;
|
|
21
|
+
/**
|
|
22
|
+
* The caching options that were passed to the query.
|
|
23
|
+
*/
|
|
24
|
+
options: CacheOptions;
|
|
25
|
+
/**
|
|
26
|
+
* The result of executing the query.
|
|
27
|
+
*/
|
|
28
|
+
result: unknown;
|
|
29
|
+
};
|
|
30
|
+
type CachePluginOptions = {
|
|
31
|
+
provider: CacheProvider;
|
|
32
|
+
};
|
|
33
|
+
type CacheStatus = 'hit' | 'miss' | 'stale';
|
|
34
|
+
//#endregion
|
|
35
|
+
export { CacheEntry, CacheEnvelope, CacheInvalidationOptions, CacheOptions, CachePluginOptions, CacheProvider, CacheStatus };
|
package/dist/types.d.mts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { cacheEnvelopeSchema, cacheOptionsSchema } from "./schemas.mjs";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
|
|
4
|
+
//#region src/types.d.ts
|
|
5
|
+
type CacheEnvelope = z.infer<typeof cacheEnvelopeSchema>;
|
|
6
|
+
type CacheOptions = z.infer<typeof cacheOptionsSchema>;
|
|
7
|
+
interface CacheProvider {
|
|
8
|
+
get: (key: string) => Promise<CacheEntry | undefined>;
|
|
9
|
+
set: (key: string, entry: CacheEntry) => Promise<void>;
|
|
10
|
+
invalidate: (options: CacheInvalidationOptions) => Promise<void>;
|
|
11
|
+
invalidateAll: () => Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
type CacheInvalidationOptions = {
|
|
14
|
+
tags?: string[];
|
|
15
|
+
};
|
|
16
|
+
type CacheEntry = {
|
|
17
|
+
/**
|
|
18
|
+
* In unix epoch milliseconds.
|
|
19
|
+
*/
|
|
20
|
+
createdAt: number;
|
|
21
|
+
/**
|
|
22
|
+
* The caching options that were passed to the query.
|
|
23
|
+
*/
|
|
24
|
+
options: CacheOptions;
|
|
25
|
+
/**
|
|
26
|
+
* The result of executing the query.
|
|
27
|
+
*/
|
|
28
|
+
result: unknown;
|
|
29
|
+
};
|
|
30
|
+
type CachePluginOptions = {
|
|
31
|
+
provider: CacheProvider;
|
|
32
|
+
};
|
|
33
|
+
type CacheStatus = 'hit' | 'miss' | 'stale';
|
|
34
|
+
//#endregion
|
|
35
|
+
export { CacheEntry, CacheEnvelope, CacheInvalidationOptions, CacheOptions, CachePluginOptions, CacheProvider, CacheStatus };
|
package/dist/types.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/utils.cjs
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/utils.ts
|
|
3
|
+
function getTotalTtl(entry) {
|
|
4
|
+
return (entry.options.ttl ?? 0) + (entry.options.swr ?? 0);
|
|
5
|
+
}
|
|
6
|
+
function entryIsFresh(entry) {
|
|
7
|
+
return entry.options.ttl ? Date.now() <= entry.createdAt + (entry.options.ttl ?? 0) * 1e3 : false;
|
|
8
|
+
}
|
|
9
|
+
function entryIsStale(entry) {
|
|
10
|
+
return entry.options.swr ? Date.now() <= entry.createdAt + getTotalTtl(entry) * 1e3 : false;
|
|
11
|
+
}
|
|
12
|
+
function entryIsExpired(entry) {
|
|
13
|
+
return Date.now() > entry.createdAt + getTotalTtl(entry) * 1e3;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
//#endregion
|
|
17
|
+
exports.entryIsExpired = entryIsExpired;
|
|
18
|
+
exports.entryIsFresh = entryIsFresh;
|
|
19
|
+
exports.entryIsStale = entryIsStale;
|
|
20
|
+
exports.getTotalTtl = getTotalTtl;
|
package/dist/utils.d.cts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { CacheEntry } from "./types.cjs";
|
|
2
|
+
|
|
3
|
+
//#region src/utils.d.ts
|
|
4
|
+
declare function getTotalTtl(entry: CacheEntry): number;
|
|
5
|
+
declare function entryIsFresh(entry: CacheEntry): boolean;
|
|
6
|
+
declare function entryIsStale(entry: CacheEntry): boolean;
|
|
7
|
+
declare function entryIsExpired(entry: CacheEntry): boolean;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { entryIsExpired, entryIsFresh, entryIsStale, getTotalTtl };
|
package/dist/utils.d.mts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { CacheEntry } from "./types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/utils.d.ts
|
|
4
|
+
declare function getTotalTtl(entry: CacheEntry): number;
|
|
5
|
+
declare function entryIsFresh(entry: CacheEntry): boolean;
|
|
6
|
+
declare function entryIsStale(entry: CacheEntry): boolean;
|
|
7
|
+
declare function entryIsExpired(entry: CacheEntry): boolean;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { entryIsExpired, entryIsFresh, entryIsStale, getTotalTtl };
|
package/dist/utils.mjs
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/utils.ts
|
|
2
|
+
function getTotalTtl(entry) {
|
|
3
|
+
return (entry.options.ttl ?? 0) + (entry.options.swr ?? 0);
|
|
4
|
+
}
|
|
5
|
+
function entryIsFresh(entry) {
|
|
6
|
+
return entry.options.ttl ? Date.now() <= entry.createdAt + (entry.options.ttl ?? 0) * 1e3 : false;
|
|
7
|
+
}
|
|
8
|
+
function entryIsStale(entry) {
|
|
9
|
+
return entry.options.swr ? Date.now() <= entry.createdAt + getTotalTtl(entry) * 1e3 : false;
|
|
10
|
+
}
|
|
11
|
+
function entryIsExpired(entry) {
|
|
12
|
+
return Date.now() > entry.createdAt + getTotalTtl(entry) * 1e3;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
//#endregion
|
|
16
|
+
export { entryIsExpired, entryIsFresh, entryIsStale, getTotalTtl };
|
package/package.json
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@visualbravo/zenstack-cache",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.cts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"@zenstack-cache/source": "./src/index.ts",
|
|
11
|
+
"require": "./dist/index.cjs",
|
|
12
|
+
"import": "./dist/index.mjs"
|
|
13
|
+
},
|
|
14
|
+
"./plugin": {
|
|
15
|
+
"@zenstack-cache/source": "./src/plugin.ts",
|
|
16
|
+
"require": "./dist/plugin.cjs",
|
|
17
|
+
"import": "./dist/plugin.mjs"
|
|
18
|
+
},
|
|
19
|
+
"./providers/memory": {
|
|
20
|
+
"@zenstack-cache/source": "./src/providers/memory.ts",
|
|
21
|
+
"require": "./dist/providers/memory.cjs",
|
|
22
|
+
"import": "./dist/providers/memory.mjs"
|
|
23
|
+
},
|
|
24
|
+
"./providers/redis": {
|
|
25
|
+
"@zenstack-cache/source": "./src/providers/redis.ts",
|
|
26
|
+
"require": "./dist/providers/redis.cjs",
|
|
27
|
+
"import": "./dist/providers/redis.mjs"
|
|
28
|
+
},
|
|
29
|
+
"./schemas": {
|
|
30
|
+
"@zenstack-cache/source": "./src/schemas.ts",
|
|
31
|
+
"require": "./dist/schemas.cjs",
|
|
32
|
+
"import": "./dist/schemas.mjs"
|
|
33
|
+
},
|
|
34
|
+
"./superjson": {
|
|
35
|
+
"@zenstack-cache/source": "./src/superjson.ts",
|
|
36
|
+
"require": "./dist/superjson.cjs",
|
|
37
|
+
"import": "./dist/superjson.mjs"
|
|
38
|
+
},
|
|
39
|
+
"./types": {
|
|
40
|
+
"@zenstack-cache/source": "./src/types.ts",
|
|
41
|
+
"require": "./dist/types.cjs",
|
|
42
|
+
"import": "./dist/types.mjs"
|
|
43
|
+
},
|
|
44
|
+
"./utils": {
|
|
45
|
+
"@zenstack-cache/source": "./src/utils.ts",
|
|
46
|
+
"require": "./dist/utils.cjs",
|
|
47
|
+
"import": "./dist/utils.mjs"
|
|
48
|
+
},
|
|
49
|
+
"./package.json": "./package.json"
|
|
50
|
+
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"exports": {
|
|
53
|
+
".": {
|
|
54
|
+
"require": "./dist/index.cjs",
|
|
55
|
+
"import": "./dist/index.mjs"
|
|
56
|
+
},
|
|
57
|
+
"./plugin": {
|
|
58
|
+
"require": "./dist/plugin.cjs",
|
|
59
|
+
"import": "./dist/plugin.mjs"
|
|
60
|
+
},
|
|
61
|
+
"./providers/memory": {
|
|
62
|
+
"require": "./dist/providers/memory.cjs",
|
|
63
|
+
"import": "./dist/providers/memory.mjs"
|
|
64
|
+
},
|
|
65
|
+
"./providers/redis": {
|
|
66
|
+
"require": "./dist/providers/redis.cjs",
|
|
67
|
+
"import": "./dist/providers/redis.mjs"
|
|
68
|
+
},
|
|
69
|
+
"./schemas": {
|
|
70
|
+
"require": "./dist/schemas.cjs",
|
|
71
|
+
"import": "./dist/schemas.mjs"
|
|
72
|
+
},
|
|
73
|
+
"./superjson": {
|
|
74
|
+
"require": "./dist/superjson.cjs",
|
|
75
|
+
"import": "./dist/superjson.mjs"
|
|
76
|
+
},
|
|
77
|
+
"./types": {
|
|
78
|
+
"require": "./dist/types.cjs",
|
|
79
|
+
"import": "./dist/types.mjs"
|
|
80
|
+
},
|
|
81
|
+
"./utils": {
|
|
82
|
+
"require": "./dist/utils.cjs",
|
|
83
|
+
"import": "./dist/utils.mjs"
|
|
84
|
+
},
|
|
85
|
+
"./package.json": "./package.json"
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"scripts": {
|
|
89
|
+
"build": "tsdown",
|
|
90
|
+
"watch": "tsdown --watch",
|
|
91
|
+
"lint": "oxlint",
|
|
92
|
+
"lint:fix": "oxlint --fix --fix-suggestions",
|
|
93
|
+
"format:fix": "oxfmt",
|
|
94
|
+
"test": "vitest run",
|
|
95
|
+
"test:watch": "vitest --watch"
|
|
96
|
+
},
|
|
97
|
+
"dependencies": {
|
|
98
|
+
"decimal.js": "^10.6.0",
|
|
99
|
+
"ioredis": "^5.0.0",
|
|
100
|
+
"murmurhash": "^2.0.1",
|
|
101
|
+
"stable-hash": "^0.0.6",
|
|
102
|
+
"superjson": "^2.2.2",
|
|
103
|
+
"zod": "^4.1.0"
|
|
104
|
+
},
|
|
105
|
+
"devDependencies": {
|
|
106
|
+
"@zenstack-cache/config": "0.0.0",
|
|
107
|
+
"kysely": "~0.28.8",
|
|
108
|
+
"better-sqlite3": "12.6.2",
|
|
109
|
+
"@types/better-sqlite3": "7.6.13"
|
|
110
|
+
},
|
|
111
|
+
"peerDependencies": {
|
|
112
|
+
"@zenstackhq/orm": "canary"
|
|
113
|
+
},
|
|
114
|
+
"packageManager": "bun@1.3.6"
|
|
115
|
+
}
|