@smartive/datocms-utils 3.0.0-next.6 → 3.0.0-next.8
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 +20 -20
- package/dist/cache-tags/index.js.map +1 -0
- package/dist/cache-tags/provider/neon.d.ts +33 -0
- package/dist/cache-tags/provider/neon.js +44 -0
- package/dist/cache-tags/provider/neon.js.map +1 -0
- package/dist/cache-tags/provider/noop.d.ts +12 -0
- package/dist/cache-tags/provider/noop.js +24 -0
- package/dist/cache-tags/provider/noop.js.map +1 -0
- package/dist/cache-tags/provider/redis.d.ts +26 -0
- package/dist/cache-tags/provider/redis.js +48 -0
- package/dist/cache-tags/provider/redis.js.map +1 -0
- package/dist/{cache → cache-tags}/types.d.ts +3 -3
- package/dist/{cache → cache-tags}/types.js.map +1 -1
- package/dist/cache-tags/utils.js.map +1 -0
- package/package.json +13 -13
- package/src/cache-tags/provider/neon.ts +80 -0
- package/src/cache-tags/provider/noop.ts +32 -0
- package/src/cache-tags/provider/redis.ts +76 -0
- package/src/{cache → cache-tags}/types.ts +3 -3
- package/dist/cache/index.js.map +0 -1
- package/dist/cache/provider/neon.d.ts +0 -28
- package/dist/cache/provider/neon.js +0 -46
- package/dist/cache/provider/neon.js.map +0 -1
- package/dist/cache/provider/noop.d.ts +0 -9
- package/dist/cache/provider/noop.js +0 -32
- package/dist/cache/provider/noop.js.map +0 -1
- package/dist/cache/provider/redis.d.ts +0 -21
- package/dist/cache/provider/redis.js +0 -52
- package/dist/cache/provider/redis.js.map +0 -1
- package/dist/cache/utils.js.map +0 -1
- package/src/cache/provider/neon.ts +0 -82
- package/src/cache/provider/noop.ts +0 -41
- package/src/cache/provider/redis.ts +0 -80
- /package/dist/{cache → cache-tags}/index.d.ts +0 -0
- /package/dist/{cache → cache-tags}/index.js +0 -0
- /package/dist/{cache → cache-tags}/types.js +0 -0
- /package/dist/{cache → cache-tags}/utils.d.ts +0 -0
- /package/dist/{cache → cache-tags}/utils.js +0 -0
- /package/src/{cache → cache-tags}/index.ts +0 -0
- /package/src/{cache → cache-tags}/utils.ts +0 -0
package/README.md
CHANGED
|
@@ -53,7 +53,7 @@ const tags = parseXCacheTagsResponseHeader('tag-a tag-2 other-tag');
|
|
|
53
53
|
|
|
54
54
|
#### Storage Providers
|
|
55
55
|
|
|
56
|
-
The package provides two storage backends for cache tags: **Neon (Postgres)** and **Redis**. Both implement the same `
|
|
56
|
+
The package provides two storage backends for cache tags: **Neon (Postgres)** and **Redis**. Both implement the same `DatoCacheTagsProvider` interface.
|
|
57
57
|
|
|
58
58
|
##### Neon (Postgres) Provider
|
|
59
59
|
|
|
@@ -80,24 +80,24 @@ npm install @neondatabase/serverless
|
|
|
80
80
|
3. Create and use the store:
|
|
81
81
|
|
|
82
82
|
```typescript
|
|
83
|
-
import {
|
|
83
|
+
import { NeonDatoCacheTagsProvider } from '@smartive/datocms-utils/cache-tags/neon';
|
|
84
84
|
|
|
85
|
-
const
|
|
85
|
+
const provider = new NeonDatoCacheTagsProvider({
|
|
86
86
|
connectionString: process.env.DATABASE_URL!,
|
|
87
87
|
table: 'query_cache_tags',
|
|
88
88
|
});
|
|
89
89
|
|
|
90
90
|
// Store cache tags for a query
|
|
91
|
-
await
|
|
91
|
+
await provider.storeQueryCacheTags(queryId, ['item:42', 'product']);
|
|
92
92
|
|
|
93
93
|
// Find queries that reference specific tags
|
|
94
|
-
const queries = await
|
|
94
|
+
const queries = await provider.queriesReferencingCacheTags(['item:42']);
|
|
95
95
|
|
|
96
96
|
// Delete specific cache tags
|
|
97
|
-
await
|
|
97
|
+
await provider.deleteCacheTags(['item:42']);
|
|
98
98
|
|
|
99
99
|
// Clear all cache tags
|
|
100
|
-
await
|
|
100
|
+
await provider.truncateCacheTags();
|
|
101
101
|
```
|
|
102
102
|
|
|
103
103
|
##### Redis Provider
|
|
@@ -112,21 +112,21 @@ Use Redis to store cache tag mappings with better performance for high-traffic a
|
|
|
112
112
|
npm install ioredis
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
-
2. Create and use the
|
|
115
|
+
2. Create and use the provider:
|
|
116
116
|
|
|
117
117
|
```typescript
|
|
118
|
-
import {
|
|
118
|
+
import { RedisDatoCacheTagsProvider } from '@smartive/datocms-utils/cache-tags/redis';
|
|
119
119
|
|
|
120
|
-
const
|
|
120
|
+
const provider = new RedisDatoCacheTagsProvider({
|
|
121
121
|
url: process.env.REDIS_URL!,
|
|
122
122
|
keyPrefix: 'prod:', // Optional: namespace for multi-environment setups
|
|
123
123
|
});
|
|
124
124
|
|
|
125
125
|
// Same API as Neon provider
|
|
126
|
-
await
|
|
126
|
+
await provider.storeQueryCacheTags(queryId, ['item:42', 'product']);
|
|
127
127
|
const queries = await store.queriesReferencingCacheTags(['item:42']);
|
|
128
|
-
await
|
|
129
|
-
await
|
|
128
|
+
await provider.deleteCacheTags(['item:42']);
|
|
129
|
+
await provider.truncateCacheTags();
|
|
130
130
|
```
|
|
131
131
|
|
|
132
132
|
**Redis connection string examples:**
|
|
@@ -142,7 +142,7 @@ REDIS_URL=redis://username:password@redis-host:6379
|
|
|
142
142
|
REDIS_URL=redis://localhost:6379
|
|
143
143
|
```
|
|
144
144
|
|
|
145
|
-
#### `
|
|
145
|
+
#### `DatoCacheTagsProvider` Interface
|
|
146
146
|
|
|
147
147
|
Both providers implement:
|
|
148
148
|
|
|
@@ -155,9 +155,9 @@ Both providers implement:
|
|
|
155
155
|
|
|
156
156
|
```typescript
|
|
157
157
|
import { generateQueryId, parseXCacheTagsResponseHeader } from '@smartive/datocms-utils/cache';
|
|
158
|
-
import {
|
|
158
|
+
import { RedisDatoCacheTagsProvider } from '@smartive/datocms-utils/cache-tags/redis';
|
|
159
159
|
|
|
160
|
-
const
|
|
160
|
+
const provider = new RedisDatoCacheTagsProvider({
|
|
161
161
|
url: process.env.REDIS_URL!,
|
|
162
162
|
keyPrefix: 'myapp:',
|
|
163
163
|
});
|
|
@@ -165,12 +165,12 @@ const store = createCacheTagsStore({
|
|
|
165
165
|
// After making a DatoCMS query
|
|
166
166
|
const queryId = generateQueryId(query, variables);
|
|
167
167
|
const cacheTags = parseXCacheTagsResponseHeader(response.headers['x-cache-tags']);
|
|
168
|
-
await
|
|
168
|
+
await provider.storeQueryCacheTags(queryId, cacheTags);
|
|
169
169
|
|
|
170
170
|
// When handling DatoCMS webhook for cache invalidation
|
|
171
|
-
const affectedQueries = await
|
|
171
|
+
const affectedQueries = await provider.queriesReferencingCacheTags(webhook.entity.attributes.tags);
|
|
172
172
|
// Revalidate affected queries...
|
|
173
|
-
await
|
|
173
|
+
await provider.deleteCacheTags(webhook.entity.attributes.tags);
|
|
174
174
|
```
|
|
175
175
|
|
|
176
176
|
## TypeScript Types
|
|
@@ -179,7 +179,7 @@ The package includes TypeScript types for DatoCMS webhooks and cache tags:
|
|
|
179
179
|
|
|
180
180
|
- `CacheTag`: A branded type for cache tags, ensuring type safety
|
|
181
181
|
- `CacheTagsInvalidateWebhook`: Type definition for DatoCMS cache tag invalidation webhook payloads
|
|
182
|
-
- `
|
|
182
|
+
- `DatoCacheTagsProvider`: Interface for cache tag storage implementations
|
|
183
183
|
|
|
184
184
|
## License
|
|
185
185
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cache-tags/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type CacheTag, type DatoCacheTagsProvider } from '../types.js';
|
|
2
|
+
type NeonDatoCacheTagsProviderConfig = {
|
|
3
|
+
/**
|
|
4
|
+
* Neon connection string. You can find it in the "Connection" tab of your Neon project dashboard.
|
|
5
|
+
* Has the format `postgresql://user:pass@host/db`
|
|
6
|
+
*/
|
|
7
|
+
readonly connectionUrl: string;
|
|
8
|
+
/**
|
|
9
|
+
* Name of the table where cache tags will be stored. The table must have the following schema:
|
|
10
|
+
*
|
|
11
|
+
* ```sql
|
|
12
|
+
* CREATE TABLE your_table_name (
|
|
13
|
+
* query_id TEXT NOT NULL,
|
|
14
|
+
* cache_tag TEXT NOT NULL,
|
|
15
|
+
* PRIMARY KEY (query_id, cache_tag)
|
|
16
|
+
* );
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
readonly table: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* A `DatoCacheTagsProvider` implementation that uses Neon as the storage backend.
|
|
23
|
+
*/
|
|
24
|
+
export declare class NeonDatoCacheTagsProvider implements DatoCacheTagsProvider {
|
|
25
|
+
private readonly sql;
|
|
26
|
+
private readonly table;
|
|
27
|
+
constructor({ connectionUrl, table }: NeonDatoCacheTagsProviderConfig);
|
|
28
|
+
storeQueryCacheTags(queryId: string, cacheTags: CacheTag[]): Promise<void>;
|
|
29
|
+
queriesReferencingCacheTags(cacheTags: CacheTag[]): Promise<string[]>;
|
|
30
|
+
deleteCacheTags(cacheTags: CacheTag[]): Promise<number>;
|
|
31
|
+
truncateCacheTags(): Promise<number>;
|
|
32
|
+
}
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { neon } from '@neondatabase/serverless';
|
|
2
|
+
/**
|
|
3
|
+
* A `DatoCacheTagsProvider` implementation that uses Neon as the storage backend.
|
|
4
|
+
*/
|
|
5
|
+
export class NeonDatoCacheTagsProvider {
|
|
6
|
+
sql;
|
|
7
|
+
table;
|
|
8
|
+
constructor({ connectionUrl, table }) {
|
|
9
|
+
this.sql = neon(connectionUrl, { fullResults: true });
|
|
10
|
+
this.table = table;
|
|
11
|
+
}
|
|
12
|
+
async storeQueryCacheTags(queryId, cacheTags) {
|
|
13
|
+
if (!cacheTags?.length) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const tags = cacheTags.flatMap((_, i) => [queryId, cacheTags[i]]);
|
|
17
|
+
const placeholders = cacheTags.map((_, i) => `($${2 * i + 1}, $${2 * i + 2})`).join(',');
|
|
18
|
+
await this.sql.query(`INSERT INTO ${this.table} VALUES ${placeholders} ON CONFLICT DO NOTHING`, tags);
|
|
19
|
+
}
|
|
20
|
+
async queriesReferencingCacheTags(cacheTags) {
|
|
21
|
+
if (!cacheTags?.length) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
const placeholders = cacheTags.map((_, i) => `$${i + 1}`).join(',');
|
|
25
|
+
const { rows } = await this.sql.query(`SELECT DISTINCT query_id FROM ${this.table} WHERE cache_tag IN (${placeholders})`, cacheTags);
|
|
26
|
+
return rows.reduce((queryIds, row) => {
|
|
27
|
+
if (typeof row.query_id === 'string') {
|
|
28
|
+
queryIds.push(row.query_id);
|
|
29
|
+
}
|
|
30
|
+
return queryIds;
|
|
31
|
+
}, []);
|
|
32
|
+
}
|
|
33
|
+
async deleteCacheTags(cacheTags) {
|
|
34
|
+
if (cacheTags.length === 0) {
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
const placeholders = cacheTags.map((_, i) => `$${i + 1}`).join(',');
|
|
38
|
+
return (await this.sql.query(`DELETE FROM ${this.table} WHERE cache_tag IN (${placeholders})`, cacheTags)).rowCount ?? 0;
|
|
39
|
+
}
|
|
40
|
+
async truncateCacheTags() {
|
|
41
|
+
return (await this.sql.query(`DELETE FROM ${this.table}`)).rowCount ?? 0;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=neon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"neon.js","sourceRoot":"","sources":["../../../src/cache-tags/provider/neon.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAuBhD;;GAEG;AACH,MAAM,OAAO,yBAAyB;IACnB,GAAG,CAAC;IACJ,KAAK,CAAC;IAEvB,YAAY,EAAE,aAAa,EAAE,KAAK,EAAmC;QACnE,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,aAAa,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAEM,KAAK,CAAC,mBAAmB,CAAC,OAAe,EAAE,SAAqB;QACrE,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC;YACvB,OAAO;QACT,CAAC;QAED,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAClE,MAAM,YAAY,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAEzF,MAAM,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,eAAe,IAAI,CAAC,KAAK,WAAW,YAAY,yBAAyB,EAAE,IAAI,CAAC,CAAC;IACxG,CAAC;IAEM,KAAK,CAAC,2BAA2B,CAAC,SAAqB;QAC5D,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC;YACvB,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,YAAY,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAEpE,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,KAAK,CACnC,iCAAiC,IAAI,CAAC,KAAK,wBAAwB,YAAY,GAAG,EAClF,SAAS,CACV,CAAC;QAEF,OAAO,IAAI,CAAC,MAAM,CAAW,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE;YAC7C,IAAI,OAAO,GAAG,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;gBACrC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAC9B,CAAC;YAED,OAAO,QAAQ,CAAC;QAClB,CAAC,EAAE,EAAE,CAAC,CAAC;IACT,CAAC;IAEM,KAAK,CAAC,eAAe,CAAC,SAAqB;QAChD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,OAAO,CAAC,CAAC;QACX,CAAC;QACD,MAAM,YAAY,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAEpE,OAAO,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,eAAe,IAAI,CAAC,KAAK,wBAAwB,YAAY,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC;IAC3H,CAAC;IAEM,KAAK,CAAC,iBAAiB;QAC5B,OAAO,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,eAAe,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC;IAC3E,CAAC;CACF"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type CacheTag, type DatoCacheTagsProvider } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* A `DatoCacheTagsProvider` implementation that does not perform any actual storage operations.
|
|
4
|
+
*
|
|
5
|
+
* _Note: This implementation is useful for testing purposes or when you want to disable caching without changing the code that interacts with the cache._
|
|
6
|
+
*/
|
|
7
|
+
export declare class NoopDatoCacheTagsProvider implements DatoCacheTagsProvider {
|
|
8
|
+
storeQueryCacheTags(queryId: string, cacheTags: CacheTag[]): Promise<void>;
|
|
9
|
+
queriesReferencingCacheTags(cacheTags: CacheTag[]): Promise<string[]>;
|
|
10
|
+
deleteCacheTags(cacheTags: CacheTag[]): Promise<number>;
|
|
11
|
+
truncateCacheTags(): Promise<number>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `DatoCacheTagsProvider` implementation that does not perform any actual storage operations.
|
|
3
|
+
*
|
|
4
|
+
* _Note: This implementation is useful for testing purposes or when you want to disable caching without changing the code that interacts with the cache._
|
|
5
|
+
*/
|
|
6
|
+
export class NoopDatoCacheTagsProvider {
|
|
7
|
+
async storeQueryCacheTags(queryId, cacheTags) {
|
|
8
|
+
console.debug('-- storeQueryCacheTags called', { queryId, cacheTags });
|
|
9
|
+
return Promise.resolve();
|
|
10
|
+
}
|
|
11
|
+
async queriesReferencingCacheTags(cacheTags) {
|
|
12
|
+
console.debug('-- queriesReferencingCacheTags called', { cacheTags });
|
|
13
|
+
return Promise.resolve([]);
|
|
14
|
+
}
|
|
15
|
+
async deleteCacheTags(cacheTags) {
|
|
16
|
+
console.debug('-- deleteCacheTags called', { cacheTags });
|
|
17
|
+
return Promise.resolve(0);
|
|
18
|
+
}
|
|
19
|
+
async truncateCacheTags() {
|
|
20
|
+
console.debug('-- truncateCacheTags called');
|
|
21
|
+
return Promise.resolve(0);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=noop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"noop.js","sourceRoot":"","sources":["../../../src/cache-tags/provider/noop.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,OAAO,yBAAyB;IAC7B,KAAK,CAAC,mBAAmB,CAAC,OAAe,EAAE,SAAqB;QACrE,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;QAEvE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC3B,CAAC;IAEM,KAAK,CAAC,2BAA2B,CAAC,SAAqB;QAC5D,OAAO,CAAC,KAAK,CAAC,uCAAuC,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;QAEtE,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC7B,CAAC;IAEM,KAAK,CAAC,eAAe,CAAC,SAAqB;QAChD,OAAO,CAAC,KAAK,CAAC,2BAA2B,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;QAE1D,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5B,CAAC;IAEM,KAAK,CAAC,iBAAiB;QAC5B,OAAO,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC;QAE7C,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5B,CAAC;CACF"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type CacheTag, type DatoCacheTagsProvider } from '../types.js';
|
|
2
|
+
type RedisDatoCacheTagsProviderConfig = {
|
|
3
|
+
/**
|
|
4
|
+
* Redis connection string. For example, `redis://user:pass@host:port/db`.
|
|
5
|
+
*/
|
|
6
|
+
readonly connectionUrl: string;
|
|
7
|
+
/**
|
|
8
|
+
* Optional prefix for Redis keys. If provided, all keys used to store cache tags will be prefixed with this value.
|
|
9
|
+
* This can be useful to avoid key collisions if the same Redis instance is used for multiple purposes.
|
|
10
|
+
* For example, if you set `keyPrefix` to `'myapp:'`, a cache tag like `'tag1'` will be stored under the key `'myapp:tag1'`.
|
|
11
|
+
*/
|
|
12
|
+
readonly keyPrefix?: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* A `DatoCacheTagsProvider` implementation that uses Redis as the storage backend.
|
|
16
|
+
*/
|
|
17
|
+
export declare class RedisDatoCacheTagsProvider implements DatoCacheTagsProvider {
|
|
18
|
+
private readonly redis;
|
|
19
|
+
private readonly keyPrefix;
|
|
20
|
+
constructor({ connectionUrl, keyPrefix }: RedisDatoCacheTagsProviderConfig);
|
|
21
|
+
storeQueryCacheTags(queryId: string, cacheTags: CacheTag[]): Promise<void>;
|
|
22
|
+
queriesReferencingCacheTags(cacheTags: CacheTag[]): Promise<string[]>;
|
|
23
|
+
deleteCacheTags(cacheTags: CacheTag[]): Promise<number>;
|
|
24
|
+
truncateCacheTags(): Promise<number>;
|
|
25
|
+
}
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Redis } from 'ioredis';
|
|
2
|
+
/**
|
|
3
|
+
* A `DatoCacheTagsProvider` implementation that uses Redis as the storage backend.
|
|
4
|
+
*/
|
|
5
|
+
export class RedisDatoCacheTagsProvider {
|
|
6
|
+
redis;
|
|
7
|
+
keyPrefix;
|
|
8
|
+
constructor({ connectionUrl, keyPrefix }) {
|
|
9
|
+
this.redis = new Redis(connectionUrl, {
|
|
10
|
+
maxRetriesPerRequest: 3,
|
|
11
|
+
lazyConnect: true,
|
|
12
|
+
});
|
|
13
|
+
this.keyPrefix = keyPrefix ?? '';
|
|
14
|
+
}
|
|
15
|
+
async storeQueryCacheTags(queryId, cacheTags) {
|
|
16
|
+
if (!cacheTags?.length) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const pipeline = this.redis.pipeline();
|
|
20
|
+
for (const tag of cacheTags) {
|
|
21
|
+
pipeline.sadd(`${this.keyPrefix}${tag}`, queryId);
|
|
22
|
+
}
|
|
23
|
+
await pipeline.exec();
|
|
24
|
+
}
|
|
25
|
+
async queriesReferencingCacheTags(cacheTags) {
|
|
26
|
+
if (!cacheTags?.length) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
const keys = cacheTags.map((tag) => `${this.keyPrefix}${tag}`);
|
|
30
|
+
return this.redis.sunion(...keys);
|
|
31
|
+
}
|
|
32
|
+
async deleteCacheTags(cacheTags) {
|
|
33
|
+
if (!cacheTags?.length) {
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
const keys = cacheTags.map((tag) => `${this.keyPrefix}${tag}`);
|
|
37
|
+
return this.redis.del(...keys);
|
|
38
|
+
}
|
|
39
|
+
async truncateCacheTags() {
|
|
40
|
+
const pattern = `${this.keyPrefix}*`;
|
|
41
|
+
const keys = await this.redis.keys(pattern);
|
|
42
|
+
if (keys.length === 0) {
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
return await this.redis.del(...keys);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=redis.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis.js","sourceRoot":"","sources":["../../../src/cache-tags/provider/redis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAgBhC;;GAEG;AACH,MAAM,OAAO,0BAA0B;IACpB,KAAK,CAAC;IACN,SAAS,CAAC;IAE3B,YAAY,EAAE,aAAa,EAAE,SAAS,EAAoC;QACxE,IAAI,CAAC,KAAK,GAAG,IAAI,KAAK,CAAC,aAAa,EAAE;YACpC,oBAAoB,EAAE,CAAC;YACvB,WAAW,EAAE,IAAI;SAClB,CAAC,CAAC;QACH,IAAI,CAAC,SAAS,GAAG,SAAS,IAAI,EAAE,CAAC;IACnC,CAAC;IAEM,KAAK,CAAC,mBAAmB,CAAC,OAAe,EAAE,SAAqB;QACrE,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC;YACvB,OAAO;QACT,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;QAEvC,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;YAC5B,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,SAAS,GAAG,GAAG,EAAE,EAAE,OAAO,CAAC,CAAC;QACpD,CAAC;QAED,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IACxB,CAAC;IAEM,KAAK,CAAC,2BAA2B,CAAC,SAAqB;QAC5D,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC;YACvB,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC,CAAC;QAE/D,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC;IACpC,CAAC;IAEM,KAAK,CAAC,eAAe,CAAC,SAAqB;QAChD,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC;YACvB,OAAO,CAAC,CAAC;QACX,CAAC;QAED,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC,CAAC;QAE/D,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IACjC,CAAC;IAEM,KAAK,CAAC,iBAAiB;QAC5B,MAAM,OAAO,GAAG,GAAG,IAAI,CAAC,SAAS,GAAG,CAAC;QACrC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAE5C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtB,OAAO,CAAC,CAAC;QACX,CAAC;QAED,OAAO,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IACvC,CAAC;CACF"}
|
|
@@ -24,9 +24,9 @@ export type CacheTagsInvalidateWebhook = {
|
|
|
24
24
|
};
|
|
25
25
|
};
|
|
26
26
|
/**
|
|
27
|
-
* Configuration object for creating a `
|
|
27
|
+
* Configuration object for creating a `DatoCacheTagsProvider` implementation.
|
|
28
28
|
*/
|
|
29
|
-
export
|
|
29
|
+
export interface DatoCacheTagsProvider {
|
|
30
30
|
/**
|
|
31
31
|
* Stores the cache tags of a query.
|
|
32
32
|
*
|
|
@@ -60,4 +60,4 @@ export type CacheTagsStore = {
|
|
|
60
60
|
* ⚠️ **Warning**: This will delete all cache tag data. Use with caution!
|
|
61
61
|
*/
|
|
62
62
|
truncateCacheTags(): Promise<number>;
|
|
63
|
-
}
|
|
63
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/cache/types.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/cache-tags/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/cache-tags/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAqB,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,MAAsB,EAAE,EAAE,CACtE,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAe,CAAC,CAAC;AAE3D;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAuB,QAAsB,EAAE,SAAsB,EAAU,EAAE;IAC9G,OAAO,UAAU,CAAC,MAAM,CAAC;SACtB,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;SACvB,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;SACvC,MAAM,CAAC,KAAK,CAAC,CAAC;AACnB,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smartive/datocms-utils",
|
|
3
|
-
"version": "3.0.0-next.
|
|
3
|
+
"version": "3.0.0-next.8",
|
|
4
4
|
"description": "A set of utilities and helpers to work with DatoCMS in a Next.js project.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"source": "./src/index.ts",
|
|
@@ -9,21 +9,21 @@
|
|
|
9
9
|
"types": "./dist/index.d.ts",
|
|
10
10
|
"import": "./dist/index.js"
|
|
11
11
|
},
|
|
12
|
-
"./cache": {
|
|
13
|
-
"types": "./dist/cache/index.d.ts",
|
|
14
|
-
"import": "./dist/cache/index.js"
|
|
12
|
+
"./cache-tags": {
|
|
13
|
+
"types": "./dist/cache-tags/index.d.ts",
|
|
14
|
+
"import": "./dist/cache-tags/index.js"
|
|
15
15
|
},
|
|
16
|
-
"./cache/redis": {
|
|
17
|
-
"types": "./dist/cache/provider/redis.d.ts",
|
|
18
|
-
"import": "./dist/cache/provider/redis.js"
|
|
16
|
+
"./cache-tags/redis": {
|
|
17
|
+
"types": "./dist/cache-tags/provider/redis.d.ts",
|
|
18
|
+
"import": "./dist/cache-tags/provider/redis.js"
|
|
19
19
|
},
|
|
20
|
-
"./cache/neon": {
|
|
21
|
-
"types": "./dist/cache/provider/neon.d.ts",
|
|
22
|
-
"import": "./dist/cache/provider/neon.js"
|
|
20
|
+
"./cache-tags/neon": {
|
|
21
|
+
"types": "./dist/cache-tags/provider/neon.d.ts",
|
|
22
|
+
"import": "./dist/cache-tags/provider/neon.js"
|
|
23
23
|
},
|
|
24
|
-
"./cache/noop": {
|
|
25
|
-
"types": "./dist/cache/provider/noop.d.ts",
|
|
26
|
-
"import": "./dist/cache/provider/noop.js"
|
|
24
|
+
"./cache-tags/noop": {
|
|
25
|
+
"types": "./dist/cache-tags/provider/noop.d.ts",
|
|
26
|
+
"import": "./dist/cache-tags/provider/noop.js"
|
|
27
27
|
}
|
|
28
28
|
},
|
|
29
29
|
"files": [
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { neon } from '@neondatabase/serverless';
|
|
2
|
+
import { type CacheTag, type DatoCacheTagsProvider } from '../types.js';
|
|
3
|
+
|
|
4
|
+
type NeonDatoCacheTagsProviderConfig = {
|
|
5
|
+
/**
|
|
6
|
+
* Neon connection string. You can find it in the "Connection" tab of your Neon project dashboard.
|
|
7
|
+
* Has the format `postgresql://user:pass@host/db`
|
|
8
|
+
*/
|
|
9
|
+
readonly connectionUrl: string;
|
|
10
|
+
/**
|
|
11
|
+
* Name of the table where cache tags will be stored. The table must have the following schema:
|
|
12
|
+
*
|
|
13
|
+
* ```sql
|
|
14
|
+
* CREATE TABLE your_table_name (
|
|
15
|
+
* query_id TEXT NOT NULL,
|
|
16
|
+
* cache_tag TEXT NOT NULL,
|
|
17
|
+
* PRIMARY KEY (query_id, cache_tag)
|
|
18
|
+
* );
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
readonly table: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A `DatoCacheTagsProvider` implementation that uses Neon as the storage backend.
|
|
26
|
+
*/
|
|
27
|
+
export class NeonDatoCacheTagsProvider implements DatoCacheTagsProvider {
|
|
28
|
+
private readonly sql;
|
|
29
|
+
private readonly table;
|
|
30
|
+
|
|
31
|
+
constructor({ connectionUrl, table }: NeonDatoCacheTagsProviderConfig) {
|
|
32
|
+
this.sql = neon(connectionUrl, { fullResults: true });
|
|
33
|
+
this.table = table;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public async storeQueryCacheTags(queryId: string, cacheTags: CacheTag[]) {
|
|
37
|
+
if (!cacheTags?.length) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const tags = cacheTags.flatMap((_, i) => [queryId, cacheTags[i]]);
|
|
42
|
+
const placeholders = cacheTags.map((_, i) => `($${2 * i + 1}, $${2 * i + 2})`).join(',');
|
|
43
|
+
|
|
44
|
+
await this.sql.query(`INSERT INTO ${this.table} VALUES ${placeholders} ON CONFLICT DO NOTHING`, tags);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public async queriesReferencingCacheTags(cacheTags: CacheTag[]): Promise<string[]> {
|
|
48
|
+
if (!cacheTags?.length) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const placeholders = cacheTags.map((_, i) => `$${i + 1}`).join(',');
|
|
53
|
+
|
|
54
|
+
const { rows } = await this.sql.query(
|
|
55
|
+
`SELECT DISTINCT query_id FROM ${this.table} WHERE cache_tag IN (${placeholders})`,
|
|
56
|
+
cacheTags,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
return rows.reduce<string[]>((queryIds, row) => {
|
|
60
|
+
if (typeof row.query_id === 'string') {
|
|
61
|
+
queryIds.push(row.query_id);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return queryIds;
|
|
65
|
+
}, []);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public async deleteCacheTags(cacheTags: CacheTag[]) {
|
|
69
|
+
if (cacheTags.length === 0) {
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
72
|
+
const placeholders = cacheTags.map((_, i) => `$${i + 1}`).join(',');
|
|
73
|
+
|
|
74
|
+
return (await this.sql.query(`DELETE FROM ${this.table} WHERE cache_tag IN (${placeholders})`, cacheTags)).rowCount ?? 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public async truncateCacheTags() {
|
|
78
|
+
return (await this.sql.query(`DELETE FROM ${this.table}`)).rowCount ?? 0;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type CacheTag, type DatoCacheTagsProvider } from '../types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A `DatoCacheTagsProvider` implementation that does not perform any actual storage operations.
|
|
5
|
+
*
|
|
6
|
+
* _Note: This implementation is useful for testing purposes or when you want to disable caching without changing the code that interacts with the cache._
|
|
7
|
+
*/
|
|
8
|
+
export class NoopDatoCacheTagsProvider implements DatoCacheTagsProvider {
|
|
9
|
+
public async storeQueryCacheTags(queryId: string, cacheTags: CacheTag[]) {
|
|
10
|
+
console.debug('-- storeQueryCacheTags called', { queryId, cacheTags });
|
|
11
|
+
|
|
12
|
+
return Promise.resolve();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public async queriesReferencingCacheTags(cacheTags: CacheTag[]): Promise<string[]> {
|
|
16
|
+
console.debug('-- queriesReferencingCacheTags called', { cacheTags });
|
|
17
|
+
|
|
18
|
+
return Promise.resolve([]);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public async deleteCacheTags(cacheTags: CacheTag[]) {
|
|
22
|
+
console.debug('-- deleteCacheTags called', { cacheTags });
|
|
23
|
+
|
|
24
|
+
return Promise.resolve(0);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public async truncateCacheTags() {
|
|
28
|
+
console.debug('-- truncateCacheTags called');
|
|
29
|
+
|
|
30
|
+
return Promise.resolve(0);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Redis } from 'ioredis';
|
|
2
|
+
import { type CacheTag, type DatoCacheTagsProvider } from '../types.js';
|
|
3
|
+
|
|
4
|
+
type RedisDatoCacheTagsProviderConfig = {
|
|
5
|
+
/**
|
|
6
|
+
* Redis connection string. For example, `redis://user:pass@host:port/db`.
|
|
7
|
+
*/
|
|
8
|
+
readonly connectionUrl: string;
|
|
9
|
+
/**
|
|
10
|
+
* Optional prefix for Redis keys. If provided, all keys used to store cache tags will be prefixed with this value.
|
|
11
|
+
* This can be useful to avoid key collisions if the same Redis instance is used for multiple purposes.
|
|
12
|
+
* For example, if you set `keyPrefix` to `'myapp:'`, a cache tag like `'tag1'` will be stored under the key `'myapp:tag1'`.
|
|
13
|
+
*/
|
|
14
|
+
readonly keyPrefix?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A `DatoCacheTagsProvider` implementation that uses Redis as the storage backend.
|
|
19
|
+
*/
|
|
20
|
+
export class RedisDatoCacheTagsProvider implements DatoCacheTagsProvider {
|
|
21
|
+
private readonly redis;
|
|
22
|
+
private readonly keyPrefix;
|
|
23
|
+
|
|
24
|
+
constructor({ connectionUrl, keyPrefix }: RedisDatoCacheTagsProviderConfig) {
|
|
25
|
+
this.redis = new Redis(connectionUrl, {
|
|
26
|
+
maxRetriesPerRequest: 3,
|
|
27
|
+
lazyConnect: true,
|
|
28
|
+
});
|
|
29
|
+
this.keyPrefix = keyPrefix ?? '';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public async storeQueryCacheTags(queryId: string, cacheTags: CacheTag[]) {
|
|
33
|
+
if (!cacheTags?.length) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const pipeline = this.redis.pipeline();
|
|
38
|
+
|
|
39
|
+
for (const tag of cacheTags) {
|
|
40
|
+
pipeline.sadd(`${this.keyPrefix}${tag}`, queryId);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
await pipeline.exec();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public async queriesReferencingCacheTags(cacheTags: CacheTag[]) {
|
|
47
|
+
if (!cacheTags?.length) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const keys = cacheTags.map((tag) => `${this.keyPrefix}${tag}`);
|
|
52
|
+
|
|
53
|
+
return this.redis.sunion(...keys);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public async deleteCacheTags(cacheTags: CacheTag[]) {
|
|
57
|
+
if (!cacheTags?.length) {
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const keys = cacheTags.map((tag) => `${this.keyPrefix}${tag}`);
|
|
62
|
+
|
|
63
|
+
return this.redis.del(...keys);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public async truncateCacheTags() {
|
|
67
|
+
const pattern = `${this.keyPrefix}*`;
|
|
68
|
+
const keys = await this.redis.keys(pattern);
|
|
69
|
+
|
|
70
|
+
if (keys.length === 0) {
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return await this.redis.del(...keys);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -24,9 +24,9 @@ export type CacheTagsInvalidateWebhook = {
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
* Configuration object for creating a `
|
|
27
|
+
* Configuration object for creating a `DatoCacheTagsProvider` implementation.
|
|
28
28
|
*/
|
|
29
|
-
export
|
|
29
|
+
export interface DatoCacheTagsProvider {
|
|
30
30
|
/**
|
|
31
31
|
* Stores the cache tags of a query.
|
|
32
32
|
*
|
|
@@ -63,4 +63,4 @@ export type CacheTagsStore = {
|
|
|
63
63
|
* ⚠️ **Warning**: This will delete all cache tag data. Use with caution!
|
|
64
64
|
*/
|
|
65
65
|
truncateCacheTags(): Promise<number>;
|
|
66
|
-
}
|
|
66
|
+
}
|
package/dist/cache/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cache/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC"}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { type CacheTagsStore } from '../types.js';
|
|
2
|
-
type NeonCacheTagsStoreConfig = {
|
|
3
|
-
/**
|
|
4
|
-
* Neon connection string. You can find it in the "Connection" tab of your Neon project dashboard.
|
|
5
|
-
* Has the format `postgresql://user:pass@host/db`
|
|
6
|
-
*/
|
|
7
|
-
readonly connectionUrl: string;
|
|
8
|
-
/**
|
|
9
|
-
* Name of the table where cache tags will be stored. The table must have the following schema:
|
|
10
|
-
*
|
|
11
|
-
* ```sql
|
|
12
|
-
* CREATE TABLE your_table_name (
|
|
13
|
-
* query_id TEXT NOT NULL,
|
|
14
|
-
* cache_tag TEXT NOT NULL,
|
|
15
|
-
* PRIMARY KEY (query_id, cache_tag)
|
|
16
|
-
* );
|
|
17
|
-
* ```
|
|
18
|
-
*/
|
|
19
|
-
readonly table: string;
|
|
20
|
-
};
|
|
21
|
-
/**
|
|
22
|
-
* Creates a `CacheTagsStore` implementation using Neon as the storage backend. Neon is a serverless Postgres database service.
|
|
23
|
-
*
|
|
24
|
-
* @param {NeonCacheTagsStoreConfig} config Configuration object containing the Neon connection string and table name.
|
|
25
|
-
* @returns An object implementing the `CacheTagsStore` interface, allowing you to store and manage cache tags in a Neon database.
|
|
26
|
-
*/
|
|
27
|
-
export declare const createCacheTagsStore: ({ connectionUrl, table }: NeonCacheTagsStoreConfig) => CacheTagsStore;
|
|
28
|
-
export {};
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { neon } from '@neondatabase/serverless';
|
|
2
|
-
/**
|
|
3
|
-
* Creates a `CacheTagsStore` implementation using Neon as the storage backend. Neon is a serverless Postgres database service.
|
|
4
|
-
*
|
|
5
|
-
* @param {NeonCacheTagsStoreConfig} config Configuration object containing the Neon connection string and table name.
|
|
6
|
-
* @returns An object implementing the `CacheTagsStore` interface, allowing you to store and manage cache tags in a Neon database.
|
|
7
|
-
*/
|
|
8
|
-
export const createCacheTagsStore = ({ connectionUrl, table }) => {
|
|
9
|
-
const sql = neon(connectionUrl, { fullResults: true });
|
|
10
|
-
const storeQueryCacheTags = async (queryId, cacheTags) => {
|
|
11
|
-
if (!cacheTags?.length) {
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
const tags = cacheTags.flatMap((_, i) => [queryId, cacheTags[i]]);
|
|
15
|
-
const placeholders = cacheTags.map((_, i) => `($${2 * i + 1}, $${2 * i + 2})`).join(',');
|
|
16
|
-
await sql.query(`INSERT INTO ${table} VALUES ${placeholders} ON CONFLICT DO NOTHING`, tags);
|
|
17
|
-
};
|
|
18
|
-
const queriesReferencingCacheTags = async (cacheTags) => {
|
|
19
|
-
if (!cacheTags?.length) {
|
|
20
|
-
return [];
|
|
21
|
-
}
|
|
22
|
-
const placeholders = cacheTags.map((_, i) => `$${i + 1}`).join(',');
|
|
23
|
-
const { rows } = await sql.query(`SELECT DISTINCT query_id FROM ${table} WHERE cache_tag IN (${placeholders})`, cacheTags);
|
|
24
|
-
return rows.reduce((queryIds, row) => {
|
|
25
|
-
if (typeof row.query_id === 'string') {
|
|
26
|
-
queryIds.push(row.query_id);
|
|
27
|
-
}
|
|
28
|
-
return queryIds;
|
|
29
|
-
}, []);
|
|
30
|
-
};
|
|
31
|
-
const deleteCacheTags = async (cacheTags) => {
|
|
32
|
-
if (cacheTags.length === 0) {
|
|
33
|
-
return 0;
|
|
34
|
-
}
|
|
35
|
-
const placeholders = cacheTags.map((_, i) => `$${i + 1}`).join(',');
|
|
36
|
-
return (await sql.query(`DELETE FROM ${table} WHERE cache_tag IN (${placeholders})`, cacheTags)).rowCount ?? 0;
|
|
37
|
-
};
|
|
38
|
-
const truncateCacheTags = async () => (await sql.query(`DELETE FROM ${table}`)).rowCount ?? 0;
|
|
39
|
-
return {
|
|
40
|
-
storeQueryCacheTags,
|
|
41
|
-
queriesReferencingCacheTags,
|
|
42
|
-
deleteCacheTags,
|
|
43
|
-
truncateCacheTags,
|
|
44
|
-
};
|
|
45
|
-
};
|
|
46
|
-
//# sourceMappingURL=neon.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"neon.js","sourceRoot":"","sources":["../../../src/cache/provider/neon.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAuBhD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,EAAE,aAAa,EAAE,KAAK,EAA4B,EAAkB,EAAE;IACzG,MAAM,GAAG,GAAG,IAAI,CAAC,aAAa,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;IAEvD,MAAM,mBAAmB,GAAG,KAAK,EAAE,OAAe,EAAE,SAAqB,EAAE,EAAE;QAC3E,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC;YACvB,OAAO;QACT,CAAC;QAED,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAClE,MAAM,YAAY,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAEzF,MAAM,GAAG,CAAC,KAAK,CAAC,eAAe,KAAK,WAAW,YAAY,yBAAyB,EAAE,IAAI,CAAC,CAAC;IAC9F,CAAC,CAAC;IAEF,MAAM,2BAA2B,GAAG,KAAK,EAAE,SAAqB,EAAqB,EAAE;QACrF,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC;YACvB,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,YAAY,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAEpE,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,GAAG,CAAC,KAAK,CAC9B,iCAAiC,KAAK,wBAAwB,YAAY,GAAG,EAC7E,SAAS,CACV,CAAC;QAEF,OAAO,IAAI,CAAC,MAAM,CAAW,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE;YAC7C,IAAI,OAAO,GAAG,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;gBACrC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAC9B,CAAC;YAED,OAAO,QAAQ,CAAC;QAClB,CAAC,EAAE,EAAE,CAAC,CAAC;IACT,CAAC,CAAC;IAEF,MAAM,eAAe,GAAG,KAAK,EAAE,SAAqB,EAAE,EAAE;QACtD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,OAAO,CAAC,CAAC;QACX,CAAC;QACD,MAAM,YAAY,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAEpE,OAAO,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,eAAe,KAAK,wBAAwB,YAAY,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC;IACjH,CAAC,CAAC;IAEF,MAAM,iBAAiB,GAAG,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,eAAe,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC;IAE9F,OAAO;QACL,mBAAmB;QACnB,2BAA2B;QAC3B,eAAe;QACf,iBAAiB;KAClB,CAAC;AACJ,CAAC,CAAC"}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { type CacheTagsStore } from '../types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Creates a `CacheTagsStore` implementation that does not perform any actual storage operations.
|
|
4
|
-
*
|
|
5
|
-
* _Note: This implementation is useful for testing purposes or when you want to disable caching without changing the code that interacts with the cache._
|
|
6
|
-
*
|
|
7
|
-
* @returns An object implementing the `CacheTagsStore` interface.
|
|
8
|
-
*/
|
|
9
|
-
export declare const createCacheTagsStore: () => CacheTagsStore;
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Creates a `CacheTagsStore` implementation that does not perform any actual storage operations.
|
|
3
|
-
*
|
|
4
|
-
* _Note: This implementation is useful for testing purposes or when you want to disable caching without changing the code that interacts with the cache._
|
|
5
|
-
*
|
|
6
|
-
* @returns An object implementing the `CacheTagsStore` interface.
|
|
7
|
-
*/
|
|
8
|
-
export const createCacheTagsStore = () => {
|
|
9
|
-
const storeQueryCacheTags = async (queryId, cacheTags) => {
|
|
10
|
-
console.debug('-- storeQueryCacheTags called', { queryId, cacheTags });
|
|
11
|
-
return Promise.resolve();
|
|
12
|
-
};
|
|
13
|
-
const queriesReferencingCacheTags = async (cacheTags) => {
|
|
14
|
-
console.debug('-- queriesReferencingCacheTags called', { cacheTags });
|
|
15
|
-
return Promise.resolve([]);
|
|
16
|
-
};
|
|
17
|
-
const deleteCacheTags = async (cacheTags) => {
|
|
18
|
-
console.debug('-- deleteCacheTags called', { cacheTags });
|
|
19
|
-
return Promise.resolve(0);
|
|
20
|
-
};
|
|
21
|
-
const truncateCacheTags = async () => {
|
|
22
|
-
console.debug('-- truncateCacheTags called');
|
|
23
|
-
return Promise.resolve(0);
|
|
24
|
-
};
|
|
25
|
-
return {
|
|
26
|
-
storeQueryCacheTags,
|
|
27
|
-
queriesReferencingCacheTags,
|
|
28
|
-
deleteCacheTags,
|
|
29
|
-
truncateCacheTags,
|
|
30
|
-
};
|
|
31
|
-
};
|
|
32
|
-
//# sourceMappingURL=noop.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"noop.js","sourceRoot":"","sources":["../../../src/cache/provider/noop.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAmB,EAAE;IACvD,MAAM,mBAAmB,GAAG,KAAK,EAAE,OAAe,EAAE,SAAqB,EAAE,EAAE;QAC3E,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;QAEvE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC3B,CAAC,CAAC;IAEF,MAAM,2BAA2B,GAAG,KAAK,EAAE,SAAqB,EAAE,EAAE;QAClE,OAAO,CAAC,KAAK,CAAC,uCAAuC,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;QAEtE,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC7B,CAAC,CAAC;IAEF,MAAM,eAAe,GAAG,KAAK,EAAE,SAAqB,EAAE,EAAE;QACtD,OAAO,CAAC,KAAK,CAAC,2BAA2B,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;QAE1D,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5B,CAAC,CAAC;IAEF,MAAM,iBAAiB,GAAG,KAAK,IAAI,EAAE;QACnC,OAAO,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC;QAE7C,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5B,CAAC,CAAC;IAEF,OAAO;QACL,mBAAmB;QACnB,2BAA2B;QAC3B,eAAe;QACf,iBAAiB;KAClB,CAAC;AACJ,CAAC,CAAC"}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { type CacheTagsStore } from '../types.js';
|
|
2
|
-
type RedisCacheTagsStoreConfig = {
|
|
3
|
-
/**
|
|
4
|
-
* Redis connection string. For example, `redis://user:pass@host:port/db`.
|
|
5
|
-
*/
|
|
6
|
-
readonly connectionUrl: string;
|
|
7
|
-
/**
|
|
8
|
-
* Optional prefix for Redis keys. If provided, all keys used to store cache tags will be prefixed with this value.
|
|
9
|
-
* This can be useful to avoid key collisions if the same Redis instance is used for multiple purposes.
|
|
10
|
-
* For example, if you set `keyPrefix` to `'myapp:'`, a cache tag like `'tag1'` will be stored under the key `'myapp:tag1'`.
|
|
11
|
-
*/
|
|
12
|
-
readonly keyPrefix?: string;
|
|
13
|
-
};
|
|
14
|
-
/**
|
|
15
|
-
* Creates a `CacheTagsStore` implementation using Redis as the storage backend.
|
|
16
|
-
*
|
|
17
|
-
* @param {RedisCacheTagsStoreConfig} config Configuration object containing the Redis connection string and optional key prefix.
|
|
18
|
-
* @returns An object implementing the `CacheTagsStore` interface, allowing you to store and manage cache tags in a Redis database.
|
|
19
|
-
*/
|
|
20
|
-
export declare const createCacheTagsStore: ({ connectionUrl, keyPrefix }: RedisCacheTagsStoreConfig) => CacheTagsStore;
|
|
21
|
-
export {};
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { Redis } from 'ioredis';
|
|
2
|
-
/**
|
|
3
|
-
* Creates a `CacheTagsStore` implementation using Redis as the storage backend.
|
|
4
|
-
*
|
|
5
|
-
* @param {RedisCacheTagsStoreConfig} config Configuration object containing the Redis connection string and optional key prefix.
|
|
6
|
-
* @returns An object implementing the `CacheTagsStore` interface, allowing you to store and manage cache tags in a Redis database.
|
|
7
|
-
*/
|
|
8
|
-
export const createCacheTagsStore = ({ connectionUrl, keyPrefix = '' }) => {
|
|
9
|
-
const redis = new Redis(connectionUrl, {
|
|
10
|
-
maxRetriesPerRequest: 3,
|
|
11
|
-
lazyConnect: true,
|
|
12
|
-
});
|
|
13
|
-
const storeQueryCacheTags = async (queryId, cacheTags) => {
|
|
14
|
-
if (!cacheTags?.length) {
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
const pipeline = redis.pipeline();
|
|
18
|
-
for (const tag of cacheTags) {
|
|
19
|
-
pipeline.sadd(`${keyPrefix}${tag}`, queryId);
|
|
20
|
-
}
|
|
21
|
-
await pipeline.exec();
|
|
22
|
-
};
|
|
23
|
-
const queriesReferencingCacheTags = async (cacheTags) => {
|
|
24
|
-
if (!cacheTags?.length) {
|
|
25
|
-
return [];
|
|
26
|
-
}
|
|
27
|
-
const keys = cacheTags.map((tag) => `${keyPrefix}${tag}`);
|
|
28
|
-
return redis.sunion(...keys);
|
|
29
|
-
};
|
|
30
|
-
const deleteCacheTags = async (cacheTags) => {
|
|
31
|
-
if (!cacheTags?.length) {
|
|
32
|
-
return 0;
|
|
33
|
-
}
|
|
34
|
-
const keys = cacheTags.map((tag) => `${keyPrefix}${tag}`);
|
|
35
|
-
return redis.del(...keys);
|
|
36
|
-
};
|
|
37
|
-
const truncateCacheTags = async () => {
|
|
38
|
-
const pattern = `${keyPrefix}*`;
|
|
39
|
-
const keys = await redis.keys(pattern);
|
|
40
|
-
if (keys.length === 0) {
|
|
41
|
-
return 0;
|
|
42
|
-
}
|
|
43
|
-
return await redis.del(...keys);
|
|
44
|
-
};
|
|
45
|
-
return {
|
|
46
|
-
storeQueryCacheTags,
|
|
47
|
-
queriesReferencingCacheTags,
|
|
48
|
-
deleteCacheTags,
|
|
49
|
-
truncateCacheTags,
|
|
50
|
-
};
|
|
51
|
-
};
|
|
52
|
-
//# sourceMappingURL=redis.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"redis.js","sourceRoot":"","sources":["../../../src/cache/provider/redis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAgBhC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,EAAE,aAAa,EAAE,SAAS,GAAG,EAAE,EAA6B,EAAkB,EAAE;IACnH,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,aAAa,EAAE;QACrC,oBAAoB,EAAE,CAAC;QACvB,WAAW,EAAE,IAAI;KAClB,CAAC,CAAC;IAEH,MAAM,mBAAmB,GAAG,KAAK,EAAE,OAAe,EAAE,SAAqB,EAAE,EAAE;QAC3E,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC;YACvB,OAAO;QACT,CAAC;QAED,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC;QAElC,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;YAC5B,QAAQ,CAAC,IAAI,CAAC,GAAG,SAAS,GAAG,GAAG,EAAE,EAAE,OAAO,CAAC,CAAC;QAC/C,CAAC;QAED,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IACxB,CAAC,CAAC;IAEF,MAAM,2BAA2B,GAAG,KAAK,EAAE,SAAqB,EAAE,EAAE;QAClE,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC;YACvB,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,SAAS,GAAG,GAAG,EAAE,CAAC,CAAC;QAE1D,OAAO,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC;IAC/B,CAAC,CAAC;IAEF,MAAM,eAAe,GAAG,KAAK,EAAE,SAAqB,EAAE,EAAE;QACtD,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC;YACvB,OAAO,CAAC,CAAC;QACX,CAAC;QAED,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,SAAS,GAAG,GAAG,EAAE,CAAC,CAAC;QAE1D,OAAO,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IAC5B,CAAC,CAAC;IAEF,MAAM,iBAAiB,GAAG,KAAK,IAAI,EAAE;QACnC,MAAM,OAAO,GAAG,GAAG,SAAS,GAAG,CAAC;QAChC,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAEvC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtB,OAAO,CAAC,CAAC;QACX,CAAC;QAED,OAAO,MAAM,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IAClC,CAAC,CAAC;IAEF,OAAO;QACL,mBAAmB;QACnB,2BAA2B;QAC3B,eAAe;QACf,iBAAiB;KAClB,CAAC;AACJ,CAAC,CAAC"}
|
package/dist/cache/utils.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/cache/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAqB,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,MAAsB,EAAE,EAAE,CACtE,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAe,CAAC,CAAC;AAE3D;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAuB,QAAsB,EAAE,SAAsB,EAAU,EAAE;IAC9G,OAAO,UAAU,CAAC,MAAM,CAAC;SACtB,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;SACvB,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;SACvC,MAAM,CAAC,KAAK,CAAC,CAAC;AACnB,CAAC,CAAC"}
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { neon } from '@neondatabase/serverless';
|
|
2
|
-
import { type CacheTag, type CacheTagsStore } from '../types.js';
|
|
3
|
-
|
|
4
|
-
type NeonCacheTagsStoreConfig = {
|
|
5
|
-
/**
|
|
6
|
-
* Neon connection string. You can find it in the "Connection" tab of your Neon project dashboard.
|
|
7
|
-
* Has the format `postgresql://user:pass@host/db`
|
|
8
|
-
*/
|
|
9
|
-
readonly connectionUrl: string;
|
|
10
|
-
/**
|
|
11
|
-
* Name of the table where cache tags will be stored. The table must have the following schema:
|
|
12
|
-
*
|
|
13
|
-
* ```sql
|
|
14
|
-
* CREATE TABLE your_table_name (
|
|
15
|
-
* query_id TEXT NOT NULL,
|
|
16
|
-
* cache_tag TEXT NOT NULL,
|
|
17
|
-
* PRIMARY KEY (query_id, cache_tag)
|
|
18
|
-
* );
|
|
19
|
-
* ```
|
|
20
|
-
*/
|
|
21
|
-
readonly table: string;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Creates a `CacheTagsStore` implementation using Neon as the storage backend. Neon is a serverless Postgres database service.
|
|
26
|
-
*
|
|
27
|
-
* @param {NeonCacheTagsStoreConfig} config Configuration object containing the Neon connection string and table name.
|
|
28
|
-
* @returns An object implementing the `CacheTagsStore` interface, allowing you to store and manage cache tags in a Neon database.
|
|
29
|
-
*/
|
|
30
|
-
export const createCacheTagsStore = ({ connectionUrl, table }: NeonCacheTagsStoreConfig): CacheTagsStore => {
|
|
31
|
-
const sql = neon(connectionUrl, { fullResults: true });
|
|
32
|
-
|
|
33
|
-
const storeQueryCacheTags = async (queryId: string, cacheTags: CacheTag[]) => {
|
|
34
|
-
if (!cacheTags?.length) {
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const tags = cacheTags.flatMap((_, i) => [queryId, cacheTags[i]]);
|
|
39
|
-
const placeholders = cacheTags.map((_, i) => `($${2 * i + 1}, $${2 * i + 2})`).join(',');
|
|
40
|
-
|
|
41
|
-
await sql.query(`INSERT INTO ${table} VALUES ${placeholders} ON CONFLICT DO NOTHING`, tags);
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const queriesReferencingCacheTags = async (cacheTags: CacheTag[]): Promise<string[]> => {
|
|
45
|
-
if (!cacheTags?.length) {
|
|
46
|
-
return [];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const placeholders = cacheTags.map((_, i) => `$${i + 1}`).join(',');
|
|
50
|
-
|
|
51
|
-
const { rows } = await sql.query(
|
|
52
|
-
`SELECT DISTINCT query_id FROM ${table} WHERE cache_tag IN (${placeholders})`,
|
|
53
|
-
cacheTags,
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
return rows.reduce<string[]>((queryIds, row) => {
|
|
57
|
-
if (typeof row.query_id === 'string') {
|
|
58
|
-
queryIds.push(row.query_id);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return queryIds;
|
|
62
|
-
}, []);
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
const deleteCacheTags = async (cacheTags: CacheTag[]) => {
|
|
66
|
-
if (cacheTags.length === 0) {
|
|
67
|
-
return 0;
|
|
68
|
-
}
|
|
69
|
-
const placeholders = cacheTags.map((_, i) => `$${i + 1}`).join(',');
|
|
70
|
-
|
|
71
|
-
return (await sql.query(`DELETE FROM ${table} WHERE cache_tag IN (${placeholders})`, cacheTags)).rowCount ?? 0;
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
const truncateCacheTags = async () => (await sql.query(`DELETE FROM ${table}`)).rowCount ?? 0;
|
|
75
|
-
|
|
76
|
-
return {
|
|
77
|
-
storeQueryCacheTags,
|
|
78
|
-
queriesReferencingCacheTags,
|
|
79
|
-
deleteCacheTags,
|
|
80
|
-
truncateCacheTags,
|
|
81
|
-
};
|
|
82
|
-
};
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { type CacheTag, type CacheTagsStore } from '../types.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Creates a `CacheTagsStore` implementation that does not perform any actual storage operations.
|
|
5
|
-
*
|
|
6
|
-
* _Note: This implementation is useful for testing purposes or when you want to disable caching without changing the code that interacts with the cache._
|
|
7
|
-
*
|
|
8
|
-
* @returns An object implementing the `CacheTagsStore` interface.
|
|
9
|
-
*/
|
|
10
|
-
export const createCacheTagsStore = (): CacheTagsStore => {
|
|
11
|
-
const storeQueryCacheTags = async (queryId: string, cacheTags: CacheTag[]) => {
|
|
12
|
-
console.debug('-- storeQueryCacheTags called', { queryId, cacheTags });
|
|
13
|
-
|
|
14
|
-
return Promise.resolve();
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
const queriesReferencingCacheTags = async (cacheTags: CacheTag[]) => {
|
|
18
|
-
console.debug('-- queriesReferencingCacheTags called', { cacheTags });
|
|
19
|
-
|
|
20
|
-
return Promise.resolve([]);
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const deleteCacheTags = async (cacheTags: CacheTag[]) => {
|
|
24
|
-
console.debug('-- deleteCacheTags called', { cacheTags });
|
|
25
|
-
|
|
26
|
-
return Promise.resolve(0);
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const truncateCacheTags = async () => {
|
|
30
|
-
console.debug('-- truncateCacheTags called');
|
|
31
|
-
|
|
32
|
-
return Promise.resolve(0);
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
return {
|
|
36
|
-
storeQueryCacheTags,
|
|
37
|
-
queriesReferencingCacheTags,
|
|
38
|
-
deleteCacheTags,
|
|
39
|
-
truncateCacheTags,
|
|
40
|
-
};
|
|
41
|
-
};
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { Redis } from 'ioredis';
|
|
2
|
-
import { type CacheTag, type CacheTagsStore } from '../types.js';
|
|
3
|
-
|
|
4
|
-
type RedisCacheTagsStoreConfig = {
|
|
5
|
-
/**
|
|
6
|
-
* Redis connection string. For example, `redis://user:pass@host:port/db`.
|
|
7
|
-
*/
|
|
8
|
-
readonly connectionUrl: string;
|
|
9
|
-
/**
|
|
10
|
-
* Optional prefix for Redis keys. If provided, all keys used to store cache tags will be prefixed with this value.
|
|
11
|
-
* This can be useful to avoid key collisions if the same Redis instance is used for multiple purposes.
|
|
12
|
-
* For example, if you set `keyPrefix` to `'myapp:'`, a cache tag like `'tag1'` will be stored under the key `'myapp:tag1'`.
|
|
13
|
-
*/
|
|
14
|
-
readonly keyPrefix?: string;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Creates a `CacheTagsStore` implementation using Redis as the storage backend.
|
|
19
|
-
*
|
|
20
|
-
* @param {RedisCacheTagsStoreConfig} config Configuration object containing the Redis connection string and optional key prefix.
|
|
21
|
-
* @returns An object implementing the `CacheTagsStore` interface, allowing you to store and manage cache tags in a Redis database.
|
|
22
|
-
*/
|
|
23
|
-
export const createCacheTagsStore = ({ connectionUrl, keyPrefix = '' }: RedisCacheTagsStoreConfig): CacheTagsStore => {
|
|
24
|
-
const redis = new Redis(connectionUrl, {
|
|
25
|
-
maxRetriesPerRequest: 3,
|
|
26
|
-
lazyConnect: true,
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
const storeQueryCacheTags = async (queryId: string, cacheTags: CacheTag[]) => {
|
|
30
|
-
if (!cacheTags?.length) {
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const pipeline = redis.pipeline();
|
|
35
|
-
|
|
36
|
-
for (const tag of cacheTags) {
|
|
37
|
-
pipeline.sadd(`${keyPrefix}${tag}`, queryId);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
await pipeline.exec();
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const queriesReferencingCacheTags = async (cacheTags: CacheTag[]) => {
|
|
44
|
-
if (!cacheTags?.length) {
|
|
45
|
-
return [];
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const keys = cacheTags.map((tag) => `${keyPrefix}${tag}`);
|
|
49
|
-
|
|
50
|
-
return redis.sunion(...keys);
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const deleteCacheTags = async (cacheTags: CacheTag[]) => {
|
|
54
|
-
if (!cacheTags?.length) {
|
|
55
|
-
return 0;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const keys = cacheTags.map((tag) => `${keyPrefix}${tag}`);
|
|
59
|
-
|
|
60
|
-
return redis.del(...keys);
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
const truncateCacheTags = async () => {
|
|
64
|
-
const pattern = `${keyPrefix}*`;
|
|
65
|
-
const keys = await redis.keys(pattern);
|
|
66
|
-
|
|
67
|
-
if (keys.length === 0) {
|
|
68
|
-
return 0;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return await redis.del(...keys);
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
return {
|
|
75
|
-
storeQueryCacheTags,
|
|
76
|
-
queriesReferencingCacheTags,
|
|
77
|
-
deleteCacheTags,
|
|
78
|
-
truncateCacheTags,
|
|
79
|
-
};
|
|
80
|
-
};
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|