slonik-interceptor-query-cache 2.4.1 → 3.1.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.
Files changed (35) hide show
  1. package/README.md +36 -8
  2. package/dist/{src/factories → factories}/createQueryCacheInterceptor.d.ts +2 -1
  3. package/dist/{src/factories → factories}/createQueryCacheInterceptor.js +14 -8
  4. package/dist/factories/createQueryContext.d.ts +3 -0
  5. package/dist/factories/createQueryContext.js +16 -0
  6. package/dist/factories/index.d.ts +1 -0
  7. package/dist/index.d.ts +1 -0
  8. package/dist/utilities/extractCacheAttributes.d.ts +9 -0
  9. package/dist/utilities/extractCacheAttributes.js +39 -0
  10. package/dist/utilities/extractCacheAttributes.test.d.ts +1 -0
  11. package/dist/utilities/extractCacheAttributes.test.js +76 -0
  12. package/dist/utilities/index.d.ts +2 -0
  13. package/dist/utilities/index.js +7 -0
  14. package/dist/utilities/normalizeCacheAttributes.d.ts +3 -0
  15. package/dist/utilities/normalizeCacheAttributes.js +13 -0
  16. package/dist/utilities/normalizeCacheAttributes.test.d.ts +1 -0
  17. package/dist/utilities/normalizeCacheAttributes.test.js +20 -0
  18. package/package.json +16 -16
  19. package/dist/src/factories/index.d.ts +0 -1
  20. package/dist/src/index.d.ts +0 -1
  21. package/dist/src/utilities/extractCacheAttributes.d.ts +0 -5
  22. package/dist/src/utilities/extractCacheAttributes.js +0 -23
  23. package/dist/src/utilities/index.d.ts +0 -1
  24. package/dist/src/utilities/index.js +0 -5
  25. package/src/Logger.ts +0 -7
  26. package/src/factories/createQueryCacheInterceptor.ts +0 -102
  27. package/src/factories/index.ts +0 -3
  28. package/src/index.ts +0 -3
  29. package/src/utilities/extractCacheAttributes.ts +0 -29
  30. package/src/utilities/index.ts +0 -3
  31. package/tsconfig.json +0 -26
  32. /package/dist/{src/Logger.d.ts → Logger.d.ts} +0 -0
  33. /package/dist/{src/Logger.js → Logger.js} +0 -0
  34. /package/dist/{src/factories → factories}/index.js +0 -0
  35. /package/dist/{src/index.js → index.js} +0 -0
package/README.md CHANGED
@@ -1,7 +1,5 @@
1
1
  # slonik-interceptor-query-cache
2
2
 
