feat(zero-client): add delete() to zero client#5579
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
| Branch | 0xcadams/drop-db |
| Testbed | Linux |
Click to view all benchmark results
| Benchmark | File Size | Benchmark Result kilobytes (KB) (Result Δ%) | Upper Boundary kilobytes (KB) (Limit %) |
|---|---|---|---|
| zero-package.tgz | 📈 view plot 🚷 view threshold | 1,903.53 KB(+0.03%)Baseline: 1,903.03 KB | 1,941.09 KB (98.06%) |
| zero.js | 📈 view plot 🚷 view threshold | 247.10 KB(+0.09%)Baseline: 246.88 KB | 251.82 KB (98.13%) |
| zero.js.br | 📈 view plot 🚷 view threshold | 67.68 KB(+0.13%)Baseline: 67.59 KB | 68.94 KB (98.17%) |
|
| Branch | 0xcadams/drop-db |
| Testbed | self-hosted |
Click to view all benchmark results
| Benchmark | Throughput | Benchmark Result operations / second (ops/s) (Result Δ%) | Lower Boundary operations / second (ops/s) (Limit %) |
|---|---|---|---|
| 1 exists: track.exists(album) | 📈 view plot 🚷 view threshold | 13,043.58 ops/s(-1.13%)Baseline: 13,192.52 ops/s | 11,554.54 ops/s (88.58%) |
| 10 exists (AND) | 📈 view plot 🚷 view threshold | 188,839.02 ops/s(-3.28%)Baseline: 195,252.10 ops/s | 173,529.40 ops/s (91.89%) |
| 10 exists (OR) | 📈 view plot 🚷 view threshold | 3,754.78 ops/s(-1.27%)Baseline: 3,803.19 ops/s | 3,358.54 ops/s (89.45%) |
| 12 exists (AND) | 📈 view plot 🚷 view threshold | 166,441.92 ops/s(-3.86%)Baseline: 173,121.32 ops/s | 152,430.78 ops/s (91.58%) |
| 12 exists (OR) | 📈 view plot 🚷 view threshold | 3,177.95 ops/s(-1.44%)Baseline: 3,224.49 ops/s | 2,854.52 ops/s (89.82%) |
| 12 level nesting | 📈 view plot 🚷 view threshold | 2,739.66 ops/s(-2.86%)Baseline: 2,820.25 ops/s | 2,488.60 ops/s (90.84%) |
| 2 exists (AND): track.exists(album).exists(genre) | 📈 view plot 🚷 view threshold | 4,743.05 ops/s(-4.59%)Baseline: 4,971.46 ops/s | 4,389.11 ops/s (92.54%) |
| 3 exists (AND) | 📈 view plot 🚷 view threshold | 1,883.01 ops/s(-3.03%)Baseline: 1,941.88 ops/s | 1,719.10 ops/s (91.30%) |
| 3 exists (OR) | 📈 view plot 🚷 view threshold | 953.59 ops/s(-1.54%)Baseline: 968.51 ops/s | 852.56 ops/s (89.40%) |
| 5 exists (AND) | 📈 view plot 🚷 view threshold | 296.44 ops/s(-2.93%)Baseline: 305.40 ops/s | 270.99 ops/s (91.41%) |
| 5 exists (OR) | 📈 view plot 🚷 view threshold | 156.43 ops/s(-2.94%)Baseline: 161.17 ops/s | 142.25 ops/s (90.93%) |
| Nested 2 levels: track > album > artist | 📈 view plot 🚷 view threshold | 4,265.54 ops/s(-1.51%)Baseline: 4,331.02 ops/s | 3,883.84 ops/s (91.05%) |
| Nested 4 levels: playlist > tracks > album > artist | 📈 view plot 🚷 view threshold | 705.40 ops/s(-1.05%)Baseline: 712.86 ops/s | 636.69 ops/s (90.26%) |
| Nested with filters: track > album > artist (filtered) | 📈 view plot 🚷 view threshold | 3,512.95 ops/s(-2.59%)Baseline: 3,606.50 ops/s | 3,184.16 ops/s (90.64%) |
| planned: playlist.exists(tracks) | 📈 view plot 🚷 view threshold | 244.65 ops/s(-36.44%)Baseline: 384.91 ops/s | -33.62 ops/s (-13.74%) |
| planned: track.exists(album) OR exists(genre) | 📈 view plot 🚷 view threshold | 128.70 ops/s(-7.16%)Baseline: 138.63 ops/s | 94.91 ops/s (73.74%) |
| planned: track.exists(album) where title="Big Ones" | 📈 view plot 🚷 view threshold | 6,506.21 ops/s(-4.12%)Baseline: 6,785.56 ops/s | 5,520.82 ops/s (84.85%) |
| planned: track.exists(album).exists(genre) | 📈 view plot 🚷 view threshold | 7.18 ops/s(-62.51%)Baseline: 19.15 ops/s | -17.48 ops/s (-243.47%) |
| planned: track.exists(album).exists(genre) with filters | 📈 view plot 🚷 view threshold | 1,989.04 ops/s(-37.67%)Baseline: 3,191.35 ops/s | -626.99 ops/s (-31.52%) |
| planned: track.exists(playlists) | 📈 view plot 🚷 view threshold | 1.58 ops/s(-36.21%)Baseline: 2.47 ops/s | -0.26 ops/s (-16.77%) |
| unplanned: playlist.exists(tracks) | 📈 view plot 🚷 view threshold | 235.87 ops/s(-37.15%)Baseline: 375.30 ops/s | -32.29 ops/s (-13.69%) |
| unplanned: track.exists(album) OR exists(genre) | 📈 view plot 🚷 view threshold | 19.42 ops/s(-32.79%)Baseline: 28.90 ops/s | 0.30 ops/s (1.52%) |
| unplanned: track.exists(album) where title="Big Ones" | 📈 view plot 🚷 view threshold | 52.10 ops/s(-1.30%)Baseline: 52.79 ops/s | 46.80 ops/s (89.83%) |
| unplanned: track.exists(album).exists(genre) | 📈 view plot 🚷 view threshold | 7.23 ops/s(-62.01%)Baseline: 19.02 ops/s | -17.28 ops/s (-239.06%) |
| unplanned: track.exists(album).exists(genre) with filters | 📈 view plot 🚷 view threshold | 51.61 ops/s(+0.35%)Baseline: 51.43 ops/s | 45.10 ops/s (87.40%) |
| unplanned: track.exists(playlists) | 📈 view plot 🚷 view threshold | 1.59 ops/s(-35.70%)Baseline: 2.47 ops/s | -0.26 ops/s (-16.46%) |
| zpg: all playlists | 📈 view plot 🚷 view threshold | 5.18 ops/s(+11.54%)Baseline: 4.65 ops/s | 1.37 ops/s (26.42%) |
| zql: all playlists | 📈 view plot 🚷 view threshold | 6.01 ops/s(-9.00%)Baseline: 6.61 ops/s | 4.74 ops/s (78.76%) |
| zql: edit for limited query, inside the bound | 📈 view plot 🚷 view threshold | 196,037.67 ops/s(-1.33%)Baseline: 198,672.40 ops/s | 174,218.97 ops/s (88.87%) |
| zql: edit for limited query, outside the bound | 📈 view plot 🚷 view threshold | 207,309.04 ops/s(-0.58%)Baseline: 208,514.73 ops/s | 184,914.27 ops/s (89.20%) |
| zql: push into limited query, inside the bound | 📈 view plot 🚷 view threshold | 117,953.67 ops/s(+5.73%)Baseline: 111,562.60 ops/s | 96,730.90 ops/s (82.01%) |
| zql: push into limited query, outside the bound | 📈 view plot 🚷 view threshold | 369,672.27 ops/s(-4.49%)Baseline: 387,059.89 ops/s | 338,622.76 ops/s (91.60%) |
| zql: push into unlimited query | 📈 view plot 🚷 view threshold | 398,358.59 ops/s(+4.29%)Baseline: 381,969.03 ops/s | 263,698.34 ops/s (66.20%) |
| zqlite: all playlists | 📈 view plot 🚷 view threshold | 1.52 ops/s(-7.00%)Baseline: 1.63 ops/s | 1.36 ops/s (90.02%) |
| zqlite: edit for limited query, inside the bound | 📈 view plot 🚷 view threshold | 67,413.98 ops/s(-1.54%)Baseline: 68,466.01 ops/s | 54,780.12 ops/s (81.26%) |
| zqlite: edit for limited query, outside the bound | 📈 view plot 🚷 view threshold | 66,883.01 ops/s(-4.52%)Baseline: 70,051.17 ops/s | 56,263.45 ops/s (84.12%) |
| zqlite: push into limited query, inside the bound | 📈 view plot 🚷 view threshold | 3,752.73 ops/s(-1.88%)Baseline: 3,824.69 ops/s | 3,440.55 ops/s (91.68%) |
| zqlite: push into limited query, outside the bound | 📈 view plot 🚷 view threshold | 84,695.75 ops/s(+2.04%)Baseline: 83,005.43 ops/s | 70,749.86 ops/s (83.53%) |
| zqlite: push into unlimited query | 📈 view plot 🚷 view threshold | 127,033.65 ops/s(+1.42%)Baseline: 125,253.07 ops/s | 111,962.38 ops/s (88.14%) |
|
| Branch | 0xcadams/drop-db |
| Testbed | self-hosted |
Click to view all benchmark results
| Benchmark | Throughput | Benchmark Result operations / second (ops/s) (Result Δ%) | Lower Boundary operations / second (ops/s) (Limit %) |
|---|---|---|---|
| src/client/custom.bench.ts > big schema | 📈 view plot 🚷 view threshold | 128,718.00 ops/s(-2.26%)Baseline: 131,691.64 ops/s | 116,098.99 ops/s (90.20%) |
| src/client/zero.bench.ts > basics > All 1000 rows x 10 columns (numbers) | 📈 view plot 🚷 view threshold | 1,406.87 ops/s(-19.94%)Baseline: 1,757.37 ops/s | 505.57 ops/s (35.94%) |
| src/client/zero.bench.ts > pk compare > pk = N | 📈 view plot 🚷 view threshold | 59,679.51 ops/s(-0.52%)Baseline: 59,989.76 ops/s | 52,429.72 ops/s (87.85%) |
| src/client/zero.bench.ts > with filter > Lower rows 500 x 10 columns (numbers) | 📈 view plot 🚷 view threshold | 2,813.68 ops/s(-8.06%)Baseline: 3,060.30 ops/s | 1,820.15 ops/s (64.69%) |
|
What happens if you delete data for an IDB Zero that is currently open? There can be multiple Zero instances open for same datastore. If they just stop working that's OK with me. If the delete is blocked don't love that. |
It's a good question - I thought about just doing delete for only the current zero replicache db only, but we have this delete all that we recommend currently in docs. This should've been a draft PR - bringing it up for feedback. I'll test this more. |
|
It does seem a bit odd. I would prefer that it only deletes the databases belonging to the current client group. |
delete() to zero client
| } | ||
| } | ||
|
|
||
| async delete(): Promise<{deleted: string[]; errors: unknown[]}> { |
There was a problem hiding this comment.
I think this could use some JSDoc explaining that it closes the instance and then deletes the related databases.
| const idbDatabasesStore = new IDBDatabasesStore(kvStoreProvider.create); | ||
| try { | ||
| const databases = await idbDatabasesStore.getDatabases(); | ||
| const dbNamesToDelete = Object.values(databases) |
There was a problem hiding this comment.
Can there be more than one??
|
Yeah so any changes to the userID related to the zero instance (there can be multiple), or adding tables and that triggering a schema version change, there will be leftover DBs in local IDB.`rep:zero-<userID>-<hash>:<formatVersion>:<schemaVersion>`On Feb 27, 2026, at 01:14, Aaron Boodman ***@***.***> wrote:
@aboodman commented on this pull request.
In packages/zero-client/src/client/zero.ts:
@@ -2387,6 +2394,40 @@ export class Zero<
}
}
+ async delete(): Promise<{deleted: string[]; errors: unknown[]}> {
+ await this.close();
+
+ const kvStoreProvider = getKVStoreProvider(this.#lc, this.#kvStore);
+ const idbDatabasesStore = new IDBDatabasesStore(kvStoreProvider.create);
+ try {
+ const databases = await idbDatabasesStore.getDatabases();
+ const dbNamesToDelete = Object.values(databases)
Can there be more than one??
—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you modified the open/close state.Message ID: ***@***.***>
|
|
Ok, I find it kind of weird to delete those. They are completely separate
from the called Zero instance and can’t interact.
Before we had deleteAllDatabases which is the nuclear option and deletes
all Zero data in the entire origin. This was good for “the app is broken
reset everything”.
And I could see “delete just the data for this Zero instance”. That would
be good for logging out a particular user.
But this in between setting is odd.
I think there should be delete() (just this instance) and deleteAll() (all
instances). These can be implemented by directly calling the existing
functions.
a (phone)
On Fri, Feb 27, 2026 at 5:34 AM Chase Adams ***@***.***>
wrote:
… *0xcadams* left a comment (rocicorp/mono#5579)
<#5579 (comment)>
Yeah so any changes to the userID related to the zero instance (there can
be multiple), or adding tables and that triggering a schema version change,
there will be leftover DBs in local
IDB.`rep:zero-<userID>-<hash>:<formatVersion>:<schemaVersion>`On Feb 27,
2026, at 01:14, Aaron Boodman ***@***.***> wrote:
@aboodman commented on this pull request.
In packages/zero-client/src/client/zero.ts:
> @@ -2387,6 +2394,40 @@ export class Zero<
}
}
+ async delete(): Promise<{deleted: string[]; errors: unknown[]}> {
+ await this.close();
+
+ const kvStoreProvider = getKVStoreProvider(this.#lc, this.#kvStore);
+ const idbDatabasesStore = new IDBDatabasesStore(kvStoreProvider.create);
+ try {
+ const databases = await idbDatabasesStore.getDatabases();
+ const dbNamesToDelete = Object.values(databases)
Can there be more than one??
—Reply to this email directly, view it on GitHub, or unsubscribe.You are
receiving this because you modified the open/close state.Message ID:
***@***.***>
—
Reply to this email directly, view it on GitHub
<#5579 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAATUBA6YMWT5VR2UCJYKOT4OBPXDAVCNFSM6AAAAACVPCBPGKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZTSNZTGU4DAOBRHA>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
|
I guess I was seeing this as “I log out and delete all DBs associated with this Zero client”. Digging into this deeper, that’s how this is working in practice, since in ZeroProvider we create a new Zero client when user ID changes or many of the props. Maybe @arv knows what the cases are where this “multiple DBs exist associated with one Zero client” happens. Maybe it never happens.And then we have deleteAllDatabases which would be “delete all DBs associated with any Zero client”.
|
|
Sounds good. That's how this PR was originally, then I doubted myself and thought I missed something.I will fix when I'm back.Chase Adamshttps://cadams.ioOn Feb 27, 2026, at 17:42, Aaron Boodman ***@***.***> wrote:aboodman left a comment (rocicorp/mono#5579)
So the replicacheName you are comparing to is computed like so:
name: `zero-${userID}-${hashedKey}`,
and hashedKey is:
// Create a hash that includes storage key, URL configuration, and query parameters
const nameKey = JSON.stringify({
storageKey: this.storageKey,
mutateUrl: options.mutateURL ?? '',
queryUrl: options.queryURL ?? options.getQueriesURL ?? '',
});
const hashedKey = h64(nameKey).toString(36);
These replicacheName then become the IDB database name just prefixed with rep:. They are also used as keys within replicache-dbs-v0:
***@***.*** (view on web)
So I think this iteration is just unnecessary. It can only ever find one item. You can just use dropDatabase and pass this.#rep.name and be done with it.
I would also still like to see a deleteAll() convenience that calls through to dropAllDatabases with the kvStore and logging options from Zero.
—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you modified the open/close state.Message ID: ***@***.***>
|
|
Wait, no, sorry - you are right. Your impl is correct. The hashedKey
contains Zero-specific bits, but Replicache also adds on "formatVersion"
and "schemaVersion".
Also if the intent of this is for privacy/logout then I suppose it makes
sense to delete old schema/verions of this Zero store.
It might makes sense to add some comments about this so we don't have to
think about it so hard next time.
Sorry for the waste of time. I still think deleteAll() would be good!
On Fri, Feb 27, 2026 at 8:19 AM Chase Adams ***@***.***>
wrote:
… *0xcadams* left a comment (rocicorp/mono#5579)
<#5579 (comment)>
Sounds good. That's how this PR was originally, then I doubted myself
and thought I missed something.I will fix when I'm back.Chase
Adamshttps://cadams.ioOn Feb 27, 2026, at 17:42, Aaron Boodman ***@***.***>
wrote:aboodman left a comment (rocicorp/mono#5579)
So the replicacheName you are comparing to is computed like so:
name: `zero-${userID}-${hashedKey}`,
and hashedKey is:
// Create a hash that includes storage key, URL configuration, and query
parameters
const nameKey = JSON.stringify({
storageKey: this.storageKey,
mutateUrl: options.mutateURL ?? '',
queryUrl: options.queryURL ?? options.getQueriesURL ?? '',
});
const hashedKey = h64(nameKey).toString(36);
These replicacheName then become the IDB database name just prefixed with
rep:. They are also used as keys within replicache-dbs-v0:
***@***.*** (view on web)
So I think this iteration is just unnecessary. It can only ever find one
item. You can just use dropDatabase and pass this.#rep.name and be done
with it.
I would also still like to see a deleteAll() convenience that calls
through to dropAllDatabases with the kvStore and logging options from Zero.
—Reply to this email directly, view it on GitHub, or unsubscribe.You are
receiving this because you modified the open/close state.Message ID:
***@***.***>
—
Reply to this email directly, view it on GitHub
<#5579 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAATUBEJNCVPGY262M4URO34OCDEBAVCNFSM6AAAAACVPCBPGKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZTSNZUGM3DMNJZGM>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|

replicacheName,The current docs are awkward about dropping all databases, and this is a core workflow for Zero client. Currently it requires passing the same kvStore as initialized in the Zero client.
https://zero.rocicorp.dev/docs/auth#logging-out