From b188dd10e92259f4598e8636112bb5c7f161ee65 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 18:22:24 +0000 Subject: [PATCH 1/2] Initial plan From d73467f1728c44a657642b84647937143c1db634 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 18:26:20 +0000 Subject: [PATCH 2/2] fix: Address PR #5 review comments - hook ordering, error handling, polyfills Co-authored-by: miccy <9729864+miccy@users.noreply.github.com> --- biome_errors_2.txt | 137 ----------------------- packages/nodejs/src/local-first/Relay.ts | 17 +-- packages/nodejs/test/setup.ts | 18 +++ packages/vue/src/useOwner.ts | 6 +- 4 files changed, 29 insertions(+), 149 deletions(-) delete mode 100644 biome_errors_2.txt diff --git a/biome_errors_2.txt b/biome_errors_2.txt deleted file mode 100644 index 3cd52c871..000000000 --- a/biome_errors_2.txt +++ /dev/null @@ -1,137 +0,0 @@ -apps/web/src/components/SectionProvider.tsx:3:1 assist/source/organizeImports FIXABLE ━━━━━━━━━━ - - × The imports and exports are not sorted. - - 1 │ "use client"; - 2 │ - > 3 │ import { - │ ^^^^^^^^ - > 4 │ createContext, - ... - > 8 │ useState, - > 9 │ } from "react"; - │ ^^^^^^^^^^^^^^^ - 10 │ import { createStore, useStore, type StoreApi } from "zustand"; - 11 │ - - i Safe fix: Organize Imports (Biome) - - 8 8 │ useState, - 9 9 │ } from "react"; - 10 │ - import·{·createStore,·useStore,·type·StoreApi·}·from·"zustand"; - 10 │ + import·{·createStore,·type·StoreApi,·useStore·}·from·"zustand"; - 11 11 │ - 12 12 │ import { remToPx } from "@/lib/remToPx"; - - -packages/common/test/Function.test.ts:1:1 assist/source/organizeImports FIXABLE ━━━━━━━━━━ - - × The imports and exports are not sorted. - - > 1 │ import { describe, expect, expectTypeOf, test } from "vitest"; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 2 │ import type { NonEmptyArray, NonEmptyReadonlyArray } from "../src/Array.js"; - 3 │ import { - - i Safe fix: Organize Imports (Biome) - - 7 7 │ lazyNull, - 8 8 │ lazyTrue, - 9 │ - ··readonly, - 10 │ - ··todo, - 11 │ - ··type·lazyUndefined, - 12 │ - ··type·lazyVoid, - 9 │ + ··type·lazyUndefined, - 10 │ + ··type·lazyVoid, - 11 │ + ··readonly, - 12 │ + ··todo, - 13 13 │ } from "../src/Function.js"; - 14 14 │ import type { ReadonlyRecord } from "../src/Object.js"; - - -packages/react/src/useQuerySubscription.ts:40:5 lint/correctness/useHookAtTopLevel ━━━━━━━━━━ - - × This hook is being called conditionally, but all hooks must be called in the exact same order in every component render. - - 38 │ // biome-ignore lint/correctness/useHookAtTopLevel: intentional - 39 │ return useSyncExternalStore( - > 40 │ useMemo(() => evolu.subscribeQuery(query), [evolu, query]), - │ ^^^^^^^ - 41 │ useMemo(() => () => evolu.getQueryRows(query), [evolu, query]), - 42 │ () => emptyRows as QueryRows, - - i Hooks should not be called after an early return. - - 32 │ () => evolu.subscribeQuery(query)(lazyVoid), - 33 │ [evolu, query], - > 34 │ ); - │ - > 35 │ return evolu.getQueryRows(query); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 36 │ } - 37 │ - - i For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order. - - i See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level - - -packages/react/src/useQuerySubscription.ts:41:5 lint/correctness/useHookAtTopLevel ━━━━━━━━━━ - - × This hook is being called conditionally, but all hooks must be called in the exact same order in every component render. - - 39 │ return useSyncExternalStore( - 40 │ useMemo(() => evolu.subscribeQuery(query), [evolu, query]), - > 41 │ useMemo(() => () => evolu.getQueryRows(query), [evolu, query]), - │ ^^^^^^^ - 42 │ () => emptyRows as QueryRows, - 43 │ /* eslint-enable react-hooks/rules-of-hooks */ - - i Hooks should not be called after an early return. - - 32 │ () => evolu.subscribeQuery(query)(lazyVoid), - 33 │ [evolu, query], - > 34 │ ); - │ - > 35 │ return evolu.getQueryRows(query); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 36 │ } - 37 │ - - i For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order. - - i See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level - - -packages/vue/src/useOwner.ts:13:17 lint/correctness/useHookAtTopLevel ━━━━━━━━━━━━━━━━━━ - - × This hook is being called conditionally, but all hooks must be called in the exact same order in every component render. - - 11 │ if (owner == null) return; - 12 │ - > 13 │ const evolu = useEvolu(); - │ ^^^^^^^^ - 14 │ - 15 │ // biome-ignore lint/correctness/useHookAtTopLevel: intentional - - i Hooks should not be called after an early return. - - 9 │ */ - 10 │ export const useOwner = (owner: SyncOwner | null): void => { - > 11 │ if (owner == null) return; - │ ^^^^^^^ - 12 │ - 13 │ const evolu = useEvolu(); - - i For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order. - - i See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level - - -Checked 361 files in 1511ms. No fixes applied. -Found 5 errors. -check ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × Some errors were emitted while running checks. - - diff --git a/packages/nodejs/src/local-first/Relay.ts b/packages/nodejs/src/local-first/Relay.ts index e18a577c8..cfa174229 100644 --- a/packages/nodejs/src/local-first/Relay.ts +++ b/packages/nodejs/src/local-first/Relay.ts @@ -71,20 +71,18 @@ export const startRelay = name = SimpleName.orThrow("evolu-relay"), isOwnerAllowed, isOwnerWithinQuota, - }: NodeJsRelayConfig): Task => + }: NodeJsRelayConfig): Task => async (_run) => { await using stack = _run.stack(); const console = _run.deps.console.child("relay"); const dbFileExists = existsSync(`${name}.db`); - const handleError = (error: SqliteError) => { - console.error(error); - return ok(stack); - }; - const sqlite = await stack.use(createSqlite(name)); - if (!sqlite.ok) return handleError(sqlite.error); + if (!sqlite.ok) { + console.error(sqlite.error); + return sqlite; + } const deps = { ..._run.deps, sqlite: sqlite.value }; if (!dbFileExists) { @@ -92,7 +90,10 @@ export const startRelay = createBaseSqliteStorageTables(deps), createRelayStorageTables(deps), ]); - if (!result.ok) return handleError(result.error); + if (!result.ok) { + console.error(result.error); + return result; + } } const storage = createRelaySqliteStorage(deps)({ diff --git a/packages/nodejs/test/setup.ts b/packages/nodejs/test/setup.ts index d427d80b5..919e87607 100644 --- a/packages/nodejs/test/setup.ts +++ b/packages/nodejs/test/setup.ts @@ -10,6 +10,24 @@ if (!(Promise as any).try) { ): Promise => new Promise((resolve) => resolve(callback(...args))); } +// Polyfill Promise.withResolvers for Node.js/Bun versions that don't support it (ES2024) +// The @evolu/common package uses Promise.withResolvers in Task.ts +if (!(Promise as any).withResolvers) { + (Promise as any).withResolvers = (): { + promise: Promise; + resolve: (value: T | PromiseLike) => void; + reject: (reason?: any) => void; + } => { + let resolve!: (value: T | PromiseLike) => void; + let reject!: (reason?: any) => void; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return { promise, resolve, reject }; + }; +} + // Polyfill WebSocket for Node.js tests // IMPORTANT: Import WebSocket AFTER other polyfills to avoid circular dependency issues import { WebSocket } from "ws"; diff --git a/packages/vue/src/useOwner.ts b/packages/vue/src/useOwner.ts index 3fc33ac62..8e19ff3f6 100644 --- a/packages/vue/src/useOwner.ts +++ b/packages/vue/src/useOwner.ts @@ -8,11 +8,9 @@ import { useEvolu } from "./useEvolu.js"; * defined in Evolu config if the owner has no transports defined. */ export const useOwner = (owner: SyncOwner | null): void => { - if (owner == null) return; - - // biome-ignore lint/correctness/useHookAtTopLevel: intentional const evolu = useEvolu(); - // biome-ignore lint/correctness/useHookAtTopLevel: intentional + if (owner == null) return; + evolu.useOwner(owner); };