Skip to content

Port custom DisposableStack polyfills and simplify build scripts#63

Closed
miccy wants to merge 15 commits intomainfrom
sync/common-v8-2026-03-04
Closed

Port custom DisposableStack polyfills and simplify build scripts#63
miccy wants to merge 15 commits intomainfrom
sync/common-v8-2026-03-04

Conversation

@miccy
Copy link
Contributor

@miccy miccy commented Mar 4, 2026

This pull request introduces several improvements and refactors across the codebase, focusing primarily on replacing the use of rimraf with a custom Bun-based removal script, updating dependencies, and making platform compatibility enhancements. The most significant changes are grouped below.

Build and Clean Script Improvements:

  • Replaced all uses of rimraf in clean and build scripts across the monorepo with a custom Bun-based removal script (bun ../../scripts/rm.mts), improving consistency and speed for cleaning tasks. This affects root package.json and multiple example and package scripts. [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13]

  • Added a clean:ts script at the root for cleaning TypeScript build artifacts, and updated the build script to run this before building all packages. [1] [2]

Platform and Compatibility Enhancements:

  • Improved Node.js Buffer detection and usage by introducing a nodeBuffer abstraction and updating base64 encoding/decoding logic to use this, ensuring compatibility in environments where Buffer may not be globally available. [1] [2] [3] [4] [5]

  • Enhanced stack trace handling in custom error classes (BufferError, ProtocolDecodeError) to avoid errors in environments where Error.captureStackTrace is not available. [1] [2]

  • Improved scheduler and async task compatibility by using a global setImmediate fallback for environments where it is not defined. [1] [2]

Dependency and Configuration Updates:

  • Updated several dependencies to newer versions, including react-native-quick-crypto, @tauri-apps/cli, and svelte. [1] [2] [3]

  • Updated TypeScript configuration in apps/relay/tsconfig.json to explicitly include Node types and type roots for better type checking.

  • Removed unused dependencies such as disposablestack, webpack, and @types/webpack from package manifests. [1] [2] [3]

Other Improvements:

  • Refactored the AsyncDisposableStack to better handle both synchronous and asynchronous disposables, improving resource management.

These changes collectively modernize the build process, improve cross-platform compatibility, and update dependencies for better reliability and maintainability.

Summary by CodeRabbit

  • Chores
    • Přechod build/clean skriptů na Bun‑based čistící skript, přidání dedikovaného TypeScript clean kroku a drobné aktualizace závislostí.
  • New Features
    • Explicitní instalace a rozsáhlé polyfily pro správu zdrojů (DisposableStack/AsyncDisposableStack).
    • Nová Vitest coverage konfigurace.
  • Tests
    • Velké sady testů pro polyfily, chování disposal stacků a aktualizované tree‑shaking testy používající Bun.
  • Bug Fixes
    • Robustnější runtime detekce pro SQLite, spolehlivější start/ukončení serverů a odstraňování visících připojení.

@miccy miccy requested review from Jakub-coding99 and Copilot March 4, 2026 14:30
@miccy miccy self-assigned this Mar 4, 2026
@miccy miccy added bug Something isn't working upstream Needs cherry-pick or merge from upstream fix Repair any bug update Update libs and deps labels Mar 4, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 4, 2026

📝 Walkthrough

Walkthrough

Přechod mnoha skriptů z rimraf na Bun-based scripts/rm.mts, rozsáhlá implementace a explicitní instalace polyfillů DisposableStack/AsyncDisposableStack včetně testů, úpravy testovacího bootstrapu a konfigurace coverage, plus robustnější start/stop/upgrade logika v Relay/WebSocket serveru a drobné runtime-compat opravy.

Changes

Cohort / File(s) Summary
Build & Clean Scripts
package.json, apps/relay/package.json, packages/*/package.json, examples/*/package.json
Náhrada rimraf příkazů za volání bun .../scripts/rm.mts ve build/clean skriptech; některé build skripty přidaly/odstranily inline či nový clean:ts krok; drobné dependency/upgrady.
Polyfills & Platform
packages/common/src/Polyfills.ts, packages/common/src/Platform.ts, packages/common/src/Buffer.ts, packages/common/src/Type.ts
Plná implementace instalovatelného polyfill bootstrapu pro DisposableStack/AsyncDisposableStack a SuppressedError; symbol management; bezpečné volání captureStackTrace; explicitní NodeBuffer typ a bezpečnější Buffer cesty.
Polyfill Tests & Test Setup
packages/common/test/Polyfills.test.ts, packages/common/test/_browserSetup.ts, packages/web/test/_browserSetup.ts, packages/nodejs/test/setup.ts
Velká sada testů pro polyfily; testovací bootstrap nyní explicitně volá installPolyfills(); přidány Promise/WS polyfily a explicitní setupFiles pro web.
Task / Scheduler Compatibility
packages/common/src/Task.ts
Normalizace registrace disposables v use; fallback na globalThis.setImmediate pokud scheduler.yield chybí; drobné interní úpravy.
Tree-shaking / Bundling Tests
packages/common/test/TreeShaking.test.ts
Přechod testovacího bundlingu z webpacku na Bun-based bundling; synchronní bundling API, detekce dostupnosti Bun runtime a guardy pro platformy.
Relay / WebSocket Server
packages/nodejs/src/local-first/Relay.ts, packages/nodejs/test/Relay.test.ts
Centralizované rejectUpgrade, autorizace v try/catch, startup awaiting na server.listen, robustní shutdown s timeouty, force-close klientů a tolerantnější testy k runtime-variacím.
SQLite Runtime Selection
packages/nodejs/src/Sqlite.ts, packages/nodejs/test/Sqlite.test.ts
Přidaný helper hasBunRuntime() s env-override EVOLU_NODEJS_SQLITE_RUNTIME; testy používají helper místo přepisování globalThis.Bun.
Vitest Coverage / Config
vitest.coverage.config.mts, packages/web/vitest.config.ts, package.json
Nový Vitest coverage config (Istanbul), přidání setupFiles pro web projekty a root skripty upraveny pro coverage a clean:ts krok.
Utility Script
scripts/rm.mts
Nový Bun-compatible TypeScript skript pro paralelní bezpečné mazání cest s retry logikou, reportingem chyb a návratovým kódem.
Misc Tests & Small Fixes
packages/common/test/local-first/Sync.test.ts, packages/common/test/WebSocket.test.ts, packages/nodejs/test/Sqlite.test.ts, další testy
Drobné testové úpravy (quota timestamp handling), změna hostu na 127.0.0.1, přidány timeouty/close helpery pro servery a klienty; přepis některých global mutací ve prospěch env-overrides.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant HTTP as HTTP Server
    participant Relay as Relay Upgrade Handler
    participant WS as WebSocketServer
    participant Shutdown as Shutdown Manager

    Client->>HTTP: HTTP Upgrade request (ownerId)
    HTTP->>Relay: forward upgrade
    Relay->>Relay: authorize ownerId (try/catch)
    alt auth ok
        Relay->>WS: completeUpgrade (establish WS)
        WS-->>Client: WebSocket open
    else auth fail
        Relay->>HTTP: rejectUpgrade(400/"Bad Request")
        HTTP-->>Client: HTTP error + close socket
    end

    Note over Shutdown,WS: On shutdown
    Shutdown->>WS: close all clients (with timeout/terminate)
    Shutdown->>HTTP: close server (with timeout)
    WS-->>Shutdown: closed
    HTTP-->>Shutdown: closed
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested labels

