@c9up/echo 0.1.13 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CacheManager.d.ts.map +1 -1
- package/dist/CacheManager.js +20 -3
- package/dist/CacheManager.js.map +1 -1
- package/dist/EchoProvider.d.ts +1 -0
- package/dist/EchoProvider.d.ts.map +1 -1
- package/dist/EchoProvider.js +9 -1
- package/dist/EchoProvider.js.map +1 -1
- package/dist/StoreManager.d.ts +18 -0
- package/dist/StoreManager.d.ts.map +1 -1
- package/dist/StoreManager.js +18 -2
- package/dist/StoreManager.js.map +1 -1
- package/dist/augmentations.d.ts +29 -0
- package/dist/augmentations.d.ts.map +1 -0
- package/dist/augmentations.js +18 -0
- package/dist/augmentations.js.map +1 -0
- package/dist/drivers/MemoryDriver.d.ts +1 -1
- package/dist/drivers/MemoryDriver.d.ts.map +1 -1
- package/dist/drivers/MemoryDriver.js +41 -1
- package/dist/drivers/MemoryDriver.js.map +1 -1
- package/dist/drivers/TieredDriver.d.ts +17 -0
- package/dist/drivers/TieredDriver.d.ts.map +1 -1
- package/dist/drivers/TieredDriver.js +88 -17
- package/dist/drivers/TieredDriver.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/quasar.d.ts +17 -0
- package/dist/quasar.d.ts.map +1 -1
- package/dist/quasar.js +77 -0
- package/dist/quasar.js.map +1 -1
- package/dist/types.d.ts +12 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -3
- package/src/CacheManager.ts +29 -4
- package/src/EchoProvider.ts +10 -3
- package/src/StoreManager.ts +22 -3
- package/src/augmentations.ts +33 -0
- package/src/drivers/MemoryDriver.ts +39 -1
- package/src/drivers/TieredDriver.ts +94 -17
- package/src/index.ts +2 -0
- package/src/quasar.ts +101 -0
- package/src/types.ts +12 -1
|
@@ -30,15 +30,40 @@ async function writeEntry(driver, key, value, options) {
|
|
|
30
30
|
}
|
|
31
31
|
await driver.set(key, value, options.ttlSeconds);
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Run one tier's operation, capturing a rejection instead of letting it escape.
|
|
35
|
+
*
|
|
36
|
+
* The two tiers are attempted independently on purpose: awaiting L1 first meant
|
|
37
|
+
* a local driver that threw — a Redis L1 whose socket just dropped, one
|
|
38
|
+
* mid-shutdown — aborted the call before the SHARED tier was touched, so a
|
|
39
|
+
* `delete` left the copy every other instance reads. Upstream reaches the same
|
|
40
|
+
* end by never awaiting L1 at all (`this.l1?.set(...)` with no await, then
|
|
41
|
+
* `await this.l2?.set(...)`). The failure is still reported, after both tiers
|
|
42
|
+
* have had their turn.
|
|
43
|
+
*/
|
|
44
|
+
async function settle(run) {
|
|
45
|
+
try {
|
|
46
|
+
return { ok: true, value: await run() };
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
return { ok: false, error };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
33
52
|
export class TieredDriver {
|
|
34
53
|
#l1;
|
|
35
54
|
#l2;
|
|
36
55
|
#bus;
|
|
56
|
+
/** This tier, as a bus sender. */
|
|
57
|
+
#id = crypto.randomUUID();
|
|
37
58
|
constructor(options) {
|
|
38
59
|
this.#l1 = options.l1;
|
|
39
60
|
this.#l2 = options.l2;
|
|
40
61
|
this.#bus = options.bus;
|
|
41
62
|
this.#bus?.subscribe((message) => {
|
|
63
|
+
// Our own invalidation, come back round the bus. Acting on it would
|
|
64
|
+
// undo the write that sent it.
|
|
65
|
+
if (message.senderId === this.#id)
|
|
66
|
+
return;
|
|
42
67
|
// Peer invalidation: only the local L1 needs clearing (L2 is shared).
|
|
43
68
|
if (message.type === "clear") {
|
|
44
69
|
this.#invalidate("flush", () => this.#l1.flush());
|
|
@@ -109,20 +134,50 @@ export class TieredDriver {
|
|
|
109
134
|
await this.setEntry(key, value, { ttlSeconds });
|
|
110
135
|
}
|
|
111
136
|
async setEntry(key, value, options) {
|
|
112
|
-
await writeEntry(this.#l1, key, value, options);
|
|
113
|
-
await writeEntry(this.#l2, key, value, options);
|
|
114
|
-
|
|
137
|
+
const l1 = await settle(() => writeEntry(this.#l1, key, value, options));
|
|
138
|
+
const l2 = await settle(() => writeEntry(this.#l2, key, value, options));
|
|
139
|
+
// Peers are told only when the shared write landed. Telling them to drop
|
|
140
|
+
// their L1 for a value that never reached L2 sends every one of them to a
|
|
141
|
+
// tier that does not have it. Upstream gates the same publish on the same
|
|
142
|
+
// thing (`if (this.l2 && l2Success || !this.l2)`).
|
|
143
|
+
if (l2.ok) {
|
|
144
|
+
await this.#bus?.publish({
|
|
145
|
+
type: "delete",
|
|
146
|
+
keys: [key],
|
|
147
|
+
senderId: this.#id,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
if (!l1.ok)
|
|
151
|
+
throw l1.error;
|
|
152
|
+
if (!l2.ok)
|
|
153
|
+
throw l2.error;
|
|
115
154
|
}
|
|
116
155
|
async delete(key) {
|
|
117
|
-
const l1 = await this.#l1.delete(key);
|
|
118
|
-
const l2 = await this.#l2.delete(key);
|
|
119
|
-
|
|
120
|
-
|
|
156
|
+
const l1 = await settle(() => this.#l1.delete(key));
|
|
157
|
+
const l2 = await settle(() => this.#l2.delete(key));
|
|
158
|
+
if (l2.ok) {
|
|
159
|
+
await this.#bus?.publish({
|
|
160
|
+
type: "delete",
|
|
161
|
+
keys: [key],
|
|
162
|
+
senderId: this.#id,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
if (!l1.ok)
|
|
166
|
+
throw l1.error;
|
|
167
|
+
if (!l2.ok)
|
|
168
|
+
throw l2.error;
|
|
169
|
+
return l1.value || l2.value;
|
|
121
170
|
}
|
|
122
171
|
async flush() {
|
|
123
|
-
await this.#l1.flush();
|
|
124
|
-
await this.#l2.flush();
|
|
125
|
-
|
|
172
|
+
const l1 = await settle(() => this.#l1.flush());
|
|
173
|
+
const l2 = await settle(() => this.#l2.flush());
|
|
174
|
+
if (l2.ok) {
|
|
175
|
+
await this.#bus?.publish({ type: "clear", keys: [], senderId: this.#id });
|
|
176
|
+
}
|
|
177
|
+
if (!l1.ok)
|
|
178
|
+
throw l1.error;
|
|
179
|
+
if (!l2.ok)
|
|
180
|
+
throw l2.error;
|
|
126
181
|
}
|
|
127
182
|
async has(key) {
|
|
128
183
|
return (await this.get(key)) !== null;
|
|
@@ -136,11 +191,17 @@ export class TieredDriver {
|
|
|
136
191
|
if (!isTaggable(l1) || !isTaggable(l2)) {
|
|
137
192
|
throw new Error("Echo: TieredDriver.deleteByTag requires both tiers to be taggable");
|
|
138
193
|
}
|
|
139
|
-
await l1.deleteByTag(tags);
|
|
140
|
-
await l2.deleteByTag(tags);
|
|
194
|
+
const local = await settle(() => l1.deleteByTag(tags));
|
|
195
|
+
const shared = await settle(() => l2.deleteByTag(tags));
|
|
141
196
|
// Peers can't map tags → keys locally; broadcast a clear so their L1 drops
|
|
142
197
|
// any tagged copies (conservative but correct).
|
|
143
|
-
|
|
198
|
+
if (shared.ok) {
|
|
199
|
+
await this.#bus?.publish({ type: "clear", keys: [], senderId: this.#id });
|
|
200
|
+
}
|
|
201
|
+
if (!local.ok)
|
|
202
|
+
throw local.error;
|
|
203
|
+
if (!shared.ok)
|
|
204
|
+
throw shared.error;
|
|
144
205
|
}
|
|
145
206
|
/** @deprecated alias of {@link deleteByTag}. */
|
|
146
207
|
async flushTags(tags) {
|
|
@@ -148,13 +209,23 @@ export class TieredDriver {
|
|
|
148
209
|
}
|
|
149
210
|
/** Prune both layers (bentocache `prune`). */
|
|
150
211
|
async prune() {
|
|
151
|
-
await this.#l1.prune?.();
|
|
152
|
-
await this.#l2.prune?.();
|
|
212
|
+
const l1 = await settle(async () => this.#l1.prune?.());
|
|
213
|
+
const l2 = await settle(async () => this.#l2.prune?.());
|
|
214
|
+
if (!l1.ok)
|
|
215
|
+
throw l1.error;
|
|
216
|
+
if (!l2.ok)
|
|
217
|
+
throw l2.error;
|
|
153
218
|
}
|
|
154
219
|
/** Release both layers (bentocache `disconnect`). */
|
|
155
220
|
async disconnect() {
|
|
156
|
-
|
|
157
|
-
|
|
221
|
+
// Both are released even when the first refuses: a tier left connected
|
|
222
|
+
// because its neighbour threw is a socket nobody closes.
|
|
223
|
+
const l1 = await settle(async () => this.#l1.disconnect?.());
|
|
224
|
+
const l2 = await settle(async () => this.#l2.disconnect?.());
|
|
225
|
+
if (!l1.ok)
|
|
226
|
+
throw l1.error;
|
|
227
|
+
if (!l2.ok)
|
|
228
|
+
throw l2.error;
|
|
158
229
|
}
|
|
159
230
|
}
|
|
160
231
|
//# sourceMappingURL=TieredDriver.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TieredDriver.js","sourceRoot":"","sources":["../../src/drivers/TieredDriver.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"TieredDriver.js","sourceRoot":"","sources":["../../src/drivers/TieredDriver.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,YAAY,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,YAAY,EACX,UAAU,EACV,QAAQ,EACR,mBAAmB,GACnB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACjE,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EACN,iBAAiB,EACjB,KAAK,aAAa,EAClB,OAAO,EACP,KAAK,gBAAgB,EACrB,KAAK,EACL,KAAK,WAAW,EAChB,KAAK,GACL,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACX,YAAY,EACZ,UAAU,EACV,aAAa,EACb,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,OAAO,EACP,UAAU,EACV,sBAAsB,EACtB,eAAe,EACf,UAAU,EACV,UAAU,EACV,cAAc,GACd,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,kBAAkB,GAAG,gBAAgB,EAC3E,MAAM,EAAE,CAAC,GACP,CAAC,CAEH"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,oBAAoB,CAAC;AAE5B,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,YAAY,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,YAAY,EACX,UAAU,EACV,QAAQ,EACR,mBAAmB,GACnB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACjE,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EACN,iBAAiB,EACjB,KAAK,aAAa,EAClB,OAAO,EACP,KAAK,gBAAgB,EACrB,KAAK,EACL,KAAK,WAAW,EAChB,KAAK,GACL,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACX,YAAY,EACZ,UAAU,EACV,aAAa,EACb,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,OAAO,EACP,UAAU,EACV,sBAAsB,EACtB,eAAe,EACf,UAAU,EACV,UAAU,EACV,cAAc,GACd,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,kBAAkB,GAAG,gBAAgB,EAC3E,MAAM,EAAE,CAAC,GACP,CAAC,CAEH"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,oBAAoB,CAAC;AAG5B,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAMvD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEjE,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EACN,iBAAiB,EAEjB,OAAO,EAEP,KAAK,EAEL,KAAK,GACL,MAAM,mBAAmB,CAAC;AAsB3B;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAC3B,MAAS;IAET,OAAO,MAAM,CAAC;AACf,CAAC"}
|
package/dist/quasar.d.ts
CHANGED
|
@@ -10,9 +10,26 @@
|
|
|
10
10
|
* duck-types its host framework.
|
|
11
11
|
*/
|
|
12
12
|
import type { RedisClient } from "./drivers/RedisDriver.js";
|
|
13
|
+
import type { CacheBus } from "./drivers/TieredDriver.js";
|
|
13
14
|
/**
|
|
14
15
|
* A resolver for `drivers.redis({ connection })` — quasar is loaded on the
|
|
15
16
|
* first cache command, not at config time.
|
|
16
17
|
*/
|
|
17
18
|
export declare function quasarConnection(name?: string): () => Promise<RedisClient>;
|
|
19
|
+
/**
|
|
20
|
+
* Redis pub/sub as a cache bus, for keeping each instance's L1 in step.
|
|
21
|
+
*
|
|
22
|
+
* A two-layer store without one is wrong the moment a second instance exists:
|
|
23
|
+
* each process keeps serving its own L1 copy of a key another process has
|
|
24
|
+
* already deleted, and nothing ever tells it. That is what the bus is for, and
|
|
25
|
+
* it is why `useBus` sits next to `useL2Layer` in the generated config.
|
|
26
|
+
*
|
|
27
|
+
* Quasar opens the subscriber socket lazily and on its own connection — Redis
|
|
28
|
+
* puts a subscribed client into a mode where it accepts nothing else, so a
|
|
29
|
+
* connection that both publishes and listens needs two.
|
|
30
|
+
*/
|
|
31
|
+
export declare function quasarBus(options?: {
|
|
32
|
+
connection?: string;
|
|
33
|
+
channel?: string;
|
|
34
|
+
}): CacheBus;
|
|
18
35
|
//# sourceMappingURL=quasar.d.ts.map
|
package/dist/quasar.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quasar.d.ts","sourceRoot":"","sources":["../src/quasar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"quasar.d.ts","sourceRoot":"","sources":["../src/quasar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,KAAK,EAAc,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAyCtE;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,CA+B1E;AAuCD;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,OAAO,CAAC,EAAE;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,QAAQ,CA8CX"}
|
package/dist/quasar.js
CHANGED
|
@@ -66,4 +66,81 @@ export function quasarConnection(name) {
|
|
|
66
66
|
return connection;
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
|
+
function isPubSubSource(value) {
|
|
70
|
+
return (typeof value === "object" &&
|
|
71
|
+
value !== null &&
|
|
72
|
+
typeof Reflect.get(value, "publish") === "function" &&
|
|
73
|
+
typeof Reflect.get(value, "subscribe") === "function");
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Resolve quasar's manager, loading it on first use.
|
|
77
|
+
*
|
|
78
|
+
* Shared by the connection resolver and the bus: echo declares quasar an
|
|
79
|
+
* optional peer and never imports it statically, so an application that caches
|
|
80
|
+
* in memory neither installs it nor pays for it.
|
|
81
|
+
*/
|
|
82
|
+
async function loadQuasar(context) {
|
|
83
|
+
const specifier = "@c9up/quasar/services/main";
|
|
84
|
+
try {
|
|
85
|
+
const loaded = await import(/* @vite-ignore */ specifier);
|
|
86
|
+
return isConnectionSource(loaded) || isPubSubSource(loaded)
|
|
87
|
+
? loaded
|
|
88
|
+
: Reflect.get(Object(loaded), "default");
|
|
89
|
+
}
|
|
90
|
+
catch (cause) {
|
|
91
|
+
throw new Error(`Echo: ${context}, but @c9up/quasar is not installed.\n pnpm add @c9up/quasar`, { cause });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Redis pub/sub as a cache bus, for keeping each instance's L1 in step.
|
|
96
|
+
*
|
|
97
|
+
* A two-layer store without one is wrong the moment a second instance exists:
|
|
98
|
+
* each process keeps serving its own L1 copy of a key another process has
|
|
99
|
+
* already deleted, and nothing ever tells it. That is what the bus is for, and
|
|
100
|
+
* it is why `useBus` sits next to `useL2Layer` in the generated config.
|
|
101
|
+
*
|
|
102
|
+
* Quasar opens the subscriber socket lazily and on its own connection — Redis
|
|
103
|
+
* puts a subscribed client into a mode where it accepts nothing else, so a
|
|
104
|
+
* connection that both publishes and listens needs two.
|
|
105
|
+
*/
|
|
106
|
+
export function quasarBus(options) {
|
|
107
|
+
const channel = options?.channel ?? "echo::invalidate";
|
|
108
|
+
let ready;
|
|
109
|
+
const manager = () => {
|
|
110
|
+
ready ??= loadQuasar(`the cache bus asks for the "${options?.connection ?? "default"}" quasar connection`).then((loaded) => {
|
|
111
|
+
const source = isConnectionSource(loaded)
|
|
112
|
+
? loaded.connection(options?.connection)
|
|
113
|
+
: loaded;
|
|
114
|
+
if (!isPubSubSource(source)) {
|
|
115
|
+
throw new Error("Echo: the quasar connection does not expose publish()/subscribe()");
|
|
116
|
+
}
|
|
117
|
+
return source;
|
|
118
|
+
});
|
|
119
|
+
return ready;
|
|
120
|
+
};
|
|
121
|
+
return {
|
|
122
|
+
async publish(message) {
|
|
123
|
+
const source = await manager();
|
|
124
|
+
await source.publish(channel, JSON.stringify(message));
|
|
125
|
+
},
|
|
126
|
+
subscribe(handler) {
|
|
127
|
+
// Not awaited: `subscribe` is synchronous in the bus contract, and the
|
|
128
|
+
// socket opens on quasar's own schedule. A failure to reach Redis must
|
|
129
|
+
// not take down the store — a bus that is down costs staleness, and
|
|
130
|
+
// throwing here would cost every cache read.
|
|
131
|
+
void manager()
|
|
132
|
+
.then((source) => source.subscribe(channel, (raw) => {
|
|
133
|
+
try {
|
|
134
|
+
handler(JSON.parse(raw));
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
/* a malformed frame is not a reason to stop listening */
|
|
138
|
+
}
|
|
139
|
+
}))
|
|
140
|
+
.catch(() => {
|
|
141
|
+
/* reported by the first publish, which does surface its error */
|
|
142
|
+
});
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
}
|
|
69
146
|
//# sourceMappingURL=quasar.js.map
|
package/dist/quasar.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quasar.js","sourceRoot":"","sources":["../src/quasar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;
|
|
1
|
+
{"version":3,"file":"quasar.js","sourceRoot":"","sources":["../src/quasar.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -152,6 +152,17 @@ export interface CacheEventMap {
|
|
|
152
152
|
* (ream's emitter, Node's EventEmitter, mitt, …). echo never imports one.
|
|
153
153
|
*/
|
|
154
154
|
export interface CacheEmitter {
|
|
155
|
-
|
|
155
|
+
/**
|
|
156
|
+
* NAMED DEVIATION from `@adonisjs/events`, which declares
|
|
157
|
+
* `emit(): Promise<void>` on its own class. This is a DUCK-TYPE of an
|
|
158
|
+
* emitter echo does not own, so it must also accept a synchronous one — a
|
|
159
|
+
* Node `EventEmitter` returns `boolean`, for instance.
|
|
160
|
+
*
|
|
161
|
+
* `unknown` rather than `void`, because `void` ACCEPTS a
|
|
162
|
+
* promise-returning function and then reads as if there were nothing to
|
|
163
|
+
* handle: that is what hid an Adonis emitter's rejection here. Same choice
|
|
164
|
+
* warden's `AuthManager` already made.
|
|
165
|
+
*/
|
|
166
|
+
emit(event: string, payload: unknown): unknown;
|
|
156
167
|
}
|
|
157
168
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c9up/echo",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "Cache — pluggable cache contract with memory + Redis drivers for the Ream framework",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -36,8 +36,10 @@
|
|
|
36
36
|
"@biomejs/biome": "^2.4.10",
|
|
37
37
|
"@c9up/quasar": "^0.1.0",
|
|
38
38
|
"@types/node": "^22.19.15",
|
|
39
|
+
"@vitest/coverage-v8": "^4.1.2",
|
|
39
40
|
"typescript": "^6.0.2",
|
|
40
|
-
"vitest": "^4.1.2"
|
|
41
|
+
"vitest": "^4.1.2",
|
|
42
|
+
"@c9up/ream": "^0.2.0"
|
|
41
43
|
},
|
|
42
44
|
"engines": {
|
|
43
45
|
"node": ">=22.0.0"
|
|
@@ -66,7 +68,7 @@
|
|
|
66
68
|
"scripts": {
|
|
67
69
|
"build": "tsc -p tsconfig.build.json",
|
|
68
70
|
"test": "vitest run",
|
|
69
|
-
"lint": "biome check src/",
|
|
71
|
+
"lint": "biome check src/ tests/",
|
|
70
72
|
"test:coverage": "vitest run --coverage",
|
|
71
73
|
"typecheck": "tsc --noEmit"
|
|
72
74
|
}
|
package/src/CacheManager.ts
CHANGED
|
@@ -52,15 +52,25 @@ function resolveMs(duration: Duration | undefined): number | undefined {
|
|
|
52
52
|
|
|
53
53
|
const TIMEOUT: unique symbol = Symbol("echo.timeout");
|
|
54
54
|
|
|
55
|
+
/** A timer handle that can be unreferenced — Node's, not the browser's. */
|
|
56
|
+
function hasUnref(timer: unknown): timer is { unref(): void } {
|
|
57
|
+
return (
|
|
58
|
+
typeof timer === "object" &&
|
|
59
|
+
timer !== null &&
|
|
60
|
+
typeof Reflect.get(timer, "unref") === "function"
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
55
64
|
function withTimeout<T>(
|
|
56
65
|
promise: Promise<T>,
|
|
57
66
|
ms: number,
|
|
58
67
|
): Promise<T | typeof TIMEOUT> {
|
|
59
68
|
return new Promise((resolve, reject) => {
|
|
60
69
|
const timer = setTimeout(() => resolve(TIMEOUT), ms);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
70
|
+
// A soft timeout is for the request in flight, not a reason for the
|
|
71
|
+
// process to stay up. Checked rather than asserted: the browser's
|
|
72
|
+
// `setTimeout` answers a number, which has no `unref`.
|
|
73
|
+
if (hasUnref(timer)) timer.unref();
|
|
64
74
|
promise.then(
|
|
65
75
|
(value) => {
|
|
66
76
|
clearTimeout(timer);
|
|
@@ -156,7 +166,22 @@ export class CacheManager {
|
|
|
156
166
|
event: E,
|
|
157
167
|
payload: CacheEventMap[E],
|
|
158
168
|
): void {
|
|
159
|
-
|
|
169
|
+
// Not just called: an Adonis emitter's `emit` is `async` and REJECTS when
|
|
170
|
+
// a listener throws and the application registered no error handler
|
|
171
|
+
// (`@adonisjs/events`: `if (this.#errorHandler) … else throw error`).
|
|
172
|
+
// Nobody awaits a cache event, so that rejection had nowhere to go and
|
|
173
|
+
// ended the process over a metrics listener. The interface said `void`,
|
|
174
|
+
// which is why nothing looked wrong — TypeScript accepts a
|
|
175
|
+
// promise-returning function for a `void` return.
|
|
176
|
+
void (async () => this.#emitter?.emit(event, payload))().catch(
|
|
177
|
+
(error: unknown) => {
|
|
178
|
+
process.stderr.write(
|
|
179
|
+
`[echo] '${String(event)}' listener failed: ${
|
|
180
|
+
error instanceof Error ? error.message : String(error)
|
|
181
|
+
}\n`,
|
|
182
|
+
);
|
|
183
|
+
},
|
|
184
|
+
);
|
|
160
185
|
for (const listener of this.#listeners[event]) {
|
|
161
186
|
try {
|
|
162
187
|
listener(payload);
|
package/src/EchoProvider.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "./augmentations.js";
|
|
1
2
|
import { type CacheConfig, CacheManager } from "./CacheManager.js";
|
|
2
3
|
import { MemoryDriver } from "./drivers/MemoryDriver.js";
|
|
3
4
|
import { CacheStoreManager, type MultiStoreConfig } from "./StoreManager.js";
|
|
@@ -104,9 +105,15 @@ export default class EchoProvider {
|
|
|
104
105
|
}
|
|
105
106
|
return new CacheManager(new MemoryDriver(), { ...config, emitter });
|
|
106
107
|
});
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
// Namespaced by the package that owns it, the way upstream namespaces
|
|
109
|
+
// `lucid.db`, `auth.manager` and `drive.manager` by theirs. The bare
|
|
110
|
+
// token stays bound beside it: it is what every existing
|
|
111
|
+
// `container.make(...)` asks for, and a token is not worth breaking an
|
|
112
|
+
// application over.
|
|
113
|
+
const cache = (): Promise<CacheManager> =>
|
|
114
|
+
this.app.container.resolve<CacheManager>(CacheManager);
|
|
115
|
+
this.app.container.singleton("echo.cache", cache);
|
|
116
|
+
this.app.container.singleton("cache", cache);
|
|
110
117
|
}
|
|
111
118
|
|
|
112
119
|
/** The cache THIS provider booted — not whatever the module singleton holds. */
|
package/src/StoreManager.ts
CHANGED
|
@@ -20,7 +20,7 @@ import { MemoryDriver } from "./drivers/MemoryDriver.js";
|
|
|
20
20
|
import { type RedisClient, RedisDriver } from "./drivers/RedisDriver.js";
|
|
21
21
|
import { type CacheBus, TieredDriver } from "./drivers/TieredDriver.js";
|
|
22
22
|
import type { Duration } from "./duration.js";
|
|
23
|
-
import { quasarConnection } from "./quasar.js";
|
|
23
|
+
import { quasarBus, quasarConnection } from "./quasar.js";
|
|
24
24
|
import type { CacheDriver, CacheEmitter, CacheEventMap } from "./types.js";
|
|
25
25
|
|
|
26
26
|
/** A lazily-instantiated driver (built once per store, on first `use`). */
|
|
@@ -127,8 +127,16 @@ function entryOf(config: StoreConfig | Store): StoreConfig {
|
|
|
127
127
|
|
|
128
128
|
/** Driver factory helpers (bento `drivers.memory` / `drivers.redis`). */
|
|
129
129
|
export const drivers = {
|
|
130
|
-
|
|
131
|
-
|
|
130
|
+
/**
|
|
131
|
+
* `maxItems` bounds the store by COUNT, which a TTL cannot: a cache keyed by
|
|
132
|
+
* a user id or a search term grows until the process runs out of memory,
|
|
133
|
+
* however short each entry's life. Omit it for no ceiling.
|
|
134
|
+
*/
|
|
135
|
+
memory(options?: {
|
|
136
|
+
sweepIntervalMs?: number;
|
|
137
|
+
maxItems?: number;
|
|
138
|
+
}): DriverFactory {
|
|
139
|
+
return () => new MemoryDriver(options?.sweepIntervalMs, options?.maxItems);
|
|
132
140
|
},
|
|
133
141
|
/**
|
|
134
142
|
* Either hand it a client, or name a quasar connection — the AdonisJS shape,
|
|
@@ -147,6 +155,17 @@ export const drivers = {
|
|
|
147
155
|
return () =>
|
|
148
156
|
new RedisDriver(quasarConnection(options.connection), options.prefix);
|
|
149
157
|
},
|
|
158
|
+
/**
|
|
159
|
+
* Redis pub/sub as the bus that keeps each instance's L1 in step.
|
|
160
|
+
*
|
|
161
|
+
* Named alongside `redis` and taking the same `connection`, because it is
|
|
162
|
+
* the same deployment decision: a two-layer store without a bus is wrong as
|
|
163
|
+
* soon as a second instance exists, each process serving its own L1 copy of
|
|
164
|
+
* a key another has already deleted.
|
|
165
|
+
*/
|
|
166
|
+
redisBus(options?: { connection?: string; channel?: string }): CacheBus {
|
|
167
|
+
return quasarBus(options);
|
|
168
|
+
},
|
|
150
169
|
tiered(options: {
|
|
151
170
|
l1: DriverFactory;
|
|
152
171
|
l2: DriverFactory;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Teach ream's `ContainerBindings` what `container.make(...)` returns for the
|
|
3
|
+
* tokens echo binds.
|
|
4
|
+
*
|
|
5
|
+
* ream declares that interface open on purpose: it registers its own entries
|
|
6
|
+
* and expects each package to contribute the ones it owns. Nothing filled
|
|
7
|
+
* these in, so resolving by the string token answered `unknown` and every call
|
|
8
|
+
* site had to assert a type it could not prove.
|
|
9
|
+
*
|
|
10
|
+
* Loaded from the package barrel, so importing Echo anywhere in the
|
|
11
|
+
* application is enough — nobody writes a `declare module` of their own.
|
|
12
|
+
*
|
|
13
|
+
* Type-only, and ream stays an OPTIONAL peer: nothing here reaches a runtime
|
|
14
|
+
* import, and a `declare module` for a specifier that does not resolve is
|
|
15
|
+
* simply inert.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// Referenced so the augmentation below resolves the module it augments.
|
|
19
|
+
import type {} from "@c9up/ream/types";
|
|
20
|
+
|
|
21
|
+
import type { CacheManager } from "./CacheManager.js";
|
|
22
|
+
|
|
23
|
+
declare module "@c9up/ream/types" {
|
|
24
|
+
interface ContainerBindings {
|
|
25
|
+
/** The cache manager, bound by `EchoProvider`. */
|
|
26
|
+
"echo.cache": CacheManager;
|
|
27
|
+
/**
|
|
28
|
+
* The same binding under the name it had before the token carried its
|
|
29
|
+
* package. Kept bound so an existing `container.make(...)` resolves.
|
|
30
|
+
*/
|
|
31
|
+
cache: CacheManager;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -19,8 +19,19 @@ export class MemoryDriver implements TaggableDriver {
|
|
|
19
19
|
#store: Map<string, StoredEntry> = new Map();
|
|
20
20
|
#tagIndex: Map<string, Set<string>> = new Map();
|
|
21
21
|
#sweepInterval: ReturnType<typeof setInterval>;
|
|
22
|
+
/**
|
|
23
|
+
* How many entries this driver holds before it starts dropping the oldest.
|
|
24
|
+
*
|
|
25
|
+
* Eviction was by TTL alone, which bounds how long an entry lives and not
|
|
26
|
+
* how many there are: a cache keyed by anything unbounded — a user id, a
|
|
27
|
+
* search term — grew until the process ran out of memory, and only a key
|
|
28
|
+
* that was written ever left. `0` keeps the old behaviour for a caller that
|
|
29
|
+
* genuinely wants no ceiling.
|
|
30
|
+
*/
|
|
31
|
+
readonly #maxItems: number;
|
|
22
32
|
|
|
23
|
-
constructor(sweepIntervalMs = 60_000) {
|
|
33
|
+
constructor(sweepIntervalMs = 60_000, maxItems = 0) {
|
|
34
|
+
this.#maxItems = maxItems > 0 ? maxItems : 0;
|
|
24
35
|
this.#sweepInterval = setInterval(() => this.#sweep(), sweepIntervalMs);
|
|
25
36
|
if (
|
|
26
37
|
typeof this.#sweepInterval === "object" &&
|
|
@@ -30,6 +41,28 @@ export class MemoryDriver implements TaggableDriver {
|
|
|
30
41
|
}
|
|
31
42
|
}
|
|
32
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Drop the oldest entries until the store fits under its ceiling.
|
|
46
|
+
*
|
|
47
|
+
* Expired ones go first — evicting a live entry while a dead one sits in
|
|
48
|
+
* the map would throw away a value someone still wants. Only if that is not
|
|
49
|
+
* enough does it take the least recently written.
|
|
50
|
+
*/
|
|
51
|
+
#enforceCeiling(): void {
|
|
52
|
+
if (this.#maxItems === 0 || this.#store.size <= this.#maxItems) return;
|
|
53
|
+
const now = Date.now();
|
|
54
|
+
for (const [key, entry] of this.#store) {
|
|
55
|
+
if (this.#store.size <= this.#maxItems) return;
|
|
56
|
+
if (entry.staleUntil > 0 && entry.staleUntil < now) {
|
|
57
|
+
this.#evict(key, entry);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (const [key, entry] of this.#store) {
|
|
61
|
+
if (this.#store.size <= this.#maxItems) return;
|
|
62
|
+
this.#evict(key, entry);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
33
66
|
/** Evict every entry past its grace window. */
|
|
34
67
|
#sweep(): void {
|
|
35
68
|
const now = Date.now();
|
|
@@ -130,7 +163,12 @@ export class MemoryDriver implements TaggableDriver {
|
|
|
130
163
|
if (prev !== undefined) {
|
|
131
164
|
for (const t of prev.tags) this.#tagIndex.get(t)?.delete(key);
|
|
132
165
|
}
|
|
166
|
+
// Re-inserting moves the key to the end of the Map's iteration order, so
|
|
167
|
+
// the first entry is always the least recently WRITTEN — which is the
|
|
168
|
+
// order this evicts in.
|
|
169
|
+
this.#store.delete(key);
|
|
133
170
|
this.#store.set(key, { value, expiresAt, staleUntil, tags });
|
|
171
|
+
this.#enforceCeiling();
|
|
134
172
|
for (const tag of tags) {
|
|
135
173
|
let set = this.#tagIndex.get(tag);
|
|
136
174
|
if (!set) {
|