43 lines
1.7 KiB
Markdown
43 lines
1.7 KiB
Markdown
# Atomsync
|
|
|
|
Clojure/ClojureScript offline-first synced atom library backed by IndexedDB (CLJS) or SQLite (CLJ).
|
|
|
|
## Async: use promesa, not core.async
|
|
|
|
This project uses **promesa** (`funcool/promesa`) for all async operations. Do not introduce `core.async`.
|
|
|
|
- **Store protocol** (`store.cljc`): all methods return promesa promises
|
|
- Sync operations (e.g. MemoryStore): `(p/resolved val)`
|
|
- Async operations (e.g. IDBStore): `(p/create (fn [resolve reject] ...))`
|
|
- **HTTP / sync** (`sync.cljc`): CLJ uses `p/vthread`, CLJS returns native JS Promise chains
|
|
- **Core** (`core.cljc`): `p/let` for sequential async, `p/all` for parallel
|
|
- **Sync loop**: platform timers (`setInterval` / `ScheduledExecutorService`), not go-loop
|
|
- **Push coalescing**: debounced with 50ms timer + `pushing?` atom guard, not channels
|
|
|
|
### Why not core.async
|
|
|
|
- Channels-as-one-shot-promises add ceremony (`chan` + `put!` + `close!`) for what is conceptually a return value
|
|
- promesa maps to native primitives: JS Promises on CLJS, CompletableFuture on CLJ
|
|
- `p/let` reads cleaner than `go` + `<!` for sequential async
|
|
- Timer-based sync loop is simpler than `go-loop` + `alts!` for this use case
|
|
|
|
## Project structure
|
|
|
|
```
|
|
src/atomsync/
|
|
store.cljc — PStore protocol (promises)
|
|
store/memory.cljc — in-memory store (testing/JVM)
|
|
store/idb.cljs — IndexedDB store (browser)
|
|
sync.cljc — HTTP pull/push, SSE, connectivity
|
|
core.cljc — SyncedAtom, sync loop, public API
|
|
transit.cljc — Transit encoding/decoding
|
|
db.clj — SQLite server storage (nippy)
|
|
server.clj — HTTP sync server (http-kit)
|
|
```
|
|
|
|
## Running tests
|
|
|
|
```
|
|
clj -M:dev:test
|
|
```
|