From 16f5d92598de05e92b88af98a9d63eecf27ab819 Mon Sep 17 00:00:00 2001 From: Nathaniel Tucker Date: Sun, 5 Apr 2026 12:33:14 -0400 Subject: [PATCH 1/9] fix(rest): Bundle path-to-regexp into ESM output for StackBlitz compat (#3866) * fix(rest): Bundle path-to-regexp into ESM output for StackBlitz compat StackBlitz WebContainers fundamentally cannot resolve CJS named exports from path-to-regexp, regardless of import style (named, namespace, etc). Add a rollup ESM build (dist/esm/index.js) that bundles path-to-regexp directly, eliminating the CJS boundary. Point the "module" and "browser" export conditions to this bundled ESM output. CJS and React Native paths are unchanged. Reverts the namespace import workaround since it's no longer needed. Made-with: Cursor * fix(rest): Handle any number of chained CJS void-initializations in pathToRegexpESM The regex that strips CJS `exports.X = void 0` lines only matched exactly two chained assignments. TypeScript CJS output can chain all exports on one line (e.g. 7+ assignments). The old regex would leave the line intact, causing a ReferenceError in ESM where `exports` is undefined. Use `/^(?:exports\.\w+\s*=\s*)+void 0;\s*\n/gm` which matches one or more chained `exports.X =` followed by `void 0`. Co-authored-by: Nathaniel Tucker * fix(rest): dev script passes -w to babel instead of rollup The build:lib script chains babel && rollup. When dev ran 'run build:lib -w', the -w flag was appended to the last command (rollup), so babel never watched for changes. Fix by inlining the babel command directly in the dev script. Co-authored-by: Nathaniel Tucker --------- Co-authored-by: Cursor Agent --- .changeset/bundle-path-to-regexp-esm.md | 10 +++++ packages/rest/package.json | 5 ++- packages/rest/rollup.config.mjs | 57 ++++++++++++++++++++++++- packages/rest/src/RestHelpers.ts | 17 ++++---- packages/rest/src/pathToRegexp.ts | 2 + 5 files changed, 79 insertions(+), 12 deletions(-) create mode 100644 .changeset/bundle-path-to-regexp-esm.md create mode 100644 packages/rest/src/pathToRegexp.ts diff --git a/.changeset/bundle-path-to-regexp-esm.md b/.changeset/bundle-path-to-regexp-esm.md new file mode 100644 index 000000000000..f39f87fb5e58 --- /dev/null +++ b/.changeset/bundle-path-to-regexp-esm.md @@ -0,0 +1,10 @@ +--- +'@data-client/rest': patch +--- + +Bundle `path-to-regexp` as tree-shaken ESM + +Bundle only the functions we use (`compile`, `parse`, `pathToRegexp`) from +`path-to-regexp` into the ESM/browser build via rollup. This eliminates +the CJS/ESM boundary that broke StackBlitz WebContainers and reduces bundle +size by tree-shaking unused exports (`match`, `stringify`, and the `ID` regex). diff --git a/packages/rest/package.json b/packages/rest/package.json index 5a2e9e6fff27..30f043296817 100644 --- a/packages/rest/package.json +++ b/packages/rest/package.json @@ -84,6 +84,7 @@ }, "exports": { ".": { + "types": "./lib/index.d.ts", "module": "./lib/index.js", "import": "./node.mjs", "require": "./dist/index.js", @@ -116,7 +117,7 @@ "README.md" ], "scripts": { - "build:lib": "NODE_ENV=production BROWSERSLIST_ENV='2020' POLYFILL_TARGETS='chrome>88,safari>14' yarn g:babel --out-dir lib", + "build:lib": "NODE_ENV=production BROWSERSLIST_ENV='2020' POLYFILL_TARGETS='chrome>88,safari>14' yarn g:babel --out-dir lib && NODE_ENV=production BROWSERSLIST_ENV='2020' yarn g:rollup", "build:legacy:lib": "NODE_ENV=production BROWSERSLIST_ENV='2018' POLYFILL_TARGETS='chrome>80,safari>13,firefox>72' yarn g:babel --out-dir legacy", "build:js:node": "BROWSERSLIST_ENV=node12 yarn g:rollup", "build:js:browser": "BROWSERSLIST_ENV=legacy yarn g:rollup", @@ -124,7 +125,7 @@ "build:clean": "yarn g:clean ts4.0 ts4.1 index.d.ts next.d.ts", "build:legacy-types": "yarn g:downtypes lib ts4.0 --to=4.0 && yarn g:downtypes lib ts4.1 --to=4.1 && yarn g:copy --up 1 ./src-4.1-types/**/*.d.ts ./ts4.0/ && yarn g:copy --up 1 ./src-4.1-types/**/*.d.ts ./ts4.1 && yarn g:copy --up 1 ./src-4.0-types/**/*.d.ts ./ts4.0", "build": "run build:lib && run build:legacy:lib && run build:bundle", - "dev": "run build:lib -w", + "dev": "NODE_ENV=production BROWSERSLIST_ENV='2020' POLYFILL_TARGETS='chrome>88,safari>14' yarn g:babel --out-dir lib -w", "prepare": "run build:lib", "prepack": "run prepare && run build:bundle && run build:legacy:lib", "tsc:ci": "yarn g:tsc --project tsconfig.test.json", diff --git a/packages/rest/rollup.config.mjs b/packages/rest/rollup.config.mjs index d5c1777d6b54..a7cbff4746c7 100644 --- a/packages/rest/rollup.config.mjs +++ b/packages/rest/rollup.config.mjs @@ -1,3 +1,5 @@ +import { readFileSync } from 'fs'; +import { createRequire } from 'module'; import { babel, commonjs, @@ -26,8 +28,61 @@ function isExternal(id) { return dependencies.some(dep => dep === id || id.startsWith(dep)); } +// Converts path-to-regexp CJS to ESM so rollup can tree-shake unused exports +// (match, stringify, etc.). The standard @rollup/plugin-commonjs wraps the +// entire module in an opaque function, defeating tree-shaking. +function pathToRegexpESM() { + const require = createRequire(import.meta.url); + const VIRTUAL_ID = '\0path-to-regexp-esm'; + return { + name: 'path-to-regexp-esm', + resolveId(source) { + if (source === 'path-to-regexp') return VIRTUAL_ID; + }, + load(id) { + if (id !== VIRTUAL_ID) return null; + const source = readFileSync(require.resolve('path-to-regexp'), 'utf-8'); + // Strip CJS boilerplate — remaining code is plain function/class/const + // declarations that work as-is in ESM (function declarations are hoisted). + return ( + source + .replace(/^"use strict";\s*/m, '') + .replace(/^Object\.defineProperty\(exports,\s*"__esModule".*\n/m, '') + .replace(/^(?:exports\.\w+\s*=\s*)+void 0;\s*\n/gm, '') + .replace(/^exports\.(\w+)\s*=\s*\w+;\s*\n/gm, '') + .replace(/\/\/# sourceMappingURL=.*$/, '') + + '\nexport { compile, parse, pathToRegexp, TokenData };\n' + ); + }, + }; +} + const configs = []; -if (process.env.BROWSERSLIST_ENV !== 'node12') { +if (process.env.BROWSERSLIST_ENV === '2020') { + // Bundle only path-to-regexp into lib/pathToRegexp.js as tree-shaken ESM. + // Overwrites the Babel pass-through re-export with an inlined build that + // eliminates the CJS/ESM boundary (fixes StackBlitz WebContainers) and + // drops unused exports (match, stringify, ID regex). + configs.push({ + input: 'src/pathToRegexp.ts', + external: id => id === '@babel/runtime' || id.startsWith('@babel/runtime/'), + output: [{ file: 'lib/pathToRegexp.js', format: 'esm' }], + onwarn, + plugins: [ + pathToRegexpESM(), + babel({ + exclude: ['node_modules/**', '**/__tests__/**', '**/*.d.ts'], + extensions, + babelHelpers: 'runtime', + rootMode: 'upward', + caller: { polyfillMethod: false }, + }), + resolve({ extensions }), + resolveTsAsJs, + json(), + ], + }); +} else if (process.env.BROWSERSLIST_ENV !== 'node12') { //TODO: this needs to use src/index.ts so it doesn't include the wrong polyfills // browser-friendly UMD build configs.push({ diff --git a/packages/rest/src/RestHelpers.ts b/packages/rest/src/RestHelpers.ts index 9f89a88142d7..bc0d7d2c6760 100644 --- a/packages/rest/src/RestHelpers.ts +++ b/packages/rest/src/RestHelpers.ts @@ -1,12 +1,11 @@ -// Namespace import avoids webpack's per-export CJS validation, which fails in -// StackBlitz WebContainers. Named imports (`import { compile }`) trigger -// `importExportsPresence` checks that break there. `import *` defers property -// access to runtime. No tree-shaking loss: CJS modules are included whole -// regardless of import style, and webpack 5 traces static access on namespaces. -import type { PathFunction, Token, ParamData } from 'path-to-regexp'; -import * as pathToRegexpModule from 'path-to-regexp'; -const { compile, parse, pathToRegexp } = pathToRegexpModule; - +import { + compile, + PathFunction, + parse, + pathToRegexp, + Token, + ParamData, +} from './pathToRegexp.js'; import { ShortenPath } from './pathTypes.js'; const urlBaseCache: Record> = Object.create(null); diff --git a/packages/rest/src/pathToRegexp.ts b/packages/rest/src/pathToRegexp.ts new file mode 100644 index 000000000000..c02637c42c73 --- /dev/null +++ b/packages/rest/src/pathToRegexp.ts @@ -0,0 +1,2 @@ +export type { PathFunction, Token, ParamData } from 'path-to-regexp'; +export { compile, parse, pathToRegexp } from 'path-to-regexp'; From be8f8d0990ccf0bdf08a736f2694e97beb8e3946 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 5 Apr 2026 12:40:46 -0400 Subject: [PATCH 2/9] internal: Publish new version (#3870) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/bundle-path-to-regexp-esm.md | 10 ---------- examples/benchmark-react/CHANGELOG.md | 8 ++++++++ examples/benchmark-react/package.json | 2 +- examples/coin-app/CHANGELOG.md | 7 +++++++ examples/coin-app/package.json | 4 ++-- examples/test-bundlesize/CHANGELOG.md | 8 ++++++++ examples/test-bundlesize/package.json | 2 +- packages/rest/CHANGELOG.md | 11 +++++++++++ packages/rest/package.json | 2 +- yarn.lock | 4 ++-- 10 files changed, 41 insertions(+), 17 deletions(-) delete mode 100644 .changeset/bundle-path-to-regexp-esm.md diff --git a/.changeset/bundle-path-to-regexp-esm.md b/.changeset/bundle-path-to-regexp-esm.md deleted file mode 100644 index f39f87fb5e58..000000000000 --- a/.changeset/bundle-path-to-regexp-esm.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -'@data-client/rest': patch ---- - -Bundle `path-to-regexp` as tree-shaken ESM - -Bundle only the functions we use (`compile`, `parse`, `pathToRegexp`) from -`path-to-regexp` into the ESM/browser build via rollup. This eliminates -the CJS/ESM boundary that broke StackBlitz WebContainers and reduces bundle -size by tree-shaking unused exports (`match`, `stringify`, and the `ID` regex). diff --git a/examples/benchmark-react/CHANGELOG.md b/examples/benchmark-react/CHANGELOG.md index abc8ef8b6ffd..5d83a873ce89 100644 --- a/examples/benchmark-react/CHANGELOG.md +++ b/examples/benchmark-react/CHANGELOG.md @@ -1,5 +1,13 @@ # example-benchmark-react +## 0.1.6 + +### Patch Changes + +- Updated dependencies [[`16f5d92`](https://github.com/reactive/data-client/commit/16f5d92598de05e92b88af98a9d63eecf27ab819)]: + - @data-client/rest@0.16.5 + - @data-client/react@0.16.0 + ## 0.1.5 ### Patch Changes diff --git a/examples/benchmark-react/package.json b/examples/benchmark-react/package.json index c9561836b361..c16914666d22 100644 --- a/examples/benchmark-react/package.json +++ b/examples/benchmark-react/package.json @@ -1,6 +1,6 @@ { "name": "example-benchmark-react", - "version": "0.1.5", + "version": "0.1.6", "private": true, "description": "React rendering benchmark comparing @data-client/react against other data libraries", "scripts": { diff --git a/examples/coin-app/CHANGELOG.md b/examples/coin-app/CHANGELOG.md index 1ed151ca1f0a..74e32ebc123c 100644 --- a/examples/coin-app/CHANGELOG.md +++ b/examples/coin-app/CHANGELOG.md @@ -1,5 +1,12 @@ # coinbase-lite +## 0.0.21 + +### Patch Changes + +- Updated dependencies [[`16f5d92`](https://github.com/reactive/data-client/commit/16f5d92598de05e92b88af98a9d63eecf27ab819)]: + - @data-client/rest@0.16.5 + ## 0.0.20 ### Patch Changes diff --git a/examples/coin-app/package.json b/examples/coin-app/package.json index a7dfa7ed64eb..ca9b9433391d 100644 --- a/examples/coin-app/package.json +++ b/examples/coin-app/package.json @@ -1,6 +1,6 @@ { "name": "coinbase-lite", - "version": "0.0.20", + "version": "0.0.21", "packageManager": "yarn@4.13.0", "description": "Coin App", "scripts": { @@ -46,7 +46,7 @@ "@babel/runtime-corejs3": "7.29.2", "@data-client/img": "0.16.0", "@data-client/react": "0.16.0", - "@data-client/rest": "0.16.4", + "@data-client/rest": "0.16.5", "core-js": "3.49.0", "d3": "7.9.0", "history": "*", diff --git a/examples/test-bundlesize/CHANGELOG.md b/examples/test-bundlesize/CHANGELOG.md index 5dca8b0d6242..442dafc84b92 100644 --- a/examples/test-bundlesize/CHANGELOG.md +++ b/examples/test-bundlesize/CHANGELOG.md @@ -1,5 +1,13 @@ # test-bundlesize +## 0.1.13 + +### Patch Changes + +- Updated dependencies [[`16f5d92`](https://github.com/reactive/data-client/commit/16f5d92598de05e92b88af98a9d63eecf27ab819)]: + - @data-client/rest@0.16.5 + - @data-client/react@0.16.0 + ## 0.1.12 ### Patch Changes diff --git a/examples/test-bundlesize/package.json b/examples/test-bundlesize/package.json index f6c09c0ce11c..844fda6cd1bd 100644 --- a/examples/test-bundlesize/package.json +++ b/examples/test-bundlesize/package.json @@ -1,6 +1,6 @@ { "name": "test-bundlesize", - "version": "0.1.12", + "version": "0.1.13", "packageManager": "yarn@4.13.0", "description": "Testing Bundled Size", "scripts": { diff --git a/packages/rest/CHANGELOG.md b/packages/rest/CHANGELOG.md index b1fae45b6987..5b781b36b14e 100644 --- a/packages/rest/CHANGELOG.md +++ b/packages/rest/CHANGELOG.md @@ -1,5 +1,16 @@ # @data-client/rest +## 0.16.5 + +### Patch Changes + +- [#3866](https://github.com/reactive/data-client/pull/3866) [`16f5d92`](https://github.com/reactive/data-client/commit/16f5d92598de05e92b88af98a9d63eecf27ab819) - Bundle `path-to-regexp` as tree-shaken ESM + + Bundle only the functions we use (`compile`, `parse`, `pathToRegexp`) from + `path-to-regexp` into the ESM/browser build via rollup. This eliminates + the CJS/ESM boundary that broke StackBlitz WebContainers and reduces bundle + size by tree-shaking unused exports (`match`, `stringify`, and the `ID` regex). + ## 0.16.4 ### Patch Changes diff --git a/packages/rest/package.json b/packages/rest/package.json index 30f043296817..6e1dc341fe27 100644 --- a/packages/rest/package.json +++ b/packages/rest/package.json @@ -1,6 +1,6 @@ { "name": "@data-client/rest", - "version": "0.16.4", + "version": "0.16.5", "description": "Quickly define typed REST resources and endpoints", "homepage": "https://dataclient.io/rest", "repository": { diff --git a/yarn.lock b/yarn.lock index b5d10b4a0725..9fc76d0a296b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3352,7 +3352,7 @@ __metadata: languageName: unknown linkType: soft -"@data-client/rest@npm:0.16.4, @data-client/rest@workspace:*, @data-client/rest@workspace:packages/rest": +"@data-client/rest@npm:0.16.5, @data-client/rest@workspace:*, @data-client/rest@workspace:packages/rest": version: 0.0.0-use.local resolution: "@data-client/rest@workspace:packages/rest" dependencies: @@ -11464,7 +11464,7 @@ __metadata: "@babel/runtime-corejs3": "npm:7.29.2" "@data-client/img": "npm:0.16.0" "@data-client/react": "npm:0.16.0" - "@data-client/rest": "npm:0.16.4" + "@data-client/rest": "npm:0.16.5" "@linaria/core": "npm:*" "@linaria/react": "npm:*" "@linaria/shaker": "npm:*" From aeeaeeb116afef5704ce5fc4281e55ba7d66698d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 5 Apr 2026 13:08:48 -0400 Subject: [PATCH 3/9] pkg: Update `@data-client/rest` to v0.16.5 (#3871) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- examples/github-app/package-lock.json | 8 ++++---- examples/github-app/package.json | 2 +- examples/nextjs/package-lock.json | 6 +++--- examples/todo-app/package-lock.json | 8 ++++---- examples/todo-app/package.json | 2 +- examples/vue-todo-app/package-lock.json | 8 ++++---- examples/vue-todo-app/package.json | 2 +- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/examples/github-app/package-lock.json b/examples/github-app/package-lock.json index e64089af5d43..1de9e20419c8 100644 --- a/examples/github-app/package-lock.json +++ b/examples/github-app/package-lock.json @@ -17,7 +17,7 @@ "@data-client/graphql": "0.16.3", "@data-client/img": "0.16.0", "@data-client/react": "0.16.0", - "@data-client/rest": "0.16.4", + "@data-client/rest": "0.16.5", "antd": "6.3.5", "core-js": "^3.48.0", "history": "^5.3.0", @@ -3922,9 +3922,9 @@ } }, "node_modules/@data-client/rest": { - "version": "0.16.4", - "resolved": "https://registry.npmjs.org/@data-client/rest/-/rest-0.16.4.tgz", - "integrity": "sha512-DOvvPMRFyufAWVTJB9c5p7TVbJYC6iKIL6CWflARgeduZ436x4m6+i9uBnbQKqlpaPSlfvVw/+nwFAR2Bg7Dow==", + "version": "0.16.5", + "resolved": "https://registry.npmjs.org/@data-client/rest/-/rest-0.16.5.tgz", + "integrity": "sha512-SGPeje4pG7VVqyb6YyU2SsiNRZSqaostCsbtswy9e4O/B7jIyoVV+xpd+nflhyLglHInKBxUNCqvsyRxZXXcqQ==", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.20.0", diff --git a/examples/github-app/package.json b/examples/github-app/package.json index 8013ec38394f..a5c1d8e1b083 100644 --- a/examples/github-app/package.json +++ b/examples/github-app/package.json @@ -57,7 +57,7 @@ "@data-client/graphql": "0.16.3", "@data-client/img": "0.16.0", "@data-client/react": "0.16.0", - "@data-client/rest": "0.16.4", + "@data-client/rest": "0.16.5", "temporal-polyfill": "^0.3.0", "antd": "6.3.5", "core-js": "^3.48.0", diff --git a/examples/nextjs/package-lock.json b/examples/nextjs/package-lock.json index a2284c52b9fe..f2cd888539ae 100644 --- a/examples/nextjs/package-lock.json +++ b/examples/nextjs/package-lock.json @@ -1984,9 +1984,9 @@ } }, "node_modules/@data-client/rest": { - "version": "0.16.4", - "resolved": "https://registry.npmjs.org/@data-client/rest/-/rest-0.16.4.tgz", - "integrity": "sha512-DOvvPMRFyufAWVTJB9c5p7TVbJYC6iKIL6CWflARgeduZ436x4m6+i9uBnbQKqlpaPSlfvVw/+nwFAR2Bg7Dow==", + "version": "0.16.5", + "resolved": "https://registry.npmjs.org/@data-client/rest/-/rest-0.16.5.tgz", + "integrity": "sha512-SGPeje4pG7VVqyb6YyU2SsiNRZSqaostCsbtswy9e4O/B7jIyoVV+xpd+nflhyLglHInKBxUNCqvsyRxZXXcqQ==", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.20.0", diff --git a/examples/todo-app/package-lock.json b/examples/todo-app/package-lock.json index 763900e91023..99f968ac20ec 100644 --- a/examples/todo-app/package-lock.json +++ b/examples/todo-app/package-lock.json @@ -12,7 +12,7 @@ "@babel/runtime-corejs3": "^7.26.7", "@data-client/endpoint": "0.16.3", "@data-client/react": "0.16.0", - "@data-client/rest": "0.16.4", + "@data-client/rest": "0.16.5", "core-js": "^3.40.0", "react": "19.2.3", "react-dom": "19.2.3", @@ -3737,9 +3737,9 @@ } }, "node_modules/@data-client/rest": { - "version": "0.16.4", - "resolved": "https://registry.npmjs.org/@data-client/rest/-/rest-0.16.4.tgz", - "integrity": "sha512-DOvvPMRFyufAWVTJB9c5p7TVbJYC6iKIL6CWflARgeduZ436x4m6+i9uBnbQKqlpaPSlfvVw/+nwFAR2Bg7Dow==", + "version": "0.16.5", + "resolved": "https://registry.npmjs.org/@data-client/rest/-/rest-0.16.5.tgz", + "integrity": "sha512-SGPeje4pG7VVqyb6YyU2SsiNRZSqaostCsbtswy9e4O/B7jIyoVV+xpd+nflhyLglHInKBxUNCqvsyRxZXXcqQ==", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.20.0", diff --git a/examples/todo-app/package.json b/examples/todo-app/package.json index 9551b770c3ec..20a5f025a534 100644 --- a/examples/todo-app/package.json +++ b/examples/todo-app/package.json @@ -47,7 +47,7 @@ "@babel/runtime-corejs3": "^7.26.7", "@data-client/endpoint": "0.16.3", "@data-client/react": "0.16.0", - "@data-client/rest": "0.16.4", + "@data-client/rest": "0.16.5", "core-js": "^3.40.0", "react": "19.2.3", "react-dom": "19.2.3", diff --git a/examples/vue-todo-app/package-lock.json b/examples/vue-todo-app/package-lock.json index 3fb0dec363ef..4ebe2cd2e7b4 100644 --- a/examples/vue-todo-app/package-lock.json +++ b/examples/vue-todo-app/package-lock.json @@ -8,7 +8,7 @@ "name": "vue-todo-app", "version": "0.1.0", "dependencies": { - "@data-client/rest": "0.16.4", + "@data-client/rest": "0.16.5", "@data-client/vue": "^0.16.0", "uuid": "^13.0.0", "vue": "^3.5.22", @@ -128,9 +128,9 @@ } }, "node_modules/@data-client/rest": { - "version": "0.16.4", - "resolved": "https://registry.npmjs.org/@data-client/rest/-/rest-0.16.4.tgz", - "integrity": "sha512-DOvvPMRFyufAWVTJB9c5p7TVbJYC6iKIL6CWflARgeduZ436x4m6+i9uBnbQKqlpaPSlfvVw/+nwFAR2Bg7Dow==", + "version": "0.16.5", + "resolved": "https://registry.npmjs.org/@data-client/rest/-/rest-0.16.5.tgz", + "integrity": "sha512-SGPeje4pG7VVqyb6YyU2SsiNRZSqaostCsbtswy9e4O/B7jIyoVV+xpd+nflhyLglHInKBxUNCqvsyRxZXXcqQ==", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.20.0", diff --git a/examples/vue-todo-app/package.json b/examples/vue-todo-app/package.json index 435c415e8e6b..532a7526084e 100644 --- a/examples/vue-todo-app/package.json +++ b/examples/vue-todo-app/package.json @@ -16,7 +16,7 @@ }, "dependencies": { "@data-client/vue": "^0.16.0", - "@data-client/rest": "0.16.4", + "@data-client/rest": "0.16.5", "vue": "^3.5.22", "vue-router": "^4.6.3", "uuid": "^13.0.0" From 3b84450af8f65bbaa9c15a9c05150093bdc0d711 Mon Sep 17 00:00:00 2001 From: Nathaniel Tucker Date: Sun, 5 Apr 2026 13:18:23 -0400 Subject: [PATCH 4/9] demo(pkg): Fix nextjs demo stackblitz compat --- examples/nextjs/package-lock.json | 898 +++++++++++++++++------------- examples/nextjs/package.json | 2 +- 2 files changed, 522 insertions(+), 378 deletions(-) diff --git a/examples/nextjs/package-lock.json b/examples/nextjs/package-lock.json index f2cd888539ae..6e924a20226f 100644 --- a/examples/nextjs/package-lock.json +++ b/examples/nextjs/package-lock.json @@ -19,7 +19,7 @@ "@types/react-dom": "19.2.3", "clsx": "^2.1.1", "core-js": "^3.40.0", - "next": "~15.5.0", + "next": "~15.4.0", "react": "19.2.3", "react-dom": "19.2.3", "tar-fs": "^3.1.2", @@ -108,19 +108,6 @@ } } }, - "node_modules/@anansi/babel-preset/node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz", - "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==", - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.8", - "core-js-compat": "^3.48.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, "node_modules/@anansi/ts-utils": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/@anansi/ts-utils/-/ts-utils-0.3.9.tgz", @@ -220,13 +207,13 @@ } }, "node_modules/@babel/helper-builder-react-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.27.1.tgz", - "integrity": "sha512-l/OHIt7g/cpCcj+ngMbyZ8PTsCiZhaa4/sG5Qy33VhrdIaWD3lyRH4V8v0CsL67CSQDPynYT83x5SL9weRQ8uw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.28.6.tgz", + "integrity": "sha512-M//zZWRDMXmk9LasvZv+ThXvsDfgvIujlXkWSr3e25KJXWBCItfJqqnYrBelIRVTbqs+pGZY/AppGQEhA8m24w==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -464,22 +451,22 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", "license": "MIT", "dependencies": { "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/types": "^7.29.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "license": "MIT", "dependencies": { "@babel/types": "^7.29.0" @@ -606,6 +593,7 @@ "version": "7.21.0-placeholder-for-preset-env.2", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "license": "MIT", "engines": { "node": ">=6.9.0" }, @@ -692,12 +680,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -740,6 +728,7 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" @@ -1433,16 +1422,16 @@ } }, "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz", - "integrity": "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz", + "integrity": "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-syntax-jsx": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1548,6 +1537,19 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-runtime/node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, "node_modules/@babel/plugin-transform-shorthand-properties": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", @@ -1790,23 +1792,11 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.0.tgz", - "integrity": "sha512-AvDcMxJ34W4Wgy4KBIIePQTAOP1Ie2WFwkQp3dB7FQ/f0lI5+nM96zUnYEOE1P9sEg0es5VCP0HxiWu5fUHZAQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.6", - "core-js-compat": "^3.48.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, "node_modules/@babel/preset-modules": { "version": "0.1.6-no-external-plugins", "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/types": "^7.4.4", @@ -1837,25 +1827,21 @@ } }, "node_modules/@babel/runtime": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", - "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/runtime-corejs3": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.26.10.tgz", - "integrity": "sha512-uITFQYO68pMEYR46AHgQoyBg7KPPJDAbGn4jUTIRgCFJIp88MIBUianVOplhZDEec07bp9zIyr4Kp0FCyQzmWg==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.29.2.tgz", + "integrity": "sha512-Lc94FOD5+0aXhdb0Tdg3RUtqT6yWbI/BbFWvlaSJ3gAb9Ks+99nHRDKADVqC37er4eCB0fHyWT+y+K3QOvJKbw==", "license": "MIT", "dependencies": { - "core-js-pure": "^3.30.2", - "regenerator-runtime": "^0.14.0" + "core-js-pure": "^3.48.0" }, "engines": { "node": ">=6.9.0" @@ -2022,9 +2008,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.6.0.tgz", - "integrity": "sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", "license": "MIT", "optional": true, "dependencies": { @@ -2032,9 +2018,9 @@ } }, "node_modules/@img/colour": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", - "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", "license": "MIT", "optional": true, "engines": { @@ -2042,9 +2028,9 @@ } }, "node_modules/@img/sharp-darwin-arm64": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.4.tgz", - "integrity": "sha512-sitdlPzDVyvmINUdJle3TNHl+AG9QcwiAMsXmccqsCOMZNIdW2/7S26w0LyU8euiLVzFBL3dXPwVCq/ODnf2vA==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", "cpu": [ "arm64" ], @@ -2060,13 +2046,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.2.3" + "@img/sharp-libvips-darwin-arm64": "1.2.4" } }, "node_modules/@img/sharp-darwin-x64": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.4.tgz", - "integrity": "sha512-rZheupWIoa3+SOdF/IcUe1ah4ZDpKBGWcsPX6MT0lYniH9micvIU7HQkYTfrx5Xi8u+YqwLtxC/3vl8TQN6rMg==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", "cpu": [ "x64" ], @@ -2082,13 +2068,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.2.3" + "@img/sharp-libvips-darwin-x64": "1.2.4" } }, "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.3.tgz", - "integrity": "sha512-QzWAKo7kpHxbuHqUC28DZ9pIKpSi2ts2OJnoIGI26+HMgq92ZZ4vk8iJd4XsxN+tYfNJxzH6W62X5eTcsBymHw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", "cpu": [ "arm64" ], @@ -2102,9 +2088,9 @@ } }, "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.3.tgz", - "integrity": "sha512-Ju+g2xn1E2AKO6YBhxjj+ACcsPQRHT0bhpglxcEf+3uyPY+/gL8veniKoo96335ZaPo03bdDXMv0t+BBFAbmRA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", "cpu": [ "x64" ], @@ -2118,9 +2104,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.3.tgz", - "integrity": "sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", "cpu": [ "arm" ], @@ -2134,9 +2120,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.3.tgz", - "integrity": "sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", "cpu": [ "arm64" ], @@ -2150,9 +2136,9 @@ } }, "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.3.tgz", - "integrity": "sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", "cpu": [ "ppc64" ], @@ -2165,10 +2151,26 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.3.tgz", - "integrity": "sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", "cpu": [ "s390x" ], @@ -2182,9 +2184,9 @@ } }, "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.3.tgz", - "integrity": "sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", "cpu": [ "x64" ], @@ -2198,9 +2200,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.3.tgz", - "integrity": "sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", "cpu": [ "arm64" ], @@ -2214,9 +2216,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.3.tgz", - "integrity": "sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", "cpu": [ "x64" ], @@ -2230,9 +2232,9 @@ } }, "node_modules/@img/sharp-linux-arm": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.4.tgz", - "integrity": "sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", "cpu": [ "arm" ], @@ -2248,13 +2250,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.2.3" + "@img/sharp-libvips-linux-arm": "1.2.4" } }, "node_modules/@img/sharp-linux-arm64": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.4.tgz", - "integrity": "sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", "cpu": [ "arm64" ], @@ -2270,13 +2272,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.2.3" + "@img/sharp-libvips-linux-arm64": "1.2.4" } }, "node_modules/@img/sharp-linux-ppc64": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.4.tgz", - "integrity": "sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", "cpu": [ "ppc64" ], @@ -2292,13 +2294,35 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.2.3" + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" } }, "node_modules/@img/sharp-linux-s390x": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.4.tgz", - "integrity": "sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", "cpu": [ "s390x" ], @@ -2314,13 +2338,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.2.3" + "@img/sharp-libvips-linux-s390x": "1.2.4" } }, "node_modules/@img/sharp-linux-x64": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.4.tgz", - "integrity": "sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", "cpu": [ "x64" ], @@ -2336,13 +2360,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.2.3" + "@img/sharp-libvips-linux-x64": "1.2.4" } }, "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.4.tgz", - "integrity": "sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", "cpu": [ "arm64" ], @@ -2358,13 +2382,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.2.3" + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" } }, "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.4.tgz", - "integrity": "sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", "cpu": [ "x64" ], @@ -2380,20 +2404,20 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.2.3" + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" } }, "node_modules/@img/sharp-wasm32": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.4.tgz", - "integrity": "sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", "cpu": [ "wasm32" ], "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { - "@emnapi/runtime": "^1.5.0" + "@emnapi/runtime": "^1.7.0" }, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" @@ -2403,9 +2427,9 @@ } }, "node_modules/@img/sharp-win32-arm64": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.4.tgz", - "integrity": "sha512-2Q250do/5WXTwxW3zjsEuMSv5sUU4Tq9VThWKlU2EYLm4MB7ZeMwF+SFJutldYODXF6jzc6YEOC+VfX0SZQPqA==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", "cpu": [ "arm64" ], @@ -2422,9 +2446,9 @@ } }, "node_modules/@img/sharp-win32-ia32": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.4.tgz", - "integrity": "sha512-3ZeLue5V82dT92CNL6rsal6I2weKw1cYu+rGKm8fOCCtJTR2gYeUfY3FqUnIJsMUPIH68oS5jmZ0NiJ508YpEw==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", "cpu": [ "ia32" ], @@ -2441,9 +2465,9 @@ } }, "node_modules/@img/sharp-win32-x64": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.4.tgz", - "integrity": "sha512-xIyj4wpYs8J18sVN3mSQjwrw7fKUqRw+Z5rnHNCy5fYTxigBz81u5mOMPmFumwjcn8+ld1ppptMBCLic1nz6ig==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", "cpu": [ "x64" ], @@ -2460,9 +2484,9 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -2483,19 +2507,21 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -2503,15 +2529,15 @@ } }, "node_modules/@next/env": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.14.tgz", - "integrity": "sha512-aXeirLYuASxEgi4X4WhfXsShCFxWDfNn/8ZeC5YXAS2BB4A8FJi1kwwGL6nvMVboE7fZCzmJPNdMvVHc8JpaiA==", + "version": "15.4.11", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.4.11.tgz", + "integrity": "sha512-mIYp/091eYfPFezKX7ZPTWqrmSXq+ih6+LcUyKvLmeLQGhlPtot33kuEOd4U+xAA7sFfj21+OtCpIZx0g5SpvQ==", "license": "MIT" }, "node_modules/@next/swc-darwin-arm64": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.14.tgz", - "integrity": "sha512-Y9K6SPzobnZvrRDPO2s0grgzC+Egf0CqfbdvYmQVaztV890zicw8Z8+4Vqw8oPck8r1TjUHxVh8299Cg4TrxXg==", + "version": "15.4.8", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.4.8.tgz", + "integrity": "sha512-Pf6zXp7yyQEn7sqMxur6+kYcywx5up1J849psyET7/8pG2gQTVMjU3NzgIt8SeEP5to3If/SaWmaA6H6ysBr1A==", "cpu": [ "arm64" ], @@ -2525,9 +2551,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.14.tgz", - "integrity": "sha512-aNnkSMjSFRTOmkd7qoNI2/rETQm/vKD6c/Ac9BZGa9CtoOzy3c2njgz7LvebQJ8iPxdeTuGnAjagyis8a9ifBw==", + "version": "15.4.8", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.4.8.tgz", + "integrity": "sha512-xla6AOfz68a6kq3gRQccWEvFC/VRGJmA/QuSLENSO7CZX5WIEkSz7r1FdXUjtGCQ1c2M+ndUAH7opdfLK1PQbw==", "cpu": [ "x64" ], @@ -2541,9 +2567,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.14.tgz", - "integrity": "sha512-tjlpia+yStPRS//6sdmlVwuO1Rioern4u2onafa5n+h2hCS9MAvMXqpVbSrjgiEOoCs0nJy7oPOmWgtRRNSM5Q==", + "version": "15.4.8", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.4.8.tgz", + "integrity": "sha512-y3fmp+1Px/SJD+5ntve5QLZnGLycsxsVPkTzAc3zUiXYSOlTPqT8ynfmt6tt4fSo1tAhDPmryXpYKEAcoAPDJw==", "cpu": [ "arm64" ], @@ -2557,9 +2583,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.14.tgz", - "integrity": "sha512-8B8cngBaLadl5lbDRdxGCP1Lef8ipD6KlxS3v0ElDAGil6lafrAM3B258p1KJOglInCVFUjk751IXMr2ixeQOQ==", + "version": "15.4.8", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.4.8.tgz", + "integrity": "sha512-DX/L8VHzrr1CfwaVjBQr3GWCqNNFgyWJbeQ10Lx/phzbQo3JNAxUok1DZ8JHRGcL6PgMRgj6HylnLNndxn4Z6A==", "cpu": [ "arm64" ], @@ -2573,9 +2599,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.14.tgz", - "integrity": "sha512-bAS6tIAg8u4Gn3Nz7fCPpSoKAexEt2d5vn1mzokcqdqyov6ZJ6gu6GdF9l8ORFrBuRHgv3go/RfzYz5BkZ6YSQ==", + "version": "15.4.8", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.4.8.tgz", + "integrity": "sha512-9fLAAXKAL3xEIFdKdzG5rUSvSiZTLLTCc6JKq1z04DR4zY7DbAPcRvNm3K1inVhTiQCs19ZRAgUerHiVKMZZIA==", "cpu": [ "x64" ], @@ -2589,9 +2615,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.14.tgz", - "integrity": "sha512-mMxv/FcrT7Gfaq4tsR22l17oKWXZmH/lVqcvjX0kfp5I0lKodHYLICKPoX1KRnnE+ci6oIUdriUhuA3rBCDiSw==", + "version": "15.4.8", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.4.8.tgz", + "integrity": "sha512-s45V7nfb5g7dbS7JK6XZDcapicVrMMvX2uYgOHP16QuKH/JA285oy6HcxlKqwUNaFY/UC6EvQ8QZUOo19cBKSA==", "cpu": [ "x64" ], @@ -2605,9 +2631,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.14.tgz", - "integrity": "sha512-OTmiBlYThppnvnsqx0rBqjDRemlmIeZ8/o4zI7veaXoeO1PVHoyj2lfTfXTiiGjCyRDhA10y4h6ZvZvBiynr2g==", + "version": "15.4.8", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.4.8.tgz", + "integrity": "sha512-KjgeQyOAq7t/HzAJcWPGA8X+4WY03uSCZ2Ekk98S9OgCFsb6lfBE3dbUzUuEQAN2THbwYgFfxX2yFTCMm8Kehw==", "cpu": [ "arm64" ], @@ -2621,9 +2647,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.14.tgz", - "integrity": "sha512-+W7eFf3RS7m4G6tppVTOSyP9Y6FsJXfOuKzav1qKniiFm3KFByQfPEcouHdjlZmysl4zJGuGLQ/M9XyVeyeNEg==", + "version": "15.4.8", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.4.8.tgz", + "integrity": "sha512-Exsmf/+42fWVnLMaZHzshukTBxZrSwuuLKFvqhGHJ+mC1AokqieLY/XzAl3jc/CqhXLqLY3RRjkKJ9YnLPcRWg==", "cpu": [ "x64" ], @@ -2671,7 +2697,8 @@ "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", - "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" }, "node_modules/@types/react": { "version": "19.2.14", @@ -2692,14 +2719,24 @@ } }, "node_modules/b4a": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", - "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz", + "integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } }, "node_modules/babel-plugin-macros": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", @@ -2724,13 +2761,13 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.15", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.15.tgz", - "integrity": "sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw==", + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", + "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", "license": "MIT", "dependencies": { "@babel/compat-data": "^7.28.6", - "@babel/helper-define-polyfill-provider": "^0.6.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", "semver": "^6.3.1" }, "peerDependencies": { @@ -2738,25 +2775,25 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", - "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz", + "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==", "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5", - "core-js-compat": "^3.43.0" + "@babel/helper-define-polyfill-provider": "^0.6.8", + "core-js-compat": "^3.48.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.6.tgz", - "integrity": "sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==", + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", + "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.6" + "@babel/helper-define-polyfill-provider": "^0.6.8" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -2775,6 +2812,7 @@ "version": "6.6.0", "resolved": "https://registry.npmjs.org/babel-plugin-root-import/-/babel-plugin-root-import-6.6.0.tgz", "integrity": "sha512-SPzVOHd7nDh5loZwZBxtX/oOu1MXeKjTkz+1VnnzLWC0dk8sJIGC2IDQ2uWIBjE5mUtXlQ35MTHSqN0Xn7qHrg==", + "license": "MIT", "dependencies": { "slash": "^3.0.0" } @@ -2795,42 +2833,60 @@ "node_modules/babel-plugin-transform-react-remove-prop-types": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", - "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==" + "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==", + "license": "MIT" }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" }, "node_modules/bare-events": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.4.2.tgz", - "integrity": "sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q==", - "optional": true + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", + "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } }, "node_modules/bare-fs": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.0.1.tgz", - "integrity": "sha512-ilQs4fm/l9eMfWY2dY0WCIUplSUp7U0CT1vrqMg1MUdeZl4fypu5UP0XcDBK5WBQPJAKP1b7XEodISmekH/CEg==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.6.0.tgz", + "integrity": "sha512-2YkS7NuiJceSEbyEOdSNLE9tsGd+f4+f7C+Nik/MCk27SYdwIMPT/yRKvg++FZhQXgk0KWJKJyXX9RhVV0RGqA==", "license": "Apache-2.0", - "optional": true, "dependencies": { - "bare-events": "^2.0.0", + "bare-events": "^2.5.4", "bare-path": "^3.0.0", - "bare-stream": "^2.0.0" + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" }, "engines": { - "bare": ">=1.7.0" + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } } }, "node_modules/bare-os": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.4.0.tgz", - "integrity": "sha512-9Ous7UlnKbe3fMi7Y+qh0DwAup6A1JkYgPnjvMDNOlmnxNRQvQ/7Nst+OnUQKzk0iAT0m9BisbDVp9gCv8+ETA==", + "version": "3.8.7", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.8.7.tgz", + "integrity": "sha512-G4Gr1UsGeEy2qtDTZwL7JFLo2wapUarz7iTMcYcMFdS89AIQuBoyjgXZz0Utv7uHs3xA9LckhVbeBi8lEQrC+w==", "license": "Apache-2.0", - "optional": true, "engines": { - "bare": ">=1.6.0" + "bare": ">=1.14.0" } }, "node_modules/bare-path": { @@ -2838,27 +2894,55 @@ "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", "license": "Apache-2.0", - "optional": true, "dependencies": { "bare-os": "^3.0.1" } }, "node_modules/bare-stream": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.1.3.tgz", - "integrity": "sha512-tiDAH9H/kP+tvNO5sczyn9ZAA7utrSMobyDchsnyyXBuUe2FSQWbxhtuHB8jwpHYYevVo2UJpcmvvjrbHboUUQ==", - "optional": true, + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.12.0.tgz", + "integrity": "sha512-w28i8lkBgREV3rPXGbgK+BO66q+ZpKqRWrZLiCdmmUlLPrQ45CzkvRhN+7lnv00Gpi2zy5naRxnUFAxCECDm9g==", + "license": "Apache-2.0", "dependencies": { - "streamx": "^2.18.0" + "streamx": "^2.25.0", + "teex": "^1.0.1" + }, + "peerDependencies": { + "bare-abort-controller": "*", + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.0.tgz", + "integrity": "sha512-NSTU5WN+fy/L0DDenfE8SXQna4voXuW0FHM7wH8i3/q9khUSchfPbPezO4zSFMnDGIf9YE+mt/RWhZgNRKRIXA==", + "license": "Apache-2.0", + "dependencies": { + "bare-path": "^3.0.0" } }, "node_modules/baseline-browser-mapping": { - "version": "2.9.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", - "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "version": "2.10.15", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.15.tgz", + "integrity": "sha512-1nfKCq9wuAZFTkA2ey/3OXXx7GzFjLdkTiFVNwlJ9WqdI706CZRIhEqjuwanjMIja+84jDLa9rcyZDPDiVkASQ==", "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.js" + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/brace-expansion": { @@ -2871,9 +2955,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", "funding": [ { "type": "opencollective", @@ -2890,11 +2974,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" @@ -2907,14 +2991,15 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/caniuse-lite": { - "version": "1.0.30001769", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", - "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", + "version": "1.0.30001785", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001785.tgz", + "integrity": "sha512-blhOL/WNR+Km1RI/LCVAvA73xplXA7ZbjzI4YkMK9pa6T/P3F2GxjNpEkyw5repTw9IvkyrjyHpwjnhZ5FOvYQ==", "funding": [ { "type": "opencollective", @@ -2934,12 +3019,14 @@ "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", - "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", "engines": { "node": ">=6" } @@ -2947,12 +3034,13 @@ "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" }, "node_modules/core-js": { - "version": "3.40.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.40.0.tgz", - "integrity": "sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ==", + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz", + "integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==", "hasInstallScript": true, "license": "MIT", "funding": { @@ -2961,9 +3049,9 @@ } }, "node_modules/core-js-compat": { - "version": "3.48.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.48.0.tgz", - "integrity": "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==", + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", + "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", "license": "MIT", "dependencies": { "browserslist": "^4.28.1" @@ -2974,9 +3062,9 @@ } }, "node_modules/core-js-pure": { - "version": "3.40.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.40.0.tgz", - "integrity": "sha512-AtDzVIgRrmRKQai62yuSIN5vNiQjcJakJb4fbhVw3ehxx7Lohphvw9SGNWKhLFqSxC4ilD0g/L1huAYFQU3Q6A==", + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.49.0.tgz", + "integrity": "sha512-XM4RFka59xATyJv/cS3O3Kml72hQXUeGRuuTmMYFxwzc9/7C8OYTaIR/Ji+Yt8DXzsFLNhat15cE/JP15HrCgw==", "hasInstallScript": true, "license": "MIT", "funding": { @@ -2988,6 +3076,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", @@ -3033,23 +3122,25 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.286", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", - "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", + "version": "1.5.331", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.331.tgz", + "integrity": "sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q==", "license": "ISC" }, "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", "dependencies": { "once": "^1.4.0" } }, "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" } @@ -3064,37 +3155,49 @@ } }, "node_modules/esm-env": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.1.4.tgz", - "integrity": "sha512-oO82nKPHKkzIj/hbtuDYy/JHqBHFlMIW36SDiPCVsj87ntDLcWN+sJ1erdVryd4NxODacFTsdrIE3b7IamqbOg==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", "license": "MIT" }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, "node_modules/fast-fifo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" }, "node_modules/find-babel-config": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/find-babel-config/-/find-babel-config-2.1.1.tgz", - "integrity": "sha512-5Ji+EAysHGe1OipH7GN4qDjok5Z1uw5KAwDCbicU/4wyTZY7CqOCzcWbG7J5ad9mazq67k89fXlbc1MuIfl9uA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/find-babel-config/-/find-babel-config-2.1.2.tgz", + "integrity": "sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg==", + "license": "MIT", "dependencies": { - "json5": "^2.2.3", - "path-exists": "^4.0.0" + "json5": "^2.2.3" } }, "node_modules/find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "license": "MIT", "dependencies": { "locate-path": "^3.0.0" }, @@ -3106,6 +3209,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/flux-standard-action/-/flux-standard-action-2.1.2.tgz", "integrity": "sha512-7vdgawlphCjzaMLdpZv8hlGC/FJCXu6sqE3Wuqe3HLZ22KcDiO4IFplxLDePDhEt6hgCrugt45RoUObuzZP6Kg==", + "license": "MIT", "dependencies": { "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1" @@ -3114,7 +3218,8 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" }, "node_modules/function-bind": { "version": "1.1.2", @@ -3129,6 +3234,7 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -3137,6 +3243,8 @@ "version": "9.3.5", "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", @@ -3153,7 +3261,8 @@ "node_modules/glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "license": "BSD-2-Clause" }, "node_modules/hasown": { "version": "2.0.2", @@ -3168,9 +3277,10 @@ } }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -3185,12 +3295,14 @@ "node_modules/inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "license": "ISC" }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" }, "node_modules/is-core-module": { "version": "2.16.1", @@ -3228,12 +3340,14 @@ "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", "bin": { "json5": "lib/cli.js" }, @@ -3244,12 +3358,14 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" }, "node_modules/locate-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "license": "MIT", "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" @@ -3258,14 +3374,6 @@ "node": ">=6" } }, - "node_modules/locate-path/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "engines": { - "node": ">=4" - } - }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -3275,17 +3383,20 @@ "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" }, "node_modules/lodash.isstring": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", "dependencies": { "yallist": "^3.0.2" } @@ -3309,6 +3420,7 @@ "version": "4.2.8", "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "license": "ISC", "engines": { "node": ">=8" } @@ -3320,15 +3432,16 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -3337,12 +3450,12 @@ } }, "node_modules/next": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/next/-/next-15.5.14.tgz", - "integrity": "sha512-M6S+4JyRjmKic2Ssm7jHUPkE6YUJ6lv4507jprsSZLulubz0ihO2E+S4zmQK3JZ2ov81JrugukKU4Tz0ivgqqQ==", + "version": "15.4.11", + "resolved": "https://registry.npmjs.org/next/-/next-15.4.11.tgz", + "integrity": "sha512-IJRyXal45mIsshZI5XJne/intjusslUP1F+FHVBIyMGEqbYtIq1Irdx5vdWBBg58smviPDycmDeV6txsfkv1RQ==", "license": "MIT", "dependencies": { - "@next/env": "15.5.14", + "@next/env": "15.4.11", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", @@ -3355,14 +3468,14 @@ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "15.5.14", - "@next/swc-darwin-x64": "15.5.14", - "@next/swc-linux-arm64-gnu": "15.5.14", - "@next/swc-linux-arm64-musl": "15.5.14", - "@next/swc-linux-x64-gnu": "15.5.14", - "@next/swc-linux-x64-musl": "15.5.14", - "@next/swc-win32-arm64-msvc": "15.5.14", - "@next/swc-win32-x64-msvc": "15.5.14", + "@next/swc-darwin-arm64": "15.4.8", + "@next/swc-darwin-x64": "15.4.8", + "@next/swc-linux-arm64-gnu": "15.4.8", + "@next/swc-linux-arm64-musl": "15.4.8", + "@next/swc-linux-x64-gnu": "15.4.8", + "@next/swc-linux-x64-musl": "15.4.8", + "@next/swc-win32-arm64-msvc": "15.4.8", + "@next/swc-win32-x64-msvc": "15.4.8", "sharp": "^0.34.3" }, "peerDependencies": { @@ -3389,9 +3502,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "version": "2.0.37", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", + "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", "license": "MIT" }, "node_modules/number-flow": { @@ -3407,6 +3520,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", "dependencies": { "wrappy": "1" } @@ -3415,6 +3529,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", "dependencies": { "p-try": "^2.0.0" }, @@ -3429,6 +3544,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "license": "MIT", "dependencies": { "p-limit": "^2.0.0" }, @@ -3440,6 +3556,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", "engines": { "node": ">=6" } @@ -3448,6 +3565,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -3459,6 +3577,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -3476,28 +3595,32 @@ "version": "0.12.7", "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", + "license": "MIT", "dependencies": { "process": "^0.11.1", "util": "^0.10.3" } }, "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=4" } }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" }, "node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -3512,12 +3635,14 @@ "node_modules/path-scurry/node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" }, "node_modules/path-scurry/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } @@ -3536,6 +3661,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", "engines": { "node": ">=8" } @@ -3550,6 +3676,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "license": "MIT", "dependencies": { "find-up": "^3.0.0" }, @@ -3575,6 +3702,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", @@ -3588,24 +3716,21 @@ "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", "engines": { "node": ">= 0.6.0" } }, "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, - "node_modules/queue-tick": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", - "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" - }, "node_modules/react": { "version": "19.2.3", "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", @@ -3645,11 +3770,6 @@ "node": ">=4" } }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" - }, "node_modules/regexpu-core": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", @@ -3674,9 +3794,9 @@ "license": "MIT" }, "node_modules/regjsparser": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", - "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.1.tgz", + "integrity": "sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==", "license": "BSD-2-Clause", "dependencies": { "jsesc": "~3.1.0" @@ -3688,7 +3808,8 @@ "node_modules/reselect": { "version": "4.1.8", "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", - "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==" + "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==", + "license": "MIT" }, "node_modules/resolve": { "version": "1.22.11", @@ -3714,6 +3835,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", "engines": { "node": ">=4" } @@ -3735,7 +3857,8 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/scheduler": { "version": "0.27.0", @@ -3747,21 +3870,22 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/sharp": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.4.tgz", - "integrity": "sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", "hasInstallScript": true, "license": "Apache-2.0", "optional": true, "dependencies": { "@img/colour": "^1.0.0", - "detect-libc": "^2.1.0", - "semver": "^7.7.2" + "detect-libc": "^2.1.2", + "semver": "^7.7.3" }, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" @@ -3770,34 +3894,36 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.34.4", - "@img/sharp-darwin-x64": "0.34.4", - "@img/sharp-libvips-darwin-arm64": "1.2.3", - "@img/sharp-libvips-darwin-x64": "1.2.3", - "@img/sharp-libvips-linux-arm": "1.2.3", - "@img/sharp-libvips-linux-arm64": "1.2.3", - "@img/sharp-libvips-linux-ppc64": "1.2.3", - "@img/sharp-libvips-linux-s390x": "1.2.3", - "@img/sharp-libvips-linux-x64": "1.2.3", - "@img/sharp-libvips-linuxmusl-arm64": "1.2.3", - "@img/sharp-libvips-linuxmusl-x64": "1.2.3", - "@img/sharp-linux-arm": "0.34.4", - "@img/sharp-linux-arm64": "0.34.4", - "@img/sharp-linux-ppc64": "0.34.4", - "@img/sharp-linux-s390x": "0.34.4", - "@img/sharp-linux-x64": "0.34.4", - "@img/sharp-linuxmusl-arm64": "0.34.4", - "@img/sharp-linuxmusl-x64": "0.34.4", - "@img/sharp-wasm32": "0.34.4", - "@img/sharp-win32-arm64": "0.34.4", - "@img/sharp-win32-ia32": "0.34.4", - "@img/sharp-win32-x64": "0.34.4" + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" } }, "node_modules/sharp/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "optional": true, "bin": { @@ -3811,35 +3937,36 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, "node_modules/streamx": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.18.0.tgz", - "integrity": "sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ==", + "version": "2.25.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.25.0.tgz", + "integrity": "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==", + "license": "MIT", "dependencies": { + "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", - "queue-tick": "^1.0.1", "text-decoder": "^1.1.0" - }, - "optionalDependencies": { - "bare-events": "^2.2.0" } }, "node_modules/styled-jsx": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", "dependencies": { "client-only": "0.0.1" }, @@ -3862,6 +3989,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -3884,19 +4012,31 @@ } }, "node_modules/tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.8.tgz", + "integrity": "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==", + "license": "MIT", "dependencies": { "b4a": "^1.6.4", + "bare-fs": "^4.5.5", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, "node_modules/text-decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.1.tgz", - "integrity": "sha512-8zll7REEv4GDD3x4/0pW+ppIxSNs7H1J10IKFZsuOMscumCdM2a+toDGLPA3T+1+fLBql4zbt5z83GEQGGV5VA==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "license": "Apache-2.0", "dependencies": { "b4a": "^1.6.4" } @@ -3911,6 +4051,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" }, @@ -4011,6 +4152,7 @@ "version": "0.10.4", "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "license": "MIT", "dependencies": { "inherits": "2.0.3" } @@ -4018,12 +4160,14 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" }, "node_modules/yaml": { "version": "1.10.3", diff --git a/examples/nextjs/package.json b/examples/nextjs/package.json index e2304c1269e3..676fe7755620 100644 --- a/examples/nextjs/package.json +++ b/examples/nextjs/package.json @@ -27,7 +27,7 @@ "@types/react-dom": "19.2.3", "clsx": "^2.1.1", "core-js": "^3.40.0", - "next": "~15.5.0", + "next": "~15.4.0", "react": "19.2.3", "react-dom": "19.2.3", "tar-fs": "^3.1.2", From 8a7c8d9cc4228a6281849f56121699f237af4b0f Mon Sep 17 00:00:00 2001 From: Nathaniel Tucker Date: Sun, 5 Apr 2026 15:19:51 -0400 Subject: [PATCH 5/9] feat(rest): Add `content` property to RestEndpoint for typed response parsing (#3868) * feat(rest): add content property for response parsing and binary auto-detection Add a 'content' string property to RestEndpoint that controls response parsing ('json' | 'blob' | 'text' | 'arrayBuffer' | 'stream'), infers return types via ContentTypeMap, and constrains schema compatibility via ContentSchemaGuard -- all without explicit generic parameters. When content is not set, auto-detect binary Content-Types using a compact regex and return response.blob() instead of corrupting data via .text(). Runtime schema check throws when binary content is used with a normalizable schema, matching the existing pattern in parseResponse. Co-authored-by: natmaster * test(rest): add runtime and type tests for content property and binary auto-detection Runtime tests: content: blob/text/json dispatch, schema check throw, 204 handling, extend/subclass propagation, and auto-detection for image/png, application/octet-stream, application/pdf, text/plain, application/json, application/xml, and missing Content-Type. Type tests: return type inference (Blob, string, ArrayBuffer, ReadableStream), schema constraint errors, extend type propagation, process priority over content, subclass pattern. Co-authored-by: natmaster * docs(rest): document content property, update file download examples Add content section to RestEndpoint.md with table of values, return types, and file download example. Update parseResponse section to reference content and auto-detection. Simplify network-transform.md file download guide to use content: 'blob' as primary pattern. Co-authored-by: natmaster * changeset: add content property to RestEndpoint Co-authored-by: natmaster * refactor(rest): simplify parseResponse for smaller bundle size - Extract shared jsonResponse() helper, eliminating duplicated response.json().catch() block - Move content+schema incompatibility check behind NODE_ENV guard (TypeScript already prevents this at compile time; normalization would catch it at runtime regardless) - Replace array allocation ['string','undefined'].includes() with direct typeof comparisons - Combine content: 'json' into the ternary with response[content]() Co-authored-by: natmaster * fix: resolve lint and prettier formatting issues Co-authored-by: natmaster * test: add missing test cases from validation review, add v0.17 blog post Runtime tests: content: 'arrayBuffer', content: 'stream', auto-detect for audio/mpeg, video/mp4, font/woff2. Type tests: explicit schema: undefined with content: 'blob', switch from blob back to json via extend(), content property typed on instance. Blog: draft v0.17 release post covering content property and binary auto-detection. Co-authored-by: natmaster * enhance: Early warn against schema + binary types * internal: Make feat addition a patch * fix: Bugbot * fix: single-line arrayBuffer schema constraint test for ts-expect-error Co-authored-by: natmaster * enhance: Edge case warning --------- Co-authored-by: Cursor Agent --- .changeset/content-property.md | 25 ++ docs/rest/api/RestEndpoint.md | 54 ++- docs/rest/guides/network-transform.md | 50 ++- packages/rest/src/RestEndpoint.js | 119 +++-- packages/rest/src/RestEndpointTypes.ts | 55 ++- packages/rest/src/__tests__/RestEndpoint.ts | 420 ++++++++++++++++++ packages/rest/src/index.ts | 1 + packages/rest/typescript-tests/types.test.ts | 165 +++++++ ...-content-property-binary-auto-detection.md | 87 ++++ 9 files changed, 898 insertions(+), 78 deletions(-) create mode 100644 .changeset/content-property.md create mode 100644 website/blog/2026-04-05-v0.17-content-property-binary-auto-detection.md diff --git a/.changeset/content-property.md b/.changeset/content-property.md new file mode 100644 index 000000000000..d229d58addf0 --- /dev/null +++ b/.changeset/content-property.md @@ -0,0 +1,25 @@ +--- +'@data-client/rest': patch +--- + +Add `content` property to RestEndpoint for typed response parsing + +Set `content` to control how the response body is parsed, with automatic return type inference: + +```ts +const downloadFile = new RestEndpoint({ + path: '/files/:id/download', + content: 'blob', + dataExpiryLength: 0, +}); +const blob: Blob = await ctrl.fetch(downloadFile, { id: '123' }); +``` + +Accepted values: `'json'`, `'blob'`, `'text'`, `'arrayBuffer'`, `'stream'`. + +Non-JSON content types (`'blob'`, `'text'`, `'arrayBuffer'`, `'stream'`) constrain `schema` to +`undefined` at the type level, with a runtime check that throws if a normalizable schema is set. + +When `content` is not set, auto-detection now handles binary Content-Types (`image/*`, +`application/octet-stream`, `application/pdf`, etc.) by returning `response.blob()` instead of +corrupting data via `.text()`. diff --git a/docs/rest/api/RestEndpoint.md b/docs/rest/api/RestEndpoint.md index 0e28499d0c78..0c3494601cf2 100644 --- a/docs/rest/api/RestEndpoint.md +++ b/docs/rest/api/RestEndpoint.md @@ -45,6 +45,7 @@ interface RestGenerics { readonly body?: any; readonly searchParams?: any; readonly paginationField?: string; + readonly content?: 'json' | 'blob' | 'text' | 'arrayBuffer' | 'stream'; process?(value: any, ...args: any): any; } @@ -55,6 +56,7 @@ export class RestEndpoint extends Endpoint { readonly requestInit: RequestInit; readonly method: string; readonly paginationField?: string; + readonly content?: 'json' | 'blob' | 'text' | 'arrayBuffer' | 'stream'; readonly signal: AbortSignal | undefined; url(...args: Parameters): string; searchToString(searchParams: Record): string; @@ -776,41 +778,45 @@ Performs the [fetch(input, init)](https://developer.mozilla.org/en-US/docs/Web/A [response.ok](https://developer.mozilla.org/en-US/docs/Web/API/Response/ok) is not `true` (like 404), will throw a NetworkError. -### parseResponse(response): Promise {#parseResponse} +### content {#content} -Takes the [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) and parses via [.text()](https://developer.mozilla.org/en-US/docs/Web/API/Response/text) or [.json()](https://developer.mozilla.org/en-US/docs/Web/API/Response/json) depending -on ['content-type' header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) having 'json' (e.g., `application/json`). +Controls how the [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) body is parsed. +When set, the return type is inferred automatically, and `schema` is constrained to `undefined` +for non-JSON content types. -If `status` is 204, resolves as `null`. +| Value | Parses via | Return type | +|---|---|---| +| `'json'` | [response.json()](https://developer.mozilla.org/en-US/docs/Web/API/Response/json) | `any` | +| `'blob'` | [response.blob()](https://developer.mozilla.org/en-US/docs/Web/API/Response/blob) | `Blob` | +| `'text'` | [response.text()](https://developer.mozilla.org/en-US/docs/Web/API/Response/text) | `string` | +| `'arrayBuffer'` | [response.arrayBuffer()](https://developer.mozilla.org/en-US/docs/Web/API/Response/arrayBuffer) | `ArrayBuffer` | +| `'stream'` | `response.body` | `ReadableStream` | +| *unset* | Auto-detect from Content-Type header | `any` | -Override this to handle other response types like [blob](https://developer.mozilla.org/en-US/docs/Web/API/Response/blob) or [arrayBuffer](https://developer.mozilla.org/en-US/docs/Web/API/Response/arrayBuffer). +When `content` is not set, `parseResponse` auto-detects the response type from the +`Content-Type` header: JSON types call `.json()`, binary types (images, `application/octet-stream`, +PDFs, etc.) call `.blob()`, and text-like types call `.text()`. #### File downloads {#file-download} -For binary responses like file downloads, override `parseResponse` to use `response.blob()`. -Set `schema: undefined` since binary data is not normalizable. Use `dataExpiryLength: 0` to -avoid caching large blobs in memory. +For file downloads, set `content: 'blob'`. The return type is `Blob` and `schema` must be +`undefined` (binary data cannot be normalized). Use `dataExpiryLength: 0` to avoid caching +large blobs in memory. ```ts const downloadFile = new RestEndpoint({ path: '/files/:id/download', - schema: undefined, + content: 'blob', dataExpiryLength: 0, - parseResponse(response) { - return response.blob(); - }, - process(blob): { blob: Blob; filename: string } { - return { blob, filename: 'download' }; - }, }); ``` -To extract the filename from the `Content-Disposition` header, override both `parseResponse` and `process`: +To extract the filename from the `Content-Disposition` header, override `parseResponse`: ```ts const downloadFile = new RestEndpoint({ path: '/files/:id/download', - schema: undefined, + content: 'blob', dataExpiryLength: 0, async parseResponse(response) { const blob = await response.blob(); @@ -827,6 +833,20 @@ const downloadFile = new RestEndpoint({ See [file download guide](../guides/network-transform.md#file-download) for complete usage with browser download trigger. +### parseResponse(response): Promise {#parseResponse} + +Takes the [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) and parses the body. + +When [`content`](#content) is set, it controls parsing directly. Otherwise, auto-detection runs +based on the [`Content-Type` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type): +JSON types call [.json()](https://developer.mozilla.org/en-US/docs/Web/API/Response/json), binary +types call [.blob()](https://developer.mozilla.org/en-US/docs/Web/API/Response/blob), and text-like +types call [.text()](https://developer.mozilla.org/en-US/docs/Web/API/Response/text). + +If `status` is 204, resolves as `null`. + +Override this for advanced cases like extracting headers alongside the body. + ### process(value, ...args): any {#process} Perform any transforms with the parsed result. Defaults to identity function (do nothing). diff --git a/docs/rest/guides/network-transform.md b/docs/rest/guides/network-transform.md index 8f16f892e8c2..e4c3e9463c8a 100644 --- a/docs/rest/guides/network-transform.md +++ b/docs/rest/guides/network-transform.md @@ -296,29 +296,18 @@ class GithubEndpoint< ## File download {#file-download} -For endpoints that return binary data (files, images, PDFs), override -[parseResponse](../api/RestEndpoint.md#parseResponse) to call -[response.blob()](https://developer.mozilla.org/en-US/docs/Web/API/Response/blob) instead of the -default JSON/text parsing. Set `schema: undefined` since binary data isn't normalizable, and -`dataExpiryLength: 0` to avoid caching large blobs in memory. +For endpoints that return binary data (files, images, PDFs), set +[`content: 'blob'`](../api/RestEndpoint.md#content). The return type is `Blob` and +`schema` defaults to `undefined` (binary data isn't normalizable). Use `dataExpiryLength: 0` +to avoid caching large blobs in memory. ```typescript title="downloadFile.ts" import { RestEndpoint } from '@data-client/rest'; const downloadFile = new RestEndpoint({ path: '/files/:id/download', - schema: undefined, + content: 'blob', dataExpiryLength: 0, - async parseResponse(response) { - const blob = await response.blob(); - const disposition = response.headers.get('Content-Disposition'); - const filename = - disposition?.match(/filename="?(.+?)"?$/)?.[1] ?? 'download'; - return { blob, filename }; - }, - process(value): { blob: Blob; filename: string } { - return value; - }, }); ``` @@ -330,11 +319,11 @@ function DownloadButton({ id }: { id: string }) { const ctrl = useController(); const handleDownload = async () => { - const { blob, filename } = await ctrl.fetch(downloadFile, { id }); + const blob: Blob = await ctrl.fetch(downloadFile, { id }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; - a.download = filename; + a.download = 'download'; a.click(); URL.revokeObjectURL(url); }; @@ -343,8 +332,31 @@ function DownloadButton({ id }: { id: string }) { } ``` +To extract the filename from the `Content-Disposition` header, override +[parseResponse](../api/RestEndpoint.md#parseResponse): + +```typescript title="downloadFile.ts" +import { RestEndpoint } from '@data-client/rest'; + +const downloadFile = new RestEndpoint({ + path: '/files/:id/download', + content: 'blob', + dataExpiryLength: 0, + async parseResponse(response) { + const blob = await response.blob(); + const disposition = response.headers.get('Content-Disposition'); + const filename = + disposition?.match(/filename="?(.+?)"?$/)?.[1] ?? 'download'; + return { blob, filename }; + }, + process(value): { blob: Blob; filename: string } { + return value; + }, +}); +``` + For `ArrayBuffer` responses (useful for processing binary data in-memory), use -`response.arrayBuffer()` the same way. +`content: 'arrayBuffer'` the same way. ## Name calling diff --git a/packages/rest/src/RestEndpoint.js b/packages/rest/src/RestEndpoint.js index b362163b3f05..cf258479dffe 100644 --- a/packages/rest/src/RestEndpoint.js +++ b/packages/rest/src/RestEndpoint.js @@ -12,6 +12,16 @@ import { isPojo, } from './RestHelpers.js'; +const textLikeRe = + /\btext\b|\bxml\b|\bhtml\b|\bjavascript\b|\bcss\b|\bcsv\b|\burlencoded\b/; + +function jsonResponse(response) { + return response.json().catch(error => { + error.status = 400; + throw error; + }); +} + /** Simplifies endpoint definitions that follow REST patterns * * @see https://dataclient.io/rest/api/RestEndpoint @@ -54,6 +64,21 @@ export default class RestEndpoint extends Endpoint { (!('body' in this) || this.body !== undefined) && !['GET', 'DELETE'].includes(this.method); + /* istanbul ignore else */ + if (process.env.NODE_ENV !== 'production') { + if ( + this.content && + this.content !== 'json' && + this.schema != null && + typeof this.schema !== 'string' && + typeof this.schema !== 'undefined' + ) { + console.error( + `content '${this.content}' is incompatible with schema. Binary/text responses cannot be normalized. Use schema: undefined.`, + ); + } + } + Object.defineProperty(this, 'name', { get() { // using 'in' to ensure inheritance lookup @@ -143,50 +168,76 @@ export default class RestEndpoint extends Endpoint { } parseResponse(response) { - // this should not have any content to read if (response.status === 204) return Promise.resolve(null); - if (!response.headers.get('content-type')?.includes('json')) { - return response.text().then(text => { - // string or 'not set' schema, are valid - // when overriding process they might handle other cases, so we don't want to block on our logic + + if (this.content) { + /* istanbul ignore else */ + if (process.env.NODE_ENV !== 'production') { + if ( + this.content !== 'json' && + this.schema != null && + typeof this.schema !== 'string' && + typeof this.schema !== 'undefined' + ) { + const error = new NetworkError(response); + error.status = 400; + error.message = `content '${this.content}' is incompatible with schema. Binary/text responses cannot be normalized. Use schema: undefined.`; + throw error; + } + } + if (this.content === 'stream') return Promise.resolve(response.body); + return this.content === 'json' ? + jsonResponse(response) + : response[this.content](); + } + + const contentType = response.headers.get('content-type'); + if (contentType?.includes('json')) return jsonResponse(response); + + const isBinary = contentType && !textLikeRe.test(contentType); + + if ( + typeof this.schema === 'string' || + typeof this.schema === 'undefined' || + this.schema === null || + this.process !== RestEndpoint.prototype.process + ) + return isBinary ? response.blob() : response.text(); + + if (isBinary) { + const error = new NetworkError(response); + error.status = 400; + error.message = `Unexpected binary content-type for schema ${this.schema}`; + /* istanbul ignore else */ + if (process.env.NODE_ENV !== 'production') { + error.message = `Unexpected binary content-type "${contentType}" for schema ${this.schema}. Binary responses cannot be normalized. Use schema: undefined or set content: 'blob'.`; + } + throw error; + } + + return response.text().then(text => { + const error = new NetworkError(response); + error.status = 404; + error.message = `Unexpected text response for schema ${this.schema}`; + /* istanbul ignore else */ + if (process.env.NODE_ENV !== 'production') { if ( - ['string', 'undefined'].includes(typeof this.schema) || - this.schema === null || - this.process !== RestEndpoint.prototype.process - ) - return text; - - const error = new NetworkError(response); - error.status = 404; - error.message = `Unexpected text response for schema ${this.schema}`; - // custom dev-only messages for more detailed cause - /* istanbul ignore else */ - if (process.env.NODE_ENV !== 'production') { - if ( - !( - response.headers.get('content-type')?.includes('html') || - text.startsWith('') - ) - ) { - if (tryParse(text) !== undefined) { - error.message = `"content-type" header does not include "json", but JSON response found. + !(contentType?.includes('html') || text.startsWith('')) + ) { + if (tryParse(text) !== undefined) { + error.message = `"content-type" header does not include "json", but JSON response found. See https://www.rfc-editor.org/rfc/rfc4627 for information on JSON responses Using parsed JSON. If text content was expected see https://dataclient.io/rest/api/RestEndpoint#parseResponse`; - } - } else { - error.message = `Unexpected html response for schema ${this.schema} + } + } else { + error.message = `Unexpected html response for schema ${this.schema} This likely means no API endpoint was configured for this request, resulting in an HTML fallback. Response (first 300 characters): ${text.substring(0, 300)}`; - } } - throw error; - }); - } - return response.json().catch(error => { - error.status = 400; + } throw error; }); } diff --git a/packages/rest/src/RestEndpointTypes.ts b/packages/rest/src/RestEndpointTypes.ts index 74b1cf5949b2..44fc22b830fd 100644 --- a/packages/rest/src/RestEndpointTypes.ts +++ b/packages/rest/src/RestEndpointTypes.ts @@ -14,6 +14,23 @@ import { import { PathArgs, SoftPathArgs } from './pathTypes.js'; import { EndpointUpdateFunction } from './RestEndpointTypeHelp.js'; +export type ContentType = 'json' | 'blob' | 'text' | 'arrayBuffer' | 'stream'; + +interface ContentTypeMap { + blob: Blob; + text: string; + arrayBuffer: ArrayBuffer; + stream: ReadableStream; + json: any; +} + +type ContentReturnType = ContentTypeMap[C]; + +type ContentSchemaGuard = + O extends { content: 'blob' | 'text' | 'arrayBuffer' | 'stream' } ? + { schema?: undefined } + : {}; + export interface RestInstanceBase< F extends FetchFunction = FetchFunction, S extends Schema | undefined = any, @@ -48,6 +65,8 @@ export interface RestInstanceBase< readonly signal: AbortSignal | undefined; /** @see https://dataclient.io/rest/api/RestEndpoint#paginationField */ readonly paginationField?: string; + /** @see https://dataclient.io/rest/api/RestEndpoint#content */ + readonly content?: ContentType; /* fetch lifecycles */ /* before-fetch */ @@ -105,7 +124,8 @@ export interface RestInstanceBase< this: E, options: Readonly< RestEndpointExtendOptions & ExtendOptions - >, + > & + ContentSchemaGuard, ): RestExtendedEndpoint; } @@ -248,7 +268,10 @@ type OptionsToRestEndpoint< Extract : 'method' extends keyof O ? MethodToSide : E['sideEffect'], - O['process'] extends {} ? ReturnType : ResolveType, + O['process'] extends {} ? ReturnType + : 'content' extends keyof O ? + ContentReturnType + : ResolveType, { path: Exclude; body: 'body' extends keyof O ? O['body'] : E['body']; @@ -276,7 +299,10 @@ type OptionsToRestEndpoint< Extract : 'method' extends keyof O ? MethodToSide : E['sideEffect'], - O['process'] extends {} ? ReturnType : ResolveType, + O['process'] extends {} ? ReturnType + : 'content' extends keyof O ? + ContentReturnType + : ResolveType, { path: E['path']; body: O['body']; @@ -302,7 +328,10 @@ type OptionsToRestEndpoint< Extract : 'method' extends keyof O ? MethodToSide : E['sideEffect'], - O['process'] extends {} ? ReturnType : ResolveType, + O['process'] extends {} ? ReturnType + : 'content' extends keyof O ? + ContentReturnType + : ResolveType, { path: E['path']; body: E['body']; @@ -343,9 +372,10 @@ export type RestExtendedEndpoint< { paginationField: E['getPage']['paginationField'] } : unknown), RestInstance< - ( - ...args: Parameters - ) => O['process'] extends {} ? Promise> + (...args: Parameters) => O['process'] extends {} ? + Promise> + : 'content' extends keyof O ? + Promise> : ReturnType, 'schema' extends keyof O ? O['schema'] : E['schema'], 'sideEffect' extends keyof O ? Extract @@ -373,6 +403,8 @@ export interface PartialRestGenerics { readonly paginationField?: string; /** @see https://dataclient.io/rest/api/RestEndpoint#process */ process?(value: any, ...args: any): any; + /** @see https://dataclient.io/rest/api/RestEndpoint#content */ + readonly content?: ContentType; } /** Generic types when constructing a RestEndpoint * @@ -560,6 +592,8 @@ export interface RestEndpointOptions< * @see https://dataclient.io/rest/api/RestEndpoint#parseResponse */ parseResponse?(response: Response): Promise; + /** @see https://dataclient.io/rest/api/RestEndpoint#content */ + content?: ContentType; sideEffect?: boolean | undefined; name?: string; @@ -586,6 +620,8 @@ export type RestEndpointConstructorOptions = : SoftPathArgs, OptionsToBodyArgument>, O['process'] extends {} ? ReturnType + : 'content' extends keyof O ? + ContentReturnType : any /*Denormalize*/ >, O['schema'] @@ -607,6 +643,7 @@ export interface RestEndpoint< : PathArgs, OptionsToBodyArgument>, O['process'] extends {} ? ReturnType + : 'content' extends keyof O ? ContentReturnType : any /*Denormalize*/ >, 'schema' extends keyof O ? O['schema'] : undefined, @@ -628,7 +665,9 @@ export interface RestEndpointConstructor { sideEffect, name, ...options - }: RestEndpointConstructorOptions & Readonly): RestEndpoint; + }: RestEndpointConstructorOptions & + Readonly & + ContentSchemaGuard): RestEndpoint; readonly prototype: RestInstanceBase; } diff --git a/packages/rest/src/__tests__/RestEndpoint.ts b/packages/rest/src/__tests__/RestEndpoint.ts index 7635e79e624b..1e86ad22a794 100644 --- a/packages/rest/src/__tests__/RestEndpoint.ts +++ b/packages/rest/src/__tests__/RestEndpoint.ts @@ -1736,6 +1736,426 @@ describe('RestEndpoint.fetch()', () => { expect(() => noColletionEndpoint.getPage).toThrowErrorMatchingSnapshot(); }); }); + +describe('content property', () => { + const binaryData = Buffer.from([0x89, 0x50, 0x4e, 0x47]); + + it("content: 'blob' calls response.blob()", async () => { + nock(/.*/) + .defaultReplyHeaders({ + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/octet-stream', + }) + .get('/files/1') + .reply(200, binaryData); + + const ep = new RestEndpoint({ + path: 'http\\://test.com/files/:id', + content: 'blob', + }); + const result = await ep({ id: 1 }); + expect(result).toBeInstanceOf(Blob); + }); + + it("content: 'text' calls response.text()", async () => { + nock(/.*/) + .defaultReplyHeaders({ + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'text/plain', + }) + .get('/files/1') + .reply(200, 'hello world'); + + const ep = new RestEndpoint({ + path: 'http\\://test.com/files/:id', + content: 'text', + }); + const result = await ep({ id: 1 }); + expect(result).toBe('hello world'); + }); + + it("content: 'json' calls response.json() strictly", async () => { + nock(/.*/) + .defaultReplyHeaders({ + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'text/plain', + }) + .get('/files/1') + .reply(200, { ok: true }); + + const ep = new RestEndpoint({ + path: 'http\\://test.com/files/:id', + content: 'json', + }); + const result = await ep({ id: 1 }); + expect(result).toEqual({ ok: true }); + }); + + it('content: blob with schema set throws', async () => { + nock(/.*/) + .defaultReplyHeaders({ + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/octet-stream', + }) + .get('/files/1') + .reply(200, binaryData); + + const ep = new RestEndpoint({ + path: 'http\\://test.com/files/:id', + content: 'blob', + schema: User, + } as any); + await expect(async () => await ep({ id: 1 })).rejects.toMatchObject({ + status: 400, + message: expect.stringContaining('incompatible with schema'), + }); + }); + + it.each(['blob', 'text', 'arrayBuffer', 'stream'] as const)( + 'content: %s with schema warns at construction', + content => { + const errorSpy = jest + .spyOn(console, 'error') + .mockImplementation(() => {}); + try { + new RestEndpoint({ + path: 'http\\://test.com/files/:id', + content, + schema: User, + } as any); + expect(errorSpy).toHaveBeenCalledWith( + expect.stringContaining('incompatible with schema'), + ); + } finally { + errorSpy.mockRestore(); + } + }, + ); + + it('204 with content: blob returns null', async () => { + nock(/.*/) + .defaultReplyHeaders({ + 'Access-Control-Allow-Origin': '*', + }) + .get('/files/1') + .reply(204); + + const ep = new RestEndpoint({ + path: 'http\\://test.com/files/:id', + content: 'blob', + }); + const result = await ep({ id: 1 }); + expect(result).toBe(null); + }); + + it("extend({ content: 'blob' }) propagates", async () => { + nock(/.*/) + .defaultReplyHeaders({ + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/octet-stream', + }) + .get('/files/1') + .reply(200, binaryData); + + const base = new RestEndpoint({ + path: 'http\\://test.com/files/:id', + }); + const blobEp = base.extend({ content: 'blob' }); + expect(blobEp.content).toBe('blob'); + const result = await blobEp({ id: 1 }); + expect(result).toBeInstanceOf(Blob); + }); + + it('subclass with content = blob works', async () => { + nock(/.*/) + .defaultReplyHeaders({ + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/octet-stream', + }) + .get('/files/1') + .reply(200, binaryData); + + class BlobEndpoint extends RestEndpoint { + content = 'blob' as const; + } + const ep = new BlobEndpoint({ path: 'http\\://test.com/files/:id' }); + expect(ep.content).toBe('blob'); + const result = await ep({ id: 1 }); + expect(result).toBeInstanceOf(Blob); + }); + + it("content: 'arrayBuffer' calls response.arrayBuffer()", async () => { + nock(/.*/) + .defaultReplyHeaders({ + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/octet-stream', + }) + .get('/files/1') + .reply(200, Buffer.from([1, 2, 3, 4])); + + const ep = new RestEndpoint({ + path: 'http\\://test.com/files/:id', + content: 'arrayBuffer', + }); + const result = await ep({ id: 1 }); + expect(result).toBeInstanceOf(ArrayBuffer); + }); + + it("content: 'stream' returns response.body", async () => { + const mockBody = { + getReader() { + return {}; + }, + }; + const mockResponse = { + ok: true, + status: 200, + body: mockBody, + headers: new Headers({ 'Content-Type': 'application/octet-stream' }), + } as unknown as Response; + + const ep = new RestEndpoint({ + path: 'http\\://test.com/files/:id', + content: 'stream', + fetchResponse() { + return Promise.resolve(mockResponse); + }, + }); + const result = await ep({ id: 1 }); + expect(result).toBe(mockBody); + }); +}); + +describe('auto-detection (no content)', () => { + it('Content-Type: image/png returns blob', async () => { + const imgData = Buffer.from([0x89, 0x50, 0x4e, 0x47]); + nock(/.*/) + .defaultReplyHeaders({ + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'image/png', + }) + .get('/files/1') + .reply(200, imgData); + + const ep = new RestEndpoint({ + path: 'http\\://test.com/files/:id', + }); + const result = await ep({ id: 1 }); + expect(result).toBeInstanceOf(Blob); + }); + + it('Content-Type: application/octet-stream returns blob', async () => { + nock(/.*/) + .defaultReplyHeaders({ + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/octet-stream', + }) + .get('/files/1') + .reply(200, Buffer.from([1, 2, 3])); + + const ep = new RestEndpoint({ + path: 'http\\://test.com/files/:id', + }); + const result = await ep({ id: 1 }); + expect(result).toBeInstanceOf(Blob); + }); + + it('Content-Type: application/pdf returns blob', async () => { + nock(/.*/) + .defaultReplyHeaders({ + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/pdf', + }) + .get('/files/1') + .reply(200, Buffer.from([0x25, 0x50, 0x44, 0x46])); + + const ep = new RestEndpoint({ + path: 'http\\://test.com/files/:id', + }); + const result = await ep({ id: 1 }); + expect(result).toBeInstanceOf(Blob); + }); + + it('Content-Type: text/plain returns text (unchanged)', async () => { + nock(/.*/) + .defaultReplyHeaders({ + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'text/plain', + }) + .get('/files/1') + .reply(200, 'plain text'); + + const ep = new RestEndpoint({ + path: 'http\\://test.com/files/:id', + }); + const result = await ep({ id: 1 }); + expect(result).toBe('plain text'); + }); + + it('Content-Type: application/json returns JSON (unchanged)', async () => { + nock(/.*/) + .defaultReplyHeaders({ + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/json', + }) + .get('/files/1') + .reply(200, { id: 1, name: 'test' }); + + const ep = new RestEndpoint({ + path: 'http\\://test.com/files/:id', + }); + const result = await ep({ id: 1 }); + expect(result).toEqual({ id: 1, name: 'test' }); + }); + + it('Content-Type: application/xml returns text (text-like)', async () => { + nock(/.*/) + .defaultReplyHeaders({ + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/xml', + }) + .get('/files/1') + .reply(200, 'hi'); + + const ep = new RestEndpoint({ + path: 'http\\://test.com/files/:id', + }); + const result = await ep({ id: 1 }); + expect(result).toBe('hi'); + }); + + it('Content-Type: audio/mpeg returns blob', async () => { + nock(/.*/) + .defaultReplyHeaders({ + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'audio/mpeg', + }) + .get('/files/1') + .reply(200, Buffer.from([0xff, 0xfb])); + + const ep = new RestEndpoint({ + path: 'http\\://test.com/files/:id', + }); + const result = await ep({ id: 1 }); + expect(result).toBeInstanceOf(Blob); + }); + + it('Content-Type: video/mp4 returns blob', async () => { + nock(/.*/) + .defaultReplyHeaders({ + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'video/mp4', + }) + .get('/files/1') + .reply(200, Buffer.from([0x00, 0x00])); + + const ep = new RestEndpoint({ + path: 'http\\://test.com/files/:id', + }); + const result = await ep({ id: 1 }); + expect(result).toBeInstanceOf(Blob); + }); + + it('Content-Type: font/woff2 returns blob', async () => { + nock(/.*/) + .defaultReplyHeaders({ + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'font/woff2', + }) + .get('/files/1') + .reply(200, Buffer.from([0x77, 0x4f])); + + const ep = new RestEndpoint({ + path: 'http\\://test.com/files/:id', + }); + const result = await ep({ id: 1 }); + expect(result).toBeInstanceOf(Blob); + }); + + it('Content-Type: XLSX (openxmlformats) returns blob', async () => { + nock(/.*/) + .defaultReplyHeaders({ + 'Access-Control-Allow-Origin': '*', + 'Content-Type': + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + }) + .get('/files/1') + .reply(200, Buffer.from([0x50, 0x4b, 0x03, 0x04])); + + const ep = new RestEndpoint({ + path: 'http\\://test.com/files/:id', + }); + const result = await ep({ id: 1 }); + expect(result).toBeInstanceOf(Blob); + }); + + it('Content-Type: DOCX (openxmlformats) returns blob', async () => { + nock(/.*/) + .defaultReplyHeaders({ + 'Access-Control-Allow-Origin': '*', + 'Content-Type': + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + }) + .get('/files/1') + .reply(200, Buffer.from([0x50, 0x4b, 0x03, 0x04])); + + const ep = new RestEndpoint({ + path: 'http\\://test.com/files/:id', + }); + const result = await ep({ id: 1 }); + expect(result).toBeInstanceOf(Blob); + }); + + it('Content-Type: PPTX (openxmlformats) returns blob', async () => { + nock(/.*/) + .defaultReplyHeaders({ + 'Access-Control-Allow-Origin': '*', + 'Content-Type': + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + }) + .get('/files/1') + .reply(200, Buffer.from([0x50, 0x4b, 0x03, 0x04])); + + const ep = new RestEndpoint({ + path: 'http\\://test.com/files/:id', + }); + const result = await ep({ id: 1 }); + expect(result).toBeInstanceOf(Blob); + }); + + it('binary content-type with normalizable schema throws', async () => { + nock(/.*/) + .defaultReplyHeaders({ + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/octet-stream', + }) + .get('/files/1') + .reply(200, Buffer.from([1, 2, 3])); + + const ep = new RestEndpoint({ + path: 'http\\://test.com/files/:id', + schema: User, + }); + await expect(async () => await ep({ id: 1 })).rejects.toMatchObject({ + status: 400, + }); + }); + + it('No Content-Type returns text (backward compat)', async () => { + nock(/.*/) + .defaultReplyHeaders({ + 'Access-Control-Allow-Origin': '*', + }) + .get('/files/1') + .reply(200, 'no content type'); + + const ep = new RestEndpoint({ + path: 'http\\://test.com/files/:id', + }); + const result = await ep({ id: 1 }); + expect(result).toBe('no content type'); + }); +}); const proto = Object.prototype; const gpo = Object.getPrototypeOf; diff --git a/packages/rest/src/index.ts b/packages/rest/src/index.ts index b1f469d7bc3f..1e38925951c9 100644 --- a/packages/rest/src/index.ts +++ b/packages/rest/src/index.ts @@ -14,6 +14,7 @@ export type { AddEndpoint, PaginationFieldEndpoint, PaginationEndpoint, + ContentType, } from './RestEndpoint.js'; export type { RestEndpoint as IRestEndpoint } from './RestEndpointTypes.js'; export { getUrlBase, getUrlTokens } from './RestHelpers.js'; diff --git a/packages/rest/typescript-tests/types.test.ts b/packages/rest/typescript-tests/types.test.ts index 4c8612c2082b..661b1f13afab 100644 --- a/packages/rest/typescript-tests/types.test.ts +++ b/packages/rest/typescript-tests/types.test.ts @@ -1982,3 +1982,168 @@ it('should type resource using RestEndpoint subclass with O=any', () => { })); () => FnExtended.current(); }); + +it('content property: return type inference', () => { + const blobEp = new RestEndpoint({ + path: '/files/:id' as const, + content: 'blob', + }); + async () => { + const result = await blobEp({ id: '1' }); + result satisfies Blob; + // @ts-expect-error - Blob is not string + result satisfies string; + }; + + const textEp = new RestEndpoint({ + path: '/files/:id' as const, + content: 'text', + }); + async () => { + const result = await textEp({ id: '1' }); + result satisfies string; + // @ts-expect-error - string is not Blob + result satisfies Blob; + }; + + const abEp = new RestEndpoint({ + path: '/files/:id' as const, + content: 'arrayBuffer', + }); + async () => { + const result = await abEp({ id: '1' }); + result satisfies ArrayBuffer; + }; + + const streamEp = new RestEndpoint({ + path: '/files/:id' as const, + content: 'stream', + }); + async () => { + const result = await streamEp({ id: '1' }); + result satisfies ReadableStream; + }; + + // content: 'json' -> any (schema handles typing) + const jsonEp = new RestEndpoint({ + path: '/files/:id' as const, + content: 'json', + schema: Article, + }); + async () => { + const result = await jsonEp({ id: '1' }); + result satisfies any; + }; + + // no content -> any (unchanged) + const defaultEp = new RestEndpoint({ + path: '/files/:id' as const, + schema: Article, + }); + async () => { + const result = await defaultEp({ id: '1' }); + result satisfies any; + }; +}); + +it('content property: schema constraint', () => { + // @ts-expect-error - schema incompatible with content: 'blob' + new RestEndpoint({ path: '/x' as const, content: 'blob', schema: Article }); + + // @ts-expect-error - schema incompatible with content: 'text' + new RestEndpoint({ path: '/x' as const, content: 'text', schema: Article }); + + // @ts-expect-error - schema incompatible with content: 'arrayBuffer' + new RestEndpoint({ path: '/x' as const, content: 'arrayBuffer', schema: Article }); + + // @ts-expect-error - schema incompatible with content: 'stream' + new RestEndpoint({ path: '/x' as const, content: 'stream', schema: Article }); + + // content: 'json' allows schema + new RestEndpoint({ + path: '/x' as const, + content: 'json', + schema: Article, + }); + + // No content -> schema works as before + new RestEndpoint({ path: '/x' as const, schema: Article }); +}); + +it('content property: extend and subclass', () => { + const blobEp = new RestEndpoint({ + path: '/files/:id' as const, + content: 'blob', + }); + + // extend preserves content return type + const extended = blobEp.extend({ dataExpiryLength: 0 }); + async () => { + const result = await extended({ id: '1' }); + result satisfies Blob; + }; + + // extend with content change (schema: undefined needed when base has schema) + const jsonEp = new RestEndpoint({ + path: '/files/:id' as const, + schema: Article, + }); + const switched = jsonEp.extend({ content: 'blob', schema: undefined }); + async () => { + const result = await switched({ id: '1' }); + result satisfies Blob; + }; + + // process takes priority over content for return type + const withProcess = new RestEndpoint({ + path: '/x' as const, + content: 'blob', + process(value): { blob: Blob; name: string } { + return value; + }, + }); + async () => { + const result = await withProcess(); + result satisfies { blob: Blob; name: string }; + }; + + // content: 'blob' with explicit schema: undefined compiles + const explicitUndefined = new RestEndpoint({ + path: '/files/:id' as const, + content: 'blob', + schema: undefined, + }); + async () => { + const result = await explicitUndefined({ id: '1' }); + result satisfies Blob; + }; + + // switch from blob back to json + const backToJson = blobEp.extend({ content: 'json', schema: Article }); + async () => { + const result = await backToJson({ id: '1' }); + result satisfies any; + }; + + // content property is typed on instance + const ep = new RestEndpoint({ + path: '/files/:id' as const, + content: 'blob', + }); + const contentVal: + | 'json' + | 'blob' + | 'text' + | 'arrayBuffer' + | 'stream' + | undefined = ep.content; + + // subclass pattern + class BlobEndpoint extends RestEndpoint { + content = 'blob' as const; + } + const subclassed = new BlobEndpoint({ + path: '/files/:id' as const, + }); + expect(subclassed.content).toBe('blob'); +}); diff --git a/website/blog/2026-04-05-v0.17-content-property-binary-auto-detection.md b/website/blog/2026-04-05-v0.17-content-property-binary-auto-detection.md new file mode 100644 index 000000000000..0f637aca30fc --- /dev/null +++ b/website/blog/2026-04-05-v0.17-content-property-binary-auto-detection.md @@ -0,0 +1,87 @@ +--- +title: 'v0.17: Typed File Downloads, Binary Auto-Detection' +description: RestEndpoint content property for typed response parsing and automatic binary Content-Type detection +authors: [ntucker] +tags: [releases, rest] +draft: true +--- + +**New Features:** + +- [RestEndpoint `content` property](/blog/2026/04/05/v0.17-content-property-binary-auto-detection#content-property) - Typed file downloads, text responses, and streaming with a single property +- [Binary Content-Type auto-detection](/blog/2026/04/05/v0.17-content-property-binary-auto-detection#binary-auto-detection) - Images, PDFs, and other binary responses are automatically handled + + + +## RestEndpoint `content` property {#content-property} + +[RestEndpoint](/rest/api/RestEndpoint) now accepts a [`content`](/rest/api/RestEndpoint#content) property that controls +how the response body is parsed. The return type is inferred automatically from the value, and `schema` is +constrained to `undefined` at the type level for non-JSON content types. + +```typescript +import { RestEndpoint } from '@data-client/rest'; + +const downloadFile = new RestEndpoint({ + path: '/files/:id/download', + // highlight-next-line + content: 'blob', + dataExpiryLength: 0, +}); + +// Return type is Blob -- inferred from content, no explicit generics needed +const blob: Blob = await ctrl.fetch(downloadFile, { id: '123' }); +``` + +Previously, file downloads required a verbose `parseResponse` override. Now it's a single property. + +Accepted values: + +| Value | Parses via | Return type | +|---|---|---| +| `'json'` | `response.json()` | `any` | +| `'blob'` | `response.blob()` | `Blob` | +| `'text'` | `response.text()` | `string` | +| `'arrayBuffer'` | `response.arrayBuffer()` | `ArrayBuffer` | +| `'stream'` | `response.body` | `ReadableStream` | + +Setting `content` to a non-JSON value enforces `schema: undefined` at the type level, since binary data cannot be +normalized. A runtime check provides a clear error message if a normalizable schema is accidentally used. + +Works with `extend()` and subclasses: + +```typescript +// Extend to add blob support +const download = jsonEndpoint.extend({ + content: 'blob', + schema: undefined, +}); + +// Subclass pattern +class BlobEndpoint extends RestEndpoint { + content = 'blob' as const; +} +``` + +[#3868](https://github.com/reactive/data-client/pull/3868) - [`content` docs](/rest/api/RestEndpoint#content) + +## Binary Content-Type auto-detection {#binary-auto-detection} + +When `content` is not set, [`parseResponse`](/rest/api/RestEndpoint#parseResponse) now auto-detects binary +Content-Types and returns `response.blob()` instead of corrupting the data via `.text()`. + +Binary types like `image/*`, `audio/*`, `video/*`, `font/*`, `application/octet-stream`, `application/pdf`, and +others are now handled automatically. Text-like types (`text/*`, XML, HTML, JavaScript, CSS, etc.) continue to +use the existing text fallback with schema-aware error messages. + +This means endpoints that serve binary data with correct Content-Type headers work without any configuration: + +```typescript +const ep = new RestEndpoint({ + path: '/avatars/:id', +}); +// Server responds with Content-Type: image/png +// -> automatically returns a Blob (previously corrupted via .text()) +``` + +[#3868](https://github.com/reactive/data-client/pull/3868) From 5b7dd48cf6f85a25293956a86644d105c566ce64 Mon Sep 17 00:00:00 2001 From: Nathaniel Tucker Date: Sun, 5 Apr 2026 15:25:54 -0400 Subject: [PATCH 6/9] docs(skill): Supplementary endpoint section --- .cursor/skills/data-client-schema/SKILL.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.cursor/skills/data-client-schema/SKILL.md b/.cursor/skills/data-client-schema/SKILL.md index 7caf167f6ec8..b10cf941125d 100644 --- a/.cursor/skills/data-client-schema/SKILL.md +++ b/.cursor/skills/data-client-schema/SKILL.md @@ -93,14 +93,24 @@ export const EventResource = resource({ --- -## 5. Best Practices & Notes +## 5. Supplementary Endpoints (enrich existing entities) + +When an endpoint returns partial or differently-shaped data for an entity already in cache +(e.g., a metadata endpoint, a stats endpoint, a lazy-load expansion endpoint), +use the **same Entity** as the schema — don't create a wrapper entity. + +See [partial-entities](references/partial-entities.md) for patterns and examples. + +--- + +## 6. Best Practices & Notes - Always set up `schema` on every resource/entity/collection for normalization - Normalize deeply nested or relational data by defining proper schemas - Use `Entity.schema` for client-side joins - Use `Denormalize<>` type from rest/endpoint/graphql instead of InstanceType<>. This will handle all schemas like Unions, not just Entity. -## 6. Common Mistakes to Avoid +## 7. Common Mistakes to Avoid - Don't forget to use `fromJS()` or assign default properties for class fields - Manually merging or 'enriching' data; instead use `Entity.schema` for client-side joins From 115c762307b938de6e0da92e8987a48d611c0bc7 Mon Sep 17 00:00:00 2001 From: Nathaniel Tucker Date: Sun, 5 Apr 2026 15:37:56 -0400 Subject: [PATCH 7/9] fix: Inefficient regular expression --- website/static/codemods/v0.16.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/static/codemods/v0.16.js b/website/static/codemods/v0.16.js index 1c85a6724d92..355f5c703db4 100644 --- a/website/static/codemods/v0.16.js +++ b/website/static/codemods/v0.16.js @@ -21,7 +21,7 @@ const DATA_CLIENT_PACKAGES = new Set([ function transformPathString(s) { // :name(\d+) → :name (handles nested non-capturing groups) - s = s.replace(/:(\w+)\((?:[^()]*|\([^()]*\))*\)/g, ':$1'); + s = s.replace(/:(\w+)\([^()]*(?:\([^()]*\)[^()]*)*\)/g, ':$1'); // {group}? → {group} s = s.replace(/(\{[^}]+\})\?/g, '$1'); // /:name? → {/:name} (also handles - . ~ prefixes) From 467a5f6f9d4cdaf0927fa7e22520c5d2c1462ff5 Mon Sep 17 00:00:00 2001 From: Nathaniel Tucker Date: Sun, 5 Apr 2026 16:06:36 -0400 Subject: [PATCH 8/9] fix(normalizr): Use Object.keys() in deepClone to avoid inherited properties (#3875) * fix(normalizr): Use Object.keys() in deepClone to avoid inherited properties for...in iterates inherited properties, which could copy polluted Object.prototype entries. Object.keys() restricts to own enumerable properties only. Made-with: Cursor * internal: Add changeset for deepClone fix Made-with: Cursor --- .changeset/deepclone-own-properties.md | 7 +++++++ packages/normalizr/src/normalize/normalize.imm.ts | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 .changeset/deepclone-own-properties.md diff --git a/.changeset/deepclone-own-properties.md b/.changeset/deepclone-own-properties.md new file mode 100644 index 000000000000..46bd9b8f602b --- /dev/null +++ b/.changeset/deepclone-own-properties.md @@ -0,0 +1,7 @@ +--- +'@data-client/normalizr': patch +--- + +Fix deepClone to only copy own properties + +`deepClone` in the immutable store path now uses `Object.keys()` instead of `for...in`, preventing inherited properties from being copied into cloned state. diff --git a/packages/normalizr/src/normalize/normalize.imm.ts b/packages/normalizr/src/normalize/normalize.imm.ts index 0dff28d2fdb6..00d97974af2e 100644 --- a/packages/normalizr/src/normalize/normalize.imm.ts +++ b/packages/normalizr/src/normalize/normalize.imm.ts @@ -169,7 +169,7 @@ function deepClone(obj: any): any { if (obj === null || typeof obj !== 'object') return obj; if (Array.isArray(obj)) return obj.map(deepClone); const result: any = {}; - for (const key in obj) { + for (const key of Object.keys(obj)) { result[key] = deepClone(obj[key]); } return result; From f5797b4deb2b1c24534e29bf7e8b61e9918b20d0 Mon Sep 17 00:00:00 2001 From: Nathaniel Tucker Date: Sun, 5 Apr 2026 16:48:49 -0400 Subject: [PATCH 9/9] internal(bench-react): Add V8 opt/deopt investigation (#3874) * internal(bench-react): Add V8 opt/deopt investigation Add BENCH_V8_TRACE and BENCH_V8_DEOPT env vars to the React benchmark runner. Trace mode uses launchServer to pipe Chromium's --trace-opt --trace-deopt output to v8-trace.log. Deopt mode passes --prof to write per-process V8 profiling logs to v8-logs/. Convenience scripts bench:trace and bench:deopt default to data-client small scenarios. Made-with: Cursor * fix: Bugbot * fix: Stale V8 logs corrupt "largest file" heuristic --- .cursor/rules/benchmarking.mdc | 15 ++++ .gitignore | 1 + examples/benchmark-react/AGENTS.md | 2 + examples/benchmark-react/README.md | 30 ++++++- examples/benchmark-react/bench/runner.ts | 108 ++++++++++++++++++++++- examples/benchmark-react/package.json | 2 + 6 files changed, 154 insertions(+), 4 deletions(-) diff --git a/.cursor/rules/benchmarking.mdc b/.cursor/rules/benchmarking.mdc index c9f8873b42e7..1dd931a631ea 100644 --- a/.cursor/rules/benchmarking.mdc +++ b/.cursor/rules/benchmarking.mdc @@ -69,6 +69,21 @@ Use this mapping when deciding which React benchmark scenarios are relevant to a Regressions >5% on stable scenarios or >15% on volatile scenarios are worth investigating. +### Profiling / tracing (opt + deopt investigation) + +The React benchmark supports the same V8 opt/deopt investigation as the Node benchmark, via Chromium's `--js-flags`: + +- **`bench:trace`** (`BENCH_V8_TRACE=true`): launches Chromium with `--trace-opt --trace-deopt`; browser process output piped to `v8-trace.log`. Equivalent to `examples/benchmark`'s `start:trace`. +- **`bench:deopt`** (`BENCH_V8_DEOPT=true`): launches Chromium with `--prof`; V8 writes per-process logs to `v8-logs/v8-.log`. Process the renderer log (largest file) with `node --prof-process`. + +Both default to `--lib data-client --size small` for focused runs. Override with additional flags: + +```bash +yarn workspace example-benchmark-react bench:trace +yarn workspace example-benchmark-react bench:deopt +BENCH_V8_TRACE=true yarn workspace example-benchmark-react bench --scenario update-entity +``` + ### When to use Node vs React benchmark - **Core/normalizr/endpoint changes only** (no rendering impact): Run `examples/benchmark` (Node). Faster iteration, no browser needed. diff --git a/.gitignore b/.gitignore index b73464eb6524..fa444b77ffd5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Logs logs *.log +v8-logs/ npm-debug.log* yarn-debug.log* yarn-error.log* diff --git a/examples/benchmark-react/AGENTS.md b/examples/benchmark-react/AGENTS.md index ac3d67d43874..bbce217dcc65 100644 --- a/examples/benchmark-react/AGENTS.md +++ b/examples/benchmark-react/AGENTS.md @@ -73,3 +73,5 @@ Filtering: `yarn bench --lib data-client --size small --action update` | `BENCH_LABEL=` | Appends `[]` to result names | | `BENCH_PORT` | Preview port (default 5173) | | `BENCH_TRACE=true` | Chrome tracing for duration scenarios | +| `BENCH_V8_TRACE=true` | Launch Chromium with `--trace-opt --trace-deopt`; output to `v8-trace.log` | +| `BENCH_V8_DEOPT=true` | Launch Chromium with `--prof`; V8 logs to `v8-logs/` | diff --git a/examples/benchmark-react/README.md b/examples/benchmark-react/README.md index 24c331cd9950..5709f19d0605 100644 --- a/examples/benchmark-react/README.md +++ b/examples/benchmark-react/README.md @@ -210,6 +210,34 @@ The runner prints a JSON array in `customBiggerIsBetter` format (name, unit, val To view results locally, open `bench/report-viewer.html` in a browser and paste the JSON (or upload `react-bench-output.json`) to see a comparison table and bar chart. -## Optional: Chrome trace +## Profiling + +### Chrome trace (timeline duration) Set `BENCH_TRACE=true` when running the bench to enable Chrome tracing for duration scenarios. Trace files are written to disk; parsing and reporting trace duration is best-effort and may require additional tooling for the trace zip format. + +### V8 opt/deopt investigation + +For the same granularity of V8 optimization investigation available in `examples/benchmark` (Node), two modes pass `--js-flags` to Chromium via Playwright: + +```bash +yarn bench:trace # --trace-opt --trace-deopt → v8-trace.log +yarn bench:deopt # --prof → v8-logs/v8-.log +``` + +Both default to `--lib data-client --size small` for focused, fast investigation. Add other flags as needed (e.g. `--scenario update-entity`). + +**`bench:trace`** (`BENCH_V8_TRACE=true`) launches Chromium with `--js-flags="--trace-opt --trace-deopt"`. The runner uses Playwright `launchServer` (not `launch`) so the root browser process stdout/stderr can be piped to `v8-trace.log` — `Browser` from `launch()` does not expose `process()`. This is the browser equivalent of `examples/benchmark`'s `start:trace` — look for optimization and deoptimization lines for functions of interest. + +**`bench:deopt`** (`BENCH_V8_DEOPT=true`) launches Chromium with `--js-flags="--prof"`. V8 writes per-process profiling logs to `v8-logs/v8-.log`. Chromium is multi-process, so several files are created; the renderer log (typically the largest) contains the benchmark's hot path. Process it with: + +```bash +node --prof-process v8-logs/v8-.log > processed.txt +``` + +Both env vars can be combined for simultaneous trace output and profiling logs. The convenience scripts can be overridden: + +```bash +BENCH_V8_TRACE=true yarn bench --lib data-client --scenario update-entity +BENCH_V8_DEOPT=true yarn bench --lib data-client --size large +``` diff --git a/examples/benchmark-react/bench/runner.ts b/examples/benchmark-react/bench/runner.ts index e32912e0e1c1..f486030ab7c9 100644 --- a/examples/benchmark-react/bench/runner.ts +++ b/examples/benchmark-react/bench/runner.ts @@ -1,6 +1,14 @@ /// +import * as fs from 'node:fs'; +import * as path from 'node:path'; import { chromium } from 'playwright'; -import type { Browser, CDPSession, Locator, Page } from 'playwright'; +import type { + Browser, + BrowserServer, + CDPSession, + Locator, + Page, +} from 'playwright'; import { collectMeasures, getMeasureDuration } from './measure.js'; import { collectHeapUsed } from './memory.js'; @@ -141,6 +149,9 @@ const BASE_URL = const BENCH_LABEL = process.env.BENCH_LABEL ? ` [${process.env.BENCH_LABEL}]` : ''; const USE_TRACE = process.env.BENCH_TRACE === 'true'; +const BENCH_V8_TRACE = process.env.BENCH_V8_TRACE === 'true'; +const BENCH_V8_DEOPT = process.env.BENCH_V8_DEOPT === 'true'; +const V8_LOG_DIR = path.resolve('v8-logs'); const MEMORY_WARMUP = 1; const MEMORY_MEASUREMENTS = 3; @@ -649,6 +660,93 @@ function shuffle(arr: T[]): T[] { return out; } +/** Chromium from `launch()` does not expose `process()`; use `launchServer` when piping V8 trace output. */ +async function launchBenchChromium(): Promise<{ + browser: Browser; + closeBenchBrowser: () => Promise; +}> { + const launchOpts = { + headless: true, + args: buildV8LaunchArgs(), + }; + + if (BENCH_V8_TRACE) { + const server: BrowserServer = await chromium.launchServer(launchOpts); + let v8TraceStream: fs.WriteStream | undefined; + const proc = server.process(); + if (proc?.stderr ?? proc?.stdout) { + v8TraceStream = fs.createWriteStream('v8-trace.log'); + proc.stderr?.pipe(v8TraceStream, { end: false }); + proc.stdout?.pipe(v8TraceStream, { end: false }); + process.stderr.write( + 'V8 trace output → v8-trace.log (root browser process stderr/stdout)\n', + ); + } else { + process.stderr.write( + 'Warning: BENCH_V8_TRACE but browser server process streams unavailable; v8-trace.log may be empty.\n', + ); + } + const browser = await chromium.connect({ wsEndpoint: server.wsEndpoint() }); + return { + browser, + closeBenchBrowser: async () => { + await browser.close(); + await server.close(); + if (v8TraceStream) { + v8TraceStream.end(); + process.stderr.write( + '\nV8 opt/deopt trace written to v8-trace.log\n', + ); + } + }, + }; + } + + const browser = await chromium.launch(launchOpts); + return { + browser, + closeBenchBrowser: () => browser.close(), + }; +} + +function buildV8LaunchArgs(): string[] { + const jsFlags: string[] = []; + if (BENCH_V8_TRACE) { + jsFlags.push('--trace-opt', '--trace-deopt'); + } + if (BENCH_V8_DEOPT) { + fs.rmSync(V8_LOG_DIR, { recursive: true, force: true }); + fs.mkdirSync(V8_LOG_DIR, { recursive: true }); + jsFlags.push('--prof', `--logfile=${V8_LOG_DIR}/v8-%p.log`); + } + if (jsFlags.length === 0) return []; + return [`--js-flags=${jsFlags.join(' ')}`]; +} + +function reportV8Logs(): void { + if (!BENCH_V8_DEOPT) return; + try { + const logs = fs.readdirSync(V8_LOG_DIR).filter(f => f.endsWith('.log')); + if (logs.length === 0) return; + process.stderr.write(`\nV8 profiling logs written to ${V8_LOG_DIR}/:\n`); + for (const log of logs) { + const size = fs.statSync(path.join(V8_LOG_DIR, log)).size; + process.stderr.write(` ${log} (${(size / 1024).toFixed(1)} KB)\n`); + } + const largest = logs.reduce((a, b) => { + const sa = fs.statSync(path.join(V8_LOG_DIR, a)).size; + const sb = fs.statSync(path.join(V8_LOG_DIR, b)).size; + return sa >= sb ? a : b; + }); + process.stderr.write( + `\nProcess the renderer log (typically the largest file) with:\n` + + ` node --prof-process ${V8_LOG_DIR}/${largest}\n\n`, + ); + } catch { + // best-effort reporting + } +} + function scenarioUnit(scenario: Scenario): string { if (isRefStabilityScenario(scenario)) return 'count'; if (scenario.resultMetric === 'heapDelta') return 'bytes'; @@ -778,7 +876,10 @@ async function main() { samples.set(s.name, { value: [], reactCommit: [], trace: [] }); } - const browser = await chromium.launch({ headless: true }); + const { browser, closeBenchBrowser } = await launchBenchChromium(); + if (BENCH_V8_DEOPT) { + process.stderr.write(`V8 profiling logs → ${V8_LOG_DIR}/\n`); + } // Deterministic scenarios: run once, no warmup const deterministicNames = new Set(); @@ -924,7 +1025,8 @@ async function main() { } } - await browser.close(); + await closeBenchBrowser(); + reportV8Logs(); // --------------------------------------------------------------------------- // Report diff --git a/examples/benchmark-react/package.json b/examples/benchmark-react/package.json index c16914666d22..b0ad7df30df7 100644 --- a/examples/benchmark-react/package.json +++ b/examples/benchmark-react/package.json @@ -12,6 +12,8 @@ "bench:small": "npx tsx bench/runner.ts --size small", "bench:large": "npx tsx bench/runner.ts --size large", "bench:dc": "npx tsx bench/runner.ts --lib data-client", + "bench:trace": "BENCH_V8_TRACE=true npx tsx bench/runner.ts --lib data-client --size small", + "bench:deopt": "BENCH_V8_DEOPT=true npx tsx bench/runner.ts --lib data-client --size small", "bench:run": "yarn build && (yarn preview &) && sleep 5 && yarn bench", "bench:run:no-compiler": "yarn build:no-compiler && (yarn preview &) && sleep 5 && yarn bench:no-compiler", "validate": "npx tsx bench/validate.ts",