From c8858e97f7cf1a305b032ad3e88adab3521427e3 Mon Sep 17 00:00:00 2001 From: mshd Date: Thu, 27 Aug 2026 23:11:50 +0800 Subject: [PATCH] duckdb --- .gitignore | 1 + README.md | 2 + apps/duckdb-admin/README.md | 16 + apps/duckdb-admin/app/Admin.tsx | 10 + apps/duckdb-admin/app/AdminApp.tsx | 73 ++ apps/duckdb-admin/app/api/[resource]/route.ts | 63 ++ apps/duckdb-admin/app/layout.tsx | 15 + apps/duckdb-admin/app/page.tsx | 5 + apps/duckdb-admin/next-env.d.ts | 5 + apps/duckdb-admin/next.config.js | 37 + apps/duckdb-admin/package.json | 27 + apps/duckdb-admin/tsconfig.json | 25 + packages/ra-data-duckdb/README.md | 81 ++ packages/ra-data-duckdb/jest.config.ts | 19 + packages/ra-data-duckdb/package.json | 74 ++ packages/ra-data-duckdb/src/Http.ts | 76 ++ packages/ra-data-duckdb/src/createHandler.ts | 70 ++ packages/ra-data-duckdb/src/dataProvider.ts | 164 +++ packages/ra-data-duckdb/src/defaultHandler.ts | 126 +++ packages/ra-data-duckdb/src/deleteHandler.ts | 34 + .../ra-data-duckdb/src/deleteManyHandler.ts | 40 + packages/ra-data-duckdb/src/duckdb.ts | 39 + packages/ra-data-duckdb/src/extractOrderBy.ts | 17 + .../ra-data-duckdb/src/extractSkipTake.ts | 22 + packages/ra-data-duckdb/src/extractWhere.ts | 214 ++++ packages/ra-data-duckdb/src/getListHandler.ts | 95 ++ packages/ra-data-duckdb/src/getManyHandler.ts | 46 + .../src/getManyReferenceHandler.ts | 78 ++ packages/ra-data-duckdb/src/getOneHandler.ts | 42 + packages/ra-data-duckdb/src/getTable.ts | 13 + packages/ra-data-duckdb/src/index.ts | 19 + .../ra-data-duckdb/src/lib/NotFoundError.ts | 9 + packages/ra-data-duckdb/src/lib/SqlParams.ts | 18 + packages/ra-data-duckdb/src/lib/isNotField.ts | 7 + packages/ra-data-duckdb/src/lib/quoteIdent.ts | 9 + .../ra-data-duckdb/src/mapPrimaryKeyToId.ts | 32 + packages/ra-data-duckdb/src/server.ts | 18 + packages/ra-data-duckdb/src/updateHandler.ts | 82 ++ .../ra-data-duckdb/src/updateManyHandler.ts | 42 + .../ra-data-duckdb/tests/handlers.test.ts | 384 +++++++ packages/ra-data-duckdb/tsconfig.json | 24 + pnpm-lock.yaml | 965 ++++++++++-------- 42 files changed, 2690 insertions(+), 448 deletions(-) create mode 100644 apps/duckdb-admin/README.md create mode 100644 apps/duckdb-admin/app/Admin.tsx create mode 100644 apps/duckdb-admin/app/AdminApp.tsx create mode 100644 apps/duckdb-admin/app/api/[resource]/route.ts create mode 100644 apps/duckdb-admin/app/layout.tsx create mode 100644 apps/duckdb-admin/app/page.tsx create mode 100644 apps/duckdb-admin/next-env.d.ts create mode 100644 apps/duckdb-admin/next.config.js create mode 100644 apps/duckdb-admin/package.json create mode 100644 apps/duckdb-admin/tsconfig.json create mode 100644 packages/ra-data-duckdb/README.md create mode 100644 packages/ra-data-duckdb/jest.config.ts create mode 100644 packages/ra-data-duckdb/package.json create mode 100644 packages/ra-data-duckdb/src/Http.ts create mode 100644 packages/ra-data-duckdb/src/createHandler.ts create mode 100644 packages/ra-data-duckdb/src/dataProvider.ts create mode 100644 packages/ra-data-duckdb/src/defaultHandler.ts create mode 100644 packages/ra-data-duckdb/src/deleteHandler.ts create mode 100644 packages/ra-data-duckdb/src/deleteManyHandler.ts create mode 100644 packages/ra-data-duckdb/src/duckdb.ts create mode 100644 packages/ra-data-duckdb/src/extractOrderBy.ts create mode 100644 packages/ra-data-duckdb/src/extractSkipTake.ts create mode 100644 packages/ra-data-duckdb/src/extractWhere.ts create mode 100644 packages/ra-data-duckdb/src/getListHandler.ts create mode 100644 packages/ra-data-duckdb/src/getManyHandler.ts create mode 100644 packages/ra-data-duckdb/src/getManyReferenceHandler.ts create mode 100644 packages/ra-data-duckdb/src/getOneHandler.ts create mode 100644 packages/ra-data-duckdb/src/getTable.ts create mode 100644 packages/ra-data-duckdb/src/index.ts create mode 100644 packages/ra-data-duckdb/src/lib/NotFoundError.ts create mode 100644 packages/ra-data-duckdb/src/lib/SqlParams.ts create mode 100644 packages/ra-data-duckdb/src/lib/isNotField.ts create mode 100644 packages/ra-data-duckdb/src/lib/quoteIdent.ts create mode 100644 packages/ra-data-duckdb/src/mapPrimaryKeyToId.ts create mode 100644 packages/ra-data-duckdb/src/server.ts create mode 100644 packages/ra-data-duckdb/src/updateHandler.ts create mode 100644 packages/ra-data-duckdb/src/updateManyHandler.ts create mode 100644 packages/ra-data-duckdb/tests/handlers.test.ts create mode 100644 packages/ra-data-duckdb/tsconfig.json diff --git a/.gitignore b/.gitignore index dd37e8f..b85c72a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ coverage out/ build dist +*.tsbuildinfo # misc .DS_Store diff --git a/README.md b/README.md index f66e77b..72be1d0 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,14 @@ Check the example apps to see common usages or use them as a boilerplate. # Packages - [ra-data-simple-prisma](./packages/ra-data-simple-prisma/) +- [ra-data-duckdb](./packages/ra-data-duckdb/) - [next-auth-prisma-adapter](./packages/next-auth-prisma-adapter/) # Examples [example admin app](./apps/admin/) Debug, test, and develop the packages, but also use it as the admin/CMS for the website! [example website](./apps/website/) A nextjs/mui boilerplate to show the data (very much under construction yet) +[DuckDB admin example](./apps/duckdb-admin/) Minimal in-memory DuckDB and React Admin app. ### Development diff --git a/apps/duckdb-admin/README.md b/apps/duckdb-admin/README.md new file mode 100644 index 0000000..e58d0d0 --- /dev/null +++ b/apps/duckdb-admin/README.md @@ -0,0 +1,16 @@ +# DuckDB Admin Example + +A minimal Next.js app using React Admin and the workspace's +`ra-data-duckdb` package. + +The API creates an in-memory DuckDB database with three users when the server +starts. Changes persist until the development server restarts. + +```sh +pnpm --filter duckdb-admin-example dev +``` + +Open [http://localhost:3030](http://localhost:3030). + +Scripts pass `NODE_OPTIONS=--no-webstorage` because Node.js 25+ ships a partial +`localStorage` that breaks Next.js 15.3 SSR. diff --git a/apps/duckdb-admin/app/Admin.tsx b/apps/duckdb-admin/app/Admin.tsx new file mode 100644 index 0000000..9cdcffd --- /dev/null +++ b/apps/duckdb-admin/app/Admin.tsx @@ -0,0 +1,10 @@ +"use client"; + +import dynamic from "next/dynamic"; + +const AdminApp = dynamic(() => import("./AdminApp"), { + ssr: false, + loading: () =>

Loading admin…

