@prezly/theme-kit-nextjs 10.9.2 → 10.10.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 +24 -2
- package/build/adapters/prezly/cache/configure.cjs +6 -3
- package/build/adapters/prezly/cache/configure.d.ts +4 -0
- package/build/adapters/prezly/cache/configure.mjs +6 -3
- package/build/adapters/prezly/cache/redis.cjs +51 -15
- package/build/adapters/prezly/cache/redis.d.ts +3 -2
- package/build/adapters/prezly/cache/redis.mjs +51 -15
- package/build/adapters/prezly/cache/redis.test.cjs +104 -7
- package/build/adapters/prezly/cache/redis.test.mjs +104 -7
- package/build/adapters/prezly/requests.cjs +54 -8
- package/build/adapters/prezly/requests.d.ts +3 -2
- package/build/adapters/prezly/requests.mjs +55 -9
- package/build/adapters/prezly/server.cjs +14 -5
- package/build/adapters/prezly/server.d.ts +2 -1
- package/build/adapters/prezly/server.mjs +14 -5
- package/build/adapters/prezly/telemetry.cjs +56 -0
- package/build/adapters/prezly/telemetry.d.ts +4 -0
- package/build/adapters/prezly/telemetry.mjs +49 -0
- package/build/adapters/prezly/telemetry.test.cjs +167 -0
- package/build/adapters/prezly/telemetry.test.mjs +165 -0
- package/package.json +109 -112
package/README.md
CHANGED
|
@@ -413,8 +413,30 @@ new retained key namespaces for every token or invalidation version. This create
|
|
|
413
413
|
a one-time cold cache on upgrade. Roll out gradually and warm representative
|
|
414
414
|
newsrooms before an expected traffic spike. Existing unscoped entries remain
|
|
415
415
|
subject to their configured expiration; do not flush a Redis instance that also
|
|
416
|
-
contains routing data. TTL
|
|
417
|
-
|
|
416
|
+
contains routing data. TTL and query canonicalization are separate policies and
|
|
417
|
+
are unchanged here.
|
|
418
|
+
|
|
419
|
+
### Not-found results
|
|
420
|
+
|
|
421
|
+
`contentDelivery.story()` and `contentDelivery.gallery()` resolve `null` when the
|
|
422
|
+
API answers 403, 404 or 410. A `null` result is a cache hit like any other value:
|
|
423
|
+
only an absent entry falls back to the API, so `false` and `0` are also valid
|
|
424
|
+
cached values. Not-found entries are kept for `negativeTtl` seconds (default 60,
|
|
425
|
+
`ContentDelivery.DEFAULT_NEGATIVE_TTL`). The deadline is written into the cached
|
|
426
|
+
envelope and checked on every read, so a cache layer that ignores the `ttl`
|
|
427
|
+
hint, or a `null` entry written by an older Theme Kit, is a miss rather than a
|
|
428
|
+
stale hit. Memory and Redis also expire these entries on their own, and Redis
|
|
429
|
+
does not renew that lifetime on read. A cache version change still invalidates
|
|
430
|
+
them at once, so publishing a story makes it visible immediately; a story that
|
|
431
|
+
becomes available without a version change appears within the TTL. Errors such
|
|
432
|
+
as 401, 429, 5xx and transport failures throw and are never stored. `undefined`
|
|
433
|
+
results are not stored either.
|
|
434
|
+
|
|
435
|
+
Set `cache.negativeTtl` in `PrezlyAdapter.connect` to change the retention. It
|
|
436
|
+
must be a positive integer number of seconds. Not-found caching needs the
|
|
437
|
+
source/authorization scope described above; a client created with a custom
|
|
438
|
+
`fetch` and no `requestScope` keeps treating `null` as a miss and does not
|
|
439
|
+
store it.
|
|
418
440
|
|
|
419
441
|
### Verification and release
|
|
420
442
|
|
|
@@ -19,15 +19,18 @@ function configure(config) {
|
|
|
19
19
|
if (storage) {
|
|
20
20
|
return {
|
|
21
21
|
storage: config.namespace ? storage.namespace(config.namespace) : storage,
|
|
22
|
-
latestVersion: _themeKitCore.Resolvable.resolve(config.latestVersion)
|
|
22
|
+
latestVersion: _themeKitCore.Resolvable.resolve(config.latestVersion),
|
|
23
|
+
negativeTtl: config.negativeTtl
|
|
23
24
|
};
|
|
24
25
|
}
|
|
25
26
|
return undefined;
|
|
26
27
|
}
|
|
27
28
|
function configureStorage(config) {
|
|
28
|
-
var caches = [config.memory ? _themeKitCore.ContentDelivery.createSharedMemoryCache() : undefined, config.redis ? (0, _redis.createRedisCache)(_objectSpread({
|
|
29
|
+
var caches = [config.memory ? _themeKitCore.ContentDelivery.createSharedMemoryCache() : undefined, config.redis ? (0, _redis.createRedisCache)(_objectSpread(_objectSpread({
|
|
29
30
|
ttl: DEFAULT_REDIS_CACHE_TTL
|
|
30
|
-
}, config.redis)
|
|
31
|
+
}, config.redis), {}, {
|
|
32
|
+
telemetry: config.telemetry
|
|
33
|
+
})) : undefined].filter(_isNotUndefined.isNotUndefined);
|
|
31
34
|
if (caches.length === 0) {
|
|
32
35
|
return undefined;
|
|
33
36
|
}
|
|
@@ -10,8 +10,12 @@ export interface Configuration {
|
|
|
10
10
|
namespace?: string;
|
|
11
11
|
/** Extra immutable identity for custom fetch implementations. Enables request sharing. */
|
|
12
12
|
requestScope?: string;
|
|
13
|
+
/** Retention in seconds for not-found results. See `ContentDelivery.DEFAULT_NEGATIVE_TTL`. */
|
|
14
|
+
negativeTtl?: number;
|
|
15
|
+
telemetry?: ContentDelivery.Telemetry;
|
|
13
16
|
}
|
|
14
17
|
export declare function configure(config: Configuration): {
|
|
15
18
|
storage: ContentDelivery.Cache;
|
|
16
19
|
latestVersion: number;
|
|
20
|
+
negativeTtl: number | undefined;
|
|
17
21
|
} | undefined;
|
|
@@ -13,15 +13,18 @@ export function configure(config) {
|
|
|
13
13
|
if (storage) {
|
|
14
14
|
return {
|
|
15
15
|
storage: config.namespace ? storage.namespace(config.namespace) : storage,
|
|
16
|
-
latestVersion: Resolvable.resolve(config.latestVersion)
|
|
16
|
+
latestVersion: Resolvable.resolve(config.latestVersion),
|
|
17
|
+
negativeTtl: config.negativeTtl
|
|
17
18
|
};
|
|
18
19
|
}
|
|
19
20
|
return undefined;
|
|
20
21
|
}
|
|
21
22
|
function configureStorage(config) {
|
|
22
|
-
var caches = [config.memory ? ContentDelivery.createSharedMemoryCache() : undefined, config.redis ? createRedisCache(_objectSpread({
|
|
23
|
+
var caches = [config.memory ? ContentDelivery.createSharedMemoryCache() : undefined, config.redis ? createRedisCache(_objectSpread(_objectSpread({
|
|
23
24
|
ttl: DEFAULT_REDIS_CACHE_TTL
|
|
24
|
-
}, config.redis)
|
|
25
|
+
}, config.redis), {}, {
|
|
26
|
+
telemetry: config.telemetry
|
|
27
|
+
})) : undefined].filter(isNotUndefined);
|
|
25
28
|
if (caches.length === 0) {
|
|
26
29
|
return undefined;
|
|
27
30
|
}
|
|
@@ -4,9 +4,10 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.createRedisCache = createRedisCache;
|
|
7
|
+
var _themeKitCore = require("@prezly/theme-kit-core");
|
|
7
8
|
var _jsonStableStringify = _interopRequireDefault(require("json-stable-stringify"));
|
|
8
9
|
var _redis = require("redis");
|
|
9
|
-
var _excluded = ["ttl", "prefix"];
|
|
10
|
+
var _excluded = ["ttl", "prefix", "telemetry"];
|
|
10
11
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
12
|
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
12
13
|
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
@@ -19,21 +20,32 @@ function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.
|
|
|
19
20
|
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
|
|
20
21
|
var COMMAND_TIMEOUT = 1000;
|
|
21
22
|
var CONNECTIONS = new Map();
|
|
22
|
-
function command(_x) {
|
|
23
|
+
function command(_x, _x2) {
|
|
23
24
|
return _command.apply(this, arguments);
|
|
24
25
|
}
|
|
25
26
|
function _command() {
|
|
26
|
-
_command = _asyncToGenerator(function* (invoke) {
|
|
27
|
+
_command = _asyncToGenerator(function* (invoke, telemetry) {
|
|
28
|
+
var operation = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'get';
|
|
27
29
|
var timer;
|
|
30
|
+
var start = telemetry ? _themeKitCore.ContentDelivery.telemetryNow() : 0;
|
|
31
|
+
var outcome = 'error';
|
|
28
32
|
try {
|
|
29
33
|
// node-redis 4 can corrupt its command queue when aborting a command
|
|
30
34
|
// already sent to the socket. Leave that operation tracked by the client;
|
|
31
35
|
// commandsQueueMaxLength bounds all outstanding replies, including these.
|
|
32
|
-
|
|
36
|
+
var value = yield Promise.race([Promise.resolve().then(invoke), new Promise((_, reject) => {
|
|
33
37
|
timer = setTimeout(() => reject(new Error('Content cache command timed out.')), COMMAND_TIMEOUT);
|
|
34
38
|
})]);
|
|
39
|
+
outcome = 'success';
|
|
40
|
+
return value;
|
|
35
41
|
} finally {
|
|
36
42
|
clearTimeout(timer);
|
|
43
|
+
_themeKitCore.ContentDelivery.emit(telemetry, {
|
|
44
|
+
type: 'redis_command',
|
|
45
|
+
command: operation,
|
|
46
|
+
outcome,
|
|
47
|
+
seconds: telemetry ? _themeKitCore.ContentDelivery.elapsedSeconds(start) : 0
|
|
48
|
+
});
|
|
37
49
|
}
|
|
38
50
|
});
|
|
39
51
|
return _command.apply(this, arguments);
|
|
@@ -42,6 +54,7 @@ function createRedisCache(_ref) {
|
|
|
42
54
|
var ttl = _ref.ttl,
|
|
43
55
|
_ref$prefix = _ref.prefix,
|
|
44
56
|
prefix = _ref$prefix === void 0 ? '' : _ref$prefix,
|
|
57
|
+
telemetry = _ref.telemetry,
|
|
45
58
|
options = _objectWithoutProperties(_ref, _excluded);
|
|
46
59
|
var connectionKey = (0, _jsonStableStringify.default)(options);
|
|
47
60
|
var client = CONNECTIONS.get(connectionKey);
|
|
@@ -63,29 +76,52 @@ function createRedisCache(_ref) {
|
|
|
63
76
|
function createCache() {
|
|
64
77
|
var namespacePrefix = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
|
65
78
|
return {
|
|
66
|
-
get(key, latestVersion) {
|
|
79
|
+
get(key, latestVersion, onSource) {
|
|
67
80
|
return _asyncToGenerator(function* () {
|
|
68
|
-
if (!connection.isReady)
|
|
69
|
-
|
|
81
|
+
if (!connection.isReady) {
|
|
82
|
+
_themeKitCore.ContentDelivery.emit(telemetry, {
|
|
83
|
+
type: 'redis_unavailable',
|
|
84
|
+
command: 'get'
|
|
85
|
+
});
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
var cached = yield command(() => connection.get("".concat(namespacePrefix).concat(key)), telemetry, 'get');
|
|
70
89
|
if (!cached) return undefined;
|
|
71
90
|
var entry = JSON.parse(cached);
|
|
72
91
|
if (entry.version < latestVersion) return undefined;
|
|
73
|
-
|
|
74
|
-
|
|
92
|
+
// Sliding expiry applies to regular content only. An entry with its
|
|
93
|
+
// own short retention must not be renewed to the default lifetime.
|
|
94
|
+
if (ttl && entry.ttl === undefined) {
|
|
95
|
+
void command(() => connection.expire("".concat(namespacePrefix).concat(key), ttl), telemetry, 'expire').catch(() => undefined);
|
|
75
96
|
}
|
|
97
|
+
if (onSource) _themeKitCore.ContentDelivery.notify(onSource, 'redis');
|
|
76
98
|
return entry.value;
|
|
77
99
|
})();
|
|
78
100
|
},
|
|
79
|
-
set(key, value, version) {
|
|
101
|
+
set(key, value, version, options) {
|
|
80
102
|
return _asyncToGenerator(function* () {
|
|
81
|
-
if (!connection.isReady)
|
|
82
|
-
|
|
103
|
+
if (!connection.isReady) {
|
|
104
|
+
_themeKitCore.ContentDelivery.emit(telemetry, {
|
|
105
|
+
type: 'redis_unavailable',
|
|
106
|
+
command: 'set'
|
|
107
|
+
});
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
// SET EX takes whole seconds; round a fractional hint up, never down.
|
|
111
|
+
var entry = (options === null || options === void 0 ? void 0 : options.ttl) === undefined ? {
|
|
83
112
|
value,
|
|
84
113
|
version
|
|
114
|
+
} : {
|
|
115
|
+
value,
|
|
116
|
+
version,
|
|
117
|
+
ttl: Math.max(1, Math.ceil(options.ttl))
|
|
85
118
|
};
|
|
86
|
-
yield command(() =>
|
|
87
|
-
|
|
88
|
-
|
|
119
|
+
yield command(() => {
|
|
120
|
+
var _entry$ttl;
|
|
121
|
+
return connection.set("".concat(namespacePrefix).concat(key), JSON.stringify(entry), {
|
|
122
|
+
EX: (_entry$ttl = entry.ttl) !== null && _entry$ttl !== void 0 ? _entry$ttl : ttl
|
|
123
|
+
});
|
|
124
|
+
}, telemetry, 'set');
|
|
89
125
|
})();
|
|
90
126
|
},
|
|
91
127
|
namespace(namespace) {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ContentDelivery } from '@prezly/theme-kit-core';
|
|
2
2
|
import { type RedisClientOptions } from 'redis';
|
|
3
3
|
type Seconds = number;
|
|
4
4
|
type Options = RedisClientOptions & {
|
|
5
5
|
ttl?: Seconds;
|
|
6
6
|
prefix?: string;
|
|
7
|
+
telemetry?: ContentDelivery.Telemetry;
|
|
7
8
|
};
|
|
8
|
-
export declare function createRedisCache({ ttl, prefix, ...options }: Options): ContentDelivery.Cache;
|
|
9
|
+
export declare function createRedisCache({ ttl, prefix, telemetry, ...options }: Options): ContentDelivery.Cache;
|
|
9
10
|
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var _excluded = ["ttl", "prefix"];
|
|
1
|
+
var _excluded = ["ttl", "prefix", "telemetry"];
|
|
2
2
|
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
3
3
|
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
4
4
|
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
@@ -8,25 +8,37 @@ function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i
|
|
|
8
8
|
function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
|
|
9
9
|
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
|
|
10
10
|
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
|
|
11
|
+
import { ContentDelivery } from '@prezly/theme-kit-core';
|
|
11
12
|
import stableStringify from 'json-stable-stringify';
|
|
12
13
|
import { createClient } from 'redis';
|
|
13
14
|
var COMMAND_TIMEOUT = 1000;
|
|
14
15
|
var CONNECTIONS = new Map();
|
|
15
|
-
function command(_x) {
|
|
16
|
+
function command(_x, _x2) {
|
|
16
17
|
return _command.apply(this, arguments);
|
|
17
18
|
}
|
|
18
19
|
function _command() {
|
|
19
|
-
_command = _asyncToGenerator(function* (invoke) {
|
|
20
|
+
_command = _asyncToGenerator(function* (invoke, telemetry) {
|
|
21
|
+
var operation = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'get';
|
|
20
22
|
var timer;
|
|
23
|
+
var start = telemetry ? ContentDelivery.telemetryNow() : 0;
|
|
24
|
+
var outcome = 'error';
|
|
21
25
|
try {
|
|
22
26
|
// node-redis 4 can corrupt its command queue when aborting a command
|
|
23
27
|
// already sent to the socket. Leave that operation tracked by the client;
|
|
24
28
|
// commandsQueueMaxLength bounds all outstanding replies, including these.
|
|
25
|
-
|
|
29
|
+
var value = yield Promise.race([Promise.resolve().then(invoke), new Promise((_, reject) => {
|
|
26
30
|
timer = setTimeout(() => reject(new Error('Content cache command timed out.')), COMMAND_TIMEOUT);
|
|
27
31
|
})]);
|
|
32
|
+
outcome = 'success';
|
|
33
|
+
return value;
|
|
28
34
|
} finally {
|
|
29
35
|
clearTimeout(timer);
|
|
36
|
+
ContentDelivery.emit(telemetry, {
|
|
37
|
+
type: 'redis_command',
|
|
38
|
+
command: operation,
|
|
39
|
+
outcome,
|
|
40
|
+
seconds: telemetry ? ContentDelivery.elapsedSeconds(start) : 0
|
|
41
|
+
});
|
|
30
42
|
}
|
|
31
43
|
});
|
|
32
44
|
return _command.apply(this, arguments);
|
|
@@ -35,6 +47,7 @@ export function createRedisCache(_ref) {
|
|
|
35
47
|
var ttl = _ref.ttl,
|
|
36
48
|
_ref$prefix = _ref.prefix,
|
|
37
49
|
prefix = _ref$prefix === void 0 ? '' : _ref$prefix,
|
|
50
|
+
telemetry = _ref.telemetry,
|
|
38
51
|
options = _objectWithoutProperties(_ref, _excluded);
|
|
39
52
|
var connectionKey = stableStringify(options);
|
|
40
53
|
var client = CONNECTIONS.get(connectionKey);
|
|
@@ -56,29 +69,52 @@ export function createRedisCache(_ref) {
|
|
|
56
69
|
function createCache() {
|
|
57
70
|
var namespacePrefix = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
|
58
71
|
return {
|
|
59
|
-
get(key, latestVersion) {
|
|
72
|
+
get(key, latestVersion, onSource) {
|
|
60
73
|
return _asyncToGenerator(function* () {
|
|
61
|
-
if (!connection.isReady)
|
|
62
|
-
|
|
74
|
+
if (!connection.isReady) {
|
|
75
|
+
ContentDelivery.emit(telemetry, {
|
|
76
|
+
type: 'redis_unavailable',
|
|
77
|
+
command: 'get'
|
|
78
|
+
});
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
var cached = yield command(() => connection.get("".concat(namespacePrefix).concat(key)), telemetry, 'get');
|
|
63
82
|
if (!cached) return undefined;
|
|
64
83
|
var entry = JSON.parse(cached);
|
|
65
84
|
if (entry.version < latestVersion) return undefined;
|
|
66
|
-
|
|
67
|
-
|
|
85
|
+
// Sliding expiry applies to regular content only. An entry with its
|
|
86
|
+
// own short retention must not be renewed to the default lifetime.
|
|
87
|
+
if (ttl && entry.ttl === undefined) {
|
|
88
|
+
void command(() => connection.expire("".concat(namespacePrefix).concat(key), ttl), telemetry, 'expire').catch(() => undefined);
|
|
68
89
|
}
|
|
90
|
+
if (onSource) ContentDelivery.notify(onSource, 'redis');
|
|
69
91
|
return entry.value;
|
|
70
92
|
})();
|
|
71
93
|
},
|
|
72
|
-
set(key, value, version) {
|
|
94
|
+
set(key, value, version, options) {
|
|
73
95
|
return _asyncToGenerator(function* () {
|
|
74
|
-
if (!connection.isReady)
|
|
75
|
-
|
|
96
|
+
if (!connection.isReady) {
|
|
97
|
+
ContentDelivery.emit(telemetry, {
|
|
98
|
+
type: 'redis_unavailable',
|
|
99
|
+
command: 'set'
|
|
100
|
+
});
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
// SET EX takes whole seconds; round a fractional hint up, never down.
|
|
104
|
+
var entry = (options === null || options === void 0 ? void 0 : options.ttl) === undefined ? {
|
|
76
105
|
value,
|
|
77
106
|
version
|
|
107
|
+
} : {
|
|
108
|
+
value,
|
|
109
|
+
version,
|
|
110
|
+
ttl: Math.max(1, Math.ceil(options.ttl))
|
|
78
111
|
};
|
|
79
|
-
yield command(() =>
|
|
80
|
-
|
|
81
|
-
|
|
112
|
+
yield command(() => {
|
|
113
|
+
var _entry$ttl;
|
|
114
|
+
return connection.set("".concat(namespacePrefix).concat(key), JSON.stringify(entry), {
|
|
115
|
+
EX: (_entry$ttl = entry.ttl) !== null && _entry$ttl !== void 0 ? _entry$ttl : ttl
|
|
116
|
+
});
|
|
117
|
+
}, telemetry, 'set');
|
|
82
118
|
})();
|
|
83
119
|
},
|
|
84
120
|
namespace(namespace) {
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
var _redis = require("redis");
|
|
4
4
|
var _redis2 = require("./redis.cjs");
|
|
5
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
6
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
7
|
+
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
8
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
|
|
9
|
+
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
5
10
|
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
|
|
6
11
|
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
|
|
7
12
|
jest.mock('redis', () => ({
|
|
@@ -78,12 +83,73 @@ it('preserves namespace, version and sliding expiry and observes expiry rejectio
|
|
|
78
83
|
EX: 300
|
|
79
84
|
});
|
|
80
85
|
}));
|
|
86
|
+
it('stores a short-lived entry with its own expiry and never renews it', /*#__PURE__*/_asyncToGenerator(function* () {
|
|
87
|
+
var _connection3 = connection(),
|
|
88
|
+
client = _connection3.client,
|
|
89
|
+
cache = _connection3.cache;
|
|
90
|
+
yield cache.set('missing', null, 4, {
|
|
91
|
+
ttl: 60
|
|
92
|
+
});
|
|
93
|
+
expect(client.set).toHaveBeenLastCalledWith('test:missing', JSON.stringify({
|
|
94
|
+
value: null,
|
|
95
|
+
version: 4,
|
|
96
|
+
ttl: 60
|
|
97
|
+
}), {
|
|
98
|
+
EX: 60
|
|
99
|
+
});
|
|
100
|
+
client.get.mockResolvedValueOnce(JSON.stringify({
|
|
101
|
+
value: null,
|
|
102
|
+
version: 4,
|
|
103
|
+
ttl: 60
|
|
104
|
+
}));
|
|
105
|
+
var onSource = jest.fn();
|
|
106
|
+
expect(yield cache.get('missing', 4, onSource)).toBeNull();
|
|
107
|
+
expect(onSource).toHaveBeenCalledWith('redis');
|
|
108
|
+
expect(client.expire).not.toHaveBeenCalled();
|
|
109
|
+
client.get.mockResolvedValueOnce(JSON.stringify({
|
|
110
|
+
value: null,
|
|
111
|
+
version: 4,
|
|
112
|
+
ttl: 60
|
|
113
|
+
}));
|
|
114
|
+
expect(yield cache.get('missing', 5)).toBeUndefined();
|
|
115
|
+
}));
|
|
116
|
+
it('rounds a fractional ttl hint up to whole seconds for SET EX', /*#__PURE__*/_asyncToGenerator(function* () {
|
|
117
|
+
var _connection4 = connection(),
|
|
118
|
+
client = _connection4.client,
|
|
119
|
+
cache = _connection4.cache;
|
|
120
|
+
yield cache.set('short', null, 1, {
|
|
121
|
+
ttl: 0.5
|
|
122
|
+
});
|
|
123
|
+
expect(client.set).toHaveBeenLastCalledWith('test:short', JSON.stringify({
|
|
124
|
+
value: null,
|
|
125
|
+
version: 1,
|
|
126
|
+
ttl: 1
|
|
127
|
+
}), {
|
|
128
|
+
EX: 1
|
|
129
|
+
});
|
|
130
|
+
}));
|
|
131
|
+
it.each([false, 0, ''])('returns a stored %p as a hit', /*#__PURE__*/function () {
|
|
132
|
+
var _ref8 = _asyncToGenerator(function* (value) {
|
|
133
|
+
var _connection5 = connection(),
|
|
134
|
+
client = _connection5.client,
|
|
135
|
+
cache = _connection5.cache;
|
|
136
|
+
client.get.mockResolvedValueOnce(JSON.stringify({
|
|
137
|
+
value,
|
|
138
|
+
version: 2
|
|
139
|
+
}));
|
|
140
|
+
expect(yield cache.get('key', 2)).toBe(value);
|
|
141
|
+
expect(client.expire).toHaveBeenCalledTimes(1);
|
|
142
|
+
});
|
|
143
|
+
return function (_x) {
|
|
144
|
+
return _ref8.apply(this, arguments);
|
|
145
|
+
};
|
|
146
|
+
}());
|
|
81
147
|
it.each(['get', 'set'])('times out stalled %s commands without unsafe cancellation and recovers', /*#__PURE__*/function () {
|
|
82
|
-
var
|
|
148
|
+
var _ref9 = _asyncToGenerator(function* (method) {
|
|
83
149
|
jest.useFakeTimers();
|
|
84
|
-
var
|
|
85
|
-
client =
|
|
86
|
-
cache =
|
|
150
|
+
var _connection6 = connection(),
|
|
151
|
+
client = _connection6.client,
|
|
152
|
+
cache = _connection6.cache;
|
|
87
153
|
var rejectOperation;
|
|
88
154
|
client[method].mockImplementationOnce(() => new Promise((_, reject) => {
|
|
89
155
|
rejectOperation = reject;
|
|
@@ -97,7 +163,38 @@ it.each(['get', 'set'])('times out stalled %s commands without unsafe cancellati
|
|
|
97
163
|
yield jest.advanceTimersByTimeAsync(0);
|
|
98
164
|
expect(jest.getTimerCount()).toBe(0);
|
|
99
165
|
});
|
|
100
|
-
return function (
|
|
101
|
-
return
|
|
166
|
+
return function (_x2) {
|
|
167
|
+
return _ref9.apply(this, arguments);
|
|
102
168
|
};
|
|
103
|
-
}());
|
|
169
|
+
}());
|
|
170
|
+
it('does not fragment Redis connections by observer and distinguishes unavailable from commands', /*#__PURE__*/_asyncToGenerator(function* () {
|
|
171
|
+
var _connection7 = connection(false),
|
|
172
|
+
client = _connection7.client,
|
|
173
|
+
options = _connection7.options;
|
|
174
|
+
var events = [];
|
|
175
|
+
var observe = event => {
|
|
176
|
+
events.push(event);
|
|
177
|
+
};
|
|
178
|
+
var cache = (0, _redis2.createRedisCache)(_objectSpread(_objectSpread({}, options), {}, {
|
|
179
|
+
telemetry: {
|
|
180
|
+
observe,
|
|
181
|
+
source: 'prezly'
|
|
182
|
+
}
|
|
183
|
+
}));
|
|
184
|
+
yield cache.get('private-key', 1);
|
|
185
|
+
yield cache.set('private-key', 'private-value', 1);
|
|
186
|
+
expect(events.filter(e => e.type === 'redis_unavailable')).toHaveLength(2);
|
|
187
|
+
expect(events.some(e => e.type === 'redis_command')).toBe(false);
|
|
188
|
+
expect(client.connect).toHaveBeenCalledTimes(1);
|
|
189
|
+
client.isReady = true;
|
|
190
|
+
var source = jest.fn();
|
|
191
|
+
expect(yield cache.namespace('private-room').get('private-key', 1, source)).toBe('cached');
|
|
192
|
+
expect(source).toHaveBeenCalledWith('redis');
|
|
193
|
+
yield cache.set('private-key', 'value', 2);
|
|
194
|
+
yield new Promise(resolve => setImmediate(resolve));
|
|
195
|
+
expect(events.filter(e => e.type === 'redis_command').map(e => e.command).sort()).toEqual(['expire', 'get', 'set']);
|
|
196
|
+
expect(JSON.stringify(events)).not.toContain('private-');
|
|
197
|
+
expect(_redis.createClient).toHaveBeenLastCalledWith(expect.not.objectContaining({
|
|
198
|
+
telemetry: expect.anything()
|
|
199
|
+
}));
|
|
200
|
+
}));
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
2
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
3
|
+
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
4
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
|
|
5
|
+
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
1
6
|
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
|
|
2
7
|
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
|
|
3
8
|
import { createClient } from 'redis';
|
|
@@ -76,12 +81,73 @@ it('preserves namespace, version and sliding expiry and observes expiry rejectio
|
|
|
76
81
|
EX: 300
|
|
77
82
|
});
|
|
78
83
|
}));
|
|
84
|
+
it('stores a short-lived entry with its own expiry and never renews it', /*#__PURE__*/_asyncToGenerator(function* () {
|
|
85
|
+
var _connection3 = connection(),
|
|
86
|
+
client = _connection3.client,
|
|
87
|
+
cache = _connection3.cache;
|
|
88
|
+
yield cache.set('missing', null, 4, {
|
|
89
|
+
ttl: 60
|
|
90
|
+
});
|
|
91
|
+
expect(client.set).toHaveBeenLastCalledWith('test:missing', JSON.stringify({
|
|
92
|
+
value: null,
|
|
93
|
+
version: 4,
|
|
94
|
+
ttl: 60
|
|
95
|
+
}), {
|
|
96
|
+
EX: 60
|
|
97
|
+
});
|
|
98
|
+
client.get.mockResolvedValueOnce(JSON.stringify({
|
|
99
|
+
value: null,
|
|
100
|
+
version: 4,
|
|
101
|
+
ttl: 60
|
|
102
|
+
}));
|
|
103
|
+
var onSource = jest.fn();
|
|
104
|
+
expect(yield cache.get('missing', 4, onSource)).toBeNull();
|
|
105
|
+
expect(onSource).toHaveBeenCalledWith('redis');
|
|
106
|
+
expect(client.expire).not.toHaveBeenCalled();
|
|
107
|
+
client.get.mockResolvedValueOnce(JSON.stringify({
|
|
108
|
+
value: null,
|
|
109
|
+
version: 4,
|
|
110
|
+
ttl: 60
|
|
111
|
+
}));
|
|
112
|
+
expect(yield cache.get('missing', 5)).toBeUndefined();
|
|
113
|
+
}));
|
|
114
|
+
it('rounds a fractional ttl hint up to whole seconds for SET EX', /*#__PURE__*/_asyncToGenerator(function* () {
|
|
115
|
+
var _connection4 = connection(),
|
|
116
|
+
client = _connection4.client,
|
|
117
|
+
cache = _connection4.cache;
|
|
118
|
+
yield cache.set('short', null, 1, {
|
|
119
|
+
ttl: 0.5
|
|
120
|
+
});
|
|
121
|
+
expect(client.set).toHaveBeenLastCalledWith('test:short', JSON.stringify({
|
|
122
|
+
value: null,
|
|
123
|
+
version: 1,
|
|
124
|
+
ttl: 1
|
|
125
|
+
}), {
|
|
126
|
+
EX: 1
|
|
127
|
+
});
|
|
128
|
+
}));
|
|
129
|
+
it.each([false, 0, ''])('returns a stored %p as a hit', /*#__PURE__*/function () {
|
|
130
|
+
var _ref8 = _asyncToGenerator(function* (value) {
|
|
131
|
+
var _connection5 = connection(),
|
|
132
|
+
client = _connection5.client,
|
|
133
|
+
cache = _connection5.cache;
|
|
134
|
+
client.get.mockResolvedValueOnce(JSON.stringify({
|
|
135
|
+
value,
|
|
136
|
+
version: 2
|
|
137
|
+
}));
|
|
138
|
+
expect(yield cache.get('key', 2)).toBe(value);
|
|
139
|
+
expect(client.expire).toHaveBeenCalledTimes(1);
|
|
140
|
+
});
|
|
141
|
+
return function (_x) {
|
|
142
|
+
return _ref8.apply(this, arguments);
|
|
143
|
+
};
|
|
144
|
+
}());
|
|
79
145
|
it.each(['get', 'set'])('times out stalled %s commands without unsafe cancellation and recovers', /*#__PURE__*/function () {
|
|
80
|
-
var
|
|
146
|
+
var _ref9 = _asyncToGenerator(function* (method) {
|
|
81
147
|
jest.useFakeTimers();
|
|
82
|
-
var
|
|
83
|
-
client =
|
|
84
|
-
cache =
|
|
148
|
+
var _connection6 = connection(),
|
|
149
|
+
client = _connection6.client,
|
|
150
|
+
cache = _connection6.cache;
|
|
85
151
|
var rejectOperation;
|
|
86
152
|
client[method].mockImplementationOnce(() => new Promise((_, reject) => {
|
|
87
153
|
rejectOperation = reject;
|
|
@@ -95,7 +161,38 @@ it.each(['get', 'set'])('times out stalled %s commands without unsafe cancellati
|
|
|
95
161
|
yield jest.advanceTimersByTimeAsync(0);
|
|
96
162
|
expect(jest.getTimerCount()).toBe(0);
|
|
97
163
|
});
|
|
98
|
-
return function (
|
|
99
|
-
return
|
|
164
|
+
return function (_x2) {
|
|
165
|
+
return _ref9.apply(this, arguments);
|
|
100
166
|
};
|
|
101
|
-
}());
|
|
167
|
+
}());
|
|
168
|
+
it('does not fragment Redis connections by observer and distinguishes unavailable from commands', /*#__PURE__*/_asyncToGenerator(function* () {
|
|
169
|
+
var _connection7 = connection(false),
|
|
170
|
+
client = _connection7.client,
|
|
171
|
+
options = _connection7.options;
|
|
172
|
+
var events = [];
|
|
173
|
+
var observe = event => {
|
|
174
|
+
events.push(event);
|
|
175
|
+
};
|
|
176
|
+
var cache = createRedisCache(_objectSpread(_objectSpread({}, options), {}, {
|
|
177
|
+
telemetry: {
|
|
178
|
+
observe,
|
|
179
|
+
source: 'prezly'
|
|
180
|
+
}
|
|
181
|
+
}));
|
|
182
|
+
yield cache.get('private-key', 1);
|
|
183
|
+
yield cache.set('private-key', 'private-value', 1);
|
|
184
|
+
expect(events.filter(e => e.type === 'redis_unavailable')).toHaveLength(2);
|
|
185
|
+
expect(events.some(e => e.type === 'redis_command')).toBe(false);
|
|
186
|
+
expect(client.connect).toHaveBeenCalledTimes(1);
|
|
187
|
+
client.isReady = true;
|
|
188
|
+
var source = jest.fn();
|
|
189
|
+
expect(yield cache.namespace('private-room').get('private-key', 1, source)).toBe('cached');
|
|
190
|
+
expect(source).toHaveBeenCalledWith('redis');
|
|
191
|
+
yield cache.set('private-key', 'value', 2);
|
|
192
|
+
yield new Promise(resolve => setImmediate(resolve));
|
|
193
|
+
expect(events.filter(e => e.type === 'redis_command').map(e => e.command).sort()).toEqual(['expire', 'get', 'set']);
|
|
194
|
+
expect(JSON.stringify(events)).not.toContain('private-');
|
|
195
|
+
expect(createClient).toHaveBeenLastCalledWith(expect.not.objectContaining({
|
|
196
|
+
telemetry: expect.anything()
|
|
197
|
+
}));
|
|
198
|
+
}));
|