Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/danielroe/zero-vue. Vue bindings for Zero
sync vue zero
Something went wrong. Try again.
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494import type { TTL } from '@rocicorp/zero'import { createBuilder, createSchema, defineMutatorsWithType, defineMutatorWithType, defineQueriesWithType, defineQuery, number, relationships, string, table, Zero,} from '@rocicorp/zero'import { asQueryInternals } from '@rocicorp/zero/bindings'import { describe, expect, it, onTestFinished, vi } from 'vitest'import { nextTick, ref, watchEffect } from 'vue'import z from 'zod'import { createZeroComposables } from './create-zero-composables'import { useQuery } from './query'import { VueView, vueViewFactory } from './view'
const schema = createSchema({ tables: [ table('table') .columns({ a: number(), b: string(), }) .primaryKey('a'), ],})
async function setupTestEnvironment() { const userID = ref('asdf')
const defineMutators = defineMutatorsWithType<typeof schema>() const defineMutator = defineMutatorWithType<typeof schema>() const mutators = defineMutators({ table: { insert: defineMutator( z.object({ a: z.number(), b: z.string() }), async ({ tx, args: { a, b } }) => { return tx.mutate.table.insert({ a, b }) }, ), update: defineMutator( z.object({ a: z.number(), b: z.string() }), async ({ tx, args: { a, b } }) => { return tx.mutate.table.update({ a, b }) }, ), }, })
const { useZero, useQuery } = createZeroComposables(() => ({ userID: userID.value, server: null, schema, mutators, kvStore: 'mem', }))
const zero = useZero() await zero.value.mutate(mutators.table.insert({ a: 1, b: 'a' })).client await zero.value.mutate(mutators.table.insert({ a: 2, b: 'b' })).client
const zql = createBuilder(schema) const defineQueries = defineQueriesWithType<typeof schema>() const queries = defineQueries({ byId: defineQuery( z.number(), ({ args: a }) => zql.table.where('a', a), ), table: defineQuery( () => zql.table, ), })
onTestFinished(async () => { await zero.value.close() })
return { zero, queries, useQuery, mutators, userID }}
describe('useQuery', () => { it('useQuery', async () => { const { zero, mutators, queries, useQuery } = await setupTestEnvironment() const { data: rows, status } = useQuery(() => queries.table()) expect(rows.value).toMatchInlineSnapshot(`[ { "a": 1, "b": "a", Symbol(rc): 1, }, { "a": 2, "b": "b", Symbol(rc): 1, },]`) expect(status.value).toEqual('unknown')
await zero.value.mutate(mutators.table.insert({ a: 3, b: 'c' })).client await nextTick()
expect(rows.value).toMatchInlineSnapshot(`[ { "a": 1, "b": "a", Symbol(rc): 1, }, { "a": 2, "b": "b", Symbol(rc): 1, }, { "a": 3, "b": "c", Symbol(rc): 1, },]`)
// TODO: this is not working at the moment, possibly because we don't have a server connection in test // expect(resultType.value).toEqual("complete"); })
it('useQuery with ttl', async () => { const { zero, queries, useQuery } = await setupTestEnvironment()
const ttl = ref<TTL>('1m')
const materializeSpy = vi.spyOn(zero.value, 'materialize') const queryGetter = vi.fn(() => queries.table())
useQuery(queryGetter, () => ({ ttl: ttl.value })) expect(queryGetter).toHaveBeenCalledTimes(1)
expect(materializeSpy).toHaveLastReturnedWith(expect.any(VueView)) expect(materializeSpy).toHaveBeenCalledExactlyOnceWith( expect.any(Object), vueViewFactory, { ttl: '1m' }, ) expect(materializeSpy.mock.calls[0]![0]).toMatchInlineSnapshot(` QueryImpl { "customQueryID": { "args": [], "name": "table", }, "format": { "relationships": {}, "singular": false, }, "limit": [Function], "one": [Function], "orderBy": [Function], "related": [Function], "start": [Function], "where": [Function], "whereExists": [Function], Symbol(): true, } `)
const view: VueView = materializeSpy.mock.results[0]!.value const updateTTLSpy = vi.spyOn(view, 'updateTTL')
materializeSpy.mockClear()
ttl.value = '10m' await nextTick()
expect(materializeSpy).toHaveBeenCalledTimes(0) expect(updateTTLSpy).toHaveBeenCalledExactlyOnceWith('10m') })
it('useQuery supports disabled maybe queries', async () => { const { zero, queries, useQuery } = await setupTestEnvironment() const enabled = ref(false) const materializeSpy = vi.spyOn(zero.value, 'materialize')
const { data: rows, status } = useQuery(() => enabled.value && queries.table())
expect(rows.value).toBeUndefined() expect(status.value).toBe('disabled') expect(materializeSpy).not.toHaveBeenCalled()
enabled.value = true await nextTick()
expect(materializeSpy).toHaveBeenCalledTimes(1) expect(rows.value).toMatchInlineSnapshot(`[ { "a": 1, "b": "a", Symbol(rc): 1, }, { "a": 2, "b": "b", Symbol(rc): 1, },]`) expect(status.value).toEqual('unknown')
const view: VueView = materializeSpy.mock.results[0]!.value const destroySpy = vi.spyOn(view, 'destroy')
enabled.value = false await nextTick()
expect(destroySpy).toHaveBeenCalledTimes(1) expect(rows.value).toBeUndefined() expect(status.value).toBe('disabled') })
it('useQuery deps change', async () => { const { queries, useQuery } = await setupTestEnvironment()
const a = ref(1)
const { data: rows, status } = useQuery(() => queries.byId(a.value), )
const rowLog: unknown[] = [] const resultDetailsLog: unknown[] = [] const resetLogs = () => { rowLog.length = 0 resultDetailsLog.length = 0 }
watchEffect(() => { rowLog.push(rows.value) })
watchEffect(() => { resultDetailsLog.push(status.value) })
expect(rowLog).toMatchInlineSnapshot(`[ [ { "a": 1, "b": "a", Symbol(rc): 1, }, ],]`) // expect(resultDetailsLog).toEqual(['unknown']) resetLogs()
expect(rowLog).toEqual([]) // expect(resultDetailsLog).toEqual(['complete']) resetLogs()
a.value = 2 await nextTick()
expect(rowLog).toMatchInlineSnapshot(`[ [ { "a": 2, "b": "b", Symbol(rc): 1, }, ],]`) // expect(resultDetailsLog).toEqual(["unknown"]); resetLogs()
expect(rowLog).toEqual([]) // expect(resultDetailsLog).toEqual(["complete"]); })
it('re-materializes when query format changes', async () => { const { zero, useQuery } = await setupTestEnvironment() const zql = createBuilder(schema) const pluralQuery = zql.table.where('a', 1).limit(1) const singularQuery = zql.table.where('a', 1).one() expect(asQueryInternals(pluralQuery).hash()).toBe(asQueryInternals(singularQuery).hash())
const singular = ref(false) const materializeSpy = vi.spyOn(zero.value, 'materialize') const query = () => singular.value ? singularQuery : pluralQuery
const { data } = useQuery(query as Parameters<typeof useQuery>[0])
expect(materializeSpy).toHaveBeenCalledTimes(1) expect(data.value).toMatchObject([{ a: 1, b: 'a' }])
singular.value = true await nextTick()
expect(materializeSpy).toHaveBeenCalledTimes(2) expect(data.value).toMatchObject({ a: 1, b: 'a' }) })
it('re-materializes when nested query format changes', async () => { const issue = table('issue') .columns({ id: string() }) .primaryKey('id') const comment = table('comment') .columns({ id: string(), issueID: string() }) .primaryKey('id') const nestedSchema = createSchema({ tables: [issue, comment], relationships: [ relationships(issue, ({ many }) => ({ comments: many({ sourceField: ['id'], destField: ['issueID'], destSchema: comment, }), })), ], }) const zero = new Zero({ userID: 'test-user', server: null, schema: nestedSchema, kvStore: 'mem' as const, }) const zql = createBuilder(nestedSchema) const pluralQuery = zql.issue .where('id', 'i1') .related('comments', q => q.limit(1)) const singularQuery = zql.issue .where('id', 'i1') .related('comments', q => q.one()) expect(asQueryInternals(pluralQuery).hash()).toBe(asQueryInternals(singularQuery).hash())
onTestFinished(async () => { await zero.close() })
const singular = ref(false) const materializeSpy = vi.spyOn(zero, 'materialize') const query = () => singular.value ? singularQuery : pluralQuery
useQuery(zero, query as never)
expect(materializeSpy).toHaveBeenCalledTimes(1)
singular.value = true await nextTick()
expect(materializeSpy).toHaveBeenCalledTimes(2) })
it('useQuery deps change watchEffect', async () => { const { zero, queries, mutators, useQuery } = await setupTestEnvironment() const a = ref(1) const { data: rows } = useQuery(() => queries.byId(a.value))
let run = 0
await new Promise((resolve) => { watchEffect(() => { if (run === 0) { expect(rows.value).toMatchInlineSnapshot( `[ { "a": 1, "b": "a", Symbol(rc): 1, },]`, ) zero.value.mutate(mutators.table.update({ a: 1, b: 'a2' })) } else if (run === 1) { expect(rows.value).toMatchInlineSnapshot( `[ { "a": 1, "b": "a2", Symbol(rc): 1, },]`, ) a.value = 2 } else if (run === 2) { expect(rows.value).toMatchInlineSnapshot( `[ { "a": 2, "b": "b", Symbol(rc): 1, },]`, ) resolve(true) } run++ }) }) })
it('can still be used without createZero', async () => { const defineMutators = defineMutatorsWithType<typeof schema>() const defineMutator = defineMutatorWithType<typeof schema>() const mutators = defineMutators({ table: { insert: defineMutator( z.object({ a: z.number(), b: z.string() }), async ({ tx, args: { a, b } }) => { return tx.mutate.table.insert({ a, b }) }, ), }, }) const zero = new Zero({ userID: 'test-user', server: null, schema, mutators, kvStore: 'mem' as const, }) await zero.mutate(mutators.table.insert({ a: 1, b: 'a' })).client await zero.mutate(mutators.table.insert({ a: 2, b: 'b' })).client
const zql = createBuilder(schema) const defineQueries = defineQueriesWithType<typeof schema>() const queries = defineQueries({ table: defineQuery( () => zql.table, ), })
const { data: rows, status } = useQuery(zero, () => queries.table()) expect(rows.value).toMatchInlineSnapshot(`[ { "a": 1, "b": "a", Symbol(rc): 1, }, { "a": 2, "b": "b", Symbol(rc): 1, },]`) expect(status.value).toEqual('unknown') })
it('still works with legacy queries', async () => { const schema = createSchema({ tables: [ table('table') .columns({ a: number(), b: string(), }) .primaryKey('a'), ], enableLegacyQueries: true, enableLegacyMutators: true, })
const zero = new Zero({ userID: 'test-user', server: null, schema, kvStore: 'mem' as const, })
const { data } = useQuery(zero, zero.query.table) expect(data.value).toEqual([])
await zero.mutate.table.insert({ a: 1, b: 'a2' })
expect(data.value).toMatchInlineSnapshot(` [ { "a": 1, "b": "a2", Symbol(rc): 1, }, ] `) })})