diff --git a/.gitignore b/.gitignore index 6089279..1f34152 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ coverage docs .turbo + +# AppKit type generation cache +.appkit-types-cache.json diff --git a/apps/dev-playground/client/package.json b/apps/dev-playground/client/package.json index affa042..332aed7 100644 --- a/apps/dev-playground/client/package.json +++ b/apps/dev-playground/client/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "dev": "vite", - "build": "tsc -b && vite build", + "build": "vite build", "lint": "eslint .", "preview": "vite preview" }, diff --git a/apps/dev-playground/client/src/appKitTypes.d.ts b/apps/dev-playground/client/src/appKitTypes.d.ts index 07e759c..304377c 100644 --- a/apps/dev-playground/client/src/appKitTypes.d.ts +++ b/apps/dev-playground/client/src/appKitTypes.d.ts @@ -1,75 +1,160 @@ // Auto-generated by AppKit - DO NOT EDIT +// Generated by 'npx appkit-generate-types' or Vite plugin during build import "@databricks/app-kit-ui/react"; +import type { SQLTypeMarker, SQLStringMarker, SQLNumberMarker, SQLBooleanMarker, SQLBinaryMarker, SQLDateMarker, SQLTimestampMarker } from "@databricks/app-kit-ui/js"; declare module "@databricks/app-kit-ui/react" { - interface PluginRegistry { - "reconnect": { - "/": { - message: string; + interface QueryRegistry { + apps_list: { + name: "apps_list"; + parameters: Record; + result: Array<{ + /** @sqlType STRING */ + id: string; + /** @sqlType STRING */ + name: string; + /** @sqlType STRING */ + creator: string; + /** @sqlType STRING */ + tags: string; + /** @sqlType DECIMAL(38,6) */ + totalSpend: number; + /** @sqlType DATE */ + createdAt: string; + }>; }; - "/stream": { - type: string; - count: number; - total: number; - timestamp: string; - content: string; + cost_recommendations: { + name: "cost_recommendations"; + parameters: Record; + result: Array<{ + /** @sqlType INT */ + dummy: number; + }>; }; - } - "analytics": { - "/users/me/query/:query_key": { - chunk_index: number; - row_offset: number; - row_count: number; - data: any[]; + example: { + name: "example"; + parameters: Record; + result: Array<{ + /** @sqlType BOOLEAN */ + "(1 = 1)": boolean; + }>; }; - "/query/:query_key": { - chunk_index: number; - row_offset: number; - row_count: number; - data: any[]; + spend_data: { + name: "spend_data"; + parameters: { + /** STRING - use sql.string() */ + groupBy: SQLStringMarker; + /** STRING - use sql.string() */ + aggregationLevel: SQLStringMarker; + /** DATE - use sql.date() */ + startDate: SQLDateMarker; + /** DATE - use sql.date() */ + endDate: SQLDateMarker; + /** STRING - use sql.string() */ + appId: SQLStringMarker; + /** STRING - use sql.string() */ + creator: SQLStringMarker; + }; + result: Array<{ + /** @sqlType STRING */ + group_key: string; + /** @sqlType TIMESTAMP */ + aggregation_period: string; + /** @sqlType DECIMAL(38,6) */ + cost_usd: number; + }>; }; - } - } - - interface QueryRegistry { - - apps_list: { - id: string; - name: string; - creator: string; - tags: string[]; - totalSpend: number; - createdAt: string; - }[]; spend_summary: { - total: number; - average: number; - forecasted: number; - }[]; - untagged_apps: { - app_name: string; - creator: string; - total_cost_usd: number; - avg_period_cost_usd: number; - }[]; - spend_data: { - group_key: string; - aggregation_period: string; - cost_usd: number; - }[]; - top_contributors: { - app_name: string; - total_cost_usd: number; - }[]; + name: "spend_summary"; + parameters: { + /** STRING - use sql.string() */ + aggregationLevel: SQLStringMarker; + /** DATE - use sql.date() */ + endDate: SQLDateMarker; + /** DATE - use sql.date() */ + startDate: SQLDateMarker; + }; + result: Array<{ + /** @sqlType DECIMAL(33,0) */ + total: number; + /** @sqlType DECIMAL(33,0) */ + average: number; + /** @sqlType DECIMAL(33,0) */ + forecasted: number; + }>; + }; sql_helpers_test: { - string_value: string; - number_value: number; - boolean_value: boolean; - date_value: string; - timestamp_value: string; - binary_value: string; - binary_hex: string; - binary_length: number; - }; + name: "sql_helpers_test"; + parameters: { + /** STRING - use sql.string() */ + stringParam: SQLStringMarker; + /** NUMERIC - use sql.number() */ + numberParam: SQLNumberMarker; + /** BOOLEAN - use sql.boolean() */ + booleanParam: SQLBooleanMarker; + /** DATE - use sql.date() */ + dateParam: SQLDateMarker; + /** TIMESTAMP - use sql.timestamp() */ + timestampParam: SQLTimestampMarker; + /** STRING - use sql.string() */ + binaryParam: SQLStringMarker; + }; + result: Array<{ + /** @sqlType STRING */ + string_value: string; + /** @sqlType STRING */ + number_value: string; + /** @sqlType STRING */ + boolean_value: string; + /** @sqlType STRING */ + date_value: string; + /** @sqlType STRING */ + timestamp_value: string; + /** @sqlType BINARY */ + binary_value: string; + /** @sqlType STRING */ + binary_hex: string; + /** @sqlType INT */ + binary_length: number; + }>; + }; + top_contributors: { + name: "top_contributors"; + parameters: { + /** STRING - use sql.string() */ + aggregationLevel: SQLStringMarker; + /** DATE - use sql.date() */ + startDate: SQLDateMarker; + /** DATE - use sql.date() */ + endDate: SQLDateMarker; + }; + result: Array<{ + /** @sqlType STRING */ + app_name: string; + /** @sqlType DECIMAL(38,6) */ + total_cost_usd: number; + }>; + }; + untagged_apps: { + name: "untagged_apps"; + parameters: { + /** STRING - use sql.string() */ + aggregationLevel: SQLStringMarker; + /** DATE - use sql.date() */ + startDate: SQLDateMarker; + /** DATE - use sql.date() */ + endDate: SQLDateMarker; + }; + result: Array<{ + /** @sqlType STRING */ + app_name: string; + /** @sqlType STRING */ + creator: string; + /** @sqlType DECIMAL(38,6) */ + total_cost_usd: number; + /** @sqlType DECIMAL(38,10) */ + avg_period_cost_usd: number; + }>; + }; } } diff --git a/apps/dev-playground/client/src/routeTree.gen.ts b/apps/dev-playground/client/src/routeTree.gen.ts index 884804f..4824458 100644 --- a/apps/dev-playground/client/src/routeTree.gen.ts +++ b/apps/dev-playground/client/src/routeTree.gen.ts @@ -9,6 +9,7 @@ // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. import { Route as rootRouteImport } from './routes/__root' +import { Route as TypeSafetyRouteRouteImport } from './routes/type-safety.route' import { Route as TelemetryRouteRouteImport } from './routes/telemetry.route' import { Route as SqlHelpersRouteRouteImport } from './routes/sql-helpers.route' import { Route as ReconnectRouteRouteImport } from './routes/reconnect.route' @@ -16,6 +17,11 @@ import { Route as DataVisualizationRouteRouteImport } from './routes/data-visual import { Route as AnalyticsRouteRouteImport } from './routes/analytics.route' import { Route as IndexRouteImport } from './routes/index' +const TypeSafetyRouteRoute = TypeSafetyRouteRouteImport.update({ + id: '/type-safety', + path: '/type-safety', + getParentRoute: () => rootRouteImport, +} as any) const TelemetryRouteRoute = TelemetryRouteRouteImport.update({ id: '/telemetry', path: '/telemetry', @@ -54,6 +60,7 @@ export interface FileRoutesByFullPath { '/reconnect': typeof ReconnectRouteRoute '/sql-helpers': typeof SqlHelpersRouteRoute '/telemetry': typeof TelemetryRouteRoute + '/type-safety': typeof TypeSafetyRouteRoute } export interface FileRoutesByTo { '/': typeof IndexRoute @@ -62,6 +69,7 @@ export interface FileRoutesByTo { '/reconnect': typeof ReconnectRouteRoute '/sql-helpers': typeof SqlHelpersRouteRoute '/telemetry': typeof TelemetryRouteRoute + '/type-safety': typeof TypeSafetyRouteRoute } export interface FileRoutesById { __root__: typeof rootRouteImport @@ -71,6 +79,7 @@ export interface FileRoutesById { '/reconnect': typeof ReconnectRouteRoute '/sql-helpers': typeof SqlHelpersRouteRoute '/telemetry': typeof TelemetryRouteRoute + '/type-safety': typeof TypeSafetyRouteRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath @@ -81,6 +90,7 @@ export interface FileRouteTypes { | '/reconnect' | '/sql-helpers' | '/telemetry' + | '/type-safety' fileRoutesByTo: FileRoutesByTo to: | '/' @@ -89,6 +99,7 @@ export interface FileRouteTypes { | '/reconnect' | '/sql-helpers' | '/telemetry' + | '/type-safety' id: | '__root__' | '/' @@ -97,6 +108,7 @@ export interface FileRouteTypes { | '/reconnect' | '/sql-helpers' | '/telemetry' + | '/type-safety' fileRoutesById: FileRoutesById } export interface RootRouteChildren { @@ -106,10 +118,18 @@ export interface RootRouteChildren { ReconnectRouteRoute: typeof ReconnectRouteRoute SqlHelpersRouteRoute: typeof SqlHelpersRouteRoute TelemetryRouteRoute: typeof TelemetryRouteRoute + TypeSafetyRouteRoute: typeof TypeSafetyRouteRoute } declare module '@tanstack/react-router' { interface FileRoutesByPath { + '/type-safety': { + id: '/type-safety' + path: '/type-safety' + fullPath: '/type-safety' + preLoaderRoute: typeof TypeSafetyRouteRouteImport + parentRoute: typeof rootRouteImport + } '/telemetry': { id: '/telemetry' path: '/telemetry' @@ -162,6 +182,7 @@ const rootRouteChildren: RootRouteChildren = { ReconnectRouteRoute: ReconnectRouteRoute, SqlHelpersRouteRoute: SqlHelpersRouteRoute, TelemetryRouteRoute: TelemetryRouteRoute, + TypeSafetyRouteRoute: TypeSafetyRouteRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) diff --git a/apps/dev-playground/client/src/routes/__root.tsx b/apps/dev-playground/client/src/routes/__root.tsx index 376d8f0..31e7c33 100644 --- a/apps/dev-playground/client/src/routes/__root.tsx +++ b/apps/dev-playground/client/src/routes/__root.tsx @@ -63,6 +63,14 @@ function RootComponent() { SQL Helpers + + + diff --git a/apps/dev-playground/client/src/routes/index.tsx b/apps/dev-playground/client/src/routes/index.tsx index 203fe1a..0ae35bf 100644 --- a/apps/dev-playground/client/src/routes/index.tsx +++ b/apps/dev-playground/client/src/routes/index.tsx @@ -121,6 +121,24 @@ function IndexRoute() { + + +
+

+ Type-Safe SQL +

+

+ Generate TypeScript types from SQL files at build time. Full + IntelliSense for query names, parameters, and results. +

