@prezly/theme-kit-nextjs 10.10.1 → 10.11.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 +36 -0
- package/build/adapters/prezly/cache/configure.cjs +1 -1
- package/build/adapters/prezly/cache/configure.d.ts +6 -1
- package/build/adapters/prezly/cache/configure.mjs +1 -1
- package/build/adapters/prezly/cache/redis.cjs +70 -18
- package/build/adapters/prezly/cache/redis.mjs +70 -18
- package/build/adapters/prezly/cache/redis.test.cjs +135 -24
- package/build/adapters/prezly/cache/redis.test.mjs +135 -24
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -416,6 +416,42 @@ subject to their configured expiration; do not flush a Redis instance that also
|
|
|
416
416
|
contains routing data. TTL and query canonicalization are separate policies and
|
|
417
417
|
are unchanged here.
|
|
418
418
|
|
|
419
|
+
### Memory layer refill and bounds
|
|
420
|
+
|
|
421
|
+
With both `memory` and `redis` enabled the layers form a stack. A hit found in
|
|
422
|
+
Redis is written back into the in-process layer with the entry's own version
|
|
423
|
+
and retention, so the next request on that runtime is served from memory
|
|
424
|
+
without a Redis round trip or a JSON decode. Before this, a memory entry lost to
|
|
425
|
+
a version change was never refilled and every later read on that pod went to
|
|
426
|
+
Redis. Stale entries are never copied: only a value that passed the version
|
|
427
|
+
check is written back, and a not-found entry keeps its short retention. Only
|
|
428
|
+
layers that implement the optional `lookup` method are refill sources; a
|
|
429
|
+
third-party `Cache` that implements `get` alone is read as before and never
|
|
430
|
+
copied from.
|
|
431
|
+
|
|
432
|
+
The in-process store is one `Map` per runtime shared by every adapter and
|
|
433
|
+
namespace, bounded to 128 MiB of estimated payload and 20,000 entries by
|
|
434
|
+
default. It evicts the least recently used entry first, deterministically and
|
|
435
|
+
without sorting; a read moves the entry to the most recent position. Pass
|
|
436
|
+
`memory: { maxBytes, maxRecords }` to `PrezlyAdapter.connect` to change the
|
|
437
|
+
bounds; both must be positive integers. `ContentDelivery.inspectSharedMemoryCache()`
|
|
438
|
+
reports the current occupancy.
|
|
439
|
+
|
|
440
|
+
Redis renewal changed at the same time. Regular content keeps a sliding
|
|
441
|
+
expiry, but instead of an `EXPIRE` on every read, an entry is rewritten with a
|
|
442
|
+
fresh timestamp only once it has consumed half of its lifetime, so a hot key
|
|
443
|
+
costs one write per half-life instead of one per read. The rewrite runs as a
|
|
444
|
+
small Lua script that compares the stored payload with the one that was read,
|
|
445
|
+
so a renewal landing after another pod refreshed the key never puts the older
|
|
446
|
+
payload back. Entries written by older releases carry no timestamp and are
|
|
447
|
+
renewed on their first read. The renewal still reports as the `expire` command
|
|
448
|
+
in telemetry. Not-found entries are never renewed.
|
|
449
|
+
|
|
450
|
+
The in-process store also refuses two kinds of write: an entry older than the
|
|
451
|
+
one it already holds for that key (a refill that lost a race with an origin
|
|
452
|
+
fetch) and an entry larger than `maxBytes` on its own, which would otherwise
|
|
453
|
+
evict every other tenant and still not fit.
|
|
454
|
+
|
|
419
455
|
### Not-found results
|
|
420
456
|
|
|
421
457
|
`contentDelivery.story()` and `contentDelivery.gallery()` resolve `null` when the
|
|
@@ -26,7 +26,7 @@ function configure(config) {
|
|
|
26
26
|
return undefined;
|
|
27
27
|
}
|
|
28
28
|
function configureStorage(config) {
|
|
29
|
-
var caches = [config.memory ? _themeKitCore.ContentDelivery.createSharedMemoryCache() : undefined, config.redis ? (0, _redis.createRedisCache)(_objectSpread(_objectSpread({
|
|
29
|
+
var caches = [config.memory ? _themeKitCore.ContentDelivery.createSharedMemoryCache('', typeof config.memory === 'object' ? config.memory : undefined) : undefined, config.redis ? (0, _redis.createRedisCache)(_objectSpread(_objectSpread({
|
|
30
30
|
ttl: DEFAULT_REDIS_CACHE_TTL
|
|
31
31
|
}, config.redis), {}, {
|
|
32
32
|
telemetry: config.telemetry
|
|
@@ -5,7 +5,12 @@ export interface Configuration {
|
|
|
5
5
|
prefix?: string;
|
|
6
6
|
ttl?: number;
|
|
7
7
|
};
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* In-process cache in front of Redis. `true` uses the default bounds
|
|
10
|
+
* (128 MiB of estimated payload, 20,000 entries); pass an object to change
|
|
11
|
+
* them. The store is shared by every adapter in the runtime.
|
|
12
|
+
*/
|
|
13
|
+
memory?: boolean | ContentDelivery.MemoryCacheOptions;
|
|
9
14
|
latestVersion: Resolvable<number>;
|
|
10
15
|
namespace?: string;
|
|
11
16
|
/** Extra immutable identity for custom fetch implementations. Enables request sharing. */
|
|
@@ -20,7 +20,7 @@ export function configure(config) {
|
|
|
20
20
|
return undefined;
|
|
21
21
|
}
|
|
22
22
|
function configureStorage(config) {
|
|
23
|
-
var caches = [config.memory ? ContentDelivery.createSharedMemoryCache() : undefined, config.redis ? createRedisCache(_objectSpread(_objectSpread({
|
|
23
|
+
var caches = [config.memory ? ContentDelivery.createSharedMemoryCache('', typeof config.memory === 'object' ? config.memory : undefined) : undefined, config.redis ? createRedisCache(_objectSpread(_objectSpread({
|
|
24
24
|
ttl: DEFAULT_REDIS_CACHE_TTL
|
|
25
25
|
}, config.redis), {}, {
|
|
26
26
|
telemetry: config.telemetry
|
|
@@ -19,6 +19,13 @@ function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t =
|
|
|
19
19
|
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); }
|
|
20
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); }); }; }
|
|
21
21
|
var COMMAND_TIMEOUT = 1000;
|
|
22
|
+
/**
|
|
23
|
+
* Renews an entry only if it is still the one that was read, so a renewal
|
|
24
|
+
* that lands after a newer write (another pod refreshing the key from origin)
|
|
25
|
+
* never puts the older payload back. EXPIRE had no such race; a plain SET has.
|
|
26
|
+
*/
|
|
27
|
+
var RENEW_IF_UNCHANGED = "\nif redis.call('GET', KEYS[1]) == ARGV[1] then\n return redis.call('SET', KEYS[1], ARGV[2], 'EX', ARGV[3])\nend\nreturn nil\n";
|
|
28
|
+
var now = () => Math.floor(Date.now() / 1000);
|
|
22
29
|
var CONNECTIONS = new Map();
|
|
23
30
|
function command(_x, _x2) {
|
|
24
31
|
return _command.apply(this, arguments);
|
|
@@ -75,29 +82,72 @@ function createRedisCache(_ref) {
|
|
|
75
82
|
var connection = client;
|
|
76
83
|
function createCache() {
|
|
77
84
|
var namespacePrefix = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
|
85
|
+
function read(_x3, _x4) {
|
|
86
|
+
return _read.apply(this, arguments);
|
|
87
|
+
}
|
|
88
|
+
function _read() {
|
|
89
|
+
_read = _asyncToGenerator(function* (key, latestVersion) {
|
|
90
|
+
if (!connection.isReady) {
|
|
91
|
+
_themeKitCore.ContentDelivery.emit(telemetry, {
|
|
92
|
+
type: 'redis_unavailable',
|
|
93
|
+
command: 'get'
|
|
94
|
+
});
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
var fullKey = "".concat(namespacePrefix).concat(key);
|
|
98
|
+
var cached = yield command(() => connection.get(fullKey), telemetry, 'get');
|
|
99
|
+
if (!cached) return undefined;
|
|
100
|
+
var entry = JSON.parse(cached);
|
|
101
|
+
if (entry.version < latestVersion) return undefined;
|
|
102
|
+
// Sliding expiry applies to regular content only: an entry with its
|
|
103
|
+
// own short retention must not be renewed to the default lifetime.
|
|
104
|
+
// Renewal used to be an EXPIRE on every read, doubling the command
|
|
105
|
+
// rate. Now an entry is rewritten with a fresh timestamp only once it
|
|
106
|
+
// has consumed half of its lifetime, so a hot key costs one write per
|
|
107
|
+
// half-life instead of one per read. Entries from older releases
|
|
108
|
+
// carry no timestamp and are renewed on their first read. A timestamp
|
|
109
|
+
// in the future (clock skew) is treated as due as well.
|
|
110
|
+
if (ttl && entry.ttl === undefined) {
|
|
111
|
+
var time = now();
|
|
112
|
+
if (entry.written === undefined || entry.written > time || time - entry.written >= ttl / 2) {
|
|
113
|
+
var renewed = _objectSpread(_objectSpread({}, entry), {}, {
|
|
114
|
+
written: time
|
|
115
|
+
});
|
|
116
|
+
void command(() => connection.eval(RENEW_IF_UNCHANGED, {
|
|
117
|
+
keys: [fullKey],
|
|
118
|
+
arguments: [cached, JSON.stringify(renewed), String(ttl)]
|
|
119
|
+
}), telemetry, 'expire').catch(() => undefined);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return entry;
|
|
123
|
+
});
|
|
124
|
+
return _read.apply(this, arguments);
|
|
125
|
+
}
|
|
78
126
|
return {
|
|
79
127
|
get(key, latestVersion, onSource) {
|
|
80
128
|
return _asyncToGenerator(function* () {
|
|
81
|
-
|
|
82
|
-
|
|
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');
|
|
89
|
-
if (!cached) return undefined;
|
|
90
|
-
var entry = JSON.parse(cached);
|
|
91
|
-
if (entry.version < latestVersion) return undefined;
|
|
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);
|
|
96
|
-
}
|
|
129
|
+
var entry = yield read(key, latestVersion);
|
|
130
|
+
if (!entry) return undefined;
|
|
97
131
|
if (onSource) _themeKitCore.ContentDelivery.notify(onSource, 'redis');
|
|
98
132
|
return entry.value;
|
|
99
133
|
})();
|
|
100
134
|
},
|
|
135
|
+
lookup(key, latestVersion) {
|
|
136
|
+
return _asyncToGenerator(function* () {
|
|
137
|
+
var entry = yield read(key, latestVersion);
|
|
138
|
+
if (!entry) return undefined;
|
|
139
|
+
// Report what is left of a short retention, so a refilled copy
|
|
140
|
+
// does not outlive this one. Legacy entries have no timestamp
|
|
141
|
+
// and report their original retention.
|
|
142
|
+
var age = entry.written === undefined ? 0 : Math.max(0, now() - entry.written);
|
|
143
|
+
return {
|
|
144
|
+
value: entry.value,
|
|
145
|
+
version: entry.version,
|
|
146
|
+
ttl: entry.ttl === undefined ? undefined : Math.max(1, entry.ttl - age),
|
|
147
|
+
layer: 'redis'
|
|
148
|
+
};
|
|
149
|
+
})();
|
|
150
|
+
},
|
|
101
151
|
set(key, value, version, options) {
|
|
102
152
|
return _asyncToGenerator(function* () {
|
|
103
153
|
if (!connection.isReady) {
|
|
@@ -110,11 +160,13 @@ function createRedisCache(_ref) {
|
|
|
110
160
|
// SET EX takes whole seconds; round a fractional hint up, never down.
|
|
111
161
|
var entry = (options === null || options === void 0 ? void 0 : options.ttl) === undefined ? {
|
|
112
162
|
value,
|
|
113
|
-
version
|
|
163
|
+
version,
|
|
164
|
+
written: now()
|
|
114
165
|
} : {
|
|
115
166
|
value,
|
|
116
167
|
version,
|
|
117
|
-
ttl: Math.max(1, Math.ceil(options.ttl))
|
|
168
|
+
ttl: Math.max(1, Math.ceil(options.ttl)),
|
|
169
|
+
written: now()
|
|
118
170
|
};
|
|
119
171
|
yield command(() => {
|
|
120
172
|
var _entry$ttl;
|
|
@@ -12,6 +12,13 @@ import { ContentDelivery } from '@prezly/theme-kit-core';
|
|
|
12
12
|
import stableStringify from 'json-stable-stringify';
|
|
13
13
|
import { createClient } from 'redis';
|
|
14
14
|
var COMMAND_TIMEOUT = 1000;
|
|
15
|
+
/**
|
|
16
|
+
* Renews an entry only if it is still the one that was read, so a renewal
|
|
17
|
+
* that lands after a newer write (another pod refreshing the key from origin)
|
|
18
|
+
* never puts the older payload back. EXPIRE had no such race; a plain SET has.
|
|
19
|
+
*/
|
|
20
|
+
var RENEW_IF_UNCHANGED = "\nif redis.call('GET', KEYS[1]) == ARGV[1] then\n return redis.call('SET', KEYS[1], ARGV[2], 'EX', ARGV[3])\nend\nreturn nil\n";
|
|
21
|
+
var now = () => Math.floor(Date.now() / 1000);
|
|
15
22
|
var CONNECTIONS = new Map();
|
|
16
23
|
function command(_x, _x2) {
|
|
17
24
|
return _command.apply(this, arguments);
|
|
@@ -68,29 +75,72 @@ export function createRedisCache(_ref) {
|
|
|
68
75
|
var connection = client;
|
|
69
76
|
function createCache() {
|
|
70
77
|
var namespacePrefix = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
|
78
|
+
function read(_x3, _x4) {
|
|
79
|
+
return _read.apply(this, arguments);
|
|
80
|
+
}
|
|
81
|
+
function _read() {
|
|
82
|
+
_read = _asyncToGenerator(function* (key, latestVersion) {
|
|
83
|
+
if (!connection.isReady) {
|
|
84
|
+
ContentDelivery.emit(telemetry, {
|
|
85
|
+
type: 'redis_unavailable',
|
|
86
|
+
command: 'get'
|
|
87
|
+
});
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
var fullKey = "".concat(namespacePrefix).concat(key);
|
|
91
|
+
var cached = yield command(() => connection.get(fullKey), telemetry, 'get');
|
|
92
|
+
if (!cached) return undefined;
|
|
93
|
+
var entry = JSON.parse(cached);
|
|
94
|
+
if (entry.version < latestVersion) return undefined;
|
|
95
|
+
// Sliding expiry applies to regular content only: an entry with its
|
|
96
|
+
// own short retention must not be renewed to the default lifetime.
|
|
97
|
+
// Renewal used to be an EXPIRE on every read, doubling the command
|
|
98
|
+
// rate. Now an entry is rewritten with a fresh timestamp only once it
|
|
99
|
+
// has consumed half of its lifetime, so a hot key costs one write per
|
|
100
|
+
// half-life instead of one per read. Entries from older releases
|
|
101
|
+
// carry no timestamp and are renewed on their first read. A timestamp
|
|
102
|
+
// in the future (clock skew) is treated as due as well.
|
|
103
|
+
if (ttl && entry.ttl === undefined) {
|
|
104
|
+
var time = now();
|
|
105
|
+
if (entry.written === undefined || entry.written > time || time - entry.written >= ttl / 2) {
|
|
106
|
+
var renewed = _objectSpread(_objectSpread({}, entry), {}, {
|
|
107
|
+
written: time
|
|
108
|
+
});
|
|
109
|
+
void command(() => connection.eval(RENEW_IF_UNCHANGED, {
|
|
110
|
+
keys: [fullKey],
|
|
111
|
+
arguments: [cached, JSON.stringify(renewed), String(ttl)]
|
|
112
|
+
}), telemetry, 'expire').catch(() => undefined);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return entry;
|
|
116
|
+
});
|
|
117
|
+
return _read.apply(this, arguments);
|
|
118
|
+
}
|
|
71
119
|
return {
|
|
72
120
|
get(key, latestVersion, onSource) {
|
|
73
121
|
return _asyncToGenerator(function* () {
|
|
74
|
-
|
|
75
|
-
|
|
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');
|
|
82
|
-
if (!cached) return undefined;
|
|
83
|
-
var entry = JSON.parse(cached);
|
|
84
|
-
if (entry.version < latestVersion) return undefined;
|
|
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);
|
|
89
|
-
}
|
|
122
|
+
var entry = yield read(key, latestVersion);
|
|
123
|
+
if (!entry) return undefined;
|
|
90
124
|
if (onSource) ContentDelivery.notify(onSource, 'redis');
|
|
91
125
|
return entry.value;
|
|
92
126
|
})();
|
|
93
127
|
},
|
|
128
|
+
lookup(key, latestVersion) {
|
|
129
|
+
return _asyncToGenerator(function* () {
|
|
130
|
+
var entry = yield read(key, latestVersion);
|
|
131
|
+
if (!entry) return undefined;
|
|
132
|
+
// Report what is left of a short retention, so a refilled copy
|
|
133
|
+
// does not outlive this one. Legacy entries have no timestamp
|
|
134
|
+
// and report their original retention.
|
|
135
|
+
var age = entry.written === undefined ? 0 : Math.max(0, now() - entry.written);
|
|
136
|
+
return {
|
|
137
|
+
value: entry.value,
|
|
138
|
+
version: entry.version,
|
|
139
|
+
ttl: entry.ttl === undefined ? undefined : Math.max(1, entry.ttl - age),
|
|
140
|
+
layer: 'redis'
|
|
141
|
+
};
|
|
142
|
+
})();
|
|
143
|
+
},
|
|
94
144
|
set(key, value, version, options) {
|
|
95
145
|
return _asyncToGenerator(function* () {
|
|
96
146
|
if (!connection.isReady) {
|
|
@@ -103,11 +153,13 @@ export function createRedisCache(_ref) {
|
|
|
103
153
|
// SET EX takes whole seconds; round a fractional hint up, never down.
|
|
104
154
|
var entry = (options === null || options === void 0 ? void 0 : options.ttl) === undefined ? {
|
|
105
155
|
value,
|
|
106
|
-
version
|
|
156
|
+
version,
|
|
157
|
+
written: now()
|
|
107
158
|
} : {
|
|
108
159
|
value,
|
|
109
160
|
version,
|
|
110
|
-
ttl: Math.max(1, Math.ceil(options.ttl))
|
|
161
|
+
ttl: Math.max(1, Math.ceil(options.ttl)),
|
|
162
|
+
written: now()
|
|
111
163
|
};
|
|
112
164
|
yield command(() => {
|
|
113
165
|
var _entry$ttl;
|
|
@@ -28,6 +28,9 @@ function connection() {
|
|
|
28
28
|
set: jest.fn(/*#__PURE__*/_asyncToGenerator(function* () {
|
|
29
29
|
return 'OK';
|
|
30
30
|
})),
|
|
31
|
+
eval: jest.fn(/*#__PURE__*/_asyncToGenerator(function* () {
|
|
32
|
+
return 'OK';
|
|
33
|
+
})),
|
|
31
34
|
expire: jest.fn(/*#__PURE__*/_asyncToGenerator(function* () {
|
|
32
35
|
return true;
|
|
33
36
|
}))
|
|
@@ -66,34 +69,136 @@ it('fails open while disconnected without waiting for connection establishment',
|
|
|
66
69
|
client.isReady = true;
|
|
67
70
|
expect(yield cache.get('key', 1)).toBe('cached');
|
|
68
71
|
}));
|
|
69
|
-
it('preserves namespace
|
|
72
|
+
it('preserves namespace and version, renews legacy entries in place, and observes renewal rejection', /*#__PURE__*/_asyncToGenerator(function* () {
|
|
73
|
+
jest.useFakeTimers({
|
|
74
|
+
now: 1700000000000
|
|
75
|
+
});
|
|
70
76
|
var _connection2 = connection(),
|
|
71
77
|
client = _connection2.client,
|
|
72
78
|
cache = _connection2.cache;
|
|
73
|
-
|
|
79
|
+
// A legacy entry (no `written`) is renewed on first read even when the write fails.
|
|
80
|
+
client.eval.mockRejectedValueOnce(new Error('renewal failed'));
|
|
74
81
|
expect(yield cache.namespace('room').get('key', 2)).toBe('cached');
|
|
75
82
|
expect(client.get).toHaveBeenLastCalledWith('test:room:key');
|
|
83
|
+
expect(client.eval).toHaveBeenCalledTimes(1);
|
|
84
|
+
expect(client.eval).toHaveBeenLastCalledWith(expect.stringContaining("redis.call('GET', KEYS[1]) == ARGV[1]"), {
|
|
85
|
+
keys: ['test:room:key'],
|
|
86
|
+
arguments: [JSON.stringify({
|
|
87
|
+
version: 2,
|
|
88
|
+
value: 'cached'
|
|
89
|
+
}), JSON.stringify({
|
|
90
|
+
version: 2,
|
|
91
|
+
value: 'cached',
|
|
92
|
+
written: 1700000000
|
|
93
|
+
}), '300']
|
|
94
|
+
});
|
|
95
|
+
expect(client.set).not.toHaveBeenCalled();
|
|
96
|
+
expect(client.expire).not.toHaveBeenCalled();
|
|
76
97
|
expect(yield cache.get('key', 3)).toBeUndefined();
|
|
77
|
-
expect(client.
|
|
98
|
+
expect(client.eval).toHaveBeenCalledTimes(1);
|
|
78
99
|
yield cache.set('key', 'updated', 3);
|
|
79
100
|
expect(client.set).toHaveBeenLastCalledWith('test:key', JSON.stringify({
|
|
80
101
|
value: 'updated',
|
|
81
|
-
version: 3
|
|
102
|
+
version: 3,
|
|
103
|
+
written: 1700000000
|
|
82
104
|
}), {
|
|
83
105
|
EX: 300
|
|
84
106
|
});
|
|
85
107
|
}));
|
|
86
|
-
it('
|
|
108
|
+
it('renews a regular entry only once it has consumed half of its lifetime, or when its timestamp is in the future', /*#__PURE__*/_asyncToGenerator(function* () {
|
|
109
|
+
jest.useFakeTimers({
|
|
110
|
+
now: 1700000000000
|
|
111
|
+
});
|
|
87
112
|
var _connection3 = connection(),
|
|
88
113
|
client = _connection3.client,
|
|
89
114
|
cache = _connection3.cache;
|
|
115
|
+
var entry = {
|
|
116
|
+
value: 'cached',
|
|
117
|
+
version: 2,
|
|
118
|
+
written: 1700000000
|
|
119
|
+
};
|
|
120
|
+
client.get.mockResolvedValue(JSON.stringify(entry));
|
|
121
|
+
expect(yield cache.get('key', 2)).toBe('cached');
|
|
122
|
+
jest.setSystemTime(1700000149000); // 149s later, under half of the 300s ttl
|
|
123
|
+
expect(yield cache.get('key', 2)).toBe('cached');
|
|
124
|
+
expect(client.eval).not.toHaveBeenCalled();
|
|
125
|
+
jest.setSystemTime(1700000150000); // exactly half of the lifetime
|
|
126
|
+
expect(yield cache.get('key', 2)).toBe('cached');
|
|
127
|
+
expect(client.eval).toHaveBeenCalledTimes(1);
|
|
128
|
+
expect(client.eval).toHaveBeenLastCalledWith(expect.any(String), {
|
|
129
|
+
keys: ['test:key'],
|
|
130
|
+
arguments: [JSON.stringify(entry), JSON.stringify(_objectSpread(_objectSpread({}, entry), {}, {
|
|
131
|
+
written: 1700000150
|
|
132
|
+
})), '300']
|
|
133
|
+
});
|
|
134
|
+
client.get.mockResolvedValue(JSON.stringify(_objectSpread(_objectSpread({}, entry), {}, {
|
|
135
|
+
written: 1700000999
|
|
136
|
+
}))); // skewed writer
|
|
137
|
+
expect(yield cache.get('key', 2)).toBe('cached');
|
|
138
|
+
expect(client.eval).toHaveBeenCalledTimes(2);
|
|
139
|
+
expect(client.set).not.toHaveBeenCalled();
|
|
140
|
+
expect(client.expire).not.toHaveBeenCalled();
|
|
141
|
+
}));
|
|
142
|
+
it('exposes the entry version and remaining retention through lookup', /*#__PURE__*/_asyncToGenerator(function* () {
|
|
143
|
+
var _cache$lookup, _cache$namespace$look, _cache$namespace, _yield$cache$lookup, _cache$lookup2, _cache$lookup3;
|
|
144
|
+
jest.useFakeTimers({
|
|
145
|
+
now: 1700000000000
|
|
146
|
+
});
|
|
147
|
+
var _connection4 = connection(),
|
|
148
|
+
client = _connection4.client,
|
|
149
|
+
cache = _connection4.cache;
|
|
150
|
+
client.get.mockResolvedValueOnce(JSON.stringify({
|
|
151
|
+
value: 'cached',
|
|
152
|
+
version: 2,
|
|
153
|
+
written: 1700000000
|
|
154
|
+
}));
|
|
155
|
+
expect(yield (_cache$lookup = cache.lookup) === null || _cache$lookup === void 0 ? void 0 : _cache$lookup.call(cache, 'key', 2)).toEqual({
|
|
156
|
+
value: 'cached',
|
|
157
|
+
version: 2,
|
|
158
|
+
ttl: undefined,
|
|
159
|
+
layer: 'redis'
|
|
160
|
+
});
|
|
161
|
+
client.get.mockResolvedValueOnce(JSON.stringify({
|
|
162
|
+
value: null,
|
|
163
|
+
version: 4,
|
|
164
|
+
ttl: 60,
|
|
165
|
+
written: 1699999955
|
|
166
|
+
}));
|
|
167
|
+
expect(yield (_cache$namespace$look = (_cache$namespace = cache.namespace('room')).lookup) === null || _cache$namespace$look === void 0 ? void 0 : _cache$namespace$look.call(_cache$namespace, 'missing', 4)).toEqual({
|
|
168
|
+
value: null,
|
|
169
|
+
version: 4,
|
|
170
|
+
ttl: 15,
|
|
171
|
+
// 45s of the 60s retention consumed
|
|
172
|
+
layer: 'redis'
|
|
173
|
+
});
|
|
174
|
+
client.get.mockResolvedValueOnce(JSON.stringify({
|
|
175
|
+
value: null,
|
|
176
|
+
version: 4,
|
|
177
|
+
ttl: 60
|
|
178
|
+
}));
|
|
179
|
+
expect((_yield$cache$lookup = yield (_cache$lookup2 = cache.lookup) === null || _cache$lookup2 === void 0 ? void 0 : _cache$lookup2.call(cache, 'legacy', 4)) === null || _yield$cache$lookup === void 0 ? void 0 : _yield$cache$lookup.ttl).toBe(60);
|
|
180
|
+
client.get.mockResolvedValueOnce(JSON.stringify({
|
|
181
|
+
value: 'old',
|
|
182
|
+
version: 1
|
|
183
|
+
}));
|
|
184
|
+
expect(yield (_cache$lookup3 = cache.lookup) === null || _cache$lookup3 === void 0 ? void 0 : _cache$lookup3.call(cache, 'key', 2)).toBeUndefined();
|
|
185
|
+
expect(client.eval).not.toHaveBeenCalled();
|
|
186
|
+
}));
|
|
187
|
+
it('stores a short-lived entry with its own expiry and never renews it', /*#__PURE__*/_asyncToGenerator(function* () {
|
|
188
|
+
jest.useFakeTimers({
|
|
189
|
+
now: 1700000000000
|
|
190
|
+
});
|
|
191
|
+
var _connection5 = connection(),
|
|
192
|
+
client = _connection5.client,
|
|
193
|
+
cache = _connection5.cache;
|
|
90
194
|
yield cache.set('missing', null, 4, {
|
|
91
195
|
ttl: 60
|
|
92
196
|
});
|
|
93
197
|
expect(client.set).toHaveBeenLastCalledWith('test:missing', JSON.stringify({
|
|
94
198
|
value: null,
|
|
95
199
|
version: 4,
|
|
96
|
-
ttl: 60
|
|
200
|
+
ttl: 60,
|
|
201
|
+
written: 1700000000
|
|
97
202
|
}), {
|
|
98
203
|
EX: 60
|
|
99
204
|
});
|
|
@@ -106,6 +211,8 @@ it('stores a short-lived entry with its own expiry and never renews it', /*#__PU
|
|
|
106
211
|
expect(yield cache.get('missing', 4, onSource)).toBeNull();
|
|
107
212
|
expect(onSource).toHaveBeenCalledWith('redis');
|
|
108
213
|
expect(client.expire).not.toHaveBeenCalled();
|
|
214
|
+
expect(client.eval).not.toHaveBeenCalled();
|
|
215
|
+
expect(client.set).toHaveBeenCalledTimes(1); // the initial write only, never a renewal
|
|
109
216
|
client.get.mockResolvedValueOnce(JSON.stringify({
|
|
110
217
|
value: null,
|
|
111
218
|
version: 4,
|
|
@@ -114,42 +221,46 @@ it('stores a short-lived entry with its own expiry and never renews it', /*#__PU
|
|
|
114
221
|
expect(yield cache.get('missing', 5)).toBeUndefined();
|
|
115
222
|
}));
|
|
116
223
|
it('rounds a fractional ttl hint up to whole seconds for SET EX', /*#__PURE__*/_asyncToGenerator(function* () {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
224
|
+
jest.useFakeTimers({
|
|
225
|
+
now: 1700000000000
|
|
226
|
+
});
|
|
227
|
+
var _connection6 = connection(),
|
|
228
|
+
client = _connection6.client,
|
|
229
|
+
cache = _connection6.cache;
|
|
120
230
|
yield cache.set('short', null, 1, {
|
|
121
231
|
ttl: 0.5
|
|
122
232
|
});
|
|
123
233
|
expect(client.set).toHaveBeenLastCalledWith('test:short', JSON.stringify({
|
|
124
234
|
value: null,
|
|
125
235
|
version: 1,
|
|
126
|
-
ttl: 1
|
|
236
|
+
ttl: 1,
|
|
237
|
+
written: 1700000000
|
|
127
238
|
}), {
|
|
128
239
|
EX: 1
|
|
129
240
|
});
|
|
130
241
|
}));
|
|
131
242
|
it.each([false, 0, ''])('returns a stored %p as a hit', /*#__PURE__*/function () {
|
|
132
|
-
var
|
|
133
|
-
var
|
|
134
|
-
client =
|
|
135
|
-
cache =
|
|
243
|
+
var _ref1 = _asyncToGenerator(function* (value) {
|
|
244
|
+
var _connection7 = connection(),
|
|
245
|
+
client = _connection7.client,
|
|
246
|
+
cache = _connection7.cache;
|
|
136
247
|
client.get.mockResolvedValueOnce(JSON.stringify({
|
|
137
248
|
value,
|
|
138
249
|
version: 2
|
|
139
250
|
}));
|
|
140
251
|
expect(yield cache.get('key', 2)).toBe(value);
|
|
141
|
-
expect(client.
|
|
252
|
+
expect(client.eval).toHaveBeenCalledTimes(1); // legacy entry renewed in place
|
|
142
253
|
});
|
|
143
254
|
return function (_x) {
|
|
144
|
-
return
|
|
255
|
+
return _ref1.apply(this, arguments);
|
|
145
256
|
};
|
|
146
257
|
}());
|
|
147
258
|
it.each(['get', 'set'])('times out stalled %s commands without unsafe cancellation and recovers', /*#__PURE__*/function () {
|
|
148
|
-
var
|
|
259
|
+
var _ref10 = _asyncToGenerator(function* (method) {
|
|
149
260
|
jest.useFakeTimers();
|
|
150
|
-
var
|
|
151
|
-
client =
|
|
152
|
-
cache =
|
|
261
|
+
var _connection8 = connection(),
|
|
262
|
+
client = _connection8.client,
|
|
263
|
+
cache = _connection8.cache;
|
|
153
264
|
var rejectOperation;
|
|
154
265
|
client[method].mockImplementationOnce(() => new Promise((_, reject) => {
|
|
155
266
|
rejectOperation = reject;
|
|
@@ -164,13 +275,13 @@ it.each(['get', 'set'])('times out stalled %s commands without unsafe cancellati
|
|
|
164
275
|
expect(jest.getTimerCount()).toBe(0);
|
|
165
276
|
});
|
|
166
277
|
return function (_x2) {
|
|
167
|
-
return
|
|
278
|
+
return _ref10.apply(this, arguments);
|
|
168
279
|
};
|
|
169
280
|
}());
|
|
170
281
|
it('does not fragment Redis connections by observer and distinguishes unavailable from commands', /*#__PURE__*/_asyncToGenerator(function* () {
|
|
171
|
-
var
|
|
172
|
-
client =
|
|
173
|
-
options =
|
|
282
|
+
var _connection9 = connection(false),
|
|
283
|
+
client = _connection9.client,
|
|
284
|
+
options = _connection9.options;
|
|
174
285
|
var events = [];
|
|
175
286
|
var observe = event => {
|
|
176
287
|
events.push(event);
|
|
@@ -26,6 +26,9 @@ function connection() {
|
|
|
26
26
|
set: jest.fn(/*#__PURE__*/_asyncToGenerator(function* () {
|
|
27
27
|
return 'OK';
|
|
28
28
|
})),
|
|
29
|
+
eval: jest.fn(/*#__PURE__*/_asyncToGenerator(function* () {
|
|
30
|
+
return 'OK';
|
|
31
|
+
})),
|
|
29
32
|
expire: jest.fn(/*#__PURE__*/_asyncToGenerator(function* () {
|
|
30
33
|
return true;
|
|
31
34
|
}))
|
|
@@ -64,34 +67,136 @@ it('fails open while disconnected without waiting for connection establishment',
|
|
|
64
67
|
client.isReady = true;
|
|
65
68
|
expect(yield cache.get('key', 1)).toBe('cached');
|
|
66
69
|
}));
|
|
67
|
-
it('preserves namespace
|
|
70
|
+
it('preserves namespace and version, renews legacy entries in place, and observes renewal rejection', /*#__PURE__*/_asyncToGenerator(function* () {
|
|
71
|
+
jest.useFakeTimers({
|
|
72
|
+
now: 1700000000000
|
|
73
|
+
});
|
|
68
74
|
var _connection2 = connection(),
|
|
69
75
|
client = _connection2.client,
|
|
70
76
|
cache = _connection2.cache;
|
|
71
|
-
|
|
77
|
+
// A legacy entry (no `written`) is renewed on first read even when the write fails.
|
|
78
|
+
client.eval.mockRejectedValueOnce(new Error('renewal failed'));
|
|
72
79
|
expect(yield cache.namespace('room').get('key', 2)).toBe('cached');
|
|
73
80
|
expect(client.get).toHaveBeenLastCalledWith('test:room:key');
|
|
81
|
+
expect(client.eval).toHaveBeenCalledTimes(1);
|
|
82
|
+
expect(client.eval).toHaveBeenLastCalledWith(expect.stringContaining("redis.call('GET', KEYS[1]) == ARGV[1]"), {
|
|
83
|
+
keys: ['test:room:key'],
|
|
84
|
+
arguments: [JSON.stringify({
|
|
85
|
+
version: 2,
|
|
86
|
+
value: 'cached'
|
|
87
|
+
}), JSON.stringify({
|
|
88
|
+
version: 2,
|
|
89
|
+
value: 'cached',
|
|
90
|
+
written: 1700000000
|
|
91
|
+
}), '300']
|
|
92
|
+
});
|
|
93
|
+
expect(client.set).not.toHaveBeenCalled();
|
|
94
|
+
expect(client.expire).not.toHaveBeenCalled();
|
|
74
95
|
expect(yield cache.get('key', 3)).toBeUndefined();
|
|
75
|
-
expect(client.
|
|
96
|
+
expect(client.eval).toHaveBeenCalledTimes(1);
|
|
76
97
|
yield cache.set('key', 'updated', 3);
|
|
77
98
|
expect(client.set).toHaveBeenLastCalledWith('test:key', JSON.stringify({
|
|
78
99
|
value: 'updated',
|
|
79
|
-
version: 3
|
|
100
|
+
version: 3,
|
|
101
|
+
written: 1700000000
|
|
80
102
|
}), {
|
|
81
103
|
EX: 300
|
|
82
104
|
});
|
|
83
105
|
}));
|
|
84
|
-
it('
|
|
106
|
+
it('renews a regular entry only once it has consumed half of its lifetime, or when its timestamp is in the future', /*#__PURE__*/_asyncToGenerator(function* () {
|
|
107
|
+
jest.useFakeTimers({
|
|
108
|
+
now: 1700000000000
|
|
109
|
+
});
|
|
85
110
|
var _connection3 = connection(),
|
|
86
111
|
client = _connection3.client,
|
|
87
112
|
cache = _connection3.cache;
|
|
113
|
+
var entry = {
|
|
114
|
+
value: 'cached',
|
|
115
|
+
version: 2,
|
|
116
|
+
written: 1700000000
|
|
117
|
+
};
|
|
118
|
+
client.get.mockResolvedValue(JSON.stringify(entry));
|
|
119
|
+
expect(yield cache.get('key', 2)).toBe('cached');
|
|
120
|
+
jest.setSystemTime(1700000149000); // 149s later, under half of the 300s ttl
|
|
121
|
+
expect(yield cache.get('key', 2)).toBe('cached');
|
|
122
|
+
expect(client.eval).not.toHaveBeenCalled();
|
|
123
|
+
jest.setSystemTime(1700000150000); // exactly half of the lifetime
|
|
124
|
+
expect(yield cache.get('key', 2)).toBe('cached');
|
|
125
|
+
expect(client.eval).toHaveBeenCalledTimes(1);
|
|
126
|
+
expect(client.eval).toHaveBeenLastCalledWith(expect.any(String), {
|
|
127
|
+
keys: ['test:key'],
|
|
128
|
+
arguments: [JSON.stringify(entry), JSON.stringify(_objectSpread(_objectSpread({}, entry), {}, {
|
|
129
|
+
written: 1700000150
|
|
130
|
+
})), '300']
|
|
131
|
+
});
|
|
132
|
+
client.get.mockResolvedValue(JSON.stringify(_objectSpread(_objectSpread({}, entry), {}, {
|
|
133
|
+
written: 1700000999
|
|
134
|
+
}))); // skewed writer
|
|
135
|
+
expect(yield cache.get('key', 2)).toBe('cached');
|
|
136
|
+
expect(client.eval).toHaveBeenCalledTimes(2);
|
|
137
|
+
expect(client.set).not.toHaveBeenCalled();
|
|
138
|
+
expect(client.expire).not.toHaveBeenCalled();
|
|
139
|
+
}));
|
|
140
|
+
it('exposes the entry version and remaining retention through lookup', /*#__PURE__*/_asyncToGenerator(function* () {
|
|
141
|
+
var _cache$lookup, _cache$namespace$look, _cache$namespace, _yield$cache$lookup, _cache$lookup2, _cache$lookup3;
|
|
142
|
+
jest.useFakeTimers({
|
|
143
|
+
now: 1700000000000
|
|
144
|
+
});
|
|
145
|
+
var _connection4 = connection(),
|
|
146
|
+
client = _connection4.client,
|
|
147
|
+
cache = _connection4.cache;
|
|
148
|
+
client.get.mockResolvedValueOnce(JSON.stringify({
|
|
149
|
+
value: 'cached',
|
|
150
|
+
version: 2,
|
|
151
|
+
written: 1700000000
|
|
152
|
+
}));
|
|
153
|
+
expect(yield (_cache$lookup = cache.lookup) === null || _cache$lookup === void 0 ? void 0 : _cache$lookup.call(cache, 'key', 2)).toEqual({
|
|
154
|
+
value: 'cached',
|
|
155
|
+
version: 2,
|
|
156
|
+
ttl: undefined,
|
|
157
|
+
layer: 'redis'
|
|
158
|
+
});
|
|
159
|
+
client.get.mockResolvedValueOnce(JSON.stringify({
|
|
160
|
+
value: null,
|
|
161
|
+
version: 4,
|
|
162
|
+
ttl: 60,
|
|
163
|
+
written: 1699999955
|
|
164
|
+
}));
|
|
165
|
+
expect(yield (_cache$namespace$look = (_cache$namespace = cache.namespace('room')).lookup) === null || _cache$namespace$look === void 0 ? void 0 : _cache$namespace$look.call(_cache$namespace, 'missing', 4)).toEqual({
|
|
166
|
+
value: null,
|
|
167
|
+
version: 4,
|
|
168
|
+
ttl: 15,
|
|
169
|
+
// 45s of the 60s retention consumed
|
|
170
|
+
layer: 'redis'
|
|
171
|
+
});
|
|
172
|
+
client.get.mockResolvedValueOnce(JSON.stringify({
|
|
173
|
+
value: null,
|
|
174
|
+
version: 4,
|
|
175
|
+
ttl: 60
|
|
176
|
+
}));
|
|
177
|
+
expect((_yield$cache$lookup = yield (_cache$lookup2 = cache.lookup) === null || _cache$lookup2 === void 0 ? void 0 : _cache$lookup2.call(cache, 'legacy', 4)) === null || _yield$cache$lookup === void 0 ? void 0 : _yield$cache$lookup.ttl).toBe(60);
|
|
178
|
+
client.get.mockResolvedValueOnce(JSON.stringify({
|
|
179
|
+
value: 'old',
|
|
180
|
+
version: 1
|
|
181
|
+
}));
|
|
182
|
+
expect(yield (_cache$lookup3 = cache.lookup) === null || _cache$lookup3 === void 0 ? void 0 : _cache$lookup3.call(cache, 'key', 2)).toBeUndefined();
|
|
183
|
+
expect(client.eval).not.toHaveBeenCalled();
|
|
184
|
+
}));
|
|
185
|
+
it('stores a short-lived entry with its own expiry and never renews it', /*#__PURE__*/_asyncToGenerator(function* () {
|
|
186
|
+
jest.useFakeTimers({
|
|
187
|
+
now: 1700000000000
|
|
188
|
+
});
|
|
189
|
+
var _connection5 = connection(),
|
|
190
|
+
client = _connection5.client,
|
|
191
|
+
cache = _connection5.cache;
|
|
88
192
|
yield cache.set('missing', null, 4, {
|
|
89
193
|
ttl: 60
|
|
90
194
|
});
|
|
91
195
|
expect(client.set).toHaveBeenLastCalledWith('test:missing', JSON.stringify({
|
|
92
196
|
value: null,
|
|
93
197
|
version: 4,
|
|
94
|
-
ttl: 60
|
|
198
|
+
ttl: 60,
|
|
199
|
+
written: 1700000000
|
|
95
200
|
}), {
|
|
96
201
|
EX: 60
|
|
97
202
|
});
|
|
@@ -104,6 +209,8 @@ it('stores a short-lived entry with its own expiry and never renews it', /*#__PU
|
|
|
104
209
|
expect(yield cache.get('missing', 4, onSource)).toBeNull();
|
|
105
210
|
expect(onSource).toHaveBeenCalledWith('redis');
|
|
106
211
|
expect(client.expire).not.toHaveBeenCalled();
|
|
212
|
+
expect(client.eval).not.toHaveBeenCalled();
|
|
213
|
+
expect(client.set).toHaveBeenCalledTimes(1); // the initial write only, never a renewal
|
|
107
214
|
client.get.mockResolvedValueOnce(JSON.stringify({
|
|
108
215
|
value: null,
|
|
109
216
|
version: 4,
|
|
@@ -112,42 +219,46 @@ it('stores a short-lived entry with its own expiry and never renews it', /*#__PU
|
|
|
112
219
|
expect(yield cache.get('missing', 5)).toBeUndefined();
|
|
113
220
|
}));
|
|
114
221
|
it('rounds a fractional ttl hint up to whole seconds for SET EX', /*#__PURE__*/_asyncToGenerator(function* () {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
222
|
+
jest.useFakeTimers({
|
|
223
|
+
now: 1700000000000
|
|
224
|
+
});
|
|
225
|
+
var _connection6 = connection(),
|
|
226
|
+
client = _connection6.client,
|
|
227
|
+
cache = _connection6.cache;
|
|
118
228
|
yield cache.set('short', null, 1, {
|
|
119
229
|
ttl: 0.5
|
|
120
230
|
});
|
|
121
231
|
expect(client.set).toHaveBeenLastCalledWith('test:short', JSON.stringify({
|
|
122
232
|
value: null,
|
|
123
233
|
version: 1,
|
|
124
|
-
ttl: 1
|
|
234
|
+
ttl: 1,
|
|
235
|
+
written: 1700000000
|
|
125
236
|
}), {
|
|
126
237
|
EX: 1
|
|
127
238
|
});
|
|
128
239
|
}));
|
|
129
240
|
it.each([false, 0, ''])('returns a stored %p as a hit', /*#__PURE__*/function () {
|
|
130
|
-
var
|
|
131
|
-
var
|
|
132
|
-
client =
|
|
133
|
-
cache =
|
|
241
|
+
var _ref1 = _asyncToGenerator(function* (value) {
|
|
242
|
+
var _connection7 = connection(),
|
|
243
|
+
client = _connection7.client,
|
|
244
|
+
cache = _connection7.cache;
|
|
134
245
|
client.get.mockResolvedValueOnce(JSON.stringify({
|
|
135
246
|
value,
|
|
136
247
|
version: 2
|
|
137
248
|
}));
|
|
138
249
|
expect(yield cache.get('key', 2)).toBe(value);
|
|
139
|
-
expect(client.
|
|
250
|
+
expect(client.eval).toHaveBeenCalledTimes(1); // legacy entry renewed in place
|
|
140
251
|
});
|
|
141
252
|
return function (_x) {
|
|
142
|
-
return
|
|
253
|
+
return _ref1.apply(this, arguments);
|
|
143
254
|
};
|
|
144
255
|
}());
|
|
145
256
|
it.each(['get', 'set'])('times out stalled %s commands without unsafe cancellation and recovers', /*#__PURE__*/function () {
|
|
146
|
-
var
|
|
257
|
+
var _ref10 = _asyncToGenerator(function* (method) {
|
|
147
258
|
jest.useFakeTimers();
|
|
148
|
-
var
|
|
149
|
-
client =
|
|
150
|
-
cache =
|
|
259
|
+
var _connection8 = connection(),
|
|
260
|
+
client = _connection8.client,
|
|
261
|
+
cache = _connection8.cache;
|
|
151
262
|
var rejectOperation;
|
|
152
263
|
client[method].mockImplementationOnce(() => new Promise((_, reject) => {
|
|
153
264
|
rejectOperation = reject;
|
|
@@ -162,13 +273,13 @@ it.each(['get', 'set'])('times out stalled %s commands without unsafe cancellati
|
|
|
162
273
|
expect(jest.getTimerCount()).toBe(0);
|
|
163
274
|
});
|
|
164
275
|
return function (_x2) {
|
|
165
|
-
return
|
|
276
|
+
return _ref10.apply(this, arguments);
|
|
166
277
|
};
|
|
167
278
|
}());
|
|
168
279
|
it('does not fragment Redis connections by observer and distinguishes unavailable from commands', /*#__PURE__*/_asyncToGenerator(function* () {
|
|
169
|
-
var
|
|
170
|
-
client =
|
|
171
|
-
options =
|
|
280
|
+
var _connection9 = connection(false),
|
|
281
|
+
client = _connection9.client,
|
|
282
|
+
options = _connection9.options;
|
|
172
283
|
var events = [];
|
|
173
284
|
var observe = event => {
|
|
174
285
|
events.push(event);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prezly/theme-kit-nextjs",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.11.0",
|
|
4
4
|
"description": "Data layer and utility library for developing Prezly themes with NextJS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "build/index.mjs",
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"json-stable-stringify": "^1.2.1",
|
|
80
80
|
"redis": "^4.7.0",
|
|
81
81
|
"url-pattern": "^1.0.3",
|
|
82
|
-
"@prezly/theme-kit-core": "^10.
|
|
82
|
+
"@prezly/theme-kit-core": "^10.11.0",
|
|
83
83
|
"@prezly/theme-kit-react": "^10.9.1",
|
|
84
84
|
"@prezly/theme-kit-intl": "^10.9.1"
|
|
85
85
|
},
|