[READ-ONLY] Mirror of https://github.com/danielroe/zero-vue. Vue bindings for Zero
sync
vue
zero
1// based on https://github.com/rocicorp/mono/tree/main/packages/zero-solid
2
3import type {
4 CustomMutatorDefs,
5 DefaultContext,
6 DefaultSchema,
7 Falsy,
8 HumanReadable,
9 PullRow,
10 QueryOrQueryRequest,
11 ReadonlyJSONValue,
12 Schema,
13 TTL,
14 Zero,
15} from '@rocicorp/zero'
16import type { ComputedRef, MaybeRefOrGetter } from 'vue'
17import type { QueryError, QueryStatus, VueView } from './view'
18
19import { addContextToQuery, asQueryInternals, DEFAULT_TTL_MS } from '@rocicorp/zero/bindings'
20import {
21 computed,
22 getCurrentInstance,
23 onUnmounted,
24 shallowRef,
25 toValue,
26 watch,
27} from 'vue'
28import { vueViewFactory } from './view'
29
30export interface UseQueryOptions {
31 ttl?: TTL | undefined
32}
33
34export interface QueryResult<TReturn> {
35 data: ComputedRef<HumanReadable<TReturn>>
36 status: ComputedRef<QueryStatus>
37 error: ComputedRef<QueryError & { retry: () => void } | undefined>
38}
39
40export interface MaybeQueryResult<TReturn> {
41 data: ComputedRef<HumanReadable<TReturn> | undefined>
42 status: ComputedRef<QueryStatus | 'disabled'>
43 error: ComputedRef<QueryError & { retry: () => void } | undefined>
44}
45
46// Overload 1: Query
47export function useQuery<
48 TTable extends keyof TSchema['tables'] & string,
49 TInput extends ReadonlyJSONValue | undefined,
50 TOutput extends ReadonlyJSONValue | undefined,
51 TSchema extends Schema = DefaultSchema,
52 TReturn = PullRow<TTable, TSchema>,
53 TContext = DefaultContext,
54 MD extends CustomMutatorDefs | undefined = undefined,
55>(
56 z: MaybeRefOrGetter<Zero<TSchema, MD, TContext>>,
57 query: MaybeRefOrGetter<QueryOrQueryRequest<TTable, TInput, TOutput, TSchema, TReturn, TContext>>,
58 options?: MaybeRefOrGetter<UseQueryOptions>,
59): QueryResult<TReturn>
60
61// Overload 2: Maybe query
62export function useQuery<
63 TTable extends keyof TSchema['tables'] & string,
64 TInput extends ReadonlyJSONValue | undefined,
65 TOutput extends ReadonlyJSONValue | undefined,
66 TSchema extends Schema = DefaultSchema,
67 TReturn = PullRow<TTable, TSchema>,
68 TContext = DefaultContext,
69 MD extends CustomMutatorDefs | undefined = undefined,
70>(
71 z: MaybeRefOrGetter<Zero<TSchema, MD, TContext>>,
72 query: MaybeRefOrGetter<QueryOrQueryRequest<TTable, TInput, TOutput, TSchema, TReturn, TContext> | Falsy>,
73 options?: MaybeRefOrGetter<UseQueryOptions>,
74): MaybeQueryResult<TReturn>
75
76// Implementation
77export function useQuery<
78 TTable extends keyof TSchema['tables'] & string,
79 TInput extends ReadonlyJSONValue | undefined,
80 TOutput extends ReadonlyJSONValue | undefined,
81 TSchema extends Schema = DefaultSchema,
82 TReturn = PullRow<TTable, TSchema>,
83 TContext = DefaultContext,
84 MD extends CustomMutatorDefs | undefined = undefined,
85>(
86 z: MaybeRefOrGetter<Zero<TSchema, MD, TContext>>,
87 query: MaybeRefOrGetter<QueryOrQueryRequest<TTable, TInput, TOutput, TSchema, TReturn, TContext> | Falsy>,
88 options?: MaybeRefOrGetter<UseQueryOptions>,
89): QueryResult<TReturn> | MaybeQueryResult<TReturn> {
90 const ttl = computed(() => toValue(options)?.ttl ?? DEFAULT_TTL_MS)
91 const view = shallowRef<VueView | null>(null)
92 const refetchKey = shallowRef(0)
93
94 const q = shallowRef()
95 watch(
96 [
97 () => toValue(query),
98 () => toValue(z),
99 ],
100 ([query, z]) => {
101 q.value = query ? addContextToQuery(toValue(query), toValue(z).context) : undefined
102 },
103 { immediate: true },
104 )
105
106 const qi = computed(() => q.value ? asQueryInternals(q.value) : undefined)
107 const hash = computed(() => qi.value?.hash())
108
109 watch(
110 [
111 () => toValue(z),
112 hash,
113 refetchKey,
114 ],
115 ([z]) => {
116 view.value?.destroy()
117 if (!hash.value) {
118 view.value = null
119 }
120 else {
121 view.value = z.materialize(toValue(q), vueViewFactory, { ttl: toValue(ttl) })
122 }
123 },
124 { immediate: true },
125 )
126
127 watch(ttl, (ttl) => {
128 toValue(view)?.updateTTL(ttl)
129 })
130
131 if (getCurrentInstance()) {
132 onUnmounted(() => view.value?.destroy())
133 }
134
135 return {
136 data: computed(() => view.value?.data as HumanReadable<TReturn>),
137 status: computed(() => view.value?.status ?? 'disabled'),
138 error: computed(() => view.value?.error
139 ? {
140 retry: () => { refetchKey.value++ },
141 ...view.value.error,
142 }
143 : undefined,
144 ),
145 }
146}