@smartive/datocms-utils 2.3.2 → 3.0.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ export * from './store/postgres.js';
2
+ export * from './store/redis.js';
3
+ export * from './types.js';
4
+ export * from './utils.js';
@@ -0,0 +1,5 @@
1
+ export * from './store/postgres.js';
2
+ export * from './store/redis.js';
3
+ export * from './types.js';
4
+ export * from './utils.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cache/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { type CacheTagsStore } from '../types.js';
2
+ export declare const createPostgresCacheTagsStore: ({ table }: {
3
+ table: string;
4
+ }) => CacheTagsStore;
@@ -0,0 +1,34 @@
1
+ import { sql } from '@vercel/postgres';
2
+ export const createPostgresCacheTagsStore = ({ table }) => {
3
+ const storeQueryCacheTags = async (queryId, cacheTags) => {
4
+ if (!cacheTags?.length) {
5
+ return;
6
+ }
7
+ const tags = cacheTags.flatMap((_, i) => [queryId, cacheTags[i]]);
8
+ const placeholders = cacheTags.map((_, i) => `($${2 * i + 1}, $${2 * i + 2})`).join(',');
9
+ await sql.query(`INSERT INTO ${table} VALUES ${placeholders} ON CONFLICT DO NOTHING`, tags);
10
+ };
11
+ const queriesReferencingCacheTags = async (cacheTags) => {
12
+ if (!cacheTags?.length) {
13
+ return [];
14
+ }
15
+ const placeholders = cacheTags.map((_, i) => `$${i + 1}`).join(',');
16
+ const { rows } = await sql.query(`SELECT DISTINCT query_id FROM ${table} WHERE cache_tag IN (${placeholders})`, cacheTags);
17
+ return rows.map((row) => row.query_id);
18
+ };
19
+ const deleteCacheTags = async (cacheTags) => {
20
+ if (cacheTags.length === 0) {
21
+ return 0;
22
+ }
23
+ const placeholders = cacheTags.map((_, i) => `$${i + 1}`).join(',');
24
+ return (await sql.query(`DELETE FROM ${table} WHERE cache_tag IN (${placeholders})`, cacheTags)).rowCount ?? 0;
25
+ };
26
+ const truncateCacheTags = async () => (await sql.query(`DELETE FROM ${table}`)).rowCount ?? 0;
27
+ return {
28
+ storeQueryCacheTags,
29
+ queriesReferencingCacheTags,
30
+ deleteCacheTags,
31
+ truncateCacheTags,
32
+ };
33
+ };
34
+ //# sourceMappingURL=postgres.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres.js","sourceRoot":"","sources":["../../../src/cache/store/postgres.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAGvC,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,EAAE,KAAK,EAAqB,EAAkB,EAAE;IAC3F,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,GAAqC,MAAM,GAAG,CAAC,KAAK,CAChE,iCAAiC,KAAK,wBAAwB,YAAY,GAAG,EAC7E,SAAS,CACV,CAAC;QAEF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACzC,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"}
@@ -0,0 +1,5 @@
1
+ import { type CacheTagsStore } from '../types.js';
2
+ export declare const createRedisCacheTagsStore: ({ url, keyPrefix }: {
3
+ url: string;
4
+ keyPrefix?: string;
5
+ }) => CacheTagsStore;
@@ -0,0 +1,46 @@
1
+ import { Redis } from 'ioredis';
2
+ export const createRedisCacheTagsStore = ({ url, keyPrefix = '' }) => {
3
+ const redis = new Redis(url, {
4
+ maxRetriesPerRequest: 3,
5
+ lazyConnect: true,
6
+ });
7
+ const storeQueryCacheTags = async (queryId, cacheTags) => {
8
+ if (!cacheTags?.length) {
9
+ return;
10
+ }
11
+ const pipeline = redis.pipeline();
12
+ for (const tag of cacheTags) {
13
+ pipeline.sadd(`${keyPrefix}${tag}`, queryId);
14
+ }
15
+ await pipeline.exec();
16
+ };
17
+ const queriesReferencingCacheTags = async (cacheTags) => {
18
+ if (!cacheTags?.length) {
19
+ return [];
20
+ }
21
+ const keys = cacheTags.map((tag) => `${keyPrefix}${tag}`);
22
+ return redis.sunion(...keys);
23
+ };
24
+ const deleteCacheTags = async (cacheTags) => {
25
+ if (!cacheTags?.length) {
26
+ return 0;
27
+ }
28
+ const keys = cacheTags.map((tag) => `${keyPrefix}${tag}`);
29
+ return redis.del(...keys);
30
+ };
31
+ const truncateCacheTags = async () => {
32
+ const pattern = `${keyPrefix}*`;
33
+ const keys = await redis.keys(pattern);
34
+ if (keys.length === 0) {
35
+ return 0;
36
+ }
37
+ return await redis.del(...keys);
38
+ };
39
+ return {
40
+ storeQueryCacheTags,
41
+ queriesReferencingCacheTags,
42
+ deleteCacheTags,
43
+ truncateCacheTags,
44
+ };
45
+ };
46
+ //# sourceMappingURL=redis.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis.js","sourceRoot":"","sources":["../../../src/cache/store/redis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAGhC,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,EAAE,GAAG,EAAE,SAAS,GAAG,EAAE,EAAuC,EAAkB,EAAE;IACxH,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,GAAG,EAAE;QAC3B,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"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * A branded type for cache tags. This is created by intersecting `string`
3
+ * with `{ readonly _: unique symbol }`, making it a unique type.
4
+ * Although it is fundamentally a string, it is treated as a distinct type
5
+ * due to the unique symbol.
6
+ */
7
+ export type CacheTag = string & {
8
+ readonly _: unique symbol;
9
+ };
10
+ /**
11
+ * A type representing the structure of a webhook payload for cache tag invalidation.
12
+ * It includes the entity type, event type, and the entity details which contain
13
+ * the cache tags to be invalidated.
14
+ */
15
+ export type CacheTagsInvalidateWebhook = {
16
+ entity_type: 'cda_cache_tags';
17
+ event_type: 'invalidate';
18
+ entity: {
19
+ id: 'cda_cache_tags';
20
+ type: 'cda_cache_tags';
21
+ attributes: {
22
+ tags: CacheTag[];
23
+ };
24
+ };
25
+ };
26
+ export type CacheTagsStore = {
27
+ /**
28
+ * Stores the cache tags of a query.
29
+ *
30
+ * @param {string} queryId Unique query ID
31
+ * @param {CacheTag[]} cacheTags Array of cache tags
32
+ *
33
+ */
34
+ storeQueryCacheTags(queryId: string, cacheTags: CacheTag[]): Promise<void>;
35
+ /**
36
+ * Retrieves the query IDs that reference any of the specified cache tags.
37
+ *
38
+ * @param {CacheTag[]} cacheTags Array of cache tags to check
39
+ * @returns Array of unique query IDs
40
+ *
41
+ */
42
+ queriesReferencingCacheTags(cacheTags: CacheTag[]): Promise<string[]>;
43
+ /**
44
+ * Deletes the specified cache tags.
45
+ *
46
+ * This removes the cache tag keys entirely. When queries are revalidated and
47
+ * run again, fresh cache tag mappings will be created.
48
+ *
49
+ * @param {CacheTag[]} cacheTags Array of cache tags to delete
50
+ * @returns Number of keys deleted, or null if there was an error
51
+ *
52
+ */
53
+ deleteCacheTags(cacheTags: CacheTag[]): Promise<number>;
54
+ /**
55
+ * Wipes out all cache tags.
56
+ *
57
+ * ⚠️ **Warning**: This will delete all cache tag data. Use with caution!
58
+ */
59
+ truncateCacheTags(): Promise<number>;
60
+ };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/cache/types.ts"],"names":[],"mappings":""}
@@ -1,5 +1,5 @@
1
1
  import { type DocumentNode } from 'graphql';
