Skip to content

Releases: odiak/livesync-workers

v0.7.0 — Read attachments through the vault client

Choose a tag to compare

@odiak odiak released this 09 Oct 07:12
6e74802

Attachments

  • Add Vault.listFileStats(), which lists every synced file including attachments such as images. Hidden internal files (i:) are excluded and reserved paths are enforced.
  • Add Vault.readFile(path), which returns a file's bytes as Uint8Array, or null when it is missing, incomplete or cannot be decoded.
  • Binary documents are decoded chunk by chunk, since LiveSync encodes each piece separately; LiveSync's legacy % encoding is supported as well. Text documents are returned as UTF-8.

Existing Markdown APIs (listNoteStats, readNote, …) are unchanged.

Validation

Type checking, 170 tests, and the library build passed.

v0.6.0 — R2 chunk storage and persistent MCP authorizations

Choose a tag to compare

@odiak odiak released this 09 Oct 06:25
68b58c2

R2 chunk storage

  • Add optional R2 storage for large LiveSync leaf revision bodies. Hosts configure chunkBucket and an optional byte threshold (64 KiB by default); the policy applies to every new write.
  • Add internal storage statistics and resumable migration for existing SQLite chunks, with read-back verification before switching storage.
  • Preserve replication document IDs, revisions and change sequences, and support external bodies in note reconstruction and search indexing.
  • Track R2 usage, clean up compacted revisions, and purge vault objects. Serialize vault operations across asynchronous storage calls.

See the storage and migration guide for host integration and operational requirements. Hosts without a chunk bucket retain SQLite-only storage. Once chunks are externalized, keep the bucket binding and reader support; recovery requires both SQLite and R2.

MCP authorization and connections

  • Allow refreshTokenTTL: null for indefinite authorizations; the library's default remains 30 days and access tokens still expire after one hour.
  • Enable indefinite authorizations in the single-tenant app and show authorized MCP clients to signed-in administrators.
  • Existing finite grants keep their expiry until the client reconnects.

Validation

Type checking, 174 tests, and the library build passed. R2 storage and migration were also exercised with local Durable Object SQLite and R2 bindings.

v0.5.4

Choose a tag to compare

@odiak odiak released this 03 Oct 08:53
06258af

Fix one-shot LiveSync replication stopping before all matching notes have been received.

  • Continue scanning _changes through runs of filtered-out chunk documents until the requested number of matching rows is found or the database is exhausted.
  • Report the number of remaining changes in pending instead of always returning zero.
  • Add regression coverage for filtered all_docs and main_only feeds, trailing chunks, and unfiltered pagination.

Validation: 161 tests passed, type checking and the package build passed, and a PouchDB 9 one-shot replication check received notes after long runs of chunk documents.

v0.5.3

Choose a tag to compare

@odiak odiak released this 01 Oct 09:31
bdfc4fa

MCPの認証・同期データ保全・検索を修正しました。

  • MCPセッションを認証ユーザーとトークンの権限ごとに分離し、OAuthの権限縮小をツールへ反映。
  • 同期未完了ノートへの書き込みを拒否し、revision履歴の接続と論理削除後の再作成を修正。
  • Vault削除と索引処理を直列化し、vector更新の途中失敗でも削除対象を追跡。
  • 削除・更新済みの検索結果を除外し、全文検索の候補不足と長い単独行の切り捨てを修正。

更新後、MCPクライアントは再接続してください。既存のVaultノートは通常のバックグラウンド処理で一度再索引されます。

回帰テスト、型チェック、ライブラリbuildで検証しています。

v0.5.2

Choose a tag to compare

@odiak odiak released this 28 Sep 04:00
04f4afc

Fixes two ways the built-in full-text index could stall, both seen on a production vault after upgrading to 0.5.1:

  • A pass that found pending notes but indexed none of them (all skipped) left its phase marker unsettled, so the next pass counted it as an interrupted attempt; after three such passes the index disarmed itself with "full-text pass was interrupted 3 times". Such a pass now writes a segment-empty marker.
  • A note whose recorded hash (index_state.hash) never matched its current body was skipped as "moved on" on every pass and stayed pending forever, re-arming a pass on every request. It is now indexed as its body is, under the recorded hash; a body that really did change has its change in the feed, which re-queues the note.

The index version meta is bumped to 5, so a vault disarmed this way recovers on its first start after upgrading.

v0.5.1

Choose a tag to compare

@odiak odiak released this 28 Sep 03:45
1828e32

Fixes the upgrade path for vaults whose full-text index had given up under 0.4.0 ("full-text pass was interrupted 3 times" in indexStatus().fts.error, typically caused by one very large note). 0.5.0 handles such notes but left that verdict in place, so the index stayed disarmed. The index version meta is bumped to 4; on the first start after upgrading, the verdict is cleared and a pass is armed, and the pending notes are indexed.

v0.5.0

Choose a tag to compare

@odiak odiak released this 28 Sep 03:36
64ac31c

