@holo-js/cache 0.1.3
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/dist/chunk-KIYULZES.mjs +253 -0
- package/dist/contracts.d.ts +196 -0
- package/dist/contracts.mjs +38 -0
- package/dist/index.d.ts +199 -0
- package/dist/index.mjs +1523 -0
- package/package.json +39 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1523 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CacheConfigError,
|
|
3
|
+
CacheDriverResolutionError,
|
|
4
|
+
CacheError,
|
|
5
|
+
CacheInvalidNumericMutationError,
|
|
6
|
+
CacheInvalidTtlError,
|
|
7
|
+
CacheLockAcquisitionError,
|
|
8
|
+
CacheOptionalPackageError,
|
|
9
|
+
CacheQueryIntegrationError,
|
|
10
|
+
CacheRuntimeNotConfiguredError,
|
|
11
|
+
CacheSerializationError,
|
|
12
|
+
cacheContractsInternals,
|
|
13
|
+
defineCacheKey,
|
|
14
|
+
deserializeCacheValue,
|
|
15
|
+
isCacheKey,
|
|
16
|
+
normalizeCacheTtl,
|
|
17
|
+
resolveCacheKey,
|
|
18
|
+
serializeCacheValue
|
|
19
|
+
} from "./chunk-KIYULZES.mjs";
|
|
20
|
+
|
|
21
|
+
// src/index.ts
|
|
22
|
+
import { defineCacheConfig } from "@holo-js/config";
|
|
23
|
+
|
|
24
|
+
// src/runtime-shared.ts
|
|
25
|
+
import {
|
|
26
|
+
holoCacheDefaults,
|
|
27
|
+
normalizeCacheConfig
|
|
28
|
+
} from "@holo-js/config";
|
|
29
|
+
|
|
30
|
+
// src/db.ts
|
|
31
|
+
import {
|
|
32
|
+
normalizeDatabaseConfig
|
|
33
|
+
} from "@holo-js/config";
|
|
34
|
+
function escapeRegExp(value) {
|
|
35
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
36
|
+
}
|
|
37
|
+
function isNormalizedDatabaseConfig(config) {
|
|
38
|
+
return typeof config === "object" && config !== null && typeof config.connections === "object" && config.connections !== null;
|
|
39
|
+
}
|
|
40
|
+
function normalizeRuntimeDatabaseConfig(config) {
|
|
41
|
+
if (!config) return void 0;
|
|
42
|
+
return normalizeDatabaseConfig(config);
|
|
43
|
+
}
|
|
44
|
+
function resolveSharedDatabaseConnection(databaseConfig, connectionName) {
|
|
45
|
+
if (!databaseConfig) {
|
|
46
|
+
throw new CacheDriverResolutionError(
|
|
47
|
+
`[@holo-js/cache] Database cache driver "${connectionName}" requires a top-level database config from config/database.ts.`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
const connection = databaseConfig.connections[connectionName];
|
|
51
|
+
if (connection) return connection;
|
|
52
|
+
const availableConnections = Object.keys(databaseConfig.connections);
|
|
53
|
+
throw new CacheDriverResolutionError(
|
|
54
|
+
`[@holo-js/cache] Database cache connection "${connectionName}" was not found in config/database.ts. Available connections: ${availableConnections.join(", ") || "(none)"}.`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
function isModuleNotFoundError(error, expectedSpecifier = "@holo-js/cache-db") {
|
|
58
|
+
if (!error || typeof error !== "object") {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
const message = "message" in error && typeof error.message === "string" ? error.message : "";
|
|
62
|
+
const escapedSpecifier = escapeRegExp(expectedSpecifier);
|
|
63
|
+
if ("code" in error && error.code === "ERR_MODULE_NOT_FOUND" && [
|
|
64
|
+
new RegExp(`Cannot find package ['"]${escapedSpecifier}['"]`),
|
|
65
|
+
new RegExp(`Cannot find module ['"]${escapedSpecifier}['"]`),
|
|
66
|
+
new RegExp(`Could not resolve ['"]${escapedSpecifier}['"]`),
|
|
67
|
+
new RegExp(`Failed to load url\\s+(?:['"\`]${escapedSpecifier}['"\`]|${escapedSpecifier}(?=[\\s(]|$))`)
|
|
68
|
+
].some((pattern) => pattern.test(message))) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
if ("cause" in error) {
|
|
72
|
+
return isModuleNotFoundError(error.cause, expectedSpecifier);
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
function normalizeDatabaseModuleLoadError(error, expectedSpecifier = "@holo-js/cache-db") {
|
|
77
|
+
if (isModuleNotFoundError(error, expectedSpecifier)) {
|
|
78
|
+
return new CacheOptionalPackageError(
|
|
79
|
+
"[@holo-js/cache] Database cache support requires @holo-js/cache-db to be installed.",
|
|
80
|
+
{ cause: error }
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return error;
|
|
84
|
+
}
|
|
85
|
+
async function loadDatabaseDriverModule() {
|
|
86
|
+
try {
|
|
87
|
+
const specifier = "@holo-js/cache-db";
|
|
88
|
+
return await import(specifier);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
throw normalizeDatabaseModuleLoadError(error, "@holo-js/cache-db");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
var databaseDriverModuleLoader = loadDatabaseDriverModule;
|
|
94
|
+
function setDatabaseDriverModuleLoader(loader) {
|
|
95
|
+
databaseDriverModuleLoader = loader;
|
|
96
|
+
}
|
|
97
|
+
function resetDatabaseDriverModuleLoader() {
|
|
98
|
+
databaseDriverModuleLoader = loadDatabaseDriverModule;
|
|
99
|
+
}
|
|
100
|
+
var LazyDatabaseCacheDriver = class {
|
|
101
|
+
constructor(options) {
|
|
102
|
+
this.options = options;
|
|
103
|
+
}
|
|
104
|
+
driver = "database";
|
|
105
|
+
driverInstance;
|
|
106
|
+
pending;
|
|
107
|
+
get name() {
|
|
108
|
+
return this.options.name;
|
|
109
|
+
}
|
|
110
|
+
async resolveDriver() {
|
|
111
|
+
if (this.driverInstance) return this.driverInstance;
|
|
112
|
+
this.pending ??= databaseDriverModuleLoader().then((module) => {
|
|
113
|
+
const driver = module.createDatabaseCacheDriver(this.options);
|
|
114
|
+
this.driverInstance = driver;
|
|
115
|
+
return driver;
|
|
116
|
+
}).finally(() => {
|
|
117
|
+
this.pending = void 0;
|
|
118
|
+
});
|
|
119
|
+
return this.pending;
|
|
120
|
+
}
|
|
121
|
+
async withDriver(callback) {
|
|
122
|
+
return callback(await this.resolveDriver());
|
|
123
|
+
}
|
|
124
|
+
createLockProxy(name, seconds) {
|
|
125
|
+
let lockPromise;
|
|
126
|
+
const resolveLock = async () => {
|
|
127
|
+
lockPromise ??= this.withDriver((driver) => driver.lock(name, seconds));
|
|
128
|
+
return lockPromise;
|
|
129
|
+
};
|
|
130
|
+
return {
|
|
131
|
+
name,
|
|
132
|
+
async get(callback) {
|
|
133
|
+
return (await resolveLock()).get(callback);
|
|
134
|
+
},
|
|
135
|
+
async release() {
|
|
136
|
+
return (await resolveLock()).release();
|
|
137
|
+
},
|
|
138
|
+
async block(waitSeconds, callback) {
|
|
139
|
+
return (await resolveLock()).block(waitSeconds, callback);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
async get(key) {
|
|
144
|
+
return this.withDriver((driver) => driver.get(key));
|
|
145
|
+
}
|
|
146
|
+
async put(input) {
|
|
147
|
+
return this.withDriver((driver) => driver.put(input));
|
|
148
|
+
}
|
|
149
|
+
async add(input) {
|
|
150
|
+
return this.withDriver((driver) => driver.add(input));
|
|
151
|
+
}
|
|
152
|
+
async forget(key) {
|
|
153
|
+
return this.withDriver((driver) => driver.forget(key));
|
|
154
|
+
}
|
|
155
|
+
async flush() {
|
|
156
|
+
await this.withDriver((driver) => driver.flush());
|
|
157
|
+
}
|
|
158
|
+
async increment(key, amount) {
|
|
159
|
+
return this.withDriver((driver) => driver.increment(key, amount));
|
|
160
|
+
}
|
|
161
|
+
async decrement(key, amount) {
|
|
162
|
+
return this.withDriver((driver) => driver.decrement(key, amount));
|
|
163
|
+
}
|
|
164
|
+
lock(name, seconds) {
|
|
165
|
+
return this.createLockProxy(name, seconds);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
function createDatabaseCacheDriver(options) {
|
|
169
|
+
return new LazyDatabaseCacheDriver(options);
|
|
170
|
+
}
|
|
171
|
+
var cacheDbInternals = {
|
|
172
|
+
isModuleNotFoundError,
|
|
173
|
+
isNormalizedDatabaseConfig,
|
|
174
|
+
loadDatabaseDriverModule,
|
|
175
|
+
normalizeDatabaseModuleLoadError,
|
|
176
|
+
normalizeRuntimeDatabaseConfig,
|
|
177
|
+
resolveSharedDatabaseConnection,
|
|
178
|
+
resetDatabaseDriverModuleLoader,
|
|
179
|
+
setDatabaseDriverModuleLoader
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// src/file.ts
|
|
183
|
+
import { createHash, randomUUID } from "crypto";
|
|
184
|
+
import { mkdir, open, readFile, readdir, rename, rm, writeFile } from "fs/promises";
|
|
185
|
+
import { dirname, join, resolve } from "path";
|
|
186
|
+
var MALFORMED_FILE = /* @__PURE__ */ Symbol("MALFORMED_FILE");
|
|
187
|
+
function defaultSleep(milliseconds) {
|
|
188
|
+
return new Promise((resolveDelay) => {
|
|
189
|
+
setTimeout(resolveDelay, milliseconds);
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
function hashCacheKey(key) {
|
|
193
|
+
return createHash("sha256").update(key).digest("hex");
|
|
194
|
+
}
|
|
195
|
+
function resolveDriverRoot(path) {
|
|
196
|
+
return resolve(path);
|
|
197
|
+
}
|
|
198
|
+
function resolveEntryFilePath(rootPath, key) {
|
|
199
|
+
const hash = hashCacheKey(key);
|
|
200
|
+
return join(rootPath, "entries", hash.slice(0, 2), `${hash}.json`);
|
|
201
|
+
}
|
|
202
|
+
function resolveLockFilePath(rootPath, name) {
|
|
203
|
+
const hash = hashCacheKey(name);
|
|
204
|
+
return join(rootPath, "locks", hash.slice(0, 2), `${hash}.lock`);
|
|
205
|
+
}
|
|
206
|
+
function isPositiveTimestamp(value) {
|
|
207
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
208
|
+
}
|
|
209
|
+
function isFileCacheEntryEnvelope(value) {
|
|
210
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
const entry = value;
|
|
214
|
+
return typeof entry.key === "string" && typeof entry.payload === "string" && (typeof entry.expiresAt === "undefined" || isPositiveTimestamp(entry.expiresAt));
|
|
215
|
+
}
|
|
216
|
+
function isFileCacheLockEnvelope(value) {
|
|
217
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
const lock = value;
|
|
221
|
+
return typeof lock.name === "string" && typeof lock.owner === "string" && isPositiveTimestamp(lock.expiresAt);
|
|
222
|
+
}
|
|
223
|
+
async function ensureParentDirectory(filePath) {
|
|
224
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
225
|
+
}
|
|
226
|
+
async function removeFileIfPresent(filePath) {
|
|
227
|
+
await rm(filePath, { force: true });
|
|
228
|
+
}
|
|
229
|
+
async function readFileIfPresent(filePath) {
|
|
230
|
+
try {
|
|
231
|
+
return await readFile(filePath, "utf8");
|
|
232
|
+
} catch (error) {
|
|
233
|
+
if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR")) {
|
|
234
|
+
return void 0;
|
|
235
|
+
}
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
async function writeFileAtomically(filePath, contents) {
|
|
240
|
+
await ensureParentDirectory(filePath);
|
|
241
|
+
const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
242
|
+
await writeFile(temporaryPath, contents, "utf8");
|
|
243
|
+
try {
|
|
244
|
+
await rename(temporaryPath, filePath);
|
|
245
|
+
} catch (error) {
|
|
246
|
+
await removeFileIfPresent(temporaryPath);
|
|
247
|
+
throw error;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
async function writeFileExclusively(filePath, contents) {
|
|
251
|
+
await ensureParentDirectory(filePath);
|
|
252
|
+
try {
|
|
253
|
+
const handle = await open(filePath, "wx");
|
|
254
|
+
try {
|
|
255
|
+
await handle.writeFile(contents, "utf8");
|
|
256
|
+
} finally {
|
|
257
|
+
await handle.close();
|
|
258
|
+
}
|
|
259
|
+
return true;
|
|
260
|
+
} catch (error) {
|
|
261
|
+
if (error instanceof Error && "code" in error && error.code === "EEXIST") {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
throw error;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
async function readJsonFile(filePath) {
|
|
268
|
+
const contents = await readFileIfPresent(filePath);
|
|
269
|
+
if (typeof contents === "undefined") return void 0;
|
|
270
|
+
try {
|
|
271
|
+
return JSON.parse(contents);
|
|
272
|
+
} catch {
|
|
273
|
+
return MALFORMED_FILE;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
async function listFiles(rootPath) {
|
|
277
|
+
try {
|
|
278
|
+
const entries = await readdir(rootPath, { withFileTypes: true });
|
|
279
|
+
const nested = await Promise.all(entries.map(async (entry) => {
|
|
280
|
+
const entryPath = join(rootPath, entry.name);
|
|
281
|
+
if (entry.isDirectory()) return listFiles(entryPath);
|
|
282
|
+
return [entryPath];
|
|
283
|
+
}));
|
|
284
|
+
return nested.flat();
|
|
285
|
+
} catch (error) {
|
|
286
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
287
|
+
return [];
|
|
288
|
+
}
|
|
289
|
+
throw error;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
async function removeInvalidFileAndReadMissing(filePath, decoded) {
|
|
293
|
+
if (typeof decoded !== "undefined") {
|
|
294
|
+
await removeFileIfPresent(filePath);
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
state: "missing",
|
|
298
|
+
filePath
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
async function removeScopedCacheFiles(rootPath, prefix, isEnvelope, resolveName) {
|
|
302
|
+
if (!prefix) {
|
|
303
|
+
await rm(rootPath, { recursive: true, force: true });
|
|
304
|
+
await mkdir(rootPath, { recursive: true });
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
for (const filePath of await listFiles(rootPath)) {
|
|
308
|
+
const decoded = await readJsonFile(filePath);
|
|
309
|
+
if (!decoded || decoded === MALFORMED_FILE || !isEnvelope(decoded)) {
|
|
310
|
+
await removeFileIfPresent(filePath);
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
if (resolveName(decoded).startsWith(prefix)) {
|
|
314
|
+
await removeFileIfPresent(filePath);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
async function readEntry(rootPath, key, now) {
|
|
319
|
+
const filePath = resolveEntryFilePath(rootPath, key);
|
|
320
|
+
const decoded = await readJsonFile(filePath);
|
|
321
|
+
if (decoded === MALFORMED_FILE || !isFileCacheEntryEnvelope(decoded) || decoded.key !== key) {
|
|
322
|
+
return removeInvalidFileAndReadMissing(filePath, decoded);
|
|
323
|
+
}
|
|
324
|
+
if (typeof decoded.expiresAt === "number" && decoded.expiresAt <= now) {
|
|
325
|
+
return removeInvalidFileAndReadMissing(filePath, decoded);
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
state: "hit",
|
|
329
|
+
filePath,
|
|
330
|
+
value: decoded
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
async function readLock(rootPath, name, now) {
|
|
334
|
+
const filePath = resolveLockFilePath(rootPath, name);
|
|
335
|
+
const decoded = await readJsonFile(filePath);
|
|
336
|
+
if (decoded === MALFORMED_FILE || !isFileCacheLockEnvelope(decoded) || decoded.name !== name) {
|
|
337
|
+
return removeInvalidFileAndReadMissing(filePath, decoded);
|
|
338
|
+
}
|
|
339
|
+
if (decoded.expiresAt <= now) {
|
|
340
|
+
return removeInvalidFileAndReadMissing(filePath, decoded);
|
|
341
|
+
}
|
|
342
|
+
return {
|
|
343
|
+
state: "hit",
|
|
344
|
+
filePath,
|
|
345
|
+
value: decoded
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
function createEntryEnvelope(input) {
|
|
349
|
+
return {
|
|
350
|
+
key: input.key,
|
|
351
|
+
payload: input.payload,
|
|
352
|
+
expiresAt: input.expiresAt
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
function createFileLock(rootPath, name, seconds, now, sleep, ownerFactory) {
|
|
356
|
+
const owner = ownerFactory();
|
|
357
|
+
async function tryAcquire() {
|
|
358
|
+
const filePath = resolveLockFilePath(rootPath, name);
|
|
359
|
+
const envelope = JSON.stringify({
|
|
360
|
+
name,
|
|
361
|
+
owner,
|
|
362
|
+
expiresAt: now() + seconds * 1e3
|
|
363
|
+
});
|
|
364
|
+
if (await writeFileExclusively(filePath, envelope)) {
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
const currentLock = await readLock(rootPath, name, now());
|
|
368
|
+
if (currentLock.state === "missing") {
|
|
369
|
+
return writeFileExclusively(filePath, envelope);
|
|
370
|
+
}
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
async function withCallback(callback) {
|
|
374
|
+
if (!callback) {
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
try {
|
|
378
|
+
return await callback();
|
|
379
|
+
} finally {
|
|
380
|
+
await lock.release();
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const lock = {
|
|
384
|
+
name,
|
|
385
|
+
async get(callback) {
|
|
386
|
+
if (!await tryAcquire()) {
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
return withCallback(callback);
|
|
390
|
+
},
|
|
391
|
+
async release() {
|
|
392
|
+
const currentLock = await readLock(rootPath, name, now());
|
|
393
|
+
if (currentLock.state === "missing" || currentLock.value.owner !== owner) {
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
const latestLock = await readLock(rootPath, name, now());
|
|
397
|
+
if (latestLock.state === "missing" || latestLock.value.owner !== owner) {
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
await removeFileIfPresent(currentLock.filePath);
|
|
401
|
+
return true;
|
|
402
|
+
},
|
|
403
|
+
async block(waitSeconds, callback) {
|
|
404
|
+
const deadline = now() + waitSeconds * 1e3;
|
|
405
|
+
while (true) {
|
|
406
|
+
if (await tryAcquire()) {
|
|
407
|
+
return withCallback(callback);
|
|
408
|
+
}
|
|
409
|
+
if (now() >= deadline) {
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
await sleep(10);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
return lock;
|
|
417
|
+
}
|
|
418
|
+
function createFileCacheDriver(options) {
|
|
419
|
+
const rootPath = resolveDriverRoot(options.path);
|
|
420
|
+
const prefix = options.prefix ?? "";
|
|
421
|
+
const now = options.now ?? Date.now;
|
|
422
|
+
const sleep = options.sleep ?? defaultSleep;
|
|
423
|
+
const ownerFactory = options.ownerFactory ?? randomUUID;
|
|
424
|
+
const numericMutationLockSeconds = options.numericMutationLockSeconds ?? 5;
|
|
425
|
+
const numericMutationWaitSeconds = options.numericMutationWaitSeconds ?? 2;
|
|
426
|
+
async function writeEntry(input) {
|
|
427
|
+
const filePath = resolveEntryFilePath(rootPath, input.key);
|
|
428
|
+
await writeFileAtomically(filePath, JSON.stringify(createEntryEnvelope(input)));
|
|
429
|
+
return true;
|
|
430
|
+
}
|
|
431
|
+
async function mutateNumericValue(key, amount) {
|
|
432
|
+
const result = await createFileLock(
|
|
433
|
+
rootPath,
|
|
434
|
+
`__numeric__:${key}`,
|
|
435
|
+
numericMutationLockSeconds,
|
|
436
|
+
now,
|
|
437
|
+
sleep,
|
|
438
|
+
ownerFactory
|
|
439
|
+
).block(numericMutationWaitSeconds, async () => {
|
|
440
|
+
const entry = await readEntry(rootPath, key, now());
|
|
441
|
+
const currentValue = entry.state === "hit" ? deserializeCacheValue(entry.value.payload) : 0;
|
|
442
|
+
if (typeof currentValue !== "number" || !Number.isFinite(currentValue)) {
|
|
443
|
+
throw new CacheInvalidNumericMutationError(`[@holo-js/cache] Cache key "${key}" does not contain a numeric value.`);
|
|
444
|
+
}
|
|
445
|
+
const nextValue = currentValue + amount;
|
|
446
|
+
await writeEntry({
|
|
447
|
+
key,
|
|
448
|
+
payload: serializeCacheValue(nextValue),
|
|
449
|
+
expiresAt: entry.state === "hit" ? entry.value.expiresAt : void 0
|
|
450
|
+
});
|
|
451
|
+
return nextValue;
|
|
452
|
+
});
|
|
453
|
+
if (result === false) {
|
|
454
|
+
throw new CacheLockAcquisitionError(`[@holo-js/cache] Could not acquire file cache mutation lock for "${key}".`);
|
|
455
|
+
}
|
|
456
|
+
if (result === true) {
|
|
457
|
+
throw new CacheLockAcquisitionError(`[@holo-js/cache] File cache mutation lock for "${key}" returned no numeric result.`);
|
|
458
|
+
}
|
|
459
|
+
return result;
|
|
460
|
+
}
|
|
461
|
+
return {
|
|
462
|
+
name: options.name,
|
|
463
|
+
driver: "file",
|
|
464
|
+
async get(key) {
|
|
465
|
+
const entry = await readEntry(rootPath, key, now());
|
|
466
|
+
if (entry.state === "missing") {
|
|
467
|
+
return Object.freeze({ hit: false });
|
|
468
|
+
}
|
|
469
|
+
return Object.freeze({
|
|
470
|
+
hit: true,
|
|
471
|
+
payload: entry.value.payload,
|
|
472
|
+
expiresAt: entry.value.expiresAt
|
|
473
|
+
});
|
|
474
|
+
},
|
|
475
|
+
async put(input) {
|
|
476
|
+
return writeEntry(input);
|
|
477
|
+
},
|
|
478
|
+
async add(input) {
|
|
479
|
+
const nowValue = now();
|
|
480
|
+
const filePath = resolveEntryFilePath(rootPath, input.key);
|
|
481
|
+
const existing = await readEntry(rootPath, input.key, nowValue);
|
|
482
|
+
if (existing.state === "hit") {
|
|
483
|
+
return false;
|
|
484
|
+
}
|
|
485
|
+
if (await writeFileExclusively(filePath, JSON.stringify(createEntryEnvelope(input)))) {
|
|
486
|
+
return true;
|
|
487
|
+
}
|
|
488
|
+
const afterCollision = await readEntry(rootPath, input.key, nowValue);
|
|
489
|
+
if (afterCollision.state === "hit") {
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
492
|
+
return writeFileExclusively(filePath, JSON.stringify(createEntryEnvelope(input)));
|
|
493
|
+
},
|
|
494
|
+
async forget(key) {
|
|
495
|
+
const filePath = resolveEntryFilePath(rootPath, key);
|
|
496
|
+
const contents = await readFileIfPresent(filePath);
|
|
497
|
+
if (typeof contents === "undefined") {
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
await removeFileIfPresent(filePath);
|
|
501
|
+
return true;
|
|
502
|
+
},
|
|
503
|
+
async flush() {
|
|
504
|
+
await removeScopedCacheFiles(
|
|
505
|
+
join(rootPath, "entries"),
|
|
506
|
+
prefix,
|
|
507
|
+
isFileCacheEntryEnvelope,
|
|
508
|
+
(entry) => entry.key
|
|
509
|
+
);
|
|
510
|
+
await removeScopedCacheFiles(
|
|
511
|
+
join(rootPath, "locks"),
|
|
512
|
+
prefix,
|
|
513
|
+
isFileCacheLockEnvelope,
|
|
514
|
+
(lock) => lock.name
|
|
515
|
+
);
|
|
516
|
+
},
|
|
517
|
+
async increment(key, amount) {
|
|
518
|
+
return mutateNumericValue(key, amount);
|
|
519
|
+
},
|
|
520
|
+
async decrement(key, amount) {
|
|
521
|
+
return mutateNumericValue(key, -amount);
|
|
522
|
+
},
|
|
523
|
+
lock(name, seconds) {
|
|
524
|
+
return createFileLock(rootPath, name, seconds, now, sleep, ownerFactory);
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
var fileDriverInternals = {
|
|
529
|
+
hashCacheKey,
|
|
530
|
+
isFileCacheEntryEnvelope,
|
|
531
|
+
isFileCacheLockEnvelope,
|
|
532
|
+
resolveDriverRoot,
|
|
533
|
+
resolveEntryFilePath,
|
|
534
|
+
resolveLockFilePath
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
// src/memory.ts
|
|
538
|
+
function createMemoryCacheDriverState() {
|
|
539
|
+
return {
|
|
540
|
+
entries: /* @__PURE__ */ new Map(),
|
|
541
|
+
locks: /* @__PURE__ */ new Map()
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
function defaultSleep2(milliseconds) {
|
|
545
|
+
return new Promise((resolve2) => {
|
|
546
|
+
setTimeout(resolve2, milliseconds);
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
function createMemoryLock(state, name, seconds, now, sleep) {
|
|
550
|
+
const owner = Symbol(name);
|
|
551
|
+
function clearExpiredLock() {
|
|
552
|
+
const current = state.locks.get(name);
|
|
553
|
+
if (current && current.expiresAt <= now()) {
|
|
554
|
+
state.locks.delete(name);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
function tryAcquire() {
|
|
558
|
+
clearExpiredLock();
|
|
559
|
+
const current = state.locks.get(name);
|
|
560
|
+
if (current) {
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
state.locks.set(name, {
|
|
564
|
+
owner,
|
|
565
|
+
expiresAt: now() + seconds * 1e3
|
|
566
|
+
});
|
|
567
|
+
return true;
|
|
568
|
+
}
|
|
569
|
+
async function withCallback(callback) {
|
|
570
|
+
if (!callback) {
|
|
571
|
+
return true;
|
|
572
|
+
}
|
|
573
|
+
try {
|
|
574
|
+
return await callback();
|
|
575
|
+
} finally {
|
|
576
|
+
await lock.release();
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
const lock = {
|
|
580
|
+
name,
|
|
581
|
+
async get(callback) {
|
|
582
|
+
if (!tryAcquire()) {
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
return withCallback(callback);
|
|
586
|
+
},
|
|
587
|
+
async release() {
|
|
588
|
+
clearExpiredLock();
|
|
589
|
+
const current = state.locks.get(name);
|
|
590
|
+
if (!current || current.owner !== owner) {
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
state.locks.delete(name);
|
|
594
|
+
return true;
|
|
595
|
+
},
|
|
596
|
+
async block(waitSeconds, callback) {
|
|
597
|
+
const deadline = now() + waitSeconds * 1e3;
|
|
598
|
+
while (true) {
|
|
599
|
+
if (tryAcquire()) {
|
|
600
|
+
return withCallback(callback);
|
|
601
|
+
}
|
|
602
|
+
if (now() >= deadline) {
|
|
603
|
+
return false;
|
|
604
|
+
}
|
|
605
|
+
await sleep(10);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
return lock;
|
|
610
|
+
}
|
|
611
|
+
function isExpired(entry, timestamp) {
|
|
612
|
+
return typeof entry.expiresAt === "number" && entry.expiresAt <= timestamp;
|
|
613
|
+
}
|
|
614
|
+
function pruneExpiredEntry(state, key, timestamp) {
|
|
615
|
+
const entry = state.entries.get(key);
|
|
616
|
+
if (!entry) {
|
|
617
|
+
return void 0;
|
|
618
|
+
}
|
|
619
|
+
if (isExpired(entry, timestamp)) {
|
|
620
|
+
state.entries.delete(key);
|
|
621
|
+
return void 0;
|
|
622
|
+
}
|
|
623
|
+
return entry;
|
|
624
|
+
}
|
|
625
|
+
function pruneExpiredEntries(state, timestamp) {
|
|
626
|
+
for (const [key, entry] of state.entries.entries()) {
|
|
627
|
+
if (isExpired(entry, timestamp)) {
|
|
628
|
+
state.entries.delete(key);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
function setEntry(state, input) {
|
|
633
|
+
state.entries.set(input.key, {
|
|
634
|
+
payload: input.payload,
|
|
635
|
+
expiresAt: input.expiresAt
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
function enforceMaxEntries(state, maxEntries, timestamp) {
|
|
639
|
+
if (typeof maxEntries === "undefined") {
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
pruneExpiredEntries(state, timestamp);
|
|
643
|
+
while (state.entries.size > maxEntries) {
|
|
644
|
+
const oldestKey = state.entries.keys().next().value;
|
|
645
|
+
if (typeof oldestKey === "undefined") {
|
|
646
|
+
break;
|
|
647
|
+
}
|
|
648
|
+
state.entries.delete(oldestKey);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
function createMemoryCacheDriver(options) {
|
|
652
|
+
const state = createMemoryCacheDriverState();
|
|
653
|
+
const now = options.now ?? Date.now;
|
|
654
|
+
const sleep = options.sleep ?? defaultSleep2;
|
|
655
|
+
return {
|
|
656
|
+
name: options.name,
|
|
657
|
+
driver: "memory",
|
|
658
|
+
async get(key) {
|
|
659
|
+
const entry = pruneExpiredEntry(state, key, now());
|
|
660
|
+
if (!entry) return Object.freeze({ hit: false });
|
|
661
|
+
return Object.freeze({
|
|
662
|
+
hit: true,
|
|
663
|
+
payload: entry.payload,
|
|
664
|
+
expiresAt: entry.expiresAt
|
|
665
|
+
});
|
|
666
|
+
},
|
|
667
|
+
async put(input) {
|
|
668
|
+
setEntry(state, input);
|
|
669
|
+
enforceMaxEntries(state, options.maxEntries, now());
|
|
670
|
+
return true;
|
|
671
|
+
},
|
|
672
|
+
async add(input) {
|
|
673
|
+
const timestamp = now();
|
|
674
|
+
if (pruneExpiredEntry(state, input.key, timestamp)) return false;
|
|
675
|
+
setEntry(state, input);
|
|
676
|
+
enforceMaxEntries(state, options.maxEntries, timestamp);
|
|
677
|
+
return true;
|
|
678
|
+
},
|
|
679
|
+
async forget(key) {
|
|
680
|
+
const existed = typeof pruneExpiredEntry(state, key, now()) !== "undefined";
|
|
681
|
+
state.entries.delete(key);
|
|
682
|
+
return existed;
|
|
683
|
+
},
|
|
684
|
+
async flush() {
|
|
685
|
+
state.entries.clear();
|
|
686
|
+
state.locks.clear();
|
|
687
|
+
},
|
|
688
|
+
async increment(key, amount) {
|
|
689
|
+
const entry = pruneExpiredEntry(state, key, now());
|
|
690
|
+
const currentValue = entry ? deserializeCacheValue(entry.payload) : 0;
|
|
691
|
+
if (typeof currentValue !== "number" || !Number.isFinite(currentValue)) {
|
|
692
|
+
throw new CacheInvalidNumericMutationError(`[@holo-js/cache] Cache key "${key}" does not contain a numeric value.`);
|
|
693
|
+
}
|
|
694
|
+
const nextValue = currentValue + amount;
|
|
695
|
+
setEntry(state, {
|
|
696
|
+
key,
|
|
697
|
+
payload: serializeCacheValue(nextValue),
|
|
698
|
+
expiresAt: entry?.expiresAt
|
|
699
|
+
});
|
|
700
|
+
enforceMaxEntries(state, options.maxEntries, now());
|
|
701
|
+
return nextValue;
|
|
702
|
+
},
|
|
703
|
+
async decrement(key, amount) {
|
|
704
|
+
return this.increment(key, -amount);
|
|
705
|
+
},
|
|
706
|
+
lock(name, seconds) {
|
|
707
|
+
return createMemoryLock(state, name, seconds, now, sleep);
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// src/redis.ts
|
|
713
|
+
import {
|
|
714
|
+
normalizeRedisConfig
|
|
715
|
+
} from "@holo-js/config";
|
|
716
|
+
function isNormalizedRedisConfig(config) {
|
|
717
|
+
return typeof config.default === "string" && typeof config.connections === "object" && config.connections !== null && Object.values(config.connections).every((connection) => {
|
|
718
|
+
return typeof connection === "object" && connection !== null && "name" in connection && "host" in connection && "port" in connection && typeof connection.name === "string" && typeof connection.host === "string" && typeof connection.port === "number";
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
function normalizeRuntimeRedisConfig(config) {
|
|
722
|
+
if (!config) return void 0;
|
|
723
|
+
return isNormalizedRedisConfig(config) ? config : normalizeRedisConfig(config);
|
|
724
|
+
}
|
|
725
|
+
function resolveSharedRedisConnection(redisConfig, connectionName) {
|
|
726
|
+
if (!redisConfig) {
|
|
727
|
+
throw new CacheDriverResolutionError(
|
|
728
|
+
`[@holo-js/cache] Redis cache driver "${connectionName}" requires a top-level redis config from config/redis.ts.`
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
const connection = redisConfig.connections[connectionName];
|
|
732
|
+
if (connection) return connection;
|
|
733
|
+
const availableConnections = Object.keys(redisConfig.connections);
|
|
734
|
+
throw new CacheDriverResolutionError(
|
|
735
|
+
`[@holo-js/cache] Redis cache connection "${connectionName}" was not found in config/redis.ts. Available connections: ${availableConnections.join(", ") || "(none)"}.`
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
function isModuleNotFoundError2(error) {
|
|
739
|
+
return !!error && typeof error === "object" && "code" in error && error.code === "ERR_MODULE_NOT_FOUND";
|
|
740
|
+
}
|
|
741
|
+
function normalizeRedisModuleLoadError(error) {
|
|
742
|
+
if (isModuleNotFoundError2(error)) {
|
|
743
|
+
return new CacheOptionalPackageError(
|
|
744
|
+
"[@holo-js/cache] Redis cache support requires @holo-js/cache-redis to be installed.",
|
|
745
|
+
{ cause: error }
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
return error;
|
|
749
|
+
}
|
|
750
|
+
async function loadRedisDriverModule() {
|
|
751
|
+
try {
|
|
752
|
+
const specifier = "@holo-js/cache-redis";
|
|
753
|
+
return await import(specifier);
|
|
754
|
+
} catch (error) {
|
|
755
|
+
throw normalizeRedisModuleLoadError(error);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
var redisDriverModuleLoader = loadRedisDriverModule;
|
|
759
|
+
function setRedisDriverModuleLoader(loader) {
|
|
760
|
+
redisDriverModuleLoader = loader;
|
|
761
|
+
}
|
|
762
|
+
function resetRedisDriverModuleLoader() {
|
|
763
|
+
redisDriverModuleLoader = loadRedisDriverModule;
|
|
764
|
+
}
|
|
765
|
+
var LazyRedisCacheDriver = class {
|
|
766
|
+
constructor(options) {
|
|
767
|
+
this.options = options;
|
|
768
|
+
}
|
|
769
|
+
driver = "redis";
|
|
770
|
+
driverInstance;
|
|
771
|
+
pending;
|
|
772
|
+
get name() {
|
|
773
|
+
return this.options.name;
|
|
774
|
+
}
|
|
775
|
+
async resolveDriver() {
|
|
776
|
+
if (this.driverInstance) return this.driverInstance;
|
|
777
|
+
this.pending ??= redisDriverModuleLoader().then((module) => {
|
|
778
|
+
const driver = module.createRedisCacheDriver(this.options);
|
|
779
|
+
this.driverInstance = driver;
|
|
780
|
+
return driver;
|
|
781
|
+
}).finally(() => {
|
|
782
|
+
this.pending = void 0;
|
|
783
|
+
});
|
|
784
|
+
return this.pending;
|
|
785
|
+
}
|
|
786
|
+
async withDriver(callback) {
|
|
787
|
+
return callback(await this.resolveDriver());
|
|
788
|
+
}
|
|
789
|
+
createLockProxy(name, seconds) {
|
|
790
|
+
let lockPromise;
|
|
791
|
+
const resolveLock = async () => {
|
|
792
|
+
lockPromise ??= this.withDriver((driver) => driver.lock(name, seconds));
|
|
793
|
+
return lockPromise;
|
|
794
|
+
};
|
|
795
|
+
return {
|
|
796
|
+
name,
|
|
797
|
+
async get(callback) {
|
|
798
|
+
return (await resolveLock()).get(callback);
|
|
799
|
+
},
|
|
800
|
+
async release() {
|
|
801
|
+
return (await resolveLock()).release();
|
|
802
|
+
},
|
|
803
|
+
async block(waitSeconds, callback) {
|
|
804
|
+
return (await resolveLock()).block(waitSeconds, callback);
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
async get(key) {
|
|
809
|
+
return this.withDriver((driver) => driver.get(key));
|
|
810
|
+
}
|
|
811
|
+
async put(input) {
|
|
812
|
+
return this.withDriver((driver) => driver.put(input));
|
|
813
|
+
}
|
|
814
|
+
async add(input) {
|
|
815
|
+
return this.withDriver((driver) => driver.add(input));
|
|
816
|
+
}
|
|
817
|
+
async forget(key) {
|
|
818
|
+
return this.withDriver((driver) => driver.forget(key));
|
|
819
|
+
}
|
|
820
|
+
async flush() {
|
|
821
|
+
await this.withDriver((driver) => driver.flush());
|
|
822
|
+
}
|
|
823
|
+
async increment(key, amount) {
|
|
824
|
+
return this.withDriver((driver) => driver.increment(key, amount));
|
|
825
|
+
}
|
|
826
|
+
async decrement(key, amount) {
|
|
827
|
+
return this.withDriver((driver) => driver.decrement(key, amount));
|
|
828
|
+
}
|
|
829
|
+
lock(name, seconds) {
|
|
830
|
+
return this.createLockProxy(name, seconds);
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
function createRedisCacheDriver(options) {
|
|
834
|
+
return new LazyRedisCacheDriver(options);
|
|
835
|
+
}
|
|
836
|
+
var cacheRedisInternals = {
|
|
837
|
+
isModuleNotFoundError: isModuleNotFoundError2,
|
|
838
|
+
isNormalizedRedisConfig,
|
|
839
|
+
loadRedisDriverModule,
|
|
840
|
+
normalizeRedisModuleLoadError,
|
|
841
|
+
normalizeRuntimeRedisConfig,
|
|
842
|
+
resolveSharedRedisConnection,
|
|
843
|
+
resetRedisDriverModuleLoader,
|
|
844
|
+
setRedisDriverModuleLoader
|
|
845
|
+
};
|
|
846
|
+
|
|
847
|
+
// src/runtime-shared.ts
|
|
848
|
+
function isNormalizedCacheConfig(config) {
|
|
849
|
+
return typeof config.default === "string" && typeof config.prefix === "string" && typeof config.drivers === "object" && config.drivers !== null && Object.values(config.drivers).every((driver) => {
|
|
850
|
+
return typeof driver === "object" && driver !== null && "name" in driver && "prefix" in driver && typeof driver.name === "string" && typeof driver.prefix === "string";
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
function normalizeRuntimeConfig(config) {
|
|
854
|
+
if (!config) return holoCacheDefaults;
|
|
855
|
+
return isNormalizedCacheConfig(config) ? config : normalizeCacheConfig(config);
|
|
856
|
+
}
|
|
857
|
+
function getCacheRuntimeState() {
|
|
858
|
+
const runtime = globalThis;
|
|
859
|
+
runtime.__holoCacheRuntime__ ??= {};
|
|
860
|
+
return runtime.__holoCacheRuntime__;
|
|
861
|
+
}
|
|
862
|
+
function getCacheRuntimeBindings() {
|
|
863
|
+
return getCacheRuntimeState().bindings;
|
|
864
|
+
}
|
|
865
|
+
function getCacheRuntime() {
|
|
866
|
+
const bindings = getCacheRuntimeBindings();
|
|
867
|
+
if (!bindings) {
|
|
868
|
+
throw new CacheRuntimeNotConfiguredError();
|
|
869
|
+
}
|
|
870
|
+
return bindings;
|
|
871
|
+
}
|
|
872
|
+
function createDriverMap(drivers) {
|
|
873
|
+
return drivers ? new Map(drivers.entries()) : /* @__PURE__ */ new Map();
|
|
874
|
+
}
|
|
875
|
+
function cacheResolvedDriver(facade, driverName, driver) {
|
|
876
|
+
facade.drivers.set(driverName, driver);
|
|
877
|
+
return driver;
|
|
878
|
+
}
|
|
879
|
+
function resolveConfiguredDriver(facade, requestedName) {
|
|
880
|
+
const driverName = requestedName?.trim() || facade.config.default;
|
|
881
|
+
const cachedDriver = facade.drivers.get(driverName);
|
|
882
|
+
if (cachedDriver) {
|
|
883
|
+
return cachedDriver;
|
|
884
|
+
}
|
|
885
|
+
const driverConfig = facade.config.drivers[driverName];
|
|
886
|
+
if (!driverConfig) {
|
|
887
|
+
throw new CacheDriverResolutionError(`[@holo-js/cache] Cache driver "${driverName}" is not configured.`);
|
|
888
|
+
}
|
|
889
|
+
switch (driverConfig.driver) {
|
|
890
|
+
case "file": {
|
|
891
|
+
return cacheResolvedDriver(facade, driverName, createFileCacheDriver({
|
|
892
|
+
name: driverConfig.name,
|
|
893
|
+
path: driverConfig.path,
|
|
894
|
+
prefix: driverConfig.prefix
|
|
895
|
+
}));
|
|
896
|
+
}
|
|
897
|
+
case "memory": {
|
|
898
|
+
return cacheResolvedDriver(facade, driverName, createMemoryCacheDriver({
|
|
899
|
+
name: driverConfig.name,
|
|
900
|
+
maxEntries: driverConfig.maxEntries
|
|
901
|
+
}));
|
|
902
|
+
}
|
|
903
|
+
case "redis": {
|
|
904
|
+
const connection = cacheRedisInternals.resolveSharedRedisConnection(
|
|
905
|
+
facade.redisConfig,
|
|
906
|
+
driverConfig.connection
|
|
907
|
+
);
|
|
908
|
+
return cacheResolvedDriver(facade, driverName, createRedisCacheDriver({
|
|
909
|
+
name: driverConfig.name,
|
|
910
|
+
connectionName: connection.name,
|
|
911
|
+
prefix: driverConfig.prefix,
|
|
912
|
+
redis: connection
|
|
913
|
+
}));
|
|
914
|
+
}
|
|
915
|
+
case "database": {
|
|
916
|
+
const connection = cacheDbInternals.resolveSharedDatabaseConnection(
|
|
917
|
+
facade.databaseConfig,
|
|
918
|
+
driverConfig.connection
|
|
919
|
+
);
|
|
920
|
+
return cacheResolvedDriver(facade, driverName, createDatabaseCacheDriver({
|
|
921
|
+
name: driverConfig.name,
|
|
922
|
+
connectionName: driverConfig.connection,
|
|
923
|
+
table: driverConfig.table,
|
|
924
|
+
lockTable: driverConfig.lockTable,
|
|
925
|
+
prefix: driverConfig.prefix,
|
|
926
|
+
connection
|
|
927
|
+
}));
|
|
928
|
+
}
|
|
929
|
+
default:
|
|
930
|
+
throw new CacheDriverResolutionError(
|
|
931
|
+
`[@holo-js/cache] Cache driver "${driverName}" uses unsupported driver "${String(driverConfig.driver)}" in this phase.`
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// src/query-bridge.ts
|
|
937
|
+
function createDependencyIndexState() {
|
|
938
|
+
return {
|
|
939
|
+
keyToDependencies: /* @__PURE__ */ new Map(),
|
|
940
|
+
dependencyToKeys: /* @__PURE__ */ new Map()
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
function createMemoryDependencyIndex(state = createDependencyIndexState()) {
|
|
944
|
+
return Object.freeze({
|
|
945
|
+
async register(key, dependencies) {
|
|
946
|
+
await this.removeKey(key);
|
|
947
|
+
if (dependencies.length === 0) {
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
const uniqueDependencies = new Set(dependencies);
|
|
951
|
+
state.keyToDependencies.set(key, uniqueDependencies);
|
|
952
|
+
for (const dependency of uniqueDependencies) {
|
|
953
|
+
const keys = state.dependencyToKeys.get(dependency) ?? /* @__PURE__ */ new Set();
|
|
954
|
+
keys.add(key);
|
|
955
|
+
state.dependencyToKeys.set(dependency, keys);
|
|
956
|
+
}
|
|
957
|
+
},
|
|
958
|
+
async listKeys(dependency) {
|
|
959
|
+
return Object.freeze([...state.dependencyToKeys.get(dependency) ?? /* @__PURE__ */ new Set()]);
|
|
960
|
+
},
|
|
961
|
+
async listRegisteredKeys() {
|
|
962
|
+
return Object.freeze([...state.keyToDependencies.keys()]);
|
|
963
|
+
},
|
|
964
|
+
async removeKey(key) {
|
|
965
|
+
const dependencies = state.keyToDependencies.get(key);
|
|
966
|
+
if (!dependencies) {
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
state.keyToDependencies.delete(key);
|
|
970
|
+
for (const dependency of dependencies) {
|
|
971
|
+
const keys = state.dependencyToKeys.get(dependency);
|
|
972
|
+
if (!keys) {
|
|
973
|
+
continue;
|
|
974
|
+
}
|
|
975
|
+
keys.delete(key);
|
|
976
|
+
if (keys.size === 0) {
|
|
977
|
+
state.dependencyToKeys.delete(dependency);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
},
|
|
981
|
+
async clear() {
|
|
982
|
+
state.keyToDependencies.clear();
|
|
983
|
+
state.dependencyToKeys.clear();
|
|
984
|
+
}
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
function getQueryBridgeState() {
|
|
988
|
+
const runtime = globalThis;
|
|
989
|
+
runtime.__holoCacheQueryBridge__ ??= {};
|
|
990
|
+
return runtime.__holoCacheQueryBridge__;
|
|
991
|
+
}
|
|
992
|
+
function getOrCreateDependencyIndex() {
|
|
993
|
+
const state = getQueryBridgeState();
|
|
994
|
+
state.dependencyIndex ??= createMemoryDependencyIndex();
|
|
995
|
+
return state.dependencyIndex;
|
|
996
|
+
}
|
|
997
|
+
function resetDefaultDependencyIndex() {
|
|
998
|
+
getQueryBridgeState().dependencyIndex = void 0;
|
|
999
|
+
}
|
|
1000
|
+
function resolveDriverContext(driverName) {
|
|
1001
|
+
const runtime = getCacheRuntime();
|
|
1002
|
+
const configuredDriverName = driverName?.trim() || runtime.config.default;
|
|
1003
|
+
const driverConfig = runtime.config.drivers[configuredDriverName];
|
|
1004
|
+
let normalizedKeyPrefix = runtime.config.prefix;
|
|
1005
|
+
if (typeof driverConfig?.prefix === "string") {
|
|
1006
|
+
normalizedKeyPrefix = driverConfig.prefix;
|
|
1007
|
+
}
|
|
1008
|
+
return Object.freeze({
|
|
1009
|
+
driverName: configuredDriverName,
|
|
1010
|
+
driver: resolveConfiguredDriver(runtime, configuredDriverName),
|
|
1011
|
+
normalizedKeyPrefix
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
function resolveNormalizedKey(key, driverName) {
|
|
1015
|
+
const context = resolveDriverContext(driverName);
|
|
1016
|
+
return `${context.normalizedKeyPrefix}${resolveCacheKey(key)}`;
|
|
1017
|
+
}
|
|
1018
|
+
function normalizeFlexibleTtl(ttl) {
|
|
1019
|
+
const freshSeconds = "fresh" in ttl ? ttl.fresh : ttl[0];
|
|
1020
|
+
const staleSeconds = "stale" in ttl ? ttl.stale : ttl[1];
|
|
1021
|
+
if (!Number.isInteger(freshSeconds) || freshSeconds < 0) {
|
|
1022
|
+
throw new CacheInvalidTtlError("[@holo-js/cache] Flexible fresh TTL must be an integer greater than or equal to 0.");
|
|
1023
|
+
}
|
|
1024
|
+
if (!Number.isInteger(staleSeconds) || staleSeconds < freshSeconds) {
|
|
1025
|
+
throw new CacheInvalidTtlError("[@holo-js/cache] Flexible stale TTL must be an integer greater than or equal to the fresh TTL.");
|
|
1026
|
+
}
|
|
1027
|
+
return Object.freeze({
|
|
1028
|
+
freshSeconds,
|
|
1029
|
+
staleSeconds
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
function isFlexibleEnvelope(value) {
|
|
1033
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1034
|
+
return false;
|
|
1035
|
+
}
|
|
1036
|
+
const envelope = value;
|
|
1037
|
+
return envelope.__holo_cache_flexible === true && typeof envelope.freshUntil === "number" && Number.isFinite(envelope.freshUntil) && typeof envelope.staleUntil === "number" && Number.isFinite(envelope.staleUntil) && "value" in envelope;
|
|
1038
|
+
}
|
|
1039
|
+
async function getCachedValue(key, driverName) {
|
|
1040
|
+
const context = resolveDriverContext(driverName);
|
|
1041
|
+
const entry = await context.driver.get(resolveNormalizedKey(key, driverName));
|
|
1042
|
+
if (!entry.hit || typeof entry.payload !== "string") {
|
|
1043
|
+
return null;
|
|
1044
|
+
}
|
|
1045
|
+
return deserializeCacheValue(entry.payload);
|
|
1046
|
+
}
|
|
1047
|
+
async function putCachedValue(key, value, ttl, driverName) {
|
|
1048
|
+
const context = resolveDriverContext(driverName);
|
|
1049
|
+
const expiresAt = typeof ttl === "undefined" ? void 0 : normalizeCacheTtl(ttl).expiresAt;
|
|
1050
|
+
await context.driver.put({
|
|
1051
|
+
key: resolveNormalizedKey(key, driverName),
|
|
1052
|
+
payload: serializeCacheValue(value),
|
|
1053
|
+
expiresAt
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
function createFlexibleLock(key, ttl, driverName) {
|
|
1057
|
+
const context = resolveDriverContext(driverName);
|
|
1058
|
+
return context.driver.lock(
|
|
1059
|
+
`${context.normalizedKeyPrefix}__flexible__:${resolveCacheKey(key)}`,
|
|
1060
|
+
Math.max(1, ttl.staleSeconds)
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
function createIndexedKey(key, driverName) {
|
|
1064
|
+
const context = resolveDriverContext(driverName);
|
|
1065
|
+
return `${context.driverName}\0${resolveNormalizedKey(key, driverName)}`;
|
|
1066
|
+
}
|
|
1067
|
+
function parseIndexedKey(indexedKey) {
|
|
1068
|
+
const delimiterIndex = indexedKey.indexOf("\0");
|
|
1069
|
+
if (delimiterIndex === -1) {
|
|
1070
|
+
return Object.freeze({
|
|
1071
|
+
driverName: getCacheRuntime().config.default,
|
|
1072
|
+
normalizedKey: indexedKey
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
return Object.freeze({
|
|
1076
|
+
driverName: indexedKey.slice(0, delimiterIndex),
|
|
1077
|
+
normalizedKey: indexedKey.slice(delimiterIndex + 1)
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
function setGlobalDatabaseQueryCacheBridge(bridge) {
|
|
1081
|
+
const runtime = globalThis;
|
|
1082
|
+
runtime.__holoDbQueryCacheBridge__ ??= {};
|
|
1083
|
+
runtime.__holoDbQueryCacheBridge__.bridge = bridge;
|
|
1084
|
+
}
|
|
1085
|
+
function createCacheQueryBridge(dependencyIndex = getOrCreateDependencyIndex()) {
|
|
1086
|
+
async function syncDependencies(indexedKey, dependencies) {
|
|
1087
|
+
if (dependencies && dependencies.length > 0) {
|
|
1088
|
+
await dependencyIndex.register(indexedKey, dependencies);
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
await dependencyIndex.removeKey(indexedKey);
|
|
1092
|
+
}
|
|
1093
|
+
return Object.freeze({
|
|
1094
|
+
async get(key, options) {
|
|
1095
|
+
return getCachedValue(key, options?.driver);
|
|
1096
|
+
},
|
|
1097
|
+
async put(key, value, options) {
|
|
1098
|
+
const indexedKey = createIndexedKey(key, options.driver);
|
|
1099
|
+
const resolvedTtl = typeof options.flexible === "undefined" ? options.ttl : normalizeFlexibleTtl(options.flexible).staleSeconds;
|
|
1100
|
+
await putCachedValue(key, value, resolvedTtl, options.driver);
|
|
1101
|
+
await syncDependencies(indexedKey, options.dependencies);
|
|
1102
|
+
},
|
|
1103
|
+
async flexible(key, ttl, callback, options = {}) {
|
|
1104
|
+
const indexedKey = createIndexedKey(key, options.driver);
|
|
1105
|
+
const normalizedTtl = normalizeFlexibleTtl(ttl);
|
|
1106
|
+
const now = Date.now();
|
|
1107
|
+
const cached = await getCachedValue(key, options.driver);
|
|
1108
|
+
const refreshValue = async () => {
|
|
1109
|
+
const value = await callback();
|
|
1110
|
+
const refreshedAt = Date.now();
|
|
1111
|
+
const envelope = {
|
|
1112
|
+
__holo_cache_flexible: true,
|
|
1113
|
+
value,
|
|
1114
|
+
freshUntil: refreshedAt + normalizedTtl.freshSeconds * 1e3,
|
|
1115
|
+
staleUntil: refreshedAt + normalizedTtl.staleSeconds * 1e3
|
|
1116
|
+
};
|
|
1117
|
+
await putCachedValue(
|
|
1118
|
+
key,
|
|
1119
|
+
envelope,
|
|
1120
|
+
normalizedTtl.staleSeconds,
|
|
1121
|
+
options.driver
|
|
1122
|
+
);
|
|
1123
|
+
await syncDependencies(indexedKey, options.dependencies);
|
|
1124
|
+
return value;
|
|
1125
|
+
};
|
|
1126
|
+
if (isFlexibleEnvelope(cached)) {
|
|
1127
|
+
if (now <= cached.freshUntil) {
|
|
1128
|
+
return cached.value;
|
|
1129
|
+
}
|
|
1130
|
+
if (now <= cached.staleUntil) {
|
|
1131
|
+
const refreshLock2 = createFlexibleLock(key, normalizedTtl, options.driver);
|
|
1132
|
+
void refreshLock2.get(async () => {
|
|
1133
|
+
await refreshValue();
|
|
1134
|
+
return true;
|
|
1135
|
+
}).catch(() => void 0);
|
|
1136
|
+
return cached.value;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
const refreshLock = createFlexibleLock(key, normalizedTtl, options.driver);
|
|
1140
|
+
const refreshed = await refreshLock.block(1, async () => refreshValue());
|
|
1141
|
+
if (refreshed !== false) {
|
|
1142
|
+
return refreshed;
|
|
1143
|
+
}
|
|
1144
|
+
const retried = await getCachedValue(key, options.driver);
|
|
1145
|
+
if (isFlexibleEnvelope(retried)) {
|
|
1146
|
+
if (Date.now() <= retried.staleUntil) {
|
|
1147
|
+
return retried.value;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
return refreshValue();
|
|
1151
|
+
},
|
|
1152
|
+
async forget(key, options) {
|
|
1153
|
+
const indexedKey = createIndexedKey(key, options?.driver);
|
|
1154
|
+
const context = resolveDriverContext(options?.driver);
|
|
1155
|
+
await dependencyIndex.removeKey(indexedKey);
|
|
1156
|
+
return context.driver.forget(resolveNormalizedKey(key, options?.driver));
|
|
1157
|
+
},
|
|
1158
|
+
async invalidateDependencies(dependencies) {
|
|
1159
|
+
const invalidatedKeys = /* @__PURE__ */ new Set();
|
|
1160
|
+
const runtime = getCacheRuntime();
|
|
1161
|
+
for (const dependency of dependencies) {
|
|
1162
|
+
const indexedKeys = await dependencyIndex.listKeys(dependency);
|
|
1163
|
+
for (const indexedKey of indexedKeys) {
|
|
1164
|
+
if (invalidatedKeys.has(indexedKey)) {
|
|
1165
|
+
continue;
|
|
1166
|
+
}
|
|
1167
|
+
invalidatedKeys.add(indexedKey);
|
|
1168
|
+
const parsed = parseIndexedKey(indexedKey);
|
|
1169
|
+
const driver = resolveConfiguredDriver(runtime, parsed.driverName);
|
|
1170
|
+
await driver.forget(parsed.normalizedKey);
|
|
1171
|
+
await dependencyIndex.removeKey(indexedKey);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1177
|
+
var cacheQueryBridgeInternals = {
|
|
1178
|
+
createCacheQueryBridge,
|
|
1179
|
+
createIndexedKey,
|
|
1180
|
+
createMemoryDependencyIndex,
|
|
1181
|
+
getOrCreateDependencyIndex,
|
|
1182
|
+
parseIndexedKey,
|
|
1183
|
+
resetDefaultDependencyIndex,
|
|
1184
|
+
setGlobalDatabaseQueryCacheBridge
|
|
1185
|
+
};
|
|
1186
|
+
|
|
1187
|
+
// src/runtime.ts
|
|
1188
|
+
function configureCacheRuntime(bindings) {
|
|
1189
|
+
if (!bindings) {
|
|
1190
|
+
getCacheRuntimeState().bindings = void 0;
|
|
1191
|
+
resetDefaultDependencyIndex();
|
|
1192
|
+
setGlobalDatabaseQueryCacheBridge(void 0);
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
const dependencyIndex = bindings.dependencyIndex ?? getOrCreateDependencyIndex();
|
|
1196
|
+
const queryBridge = bindings.queryBridge ?? createCacheQueryBridge(dependencyIndex);
|
|
1197
|
+
getCacheRuntimeState().bindings = Object.freeze({
|
|
1198
|
+
config: normalizeRuntimeConfig(bindings.config),
|
|
1199
|
+
databaseConfig: cacheDbInternals.normalizeRuntimeDatabaseConfig(bindings.databaseConfig),
|
|
1200
|
+
redisConfig: cacheRedisInternals.normalizeRuntimeRedisConfig(bindings.redisConfig),
|
|
1201
|
+
drivers: createDriverMap(bindings.drivers),
|
|
1202
|
+
dependencyIndex,
|
|
1203
|
+
queryBridge
|
|
1204
|
+
});
|
|
1205
|
+
setGlobalDatabaseQueryCacheBridge(queryBridge);
|
|
1206
|
+
}
|
|
1207
|
+
function resetCacheRuntime() {
|
|
1208
|
+
getCacheRuntimeState().bindings = void 0;
|
|
1209
|
+
resetDefaultDependencyIndex();
|
|
1210
|
+
setGlobalDatabaseQueryCacheBridge(void 0);
|
|
1211
|
+
}
|
|
1212
|
+
var cacheRuntimeInternals = {
|
|
1213
|
+
getCacheRuntimeState,
|
|
1214
|
+
isNormalizedCacheConfig,
|
|
1215
|
+
normalizeRuntimeConfig,
|
|
1216
|
+
resolveConfiguredDriver
|
|
1217
|
+
};
|
|
1218
|
+
|
|
1219
|
+
// src/facade.ts
|
|
1220
|
+
var MAX_REFRESH_BLOCK_SECONDS = 30;
|
|
1221
|
+
function resolveFallback(fallback) {
|
|
1222
|
+
return typeof fallback === "function" ? fallback() : fallback;
|
|
1223
|
+
}
|
|
1224
|
+
function resolveValue(callback) {
|
|
1225
|
+
return Promise.resolve(callback());
|
|
1226
|
+
}
|
|
1227
|
+
function resolveDriverKey(driverName) {
|
|
1228
|
+
const normalized = driverName?.trim();
|
|
1229
|
+
return normalized || "__default__";
|
|
1230
|
+
}
|
|
1231
|
+
function normalizeFlexibleTtl2(ttl) {
|
|
1232
|
+
const freshSeconds = "fresh" in ttl ? ttl.fresh : ttl[0];
|
|
1233
|
+
const staleSeconds = "stale" in ttl ? ttl.stale : ttl[1];
|
|
1234
|
+
if (!Number.isInteger(freshSeconds) || freshSeconds < 0) {
|
|
1235
|
+
throw new CacheInvalidTtlError("[@holo-js/cache] Flexible fresh TTL must be an integer greater than or equal to 0.");
|
|
1236
|
+
}
|
|
1237
|
+
if (!Number.isInteger(staleSeconds) || staleSeconds < freshSeconds) {
|
|
1238
|
+
throw new CacheInvalidTtlError("[@holo-js/cache] Flexible stale TTL must be an integer greater than or equal to the fresh TTL.");
|
|
1239
|
+
}
|
|
1240
|
+
return Object.freeze({
|
|
1241
|
+
freshSeconds,
|
|
1242
|
+
staleSeconds
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
function isFlexibleEnvelope2(value) {
|
|
1246
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1247
|
+
return false;
|
|
1248
|
+
}
|
|
1249
|
+
const envelope = value;
|
|
1250
|
+
return envelope.__holo_cache_flexible === true && typeof envelope.freshUntil === "number" && Number.isFinite(envelope.freshUntil) && typeof envelope.staleUntil === "number" && Number.isFinite(envelope.staleUntil) && "value" in envelope;
|
|
1251
|
+
}
|
|
1252
|
+
function createCacheRepository(driverName) {
|
|
1253
|
+
function resolveDriverContext2() {
|
|
1254
|
+
const runtime = getCacheRuntime();
|
|
1255
|
+
const configuredDriverName = driverName?.trim() || runtime.config.default;
|
|
1256
|
+
const driver = cacheRuntimeInternals.resolveConfiguredDriver(runtime, configuredDriverName);
|
|
1257
|
+
const config = runtime.config.drivers[configuredDriverName];
|
|
1258
|
+
return {
|
|
1259
|
+
configuredDriverName,
|
|
1260
|
+
driver,
|
|
1261
|
+
prefix: config?.prefix ?? runtime.config.prefix
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
function resolveNormalizedKey2(key) {
|
|
1265
|
+
const { prefix } = resolveDriverContext2();
|
|
1266
|
+
return `${prefix}${resolveCacheKey(key)}`;
|
|
1267
|
+
}
|
|
1268
|
+
function resolveNormalizedLockName(name) {
|
|
1269
|
+
const { prefix } = resolveDriverContext2();
|
|
1270
|
+
return `${prefix}${resolveCacheKey(name)}`;
|
|
1271
|
+
}
|
|
1272
|
+
async function getEntryPayload(key) {
|
|
1273
|
+
const { driver } = resolveDriverContext2();
|
|
1274
|
+
const entry = await driver.get(resolveNormalizedKey2(key));
|
|
1275
|
+
return entry.hit ? entry.payload : void 0;
|
|
1276
|
+
}
|
|
1277
|
+
async function putSerializedValue(key, payload, ttl) {
|
|
1278
|
+
const { driver } = resolveDriverContext2();
|
|
1279
|
+
const normalizedTtl = normalizeCacheTtl(ttl);
|
|
1280
|
+
return driver.put({
|
|
1281
|
+
key: resolveNormalizedKey2(key),
|
|
1282
|
+
payload,
|
|
1283
|
+
expiresAt: normalizedTtl.expiresAt
|
|
1284
|
+
});
|
|
1285
|
+
}
|
|
1286
|
+
async function getCachedValue2(key) {
|
|
1287
|
+
const payload = await getEntryPayload(key);
|
|
1288
|
+
return typeof payload === "string" ? deserializeCacheValue(payload) : null;
|
|
1289
|
+
}
|
|
1290
|
+
async function putFlexibleEnvelope(key, ttl, value) {
|
|
1291
|
+
const now = Date.now();
|
|
1292
|
+
const envelope = {
|
|
1293
|
+
__holo_cache_flexible: true,
|
|
1294
|
+
value,
|
|
1295
|
+
freshUntil: now + ttl.freshSeconds * 1e3,
|
|
1296
|
+
staleUntil: now + ttl.staleSeconds * 1e3
|
|
1297
|
+
};
|
|
1298
|
+
await putSerializedValue(key, serializeCacheValue(envelope), ttl.staleSeconds);
|
|
1299
|
+
return value;
|
|
1300
|
+
}
|
|
1301
|
+
async function refreshFlexibleValue(key, ttl, callback) {
|
|
1302
|
+
const value = await resolveValue(callback);
|
|
1303
|
+
return putFlexibleEnvelope(key, ttl, value);
|
|
1304
|
+
}
|
|
1305
|
+
function createRefreshLock(key, staleSeconds) {
|
|
1306
|
+
return repository.lock(`__flexible__:${resolveCacheKey(key)}`, Math.max(1, staleSeconds));
|
|
1307
|
+
}
|
|
1308
|
+
const repository = Object.freeze({
|
|
1309
|
+
async get(key, fallback) {
|
|
1310
|
+
const payload = await getEntryPayload(key);
|
|
1311
|
+
if (typeof payload === "string") {
|
|
1312
|
+
return deserializeCacheValue(payload);
|
|
1313
|
+
}
|
|
1314
|
+
if (typeof fallback === "undefined") {
|
|
1315
|
+
return null;
|
|
1316
|
+
}
|
|
1317
|
+
return await resolveFallback(fallback);
|
|
1318
|
+
},
|
|
1319
|
+
async put(key, value, ttl) {
|
|
1320
|
+
return putSerializedValue(key, serializeCacheValue(value), ttl);
|
|
1321
|
+
},
|
|
1322
|
+
async add(key, value, ttl) {
|
|
1323
|
+
const { driver } = resolveDriverContext2();
|
|
1324
|
+
const normalizedTtl = normalizeCacheTtl(ttl);
|
|
1325
|
+
return driver.add({
|
|
1326
|
+
key: resolveNormalizedKey2(key),
|
|
1327
|
+
payload: serializeCacheValue(value),
|
|
1328
|
+
expiresAt: normalizedTtl.expiresAt
|
|
1329
|
+
});
|
|
1330
|
+
},
|
|
1331
|
+
async forever(key, value) {
|
|
1332
|
+
const { driver } = resolveDriverContext2();
|
|
1333
|
+
return driver.put({
|
|
1334
|
+
key: resolveNormalizedKey2(key),
|
|
1335
|
+
payload: serializeCacheValue(value)
|
|
1336
|
+
});
|
|
1337
|
+
},
|
|
1338
|
+
async has(key) {
|
|
1339
|
+
return typeof await getEntryPayload(key) === "string";
|
|
1340
|
+
},
|
|
1341
|
+
async missing(key) {
|
|
1342
|
+
return !await this.has(key);
|
|
1343
|
+
},
|
|
1344
|
+
async forget(key) {
|
|
1345
|
+
const runtime = getCacheRuntime();
|
|
1346
|
+
const { configuredDriverName, driver } = resolveDriverContext2();
|
|
1347
|
+
const forgotten = await driver.forget(resolveNormalizedKey2(key));
|
|
1348
|
+
const dependencyIndex = runtime.dependencyIndex;
|
|
1349
|
+
if (!dependencyIndex) {
|
|
1350
|
+
return forgotten;
|
|
1351
|
+
}
|
|
1352
|
+
await dependencyIndex.removeKey(cacheQueryBridgeInternals.createIndexedKey(key, configuredDriverName));
|
|
1353
|
+
return forgotten;
|
|
1354
|
+
},
|
|
1355
|
+
async flush() {
|
|
1356
|
+
const runtime = getCacheRuntime();
|
|
1357
|
+
const { configuredDriverName, driver } = resolveDriverContext2();
|
|
1358
|
+
await driver.flush();
|
|
1359
|
+
const dependencyIndex = runtime.dependencyIndex;
|
|
1360
|
+
if (!dependencyIndex) {
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
const registeredKeys = await dependencyIndex.listRegisteredKeys();
|
|
1364
|
+
for (const indexedKey of registeredKeys) {
|
|
1365
|
+
if (cacheQueryBridgeInternals.parseIndexedKey(indexedKey).driverName === configuredDriverName) {
|
|
1366
|
+
await dependencyIndex.removeKey(indexedKey);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
},
|
|
1370
|
+
async increment(key, amount = 1) {
|
|
1371
|
+
const { driver } = resolveDriverContext2();
|
|
1372
|
+
return driver.increment(resolveNormalizedKey2(key), amount);
|
|
1373
|
+
},
|
|
1374
|
+
async decrement(key, amount = 1) {
|
|
1375
|
+
const { driver } = resolveDriverContext2();
|
|
1376
|
+
return driver.decrement(resolveNormalizedKey2(key), amount);
|
|
1377
|
+
},
|
|
1378
|
+
async remember(key, ttl, callback) {
|
|
1379
|
+
const cached = await getCachedValue2(key);
|
|
1380
|
+
if (cached !== null) {
|
|
1381
|
+
return cached;
|
|
1382
|
+
}
|
|
1383
|
+
const value = await resolveValue(callback);
|
|
1384
|
+
await repository.put(key, value, ttl);
|
|
1385
|
+
return value;
|
|
1386
|
+
},
|
|
1387
|
+
async rememberForever(key, callback) {
|
|
1388
|
+
const cached = await getCachedValue2(key);
|
|
1389
|
+
if (cached !== null) {
|
|
1390
|
+
return cached;
|
|
1391
|
+
}
|
|
1392
|
+
const value = await resolveValue(callback);
|
|
1393
|
+
await repository.forever(key, value);
|
|
1394
|
+
return value;
|
|
1395
|
+
},
|
|
1396
|
+
async flexible(key, ttl, callback) {
|
|
1397
|
+
const normalizedTtl = normalizeFlexibleTtl2(ttl);
|
|
1398
|
+
const now = Date.now();
|
|
1399
|
+
const cached = await getCachedValue2(key);
|
|
1400
|
+
if (isFlexibleEnvelope2(cached)) {
|
|
1401
|
+
if (now <= cached.freshUntil) {
|
|
1402
|
+
return cached.value;
|
|
1403
|
+
}
|
|
1404
|
+
if (now <= cached.staleUntil) {
|
|
1405
|
+
const refreshLock2 = createRefreshLock(key, normalizedTtl.staleSeconds);
|
|
1406
|
+
void refreshLock2.get(async () => {
|
|
1407
|
+
await refreshFlexibleValue(key, normalizedTtl, callback);
|
|
1408
|
+
return true;
|
|
1409
|
+
}).catch(() => void 0);
|
|
1410
|
+
return cached.value;
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
const refreshLock = createRefreshLock(key, normalizedTtl.staleSeconds);
|
|
1414
|
+
const refreshed = await refreshLock.block(
|
|
1415
|
+
Math.min(
|
|
1416
|
+
MAX_REFRESH_BLOCK_SECONDS,
|
|
1417
|
+
Math.max(1, Math.ceil(normalizedTtl.staleSeconds / 300))
|
|
1418
|
+
),
|
|
1419
|
+
async () => refreshFlexibleValue(key, normalizedTtl, callback)
|
|
1420
|
+
);
|
|
1421
|
+
if (refreshed !== false) {
|
|
1422
|
+
return refreshed;
|
|
1423
|
+
}
|
|
1424
|
+
const retried = await getCachedValue2(key);
|
|
1425
|
+
if (isFlexibleEnvelope2(retried)) {
|
|
1426
|
+
if (Date.now() <= retried.staleUntil) {
|
|
1427
|
+
return retried.value;
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
return refreshFlexibleValue(key, normalizedTtl, callback);
|
|
1431
|
+
},
|
|
1432
|
+
lock(name, seconds) {
|
|
1433
|
+
const { driver } = resolveDriverContext2();
|
|
1434
|
+
return driver.lock(resolveNormalizedLockName(name), seconds);
|
|
1435
|
+
}
|
|
1436
|
+
});
|
|
1437
|
+
return repository;
|
|
1438
|
+
}
|
|
1439
|
+
var repositories = /* @__PURE__ */ new Map();
|
|
1440
|
+
function getOrCreateRepository(driverName) {
|
|
1441
|
+
const key = resolveDriverKey(driverName);
|
|
1442
|
+
const existing = repositories.get(key);
|
|
1443
|
+
if (existing) {
|
|
1444
|
+
return existing;
|
|
1445
|
+
}
|
|
1446
|
+
const repository = createCacheRepository(driverName);
|
|
1447
|
+
repositories.set(key, repository);
|
|
1448
|
+
return repository;
|
|
1449
|
+
}
|
|
1450
|
+
function resetCacheFacadeRepositories() {
|
|
1451
|
+
repositories.clear();
|
|
1452
|
+
}
|
|
1453
|
+
var defaultRepository = getOrCreateRepository();
|
|
1454
|
+
var cacheFacade = Object.freeze({
|
|
1455
|
+
...defaultRepository,
|
|
1456
|
+
driver(name) {
|
|
1457
|
+
return getOrCreateRepository(name);
|
|
1458
|
+
}
|
|
1459
|
+
});
|
|
1460
|
+
var cacheFacadeInternals = {
|
|
1461
|
+
createRefreshLockName(key) {
|
|
1462
|
+
return `__flexible__:${resolveCacheKey(key)}`;
|
|
1463
|
+
},
|
|
1464
|
+
getOrCreateRepository,
|
|
1465
|
+
isFlexibleEnvelope: isFlexibleEnvelope2,
|
|
1466
|
+
normalizeFlexibleTtl: normalizeFlexibleTtl2,
|
|
1467
|
+
resolveDriverKey,
|
|
1468
|
+
resolveFallback,
|
|
1469
|
+
resolveValue
|
|
1470
|
+
};
|
|
1471
|
+
|
|
1472
|
+
// src/index.ts
|
|
1473
|
+
function configureCacheRuntime2(...parameters) {
|
|
1474
|
+
configureCacheRuntime(...parameters);
|
|
1475
|
+
}
|
|
1476
|
+
function resetCacheRuntime2() {
|
|
1477
|
+
resetCacheRuntime();
|
|
1478
|
+
resetCacheFacadeRepositories();
|
|
1479
|
+
}
|
|
1480
|
+
var cache = Object.freeze({
|
|
1481
|
+
defineCacheKey,
|
|
1482
|
+
normalizeCacheTtl,
|
|
1483
|
+
serializeCacheValue,
|
|
1484
|
+
deserializeCacheValue,
|
|
1485
|
+
configureCacheRuntime: configureCacheRuntime2,
|
|
1486
|
+
getCacheRuntime,
|
|
1487
|
+
getCacheRuntimeBindings,
|
|
1488
|
+
resetCacheRuntime: resetCacheRuntime2,
|
|
1489
|
+
...cacheFacade
|
|
1490
|
+
});
|
|
1491
|
+
var src_default = cache;
|
|
1492
|
+
export {
|
|
1493
|
+
CacheConfigError,
|
|
1494
|
+
CacheDriverResolutionError,
|
|
1495
|
+
CacheError,
|
|
1496
|
+
CacheInvalidNumericMutationError,
|
|
1497
|
+
CacheInvalidTtlError,
|
|
1498
|
+
CacheLockAcquisitionError,
|
|
1499
|
+
CacheOptionalPackageError,
|
|
1500
|
+
CacheQueryIntegrationError,
|
|
1501
|
+
CacheRuntimeNotConfiguredError,
|
|
1502
|
+
CacheSerializationError,
|
|
1503
|
+
cacheContractsInternals,
|
|
1504
|
+
cacheDbInternals,
|
|
1505
|
+
cacheFacade,
|
|
1506
|
+
cacheFacadeInternals,
|
|
1507
|
+
cacheQueryBridgeInternals,
|
|
1508
|
+
cacheRedisInternals,
|
|
1509
|
+
cacheRuntimeInternals,
|
|
1510
|
+
configureCacheRuntime2 as configureCacheRuntime,
|
|
1511
|
+
src_default as default,
|
|
1512
|
+
defineCacheConfig,
|
|
1513
|
+
defineCacheKey,
|
|
1514
|
+
deserializeCacheValue,
|
|
1515
|
+
fileDriverInternals,
|
|
1516
|
+
getCacheRuntime,
|
|
1517
|
+
getCacheRuntimeBindings,
|
|
1518
|
+
isCacheKey,
|
|
1519
|
+
normalizeCacheTtl,
|
|
1520
|
+
resetCacheRuntime2 as resetCacheRuntime,
|
|
1521
|
+
resolveCacheKey,
|
|
1522
|
+
serializeCacheValue
|
|
1523
|
+
};
|