From 7f35459d3c738a2e2e7b905963c60f3fd7e7c823 Mon Sep 17 00:00:00 2001 From: froehlichA Date: Tue, 10 Oct 2023 19:29:16 +0200 Subject: [PATCH] adapt API of watchMany & watchOne to use individual refs --- packages/vue/src/watchFirst.ts | 12 ++--- packages/vue/src/watchMany.spec.ts | 44 ++++++++-------- packages/vue/src/watchMany.ts | 28 +++++------ packages/vue/src/watchOne.spec.ts | 80 +++++++++++++++--------------- packages/vue/src/watchOne.ts | 48 ++++++++---------- 5 files changed, 103 insertions(+), 109 deletions(-) diff --git a/packages/vue/src/watchFirst.ts b/packages/vue/src/watchFirst.ts index f868b3f..8dea539 100644 --- a/packages/vue/src/watchFirst.ts +++ b/packages/vue/src/watchFirst.ts @@ -1,5 +1,5 @@ import { Entity, key, PrimaryKeyOf, Query, Table } from "blinkdb"; -import { computed, ComputedRef, ref, ToRefs } from "vue"; +import { computed, ref, ToRefs } from "vue"; import { QueryResult } from "./types"; import { watchMany } from "./watchMany"; @@ -46,21 +46,21 @@ export function watchFirst, P extends PrimaryKeyOf>( table: Table, queryOrId?: Query | T[P] ): ToRefs> { - const primaryKeyProperty = key(table); - let result: ComputedRef>; + let result: ToRefs>; + if (queryOrId === undefined) { result = watchMany(table); } else { const query = typeof queryOrId === "object" ? queryOrId - : ({ where: { [primaryKeyProperty]: queryOrId } } as unknown as Query); + : ({ where: { [key(table)]: queryOrId } } as unknown as Query); result = watchMany(table, query); } return { - state: computed(() => result.value.state), - data: computed(() => (result.value.data ? result.value.data[0] ?? null : undefined)), + state: result.state, + data: computed(() => (result.data.value ? result.data.value[0] ?? null : undefined)), error: ref(undefined), } as ToRefs>; } diff --git a/packages/vue/src/watchMany.spec.ts b/packages/vue/src/watchMany.spec.ts index 74e7834..caccef6 100644 --- a/packages/vue/src/watchMany.spec.ts +++ b/packages/vue/src/watchMany.spec.ts @@ -17,21 +17,21 @@ beforeEach(() => { }); test("shows loading state on first render", async () => { - const [result, app] = withSetup(() => watchMany(userTable)); + const [{ state, data }, app] = withSetup(() => watchMany(userTable)); - expect(result.value.state).toBe("loading"); - expect(result.value.data).toBe(undefined); + expect(state.value).toBe("loading"); + expect(data.value).toBe(undefined); app.unmount(); }); test("shows done state on subsequent renders", async () => { - const [result, app] = withSetup(() => watchMany(userTable)); + const [{ state, data }, app] = withSetup(() => watchMany(userTable)); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); - expect(result.value.data).toStrictEqual([]); + expect(data.value).toStrictEqual([]); app.unmount(); }); @@ -42,28 +42,28 @@ describe("without filter", () => { }); it("returns users", async () => { - const [result, app] = withSetup(() => watchMany(userTable)); + const [{ state, data }, app] = withSetup(() => watchMany(userTable)); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); - expect(result.value.data).toStrictEqual(users); + expect(data.value).toStrictEqual(users); app.unmount(); }); it("updates on changes", async () => { - const [result, app] = withSetup(() => watchMany(userTable)); + const [{ state, data }, app] = withSetup(() => watchMany(userTable)); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); const newUser: User = { id: "4", name: "Delta" }; await insert(userTable, newUser); await waitFor(() => { - expect(result.value.data).toStrictEqual([...users, newUser]); + expect(data.value).toStrictEqual([...users, newUser]); }); app.unmount(); @@ -76,15 +76,15 @@ describe("with filter", () => { }); it("returns users", async () => { - const [result, app] = withSetup(() => { + const [{ state, data }, app] = withSetup(() => { return watchMany(userTable, { where: { name: { in: ["Alice", "Bob", "Elise"] } } }); }); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); - expect(result.value.data).toStrictEqual( + expect(data.value).toStrictEqual( users.filter((u) => ["Alice", "Bob"].includes(u.name)) ); @@ -92,22 +92,22 @@ describe("with filter", () => { }); it("doesn't update on changes not matching the query", async () => { - const [result, app] = withSetup(() => { + const [{ state, data }, app] = withSetup(() => { return watchMany(userTable, { where: { name: { in: ["Alice", "Bob", "Elise"] } } }); }); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); const newUser: User = { id: "4", name: "Delta" }; await insert(userTable, newUser); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); - expect(result.value.data).toStrictEqual( + expect(data.value).toStrictEqual( users.filter((u) => ["Alice", "Bob"].includes(u.name)) ); @@ -115,12 +115,12 @@ describe("with filter", () => { }); it("updates on changes matching the query", async () => { - const [result, app] = withSetup(() => { + const [{ state, data }, app] = withSetup(() => { return watchMany(userTable, { where: { name: { in: ["Alice", "Bob", "Elise"] } } }); }); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); const newUser: User = { id: "4", name: "Elise" }; @@ -128,7 +128,7 @@ describe("with filter", () => { await new Promise((res) => setTimeout(res, 100)); - expect(result.value.data).toStrictEqual([ + expect(data.value).toStrictEqual([ ...users.filter((u) => ["Alice", "Bob"].includes(u.name)), newUser, ]); diff --git a/packages/vue/src/watchMany.ts b/packages/vue/src/watchMany.ts index 9fd506f..848f5a3 100644 --- a/packages/vue/src/watchMany.ts +++ b/packages/vue/src/watchMany.ts @@ -1,5 +1,5 @@ import { Entity, PrimaryKeyOf, Query, Table, watch } from "blinkdb"; -import { computed, ComputedRef, onBeforeMount, onBeforeUnmount, ref } from "vue"; +import { computed, onBeforeMount, onBeforeUnmount, ref, ToRefs } from "vue"; import { QueryResult } from "./types"; /** @@ -10,7 +10,7 @@ import { QueryResult } from "./types"; */ export function watchMany, P extends PrimaryKeyOf>( table: Table -): ComputedRef>; +): ToRefs>; /** * Retrieve all entities from `table` that match the given `filter`. @@ -32,36 +32,32 @@ export function watchMany, P extends PrimaryKeyOf>( export function watchMany, P extends PrimaryKeyOf>( table: Table, query: Query -): ComputedRef>; +): ToRefs>; export function watchMany, P extends PrimaryKeyOf>( table: Table, query?: Query -): ComputedRef> { - const state = ref(); +): ToRefs> { + const result = ref(); let dispose: (() => void) | undefined = undefined; onBeforeMount(async () => { if (query) { dispose = await watch(table, query, (items) => { - state.value = items; + result.value = items; }); } else { dispose = await watch(table, (items) => { - state.value = items; + result.value = items; }); } }); onBeforeUnmount(() => dispose?.()); - return computed( - () => - ({ - data: state.value, - state: state.value === undefined ? "loading" : "done", - error: undefined, - } as QueryResult), - { onTrigger: console.log } - ); + return { + data: result, + state: computed(() => (result.value === undefined ? "loading" : "done")), + error: ref(undefined), + } as ToRefs>; } diff --git a/packages/vue/src/watchOne.spec.ts b/packages/vue/src/watchOne.spec.ts index 714903a..13eef69 100644 --- a/packages/vue/src/watchOne.spec.ts +++ b/packages/vue/src/watchOne.spec.ts @@ -17,20 +17,20 @@ beforeEach(() => { }); test("shows loading state on first render", async () => { - const [result, app] = withSetup(() => watchOne(userTable, "0")); + const [{ state, data }, app] = withSetup(() => watchOne(userTable, "0")); - expect(result.value.state).toBe("loading"); - expect(result.value.data).toBe(undefined); + expect(state.value).toBe("loading"); + expect(data.value).toBe(undefined); app.unmount(); }); test("shows done state on subsequent renders", async () => { await insertMany(userTable, users); - const [result, app] = withSetup(() => watchOne(userTable, "0")); + const [{ state, data }, app] = withSetup(() => watchOne(userTable, "0")); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); app.unmount(); @@ -42,78 +42,80 @@ describe("with filter", () => { }); it("returns first user matching filter", async () => { - const [result, app] = withSetup(() => + const [{ state, data }, app] = withSetup(() => watchOne(userTable, { where: { name: "Bob" } }) ); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); - expect(result.value.data).toStrictEqual(users[1]); + expect(data.value).toStrictEqual(users[1]); app.unmount(); }); it("returns error if no user matches filter", async () => { - const [result, app] = withSetup(() => + const [{ state, data, error }, app] = withSetup(() => watchOne(userTable, { where: { name: "Bobby" } }) ); await waitFor(() => { - expect(result.value.state).toBe("error"); + expect(state.value).toBe("error"); }); - expect(result.value.data).toBe(undefined); - expect(result.value.error?.message).toMatch(/No item found/); + expect(data.value).toBe(undefined); + expect(error.value?.message).toMatch(/No item found/); app.unmount(); }); it("returns error if more than one user matches filter", async () => { - const [result, app] = withSetup(() => + const [{ state, data, error }, app] = withSetup(() => watchOne(userTable, { where: { name: { in: ["Alice", "Bob"] } } }) ); await waitFor(() => { - expect(result.value.state).toBe("error"); + expect(state.value).toBe("error"); }); - expect(result.value.data).toBe(undefined); - expect(result.value.error?.message).toMatch(/More than one item found/); + expect(data.value).toBe(undefined); + expect(error.value?.message).toMatch(/More than one item found/); app.unmount(); }); it("doesn't update on changes not matching the query", async () => { - const [result, app] = withSetup(() => + const [{ state, data }, app] = withSetup(() => watchOne(userTable, { where: { name: "Bob" } }) ); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); await update(userTable, { ...users[0], name: "Alice the II." }); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); - expect(result.value.data).toStrictEqual(users[1]); + expect(data.value).toStrictEqual(users[1]); app.unmount(); }); it("updates on changes matching the query", async () => { - const [result, app] = withSetup(() => watchOne(userTable, { where: { id: "1" } })); + const [{ state, data }, app] = withSetup(() => + watchOne(userTable, { where: { id: "1" } }) + ); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); await update(userTable, { ...users[1], name: "Bob the II." }); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); - expect(result.value.data).toStrictEqual({ ...users[1], name: "Bob the II." }); + expect(data.value).toStrictEqual({ ...users[1], name: "Bob the II." }); app.unmount(); }); @@ -125,58 +127,58 @@ describe("with id", () => { }); it("returns user with the given id", async () => { - const [result, app] = withSetup(() => watchOne(userTable, "0")); + const [{ state, data }, app] = withSetup(() => watchOne(userTable, "0")); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); - expect(result.value.data).toStrictEqual(users[0]); + expect(data.value).toStrictEqual(users[0]); app.unmount(); }); it("returns error if no user matches id", async () => { - const [result, app] = withSetup(() => watchOne(userTable, "123")); + const [{ state, data, error }, app] = withSetup(() => watchOne(userTable, "123")); await waitFor(() => { - expect(result.value.state).toBe("error"); + expect(state.value).toBe("error"); }); - expect(result.value.data).toBe(undefined); - expect(result.value.error?.message).toMatch(/No item found/); + expect(data.value).toBe(undefined); + expect(error.value?.message).toMatch(/No item found/); app.unmount(); }); it("updates on changes not matching the id", async () => { - const [result, app] = withSetup(() => watchOne(userTable, "0")); + const [{ state, data }, app] = withSetup(() => watchOne(userTable, "0")); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); await update(userTable, { ...users[1], name: "Bob the II." }); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); - expect(result.value.data).toStrictEqual(users[0]); + expect(data.value).toStrictEqual(users[0]); app.unmount(); }); it("updates on changes matching the id", async () => { - const [result, app] = withSetup(() => watchOne(userTable, "0")); + const [{ state, data }, app] = withSetup(() => watchOne(userTable, "0")); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); await update(userTable, { ...users[0], name: "Alice the II." }); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); - expect(result.value.data).toStrictEqual({ ...users[0], name: "Alice the II." }); + expect(data.value).toStrictEqual({ ...users[0], name: "Alice the II." }); app.unmount(); }); diff --git a/packages/vue/src/watchOne.ts b/packages/vue/src/watchOne.ts index 4c4953a..3a9a60c 100644 --- a/packages/vue/src/watchOne.ts +++ b/packages/vue/src/watchOne.ts @@ -7,7 +7,7 @@ import { Query, Table, } from "blinkdb"; -import { computed, ComputedRef } from "vue"; +import { computed, ToRefs } from "vue"; import { QueryResult } from "./types"; import { watchMany } from "./watchMany"; @@ -27,7 +27,7 @@ import { watchMany } from "./watchMany"; export function watchOne, P extends PrimaryKeyOf>( table: Table, query: Query -): ComputedRef>; +): ToRefs>; /** * Retrieves an entity from `table` with the given `id`. @@ -41,44 +41,40 @@ export function watchOne, P extends PrimaryKeyOf>( export function watchOne, P extends PrimaryKeyOf>( table: Table, id: T[P] -): ComputedRef>; +): ToRefs>; export function watchOne, P extends PrimaryKeyOf>( table: Table, queryOrId: Query | T[P] -): ComputedRef> { - const primaryKeyProperty = key(table); - let result: ComputedRef>; +): ToRefs> { + let result: ToRefs>; + if (queryOrId === undefined) { result = watchMany(table); } else { const query = typeof queryOrId === "object" ? queryOrId - : ({ where: { [primaryKeyProperty]: queryOrId } } as unknown as Query); + : ({ where: { [key(table)]: queryOrId } } as unknown as Query); result = watchMany(table, query); } - return computed((): QueryResult => { - if (result.value.state === "done") { - if (result.value.data.length === 0) { - return { - state: "error", - data: undefined, - error: new ItemNotFoundError(queryOrId), - }; - } else if (result.value.data.length > 1) { - return { - state: "error", - data: undefined, - error: new MoreThanOneItemFoundError(queryOrId), - }; + const error = computed(() => { + if (result.state.value === "done") { + if (result.data.value!.length === 0) { + return new ItemNotFoundError(queryOrId); + } + if (result.data.value!.length > 1) { + return new MoreThanOneItemFoundError(queryOrId); } } - - return { - ...result.value, - data: result.value.data ? result.value.data[0] : undefined, - } as QueryResult; }); + + return { + error, + state: computed(() => (error.value !== undefined ? "error" : result.state.value)), + data: computed(() => + error.value === undefined && result.data.value ? result.data.value[0] : undefined + ), + } as ToRefs>; }