[READ-ONLY] Mirror of https://github.com/danielroe/zero-vue. Vue bindings for Zero
sync vue zero
0

Configure Feed

Select the types of activity you want to include in your feed.

zero-vue / src / view.ts
4.7 kB 196 lines
1// based on https://github.com/rocicorp/mono/tree/main/packages/zero-solid 2 3import type { 4 AnyViewFactory, 5 Change, 6 Entry, 7 ErroredQuery, 8 Format, 9 Input, 10 Node, 11 Output, 12 Query, 13 QueryErrorDetails, 14 QueryResultDetails, 15 Schema, 16 Stream, 17 TTL, 18} from '@rocicorp/zero' 19import type { ViewChange } from '@rocicorp/zero/bindings' 20import type { Ref } from 'vue' 21import { applyChange, skipYields } from '@rocicorp/zero/bindings' 22import { ref } from 'vue' 23 24export type QueryStatus = QueryResultDetails['type'] 25export type QueryError = QueryErrorDetails['error'] 26 27export class VueView implements Output { 28 readonly #input: Input 29 readonly #format: Format 30 readonly #onDestroy: () => void 31 readonly #updateTTL: (ttl: TTL) => void 32 33 #data: Ref<Entry> 34 #status: Ref<QueryStatus> 35 #error: Ref<QueryError | undefined> 36 #isDestroyed = false 37 38 constructor( 39 input: Input, 40 onTransactionCommit: (cb: () => void) => void, 41 format: Format, 42 onDestroy: () => void = () => {}, 43 queryComplete: true | ErroredQuery | Promise<true>, 44 updateTTL: (ttl: TTL) => void, 45 ) { 46 this.#input = input 47 this.#format = format 48 this.#onDestroy = onDestroy 49 this.#updateTTL = updateTTL 50 this.#data = ref({ '': format.singular ? undefined : [] }) 51 this.#status = ref(queryComplete === true ? 'complete' : 'error' in queryComplete ? 'error' : 'unknown') 52 this.#error = ref(queryComplete !== true && 'error' in queryComplete ? makeError(queryComplete) : undefined) as Ref<QueryError | undefined> 53 54 input.setOutput(this) 55 56 for (const node of skipYields(input.fetch({}))) { 57 this.#applyChange({ type: 'add', node }) 58 } 59 60 if (queryComplete !== true && !('error' in queryComplete)) { 61 void queryComplete.then(() => { 62 this.#status.value = 'complete' 63 this.#error.value = undefined 64 }).catch((error: ErroredQuery) => { 65 this.#status.value = 'error' 66 this.#error.value = makeError(error) 67 }) 68 } 69 } 70 71 get data(): Entry[string] { 72 return this.#data.value[''] 73 } 74 75 get status(): QueryStatus { 76 return this.#status.value 77 } 78 79 get error(): QueryError | undefined { 80 return this.#error.value 81 } 82 83 destroy(): void { 84 if (!this.#isDestroyed) { 85 this.#isDestroyed = true 86 this.#onDestroy() 87 } 88 } 89 90 #applyChange(change: ViewChange): void { 91 applyChange( 92 this.#data.value, 93 change, 94 this.#input.getSchema(), 95 '', 96 this.#format, 97 ) 98 } 99 100 push(change: Change): readonly never[] { 101 this.#applyChange(materializeRelationships(change)) 102 return Object.freeze([]) 103 } 104 105 updateTTL(ttl: TTL): void { 106 this.#updateTTL(ttl) 107 } 108} 109 110function materializeRelationships(change: Change): ViewChange { 111 switch (change[0]) { 112 case 0: 113 return { 114 type: 'add', 115 node: materializeNodeRelationships(change[1]), 116 } 117 case 1: 118 return { 119 type: 'remove', 120 node: materializeNodeRelationships(change[1]), 121 } 122 case 2: 123 return { 124 type: 'edit', 125 node: { row: change[1].row }, 126 oldNode: { row: change[2].row }, 127 } 128 case 3: 129 return { 130 type: 'child', 131 node: { row: change[1].row }, 132 child: { 133 relationshipName: change[2].relationshipName, 134 change: materializeRelationships(change[2].change), 135 }, 136 } 137 } 138} 139 140function materializeNodeRelationships(node: Node): Node { 141 const relationships: Record<string, () => Stream<Node>> = {} 142 143 for (const relationship in node.relationships) { 144 const children = node.relationships[relationship] 145 if (!children) { 146 continue 147 } 148 149 const materialized: Node[] = [] 150 151 for (const child of skipYields(children())) { 152 materialized.push(materializeNodeRelationships(child)) 153 } 154 155 relationships[relationship] = () => materialized 156 } 157 158 return { 159 row: node.row, 160 relationships, 161 } 162} 163 164function makeError(error: ErroredQuery): QueryError { 165 const message = error.message ?? 'An unknown error occurred' 166 return { 167 type: error.error, 168 message, 169 ...(error.details ? { details: error.details } : {}), 170 } 171} 172 173export function vueViewFactory< 174 TTable extends keyof TSchema['tables'] & string, 175 TSchema extends Schema, 176 TReturn, 177>( 178 _query: Query<TTable, TSchema, TReturn>, 179 input: Input, 180 format: Format, 181 onDestroy: () => void, 182 onTransactionCommit: (cb: () => void) => void, 183 queryComplete: true | ErroredQuery | Promise<true>, 184 updateTTL: (ttl: TTL) => void, 185): VueView { 186 return new VueView( 187 input, 188 onTransactionCommit, 189 format, 190 onDestroy, 191 queryComplete, 192 updateTTL, 193 ) 194} 195 196vueViewFactory satisfies AnyViewFactory