Skip to content

PR: Merge upstream Common v8 branch plus my fixes#8

Closed
miccy wants to merge 125 commits intomainfrom
dev
Closed

PR: Merge upstream Common v8 branch plus my fixes#8
miccy wants to merge 125 commits intomainfrom
dev

Conversation

@miccy
Copy link
Collaborator

@miccy miccy commented Dec 23, 2025

Description

Type of Change

  • 🐛 Bug fix
  • ✨ New feature
  • 📝 Documentation update
  • ♻️ Refactor
  • 🔧 Configuration change
  • ⬆️ Dependency update

Related Issues

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review
  • I have added tests (if applicable)
  • Documentation has been updated (if needed)
  • All tests pass locally

Summary by CodeRabbit

  • Nové funkce

    • Rozšířené pole‑nástroje: flatMap, concat, sort, reverse, splice a přejmenované nemutující operace.
    • Resource management: DisposableStack a AsyncDisposableStack; createObjectURL s automatickým dispose.
    • Platformně‑agnostické worker/message rozhraní a React Native polyfily.
    • Globalní error scope API (handleGlobalError, createGlobalErrorScope).
  • Zásadní změny

    • Kryptografie: nové encrypt/decrypt XChaCha20-Poly1305 API a nové chybové typy.
    • Result/Ref/Store: ok/err návratové typy a Ref.set/modify vracejí boolean; API Array přejmenování/remove.
    • Min. Node.js zvýšeno na 24.
  • Dokumentace

    • Nové/repracované stránky: Resource management, Dependency injection, Conventions.

✏️ Tip: You can customize this high-level summary in your review settings.

steida and others added 30 commits November 30, 2025 23:45
Updated the 'engines.node' field from >=22.0.0 to >=24.0.0 across all package.json files to require the current LTS version. This is a major version change for all affected packages.
Introduces polyfills for Symbol.dispose, Symbol.asyncDispose, DisposableStack, and AsyncDisposableStack to support automatic resource cleanup in environments lacking native support. Updates documentation and navigation to cover resource management concepts and usage. Refactors example to use DisposableStack for safer URL cleanup. Adds comprehensive tests for polyfill correctness and updates Result tests for resource management patterns.
Groups tests for resource management under more descriptive nested 'describe' blocks for 'using keyword', 'DisposableStack', and 'AsyncDisposableStack'. This improves test organization and readability without changing test logic.
Introduces tests demonstrating generator-based monadic composition with the Result type, comparing imperative and generator patterns, verifying type inference, performance, and resource disposal behavior. These tests illustrate how generator composition can reduce boilerplate.
Added 'sql.raw' to the embeddedSqlTags in prettier.config.mjs to support additional SQL formatting. Updated .prettierignore to adjust ignored paths for API reference MDX files.
Bump various dependencies including @typescript-eslint, shiki, vitest, @op-engineering/op-sqlite, @react-navigation, electron-to-chromium, sf-symbols-typescript, and tinyexec to their latest versions for improved stability, features, and security.
Bumps the npm_and_yarn group with 1 update in the /examples/react-electron directory: [electron](https://github.com/electron/electron).


Updates `electron` from 30.5.1 to 35.7.5
- [Release notes](https://github.com/electron/electron/releases)
- [Changelog](https://github.com/electron/electron/blob/main/docs/breaking-changes.md)
- [Commits](electron/electron@v30.5.1...v35.7.5)

Updates `vite` from 5.4.20 to 7.2.4
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.2.4/packages/vite)

Updates `esbuild` from 0.21.5 to 0.25.12
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG-2024.md)
- [Commits](evanw/esbuild@v0.21.5...v0.25.12)

Updates `js-yaml` from 4.1.0 to 4.1.1
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](nodeca/js-yaml@4.1.0...4.1.1)

---
updated-dependencies:
- dependency-name: electron
  dependency-version: 35.7.5
  dependency-type: direct:development
  dependency-group: npm_and_yarn
- dependency-name: vite
  dependency-version: 7.2.4
  dependency-type: direct:development
  dependency-group: npm_and_yarn
- dependency-name: esbuild
  dependency-version: 0.25.12
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: js-yaml
  dependency-version: 4.1.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Updated the 'engines.node' field from >=22.0.0 to >=24.0.0 across all package.json files to require the current LTS version. This is a major version change for all affected packages.
Introduces polyfills for Symbol.dispose, Symbol.asyncDispose, DisposableStack, and AsyncDisposableStack to support automatic resource cleanup in environments lacking native support. Updates documentation and navigation to cover resource management concepts and usage. Refactors example to use DisposableStack for safer URL cleanup. Adds comprehensive tests for polyfill correctness and updates Result tests for resource management patterns.
Groups tests for resource management under more descriptive nested 'describe' blocks for 'using keyword', 'DisposableStack', and 'AsyncDisposableStack'. This improves test organization and readability without changing test logic.
Introduces tests demonstrating generator-based monadic composition with the Result type, comparing imperative and generator patterns, verifying type inference, performance, and resource disposal behavior. These tests illustrate how generator composition can reduce boilerplate.
Added 'sql.raw' to the embeddedSqlTags in prettier.config.mjs to support additional SQL formatting. Updated .prettierignore to adjust ignored paths for API reference MDX files.
Bump various dependencies including @typescript-eslint, shiki, vitest, @op-engineering/op-sqlite, @react-navigation, electron-to-chromium, sf-symbols-typescript, and tinyexec to their latest versions for improved stability, features, and security.
@miccy miccy self-assigned this Dec 23, 2025
@miccy miccy added feature New feature or request dependencies Pull requests that update a dependency file fix Bugfixes labels Dec 23, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 23, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Refaktoruje Array API (sjednocení a přejmenování), zavádí nové crypto funkce a branded typy, přidává resource‑management polyfily, přepisuje worker/message‑port abstrakci, mění několik veřejných typů (Lazy, Result, UnknownError), upravuje Ref/Store a rozšiřuje dokumentaci, testy a příklady.

Changes

Cohort / File(s) Popis
Changesets & versioning
.changeset/*.md
Hromadné bumps verzí, Node engine → >=24, dokumentované breaking změny.
Array module
packages/common/src/Array.ts, packages/common/test/Array.test.ts
Sjednocení isNonEmptyArray (overloady), isNonEmptyReadonlyArray odstraněn; shiftArrayshiftFromArray, popArraypopFromArray; přidány flatMapArray, concatArrays, sortArray, reverseArray, spliceArray.
Function / Lazy rename
packages/common/src/Function.ts, docs/tests
LazyValue<T> přejmenováno na Lazy<T>; aktualizace konstant a příkladů.
Error model → UnknownError
packages/common/src/Error.ts, tests, local-first/Sqlite.ts
TransferableErrorUnknownError; createTransferableErrorcreateUnknownError; přidán GlobalErrorScope a handleGlobalError.
Result typing
packages/common/src/Result.ts, tests
ok()/err() overloady nyní vrací Result<T, never> / Result<never, E> (změna inference).
Crypto refactor
packages/common/src/Crypto.ts, packages/common/test/Crypto.test.ts, packages/common/src/local-first/Protocol.ts
Odebrán SymmetricCrypto, přidáno encryptWithXChaCha20Poly1305/decryptWithXChaCha20Poly1305, branded Entropy24 a ciphertext, xChaCha20Poly1305NonceLength a migrace protokolu na RandomBytesDep + nové chyby.
Resource management polyfills
packages/common/src/Polyfills.ts, packages/common/src/Resources.ts, packages/common/src/Object.ts
Přidány DisposableStack, AsyncDisposableStack, ensurePolyfills(), createObjectURL a Dep wrappery (DisposableDep, DisposableStackDep).
Ref / Store changes
packages/common/src/Ref.ts, packages/common/src/Store.ts, tests
Ref.set/modify nyní vrací boolean; createRef akceptuje volitelný eq; Store rozdělena na ReadonlyStore + Ref a interně používá createRef.
Worker / MessagePort abstraction
packages/common/src/Worker.ts, packages/react-native/src/Worker.ts, packages/nodejs/src/Worker.ts, docs/tests
Nový port/kanálový model: MessagePort, MessageChannel, Worker, SharedWorker, WorkerScope, SharedWorkerScope, factory typy (CreateMessagePort, CreateMessageChannel); onMessage → property; RN polyfill implementace; odstraněny staré helpery.
Index side‑effects / docs
packages/common/src/index.ts, packages/common/typedoc.json
Volání ensurePolyfills() při importu balíčku; Polyfills.ts přidán do Typedoc entryPoints.
Local‑first / Evolu surface
packages/common/src/local-first/*.ts
Přidány EvoluConfig, EvoluDeps, ErrorStoreDep; typové refaktory mutation/query; Db.ts dočasně zakomentován; šifrování a dekódování přes nové crypto API; přidán decodeRle.
Docs, web app, examples & tooling
apps/web/src/app/(docs)/*.mdx, apps/web/src/app/(playgrounds)/*, package.json, apps/*/package.json, eslint.config.mjs, tsconfig.json
Nové stránky (Resource Management), přepracovaná DI guidance, příklady přepsány na createEvoluDeps a createObjectURL, podpora .mts, přidán script build:docs, rozsáhlé dependency bumps a Node engine bump.
Tests & test scaffolding
packages/common/test/**/*
Nové/rozšířené testy pro Polyfills, Ref, Store, Result, Array, Crypto, Protocol; některé integrační/local‑first testy výrazně zredukovány (TODO stubs).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant UI as Client (UI)
  participant Channel as MessageChannel / MessagePort
  participant Worker as WorkerScope
  participant ErrorScope as GlobalErrorScope

  Note over UI,Channel: Vytvoření kanálu/portu (factory)
  UI->>Channel: createMessageChannel() / createMessagePort(native)
  UI->>Channel: postMessage(payload)
  Channel->>Worker: deliver message (microtask / postMessage)
  Worker->>Worker: onMessage handler zpracuje zprávu
  alt Handler vyhodí chybu
    Worker->>ErrorScope: handleGlobalError(scope, error)
    ErrorScope->>UI: onError callback (notifikace / log)
  else Odpověď
    Worker->>Channel: postMessage(response)
    Channel->>UI: deliver response
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • Update issue templates #6 — Přidává stejné .github/ISSUE_TEMPLATE/bug_report.md (pravděpodobná duplikace přidání šablony).

Poem

🐰 Jsem králík, v kůži bílý,
přepsal jsem pole, klíče i vlákna tiché.
Porty šeptají zprávy, stacky hlídají zdroje,
Lazy a Unknown teď v nových oblecích stojí.
Hop — migrace skokem, mrkev v kapse, hurá!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive Název není jasně spojen se hauptním obsahu pull requestu. Zmínka o 'upstream Common v8' a opravách není dostatečně specifická. Pojmenujte pull request více konkrétně, např. 'Upgrade Node.js minimum na 24 a refaktor Array/Task modulů' nebo podobně, aby jasně vyjadřoval hlavní změny.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

@github-actions
Copy link

github-actions bot commented Dec 23, 2025

@github-actions
Copy link

github-actions bot commented Dec 23, 2025

❗️ Caution
Review the following alerts detected in dependencies.

According to your organization's policies, you must resolve all "Block" alerts before proceeding. It's recommended to resolve "Warn" alerts too.
Learn more about Socket for GitHub.

Action Severity Alert (click for details)
Warn SocketIssueSeverity.MIDDLE
unconfig-core@7.4.2 - Unpopular package

Note: This package is not very popular.

Source: Manifest File

ℹ️ Read more on: This package | This alert | What is known malware?

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

Mark as acceptable risk: To ignore this alert only in this pull request, reply with:
@SocketSecurity ignore unconfig-core@7.4.2
Or ignore all future alerts with:
@SocketSecurity ignore-all

Warn SocketIssueSeverity.HIGH
oxide-android-arm64@4.1.18 - Unstable ownership

Note: A new collaborator has begun publishing package versions. Package stability and security risk may be elevated.

Source: Manifest File

ℹ️ Read more on: This package | This alert | What is known malware?

Suggestion: Try to reduce the amount 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 as acceptable risk: To ignore this alert only in this pull request, reply with:
@SocketSecurity ignore oxide-android-arm64@4.1.18
Or ignore all future alerts with:
@SocketSecurity ignore-all

Warn SocketIssueSeverity.HIGH
oxide-win32-arm64-msvc@4.1.18 - Unstable ownership

Note: A new collaborator has begun publishing package versions. Package stability and security risk may be elevated.

Source: Manifest File

ℹ️ Read more on: This package | This alert | What is known malware?

Suggestion: Try to reduce the amount 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 as acceptable risk: To ignore this alert only in this pull request, reply with:
@SocketSecurity ignore oxide-win32-arm64-msvc@4.1.18
Or ignore all future alerts with:
@SocketSecurity ignore-all

Warn SocketIssueSeverity.HIGH
node@4.1.18 - Unstable ownership

Note: A new collaborator has begun publishing package versions. Package stability and security risk may be elevated.

Source: Manifest File

ℹ️ Read more on: This package | This alert | What is known malware?

Suggestion: Try to reduce the amount 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 as acceptable risk: To ignore this alert only in this pull request, reply with:
@SocketSecurity ignore node@4.1.18
Or ignore all future alerts with:
@SocketSecurity ignore-all

Warn SocketIssueSeverity.HIGH
oxide-wasm32-wasi@4.1.18 - Unstable ownership

Note: A new collaborator has begun publishing package versions. Package stability and security risk may be elevated.

Source: Manifest File

ℹ️ Read more on: This package | This alert | What is known malware?

Suggestion: Try to reduce the amount 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 as acceptable risk: To ignore this alert only in this pull request, reply with:
@SocketSecurity ignore oxide-wasm32-wasi@4.1.18
Or ignore all future alerts with:
@SocketSecurity ignore-all

Warn SocketIssueSeverity.HIGH
svelte@5.46.0 - Obfuscated code

Note: Obfuscated files are intentionally packed to hide their behavior. This could be a sign of malware

Source: Manifest File

ℹ️ Read more on: This package | This alert | What is known malware?

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

Mark as acceptable risk: To ignore this alert only in this pull request, reply with:
@SocketSecurity ignore svelte@5.46.0
Or ignore all future alerts with:
@SocketSecurity ignore-all

Warn SocketIssueSeverity.HIGH
vite@7.3.0 - Obfuscated code

Note: Obfuscated files are intentionally packed to hide their behavior. This could be a sign of malware

Source: Manifest File

ℹ️ Read more on: This package | This alert | What is known malware?

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

Mark as acceptable risk: To ignore this alert only in this pull request, reply with:
@SocketSecurity ignore vite@7.3.0
Or ignore all future alerts with:
@SocketSecurity ignore-all

Warn SocketIssueSeverity.HIGH
next@16.1.1 - Telemetry

Note: This package contains telemetry which tracks how it is used.

Source: Manifest File

ℹ️ Read more on: This package | This alert | What is known malware?

Suggestion: Most telemetry comes with settings to disable it. Consider disabling telemetry if you do not want to be tracked.

Mark as acceptable risk: To ignore this alert only in this pull request, reply with:
@SocketSecurity ignore next@16.1.1
Or ignore all future alerts with:
@SocketSecurity ignore-all

View full report

@socket-security
Copy link

socket-security bot commented Dec 23, 2025

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatedeslint-config-next@​16.0.7 ⏵ 16.1.1991006798100
Updated@​types/​react-highlight-words@​0.20.0 ⏵ 0.20.11001006986 +7100
Updatedvue-tsc@​3.1.5 ⏵ 3.2.1100 +110070 +397 +2100
Updated@​typescript-eslint/​parser@​7.18.0 ⏵ 8.50.110010071 +198100
Updatedtypescript-eslint@​8.48.1 ⏵ 8.50.11001007498100
Updated@​angular/​platform-browser@​21.0.3 ⏵ 21.0.61001007598 +2100
Updated@​angular/​compiler-cli@​21.0.3 ⏵ 21.0.6100 +110077 +198 +1100
Updatedshiki@​3.19.0 ⏵ 3.20.01001007798 +1100
Updatedexpo-router@​6.0.15 ⏵ 6.0.219510077 +1100 +1100
Updatedexpo@​54.0.25 ⏵ 54.0.309810078 +1100 +1100
Updated@​angular/​build@​21.0.2 ⏵ 21.0.494 -110078 +198 +2100
Added@​vitest/​browser@​4.0.16981007899100
Updatedvitest@​4.0.15 ⏵ 4.0.1697 +110079 +198100
Updated@​types/​react@​18.3.24 ⏵ 19.2.71001007994100
Updated@​angular/​core@​21.0.3 ⏵ 21.0.694 +110079 +198 +2100 +20
Updatedexpo-constants@​18.0.10 ⏵ 18.0.1210010079100 +1100
Updatedexpo-linking@​8.0.9 ⏵ 8.0.1110010080100 +1100
Updated@​typescript-eslint/​eslint-plugin@​7.18.0 ⏵ 8.50.199 +110080 +198100
Updated@​types/​node@​24.5.2 ⏵ 24.10.4100 +110081 +195 -1100
Updatednext@​16.0.7 ⏵ 16.1.183100 +18919870
Updatedvite@​5.4.20 ⏵ 7.3.092 +1100 +28299 +1100
Updatedprettier-plugin-sql-cst@​0.16.0 ⏵ 0.17.392100100 +194 +270
Updatedexpo-secure-store@​15.0.7 ⏵ 15.0.810010083100 +1100
Added@​types/​sharedworker@​0.0.198991008496100
Updatedtailwindcss@​4.1.17 ⏵ 4.1.1810010084 +198100
Updatedturbo@​2.6.2 ⏵ 2.6.310010084 -197 +1100
Updatedexpo-font@​14.0.9 ⏵ 14.0.1010010084100 +1100
Added@​vitest/​browser-playwright@​4.0.16991008698100
Updatedprettier-plugin-embed@​0.5.0 ⏵ 0.5.199 +1100100 +187100
Updatedsvelte@​5.45.5 ⏵ 5.46.087 -510087 +197 -1100
Updated@​tailwindcss/​vite@​4.1.17 ⏵ 4.1.18100 +110089 +199100
Updated@​tabler/​icons-react@​3.35.0 ⏵ 3.36.099 +110098 +190 -1100
See 23 more rows in the dashboard

View full report

@socket-security
Copy link

socket-security bot commented Dec 23, 2025

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 @tailwindcss/node is now published by malfaitrobin

Author: malfaitrobin

From: pnpm-lock.yamlnpm/@tailwindcss/vite@4.1.18npm/@tailwindcss/postcss@4.1.18npm/@tailwindcss/node@4.1.18

ℹ 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/@tailwindcss/node@4.1.18. 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 @tailwindcss/oxide-android-arm64 is now published by malfaitrobin

Author: malfaitrobin

From: pnpm-lock.yamlnpm/@tailwindcss/oxide-android-arm64@4.1.18

ℹ 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/@tailwindcss/oxide-android-arm64@4.1.18. 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 @tailwindcss/oxide-wasm32-wasi is now published by malfaitrobin

Author: malfaitrobin

From: pnpm-lock.yamlnpm/@tailwindcss/oxide-wasm32-wasi@4.1.18

ℹ 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/@tailwindcss/oxide-wasm32-wasi@4.1.18. 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 @tailwindcss/oxide-win32-arm64-msvc is now published by malfaitrobin

Author: malfaitrobin

From: pnpm-lock.yamlnpm/@tailwindcss/oxide-win32-arm64-msvc@4.1.18

ℹ 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/@tailwindcss/oxide-win32-arm64-msvc@4.1.18. 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

…er sanitization

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Signed-off-by: Miccy <code@miccy.dev>
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.

ℹ️ 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".

Comment on lines 962 to +969
loadQueryMicrotaskQueue.length = 0;
assertNonEmptyReadonlyArray(queries);
deps.console.log("[evolu]", "loadQuery", { queries });
dbWorker.postMessage({
type: "query",
tabId: getTabId(),
queries,
});
// dbWorker.postMessage({
// type: "query",
// tabId: getTabId(),
// queries,
// });

Choose a reason for hiding this comment

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

P0 Badge Dispatch queries to worker to resolve loadQuery promises

In createEvolu the loadQuery path enqueues queries but the call that actually posts them to the DB worker is commented out. With no dbWorker.postMessage dispatch, nothing ever calls loadingPromises.resolve, so loadQuery returns a promise that never settles and subscribed queries never populate. Any consumer calling loadQuery or using subscribeQuery will hang indefinitely because no results are produced.

Useful? React with 👍 / 👎.

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: 43

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (8)
packages/common/src/local-first/PublicKysely.ts (1)

21-67: Opravte formátování příkladů v JSDoc - použijte ### Example namíste ## Examples.

Příručka kodování specifikuje: "Use ### Example instead of @example in JSDoc - for better markdown rendering and consistency". Všechny tři JSDoc bloky (jsonArrayFrom na řádku 24, jsonObjectFrom na řádku 83, jsonBuildObject na řádku 138) používají ## Examples, ale správný formát je ### Example (tři haše, jednotné číslo).

- * ## Examples
+ * ### Example
apps/web/src/app/(docs)/docs/conventions/page.mdx (1)

115-117: Opravte neplatnou TypeScript syntaxi v příkladu.

{ ... } není platná TypeScript syntaxe. Použijte prázdný objekt {} nebo konkrétní příklad s reálnými vlastnostmi pro lepší srozumitelnost.

🔎 Navrhovaná oprava
-const users: Record<UserId, string> = { ... };
+const users: Record<UserId, string> = { user1: "Alice", user2: "Bob" };
apps/web/src/app/(docs)/docs/privacy/page.mdx (1)

61-67: Sledujte nedokončenou dokumentaci.

Sekce zmiňuje, že "Detailed documentation will be provided soon" pro kolaboraci s asymetrickou kryptografií. Tento zástupný text by měl být vyřešen nebo sledován.

Chcete, abych vytvořil issue pro sledování této nedokončené dokumentace?

apps/web/src/components/Features.tsx (3)

23-32: Zvažte použití readonly modifikátorů pro neměnnost.

Podle coding guidelines by definice typů měly preferovat neměnnost použitím readonly vlastností.