The built-in full-text index no longer breaks on very large notes or on vaults that are edited a lot. No host-side code changes are needed; existing indexes keep working as they are.

  • Large notes no longer take the index down. A single note of 1–2 million characters used to push the Durable Object past its 128 MB memory limit, and after three resets the index stopped updating for the whole vault. Tokenization now streams straight into the postings (a 2M-character note peaks at ~65 MB instead of ~265 MB, same CPU), and a note is indexed up to its first million characters rather than being skipped entirely.
  • Random-looking text is bounded. A segment holds at most 400k distinct terms. A note that would overflow a segment starts the next one; only a note that overflows on its own (e.g. mojibake, where every character bigram is distinct) is left out.
  • Replaced versions are shed. Old versions of edited notes stayed in their segments until a merge happened to include them, and they counted toward the size guard, so a vault could reach the guard by being edited rather than by growing. Idle passes now rewrite a segment once a quarter of its text (and at least 250k characters) is dead weight, and a pass that hits the guard sheds such text first, stopping only when nothing can be freed.
  • ftsRebuild can recover a vault that hit the guard. The rebuild records an epoch; segments from before it are ignored by the guard and retired in one step once every note has been re-indexed. Searches keep working throughout.
  • Over-long queries are refused instead of failing. grepNotes and VaultClient.grep() return { status: "query-too-long", maxTokens } for queries of more than 32 index terms (about 60 CJK characters), which would otherwise exceed the Worker's subrequest limit on a large index.

The README now has a "Search coverage and limits" section (CJK support, tested vault size, the per-note cut-off, query length).

Library API: FullTextSearchResult gains the query-too-long variant; appendFtsSegment() returns { manifest, segment, docs, dropped } (with manifest/segment null when nothing was written) instead of null; buildIndex() takes maxTerms and reports dropped.

v0.4.0

Choose a tag to compare

@odiak odiak released this 27 Sep 10:07
1cf27f9

Full-text search reads only the postings it needs, which makes searches fast on large vaults. No host-side code changes are needed; existing indexes are rewritten to the new layout automatically after deployment.

  • Each shard is split into 64 buckets, compressed separately, and a search reads only the buckets its query terms hash into, with R2 range reads. The bytes read depend on the number of terms, not on the size of the index (measured on a 100 MB vault of 12,720 notes: long phrases and 4-word AND queries went from 5–7 s to about 1 s, and to 0.2–0.6 s once cached).
  • A phrase query reads the smallest set of bigrams that covers every character (the set of matching notes is unchanged).
  • Segments written by 0.3.0 are rewritten to the new layout one at a time by the maintenance pass (10–20 s each) and remain searchable meanwhile. Upgrading directly from a version-1 index (0.2.0 or earlier) also works.
  • If a rebuild pass was scheduled but its alarm was lost, the next request picks it up again.

R2 layout: seg-*/shard-NNN.bin (concatenated buckets) and seg-*/index.bin (bucket byte ranges) are added; the shard-NNN.bin.gz files of 0.3.0 are still read.

v0.3.0

Choose a tag to compare

@odiak odiak released this 27 Sep 00:52
fd1e98a

The built-in full-text index (R2) is rebuilt as incremental segments, so it keeps working on large vaults. No host-side code changes are needed; existing vaults migrate automatically on their first access after deployment.

  • The index is now a set of immutable segments. Each indexing pass writes only the notes that changed into one segment (at most 2 million code units per pass, a few seconds), so the cost of an update is proportional to the change, not to the vault. The initial indexing is split into passes the same way and stays under the Durable Object CPU and memory limits (measured: a 100 MB vault of 12,720 notes indexes in about 2 minutes).
  • Idle passes merge small segments automatically (compaction, without re-tokenizing).
  • Ranking is now BM25 over the query phrases (it used to be matches / log2(length), with no IDF).
  • The previous whole-vault generation is retired and deleted automatically once every note is in a new segment.
  • indexStatus().fts.pending (notes not yet in the built-in index) is added.
  • VaultBindings.ftsMaxTotalCodeUnits now bounds the whole index (default 50,000,000 code units ≈ 120 MB of Japanese Markdown; in 0.2.0 it bounded one rebuild at 8,000,000). Exceeding it is still reported in indexStatus().fts.error.
  • A rebuild that keeps dying mid-way is recorded in indexStatus().fts.error instead of being retried forever (up to 0.2.0 it retried every 60 seconds).
  • Omitting VaultBindings.vectorize / embedder runs without semantic search (no embeddings, no Vectorize). In this repository's Worker that is the SEMANTIC_SEARCH=off variable.

R2 layout: fts/{tenant}/{db}/manifest.json plus seg-*/. A manifest.json written by 0.2.0 or earlier (version 1) is still read.

v0.2.0

Choose a tag to compare

@odiak odiak released this 24 Sep 11:10
5a1f648

Default behaviour is unchanged; this release adds extension points for hosts, each of which only takes effect when set.

  • VaultBindings.fullText (FullTextIndex): replace the full-text index with an external implementation. Notes are upserted / deleted one by one as they change, and the in-DO R2 rebuild of the whole vault (which exceeds 128 MB for large vaults) is not run. Progress is tracked separately from the vectors; failures are retried without re-embedding. Existing notes are backfilled automatically. bucket may be omitted when fullText is given.
  • VaultBindings.objectName: choose the Durable Object name (default tenantId:databaseName; override LiveSyncVaultDO.vaultRef() as well if you change it).
  • VaultPolicy.excludeHiddenPaths: keep dot-prefixed paths such as .obsidian/ and .trash/, and i: hidden files, out of the indexes (default false).
  • vaultDb accepts the class-typed DurableObjectNamespace<…> that wrangler types generates.
  • handleLiveSyncRequest: exceptions become 500 responses with CORS headers (so the plugin no longer misreports them as CORS errors), and preflight responses are cacheable via Access-Control-Max-Age.

See "Optional: an external full-text index" and "Optional: Durable Object names" in docs/embedding.md.