slonik-interceptor-query-cache 2.4.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/LICENSE +3 -3
  2. package/README.md +30 -8
  3. package/dist/{src/Logger.d.ts → Logger.d.ts} +1 -1
  4. package/dist/{src/factories → factories}/createQueryCacheInterceptor.d.ts +5 -4
  5. package/dist/{src/factories → factories}/createQueryCacheInterceptor.js +14 -8
  6. package/dist/factories/createQueryContext.d.ts +3 -0
  7. package/dist/factories/createQueryContext.js +16 -0
  8. package/dist/factories/index.d.ts +1 -0
  9. package/dist/index.d.ts +1 -0
  10. package/dist/utilities/extractCacheAttributes.d.ts +9 -0
  11. package/dist/utilities/extractCacheAttributes.js +39 -0
  12. package/dist/utilities/extractCacheAttributes.test.d.ts +1 -0
  13. package/dist/utilities/extractCacheAttributes.test.js +76 -0
  14. package/dist/utilities/index.d.ts +2 -0
  15. package/dist/utilities/index.js +7 -0
  16. package/dist/utilities/normalizeCacheAttributes.d.ts +3 -0
  17. package/dist/utilities/normalizeCacheAttributes.js +13 -0
  18. package/dist/utilities/normalizeCacheAttributes.test.d.ts +1 -0
  19. package/dist/utilities/normalizeCacheAttributes.test.js +20 -0
  20. package/package.json +26 -22
  21. package/dist/src/factories/index.d.ts +0 -1
  22. package/dist/src/index.d.ts +0 -1
  23. package/dist/src/utilities/extractCacheAttributes.d.ts +0 -5
  24. package/dist/src/utilities/extractCacheAttributes.js +0 -23
  25. package/dist/src/utilities/index.d.ts +0 -1
  26. package/dist/src/utilities/index.js +0 -5
  27. package/src/Logger.ts +0 -7
  28. package/src/factories/createQueryCacheInterceptor.ts +0 -102
  29. package/src/factories/index.ts +0 -3
  30. package/src/index.ts +0 -3
  31. package/src/utilities/extractCacheAttributes.ts +0 -29
  32. package/src/utilities/index.ts +0 -3
  33. package/tsconfig.json +0 -26
  34. /package/dist/{src/Logger.js → Logger.js} +0 -0
  35. /package/dist/{src/factories → factories}/index.js +0 -0
  36. /package/dist/{src/index.js → index.js} +0 -0