feat

Suggested reviewers

  • Jakub-coding99

Poem

🐰 Hop, hop, kód jak mrkev září,

rm.mts smete staré stopy z dální,
Stacky teď hlídaj pořádek v trávě,
testy skáčou, Bun tančí v slávě,
Králíček burácí: hotovo v hlavě.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Nadpis přesně popisuje hlavní změny: migrace vlastních DisposableStack polyfillů a zjednodušení build skriptů.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch sync/common-v8-2026-03-04

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@miccy miccy marked this pull request as ready for review March 4, 2026 14:30
@miccy miccy requested a review from a team as a code owner March 4, 2026 14:30
@socket-security
Copy link

socket-security bot commented Mar 4, 2026

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Publisher changed: npm @expo/require-utils is now published by brentvatne

Author: brentvatne

From: ?npm/expo-constants@55.0.7npm/expo-splash-screen@55.0.10npm/expo@55.0.5npm/@expo/require-utils@55.0.2

ℹ Read more on: This package | This alert | What is unstable ownership?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Try to reduce the number of authors you depend on to reduce the risk to malicious actors gaining access to your supply chain. Packages should remove inactive collaborators with publishing rights from packages on npm.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@expo/require-utils@55.0.2. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm @react-native/debugger-frontend is 96.0% likely obfuscated

Confidence: 0.96

Location: Package overview

From: ?npm/expo@55.0.5npm/@react-native/debugger-frontend@0.83.2

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@react-native/debugger-frontend@0.83.2. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm @react-native/debugger-frontend is 96.0% likely obfuscated

Confidence: 0.96

Location: Package overview

From: ?npm/react-native@0.84.1npm/@react-native/debugger-frontend@0.84.1

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@react-native/debugger-frontend@0.84.1. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm entities is 91.0% likely obfuscated

Confidence: 0.91

Location: Package overview

From: ?npm/react-native-svg@15.15.3npm/entities@4.5.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/entities@4.5.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
License policy violation: npm react-native-quick-crypto

License: Apache-2.0 WITH LLVM-exception - the applicable license policy does not allow this license exception (package/deps/blake3/LICENSE_A2LLVM)

From: examples/react-expo/package.jsonnpm/react-native-quick-crypto@1.0.16

ℹ Read more on: This package | This alert | What is a license policy violation?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Find a package that does not violate your license policy or adjust your policy to allow this package's license.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/react-native-quick-crypto@1.0.16. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Publisher changed: npm react-native-safe-area-context is now published by melvalesca

Author: melvalesca

From: examples/react-expo/package.jsonnpm/react-native-safe-area-context@5.7.0

ℹ Read more on: This package | This alert | What is unstable ownership?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Try to reduce the number of authors you depend on to reduce the risk to malicious actors gaining access to your supply chain. Packages should remove inactive collaborators with publishing rights from packages on npm.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/react-native-safe-area-context@5.7.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Medium
Low adoption: npm @blazejkustra/react-native-alert

Location: Package overview

From: examples/react-expo/package.jsonnpm/@blazejkustra/react-native-alert@1.0.0

ℹ Read more on: This package | This alert | What are unpopular packages?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Unpopular packages may have less maintenance and contain other problems.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@blazejkustra/react-native-alert@1.0.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Medium
Low adoption: npm abort-signal-polyfill

Location: Package overview

From: examples/react-expo/package.jsonnpm/abort-signal-polyfill@1.0.0

ℹ Read more on: This package | This alert | What are unpopular packages?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Unpopular packages may have less maintenance and contain other problems.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/abort-signal-polyfill@1.0.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Medium
Low adoption: npm fetch-nodeshim

Location: Package overview

From: ?npm/expo@55.0.5npm/fetch-nodeshim@0.4.9

ℹ Read more on: This package | This alert | What are unpopular packages?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Unpopular packages may have less maintenance and contain other problems.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/fetch-nodeshim@0.4.9. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Medium
Deprecated by its maintainer: npm inflight

Reason: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.

From: ?npm/babel-preset-expo@55.0.10npm/react-native@0.84.1npm/expo@55.0.5npm/inflight@1.0.6

ℹ Read more on: This package | This alert | What is a deprecated package?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Research the state of the package and determine if there are non-deprecated versions that can be used, or if it should be replaced with a new, supported solution.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/inflight@1.0.6. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Medium
Deprecated by its maintainer: npm rimraf

Reason: Rimraf versions prior to v4 are no longer supported

From: ?npm/react-native@0.84.1npm/expo@55.0.5npm/rimraf@3.0.2

ℹ Read more on: This package | This alert | What is a deprecated package?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Research the state of the package and determine if there are non-deprecated versions that can be used, or if it should be replaced with a new, supported solution.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/rimraf@3.0.2. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Medium
Low adoption: npm set.prototype.isdisjointfrom

Location: Package overview

From: examples/react-expo/package.jsonnpm/set.prototype.isdisjointfrom@1.1.5

ℹ Read more on: This package | This alert | What are unpopular packages?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Unpopular packages may have less maintenance and contain other problems.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/set.prototype.isdisjointfrom@1.1.5. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Medium
Low adoption: npm set.prototype.issubsetof

Location: Package overview

From: examples/react-expo/package.jsonnpm/set.prototype.issubsetof@1.1.4

ℹ Read more on: This package | This alert | What are unpopular packages?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Unpopular packages may have less maintenance and contain other problems.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/set.prototype.issubsetof@1.1.4. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Medium
Low adoption: npm set.prototype.issupersetof

Location: Package overview

From: examples/react-expo/package.jsonnpm/set.prototype.issupersetof@1.1.3

ℹ Read more on: This package | This alert | What are unpopular packages?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Unpopular packages may have less maintenance and contain other problems.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/set.prototype.issupersetof@1.1.3. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 239bb3c0a1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR modernizes the monorepo’s build/clean tooling (moving off rimraf), ports/owns DisposableStack + AsyncDisposableStack polyfills for cross-runtime determinism (incl. WebKit), and improves test/coverage lane reliability across Bun/Node/browser environments.

Changes:

  • Replaced rimraf usage across packages/examples with a Bun-run TypeScript removal script (scripts/rm.mts) and added a root clean:ts step to clean TypeScript build artifacts before turbo build.
  • Introduced/expanded owned polyfills (installPolyfills) for resource-management APIs and updated test setups (Node + browser) to install them explicitly.
  • Improved runtime compatibility and test stability (Relay upgrade rejection handling, close timeouts, 127.0.0.1 binding, Bun-based tree-shaking lane, and a dedicated Istanbul coverage config).

Reviewed changes