2
- import { type CacheTag } from './types';
2
+ import { type CacheTag } from './types.js';
3
3
  /**
4
4
  * Converts the value of DatoCMS's `X-Cache-Tags` header into an array of strings typed as `CacheTag`.
5
5
  * For example, it transforms `'tag-a tag-2 other-tag'` into `['tag-a', 'tag-2', 'other-tag']`.
@@ -0,0 +1 @@
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"}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,2 @@
1
- export * from './cache-tags';
2
- export * as postgres from './cache-tags';
3
- export * as redis from './cache-tags-redis';
4
- export * from './classnames';
5
- export * from './links';
6
- export * from './types';
1
+ export * from './classnames.js';
2
+ export * from './links.js';
package/dist/index.js CHANGED
@@ -1,7 +1,3 @@
1
- export * from './cache-tags';
2
- export * as postgres from './cache-tags';
3
- export * as redis from './cache-tags-redis';
4
- export * from './classnames';
5
- export * from './links';
6
- export * from './types';
1
+ export * from './classnames.js';
2
+ export * from './links.js';
7
3
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,OAAO,KAAK,QAAQ,MAAM,cAAc,CAAC;AACzC,OAAO,KAAK,KAAK,MAAM,oBAAoB,CAAC;AAC5C,cAAc,cAAc,CAAC;AAC7B,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC"}
package/package.json CHANGED
@@ -1,30 +1,26 @@
1
1
  {
2
2
  "name": "@smartive/datocms-utils",
3
- "version": "2.3.2",
3
+ "version": "3.0.0-next.1",
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",
7
- "main": "./dist/index.js",
8
- "types": "./dist/index.d.ts",
9
- "files": [
10
- "dist/**/*",
11
- "src/**/*"
12
- ],
13
7
  "exports": {
14
8
  ".": {
15
9
  "types": "./dist/index.d.ts",
16
- "default": "./dist/index.js"
10
+ "import": "./dist/index.js"
17
11
  },
18
- "./redis": {
19
- "types": "./dist/cache-tags-redis.d.ts",
20
- "default": "./dist/cache-tags-redis.js"
21
- },
22
- "./postgres": {
23
- "types": "./dist/cache-tags.d.ts",
24
- "default": "./dist/cache-tags.js"
12
+ "./cache": {
13
+ "types": "./dist/cache/index.d.ts",
14
+ "import": "./dist/cache/index.js"
25
15
  }
26
16
  },
17
+ "files": [
18
+ "dist/**/*",
19
+ "src/**/*"
20
+ ],
27
21
  "scripts": {
22
+ "clean": "rimraf dist",
23
+ "prebuild": "npm run clean",
28
24
  "build": "tsc",
29
25
  "lint": "eslint src",
30
26
  "prettier": "prettier --check src"
@@ -45,20 +41,27 @@
45
41
  "@smartive/eslint-config": "7.0.1",
46
42
  "@smartive/prettier-config": "3.1.2",
47
43
  "@types/node": "24.10.12",
44
+ "@vercel/postgres": "0.10.0",
48
45
  "eslint": "9.39.2",
49
46
  "eslint-import-resolver-typescript": "4.4.4",
47
+ "graphql": "16.12.0",
48
+ "ioredis": "5.9.2",
50
49
  "prettier": "3.8.1",
51
- "typescript": "5.9.3",
52
- "ioredis": "5.9.2"
53
- },
54
- "dependencies": {
55
- "@vercel/postgres": "^0.10.0",
56
- "graphql": "^16.9.0"
50
+ "rimraf": "6.1.2",
51
+ "typescript": "5.9.3"
57
52
  },
58
53
  "peerDependencies": {
54
+ "@vercel/postgres": "^0.10.0",
55
+ "graphql": "^15.0.0 || ^16.0.0",
59
56
  "ioredis": "^5.4.0"
60
57
  },
61
58
  "peerDependenciesMeta": {
59
+ "@vercel/postgres": {
60
+ "optional": true
61
+ },
62
+ "graphql": {
63
+ "optional": true
64
+ },
62
65
  "ioredis": {
63
66
  "optional": true
64
67
  }
@@ -0,0 +1,4 @@
1
+ export * from './store/postgres.js';
2
+ export * from './store/redis.js';
3
+ export * from './types.js';
4
+ export * from './utils.js';
@@ -0,0 +1,48 @@
1
+ import { sql } from '@vercel/postgres';
2
+ import { type CacheTag, type CacheTagsStore } from '../types.js';
3
+
4
+ export const createPostgresCacheTagsStore = ({ table }: { table: string }): CacheTagsStore => {
5
+ const storeQueryCacheTags = async (queryId: string, cacheTags: CacheTag[]) => {
6
+ if (!cacheTags?.length) {
7
+ return;
8
+ }
9
+
10
+ const tags = cacheTags.flatMap((_, i) => [queryId, cacheTags[i]]);
11
+ const placeholders = cacheTags.map((_, i) => `($${2 * i + 1}, $${2 * i + 2})`).join(',');
12
+
13
+ await sql.query(`INSERT INTO ${table} VALUES ${placeholders} ON CONFLICT DO NOTHING`, tags);
14
+ };
15
+
16
+ const queriesReferencingCacheTags = async (cacheTags: CacheTag[]): Promise<string[]> => {
17
+ if (!cacheTags?.length) {
18
+ return [];
19
+ }
20
+
21
+ const placeholders = cacheTags.map((_, i) => `$${i + 1}`).join(',');
22
+
23
+ const { rows }: { rows: { query_id: string }[] } = await sql.query(
24
+ `SELECT DISTINCT query_id FROM ${table} WHERE cache_tag IN (${placeholders})`,
25
+ cacheTags,
26
+ );
27
+
28
+ return rows.map((row) => row.query_id);
29
+ };
30
+
31
+ const deleteCacheTags = async (cacheTags: CacheTag[]) => {
32
+ if (cacheTags.length === 0) {
33
+ return 0;
34
+ }
35
+ const placeholders = cacheTags.map((_, i) => `$${i + 1}`).join(',');
36
+
37
+ return (await sql.query(`DELETE FROM ${table} WHERE cache_tag IN (${placeholders})`, cacheTags)).rowCount ?? 0;
38
+ };
39
+
40
+ const truncateCacheTags = async () => (await sql.query(`DELETE FROM ${table}`)).rowCount ?? 0;
41
+
42
+ return {
43
+ storeQueryCacheTags,
44
+ queriesReferencingCacheTags,
45
+ deleteCacheTags,
46
+ truncateCacheTags,
47
+ };
48
+ };
@@ -0,0 +1,61 @@
1
+ import { Redis } from 'ioredis';
2
+ import { type CacheTag, type CacheTagsStore } from '../types.js';
3
+
4
+ export const createRedisCacheTagsStore = ({ url, keyPrefix = '' }: { url: string; keyPrefix?: string }): CacheTagsStore => {
5
+ const redis = new Redis(url, {
6
+ maxRetriesPerRequest: 3,
7
+ lazyConnect: true,
8
+ });
9
+
10
+ const storeQueryCacheTags = async (queryId: string, cacheTags: CacheTag[]) => {
11
+ if (!cacheTags?.length) {
12
+ return;
13
+ }
14
+
15
+ const pipeline = redis.pipeline();
16
+
17
+ for (const tag of cacheTags) {
18
+ pipeline.sadd(`${keyPrefix}${tag}`, queryId);
19
+ }
20
+
21
+ await pipeline.exec();
22
+ };
23
+
24
+ const queriesReferencingCacheTags = async (cacheTags: CacheTag[]) => {
25
+ if (!cacheTags?.length) {
26
+ return [];
27
+ }
28
+
29
+ const keys = cacheTags.map((tag) => `${keyPrefix}${tag}`);
30
+
31
+ return redis.sunion(...keys);
32
+ };
33
+
34
+ const deleteCacheTags = async (cacheTags: CacheTag[]) => {
35
+ if (!cacheTags?.length) {
36
+ return 0;
37
+ }
38
+
39
+ const keys = cacheTags.map((tag) => `${keyPrefix}${tag}`);
40
+
41
+ return redis.del(...keys);
42
+ };
43
+
44
+ const truncateCacheTags = async () => {
45
+ const pattern = `${keyPrefix}*`;
46
+ const keys = await redis.keys(pattern);
47
+
48
+ if (keys.length === 0) {
49
+ return 0;
50
+ }
51
+
52
+ return await redis.del(...keys);
53
+ };
54
+
55
+ return {
56
+ storeQueryCacheTags,
57
+ queriesReferencingCacheTags,
58
+ deleteCacheTags,
59
+ truncateCacheTags,
60
+ };
61
+ };
@@ -0,0 +1,63 @@
1
+ /**
2
+ * A branded type for cache tags. This is created by intersecting `string`
3
+ * with `{ readonly _: unique symbol }`, making it a unique type.
4
+ * Although it is fundamentally a string, it is treated as a distinct type
5
+ * due to the unique symbol.
6
+ */
7
+ export type CacheTag = string & { readonly _: unique symbol };
8
+
9
+ /**
10
+ * A type representing the structure of a webhook payload for cache tag invalidation.
11
+ * It includes the entity type, event type, and the entity details which contain
12
+ * the cache tags to be invalidated.
13
+ */
14
+ export type CacheTagsInvalidateWebhook = {
15
+ entity_type: 'cda_cache_tags';
16
+ event_type: 'invalidate';
17
+ entity: {
18
+ id: 'cda_cache_tags';
19
+ type: 'cda_cache_tags';
20
+ attributes: {
21
+ tags: CacheTag[];
22
+ };
23
+ };
24
+ };
25
+
26
+ export type CacheTagsStore = {
27
+ /**
28
+ * Stores the cache tags of a query.
29
+ *
30
+ * @param {string} queryId Unique query ID
31
+ * @param {CacheTag[]} cacheTags Array of cache tags
32
+ *
33
+ */
34
+ storeQueryCacheTags(queryId: string, cacheTags: CacheTag[]): Promise<void>;
35
+
36
+ /**
37
+ * Retrieves the query IDs that reference any of the specified cache tags.
38
+ *
39
+ * @param {CacheTag[]} cacheTags Array of cache tags to check
40
+ * @returns Array of unique query IDs
41
+ *
42
+ */
43
+ queriesReferencingCacheTags(cacheTags: CacheTag[]): Promise<string[]>;
44
+
45
+ /**
46
+ * Deletes the specified cache tags.
47
+ *
48
+ * This removes the cache tag keys entirely. When queries are revalidated and
49
+ * run again, fresh cache tag mappings will be created.
50
+ *
51
+ * @param {CacheTag[]} cacheTags Array of cache tags to delete
52
+ * @returns Number of keys deleted, or null if there was an error
53
+ *
54
+ */
55
+ deleteCacheTags(cacheTags: CacheTag[]): Promise<number>;
56
+
57
+ /**
58
+ * Wipes out all cache tags.
59
+ *
60
+ * ⚠️ **Warning**: This will delete all cache tag data. Use with caution!
61
+ */
62
+ truncateCacheTags(): Promise<number>;
63
+ };
@@ -1,6 +1,6 @@
1
1
  import { print, type DocumentNode } from 'graphql';
2
2
  import { createHash } from 'node:crypto';
3
- import { type CacheTag } from './types';
3
+ import { type CacheTag } from './types.js';
4
4
 
5
5
  /**
6
6
  * Converts the value of DatoCMS's `X-Cache-Tags` header into an array of strings typed as `CacheTag`.
package/src/index.ts CHANGED
@@ -1,6 +1,2 @@
1
- export * from './cache-tags';
2
- export * as postgres from './cache-tags';
3
- export * as redis from './cache-tags-redis';
4
- export * from './classnames';
5
- export * from './links';
6
- export * from './types';
1
+ export * from './classnames.js';
2
+ export * from './links.js';
@@ -1,39 +0,0 @@
1
- import { type CacheTag } from './types';
2
- /**
3
- * Stores the cache tags of a query in Redis.
4
- *
5
- * For each cache tag, adds the query ID to a Redis Set. Sets are unordered
6
- * collections of unique strings, perfect for tracking which queries use which tags.
7
- *
8
- * @param {string} queryId Unique query ID
9
- * @param {CacheTag[]} cacheTags Array of cache tags
10
- *
11
- */
12
- export declare const storeQueryCacheTags: (queryId: string, cacheTags: CacheTag[]) => Promise<void>;
13
- /**
14
- * Retrieves the query IDs that reference any of the specified cache tags.
15
- *
16
- * Uses Redis SUNION to efficiently find all queries associated with the given tags.
17
- *
18
- * @param {CacheTag[]} cacheTags Array of cache tags to check
19
- * @returns Array of unique query IDs
20
- *
21
- */
22
- export declare const queriesReferencingCacheTags: (cacheTags: CacheTag[]) => Promise<string[]>;
23
- /**
24
- * Deletes the specified cache tags from Redis.
25
- *
26
- * This removes the cache tag keys entirely. When queries are revalidated and
27
- * run again, fresh cache tag mappings will be created.
28
- *
29
- * @param {CacheTag[]} cacheTags Array of cache tags to delete
30
- * @returns Number of keys deleted, or null if there was an error
31
- *
32
- */
33
- export declare const deleteCacheTags: (cacheTags: CacheTag[]) => Promise<number>;
34
- /**
35
- * Wipes out all cache tags from Redis.
36
- *
37
- * ⚠️ **Warning**: This will delete all cache tag data. Use with caution!
38
- */
39
- export declare const truncateCacheTags: () => Promise<void>;
@@ -1,80 +0,0 @@
1
- import { Redis } from 'ioredis';
2
- let redis = null;
3
- const getRedis = () => {
4
- redis ??= new Redis(process.env.REDIS_URL, {
5
- maxRetriesPerRequest: 3,
6
- lazyConnect: true,
7
- });
8
- return redis;
9
- };
10
- const keyPrefix = process.env.REDIS_KEY_PREFIX ? `${process.env.REDIS_KEY_PREFIX}:` : '';
11
- /**
12
- * Stores the cache tags of a query in Redis.
13
- *
14
- * For each cache tag, adds the query ID to a Redis Set. Sets are unordered
15
- * collections of unique strings, perfect for tracking which queries use which tags.
16
- *
17
- * @param {string} queryId Unique query ID
18
- * @param {CacheTag[]} cacheTags Array of cache tags
19
- *
20
- */
21
- export const storeQueryCacheTags = async (queryId, cacheTags) => {
22
- if (!cacheTags?.length) {
23
- return;
24
- }
25
- const redis = getRedis();
26
- const pipeline = redis.pipeline();
27
- for (const tag of cacheTags) {
28
- pipeline.sadd(`${keyPrefix}${tag}`, queryId);
29
- }
30
- await pipeline.exec();
31
- };
32
- /**
33
- * Retrieves the query IDs that reference any of the specified cache tags.
34
- *
35
- * Uses Redis SUNION to efficiently find all queries associated with the given tags.
36
- *
37
- * @param {CacheTag[]} cacheTags Array of cache tags to check
38
- * @returns Array of unique query IDs
39
- *
40
- */
41
- export const queriesReferencingCacheTags = async (cacheTags) => {
42
- if (!cacheTags?.length) {
43
- return [];
44
- }
45
- const redis = getRedis();
46
- const keys = cacheTags.map((tag) => `${keyPrefix}${tag}`);
47
- return redis.sunion(...keys);
48
- };
49
- /**
50
- * Deletes the specified cache tags from Redis.
51
- *
52
- * This removes the cache tag keys entirely. When queries are revalidated and
53
- * run again, fresh cache tag mappings will be created.
54
- *
55
- * @param {CacheTag[]} cacheTags Array of cache tags to delete
56
- * @returns Number of keys deleted, or null if there was an error
57
- *
58
- */
59
- export const deleteCacheTags = async (cacheTags) => {
60
- if (!cacheTags?.length) {
61
- return 0;
62
- }
63
- const redis = getRedis();
64
- const keys = cacheTags.map((tag) => `${keyPrefix}${tag}`);
65
- return redis.del(...keys);
66
- };
67
- /**
68
- * Wipes out all cache tags from Redis.
69
- *
70
- * ⚠️ **Warning**: This will delete all cache tag data. Use with caution!
71
- */
72
- export const truncateCacheTags = async () => {
73
- const redis = getRedis();
74
- const pattern = `${keyPrefix}*`;
75
- const keys = await redis.keys(pattern);
76
- if (keys.length > 0) {
77
- await redis.del(...keys);
78
- }
79
- };
80
- //# sourceMappingURL=cache-tags-redis.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cache-tags-redis.js","sourceRoot":"","sources":["../src/cache-tags-redis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAGhC,IAAI,KAAK,GAAiB,IAAI,CAAC;AAE/B,MAAM,QAAQ,GAAG,GAAU,EAAE;IAC3B,KAAK,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,SAAU,EAAE;QAC1C,oBAAoB,EAAE,CAAC;QACvB,WAAW,EAAE,IAAI;KAClB,CAAC,CAAC;IAEH,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AAEF,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;AAEzF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,KAAK,EAAE,OAAe,EAAE,SAAqB,EAAiB,EAAE;IACjG,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC;QACvB,OAAO;IACT,CAAC;IAED,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC;IAElC,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;QAC5B,QAAQ,CAAC,IAAI,CAAC,GAAG,SAAS,GAAG,GAAG,EAAE,EAAE,OAAO,CAAC,CAAC;IAC/C,CAAC;IAED,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;AACxB,CAAC,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,KAAK,EAAE,SAAqB,EAAqB,EAAE;IAC5F,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC;QACvB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,SAAS,GAAG,GAAG,EAAE,CAAC,CAAC;IAE1D,OAAO,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC;AAC/B,CAAC,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,EAAE,SAAqB,EAAmB,EAAE;IAC9E,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC;QACvB,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,SAAS,GAAG,GAAG,EAAE,CAAC,CAAC;IAE1D,OAAO,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;AAC5B,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,IAAmB,EAAE;IACzD,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,MAAM,OAAO,GAAG,GAAG,SAAS,GAAG,CAAC;IAChC,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAEvC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpB,MAAM,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IAC3B,CAAC;AACH,CAAC,CAAC"}
@@ -1,43 +0,0 @@
1
- import { type CacheTag } from './types';
2
- export { generateQueryId, parseXCacheTagsResponseHeader } from './utils';
3
- /**
4
- * Stores the cache tags of a query in the database.
5
- *
6
- * @param {string} queryId Unique query ID
7
- * @param {CacheTag[]} cacheTags Array of cache tags
8
- * @param {string} tableId Database table ID
9
- */
10
- export declare const storeQueryCacheTags: (queryId: string, cacheTags: CacheTag[], tableId: string) => Promise<void>;
11
- /**
12
- * Retrieves the queries that reference cache tags.
13
- *
14
- * @param {CacheTag[]} cacheTags Array of cache tags
15
- * @param {string} tableId Database table ID
16
- * @returns Array of query IDs
17
- */
18
- export declare const queriesReferencingCacheTags: (cacheTags: CacheTag[], tableId: string) => Promise<string[]>;
19
- /**
20
- * Deletes the specified cache tags from the database.
21
- *
22
- * This removes the cache tag keys entirely. When queries are revalidated and
23
- * run again, fresh cache tag mappings will be created.
24
- *
25
- * @param {CacheTag[]} cacheTags Array of cache tags to delete
26
- * @param {string} tableId Database table ID
27
- *
28
- */
29
- export declare const deleteCacheTags: (cacheTags: CacheTag[], tableId: string) => Promise<void>;
30
- /**
31
- * Deletes the cache tags of a query from the database.
32
- *
33
- * @param {string} queryId Unique query ID
34
- * @param {string} tableId Database table ID
35
- * @deprecated Use `deleteCacheTags` instead.
36
- */
37
- export declare const deleteQueries: (queryIds: string[], tableId: string) => Promise<void>;
38
- /**
39
- * Wipes out all cache tags from the database.
40
- *
41
- * @param {string} tableId Database table ID
42
- */
43
- export declare function truncateCacheTags(tableId: string): Promise<void>;
@@ -1,72 +0,0 @@
1
- import { sql } from '@vercel/postgres';
2
- export { generateQueryId, parseXCacheTagsResponseHeader } from './utils';
3
- /**
4
- * Stores the cache tags of a query in the database.
5
- *
6
- * @param {string} queryId Unique query ID
7
- * @param {CacheTag[]} cacheTags Array of cache tags
8
- * @param {string} tableId Database table ID
9
- */
10
- export const storeQueryCacheTags = async (queryId, cacheTags, tableId) => {
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 ${tableId} VALUES ${placeholders} ON CONFLICT DO NOTHING`, tags);
17
- };
18
- /**
19
- * Retrieves the queries that reference cache tags.
20
- *
21
- * @param {CacheTag[]} cacheTags Array of cache tags
22
- * @param {string} tableId Database table ID
23
- * @returns Array of query IDs
24
- */
25
- export const queriesReferencingCacheTags = async (cacheTags, tableId) => {
26
- if (!cacheTags?.length) {
27
- return [];
28
- }
29
- const placeholders = cacheTags.map((_, i) => `$${i + 1}`).join(',');
30
- const { rows } = await sql.query(`SELECT DISTINCT query_id FROM ${tableId} WHERE cache_tag IN (${placeholders})`, cacheTags);
31
- return rows.map((row) => row.query_id);
32
- };
33
- /**
34
- * Deletes the specified cache tags from the database.
35
- *
36
- * This removes the cache tag keys entirely. When queries are revalidated and
37
- * run again, fresh cache tag mappings will be created.
38
- *
39
- * @param {CacheTag[]} cacheTags Array of cache tags to delete
40
- * @param {string} tableId Database table ID
41
- *
42
- */
43
- export const deleteCacheTags = async (cacheTags, tableId) => {
44
- if (cacheTags.length === 0) {
45
- return;
46
- }
47
- const placeholders = cacheTags.map((_, i) => `$${i + 1}`).join(',');
48
- await sql.query(`DELETE FROM ${tableId} WHERE cache_tag IN (${placeholders})`, cacheTags);
49
- };
50
- /**
51
- * Deletes the cache tags of a query from the database.
52
- *
53
- * @param {string} queryId Unique query ID
54
- * @param {string} tableId Database table ID
55
- * @deprecated Use `deleteCacheTags` instead.
56
- */
57
- export const deleteQueries = async (queryIds, tableId) => {
58
- if (!queryIds?.length) {
59
- return;
60
- }
61
- const placeholders = queryIds.map((_, i) => `$${i + 1}`).join(',');
62
- await sql.query(`DELETE FROM ${tableId} WHERE query_id IN (${placeholders})`, queryIds);
63
- };
64
- /**
65
- * Wipes out all cache tags from the database.
66
- *
67
- * @param {string} tableId Database table ID
68
- */
69
- export async function truncateCacheTags(tableId) {
70
- await sql.query(`DELETE FROM ${tableId}`);
71
- }
72
- //# sourceMappingURL=cache-tags.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cache-tags.js","sourceRoot":"","sources":["../src/cache-tags.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts DELETED
@@ -1,25 +0,0 @@
1
- /**
2
- * A branded type for cache tags. This is created by intersecting `string`
3
- * with `{ readonly _: unique symbol }`, making it a unique type.
4
- * Although it is fundamentally a string, it is treated as a distinct type
5
- * due to the unique symbol.
6
- */
7
- export type CacheTag = string & {
8
- readonly _: unique symbol;
9
- };
10
- /**
11
- * A type representing the structure of a webhook payload for cache tag invalidation.
12
- * It includes the entity type, event type, and the entity details which contain
13
- * the cache tags to be invalidated.
14
- */
15
- export type CacheTagsInvalidateWebhook = {
16
- entity_type: 'cda_cache_tags';
17
- event_type: 'invalidate';
18
- entity: {
19
- id: 'cda_cache_tags';
20
- type: 'cda_cache_tags';
21
- attributes: {
22
- tags: CacheTag[];
23
- };
24
- };
25
- };
package/dist/types.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/dist/utils.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/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,96 +0,0 @@
1
- import { Redis } from 'ioredis';
2
- import { type CacheTag } from './types';
3
-
4
- let redis: Redis | null = null;
5
-
6
- const getRedis = (): Redis => {
7
- redis ??= new Redis(process.env.REDIS_URL!, {
8
- maxRetriesPerRequest: 3,
9
- lazyConnect: true,
10
- });
11
-
12
- return redis;
13
- };
14
-
15
- const keyPrefix = process.env.REDIS_KEY_PREFIX ? `${process.env.REDIS_KEY_PREFIX}:` : '';
16
-
17
- /**
18
- * Stores the cache tags of a query in Redis.
19
- *
20
- * For each cache tag, adds the query ID to a Redis Set. Sets are unordered
21
- * collections of unique strings, perfect for tracking which queries use which tags.
22
- *
23
- * @param {string} queryId Unique query ID
24
- * @param {CacheTag[]} cacheTags Array of cache tags
25
- *
26
- */
27
- export const storeQueryCacheTags = async (queryId: string, cacheTags: CacheTag[]): Promise<void> => {
28
- if (!cacheTags?.length) {
29
- return;
30
- }
31
-
32
- const redis = getRedis();
33
- const pipeline = redis.pipeline();
34
-
35
- for (const tag of cacheTags) {
36
- pipeline.sadd(`${keyPrefix}${tag}`, queryId);
37
- }
38
-
39
- await pipeline.exec();
40
- };
41
-
42
- /**
43
- * Retrieves the query IDs that reference any of the specified cache tags.
44
- *
45
- * Uses Redis SUNION to efficiently find all queries associated with the given tags.
46
- *
47
- * @param {CacheTag[]} cacheTags Array of cache tags to check
48
- * @returns Array of unique query IDs
49
- *
50
- */
51
- export const queriesReferencingCacheTags = async (cacheTags: CacheTag[]): Promise<string[]> => {
52
- if (!cacheTags?.length) {
53
- return [];
54
- }
55
-
56
- const redis = getRedis();
57
- const keys = cacheTags.map((tag) => `${keyPrefix}${tag}`);
58
-
59
- return redis.sunion(...keys);
60
- };
61
-
62
- /**
63
- * Deletes the specified cache tags from Redis.
64
- *
65
- * This removes the cache tag keys entirely. When queries are revalidated and
66
- * run again, fresh cache tag mappings will be created.
67
- *
68
- * @param {CacheTag[]} cacheTags Array of cache tags to delete
69
- * @returns Number of keys deleted, or null if there was an error
70
- *
71
- */
72
- export const deleteCacheTags = async (cacheTags: CacheTag[]): Promise<number> => {
73
- if (!cacheTags?.length) {
74
- return 0;
75
- }
76
-
77
- const redis = getRedis();
78
- const keys = cacheTags.map((tag) => `${keyPrefix}${tag}`);
79
-
80
- return redis.del(...keys);
81
- };
82
-
83
- /**
84
- * Wipes out all cache tags from Redis.
85
- *
86
- * ⚠️ **Warning**: This will delete all cache tag data. Use with caution!
87
- */
88
- export const truncateCacheTags = async (): Promise<void> => {
89
- const redis = getRedis();
90
- const pattern = `${keyPrefix}*`;
91
- const keys = await redis.keys(pattern);
92
-
93
- if (keys.length > 0) {
94
- await redis.del(...keys);
95
- }
96
- };
package/src/cache-tags.ts DELETED
@@ -1,88 +0,0 @@
1
- import { sql } from '@vercel/postgres';
2
- import { type CacheTag } from './types';
3
-
4
- export { generateQueryId, parseXCacheTagsResponseHeader } from './utils';
5
-
6
- /**
7
- * Stores the cache tags of a query in the database.
8
- *
9
- * @param {string} queryId Unique query ID
10
- * @param {CacheTag[]} cacheTags Array of cache tags
11
- * @param {string} tableId Database table ID
12
- */
13
- export const storeQueryCacheTags = async (queryId: string, cacheTags: CacheTag[], tableId: string) => {
14
- if (!cacheTags?.length) {
15
- return;
16
- }
17
-
18
- const tags = cacheTags.flatMap((_, i) => [queryId, cacheTags[i]]);
19
- const placeholders = cacheTags.map((_, i) => `($${2 * i + 1}, $${2 * i + 2})`).join(',');
20
-
21
- await sql.query(`INSERT INTO ${tableId} VALUES ${placeholders} ON CONFLICT DO NOTHING`, tags);
22
- };
23
-
24
- /**
25
- * Retrieves the queries that reference cache tags.
26
- *
27
- * @param {CacheTag[]} cacheTags Array of cache tags
28
- * @param {string} tableId Database table ID
29
- * @returns Array of query IDs
30
- */
31
- export const queriesReferencingCacheTags = async (cacheTags: CacheTag[], tableId: string): Promise<string[]> => {
32
- if (!cacheTags?.length) {
33
- return [];
34
- }
35
-
36
- const placeholders = cacheTags.map((_, i) => `$${i + 1}`).join(',');
37
-
38
- const { rows }: { rows: { query_id: string }[] } = await sql.query(
39
- `SELECT DISTINCT query_id FROM ${tableId} WHERE cache_tag IN (${placeholders})`,
40
- cacheTags,
41
- );
42
-
43
- return rows.map((row) => row.query_id);
44
- };
45
-
46
- /**
47
- * Deletes the specified cache tags from the database.
48
- *
49
- * This removes the cache tag keys entirely. When queries are revalidated and
50
- * run again, fresh cache tag mappings will be created.
51
- *
52
- * @param {CacheTag[]} cacheTags Array of cache tags to delete
53
- * @param {string} tableId Database table ID
54
- *
55
- */
56
- export const deleteCacheTags = async (cacheTags: CacheTag[], tableId: string) => {
57
- if (cacheTags.length === 0) {
58
- return;
59
- }
60
- const placeholders = cacheTags.map((_, i) => `$${i + 1}`).join(',');
61
-
62
- await sql.query(`DELETE FROM ${tableId} WHERE cache_tag IN (${placeholders})`, cacheTags);
63
- };
64
-
65
- /**
66
- * Deletes the cache tags of a query from the database.
67
- *
68
- * @param {string} queryId Unique query ID
69
- * @param {string} tableId Database table ID
70
- * @deprecated Use `deleteCacheTags` instead.
71
- */
72
- export const deleteQueries = async (queryIds: string[], tableId: string) => {
73
- if (!queryIds?.length) {
74
- return;
75
- }
76
- const placeholders = queryIds.map((_, i) => `$${i + 1}`).join(',');
77
-
78
- await sql.query(`DELETE FROM ${tableId} WHERE query_id IN (${placeholders})`, queryIds);
79
- };
80
-
81
- /**
82
- * Wipes out all cache tags from the database.
83
- *
84
- * @param {string} tableId Database table ID
85
- */
86
- export async function truncateCacheTags(tableId: string) {
87
- await sql.query(`DELETE FROM ${tableId}`);
88
- }
package/src/types.ts DELETED
@@ -1,24 +0,0 @@
1
- /**
2
- * A branded type for cache tags. This is created by intersecting `string`
3
- * with `{ readonly _: unique symbol }`, making it a unique type.
4
- * Although it is fundamentally a string, it is treated as a distinct type
5
- * due to the unique symbol.
6
- */
7
- export type CacheTag = string & { readonly _: unique symbol };
8
-
9
- /**
10
- * A type representing the structure of a webhook payload for cache tag invalidation.
11
- * It includes the entity type, event type, and the entity details which contain
12
- * the cache tags to be invalidated.
13
- */
14
- export type CacheTagsInvalidateWebhook = {
15
- entity_type: 'cda_cache_tags';
16
- event_type: 'invalidate';
17
- entity: {
18
- id: 'cda_cache_tags';
19
- type: 'cda_cache_tags';
20
- attributes: {
21
- tags: CacheTag[];
22
- };
23
- };
24
- };
File without changes
File without changes