Skip to content

Commit 95d91ac

Browse files
authored
feat: upgrade to zero v0.18 (danielroe#52)
1 parent e4c2a7d commit 95d91ac

File tree

9 files changed

+1776
-184
lines changed

9 files changed

+1776
-184
lines changed

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,11 @@
3535
"vue": "^3.5.13"
3636
},
3737
"dependencies": {
38-
"@rocicorp/zero": "^0.17.0"
38+
"@rocicorp/zero": "^0.18.0"
3939
},
4040
"devDependencies": {
4141
"@antfu/eslint-config": "latest",
42+
"@rocicorp/resolver": "^1.0.2",
4243
"@vitest/coverage-v8": "latest",
4344
"bumpp": "latest",
4445
"changelogithub": "13.13.0",

playground/src/db/schema.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ const messageRelationships = relationships(message, ({ one }) => ({
5555
}),
5656
}))
5757

58-
export const schema = createSchema(1, {
58+
export const schema = createSchema({
5959
tables: [user, medium, message],
6060
relationships: [messageRelationships],
6161
})

pnpm-lock.yaml

+168-134
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export { useQuery } from './query'
1+
export { useQuery, type UseQueryOptions } from './query'

src/query.test.ts

+222
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
import type { TTL } from '@rocicorp/zero'
2+
import { createSchema, number, string, table, Zero } from '@rocicorp/zero'
3+
import { describe, expect, it, vi } from 'vitest'
4+
import { ref, watchEffect } from 'vue'
5+
import { useQuery } from './query'
6+
import { vueViewFactory } from './view'
7+
8+
async function setupTestEnvironment() {
9+
const schema = createSchema({
10+
tables: [
11+
table('table')
12+
.columns({
13+
a: number(),
14+
b: string(),
15+
})
16+
.primaryKey('a'),
17+
],
18+
})
19+
20+
const z = new Zero({
21+
userID: 'asdf',
22+
server: null,
23+
schema,
24+
// This is often easier to develop with if you're frequently changing
25+
// the schema. Switch to 'idb' for local-persistence.
26+
kvStore: 'mem',
27+
})
28+
29+
await z.mutate.table.insert({ a: 1, b: 'a' })
30+
await z.mutate.table.insert({ a: 2, b: 'b' })
31+
32+
const tableQuery = z.query.table
33+
34+
return { z, tableQuery }
35+
}
36+
37+
describe('useQuery', () => {
38+
it('useQuery', async () => {
39+
const { z, tableQuery } = await setupTestEnvironment()
40+
41+
const { data: rows, status } = useQuery(() => tableQuery)
42+
expect(rows.value).toMatchInlineSnapshot(`[
43+
{
44+
"a": 1,
45+
"b": "a",
46+
Symbol(rc): 1,
47+
},
48+
{
49+
"a": 2,
50+
"b": "b",
51+
Symbol(rc): 1,
52+
},
53+
]`)
54+
expect(status.value).toEqual('unknown')
55+
56+
await z.mutate.table.insert({ a: 3, b: 'c' })
57+
await 1
58+
59+
expect(rows.value).toMatchInlineSnapshot(`[
60+
{
61+
"a": 1,
62+
"b": "a",
63+
Symbol(rc): 1,
64+
},
65+
{
66+
"a": 2,
67+
"b": "b",
68+
Symbol(rc): 1,
69+
},
70+
{
71+
"a": 3,
72+
"b": "c",
73+
Symbol(rc): 1,
74+
},
75+
]`)
76+
77+
// TODO: this is not working at the moment, possibly because we don't have a server connection in test
78+
// expect(resultType.value).toEqual("complete");
79+
80+
z.close()
81+
})
82+
83+
it('useQuery with ttl', async () => {
84+
const { z, tableQuery } = await setupTestEnvironment()
85+
const ttl = ref<TTL>('1m')
86+
87+
const materializeSpy = vi.spyOn(tableQuery, 'materialize')
88+
const updateTTLSpy = vi.spyOn(tableQuery, 'updateTTL')
89+
const queryGetter = vi.fn(() => tableQuery)
90+
91+
useQuery(queryGetter, () => ({ ttl: ttl.value }))
92+
93+
expect(queryGetter).toHaveBeenCalledTimes(1)
94+
expect(updateTTLSpy).toHaveBeenCalledTimes(0)
95+
expect(materializeSpy).toHaveBeenCalledExactlyOnceWith(
96+
vueViewFactory,
97+
'1m',
98+
)
99+
materializeSpy.mockClear()
100+
101+
ttl.value = '10m'
102+
await 1
103+
104+
expect(materializeSpy).toHaveBeenCalledTimes(0)
105+
expect(updateTTLSpy).toHaveBeenCalledExactlyOnceWith('10m')
106+
107+
z.close()
108+
})
109+
110+
it('useQuery deps change', async () => {
111+
const { z, tableQuery } = await setupTestEnvironment()
112+
113+
const a = ref(1)
114+
115+
const { data: rows, status } = useQuery(() =>
116+
tableQuery.where('a', a.value),
117+
)
118+
119+
const rowLog: unknown[] = []
120+
const resultDetailsLog: unknown[] = []
121+
const resetLogs = () => {
122+
rowLog.length = 0
123+
resultDetailsLog.length = 0
124+
}
125+
126+
watchEffect(() => {
127+
rowLog.push(rows.value)
128+
})
129+
130+
watchEffect(() => {
131+
resultDetailsLog.push(status.value)
132+
})
133+
134+
expect(rowLog).toMatchInlineSnapshot(`[
135+
[
136+
{
137+
"a": 1,
138+
"b": "a",
139+
Symbol(rc): 1,
140+
},
141+
],
142+
]`)
143+
// expect(resultDetailsLog).toEqual(["unknown"]);
144+
resetLogs()
145+
146+
expect(rowLog).toEqual([])
147+
// expect(resultDetailsLog).toEqual(["complete"]);
148+
resetLogs()
149+
150+
a.value = 2
151+
await 1
152+
153+
expect(rowLog).toMatchInlineSnapshot(`[
154+
[
155+
{
156+
"a": 2,
157+
"b": "b",
158+
Symbol(rc): 1,
159+
},
160+
],
161+
]`)
162+
// expect(resultDetailsLog).toEqual(["unknown"]);
163+
resetLogs()
164+
165+
expect(rowLog).toEqual([])
166+
// expect(resultDetailsLog).toEqual(["complete"]);
167+
168+
z.close()
169+
})
170+
171+
it('useQuery deps change watchEffect', async () => {
172+
const { z, tableQuery } = await setupTestEnvironment()
173+
const a = ref(1)
174+
const { data: rows } = useQuery(() => tableQuery.where('a', a.value))
175+
176+
let run = 0
177+
178+
await new Promise((resolve) => {
179+
watchEffect(() => {
180+
if (run === 0) {
181+
expect(rows.value).toMatchInlineSnapshot(
182+
`[
183+
{
184+
"a": 1,
185+
"b": "a",
186+
Symbol(rc): 1,
187+
},
188+
]`,
189+
)
190+
z.mutate.table.update({ a: 1, b: 'a2' })
191+
}
192+
else if (run === 1) {
193+
expect(rows.value).toMatchInlineSnapshot(
194+
`[
195+
{
196+
"a": 1,
197+
"b": "a2",
198+
Symbol(rc): 1,
199+
},
200+
]`,
201+
)
202+
a.value = 2
203+
}
204+
else if (run === 2) {
205+
expect(rows.value).toMatchInlineSnapshot(
206+
`[
207+
{
208+
"a": 2,
209+
"b": "b",
210+
Symbol(rc): 1,
211+
},
212+
]`,
213+
)
214+
resolve(true)
215+
}
216+
run++
217+
})
218+
})
219+
220+
z.close()
221+
})
222+
})

src/query.ts

+39-16
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,24 @@
11
// based on https://github.com/rocicorp/mono/tree/main/packages/zero-solid
22

3-
import type { ResultType, Schema } from '@rocicorp/zero'
4-
import type { AdvancedQuery, HumanReadable, Query } from '@rocicorp/zero/advanced'
3+
import type { HumanReadable, Query, ResultType, Schema, TTL } from '@rocicorp/zero'
54
import type { ComputedRef, MaybeRefOrGetter } from 'vue'
5+
import type { VueView } from './view'
66

7-
import { computed, getCurrentInstance, isRef, onUnmounted, shallowRef, toValue, watch } from 'vue'
7+
import { DEFAULT_TTL } from '@rocicorp/zero'
8+
import {
9+
computed,
10+
getCurrentInstance,
11+
onUnmounted,
12+
shallowRef,
13+
toValue,
14+
watch,
15+
} from 'vue'
816
import { vueViewFactory } from './view'
917

18+
export interface UseQueryOptions {
19+
ttl?: TTL | undefined
20+
}
21+
1022
interface QueryResult<TReturn> {
1123
data: ComputedRef<HumanReadable<TReturn>>
1224
status: ComputedRef<ResultType>
@@ -16,23 +28,34 @@ export function useQuery<
1628
TSchema extends Schema,
1729
TTable extends keyof TSchema['tables'] & string,
1830
TReturn,
19-
>(_query: MaybeRefOrGetter<Query<TSchema, TTable, TReturn>>): QueryResult<TReturn> {
20-
const query = toValue(_query) as AdvancedQuery<TSchema, TTable, TReturn>
21-
const view = shallowRef(query.materialize(vueViewFactory))
22-
23-
if (isRef(_query) || typeof _query === 'function') {
24-
watch(_query, (query) => {
25-
view.value.destroy()
26-
view.value = (query as AdvancedQuery<TSchema, TTable, TReturn>).materialize(vueViewFactory)
27-
})
28-
}
31+
>(
32+
query: MaybeRefOrGetter<Query<TSchema, TTable, TReturn>>,
33+
options?: MaybeRefOrGetter<UseQueryOptions>,
34+
): QueryResult<TReturn> {
35+
const ttl = computed(() => {
36+
return toValue(options)?.ttl ?? DEFAULT_TTL
37+
})
38+
const view = shallowRef<VueView<HumanReadable<TReturn>> | null>(null)
39+
40+
watch(
41+
() => toValue(query),
42+
(q) => {
43+
view.value?.destroy()
44+
view.value = q.materialize(vueViewFactory, ttl.value)
45+
},
46+
{ immediate: true },
47+
)
48+
49+
watch(ttl, (ttl) => {
50+
toValue(query).updateTTL(ttl)
51+
})
2952

3053
if (getCurrentInstance()) {
31-
onUnmounted(() => view.value.destroy())
54+
onUnmounted(() => view.value!.destroy())
3255
}
3356

3457
return {
35-
data: computed(() => view.value.data),
36-
status: computed(() => view.value.status),
58+
data: computed(() => view.value!.data),
59+
status: computed(() => view.value!.status),
3760
}
3861
}

0 commit comments

Comments
 (0)