Copilot reviewed 43 out of 44 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
vitest.coverage.config.mts Adds a coverage-specific Vitest workspace config (Istanbul provider, excludes browser lanes).
scripts/rm.mts New Bun-run deletion helper replacing rimraf in scripts.
packages/web/vitest.config.ts Ensures polyfills are installed for browser tests via setupFiles.
packages/web/test/_browserSetup.ts Installs @evolu/common polyfills before browser tests.
packages/web/package.json Replaces rimraf in clean script with rm.mts.
packages/vue/package.json Replaces rimraf in clean script with rm.mts.
packages/tauri/package.json Replaces rimraf in clean script; build now cleans dist via rm.mts.
packages/tanstack-start/package.json Replaces rimraf in clean script; build now cleans dist via rm.mts.
packages/svelte/package.json Replaces rimraf; bumps Svelte; aligns peer dependency floor.
packages/react/package.json Replaces rimraf in clean script; build now cleans dist via rm.mts.
packages/react-web/package.json Replaces rimraf in clean script; build now cleans dist via rm.mts.
packages/react-native/package.json Replaces rimraf in clean script; build now cleans dist via rm.mts.
packages/nodejs/test/setup.ts Installs polyfills in Node test setup.
packages/nodejs/test/Sqlite.test.ts Uses env override to force Bun/Node sqlite runtime paths in tests.
packages/nodejs/test/Relay.test.ts Stabilizes websocket error handling + loosens runtime-specific error expectations.
packages/nodejs/src/local-first/Relay.ts Adds upgrade rejection helper, hardens auth/upgrade error handling, and improves shutdown/listen reliability.
packages/nodejs/src/Sqlite.ts Adds EVOLU_NODEJS_SQLITE_RUNTIME override and centralizes Bun-runtime detection.
packages/nodejs/package.json Replaces rimraf in clean script with rm.mts.
packages/common/test/local-first/Sync.test.ts Adjusts quota test to re-encrypt change using updated timestamp value.
packages/common/test/_globalSetup.ts Binds test server to 127.0.0.1 and adds close timeouts/termination to avoid hangs.
packages/common/test/_browserSetup.ts Switches to installPolyfills() instead of upstream disposablestack/auto.
packages/common/test/WebSocket.test.ts Uses 127.0.0.1 and conditionally skips server-dependent cases when not applicable.
packages/common/test/TreeShaking.test.ts Replaces webpack-based bundling with Bun bundler for tree-shaking lane tests.
packages/common/test/Polyfills.test.ts Adds extensive conformance tests for owned DisposableStack polyfills.
packages/common/src/local-first/Protocol.ts Makes stack trace capture safe when Error.captureStackTrace is unavailable.
packages/common/src/Type.ts Uses nodeBuffer abstraction for base64url encode/decode to avoid assuming global Buffer.
packages/common/src/Task.ts Improves AsyncDisposableStack handling of sync disposables + uses global setImmediate fallback.
packages/common/src/Polyfills.ts Replaces runtime dependency on disposablestack with owned polyfill implementation.
packages/common/src/Platform.ts Adds nodeBuffer/NodeBufferLike and updates hasNodeBuffer detection.
packages/common/src/Buffer.ts Makes stack trace capture safe when Error.captureStackTrace is unavailable.
packages/common/package.json Removes disposablestack/webpack deps; replaces rimraf usage with rm.mts.
packages/bun/package.json Replaces rimraf in clean script; build now cleans dist via rm.mts.
packages/astro/package.json Replaces rimraf in clean script; build now cleans dist via rm.mts.
package.json Adds clean:ts, wires it into root build, switches coverage config, removes rimraf/webpack deps, bumps turbo, adds Istanbul coverage plugin.
examples/vue-vite-pwa/package.json Replaces rimraf in clean script with rm.mts.
examples/tauri/package.json Replaces rimraf in clean script; bumps @tauri-apps/cli.
examples/svelte-vite-pwa/package.json Replaces rimraf in clean script; bumps Svelte.
examples/react-vite-pwa/package.json Replaces rimraf in clean script.
examples/react-nextjs/package.json Replaces rimraf in clean script.
examples/react-expo/package.json Replaces rimraf in clean script; bumps react-native-quick-crypto.
examples/react-electron/package.json Replaces rimraf in clean script.
bun.lock Lockfile updates reflecting dependency removals/bumps and new coverage plugin.
apps/relay/tsconfig.json Explicitly includes Node types/typeRoots for better type checking.
apps/relay/package.json Switches build to tsc --build; replaces rimraf in clean script with rm.mts.