, +}); + +export default AdminApp; diff --git a/apps/duckdb-admin/app/AdminApp.tsx b/apps/duckdb-admin/app/AdminApp.tsx new file mode 100644 index 0000000..8850d24 --- /dev/null +++ b/apps/duckdb-admin/app/AdminApp.tsx @@ -0,0 +1,73 @@ +"use client"; + +import { + Admin, + BooleanField, + BooleanInput, + Create, + Datagrid, + Edit, + EditButton, + List, + NumberField, + NumberInput, + Resource, + SimpleForm, + TextField, + TextInput, + required, +} from "react-admin"; +import { dataProvider } from "ra-data-duckdb"; + +const usersProvider = dataProvider("/api"); + +const userFilters = [ + , +]; + +const UserList = () => ( + + + + + + + + + + +); + +const UserForm = () => ( + + + + + + +); + +const UserCreate = () => ( + + + +); + +const UserEdit = () => ( + + + +); + +const AdminApp = () => ( + + + +); + +export default AdminApp; diff --git a/apps/duckdb-admin/app/api/[resource]/route.ts b/apps/duckdb-admin/app/api/[resource]/route.ts new file mode 100644 index 0000000..86e1315 --- /dev/null +++ b/apps/duckdb-admin/app/api/[resource]/route.ts @@ -0,0 +1,63 @@ +import { DuckDBInstance } from "@duckdb/node-api"; +import { + defaultHandler, + DuckDBExecutor, + fromDuckDBConnection, + NotFoundError, +} from "ra-data-duckdb/server"; +import { NextResponse } from "next/server"; + +export const runtime = "nodejs"; + +const globalForDuckDB = globalThis as typeof globalThis & { + duckDBDemo?: Promise; +}; + +const createDatabase = async (): Promise => { + const instance = await DuckDBInstance.create(":memory:"); + const connection = await instance.connect(); + const db = fromDuckDBConnection(connection); + + await db.run("CREATE SEQUENCE users_id_seq START 4"); + await db.run(` + CREATE TABLE users ( + id INTEGER PRIMARY KEY DEFAULT nextval('users_id_seq'), + name VARCHAR NOT NULL, + email VARCHAR NOT NULL, + age INTEGER, + active BOOLEAN DEFAULT true + ) + `); + await db.run(` + INSERT INTO users (id, name, email, age, active) + VALUES + (1, 'Ada Lovelace', 'ada@example.com', 36, true), + (2, 'Grace Hopper', 'grace@example.com', 85, true), + (3, 'Alan Turing', 'alan@example.com', 41, false) + `); + + return db; +}; + +const getDatabase = () => { + globalForDuckDB.duckDBDemo ??= createDatabase(); + return globalForDuckDB.duckDBDemo; +}; + +const handler = async (request: Request) => { + try { + const payload = await request.json(); + const db = await getDatabase(); + const result = await defaultHandler(payload, db, { + getList: { searchColumns: ["name", "email"] }, + }); + + return NextResponse.json(result); + } catch (error) { + const message = error instanceof Error ? error.message : "Unknown error"; + const status = error instanceof NotFoundError ? error.status : 400; + return NextResponse.json({ message }, { status }); + } +}; + +export { handler as GET, handler as POST }; diff --git a/apps/duckdb-admin/app/layout.tsx b/apps/duckdb-admin/app/layout.tsx new file mode 100644 index 0000000..390f1b4 --- /dev/null +++ b/apps/duckdb-admin/app/layout.tsx @@ -0,0 +1,15 @@ +import type { Metadata } from "next"; +import type { ReactNode } from "react"; + +export const metadata: Metadata = { + title: "React Admin + DuckDB", + description: "Minimal ra-data-duckdb example", +}; + +export default function RootLayout({ children }: { children: ReactNode }) { + return ( + + {children} + + ); +} diff --git a/apps/duckdb-admin/app/page.tsx b/apps/duckdb-admin/app/page.tsx new file mode 100644 index 0000000..0f85aa2 --- /dev/null +++ b/apps/duckdb-admin/app/page.tsx @@ -0,0 +1,5 @@ +import Admin from "./Admin"; + +export default function HomePage() { + return ; +} diff --git a/apps/duckdb-admin/next-env.d.ts b/apps/duckdb-admin/next-env.d.ts new file mode 100644 index 0000000..1b3be08 --- /dev/null +++ b/apps/duckdb-admin/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/duckdb-admin/next.config.js b/apps/duckdb-admin/next.config.js new file mode 100644 index 0000000..e100f86 --- /dev/null +++ b/apps/duckdb-admin/next.config.js @@ -0,0 +1,37 @@ +const path = require("path"); + +/** @type {import('next').NextConfig} */ +const nextConfig = { + serverExternalPackages: ["@duckdb/node-api"], + transpilePackages: [ + "ra-data-duckdb", + "react-admin", + "ra-core", + "ra-ui-materialui", + "ra-i18n-polyglot", + "ra-language-english", + "@mui/material", + "@mui/icons-material", + "@mui/system", + "@mui/utils", + ], + modularizeImports: { + "@mui/icons-material": { + transform: "@mui/icons-material/{{member}}", + }, + }, + webpack: (config) => { + config.resolve.alias = { + ...config.resolve.alias, + // ESM ra-ui-materialui default-imports CJS icon files; without this, + // webpack treats the CJS module namespace as the component (got: object). + "@mui/icons-material": path.resolve( + __dirname, + "../../node_modules/@mui/icons-material/esm" + ), + }; + return config; + }, +}; + +module.exports = nextConfig; diff --git a/apps/duckdb-admin/package.json b/apps/duckdb-admin/package.json new file mode 100644 index 0000000..7b2a6e8 --- /dev/null +++ b/apps/duckdb-admin/package.json @@ -0,0 +1,27 @@ +{ + "name": "duckdb-admin-example", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "NODE_OPTIONS=--no-webstorage next dev -p 3030", + "build": "NODE_OPTIONS=--no-webstorage next build", + "start": "NODE_OPTIONS=--no-webstorage next start -p 3030" + }, + "dependencies": { + "@duckdb/node-api": "1.5.5-r.4", + "@mui/icons-material": "5.17.1", + "@mui/material": "5.17.1", + "next": "15.3.0", + "ra-data-duckdb": "workspace:*", + "react": "19.1.0", + "react-admin": "^5.14.1", + "react-dom": "19.1.0", + "react-hook-form": "^7.65.0" + }, + "devDependencies": { + "@types/node": "^22", + "@types/react": "19.1.1", + "@types/react-dom": "19.1.2", + "typescript": "^5.9.3" + } +} diff --git a/apps/duckdb-admin/tsconfig.json b/apps/duckdb-admin/tsconfig.json new file mode 100644 index 0000000..287a27e --- /dev/null +++ b/apps/duckdb-admin/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": false, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [{ "name": "next" }] + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts" + ], + "exclude": ["node_modules"] +} diff --git a/packages/ra-data-duckdb/README.md b/packages/ra-data-duckdb/README.md new file mode 100644 index 0000000..491d618 --- /dev/null +++ b/packages/ra-data-duckdb/README.md @@ -0,0 +1,81 @@ +# React Admin + DuckDB + +Simple react-admin data provider backed by [DuckDB](https://duckdb.org/). Same request shape as `ra-data-simple-prisma`: the browser talks HTTP, the server runs SQL. + +### Installation + +``` +pnpm i ra-data-duckdb @duckdb/node-api +``` + +### Frontend + +```tsx +import { Admin, Resource } from "react-admin"; +import { dataProvider } from "ra-data-duckdb"; + +const ReactAdmin = () => ( + + + +); +``` + +### Backend (Next.js App Router) + +```ts +// app/api/[resource]/route.ts +import { DuckDBInstance } from "@duckdb/node-api"; +import { defaultHandler, fromDuckDBConnection } from "ra-data-duckdb/server"; +import { NextResponse } from "next/server"; + +const instance = await DuckDBInstance.create("data.duckdb"); +const connection = await instance.connect(); +const db = fromDuckDBConnection(connection); + +const handler = async (req: Request) => { + const body = await req.json(); + const result = await defaultHandler(body, db, { + // optional: map react-admin resource names to table names + resourceToTableMap: { users: "app_users" }, + }); + return NextResponse.json(result); +}; + +export { handler as GET, handler as POST }; +``` + +### Custom executor + +Handlers take a minimal `DuckDBExecutor` (`all` / `run`). Use `fromDuckDBConnection` for `@duckdb/node-api`, or supply your own: + +```ts +import { defaultHandler, DuckDBExecutor } from "ra-data-duckdb/server"; + +const db: DuckDBExecutor = { + all: async (sql, params) => { /* ... */ }, + run: async (sql, params) => { /* ... */ }, +}; + +await defaultHandler(body, db); +``` + +### Filters + +Common react-admin filters are mapped to SQL: + +| Filter | SQL | +| --- | --- | +| `age: 30` / `age_eq` | `"age" = $p` | +| `age_gte` / `_gt` / `_lte` / `_lt` | comparison | +| `name: "Al"` (string) | `LIKE '%Al%'` (case-insensitive by default) | +| `id_in: [1,2]` / array value | `IN (...)` | +| `q` + `searchColumns` | OR of LIKE across columns | +| `OR` / `AND` / `NOT` | grouped boolean | + +Resource and column names are validated and quoted; only `[A-Za-z_][A-Za-z0-9_]*` identifiers are allowed. + +### Notes + +- DuckDB is not ideal for high-concurrency multi-writer admin apps; prefer a single writer or an analytics/read-heavy use case. +- Audit logs and Prisma-style relation `include` / `connect` are not ported — keep v1 flat-table CRUD. diff --git a/packages/ra-data-duckdb/jest.config.ts b/packages/ra-data-duckdb/jest.config.ts new file mode 100644 index 0000000..5ef8077 --- /dev/null +++ b/packages/ra-data-duckdb/jest.config.ts @@ -0,0 +1,19 @@ +import type { Config } from "jest"; + +const config: Config = { + testEnvironment: "node", + transform: { + "^.+\\.(t|j)sx?$": [ + "babel-jest", + { + presets: [ + ["@babel/preset-env", { targets: { node: "current" } }], + "@babel/preset-typescript", + ], + }, + ], + }, + testMatch: ["**/tests/**/*.test.ts"], +}; + +export default config; diff --git a/packages/ra-data-duckdb/package.json b/packages/ra-data-duckdb/package.json new file mode 100644 index 0000000..78ae076 --- /dev/null +++ b/packages/ra-data-duckdb/package.json @@ -0,0 +1,74 @@ +{ + "name": "ra-data-duckdb", + "version": "0.0.1", + "description": "Simple react-admin dataprovider for DuckDB", + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "scripts": { + "build": "tsup src/index.ts src/server.ts --format esm,cjs --dts --treeshake --external react-admin --external ra-core --external ra-ui-materialui --external @duckdb/node-api", + "dev": "pnpm build --watch", + "lint": "eslint src --fix", + "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist", + "prepublish": "pnpm build", + "test": "jest" + }, + "files": [ + "/dist" + ], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.js" + }, + "./server": { + "types": "./dist/server.d.ts", + "import": "./dist/server.mjs", + "require": "./dist/server.js" + } + }, + "keywords": [ + "duckdb", + "rest", + "marmelab", + "react-admin", + "dataprovider", + "admin", + "react" + ], + "repository": { + "type": "git", + "url": "https://github.com/codeledge/ra-data-simple-prisma.git", + "directory": "packages/ra-data-duckdb" + }, + "author": "Orlando Groppo ", + "license": "MIT", + "peerDependencies": { + "@duckdb/node-api": "*", + "react-admin": ">=5.7.2" + }, + "peerDependenciesMeta": { + "@duckdb/node-api": { + "optional": true + } + }, + "dependencies": { + "axios": "^1.13.5", + "deverything": "^5.5.1" + }, + "devDependencies": { + "@babel/core": "7.29.0", + "@babel/preset-env": "^7.29.0", + "@babel/preset-typescript": "^7.28.5", + "@duckdb/node-api": "1.5.5-r.4", + "@types/jest": "^30.0.0", + "@types/node": "^25.2.3", + "eslint-config-custom": "workspace:*", + "jest": "^30.2.0", + "react-admin": "^5.14.1", + "ts-node": "^10.9.2", + "tsup": "^8.5.1", + "typescript": "^5.9.3" + } +} diff --git a/packages/ra-data-duckdb/src/Http.ts b/packages/ra-data-duckdb/src/Http.ts new file mode 100644 index 0000000..b4a487f --- /dev/null +++ b/packages/ra-data-duckdb/src/Http.ts @@ -0,0 +1,76 @@ +import type { + CreateParams, + DeleteManyParams, + DeleteParams, + GetListParams, + GetManyParams, + GetManyReferenceParams, + GetOneParams, + UpdateManyParams, + UpdateParams, +} from "react-admin"; + +export type RaPayload = + | GetListRequest + | GetOneRequest + | GetManyRequest + | GetManyReferenceRequest + | CreateRequest + | UpdateRequest + | UpdateManyRequest + | DeleteRequest + | DeleteManyRequest; + +export type GetListRequest = { + method: "getList"; + params: GetListParams; + resource: T; +}; + +export type GetOneRequest = { + method: "getOne"; + params: GetOneParams; + resource: T; +}; + +export type GetManyRequest = { + method: "getMany"; + params: GetManyParams; + resource: T; +}; + +export type GetManyReferenceRequest = { + method: "getManyReference"; + params: GetManyReferenceParams; + resource: T; +}; + +export type CreateRequest = { + method: "create"; + params: CreateParams; + resource: T; +}; + +export type UpdateRequest = { + method: "update"; + params: UpdateParams; + resource: T; +}; + +export type UpdateManyRequest = { + method: "updateMany"; + params: UpdateManyParams; + resource: T; +}; + +export type DeleteRequest = { + method: "delete"; + params: DeleteParams; + resource: T; +}; + +export type DeleteManyRequest = { + method: "deleteMany"; + params: DeleteManyParams; + resource: T; +}; diff --git a/packages/ra-data-duckdb/src/createHandler.ts b/packages/ra-data-duckdb/src/createHandler.ts new file mode 100644 index 0000000..fbbaff3 --- /dev/null +++ b/packages/ra-data-duckdb/src/createHandler.ts @@ -0,0 +1,70 @@ +import { DuckDBExecutor } from "./duckdb"; +import { getTable, type ResourceToTableMap } from "./getTable"; +import type { CreateRequest } from "./Http"; +import { isNotField } from "./lib/isNotField"; +import { quoteIdent } from "./lib/quoteIdent"; +import { SqlParams } from "./lib/SqlParams"; +import { mapPrimaryKeyToId } from "./mapPrimaryKeyToId"; + +export type CreateOptions = { + allowOnlyFields?: Record; + debug?: boolean; + primaryKey?: string; + resourceToTableMap?: ResourceToTableMap; +}; + +const sanitizeWriteData = ( + data: Record, + options?: CreateOptions, +) => { + const primaryKey = options?.primaryKey ?? "id"; + const cleaned: Record = {}; + + Object.entries(data).forEach(([key, value]) => { + // With a custom primary key, `id` is only React-admin's response alias. + if (primaryKey !== "id" && key === "id") return; + if (primaryKey !== "id" && key === primaryKey) { + throw new Error( + `createHandler: Field ${key} is reserved when primaryKey is configured; use id in responses and omit the original primary key from writes`, + ); + } + if (value === "") return; + if (isNotField(key)) return; + if (options?.allowOnlyFields && !options.allowOnlyFields[key]) { + throw new Error(`createHandler: Field ${key} is not allowed in create`); + } + cleaned[key] = value; + }); + + return cleaned; +}; + +export const createHandler = async ( + req: CreateRequest, + db: DuckDBExecutor, + options?: CreateOptions, +) => { + const table = getTable(req, options?.resourceToTableMap); + const primaryKey = options?.primaryKey ?? "id"; + const data = sanitizeWriteData(req.params.data as Record, options); + + if (options?.debug) console.log("createHandler:data", data); + + const columns = Object.keys(data); + if (!columns.length) { + throw new Error("createHandler: no fields to insert"); + } + + const params = new SqlParams(); + const placeholders = columns.map((col) => params.add(data[col])); + const columnSql = columns.map(quoteIdent).join(", "); + + const sql = `INSERT INTO ${table} (${columnSql}) VALUES (${placeholders.join(", ")}) RETURNING *`; + + if (options?.debug) console.log("createHandler:sql", sql, params.get()); + + const rows = await db.all(sql, params.get()); + const created = rows[0]; + + return { data: mapPrimaryKeyToId(created, primaryKey) }; +}; diff --git a/packages/ra-data-duckdb/src/dataProvider.ts b/packages/ra-data-duckdb/src/dataProvider.ts new file mode 100644 index 0000000..b35c607 --- /dev/null +++ b/packages/ra-data-duckdb/src/dataProvider.ts @@ -0,0 +1,164 @@ +import { DataProvider, HttpError } from "react-admin"; +import axios from "axios"; +import type { + AxiosError, + AxiosInterceptorOptions, + CreateAxiosDefaults, +} from "axios"; +import { isNumericId } from "deverything"; + +type AxiosInterceptorFulfilled = ((value: V) => V | Promise) | null; +type AxiosInterceptorError = ((error: any) => any) | null; + +export const dataProvider = ( + endpoint: string, + options?: { + headers?: CreateAxiosDefaults["headers"]; + withCredentials?: boolean; + axiosInterceptors?: { + response?: { + onFulfilled?: AxiosInterceptorFulfilled; + onRejected?: AxiosInterceptorError; + options?: AxiosInterceptorOptions; + }[]; + request?: { + onFulfilled?: AxiosInterceptorFulfilled; + onRejected?: AxiosInterceptorError; + options?: AxiosInterceptorOptions; + }[]; + }; + }, +): DataProvider => { + const apiService = axios.create({ + baseURL: endpoint, + headers: options?.headers, + withCredentials: options?.withCredentials, + }); + + apiService.interceptors.response.use((res) => res.data); + + if (options?.axiosInterceptors) { + if (options.axiosInterceptors.request) + options.axiosInterceptors.request.forEach((value) => + apiService.interceptors.request.use( + value.onFulfilled, + value.onRejected, + value.options, + ), + ); + + if (options.axiosInterceptors.response) + options.axiosInterceptors.response.forEach((value) => + apiService.interceptors.response.use( + value.onFulfilled, + value.onRejected, + ), + ); + } + + return { + getList: (resource, params) => { + return apiService + .post(resource, { + method: "getList", + resource, + params, + }) + .catch(reactAdminAxiosErrorHandler); + }, + getOne: (resource, params) => { + castIdToOriginalType(params); + + return apiService + .post(resource, { + method: "getOne", + resource, + params, + }) + .catch(reactAdminAxiosErrorHandler); + }, + getMany: (resource, params) => { + return apiService + .post(resource, { + method: "getMany", + resource, + params, + }) + .catch(reactAdminAxiosErrorHandler); + }, + getManyReference: (resource, params) => { + return apiService + .post(resource, { + method: "getManyReference", + resource, + params, + }) + .catch(reactAdminAxiosErrorHandler); + }, + create: (resource, params) => { + return apiService + .post(resource, { + method: "create", + resource, + params, + }) + .catch(reactAdminAxiosErrorHandler); + }, + update: (resource, params) => { + castIdToOriginalType(params); + + return apiService + .post(resource, { + method: "update", + resource, + params, + }) + .catch(reactAdminAxiosErrorHandler); + }, + updateMany: (resource, params) => { + return apiService + .post(resource, { + method: "updateMany", + resource, + params, + }) + .catch(reactAdminAxiosErrorHandler); + }, + delete: (resource, params) => { + castIdToOriginalType(params); + + return apiService + .post(resource, { + method: "delete", + resource, + params, + }) + .catch(reactAdminAxiosErrorHandler); + }, + deleteMany: (resource, params) => { + return apiService + .post(resource, { + method: "deleteMany", + resource, + params, + }) + .catch(reactAdminAxiosErrorHandler); + }, + }; +}; + +// https://github.com/marmelab/react-admin/issues/7728#issuecomment-1133959466 +// getOne will get the id from url so if the id is number it will be sent as string +const castIdToOriginalType = (params: any) => { + if (isNumericId(params.id)) params.id = +params.id; +}; + +// react-admin expects the error to be thrown +// https://marmelab.com/admin-on-rest/RestClients.html#writing-your-own-rest-client +const reactAdminAxiosErrorHandler = (error: AxiosError) => { + throw new HttpError( + (error?.response?.data as any)?.message || error?.response?.statusText, + error?.response?.status, + error?.response?.data, + ); +}; diff --git a/packages/ra-data-duckdb/src/defaultHandler.ts b/packages/ra-data-duckdb/src/defaultHandler.ts new file mode 100644 index 0000000..52a5fc0 --- /dev/null +++ b/packages/ra-data-duckdb/src/defaultHandler.ts @@ -0,0 +1,126 @@ +import { type CreateOptions, createHandler } from "./createHandler"; +import { type DeleteOptions, deleteHandler } from "./deleteHandler"; +import { type DeleteManyOptions, deleteManyHandler } from "./deleteManyHandler"; +import { DuckDBExecutor } from "./duckdb"; +import { type GetListOptions, getListHandler } from "./getListHandler"; +import { type GetManyOptions, getManyHandler } from "./getManyHandler"; +import { + type GetManyReferenceOptions, + getManyReferenceHandler, +} from "./getManyReferenceHandler"; +import { type ResourceToTableMap } from "./getTable"; +import { type GetOneOptions, getOneHandler } from "./getOneHandler"; +import type { + CreateRequest, + DeleteManyRequest, + DeleteRequest, + GetListRequest, + GetManyReferenceRequest, + GetManyRequest, + GetOneRequest, + RaPayload, + UpdateManyRequest, + UpdateRequest, +} from "./Http"; +import { type UpdateOptions, updateHandler } from "./updateHandler"; +import { updateManyHandler } from "./updateManyHandler"; + +export type DefaultHandlerOptions = { + create?: CreateOptions; + delete?: DeleteOptions; + deleteMany?: DeleteManyOptions; + getList?: GetListOptions; + getMany?: GetManyOptions; + getManyReference?: GetManyReferenceOptions; + getOne?: GetOneOptions; + resourceToTableMap?: ResourceToTableMap; + update?: UpdateOptions; +}; + +const withResourceToTableMap = ( + handlerOptions: T | undefined, + resourceToTableMap?: ResourceToTableMap, +) => ({ + ...(resourceToTableMap ? { resourceToTableMap } : {}), + ...handlerOptions, +}); + +export const defaultHandler = async ( + req: RaPayload, + db: DuckDBExecutor, + options?: DefaultHandlerOptions, +) => { + switch (req.method) { + case "create": { + return await createHandler( + req as CreateRequest, + db, + withResourceToTableMap(options?.create, options?.resourceToTableMap), + ); + } + case "delete": { + return await deleteHandler( + req as DeleteRequest, + db, + withResourceToTableMap(options?.delete, options?.resourceToTableMap), + ); + } + case "deleteMany": { + return deleteManyHandler( + req as DeleteManyRequest, + db, + withResourceToTableMap( + options?.deleteMany ?? options?.delete, + options?.resourceToTableMap, + ), + ); + } + case "getList": { + return getListHandler( + req as GetListRequest, + db, + withResourceToTableMap(options?.getList, options?.resourceToTableMap), + ); + } + case "getMany": { + return getManyHandler( + req as GetManyRequest, + db, + withResourceToTableMap(options?.getMany, options?.resourceToTableMap), + ); + } + case "getManyReference": { + return getManyReferenceHandler( + req as GetManyReferenceRequest, + db, + withResourceToTableMap( + options?.getManyReference, + options?.resourceToTableMap, + ), + ); + } + case "getOne": { + return getOneHandler( + req as GetOneRequest, + db, + withResourceToTableMap(options?.getOne, options?.resourceToTableMap), + ); + } + case "update": { + return await updateHandler( + req as UpdateRequest, + db, + withResourceToTableMap(options?.update, options?.resourceToTableMap), + ); + } + case "updateMany": { + return await updateManyHandler( + req as UpdateManyRequest, + db, + withResourceToTableMap(options?.update, options?.resourceToTableMap), + ); + } + default: + throw new Error("Invalid method"); + } +}; diff --git a/packages/ra-data-duckdb/src/deleteHandler.ts b/packages/ra-data-duckdb/src/deleteHandler.ts new file mode 100644 index 0000000..0534c09 --- /dev/null +++ b/packages/ra-data-duckdb/src/deleteHandler.ts @@ -0,0 +1,34 @@ +import { DuckDBExecutor } from "./duckdb"; +import { getTable, type ResourceToTableMap } from "./getTable"; +import { DeleteRequest } from "./Http"; +import { quoteIdent } from "./lib/quoteIdent"; +import { mapPrimaryKeyToId } from "./mapPrimaryKeyToId"; + +export type DeleteOptions = { + softDeleteField?: string; + debug?: boolean; + primaryKey?: string; + resourceToTableMap?: ResourceToTableMap; +}; + +export const deleteHandler = async ( + req: DeleteRequest, + db: DuckDBExecutor, + options?: DeleteOptions, +) => { + const { id } = req.params; + const primaryKey = options?.primaryKey ?? "id"; + const table = getTable(req, options?.resourceToTableMap); + const pk = quoteIdent(primaryKey); + + const sql = options?.softDeleteField + ? `UPDATE ${table} SET ${quoteIdent(options.softDeleteField)} = CURRENT_TIMESTAMP WHERE ${pk} = $id RETURNING *` + : `DELETE FROM ${table} WHERE ${pk} = $id RETURNING *`; + + if (options?.debug) console.log("deleteHandler", sql, { id }); + + const rows = await db.all(sql, { id }); + const deleted = rows[0]; + + return { data: mapPrimaryKeyToId(deleted, primaryKey) }; +}; diff --git a/packages/ra-data-duckdb/src/deleteManyHandler.ts b/packages/ra-data-duckdb/src/deleteManyHandler.ts new file mode 100644 index 0000000..edbe085 --- /dev/null +++ b/packages/ra-data-duckdb/src/deleteManyHandler.ts @@ -0,0 +1,40 @@ +import { DuckDBExecutor } from "./duckdb"; +import { getTable, type ResourceToTableMap } from "./getTable"; +import { DeleteManyRequest } from "./Http"; +import { quoteIdent } from "./lib/quoteIdent"; +import { SqlParams } from "./lib/SqlParams"; + +export type DeleteManyOptions = { + softDeleteField?: string; + debug?: boolean; + primaryKey?: string; + resourceToTableMap?: ResourceToTableMap; +}; + +export const deleteManyHandler = async ( + req: DeleteManyRequest, + db: DuckDBExecutor, + options?: DeleteManyOptions, +) => { + const { ids } = req.params; + const primaryKey = options?.primaryKey ?? "id"; + const table = getTable(req, options?.resourceToTableMap); + + if (!ids?.length) { + return { data: [] }; + } + + const params = new SqlParams(); + const placeholders = ids.map((id) => params.add(id)); + const pk = quoteIdent(primaryKey); + + const sql = options?.softDeleteField + ? `UPDATE ${table} SET ${quoteIdent(options.softDeleteField)} = CURRENT_TIMESTAMP WHERE ${pk} IN (${placeholders.join(", ")})` + : `DELETE FROM ${table} WHERE ${pk} IN (${placeholders.join(", ")})`; + + if (options?.debug) console.log("deleteManyHandler", sql, params.get()); + + await db.run(sql, params.get()); + + return { data: ids }; +}; diff --git a/packages/ra-data-duckdb/src/duckdb.ts b/packages/ra-data-duckdb/src/duckdb.ts new file mode 100644 index 0000000..e93e85c --- /dev/null +++ b/packages/ra-data-duckdb/src/duckdb.ts @@ -0,0 +1,39 @@ +/** + * Minimal DuckDB query surface used by handlers. + * Pass your own implementation, or wrap `@duckdb/node-api` via `fromDuckDBConnection`. + */ +export type DuckDBExecutor = { + all: >( + sql: string, + params?: Record, + ) => Promise; + run: (sql: string, params?: Record) => Promise; +}; + +type DuckDBConnectionLike = { + runAndReadAll: ( + sql: string, + params?: any, + ) => Promise<{ + getRowObjectsJson: () => Record[]; + }>; + run: (sql: string, params?: any) => Promise; +}; + +/** + * Wrap a `@duckdb/node-api` DuckDBConnection (or compatible) as a DuckDBExecutor. + */ +export const fromDuckDBConnection = ( + connection: DuckDBConnectionLike, +): DuckDBExecutor => ({ + all: async >( + sql: string, + params?: Record, + ) => { + const reader = await connection.runAndReadAll(sql, params); + return reader.getRowObjectsJson() as T[]; + }, + run: async (sql: string, params?: Record) => { + await connection.run(sql, params); + }, +}); diff --git a/packages/ra-data-duckdb/src/extractOrderBy.ts b/packages/ra-data-duckdb/src/extractOrderBy.ts new file mode 100644 index 0000000..1991bc1 --- /dev/null +++ b/packages/ra-data-duckdb/src/extractOrderBy.ts @@ -0,0 +1,17 @@ +import { GetListRequest, GetManyReferenceRequest } from "./Http"; +import { quoteIdent } from "./lib/quoteIdent"; + +export const extractOrderBy = ( + req: GetListRequest | GetManyReferenceRequest, + primaryKey = "id", +): string => { + const { sort } = req.params; + + if (sort?.field && sort?.order) { + const field = sort.field === "id" ? primaryKey : sort.field; + const direction = sort.order.toUpperCase() === "DESC" ? "DESC" : "ASC"; + return `ORDER BY ${quoteIdent(field)} ${direction}`; + } + + return ""; +}; diff --git a/packages/ra-data-duckdb/src/extractSkipTake.ts b/packages/ra-data-duckdb/src/extractSkipTake.ts new file mode 100644 index 0000000..8a6fcf3 --- /dev/null +++ b/packages/ra-data-duckdb/src/extractSkipTake.ts @@ -0,0 +1,22 @@ +import { GetListRequest, GetManyReferenceRequest } from "./Http"; + +export const extractSkipTake = ( + req: GetListRequest | GetManyReferenceRequest, +): { skip: number | undefined; take: number | undefined } => { + const { pagination } = req.params; + + let skip: number | undefined; + let take: number | undefined; + + if (pagination) { + const { page, perPage } = pagination; + + const first = (page - 1) * perPage; + const last = page * perPage - 1; + + skip = first; + take = last - first + 1; + } + + return { skip, take }; +}; diff --git a/packages/ra-data-duckdb/src/extractWhere.ts b/packages/ra-data-duckdb/src/extractWhere.ts new file mode 100644 index 0000000..2635bfc --- /dev/null +++ b/packages/ra-data-duckdb/src/extractWhere.ts @@ -0,0 +1,214 @@ +import { isArray, isObject, isString, PlainObject } from "deverything"; +import { GetListRequest, GetManyReferenceRequest } from "./Http"; +import { isNotField } from "./lib/isNotField"; +import { quoteIdent } from "./lib/quoteIdent"; +import { SqlParams } from "./lib/SqlParams"; + +export type FilterMode = "insensitive" | "default" | undefined; + +export type ExtractWhereOptions = { + filterMode?: FilterMode; + debug?: boolean; + /** Columns used when filter key `q` is present (OR of LIKE / ILIKE). */ + searchColumns?: string[]; + primaryKey?: string; +}; + +const SUFFIX_OPS: Record = { + _eq: "=", + _exact: "=", + _enum: "=", + _gt: ">", + _gte: ">=", + _lt: "<", + _lte: "<=", + _ne: "!=", + _not: "!=", +}; + +export type WhereClause = { + sql: string; + params: Record; +}; + +const buildClauses = ( + filterObj: PlainObject, + params: SqlParams, + options: ExtractWhereOptions | undefined, + primaryKey: string, + caseInsensitive: boolean, +): string[] => { + const clauses: string[] = []; + + const pushCompare = (column: string, op: string, value: unknown) => { + const col = quoteIdent(column === "id" ? primaryKey : column); + if (value === null) { + clauses.push(op === "!=" ? `${col} IS NOT NULL` : `${col} IS NULL`); + return; + } + clauses.push(`${col} ${op} ${params.add(value)}`); + }; + + const pushLike = (column: string, pattern: string) => { + const col = quoteIdent(column === "id" ? primaryKey : column); + const placeholder = params.add(pattern); + clauses.push( + caseInsensitive + ? `LOWER(CAST(${col} AS VARCHAR)) LIKE LOWER(${placeholder})` + : `CAST(${col} AS VARCHAR) LIKE ${placeholder}`, + ); + }; + + Object.entries(filterObj).forEach(([field, value]) => { + if (isNotField(field)) return; + if (value === "") return; + + if (field === "q") { + if (isString(value) && options?.searchColumns?.length && value.length > 0) { + const ors = options.searchColumns.map((col) => { + const placeholder = params.add(`%${value}%`); + const quoted = quoteIdent(col); + return caseInsensitive + ? `LOWER(CAST(${quoted} AS VARCHAR)) LIKE LOWER(${placeholder})` + : `CAST(${quoted} AS VARCHAR) LIKE ${placeholder}`; + }); + clauses.push(`(${ors.join(" OR ")})`); + } + return; + } + + if (["OR", "AND", "NOT"].includes(field)) { + if (isArray(value) && value.every((item) => isObject(item))) { + const group: string[] = []; + for (const item of value as PlainObject[]) { + const nested = buildClauses( + item, + params, + options, + primaryKey, + caseInsensitive, + ); + if (nested.length) { + group.push(`(${nested.join(" AND ")})`); + } + } + if (group.length) { + if (field === "NOT") { + clauses.push(`NOT (${group.join(" AND ")})`); + } else { + clauses.push(`(${group.join(` ${field} `)})`); + } + } + } + return; + } + + const suffix = Object.keys(SUFFIX_OPS).find((s) => field.endsWith(s)); + if (suffix) { + pushCompare(field.slice(0, -suffix.length), SUFFIX_OPS[suffix], value); + return; + } + + if (field.endsWith("_contains")) { + pushLike(field.slice(0, -"_contains".length), `%${value}%`); + return; + } + if (field.endsWith("_startsWith")) { + pushLike(field.slice(0, -"_startsWith".length), `${value}%`); + return; + } + if (field.endsWith("_endsWith")) { + pushLike(field.slice(0, -"_endsWith".length), `%${value}`); + return; + } + if (field.endsWith("_trueOnly")) { + if (value === true) { + pushCompare(field.slice(0, -"_trueOnly".length), "=", true); + } + return; + } + if (field.endsWith("_in") && isArray(value)) { + const column = field.slice(0, -"_in".length); + const col = quoteIdent(column === "id" ? primaryKey : column); + if (value.length === 0) { + clauses.push("FALSE"); + return; + } + const placeholders = value.map((v) => params.add(v)); + clauses.push(`${col} IN (${placeholders.join(", ")})`); + return; + } + + // Array values always become IN (...), including id-like fields such as + // `id`, `author_id`, or `userId` (ReferenceArrayInput / multi-select FKs). + if (isArray(value)) { + const col = quoteIdent(field === "id" ? primaryKey : field); + if (value.length === 0) { + clauses.push("FALSE"); + return; + } + const placeholders = value.map((v) => params.add(v)); + clauses.push(`${col} IN (${placeholders.join(", ")})`); + return; + } + + if ( + field === "id" || + field === "uuid" || + field === "uid" || + field === "cuid" || + field.endsWith("_id") || + field.endsWith("_uuid") || + field.endsWith("_uid") || + field.endsWith("_cuid") || + field.endsWith("Id") || + typeof value === "number" || + typeof value === "boolean" || + value === null + ) { + pushCompare(field, "=", value); + } else if (isString(value)) { + pushLike(field, `%${value}%`); + } else if (isObject(value)) { + throw new Error( + `ra-data-duckdb: Nested filter "${field}" is not supported; use a flat column filter`, + ); + } else { + console.info("ra-data-duckdb: Filter not handled", field, value); + } + }); + + return clauses; +}; + +export const extractWhere = ( + req: GetListRequest | GetManyReferenceRequest, + options?: ExtractWhereOptions, +): WhereClause => { + const { filter } = req.params; + const params = new SqlParams(); + const primaryKey = options?.primaryKey ?? "id"; + const caseInsensitive = options?.filterMode !== "default"; + + const clauses = filter + ? buildClauses(filter, params, options, primaryKey, caseInsensitive) + : []; + + const sql = clauses.length ? `WHERE ${clauses.join(" AND ")}` : ""; + + if (options?.debug) console.debug("extractWhere", sql, params.get()); + + return { sql, params: params.get() }; +}; + +/** Append an AND condition onto an existing WHERE clause. */ +export const andWhere = ( + where: WhereClause, + extraSql: string, + extraParams?: Record, +): WhereClause => { + const params = { ...where.params, ...extraParams }; + if (!extraSql) return { sql: where.sql, params }; + if (!where.sql) return { sql: `WHERE ${extraSql}`, params }; + return { sql: `${where.sql} AND ${extraSql}`, params }; +}; diff --git a/packages/ra-data-duckdb/src/getListHandler.ts b/packages/ra-data-duckdb/src/getListHandler.ts new file mode 100644 index 0000000..5ea5644 --- /dev/null +++ b/packages/ra-data-duckdb/src/getListHandler.ts @@ -0,0 +1,95 @@ +import { DuckDBExecutor } from "./duckdb"; +import { extractOrderBy } from "./extractOrderBy"; +import { extractSkipTake } from "./extractSkipTake"; +import { andWhere, extractWhere, FilterMode } from "./extractWhere"; +import { getTable, type ResourceToTableMap } from "./getTable"; +import { GetListRequest } from "./Http"; +import { mapPrimaryKeyToId } from "./mapPrimaryKeyToId"; + +export type GetListOptions = { + debug?: boolean; + primaryKey?: string; + resourceToTableMap?: ResourceToTableMap; + filterMode?: FilterMode; + searchColumns?: string[]; + /** Extra SQL AND condition (no leading WHERE/AND), e.g. `"active" = true` */ + extraWhere?: string; + transformRow?: ( + row: any, + rowIndex: number, + rows: any[], + ) => any | Promise; + transformRows?: (rows: any[]) => any[] | Promise; +}; + +export const getListHandler = async ( + req: GetListRequest, + db: DuckDBExecutor, + options?: GetListOptions, +) => { + const table = getTable(req, options?.resourceToTableMap); + const primaryKey = options?.primaryKey ?? "id"; + + let where = extractWhere(req, { + filterMode: options?.filterMode, + debug: options?.debug, + searchColumns: options?.searchColumns, + primaryKey, + }); + + if (options?.extraWhere) { + where = andWhere(where, options.extraWhere); + } + + const { skip, take } = extractSkipTake(req); + const orderBy = extractOrderBy(req, primaryKey); + + const limitSql = + take != null + ? `LIMIT ${Math.max(0, take)}${skip != null ? ` OFFSET ${Math.max(0, skip)}` : ""}` + : skip != null + ? `OFFSET ${Math.max(0, skip)}` + : ""; + + const listSql = [ + `SELECT * FROM ${table}`, + where.sql, + orderBy, + limitSql, + ] + .filter(Boolean) + .join(" "); + + const countSql = [`SELECT COUNT(*) AS total FROM ${table}`, where.sql] + .filter(Boolean) + .join(" "); + + if (options?.debug) { + console.log("getListHandler:listSql", listSql, where.params); + console.log("getListHandler:countSql", countSql, where.params); + } + + const [rows, countRows] = await Promise.all([ + db.all(listSql, where.params), + db.all<{ total: number | bigint | string }>(countSql, where.params), + ]); + + const total = Number(countRows[0]?.total ?? 0); + + let data = rows; + + if (options?.transformRow) { + data = await Promise.all( + data.map((row, i, arr) => options.transformRow!(row, i, arr)), + ); + } + + if (options?.transformRows) { + data = await options.transformRows(data); + } + + return { + data: mapPrimaryKeyToId(data, primaryKey), + total, + }; +}; diff --git a/packages/ra-data-duckdb/src/getManyHandler.ts b/packages/ra-data-duckdb/src/getManyHandler.ts new file mode 100644 index 0000000..b9a9f93 --- /dev/null +++ b/packages/ra-data-duckdb/src/getManyHandler.ts @@ -0,0 +1,46 @@ +import { DuckDBExecutor } from "./duckdb"; +import { getTable, type ResourceToTableMap } from "./getTable"; +import { GetManyRequest } from "./Http"; +import { quoteIdent } from "./lib/quoteIdent"; +import { SqlParams } from "./lib/SqlParams"; +import { mapPrimaryKeyToId } from "./mapPrimaryKeyToId"; + +export type GetManyOptions = { + debug?: boolean; + primaryKey?: string; + resourceToTableMap?: ResourceToTableMap; + transformRow?: (data: any) => any | Promise; + transformRows?: (rows: any[]) => any[] | Promise; +}; + +export const getManyHandler = async ( + req: GetManyRequest, + db: DuckDBExecutor, + options?: GetManyOptions, +) => { + const { ids } = req.params; + const primaryKey = options?.primaryKey ?? "id"; + const table = getTable(req, options?.resourceToTableMap); + const pk = quoteIdent(primaryKey); + + if (!ids?.length) { + return { data: [] }; + } + + const params = new SqlParams(); + const placeholders = ids.map((id) => params.add(id)); + const sql = `SELECT * FROM ${table} WHERE ${pk} IN (${placeholders.join(", ")})`; + + if (options?.debug) console.log("getManyHandler", sql, params.get()); + + let data = await db.all(sql, params.get()); + + if (options?.transformRow) { + data = await Promise.all(data.map(options.transformRow)); + } + if (options?.transformRows) { + data = await options.transformRows(data); + } + + return { data: mapPrimaryKeyToId(data, primaryKey) }; +}; diff --git a/packages/ra-data-duckdb/src/getManyReferenceHandler.ts b/packages/ra-data-duckdb/src/getManyReferenceHandler.ts new file mode 100644 index 0000000..87061cb --- /dev/null +++ b/packages/ra-data-duckdb/src/getManyReferenceHandler.ts @@ -0,0 +1,78 @@ +import { DuckDBExecutor } from "./duckdb"; +import { extractOrderBy } from "./extractOrderBy"; +import { extractSkipTake } from "./extractSkipTake"; +import { andWhere, extractWhere, FilterMode } from "./extractWhere"; +import { getTable, type ResourceToTableMap } from "./getTable"; +import { GetManyReferenceRequest } from "./Http"; +import { quoteIdent } from "./lib/quoteIdent"; +import { mapPrimaryKeyToId } from "./mapPrimaryKeyToId"; + +export type GetManyReferenceOptions = { + debug?: boolean; + primaryKey?: string; + resourceToTableMap?: ResourceToTableMap; + filterMode?: FilterMode; + searchColumns?: string[]; + transformRow?: (data: any) => any | Promise; + transformRows?: (rows: any[]) => any[] | Promise; +}; + +export const getManyReferenceHandler = async ( + req: GetManyReferenceRequest, + db: DuckDBExecutor, + options?: GetManyReferenceOptions, +) => { + const { id, target } = req.params; + const primaryKey = options?.primaryKey ?? "id"; + const table = getTable(req, options?.resourceToTableMap); + + let where = extractWhere(req, { + filterMode: options?.filterMode, + debug: options?.debug, + searchColumns: options?.searchColumns, + primaryKey, + }); + + where = andWhere(where, `${quoteIdent(target)} = $refId`, { refId: id }); + + const { skip, take } = extractSkipTake(req); + const orderBy = extractOrderBy(req, primaryKey); + + const limitSql = + take != null + ? `LIMIT ${Math.max(0, take)}${skip != null ? ` OFFSET ${Math.max(0, skip)}` : ""}` + : skip != null + ? `OFFSET ${Math.max(0, skip)}` + : ""; + + const listSql = [`SELECT * FROM ${table}`, where.sql, orderBy, limitSql] + .filter(Boolean) + .join(" "); + + const countSql = [`SELECT COUNT(*) AS total FROM ${table}`, where.sql] + .filter(Boolean) + .join(" "); + + if (options?.debug) { + console.log("getManyReferenceHandler", listSql, where.params); + } + + const [rows, countRows] = await Promise.all([ + db.all(listSql, where.params), + db.all<{ total: number | bigint | string }>(countSql, where.params), + ]); + + let data = rows; + + if (options?.transformRow) { + data = await Promise.all(data.map(options.transformRow)); + } + if (options?.transformRows) { + data = await options.transformRows(data); + } + + return { + data: mapPrimaryKeyToId(data, primaryKey), + total: Number(countRows[0]?.total ?? 0), + }; +}; diff --git a/packages/ra-data-duckdb/src/getOneHandler.ts b/packages/ra-data-duckdb/src/getOneHandler.ts new file mode 100644 index 0000000..d624f70 --- /dev/null +++ b/packages/ra-data-duckdb/src/getOneHandler.ts @@ -0,0 +1,42 @@ +import { DuckDBExecutor } from "./duckdb"; +import { getTable, type ResourceToTableMap } from "./getTable"; +import { GetOneRequest } from "./Http"; +import { NotFoundError } from "./lib/NotFoundError"; +import { quoteIdent } from "./lib/quoteIdent"; +import { mapPrimaryKeyToId } from "./mapPrimaryKeyToId"; + +export type GetOneOptions = { + debug?: boolean; + primaryKey?: string; + resourceToTableMap?: ResourceToTableMap; + transform?: (row: any) => any | Promise; +}; + +export const getOneHandler = async ( + req: GetOneRequest, + db: DuckDBExecutor, + options?: GetOneOptions, +) => { + const { id } = req.params; + const primaryKey = options?.primaryKey ?? "id"; + const table = getTable(req, options?.resourceToTableMap); + const pk = quoteIdent(primaryKey); + + const sql = `SELECT * FROM ${table} WHERE ${pk} = $id LIMIT 1`; + + if (options?.debug) console.log("getOneHandler", sql, { id }); + + const rows = await db.all(sql, { id }); + const row = rows[0]; + if (!row) { + throw new NotFoundError( + `getOneHandler: no row found in ${table} for ${primaryKey}=${id}`, + ); + } + + const transformedRow = options?.transform + ? await options.transform(row) + : row; + + return { data: mapPrimaryKeyToId(transformedRow, primaryKey) }; +}; diff --git a/packages/ra-data-duckdb/src/getTable.ts b/packages/ra-data-duckdb/src/getTable.ts new file mode 100644 index 0000000..1f5a1d7 --- /dev/null +++ b/packages/ra-data-duckdb/src/getTable.ts @@ -0,0 +1,13 @@ +import { RaPayload } from "./Http"; +import { quoteIdent } from "./lib/quoteIdent"; + +export type ResourceToTableMap = Record; + +export const getTable = ( + req: RaPayload, + resourceToTableMap?: ResourceToTableMap, +): string => { + const tableName = resourceToTableMap?.[req.resource] ?? req.resource; + if (!tableName) throw new Error(`table name is empty`); + return quoteIdent(tableName); +}; diff --git a/packages/ra-data-duckdb/src/index.ts b/packages/ra-data-duckdb/src/index.ts new file mode 100644 index 0000000..422c601 --- /dev/null +++ b/packages/ra-data-duckdb/src/index.ts @@ -0,0 +1,19 @@ +export * from "./createHandler"; +export * from "./dataProvider"; +export * from "./defaultHandler"; +export * from "./deleteHandler"; +export * from "./deleteManyHandler"; +export * from "./duckdb"; +export * from "./extractOrderBy"; +export * from "./extractSkipTake"; +export * from "./extractWhere"; +export * from "./getListHandler"; +export * from "./getManyHandler"; +export * from "./getManyReferenceHandler"; +export * from "./getOneHandler"; +export * from "./getTable"; +export * from "./Http"; +export * from "./lib/NotFoundError"; +export * from "./mapPrimaryKeyToId"; +export * from "./updateHandler"; +export * from "./updateManyHandler"; diff --git a/packages/ra-data-duckdb/src/lib/NotFoundError.ts b/packages/ra-data-duckdb/src/lib/NotFoundError.ts new file mode 100644 index 0000000..bcc1ca5 --- /dev/null +++ b/packages/ra-data-duckdb/src/lib/NotFoundError.ts @@ -0,0 +1,9 @@ +/** Thrown by getOne/update when no row matches the requested id. */ +export class NotFoundError extends Error { + readonly status = 404; + + constructor(message = "Record not found") { + super(message); + this.name = "NotFoundError"; + } +} diff --git a/packages/ra-data-duckdb/src/lib/SqlParams.ts b/packages/ra-data-duckdb/src/lib/SqlParams.ts new file mode 100644 index 0000000..e8be1e2 --- /dev/null +++ b/packages/ra-data-duckdb/src/lib/SqlParams.ts @@ -0,0 +1,18 @@ +export class SqlParams { + private values: Record = {}; + private i = 0; + + add(value: unknown): string { + const key = `p${++this.i}`; + this.values[key] = value; + return `$${key}`; + } + + get(): Record { + return this.values; + } + + merge(other: Record): void { + Object.assign(this.values, other); + } +} diff --git a/packages/ra-data-duckdb/src/lib/isNotField.ts b/packages/ra-data-duckdb/src/lib/isNotField.ts new file mode 100644 index 0000000..5e6f70d --- /dev/null +++ b/packages/ra-data-duckdb/src/lib/isNotField.ts @@ -0,0 +1,7 @@ +export const isNotField = (fieldName: string) => { + // ignore underscored fields (_count, _sum, _avg, _min, _max and _helpers) + // especially in updates they would throw an error + if (fieldName.startsWith("_")) return true; + + return false; +}; diff --git a/packages/ra-data-duckdb/src/lib/quoteIdent.ts b/packages/ra-data-duckdb/src/lib/quoteIdent.ts new file mode 100644 index 0000000..4b1b035 --- /dev/null +++ b/packages/ra-data-duckdb/src/lib/quoteIdent.ts @@ -0,0 +1,9 @@ +const IDENT_RE = /^[A-Za-z_][A-Za-z0-9_]*$/; + +/** Validate and double-quote a SQL identifier (table/column). */ +export const quoteIdent = (name: string): string => { + if (!IDENT_RE.test(name)) { + throw new Error(`Invalid SQL identifier: "${name}"`); + } + return `"${name}"`; +}; diff --git a/packages/ra-data-duckdb/src/mapPrimaryKeyToId.ts b/packages/ra-data-duckdb/src/mapPrimaryKeyToId.ts new file mode 100644 index 0000000..2f2329f --- /dev/null +++ b/packages/ra-data-duckdb/src/mapPrimaryKeyToId.ts @@ -0,0 +1,32 @@ +const mapRowPrimaryKeyToId = (row: T, primaryKey: string): T => { + if (primaryKey === "id" || row == null || typeof row !== "object") { + return row; + } + + const hasIdField = Object.prototype.hasOwnProperty.call(row, "id"); + const existingId = (row as any).id; + const keyValue = (row as any)[primaryKey]; + + if (typeof keyValue === "undefined") { + return row; + } + + if (hasIdField && typeof existingId !== "undefined" && existingId !== keyValue) { + console.warn(`ra-data-duckdb: overwriting existing id with value from primaryKey "${primaryKey}"`); + } + + const { [primaryKey]: _, ...rest } = row as any; + + return { + ...rest, + id: keyValue, + }; +}; + +export const mapPrimaryKeyToId = (data: T, primaryKey: string): T => { + if (Array.isArray(data)) { + return data.map((row) => mapRowPrimaryKeyToId(row, primaryKey)) as T; + } + + return mapRowPrimaryKeyToId(data, primaryKey); +}; diff --git a/packages/ra-data-duckdb/src/server.ts b/packages/ra-data-duckdb/src/server.ts new file mode 100644 index 0000000..c047159 --- /dev/null +++ b/packages/ra-data-duckdb/src/server.ts @@ -0,0 +1,18 @@ +export * from "./createHandler"; +export * from "./defaultHandler"; +export * from "./deleteHandler"; +export * from "./deleteManyHandler"; +export * from "./duckdb"; +export * from "./extractOrderBy"; +export * from "./extractSkipTake"; +export * from "./extractWhere"; +export * from "./getListHandler"; +export * from "./getManyHandler"; +export * from "./getManyReferenceHandler"; +export * from "./getOneHandler"; +export * from "./getTable"; +export * from "./Http"; +export * from "./lib/NotFoundError"; +export * from "./mapPrimaryKeyToId"; +export * from "./updateHandler"; +export * from "./updateManyHandler"; diff --git a/packages/ra-data-duckdb/src/updateHandler.ts b/packages/ra-data-duckdb/src/updateHandler.ts new file mode 100644 index 0000000..cd73cbc --- /dev/null +++ b/packages/ra-data-duckdb/src/updateHandler.ts @@ -0,0 +1,82 @@ +import { DuckDBExecutor } from "./duckdb"; +import { getTable, type ResourceToTableMap } from "./getTable"; +import type { UpdateRequest } from "./Http"; +import { isNotField } from "./lib/isNotField"; +import { NotFoundError } from "./lib/NotFoundError"; +import { quoteIdent } from "./lib/quoteIdent"; +import { SqlParams } from "./lib/SqlParams"; +import { mapPrimaryKeyToId } from "./mapPrimaryKeyToId"; + +export type UpdateOptions = { + debug?: boolean; + skipFields?: Record; + allowOnlyFields?: Record; + primaryKey?: string; + resourceToTableMap?: ResourceToTableMap; +}; + +export const reduceData = ( + data: Record, + options?: UpdateOptions, +) => { + const primaryKey = options?.primaryKey ?? "id"; + const fields: Record = {}; + + Object.entries(data).forEach(([key, value]) => { + // React-admin includes its record identifier in `data`. It is used in the + // WHERE clause and must not be written back, especially when `id` is only + // an alias for a custom primary key. + if (key === "id") return; + if (primaryKey !== "id" && key === primaryKey) { + throw new Error( + `updateHandler: Field ${key} is reserved when primaryKey is configured; use params.id and omit the original primary key from writes`, + ); + } + if (isNotField(key)) return; + if (options?.skipFields?.[key]) return; + if (options?.allowOnlyFields && !options.allowOnlyFields[key]) { + throw new Error(`updateHandler: Field ${key} is not allowed in update`); + } + fields[key] = value; + }); + + return fields; +}; + +export const updateHandler = async ( + req: UpdateRequest, + db: DuckDBExecutor, + options?: UpdateOptions, +) => { + const { id } = req.params; + const primaryKey = options?.primaryKey ?? "id"; + const table = getTable(req, options?.resourceToTableMap); + const data = reduceData(req.params.data as Record, options); + + if (options?.debug) console.log("updateHandler:data", data); + + const columns = Object.keys(data); + if (!columns.length) { + throw new Error("updateHandler: no fields to update"); + } + + const params = new SqlParams(); + const setSql = columns + .map((col) => `${quoteIdent(col)} = ${params.add(data[col])}`) + .join(", "); + const idPlaceholder = params.add(id); + + const sql = `UPDATE ${table} SET ${setSql} WHERE ${quoteIdent(primaryKey)} = ${idPlaceholder} RETURNING *`; + + if (options?.debug) console.log("updateHandler:sql", sql, params.get()); + + const rows = await db.all(sql, params.get()); + const updated = rows[0]; + if (!updated) { + throw new NotFoundError( + `updateHandler: no row found in ${table} for ${primaryKey}=${id}`, + ); + } + + return { data: mapPrimaryKeyToId(updated, primaryKey) }; +}; diff --git a/packages/ra-data-duckdb/src/updateManyHandler.ts b/packages/ra-data-duckdb/src/updateManyHandler.ts new file mode 100644 index 0000000..1c89030 --- /dev/null +++ b/packages/ra-data-duckdb/src/updateManyHandler.ts @@ -0,0 +1,42 @@ +import { DuckDBExecutor } from "./duckdb"; +import { getTable, type ResourceToTableMap } from "./getTable"; +import { UpdateManyRequest } from "./Http"; +import { quoteIdent } from "./lib/quoteIdent"; +import { SqlParams } from "./lib/SqlParams"; +import { reduceData, UpdateOptions } from "./updateHandler"; + +export const updateManyHandler = async ( + req: UpdateManyRequest, + db: DuckDBExecutor, + options?: UpdateOptions, +) => { + const { ids } = req.params; + const primaryKey = options?.primaryKey ?? "id"; + const table = getTable(req, options?.resourceToTableMap); + const data = reduceData(req.params.data as Record, options); + + if (options?.debug) console.log("updateManyHandler:data", data); + + if (!ids?.length) { + return { data: [] }; + } + + const columns = Object.keys(data); + if (!columns.length) { + throw new Error("updateManyHandler: no fields to update"); + } + + const params = new SqlParams(); + const setSql = columns + .map((col) => `${quoteIdent(col)} = ${params.add(data[col])}`) + .join(", "); + const placeholders = ids.map((id) => params.add(id)); + + const sql = `UPDATE ${table} SET ${setSql} WHERE ${quoteIdent(primaryKey)} IN (${placeholders.join(", ")})`; + + if (options?.debug) console.log("updateManyHandler:sql", sql, params.get()); + + await db.run(sql, params.get()); + + return { data: ids }; +}; diff --git a/packages/ra-data-duckdb/tests/handlers.test.ts b/packages/ra-data-duckdb/tests/handlers.test.ts new file mode 100644 index 0000000..42bb1df --- /dev/null +++ b/packages/ra-data-duckdb/tests/handlers.test.ts @@ -0,0 +1,384 @@ +import { + DuckDBConnection, + DuckDBInstance, +} from "@duckdb/node-api"; +import { createHandler } from "../src/createHandler"; +import { defaultHandler } from "../src/defaultHandler"; +import { + DuckDBExecutor, + fromDuckDBConnection, +} from "../src/duckdb"; +import { extractWhere } from "../src/extractWhere"; +import { getListHandler } from "../src/getListHandler"; +import { getOneHandler } from "../src/getOneHandler"; +import { NotFoundError } from "../src/lib/NotFoundError"; +import { quoteIdent } from "../src/lib/quoteIdent"; +import { updateHandler } from "../src/updateHandler"; +import { deleteHandler } from "../src/deleteHandler"; + +let connection: DuckDBConnection; +let db: DuckDBExecutor; + +beforeEach(async () => { + const instance = await DuckDBInstance.create(":memory:"); + connection = await instance.connect(); + db = fromDuckDBConnection(connection); + + await db.run("CREATE SEQUENCE users_id_seq START 4"); + await db.run(` + CREATE TABLE users ( + id INTEGER PRIMARY KEY DEFAULT nextval('users_id_seq'), + name VARCHAR NOT NULL, + age INTEGER, + active BOOLEAN DEFAULT false + ) + `); + await db.run(` + INSERT INTO users (id, name, age, active) + VALUES + (1, 'Alice', 30, false), + (2, 'Bob', 25, false), + (3, 'Carol', 40, false) + `); + + await db.run("CREATE SEQUENCE custom_users_id_seq START 4"); + await db.run(` + CREATE TABLE custom_users ( + user_key INTEGER PRIMARY KEY DEFAULT nextval('custom_users_id_seq'), + name VARCHAR NOT NULL + ) + `); + await db.run(` + INSERT INTO custom_users (user_key, name) + VALUES (1, 'Alice'), (2, 'Bob'), (3, 'Carol') + `); + + await db.run(` + CREATE TABLE posts ( + id INTEGER PRIMARY KEY, + user_id INTEGER NOT NULL, + title VARCHAR NOT NULL + ) + `); + await db.run(` + INSERT INTO posts VALUES + (1, 1, 'First'), + (2, 1, 'Second'), + (3, 2, 'Third') + `); +}); + +afterEach(() => { + connection.disconnectSync(); +}); + +describe("quoteIdent", () => { + it("quotes valid identifiers", () => { + expect(quoteIdent("users")).toBe('"users"'); + }); + + it("rejects invalid identifiers", () => { + expect(() => quoteIdent('users; drop')).toThrow(); + }); +}); + +describe("extractWhere", () => { + it("builds equality and range filters", () => { + const where = extractWhere({ + method: "getList", + resource: "users", + params: { + filter: { age_gte: 30, name: "Al" }, + pagination: { page: 1, perPage: 10 }, + sort: { field: "id", order: "ASC" }, + }, + } as any); + + expect(where.sql).toContain('"age" >= $p1'); + expect(where.sql).toContain("LIKE"); + expect(where.params.p1).toBe(30); + }); + + it("supports OR groups without colliding params", () => { + const where = extractWhere({ + method: "getList", + resource: "users", + params: { + filter: { + OR: [{ age_gte: 30 }, { name_eq: "Bob" }], + }, + pagination: { page: 1, perPage: 10 }, + sort: { field: "id", order: "ASC" }, + }, + } as any); + + expect(where.sql).toContain(" OR "); + expect(where.params.p1).toBe(30); + expect(where.params.p2).toBe("Bob"); + }); + + it("turns empty lists into an always-false condition", () => { + const where = extractWhere({ + method: "getList", + resource: "users", + params: { + filter: { id_in: [] }, + pagination: { page: 1, perPage: 10 }, + sort: { field: "id", order: "ASC" }, + }, + } as any); + + expect(where).toEqual({ sql: "WHERE FALSE", params: {} }); + }); + + it("uses IN for plain array filters on id-like fields", () => { + const where = extractWhere({ + method: "getList", + resource: "posts", + params: { + filter: { id: [1, 2], user_id: [1, 2], authorId: [3] }, + pagination: { page: 1, perPage: 10 }, + sort: { field: "id", order: "ASC" }, + }, + } as any); + + expect(where.sql).toContain('"id" IN ($p1, $p2)'); + expect(where.sql).toContain('"user_id" IN ($p3, $p4)'); + expect(where.sql).toContain('"authorId" IN ($p5)'); + expect(where.params).toEqual({ + p1: 1, + p2: 2, + p3: 1, + p4: 2, + p5: 3, + }); + }); + + it("rejects nested filters instead of querying the wrong column", () => { + expect(() => + extractWhere({ + method: "getList", + resource: "users", + params: { + filter: { profile: { city: "Rome" } }, + pagination: { page: 1, perPage: 10 }, + sort: { field: "id", order: "ASC" }, + }, + } as any), + ).toThrow('Nested filter "profile" is not supported'); + }); +}); + +describe("DuckDB integration", () => { + it("getList applies filters, sorting, pagination, and count", async () => { + const result = await getListHandler( + { + method: "getList", + resource: "users", + params: { + filter: { age_gte: 30 }, + pagination: { page: 1, perPage: 1 }, + sort: { field: "age", order: "DESC" }, + }, + }, + db, + ); + + expect(result.data).toEqual([ + expect.objectContaining({ id: 3, name: "Carol" }), + ]); + expect(result.total).toBe(2); + }); + + it("getOne / create / update / delete round-trip", async () => { + const one = await getOneHandler( + { method: "getOne", resource: "users", params: { id: 1 } }, + db, + ); + expect(one.data.name).toBe("Alice"); + + const created = await createHandler( + { + method: "create", + resource: "users", + params: { data: { name: "Dave", age: 22 } }, + }, + db, + ); + expect(created.data.name).toBe("Dave"); + expect(created.data.id).toBeDefined(); + + const updated = await updateHandler( + { + method: "update", + resource: "users", + params: { + id: created.data.id, + data: { id: created.data.id, name: "David", age: 23 }, + previousData: created.data, + }, + }, + db, + ); + expect(updated.data.name).toBe("David"); + + const deleted = await deleteHandler( + { + method: "delete", + resource: "users", + params: { id: created.data.id, previousData: updated.data }, + }, + db, + ); + expect(deleted.data.id).toBe(created.data.id); + }); + + it("handles updateMany and deleteMany against DuckDB", async () => { + const updated = await defaultHandler( + { + method: "updateMany", + resource: "users", + params: { ids: [1, 2], data: { active: true } }, + }, + db, + ); + expect(updated.data).toEqual([1, 2]); + + const rows = await defaultHandler( + { + method: "getMany", + resource: "users", + params: { ids: [1, 2] }, + }, + db, + ); + expect(rows.data).toEqual([ + expect.objectContaining({ id: 1, active: true }), + expect.objectContaining({ id: 2, active: true }), + ]); + + const deleted = await defaultHandler( + { + method: "deleteMany", + resource: "users", + params: { ids: [1, 2] }, + }, + db, + ); + expect(deleted.data).toEqual([1, 2]); + + const remaining = await db.all<{ id: number }>( + "SELECT id FROM users ORDER BY id", + ); + expect(remaining).toEqual([{ id: 3 }]); + }); + + it("maps custom primary keys without writing the synthetic id", async () => { + const options = { + resourceToTableMap: { users: "custom_users" }, + getOne: { primaryKey: "user_key" }, + create: { primaryKey: "user_key" }, + update: { primaryKey: "user_key" }, + }; + + const one = await defaultHandler( + { method: "getOne", resource: "users", params: { id: 1 } }, + db, + options, + ); + expect(one.data).toEqual({ id: 1, name: "Alice" }); + + const updated = await defaultHandler( + { + method: "update", + resource: "users", + params: { + id: 1, + data: { ...one.data, name: "Alicia" }, + previousData: one.data, + }, + }, + db, + options, + ); + expect(updated.data).toEqual({ id: 1, name: "Alicia" }); + + const created = await defaultHandler( + { + method: "create", + resource: "users", + params: { data: { id: 999, name: "Dave" } }, + }, + db, + options, + ); + expect(created.data).toEqual({ id: 4, name: "Dave" }); + }); + + it("gets referenced records with the correct total", async () => { + const result = await defaultHandler( + { + method: "getManyReference", + resource: "posts", + params: { + id: 1, + target: "user_id", + filter: {}, + pagination: { page: 1, perPage: 1 }, + sort: { field: "id", order: "DESC" }, + }, + }, + db, + ); + + expect(result.data).toEqual([ + expect.objectContaining({ id: 2, title: "Second" }), + ]); + expect("total" in result && result.total).toBe(2); + }); + + it("filters id-like array fields with IN against DuckDB", async () => { + const result = await getListHandler( + { + method: "getList", + resource: "posts", + params: { + filter: { user_id: [1] }, + pagination: { page: 1, perPage: 10 }, + sort: { field: "id", order: "ASC" }, + }, + }, + db, + ); + + expect(result.total).toBe(2); + expect(result.data).toEqual([ + expect.objectContaining({ id: 1, user_id: 1 }), + expect.objectContaining({ id: 2, user_id: 1 }), + ]); + }); + + it("throws NotFoundError when getOne/update miss the row", async () => { + await expect( + getOneHandler( + { method: "getOne", resource: "users", params: { id: 999 } }, + db, + ), + ).rejects.toBeInstanceOf(NotFoundError); + + await expect( + updateHandler( + { + method: "update", + resource: "users", + params: { + id: 999, + data: { name: "Missing" }, + previousData: { id: 999, name: "x" }, + }, + }, + db, + ), + ).rejects.toBeInstanceOf(NotFoundError); + }); +}); diff --git a/packages/ra-data-duckdb/tsconfig.json b/packages/ra-data-duckdb/tsconfig.json new file mode 100644 index 0000000..884d778 --- /dev/null +++ b/packages/ra-data-duckdb/tsconfig.json @@ -0,0 +1,24 @@ +{ + "display": "Default", + "compilerOptions": { + "composite": false, + "declaration": true, + "declarationMap": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "inlineSources": false, + "isolatedModules": true, + "module": "ESNext", + "moduleResolution": "bundler", + "noUnusedLocals": false, + "noUnusedParameters": false, + "preserveWatchOutput": true, + "skipLibCheck": true, + "strict": false, + "target": "es2017", + "lib": ["es2017"], + "types": ["node", "jest"] + }, + "include": ["."], + "exclude": ["dist", "build", "node_modules"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d76c8d6..a817cf3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -55,7 +55,7 @@ importers: version: 18.3.1 react-admin: specifier: ^5.7.2 - version: 5.8.3(@mui/system@7.3.8(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@mui/utils@7.3.8(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(csstype@3.2.3)(react-dom@18.3.1(react@18.3.1))(react-is@19.2.4)(react@18.3.1) + version: 5.8.3(@mui/system@7.3.8(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@mui/utils@7.3.8(@types/react@18.3.12)(react@18.3.1))(@tanstack/react-query@5.90.21(react@18.3.1))(@types/react@18.3.12)(csstype@3.2.3)(react-dom@18.3.1(react@18.3.1))(react-is@19.2.4)(react@18.3.1) react-dom: specifier: 18.3.1 version: 18.3.1(react@18.3.1) @@ -82,6 +82,49 @@ importers: specifier: 5.8.3 version: 5.8.3 + apps/duckdb-admin: + dependencies: + '@duckdb/node-api': + specifier: 1.5.5-r.4 + version: 1.5.5-r.4 + '@mui/icons-material': + specifier: 5.17.1 + version: 5.17.1(@mui/material@5.17.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@types/react@19.1.1)(react@19.1.0) + '@mui/material': + specifier: 5.17.1 + version: 5.17.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + next: + specifier: 15.3.0 + version: 15.3.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + ra-data-duckdb: + specifier: workspace:* + version: link:../../packages/ra-data-duckdb + react: + specifier: 19.1.0 + version: 19.1.0 + react-admin: + specifier: ^5.14.1 + version: 5.14.1(@mui/system@7.3.8(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0))(@mui/utils@7.3.8(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(csstype@3.2.3)(react-dom@19.1.0(react@19.1.0))(react-is@19.2.4)(react@19.1.0) + react-dom: + specifier: 19.1.0 + version: 19.1.0(react@19.1.0) + react-hook-form: + specifier: ^7.65.0 + version: 7.71.1(react@19.1.0) + devDependencies: + '@types/node': + specifier: ^22 + version: 22.14.1 + '@types/react': + specifier: 19.1.1 + version: 19.1.1 + '@types/react-dom': + specifier: 19.1.2 + version: 19.1.2(@types/react@19.1.1) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + apps/website: dependencies: '@babel/core': @@ -169,7 +212,7 @@ importers: devDependencies: jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.2.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.8.3)) + version: 29.7.0(@types/node@22.14.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.14.1)(typescript@5.8.3)) prisma: specifier: ^6.8.2 version: 6.8.2(typescript@5.8.3) @@ -220,6 +263,52 @@ importers: specifier: ^5.8.3 version: 5.8.3 + packages/ra-data-duckdb: + dependencies: + axios: + specifier: ^1.13.5 + version: 1.13.5 + deverything: + specifier: ^5.5.1 + version: 5.5.1 + devDependencies: + '@babel/core': + specifier: 7.29.0 + version: 7.29.0 + '@babel/preset-env': + specifier: ^7.29.0 + version: 7.29.0(@babel/core@7.29.0) + '@babel/preset-typescript': + specifier: ^7.28.5 + version: 7.28.5(@babel/core@7.29.0) + '@duckdb/node-api': + specifier: 1.5.5-r.4 + version: 1.5.5-r.4 + '@types/jest': + specifier: ^30.0.0 + version: 30.0.0 + '@types/node': + specifier: ^25.2.3 + version: 25.2.3 + eslint-config-custom: + specifier: workspace:* + version: link:../eslint-config-custom + jest: + specifier: ^30.2.0 + version: 30.2.0(@types/node@25.2.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)) + react-admin: + specifier: ^5.14.1 + version: 5.14.1(@mui/system@7.3.8(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0))(@mui/utils@7.3.8(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(csstype@3.2.3)(react-dom@19.1.0(react@19.1.0))(react-is@19.2.4)(react@19.1.0) + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@25.2.3)(typescript@5.9.3) + tsup: + specifier: ^8.5.1 + version: 8.5.1(jiti@2.4.2)(postcss@8.4.31)(typescript@5.9.3) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + packages/ra-data-simple-prisma: dependencies: '@prisma/client': @@ -984,21 +1073,58 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} - '@emnapi/core@1.4.3': - resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==} + '@duckdb/node-api@1.5.5-r.4': + resolution: {integrity: sha512-8v0CZNo7aM6GQCNUHERGTrZWIfss8xKzZPF3ACNhPdMMrt7UjkNI5nRQ9FTFO7Yx8ghxYxVpdotmBBMQ5vXUOA==} + + '@duckdb/node-bindings-darwin-arm64@1.5.5-r.4': + resolution: {integrity: sha512-4OdO3pkoJzAZDnZ2iyehi67XL4+WqHPCGYWbyAsYyhGJS+WscGrAnFhMhHudMM2XF8pIEM2tuOKMmwWnTNN2wQ==} + cpu: [arm64] + os: [darwin] + + '@duckdb/node-bindings-darwin-x64@1.5.5-r.4': + resolution: {integrity: sha512-WHg3E+TupdujG31LGujMMcmtPIdW6rqRHeihlKgJR8EMetHycoYkwYxRud0niitJvS+uV0du/6pdemzs3HG9GQ==} + cpu: [x64] + os: [darwin] + + '@duckdb/node-bindings-linux-arm64-musl@1.5.5-r.4': + resolution: {integrity: sha512-Tswnf+/XWpOcFJNUUu1fkFIX/su4tMcnNz0ZV0Nru3/CkJKIMwBh+VL4SM9YV4zPK90GC4Lm8gzafjc1qDmfyQ==} + cpu: [arm64] + os: [linux] + + '@duckdb/node-bindings-linux-arm64@1.5.5-r.4': + resolution: {integrity: sha512-VIeHMpYAKpGiWZ4QYsOhODAHDEcXcn089aKty0MFsMEKaEfRJWixKwnwXy/ba2/wwFmnPiSFhKNqygj2BrQbqw==} + cpu: [arm64] + os: [linux] + + '@duckdb/node-bindings-linux-x64-musl@1.5.5-r.4': + resolution: {integrity: sha512-h0ixrgGHtHh+C/Fu1eAL9hX4iCf8yuyJN6Y24Gh8axXXP8UuSh0rQRAQUQTYarQ8pm2qSG/67ZYjyYYydD+J/w==} + cpu: [x64] + os: [linux] + + '@duckdb/node-bindings-linux-x64@1.5.5-r.4': + resolution: {integrity: sha512-EY+CL/4h8MQZd9MxTBq+98m3U9osmvHBwhE9b5fMWQGt2I6p1j8fvX0SXiwy9KBfQIbjVUOf5XvGdXncI54KSg==} + cpu: [x64] + os: [linux] + + '@duckdb/node-bindings-win32-arm64@1.5.5-r.4': + resolution: {integrity: sha512-uorAnySIMwWkRDuUhM1yTDxWIislnX8mndhFIw6r4VKhVW61HEOOMX5oAgkSQFII4RNWpC5PCcUAEhSATgNvVQ==} + cpu: [arm64] + os: [win32] + + '@duckdb/node-bindings-win32-x64@1.5.5-r.4': + resolution: {integrity: sha512-X9XGcWQ10P3mvUIaMXXk2bi94Cow7b/ziTPMKxJ0U8U3wQPxsLzEUP7D7C/KrBWINl5am2k+5SkDVyA/THUgPg==} + cpu: [x64] + os: [win32] + + '@duckdb/node-bindings@1.5.5-r.4': + resolution: {integrity: sha512-n+4hEfjp4vny3BuWn5p1Gh5CzHaPRxoI8TzTytxL1GMlIKKrXcg/o6sSAjrsROUXGAM4WQCiWPLKnPsjJJSggg==} '@emnapi/core@1.8.1': resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} - '@emnapi/runtime@1.4.3': - resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==} - '@emnapi/runtime@1.8.1': resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} - '@emnapi/wasi-threads@1.0.2': - resolution: {integrity: sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==} - '@emnapi/wasi-threads@1.1.0': resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} @@ -1767,9 +1893,6 @@ packages: '@mui/core-downloads-tracker@5.17.1': resolution: {integrity: sha512-OcZj+cs6EfUD39IoPBOgN61zf1XFVY+imsGoBDwXeSq2UHJZE3N59zzBOVjclck91Ne3e9gudONOeILvHCIhUA==} - '@mui/core-downloads-tracker@7.3.8': - resolution: {integrity: sha512-s9UHZo7QJVly7gNArEZkbbsimHqJZhElgBpXIJdehZ4OWXt+CCr0SBDgUCDJnQrqpd1dWK2dLq5rmO4mCBmI3w==} - '@mui/icons-material@5.17.1': resolution: {integrity: sha512-CN86LocjkunFGG0yPlO4bgqHkNGgaEOEc3X/jG5Bzm401qYw79/SaLrofA7yAKCCXAGdIGnLoMHohc3+ubs95A==} engines: {node: '>=12.0.0'} @@ -1781,17 +1904,6 @@ packages: '@types/react': optional: true - '@mui/icons-material@7.3.8': - resolution: {integrity: sha512-88sWg/UJc1X82OMO+ISR4E3P58I3BjFVg0qkmDu7OWlN8VijneZD3ylFA+ImxuPjMHW3SHosfSJYy1fztoz0fw==} - engines: {node: '>=14.0.0'} - peerDependencies: - '@mui/material': ^7.3.8 - '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@mui/material@5.17.1': resolution: {integrity: sha512-2B33kQf+GmPnrvXXweWAx+crbiUEsxCdCN979QDYnlH9ox4pd+0/IBriWLV+l6ORoBF60w39cWjFnJYGFdzXcw==} engines: {node: '>=12.0.0'} @@ -1809,26 +1921,6 @@ packages: '@types/react': optional: true - '@mui/material@7.3.8': - resolution: {integrity: sha512-QKd1RhDXE1hf2sQDNayA9ic9jGkEgvZOf0tTkJxlBPG8ns8aS4rS8WwYURw2x5y3739p0HauUXX9WbH7UufFLw==} - engines: {node: '>=14.0.0'} - peerDependencies: - '@emotion/react': ^11.5.0 - '@emotion/styled': ^11.3.0 - '@mui/material-pigment-css': ^7.3.8 - '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - '@mui/material-pigment-css': - optional: true - '@types/react': - optional: true - '@mui/private-theming@5.17.1': resolution: {integrity: sha512-XMxU0NTYcKqdsG8LRmSoxERPXwMbp16sIXPcLVgLGII/bVNagX0xaheWAwFv8+zDK7tI3ajllkuD3GZZE++ICQ==} engines: {node: '>=12.0.0'} @@ -1954,9 +2046,6 @@ packages: '@types/react': optional: true - '@napi-rs/wasm-runtime@0.2.10': - resolution: {integrity: sha512-bCsCyeZEwVErsGmyPNSzwfwFn4OdxBj0mmv6hOFucB/k81Ojdu68RbZdxYsRQUPc9l6SU5F/cG+bXgWs3oUgsQ==} - '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} @@ -2331,17 +2420,9 @@ packages: '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} - '@tanstack/query-core@5.79.0': - resolution: {integrity: sha512-s+epTqqLM0/TbJzMAK7OEhZIzh63P9sWz5HEFc5XHL4FvKQXQkcjI8F3nee+H/xVVn7mrP610nVXwOytTSYd0w==} - '@tanstack/query-core@5.90.20': resolution: {integrity: sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==} - '@tanstack/react-query@5.79.0': - resolution: {integrity: sha512-DjC4JIYZnYzxaTzbg3osOU63VNLP67dOrWet2cZvXgmgwAXNxfS52AMq86M5++ILuzW+BqTUEVMTjhrZ7/XBuA==} - peerDependencies: - react: ^18 || ^19 - '@tanstack/react-query@5.90.21': resolution: {integrity: sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==} peerDependencies: @@ -2362,9 +2443,6 @@ packages: '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} - '@tybys/wasm-util@0.9.0': - resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} - '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -2374,9 +2452,6 @@ packages: '@types/babel__template@7.4.4': resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - '@types/babel__traverse@7.20.7': - resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==} - '@types/babel__traverse@7.28.0': resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} @@ -2422,9 +2497,6 @@ packages: '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} - '@types/prop-types@15.7.14': - resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==} - '@types/prop-types@15.7.15': resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} @@ -2521,6 +2593,7 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + deprecated: Potential CWE-502 - Update to 1.3.1 or higher '@unrs/resolver-binding-android-arm-eabi@1.11.1': resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} @@ -2537,171 +2610,86 @@ packages: cpu: [arm64] os: [darwin] - '@unrs/resolver-binding-darwin-arm64@1.7.8': - resolution: {integrity: sha512-rsRK8T7yxraNRDmpFLZCWqpea6OlXPNRRCjWMx24O1V86KFol7u2gj9zJCv6zB1oJjtnzWceuqdnCgOipFcJPA==} - cpu: [arm64] - os: [darwin] - '@unrs/resolver-binding-darwin-x64@1.11.1': resolution: {integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==} cpu: [x64] os: [darwin] - '@unrs/resolver-binding-darwin-x64@1.7.8': - resolution: {integrity: sha512-16yEMWa+Olqkk8Kl6Bu0ltT5OgEedkSAsxcz1B3yEctrDYp3EMBu/5PPAGhWVGnwhtf3hNe3y15gfYBAjOv5tQ==} - cpu: [x64] - os: [darwin] - '@unrs/resolver-binding-freebsd-x64@1.11.1': resolution: {integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==} cpu: [x64] os: [freebsd] - '@unrs/resolver-binding-freebsd-x64@1.7.8': - resolution: {integrity: sha512-ST4uqF6FmdZQgv+Q73FU1uHzppeT4mhX3IIEmHlLObrv5Ep50olWRz0iQ4PWovadjHMTAmpuJAGaAuCZYb7UAQ==} - cpu: [x64] - os: [freebsd] - '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': resolution: {integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==} cpu: [arm] os: [linux] - '@unrs/resolver-binding-linux-arm-gnueabihf@1.7.8': - resolution: {integrity: sha512-Z/A/4Rm2VWku2g25C3tVb986fY6unx5jaaCFpx1pbAj0OKkyuJ5wcQLHvNbIcJ9qhiYwXfrkB7JNlxrAbg7YFg==} - cpu: [arm] - os: [linux] - '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': resolution: {integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==} cpu: [arm] os: [linux] - '@unrs/resolver-binding-linux-arm-musleabihf@1.7.8': - resolution: {integrity: sha512-HN0p7o38qKmDo3bZUiQa6gP7Qhf0sKgJZtRfSHi6JL2Gi4NaUVF0EO1sQ1RHbeQ4VvfjUGMh3QE5dxEh06BgQQ==} - cpu: [arm] - os: [linux] - '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] - '@unrs/resolver-binding-linux-arm64-gnu@1.7.8': - resolution: {integrity: sha512-HsoVqDBt9G69AN0KWeDNJW+7i8KFlwxrbbnJffgTGpiZd6Jw+Q95sqkXp8y458KhKduKLmXfVZGnKBTNxAgPjw==} - cpu: [arm64] - os: [linux] - '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] - '@unrs/resolver-binding-linux-arm64-musl@1.7.8': - resolution: {integrity: sha512-VfR2yTDUbUvn+e/Aw22CC9fQg9zdShHAfwWctNBdOk7w9CHWl2OtYlcMvjzMAns8QxoHQoqn3/CEnZ4Ts7hfrA==} - cpu: [arm64] - os: [linux] - '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] - '@unrs/resolver-binding-linux-ppc64-gnu@1.7.8': - resolution: {integrity: sha512-xUauVQNz4uDgs4UJJiUAwMe3N0PA0wvtImh7V0IFu++UKZJhssXbKHBRR4ecUJpUHCX2bc4Wc8sGsB6P+7BANg==} - cpu: [ppc64] - os: [linux] - '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] - '@unrs/resolver-binding-linux-riscv64-gnu@1.7.8': - resolution: {integrity: sha512-GqyIB+CuSHGhhc8ph5RrurtNetYJjb6SctSHafqmdGcRuGi6uyTMR8l18hMEhZFsXdFMc/MpInPLvmNV22xn+A==} - cpu: [riscv64] - os: [linux] - '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] - '@unrs/resolver-binding-linux-riscv64-musl@1.7.8': - resolution: {integrity: sha512-eEU3rWIFRv60xaAbtsgwHNWRZGD7cqkpCvNtio/f1TjEE3HfKLzPNB24fA9X/8ZXQrGldE65b7UKK3PmO4eWIQ==} - cpu: [riscv64] - os: [linux] - '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] - '@unrs/resolver-binding-linux-s390x-gnu@1.7.8': - resolution: {integrity: sha512-GVLI0f4I4TlLqEUoOFvTWedLsJEdvsD0+sxhdvQ5s+N+m2DSynTs8h9jxR0qQbKlpHWpc2Ortz3z48NHRT4l+w==} - cpu: [s390x] - os: [linux] - '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] - '@unrs/resolver-binding-linux-x64-gnu@1.7.8': - resolution: {integrity: sha512-GX1pZ/4ncUreB0Rlp1l7bhKAZ8ZmvDIgXdeb5V2iK0eRRF332+6gRfR/r5LK88xfbtOpsmRHU6mQ4N8ZnwvGEA==} - cpu: [x64] - os: [linux] - '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] - '@unrs/resolver-binding-linux-x64-musl@1.7.8': - resolution: {integrity: sha512-n1N84MnsvDupzVuYqJGj+2pb9s8BI1A5RgXHvtVFHedGZVBCFjDpQVRlmsFMt6xZiKwDPaqsM16O/1isCUGt7w==} - cpu: [x64] - os: [linux] - '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@unrs/resolver-binding-wasm32-wasi@1.7.8': - resolution: {integrity: sha512-x94WnaU5g+pCPDVedfnXzoG6lCOF2xFGebNwhtbJCWfceE94Zj8aysSxdxotlrZrxnz5D3ijtyFUYtpz04n39Q==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': resolution: {integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==} cpu: [arm64] os: [win32] - '@unrs/resolver-binding-win32-arm64-msvc@1.7.8': - resolution: {integrity: sha512-vst2u8EJZ5L6jhJ6iLis3w9rg16aYqRxQuBAMYQRVrPMI43693hLP7DuqyOBRKgsQXy9/jgh204k0ViHkqQgdg==} - cpu: [arm64] - os: [win32] - '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': resolution: {integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==} cpu: [ia32] os: [win32] - '@unrs/resolver-binding-win32-ia32-msvc@1.7.8': - resolution: {integrity: sha512-yb3LZOLMFqnA+/ShlE1E5bpYPGDsA590VHHJPB+efnyowT776GJXBoh82em6O9WmYBUq57YblGTcMYAFBm72HA==} - cpu: [ia32] - os: [win32] - '@unrs/resolver-binding-win32-x64-msvc@1.11.1': resolution: {integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==} cpu: [x64] os: [win32] - '@unrs/resolver-binding-win32-x64-msvc@1.7.8': - resolution: {integrity: sha512-hHKFx+opG5BA3/owMXon8ypwSotBGTdblG6oda/iOu9+OEYnk0cxD2uIcGyGT8jCK578kV+xMrNxqbn8Zjlpgw==} - cpu: [x64] - os: [win32] - acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -2711,11 +2699,6 @@ packages: resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} engines: {node: '>=0.4.0'} - acorn@8.14.1: - resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} - engines: {node: '>=0.4.0'} - hasBin: true - acorn@8.15.0: resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} engines: {node: '>=0.4.0'} @@ -2994,12 +2977,6 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001720: - resolution: {integrity: sha512-Ec/2yV2nNPwb4DnTANEV99ZWwm3ZWfdlfkQbWSDDt+PsXEVYwlhPH8tdMaPunYTKKmz7AnHi2oNEi1GcmKCD8g==} - - caniuse-lite@1.0.30001735: - resolution: {integrity: sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==} - caniuse-lite@1.0.30001769: resolution: {integrity: sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==} @@ -3225,8 +3202,8 @@ packages: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} engines: {node: '>=8'} - detect-libc@2.0.4: - resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} detect-newline@3.1.0: @@ -3459,6 +3436,7 @@ packages: eslint@9.24.0: resolution: {integrity: sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true peerDependencies: jiti: '*' @@ -4481,11 +4459,6 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - napi-postinstall@0.2.4: - resolution: {integrity: sha512-ZEzHJwBhZ8qQSbknHqYcdtQVr8zUgGyM/q6h6qAyhtyVMNrSgDhrC4disf03dYW0e+czXyLnZINnCTEkWy0eJg==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - hasBin: true - napi-postinstall@0.3.4: resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -4910,12 +4883,6 @@ packages: peerDependencies: react: '>=16.13.1' - react-hook-form@7.56.4: - resolution: {integrity: sha512-Rob7Ftz2vyZ/ZGsQZPaRdIefkgOSrQSPXfqBdvOPwJfoGnjwRJUs7EM7Kc1mcoDv3NOtqBzPGbcMB8CGn9CKgw==} - engines: {node: '>=18.0.0'} - peerDependencies: - react: ^16.8.0 || ^17 || ^18 || ^19 - react-hook-form@7.71.1: resolution: {integrity: sha512-9SUJKCGKo8HUSsCO+y0CtqkqI5nNuaDqTxyqPsZPqIwudpj4rCrAz/jZV+jn57bx5gtZKOh3neQu94DXMc+w5w==} engines: {node: '>=18.0.0'} @@ -5554,9 +5521,6 @@ packages: unrs-resolver@1.11.1: resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} - unrs-resolver@1.7.8: - resolution: {integrity: sha512-2zsXwyOXmCX9nGz4vhtZRYhe30V78heAv+KDc21A/KMdovGHbZcixeD5JHEF0DrFXzdytwuzYclcPbvp8A3Jlw==} - update-browserslist-db@1.1.3: resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} hasBin: true @@ -5574,6 +5538,7 @@ packages: uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true v8-compile-cache-lib@3.0.1: @@ -5732,8 +5697,8 @@ snapshots: '@babel/generator@7.27.3': dependencies: - '@babel/parser': 7.27.4 - '@babel/types': 7.27.3 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 jsesc: 3.1.0 @@ -5809,7 +5774,7 @@ snapshots: '@babel/helper-module-imports@7.27.1': dependencies: '@babel/traverse': 7.29.0 - '@babel/types': 7.27.3 + '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color @@ -5825,7 +5790,7 @@ snapshots: '@babel/core': 7.26.10 '@babel/helper-module-imports': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.27.4 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color @@ -5887,8 +5852,8 @@ snapshots: '@babel/helpers@7.27.4': dependencies: - '@babel/template': 7.27.2 - '@babel/types': 7.27.3 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 '@babel/helpers@7.28.6': dependencies: @@ -5897,7 +5862,7 @@ snapshots: '@babel/parser@7.27.4': dependencies: - '@babel/types': 7.27.3 + '@babel/types': 7.29.0 '@babel/parser@7.29.0': dependencies: @@ -6482,9 +6447,9 @@ snapshots: '@babel/template@7.27.2': dependencies: - '@babel/code-frame': 7.27.1 - '@babel/parser': 7.27.4 - '@babel/types': 7.27.3 + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 '@babel/template@7.28.6': dependencies: @@ -6494,12 +6459,12 @@ snapshots: '@babel/traverse@7.27.4': dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.27.3 - '@babel/parser': 7.27.4 - '@babel/template': 7.27.2 - '@babel/types': 7.27.3 - debug: 4.4.1 + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -6675,29 +6640,54 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@emnapi/core@1.4.3': + '@duckdb/node-api@1.5.5-r.4': dependencies: - '@emnapi/wasi-threads': 1.0.2 - tslib: 2.8.1 + '@duckdb/node-bindings': 1.5.5-r.4 + + '@duckdb/node-bindings-darwin-arm64@1.5.5-r.4': optional: true - '@emnapi/core@1.8.1': - dependencies: - '@emnapi/wasi-threads': 1.1.0 - tslib: 2.8.1 + '@duckdb/node-bindings-darwin-x64@1.5.5-r.4': optional: true - '@emnapi/runtime@1.4.3': - dependencies: - tslib: 2.8.1 + '@duckdb/node-bindings-linux-arm64-musl@1.5.5-r.4': optional: true - '@emnapi/runtime@1.8.1': + '@duckdb/node-bindings-linux-arm64@1.5.5-r.4': + optional: true + + '@duckdb/node-bindings-linux-x64-musl@1.5.5-r.4': + optional: true + + '@duckdb/node-bindings-linux-x64@1.5.5-r.4': + optional: true + + '@duckdb/node-bindings-win32-arm64@1.5.5-r.4': + optional: true + + '@duckdb/node-bindings-win32-x64@1.5.5-r.4': + optional: true + + '@duckdb/node-bindings@1.5.5-r.4': + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + '@duckdb/node-bindings-darwin-arm64': 1.5.5-r.4 + '@duckdb/node-bindings-darwin-x64': 1.5.5-r.4 + '@duckdb/node-bindings-linux-arm64': 1.5.5-r.4 + '@duckdb/node-bindings-linux-arm64-musl': 1.5.5-r.4 + '@duckdb/node-bindings-linux-x64': 1.5.5-r.4 + '@duckdb/node-bindings-linux-x64-musl': 1.5.5-r.4 + '@duckdb/node-bindings-win32-arm64': 1.5.5-r.4 + '@duckdb/node-bindings-win32-x64': 1.5.5-r.4 + + '@emnapi/core@1.8.1': dependencies: + '@emnapi/wasi-threads': 1.1.0 tslib: 2.8.1 optional: true - '@emnapi/wasi-threads@1.0.2': + '@emnapi/runtime@1.8.1': dependencies: tslib: 2.8.1 optional: true @@ -6710,7 +6700,7 @@ snapshots: '@emotion/babel-plugin@11.13.5': dependencies: '@babel/helper-module-imports': 7.28.6 - '@babel/runtime': 7.27.4 + '@babel/runtime': 7.28.6 '@emotion/hash': 0.9.2 '@emotion/memoize': 0.9.0 '@emotion/serialize': 1.3.3 @@ -6781,13 +6771,13 @@ snapshots: '@emotion/memoize': 0.9.0 '@emotion/unitless': 0.10.0 '@emotion/utils': 1.4.2 - csstype: 3.1.3 + csstype: 3.2.3 '@emotion/sheet@1.4.0': {} '@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1)': dependencies: - '@babel/runtime': 7.27.4 + '@babel/runtime': 7.28.6 '@emotion/babel-plugin': 11.13.5 '@emotion/is-prop-valid': 1.3.1 '@emotion/react': 11.14.0(@types/react@18.3.12)(react@18.3.1) @@ -6992,7 +6982,7 @@ snapshots: '@eslint/config-array@0.20.0': dependencies: '@eslint/object-schema': 2.1.6 - debug: 4.4.1 + debug: 4.4.3 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -7010,7 +7000,7 @@ snapshots: '@eslint/eslintrc@3.3.1': dependencies: ajv: 6.12.6 - debug: 4.4.1 + debug: 4.4.3 espree: 10.3.0 globals: 14.0.0 ignore: 5.3.2 @@ -7112,7 +7102,7 @@ snapshots: '@img/sharp-wasm32@0.34.2': dependencies: - '@emnapi/runtime': 1.4.3 + '@emnapi/runtime': 1.8.1 optional: true '@img/sharp-win32-arm64@0.34.2': @@ -7153,7 +7143,7 @@ snapshots: '@jest/console@29.7.0': dependencies: '@jest/types': 29.6.3 - '@types/node': 25.2.3 + '@types/node': 22.14.1 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -7162,12 +7152,47 @@ snapshots: '@jest/console@30.2.0': dependencies: '@jest/types': 30.2.0 - '@types/node': 25.2.3 + '@types/node': 22.14.1 chalk: 4.1.2 jest-message-util: 30.2.0 jest-util: 30.2.0 slash: 3.0.0 + '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.14.1)(typescript@5.8.3))': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.14.1 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@22.14.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.14.1)(typescript@5.8.3)) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.8.3))': dependencies: '@jest/console': 29.7.0 @@ -7175,14 +7200,14 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 25.2.3 + '@types/node': 22.14.1 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@25.2.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.8.3)) + jest-config: 29.7.0(@types/node@22.14.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.8.3)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -7211,14 +7236,14 @@ snapshots: '@jest/test-result': 30.2.0 '@jest/transform': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 25.2.3 + '@types/node': 22.14.1 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 4.4.0 exit-x: 0.2.2 graceful-fs: 4.2.11 jest-changed-files: 30.2.0 - jest-config: 30.2.0(@types/node@25.2.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)) + jest-config: 30.2.0(@types/node@22.14.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)) jest-haste-map: 30.2.0 jest-message-util: 30.2.0 jest-regex-util: 30.0.1 @@ -7245,14 +7270,14 @@ snapshots: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 25.2.3 + '@types/node': 22.14.1 jest-mock: 29.7.0 '@jest/environment@30.2.0': dependencies: '@jest/fake-timers': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 25.2.3 + '@types/node': 22.14.1 jest-mock: 30.2.0 '@jest/expect-utils@29.7.0': @@ -7281,7 +7306,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 25.2.3 + '@types/node': 22.14.1 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -7290,7 +7315,7 @@ snapshots: dependencies: '@jest/types': 30.2.0 '@sinonjs/fake-timers': 13.0.5 - '@types/node': 25.2.3 + '@types/node': 22.14.1 jest-message-util: 30.2.0 jest-mock: 30.2.0 jest-util: 30.2.0 @@ -7317,7 +7342,7 @@ snapshots: '@jest/pattern@30.0.1': dependencies: - '@types/node': 25.2.3 + '@types/node': 22.14.1 jest-regex-util: 30.0.1 '@jest/reporters@29.7.0': @@ -7328,7 +7353,7 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.31 - '@types/node': 25.2.3 + '@types/node': 22.14.1 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -7357,7 +7382,7 @@ snapshots: '@jest/transform': 30.2.0 '@jest/types': 30.2.0 '@jridgewell/trace-mapping': 0.3.31 - '@types/node': 25.2.3 + '@types/node': 22.14.1 chalk: 4.1.2 collect-v8-coverage: 1.0.3 exit-x: 0.2.2 @@ -7477,7 +7502,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 25.2.3 + '@types/node': 22.14.1 '@types/yargs': 17.0.33 chalk: 4.1.2 @@ -7487,7 +7512,7 @@ snapshots: '@jest/schemas': 30.0.5 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 25.2.3 + '@types/node': 22.14.1 '@types/yargs': 17.0.35 chalk: 4.1.2 @@ -7553,35 +7578,25 @@ snapshots: '@mui/core-downloads-tracker@5.17.1': {} - '@mui/core-downloads-tracker@7.3.8': {} - '@mui/icons-material@5.17.1(@mui/material@5.17.1(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.12)(react@18.3.1)': dependencies: - '@babel/runtime': 7.27.4 + '@babel/runtime': 7.28.6 '@mui/material': 5.17.1(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 optionalDependencies: '@types/react': 18.3.12 '@mui/icons-material@5.17.1(@mui/material@5.17.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@types/react@19.1.1)(react@19.1.0)': - dependencies: - '@babel/runtime': 7.27.4 - '@mui/material': 5.17.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - react: 19.1.0 - optionalDependencies: - '@types/react': 19.1.1 - - '@mui/icons-material@7.3.8(@mui/material@7.3.8(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@types/react@19.1.1)(react@19.1.0)': dependencies: '@babel/runtime': 7.28.6 - '@mui/material': 7.3.8(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@mui/material': 5.17.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: 19.1.0 optionalDependencies: '@types/react': 19.1.1 '@mui/material@5.17.1(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.27.4 + '@babel/runtime': 7.28.6 '@mui/core-downloads-tracker': 5.17.1 '@mui/system': 5.17.1(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1) '@mui/types': 7.2.24(@types/react@18.3.12) @@ -7589,11 +7604,11 @@ snapshots: '@popperjs/core': 2.11.8 '@types/react-transition-group': 4.4.12(@types/react@18.3.12) clsx: 2.1.1 - csstype: 3.1.3 + csstype: 3.2.3 prop-types: 15.8.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-is: 19.1.0 + react-is: 19.2.4 react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) optionalDependencies: '@emotion/react': 11.14.0(@types/react@18.3.12)(react@18.3.1) @@ -7602,7 +7617,7 @@ snapshots: '@mui/material@5.17.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - '@babel/runtime': 7.27.4 + '@babel/runtime': 7.28.6 '@mui/core-downloads-tracker': 5.17.1 '@mui/system': 5.17.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0) '@mui/types': 7.2.24(@types/react@19.1.1) @@ -7610,27 +7625,6 @@ snapshots: '@popperjs/core': 2.11.8 '@types/react-transition-group': 4.4.12(@types/react@19.1.1) clsx: 2.1.1 - csstype: 3.1.3 - prop-types: 15.8.1 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - react-is: 19.1.0 - react-transition-group: 4.4.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.1.1)(react@19.1.0) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0) - '@types/react': 19.1.1 - - '@mui/material@7.3.8(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@babel/runtime': 7.28.6 - '@mui/core-downloads-tracker': 7.3.8 - '@mui/system': 7.3.8(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0) - '@mui/types': 7.4.11(@types/react@19.1.1) - '@mui/utils': 7.3.8(@types/react@19.1.1)(react@19.1.0) - '@popperjs/core': 2.11.8 - '@types/react-transition-group': 4.4.12(@types/react@19.1.1) - clsx: 2.1.1 csstype: 3.2.3 prop-types: 15.8.1 react: 19.1.0 @@ -7680,9 +7674,9 @@ snapshots: '@mui/styled-engine@5.16.14(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.27.4 + '@babel/runtime': 7.28.6 '@emotion/cache': 11.14.0 - csstype: 3.1.3 + csstype: 3.2.3 prop-types: 15.8.1 react: 18.3.1 optionalDependencies: @@ -7691,9 +7685,9 @@ snapshots: '@mui/styled-engine@5.16.14(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0))(react@19.1.0)': dependencies: - '@babel/runtime': 7.27.4 + '@babel/runtime': 7.28.6 '@emotion/cache': 11.14.0 - csstype: 3.1.3 + csstype: 3.2.3 prop-types: 15.8.1 react: 19.1.0 optionalDependencies: @@ -7774,13 +7768,13 @@ snapshots: '@mui/system@5.17.1(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1)': dependencies: - '@babel/runtime': 7.27.4 + '@babel/runtime': 7.28.6 '@mui/private-theming': 5.17.1(@types/react@18.3.12)(react@18.3.1) '@mui/styled-engine': 5.16.14(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1) '@mui/types': 7.2.24(@types/react@18.3.12) '@mui/utils': 5.17.1(@types/react@18.3.12)(react@18.3.1) clsx: 2.1.1 - csstype: 3.1.3 + csstype: 3.2.3 prop-types: 15.8.1 react: 18.3.1 optionalDependencies: @@ -7790,13 +7784,13 @@ snapshots: '@mui/system@5.17.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0)': dependencies: - '@babel/runtime': 7.27.4 + '@babel/runtime': 7.28.6 '@mui/private-theming': 5.17.1(@types/react@19.1.1)(react@19.1.0) '@mui/styled-engine': 5.16.14(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0))(react@19.1.0) '@mui/types': 7.2.24(@types/react@19.1.1) '@mui/utils': 5.17.1(@types/react@19.1.1)(react@19.1.0) clsx: 2.1.1 - csstype: 3.1.3 + csstype: 3.2.3 prop-types: 15.8.1 react: 19.1.0 optionalDependencies: @@ -7858,9 +7852,9 @@ snapshots: '@mui/utils@5.17.1(@types/react@18.3.12)(react@18.3.1)': dependencies: - '@babel/runtime': 7.27.4 + '@babel/runtime': 7.28.6 '@mui/types': 7.2.24(@types/react@18.3.12) - '@types/prop-types': 15.7.14 + '@types/prop-types': 15.7.15 clsx: 2.1.1 prop-types: 15.8.1 react: 18.3.1 @@ -7870,9 +7864,9 @@ snapshots: '@mui/utils@5.17.1(@types/react@19.1.1)(react@19.1.0)': dependencies: - '@babel/runtime': 7.27.4 + '@babel/runtime': 7.28.6 '@mui/types': 7.2.24(@types/react@19.1.1) - '@types/prop-types': 15.7.14 + '@types/prop-types': 15.7.15 clsx: 2.1.1 prop-types: 15.8.1 react: 19.1.0 @@ -7904,13 +7898,6 @@ snapshots: optionalDependencies: '@types/react': 19.1.1 - '@napi-rs/wasm-runtime@0.2.10': - dependencies: - '@emnapi/core': 1.4.3 - '@emnapi/runtime': 1.4.3 - '@tybys/wasm-util': 0.9.0 - optional: true - '@napi-rs/wasm-runtime@0.2.12': dependencies: '@emnapi/core': 1.8.1 @@ -8171,13 +8158,11 @@ snapshots: dependencies: tslib: 2.8.1 - '@tanstack/query-core@5.79.0': {} - '@tanstack/query-core@5.90.20': {} - '@tanstack/react-query@5.79.0(react@18.3.1)': + '@tanstack/react-query@5.90.21(react@18.3.1)': dependencies: - '@tanstack/query-core': 5.79.0 + '@tanstack/query-core': 5.90.20 react: 18.3.1 '@tanstack/react-query@5.90.21(react@19.1.0)': @@ -8198,11 +8183,6 @@ snapshots: tslib: 2.8.1 optional: true - '@tybys/wasm-util@0.9.0': - dependencies: - tslib: 2.8.1 - optional: true - '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.29.0 @@ -8220,10 +8200,6 @@ snapshots: '@babel/parser': 7.29.0 '@babel/types': 7.29.0 - '@types/babel__traverse@7.20.7': - dependencies: - '@babel/types': 7.29.0 - '@types/babel__traverse@7.28.0': dependencies: '@babel/types': 7.29.0 @@ -8234,7 +8210,7 @@ snapshots: '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 25.2.3 + '@types/node': 22.14.1 '@types/istanbul-lib-coverage@2.0.6': {} @@ -8269,8 +8245,6 @@ snapshots: '@types/parse-json@4.0.2': {} - '@types/prop-types@15.7.14': {} - '@types/prop-types@15.7.15': {} '@types/react-dom@18.3.1': @@ -8291,12 +8265,12 @@ snapshots: '@types/react@18.3.12': dependencies: - '@types/prop-types': 15.7.14 - csstype: 3.1.3 + '@types/prop-types': 15.7.15 + csstype: 3.2.3 '@types/react@19.1.1': dependencies: - csstype: 3.1.3 + csstype: 3.2.3 '@types/stack-utils@2.0.3': {} @@ -8496,119 +8470,64 @@ snapshots: '@unrs/resolver-binding-darwin-arm64@1.11.1': optional: true - '@unrs/resolver-binding-darwin-arm64@1.7.8': - optional: true - '@unrs/resolver-binding-darwin-x64@1.11.1': optional: true - '@unrs/resolver-binding-darwin-x64@1.7.8': - optional: true - '@unrs/resolver-binding-freebsd-x64@1.11.1': optional: true - '@unrs/resolver-binding-freebsd-x64@1.7.8': - optional: true - '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': optional: true - '@unrs/resolver-binding-linux-arm-gnueabihf@1.7.8': - optional: true - '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': optional: true - '@unrs/resolver-binding-linux-arm-musleabihf@1.7.8': - optional: true - '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': optional: true - '@unrs/resolver-binding-linux-arm64-gnu@1.7.8': - optional: true - '@unrs/resolver-binding-linux-arm64-musl@1.11.1': optional: true - '@unrs/resolver-binding-linux-arm64-musl@1.7.8': - optional: true - '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': optional: true - '@unrs/resolver-binding-linux-ppc64-gnu@1.7.8': - optional: true - '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': optional: true - '@unrs/resolver-binding-linux-riscv64-gnu@1.7.8': - optional: true - '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': optional: true - '@unrs/resolver-binding-linux-riscv64-musl@1.7.8': - optional: true - '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': optional: true - '@unrs/resolver-binding-linux-s390x-gnu@1.7.8': - optional: true - '@unrs/resolver-binding-linux-x64-gnu@1.11.1': optional: true - '@unrs/resolver-binding-linux-x64-gnu@1.7.8': - optional: true - '@unrs/resolver-binding-linux-x64-musl@1.11.1': optional: true - '@unrs/resolver-binding-linux-x64-musl@1.7.8': - optional: true - '@unrs/resolver-binding-wasm32-wasi@1.11.1': dependencies: '@napi-rs/wasm-runtime': 0.2.12 optional: true - '@unrs/resolver-binding-wasm32-wasi@1.7.8': - dependencies: - '@napi-rs/wasm-runtime': 0.2.10 - optional: true - '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': optional: true - '@unrs/resolver-binding-win32-arm64-msvc@1.7.8': - optional: true - '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': optional: true - '@unrs/resolver-binding-win32-ia32-msvc@1.7.8': - optional: true - '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true - '@unrs/resolver-binding-win32-x64-msvc@1.7.8': - optional: true - - acorn-jsx@5.3.2(acorn@8.14.1): + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: - acorn: 8.14.1 + acorn: 8.15.0 acorn-walk@8.3.4: dependencies: acorn: 8.15.0 - acorn@8.14.1: {} - acorn@8.15.0: {} ajv@6.12.6: @@ -8807,7 +8726,7 @@ snapshots: '@babel/template': 7.28.6 '@babel/types': 7.29.0 '@types/babel__core': 7.20.5 - '@types/babel__traverse': 7.20.7 + '@types/babel__traverse': 7.28.0 babel-plugin-jest-hoist@30.2.0: dependencies: @@ -8926,7 +8845,7 @@ snapshots: browserslist@4.25.2: dependencies: - caniuse-lite: 1.0.30001735 + caniuse-lite: 1.0.30001769 electron-to-chromium: 1.5.203 node-releases: 2.0.19 update-browserslist-db: 1.1.3(browserslist@4.25.2) @@ -8984,10 +8903,6 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001720: {} - - caniuse-lite@1.0.30001735: {} - caniuse-lite@1.0.30001769: {} chalk@4.1.2: @@ -9083,6 +8998,21 @@ snapshots: path-type: 4.0.0 yaml: 1.10.2 + create-jest@29.7.0(@types/node@22.14.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.14.1)(typescript@5.8.3)): + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@22.14.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.14.1)(typescript@5.8.3)) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + create-jest@29.7.0(@types/node@25.2.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.8.3)): dependencies: '@jest/types': 29.6.3 @@ -9110,7 +9040,7 @@ snapshots: css-vendor@2.0.8: dependencies: - '@babel/runtime': 7.27.4 + '@babel/runtime': 7.28.6 is-in-browser: 1.1.3 csstype@3.1.3: {} @@ -9181,8 +9111,7 @@ snapshots: detect-indent@6.1.0: {} - detect-libc@2.0.4: - optional: true + detect-libc@2.1.2: {} detect-newline@3.1.0: {} @@ -9204,8 +9133,8 @@ snapshots: dom-helpers@5.2.1: dependencies: - '@babel/runtime': 7.27.4 - csstype: 3.1.3 + '@babel/runtime': 7.28.6 + csstype: 3.2.3 dompurify@3.2.6: optionalDependencies: @@ -9462,7 +9391,7 @@ snapshots: dependencies: debug: 3.2.7 is-core-module: 2.16.1 - resolve: 1.22.10 + resolve: 1.22.11 transitivePeerDependencies: - supports-color @@ -9474,8 +9403,8 @@ snapshots: get-tsconfig: 4.10.1 is-bun-module: 2.0.0 stable-hash: 0.0.5 - tinyglobby: 0.2.14 - unrs-resolver: 1.7.8 + tinyglobby: 0.2.15 + unrs-resolver: 1.11.1 optionalDependencies: eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.33.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)) transitivePeerDependencies: @@ -9665,8 +9594,8 @@ snapshots: espree@10.3.0: dependencies: - acorn: 8.14.1 - acorn-jsx: 5.3.2(acorn@8.14.1) + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) eslint-visitor-keys: 4.2.0 esprima@4.0.1: {} @@ -10245,7 +10174,7 @@ snapshots: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 25.2.3 + '@types/node': 22.14.1 chalk: 4.1.2 co: 4.6.0 dedent: 1.6.0(babel-plugin-macros@3.1.0) @@ -10271,7 +10200,7 @@ snapshots: '@jest/expect': 30.2.0 '@jest/test-result': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 25.2.3 + '@types/node': 22.14.1 chalk: 4.1.2 co: 4.6.0 dedent: 1.7.1(babel-plugin-macros@3.1.0) @@ -10291,6 +10220,25 @@ snapshots: - babel-plugin-macros - supports-color + jest-cli@29.7.0(@types/node@22.14.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.14.1)(typescript@5.8.3)): + dependencies: + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.14.1)(typescript@5.8.3)) + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@22.14.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.14.1)(typescript@5.8.3)) + exit: 0.1.2 + import-local: 3.2.0 + jest-config: 29.7.0(@types/node@22.14.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.14.1)(typescript@5.8.3)) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + jest-cli@29.7.0(@types/node@25.2.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.8.3)): dependencies: '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.8.3)) @@ -10329,6 +10277,68 @@ snapshots: - supports-color - ts-node + jest-config@29.7.0(@types/node@22.14.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.14.1)(typescript@5.8.3)): + dependencies: + '@babel/core': 7.29.0 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.29.0) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0(babel-plugin-macros@3.1.0) + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 22.14.1 + ts-node: 10.9.2(@types/node@22.14.1)(typescript@5.8.3) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-config@29.7.0(@types/node@22.14.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.8.3)): + dependencies: + '@babel/core': 7.29.0 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.29.0) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0(babel-plugin-macros@3.1.0) + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 22.14.1 + ts-node: 10.9.2(@types/node@25.2.3)(typescript@5.8.3) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + jest-config@29.7.0(@types/node@25.2.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.8.3)): dependencies: '@babel/core': 7.29.0 @@ -10360,6 +10370,39 @@ snapshots: - babel-plugin-macros - supports-color + jest-config@30.2.0(@types/node@22.14.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)): + dependencies: + '@babel/core': 7.29.0 + '@jest/get-type': 30.1.0 + '@jest/pattern': 30.0.1 + '@jest/test-sequencer': 30.2.0 + '@jest/types': 30.2.0 + babel-jest: 30.2.0(@babel/core@7.29.0) + chalk: 4.1.2 + ci-info: 4.4.0 + deepmerge: 4.3.1 + glob: 10.5.0 + graceful-fs: 4.2.11 + jest-circus: 30.2.0(babel-plugin-macros@3.1.0) + jest-docblock: 30.2.0 + jest-environment-node: 30.2.0 + jest-regex-util: 30.0.1 + jest-resolve: 30.2.0 + jest-runner: 30.2.0 + jest-util: 30.2.0 + jest-validate: 30.2.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 30.2.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 22.14.1 + ts-node: 10.9.2(@types/node@25.2.3)(typescript@5.9.3) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + jest-config@30.2.0(@types/node@25.2.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)): dependencies: '@babel/core': 7.29.0 @@ -10436,7 +10479,7 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 25.2.3 + '@types/node': 22.14.1 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -10445,7 +10488,7 @@ snapshots: '@jest/environment': 30.2.0 '@jest/fake-timers': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 25.2.3 + '@types/node': 22.14.1 jest-mock: 30.2.0 jest-util: 30.2.0 jest-validate: 30.2.0 @@ -10456,7 +10499,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 25.2.3 + '@types/node': 22.14.1 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -10471,7 +10514,7 @@ snapshots: jest-haste-map@30.2.0: dependencies: '@jest/types': 30.2.0 - '@types/node': 25.2.3 + '@types/node': 22.14.1 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -10534,13 +10577,13 @@ snapshots: jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 25.2.3 + '@types/node': 22.14.1 jest-util: 29.7.0 jest-mock@30.2.0: dependencies: '@jest/types': 30.2.0 - '@types/node': 25.2.3 + '@types/node': 22.14.1 jest-util: 30.2.0 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): @@ -10599,7 +10642,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 25.2.3 + '@types/node': 22.14.1 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -10625,7 +10668,7 @@ snapshots: '@jest/test-result': 30.2.0 '@jest/transform': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 25.2.3 + '@types/node': 22.14.1 chalk: 4.1.2 emittery: 0.13.1 exit-x: 0.2.2 @@ -10654,7 +10697,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 25.2.3 + '@types/node': 22.14.1 chalk: 4.1.2 cjs-module-lexer: 1.4.3 collect-v8-coverage: 1.0.2 @@ -10681,7 +10724,7 @@ snapshots: '@jest/test-result': 30.2.0 '@jest/transform': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 25.2.3 + '@types/node': 22.14.1 chalk: 4.1.2 cjs-module-lexer: 2.2.0 collect-v8-coverage: 1.0.3 @@ -10753,7 +10796,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 25.2.3 + '@types/node': 22.14.1 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -10762,7 +10805,7 @@ snapshots: jest-util@30.2.0: dependencies: '@jest/types': 30.2.0 - '@types/node': 25.2.3 + '@types/node': 22.14.1 chalk: 4.1.2 ci-info: 4.4.0 graceful-fs: 4.2.11 @@ -10790,7 +10833,7 @@ snapshots: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 25.2.3 + '@types/node': 22.14.1 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -10801,7 +10844,7 @@ snapshots: dependencies: '@jest/test-result': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 25.2.3 + '@types/node': 22.14.1 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -10810,19 +10853,31 @@ snapshots: jest-worker@29.7.0: dependencies: - '@types/node': 25.2.3 + '@types/node': 22.14.1 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 jest-worker@30.2.0: dependencies: - '@types/node': 25.2.3 + '@types/node': 22.14.1 '@ungap/structured-clone': 1.3.0 jest-util: 30.2.0 merge-stream: 2.0.0 supports-color: 8.1.1 + jest@29.7.0(@types/node@22.14.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.14.1)(typescript@5.8.3)): + dependencies: + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.14.1)(typescript@5.8.3)) + '@jest/types': 29.6.3 + import-local: 3.2.0 + jest-cli: 29.7.0(@types/node@22.14.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.14.1)(typescript@5.8.3)) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + jest@29.7.0(@types/node@25.2.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.8.3)): dependencies: '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.8.3)) @@ -10933,7 +10988,7 @@ snapshots: jss@10.10.0: dependencies: '@babel/runtime': 7.27.4 - csstype: 3.1.3 + csstype: 3.2.3 is-in-browser: 1.1.3 tiny-warning: 1.0.3 @@ -11067,8 +11122,6 @@ snapshots: nanoid@3.3.11: {} - napi-postinstall@0.2.4: {} - napi-postinstall@0.3.4: {} natural-compare@1.4.0: {} @@ -11124,7 +11177,7 @@ snapshots: '@swc/counter': 0.1.3 '@swc/helpers': 0.5.15 busboy: 1.6.0 - caniuse-lite: 1.0.30001720 + caniuse-lite: 1.0.30001769 postcss: 8.4.31 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -11149,7 +11202,7 @@ snapshots: '@swc/counter': 0.1.3 '@swc/helpers': 0.5.15 busboy: 1.6.0 - caniuse-lite: 1.0.30001720 + caniuse-lite: 1.0.30001769 postcss: 8.4.31 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) @@ -11174,7 +11227,7 @@ snapshots: '@swc/counter': 0.1.3 '@swc/helpers': 0.5.15 busboy: 1.6.0 - caniuse-lite: 1.0.30001720 + caniuse-lite: 1.0.30001769 postcss: 8.4.31 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) @@ -11199,7 +11252,7 @@ snapshots: '@swc/counter': 0.1.3 '@swc/helpers': 0.5.15 busboy: 1.6.0 - caniuse-lite: 1.0.30001720 + caniuse-lite: 1.0.30001769 postcss: 8.4.31 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) @@ -11481,6 +11534,23 @@ snapshots: queue-microtask@1.2.3: {} + ra-core@5.14.1(@tanstack/react-query@5.90.21(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.71.1(react@18.3.1))(react-router-dom@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): + dependencies: + '@tanstack/react-query': 5.90.21(react@18.3.1) + date-fns: 3.6.0 + eventemitter3: 5.0.4 + inflection: 3.0.2 + jsonexport: 3.2.0 + lodash: 4.17.23 + query-string: 7.1.3 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-error-boundary: 4.1.2(react@18.3.1) + react-hook-form: 7.71.1(react@18.3.1) + react-is: 19.2.4 + react-router: 7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-router-dom: 7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + ra-core@5.14.1(@tanstack/react-query@5.90.21(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react-hook-form@7.71.1(react@19.1.0))(react-router-dom@7.13.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-router@7.13.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0): dependencies: '@tanstack/react-query': 5.90.21(react@19.1.0) @@ -11498,9 +11568,9 @@ snapshots: react-router: 7.13.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react-router-dom: 7.13.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - ra-core@5.8.3(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.56.4(react@18.3.1))(react-router-dom@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): + ra-core@5.8.3(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.71.1(react@18.3.1))(react-router-dom@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): dependencies: - '@tanstack/react-query': 5.79.0(react@18.3.1) + '@tanstack/react-query': 5.90.21(react@18.3.1) clsx: 2.1.1 date-fns: 3.6.0 eventemitter3: 5.0.1 @@ -11511,7 +11581,7 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) react-error-boundary: 4.1.2(react@18.3.1) - react-hook-form: 7.56.4(react@18.3.1) + react-hook-form: 7.71.1(react@18.3.1) react-is: 19.1.0 react-router: 7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-router-dom: 7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -11528,11 +11598,12 @@ snapshots: - react-router - react-router-dom - ra-i18n-polyglot@5.8.3(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.56.4(react@18.3.1))(react-router-dom@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): + ra-i18n-polyglot@5.8.3(@tanstack/react-query@5.90.21(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.71.1(react@18.3.1))(react-router-dom@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): dependencies: node-polyglot: 2.6.0 - ra-core: 5.8.3(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.56.4(react@18.3.1))(react-router-dom@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + ra-core: 5.14.1(@tanstack/react-query@5.90.21(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.71.1(react@18.3.1))(react-router-dom@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) transitivePeerDependencies: + - '@tanstack/react-query' - react - react-dom - react-hook-form @@ -11550,20 +11621,21 @@ snapshots: - react-router - react-router-dom - ra-language-english@5.8.3(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.56.4(react@18.3.1))(react-router-dom@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): + ra-language-english@5.8.3(@tanstack/react-query@5.90.21(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.71.1(react@18.3.1))(react-router-dom@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): dependencies: - ra-core: 5.8.3(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.56.4(react@18.3.1))(react-router-dom@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + ra-core: 5.14.1(@tanstack/react-query@5.90.21(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.71.1(react@18.3.1))(react-router-dom@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) transitivePeerDependencies: + - '@tanstack/react-query' - react - react-dom - react-hook-form - react-router - react-router-dom - ra-ui-materialui@5.14.1(e48715e1df3f51681540fa7fbce63ad8): + ra-ui-materialui@5.14.1(e044a4ef4fe72f4c3535eee066d44fff): dependencies: - '@mui/icons-material': 7.3.8(@mui/material@7.3.8(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@types/react@19.1.1)(react@19.1.0) - '@mui/material': 7.3.8(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@mui/icons-material': 5.17.1(@mui/material@5.17.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@types/react@19.1.1)(react@19.1.0) + '@mui/material': 5.17.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@mui/system': 7.3.8(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0) '@mui/utils': 7.3.8(@types/react@19.1.1)(react@19.1.0) '@tanstack/react-query': 5.90.21(react@19.1.0) @@ -11589,13 +11661,13 @@ snapshots: react-router-dom: 7.13.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react-transition-group: 4.4.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - ra-ui-materialui@5.8.3(520842cc75124fee71df308e74da342e): + ra-ui-materialui@5.8.3(b1e91f9a525aa665dc88318da06dae04): dependencies: '@mui/icons-material': 5.17.1(@mui/material@5.17.1(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.12)(react@18.3.1) '@mui/material': 5.17.1(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mui/system': 7.3.8(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1) '@mui/utils': 7.3.8(@types/react@18.3.12)(react@18.3.1) - '@tanstack/react-query': 5.79.0(react@18.3.1) + '@tanstack/react-query': 5.90.21(react@18.3.1) autosuggest-highlight: 3.3.4 clsx: 2.1.1 css-mediaquery: 0.1.2 @@ -11605,12 +11677,12 @@ snapshots: jsonexport: 3.2.0 lodash: 4.17.21 query-string: 7.1.3 - ra-core: 5.8.3(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.56.4(react@18.3.1))(react-router-dom@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + ra-core: 5.8.3(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.71.1(react@18.3.1))(react-router-dom@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) react-dropzone: 14.3.8(react@18.3.1) react-error-boundary: 4.1.2(react@18.3.1) - react-hook-form: 7.56.4(react@18.3.1) + react-hook-form: 7.71.1(react@18.3.1) react-is: 19.2.4 react-router: 7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-router-dom: 7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -11620,20 +11692,19 @@ snapshots: dependencies: '@emotion/react': 11.14.0(@types/react@19.1.1)(react@19.1.0) '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0) - '@mui/icons-material': 7.3.8(@mui/material@7.3.8(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@types/react@19.1.1)(react@19.1.0) - '@mui/material': 7.3.8(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@mui/icons-material': 5.17.1(@mui/material@5.17.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@types/react@19.1.1)(react@19.1.0) + '@mui/material': 5.17.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react@19.1.0))(@types/react@19.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@tanstack/react-query': 5.90.21(react@19.1.0) ra-core: 5.14.1(@tanstack/react-query@5.90.21(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react-hook-form@7.71.1(react@19.1.0))(react-router-dom@7.13.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-router@7.13.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0) ra-i18n-polyglot: 5.14.1(@tanstack/react-query@5.90.21(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react-hook-form@7.71.1(react@19.1.0))(react-router-dom@7.13.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-router@7.13.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0) ra-language-english: 5.14.1(@tanstack/react-query@5.90.21(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react-hook-form@7.71.1(react@19.1.0))(react-router-dom@7.13.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-router@7.13.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0) - ra-ui-materialui: 5.14.1(e48715e1df3f51681540fa7fbce63ad8) + ra-ui-materialui: 5.14.1(e044a4ef4fe72f4c3535eee066d44fff) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) react-hook-form: 7.71.1(react@19.1.0) react-router: 7.13.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react-router-dom: 7.13.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) transitivePeerDependencies: - - '@mui/material-pigment-css' - '@mui/system' - '@mui/utils' - '@types/react' @@ -11641,24 +11712,25 @@ snapshots: - react-is - supports-color - react-admin@5.8.3(@mui/system@7.3.8(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@mui/utils@7.3.8(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(csstype@3.2.3)(react-dom@18.3.1(react@18.3.1))(react-is@19.2.4)(react@18.3.1): + react-admin@5.8.3(@mui/system@7.3.8(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@mui/utils@7.3.8(@types/react@18.3.12)(react@18.3.1))(@tanstack/react-query@5.90.21(react@18.3.1))(@types/react@18.3.12)(csstype@3.2.3)(react-dom@18.3.1(react@18.3.1))(react-is@19.2.4)(react@18.3.1): dependencies: '@emotion/react': 11.14.0(@types/react@18.3.12)(react@18.3.1) '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1) '@mui/icons-material': 5.17.1(@mui/material@5.17.1(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.12)(react@18.3.1) '@mui/material': 5.17.1(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - ra-core: 5.8.3(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.56.4(react@18.3.1))(react-router-dom@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) - ra-i18n-polyglot: 5.8.3(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.56.4(react@18.3.1))(react-router-dom@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) - ra-language-english: 5.8.3(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.56.4(react@18.3.1))(react-router-dom@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) - ra-ui-materialui: 5.8.3(520842cc75124fee71df308e74da342e) + ra-core: 5.8.3(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.71.1(react@18.3.1))(react-router-dom@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + ra-i18n-polyglot: 5.8.3(@tanstack/react-query@5.90.21(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.71.1(react@18.3.1))(react-router-dom@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + ra-language-english: 5.8.3(@tanstack/react-query@5.90.21(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.71.1(react@18.3.1))(react-router-dom@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + ra-ui-materialui: 5.8.3(b1e91f9a525aa665dc88318da06dae04) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-hook-form: 7.56.4(react@18.3.1) + react-hook-form: 7.71.1(react@18.3.1) react-router: 7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-router-dom: 7.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) transitivePeerDependencies: - '@mui/system' - '@mui/utils' + - '@tanstack/react-query' - '@types/react' - csstype - react-is @@ -11699,7 +11771,7 @@ snapshots: '@babel/runtime': 7.28.6 react: 19.1.0 - react-hook-form@7.56.4(react@18.3.1): + react-hook-form@7.71.1(react@18.3.1): dependencies: react: 18.3.1 @@ -11754,7 +11826,7 @@ snapshots: react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.27.4 + '@babel/runtime': 7.28.6 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 @@ -11763,7 +11835,7 @@ snapshots: react-transition-group@4.4.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: - '@babel/runtime': 7.27.4 + '@babel/runtime': 7.28.6 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 @@ -11983,7 +12055,7 @@ snapshots: sharp@0.34.2: dependencies: color: 4.2.3 - detect-libc: 2.0.4 + detect-libc: 2.1.2 semver: 7.7.4 optionalDependencies: '@img/sharp-darwin-arm64': 0.34.2 @@ -12298,6 +12370,25 @@ snapshots: ts-interface-checker@0.1.13: {} + ts-node@10.9.2(@types/node@22.14.1)(typescript@5.8.3): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.12 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 22.14.1 + acorn: 8.15.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.4 + make-error: 1.3.6 + typescript: 5.8.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + optional: true + ts-node@10.9.2(@types/node@25.2.3)(typescript@5.8.3): dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -12522,28 +12613,6 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 - unrs-resolver@1.7.8: - dependencies: - napi-postinstall: 0.2.4 - optionalDependencies: - '@unrs/resolver-binding-darwin-arm64': 1.7.8 - '@unrs/resolver-binding-darwin-x64': 1.7.8 - '@unrs/resolver-binding-freebsd-x64': 1.7.8 - '@unrs/resolver-binding-linux-arm-gnueabihf': 1.7.8 - '@unrs/resolver-binding-linux-arm-musleabihf': 1.7.8 - '@unrs/resolver-binding-linux-arm64-gnu': 1.7.8 - '@unrs/resolver-binding-linux-arm64-musl': 1.7.8 - '@unrs/resolver-binding-linux-ppc64-gnu': 1.7.8 - '@unrs/resolver-binding-linux-riscv64-gnu': 1.7.8 - '@unrs/resolver-binding-linux-riscv64-musl': 1.7.8 - '@unrs/resolver-binding-linux-s390x-gnu': 1.7.8 - '@unrs/resolver-binding-linux-x64-gnu': 1.7.8 - '@unrs/resolver-binding-linux-x64-musl': 1.7.8 - '@unrs/resolver-binding-wasm32-wasi': 1.7.8 - '@unrs/resolver-binding-win32-arm64-msvc': 1.7.8 - '@unrs/resolver-binding-win32-ia32-msvc': 1.7.8 - '@unrs/resolver-binding-win32-x64-msvc': 1.7.8 - update-browserslist-db@1.1.3(browserslist@4.25.2): dependencies: browserslist: 4.25.2