webspresso 0.0.106 → 0.0.107
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/bin/webspresso.js +17 -7
- package/core/app.js +38 -6
- package/core/auth/index.js +4 -1
- package/core/auth/jwt.js +85 -13
- package/core/auth/magic-link.js +23 -5
- package/core/auth/manager.js +7 -1
- package/core/auth/redis-session.js +62 -25
- package/core/auth/session-manager.js +28 -2
- package/core/auth/sessions.js +182 -52
- package/core/console/eval.js +4 -1
- package/core/content/service.js +1 -0
- package/core/errors/domain.js +20 -2
- package/core/errors/http.js +1 -0
- package/core/http/hono-adapter.js +1 -1
- package/core/http/pipeline.js +36 -2
- package/core/index.js +12 -1
- package/core/mail/mailer.js +1 -1
- package/core/orm/eager-loader.js +81 -1
- package/core/orm/index.js +27 -13
- package/core/orm/json-fields.js +44 -13
- package/core/orm/model.js +12 -0
- package/core/orm/query-builder.js +33 -15
- package/core/orm/repository.js +59 -12
- package/core/orm/scopes.js +54 -4
- package/core/services/index.js +9 -0
- package/core/shutdown/shutdown-manager.js +57 -38
- package/core/studio/inspector.js +9 -1
- package/core/studio/server.js +24 -8
- package/core/utils/string.js +77 -16
- package/core/validation/index.js +18 -0
- package/index.d.ts +172 -8
- package/index.js +4 -0
- package/package.json +18 -8
- package/plugins/admin-panel/client/parts/07-model-list.js +5 -2
- package/plugins/admin-panel/index.js +24 -11
- package/plugins/admin-panel/modules/user-management.js +3 -2
- package/plugins/csrf/index.js +2 -1
- package/plugins/dashboard/index.js +27 -7
- package/src/app-context.js +9 -1
- package/src/file-router.js +12 -3
- package/src/plugin-manager.js +273 -48
- package/src/server.js +26 -33
- package/src/services/discovery.js +9 -1
- package/src/services/executor.js +271 -58
- package/src/services/index.js +52 -3
- package/src/services/loader.js +120 -0
- package/src/services/memoize.js +223 -49
- package/src/services/registry.js +492 -94
- package/src/services/validator.js +164 -13
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webspresso Services Module Loader
|
|
3
|
+
* Handles synchronous CommonJS and asynchronous native dynamic import (ESM)
|
|
4
|
+
* with robust export normalization and dev-mode cache busting.
|
|
5
|
+
* @module src/services/loader
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const { pathToFileURL } = require('url');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Execute native dynamic import() via Function constructor to prevent
|
|
15
|
+
* compilers/bundlers from down-leveling import() into require().
|
|
16
|
+
* @param {string} specifier - Module URL string
|
|
17
|
+
* @returns {Promise<any>}
|
|
18
|
+
*/
|
|
19
|
+
function nativeImport(specifier) {
|
|
20
|
+
return new Function('specifier', 'return import(specifier);')(specifier);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Load a module synchronously via CommonJS require
|
|
25
|
+
* @param {string} filePath - Absolute file path
|
|
26
|
+
* @param {Object} [options]
|
|
27
|
+
* @param {boolean} [options.isDev=false] - Invalidate require.cache if true
|
|
28
|
+
* @returns {any} Exported module
|
|
29
|
+
*/
|
|
30
|
+
function loadModuleSync(filePath, options = {}) {
|
|
31
|
+
const resolved = require.resolve(filePath);
|
|
32
|
+
if (options.isDev) {
|
|
33
|
+
try {
|
|
34
|
+
delete require.cache[resolved];
|
|
35
|
+
} catch (_) {}
|
|
36
|
+
}
|
|
37
|
+
return require(resolved);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Load a module asynchronously supporting both CommonJS and native ES Modules (ESM)
|
|
42
|
+
* @param {string} filePath - Absolute file path
|
|
43
|
+
* @param {Object} [options]
|
|
44
|
+
* @param {boolean} [options.isDev=false] - Cache busting via timestamp query param
|
|
45
|
+
* @returns {Promise<any>} Exported module
|
|
46
|
+
*/
|
|
47
|
+
async function loadModuleAsync(filePath, options = {}) {
|
|
48
|
+
const isDev = Boolean(options.isDev);
|
|
49
|
+
const ext = path.extname(filePath);
|
|
50
|
+
|
|
51
|
+
// If not explicitly .mjs, try synchronous CommonJS require first for maximum speed
|
|
52
|
+
if (ext !== '.mjs') {
|
|
53
|
+
try {
|
|
54
|
+
return loadModuleSync(filePath, { isDev });
|
|
55
|
+
} catch (err) {
|
|
56
|
+
// If Node.js throws ERR_REQUIRE_ESM, fall through to native dynamic import
|
|
57
|
+
if (err.code !== 'ERR_REQUIRE_ESM' && !err.message?.includes('ERR_REQUIRE_ESM')) {
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Load via native dynamic import() with file URL
|
|
64
|
+
let specifier = pathToFileURL(path.resolve(filePath)).href;
|
|
65
|
+
if (isDev) {
|
|
66
|
+
specifier += `?t=${Date.now()}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return nativeImport(specifier);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Normalize and extract service definition from various module export conventions:
|
|
74
|
+
* 1. module.exports = defineService({ handler })
|
|
75
|
+
* 2. module.exports = async function() {}
|
|
76
|
+
* 3. export default defineService({ handler })
|
|
77
|
+
* 4. export default async function() {}
|
|
78
|
+
* 5. export const handler = async function() {}
|
|
79
|
+
* 6. export const service = defineService({ handler })
|
|
80
|
+
*
|
|
81
|
+
* @param {any} mod - Loaded module
|
|
82
|
+
* @returns {any} Extracted service definition or handler
|
|
83
|
+
*/
|
|
84
|
+
function extractModuleExport(mod) {
|
|
85
|
+
if (mod === null || mod === undefined) {
|
|
86
|
+
return mod;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// TypeScript / Babel transpiled default
|
|
90
|
+
if (mod.__esModule && mod.default !== undefined) {
|
|
91
|
+
return mod.default;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Native ESM default export
|
|
95
|
+
if (mod.default !== undefined) {
|
|
96
|
+
const d = mod.default;
|
|
97
|
+
if (typeof d === 'function' || (typeof d === 'object' && d !== null && typeof d.handler === 'function')) {
|
|
98
|
+
return d;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Named export: export const handler = ...
|
|
103
|
+
if (typeof mod.handler === 'function') {
|
|
104
|
+
return mod;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Named export: export const service = defineService(...)
|
|
108
|
+
if (mod.service && typeof mod.service === 'object' && typeof mod.service.handler === 'function') {
|
|
109
|
+
return mod.service;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return mod.default !== undefined ? mod.default : mod;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = {
|
|
116
|
+
nativeImport,
|
|
117
|
+
loadModuleSync,
|
|
118
|
+
loadModuleAsync,
|
|
119
|
+
extractModuleExport,
|
|
120
|
+
};
|
package/src/services/memoize.js
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Webspresso Services Memoization & Caching
|
|
3
|
-
* Lightweight, zero-dependency in-memory cache with stampede protection, LRU eviction, and safe serialization
|
|
4
|
-
* @module src/services/memoize
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
1
|
const { parseDurationMs } = require('../../core/utils/time');
|
|
2
|
+
const { TaggedLruCache, normalizeTags } = require('../../core/cache/tagged-lru');
|
|
8
3
|
|
|
9
4
|
/**
|
|
10
5
|
* Parse TTL duration into milliseconds
|
|
@@ -94,8 +89,11 @@ function stableCacheKey(input, seen = new WeakSet()) {
|
|
|
94
89
|
/**
|
|
95
90
|
* Memoize an async or sync function with:
|
|
96
91
|
* - Cache stampede / thundering herd protection (in-flight promise deduping)
|
|
97
|
-
* - LRU cache eviction
|
|
92
|
+
* - Zero-allocation O(1) LRU cache eviction
|
|
98
93
|
* - TTL expiration
|
|
94
|
+
* - Bidirectional tag-based invalidation with zero memory leaks
|
|
95
|
+
* - Dynamic tag resolvers: `(input, ctx) => ['user:' + input.id]`
|
|
96
|
+
* - Pluggable custom or distributed cache providers (Redis, memory, etc.)
|
|
99
97
|
* - Cache mutation protection (safe cloning)
|
|
100
98
|
* - Safe serialization (BigInt, circular refs, Buffers)
|
|
101
99
|
*
|
|
@@ -105,7 +103,9 @@ function stableCacheKey(input, seen = new WeakSet()) {
|
|
|
105
103
|
* @param {number} [options.maxSize=500] - Max cache entries before LRU eviction
|
|
106
104
|
* @param {boolean} [options.clone=true] - Clone cached results to prevent mutation
|
|
107
105
|
* @param {Function} [options.key] - Custom cache key generator `(input, ctx) => string`
|
|
108
|
-
* @
|
|
106
|
+
* @param {string|string[]|Function} [options.tags] - Static tags or dynamic tag resolver `(input, ctx) => string|string[]`
|
|
107
|
+
* @param {Object} [options.provider] - Pluggable cache provider (e.g. Redis, distributed store)
|
|
108
|
+
* @returns {Function} Memoized function with .clear(), .invalidate(), .delete(), .has(), .size, .invalidateByTag(), .invalidateByTags()
|
|
109
109
|
*/
|
|
110
110
|
function memoize(fn, options = {}) {
|
|
111
111
|
if (typeof fn !== 'function') {
|
|
@@ -116,13 +116,53 @@ function memoize(fn, options = {}) {
|
|
|
116
116
|
const maxSize = Number(options.maxSize) || 500;
|
|
117
117
|
const shouldClone = options.clone !== false;
|
|
118
118
|
const customKeyFn = typeof options.key === 'function' ? options.key : null;
|
|
119
|
+
const tagsOption = options.tags;
|
|
120
|
+
const provider = options.provider || null;
|
|
119
121
|
|
|
120
|
-
/** @type {Map<string,
|
|
121
|
-
const cache = new Map();
|
|
122
|
-
|
|
123
|
-
/** @type {Map<string, Promise<*>>} In-flight request deduplication map */
|
|
122
|
+
/** @type {Map<string, Promise<*>>} In-flight request deduplication map (Stampede protection) */
|
|
124
123
|
const inFlight = new Map();
|
|
125
124
|
|
|
125
|
+
/** @type {TaggedLruCache|null} Local zero-leak tagged LRU cache if no external provider */
|
|
126
|
+
const localCache = provider
|
|
127
|
+
? null
|
|
128
|
+
: new TaggedLruCache({
|
|
129
|
+
maxEntries: maxSize,
|
|
130
|
+
defaultTtl: ttlMs,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Fallback tag tracking for basic external providers that lack native tag support
|
|
134
|
+
const providerTagToKeys = new Map();
|
|
135
|
+
const providerKeyToTags = new Map();
|
|
136
|
+
|
|
137
|
+
function trackProviderTags(key, tags) {
|
|
138
|
+
if (!tags || tags.length === 0) return;
|
|
139
|
+
untrackProviderKey(key);
|
|
140
|
+
providerKeyToTags.set(key, new Set(tags));
|
|
141
|
+
for (const t of tags) {
|
|
142
|
+
let set = providerTagToKeys.get(t);
|
|
143
|
+
if (!set) {
|
|
144
|
+
set = new Set();
|
|
145
|
+
providerTagToKeys.set(t, set);
|
|
146
|
+
}
|
|
147
|
+
set.add(key);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function untrackProviderKey(key) {
|
|
152
|
+
const tags = providerKeyToTags.get(key);
|
|
153
|
+
if (!tags) return;
|
|
154
|
+
for (const t of tags) {
|
|
155
|
+
const set = providerTagToKeys.get(t);
|
|
156
|
+
if (set) {
|
|
157
|
+
set.delete(key);
|
|
158
|
+
if (set.size === 0) {
|
|
159
|
+
providerTagToKeys.delete(t);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
providerKeyToTags.delete(key);
|
|
164
|
+
}
|
|
165
|
+
|
|
126
166
|
function getKey(input, ctx) {
|
|
127
167
|
if (customKeyFn) {
|
|
128
168
|
return String(customKeyFn(input, ctx));
|
|
@@ -130,20 +170,20 @@ function memoize(fn, options = {}) {
|
|
|
130
170
|
return stableCacheKey(input);
|
|
131
171
|
}
|
|
132
172
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
// Refresh LRU position
|
|
141
|
-
cache.delete(key);
|
|
142
|
-
cache.set(key, hit);
|
|
143
|
-
return shouldClone ? safeClone(hit.value) : hit.value;
|
|
173
|
+
function resolveTags(input, ctx) {
|
|
174
|
+
if (!tagsOption) return [];
|
|
175
|
+
if (typeof tagsOption === 'function') {
|
|
176
|
+
try {
|
|
177
|
+
return normalizeTags(tagsOption(input, ctx));
|
|
178
|
+
} catch (_) {
|
|
179
|
+
return [];
|
|
144
180
|
}
|
|
145
|
-
cache.delete(key);
|
|
146
181
|
}
|
|
182
|
+
return normalizeTags(tagsOption);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function memoized(input, ctx) {
|
|
186
|
+
const key = getKey(input, ctx);
|
|
147
187
|
|
|
148
188
|
// 1. In-flight Promise Deduping (Thundering Herd / Cache Stampede Protection)
|
|
149
189
|
if (inFlight.has(key)) {
|
|
@@ -151,7 +191,29 @@ function memoize(fn, options = {}) {
|
|
|
151
191
|
return shouldClone ? safeClone(inFlightResult) : inFlightResult;
|
|
152
192
|
}
|
|
153
193
|
|
|
154
|
-
// 2.
|
|
194
|
+
// 2. Check Cache Hit
|
|
195
|
+
if (provider) {
|
|
196
|
+
const hit = await provider.get(key);
|
|
197
|
+
if (hit !== undefined && hit !== null) {
|
|
198
|
+
return shouldClone ? safeClone(hit) : hit;
|
|
199
|
+
}
|
|
200
|
+
if (typeof provider.has === 'function') {
|
|
201
|
+
const exists = await provider.has(key);
|
|
202
|
+
if (exists) {
|
|
203
|
+
return shouldClone ? safeClone(hit) : hit;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
const hit = localCache.get(key);
|
|
208
|
+
if (hit !== undefined) {
|
|
209
|
+
return shouldClone ? safeClone(hit) : hit;
|
|
210
|
+
}
|
|
211
|
+
if (localCache.has(key)) {
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// 3. Execute and track in-flight promise
|
|
155
217
|
const executionPromise = (async () => {
|
|
156
218
|
try {
|
|
157
219
|
return await fn(input, ctx);
|
|
@@ -170,18 +232,34 @@ function memoize(fn, options = {}) {
|
|
|
170
232
|
throw execErr;
|
|
171
233
|
}
|
|
172
234
|
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
235
|
+
// 4. Store result with tags
|
|
236
|
+
const entryTags = resolveTags(input, ctx);
|
|
237
|
+
const valToStore = shouldClone ? safeClone(result) : result;
|
|
238
|
+
|
|
239
|
+
if (provider) {
|
|
240
|
+
const hasNativeTagSupport = typeof provider.invalidateTag === 'function' ||
|
|
241
|
+
typeof provider.invalidateByTag === 'function' ||
|
|
242
|
+
typeof provider.invalidateTags === 'function' ||
|
|
243
|
+
typeof provider.invalidateByTags === 'function';
|
|
244
|
+
|
|
245
|
+
if (!hasNativeTagSupport && entryTags.length > 0) {
|
|
246
|
+
trackProviderTags(key, entryTags);
|
|
178
247
|
}
|
|
179
|
-
}
|
|
180
248
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
249
|
+
if (typeof provider.set === 'function') {
|
|
250
|
+
await provider.set(key, valToStore, {
|
|
251
|
+
ttl: ttlMs,
|
|
252
|
+
ttlMs,
|
|
253
|
+
tags: entryTags,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
} else {
|
|
257
|
+
localCache.set(key, valToStore, {
|
|
258
|
+
tags: entryTags,
|
|
259
|
+
ttlMs,
|
|
260
|
+
ttl: ttlMs,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
185
263
|
|
|
186
264
|
return shouldClone ? safeClone(result) : result;
|
|
187
265
|
}
|
|
@@ -189,40 +267,135 @@ function memoize(fn, options = {}) {
|
|
|
189
267
|
memoized.invalidate = function invalidate(input, ctx) {
|
|
190
268
|
const key = getKey(input, ctx);
|
|
191
269
|
inFlight.delete(key);
|
|
192
|
-
|
|
270
|
+
if (provider) {
|
|
271
|
+
untrackProviderKey(key);
|
|
272
|
+
if (typeof provider.delete === 'function') return provider.delete(key);
|
|
273
|
+
if (typeof provider.del === 'function') return provider.del(key);
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
return localCache.delete(key);
|
|
193
277
|
};
|
|
194
278
|
|
|
195
279
|
memoized.delete = memoized.invalidate;
|
|
196
280
|
|
|
281
|
+
memoized.invalidateByTag = function invalidateByTag(tag) {
|
|
282
|
+
const tagStr = String(tag);
|
|
283
|
+
|
|
284
|
+
if (provider) {
|
|
285
|
+
if (typeof provider.invalidateTag === 'function') {
|
|
286
|
+
return provider.invalidateTag(tagStr);
|
|
287
|
+
}
|
|
288
|
+
if (typeof provider.invalidateByTag === 'function') {
|
|
289
|
+
return provider.invalidateByTag(tagStr);
|
|
290
|
+
}
|
|
291
|
+
if (typeof provider.invalidateTags === 'function') {
|
|
292
|
+
return provider.invalidateTags([tagStr]);
|
|
293
|
+
}
|
|
294
|
+
if (typeof provider.invalidateByTags === 'function') {
|
|
295
|
+
return provider.invalidateByTags([tagStr]);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Fallback tag tracking for basic providers
|
|
299
|
+
const keys = providerTagToKeys.get(tagStr);
|
|
300
|
+
if (!keys || keys.size === 0) return 0;
|
|
301
|
+
const keyArr = Array.from(keys);
|
|
302
|
+
providerTagToKeys.delete(tagStr);
|
|
303
|
+
for (const k of keyArr) {
|
|
304
|
+
inFlight.delete(k);
|
|
305
|
+
untrackProviderKey(k);
|
|
306
|
+
}
|
|
307
|
+
const delOps = keyArr.map(k => {
|
|
308
|
+
if (typeof provider.delete === 'function') return provider.delete(k);
|
|
309
|
+
if (typeof provider.del === 'function') return provider.del(k);
|
|
310
|
+
return false;
|
|
311
|
+
});
|
|
312
|
+
const hasPromise = delOps.some(p => p && typeof p.then === 'function');
|
|
313
|
+
if (hasPromise) {
|
|
314
|
+
return Promise.all(delOps).then(() => keyArr.length);
|
|
315
|
+
}
|
|
316
|
+
return keyArr.length;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// TaggedLruCache
|
|
320
|
+
const keys = localCache.getKeysForTag(tagStr);
|
|
321
|
+
for (const k of keys) {
|
|
322
|
+
inFlight.delete(k);
|
|
323
|
+
}
|
|
324
|
+
return localCache.invalidateTag(tagStr);
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
memoized.invalidateByTags = function invalidateByTags(tags) {
|
|
328
|
+
const normTags = normalizeTags(tags);
|
|
329
|
+
if (normTags.length === 0) return 0;
|
|
330
|
+
|
|
331
|
+
if (provider) {
|
|
332
|
+
if (typeof provider.invalidateTags === 'function') {
|
|
333
|
+
return provider.invalidateTags(normTags);
|
|
334
|
+
}
|
|
335
|
+
if (typeof provider.invalidateByTags === 'function') {
|
|
336
|
+
return provider.invalidateByTags(normTags);
|
|
337
|
+
}
|
|
338
|
+
const results = [];
|
|
339
|
+
for (const t of normTags) {
|
|
340
|
+
results.push(memoized.invalidateByTag(t));
|
|
341
|
+
}
|
|
342
|
+
const hasPromise = results.some(r => r && typeof r.then === 'function');
|
|
343
|
+
if (hasPromise) {
|
|
344
|
+
return Promise.all(results).then(counts =>
|
|
345
|
+
counts.reduce((acc, c) => acc + (typeof c === 'number' ? c : 0), 0)
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
return results.reduce((acc, c) => acc + (typeof c === 'number' ? c : 0), 0);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// TaggedLruCache
|
|
352
|
+
for (const t of normTags) {
|
|
353
|
+
const keys = localCache.getKeysForTag(t);
|
|
354
|
+
for (const k of keys) {
|
|
355
|
+
inFlight.delete(k);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return localCache.invalidateTags(normTags);
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
memoized.invalidateTag = memoized.invalidateByTag;
|
|
362
|
+
memoized.invalidateTags = memoized.invalidateByTags;
|
|
363
|
+
|
|
197
364
|
memoized.clear = function clear() {
|
|
198
365
|
inFlight.clear();
|
|
199
|
-
|
|
366
|
+
if (provider) {
|
|
367
|
+
providerTagToKeys.clear();
|
|
368
|
+
providerKeyToTags.clear();
|
|
369
|
+
if (typeof provider.clear === 'function') return provider.clear();
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
return localCache.clear();
|
|
200
373
|
};
|
|
201
374
|
|
|
202
375
|
memoized.has = function has(input, ctx) {
|
|
203
376
|
const key = getKey(input, ctx);
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if (hit.expiresAt <= Date.now()) {
|
|
207
|
-
cache.delete(key);
|
|
377
|
+
if (provider) {
|
|
378
|
+
if (typeof provider.has === 'function') return provider.has(key);
|
|
208
379
|
return false;
|
|
209
380
|
}
|
|
210
|
-
return
|
|
381
|
+
return localCache.has(key);
|
|
211
382
|
};
|
|
212
383
|
|
|
213
384
|
Object.defineProperty(memoized, 'size', {
|
|
214
385
|
get() {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
cache.delete(k);
|
|
220
|
-
}
|
|
386
|
+
if (provider) {
|
|
387
|
+
if (typeof provider.size === 'number') return provider.size;
|
|
388
|
+
if (typeof provider.getSizeStats === 'function') return provider.getSizeStats().entries;
|
|
389
|
+
return providerKeyToTags.size;
|
|
221
390
|
}
|
|
222
|
-
|
|
391
|
+
localCache.pruneExpired();
|
|
392
|
+
return localCache.size;
|
|
223
393
|
},
|
|
394
|
+
configurable: true,
|
|
224
395
|
});
|
|
225
396
|
|
|
397
|
+
memoized.cache = provider || localCache;
|
|
398
|
+
|
|
226
399
|
return memoized;
|
|
227
400
|
}
|
|
228
401
|
|
|
@@ -231,4 +404,5 @@ module.exports = {
|
|
|
231
404
|
parseTtlMs,
|
|
232
405
|
stableCacheKey,
|
|
233
406
|
safeClone,
|
|
407
|
+
normalizeTags,
|
|
234
408
|
};
|