diff --git a/docs/core/concepts/performance.md b/docs/core/concepts/performance.md index 61e7310089bb..607337c3d144 100644 --- a/docs/core/concepts/performance.md +++ b/docs/core/concepts/performance.md @@ -11,13 +11,12 @@ import useBaseUrl from '@docusaurus/useBaseUrl'; -[Normalized caching](./normalization.md) with entity-level memoization enables +In addition to the data integirty benefits, [normalized caching](./normalization.md) with entity-level memoization enables significant performance gains for rich interactive applications. - ## React rendering benchmarks -Full rendering pipeline (fetch through paint) measured in a real browser via Playwright. +Full rendering pipeline (fetch through DOM commit) measured in a real browser via Playwright. React baseline uses useEffect + useState from the React docs.
@@ -39,7 +38,10 @@ sources={{ - **Mutation Propagation**: One store write updates every view that references the entity. - **Scaling**: Mutations with 10k items in the list rendered. - +These benchmarks measure the framework's impact within the larger system. That +makes them most useful as comparisons between approaches, rather than as +absolute measurements of an application's overall performance. We use them to +guide library optimizations and catch performance regressions over time. ## Normalization benchmarks diff --git a/examples/benchmark-react/AGENTS.md b/examples/benchmark-react/AGENTS.md new file mode 100644 index 000000000000..ac3d67d43874 --- /dev/null +++ b/examples/benchmark-react/AGENTS.md @@ -0,0 +1,75 @@ +# React Rendering Benchmark + +Browser benchmark comparing `@data-client/react`, TanStack Query, SWR, and plain React. Webpack build, Playwright runner. See `README.md` for methodology and running instructions. + +## Build & Run + +```bash +yarn build:benchmark-react # from repo root +yarn workspace example-benchmark-react preview & # serve dist/ on port 5173 +cd examples/benchmark-react && yarn bench # all libs (local) or data-client only (CI) +``` + +Filtering: `yarn bench --lib data-client --size small --action update` + +## Architecture + +**Runner → `window.__BENCH__` → React**: `bench/runner.ts` opens `localhost:5173//` in Playwright, calls `BenchAPI` methods on `window.__BENCH__`, waits for `[data-bench-complete]` attribute, then collects `performance.measure` entries. This is the only runner↔app channel. + +**Web Worker server**: All "network" goes to an in-memory Worker (`server.worker.ts` via `server.ts` RPC) with configurable latency. Keeps fake-server work off main thread. + +**Shared vs library-specific**: `src/shared/` (harness, components, fixtures, resources, server) is identical across all apps. Each `src//index.tsx` only contains data-layer wiring. Divergence from shared code breaks fairness. + +**Webpack multi-entry**: `webpack.config.cjs` produces four apps at `dist//index.html`. `@shared` path alias configured in Webpack + `tsconfig.json`. + +## Key Design Decisions + +- **MutationObserver timing**: `measureMount`/`measureUpdate` in `benchHarness.tsx` use `MutationObserver` on `[data-bench-harness]`, not React lifecycle. Mount waits for `[data-bench-item]`/`[data-sorted-list]`. Update triggers on first mutation batch, or waits for `isReady` predicate on multi-phase updates. +- **Proxy API**: `window.__BENCH__` is a `Proxy` → `apiRef.current`. `registerAPI` merges library actions with shared defaults. Methods always reflect current React state; adding new `BenchAPI` methods needs no registration boilerplate. +- **renderLimit**: Update scenarios store 1000 items but render only 100 — isolates cache-propagation cost from DOM reconciliation. +- **Expensive UserView**: `components.tsx` `UserView` does deliberate hash/string/date work. Libraries preserving referential equality skip it on unrelated updates; others pay per row. +- **BenchGCPolicy**: data-client's custom `GCPolicy` — zero expiry, no interval timer. Prevents GC during timing; `sweep()` called explicitly for memory scenarios. + +## Scenario System + +`BASE_SCENARIOS` in `bench/scenarios.ts` × `LIBRARIES` via `flatMap`. `onlyLibs` restricts to specific libs. CI runs data-client hot-path only (no memory/startup/deterministic). Memory is opt-in locally (`--action memory`). Convergent timing uses single page load with adaptive iterations and early stopping on statistical convergence. Ref-stability scenarios run once (deterministic count, not ops/s). + +## Update Data Flow + +1. Runner calls `window.__BENCH__.updateEntity(1)` +2. `measureUpdate` marks `update-start`, invokes action, `MutationObserver` detects DOM change, marks `update-end` + sets `data-bench-complete` +3. Runner reads `performance.measure('update-duration')` +4. **Core asymmetry**: data-client propagates via one store write; TanStack Query/SWR/baseline invalidate + re-fetch from Worker + +## Adding / Modifying + +**New scenario**: Add to `BASE_SCENARIOS` → add action to `BenchAPI` in `types.ts` if new → implement in each `src//index.tsx` (or use `onlyLibs`) → set `preMountAction`/`mountCount` if setup needed. + +**New library**: `src//index.tsx` using `registerAPI` → add to `LIBRARIES` in `scenarios.ts` → webpack entry + `HtmlWebpackPlugin` → `package.json` dep. + +**Shared components**: Changes to `components.tsx` or `resources.ts` shift all four libraries equally (by design). + +## Data Attributes + +| Attribute | Flow | Purpose | +|---|---|---| +| `data-app-ready` | harness → runner | `__BENCH__` available | +| `data-bench-harness` | lib → runner | Container for MutationObserver | +| `data-bench-complete` | harness → runner | Iteration finished | +| `data-bench-timeout` | harness → runner | 30s timeout (error) | +| `data-bench-item` | components → harness | Mount detection | +| `data-sorted-list` | lib views → harness | Sorted-view mount detection | +| `data-detail-view` | lib views → harness | Multi-view detection | +| `data-issue-number` | components → runner/harness | Item identity assertion | +| `data-title` | components → lib views | Text content assertion | +| `data-state-list` | lib views → harness | Move-item verification | + +## Environment Variables + +| Variable | Effect | +|---|---| +| `CI` | data-client hot-path only; tighter convergence | +| `REACT_COMPILER=false` | Disables React Compiler at build | +| `BENCH_LABEL=` | Appends `[]` to result names | +| `BENCH_PORT` | Preview port (default 5173) | +| `BENCH_TRACE=true` | Chrome tracing for duration scenarios | diff --git a/examples/benchmark-react/README.md b/examples/benchmark-react/README.md index 234b462e90c5..24c331cd9950 100644 --- a/examples/benchmark-react/README.md +++ b/examples/benchmark-react/README.md @@ -1,25 +1,33 @@ # React Rendering Benchmark -Browser-based benchmark comparing `@data-client/react`, TanStack Query, and SWR on mount/update scenarios. Built with Webpack via `@anansi/webpack-config`. Results are reported to CI via `rhysd/github-action-benchmark`. +Browser-based benchmark for `@data-client/react` measuring mount/update scenarios. Includes TanStack Query, SWR, and a plain-React baseline for reference. Built with Webpack via `@anansi/webpack-config`. Results are reported to CI via `rhysd/github-action-benchmark`. ## Comparison to Node benchmarks The repo has two benchmark suites: - **`examples/benchmark`** (Node) — Measures the JS engine only: `normalize`/`denormalize`, `Controller.setResponse`/`getResponse`, reducer throughput. No browser, no React. Use it to validate core and normalizr changes. -- **`examples/benchmark-react`** (this app) — Measures the full React rendering pipeline: same operations driven in a real browser, with layout and paint. Use it to validate `@data-client/react` and compare against other data libraries. +- **`examples/benchmark-react`** (this app) — Measures the full React rendering pipeline: same operations driven in a real browser, with layout and paint. Use it to validate `@data-client/react` changes; other libraries are included for reference. ## Methodology - **What we measure:** Wall-clock time from triggering an action (e.g. `init(100)` or `updateUser('user0')`) until a MutationObserver detects the expected DOM change in the benchmark container. Optionally we also record React Profiler commit duration and, with `BENCH_TRACE=true`, Chrome trace duration. -- **Why:** Normalized caching should show wins on shared-entity updates (one store write, many components update), ref stability (fewer new object references), and derived-view memoization (`Query` schema avoids re-sorting when entities haven't changed). See [js-framework-benchmark "How the duration is measured"](https://github.com/krausest/js-framework-benchmark/wiki/How-the-duration-is-measured) for a similar timeline-based approach. +- **Why:** Scenarios are chosen to exercise areas where caching strategies differ: shared-entity updates, referential stability, and derived-view memoization. See [js-framework-benchmark "How the duration is measured"](https://github.com/krausest/js-framework-benchmark/wiki/How-the-duration-is-measured) for a similar timeline-based approach. - **Statistical:** Warmup runs are discarded; we report median and 95% CI (as percentage of median). Timing scenarios (navigation and mutation) use **convergent mode**: a single page load per scenario, with warmup iterations followed by adaptive measurement iterations where each iteration produces one sample and convergence is checked inline. This eliminates page-reload overhead between samples for faster, lower-variance results. Deterministic scenarios (ref-stability) run once. Memory scenarios use a separate outer loop with a fresh page per round. - **No CPU throttling:** Runs at native speed with more samples for statistical significance rather than artificial slowdown. Convergent timing scenarios use 5 warmup + up to 50 measurement iterations (small) or 3 warmup + up to 40 (large). Early stopping triggers when 95% CI margin drops below the target percentage. +## Comparison philosophy + +The primary purpose is to track data-client's own performance — catch regressions and validate improvements. Other libraries are included for context; CI runs data-client only. + +Scenarios are designed to isolate the data framework layer: fetching, caching, update propagation, and rendering in response to data changes. Real-world applications will have additional performance considerations (routing, animation, third-party scripts, etc.) beyond what is measured here. + +All implementations share presentational components, fixture data, fetch functions, and the `useBenchState` harness. They only diverge where each library's data layer requires it, using idiomatic patterns from that library's documentation. No implementation builds custom state management on top of its library. + ## Scenario categories -- **Hot path (in CI, data-client only)** — JS-only: init (fetch + render), update propagation, ref-stability, sorted-view. No simulated network. CI runs only `data-client` scenarios to track our own regressions; competitor libraries are benchmarked locally for comparison. -- **With network (local comparison)** — Same shared-author update but with simulated network delay (consistent ms per "request"). Used to compare overfetching: data-client needs one store update (1 × delay); non-normalized libs typically invalidate/refetch multiple queries (N × delay). **Not run in CI** — run locally with `yarn bench` (no `CI` env) to include these. +- **Hot path (in CI, data-client only)** — JS-only: init (fetch + render), update propagation, ref-stability, sorted-view. No simulated network. CI runs only data-client scenarios to track regressions; other libraries are benchmarked locally. +- **With network (local)** — Same shared-author update but with simulated network delay (consistent ms per "request"). Normalized caches propagate via a single store update; query-keyed caches invalidate and refetch affected queries. **Not run in CI** — run locally with `yarn bench` (no `CI` env) to include these. - **Memory (local only)** — Heap delta after repeated mount/unmount cycles. - **Startup (local only)** — FCP and task duration via CDP `Performance.getMetrics`. @@ -28,17 +36,17 @@ The repo has two benchmark suites: **Hot path (CI)** - **Get list** (`getlist-100`, `getlist-500`) — Time to show a ListView component that auto-fetches 100 or 500 issues from the list endpoint, then renders (unit: ops/s). Exercises the full fetch + normalization + render pipeline. -- **Get list sorted** (`getlist-500-sorted`) — Mount 500 issues through a sorted/derived view. data-client uses `useQuery(sortedIssuesQuery)` with `Query` schema memoization; competitors use `useMemo` + sort. +- **Get list sorted** (`getlist-500-sorted`) — Mount 500 issues through a sorted/derived view. data-client uses `useQuery(sortedIssuesQuery)` with `Query` schema memoization; other libraries use `useMemo` + sort. - **Update entity** (`update-entity`) — Time to update one issue and propagate to the UI (unit: ops/s). - **Update entity sorted** (`update-entity-sorted`) — After mounting a sorted view, update one entity. data-client's `Query` memoization avoids re-sorting when sort keys are unchanged. -- **Update entity multi-view** (`update-entity-multi-view`) — Update one issue that appears simultaneously in a list, a detail panel, and a pinned-cards strip. Exercises cross-query entity propagation: normalized cache updates once and all three views reflect the change; non-normalized libraries must invalidate and refetch each query independently. -- **Update user (scaling)** (`update-user`, `update-user-10000`) — Update one shared user with 1,000 or 10,000 mounted issues to test subscriber scaling. Normalized cache: one store update, all views of that user update. -- **Ref-stability** (`ref-stability-issue-changed`, `ref-stability-user-changed`) — Count of components that received a **new** object reference after an update (unit: count; smaller is better). Normalization keeps referential equality for unchanged entities. +- **Update entity multi-view** (`update-entity-multi-view`) — Update one issue that appears simultaneously in a list, a detail panel, and a pinned-cards strip. Normalized caches propagate via a single store write; query-keyed caches invalidate and refetch each query. +- **Update user (scaling)** (`update-user`, `update-user-10000`) — Update one shared user with 1,000 or 10,000 mounted issues to test subscriber scaling. +- **Ref-stability** (`ref-stability-issue-changed`, `ref-stability-user-changed`) — Count of components that received a **new** object reference after an update (unit: count; smaller is better). - **Invalidate and resolve** (`invalidate-and-resolve`) — data-client only; invalidates a cached endpoint and immediately re-resolves. Measures Suspense boundary round-trip. **With network (local comparison)** -- **Update shared user with network** (`update-shared-user-with-network`) — Same as above with a simulated delay (e.g. 50 ms) per "request." data-client propagates via normalization (no extra request); other libs invalidate/refetch the list endpoint. +- **Update shared user with network** (`update-shared-user-with-network`) — Same as above with a simulated delay (e.g. 50 ms) per "request." **Memory (local only)** @@ -99,7 +107,7 @@ Regressions >5% on stable scenarios or >15% on volatile scenarios are worth inve ## Interpreting results - **Higher is better** for throughput (ops/s). **Lower is better** for ref-stability counts and heap delta (bytes). -- **Ref-stability:** data-client's normalized cache keeps referential equality for unchanged entities, so `issueRefChanged` and `userRefChanged` should stay low. Non-normalized libs typically show higher counts because they create new object references for every cache write. +- **Ref-stability:** `issueRefChanged` and `userRefChanged` count how many components received a new object reference. Normalized caches preserve referential equality for unchanged entities; query-keyed caches typically create new references on each cache write. - **React commit:** Reported as `(react commit)` suffix entries. These measure React Profiler `actualDuration` and isolate React reconciliation cost from layout/paint. - **Report viewer:** Toggle the "Base metrics", "React commit", and "Trace" checkboxes to filter the comparison table. Use "Load history" to compare multiple runs over time. diff --git a/examples/benchmark-react/package.json b/examples/benchmark-react/package.json index 6e664820d728..f6ce7ac185d1 100644 --- a/examples/benchmark-react/package.json +++ b/examples/benchmark-react/package.json @@ -42,7 +42,7 @@ "playwright": "1.58.2", "serve": "14.2.6", "tsx": "4.21.0", - "typescript": "6.0.1-rc", + "typescript": "6.0.2", "webpack": "5.105.4", "webpack-cli": "6.0.1" }, diff --git a/examples/benchmark-react/src/baseline/index.tsx b/examples/benchmark-react/src/baseline/index.tsx index 4a5185f34e55..61085bbba25d 100644 --- a/examples/benchmark-react/src/baseline/index.tsx +++ b/examples/benchmark-react/src/baseline/index.tsx @@ -17,14 +17,7 @@ import { sortByTitle, } from '@shared/data'; import { setCurrentIssues } from '@shared/refStability'; -import { - fetchIssue, - fetchIssueList, - updateIssue, - updateUser as serverUpdateUser, - createIssue, - deleteIssue, -} from '@shared/server'; +import { IssueResource, UserResource } from '@shared/resources'; import type { Issue } from '@shared/types'; import React, { useCallback, @@ -45,7 +38,7 @@ function SortedListView({ }) { const [issues, setIssues] = useState(null); useEffect(() => { - fetchIssueList().then(setIssues); + IssueResource.getList().then(setIssues); }, [refetchKey]); const sorted = useMemo(() => (issues ? sortByTitle(issues) : []), [issues]); if (!sorted.length) return null; @@ -65,7 +58,7 @@ function DetailView({ }) { const [issue, setIssue] = useState(null); useEffect(() => { - fetchIssue({ number }).then(setIssue); + IssueResource.get({ number }).then(setIssue); }, [number, refetchKey]); if (!issue) return null; return ( @@ -84,7 +77,7 @@ function PinnedCard({ }) { const [issue, setIssue] = useState(null); useEffect(() => { - fetchIssue({ number }).then(setIssue); + IssueResource.get({ number }).then(setIssue); }, [number, refetchKey]); if (!issue) return null; return ; @@ -117,7 +110,7 @@ function ListView({ }) { const [issues, setIssues] = useState(null); useEffect(() => { - fetchIssueList({ count }).then(setIssues); + IssueResource.getList({ count }).then(setIssues); }, [count, refetchKey]); if (!issues) return null; setCurrentIssues(issues); @@ -137,7 +130,7 @@ function StateListView({ }) { const [issues, setIssues] = useState(null); useEffect(() => { - fetchIssueList({ state, count }).then(setIssues); + IssueResource.getList({ state, count }).then(setIssues); }, [state, count, refetchKey]); if (!issues) return null; return ( @@ -198,10 +191,10 @@ function BenchmarkHarness() { if (!issue) return; const v = ++mutationCounter; measureUpdate(() => - updateIssue({ - number, - title: `${issue.title} (v${v})`, - }).then(triggerRefetch), + IssueResource.update( + { number }, + { title: `${issue.title} (v${v})` }, + ).then(triggerRefetch), ); }, [measureUpdate, triggerRefetch], @@ -213,10 +206,9 @@ function BenchmarkHarness() { if (!user) return; const v = ++mutationCounter; measureUpdate(() => - serverUpdateUser({ - login, - name: `${user.name} (v${v})`, - }).then(triggerRefetch), + UserResource.update({ login }, { name: `${user.name} (v${v})` }).then( + triggerRefetch, + ), ); }, [measureUpdate, triggerRefetch], @@ -225,13 +217,15 @@ function BenchmarkHarness() { const unshiftItem = useCallback(() => { const user = FIXTURE_USERS[0]; measureUpdate(() => - createIssue({ title: 'New Issue', user }).then(triggerRefetch), + IssueResource.create({ title: 'New Issue', user }).then(triggerRefetch), ); }, [measureUpdate, triggerRefetch]); const deleteEntity = useCallback( (number: number) => { - measureUpdate(() => deleteIssue({ number }).then(triggerRefetch)); + measureUpdate(() => + IssueResource.delete({ number }).then(triggerRefetch), + ); }, [measureUpdate, triggerRefetch], ); @@ -243,7 +237,10 @@ function BenchmarkHarness() { const targetState = moveStateRef.current; moveStateRef.current = targetState === 'closed' ? 'open' : 'closed'; measureUpdate( - () => updateIssue({ number, state: targetState }).then(triggerRefetch), + () => + IssueResource.update({ number }, { state: targetState }).then( + triggerRefetch, + ), () => moveItemIsReady(containerRef, number, targetState), ); }, @@ -257,7 +254,10 @@ function BenchmarkHarness() { const v = ++mutationCounter; const expected = `${issue.title} (v${v})`; measureUpdate( - () => updateIssue({ number, title: expected }).then(triggerRefetch), + () => + IssueResource.update({ number }, { title: expected }).then( + triggerRefetch, + ), () => { const container = containerRef.current!; const listTitle = container.querySelector( diff --git a/examples/coin-app/package.json b/examples/coin-app/package.json index f39763e8683c..6497ddef9cc5 100644 --- a/examples/coin-app/package.json +++ b/examples/coin-app/package.json @@ -35,7 +35,7 @@ "@types/react": "*", "@types/react-dom": "*", "react-refresh": "*", - "typescript": "6.0.1-rc", + "typescript": "6.0.2", "webpack": "*", "webpack-cli": "*" }, diff --git a/examples/nextjs/package-lock.json b/examples/nextjs/package-lock.json index fdc7bc44b6f6..bd55e52978d6 100644 --- a/examples/nextjs/package-lock.json +++ b/examples/nextjs/package-lock.json @@ -3915,9 +3915,9 @@ } }, "node_modules/typescript": { - "version": "6.0.1-rc", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.1-rc.tgz", - "integrity": "sha512-7XlzYb+p/7YxX6qSOzwB4mxVFRdAgWWkj1PgAZ+jzldeuFV6Z77vwFbNxHsUXAL/bhlWY2jCT8shLwDJR8337g==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/examples/test-bundlesize/package.json b/examples/test-bundlesize/package.json index c19f7d85f171..239c93dd8cb9 100644 --- a/examples/test-bundlesize/package.json +++ b/examples/test-bundlesize/package.json @@ -32,7 +32,7 @@ "@types/react": "*", "@types/react-dom": "*", "react-refresh": "*", - "typescript": "6.0.1-rc", + "typescript": "6.0.2", "webpack": "*", "webpack-cli": "*" }, diff --git a/examples/vue-todo-app/package-lock.json b/examples/vue-todo-app/package-lock.json index 598fc1181f60..7f9864279d0a 100644 --- a/examples/vue-todo-app/package-lock.json +++ b/examples/vue-todo-app/package-lock.json @@ -1458,9 +1458,9 @@ } }, "node_modules/typescript": { - "version": "6.0.1-rc", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.1-rc.tgz", - "integrity": "sha512-7XlzYb+p/7YxX6qSOzwB4mxVFRdAgWWkj1PgAZ+jzldeuFV6Z77vwFbNxHsUXAL/bhlWY2jCT8shLwDJR8337g==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", "dev": true, "license": "Apache-2.0", "bin": { diff --git a/package.json b/package.json index 4d0bce3d7136..a4c789f705c9 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,7 @@ "react-test-renderer": "19.2.3", "rimraf": "^6.0.0", "rollup": "4.59.0", - "typescript": "6.0.1-rc", + "typescript": "6.0.2", "whatwg-fetch": "3.0.0" }, "resolutions": { diff --git a/website/package.json b/website/package.json index 18dcbdd91041..561a0b75b701 100644 --- a/website/package.json +++ b/website/package.json @@ -56,7 +56,7 @@ "react-dom": "^19.0.0", "react-json-tree": "0.20.0", "react-live": "^4.0.0", - "typescript": "6.0.1-rc", + "typescript": "6.0.2", "uuid": "^13.0.0" }, "browserslist": [ diff --git a/yarn.lock b/yarn.lock index 27650ce296d3..cd16f9ef4f03 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11495,7 +11495,7 @@ __metadata: react: "npm:19.2.3" react-dom: "npm:19.2.3" react-refresh: "npm:*" - typescript: "npm:6.0.1-rc" + typescript: "npm:6.0.2" webpack: "npm:*" webpack-cli: "npm:*" languageName: unknown @@ -14657,7 +14657,7 @@ __metadata: serve: "npm:14.2.6" swr: "npm:2.4.1" tsx: "npm:4.21.0" - typescript: "npm:6.0.1-rc" + typescript: "npm:6.0.2" webpack: "npm:5.105.4" webpack-cli: "npm:6.0.1" languageName: unknown @@ -24683,7 +24683,7 @@ __metadata: react-json-tree: "npm:0.20.0" react-live: "npm:^4.0.0" serve: "npm:14.2.6" - typescript: "npm:6.0.1-rc" + typescript: "npm:6.0.2" uuid: "npm:^13.0.0" webpack: "npm:^5.76.0" languageName: unknown @@ -26075,7 +26075,7 @@ __metadata: react-test-renderer: "npm:19.2.3" rimraf: "npm:^6.0.0" rollup: "npm:4.59.0" - typescript: "npm:6.0.1-rc" + typescript: "npm:6.0.2" whatwg-fetch: "npm:3.0.0" languageName: unknown linkType: soft @@ -27981,7 +27981,7 @@ __metadata: react: "npm:^19.0.0" react-dom: "npm:^19.0.0" react-refresh: "npm:*" - typescript: "npm:6.0.1-rc" + typescript: "npm:6.0.2" webpack: "npm:*" webpack-cli: "npm:*" languageName: unknown @@ -28608,13 +28608,13 @@ __metadata: languageName: node linkType: hard -"typescript@npm:6.0.1-rc": - version: 6.0.1-rc - resolution: "typescript@npm:6.0.1-rc" +"typescript@npm:6.0.2": + version: 6.0.2 + resolution: "typescript@npm:6.0.2" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/8f042a30f2111700e2172c49d025f90c825fb5a44c17f4ef5092d3025f314ad7f503218c410720f491f209861c9083490741599da8c2395eef0e3d9d3808657e + checksum: 10c0/4b860b0bf87cc0fee0f66d8ef2640b5a8a8a8c74d1129adb82e389e5f97124383823c47946bef8a73ede371461143a3aa8544399d2133c7b2e4f07e81860af7f languageName: node linkType: hard @@ -28628,13 +28628,13 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A6.0.1-rc#optional!builtin": - version: 6.0.1-rc - resolution: "typescript@patch:typescript@npm%3A6.0.1-rc#optional!builtin::version=6.0.1-rc&hash=5786d5" +"typescript@patch:typescript@npm%3A6.0.2#optional!builtin": + version: 6.0.2 + resolution: "typescript@patch:typescript@npm%3A6.0.2#optional!builtin::version=6.0.2&hash=5786d5" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/891d5e284e6c1a1215cc938a93926244de207c64ef78c349f09473e9040b4ef9f039b04b4445148133c63cb679d13c15230e73491ce7984f4e04ab09ea35c46d + checksum: 10c0/49f0b84fc6ca55653e77752b8a61beabc09ee3dae5d965c31596225aa6ef213c5727b1d2e895b900416dc603854ba0872ac4a812c2a4ed6793a601f9c675de02 languageName: node linkType: hard