slonik-interceptor-query-cache 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,11 +14,17 @@ Query cache interceptor is initialised using a custom storage service. The [Exam
14
14
 
15
15
  Which queries are cached is controlled using cache attributes. Cache attributes are comments starting with `-- @cache-` prefix. Only queries with cache attributes are cached (see [Cache attributes](#cache-attributes))
16
16
 
17
+ ## Behavior
18
+
19
+ * Does not cache queries inside of a transaction.
20
+
17
21
  ## Cache attributes
18
22
 
19
23
  |Cache attribute|Description|Required?|Default|
20
24
  |---|---|---|---|
21
25
  |`@cache-ttl`|Number (in seconds) to cache the query for.|Yes|N/A|
26
+ |`@cache-key`|Key (`/^[A-Za-z0-9\-_:]+$/`) that uniquely identifies the query.|Yes|N/A|
27
+ |`@cache-hash-values`|Sets this value to `false` to ignore values and only use `@cache-key`.|No|`true`|
22
28
 
23
29
  ### Example usage
24
30
 
@@ -39,19 +45,16 @@ const nodeCache = new NodeCache({
39
45
  useClones: false,
40
46
  });
41
47
 
42
- const hashQuery = (query: QueryType): string => {
43
- return JSON.stringify(query);
44
- };
45
-
46
48
  const pool = await createPool('postgres://', {
47
49
  interceptors: [
48
50
  createQueryCacheInterceptor({
49
51
  storage: {
50
- get: (query) => {
51
- return cache.get(hashQuery(query)) || null;
52
+ get: (query, cacheAttributes) => {
53
+ // Returning null results in the query being executed.
54
+ return cache.get(cacheAttributes.key) || null;
52
55
  },
53
56
  set: (query, cacheAttributes, queryResult) => {
54
- cache.set(hashQuery(query), queryResult, cacheAttributes.ttl);
57
+ cache.set(cacheAttributes.key, queryResult, cacheAttributes.ttl);
55
58
  },
56
59
  },
57
60
  }),
@@ -60,6 +63,7 @@ const pool = await createPool('postgres://', {
60
63
 
61
64
  await connection.any(sql`
62
65
  -- @cache-ttl 60
66
+ -- @cache-key foo
63
67
  SELECT
64
68
  id,
65
69
  code_alpha_2
@@ -67,4 +71,4 @@ await connection.any(sql`
67
71
  WHERE
68
72
  code_alpha_2 = ${countryCode}
69
73
  `);
70
- ```
74
+ ```
@@ -1,5 +1,6 @@
1
1
  import type { Interceptor, Query, QueryResultRow, QueryResult } from 'slonik';
2
2
  declare type CacheAttributes = {
3
+ key: string;
3
4
  ttl: number;
4
5
  };
5
6
  declare type Storage = {
@@ -13,6 +13,9 @@ const createQueryCacheInterceptor = (configurationInput) => {
13
13
  return {
14
14
  beforeQueryExecution: async (context, query) => {
15
15
  var _a;
16
+ if (context.transactionId) {
17
+ return null;
18
+ }
16
19
  const cacheAttributes = (_a = context.sandbox.cache) === null || _a === void 0 ? void 0 : _a.cacheAttributes;
17
20
  if (!cacheAttributes) {
18
21
  return null;
@@ -28,6 +31,9 @@ const createQueryCacheInterceptor = (configurationInput) => {
28
31
  },
29
32
  beforeQueryResult: async (context, query, result) => {
30
33
  var _a;
34
+ if (context.transactionId) {
35
+ return null;
36
+ }
31
37
  const cacheAttributes = (_a = context.sandbox.cache) === null || _a === void 0 ? void 0 : _a.cacheAttributes;
32
38
  if (cacheAttributes) {
33
39
  await configuration.storage.set(query, cacheAttributes, result);
@@ -35,7 +41,10 @@ const createQueryCacheInterceptor = (configurationInput) => {
35
41
  return null;
36
42
  },
37
43
  beforeTransformQuery: async (context, query) => {
38
- const cacheAttributes = (0, utilities_1.extractCacheAttributes)(query.sql);
44
+ if (context.transactionId) {
45
+ return null;
46
+ }
47
+ const cacheAttributes = (0, utilities_1.extractCacheAttributes)(query.sql, query.values);
39
48
  if (!cacheAttributes) {
40
49
  return null;
41
50
  }
@@ -1,3 +1,5 @@
1
- export declare const extractCacheAttributes: (subject: string) => {
1
+ import type { PrimitiveValueExpression } from 'slonik';
2
+ export declare const extractCacheAttributes: (subject: string, values: readonly PrimitiveValueExpression[]) => {
3
+ key: string;
2
4
  ttl: number;
3
5
  } | null;
@@ -1,11 +1,21 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.extractCacheAttributes = void 0;
4
- const extractCacheAttributes = (subject) => {
5
- const matches = /-- @cache-ttl (\d+)/u.exec(subject);
6
- if (matches) {
4
+ const node_crypto_1 = require("node:crypto");
5
+ const extractCacheAttributes = (subject, values) => {
6
+ var _a, _b;
7
+ const ttl = (_a = /-- @cache-ttl (\d+)/u.exec(subject)) === null || _a === void 0 ? void 0 : _a[1];
8
+ if (ttl) {
9
+ let key = (_b = /-- @cache-key ([a-zA-Z0-9\-_:/]+)/ui.exec(subject)) === null || _b === void 0 ? void 0 : _b[1];
10
+ if (!key) {
11
+ throw new Error('@cache-key must be specified when @cache-ttl is specified.');
12
+ }
13
+ if (!/-- @cache-hash-values false/ui.test(subject)) {
14
+ key += ':' + (0, node_crypto_1.createHash)('sha256').update(JSON.stringify(values)).digest('hex');
15
+ }
7
16
  return {
8
- ttl: Number(matches[1]),
17
+ key,
18
+ ttl: Number(ttl),
9
19
  };
10
20
  }
11
21
  return null;
package/package.json CHANGED
@@ -57,7 +57,7 @@
57
57
  "test": "NODE_ENV=test nyc ava --verbose --serial"
58
58
  },
59
59
  "typings": "./dist/src/index.d.ts",
60
- "version": "2.1.0",
60
+ "version": "2.3.0",
61
61
  "peerDependencies": {
62
62
  "slonik": ">=27.0.0"
63
63
  },
@@ -22,6 +22,7 @@ type Sandbox = {
22
22
  };
23
23
 
24
24
  type CacheAttributes = {
25
+ key: string,
25
26
  ttl: number,
26
27
  };
27
28
 
@@ -45,6 +46,10 @@ export const createQueryCacheInterceptor = (configurationInput: ConfigurationInp
45
46
 
46
47
  return {
47
48
  beforeQueryExecution: async (context, query) => {
49
+ if (context.transactionId) {
50
+ return null;
51
+ }
52
+
48
53
  const cacheAttributes = (context.sandbox as Sandbox).cache?.cacheAttributes;
49
54
 
50
55
  if (!cacheAttributes) {
@@ -64,6 +69,10 @@ export const createQueryCacheInterceptor = (configurationInput: ConfigurationInp
64
69
  return null;
65
70
  },
66
71
  beforeQueryResult: async (context, query, result) => {
72
+ if (context.transactionId) {
73
+ return null;
74
+ }
75
+
67
76
  const cacheAttributes = (context.sandbox as Sandbox).cache?.cacheAttributes;
68
77
 
69
78
  if (cacheAttributes) {
@@ -73,7 +82,11 @@ export const createQueryCacheInterceptor = (configurationInput: ConfigurationInp
73
82
  return null;
74
83
  },
75
84
  beforeTransformQuery: async (context, query) => {
76
- const cacheAttributes = extractCacheAttributes(query.sql);
85
+ if (context.transactionId) {
86
+ return null;
87
+ }
88
+
89
+ const cacheAttributes = extractCacheAttributes(query.sql, query.values);
77
90
 
78
91
  if (!cacheAttributes) {
79
92
  return null;
@@ -1,9 +1,27 @@
1
- export const extractCacheAttributes = (subject: string) => {
2
- const matches = /-- @cache-ttl (\d+)/u.exec(subject);
1
+ import {
2
+ createHash,
3
+ } from 'node:crypto';
4
+ import type {
5
+ PrimitiveValueExpression,
6
+ } from 'slonik';
7
+
8
+ export const extractCacheAttributes = (subject: string, values: readonly PrimitiveValueExpression[]) => {
9
+ const ttl = /-- @cache-ttl (\d+)/u.exec(subject)?.[1];
10
+
11
+ if (ttl) {
12
+ let key = /-- @cache-key ([a-zA-Z0-9\-_:/]+)/ui.exec(subject)?.[1];
13
+
14
+ if (!key) {
15
+ throw new Error('@cache-key must be specified when @cache-ttl is specified.');
16
+ }
17
+
18
+ if (!/-- @cache-hash-values false/ui.test(subject)) {
19
+ key += ':' + createHash('sha256').update(JSON.stringify(values)).digest('hex');
20
+ }
3
21
 
4
- if (matches) {
5
22
  return {
6
- ttl: Number(matches[1]),
23
+ key,
24
+ ttl: Number(ttl),
7
25
  };
8
26
  }
9
27