3
- [![Travis build status](http://img.shields.io/travis/gajus/slonik-interceptor-query-cache/master.svg?style=flat-square)](https://travis-ci.com/github/gajus/slonik-interceptor-query-cache)
4
- [![Coveralls](https://img.shields.io/coveralls/gajus/slonik-interceptor-query-cache.svg?style=flat-square)](https://coveralls.io/github/gajus/slonik-interceptor-query-cache)
5
3
  [![NPM version](http://img.shields.io/npm/v/slonik-interceptor-query-cache.svg?style=flat-square)](https://www.npmjs.org/package/slonik-interceptor-query-cache)
6
4
  [![Canonical Code Style](https://img.shields.io/badge/code%20style-canonical-blue.svg?style=flat-square)](https://github.com/gajus/canonical)
7
5
  [![Twitter Follow](https://img.shields.io/twitter/follow/kuizinas.svg?style=social&label=Follow)](https://twitter.com/kuizinas)
@@ -10,7 +8,7 @@ Caches [Slonik](https://github.com/gajus/slonik) query results.
10
8
 
11
9
  ## Usage
12
10
 
13
- Query cache interceptor is initialised using a custom storage service. The [Example Usage](#example-usage) documentation shows how to create a compatible storage service using [`node-cache`](https://www.npmjs.com/package/node-cache).
11
+ Query cache interceptor is initialized using a custom storage service. The [Example Usage](#example-usage) documentation shows how to create a compatible storage service using [`node-cache`](https://www.npmjs.com/package/node-cache).
14
12
 
15
13
  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
14
 
@@ -20,11 +18,17 @@ Which queries are cached is controlled using cache attributes. Cache attributes
20
18
 
21
19
  ## Cache attributes
22
20
 
23
- |Cache attribute|Description|Required?|Default|
24
- |---|---|---|---|
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`|
21
+ |Cache attribute|Description|Required?|Format|Default|
22
+ |---|---|---|---|---|
23
+ |`@cache-ttl`|Number (in seconds) to cache the query for.|Yes|`/^d+$/`|N/A|
24
+ |`@cache-key`|Cache key that uniquelly identifies the query.|No|`/^[$A-Za-z0-9\-_:]+$/`|`$bodyHash:$valueHash`|
25
+ |`@cache-discard-empty`|If set to `true`, then `storage.set` is not invoked when query produces no results.|No|`/^(false|true)$/`|`false`|
26
+
27
+ ### `@cache-key`
28
+
29
+ Overrides the default cache key that uniquely identifies the query.
30
+
31
+ If present, `$bodyHash` is substituted with the hash of the query (comments and white-spaces are stripped before hashing the query). `$valueHash` is substituted with the hash of the parameter values.
28
32
 
29
33
  ### Example usage
30
34
 
@@ -61,6 +65,30 @@ const pool = await createPool('postgres://', {
61
65
  ]
62
66
  });
63
67
 
68
+ // Caches the query results based on a combination of the query hash and the parameter value hash.
69
+ await connection.any(sql`
70
+ -- @cache-ttl 60
71
+ SELECT
72
+ id,
73
+ code_alpha_2
74
+ FROM country
75
+ WHERE
76
+ code_alpha_2 = ${countryCode}
77
+ `);
78
+
79
+ // Caches the query results based only on the parameter value hash.
80
+ await connection.any(sql`
81
+ -- @cache-ttl 60
82
+ -- @cache-key $bodyHash
83
+ SELECT
84
+ id,
85
+ code_alpha_2
86
+ FROM country
87
+ WHERE
88
+ code_alpha_2 = ${countryCode}
89
+ `);
90
+
91
+ // Caches the query results using 'foo' key.
64
92
  await connection.any(sql`
65
93
  -- @cache-ttl 60
66
94
  -- @cache-key foo
@@ -1,5 +1,6 @@
1
1
  import { type Interceptor, type Query, type QueryResult, type QueryResultRow } from 'slonik';
2
- type CacheAttributes = {
2
+ export type CacheAttributes = {
3
+ discardEmpty: boolean;
3
4
  key: string;
4
5
  ttl: number;
5
6
  };
@@ -12,17 +12,17 @@ const createQueryCacheInterceptor = (configurationInput) => {
12
12
  };
13
13
  return {
14
14
  beforeQueryExecution: async (context, query) => {
15
- var _a;
16
15
  if (context.transactionId) {
17
16
  return null;
18
17
  }
19
- const cacheAttributes = (_a = context.sandbox.cache) === null || _a === void 0 ? void 0 : _a.cacheAttributes;
18
+ const cacheAttributes = context.sandbox.cache
19
+ ?.cacheAttributes;
20
20
  if (!cacheAttributes) {
21
21
  return null;
22
22
  }
23
23
  const maybeResult = await configuration.storage.get(query, cacheAttributes);
24
24
  if (maybeResult) {
25
- log.info({
25
+ log.debug({
26
26
  queryId: context.queryId,
27
27
  }, 'query is served from cache');
28
28
  return maybeResult;
@@ -30,13 +30,18 @@ const createQueryCacheInterceptor = (configurationInput) => {
30
30
  return null;
31
31
  },
32
32
  beforeQueryResult: async (context, query, result) => {
33
- var _a;
34
33
  if (context.transactionId) {
35
34
  return null;
36
35
  }
37
- const cacheAttributes = (_a = context.sandbox.cache) === null || _a === void 0 ? void 0 : _a.cacheAttributes;
36
+ const cacheAttributes = context.sandbox.cache
37
+ ?.cacheAttributes;
38
38
  if (cacheAttributes) {
39
- await configuration.storage.set(query, cacheAttributes, result);
39
+ if (cacheAttributes.discardEmpty && result.rowCount === 0) {
40
+ log.debug('@cache-discard-empty is set and the query result is empty; not caching');
41
+ }
42
+ else {
43
+ await configuration.storage.set(query, cacheAttributes, result);
44
+ }
40
45
  }
41
46
  return null;
42
47
  },
@@ -44,10 +49,11 @@ const createQueryCacheInterceptor = (configurationInput) => {
44
49
  if (context.transactionId) {
45
50
  return null;
46
51
  }
47
- const cacheAttributes = (0, utilities_1.extractCacheAttributes)(query.sql, query.values);
48
- if (!cacheAttributes) {
52
+ const extractedCacheAttributes = (0, utilities_1.extractCacheAttributes)(query.sql, query.values);
53
+ if (!extractedCacheAttributes) {
49
54
  return null;
50
55
  }
56
+ const cacheAttributes = (0, utilities_1.normalizeCacheAttributes)(extractedCacheAttributes);
51
57
  context.sandbox.cache = {
52
58
  cacheAttributes,
53
59
  };
@@ -0,0 +1,3 @@
1
+ import { type QueryContext } from 'slonik';
2
+ declare const _default: () => QueryContext;
3
+ export default _default;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = () => {
4
+ return {
5
+ connectionId: '1',
6
+ log: {
7
+ getContext: () => {
8
+ return {
9
+ connectionId: '1',
10
+ poolId: '1',
11
+ };
12
+ },
13
+ },
14
+ poolId: '1',
15
+ };
16
+ };
@@ -0,0 +1 @@
1
+ export { createQueryCacheInterceptor } from './createQueryCacheInterceptor';
@@ -0,0 +1 @@
1
+ export { createQueryCacheInterceptor } from './factories';
@@ -0,0 +1,9 @@
1
+ import { type PrimitiveValueExpression } from 'slonik';
2
+ export type ExtractedCacheAttributes = {
3
+ bodyHash: string;
4
+ discardEmpty: boolean;
5
+ key: string;
6
+ ttl: number;
7
+ valueHash: string;
8
+ };
9
+ export declare const extractCacheAttributes: (subject: string, values: readonly PrimitiveValueExpression[]) => ExtractedCacheAttributes | null;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.extractCacheAttributes = void 0;
7
+ const node_crypto_1 = require("node:crypto");
8
+ const strip_comments_1 = __importDefault(require("strip-comments"));
9
+ const hash = (subject) => {
10
+ return (0, node_crypto_1.createHash)('shake256', {
11
+ outputLength: 12,
12
+ })
13
+ .update(subject)
14
+ .digest('hex');
15
+ };
16
+ // TODO throw an error if an unknown attribute is used
17
+ const extractCacheAttributes = (subject, values) => {
18
+ const ttl = /-- @cache-ttl (\d+)/u.exec(subject)?.[1];
19
+ // https://github.com/jonschlinkert/strip-comments/issues/71
20
+ const bodyHash = hash((0, strip_comments_1.default)(subject)
21
+ .replaceAll(/^\s*--.*$/gmu, '')
22
+ .replaceAll(/\s/gu, ''));
23
+ const discardEmpty = (/-- @cache-discard-empty (true|false)/u.exec(subject)?.[1] ?? 'false') ===
24
+ 'true';
25
+ const valueHash = hash(JSON.stringify(values));
26
+ if (ttl) {
27
+ const key = /-- @cache-key ([$\w\-:/]+)/iu.exec(subject)?.[1] ??
28
+ 'query:$bodyHash:$valueHash';
29
+ return {
30
+ bodyHash,
31
+ discardEmpty,
32
+ key,
33
+ ttl: Number(ttl),
34
+ valueHash,
35
+ };
36
+ }
37
+ return null;
38
+ };
39
+ exports.extractCacheAttributes = extractCacheAttributes;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const extractCacheAttributes_1 = require("./extractCacheAttributes");
7
+ const ava_1 = __importDefault(require("ava"));
8
+ (0, ava_1.default)('returns null when query does not contain cache attributes', (t) => {
9
+ t.is((0, extractCacheAttributes_1.extractCacheAttributes)('', []), null);
10
+ });
11
+ (0, ava_1.default)('extracts @cache-ttl', (t) => {
12
+ t.deepEqual((0, extractCacheAttributes_1.extractCacheAttributes)('-- @cache-ttl 60', []), {
13
+ bodyHash: '46b9dd2b0ba88d13233b3feb',
14
+ discardEmpty: false,
15
+ key: 'query:$bodyHash:$valueHash',
16
+ ttl: 60,
17
+ valueHash: 'ec784925b52067bce01fd820',
18
+ });
19
+ });
20
+ (0, ava_1.default)('extracts @cache-discard-empty', (t) => {
21
+ t.deepEqual((0, extractCacheAttributes_1.extractCacheAttributes)('-- @cache-ttl 60\n-- @cache-discard-empty false', []), {
22
+ bodyHash: '46b9dd2b0ba88d13233b3feb',
23
+ discardEmpty: false,
24
+ key: 'query:$bodyHash:$valueHash',
25
+ ttl: 60,
26
+ valueHash: 'ec784925b52067bce01fd820',
27
+ });
28
+ t.deepEqual((0, extractCacheAttributes_1.extractCacheAttributes)('-- @cache-ttl 60\n-- @cache-discard-empty true', []), {
29
+ bodyHash: '46b9dd2b0ba88d13233b3feb',
30
+ discardEmpty: true,
31
+ key: 'query:$bodyHash:$valueHash',
32
+ ttl: 60,
33
+ valueHash: 'ec784925b52067bce01fd820',
34
+ });
35
+ });
36
+ (0, ava_1.default)('computes the parameter value hash', (t) => {
37
+ t.deepEqual((0, extractCacheAttributes_1.extractCacheAttributes)('-- @cache-ttl 60', [1]), {
38
+ bodyHash: '46b9dd2b0ba88d13233b3feb',
39
+ discardEmpty: false,
40
+ key: 'query:$bodyHash:$valueHash',
41
+ ttl: 60,
42
+ valueHash: '62eb54746ae2932850f8d6ff',
43
+ });
44
+ });
45
+ (0, ava_1.default)('computes the body hash; white spaces do not affect the body hash', (t) => {
46
+ t.deepEqual((0, extractCacheAttributes_1.extractCacheAttributes)('-- @cache-ttl 60\nSELECT 1', []), {
47
+ bodyHash: '553ebdb024592064ca4c2c3a',
48
+ discardEmpty: false,
49
+ key: 'query:$bodyHash:$valueHash',
50
+ ttl: 60,
51
+ valueHash: 'ec784925b52067bce01fd820',
52
+ });
53
+ t.deepEqual((0, extractCacheAttributes_1.extractCacheAttributes)('-- @cache-ttl 60\n\nSELECT 1', []), {
54
+ bodyHash: '553ebdb024592064ca4c2c3a',
55
+ discardEmpty: false,
56
+ key: 'query:$bodyHash:$valueHash',
57
+ ttl: 60,
58
+ valueHash: 'ec784925b52067bce01fd820',
59
+ });
60
+ });
61
+ (0, ava_1.default)('computes the body hash; comments do not affect the body hash', (t) => {
62
+ t.deepEqual((0, extractCacheAttributes_1.extractCacheAttributes)('-- @cache-ttl 60\nSELECT 1', []), {
63
+ bodyHash: '553ebdb024592064ca4c2c3a',
64
+ discardEmpty: false,
65
+ key: 'query:$bodyHash:$valueHash',
66
+ ttl: 60,
67
+ valueHash: 'ec784925b52067bce01fd820',
68
+ });
69
+ t.deepEqual((0, extractCacheAttributes_1.extractCacheAttributes)('-- @cache-ttl 120\nSELECT 1', []), {
70
+ bodyHash: '553ebdb024592064ca4c2c3a',
71
+ discardEmpty: false,
72
+ key: 'query:$bodyHash:$valueHash',
73
+ ttl: 120,
74
+ valueHash: 'ec784925b52067bce01fd820',
75
+ });
76
+ });
@@ -0,0 +1,2 @@
1
+ export { extractCacheAttributes } from './extractCacheAttributes';
2
+ export { normalizeCacheAttributes } from './normalizeCacheAttributes';
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeCacheAttributes = exports.extractCacheAttributes = void 0;
4
+ var extractCacheAttributes_1 = require("./extractCacheAttributes");
5
+ Object.defineProperty(exports, "extractCacheAttributes", { enumerable: true, get: function () { return extractCacheAttributes_1.extractCacheAttributes; } });
6
+ var normalizeCacheAttributes_1 = require("./normalizeCacheAttributes");
7
+ Object.defineProperty(exports, "normalizeCacheAttributes", { enumerable: true, get: function () { return normalizeCacheAttributes_1.normalizeCacheAttributes; } });
@@ -0,0 +1,3 @@
1
+ import { type CacheAttributes } from '../factories/createQueryCacheInterceptor';
2
+ import { type ExtractedCacheAttributes } from './extractCacheAttributes';
3
+ export declare const normalizeCacheAttributes: (extractedCacheAttributes: ExtractedCacheAttributes) => CacheAttributes;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeCacheAttributes = void 0;
4
+ const normalizeCacheAttributes = (extractedCacheAttributes) => {
5
+ return {
6
+ discardEmpty: extractedCacheAttributes.discardEmpty,
7
+ key: extractedCacheAttributes.key
8
+ .replaceAll('$bodyHash', extractedCacheAttributes.bodyHash)
9
+ .replaceAll('$valueHash', extractedCacheAttributes.valueHash),
10
+ ttl: extractedCacheAttributes.ttl,
11
+ };
12
+ };
13
+ exports.normalizeCacheAttributes = normalizeCacheAttributes;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const normalizeCacheAttributes_1 = require("./normalizeCacheAttributes");
7
+ const ava_1 = __importDefault(require("ava"));
8
+ (0, ava_1.default)('replaces $bodyHash and $valueHash', (t) => {
9
+ t.deepEqual((0, normalizeCacheAttributes_1.normalizeCacheAttributes)({
10
+ bodyHash: 'foo',
11
+ discardEmpty: false,
12
+ key: '$bodyHash:$valueHash',
13
+ ttl: 60,
14
+ valueHash: 'bar',
15
+ }), {
16
+ discardEmpty: false,
17
+ key: 'foo:bar',
18
+ ttl: 60,
19
+ });
20
+ });
package/package.json CHANGED
@@ -9,12 +9,16 @@
9
9
  "ts"
10
10
  ],
11
11
  "files": [
12
- "test/slonik-interceptor-query-cache/**/*"
12
+ "src/**/*.test.ts"
13
13
  ],
14
14
  "require": [
15
15
  "ts-node/register/transpile-only"
16
16
  ]
17
17
  },
18
+ "dependencies": {
19
+ "roarr": "^7.14.0",
20
+ "strip-comments": "^2.0.1"
21
+ },
18
22
  "description": "Cache Slonik query results.",
19
23
  "devDependencies": {
20
24
  "@istanbuljs/nyc-config-typescript": "^1.0.2",
@@ -24,9 +28,9 @@
24
28
  "ava": "^3.15.0",
25
29
  "coveralls": "^3.1.1",
26
30
  "del-cli": "^4.0.1",
27
- "eslint-config-canonical": "^41.0.3",
28
31
  "eslint": "^8.39.0",
29
- "husky": "^7.0.4",
32
+ "eslint-config-canonical": "^41.0.3",
33
+ "husky": "^8.0.0",
30
34
  "inline-loops.macro": "^1.2.2",
31
35
  "nyc": "^15.1.0",
32
36
  "semantic-release": "^20.1.3",
@@ -37,11 +41,9 @@
37
41
  "engines": {
38
42
  "node": ">=16.0"
39
43
  },
40
- "husky": {
41
- "hooks": {
42
- "pre-commit": "npm run lint && npm run test && npm run build"
43
- }
44
- },
44
+ "files": [
45
+ "dist"
46
+ ],
45
47
  "keywords": [
46
48
  "postgresql",
47
49
  "interceptor",
@@ -50,22 +52,20 @@
50
52
  "license": "BSD-3-Clause",
51
53
  "main": "./dist/src/index.js",
52
54
  "name": "slonik-interceptor-query-cache",
55
+ "peerDependencies": {
56
+ "slonik": ">=27.0.0"
57
+ },
53
58
  "repository": {
54
59
  "type": "git",
55
60
  "url": "https://github.com/gajus/slonik-interceptor-query-cache"
56
61
  },
57
62
  "scripts": {
58
63
  "build": "del-cli ./dist && tsc",
59
- "lint:eslint": "eslint ./src ./test",
64
+ "lint:eslint": "eslint .",
60
65
  "lint:tsc": "tsc --noEmit",
66
+ "prepare": "husky install",
61
67
  "test:ava": "nyc ava --verbose --serial"
62
68
  },
63
69
  "typings": "./dist/src/index.d.ts",
64
- "version": "2.4.1",
65
- "peerDependencies": {
66
- "slonik": ">=27.0.0"
67
- },
68
- "dependencies": {
69
- "roarr": "^7.14.0"
70
- }
70
+ "version": "3.1.0"
71
71
  }
@@ -1 +0,0 @@
1
- export { createQueryCacheInterceptor, } from './createQueryCacheInterceptor';
@@ -1 +0,0 @@
1
- export { createQueryCacheInterceptor, } from './factories';
@@ -1,5 +0,0 @@
1
- import { type PrimitiveValueExpression } from 'slonik';
2
- export declare const extractCacheAttributes: (subject: string, values: readonly PrimitiveValueExpression[]) => {
3
- key: string;
4
- ttl: number;
5
- } | null;
@@ -1,23 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.extractCacheAttributes = void 0;
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) && values.length > 0) {
14
- key += ':' + (0, node_crypto_1.createHash)('sha256').update(JSON.stringify(values)).digest('hex');
15
- }
16
- return {
17
- key,
18
- ttl: Number(ttl),
19
- };
20
- }
21
- return null;
22
- };
23
- exports.extractCacheAttributes = extractCacheAttributes;
@@ -1 +0,0 @@
1
- export { extractCacheAttributes, } from './extractCacheAttributes';
@@ -1,5 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.extractCacheAttributes = void 0;
4
- var extractCacheAttributes_1 = require("./extractCacheAttributes");
5
- Object.defineProperty(exports, "extractCacheAttributes", { enumerable: true, get: function () { return extractCacheAttributes_1.extractCacheAttributes; } });
package/src/Logger.ts DELETED
@@ -1,7 +0,0 @@
1
- import {
2
- Roarr,
3
- } from 'roarr';
4
-
5
- export const Logger = Roarr.child({
6
- package: 'slonik-interceptor-query-cache',
7
- });
@@ -1,102 +0,0 @@
1
- import {
2
- Logger,
3
- } from '../Logger';
4
- import {
5
- extractCacheAttributes,
6
- } from '../utilities';
7
- import {
8
- type Interceptor,
9
- type Query,
10
- type QueryResult,
11
- type QueryResultRow,
12
- } from 'slonik';
13
-
14
- const log = Logger.child({
15
- namespace: 'createQueryCacheInterceptor',
16
- });
17
-
18
- type Sandbox = {
19
- cache: {
20
- cacheAttributes: CacheAttributes,
21
- },
22
- };
23
-
24
- type CacheAttributes = {
25
- key: string,
26
- ttl: number,
27
- };
28
-
29
- type Storage = {
30
- get: (query: Query, cacheAttributes: CacheAttributes) => Promise<QueryResult<QueryResultRow> | null>,
31
- set: (query: Query, cacheAttributes: CacheAttributes, queryResult: QueryResult<QueryResultRow>) => Promise<void>,
32
- };
33
-
34
- type ConfigurationInput = {
35
- storage: Storage,
36
- };
37
-
38
- type Configuration = {
39
- storage: Storage,
40
- };
41
-
42
- export const createQueryCacheInterceptor = (configurationInput: ConfigurationInput): Interceptor => {
43
- const configuration: Configuration = {
44
- ...configurationInput,
45
- };
46
-
47
- return {
48
- beforeQueryExecution: async (context, query) => {
49
- if (context.transactionId) {
50
- return null;
51
- }
52
-
53
- const cacheAttributes = (context.sandbox as Sandbox).cache?.cacheAttributes;
54
-
55
- if (!cacheAttributes) {
56
- return null;
57
- }
58
-
59
- const maybeResult = await configuration.storage.get(query, cacheAttributes);
60
-
61
- if (maybeResult) {
62
- log.info({
63
- queryId: context.queryId,
64
- }, 'query is served from cache');
65
-
66
- return maybeResult;
67
- }
68
-
69
- return null;
70
- },
71
- beforeQueryResult: async (context, query, result) => {
72
- if (context.transactionId) {
73
- return null;
74
- }
75
-
76
- const cacheAttributes = (context.sandbox as Sandbox).cache?.cacheAttributes;
77
-
78
- if (cacheAttributes) {
79
- await configuration.storage.set(query, cacheAttributes, result);
80
- }
81
-
82
- return null;
83
- },
84
- beforeTransformQuery: async (context, query) => {
85
- if (context.transactionId) {
86
- return null;
87
- }
88
-
89
- const cacheAttributes = extractCacheAttributes(query.sql, query.values);
90
-
91
- if (!cacheAttributes) {
92
- return null;
93
- }
94
-
95
- context.sandbox.cache = {
96
- cacheAttributes,
97
- };
98
-
99
- return null;
100
- },
101
- };
102
- };
@@ -1,3 +0,0 @@
1
- export {
2
- createQueryCacheInterceptor,
3
- } from './createQueryCacheInterceptor';
package/src/index.ts DELETED
@@ -1,3 +0,0 @@
1
- export {
2
- createQueryCacheInterceptor,
3
- } from './factories';
@@ -1,29 +0,0 @@
1
- import {
2
- createHash,
3
- } from 'node:crypto';
4
- import {
5
- type 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) && values.length > 0) {
19
- key += ':' + createHash('sha256').update(JSON.stringify(values)).digest('hex');
20
- }
21
-
22
- return {
23
- key,
24
- ttl: Number(ttl),
25
- };
26
- }
27
-
28
- return null;
29
- };
@@ -1,3 +0,0 @@
1
- export {
2
- extractCacheAttributes,
3
- } from './extractCacheAttributes';
package/tsconfig.json DELETED
@@ -1,26 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "allowSyntheticDefaultImports": true,
4
- "declaration": true,
5
- "esModuleInterop": true,
6
- "forceConsistentCasingInFileNames": true,
7
- "module": "commonjs",
8
- "moduleResolution": "node",
9
- "noImplicitAny": false,
10
- "noImplicitReturns": true,
11
- "noUnusedLocals": true,
12
- "noUnusedParameters": false,
13
- "outDir": "dist",
14
- "skipLibCheck": true,
15
- "strict": true,
16
- "target": "es2018"
17
- },
18
- "exclude": [
19
- "dist",
20
- "node_modules"
21
- ],
22
- "include": [
23
- "src",
24
- "test"
25
- ]
26
- }
File without changes
File without changes
File without changes
File without changes