🔎 Navrhovaná úprava
 interface Feature {
-  id: string;
-  name: string;
-  description: string;
-  icon: React.ComponentType<IconProps>;
-  pattern: Omit<
+  readonly id: string;
+  readonly name: string;
+  readonly description: string;
+  readonly icon: React.ComponentType<IconProps>;
+  readonly pattern: Omit<
     React.ComponentPropsWithoutRef<typeof GridPattern>,
     "width" | "height"
   >;
 }

Podle coding guidelines.


34-34: Použijte ReadonlyArray pro neměnnost pole.

Podle coding guidelines by pole mělo být typováno jako ReadonlyArray<Feature> nebo readonly Feature[] pro podporu neměnnosti.

🔎 Navrhovaná úprava
-const features: Array<Feature> = [
+const features: ReadonlyArray<Feature> = [

nebo alternativně:

-const features: Array<Feature> = [
+const features: readonly Feature[] = [

Podle coding guidelines.


188-188: Použijte arrow function pro konzistenci.

Podle coding guidelines by komponenta měla používat arrow function místo klíčového slova function pro zajištění konzistence v celém kódu.

🔎 Navrhovaná úprava
-function Feature({ feature }: { feature: Feature }) {
+const Feature = ({ feature }: { feature: Feature }) => {

Podle coding guidelines.

packages/common/src/Task.ts (1)

1-1: Kritická chyba: Import neexistujícího shiftArray způsobuje selhání buildu.

Modul ./Array.js neexportuje shiftArray. Funkcionalita existuje pod názvem shiftFromArray.

Opravte import a všechna použití:

  • Řádek 1: Změňte shiftArray na shiftFromArray
  • Řádky 655, 692: Aktualizujte volání na shiftFromArray
packages/common/src/local-first/Evolu.ts (1)

1106-1109: Dispose není implementován – zvažte jiný přístup.

Vyhazování výjimky v [Symbol.dispose] je problematické, protože:

  • using statement způsobí neočekávanou výjimku
  • Dispose by měl být bezpečný pro volání vždy
🔎 Navrhovaná úprava
      /** Disposal is not implemented yet. */
      [Symbol.dispose]: () => {
-       throw new Error("Evolu instance disposal is not yet implemented");
+       // TODO: Implement proper disposal
+       deps.console.log("[evolu]", "dispose called but not yet implemented");
      },
♻️ Duplicate comments (1)
apps/web/src/mdx/searchIndex.js (1)

20-28: Poznámka k bezpečnostnímu varování: Regex je bezpečný, ale ověřte escapování v render vrstvě.

K řádku 24 existuje bezpečnostní varování o možné HTML injekci. Regex /\s*<[^>]*>$/ je určen pro odstranění generických parametrů TypeScriptu (např. Evolu<S>Evolu) a sám o sobě není zranitelný.

Data procházejí tímto flow:

  1. Build time: MDX obsah → JSON.stringify() → injekce do šablony
  2. Runtime: search() vrací { url, title, pageTitle }
  3. UI: Search.tsx renderuje pomocí react-highlight-words

Ověřte, že komponenta Highlighter v Search.tsx (řádky 155-161) správně escapuje HTML entity. Knihovna react-highlight-words by měla být bezpečná, ale ujistěte se, že je používána nejnovější verze bez známých zranitelností:

#!/bin/bash
# Ověření verze a bezpečnostních advisories pro react-highlight-words

# Zkontrolovat aktuální verzi v package.json
rg -A 2 '"react-highlight-words"' package.json

# Vyhledat známé zranitelnosti
gh api graphql -f query='
{
  securityVulnerabilities(first: 10, ecosystem: NPM, package: "react-highlight-words") {
    nodes {
      advisory {
        summary
        severity
        publishedAt
      }
      vulnerableVersionRange
      firstPatchedVersion {
        identifier
      }
    }
  }
}'
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a89fadf and 0936343.

⛔ Files ignored due to path filters (2)
  • examples/react-electron/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (166)
  • .changeset/array-module-refactor.md
  • .changeset/gentle-pumas-eat.md
  • .changeset/global-error-scope.md
  • .changeset/lazy-rename.md
  • .changeset/lovely-aliens-camp.md
  • .changeset/red-wings-itch.md
  • .changeset/result-never-inference.md
  • .changeset/smart-refs-store.md
  • .changeset/spotty-coats-sort.md
  • .changeset/tough-cats-fall.md
  • .changeset/transferable-error-rename.md
  • .changeset/typed-discriminant.md
  • .changeset/worker-abstraction-refactor.md
  • .github/ISSUE_TEMPLATE/bug_report.md
  • .github/copilot-instructions.md
  • .prettierignore
  • README.md
  • apps/relay/package.json
  • apps/relay/src/index.ts
  • apps/web/package.json
  • apps/web/scripts/fix-api-reference.mts
  • apps/web/src/app/(docs)/docs/conventions/page.mdx
  • apps/web/src/app/(docs)/docs/dependency-injection/page.mdx
  • apps/web/src/app/(docs)/docs/library/page.mdx
  • apps/web/src/app/(docs)/docs/local-first/page.mdx
  • apps/web/src/app/(docs)/docs/page.mdx
  • apps/web/src/app/(docs)/docs/privacy/page.mdx
  • apps/web/src/app/(docs)/docs/relay/page.mdx
  • apps/web/src/app/(docs)/docs/resource-management/page.mdx
  • apps/web/src/app/(landing)/blog/the-copy-paste-typescript-standard-library/page.mdx
  • apps/web/src/app/(landing)/blog/you-might-not-need-comlink/page.mdx
  • apps/web/src/app/(playgrounds)/playgrounds/full/EvoluFullExample.tsx
  • apps/web/src/app/(playgrounds)/playgrounds/minimal/EvoluMinimalExample.tsx
  • apps/web/src/app/(playgrounds)/playgrounds/multitenant/EvoluMultitenantExample.tsx
  • apps/web/src/app/(playgrounds)/playgrounds/multitenant/page.tsx
  • apps/web/src/components/Alerts.tsx
  • apps/web/src/components/Features.tsx
  • apps/web/src/components/Search.tsx
  • apps/web/src/lib/navigation.ts
  • apps/web/src/mdx/search.mjs
  • apps/web/src/mdx/searchIndex.js
  • apps/web/src/styles/tailwind.css
  • apps/web/tsconfig.json
  • apps/web/typography.mts
  • biome.json
  • eslint.config.mjs
  • examples/angular-vite-pwa/package.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/vue-vite-pwa/package.json
  • package.json
  • packages/common/README.md
  • packages/common/package.json
  • packages/common/src/Array.ts
  • packages/common/src/Assert.ts
  • packages/common/src/BigInt.ts
  • packages/common/src/Brand.ts
  • packages/common/src/Buffer.ts
  • packages/common/src/Cache.ts
  • packages/common/src/Callbacks.ts
  • packages/common/src/Console.ts
  • packages/common/src/Crypto.ts
  • packages/common/src/Eq.ts
  • packages/common/src/Error.ts
  • packages/common/src/Function.ts
  • packages/common/src/Identicon.ts
  • packages/common/src/Number.ts
  • packages/common/src/Object.ts
  • packages/common/src/Order.ts
  • packages/common/src/Platform.ts
  • packages/common/src/Polyfills.ts
  • packages/common/src/Random.ts
  • packages/common/src/Redacted.ts
  • packages/common/src/Ref.ts
  • packages/common/src/Resources.ts
  • packages/common/src/Result.ts
  • packages/common/src/Sqlite.ts
  • packages/common/src/Store.ts
  • packages/common/src/Task.ts
  • packages/common/src/Time.ts
  • packages/common/src/Type.ts
  • packages/common/src/Types.ts
  • packages/common/src/WebSocket.ts
  • packages/common/src/Worker.ts
  • packages/common/src/index.ts
  • packages/common/src/local-first/Db.ts
  • packages/common/src/local-first/Error.ts
  • packages/common/src/local-first/Evolu.ts
  • packages/common/src/local-first/Owner.ts
  • packages/common/src/local-first/Platform.ts
  • packages/common/src/local-first/Protocol.ts
  • packages/common/src/local-first/Public.ts
  • packages/common/src/local-first/PublicKysely.ts
  • packages/common/src/local-first/Query.ts
  • packages/common/src/local-first/Relay.ts
  • packages/common/src/local-first/Schema.ts
  • packages/common/src/local-first/SharedWorker.ts
  • packages/common/src/local-first/Storage.ts
  • packages/common/src/local-first/Sync.ts
  • packages/common/src/local-first/Timestamp.ts
  • packages/common/src/local-first/index.ts
  • packages/common/test/Array.test.ts
  • packages/common/test/Brand.test.ts
  • packages/common/test/Crypto.test.ts
  • packages/common/test/Error.test.ts
  • packages/common/test/Function.test.ts
  • packages/common/test/Polyfills.test.ts
  • packages/common/test/Ref.test.ts
  • packages/common/test/Result.test.ts
  • packages/common/test/Sqlite.test.ts
  • packages/common/test/Store.test.ts
  • packages/common/test/_deps.ts
  • packages/common/test/local-first/Db.test.ts
  • packages/common/test/local-first/Evolu.test.ts
  • packages/common/test/local-first/Protocol.test.ts
  • packages/common/test/local-first/Timestamp.test.ts
  • packages/common/typedoc.json
  • packages/nodejs/README.md
  • packages/nodejs/package.json
  • packages/nodejs/src/Error.ts
  • packages/nodejs/src/Worker.ts
  • packages/react-native/README.md
  • packages/react-native/package.json
  • packages/react-native/src/Worker.ts
  • packages/react-native/src/createExpoDeps.ts
  • packages/react-native/src/exports/bare-op-sqlite.ts
  • packages/react-native/src/index.ts
  • packages/react-native/src/shared.ts
  • packages/react-web/README.md
  • packages/react-web/package.json
  • packages/react-web/src/components/index.ts
  • packages/react-web/src/index.ts
  • packages/react/README.md
  • packages/react/package.json
  • packages/react/src/createUseEvolu.ts
  • packages/react/src/useQuery.ts
  • packages/svelte/README.md
  • packages/svelte/package.json
  • packages/svelte/src/lib/index.svelte.ts
  • packages/vue/README.md
  • packages/vue/package.json
  • packages/vue/src/createUseEvolu.ts
  • packages/vue/src/useQuery.ts
  • packages/web/README.md
  • packages/web/package.json
  • packages/web/src/Error.ts
  • packages/web/src/Platform.ts
  • packages/web/src/SharedWebWorker.ts
  • packages/web/src/WebWorker.ts
  • packages/web/src/Worker.ts
  • packages/web/src/index.ts
  • packages/web/src/local-first/Db.worker.ts
  • packages/web/src/local-first/Evolu.ts
  • packages/web/src/local-first/LocalAuth.ts
  • packages/web/src/local-first/Platform.ts
  • packages/web/src/local-first/SharedWorker.worker.ts
  • packages/web/src/local-first/index.ts
  • packages/web/test/SharedWebWorker.test.ts
  • pnpm-workspace.yaml
  • prettier.config.mjs
  • scripts/publish-local.mts
  • scripts/toggle-examples-deps.mts
  • typedoc.json
💤 Files with no reviewable changes (4)
  • apps/web/typography.mts
  • packages/common/src/local-first/Platform.ts
  • apps/web/src/app/(docs)/docs/relay/page.mdx
  • .prettierignore
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use named imports only - avoid default exports and namespace imports
Use unique exported members - avoid namespaces, use descriptive names to prevent conflicts
Organize code top-down - public interfaces first, then implementation, then implementation details
Use arrow functions - avoid the function keyword for consistency, except for function overloads where TypeScript requires the function keyword
Favor immutability - use readonly properties and ReadonlyArray/NonEmptyReadonlyArray in type definitions
Avoid @param and @return tags in JSDoc - TypeScript provides type information, focus on describing the function's purpose instead
Use ### Example instead of @example in JSDoc - for better markdown rendering and consistency
Use @experimental tag for new APIs that may change or be removed in minor/patch versions without breaking semver
Use Result<T, E> for business/domain errors in public APIs - keep implementation-specific errors internal
Favor imperative patterns over monadic helpers for readability in Result handling
Use plain objects for business errors, Error instances only for debugging
Use Result<void, E> for operations that don't return values
Use trySync for wrapping synchronous unsafe code and tryAsync for wrapping asynchronous unsafe code
Use getOrThrow only for critical startup code where failure should crash
Use Type for validation/parsing - leverage Evolu's Type system for runtime validation
Define typed errors using interfaces extending TypeError<Name>
Create Type factories using brand, transform, array, object and other Evolu Type utilities
Use Brand types for semantic distinctions and constraints
Use assertions for conditions logically guaranteed but not statically known by TypeScript, catching developer mistakes eagerly
Never use assertions instead of proper type validation - use Type system for runtime validation
Define dependencies as interfaces following Evolu's convention-based DI approach
Use...

Files:

  • packages/common/src/Eq.ts
  • packages/common/src/Identicon.ts
  • packages/common/src/Console.ts
  • packages/common/src/local-first/Error.ts
  • packages/common/test/Ref.test.ts
  • packages/common/src/Number.ts
  • apps/web/src/components/Features.tsx
  • packages/common/src/Assert.ts
  • packages/common/src/Random.ts
  • packages/common/src/Callbacks.ts
  • packages/common/src/Object.ts
  • packages/common/src/Brand.ts
  • packages/common/src/local-first/Query.ts
  • packages/common/src/Buffer.ts
  • packages/common/test/Error.test.ts
  • apps/web/src/app/(playgrounds)/playgrounds/multitenant/page.tsx
  • packages/common/src/local-first/PublicKysely.ts
  • packages/common/test/Sqlite.test.ts
  • packages/common/src/Resources.ts
  • packages/nodejs/src/Error.ts
  • packages/common/test/Brand.test.ts
  • packages/common/src/BigInt.ts
  • packages/common/src/Platform.ts
  • packages/common/src/local-first/SharedWorker.ts
  • packages/common/test/local-first/Db.test.ts
  • packages/common/test/Polyfills.test.ts
  • packages/common/test/Function.test.ts
  • packages/common/src/Polyfills.ts
  • packages/common/src/Order.ts
  • packages/common/src/Types.ts
  • apps/web/src/components/Alerts.tsx
  • packages/common/src/local-first/Relay.ts
  • apps/web/src/app/(playgrounds)/playgrounds/multitenant/EvoluMultitenantExample.tsx
  • packages/common/src/local-first/Timestamp.ts
  • packages/common/src/Redacted.ts
  • packages/nodejs/src/Worker.ts
  • apps/relay/src/index.ts
  • packages/common/src/Type.ts
  • packages/common/src/WebSocket.ts
  • packages/common/src/local-first/Owner.ts
  • packages/common/src/Time.ts
  • packages/common/test/_deps.ts
  • apps/web/src/app/(playgrounds)/playgrounds/full/EvoluFullExample.tsx
  • apps/web/src/lib/navigation.ts
  • packages/react-native/src/Worker.ts
  • packages/common/test/Result.test.ts
  • packages/common/src/Ref.ts
  • packages/common/test/local-first/Protocol.test.ts
  • packages/common/src/Cache.ts
  • packages/common/src/local-first/Sync.ts
  • apps/web/src/components/Search.tsx
  • packages/common/src/local-first/Storage.ts
  • packages/common/src/index.ts
  • packages/common/src/Result.ts
  • packages/common/src/Sqlite.ts
  • packages/common/src/Task.ts
  • packages/common/src/local-first/Schema.ts
  • packages/common/test/Store.test.ts
  • packages/common/test/local-first/Timestamp.test.ts
  • packages/common/src/Function.ts
  • packages/common/test/local-first/Evolu.test.ts
  • packages/common/src/Error.ts
  • apps/web/src/app/(playgrounds)/playgrounds/minimal/EvoluMinimalExample.tsx
  • packages/common/src/local-first/index.ts
  • packages/common/src/local-first/Public.ts
  • packages/common/src/local-first/Db.ts
  • packages/common/src/Store.ts
  • packages/common/test/Crypto.test.ts
  • packages/common/src/Crypto.ts
  • packages/common/src/Array.ts
  • packages/common/src/local-first/Protocol.ts
  • packages/common/test/Array.test.ts
  • packages/common/src/local-first/Evolu.ts
  • packages/common/src/Worker.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.test.{ts,tsx}: Leverage _deps.ts - use existing test utilities and mocks from packages/common/test/_deps.ts such as testCreateId, testTime, testOwner
Mock dependencies using the same interfaces as production code
Create test factories like createTestTime for generating test dependencies
Never rely on global state in tests
Use assertions in tests for conditions that should never fail

Files:

  • packages/common/test/Ref.test.ts
  • packages/common/test/Error.test.ts
  • packages/common/test/Sqlite.test.ts
  • packages/common/test/Brand.test.ts
  • packages/common/test/local-first/Db.test.ts
  • packages/common/test/Polyfills.test.ts
  • packages/common/test/Function.test.ts
  • packages/common/test/Result.test.ts
  • packages/common/test/local-first/Protocol.test.ts
  • packages/common/test/Store.test.ts
  • packages/common/test/local-first/Timestamp.test.ts
  • packages/common/test/local-first/Evolu.test.ts
  • packages/common/test/Crypto.test.ts
  • packages/common/test/Array.test.ts
🧠 Learnings (36)
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Use `### Example` instead of `example` in JSDoc - for better markdown rendering and consistency

Applied to files:

  • packages/common/src/Eq.ts
  • packages/common/src/Identicon.ts
  • packages/common/src/Number.ts
  • packages/common/src/Assert.ts
  • packages/common/src/Random.ts
  • packages/common/src/Callbacks.ts
  • packages/common/src/Brand.ts
  • packages/common/src/local-first/Query.ts
  • packages/common/src/Buffer.ts
  • packages/common/src/Order.ts
  • packages/common/src/Types.ts
  • packages/common/src/Redacted.ts
  • packages/common/src/Time.ts
  • apps/web/src/app/(docs)/docs/conventions/page.mdx
  • packages/common/src/Cache.ts
  • .github/copilot-instructions.md
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Use `Result<T, E>` for business/domain errors in public APIs - keep implementation-specific errors internal

Applied to files:

  • packages/common/src/local-first/Error.ts
  • packages/common/src/Callbacks.ts
  • packages/common/src/Type.ts
  • .changeset/result-never-inference.md
  • packages/common/test/Result.test.ts
  • .github/copilot-instructions.md
  • packages/common/src/Result.ts
  • packages/common/src/Sqlite.ts
  • packages/common/src/Error.ts
  • packages/common/src/local-first/Public.ts
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Define typed errors using interfaces extending `TypeError<Name>`

Applied to files:

  • packages/common/src/local-first/Error.ts
  • packages/common/src/Assert.ts
  • packages/common/src/Brand.ts
  • packages/common/test/Error.test.ts
  • packages/common/src/Type.ts
  • apps/web/src/app/(docs)/docs/conventions/page.mdx
  • .github/copilot-instructions.md
  • packages/common/src/Result.ts
  • .changeset/typed-discriminant.md
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.test.{ts,tsx} : Leverage `_deps.ts` - use existing test utilities and mocks from `packages/common/test/_deps.ts` such as `testCreateId`, `testTime`, `testOwner`

Applied to files:

  • packages/common/test/Ref.test.ts
  • packages/common/test/local-first/Db.test.ts
  • packages/common/test/Polyfills.test.ts
  • packages/common/test/Function.test.ts
  • packages/common/src/local-first/Relay.ts
  • apps/relay/src/index.ts
  • packages/common/src/Type.ts
  • apps/web/src/app/(landing)/blog/the-copy-paste-typescript-standard-library/page.mdx
  • packages/common/test/_deps.ts
  • packages/common/test/Result.test.ts
  • packages/common/test/local-first/Protocol.test.ts
  • packages/common/typedoc.json
  • packages/common/test/Store.test.ts
  • packages/common/test/local-first/Timestamp.test.ts
  • packages/common/test/local-first/Evolu.test.ts
  • apps/web/src/app/(playgrounds)/playgrounds/minimal/EvoluMinimalExample.tsx
  • packages/common/test/Crypto.test.ts
  • packages/common/package.json
  • packages/common/test/Array.test.ts
  • packages/common/src/local-first/Evolu.ts
  • apps/web/src/app/(docs)/docs/dependency-injection/page.mdx
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.test.{ts,tsx} : Never rely on global state in tests

Applied to files:

  • packages/common/test/Ref.test.ts
  • packages/common/test/local-first/Db.test.ts
  • packages/common/test/Polyfills.test.ts
  • packages/common/test/Function.test.ts
  • packages/common/test/Result.test.ts
  • .github/copilot-instructions.md
  • packages/common/test/Store.test.ts
  • packages/common/test/local-first/Timestamp.test.ts
  • packages/common/test/local-first/Evolu.test.ts
  • packages/common/test/Array.test.ts
  • apps/web/src/app/(docs)/docs/dependency-injection/page.mdx
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.test.{ts,tsx} : Use assertions in tests for conditions that should never fail

Applied to files:

  • packages/common/test/Ref.test.ts
  • packages/common/src/Assert.ts
  • packages/common/test/Error.test.ts
  • packages/common/test/Polyfills.test.ts
  • packages/common/test/Function.test.ts
  • packages/common/src/Type.ts
  • packages/common/test/Result.test.ts
  • packages/common/src/Result.ts
  • packages/common/test/local-first/Evolu.test.ts
  • packages/common/test/Array.test.ts
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.test.{ts,tsx} : Create test factories like `createTestTime` for generating test dependencies

Applied to files:

  • packages/common/test/Ref.test.ts
  • apps/web/tsconfig.json
  • packages/common/test/Brand.test.ts
  • packages/common/test/Polyfills.test.ts
  • packages/common/test/Function.test.ts
  • packages/common/test/_deps.ts
  • packages/common/typedoc.json
  • packages/common/test/Store.test.ts
  • packages/common/test/local-first/Timestamp.test.ts
  • packages/common/test/local-first/Evolu.test.ts
  • packages/common/package.json
  • apps/web/src/app/(docs)/docs/dependency-injection/page.mdx
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Use `experimental` tag for new APIs that may change or be removed in minor/patch versions without breaking semver

Applied to files:

  • apps/web/tsconfig.json
  • packages/common/src/Types.ts
  • .github/copilot-instructions.md
  • eslint.config.mjs
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Use `Partial<>` for optional dependencies

Applied to files:

  • apps/web/tsconfig.json
  • packages/common/src/Types.ts
  • packages/common/src/Type.ts
  • packages/common/typedoc.json
  • eslint.config.mjs
  • apps/web/src/app/(docs)/docs/dependency-injection/page.mdx
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Use Type for validation/parsing - leverage Evolu's Type system for runtime validation

Applied to files:

  • apps/web/tsconfig.json
  • packages/common/src/Assert.ts
  • packages/common/src/Type.ts
  • apps/web/src/app/(landing)/blog/the-copy-paste-typescript-standard-library/page.mdx
  • apps/web/src/app/(docs)/docs/conventions/page.mdx
  • .github/copilot-instructions.md
  • packages/common/src/Result.ts
  • packages/common/src/local-first/Public.ts
  • packages/common/src/Array.ts
  • .changeset/typed-discriminant.md
  • eslint.config.mjs
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Use assertions for conditions logically guaranteed but not statically known by TypeScript, catching developer mistakes eagerly

Applied to files:

  • apps/web/tsconfig.json
  • packages/common/src/Assert.ts
  • packages/common/src/Type.ts
  • apps/web/src/app/(landing)/blog/the-copy-paste-typescript-standard-library/page.mdx
  • packages/common/test/Result.test.ts
  • packages/common/src/Result.ts
  • packages/common/src/Array.ts
  • eslint.config.mjs
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Create Type factories using `brand`, `transform`, `array`, `object` and other Evolu Type utilities

Applied to files:

  • apps/web/tsconfig.json
  • packages/common/src/Brand.ts
  • packages/common/test/Brand.test.ts
  • packages/common/src/Type.ts
  • apps/web/src/app/(landing)/blog/the-copy-paste-typescript-standard-library/page.mdx
  • apps/web/src/app/(docs)/docs/conventions/page.mdx
  • .github/copilot-instructions.md
  • packages/common/typedoc.json
  • packages/common/src/local-first/Public.ts
  • packages/common/src/Array.ts
  • .changeset/typed-discriminant.md
  • eslint.config.mjs
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Define dependencies as interfaces following Evolu's convention-based DI approach

Applied to files:

  • apps/web/tsconfig.json
  • apps/relay/src/index.ts
  • apps/web/src/app/(playgrounds)/playgrounds/full/EvoluFullExample.tsx
  • apps/web/src/app/(docs)/docs/conventions/page.mdx
  • .github/copilot-instructions.md
  • packages/common/typedoc.json
  • apps/web/src/app/(playgrounds)/playgrounds/minimal/EvoluMinimalExample.tsx
  • apps/relay/package.json
  • packages/common/src/local-first/Public.ts
  • .changeset/typed-discriminant.md
  • eslint.config.mjs
  • packages/common/src/local-first/Evolu.ts
  • apps/web/src/app/(docs)/docs/dependency-injection/page.mdx
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Use Brand types for semantic distinctions and constraints

Applied to files:

  • apps/web/tsconfig.json
  • packages/common/src/Brand.ts
  • packages/common/test/Brand.test.ts
  • packages/common/src/Types.ts
  • apps/web/src/components/Alerts.tsx
  • packages/common/src/Type.ts
  • apps/web/src/app/(landing)/blog/the-copy-paste-typescript-standard-library/page.mdx
  • apps/web/src/app/(docs)/docs/conventions/page.mdx
  • .github/copilot-instructions.md
  • .changeset/typed-discriminant.md
  • eslint.config.mjs
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Favor immutability - use `readonly` properties and `ReadonlyArray`/`NonEmptyReadonlyArray` in type definitions

Applied to files:

  • apps/web/tsconfig.json
  • packages/common/src/Assert.ts
  • packages/common/test/Function.test.ts
  • packages/common/src/Types.ts
  • packages/common/src/Type.ts
  • .changeset/array-module-refactor.md
  • apps/web/src/app/(docs)/docs/conventions/page.mdx
  • packages/common/typedoc.json
  • packages/common/src/local-first/Schema.ts
  • apps/web/scripts/fix-api-reference.mts
  • packages/common/src/Function.ts
  • packages/common/src/Store.ts
  • packages/common/src/Array.ts
  • eslint.config.mjs
  • packages/common/test/Array.test.ts
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Use named imports only - avoid default exports and namespace imports

Applied to files:

  • apps/web/tsconfig.json
  • apps/web/src/app/(docs)/docs/conventions/page.mdx
  • .github/copilot-instructions.md
  • packages/common/src/index.ts
  • eslint.config.mjs
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Wrap dependencies using naming conventions like `TimeDep`, `LoggerDep` etc. to avoid property clashes

Applied to files:

  • apps/web/tsconfig.json
  • packages/common/src/Resources.ts
  • packages/common/src/local-first/Relay.ts
  • apps/relay/src/index.ts
  • packages/common/src/Type.ts
  • apps/web/src/app/(landing)/blog/the-copy-paste-typescript-standard-library/page.mdx
  • packages/common/test/_deps.ts
  • apps/web/src/app/(docs)/docs/conventions/page.mdx
  • .github/copilot-instructions.md
  • packages/common/typedoc.json
  • apps/web/src/app/(playgrounds)/playgrounds/minimal/EvoluMinimalExample.tsx
  • apps/web/src/app/(docs)/docs/dependency-injection/page.mdx
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Organize code top-down - public interfaces first, then implementation, then implementation details

Applied to files:

  • apps/web/tsconfig.json
  • apps/web/src/app/(docs)/docs/conventions/page.mdx
  • .github/copilot-instructions.md
  • packages/common/typedoc.json
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Over-providing dependencies is acceptable, over-depending is not

Applied to files:

  • apps/web/tsconfig.json
  • apps/web/src/app/(docs)/docs/conventions/page.mdx
  • eslint.config.mjs
  • apps/web/src/app/(docs)/docs/dependency-injection/page.mdx
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Never use assertions instead of proper type validation - use Type system for runtime validation

Applied to files:

  • packages/common/src/Assert.ts
  • packages/common/src/Type.ts
  • apps/web/src/app/(landing)/blog/the-copy-paste-typescript-standard-library/page.mdx
  • apps/web/src/app/(docs)/docs/conventions/page.mdx
  • .github/copilot-instructions.md
  • packages/common/src/Result.ts
  • eslint.config.mjs
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Use `getOrThrow` only for critical startup code where failure should crash

Applied to files:

  • packages/common/src/Assert.ts
  • packages/common/src/Result.ts
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Favor imperative patterns over monadic helpers for readability in Result handling

Applied to files:

  • packages/common/src/Assert.ts
  • packages/common/src/Callbacks.ts
  • packages/common/src/Type.ts
  • .changeset/result-never-inference.md
  • packages/common/test/Result.test.ts
  • apps/web/src/app/(docs)/docs/conventions/page.mdx
  • .github/copilot-instructions.md
  • packages/common/src/Result.ts
  • packages/common/src/Array.ts
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Use `Result<void, E>` for operations that don't return values

Applied to files:

  • packages/common/src/Callbacks.ts
  • packages/common/src/Type.ts
  • .changeset/result-never-inference.md
  • packages/common/test/Result.test.ts
  • .github/copilot-instructions.md
  • packages/common/src/Result.ts
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Avoid `param` and `return` tags in JSDoc - TypeScript provides type information, focus on describing the function's purpose instead

Applied to files:

  • packages/common/src/local-first/Query.ts
  • packages/common/src/Types.ts
  • packages/common/src/Time.ts
  • .github/copilot-instructions.md
  • apps/web/scripts/fix-api-reference.mts
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Use plain objects for business errors, Error instances only for debugging

Applied to files:

  • packages/common/test/Error.test.ts
  • packages/common/src/Type.ts
  • .github/copilot-instructions.md
  • packages/common/src/Result.ts
  • packages/common/src/Error.ts
  • packages/common/src/local-first/Public.ts
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Do not use generics in dependency interfaces - keep them implementation-agnostic

Applied to files:

  • packages/common/src/Types.ts
  • apps/web/src/app/(docs)/docs/conventions/page.mdx
  • apps/web/src/app/(docs)/docs/dependency-injection/page.mdx
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Use currying for functions with dependencies - functions accept one `deps` parameter combining dependencies

Applied to files:

  • apps/relay/src/index.ts
  • packages/common/test/_deps.ts
  • apps/web/src/app/(playgrounds)/playgrounds/minimal/EvoluMinimalExample.tsx
  • apps/web/src/app/(docs)/docs/dependency-injection/page.mdx
📚 Learning: 2025-12-23T13:22:25.317Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.317Z
Learning: Check compilation using `pnpm build` to verify TypeScript compilation across all packages

Applied to files:

  • README.md
  • package.json
📚 Learning: 2025-12-23T13:22:25.317Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.317Z
Learning: Run tests using pnpm with `pnpm test` from the project root to run all tests

Applied to files:

  • README.md
📚 Learning: 2025-12-23T13:22:25.317Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.317Z
Learning: Run linting using `pnpm lint` to check code style and linting rules

Applied to files:

  • README.md
📚 Learning: 2025-12-23T13:22:25.317Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.317Z
Learning: Run specific test files using `pnpm test --filter evolu/package-name -- test-file-pattern` from project root

Applied to files:

  • README.md
  • package.json
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.test.{ts,tsx} : Mock dependencies using the same interfaces as production code

Applied to files:

  • packages/common/test/_deps.ts
  • apps/web/src/app/(docs)/docs/dependency-injection/page.mdx
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Use `trySync` for wrapping synchronous unsafe code and `tryAsync` for wrapping asynchronous unsafe code

Applied to files:

  • packages/common/test/Result.test.ts
  • apps/web/src/app/(docs)/docs/conventions/page.mdx
  • .github/copilot-instructions.md
  • packages/common/src/Result.ts
  • packages/common/src/Error.ts
  • eslint.config.mjs
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Use unique exported members - avoid namespaces, use descriptive names to prevent conflicts

Applied to files:

  • apps/web/src/app/(docs)/docs/conventions/page.mdx
  • .github/copilot-instructions.md
  • packages/common/src/index.ts
  • packages/common/typedoc.json
  • apps/web/src/app/(docs)/docs/page.mdx
  • packages/common/src/local-first/index.ts
  • packages/common/src/local-first/Public.ts
  • eslint.config.mjs
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Use arrow functions - avoid the `function` keyword for consistency, except for function overloads where TypeScript requires the `function` keyword

Applied to files:

  • apps/web/src/app/(docs)/docs/conventions/page.mdx
  • .github/copilot-instructions.md
  • apps/web/scripts/fix-api-reference.mts
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Avoid global static instances - do not use service locator pattern

Applied to files:

  • apps/web/src/app/(docs)/docs/conventions/page.mdx
  • .github/copilot-instructions.md
  • apps/web/src/app/(docs)/docs/dependency-injection/page.mdx
🧬 Code graph analysis (28)
packages/common/test/Ref.test.ts (2)
packages/common/src/Ref.ts (1)
  • createRef (54-70)
packages/common/src/Eq.ts (1)
  • eqStrict (13-13)
packages/common/test/Error.test.ts (1)
packages/common/src/Error.ts (1)
  • createUnknownError (22-67)
apps/web/src/app/(playgrounds)/playgrounds/multitenant/page.tsx (1)
apps/web/src/app/(landing)/blog/rss.xml/route.ts (1)
  • dynamic (48-48)
apps/web/src/mdx/search.mjs (1)
apps/web/src/mdx/searchIndex.js (1)
  • data (14-14)
packages/nodejs/src/Error.ts (2)
packages/web/src/Error.ts (1)
  • createGlobalErrorScope (19-42)
packages/common/src/Error.ts (2)
  • GlobalErrorScope (84-91)
  • handleGlobalError (99-109)
packages/common/test/Brand.test.ts (2)
packages/common/src/Brand.ts (2)
  • Brand (79-81)
  • IsBranded (95-95)
packages/common/src/Function.ts (1)
  • constVoid (143-143)
packages/common/test/Polyfills.test.ts (2)
packages/common/src/Polyfills.ts (2)
  • DisposableStack (354-357)
  • AsyncDisposableStack (363-367)
packages/common/src/Type.ts (1)
  • TypeError (440-448)
packages/common/test/Function.test.ts (1)
packages/common/src/Function.ts (1)
  • readonly (104-112)
packages/common/src/Polyfills.ts (1)
packages/common/src/Type.ts (1)
  • TypeError (440-448)
packages/common/src/local-first/Relay.ts (1)
packages/common/src/Function.ts (1)
  • Lazy (141-141)
apps/relay/src/index.ts (2)
packages/common/src/Console.ts (1)
  • createConsole (101-165)
packages/nodejs/src/local-first/Relay.ts (1)
  • createNodeJsRelay (47-53)
packages/common/test/_deps.ts (2)
packages/common/src/Crypto.ts (1)
  • RandomBytesDep (53-55)
packages/common/src/Time.ts (1)
  • TimeDep (10-12)
apps/web/src/app/(playgrounds)/playgrounds/full/EvoluFullExample.tsx (2)
packages/common/src/local-first/Evolu.ts (2)
  • createEvoluDeps (609-620)
  • createEvolu (677-1113)
packages/common/src/Object.ts (1)
  • createObjectURL (136-144)
packages/common/test/local-first/Protocol.test.ts (2)
packages/common/src/local-first/Protocol.ts (3)
  • encodeAndEncryptDbChange (1741-1783)
  • decryptAndDecodeDbChange (1790-1859)
  • decodeRle (1617-1648)
packages/common/src/Crypto.ts (2)
  • EncryptionKey (116-116)
  • EncryptionKey (117-117)
packages/common/src/local-first/Sync.ts (5)
packages/common/src/Crypto.ts (2)
  • DecryptWithXChaCha20Poly1305Error (165-168)
  • RandomBytesDep (53-55)
packages/common/src/local-first/Timestamp.ts (3)
  • TimestampCounterOverflowError (48-50)
  • TimestampDriftError (42-46)
  • TimestampTimeOutOfRangeError (52-54)
packages/common/src/local-first/Public.ts (3)
  • TimestampCounterOverflowError (38-38)
  • TimestampDriftError (39-39)
  • TimestampTimeOutOfRangeError (41-41)
packages/common/src/Error.ts (2)
  • UnknownError (16-19)
  • createUnknownError (22-67)
packages/common/src/local-first/Protocol.ts (1)
  • decryptAndDecodeDbChange (1790-1859)
apps/web/src/components/Search.tsx (1)
apps/web/src/mdx/searchIndex.js (2)
  • search (63-145)
  • search (63-145)
packages/common/src/index.ts (1)
packages/common/src/Polyfills.ts (1)
  • ensurePolyfills (373-381)
packages/common/src/Result.ts (1)
packages/common/src/Type.ts (1)
  • Result (4654-4654)
packages/common/src/Sqlite.ts (1)
packages/common/src/Error.ts (2)
  • UnknownError (16-19)
  • createUnknownError (22-67)
packages/common/test/Store.test.ts (1)
packages/common/src/Store.ts (1)
  • createStore (49-86)
packages/common/test/local-first/Timestamp.test.ts (2)
packages/common/src/local-first/Timestamp.ts (3)
  • defaultTimestampMaxDrift (31-31)
  • TimestampConfigDep (33-35)
  • minMillis (74-74)
packages/common/src/Time.ts (1)
  • TimeDep (10-12)
packages/common/test/local-first/Evolu.test.ts (1)
packages/common/test/local-first/_uint8ArraySerializer.ts (1)
  • test (7-9)
packages/common/src/Error.ts (2)
packages/common/src/Result.ts (1)
  • err (321-321)
packages/common/src/Assert.ts (1)
  • assert (29-36)
apps/web/src/app/(playgrounds)/playgrounds/minimal/EvoluMinimalExample.tsx (2)
packages/common/src/local-first/Evolu.ts (2)
  • createEvoluDeps (609-620)
  • Evolu (227-574)
packages/react-web/src/index.ts (1)
  • createEvoluDeps (9-12)
packages/common/src/Store.ts (1)
packages/common/src/Ref.ts (2)
  • Ref (33-45)
  • createRef (54-70)
packages/common/test/Crypto.test.ts (1)
packages/common/src/Crypto.ts (5)
  • encryptWithXChaCha20Poly1305 (152-163)
  • decryptWithXChaCha20Poly1305 (192-203)
  • XChaCha20Poly1305Ciphertext (127-130)
  • XChaCha20Poly1305Ciphertext (131-132)
  • createRandomBytes (72-74)
packages/common/src/local-first/Protocol.ts (5)
packages/common/src/Crypto.ts (11)
  • RandomBytesDep (53-55)
  • encryptWithXChaCha20Poly1305 (152-163)
  • EncryptionKey (116-116)
  • EncryptionKey (117-117)
  • DecryptWithXChaCha20Poly1305Error (165-168)
  • xChaCha20Poly1305NonceLength (120-120)
  • decryptWithXChaCha20Poly1305 (192-203)
  • XChaCha20Poly1305Ciphertext (127-130)
  • XChaCha20Poly1305Ciphertext (131-132)
  • Entropy24 (63-63)
  • Entropy24 (64-64)
packages/common/src/local-first/Storage.ts (8)
  • EncryptedCrdtMessage (238-241)
  • EncryptedDbChange (244-244)
  • RangeType (213-217)
  • RangeType (219-219)
  • Range (235-235)
  • Fingerprint (191-191)
  • DbChange (286-292)
  • DbChange (293-293)
packages/common/src/local-first/Timestamp.ts (6)
  • timestampToTimestampBytes (293-321)
  • Timestamp (177-181)
  • Timestamp (182-182)
  • timestampBytesToTimestamp (323-345)
  • TimestampBytes (288-288)
  • TimestampBytes (289-289)
packages/common/src/local-first/Public.ts (4)
  • timestampToTimestampBytes (34-34)
  • Timestamp (32-32)
  • timestampBytesToTimestamp (33-33)
  • TimestampBytes (37-37)
packages/common/src/Buffer.ts (1)
  • Buffer (65-111)
packages/common/src/Worker.ts (2)
packages/common/src/Brand.ts (1)
  • Brand (79-81)
packages/common/src/Error.ts (1)
  • GlobalErrorScope (84-91)
🪛 GitHub Actions: CI
packages/common/src/Task.ts

[error] 1-1: TS2305: Module './Array.js' has no exported member 'shiftArray'. Build failed during step 'turbo --filter @evolu/* build'.

🪛 GitHub Actions: Web Build
packages/common/src/Task.ts

[error] 1-1: TypeScript error in @evolu/common build: Module './Array.js' has no exported member 'shiftArray'.

🪛 markdownlint-cli2 (0.18.1)
.changeset/red-wings-itch.md

5-5: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

.changeset/lovely-aliens-camp.md

5-5: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

.changeset/gentle-pumas-eat.md

7-7: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

.changeset/array-module-refactor.md

5-5: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

.changeset/result-never-inference.md

5-5: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

.changeset/lazy-rename.md

5-5: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

.changeset/spotty-coats-sort.md

5-5: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

.changeset/global-error-scope.md

7-7: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

.changeset/tough-cats-fall.md

5-5: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

.changeset/worker-abstraction-refactor.md

5-5: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

.changeset/typed-discriminant.md

5-5: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

Comment on lines +1 to +68
---
"@evolu/common": major
"@evolu/web": major
---

Replaced interface-based symmetric encryption with direct function-based API

### Breaking Changes

**Removed:**

- `SymmetricCrypto` interface
- `SymmetricCryptoDep` interface
- `createSymmetricCrypto()` factory function
- `SymmetricCryptoDecryptError` error type

**Added:**

- `encryptWithXChaCha20Poly1305()` - Direct encryption function with explicit algorithm name
- `decryptWithXChaCha20Poly1305()` - Direct decryption function
- `XChaCha20Poly1305Ciphertext` - Branded type for ciphertext
- `Entropy24` - Branded type for 24-byte nonces
- `DecryptWithXChaCha20Poly1305Error` - Algorithm-specific error type
- `xChaCha20Poly1305NonceLength` - Constant for nonce length (24)

### Migration Guide

**Before:**

```ts
const symmetricCrypto = createSymmetricCrypto({ randomBytes });
const { nonce, ciphertext } = symmetricCrypto.encrypt(plaintext, key);
const result = symmetricCrypto.decrypt(ciphertext, key, nonce);
```

**After:**

```ts
const [ciphertext, nonce] = encryptWithXChaCha20Poly1305({ randomBytes })(
plaintext,
key,
);
const result = decryptWithXChaCha20Poly1305(ciphertext, nonce, key);
```

**Error handling:**

```ts
// Before
if (!result.ok && result.error.type === "SymmetricCryptoDecryptError") { ... }

// After
if (!result.ok && result.error.type === "DecryptWithXChaCha20Poly1305Error") { ... }
```

**Dependency injection:**

```ts
// Before
interface Deps extends SymmetricCryptoDep { ... }

// After - only encrypt needs RandomBytesDep
interface Deps extends RandomBytesDep { ... }
```

### Rationale

This change improves API extensibility by using explicit function names instead of a generic interface. Adding new encryption algorithms (e.g., `encryptWithAES256GCM`) is now straightforward without breaking existing code.
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Changeset jasně popisuje breaking změnu kryptografického API; lint MD041 je spíše kosmetika

Text dobře vystihuje odstranění SymmetricCrypto* a přechod na explicitní encryptWithXChaCha20Poly1305 / decryptWithXChaCha20Poly1305, včetně migrace chyb (SymmetricCryptoDecryptErrorDecryptWithXChaCha20Poly1305Error) a DI (RandomBytesDep místo SymmetricCryptoDep). To je pro uživatele velice užitečné.

Upozornění markdownlintu MD041 („First line in a file should be a top-level heading“) je v kontextu Changeset souborů spíš falešný poplach – frontmatter na první řádce je v tomto formátu standard. Pokud ale chcete lint úplně utišit, můžete za frontmatter přidat jednoduchý nadpis (např. # Crypto API refactor), nebo v konfiguraci markdownlintu vyloučit .changeset/* soubory z MD041.

🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

5-5: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

🤖 Prompt for AI Agents
In .changeset/tough-cats-fall.md around lines 1 to 68, markdownlint MD041 flags
the file because the first line is frontmatter rather than a top-level heading;
either add a simple H1 line (for example "# Crypto API refactor") immediately
after the frontmatter to satisfy MD041, or update the markdownlint configuration
to exclude .changeset/* from rule MD041 so changeset files are not linted for
this rule.

Comment on lines +1 to +8
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''

---
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Zvažte přidání výchozích štítků a kontextu specifického pro @evolu.

Šablona je funkční, ale vzhledem k rozsáhlým změnám v tomto PR (přejmenování API, transformace šifrování, abstractions pro workery) by mohla být obohacena o:

  1. Výchozí štítky (např. bug, triage) pro lepší třídění problémů
  2. Otázku na konkrétní balíček @evolu, který je ovlivněn
  3. Informaci o verzi @evolu balíčků

Namíchť generické šablony bude lépe sloužit k rychlejšímu třídění a diagnostice problémů specifických pro ekosystém.

🔎 Navrhovaná vylepšení
 ---
 name: Bug report
 about: Create a report to help us improve
 title: ''
-labels: ''
-assignees: ''
+labels: 'bug'
+assignees: ''
 
 ---
 
 **Describe the bug**
 A clear and concise description of what the bug is.
 
+**Affected package**
+Which @evolu package(s) are affected? (e.g., @evolu/react, @evolu/sqlite, @evolu/common)
+
+**Package version**
+What version of @evolu are you using?
+
 **To Reproduce**

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
.github/ISSUE_TEMPLATE/bug_report.md lines 1-8: the bug report template lacks
default labels and project-specific context for the @evolu ecosystem; update the
template to include default labels such as "bug" and "triage", add a question
field asking which @evolu package is affected, and add a field for the @evolu
package version(s) (or a dropdown/free-text) so incoming reports include
immediate triage metadata and package/version context for faster diagnosis.

description: article.description,
};

export default (props) => <ArticleLayout article={article} {...props} />;
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Upřednostněte pojmenovanou arrow funkci místo anonymní.

Výchozí export používá anonymní arrow funkci (props) => <ArticleLayout...>. Podle konvence kódování preferujte pojmenované funkce pro lepší čitelnost a debugovatelnost.

🔎 Navrhovaná úprava
-export default (props) => <ArticleLayout article={article} {...props} />;
+const YouMightNotNeedComlinkArticle = (props) => <ArticleLayout article={article} {...props} />;
+export default YouMightNotNeedComlinkArticle;

Podle pokynů pro kódování.

📝 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
export default (props) => <ArticleLayout article={article} {...props} />;
const YouMightNotNeedComlinkArticle = (props) => <ArticleLayout article={article} {...props} />;
export default YouMightNotNeedComlinkArticle;
🤖 Prompt for AI Agents
apps/web/src/app/(landing)/blog/you-might-not-need-comlink/page.mdx around line
16: the default export is an anonymous arrow function which reduces readability
and stacktrace clarity; replace it with a named function (e.g., const
YouMightNotNeedComlink = (props) => <ArticleLayout article={article} {...props}
/> or a named function declaration) and then export that name as default so the
component has a clear identifier for debugging and consistency with coding
conventions.

Comment on lines +3 to 5
test("TODO", () => {
expect(1).toBe(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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the actual test file
cat -n packages/common/test/local-first/Db.test.ts

Repository: miccy/evolu

Length of output: 50367


🏁 Script executed:

# Check if there are commented-out tests in the file
rg -n "//|/\*" packages/common/test/local-first/Db.test.ts

Repository: miccy/evolu

Length of output: 49578


🏁 Script executed:

# Search for other tests that might cover Db functionality
rg -n "Db|DbWorker" --type=ts --glob="**/*.test.ts" | head -50

Repository: miccy/evolu

Length of output: 4750


🏁 Script executed:

# Check the implementation file to see if code is commented out
cat -n packages/common/src/local-first/Db.ts | head -100

Repository: miccy/evolu

Length of output: 3553


🏁 Script executed:

# Search for git history to understand recent changes
git log --oneline -20 packages/common/test/local-first/Db.test.ts 2>/dev/null || echo "Git history not available"

Repository: miccy/evolu

Length of output: 147


Obnovit testovací pokrytí pro Db modul nebo vyjasnit záměr refaktoru.

Všechny testy v Db.test.ts (řádky 7-1375) jsou zakomentovány spolu s celou implementací v Db.ts. Testy pokrývaly inicializaci, lokální a synchronizované mutace, WebSocket komunikaci, karanténu zpráv a last-write-wins logiku. Pokud se jedná o probíhající refaktor, přidejte komentář s plánem obnovy nebo odkazem na související PR/issue. Pokud je to záměrné, zdokumentujte důvod a stav v kódu nebo v popisu commitu.

🤖 Prompt for AI Agents
In packages/common/test/local-first/Db.test.ts around lines 3-5 (and note the
larger commented block from ~7-1375), the original Db tests and implementation
were removed/commented out leaving a TODO placeholder; either restore the
original test coverage or clearly document the refactor intent. Fix by one of:
(A) re-add and enable the original tests covering init, local vs synced
mutations, WebSocket behavior, quarantine handling and LWW logic (ensure imports
match restored Db.ts implementation), or (B) replace the placeholder with a
concise comment at the top explaining this is a temporary refactor, link the
PR/issue tracking restoration, and update the commit/PR description accordingly
so coverage gaps are tracked; if intentionally removed, add a test-plan TODO
with an ETA and owner. Ensure CI passes and reference the related issue/PR ID in
the comment.

Comment on lines +3 to +4
test("TODO", () => {
expect(1).toBe(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 | 🔴 Critical

Kritický problém: Žádné testovací pokrytí pro funkcionalitu Evolu.

Placeholder test neposkytuje žádné skutečné pokrytí. Komentovaný kód (řádky 7-1343) ukazuje, že zde dříve existovala rozsáhlá testovací sada pokrývající validaci schémat, mutace, dotazy, subscriptions a další klíčovou funkcionalitu.

Vzhledem k tomu, že PR zahrnuje breaking changes a rozsáhlé refaktoringy (změny v API, typu systému, závislostí), absence testů představuje značné riziko pro stabilitu a správnost kódu.

Chcete, abych pomohl obnovit a aktualizovat testy tak, aby odpovídaly novému API? Mohu vygenerovat modernizované testy na základě komentovaného kódu a nových API změn zmíněných v PR.

🤖 Prompt for AI Agents
In packages/common/test/local-first/Evolu.test.ts around lines 3 to 4, the file
contains only a placeholder test and the original comprehensive test suite
(lines 7-1343) was removed, leaving no coverage for Evolu; replace the
placeholder with a restored modernized test suite that exercises schema
validation, mutations, queries, and subscriptions against the new API: recover
relevant scenarios from the commented tests, update calls and assertions to
match API/type changes in the PR, re-add necessary fixtures/mocks (e.g., test
DB, network, timers), group tests into clear describe blocks, and ensure they
run reliably in CI by using beforeAll/afterAll to init/teardown resources and by
mocking external dependencies.

Comment on lines +7 to +1343
// import { describe, expectTypeOf, test } from "vitest";
// import { createConsole } from "../../src/Console.js";
// import { constVoid } from "../../src/Function.js";
// import {
// createDbWorkerForPlatform,
// DbWorkerInput,
// DbWorkerOutput,
// } from "../../src/local-first/Db.js";
// import { createEvolu } from "../../src/local-first/Evolu.js";
// import {
// ValidateColumnTypes,
// ValidateIdColumnType,
// ValidateNoSystemColumns,
// ValidateSchemaHasId,
// } from "../../src/local-first/Schema.js";
// import { getOrThrow } from "../../src/Result.js";
// import { createSqlite, SqliteBoolean } from "../../src/Sqlite.js";
// import {
// Boolean,
// id,
// InferType,
// maxLength,
// NonEmptyString,
// nullOr,
// SimpleName,
// } from "../../src/Type.js";
// import {
// testCreateDummyWebSocket,
// testCreateSqliteDriver,
// testRandom,
// testRandomBytes,
// testSimpleName,
// testTime,
// } from "../_deps.js";

// const TodoId = id("Todo");
// type TodoId = InferType<typeof TodoId>;

// const TodoCategoryId = id("TodoCategory");
// type TodoCategoryId = InferType<typeof TodoCategoryId>;

// const NonEmptyString50 = maxLength(50)(NonEmptyString);
// type NonEmptyString50 = InferType<typeof NonEmptyString50>;

// const Schema = {
// todo: {
// id: TodoId,
// title: NonEmptyString50,
// isCompleted: nullOr(SqliteBoolean),
// categoryId: nullOr(TodoCategoryId),
// },
// todoCategory: {
// id: TodoCategoryId,
// name: NonEmptyString50,
// },
// };

// const testCreateEvolu = async (options?: {
// onInit?: (postMessageCalls: ReadonlyArray<DbWorkerInput>) => void;
// }) => {
// const { deps, postMessageCalls, instanceName, getOnMessageCallback } =
// await testCreateEvoluDeps();

// const evolu = createEvolu(deps)(Schema, {
// name: instanceName,
// });

// if (options?.onInit) options.onInit(postMessageCalls);
// postMessageCalls.length = 0;

// const allTodosQuery = evolu.createQuery((db) =>
// db.selectFrom("todo").selectAll(),
// );

// return {
// evolu,
// postMessageCalls,
// allTodosQuery,
// getOnMessageCallback,
// };
// };

// let testInstanceCounter = 0;

// const testCreateEvoluDeps = async () => {
// const instanceName = SimpleName.orThrow(`Test${testInstanceCounter++}`);
// // We eagerly create a SqliteDriver instance so we can use it for SQL tests.
// const sqliteDriver = await testCreateSqliteDriver(instanceName);
// const createSqliteDriver = () => Promise.resolve(sqliteDriver);

// const postMessageCalls: Array<DbWorkerInput> = [];
// let onMessageCallback: ((message: DbWorkerOutput) => void) | undefined;

// const innerDbWorker = createDbWorkerForPlatform({
// console: createConsole(),
// createSqliteDriver,
// createWebSocket: testCreateDummyWebSocket,
// random: testRandom,
// randomBytes: testRandomBytes,
// time: testTime,
// });

// const deps = {
// console: createConsole(),
// createDbWorker: () => ({
// onMessage: (callback: (message: DbWorkerOutput) => void) => {
// onMessageCallback = callback;
// innerDbWorker.onMessage(callback);
// },
// postMessage: (
// message: Parameters<typeof innerDbWorker.postMessage>[0],
// ) => {
// postMessageCalls.push(message);
// innerDbWorker.postMessage(message);
// },
// }),
// randomBytes: testRandomBytes,
// reloadApp: constVoid,
// time: testTime,
// };

// const sqlite = getOrThrow(
// await createSqlite({ createSqliteDriver })(instanceName),
// );

// return {
// instanceName,
// deps,
// postMessageCalls,
// sqlite,
// innerDbWorker,
// getOnMessageCallback: () => onMessageCallback,
// };
// };

// describe("createEvolu schema validation", () => {
// test("schema without id column", async () => {
// const { deps } = await testCreateEvoluDeps();

// const SchemaWithoutId = {
// todo: {
// // Missing id column - should cause TypeScript error
// title: NonEmptyString50,
// },
// };

// // Type-level assertion for the exact error message
// type ValidationResult = ValidateSchemaHasId<typeof SchemaWithoutId>;
// expectTypeOf<ValidationResult>().toEqualTypeOf<'❌ Schema Error: Table "todo" is missing required id column.'>();

// // @ts-expect-error - Schema validation should catch missing id column
// createEvolu(deps)(SchemaWithoutId, {
// name: testSimpleName,
// });
// });

// test("schema with system column createdAt", async () => {
// const { deps } = await testCreateEvoluDeps();

// const SchemaWithDefaultColumn = {
// todo: {
// id: TodoId,
// createdAt: NonEmptyString50,
// },
// };

// // Type-level assertion for the exact error message
// type ValidationResult = ValidateNoSystemColumns<
// typeof SchemaWithDefaultColumn
// >;
// expectTypeOf<ValidationResult>().toEqualTypeOf<'❌ Schema Error: Table "todo" uses system column name "createdAt". System columns (createdAt, updatedAt, isDeleted, ownerId) are added automatically.'>();

// // @ts-expect-error - Schema validation should catch system column name
// createEvolu(deps)(SchemaWithDefaultColumn, {
// name: testSimpleName,
// });
// });

// test("schema with system column updatedAt", async () => {
// const { deps } = await testCreateEvoluDeps();

// const SchemaWithDefaultColumn = {
// todo: {
// id: TodoId,
// updatedAt: NonEmptyString50,
// },
// };

// // Type-level assertion for the exact error message
// type ValidationResult = ValidateNoSystemColumns<
// typeof SchemaWithDefaultColumn
// >;
// expectTypeOf<ValidationResult>().toEqualTypeOf<'❌ Schema Error: Table "todo" uses system column name "updatedAt". System columns (createdAt, updatedAt, isDeleted, ownerId) are added automatically.'>();

// // @ts-expect-error - Schema validation should catch system column name
// createEvolu(deps)(SchemaWithDefaultColumn, {
// name: testSimpleName,
// });
// });

// test("schema with system column isDeleted", async () => {
// const { deps } = await testCreateEvoluDeps();

// const SchemaWithDefaultColumn = {
// todo: {
// id: TodoId,
// isDeleted: NonEmptyString50,
// },
// };

// // Type-level assertion for the exact error message
// type ValidationResult = ValidateNoSystemColumns<
// typeof SchemaWithDefaultColumn
// >;
// expectTypeOf<ValidationResult>().toEqualTypeOf<'❌ Schema Error: Table "todo" uses system column name "isDeleted". System columns (createdAt, updatedAt, isDeleted, ownerId) are added automatically.'>();

// // @ts-expect-error - Schema validation should catch system column name
// createEvolu(deps)(SchemaWithDefaultColumn, {
// name: testSimpleName,
// });
// });

// test("schema with system column ownerId", async () => {
// const { deps } = await testCreateEvoluDeps();

// const SchemaWithDefaultColumn = {
// todo: {
// id: TodoId,
// ownerId: NonEmptyString50,
// },
// };

// // Type-level assertion for the exact error message
// type ValidationResult = ValidateNoSystemColumns<
// typeof SchemaWithDefaultColumn
// >;
// expectTypeOf<ValidationResult>().toEqualTypeOf<'❌ Schema Error: Table "todo" uses system column name "ownerId". System columns (createdAt, updatedAt, isDeleted, ownerId) are added automatically.'>();

// // @ts-expect-error - Schema validation should catch system column name
// createEvolu(deps)(SchemaWithDefaultColumn, {
// name: testSimpleName,
// });
// });

// test("schema with non-branded id column", async () => {
// const { deps } = await testCreateEvoluDeps();

// const SchemaWithInvalidId = {
// todo: {
// id: NonEmptyString50,
// title: NonEmptyString50,
// },
// };

// // Type-level assertion for the exact error message
// type ValidationResult = ValidateIdColumnType<typeof SchemaWithInvalidId>;
// expectTypeOf<ValidationResult>().toEqualTypeOf<'❌ Schema Error: Table "todo" id column must be a branded ID type (created with id("todo")).'>();

// // @ts-expect-error - Schema validation should catch non-branded id column
// createEvolu(deps)(SchemaWithInvalidId, {
// name: testSimpleName,
// });
// });

// test("schema with incompatible column type", async () => {
// const { deps } = await testCreateEvoluDeps();

// const SchemaWithInvalidType = {
// todo: {
// id: TodoId,
// title: NonEmptyString50,
// invalidColumn: Boolean, // Boolean is not compatible with SQLite
// },
// };

// // Type-level assertion for the exact error message
// type ValidationResult = ValidateColumnTypes<typeof SchemaWithInvalidType>;
// expectTypeOf<ValidationResult>().toEqualTypeOf<'❌ Schema Error: Table "todo" column "invalidColumn" type is not compatible with SQLite. Column types must extend SqliteValue (string, number, Uint8Array, or null).'>();

// // @ts-expect-error - Schema validation should catch incompatible column type
// createEvolu(deps)(SchemaWithInvalidType, {
// name: testSimpleName,
// });
// });
// });

// describe("createQuery type inference", () => {
// test("Query.Row infers correct types for simple selectAll", async () => {
// const { evolu } = await testCreateEvolu();

// const _allTodosQuery = evolu.createQuery((db) =>
// db.selectFrom("todo").selectAll(),
// );

// type AllTodosRow = typeof _allTodosQuery.Row;

// // Verify the Row type has the correct shape including user-defined columns
// expectTypeOf<AllTodosRow>().toExtend<{
// readonly id: TodoId;
// readonly title: NonEmptyString50 | null;
// readonly isCompleted: SqliteBoolean | null;
// readonly categoryId: TodoCategoryId | null;
// }>();

// // Verify system columns are included
// expectTypeOf<AllTodosRow>().toHaveProperty("createdAt");
// expectTypeOf<AllTodosRow>().toHaveProperty("updatedAt");
// expectTypeOf<AllTodosRow>().toHaveProperty("isDeleted");
// expectTypeOf<AllTodosRow>().toHaveProperty("ownerId");
// });

// test("Query.Row infers correct types for select with specific columns", async () => {
// const { evolu } = await testCreateEvolu();

// const _todoTitlesQuery = evolu.createQuery((db) =>
// db.selectFrom("todo").select(["id", "title"]),
// );

// type TodoTitlesRow = typeof _todoTitlesQuery.Row;

// // Should only have selected columns
// expectTypeOf<TodoTitlesRow["id"]>().toEqualTypeOf<TodoId>();
// expectTypeOf<
// TodoTitlesRow["title"]
// >().toEqualTypeOf<NonEmptyString50 | null>();
// });

// test("Query.Row infers correct types for table with foreign key", async () => {
// const { evolu } = await testCreateEvolu();

// const _todosWithCategoryQuery = evolu.createQuery((db) =>
// db.selectFrom("todo").select(["id", "title", "categoryId"]),
// );

// type TodosWithCategoryRow = typeof _todosWithCategoryQuery.Row;

// expectTypeOf<TodosWithCategoryRow["id"]>().toEqualTypeOf<TodoId>();
// expectTypeOf<
// TodosWithCategoryRow["title"]
// >().toEqualTypeOf<NonEmptyString50 | null>();
// expectTypeOf<
// TodosWithCategoryRow["categoryId"]
// >().toEqualTypeOf<TodoCategoryId | null>();
// });

// test("Query.Row infers correct types for different table", async () => {
// const { evolu } = await testCreateEvolu();

// const _categoriesQuery = evolu.createQuery((db) =>
// db.selectFrom("todoCategory").select(["id", "name"]),
// );

// type CategoriesRow = typeof _categoriesQuery.Row;

// expectTypeOf<CategoriesRow["id"]>().toEqualTypeOf<TodoCategoryId>();
// expectTypeOf<
// CategoriesRow["name"]
// >().toEqualTypeOf<NonEmptyString50 | null>();
// });

// test("Query.Row infers correct types with $narrowType", async () => {
// const { evolu } = await testCreateEvolu();

// const _nonNullTitlesQuery = evolu.createQuery((db) =>
// db
// .selectFrom("todo")
// .select(["id", "title"])
// .where("title", "is not", null)
// .$narrowType<{ title: NonEmptyString50 }>(),
// );

// type NonNullTitlesRow = typeof _nonNullTitlesQuery.Row;

// // After $narrowType, title should not be nullable
// expectTypeOf<NonNullTitlesRow["id"]>().toEqualTypeOf<TodoId>();
// expectTypeOf<NonNullTitlesRow["title"]>().toEqualTypeOf<NonEmptyString50>();
// });
// });

// // test("init", async () => {
// // let postMessageCallsCalled = false;

// // await testCreateEvolu({
// // onInit: (postMessageCalls) => {
// // postMessageCallsCalled = true;
// // expect(postMessageCalls).toMatchInlineSnapshot(`
// // [
// // {
// // "config": {
// // "enableLogging": false,
// // "maxDrift": 300000,
// // "name": "Test7",
// // "transports": [
// // {
// // "type": "WebSocket",
// // "url": "wss://free.evoluhq.com",
// // },
// // ],
// // },
// // "dbSchema": {
// // "indexes": [],
// // "tables": {
// // "todo": Set {
// // "title",
// // "isCompleted",
// // "categoryId",
// // },
// // "todoCategory": Set {
// // "name",
// // },
// // },
// // },
// // "type": "init",
// // },
// // {
// // "type": "getAppOwner",
// // },
// // ]
// // `);
// // },
// // });

// // expect(postMessageCallsCalled).toBe(true);
// // });

// // test("externalAppOwner should use provided owner", async () => {
// // const { instanceName, deps, sqlite } = await testCreateEvoluDeps();

// // const externalAppOwner = createAppOwner(testOwnerSecret);

// // createEvolu(deps)(Schema, {
// // name: instanceName,
// // externalAppOwner,
// // });

// // await wait("10ms")();

// // const snapshot = getDbSnapshot({ sqlite });
// // expect(snapshot).toMatchSnapshot();

// // const configTable = snapshot.tables.find(
// // (table) => table.name === "evolu_config",
// // );
// // expect(configTable?.rows[0].appOwnerId).toBe(externalAppOwner.id);
// // });

// // describe("mutations", () => {
// // test("insert should validate and call postMessage", async () => {
// // const { evolu, postMessageCalls } = await testCreateEvolu();

// // const invalidTodo = {
// // title: "",
// // };

// // const invalidResult = evolu.insert("todo", invalidTodo);
// // expect(invalidResult).toMatchInlineSnapshot(`
// // {
// // "error": {
// // "reason": {
// // "errors": {
// // "title": {
// // "min": 1,
// // "type": "MinLength",
// // "value": "",
// // },
// // },
// // "kind": "Props",
// // },
// // "type": "Object",
// // "value": {
// // "title": "",
// // },
// // },
// // "ok": false,
// // }
// // `);

// // // Wait for microtask queue to process (invalid mutation won't be sent)
// // await Promise.resolve();

// // expect(postMessageCalls).toHaveLength(0);

// // const validTodo = {
// // title: "Test Todo",
// // };

// // const validResult = evolu.insert("todo", validTodo);

// // expect(validResult).toMatchInlineSnapshot(`
// // {
// // "ok": true,
// // "value": {
// // "id": "1XirdqSNyyoJfY1psc1W0Q",
// // },
// // }
// // `);

// // // Wait for microtask queue to process
// // await Promise.resolve();

// // expect(postMessageCalls).toMatchInlineSnapshot(`
// // [
// // {
// // "changes": [
// // {
// // "id": "1XirdqSNyyoJfY1psc1W0Q",
// // "isDelete": null,
// // "isInsert": true,
// // "ownerId": undefined,
// // "table": "todo",
// // "values": {
// // "title": "Test Todo",
// // },
// // },
// // ],
// // "onCompleteIds": [],
// // "subscribedQueries": [],
// // "tabId": "l7NvoJDLyCIlL8A1b4lblg",
// // "type": "mutate",
// // },
// // ]
// // `);
// // });

// // test("update should validate and call postMessage", async () => {
// // const { evolu, postMessageCalls } = await testCreateEvolu();

// // const testId = testCreateId();

// // const invalidUpdate = {
// // title: "Updated Todo",
// // };

// // // @ts-expect-error - Testing runtime validation
// // const invalidResult = evolu.update("todo", invalidUpdate);
// // expect(invalidResult.ok).toBe(false);

// // // Wait for microtask queue to process
// // await Promise.resolve();

// // expect(postMessageCalls).toHaveLength(0);

// // const validUpdate = {
// // id: testId,
// // title: "Updated Todo",
// // };

// // const validResult = evolu.update("todo", validUpdate);

// // expect(validResult).toMatchInlineSnapshot(`
// // {
// // "ok": true,
// // "value": {
// // "id": "clE52X3Xyxo0jShkCjrbjg",
// // },
// // }
// // `);

// // // Wait for microtask queue to process
// // await Promise.resolve();

// // expect(postMessageCalls).toMatchInlineSnapshot(`
// // [
// // {
// // "changes": [
// // {
// // "id": "clE52X3Xyxo0jShkCjrbjg",
// // "isDelete": null,
// // "isInsert": false,
// // "ownerId": undefined,
// // "table": "todo",
// // "values": {
// // "title": "Updated Todo",
// // },
// // },
// // ],
// // "onCompleteIds": [],
// // "subscribedQueries": [],
// // "tabId": "l7NvoJDLyCIlL8A1b4lblg",
// // "type": "mutate",
// // },
// // ]
// // `);
// // });

// // test("upsert should validate and call postMessage", async () => {
// // const { evolu, postMessageCalls } = await testCreateEvolu();

// // const testId = testCreateId();

// // const invalidUpsert = {
// // id: testId,
// // title: "",
// // };

// // const invalidResult = evolu.upsert("todo", invalidUpsert);
// // expect(invalidResult.ok).toBe(false);

// // // Wait for microtask queue to process
// // await Promise.resolve();

// // expect(postMessageCalls).toHaveLength(0);

// // const validUpsert = {
// // id: testId,
// // title: "Upserted Todo",
// // };

// // const validResult = evolu.upsert("todo", validUpsert);

// // expect(validResult).toMatchInlineSnapshot(`
// // {
// // "ok": true,
// // "value": {
// // "id": "_6EDjBwdU3ZCo-iXpJ29DQ",
// // },
// // }
// // `);

// // // Wait for microtask queue to process
// // await Promise.resolve();

// // expect(postMessageCalls).toMatchInlineSnapshot(`
// // [
// // {
// // "changes": [
// // {
// // "id": "_6EDjBwdU3ZCo-iXpJ29DQ",
// // "isDelete": null,
// // "isInsert": true,
// // "ownerId": undefined,
// // "table": "todo",
// // "values": {
// // "title": "Upserted Todo",
// // },
// // },
// // ],
// // "onCompleteIds": [],
// // "subscribedQueries": [],
// // "tabId": "l7NvoJDLyCIlL8A1b4lblg",
// // "type": "mutate",
// // },
// // ]
// // `);
// // });

// // test("mutations should be processed in microtask queue", async () => {
// // const { evolu, postMessageCalls } = await testCreateEvolu();

// // // Queue multiple mutations
// // evolu.insert("todo", { title: "Todo 1" });
// // evolu.insert("todo", { title: "Todo 2" });
// // evolu.insert("todo", { title: "Todo 3" });

// // // Wait for microtask queue to process
// // await Promise.resolve();

// // // Only one postMessage call should happen with all changes
// // expect(postMessageCalls).toHaveLength(1);
// // });

// // test("mutation with onlyValidate should not call postMessage", async () => {
// // const { evolu, postMessageCalls } = await testCreateEvolu();

// // evolu.insert("todo", { title: "Validation only" }, { onlyValidate: true });

// // // Wait for microtask queue to process
// // await Promise.resolve();

// // expect(postMessageCalls).toHaveLength(0);
// // });

const rows = evolu.getQueryRows(allTodosQuery);
// // test("mutations should fail as a transaction when any mutation fails", async () => {
// // const { evolu, postMessageCalls } = await testCreateEvolu();

expect(rows).toMatchInlineSnapshot(`[]`);
});
// // // Queue valid and invalid mutations
// // evolu.insert("todo", { title: "Valid Todo" });
// // evolu.insert("todo", { title: "" }); // Invalid - empty title
// // evolu.insert("todo", { title: "Another Valid Todo" });

test("getQueryRows should return data after loadQuery", async () => {
const { evolu, allTodosQuery } = await testCreateEvolu();
// // // Wait for microtask queue to process
// // await Promise.resolve();

evolu.insert("todo", { title: "Test Todo" });
await evolu.loadQuery(allTodosQuery);
// // expect(postMessageCalls).toHaveLength(0);
// // });
// // });

const rows = evolu.getQueryRows(allTodosQuery);
// // describe("queries", () => {
// // test("loadQuery should return initial empty result", async () => {
// // const { evolu, allTodosQuery } = await testCreateEvolu();

expect(rows).toHaveLength(1);
expect(rows[0]?.title).toBe("Test Todo");
});
// // const result = await evolu.loadQuery(allTodosQuery);

test("subscribeQuery should call listener when data changes", async () => {
const { evolu, allTodosQuery } = await testCreateEvolu();
// // expect(result).toMatchInlineSnapshot(`[]`);
// // });

let callCount = 0;
const unsubscribe = evolu.subscribeQuery(allTodosQuery)(() => {
callCount++;
});
// // test("loadQuery should cache promises for the same query", async () => {
// // const { evolu, allTodosQuery } = await testCreateEvolu();

// // const promise1 = evolu.loadQuery(allTodosQuery);
// // const promise2 = evolu.loadQuery(allTodosQuery);

// // // Same query should return the same promise instance
// // expect(promise1).toBe(promise2);

// // // Both should resolve to the same result
// // const [result1, result2] = await Promise.all([promise1, promise2]);
// // expect(result1).toBe(result2);
// // });

// // test("loadQuery should return inserted data", async () => {
// // const { evolu, allTodosQuery } = await testCreateEvolu();

// // const result = evolu.insert("todo", { title: "Test Todo" });
// // expect(result.ok).toBe(true);

// // const rows = await evolu.loadQuery(allTodosQuery);
// // expect(rows.length).toBe(1);
// // expect(rows[0]?.title).toBe("Test Todo");
// // });

// // test("loadQuery unsubscribed query should be released on mutation", async () => {
// // const { evolu, postMessageCalls, allTodosQuery } = await testCreateEvolu();

// // // Load query (creates promise in cache)
// // const promise1 = evolu.loadQuery(allTodosQuery);
// // await promise1;

// // // Clear to track only what happens after initial load
// // postMessageCalls.length = 0;

// Initial subscription should not call listener
expect(callCount).toBe(0);
// // // Mutate (should release unsubscribed queries from cache)
// // evolu.insert("todo", { title: "Test Todo" });

// Insert and load - should trigger listener
evolu.insert("todo", { title: "Test Todo" });
await evolu.loadQuery(allTodosQuery);
// // // Wait for microtask queue to process
// // await Promise.resolve();

expect(callCount).toBe(1);
// // // Should have 1 mutate call
// // expect(postMessageCalls).toHaveLength(1);
// // expect(postMessageCalls[0]?.type).toBe("mutate");

unsubscribe();
});
// // // Load again - cache was released, so this sends a NEW query to worker
// // const promise2 = evolu.loadQuery(allTodosQuery);

test("subscribeQuery should not call listener if result unchanged", async () => {
const { evolu, allTodosQuery } = await testCreateEvolu();
// // // Wait for microtask queue to process
// // await Promise.resolve();

let callCount = 0;
const unsubscribe = evolu.subscribeQuery(allTodosQuery)(() => {
callCount++;
});
// // // Now should have 2 calls: mutate + new query
// // expect(postMessageCalls).toHaveLength(2);
// // expect(postMessageCalls[1]?.type).toBe("query");

// Load initial data
await evolu.loadQuery(allTodosQuery);
// // // Promise is different because cache was released
// // expect(promise1).not.toBe(promise2);
// // });

expect(callCount).toBe(1);
// // test("loadQuery subscribed query should not be released on mutation", async () => {
// // const { evolu, postMessageCalls, allTodosQuery } = await testCreateEvolu();

// // const promise1 = evolu.loadQuery(allTodosQuery);
// // await promise1;

// // evolu.subscribeQuery(allTodosQuery)(constVoid);

// // // Clear previous calls to track only what happens after subscription
// // postMessageCalls.length = 0;

// // // Mutate (should NOT release subscribed queries from cache)
// // evolu.insert("todo", { title: "Test Todo" });

// // // Wait for microtask queue to process
// // await Promise.resolve();

// // // Should have 1 mutate call
// // expect(postMessageCalls).toHaveLength(1);
// // expect(postMessageCalls[0]?.type).toBe("mutate");

// // // Load again - cache entry stays, so NO new query postMessage
// // const promise2 = evolu.loadQuery(allTodosQuery);

// // // Wait for microtask queue to process
// // await Promise.resolve();

// // // Still only 1 call (the mutation) - no new query was sent to worker
// // expect(postMessageCalls).toHaveLength(1);

// // // Check the value property that React's use() reads (not await result)
// // expect(promise1).toMatchInlineSnapshot(`
// // Promise {
// // "status": "fulfilled",
// // "value": [],
// // }
// // `);
// // expect(promise2).toMatchInlineSnapshot(`
// // Promise {
// // "status": "fulfilled",
// // "value": [
// // {
// // "categoryId": null,
// // "createdAt": "1970-01-01T00:00:00.008Z",
// // "id": "EXqDJoTfofrVXy_-hTIKow",
// // "isCompleted": null,
// // "isDeleted": null,
// // "ownerId": "O-CuBGc9kBPdNNkVCKM1uA",
// // "title": "Test Todo",
// // "updatedAt": null,
// // },
// // ],
// // }
// // `);
// // });

// // test("loadQuery pending unsubscribed query should be released after resolve", async () => {
// // const { evolu, postMessageCalls, allTodosQuery } = await testCreateEvolu();

// // // Load query - creates pending promise in cache
// // const promise1 = evolu.loadQuery(allTodosQuery);

// // // Mutate BEFORE promise1 resolves. releaseUnsubscribedOnMutation() runs
// // // but can't delete the pending promise (would break promise resolution).
// // evolu.insert("todo", { title: "Test Todo" });

// // // Wait for query to resolve - when resolve() runs, it checks releaseOnResolve
// // // flag and deletes the cache entry after fulfilling the promise
// // await promise1;

// // postMessageCalls.length = 0;

// // // Load again - cache entry was deleted, so this sends a NEW query
// // const promise2 = evolu.loadQuery(allTodosQuery);

// // // Wait for microtask queue to process
// // await Promise.resolve();

// // // Verify new query was sent to worker (cache was released)
// // expect(postMessageCalls).toHaveLength(1);
// // expect(postMessageCalls[0]?.type).toBe("query");

// // expect(promise1).not.toBe(promise2);

// // // Check the value property that React's use() reads (not await result)
// // expect(promise1).toMatchInlineSnapshot(`
// // Promise {
// // "status": "fulfilled",
// // "value": [],
// // }
// // `);
// // expect(promise2).toMatchInlineSnapshot(`
// // Promise {
// // "status": "fulfilled",
// // "value": [
// // {
// // "categoryId": null,
// // "createdAt": "1970-01-01T00:00:00.009Z",
// // "id": "V9jl1rlzsDtroJAB4SK5Bg",
// // "isCompleted": null,
// // "isDeleted": null,
// // "ownerId": "eE5PP1qED8YN2k3_gFg8Zw",
// // "title": "Test Todo",
// // "updatedAt": null,
// // },
// // ],
// // }
// // `);
// // });
// // });

// // describe("subscribeQuery and getQueryRows", () => {
// // test("getQueryRows should return empty rows initially", async () => {
// // const { evolu, allTodosQuery } = await testCreateEvolu();

// // const rows = evolu.getQueryRows(allTodosQuery);

// // expect(rows).toMatchInlineSnapshot(`[]`);
// // });

// // test("getQueryRows should return data after loadQuery", async () => {
// // const { evolu, allTodosQuery } = await testCreateEvolu();

// // evolu.insert("todo", { title: "Test Todo" });
// // await evolu.loadQuery(allTodosQuery);

// // const rows = evolu.getQueryRows(allTodosQuery);

// // expect(rows).toHaveLength(1);
// // expect(rows[0]?.title).toBe("Test Todo");
// // });

// // test("subscribeQuery should call listener when data changes", async () => {
// // const { evolu, allTodosQuery } = await testCreateEvolu();

// // let callCount = 0;
// // const unsubscribe = evolu.subscribeQuery(allTodosQuery)(() => {
// // callCount++;
// // });

// // // Initial subscription should not call listener
// // expect(callCount).toBe(0);

// // // Insert and load - should trigger listener
// // evolu.insert("todo", { title: "Test Todo" });
// // await evolu.loadQuery(allTodosQuery);

// // expect(callCount).toBe(1);

// // unsubscribe();
// // });

// // test("subscribeQuery should not call listener if result unchanged", async () => {
// // const { evolu, allTodosQuery } = await testCreateEvolu();

// // let callCount = 0;
// // const unsubscribe = evolu.subscribeQuery(allTodosQuery)(() => {
// // callCount++;
// // });

// // // Load initial data
// // await evolu.loadQuery(allTodosQuery);

// // expect(callCount).toBe(1);

// // // Load again - same result, should not call listener
// // await evolu.loadQuery(allTodosQuery);

// // expect(callCount).toBe(1);

// // unsubscribe();
// // });

// // test("subscribeQuery listener should see updated data via getQueryRows", async () => {
// // const { evolu, allTodosQuery } = await testCreateEvolu();

// // const results: Array<number> = [];
// // const unsubscribe = evolu.subscribeQuery(allTodosQuery)(() => {
// // const rows = evolu.getQueryRows(allTodosQuery);
// // results.push(rows.length);
// // });

// Load again - same result, should not call listener
await evolu.loadQuery(allTodosQuery);
// // // Insert first todo
// // evolu.insert("todo", { title: "First Todo" });
// // await evolu.loadQuery(allTodosQuery);

// // // Insert second todo
// // evolu.insert("todo", { title: "Second Todo" });
// // await evolu.loadQuery(allTodosQuery);

expect(callCount).toBe(1);
// // expect(results).toEqual([1, 2]);

unsubscribe();
});
// // unsubscribe();
// // });

test("subscribeQuery listener should see updated data via getQueryRows", async () => {
const { evolu, allTodosQuery } = await testCreateEvolu();
// // test("unsubscribe should stop calling listener", async () => {
// // const { evolu, allTodosQuery } = await testCreateEvolu();

const results: Array<number> = [];
const unsubscribe = evolu.subscribeQuery(allTodosQuery)(() => {
const rows = evolu.getQueryRows(allTodosQuery);
results.push(rows.length);
});
// // let callCount = 0;
// // const unsubscribe = evolu.subscribeQuery(allTodosQuery)(() => {
// // callCount++;
// // });

// Insert first todo
evolu.insert("todo", { title: "First Todo" });
await evolu.loadQuery(allTodosQuery);
// // // First mutation - listener should be called
// // evolu.insert("todo", { title: "First Todo" });
// // await evolu.loadQuery(allTodosQuery);

// Insert second todo
evolu.insert("todo", { title: "Second Todo" });
await evolu.loadQuery(allTodosQuery);
// // expect(callCount).toBe(1);

expect(results).toEqual([1, 2]);
// // unsubscribe();

unsubscribe();
});
// // // Second mutation - listener should NOT be called
// // evolu.insert("todo", { title: "Second Todo" });
// // await evolu.loadQuery(allTodosQuery);

test("unsubscribe should stop calling listener", async () => {
const { evolu, allTodosQuery } = await testCreateEvolu();
// // expect(callCount).toBe(1);
// // });
// // });

let callCount = 0;
const unsubscribe = evolu.subscribeQuery(allTodosQuery)(() => {
callCount++;
});
// // describe("refreshQueries", () => {
// // /**
// // * This is not an ideal test; we should run Evolu in a browser with React
// // * useQuery to detect a condition when a component is suspended via loadQuery,
// // * so useQuerySubscription is not yet called, but refreshQueries is, so
// // * subscribedQueries is empty, but loadingPromisesQueries is not. The problem
// // * is that the React component is rendered with stale data which are not
// // * updated. Using loadingPromisesQueries in refreshQueries fixes that.
// // *
// // * Manual test: Open EvoluMinimalExample, close browser dev tools (yes), and
// // * restore account. Without using loadingPromisesQueries in refreshQueries,
// // * React will render stale data, but when we click into the input and write
// // * something, the UI is immediately updated with actual data. It's happening
// // * in all browsers, and NOT happening when dev tools are open. This race
// // * condition is hard to simulate in Node.js, probably because we don't have an
// // * async DB worker.
// // */
// // test("refreshQueries includes pending loadQuery queries", async () => {
// // const { evolu, postMessageCalls, allTodosQuery, getOnMessageCallback } =
// // await testCreateEvolu();

// // // Start a loadQuery - this creates a pending promise but DON'T await it yet
// // void evolu.loadQuery(allTodosQuery);

// // // Wait for the microtask to execute so the query is sent
// // await Promise.resolve();

// // // Verify initial query was sent
// // expect(postMessageCalls).toHaveLength(1);
// // expect(postMessageCalls[0]).toMatchObject({
// // type: "query",
// // queries: [allTodosQuery],
// // });

// // postMessageCalls.length = 0;

// // const handler = getOnMessageCallback();
// // assert(handler, "getOnMessageCallback");

// // // Directly call Evolu's message handler with refreshQueries.
// // // This simulates what happens when sync data arrives.
// // handler({ type: "refreshQueries" });

// // await Promise.resolve();

// // const queryMessages = postMessageCalls.filter(
// // (call) => call.type === "query",
// // );

// // expect(queryMessages.length).toBe(1);
// // expect(queryMessages[0]?.queries).toContain(allTodosQuery);
// // });

// // test("refreshQueries includes subscribed queries", async () => {
// // const { evolu, postMessageCalls, allTodosQuery, getOnMessageCallback } =
// // await testCreateEvolu();

// // const unsubscribe = evolu.subscribeQuery(allTodosQuery)(constVoid);

// // await Promise.resolve();

// // postMessageCalls.length = 0;

// // const handler = getOnMessageCallback();
// // assert(handler, "getOnMessageCallback");

// // // Directly call Evolu's message handler with refreshQueries.
// // // This simulates what happens when sync data arrives.
// // handler({ type: "refreshQueries" });

// // await Promise.resolve();

// // const queryMessages = postMessageCalls.filter(
// // (call) => call.type === "query",
// // );

// // expect(queryMessages.length).toBe(1);
// // expect(queryMessages[0]?.queries).toContain(allTodosQuery);

// // unsubscribe();
// // });
// // });

// // describe("createdAt behavior", () => {
// // test("insert should set createdAt to current time", async () => {
// // const { evolu, postMessageCalls } = await testCreateEvolu();

// // const result = evolu.insert("todo", { title: "Test Todo" });
// // expect(result).toMatchInlineSnapshot(`
// // {
// // "ok": true,
// // "value": {
// // "id": "p-twDTGK4YVi7ZZmiCi9TA",
// // },
// // }
// // `);

// // await Promise.resolve();

// // expect(postMessageCalls).toMatchInlineSnapshot(`
// // [
// // {
// // "changes": [
// // {
// // "id": "p-twDTGK4YVi7ZZmiCi9TA",
// // "isDelete": null,
// // "isInsert": true,
// // "ownerId": undefined,
// // "table": "todo",
// // "values": {
// // "title": "Test Todo",
// // },
// // },
// // ],
// // "onCompleteIds": [],
// // "subscribedQueries": [],
// // "tabId": "l7NvoJDLyCIlL8A1b4lblg",
// // "type": "mutate",
// // },
// // ]
// // `);
// // });

// // test("upsert should set createdAt to current time", async () => {
// // const { evolu, postMessageCalls } = await testCreateEvolu();

// // const testId = testCreateId();
// // const result = evolu.upsert("todo", { id: testId, title: "Upserted Todo" });
// // expect(result).toMatchInlineSnapshot(`
// // {
// // "ok": true,
// // "value": {
// // "id": "aVm9lRgGoF6038X2MlJ2Cw",
// // },
// // }
// // `);

// // await Promise.resolve();

// // expect(postMessageCalls).toMatchInlineSnapshot(`
// // [
// // {
// // "changes": [
// // {
// // "id": "aVm9lRgGoF6038X2MlJ2Cw",
// // "isDelete": null,
// // "isInsert": true,
// // "ownerId": undefined,
// // "table": "todo",
// // "values": {
// // "title": "Upserted Todo",
// // },
// // },
// // ],
// // "onCompleteIds": [],
// // "subscribedQueries": [],
// // "tabId": "l7NvoJDLyCIlL8A1b4lblg",
// // "type": "mutate",
// // },
// // ]
// // `);
// // });

// // test("update should NOT set createdAt", async () => {
// // const { evolu, postMessageCalls } = await testCreateEvolu();

// // const testId = testCreateId();
// // const result = evolu.update("todo", { id: testId, title: "Updated Todo" });
// // expect(result).toMatchInlineSnapshot(`
// // {
// // "ok": true,
// // "value": {
// // "id": "R8qs_iP8FEwYBfwzQ7o_Og",
// // },
// // }
// // `);

// // await Promise.resolve();

// // expect(postMessageCalls).toMatchInlineSnapshot(`
// // [
// // {
// // "changes": [
// // {
// // "id": "R8qs_iP8FEwYBfwzQ7o_Og",
// // "isDelete": null,
// // "isInsert": false,
// // "ownerId": undefined,
// // "table": "todo",
// // "values": {
// // "title": "Updated Todo",
// // },
// // },
// // ],
// // "onCompleteIds": [],
// // "subscribedQueries": [],
// // "tabId": "l7NvoJDLyCIlL8A1b4lblg",
// // "type": "mutate",
// // },
// // ]
// // `);
// // });
// // });

// // describe("useOwner", () => {
// // const ownerMessage = (owner: SyncOwner, use: boolean) => ({
// // type: "useOwner",
// // owner,
// // use,
// // });

// // test("single useOwner call", async () => {
// // const { evolu, postMessageCalls } = await testCreateEvolu();

// // evolu.useOwner(testOwner);

// // await Promise.resolve();

// // expect(postMessageCalls).toHaveLength(1);
// // expect(postMessageCalls[0]).toEqual(ownerMessage(testOwner, true));
// // });

// // test("multiple useOwner calls for same owner preserves count", async () => {
// // const { evolu, postMessageCalls } = await testCreateEvolu();

// // // Each call should result in a separate postMessage for reference counting
// // evolu.useOwner(testOwner);
// // evolu.useOwner(testOwner);
// // evolu.useOwner(testOwner);

// // await Promise.resolve();

// // expect(postMessageCalls).toHaveLength(3);
// // for (let i = 0; i < 3; i++) {
// // expect(postMessageCalls[i]).toEqual(ownerMessage(testOwner, true));
// // }
// // });

// // test("exact use/unuse pair cancels out", async () => {
// // const { evolu, postMessageCalls } = await testCreateEvolu();

// // // Add testOwner, then remove it - should cancel out
// // const unuse1 = evolu.useOwner(testOwner);
// // unuse1();

// // queueMicrotask(() => {
// // expect(postMessageCalls).toHaveLength(0);
// // });

// // await Promise.resolve();
// // });

// // test("multiple exact pairs cancel out", async () => {
// // const { evolu, postMessageCalls } = await testCreateEvolu();

// // // Two separate use/unuse pairs - both should cancel out
// // const unuse1 = evolu.useOwner(testOwner);
// // const unuse2 = evolu.useOwner(testOwner);
// // unuse1();
// // unuse2();

// // queueMicrotask(() => {
// // expect(postMessageCalls).toHaveLength(0);
// // });

// // await Promise.resolve();
// // });

// // test("partial pairs leave remainder", async () => {
// // const { evolu, postMessageCalls } = await testCreateEvolu();

// // // Three uses, one unuse - should result in two remaining uses
// // evolu.useOwner(testOwner);
// // evolu.useOwner(testOwner);
// // const unuse3 = evolu.useOwner(testOwner);
// // unuse3();

// First mutation - listener should be called
evolu.insert("todo", { title: "First Todo" });
await evolu.loadQuery(allTodosQuery);
// // await Promise.resolve();

// // expect(postMessageCalls).toHaveLength(2);
// // for (let i = 0; i < 2; i++) {
// // expect(postMessageCalls[i]).toEqual(ownerMessage(testOwner, true));
// // }
// // });

expect(callCount).toBe(1);
// // test("different owners don't interfere", async () => {
// // const { evolu, postMessageCalls } = await testCreateEvolu();

unsubscribe();
// // // Different owners should not cancel each other
// // evolu.useOwner(testOwner);
// // const unuse2 = evolu.useOwner(testOwner2);
// // unuse2();

// Second mutation - listener should NOT be called
evolu.insert("todo", { title: "Second Todo" });
await evolu.loadQuery(allTodosQuery);
// // await Promise.resolve();

expect(callCount).toBe(1);
});
});
// // expect(postMessageCalls).toHaveLength(1);
// // expect(postMessageCalls[0]).toEqual(ownerMessage(testOwner, true));
// // });

describe("refreshQueries", () => {
/**
* This is not an ideal test; we should run Evolu in a browser with React
* useQuery to detect a condition when a component is suspended via loadQuery,
* so useQuerySubscription is not yet called, but refreshQueries is, so
* subscribedQueries is empty, but loadingPromisesQueries is not. The problem
* is that the React component is rendered with stale data which are not
* updated. Using loadingPromisesQueries in refreshQueries fixes that.
*
* Manual test: Open EvoluMinimalExample, close browser dev tools (yes), and
* restore account. Without using loadingPromisesQueries in refreshQueries,
* React will render stale data, but when we click into the input and write
* something, the UI is immediately updated with actual data. It's happening
* in all browsers, and NOT happening when dev tools are open. This race
* condition is hard to simulate in Node.js, probably because we don't have an
* async DB worker.
*/
test("refreshQueries includes pending loadQuery queries", async () => {
const { evolu, postMessageCalls, allTodosQuery, getOnMessageCallback } =
await testCreateEvolu();
// // test("order preservation with mixed operations", async () => {
// // const { evolu, postMessageCalls } = await testCreateEvolu();

// Start a loadQuery - this creates a pending promise but DON'T await it yet
void evolu.loadQuery(allTodosQuery);
// // // Mixed operations: use, use, unuse, use
// // // Should cancel one pair and leave: use, use
// // evolu.useOwner(testOwner); // use #1
// // const unuse2 = evolu.useOwner(testOwner); // use #2
// // unuse2(); // unuse (cancels with use #2)
// // evolu.useOwner(testOwner); // use #3

// Wait for the microtask to execute so the query is sent
await Promise.resolve();
// // await Promise.resolve();

// Verify initial query was sent
expect(postMessageCalls).toHaveLength(1);
expect(postMessageCalls[0]).toMatchObject({
type: "query",
queries: [allTodosQuery],
});
// // expect(postMessageCalls).toHaveLength(2);
// // for (let i = 0; i < 2; i++) {
// // expect(postMessageCalls[i]).toEqual(ownerMessage(testOwner, true));
// // }
// // });

postMessageCalls.length = 0;
// // test("remove before add - processed owner requires explicit remove", async () => {
// // const { evolu, postMessageCalls } = await testCreateEvolu();

const handler = getOnMessageCallback();
assert(handler, "getOnMessageCallback");
// // // Add owner and wait for it to be processed
// // const unuse1 = evolu.useOwner(testOwner);

// Directly call Evolu's message handler with refreshQueries.
// This simulates what happens when sync data arrives.
handler({ type: "refreshQueries" });
// // await Promise.resolve();

await Promise.resolve();
// // // Verify it was added
// // expect(postMessageCalls).toHaveLength(1);
// // expect(postMessageCalls[0]).toEqual(ownerMessage(testOwner, true));

const queryMessages = postMessageCalls.filter(
(call) => call.type === "query",
);
// // postMessageCalls.length = 0; // Clear previous calls

expect(queryMessages.length).toBe(1);
expect(queryMessages[0]?.queries).toContain(allTodosQuery);
});
// // // Now remove and immediately add again
// // unuse1(); // Remove
// // evolu.useOwner(testOwner); // Add again

test("refreshQueries includes subscribed queries", async () => {
const { evolu, postMessageCalls, allTodosQuery, getOnMessageCallback } =
await testCreateEvolu();
// // await Promise.resolve();

const unsubscribe = evolu.subscribeQuery(allTodosQuery)(constVoid);
// // // Should result in no calls since remove/add cancel out
// // expect(postMessageCalls).toHaveLength(0);
// // });

await Promise.resolve();

postMessageCalls.length = 0;

const handler = getOnMessageCallback();
assert(handler, "getOnMessageCallback");

// Directly call Evolu's message handler with refreshQueries.
// This simulates what happens when sync data arrives.
handler({ type: "refreshQueries" });

await Promise.resolve();

const queryMessages = postMessageCalls.filter(
(call) => call.type === "query",
);

expect(queryMessages.length).toBe(1);
expect(queryMessages[0]?.queries).toContain(allTodosQuery);

unsubscribe();
});
});

describe("createdAt behavior", () => {
test("insert should set createdAt to current time", async () => {
const { evolu, postMessageCalls } = await testCreateEvolu();

const result = evolu.insert("todo", { title: "Test Todo" });
expect(result).toMatchInlineSnapshot(`
{
"ok": true,
"value": {
"id": "p-twDTGK4YVi7ZZmiCi9TA",
},
}
`);

await Promise.resolve();

expect(postMessageCalls).toMatchInlineSnapshot(`
[
{
"changes": [
{
"id": "p-twDTGK4YVi7ZZmiCi9TA",
"isDelete": null,
"isInsert": true,
"ownerId": undefined,
"table": "todo",
"values": {
"title": "Test Todo",
},
},
],
"onCompleteIds": [],
"subscribedQueries": [],
"tabId": "l7NvoJDLyCIlL8A1b4lblg",
"type": "mutate",
},
]
`);
});

test("upsert should set createdAt to current time", async () => {
const { evolu, postMessageCalls } = await testCreateEvolu();

const testId = testCreateId();
const result = evolu.upsert("todo", { id: testId, title: "Upserted Todo" });
expect(result).toMatchInlineSnapshot(`
{
"ok": true,
"value": {
"id": "aVm9lRgGoF6038X2MlJ2Cw",
},
}
`);

await Promise.resolve();

expect(postMessageCalls).toMatchInlineSnapshot(`
[
{
"changes": [
{
"id": "aVm9lRgGoF6038X2MlJ2Cw",
"isDelete": null,
"isInsert": true,
"ownerId": undefined,
"table": "todo",
"values": {
"title": "Upserted Todo",
},
},
],
"onCompleteIds": [],
"subscribedQueries": [],
"tabId": "l7NvoJDLyCIlL8A1b4lblg",
"type": "mutate",
},
]
`);
});

test("update should NOT set createdAt", async () => {
const { evolu, postMessageCalls } = await testCreateEvolu();

const testId = testCreateId();
const result = evolu.update("todo", { id: testId, title: "Updated Todo" });
expect(result).toMatchInlineSnapshot(`
{
"ok": true,
"value": {
"id": "R8qs_iP8FEwYBfwzQ7o_Og",
},
}
`);

await Promise.resolve();

expect(postMessageCalls).toMatchInlineSnapshot(`
[
{
"changes": [
{
"id": "R8qs_iP8FEwYBfwzQ7o_Og",
"isDelete": null,
"isInsert": false,
"ownerId": undefined,
"table": "todo",
"values": {
"title": "Updated Todo",
},
},
],
"onCompleteIds": [],
"subscribedQueries": [],
"tabId": "l7NvoJDLyCIlL8A1b4lblg",
"type": "mutate",
},
]
`);
});
});
// // test("delayed unuse call is processed", async () => {
// // const { evolu, postMessageCalls } = await testCreateEvolu();

describe("useOwner", () => {
const ownerMessage = (owner: SyncOwner, use: boolean) => ({
type: "useOwner",
owner,
use,
});
// // const unuse = evolu.useOwner(testOwner);

test("single useOwner call", async () => {
const { evolu, postMessageCalls } = await testCreateEvolu();
// // await Promise.resolve();
// // expect(postMessageCalls).toHaveLength(1);
// // expect(postMessageCalls[0]).toEqual(ownerMessage(testOwner, true));

evolu.useOwner(testOwner);
// // postMessageCalls.length = 0; // Clear previous calls

await Promise.resolve();
// // // Delayed unuse without any subsequent useOwner calls
// // setTimeout(() => {
// // unuse();
// // }, 10);

expect(postMessageCalls).toHaveLength(1);
expect(postMessageCalls[0]).toEqual(ownerMessage(testOwner, true));
});
// // await wait("20ms")();

test("multiple useOwner calls for same owner preserves count", async () => {
const { evolu, postMessageCalls } = await testCreateEvolu();

// Each call should result in a separate postMessage for reference counting
evolu.useOwner(testOwner);
evolu.useOwner(testOwner);
evolu.useOwner(testOwner);

await Promise.resolve();

expect(postMessageCalls).toHaveLength(3);
for (let i = 0; i < 3; i++) {
expect(postMessageCalls[i]).toEqual(ownerMessage(testOwner, true));
}
});

test("exact use/unuse pair cancels out", async () => {
const { evolu, postMessageCalls } = await testCreateEvolu();

// Add testOwner, then remove it - should cancel out
const unuse1 = evolu.useOwner(testOwner);
unuse1();

queueMicrotask(() => {
expect(postMessageCalls).toHaveLength(0);
});

await Promise.resolve();
});

test("multiple exact pairs cancel out", async () => {
const { evolu, postMessageCalls } = await testCreateEvolu();

// Two separate use/unuse pairs - both should cancel out
const unuse1 = evolu.useOwner(testOwner);
const unuse2 = evolu.useOwner(testOwner);
unuse1();
unuse2();

queueMicrotask(() => {
expect(postMessageCalls).toHaveLength(0);
});

await Promise.resolve();
});

test("partial pairs leave remainder", async () => {
const { evolu, postMessageCalls } = await testCreateEvolu();

// Three uses, one unuse - should result in two remaining uses
evolu.useOwner(testOwner);
evolu.useOwner(testOwner);
const unuse3 = evolu.useOwner(testOwner);
unuse3();

await Promise.resolve();

expect(postMessageCalls).toHaveLength(2);
for (let i = 0; i < 2; i++) {
expect(postMessageCalls[i]).toEqual(ownerMessage(testOwner, true));
}
});

test("different owners don't interfere", async () => {
const { evolu, postMessageCalls } = await testCreateEvolu();

// Different owners should not cancel each other
evolu.useOwner(testOwner);
const unuse2 = evolu.useOwner(testOwner2);
unuse2();

await Promise.resolve();

expect(postMessageCalls).toHaveLength(1);
expect(postMessageCalls[0]).toEqual(ownerMessage(testOwner, true));
});

test("order preservation with mixed operations", async () => {
const { evolu, postMessageCalls } = await testCreateEvolu();

// Mixed operations: use, use, unuse, use
// Should cancel one pair and leave: use, use
evolu.useOwner(testOwner); // use #1
const unuse2 = evolu.useOwner(testOwner); // use #2
unuse2(); // unuse (cancels with use #2)
evolu.useOwner(testOwner); // use #3

await Promise.resolve();

expect(postMessageCalls).toHaveLength(2);
for (let i = 0; i < 2; i++) {
expect(postMessageCalls[i]).toEqual(ownerMessage(testOwner, true));
}
});

test("remove before add - processed owner requires explicit remove", async () => {
const { evolu, postMessageCalls } = await testCreateEvolu();

// Add owner and wait for it to be processed
const unuse1 = evolu.useOwner(testOwner);

await Promise.resolve();

// Verify it was added
expect(postMessageCalls).toHaveLength(1);
expect(postMessageCalls[0]).toEqual(ownerMessage(testOwner, true));

postMessageCalls.length = 0; // Clear previous calls

// Now remove and immediately add again
unuse1(); // Remove
evolu.useOwner(testOwner); // Add again

await Promise.resolve();

// Should result in no calls since remove/add cancel out
expect(postMessageCalls).toHaveLength(0);
});

test("delayed unuse call is processed", async () => {
const { evolu, postMessageCalls } = await testCreateEvolu();

const unuse = evolu.useOwner(testOwner);

await Promise.resolve();
expect(postMessageCalls).toHaveLength(1);
expect(postMessageCalls[0]).toEqual(ownerMessage(testOwner, true));

postMessageCalls.length = 0; // Clear previous calls

// Delayed unuse without any subsequent useOwner calls
setTimeout(() => {
unuse();
}, 10);

await wait("20ms")();

expect(postMessageCalls).toHaveLength(1);
expect(postMessageCalls[0]).toEqual(ownerMessage(testOwner, false));
});
});

describe("createQuery type inference", () => {
test("Query.Row infers correct types for simple selectAll", async () => {
const { evolu } = await testCreateEvolu();

const _allTodosQuery = evolu.createQuery((db) =>
db.selectFrom("todo").selectAll(),
);

type AllTodosRow = typeof _allTodosQuery.Row;

// Verify the Row type has the correct shape including user-defined columns
expectTypeOf<AllTodosRow>().toExtend<{
readonly id: TodoId;
readonly title: NonEmptyString50 | null;
readonly isCompleted: SqliteBoolean | null;
readonly categoryId: TodoCategoryId | null;
}>();

// Verify system columns are included
expectTypeOf<AllTodosRow>().toHaveProperty("createdAt");
expectTypeOf<AllTodosRow>().toHaveProperty("updatedAt");
expectTypeOf<AllTodosRow>().toHaveProperty("isDeleted");
expectTypeOf<AllTodosRow>().toHaveProperty("ownerId");
});

test("Query.Row infers correct types for select with specific columns", async () => {
const { evolu } = await testCreateEvolu();

const _todoTitlesQuery = evolu.createQuery((db) =>
db.selectFrom("todo").select(["id", "title"]),
);

type TodoTitlesRow = typeof _todoTitlesQuery.Row;

// Should only have selected columns
expectTypeOf<TodoTitlesRow["id"]>().toEqualTypeOf<TodoId>();
expectTypeOf<
TodoTitlesRow["title"]
>().toEqualTypeOf<NonEmptyString50 | null>();
});

test("Query.Row infers correct types for table with foreign key", async () => {
const { evolu } = await testCreateEvolu();

const _todosWithCategoryQuery = evolu.createQuery((db) =>
db.selectFrom("todo").select(["id", "title", "categoryId"]),
);

type TodosWithCategoryRow = typeof _todosWithCategoryQuery.Row;

expectTypeOf<TodosWithCategoryRow["id"]>().toEqualTypeOf<TodoId>();
expectTypeOf<
TodosWithCategoryRow["title"]
>().toEqualTypeOf<NonEmptyString50 | null>();
expectTypeOf<
TodosWithCategoryRow["categoryId"]
>().toEqualTypeOf<TodoCategoryId | null>();
});

test("Query.Row infers correct types for different table", async () => {
const { evolu } = await testCreateEvolu();

const _categoriesQuery = evolu.createQuery((db) =>
db.selectFrom("todoCategory").select(["id", "name"]),
);

type CategoriesRow = typeof _categoriesQuery.Row;

expectTypeOf<CategoriesRow["id"]>().toEqualTypeOf<TodoCategoryId>();
expectTypeOf<
CategoriesRow["name"]
>().toEqualTypeOf<NonEmptyString50 | null>();
});

test("Query.Row infers correct types with $narrowType", async () => {
const { evolu } = await testCreateEvolu();

const _nonNullTitlesQuery = evolu.createQuery((db) =>
db
.selectFrom("todo")
.select(["id", "title"])
.where("title", "is not", null)
.$narrowType<{ title: NonEmptyString50 }>(),
);

type NonNullTitlesRow = typeof _nonNullTitlesQuery.Row;

// After $narrowType, title should not be nullable
expectTypeOf<NonNullTitlesRow["id"]>().toEqualTypeOf<TodoId>();
expectTypeOf<NonNullTitlesRow["title"]>().toEqualTypeOf<NonEmptyString50>();
});
});
// // expect(postMessageCalls).toHaveLength(1);
// // expect(postMessageCalls[0]).toEqual(ownerMessage(testOwner, false));
// // });
// // });
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Zvažte zpracování komentovaného kódu.

Přes 1300 řádků komentovaného testovacího kódu by mělo být buď:

  1. Obnoveno a aktualizováno pro nové API (pokud jsou testy stále relevantní), nebo
  2. Odstraněno (pokud již nejsou potřebné nebo budou přepsány jinak)

Ponechání rozsáhlého komentovaného kódu v souboru zvyšuje jeho velikost, snižuje čitelnost a vytváří nejistotu ohledně toho, zda bude kód obnoven či ne.

Pokud je to dočasné během refaktoringu, zvažte:

  • Přidání jasného komentáře s datem a plánem obnovení
  • Vytvoření trackující issue pro obnovení testů
  • Alternativně použití git historie místo ponechání komentovaného kódu

Mohu pomoci s aktualizací těchto testů na nové API nebo s vytvořením plánu pro jejich obnovu.

🤖 Prompt for AI Agents
packages/common/test/local-first/Evolu.test.ts lines 7-1343 contain ~1,300 lines
of commented-out tests that should not remain as-is; either restore/update them
to the current API or remove/archive them. Choose one: (A) Restore: update the
tests to the new API, re-enable them, run the test suite and fix failures; (B)
Archive/Remove: delete the commented block from this file, move it to a new file
like tests/archived/Evolu.test.archived.ts in the repo (or a branch) with a
brief header explaining why and when to restore, and create a tracking issue
referencing that archive; If this is temporary during refactor, replace the
block with a short TODO header including date, planned owner, and issue/PR link;
finally run lint/tests and commit only the cleaned file.

Comment on lines +1 to +11
// TODO: Implement Node.js Worker API
//
// This module should provide Node.js implementations of the common Worker API:
// - createWorker
// - createWorkerSelf (with onError hooking process.on('uncaughtException') and
// process.on('unhandledRejection'))
// - createMessageChannel
// - createMessagePort
//
// Node.js uses worker_threads module for Worker/MessageChannel/MessagePort.
// Error handling uses process events instead of globalThis.onerror.
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Placeholder soubor vyžaduje sledování implementace.

Soubor obsahuje pouze TODO komentáře popisující zamýšlené API pro Node.js Worker. Ačkoli dokumentace záměru je užitečná, absence skutečné implementace by měla být sledována.

Chcete, abych vytvořil nový issue pro sledování implementace Node.js Worker API (createWorker, createWorkerSelf, createMessageChannel, createMessagePort)?

🤖 Prompt for AI Agents
In packages/nodejs/src/Worker.ts lines 1-11, the file only contains TODO
comments and lacks the Node.js Worker API implementation; implement the missing
API by importing worker_threads and exporting createWorker (spawning new Worker
from worker_threads.Worker with passed filename and options), createWorkerSelf
(when running inside a worker, return an object that posts messages to
parentPort and register process.on('uncaughtException') and
process.on('unhandledRejection') to forward errors to parentPort),
createMessageChannel (wrap new worker_threads.MessageChannel and return its two
ports), and createMessagePort (wrap a MessagePort from
worker_threads.MessagePort and expose postMessage/onmessage/close), ensure types
and option mapping match the shared/common Worker API, add unit tests or simple
runtime examples, and if you prefer to postpone full implementation create a new
issue tracking the missing functions and export a minimal stub that throws a
clear "Not implemented" error for each function so callers fail fast.

});
},
onMessage: null,
native: null as unknown as NativeMessagePort, // React Native runs in-process, no real native port
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Přetypování null as unknown as NativeMessagePort není typově bezpečné.

Přetypování null as unknown as NativeMessagePort obchází typový systém. Pokud native port není v React Native dostupný, zvažte:

  1. Změnu typu NativeMessagePort na NativeMessagePort | null
  2. Nebo vytvoření speciálního "no-op" portu

Současné řešení funguje, ale skrývá skutečnost, že hodnota je null.

🤖 Prompt for AI Agents
In packages/react-native/src/Worker.ts around line 36, the field is being
force-cast with "null as unknown as NativeMessagePort", which bypasses the type
system; change the declaration to accurately reflect nullable state or provide a
safe no-op port: either update the type to NativeMessagePort | null and handle
null checks wherever used, or implement and assign a concrete no-op
NativeMessagePort that implements the port interface so callers can use it
without null checks; ensure all usages are updated to handle the new nullable
type or rely on the no-op implementation.

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.

Review continued from previous batch...

@@ -0,0 +1,345 @@
"use client";

import * as Evolu from "@evolu/common";
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Použití namespace importu porušuje coding guidelines.

Podle coding guidelines: "Use named imports only - avoid default exports and namespace imports". Použijte pojmenované importy místo namespace importu.

🔎 Návrh opravy
-import * as Evolu from "@evolu/common";
+import {
+  id,
+  NonEmptyString100,
+  nullOr,
+  SqliteBoolean,
+  createEvolu,
+  SimpleName,
+  sqliteTrue,
+  kysely,
+  booleanToSqliteBoolean,
+  Mnemonic,
+  createObjectURL,
+  createFormatTypeError,
+  MinLengthError,
+  MaxLengthError,
+} from "@evolu/common";

A následně aktualizujte všechny reference z Evolu.xxx na přímé importy.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
apps/web/src/app/(playgrounds)/playgrounds/multitenant/EvoluMultitenantExample.tsx
around line 3, replace the namespace import "import * as Evolu from
'@evolu/common';" with explicit named imports for only the symbols used in this
file (e.g., import { Foo, Bar } from '@evolu/common';), then update all
occurrences of Evolu.xxx to the direct names (Foo, Bar, etc.), remove the unused
namespace import, and run a quick TypeScript compile/IDE refactor to catch and
fix any remaining references or import ordering issues.

Comment on lines +77 to +94
const disposeSync = (stack: ReadonlyArray<DisposableResource>): void => {
let error: unknown;
let hasError = false;

for (let i = stack.length - 1; i >= 0; i--) {
const { method, value } = stack[i];
if (method) {
try {
method.call(value);
} catch (e) {
error = hasError ? new SuppressedError(e, error) : e;
hasError = true;
}
}
}

if (hasError) throw error;
};
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

🌐 Web query:

SuppressedError browser support Safari availability

💡 Result:

Short answer: Safari (macOS and iOS) does not currently support the JavaScript SuppressedError built-in; support exists in recent Chromium and Firefox builds but is absent in shipping Safari releases (only unknown/TP status). [1][2]

Sources:

  • MDN: SuppressedError (limited availability). [1]
  • Can I use: SuppressedError support table (shows Safari not supported; support in Chrome/Edge/Firefox). [2]

Řešit nedostupnost SuppressedError v Safari a dalších prostředích.

SuppressedError není podporován v Safari (macOS ani iOS) a není dostupný v dalších prostředích bez nativní podpory DisposableStack. Kód v tomto polyfilu by měl buď zkontrolovat dostupnost konstruktoru před použitím, nebo poskytovat fallback řešení, aby nedošlo k ReferenceError v Safari.

🤖 Prompt for AI Agents
In packages/common/src/Polyfills.ts around lines 77 to 94, the code uses
SuppressedError directly which can throw ReferenceError in Safari/other
runtimes; add a safe check and fallback: detect if globalThis.SuppressedError
exists and use it, otherwise define a minimal local SuppressedError class
(extending Error, setting name = 'SuppressedError', original error, suppressed
error properties and preserving stack) and use that fallback when combining
errors in disposeSync so the code never references an undefined identifier.

Comment on lines +74 to +92
export type Transferable = NativeMessagePort | ArrayBuffer;

/**
* Error reporting wrapper that catches synchronous errors in handlers and
* converts them to transferable error messages sent to the main thread.
* Opaque type for platform-specific native MessagePort.
*
* Exists because `postMessage` transfer requires the native object itself, not
* a wrapper. Ensures type-safe wiring between {@link MessagePort.native} and
* {@link CreateMessagePort} without exposing platform details.
*/
export type WithErrorReporting = <A extends Array<any>>(
handler: (...args: A) => void,
) => (...args: A) => void;

type HasInit<Input> =
Extract<Input, { type: "init" }> extends never
? ["Input must contain a variant with { type: 'init' }"]
: unknown;

type HasWorkerErrorOutput<T> =
Extract<T, { type: "onError" }> extends infer E
? [E] extends [never]
? [
"Output must contain { type: 'onError'; error: TransferableError | ... }",
]
: E extends { error: infer Err }
? TransferableError extends Err
? unknown
: ["Output.onError.error must include TransferableError"]
: ["Output.onError must have an error property"]
: never;
export type NativeMessagePort = Brand<"NativeMessagePort">;

/** Factory function to create a {@link MessagePort} from a native port. */
export type CreateMessagePort = <Input, Output = never>(
nativePort: NativeMessagePort,
) => MessagePort<Input, Output>;

export interface CreateMessagePortDep {
readonly createMessagePort: CreateMessagePort;
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Zvažte rozšíření typu Transferable.

Aktuální definice Transferable = NativeMessagePort | ArrayBuffer pokrývá základní případy. Web API však podporuje i další přenositelné objekty jako ImageBitmap, OffscreenCanvas, ReadableStream, WritableStream atd.

Pokud je záměr podporovat pouze tyto dva typy pro zjednodušení, je to v pořádku - jen doporučuji zdokumentovat toto rozhodnutí.

🔎 Volitelné rozšíření
-export type Transferable = NativeMessagePort | ArrayBuffer;
+/**
+ * Transferable objects for postMessage.
+ * Currently limited to MessagePort and ArrayBuffer. Extend as needed
+ * (e.g., ImageBitmap, OffscreenCanvas, ReadableStream, WritableStream).
+ */
+export type Transferable = NativeMessagePort | ArrayBuffer;
📝 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
export type Transferable = NativeMessagePort | ArrayBuffer;
/**
* Error reporting wrapper that catches synchronous errors in handlers and
* converts them to transferable error messages sent to the main thread.
* Opaque type for platform-specific native MessagePort.
*
* Exists because `postMessage` transfer requires the native object itself, not
* a wrapper. Ensures type-safe wiring between {@link MessagePort.native} and
* {@link CreateMessagePort} without exposing platform details.
*/
export type WithErrorReporting = <A extends Array<any>>(
handler: (...args: A) => void,
) => (...args: A) => void;
type HasInit<Input> =
Extract<Input, { type: "init" }> extends never
? ["Input must contain a variant with { type: 'init' }"]
: unknown;
type HasWorkerErrorOutput<T> =
Extract<T, { type: "onError" }> extends infer E
? [E] extends [never]
? [
"Output must contain { type: 'onError'; error: TransferableError | ... }",
]
: E extends { error: infer Err }
? TransferableError extends Err
? unknown
: ["Output.onError.error must include TransferableError"]
: ["Output.onError must have an error property"]
: never;
export type NativeMessagePort = Brand<"NativeMessagePort">;
/** Factory function to create a {@link MessagePort} from a native port. */
export type CreateMessagePort = <Input, Output = never>(
nativePort: NativeMessagePort,
) => MessagePort<Input, Output>;
export interface CreateMessagePortDep {
readonly createMessagePort: CreateMessagePort;
}
export type Transferable = NativeMessagePort | ArrayBuffer;
/**
* Transferable objects for postMessage.
* Currently limited to MessagePort and ArrayBuffer. Extend as needed
* (e.g., ImageBitmap, OffscreenCanvas, ReadableStream, WritableStream).
*/
export type Transferable = NativeMessagePort | ArrayBuffer;
/** Factory function to create a {@link MessagePort} from a native port. */
export type CreateMessagePort = <Input, Output = never>(
nativePort: NativeMessagePort,
) => MessagePort<Input, Output>;
export interface CreateMessagePortDep {
readonly createMessagePort: CreateMessagePort;
}
🤖 Prompt for AI Agents
In packages/common/src/Worker.ts around lines 74–92, the Transferable union only
includes NativeMessagePort and ArrayBuffer which omits other standard
transferable Web types; either explicitly extend the Transferable union to
include the common transferables (e.g., ImageBitmap, OffscreenCanvas,
MessagePort, ReadableStream<any>, WritableStream<any>, etc.) or document the
intentional limitation; to fix, choose one approach: (A) expand the union to add
the standard transferable types so the type system accepts them, or (B) add a
clear doc comment above the Transferable type stating the deliberate scope
(NativeMessagePort | ArrayBuffer) and why other transferables are excluded, and
consider exposing a generic extension point if callers need additional types.

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: 1

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0936343 and eaa5a43.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • apps/web/package.json
  • examples/react-nextjs/package.json
  • examples/react-vite-pwa/package.json
  • examples/svelte-vite-pwa/package.json
  • package.json
  • packages/svelte/package.json
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-12-23T13:22:25.317Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.317Z
Learning: Check compilation using `pnpm build` to verify TypeScript compilation across all packages

Applied to files:

  • package.json
📚 Learning: 2025-12-23T13:22:25.317Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.317Z
Learning: Run specific test files using `pnpm test --filter evolu/package-name -- test-file-pattern` from project root

Applied to files:

  • package.json
🔇 Additional comments (9)
examples/svelte-vite-pwa/package.json (1)

17-24: Aktualizace závislostí vypadají dobře.

Všechny aktualizace jsou minor/patch verze bez breaking changes. Změny jsou v souladu se semantic versioning.

examples/react-vite-pwa/package.json (1)

22-43: Aktualizace závislostí jsou v pořádku.

Všechny změny jsou minor/patch verze. Aktualizace @types/node na v24 je major verze, ale jedná se pouze o typové definice, které jsou kompatibilní s novějšími verzemi Node.js.

apps/web/package.json (2)

15-18: Aktualizace závislostí vypadají dobře.

Všechny aktualizace jsou minor/patch verze bez očekávaných breaking changes. Next.js 16.1.1, Tailwind 4.1.18 a další jsou standardní údržbové aktualizace.

Also applies to: 20-63


19-19: Závislost je použita v kódové bázi.

Závislost @evolu/sqlite-wasm verze 2.2.4 je aktivně používána v packages/web/src/WasmSqliteDriver.ts a exportována pro ostatní balíčky. Přesné verzování bez ^ nebo ~ je záměrné a konzistentní v celém monorepozitáři (stejná verze je pinována také v packages/web/package.json). Toto je standardní postup pro zajištění konzistence verzí v monorepozitáři.

examples/react-nextjs/package.json (1)

18-33: Aktualizace závislostí jsou v pořádku.

Všechny změny jsou minor/patch verze. Změna formátu verze eslint z přesné "9.39.1" na "^9.39.1" je dobrá praxe umožňující automatické patch aktualizace.

package.json (4)

7-8: Změny build workflow vypadají dobře.

Nová struktura zajišťuje, že dokumentace je vždy aktuální před buildem webové aplikace. Script build:docs kombinuje generování typedoc s následnou úpravou, což je logické.

Also applies to: 16-16


32-51: Aktualizace devDependencies a přidání vitest.

Všechny aktualizace jsou minor/patch verze. Přidání vitest ^4.0.16 naznačuje nový testovací framework - ujistěte se, že je správně nakonfigurován.


56-56: Ověřte kompatibilitu pnpm@10.26.1.

Aktualizace na pnpm@10.26.1 může obsahovat změny v chování workspaců. Ujistěte se, že všechny workspace operace fungují správně.


79-95: Nová struktura workspace a katalogy vypadají dobře.

Přidání workspaces.catalogs pro React 19 zajišťuje konzistentní verze napříč balíčky. Struktura je v souladu s best practices pro monorepo správu. React 19.2.3 je stabilní verze.

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

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eaa5a43 and b80fd81.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • examples/vue-vite-pwa/package.json
  • packages/common/package.json
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.test.{ts,tsx} : Leverage `_deps.ts` - use existing test utilities and mocks from `packages/common/test/_deps.ts` such as `testCreateId`, `testTime`, `testOwner`

Applied to files:

  • packages/common/package.json
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.test.{ts,tsx} : Create test factories like `createTestTime` for generating test dependencies

Applied to files:

  • packages/common/package.json
🔇 Additional comments (3)
packages/common/package.json (2)

67-74: Všechny verze vývojových závislostí jsou platné a dostupné.

Verifikace potvrdila, že TypeScript 5.9.3, Playwright 1.57.0, Vitest 4.0.16 a balíčky pro browser testing (@vitest/browser@4.0.16, @vitest/browser-playwright@4.0.16) všechny existují a jsou na npm registry. Všechny vitest balíčky jsou konzistentně na verzi 4.0.16. Před mergením ověřte, že jsou tato nástrojí vzájemně kompatibilní v rámci vašeho projektu.


55-58: Verze závislostí jsou platné a dostupné, bez známých bezpečnostních zranitelností.

Ověřením byl potvrzen status všech balíčků:

  • @noble/ciphers@2.1.1: Dostupná verze, bez bezpečnostních zranitelností. Breaking changes pocházejí z v2 migrace (ESM-only, Node v20.19+, .js přípon v imporech), již by měly být v kódu řešeny.
  • @noble/hashes@2.0.1: Dostupná verze, bez bezpečnostních zranitelností, auditovaný projekt s podepsanými build-y.
  • @scure/bip39@2.0.1: Dostupná verze, publikovaná na npm.
  • kysely@0.28.8: Dostupná verze, bez bezpečnostních zranitelností. Nižší patch verze 0.28.9 je k dispozici pro aktualizaci.

Zvažte upgrade kysely na 0.28.9 pro nejnovější opravy chyb.

examples/vue-vite-pwa/package.json (1)

21-27: Otestujte aplikaci před sloučením – kritické upozornění na selhání testů.

DevDependencies obsahují aktualizace, z nichž většina jsou patch verze (@vite-pwa/assets-generator 1.0.0→1.0.2, @vitejs/plugin-vue 6.0.1→6.0.3, typescript 5.9.2→5.9.3). Minorní aktualizace zahrnují vite (7.1.3→7.2.7), vite-plugin-pwa (1.0.2→1.2.0) a vue-tsc (3.1.4→3.2.1). Kontrola release notes ukazuje, že vite 7.2.7 i vite-plugin-pwa 1.2.0 neobsahují signifikantní breaking changes.

Hlavní problém: Testy nebyly přidány a podle PR checklistu neprochází lokálně. Všechny testy musí procházet a aplikace musí být otestována s těmito verzemi před sloučením.

Comment on lines +17 to +18
"vue": "^3.5.25",
"workbox-window": "^7.4.0"
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:

#!/bin/bash
# Ověření existences verzí na npm registru

echo "=== Ověření runtime závislostí ==="
echo "vue@3.5.25:"
npm view vue@3.5.25 version 2>/dev/null || echo "Verze nenalezena"

echo -e "\nworkbox-window@7.4.0:"
npm view workbox-window@7.4.0 version 2>/dev/null || echo "Verze nenalezena"

echo -e "\n=== Ověření devDependencies ==="
npm view @vite-pwa/assets-generator@1.0.2 version 2>/dev/null || echo "Verze nenalezena"
npm view @vitejs/plugin-vue@6.0.3 version 2>/dev/null || echo "Verze nenalezena"
npm view typescript@5.9.3 version 2>/dev/null || echo "Verze nenalezena"
npm view vite@7.2.7 version 2>/dev/null || echo "Verze nenalezena"
npm view vite-plugin-pwa@1.2.0 version 2>/dev/null || echo "Verze nenalezena"
npm view vue-tsc@3.2.1 version 2>/dev/null || echo "Verze nenalezena"

echo -e "\n=== Ověření bezpečnostních zranitelností ==="
# Kontrola pomocí npm audit
npm audit --audit-level=moderate 2>/dev/null | head -20 || echo "npm audit není dostupný"

Repository: miccy/evolu

Length of output: 252


Všechny verze balíčků existují, ale testy musí projít místně před sloučením.

Ověřili jsme, že všechny aktualizované verze (vue@3.5.25, workbox-window@7.4.0 a všechny devDependencies) existují na npm registru a nejsou hlášeny žádné bezpečnostní zranitelnosti.

Kritická poznámka: Podle kontrolního seznamu PR nejsou testy spuštěné místně (zaškrtávací pole "All tests pass locally" je zrušeno). Před sloučením je nutné zajistit, aby všechny testy prošly.

🤖 Prompt for AI Agents
In examples/vue-vite-pwa/package.json around lines 17-18, the PR updates package
versions (vue@3.5.25, workbox-window@7.4.0 and devDependencies) but the "All
tests pass locally" checklist is unchecked; run the full test suite locally
(install dependencies cleanly with npm ci or pnpm install, rebuild if needed),
fix any test failures or lint errors introduced by the dependency bumps (update
code, mocks, or test snapshots), regenerate and commit updated
lockfile/package-lock or pnpm-lock if changes are required, re-run tests until
all pass, then push the fixes and mark the checklist item.

},
"engines": {
"node": ">=22.0.0"
"node": ">=24.0.0"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Vysvětlete a ověřte požadavek na Node.js 24.

Změna z Node.js >=22.0.0 na >=24.0.0 je významný breaking change, který:

  • Přeskakuje Node.js 23 (očekávané chování pro LTS cyklus)
  • Může být předčasný, pokud Node.js 24 ještě není široce dostupný nebo stabilní
  • Ovlivní všechny uživatele a CI/CD pipeline
  • Není odůvodněn v popisu PR

Ujistěte se, že:

  1. Node.js 24 je skutečně požadován pro nové funkce v kódu
  2. Uživatelé a vývojáři byli informováni o této změně
  3. CI/CD konfigurace byla aktualizována
When was Node.js 24 released and is it considered stable for production use?

Comment on lines +82 to +84
"sideEffects": [
"./dist/src/index.js"
]
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Nalezení side effects v index.ts a ověření konfigurace sideEffects

echo "=== Obsah packages/common/src/index.ts ==="
cat packages/common/src/index.ts

echo -e "\n=== Hledání globálních error handlerů ==="
rg -n --type ts -C3 'handleGlobalError|addEventListener.*error|process\.on.*error|globalThis' packages/common/src/ | head -50

echo -e "\n=== Hledání polyfills a runtime patches ==="
rg -n --type ts -C3 'Symbol\.(dispose|asyncDispose)|polyfill|Object\.defineProperty.*prototype' packages/common/src/ | head -50

echo -e "\n=== Hledání side-effect importů ==="
rg -n --type ts '^import.*from.*(?!.*{)' packages/common/src/ | head -30

echo -e "\n=== Kontrola package.json sideEffects ==="
cat packages/common/package.json | grep -A 5 'sideEffects'

Repository: miccy/evolu

Length of output: 6895


Konfigurace sideEffects je správná a nezbytná.

Polem sideEffects je správně nakonfigurováno, protože index.ts volá ensurePolyfills() na nejvyšší úrovni importu. Tato inicializace polyfillů je legitimní side effect, který musí být vykonán, aby byla zajištěna správná funkčnost knihovny.

Bez tohoto nastavení by bundlery mohly během tree-shakingu odstranit index.js, přestože má side effects, což by způsobilo, že by se polyfilly neinicializovaly pro spotřebitele.

Zvažte zdokumentovat toto chování v README nebo developer dokumentaci, zejména upozornění, že importy z hluboké cesty (např. @evolu/common/Resources namísto @evolu/common) obcházejí ensurePolyfills() a mohou vyžadovat ruční inicializaci polyfillů.

🤖 Prompt for AI Agents
In packages/common/package.json around lines 82-84, the sideEffects field is
correctly set to preserve dist/src/index.js because index.ts calls
ensurePolyfills() at top-level; update the repository docs by adding a short
note to README or developer docs explaining that this sideEffects entry is
intentional and necessary, and warn that deep imports (e.g.,
@evolu/common/Resources) bypass the top-level initializer and therefore
consumers must either import the package root or manually call
ensurePolyfills(); keep the note concise and include an example import and the
recommended usage.

Upgrades the minimum Node.js version from 22 to 24 (current LTS), allowing for the use of modern JavaScript features like `Promise.withResolvers`.

This change also includes significant internal refactoring:
- Consolidates `Task` and array utility modules.
- Adopts `import type` extensively for better tree-shaking and optimized bundle sizes.
- Enhances type safety with explicit annotations and assertions.
- Removes the deprecated `erasableSyntaxOnly` TypeScript configuration option.
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: 5

♻️ Duplicate comments (5)
packages/common/src/local-first/Storage.ts (1)

54-54: JSDoc příklad používá správný formát ### Example.

Řádek 54 obsahuje ### Example, což je v souladu s coding guidelines. Předchozí review komentář o nekonzistenci již neplatí - kód byl opraven.

packages/common/src/local-first/Evolu.ts (3)

954-969: Kritický problém: loadQuery nikdy nevrátí výsledky.

Volání dbWorker.postMessage je zakomentováno na řádcích 965-969, ale promises se vytvářejí a vracejí volajícímu. To znamená:

  1. loadQuery vrátí promise, která nikdy nebude resolved
  2. subscribeQuery nebude fungovat
  3. Jakýkoli kód používající evolu.loadQuery() nebo React Suspense s use(loadQuery(...)) se zasekne

Toto je blokující problém pro použití Evolu API.


702-815: Velké množství zakomentovaného kódu v produkčním souboru.

Tento blok obsahuje ~115 řádků zakomentovaného kódu včetně důležité logiky pro zpracování worker zpráv (dbWorker.onMessage). Toto výrazně snižuje čitelnost a údržbu kódu.

Pokud je toto WIP refaktoring:

  • Zvažte použití feature flags místo komentářů
  • Nebo dokončete refaktoring v této PR před mergem
  • Nebo přesuňte zakomentovaný kód do samostatného souboru/větve

1115-1115: TODO komentář o exportu createQuery.

Komentář naznačuje, že createQuery by nemělo být exportováno, ale je kvůli testům. Zvažte vytvoření testovacího entry pointu nebo použití @internal JSDoc tagu.

packages/common/src/WebSocket.ts (1)

120-120: Vraťte nadpisy dokumentace na úroveň ### podle konvencí projektu.

Tato změna porušuje coding guidelines, které explicitně vyžadují ### pro konzistenci (viz též předchozí review komentář). Použijte ### místo ## pro obě sekce.

Podle coding guidelines.

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b80fd81 and a90b889.

📒 Files selected for processing (11)
  • .changeset/gentle-pumas-eat.md
  • .nvmrc
  • packages/common/src/Task.ts
  • packages/common/src/WebSocket.ts
  • packages/common/src/local-first/Evolu.ts
  • packages/common/src/local-first/Protocol.ts
  • packages/common/src/local-first/Relay.ts
  • packages/common/src/local-first/Storage.ts
  • packages/common/src/local-first/Sync.ts
  • packages/common/test/WebSocket.test.ts
  • packages/tsconfig/base.json
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use named imports only - avoid default exports and namespace imports
Use unique exported members - avoid namespaces, use descriptive names to prevent conflicts
Organize code top-down - public interfaces first, then implementation, then implementation details
Use arrow functions - avoid the function keyword for consistency, except for function overloads where TypeScript requires the function keyword
Favor immutability - use readonly properties and ReadonlyArray/NonEmptyReadonlyArray in type definitions
Avoid @param and @return tags in JSDoc - TypeScript provides type information, focus on describing the function's purpose instead
Use ### Example instead of @example in JSDoc - for better markdown rendering and consistency
Use @experimental tag for new APIs that may change or be removed in minor/patch versions without breaking semver
Use Result<T, E> for business/domain errors in public APIs - keep implementation-specific errors internal
Favor imperative patterns over monadic helpers for readability in Result handling
Use plain objects for business errors, Error instances only for debugging
Use Result<void, E> for operations that don't return values
Use trySync for wrapping synchronous unsafe code and tryAsync for wrapping asynchronous unsafe code
Use getOrThrow only for critical startup code where failure should crash
Use Type for validation/parsing - leverage Evolu's Type system for runtime validation
Define typed errors using interfaces extending TypeError<Name>
Create Type factories using brand, transform, array, object and other Evolu Type utilities
Use Brand types for semantic distinctions and constraints
Use assertions for conditions logically guaranteed but not statically known by TypeScript, catching developer mistakes eagerly
Never use assertions instead of proper type validation - use Type system for runtime validation
Define dependencies as interfaces following Evolu's convention-based DI approach
Use...

Files:

  • packages/common/src/WebSocket.ts
  • packages/common/src/Task.ts
  • packages/common/src/local-first/Storage.ts
  • packages/common/src/local-first/Relay.ts
  • packages/common/src/local-first/Sync.ts
  • packages/common/src/local-first/Protocol.ts
  • packages/common/src/local-first/Evolu.ts
  • packages/common/test/WebSocket.test.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.test.{ts,tsx}: Leverage _deps.ts - use existing test utilities and mocks from packages/common/test/_deps.ts such as testCreateId, testTime, testOwner
Mock dependencies using the same interfaces as production code
Create test factories like createTestTime for generating test dependencies
Never rely on global state in tests
Use assertions in tests for conditions that should never fail

Files:

  • packages/common/test/WebSocket.test.ts
🧠 Learnings (9)
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Use `### Example` instead of `example` in JSDoc - for better markdown rendering and consistency

Applied to files:

  • packages/common/src/WebSocket.ts
  • packages/common/src/local-first/Storage.ts
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Favor imperative patterns over monadic helpers for readability in Result handling

Applied to files:

  • packages/common/src/Task.ts
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Use `Result<void, E>` for operations that don't return values

Applied to files:

  • packages/common/src/Task.ts
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Use `Result<T, E>` for business/domain errors in public APIs - keep implementation-specific errors internal

Applied to files:

  • packages/common/src/Task.ts
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.test.{ts,tsx} : Leverage `_deps.ts` - use existing test utilities and mocks from `packages/common/test/_deps.ts` such as `testCreateId`, `testTime`, `testOwner`

Applied to files:

  • packages/common/src/local-first/Relay.ts
  • packages/common/src/local-first/Sync.ts
  • packages/common/src/local-first/Protocol.ts
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Use `trySync` for wrapping synchronous unsafe code and `tryAsync` for wrapping asynchronous unsafe code

Applied to files:

  • packages/common/src/local-first/Sync.ts
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Wrap dependencies using naming conventions like `TimeDep`, `LoggerDep` etc. to avoid property clashes

Applied to files:

  • packages/common/src/local-first/Sync.ts
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Define dependencies as interfaces following Evolu's convention-based DI approach

Applied to files:

  • packages/common/src/local-first/Evolu.ts
📚 Learning: 2025-12-23T13:22:25.316Z
Learnt from: CR
Repo: miccy/evolu PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T13:22:25.316Z
Learning: Applies to **/*.{ts,tsx} : Use unique exported members - avoid namespaces, use descriptive names to prevent conflicts

Applied to files:

  • packages/common/src/local-first/Evolu.ts
🧬 Code graph analysis (5)
packages/common/src/Task.ts (1)
packages/common/src/Array.ts (1)
  • shiftFromArray (579-580)
packages/common/src/local-first/Storage.ts (2)
packages/common/src/Assert.ts (1)
  • assert (29-36)
packages/common/src/Array.ts (1)
  • isNonEmptyArray (164-168)
packages/common/src/local-first/Relay.ts (3)
packages/common/src/Array.ts (1)
  • isNonEmptyArray (164-168)
packages/common/src/Function.ts (1)
  • Lazy (141-141)
packages/common/src/local-first/Owner.ts (2)
  • OwnerId (72-72)
  • OwnerId (73-73)
packages/common/src/local-first/Sync.ts (4)
packages/common/src/Crypto.ts (1)
  • DecryptWithXChaCha20Poly1305Error (165-168)
packages/common/src/Error.ts (2)
  • UnknownError (16-19)
  • createUnknownError (22-67)
packages/common/src/local-first/Protocol.ts (1)
  • decryptAndDecodeDbChange (1790-1859)
packages/common/src/Array.ts (1)
  • isNonEmptyArray (164-168)
packages/common/test/WebSocket.test.ts (2)
packages/common/src/WebSocket.ts (1)
  • WebSocketRetryError (102-104)
packages/common/src/Task.ts (1)
  • AbortError (229-232)
🔇 Additional comments (33)
.nvmrc (1)

1-1: Soubor .nvmrc byl nově přidán s verzí v24.

Ověřeno, že všechny soubory package.json v workspace mají konzistentní omezení engines.node>=24.0.0:

  • apps/relay/package.json
  • packages/common/package.json
  • packages/nodejs/package.json
  • packages/react-native/package.json
  • packages/react-web/package.json
  • packages/react/package.json
  • packages/vue/package.json
  • packages/web/package.json
  • package.json (root)

Tato změna zajišťuje konzistenci minimální verze Node.js v celém workspace.

Likely an incorrect or invalid review comment.

packages/common/src/local-first/Relay.ts (3)

2-13: Vylepšení importů je správné.

Změny v importech jsou konzistentní:

  • Přejmenování LazyValue na Lazy odpovídá aktuální definici typu v Function.ts
  • Přejmenování isNonEmptyReadonlyArray na isNonEmptyArray odpovídá aktuálnímu názvu funkce v Array.ts
  • Použití explicitního klíčového slova type pro MaybeAsync a Mutex zlepšuje tree-shaking

143-143: Konzistentní refaktoring kontrolních funkcí.

Použití isNonEmptyArray místo isNonEmptyReadonlyArray je v souladu s přejmenováním v modulu Array.ts. Funkční chování zůstává nezměněno.

Also applies to: 200-200


369-369: Aktualizace typů v RelayLogger rozhraní je správná.

Změna z LazyValue<number> na Lazy<number> je konzistentní s přejmenováním typu v Function.ts a s importem na řádku 9. Funkční chování zůstává zachováno.

Also applies to: 373-373

.changeset/gentle-pumas-eat.md (2)

12-14: Tvrzení o Node.js verzi je přesné.

Changeset správně uvádí Node.js 24 jako aktuální LTS. Selon webu Node.js Release WG, Node.js 24 (kódové jméno "Krypton") vstoupila do Active LTS fáze 28. října 2025, což činí toto tvrzení v prosinci 2025 správným.


1-10: Všechny uvedené balíčky v changesestu existují a jsou správně identifikovány.

Verifikace potvrdila, že všech 8 balíčků s major version bump skutečně existuje v monorepu: @evolu/react-native, @evolu/react-web, @evolu/common, @evolu/nodejs, @evolu/react, @evolu/vue, @evolu/web a @evolu/relay (umístěný v apps/relay/). Changeset je přesný a odpovídá struktuře monorepa.

packages/common/src/local-first/Protocol.ts (6)

181-263: Importy a závislosti vypadají správně.

Přechod z SymmetricCryptoDep na RandomBytesDep a použití nových XChaCha20-Poly1305 funkcí je konzistentní s celkovým refaktoringem kryptografické vrstvy v tomto PR. Import isNonEmptyArray nahrazuje isNonEmptyReadonlyArray v souladu se změnami v Array.ts.


459-461: Změna závislosti na RandomBytesDep je správná.

Funkce createProtocolMessageFromCrdtMessages nyní závisí pouze na RandomBytesDep místo SymmetricCryptoDep, což odpovídá refaktoringu kryptografické vrstvy. Šifrování je delegováno na encodeAndEncryptDbChange, které interně používá encryptWithXChaCha20Poly1305.


1617-1648: Výborná validace v decodeRle pro prevenci DoS útoků.

Nová funkce decodeRle správně validuje:

  1. runLength === 0 - prevence nekonečné smyčky
  2. runLength > remaining - prevence CPU/memory amplifikace

Toto je důležitá bezpečnostní ochrana proti škodlivým vstupům.


1790-1859: Refaktorovaná funkce decryptAndDecodeDbChange vypadá správně.

Změna signatury z curried formy (deps) => (message, key) na přímou (message, key) je v pořádku. Funkce správně:

  1. Extrahuje nonce a ciphertext
  2. Dešifruje pomocí decryptWithXChaCha20Poly1305
  3. Validuje timestamp proti očekávanému
  4. Vrací Result s odpovídajícími chybovými typy

Ošetření chyb pomocí try-catch s návratem ProtocolInvalidDataError je konzistentní s ostatními dekódovacími funkcemi.


1692-1693: JSDoc příklady používají správný formát ### Example.

Změna z ## Example na standardní formát je v souladu s coding guidelines: "Use ### Example instead of @example in JSDoc".

Also applies to: 1714-1715


1233-1247: Optimalizace pre-alokace pole v decodeMessages.

Změna z push na přímé přiřazení pomocí indexu (messages[i] = ...) s pre-alokací (new Array<EncryptedCrdtMessage>(timestamps.length)) je výkonnostní optimalizace, která redukuje realokace pole.

packages/common/src/local-first/Storage.ts (4)

1-14: Importy aktualizovány správně.

Přechod na type-only importy (type Brand, type RandomDep, atd.) je v souladu s PR cílem zlepšit tree-shaking. Použití isNonEmptyArray místo isNonEmptyReadonlyArray odpovídá unifikaci v Array.ts.


208-211: Symbol InfiniteUpperBound má nyní plně kvalifikovaný název.

Přejmenování z Symbol("InfiniteUpperBound") na Symbol("evolu.local-first.Storage.InfiniteUpperBound") zlepšuje debugovatelnost a zabraňuje kolizím názvů. Toto je dobrá praxe pro veřejné symboly.


1538-1554: Logika fingerprintRanges správně odvozuje upperBound pro každý řádek.

Kód nyní korektně:

  1. Pro všechny řádky kromě posledního používá row.b jako upperBound
  2. Pro poslední řádek používá předaný upperBound parametr
  3. Assertion row.b !== null zajišťuje, že neexistují null hodnoty pro ne-poslední řádky

Toto je bezpečnější implementace než předchozí verze.


1671-1671: Použití isNonEmptyArray je v pořádku.

Funkce isNonEmptyArray v Array.ts má overloady pro ReadonlyArray<T> i Array<T>, takže tato změna je zpětně kompatibilní.

packages/common/src/local-first/Sync.ts (6)

139-150: Aktualizované typy chyb v SyncConfig.onError jsou správné.

Změna z SymmetricCryptoDecryptError na DecryptWithXChaCha20Poly1305Error a z TransferableError na UnknownError odpovídá refaktoringu kryptografické a chybové vrstvy v tomto PR.


155-167: Závislosti createSync aktualizovány správně.

Nahrazení SymmetricCryptoDep za RandomBytesDep je konzistentní s celkovým refaktoringem. Šifrovací operace nyní používají XChaCha20-Poly1305 API interně.


264-266: Použití createUnknownError je správné.

Nahrazení createTransferableError(error) za createUnknownError(error) v catch bloku je v souladu se změnami v Error.ts. createUnknownError poskytuje robustní serializaci různých typů chyb.


513-516: Volání decryptAndDecodeDbChange aktualizováno správně.

Změna z curried formy decryptAndDecodeDbChange(deps)(message, key) na přímou decryptAndDecodeDbChange(message, key) odpovídá nové signatuře funkce v Protocol.ts.


538-538: Použití isNonEmptyArray místo isNonEmptyReadonlyArray je v pořádku.

Funkce isNonEmptyArray podporuje jak ReadonlyArray, tak Array díky přetížení v Array.ts.


948-950: TODO komentář pro budoucí práci.

Komentář naznačuje plánovanou implementaci createSyncState. Toto je jen poznámka pro budoucí vývoj.

packages/common/src/local-first/Evolu.ts (3)

74-225: Nové EvoluConfig rozhraní je dobře zdokumentováno.

Konfigurace obsahuje jasnou dokumentaci pro name, transports, externalAppOwner a inMemory s praktickými příklady. Toto zlepšuje developer experience.


579-620: Nové typy závislostí a createEvoluDeps helper jsou dobře navrženy.

EvoluPlatformDeps, EvoluDeps a ErrorStoreDep následují konvenci pojmenování závislostí (*Dep) dle coding guidelines. Centralizovaný error store umožňuje globální zpracování chyb napříč instancemi.


946-948: Evolu instance nyní obsahuje name property.

Přidání name: SimpleName do rozhraní Evolu je užitečné pro debugging a identifikaci instancí.

packages/common/src/WebSocket.ts (2)

3-3: Výborné použití type-only importu!

Type-only import pro RetryOptions zlepšuje tree-shaking a odpovídá best practices pro TypeScript. Žádný dopad na runtime.


235-239: Skvělé vylepšení type safety!

Explicitní typování parametru onmessage handleru zlepšuje type safety a správně reflektuje možné typy zpráv (string | ArrayBuffer | Blob) na základě nastavení binaryType. Žádná změna runtime chování.

packages/common/test/WebSocket.test.ts (1)

422-422: Výborná type safety v testech!

Explicitní typové anotace pro onRetry callback parametry zlepšují type safety a konzistentně reflektují možné error typy (WebSocketRetryError | AbortError). Toto je v souladu s mechanismem retry a přidává užitečnou dokumentaci typu přímo v testech.

Also applies to: 467-467, 510-510

packages/common/src/Task.ts (5)

1-2: Správné použití type importu a konzistentní přejmenování!

Import změny jsou v pořádku:

  • Přejmenování shiftArray na shiftFromArray je konzistentní s úpravami v Array modulu
  • Type-only import pro Result zlepšuje tree-shaking

20-31: Vynikající zlepšení dokumentace!

Aktualizace dokumentace správně:

  • Přidává odkazy na MDN pro AbortSignal a AbortController
  • Objasňuje mechanismus cancellation s přesnými type příklady
  • Ukazuje type narrowing s/bez signálu

Dokumentace je nyní jasnější a lépe vysvětluje cancellation pattern.

Also applies to: 159-208, 210-223, 225-232


243-257: Type assertion je přijatelný workaround pro AbortSignal.any().

Implementace combineSignal používá type cast pro přístup k AbortSignal.any(), což je potřebné, protože TypeScript DOM types nemusí obsahovat nejnovější API. Vzhledem k tomu, že minimum je Node.js 24 (obsahuje AbortSignal.any() od verze 20.3.0), je runtime chování v pořádku.


672-672: Konzistentní použití přejmenované funkce!

Použití shiftFromArray je v souladu s přejmenováním v Array modulu. Funkce je správně aplikována v kontextu semaphore queue managementu.

Also applies to: 709-709


7-15: Globální deklarace Promise.withResolvers se správně předpokládá, že bude dostupná za běhu.

Deklarace typu v řádcích 7-15 je správná. Protože je minimální verze Node.js nastavena na 24.0.0, má Promise.withResolvers nativní podporu. Pro prohlížeče starší než Chrome 119, Firefox 121 nebo Safari 17.4 by konzumenti knihovny měli poskytnout svůj vlastní polyfill. Příklad polyfill implementace je k dispozici v examples/react-expo/polyfills.ts pro React Native/Hermes prostředí.

Comment on lines +12 to +14
# Update Node.js requirement

Updated minimum Node.js version from 22 to 24 (current LTS)
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Zvažte rozšíření popisu změn o kontext refaktoringu.

Aktuální popis se zaměřuje pouze na upgrade Node.js. Podle kontextu PR zahrnuje tato verze značné refaktoringové práce (sjednocení Array API, nové typy chyb, správa zdrojů, vývoj veřejného API). Zvažte, zda by se těmito položkami mělo zmínit v popisu changésetu pro jasnost v changeloge.

Alternativně, pokud se opírá příslušné detaily na commit messages, je aktuální minimalistický přístup přijatelný za předpokladu, že commit zprávy jsou vhodně detailní.

🤖 Prompt for AI Agents
In .changeset/gentle-pumas-eat.md around lines 12–14, the changeset only
mentions the Node.js version bump and should either be expanded to include the
PR's refactoring context (unification of the Array API, introduction of new
error types, resource management improvements, and public API development) for
clearer changelogs, or you should verify and document that those details are
captured in the related commit messages; update the changeset to briefly list
those refactor items (or add a note pointing to the detailed commits) so release
notes are informative.

Comment on lines +936 to 944
// TODO:
// dbWorker.postMessage({
// type: "mutate",
// tabId: getTabId(),
// changes,
// onCompleteIds,
// subscribedQueries: subscribedQueries.get(),
// });
};
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

Mutace nebudou zpracovány - dbWorker.postMessage je zakomentováno.

processMutationQueue zakomentovalo odeslání změn workeru (řádky 937-943). To znamená, že insert, update a upsert nebudou perzistovat data do databáze.

🤖 Prompt for AI Agents
In packages/common/src/local-first/Evolu.ts around lines 936 to 944, the call to
dbWorker.postMessage that sends the "mutate" payload is commented out, so
mutations (insert/update/upsert) are not being persisted; restore the
postMessage call and send the complete payload (type: "mutate", tabId:
getTabId(), changes, onCompleteIds, subscribedQueries: subscribedQueries.get()),
ensuring dbWorker is defined before calling and wrap the postMessage in a
try/catch to log or handle failures so persistence errors don't silently fail.

Comment on lines 1044 to 1049
exportDatabase: () => {
const { promise, resolve } =
Promise.withResolvers<Uint8Array<ArrayBuffer>>();
const onCompleteId = exportCallbacks.register(resolve);
dbWorker.postMessage({ type: "export", onCompleteId });
const _onCompleteId = exportCallbacks.register(resolve);
// dbWorker.postMessage({ type: "export", onCompleteId });
return promise;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

exportDatabase nikdy nevrátí výsledek.

Stejný problém jako u loadQuery - dbWorker.postMessage je zakomentováno, takže promise nikdy nebude resolved.

🤖 Prompt for AI Agents
In packages/common/src/local-first/Evolu.ts around lines 1044 to 1049,
exportDatabase never resolves because the dbWorker.postMessage call is commented
out and the registered callback id is stored in _onCompleteId (but the message
used onCompleteId); uncomment and send the export message to the worker using
the correct id: call dbWorker.postMessage({ type: "export", onCompleteId:
_onCompleteId }) (or rename the variable to onCompleteId to match existing code)
so the worker receives the request and the registered resolve is invoked.

Comment on lines +1089 to 1091
for (const [_owner, _use] of result) {
// dbWorker.postMessage({ type: "useOwner", owner, use });
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

useOwner neodešle zprávu workeru.

Zakomentované dbWorker.postMessage znamená, že owners nebudou synchronizováni.

🤖 Prompt for AI Agents
In packages/common/src/local-first/Evolu.ts around lines 1089 to 1091, the
dbWorker.postMessage call is commented out so the worker never receives the
"useOwner" message and owners won't be synchronized; restore that call
(uncomment it) and ensure it sends the correct data (use the owner and use
values from the loop, not the underscored placeholders) and that dbWorker is
defined in this scope, e.g. dbWorker.postMessage({ type: "useOwner", owner:
_owner, use: _use }); so the worker gets the expected payload for
synchronization.

* correct format for OwnerId extraction.
*
* ### Example
* ## Example
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

Opravte úroveň nadpisu v dokumentaci.

Nadpis by měl být ### Example místo ## Example podle pravidel projektu: "Use ### Example instead of @example in JSDoc - for better markdown rendering and consistency".

🔎 Navrhovaná oprava
-   * ## Example
+   * ### Example

Based on coding guidelines.

📝 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
* ## Example
* ### Example
🤖 Prompt for AI Agents
In packages/common/src/local-first/Relay.ts around line 69, the JSDoc heading
uses "## Example" but the project requires "### Example"; update the
documentation comment to change the heading from "## Example" to "### Example"
to comply with the guideline "Use `### Example` instead of `@example` in JSDoc".

@miccy miccy changed the title Dev PR: Merge upstream Common v8 branch plus my fixes Dec 23, 2025
@miccy miccy marked this pull request as draft December 23, 2025 22:41
- Fix Uint8Array<ArrayBuffer> -> Uint8Array (not a generic type)
- Disable conflicting Biome auto-actions in VSCode
- Keep Prettier for formatting, Biome for linting only

This resolves TypeScript errors and VSCode warnings while maintaining
compatibility with upstream formatting. Full Biome migration planned
for dev-bun branch.

Fixes 3 TypeScript type errors in Evolu.ts
@miccy miccy closed this Dec 24, 2025
@miccy miccy deleted the dev branch December 24, 2025 00:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file feature New feature or request fix Bugfixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants