@arkstack/cache 0.13.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Toneflix Technologies Limited
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # @arkstack/cache
2
+
3
+ [![@arkstack/cache](https://img.shields.io/npm/dt/@arkstack/cache?style=flat-square&label=@arkstack/cache&link=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2F@arkstack/cache)](https://www.npmjs.com/package/@arkstack/cache)
4
+
5
+ Cache module for Arkstack, providing a unified, driver based caching layer for the framework.
6
+
7
+ ## Stores
8
+
9
+ `@arkstack/cache` ships with four stores out of the box:
10
+
11
+ | Driver | Backing store | Notes |
12
+ | ---------- | ---------------------------- | ---------------------------------------- |
13
+ | `memory` | in-process `Map` | default; great for dev and tests |
14
+ | `file` | JSON files on disk | no external service required |
15
+ | `redis` | Redis (via `ioredis`) | distributed; atomic counters |
16
+ | `database` | a table via `@arkstack/database` | reuses your database connection |
17
+
18
+ ## Configuration
19
+
20
+ ```ts
21
+ // src/config/cache.ts
22
+ import { Arkstack } from '@arkstack/contract'
23
+ import type { CacheConfig } from '@arkstack/cache'
24
+ import path from 'node:path'
25
+
26
+ export default (): CacheConfig => ({
27
+ default: env('CACHE_STORE', 'memory'),
28
+ prefix: env('CACHE_PREFIX', 'arkstack_cache_'),
29
+ stores: {
30
+ memory: { driver: 'memory' },
31
+ file: { driver: 'file', path: path.join(Arkstack.rootDir(), './storage/framework/cache') },
32
+ redis: { driver: 'redis', host: env('REDIS_HOST', '127.0.0.1'), port: env('REDIS_PORT', 6379) },
33
+ database: { driver: 'database', table: 'cache' },
34
+ },
35
+ })
36
+ ```
37
+
38
+ ## Usage
39
+
40
+ ```ts
41
+ import { Cache } from '@arkstack/cache'
42
+
43
+ // default store
44
+ await Cache.put('user:1', user, 60) // ttl in seconds
45
+ await Cache.get('user:1')
46
+ await Cache.forget('user:1')
47
+
48
+ // remember: read-through caching
49
+ const stats = await Cache.remember('stats', 300, () => computeStats())
50
+
51
+ // counters
52
+ await Cache.increment('hits')
53
+ await Cache.decrement('hits', 2)
54
+
55
+ // a specific store
56
+ await Cache.store('redis').forever('flag', true)
57
+ ```
58
+
59
+ The ttl accepts a number of seconds, a `Date`, or `null` (forever).
60
+
61
+ ## Custom drivers
62
+
63
+ ```ts
64
+ import { Cache, Store } from '@arkstack/cache'
65
+
66
+ class TagStore extends Store { /* ... implement the Store contract ... */ }
67
+
68
+ Cache.extend('tags', (config) => new TagStore(config))
69
+ ```
70
+
71
+ Then reference `{ driver: 'tags' }` in a store config.
@@ -0,0 +1,708 @@
1
+ import { mkdir, readFile, readdir, rm, unlink, writeFile } from "node:fs/promises";
2
+ import { createHash } from "node:crypto";
3
+ import { existsSync } from "node:fs";
4
+ import path from "node:path";
5
+ import { config } from "@arkstack/common";
6
+ //#region src/Contracts/Store.ts
7
+ /**
8
+ * The low level contract every cache store driver must satisfy.
9
+ *
10
+ * Stores are intentionally thin: they only deal in raw key/value persistence
11
+ * and expiration. The higher level convenience API (`remember`, `add`, `pull`,
12
+ * default value resolution, etc.) lives in {@link Repository}, which wraps a
13
+ * store. This keeps drivers simple and behaviour consistent across backends.
14
+ */
15
+ var Store = class {};
16
+ //#endregion
17
+ //#region src/drivers/DatabaseStore.ts
18
+ /**
19
+ * A cache store that persists entries in a relational table via
20
+ * `@arkstack/database`.
21
+ *
22
+ * The table is expected to have `key` (string, primary), `value` (text), and
23
+ * `expiration` (nullable integer epoch seconds) columns. `@arkstack/database`
24
+ * is an optional peer dependency and is imported lazily.
25
+ */
26
+ var DatabaseStore = class extends Store {
27
+ databaseConfig;
28
+ prefix;
29
+ db;
30
+ constructor(databaseConfig, prefix = "") {
31
+ super();
32
+ this.databaseConfig = databaseConfig;
33
+ this.prefix = prefix;
34
+ }
35
+ getPrefix() {
36
+ return this.prefix;
37
+ }
38
+ prefixed(key) {
39
+ return this.prefix + key;
40
+ }
41
+ get table() {
42
+ return this.databaseConfig.table;
43
+ }
44
+ async database() {
45
+ if (this.db) return this.db;
46
+ try {
47
+ const { DB } = await import("@arkstack/database");
48
+ this.db = DB;
49
+ } catch {
50
+ throw new Error("The database cache store requires the \"@arkstack/database\" package.");
51
+ }
52
+ return this.db;
53
+ }
54
+ async get(key) {
55
+ const db = await this.database();
56
+ const row = await db.table(this.table).where({ key: this.prefixed(key) }).first();
57
+ if (!row) return null;
58
+ if (row.expiration !== null && row.expiration <= Math.floor(Date.now() / 1e3)) {
59
+ await db.table(this.table).where({ key: this.prefixed(key) }).delete();
60
+ return null;
61
+ }
62
+ try {
63
+ return JSON.parse(row.value);
64
+ } catch {
65
+ return row.value;
66
+ }
67
+ }
68
+ async put(key, value, seconds = null) {
69
+ const db = await this.database();
70
+ const expiration = seconds === null ? null : Math.floor(Date.now() / 1e3) + seconds;
71
+ await db.table(this.table).updateOrInsert({ key: this.prefixed(key) }, {
72
+ value: JSON.stringify(value),
73
+ expiration
74
+ });
75
+ return true;
76
+ }
77
+ async forever(key, value) {
78
+ return this.put(key, value, null);
79
+ }
80
+ async increment(key, value = 1) {
81
+ const current = await this.get(key);
82
+ const base = current === null ? 0 : current;
83
+ if (typeof base !== "number" || Number.isNaN(base)) return false;
84
+ const next = base + value;
85
+ const db = await this.database();
86
+ const existing = await db.table(this.table).where({ key: this.prefixed(key) }).first();
87
+ await db.table(this.table).updateOrInsert({ key: this.prefixed(key) }, {
88
+ value: JSON.stringify(next),
89
+ expiration: existing?.expiration ?? null
90
+ });
91
+ return next;
92
+ }
93
+ async decrement(key, value = 1) {
94
+ return this.increment(key, -value);
95
+ }
96
+ async forget(key) {
97
+ await (await this.database()).table(this.table).where({ key: this.prefixed(key) }).delete();
98
+ return true;
99
+ }
100
+ async flush() {
101
+ await (await this.database()).table(this.table).delete();
102
+ return true;
103
+ }
104
+ };
105
+ //#endregion
106
+ //#region src/drivers/FileStore.ts
107
+ /**
108
+ * A cache store that persists each entry as a JSON file on disk.
109
+ *
110
+ * Keys are hashed to produce safe, fixed length file names. Expired files are
111
+ * removed lazily on read and ignored otherwise.
112
+ */
113
+ var FileStore = class extends Store {
114
+ directory;
115
+ prefix;
116
+ constructor(directory, prefix = "") {
117
+ super();
118
+ this.directory = directory;
119
+ this.prefix = prefix;
120
+ }
121
+ getPrefix() {
122
+ return this.prefix;
123
+ }
124
+ pathFor(key) {
125
+ const hash = createHash("sha1").update(this.prefix + key).digest("hex");
126
+ return path.join(this.directory, `${hash}.json`);
127
+ }
128
+ async ensureDirectory() {
129
+ if (!existsSync(this.directory)) await mkdir(this.directory, { recursive: true });
130
+ }
131
+ async get(key) {
132
+ const file = this.pathFor(key);
133
+ if (!existsSync(file)) return null;
134
+ try {
135
+ const payload = JSON.parse(await readFile(file, "utf8"));
136
+ if (payload.expiresAt !== null && payload.expiresAt <= Date.now()) {
137
+ await unlink(file).catch(() => void 0);
138
+ return null;
139
+ }
140
+ return payload.value;
141
+ } catch {
142
+ return null;
143
+ }
144
+ }
145
+ async put(key, value, seconds = null) {
146
+ await this.ensureDirectory();
147
+ const payload = {
148
+ value,
149
+ expiresAt: seconds === null ? null : Date.now() + seconds * 1e3
150
+ };
151
+ await writeFile(this.pathFor(key), JSON.stringify(payload), "utf8");
152
+ return true;
153
+ }
154
+ async forever(key, value) {
155
+ return this.put(key, value, null);
156
+ }
157
+ async increment(key, value = 1) {
158
+ const current = await this.get(key);
159
+ const base = current === null ? 0 : current;
160
+ if (typeof base !== "number" || Number.isNaN(base)) return false;
161
+ const next = base + value;
162
+ const file = this.pathFor(key);
163
+ let expiresAt = null;
164
+ if (existsSync(file)) try {
165
+ expiresAt = JSON.parse(await readFile(file, "utf8")).expiresAt;
166
+ } catch {
167
+ expiresAt = null;
168
+ }
169
+ await this.ensureDirectory();
170
+ await writeFile(file, JSON.stringify({
171
+ value: next,
172
+ expiresAt
173
+ }), "utf8");
174
+ return next;
175
+ }
176
+ async decrement(key, value = 1) {
177
+ return this.increment(key, -value);
178
+ }
179
+ async forget(key) {
180
+ const file = this.pathFor(key);
181
+ if (!existsSync(file)) return false;
182
+ await unlink(file).catch(() => void 0);
183
+ return true;
184
+ }
185
+ async flush() {
186
+ if (!existsSync(this.directory)) return true;
187
+ const files = await readdir(this.directory);
188
+ await Promise.all(files.filter((name) => name.endsWith(".json")).map((name) => rm(path.join(this.directory, name), { force: true })));
189
+ return true;
190
+ }
191
+ };
192
+ //#endregion
193
+ //#region src/drivers/MemoryStore.ts
194
+ /**
195
+ * An in-process cache store backed by a `Map`.
196
+ *
197
+ * The backing map is shared across every `MemoryStore` instance that uses the
198
+ * same prefix, so resolving the store twice within a process sees the same data
199
+ * (matching how the other backends behave). Great for development and tests; it
200
+ * is not shared across processes.
201
+ */
202
+ var MemoryStore = class MemoryStore extends Store {
203
+ prefix;
204
+ static stores = /* @__PURE__ */ new Map();
205
+ entries;
206
+ constructor(prefix = "") {
207
+ super();
208
+ this.prefix = prefix;
209
+ if (!MemoryStore.stores.has(prefix)) MemoryStore.stores.set(prefix, /* @__PURE__ */ new Map());
210
+ this.entries = MemoryStore.stores.get(prefix);
211
+ }
212
+ getPrefix() {
213
+ return this.prefix;
214
+ }
215
+ async get(key) {
216
+ const entry = this.entries.get(key);
217
+ if (!entry) return null;
218
+ if (entry.expiresAt !== null && entry.expiresAt <= Date.now()) {
219
+ this.entries.delete(key);
220
+ return null;
221
+ }
222
+ return entry.value;
223
+ }
224
+ async put(key, value, seconds = null) {
225
+ this.entries.set(key, {
226
+ value,
227
+ expiresAt: seconds === null ? null : Date.now() + seconds * 1e3
228
+ });
229
+ return true;
230
+ }
231
+ async forever(key, value) {
232
+ return this.put(key, value, null);
233
+ }
234
+ async increment(key, value = 1) {
235
+ const current = await this.get(key);
236
+ const base = current === null ? 0 : current;
237
+ if (typeof base !== "number" || Number.isNaN(base)) return false;
238
+ const next = base + value;
239
+ const entry = this.entries.get(key);
240
+ this.entries.set(key, {
241
+ value: next,
242
+ expiresAt: entry?.expiresAt ?? null
243
+ });
244
+ return next;
245
+ }
246
+ async decrement(key, value = 1) {
247
+ return this.increment(key, -value);
248
+ }
249
+ async forget(key) {
250
+ return this.entries.delete(key);
251
+ }
252
+ async flush() {
253
+ this.entries.clear();
254
+ return true;
255
+ }
256
+ };
257
+ //#endregion
258
+ //#region src/drivers/RedisStore.ts
259
+ /**
260
+ * A Redis backed cache store using an ioredis compatible client.
261
+ *
262
+ * Values are JSON serialized. Numeric helpers use native `INCRBY`/`DECRBY` so
263
+ * counters stay atomic across processes. `ioredis` is an optional peer
264
+ * dependency; it is imported lazily so applications that never use the redis
265
+ * store don't need it installed.
266
+ */
267
+ var RedisStore = class extends Store {
268
+ redisConfig;
269
+ prefix;
270
+ client;
271
+ constructor(redisConfig, prefix = "") {
272
+ super();
273
+ this.redisConfig = redisConfig;
274
+ this.prefix = prefix;
275
+ }
276
+ getPrefix() {
277
+ return [this.prefix, this.redisConfig.prefix].filter(Boolean).join("");
278
+ }
279
+ prefixed(key) {
280
+ return this.getPrefix() + key;
281
+ }
282
+ async connection() {
283
+ if (this.client) return this.client;
284
+ let Redis;
285
+ try {
286
+ Redis = (await import("ioredis")).default;
287
+ } catch {
288
+ throw new Error("The redis cache store requires the \"ioredis\" package. Install it with your package manager.");
289
+ }
290
+ this.client = this.redisConfig.url ? new Redis(this.redisConfig.url) : new Redis({
291
+ host: this.redisConfig.host ?? "127.0.0.1",
292
+ port: this.redisConfig.port ?? 6379,
293
+ password: this.redisConfig.password,
294
+ db: this.redisConfig.db ?? 0
295
+ });
296
+ return this.client;
297
+ }
298
+ /**
299
+ * Disconnect the underlying client. Useful for tests and graceful shutdown.
300
+ */
301
+ async disconnect() {
302
+ await this.client?.quit();
303
+ this.client = void 0;
304
+ }
305
+ async get(key) {
306
+ const raw = await (await this.connection()).get(this.prefixed(key));
307
+ if (raw === null) return null;
308
+ try {
309
+ return JSON.parse(raw);
310
+ } catch {
311
+ return raw;
312
+ }
313
+ }
314
+ async put(key, value, seconds = null) {
315
+ const client = await this.connection();
316
+ const serialized = JSON.stringify(value);
317
+ if (seconds === null) await client.set(this.prefixed(key), serialized);
318
+ else await client.set(this.prefixed(key), serialized, "EX", seconds);
319
+ return true;
320
+ }
321
+ async forever(key, value) {
322
+ return this.put(key, value, null);
323
+ }
324
+ async increment(key, value = 1) {
325
+ return (await this.connection()).incrby(this.prefixed(key), value);
326
+ }
327
+ async decrement(key, value = 1) {
328
+ return (await this.connection()).decrby(this.prefixed(key), value);
329
+ }
330
+ async forget(key) {
331
+ return await (await this.connection()).del(this.prefixed(key)) > 0;
332
+ }
333
+ async flush() {
334
+ const client = await this.connection();
335
+ const pattern = `${this.getPrefix()}*`;
336
+ let cursor = "0";
337
+ do {
338
+ const [next, keys] = await client.scan(cursor, "MATCH", pattern, "COUNT", 100);
339
+ cursor = next;
340
+ if (keys.length > 0) await client.del(...keys);
341
+ } while (cursor !== "0");
342
+ return true;
343
+ }
344
+ };
345
+ //#endregion
346
+ //#region src/Repository.ts
347
+ /**
348
+ * Resolve a possibly callable default/value into a concrete value.
349
+ */
350
+ const resolve = async (value) => {
351
+ return typeof value === "function" ? await value() : value;
352
+ };
353
+ /**
354
+ * Normalize a {@link CacheTtl} into a whole number of seconds, or `null` for a
355
+ * forever entry. A non positive duration is treated as already expired and
356
+ * returns `0`, signalling the caller to skip writing.
357
+ */
358
+ const ttlToSeconds = (ttl) => {
359
+ if (ttl === null || ttl === void 0) return null;
360
+ if (ttl instanceof Date) return Math.max(0, Math.round((ttl.getTime() - Date.now()) / 1e3));
361
+ return Math.max(0, Math.round(ttl));
362
+ };
363
+ /**
364
+ * A high level wrapper around a {@link Store} that provides the developer facing
365
+ * cache API. A repository is what `Cache.store()` returns.
366
+ */
367
+ var Repository = class {
368
+ store;
369
+ constructor(store) {
370
+ this.store = store;
371
+ }
372
+ /**
373
+ * Get the underlying store driver.
374
+ */
375
+ getStore() {
376
+ return this.store;
377
+ }
378
+ /**
379
+ * Determine whether an item exists in the cache and has not expired.
380
+ *
381
+ * @param key
382
+ * @returns
383
+ */
384
+ async has(key) {
385
+ return await this.store.get(key) !== null;
386
+ }
387
+ /**
388
+ * Determine whether an item is missing from the cache.
389
+ *
390
+ * @param key
391
+ * @returns
392
+ */
393
+ async missing(key) {
394
+ return !await this.has(key);
395
+ }
396
+ /**
397
+ * Retrieve an item from the cache, falling back to `defaultValue` (which may
398
+ * be a callback) when the item is absent.
399
+ *
400
+ * @param key
401
+ * @returns
402
+ */
403
+ async get(key, defaultValue = null) {
404
+ const value = await this.store.get(key);
405
+ if (value === null || value === void 0) return resolve(defaultValue);
406
+ return value;
407
+ }
408
+ /**
409
+ * Retrieve an item and delete it from the cache in a single call.
410
+ *
411
+ * @param key
412
+ * @param defaultValue
413
+ * @returns
414
+ */
415
+ async pull(key, defaultValue = null) {
416
+ const value = await this.get(key, defaultValue);
417
+ await this.forget(key);
418
+ return value;
419
+ }
420
+ /**
421
+ * Store an item in the cache. A `null`/omitted ttl stores the item forever;
422
+ * a non positive ttl is a no-op that also forgets any existing entry.
423
+ *
424
+ * @param key
425
+ * @param defaultValue
426
+ * @returns
427
+ */
428
+ async put(key, value, ttl = null) {
429
+ const seconds = ttlToSeconds(ttl);
430
+ if (seconds !== null && seconds <= 0) {
431
+ await this.forget(key);
432
+ return false;
433
+ }
434
+ return this.store.put(key, value, seconds);
435
+ }
436
+ /**
437
+ * Alias for {@link put}.
438
+ *
439
+ * @param key
440
+ * @param defaultValue
441
+ * @returns
442
+ */
443
+ async set(key, value, ttl = null) {
444
+ return this.put(key, value, ttl);
445
+ }
446
+ /**
447
+ * Store an item only if it is not already present. Returns `false` when the
448
+ * key already exists.
449
+ *
450
+ * @param key
451
+ * @param defaultValue
452
+ * @returns
453
+ */
454
+ async add(key, value, ttl = null) {
455
+ if (await this.has(key)) return false;
456
+ return this.put(key, value, ttl);
457
+ }
458
+ /**
459
+ * Store an item that never expires.
460
+ *
461
+ * @param key
462
+ * @param defaultValue
463
+ * @returns
464
+ */
465
+ async forever(key, value) {
466
+ return this.store.forever(key, value);
467
+ }
468
+ /**
469
+ * Increment a stored numeric value.
470
+ *
471
+ * @param key
472
+ * @param defaultValue
473
+ * @returns
474
+ */
475
+ async increment(key, value = 1) {
476
+ return this.store.increment(key, value);
477
+ }
478
+ /**
479
+ * Decrement a stored numeric value.
480
+ *
481
+ * @param key
482
+ * @param defaultValue
483
+ * @returns
484
+ */
485
+ async decrement(key, value = 1) {
486
+ return this.store.decrement(key, value);
487
+ }
488
+ /**
489
+ * Get an item from the cache, or execute the callback, store its result for
490
+ * the given ttl, and return it.
491
+ *
492
+ * @param key
493
+ * @param defaultValue
494
+ * @returns
495
+ */
496
+ async remember(key, ttl, callback) {
497
+ const existing = await this.store.get(key);
498
+ if (existing !== null && existing !== void 0) return existing;
499
+ const value = await callback();
500
+ await this.put(key, value, ttl);
501
+ return value;
502
+ }
503
+ /**
504
+ * Get an item from the cache, or execute the callback and store its result
505
+ * forever.
506
+ *
507
+ * @param key
508
+ * @param defaultValue
509
+ * @returns
510
+ */
511
+ async rememberForever(key, callback) {
512
+ const existing = await this.store.get(key);
513
+ if (existing !== null && existing !== void 0) return existing;
514
+ const value = await callback();
515
+ await this.forever(key, value);
516
+ return value;
517
+ }
518
+ /**
519
+ * Alias for {@link rememberForever}.
520
+ *
521
+ * @param key
522
+ * @param callback
523
+ * @returns
524
+ */
525
+ async sear(key, callback) {
526
+ return this.rememberForever(key, callback);
527
+ }
528
+ /**
529
+ * Remove an item from the cache.
530
+ *
531
+ * @param key
532
+ * @param callback
533
+ * @returns
534
+ */
535
+ async forget(key) {
536
+ return this.store.forget(key);
537
+ }
538
+ /**
539
+ * Alias for {@link forget}.
540
+ *
541
+ * @param key
542
+ * @param callback
543
+ * @returns
544
+ */
545
+ async delete(key) {
546
+ return this.forget(key);
547
+ }
548
+ /**
549
+ * Remove every item from the cache store.
550
+ *
551
+ * @param key
552
+ * @param callback
553
+ * @returns
554
+ */
555
+ async flush() {
556
+ return this.store.flush();
557
+ }
558
+ /**
559
+ * Alias for {@link flush}.
560
+ *
561
+ * @param key
562
+ * @param callback
563
+ * @returns
564
+ */
565
+ async clear() {
566
+ return this.flush();
567
+ }
568
+ };
569
+ //#endregion
570
+ //#region src/config.ts
571
+ /**
572
+ * Read a value from the `cache` configuration namespace with a fallback.
573
+ *
574
+ * Mirrors the framework `config()` helper but is scoped to the cache config and
575
+ * never throws when the config file is missing, returning the default instead.
576
+ *
577
+ * @param key Dot path within the cache config.
578
+ * @param defaultValue Value returned when the key is not set.
579
+ */
580
+ const configure = (key, defaultValue) => {
581
+ try {
582
+ return config(`cache.${key}`, defaultValue);
583
+ } catch {
584
+ return defaultValue;
585
+ }
586
+ };
587
+ //#endregion
588
+ //#region src/CacheManager.ts
589
+ /**
590
+ * The cache manager and primary entry point of `@arkstack/cache`.
591
+ *
592
+ * Resolves named cache stores from configuration, memoizes the resulting
593
+ * repositories, and exposes static convenience methods that proxy the default
594
+ * store, e.g.:
595
+ *
596
+ * ```ts
597
+ * await Cache.put('user:1', user, 60)
598
+ * await Cache.store('redis').remember('stats', 300, computeStats)
599
+ * ```
600
+ */
601
+ var Cache = class {
602
+ static repositories = {};
603
+ static customDrivers = {};
604
+ /**
605
+ * Resolve a cache repository for the given store name (or the default store
606
+ * when omitted). Repositories are memoized per name.
607
+ */
608
+ static store(name) {
609
+ const store = name ?? configure("default", "memory");
610
+ if (!this.repositories[store]) this.repositories[store] = this.resolve(store);
611
+ return this.repositories[store];
612
+ }
613
+ /**
614
+ * Alias for {@link store}.
615
+ */
616
+ static driver(name) {
617
+ return this.store(name);
618
+ }
619
+ /**
620
+ * Register a custom store driver factory, used when a store's `driver` does
621
+ * not match a built in one.
622
+ */
623
+ static extend(driver, factory) {
624
+ this.customDrivers[driver] = factory;
625
+ return this;
626
+ }
627
+ /**
628
+ * Clear the memoized repositories. Mainly useful between tests or after the
629
+ * configuration changes at runtime.
630
+ */
631
+ static clearResolved() {
632
+ this.repositories = {};
633
+ }
634
+ /**
635
+ * Build a repository for a configured store from scratch.
636
+ */
637
+ static resolve(name) {
638
+ const config = configure(`stores.${name}`, void 0);
639
+ if (!config) throw new Error(`Cache store "${name}" is not configured.`);
640
+ return new Repository(this.createStore(config));
641
+ }
642
+ /**
643
+ * Instantiate the concrete {@link Store} for a store config.
644
+ */
645
+ static createStore(config) {
646
+ const prefix = configure("prefix", "");
647
+ switch (config.driver) {
648
+ case "memory":
649
+ case "array": return new MemoryStore(prefix);
650
+ case "file": return new FileStore(config.path, prefix);
651
+ case "redis": return new RedisStore(config, prefix);
652
+ case "database": return new DatabaseStore(config, prefix);
653
+ default:
654
+ if (this.customDrivers[config.driver]) return this.customDrivers[config.driver](config);
655
+ throw new Error(`Unsupported cache driver: ${config.driver}`);
656
+ }
657
+ }
658
+ static has(key) {
659
+ return this.store().has(key);
660
+ }
661
+ static missing(key) {
662
+ return this.store().missing(key);
663
+ }
664
+ static get(key, defaultValue) {
665
+ return this.store().get(key, defaultValue);
666
+ }
667
+ static pull(key, defaultValue) {
668
+ return this.store().pull(key, defaultValue);
669
+ }
670
+ static put(key, value, ttl) {
671
+ return this.store().put(key, value, ttl);
672
+ }
673
+ static set(key, value, ttl) {
674
+ return this.store().set(key, value, ttl);
675
+ }
676
+ static add(key, value, ttl) {
677
+ return this.store().add(key, value, ttl);
678
+ }
679
+ static forever(key, value) {
680
+ return this.store().forever(key, value);
681
+ }
682
+ static increment(key, value) {
683
+ return this.store().increment(key, value);
684
+ }
685
+ static decrement(key, value) {
686
+ return this.store().decrement(key, value);
687
+ }
688
+ static remember(key, ttl, callback) {
689
+ return this.store().remember(key, ttl, callback);
690
+ }
691
+ static rememberForever(key, callback) {
692
+ return this.store().rememberForever(key, callback);
693
+ }
694
+ static forget(key) {
695
+ return this.store().forget(key);
696
+ }
697
+ static delete(key) {
698
+ return this.store().delete(key);
699
+ }
700
+ static flush() {
701
+ return this.store().flush();
702
+ }
703
+ static clear() {
704
+ return this.store().clear();
705
+ }
706
+ };
707
+ //#endregion
708
+ export { RedisStore as a, DatabaseStore as c, ttlToSeconds as i, Store as l, configure as n, MemoryStore as o, Repository as r, FileStore as s, Cache as t };
@@ -0,0 +1,13 @@
1
+ import { Command } from "@h3ravel/musket";
2
+
3
+ //#region src/commands/CacheClearCommand.d.ts
4
+ /**
5
+ * Flush all entries from a cache store.
6
+ */
7
+ declare class CacheClearCommand extends Command {
8
+ protected signature: string;
9
+ protected description: string;
10
+ handle(): Promise<void>;
11
+ }
12
+ //#endregion
13
+ export { CacheClearCommand };
@@ -0,0 +1,19 @@
1
+ import { t as Cache } from "../CacheManager-DfAiI5Ft.js";
2
+ import { Command } from "@h3ravel/musket";
3
+ //#region src/commands/CacheClearCommand.ts
4
+ /**
5
+ * Flush all entries from a cache store.
6
+ */
7
+ var CacheClearCommand = class extends Command {
8
+ signature = `cache:clear
9
+ {--store= : The cache store to flush. Defaults to the configured default store.}
10
+ `;
11
+ description = "Flush the application cache.";
12
+ async handle() {
13
+ const store = this.option("store");
14
+ await Cache.store(store).flush();
15
+ this.info(`Cache store [${store ?? "default"}] cleared successfully.`);
16
+ }
17
+ };
18
+ //#endregion
19
+ export { CacheClearCommand };
@@ -0,0 +1,525 @@
1
+ import { DotPath, DotPathValue } from "@arkstack/common";
2
+
3
+ //#region src/Contracts/Store.d.ts
4
+ /**
5
+ * The low level contract every cache store driver must satisfy.
6
+ *
7
+ * Stores are intentionally thin: they only deal in raw key/value persistence
8
+ * and expiration. The higher level convenience API (`remember`, `add`, `pull`,
9
+ * default value resolution, etc.) lives in {@link Repository}, which wraps a
10
+ * store. This keeps drivers simple and behaviour consistent across backends.
11
+ */
12
+ declare abstract class Store {
13
+ /**
14
+ * Retrieve the value for the given key, or `null` when the key is missing
15
+ * or has expired.
16
+ *
17
+ * @param key
18
+ */
19
+ abstract get<T = unknown>(key: string): Promise<T | null>;
20
+ /**
21
+ * Store a value for the given key.
22
+ *
23
+ * @param key The cache key.
24
+ * @param value The value to store (will be serialized by the driver).
25
+ * @param seconds Lifetime in seconds, or `null` to store forever.
26
+ * @returns Whether the value was stored.
27
+ */
28
+ abstract put(key: string, value: unknown, seconds?: number | null): Promise<boolean>;
29
+ /**
30
+ * Store a value that never expires.
31
+ *
32
+ * @param key
33
+ * @param value
34
+ */
35
+ abstract forever(key: string, value: unknown): Promise<boolean>;
36
+ /**
37
+ * Increment a numeric value, creating it (starting at 0) when absent.
38
+ *
39
+ * @returns The new value, or `false` when the existing value is not numeric.
40
+ *
41
+ * @param key
42
+ * @param value
43
+ */
44
+ abstract increment(key: string, value?: number): Promise<number | false>;
45
+ /**
46
+ * Decrement a numeric value, creating it (starting at 0) when absent.
47
+ *
48
+ * @returns The new value, or `false` when the existing value is not numeric.
49
+ *
50
+ * @param key
51
+ * @param value
52
+ */
53
+ abstract decrement(key: string, value?: number): Promise<number | false>;
54
+ /**
55
+ * Remove a single key from the store.
56
+ *
57
+ * @param key
58
+ * @param value
59
+ */
60
+ abstract forget(key: string): Promise<boolean>;
61
+ /**
62
+ * Remove every entry owned by this store.
63
+ */
64
+ abstract flush(): Promise<boolean>;
65
+ /**
66
+ * The key prefix applied to every entry by this store.
67
+ */
68
+ abstract getPrefix(): string;
69
+ }
70
+ //#endregion
71
+ //#region src/types.d.ts
72
+ /**
73
+ * A value, or a function (sync or async) that resolves to a value.
74
+ *
75
+ * Used for cache defaults and the `remember` callbacks.
76
+ */
77
+ type Resolvable<T> = T | (() => T | Promise<T>);
78
+ /**
79
+ * Time to live for a cache entry.
80
+ *
81
+ * - `number` number of seconds the entry should live.
82
+ * - `Date` an absolute expiry time.
83
+ * - `null` store forever (no expiration).
84
+ */
85
+ type CacheTtl = number | Date | null;
86
+ /**
87
+ * Built in cache store driver names.
88
+ */
89
+ type CacheDriverName = 'memory' | 'array' | 'file' | 'redis' | 'database';
90
+ interface FilePayload {
91
+ value: unknown;
92
+ /** Absolute expiry in epoch milliseconds, or `null` for forever. */
93
+ expiresAt: number | null;
94
+ }
95
+ interface MemoryStoreConfig {
96
+ driver: 'memory' | 'array';
97
+ }
98
+ interface FileStoreConfig {
99
+ driver: 'file';
100
+ /** Absolute path to the directory cache files are written to. */
101
+ path: string;
102
+ }
103
+ interface RedisStoreConfig {
104
+ driver: 'redis';
105
+ /** A pre built ioredis connection string, e.g. `redis://localhost:6379`. */
106
+ url?: string;
107
+ host?: string;
108
+ port?: number;
109
+ password?: string;
110
+ db?: number;
111
+ /** Optional per store key prefix applied on top of the global prefix. */
112
+ prefix?: string;
113
+ }
114
+ interface DatabaseStoreConfig {
115
+ driver: 'database';
116
+ /** The table the cache entries are stored in. */
117
+ table: string;
118
+ /** Optional named database connection. */
119
+ connection?: string;
120
+ }
121
+ /**
122
+ * Apps may augment this registry to register custom store driver configs.
123
+ */
124
+ interface CustomCacheStoreRegistry {}
125
+ type CacheStoreConfig = MemoryStoreConfig | FileStoreConfig | RedisStoreConfig | DatabaseStoreConfig | ({
126
+ driver: string;
127
+ } & Record<string, unknown>);
128
+ interface CacheConfig {
129
+ /**
130
+ * The default cache store used when no store is explicitly requested.
131
+ */
132
+ default: string;
133
+ /**
134
+ * A string prepended to every cache key to avoid collisions between
135
+ * applications sharing the same backing store.
136
+ */
137
+ prefix: string;
138
+ /**
139
+ * The configured cache stores. Each entry is keyed by the name used with
140
+ * `Cache.store('<name>')`.
141
+ */
142
+ stores: Record<string, CacheStoreConfig> & CustomCacheStoreRegistry;
143
+ }
144
+ /**
145
+ * Signature for a custom store factory registered via `Cache.extend`.
146
+ */
147
+ type CacheStoreFactory = (config: CacheStoreConfig) => Store;
148
+ interface MemoryEntry {
149
+ value: unknown;
150
+ /** Absolute expiry in epoch milliseconds, or `null` for forever. */
151
+ expiresAt: number | null;
152
+ }
153
+ interface CacheRow {
154
+ key: string;
155
+ value: string;
156
+ /** Absolute expiry in epoch seconds, or `null` for forever. */
157
+ expiration: number | null;
158
+ }
159
+ /**
160
+ * Structural type for the `DB` class exported by `@arkstack/database`, declared
161
+ * locally so the cache package does not depend on it at build time (it is an
162
+ * optional peer dependency).
163
+ */
164
+ interface DatabaseFacade {
165
+ table(table: string): {
166
+ where(where: Record<string, unknown>): {
167
+ first(): Promise<CacheRow | null>;
168
+ delete(): Promise<unknown>;
169
+ };
170
+ updateOrInsert(attributes: Record<string, unknown>, values: Record<string, unknown>): Promise<boolean>;
171
+ delete(): Promise<unknown>;
172
+ };
173
+ }
174
+ /**
175
+ * Minimal structural type for the slice of the ioredis client we use. Declared
176
+ * locally so the package does not need ioredis types at build time (it is an
177
+ * optional peer dependency).
178
+ */
179
+ interface RedisClient {
180
+ get(key: string): Promise<string | null>;
181
+ set(key: string, value: string): Promise<unknown>;
182
+ set(key: string, value: string, mode: 'EX', seconds: number): Promise<unknown>;
183
+ del(...keys: string[]): Promise<number>;
184
+ incrby(key: string, value: number): Promise<number>;
185
+ decrby(key: string, value: number): Promise<number>;
186
+ scan(cursor: string, match: 'MATCH', pattern: string, count: 'COUNT', n: number): Promise<[string, string[]]>;
187
+ quit(): Promise<unknown>;
188
+ }
189
+ //#endregion
190
+ //#region src/Repository.d.ts
191
+ /**
192
+ * Normalize a {@link CacheTtl} into a whole number of seconds, or `null` for a
193
+ * forever entry. A non positive duration is treated as already expired and
194
+ * returns `0`, signalling the caller to skip writing.
195
+ */
196
+ declare const ttlToSeconds: (ttl: CacheTtl) => number | null;
197
+ /**
198
+ * A high level wrapper around a {@link Store} that provides the developer facing
199
+ * cache API. A repository is what `Cache.store()` returns.
200
+ */
201
+ declare class Repository {
202
+ protected readonly store: Store;
203
+ constructor(store: Store);
204
+ /**
205
+ * Get the underlying store driver.
206
+ */
207
+ getStore(): Store;
208
+ /**
209
+ * Determine whether an item exists in the cache and has not expired.
210
+ *
211
+ * @param key
212
+ * @returns
213
+ */
214
+ has(key: string): Promise<boolean>;
215
+ /**
216
+ * Determine whether an item is missing from the cache.
217
+ *
218
+ * @param key
219
+ * @returns
220
+ */
221
+ missing(key: string): Promise<boolean>;
222
+ /**
223
+ * Retrieve an item from the cache, falling back to `defaultValue` (which may
224
+ * be a callback) when the item is absent.
225
+ *
226
+ * @param key
227
+ * @returns
228
+ */
229
+ get<T = unknown>(key: string, defaultValue?: Resolvable<T | null>): Promise<T | null>;
230
+ /**
231
+ * Retrieve an item and delete it from the cache in a single call.
232
+ *
233
+ * @param key
234
+ * @param defaultValue
235
+ * @returns
236
+ */
237
+ pull<T = unknown>(key: string, defaultValue?: Resolvable<T | null>): Promise<T | null>;
238
+ /**
239
+ * Store an item in the cache. A `null`/omitted ttl stores the item forever;
240
+ * a non positive ttl is a no-op that also forgets any existing entry.
241
+ *
242
+ * @param key
243
+ * @param defaultValue
244
+ * @returns
245
+ */
246
+ put(key: string, value: unknown, ttl?: CacheTtl): Promise<boolean>;
247
+ /**
248
+ * Alias for {@link put}.
249
+ *
250
+ * @param key
251
+ * @param defaultValue
252
+ * @returns
253
+ */
254
+ set(key: string, value: unknown, ttl?: CacheTtl): Promise<boolean>;
255
+ /**
256
+ * Store an item only if it is not already present. Returns `false` when the
257
+ * key already exists.
258
+ *
259
+ * @param key
260
+ * @param defaultValue
261
+ * @returns
262
+ */
263
+ add(key: string, value: unknown, ttl?: CacheTtl): Promise<boolean>;
264
+ /**
265
+ * Store an item that never expires.
266
+ *
267
+ * @param key
268
+ * @param defaultValue
269
+ * @returns
270
+ */
271
+ forever(key: string, value: unknown): Promise<boolean>;
272
+ /**
273
+ * Increment a stored numeric value.
274
+ *
275
+ * @param key
276
+ * @param defaultValue
277
+ * @returns
278
+ */
279
+ increment(key: string, value?: number): Promise<number | false>;
280
+ /**
281
+ * Decrement a stored numeric value.
282
+ *
283
+ * @param key
284
+ * @param defaultValue
285
+ * @returns
286
+ */
287
+ decrement(key: string, value?: number): Promise<number | false>;
288
+ /**
289
+ * Get an item from the cache, or execute the callback, store its result for
290
+ * the given ttl, and return it.
291
+ *
292
+ * @param key
293
+ * @param defaultValue
294
+ * @returns
295
+ */
296
+ remember<T>(key: string, ttl: CacheTtl, callback: () => T | Promise<T>): Promise<T>;
297
+ /**
298
+ * Get an item from the cache, or execute the callback and store its result
299
+ * forever.
300
+ *
301
+ * @param key
302
+ * @param defaultValue
303
+ * @returns
304
+ */
305
+ rememberForever<T>(key: string, callback: () => T | Promise<T>): Promise<T>;
306
+ /**
307
+ * Alias for {@link rememberForever}.
308
+ *
309
+ * @param key
310
+ * @param callback
311
+ * @returns
312
+ */
313
+ sear<T>(key: string, callback: () => T | Promise<T>): Promise<T>;
314
+ /**
315
+ * Remove an item from the cache.
316
+ *
317
+ * @param key
318
+ * @param callback
319
+ * @returns
320
+ */
321
+ forget(key: string): Promise<boolean>;
322
+ /**
323
+ * Alias for {@link forget}.
324
+ *
325
+ * @param key
326
+ * @param callback
327
+ * @returns
328
+ */
329
+ delete(key: string): Promise<boolean>;
330
+ /**
331
+ * Remove every item from the cache store.
332
+ *
333
+ * @param key
334
+ * @param callback
335
+ * @returns
336
+ */
337
+ flush(): Promise<boolean>;
338
+ /**
339
+ * Alias for {@link flush}.
340
+ *
341
+ * @param key
342
+ * @param callback
343
+ * @returns
344
+ */
345
+ clear(): Promise<boolean>;
346
+ }
347
+ //#endregion
348
+ //#region src/CacheManager.d.ts
349
+ /**
350
+ * The cache manager and primary entry point of `@arkstack/cache`.
351
+ *
352
+ * Resolves named cache stores from configuration, memoizes the resulting
353
+ * repositories, and exposes static convenience methods that proxy the default
354
+ * store, e.g.:
355
+ *
356
+ * ```ts
357
+ * await Cache.put('user:1', user, 60)
358
+ * await Cache.store('redis').remember('stats', 300, computeStats)
359
+ * ```
360
+ */
361
+ declare class Cache {
362
+ private static repositories;
363
+ private static customDrivers;
364
+ /**
365
+ * Resolve a cache repository for the given store name (or the default store
366
+ * when omitted). Repositories are memoized per name.
367
+ */
368
+ static store(name?: string): Repository;
369
+ /**
370
+ * Alias for {@link store}.
371
+ */
372
+ static driver(name?: string): Repository;
373
+ /**
374
+ * Register a custom store driver factory, used when a store's `driver` does
375
+ * not match a built in one.
376
+ */
377
+ static extend(driver: string, factory: CacheStoreFactory): typeof Cache;
378
+ /**
379
+ * Clear the memoized repositories. Mainly useful between tests or after the
380
+ * configuration changes at runtime.
381
+ */
382
+ static clearResolved(): void;
383
+ /**
384
+ * Build a repository for a configured store from scratch.
385
+ */
386
+ private static resolve;
387
+ /**
388
+ * Instantiate the concrete {@link Store} for a store config.
389
+ */
390
+ private static createStore;
391
+ static has(key: string): Promise<boolean>;
392
+ static missing(key: string): Promise<boolean>;
393
+ static get<T = unknown>(key: string, defaultValue?: Resolvable<T | null>): Promise<T | null>;
394
+ static pull<T = unknown>(key: string, defaultValue?: Resolvable<T | null>): Promise<T | null>;
395
+ static put(key: string, value: unknown, ttl?: CacheTtl): Promise<boolean>;
396
+ static set(key: string, value: unknown, ttl?: CacheTtl): Promise<boolean>;
397
+ static add(key: string, value: unknown, ttl?: CacheTtl): Promise<boolean>;
398
+ static forever(key: string, value: unknown): Promise<boolean>;
399
+ static increment(key: string, value?: number): Promise<number | false>;
400
+ static decrement(key: string, value?: number): Promise<number | false>;
401
+ static remember<T>(key: string, ttl: CacheTtl, callback: () => T | Promise<T>): Promise<T>;
402
+ static rememberForever<T>(key: string, callback: () => T | Promise<T>): Promise<T>;
403
+ static forget(key: string): Promise<boolean>;
404
+ static delete(key: string): Promise<boolean>;
405
+ static flush(): Promise<boolean>;
406
+ static clear(): Promise<boolean>;
407
+ }
408
+ //#endregion
409
+ //#region src/config.d.ts
410
+ /**
411
+ * Read a value from the `cache` configuration namespace with a fallback.
412
+ *
413
+ * Mirrors the framework `config()` helper but is scoped to the cache config and
414
+ * never throws when the config file is missing, returning the default instead.
415
+ *
416
+ * @param key Dot path within the cache config.
417
+ * @param defaultValue Value returned when the key is not set.
418
+ */
419
+ declare const configure: <T extends DotPath<CacheConfig>>(key: T, defaultValue: unknown) => DotPathValue<CacheConfig, T>;
420
+ //#endregion
421
+ //#region src/drivers/MemoryStore.d.ts
422
+ /**
423
+ * An in-process cache store backed by a `Map`.
424
+ *
425
+ * The backing map is shared across every `MemoryStore` instance that uses the
426
+ * same prefix, so resolving the store twice within a process sees the same data
427
+ * (matching how the other backends behave). Great for development and tests; it
428
+ * is not shared across processes.
429
+ */
430
+ declare class MemoryStore extends Store {
431
+ private readonly prefix;
432
+ private static stores;
433
+ private readonly entries;
434
+ constructor(prefix?: string);
435
+ getPrefix(): string;
436
+ get<T = unknown>(key: string): Promise<T | null>;
437
+ put(key: string, value: unknown, seconds?: number | null): Promise<boolean>;
438
+ forever(key: string, value: unknown): Promise<boolean>;
439
+ increment(key: string, value?: number): Promise<number | false>;
440
+ decrement(key: string, value?: number): Promise<number | false>;
441
+ forget(key: string): Promise<boolean>;
442
+ flush(): Promise<boolean>;
443
+ }
444
+ //#endregion
445
+ //#region src/drivers/FileStore.d.ts
446
+ /**
447
+ * A cache store that persists each entry as a JSON file on disk.
448
+ *
449
+ * Keys are hashed to produce safe, fixed length file names. Expired files are
450
+ * removed lazily on read and ignored otherwise.
451
+ */
452
+ declare class FileStore extends Store {
453
+ private readonly directory;
454
+ private readonly prefix;
455
+ constructor(directory: string, prefix?: string);
456
+ getPrefix(): string;
457
+ private pathFor;
458
+ private ensureDirectory;
459
+ get<T = unknown>(key: string): Promise<T | null>;
460
+ put(key: string, value: unknown, seconds?: number | null): Promise<boolean>;
461
+ forever(key: string, value: unknown): Promise<boolean>;
462
+ increment(key: string, value?: number): Promise<number | false>;
463
+ decrement(key: string, value?: number): Promise<number | false>;
464
+ forget(key: string): Promise<boolean>;
465
+ flush(): Promise<boolean>;
466
+ }
467
+ //#endregion
468
+ //#region src/drivers/RedisStore.d.ts
469
+ /**
470
+ * A Redis backed cache store using an ioredis compatible client.
471
+ *
472
+ * Values are JSON serialized. Numeric helpers use native `INCRBY`/`DECRBY` so
473
+ * counters stay atomic across processes. `ioredis` is an optional peer
474
+ * dependency; it is imported lazily so applications that never use the redis
475
+ * store don't need it installed.
476
+ */
477
+ declare class RedisStore extends Store {
478
+ private readonly redisConfig;
479
+ private readonly prefix;
480
+ private client?;
481
+ constructor(redisConfig: RedisStoreConfig, prefix?: string);
482
+ getPrefix(): string;
483
+ private prefixed;
484
+ private connection;
485
+ /**
486
+ * Disconnect the underlying client. Useful for tests and graceful shutdown.
487
+ */
488
+ disconnect(): Promise<void>;
489
+ get<T = unknown>(key: string): Promise<T | null>;
490
+ put(key: string, value: unknown, seconds?: number | null): Promise<boolean>;
491
+ forever(key: string, value: unknown): Promise<boolean>;
492
+ increment(key: string, value?: number): Promise<number | false>;
493
+ decrement(key: string, value?: number): Promise<number | false>;
494
+ forget(key: string): Promise<boolean>;
495
+ flush(): Promise<boolean>;
496
+ }
497
+ //#endregion
498
+ //#region src/drivers/DatabaseStore.d.ts
499
+ /**
500
+ * A cache store that persists entries in a relational table via
501
+ * `@arkstack/database`.
502
+ *
503
+ * The table is expected to have `key` (string, primary), `value` (text), and
504
+ * `expiration` (nullable integer epoch seconds) columns. `@arkstack/database`
505
+ * is an optional peer dependency and is imported lazily.
506
+ */
507
+ declare class DatabaseStore extends Store {
508
+ private readonly databaseConfig;
509
+ private readonly prefix;
510
+ private db?;
511
+ constructor(databaseConfig: DatabaseStoreConfig, prefix?: string);
512
+ getPrefix(): string;
513
+ private prefixed;
514
+ private get table();
515
+ private database;
516
+ get<T = unknown>(key: string): Promise<T | null>;
517
+ put(key: string, value: unknown, seconds?: number | null): Promise<boolean>;
518
+ forever(key: string, value: unknown): Promise<boolean>;
519
+ increment(key: string, value?: number): Promise<number | false>;
520
+ decrement(key: string, value?: number): Promise<number | false>;
521
+ forget(key: string): Promise<boolean>;
522
+ flush(): Promise<boolean>;
523
+ }
524
+ //#endregion
525
+ export { Cache, CacheConfig, CacheDriverName, CacheRow, CacheStoreConfig, CacheStoreFactory, CacheTtl, CustomCacheStoreRegistry, DatabaseFacade, DatabaseStore, DatabaseStoreConfig, FilePayload, FileStore, FileStoreConfig, MemoryEntry, MemoryStore, MemoryStoreConfig, RedisClient, RedisStore, RedisStoreConfig, Repository, Resolvable, Store, configure, ttlToSeconds };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import { a as RedisStore, c as DatabaseStore, i as ttlToSeconds, l as Store, n as configure, o as MemoryStore, r as Repository, s as FileStore, t as Cache } from "./CacheManager-DfAiI5Ft.js";
2
+ export { Cache, DatabaseStore, FileStore, MemoryStore, RedisStore, Repository, Store, configure, ttlToSeconds };
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@arkstack/cache",
3
+ "version": "0.13.0",
4
+ "type": "module",
5
+ "description": "Cache module for Arkstack, providing a unified, driver based caching layer for the framework.",
6
+ "homepage": "https://arkstack.toneflix.net/guide/cache",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/arkstack-hq/arkstack.git",
10
+ "directory": "packages/cache"
11
+ },
12
+ "keywords": [
13
+ "cache",
14
+ "caching",
15
+ "memory",
16
+ "file",
17
+ "redis",
18
+ "database",
19
+ "store",
20
+ "arkstack"
21
+ ],
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "exports": {
29
+ ".": "./dist/index.js",
30
+ "./commands/CacheClearCommand": "./dist/commands/CacheClearCommand.js",
31
+ "./package.json": "./package.json"
32
+ },
33
+ "dependencies": {
34
+ "@arkstack/common": "^0.13.0"
35
+ },
36
+ "peerDependencies": {
37
+ "@h3ravel/musket": "^2.2.0",
38
+ "ioredis": "^5.4.1",
39
+ "@arkstack/contract": "^0.13.0",
40
+ "@arkstack/database": "^0.13.0"
41
+ },
42
+ "peerDependenciesMeta": {
43
+ "@arkstack/database": {
44
+ "optional": true
45
+ },
46
+ "ioredis": {
47
+ "optional": true
48
+ }
49
+ },
50
+ "scripts": {
51
+ "build": "tsdown",
52
+ "test": "vitest",
53
+ "version:patch": "pnpm version patch"
54
+ }
55
+ }