@warlock.js/cache 4.0.171 → 4.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -0
- package/cjs/index.cjs +4088 -0
- package/cjs/index.cjs.map +1 -0
- package/esm/cache-manager.d.mts +314 -0
- package/esm/cache-manager.d.mts.map +1 -0
- package/esm/cache-manager.mjs +486 -0
- package/esm/cache-manager.mjs.map +1 -0
- package/esm/cached/auto-key.d.mts +25 -0
- package/esm/cached/auto-key.d.mts.map +1 -0
- package/esm/cached/auto-key.mjs +55 -0
- package/esm/cached/auto-key.mjs.map +1 -0
- package/esm/cached/cached.d.mts +54 -0
- package/esm/cached/cached.d.mts.map +1 -0
- package/esm/cached/cached.mjs +25 -0
- package/esm/cached/cached.mjs.map +1 -0
- package/esm/cached/index.d.mts +3 -0
- package/esm/cached/index.mjs +5 -0
- package/esm/cached/normalize-args.d.mts +51 -0
- package/esm/cached/normalize-args.d.mts.map +1 -0
- package/esm/cached/normalize-args.mjs +26 -0
- package/esm/cached/normalize-args.mjs.map +1 -0
- package/esm/drivers/base-cache-driver.d.mts +322 -0
- package/esm/drivers/base-cache-driver.d.mts.map +1 -0
- package/esm/drivers/base-cache-driver.mjs +522 -0
- package/esm/drivers/base-cache-driver.mjs.map +1 -0
- package/esm/drivers/file-cache-driver.d.mts +68 -0
- package/esm/drivers/file-cache-driver.d.mts.map +1 -0
- package/esm/drivers/file-cache-driver.mjs +174 -0
- package/esm/drivers/file-cache-driver.mjs.map +1 -0
- package/esm/drivers/index.d.mts +9 -0
- package/esm/drivers/index.mjs +11 -0
- package/esm/drivers/lru-memory-cache-driver.d.mts +136 -0
- package/esm/drivers/lru-memory-cache-driver.d.mts.map +1 -0
- package/esm/drivers/lru-memory-cache-driver.mjs +317 -0
- package/esm/drivers/lru-memory-cache-driver.mjs.map +1 -0
- package/esm/drivers/memory-cache-driver.d.mts +112 -0
- package/esm/drivers/memory-cache-driver.d.mts.map +1 -0
- package/esm/drivers/memory-cache-driver.mjs +241 -0
- package/esm/drivers/memory-cache-driver.mjs.map +1 -0
- package/esm/drivers/memory-extended-cache-driver.d.mts +17 -0
- package/esm/drivers/memory-extended-cache-driver.d.mts.map +1 -0
- package/esm/drivers/memory-extended-cache-driver.mjs +34 -0
- package/esm/drivers/memory-extended-cache-driver.mjs.map +1 -0
- package/esm/drivers/mock-cache-driver.d.mts +137 -0
- package/esm/drivers/mock-cache-driver.d.mts.map +1 -0
- package/esm/drivers/mock-cache-driver.mjs +226 -0
- package/esm/drivers/mock-cache-driver.mjs.map +1 -0
- package/esm/drivers/null-cache-driver.d.mts +69 -0
- package/esm/drivers/null-cache-driver.d.mts.map +1 -0
- package/esm/drivers/null-cache-driver.mjs +92 -0
- package/esm/drivers/null-cache-driver.mjs.map +1 -0
- package/esm/drivers/pg-cache-driver.d.mts +148 -0
- package/esm/drivers/pg-cache-driver.d.mts.map +1 -0
- package/esm/drivers/pg-cache-driver.mjs +437 -0
- package/esm/drivers/pg-cache-driver.mjs.map +1 -0
- package/esm/drivers/redis-cache-driver.d.mts +86 -0
- package/esm/drivers/redis-cache-driver.d.mts.map +1 -0
- package/esm/drivers/redis-cache-driver.mjs +312 -0
- package/esm/drivers/redis-cache-driver.mjs.map +1 -0
- package/esm/index.d.mts +21 -0
- package/esm/index.mjs +24 -0
- package/esm/list/index.d.mts +1 -0
- package/esm/list/memory-cache-list.d.mts +77 -0
- package/esm/list/memory-cache-list.d.mts.map +1 -0
- package/esm/list/memory-cache-list.mjs +119 -0
- package/esm/list/memory-cache-list.mjs.map +1 -0
- package/esm/metrics.d.mts +118 -0
- package/esm/metrics.d.mts.map +1 -0
- package/esm/metrics.mjs +197 -0
- package/esm/metrics.mjs.map +1 -0
- package/esm/scoped-cache.d.mts +205 -0
- package/esm/scoped-cache.d.mts.map +1 -0
- package/esm/scoped-cache.mjs +274 -0
- package/esm/scoped-cache.mjs.map +1 -0
- package/esm/tagged-cache.d.mts +89 -0
- package/esm/tagged-cache.d.mts.map +1 -0
- package/esm/tagged-cache.mjs +147 -0
- package/esm/tagged-cache.mjs.map +1 -0
- package/esm/tagged-scoped-cache.d.mts +111 -0
- package/esm/tagged-scoped-cache.d.mts.map +1 -0
- package/esm/tagged-scoped-cache.mjs +142 -0
- package/esm/tagged-scoped-cache.mjs.map +1 -0
- package/esm/types.d.mts +1067 -0
- package/esm/types.d.mts.map +1 -0
- package/esm/types.mjs +62 -0
- package/esm/types.mjs.map +1 -0
- package/esm/utils.d.mts +161 -0
- package/esm/utils.d.mts.map +1 -0
- package/esm/utils.mjs +222 -0
- package/esm/utils.mjs.map +1 -0
- package/llms-full.txt +2071 -0
- package/llms.txt +28 -0
- package/package.json +53 -39
- package/skills/apply-cache-patterns/SKILL.md +97 -0
- package/skills/cache-basics/SKILL.md +121 -0
- package/skills/configure-pg-cache/SKILL.md +115 -0
- package/skills/configure-set-options/SKILL.md +96 -0
- package/skills/handle-cache-errors/SKILL.md +91 -0
- package/skills/observe-cache/SKILL.md +103 -0
- package/skills/overview/SKILL.md +69 -0
- package/skills/pick-cache-driver/SKILL.md +115 -0
- package/skills/test-cache-code/SKILL.md +219 -0
- package/skills/use-cache-atomic/SKILL.md +67 -0
- package/skills/use-cache-bulk/SKILL.md +57 -0
- package/skills/use-cache-list/SKILL.md +85 -0
- package/skills/use-cache-lock/SKILL.md +104 -0
- package/skills/use-cache-namespace/SKILL.md +88 -0
- package/skills/use-cache-similarity/SKILL.md +94 -0
- package/skills/use-cache-tags/SKILL.md +85 -0
- package/skills/use-cache-update-merge/SKILL.md +84 -0
- package/skills/use-cache-utils/SKILL.md +89 -0
- package/skills/use-cached-hof/SKILL.md +102 -0
- package/skills/use-swr/SKILL.md +104 -0
- package/cjs/cache-manager.d.ts +0 -163
- package/cjs/cache-manager.d.ts.map +0 -1
- package/cjs/cache-manager.js +0 -322
- package/cjs/cache-manager.js.map +0 -1
- package/cjs/drivers/base-cache-driver.d.ts +0 -152
- package/cjs/drivers/base-cache-driver.d.ts.map +0 -1
- package/cjs/drivers/base-cache-driver.js +0 -321
- package/cjs/drivers/base-cache-driver.js.map +0 -1
- package/cjs/drivers/file-cache-driver.d.ts +0 -45
- package/cjs/drivers/file-cache-driver.d.ts.map +0 -1
- package/cjs/drivers/file-cache-driver.js +0 -133
- package/cjs/drivers/file-cache-driver.js.map +0 -1
- package/cjs/drivers/index.d.ts +0 -8
- package/cjs/drivers/index.d.ts.map +0 -1
- package/cjs/drivers/lru-memory-cache-driver.d.ts +0 -98
- package/cjs/drivers/lru-memory-cache-driver.d.ts.map +0 -1
- package/cjs/drivers/lru-memory-cache-driver.js +0 -252
- package/cjs/drivers/lru-memory-cache-driver.js.map +0 -1
- package/cjs/drivers/memory-cache-driver.d.ts +0 -82
- package/cjs/drivers/memory-cache-driver.d.ts.map +0 -1
- package/cjs/drivers/memory-cache-driver.js +0 -218
- package/cjs/drivers/memory-cache-driver.js.map +0 -1
- package/cjs/drivers/memory-extended-cache-driver.d.ts +0 -13
- package/cjs/drivers/memory-extended-cache-driver.d.ts.map +0 -1
- package/cjs/drivers/memory-extended-cache-driver.js +0 -25
- package/cjs/drivers/memory-extended-cache-driver.js.map +0 -1
- package/cjs/drivers/null-cache-driver.d.ts +0 -58
- package/cjs/drivers/null-cache-driver.d.ts.map +0 -1
- package/cjs/drivers/null-cache-driver.js +0 -84
- package/cjs/drivers/null-cache-driver.js.map +0 -1
- package/cjs/drivers/redis-cache-driver.d.ts +0 -57
- package/cjs/drivers/redis-cache-driver.d.ts.map +0 -1
- package/cjs/drivers/redis-cache-driver.js +0 -263
- package/cjs/drivers/redis-cache-driver.js.map +0 -1
- package/cjs/index.d.ts +0 -6
- package/cjs/index.d.ts.map +0 -1
- package/cjs/index.js +0 -1
- package/cjs/index.js.map +0 -1
- package/cjs/tagged-cache.d.ts +0 -77
- package/cjs/tagged-cache.d.ts.map +0 -1
- package/cjs/tagged-cache.js +0 -160
- package/cjs/tagged-cache.js.map +0 -1
- package/cjs/types.d.ts +0 -391
- package/cjs/types.d.ts.map +0 -1
- package/cjs/types.js +0 -36
- package/cjs/types.js.map +0 -1
- package/cjs/utils.d.ts +0 -50
- package/cjs/utils.d.ts.map +0 -1
- package/cjs/utils.js +0 -55
- package/cjs/utils.js.map +0 -1
- package/esm/cache-manager.d.ts +0 -163
- package/esm/cache-manager.d.ts.map +0 -1
- package/esm/cache-manager.js +0 -322
- package/esm/cache-manager.js.map +0 -1
- package/esm/drivers/base-cache-driver.d.ts +0 -152
- package/esm/drivers/base-cache-driver.d.ts.map +0 -1
- package/esm/drivers/base-cache-driver.js +0 -321
- package/esm/drivers/base-cache-driver.js.map +0 -1
- package/esm/drivers/file-cache-driver.d.ts +0 -45
- package/esm/drivers/file-cache-driver.d.ts.map +0 -1
- package/esm/drivers/file-cache-driver.js +0 -133
- package/esm/drivers/file-cache-driver.js.map +0 -1
- package/esm/drivers/index.d.ts +0 -8
- package/esm/drivers/index.d.ts.map +0 -1
- package/esm/drivers/lru-memory-cache-driver.d.ts +0 -98
- package/esm/drivers/lru-memory-cache-driver.d.ts.map +0 -1
- package/esm/drivers/lru-memory-cache-driver.js +0 -252
- package/esm/drivers/lru-memory-cache-driver.js.map +0 -1
- package/esm/drivers/memory-cache-driver.d.ts +0 -82
- package/esm/drivers/memory-cache-driver.d.ts.map +0 -1
- package/esm/drivers/memory-cache-driver.js +0 -218
- package/esm/drivers/memory-cache-driver.js.map +0 -1
- package/esm/drivers/memory-extended-cache-driver.d.ts +0 -13
- package/esm/drivers/memory-extended-cache-driver.d.ts.map +0 -1
- package/esm/drivers/memory-extended-cache-driver.js +0 -25
- package/esm/drivers/memory-extended-cache-driver.js.map +0 -1
- package/esm/drivers/null-cache-driver.d.ts +0 -58
- package/esm/drivers/null-cache-driver.d.ts.map +0 -1
- package/esm/drivers/null-cache-driver.js +0 -84
- package/esm/drivers/null-cache-driver.js.map +0 -1
- package/esm/drivers/redis-cache-driver.d.ts +0 -57
- package/esm/drivers/redis-cache-driver.d.ts.map +0 -1
- package/esm/drivers/redis-cache-driver.js +0 -263
- package/esm/drivers/redis-cache-driver.js.map +0 -1
- package/esm/index.d.ts +0 -6
- package/esm/index.d.ts.map +0 -1
- package/esm/index.js +0 -1
- package/esm/index.js.map +0 -1
- package/esm/tagged-cache.d.ts +0 -77
- package/esm/tagged-cache.d.ts.map +0 -1
- package/esm/tagged-cache.js +0 -160
- package/esm/tagged-cache.js.map +0 -1
- package/esm/types.d.ts +0 -391
- package/esm/types.d.ts.map +0 -1
- package/esm/types.js +0 -36
- package/esm/types.js.map +0 -1
- package/esm/utils.d.ts +0 -50
- package/esm/utils.d.ts.map +0 -1
- package/esm/utils.js +0 -55
- package/esm/utils.js.map +0 -1
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
import { CacheMetricsCollector } from "./metrics.mjs";
|
|
2
|
+
import { CacheConfigurationError, CacheDriverNotInitializedError } from "./types.mjs";
|
|
3
|
+
import { ScopedCache } from "./scoped-cache.mjs";
|
|
4
|
+
|
|
5
|
+
//#region ../../@warlock.js/cache/src/cache-manager.ts
|
|
6
|
+
var CacheManager = class {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.loadedDrivers = {};
|
|
9
|
+
this.configurations = {
|
|
10
|
+
drivers: {},
|
|
11
|
+
options: {}
|
|
12
|
+
};
|
|
13
|
+
this.globalEventListeners = /* @__PURE__ */ new Map();
|
|
14
|
+
this.name = "cacheManager";
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* {@inheritdoc}
|
|
18
|
+
*/
|
|
19
|
+
get client() {
|
|
20
|
+
return this.currentDriver?.client;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Set the cache configurations
|
|
24
|
+
*/
|
|
25
|
+
setCacheConfigurations(configurations) {
|
|
26
|
+
this.configurations.default = configurations.default;
|
|
27
|
+
this.configurations.drivers = configurations.drivers;
|
|
28
|
+
this.configurations.options = configurations.options;
|
|
29
|
+
this.configurations.logging = configurations.logging;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Set logging state
|
|
33
|
+
*/
|
|
34
|
+
setLoggingState(loggingState) {
|
|
35
|
+
this.ensureDriverInitialized();
|
|
36
|
+
this.currentDriver.setLoggingState(loggingState);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Switch the manager to a registered driver, optionally injecting runtime
|
|
40
|
+
* options that merge over the static config.
|
|
41
|
+
*
|
|
42
|
+
* The string form looks the driver up in `setCacheConfigurations({ drivers })`,
|
|
43
|
+
* loads it (or returns the cached instance), and sets it as `currentDriver`.
|
|
44
|
+
* The instance form takes a pre-built driver and bypasses the registry; the
|
|
45
|
+
* `runtimeOptions` argument is silently ignored in that case because the
|
|
46
|
+
* instance was constructed externally.
|
|
47
|
+
*
|
|
48
|
+
* Runtime options merge over `config.options[name]` per-key — runtime wins.
|
|
49
|
+
* Use this for constructor-only knobs that can't live in static config
|
|
50
|
+
* (e.g. `pg`'s `client: pg.Pool`).
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* const pool = new Pool({ connectionString });
|
|
54
|
+
* await cache.use("pg", { client: pool });
|
|
55
|
+
*/
|
|
56
|
+
async use(driver, runtimeOptions) {
|
|
57
|
+
if (typeof driver === "string") {
|
|
58
|
+
const driverInstance = await this.load(driver, runtimeOptions);
|
|
59
|
+
if (!driverInstance) throw new CacheConfigurationError(`Cache driver ${driver} is not found, please declare it in the cache drivers in the configurations list.`);
|
|
60
|
+
driver = driverInstance;
|
|
61
|
+
}
|
|
62
|
+
this.attachGlobalListeners(driver);
|
|
63
|
+
if (this.configurations.logging !== void 0) driver.setLoggingState(this.configurations.logging);
|
|
64
|
+
this.currentDriver = driver;
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Ensure driver is initialized before operations
|
|
69
|
+
*/
|
|
70
|
+
ensureDriverInitialized() {
|
|
71
|
+
if (!this.currentDriver) throw new CacheDriverNotInitializedError();
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Return the running metrics snapshot — counters, hit-rate, latency
|
|
75
|
+
* percentiles, per-driver breakdowns. Lazy-attaches the collector on
|
|
76
|
+
* first call so apps that never read metrics pay zero cost.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* const m = cache.metrics();
|
|
80
|
+
* console.log(`hit rate: ${(m.hitRate * 100).toFixed(1)}%`);
|
|
81
|
+
* console.log(`p95: ${m.latencyMs.p95.toFixed(2)}ms`);
|
|
82
|
+
*/
|
|
83
|
+
metrics() {
|
|
84
|
+
return this.ensureMetricsCollector().snapshot();
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Wipe every counter + latency sample and reset `startedAt` to now.
|
|
88
|
+
* The collector itself stays subscribed to events.
|
|
89
|
+
*/
|
|
90
|
+
resetMetrics() {
|
|
91
|
+
this.ensureMetricsCollector().reset();
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Lazy-construct the metrics collector and wire it to the global event
|
|
95
|
+
* bus. Subsequent calls return the same instance — survives `cache.use()`
|
|
96
|
+
* driver switches because handlers attach via `on()` and re-bind to every
|
|
97
|
+
* loaded driver.
|
|
98
|
+
*/
|
|
99
|
+
ensureMetricsCollector() {
|
|
100
|
+
if (this.metricsCollector) return this.metricsCollector;
|
|
101
|
+
const collector = new CacheMetricsCollector();
|
|
102
|
+
this.on("hit", (data) => collector.recordEvent("hit", data));
|
|
103
|
+
this.on("miss", (data) => collector.recordEvent("miss", data));
|
|
104
|
+
this.on("set", (data) => collector.recordEvent("set", data));
|
|
105
|
+
this.on("removed", (data) => collector.recordEvent("removed", data));
|
|
106
|
+
this.on("error", (data) => collector.recordEvent("error", data));
|
|
107
|
+
this.metricsCollector = collector;
|
|
108
|
+
return collector;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Time the body, record the elapsed milliseconds against the metrics
|
|
112
|
+
* collector for the given driver (defaults to the current driver's name).
|
|
113
|
+
* Pass-through if the collector hasn't been instantiated yet — apps that
|
|
114
|
+
* don't read metrics never pay for sample collection.
|
|
115
|
+
*/
|
|
116
|
+
async timed(body, driverName) {
|
|
117
|
+
if (!this.metricsCollector) return body();
|
|
118
|
+
const start = performance.now();
|
|
119
|
+
try {
|
|
120
|
+
return await body();
|
|
121
|
+
} finally {
|
|
122
|
+
const elapsed = performance.now() - start;
|
|
123
|
+
const name = driverName ?? this.currentDriver?.name ?? "unknown";
|
|
124
|
+
this.metricsCollector.recordLatency(name, elapsed);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* {@inheritdoc}
|
|
129
|
+
*/
|
|
130
|
+
async get(key) {
|
|
131
|
+
this.ensureDriverInitialized();
|
|
132
|
+
return this.timed(() => this.currentDriver.get(key));
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Set a value in the cache.
|
|
136
|
+
*
|
|
137
|
+
* Accepts a positional TTL (number of seconds or duration string like `"1h"`)
|
|
138
|
+
* or a rich {@link CacheSetOptions} object supporting `ttl`, `expiresAt`,
|
|
139
|
+
* `tags`, `onConflict`, `namespace`, and per-call `driver` overrides.
|
|
140
|
+
*/
|
|
141
|
+
async set(key, value, ttlOrOptions) {
|
|
142
|
+
this.ensureDriverInitialized();
|
|
143
|
+
const driverOverride = ttlOrOptions && typeof ttlOrOptions === "object" && "driver" in ttlOrOptions ? ttlOrOptions.driver : void 0;
|
|
144
|
+
if (driverOverride) {
|
|
145
|
+
const driver = await this.load(driverOverride);
|
|
146
|
+
return this.timed(() => driver.set(key, value, ttlOrOptions), driver.name);
|
|
147
|
+
}
|
|
148
|
+
return this.timed(() => this.currentDriver.set(key, value, ttlOrOptions));
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* {@inheritdoc}
|
|
152
|
+
*/
|
|
153
|
+
async remove(key) {
|
|
154
|
+
this.ensureDriverInitialized();
|
|
155
|
+
return this.timed(() => this.currentDriver.remove(key));
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* {@inheritdoc}
|
|
159
|
+
*/
|
|
160
|
+
async removeNamespace(namespace) {
|
|
161
|
+
this.ensureDriverInitialized();
|
|
162
|
+
return this.currentDriver.removeNamespace(namespace);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* {@inheritdoc}
|
|
166
|
+
*/
|
|
167
|
+
async flush() {
|
|
168
|
+
this.ensureDriverInitialized();
|
|
169
|
+
return this.currentDriver.flush();
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* {@inheritdoc}
|
|
173
|
+
*/
|
|
174
|
+
async connect() {
|
|
175
|
+
this.ensureDriverInitialized();
|
|
176
|
+
return this.currentDriver.connect();
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* {@inheritdoc}
|
|
180
|
+
*/
|
|
181
|
+
parseKey(key) {
|
|
182
|
+
this.ensureDriverInitialized();
|
|
183
|
+
return this.currentDriver.parseKey(key);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* {@inheritdoc}
|
|
187
|
+
*/
|
|
188
|
+
get options() {
|
|
189
|
+
this.ensureDriverInitialized();
|
|
190
|
+
return this.currentDriver.options;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* {@inheritdoc}
|
|
194
|
+
*/
|
|
195
|
+
setOptions(options) {
|
|
196
|
+
this.ensureDriverInitialized();
|
|
197
|
+
return this.currentDriver.setOptions(options || {});
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Return the loaded driver instance for `driverName`, loading it on first
|
|
201
|
+
* call. Optional `runtimeOptions` follow the same merge-over-config rules
|
|
202
|
+
* as {@link load}; passing options after the driver has already been
|
|
203
|
+
* loaded throws to avoid silent swallowing.
|
|
204
|
+
*/
|
|
205
|
+
async driver(driverName, runtimeOptions) {
|
|
206
|
+
if (this.loadedDrivers[driverName]) {
|
|
207
|
+
this.assertNoConflictingReload(driverName, runtimeOptions);
|
|
208
|
+
return this.loadedDrivers[driverName];
|
|
209
|
+
}
|
|
210
|
+
return this.load(driverName, runtimeOptions);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Initialize the cache manager and pick the default driver
|
|
214
|
+
*/
|
|
215
|
+
async init() {
|
|
216
|
+
const defaultCacheDriverName = this.configurations.default;
|
|
217
|
+
if (!defaultCacheDriverName) return;
|
|
218
|
+
const driver = await this.driver(defaultCacheDriverName);
|
|
219
|
+
await this.use(driver);
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Load and connect the registered driver named `driver`. First-call wins —
|
|
223
|
+
* subsequent calls without `runtimeOptions` return the cached instance, and
|
|
224
|
+
* subsequent calls *with* `runtimeOptions` throw {@link CacheConfigurationError}
|
|
225
|
+
* to avoid silently dropping the new options.
|
|
226
|
+
*
|
|
227
|
+
* `runtimeOptions` merge over `config.options[driver]` per-key (runtime wins),
|
|
228
|
+
* letting consumers split static knobs (table, ttl, globalPrefix) from
|
|
229
|
+
* constructor-only ones (pg's `client`, custom adapters, etc.).
|
|
230
|
+
*
|
|
231
|
+
* @example
|
|
232
|
+
* const pool = new Pool({ connectionString });
|
|
233
|
+
* const pg = await cache.load("pg", { client: pool });
|
|
234
|
+
*/
|
|
235
|
+
async load(driver, runtimeOptions) {
|
|
236
|
+
if (this.loadedDrivers[driver]) {
|
|
237
|
+
this.assertNoConflictingReload(driver, runtimeOptions);
|
|
238
|
+
return this.loadedDrivers[driver];
|
|
239
|
+
}
|
|
240
|
+
const Driver = this.configurations.drivers[driver];
|
|
241
|
+
if (!Driver) throw new CacheConfigurationError(`Cache driver ${driver} is not found, please declare it in the cache drivers in the configurations list.`);
|
|
242
|
+
const driverInstance = new Driver();
|
|
243
|
+
const configOptions = this.configurations.options[driver] || {};
|
|
244
|
+
driverInstance.setOptions({
|
|
245
|
+
...configOptions,
|
|
246
|
+
...runtimeOptions ?? {}
|
|
247
|
+
});
|
|
248
|
+
await driverInstance.connect();
|
|
249
|
+
this.attachGlobalListeners(driverInstance);
|
|
250
|
+
this.loadedDrivers[driver] = driverInstance;
|
|
251
|
+
return driverInstance;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Guard against silently dropping runtime options on a re-load. Once a
|
|
255
|
+
* driver has been instantiated, its options are frozen — calling `load` /
|
|
256
|
+
* `driver` / `use` again with a non-empty `runtimeOptions` would otherwise
|
|
257
|
+
* appear to work but actually use the original options. We throw instead
|
|
258
|
+
* so the misuse surfaces at the call site.
|
|
259
|
+
*/
|
|
260
|
+
assertNoConflictingReload(driverName, runtimeOptions) {
|
|
261
|
+
if (runtimeOptions === void 0) return;
|
|
262
|
+
if (Object.keys(runtimeOptions).length === 0) return;
|
|
263
|
+
throw new CacheConfigurationError(`Cache driver '${driverName}' is already loaded; runtime options on subsequent calls are ignored — register a second driver name if you need a different configuration.`);
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Register and bind a driver
|
|
267
|
+
*/
|
|
268
|
+
registerDriver(driverName, driverClass) {
|
|
269
|
+
this.configurations.drivers[driverName] = driverClass;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Disconnect the cache manager
|
|
273
|
+
*/
|
|
274
|
+
async disconnect() {
|
|
275
|
+
if (this.currentDriver) await this.currentDriver.disconnect();
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* {@inheritdoc}
|
|
279
|
+
*/
|
|
280
|
+
async has(key) {
|
|
281
|
+
this.ensureDriverInitialized();
|
|
282
|
+
return this.currentDriver.has(key);
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* {@inheritdoc}
|
|
286
|
+
*/
|
|
287
|
+
async remember(key, ttlOrOptions, callback) {
|
|
288
|
+
this.ensureDriverInitialized();
|
|
289
|
+
const driverOverride = ttlOrOptions && typeof ttlOrOptions === "object" && "driver" in ttlOrOptions ? ttlOrOptions.driver : void 0;
|
|
290
|
+
if (driverOverride) return (await this.load(driverOverride)).remember(key, ttlOrOptions, callback);
|
|
291
|
+
return this.currentDriver.remember(key, ttlOrOptions, callback);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Stale-while-revalidate. Returns cached when fresh, returns the stale
|
|
295
|
+
* value plus a background refresh when within `freshTtl..staleTtl`,
|
|
296
|
+
* blocks like a normal miss past `staleTtl`. Honors per-call `driver`
|
|
297
|
+
* override the same way `remember()` does.
|
|
298
|
+
*
|
|
299
|
+
* @example
|
|
300
|
+
* const product = await cache.swr(
|
|
301
|
+
* "product.42",
|
|
302
|
+
* { freshTtl: "1m", staleTtl: "1h" },
|
|
303
|
+
* () => db.products.find(42),
|
|
304
|
+
* );
|
|
305
|
+
*/
|
|
306
|
+
async swr(key, options, callback) {
|
|
307
|
+
this.ensureDriverInitialized();
|
|
308
|
+
const driverOverride = options.driver;
|
|
309
|
+
if (driverOverride) return (await this.load(driverOverride)).swr(key, options, callback);
|
|
310
|
+
return this.currentDriver.swr(key, options, callback);
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* {@inheritdoc}
|
|
314
|
+
*/
|
|
315
|
+
async pull(key) {
|
|
316
|
+
this.ensureDriverInitialized();
|
|
317
|
+
return this.currentDriver.pull(key);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* {@inheritdoc}
|
|
321
|
+
*/
|
|
322
|
+
async forever(key, value) {
|
|
323
|
+
this.ensureDriverInitialized();
|
|
324
|
+
return this.currentDriver.forever(key, value);
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* {@inheritdoc}
|
|
328
|
+
*/
|
|
329
|
+
async increment(key, value) {
|
|
330
|
+
this.ensureDriverInitialized();
|
|
331
|
+
return this.currentDriver.increment(key, value);
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* {@inheritdoc}
|
|
335
|
+
*/
|
|
336
|
+
async decrement(key, value) {
|
|
337
|
+
this.ensureDriverInitialized();
|
|
338
|
+
return this.currentDriver.decrement(key, value);
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* {@inheritdoc}
|
|
342
|
+
*/
|
|
343
|
+
async many(keys) {
|
|
344
|
+
this.ensureDriverInitialized();
|
|
345
|
+
return this.currentDriver.many(keys);
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* {@inheritdoc}
|
|
349
|
+
*/
|
|
350
|
+
async setMany(items, ttl) {
|
|
351
|
+
this.ensureDriverInitialized();
|
|
352
|
+
return this.currentDriver.setMany(items, ttl);
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Register a global event listener (applies to all drivers)
|
|
356
|
+
*/
|
|
357
|
+
on(event, handler) {
|
|
358
|
+
if (!this.globalEventListeners.has(event)) this.globalEventListeners.set(event, /* @__PURE__ */ new Set());
|
|
359
|
+
this.globalEventListeners.get(event).add(handler);
|
|
360
|
+
if (this.currentDriver) this.currentDriver.on(event, handler);
|
|
361
|
+
for (const driver of Object.values(this.loadedDrivers)) driver.on(event, handler);
|
|
362
|
+
return this;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Remove a global event listener
|
|
366
|
+
*/
|
|
367
|
+
off(event, handler) {
|
|
368
|
+
const handlers = this.globalEventListeners.get(event);
|
|
369
|
+
if (handlers) handlers.delete(handler);
|
|
370
|
+
if (this.currentDriver) this.currentDriver.off(event, handler);
|
|
371
|
+
for (const driver of Object.values(this.loadedDrivers)) driver.off(event, handler);
|
|
372
|
+
return this;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Register a one-time global event listener
|
|
376
|
+
*/
|
|
377
|
+
once(event, handler) {
|
|
378
|
+
const onceHandler = async (data) => {
|
|
379
|
+
await handler(data);
|
|
380
|
+
this.off(event, onceHandler);
|
|
381
|
+
};
|
|
382
|
+
return this.on(event, onceHandler);
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Attach global listeners to a driver
|
|
386
|
+
*/
|
|
387
|
+
attachGlobalListeners(driver) {
|
|
388
|
+
for (const [event, handlers] of this.globalEventListeners) for (const handler of handlers) driver.on(event, handler);
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Set if not exists (atomic operation)
|
|
392
|
+
* Returns true if key was set, false if key already existed
|
|
393
|
+
* Note: Only supported by drivers that implement setNX (e.g., Redis)
|
|
394
|
+
*/
|
|
395
|
+
async setNX(key, value, ttl) {
|
|
396
|
+
this.ensureDriverInitialized();
|
|
397
|
+
if (!this.currentDriver.setNX) throw new Error(`setNX is not supported by the current cache driver: ${this.currentDriver.name}`);
|
|
398
|
+
return this.currentDriver.setNX(key, value, ttl);
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Create a tagged cache instance for the given tags
|
|
402
|
+
*/
|
|
403
|
+
tags(tags) {
|
|
404
|
+
this.ensureDriverInitialized();
|
|
405
|
+
return this.currentDriver.tags(tags);
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Atomically read, transform, and write a cached value. Delegates to the current driver.
|
|
409
|
+
*/
|
|
410
|
+
async update(key, fn, options) {
|
|
411
|
+
this.ensureDriverInitialized();
|
|
412
|
+
return this.currentDriver.update(key, fn, options);
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Shallow-merge a partial object into a cached value.
|
|
416
|
+
*/
|
|
417
|
+
async merge(key, partial, options) {
|
|
418
|
+
this.ensureDriverInitialized();
|
|
419
|
+
return this.currentDriver.merge(key, partial, options);
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Obtain a list accessor bound to the current driver.
|
|
423
|
+
*/
|
|
424
|
+
list(key) {
|
|
425
|
+
this.ensureDriverInitialized();
|
|
426
|
+
return this.currentDriver.list(key);
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Acquire a distributed lock, run `fn`, and auto-release. Returns a
|
|
430
|
+
* {@link LockOutcome} discriminated union so callers can distinguish
|
|
431
|
+
* "ran and got this value" from "skipped because someone else holds it".
|
|
432
|
+
*
|
|
433
|
+
* Honors the `driver` option for per-call driver override, same as `set`
|
|
434
|
+
* and `remember`.
|
|
435
|
+
*
|
|
436
|
+
* @example
|
|
437
|
+
* const outcome = await cache.lock("lock.import", "5m", async () => {
|
|
438
|
+
* await runImport();
|
|
439
|
+
* return "done";
|
|
440
|
+
* });
|
|
441
|
+
* if (!outcome.acquired) {
|
|
442
|
+
* console.log("another worker is already importing");
|
|
443
|
+
* }
|
|
444
|
+
*/
|
|
445
|
+
async lock(key, ttlOrOptions, fn) {
|
|
446
|
+
this.ensureDriverInitialized();
|
|
447
|
+
const driverOverride = ttlOrOptions && typeof ttlOrOptions === "object" && "driver" in ttlOrOptions ? ttlOrOptions.driver : void 0;
|
|
448
|
+
return (driverOverride ? await this.load(driverOverride) : this.currentDriver).lock(key, ttlOrOptions, fn);
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Similarity retrieval. Delegates to the current driver's `similar()` impl.
|
|
452
|
+
*
|
|
453
|
+
* Drivers that lack a similarity index throw {@link CacheUnsupportedError}.
|
|
454
|
+
*
|
|
455
|
+
* @example
|
|
456
|
+
* const hits = await cache.similar(await embed(query), { topK: 5, threshold: 0.7 });
|
|
457
|
+
*/
|
|
458
|
+
/**
|
|
459
|
+
* Create a scoped view over the cache. Every key written through the
|
|
460
|
+
* returned scope is automatically prefixed with `prefix`; optional defaults
|
|
461
|
+
* (`ttl`, `tags`) flow through every write inside the scope.
|
|
462
|
+
*
|
|
463
|
+
* Per-call options always win over scope defaults. Scope tags merge
|
|
464
|
+
* additively with per-call tags. Nested scopes inherit from the parent.
|
|
465
|
+
*
|
|
466
|
+
* @example
|
|
467
|
+
* const chat = cache.namespace("chats.10", { ttl: "30d" });
|
|
468
|
+
* await chat.set("messages.1", msg); // → "chats.10.messages.1", 30d
|
|
469
|
+
* await chat.set("draft", d, { ttl: "1h" }); // per-call ttl wins
|
|
470
|
+
* await chat.namespace("typing", { ttl: "5s" }).set("user.42", true);
|
|
471
|
+
* await chat.clear(); // wipe the whole scope
|
|
472
|
+
*/
|
|
473
|
+
namespace(prefix, options) {
|
|
474
|
+
this.ensureDriverInitialized();
|
|
475
|
+
return new ScopedCache(this, prefix, options);
|
|
476
|
+
}
|
|
477
|
+
async similar(vector, options) {
|
|
478
|
+
this.ensureDriverInitialized();
|
|
479
|
+
return this.currentDriver.similar(vector, options);
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
const cache = new CacheManager();
|
|
483
|
+
|
|
484
|
+
//#endregion
|
|
485
|
+
export { CacheManager, cache };
|
|
486
|
+
//# sourceMappingURL=cache-manager.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-manager.mjs","names":[],"sources":["../../../../../@warlock.js/cache/src/cache-manager.ts"],"sourcesContent":["import { CacheMetricsCollector } from \"./metrics\";\r\nimport { ScopedCache } from \"./scoped-cache\";\r\nimport type {\r\n CacheConfigurations,\r\n CacheDriver,\r\n CacheEventHandler,\r\n CacheEventType,\r\n CacheKey,\r\n CacheListAccessor,\r\n CacheMetricsSnapshot,\r\n CacheNamespaceOptions,\r\n CacheSetOptions,\r\n CacheSimilarHit,\r\n CacheSimilarOptions,\r\n CacheSwrOptions,\r\n CacheTtl,\r\n DriverClass,\r\n LockOptions,\r\n LockOutcome,\r\n RememberOptions,\r\n ScopedCacheContract,\r\n TaggedCacheDriver,\r\n} from \"./types\";\r\nimport { CacheConfigurationError, CacheDriverNotInitializedError } from \"./types\";\r\n\r\nexport class CacheManager implements CacheDriver<any, any> {\r\n /**\r\n * Cache Driver\r\n */\r\n public currentDriver?: CacheDriver<any, any>;\r\n\r\n /**\r\n * Loaded drivers\r\n */\r\n public loadedDrivers: Record<string, CacheDriver<any, any>> = {};\r\n\r\n /**\r\n * Configurations list\r\n */\r\n protected configurations: CacheConfigurations = {\r\n drivers: {},\r\n options: {},\r\n };\r\n\r\n /**\r\n * Global event listeners\r\n */\r\n protected globalEventListeners: Map<CacheEventType, Set<CacheEventHandler>> = new Map();\r\n\r\n /**\r\n * Metrics collector — lazy on first {@link metrics} call so apps that\r\n * never read metrics pay zero cost. Once instantiated, it stays\r\n * subscribed to events for the manager's lifetime.\r\n */\r\n protected metricsCollector?: CacheMetricsCollector;\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public name = \"cacheManager\";\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public get client() {\r\n return this.currentDriver?.client;\r\n }\r\n\r\n /**\r\n * Set the cache configurations\r\n */\r\n public setCacheConfigurations(configurations: CacheConfigurations) {\r\n this.configurations.default = configurations.default;\r\n this.configurations.drivers = configurations.drivers;\r\n this.configurations.options = configurations.options;\r\n this.configurations.logging = configurations.logging;\r\n }\r\n\r\n /**\r\n * Set logging state\r\n */\r\n public setLoggingState(loggingState: boolean) {\r\n this.ensureDriverInitialized();\r\n\r\n this.currentDriver!.setLoggingState(loggingState);\r\n }\r\n\r\n /**\r\n * Switch the manager to a registered driver, optionally injecting runtime\r\n * options that merge over the static config.\r\n *\r\n * The string form looks the driver up in `setCacheConfigurations({ drivers })`,\r\n * loads it (or returns the cached instance), and sets it as `currentDriver`.\r\n * The instance form takes a pre-built driver and bypasses the registry; the\r\n * `runtimeOptions` argument is silently ignored in that case because the\r\n * instance was constructed externally.\r\n *\r\n * Runtime options merge over `config.options[name]` per-key — runtime wins.\r\n * Use this for constructor-only knobs that can't live in static config\r\n * (e.g. `pg`'s `client: pg.Pool`).\r\n *\r\n * @example\r\n * const pool = new Pool({ connectionString });\r\n * await cache.use(\"pg\", { client: pool });\r\n */\r\n public async use(\r\n driver: string | CacheDriver<any, any>,\r\n runtimeOptions?: Record<string, any>,\r\n ) {\r\n if (typeof driver === \"string\") {\r\n const driverInstance = await this.load(driver, runtimeOptions);\r\n\r\n if (!driverInstance) {\r\n throw new CacheConfigurationError(\r\n `Cache driver ${driver} is not found, please declare it in the cache drivers in the configurations list.`,\r\n );\r\n }\r\n\r\n driver = driverInstance;\r\n }\r\n\r\n this.attachGlobalListeners(driver);\r\n\r\n if (this.configurations.logging !== undefined) {\r\n driver.setLoggingState(this.configurations.logging);\r\n }\r\n\r\n this.currentDriver = driver;\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Ensure driver is initialized before operations\r\n */\r\n protected ensureDriverInitialized(): void {\r\n if (!this.currentDriver) {\r\n throw new CacheDriverNotInitializedError();\r\n }\r\n }\r\n\r\n /**\r\n * Return the running metrics snapshot — counters, hit-rate, latency\r\n * percentiles, per-driver breakdowns. Lazy-attaches the collector on\r\n * first call so apps that never read metrics pay zero cost.\r\n *\r\n * @example\r\n * const m = cache.metrics();\r\n * console.log(`hit rate: ${(m.hitRate * 100).toFixed(1)}%`);\r\n * console.log(`p95: ${m.latencyMs.p95.toFixed(2)}ms`);\r\n */\r\n public metrics(): CacheMetricsSnapshot {\r\n return this.ensureMetricsCollector().snapshot();\r\n }\r\n\r\n /**\r\n * Wipe every counter + latency sample and reset `startedAt` to now.\r\n * The collector itself stays subscribed to events.\r\n */\r\n public resetMetrics(): void {\r\n this.ensureMetricsCollector().reset();\r\n }\r\n\r\n /**\r\n * Lazy-construct the metrics collector and wire it to the global event\r\n * bus. Subsequent calls return the same instance — survives `cache.use()`\r\n * driver switches because handlers attach via `on()` and re-bind to every\r\n * loaded driver.\r\n */\r\n protected ensureMetricsCollector(): CacheMetricsCollector {\r\n if (this.metricsCollector) {\r\n return this.metricsCollector;\r\n }\r\n\r\n const collector = new CacheMetricsCollector();\r\n\r\n this.on(\"hit\", (data) => collector.recordEvent(\"hit\", data));\r\n this.on(\"miss\", (data) => collector.recordEvent(\"miss\", data));\r\n this.on(\"set\", (data) => collector.recordEvent(\"set\", data));\r\n this.on(\"removed\", (data) => collector.recordEvent(\"removed\", data));\r\n this.on(\"error\", (data) => collector.recordEvent(\"error\", data));\r\n\r\n this.metricsCollector = collector;\r\n\r\n return collector;\r\n }\r\n\r\n /**\r\n * Time the body, record the elapsed milliseconds against the metrics\r\n * collector for the given driver (defaults to the current driver's name).\r\n * Pass-through if the collector hasn't been instantiated yet — apps that\r\n * don't read metrics never pay for sample collection.\r\n */\r\n protected async timed<T>(\r\n body: () => Promise<T>,\r\n driverName?: string,\r\n ): Promise<T> {\r\n if (!this.metricsCollector) {\r\n return body();\r\n }\r\n\r\n const start = performance.now();\r\n\r\n try {\r\n return await body();\r\n } finally {\r\n const elapsed = performance.now() - start;\r\n const name = driverName ?? this.currentDriver?.name ?? \"unknown\";\r\n this.metricsCollector.recordLatency(name, elapsed);\r\n }\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async get<T = any>(key: CacheKey): Promise<T | null> {\r\n this.ensureDriverInitialized();\r\n return this.timed(() => this.currentDriver!.get<T>(key));\r\n }\r\n\r\n /**\r\n * Set a value in the cache.\r\n *\r\n * Accepts a positional TTL (number of seconds or duration string like `\"1h\"`)\r\n * or a rich {@link CacheSetOptions} object supporting `ttl`, `expiresAt`,\r\n * `tags`, `onConflict`, `namespace`, and per-call `driver` overrides.\r\n */\r\n public async set(key: CacheKey, value: any, ttlOrOptions?: CacheTtl | CacheSetOptions) {\r\n this.ensureDriverInitialized();\r\n\r\n const driverOverride =\r\n ttlOrOptions && typeof ttlOrOptions === \"object\" && \"driver\" in ttlOrOptions\r\n ? ttlOrOptions.driver\r\n : undefined;\r\n\r\n if (driverOverride) {\r\n const driver = await this.load(driverOverride);\r\n return this.timed(() => driver.set(key, value, ttlOrOptions), driver.name);\r\n }\r\n\r\n return this.timed(() => this.currentDriver!.set(key, value, ttlOrOptions));\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async remove(key: CacheKey) {\r\n this.ensureDriverInitialized();\r\n return this.timed(() => this.currentDriver!.remove(key));\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async removeNamespace(namespace: string) {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.removeNamespace(namespace);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async flush() {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.flush();\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async connect() {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.connect();\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public parseKey(key: CacheKey) {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.parseKey(key);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public get options() {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.options;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public setOptions(options: Record<string, any>) {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.setOptions(options || {});\r\n }\r\n\r\n /**\r\n * Return the loaded driver instance for `driverName`, loading it on first\r\n * call. Optional `runtimeOptions` follow the same merge-over-config rules\r\n * as {@link load}; passing options after the driver has already been\r\n * loaded throws to avoid silent swallowing.\r\n */\r\n public async driver(driverName: string, runtimeOptions?: Record<string, any>) {\r\n if (this.loadedDrivers[driverName]) {\r\n this.assertNoConflictingReload(driverName, runtimeOptions);\r\n\r\n return this.loadedDrivers[driverName];\r\n }\r\n\r\n return this.load(driverName, runtimeOptions);\r\n }\r\n\r\n /**\r\n * Initialize the cache manager and pick the default driver\r\n */\r\n public async init() {\r\n const defaultCacheDriverName = this.configurations.default;\r\n\r\n if (!defaultCacheDriverName) {\r\n return;\r\n }\r\n\r\n const driver = await this.driver(defaultCacheDriverName);\r\n\r\n await this.use(driver);\r\n }\r\n\r\n /**\r\n * Load and connect the registered driver named `driver`. First-call wins —\r\n * subsequent calls without `runtimeOptions` return the cached instance, and\r\n * subsequent calls *with* `runtimeOptions` throw {@link CacheConfigurationError}\r\n * to avoid silently dropping the new options.\r\n *\r\n * `runtimeOptions` merge over `config.options[driver]` per-key (runtime wins),\r\n * letting consumers split static knobs (table, ttl, globalPrefix) from\r\n * constructor-only ones (pg's `client`, custom adapters, etc.).\r\n *\r\n * @example\r\n * const pool = new Pool({ connectionString });\r\n * const pg = await cache.load(\"pg\", { client: pool });\r\n */\r\n public async load(driver: string, runtimeOptions?: Record<string, any>) {\r\n if (this.loadedDrivers[driver]) {\r\n this.assertNoConflictingReload(driver, runtimeOptions);\r\n\r\n return this.loadedDrivers[driver];\r\n }\r\n\r\n const Driver = this.configurations.drivers[\r\n driver as keyof typeof this.configurations.drivers\r\n ] as DriverClass | undefined;\r\n\r\n if (!Driver) {\r\n throw new CacheConfigurationError(\r\n `Cache driver ${driver} is not found, please declare it in the cache drivers in the configurations list.`,\r\n );\r\n }\r\n\r\n const driverInstance = new Driver();\r\n const configOptions =\r\n this.configurations.options[driver as keyof typeof this.configurations.options] || {};\r\n\r\n driverInstance.setOptions({ ...configOptions, ...(runtimeOptions ?? {}) });\r\n\r\n await driverInstance.connect();\r\n\r\n this.attachGlobalListeners(driverInstance);\r\n\r\n this.loadedDrivers[driver] = driverInstance;\r\n\r\n return driverInstance as CacheDriver<any, any>;\r\n }\r\n\r\n /**\r\n * Guard against silently dropping runtime options on a re-load. Once a\r\n * driver has been instantiated, its options are frozen — calling `load` /\r\n * `driver` / `use` again with a non-empty `runtimeOptions` would otherwise\r\n * appear to work but actually use the original options. We throw instead\r\n * so the misuse surfaces at the call site.\r\n */\r\n protected assertNoConflictingReload(\r\n driverName: string,\r\n runtimeOptions: Record<string, any> | undefined,\r\n ): void {\r\n if (runtimeOptions === undefined) {\r\n return;\r\n }\r\n\r\n if (Object.keys(runtimeOptions).length === 0) {\r\n return;\r\n }\r\n\r\n throw new CacheConfigurationError(\r\n `Cache driver '${driverName}' is already loaded; runtime options on subsequent calls are ignored — register a second driver name if you need a different configuration.`,\r\n );\r\n }\r\n\r\n /**\r\n * Register and bind a driver\r\n */\r\n public registerDriver(driverName: string, driverClass: DriverClass) {\r\n (this.configurations.drivers as Record<string, DriverClass>)[driverName] = driverClass;\r\n }\r\n\r\n /**\r\n * Disconnect the cache manager\r\n */\r\n public async disconnect() {\r\n if (this.currentDriver) {\r\n await this.currentDriver.disconnect();\r\n }\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async has(key: CacheKey): Promise<boolean> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.has(key);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async remember<T = any>(\r\n key: CacheKey,\r\n ttlOrOptions: CacheTtl | RememberOptions,\r\n callback: () => Promise<T>,\r\n ): Promise<T> {\r\n this.ensureDriverInitialized();\r\n\r\n const driverOverride =\r\n ttlOrOptions && typeof ttlOrOptions === \"object\" && \"driver\" in ttlOrOptions\r\n ? ttlOrOptions.driver\r\n : undefined;\r\n\r\n if (driverOverride) {\r\n const driver = await this.load(driverOverride);\r\n return driver.remember(key, ttlOrOptions, callback);\r\n }\r\n\r\n return this.currentDriver!.remember(key, ttlOrOptions, callback);\r\n }\r\n\r\n /**\r\n * Stale-while-revalidate. Returns cached when fresh, returns the stale\r\n * value plus a background refresh when within `freshTtl..staleTtl`,\r\n * blocks like a normal miss past `staleTtl`. Honors per-call `driver`\r\n * override the same way `remember()` does.\r\n *\r\n * @example\r\n * const product = await cache.swr(\r\n * \"product.42\",\r\n * { freshTtl: \"1m\", staleTtl: \"1h\" },\r\n * () => db.products.find(42),\r\n * );\r\n */\r\n public async swr<T = any>(\r\n key: CacheKey,\r\n options: CacheSwrOptions,\r\n callback: () => Promise<T>,\r\n ): Promise<T> {\r\n this.ensureDriverInitialized();\r\n\r\n const driverOverride = options.driver;\r\n\r\n if (driverOverride) {\r\n const driver = await this.load(driverOverride);\r\n\r\n return driver.swr<T>(key, options, callback);\r\n }\r\n\r\n return this.currentDriver!.swr<T>(key, options, callback);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async pull(key: CacheKey): Promise<any | null> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.pull(key);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async forever(key: CacheKey, value: any): Promise<any> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.forever(key, value);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async increment(key: CacheKey, value?: number): Promise<number> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.increment(key, value);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async decrement(key: CacheKey, value?: number): Promise<number> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.decrement(key, value);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async many(keys: CacheKey[]): Promise<any[]> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.many(keys);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async setMany(items: Record<string, any>, ttl?: number): Promise<void> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.setMany(items, ttl);\r\n }\r\n\r\n /**\r\n * Register a global event listener (applies to all drivers)\r\n */\r\n public on(event: CacheEventType, handler: CacheEventHandler): this {\r\n if (!this.globalEventListeners.has(event)) {\r\n this.globalEventListeners.set(event, new Set());\r\n }\r\n this.globalEventListeners.get(event)!.add(handler);\r\n\r\n // Also attach to current driver if exists\r\n if (this.currentDriver) {\r\n this.currentDriver.on(event, handler);\r\n }\r\n\r\n // Attach to all loaded drivers\r\n for (const driver of Object.values(this.loadedDrivers)) {\r\n driver.on(event, handler);\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Remove a global event listener\r\n */\r\n public off(event: CacheEventType, handler: CacheEventHandler): this {\r\n const handlers = this.globalEventListeners.get(event);\r\n if (handlers) {\r\n handlers.delete(handler);\r\n }\r\n\r\n // Also remove from current driver\r\n if (this.currentDriver) {\r\n this.currentDriver.off(event, handler);\r\n }\r\n\r\n // Remove from all loaded drivers\r\n for (const driver of Object.values(this.loadedDrivers)) {\r\n driver.off(event, handler);\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Register a one-time global event listener\r\n */\r\n public once(event: CacheEventType, handler: CacheEventHandler): this {\r\n const onceHandler: CacheEventHandler = async (data) => {\r\n await handler(data);\r\n this.off(event, onceHandler);\r\n };\r\n return this.on(event, onceHandler);\r\n }\r\n\r\n /**\r\n * Attach global listeners to a driver\r\n */\r\n protected attachGlobalListeners(driver: CacheDriver<any, any>) {\r\n for (const [event, handlers] of this.globalEventListeners) {\r\n for (const handler of handlers) {\r\n driver.on(event, handler);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Set if not exists (atomic operation)\r\n * Returns true if key was set, false if key already existed\r\n * Note: Only supported by drivers that implement setNX (e.g., Redis)\r\n */\r\n public async setNX(key: CacheKey, value: any, ttl?: number): Promise<boolean> {\r\n this.ensureDriverInitialized();\r\n\r\n if (!this.currentDriver!.setNX) {\r\n throw new Error(\r\n `setNX is not supported by the current cache driver: ${this.currentDriver!.name}`,\r\n );\r\n }\r\n\r\n return this.currentDriver!.setNX(key, value, ttl);\r\n }\r\n\r\n /**\r\n * Create a tagged cache instance for the given tags\r\n */\r\n public tags(tags: string[]): TaggedCacheDriver {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.tags(tags);\r\n }\r\n\r\n /**\r\n * Atomically read, transform, and write a cached value. Delegates to the current driver.\r\n */\r\n public async update<T = any>(\r\n key: CacheKey,\r\n fn: (current: T | null) => T | null | Promise<T | null>,\r\n options?: { ttl?: CacheTtl },\r\n ): Promise<T | null> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.update<T>(key, fn, options);\r\n }\r\n\r\n /**\r\n * Shallow-merge a partial object into a cached value.\r\n */\r\n public async merge<T extends Record<string, any> = Record<string, any>>(\r\n key: CacheKey,\r\n partial: Partial<T>,\r\n options?: { ttl?: CacheTtl },\r\n ): Promise<T> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.merge<T>(key, partial, options);\r\n }\r\n\r\n /**\r\n * Obtain a list accessor bound to the current driver.\r\n */\r\n public list<T = any>(key: CacheKey): CacheListAccessor<T> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.list<T>(key);\r\n }\r\n\r\n /**\r\n * Acquire a distributed lock, run `fn`, and auto-release. Returns a\r\n * {@link LockOutcome} discriminated union so callers can distinguish\r\n * \"ran and got this value\" from \"skipped because someone else holds it\".\r\n *\r\n * Honors the `driver` option for per-call driver override, same as `set`\r\n * and `remember`.\r\n *\r\n * @example\r\n * const outcome = await cache.lock(\"lock.import\", \"5m\", async () => {\r\n * await runImport();\r\n * return \"done\";\r\n * });\r\n * if (!outcome.acquired) {\r\n * console.log(\"another worker is already importing\");\r\n * }\r\n */\r\n public async lock<T>(\r\n key: CacheKey,\r\n ttlOrOptions: CacheTtl | LockOptions,\r\n fn: () => Promise<T>,\r\n ): Promise<LockOutcome<T>> {\r\n this.ensureDriverInitialized();\r\n\r\n const driverOverride =\r\n ttlOrOptions && typeof ttlOrOptions === \"object\" && \"driver\" in ttlOrOptions\r\n ? ttlOrOptions.driver\r\n : undefined;\r\n\r\n const driver = driverOverride\r\n ? await this.load(driverOverride)\r\n : this.currentDriver!;\r\n\r\n return driver.lock<T>(key, ttlOrOptions as CacheTtl | Omit<LockOptions, \"driver\">, fn);\r\n }\r\n\r\n /**\r\n * Similarity retrieval. Delegates to the current driver's `similar()` impl.\r\n *\r\n * Drivers that lack a similarity index throw {@link CacheUnsupportedError}.\r\n *\r\n * @example\r\n * const hits = await cache.similar(await embed(query), { topK: 5, threshold: 0.7 });\r\n */\r\n /**\r\n * Create a scoped view over the cache. Every key written through the\r\n * returned scope is automatically prefixed with `prefix`; optional defaults\r\n * (`ttl`, `tags`) flow through every write inside the scope.\r\n *\r\n * Per-call options always win over scope defaults. Scope tags merge\r\n * additively with per-call tags. Nested scopes inherit from the parent.\r\n *\r\n * @example\r\n * const chat = cache.namespace(\"chats.10\", { ttl: \"30d\" });\r\n * await chat.set(\"messages.1\", msg); // → \"chats.10.messages.1\", 30d\r\n * await chat.set(\"draft\", d, { ttl: \"1h\" }); // per-call ttl wins\r\n * await chat.namespace(\"typing\", { ttl: \"5s\" }).set(\"user.42\", true);\r\n * await chat.clear(); // wipe the whole scope\r\n */\r\n public namespace(prefix: string, options?: CacheNamespaceOptions): ScopedCacheContract {\r\n this.ensureDriverInitialized();\r\n return new ScopedCache(this, prefix, options);\r\n }\r\n\r\n public async similar<T = any>(\r\n vector: number[],\r\n options: CacheSimilarOptions,\r\n ): Promise<CacheSimilarHit<T>[]> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.similar<T>(vector, options);\r\n }\r\n}\r\n\r\nexport const cache = new CacheManager();\r\n"],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//#region ../../@warlock.js/cache/src/cached/auto-key.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Derive a cache key from a prefix and a set of function arguments.
|
|
4
|
+
*
|
|
5
|
+
* Rules (in order of precedence):
|
|
6
|
+
* 1. No args → prefix alone.
|
|
7
|
+
* 2. All primitives (`string`, `number`, `boolean`) or `null` / `undefined` /
|
|
8
|
+
* `bigint` → joined onto the prefix with dots.
|
|
9
|
+
* 3. Any non-primitive arg present → the full args array is `JSON.stringify`-ed
|
|
10
|
+
* and appended to the prefix.
|
|
11
|
+
* 4. Serialization throws (circular refs, `BigInt` nested in an object) → we
|
|
12
|
+
* re-throw as `CacheConfigurationError` so the caller sees a cache-scoped
|
|
13
|
+
* error rather than a cryptic `TypeError`.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* deriveAutoKey("user", [42]); // "user.42"
|
|
17
|
+
* deriveAutoKey("orders", [42, "abc"]); // "orders.42.abc"
|
|
18
|
+
* deriveAutoKey("featured", []); // "featured"
|
|
19
|
+
* deriveAutoKey("search", [{ q: "hello" }]); // "search.[{\"q\":\"hello\"}]"
|
|
20
|
+
* deriveAutoKey("user", [null, undefined]); // "user.null.undefined"
|
|
21
|
+
*/
|
|
22
|
+
declare function deriveAutoKey(prefix: string, args: readonly unknown[]): string;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { deriveAutoKey };
|
|
25
|
+
//# sourceMappingURL=auto-key.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-key.d.mts","names":[],"sources":["../../../../../../@warlock.js/cache/src/cached/auto-key.ts"],"mappings":";;AAsBA;;;;AAAsE;;;;;;;;;;;;;;;iBAAtD,aAAA,CAAc,MAAA,UAAgB,IAAwB"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { CacheConfigurationError } from "../types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region ../../@warlock.js/cache/src/cached/auto-key.ts
|
|
4
|
+
/**
|
|
5
|
+
* Derive a cache key from a prefix and a set of function arguments.
|
|
6
|
+
*
|
|
7
|
+
* Rules (in order of precedence):
|
|
8
|
+
* 1. No args → prefix alone.
|
|
9
|
+
* 2. All primitives (`string`, `number`, `boolean`) or `null` / `undefined` /
|
|
10
|
+
* `bigint` → joined onto the prefix with dots.
|
|
11
|
+
* 3. Any non-primitive arg present → the full args array is `JSON.stringify`-ed
|
|
12
|
+
* and appended to the prefix.
|
|
13
|
+
* 4. Serialization throws (circular refs, `BigInt` nested in an object) → we
|
|
14
|
+
* re-throw as `CacheConfigurationError` so the caller sees a cache-scoped
|
|
15
|
+
* error rather than a cryptic `TypeError`.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* deriveAutoKey("user", [42]); // "user.42"
|
|
19
|
+
* deriveAutoKey("orders", [42, "abc"]); // "orders.42.abc"
|
|
20
|
+
* deriveAutoKey("featured", []); // "featured"
|
|
21
|
+
* deriveAutoKey("search", [{ q: "hello" }]); // "search.[{\"q\":\"hello\"}]"
|
|
22
|
+
* deriveAutoKey("user", [null, undefined]); // "user.null.undefined"
|
|
23
|
+
*/
|
|
24
|
+
function deriveAutoKey(prefix, args) {
|
|
25
|
+
if (args.length === 0) return prefix;
|
|
26
|
+
if (args.every(isPrimitiveOrNullish)) return prefix + "." + args.map(serializePrimitive).join(".");
|
|
27
|
+
try {
|
|
28
|
+
return prefix + "." + JSON.stringify(args);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
throw new CacheConfigurationError(`cached(): could not derive an auto-key from args for prefix "${prefix}". The args include a value that is not JSON-serializable (circular reference, BigInt nested inside an object, or similar). Use the options form with a custom key function. Original error: ${error.message}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Primitives and nullish values can be concatenated directly onto a key without
|
|
35
|
+
* JSON serialization. Adding `bigint` here avoids the `JSON.stringify` throw on
|
|
36
|
+
* top-level bigint args.
|
|
37
|
+
*/
|
|
38
|
+
function isPrimitiveOrNullish(value) {
|
|
39
|
+
if (value === null || value === void 0) return true;
|
|
40
|
+
const type = typeof value;
|
|
41
|
+
return type === "string" || type === "number" || type === "boolean" || type === "bigint";
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Serialize a single primitive or nullish value to its string key-segment form.
|
|
45
|
+
*/
|
|
46
|
+
function serializePrimitive(value) {
|
|
47
|
+
if (value === null) return "null";
|
|
48
|
+
if (value === void 0) return "undefined";
|
|
49
|
+
if (typeof value === "bigint") return value.toString();
|
|
50
|
+
return String(value);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
//#endregion
|
|
54
|
+
export { deriveAutoKey };
|
|
55
|
+
//# sourceMappingURL=auto-key.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-key.mjs","names":[],"sources":["../../../../../../@warlock.js/cache/src/cached/auto-key.ts"],"sourcesContent":["import { CacheConfigurationError } from \"../types\";\n\n/**\n * Derive a cache key from a prefix and a set of function arguments.\n *\n * Rules (in order of precedence):\n * 1. No args → prefix alone.\n * 2. All primitives (`string`, `number`, `boolean`) or `null` / `undefined` /\n * `bigint` → joined onto the prefix with dots.\n * 3. Any non-primitive arg present → the full args array is `JSON.stringify`-ed\n * and appended to the prefix.\n * 4. Serialization throws (circular refs, `BigInt` nested in an object) → we\n * re-throw as `CacheConfigurationError` so the caller sees a cache-scoped\n * error rather than a cryptic `TypeError`.\n *\n * @example\n * deriveAutoKey(\"user\", [42]); // \"user.42\"\n * deriveAutoKey(\"orders\", [42, \"abc\"]); // \"orders.42.abc\"\n * deriveAutoKey(\"featured\", []); // \"featured\"\n * deriveAutoKey(\"search\", [{ q: \"hello\" }]); // \"search.[{\\\"q\\\":\\\"hello\\\"}]\"\n * deriveAutoKey(\"user\", [null, undefined]); // \"user.null.undefined\"\n */\nexport function deriveAutoKey(prefix: string, args: readonly unknown[]): string {\n if (args.length === 0) {\n return prefix;\n }\n\n if (args.every(isPrimitiveOrNullish)) {\n return prefix + \".\" + args.map(serializePrimitive).join(\".\");\n }\n\n try {\n return prefix + \".\" + JSON.stringify(args);\n } catch (error) {\n throw new CacheConfigurationError(\n `cached(): could not derive an auto-key from args for prefix \"${prefix}\". ` +\n `The args include a value that is not JSON-serializable (circular reference, ` +\n `BigInt nested inside an object, or similar). Use the options form with a custom ` +\n `key function. Original error: ${(error as Error).message}`,\n );\n }\n}\n\n/**\n * Primitives and nullish values can be concatenated directly onto a key without\n * JSON serialization. Adding `bigint` here avoids the `JSON.stringify` throw on\n * top-level bigint args.\n */\nfunction isPrimitiveOrNullish(value: unknown): boolean {\n if (value === null || value === undefined) {\n return true;\n }\n\n const type = typeof value;\n return type === \"string\" || type === \"number\" || type === \"boolean\" || type === \"bigint\";\n}\n\n/**\n * Serialize a single primitive or nullish value to its string key-segment form.\n */\nfunction serializePrimitive(value: unknown): string {\n if (value === null) return \"null\";\n if (value === undefined) return \"undefined\";\n if (typeof value === \"bigint\") return value.toString();\n return String(value);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,cAAc,QAAgB,MAAkC;CAC9E,IAAI,KAAK,WAAW,GAClB,OAAO;CAGT,IAAI,KAAK,MAAM,oBAAoB,GACjC,OAAO,SAAS,MAAM,KAAK,IAAI,kBAAkB,EAAE,KAAK,GAAG;CAG7D,IAAI;EACF,OAAO,SAAS,MAAM,KAAK,UAAU,IAAI;CAC3C,SAAS,OAAO;EACd,MAAM,IAAI,wBACR,gEAAgE,OAAO,+LAGnC,MAAgB,SACtD;CACF;AACF;;;;;;AAOA,SAAS,qBAAqB,OAAyB;CACrD,IAAI,UAAU,QAAQ,UAAU,QAC9B,OAAO;CAGT,MAAM,OAAO,OAAO;CACpB,OAAO,SAAS,YAAY,SAAS,YAAY,SAAS,aAAa,SAAS;AAClF;;;;AAKA,SAAS,mBAAmB,OAAwB;CAClD,IAAI,UAAU,MAAM,OAAO;CAC3B,IAAI,UAAU,QAAW,OAAO;CAChC,IAAI,OAAO,UAAU,UAAU,OAAO,MAAM,SAAS;CACrD,OAAO,OAAO,KAAK;AACrB"}
|