Skip to content

Commit fd078a7

Browse files
feat: support synced queries (#119)
1 parent d9d18c8 commit fd078a7

File tree

14 files changed

+596
-110
lines changed

14 files changed

+596
-110
lines changed

README.md

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,48 @@ npm install zero-vue
1919
pnpm install zero-vue
2020
```
2121

22-
```js
23-
import { Zero } from '@rocicorp/zero'
24-
import { useQuery } from 'zero-vue'
25-
26-
// see docs: https://zero.rocicorp.dev/docs/introduction
27-
const z = new Zero({
22+
Creating `useZero` and `useQuery` composables:
23+
```ts
24+
import { createZero } from 'zero-vue'
25+
import { mutators } from './mutators.ts'
26+
import { schema } from './schema.ts'
27+
28+
// see docs for all options: https://zero.rocicorp.dev/docs/introduction
29+
const { useZero, useQuery } = createZero({
2830
userID,
2931
server: import.meta.env.VITE_PUBLIC_SERVER,
3032
schema,
33+
mutators,
34+
kvStore: 'mem',
35+
})
36+
37+
// OR with computed options:
38+
const { useZero, useQuery } = createZero(() => ({
39+
userID: userID.value,
40+
server: import.meta.env.VITE_PUBLIC_SERVER,
41+
schema,
42+
mutators,
3143
kvStore: 'mem',
44+
}))
45+
46+
// OR with a Zero instance:
47+
const { useZero, useQuery } = createZero({
48+
zero: new Zero({
49+
userID,
50+
server: import.meta.env.VITE_PUBLIC_SERVER,
51+
schema,
52+
mutators,
53+
kvStore: 'mem',
54+
}),
3255
})
56+
```
57+
58+
To query data:
59+
```js
60+
import { useQuery, useZero } from './use-zero.ts'
3361

34-
const { data: users } = useQuery(z.query.user)
62+
const z = useZero()
63+
const { data: users } = useQuery(() => z.value.query.user)
3564
```
3665

3766
> [!TIP]

playground/.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ ZERO_AUTH_SECRET="secretkey"
55
ZERO_REPLICA_FILE="/tmp/zstart_replica.db"
66

77
VITE_PUBLIC_SERVER='http://localhost:4848'
8+
VITE_PUBLIC_AUTH_SECRET='secretkey'

playground/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
"test:types": "vue-tsc --build"
1515
},
1616
"dependencies": {
17+
"@vueuse/integrations": "^13.9.0",
1718
"jose": "^6.0.0",
18-
"js-cookie": "^3.0.5",
19+
"universal-cookie": "^7.2.2",
1920
"vue": "^3.5.13",
2021
"zero-vue": "latest"
2122
},

playground/src/app.vue

Lines changed: 23 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,28 @@
11
<script setup lang="ts">
2-
import { escapeLike, Zero } from '@rocicorp/zero'
3-
import { decodeJwt } from 'jose'
4-
import Cookies from 'js-cookie'
5-
import { computed, ref } from 'vue'
6-
import { useQuery } from 'zero-vue'
2+
import { escapeLike } from '@rocicorp/zero'
3+
import { useCookies } from '@vueuse/integrations/useCookies'
74
5+
import { SignJWT } from 'jose'
6+
import { computed, ref } from 'vue'
87
import { useInterval } from '~/composables/use-interval'
98
import { randomMessage } from '~/db/data/test-data'
10-
import { schema } from '~/db/schema'
119
import { formatDate } from '~/utils/date'
1210
import { randInt } from '~/utils/rand'
11+
import { useQuery, useZero } from './zero'
1312
14-
const encodedJWT = Cookies.get('jwt')
15-
const decodedJWT = encodedJWT && decodeJwt(encodedJWT)
16-
const userID = decodedJWT?.sub ? (decodedJWT.sub as string) : 'anon'
17-
18-
const z = new Zero({
19-
userID,
20-
auth: () => encodedJWT || undefined,
21-
server: import.meta.env.VITE_PUBLIC_SERVER,
22-
schema,
23-
// This is often easier to develop with if you're frequently changing
24-
// the schema. Switch to 'idb' for local-persistence.
25-
kvStore: 'mem',
26-
})
13+
const cookies = useCookies()
2714
28-
const { data: users } = useQuery(z.query.user)
29-
const { data: mediums } = useQuery(z.query.medium)
30-
const { data: allMessages } = useQuery(z.query.message)
15+
const z = useZero()
16+
const { data: users } = useQuery(() => z.value.query.user)
17+
const { data: mediums } = useQuery(() => z.value.query.medium)
18+
const { data: allMessages } = useQuery(() => z.value.query.message)
3119
3220
const filterUser = ref('')
3321
const filterText = ref('')
3422
const action = ref<'add' | 'remove' | undefined>(undefined)
3523
3624
const { data: filteredMessages } = useQuery(() => {
37-
let filtered = z.query.message
25+
let filtered = z.value.query.message
3826
.related('medium', medium => medium.one())
3927
.related('sender', sender => sender.one())
4028
.orderBy('timestamp', 'desc')
@@ -57,13 +45,13 @@ function deleteRandomMessage() {
5745
return false
5846
}
5947
const index = randInt(allMessages.value.length)
60-
z.mutate.message.delete({ id: allMessages.value[index]!.id })
48+
z.value.mutate.message.delete({ id: allMessages.value[index]!.id })
6149
6250
return true
6351
}
6452
6553
function addRandomMessage() {
66-
z.mutate.message.insert(randomMessage(users.value, mediums.value))
54+
z.value.mutate.message.insert(randomMessage(users.value, mediums.value))
6755
return true
6856
}
6957
@@ -97,7 +85,7 @@ function handleAddAction() {
9785
}
9886
9987
function handleRemoveAction(e: MouseEvent | TouchEvent) {
100-
if (z.userID === 'anon' && 'shiftKey' in e && !e.shiftKey) {
88+
if (z.value.userID === 'anon' && 'shiftKey' in e && !e.shiftKey) {
10189
// eslint-disable-next-line no-alert
10290
alert('You must be logged in to delete. Hold shift to try anyway.')
10391
return
@@ -119,7 +107,7 @@ function stopAction() {
119107
}
120108
121109
function editMessage(e: MouseEvent, id: string, senderID: string, prev: string) {
122-
if (senderID !== z.userID && !e.shiftKey) {
110+
if (senderID !== z.value.userID && !e.shiftKey) {
123111
// eslint-disable-next-line no-alert
124112
alert(
125113
'You aren\'t logged in as the sender of this message. Editing won\'t be permitted. Hold the shift key to try anyway.',
@@ -129,23 +117,25 @@ function editMessage(e: MouseEvent, id: string, senderID: string, prev: string)
129117
130118
// eslint-disable-next-line no-alert
131119
const body = prompt('Edit message', prev)
132-
z.mutate.message.update({
120+
z.value.mutate.message.update({
133121
id,
134122
body: body ?? prev,
135123
})
136124
}
137125
138126
async function toggleLogin() {
139-
if (z.userID === 'anon') {
140-
await fetch('/api/login')
127+
if (z.value.userID === 'anon') {
128+
const jwt = await new SignJWT({ sub: 'ENzoNm7g4E' })
129+
.setProtectedHeader({ alg: 'HS256' })
130+
.sign(new TextEncoder().encode(import.meta.env.VITE_PUBLIC_AUTH_SECRET))
131+
cookies.set('jwt', jwt)
141132
}
142133
else {
143-
Cookies.remove('jwt')
134+
cookies.remove('jwt')
144135
}
145-
location.reload()
146136
}
147137
148-
const user = computed(() => users.value.find(user => user.id === z.userID)?.name ?? 'anon')
138+
const user = computed(() => users.value.find(user => user.id === z.value.userID)?.name ?? 'anon')
149139
</script>
150140

151141
<template>

playground/src/zero.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { useCookies } from '@vueuse/integrations/useCookies'
2+
import { decodeJwt } from 'jose'
3+
import { createZeroComposables } from 'zero-vue'
4+
5+
import { schema } from '~/db/schema'
6+
7+
const cookies = useCookies()
8+
9+
export const { useZero, useQuery } = createZeroComposables(() => {
10+
const encodedJWT = cookies.get('jwt')
11+
const decodedJWT = encodedJWT && decodeJwt(encodedJWT)
12+
const userID = decodedJWT?.sub ? (decodedJWT.sub as string) : 'anon'
13+
14+
return {
15+
userID,
16+
auth: () => encodedJWT || undefined,
17+
server: import.meta.env.VITE_PUBLIC_SERVER,
18+
schema,
19+
// This is often easier to develop with if you're frequently changing
20+
// the schema. Switch to 'idb' for local-persistence.
21+
kvStore: 'mem',
22+
}
23+
})

0 commit comments

Comments
 (0)