From f24ca1daec57d660cb1750cfa82c1c8485895623 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 05:56:29 +0000 Subject: [PATCH 1/8] Initial plan From bc43a97e007f88a75cb02f2dcc5a8d1c8f6ad52b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 06:01:27 +0000 Subject: [PATCH 2/8] Add WebSocket polyfill for Node.js tests Co-authored-by: miccy <9729864+miccy@users.noreply.github.com> --- packages/nodejs/test/setup.ts | 8 ++++++++ packages/nodejs/vitest.config.ts | 1 + 2 files changed, 9 insertions(+) create mode 100644 packages/nodejs/test/setup.ts diff --git a/packages/nodejs/test/setup.ts b/packages/nodejs/test/setup.ts new file mode 100644 index 000000000..6328c9fb6 --- /dev/null +++ b/packages/nodejs/test/setup.ts @@ -0,0 +1,8 @@ +// Polyfill WebSocket for Node.js tests +// The @evolu/common package accesses globalThis.WebSocket at import time +import { WebSocket } from "ws"; + +if (!globalThis.WebSocket) { + // @ts-expect-error - ws WebSocket is compatible enough for our needs + globalThis.WebSocket = WebSocket; +} diff --git a/packages/nodejs/vitest.config.ts b/packages/nodejs/vitest.config.ts index 4705738a3..8c5405af1 100644 --- a/packages/nodejs/vitest.config.ts +++ b/packages/nodejs/vitest.config.ts @@ -2,6 +2,7 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { + setupFiles: ["./test/setup.ts"], exclude: ["**/node_modules/**", "**/dist/**"], include: ["test/**/*.test.ts"], coverage: { From 03e249deb5403e694222b35200b898e9659197b1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 06:13:43 +0000 Subject: [PATCH 3/8] Replace Promise.withResolvers with compatibility helper Co-authored-by: miccy <9729864+miccy@users.noreply.github.com> --- packages/nodejs/test/Task.test.ts | 52 +++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/packages/nodejs/test/Task.test.ts b/packages/nodejs/test/Task.test.ts index e785cd5aa..1b8fabf18 100644 --- a/packages/nodejs/test/Task.test.ts +++ b/packages/nodejs/test/Task.test.ts @@ -2,6 +2,24 @@ import { type MainTask, ok, testCreateConsole } from "@evolu/common"; import { afterEach, beforeEach, describe, expect, test } from "vitest"; import { runMain } from "../src/Task.js"; +/** + * Helper function to create a deferred promise (similar to Promise.withResolvers). + * This is needed for compatibility with environments that don't support Promise.withResolvers. + */ +const withResolvers = (): { + promise: Promise; + resolve: (value: T | PromiseLike) => void; + reject: (reason?: unknown) => void; +} => { + let resolve!: (value: T | PromiseLike) => void; + let reject!: (reason?: unknown) => void; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return { promise, resolve, reject }; +}; + describe("runMain", () => { beforeEach(() => { // Clean up any signal listeners from previous tests @@ -25,7 +43,7 @@ describe("runMain", () => { test("executes main task", async () => { let called = false; - const executed = Promise.withResolvers(); + const executed = withResolvers(); runMain({})(() => { called = true; @@ -39,7 +57,7 @@ describe("runMain", () => { }); test("passes custom deps", async () => { - const depsValue = Promise.withResolvers(); + const depsValue = withResolvers(); const customDep = { myValue: 42 }; interface MyDep { @@ -60,7 +78,7 @@ describe("runMain", () => { test("handles aborted runner", async () => { let taskRan = false; - const taskCompleted = Promise.withResolvers(); + const taskCompleted = withResolvers(); runMain({})(async (run) => { taskRan = true; @@ -78,8 +96,8 @@ describe("runMain", () => { test("disposes returned Disposable after signal", async () => { let disposed = false; - const taskStarted = Promise.withResolvers(); - const disposeCalled = Promise.withResolvers(); + const taskStarted = withResolvers(); + const disposeCalled = withResolvers(); runMain({})(() => { taskStarted.resolve(); @@ -100,8 +118,8 @@ describe("runMain", () => { test("disposes returned AsyncDisposable after signal", async () => { let disposed = false; - const taskStarted = Promise.withResolvers(); - const disposeCalled = Promise.withResolvers(); + const taskStarted = withResolvers(); + const disposeCalled = withResolvers(); runMain({})(() => { taskStarted.resolve(); @@ -123,7 +141,7 @@ describe("runMain", () => { test("handles void return without disposal", async () => { let called = false; - const taskStarted = Promise.withResolvers(); + const taskStarted = withResolvers(); runMain({})(() => { called = true; @@ -139,8 +157,8 @@ describe("runMain", () => { test("responds to SIGTERM", async () => { let disposed = false; - const taskStarted = Promise.withResolvers(); - const disposeCalled = Promise.withResolvers(); + const taskStarted = withResolvers(); + const disposeCalled = withResolvers(); runMain({})(() => { taskStarted.resolve(); @@ -161,8 +179,8 @@ describe("runMain", () => { test("responds to SIGHUP", async () => { let disposed = false; - const taskStarted = Promise.withResolvers(); - const disposeCalled = Promise.withResolvers(); + const taskStarted = withResolvers(); + const disposeCalled = withResolvers(); runMain({})(() => { taskStarted.resolve(); @@ -182,7 +200,7 @@ describe("runMain", () => { }); test("cleans up signal listeners after signal", async () => { - const taskCompleted = Promise.withResolvers(); + const taskCompleted = withResolvers(); const initialSigintCount = process.listenerCount("SIGINT"); runMain({})(() => @@ -207,8 +225,8 @@ describe("runMain", () => { }); test("sets exitCode to 1 on uncaughtException", async () => { - const disposed = Promise.withResolvers(); - const taskStarted = Promise.withResolvers(); + const disposed = withResolvers(); + const taskStarted = withResolvers(); const console = testCreateConsole(); runMain({ console })(() => { @@ -238,8 +256,8 @@ describe("runMain", () => { }); test("sets exitCode to 1 on unhandledRejection", async () => { - const disposed = Promise.withResolvers(); - const taskStarted = Promise.withResolvers(); + const disposed = withResolvers(); + const taskStarted = withResolvers(); const console = testCreateConsole(); runMain({ console })(() => { From f926c1485f3166d25865c9b94255f7c8b09d22fc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 06:42:15 +0000 Subject: [PATCH 4/8] Add Promise.try polyfill for Node.js tests Co-authored-by: miccy <9729864+miccy@users.noreply.github.com> --- packages/nodejs/test/setup.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/nodejs/test/setup.ts b/packages/nodejs/test/setup.ts index 6328c9fb6..d274e2356 100644 --- a/packages/nodejs/test/setup.ts +++ b/packages/nodejs/test/setup.ts @@ -6,3 +6,18 @@ if (!globalThis.WebSocket) { // @ts-expect-error - ws WebSocket is compatible enough for our needs globalThis.WebSocket = WebSocket; } + +// Polyfill Promise.try for Node.js/Bun versions that don't support it (ES2024) +// The @evolu/common package uses Promise.try in Task.ts +if (!Promise.try) { + // @ts-expect-error - Adding ES2024 Promise.try polyfill + Promise.try = function ( + callback: () => T | PromiseLike, + ...args: unknown[] + ): Promise { + return new Promise((resolve) => { + resolve(callback(...args)); + }); + }; +} + From 602577bcd69bd94acf41d83ee73698838f49e85d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 06:42:58 +0000 Subject: [PATCH 5/8] Fix Promise.try polyfill error handling and type signature Co-authored-by: miccy <9729864+miccy@users.noreply.github.com> --- packages/nodejs/test/setup.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/nodejs/test/setup.ts b/packages/nodejs/test/setup.ts index d274e2356..49c15f61d 100644 --- a/packages/nodejs/test/setup.ts +++ b/packages/nodejs/test/setup.ts @@ -11,13 +11,12 @@ if (!globalThis.WebSocket) { // The @evolu/common package uses Promise.try in Task.ts if (!Promise.try) { // @ts-expect-error - Adding ES2024 Promise.try polyfill - Promise.try = function ( - callback: () => T | PromiseLike, - ...args: unknown[] + Promise.try = function ( + callback: (...args: Args) => T | PromiseLike, + ...args: Args ): Promise { - return new Promise((resolve) => { - resolve(callback(...args)); - }); + return Promise.resolve().then(() => callback(...args)); }; } + From 50d3bef9498a230d99b1f8716c630d07602aa692 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 06:48:48 +0000 Subject: [PATCH 6/8] Fix TypeScript error: change @ts-expect-error to @ts-ignore Co-authored-by: miccy <9729864+miccy@users.noreply.github.com> --- packages/nodejs/test/setup.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/nodejs/test/setup.ts b/packages/nodejs/test/setup.ts index 49c15f61d..5f0eb2892 100644 --- a/packages/nodejs/test/setup.ts +++ b/packages/nodejs/test/setup.ts @@ -10,7 +10,7 @@ if (!globalThis.WebSocket) { // Polyfill Promise.try for Node.js/Bun versions that don't support it (ES2024) // The @evolu/common package uses Promise.try in Task.ts if (!Promise.try) { - // @ts-expect-error - Adding ES2024 Promise.try polyfill + // @ts-ignore - Adding ES2024 Promise.try polyfill Promise.try = function ( callback: (...args: Args) => T | PromiseLike, ...args: Args @@ -18,5 +18,3 @@ if (!Promise.try) { return Promise.resolve().then(() => callback(...args)); }; } - - From e201c7ee901fbb25ed4e9686376aa51f156d3a5a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 07:04:31 +0000 Subject: [PATCH 7/8] Add polyfills for common package Node.js tests Co-authored-by: miccy <9729864+miccy@users.noreply.github.com> --- packages/common/test/_nodeSetup.ts | 40 ++++++++++++++++++++++++++++++ packages/common/vitest.config.ts | 1 + 2 files changed, 41 insertions(+) create mode 100644 packages/common/test/_nodeSetup.ts diff --git a/packages/common/test/_nodeSetup.ts b/packages/common/test/_nodeSetup.ts new file mode 100644 index 000000000..57c33e0ac --- /dev/null +++ b/packages/common/test/_nodeSetup.ts @@ -0,0 +1,40 @@ +// Polyfills for Node.js test environment +// The @evolu/common package uses ES2024 features and WebSocket that aren't available in Node.js + +// Polyfill WebSocket for Node.js tests +// Import WebSocket from 'ws' package +import { WebSocket } from "ws"; + +if (!globalThis.WebSocket) { + // @ts-expect-error - ws WebSocket is compatible enough for our needs + globalThis.WebSocket = WebSocket; +} + +// Polyfill Promise.try for Node.js/Bun versions that don't support it (ES2024) +if (!Promise.try) { + // @ts-ignore - Adding ES2024 Promise.try polyfill + Promise.try = function ( + callback: (...args: Args) => T | PromiseLike, + ...args: Args + ): Promise { + return Promise.resolve().then(() => callback(...args)); + }; +} + +// Polyfill Promise.withResolvers for Node.js/Bun versions that don't support it (ES2024) +if (!Promise.withResolvers) { + // @ts-ignore - Adding ES2024 Promise.withResolvers polyfill + Promise.withResolvers = function (): { + promise: Promise; + resolve: (value: T | PromiseLike) => void; + reject: (reason?: unknown) => void; + } { + let resolve!: (value: T | PromiseLike) => void; + let reject!: (reason?: unknown) => void; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return { promise, resolve, reject }; + }; +} diff --git a/packages/common/vitest.config.ts b/packages/common/vitest.config.ts index 71b04ecd9..2b5ed271a 100644 --- a/packages/common/vitest.config.ts +++ b/packages/common/vitest.config.ts @@ -31,6 +31,7 @@ export default defineConfig({ include: ["test/**/*.test.ts"], name: "unit", environment: "node", + setupFiles: ["./test/_nodeSetup.ts"], }, }, { From 617c7aa8a6bf01e46c710b7a6c6ddf98a2a789e2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 07:15:50 +0000 Subject: [PATCH 8/8] Fix polyfill initialization order and add missing polyfills Co-authored-by: miccy <9729864+miccy@users.noreply.github.com> --- packages/common/test/_nodeSetup.ts | 23 ++++++++++++++++++++--- packages/nodejs/test/setup.ts | 20 ++++++++++++-------- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/packages/common/test/_nodeSetup.ts b/packages/common/test/_nodeSetup.ts index 57c33e0ac..4a345301d 100644 --- a/packages/common/test/_nodeSetup.ts +++ b/packages/common/test/_nodeSetup.ts @@ -1,8 +1,12 @@ // Polyfills for Node.js test environment -// The @evolu/common package uses ES2024 features and WebSocket that aren't available in Node.js +// The @evolu/common package uses ES2024+ features that aren't available in Node.js -// Polyfill WebSocket for Node.js tests -// Import WebSocket from 'ws' package +// IMPORTANT: Install polyfills FIRST before any other imports +// This provides AsyncDisposableStack, DisposableStack, Symbol.dispose, etc. +import { installPolyfills } from "../src/Polyfills.js"; +installPolyfills(); + +// Polyfill WebSocket for Node.js tests (after polyfills are installed) import { WebSocket } from "ws"; if (!globalThis.WebSocket) { @@ -38,3 +42,16 @@ if (!Promise.withResolvers) { return { promise, resolve, reject }; }; } + +// Polyfill Set.prototype.difference for Node.js/Bun (ES2025) +if (!Set.prototype.difference) { + // @ts-ignore - Adding ES2025 Set.prototype.difference polyfill + Set.prototype.difference = function (this: Set, other: Set): Set { + const result = new Set(this); + for (const elem of other) { + result.delete(elem); + } + return result; + }; +} + diff --git a/packages/nodejs/test/setup.ts b/packages/nodejs/test/setup.ts index 5f0eb2892..cbb2c49e1 100644 --- a/packages/nodejs/test/setup.ts +++ b/packages/nodejs/test/setup.ts @@ -1,11 +1,5 @@ -// Polyfill WebSocket for Node.js tests -// The @evolu/common package accesses globalThis.WebSocket at import time -import { WebSocket } from "ws"; - -if (!globalThis.WebSocket) { - // @ts-expect-error - ws WebSocket is compatible enough for our needs - globalThis.WebSocket = WebSocket; -} +// Polyfills for Node.js test environment +// The @evolu/common package uses ES2024+ features // Polyfill Promise.try for Node.js/Bun versions that don't support it (ES2024) // The @evolu/common package uses Promise.try in Task.ts @@ -18,3 +12,13 @@ if (!Promise.try) { return Promise.resolve().then(() => callback(...args)); }; } + +// Polyfill WebSocket for Node.js tests +// IMPORTANT: Import WebSocket AFTER other polyfills to avoid circular dependency issues +import { WebSocket } from "ws"; + +if (!globalThis.WebSocket) { + // @ts-expect-error - ws WebSocket is compatible enough for our needs + globalThis.WebSocket = WebSocket; +} +