diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 971a355..47ad0ea 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -63,4 +63,4 @@ jobs: - uses: actions/checkout@v4 - name: 🔍 Spell Check Repo - uses: crate-ci/typos@v1.34.0 + uses: crate-ci/typos@v1.35.2 diff --git a/.gitignore b/.gitignore index 8d626dc..e517eaf 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ scripts/**.js dist .DS_Store !.vscode +__marimo__ \ No newline at end of file diff --git a/_typos.toml b/_typos.toml new file mode 100644 index 0000000..7520fcc --- /dev/null +++ b/_typos.toml @@ -0,0 +1,6 @@ +[default] +# Use typos:ignore-next-line to ignore the next line in the codebase +extend-ignore-re = ["(#|//)\\s*typos:ignore-next-line\\n.*"] + +[files] +extend-exclude = ["src/data/duckdb-keywords.json"] \ No newline at end of file diff --git a/demo/custom-renderers.ts b/demo/custom-renderers.ts new file mode 100644 index 0000000..1949c20 --- /dev/null +++ b/demo/custom-renderers.ts @@ -0,0 +1,306 @@ +import type { NamespaceTooltipData } from "../src/sql/hover.js"; + +interface ColumnMetadata { + type: string; + primaryKey?: boolean; + foreignKey?: boolean; + unique?: boolean; + default?: string; + notNull?: boolean; + comment?: string; +} + +interface ForeignKeyMetadata { + column: string; + referencedTable: string; + referencedColumn: string; +} + +interface IndexMetadata { + name: string; + columns: string[]; + unique: boolean; +} + +interface TableMetadata { + description: string; + rowCount: string; + columns: Record; + foreignKeys: ForeignKeyMetadata[]; + indexes: IndexMetadata[]; +} + +export type Schema = "users" | "posts" | "orders" | "customers" | "categories"; + +export const tableTooltipRenderer = (data: NamespaceTooltipData) => { + // Show table name, columns, description, primary key, foreign key, index, unique, check, default, comment + const table = data.item.path.join("."); + const columns = data.item.namespace?.[table] ?? []; + + // Enhanced table metadata (simulated for demo purposes) + const tableMetadata = getTableMetadata(table); + + let tooltip = `
`; + + // Table header + tooltip += `
📋 Table: ${table}
`; + + // Description + if (tableMetadata.description) { + tooltip += `
${tableMetadata.description}
`; + } + + // Column details in a table + if (columns.length > 0) { + tooltip += `
`; + tooltip += `
📊 Columns
`; + tooltip += ``; + tooltip += ``; + tooltip += ``; + tooltip += ``; + tooltip += ``; + tooltip += ``; + tooltip += ``; + + columns.forEach((column) => { + const columnInfo = tableMetadata.columns[column]; + const constraints: string[] = []; + + if (columnInfo) { + if (columnInfo.primaryKey) constraints.push("🔑 PK"); + if (columnInfo.foreignKey) constraints.push("🔗 FK"); + if (columnInfo.unique) constraints.push("✨ UNIQUE"); + if (columnInfo.notNull) constraints.push("❌ NOT NULL"); + if (columnInfo.default) constraints.push(`💡 DEFAULT ${columnInfo.default}`); + + tooltip += ``; + tooltip += ``; + tooltip += ``; + tooltip += ``; + tooltip += ``; + tooltip += ``; + } else { + tooltip += ``; + tooltip += ``; + tooltip += ``; + tooltip += ``; + tooltip += ``; + tooltip += ``; + } + }); + + tooltip += `
ColumnTypeConstraintsDescription
${column}${columnInfo.type}${constraints.join(" ")}${columnInfo.comment || ""}
${column}---
`; + } + + // Foreign key relationships + if (tableMetadata.foreignKeys.length > 0) { + tooltip += `
`; + tooltip += `
🔗 Foreign Keys
`; + tooltip += ``; + tooltip += ``; + tooltip += ``; + tooltip += ``; + tooltip += ``; + + tableMetadata.foreignKeys.forEach((fk) => { + tooltip += ``; + tooltip += ``; + tooltip += ``; + tooltip += ``; + }); + + tooltip += `
ColumnReferences
${fk.column}${fk.referencedTable}.${fk.referencedColumn}
`; + } + + // Indexes + if (tableMetadata.indexes.length > 0) { + tooltip += `
`; + tooltip += `
📈 Indexes
`; + tooltip += ``; + tooltip += ``; + tooltip += ``; + tooltip += ``; + tooltip += ``; + tooltip += ``; + + tableMetadata.indexes.forEach((index) => { + tooltip += ``; + tooltip += ``; + tooltip += ``; + tooltip += ``; + tooltip += ``; + }); + + tooltip += `
NameColumnsType
${index.name}${index.columns.join(", ")}${index.unique ? "UNIQUE" : "NORMAL"}
`; + } + + // Table statistics + tooltip += `
`; + tooltip += `📊 ${tableMetadata.rowCount} rows`; + tooltip += `
`; + + tooltip += `
`; + + return tooltip; +}; + +// Helper function to get enhanced table metadata +function getTableMetadata(tableName: string): TableMetadata { + const metadata: Record = { + users: { + description: "User accounts and profile information", + rowCount: "1,234", + columns: { + id: { type: "INT", primaryKey: true, notNull: true, comment: "Unique user identifier" }, + name: { type: "VARCHAR(255)", notNull: true, comment: "User's full name" }, + email: { + type: "VARCHAR(255)", + unique: true, + notNull: true, + comment: "User's email address", + }, + active: { type: "BOOLEAN", default: "true", comment: "Whether the user account is active" }, + status: { + type: "ENUM('active','inactive','suspended')", + default: "'active'", + comment: "User account status", + }, + created_at: { + type: "TIMESTAMP", + default: "CURRENT_TIMESTAMP", + comment: "Account creation date", + }, + updated_at: { + type: "TIMESTAMP", + default: "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP", + comment: "Last update timestamp", + }, + profile_id: { type: "INT", foreignKey: true, comment: "Reference to user profile" }, + }, + foreignKeys: [{ column: "profile_id", referencedTable: "profiles", referencedColumn: "id" }], + indexes: [ + { name: "idx_users_email", columns: ["email"], unique: true }, + { name: "idx_users_status", columns: ["status"], unique: false }, + { name: "idx_users_created", columns: ["created_at"], unique: false }, + ], + }, + posts: { + description: "Blog posts and articles", + rowCount: "5,678", + columns: { + id: { type: "INT", primaryKey: true, notNull: true, comment: "Unique post identifier" }, + title: { type: "VARCHAR(255)", notNull: true, comment: "Post title" }, + content: { type: "TEXT", comment: "Post content" }, + user_id: { type: "INT", notNull: true, foreignKey: true, comment: "Author of the post" }, + published: { type: "BOOLEAN", default: "false", comment: "Publication status" }, + created_at: { + type: "TIMESTAMP", + default: "CURRENT_TIMESTAMP", + comment: "Post creation date", + }, + updated_at: { + type: "TIMESTAMP", + default: "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP", + comment: "Last update timestamp", + }, + category_id: { type: "INT", foreignKey: true, comment: "Post category" }, + }, + foreignKeys: [ + { column: "user_id", referencedTable: "users", referencedColumn: "id" }, + { column: "category_id", referencedTable: "categories", referencedColumn: "id" }, + ], + indexes: [ + { name: "idx_posts_user", columns: ["user_id"], unique: false }, + { name: "idx_posts_published", columns: ["published"], unique: false }, + { name: "idx_posts_created", columns: ["created_at"], unique: false }, + ], + }, + orders: { + description: "Customer orders and transactions", + rowCount: "12,345", + columns: { + id: { type: "INT", primaryKey: true, notNull: true, comment: "Unique order identifier" }, + customer_id: { + type: "INT", + notNull: true, + foreignKey: true, + comment: "Customer who placed the order", + }, + order_date: { type: "DATE", notNull: true, comment: "Date when order was placed" }, + total_amount: { type: "DECIMAL(10,2)", notNull: true, comment: "Total order amount" }, + status: { + type: "ENUM('pending','processing','shipped','delivered','cancelled')", + default: "'pending'", + comment: "Order status", + }, + shipping_address: { type: "TEXT", comment: "Shipping address" }, + created_at: { + type: "TIMESTAMP", + default: "CURRENT_TIMESTAMP", + comment: "Order creation timestamp", + }, + }, + foreignKeys: [ + { column: "customer_id", referencedTable: "customers", referencedColumn: "id" }, + ], + indexes: [ + { name: "idx_orders_customer", columns: ["customer_id"], unique: false }, + { name: "idx_orders_date", columns: ["order_date"], unique: false }, + { name: "idx_orders_status", columns: ["status"], unique: false }, + ], + }, + customers: { + description: "Customer information and profiles", + rowCount: "2,345", + columns: { + id: { type: "INT", primaryKey: true, notNull: true, comment: "Unique customer identifier" }, + first_name: { type: "VARCHAR(100)", notNull: true, comment: "Customer's first name" }, + last_name: { type: "VARCHAR(100)", notNull: true, comment: "Customer's last name" }, + email: { + type: "VARCHAR(255)", + unique: true, + notNull: true, + comment: "Customer's email address", + }, + phone: { type: "VARCHAR(20)", comment: "Customer's phone number" }, + address: { type: "TEXT", comment: "Customer's address" }, + city: { type: "VARCHAR(100)", comment: "Customer's city" }, + country: { type: "VARCHAR(100)", comment: "Customer's country" }, + }, + foreignKeys: [], + indexes: [ + { name: "idx_customers_email", columns: ["email"], unique: true }, + { name: "idx_customers_name", columns: ["last_name", "first_name"], unique: false }, + ], + }, + categories: { + description: "Product and post categories", + rowCount: "89", + columns: { + id: { type: "INT", primaryKey: true, notNull: true, comment: "Unique category identifier" }, + name: { type: "VARCHAR(100)", notNull: true, comment: "Category name" }, + description: { type: "TEXT", comment: "Category description" }, + parent_id: { + type: "INT", + foreignKey: true, + comment: "Parent category for hierarchical structure", + }, + }, + foreignKeys: [{ column: "parent_id", referencedTable: "categories", referencedColumn: "id" }], + indexes: [ + { name: "idx_categories_name", columns: ["name"], unique: false }, + { name: "idx_categories_parent", columns: ["parent_id"], unique: false }, + ], + }, + }; + + return ( + metadata[tableName] || { + description: "Table information", + rowCount: "Unknown", + columns: {}, + foreignKeys: [], + indexes: [], + } + ); +} diff --git a/demo/index.html b/demo/index.html index 5cc6199..0dc206b 100644 --- a/demo/index.html +++ b/demo/index.html @@ -13,14 +13,16 @@

- codemirror-sql

-

by marimo

+

by marimo

A CodeMirror extension for SQL with real-time syntax validation and error diagnostics using - node-sql-parser. + node-sql-parser.

@@ -30,6 +32,18 @@

SQL Editor with Diagnostics

Try typing invalid SQL syntax to see real-time error highlighting and messages. Valid tables are: users, posts, orders, customers, categories

+ +
+ +
+
@@ -50,6 +64,9 @@

Valid Queries

+

Invalid Queries (will show errors)

@@ -79,7 +96,11 @@

Error Highlighting

Multiple SQL Dialects

-

Supports MySQL, PostgreSQL, MariaDB, and SQLite syntax validation.

+

Supports MySQL, PostgreSQL, MariaDB, SQLite, Snowflake, and DuckDB syntax validation.

+
+
+

DuckDB Support

+

Special support for DuckDB-specific syntax like "from table select * limit 100".

TypeScript Support

diff --git a/demo/index.ts b/demo/index.ts index 3147b7d..67915b0 100644 --- a/demo/index.ts +++ b/demo/index.ts @@ -1,7 +1,15 @@ -import { StandardSQL, sql } from "@codemirror/lang-sql"; +import { acceptCompletion } from "@codemirror/autocomplete"; +import { PostgreSQL, sql } from "@codemirror/lang-sql"; +import { type EditorState, StateEffect, StateField } from "@codemirror/state"; +import { keymap } from "@codemirror/view"; import { basicSetup, EditorView } from "codemirror"; -import { cteCompletionSource } from "../src/sql/cte-completion-source.js"; -import { sqlExtension } from "../src/sql/extension.js"; +import { + cteCompletionSource, + DefaultSqlTooltipRenders, + NodeSqlParser, + sqlExtension, +} from "../src/index.js"; +import { type Schema, tableTooltipRenderer } from "./custom-renderers.js"; // Default SQL content for the demo const defaultSqlDoc = `-- Welcome to the SQL Editor Demo! @@ -51,7 +59,7 @@ WHERE order_date >= '2024-01-01' ORDER BY total_amount DESC; `; -const schema = { +const schema: Record = { // Users table users: ["id", "name", "email", "active", "status", "created_at", "updated_at", "profile_id"], // Posts table @@ -94,24 +102,110 @@ const completionKindStyles = { height: "12px", }; -const dialect = StandardSQL; +const dialect = PostgreSQL; + +const defaultKeymap = [ + { + key: "Tab", + run: (view: EditorView) => { + // Try to accept completion first + if (acceptCompletion(view)) { + return true; + } + // In production, you can use @codemirror/commands.indentWithTab instead of custom logic + // If no completion to accept, insert a tab character + const { state } = view; + const { selection } = state; + if (selection.main.empty) { + // Insert tab at cursor position + view.dispatch({ + changes: { + from: selection.main.from, + insert: "\t", + }, + selection: { + anchor: selection.main.from + 1, + head: selection.main.from + 1, + }, + }); + return true; + } + return false; + }, + }, +]; + +// e.g. lazily load keyword docs +const getKeywordDocs = async () => { + const keywords = await import("@marimo-team/codemirror-sql/data/common-keywords.json"); + const duckdbKeywords = await import("@marimo-team/codemirror-sql/data/duckdb-keywords.json"); + return { + ...keywords.default.keywords, + ...duckdbKeywords.default.keywords, + }; +}; + +const setDatabase = StateEffect.define(); + +const databaseField = StateField.define({ + create: () => "PostgreSQL", + update: (prevValue, transaction) => { + for (const effect of transaction.effects) { + if (effect.is(setDatabase)) { + return effect.value; + } + } + return prevValue; + }, +}); // Initialize the SQL editor function initializeEditor() { + // Use the same parser for linter and gutter + const parser = new NodeSqlParser({ + getParserOptions: (state: EditorState) => { + return { + database: getDialect(state), + }; + }, + }); + const extensions = [ basicSetup, EditorView.lineWrapping, + keymap.of(defaultKeymap), + databaseField, sql({ dialect: dialect, // Example schema for autocomplete schema: schema, // Enable uppercase keywords for more traditional SQL style upperCaseKeywords: true, + keywordCompletion: (label, _type) => { + return { + label, + keyword: label, + info: async () => { + const dom = document.createElement("div"); + const keywordDocs = await getKeywordDocs(); + const description = keywordDocs[label.toLocaleLowerCase()]; + if (!description) { + return null; + } + dom.innerHTML = DefaultSqlTooltipRenders.keyword({ + keyword: label, + info: description, + }); + return dom; + }, + }; + }, }), sqlExtension({ // Linter extension configuration linterConfig: { delay: 250, // Delay before running validation + parser, }, // Gutter extension configuration @@ -119,6 +213,7 @@ function initializeEditor() { backgroundColor: "#3b82f6", // Blue for current statement errorBackgroundColor: "#ef4444", // Red for invalid statements hideWhenNotFocused: true, // Hide gutter when editor loses focus + parser, }, // Hover extension configuration enableHover: true, // Enable hover tooltips @@ -129,8 +224,12 @@ function initializeEditor() { enableTables: true, // Show table information enableColumns: true, // Show column information keywords: async () => { - const keywords = await import("../src/data/common-keywords.json"); - return keywords.default.keywords; + const keywords = await getKeywordDocs(); + return keywords; + }, + tooltipRenderers: { + // Custom renderer for tables + table: tableTooltipRenderer, }, }, }), @@ -236,10 +335,27 @@ function setupExampleButtons() { }); } +function getDialect(state: EditorState): string { + return state.field(databaseField); +} + +function setupDialectSelect() { + const select = document.querySelector("#dialect-select"); + if (select) { + select.addEventListener("change", (e) => { + const value = (e.target as HTMLSelectElement).value; + editor.dispatch({ + effects: [setDatabase.of(value)], + }); + }); + } +} + // Initialize everything when the page loads document.addEventListener("DOMContentLoaded", () => { initializeEditor(); setupExampleButtons(); + setupDialectSelect(); console.log("SQL Editor Demo initialized!"); console.log("Features:"); diff --git a/package.json b/package.json index 9c74b19..5e88020 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@marimo-team/codemirror-sql", - "version": "0.1.1", + "version": "0.1.2", "publishConfig": { "access": "public" }, @@ -34,17 +34,17 @@ "devDependencies": { "@biomejs/biome": "^2.1.3", "@codemirror/lang-sql": "^6.9.1", - "@codemirror/view": "^6.36.2", - "@vitest/coverage-v8": "3.1.3", - "codemirror": "^6.0.1", - "jsdom": "^26.0.0", - "typescript": "^5.7.3", - "vite": "^7.0.0", - "vitest": "^3.0.5" + "@codemirror/view": "^6.38.1", + "@vitest/coverage-v8": "3.2.4", + "codemirror": "^6.0.2", + "jsdom": "^26.1.0", + "typescript": "^5.9.2", + "vite": "^7.0.6", + "vitest": "^3.2.4" }, "files": [ "dist", - "src/data" + "src/data/*.json" ], "exports": { "./package.json": "./package.json", @@ -54,7 +54,8 @@ "default": "./dist/index.js" } }, - "./data/common-keywords.json": "./src/data/common-keywords.json" + "./data/common-keywords.json": "./src/data/common-keywords.json", + "./data/duckdb-keywords.json": "./src/data/duckdb-keywords.json" }, "types": "./dist/index.d.ts", "type": "module", @@ -63,8 +64,8 @@ }, "module": "./dist/index.js", "dependencies": { - "@codemirror/autocomplete": "^6.0.0", - "@codemirror/lint": "^6.0.0", + "@codemirror/autocomplete": "^6.18.6", + "@codemirror/lint": "^6.8.5", "node-sql-parser": "^5.3.10" } -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 946a730..7a0b047 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,45 +9,45 @@ importers: .: dependencies: '@codemirror/autocomplete': - specifier: ^6.0.0 - version: 6.18.4 + specifier: ^6.18.6 + version: 6.18.6 '@codemirror/lint': - specifier: ^6.0.0 + specifier: ^6.8.5 version: 6.8.5 '@codemirror/state': specifier: ^6 - version: 6.5.0 + version: 6.5.2 node-sql-parser: specifier: ^5.3.10 - version: 5.3.10 + version: 5.3.11 devDependencies: '@biomejs/biome': specifier: ^2.1.3 - version: 2.1.3 + version: 2.1.4 '@codemirror/lang-sql': specifier: ^6.9.1 version: 6.9.1 '@codemirror/view': - specifier: ^6.36.2 - version: 6.36.7 + specifier: ^6.38.1 + version: 6.38.1 '@vitest/coverage-v8': - specifier: 3.1.3 - version: 3.1.3(vitest@3.1.3(@types/node@22.13.1)(happy-dom@15.11.7)(jsdom@26.1.0)(yaml@2.8.0)) + specifier: 3.2.4 + version: 3.2.4(vitest@3.2.4(@types/node@22.13.1)(happy-dom@15.11.7)(jsdom@26.1.0)(yaml@2.8.0)) codemirror: - specifier: ^6.0.1 - version: 6.0.1 + specifier: ^6.0.2 + version: 6.0.2 jsdom: - specifier: ^26.0.0 + specifier: ^26.1.0 version: 26.1.0 typescript: - specifier: ^5.7.3 - version: 5.8.3 + specifier: ^5.9.2 + version: 5.9.2 vite: - specifier: ^7.0.0 - version: 7.0.6(@types/node@22.13.1)(yaml@2.8.0) + specifier: ^7.0.6 + version: 7.1.1(@types/node@22.13.1)(yaml@2.8.0) vitest: - specifier: ^3.0.5 - version: 3.1.3(@types/node@22.13.1)(happy-dom@15.11.7)(jsdom@26.1.0)(yaml@2.8.0) + specifier: ^3.2.4 + version: 3.2.4(@types/node@22.13.1)(happy-dom@15.11.7)(jsdom@26.1.0)(yaml@2.8.0) packages: @@ -55,381 +55,255 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - '@asamuzakjp/css-color@3.1.4': - resolution: {integrity: sha512-SeuBV4rnjpFNjI8HSgKUwteuFdkHwkboq31HWzznuqgySQir+jSTczoWVVL4jvOjKjuH80fMDG0Fvg1Sb+OJsA==} + '@asamuzakjp/css-color@3.2.0': + resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} - '@babel/helper-string-parser@7.25.9': - resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.25.9': - resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} engines: {node: '>=6.9.0'} - '@babel/parser@7.26.3': - resolution: {integrity: sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==} + '@babel/parser@7.28.0': + resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/types@7.26.3': - resolution: {integrity: sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==} + '@babel/types@7.28.2': + resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} engines: {node: '>=6.9.0'} '@bcoe/v8-coverage@1.0.2': resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} - '@biomejs/biome@2.1.3': - resolution: {integrity: sha512-KE/tegvJIxTkl7gJbGWSgun7G6X/n2M6C35COT6ctYrAy7SiPyNvi6JtoQERVK/VRbttZfgGq96j2bFmhmnH4w==} + '@biomejs/biome@2.1.4': + resolution: {integrity: sha512-QWlrqyxsU0FCebuMnkvBIkxvPqH89afiJzjMl+z67ybutse590jgeaFdDurE9XYtzpjRGTI1tlUZPGWmbKsElA==} engines: {node: '>=14.21.3'} hasBin: true - '@biomejs/cli-darwin-arm64@2.1.3': - resolution: {integrity: sha512-LFLkSWRoSGS1wVUD/BE6Nlt2dSn0ulH3XImzg2O/36BoToJHKXjSxzPEMAqT9QvwVtk7/9AQhZpTneERU9qaXA==} + '@biomejs/cli-darwin-arm64@2.1.4': + resolution: {integrity: sha512-sCrNENE74I9MV090Wq/9Dg7EhPudx3+5OiSoQOkIe3DLPzFARuL1dOwCWhKCpA3I5RHmbrsbNSRfZwCabwd8Qg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] - '@biomejs/cli-darwin-x64@2.1.3': - resolution: {integrity: sha512-Q/4OTw8P9No9QeowyxswcWdm0n2MsdCwWcc5NcKQQvzwPjwuPdf8dpPPf4r+x0RWKBtl1FLiAUtJvBlri6DnYw==} + '@biomejs/cli-darwin-x64@2.1.4': + resolution: {integrity: sha512-gOEICJbTCy6iruBywBDcG4X5rHMbqCPs3clh3UQ+hRKlgvJTk4NHWQAyHOXvaLe+AxD1/TNX1jbZeffBJzcrOw==} engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] - '@biomejs/cli-linux-arm64-musl@2.1.3': - resolution: {integrity: sha512-KXouFSBnoxAWZYDQrnNRzZBbt5s9UJkIm40hdvSL9mBxSSoxRFQJbtg1hP3aa8A2SnXyQHxQfpiVeJlczZt76w==} + '@biomejs/cli-linux-arm64-musl@2.1.4': + resolution: {integrity: sha512-nYr7H0CyAJPaLupFE2cH16KZmRC5Z9PEftiA2vWxk+CsFkPZQ6dBRdcC6RuS+zJlPc/JOd8xw3uCCt9Pv41WvQ==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-arm64@2.1.3': - resolution: {integrity: sha512-2hS6LgylRqMFmAZCOFwYrf77QMdUwJp49oe8PX/O8+P2yKZMSpyQTf3Eo5ewnsMFUEmYbPOskafdV1ds1MZMJA==} + '@biomejs/cli-linux-arm64@2.1.4': + resolution: {integrity: sha512-juhEkdkKR4nbUi5k/KRp1ocGPNWLgFRD4NrHZSveYrD6i98pyvuzmS9yFYgOZa5JhaVqo0HPnci0+YuzSwT2fw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-x64-musl@2.1.3': - resolution: {integrity: sha512-KaLAxnROouzIWtl6a0Y88r/4hW5oDUJTIqQorOTVQITaKQsKjZX4XCUmHIhdEk8zMnaiLZzRTAwk1yIAl+mIew==} + '@biomejs/cli-linux-x64-musl@2.1.4': + resolution: {integrity: sha512-lvwvb2SQQHctHUKvBKptR6PLFCM7JfRjpCCrDaTmvB7EeZ5/dQJPhTYBf36BE/B4CRWR2ZiBLRYhK7hhXBCZAg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-linux-x64@2.1.3': - resolution: {integrity: sha512-NxlSCBhLvQtWGagEztfAZ4WcE1AkMTntZV65ZvR+J9jp06+EtOYEBPQndA70ZGhHbEDG57bR6uNvqkd1WrEYVA==} + '@biomejs/cli-linux-x64@2.1.4': + resolution: {integrity: sha512-Eoy9ycbhpJVYuR+LskV9s3uyaIkp89+qqgqhGQsWnp/I02Uqg2fXFblHJOpGZR8AxdB9ADy87oFVxn9MpFKUrw==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-win32-arm64@2.1.3': - resolution: {integrity: sha512-V9CUZCtWH4u0YwyCYbQ3W5F4ZGPWp2C2TYcsiWFNNyRfmOW1j/TY/jAurl33SaRjgZPO5UUhGyr9m6BN9t84NQ==} + '@biomejs/cli-win32-arm64@2.1.4': + resolution: {integrity: sha512-3WRYte7orvyi6TRfIZkDN9Jzoogbv+gSvR+b9VOXUg1We1XrjBg6WljADeVEaKTvOcpVdH0a90TwyOQ6ue4fGw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] - '@biomejs/cli-win32-x64@2.1.3': - resolution: {integrity: sha512-dxy599q6lgp8ANPpR8sDMscwdp9oOumEsVXuVCVT9N2vAho8uYXlCz53JhxX6LtJOXaE73qzgkGQ7QqvFlMC0g==} + '@biomejs/cli-win32-x64@2.1.4': + resolution: {integrity: sha512-tBc+W7anBPSFXGAoQW+f/+svkpt8/uXfRwDzN1DvnatkRMt16KIYpEi/iw8u9GahJlFv98kgHcIrSsZHZTR0sw==} engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] - '@codemirror/autocomplete@6.18.4': - resolution: {integrity: sha512-sFAphGQIqyQZfP2ZBsSHV7xQvo9Py0rV0dW7W3IMRdS+zDuNb2l3no78CvUaWKGfzFjI4FTrLdUSj86IGb2hRA==} + '@codemirror/autocomplete@6.18.6': + resolution: {integrity: sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==} - '@codemirror/commands@6.8.0': - resolution: {integrity: sha512-q8VPEFaEP4ikSlt6ZxjB3zW72+7osfAYW9i8Zu943uqbKuz6utc1+F170hyLUCUltXORjQXRyYQNfkckzA/bPQ==} + '@codemirror/commands@6.8.1': + resolution: {integrity: sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==} '@codemirror/lang-sql@6.9.1': resolution: {integrity: sha512-ecSk3gm/mlINcURMcvkCZmXgdzPSq8r/yfCtTB4vgqGGIbBC2IJIAy7GqYTy5pgBEooTVmHP2GZK6Z7h63CDGg==} - '@codemirror/language@6.10.8': - resolution: {integrity: sha512-wcP8XPPhDH2vTqf181U8MbZnW+tDyPYy0UzVOa+oHORjyT+mhhom9vBd7dApJwoDz9Nb/a8kHjJIsuA/t8vNFw==} + '@codemirror/language@6.11.2': + resolution: {integrity: sha512-p44TsNArL4IVXDTbapUmEkAlvWs2CFQbcfc0ymDsis1kH2wh0gcY96AS29c/vp2d0y2Tquk1EDSaawpzilUiAw==} '@codemirror/lint@6.8.5': resolution: {integrity: sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==} - '@codemirror/search@6.5.8': - resolution: {integrity: sha512-PoWtZvo7c1XFeZWmmyaOp2G0XVbOnm+fJzvghqGAktBW3cufwJUWvSCcNG0ppXiBEM05mZu6RhMtXPv2hpllig==} + '@codemirror/search@6.5.11': + resolution: {integrity: sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==} - '@codemirror/state@6.5.0': - resolution: {integrity: sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw==} + '@codemirror/state@6.5.2': + resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==} - '@codemirror/view@6.36.7': - resolution: {integrity: sha512-kCWGW/chWGPgZqfZ36Um9Iz0X2IVpmCjg1P/qY6B6a2ecXtWRRAigmpJ6YgUQ5lTWXMyyVdfmpzhLZmsZQMbtg==} + '@codemirror/view@6.38.1': + resolution: {integrity: sha512-RmTOkE7hRU3OVREqFVITWHz6ocgBjv08GoePscAakgVQfciA3SGCEk7mb9IzwW61cKKmlTpHXG6DUE5Ubx+MGQ==} '@csstools/color-helpers@5.0.2': resolution: {integrity: sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==} engines: {node: '>=18'} - '@csstools/css-calc@2.1.3': - resolution: {integrity: sha512-XBG3talrhid44BY1x3MHzUx/aTG8+x/Zi57M4aTKK9RFB4aLlF3TTSzfzn8nWVHWL3FgAXAxmupmDd6VWww+pw==} + '@csstools/css-calc@2.1.4': + resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} engines: {node: '>=18'} peerDependencies: - '@csstools/css-parser-algorithms': ^3.0.4 - '@csstools/css-tokenizer': ^3.0.3 + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 - '@csstools/css-color-parser@3.0.9': - resolution: {integrity: sha512-wILs5Zk7BU86UArYBJTPy/FMPPKVKHMj1ycCEyf3VUptol0JNRLFU/BZsJ4aiIHJEbSLiizzRrw8Pc1uAEDrXw==} + '@csstools/css-color-parser@3.0.10': + resolution: {integrity: sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==} engines: {node: '>=18'} peerDependencies: - '@csstools/css-parser-algorithms': ^3.0.4 - '@csstools/css-tokenizer': ^3.0.3 + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 - '@csstools/css-parser-algorithms@3.0.4': - resolution: {integrity: sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==} + '@csstools/css-parser-algorithms@3.0.5': + resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} engines: {node: '>=18'} peerDependencies: - '@csstools/css-tokenizer': ^3.0.3 + '@csstools/css-tokenizer': ^3.0.4 - '@csstools/css-tokenizer@3.0.3': - resolution: {integrity: sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==} + '@csstools/css-tokenizer@3.0.4': + resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} engines: {node: '>=18'} - '@esbuild/aix-ppc64@0.25.3': - resolution: {integrity: sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - '@esbuild/aix-ppc64@0.25.8': resolution: {integrity: sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.3': - resolution: {integrity: sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - '@esbuild/android-arm64@0.25.8': resolution: {integrity: sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.3': - resolution: {integrity: sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - '@esbuild/android-arm@0.25.8': resolution: {integrity: sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.3': - resolution: {integrity: sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - '@esbuild/android-x64@0.25.8': resolution: {integrity: sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.3': - resolution: {integrity: sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - '@esbuild/darwin-arm64@0.25.8': resolution: {integrity: sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.3': - resolution: {integrity: sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - '@esbuild/darwin-x64@0.25.8': resolution: {integrity: sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.3': - resolution: {integrity: sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - '@esbuild/freebsd-arm64@0.25.8': resolution: {integrity: sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.3': - resolution: {integrity: sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - '@esbuild/freebsd-x64@0.25.8': resolution: {integrity: sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.3': - resolution: {integrity: sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - '@esbuild/linux-arm64@0.25.8': resolution: {integrity: sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.3': - resolution: {integrity: sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - '@esbuild/linux-arm@0.25.8': resolution: {integrity: sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.3': - resolution: {integrity: sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - '@esbuild/linux-ia32@0.25.8': resolution: {integrity: sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.3': - resolution: {integrity: sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - '@esbuild/linux-loong64@0.25.8': resolution: {integrity: sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.3': - resolution: {integrity: sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-mips64el@0.25.8': resolution: {integrity: sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.3': - resolution: {integrity: sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - '@esbuild/linux-ppc64@0.25.8': resolution: {integrity: sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.3': - resolution: {integrity: sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - '@esbuild/linux-riscv64@0.25.8': resolution: {integrity: sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.3': - resolution: {integrity: sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - '@esbuild/linux-s390x@0.25.8': resolution: {integrity: sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.3': - resolution: {integrity: sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - '@esbuild/linux-x64@0.25.8': resolution: {integrity: sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.3': - resolution: {integrity: sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - '@esbuild/netbsd-arm64@0.25.8': resolution: {integrity: sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.3': - resolution: {integrity: sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - '@esbuild/netbsd-x64@0.25.8': resolution: {integrity: sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.3': - resolution: {integrity: sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - '@esbuild/openbsd-arm64@0.25.8': resolution: {integrity: sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.3': - resolution: {integrity: sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - '@esbuild/openbsd-x64@0.25.8': resolution: {integrity: sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==} engines: {node: '>=18'} @@ -442,48 +316,24 @@ packages: cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.25.3': - resolution: {integrity: sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - '@esbuild/sunos-x64@0.25.8': resolution: {integrity: sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.3': - resolution: {integrity: sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - '@esbuild/win32-arm64@0.25.8': resolution: {integrity: sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.3': - resolution: {integrity: sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - '@esbuild/win32-ia32@0.25.8': resolution: {integrity: sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.3': - resolution: {integrity: sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - '@esbuild/win32-x64@0.25.8': resolution: {integrity: sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==} engines: {node: '>=18'} @@ -498,23 +348,18 @@ packages: resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} engines: {node: '>=8'} - '@jridgewell/gen-mapping@0.3.8': - resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} - engines: {node: '>=6.0.0'} + '@jridgewell/gen-mapping@0.3.12': + resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==} '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} - '@jridgewell/set-array@1.2.1': - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.5.0': - resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + '@jridgewell/sourcemap-codec@1.5.4': + resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==} - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jridgewell/trace-mapping@0.3.29': + resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} '@lezer/common@1.2.3': resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==} @@ -532,208 +377,111 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@rollup/rollup-android-arm-eabi@4.40.1': - resolution: {integrity: sha512-kxz0YeeCrRUHz3zyqvd7n+TVRlNyTifBsmnmNPtk3hQURUyG9eAB+usz6DAwagMusjx/zb3AjvDUvhFGDAexGw==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm-eabi@4.46.1': - resolution: {integrity: sha512-oENme6QxtLCqjChRUUo3S6X8hjCXnWmJWnedD7VbGML5GUtaOtAyx+fEEXnBXVf0CBZApMQU0Idwi0FmyxzQhw==} + '@rollup/rollup-android-arm-eabi@4.46.2': + resolution: {integrity: sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.40.1': - resolution: {integrity: sha512-PPkxTOisoNC6TpnDKatjKkjRMsdaWIhyuMkA4UsBXT9WEZY4uHezBTjs6Vl4PbqQQeu6oION1w2voYZv9yquCw==} + '@rollup/rollup-android-arm64@4.46.2': + resolution: {integrity: sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==} cpu: [arm64] os: [android] - '@rollup/rollup-android-arm64@4.46.1': - resolution: {integrity: sha512-OikvNT3qYTl9+4qQ9Bpn6+XHM+ogtFadRLuT2EXiFQMiNkXFLQfNVppi5o28wvYdHL2s3fM0D/MZJ8UkNFZWsw==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.40.1': - resolution: {integrity: sha512-VWXGISWFY18v/0JyNUy4A46KCFCb9NVsH+1100XP31lud+TzlezBbz24CYzbnA4x6w4hx+NYCXDfnvDVO6lcAA==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-arm64@4.46.1': - resolution: {integrity: sha512-EFYNNGij2WllnzljQDQnlFTXzSJw87cpAs4TVBAWLdkvic5Uh5tISrIL6NRcxoh/b2EFBG/TK8hgRrGx94zD4A==} + '@rollup/rollup-darwin-arm64@4.46.2': + resolution: {integrity: sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.40.1': - resolution: {integrity: sha512-nIwkXafAI1/QCS7pxSpv/ZtFW6TXcNUEHAIA9EIyw5OzxJZQ1YDrX+CL6JAIQgZ33CInl1R6mHet9Y/UZTg2Bw==} + '@rollup/rollup-darwin-x64@4.46.2': + resolution: {integrity: sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==} cpu: [x64] os: [darwin] - '@rollup/rollup-darwin-x64@4.46.1': - resolution: {integrity: sha512-ZaNH06O1KeTug9WI2+GRBE5Ujt9kZw4a1+OIwnBHal92I8PxSsl5KpsrPvthRynkhMck4XPdvY0z26Cym/b7oA==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-freebsd-arm64@4.40.1': - resolution: {integrity: sha512-BdrLJ2mHTrIYdaS2I99mriyJfGGenSaP+UwGi1kB9BLOCu9SR8ZpbkmmalKIALnRw24kM7qCN0IOm6L0S44iWw==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-arm64@4.46.1': - resolution: {integrity: sha512-n4SLVebZP8uUlJ2r04+g2U/xFeiQlw09Me5UFqny8HGbARl503LNH5CqFTb5U5jNxTouhRjai6qPT0CR5c/Iig==} + '@rollup/rollup-freebsd-arm64@4.46.2': + resolution: {integrity: sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.40.1': - resolution: {integrity: sha512-VXeo/puqvCG8JBPNZXZf5Dqq7BzElNJzHRRw3vjBE27WujdzuOPecDPc/+1DcdcTptNBep3861jNq0mYkT8Z6Q==} + '@rollup/rollup-freebsd-x64@4.46.2': + resolution: {integrity: sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==} cpu: [x64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.46.1': - resolution: {integrity: sha512-8vu9c02F16heTqpvo3yeiu7Vi1REDEC/yES/dIfq3tSXe6mLndiwvYr3AAvd1tMNUqE9yeGYa5w7PRbI5QUV+w==} - cpu: [x64] - os: [freebsd] - - '@rollup/rollup-linux-arm-gnueabihf@4.40.1': - resolution: {integrity: sha512-ehSKrewwsESPt1TgSE/na9nIhWCosfGSFqv7vwEtjyAqZcvbGIg4JAcV7ZEh2tfj/IlfBeZjgOXm35iOOjadcg==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm-gnueabihf@4.46.1': - resolution: {integrity: sha512-K4ncpWl7sQuyp6rWiGUvb6Q18ba8mzM0rjWJ5JgYKlIXAau1db7hZnR0ldJvqKWWJDxqzSLwGUhA4jp+KqgDtQ==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm-musleabihf@4.40.1': - resolution: {integrity: sha512-m39iO/aaurh5FVIu/F4/Zsl8xppd76S4qoID8E+dSRQvTyZTOI2gVk3T4oqzfq1PtcvOfAVlwLMK3KRQMaR8lg==} + '@rollup/rollup-linux-arm-gnueabihf@4.46.2': + resolution: {integrity: sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.46.1': - resolution: {integrity: sha512-YykPnXsjUjmXE6j6k2QBBGAn1YsJUix7pYaPLK3RVE0bQL2jfdbfykPxfF8AgBlqtYbfEnYHmLXNa6QETjdOjQ==} + '@rollup/rollup-linux-arm-musleabihf@4.46.2': + resolution: {integrity: sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.40.1': - resolution: {integrity: sha512-Y+GHnGaku4aVLSgrT0uWe2o2Rq8te9hi+MwqGF9r9ORgXhmHK5Q71N757u0F8yU1OIwUIFy6YiJtKjtyktk5hg==} + '@rollup/rollup-linux-arm64-gnu@4.46.2': + resolution: {integrity: sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.46.1': - resolution: {integrity: sha512-kKvqBGbZ8i9pCGW3a1FH3HNIVg49dXXTsChGFsHGXQaVJPLA4f/O+XmTxfklhccxdF5FefUn2hvkoGJH0ScWOA==} + '@rollup/rollup-linux-arm64-musl@4.46.2': + resolution: {integrity: sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.40.1': - resolution: {integrity: sha512-jEwjn3jCA+tQGswK3aEWcD09/7M5wGwc6+flhva7dsQNRZZTe30vkalgIzV4tjkopsTS9Jd7Y1Bsj6a4lzz8gQ==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-arm64-musl@4.46.1': - resolution: {integrity: sha512-zzX5nTw1N1plmqC9RGC9vZHFuiM7ZP7oSWQGqpbmfjK7p947D518cVK1/MQudsBdcD84t6k70WNczJOct6+hdg==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-loongarch64-gnu@4.40.1': - resolution: {integrity: sha512-ySyWikVhNzv+BV/IDCsrraOAZ3UaC8SZB67FZlqVwXwnFhPihOso9rPOxzZbjp81suB1O2Topw+6Ug3JNegejQ==} + '@rollup/rollup-linux-loongarch64-gnu@4.46.2': + resolution: {integrity: sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.46.1': - resolution: {integrity: sha512-O8CwgSBo6ewPpktFfSDgB6SJN9XDcPSvuwxfejiddbIC/hn9Tg6Ai0f0eYDf3XvB/+PIWzOQL+7+TZoB8p9Yuw==} - cpu: [loong64] - os: [linux] - - '@rollup/rollup-linux-powerpc64le-gnu@4.40.1': - resolution: {integrity: sha512-BvvA64QxZlh7WZWqDPPdt0GH4bznuL6uOO1pmgPnnv86rpUpc8ZxgZwcEgXvo02GRIZX1hQ0j0pAnhwkhwPqWg==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-ppc64-gnu@4.46.1': - resolution: {integrity: sha512-JnCfFVEKeq6G3h3z8e60kAp8Rd7QVnWCtPm7cxx+5OtP80g/3nmPtfdCXbVl063e3KsRnGSKDHUQMydmzc/wBA==} + '@rollup/rollup-linux-ppc64-gnu@4.46.2': + resolution: {integrity: sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.40.1': - resolution: {integrity: sha512-EQSP+8+1VuSulm9RKSMKitTav89fKbHymTf25n5+Yr6gAPZxYWpj3DzAsQqoaHAk9YX2lwEyAf9S4W8F4l3VBQ==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-riscv64-gnu@4.46.1': - resolution: {integrity: sha512-dVxuDqS237eQXkbYzQQfdf/njgeNw6LZuVyEdUaWwRpKHhsLI+y4H/NJV8xJGU19vnOJCVwaBFgr936FHOnJsQ==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-riscv64-musl@4.40.1': - resolution: {integrity: sha512-n/vQ4xRZXKuIpqukkMXZt9RWdl+2zgGNx7Uda8NtmLJ06NL8jiHxUawbwC+hdSq1rrw/9CghCpEONor+l1e2gA==} + '@rollup/rollup-linux-riscv64-gnu@4.46.2': + resolution: {integrity: sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.46.1': - resolution: {integrity: sha512-CvvgNl2hrZrTR9jXK1ye0Go0HQRT6ohQdDfWR47/KFKiLd5oN5T14jRdUVGF4tnsN8y9oSfMOqH6RuHh+ck8+w==} + '@rollup/rollup-linux-riscv64-musl@4.46.2': + resolution: {integrity: sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.40.1': - resolution: {integrity: sha512-h8d28xzYb98fMQKUz0w2fMc1XuGzLLjdyxVIbhbil4ELfk5/orZlSTpF/xdI9C8K0I8lCkq+1En2RJsawZekkg==} + '@rollup/rollup-linux-s390x-gnu@4.46.2': + resolution: {integrity: sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.46.1': - resolution: {integrity: sha512-x7ANt2VOg2565oGHJ6rIuuAon+A8sfe1IeUx25IKqi49OjSr/K3awoNqr9gCwGEJo9OuXlOn+H2p1VJKx1psxA==} - cpu: [s390x] - os: [linux] - - '@rollup/rollup-linux-x64-gnu@4.40.1': - resolution: {integrity: sha512-XiK5z70PEFEFqcNj3/zRSz/qX4bp4QIraTy9QjwJAb/Z8GM7kVUsD0Uk8maIPeTyPCP03ChdI+VVmJriKYbRHQ==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-linux-x64-gnu@4.46.1': - resolution: {integrity: sha512-9OADZYryz/7E8/qt0vnaHQgmia2Y0wrjSSn1V/uL+zw/i7NUhxbX4cHXdEQ7dnJgzYDS81d8+tf6nbIdRFZQoQ==} + '@rollup/rollup-linux-x64-gnu@4.46.2': + resolution: {integrity: sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.40.1': - resolution: {integrity: sha512-2BRORitq5rQ4Da9blVovzNCMaUlyKrzMSvkVR0D4qPuOy/+pMCrh1d7o01RATwVy+6Fa1WBw+da7QPeLWU/1mQ==} + '@rollup/rollup-linux-x64-musl@4.46.2': + resolution: {integrity: sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.46.1': - resolution: {integrity: sha512-NuvSCbXEKY+NGWHyivzbjSVJi68Xfq1VnIvGmsuXs6TCtveeoDRKutI5vf2ntmNnVq64Q4zInet0UDQ+yMB6tA==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-win32-arm64-msvc@4.40.1': - resolution: {integrity: sha512-b2bcNm9Kbde03H+q+Jjw9tSfhYkzrDUf2d5MAd1bOJuVplXvFhWz7tRtWvD8/ORZi7qSCy0idW6tf2HgxSXQSg==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-arm64-msvc@4.46.1': - resolution: {integrity: sha512-mWz+6FSRb82xuUMMV1X3NGiaPFqbLN9aIueHleTZCc46cJvwTlvIh7reQLk4p97dv0nddyewBhwzryBHH7wtPw==} + '@rollup/rollup-win32-arm64-msvc@4.46.2': + resolution: {integrity: sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.40.1': - resolution: {integrity: sha512-DfcogW8N7Zg7llVEfpqWMZcaErKfsj9VvmfSyRjCyo4BI3wPEfrzTtJkZG6gKP/Z92wFm6rz2aDO7/JfiR/whA==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.46.1': - resolution: {integrity: sha512-7Thzy9TMXDw9AU4f4vsLNBxh7/VOKuXi73VH3d/kHGr0tZ3x/ewgL9uC7ojUKmH1/zvmZe2tLapYcZllk3SO8Q==} + '@rollup/rollup-win32-ia32-msvc@4.46.2': + resolution: {integrity: sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.40.1': - resolution: {integrity: sha512-ECyOuDeH3C1I8jH2MK1RtBJW+YPMvSfT0a5NN0nHfQYnDSJ6tUiZH3gzwVP5/Kfh/+Tt7tpWVF9LXNTnhTJ3kA==} + '@rollup/rollup-win32-x64-msvc@4.46.2': + resolution: {integrity: sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.46.1': - resolution: {integrity: sha512-7GVB4luhFmGUNXXJhH2jJwZCFB3pIOixv2E3s17GQHBFUOQaISlt7aGcQgqvCaDSxTZJUzlK/QJ1FN8S94MrzQ==} - cpu: [x64] - os: [win32] + '@types/chai@5.2.2': + resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} - '@types/estree@1.0.7': - resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -744,46 +492,46 @@ packages: '@types/pegjs@0.10.6': resolution: {integrity: sha512-eLYXDbZWXh2uxf+w8sXS8d6KSoXTswfps6fvCUuVAGN8eRpfe7h9eSRydxiSJvo9Bf+GzifsDOr9TMQlmJdmkw==} - '@vitest/coverage-v8@3.1.3': - resolution: {integrity: sha512-cj76U5gXCl3g88KSnf80kof6+6w+K4BjOflCl7t6yRJPDuCrHtVu0SgNYOUARJOL5TI8RScDbm5x4s1/P9bvpw==} + '@vitest/coverage-v8@3.2.4': + resolution: {integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==} peerDependencies: - '@vitest/browser': 3.1.3 - vitest: 3.1.3 + '@vitest/browser': 3.2.4 + vitest: 3.2.4 peerDependenciesMeta: '@vitest/browser': optional: true - '@vitest/expect@3.1.3': - resolution: {integrity: sha512-7FTQQuuLKmN1Ig/h+h/GO+44Q1IlglPlR2es4ab7Yvfx+Uk5xsv+Ykk+MEt/M2Yn/xGmzaLKxGw2lgy2bwuYqg==} + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} - '@vitest/mocker@3.1.3': - resolution: {integrity: sha512-PJbLjonJK82uCWHjzgBJZuR7zmAOrSvKk1QBxrennDIgtH4uK0TB1PvYmc0XBCigxxtiAVPfWtAdy4lpz8SQGQ==} + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} peerDependencies: msw: ^2.4.9 - vite: ^5.0.0 || ^6.0.0 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 peerDependenciesMeta: msw: optional: true vite: optional: true - '@vitest/pretty-format@3.1.3': - resolution: {integrity: sha512-i6FDiBeJUGLDKADw2Gb01UtUNb12yyXAqC/mmRWuYl+m/U9GS7s8us5ONmGkGpUUo7/iAYzI2ePVfOZTYvUifA==} + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} - '@vitest/runner@3.1.3': - resolution: {integrity: sha512-Tae+ogtlNfFei5DggOsSUvkIaSuVywujMj6HzR97AHK6XK8i3BuVyIifWAm/sE3a15lF5RH9yQIrbXYuo0IFyA==} + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} - '@vitest/snapshot@3.1.3': - resolution: {integrity: sha512-XVa5OPNTYUsyqG9skuUkFzAeFnEzDp8hQu7kZ0N25B1+6KjGm4hWLtURyBbsIAOekfWQ7Wuz/N/XXzgYO3deWQ==} + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} - '@vitest/spy@3.1.3': - resolution: {integrity: sha512-x6w+ctOEmEXdWaa6TO4ilb7l9DxPR5bwEb6hILKuxfU1NqWT2mpJD9NJN7t3OTfxmVlOMrvtoFJGdgyzZ605lQ==} + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} - '@vitest/utils@3.1.3': - resolution: {integrity: sha512-2Ltrpht4OmHO9+c/nmHtF09HWiyWdworqnHIwjfvDyWjuwKbdkcS9AnhsDn+8E2RM4x++foD1/tNuLPVvWG1Rg==} + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} - agent-base@7.1.3: - resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} ansi-regex@5.0.1: @@ -806,6 +554,9 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + ast-v8-to-istanbul@0.3.3: + resolution: {integrity: sha512-MuXMrSLVVoA6sYN/6Hke18vMzrT4TZNbZIj/hvh0fnYFpO+/kFXcLIaiPwXXWaQUPg4yJD8fj+lfJ7/1EBconw==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -813,23 +564,23 @@ packages: resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} engines: {node: '>=0.6'} - brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} - chai@5.2.0: - resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} - engines: {node: '>=12'} + chai@5.2.1: + resolution: {integrity: sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==} + engines: {node: '>=18'} check-error@2.1.1: resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} engines: {node: '>= 16'} - codemirror@6.0.1: - resolution: {integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==} + codemirror@6.0.2: + resolution: {integrity: sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==} color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} @@ -845,23 +596,14 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - cssstyle@4.3.1: - resolution: {integrity: sha512-ZgW+Jgdd7i52AaLYCriF8Mxqft0gD/R9i9wi6RWBhs1pqdPEzPjym7rvRKi397WmQFf3SlyUsszhw+VVCbx79Q==} + cssstyle@4.6.0: + resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} engines: {node: '>=18'} data-urls@5.0.0: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} - debug@4.4.0: - resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@4.4.1: resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} engines: {node: '>=6.0'} @@ -871,8 +613,8 @@ packages: supports-color: optional: true - decimal.js@10.5.0: - resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==} + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} @@ -891,18 +633,13 @@ packages: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} - entities@6.0.0: - resolution: {integrity: sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==} + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - esbuild@0.25.3: - resolution: {integrity: sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==} - engines: {node: '>=18'} - hasBin: true - esbuild@0.25.8: resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==} engines: {node: '>=18'} @@ -911,18 +648,10 @@ packages: estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - expect-type@1.2.1: - resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} + expect-type@1.2.2: + resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} engines: {node: '>=12.0.0'} - fdir@6.4.4: - resolution: {integrity: sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - fdir@6.4.6: resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} peerDependencies: @@ -931,8 +660,8 @@ packages: picomatch: optional: true - foreground-child@3.3.0: - resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} fsevents@2.3.3: @@ -1000,6 +729,9 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + jsdom@26.1.0: resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==} engines: {node: '>=18'} @@ -1009,8 +741,8 @@ packages: canvas: optional: true - loupe@3.1.3: - resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} + loupe@3.2.0: + resolution: {integrity: sha512-2NCfZcT5VGVNX9mSZIxLRkEAegDGBpuQZBy13desuHeVORmBDyAET4TkJr4SjqQy3A8JDofMN6LpkK8Xcm/dlw==} lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -1041,12 +773,12 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - node-sql-parser@5.3.10: - resolution: {integrity: sha512-cf+iXXJ9Foz4hBIu+eNNeg207ac6XruA9I9DXEs+jCxeS9t/k9T0GZK8NZngPwkv+P26i3zNFj9jxJU2v3pJnw==} + node-sql-parser@5.3.11: + resolution: {integrity: sha512-1ZcXu2qEFZlMkkaYPpMoTkypTunYTvtnInzVjWDhjc1+FS5h/WqT1rbgP2pP42/nXBWfPVN66mEfidWSpYVs1Q==} engines: {node: '>=8'} - nwsapi@2.2.20: - resolution: {integrity: sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==} + nwsapi@2.2.21: + resolution: {integrity: sha512-o6nIY3qwiSXl7/LuOU0Dmuctd34Yay0yeuZRLFmDPrrdHpXKFndPj3hM+YEPVHYC5fx2otBx4Ilc/gyYSAUaIA==} package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -1065,25 +797,17 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - pathval@2.0.0: - resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} engines: {node: '>= 14.16'} picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch@4.0.2: - resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} - engines: {node: '>=12'} - picomatch@4.0.3: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} - postcss@8.5.3: - resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} - engines: {node: ^10 || ^12 || >=14} - postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} @@ -1092,13 +816,8 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - rollup@4.40.1: - resolution: {integrity: sha512-C5VvvgCCyfyotVITIAv+4efVytl5F7wt+/I2i9q9GZcEXW9BP52YYOXC58igUi+LFZVHukErIIqQSWwv/M3WRw==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - rollup@4.46.1: - resolution: {integrity: sha512-33xGNBsDJAkzt0PvninskHlWnTIPgDtTwhg0U38CUoNP/7H6wI2Cz6dUeoNPbjdTdsYTGuiFFASuUOWovH0SyQ==} + rollup@4.46.2: + resolution: {integrity: sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -1112,8 +831,8 @@ packages: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} - semver@7.6.3: - resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} engines: {node: '>=10'} hasBin: true @@ -1158,6 +877,9 @@ packages: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} + strip-literal@3.0.0: + resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} + style-mod@4.1.2: resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==} @@ -1178,24 +900,20 @@ packages: tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - tinyglobby@0.2.13: - resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==} - engines: {node: '>=12.0.0'} - tinyglobby@0.2.14: resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} engines: {node: '>=12.0.0'} - tinypool@1.0.2: - resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} engines: {node: ^18.0.0 || >=20.0.0} tinyrainbow@2.0.0: resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} engines: {node: '>=14.0.0'} - tinyspy@3.0.2: - resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + tinyspy@4.0.3: + resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} engines: {node: '>=14.0.0'} tldts-core@6.1.86: @@ -1213,61 +931,21 @@ packages: resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} engines: {node: '>=18'} - typescript@5.8.3: - resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + typescript@5.9.2: + resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} engines: {node: '>=14.17'} hasBin: true undici-types@6.20.0: resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} - vite-node@3.1.3: - resolution: {integrity: sha512-uHV4plJ2IxCl4u1up1FQRrqclylKAogbtBfOTwcuJ28xFi+89PZ57BRh+naIRvH70HPwxy5QHYzg1OrEaC7AbA==} + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true - vite@6.3.5: - resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - jiti: '>=1.21.0' - less: '*' - lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - - vite@7.0.6: - resolution: {integrity: sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==} + vite@7.1.1: + resolution: {integrity: sha512-yJ+Mp7OyV+4S+afWo+QyoL9jFWD11QFH0i5i7JypnfTcA1rmgxCbiA8WwAICDEtZ1Z1hzrVhN8R8rGTqkTY8ZQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -1306,16 +984,16 @@ packages: yaml: optional: true - vitest@3.1.3: - resolution: {integrity: sha512-188iM4hAHQ0km23TN/adso1q5hhwKqUpv+Sd6p5sOuh6FhQnRNW3IsiIpvxqahtBabsJ2SLZgmGSpcYK4wQYJw==} + vitest@3.2.4: + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/debug': ^4.1.12 '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - '@vitest/browser': 3.1.3 - '@vitest/ui': 3.1.3 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -1379,8 +1057,8 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} - ws@8.18.1: - resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==} + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -1407,94 +1085,94 @@ snapshots: '@ampproject/remapping@2.3.0': dependencies: - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/trace-mapping': 0.3.29 - '@asamuzakjp/css-color@3.1.4': + '@asamuzakjp/css-color@3.2.0': dependencies: - '@csstools/css-calc': 2.1.3(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) - '@csstools/css-color-parser': 3.0.9(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) - '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) - '@csstools/css-tokenizer': 3.0.3 + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 lru-cache: 10.4.3 - '@babel/helper-string-parser@7.25.9': {} + '@babel/helper-string-parser@7.27.1': {} - '@babel/helper-validator-identifier@7.25.9': {} + '@babel/helper-validator-identifier@7.27.1': {} - '@babel/parser@7.26.3': + '@babel/parser@7.28.0': dependencies: - '@babel/types': 7.26.3 + '@babel/types': 7.28.2 - '@babel/types@7.26.3': + '@babel/types@7.28.2': dependencies: - '@babel/helper-string-parser': 7.25.9 - '@babel/helper-validator-identifier': 7.25.9 + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 '@bcoe/v8-coverage@1.0.2': {} - '@biomejs/biome@2.1.3': + '@biomejs/biome@2.1.4': optionalDependencies: - '@biomejs/cli-darwin-arm64': 2.1.3 - '@biomejs/cli-darwin-x64': 2.1.3 - '@biomejs/cli-linux-arm64': 2.1.3 - '@biomejs/cli-linux-arm64-musl': 2.1.3 - '@biomejs/cli-linux-x64': 2.1.3 - '@biomejs/cli-linux-x64-musl': 2.1.3 - '@biomejs/cli-win32-arm64': 2.1.3 - '@biomejs/cli-win32-x64': 2.1.3 - - '@biomejs/cli-darwin-arm64@2.1.3': + '@biomejs/cli-darwin-arm64': 2.1.4 + '@biomejs/cli-darwin-x64': 2.1.4 + '@biomejs/cli-linux-arm64': 2.1.4 + '@biomejs/cli-linux-arm64-musl': 2.1.4 + '@biomejs/cli-linux-x64': 2.1.4 + '@biomejs/cli-linux-x64-musl': 2.1.4 + '@biomejs/cli-win32-arm64': 2.1.4 + '@biomejs/cli-win32-x64': 2.1.4 + + '@biomejs/cli-darwin-arm64@2.1.4': optional: true - '@biomejs/cli-darwin-x64@2.1.3': + '@biomejs/cli-darwin-x64@2.1.4': optional: true - '@biomejs/cli-linux-arm64-musl@2.1.3': + '@biomejs/cli-linux-arm64-musl@2.1.4': optional: true - '@biomejs/cli-linux-arm64@2.1.3': + '@biomejs/cli-linux-arm64@2.1.4': optional: true - '@biomejs/cli-linux-x64-musl@2.1.3': + '@biomejs/cli-linux-x64-musl@2.1.4': optional: true - '@biomejs/cli-linux-x64@2.1.3': + '@biomejs/cli-linux-x64@2.1.4': optional: true - '@biomejs/cli-win32-arm64@2.1.3': + '@biomejs/cli-win32-arm64@2.1.4': optional: true - '@biomejs/cli-win32-x64@2.1.3': + '@biomejs/cli-win32-x64@2.1.4': optional: true - '@codemirror/autocomplete@6.18.4': + '@codemirror/autocomplete@6.18.6': dependencies: - '@codemirror/language': 6.10.8 - '@codemirror/state': 6.5.0 - '@codemirror/view': 6.36.7 + '@codemirror/language': 6.11.2 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.1 '@lezer/common': 1.2.3 - '@codemirror/commands@6.8.0': + '@codemirror/commands@6.8.1': dependencies: - '@codemirror/language': 6.10.8 - '@codemirror/state': 6.5.0 - '@codemirror/view': 6.36.7 + '@codemirror/language': 6.11.2 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.1 '@lezer/common': 1.2.3 '@codemirror/lang-sql@6.9.1': dependencies: - '@codemirror/autocomplete': 6.18.4 - '@codemirror/language': 6.10.8 - '@codemirror/state': 6.5.0 + '@codemirror/autocomplete': 6.18.6 + '@codemirror/language': 6.11.2 + '@codemirror/state': 6.5.2 '@lezer/common': 1.2.3 '@lezer/highlight': 1.2.1 '@lezer/lr': 1.4.2 - '@codemirror/language@6.10.8': + '@codemirror/language@6.11.2': dependencies: - '@codemirror/state': 6.5.0 - '@codemirror/view': 6.36.7 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.1 '@lezer/common': 1.2.3 '@lezer/highlight': 1.2.1 '@lezer/lr': 1.4.2 @@ -1502,196 +1180,122 @@ snapshots: '@codemirror/lint@6.8.5': dependencies: - '@codemirror/state': 6.5.0 - '@codemirror/view': 6.36.7 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.1 crelt: 1.0.6 - '@codemirror/search@6.5.8': + '@codemirror/search@6.5.11': dependencies: - '@codemirror/state': 6.5.0 - '@codemirror/view': 6.36.7 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.1 crelt: 1.0.6 - '@codemirror/state@6.5.0': + '@codemirror/state@6.5.2': dependencies: '@marijn/find-cluster-break': 1.0.2 - '@codemirror/view@6.36.7': + '@codemirror/view@6.38.1': dependencies: - '@codemirror/state': 6.5.0 + '@codemirror/state': 6.5.2 + crelt: 1.0.6 style-mod: 4.1.2 w3c-keyname: 2.2.8 '@csstools/color-helpers@5.0.2': {} - '@csstools/css-calc@2.1.3(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': + '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': dependencies: - '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) - '@csstools/css-tokenizer': 3.0.3 + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 - '@csstools/css-color-parser@3.0.9(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': + '@csstools/css-color-parser@3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': dependencies: '@csstools/color-helpers': 5.0.2 - '@csstools/css-calc': 2.1.3(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) - '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) - '@csstools/css-tokenizer': 3.0.3 + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 - '@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3)': + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': dependencies: - '@csstools/css-tokenizer': 3.0.3 - - '@csstools/css-tokenizer@3.0.3': {} + '@csstools/css-tokenizer': 3.0.4 - '@esbuild/aix-ppc64@0.25.3': - optional: true + '@csstools/css-tokenizer@3.0.4': {} '@esbuild/aix-ppc64@0.25.8': optional: true - '@esbuild/android-arm64@0.25.3': - optional: true - '@esbuild/android-arm64@0.25.8': optional: true - '@esbuild/android-arm@0.25.3': - optional: true - '@esbuild/android-arm@0.25.8': optional: true - '@esbuild/android-x64@0.25.3': - optional: true - '@esbuild/android-x64@0.25.8': optional: true - '@esbuild/darwin-arm64@0.25.3': - optional: true - '@esbuild/darwin-arm64@0.25.8': optional: true - '@esbuild/darwin-x64@0.25.3': - optional: true - '@esbuild/darwin-x64@0.25.8': optional: true - '@esbuild/freebsd-arm64@0.25.3': - optional: true - '@esbuild/freebsd-arm64@0.25.8': optional: true - '@esbuild/freebsd-x64@0.25.3': - optional: true - '@esbuild/freebsd-x64@0.25.8': optional: true - '@esbuild/linux-arm64@0.25.3': - optional: true - '@esbuild/linux-arm64@0.25.8': optional: true - '@esbuild/linux-arm@0.25.3': - optional: true - '@esbuild/linux-arm@0.25.8': optional: true - '@esbuild/linux-ia32@0.25.3': - optional: true - '@esbuild/linux-ia32@0.25.8': optional: true - '@esbuild/linux-loong64@0.25.3': - optional: true - '@esbuild/linux-loong64@0.25.8': optional: true - '@esbuild/linux-mips64el@0.25.3': - optional: true - '@esbuild/linux-mips64el@0.25.8': optional: true - '@esbuild/linux-ppc64@0.25.3': - optional: true - '@esbuild/linux-ppc64@0.25.8': optional: true - '@esbuild/linux-riscv64@0.25.3': - optional: true - '@esbuild/linux-riscv64@0.25.8': optional: true - '@esbuild/linux-s390x@0.25.3': - optional: true - '@esbuild/linux-s390x@0.25.8': optional: true - '@esbuild/linux-x64@0.25.3': - optional: true - '@esbuild/linux-x64@0.25.8': optional: true - '@esbuild/netbsd-arm64@0.25.3': - optional: true - '@esbuild/netbsd-arm64@0.25.8': optional: true - '@esbuild/netbsd-x64@0.25.3': - optional: true - '@esbuild/netbsd-x64@0.25.8': optional: true - '@esbuild/openbsd-arm64@0.25.3': - optional: true - '@esbuild/openbsd-arm64@0.25.8': optional: true - '@esbuild/openbsd-x64@0.25.3': - optional: true - '@esbuild/openbsd-x64@0.25.8': optional: true '@esbuild/openharmony-arm64@0.25.8': optional: true - '@esbuild/sunos-x64@0.25.3': - optional: true - '@esbuild/sunos-x64@0.25.8': optional: true - '@esbuild/win32-arm64@0.25.3': - optional: true - '@esbuild/win32-arm64@0.25.8': optional: true - '@esbuild/win32-ia32@0.25.3': - optional: true - '@esbuild/win32-ia32@0.25.8': optional: true - '@esbuild/win32-x64@0.25.3': - optional: true - '@esbuild/win32-x64@0.25.8': optional: true @@ -1706,22 +1310,19 @@ snapshots: '@istanbuljs/schema@0.1.3': {} - '@jridgewell/gen-mapping@0.3.8': + '@jridgewell/gen-mapping@0.3.12': dependencies: - '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/sourcemap-codec': 1.5.4 + '@jridgewell/trace-mapping': 0.3.29 '@jridgewell/resolve-uri@3.1.2': {} - '@jridgewell/set-array@1.2.1': {} - - '@jridgewell/sourcemap-codec@1.5.0': {} + '@jridgewell/sourcemap-codec@1.5.4': {} - '@jridgewell/trace-mapping@0.3.25': + '@jridgewell/trace-mapping@0.3.29': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.4 '@lezer/common@1.2.3': {} @@ -1738,127 +1339,71 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@rollup/rollup-android-arm-eabi@4.40.1': - optional: true - - '@rollup/rollup-android-arm-eabi@4.46.1': - optional: true - - '@rollup/rollup-android-arm64@4.40.1': - optional: true - - '@rollup/rollup-android-arm64@4.46.1': - optional: true - - '@rollup/rollup-darwin-arm64@4.40.1': - optional: true - - '@rollup/rollup-darwin-arm64@4.46.1': - optional: true - - '@rollup/rollup-darwin-x64@4.40.1': - optional: true - - '@rollup/rollup-darwin-x64@4.46.1': - optional: true - - '@rollup/rollup-freebsd-arm64@4.40.1': - optional: true - - '@rollup/rollup-freebsd-arm64@4.46.1': - optional: true - - '@rollup/rollup-freebsd-x64@4.40.1': - optional: true - - '@rollup/rollup-freebsd-x64@4.46.1': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.40.1': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.46.1': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.40.1': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.46.1': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.40.1': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.46.1': + '@rollup/rollup-android-arm-eabi@4.46.2': optional: true - '@rollup/rollup-linux-arm64-musl@4.40.1': + '@rollup/rollup-android-arm64@4.46.2': optional: true - '@rollup/rollup-linux-arm64-musl@4.46.1': + '@rollup/rollup-darwin-arm64@4.46.2': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.40.1': + '@rollup/rollup-darwin-x64@4.46.2': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.46.1': + '@rollup/rollup-freebsd-arm64@4.46.2': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.40.1': + '@rollup/rollup-freebsd-x64@4.46.2': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.46.1': + '@rollup/rollup-linux-arm-gnueabihf@4.46.2': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.40.1': + '@rollup/rollup-linux-arm-musleabihf@4.46.2': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.46.1': + '@rollup/rollup-linux-arm64-gnu@4.46.2': optional: true - '@rollup/rollup-linux-riscv64-musl@4.40.1': + '@rollup/rollup-linux-arm64-musl@4.46.2': optional: true - '@rollup/rollup-linux-riscv64-musl@4.46.1': + '@rollup/rollup-linux-loongarch64-gnu@4.46.2': optional: true - '@rollup/rollup-linux-s390x-gnu@4.40.1': + '@rollup/rollup-linux-ppc64-gnu@4.46.2': optional: true - '@rollup/rollup-linux-s390x-gnu@4.46.1': + '@rollup/rollup-linux-riscv64-gnu@4.46.2': optional: true - '@rollup/rollup-linux-x64-gnu@4.40.1': + '@rollup/rollup-linux-riscv64-musl@4.46.2': optional: true - '@rollup/rollup-linux-x64-gnu@4.46.1': + '@rollup/rollup-linux-s390x-gnu@4.46.2': optional: true - '@rollup/rollup-linux-x64-musl@4.40.1': + '@rollup/rollup-linux-x64-gnu@4.46.2': optional: true - '@rollup/rollup-linux-x64-musl@4.46.1': + '@rollup/rollup-linux-x64-musl@4.46.2': optional: true - '@rollup/rollup-win32-arm64-msvc@4.40.1': + '@rollup/rollup-win32-arm64-msvc@4.46.2': optional: true - '@rollup/rollup-win32-arm64-msvc@4.46.1': + '@rollup/rollup-win32-ia32-msvc@4.46.2': optional: true - '@rollup/rollup-win32-ia32-msvc@4.40.1': + '@rollup/rollup-win32-x64-msvc@4.46.2': optional: true - '@rollup/rollup-win32-ia32-msvc@4.46.1': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.40.1': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.46.1': - optional: true + '@types/chai@5.2.2': + dependencies: + '@types/deep-eql': 4.0.2 - '@types/estree@1.0.7': {} + '@types/deep-eql@4.0.2': {} '@types/estree@1.0.8': {} @@ -1869,11 +1414,12 @@ snapshots: '@types/pegjs@0.10.6': {} - '@vitest/coverage-v8@3.1.3(vitest@3.1.3(@types/node@22.13.1)(happy-dom@15.11.7)(jsdom@26.1.0)(yaml@2.8.0))': + '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/node@22.13.1)(happy-dom@15.11.7)(jsdom@26.1.0)(yaml@2.8.0))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 - debug: 4.4.0 + ast-v8-to-istanbul: 0.3.3 + debug: 4.4.1 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 @@ -1883,51 +1429,53 @@ snapshots: std-env: 3.9.0 test-exclude: 7.0.1 tinyrainbow: 2.0.0 - vitest: 3.1.3(@types/node@22.13.1)(happy-dom@15.11.7)(jsdom@26.1.0)(yaml@2.8.0) + vitest: 3.2.4(@types/node@22.13.1)(happy-dom@15.11.7)(jsdom@26.1.0)(yaml@2.8.0) transitivePeerDependencies: - supports-color - '@vitest/expect@3.1.3': + '@vitest/expect@3.2.4': dependencies: - '@vitest/spy': 3.1.3 - '@vitest/utils': 3.1.3 - chai: 5.2.0 + '@types/chai': 5.2.2 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.2.1 tinyrainbow: 2.0.0 - '@vitest/mocker@3.1.3(vite@6.3.5(@types/node@22.13.1)(yaml@2.8.0))': + '@vitest/mocker@3.2.4(vite@7.1.1(@types/node@22.13.1)(yaml@2.8.0))': dependencies: - '@vitest/spy': 3.1.3 + '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 6.3.5(@types/node@22.13.1)(yaml@2.8.0) + vite: 7.1.1(@types/node@22.13.1)(yaml@2.8.0) - '@vitest/pretty-format@3.1.3': + '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 - '@vitest/runner@3.1.3': + '@vitest/runner@3.2.4': dependencies: - '@vitest/utils': 3.1.3 + '@vitest/utils': 3.2.4 pathe: 2.0.3 + strip-literal: 3.0.0 - '@vitest/snapshot@3.1.3': + '@vitest/snapshot@3.2.4': dependencies: - '@vitest/pretty-format': 3.1.3 + '@vitest/pretty-format': 3.2.4 magic-string: 0.30.17 pathe: 2.0.3 - '@vitest/spy@3.1.3': + '@vitest/spy@3.2.4': dependencies: - tinyspy: 3.0.2 + tinyspy: 4.0.3 - '@vitest/utils@3.1.3': + '@vitest/utils@3.2.4': dependencies: - '@vitest/pretty-format': 3.1.3 - loupe: 3.1.3 + '@vitest/pretty-format': 3.2.4 + loupe: 3.2.0 tinyrainbow: 2.0.0 - agent-base@7.1.3: {} + agent-base@7.1.4: {} ansi-regex@5.0.1: {} @@ -1941,35 +1489,41 @@ snapshots: assertion-error@2.0.1: {} + ast-v8-to-istanbul@0.3.3: + dependencies: + '@jridgewell/trace-mapping': 0.3.29 + estree-walker: 3.0.3 + js-tokens: 9.0.1 + balanced-match@1.0.2: {} big-integer@1.6.52: {} - brace-expansion@2.0.1: + brace-expansion@2.0.2: dependencies: balanced-match: 1.0.2 cac@6.7.14: {} - chai@5.2.0: + chai@5.2.1: dependencies: assertion-error: 2.0.1 check-error: 2.1.1 deep-eql: 5.0.2 - loupe: 3.1.3 - pathval: 2.0.0 + loupe: 3.2.0 + pathval: 2.0.1 check-error@2.1.1: {} - codemirror@6.0.1: + codemirror@6.0.2: dependencies: - '@codemirror/autocomplete': 6.18.4 - '@codemirror/commands': 6.8.0 - '@codemirror/language': 6.10.8 + '@codemirror/autocomplete': 6.18.6 + '@codemirror/commands': 6.8.1 + '@codemirror/language': 6.11.2 '@codemirror/lint': 6.8.5 - '@codemirror/search': 6.5.8 - '@codemirror/state': 6.5.0 - '@codemirror/view': 6.36.7 + '@codemirror/search': 6.5.11 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.1 color-convert@2.0.1: dependencies: @@ -1985,9 +1539,9 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - cssstyle@4.3.1: + cssstyle@4.6.0: dependencies: - '@asamuzakjp/css-color': 3.1.4 + '@asamuzakjp/css-color': 3.2.0 rrweb-cssom: 0.8.0 data-urls@5.0.0: @@ -1995,15 +1549,11 @@ snapshots: whatwg-mimetype: 4.0.0 whatwg-url: 14.2.0 - debug@4.4.0: - dependencies: - ms: 2.1.3 - debug@4.4.1: dependencies: ms: 2.1.3 - decimal.js@10.5.0: {} + decimal.js@10.6.0: {} deep-eql@5.0.2: {} @@ -2016,38 +1566,10 @@ snapshots: entities@4.5.0: optional: true - entities@6.0.0: {} + entities@6.0.1: {} es-module-lexer@1.7.0: {} - esbuild@0.25.3: - optionalDependencies: - '@esbuild/aix-ppc64': 0.25.3 - '@esbuild/android-arm': 0.25.3 - '@esbuild/android-arm64': 0.25.3 - '@esbuild/android-x64': 0.25.3 - '@esbuild/darwin-arm64': 0.25.3 - '@esbuild/darwin-x64': 0.25.3 - '@esbuild/freebsd-arm64': 0.25.3 - '@esbuild/freebsd-x64': 0.25.3 - '@esbuild/linux-arm': 0.25.3 - '@esbuild/linux-arm64': 0.25.3 - '@esbuild/linux-ia32': 0.25.3 - '@esbuild/linux-loong64': 0.25.3 - '@esbuild/linux-mips64el': 0.25.3 - '@esbuild/linux-ppc64': 0.25.3 - '@esbuild/linux-riscv64': 0.25.3 - '@esbuild/linux-s390x': 0.25.3 - '@esbuild/linux-x64': 0.25.3 - '@esbuild/netbsd-arm64': 0.25.3 - '@esbuild/netbsd-x64': 0.25.3 - '@esbuild/openbsd-arm64': 0.25.3 - '@esbuild/openbsd-x64': 0.25.3 - '@esbuild/sunos-x64': 0.25.3 - '@esbuild/win32-arm64': 0.25.3 - '@esbuild/win32-ia32': 0.25.3 - '@esbuild/win32-x64': 0.25.3 - esbuild@0.25.8: optionalDependencies: '@esbuild/aix-ppc64': 0.25.8 @@ -2079,19 +1601,15 @@ snapshots: estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.7 + '@types/estree': 1.0.8 - expect-type@1.2.1: {} - - fdir@6.4.4(picomatch@4.0.2): - optionalDependencies: - picomatch: 4.0.2 + expect-type@1.2.2: {} fdir@6.4.6(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 - foreground-child@3.3.0: + foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 signal-exit: 4.1.0 @@ -2101,7 +1619,7 @@ snapshots: glob@10.4.5: dependencies: - foreground-child: 3.3.0 + foreground-child: 3.3.1 jackspeak: 3.4.3 minimatch: 9.0.5 minipass: 7.1.2 @@ -2125,14 +1643,14 @@ snapshots: http-proxy-agent@7.0.2: dependencies: - agent-base: 7.1.3 + agent-base: 7.1.4 debug: 4.4.1 transitivePeerDependencies: - supports-color https-proxy-agent@7.0.6: dependencies: - agent-base: 7.1.3 + agent-base: 7.1.4 debug: 4.4.1 transitivePeerDependencies: - supports-color @@ -2157,8 +1675,8 @@ snapshots: istanbul-lib-source-maps@5.0.6: dependencies: - '@jridgewell/trace-mapping': 0.3.25 - debug: 4.4.0 + '@jridgewell/trace-mapping': 0.3.29 + debug: 4.4.1 istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: - supports-color @@ -2174,16 +1692,18 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 + js-tokens@9.0.1: {} + jsdom@26.1.0: dependencies: - cssstyle: 4.3.1 + cssstyle: 4.6.0 data-urls: 5.0.0 - decimal.js: 10.5.0 + decimal.js: 10.6.0 html-encoding-sniffer: 4.0.0 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.20 + nwsapi: 2.2.21 parse5: 7.3.0 rrweb-cssom: 0.8.0 saxes: 6.0.0 @@ -2194,34 +1714,34 @@ snapshots: whatwg-encoding: 3.1.1 whatwg-mimetype: 4.0.0 whatwg-url: 14.2.0 - ws: 8.18.1 + ws: 8.18.3 xml-name-validator: 5.0.0 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - loupe@3.1.3: {} + loupe@3.2.0: {} lru-cache@10.4.3: {} magic-string@0.30.17: dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.4 magicast@0.3.5: dependencies: - '@babel/parser': 7.26.3 - '@babel/types': 7.26.3 + '@babel/parser': 7.28.0 + '@babel/types': 7.28.2 source-map-js: 1.2.1 make-dir@4.0.0: dependencies: - semver: 7.6.3 + semver: 7.7.2 minimatch@9.0.5: dependencies: - brace-expansion: 2.0.1 + brace-expansion: 2.0.2 minipass@7.1.2: {} @@ -2229,18 +1749,18 @@ snapshots: nanoid@3.3.11: {} - node-sql-parser@5.3.10: + node-sql-parser@5.3.11: dependencies: '@types/pegjs': 0.10.6 big-integer: 1.6.52 - nwsapi@2.2.20: {} + nwsapi@2.2.21: {} package-json-from-dist@1.0.1: {} parse5@7.3.0: dependencies: - entities: 6.0.0 + entities: 6.0.1 path-key@3.1.1: {} @@ -2251,20 +1771,12 @@ snapshots: pathe@2.0.3: {} - pathval@2.0.0: {} + pathval@2.0.1: {} picocolors@1.1.1: {} - picomatch@4.0.2: {} - picomatch@4.0.3: {} - postcss@8.5.3: - dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 - postcss@8.5.6: dependencies: nanoid: 3.3.11 @@ -2273,56 +1785,30 @@ snapshots: punycode@2.3.1: {} - rollup@4.40.1: - dependencies: - '@types/estree': 1.0.7 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.40.1 - '@rollup/rollup-android-arm64': 4.40.1 - '@rollup/rollup-darwin-arm64': 4.40.1 - '@rollup/rollup-darwin-x64': 4.40.1 - '@rollup/rollup-freebsd-arm64': 4.40.1 - '@rollup/rollup-freebsd-x64': 4.40.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.40.1 - '@rollup/rollup-linux-arm-musleabihf': 4.40.1 - '@rollup/rollup-linux-arm64-gnu': 4.40.1 - '@rollup/rollup-linux-arm64-musl': 4.40.1 - '@rollup/rollup-linux-loongarch64-gnu': 4.40.1 - '@rollup/rollup-linux-powerpc64le-gnu': 4.40.1 - '@rollup/rollup-linux-riscv64-gnu': 4.40.1 - '@rollup/rollup-linux-riscv64-musl': 4.40.1 - '@rollup/rollup-linux-s390x-gnu': 4.40.1 - '@rollup/rollup-linux-x64-gnu': 4.40.1 - '@rollup/rollup-linux-x64-musl': 4.40.1 - '@rollup/rollup-win32-arm64-msvc': 4.40.1 - '@rollup/rollup-win32-ia32-msvc': 4.40.1 - '@rollup/rollup-win32-x64-msvc': 4.40.1 - fsevents: 2.3.3 - - rollup@4.46.1: + rollup@4.46.2: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.46.1 - '@rollup/rollup-android-arm64': 4.46.1 - '@rollup/rollup-darwin-arm64': 4.46.1 - '@rollup/rollup-darwin-x64': 4.46.1 - '@rollup/rollup-freebsd-arm64': 4.46.1 - '@rollup/rollup-freebsd-x64': 4.46.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.46.1 - '@rollup/rollup-linux-arm-musleabihf': 4.46.1 - '@rollup/rollup-linux-arm64-gnu': 4.46.1 - '@rollup/rollup-linux-arm64-musl': 4.46.1 - '@rollup/rollup-linux-loongarch64-gnu': 4.46.1 - '@rollup/rollup-linux-ppc64-gnu': 4.46.1 - '@rollup/rollup-linux-riscv64-gnu': 4.46.1 - '@rollup/rollup-linux-riscv64-musl': 4.46.1 - '@rollup/rollup-linux-s390x-gnu': 4.46.1 - '@rollup/rollup-linux-x64-gnu': 4.46.1 - '@rollup/rollup-linux-x64-musl': 4.46.1 - '@rollup/rollup-win32-arm64-msvc': 4.46.1 - '@rollup/rollup-win32-ia32-msvc': 4.46.1 - '@rollup/rollup-win32-x64-msvc': 4.46.1 + '@rollup/rollup-android-arm-eabi': 4.46.2 + '@rollup/rollup-android-arm64': 4.46.2 + '@rollup/rollup-darwin-arm64': 4.46.2 + '@rollup/rollup-darwin-x64': 4.46.2 + '@rollup/rollup-freebsd-arm64': 4.46.2 + '@rollup/rollup-freebsd-x64': 4.46.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.46.2 + '@rollup/rollup-linux-arm-musleabihf': 4.46.2 + '@rollup/rollup-linux-arm64-gnu': 4.46.2 + '@rollup/rollup-linux-arm64-musl': 4.46.2 + '@rollup/rollup-linux-loongarch64-gnu': 4.46.2 + '@rollup/rollup-linux-ppc64-gnu': 4.46.2 + '@rollup/rollup-linux-riscv64-gnu': 4.46.2 + '@rollup/rollup-linux-riscv64-musl': 4.46.2 + '@rollup/rollup-linux-s390x-gnu': 4.46.2 + '@rollup/rollup-linux-x64-gnu': 4.46.2 + '@rollup/rollup-linux-x64-musl': 4.46.2 + '@rollup/rollup-win32-arm64-msvc': 4.46.2 + '@rollup/rollup-win32-ia32-msvc': 4.46.2 + '@rollup/rollup-win32-x64-msvc': 4.46.2 fsevents: 2.3.3 rrweb-cssom@0.8.0: {} @@ -2333,7 +1819,7 @@ snapshots: dependencies: xmlchars: 2.2.0 - semver@7.6.3: {} + semver@7.7.2: {} shebang-command@2.0.0: dependencies: @@ -2371,6 +1857,10 @@ snapshots: dependencies: ansi-regex: 6.1.0 + strip-literal@3.0.0: + dependencies: + js-tokens: 9.0.1 + style-mod@4.1.2: {} supports-color@7.2.0: @@ -2389,21 +1879,16 @@ snapshots: tinyexec@0.3.2: {} - tinyglobby@0.2.13: - dependencies: - fdir: 6.4.4(picomatch@4.0.2) - picomatch: 4.0.2 - tinyglobby@0.2.14: dependencies: fdir: 6.4.6(picomatch@4.0.3) picomatch: 4.0.3 - tinypool@1.0.2: {} + tinypool@1.1.1: {} tinyrainbow@2.0.0: {} - tinyspy@3.0.2: {} + tinyspy@4.0.3: {} tldts-core@6.1.86: {} @@ -2419,18 +1904,18 @@ snapshots: dependencies: punycode: 2.3.1 - typescript@5.8.3: {} + typescript@5.9.2: {} undici-types@6.20.0: optional: true - vite-node@3.1.3(@types/node@22.13.1)(yaml@2.8.0): + vite-node@3.2.4(@types/node@22.13.1)(yaml@2.8.0): dependencies: cac: 6.7.14 - debug: 4.4.0 + debug: 4.4.1 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 6.3.5(@types/node@22.13.1)(yaml@2.8.0) + vite: 7.1.1(@types/node@22.13.1)(yaml@2.8.0) transitivePeerDependencies: - '@types/node' - jiti @@ -2445,54 +1930,43 @@ snapshots: - tsx - yaml - vite@6.3.5(@types/node@22.13.1)(yaml@2.8.0): - dependencies: - esbuild: 0.25.3 - fdir: 6.4.4(picomatch@4.0.2) - picomatch: 4.0.2 - postcss: 8.5.3 - rollup: 4.40.1 - tinyglobby: 0.2.13 - optionalDependencies: - '@types/node': 22.13.1 - fsevents: 2.3.3 - yaml: 2.8.0 - - vite@7.0.6(@types/node@22.13.1)(yaml@2.8.0): + vite@7.1.1(@types/node@22.13.1)(yaml@2.8.0): dependencies: esbuild: 0.25.8 fdir: 6.4.6(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.46.1 + rollup: 4.46.2 tinyglobby: 0.2.14 optionalDependencies: '@types/node': 22.13.1 fsevents: 2.3.3 yaml: 2.8.0 - vitest@3.1.3(@types/node@22.13.1)(happy-dom@15.11.7)(jsdom@26.1.0)(yaml@2.8.0): - dependencies: - '@vitest/expect': 3.1.3 - '@vitest/mocker': 3.1.3(vite@6.3.5(@types/node@22.13.1)(yaml@2.8.0)) - '@vitest/pretty-format': 3.1.3 - '@vitest/runner': 3.1.3 - '@vitest/snapshot': 3.1.3 - '@vitest/spy': 3.1.3 - '@vitest/utils': 3.1.3 - chai: 5.2.0 - debug: 4.4.0 - expect-type: 1.2.1 + vitest@3.2.4(@types/node@22.13.1)(happy-dom@15.11.7)(jsdom@26.1.0)(yaml@2.8.0): + dependencies: + '@types/chai': 5.2.2 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@7.1.1(@types/node@22.13.1)(yaml@2.8.0)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.2.1 + debug: 4.4.1 + expect-type: 1.2.2 magic-string: 0.30.17 pathe: 2.0.3 + picomatch: 4.0.3 std-env: 3.9.0 tinybench: 2.9.0 tinyexec: 0.3.2 - tinyglobby: 0.2.13 - tinypool: 1.0.2 + tinyglobby: 0.2.14 + tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 6.3.5(@types/node@22.13.1)(yaml@2.8.0) - vite-node: 3.1.3(@types/node@22.13.1)(yaml@2.8.0) + vite: 7.1.1(@types/node@22.13.1)(yaml@2.8.0) + vite-node: 3.2.4(@types/node@22.13.1)(yaml@2.8.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 22.13.1 @@ -2555,7 +2029,7 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.0 - ws@8.18.1: {} + ws@8.18.3: {} xml-name-validator@5.0.0: {} diff --git a/src/__tests__/index.test.ts b/src/__tests__/index.test.ts index 9252db0..793aced 100644 --- a/src/__tests__/index.test.ts +++ b/src/__tests__/index.test.ts @@ -1,3 +1,5 @@ +import { readdir } from "node:fs/promises"; +import { join } from "node:path"; import { describe, expect, it } from "vitest"; import * as exports from "../index"; @@ -6,15 +8,29 @@ describe("index.ts exports", () => { const sortedExports = Object.keys(exports).sort(); expect(sortedExports).toMatchInlineSnapshot(` [ - "SqlParser", + "DefaultSqlTooltipRenders", + "NodeSqlParser", "SqlStructureAnalyzer", "cteCompletionSource", + "defaultSqlHoverTheme", "sqlExtension", "sqlHover", - "sqlHoverTheme", "sqlLinter", "sqlStructureGutter", ] `); }); }); + +describe("keywords", async () => { + it("should have the correct structure, keywords.keywords should be an object", async () => { + const dataDir = join(__dirname, "../data"); + const files = await readdir(dataDir); + const keywordFiles = files.filter((file) => file.endsWith("-keywords.json")); + + for (const file of keywordFiles) { + const keywords = await import(`../data/${file}`); + expect(typeof keywords.keywords).toBe("object"); + } + }); +}); diff --git a/src/data/README.md b/src/data/README.md new file mode 100644 index 0000000..6033a8d --- /dev/null +++ b/src/data/README.md @@ -0,0 +1,39 @@ +# SQL Keywords + +This directory contains the SQL keywords for the different dialects. Keywords are stored here so they can be lazily loaded. + +## Structure + +By default, we use the `SqlKeywordInfo` type to define the keywords. + +```json +{ + "keywords": { + "keyword1": { + "description": "Description of the keyword", + "syntax": "Syntax of the keyword", + "example": "Example of the keyword", + "metadata": { + "tag1": "value1", + "tag2": "value2" + } + }, + "keyword2": { + "description": "Description of the keyword", + "syntax": "Syntax of the keyword", + "example": "Example of the keyword", + "metadata": { + "tag1": "value1", + "tag2": "value2" + } + } + } +} +``` + +## Adding a new dialect + +For compatibility with Vite and other bundlers, `import` returns a JS module and not a JSON object + +So we need to nest the keywords under a json key to access them, +otherwise a keyword can conflict with a JS reserved keyword (e.g. `default` or `with`) diff --git a/src/data/duckdb-keywords.json b/src/data/duckdb-keywords.json new file mode 100644 index 0000000..630f50f --- /dev/null +++ b/src/data/duckdb-keywords.json @@ -0,0 +1,360 @@ +{ + "keywords": { + "array_agg": { + "description": "Returns a LIST containing all the values of a column.", + "example": "list(A)" + }, + "array_aggr": { + "description": "Executes the aggregate function name on the elements of list", + "example": "list_aggregate([1, 2, NULL], 'min')" + }, + "array_aggregate": { + "description": "Executes the aggregate function name on the elements of list", + "example": "list_aggregate([1, 2, NULL], 'min')" + }, + "array_apply": { + "description": "Returns a list that is the result of applying the lambda function to each element of the input list. See the Lambda Functions section for more details", + "example": "list_transform([1, 2, 3], x -> x + 1)" + }, + "array_cat": { + "description": "Concatenates two lists.", + "example": "list_concat([2, 3], [4, 5, 6])" + }, + "array_concat": { + "description": "Concatenates two lists.", + "example": "list_concat([2, 3], [4, 5, 6])" + }, + "array_contains": { + "description": "Returns true if the list contains the element.", + "example": "list_contains([1, 2, NULL], 1)" + }, + "array_cosine_distance": { + "description": "Compute the cosine distance between two arrays of the same size. The array elements can not be NULL. The arrays can have any size as long as the size is the same for both arguments.", + "example": "array_cosine_distance([1, 2, 3], [1, 2, 3])" + }, + "array_cosine_similarity": { + "description": "Compute the cosine similarity between two arrays of the same size. The array elements can not be NULL. The arrays can have any size as long as the size is the same for both arguments.", + "example": "array_cosine_similarity([1, 2, 3], [1, 2, 3])" + }, + "array_cross_product": { + "description": "Compute the cross product of two arrays of size 3. The array elements can not be NULL.", + "example": "array_cross_product([1, 2, 3], [1, 2, 3])" + }, + "array_distance": { + "description": "Compute the distance between two arrays of the same size. The array elements can not be NULL. The arrays can have any size as long as the size is the same for both arguments.", + "example": "array_distance([1, 2, 3], [1, 2, 3])" + }, + "array_distinct": { + "description": "Removes all duplicates and NULLs from a list. Does not preserve the original order", + "example": "list_distinct([1, 1, NULL, -3, 1, 5])" + }, + "array_dot_product": { + "description": "Compute the inner product between two arrays of the same size. The array elements can not be NULL. The arrays can have any size as long as the size is the same for both arguments.", + "example": "array_inner_product([1, 2, 3], [1, 2, 3])" + }, + "array_extract": { + "description": "Extract the indexth (1-based) value from the array.", + "example": "array_extract('DuckDB', 2)" + }, + "array_filter": { + "description": "Constructs a list from those elements of the input list for which the lambda function returns true", + "example": "list_filter([3, 4, 5], x -> x > 4)" + }, + "array_grade_up": { + "description": "Returns the index of their sorted position.", + "example": "list_grade_up([3, 6, 1, 2])" + }, + "array_has": { + "description": "Returns true if the list contains the element.", + "example": "list_contains([1, 2, NULL], 1)" + }, + "array_has_all": { + "description": "Returns true if all elements of l2 are in l1. NULLs are ignored.", + "example": "list_has_all([1, 2, 3], [2, 3])" + }, + "array_has_any": { + "description": "Returns true if the lists have any element in common. NULLs are ignored.", + "example": "list_has_any([1, 2, 3], [2, 3, 4])" + }, + "array_indexof": { + "description": "Returns the index of the element if the list contains the element. If the element is not found, it returns NULL.", + "example": "list_position([1, 2, NULL], 2)" + }, + "array_inner_product": { + "description": "Compute the inner product between two arrays of the same size. The array elements can not be NULL. The arrays can have any size as long as the size is the same for both arguments.", + "example": "array_inner_product([1, 2, 3], [1, 2, 3])" + }, + "array_length": { + "description": "Returns the length of the `list`.", + "example": "array_length([1,2,3])" + }, + "array_negative_dot_product": { + "description": "Compute the negative inner product between two arrays of the same size. The array elements can not be NULL. The arrays can have any size as long as the size is the same for both arguments.", + "example": "array_negative_inner_product([1, 2, 3], [1, 2, 3])" + }, + "array_negative_inner_product": { + "description": "Compute the negative inner product between two arrays of the same size. The array elements can not be NULL. The arrays can have any size as long as the size is the same for both arguments.", + "example": "array_negative_inner_product([1, 2, 3], [1, 2, 3])" + }, + "array_position": { + "description": "Returns the index of the element if the list contains the element. If the element is not found, it returns NULL.", + "example": "list_position([1, 2, NULL], 2)" + }, + "array_reduce": { + "description": "Returns a single value that is the result of applying the lambda function to each element of the input list, starting with the first element and then repeatedly applying the lambda function to the result of the previous application and the next element of the list. When an initial value is provided, it is used as the first argument to the lambda function", + "example": "list_reduce([1, 2, 3], (x, y) -> x + y)" + }, + "array_resize": { + "description": "Resizes the list to contain size elements. Initializes new elements with value or NULL if value is not set.", + "example": "list_resize([1, 2, 3], 5, 0)" + }, + "array_reverse_sort": { + "description": "Sorts the elements of the list in reverse order", + "example": "list_reverse_sort([3, 6, 1, 2])" + }, + "array_select": { + "description": "Returns a list based on the elements selected by the index_list.", + "example": "list_select([10, 20, 30, 40], [1, 4])" + }, + "array_slice": { + "description": "list_slice with added step feature.", + "example": "list_slice([4, 5, 6], 2, 3)" + }, + "array_sort": { + "description": "Sorts the elements of the list", + "example": "list_sort([3, 6, 1, 2])" + }, + "array_transform": { + "description": "Returns a list that is the result of applying the lambda function to each element of the input list. See the Lambda Functions section for more details", + "example": "list_transform([1, 2, 3], x -> x + 1)" + }, + "array_unique": { + "description": "Counts the unique elements of a list", + "example": "list_unique([1, 1, NULL, -3, 1, 5])" + }, + "array_value": { + "description": "Create an ARRAY containing the argument values.", + "example": "array_value(4, 5, 6)" + }, + "array_where": { + "description": "Returns a list with the BOOLEANs in mask_list applied as a mask to the value_list.", + "example": "list_where([10, 20, 30, 40], [true, false, false, true])" + }, + "array_zip": { + "description": "Zips k LISTs to a new LIST whose length will be that of the longest list. Its elements are structs of k elements from each list list_1, …, list_k, missing elements are replaced with NULL. If truncate is set, all lists are truncated to the smallest list length.", + "example": "list_zip([1, 2], [3, 4], [5, 6])" + }, + "cast_to_type": { + "description": "Casts the first argument to the type of the second argument", + "example": "cast_to_type('42', NULL::INTEGER)" + }, + "concat": { + "description": "Concatenates many strings together.", + "example": "concat('Hello', ' ', 'World')" + }, + "concat_ws": { + "description": "Concatenates strings together separated by the specified separator.", + "example": "concat_ws(', ', 'Banana', 'Apple', 'Melon')" + }, + "contains": { + "description": "Returns true if the `list` contains the `element`.", + "example": "contains([1, 2, NULL], 1)" + }, + "count": { + "description": "Returns the number of non-null values in arg.", + "example": "count(A)" + }, + "count_if": { + "description": "Counts the total number of TRUE values for a boolean column", + "example": "count_if(A)" + }, + "countif": { + "description": "Counts the total number of TRUE values for a boolean column", + "example": "count_if(A)" + }, + "date_diff": { + "description": "The number of partition boundaries between the timestamps", + "example": "date_diff('hour', TIMESTAMPTZ '1992-09-30 23:59:59', TIMESTAMPTZ '1992-10-01 01:58:00')" + }, + "date_part": { + "description": "Get subfield (equivalent to extract)", + "example": "date_part('minute', TIMESTAMP '1992-09-20 20:38:40')" + }, + "date_sub": { + "description": "The number of complete partitions between the timestamps", + "example": "date_sub('hour', TIMESTAMPTZ '1992-09-30 23:59:59', TIMESTAMPTZ '1992-10-01 01:58:00')" + }, + "date_trunc": { + "description": "Truncate to specified precision", + "example": "date_trunc('hour', TIMESTAMPTZ '1992-09-20 20:38:40')" + }, + "datediff": { + "description": "The number of partition boundaries between the timestamps", + "example": "date_diff('hour', TIMESTAMPTZ '1992-09-30 23:59:59', TIMESTAMPTZ '1992-10-01 01:58:00')" + }, + "datepart": { + "description": "Get subfield (equivalent to extract)", + "example": "date_part('minute', TIMESTAMP '1992-09-20 20:38:40')" + }, + "datesub": { + "description": "The number of complete partitions between the timestamps", + "example": "date_sub('hour', TIMESTAMPTZ '1992-09-30 23:59:59', TIMESTAMPTZ '1992-10-01 01:58:00')" + }, + "datetrunc": { + "description": "Truncate to specified precision", + "example": "date_trunc('hour', TIMESTAMPTZ '1992-09-20 20:38:40')" + }, + "day": { + "description": "Extract the day component from a date or timestamp", + "example": "day(timestamp '2021-08-03 11:59:44.123456')" + }, + "dayname": { + "description": "The (English) name of the weekday", + "example": "dayname(TIMESTAMP '1992-03-22')" + }, + "dayofmonth": { + "description": "Extract the dayofmonth component from a date or timestamp", + "example": "dayofmonth(timestamp '2021-08-03 11:59:44.123456')" + }, + "dayofweek": { + "description": "Extract the dayofweek component from a date or timestamp", + "example": "dayofweek(timestamp '2021-08-03 11:59:44.123456')" + }, + "dayofyear": { + "description": "Extract the dayofyear component from a date or timestamp", + "example": "dayofyear(timestamp '2021-08-03 11:59:44.123456')" + }, + "generate_series": { + "description": "Create a list of values between start and stop - the stop parameter is inclusive", + "example": "generate_series(2, 5, 3)" + }, + "histogram": { + "description": "Returns a LIST of STRUCTs with the fields bucket and count.", + "example": "histogram(A)" + }, + "histogram_exact": { + "description": "Returns a LIST of STRUCTs with the fields bucket and count matching the buckets exactly.", + "example": "histogram_exact(A, [0, 1, 2])" + }, + "string_agg": { + "description": "Concatenates the column string values with an optional separator.", + "example": "string_agg(A, '-')" + }, + "string_split": { + "description": "Splits the `string` along the `separator`", + "example": "string_split('hello-world', '-')" + }, + "string_split_regex": { + "description": "Splits the `string` along the `regex`", + "example": "string_split_regex('hello world; 42', ';? ')" + }, + "string_to_array": { + "description": "Splits the `string` along the `separator`", + "example": "string_split('hello-world', '-')" + }, + "struct_concat": { + "description": "Merge the multiple STRUCTs into a single STRUCT.", + "example": "struct_concat(struct_pack(i := 4), struct_pack(s := 'string'))" + }, + "struct_extract": { + "description": "Extract the named entry from the STRUCT.", + "example": "struct_extract({'i': 3, 'v2': 3, 'v3': 0}, 'i')" + }, + "struct_extract_at": { + "description": "Extract the entry from the STRUCT by position (starts at 1!).", + "example": "struct_extract_at({'i': 3, 'v2': 3, 'v3': 0}, 2)" + }, + "struct_insert": { + "description": "Adds field(s)/value(s) to an existing STRUCT with the argument values. The entry name(s) will be the bound variable name(s)", + "example": "struct_insert({'a': 1}, b := 2)" + }, + "struct_pack": { + "description": "Create a STRUCT containing the argument values. The entry name will be the bound variable name.", + "example": "struct_pack(i := 4, s := 'string')" + }, + "substring": { + "description": "Extract substring of `length` characters starting from character `start`. Note that a start value of 1 refers to the first character of the `string`.", + "example": "substring('Hello', 2, 2)" + }, + "substring_grapheme": { + "description": "Extract substring of `length` grapheme clusters starting from character `start`. Note that a start value of 1 refers to the first character of the `string`.", + "example": "substring_grapheme('🦆🤦🏼‍♂️🤦🏽‍♀️🦆', 3, 2)" + }, + "to_base": { + "description": "Converts a value to a string in the given base radix, optionally padding with leading zeros to the minimum length", + "example": "to_base(42, 16)" + }, + "to_base64": { + "description": "Converts a `blob` to a base64 encoded `string`.", + "example": "base64('A'::BLOB)" + }, + "to_binary": { + "description": "Converts the value to binary representation", + "example": "bin(42)" + }, + "to_centuries": { + "description": "Construct a century interval", + "example": "to_centuries(5)" + }, + "to_days": { + "description": "Construct a day interval", + "example": "to_days(5)" + }, + "to_decades": { + "description": "Construct a decade interval", + "example": "to_decades(5)" + }, + "to_hex": { + "description": "Converts the value to hexadecimal representation.", + "example": "hex(42)" + }, + "to_hours": { + "description": "Construct a hour interval", + "example": "to_hours(5)" + }, + "to_microseconds": { + "description": "Construct a microsecond interval", + "example": "to_microseconds(5)" + }, + "to_millennia": { + "description": "Construct a millennium interval", + "example": "to_millennia(1)" + }, + "to_milliseconds": { + "description": "Construct a millisecond interval", + "example": "to_milliseconds(5.5)" + }, + "to_minutes": { + "description": "Construct a minute interval", + "example": "to_minutes(5)" + }, + "to_months": { + "description": "Construct a month interval", + "example": "to_months(5)" + }, + "to_quarters": { + "description": "Construct a quarter interval", + "example": "to_quarters(5)" + }, + "to_seconds": { + "description": "Construct a second interval", + "example": "to_seconds(5.5)" + }, + "to_timestamp": { + "description": "Converts secs since epoch to a timestamp with time zone", + "example": "to_timestamp(1284352323.5)" + }, + "to_weeks": { + "description": "Construct a week interval", + "example": "to_weeks(5)" + }, + "to_years": { + "description": "Construct a year interval", + "example": "to_years(5)" + }, + "trim": { + "description": "Removes any spaces from either side of the string.", + "example": "trim('>>>>test<<', '><')" + } + } +} diff --git a/src/dialects/duckdb/README.md b/src/dialects/duckdb/README.md new file mode 100644 index 0000000..a59d462 --- /dev/null +++ b/src/dialects/duckdb/README.md @@ -0,0 +1,16 @@ +# Generating SQL Dialects and Keywords + +Dialects enable CodeMirror to provide SQL syntax highlighting. + +Keywords allow CodeMirror to offer SQL autocompletion and display hover tooltips. + +| Database | How to Run Spec Script | +| -------- | --------------------------------------- | +| DuckDB | `python src/data/duckdb/spec_duckdb.py` | + +> 💡 **Tip:** Update the script path to match your target SQL dialect. +> Running the script will automatically generate the keywords and types for the corresponding `*.ts` dialect file. + +> 🚀 **Quick Start:** +> To open and run the script interactively in marimo, use: +> `uvx marimo edit ` diff --git a/src/dialects/duckdb/duckdb.ts b/src/dialects/duckdb/duckdb.ts new file mode 100644 index 0000000..81d1aaa --- /dev/null +++ b/src/dialects/duckdb/duckdb.ts @@ -0,0 +1,22 @@ +/* Copyright 2025 Marimo. All rights reserved. */ +// Credit to https://github.com/sekuel/codemirror-sql-duckdb/blob/main/DuckDBDialect.js for the dialect spec + +import { SQLDialect, type SQLDialectSpec } from "@codemirror/lang-sql"; + +const otherFunctions = + "percentile_cont row_number rank dense_rank rank_dense percent_rank cume_dist ntile lag lead first_value last_value nth_value"; + +const DuckDBDialectSpec: SQLDialectSpec = { + charSetCasts: true, + doubleQuotedStrings: false, + unquotedBitLiterals: true, + hashComments: false, + spaceAfterDashes: false, + specialVar: "@?", + identifierQuotes: "`", + keywords: `${otherFunctions} !__postfix !~~ !~~* % & && * ** + - ->> / // <-> << <=> <@ >> @ @> Calendar JSON TimeZone ^ ^@ abort abs absolute access access_mode acos acosh action add add_parquet_key admin after age aggregate alias all all_profiling_output allow_community_extensions allow_extensions_metadata_mismatch allow_persistent_secrets allow_unredacted_secrets allow_unsigned_extensions allowed_directories allowed_paths also alter always analyse analyze and anti any any_value apply approx_count_distinct approx_quantile approx_top_k arbitrary arg_max arg_max_null arg_min arg_min_null argmax argmin array array_agg array_aggr array_aggregate array_append array_apply array_cat array_concat array_contains array_cosine_distance array_cosine_similarity array_cross_product array_distance array_distinct array_dot_product array_extract array_filter array_grade_up array_has array_has_all array_has_any array_indexof array_inner_product array_intersect array_length array_negative_dot_product array_negative_inner_product array_pop_back array_pop_front array_position array_prepend array_push_back array_push_front array_reduce array_resize array_reverse array_reverse_sort array_select array_slice array_sort array_to_json array_to_string array_to_string_comma_default array_transform array_unique array_value array_where array_zip arrow_large_buffer_size arrow_lossless_conversion arrow_output_list_view arrow_output_version arrow_scan arrow_scan_dumb as asc ascii asin asinh asof asof_loop_join_threshold assertion assignment asymmetric at atan atan2 atanh attach attribute authorization autoinstall_extension_repository autoinstall_known_extensions autoload_known_extensions avg backward bar base64 before begin between bigint bin binary binary_as_string bit bit_and bit_count bit_length bit_or bit_position bit_xor bitstring bitstring_agg blob bool bool_and bool_or boolean both bpchar by bytea cache call called can_cast_implicitly cardinality cascade cascaded case cast cast_to_type catalog catalog_error_max_schemas cbrt ceil ceiling centuries century chain char char_length character character_length characteristics check checkpoint checkpoint_threshold chr class close cluster coalesce col_description collate collation collations column columns combine comment comments commit committed compression concat concat_ws concurrently configuration conflict connection constant_or_null constraint constraints contains content continue conversion copy copy_database corr cos cosh cost cot count count_if count_star countif covar_pop covar_samp create create_sort_key cross csv cube current current_catalog current_connection_id current_database current_date current_localtime current_localtimestamp current_query current_query_id current_role current_schema current_schemas current_setting current_transaction_id current_user currval cursor custom_extension_repository custom_profiling_settings custom_user_agent cycle damerau_levenshtein data database database_list database_size date date_add date_diff date_part date_sub date_trunc datediff datepart datesub datetime datetrunc day dayname dayofmonth dayofweek dayofyear days deallocate debug_asof_iejoin debug_checkpoint_abort debug_force_external debug_force_no_cross_product debug_skip_checkpoint_on_commit debug_verify_vector debug_window_mode dec decade decades decimal declare decode default default_block_size default_collation default_null_order default_order default_secret_storage defaults deferrable deferred definer degrees delete delimiter delimiters depends desc describe detach dictionary disable disable_checkpoint_on_shutdown disable_logging disable_object_cache disable_optimizer disable_parquet_prefetching disable_print_progress_bar disable_profile disable_profiling disable_progress_bar disable_timestamptz_casts disable_verification disable_verify_external disable_verify_fetch_row disable_verify_parallelism disable_verify_serializer disabled_compression_methods disabled_filesystems disabled_log_types disabled_optimizers discard distinct divide do document domain double drop duckdb_api duckdb_columns duckdb_constraints duckdb_databases duckdb_dependencies duckdb_extensions duckdb_external_file_cache duckdb_functions duckdb_indexes duckdb_keywords duckdb_log_contexts duckdb_logs duckdb_logs_parsed duckdb_memory duckdb_optimizers duckdb_prepared_statements duckdb_schemas duckdb_secret_types duckdb_secrets duckdb_sequences duckdb_settings duckdb_table_sample duckdb_tables duckdb_temporary_files duckdb_types duckdb_variables duckdb_views dynamic_or_filter_threshold each editdist3 element_at else enable enable_checkpoint_on_shutdown enable_external_access enable_external_file_cache enable_fsst_vectors enable_geoparquet_conversion enable_http_logging enable_http_metadata_cache enable_logging enable_macro_dependencies enable_object_cache enable_optimizer enable_print_progress_bar enable_profile enable_profiling enable_progress_bar enable_progress_bar_print enable_verification enable_view_dependencies enabled_log_types encode encoding encrypted end ends_with entropy enum enum_code enum_first enum_last enum_range enum_range_boundary epoch epoch_ms epoch_ns epoch_us equi_width_bins era error errors_as_json escape even event except exclude excluding exclusive execute exists exp explain explain_output export export_state extension extension_directory extension_versions extensions external external_threads extract factorial false family favg fdiv fetch file_search_path filter finalize first flatten float float4 float8 floor fmod following for force force_bitpacking_mode force_checkpoint force_compression foreign format formatReadableDecimalSize formatReadableSize format_bytes format_pg_type format_type forward freeze from from_base64 from_binary from_hex from_json from_json_strict fsum full function functions gamma gcd gen_random_uuid generate_series generate_subscripts generated geomean geometric_mean get_bit get_block_size get_current_time get_current_timestamp getvariable glob global grade_up grant granted greatest greatest_common_divisor group group_concat grouping grouping_id groups guid hamming handler having header hex histogram histogram_exact histogram_values hold home_directory hour hours http_logging_output http_proxy http_proxy_password http_proxy_username hugeint identity ieee_floating_point_ops if ignore ilike ilike_escape immediate immediate_transaction_mode immutable implicit import import_database in in_search_path include including increment index index_scan_max_count index_scan_percentage indexes inet_client_addr inet_client_port inet_server_addr inet_server_port inherit inherits initially inline inner inout input insensitive insert install instead instr int int1 int128 int16 int2 int32 int4 int64 int8 integer integer_division integral intersect interval into invoker is is_histogram_other_bin isfinite isinf isnan isnull isodow isolation isoyear jaccard jaro_similarity jaro_winkler_similarity join json json_array json_array_length json_contains json_deserialize_sql json_each json_execute_serialized_sql json_exists json_extract json_extract_path json_extract_path_text json_extract_string json_group_array json_group_object json_group_structure json_keys json_merge_patch json_object json_pretty json_quote json_serialize_plan json_serialize_sql json_structure json_transform json_transform_strict json_tree json_type json_valid json_value julian kahan_sum key kurtosis kurtosis_pop label lambda lambda_syntax language large last last_day late_materialization_max_rows lateral lcase lcm leading leakproof least least_common_multiple left left_grapheme len length length_grapheme level levenshtein lgamma like like_escape limit list list_aggr list_aggregate list_any_value list_append list_apply list_approx_count_distinct list_avg list_bit_and list_bit_or list_bit_xor list_bool_and list_bool_or list_cat list_concat list_contains list_cosine_distance list_cosine_similarity list_count list_distance list_distinct list_dot_product list_element list_entropy list_extract list_filter list_first list_grade_up list_has list_has_all list_has_any list_histogram list_indexof list_inner_product list_intersect list_kurtosis list_kurtosis_pop list_last list_mad list_max list_median list_min list_mode list_negative_dot_product list_negative_inner_product list_pack list_position list_prepend list_product list_reduce list_resize list_reverse list_reverse_sort list_select list_sem list_skewness list_slice list_sort list_stddev_pop list_stddev_samp list_string_agg list_sum list_transform list_unique list_value list_var_pop list_var_samp list_where list_zip listagg listen ln load local location lock lock_configuration locked log log10 log2 log_query_path logged logging_level logging_mode logging_storage logical long lower lpad ltrim macro mad make_date make_time make_timestamp make_timestamp_ns make_timestamptz map map_concat map_contains map_contains_entry map_contains_value map_entries map_extract map_extract_value map_from_entries map_keys map_to_pg_oid map_values mapping match materialized max max_by max_expression_depth max_memory max_temp_directory_size max_vacuum_tasks maxvalue md5 md5_number md5_number_lower md5_number_upper mean median memory_limit merge_join_threshold metadata_info method microsecond microseconds millennia millennium millisecond milliseconds min min_by minute minutes minvalue mismatches mod mode month monthname months move multiply name names nanosecond national natural nchar nested_loop_join_threshold new next nextafter nextval nfc_normalize no none normalized_interval not not_ilike_escape not_like_escape nothing notify notnull now nowait null null_order nullif nulls numeric nvarchar obj_description object octet_length of off offset oid oids old old_implicit_casting on only operator option options or ord order order_by_non_integer_literal ordered_aggregate_threshold ordinality others out outer over overlaps overlay overriding owned owner pandas_analyze_sample pandas_scan parallel parquet_bloom_probe parquet_file_metadata parquet_kv_metadata parquet_metadata parquet_metadata_cache parquet_scan parquet_schema parse_dirname parse_dirpath parse_duckdb_log_message parse_filename parse_path parser partial partition partitioned partitioned_write_flush_threshold partitioned_write_max_open_files passing password percent perfect_ht_threshold persistent pi pivot pivot_filter_threshold pivot_limit pivot_longer pivot_wider placing plans platform policy position positional pow power pragma pragma_collations pragma_database_size pragma_metadata_info pragma_platform pragma_show pragma_storage_info pragma_table_info pragma_user_agent pragma_version preceding precision prefer_range_joins prefetch_all_parquet_files prefix prepare prepared preserve preserve_identifier_case preserve_insertion_order primary printf prior privileges procedural procedure produce_arrow_string_view product profile_output profiling_mode profiling_output program progress_bar_time publication python_enable_replacements python_map_function python_scan_all_frames qualify quantile quantile_cont quantile_disc quarter quarters query query_table quote radians random range read read_blob read_csv read_csv_auto read_json read_json_auto read_json_objects read_json_objects_auto read_ndjson read_ndjson_auto read_ndjson_objects read_parquet read_text real reassign recheck recursive reduce ref references referencing refresh regexp_escape regexp_extract regexp_extract_all regexp_full_match regexp_matches regexp_replace regexp_split_to_array regexp_split_to_table regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy regr_syy reindex relative release remap_struct rename repeat repeat_row repeatable replace replica reservoir_quantile reset respect restart restrict returning returns reverse revoke right right_grapheme role rollback rollup round round_even roundbankers row row_to_json rows rpad rtrim rule sample savepoint scalar_subquery_error_on_multiple_rows scheduler_process_partial schema schemas scope scroll search search_path second seconds secret secret_directory security select sem semi seq_scan sequence sequences serializable server session session_user set set_bit setof sets setseed sha1 sha256 share shobj_description short show show_databases show_tables show_tables_expanded sign signbit signed similar simple sin sinh skewness skip smallint snapshot sniff_csv some sorted split split_part sql sqrt stable standalone start starts_with statement statistics stats stddev stddev_pop stddev_samp stdin stdout storage storage_compatibility_version storage_info stored str_split str_split_regex streaming_buffer_size strftime strict string string_agg string_split string_split_regex string_to_array strip strip_accents strlen strpos strptime struct struct_concat struct_extract struct_extract_at struct_insert struct_pack subscription substr substring substring_grapheme subtract suffix sum sum_no_overflow sumkahan summarize summary symmetric sysid system table table_info tables tablesample tablespace tan tanh temp temp_directory template temporary test_all_types test_vector_types text then threads ties time time_bucket timestamp timestamp_ms timestamp_ns timestamp_s timestamp_us timestamptz timetz timetz_byte_comparable timezone timezone_hour timezone_minute tinyint to to_base to_base64 to_binary to_centuries to_days to_decades to_hex to_hours to_json to_microseconds to_millennia to_milliseconds to_minutes to_months to_quarters to_seconds to_timestamp to_weeks to_years today trailing transaction transaction_timestamp transform translate treat trigger trim true trunc truncate truncate_duckdb_logs trusted try_cast try_strptime txid_current type typeof types ubigint ucase uhugeint uint128 uint16 uint32 uint64 uint8 uinteger unbin unbounded uncommitted unencrypted unhex unicode union union_extract union_tag union_value unique unknown unlisten unlogged unnest unpack unpivot unpivot_list until update upper url_decode url_encode use user user_agent username using usmallint utinyint uuid uuid_extract_timestamp uuid_extract_version uuidv4 uuidv7 vacuum valid validate validator value values var_pop var_samp varbinary varchar variable variadic variance varint varying vector_type verbose verify_external verify_fetch_row verify_parallelism verify_serializer version view views virtual volatile wal_autocheckpoint wavg week weekday weekofyear weeks weighted_avg when where which_secret whitespace window with within without work worker_threads wrapper write write_log xml xmlattributes xmlconcat xmlelement xmlexists xmlforest xmlnamespaces xmlparse xmlpi xmlroot xmlserialize xmltable xor year years yearweek yes zone zstd_min_string_length | || ~ ~~ ~~* ~~~`, + types: + "JSON bigint binary bit bitstring blob bool boolean bpchar bytea char date datetime dec decimal double enum float float4 float8 guid hugeint int int1 int128 int16 int2 int32 int4 int64 int8 integer integral interval list logical long map null numeric nvarchar oid real row short signed smallint string struct text time timestamp timestamp_ms timestamp_ns timestamp_s timestamp_us timestamptz timetz tinyint ubigint uhugeint uint128 uint16 uint32 uint64 uint8 uinteger union usmallint utinyint uuid varbinary varchar varint", +}; + +export const DuckDBDialect = SQLDialect.define(DuckDBDialectSpec); diff --git a/src/dialects/duckdb/spec_duckdb.py b/src/dialects/duckdb/spec_duckdb.py new file mode 100644 index 0000000..c714631 --- /dev/null +++ b/src/dialects/duckdb/spec_duckdb.py @@ -0,0 +1,367 @@ +# Copyright 2025 Marimo. All rights reserved. +# /// script +# requires-python = ">=3.12" +# dependencies = [ +# "duckdb==1.3.2", +# "marimo", +# "polars==1.31.0", +# "pyarrow==21.0.0", +# "sqlglot==27.4.1", +# ] +# /// + +# Ignores lack of return type for functions +# ruff: noqa: ANN202 +# mypy: disable-error-code="no-untyped-def" + +# Ignore SQL Types +# ruff: noqa: F541 + +import marimo + +__generated_with = "0.14.16" +app = marimo.App(width="medium") + +with app.setup: + import argparse + from datetime import datetime + import os + + import duckdb + import polars as pl + import json + + import marimo as mo + from marimo import _loggers + + LOGGER = _loggers.marimo_logger() + + +@app.cell +def _(): + EXCLUDED_KEYWORDS = ["__internal", "icu", "has_", "pg_", "allocator"] + return (EXCLUDED_KEYWORDS,) + + +@app.cell(hide_code=True) +def _(EXCLUDED_KEYWORDS, form, num_keywords, num_types): + mo.md( + rf""" + ## DuckDB Schema + + #### Number of keywords: **{num_keywords}** + + #### Number of types: **{num_types}** + + To update the duckdb codemirror spec, you can either + + - Run this python script to generate the outputs or + - Submit the form below + + {form} + + ❗ And then update the duckdb.ts file + + *Excluded keywords: {EXCLUDED_KEYWORDS} + + _{mo.md(f"Ran on DuckDB {duckdb.__version__}").right()}_ + _{mo.md(f"Last updated: {datetime.today().date().strftime('%B %d %Y')}").right()}_ + """ + ) + return + + +@app.cell +def _(df, fn_dict, form, save_to_json, write_to_file): + parser = argparse.ArgumentParser( + prog="DuckDB Spec", + description="Runs SQL to save types and keywords for DuckDB dialect in Codemirror", + ) + + parser.add_argument("-s", "--savepath", default="temp.json") + args = parser.parse_args() + + if mo.app_meta().mode == "script": + savepath = args.savepath + LOGGER.info(f"Saving JSON files to {savepath}") + write_to_file(df, savepath) + save_to_json(fn_dict, f"{savepath}/functions.json") + LOGGER.info(f"Saved files to {savepath}") + else: + savepath = form.value + if savepath is not None and savepath.strip() != "": + if not os.path.exists(savepath): + os.makedirs(savepath) + + write_to_file(df, f"{savepath}/keywords.json") + save_to_json(fn_dict, f"{savepath}/functions.json") + + mo.output.replace(mo.md(f"## Saved JSON files to {savepath}!")) + return + + +@app.cell +def _(): + def write_to_file(df: pl.DataFrame, filepath: str) -> None: + df.select(pl.exclude("builtin")).write_json(filepath) + + + def save_to_json(obj, filepath: str) -> None: + with open(filepath, "w") as f: + json.dump(obj, f, indent=4) + return save_to_json, write_to_file + + +@app.cell +def _(EXCLUDED_KEYWORDS): + functions = mo.sql( + f""" + -- Find functions and their descriptions + -- Functions tend to have overloading properties, so we capture them in a single row. + + WITH + function_details AS ( + SELECT + function_name, + parameters AS duckdb_params, + return_type, + parameter_types, + description, + alias_of, + examples + FROM + duckdb_functions() + ) + SELECT + function_name, + -- For properties that are consistent across overloads, you can use MIN/MAX or just ARRAY_AGG and pick the first element if appropriate + ARRAY_AGG(duckdb_params) AS parameter_overloads, + ARRAY_AGG(return_type) AS return_type_overloads, + ARRAY_AGG(parameter_types) AS parameter_types_overloads, + MAX(description) AS description, -- Assuming description is the same for all overloads + MAX(alias_of) AS alias_of, -- Assuming alias_of is the same for all overloads + MAX(examples)[1] AS example -- Examples tend to be consistent + FROM + function_details + WHERE {filter_keywords_query("function_name", EXCLUDED_KEYWORDS)} + GROUP BY + function_name + ORDER BY + function_name; + """, + output=False + ) + return (functions,) + + +@app.cell(hide_code=True) +def _(): + _df = mo.sql( + f""" + WITH + duckdb_types_cte AS ( + SELECT + ROW_NUMBER() OVER () AS rn, + type_name AS duckdb_types + FROM + ( + SELECT DISTINCT + type_name + FROM + duckdb_types() + ) + ), + duckdb_settings_cte AS ( + SELECT + ROW_NUMBER() OVER () AS rn, + name AS duckdb_settings + FROM + ( + SELECT DISTINCT + name + FROM + duckdb_settings() + ) + ), + duckdb_functions_cte AS ( + SELECT + ROW_NUMBER() OVER () AS rn, + function_name AS duckdb_functions + FROM + ( + SELECT DISTINCT + function_name + FROM + duckdb_functions() + ) + ), + duckdb_keywords_cte AS ( + SELECT + ROW_NUMBER() OVER () AS rn, + keyword_name AS duckdb_keywords + FROM + ( + SELECT DISTINCT + keyword_name + FROM + duckdb_keywords() + ) + ) + SELECT + t.duckdb_types, + s.duckdb_settings, + f.duckdb_functions, + k.duckdb_keywords + FROM + duckdb_types_cte AS t + FULL OUTER JOIN duckdb_settings_cte AS s ON t.rn = s.rn + FULL OUTER JOIN duckdb_functions_cte AS f ON COALESCE(t.rn, s.rn) = f.rn + FULL OUTER JOIN duckdb_keywords_cte AS k ON COALESCE(t.rn, s.rn, f.rn) = k.rn + ORDER BY + COALESCE(t.rn, s.rn, f.rn, k.rn); + """ + ) + return + + +@app.cell(hide_code=True) +def _(EXCLUDED_KEYWORDS): + df = mo.sql( + f""" + WITH + stg_keywords AS ( + SELECT + 'duckdb_keywords' AS keyword_group, + keyword_name AS keyword + FROM + duckdb_keywords() + UNION ALL + SELECT + 'duckdb_settings' AS keyword_group, + name AS keyword + FROM + duckdb_settings() + UNION ALL + SELECT + 'duckdb_functions' AS keyword_group, + function_name AS keyword + FROM + duckdb_functions() + UNION ALL + SELECT + 'duckdb_types' AS keyword_group, + type_name AS keyword + FROM + duckdb_types() + ), + all_keywords AS ( + SELECT + STRING_AGG(DISTINCT keyword, ' ' ORDER BY keyword) AS keywords_str + FROM + stg_keywords + WHERE {filter_keywords_query("keyword", EXCLUDED_KEYWORDS)} + ), + builtin_keywords AS ( + SELECT + STRING_AGG(DISTINCT keyword, ' ' ORDER BY keyword) AS builtin_str + FROM + stg_keywords + WHERE + keyword_group IN ('duckdb_keywords', 'duckdb_settings') + ), + duckdb_types_str AS ( + SELECT + STRING_AGG(DISTINCT type_name, ' ' ORDER BY type_name) AS types_str + FROM + duckdb_types() + ) + SELECT + version() as duckdb_version, + today() as last_updated, + ak.keywords_str AS keywords, + bk.builtin_str AS builtin, + dts.types_str AS types + FROM + all_keywords AS ak, + builtin_keywords AS bk, + duckdb_types_str AS dts; + """ + ) + return (df,) + + +@app.cell +def _(df): + num_keywords = len(df["keywords"][0].split(" ")) + num_types = len(df["types"][0].split(" ")) + + form = mo.ui.text(placeholder="duckdb", label="Folder").form( + submit_button_label="Save" + ) + return form, num_keywords, num_types + + +@app.function +def filter_keywords_query(column: str, EXCLUDED_KEYWORDS: list[str]) -> str: + like_conditions = [ + f"{column} NOT LIKE '{kw}%'" for kw in EXCLUDED_KEYWORDS + ] + where_clause = " AND ".join(like_conditions) + return where_clause + + +@app.cell(hide_code=True) +def _(functions): + mo.md( + rf""" + ## DuckDB Functions + + {mo.ui.table(functions)} + """ + ) + return + + +@app.cell +def _(functions): + # We only want certain functions + included_functions = [ + "array", + "count", + "struct", + "concat", + "cast", + "combine", + "contains", + "date", + "day", + "histogram", + "generate_series", + "json", + "string", + "substring", + "to_", + "trim", + ] + + filtered_fn = functions.select(["function_name", "description", "example"]) + + prefix_filter = pl.any_horizontal( + pl.col("function_name").str.starts_with(prefix) + for prefix in included_functions + ) + + filtered_fn = filtered_fn.filter(prefix_filter, pl.col("description") != "") + + # transform to {keyword: {description, example }} + fn_dict = {} + for row in filtered_fn.iter_rows(named=True): + fn_dict[row["function_name"]] = { + "description": row["description"], + "example": row["example"], + } + return (fn_dict,) + + +if __name__ == "__main__": + app.run() diff --git a/src/index.ts b/src/index.ts index 7149a0c..d5f81d5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,9 +7,10 @@ export type { SqlHoverConfig, SqlKeywordInfo, } from "./sql/hover.js"; -export { sqlHover, sqlHoverTheme } from "./sql/hover.js"; -export { SqlParser } from "./sql/parser.js"; +export { DefaultSqlTooltipRenders, defaultSqlHoverTheme, sqlHover } from "./sql/hover.js"; +export { NodeSqlParser } from "./sql/parser.js"; export type { SqlStatement } from "./sql/structure-analyzer.js"; export { SqlStructureAnalyzer } from "./sql/structure-analyzer.js"; export type { SqlGutterConfig } from "./sql/structure-extension.js"; export { sqlStructureGutter } from "./sql/structure-extension.js"; +export type { SqlParseError, SqlParseResult, SqlParser } from "./sql/types.js"; diff --git a/src/sql/__tests__/cte-completion-source.test.ts b/src/sql/__tests__/cte-completion-source.test.ts index d44d022..0089f68 100644 --- a/src/sql/__tests__/cte-completion-source.test.ts +++ b/src/sql/__tests__/cte-completion-source.test.ts @@ -3,6 +3,15 @@ import { EditorState } from "@codemirror/state"; import { describe, expect, it } from "vitest"; import { cteCompletionSource } from "../cte-completion-source.js"; +// Helper function to handle both sync and async completion results +async function getCompletionResult(context: CompletionContext) { + const result = cteCompletionSource(context); + if (result instanceof Promise) { + return await result; + } + return result; +} + // Helper function to create a mock completion context function createMockContext(doc: string, pos: number, explicit = false): CompletionContext { const state = EditorState.create({ doc }); @@ -45,14 +54,14 @@ function createMockContext(doc: string, pos: number, explicit = false): Completi describe("cteCompletionSource", () => { describe("basic CTE detection", () => { - it("should detect single CTE", () => { + it("should detect single CTE", async () => { const sql = `WITH user_stats AS ( SELECT id, name FROM users ) SELECT * FROM user_`; const context = createMockContext(sql, sql.length, true); - const result = cteCompletionSource(context); + const result = await getCompletionResult(context); expect(result).toBeTruthy(); expect(result?.options).toHaveLength(1); @@ -61,14 +70,14 @@ describe("cteCompletionSource", () => { expect(result?.options[0].info).toBe("Common Table Expression: user_stats"); }); - it("should detect multiple CTEs", () => { + it("should detect multiple CTEs", async () => { const sql = `WITH user_stats AS (SELECT id, name FROM users), post_counts AS (SELECT user_id, COUNT(*) as count FROM posts GROUP BY user_id) SELECT * FROM `; const context = createMockContext(sql, sql.length, true); - const result = cteCompletionSource(context); + const result = await getCompletionResult(context); expect(result).toBeTruthy(); expect(result?.options).toHaveLength(2); @@ -77,7 +86,7 @@ describe("cteCompletionSource", () => { expect(labels).toEqual(["post_counts", "user_stats"]); }); - it("should detect RECURSIVE CTEs", () => { + it("should detect RECURSIVE CTEs", async () => { const sql = `WITH RECURSIVE category_tree AS ( SELECT id, name, parent_id FROM categories WHERE parent_id IS NULL UNION ALL @@ -88,7 +97,7 @@ describe("cteCompletionSource", () => { SELECT * FROM category_`; const context = createMockContext(sql, sql.length, true); - const result = cteCompletionSource(context); + const result = await getCompletionResult(context); expect(result).toBeTruthy(); expect(result?.options).toHaveLength(1); @@ -97,40 +106,40 @@ describe("cteCompletionSource", () => { }); describe("CTE name patterns", () => { - it("should handle CTEs with underscores", () => { + it("should handle CTEs with underscores", async () => { const sql = `WITH user_activity_stats AS ( SELECT * FROM users ) SELECT * FROM user_`; const context = createMockContext(sql, sql.length, true); - const result = cteCompletionSource(context); + const result = await getCompletionResult(context); expect(result).toBeTruthy(); expect(result?.options[0].label).toBe("user_activity_stats"); }); - it("should handle CTEs with numbers", () => { + it("should handle CTEs with numbers", async () => { const sql = `WITH stats2024 AS ( SELECT * FROM users ) SELECT * FROM stats`; const context = createMockContext(sql, sql.length, true); - const result = cteCompletionSource(context); + const result = await getCompletionResult(context); expect(result).toBeTruthy(); expect(result?.options[0].label).toBe("stats2024"); }); - it("should handle case-insensitive WITH keyword", () => { + it("should handle case-insensitive WITH keyword", async () => { const sql = `with user_data as ( select * from users ) select * from user_`; const context = createMockContext(sql, sql.length, true); - const result = cteCompletionSource(context); + const result = await getCompletionResult(context); expect(result).toBeTruthy(); expect(result?.options[0].label).toBe("user_data"); @@ -138,7 +147,7 @@ describe("cteCompletionSource", () => { }); describe("complex SQL scenarios", () => { - it("should detect CTEs in nested queries", () => { + it("should detect CTEs in nested queries", async () => { const sql = `WITH outer_cte AS ( WITH inner_cte AS (SELECT id FROM users) SELECT * FROM inner_cte @@ -146,7 +155,7 @@ describe("cteCompletionSource", () => { SELECT * FROM `; const context = createMockContext(sql, sql.length, true); - const result = cteCompletionSource(context); + const result = await getCompletionResult(context); expect(result).toBeTruthy(); expect(result?.options).toHaveLength(2); @@ -155,7 +164,7 @@ describe("cteCompletionSource", () => { expect(labels).toEqual(["inner_cte", "outer_cte"]); }); - it("should handle CTEs with complex subqueries", () => { + it("should handle CTEs with complex subqueries", async () => { const sql = `WITH filtered_users AS ( SELECT u.id, u.name FROM users u @@ -167,13 +176,13 @@ describe("cteCompletionSource", () => { SELECT * FROM filtered_`; const context = createMockContext(sql, sql.length, true); - const result = cteCompletionSource(context); + const result = await getCompletionResult(context); expect(result).toBeTruthy(); expect(result?.options[0].label).toBe("filtered_users"); }); - it("should handle multiple WITH clauses in different statements", () => { + it("should handle multiple WITH clauses in different statements", async () => { const sql = `WITH first_cte AS (SELECT 1) SELECT * FROM first_cte; @@ -181,7 +190,7 @@ describe("cteCompletionSource", () => { SELECT * FROM `; const context = createMockContext(sql, sql.length, true); - const result = cteCompletionSource(context); + const result = await getCompletionResult(context); expect(result).toBeTruthy(); expect(result?.options).toHaveLength(2); @@ -192,41 +201,41 @@ describe("cteCompletionSource", () => { }); describe("edge cases", () => { - it("should return null when no CTEs are present", () => { + it("should return null when no CTEs are present", async () => { const sql = "SELECT * FROM users WHERE id = 1"; const context = createMockContext(sql, sql.length, true); - const result = cteCompletionSource(context); + const result = await getCompletionResult(context); expect(result).toBeNull(); }); - it("should return null when not in explicit mode and no word being typed", () => { + it("should return null when not in explicit mode and no word being typed", async () => { const sql = `WITH user_stats AS (SELECT * FROM users) SELECT * FROM `; const context = createMockContext(sql, sql.length, false); // explicit = false - const result = cteCompletionSource(context); + const result = await getCompletionResult(context); expect(result).toBeNull(); }); - it("should handle incomplete CTEs gracefully", () => { + it("should handle incomplete CTEs gracefully", async () => { const sql = "WITH incomplete_cte AS SELECT * FROM users"; const context = createMockContext(sql, sql.length, true); - const result = cteCompletionSource(context); + const result = await getCompletionResult(context); expect(result).toBeNull(); }); - it("should handle empty document", () => { + it("should handle empty document", async () => { const sql = ""; const context = createMockContext(sql, 0, true); - const result = cteCompletionSource(context); + const result = await getCompletionResult(context); expect(result).toBeNull(); }); - it("should deduplicate CTE names", () => { + it("should deduplicate CTE names", async () => { const sql = `WITH user_stats AS (SELECT * FROM users) SELECT * FROM user_stats UNION ALL @@ -234,7 +243,7 @@ describe("cteCompletionSource", () => { SELECT * FROM `; const context = createMockContext(sql, sql.length, true); - const result = cteCompletionSource(context); + const result = await getCompletionResult(context); expect(result).toBeTruthy(); expect(result?.options).toHaveLength(1); @@ -243,24 +252,24 @@ describe("cteCompletionSource", () => { }); describe("completion context", () => { - it("should respect word boundaries", () => { + it("should respect word boundaries", async () => { const sql = `WITH user_stats AS (SELECT * FROM users) SELECT * FROM user_st`; const context = createMockContext(sql, sql.length, true); - const result = cteCompletionSource(context); + const result = await getCompletionResult(context); expect(result).toBeTruthy(); expect(result?.from).toBe(sql.lastIndexOf("user_st")); expect(result?.options[0].label).toBe("user_stats"); }); - it("should provide correct completion metadata", () => { + it("should provide correct completion metadata", async () => { const sql = `WITH my_cte AS (SELECT * FROM users) SELECT * FROM my_`; const context = createMockContext(sql, sql.length, true); - const result = cteCompletionSource(context); + const result = await getCompletionResult(context); expect(result).toBeTruthy(); const completion = result?.options[0]; diff --git a/src/sql/__tests__/diagnostics.test.ts b/src/sql/__tests__/diagnostics.test.ts index 89ea7bf..6394edd 100644 --- a/src/sql/__tests__/diagnostics.test.ts +++ b/src/sql/__tests__/diagnostics.test.ts @@ -2,6 +2,7 @@ import { Text } from "@codemirror/state"; import type { EditorView } from "@codemirror/view"; import { describe, expect, it, vi } from "vitest"; import { sqlLinter } from "../diagnostics.js"; +import type { SqlParser } from "../parser.js"; // Mock EditorView const _createMockView = (content: string) => { @@ -26,7 +27,7 @@ describe("sqlLinter", () => { const mockParser = { validateSql: vi.fn(() => []), parseSql: vi.fn(() => ({ statements: [] })), - } as any; + } as unknown as SqlParser; const linter = sqlLinter({ parser: mockParser }); expect(linter).toBeDefined(); diff --git a/src/sql/__tests__/hover-integration.test.ts b/src/sql/__tests__/hover-integration.test.ts index 23bd639..e0114ac 100644 --- a/src/sql/__tests__/hover-integration.test.ts +++ b/src/sql/__tests__/hover-integration.test.ts @@ -1,7 +1,7 @@ import type { Completion } from "@codemirror/autocomplete"; import type { SQLNamespace } from "@codemirror/lang-sql"; import { describe, expect, it, vi } from "vitest"; -import { sqlHover, sqlHoverTheme } from "../hover.js"; +import { defaultSqlHoverTheme, sqlHover } from "../hover.js"; import { resolveNamespaceItem } from "../namespace-utils.js"; // Helper function to create completion objects @@ -73,7 +73,7 @@ describe("Hover Integration Tests", () => { it("should create hover theme without errors", () => { expect(() => { - sqlHoverTheme(); + defaultSqlHoverTheme(); }).not.toThrow(); }); @@ -402,13 +402,14 @@ describe("Custom Tooltip Renderers", () => { expect(result?.semanticType).toBe("table"); // Verify data structure for namespace renderer + expect(result).toBeTruthy(); const namespaceData = { - item: result!, + item: result, word: "users", resolvedSchema: mockSchema, }; - expect(namespaceData.item.semanticType).toBe("table"); + expect(namespaceData.item?.semanticType).toBe("table"); expect(namespaceData.word).toBe("users"); expect(namespaceData.resolvedSchema).toBe(mockSchema); }); @@ -435,13 +436,13 @@ describe("Custom Tooltip Renderers", () => { // Verify this would be passed to table renderer const tableData = { - item: result!, + item: result, word: "users", resolvedSchema: mockSchema, }; - expect(tableData.item.completion?.label).toBe("users"); - expect(tableData.item.namespace).toBeDefined(); + expect(tableData.item?.completion?.label).toBe("users"); + expect(tableData.item?.namespace).toBeDefined(); }); }); @@ -466,13 +467,13 @@ describe("Custom Tooltip Renderers", () => { // Verify data structure for column renderer const columnData = { - item: result!, + item: result, word: "id", resolvedSchema: mockSchema, }; - expect(columnData.item.completion?.label).toBe("id"); - expect(columnData.item.semanticType).toBe("column"); + expect(columnData.item?.completion?.label).toBe("id"); + expect(columnData.item?.semanticType).toBe("column"); }); }); @@ -529,3 +530,246 @@ describe("Custom Tooltip Renderers", () => { }); }); }); + +describe("Query-aware hover behavior", () => { + const testSchema: SQLNamespace = { + users: { + self: createCompletion("users", "User table"), + children: [ + createCompletion("id", "Primary key"), + createCompletion("username", "Username"), + createCompletion("email", "Email address"), + "created_at", + "updated_at", + ], + }, + orders: { + self: createCompletion("orders", "Order table"), + children: [ + createCompletion("id", "Order ID"), + createCompletion("user_id", "User ID"), + createCompletion("order_date", "Order date"), + createCompletion("total", "Order total"), + "status", + "created_at", + ], + }, + products: { + self: createCompletion("products", "Product table"), + children: [ + createCompletion("id", "Product ID"), + createCompletion("name", "Product name"), + createCompletion("price", "Product price"), + "category", + "created_at", + ], + }, + }; + + describe("Table reference extraction", () => { + it("should extract table references from simple SELECT queries", async () => { + const { NodeSqlParser } = await import("../parser.js"); + const parser = new NodeSqlParser(); + + const tableList = await parser.extractTableReferences("SELECT order_date FROM users"); + expect(tableList).toEqual(["users"]); + }); + + it("should extract table references from queries with multiple tables", async () => { + const { NodeSqlParser } = await import("../parser.js"); + const parser = new NodeSqlParser(); + + const tableList = await parser.extractTableReferences( + "SELECT u.username, o.order_date FROM users u JOIN orders o ON u.id = o.user_id", + ); + expect(tableList).toContain("users"); + expect(tableList).toContain("orders"); + }); + + it("should handle queries with aliases", async () => { + const { NodeSqlParser } = await import("../parser.js"); + const parser = new NodeSqlParser(); + + const tableList = await parser.extractTableReferences("SELECT u.username FROM users AS u"); + expect(tableList).toEqual(["users"]); + }); + + it("should handle UPDATE queries", async () => { + const { NodeSqlParser } = await import("../parser.js"); + const parser = new NodeSqlParser(); + + const tableList = await parser.extractTableReferences( + "UPDATE users SET email = 'test@example.com' WHERE id = 1", + ); + expect(tableList).toEqual(["users"]); + }); + + it("should handle INSERT queries", async () => { + const { NodeSqlParser } = await import("../parser.js"); + const parser = new NodeSqlParser(); + + const tableList = await parser.extractTableReferences( + "INSERT INTO users (username, email) VALUES ('test', 'test@example.com')", + ); + expect(tableList).toEqual(["users"]); + }); + + it("should handle DELETE queries", async () => { + const { NodeSqlParser } = await import("../parser.js"); + const parser = new NodeSqlParser(); + + const tableList = await parser.extractTableReferences("DELETE FROM users WHERE id = 1"); + expect(tableList).toEqual(["users"]); + }); + + it("should return empty array for invalid SQL", async () => { + const { NodeSqlParser } = await import("../parser.js"); + const parser = new NodeSqlParser(); + + const tableList = await parser.extractTableReferences("INVALID SQL QUERY"); + expect(tableList).toEqual([]); + }); + }); + + describe("Schema filtering based on query", () => { + it("should filter schema to only include referenced tables", async () => { + const { filterSchemaByTableRefs } = await import("../hover.js"); + + const tableRefs = new Set(["users"]); + const filteredSchema = filterSchemaByTableRefs(testSchema, tableRefs); + + expect(filteredSchema).toHaveProperty("users"); + expect(filteredSchema).not.toHaveProperty("orders"); + expect(filteredSchema).not.toHaveProperty("products"); + }); + + it("should include multiple tables when multiple tables are referenced", async () => { + const { filterSchemaByTableRefs } = await import("../hover.js"); + + const tableRefs = new Set(["users", "orders"]); + const filteredSchema = filterSchemaByTableRefs(testSchema, tableRefs); + + expect(filteredSchema).toHaveProperty("users"); + expect(filteredSchema).toHaveProperty("orders"); + expect(filteredSchema).not.toHaveProperty("products"); + }); + + it("should return empty schema when no tables are referenced", async () => { + const { filterSchemaByTableRefs } = await import("../hover.js"); + + const tableRefs = new Set(); + const filteredSchema = filterSchemaByTableRefs(testSchema, tableRefs); + + expect(filteredSchema).toEqual({}); + }); + + it("should handle case-insensitive table matching", async () => { + const { filterSchemaByTableRefs } = await import("../hover.js"); + + const tableRefs = new Set(["USERS", "Orders"]); + const filteredSchema = filterSchemaByTableRefs(testSchema, tableRefs); + + expect(filteredSchema).toHaveProperty("users"); + expect(filteredSchema).toHaveProperty("orders"); + expect(filteredSchema).not.toHaveProperty("products"); + }); + }); + + describe("Query-aware column resolution", () => { + it("should only show columns from referenced tables", async () => { + const { resolveNamespaceItem } = await import("../namespace-utils.js"); + + // When only 'users' table is referenced, we should only see columns from 'users' + const filteredSchema = { + users: testSchema.users, + }; + + // Should find columns from users table + const userColumnResult = resolveNamespaceItem(filteredSchema, "username", { + enableFuzzySearch: true, + }); + expect(userColumnResult).toBeTruthy(); + expect(userColumnResult?.semanticType).toBe("column"); + expect(userColumnResult?.completion?.label).toBe("username"); + + // Should not find columns from other tables + const orderColumnResult = resolveNamespaceItem(filteredSchema, "order_date", { + enableFuzzySearch: true, + }); + expect(orderColumnResult).toBeNull(); + }); + + it("should show columns from multiple tables when multiple tables are referenced", async () => { + const { resolveNamespaceItem } = await import("../namespace-utils.js"); + + const filteredSchema = { + users: testSchema.users, + orders: testSchema.orders, + }; + + // Should find columns from both tables + const userColumnResult = resolveNamespaceItem(filteredSchema, "username", { + enableFuzzySearch: true, + }); + expect(userColumnResult).toBeTruthy(); + expect(userColumnResult?.semanticType).toBe("column"); + + const orderColumnResult = resolveNamespaceItem(filteredSchema, "order_date", { + enableFuzzySearch: true, + }); + expect(orderColumnResult).toBeTruthy(); + expect(orderColumnResult?.semanticType).toBe("column"); + }); + }); +}); + +describe("Query-aware hover behavior - edge cases", () => { + const testSchema: SQLNamespace = { + users: { + self: createCompletion("users", "User table"), + children: [ + createCompletion("id", "Primary key"), + createCompletion("username", "Username"), + createCompletion("email", "Email address"), + "created_at", + "updated_at", + ], + }, + orders: { + self: createCompletion("orders", "Order table"), + children: [ + createCompletion("id", "Order ID"), + createCompletion("user_id", "User ID"), + createCompletion("order_date", "Order date"), + createCompletion("total", "Order total"), + "status", + "created_at", + ], + }, + }; + + describe("Fallback behavior when no tables found in query", () => { + it("should show hover for table names even when no tables are found in query", async () => { + const { resolveNamespaceItem } = await import("../namespace-utils.js"); + + // Simulate a query with no tables found (e.g., invalid SQL or empty query) + // Should be able to find the table in the full schema when no tables are referenced + const tableResult = resolveNamespaceItem(testSchema, "users", { enableFuzzySearch: true }); + expect(tableResult).toBeTruthy(); + expect(tableResult?.semanticType).toBe("table"); + expect(tableResult?.completion?.label).toBe("users"); + }); + + it("should show hover for column names even when no tables are found in query", async () => { + const { resolveNamespaceItem } = await import("../namespace-utils.js"); + + // Should be able to find columns in the full schema when no tables are referenced + const columnResult = resolveNamespaceItem(testSchema, "username", { + enableFuzzySearch: true, + }); + expect(columnResult).toBeTruthy(); + expect(columnResult?.semanticType).toBe("column"); + expect(columnResult?.completion?.label).toBe("username"); + }); + }); +}); diff --git a/src/sql/__tests__/namespace-utils.test.ts b/src/sql/__tests__/namespace-utils.test.ts index 6e92ffa..7417ed0 100644 --- a/src/sql/__tests__/namespace-utils.test.ts +++ b/src/sql/__tests__/namespace-utils.test.ts @@ -570,6 +570,7 @@ describe("edge cases and error handling", () => { it("should handle circular references without infinite loops", () => { // Create a namespace with potential circular reference + // biome-ignore lint/suspicious/noExplicitAny: Mock SQLNamespace const circularNamespace: any = { parent: { child: null, @@ -588,7 +589,11 @@ describe("performance and memory", () => { // Create a large namespace const largeNamespace: SQLNamespace = {}; for (let i = 0; i < 1000; i++) { - (largeNamespace as any)[`table_${i}`] = [`col1_${i}`, `col2_${i}`, `col3_${i}`]; + (largeNamespace as Record)[`table_${i}`] = [ + `col1_${i}`, + `col2_${i}`, + `col3_${i}`, + ]; } const startTime = performance.now(); diff --git a/src/sql/__tests__/parser.test.ts b/src/sql/__tests__/parser.test.ts index 7afcd49..7f2d361 100644 --- a/src/sql/__tests__/parser.test.ts +++ b/src/sql/__tests__/parser.test.ts @@ -1,20 +1,24 @@ -import { describe, expect, it } from "vitest"; -import { SqlParser } from "../parser.js"; +import { EditorState } from "@codemirror/state"; +import { describe, expect, it, vi } from "vitest"; +import { NodeSqlParser } from "../parser.js"; describe("SqlParser", () => { - const parser = new SqlParser(); + const parser = new NodeSqlParser(); + const state = EditorState.create({ + doc: "SELECT * FROM users WHERE id = 1", + }); describe("parse", () => { - it("should parse valid SQL successfully", () => { + it("should parse valid SQL successfully", async () => { const sql = "SELECT * FROM users WHERE id = 1"; - const result = parser.parse(sql); + const result = await parser.parse(sql, { state }); expect(result.success).toBe(true); expect(result.errors).toHaveLength(0); expect(result.ast).toBeDefined(); }); - it("should handle complex queries", () => { + it("should handle complex queries", async () => { const sql = ` SELECT u.name, p.title FROM users u @@ -22,15 +26,15 @@ describe("SqlParser", () => { WHERE u.active = true ORDER BY p.created_at DESC `; - const result = parser.parse(sql); + const result = await parser.parse(sql, { state }); expect(result.success).toBe(true); expect(result.errors).toHaveLength(0); }); - it("should return errors for invalid SQL", () => { + it("should return errors for invalid SQL", async () => { const sql = "SELECT * FROM"; - const result = parser.parse(sql); + const result = await parser.parse(sql, { state }); expect(result.success).toBe(false); expect(result.errors).toHaveLength(1); @@ -38,26 +42,66 @@ describe("SqlParser", () => { expect(result.errors[0].message).toBeTruthy(); }); - it("should return errors for syntax errors", () => { + it("should return errors for syntax errors", async () => { const sql = "SELECT * FORM users"; - const result = parser.parse(sql); + const result = await parser.parse(sql, { state }); expect(result.success).toBe(false); expect(result.errors).toHaveLength(1); }); + + it("should use custom parser options when provided", async () => { + const customParser = new NodeSqlParser({ + getParserOptions: () => ({ + database: "PostgreSQL", + parseOptions: { + includeLocations: true, + }, + }), + }); + + const sql = "SELECT * FROM users"; + const result = await customParser.parse(sql, { state }); + + expect(result.success).toBe(true); + expect(result.ast).toBeDefined(); + // The AST should include location information when includeLocations is true + if (result.success && Array.isArray(result.ast)) { + const selectStmt = result.ast[0]; + if (selectStmt && typeof selectStmt === "object" && "loc" in selectStmt) { + expect(selectStmt.loc).toBeDefined(); + } + } + }); + + it("should call getParserOptions with correct state", async () => { + const mockGetParserOptions = vi.fn().mockReturnValue({ + database: "MySQL", + }); + + const customParser = new NodeSqlParser({ + getParserOptions: mockGetParserOptions, + }); + + const sql = "SELECT 1"; + await customParser.parse(sql, { state }); + + expect(mockGetParserOptions).toHaveBeenCalledTimes(1); + expect(mockGetParserOptions).toHaveBeenCalledWith(state); + }); }); describe("validateSql", () => { - it("should return empty array for valid SQL", () => { + it("should return empty array for valid SQL", async () => { const sql = "SELECT 1"; - const errors = parser.validateSql(sql); + const errors = await parser.validateSql(sql, { state }); expect(errors).toHaveLength(0); }); - it("should return errors for invalid SQL", () => { + it("should return errors for invalid SQL", async () => { const sql = "SELECT * FROM WHERE"; - const errors = parser.validateSql(sql); + const errors = await parser.validateSql(sql, { state }); expect(errors.length).toBeGreaterThan(0); expect(errors[0]).toHaveProperty("message"); @@ -66,4 +110,80 @@ describe("SqlParser", () => { expect(errors[0]).toHaveProperty("severity"); }); }); + + describe("DuckDB dialect support", () => { + it("should accept DuckDB-specific syntax without parsing", async () => { + const duckdbParser = new NodeSqlParser({ + getParserOptions: () => ({ + database: "DuckDB", + }), + }); + + const state = EditorState.create({ + doc: "from nyc.rideshare select * limit 100", + }); + + const result = await duckdbParser.parse("from nyc.rideshare select * limit 100", { state }); + + expect(result.success).toBe(true); + expect(result.errors).toHaveLength(0); + expect(result.ast).toBeUndefined(); + }); + + it("should still parse standard SQL with DuckDB dialect", async () => { + const duckdbParser = new NodeSqlParser({ + getParserOptions: () => ({ + database: "DuckDB", + }), + }); + + const state = EditorState.create({ + doc: "SELECT * FROM users WHERE id = 1", + }); + + const result = await duckdbParser.parse("SELECT * FROM users WHERE id = 1", { state }); + + expect(result.success).toBe(true); + expect(result.errors).toHaveLength(0); + }); + + it("should accept complex DuckDB queries without parsing", async () => { + const duckdbParser = new NodeSqlParser({ + getParserOptions: () => ({ + database: "DuckDB", + }), + }); + + const state = EditorState.create({ + doc: "from nyc.rideshare select pickup_datetime, dropoff_datetime limit 50", + }); + + const result = await duckdbParser.parse( + "from nyc.rideshare select pickup_datetime, dropoff_datetime limit 50", + { state }, + ); + + expect(result.success).toBe(true); + expect(result.errors).toHaveLength(0); + expect(result.ast).toBeUndefined(); + }); + + it("should accept DuckDB queries with semicolons without parsing", async () => { + const duckdbParser = new NodeSqlParser({ + getParserOptions: () => ({ + database: "DuckDB", + }), + }); + + const state = EditorState.create({ + doc: "from posts select title, name;", + }); + + const result = await duckdbParser.parse("from posts select title, name;", { state }); + + expect(result.success).toBe(true); + expect(result.errors).toHaveLength(0); + expect(result.ast).toBeUndefined(); + }); + }); }); diff --git a/src/sql/__tests__/structure-analyzer.test.ts b/src/sql/__tests__/structure-analyzer.test.ts index 9337bc5..495f906 100644 --- a/src/sql/__tests__/structure-analyzer.test.ts +++ b/src/sql/__tests__/structure-analyzer.test.ts @@ -1,5 +1,6 @@ import { EditorState } from "@codemirror/state"; import { beforeEach, describe, expect, it } from "vitest"; +import { NodeSqlParser } from "../parser.js"; import { SqlStructureAnalyzer } from "../structure-analyzer.js"; describe("SqlStructureAnalyzer", () => { @@ -7,7 +8,7 @@ describe("SqlStructureAnalyzer", () => { let state: EditorState; beforeEach(() => { - analyzer = new SqlStructureAnalyzer(); + analyzer = new SqlStructureAnalyzer(new NodeSqlParser()); }); const createState = (content: string) => { @@ -15,9 +16,9 @@ describe("SqlStructureAnalyzer", () => { }; describe("analyzeDocument", () => { - it("should identify single SQL statement", () => { + it("should identify single SQL statement", async () => { state = createState("SELECT * FROM users WHERE id = 1;"); - const statements = analyzer.analyzeDocument(state); + const statements = await analyzer.analyzeDocument(state); expect(statements).toHaveLength(1); expect(statements[0].content).toBe("SELECT * FROM users WHERE id = 1"); @@ -27,13 +28,13 @@ describe("SqlStructureAnalyzer", () => { expect(statements[0].lineTo).toBe(1); }); - it("should identify multiple SQL statements", () => { + it("should identify multiple SQL statements", async () => { state = createState(` SELECT * FROM users; INSERT INTO users (name) VALUES ('John'); DELETE FROM users WHERE id = 1; `); - const statements = analyzer.analyzeDocument(state); + const statements = await analyzer.analyzeDocument(state); expect(statements).toHaveLength(3); expect(statements[0].type).toBe("select"); @@ -41,18 +42,18 @@ describe("SqlStructureAnalyzer", () => { expect(statements[2].type).toBe("delete"); }); - it("should handle statements without semicolons", () => { + it("should handle statements without semicolons", async () => { state = createState("SELECT * FROM users WHERE id = 1"); - const statements = analyzer.analyzeDocument(state); + const statements = await analyzer.analyzeDocument(state); expect(statements).toHaveLength(1); expect(statements[0].content).toBe("SELECT * FROM users WHERE id = 1"); expect(statements[0].type).toBe("select"); }); - it("should handle semicolons in string literals", () => { + it("should handle semicolons in string literals", async () => { state = createState(`SELECT 'Hello; World' FROM users; UPDATE users SET name = 'Test;';`); - const statements = analyzer.analyzeDocument(state); + const statements = await analyzer.analyzeDocument(state); expect(statements).toHaveLength(2); expect(statements[0].content).toBe("SELECT 'Hello; World' FROM users"); @@ -72,27 +73,27 @@ describe("SqlStructureAnalyzer", () => { { sql: "SHOW TABLES", expected: "other" }, ]; - testCases.forEach(({ sql, expected }) => { + testCases.forEach(async ({ sql, expected }) => { state = createState(`${sql};`); - const statements = analyzer.analyzeDocument(state); + const statements = await analyzer.analyzeDocument(state); expect(statements[0].type).toBe(expected); }); }); - it("should handle invalid SQL statements", () => { + it("should handle invalid SQL statements", async () => { state = createState("SELECT * FROM;"); - const statements = analyzer.analyzeDocument(state); + const statements = await analyzer.analyzeDocument(state); expect(statements).toHaveLength(1); expect(statements[0].isValid).toBe(false); }); - it("should cache results for identical content", () => { + it("should cache results for identical content", async () => { const content = "SELECT * FROM users;"; state = createState(content); - const statements1 = analyzer.analyzeDocument(state); - const statements2 = analyzer.analyzeDocument(state); + const statements1 = await analyzer.analyzeDocument(state); + const statements2 = await analyzer.analyzeDocument(state); expect(statements1).toBe(statements2); // Should be the same reference (cached) }); @@ -105,19 +106,19 @@ INSERT INTO users (name) VALUES ('John'); DELETE FROM users WHERE id = 1;`); }); - it("should return correct statement for cursor position", () => { - const statement1 = analyzer.getStatementAtPosition(state, 5); // Inside first SELECT - const statement2 = analyzer.getStatementAtPosition(state, 25); // Inside INSERT - const statement3 = analyzer.getStatementAtPosition(state, 80); // Inside DELETE + it("should return correct statement for cursor position", async () => { + const statement1 = await analyzer.getStatementAtPosition(state, 5); // Inside first SELECT + const statement2 = await analyzer.getStatementAtPosition(state, 25); // Inside INSERT + const statement3 = await analyzer.getStatementAtPosition(state, 80); // Inside DELETE expect(statement1?.type).toBe("select"); expect(statement2?.type).toBe("insert"); expect(statement3?.type).toBe("delete"); }); - it("should return null for position outside any statement", () => { + it("should return null for position outside any statement", async () => { state = createState(" \n\n "); - const statement = analyzer.getStatementAtPosition(state, 2); + const statement = await analyzer.getStatementAtPosition(state, 2); expect(statement).toBeNull(); }); }); @@ -129,43 +130,43 @@ INSERT INTO users (name) VALUES ('John'); DELETE FROM users WHERE id = 1;`); }); - it("should return statements that intersect with range", () => { - const statements = analyzer.getStatementsInRange(state, 0, 50); + it("should return statements that intersect with range", async () => { + const statements = await analyzer.getStatementsInRange(state, 0, 50); expect(statements).toHaveLength(2); // SELECT and INSERT expect(statements[0].type).toBe("select"); expect(statements[1].type).toBe("insert"); }); - it("should return single statement when range is within one statement", () => { - const statements = analyzer.getStatementsInRange(state, 5, 10); + it("should return single statement when range is within one statement", async () => { + const statements = await analyzer.getStatementsInRange(state, 5, 10); expect(statements).toHaveLength(1); expect(statements[0].type).toBe("select"); }); - it("should return all statements when range covers entire document", () => { - const statements = analyzer.getStatementsInRange(state, 0, state.doc.toString().length); + it("should return all statements when range covers entire document", async () => { + const statements = await analyzer.getStatementsInRange(state, 0, state.doc.toString().length); expect(statements).toHaveLength(3); }); }); describe("clearCache", () => { - it("should clear internal cache", () => { + it("should clear internal cache", async () => { state = createState("SELECT * FROM users;"); // Populate cache - analyzer.analyzeDocument(state); + await analyzer.analyzeDocument(state); // Clear cache analyzer.clearCache(); // Should re-analyze (though we can't directly test this, we ensure no errors) - const statements = analyzer.analyzeDocument(state); + const statements = await analyzer.analyzeDocument(state); expect(statements).toHaveLength(1); }); }); describe("multiline statements", () => { - it("should handle statements spanning multiple lines", () => { + it("should handle statements spanning multiple lines", async () => { state = createState(`SELECT u.id, u.name, u.email @@ -173,7 +174,7 @@ FROM users u WHERE u.active = true AND u.created_at > '2023-01-01';`); - const statements = analyzer.analyzeDocument(state); + const statements = await analyzer.analyzeDocument(state); expect(statements).toHaveLength(1); expect(statements[0].lineFrom).toBe(1); expect(statements[0].lineTo).toBe(6); @@ -183,40 +184,40 @@ WHERE u.active = true describe("comment handling", () => { describe("single-line comments (--)", () => { - it("should strip single-line comments from statement content", () => { + it("should strip single-line comments from statement content", async () => { state = createState("SELECT * FROM users -- this is a comment\nWHERE id = 1;"); - const statements = analyzer.analyzeDocument(state); + const statements = await analyzer.analyzeDocument(state); expect(statements).toHaveLength(1); expect(statements[0].content).toBe("SELECT * FROM users \nWHERE id = 1"); expect(statements[0].type).toBe("select"); }); - it("should handle single-line comment at end of statement", () => { + it("should handle single-line comment at end of statement", async () => { state = createState("SELECT * FROM users WHERE id = 1; -- comment"); - const statements = analyzer.analyzeDocument(state); + const statements = await analyzer.analyzeDocument(state); expect(statements).toHaveLength(1); expect(statements[0].content).toBe("SELECT * FROM users WHERE id = 1"); }); - it("should handle statement that is entirely a comment", () => { + it("should handle statement that is entirely a comment", async () => { state = createState("-- This is just a comment\nSELECT * FROM users;"); - const statements = analyzer.analyzeDocument(state); + const statements = await analyzer.analyzeDocument(state); expect(statements).toHaveLength(1); expect(statements[0].content).toBe("SELECT * FROM users"); expect(statements[0].type).toBe("select"); }); - it("should handle multiple single-line comments", () => { + it("should handle multiple single-line comments", async () => { state = createState(` -- First comment SELECT * FROM users -- inline comment -- Another comment WHERE id = 1; -- final comment `); - const statements = analyzer.analyzeDocument(state); + const statements = await analyzer.analyzeDocument(state); expect(statements).toHaveLength(1); expect(statements[0].content.trim()).toBe( @@ -226,30 +227,30 @@ WHERE u.active = true }); describe("multi-line comments (/* */)", () => { - it("should strip multi-line comments from statement content", () => { + it("should strip multi-line comments from statement content", async () => { state = createState("SELECT * FROM users /* this is a comment */ WHERE id = 1;"); - const statements = analyzer.analyzeDocument(state); + const statements = await analyzer.analyzeDocument(state); expect(statements).toHaveLength(1); expect(statements[0].content).toBe("SELECT * FROM users WHERE id = 1"); expect(statements[0].type).toBe("select"); }); - it("should handle multi-line comments spanning multiple lines", () => { + it("should handle multi-line comments spanning multiple lines", async () => { state = createState(`SELECT * FROM users /* This is a multi-line comment that spans several lines */ WHERE id = 1;`); - const statements = analyzer.analyzeDocument(state); + const statements = await analyzer.analyzeDocument(state); expect(statements).toHaveLength(1); expect(statements[0].content).toBe("SELECT * FROM users WHERE id = 1"); }); - it("should handle nested-like comment patterns", () => { + it("should handle nested-like comment patterns", async () => { state = createState("SELECT * FROM users /* comment /* nested */ text */ WHERE id = 1;"); - const statements = analyzer.analyzeDocument(state); + const statements = await analyzer.analyzeDocument(state); expect(statements).toHaveLength(1); expect(statements[0].content).toBe("SELECT * FROM users text */ WHERE id = 1"); @@ -257,27 +258,27 @@ WHERE u.active = true }); describe("comments in string literals", () => { - it("should not strip comment patterns inside string literals", () => { + it("should not strip comment patterns inside string literals", async () => { state = createState("SELECT 'This -- is not a comment' FROM users;"); - const statements = analyzer.analyzeDocument(state); + const statements = await analyzer.analyzeDocument(state); expect(statements).toHaveLength(1); expect(statements[0].content).toBe("SELECT 'This -- is not a comment' FROM users"); }); - it("should not strip multi-line comment patterns inside string literals", () => { + it("should not strip multi-line comment patterns inside string literals", async () => { state = createState("SELECT 'This /* is not */ a comment' FROM users;"); - const statements = analyzer.analyzeDocument(state); + const statements = await analyzer.analyzeDocument(state); expect(statements).toHaveLength(1); expect(statements[0].content).toBe("SELECT 'This /* is not */ a comment' FROM users"); }); - it("should handle mixed real comments and string literal comment patterns", () => { + it("should handle mixed real comments and string literal comment patterns", async () => { state = createState( "SELECT 'Text -- not comment' FROM users -- real comment\nWHERE id = 1;", ); - const statements = analyzer.analyzeDocument(state); + const statements = await analyzer.analyzeDocument(state); expect(statements).toHaveLength(1); expect(statements[0].content).toBe( @@ -287,13 +288,13 @@ WHERE u.active = true }); describe("mixed comments and statements", () => { - it("should handle statements separated by comments", () => { + it("should handle statements separated by comments", async () => { state = createState(` SELECT * FROM users; -- First query /* Comment between queries */ INSERT INTO users (name) VALUES ('John'); -- Second query `); - const statements = analyzer.analyzeDocument(state); + const statements = await analyzer.analyzeDocument(state); expect(statements).toHaveLength(2); expect(statements[0].content.trim()).toBe("SELECT * FROM users"); @@ -302,27 +303,27 @@ WHERE u.active = true expect(statements[1].type).toBe("insert"); }); - it("should not create statements from comment-only content", () => { + it("should not create statements from comment-only content", async () => { state = createState(` -- Just a comment /* Another comment */ SELECT * FROM users; -- Final comment `); - const statements = analyzer.analyzeDocument(state); + const statements = await analyzer.analyzeDocument(state); expect(statements).toHaveLength(1); expect(statements[0].type).toBe("select"); }); - it("should handle semicolons in comments", () => { + it("should handle semicolons in comments", async () => { state = createState(` SELECT * FROM users; -- Comment with; semicolon /* Multi-line comment with; semicolon; on multiple; lines */ INSERT INTO logs VALUES (1); `); - const statements = analyzer.analyzeDocument(state); + const statements = await analyzer.analyzeDocument(state); expect(statements).toHaveLength(2); expect(statements[0].type).toBe("select"); diff --git a/src/sql/__tests__/structure-extension.test.ts b/src/sql/__tests__/structure-extension.test.ts index 59dba26..68a213e 100644 --- a/src/sql/__tests__/structure-extension.test.ts +++ b/src/sql/__tests__/structure-extension.test.ts @@ -3,6 +3,11 @@ import type { EditorView } from "@codemirror/view"; import { describe, expect, it } from "vitest"; import { sqlStructureGutter } from "../structure-extension.js"; +// Type for gutter extension with markers function +interface GutterExtension { + markers: (view: EditorView) => unknown; +} + // Mock EditorView const _createMockView = (content: string, hasFocus = true) => { const doc = Text.of(content.split("\n")); @@ -15,7 +20,7 @@ const _createMockView = (content: string, hasFocus = true) => { state, hasFocus, dispatch: () => {}, - } as any as EditorView; + } as EditorView; }; describe("sqlStructureGutter", () => { @@ -71,4 +76,51 @@ describe("sqlStructureGutter", () => { const extensions = sqlStructureGutter(config); expect(extensions.length).toBe(4); }); + + it("should handle line deletions gracefully without throwing invalid line number errors", () => { + // Create a multi-line SQL document + const multiLineSql = `SELECT * FROM users; +INSERT INTO users (name, email) VALUES ('John', 'john@example.com'); +UPDATE users SET name = 'Jane' WHERE id = 1; +DELETE FROM users WHERE id = 2;`; + + const doc = Text.of(multiLineSql.split("\n")); + // Create initial state (not used but shows the scenario) + EditorState.create({ + doc, + extensions: [sqlStructureGutter()], + }); + + // Simulate a document with fewer lines (like after deletion) + const shorterSql = `SELECT * FROM users;`; + const shorterDoc = Text.of(shorterSql.split("\n")); + const shorterState = EditorState.create({ + doc: shorterDoc, + extensions: [sqlStructureGutter()], + }); + + // The key test: ensure that accessing the state field doesn't throw errors + // even when the cached statements have stale line numbers + expect(() => { + // This would previously throw "Invalid line number" errors + // Now it should handle stale line numbers gracefully + const view = { + state: shorterState, + hasFocus: true, + dispatch: () => {}, + } as EditorView; + + // Trigger the gutter marker creation (this is where the error was occurring) + const extensions = sqlStructureGutter(); + const gutterExtension = extensions.find( + (ext) => typeof ext === "object" && ext !== null && "markers" in ext, + ) as GutterExtension | undefined; + + if (gutterExtension) { + const markersFn = gutterExtension.markers; + // This should not throw an error even with stale line numbers + expect(() => markersFn(view)).not.toThrow(); + } + }).not.toThrow(); + }); }); diff --git a/src/sql/diagnostics.ts b/src/sql/diagnostics.ts index 93c2465..7653665 100644 --- a/src/sql/diagnostics.ts +++ b/src/sql/diagnostics.ts @@ -1,7 +1,8 @@ import { type Diagnostic, linter } from "@codemirror/lint"; -import type { Text } from "@codemirror/state"; +import type { Extension, Text } from "@codemirror/state"; import type { EditorView } from "@codemirror/view"; -import { type SqlParseError, SqlParser } from "./parser.js"; +import { NodeSqlParser } from "./parser.js"; +import type { SqlParseError, SqlParser } from "./types.js"; const DEFAULT_DELAY = 750; @@ -48,11 +49,11 @@ function convertToCodeMirrorDiagnostic(error: SqlParseError, doc: Text): Diagnos * }); * ``` */ -export function sqlLinter(config: SqlLinterConfig = {}) { - const parser = config.parser || new SqlParser(); +export function sqlLinter(config: SqlLinterConfig = {}): Extension { + const parser = config.parser || new NodeSqlParser(); return linter( - (view: EditorView): Diagnostic[] => { + async (view: EditorView): Promise => { const doc = view.state.doc; const sql = doc.toString(); @@ -60,7 +61,7 @@ export function sqlLinter(config: SqlLinterConfig = {}) { return []; } - const errors = parser.validateSql(sql); + const errors = await parser.validateSql(sql, { state: view.state }); return errors.map((error) => convertToCodeMirrorDiagnostic(error, doc)); }, diff --git a/src/sql/extension.ts b/src/sql/extension.ts index e2803ec..25e4236 100644 --- a/src/sql/extension.ts +++ b/src/sql/extension.ts @@ -1,6 +1,6 @@ import type { Extension } from "@codemirror/state"; import { type SqlLinterConfig, sqlLinter } from "./diagnostics.js"; -import { type SqlHoverConfig, sqlHover, sqlHoverTheme } from "./hover.js"; +import { defaultSqlHoverTheme, type SqlHoverConfig, sqlHover } from "./hover.js"; import { type SqlGutterConfig, sqlStructureGutter } from "./structure-extension.js"; /** @@ -47,7 +47,7 @@ export interface SqlExtensionConfig { * }); * ``` */ -export function sqlExtension(config: SqlExtensionConfig = {}): Extension { +export function sqlExtension(config: SqlExtensionConfig = {}): Extension[] { const extensions: Extension[] = []; const { enableLinting = true, @@ -68,7 +68,9 @@ export function sqlExtension(config: SqlExtensionConfig = {}): Extension { if (enableHover) { extensions.push(sqlHover(hoverConfig)); - extensions.push(sqlHoverTheme()); + hoverConfig?.theme + ? extensions.push(hoverConfig.theme) + : extensions.push(defaultSqlHoverTheme()); } return extensions; diff --git a/src/sql/hover.ts b/src/sql/hover.ts index bf94552..ee401e6 100644 --- a/src/sql/hover.ts +++ b/src/sql/hover.ts @@ -4,9 +4,55 @@ import { EditorView, hoverTooltip, type Tooltip } from "@codemirror/view"; import { debug } from "../debug.js"; import { isArrayNamespace, + isObjectNamespace, type ResolvedNamespaceItem, resolveNamespaceItem, } from "./namespace-utils.js"; +import { NodeSqlParser } from "./parser.js"; +import type { SqlParser } from "./types.js"; + +/** + * Creates a filtered namespace that only includes tables referenced in the query + * @param schema The full schema namespace + * @param tableRefs Set of table names referenced in the query + * @returns Filtered namespace containing only referenced tables + */ +export function filterSchemaByTableRefs( + schema: SQLNamespace, + tableRefs: Set, +): SQLNamespace { + if (tableRefs.size === 0) { + // If no tables are referenced, return empty schema to avoid showing irrelevant columns + return {}; + } + + if (isObjectNamespace(schema)) { + const filtered: { [key: string]: SQLNamespace } = {}; + + for (const [key, value] of Object.entries(schema)) { + // Check if this table is referenced (case-insensitive) + const isReferenced = Array.from(tableRefs).some( + (refTable) => refTable.toLowerCase() === key.toLowerCase(), + ); + + if (isReferenced) { + // This table is referenced in the query + filtered[key] = value; + } else if (isObjectNamespace(value)) { + // Check if any child tables are referenced + const filteredChild = filterSchemaByTableRefs(value, tableRefs); + if (Object.keys(filteredChild).length > 0) { + filtered[key] = filteredChild; + } + } + } + + return filtered; + } + + // For other namespace types, return as-is (they might contain columns from referenced tables) + return schema; +} /** * SQL schema information for hover tooltips @@ -66,6 +112,8 @@ export interface SqlHoverConfig { enableColumns?: boolean; /** Enable fuzzy search for namespace items (default: false) */ enableFuzzySearch?: boolean; + /** Custom SQL parser instance to use for query analysis */ + parser?: SqlParser; /** Custom tooltip renderers for different item types */ tooltipRenderers?: { /** Custom renderer for SQL keywords */ @@ -77,6 +125,8 @@ export interface SqlHoverConfig { /** Custom renderer for column items */ column?: (data: NamespaceTooltipData) => string; }; + /** Custom CSS theme for hover tooltips */ + theme?: Extension; } /** @@ -91,6 +141,7 @@ export function sqlHover(config: SqlHoverConfig = {}): Extension { enableTables = true, enableColumns = true, enableFuzzySearch = true, + parser = new NodeSqlParser(), tooltipRenderers = {}, } = config; @@ -143,17 +194,41 @@ export function sqlHover(config: SqlHoverConfig = {}): Extension { : createKeywordTooltip(keywordData); } - // Step 2: Try to resolve directly in SQLNamespace + // Step 2: Try to resolve directly in SQLNamespace (query-aware) if (!tooltipContent && (enableTables || enableColumns) && resolvedSchema) { - const namespaceResult = resolveNamespaceItem(resolvedSchema, word, { + // Get the current SQL query to filter schema by referenced tables + const currentQuery = view.state.doc.toString(); + const tableList = await parser.extractTableReferences(currentQuery); + const tableRefs = new Set(tableList.map((table: string) => table.toLowerCase())); + + // Filter schema to only include tables referenced in the current query + const filteredSchema = filterSchemaByTableRefs(resolvedSchema, tableRefs); + + // Try to resolve in the filtered schema first (query-aware) + let namespaceResult = resolveNamespaceItem(filteredSchema, word, { enableFuzzySearch, }); + + // If no result in filtered schema and no tables were found in query, + // fall back to the full schema to show any relevant information + if (!namespaceResult && tableRefs.size === 0) { + namespaceResult = resolveNamespaceItem(resolvedSchema, word, { + enableFuzzySearch, + }); + } + if (namespaceResult) { - debug("namespaceResult", word, namespaceResult); + debug( + "namespaceResult (query-aware)", + word, + namespaceResult, + "tableRefs:", + Array.from(tableRefs), + ); const namespaceData: NamespaceTooltipData = { item: namespaceResult, word, - resolvedSchema, + resolvedSchema: tableRefs.size > 0 ? filteredSchema : resolvedSchema, }; // Use custom renderer based on semantic type, fallback to default @@ -173,6 +248,8 @@ export function sqlHover(config: SqlHoverConfig = {}): Extension { // Fallback to default renderer tooltipContent = createNamespaceTooltip(namespaceResult); } + } else { + debug("No namespace item found for:", word); } } @@ -189,7 +266,7 @@ export function sqlHover(config: SqlHoverConfig = {}): Extension { create(_view: EditorView) { const dom = document.createElement("div"); dom.className = "cm-sql-hover-tooltip"; - dom.innerHTML = tooltipContent!; + dom.innerHTML = tooltipContent; return { dom }; }, }; @@ -429,8 +506,51 @@ export const DefaultSqlTooltipRenders = { /** * Default CSS styles for hover tooltips */ -export const sqlHoverTheme = (): Extension => - EditorView.theme({ +export const defaultSqlHoverTheme = (theme: "light" | "dark" = "light"): Extension => { + // Theme-dependent color variables + const lightTheme = { + tooltipBg: "#ffffff", + tooltipBorder: "#e5e7eb", + tooltipText: "#374151", + tooltipTypeBg: "#f3f4f6", + tooltipTypeText: "#6b7280", + tooltipChildren: "#6b7280", + codeBg: "#f9fafb", + codeText: "#1f2937", + strong: "#111827", + em: "#6b7280", + header: "#111827", + info: "#374151", + related: "#374151", + path: "#374151", + example: "#374151", + columns: "#374151", + syntax: "#374151", + }; + + const darkTheme = { + tooltipBg: "#1f2937", + tooltipBorder: "#374151", + tooltipText: "#f9fafb", + tooltipTypeBg: "#374151", + tooltipTypeText: "#9ca3af", + tooltipChildren: "#9ca3af", + codeBg: "#374151", + codeText: "#f3f4f6", + strong: "#ffffff", + em: "#9ca3af", + header: "#ffffff", + info: "#d1d5db", + related: "#d1d5db", + path: "#d1d5db", + example: "#d1d5db", + columns: "#d1d5db", + syntax: "#d1d5db", + }; + + const colors = theme === "dark" ? darkTheme : lightTheme; + + return EditorView.theme({ ".cm-sql-hover-tooltip": { padding: "8px 12px", backgroundColor: "#ffffff", @@ -441,112 +561,71 @@ export const sqlHoverTheme = (): Extension => lineHeight: "1.4", maxWidth: "320px", fontFamily: "system-ui, -apple-system, sans-serif", + color: colors.tooltipText, }, ".cm-sql-hover-tooltip .sql-hover-header": { marginBottom: "6px", display: "flex", alignItems: "center", gap: "6px", + color: colors.header, }, ".cm-sql-hover-tooltip .sql-hover-type": { fontSize: "11px", padding: "2px 6px", - backgroundColor: "#f3f4f6", - color: "#6b7280", + backgroundColor: colors.tooltipTypeBg, + color: colors.tooltipTypeText, borderRadius: "4px", fontWeight: "500", }, ".cm-sql-hover-tooltip .sql-hover-description": { - color: "#374151", + color: colors.info, marginBottom: "8px", }, ".cm-sql-hover-tooltip .sql-hover-syntax": { marginBottom: "8px", - color: "#374151", + color: colors.syntax, }, ".cm-sql-hover-tooltip .sql-hover-example": { marginBottom: "4px", - color: "#374151", + color: colors.example, }, ".cm-sql-hover-tooltip .sql-hover-columns": { marginBottom: "4px", - color: "#374151", + color: colors.columns, }, ".cm-sql-hover-tooltip .sql-hover-related": { marginBottom: "4px", - color: "#374151", + color: colors.related, }, ".cm-sql-hover-tooltip .sql-hover-path": { marginBottom: "4px", - color: "#374151", + color: colors.path, }, ".cm-sql-hover-tooltip .sql-hover-info": { marginBottom: "4px", - color: "#374151", + color: colors.info, }, ".cm-sql-hover-tooltip .sql-hover-children": { marginBottom: "4px", - color: "#6b7280", + color: colors.tooltipChildren, fontSize: "12px", }, ".cm-sql-hover-tooltip code": { - backgroundColor: "#f9fafb", + backgroundColor: colors.codeBg, padding: "1px 4px", borderRadius: "3px", fontSize: "12px", fontFamily: "ui-monospace, 'SF Mono', 'Monaco', 'Cascadia Code', 'Roboto Mono', monospace", - color: "#1f2937", + color: colors.codeText, }, ".cm-sql-hover-tooltip strong": { fontWeight: "600", - color: "#111827", + color: colors.strong, }, ".cm-sql-hover-tooltip em": { fontStyle: "italic", - color: "#6b7280", - }, - // Dark theme support - ".cm-editor.cm-focused.cm-dark .cm-sql-hover-tooltip": { - backgroundColor: "#1f2937", - borderColor: "#374151", - color: "#f9fafb", - }, - ".cm-editor.cm-focused.cm-dark .cm-sql-hover-tooltip .sql-hover-type": { - backgroundColor: "#374151", - color: "#9ca3af", - }, - ".cm-editor.cm-focused.cm-dark .cm-sql-hover-tooltip .sql-hover-description": { - color: "#d1d5db", - }, - ".cm-editor.cm-focused.cm-dark .cm-sql-hover-tooltip .sql-hover-syntax": { - color: "#d1d5db", - }, - ".cm-editor.cm-focused.cm-dark .cm-sql-hover-tooltip .sql-hover-example": { - color: "#d1d5db", - }, - ".cm-editor.cm-focused.cm-dark .cm-sql-hover-tooltip .sql-hover-columns": { - color: "#d1d5db", - }, - ".cm-editor.cm-focused.cm-dark .cm-sql-hover-tooltip .sql-hover-related": { - color: "#d1d5db", - }, - ".cm-editor.cm-focused.cm-dark .cm-sql-hover-tooltip .sql-hover-path": { - color: "#d1d5db", - }, - ".cm-editor.cm-focused.cm-dark .cm-sql-hover-tooltip .sql-hover-info": { - color: "#d1d5db", - }, - ".cm-editor.cm-focused.cm-dark .cm-sql-hover-tooltip .sql-hover-children": { - color: "#9ca3af", - }, - ".cm-editor.cm-focused.cm-dark .cm-sql-hover-tooltip code": { - backgroundColor: "#374151", - color: "#f3f4f6", - }, - ".cm-editor.cm-focused.cm-dark .cm-sql-hover-tooltip strong": { - color: "#ffffff", - }, - ".cm-editor.cm-focused.cm-dark .cm-sql-hover-tooltip em": { - color: "#9ca3af", + color: colors.em, }, }); +}; diff --git a/src/sql/parser.ts b/src/sql/parser.ts index aa4cc16..9a67fcd 100644 --- a/src/sql/parser.ts +++ b/src/sql/parser.ts @@ -1,45 +1,66 @@ -import { Parser } from "node-sql-parser"; +import type { EditorState } from "@codemirror/state"; +import type { AST, Option, Parser } from "node-sql-parser"; +import { debug } from "../debug.js"; +import { lazy } from "../utils.js"; +import type { SqlParseError, SqlParseResult, SqlParser } from "./types.js"; -/** - * Represents a SQL parsing error with location information - */ -export interface SqlParseError { - /** Error message describing the issue */ - message: string; - /** Line number where the error occurred (1-indexed) */ - line: number; - /** Column number where the error occurred (1-indexed) */ - column: number; - /** Severity level of the error */ - severity: "error" | "warning"; +interface NodeSqlParserOptions { + getParserOptions?: (state: EditorState) => Option; } -/** - * Result of parsing a SQL statement - */ -export interface SqlParseResult { - /** Whether parsing was successful */ - success: boolean; - /** Array of parsing errors, if any */ - errors: SqlParseError[]; - /** The parsed AST if successful */ - ast?: unknown; +interface NodeSqlParserResult extends SqlParseResult { + ast?: AST | AST[]; } /** * A SQL parser wrapper around node-sql-parser with enhanced error handling * and validation capabilities for CodeMirror integration. + * + * @example Custom dialect + * ```ts + * import { NodeSqlParser } from "@marimo-team/codemirror-sql"; + * + * const myParser = new NodeSqlParser({ + * getParserOptions: (state) => ({ + * dialect: getDialect(state), + * parseOptions: { + * includeLocations: true, + * }, + * }), + * }); + * ``` */ -export class SqlParser { - private parser: Parser; +export class NodeSqlParser implements SqlParser { + private opts: NodeSqlParserOptions; + private parser: Parser | null = null; - constructor() { - this.parser = new Parser(); + constructor(opts: NodeSqlParserOptions = {}) { + this.opts = opts; } - parse(sql: string): SqlParseResult { + /** + * Lazy import of the node-sql-parser package and create a new Parser instance. + */ + private getParser = lazy(async () => { + if (this.parser) { + return this.parser; + } + const { Parser } = await import("node-sql-parser"); + this.parser = new Parser(); + return this.parser; + }); + + async parse(sql: string, opts: { state: EditorState }): Promise { try { - const ast = this.parser.astify(sql); + const parserOptions = this.opts.getParserOptions?.(opts.state); + const parser = await this.getParser(); + + // Check if this is DuckDB dialect and apply custom processing + if (parserOptions?.database === "DuckDB") { + return this.parseWithDuckDBSupport(sql, parserOptions); + } + + const ast = parser.astify(sql, parserOptions); return { success: true, @@ -55,6 +76,43 @@ export class SqlParser { } } + /** + * Parse SQL with DuckDB-specific syntax support + */ + private async parseWithDuckDBSupport( + sql: string, + parserOptions: Option, + ): Promise { + const parser = await this.getParser(); + + // If the query starts with "from", it's DuckDB-specific syntax + // Just return success without parsing to avoid errors + if (sql.trim().toLowerCase().startsWith("from")) { + debug("From syntax is not supported"); + return { + success: true, + errors: [], + }; + } + + // Otherwise, try standard parsing with PostgreSQL dialect + try { + const postgresOptions = { ...parserOptions, database: "PostgresQL" }; + const ast = parser.astify(sql, postgresOptions); + return { + success: true, + errors: [], + ast, + }; + } catch (error) { + const parseError = this.extractErrorInfo(error, sql); + return { + success: false, + errors: [parseError], + }; + } + } + private extractErrorInfo(error: unknown, _sql: string): SqlParseError { let line = 1; let column = 1; @@ -74,11 +132,11 @@ export class SqlParser { const lineMatch = message.match(/line (\d+)/i); const columnMatch = message.match(/column (\d+)/i); - if (lineMatch) { - line = parseInt(lineMatch[1]!, 10); + if (lineMatch?.[1]) { + line = parseInt(lineMatch[1], 10); } - if (columnMatch) { - column = parseInt(columnMatch[1]!, 10); + if (columnMatch?.[1]) { + column = parseInt(columnMatch[1], 10); } } @@ -99,8 +157,48 @@ export class SqlParser { .trim(); } - validateSql(sql: string): SqlParseError[] { - const result = this.parse(sql); + async validateSql(sql: string, opts: { state: EditorState }): Promise { + const result = await this.parse(sql, opts); return result.errors; } + + /** + * Extracts table references from a SQL query using node-sql-parser + * @param sql The SQL query to analyze + * @returns Array of table names referenced in the query + */ + async extractTableReferences(sql: string): Promise { + try { + const parser = await this.getParser(); + const tableList = parser.tableList(sql); + // Clean up table names - node-sql-parser returns format like "select::null::users" + return tableList.map((table: string) => { + const parts = table.split("::"); + return parts[parts.length - 1] || table; + }); + } catch { + return []; + } + } + + /** + * Extracts column references from a SQL query using node-sql-parser + * @param sql The SQL query to analyze + * @returns Array of column names referenced in the query + */ + async extractColumnReferences(sql: string): Promise { + try { + const parser = await this.getParser(); + const columnList = parser.columnList(sql); + + // Clean up column names - node-sql-parser returns format like "select::null::users" + const cleanColumnList = columnList.map((column: string) => { + const parts = column.split("::"); + return parts[parts.length - 1] || column; + }); + return cleanColumnList; + } catch { + return []; + } + } } diff --git a/src/sql/structure-analyzer.ts b/src/sql/structure-analyzer.ts index fd7dac9..d630b9c 100644 --- a/src/sql/structure-analyzer.ts +++ b/src/sql/structure-analyzer.ts @@ -1,5 +1,5 @@ import type { EditorState } from "@codemirror/state"; -import { SqlParser } from "./parser.js"; +import type { SqlParser } from "./types.js"; /** * Represents a SQL statement with position information @@ -28,27 +28,30 @@ export interface SqlStatement { export class SqlStructureAnalyzer { private parser: SqlParser; private cache = new Map(); + private readonly MAX_CACHE_SIZE = 10; - constructor() { - this.parser = new SqlParser(); + constructor(parser: SqlParser) { + this.parser = parser; } /** * Analyzes the document and extracts all SQL statements */ - analyzeDocument(state: EditorState): SqlStatement[] { + async analyzeDocument(state: EditorState): Promise { const content = state.doc.toString(); const cacheKey = this.generateCacheKey(content); - if (this.cache.has(cacheKey)) { - return this.cache.get(cacheKey)!; + // Check cache + const cached = this.cache.get(cacheKey); + if (cached) { + return cached; } - const statements = this.extractStatements(content, state); + const statements = await this.extractStatements(content, state); this.cache.set(cacheKey, statements); // Keep cache size reasonable - if (this.cache.size > 10) { + if (this.cache.size > this.MAX_CACHE_SIZE) { const firstKey = this.cache.keys().next().value; if (firstKey !== undefined) { this.cache.delete(firstKey); @@ -61,22 +64,26 @@ export class SqlStructureAnalyzer { /** * Gets the SQL statement at a specific cursor position */ - getStatementAtPosition(state: EditorState, position: number): SqlStatement | null { - const statements = this.analyzeDocument(state); + async getStatementAtPosition(state: EditorState, position: number): Promise { + const statements = await this.analyzeDocument(state); return statements.find((stmt) => position >= stmt.from && position <= stmt.to) || null; } /** * Gets all SQL statements that intersect with a selection range */ - getStatementsInRange(state: EditorState, from: number, to: number): SqlStatement[] { - const statements = this.analyzeDocument(state); + async getStatementsInRange( + state: EditorState, + from: number, + to: number, + ): Promise { + const statements = await this.analyzeDocument(state); return statements.filter( (stmt) => stmt.from <= to && stmt.to >= from, // Statements that overlap with the range ); } - private extractStatements(content: string, state: EditorState): SqlStatement[] { + private async extractStatements(content: string, state: EditorState): Promise { const statements: SqlStatement[] = []; // Split content by semicolons to find potential statement boundaries @@ -106,7 +113,7 @@ export class SqlStructureAnalyzer { } // Parse the statement to determine validity and type (use stripped content) - const parseResult = this.parser.parse(strippedContent); + const parseResult = await this.parser.parse(strippedContent, { state }); const type = this.determineStatementType(strippedContent); // Remove trailing semicolon from content for cleaner display diff --git a/src/sql/structure-extension.ts b/src/sql/structure-extension.ts index 9804af3..c1801ae 100644 --- a/src/sql/structure-extension.ts +++ b/src/sql/structure-extension.ts @@ -1,6 +1,8 @@ import { type Extension, RangeSet, StateEffect, StateField } from "@codemirror/state"; import { EditorView, GutterMarker, gutter, type ViewUpdate } from "@codemirror/view"; +import { NodeSqlParser } from "./parser.js"; import { type SqlStatement, SqlStructureAnalyzer } from "./structure-analyzer.js"; +import type { SqlParser } from "./types.js"; export interface SqlGutterConfig { /** Background color for the current statement indicator */ @@ -21,6 +23,8 @@ export interface SqlGutterConfig { hideWhenNotFocused?: boolean; /** Opacity when editor is not focused (overrides hideWhenNotFocused if set) */ unfocusedOpacity?: number; + /** Custom SQL parser instance to use for analysis */ + parser?: SqlParser; } interface SqlGutterState { @@ -145,6 +149,12 @@ function createSqlGutterMarkers( // Add marker to each line of the statement for (let lineNum = statement.lineFrom; lineNum <= statement.lineTo; lineNum++) { try { + // Check if line number is within valid bounds + if (lineNum < 1 || lineNum > view.state.doc.lines) { + // Skip stale line numbers silently - this is expected when text is deleted + continue; + } + const line = view.state.doc.line(lineNum); markers = markers.update({ add: [marker.range(line.from)], @@ -162,8 +172,8 @@ function createSqlGutterMarkers( return markers; } -function createUpdateListener(analyzer: SqlStructureAnalyzer) { - return EditorView.updateListener.of((update: ViewUpdate) => { +function createUpdateListener(analyzer: SqlStructureAnalyzer): Extension { + return EditorView.updateListener.of(async (update: ViewUpdate) => { // Update on document changes, selection changes, or focus changes if (!update.docChanged && !update.selectionSet && !update.focusChanged) { return; @@ -174,8 +184,8 @@ function createUpdateListener(analyzer: SqlStructureAnalyzer) { const cursorPosition = main.head; // Analyze the document for SQL statements - const allStatements = analyzer.analyzeDocument(state); - const currentStatement = analyzer.getStatementAtPosition(state, cursorPosition); + const allStatements = await analyzer.analyzeDocument(state); + const currentStatement = await analyzer.getStatementAtPosition(state, cursorPosition); const newState: SqlGutterState = { currentStatement, @@ -229,8 +239,9 @@ function createSqlGutter(config: SqlGutterConfig): Extension { * based on cursor position. Highlights the current statement and shows dimmed * indicators for other statements. */ -export function sqlStructureGutter(config: SqlGutterConfig = {}): Extension { - const analyzer = new SqlStructureAnalyzer(); +export function sqlStructureGutter(config: SqlGutterConfig = {}): Extension[] { + const parser = config.parser || new NodeSqlParser(); + const analyzer = new SqlStructureAnalyzer(parser); return [ sqlGutterStateField, diff --git a/src/sql/types.ts b/src/sql/types.ts new file mode 100644 index 0000000..03c857d --- /dev/null +++ b/src/sql/types.ts @@ -0,0 +1,56 @@ +import type { EditorState } from "@codemirror/state"; + +/** + * Represents a SQL parsing error with location information + */ +export interface SqlParseError { + /** Error message describing the issue */ + message: string; + /** Line number where the error occurred (1-indexed) */ + line: number; + /** Column number where the error occurred (1-indexed) */ + column: number; + /** Severity level of the error */ + severity: "error" | "warning"; +} +/** + * Result of parsing a SQL statement + */ + +export interface SqlParseResult { + /** Whether parsing was successful */ + success: boolean; + /** Array of parsing errors, if any */ + errors: SqlParseError[]; + /** The parsed AST if successful */ + ast?: unknown; +} + +export interface SqlParser { + /** + * Parse a SQL statement and return the AST + * @param sql - The SQL statement to parse + * @param opts - The options for the parser + * @returns The parsed AST + */ + parse(sql: string, opts: { state: EditorState }): Promise; + /** + * Validate a SQL statement and return any errors + * @param sql - The SQL statement to validate + * @param opts - The options for the parser + * @returns An array of errors + */ + validateSql(sql: string, opts: { state: EditorState }): Promise; + /** + * Extract table references from a SQL query + * @param sql - The SQL query to analyze + * @returns Array of table names referenced in the query + */ + extractTableReferences(sql: string): Promise; + /** + * Extract column references from a SQL query + * @param sql - The SQL query to analyze + * @returns Array of column names referenced in the query + */ + extractColumnReferences(sql: string): Promise; +} diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..4fd0882 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,12 @@ +/** + * Lazy evaluation of a function that returns a promise. + */ +export const lazy = (fn: () => Promise) => { + let value: Promise | undefined; + return async () => { + if (!value) { + value = fn(); + } + return await value; + }; +};