package/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2022, Gajus Kuizinas (http://gajus.com/)
1
+ Copyright (c) 2023, Gajus Kuizinas (https://gajus.com/)
2
2
  All rights reserved.
3
3
 
4
4
  Redistribution and use in source and binary forms, with or without
@@ -8,14 +8,14 @@ modification, are permitted provided that the following conditions are met:
8
8
  * Redistributions in binary form must reproduce the above copyright
9
9
  notice, this list of conditions and the following disclaimer in the
10
10
  documentation and/or other materials provided with the distribution.
11
- * Neither the name of the Gajus Kuizinas (http://gajus.com/) nor the
11
+ * Neither the name of the Gajus Kuizinas (https://gajus.com/) nor the
12
12
  names of its contributors may be used to endorse or promote products
13
13
  derived from this software without specific prior written permission.
14
14
 
15
15
  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16
16
  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17
17
  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
- DISCLAIMED. IN NO EVENT SHALL ANUARY BE LIABLE FOR ANY
18
+ DISCLAIMED. IN NO EVENT SHALL GAJUS KUIZINAS BE LIABLE FOR ANY
19
19
  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20
20
  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21
21
  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
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,11 @@ 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`|Uniquelly identifies the query. Overrides the default cache key that uniquely identifies the query. 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.|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`|
28
26
 
29
27
  ### Example usage
30
28
 
@@ -61,6 +59,30 @@ const pool = await createPool('postgres://', {
61
59
  ]
62
60
  });
63
61
 
62
+ // Caches the query results based on a combination of the query hash and the parameter value hash.
63
+ await connection.any(sql`
64
+ -- @cache-ttl 60
65
+ SELECT
66
+ id,
67
+ code_alpha_2
68
+ FROM country
69
+ WHERE
70
+ code_alpha_2 = ${countryCode}
71
+ `);
72
+
73
+ // Caches the query results based only on the parameter value hash.
74
+ await connection.any(sql`
75
+ -- @cache-ttl 60
76
+ -- @cache-key $bodyHash
77
+ SELECT
78
+ id,
79
+ code_alpha_2
80
+ FROM country
81
+ WHERE
82
+ code_alpha_2 = ${countryCode}
83
+ `);
84
+
85
+ // Caches the query results using 'foo' key.
64
86
  await connection.any(sql`
65
87
  -- @cache-ttl 60
66
88
  -- @cache-key foo
@@ -1 +1 @@
1
- export declare const Logger: import("roarr").Logger<import("roarr/dist/src/types").JsonObject>;
1
+ export declare const Logger: import("roarr").Logger<import("roarr/dist/types").JsonObject>;
@@ -1,13 +1,14 @@
1
- import type { Interceptor, Query, QueryResultRow, QueryResult } from 'slonik';
2
- declare type CacheAttributes = {
1
+ import { type Interceptor, type Query, type QueryResult, type QueryResultRow } from 'slonik';
2
+ export type CacheAttributes = {
3
+ discardEmpty: boolean;
3
4
  key: string;
4
5
  ttl: number;
5
6
  };
6
- declare type Storage = {
7
+ type Storage = {
7
8
  get: (query: Query, cacheAttributes: CacheAttributes) => Promise<QueryResult<QueryResultRow> | null>;
8
9
  set: (query: Query, cacheAttributes: CacheAttributes, queryResult: QueryResult<QueryResultRow>) => Promise<void>;
9
10
  };
10
- declare type ConfigurationInput = {
11
+ type ConfigurationInput = {
11
12
  storage: Storage;
12
13
  };
13
14
  export declare const createQueryCacheInterceptor: (configurationInput: ConfigurationInput) => Interceptor;
@@ -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,36 +9,41 @@
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",
25
+ "@semantic-release/commit-analyzer": "^9.0.2",
26
+ "@semantic-release/github": "^8.0.7",
27
+ "@semantic-release/npm": "^9.0.2",
21
28
  "ava": "^3.15.0",
22
29
  "coveralls": "^3.1.1",
23
30
  "del-cli": "^4.0.1",
24
- "eslint": "^8.4.1",
25
- "eslint-config-canonical": "^32.46.0",
26
- "husky": "^7.0.4",
31
+ "eslint": "^8.39.0",
32
+ "eslint-config-canonical": "^41.0.3",
33
+ "husky": "^8.0.0",
27
34
  "inline-loops.macro": "^1.2.2",
28
35
  "nyc": "^15.1.0",
29
- "semantic-release": "^18.0.1",
36
+ "semantic-release": "^20.1.3",
30
37
  "sinon": "^12.0.1",
31
- "ts-node": "^10.4.0",
32
- "typescript": "^4.7.4"
38
+ "ts-node": "^10.9.1",
39
+ "typescript": "^5.0.4"
33
40
  },
34
41
  "engines": {
35
- "node": ">=8.0"
36
- },
37
- "husky": {
38
- "hooks": {
39
- "pre-commit": "npm run lint && npm run test && npm run build"
40
- }
42
+ "node": ">=16.0"
41
43
  },
44
+ "files": [
45
+ "dist"
46
+ ],
42
47
  "keywords": [
43
48
  "postgresql",
44
49
  "interceptor",
@@ -47,21 +52,20 @@
47
52
  "license": "BSD-3-Clause",
48
53
  "main": "./dist/src/index.js",
49
54
  "name": "slonik-interceptor-query-cache",
55
+ "peerDependencies": {
56
+ "slonik": ">=27.0.0"
57
+ },
50
58
  "repository": {
51
59
  "type": "git",
52
60
  "url": "https://github.com/gajus/slonik-interceptor-query-cache"
53
61
  },
54
62
  "scripts": {
55
63
  "build": "del-cli ./dist && tsc",
56
- "lint": "eslint ./src ./test && tsc --noEmit",
57
- "test": "NODE_ENV=test nyc ava --verbose --serial"
64
+ "lint:eslint": "eslint .",
65
+ "lint:tsc": "tsc --noEmit",
66
+ "prepare": "husky install",
67
+ "test:ava": "nyc ava --verbose --serial"
58
68
  },
59
69
  "typings": "./dist/src/index.d.ts",
60
- "version": "2.4.0",
61
- "peerDependencies": {
62
- "slonik": ">=27.0.0"
63
- },
64
- "dependencies": {
65
- "roarr": "^7.14.0"
66
- }
70
+ "version": "3.0.0"
67
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 type {
2
- Interceptor,
3
- Query,
4
- QueryResultRow,
5
- QueryResult,
6
- } from 'slonik';
7
- import {
8
- Logger,
9
- } from '../Logger';
10
- import {
11
- extractCacheAttributes,
12
- } from '../utilities';
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 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) && 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