loro-repo 0.5.3 → 0.6.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/README.md CHANGED
@@ -69,8 +69,23 @@ Adapters are shipped as subpath exports so the default `loro-repo` entry stays h
69
69
  url: "wss://sync.example.com/repo",
70
70
  metadataRoomId: "workspace:meta",
71
71
  docAuth: (docId) => authFor(docId),
72
+ onStatusChange: (status) => setConnectionBadge(status),
73
+ onRoomStatusChange: ({ roomId, status }) =>
74
+ console.info(`room ${roomId} -> ${status}`),
72
75
  });
76
+
77
+ // Force an immediate reconnect (resets backoff) when the UI exposes a retry button.
78
+ await transport.reconnect({ resetBackoff: true });
79
+
80
+ // Per-room status callbacks surface reconnect cycles: connecting | joined | reconnecting | disconnected | error.
81
+ const live = await repo.joinDocRoom("doc:123", {
82
+ onStatusChange: (status) => setRoomState(status),
83
+ });
84
+ // Subscriptions also expose status + onStatusChange hooks:
85
+ live.onStatusChange((status) => setRoomBadge(status));
86
+ console.log(live.status); // e.g. "joined"
73
87
  ```
88
+ Auto-reconnect (via `loro-websocket@0.5.0`) is enabled by default with exponential backoff (0.5s → 15s + jitter), pings to detect half-open sockets, offline pause/resume, and automatic rejoin of previously joined rooms. Observe the lifecycle through `onStatusChange` (adapter-level), `onRoomStatusChange` (all rooms), or per-join `onStatusChange` callbacks; feed ping RTTs to telemetry with `onLatency`. Manual controls: `connect({ resetBackoff })` to restart after a fatal close and `reconnect()` / `reconnect({ resetBackoff: true })` to trigger a retry immediately. Frames now use the loro-protocol v1 format (introduced in `loro-protocol@0.3.x`), so your server endpoint must speak the v1 WebSocket dialect.
74
89
 
75
90
  - `IndexedDBStorageAdaptor` (`src/storage/indexeddb.ts`)
76
91
  Browser storage for metadata snapshots, doc snapshots/updates, and cached assets. Swap it out for SQLite/LevelDB/file-system adaptors when running on desktop or server environments. Import via `loro-repo/storage/indexeddb`.
@@ -90,7 +105,7 @@ Adapters are shipped as subpath exports so the default `loro-repo` entry stays h
90
105
 
91
106
  **Metadata**
92
107
  - `await repo.upsertDocMeta(docId, patch)` – LWW merge with your `Meta` type.
93
- - `await repo.getDocMeta(docId)` – clone the stored metadata (or `undefined`).
108
+ - `await repo.getDocMeta(docId)` – resolve to `{ meta, deletedAtMs? }` for the stored doc (or `undefined` when it doesn’t exist).
94
109
  - `await repo.listDoc(query?)` – list docs by prefix/range/limit (`RepoDocMeta<Meta>[]`).
95
110
  - `repo.getMeta()` – access raw `Flock` if you need advanced scans.
96
111
 
@@ -101,6 +116,13 @@ Adapters are shipped as subpath exports so the default `loro-repo` entry stays h
101
116
  - `await repo.unloadDoc(docId)` – flush pending work for a doc and evict it from memory.
102
117
  - `await repo.flush()` – persist all loaded docs and flush pending frontier updates.
103
118
 
119
+ **Deletion & retention**
120
+ - Tombstoning is explicit and enforced: tombstoned docs stay visible, readable, and joinable; a configurable retention window (default 30 days) governs when they can be purged.
121
+ - `await repo.deleteDoc(docId, { deletedAt?, force? })` – soft-delete by writing a tombstone (`ts/*`); repeats are no-ops unless `force: true` overwrites the timestamp.
122
+ - `await repo.restoreDoc(docId)` – remove the tombstone so the doc can be opened again (idempotent when not deleted).
123
+ - `await repo.purgeDoc(docId)` – hard-delete immediately: removes doc bodies, metadata/frontiers, tombstone, and doc→asset links; emits unlink + metadata clear events.
124
+ - `await repo.gcDeletedDocs({ minKeepMs?, now? })` – sweep tombstoned docs whose retention window expired; returns the count purged.
125
+
104
126
  **Assets**
105
127
  - `await repo.linkAsset(docId, { content, mime?, tag?, policy?, assetId?, createdAt? })` – upload + link, returning the SHA-256 assetId.
106
128
  - `await repo.uploadAsset(options)` – upload without linking to a doc (pre-warm caches).
@@ -116,6 +138,23 @@ Adapters are shipped as subpath exports so the default `loro-repo` entry stays h
116
138
  **Realtime metadata**
117
139
  - `await repo.joinMetaRoom(params?)` – opt into live metadata sync via the transport adapter; call `subscription.unsubscribe()` to leave.
118
140
 
141
+ ### Doc deletion lifecycle (soft vs. hard)
142
+
143
+ The repo separates logical deletion from storage reclamation so UIs stay responsive while bytes are cleaned up safely:
144
+
145
+ 1) **Soft delete (safe delete)** — `deleteDoc` writes a tombstone at `ts/<docId>` but keeps metadata (`m/*`), frontiers (`f/*`), doc snapshots/updates, and asset links intact. Tombstoned docs remain readable and joinable; the tombstone is a visibility/retention hint, not an access ban. UIs can still surface the doc (often with a “deleted” badge) and choose whether to join the room. Watchers see `doc-soft-deleted` events with provenance (`by: "local" | "sync" | "live"`).
146
+
147
+ 2) **Retention window** — A tombstoned doc waits for `deletedDocKeepMs` (default 30 days; configurable when creating the repo). Run `gcDeletedDocs()` periodically (or pass `{ minKeepMs, now }`) to purge anything past its keep window; each purge internally calls `purgeDoc`.
148
+
149
+ 3) **Hard delete (`purgeDoc` / `gcDeletedDocs`)** — Removes all local state and triggers storage deletion when supported:
150
+ - Clears metadata/frontiers/link rows (`m/*`, `f/*`, `ld/*`) and the tombstone from the Flock.
151
+ - `DocManager.dropDoc` evicts cached docs and calls `storage.deleteDoc` (FileSystem/IndexedDB adaptors delete the snapshot plus pending updates).
152
+ - Asset links are removed; assets that become orphaned are marked, and `gcAssets` later removes the binary via `storage.deleteAsset` once its own keep window elapses.
153
+
154
+ 4) **Remote purge propagation** — If a sync/live event shows a peer cleared doc metadata (empty `doc-metadata` patch) or removed the tombstone while no metadata remains, `handlePurgeSignals` invokes `dropDoc` locally. This keeps your storage aligned with the authoritative replica even if you never called `purgeDoc` yourself.
155
+
156
+ 5) **What does *not* delete storage** — Soft delete alone never removes bytes. `unloadDoc` only flushes snapshots; it does not delete them. Storage reclaim happens only through `purgeDoc`, `gcDeletedDocs`, or the remote-purge path described above.
157
+
119
158
  ## Commands
120
159
 
121
160
  | Command | Purpose |