Comment on lines +5 to +7
if (targets.length === 0) {
console.error("Usage: bun ./scripts/rm.mts <path> [<path> ...]");
process.exit(1);
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The usage message hard-codes bun ./scripts/rm.mts ..., but this script is also invoked from package/example directories (e.g. bun ../../scripts/rm.mts ...). Consider printing a generic usage string (or using process.argv[1]) so the guidance is accurate regardless of the current working directory.

Copilot uses AI. Check for mistakes.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (4)
scripts/rm.mts (1)

10-19: Zvážit explicitní ošetření chyb pro čitelnější výstup.

Skript je funkční, ale pokud některé mazání selže, Promise.all vyhodí neošetřenou výjimku. Pro lepší diagnostiku by stálo za zvážení wrappnout do try-catch s informativnější chybovou zprávou.

♻️ Volitelné vylepšení error handlingu
-await Promise.all(
-  targets.map((target) =>
-    rm(target, {
-      force: true,
-      recursive: true,
-      maxRetries: 3,
-      retryDelay: 100,
-    }),
-  ),
-);
+try {
+  await Promise.all(
+    targets.map((target) =>
+      rm(target, {
+        force: true,
+        recursive: true,
+        maxRetries: 3,
+        retryDelay: 100,
+      }),
+    ),
+  );
+} catch (error) {
+  console.error("Failed to remove targets:", error);
+  process.exit(1);
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/rm.mts` around lines 10 - 19, Wrap the Promise.all call that runs
targets.map and rm(...) in a try-catch to explicitly handle failures: catch
errors from the overall Promise.all and produce an informative error message
(including which target(s) failed and the original error) or aggregate
per-target results so you can log successes and failures; reference the
Promise.all invocation, targets.map, and the rm function to locate the code and
replace the unhandled rejection with a try-catch that logs a clear diagnostic
and rethrows or exits as appropriate.
apps/relay/package.json (1)

8-8: Build bez předchozího úklidu může nechat zastaralé artefakty v dist.

Na Line 8 se teď build nespouští nad čistým výstupem; při odstranění/renamu souborů mohou ve dist zůstat staré JS soubory, které pak načte start na Line 9.

💡 Návrh úpravy
-    "build": "tsc --build tsconfig.json",
+    "build": "bun ../../scripts/rm.mts dist && tsc --build tsconfig.json",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/relay/package.json` at line 8, The current package.json "build" script
runs tsc without cleaning dist first, which can leave stale JS artifacts that
"start" later serves; update package.json scripts by adding a "clean" script
(e.g., using rimraf or rm -rf to remove dist) and change the "build" script to
run the clean step first (e.g., "npm run clean && tsc --build tsconfig.json") so
the "build" and subsequent "start" scripts always operate on a fresh dist
directory.
packages/common/test/_globalSetup.ts (1)

15-30: closeWithTimeout by měl čistit timer po úspěšném uzavření.

Na Line 28 se timer vždy naplánuje, ale při rychlém close callbacku na Line 27 se neruší. V testech to zbytečně kumuluje pending callbacky.

💡 Návrh úpravy
 const closeWithTimeout = (
   close: (callback: () => void) => void,
   timeoutMs = 500,
 ): Promise<void> =>
   new Promise<void>((resolve) => {
     let settled = false;
+    let timeout: ReturnType<typeof setTimeout> | undefined;
     const settle = () => {
       if (settled) return;
       settled = true;
+      if (timeout) clearTimeout(timeout);
       resolve();
     };

     close(settle);
-    const timeout = setTimeout(settle, timeoutMs);
+    timeout = setTimeout(settle, timeoutMs);
     timeout.unref?.();
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/common/test/_globalSetup.ts` around lines 15 - 30, Funkce
closeWithTimeout nechává timer běžet i když close zavolá callback dříve; oprav
to tak, že uložíš návratovou hodnotu setTimeout do proměnné (např. timeout) před
voláním close, a v interní funkci settle explicitně zavoláš
clearTimeout(timeout) před resolve(), přičemž zachováš volání timeout.unref?.()
po nastavení setTimeout; upravuj funkci closeWithTimeout tak, aby timeout byl
přístupný v rámci settle a byl vždy zrušen při úspěšném uzavření.
packages/nodejs/test/Relay.test.ts (1)

191-193: Rozšířené regex aserce pro cross-platform kompatibilitu.

Použití regexu pro akceptování buď specifické HTTP chyby (Unexpected server response: 400/401) nebo generické zprávy (Connection ended) zvyšuje robustnost testů napříč různými runtime prostředími.

Zvažte přidání komentáře vysvětlujícího, proč jsou obě varianty akceptovatelné, aby budoucí vývojáři věděli, že to není chyba v testu:

📝 Návrh na přidání dokumentačního komentáře
+    // Runtime-dependent: ws reports HTTP status, some environments report "Connection ended"
     expect(String(error.message)).toMatch(
       /Unexpected server response: 400|Connection ended/,
     );

Also applies to: 219-221

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nodejs/test/Relay.test.ts` around lines 191 - 193, Update the flaky
assertion to explicitly accept both HTTP status variants and the generic runtime
message: change the regex used in expect(String(error.message)).toMatch(...) to
include 400 and 401 (e.g. /Unexpected server response: 400|Unexpected server
response: 401|Connection ended/) and add a short inline comment above that
expect explaining this dual-match is intentional for cross-platform/runtime
differences; repeat the same change + comment for the other occurrence of
expect(String(error.message)).toMatch at the second location.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@package.json`:
- Line 61: The "clean" npm script currently deletes the lockfile which harms
dependency reproducibility; edit the package.json "clean" script (the value for
the "clean" key) to remove "bun.lock" from the list of paths to delete so the
lockfile is preserved during clean operations while keeping the other cleanup
targets intact.

In `@packages/common/src/Task.ts`:
- Around line 1068-1086: The register function currently treats any object with
Symbol.dispose property as disposable without verifying it's callable; update
the check in register to ensure the Symbol.dispose property is a function before
wrapping it for async disposal (i.e., change the guard to something like: if
(Symbol.dispose in value && typeof (value as any)[Symbol.dispose] ===
'function') { ... } ), and if it's not a function fall back to calling
this.#stack.use(value as T) (or optionally throw a clear error) so non-callable
dispose properties don't only fail later during cleanup; reference the register
function, the Symbol.dispose check, the Disposable type, and the this.#stack.use
/ Symbol.asyncDispose wrapper when making the change.

In `@packages/common/test/TreeShaking.test.ts`:
- Around line 234-235: The current early-return in the test (the line using
isBunRuntime and process.platform) hides a misconfigured compat lane; instead,
detect the EVOLU_TREE_SHAKING_COMPAT environment flag and if it's set but the
runtime check fails (isBunRuntime is false or platform is win32), make the test
fail/throw with a clear message mentioning EVOLU_TREE_SHAKING_COMPAT,
isBunRuntime and process.platform so CI surfaces the misconfiguration rather
than returning silently.

In `@packages/common/test/WebSocket.test.ts`:
- Around line 44-46: wsTest is currently set to test.skip when !isServer which
disables the browser-specific WebSocket tests; change the wsTest assignment so
it does not skip when running in a browser environment by detecting
browser/WebSocket availability (e.g., check typeof window !== 'undefined' or
presence of window.WebSocket) instead of only isServer, so wsTest("connects,
receives message, sends message, and disposes", ...) runs in the browser branch;
update the wsTest definition (the const wsTest = ... line) to use that
browser-aware condition.

---

Nitpick comments:
In `@apps/relay/package.json`:
- Line 8: The current package.json "build" script runs tsc without cleaning dist
first, which can leave stale JS artifacts that "start" later serves; update
package.json scripts by adding a "clean" script (e.g., using rimraf or rm -rf to
remove dist) and change the "build" script to run the clean step first (e.g.,
"npm run clean && tsc --build tsconfig.json") so the "build" and subsequent
"start" scripts always operate on a fresh dist directory.

In `@packages/common/test/_globalSetup.ts`:
- Around line 15-30: Funkce closeWithTimeout nechává timer běžet i když close
zavolá callback dříve; oprav to tak, že uložíš návratovou hodnotu setTimeout do
proměnné (např. timeout) před voláním close, a v interní funkci settle
explicitně zavoláš clearTimeout(timeout) před resolve(), přičemž zachováš volání
timeout.unref?.() po nastavení setTimeout; upravuj funkci closeWithTimeout tak,
aby timeout byl přístupný v rámci settle a byl vždy zrušen při úspěšném
uzavření.

In `@packages/nodejs/test/Relay.test.ts`:
- Around line 191-193: Update the flaky assertion to explicitly accept both HTTP
status variants and the generic runtime message: change the regex used in
expect(String(error.message)).toMatch(...) to include 400 and 401 (e.g.
/Unexpected server response: 400|Unexpected server response: 401|Connection
ended/) and add a short inline comment above that expect explaining this
dual-match is intentional for cross-platform/runtime differences; repeat the
same change + comment for the other occurrence of
expect(String(error.message)).toMatch at the second location.

In `@scripts/rm.mts`:
- Around line 10-19: Wrap the Promise.all call that runs targets.map and rm(...)
in a try-catch to explicitly handle failures: catch errors from the overall
Promise.all and produce an informative error message (including which target(s)
failed and the original error) or aggregate per-target results so you can log
successes and failures; reference the Promise.all invocation, targets.map, and
the rm function to locate the code and replace the unhandled rejection with a
try-catch that logs a clear diagnostic and rethrows or exits as appropriate.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fef555db-0622-4132-bf65-f8b782cbf514

📥 Commits

Reviewing files that changed from the base of the PR and between 2fff107 and 239bb3c.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (43)
  • apps/relay/package.json
  • apps/relay/tsconfig.json
  • examples/react-electron/package.json
  • examples/react-expo/package.json
  • examples/react-nextjs/package.json
  • examples/react-vite-pwa/package.json
  • examples/svelte-vite-pwa/package.json
  • examples/tauri/package.json
  • examples/vue-vite-pwa/package.json
  • package.json
  • packages/astro/package.json
  • packages/bun/package.json
  • packages/common/package.json
  • packages/common/src/Buffer.ts
  • packages/common/src/Platform.ts
  • packages/common/src/Polyfills.ts
  • packages/common/src/Task.ts
  • packages/common/src/Type.ts
  • packages/common/src/local-first/Protocol.ts
  • packages/common/test/Polyfills.test.ts
  • packages/common/test/TreeShaking.test.ts
  • packages/common/test/WebSocket.test.ts
  • packages/common/test/_browserSetup.ts
  • packages/common/test/_globalSetup.ts
  • packages/common/test/local-first/Sync.test.ts
  • packages/nodejs/package.json
  • packages/nodejs/src/Sqlite.ts
  • packages/nodejs/src/local-first/Relay.ts
  • packages/nodejs/test/Relay.test.ts
  • packages/nodejs/test/Sqlite.test.ts
  • packages/nodejs/test/setup.ts
  • packages/react-native/package.json
  • packages/react-web/package.json
  • packages/react/package.json
  • packages/svelte/package.json
  • packages/tanstack-start/package.json
  • packages/tauri/package.json
  • packages/vue/package.json
  • packages/web/package.json
  • packages/web/test/_browserSetup.ts
  • packages/web/vitest.config.ts
  • scripts/rm.mts
  • vitest.coverage.config.mts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/common/src/Polyfills.ts (1)

119-132: Potenciální neošetřená asynchronní výjimka v detekci.

Na řádku 127 je disposeAsync() zavoláno s void, ale výsledný Promise není ošetřen. Pokud nativní implementace vyhodí asynchronní chybu, může to způsobit varování o neošetřeném rejectu.

♻️ Navrhovaná úprava pro bezpečné potlačení async chyby
     const asyncDisposableStack = new AsyncDisposableStackCtor();
     asyncDisposableStack.use(disposableResource);
     asyncDisposableStack.use(asyncDisposableResource);
-    void asyncDisposableStack.disposeAsync();
+    void asyncDisposableStack.disposeAsync().catch(() => {
+      // Ignorujeme async chyby - detekujeme pouze synchronní problémy v use()
+    });

     return false;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/common/src/Polyfills.ts` around lines 119 - 132, The detection block
may leave an unhandled rejection from AsyncDisposableStackCtor.disposeAsync();
update the AsyncDisposableStackCtor usage (where
asyncDisposableStack.use(asyncDisposableResource) and
asyncDisposableStack.disposeAsync() are called) to explicitly handle/reduce the
returned Promise (e.g., call disposeAsync() and attach a .catch(() => {}) or
otherwise await and catch errors) so any asynchronous error is suppressed during
feature detection without leaking an unhandled rejection; keep the synchronous
DisposableStackCtor logic unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/common/src/Polyfills.ts`:
- Around line 119-132: The detection block may leave an unhandled rejection from
AsyncDisposableStackCtor.disposeAsync(); update the AsyncDisposableStackCtor
usage (where asyncDisposableStack.use(asyncDisposableResource) and
asyncDisposableStack.disposeAsync() are called) to explicitly handle/reduce the
returned Promise (e.g., call disposeAsync() and attach a .catch(() => {}) or
otherwise await and catch errors) so any asynchronous error is suppressed during
feature detection without leaking an unhandled rejection; keep the synchronous
DisposableStackCtor logic unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dee697cb-f331-4fa8-9d2f-b6b7674152f2

📥 Commits

Reviewing files that changed from the base of the PR and between 239bb3c and dafcc1d.

📒 Files selected for processing (1)
  • packages/common/src/Polyfills.ts

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 43 out of 44 changed files in this pull request and generated 5 comments.

Comment on lines 7 to +10
"dev": "bun --watch src/index.ts",
"build": "rimraf dist && tsc",
"build": "tsc --build tsconfig.json",
"start": "node dist/src/index.js",
"clean": "rimraf .turbo node_modules dist data/evolu-relay.db"
"clean": "bun ../../scripts/rm.mts .turbo node_modules dist data/evolu-relay.db"
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

build changed from rimraf dist && tsc to tsc --build tsconfig.json without cleaning dist first. tsc --build won’t delete outputs for removed/renamed sources, so repeat builds can leave stale files under dist/. Consider adding a dist clean step (or a dedicated clean:dist) before building to keep the runtime output deterministic.

Copilot uses AI. Check for mistakes.
?.value as unknown;
const installed =
typeof installedValue === "symbol" ? installedValue : undefined;
if (installed !== undefined) return installed;
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
packages/common/test/WebSocket.test.ts (1)

44-46: ⚠️ Potential issue | 🟠 Major

Browser WebSocket testy jsou stále fakticky vypnuté.

Na Line 44-46 je běh v browseru podmíněný process.env.EVOLU_BROWSER_WS_TESTS === "1", ale browser konfigurace tuhle proměnnou nenastavuje, takže wsTest spadne na test.skip a browser větev se neověří.

💡 Minimální oprava
-const browserWsEnabled =
-  typeof process !== "undefined" && process.env?.EVOLU_BROWSER_WS_TESTS === "1";
+const browserWsEnabled = typeof WebSocket !== "undefined";
 const wsTest = isServer || browserWsEnabled ? test : test.skip;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/common/test/WebSocket.test.ts` around lines 44 - 46, The browser
WebSocket branch is never exercised because browserWsEnabled only checks
process.env.EVOLU_BROWSER_WS_TESTS, which isn’t set in browser builds; update
the condition that defines browserWsEnabled so it detects browser runtime
correctly (e.g., check typeof window !== "undefined" and read a browser-friendly
env like import.meta.env.VITE_EVOLU_BROWSER_WS_TESTS or default to enabled in
browser CI) and then use that in the existing wsTest assignment (symbols:
browserWsEnabled, EVOLU_BROWSER_WS_TESTS, wsTest, isServer) so tests in the
browser run instead of falling back to test.skip.
🧹 Nitpick comments (2)
packages/common/test/TreeShaking.test.ts (1)

201-202: Zvažte použití test.skipIf místo tichého return.

Tiché přeskočení testu v případě, že Bun není dostupný, může v CI skrýt problém s nastavením prostředí. Vitest nabízí explicitnější API pro podmíněné přeskakování testů.

♻️ Návrh explicitního přeskočení
-  test("bundle sizes (fast lane)", async () => {
-    if (!isBunRuntime) return;
-
+  test.skipIf(!isBunRuntime)("bundle sizes (fast lane)", async () => {
     const fixtures = getFixtures();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/common/test/TreeShaking.test.ts` around lines 201 - 202, Currently
tests use an early "if (!isBunRuntime) return;" to silently skip when Bun isn't
available; replace that pattern with Vitest's conditional skip API by using
test.skipIf(!isBunRuntime, ...) (or describe.skipIf for a block) so the skip is
explicit in test metadata; locate occurrences of the guard (isBunRuntime and the
early return) and wrap the affected test/describe callbacks with test.skipIf or
describe.skipIf, preserving the original test body and name.
packages/common/src/Polyfills.ts (1)

77-84: Potenciální race condition při asynchronní detekci.

Mezi dokončením synchronní kontroly a vyřešením asynchronního Promise může kód mezitím použít nativní (potenciálně nefunkční) implementaci. Pokud WebKit selže pouze při async disposal (ne při synchronní probe na řádcích 118-137), kód mezi řádky 76 a 84 může stále používat nefunkční nativní API.

Toto je pravděpodobně přijatelný kompromis, protože synchronní probe zachytí většinu problémů, ale stojí za zvážení, zda by nebylo lepší instalovat owned polyfill vždy, když je detekován WebKit runtime.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/common/src/Polyfills.ts` around lines 77 - 84, Asynchronous probe
(hasBrokenNativeDisposableStackAsync) can race with code using the native API;
change the initialization so the polyfill is installed deterministically: either
await hasBrokenNativeDisposableStackAsync(symbolDispose, symbolAsyncDispose)
during module init (remove the top-level void and make the initializer async or
expose an async init function) and call
installOwnedDisposableStackPolyfills(symbolDispose, symbolAsyncDispose) when it
resolves true, or detect WebKit synchronously and install
installOwnedDisposableStackPolyfills immediately for that runtime; update
callers to await the new async init if you choose the await approach.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/angular-vite-pwa/package.json`:
- Around line 19-20: Aktualizujte závislost `@angular/build` v package.json z
^21.1.5 na ^21.2.1, aby byla zarovnána s ostatními Angular core balíčky (např.
`@angular/compiler-cli` na ^21.2.1); upravte hodnotu verze pro "@angular/build" v
tom bloku a uložte změnu, aby všechny core balíčky měly stejnou minor/patch
verzi.

In `@packages/common/test/TreeShaking.test.ts`:
- Around line 187-188: Normalizační rozsah pro proměnnou raw v testu (kódový
fragment upravující raw mezi 15250–15450 na 15390) nepokrývá aktuální
snapshotovou hodnotu 15511; upravte test v TreeShaking.test.ts tak, že buď
rozšíříte rozsah pro raw tak, aby zahrnoval 15511 (např. zvětšit horní hranici)
nebo odstraníte/tlumíte normalizaci pro tu konkrétní hodnotu, aby snapshot
zůstal konzistentní; odkažte se na proměnnou raw v té části testu (a případně na
související kontrolu snapshotu na řádku se 15511) při provedení změny.

In `@packages/nodejs/test/Relay.test.ts`:
- Around line 191-194: The current loose regex in the two tests uses 40[01]
which conflates 400 and 401; update each
expect(String(error.message)).toMatch(...) call to assert the exact expected
HTTP status for that specific test (replace 40[01] with the explicit status,
e.g., /Unexpected server response: 400|Connection ended/ or /Unexpected server
response: 401|Connection ended/ as appropriate), and make the same change for
the other occurrence around lines 220-223 so each test checks the precise status
rather than a range.

---

Duplicate comments:
In `@packages/common/test/WebSocket.test.ts`:
- Around line 44-46: The browser WebSocket branch is never exercised because
browserWsEnabled only checks process.env.EVOLU_BROWSER_WS_TESTS, which isn’t set
in browser builds; update the condition that defines browserWsEnabled so it
detects browser runtime correctly (e.g., check typeof window !== "undefined" and
read a browser-friendly env like import.meta.env.VITE_EVOLU_BROWSER_WS_TESTS or
default to enabled in browser CI) and then use that in the existing wsTest
assignment (symbols: browserWsEnabled, EVOLU_BROWSER_WS_TESTS, wsTest, isServer)
so tests in the browser run instead of falling back to test.skip.

---

Nitpick comments:
In `@packages/common/src/Polyfills.ts`:
- Around line 77-84: Asynchronous probe (hasBrokenNativeDisposableStackAsync)
can race with code using the native API; change the initialization so the
polyfill is installed deterministically: either await
hasBrokenNativeDisposableStackAsync(symbolDispose, symbolAsyncDispose) during
module init (remove the top-level void and make the initializer async or expose
an async init function) and call
installOwnedDisposableStackPolyfills(symbolDispose, symbolAsyncDispose) when it
resolves true, or detect WebKit synchronously and install
installOwnedDisposableStackPolyfills immediately for that runtime; update
callers to await the new async init if you choose the await approach.

In `@packages/common/test/TreeShaking.test.ts`:
- Around line 201-202: Currently tests use an early "if (!isBunRuntime) return;"
to silently skip when Bun isn't available; replace that pattern with Vitest's
conditional skip API by using test.skipIf(!isBunRuntime, ...) (or
describe.skipIf for a block) so the skip is explicit in test metadata; locate
occurrences of the guard (isBunRuntime and the early return) and wrap the
affected test/describe callbacks with test.skipIf or describe.skipIf, preserving
the original test body and name.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0c446d70-d8d5-4cc4-a9e0-c7495cc54a83

📥 Commits

Reviewing files that changed from the base of the PR and between dafcc1d and 26323ab.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (18)
  • apps/relay/package.json
  • examples/angular-vite-pwa/package.json
  • examples/react-electron/package.json
  • examples/react-expo/package.json
  • examples/tanstack-start/package.json
  • package.json
  • packages/common/src/Polyfills.ts
  • packages/common/src/Task.ts
  • packages/common/test/Polyfills.test.ts
  • packages/common/test/TreeShaking.test.ts
  • packages/common/test/WebSocket.test.ts
  • packages/common/test/_globalSetup.ts
  • packages/nodejs/package.json
  • packages/nodejs/test/Relay.test.ts
  • packages/react-native/package.json
  • packages/vue/package.json
  • packages/web/package.json
  • scripts/rm.mts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/common/src/Task.ts
  • package.json
  • examples/react-electron/package.json
  • packages/nodejs/package.json

Comment on lines 19 to +20
"@angular/build": "^21.1.5",
"@angular/compiler-cli": "^21.1.5",
"@angular/compiler-cli": "^21.2.1",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Angular 21 @angular/build @angular/compiler-cli version compatibility peer dependencies

💡 Result:

For Angular v21 (i.e., @angular/* major 21), the relevant compatibility/peer-dependency rules are:

  • @angular/build@21.1.4 peerDependencies include:

    • @angular/compiler-cli: ^21.0.0 (so any 21.x satisfies it)
    • typescript: >=5.9 <6.0
    • plus Angular framework peers like @angular/core, @angular/compiler, etc. at ^21.0.0 (and @angular/ssr at ^21.1.4). [1]
  • Angular 21 toolchain compatibility (official matrix):

    • Node.js: ^20.19.0 || ^22.12.0 || ^24.0.0
    • TypeScript: >=5.9.0 <6.0.0 [2]
  • @angular/compiler-cli version alignment

    • Angular’s core packages and CLI are major-version aligned (Angular 21 ↔ CLI 21). [3]
    • @angular/compiler-cli requires a matching @angular/compiler version (pattern shown in @angular/compiler-cli@20.1.4 → peer @angular/compiler: 20.1.4; Angular 21 follows the same “match the framework version” rule). [4]

Practical takeaway: keep @angular/build, @angular/compiler-cli, @angular/compiler, @angular/core all on major 21 (ideally the same minor/patch), and use TypeScript 5.9.x (not 5.8 or 6.0). [1][2][4]

Sources: [1] [2] [3] [4]


🌐 Web query:

Angular 21.2.1 release notes package version requirements

💡 Result:

Angular v21.x (applies to 21.2.x as well): required versions

Angular’s official compatibility table for Angular 21.0.x (i.e., the v21 line) lists these requirements: Node.js ^20.19.0 || ^22.12.0 || ^24.0.0, TypeScript >=5.9.0 <6.0.0, RxJS ^6.5.3 || ^7.4.0. (angular.dev)

Key npm peer/engine requirements (example from @angular/core)

From @angular/core’s package.json (shown here for 21.1.2, but this is the same pattern used across the v21 line):

  • engines.node: ^20.19.0 || ^22.12.0 || >=24.0.0
  • peerDependencies.rxjs: ^6.5.3 || ^7.4.0
  • peerDependencies.zone.js: ~0.15.0 || ~0.16.0
  • peerDependencies.@angular/compiler: must match exactly (same version as @angular/core) (app.unpkg.com)

About “Angular 21.2.1 release notes”

In the official Angular monorepo tags/releases, I can see v21.2.0 (Feb 25, 2026) and a vsix-21.2.1 tag (VS Code extension), but not a framework tag v21.2.1 on the tags page. (github.com)

If you confirm whether you mean Angular framework 21.2.1 (npm packages like @angular/core@21.2.1) or the VS Code extension 21.2.1, I can point you at the exact corresponding release notes.

Citations:


🏁 Script executed:

# Zkontrolovat soubor package.json a okolní kontext
cat -n examples/angular-vite-pwa/package.json | head -30

Repository: SQLoot/evolu-plan-b

Length of output: 991


Aktualizujte @angular/build na verzi ^21.2.1.

Na řádku 19 zůstává @angular/build na ^21.1.5, zatímco @angular/compiler-cli a další Angular balíčky jsou již na ^21.2.1. Podle oficiální Angular dokumentace by měly být všechny core balíčky (@angular/build, @angular/compiler-cli, @angular/compiler, @angular/core) na stejné minor/patch verzi. Ačkoli caret notace (^) technicky zajišťuje kompatibilitu, je to proti best practices.

Návrh opravy
-    "@angular/build": "^21.1.5",
+    "@angular/build": "^21.2.1",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"@angular/build": "^21.1.5",
"@angular/compiler-cli": "^21.1.5",
"@angular/compiler-cli": "^21.2.1",
"@angular/build": "^21.2.1",
"@angular/compiler-cli": "^21.2.1",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular-vite-pwa/package.json` around lines 19 - 20, Aktualizujte
závislost `@angular/build` v package.json z ^21.1.5 na ^21.2.1, aby byla zarovnána
s ostatními Angular core balíčky (např. `@angular/compiler-cli` na ^21.2.1);
upravte hodnotu verze pro "@angular/build" v tom bloku a uložte změnu, aby
všechny core balíčky měly stejnou minor/patch verzi.

Comment on lines +187 to +188
if (gzip >= 5650 && gzip <= 5750) gzip = 5692;
if (raw >= 15250 && raw <= 15450) raw = 15390;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Rozsah normalizace nepokrývá aktuální hodnotu snapshotu.

Normalizační rozsah pro task-example raw je 15250–15450 → 15390, ale snapshot na řádku 219 obsahuje hodnotu 15511, která je mimo tento rozsah. Pokud je 15511 stabilní výstup Bun bundleru, měl by se buď rozšířit rozsah, nebo odstranit normalizace pro tuto hodnotu.

🔧 Návrh úpravy rozsahu
   if (fixture === "task-example") {
     if (gzip >= 5650 && gzip <= 5750) gzip = 5692;
-    if (raw >= 15250 && raw <= 15450) raw = 15390;
+    if (raw >= 15400 && raw <= 15600) raw = 15511;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/common/test/TreeShaking.test.ts` around lines 187 - 188,
Normalizační rozsah pro proměnnou raw v testu (kódový fragment upravující raw
mezi 15250–15450 na 15390) nepokrývá aktuální snapshotovou hodnotu 15511;
upravte test v TreeShaking.test.ts tak, že buď rozšíříte rozsah pro raw tak, aby
zahrnoval 15511 (např. zvětšit horní hranici) nebo odstraníte/tlumíte
normalizaci pro tu konkrétní hodnotu, aby snapshot zůstal konzistentní; odkažte
se na proměnnou raw v té části testu (a případně na související kontrolu
snapshotu na řádku se 15511) při provedení změny.

Comment on lines +191 to +194
// Runtime-dependent: ws may report an HTTP status error or a generic close.
expect(String(error.message)).toMatch(
/Unexpected server response: 40[01]|Connection ended/,
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Zpřesněte očekávaný HTTP status v každém testu.

Aktuální regex 40[01] v obou testech dovolí i opačný status, takže regresi (400 ↔ 401) nemusí odhalit.

🎯 Návrh úpravy
-    expect(String(error.message)).toMatch(
-      /Unexpected server response: 40[01]|Connection ended/,
-    );
+    expect(String(error.message)).toMatch(
+      /Unexpected server response: 400|Connection ended/,
+    );
-    expect(String(error.message)).toMatch(
-      /Unexpected server response: 40[01]|Connection ended/,
-    );
+    expect(String(error.message)).toMatch(
+      /Unexpected server response: 401|Connection ended/,
+    );

Also applies to: 220-223

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nodejs/test/Relay.test.ts` around lines 191 - 194, The current loose
regex in the two tests uses 40[01] which conflates 400 and 401; update each
expect(String(error.message)).toMatch(...) call to assert the exact expected
HTTP status for that specific test (replace 40[01] with the explicit status,
e.g., /Unexpected server response: 400|Connection ended/ or /Unexpected server
response: 401|Connection ended/ as appropriate), and make the same change for
the other occurrence around lines 220-223 so each test checks the precise status
rather than a range.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Miccy <code@miccy.dev>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/common/test/_globalSetup.ts`:
- Around line 15-39: The closeWithTimeout function has duplicated and incomplete
declarations causing syntax errors; fix it by keeping a single declaration of
timeout (const timeout: ReturnType<typeof setTimeout> | undefined at top of the
promise scope), define the settle function exactly once to set settled = true,
clearTimeout(timeout) and call resolve(), set timeout = setTimeout(settle,
timeoutMs) and call timeout.unref?.(), and invoke close(settle) only once;
update the function body in closeWithTimeout to follow that single, corrected
sequence so the code compiles and behaves deterministically.
- Around line 111-119: The teardown currently calls client.terminate() on each
websocket client which triggers abnormal close (1006) and causes retry logic to
run; change the loop over instance.wsServer.clients to attempt a graceful close
first by calling client.close(1001) (use an existing helper like
closeWithTimeout or implement a short timeout) and only call client.terminate()
as a fallback if the graceful close does not complete within that timeout;
ensure you still await the final Promise.all that closes wsServer and
httpServer.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f6f0bb87-6faf-426d-9893-39b2936d9351

📥 Commits

Reviewing files that changed from the base of the PR and between 26323ab and 2f1c98b.

📒 Files selected for processing (1)
  • packages/common/test/_globalSetup.ts

Comment on lines +15 to +39
const closeWithTimeout = (
close: (callback: () => void) => void,
timeoutMs = 500,
): Promise<void> =>
new Promise<void>((resolve) => {
let settled = false;
let timeout: ReturnType<typeof setTimeout> | undefined;
const settle = () => {
if (settled) return;
let timeout: ReturnType<typeof setTimeout> | undefined;

const settle = () => {
if (settled) return;
settled = true;
if (timeout !== undefined) {
clearTimeout(timeout);
}
resolve();
};

timeout = setTimeout(settle, timeoutMs);
timeout.unref?.();
close(settle);
close(settle);
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n packages/common/test/_globalSetup.ts | head -50

Repository: SQLoot/evolu-plan-b

Length of output: 1782


🏁 Script executed:

wc -l packages/common/test/_globalSetup.ts

Repository: SQLoot/evolu-plan-b

Length of output: 103


🏁 Script executed:

cat -n packages/common/test/_globalSetup.ts | sed -n '90,120p'

Repository: SQLoot/evolu-plan-b

Length of output: 1176


🏁 Script executed:

cat -n packages/common/test/_globalSetup.ts | sed -n '15,45p'

Repository: SQLoot/evolu-plan-b

Length of output: 1082


Funkce closeWithTimeout má kritické syntaktické chyby a duplicitní volání.

Blok obsahuje neúplnou definici settle na řádku 22–23 (chybí tělo funkce), poté duplicitní deklaraci timeout na řádku 24 a duplicitní kompletní definici settle na řádcích 26–33. Navíc je funkce close(settle) volána dvakrát na řádcích 37–38 místo jedenkrát, což způsobí nedeterministické chování. Kód se nepřeloží.

Navrhovaná oprava
 const closeWithTimeout = (
   close: (callback: () => void) => void,
   timeoutMs = 500,
 ): Promise<void> =>
   new Promise<void>((resolve) => {
     let settled = false;
     let timeout: ReturnType<typeof setTimeout> | undefined;
+
     const settle = () => {
       if (settled) return;
-    let timeout: ReturnType<typeof setTimeout> | undefined;
-
-    const settle = () => {
-      if (settled) return;
       settled = true;
       if (timeout !== undefined) {
         clearTimeout(timeout);
       }
       resolve();
     };
 
     timeout = setTimeout(settle, timeoutMs);
     timeout.unref?.();
     close(settle);
-    close(settle);
   });
🧰 Tools
🪛 GitHub Actions: CI

[error] 39-39: Formatter parse error: Expected a semicolon or an implicit semicolon after a statement, but found none.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/common/test/_globalSetup.ts` around lines 15 - 39, The
closeWithTimeout function has duplicated and incomplete declarations causing
syntax errors; fix it by keeping a single declaration of timeout (const timeout:
ReturnType<typeof setTimeout> | undefined at top of the promise scope), define
the settle function exactly once to set settled = true, clearTimeout(timeout)
and call resolve(), set timeout = setTimeout(settle, timeoutMs) and call
timeout.unref?.(), and invoke close(settle) only once; update the function body
in closeWithTimeout to follow that single, corrected sequence so the code
compiles and behaves deterministically.

Comment on lines +111 to +119
// Ensure active sockets don't keep `wsServer.close` pending indefinitely.
for (const client of instance.wsServer.clients) {
client.terminate();
}

await Promise.all([
closeWithTimeout((done) => instance.wsServer.close(done)),
closeWithTimeout((done) => instance.httpServer.close(done)),
]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Teardown implementace v _globalSetup.ts ==="
cat -n packages/common/test/_globalSetup.ts | sed -n '105,125p'

echo
echo "=== Kontextu kolem closeWithTimeout funkce ==="
rg -n -B10 -A5 'closeWithTimeout' packages/common/test/_globalSetup.ts | head -60

echo
echo "=== WebSocket.ts - close event a retry logika ==="
rg -n -B5 -A10 'onclose|shouldRetryOnClose' packages/common/src/WebSocket.ts | head -80

Repository: SQLoot/evolu-plan-b

Length of output: 4102


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Úplný onclose handler v WebSocket.ts ==="
rg -n -A20 'socket\.onclose = ' packages/common/src/WebSocket.ts | head -80

echo
echo "=== lazyTrue definice ==="
rg -n -B2 -A5 'lazyTrue' packages/common/src/WebSocket.ts

echo
echo "=== Jak se shouldRetryOnClose používá v retry logice ==="
rg -n -B5 -A10 'shouldRetryOnClose' packages/common/src/WebSocket.ts | grep -A10 'onclose.*shouldRetryOnClose'

Repository: SQLoot/evolu-plan-b

Length of output: 2115


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== lazyTrue implementace ==="
rg -n -B2 -A5 'export.*lazyTrue|const lazyTrue' packages/common/src/Function.ts

echo
echo "=== ws knihovna dokumentace v node_modules ==="
find . -name "package.json" -path "*/node_modules/ws/*" | head -1 | xargs -I {} dirname {} | head -1

echo
echo "=== Výchozí closeWithTimeout timeout ==="
grep -n "closeWithTimeout.*timeoutMs\|= 500" packages/common/test/_globalSetup.ts

Repository: SQLoot/evolu-plan-b

Length of output: 496


🌐 Web query:

ws npm package terminate() vs close() behavior WebSocket closing handshake abnormal closure

💡 Result:

In the Node.js ws package:

  • ws.close([code, reason]) starts the WebSocket closing handshake (it sends a Close control frame, optionally with a close code/reason). If both sides exchange Close frames, the connection is considered closed cleanly. In ws, if the peer doesn’t complete the handshake in time, ws will eventually force-close the connection after closeTimeout (default 30s). [1][2]

  • ws.terminate() forcibly closes immediately by destroying the underlying TCP socket (socket.destroy()), without waiting for (or necessarily sending) the WebSocket Close frame/handshake. [1]

Why this often shows up as “abnormal closure” / code 1006:

  • Per RFC 6455, if the TCP connection closes and no Close control frame was received, the application reports the close code as 1006 (“abnormal closure”). This happens with abrupt disconnects like terminate() (or process crash, network drop, etc.). [2]
  • 1006 is a reserved, internal-only code (it’s a reported outcome, not a code you properly send in a Close frame). [2][3]

Rule of thumb: use close() for graceful shutdown (lets the peer get a real close code/reason); use terminate() when you need an immediate teardown (e.g., stuck connection, failed heartbeat, resource protection), accepting that the peer may observe an abnormal close (1006). [1][2]

Sources:
[1] ws documentation (close(), terminate(), closeTimeout) https://github.com/websockets/ws/blob/master/doc/ws.md
[2] RFC 6455 (closing handshake; when to report 1006) https://www.rfc-editor.org/rfc/rfc6455
[3] 1006 abnormal closure overview (reserved / no close handshake) https://protocolcodes.com/websocket/1006-abnormal-closure/


Graceful close před terminate() v teardownu, aby se předešlo retry logice

client.terminate() vyvolá abnormal close event (kód 1006) bez Close handshake. Výchozí shouldRetryOnClose = lazyTrue vrací true pro všechny close eventy, což triggeruje retry logiku a způsobuje flakey chování v testech. Doporučuji nejdřív zavolat graceful client.close(1001) a terminate() pouze jako fallback, pokud se zavření nestihne za krátký timeout.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/common/test/_globalSetup.ts` around lines 111 - 119, The teardown
currently calls client.terminate() on each websocket client which triggers
abnormal close (1006) and causes retry logic to run; change the loop over
instance.wsServer.clients to attempt a graceful close first by calling
client.close(1001) (use an existing helper like closeWithTimeout or implement a
short timeout) and only call client.terminate() as a fallback if the graceful
close does not complete within that timeout; ensure you still await the final
Promise.all that closes wsServer and httpServer.

@miccy miccy closed this Mar 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working fix Repair any bug update Update libs and deps upstream Needs cherry-pick or merge from upstream

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants