Conversation
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.
…562f8 [Snyk] Security upgrade next from 16.0.7 to 16.0.10
Update issue templates
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Note Other AI code review bot(s) detectedCodeRabbit 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. WalkthroughRefaktoruje 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
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
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
Comment |
|
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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.
|
…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>
There was a problem hiding this comment.
💡 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".
| 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, | ||
| // }); |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
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### Examplenamíste## Examples.Příručka kodování specifikuje: "Use
### Exampleinstead of@examplein JSDoc - for better markdown rendering and consistency". Všechny tři JSDoc bloky (jsonArrayFromna řádku 24,jsonObjectFromna řádku 83,jsonBuildObjectna řádku 138) používají## Examples, ale správný formát je### Example(tři haše, jednotné číslo).- * ## Examples + * ### Exampleapps/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íreadonlymodifikátorů pro neměnnost.Podle coding guidelines by definice typů měly preferovat neměnnost použitím
readonlyvlastností.🔎 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žijteReadonlyArraypro neměnnost pole.Podle coding guidelines by pole mělo být typováno jako
ReadonlyArray<Feature>neboreadonly 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
functionpro 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íhoshiftArrayzpůsobuje selhání buildu.Modul
./Array.jsneexportujeshiftArray. Funkcionalita existuje pod názvemshiftFromArray.Opravte import a všechna použití:
- Řádek 1: Změňte
shiftArraynashiftFromArray- Řádky 655, 692: Aktualizujte volání na
shiftFromArraypackages/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:
usingstatement 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:
- Build time: MDX obsah →
JSON.stringify()→ injekce do šablony- Runtime:
search()vrací{ url, title, pageTitle }- UI:
Search.tsxrenderuje pomocíreact-highlight-wordsOvěřte, že komponenta
HighlightervSearch.tsx(řádky 155-161) správně escapuje HTML entity. Knihovnareact-highlight-wordsby 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
⛔ Files ignored due to path filters (2)
examples/react-electron/pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlpnpm-lock.yamlis 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.prettierignoreREADME.mdapps/relay/package.jsonapps/relay/src/index.tsapps/web/package.jsonapps/web/scripts/fix-api-reference.mtsapps/web/src/app/(docs)/docs/conventions/page.mdxapps/web/src/app/(docs)/docs/dependency-injection/page.mdxapps/web/src/app/(docs)/docs/library/page.mdxapps/web/src/app/(docs)/docs/local-first/page.mdxapps/web/src/app/(docs)/docs/page.mdxapps/web/src/app/(docs)/docs/privacy/page.mdxapps/web/src/app/(docs)/docs/relay/page.mdxapps/web/src/app/(docs)/docs/resource-management/page.mdxapps/web/src/app/(landing)/blog/the-copy-paste-typescript-standard-library/page.mdxapps/web/src/app/(landing)/blog/you-might-not-need-comlink/page.mdxapps/web/src/app/(playgrounds)/playgrounds/full/EvoluFullExample.tsxapps/web/src/app/(playgrounds)/playgrounds/minimal/EvoluMinimalExample.tsxapps/web/src/app/(playgrounds)/playgrounds/multitenant/EvoluMultitenantExample.tsxapps/web/src/app/(playgrounds)/playgrounds/multitenant/page.tsxapps/web/src/components/Alerts.tsxapps/web/src/components/Features.tsxapps/web/src/components/Search.tsxapps/web/src/lib/navigation.tsapps/web/src/mdx/search.mjsapps/web/src/mdx/searchIndex.jsapps/web/src/styles/tailwind.cssapps/web/tsconfig.jsonapps/web/typography.mtsbiome.jsoneslint.config.mjsexamples/angular-vite-pwa/package.jsonexamples/react-electron/package.jsonexamples/react-expo/package.jsonexamples/react-nextjs/package.jsonexamples/react-vite-pwa/package.jsonexamples/svelte-vite-pwa/package.jsonexamples/vue-vite-pwa/package.jsonpackage.jsonpackages/common/README.mdpackages/common/package.jsonpackages/common/src/Array.tspackages/common/src/Assert.tspackages/common/src/BigInt.tspackages/common/src/Brand.tspackages/common/src/Buffer.tspackages/common/src/Cache.tspackages/common/src/Callbacks.tspackages/common/src/Console.tspackages/common/src/Crypto.tspackages/common/src/Eq.tspackages/common/src/Error.tspackages/common/src/Function.tspackages/common/src/Identicon.tspackages/common/src/Number.tspackages/common/src/Object.tspackages/common/src/Order.tspackages/common/src/Platform.tspackages/common/src/Polyfills.tspackages/common/src/Random.tspackages/common/src/Redacted.tspackages/common/src/Ref.tspackages/common/src/Resources.tspackages/common/src/Result.tspackages/common/src/Sqlite.tspackages/common/src/Store.tspackages/common/src/Task.tspackages/common/src/Time.tspackages/common/src/Type.tspackages/common/src/Types.tspackages/common/src/WebSocket.tspackages/common/src/Worker.tspackages/common/src/index.tspackages/common/src/local-first/Db.tspackages/common/src/local-first/Error.tspackages/common/src/local-first/Evolu.tspackages/common/src/local-first/Owner.tspackages/common/src/local-first/Platform.tspackages/common/src/local-first/Protocol.tspackages/common/src/local-first/Public.tspackages/common/src/local-first/PublicKysely.tspackages/common/src/local-first/Query.tspackages/common/src/local-first/Relay.tspackages/common/src/local-first/Schema.tspackages/common/src/local-first/SharedWorker.tspackages/common/src/local-first/Storage.tspackages/common/src/local-first/Sync.tspackages/common/src/local-first/Timestamp.tspackages/common/src/local-first/index.tspackages/common/test/Array.test.tspackages/common/test/Brand.test.tspackages/common/test/Crypto.test.tspackages/common/test/Error.test.tspackages/common/test/Function.test.tspackages/common/test/Polyfills.test.tspackages/common/test/Ref.test.tspackages/common/test/Result.test.tspackages/common/test/Sqlite.test.tspackages/common/test/Store.test.tspackages/common/test/_deps.tspackages/common/test/local-first/Db.test.tspackages/common/test/local-first/Evolu.test.tspackages/common/test/local-first/Protocol.test.tspackages/common/test/local-first/Timestamp.test.tspackages/common/typedoc.jsonpackages/nodejs/README.mdpackages/nodejs/package.jsonpackages/nodejs/src/Error.tspackages/nodejs/src/Worker.tspackages/react-native/README.mdpackages/react-native/package.jsonpackages/react-native/src/Worker.tspackages/react-native/src/createExpoDeps.tspackages/react-native/src/exports/bare-op-sqlite.tspackages/react-native/src/index.tspackages/react-native/src/shared.tspackages/react-web/README.mdpackages/react-web/package.jsonpackages/react-web/src/components/index.tspackages/react-web/src/index.tspackages/react/README.mdpackages/react/package.jsonpackages/react/src/createUseEvolu.tspackages/react/src/useQuery.tspackages/svelte/README.mdpackages/svelte/package.jsonpackages/svelte/src/lib/index.svelte.tspackages/vue/README.mdpackages/vue/package.jsonpackages/vue/src/createUseEvolu.tspackages/vue/src/useQuery.tspackages/web/README.mdpackages/web/package.jsonpackages/web/src/Error.tspackages/web/src/Platform.tspackages/web/src/SharedWebWorker.tspackages/web/src/WebWorker.tspackages/web/src/Worker.tspackages/web/src/index.tspackages/web/src/local-first/Db.worker.tspackages/web/src/local-first/Evolu.tspackages/web/src/local-first/LocalAuth.tspackages/web/src/local-first/Platform.tspackages/web/src/local-first/SharedWorker.worker.tspackages/web/src/local-first/index.tspackages/web/test/SharedWebWorker.test.tspnpm-workspace.yamlprettier.config.mjsscripts/publish-local.mtsscripts/toggle-examples-deps.mtstypedoc.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 thefunctionkeyword for consistency, except for function overloads where TypeScript requires thefunctionkeyword
Favor immutability - usereadonlyproperties andReadonlyArray/NonEmptyReadonlyArrayin type definitions
Avoid@paramand@returntags in JSDoc - TypeScript provides type information, focus on describing the function's purpose instead
Use### Exampleinstead of@examplein JSDoc - for better markdown rendering and consistency
Use@experimentaltag for new APIs that may change or be removed in minor/patch versions without breaking semver
UseResult<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
UseResult<void, E>for operations that don't return values
UsetrySyncfor wrapping synchronous unsafe code andtryAsyncfor wrapping asynchronous unsafe code
UsegetOrThrowonly 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 extendingTypeError<Name>
Create Type factories usingbrand,transform,array,objectand 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.tspackages/common/src/Identicon.tspackages/common/src/Console.tspackages/common/src/local-first/Error.tspackages/common/test/Ref.test.tspackages/common/src/Number.tsapps/web/src/components/Features.tsxpackages/common/src/Assert.tspackages/common/src/Random.tspackages/common/src/Callbacks.tspackages/common/src/Object.tspackages/common/src/Brand.tspackages/common/src/local-first/Query.tspackages/common/src/Buffer.tspackages/common/test/Error.test.tsapps/web/src/app/(playgrounds)/playgrounds/multitenant/page.tsxpackages/common/src/local-first/PublicKysely.tspackages/common/test/Sqlite.test.tspackages/common/src/Resources.tspackages/nodejs/src/Error.tspackages/common/test/Brand.test.tspackages/common/src/BigInt.tspackages/common/src/Platform.tspackages/common/src/local-first/SharedWorker.tspackages/common/test/local-first/Db.test.tspackages/common/test/Polyfills.test.tspackages/common/test/Function.test.tspackages/common/src/Polyfills.tspackages/common/src/Order.tspackages/common/src/Types.tsapps/web/src/components/Alerts.tsxpackages/common/src/local-first/Relay.tsapps/web/src/app/(playgrounds)/playgrounds/multitenant/EvoluMultitenantExample.tsxpackages/common/src/local-first/Timestamp.tspackages/common/src/Redacted.tspackages/nodejs/src/Worker.tsapps/relay/src/index.tspackages/common/src/Type.tspackages/common/src/WebSocket.tspackages/common/src/local-first/Owner.tspackages/common/src/Time.tspackages/common/test/_deps.tsapps/web/src/app/(playgrounds)/playgrounds/full/EvoluFullExample.tsxapps/web/src/lib/navigation.tspackages/react-native/src/Worker.tspackages/common/test/Result.test.tspackages/common/src/Ref.tspackages/common/test/local-first/Protocol.test.tspackages/common/src/Cache.tspackages/common/src/local-first/Sync.tsapps/web/src/components/Search.tsxpackages/common/src/local-first/Storage.tspackages/common/src/index.tspackages/common/src/Result.tspackages/common/src/Sqlite.tspackages/common/src/Task.tspackages/common/src/local-first/Schema.tspackages/common/test/Store.test.tspackages/common/test/local-first/Timestamp.test.tspackages/common/src/Function.tspackages/common/test/local-first/Evolu.test.tspackages/common/src/Error.tsapps/web/src/app/(playgrounds)/playgrounds/minimal/EvoluMinimalExample.tsxpackages/common/src/local-first/index.tspackages/common/src/local-first/Public.tspackages/common/src/local-first/Db.tspackages/common/src/Store.tspackages/common/test/Crypto.test.tspackages/common/src/Crypto.tspackages/common/src/Array.tspackages/common/src/local-first/Protocol.tspackages/common/test/Array.test.tspackages/common/src/local-first/Evolu.tspackages/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 frompackages/common/test/_deps.tssuch astestCreateId,testTime,testOwner
Mock dependencies using the same interfaces as production code
Create test factories likecreateTestTimefor 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.tspackages/common/test/Error.test.tspackages/common/test/Sqlite.test.tspackages/common/test/Brand.test.tspackages/common/test/local-first/Db.test.tspackages/common/test/Polyfills.test.tspackages/common/test/Function.test.tspackages/common/test/Result.test.tspackages/common/test/local-first/Protocol.test.tspackages/common/test/Store.test.tspackages/common/test/local-first/Timestamp.test.tspackages/common/test/local-first/Evolu.test.tspackages/common/test/Crypto.test.tspackages/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.tspackages/common/src/Identicon.tspackages/common/src/Number.tspackages/common/src/Assert.tspackages/common/src/Random.tspackages/common/src/Callbacks.tspackages/common/src/Brand.tspackages/common/src/local-first/Query.tspackages/common/src/Buffer.tspackages/common/src/Order.tspackages/common/src/Types.tspackages/common/src/Redacted.tspackages/common/src/Time.tsapps/web/src/app/(docs)/docs/conventions/page.mdxpackages/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.tspackages/common/src/Callbacks.tspackages/common/src/Type.ts.changeset/result-never-inference.mdpackages/common/test/Result.test.ts.github/copilot-instructions.mdpackages/common/src/Result.tspackages/common/src/Sqlite.tspackages/common/src/Error.tspackages/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.tspackages/common/src/Assert.tspackages/common/src/Brand.tspackages/common/test/Error.test.tspackages/common/src/Type.tsapps/web/src/app/(docs)/docs/conventions/page.mdx.github/copilot-instructions.mdpackages/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.tspackages/common/test/local-first/Db.test.tspackages/common/test/Polyfills.test.tspackages/common/test/Function.test.tspackages/common/src/local-first/Relay.tsapps/relay/src/index.tspackages/common/src/Type.tsapps/web/src/app/(landing)/blog/the-copy-paste-typescript-standard-library/page.mdxpackages/common/test/_deps.tspackages/common/test/Result.test.tspackages/common/test/local-first/Protocol.test.tspackages/common/typedoc.jsonpackages/common/test/Store.test.tspackages/common/test/local-first/Timestamp.test.tspackages/common/test/local-first/Evolu.test.tsapps/web/src/app/(playgrounds)/playgrounds/minimal/EvoluMinimalExample.tsxpackages/common/test/Crypto.test.tspackages/common/package.jsonpackages/common/test/Array.test.tspackages/common/src/local-first/Evolu.tsapps/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.tspackages/common/test/local-first/Db.test.tspackages/common/test/Polyfills.test.tspackages/common/test/Function.test.tspackages/common/test/Result.test.ts.github/copilot-instructions.mdpackages/common/test/Store.test.tspackages/common/test/local-first/Timestamp.test.tspackages/common/test/local-first/Evolu.test.tspackages/common/test/Array.test.tsapps/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.tspackages/common/src/Assert.tspackages/common/test/Error.test.tspackages/common/test/Polyfills.test.tspackages/common/test/Function.test.tspackages/common/src/Type.tspackages/common/test/Result.test.tspackages/common/src/Result.tspackages/common/test/local-first/Evolu.test.tspackages/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.tsapps/web/tsconfig.jsonpackages/common/test/Brand.test.tspackages/common/test/Polyfills.test.tspackages/common/test/Function.test.tspackages/common/test/_deps.tspackages/common/typedoc.jsonpackages/common/test/Store.test.tspackages/common/test/local-first/Timestamp.test.tspackages/common/test/local-first/Evolu.test.tspackages/common/package.jsonapps/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.jsonpackages/common/src/Types.ts.github/copilot-instructions.mdeslint.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.jsonpackages/common/src/Types.tspackages/common/src/Type.tspackages/common/typedoc.jsoneslint.config.mjsapps/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.jsonpackages/common/src/Assert.tspackages/common/src/Type.tsapps/web/src/app/(landing)/blog/the-copy-paste-typescript-standard-library/page.mdxapps/web/src/app/(docs)/docs/conventions/page.mdx.github/copilot-instructions.mdpackages/common/src/Result.tspackages/common/src/local-first/Public.tspackages/common/src/Array.ts.changeset/typed-discriminant.mdeslint.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.jsonpackages/common/src/Assert.tspackages/common/src/Type.tsapps/web/src/app/(landing)/blog/the-copy-paste-typescript-standard-library/page.mdxpackages/common/test/Result.test.tspackages/common/src/Result.tspackages/common/src/Array.tseslint.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.jsonpackages/common/src/Brand.tspackages/common/test/Brand.test.tspackages/common/src/Type.tsapps/web/src/app/(landing)/blog/the-copy-paste-typescript-standard-library/page.mdxapps/web/src/app/(docs)/docs/conventions/page.mdx.github/copilot-instructions.mdpackages/common/typedoc.jsonpackages/common/src/local-first/Public.tspackages/common/src/Array.ts.changeset/typed-discriminant.mdeslint.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.jsonapps/relay/src/index.tsapps/web/src/app/(playgrounds)/playgrounds/full/EvoluFullExample.tsxapps/web/src/app/(docs)/docs/conventions/page.mdx.github/copilot-instructions.mdpackages/common/typedoc.jsonapps/web/src/app/(playgrounds)/playgrounds/minimal/EvoluMinimalExample.tsxapps/relay/package.jsonpackages/common/src/local-first/Public.ts.changeset/typed-discriminant.mdeslint.config.mjspackages/common/src/local-first/Evolu.tsapps/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.jsonpackages/common/src/Brand.tspackages/common/test/Brand.test.tspackages/common/src/Types.tsapps/web/src/components/Alerts.tsxpackages/common/src/Type.tsapps/web/src/app/(landing)/blog/the-copy-paste-typescript-standard-library/page.mdxapps/web/src/app/(docs)/docs/conventions/page.mdx.github/copilot-instructions.md.changeset/typed-discriminant.mdeslint.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.jsonpackages/common/src/Assert.tspackages/common/test/Function.test.tspackages/common/src/Types.tspackages/common/src/Type.ts.changeset/array-module-refactor.mdapps/web/src/app/(docs)/docs/conventions/page.mdxpackages/common/typedoc.jsonpackages/common/src/local-first/Schema.tsapps/web/scripts/fix-api-reference.mtspackages/common/src/Function.tspackages/common/src/Store.tspackages/common/src/Array.tseslint.config.mjspackages/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.jsonapps/web/src/app/(docs)/docs/conventions/page.mdx.github/copilot-instructions.mdpackages/common/src/index.tseslint.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.jsonpackages/common/src/Resources.tspackages/common/src/local-first/Relay.tsapps/relay/src/index.tspackages/common/src/Type.tsapps/web/src/app/(landing)/blog/the-copy-paste-typescript-standard-library/page.mdxpackages/common/test/_deps.tsapps/web/src/app/(docs)/docs/conventions/page.mdx.github/copilot-instructions.mdpackages/common/typedoc.jsonapps/web/src/app/(playgrounds)/playgrounds/minimal/EvoluMinimalExample.tsxapps/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.jsonapps/web/src/app/(docs)/docs/conventions/page.mdx.github/copilot-instructions.mdpackages/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.jsonapps/web/src/app/(docs)/docs/conventions/page.mdxeslint.config.mjsapps/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.tspackages/common/src/Type.tsapps/web/src/app/(landing)/blog/the-copy-paste-typescript-standard-library/page.mdxapps/web/src/app/(docs)/docs/conventions/page.mdx.github/copilot-instructions.mdpackages/common/src/Result.tseslint.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.tspackages/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.tspackages/common/src/Callbacks.tspackages/common/src/Type.ts.changeset/result-never-inference.mdpackages/common/test/Result.test.tsapps/web/src/app/(docs)/docs/conventions/page.mdx.github/copilot-instructions.mdpackages/common/src/Result.tspackages/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.tspackages/common/src/Type.ts.changeset/result-never-inference.mdpackages/common/test/Result.test.ts.github/copilot-instructions.mdpackages/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.tspackages/common/src/Types.tspackages/common/src/Time.ts.github/copilot-instructions.mdapps/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.tspackages/common/src/Type.ts.github/copilot-instructions.mdpackages/common/src/Result.tspackages/common/src/Error.tspackages/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.tsapps/web/src/app/(docs)/docs/conventions/page.mdxapps/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.tspackages/common/test/_deps.tsapps/web/src/app/(playgrounds)/playgrounds/minimal/EvoluMinimalExample.tsxapps/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.mdpackage.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.mdpackage.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.tsapps/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.tsapps/web/src/app/(docs)/docs/conventions/page.mdx.github/copilot-instructions.mdpackages/common/src/Result.tspackages/common/src/Error.tseslint.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.mdpackages/common/src/index.tspackages/common/typedoc.jsonapps/web/src/app/(docs)/docs/page.mdxpackages/common/src/local-first/index.tspackages/common/src/local-first/Public.tseslint.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.mdapps/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.mdapps/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)
| --- | ||
| "@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. |
There was a problem hiding this comment.
🧹 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 (SymmetricCryptoDecryptError → DecryptWithXChaCha20Poly1305Error) 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.
| --- | ||
| name: Bug report | ||
| about: Create a report to help us improve | ||
| title: '' | ||
| labels: '' | ||
| assignees: '' | ||
|
|
||
| --- |
There was a problem hiding this comment.
🧹 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:
- Výchozí štítky (např.
bug,triage) pro lepší třídění problémů - Otázku na konkrétní balíček @evolu, který je ovlivněn
- 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} />; |
There was a problem hiding this comment.
🧹 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.
| 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.
| test("TODO", () => { | ||
| expect(1).toBe(1); | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's examine the actual test file
cat -n packages/common/test/local-first/Db.test.tsRepository: 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.tsRepository: 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 -50Repository: 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 -100Repository: 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.
| test("TODO", () => { | ||
| expect(1).toBe(1); |
There was a problem hiding this comment.
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.
| // 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)); | ||
| // // }); | ||
| // // }); |
There was a problem hiding this comment.
🧹 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ď:
- Obnoveno a aktualizováno pro nové API (pokud jsou testy stále relevantní), nebo
- 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.
| // 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. |
There was a problem hiding this comment.
🧹 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 |
There was a problem hiding this comment.
🧹 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:
- Změnu typu
NativeMessagePortnaNativeMessagePort | null - 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.
| @@ -0,0 +1,345 @@ | |||
| "use client"; | |||
|
|
|||
| import * as Evolu from "@evolu/common"; | |||
There was a problem hiding this comment.
🛠️ 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.
| 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; | ||
| }; |
There was a problem hiding this comment.
🧩 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.
| 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; | ||
| } |
There was a problem hiding this comment.
🧹 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.
| 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.
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (6)
apps/web/package.jsonexamples/react-nextjs/package.jsonexamples/react-vite-pwa/package.jsonexamples/svelte-vite-pwa/package.jsonpackage.jsonpackages/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/nodena 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-wasmverze 2.2.4 je aktivně používána vpackages/web/src/WasmSqliteDriver.tsa 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é vpackages/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:docskombinuje 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.catalogspro 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.
There was a problem hiding this comment.
Actionable comments posted: 3
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (2)
examples/vue-vite-pwa/package.jsonpackages/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.
| "vue": "^3.5.25", | ||
| "workbox-window": "^7.4.0" |
There was a problem hiding this comment.
🧩 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" |
There was a problem hiding this comment.
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:
- Node.js 24 je skutečně požadován pro nové funkce v kódu
- Uživatelé a vývojáři byli informováni o této změně
- CI/CD konfigurace byla aktualizována
When was Node.js 24 released and is it considered stable for production use?
| "sideEffects": [ | ||
| "./dist/src/index.js" | ||
| ] |
There was a problem hiding this comment.
🧹 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.
There was a problem hiding this comment.
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:loadQuerynikdy nevrátí výsledky.Volání
dbWorker.postMessageje zakomentováno na řádcích 965-969, ale promises se vytvářejí a vracejí volajícímu. To znamená:
loadQueryvrátí promise, která nikdy nebude resolvedsubscribeQuerynebude fungovat- Jakýkoli kód používající
evolu.loadQuery()nebo React Suspense suse(loadQuery(...))se zasekneToto 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 exportucreateQuery.Komentář naznačuje, že
createQueryby nemělo být exportováno, ale je kvůli testům. Zvažte vytvoření testovacího entry pointu nebo použití@internalJSDoc 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
📒 Files selected for processing (11)
.changeset/gentle-pumas-eat.md.nvmrcpackages/common/src/Task.tspackages/common/src/WebSocket.tspackages/common/src/local-first/Evolu.tspackages/common/src/local-first/Protocol.tspackages/common/src/local-first/Relay.tspackages/common/src/local-first/Storage.tspackages/common/src/local-first/Sync.tspackages/common/test/WebSocket.test.tspackages/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 thefunctionkeyword for consistency, except for function overloads where TypeScript requires thefunctionkeyword
Favor immutability - usereadonlyproperties andReadonlyArray/NonEmptyReadonlyArrayin type definitions
Avoid@paramand@returntags in JSDoc - TypeScript provides type information, focus on describing the function's purpose instead
Use### Exampleinstead of@examplein JSDoc - for better markdown rendering and consistency
Use@experimentaltag for new APIs that may change or be removed in minor/patch versions without breaking semver
UseResult<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
UseResult<void, E>for operations that don't return values
UsetrySyncfor wrapping synchronous unsafe code andtryAsyncfor wrapping asynchronous unsafe code
UsegetOrThrowonly 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 extendingTypeError<Name>
Create Type factories usingbrand,transform,array,objectand 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.tspackages/common/src/Task.tspackages/common/src/local-first/Storage.tspackages/common/src/local-first/Relay.tspackages/common/src/local-first/Sync.tspackages/common/src/local-first/Protocol.tspackages/common/src/local-first/Evolu.tspackages/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 frompackages/common/test/_deps.tssuch astestCreateId,testTime,testOwner
Mock dependencies using the same interfaces as production code
Create test factories likecreateTestTimefor 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.tspackages/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.tspackages/common/src/local-first/Sync.tspackages/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.jsonv 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í
LazyValuenaLazyodpovídá aktuální definici typu vFunction.ts- Přejmenování
isNonEmptyReadonlyArraynaisNonEmptyArrayodpovídá aktuálnímu názvu funkce vArray.ts- Použití explicitního klíčového slova
typeproMaybeAsyncaMutexzlepšuje tree-shaking
143-143: Konzistentní refaktoring kontrolních funkcí.Použití
isNonEmptyArraymístoisNonEmptyReadonlyArrayje v souladu s přejmenováním v moduluArray.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>naLazy<number>je konzistentní s přejmenováním typu vFunction.tsa 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
SymmetricCryptoDepnaRandomBytesDepa použití nových XChaCha20-Poly1305 funkcí je konzistentní s celkovým refaktoringem kryptografické vrstvy v tomto PR. ImportisNonEmptyArraynahrazujeisNonEmptyReadonlyArrayv souladu se změnami vArray.ts.
459-461: Změna závislosti naRandomBytesDepje správná.Funkce
createProtocolMessageFromCrdtMessagesnyní závisí pouze naRandomBytesDepmístoSymmetricCryptoDep, což odpovídá refaktoringu kryptografické vrstvy. Šifrování je delegováno naencodeAndEncryptDbChange, které interně používáencryptWithXChaCha20Poly1305.
1617-1648: Výborná validace vdecodeRlepro prevenci DoS útoků.Nová funkce
decodeRlesprávně validuje:
runLength === 0- prevence nekonečné smyčkyrunLength > remaining- prevence CPU/memory amplifikaceToto je důležitá bezpečnostní ochrana proti škodlivým vstupům.
1790-1859: Refaktorovaná funkcedecryptAndDecodeDbChangevypadá správně.Změna signatury z curried formy
(deps) => (message, key)na přímou(message, key)je v pořádku. Funkce správně:
- Extrahuje nonce a ciphertext
- Dešifruje pomocí
decryptWithXChaCha20Poly1305- Validuje timestamp proti očekávanému
- Vrací
Results odpovídajícími chybovými typyOšetření chyb pomocí try-catch s návratem
ProtocolInvalidDataErrorje konzistentní s ostatními dekódovacími funkcemi.
1692-1693: JSDoc příklady používají správný formát### Example.Změna z
## Examplena standardní formát je v souladu s coding guidelines: "Use### Exampleinstead of@examplein JSDoc".Also applies to: 1714-1715
1233-1247: Optimalizace pre-alokace pole vdecodeMessages.Změna z
pushna 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íisNonEmptyArraymístoisNonEmptyReadonlyArrayodpovídá unifikaci vArray.ts.
208-211: SymbolInfiniteUpperBoundmá nyní plně kvalifikovaný název.Přejmenování z
Symbol("InfiniteUpperBound")naSymbol("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: LogikafingerprintRangessprávně odvozujeupperBoundpro každý řádek.Kód nyní korektně:
- Pro všechny řádky kromě posledního používá
row.bjakoupperBound- Pro poslední řádek používá předaný
upperBoundparametr- Assertion
row.b !== nullzajišťuje, že neexistují null hodnoty pro ne-poslední řádkyToto je bezpečnější implementace než předchozí verze.
1671-1671: PoužitíisNonEmptyArrayje v pořádku.Funkce
isNonEmptyArrayvArray.tsmá overloady proReadonlyArray<T>iArray<T>, takže tato změna je zpětně kompatibilní.packages/common/src/local-first/Sync.ts (6)
139-150: Aktualizované typy chyb vSyncConfig.onErrorjsou správné.Změna z
SymmetricCryptoDecryptErrornaDecryptWithXChaCha20Poly1305Errora zTransferableErrornaUnknownErrorodpovídá refaktoringu kryptografické a chybové vrstvy v tomto PR.
155-167: ZávislosticreateSyncaktualizovány správně.Nahrazení
SymmetricCryptoDepzaRandomBytesDepje konzistentní s celkovým refaktoringem. Šifrovací operace nyní používají XChaCha20-Poly1305 API interně.
264-266: PoužitícreateUnknownErrorje správné.Nahrazení
createTransferableError(error)zacreateUnknownError(error)v catch bloku je v souladu se změnami vError.ts.createUnknownErrorposkytuje robustní serializaci různých typů chyb.
513-516: VolánídecryptAndDecodeDbChangeaktualizováno správně.Změna z curried formy
decryptAndDecodeDbChange(deps)(message, key)na přímoudecryptAndDecodeDbChange(message, key)odpovídá nové signatuře funkce vProtocol.ts.
538-538: PoužitíisNonEmptyArraymístoisNonEmptyReadonlyArrayje v pořádku.Funkce
isNonEmptyArraypodporuje jakReadonlyArray, takArraydíky přetížení vArray.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éEvoluConfigrozhraní je dobře zdokumentováno.Konfigurace obsahuje jasnou dokumentaci pro
name,transports,externalAppOwnerainMemorys praktickými příklady. Toto zlepšuje developer experience.
579-620: Nové typy závislostí acreateEvoluDepshelper jsou dobře navrženy.
EvoluPlatformDeps,EvoluDepsaErrorStoreDepná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í obsahujenameproperty.Přidání
name: SimpleNamedo rozhraníEvoluje 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
RetryOptionszlepš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
onmessagehandleru 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
onRetrycallback 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í
shiftArraynashiftFromArrayje konzistentní s úpravami v Array modulu- Type-only import pro
Resultzlepšuje tree-shaking
20-31: Vynikající zlepšení dokumentace!Aktualizace dokumentace správně:
- Přidává odkazy na MDN pro
AbortSignalaAbortController- 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 proAbortSignal.any().Implementace
combineSignalpoužívá type cast pro přístup kAbortSignal.any(), což je potřebné, protože TypeScript DOM types nemusí obsahovat nejnovější API. Vzhledem k tomu, že minimum je Node.js 24 (obsahujeAbortSignal.any()od verze 20.3.0), je runtime chování v pořádku.
672-672: Konzistentní použití přejmenované funkce!Použití
shiftFromArrayje 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í deklaracePromise.withResolversse 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.withResolversnativní 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 vexamples/react-expo/polyfills.tspro React Native/Hermes prostředí.
| # Update Node.js requirement | ||
|
|
||
| Updated minimum Node.js version from 22 to 24 (current LTS) |
There was a problem hiding this comment.
🧹 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.
| // TODO: | ||
| // dbWorker.postMessage({ | ||
| // type: "mutate", | ||
| // tabId: getTabId(), | ||
| // changes, | ||
| // onCompleteIds, | ||
| // subscribedQueries: subscribedQueries.get(), | ||
| // }); | ||
| }; |
There was a problem hiding this comment.
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.
| 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; |
There was a problem hiding this comment.
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.
| for (const [_owner, _use] of result) { | ||
| // dbWorker.postMessage({ type: "useOwner", owner, use }); | ||
| } |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
+ * ### ExampleBased 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.
| * ## 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".
- 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
Description
Type of Change
Related Issues
Checklist
Summary by CodeRabbit
Nové funkce
Zásadní změny
Dokumentace
✏️ Tip: You can customize this high-level summary in your review settings.