Skip to content

publish zeroForTest as potential alternative to #5225#5392

Open
tantaman wants to merge 15 commits intomainfrom
zeroForTest
Open

publish zeroForTest as potential alternative to #5225#5392
tantaman wants to merge 15 commits intomainfrom
zeroForTest

Conversation

@tantaman
Copy link
Contributor

@tantaman tantaman commented Jan 8, 2026

Trying to accomplish the same goal as #5225 but with our existing test harnesses.

@vercel
Copy link

vercel bot commented Jan 8, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
replicache-docs Ready Ready Preview, Comment Jan 20, 2026 2:52pm
zbugs Ready Ready Preview, Comment Jan 20, 2026 2:52pm

Request Review

@tantaman tantaman changed the title publish zeroForTest as potential alternative option to #5225 publish zeroForTest as potential alternative to #5225 Jan 8, 2026
});

test('local mutate', async () => {
const zero = zeroForTest({
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

example of how you could use zeroForTest

@github-actions
Copy link

github-actions bot commented Jan 8, 2026

🐰 Bencher Report

BranchzeroForTest
TestbedLinux

🚨 1 Alert

BenchmarkMeasure
Units
ViewBenchmark Result
(Result Δ%)
Upper Boundary
(Limit %)
zero-package.tgzFile Size
megabytes (MB)
📈 plot
🚷 threshold
🚨 alert (🔔)
2.29 MB
(+12.46%)Baseline: 2.04 MB
2.08 MB
(110.25%)

Click to view all benchmark results
BenchmarkFile SizeBenchmark Result
kilobytes (KB)
(Result Δ%)
Upper Boundary
kilobytes (KB)
(Limit %)
zero-package.tgz📈 view plot
🚷 view threshold
🚨 view alert (🔔)
2,292.16 KB
(+12.46%)Baseline: 2,038.25 KB
2,079.01 KB
(110.25%)

zero.js📈 view plot
🚷 view threshold
243.49 KB
(0.00%)Baseline: 243.49 KB
248.36 KB
(98.04%)
zero.js.br📈 view plot
🚷 view threshold
66.66 KB
(0.00%)Baseline: 66.66 KB
67.99 KB
(98.04%)
🐰 View full continuous benchmarking report in Bencher

});

await zero.mutate(
// @ts-ignore - wtf?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no idea what is going on here...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard to tell. What is the type error?

Without pulling this PR to my local env I would guess it is an issue with the Context?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a context type error:

Types of property '$context' are incompatible.
                Type '{ readonly sub: string; readonly role: "user" | "crew"; } | undefined' is not assignable to type 'AuthData'.
                  Type 'undefined' is not assignable to type 'AuthData'.ts(2345)

@github-actions
Copy link

github-actions bot commented Jan 8, 2026

🐰 Bencher Report

BranchzeroForTest
Testbedself-hosted
Click to view all benchmark results
BenchmarkThroughputBenchmark Result
operations / second (ops/s)
(Result Δ%)
Lower Boundary
operations / second (ops/s)
(Limit %)
1 exists: track.exists(album)📈 view plot
🚷 view threshold
13,142.65 ops/s
(-4.98%)Baseline: 13,831.08 ops/s
11,648.56 ops/s
(88.63%)
10 exists (AND)📈 view plot
🚷 view threshold
178,773.97 ops/s
(-11.89%)Baseline: 202,902.54 ops/s
164,110.61 ops/s
(91.80%)
10 exists (OR)📈 view plot
🚷 view threshold
3,607.93 ops/s
(-9.20%)Baseline: 3,973.57 ops/s
3,355.71 ops/s
(93.01%)
12 exists (AND)📈 view plot
🚷 view threshold
165,779.95 ops/s
(-7.54%)Baseline: 179,295.57 ops/s
145,848.70 ops/s
(87.98%)
12 exists (OR)📈 view plot
🚷 view threshold
3,066.73 ops/s
(-9.09%)Baseline: 3,373.30 ops/s
2,865.01 ops/s
(93.42%)
12 level nesting📈 view plot
🚷 view threshold
2,628.49 ops/s
(-10.63%)Baseline: 2,941.25 ops/s
2,485.00 ops/s
(94.54%)
2 exists (AND): track.exists(album).exists(genre)📈 view plot
🚷 view threshold
4,820.42 ops/s
(-7.19%)Baseline: 5,193.74 ops/s
4,377.72 ops/s
(90.82%)
3 exists (AND)📈 view plot
🚷 view threshold
1,833.85 ops/s
(-9.63%)Baseline: 2,029.24 ops/s
1,709.01 ops/s
(93.19%)
3 exists (OR)📈 view plot
🚷 view threshold
960.38 ops/s
(-5.53%)Baseline: 1,016.61 ops/s
858.78 ops/s
(89.42%)
5 exists (AND)📈 view plot
🚷 view threshold
298.68 ops/s
(-6.45%)Baseline: 319.26 ops/s
269.94 ops/s
(90.38%)
5 exists (OR)📈 view plot
🚷 view threshold
150.15 ops/s
(-10.72%)Baseline: 168.19 ops/s
141.25 ops/s
(94.07%)
Nested 2 levels: track > album > artist📈 view plot
🚷 view threshold
4,142.84 ops/s
(-8.77%)Baseline: 4,541.04 ops/s
3,829.26 ops/s
(92.43%)
Nested 4 levels: playlist > tracks > album > artist📈 view plot
🚷 view threshold
700.45 ops/s
(-6.11%)Baseline: 745.99 ops/s
635.74 ops/s
(90.76%)
Nested with filters: track > album > artist (filtered)📈 view plot
🚷 view threshold
3,400.35 ops/s
(-9.94%)Baseline: 3,775.74 ops/s
3,211.58 ops/s
(94.45%)
planned: playlist.exists(tracks)📈 view plot
🚷 view threshold
599.77 ops/s
(-3.44%)Baseline: 621.12 ops/s
537.58 ops/s
(89.63%)
planned: track.exists(album) OR exists(genre)📈 view plot
🚷 view threshold
158.23 ops/s
(-4.12%)Baseline: 165.02 ops/s
144.86 ops/s
(91.55%)
planned: track.exists(album) where title="Big Ones"📈 view plot
🚷 view threshold
7,261.36 ops/s
(-4.16%)Baseline: 7,576.30 ops/s
6,544.74 ops/s
(90.13%)
planned: track.exists(album).exists(genre)📈 view plot
🚷 view threshold
37.40 ops/s
(-5.00%)Baseline: 39.37 ops/s
33.61 ops/s
(89.88%)
planned: track.exists(album).exists(genre) with filters📈 view plot
🚷 view threshold
5,142.05 ops/s
(-3.87%)Baseline: 5,349.02 ops/s
4,594.77 ops/s
(89.36%)
planned: track.exists(playlists)📈 view plot
🚷 view threshold
3.77 ops/s
(-5.93%)Baseline: 4.01 ops/s
3.47 ops/s
(91.96%)
unplanned: playlist.exists(tracks)📈 view plot
🚷 view threshold
579.26 ops/s
(-4.23%)Baseline: 604.85 ops/s
523.17 ops/s
(90.32%)
unplanned: track.exists(album) OR exists(genre)📈 view plot
🚷 view threshold
42.84 ops/s
(-4.43%)Baseline: 44.82 ops/s
37.97 ops/s
(88.64%)
unplanned: track.exists(album) where title="Big Ones"📈 view plot
🚷 view threshold
54.33 ops/s
(-3.44%)Baseline: 56.27 ops/s
48.32 ops/s
(88.94%)
unplanned: track.exists(album).exists(genre)📈 view plot
🚷 view threshold
37.10 ops/s
(-5.25%)Baseline: 39.16 ops/s
33.53 ops/s
(90.39%)
unplanned: track.exists(album).exists(genre) with filters📈 view plot
🚷 view threshold
51.57 ops/s
(-6.23%)Baseline: 55.00 ops/s
47.80 ops/s
(92.68%)
unplanned: track.exists(playlists)📈 view plot
🚷 view threshold
3.79 ops/s
(-5.25%)Baseline: 4.01 ops/s
3.46 ops/s
(91.05%)
zpg: all playlists📈 view plot
🚷 view threshold
5.47 ops/s
(-2.72%)Baseline: 5.62 ops/s
5.03 ops/s
(92.01%)
zql: all playlists📈 view plot
🚷 view threshold
6.93 ops/s
(-8.92%)Baseline: 7.61 ops/s
6.12 ops/s
(88.30%)
zql: edit for limited query, inside the bound📈 view plot
🚷 view threshold
195,904.16 ops/s
(-6.40%)Baseline: 209,292.18 ops/s
176,540.89 ops/s
(90.12%)
zql: edit for limited query, outside the bound📈 view plot
🚷 view threshold
180,582.00 ops/s
(-15.79%)Baseline: 214,434.46 ops/s
167,231.69 ops/s
(92.61%)
zql: push into limited query, inside the bound📈 view plot
🚷 view threshold
99,601.06 ops/s
(-6.95%)Baseline: 107,039.85 ops/s
89,998.97 ops/s
(90.36%)
zql: push into limited query, outside the bound📈 view plot
🚷 view threshold
347,463.98 ops/s
(-10.86%)Baseline: 389,816.90 ops/s
304,883.63 ops/s
(87.75%)
zql: push into unlimited query📈 view plot
🚷 view threshold
295,775.94 ops/s
(-8.43%)Baseline: 322,993.39 ops/s
266,924.24 ops/s
(90.25%)
zqlite: all playlists📈 view plot
🚷 view threshold
1.64 ops/s
(-6.92%)Baseline: 1.76 ops/s
1.44 ops/s
(87.97%)
zqlite: edit for limited query, inside the bound📈 view plot
🚷 view threshold
65,532.15 ops/s
(-12.50%)Baseline: 74,893.39 ops/s
59,468.07 ops/s
(90.75%)
zqlite: edit for limited query, outside the bound📈 view plot
🚷 view threshold
66,088.27 ops/s
(-11.88%)Baseline: 74,994.45 ops/s
57,346.52 ops/s
(86.77%)
zqlite: push into limited query, inside the bound📈 view plot
🚷 view threshold
4,046.52 ops/s
(+0.45%)Baseline: 4,028.31 ops/s
3,644.30 ops/s
(90.06%)
zqlite: push into limited query, outside the bound📈 view plot
🚷 view threshold
84,728.22 ops/s
(-2.84%)Baseline: 87,209.22 ops/s
75,788.69 ops/s
(89.45%)
zqlite: push into unlimited query📈 view plot
🚷 view threshold
114,941.08 ops/s
(-6.12%)Baseline: 122,427.67 ops/s
102,006.00 ops/s
(88.75%)
🐰 View full continuous benchmarking report in Bencher

@github-actions
Copy link

github-actions bot commented Jan 8, 2026

🐰 Bencher Report

BranchzeroForTest
Testbedself-hosted
Click to view all benchmark results
BenchmarkThroughputBenchmark Result
operations / second (ops/s) x 1e3
(Result Δ%)
Lower Boundary
operations / second (ops/s) x 1e3
(Limit %)
src/client/custom.bench.ts > big schema📈 view plot
🚷 view threshold
130.28 ops/s x 1e3
(-5.84%)Baseline: 138.36 ops/s x 1e3
120.65 ops/s x 1e3
(92.61%)
src/client/zero.bench.ts > basics > All 1000 rows x 10 columns (numbers)📈 view plot
🚷 view threshold
2.29 ops/s x 1e3
(-6.85%)Baseline: 2.45 ops/s x 1e3
2.07 ops/s x 1e3
(90.64%)
src/client/zero.bench.ts > pk compare > pk = N📈 view plot
🚷 view threshold
59.54 ops/s x 1e3
(-6.65%)Baseline: 63.78 ops/s x 1e3
53.87 ops/s x 1e3
(90.47%)
src/client/zero.bench.ts > with filter > Lower rows 500 x 10 columns (numbers)📈 view plot
🚷 view threshold
3.57 ops/s x 1e3
(-5.13%)Baseline: 3.76 ops/s x 1e3
3.24 ops/s x 1e3
(90.66%)
🐰 View full continuous benchmarking report in Bencher

@tantaman
Copy link
Contributor Author

tantaman commented Jan 8, 2026

@arv / @0xcadams - @kvnkusch is trying to use this but hitting errors with properties referenced by the testing symbol being undefined.

test/zeroForTest-repro.test.ts:
23 |   get connectingStart() {
24 |     return this[exposedToTestingSymbol].connectStart;
25 |   }
26 |   constructor(options) {
27 |     super(options);
28 |     this[exposedToTestingSymbol].connectionManager().subscribe((state) => {
              ^
TypeError: undefined is not an object (evaluating 'this[exposedToTestingSymbol].connectionManager')
      at new TestZero (/Users/kkusch/Code/ccs/cc1/node_modules/@rocicorp/zero/out/zero-client/src/client/test-utils.js:28:10)
      at zeroForTest (/Users/kkusch/Code/ccs/cc1/node_modules/@rocicorp/zero/out/zero-client/src/client/test-utils.js:174:10)
      at <anonymous> (/Users/kkusch/Code/ccs/cc1/packages/core/test/zeroForTest-repro.test.ts:23:15)

iirc, you guys fixed something like this recently? and that it has to do with our build?

@tantaman tantaman requested review from 0xcadams and arv January 8, 2026 19:34
Copy link
Contributor

@arv arv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like where this is going.

How about @rocicorp/zero/test or @rocicorp/zero/testing?

});

await zero.mutate(
// @ts-ignore - wtf?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard to tell. What is the type error?

Without pulling this PR to my local env I would guess it is an issue with the Context?

@tantaman
Copy link
Contributor Author

@kvnkusch - does this work for you now?

@kvnkusch
Copy link
Contributor

@kvnkusch - does this work for you now?

Getting the same error. For reference, here's a minimal repro:

import { createBuilder, defineMutator, defineMutators } from '@rocicorp/zero';
import { zeroForTest } from '@rocicorp/zero/testing';
import { expect, test } from 'vitest';
import { z } from 'zod';

const schema = {
    tables: {
        item: {
            name: 'item',
            columns: {
                id: { type: 'string' as const, optional: false },
                name: { type: 'string' as const, optional: false },
            },
            primaryKey: ['id'] as const,
        },
    },
    relationships: {},
};

declare module "@rocicorp/zero" {
    interface DefaultTypes {
        schema: typeof schema;
    }
}

const zql = createBuilder(schema);

const zero = zeroForTest({
    schema,
    mutators: defineMutators({
        item: {
            insert: defineMutator(z.object({ id: z.string(), name: z.string() }), async ({ args: { id, name }, tx }) => {
                await tx.mutate.item.insert({ id, name });
            }),
        },
    }),
});

test("basic insert and query", async () => {
    await zero.mutate.item.insert({
        id: "1",
        name: "test",
    });

    const result = await zero.run(zql.item);
    expect(result).toHaveLength(1);
});

Types on zero.mutate also don't seem to be working.

@tantaman
Copy link
Contributor Author

this is almost done... one more symbol problem.

@tantaman
Copy link
Contributor Author

ok @kvnkusch, I was able to get this to work in hello-zero so it should be good to go now...

@kvnkusch
Copy link
Contributor

The symbol error is gone, and queries are working via zero.run(zql.item)! But looks like zero.mutate is not an object:

45 | test("basic insert", async () => {
46 |     await zero.mutate.item.insert({
                           ^
TypeError: undefined is not an object (evaluating 'zero.mutate.item.insert')
      at <anonymous> (/Users/kkusch/Code/ccs/cc2/packages/test-tmp/test/repro.test.ts:46:23)
      at <anonymous> (/Users/kkusch/Code/ccs/cc2/packages/test-tmp/test/repro.test.ts:45:22)
✗ basic insert [0.21ms]

There's also a Typescript error on this line that matches this, so might be something I'm setting up the schema/mutators wrong? (using the same example as above)

test/repro.test.ts:46:23 - error TS2339: Property 'item' does not exist on type 'ZeroMutate<{ tables: { item: { name: string; columns: { id: { type: "string"; optional: boolean; }; name: { type: "string"; optional: boolean; }; }; primaryKey: readonly ["id"]; }; }; relationships: {}; }, CustomMutatorDefs | undefined, unknown>'.
  Property 'item' does not exist on type '(mr: MutateRequest<any, { tables: { item: { name: string; columns: { id: { type: "string"; optional: boolean; }; name: { type: "string"; optional: boolean; }; }; primaryKey: readonly ["id"]; }; }; relationships: {}; }, unknown, any>) => { ...; }'.

46     await zero.mutate.item.insert({
                         ~~~~


Found 1 error in test/repro.test.ts:46

Is the hello-zero branch up?

@arv
Copy link
Contributor

arv commented Jan 21, 2026

zero.mutate is only available if enableLegacyMutators is true.

@kvnkusch
Copy link
Contributor

Oh duh my bad. All working on my end.

Comment on lines +174 to +176
// Modules that should be kept external in the testing build to share
// symbol instances with the main bundle
const testingExternalModules = ['query-internals'];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is correct? Don't we want to share everything?

It is a bit strange because we do not want to get copies of the code but the code needs have have TESTING true.

I'm really not sure how to square this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should get rid of the TESTING flag and use import.meta.env or process.env?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should get rid of the TESTING flag and use import.meta.env or process.env?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants