An open API service indexing awesome lists of open source software.

https://github.com/sravan27/silentdrop

Does your JavaScript database silently return the wrong rows? Zero-dep MIT correctness checker. 8 merged PRs across 7 OSS sync engines.
https://github.com/sravan27/silentdrop

conformance correctness data-loss database dexie indexeddb local-first query-engine sql testing

Last synced: 5 days ago
JSON representation

Does your JavaScript database silently return the wrong rows? Zero-dep MIT correctness checker. 8 merged PRs across 7 OSS sync engines.

Awesome Lists containing this project

README

          

# silentdrop

**Does your JavaScript database silently return the wrong rows?**

→ **[silentdrop-sravan.vercel.app](https://silentdrop-sravan.vercel.app)** · the audit, the proof, and the booking page in one screen.

Client‑side, embedded, and sync databases re‑implement SQL‑ish operators — `LIKE`,
case‑insensitive match, range comparison — in JavaScript. Subtle gaps between those
JS implementations and the SQL semantics they're meant to mirror make queries
**silently drop or over‑match rows**. No error. No crash. Just wrong data — the
worst kind of bug.

As of this week I have shipped this exact bug class to **seven production databases**, 11 PRs in 8 days (8 merged, 1 open, 3 closed after an operator-direction call from a maintainer). Write-up: [9 silent-row-loss fixes in 7 days across 7 OSS databases](https://dev.to/sravan27/9-silent-row-loss-fixes-in-7-days-across-7-oss-databases-2nd-draft-56da).

| Database | Bug | Status |
|---|---|---|
| PowerSync | LIKE/range, CAST, div-by-zero, json_each, NOT-NULL semantics | merged: `#643`, `#644`, `#645`, `#646`, `#647` + paid hardening sprint |
| PowerSync | JOIN on aliased primary, silent zero-row sync | open: `#662` |
| PowerSync | upper/lower/length/substring ASCII vs Unicode | closed by maintainer — preferred direction is Unicode-aware JS; client-side override scoped instead (`#663`, `#664`, `#665`) |
| TanStack DB | upper/lower/ilike ASCII case fold | PR open (`db#1574`) |
| Rocicorp's Zero | range/comparison | merged (`mono#6083`, `#6088`) |
| InstantDB | `$like`/`$ilike` newline | merged (`instant#2714`) |
| ElectricSQL | LIKE newline + escaped wildcards | PR open (`electric#4437`) |
| Dexie | case‑fold drops rows | PR open (`Dexie.js#2306`) |

PowerSync's full audit summary with reproductions: [discussions/668](https://github.com/orgs/powersync-ja/discussions/668).

`silentdrop` packages that audit so you can run it against **your** database.

## It catches real bugs

Run it against Dexie (the dominant IndexedDB wrapper, ~2M downloads/week):

```
$ node examples/dexie.mjs

✗ DIVERGENCE: ilike-casefold — Case-insensitive match must handle length-changing case folds
equalsIgnoreCase('straße') must match 'STRAßE'. Index walks that assume case
conversion is length-preserving (ß→SS, fi→FI, İ) silently drop rows.
expected: ["straße","STRAßE","Straße","sTRAßE"]
got: ["Straße","sTRAßE","straße"] <-- 'STRAßE' silently dropped

✗ DIVERGENCE: compare-nonbmp — Range comparison must order non-BMP characters by code point
A range query over astral characters (emoji, CJK ext.) drops rows, because
JS compares UTF-16 code units while SQL orders by code point.

✗ 2 silent-data-loss divergences — these queries return wrong rows
```

Both are genuine: the case‑fold one is reported as [Dexie #2306](https://github.com/dexie/Dexie.js/pull/2306).

## What it checks

- **`LIKE` across newlines** — `%` must span `\n` (SQL semantics; RegExp without `dotAll` misses it)
- **`LIKE` metacharacter literalness** — `LIKE 'a.b'` matches `a.b`, not `axb` (escaping)
- **Case‑fold length changes** — `ß`→`SS`, ligatures, Turkish `İ` must not drop rows
- **Non‑BMP ordering** — range comparison must order by code point, not UTF‑16 code unit

## Usage

```js
import { check, report } from "silentdrop";

// Wire your DB's query operators (returns matching string values).
const adapter = {
async reset() { /* clear store */ },
async seed(values) { /* insert string values */ },
async like(pattern) { /* run a LIKE query, return matches (omit if unsupported) */ },
async ilike(needle) { /* case-insensitive equality */ },
async gt(bound) { /* values > bound */ },
};

report(await check(adapter));
```

See [`examples/dexie.mjs`](examples/dexie.mjs) for a complete adapter.

## Need the deep version?

`silentdrop` catches the common cases automatically. If your database/sync layer is
correctness‑critical and you want a **full manual hardening pass** — reduced repros +
fixes + regression tests across the whole operator surface (the same work behind the
5 databases above) — I do it as a fixed **$1,000 / 48‑hour sprint**, no‑find‑no‑charge:

**→ https://buy.polar.sh/polar_cl_z0eLsPUJeMwrcNs4MQPAQbKIM3Rbdb8fLDgVj2RZcmr**

Smaller scope? **$500 Diagnostic Fix** — one operator I find a divergence on, repro + fix + PR delivered:

**→ https://buy.polar.sh/polar_cl_G0fuUHHZ1tg9E0oe7gluje9gs44l8FAqVnfwS2AJkbw**

Track record (June 2026): 8 silent-row-loss PRs at PowerSync (4 merged + 4 open), 2 each at Rocicorp Zero and Autumn (all merged), plus shipped fixes at InstantDB, ElectricSQL, Dexie, RxDB. Every bug is real and silent — no error, no log, just wrong rows.

## License

MIT © Sravan Sridhar · [github.com/sravan27](https://github.com/sravan27)