@voltro/cache 0.83.1 → 0.85.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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ConfigError } from 'effect';
1
+ import { Config } from 'effect';
2
2
  import { Context } from 'effect';
3
3
  import { Effect } from 'effect';
4
4
  import { Layer } from 'effect';
@@ -6,12 +6,42 @@ import { Option } from 'effect';
6
6
  import { Schema } from 'effect';
7
7
  import { Scope } from 'effect';
8
8
  import { Stream } from 'effect';
9
+ import { YieldableError } from 'effect/Cause';
9
10
 
10
11
  export declare class Cache extends Cache_base {
12
+ /** `Cache` over whatever `CacheStore` is provided. */
13
+ static readonly layer: Layer.Layer<Cache, never, CacheStore>;
11
14
  }
12
15
 
13
- declare const Cache_base: Effect.Service.Class<Cache, "@voltro/Cache", {
14
- readonly effect: Effect.Effect<{
16
+ declare const Cache_base: Context.ServiceClass<Cache, "@voltro/Cache", {
17
+ /** Read a typed value. `None` on miss/expiry. */
18
+ readonly get: <A = unknown>(key: string) => Effect.Effect<Option.Option<A>, CacheError>;
19
+ readonly set: <A>(key: string, value: A, options?: CacheSetOptions) => Effect.Effect<void, CacheError>;
20
+ readonly setWithTags: <A>(key: string, value: A, options: CacheSetWithTagsOptions) => Effect.Effect<void, CacheError>;
21
+ /** Write-through with the SAME storage shape `wrap` uses (incl. the
22
+ * SWR envelope) — so a value written here is read back identically
23
+ * by a later `wrap`. The dispatcher uses this to keep cached
24
+ * snapshots warm from its live recompute. */
25
+ readonly put: <A>(key: string, options: WrapOptions, value: A) => Effect.Effect<void, CacheError>;
26
+ readonly remove: (key: string) => Effect.Effect<boolean, CacheError>;
27
+ readonly has: (key: string) => Effect.Effect<boolean, CacheError>;
28
+ readonly clear: () => Effect.Effect<void, CacheError>;
29
+ readonly invalidateTag: (tag: string) => Effect.Effect<number, CacheError>;
30
+ readonly wrap: <A, E, R>(key: string, options: WrapOptions, compute: Effect.Effect<A, E, R>) => Effect.Effect<A, E | CacheError, R>;
31
+ /** A named, key-prefixed partition with its own default ttl/swr/tags.
32
+ * Keys become `<name>:<key>`; per-call options override the defaults;
33
+ * tags stay global (unprefixed) so bus invalidation still reaches
34
+ * partitioned entries. */
35
+ readonly partition: (name: string, defaults?: PartitionDefaults) => CachePartition;
36
+ /** Read the hit/miss counters + derived hit-rate (0–1). Drives the
37
+ * dashboard cache panel. */
38
+ readonly stats: () => Effect.Effect<{
39
+ hits: number;
40
+ misses: number;
41
+ hitRate: number;
42
+ }>;
43
+ }> & {
44
+ readonly make: Effect.Effect<{
15
45
  /** Read a typed value. `None` on miss/expiry. */
16
46
  readonly get: <A = unknown>(key: string) => Effect.Effect<Option.Option<A>, CacheError>;
17
47
  readonly set: <A>(key: string, value: A, options?: CacheSetOptions) => Effect.Effect<void, CacheError>;
@@ -39,22 +69,20 @@ declare const Cache_base: Effect.Service.Class<Cache, "@voltro/Cache", {
39
69
  hitRate: number;
40
70
  }>;
41
71
  }, never, CacheStore>;
42
- }>;
72
+ };
43
73
 
44
74
  export declare class CacheError extends CacheError_base {
45
75
  }
46
76
 
47
- declare const CacheError_base: Schema.TaggedErrorClass<CacheError, "CacheError", {
48
- readonly _tag: Schema.tag<"CacheError">;
49
- } & {
77
+ declare const CacheError_base: Schema.Class<CacheError, Schema.TaggedStruct<"CacheError", {
50
78
  /** Which op failed: 'get' | 'set' | 'setWithTags' | 'remove' | 'has' |
51
79
  * 'clear' | 'invalidateTag' | 'connect' | 'serialize' | 'deserialize'. */
52
- operation: typeof Schema.String;
80
+ readonly operation: Schema.String;
53
81
  /** The cache key (or tag) involved; '' for clear / connect. */
54
- key: typeof Schema.String;
82
+ readonly key: Schema.String;
55
83
  /** `String(originalError)` — round-trip-safe diagnostic form. */
56
- cause: typeof Schema.String;
57
- }>;
84
+ readonly cause: Schema.String;
85
+ }>, YieldableError>;
58
86
 
59
87
  export declare namespace CacheLayer {
60
88
  export {
@@ -103,7 +131,7 @@ export declare interface CacheSetWithTagsOptions extends CacheSetOptions {
103
131
  export declare class CacheStore extends CacheStore_base {
104
132
  }
105
133
 
106
- declare const CacheStore_base: Context.TagClass<CacheStore, "@voltro/CacheStore", CacheStoreShape>;
134
+ declare const CacheStore_base: Context.ServiceClass<CacheStore, "@voltro/CacheStore", CacheStoreShape>;
107
135
 
108
136
  export declare interface CacheStoreShape {
109
137
  /** Read a value. `None` on miss OR expiry (expired entries are lazily
@@ -160,21 +188,27 @@ export declare const cosineSimilarity: (a: ReadonlyArray<number>, b: ReadonlyArr
160
188
  export declare const depsForChange: (c: ChangeSignal) => ReadonlyArray<string>;
161
189
 
162
190
  export declare class InvalidationBus extends InvalidationBus_base {
191
+ /** The bus with its own scope: sinks live as long as the layer. */
192
+ static readonly layer: Layer.Layer<InvalidationBus, never, never>;
163
193
  }
164
194
 
165
- declare const InvalidationBus_base: Effect.Service.Class<InvalidationBus, "@voltro/InvalidationBus", {
166
- readonly scoped: Effect.Effect<{
195
+ declare const InvalidationBus_base: Context.ServiceClass<InvalidationBus, "@voltro/InvalidationBus", {
196
+ readonly publish: (table: string) => Effect.Effect<void>;
197
+ readonly changes: Stream.Stream<string, never, never>;
198
+ readonly addSink: (handle: (table: string) => Effect.Effect<unknown, unknown>) => Effect.Effect<void>;
199
+ }> & {
200
+ readonly make: Effect.Effect<{
167
201
  readonly publish: (table: string) => Effect.Effect<void>;
168
202
  readonly changes: Stream.Stream<string, never, never>;
169
203
  readonly addSink: (handle: (table: string) => Effect.Effect<unknown, unknown>) => Effect.Effect<void>;
170
204
  }, never, Scope.Scope>;
171
- }>;
205
+ };
172
206
 
173
207
  /**
174
208
  * `Layer<Cache>` whose backend is chosen from the environment. Provide
175
209
  * this in the app's layer stack so handlers can `yield* Cache`.
176
210
  */
177
- declare const layer: Layer.Layer<Cache, ConfigError.ConfigError | CacheError>;
211
+ declare const layer: Layer.Layer<Cache, Config.ConfigError | CacheError>;
178
212
 
179
213
  /** `Layer<CacheStore>` backed by an in-process `Ref`. */
180
214
  export declare const layerMemory: (options?: MemoryCacheOptions) => Layer.Layer<CacheStore>;
@@ -231,15 +265,17 @@ export declare const rowDep: (table: string, id: string) => string;
231
265
  * `CacheStore` in context — mirror of how `Cache` is built on `CacheStore`.
232
266
  *
233
267
  * ```ts
234
- * const layer = SemanticCache.Default.pipe(Layer.provide(CacheLayer.layer))
268
+ * const layer = SemanticCache.layer.pipe(Layer.provide(CacheLayer.layer))
235
269
  * ```
236
270
  */
237
271
  export declare class SemanticCache extends SemanticCache_base {
272
+ /** `SemanticCache` over whatever `CacheStore` is provided. */
273
+ static readonly layer: Layer.Layer<SemanticCache, never, CacheStore>;
238
274
  }
239
275
 
240
- declare const SemanticCache_base: Effect.Service.Class<SemanticCache, "@voltro/SemanticCache", {
241
- readonly effect: Effect.Effect<SemanticCacheShape, never, CacheStore>;
242
- }>;
276
+ declare const SemanticCache_base: Context.ServiceClass<SemanticCache, "@voltro/SemanticCache", SemanticCacheShape> & {
277
+ readonly make: Effect.Effect<SemanticCacheShape, never, CacheStore>;
278
+ };
243
279
 
244
280
  export declare interface SemanticCacheOptions {
245
281
  /** Default similarity threshold when a `lookup` omits one. Default 0.95. */
@@ -313,7 +349,7 @@ export declare interface SemanticPutOptions {
313
349
  * `Cache` and a `SemanticCache` end up over the same backend — see
314
350
  * `cli/src/cacheFacade.ts`).
315
351
  */
316
- declare const storeLayer: Layer.Layer<CacheStore, ConfigError.ConfigError | CacheError>;
352
+ declare const storeLayer: Layer.Layer<CacheStore, Config.ConfigError | CacheError>;
317
353
 
318
354
  /** The dependency tag for a whole table. Coarser than {@link rowDep}: it also
319
355
  * catches INSERTs (a new matching row the cached answer never read, so no
package/dist/index.js CHANGED
@@ -12,10 +12,12 @@ var v = Object.defineProperty, y = (e, t) => {
12
12
  operation: f.String,
13
13
  key: f.String,
14
14
  cause: f.String
15
- }) {}, x = class extends n.Tag("@voltro/CacheStore")() {}, S = {
15
+ }) {}, x = class extends n.Service()("@voltro/CacheStore") {}, S = {
16
16
  entries: a.empty(),
17
17
  tagIndex: a.empty(),
18
- seq: 0
18
+ seq: 0,
19
+ oldest: void 0,
20
+ newest: void 0
19
21
  }, C = (e, t, n) => {
20
22
  let r = e;
21
23
  for (let e of n) {
@@ -27,10 +29,28 @@ var v = Object.defineProperty, y = (e, t) => {
27
29
  return r;
28
30
  }, w = (e, t) => {
29
31
  let n = a.get(e.entries, t);
30
- return l.isNone(n) ? e : {
31
- entries: a.remove(e.entries, t),
32
+ if (l.isNone(n)) return e;
33
+ let { older: r, newer: i } = n.value, o = a.remove(e.entries, t);
34
+ if (r !== void 0) {
35
+ let e = a.get(o, r);
36
+ l.isSome(e) && (o = a.set(o, r, {
37
+ ...e.value,
38
+ newer: i
39
+ }));
40
+ }
41
+ if (i !== void 0) {
42
+ let e = a.get(o, i);
43
+ l.isSome(e) && (o = a.set(o, i, {
44
+ ...e.value,
45
+ older: r
46
+ }));
47
+ }
48
+ return {
49
+ entries: o,
32
50
  tagIndex: C(e.tagIndex, t, n.value.tags),
33
- seq: e.seq
51
+ seq: e.seq,
52
+ oldest: r === void 0 ? i : e.oldest,
53
+ newest: i === void 0 ? r : e.newest
34
54
  };
35
55
  }, T = (e, t, n) => {
36
56
  let r = a.get(e, t), i = l.isSome(r) ? o.add(r.value, n) : o.make(n);
@@ -38,30 +58,39 @@ var v = Object.defineProperty, y = (e, t) => {
38
58
  }, E = (e, t) => {
39
59
  let n = a.size(e.entries) - t;
40
60
  if (n <= 0) return e;
41
- let r = [];
42
- for (let [t, n] of e.entries) r.push([t, n.seq]);
43
- r.sort((e, t) => e[1] - t[1]);
44
- let i = e;
61
+ let r = e;
45
62
  for (let e = 0; e < n; e++) {
46
- let t = r[e];
47
- if (t === void 0) break;
48
- i = w(i, t[0]);
63
+ let e = r.oldest;
64
+ if (e === void 0) break;
65
+ r = w(r, e);
49
66
  }
50
- return i;
67
+ return r;
51
68
  }, D = (e, t, n, r, i, o) => {
52
- let s = w(e, t), c = s.seq + 1, l = s.tagIndex;
53
- for (let e of i) l = T(l, e, t);
54
- let u = {
55
- entries: a.set(s.entries, t, {
56
- value: n,
57
- expiresAt: r,
58
- tags: i,
59
- seq: c
60
- }),
61
- tagIndex: l,
62
- seq: c
69
+ let s = w(e, t), c = s.seq + 1, u = s.tagIndex;
70
+ for (let e of i) u = T(u, e, t);
71
+ let d = a.set(s.entries, t, {
72
+ value: n,
73
+ expiresAt: r,
74
+ tags: i,
75
+ seq: c,
76
+ older: s.newest,
77
+ newer: void 0
78
+ });
79
+ if (s.newest !== void 0) {
80
+ let e = a.get(d, s.newest);
81
+ l.isSome(e) && (d = a.set(d, s.newest, {
82
+ ...e.value,
83
+ newer: t
84
+ }));
85
+ }
86
+ let f = {
87
+ entries: d,
88
+ tagIndex: u,
89
+ seq: c,
90
+ oldest: s.oldest ?? t,
91
+ newest: t
63
92
  };
64
- return o === void 0 ? u : E(u, o);
93
+ return o === void 0 ? f : E(f, o);
65
94
  }, O = (e, t) => t === void 0 ? Infinity : e + t, k = (t) => i.gen(function* () {
66
95
  let n = yield* d.make(S), r = t.maxEntries, o = t.now === void 0 ? e.currentTimeMillis : i.sync(t.now);
67
96
  return {
@@ -139,7 +168,7 @@ var v = Object.defineProperty, y = (e, t) => {
139
168
  return await n.del([o(e)]), i;
140
169
  })))
141
170
  };
142
- }), P = (e) => s.scoped(x, N(e)), F = (e) => typeof e == "object" && !!e && e._voltroSwr === 1, I = class extends i.Service()("@voltro/Cache", { effect: i.gen(function* () {
171
+ }), P = (e) => s.effect(x, N(e)), F = (e) => typeof e == "object" && !!e && e._voltroSwr === 1, I = class t extends n.Service()("@voltro/Cache", { make: i.gen(function* () {
143
172
  let t = yield* x, n = yield* h.make(a.empty()), o = c.counter("voltro_cache_hits"), s = c.counter("voltro_cache_misses"), u = (n, r, a) => {
144
173
  let o = a.tags === void 0 ? {} : { tags: a.tags }, s = a.swrMs;
145
174
  if (s !== void 0) {
@@ -170,17 +199,17 @@ var v = Object.defineProperty, y = (e, t) => {
170
199
  }).pipe(i.flatMap((o) => {
171
200
  if (o.kind === "await") return r.await(o.deferred);
172
201
  let s = o.deferred;
173
- return t.pipe(i.onExit((t) => r.done(s, t).pipe(i.zipRight(h.update(n, (t) => a.remove(t, e))))));
202
+ return t.pipe(i.onExit((t) => r.done(s, t).pipe(i.andThen(h.update(n, (t) => a.remove(t, e))))));
174
203
  })), p = (n, r, a) => t.get(n).pipe(i.flatMap((t) => {
175
204
  if (l.isSome(t)) {
176
205
  let s = t.value;
177
- return F(s) ? c.increment(o).pipe(i.zipRight(e.currentTimeMillis), i.flatMap((e) => {
206
+ return F(s) ? c.update(o, 1).pipe(i.andThen(e.currentTimeMillis), i.flatMap((e) => {
178
207
  if (e < s.freshUntil) return i.succeed(s.v);
179
- let t = f(n, d(n, r, a)).pipe(i.catchAllCause(() => i.void));
180
- return i.forkDaemon(t).pipe(i.as(s.v));
181
- })) : c.increment(o).pipe(i.as(s));
208
+ let t = f(n, d(n, r, a)).pipe(i.catchCause(() => i.void));
209
+ return i.forkDetach(t).pipe(i.as(s.v));
210
+ })) : c.update(o, 1).pipe(i.as(s));
182
211
  }
183
- return c.increment(s).pipe(i.zipRight(f(n, d(n, r, a))));
212
+ return c.update(s, 1).pipe(i.andThen(f(n, d(n, r, a))));
184
213
  }));
185
214
  return {
186
215
  get: (e) => t.get(e),
@@ -220,7 +249,9 @@ var v = Object.defineProperty, y = (e, t) => {
220
249
  };
221
250
  }))
222
251
  };
223
- }) }) {}, L = (e, t) => `row:${e}:${t}`, R = (e) => `table:${e}`, z = (e) => {
252
+ }) }) {
253
+ static layer = s.effect(t, t.make);
254
+ }, L = (e, t) => `row:${e}:${t}`, R = (e) => `table:${e}`, z = (e) => {
224
255
  let t = R(e.table);
225
256
  return e.op === "insert" || e.id === void 0 ? [t] : [L(e.table, e.id), t];
226
257
  }, B = (e, t) => {
@@ -272,18 +303,33 @@ var v = Object.defineProperty, y = (e, t) => {
272
303
  };
273
304
  }, K = (e, t = {}) => i.gen(function* () {
274
305
  let t = yield* d.make(H), n = (n, r) => i.gen(function* () {
275
- let i = yield* d.get(t), a = [];
306
+ let i = yield* d.get(t), a = [], o = 0;
276
307
  for (let [, e] of i.entries) {
277
308
  let t = B(n, e.embedding);
278
- t >= r.threshold && a.push([e, t]);
309
+ t >= r.threshold && a.push([
310
+ e,
311
+ t,
312
+ o
313
+ ]), o++;
279
314
  }
280
- a.sort((e, t) => t[1] - e[1]);
281
- for (let [n] of a) {
282
- let r = yield* e.get(n.key);
283
- if (l.isSome(r)) return yield* d.update(t, (e) => ({
315
+ let s = (e, t) => e[1] > t[1] || e[1] === t[1] && e[2] < t[2], c = (e) => {
316
+ let t = a[e];
317
+ for (; e * 2 + 1 < a.length;) {
318
+ let n = e * 2 + 1;
319
+ if (n + 1 < a.length && s(a[n + 1], a[n]) && n++, !s(a[n], t)) break;
320
+ a[e] = a[n], e = n;
321
+ }
322
+ a[e] = t;
323
+ };
324
+ for (let e = Math.floor(a.length / 2) - 1; e >= 0; e--) c(e);
325
+ for (; a.length > 0;) {
326
+ let [n] = a[0], r = a.pop();
327
+ a.length > 0 && (a[0] = r, c(0));
328
+ let i = yield* e.get(n.key);
329
+ if (l.isSome(i)) return yield* d.update(t, (e) => ({
284
330
  ...e,
285
331
  hits: e.hits + 1
286
- })), l.some(r.value);
332
+ })), l.some(i.value);
287
333
  yield* d.update(t, (e) => W(e, n.key));
288
334
  }
289
335
  return yield* d.update(t, (e) => ({
@@ -319,38 +365,42 @@ var v = Object.defineProperty, y = (e, t) => {
319
365
  evict: o,
320
366
  onSourceChange: (e) => o(z(e)),
321
367
  onTableChange: (e) => o([R(e)]),
322
- clear: () => e.clear().pipe(i.zipRight(d.set(t, H))),
368
+ clear: () => e.clear().pipe(i.andThen(d.set(t, H))),
323
369
  stats: () => d.get(t).pipe(i.map((e) => ({
324
370
  hits: e.hits,
325
371
  misses: e.misses,
326
372
  size: a.size(e.entries)
327
373
  })))
328
374
  };
329
- }), q = class extends i.Service()("@voltro/SemanticCache", { effect: i.gen(function* () {
375
+ }), q = class e extends n.Service()("@voltro/SemanticCache", { make: i.gen(function* () {
330
376
  return yield* K(yield* x);
331
- }) }) {}, J = class extends i.Service()("@voltro/InvalidationBus", { scoped: i.gen(function* () {
377
+ }) }) {
378
+ static layer = s.effect(e, e.make);
379
+ }, J = class e extends n.Service()("@voltro/InvalidationBus", { make: i.gen(function* () {
332
380
  let e = yield* u.unbounded(), t = yield* i.scope;
333
381
  return {
334
382
  publish: (t) => u.publish(e, t).pipe(i.asVoid),
335
383
  changes: m.fromPubSub(e),
336
- addSink: (n) => p.extend(u.subscribe(e), t).pipe(i.flatMap((e) => m.fromQueue(e).pipe(m.runForEach((e) => n(e).pipe(i.ignore)), i.forkIn(t))), i.asVoid)
384
+ addSink: (n) => p.provide(u.subscribe(e), t).pipe(i.flatMap((e) => m.fromSubscription(e).pipe(m.runForEach((e) => n(e).pipe(i.ignore)), i.forkIn(t))), i.asVoid)
337
385
  };
338
- }) }) {}, Y = /* @__PURE__ */ y({
386
+ }) }) {
387
+ static layer = s.effect(e, e.make);
388
+ }, Y = /* @__PURE__ */ y({
339
389
  layer: () => $,
340
390
  storeLayer: () => Q
341
391
  }), X = i.gen(function* () {
342
- let e = yield* t.integer("CACHE_MAX_ENTRIES").pipe(t.option);
392
+ let e = yield* t.Int("CACHE_MAX_ENTRIES").pipe(t.option);
343
393
  return A(l.isSome(e) ? { maxEntries: e.value } : {});
344
394
  }), Z = i.gen(function* () {
345
- let e = yield* t.string("CACHE_REDIS_URL").pipe(t.orElse(() => t.string("REDIS_URL"))), n = yield* t.literal("resp", "http")("CACHE_REDIS_DRIVER").pipe(t.withDefault("resp")), r = yield* t.string("CACHE_REDIS_TOKEN").pipe(t.orElse(() => t.string("UPSTASH_REDIS_REST_TOKEN")), t.option), i = yield* t.string("CACHE_KEY_PREFIX").pipe(t.option);
395
+ let e = yield* t.String("CACHE_REDIS_URL").pipe(t.orElse(() => t.String("REDIS_URL"))), n = yield* t.Literals(["resp", "http"], "CACHE_REDIS_DRIVER").pipe(t.withDefault("resp")), r = yield* t.String("CACHE_REDIS_TOKEN").pipe(t.orElse(() => t.String("UPSTASH_REDIS_REST_TOKEN")), t.option), i = yield* t.String("CACHE_KEY_PREFIX").pipe(t.option);
346
396
  return P({
347
397
  driver: n,
348
398
  url: e,
349
399
  ...l.isSome(r) ? { token: r.value } : {},
350
400
  ...l.isSome(i) ? { keyPrefix: i.value } : {}
351
401
  });
352
- }), Q = s.unwrapEffect(i.gen(function* () {
353
- return (yield* t.literal("memory", "redis")("CACHE_BACKEND").pipe(t.withDefault("memory"))) === "redis" ? yield* Z : yield* X;
354
- })), $ = I.Default.pipe(s.provide(Q));
402
+ }), Q = s.unwrap(i.gen(function* () {
403
+ return (yield* t.Literals(["memory", "redis"], "CACHE_BACKEND").pipe(t.withDefault("memory"))) === "redis" ? yield* Z : yield* X;
404
+ })), $ = I.layer.pipe(s.provide(Q));
355
405
  //#endregion
356
406
  export { I as Cache, b as CacheError, Y as CacheLayer, x as CacheStore, J as InvalidationBus, q as SemanticCache, B as cosineSimilarity, z as depsForChange, A as layerMemory, P as layerRedis, K as makeSemanticCache, L as rowDep, R as tableDep };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/cache",
3
- "version": "0.83.1",
3
+ "version": "0.85.0",
4
4
  "description": "Effect-native cache primitive — memory + RESP (Redis/Valkey/KeyDB/Dragonfly/Upstash) backends, tag invalidation, single-flight wrap.",
5
5
  "keywords": [
6
6
  "voltro",
@@ -33,10 +33,10 @@
33
33
  "node": ">=24.0.0"
34
34
  },
35
35
  "dependencies": {
36
- "@voltro/kv": "0.83.1"
36
+ "@voltro/kv": "0.85.0"
37
37
  },
38
38
  "peerDependencies": {
39
- "effect": "^3.22.2"
39
+ "effect": "4.0.1"
40
40
  },
41
41
  "publishConfig": {
42
42
  "access": "public"