@absolutejs/sync 2.1.0 → 2.2.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.
@@ -0,0 +1,90 @@
1
+ // @bun
2
+ var __defProp = Object.defineProperty;
3
+ var __returnValue = (v) => v;
4
+ function __exportSetter(name, newValue) {
5
+ this[name] = __returnValue.bind(null, newValue);
6
+ }
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, {
10
+ get: all[name],
11
+ enumerable: true,
12
+ configurable: true,
13
+ set: __exportSetter.bind(all, name)
14
+ });
15
+ };
16
+ var __require = import.meta.require;
17
+
18
+ // src/writeBehindCache.ts
19
+ var createWriteBehindCache = (options) => {
20
+ const debounceMs = options.debounceMs ?? 250;
21
+ const cache = new Map;
22
+ const timers = new Map;
23
+ const persist = async (key) => {
24
+ timers.delete(key);
25
+ const value = cache.get(key);
26
+ if (value === undefined) {
27
+ return;
28
+ }
29
+ try {
30
+ await options.persist(key, value);
31
+ if (options.evict?.(value, key)) {
32
+ cache.delete(key);
33
+ }
34
+ } catch (error) {
35
+ options.onPersistError?.(error, key);
36
+ }
37
+ };
38
+ const schedulePersist = (key) => {
39
+ if (timers.has(key)) {
40
+ return;
41
+ }
42
+ timers.set(key, setTimeout(() => {
43
+ persist(key);
44
+ }, debounceMs));
45
+ };
46
+ return {
47
+ get: async (key) => {
48
+ const cached = cache.get(key);
49
+ if (cached !== undefined) {
50
+ return cached;
51
+ }
52
+ const loaded = await options.load(key);
53
+ if (loaded !== undefined) {
54
+ cache.set(key, loaded);
55
+ }
56
+ return loaded;
57
+ },
58
+ peek: (key) => cache.get(key),
59
+ has: (key) => cache.has(key),
60
+ set: (key, value) => {
61
+ cache.set(key, value);
62
+ schedulePersist(key);
63
+ },
64
+ delete: async (key) => {
65
+ const timer = timers.get(key);
66
+ if (timer) {
67
+ clearTimeout(timer);
68
+ timers.delete(key);
69
+ }
70
+ cache.delete(key);
71
+ await options.remove?.(key);
72
+ },
73
+ keys: () => cache.keys(),
74
+ values: () => cache.values(),
75
+ size: () => cache.size,
76
+ flush: async () => {
77
+ for (const timer of timers.values()) {
78
+ clearTimeout(timer);
79
+ }
80
+ timers.clear();
81
+ await Promise.all([...cache.keys()].map((key) => persist(key)));
82
+ }
83
+ };
84
+ };
85
+ export {
86
+ createWriteBehindCache
87
+ };
88
+
89
+ //# debugId=4350F6DB7681B74A64756E2164756E21
90
+ //# sourceMappingURL=writeBehindCache.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/writeBehindCache.ts"],
4
+ "sourcesContent": [
5
+ "export type WriteBehindCacheOptions<K, V> = {\n\t/**\n\t * Read a value from the durable store on a cache miss. Called at most once per\n\t * key until the entry is evicted.\n\t */\n\tload: (key: K) => Promise<V | undefined> | V | undefined;\n\t/** Persist a value to the durable store. Runs in the background (write-behind). */\n\tpersist: (key: K, value: V) => Promise<void> | void;\n\t/** Remove a value from the durable store. */\n\tremove?: (key: K) => Promise<void> | void;\n\t/**\n\t * Coalesce writes: each key persists at most once per window. A burst of\n\t * `set`s collapses into a single durable write. Defaults to 250ms.\n\t */\n\tdebounceMs?: number;\n\t/**\n\t * After a key persists, return true to drop it from the in-memory cache so the\n\t * cache stays bounded to \"hot\" entries (e.g. evict terminal sessions). The next\n\t * `get` reloads it via `load`. Defaults to never evicting.\n\t */\n\tevict?: (value: V, key: K) => boolean;\n\t/**\n\t * Called when a background persist throws. The cache stays authoritative and the\n\t * key re-persists on its next `set`, so a transient durable-store blip does not\n\t * drop live state. Defaults to a no-op.\n\t */\n\tonPersistError?: (error: unknown, key: K) => void;\n};\n\nexport type WriteBehindCache<K, V> = {\n\t/** Cached value, or load-through from the durable store on a miss. */\n\tget: (key: K) => Promise<V | undefined>;\n\t/** Cached value only — synchronous, never touches the durable store. */\n\tpeek: (key: K) => V | undefined;\n\thas: (key: K) => boolean;\n\t/** Write to memory immediately and schedule a coalesced durable persist. */\n\tset: (key: K, value: V) => void;\n\t/** Drop from cache and the durable store. */\n\tdelete: (key: K) => Promise<void>;\n\tkeys: () => IterableIterator<K>;\n\tvalues: () => IterableIterator<V>;\n\tsize: () => number;\n\t/** Persist every pending key to the durable store now. Call on shutdown. */\n\tflush: () => Promise<void>;\n};\n\n/**\n * Wrap a durable store (Postgres, SQLite, Drizzle, Prisma, file, S3, an HTTP API …)\n * with an in-memory hot cache and write-behind persistence.\n *\n * Reads are served from memory; writes hit memory synchronously and are flushed to\n * the durable store in coalesced background batches. The durable store stays the\n * source of truth for history and cross-instance reads, while a latency-sensitive\n * hot path (a per-frame voice session, presence, cursors, game state) stays fast.\n *\n * This is the \"fast authoritative local state, durable persistence synced behind it\"\n * split a sync engine like Convex makes — without adopting a whole sync-engine\n * backend. Bring your own store via `load`/`persist`/`remove`.\n */\nexport const createWriteBehindCache = <K, V>(\n\toptions: WriteBehindCacheOptions<K, V>\n): WriteBehindCache<K, V> => {\n\tconst debounceMs = options.debounceMs ?? 250;\n\tconst cache = new Map<K, V>();\n\tconst timers = new Map<K, ReturnType<typeof setTimeout>>();\n\n\tconst persist = async (key: K) => {\n\t\ttimers.delete(key);\n\t\tconst value = cache.get(key);\n\t\tif (value === undefined) {\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tawait options.persist(key, value);\n\t\t\tif (options.evict?.(value, key)) {\n\t\t\t\tcache.delete(key);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\toptions.onPersistError?.(error, key);\n\t\t}\n\t};\n\n\tconst schedulePersist = (key: K) => {\n\t\tif (timers.has(key)) {\n\t\t\treturn;\n\t\t}\n\t\ttimers.set(\n\t\t\tkey,\n\t\t\tsetTimeout(() => {\n\t\t\t\tvoid persist(key);\n\t\t\t}, debounceMs)\n\t\t);\n\t};\n\n\treturn {\n\t\tget: async (key) => {\n\t\t\tconst cached = cache.get(key);\n\t\t\tif (cached !== undefined) {\n\t\t\t\treturn cached;\n\t\t\t}\n\t\t\tconst loaded = await options.load(key);\n\t\t\tif (loaded !== undefined) {\n\t\t\t\tcache.set(key, loaded);\n\t\t\t}\n\t\t\treturn loaded;\n\t\t},\n\t\tpeek: (key) => cache.get(key),\n\t\thas: (key) => cache.has(key),\n\t\tset: (key, value) => {\n\t\t\tcache.set(key, value);\n\t\t\tschedulePersist(key);\n\t\t},\n\t\tdelete: async (key) => {\n\t\t\tconst timer = timers.get(key);\n\t\t\tif (timer) {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\ttimers.delete(key);\n\t\t\t}\n\t\t\tcache.delete(key);\n\t\t\tawait options.remove?.(key);\n\t\t},\n\t\tkeys: () => cache.keys(),\n\t\tvalues: () => cache.values(),\n\t\tsize: () => cache.size,\n\t\tflush: async () => {\n\t\t\tfor (const timer of timers.values()) {\n\t\t\t\tclearTimeout(timer);\n\t\t\t}\n\t\t\ttimers.clear();\n\t\t\tawait Promise.all([...cache.keys()].map((key) => persist(key)));\n\t\t}\n\t};\n};\n"
6
+ ],
7
+ "mappings": ";;;;;;;;;;;;;;;;;;AA2DO,IAAM,yBAAyB,CACrC,YAC4B;AAAA,EAC5B,MAAM,aAAa,QAAQ,cAAc;AAAA,EACzC,MAAM,QAAQ,IAAI;AAAA,EAClB,MAAM,SAAS,IAAI;AAAA,EAEnB,MAAM,UAAU,OAAO,QAAW;AAAA,IACjC,OAAO,OAAO,GAAG;AAAA,IACjB,MAAM,QAAQ,MAAM,IAAI,GAAG;AAAA,IAC3B,IAAI,UAAU,WAAW;AAAA,MACxB;AAAA,IACD;AAAA,IACA,IAAI;AAAA,MACH,MAAM,QAAQ,QAAQ,KAAK,KAAK;AAAA,MAChC,IAAI,QAAQ,QAAQ,OAAO,GAAG,GAAG;AAAA,QAChC,MAAM,OAAO,GAAG;AAAA,MACjB;AAAA,MACC,OAAO,OAAO;AAAA,MACf,QAAQ,iBAAiB,OAAO,GAAG;AAAA;AAAA;AAAA,EAIrC,MAAM,kBAAkB,CAAC,QAAW;AAAA,IACnC,IAAI,OAAO,IAAI,GAAG,GAAG;AAAA,MACpB;AAAA,IACD;AAAA,IACA,OAAO,IACN,KACA,WAAW,MAAM;AAAA,MACX,QAAQ,GAAG;AAAA,OACd,UAAU,CACd;AAAA;AAAA,EAGD,OAAO;AAAA,IACN,KAAK,OAAO,QAAQ;AAAA,MACnB,MAAM,SAAS,MAAM,IAAI,GAAG;AAAA,MAC5B,IAAI,WAAW,WAAW;AAAA,QACzB,OAAO;AAAA,MACR;AAAA,MACA,MAAM,SAAS,MAAM,QAAQ,KAAK,GAAG;AAAA,MACrC,IAAI,WAAW,WAAW;AAAA,QACzB,MAAM,IAAI,KAAK,MAAM;AAAA,MACtB;AAAA,MACA,OAAO;AAAA;AAAA,IAER,MAAM,CAAC,QAAQ,MAAM,IAAI,GAAG;AAAA,IAC5B,KAAK,CAAC,QAAQ,MAAM,IAAI,GAAG;AAAA,IAC3B,KAAK,CAAC,KAAK,UAAU;AAAA,MACpB,MAAM,IAAI,KAAK,KAAK;AAAA,MACpB,gBAAgB,GAAG;AAAA;AAAA,IAEpB,QAAQ,OAAO,QAAQ;AAAA,MACtB,MAAM,QAAQ,OAAO,IAAI,GAAG;AAAA,MAC5B,IAAI,OAAO;AAAA,QACV,aAAa,KAAK;AAAA,QAClB,OAAO,OAAO,GAAG;AAAA,MAClB;AAAA,MACA,MAAM,OAAO,GAAG;AAAA,MAChB,MAAM,QAAQ,SAAS,GAAG;AAAA;AAAA,IAE3B,MAAM,MAAM,MAAM,KAAK;AAAA,IACvB,QAAQ,MAAM,MAAM,OAAO;AAAA,IAC3B,MAAM,MAAM,MAAM;AAAA,IAClB,OAAO,YAAY;AAAA,MAClB,WAAW,SAAS,OAAO,OAAO,GAAG;AAAA,QACpC,aAAa,KAAK;AAAA,MACnB;AAAA,MACA,OAAO,MAAM;AAAA,MACb,MAAM,QAAQ,IAAI,CAAC,GAAG,MAAM,KAAK,CAAC,EAAE,IAAI,CAAC,QAAQ,QAAQ,GAAG,CAAC,CAAC;AAAA;AAAA,EAEhE;AAAA;",
8
+ "debugId": "4350F6DB7681B74A64756E2164756E21",
9
+ "names": []
10
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/sync",
3
- "version": "2.1.0",
3
+ "version": "2.2.1",
4
4
  "description": "Lightweight reactive-push and write-behind-cache primitives for Elysia and the AbsoluteJS ecosystem — kill polling and keep a remote store off your hot path, without adopting a whole sync-engine backend.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -23,6 +23,11 @@
23
23
  "import": "./dist/scheduled.js",
24
24
  "default": "./dist/scheduled.js"
25
25
  },
26
+ "./write-behind-cache": {
27
+ "types": "./dist/writeBehindCache.d.ts",
28
+ "import": "./dist/writeBehindCache.js",
29
+ "default": "./dist/writeBehindCache.js"
30
+ },
26
31
  "./testing": {
27
32
  "types": "./dist/testing.d.ts",
28
33
  "import": "./dist/testing.js",