Skip to content

Commit e701544

Browse files
authored
feat: error forwarding (#141)
1 parent c38d4d8 commit e701544

File tree

2 files changed

+85
-26
lines changed

2 files changed

+85
-26
lines changed

src/query.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
// based on https://github.com/rocicorp/mono/tree/main/packages/zero-solid
22

3-
import type { CustomMutatorDefs, HumanReadable, Query, ResultType, Schema, TTL, Zero } from '@rocicorp/zero'
3+
import type { CustomMutatorDefs, HumanReadable, Query, Schema, TTL, Zero } from '@rocicorp/zero'
44
import type { ComputedRef, MaybeRefOrGetter } from 'vue'
5-
import type { VueView } from './view'
5+
import type { QueryError, QueryStatus, VueView } from './view'
66

77
import {
88
computed,
@@ -22,7 +22,8 @@ export interface UseQueryOptions {
2222

2323
export interface QueryResult<TReturn> {
2424
data: ComputedRef<HumanReadable<TReturn>>
25-
status: ComputedRef<ResultType>
25+
status: ComputedRef<QueryStatus>
26+
error: ComputedRef<QueryError & { refetch: () => void } | undefined>
2627
}
2728

2829
/**
@@ -60,9 +61,14 @@ export function useQueryWithZero<
6061
return toValue(options)?.ttl ?? DEFAULT_TTL_MS
6162
})
6263
const view = shallowRef<VueView<HumanReadable<TReturn>> | null>(null)
64+
const refetchKey = shallowRef(0)
6365

6466
watch(
65-
[() => toValue(query), () => toValue(z)],
67+
[
68+
() => toValue(query),
69+
() => toValue(z),
70+
refetchKey,
71+
],
6672
([q, z]) => {
6773
view.value?.destroy()
6874

@@ -88,5 +94,12 @@ export function useQueryWithZero<
8894
return {
8995
data: computed(() => view.value!.data),
9096
status: computed(() => view.value!.status),
97+
error: computed(() => view.value!.error
98+
? {
99+
refetch: () => { refetchKey.value++ },
100+
...view.value!.error,
101+
}
102+
: undefined,
103+
),
91104
}
92105
}

src/view.ts

Lines changed: 68 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,66 +8,97 @@ import type {
88
Input,
99
Output,
1010
Query,
11-
ResultType,
11+
ReadonlyJSONValue,
1212
Schema,
1313
TTL,
1414
ViewFactory,
1515
} from '@rocicorp/zero'
16+
import type { Ref } from 'vue'
1617
import { applyChange } from '@rocicorp/zero'
17-
import { reactive } from 'vue'
18-
19-
interface QueryResultDetails {
20-
readonly type: ResultType
18+
import { ref } from 'vue'
19+
20+
// zero does not export this type
21+
type ErroredQuery = {
22+
error: 'app'
23+
queryName: string
24+
details: ReadonlyJSONValue
25+
} | {
26+
error: 'zero'
27+
queryName: string
28+
details: ReadonlyJSONValue
29+
} | {
30+
error: 'http'
31+
queryName: string
32+
status: number
33+
details: ReadonlyJSONValue
2134
}
2235

23-
type State = [Entry, QueryResultDetails]
24-
25-
const complete = { type: 'complete' } as const
26-
const unknown = { type: 'unknown' } as const
36+
export type QueryStatus = 'complete' | 'unknown' | 'error'
37+
38+
export type QueryError = {
39+
type: 'app'
40+
queryName: string
41+
details: ReadonlyJSONValue
42+
} | {
43+
type: 'http'
44+
queryName: string
45+
status: number
46+
details: ReadonlyJSONValue
47+
}
2748

2849
export class VueView<V> implements Output {
2950
readonly #input: Input
3051
readonly #format: Format
3152
readonly #onDestroy: () => void
3253
readonly #updateTTL: (ttl: TTL) => void
3354

34-
#state: State
55+
#data: Ref<Entry>
56+
#status: Ref<QueryStatus>
57+
#error: Ref<QueryError | undefined>
3558

3659
constructor(
3760
input: Input,
3861
onTransactionCommit: (cb: () => void) => void,
39-
format: Format = { singular: false, relationships: {} },
62+
format: Format,
4063
onDestroy: () => void = () => {},
41-
queryComplete: true | Promise<true>,
64+
queryComplete: true | ErroredQuery | Promise<true>,
4265
updateTTL: (ttl: TTL) => void,
4366
) {
4467
this.#input = input
4568
this.#format = format
4669
this.#onDestroy = onDestroy
4770
this.#updateTTL = updateTTL
48-
this.#state = reactive([
49-
{ '': format.singular ? undefined : [] },
50-
queryComplete === true ? complete : unknown,
51-
])
71+
this.#data = ref({ '': format.singular ? undefined : [] })
72+
this.#status = ref(queryComplete === true ? 'complete' : 'error' in queryComplete ? 'error' : 'unknown')
73+
this.#error = ref(queryComplete !== true && 'error' in queryComplete ? makeError(queryComplete) : undefined) as Ref<QueryError | undefined>
74+
5275
input.setOutput(this)
5376

5477
for (const node of input.fetch({})) {
5578
this.#applyChange({ type: 'add', node })
5679
}
5780

58-
if (queryComplete !== true) {
81+
if (queryComplete !== true && !('error' in queryComplete)) {
5982
void queryComplete.then(() => {
60-
this.#state[1] = complete
83+
this.#status.value = 'complete'
84+
this.#error.value = undefined
85+
}).catch((error: ErroredQuery) => {
86+
this.#status.value = 'error'
87+
this.#error.value = makeError(error)
6188
})
6289
}
6390
}
6491

6592
get data() {
66-
return this.#state[0][''] as V
93+
return this.#data.value[''] as V
6794
}
6895

6996
get status() {
70-
return this.#state[1].type
97+
return this.#status.value
98+
}
99+
100+
get error() {
101+
return this.#error.value
71102
}
72103

73104
destroy() {
@@ -76,7 +107,7 @@ export class VueView<V> implements Output {
76107

77108
#applyChange(change: Change): void {
78109
applyChange(
79-
this.#state[0],
110+
this.#data.value,
80111
change,
81112
this.#input.getSchema(),
82113
'',
@@ -93,6 +124,21 @@ export class VueView<V> implements Output {
93124
}
94125
}
95126

127+
function makeError(error: ErroredQuery): QueryError {
128+
return error.error === 'app' || error.error === 'zero'
129+
? {
130+
type: 'app',
131+
queryName: error.queryName,
132+
details: error.details,
133+
}
134+
: {
135+
type: 'http',
136+
queryName: error.queryName,
137+
status: error.status,
138+
details: error.details,
139+
}
140+
}
141+
96142
export function vueViewFactory<
97143
TSchema extends Schema,
98144
TTable extends keyof TSchema['tables'] & string,
@@ -103,7 +149,7 @@ export function vueViewFactory<
103149
format: Format,
104150
onDestroy: () => void,
105151
onTransactionCommit: (cb: () => void) => void,
106-
queryComplete: true | Promise<true>,
152+
queryComplete: true | ErroredQuery | Promise<true>,
107153
updateTTL?: (ttl: TTL) => void,
108154
) {
109155
interface UpdateTTL {

0 commit comments

Comments
 (0)