+ +
+
diff --git a/apps/dev-playground/client/src/routes/type-safety.route.tsx b/apps/dev-playground/client/src/routes/type-safety.route.tsx new file mode 100644 index 0000000..1205a1c --- /dev/null +++ b/apps/dev-playground/client/src/routes/type-safety.route.tsx @@ -0,0 +1,595 @@ +import { createFileRoute, retainSearchParams } from "@tanstack/react-router"; +import { useEffect, useState } from "react"; +import { codeToHtml } from "shiki"; +import { Header } from "@/components/layout/header"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; + +export const Route = createFileRoute("/type-safety")({ + component: TypeSafetyRoute, + search: { + middlewares: [retainSearchParams(true)], + }, +}); + +function CodeBlock({ + code, + lang = "typescript", +}: { + code: string; + lang?: string; +}) { + const [html, setHtml] = useState(""); + + useEffect(() => { + codeToHtml(code, { + lang, + theme: "dark-plus", + }).then(setHtml); + }, [code, lang]); + + if (!html) { + return ( +
+        {code}
+      
+ ); + } + + return ( +
+ ); +} + +const SQL_FILE_EXAMPLE = `-- @param groupBy STRING +-- @param aggregationLevel STRING +-- @param startDate DATE +-- @param endDate DATE + +SELECT + COALESCE(:groupBy, 'default') as group_key, + date_trunc(:aggregationLevel, usage_date) AS period, + SUM(usage_quantity * pricing) AS cost_usd +FROM system.billing.usage +WHERE usage_date BETWEEN :startDate AND :endDate +GROUP BY period +ORDER BY period DESC`; + +const GENERATED_TYPES = `// Auto-generated by AppKit - DO NOT EDIT +import "@databricks/app-kit-ui/react"; +import type { SQLStringMarker, SQLDateMarker } from "@databricks/app-kit-ui/js"; + +declare module "@databricks/app-kit-ui/react" { + interface QueryRegistry { + spend_data: { + name: "spend_data"; + parameters: { + /** STRING - use sql.string() */ + groupBy: SQLStringMarker; + /** DATE - use sql.date() */ + startDate: SQLDateMarker; + /** DATE - use sql.date() */ + endDate: SQLDateMarker; + }; + result: Array<{ + /** @sqlType STRING */ + group_key: string; + /** @sqlType TIMESTAMP */ + period: string; + /** @sqlType DECIMAL */ + cost_usd: number; + }>; + }; + } +}`; + +const USAGE_EXAMPLE = `import { sql } from "@databricks/app-kit-ui/js"; +import { useAnalyticsQuery } from "@databricks/app-kit-ui/react"; + +function SpendChart() { + const { data, loading, error } = useAnalyticsQuery("spend_data", { + groupBy: sql.string("app_name"), + startDate: sql.date("2024-01-01"), + endDate: sql.date("2024-12-31"), + }); + + // data is typed as Array<{ + // group_key: string; + // period: string; + // cost_usd: number; + // }> + + return data?.map(row => ( +
+ {row.group_key}: \${row.cost_usd.toFixed(2)} +
+ )); +}`; + +function FlowStep({ + number, + title, + active, +}: { + number: number; + title: string; + active: boolean; +}) { + return ( +
+
+
+ {number} +
+ + {title} + +
+
+ ); +} + +function FlowDiagram() { + const [activeStep, setActiveStep] = useState(0); + + useEffect(() => { + const interval = setInterval(() => { + setActiveStep((prev) => (prev + 1) % 3); + }, 2000); + return () => clearInterval(interval); + }, []); + + const steps = ["SQL File", "Generated Types", "IntelliSense"]; + + return ( +
+ {steps.map((step, index) => ( +
+ + {index < steps.length - 1 && ( +
+ )} +
+ ))} +
+ ); +} + +function IntelliSenseDemo() { + const [step, setStep] = useState(0); + const [showAutocomplete, setShowAutocomplete] = useState(false); + const [showParamHint, setShowParamHint] = useState(false); + const [showResultHint, setShowResultHint] = useState(false); + const [isPaused, setIsPaused] = useState(false); + const [timeoutIds, setTimeoutIds] = useState[]>( + [], + ); + + const queryNames = [ + "apps_list", + "cost_recommendations", + "spend_data", + "spend_summary", + ]; + + const clearAllTimeouts = () => { + for (const id of timeoutIds) { + clearTimeout(id); + } + setTimeoutIds([]); + }; + + const scheduleFromStep = (fromStep: number) => { + const ids: ReturnType[] = []; + let delay = 0; + + // Step 1: Show autocomplete (500ms) + if (fromStep < 1) { + delay += 500; + ids.push( + setTimeout(() => { + setStep(1); + setShowAutocomplete(true); + }, delay), + ); + delay += 2000; // Wait 2s before next + } + + // Step 2: Hide autocomplete, select query (after autocomplete shown) + if (fromStep < 2) { + if (fromStep >= 1) delay += 2000; // If resuming from step 1 + ids.push( + setTimeout(() => { + setStep(2); + setShowAutocomplete(false); + }, delay), + ); + delay += 500; // Wait 500ms before showing params + } + + // Show param hints + if (fromStep < 2 || (fromStep === 2 && !showParamHint)) { + if (fromStep === 2) delay += 500; // If resuming from step 2 + ids.push( + setTimeout(() => { + setShowParamHint(true); + }, delay), + ); + delay += 2000; // Wait 2s before next + } + + // Step 3: Hide param hints, fill in params + if (fromStep < 3) { + if (fromStep === 2 && showParamHint) delay += 2000; // If resuming with params shown + ids.push( + setTimeout(() => { + setStep(3); + setShowParamHint(false); + }, delay), + ); + delay += 500; // Wait 500ms before showing results + } + + // Show result hints + if (fromStep < 3 || (fromStep === 3 && !showResultHint)) { + if (fromStep === 3) delay += 500; // If resuming from step 3 + ids.push( + setTimeout(() => { + setShowResultHint(true); + }, delay), + ); + delay += 2000; // Wait 2s before complete + } + + // Step 4: Complete + if (fromStep < 4) { + if (fromStep === 3 && showResultHint) delay += 2000; // If resuming with results shown + ids.push( + setTimeout(() => { + setStep(4); + }, delay), + ); + } + + setTimeoutIds(ids); + }; + + const togglePause = () => { + if (isPaused) { + // Resume from current step + setIsPaused(false); + scheduleFromStep(step); + } else { + // Pause + clearAllTimeouts(); + setIsPaused(true); + } + }; + + const runDemo = () => { + clearAllTimeouts(); + setIsPaused(false); + setStep(0); + setShowAutocomplete(false); + setShowParamHint(false); + setShowResultHint(false); + + scheduleFromStep(0); + }; + + const codeLines = [ + "const { data } = useAnalyticsQuery(", + step >= 2 ? ' "spend_data",' : ' "|"', + " {", + step >= 3 + ? ' startDate: sql.date("2024-01-01"),' + : step >= 2 + ? " |" + : "", + step >= 3 ? ' endDate: sql.date("2024-12-31"),' : "", + " }", + ");", + "", + step >= 3 ? "data?.map(row => row.|)" : "", + ].filter(Boolean); + + return ( +
+
+
+          {codeLines.map((line) => (
+            
+ {line.includes("|") ? ( + <> + {line.split("|")[0]} + + | + + {line.split("|")[1]} + + ) : ( + + {line} + + )} +
+ ))} +
+ + {showAutocomplete && ( +
+
+ QueryRegistry +
+ {queryNames.map((name, i) => ( +
+ {name} +
+ ))} +
+ )} + + {showParamHint && ( +
+
+ Parameters (spend_data) +
+
+
groupBy: SQLStringMarker
+
startDate: SQLDateMarker
+
endDate: SQLDateMarker
+
+
+ )} + + {showResultHint && ( +
+
+ Result Fields +
+ {["group_key: string", "period: string", "cost_usd: number"].map( + (field, i) => ( +
+ {field} +
+ ), + )} +
+ )} +
+ +
+ + {step > 0 && step < 4 && ( + + )} + + {isPaused && "(Paused) "} + {step === 0 && "Click to start"} + {step === 1 && "Query name autocomplete"} + {step === 2 && "Parameter hints"} + {step === 3 && "Result field suggestions"} + {step === 4 && "Complete!"} + +
+
+ ); +} + +function TypeSafetyRoute() { + return ( +
+
+
+ +
+ {/* Flow Diagram */} + + + How It Works + + SQL files with @param annotations are processed to generate + TypeScript types + + + + + + + + {/* SQL File */} + + + 1. SQL File + + Write SQL queries with @param annotations to define parameter + types + + + + + + + + {/* Generated Types */} + + + 2. Generated Types + + Vite plugin or npx command generates appKitTypes.d.ts at build + time + + + + + + + + {/* IntelliSense Demo */} + + + 3. IntelliSense + + Your IDE provides full autocomplete for query names, parameters, + and results + + + + + + + + {/* Usage Example */} + + + Usage Example + + Type-safe queries in your React components + + + + + + + + {/* Vite Plugin */} + + + Vite Plugin + + Automatic type generation during development + + + + + + + + {/* NPX Command */} + + + NPX Command + Manual or CI/CD type generation + + + + + + + {/* Supported Types */} + + + Supported Parameter Types + + Use @param annotations in SQL files to define typed parameters + + + +
+ {[ + { annotation: "@param name STRING", helper: "sql.string()" }, + { + annotation: "@param count NUMERIC", + helper: "sql.number()", + }, + { + annotation: "@param active BOOLEAN", + helper: "sql.boolean()", + }, + { annotation: "@param start DATE", helper: "sql.date()" }, + { + annotation: "@param created TIMESTAMP", + helper: "sql.timestamp()", + }, + { annotation: "@param data BINARY", helper: "sql.binary()" }, + ].map((type) => ( +
+ + -- {type.annotation} + + + {type.helper} + +
+ ))} +
+
+
+
+
+
+ ); +} diff --git a/apps/dev-playground/client/vite.config.ts b/apps/dev-playground/client/vite.config.ts index 0a555a5..1e76d7e 100644 --- a/apps/dev-playground/client/vite.config.ts +++ b/apps/dev-playground/client/vite.config.ts @@ -1,4 +1,5 @@ import path from "node:path"; +import { appKitTypesPlugin } from "@databricks/app-kit"; import { tanstackRouter } from "@tanstack/router-plugin/vite"; import react from "@vitejs/plugin-react"; import { defineConfig } from "vite"; @@ -7,6 +8,7 @@ import { defineConfig } from "vite"; export default defineConfig({ plugins: [ react(), + appKitTypesPlugin(), tanstackRouter({ target: "react", autoCodeSplitting: process.env.NODE_ENV !== "development", diff --git a/apps/dev-playground/config/queries/chat_messages.sql b/apps/dev-playground/config/queries/chat_messages.sql deleted file mode 100644 index 7e9e50a..0000000 --- a/apps/dev-playground/config/queries/chat_messages.sql +++ /dev/null @@ -1,8 +0,0 @@ -SELECT - id, - message, - response, - timestamp -FROM your_catalog.your_schema.chat_messages -ORDER BY timestamp ASC - diff --git a/apps/dev-playground/config/queries/schema.ts b/apps/dev-playground/config/queries/schema.ts deleted file mode 100644 index 4ac9621..0000000 --- a/apps/dev-playground/config/queries/schema.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { z } from "zod"; - -export const querySchemas = { - apps_list: z.array( - z.object({ - id: z.string(), - name: z.string(), - creator: z.string(), - tags: z.array(z.string()), - totalSpend: z.number(), - createdAt: z.string(), - }), - ), - - spend_summary: z.array( - z.object({ - total: z.number(), - average: z.number(), - forecasted: z.number(), - }), - ), - - untagged_apps: z.array( - z.object({ - app_name: z.string(), - creator: z.string(), - total_cost_usd: z.number(), - avg_period_cost_usd: z.number(), - }), - ), - - spend_data: z.array( - z.object({ - group_key: z.string(), - aggregation_period: z.string(), - cost_usd: z.number(), - }), - ), - - top_contributors: z.array( - z.object({ - app_name: z.string(), - total_cost_usd: z.number(), - }), - ), - sql_helpers_test: z.object({ - string_value: z.string(), - number_value: z.number(), - boolean_value: z.boolean(), - date_value: z.string(), - timestamp_value: z.string(), - binary_value: z.string(), - binary_hex: z.string(), - binary_length: z.number(), - }), -}; diff --git a/apps/dev-playground/config/queries/spend_data.sql b/apps/dev-playground/config/queries/spend_data.sql index c557930..9d28f9a 100644 --- a/apps/dev-playground/config/queries/spend_data.sql +++ b/apps/dev-playground/config/queries/spend_data.sql @@ -1,3 +1,9 @@ +-- @param groupBy STRING +-- @param aggregationLevel STRING +-- @param startDate DATE +-- @param endDate DATE +-- @param appId STRING +-- @param creator STRING SELECT COALESCE(:groupBy, 'default') as group_key, date_trunc(COALESCE(:aggregationLevel, 'day'), u.usage_date) AS aggregation_period, diff --git a/apps/dev-playground/config/queries/spend_summary.sql b/apps/dev-playground/config/queries/spend_summary.sql index 2bcc8d3..d0920ac 100644 --- a/apps/dev-playground/config/queries/spend_summary.sql +++ b/apps/dev-playground/config/queries/spend_summary.sql @@ -1,3 +1,6 @@ +-- @param aggregationLevel STRING +-- @param startDate DATE +-- @param endDate DATE SELECT ROUND(SUM(u.usage_quantity * lp.pricing.effective_list.default)) AS total, ROUND(SUM(u.usage_quantity * lp.pricing.effective_list.default) / diff --git a/apps/dev-playground/config/queries/sql_helpers_test.sql b/apps/dev-playground/config/queries/sql_helpers_test.sql index 2f0b756..0e9f2ff 100644 --- a/apps/dev-playground/config/queries/sql_helpers_test.sql +++ b/apps/dev-playground/config/queries/sql_helpers_test.sql @@ -1,3 +1,9 @@ +-- @param stringParam STRING +-- @param numberParam NUMERIC +-- @param booleanParam BOOLEAN +-- @param dateParam DATE +-- @param timestampParam TIMESTAMP +-- @param binaryParam STRING SELECT :stringParam as string_value, :numberParam as number_value, diff --git a/apps/dev-playground/config/queries/top_contributors.sql b/apps/dev-playground/config/queries/top_contributors.sql index 2f299cf..b080e8a 100644 --- a/apps/dev-playground/config/queries/top_contributors.sql +++ b/apps/dev-playground/config/queries/top_contributors.sql @@ -1,5 +1,7 @@ -- Top contributors by app with aggregation support --- Parameters: workspaceId, startDate, endDate, aggregationLevel (daily, weekly, monthly) +-- @param aggregationLevel STRING +-- @param startDate DATE +-- @param endDate DATE WITH aggregated_costs AS ( SELECT u.usage_metadata.app_name AS app_name, diff --git a/apps/dev-playground/config/queries/untagged_apps.sql b/apps/dev-playground/config/queries/untagged_apps.sql index 9e0b941..bdf3f08 100644 --- a/apps/dev-playground/config/queries/untagged_apps.sql +++ b/apps/dev-playground/config/queries/untagged_apps.sql @@ -1,3 +1,6 @@ +-- @param aggregationLevel STRING +-- @param startDate DATE +-- @param endDate DATE WITH app_periods AS ( SELECT u.usage_metadata.app_name AS app_name, diff --git a/apps/dev-playground/server/index.ts b/apps/dev-playground/server/index.ts index d8b236c..439b061 100644 --- a/apps/dev-playground/server/index.ts +++ b/apps/dev-playground/server/index.ts @@ -3,5 +3,5 @@ import { reconnect } from "./reconnect-plugin"; import { telemetryExamples } from "./telemetry-example-plugin"; createApp({ - plugins: [server(), reconnect(), telemetryExamples(), analytics()], + plugins: [server(), reconnect(), telemetryExamples(), analytics({})], }); diff --git a/apps/dev-playground/server/reconnect-plugin.ts b/apps/dev-playground/server/reconnect-plugin.ts index f9bf0a2..290d747 100644 --- a/apps/dev-playground/server/reconnect-plugin.ts +++ b/apps/dev-playground/server/reconnect-plugin.ts @@ -1,31 +1,34 @@ import { Plugin, toPlugin } from "@databricks/app-kit"; import type { IAppRouter, StreamExecutionSettings } from "shared"; -import { z } from "zod"; + +interface ReconnectResponse { + message: string; +} + +interface ReconnectStreamResponse { + type: number; + count: number; + total: number; + timestamp: string; + content: string; +} export class ReconnectPlugin extends Plugin { public name = "reconnect"; public envVars = []; injectRoutes(router: IAppRouter): void { - this.route(router, { + this.route(router, { method: "get", path: "/", - schema: z.object({ message: z.string() }), handler: async (_req, res) => { res.json({ message: "Reconnected" }); }, }); - this.route(router, { + this.route(router, { method: "get", path: "/stream", - schema: z.object({ - type: z.string(), - count: z.number(), - total: z.number(), - timestamp: z.string(), - content: z.string(), - }), handler: async (req, res) => { const sessionId = (req.query.sessionId as string) || `session-${Date.now()}`; diff --git a/packages/app-kit-ui/src/js/index.ts b/packages/app-kit-ui/src/js/index.ts index 3e9ef58..0c8a602 100644 --- a/packages/app-kit-ui/src/js/index.ts +++ b/packages/app-kit-ui/src/js/index.ts @@ -1,5 +1,12 @@ export { isSQLTypeMarker, + type SQLBinaryMarker, + type SQLBooleanMarker, + type SQLDateMarker, + type SQLNumberMarker, + type SQLStringMarker, + type SQLTimestampMarker, + type SQLTypeMarker, sql, } from "shared"; export * from "./sse"; diff --git a/packages/app-kit-ui/src/react/hooks/index.ts b/packages/app-kit-ui/src/react/hooks/index.ts index 5d4d09e..bf07741 100644 --- a/packages/app-kit-ui/src/react/hooks/index.ts +++ b/packages/app-kit-ui/src/react/hooks/index.ts @@ -6,4 +6,3 @@ export type { } from "./types"; export { useAnalyticsQuery } from "./use-analytics-query"; export { useChartData } from "./use-chart-data"; -export { useCustomPlugin } from "./use-custom-plugin"; diff --git a/packages/app-kit-ui/src/react/hooks/types.ts b/packages/app-kit-ui/src/react/hooks/types.ts index 64eec6c..d2e54eb 100644 --- a/packages/app-kit-ui/src/react/hooks/types.ts +++ b/packages/app-kit-ui/src/react/hooks/types.ts @@ -29,14 +29,21 @@ export interface UseAnalyticsQueryResult { * // config/appKitTypes.d.ts * declare module "@databricks/app-kit-ui/react" { * interface QueryRegistry { - * apps_list: AppListItem[]; - * spend_summary: SpendSummary[]; + * apps_list: { + * name: "apps_list"; + * parameters: { startDate: string; endDate: string; aggregationLevel: string }; + * result: Array<{ id: string; name: string }>; + * }; * } * } * ``` */ export interface QueryRegistry { - [key: string]: any[]; + [key: string]: { + name: string; + parameters: Record; + result: unknown[]; + }; } /** Gets only literal keys from a registry (excludes index signature) */ @@ -50,30 +57,23 @@ export type QueryKey = AugmentedRegistry extends never : AugmentedRegistry; /** - * Infers result type: uses QueryRegistry if key exists, otherwise falls back to explicit type T + * Infers result type from QueryRegistry[K]["result"] */ export type InferResult = K extends AugmentedRegistry - ? QueryRegistry[K] + ? QueryRegistry[K] extends { result: infer R } + ? R + : T : T; +/** + * Infers parameters type from QueryRegistry[K]["parameters"] + */ +export type InferParams = K extends AugmentedRegistry + ? QueryRegistry[K] extends { parameters: infer P } + ? P + : Record + : Record; + export interface PluginRegistry { [key: string]: Record; } - -export type PluginName = AugmentedRegistry extends never - ? string - : AugmentedRegistry; - -export type PluginRoutes

= - P extends AugmentedRegistry - ? AugmentedRegistry - : string; - -export type RouteResponse< - P extends PluginName, - R extends PluginRoutes

, -> = P extends keyof PluginRegistry - ? R extends keyof PluginRegistry[P] - ? PluginRegistry[P][R] - : unknown - : unknown; diff --git a/packages/app-kit-ui/src/react/hooks/use-analytics-query.ts b/packages/app-kit-ui/src/react/hooks/use-analytics-query.ts index c3cc7bf..4a42474 100644 --- a/packages/app-kit-ui/src/react/hooks/use-analytics-query.ts +++ b/packages/app-kit-ui/src/react/hooks/use-analytics-query.ts @@ -1,6 +1,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { connectSSE } from "@/js"; import type { + InferParams, InferResult, QueryKey, UseAnalyticsQueryOptions, @@ -20,17 +21,13 @@ function getDevMode() { * Subscribe to an analytics query over SSE and returns its latest result * Integration hook between client and analytics plugin * @param queryKey - Analytics query identifier - * @param parameters - Optional query parameters + * @param parameters - Query parameters (type-safe based on QueryRegistry) * @param options - Analytics query settings * @returns - Query result state */ -export function useAnalyticsQuery< - T = unknown, - K extends QueryKey = QueryKey, - P extends Record = Record, ->( +export function useAnalyticsQuery( queryKey: K, - parameters?: P | null, + parameters?: InferParams | null, options: UseAnalyticsQueryOptions = { autoStart: true }, ): UseAnalyticsQueryResult> { const format = options?.format; diff --git a/packages/app-kit-ui/src/react/hooks/use-custom-plugin.ts b/packages/app-kit-ui/src/react/hooks/use-custom-plugin.ts deleted file mode 100644 index 0142532..0000000 --- a/packages/app-kit-ui/src/react/hooks/use-custom-plugin.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { useCallback, useEffect, useRef, useState } from "react"; -import type { PluginName, PluginRoutes, RouteResponse } from "./types"; - -export interface UseCustomPluginOptions { - autoStart?: boolean; -} - -export interface UseCustomPluginResult { - data: T | null; - loading: boolean; - error: string | null; - refetch: () => void; -} - -export function useCustomPlugin< - P extends PluginName, - R extends PluginRoutes

, ->( - plugin: P, - route: R, - options?: UseCustomPluginOptions, -): UseCustomPluginResult> { - const [data, setData] = useState | null>(null); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const abortControllerRef = useRef(null); - - const start = useCallback(async () => { - abortControllerRef.current?.abort(); - - const abortController = new AbortController(); - abortControllerRef.current = abortController; - - setLoading(true); - setError(null); - - try { - const response = await fetch(`/api/${plugin}/${route}`, { - signal: abortController.signal, - }); - if (!response.ok) { - throw new Error(`Request failed ${response.statusText}`); - } - - const result = await response.json(); - - if (!abortController.signal.aborted) { - setData(result as RouteResponse); - setLoading(false); - setError(null); - } - } catch (error) { - if (abortController.signal.aborted) return; - - setLoading(false); - - let userMessage = "Unable to load data, please try again"; - - if (error instanceof Error) { - if (error.name === "AbortError") { - userMessage = "Request timed out, please try again"; - } else if (error.message.includes("Failed to fetch")) { - userMessage = "Network error. Please check your connection."; - } else { - userMessage = error.message; - } - console.error("[useCustomPlugin] Error", { - plugin, - route, - error: error.message, - stack: error.stack, - }); - } - - setError(userMessage); - } - }, [plugin, route]); - - useEffect(() => { - if (options?.autoStart) start(); - - return () => { - abortControllerRef.current?.abort(); - }; - }, [start, options?.autoStart]); - - return { data, loading, error, refetch: start }; -} diff --git a/packages/app-kit/bin/generate-types.ts b/packages/app-kit/bin/generate-types.ts new file mode 100755 index 0000000..7dead6f --- /dev/null +++ b/packages/app-kit/bin/generate-types.ts @@ -0,0 +1,27 @@ +#!/usr/bin/env tsx +import path from "node:path"; + +import { generateFromEntryPoint } from "../src/type-generator"; + +// Parse arguments +const args = process.argv.slice(2); +const noCache = args.includes("--no-cache"); +const positionalArgs = args.filter((arg) => !arg.startsWith("--")); + +const rootDir = positionalArgs[0] || process.cwd(); +const outFile = + positionalArgs[1] || path.join(process.cwd(), "client/src/appKitTypes.d.ts"); + +const queryFolder = path.join(rootDir, "config/queries"); + +const warehouseId = positionalArgs[2] || process.env.DATABRICKS_WAREHOUSE_ID; +if (!warehouseId) { + throw new Error("DATABRICKS_WAREHOUSE_ID is not set"); +} + +await generateFromEntryPoint({ + queryFolder, + outFile, + warehouseId, + noCache, +}); diff --git a/packages/app-kit/package.json b/packages/app-kit/package.json index 3c20776..d411e41 100644 --- a/packages/app-kit/package.json +++ b/packages/app-kit/package.json @@ -19,6 +19,9 @@ }, "./package.json": "./package.json" }, + "bin": { + "appkit-generate-types": "./bin/generate-types.ts" + }, "scripts": { "build:package": "tsdown --config tsdown.config.ts", "build:watch": "tsdown --config tsdown.config.ts --watch", diff --git a/packages/app-kit/src/analytics/analytics.ts b/packages/app-kit/src/analytics/analytics.ts index 9b6a408..2b91b68 100644 --- a/packages/app-kit/src/analytics/analytics.ts +++ b/packages/app-kit/src/analytics/analytics.ts @@ -1,10 +1,7 @@ -import { existsSync, type FSWatcher, watch } from "node:fs"; -import path from "node:path"; import type { WorkspaceClient } from "@databricks/sdk-experimental"; import type { IAppRouter, PluginExecuteConfig, - QuerySchemas, SQLTypeMarker, StreamExecutionSettings, } from "shared"; @@ -12,13 +9,12 @@ import { SQLWarehouseConnector } from "../connectors"; import { Plugin, toPlugin } from "../plugin"; import type { Request, Response } from "../utils"; import { getRequestContext } from "../utils"; -import { generateQueryRegistryTypes } from "../utils/type-generator"; import { queryDefaults } from "./defaults"; import { QueryProcessor } from "./query"; -import { - analyticsQueryResponseSchema, - type IAnalyticsConfig, - type IAnalyticsQueryRequest, +import type { + AnalyticsQueryResponse, + IAnalyticsConfig, + IAnalyticsQueryRequest, } from "./types"; export class AnalyticsPlugin extends Plugin { @@ -32,8 +28,6 @@ export class AnalyticsPlugin extends Plugin { private SQLClient: SQLWarehouseConnector; private queryProcessor: QueryProcessor; - private schemaWatcher: FSWatcher | null = null; - constructor(config: IAnalyticsConfig) { super(config); this.config = config; @@ -43,27 +37,21 @@ export class AnalyticsPlugin extends Plugin { timeout: config.timeout, telemetry: this.telemetry, }); - - if (process.env.NODE_ENV === "development") { - this._generateQueryTypes(); - } } injectRoutes(router: IAppRouter) { - this.route(router, { + this.route(router, { method: "post", path: "/users/me/query/:query_key", - schema: analyticsQueryResponseSchema, - handler: async (req, res) => { + handler: async (req: Request, res: Response) => { await this._handleQueryRoute(req, res, { asUser: true }); }, }); - this.route(router, { + this.route(router, { method: "post", path: "/query/:query_key", - schema: analyticsQueryResponseSchema, - handler: async (req, res) => { + handler: async (req: Request, res: Response) => { await this._handleQueryRoute(req, res, { asUser: false }); }, }); @@ -173,51 +161,6 @@ export class AnalyticsPlugin extends Plugin { async shutdown(): Promise { this.streamManager.abortAll(); - - if (this.schemaWatcher) { - this.schemaWatcher.close(); - this.schemaWatcher = null; - } - } - - // generate query types for development - private _generateQueryTypes() { - const schemaDir = path.join(process.cwd(), "config/queries"); - const schemaPath = path.join(schemaDir, "schema.ts"); - - const typePath = - this.config.typePath || path.join(process.cwd(), "client", "src"); - - const generate = () => { - let querySchemas: QuerySchemas = {}; - try { - delete require.cache[require.resolve(schemaPath)]; - querySchemas = require(schemaPath).querySchemas; - } catch (error) { - if (existsSync(schemaPath)) { - console.warn( - `[AppKit] Failed to load query schemas from ${schemaPath}:`, - error instanceof Error ? error.message : error, - ); - } - } - generateQueryRegistryTypes(querySchemas, typePath); - }; - - generate(); - - if (existsSync(schemaPath)) { - this.schemaWatcher = watch( - schemaDir, - { recursive: true }, - (_event, filename) => { - if (filename === "schema.ts") { - console.log(`[AppKit] Query schema changed, regenerating types...`); - generate(); - } - }, - ); - } } } diff --git a/packages/app-kit/src/analytics/types.ts b/packages/app-kit/src/analytics/types.ts index db3feb8..6e14607 100644 --- a/packages/app-kit/src/analytics/types.ts +++ b/packages/app-kit/src/analytics/types.ts @@ -1,5 +1,4 @@ import type { BasePluginConfig } from "shared"; -import { z } from "zod"; export interface IAnalyticsConfig extends BasePluginConfig { timeout?: number; @@ -10,9 +9,9 @@ export interface IAnalyticsQueryRequest { parameters?: Record; } -export const analyticsQueryResponseSchema = z.object({ - chunk_index: z.number(), - row_offset: z.number(), - row_count: z.number(), - data: z.array(z.any()), -}); +export interface AnalyticsQueryResponse { + chunk_index: number; + row_offset: number; + row_count: number; + data: any[]; +} diff --git a/packages/app-kit/src/index.ts b/packages/app-kit/src/index.ts index 47db9af..ebb4677 100644 --- a/packages/app-kit/src/index.ts +++ b/packages/app-kit/src/index.ts @@ -21,4 +21,5 @@ export { type Span, SpanStatusCode, } from "./telemetry"; +export { appKitTypesPlugin } from "./type-generator/vite-plugin"; export { getRequestContext } from "./utils"; diff --git a/packages/app-kit/src/plugin/index.ts b/packages/app-kit/src/plugin/index.ts index d227385..5783b21 100644 --- a/packages/app-kit/src/plugin/index.ts +++ b/packages/app-kit/src/plugin/index.ts @@ -1,2 +1,2 @@ -export { Plugin, routeSchemaRegistry } from "./plugin"; +export { Plugin } from "./plugin"; export { toPlugin } from "./to-plugin"; diff --git a/packages/app-kit/src/plugin/plugin.ts b/packages/app-kit/src/plugin/plugin.ts index bd98202..734ea40 100644 --- a/packages/app-kit/src/plugin/plugin.ts +++ b/packages/app-kit/src/plugin/plugin.ts @@ -1,11 +1,4 @@ -import { - type ITelemetry, - normalizeTelemetryOptions, - TelemetryManager, -} from "../telemetry"; -import { AppManager } from "../app"; -import { CacheManager } from "../cache"; -import { StreamManager } from "../stream"; +import type express from "express"; import type { BasePlugin, BasePluginConfig, @@ -17,9 +10,15 @@ import type { StreamExecuteHandler, StreamExecutionSettings, } from "shared"; +import { AppManager } from "../app"; +import { CacheManager } from "../cache"; +import { StreamManager } from "../stream"; +import { + type ITelemetry, + normalizeTelemetryOptions, + TelemetryManager, +} from "../telemetry"; import { deepMerge, validateEnv } from "../utils"; -import type express from "express"; -import type { z } from "zod"; import { DevFileReader } from "./dev-reader"; import { CacheInterceptor } from "./interceptors/cache"; import { RetryInterceptor } from "./interceptors/retry"; @@ -30,8 +29,6 @@ import type { ExecutionInterceptor, } from "./interceptors/types"; -export const routeSchemaRegistry = new Map>(); - export abstract class Plugin< TConfig extends BasePluginConfig = BasePluginConfig, > implements BasePlugin @@ -154,20 +151,12 @@ export abstract class Plugin< } } - protected route( + // TResponse is used for type generation + protected route<_TResponse>( router: express.Router, - config: RouteConfig, + config: RouteConfig, ): void { - const { method, path, schema, handler } = config; - - let pluginRoutes = routeSchemaRegistry.get(this.name); - - if (!pluginRoutes) { - pluginRoutes = new Map(); - routeSchemaRegistry.set(this.name, pluginRoutes); - } - - pluginRoutes.set(path, schema); + const { method, path, handler } = config; router[method](path, handler); } diff --git a/packages/app-kit/src/server/index.ts b/packages/app-kit/src/server/index.ts index e62b78b..679e548 100644 --- a/packages/app-kit/src/server/index.ts +++ b/packages/app-kit/src/server/index.ts @@ -7,7 +7,6 @@ import type { PluginPhase } from "shared"; import { Plugin, toPlugin } from "../plugin"; import { instrumentations } from "../telemetry"; import { databricksClientMiddleware, isRemoteServerEnabled } from "../utils"; -import { generatePluginRegistryTypes } from "../utils/type-generator"; import { DevModeManager } from "./dev-mode"; import type { ServerConfig } from "./types"; import { getQueries, getRoutes } from "./utils"; @@ -70,10 +69,6 @@ export class ServerPlugin extends Plugin { this.extendRoutes(); - if (process.env.NODE_ENV === "development") { - generatePluginRegistryTypes(this.config.plugins, this.config.staticPath); - } - for (const extension of this.serverExtensions) { extension(this.serverApplication); } @@ -138,7 +133,7 @@ export class ServerPlugin extends Plugin { * @throws {Error} If the server is not started or autoStart is true. * @returns {HTTPServer} The server instance. */ - getServer() { + getServer(): HTTPServer { if (this.shouldAutoStart()) { throw new Error("Cannot get server when autoStart is true."); } diff --git a/packages/app-kit/src/type-generator/cache.ts b/packages/app-kit/src/type-generator/cache.ts new file mode 100644 index 0000000..78cc26c --- /dev/null +++ b/packages/app-kit/src/type-generator/cache.ts @@ -0,0 +1,68 @@ +import crypto from "node:crypto"; +import fs from "node:fs"; +import path from "node:path"; + +/** + * Cache types + * @property hash - the hash of the SQL query + * @property type - the type of the query + */ +interface CacheEntry { + hash: string; + type: string; +} + +/** + * Cache interface + * @property version - the version of the cache + * @property queries - the queries in the cache + */ +interface Cache { + version: string; + queries: Record; +} + +export const CACHE_VERSION = "1"; +const CACHE_FILE = ".appkit-types-cache.json"; + +/** + * Hash the SQL query + * Uses MD5 to hash the SQL query + * @param sql - the SQL query to hash + * @returns - the hash of the SQL query + */ +export function hashSQL(sql: string): string { + return crypto.createHash("md5").update(sql).digest("hex"); +} + +/** + * Load the cache from the file system + * If the cache is not found, run the query explain + * @param cacheDir - the directory to load the cache from + * @returns - the cache + */ +export function loadCache(cacheDir: string): Cache { + const cachePath = path.join(cacheDir, CACHE_FILE); + try { + if (fs.existsSync(cachePath)) { + const cache = JSON.parse(fs.readFileSync(cachePath, "utf8")) as Cache; + if (cache.version === CACHE_VERSION) { + return cache; + } + } + } catch { + // ignore cache errors + } + return { version: CACHE_VERSION, queries: {} }; +} + +/** + * Save the cache to the file system + * The cache is saved as a JSON file, it is used to avoid running the query explain multiple times + * @param cacheDir - the directory to save the cache to + * @param cache - cache object to save + */ +export function saveCache(cacheDir: string, cache: Cache): void { + const cachePath = path.join(cacheDir, CACHE_FILE); + fs.writeFileSync(cachePath, JSON.stringify(cache, null, 2), "utf8"); +} diff --git a/packages/app-kit/src/type-generator/expand.ts b/packages/app-kit/src/type-generator/expand.ts new file mode 100644 index 0000000..a9928ce --- /dev/null +++ b/packages/app-kit/src/type-generator/expand.ts @@ -0,0 +1,87 @@ +import ts from "typescript"; + +/** + * Expand type to a string to be used in the type declaration + * @param type - the type to expand + * @param typeChecker - the type checker + * @param seen - the set of seen types + * @param indent - the indent level + * @returns the expanded type as a string + */ +export function expandType( + type: ts.Type, + typeChecker: ts.TypeChecker, + seen = new Set(), + indent = 0, +): string { + if (seen.has(type)) return typeChecker.typeToString(type); + + if (type.isUnion()) + return type.types.map((t) => expandType(t, typeChecker, seen)).join(" | "); + + if (type.isIntersection()) + return type.types.map((t) => expandType(t, typeChecker, seen)).join(" & "); + + if (typeChecker.isArrayType(type)) { + const typeArgs = (type as ts.TypeReference).typeArguments; + if (typeArgs?.length) { + const elementType = expandType(typeArgs[0], typeChecker, seen); + return `${elementType}[]`; + } + } + + if (type.flags & ts.TypeFlags.Object) { + const objectType = type as ts.ObjectType; + + const typeName = typeChecker.typeToString(type); + const builtInTypes = [ + "Date", + "Map", + "Set", + "Promise", + "RegExp", + "Error", + "URL", + "Buffer", + ]; + if (builtInTypes.some((t) => typeName.startsWith(t))) { + return typeName; + } + + const isNamedType = + type.aliasSymbol || + objectType.objectFlags & ts.ObjectFlags.Reference || + objectType.objectFlags & ts.ObjectFlags.Interface; + + if (isNamedType) { + const properties = type.getProperties(); + + if (properties.length > 0) { + seen.add(type); + const spaces = " ".repeat(indent + 1); + const closingSpaces = " ".repeat(indent); + + const props = properties.map((prop) => { + const propType = typeChecker.getTypeOfSymbol(prop); + const propTypeStr = expandType( + propType, + typeChecker, + seen, + indent + 1, + ); + const isOptional = prop.flags & ts.SymbolFlags.Optional; + return `${spaces}${prop.name}${isOptional ? "?" : ""}: ${propTypeStr}`; + }); + + return `{\n${props.join("\n")}\n${closingSpaces}}`; + } + } + } + + // fallback + return typeChecker.typeToString( + type, + undefined, + ts.TypeFormatFlags.NoTruncation, + ); +} diff --git a/packages/app-kit/src/type-generator/index.ts b/packages/app-kit/src/type-generator/index.ts new file mode 100644 index 0000000..a6d07c6 --- /dev/null +++ b/packages/app-kit/src/type-generator/index.ts @@ -0,0 +1,70 @@ +import fs from "node:fs"; +import dotenv from "dotenv"; +import { generateQueriesFromDescribe } from "./query-registry"; +import type { QuerySchema } from "./types"; + +dotenv.config(); + +/** + * Generate type declarations for QueryRegistry + * Create the d.ts file from the plugin routes and query schemas + * @param querySchemas - the list of query schemas + * @returns - the type declarations as a string + */ +function generateTypeDeclarations(querySchemas: QuerySchema[] = []): string { + const queryEntries = querySchemas + .map(({ name, type }) => { + const indentedType = type + .split("\n") + .map((line, i) => (i === 0 ? line : ` ${line}`)) + .join("\n"); + return ` ${name}: ${indentedType}`; + }) + .join(";\n"); + + const querySection = queryEntries ? `\n${queryEntries};\n ` : ""; + + return `// Auto-generated by AppKit - DO NOT EDIT +// Generated by 'npx appkit-generate-types' or Vite plugin during build +import "@databricks/app-kit-ui/react"; +import type { SQLTypeMarker, SQLStringMarker, SQLNumberMarker, SQLBooleanMarker, SQLBinaryMarker, SQLDateMarker, SQLTimestampMarker } from "@databricks/app-kit-ui/js"; + +declare module "@databricks/app-kit-ui/react" { + interface QueryRegistry {${querySection}} +} +`; +} + +/** + * Entry point for generating type declarations from all imported files + * @param options - the options for the generation + * @param options.entryPoint - the entry point file + * @param options.outFile - the output file + * @param options.querySchemaFile - optional path to query schema file (e.g. config/queries/schema.ts) + */ +export async function generateFromEntryPoint(options: { + outFile: string; + queryFolder?: string; + warehouseId: string; + noCache?: boolean; +}) { + const { outFile, queryFolder, warehouseId, noCache } = options; + + console.log("\n[AppKit] Starting type generation...\n"); + + let queryRegistry: QuerySchema[] = []; + if (queryFolder) + queryRegistry = await generateQueriesFromDescribe( + queryFolder, + warehouseId, + { + noCache, + }, + ); + + const typeDeclarations = generateTypeDeclarations(queryRegistry); + + fs.writeFileSync(outFile, typeDeclarations, "utf-8"); + + console.log("\n[AppKit] Type generation complete!\n"); +} diff --git a/packages/app-kit/src/type-generator/query-registry.ts b/packages/app-kit/src/type-generator/query-registry.ts new file mode 100644 index 0000000..caeaa24 --- /dev/null +++ b/packages/app-kit/src/type-generator/query-registry.ts @@ -0,0 +1,252 @@ +import fs from "node:fs"; +import path from "node:path"; +import { WorkspaceClient } from "@databricks/sdk-experimental"; +import { CACHE_VERSION, hashSQL, loadCache, saveCache } from "./cache"; +import { Spinner } from "./spinner"; +import { + type DatabricksStatementExecutionResponse, + type QuerySchema, + sqlTypeToHelper, + sqlTypeToMarker, +} from "./types"; + +/** + * Extract parameters from a SQL query + * @param sql - the SQL query to extract parameters from + * @returns an array of parameter names + */ +export function extractParameters(sql: string): string[] { + const matches = sql.matchAll(/:([a-zA-Z_]\w*)/g); + const params = new Set(); + for (const match of matches) { + params.add(match[1]); + } + return Array.from(params); +} + +// parameters that are injected by the server +export const SERVER_INJECTED_PARAMS = ["workspaceId"]; + +export function convertToQueryType( + result: DatabricksStatementExecutionResponse, + sql: string, + queryName: string, +): string { + const dataRows = result.result?.data_array || []; + const columns = dataRows.map((row) => ({ + name: row[0] || "", + type_name: row[1]?.toUpperCase() || "STRING", + comment: row[2] || undefined, + })); + + const params = extractParameters(sql).filter( + (p) => !SERVER_INJECTED_PARAMS.includes(p), + ); + + const paramTypes = extractParameterTypes(sql); + + // generate parameters types with JSDoc hints + const paramsType = + params.length > 0 + ? `{\n ${params + .map((p) => { + const sqlType = paramTypes[p]; + // if no type annotation, use SQLTypeMarker (union type) + const markerType = sqlType + ? sqlTypeToMarker[sqlType] + : "SQLTypeMarker"; + const helper = sqlType ? sqlTypeToHelper[sqlType] : "sql.*()"; + return `/** ${sqlType || "any"} - use ${helper} */\n ${p}: ${markerType}`; + }) + .join(";\n ")};\n }` + : "Record"; + + // generate result fields with JSDoc + const resultFields = columns.map((column) => { + const normalizedType = normalizeTypeName(column.type_name); + const mappedType = typeMap[normalizedType] || "unknown"; + // validate column name is a valid identifier + const name = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(column.name) + ? column.name + : `"${column.name}"`; + + // generate comment for column + const comment = column.comment + ? `/** ${column.comment} */\n ` + : `/** @sqlType ${column.type_name} */\n `; + + return `${comment}${name}: ${mappedType}`; + }); + + return `{ + name: "${queryName}"; + parameters: ${paramsType}; + result: Array<{ + ${resultFields.join(";\n ")}; + }>; + }`; +} + +export function extractParameterTypes(sql: string): Record { + const paramTypes: Record = {}; + const regex = + /--\s*@param\s+(\w+)\s+(STRING|NUMERIC|BOOLEAN|DATE|TIMESTAMP|BINARY)/gi; + const matches = sql.matchAll(regex); + for (const match of matches) { + const [, paramName, paramType] = match; + paramTypes[paramName] = paramType.toUpperCase(); + } + + return paramTypes; +} + +/** + * Generate query schemas from a folder of SQL files + * It uses DESCRIBE QUERY to get the schema without executing the query + * @param queryFolder - the folder containing the SQL files + * @param warehouseId - the warehouse id to use for schema analysis + * @param options - options for the query generation + * @param options.noCache - if true, skip the cache and regenerate all types + * @returns an array of query schemas + */ +export async function generateQueriesFromDescribe( + queryFolder: string, + warehouseId: string, + options: { noCache?: boolean } = {}, +): Promise { + const { noCache = false } = options; + + // read all query files in the folder + const queryFiles = fs + .readdirSync(queryFolder) + .filter((file) => file.endsWith(".sql")); + + console.log(` Found ${queryFiles.length} SQL queries\n`); + + // load cache + const cache = noCache + ? { version: CACHE_VERSION, queries: {} } + : loadCache(queryFolder); + + const client = new WorkspaceClient({}); + const querySchemas: QuerySchema[] = []; + const failedQueries: { name: string; error: string }[] = []; + const spinner = new Spinner(); + + // process each query file + for (let i = 0; i < queryFiles.length; i++) { + const file = queryFiles[i]; + const queryName = path.basename(file, ".sql"); + + // read query file content + const sql = fs.readFileSync(path.join(queryFolder, file), "utf8"); + const sqlHash = hashSQL(sql); + + // check cache + const cached = cache.queries[queryName]; + if (cached && cached.hash === sqlHash) { + querySchemas.push({ name: queryName, type: cached.type }); + spinner.start(`Processing ${queryName} (${i + 1}/${queryFiles.length})`); + spinner.stop(`✓ ${queryName} (cached)`); + continue; + } + + spinner.start(`Processing ${queryName} (${i + 1}/${queryFiles.length})`); + + const sqlWithDefaults = sql.replace(/:([a-zA-Z_]\w*)/g, "''"); + + // strip trailing semicolon for DESCRIBE QUERY + const cleanedSql = sqlWithDefaults.trim().replace(/;\s*$/, ""); + + // execute DESCRIBE QUERY to get schema without running the actual query + try { + const result = (await client.statementExecution.executeStatement({ + statement: `DESCRIBE QUERY ${cleanedSql}`, + warehouse_id: warehouseId, + })) as DatabricksStatementExecutionResponse; + + if (result.status.state === "FAILED") { + spinner.stop(`✗ ${queryName} - failed`); + failedQueries.push({ + name: queryName, + error: "Query execution failed", + }); + continue; + } + + // convert result to query schema + const type = convertToQueryType(result, sql, queryName); + querySchemas.push({ name: queryName, type }); + + // update cache + cache.queries[queryName] = { hash: sqlHash, type }; + + spinner.stop(`✓ ${queryName}`); + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : "Unknown error"; + spinner.stop(`✗ ${queryName} - ${errorMessage}`); + failedQueries.push({ name: queryName, error: errorMessage }); + } + } + + // save cache + saveCache(queryFolder, cache); + + // log warning if there are failed queries + if (failedQueries.length > 0) { + console.warn(` Warning: ${failedQueries.length} queries failed\n`); + } + + return querySchemas; +} + +/** + * Normalize SQL type name by removing parameters/generics + * Examples: + * DECIMAL(38,6) -> DECIMAL + * ARRAY -> ARRAY + * MAP -> MAP + * STRUCT -> STRUCT + * INTERVAL DAY TO SECOND -> INTERVAL + * GEOGRAPHY(4326) -> GEOGRAPHY + */ +export function normalizeTypeName(typeName: string): string { + return typeName + .replace(/\(.*\)$/, "") // remove (p, s) eg: DECIMAL(38,6) -> DECIMAL + .replace(/<.*>$/, "") // remove eg: ARRAY -> ARRAY + .split(" ")[0]; // take first word eg: INTERVAL DAY TO SECOND -> INTERVAL +} + +/** Type Map for Databricks data types to JavaScript types */ +const typeMap: Record = { + // string types + STRING: "string", + BINARY: "string", + // boolean + BOOLEAN: "boolean", + // numeric types + TINYINT: "number", + SMALLINT: "number", + INT: "number", + BIGINT: "number", + FLOAT: "number", + DOUBLE: "number", + DECIMAL: "number", + // date/time types + DATE: "string", + TIMESTAMP: "string", + TIMESTAMP_NTZ: "string", + INTERVAL: "string", + // complex types + ARRAY: "unknown[]", + MAP: "Record", + STRUCT: "Record", + OBJECT: "Record", + VARIANT: "unknown", + // spatial types + GEOGRAPHY: "unknown", + GEOMETRY: "unknown", + // null type + VOID: "null", +}; diff --git a/packages/app-kit/src/type-generator/spinner.ts b/packages/app-kit/src/type-generator/spinner.ts new file mode 100644 index 0000000..4949a4e --- /dev/null +++ b/packages/app-kit/src/type-generator/spinner.ts @@ -0,0 +1,28 @@ +/** + * Simple loading spinner for CLI + */ +export class Spinner { + private frames = [" ", ". ", ".. ", "..."]; + private current = 0; + private interval: NodeJS.Timeout | null = null; + private text = ""; + + start(text: string) { + this.text = text; + this.current = 0; + process.stdout.write(` ${this.text}${this.frames[0]}`); + this.interval = setInterval(() => { + this.current = (this.current + 1) % this.frames.length; + process.stdout.write(`\r ${this.text}${this.frames[this.current]}`); + }, 300); + } + + stop(finalText?: string) { + if (this.interval) { + clearInterval(this.interval); + this.interval = null; + } + // clear the line and write the final text + process.stdout.write(`\x1b[2K\r ${finalText || this.text}\n`); + } +} diff --git a/packages/app-kit/src/type-generator/tests/expand.test.ts b/packages/app-kit/src/type-generator/tests/expand.test.ts new file mode 100644 index 0000000..ff77388 --- /dev/null +++ b/packages/app-kit/src/type-generator/tests/expand.test.ts @@ -0,0 +1,216 @@ +import ts from "typescript"; +import { describe, expect, test } from "vitest"; +import { expandType } from "../expand"; + +function createProgramWithSource( + source: string, + options: ts.CompilerOptions = {}, +) { + const fileName = "test.ts"; + const compilerHost = ts.createCompilerHost({}); + const originalGetSourceFile = compilerHost.getSourceFile; + + compilerHost.getSourceFile = (name, languageVersion, onError) => { + if (name === fileName) { + return ts.createSourceFile(name, source, languageVersion, true); + } + return originalGetSourceFile(name, languageVersion, onError); + }; + + compilerHost.fileExists = (name) => + name === fileName || ts.sys.fileExists(name); + compilerHost.readFile = (name) => + name === fileName ? source : ts.sys.readFile(name); + + const program = ts.createProgram( + [fileName], + { noEmit: true, ...options }, + compilerHost, + ); + return { program, fileName }; +} + +function getTypeFromSource( + source: string, + typeName: string, + options: ts.CompilerOptions = {}, +) { + const { program, fileName } = createProgramWithSource(source, options); + const sourceFile = program.getSourceFile(fileName); + if (!sourceFile) { + throw new Error(`Could not get source file: ${fileName}`); + } + const typeChecker = program.getTypeChecker(); + + let foundType: ts.Type | null = null; + + function visit(node: ts.Node) { + if (ts.isTypeAliasDeclaration(node) && node.name.text === typeName) { + foundType = typeChecker.getTypeAtLocation(node.name); + } + if (ts.isInterfaceDeclaration(node) && node.name.text === typeName) { + foundType = typeChecker.getTypeAtLocation(node.name); + } + ts.forEachChild(node, visit); + } + + ts.forEachChild(sourceFile, visit); + if (!foundType) { + throw new Error(`Type ${typeName} not found in source`); + } + return { type: foundType, typeChecker }; +} + +describe("expandType", () => { + describe("primitive types", () => { + test("expands string type", () => { + const { type, typeChecker } = getTypeFromSource( + "type TestType = string;", + "TestType", + ); + expect(expandType(type, typeChecker)).toBe("string"); + }); + + test("expands number type", () => { + const { type, typeChecker } = getTypeFromSource( + "type TestType = number;", + "TestType", + ); + expect(expandType(type, typeChecker)).toBe("number"); + }); + + test("expands boolean type", () => { + const { type, typeChecker } = getTypeFromSource( + "type TestType = boolean;", + "TestType", + ); + const result = expandType(type, typeChecker); + // TypeScript internally represents boolean as "false | true" union + expect(result === "boolean" || result === "false | true").toBe(true); + }); + }); + + describe("union types", () => { + test("expands string literal union", () => { + const { type, typeChecker } = getTypeFromSource( + 'type TestType = "a" | "b" | "c";', + "TestType", + ); + const result = expandType(type, typeChecker); + expect(result).toContain('"a"'); + expect(result).toContain('"b"'); + expect(result).toContain('"c"'); + expect(result).toContain("|"); + }); + + test("expands mixed union", () => { + const { type, typeChecker } = getTypeFromSource( + "type TestType = string | number | null;", + "TestType", + { strictNullChecks: true }, // Required for null to be preserved in union + ); + const result = expandType(type, typeChecker); + expect(result).toContain("string"); + expect(result).toContain("number"); + expect(result).toContain("null"); + }); + }); + + describe("object types", () => { + test("expands simple object", () => { + const { type, typeChecker } = getTypeFromSource( + "interface TestType { id: string; count: number; }", + "TestType", + ); + const result = expandType(type, typeChecker); + expect(result).toContain("id"); + expect(result).toContain("string"); + expect(result).toContain("count"); + expect(result).toContain("number"); + }); + + test("expands nested object", () => { + const { type, typeChecker } = getTypeFromSource( + "interface TestType { user: { name: string; age: number; }; }", + "TestType", + ); + const result = expandType(type, typeChecker); + expect(result).toContain("user"); + expect(result).toContain("name"); + expect(result).toContain("string"); + expect(result).toContain("age"); + expect(result).toContain("number"); + }); + + test("expands optional properties", () => { + const { type, typeChecker } = getTypeFromSource( + "interface TestType { required: string; optional?: number; }", + "TestType", + ); + const result = expandType(type, typeChecker); + expect(result).toContain("required"); + expect(result).toContain("optional?"); + }); + }); + + describe("array types", () => { + test("expands primitive array", () => { + const { type, typeChecker } = getTypeFromSource( + "type TestType = string[];", + "TestType", + ); + const result = expandType(type, typeChecker); + expect(result).toContain("string"); + expect(result).toContain("[]"); + }); + + test("expands object array", () => { + const { type, typeChecker } = getTypeFromSource( + "type TestType = { id: string; name: string; }[];", + "TestType", + ); + const result = expandType(type, typeChecker); + expect(result).toContain("id"); + expect(result).toContain("name"); + expect(result).toContain("[]"); + }); + }); + + describe("intersection types", () => { + test("expands intersection", () => { + const { type, typeChecker } = getTypeFromSource( + ` + interface A { a: string; } + interface B { b: number; } + type TestType = A & B; + `, + "TestType", + ); + const result = expandType(type, typeChecker); + expect(result).toContain("a"); + expect(result).toContain("string"); + expect(result).toContain("b"); + expect(result).toContain("number"); + }); + }); + + describe("built-in types", () => { + test("preserves Date type", () => { + const { type, typeChecker } = getTypeFromSource( + "type TestType = Date;", + "TestType", + ); + const result = expandType(type, typeChecker); + expect(result).toBe("Date"); + }); + + test("preserves Promise type", () => { + const { type, typeChecker } = getTypeFromSource( + "type TestType = Promise;", + "TestType", + ); + const result = expandType(type, typeChecker); + expect(result).toContain("Promise"); + }); + }); +}); diff --git a/packages/app-kit/src/type-generator/tests/index.test.ts b/packages/app-kit/src/type-generator/tests/index.test.ts new file mode 100644 index 0000000..bd20522 --- /dev/null +++ b/packages/app-kit/src/type-generator/tests/index.test.ts @@ -0,0 +1,54 @@ +import fs from "node:fs"; +import path from "node:path"; +import { afterAll, beforeAll, describe, expect, test } from "vitest"; +import { generateFromEntryPoint } from "../index"; + +const outputDir = path.join(__dirname, "__output__"); + +describe("generateFromEntryPoint", () => { + beforeAll(() => { + // Create output directory once before all tests + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); + } + }); + + afterAll(() => { + // Clean up output directory after all tests complete + if (fs.existsSync(outputDir)) { + fs.rmSync(outputDir, { recursive: true }); + } + }); + + // Note: Query schema generation now requires Databricks connection + // This test verifies the basic structure without actual query execution + test("generates type declarations without query folder", async () => { + const outFile = path.join(outputDir, "types-with-queries.d.ts"); + + await generateFromEntryPoint({ + outFile, + warehouseId: "test", + }); + + expect(fs.existsSync(outFile)).toBe(true); + + const content = fs.readFileSync(outFile, "utf-8"); + + // Check QueryRegistry is included (empty when no queryFolder) + expect(content).toContain("interface QueryRegistry"); + }); + + test("generates empty QueryRegistry when no query folder provided", async () => { + const outFile = path.join(outputDir, "types-no-queries.d.ts"); + + await generateFromEntryPoint({ + outFile, + warehouseId: "test", + }); + + const content = fs.readFileSync(outFile, "utf-8"); + + // QueryRegistry should be empty + expect(content).toContain("interface QueryRegistry {}"); + }); +}); diff --git a/packages/app-kit/src/type-generator/tests/query-registry.test.ts b/packages/app-kit/src/type-generator/tests/query-registry.test.ts new file mode 100644 index 0000000..d467c69 --- /dev/null +++ b/packages/app-kit/src/type-generator/tests/query-registry.test.ts @@ -0,0 +1,250 @@ +import { describe, expect, test } from "vitest"; +import { + convertToQueryType, + extractParameters, + extractParameterTypes, + normalizeTypeName, + SERVER_INJECTED_PARAMS, +} from "../query-registry"; +import type { DatabricksStatementExecutionResponse } from "../types"; + +describe("normalizeTypeName", () => { + test("returns simple types unchanged", () => { + expect(normalizeTypeName("STRING")).toBe("STRING"); + expect(normalizeTypeName("INT")).toBe("INT"); + expect(normalizeTypeName("BOOLEAN")).toBe("BOOLEAN"); + }); + + test("removes precision/scale from DECIMAL", () => { + expect(normalizeTypeName("DECIMAL(38,6)")).toBe("DECIMAL"); + expect(normalizeTypeName("DECIMAL(10,2)")).toBe("DECIMAL"); + }); + + test("removes srid from spatial types", () => { + expect(normalizeTypeName("GEOGRAPHY(4326)")).toBe("GEOGRAPHY"); + expect(normalizeTypeName("GEOMETRY(4326)")).toBe("GEOMETRY"); + }); + + test("removes element type from ARRAY", () => { + expect(normalizeTypeName("ARRAY")).toBe("ARRAY"); + expect(normalizeTypeName("ARRAY")).toBe("ARRAY"); + }); + + test("removes key/value types from MAP", () => { + expect(normalizeTypeName("MAP")).toBe("MAP"); + expect(normalizeTypeName("MAP>")).toBe("MAP"); + }); + + test("removes field definitions from STRUCT", () => { + expect(normalizeTypeName("STRUCT")).toBe("STRUCT"); + }); + + test("removes qualifier from INTERVAL", () => { + expect(normalizeTypeName("INTERVAL DAY TO SECOND")).toBe("INTERVAL"); + expect(normalizeTypeName("INTERVAL YEAR TO MONTH")).toBe("INTERVAL"); + }); +}); + +describe("extractParameters", () => { + test("extracts parameters from SQL query", () => { + const sql = "SELECT * FROM users WHERE id = :userId AND status = :status"; + const params = extractParameters(sql); + + expect(params).toContain("userId"); + expect(params).toContain("status"); + expect(params.length).toBe(2); + }); + + test("extracts unique parameters (no duplicates)", () => { + const sql = + "SELECT * FROM users WHERE id = :userId OR created_by = :userId"; + const params = extractParameters(sql); + + expect(params).toEqual(["userId"]); + }); + + test("returns empty array for SQL without parameters", () => { + const sql = "SELECT * FROM users"; + const params = extractParameters(sql); + + expect(params).toEqual([]); + }); + + test("handles complex parameter names", () => { + const sql = + "SELECT * FROM data WHERE start_date = :startDate AND workspace_id = :workspaceId"; + const params = extractParameters(sql); + + expect(params).toContain("startDate"); + expect(params).toContain("workspaceId"); + }); +}); + +describe("SERVER_INJECTED_PARAMS", () => { + test("includes workspaceId", () => { + expect(SERVER_INJECTED_PARAMS).toContain("workspaceId"); + }); +}); + +describe("extractParameterTypes", () => { + test("extracts parameter types from SQL comments", () => { + const sql = `-- @param startDate DATE +-- @param endDate DATE +-- @param groupBy STRING +SELECT * FROM users WHERE date BETWEEN :startDate AND :endDate`; + const types = extractParameterTypes(sql); + + expect(types.startDate).toBe("DATE"); + expect(types.endDate).toBe("DATE"); + expect(types.groupBy).toBe("STRING"); + }); + + test("returns empty object for SQL without @param comments", () => { + const sql = "SELECT * FROM users WHERE date = :startDate"; + const types = extractParameterTypes(sql); + + expect(Object.keys(types).length).toBe(0); + }); + + test("handles all supported types", () => { + const sql = `-- @param str STRING +-- @param num NUMERIC +-- @param bool BOOLEAN +-- @param dt DATE +-- @param ts TIMESTAMP +-- @param bin BINARY +SELECT 1`; + const types = extractParameterTypes(sql); + + expect(types.str).toBe("STRING"); + expect(types.num).toBe("NUMERIC"); + expect(types.bool).toBe("BOOLEAN"); + expect(types.dt).toBe("DATE"); + expect(types.ts).toBe("TIMESTAMP"); + expect(types.bin).toBe("BINARY"); + }); + + test("ignores malformed @param comments", () => { + const sql = `-- @param startDate +-- @param INVALID +-- @param noType +-- this is not a param comment +SELECT 1`; + const types = extractParameterTypes(sql); + + expect(Object.keys(types).length).toBe(0); + }); + + test("handles mixed valid and invalid annotations", () => { + const sql = `-- @param validDate DATE +-- @param invalidParam +-- @param validString STRING +SELECT 1`; + const types = extractParameterTypes(sql); + + expect(types.validDate).toBe("DATE"); + expect(types.validString).toBe("STRING"); + expect(types.invalidParam).toBeUndefined(); + expect(Object.keys(types).length).toBe(2); + }); +}); + +describe("convertToQueryType", () => { + // DESCRIBE QUERY returns rows as [col_name, data_type, comment] + const mockResponse: DatabricksStatementExecutionResponse = { + statement_id: "test-123", + status: { state: "SUCCEEDED" }, + result: { + data_array: [ + ["id", "STRING", null], + ["name", "STRING", null], + ["count", "INT", null], + ], + }, + }; + + test("generates query type with parameters", () => { + const sql = "SELECT * FROM users WHERE start_date = :startDate"; + const result = convertToQueryType(mockResponse, sql, "users"); + + expect(result).toContain('name: "users"'); + expect(result).toContain("parameters:"); + expect(result).toContain("startDate: SQLTypeMarker"); + expect(result).toContain("result: Array<{"); + }); + + test("excludes server-injected params from parameters type", () => { + const sql = + "SELECT * FROM users WHERE workspace_id = :workspaceId AND date = :startDate"; + const result = convertToQueryType(mockResponse, sql, "users"); + + expect(result).toContain("startDate: SQLTypeMarker"); + expect(result).not.toContain("workspaceId:"); + }); + + test("uses specific marker types when @param annotation is provided", () => { + const sql = `-- @param startDate DATE +-- @param count NUMERIC +-- @param name STRING +SELECT * FROM users WHERE date = :startDate AND count = :count AND name = :name`; + const result = convertToQueryType(mockResponse, sql, "users"); + + expect(result).toContain("startDate: SQLDateMarker"); + expect(result).toContain("count: SQLNumberMarker"); + expect(result).toContain("name: SQLStringMarker"); + }); + + test("generates Record for queries without params", () => { + const sql = "SELECT * FROM users"; + const result = convertToQueryType(mockResponse, sql, "users"); + + expect(result).toContain("parameters: Record"); + }); + + test("maps column types correctly", () => { + const result = convertToQueryType(mockResponse, "SELECT 1", "test"); + + expect(result).toContain("id: string"); + expect(result).toContain("name: string"); + expect(result).toContain("count: number"); + }); + + test("adds JSDoc comments with @sqlType", () => { + const result = convertToQueryType(mockResponse, "SELECT 1", "test"); + + expect(result).toContain("/** @sqlType STRING */"); + expect(result).toContain("/** @sqlType INT */"); + }); + + test("uses column comment when available", () => { + const responseWithComment: DatabricksStatementExecutionResponse = { + statement_id: "test-123", + status: { state: "SUCCEEDED" }, + result: { + data_array: [["total", "DECIMAL", "Total amount in USD"]], + }, + }; + + const result = convertToQueryType(responseWithComment, "SELECT 1", "test"); + + expect(result).toContain("/** Total amount in USD */"); + }); + + test("quotes invalid column identifiers", () => { + const responseWithInvalidName: DatabricksStatementExecutionResponse = { + statement_id: "test-123", + status: { state: "SUCCEEDED" }, + result: { + data_array: [["(1 = 1)", "BOOLEAN", null]], + }, + }; + + const result = convertToQueryType( + responseWithInvalidName, + "SELECT 1", + "test", + ); + + expect(result).toContain('"(1 = 1)": boolean'); + }); +}); diff --git a/packages/app-kit/src/type-generator/types.ts b/packages/app-kit/src/type-generator/types.ts new file mode 100644 index 0000000..9733851 --- /dev/null +++ b/packages/app-kit/src/type-generator/types.ts @@ -0,0 +1,73 @@ +/** + * Databricks statement execution response interface for DESCRIBE QUERY + * @property statement_id - the id of the statement + * @property status - the status of the statement + * @property result - the result containing column schema as rows [col_name, data_type, comment] + */ +export interface DatabricksStatementExecutionResponse { + statement_id: string; + status: { state: string }; + result?: { + data_array?: (string | null)[][]; + }; +} + +/** + * Map of SQL types to their corresponding marker types + * Used to convert SQL types to their corresponding marker types + */ +export const sqlTypeToMarker: Record = { + // string + STRING: "SQLStringMarker", + BINARY: "SQLBinaryMarker", + // boolean + BOOLEAN: "SQLBooleanMarker", + // numeric + NUMERIC: "SQLNumberMarker", + INT: "SQLNumberMarker", + BIGINT: "SQLNumberMarker", + TINYINT: "SQLNumberMarker", + SMALLINT: "SQLNumberMarker", + FLOAT: "SQLNumberMarker", + DOUBLE: "SQLNumberMarker", + DECIMAL: "SQLNumberMarker", + // date/time + DATE: "SQLDateMarker", + TIMESTAMP: "SQLTimestampMarker", + TIMESTAMP_NTZ: "SQLTimestampMarker", +}; + +/** + * Map of SQL types to their corresponding helper function names + * Used to generate JSDoc hints for parameters + */ +export const sqlTypeToHelper: Record = { + // string + STRING: "sql.string()", + BINARY: "sql.binary()", + // boolean + BOOLEAN: "sql.boolean()", + // numeric + NUMERIC: "sql.number()", + INT: "sql.number()", + BIGINT: "sql.number()", + TINYINT: "sql.number()", + SMALLINT: "sql.number()", + FLOAT: "sql.number()", + DOUBLE: "sql.number()", + DECIMAL: "sql.number()", + // date/time + DATE: "sql.date()", + TIMESTAMP: "sql.timestamp()", + TIMESTAMP_NTZ: "sql.timestamp()", +}; + +/** + * Query schema interface + * @property name - the name of the query + * @property type - the type of the query (string, number, boolean, object, array, etc.) + */ +export interface QuerySchema { + name: string; + type: string; +} diff --git a/packages/app-kit/src/type-generator/vite-plugin.ts b/packages/app-kit/src/type-generator/vite-plugin.ts new file mode 100644 index 0000000..b23f902 --- /dev/null +++ b/packages/app-kit/src/type-generator/vite-plugin.ts @@ -0,0 +1,73 @@ +import { execSync } from "node:child_process"; +import path from "node:path"; +import type { Plugin } from "vite"; + +/** + * Options for the AppKit types plugin. + */ +interface AppKitTypesPluginOptions { + /* Path to the output d.ts file (relative to client folder). */ + outFile?: string; + /** Folders to watch for changes. */ + watchFolders?: string[]; +} + +/** + * Vite plugin to generate types for AppKit queries. + * Calls `npx appkit-generate-types` under the hood. + * @param options - Options to override default values. + * @returns Vite plugin to generate types for AppKit queries. + */ +export function appKitTypesPlugin(options?: AppKitTypesPluginOptions): Plugin { + let root: string; + let appRoot: string; + let outFile: string; + let watchFolders: string[]; + + function generate() { + try { + const args = [appRoot, outFile].join(" "); + execSync(`npx appkit-generate-types ${args}`, { + cwd: appRoot, + stdio: "inherit", + }); + } catch (error) { + // throw in production to fail the build + if (process.env.NODE_ENV === "production") { + throw error; + } + console.error("[AppKit] Error generating types:", error); + } + } + + return { + name: "appkit-types", + configResolved(config) { + root = config.root; + appRoot = path.resolve(root, ".."); + + outFile = path.resolve(root, options?.outFile ?? "src/appKitTypes.d.ts"); + + watchFolders = (options?.watchFolders ?? ["../config/queries"]).map( + (folder) => path.resolve(root, folder), + ); + }, + buildStart() { + generate(); + }, + + configureServer(server) { + server.watcher.add(watchFolders); + + server.watcher.on("change", (changedFile) => { + const isWatchedFile = watchFolders.some((folder) => + changedFile.startsWith(folder), + ); + + if (isWatchedFile && changedFile.endsWith(".sql")) { + generate(); + } + }); + }, + }; +} diff --git a/packages/app-kit/src/utils/type-generator.ts b/packages/app-kit/src/utils/type-generator.ts deleted file mode 100644 index 5387267..0000000 --- a/packages/app-kit/src/utils/type-generator.ts +++ /dev/null @@ -1,157 +0,0 @@ -import fs from "node:fs"; -import path from "node:path"; -import type { QuerySchemas } from "shared"; -import { createAuxiliaryTypeStore, printNode, zodToTs } from "zod-to-ts"; -import { type Plugin, routeSchemaRegistry } from "../plugin"; - -interface AppKitRegistry { - pluginRegistry: string; - queryRegistry: string; -} - -const TYPE_FILE_NAME = "appKitTypes.d.ts"; -const DEFAULT_TYPE_PATH = path.join(process.cwd(), "client", "src"); - -let writeLock: Promise = Promise.resolve(); - -function indentType(typeString: string, baseIndent: string): string { - const lines = typeString.split("\n"); - if (lines.length === 1) return typeString; - - return lines - .map((line, index) => (index === 0 ? line : baseIndent + line)) - .join("\n"); -} - -function resolveTypePath(typePath?: string): string { - if (!typePath) return DEFAULT_TYPE_PATH; - if (typePath.endsWith("dist")) { - return typePath.replace(/dist$/, "src"); - } - return typePath; -} - -function readExistingTypes(filePath: string): AppKitRegistry { - if (!fs.existsSync(filePath)) { - return { pluginRegistry: "", queryRegistry: "" }; - } - - const content = fs.readFileSync(filePath, "utf-8"); - - const pluginMatch = content.match( - /interface PluginRegistry \{([\s\S]*?)\n {2}\}/, - ); - const queryMatch = content.match( - /interface QueryRegistry \{([\s\S]*?)\n {2}\}/, - ); - - return { - pluginRegistry: pluginMatch?.[1] ?? "", - queryRegistry: queryMatch?.[1] ?? "", - }; -} - -function withLock(fn: () => T | Promise): Promise { - const execute = async (): Promise => { - await writeLock; - return fn(); - }; - - const newLock = execute(); - writeLock = newLock.then( - () => {}, - () => {}, - ); - return newLock; -} - -function writeTypeFile( - filePath: string, - pluginRegistry: string, - queryRegistry: string, -) { - const dir = path.dirname(filePath); - if (!fs.existsSync(dir)) { - console.log( - `[AppKit] Type path not found: ${dir}, skipping type generation`, - ); - return; - } - - const querySection = queryRegistry - ? `\n interface QueryRegistry {\n${queryRegistry}\n }` - : ""; - - const content = `// Auto-generated by AppKit - DO NOT EDIT -import "@databricks/app-kit-ui/react"; - -declare module "@databricks/app-kit-ui/react" { - interface PluginRegistry { -${pluginRegistry} - } -${querySection} -} -`; - - fs.writeFileSync(filePath, content, "utf-8"); - console.log(`[AppKit] Types generated: ${filePath}`); -} - -// generate plugin registry types for development -export function generatePluginRegistryTypes( - plugins: Record | undefined, - typePath?: string, -) { - if (!plugins) return; - - withLock(() => { - const resolvedPath = resolveTypePath(typePath); - const filePath = path.join(resolvedPath, TYPE_FILE_NAME); - const existing = readExistingTypes(filePath); - const auxiliaryTypeStore = createAuxiliaryTypeStore(); - - const pluginTypes = Object.entries(plugins) - .map(([name]) => { - const routes = routeSchemaRegistry.get(name); - if (!routes || routes.size === 0) return null; - - const routeTypes = Array.from(routes.entries()) - .map(([route, schema]) => { - const { node } = zodToTs(schema, { auxiliaryTypeStore }); - const typeString = printNode(node); - return ` "${route}": ${indentType(typeString, " ")};`; - }) - .join("\n"); - return ` "${name}": {\n${routeTypes}\n }`; - }) - .filter(Boolean) - .join("\n"); - - writeTypeFile(filePath, pluginTypes, existing.queryRegistry); - }); -} - -export function generateQueryRegistryTypes( - querySchemas: QuerySchemas, - typePath?: string, -) { - withLock(() => { - const resolvedPath = resolveTypePath(typePath); - const filePath = path.join(resolvedPath, TYPE_FILE_NAME); - const existing = readExistingTypes(filePath); - const auxiliaryTypeStore = createAuxiliaryTypeStore(); - - let queryTypes = ""; - if (querySchemas && Object.keys(querySchemas).length > 0) { - queryTypes = Object.entries(querySchemas) - .map(([queryKey, schema]) => { - const { node } = zodToTs(schema, { auxiliaryTypeStore }); - const typeString = printNode(node); - return ` ${queryKey}: ${indentType(typeString, " ")};`; - }) - .join("\n"); - } - - writeTypeFile(filePath, existing.pluginRegistry, queryTypes); - }); -} diff --git a/packages/shared/package.json b/packages/shared/package.json index 1ba9ee4..cc43ae4 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -18,8 +18,10 @@ "clean:full": "rm -rf dist node_modules" }, "devDependencies": { + "@types/dependency-tree": "^8.1.4", "@types/express": "^4.17.21", - "@types/ws": "^8.18.1" + "@types/ws": "^8.18.1", + "dependency-tree": "^11.2.0" }, "main": "./dist/index.js", "module": "./dist/index.js", @@ -29,5 +31,8 @@ ".": "./dist/index.js", "./package.json": "./package.json" } + }, + "dependencies": { + "fast-glob": "^3.3.3" } } diff --git a/packages/shared/src/plugin.ts b/packages/shared/src/plugin.ts index b93e655..eb5a889 100644 --- a/packages/shared/src/plugin.ts +++ b/packages/shared/src/plugin.ts @@ -1,5 +1,4 @@ import type express from "express"; -import type z from "zod"; export interface BasePlugin { name: string; @@ -96,13 +95,12 @@ export type IAppRequest = express.Request; export type HttpMethod = "get" | "post" | "put" | "delete" | "patch" | "head"; -export type RouteConfig = { +export type RouteConfig = { method: HttpMethod; path: string; - schema: T; handler: (req: IAppRequest, res: IAppResponse) => Promise; }; export interface QuerySchemas { - [key: string]: z.ZodType; + [key: string]: unknown; } diff --git a/packages/shared/src/sql/helpers.ts b/packages/shared/src/sql/helpers.ts index 8f77ee9..85b3952 100644 --- a/packages/shared/src/sql/helpers.ts +++ b/packages/shared/src/sql/helpers.ts @@ -1,17 +1,12 @@ -/** - * Object that identifies a typed SQL parameter. - * Created using sql.date(), sql.string(), sql.number(), sql.boolean(), sql.timestamp(), sql.binary(), or sql.interval(). - */ -export interface SQLTypeMarker { - __sql_type: - | "DATE" - | "TIMESTAMP" - | "STRING" - | "NUMERIC" - | "BOOLEAN" - | "BINARY"; - value: string; -} +import type { + SQLBinaryMarker, + SQLBooleanMarker, + SQLDateMarker, + SQLNumberMarker, + SQLStringMarker, + SQLTimestampMarker, + SQLTypeMarker, +} from "./types"; /** * SQL helper namespace @@ -33,7 +28,7 @@ export const sql = { * params = { startDate: "2024-01-01" } * ``` */ - date(value: Date | string): SQLTypeMarker { + date(value: Date | string): SQLDateMarker { let dateValue: string = ""; // check if value is a Date object @@ -84,7 +79,7 @@ export const sql = { * params = { createdTime: "2024-01-01T12:00:00Z" } * ``` */ - timestamp(value: Date | string | number): SQLTypeMarker { + timestamp(value: Date | string | number): SQLTimestampMarker { let timestampValue: string = ""; if (value instanceof Date) { @@ -129,7 +124,7 @@ export const sql = { * params = { userId: "123" } * ``` */ - number(value: number | string): SQLTypeMarker { + number(value: number | string): SQLNumberMarker { let numValue: string = ""; // check if value is a number @@ -179,7 +174,7 @@ export const sql = { * params = { name: "true" } * ``` */ - string(value: string | number | boolean): SQLTypeMarker { + string(value: string | number | boolean): SQLStringMarker { if ( typeof value !== "string" && typeof value !== "number" && @@ -236,7 +231,7 @@ export const sql = { * ``` * @returns */ - boolean(value: boolean | string | number): SQLTypeMarker { + boolean(value: boolean | string | number): SQLBooleanMarker { if ( typeof value !== "boolean" && typeof value !== "string" && @@ -298,7 +293,7 @@ export const sql = { * // Returns: { __sql_type: "STRING", value: "537061726B" } * ``` */ - binary(value: Uint8Array | ArrayBuffer | string): SQLTypeMarker { + binary(value: Uint8Array | ArrayBuffer | string): SQLBinaryMarker { let hexValue: string = ""; if (value instanceof Uint8Array) { diff --git a/packages/shared/src/sql/index.ts b/packages/shared/src/sql/index.ts index d4e09d7..872ef39 100644 --- a/packages/shared/src/sql/index.ts +++ b/packages/shared/src/sql/index.ts @@ -1 +1,2 @@ export * from "./helpers"; +export * from "./types"; diff --git a/packages/shared/src/sql/types.ts b/packages/shared/src/sql/types.ts new file mode 100644 index 0000000..e2dabcb --- /dev/null +++ b/packages/shared/src/sql/types.ts @@ -0,0 +1,42 @@ +export interface SQLStringMarker { + __sql_type: "STRING"; + value: string; +} + +export interface SQLNumberMarker { + __sql_type: "NUMERIC"; + value: string; +} + +export interface SQLBooleanMarker { + __sql_type: "BOOLEAN"; + value: string; +} + +/** SQL Binary marker is a STRING with hex encoding */ +export interface SQLBinaryMarker { + __sql_type: "STRING"; + value: string; +} + +export interface SQLDateMarker { + __sql_type: "DATE"; + value: string; +} + +export interface SQLTimestampMarker { + __sql_type: "TIMESTAMP"; + value: string; +} + +/** + * Object that identifies a typed SQL parameter. + * Created using sql.date(), sql.string(), sql.number(), sql.boolean(), sql.timestamp(), sql.binary(), or sql.interval(). + */ +export type SQLTypeMarker = + | SQLStringMarker + | SQLNumberMarker + | SQLBooleanMarker + | SQLBinaryMarker + | SQLDateMarker + | SQLTimestampMarker; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c25cbd0..1a0072f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -149,13 +149,13 @@ importers: version: 16.6.1 express: specifier: ^4.22.0 - version: 4.22.1 + version: 4.22.0 shared: specifier: workspace:* version: link:../shared vite: specifier: npm:rolldown-vite@7.1.14 - version: rolldown-vite@7.1.14(@types/node@24.7.2)(esbuild@0.25.10)(jiti@2.6.1)(tsx@4.20.6)(yaml@2.8.1) + version: rolldown-vite@7.1.14(@types/node@24.10.1)(esbuild@0.25.10)(jiti@2.6.1)(tsx@4.20.6)(yaml@2.8.1) ws: specifier: ^8.18.3 version: 8.18.3(bufferutil@4.0.9) @@ -171,7 +171,7 @@ importers: version: 8.18.1 '@vitejs/plugin-react': specifier: ^5.1.1 - version: 5.1.2(rolldown-vite@7.1.14(@types/node@24.7.2)(esbuild@0.25.10)(jiti@2.6.1)(tsx@4.20.6)(yaml@2.8.1)) + version: 5.1.1(rolldown-vite@7.1.14(@types/node@24.10.1)(esbuild@0.25.10)(jiti@2.6.1)(tsx@4.20.6)(yaml@2.8.1)) packages/app-kit-ui: dependencies: @@ -288,7 +288,7 @@ importers: version: 0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) react-day-picker: specifier: ^9.11.3 - version: 9.11.3(react@19.2.0) + version: 9.12.0(react@19.2.0) react-hook-form: specifier: ^7.68.0 version: 7.68.0(react@19.2.0) @@ -334,13 +334,23 @@ importers: version: 1.4.0 packages/shared: + dependencies: + fast-glob: + specifier: ^3.3.3 + version: 3.3.3 devDependencies: + '@types/dependency-tree': + specifier: ^8.1.4 + version: 8.1.4 '@types/express': specifier: ^4.17.21 version: 4.17.23 '@types/ws': specifier: ^8.18.1 version: 8.18.1 + dependency-tree: + specifier: ^11.2.0 + version: 11.2.0 packages: @@ -357,8 +367,8 @@ packages: resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.28.4': - resolution: {integrity: sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==} + '@babel/compat-data@7.28.5': + resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} engines: {node: '>=6.9.0'} '@babel/core@7.28.4': @@ -628,15 +638,13 @@ packages: '@date-fns/tz@1.4.1': resolution: {integrity: sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==} - '@emnapi/core@1.5.0': - resolution: {integrity: sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==} + '@dependents/detective-less@5.0.1': + resolution: {integrity: sha512-Y6+WUMsTFWE5jb20IFP4YGa5IrGY/+a/FbOSjDF/wz9gepU2hwCYSXRHP/vPwBvwcY3SVMASt4yXxbXNXigmZQ==} + engines: {node: '>=18'} '@emnapi/core@1.7.1': resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==} - '@emnapi/runtime@1.5.0': - resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==} - '@emnapi/runtime@1.7.1': resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} @@ -874,6 +882,18 @@ packages: '@napi-rs/wasm-runtime@1.1.0': resolution: {integrity: sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==} + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + '@opentelemetry/api-logs@0.208.0': resolution: {integrity: sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg==} engines: {node: '>=8.0.0'} @@ -2189,6 +2209,9 @@ packages: '@rolldown/pluginutils@1.0.0-beta.41': resolution: {integrity: sha512-ycMEPrS3StOIeb87BT3/+bu+blEtyvwQ4zmo2IcJQy0Rd1DAAhKksA0iUZ3MYSpJtjlPhg0Eo6mvVS6ggPhRbw==} + '@rolldown/pluginutils@1.0.0-beta.47': + resolution: {integrity: sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==} + '@rolldown/pluginutils@1.0.0-beta.53': resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==} @@ -2410,6 +2433,10 @@ packages: '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/dependency-tree@8.1.4': + resolution: {integrity: sha512-aZcDFIeYeluotZUI1w0D6rP967+a9paR1gimyqtr2CfKhvBwcOR2WZ+qrrnbHt0FzNct8y3j2sXRlf+G1IoUyw==} + deprecated: This is a stub types definition. dependency-tree provides its own type definitions, so you do not need this installed. + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -2446,6 +2473,9 @@ packages: '@types/node@20.19.21': resolution: {integrity: sha512-CsGG2P3I5y48RPMfprQGfy4JPRZ6csfC3ltBZSRItG3ngggmNY/qs2uZKp4p9VbrpqNNSMzUZNFZKzgOGnd/VA==} + '@types/node@24.10.1': + resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} + '@types/node@24.7.2': resolution: {integrity: sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==} @@ -2481,8 +2511,14 @@ packages: '@types/send@0.17.5': resolution: {integrity: sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==} - '@types/send@1.2.0': - resolution: {integrity: sha512-zBF6vZJn1IaMpg3xUF25VK3gd3l8zwE0ZLRX7dsQyQi+jp4E8mMDJNGDYnYse+bQhYwWERTxVwHpi3dMOq7RKQ==} + '@types/send@0.17.6': + resolution: {integrity: sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==} + + '@types/send@1.2.1': + resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} + + '@types/serve-static@1.15.10': + resolution: {integrity: sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==} '@types/serve-static@1.15.9': resolution: {integrity: sha512-dOTIuqpWLyl3BBXU3maNQsS4A3zuuoYRNIvYSxxhebPfXg2mzWQEPne/nlJ37yOse6uGgR386uTpdsx4D0QZWA==} @@ -2496,14 +2532,40 @@ packages: '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + '@typescript-eslint/project-service@8.49.0': + resolution: {integrity: sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/tsconfig-utils@8.49.0': + resolution: {integrity: sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.49.0': + resolution: {integrity: sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.49.0': + resolution: {integrity: sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.49.0': + resolution: {integrity: sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@vitejs/plugin-react@5.0.4': resolution: {integrity: sha512-La0KD0vGkVkSk6K+piWDKRUyg8Rl5iAIKRMH0vMJI0Eg47bq1eOxmoObAaQG37WMW9MSyk7Cs8EIWwJC1PtzKA==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - '@vitejs/plugin-react@5.1.2': - resolution: {integrity: sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==} + '@vitejs/plugin-react@5.1.1': + resolution: {integrity: sha512-WQfkSw0QbQ5aJ2CHYw23ZGkqnRwqKHD/KYsMeTkZzPT4Jcf0DcBxBtwMJxnu6E7oxw5+JC6ZAiePgh28uJ1HBA==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 @@ -2542,6 +2604,21 @@ packages: '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@vue/compiler-core@3.5.25': + resolution: {integrity: sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==} + + '@vue/compiler-dom@3.5.25': + resolution: {integrity: sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==} + + '@vue/compiler-sfc@3.5.25': + resolution: {integrity: sha512-PUgKp2rn8fFsI++lF2sO7gwO2d9Yj57Utr5yEsDf3GNaQcowCLKL7sf+LvVFvtJDXUp/03+dC6f2+LCv5aK1ag==} + + '@vue/compiler-ssr@3.5.25': + resolution: {integrity: sha512-ritPSKLBcParnsKYi+GNtbdbrIE1mtuFEJ4U1sWeuOMlIziK5GtOL85t5RhsNy4uWIXPgk+OUdpnXiTdzn8o3A==} + + '@vue/shared@3.5.25': + resolution: {integrity: sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==} + JSONStream@1.3.5: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} hasBin: true @@ -2599,6 +2676,9 @@ packages: resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} engines: {node: '>=14'} + app-module-path@2.2.0: + resolution: {integrity: sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ==} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -2631,14 +2711,18 @@ packages: resolution: {integrity: sha512-TH+b3Lv6pUjy/Nu0m6A2JULtdzLpmqF9x1Dhj00ZoEiML8qvVA9j1flkzTKNYgdEhWrjDwtWNpyyCUbfQe514g==} engines: {node: '>=20.19.0'} + ast-module-types@6.0.1: + resolution: {integrity: sha512-WHw67kLXYbZuHTmcdbIrVArCq5wxo6NEuj3hiYAWr8mwJeC+C2mMCIBIWCiDoCye/OF/xelc+teJ1ERoWmnEIA==} + engines: {node: '>=18'} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.8.16: - resolution: {integrity: sha512-OMu3BGQ4E7P1ErFsIPpbJh0qvDudM/UuJeHgkAvfWe+0HFJCXh+t/l8L6fVLR55RI/UbKrVLnAXZSVwd9ysWYw==} + baseline-browser-mapping@2.8.32: + resolution: {integrity: sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw==} hasBin: true bidi-js@1.0.3: @@ -2653,10 +2737,13 @@ packages: bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - body-parser@1.20.3: - resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} + body-parser@1.20.4: + resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} @@ -2664,8 +2751,8 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.26.3: - resolution: {integrity: sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==} + browserslist@4.28.0: + resolution: {integrity: sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -2699,8 +2786,8 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - caniuse-lite@1.0.30001751: - resolution: {integrity: sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==} + caniuse-lite@1.0.30001757: + resolution: {integrity: sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==} chai@5.3.3: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} @@ -2782,6 +2869,10 @@ packages: colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + commander@13.1.0: resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} engines: {node: '>=18'} @@ -2789,6 +2880,9 @@ packages: compare-func@2.0.0: resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} @@ -2813,11 +2907,11 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - cookie-signature@1.0.6: - resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + cookie-signature@1.0.7: + resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} - cookie@0.7.1: - resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} cosmiconfig-typescript-loader@6.2.0: @@ -2951,6 +3045,11 @@ packages: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} + dependency-tree@11.2.0: + resolution: {integrity: sha512-+C1H3mXhcvMCeu5i2Jpg9dc0N29TWTuT6vJD7mHLAfVmAbo9zW8NlkvQ1tYd3PDMab0IRQM0ccoyX68EZtx9xw==} + engines: {node: '>=18'} + hasBin: true + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -2970,6 +3069,49 @@ packages: detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + detective-amd@6.0.1: + resolution: {integrity: sha512-TtyZ3OhwUoEEIhTFoc1C9IyJIud3y+xYkSRjmvCt65+ycQuc3VcBrPRTMWoO/AnuCyOB8T5gky+xf7Igxtjd3g==} + engines: {node: '>=18'} + hasBin: true + + detective-cjs@6.0.1: + resolution: {integrity: sha512-tLTQsWvd2WMcmn/60T2inEJNhJoi7a//PQ7DwRKEj1yEeiQs4mrONgsUtEJKnZmrGWBBmE0kJ1vqOG/NAxwaJw==} + engines: {node: '>=18'} + + detective-es6@5.0.1: + resolution: {integrity: sha512-XusTPuewnSUdoxRSx8OOI6xIA/uld/wMQwYsouvFN2LAg7HgP06NF1lHRV3x6BZxyL2Kkoih4ewcq8hcbGtwew==} + engines: {node: '>=18'} + + detective-postcss@7.0.1: + resolution: {integrity: sha512-bEOVpHU9picRZux5XnwGsmCN4+8oZo7vSW0O0/Enq/TO5R2pIAP2279NsszpJR7ocnQt4WXU0+nnh/0JuK4KHQ==} + engines: {node: ^14.0.0 || >=16.0.0} + peerDependencies: + postcss: ^8.4.47 + + detective-sass@6.0.1: + resolution: {integrity: sha512-jSGPO8QDy7K7pztUmGC6aiHkexBQT4GIH+mBAL9ZyBmnUIOFbkfZnO8wPRRJFP/QP83irObgsZHCoDHZ173tRw==} + engines: {node: '>=18'} + + detective-scss@5.0.1: + resolution: {integrity: sha512-MAyPYRgS6DCiS6n6AoSBJXLGVOydsr9huwXORUlJ37K3YLyiN0vYHpzs3AdJOgHobBfispokoqrEon9rbmKacg==} + engines: {node: '>=18'} + + detective-stylus@5.0.1: + resolution: {integrity: sha512-Dgn0bUqdGbE3oZJ+WCKf8Dmu7VWLcmRJGc6RCzBgG31DLIyai9WAoEhYRgIHpt/BCRMrnXLbGWGPQuBUrnF0TA==} + engines: {node: '>=18'} + + detective-typescript@14.0.0: + resolution: {integrity: sha512-pgN43/80MmWVSEi5LUuiVvO/0a9ss5V7fwVfrJ4QzAQRd3cwqU1SfWGXJFcNKUqoD5cS+uIovhw5t/0rSeC5Mw==} + engines: {node: '>=18'} + peerDependencies: + typescript: ^5.4.4 + + detective-vue2@2.2.0: + resolution: {integrity: sha512-sVg/t6O2z1zna8a/UIV6xL5KUa2cMTQbdTIIvqNM0NIPswp52fe43Nwmbahzj3ww4D844u/vC2PYfiGLvD3zFA==} + engines: {node: '>=18'} + peerDependencies: + typescript: ^5.4.4 + diff@8.0.2: resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} engines: {node: '>=0.3.1'} @@ -3013,8 +3155,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.237: - resolution: {integrity: sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg==} + electron-to-chromium@1.5.262: + resolution: {integrity: sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ==} embla-carousel-react@8.6.0: resolution: {integrity: sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==} @@ -3050,6 +3192,14 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} + enhanced-resolve@5.18.3: + resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} + engines: {node: '>=10.13.0'} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + entities@6.0.1: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} @@ -3092,9 +3242,34 @@ packages: escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + etag@1.8.1: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} @@ -3117,8 +3292,8 @@ packages: resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} engines: {node: '>=12.0.0'} - express@4.22.1: - resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==} + express@4.22.0: + resolution: {integrity: sha512-c2iPh3xp5vvCLgaHK03+mWLFPhox7j1LwyxcZwFVApEv5i0X+IjPpbT50SJJwwLpdBVfp45AkK/v+AFgv/XlfQ==} engines: {node: '>= 0.10.0'} extend@3.0.2: @@ -3131,9 +3306,16 @@ packages: resolution: {integrity: sha512-/boTcHZeIAQ2r/tL11voclBHDeP9WPxLt+tyAbVSyyXuUFyh0Tne7gJZTqGbxnvj79TjLdCXLOY7UIPhyG5MTw==} engines: {node: '>=6.0.0'} + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -3147,12 +3329,17 @@ packages: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} engines: {node: ^12.20 || >= 14.13} + filing-cabinet@5.0.3: + resolution: {integrity: sha512-PlPcMwVWg60NQkhvfoxZs4wEHjhlOO/y7OAm4sKM60o1Z9nttRY4mcdQxp/iZ+kg/Vv6Hw1OAaTbYVM9DA9pYg==} + engines: {node: '>=18'} + hasBin: true + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} - finalhandler@1.3.1: - resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} + finalhandler@1.3.2: + resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==} engines: {node: '>= 0.8'} find-up@7.0.0: @@ -3198,6 +3385,9 @@ packages: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -3226,6 +3416,10 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + get-amd-module-type@6.0.1: + resolution: {integrity: sha512-MtjsmYiCXcYDDrGqtNbeIYdAl85n+5mSv2r3FbzER/YV3ZILw4HNNIw34HuV5pyl0jzs6GFYU1VHVEefhgcNHQ==} + engines: {node: '>=18'} + get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} @@ -3242,6 +3436,9 @@ packages: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} + get-own-enumerable-property-symbols@3.0.2: + resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} + get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} @@ -3258,10 +3455,22 @@ packages: engines: {node: '>=16'} hasBin: true + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + hasBin: true + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + global-directory@4.0.1: resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} engines: {node: '>=18'} @@ -3277,6 +3486,11 @@ packages: globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + gonzales-pe@4.3.0: + resolution: {integrity: sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==} + engines: {node: '>=0.6.0'} + hasBin: true + google-auth-library@10.5.0: resolution: {integrity: sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==} engines: {node: '>=18'} @@ -3293,6 +3507,9 @@ packages: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + gtoken@8.0.0: resolution: {integrity: sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==} engines: {node: '>=18'} @@ -3332,6 +3549,10 @@ packages: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} @@ -3374,6 +3595,10 @@ packages: import-meta-resolve@4.2.0: resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -3449,6 +3674,10 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-obj@1.0.1: + resolution: {integrity: sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==} + engines: {node: '>=0.10.0'} + is-obj@2.0.0: resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} engines: {node: '>=8'} @@ -3460,6 +3689,10 @@ packages: is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-regexp@1.0.0: + resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==} + engines: {node: '>=0.10.0'} + is-relative@1.0.0: resolution: {integrity: sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==} engines: {node: '>=0.10.0'} @@ -3484,6 +3717,13 @@ packages: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} + is-url-superb@4.0.0: + resolution: {integrity: sha512-GI+WjezhPPcbM+tqE9LnmsY5qqjwHzTvjJ36wxYX5ujNXefSUJ/T17r5bqDV8yLhcgB59KTPNOc9O9cmHTPWsA==} + engines: {node: '>=10'} + + is-url@1.2.4: + resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==} + is-windows@1.0.2: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} @@ -3738,6 +3978,9 @@ packages: magic-string@0.30.19: resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + magicast@0.3.5: resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} @@ -3770,6 +4013,10 @@ packages: merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} @@ -3803,6 +4050,9 @@ packages: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} @@ -3814,9 +4064,19 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + module-definition@6.0.1: + resolution: {integrity: sha512-FeVc50FTfVVQnolk/WQT8MX+2WVcDnTGiq6Wo+/+lJ2ET1bRVi3HG3YlJUfqagNMc/kUlFSoR96AJkxGpKz13g==} + engines: {node: '>=18'} + hasBin: true + module-details-from-path@1.0.4: resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} + module-lookup-amd@9.0.5: + resolution: {integrity: sha512-Rs5FVpVcBYRHPLuhHOjgbRhosaQYLtEo3JIeDIbmNo7mSssi1CTzwMh8v36gAzpbzLGXI9wB/yHh+5+3fY1QVw==} + engines: {node: '>=18'} + hasBin: true + mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -3878,8 +4138,12 @@ packages: resolution: {integrity: sha512-tn+OxutdqhvoByKJ7p84FZBSUDfUB76bcvj0ugLBvgE9V52LFcnz8cauCDKi6otnctvFCqa9XkrU35pBY5Baig==} engines: {node: '>=18'} - node-releases@2.0.25: - resolution: {integrity: sha512-4auku8B/vw5psvTiiN9j1dAOsXvMoGqJuKJcR+dTdqiXEK20mMTk1UEo3HS16LeGQsVG6+qKTPM9u/qQ2LqATA==} + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + + node-source-walk@7.0.1: + resolution: {integrity: sha512-3VW/8JpPqPvnJvseXowjZcirPisssnBuDikk6JIZ8jQzF7KJQX52iPFX4RYYxLycYH7IbMRSPUOga/esVjy5Yg==} + engines: {node: '>=18'} npm-run-path@5.3.0: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} @@ -3905,6 +4169,9 @@ packages: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} @@ -3962,6 +4229,10 @@ packages: resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -4027,6 +4298,12 @@ packages: engines: {node: '>=18'} hasBin: true + postcss-values-parser@6.0.2: + resolution: {integrity: sha512-YLJpK0N1brcNJrs9WatuJFtHaV9q5aAOj+S4DI5S7jgHlRfm0PIbDCAFRYMQD5SHq7Fy6xsDhyutgS0QOAs0qw==} + engines: {node: '>=10'} + peerDependencies: + postcss: ^8.2.9 + postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} @@ -4047,6 +4324,11 @@ packages: resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} engines: {node: '>=0.10.0'} + precinct@12.2.0: + resolution: {integrity: sha512-NFBMuwIfaJ4SocE9YXPU/n4AcNSoFMVFjP72nvl3cx69j/ke61/hPOWFREVxLkFhhEGnA8ZuVfTqJBa+PK3b5w==} + engines: {node: '>=18'} + hasBin: true + pretty-format@27.5.1: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -4071,10 +4353,6 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - qs@6.13.0: - resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} - engines: {node: '>=0.6'} - qs@6.14.0: resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} engines: {node: '>=0.6'} @@ -4082,16 +4360,22 @@ packages: quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + quote-unquote@1.0.0: + resolution: {integrity: sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg==} + range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} - raw-body@2.5.2: - resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + raw-body@2.5.3: + resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} engines: {node: '>= 0.8'} - react-day-picker@9.11.3: - resolution: {integrity: sha512-7lD12UvGbkyXqgzbYIGQTbl+x29B9bAf+k0pP5Dcs1evfpKk6zv4EdH/edNc8NxcmCiTNXr2HIYPrSZ3XvmVBg==} + react-day-picker@9.12.0: + resolution: {integrity: sha512-t8OvG/Zrciso5CQJu5b1A7yzEmebvST+S3pOVQJWxwjjVngyG/CA2htN/D15dLI4uTEuLLkbZyS4YYt480FAtA==} engines: {node: '>=18'} peerDependencies: react: '>=16.8.0' @@ -4213,6 +4497,19 @@ packages: resolution: {integrity: sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==} engines: {node: '>=9.3.0 || >=8.10.0 <9.0.0'} + requirejs-config-file@4.0.0: + resolution: {integrity: sha512-jnIre8cbWOyvr8a5F2KuqBnY+SDA4NXr/hzEZJG79Mxm2WiFQz2dzhC8ibtPJS7zkmBEl1mxSwp5HhC1W4qpxw==} + engines: {node: '>=10.13.0'} + + requirejs@2.3.8: + resolution: {integrity: sha512-7/cTSLOdYkNBNJcDMWf+luFvMriVm7eYxp4BcFCsAX0wF421Vyce5SXP17c+Jd5otXKGNehIonFlyQXSowL6Mw==} + engines: {node: '>=0.4.0'} + hasBin: true + + resolve-dependency-path@4.0.1: + resolution: {integrity: sha512-YQftIIC4vzO9UMhO/sCgXukNyiwVRCVaxiWskCBy7Zpqkplm8kTAISZ8O1MoKW1ca6xzgLUBjZTcDgypXvXxiQ==} + engines: {node: '>=18'} + resolve-dir@1.0.1: resolution: {integrity: sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==} engines: {node: '>=0.10.0'} @@ -4241,6 +4538,10 @@ packages: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} @@ -4329,6 +4630,9 @@ packages: resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} engines: {node: '>=0.12.0'} + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} @@ -4342,6 +4646,11 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + sass-lookup@6.1.0: + resolution: {integrity: sha512-Zx+lVyoWqXZxHuYWlTA17Z5sczJ6braNT2C7rmClw+c4E7r/n911Zwss3h1uHI9reR5AgHZyNHF7c2+VIp5AUA==} + engines: {node: '>=18'} + hasBin: true + saxes@6.0.0: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} @@ -4362,6 +4671,10 @@ packages: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} engines: {node: '>= 0.8.0'} + send@0.19.1: + resolution: {integrity: sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg==} + engines: {node: '>= 0.8.0'} + serve-static@1.16.2: resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} engines: {node: '>= 0.8.0'} @@ -4436,6 +4749,10 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} @@ -4458,6 +4775,10 @@ packages: string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + stringify-object@3.3.0: + resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==} + engines: {node: '>=4'} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -4466,6 +4787,10 @@ packages: resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} engines: {node: '>=12'} + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + strip-final-newline@3.0.0: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} @@ -4473,6 +4798,11 @@ packages: strip-literal@3.1.0: resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + stylus-lookup@6.1.0: + resolution: {integrity: sha512-5QSwgxAzXPMN+yugy61C60PhoANdItfdjSEZR8siFwz7yL9jTmV0UBKDCfn3K8GkGB4g0Y9py7vTCX8rFu4/pQ==} + engines: {node: '>=18'} + hasBin: true + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -4490,6 +4820,10 @@ packages: tailwindcss@4.1.17: resolution: {integrity: sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==} + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: '>=6'} + test-exclude@7.0.1: resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} engines: {node: '>=18'} @@ -4562,6 +4896,12 @@ packages: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + tsconfck@3.1.6: resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} engines: {node: ^18 || >=20} @@ -4572,6 +4912,10 @@ packages: typescript: optional: true + tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} + tsdown@0.15.7: resolution: {integrity: sha512-uFaVgWAogjOMqjY+CQwrUt3C6wzy6ynt82CIoXymnbS17ipUZ8WDXUceJjkislUahF/BZc5+W44Ue3p2oWtqUg==} engines: {node: '>=20.19.0'} @@ -4670,6 +5014,9 @@ packages: undici-types@7.14.0: resolution: {integrity: sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==} + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + unicorn-magic@0.1.0: resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} engines: {node: '>=18'} @@ -4678,8 +5025,8 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} - update-browserslist-db@1.1.3: - resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + update-browserslist-db@1.1.4: + resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -4884,6 +5231,9 @@ packages: resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} engines: {node: '>=18'} + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@8.18.3: resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} engines: {node: '>=10.0.0'} @@ -4970,7 +5320,7 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.28.4': {} + '@babel/compat-data@7.28.5': {} '@babel/core@7.28.4': dependencies: @@ -5030,9 +5380,9 @@ snapshots: '@babel/helper-compilation-targets@7.27.2': dependencies: - '@babel/compat-data': 7.28.4 + '@babel/compat-data': 7.28.5 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.26.3 + browserslist: 4.28.0 lru-cache: 5.1.1 semver: 6.3.1 @@ -5040,8 +5390,8 @@ snapshots: '@babel/helper-module-imports@7.27.1': dependencies: - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color @@ -5049,8 +5399,8 @@ snapshots: dependencies: '@babel/core': 7.28.4 '@babel/helper-module-imports': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -5058,8 +5408,8 @@ snapshots: dependencies: '@babel/core': 7.28.5 '@babel/helper-module-imports': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -5076,7 +5426,7 @@ snapshots: '@babel/helpers@7.28.4': dependencies: '@babel/template': 7.27.2 - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 '@babel/parser@7.28.4': dependencies: @@ -5111,8 +5461,8 @@ snapshots: '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 '@babel/traverse@7.28.4': dependencies: @@ -5328,11 +5678,10 @@ snapshots: '@date-fns/tz@1.4.1': {} - '@emnapi/core@1.5.0': + '@dependents/detective-less@5.0.1': dependencies: - '@emnapi/wasi-threads': 1.1.0 - tslib: 2.8.1 - optional: true + gonzales-pe: 4.3.0 + node-source-walk: 7.0.1 '@emnapi/core@1.7.1': dependencies: @@ -5340,11 +5689,6 @@ snapshots: tslib: 2.8.1 optional: true - '@emnapi/runtime@1.5.0': - dependencies: - tslib: 2.8.1 - optional: true - '@emnapi/runtime@1.7.1': dependencies: tslib: 2.8.1 @@ -5510,8 +5854,8 @@ snapshots: '@napi-rs/wasm-runtime@1.0.7': dependencies: - '@emnapi/core': 1.5.0 - '@emnapi/runtime': 1.5.0 + '@emnapi/core': 1.7.1 + '@emnapi/runtime': 1.7.1 '@tybys/wasm-util': 0.10.1 optional: true @@ -5522,6 +5866,18 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + '@opentelemetry/api-logs@0.208.0': dependencies: '@opentelemetry/api': 1.9.0 @@ -6992,6 +7348,8 @@ snapshots: '@rolldown/pluginutils@1.0.0-beta.41': {} + '@rolldown/pluginutils@1.0.0-beta.47': {} + '@rolldown/pluginutils@1.0.0-beta.53': {} '@rollup/rollup-android-arm-eabi@4.52.4': @@ -7110,33 +7468,33 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.28.0 '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 '@types/babel__traverse@7.28.0': dependencies: - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 '@types/body-parser@1.19.6': dependencies: '@types/connect': 3.4.38 - '@types/node': 24.7.2 + '@types/node': 24.10.1 '@types/bunyan@1.8.11': dependencies: - '@types/node': 24.7.2 + '@types/node': 24.10.1 '@types/chai@5.2.2': dependencies: @@ -7144,7 +7502,7 @@ snapshots: '@types/connect@3.4.38': dependencies: - '@types/node': 24.7.2 + '@types/node': 24.10.1 '@types/conventional-commits-parser@5.0.1': dependencies: @@ -7176,14 +7534,20 @@ snapshots: '@types/deep-eql@4.0.2': {} + '@types/dependency-tree@8.1.4': + dependencies: + dependency-tree: 11.2.0 + transitivePeerDependencies: + - supports-color + '@types/estree@1.0.8': {} '@types/express-serve-static-core@4.19.7': dependencies: - '@types/node': 24.7.2 + '@types/node': 24.10.1 '@types/qs': 6.14.0 '@types/range-parser': 1.2.7 - '@types/send': 1.2.0 + '@types/send': 1.2.1 '@types/express@4.17.23': dependencies: @@ -7197,7 +7561,7 @@ snapshots: '@types/body-parser': 1.19.6 '@types/express-serve-static-core': 4.19.7 '@types/qs': 6.14.0 - '@types/serve-static': 1.15.9 + '@types/serve-static': 1.15.10 '@types/fined@1.1.5': {} @@ -7215,25 +7579,29 @@ snapshots: '@types/memcached@2.2.10': dependencies: - '@types/node': 24.7.2 + '@types/node': 24.10.1 '@types/mime@1.3.5': {} '@types/mysql@2.15.27': dependencies: - '@types/node': 24.7.2 + '@types/node': 24.10.1 '@types/node@20.19.21': dependencies: undici-types: 6.21.0 + '@types/node@24.10.1': + dependencies: + undici-types: 7.16.0 + '@types/node@24.7.2': dependencies: undici-types: 7.14.0 '@types/oracledb@6.5.2': dependencies: - '@types/node': 24.7.2 + '@types/node': 24.10.1 '@types/pg-pool@2.0.6': dependencies: @@ -7241,7 +7609,7 @@ snapshots: '@types/pg@8.15.6': dependencies: - '@types/node': 24.7.2 + '@types/node': 24.10.1 pg-protocol: 1.10.3 pg-types: 2.2.0 @@ -7266,21 +7634,32 @@ snapshots: '@types/send@0.17.5': dependencies: '@types/mime': 1.3.5 - '@types/node': 24.7.2 + '@types/node': 24.10.1 - '@types/send@1.2.0': + '@types/send@0.17.6': dependencies: - '@types/node': 24.7.2 + '@types/mime': 1.3.5 + '@types/node': 24.10.1 + + '@types/send@1.2.1': + dependencies: + '@types/node': 24.10.1 + + '@types/serve-static@1.15.10': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 24.10.1 + '@types/send': 0.17.6 '@types/serve-static@1.15.9': dependencies: '@types/http-errors': 2.0.5 - '@types/node': 24.7.2 + '@types/node': 24.10.1 '@types/send': 0.17.5 '@types/tedious@4.0.14': dependencies: - '@types/node': 24.7.2 + '@types/node': 24.10.1 '@types/through@0.0.33': dependencies: @@ -7288,7 +7667,42 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 24.7.2 + '@types/node': 24.10.1 + + '@typescript-eslint/project-service@8.49.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) + '@typescript-eslint/types': 8.49.0 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/tsconfig-utils@8.49.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/types@8.49.0': {} + + '@typescript-eslint/typescript-estree@8.49.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.49.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/visitor-keys': 8.49.0 + debug: 4.4.3 + minimatch: 9.0.5 + semver: 7.7.3 + tinyglobby: 0.2.15 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.49.0': + dependencies: + '@typescript-eslint/types': 8.49.0 + eslint-visitor-keys: 4.2.1 '@vitejs/plugin-react@5.0.4(rolldown-vite@7.1.14(@types/node@20.19.21)(esbuild@0.25.10)(jiti@2.6.1)(tsx@4.20.6)(yaml@2.8.1))': dependencies: @@ -7314,15 +7728,15 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitejs/plugin-react@5.1.2(rolldown-vite@7.1.14(@types/node@24.7.2)(esbuild@0.25.10)(jiti@2.6.1)(tsx@4.20.6)(yaml@2.8.1))': + '@vitejs/plugin-react@5.1.1(rolldown-vite@7.1.14(@types/node@24.10.1)(esbuild@0.25.10)(jiti@2.6.1)(tsx@4.20.6)(yaml@2.8.1))': dependencies: '@babel/core': 7.28.5 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.5) - '@rolldown/pluginutils': 1.0.0-beta.53 + '@rolldown/pluginutils': 1.0.0-beta.47 '@types/babel__core': 7.20.5 react-refresh: 0.18.0 - vite: rolldown-vite@7.1.14(@types/node@24.7.2)(esbuild@0.25.10)(jiti@2.6.1)(tsx@4.20.6)(yaml@2.8.1) + vite: rolldown-vite@7.1.14(@types/node@24.10.1)(esbuild@0.25.10)(jiti@2.6.1)(tsx@4.20.6)(yaml@2.8.1) transitivePeerDependencies: - supports-color @@ -7384,6 +7798,38 @@ snapshots: loupe: 3.2.1 tinyrainbow: 2.0.0 + '@vue/compiler-core@3.5.25': + dependencies: + '@babel/parser': 7.28.5 + '@vue/shared': 3.5.25 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.25': + dependencies: + '@vue/compiler-core': 3.5.25 + '@vue/shared': 3.5.25 + + '@vue/compiler-sfc@3.5.25': + dependencies: + '@babel/parser': 7.28.5 + '@vue/compiler-core': 3.5.25 + '@vue/compiler-dom': 3.5.25 + '@vue/compiler-ssr': 3.5.25 + '@vue/shared': 3.5.25 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.6 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.25': + dependencies: + '@vue/compiler-dom': 3.5.25 + '@vue/shared': 3.5.25 + + '@vue/shared@3.5.25': {} + JSONStream@1.3.5: dependencies: jsonparse: 1.3.1 @@ -7431,6 +7877,8 @@ snapshots: ansis@4.2.0: {} + app-module-path@2.2.0: {} + argparse@2.0.1: {} aria-hidden@1.2.6: @@ -7456,11 +7904,13 @@ snapshots: '@babel/parser': 7.28.4 pathe: 2.0.3 + ast-module-types@6.0.1: {} + balanced-match@1.0.2: {} base64-js@1.5.1: {} - baseline-browser-mapping@2.8.16: {} + baseline-browser-mapping@2.8.32: {} bidi-js@1.0.3: dependencies: @@ -7476,23 +7926,28 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - body-parser@1.20.3: + body-parser@1.20.4: dependencies: bytes: 3.1.2 content-type: 1.0.5 debug: 2.6.9 depd: 2.0.0 destroy: 1.2.0 - http-errors: 2.0.0 + http-errors: 2.0.1 iconv-lite: 0.4.24 on-finished: 2.4.1 - qs: 6.13.0 - raw-body: 2.5.2 + qs: 6.14.0 + raw-body: 2.5.3 type-is: 1.6.18 unpipe: 1.0.0 transitivePeerDependencies: - supports-color + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + brace-expansion@2.0.2: dependencies: balanced-match: 1.0.2 @@ -7501,13 +7956,13 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.26.3: + browserslist@4.28.0: dependencies: - baseline-browser-mapping: 2.8.16 - caniuse-lite: 1.0.30001751 - electron-to-chromium: 1.5.237 - node-releases: 2.0.25 - update-browserslist-db: 1.1.3(browserslist@4.26.3) + baseline-browser-mapping: 2.8.32 + caniuse-lite: 1.0.30001757 + electron-to-chromium: 1.5.262 + node-releases: 2.0.27 + update-browserslist-db: 1.1.4(browserslist@4.28.0) buffer-equal-constant-time@1.0.1: {} @@ -7537,7 +7992,7 @@ snapshots: callsites@3.1.0: {} - caniuse-lite@1.0.30001751: {} + caniuse-lite@1.0.30001757: {} chai@5.3.3: dependencies: @@ -7617,6 +8072,8 @@ snapshots: colorette@2.0.20: {} + commander@12.1.0: {} + commander@13.1.0: {} compare-func@2.0.0: @@ -7624,6 +8081,8 @@ snapshots: array-ify: 1.0.0 dot-prop: 5.3.0 + concat-map@0.0.1: {} + content-disposition@0.5.4: dependencies: safe-buffer: 5.2.1 @@ -7647,9 +8106,9 @@ snapshots: convert-source-map@2.0.0: {} - cookie-signature@1.0.6: {} + cookie-signature@1.0.7: {} - cookie@0.7.1: {} + cookie@0.7.2: {} cosmiconfig-typescript-loader@6.2.0(@types/node@24.7.2)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3): dependencies: @@ -7761,6 +8220,15 @@ snapshots: depd@2.0.0: {} + dependency-tree@11.2.0: + dependencies: + commander: 12.1.0 + filing-cabinet: 5.0.3 + precinct: 12.2.0 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + dequal@2.0.3: {} destroy@1.2.0: {} @@ -7771,6 +8239,62 @@ snapshots: detect-node-es@1.1.0: {} + detective-amd@6.0.1: + dependencies: + ast-module-types: 6.0.1 + escodegen: 2.1.0 + get-amd-module-type: 6.0.1 + node-source-walk: 7.0.1 + + detective-cjs@6.0.1: + dependencies: + ast-module-types: 6.0.1 + node-source-walk: 7.0.1 + + detective-es6@5.0.1: + dependencies: + node-source-walk: 7.0.1 + + detective-postcss@7.0.1(postcss@8.5.6): + dependencies: + is-url: 1.2.4 + postcss: 8.5.6 + postcss-values-parser: 6.0.2(postcss@8.5.6) + + detective-sass@6.0.1: + dependencies: + gonzales-pe: 4.3.0 + node-source-walk: 7.0.1 + + detective-scss@5.0.1: + dependencies: + gonzales-pe: 4.3.0 + node-source-walk: 7.0.1 + + detective-stylus@5.0.1: {} + + detective-typescript@14.0.0(typescript@5.9.3): + dependencies: + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) + ast-module-types: 6.0.1 + node-source-walk: 7.0.1 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + detective-vue2@2.2.0(typescript@5.9.3): + dependencies: + '@dependents/detective-less': 5.0.1 + '@vue/compiler-sfc': 3.5.25 + detective-es6: 5.0.1 + detective-sass: 6.0.1 + detective-scss: 5.0.1 + detective-stylus: 5.0.1 + detective-typescript: 14.0.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + diff@8.0.2: {} dlv@1.1.3: {} @@ -7804,7 +8328,7 @@ snapshots: ee-first@1.1.1: {} - electron-to-chromium@1.5.237: {} + electron-to-chromium@1.5.262: {} embla-carousel-react@8.6.0(react@19.2.0): dependencies: @@ -7830,6 +8354,13 @@ snapshots: encodeurl@2.0.0: {} + enhanced-resolve@5.18.3: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 + + entities@4.5.0: {} + entities@6.0.1: {} env-paths@2.2.1: {} @@ -7883,10 +8414,28 @@ snapshots: escape-html@1.0.3: {} + escodegen@2.1.0: + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 + + eslint-visitor-keys@4.2.1: {} + + esprima@4.0.1: {} + + estraverse@5.3.0: {} + + estree-walker@2.0.2: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.8 + esutils@2.0.3: {} + etag@1.8.1: {} eventemitter3@4.0.7: {} @@ -7911,23 +8460,23 @@ snapshots: expect-type@1.2.2: {} - express@4.22.1: + express@4.22.0: dependencies: accepts: 1.3.8 array-flatten: 1.1.1 - body-parser: 1.20.3 + body-parser: 1.20.4 content-disposition: 0.5.4 content-type: 1.0.5 - cookie: 0.7.1 - cookie-signature: 1.0.6 + cookie: 0.7.2 + cookie-signature: 1.0.7 debug: 2.6.9 depd: 2.0.0 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - finalhandler: 1.3.1 + finalhandler: 1.3.2 fresh: 0.5.2 - http-errors: 2.0.0 + http-errors: 2.0.1 merge-descriptors: 1.0.3 methods: 1.1.2 on-finished: 2.4.1 @@ -7937,10 +8486,10 @@ snapshots: qs: 6.14.0 range-parser: 1.2.1 safe-buffer: 5.2.1 - send: 0.19.0 + send: 0.19.1 serve-static: 1.16.2 setprototypeof: 1.2.0 - statuses: 2.0.1 + statuses: 2.0.2 type-is: 1.6.18 utils-merge: 1.0.1 vary: 1.1.2 @@ -7953,8 +8502,20 @@ snapshots: fast-equals@5.3.3: {} + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + fast-uri@3.1.0: {} + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -7964,18 +8525,32 @@ snapshots: node-domexception: 1.0.0 web-streams-polyfill: 3.3.3 + filing-cabinet@5.0.3: + dependencies: + app-module-path: 2.2.0 + commander: 12.1.0 + enhanced-resolve: 5.18.3 + module-definition: 6.0.1 + module-lookup-amd: 9.0.5 + resolve: 1.22.10 + resolve-dependency-path: 4.0.1 + sass-lookup: 6.1.0 + stylus-lookup: 6.1.0 + tsconfig-paths: 4.2.0 + typescript: 5.9.3 + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 - finalhandler@1.3.1: + finalhandler@1.3.2: dependencies: debug: 2.6.9 encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 parseurl: 1.3.3 - statuses: 2.0.1 + statuses: 2.0.2 unpipe: 1.0.0 transitivePeerDependencies: - supports-color @@ -8024,6 +8599,8 @@ snapshots: fresh@0.5.2: {} + fs.realpath@1.0.0: {} + fsevents@2.3.3: optional: true @@ -8068,6 +8645,11 @@ snapshots: gensync@1.0.0-beta.2: {} + get-amd-module-type@6.0.1: + dependencies: + ast-module-types: 6.0.1 + node-source-walk: 7.0.1 + get-caller-file@2.0.5: {} get-east-asian-width@1.4.0: {} @@ -8087,6 +8669,8 @@ snapshots: get-nonce@1.0.1: {} + get-own-enumerable-property-symbols@3.0.2: {} + get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 @@ -8104,6 +8688,10 @@ snapshots: meow: 12.1.1 split2: 4.2.0 + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + glob@10.4.5: dependencies: foreground-child: 3.3.1 @@ -8113,6 +8701,24 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + global-directory@4.0.1: dependencies: ini: 4.1.1 @@ -8133,6 +8739,10 @@ snapshots: globrex@0.1.2: {} + gonzales-pe@4.3.0: + dependencies: + minimist: 1.2.8 + google-auth-library@10.5.0: dependencies: base64-js: 1.5.1 @@ -8151,6 +8761,8 @@ snapshots: gopd@1.2.0: {} + graceful-fs@4.2.11: {} + gtoken@8.0.0: dependencies: gaxios: 7.1.3 @@ -8195,6 +8807,14 @@ snapshots: statuses: 2.0.1 toidentifier: 1.0.1 + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.4 @@ -8241,6 +8861,11 @@ snapshots: import-meta-resolve@4.2.0: {} + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + inherits@2.0.4: {} ini@1.3.8: {} @@ -8306,12 +8931,16 @@ snapshots: is-number@7.0.0: {} + is-obj@1.0.1: {} + is-obj@2.0.0: {} is-plain-object@5.0.0: {} is-potential-custom-element-name@1.0.1: {} + is-regexp@1.0.0: {} + is-relative@1.0.0: dependencies: is-unc-path: 1.0.0 @@ -8330,6 +8959,10 @@ snapshots: is-unicode-supported@0.1.0: {} + is-url-superb@4.0.0: {} + + is-url@1.2.4: {} + is-windows@1.0.2: {} isbinaryfile@5.0.6: {} @@ -8342,7 +8975,7 @@ snapshots: istanbul-lib-instrument@6.0.3: dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 '@babel/parser': 7.28.4 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 @@ -8588,6 +9221,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + magicast@0.3.5: dependencies: '@babel/parser': 7.28.4 @@ -8612,6 +9249,8 @@ snapshots: merge-stream@2.0.0: {} + merge2@1.4.1: {} + methods@1.1.2: {} micromatch@4.0.8: @@ -8633,6 +9272,10 @@ snapshots: mimic-function@5.0.1: {} + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + minimatch@9.0.5: dependencies: brace-expansion: 2.0.2 @@ -8641,8 +9284,20 @@ snapshots: minipass@7.1.2: {} + module-definition@6.0.1: + dependencies: + ast-module-types: 6.0.1 + node-source-walk: 7.0.1 + module-details-from-path@1.0.4: {} + module-lookup-amd@9.0.5: + dependencies: + commander: 12.1.0 + glob: 7.2.3 + requirejs: 2.3.8 + requirejs-config-file: 4.0.0 + mri@1.2.0: {} ms@2.0.0: {} @@ -8696,7 +9351,11 @@ snapshots: transitivePeerDependencies: - '@types/node' - node-releases@2.0.25: {} + node-releases@2.0.27: {} + + node-source-walk@7.0.1: + dependencies: + '@babel/parser': 7.28.5 npm-run-path@5.3.0: dependencies: @@ -8721,6 +9380,10 @@ snapshots: dependencies: ee-first: 1.1.1 + once@1.4.0: + dependencies: + wrappy: 1.0.2 + onetime@5.1.2: dependencies: mimic-fn: 2.1.0 @@ -8784,6 +9447,8 @@ snapshots: path-exists@5.0.0: {} + path-is-absolute@1.0.1: {} + path-key@3.1.1: {} path-key@4.0.0: {} @@ -8840,6 +9505,13 @@ snapshots: transitivePeerDependencies: - '@types/node' + postcss-values-parser@6.0.2(postcss@8.5.6): + dependencies: + color-name: 1.1.4 + is-url-superb: 4.0.0 + postcss: 8.5.6 + quote-unquote: 1.0.0 + postcss@8.5.6: dependencies: nanoid: 3.3.11 @@ -8856,6 +9528,26 @@ snapshots: dependencies: xtend: 4.0.2 + precinct@12.2.0: + dependencies: + '@dependents/detective-less': 5.0.1 + commander: 12.1.0 + detective-amd: 6.0.1 + detective-cjs: 6.0.1 + detective-es6: 5.0.1 + detective-postcss: 7.0.1(postcss@8.5.6) + detective-sass: 6.0.1 + detective-scss: 5.0.1 + detective-stylus: 5.0.1 + detective-typescript: 14.0.0(typescript@5.9.3) + detective-vue2: 2.2.0(typescript@5.9.3) + module-definition: 6.0.1 + node-source-walk: 7.0.1 + postcss: 8.5.6 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + pretty-format@27.5.1: dependencies: ansi-regex: 5.0.1 @@ -8880,7 +9572,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 24.7.2 + '@types/node': 24.10.1 long: 5.3.2 proxy-addr@2.0.7: @@ -8897,26 +9589,26 @@ snapshots: punycode@2.3.1: {} - qs@6.13.0: - dependencies: - side-channel: 1.1.0 - qs@6.14.0: dependencies: side-channel: 1.1.0 quansync@0.2.11: {} + queue-microtask@1.2.3: {} + + quote-unquote@1.0.0: {} + range-parser@1.2.1: {} - raw-body@2.5.2: + raw-body@2.5.3: dependencies: bytes: 3.1.2 - http-errors: 2.0.0 + http-errors: 2.0.1 iconv-lite: 0.4.24 unpipe: 1.0.0 - react-day-picker@9.11.3(react@19.2.0): + react-day-picker@9.12.0(react@19.2.0): dependencies: '@date-fns/tz': 1.4.1 date-fns: 4.1.0 @@ -9035,6 +9727,15 @@ snapshots: transitivePeerDependencies: - supports-color + requirejs-config-file@4.0.0: + dependencies: + esprima: 4.0.1 + stringify-object: 3.3.0 + + requirejs@2.3.8: {} + + resolve-dependency-path@4.0.1: {} + resolve-dir@1.0.1: dependencies: expand-tilde: 2.0.2 @@ -9062,11 +9763,13 @@ snapshots: onetime: 7.0.0 signal-exit: 4.1.0 + reusify@1.1.0: {} + rfdc@1.4.1: {} rimraf@5.0.10: dependencies: - glob: 10.4.5 + glob: 10.5.0 rolldown-plugin-dts@0.16.11(rolldown@1.0.0-beta.53)(typescript@5.9.3): dependencies: @@ -9103,7 +9806,7 @@ snapshots: tsx: 4.20.6 yaml: 2.8.1 - rolldown-vite@7.1.14(@types/node@24.7.2)(esbuild@0.25.10)(jiti@2.6.1)(tsx@4.20.6)(yaml@2.8.1): + rolldown-vite@7.1.14(@types/node@24.10.1)(esbuild@0.25.10)(jiti@2.6.1)(tsx@4.20.6)(yaml@2.8.1): dependencies: '@oxc-project/runtime': 0.92.0 fdir: 6.5.0(picomatch@4.0.3) @@ -9113,7 +9816,7 @@ snapshots: rolldown: 1.0.0-beta.41 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 24.7.2 + '@types/node': 24.10.1 esbuild: 0.25.10 fsevents: 2.3.3 jiti: 2.6.1 @@ -9192,6 +9895,10 @@ snapshots: run-async@3.0.0: {} + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + rxjs@7.8.2: dependencies: tslib: 2.8.1 @@ -9204,6 +9911,11 @@ snapshots: safer-buffer@2.1.2: {} + sass-lookup@6.1.0: + dependencies: + commander: 12.1.0 + enhanced-resolve: 5.18.3 + saxes@6.0.0: dependencies: xmlchars: 2.2.0 @@ -9232,6 +9944,24 @@ snapshots: transitivePeerDependencies: - supports-color + send@0.19.1: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + serve-static@1.16.2: dependencies: encodeurl: 2.0.0 @@ -9308,6 +10038,8 @@ snapshots: statuses@2.0.1: {} + statuses@2.0.2: {} + std-env@3.10.0: {} string-argv@0.3.2: {} @@ -9334,6 +10066,12 @@ snapshots: dependencies: safe-buffer: 5.2.1 + stringify-object@3.3.0: + dependencies: + get-own-enumerable-property-symbols: 3.0.2 + is-obj: 1.0.1 + is-regexp: 1.0.0 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -9342,12 +10080,18 @@ snapshots: dependencies: ansi-regex: 6.2.2 + strip-bom@3.0.0: {} + strip-final-newline@3.0.0: {} strip-literal@3.1.0: dependencies: js-tokens: 9.0.1 + stylus-lookup@6.1.0: + dependencies: + commander: 12.1.0 + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -9360,6 +10104,8 @@ snapshots: tailwindcss@4.1.17: {} + tapable@2.3.0: {} + test-exclude@7.0.1: dependencies: '@istanbuljs/schema': 0.1.3 @@ -9415,10 +10161,20 @@ snapshots: tree-kill@1.2.2: {} + ts-api-utils@2.1.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + tsconfck@3.1.6(typescript@5.9.3): optionalDependencies: typescript: 5.9.3 + tsconfig-paths@4.2.0: + dependencies: + json5: 2.2.3 + minimist: 1.2.8 + strip-bom: 3.0.0 + tsdown@0.15.7(publint@0.3.15)(typescript@5.9.3): dependencies: ansis: 4.2.0 @@ -9508,13 +10264,15 @@ snapshots: undici-types@7.14.0: {} + undici-types@7.16.0: {} + unicorn-magic@0.1.0: {} unpipe@1.0.0: {} - update-browserslist-db@1.1.3(browserslist@4.26.3): + update-browserslist-db@1.1.4(browserslist@4.28.0): dependencies: - browserslist: 4.26.3 + browserslist: 4.28.0 escalade: 3.2.0 picocolors: 1.1.1 @@ -9732,6 +10490,8 @@ snapshots: string-width: 7.2.0 strip-ansi: 7.1.2 + wrappy@1.0.2: {} + ws@8.18.3(bufferutil@4.0.9): optionalDependencies: bufferutil: 4.0.9