@depup/catbox 10.0.6-depup.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/.npmignore +3 -0
- package/LICENSE +25 -0
- package/README.md +33 -0
- package/lib/client.js +118 -0
- package/lib/index.js +17 -0
- package/lib/pending.js +61 -0
- package/lib/policy.js +434 -0
- package/package.json +51 -0
package/.npmignore
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Copyright (c) 2012-2018, Project contributors
|
|
2
|
+
Copyright (c) 2012-2014, Walmart
|
|
3
|
+
All rights reserved.
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
* Redistributions of source code must retain the above copyright
|
|
8
|
+
notice, this list of conditions and the following disclaimer.
|
|
9
|
+
* Redistributions in binary form must reproduce the above copyright
|
|
10
|
+
notice, this list of conditions and the following disclaimer in the
|
|
11
|
+
documentation and/or other materials provided with the distribution.
|
|
12
|
+
* The names of any contributors may not be used to endorse or promote
|
|
13
|
+
products derived from this software without specific prior written
|
|
14
|
+
permission.
|
|
15
|
+
|
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
17
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
18
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
19
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY
|
|
20
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
21
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
22
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
23
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
24
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
25
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# @depup/catbox
|
|
2
|
+
|
|
3
|
+
> Dependency-bumped version of [catbox](https://www.npmjs.com/package/catbox)
|
|
4
|
+
|
|
5
|
+
Generated by [DepUp](https://github.com/depup/npm) -- all production
|
|
6
|
+
dependencies bumped to latest versions.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @depup/catbox
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
| Field | Value |
|
|
15
|
+
|-------|-------|
|
|
16
|
+
| Original | [catbox](https://www.npmjs.com/package/catbox) @ 10.0.6 |
|
|
17
|
+
| Processed | 2026-03-17 |
|
|
18
|
+
| Smoke test | passed |
|
|
19
|
+
| Deps updated | 3 |
|
|
20
|
+
|
|
21
|
+
## Dependency Changes
|
|
22
|
+
|
|
23
|
+
| Dependency | From | To |
|
|
24
|
+
|------------|------|-----|
|
|
25
|
+
| boom | 7.x.x | ^7.3.0 |
|
|
26
|
+
| hoek | 6.x.x | ^6.1.3 |
|
|
27
|
+
| joi | 14.x.x | ^18.0.2 |
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
Source: https://github.com/depup/npm | Original: https://www.npmjs.com/package/catbox
|
|
32
|
+
|
|
33
|
+
License inherited from the original package.
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Load modules
|
|
4
|
+
|
|
5
|
+
const Hoek = require('hoek');
|
|
6
|
+
const Boom = require('boom');
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
// Declare internals
|
|
10
|
+
|
|
11
|
+
const internals = {
|
|
12
|
+
validate: Symbol('validate')
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
internals.defaults = {
|
|
17
|
+
partition: 'catbox'
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
module.exports = class {
|
|
22
|
+
|
|
23
|
+
constructor(engine, options) {
|
|
24
|
+
|
|
25
|
+
Hoek.assert(engine, 'Missing catbox client engine');
|
|
26
|
+
Hoek.assert(typeof engine === 'object' || typeof engine === 'function', 'engine must be an engine object or engine prototype (function)');
|
|
27
|
+
Hoek.assert(typeof engine === 'function' || !options, 'Can only specify options with function engine config');
|
|
28
|
+
|
|
29
|
+
const settings = Object.assign({}, internals.defaults, options);
|
|
30
|
+
Hoek.assert(settings.partition.match(/^[\w\-]+$/), 'Invalid partition name:' + settings.partition);
|
|
31
|
+
|
|
32
|
+
this.connection = (typeof engine === 'object' ? engine : new engine(settings));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async start() {
|
|
36
|
+
|
|
37
|
+
await this.connection.start();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async stop() {
|
|
41
|
+
|
|
42
|
+
await this.connection.stop();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
isReady() {
|
|
46
|
+
|
|
47
|
+
return this.connection.isReady();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
validateSegmentName(name) {
|
|
51
|
+
|
|
52
|
+
return this.connection.validateSegmentName(name);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async get(key) {
|
|
56
|
+
|
|
57
|
+
this[internals.validate](key, null);
|
|
58
|
+
|
|
59
|
+
if (key === null) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const result = await this.connection.get(key);
|
|
64
|
+
if (!result ||
|
|
65
|
+
result.item === undefined ||
|
|
66
|
+
result.item === null) {
|
|
67
|
+
|
|
68
|
+
return null; // Not found
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const now = Date.now();
|
|
72
|
+
const expires = result.stored + result.ttl;
|
|
73
|
+
const ttl = expires - now;
|
|
74
|
+
if (ttl <= 0) {
|
|
75
|
+
return null; // Expired
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const cached = {
|
|
79
|
+
item: result.item,
|
|
80
|
+
stored: result.stored,
|
|
81
|
+
ttl
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return cached; // Valid
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async set(key, value, ttl) {
|
|
88
|
+
|
|
89
|
+
this[internals.validate](key);
|
|
90
|
+
|
|
91
|
+
if (ttl <= 0) {
|
|
92
|
+
return; // Not cachable (or bad rules)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
await this.connection.set(key, value, ttl);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async drop(key) {
|
|
99
|
+
|
|
100
|
+
this[internals.validate](key);
|
|
101
|
+
|
|
102
|
+
await this.connection.drop(key); // Always drop, regardless of caching rules
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
[internals.validate](key, allow = {}) {
|
|
106
|
+
|
|
107
|
+
if (!this.isReady()) {
|
|
108
|
+
throw Boom.internal('Disconnected'); // Disconnected
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const isValidKey = (key && typeof key.id === 'string' &&
|
|
112
|
+
key.segment && typeof key.segment === 'string');
|
|
113
|
+
|
|
114
|
+
if (!isValidKey && key !== allow) {
|
|
115
|
+
throw Boom.internal('Invalid key');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Load modules
|
|
4
|
+
|
|
5
|
+
const Client = require('./client');
|
|
6
|
+
const Policy = require('./policy');
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
// Declare internals
|
|
10
|
+
|
|
11
|
+
const internals = {};
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
exports.Client = Client;
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
exports.Policy = exports.policy = Policy;
|
package/lib/pending.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Load modules
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
// Declare internals
|
|
7
|
+
|
|
8
|
+
const internals = {};
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
exports = module.exports = class {
|
|
12
|
+
|
|
13
|
+
constructor(id, rule) {
|
|
14
|
+
|
|
15
|
+
this.id = id;
|
|
16
|
+
this.timeoutTimer = null;
|
|
17
|
+
this.count = 1;
|
|
18
|
+
this.rule = rule;
|
|
19
|
+
|
|
20
|
+
this.promise = new Promise((resolve, reject) => {
|
|
21
|
+
|
|
22
|
+
this.resolve = resolve;
|
|
23
|
+
this.reject = reject;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
join() {
|
|
28
|
+
|
|
29
|
+
++this.count;
|
|
30
|
+
return this.promise;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
send(err, value, cached, report) {
|
|
34
|
+
|
|
35
|
+
clearTimeout(this.timeoutTimer);
|
|
36
|
+
|
|
37
|
+
if (err &&
|
|
38
|
+
!cached) {
|
|
39
|
+
|
|
40
|
+
this.reject(err);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!this.rule.getDecoratedValue) {
|
|
45
|
+
this.resolve(value);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (err) {
|
|
50
|
+
report.error = err;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this.resolve({ value, cached, report });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
setTimeout(fn, timeoutMs) {
|
|
57
|
+
|
|
58
|
+
clearTimeout(this.timeoutTimer);
|
|
59
|
+
this.timeoutTimer = setTimeout(fn, timeoutMs);
|
|
60
|
+
}
|
|
61
|
+
};
|
package/lib/policy.js
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Load modules
|
|
4
|
+
|
|
5
|
+
const Boom = require('boom');
|
|
6
|
+
const Hoek = require('hoek');
|
|
7
|
+
const Joi = require('joi');
|
|
8
|
+
|
|
9
|
+
const Pending = require('./pending');
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
// Declare internals
|
|
13
|
+
|
|
14
|
+
const internals = {
|
|
15
|
+
day: 24 * 60 * 60 * 1000
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
internals.schema = Joi.object({
|
|
20
|
+
expiresIn: Joi.number().integer().min(1),
|
|
21
|
+
expiresAt: Joi.string().regex(/^\d\d?\:\d\d$/),
|
|
22
|
+
staleIn: [
|
|
23
|
+
Joi.number().integer().min(1).when('expiresAt', { is: Joi.required(), then: Joi.number().max(86400000 - 1) }), // One day - 1 (max is inclusive)
|
|
24
|
+
Joi.func()
|
|
25
|
+
],
|
|
26
|
+
staleTimeout: Joi.number().integer().min(1),
|
|
27
|
+
generateFunc: Joi.func(),
|
|
28
|
+
generateTimeout: Joi.number().integer().min(1).allow(false),
|
|
29
|
+
generateOnReadError: Joi.boolean(),
|
|
30
|
+
generateIgnoreWriteError: Joi.boolean(),
|
|
31
|
+
dropOnError: Joi.boolean(),
|
|
32
|
+
pendingGenerateTimeout: Joi.number().integer().min(1),
|
|
33
|
+
getDecoratedValue: Joi.boolean().default(false),
|
|
34
|
+
|
|
35
|
+
// Ignored external keys (hapi)
|
|
36
|
+
|
|
37
|
+
privacy: Joi.any(),
|
|
38
|
+
cache: Joi.any(),
|
|
39
|
+
segment: Joi.any(),
|
|
40
|
+
shared: Joi.any()
|
|
41
|
+
})
|
|
42
|
+
.without('expiresIn', 'expiresAt')
|
|
43
|
+
.with('staleIn', 'generateFunc')
|
|
44
|
+
.with('generateOnReadError', 'generateFunc')
|
|
45
|
+
.with('generateIgnoreWriteError', 'generateFunc')
|
|
46
|
+
.with('dropOnError', 'generateFunc')
|
|
47
|
+
.and('generateFunc', 'generateTimeout')
|
|
48
|
+
.and('staleIn', 'staleTimeout');
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
exports = module.exports = internals.Policy = class {
|
|
52
|
+
|
|
53
|
+
constructor(options, cache, segment) {
|
|
54
|
+
|
|
55
|
+
this._cache = cache;
|
|
56
|
+
this._pendings = new Map(); // id -> Pending
|
|
57
|
+
this._pendingGenerateCall = new Set(); // id
|
|
58
|
+
this.rules(options);
|
|
59
|
+
|
|
60
|
+
this.stats = {
|
|
61
|
+
sets: 0,
|
|
62
|
+
gets: 0,
|
|
63
|
+
hits: 0,
|
|
64
|
+
stales: 0,
|
|
65
|
+
generates: 0,
|
|
66
|
+
errors: 0
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
if (cache) {
|
|
70
|
+
const nameErr = cache.validateSegmentName(segment);
|
|
71
|
+
Hoek.assert(nameErr === null, 'Invalid segment name: ' + segment + (nameErr ? ' (' + nameErr.message + ')' : ''));
|
|
72
|
+
|
|
73
|
+
this._segment = segment;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
rules(options) {
|
|
78
|
+
|
|
79
|
+
this.rule = internals.Policy.compile(options, !!this._cache);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async get(key) { // key: string or { id: 'id' }
|
|
83
|
+
|
|
84
|
+
++this.stats.gets;
|
|
85
|
+
|
|
86
|
+
// Check if request is already pending
|
|
87
|
+
|
|
88
|
+
const id = (key && typeof key === 'object') ? key.id : key;
|
|
89
|
+
let pending = this._pendings.get(id);
|
|
90
|
+
if (pending !== undefined) {
|
|
91
|
+
return await pending.join();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
pending = new Pending(id, this.rule);
|
|
95
|
+
this._pendings.set(id, pending);
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
await this._get(pending, key);
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
// Safeguard to ensure that the pending rejects on any processing errors
|
|
102
|
+
|
|
103
|
+
this._send(pending, err);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return pending.promise;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async _get(pending, key) {
|
|
110
|
+
|
|
111
|
+
// Prepare report
|
|
112
|
+
|
|
113
|
+
const report = {};
|
|
114
|
+
|
|
115
|
+
// Lookup in cache
|
|
116
|
+
|
|
117
|
+
const timer = new Hoek.Bench();
|
|
118
|
+
let cached;
|
|
119
|
+
try {
|
|
120
|
+
cached = (this._cache ? await this._cache.get({ segment: this._segment, id: pending.id }) : null);
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
report.error = err;
|
|
124
|
+
++this.stats.errors;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
report.msec = timer.elapsed();
|
|
128
|
+
|
|
129
|
+
if (cached) {
|
|
130
|
+
report.stored = cached.stored;
|
|
131
|
+
report.ttl = cached.ttl;
|
|
132
|
+
const staleIn = typeof this.rule.staleIn === 'function' ? this.rule.staleIn(cached.stored, cached.ttl) : this.rule.staleIn;
|
|
133
|
+
cached.isStale = (staleIn ? (Date.now() - cached.stored) >= staleIn : false);
|
|
134
|
+
report.isStale = cached.isStale;
|
|
135
|
+
|
|
136
|
+
if (cached.isStale) {
|
|
137
|
+
++this.stats.stales;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// No generate method
|
|
142
|
+
|
|
143
|
+
if (!this.rule.generateFunc ||
|
|
144
|
+
(report.error && !this.rule.generateOnReadError)) {
|
|
145
|
+
|
|
146
|
+
this._send(pending, report.error, cached ? cached.item : null, cached, report);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Check if found and fresh
|
|
151
|
+
|
|
152
|
+
if (cached &&
|
|
153
|
+
!cached.isStale) {
|
|
154
|
+
|
|
155
|
+
this._send(pending, null, cached.item, cached, report);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Wait until generated or otherwise resolved
|
|
160
|
+
|
|
161
|
+
await Promise.race([
|
|
162
|
+
pending.promise,
|
|
163
|
+
this._generate(pending, key, cached, report)
|
|
164
|
+
]);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async _generate(pending, key, cached, report) {
|
|
168
|
+
|
|
169
|
+
if (cached) { // Must be stale
|
|
170
|
+
|
|
171
|
+
// Set stale timeout
|
|
172
|
+
|
|
173
|
+
cached.ttl = cached.ttl - this.rule.staleTimeout; // Adjust TTL for when the timeout is invoked (staleTimeout must be valid if isStale is true)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (cached &&
|
|
177
|
+
cached.ttl > 0) {
|
|
178
|
+
|
|
179
|
+
pending.setTimeout(() => {
|
|
180
|
+
|
|
181
|
+
return this._send(pending, null, cached.item, cached, report);
|
|
182
|
+
}, this.rule.staleTimeout);
|
|
183
|
+
}
|
|
184
|
+
else if (this.rule.generateTimeout) {
|
|
185
|
+
|
|
186
|
+
// Set item generation timeout (when not in cache)
|
|
187
|
+
|
|
188
|
+
pending.setTimeout(() => {
|
|
189
|
+
|
|
190
|
+
return this._send(pending, Boom.serverUnavailable(), null, null, report);
|
|
191
|
+
}, this.rule.generateTimeout);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Generate new value
|
|
195
|
+
|
|
196
|
+
if (!this._pendingGenerateCall.has(pending.id)) { // Check if a generate call is already in progress
|
|
197
|
+
++this.stats.generates; // Record generation before call in case it times out
|
|
198
|
+
|
|
199
|
+
if (this.rule.pendingGenerateTimeout) {
|
|
200
|
+
this._pendingGenerateCall.add(pending.id);
|
|
201
|
+
setTimeout(() => {
|
|
202
|
+
|
|
203
|
+
this._pendingGenerateCall.delete(pending.id);
|
|
204
|
+
}, this.rule.pendingGenerateTimeout);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
await this._callGenerateFunc(pending, key, cached, report);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async _callGenerateFunc(pending, key, cached, report) {
|
|
212
|
+
|
|
213
|
+
const flags = {};
|
|
214
|
+
try {
|
|
215
|
+
var value = await this.rule.generateFunc(key, flags);
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
var generateError = err;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (this._pendingGenerateCall.delete(pending.id)) {
|
|
222
|
+
pending = this._pendings.get(pending.id); // Fetch latest - it might have changed
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Error (if dropOnError is not set to false) or not cached
|
|
226
|
+
|
|
227
|
+
let persistError;
|
|
228
|
+
try {
|
|
229
|
+
if ((generateError && this.rule.dropOnError) || flags.ttl === 0) { // null or undefined means use policy
|
|
230
|
+
await this.drop(pending.id); // Invalidate cache
|
|
231
|
+
}
|
|
232
|
+
else if (!generateError) {
|
|
233
|
+
await this.set(pending.id, value, flags.ttl); // Lazy save (replaces stale cache copy with late-coming fresh copy)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
catch (err) {
|
|
237
|
+
persistError = err;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const error = generateError || (this.rule.generateIgnoreWriteError ? null : persistError);
|
|
241
|
+
if (cached &&
|
|
242
|
+
error &&
|
|
243
|
+
!this.rule.dropOnError) {
|
|
244
|
+
|
|
245
|
+
this._send(pending, error, cached.item, cached, report);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
this._send(pending, error, value, null, report); // Ignored if stale value already returned
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
_send(pending, err, value, cached, report) {
|
|
253
|
+
|
|
254
|
+
pending.send(err, value, cached, report);
|
|
255
|
+
this._pendings.delete(pending.id);
|
|
256
|
+
|
|
257
|
+
if (report && report.isStale !== undefined) {
|
|
258
|
+
this.stats.hits = this.stats.hits + pending.count;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async set(key, value, ttl) {
|
|
263
|
+
|
|
264
|
+
++this.stats.sets;
|
|
265
|
+
|
|
266
|
+
if (!this._cache) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
ttl = ttl || internals.Policy.ttl(this.rule);
|
|
271
|
+
const id = (key && typeof key === 'object') ? key.id : key;
|
|
272
|
+
try {
|
|
273
|
+
await this._cache.set({ segment: this._segment, id }, value, ttl);
|
|
274
|
+
}
|
|
275
|
+
catch (err) {
|
|
276
|
+
++this.stats.errors;
|
|
277
|
+
throw err;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async drop(key) {
|
|
282
|
+
|
|
283
|
+
if (!this._cache) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const id = (key && typeof key === 'object') ? key.id : key;
|
|
288
|
+
try {
|
|
289
|
+
return await this._cache.drop({ segment: this._segment, id });
|
|
290
|
+
}
|
|
291
|
+
catch (err) {
|
|
292
|
+
++this.stats.errors;
|
|
293
|
+
throw err;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
ttl(created) {
|
|
298
|
+
|
|
299
|
+
return internals.Policy.ttl(this.rule, created);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
isReady() {
|
|
303
|
+
|
|
304
|
+
if (!this._cache) {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return this._cache.connection.isReady();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
static compile(options, serverSide) {
|
|
312
|
+
|
|
313
|
+
/*
|
|
314
|
+
{
|
|
315
|
+
expiresIn: 30000,
|
|
316
|
+
expiresAt: '13:00',
|
|
317
|
+
generateFunc: (id, flags) => { throw err; } / { return result; } / { flags.ttl = ttl; return result; }
|
|
318
|
+
generateTimeout: 500,
|
|
319
|
+
generateOnReadError: true,
|
|
320
|
+
generateIgnoreWriteError: true,
|
|
321
|
+
staleIn: 20000,
|
|
322
|
+
staleTimeout: 500,
|
|
323
|
+
dropOnError: true,
|
|
324
|
+
getDecoratedValue: false
|
|
325
|
+
}
|
|
326
|
+
*/
|
|
327
|
+
|
|
328
|
+
const rule = {};
|
|
329
|
+
|
|
330
|
+
if (!options ||
|
|
331
|
+
!Object.keys(options).length) {
|
|
332
|
+
|
|
333
|
+
return rule;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Validate rule
|
|
337
|
+
|
|
338
|
+
options = Joi.attempt(options, internals.schema, 'Invalid cache policy configuration');
|
|
339
|
+
|
|
340
|
+
const hasExpiresIn = options.expiresIn !== undefined && options.expiresIn !== null;
|
|
341
|
+
const hasExpiresAt = options.expiresAt !== undefined && options.expiresAt !== null;
|
|
342
|
+
|
|
343
|
+
Hoek.assert(!hasExpiresIn || !options.staleIn || typeof options.staleIn === 'function' || options.staleIn < options.expiresIn, 'staleIn must be less than expiresIn');
|
|
344
|
+
Hoek.assert(!options.staleIn || serverSide, 'Cannot use stale options without server-side caching');
|
|
345
|
+
Hoek.assert(!options.staleTimeout || !hasExpiresIn || options.staleTimeout < options.expiresIn, 'staleTimeout must be less than expiresIn');
|
|
346
|
+
Hoek.assert(!options.staleTimeout || !hasExpiresIn || typeof options.staleIn === 'function' || options.staleTimeout < (options.expiresIn - options.staleIn), 'staleTimeout must be less than the delta between expiresIn and staleIn');
|
|
347
|
+
Hoek.assert(!options.staleTimeout || !options.pendingGenerateTimeout || options.staleTimeout < options.pendingGenerateTimeout, 'pendingGenerateTimeout must be greater than staleTimeout if specified');
|
|
348
|
+
|
|
349
|
+
// Expiration
|
|
350
|
+
|
|
351
|
+
if (hasExpiresAt) {
|
|
352
|
+
|
|
353
|
+
// expiresAt
|
|
354
|
+
|
|
355
|
+
const time = /^(\d\d?):(\d\d)$/.exec(options.expiresAt);
|
|
356
|
+
rule.expiresAt = {
|
|
357
|
+
hours: parseInt(time[1], 10),
|
|
358
|
+
minutes: parseInt(time[2], 10)
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
|
|
363
|
+
// expiresIn
|
|
364
|
+
|
|
365
|
+
rule.expiresIn = options.expiresIn || 0;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// generateTimeout
|
|
369
|
+
|
|
370
|
+
if (options.generateFunc) {
|
|
371
|
+
rule.generateFunc = options.generateFunc;
|
|
372
|
+
rule.generateTimeout = options.generateTimeout;
|
|
373
|
+
|
|
374
|
+
// Stale
|
|
375
|
+
|
|
376
|
+
if (options.staleIn) {
|
|
377
|
+
rule.staleIn = options.staleIn;
|
|
378
|
+
rule.staleTimeout = options.staleTimeout;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
rule.dropOnError = options.dropOnError !== undefined ? options.dropOnError : true; // Defaults to true
|
|
382
|
+
rule.pendingGenerateTimeout = options.pendingGenerateTimeout !== undefined ? options.pendingGenerateTimeout : 0; // Defaults to zero
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
rule.generateOnReadError = options.generateOnReadError !== undefined ? options.generateOnReadError : true; // Defaults to true
|
|
386
|
+
rule.generateIgnoreWriteError = options.generateIgnoreWriteError !== undefined ? options.generateIgnoreWriteError : true; // Defaults to true
|
|
387
|
+
|
|
388
|
+
// Decorations
|
|
389
|
+
|
|
390
|
+
rule.getDecoratedValue = options.getDecoratedValue;
|
|
391
|
+
|
|
392
|
+
return rule;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
static ttl(rule, created, now) {
|
|
396
|
+
|
|
397
|
+
now = now || Date.now();
|
|
398
|
+
created = created || now;
|
|
399
|
+
const age = now - created;
|
|
400
|
+
|
|
401
|
+
if (age < 0) {
|
|
402
|
+
return 0; // Created in the future, assume expired/bad
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (rule.expiresIn) {
|
|
406
|
+
return Math.max(rule.expiresIn - age, 0);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (rule.expiresAt) {
|
|
410
|
+
if (age > internals.day) { // If the item was created more than a 24 hours ago
|
|
411
|
+
return 0;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const expiresAt = new Date(created); // Compare expiration time on the same day
|
|
415
|
+
expiresAt.setHours(rule.expiresAt.hours);
|
|
416
|
+
expiresAt.setMinutes(rule.expiresAt.minutes);
|
|
417
|
+
expiresAt.setSeconds(0);
|
|
418
|
+
expiresAt.setMilliseconds(0);
|
|
419
|
+
let expires = expiresAt.getTime();
|
|
420
|
+
|
|
421
|
+
if (expires <= created) {
|
|
422
|
+
expires = expires + internals.day; // Move to tomorrow
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (now >= expires) { // Expired
|
|
426
|
+
return 0;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return expires - now;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return 0; // No rule
|
|
433
|
+
}
|
|
434
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@depup/catbox",
|
|
3
|
+
"description": "[DepUp] Multi-strategy object caching service",
|
|
4
|
+
"version": "10.0.6-depup.0",
|
|
5
|
+
"repository": "git://github.com/hapijs/catbox",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"depup",
|
|
9
|
+
"dependency-bumped",
|
|
10
|
+
"updated-deps",
|
|
11
|
+
"catbox",
|
|
12
|
+
"cache",
|
|
13
|
+
"generic",
|
|
14
|
+
"adapter"
|
|
15
|
+
],
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"boom": "^7.3.0",
|
|
18
|
+
"hoek": "^6.1.3",
|
|
19
|
+
"joi": "^18.0.2"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"code": "5.x.x",
|
|
23
|
+
"lab": "18.x.x"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"test": "lab -a code -t 100 -L",
|
|
27
|
+
"test-cov-html": "lab -a code -r html -o coverage.html"
|
|
28
|
+
},
|
|
29
|
+
"license": "BSD-3-Clause",
|
|
30
|
+
"depup": {
|
|
31
|
+
"changes": {
|
|
32
|
+
"boom": {
|
|
33
|
+
"from": "7.x.x",
|
|
34
|
+
"to": "^7.3.0"
|
|
35
|
+
},
|
|
36
|
+
"hoek": {
|
|
37
|
+
"from": "6.x.x",
|
|
38
|
+
"to": "^6.1.3"
|
|
39
|
+
},
|
|
40
|
+
"joi": {
|
|
41
|
+
"from": "14.x.x",
|
|
42
|
+
"to": "^18.0.2"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"depsUpdated": 3,
|
|
46
|
+
"originalPackage": "catbox",
|
|
47
|
+
"originalVersion": "10.0.6",
|
|
48
|
+
"processedAt": "2026-03-17T18:41:49.899Z",
|
|
49
|
+
"smokeTest": "passed"
|
|
50
|
+
}
|
|
51
|
+
}
|