Skip to content

refactor: vue implementation #85

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions packages/vue-db/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,12 @@
],
"packageManager": "[email protected]",
"dependencies": {
"@tanstack/db": "workspace:*",
"@tanstack/vue-store": "^0.7.0"
"@tanstack/db": "workspace:*"
},
"devDependencies": {
"@electric-sql/client": "1.0.0",
"@vitest/coverage-istanbul": "^3.0.9",
"@vitejs/plugin-vue": "^5.2.4",
"@vitest/coverage-istanbul": "^3.0.9",
"vue": "^3.5.13"
},
"exports": {
Expand Down
81 changes: 52 additions & 29 deletions packages/vue-db/src/useLiveQuery.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { computed, toValue, watch } from "vue"
import { useStore } from "@tanstack/vue-store"
import { computed, onScopeDispose, shallowRef } from "vue"
import { compileQuery, queryBuilder } from "@tanstack/db"
import { shallow } from "./useStore"
import type {
Collection,
Context,
Expand All @@ -9,52 +9,75 @@ import type {
ResultsFromContext,
Schema,
} from "@tanstack/db"
import type { ComputedRef, MaybeRefOrGetter } from "vue"

export interface UseLiveQueryReturn<T extends object> {
state: ComputedRef<Map<string, T>>
data: ComputedRef<Array<T>>
collection: ComputedRef<Collection<T>>
state: () => Readonly<Map<string, T>>
data: () => Readonly<Array<T>>
collection: () => Collection<T>
}

export function useLiveQuery<
TResultContext extends Context<Schema> = Context<Schema>,
>(
queryFn: (
q: InitialQueryBuilder<Context<Schema>>
) => QueryBuilder<TResultContext>,
deps: Array<MaybeRefOrGetter<unknown>> = []
) => QueryBuilder<TResultContext>
): UseLiveQueryReturn<ResultsFromContext<TResultContext>> {
const compiledQuery = computed(() => {
// Just reference deps to make computed reactive to them
deps.forEach((dep) => toValue(dep))

const query = queryFn(queryBuilder())
const compiled = compileQuery(query)
compiled.start()
return compiled
})
const NOOP = () => {}
let unsubCompiled = NOOP
let unsubDerivedState = NOOP
let unsubDerivedArray = NOOP

const compiled = computed<ReturnType<typeof compileQuery<TResultContext>>>(
() => {
unsubCompiled()
const compiledRef = compileQuery(queryFn(queryBuilder()))
unsubCompiled = compiledRef.start()
return compiledRef
}
)

const state = computed(() => {
return useStore(compiledQuery.value.results.derivedState).value
const derivedState = compiled.value.results.derivedState
let stateRef = derivedState.state
const ret = shallowRef(stateRef)

unsubDerivedState()
unsubDerivedState = derivedState.subscribe(() => {
const newValue = derivedState.state
if (shallow(stateRef, newValue)) return

stateRef = newValue
ret.value = newValue
})
return ret
})

const data = computed(() => {
return useStore(compiledQuery.value.results.derivedArray).value
})
const derivedArray = compiled.value.results.derivedArray
let stateRef = derivedArray.state
const ret = shallowRef(stateRef)

watch(compiledQuery, (newQuery, oldQuery, onInvalidate) => {
if (newQuery.state === `stopped`) {
newQuery.start()
}
unsubDerivedArray()
unsubDerivedArray = derivedArray.subscribe(() => {
const newValue = derivedArray.state
if (shallow(stateRef, newValue)) return

onInvalidate(() => {
oldQuery.stop()
stateRef = newValue
ret.value = newValue
})
return ret
})

onScopeDispose(() => {
unsubCompiled()
unsubDerivedState()
unsubDerivedArray()
})

return {
state,
data,
collection: computed(() => compiledQuery.value.results),
state: () => state.value.value,
data: () => data.value.value,
collection: () => compiled.value.results,
}
}
49 changes: 49 additions & 0 deletions packages/vue-db/src/useStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* @see https://github.com/TanStack/store/blob/cf37b85ddecdcb6f52ad930dcd53e294fb4b03a7/packages/vue-store/src/index.ts#L47
*/

export function shallow<T>(objA: T, objB: T) {
if (Object.is(objA, objB)) {
return true
}

if (
typeof objA !== `object` ||
objA === null ||
typeof objB !== `object` ||
objB === null
) {
return false
}

if (objA instanceof Map && objB instanceof Map) {
if (objA.size !== objB.size) return false
for (const [k, v] of objA) {
if (!objB.has(k) || !Object.is(v, objB.get(k))) return false
}
return true
}

if (objA instanceof Set && objB instanceof Set) {
if (objA.size !== objB.size) return false
for (const v of objA) {
if (!objB.has(v)) return false
}
return true
}

const keysA = Object.keys(objA)
if (keysA.length !== Object.keys(objB).length) {
return false
}

for (const keyA of keysA) {
if (
!Object.prototype.hasOwnProperty.call(objB, keyA) ||
!Object.is(objA[keyA as keyof T], objB[keyA as keyof T])
) {
return false
}
}
return true
}
Loading