@sqlrooms/sql-editor 0.29.0-rc.5 → 0.29.0-rc.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +40 -0
  2. package/dist/SqlCodeMirrorEditor.d.ts +2 -0
  3. package/dist/SqlCodeMirrorEditor.d.ts.map +1 -1
  4. package/dist/SqlCodeMirrorEditor.js +3 -3
  5. package/dist/SqlCodeMirrorEditor.js.map +1 -1
  6. package/dist/SqlEditor.d.ts.map +1 -1
  7. package/dist/SqlEditor.js +1 -1
  8. package/dist/SqlEditor.js.map +1 -1
  9. package/dist/SqlEditorModal.d.ts.map +1 -1
  10. package/dist/SqlEditorModal.js +1 -1
  11. package/dist/SqlEditorModal.js.map +1 -1
  12. package/dist/SqlEditorSlice.d.ts +41 -0
  13. package/dist/SqlEditorSlice.d.ts.map +1 -1
  14. package/dist/SqlEditorSlice.js +142 -50
  15. package/dist/SqlEditorSlice.js.map +1 -1
  16. package/dist/SqlQuery.d.ts +34 -0
  17. package/dist/SqlQuery.d.ts.map +1 -0
  18. package/dist/SqlQuery.js +51 -0
  19. package/dist/SqlQuery.js.map +1 -0
  20. package/dist/SqlQueryBlock.d.ts +20 -0
  21. package/dist/SqlQueryBlock.d.ts.map +1 -0
  22. package/dist/SqlQueryBlock.js +148 -0
  23. package/dist/SqlQueryBlock.js.map +1 -0
  24. package/dist/codemirror/extensions/completion.d.ts +5 -2
  25. package/dist/codemirror/extensions/completion.d.ts.map +1 -1
  26. package/dist/codemirror/extensions/completion.js +82 -22
  27. package/dist/codemirror/extensions/completion.js.map +1 -1
  28. package/dist/codemirror/extensions/duck-db/duck-db.d.ts.map +1 -1
  29. package/dist/codemirror/extensions/duck-db/duck-db.js +3 -3
  30. package/dist/codemirror/extensions/duck-db/duck-db.js.map +1 -1
  31. package/dist/codemirror/extensions/duck-db/duckdb-sql.d.ts +2 -0
  32. package/dist/codemirror/extensions/duck-db/duckdb-sql.d.ts.map +1 -1
  33. package/dist/codemirror/extensions/duck-db/duckdb-sql.js +33 -2
  34. package/dist/codemirror/extensions/duck-db/duckdb-sql.js.map +1 -1
  35. package/dist/codemirror/extensions/sql-keymap.d.ts.map +1 -1
  36. package/dist/codemirror/extensions/sql-keymap.js +7 -8
  37. package/dist/codemirror/extensions/sql-keymap.js.map +1 -1
  38. package/dist/codemirror/utils/schema-converter.d.ts.map +1 -1
  39. package/dist/codemirror/utils/schema-converter.js +56 -1
  40. package/dist/codemirror/utils/schema-converter.js.map +1 -1
  41. package/dist/components/QueryEditorPanel.d.ts +6 -0
  42. package/dist/components/QueryEditorPanel.d.ts.map +1 -1
  43. package/dist/components/QueryEditorPanel.js +6 -0
  44. package/dist/components/QueryEditorPanel.js.map +1 -1
  45. package/dist/components/QueryEditorPanelActions.d.ts +6 -0
  46. package/dist/components/QueryEditorPanelActions.d.ts.map +1 -1
  47. package/dist/components/QueryEditorPanelActions.js +17 -5
  48. package/dist/components/QueryEditorPanelActions.js.map +1 -1
  49. package/dist/components/QueryEditorPanelEditor.d.ts +8 -0
  50. package/dist/components/QueryEditorPanelEditor.d.ts.map +1 -1
  51. package/dist/components/QueryEditorPanelEditor.js +29 -9
  52. package/dist/components/QueryEditorPanelEditor.js.map +1 -1
  53. package/dist/components/QueryResultLimitSelect.d.ts.map +1 -1
  54. package/dist/components/QueryResultLimitSelect.js +3 -2
  55. package/dist/components/QueryResultLimitSelect.js.map +1 -1
  56. package/dist/components/QueryResultPanel.d.ts +8 -0
  57. package/dist/components/QueryResultPanel.d.ts.map +1 -1
  58. package/dist/components/QueryResultPanel.js +28 -13
  59. package/dist/components/QueryResultPanel.js.map +1 -1
  60. package/dist/components/SqlEditorHeader.d.ts +2 -0
  61. package/dist/components/SqlEditorHeader.d.ts.map +1 -1
  62. package/dist/components/SqlEditorHeader.js +3 -2
  63. package/dist/components/SqlEditorHeader.js.map +1 -1
  64. package/dist/index.d.ts +5 -1
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +2 -0
  67. package/dist/index.js.map +1 -1
  68. package/package.json +13 -12
package/README.md CHANGED
@@ -4,6 +4,8 @@ This package provides:
4
4
 
5
5
  - `createSqlEditorSlice()` for query tabs, execution, and results state
6
6
  - `SqlEditor` and `SqlEditorModal` UI
7
+ - `SqlQuery` compound components and `SqlQueryBlock` for reusable single-query
8
+ surfaces
7
9
  - `SqlMonacoEditor` standalone SQL editor
8
10
  - helpers/components for query results, table structure, and SQL data sources
9
11
 
@@ -95,6 +97,44 @@ function RunQueryButton() {
95
97
  }
96
98
  ```
97
99
 
100
+ For reusable single-query surfaces, use id-addressable query actions:
101
+
102
+ ```tsx
103
+ const ensureQuery = useRoomStore((state) => state.sqlEditor.ensureQuery);
104
+ const runQueryById = useRoomStore((state) => state.sqlEditor.runQueryById);
105
+
106
+ ensureQuery('query-1', {name: 'Top Airports', query: 'SELECT * FROM airports'});
107
+ await runQueryById('query-1');
108
+ ```
109
+
110
+ ## Single Query UI
111
+
112
+ `SqlQuery` is a compound component for rearranging and styling a single query
113
+ experience without the full tabbed workbench:
114
+
115
+ ```tsx
116
+ import {SqlQuery} from '@sqlrooms/sql-editor';
117
+
118
+ export function QueryBlock() {
119
+ return (
120
+ <SqlQuery.Root queryId="query-1" name="Top Airports">
121
+ <SqlQuery.Header title="Top Airports">
122
+ <SqlQuery.Actions />
123
+ </SqlQuery.Header>
124
+ <SqlQuery.Editor />
125
+ <SqlQuery.Results />
126
+ </SqlQuery.Root>
127
+ );
128
+ }
129
+ ```
130
+
131
+ `SqlQuery.Results` accepts the same props as `QueryResultPanel`, including
132
+ `footerDetails` for small metadata rendered at the end of the result footer and
133
+ `dataTableClassName` for styling the inner paginated table.
134
+
135
+ Use `createSqlQueryBlockDefinition()` when a SQL query should be both embeddable
136
+ as a stateful block and openable as an artifact shell.
137
+
98
138
  ## Standalone editor (without SQLRooms store)
99
139
 
100
140
  `SqlMonacoEditor` can be used independently:
@@ -12,6 +12,8 @@ export interface SqlCodeMirrorEditorProps extends Omit<CodeMirrorEditorProps, 'e
12
12
  connector?: DuckDbConnector;
13
13
  /** Table schemas for autocompletion and hover tooltips */
14
14
  tableSchemas?: DataTable[];
15
+ /** Hide all editor gutters. Useful for compact embedded editors. */
16
+ hideGutter?: boolean;
15
17
  /** Callback to get the latest table schemas */
16
18
  getLatestSchemas?: () => {
17
19
  tableSchemas: DataTable[];
@@ -1 +1 @@
1
- {"version":3,"file":"SqlCodeMirrorEditor.d.ts","sourceRoot":"","sources":["../src/SqlCodeMirrorEditor.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAqC,MAAM,OAAO,CAAC;AAG1D,OAAO,KAAK,EAAC,SAAS,EAAE,eAAe,EAAC,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAmB,qBAAqB,EAAC,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EAGL,KAAK,UAAU,EAChB,MAAM,8CAA8C,CAAC;AAItD,MAAM,WAAW,wBAAyB,SAAQ,IAAI,CACpD,qBAAqB,EACrB,YAAY,CACb;IACC,0DAA0D;IAC1D,OAAO,CAAC,EAAE,UAAU,CAAC;IACrB;;;OAGG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,0DAA0D;IAC1D,YAAY,CAAC,EAAE,SAAS,EAAE,CAAC;IAC3B,+CAA+C;IAC/C,gBAAgB,CAAC,EAAE,MAAM;QAAC,YAAY,EAAE,SAAS,EAAE,CAAA;KAAC,CAAC;IACrD,0EAA0E;IAC1E,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AASD;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,wBAAwB,CA8DlE,CAAC"}
1
+ {"version":3,"file":"SqlCodeMirrorEditor.d.ts","sourceRoot":"","sources":["../src/SqlCodeMirrorEditor.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAqC,MAAM,OAAO,CAAC;AAG1D,OAAO,KAAK,EAAC,SAAS,EAAE,eAAe,EAAC,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAmB,qBAAqB,EAAC,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EAGL,KAAK,UAAU,EAChB,MAAM,8CAA8C,CAAC;AAItD,MAAM,WAAW,wBAAyB,SAAQ,IAAI,CACpD,qBAAqB,EACrB,YAAY,CACb;IACC,0DAA0D;IAC1D,OAAO,CAAC,EAAE,UAAU,CAAC;IACrB;;;OAGG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,0DAA0D;IAC1D,YAAY,CAAC,EAAE,SAAS,EAAE,CAAC;IAC3B,oEAAoE;IACpE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,+CAA+C;IAC/C,gBAAgB,CAAC,EAAE,MAAM;QAAC,YAAY,EAAE,SAAS,EAAE,CAAA;KAAC,CAAC;IACrD,0EAA0E;IAC1E,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AASD;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,wBAAwB,CA+DlE,CAAC"}
@@ -16,7 +16,7 @@ const EDITOR_OPTIONS = {
16
16
  * Lightweight alternative to SqlMonacoEditor with syntax highlighting,
17
17
  * linting, schema-aware completions, and hover tooltips. Cmd+Enter to run query.
18
18
  */
19
- export const SqlCodeMirrorEditor = ({ dialect = SqlDialects.DuckDb, connector, tableSchemas = [], getLatestSchemas, onRunQuery, onMount, options, ...restProps }) => {
19
+ export const SqlCodeMirrorEditor = ({ dialect = SqlDialects.DuckDb, connector, tableSchemas = [], hideGutter, getLatestSchemas, onRunQuery, onMount, options, ...restProps }) => {
20
20
  const viewRef = useRef(null);
21
21
  // Get current schemas (use callback if provided)
22
22
  const currentSchemas = useMemo(() => {
@@ -34,9 +34,9 @@ export const SqlCodeMirrorEditor = ({ dialect = SqlDialects.DuckDb, connector, t
34
34
  connector,
35
35
  }),
36
36
  createSqlKeymap(onRunQuery),
37
- createSqlTheme(),
37
+ createSqlTheme({ hideGutter }),
38
38
  ];
39
- }, [dialect, currentSchemas, onRunQuery, connector]);
39
+ }, [dialect, currentSchemas, onRunQuery, connector, hideGutter]);
40
40
  // Handle editor mount
41
41
  const handleEditorMount = useCallback((view) => {
42
42
  viewRef.current = view;
@@ -1 +1 @@
1
- {"version":3,"file":"SqlCodeMirrorEditor.js","sourceRoot":"","sources":["../src/SqlCodeMirrorEditor.tsx"],"names":[],"mappings":";AAAA,OAAc,EAAC,WAAW,EAAE,OAAO,EAAE,MAAM,EAAC,MAAM,OAAO,CAAC;AAI1D,OAAO,EAAC,gBAAgB,EAAwB,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EACL,kBAAkB,EAClB,WAAW,GAEZ,MAAM,8CAA8C,CAAC;AACtD,OAAO,EAAC,eAAe,EAAC,MAAM,oCAAoC,CAAC;AACnE,OAAO,EAAC,cAAc,EAAC,MAAM,+BAA+B,CAAC;AAqB7D,MAAM,cAAc,GAAqC;IACvD,WAAW,EAAE,IAAI;IACjB,YAAY,EAAE,IAAI;IAClB,mBAAmB,EAAE,IAAI;IACzB,cAAc,EAAE,IAAI;CACrB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAuC,CAAC,EACtE,OAAO,GAAG,WAAW,CAAC,MAAM,EAC5B,SAAS,EACT,YAAY,GAAG,EAAE,EACjB,gBAAgB,EAChB,UAAU,EACV,OAAO,EACP,OAAO,EACP,GAAG,SAAS,EACb,EAAE,EAAE;IACH,MAAM,OAAO,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAC;IAEhD,iDAAiD;IACjD,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,EAAE;QAClC,IAAI,gBAAgB,EAAE,CAAC;YACrB,OAAO,gBAAgB,EAAE,CAAC,YAAY,CAAC;QACzC,CAAC;QACD,OAAO,YAAY,CAAC;IACtB,CAAC,EAAE,CAAC,gBAAgB,EAAE,YAAY,CAAC,CAAC,CAAC;IAErC,mBAAmB;IACnB,MAAM,UAAU,GAAG,OAAO,CAAc,GAAG,EAAE;QAC3C,OAAO;YACL,GAAG,kBAAkB,CAAC;gBACpB,OAAO;gBACP,cAAc;gBACd,SAAS;aACV,CAAC;YACF,eAAe,CAAC,UAAU,CAAC;YAC3B,cAAc,EAAE;SACjB,CAAC;IACJ,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC;IAErD,sBAAsB;IACtB,MAAM,iBAAiB,GAAG,WAAW,CACnC,CAAC,IAAgB,EAAE,EAAE;QACnB,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;QAEvB,gCAAgC;QAChC,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,CAAC,IAAI,CAAC,CAAC;QAChB,CAAC;IACH,CAAC,EACD,CAAC,OAAO,CAAC,CACV,CAAC;IAEF,MAAM,eAAe,GAAG,OAAO,CAC7B,GAAqC,EAAE,CAAC,CAAC;QACvC,GAAG,cAAc;QACjB,GAAG,OAAO;KACX,CAAC,EACF,CAAC,OAAO,CAAC,CACV,CAAC;IAEF,OAAO,CACL,KAAC,gBAAgB,IACf,OAAO,EAAE,iBAAiB,EAC1B,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,eAAe,KACpB,SAAS,GACb,CACH,CAAC;AACJ,CAAC,CAAC","sourcesContent":["import React, {useCallback, useMemo, useRef} from 'react';\nimport type {EditorView} from '@codemirror/view';\nimport type {Extension} from '@codemirror/state';\nimport type {DataTable, DuckDbConnector} from '@sqlrooms/duckdb';\nimport {CodeMirrorEditor, CodeMirrorEditorProps} from '@sqlrooms/codemirror';\nimport {\n createSqlExtension,\n SqlDialects,\n type SqlDialect,\n} from './codemirror/extensions/create-sql-extension';\nimport {createSqlKeymap} from './codemirror/extensions/sql-keymap';\nimport {createSqlTheme} from './codemirror/themes/sql-theme';\n\nexport interface SqlCodeMirrorEditorProps extends Omit<\n CodeMirrorEditorProps,\n 'extensions'\n> {\n /** SQL dialect for syntax highlighting and completions */\n dialect?: SqlDialect;\n /**\n * Connector for dynamic function suggestions\n * TODO: change to generic connector interface to support multiple dialects\n */\n connector?: DuckDbConnector;\n /** Table schemas for autocompletion and hover tooltips */\n tableSchemas?: DataTable[];\n /** Callback to get the latest table schemas */\n getLatestSchemas?: () => {tableSchemas: DataTable[]};\n /** Callback when Cmd+Enter is pressed (selected text or full document) */\n onRunQuery?: (query: string) => void;\n}\n\nconst EDITOR_OPTIONS: CodeMirrorEditorProps['options'] = {\n lineNumbers: true,\n lineWrapping: true,\n highlightActiveLine: true,\n autocompletion: true,\n};\n\n/**\n * CodeMirror editor for SQL with dialect-specific support\n *\n * Lightweight alternative to SqlMonacoEditor with syntax highlighting,\n * linting, schema-aware completions, and hover tooltips. Cmd+Enter to run query.\n */\nexport const SqlCodeMirrorEditor: React.FC<SqlCodeMirrorEditorProps> = ({\n dialect = SqlDialects.DuckDb,\n connector,\n tableSchemas = [],\n getLatestSchemas,\n onRunQuery,\n onMount,\n options,\n ...restProps\n}) => {\n const viewRef = useRef<EditorView | null>(null);\n\n // Get current schemas (use callback if provided)\n const currentSchemas = useMemo(() => {\n if (getLatestSchemas) {\n return getLatestSchemas().tableSchemas;\n }\n return tableSchemas;\n }, [getLatestSchemas, tableSchemas]);\n\n // Build extensions\n const extensions = useMemo<Extension[]>(() => {\n return [\n ...createSqlExtension({\n dialect,\n currentSchemas,\n connector,\n }),\n createSqlKeymap(onRunQuery),\n createSqlTheme(),\n ];\n }, [dialect, currentSchemas, onRunQuery, connector]);\n\n // Handle editor mount\n const handleEditorMount = useCallback(\n (view: EditorView) => {\n viewRef.current = view;\n\n // Call user onMount if provided\n if (onMount) {\n onMount(view);\n }\n },\n [onMount],\n );\n\n const combinedOptions = useMemo(\n (): CodeMirrorEditorProps['options'] => ({\n ...EDITOR_OPTIONS,\n ...options,\n }),\n [options],\n );\n\n return (\n <CodeMirrorEditor\n onMount={handleEditorMount}\n extensions={extensions}\n options={combinedOptions}\n {...restProps}\n />\n );\n};\n"]}
1
+ {"version":3,"file":"SqlCodeMirrorEditor.js","sourceRoot":"","sources":["../src/SqlCodeMirrorEditor.tsx"],"names":[],"mappings":";AAAA,OAAc,EAAC,WAAW,EAAE,OAAO,EAAE,MAAM,EAAC,MAAM,OAAO,CAAC;AAI1D,OAAO,EAAC,gBAAgB,EAAwB,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EACL,kBAAkB,EAClB,WAAW,GAEZ,MAAM,8CAA8C,CAAC;AACtD,OAAO,EAAC,eAAe,EAAC,MAAM,oCAAoC,CAAC;AACnE,OAAO,EAAC,cAAc,EAAC,MAAM,+BAA+B,CAAC;AAuB7D,MAAM,cAAc,GAAqC;IACvD,WAAW,EAAE,IAAI;IACjB,YAAY,EAAE,IAAI;IAClB,mBAAmB,EAAE,IAAI;IACzB,cAAc,EAAE,IAAI;CACrB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAuC,CAAC,EACtE,OAAO,GAAG,WAAW,CAAC,MAAM,EAC5B,SAAS,EACT,YAAY,GAAG,EAAE,EACjB,UAAU,EACV,gBAAgB,EAChB,UAAU,EACV,OAAO,EACP,OAAO,EACP,GAAG,SAAS,EACb,EAAE,EAAE;IACH,MAAM,OAAO,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAC;IAEhD,iDAAiD;IACjD,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,EAAE;QAClC,IAAI,gBAAgB,EAAE,CAAC;YACrB,OAAO,gBAAgB,EAAE,CAAC,YAAY,CAAC;QACzC,CAAC;QACD,OAAO,YAAY,CAAC;IACtB,CAAC,EAAE,CAAC,gBAAgB,EAAE,YAAY,CAAC,CAAC,CAAC;IAErC,mBAAmB;IACnB,MAAM,UAAU,GAAG,OAAO,CAAc,GAAG,EAAE;QAC3C,OAAO;YACL,GAAG,kBAAkB,CAAC;gBACpB,OAAO;gBACP,cAAc;gBACd,SAAS;aACV,CAAC;YACF,eAAe,CAAC,UAAU,CAAC;YAC3B,cAAc,CAAC,EAAC,UAAU,EAAC,CAAC;SAC7B,CAAC;IACJ,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC;IAEjE,sBAAsB;IACtB,MAAM,iBAAiB,GAAG,WAAW,CACnC,CAAC,IAAgB,EAAE,EAAE;QACnB,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;QAEvB,gCAAgC;QAChC,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,CAAC,IAAI,CAAC,CAAC;QAChB,CAAC;IACH,CAAC,EACD,CAAC,OAAO,CAAC,CACV,CAAC;IAEF,MAAM,eAAe,GAAG,OAAO,CAC7B,GAAqC,EAAE,CAAC,CAAC;QACvC,GAAG,cAAc;QACjB,GAAG,OAAO;KACX,CAAC,EACF,CAAC,OAAO,CAAC,CACV,CAAC;IAEF,OAAO,CACL,KAAC,gBAAgB,IACf,OAAO,EAAE,iBAAiB,EAC1B,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,eAAe,KACpB,SAAS,GACb,CACH,CAAC;AACJ,CAAC,CAAC","sourcesContent":["import React, {useCallback, useMemo, useRef} from 'react';\nimport type {EditorView} from '@codemirror/view';\nimport type {Extension} from '@codemirror/state';\nimport type {DataTable, DuckDbConnector} from '@sqlrooms/duckdb';\nimport {CodeMirrorEditor, CodeMirrorEditorProps} from '@sqlrooms/codemirror';\nimport {\n createSqlExtension,\n SqlDialects,\n type SqlDialect,\n} from './codemirror/extensions/create-sql-extension';\nimport {createSqlKeymap} from './codemirror/extensions/sql-keymap';\nimport {createSqlTheme} from './codemirror/themes/sql-theme';\n\nexport interface SqlCodeMirrorEditorProps extends Omit<\n CodeMirrorEditorProps,\n 'extensions'\n> {\n /** SQL dialect for syntax highlighting and completions */\n dialect?: SqlDialect;\n /**\n * Connector for dynamic function suggestions\n * TODO: change to generic connector interface to support multiple dialects\n */\n connector?: DuckDbConnector;\n /** Table schemas for autocompletion and hover tooltips */\n tableSchemas?: DataTable[];\n /** Hide all editor gutters. Useful for compact embedded editors. */\n hideGutter?: boolean;\n /** Callback to get the latest table schemas */\n getLatestSchemas?: () => {tableSchemas: DataTable[]};\n /** Callback when Cmd+Enter is pressed (selected text or full document) */\n onRunQuery?: (query: string) => void;\n}\n\nconst EDITOR_OPTIONS: CodeMirrorEditorProps['options'] = {\n lineNumbers: true,\n lineWrapping: true,\n highlightActiveLine: true,\n autocompletion: true,\n};\n\n/**\n * CodeMirror editor for SQL with dialect-specific support\n *\n * Lightweight alternative to SqlMonacoEditor with syntax highlighting,\n * linting, schema-aware completions, and hover tooltips. Cmd+Enter to run query.\n */\nexport const SqlCodeMirrorEditor: React.FC<SqlCodeMirrorEditorProps> = ({\n dialect = SqlDialects.DuckDb,\n connector,\n tableSchemas = [],\n hideGutter,\n getLatestSchemas,\n onRunQuery,\n onMount,\n options,\n ...restProps\n}) => {\n const viewRef = useRef<EditorView | null>(null);\n\n // Get current schemas (use callback if provided)\n const currentSchemas = useMemo(() => {\n if (getLatestSchemas) {\n return getLatestSchemas().tableSchemas;\n }\n return tableSchemas;\n }, [getLatestSchemas, tableSchemas]);\n\n // Build extensions\n const extensions = useMemo<Extension[]>(() => {\n return [\n ...createSqlExtension({\n dialect,\n currentSchemas,\n connector,\n }),\n createSqlKeymap(onRunQuery),\n createSqlTheme({hideGutter}),\n ];\n }, [dialect, currentSchemas, onRunQuery, connector, hideGutter]);\n\n // Handle editor mount\n const handleEditorMount = useCallback(\n (view: EditorView) => {\n viewRef.current = view;\n\n // Call user onMount if provided\n if (onMount) {\n onMount(view);\n }\n },\n [onMount],\n );\n\n const combinedOptions = useMemo(\n (): CodeMirrorEditorProps['options'] => ({\n ...EDITOR_OPTIONS,\n ...options,\n }),\n [options],\n );\n\n return (\n <CodeMirrorEditor\n onMount={handleEditorMount}\n extensions={extensions}\n options={combinedOptions}\n {...restProps}\n />\n );\n};\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"SqlEditor.d.ts","sourceRoot":"","sources":["../src/SqlEditor.tsx"],"names":[],"mappings":"AAOA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAGnD,OAAO,EAAC,gBAAgB,EAAC,MAAM,+BAA+B,CAAC;AAE/D,OAAO,EAEL,wBAAwB,EACzB,MAAM,kCAAkC,CAAC;AAG1C,MAAM,MAAM,cAAc,GAAG;IAC3B;;0EAEsE;IACtE,MAAM,CAAC,EAAE,wBAAwB,CAAC,QAAQ,CAAC,CAAC;IAC5C,kDAAkD;IAClD,MAAM,EAAE,OAAO,CAAC;IAChB,uEAAuE;IACvE,kBAAkB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACrC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,IAAI,CACrB,KAAK,CAAC,cAAc,CAAC,OAAO,gBAAgB,CAAC,EAC7C,YAAY,GAAG,kBAAkB,CAClC,CAAC;IACF,0DAA0D;IAC1D,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAEF,QAAA,MAAM,SAAS,4CAiFb,CAAC;AAGH,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"SqlEditor.d.ts","sourceRoot":"","sources":["../src/SqlEditor.tsx"],"names":[],"mappings":"AAOA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAGnD,OAAO,EAAC,gBAAgB,EAAC,MAAM,+BAA+B,CAAC;AAE/D,OAAO,EAEL,wBAAwB,EACzB,MAAM,kCAAkC,CAAC;AAG1C,MAAM,MAAM,cAAc,GAAG;IAC3B;;0EAEsE;IACtE,MAAM,CAAC,EAAE,wBAAwB,CAAC,QAAQ,CAAC,CAAC;IAC5C,kDAAkD;IAClD,MAAM,EAAE,OAAO,CAAC;IAChB,uEAAuE;IACvE,kBAAkB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACrC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,IAAI,CACrB,KAAK,CAAC,cAAc,CAAC,OAAO,gBAAgB,CAAC,EAC7C,YAAY,GAAG,kBAAkB,CAClC,CAAC;IACF,0DAA0D;IAC1D,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAEF,QAAA,MAAM,SAAS,4CAkFb,CAAC;AAGH,eAAe,SAAS,CAAC"}
package/dist/SqlEditor.js CHANGED
@@ -27,7 +27,7 @@ const SqlEditor = React.memo((props) => {
27
27
  const handleCreateTable = useCallback(() => {
28
28
  setCreateTableModalOpen(true);
29
29
  }, []);
30
- return (_jsxs("div", { className: "relative flex h-full w-full flex-col overflow-hidden", children: [_jsx(SqlEditorHeader, { title: "SQL Editor", showDocs: showDocs, documentationPanel: documentationPanel, onToggleDocs: handleToggleDocs }), _jsx("div", { className: "bg-muted h-full grow", children: _jsxs(ResizablePanelGroup, { orientation: "horizontal", className: "h-full", children: [_jsx(ResizablePanel, { defaultSize: showDocs ? '70' : '100', children: _jsxs(ResizablePanelGroup, { orientation: "vertical", className: "h-full", children: [_jsx(ResizablePanel, { defaultSize: "50", className: "flex flex-row", children: _jsxs(ResizablePanelGroup, { orientation: "horizontal", children: [_jsx(ResizablePanel, { defaultSize: "20", children: _jsx(TableStructurePanel, { schema: schema }) }), _jsx(ResizableHandle, { withHandle: true }), _jsx(ResizablePanel, { defaultSize: "80", children: _jsx(QueryEditorPanel, {}) })] }) }), _jsx(ResizableHandle, { withHandle: true }), _jsx(ResizablePanel, { defaultSize: "50", children: _jsx(QueryResultPanel, { onRowClick: queryResultProps?.onRowClick, onRowDoubleClick: queryResultProps?.onRowDoubleClick, renderActions: () => (_jsx("div", { className: "flex gap-2", children: _jsxs(Button, { size: "xs", onClick: handleCreateTable, children: [_jsx(PlusIcon, { className: "h-4 w-4" }), "New table"] }) })) }) })] }) }), showDocs && (_jsxs(_Fragment, { children: [_jsx(ResizableHandle, { withHandle: true }), _jsx(ResizablePanel, { defaultSize: "30", children: documentationPanel })] }))] }) }), _jsx(CreateTableModal, { query: lastQuery, isOpen: createTableModalOpen, onClose: () => setCreateTableModalOpen(false), allowMultipleStatements: true, showSchemaSelection: true })] }));
30
+ return (_jsxs("div", { className: "relative flex h-full w-full flex-col overflow-hidden", children: [_jsx(SqlEditorHeader, { title: "SQL Editor", showDocs: showDocs, documentationPanel: documentationPanel, onToggleDocs: handleToggleDocs, onClose: props.onClose }), _jsx("div", { className: "bg-muted h-full grow", children: _jsxs(ResizablePanelGroup, { orientation: "horizontal", className: "h-full", children: [_jsx(ResizablePanel, { defaultSize: showDocs ? '70' : '100', children: _jsxs(ResizablePanelGroup, { orientation: "vertical", className: "h-full", children: [_jsx(ResizablePanel, { defaultSize: "50", className: "flex flex-row", children: _jsxs(ResizablePanelGroup, { orientation: "horizontal", children: [_jsx(ResizablePanel, { defaultSize: "20", children: _jsx(TableStructurePanel, { schema: schema }) }), _jsx(ResizableHandle, { withHandle: true }), _jsx(ResizablePanel, { defaultSize: "80", children: _jsx(QueryEditorPanel, {}) })] }) }), _jsx(ResizableHandle, { withHandle: true }), _jsx(ResizablePanel, { defaultSize: "50", children: _jsx(QueryResultPanel, { onRowClick: queryResultProps?.onRowClick, onRowDoubleClick: queryResultProps?.onRowDoubleClick, renderActions: () => (_jsx("div", { className: "flex gap-2", children: _jsxs(Button, { size: "xs", onClick: handleCreateTable, children: [_jsx(PlusIcon, { className: "h-4 w-4" }), "New table"] }) })) }) })] }) }), showDocs && (_jsxs(_Fragment, { children: [_jsx(ResizableHandle, { withHandle: true }), _jsx(ResizablePanel, { defaultSize: "30", children: documentationPanel })] }))] }) }), _jsx(CreateTableModal, { query: lastQuery, isOpen: createTableModalOpen, onClose: () => setCreateTableModalOpen(false), allowMultipleStatements: true, showSchemaSelection: true })] }));
31
31
  });
32
32
  SqlEditor.displayName = 'SqlEditor';
33
33
  export default SqlEditor;
@@ -1 +1 @@
1
- {"version":3,"file":"SqlEditor.js","sourceRoot":"","sources":["../src/SqlEditor.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n Button,\n ResizableHandle,\n ResizablePanel,\n ResizablePanelGroup,\n} from '@sqlrooms/ui';\nimport {PlusIcon} from 'lucide-react';\nimport React, {useCallback, useState} from 'react';\nimport CreateTableModal from './components/CreateTableModal';\nimport {QueryEditorPanel} from './components/QueryEditorPanel';\nimport {QueryResultPanel} from './components/QueryResultPanel';\nimport {SqlEditorHeader} from './components/SqlEditorHeader';\nimport {\n TableStructurePanel,\n TableStructurePanelProps,\n} from './components/TableStructurePanel';\nimport {useStoreWithSqlEditor} from './SqlEditorSlice';\n\nexport type SqlEditorProps = {\n /** The database schema to use. Defaults to '*'.\n * If '*' is provided, all tables will be shown.\n * If a function is provided, it will be used to filter the tables. */\n schema?: TableStructurePanelProps['schema'];\n /** Whether the SQL editor is currently visible */\n isOpen: boolean;\n /** Optional component to render SQL documentation in the side panel */\n documentationPanel?: React.ReactNode;\n /**\n * Props forwarded to `QueryResultPanel` to configure result behavior.\n * This provides a single entry point for table interactions.\n */\n queryResultProps?: Pick<\n React.ComponentProps<typeof QueryResultPanel>,\n 'onRowClick' | 'onRowDoubleClick'\n >;\n /** Callback fired when the SQL editor should be closed */\n onClose: () => void;\n};\n\nconst SqlEditor = React.memo<SqlEditorProps>((props) => {\n const {schema = '*', documentationPanel, queryResultProps} = props;\n\n const lastQuery = useStoreWithSqlEditor((s) => {\n const selectedId = s.sqlEditor.config.selectedQueryId;\n const qr = s.sqlEditor.queryResultsById[selectedId];\n if (qr?.status === 'success' && qr?.type === 'select') return qr.query;\n return s.sqlEditor.getCurrentQuery();\n });\n // UI state\n const [showDocs, setShowDocs] = useState(false);\n const [createTableModalOpen, setCreateTableModalOpen] = useState(false);\n\n // Handlers\n const handleToggleDocs = useCallback((show: boolean) => {\n setShowDocs(show);\n }, []);\n\n const handleCreateTable = useCallback(() => {\n setCreateTableModalOpen(true);\n }, []);\n\n return (\n <div className=\"relative flex h-full w-full flex-col overflow-hidden\">\n <SqlEditorHeader\n title=\"SQL Editor\"\n showDocs={showDocs}\n documentationPanel={documentationPanel}\n onToggleDocs={handleToggleDocs}\n />\n <div className=\"bg-muted h-full grow\">\n <ResizablePanelGroup orientation=\"horizontal\" className=\"h-full\">\n <ResizablePanel defaultSize={showDocs ? '70' : '100'}>\n <ResizablePanelGroup orientation=\"vertical\" className=\"h-full\">\n <ResizablePanel defaultSize=\"50\" className=\"flex flex-row\">\n <ResizablePanelGroup orientation=\"horizontal\">\n <ResizablePanel defaultSize=\"20\">\n <TableStructurePanel schema={schema} />\n </ResizablePanel>\n <ResizableHandle withHandle />\n <ResizablePanel defaultSize=\"80\">\n <QueryEditorPanel />\n </ResizablePanel>\n </ResizablePanelGroup>\n </ResizablePanel>\n <ResizableHandle withHandle />\n <ResizablePanel defaultSize=\"50\">\n <QueryResultPanel\n onRowClick={queryResultProps?.onRowClick}\n onRowDoubleClick={queryResultProps?.onRowDoubleClick}\n renderActions={() => (\n <div className=\"flex gap-2\">\n <Button size=\"xs\" onClick={handleCreateTable}>\n <PlusIcon className=\"h-4 w-4\" />\n New table\n </Button>\n </div>\n )}\n />\n </ResizablePanel>\n </ResizablePanelGroup>\n </ResizablePanel>\n {showDocs && (\n <>\n <ResizableHandle withHandle />\n <ResizablePanel defaultSize=\"30\">\n {documentationPanel}\n </ResizablePanel>\n </>\n )}\n </ResizablePanelGroup>\n </div>\n <CreateTableModal\n query={lastQuery}\n isOpen={createTableModalOpen}\n onClose={() => setCreateTableModalOpen(false)}\n allowMultipleStatements={true}\n showSchemaSelection={true}\n />\n </div>\n );\n});\nSqlEditor.displayName = 'SqlEditor';\n\nexport default SqlEditor;\n"]}
1
+ {"version":3,"file":"SqlEditor.js","sourceRoot":"","sources":["../src/SqlEditor.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n Button,\n ResizableHandle,\n ResizablePanel,\n ResizablePanelGroup,\n} from '@sqlrooms/ui';\nimport {PlusIcon} from 'lucide-react';\nimport React, {useCallback, useState} from 'react';\nimport CreateTableModal from './components/CreateTableModal';\nimport {QueryEditorPanel} from './components/QueryEditorPanel';\nimport {QueryResultPanel} from './components/QueryResultPanel';\nimport {SqlEditorHeader} from './components/SqlEditorHeader';\nimport {\n TableStructurePanel,\n TableStructurePanelProps,\n} from './components/TableStructurePanel';\nimport {useStoreWithSqlEditor} from './SqlEditorSlice';\n\nexport type SqlEditorProps = {\n /** The database schema to use. Defaults to '*'.\n * If '*' is provided, all tables will be shown.\n * If a function is provided, it will be used to filter the tables. */\n schema?: TableStructurePanelProps['schema'];\n /** Whether the SQL editor is currently visible */\n isOpen: boolean;\n /** Optional component to render SQL documentation in the side panel */\n documentationPanel?: React.ReactNode;\n /**\n * Props forwarded to `QueryResultPanel` to configure result behavior.\n * This provides a single entry point for table interactions.\n */\n queryResultProps?: Pick<\n React.ComponentProps<typeof QueryResultPanel>,\n 'onRowClick' | 'onRowDoubleClick'\n >;\n /** Callback fired when the SQL editor should be closed */\n onClose: () => void;\n};\n\nconst SqlEditor = React.memo<SqlEditorProps>((props) => {\n const {schema = '*', documentationPanel, queryResultProps} = props;\n\n const lastQuery = useStoreWithSqlEditor((s) => {\n const selectedId = s.sqlEditor.config.selectedQueryId;\n const qr = s.sqlEditor.queryResultsById[selectedId];\n if (qr?.status === 'success' && qr?.type === 'select') return qr.query;\n return s.sqlEditor.getCurrentQuery();\n });\n // UI state\n const [showDocs, setShowDocs] = useState(false);\n const [createTableModalOpen, setCreateTableModalOpen] = useState(false);\n\n // Handlers\n const handleToggleDocs = useCallback((show: boolean) => {\n setShowDocs(show);\n }, []);\n\n const handleCreateTable = useCallback(() => {\n setCreateTableModalOpen(true);\n }, []);\n\n return (\n <div className=\"relative flex h-full w-full flex-col overflow-hidden\">\n <SqlEditorHeader\n title=\"SQL Editor\"\n showDocs={showDocs}\n documentationPanel={documentationPanel}\n onToggleDocs={handleToggleDocs}\n onClose={props.onClose}\n />\n <div className=\"bg-muted h-full grow\">\n <ResizablePanelGroup orientation=\"horizontal\" className=\"h-full\">\n <ResizablePanel defaultSize={showDocs ? '70' : '100'}>\n <ResizablePanelGroup orientation=\"vertical\" className=\"h-full\">\n <ResizablePanel defaultSize=\"50\" className=\"flex flex-row\">\n <ResizablePanelGroup orientation=\"horizontal\">\n <ResizablePanel defaultSize=\"20\">\n <TableStructurePanel schema={schema} />\n </ResizablePanel>\n <ResizableHandle withHandle />\n <ResizablePanel defaultSize=\"80\">\n <QueryEditorPanel />\n </ResizablePanel>\n </ResizablePanelGroup>\n </ResizablePanel>\n <ResizableHandle withHandle />\n <ResizablePanel defaultSize=\"50\">\n <QueryResultPanel\n onRowClick={queryResultProps?.onRowClick}\n onRowDoubleClick={queryResultProps?.onRowDoubleClick}\n renderActions={() => (\n <div className=\"flex gap-2\">\n <Button size=\"xs\" onClick={handleCreateTable}>\n <PlusIcon className=\"h-4 w-4\" />\n New table\n </Button>\n </div>\n )}\n />\n </ResizablePanel>\n </ResizablePanelGroup>\n </ResizablePanel>\n {showDocs && (\n <>\n <ResizableHandle withHandle />\n <ResizablePanel defaultSize=\"30\">\n {documentationPanel}\n </ResizablePanel>\n </>\n )}\n </ResizablePanelGroup>\n </div>\n <CreateTableModal\n query={lastQuery}\n isOpen={createTableModalOpen}\n onClose={() => setCreateTableModalOpen(false)}\n allowMultipleStatements={true}\n showSchemaSelection={true}\n />\n </div>\n );\n});\nSqlEditor.displayName = 'SqlEditor';\n\nexport default SqlEditor;\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"SqlEditorModal.d.ts","sourceRoot":"","sources":["../src/SqlEditorModal.tsx"],"names":[],"mappings":"AAWA,OAAO,KAA8B,MAAM,OAAO,CAAC;AACnD,OAAkB,EAAC,cAAc,EAAC,MAAM,aAAa,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,QAAA,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CA2B5C,CAAC;AAEF,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"SqlEditorModal.d.ts","sourceRoot":"","sources":["../src/SqlEditorModal.tsx"],"names":[],"mappings":"AAWA,OAAO,KAA8B,MAAM,OAAO,CAAC;AACnD,OAAkB,EAAC,cAAc,EAAC,MAAM,aAAa,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,QAAA,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CA8B5C,CAAC;AAEF,eAAe,cAAc,CAAC"}
@@ -39,7 +39,7 @@ const SqlEditorModal = (props) => {
39
39
  if (!open)
40
40
  onClose();
41
41
  }, [onClose]);
42
- return (_jsxs(Dialog, { open: isOpen, onOpenChange: handleOpenChange, children: [_jsx(DialogOverlay, { className: "bg-background/80" }), _jsxs(DialogContent, { className: "h-screen max-h-screen w-screen max-w-[100vw] p-3", children: [_jsxs(DialogHeader, { className: "sr-only", children: [_jsx(DialogTitle, { children: "SQL Editor" }), _jsx(DialogDescription, { children: "SQL editor for querying and managing database tables" })] }), _jsx(Suspense, { fallback: _jsx(SpinnerPane, { h: "100%" }), children: _jsx(SqlEditor, { ...props }) })] })] }));
42
+ return (_jsxs(Dialog, { open: isOpen, onOpenChange: handleOpenChange, children: [_jsx(DialogOverlay, { className: "bg-background/80" }), _jsxs(DialogContent, { className: "h-screen max-h-screen w-screen max-w-[100vw] p-3", showCloseButton: false, children: [_jsxs(DialogHeader, { className: "sr-only", children: [_jsx(DialogTitle, { children: "SQL Editor" }), _jsx(DialogDescription, { children: "SQL editor for querying and managing database tables" })] }), _jsx(Suspense, { fallback: _jsx(SpinnerPane, { h: "100%" }), children: _jsx(SqlEditor, { ...props }) })] })] }));
43
43
  };
44
44
  export default SqlEditorModal;
45
45
  //# sourceMappingURL=SqlEditorModal.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SqlEditorModal.js","sourceRoot":"","sources":["../src/SqlEditorModal.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EACL,MAAM,EACN,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACb,WAAW,EACX,WAAW,GACZ,MAAM,cAAc,CAAC;AACtB,OAAc,EAAC,QAAQ,EAAE,WAAW,EAAC,MAAM,OAAO,CAAC;AACnD,OAAO,SAA2B,MAAM,aAAa,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,cAAc,GAA6B,CAAC,KAAK,EAAE,EAAE;IACzD,MAAM,EAAC,MAAM,EAAE,OAAO,EAAC,GAAG,KAAK,CAAC;IAEhC,4CAA4C;IAC5C,MAAM,gBAAgB,GAAG,WAAW,CAClC,CAAC,IAAa,EAAE,EAAE;QAChB,IAAI,CAAC,IAAI;YAAE,OAAO,EAAE,CAAC;IACvB,CAAC,EACD,CAAC,OAAO,CAAC,CACV,CAAC;IAEF,OAAO,CACL,MAAC,MAAM,IAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,gBAAgB,aAClD,KAAC,aAAa,IAAC,SAAS,EAAC,kBAAkB,GAAG,EAC9C,MAAC,aAAa,IAAC,SAAS,EAAC,kDAAkD,aACzE,MAAC,YAAY,IAAC,SAAS,EAAC,SAAS,aAC/B,KAAC,WAAW,6BAAyB,EACrC,KAAC,iBAAiB,uEAEE,IACP,EACf,KAAC,QAAQ,IAAC,QAAQ,EAAE,KAAC,WAAW,IAAC,CAAC,EAAC,MAAM,GAAG,YAC1C,KAAC,SAAS,OAAK,KAAK,GAAI,GACf,IACG,IACT,CACV,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,cAAc,CAAC","sourcesContent":["'use client';\n\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogOverlay,\n DialogTitle,\n SpinnerPane,\n} from '@sqlrooms/ui';\nimport React, {Suspense, useCallback} from 'react';\nimport SqlEditor, {SqlEditorProps} from './SqlEditor';\n\n/**\n * A modal wrapper for the SQL Editor component that provides a full-screen dialog interface.\n *\n * This component wraps the main SqlEditor component in a modal dialog, making it suitable for\n * overlay/popup usage scenarios. It inherits all props from SqlEditorProps and handles the\n * modal-specific behavior.\n *\n * @example\n * ```tsx\n * <SqlEditorModal\n * isOpen={true}\n * onClose={() => setIsOpen(false)}\n * sqlEditorConfig={config}\n * onChange={handleConfigChange}\n * />\n * ```\n *\n * @see {@link SqlEditor} for detailed documentation of all available props\n *\n * @see {@link SqlEditorProps}\n * The component accepts all props from SqlEditorProps:\n * - `isOpen` - Whether the SQL editor modal is currently visible\n * - `onClose` - Callback fired when the modal should be closed\n * - `sqlEditorConfig` - Configuration object containing queries and selected query state\n * - `onChange` - Callback fired when the SQL editor configuration changes\n * - `schema` - Optional database schema to use for queries (defaults to 'main')\n * - `documentationPanel` - Optional component to render SQL documentation in the side panel\n * - `onAddOrUpdateSqlQuery` - Callback fired when a new table should be created from query results\n */\nconst SqlEditorModal: React.FC<SqlEditorProps> = (props) => {\n const {isOpen, onClose} = props;\n\n // Memoize the handler to prevent re-renders\n const handleOpenChange = useCallback(\n (open: boolean) => {\n if (!open) onClose();\n },\n [onClose],\n );\n\n return (\n <Dialog open={isOpen} onOpenChange={handleOpenChange}>\n <DialogOverlay className=\"bg-background/80\" />\n <DialogContent className=\"h-screen max-h-screen w-screen max-w-[100vw] p-3\">\n <DialogHeader className=\"sr-only\">\n <DialogTitle>SQL Editor</DialogTitle>\n <DialogDescription>\n SQL editor for querying and managing database tables\n </DialogDescription>\n </DialogHeader>\n <Suspense fallback={<SpinnerPane h=\"100%\" />}>\n <SqlEditor {...props} />\n </Suspense>\n </DialogContent>\n </Dialog>\n );\n};\n\nexport default SqlEditorModal;\n"]}
1
+ {"version":3,"file":"SqlEditorModal.js","sourceRoot":"","sources":["../src/SqlEditorModal.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EACL,MAAM,EACN,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACb,WAAW,EACX,WAAW,GACZ,MAAM,cAAc,CAAC;AACtB,OAAc,EAAC,QAAQ,EAAE,WAAW,EAAC,MAAM,OAAO,CAAC;AACnD,OAAO,SAA2B,MAAM,aAAa,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,cAAc,GAA6B,CAAC,KAAK,EAAE,EAAE;IACzD,MAAM,EAAC,MAAM,EAAE,OAAO,EAAC,GAAG,KAAK,CAAC;IAEhC,4CAA4C;IAC5C,MAAM,gBAAgB,GAAG,WAAW,CAClC,CAAC,IAAa,EAAE,EAAE;QAChB,IAAI,CAAC,IAAI;YAAE,OAAO,EAAE,CAAC;IACvB,CAAC,EACD,CAAC,OAAO,CAAC,CACV,CAAC;IAEF,OAAO,CACL,MAAC,MAAM,IAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,gBAAgB,aAClD,KAAC,aAAa,IAAC,SAAS,EAAC,kBAAkB,GAAG,EAC9C,MAAC,aAAa,IACZ,SAAS,EAAC,kDAAkD,EAC5D,eAAe,EAAE,KAAK,aAEtB,MAAC,YAAY,IAAC,SAAS,EAAC,SAAS,aAC/B,KAAC,WAAW,6BAAyB,EACrC,KAAC,iBAAiB,uEAEE,IACP,EACf,KAAC,QAAQ,IAAC,QAAQ,EAAE,KAAC,WAAW,IAAC,CAAC,EAAC,MAAM,GAAG,YAC1C,KAAC,SAAS,OAAK,KAAK,GAAI,GACf,IACG,IACT,CACV,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,cAAc,CAAC","sourcesContent":["'use client';\n\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogOverlay,\n DialogTitle,\n SpinnerPane,\n} from '@sqlrooms/ui';\nimport React, {Suspense, useCallback} from 'react';\nimport SqlEditor, {SqlEditorProps} from './SqlEditor';\n\n/**\n * A modal wrapper for the SQL Editor component that provides a full-screen dialog interface.\n *\n * This component wraps the main SqlEditor component in a modal dialog, making it suitable for\n * overlay/popup usage scenarios. It inherits all props from SqlEditorProps and handles the\n * modal-specific behavior.\n *\n * @example\n * ```tsx\n * <SqlEditorModal\n * isOpen={true}\n * onClose={() => setIsOpen(false)}\n * sqlEditorConfig={config}\n * onChange={handleConfigChange}\n * />\n * ```\n *\n * @see {@link SqlEditor} for detailed documentation of all available props\n *\n * @see {@link SqlEditorProps}\n * The component accepts all props from SqlEditorProps:\n * - `isOpen` - Whether the SQL editor modal is currently visible\n * - `onClose` - Callback fired when the modal should be closed\n * - `sqlEditorConfig` - Configuration object containing queries and selected query state\n * - `onChange` - Callback fired when the SQL editor configuration changes\n * - `schema` - Optional database schema to use for queries (defaults to 'main')\n * - `documentationPanel` - Optional component to render SQL documentation in the side panel\n * - `onAddOrUpdateSqlQuery` - Callback fired when a new table should be created from query results\n */\nconst SqlEditorModal: React.FC<SqlEditorProps> = (props) => {\n const {isOpen, onClose} = props;\n\n // Memoize the handler to prevent re-renders\n const handleOpenChange = useCallback(\n (open: boolean) => {\n if (!open) onClose();\n },\n [onClose],\n );\n\n return (\n <Dialog open={isOpen} onOpenChange={handleOpenChange}>\n <DialogOverlay className=\"bg-background/80\" />\n <DialogContent\n className=\"h-screen max-h-screen w-screen max-w-[100vw] p-3\"\n showCloseButton={false}\n >\n <DialogHeader className=\"sr-only\">\n <DialogTitle>SQL Editor</DialogTitle>\n <DialogDescription>\n SQL editor for querying and managing database tables\n </DialogDescription>\n </DialogHeader>\n <Suspense fallback={<SpinnerPane h=\"100%\" />}>\n <SqlEditor {...props} />\n </Suspense>\n </DialogContent>\n </Dialog>\n );\n};\n\nexport default SqlEditorModal;\n"]}
@@ -1,26 +1,42 @@
1
1
  import { RoomShellSliceState, StateCreator } from '@sqlrooms/room-shell';
2
2
  import { SqlEditorSliceConfig } from '@sqlrooms/sql-editor-config';
3
3
  import * as arrow from 'apache-arrow';
4
+ export type SqlEditorQuery = SqlEditorSliceConfig['queries'][number];
5
+ export type EnsureSqlQueryOptions = {
6
+ name?: string;
7
+ query?: string;
8
+ open?: boolean;
9
+ select?: boolean;
10
+ };
4
11
  export type QueryResult = {
5
12
  status: 'loading';
6
13
  isBeingAborted?: boolean;
7
14
  controller: AbortController;
15
+ startedAt?: number;
8
16
  } | {
9
17
  status: 'aborted';
18
+ durationMs?: number;
19
+ completedAt?: number;
10
20
  } | {
11
21
  status: 'error';
12
22
  error: string;
23
+ durationMs?: number;
24
+ completedAt?: number;
13
25
  } | {
14
26
  status: 'success';
15
27
  type: 'pragma' | 'explain' | 'select';
16
28
  result: arrow.Table | undefined;
17
29
  query: string;
18
30
  lastQueryStatement: string;
31
+ durationMs?: number;
32
+ completedAt?: number;
19
33
  } | {
20
34
  status: 'success';
21
35
  type: 'exec';
22
36
  query: string;
23
37
  lastQueryStatement: string;
38
+ durationMs?: number;
39
+ completedAt?: number;
24
40
  };
25
41
  export declare function isQueryWithResult(queryResult: QueryResult | undefined): queryResult is QueryResult & {
26
42
  status: 'success';
@@ -48,6 +64,31 @@ export type SqlEditorSliceState = {
48
64
  * Set the config for the sql editor slice.
49
65
  */
50
66
  setConfig(config: SqlEditorSliceConfig): void;
67
+ /**
68
+ * Ensure an id-addressable query exists without requiring it to be an open
69
+ * workbench tab.
70
+ */
71
+ ensureQuery(queryId: string, options?: EnsureSqlQueryOptions): SqlEditorQuery;
72
+ /**
73
+ * Remove an id-addressable query and its runtime result.
74
+ */
75
+ removeQuery(queryId: string): void;
76
+ /**
77
+ * Rename an id-addressable query.
78
+ */
79
+ renameQuery(queryId: string, name: string): void;
80
+ /**
81
+ * Run a query by id, optionally overriding its stored SQL text first.
82
+ */
83
+ runQueryById(queryId: string, query?: string): Promise<void>;
84
+ /**
85
+ * Abort a running query by id.
86
+ */
87
+ abortQueryById(queryId: string): void;
88
+ /**
89
+ * Clear the runtime result for a query id.
90
+ */
91
+ clearQueryResult(queryId: string): void;
51
92
  /**
52
93
  * Run the currently selected query.
53
94
  */
@@ -1 +1 @@
1
- {"version":3,"file":"SqlEditorSlice.d.ts","sourceRoot":"","sources":["../src/SqlEditorSlice.tsx"],"names":[],"mappings":"AAQA,OAAO,EAKL,mBAAmB,EACnB,YAAY,EAGb,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAEL,oBAAoB,EACrB,MAAM,6BAA6B,CAAC;AAErC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAQtC,MAAM,MAAM,WAAW,GACnB;IAAC,MAAM,EAAE,SAAS,CAAC;IAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,eAAe,CAAA;CAAC,GAC1E;IAAC,MAAM,EAAE,SAAS,CAAA;CAAC,GACnB;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAC,GAChC;IACE,MAAM,EAAE,SAAS,CAAC;IAClB,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;IACtC,MAAM,EAAE,KAAK,CAAC,KAAK,GAAG,SAAS,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,kBAAkB,EAAE,MAAM,CAAC;CAC5B,GACD;IACE,MAAM,EAAE,SAAS,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,kBAAkB,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEN,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,WAAW,GAAG,SAAS,GACnC,WAAW,IAAI,WAAW,GAAG;IAC9B,MAAM,EAAE,SAAS,CAAC;IAClB,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;CACvC,CAOA;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,EAAE;QACT,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;QACjC,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;QAC9B,MAAM,EAAE,oBAAoB,CAAC;QAE7B;;WAEG;QACH,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,CAAC,CAAC;QAC1D,mBAAmB;QACnB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,6FAA6F;QAC7F,eAAe,EAAE,OAAO,CAAC;QACzB,kBAAkB;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;QAErB,gBAAgB,EAAE,MAAM,CAAC;QACzB,4CAA4C;QAC5C,uBAAuB,EAAE,MAAM,EAAE,CAAC;QAElC;;WAEG;QACH,SAAS,CAAC,MAAM,EAAE,oBAAoB,GAAG,IAAI,CAAC;QAE9C;;WAEG;QACH,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAE/C;;WAEG;QACH,uBAAuB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;QAEzC;;WAEG;QACH,iBAAiB,IAAI,IAAI,CAAC;QAE1B;;;WAGG;QACH,kBAAkB,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAElE;;;WAGG;QACH,cAAc,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG;YACrC,EAAE,EAAE,MAAM,CAAC;YACX,IAAI,EAAE,MAAM,CAAC;YACb,KAAK,EAAE,MAAM,CAAC;SACf,CAAC;QAEF;;;WAGG;QACH,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QAEtC;;;;WAIG;QACH,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QAEvD;;;WAGG;QACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QAErC;;;WAGG;QACH,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QAEpC;;;WAGG;QACH,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;QAEpC;;;;WAIG;QACH,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QAE1D;;;WAGG;QACH,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QAE1C;;WAEG;QACH,eAAe,IAAI,MAAM,CAAC;QAE1B,kBAAkB;QAClB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;QAE7C,iBAAiB,IAAI,IAAI,CAAC;QAE1B,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;KAC1C,CAAC;CACH,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,EACnC,MAAuC,EACvC,gBAAsB,EACtB,uBAA0C,GAC3C,GAAE;IACD,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,uBAAuB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B,GAAG,YAAY,CAAC,mBAAmB,CAAC,CAsazC;AAED,KAAK,sBAAsB,GAAG,mBAAmB,GAAG,mBAAmB,CAAC;AA2RxE,wBAAgB,qBAAqB,CAAC,CAAC,EACrC,QAAQ,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,CAAC,GAC7C,CAAC,CAIH"}
1
+ {"version":3,"file":"SqlEditorSlice.d.ts","sourceRoot":"","sources":["../src/SqlEditorSlice.tsx"],"names":[],"mappings":"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"}
@@ -35,6 +35,74 @@ export function createSqlEditorSlice({ config = createDefaultSqlEditorConfig(),
35
35
  draft.sqlEditor.config = config;
36
36
  }));
37
37
  },
38
+ ensureQuery: (queryId, options = {}) => {
39
+ const now = Date.now();
40
+ const existingQuery = get().sqlEditor.config.queries.find((query) => query.id === queryId);
41
+ const shouldOpen = Boolean(options.open || options.select);
42
+ const nextQuery = existingQuery
43
+ ? {
44
+ ...existingQuery,
45
+ ...(options.name !== undefined ? { name: options.name } : {}),
46
+ ...(options.query !== undefined ? { query: options.query } : {}),
47
+ ...(options.select ? { lastOpenedAt: now } : {}),
48
+ }
49
+ : {
50
+ id: queryId,
51
+ name: options.name ?? 'SQL Query',
52
+ query: options.query ?? '',
53
+ ...(options.select ? { lastOpenedAt: now } : {}),
54
+ };
55
+ set((state) => produce(state, (draft) => {
56
+ const config = draft.sqlEditor.config;
57
+ const index = config.queries.findIndex((query) => query.id === queryId);
58
+ if (index >= 0) {
59
+ config.queries[index] = nextQuery;
60
+ }
61
+ else {
62
+ config.queries.push(nextQuery);
63
+ }
64
+ if (shouldOpen && !config.openTabs.includes(queryId)) {
65
+ config.openTabs.push(queryId);
66
+ }
67
+ if (options.select) {
68
+ config.selectedQueryId = queryId;
69
+ }
70
+ }));
71
+ return nextQuery;
72
+ },
73
+ removeQuery: (queryId) => {
74
+ const currentResult = get().sqlEditor.queryResultsById[queryId];
75
+ if (currentResult?.status === 'loading') {
76
+ currentResult.controller.abort();
77
+ }
78
+ set((state) => produce(state, (draft) => {
79
+ const config = draft.sqlEditor.config;
80
+ const wasSelected = config.selectedQueryId === queryId;
81
+ config.queries = config.queries.filter((query) => query.id !== queryId);
82
+ config.openTabs = config.openTabs.filter((id) => id !== queryId);
83
+ delete draft.sqlEditor.queryResultsById[queryId];
84
+ if (wasSelected) {
85
+ const nextSelectedId = config.openTabs[0] ?? config.queries[0]?.id;
86
+ if (nextSelectedId) {
87
+ config.selectedQueryId = nextSelectedId;
88
+ if (!config.openTabs.includes(nextSelectedId)) {
89
+ config.openTabs.push(nextSelectedId);
90
+ }
91
+ }
92
+ else {
93
+ config.selectedQueryId = '';
94
+ }
95
+ }
96
+ }));
97
+ },
98
+ renameQuery: (queryId, name) => {
99
+ set((state) => produce(state, (draft) => {
100
+ const query = draft.sqlEditor.config.queries.find((candidate) => candidate.id === queryId);
101
+ if (query) {
102
+ query.name = name || query.name;
103
+ }
104
+ }));
105
+ },
38
106
  exportResultsToCsv: (results, filename) => {
39
107
  if (!results)
40
108
  return;
@@ -60,34 +128,29 @@ export function createSqlEditorSlice({ config = createDefaultSqlEditorConfig(),
60
128
  return newQuery;
61
129
  },
62
130
  deleteQueryTab: (queryId) => {
63
- const sqlEditorConfig = get().sqlEditor.config;
64
- const queries = sqlEditorConfig.queries;
65
- const openTabs = sqlEditorConfig.openTabs;
66
- if (queries.length <= 1) {
67
- // Don't delete the last query
68
- return;
69
- }
70
- const wasSelected = sqlEditorConfig.selectedQueryId === queryId;
71
- const deletingOpenIndex = openTabs.indexOf(queryId);
72
- const filteredQueries = queries.filter((q) => q.id !== queryId);
73
131
  set((state) => produce(state, (draft) => {
74
- draft.sqlEditor.config.queries = filteredQueries;
75
- draft.sqlEditor.config.openTabs = openTabs.filter((id) => id !== queryId);
76
- const { [queryId]: _removed, ...rest } = draft.sqlEditor.queryResultsById;
77
- draft.sqlEditor.queryResultsById = rest;
132
+ const config = draft.sqlEditor.config;
133
+ if (config.queries.length <= 1) {
134
+ // Don't delete the last query
135
+ return;
136
+ }
137
+ const wasSelected = config.selectedQueryId === queryId;
138
+ const deletingOpenIndex = config.openTabs.indexOf(queryId);
139
+ config.queries = config.queries.filter((q) => q.id !== queryId);
140
+ config.openTabs = config.openTabs.filter((id) => id !== queryId);
141
+ delete draft.sqlEditor.queryResultsById[queryId];
78
142
  // If we deleted the selected query, select another one
79
143
  if (wasSelected) {
80
- const newOpenTabs = draft.sqlEditor.config.openTabs;
81
- const remainingQueries = draft.sqlEditor.config.queries;
144
+ const newOpenTabs = config.openTabs;
145
+ const remainingQueries = config.queries;
82
146
  if (newOpenTabs.length > 0) {
83
147
  // Select from remaining open tabs
84
- const newIndex = deletingOpenIndex === 0
85
- ? 0
86
- : Math.min(deletingOpenIndex - 1, newOpenTabs.length - 1);
87
- const newSelectedId = newOpenTabs[newIndex];
148
+ const baseIndex = deletingOpenIndex <= 0 ? 0 : deletingOpenIndex - 1;
149
+ const newIndex = Math.min(baseIndex, newOpenTabs.length - 1);
150
+ const newSelectedId = newOpenTabs[newIndex] ?? remainingQueries[0]?.id;
88
151
  if (newSelectedId) {
89
- draft.sqlEditor.config.selectedQueryId = newSelectedId;
90
- const newSelectedQuery = draft.sqlEditor.config.queries.find((q) => q.id === newSelectedId);
152
+ config.selectedQueryId = newSelectedId;
153
+ const newSelectedQuery = config.queries.find((q) => q.id === newSelectedId);
91
154
  if (newSelectedQuery) {
92
155
  newSelectedQuery.lastOpenedAt = Date.now();
93
156
  }
@@ -97,8 +160,8 @@ export function createSqlEditorSlice({ config = createDefaultSqlEditorConfig(),
97
160
  // No open tabs left, open a closed query
98
161
  const queryToOpen = remainingQueries[0];
99
162
  if (queryToOpen) {
100
- draft.sqlEditor.config.openTabs.push(queryToOpen.id);
101
- draft.sqlEditor.config.selectedQueryId = queryToOpen.id;
163
+ config.openTabs.push(queryToOpen.id);
164
+ config.selectedQueryId = queryToOpen.id;
102
165
  queryToOpen.lastOpenedAt = Date.now();
103
166
  }
104
167
  }
@@ -106,12 +169,7 @@ export function createSqlEditorSlice({ config = createDefaultSqlEditorConfig(),
106
169
  }));
107
170
  },
108
171
  renameQueryTab: (queryId, newName) => {
109
- set((state) => produce(state, (draft) => {
110
- const query = draft.sqlEditor.config.queries.find((q) => q.id === queryId);
111
- if (query) {
112
- query.name = newName || query.name;
113
- }
114
- }));
172
+ get().sqlEditor.renameQuery(queryId, newName);
115
173
  },
116
174
  closeQueryTab: (queryId) => {
117
175
  set((state) => produce(state, (draft) => {
@@ -170,43 +228,59 @@ export function createSqlEditorSlice({ config = createDefaultSqlEditorConfig(),
170
228
  draft.sqlEditor.queryResultsById = {};
171
229
  }));
172
230
  },
173
- parseAndRunCurrentQuery: async () => get().sqlEditor.parseAndRunQuery(get().sqlEditor.getCurrentQuery()),
231
+ parseAndRunCurrentQuery: async () => get().sqlEditor.runQueryById(get().sqlEditor.config.selectedQueryId),
174
232
  abortCurrentQuery: () => {
175
- const selectedQueryId = get().sqlEditor.config.selectedQueryId;
176
- const currentResult = get().sqlEditor.queryResultsById[selectedQueryId];
233
+ get().sqlEditor.abortQueryById(get().sqlEditor.config.selectedQueryId);
234
+ },
235
+ setQueryResultLimit: (limit) => {
236
+ set((state) => produce(state, (draft) => {
237
+ draft.sqlEditor.queryResultLimit = limit;
238
+ }));
239
+ },
240
+ abortQueryById: (queryId) => {
241
+ const currentResult = get().sqlEditor.queryResultsById[queryId];
177
242
  if (currentResult?.status === 'loading' && currentResult.controller) {
178
243
  currentResult.controller.abort();
179
244
  }
180
245
  set((state) => produce(state, (draft) => {
181
- const result = draft.sqlEditor.queryResultsById[selectedQueryId];
246
+ const result = draft.sqlEditor.queryResultsById[queryId];
182
247
  if (result?.status === 'loading') {
183
248
  result.isBeingAborted = true;
184
249
  }
185
250
  }));
186
251
  },
187
- setQueryResultLimit: (limit) => {
252
+ clearQueryResult: (queryId) => {
253
+ const currentResult = get().sqlEditor.queryResultsById[queryId];
254
+ if (currentResult?.status === 'loading') {
255
+ currentResult.controller.abort();
256
+ }
188
257
  set((state) => produce(state, (draft) => {
189
- draft.sqlEditor.queryResultLimit = limit;
258
+ delete draft.sqlEditor.queryResultsById[queryId];
190
259
  }));
191
260
  },
192
- parseAndRunQuery: async (query) => {
193
- const selectedQueryId = get().sqlEditor.config.selectedQueryId;
194
- const existingResult = get().sqlEditor.queryResultsById[selectedQueryId];
261
+ parseAndRunQuery: async (query) => get().sqlEditor.runQueryById(get().sqlEditor.config.selectedQueryId, query),
262
+ runQueryById: async (queryId, queryOverride) => {
263
+ const storedQuery = get().sqlEditor.config.queries.find((query) => query.id === queryId);
264
+ const query = queryOverride ?? storedQuery?.query ?? '';
265
+ const existingResult = get().sqlEditor.queryResultsById[queryId];
195
266
  if (existingResult?.status === 'loading') {
196
267
  throw new Error('Query already running');
197
268
  }
198
269
  if (!query.trim()) {
199
270
  return;
200
271
  }
272
+ get().sqlEditor.ensureQuery(queryId, { query });
201
273
  // Create abort controller for this query execution
202
274
  const queryController = new AbortController();
275
+ const startedAt = Date.now();
203
276
  // First update loading state and clear results
204
277
  set((state) => produce(state, (draft) => {
205
278
  draft.sqlEditor.selectedTable = undefined;
206
- draft.sqlEditor.queryResultsById[selectedQueryId] = {
279
+ draft.sqlEditor.queryResultsById[queryId] = {
207
280
  status: 'loading',
208
281
  isBeingAborted: false,
209
282
  controller: queryController,
283
+ startedAt,
210
284
  };
211
285
  }));
212
286
  let queryResult;
@@ -244,6 +318,7 @@ export function createSqlEditorSlice({ config = createDefaultSqlEditorConfig(),
244
318
  query,
245
319
  lastQueryStatement,
246
320
  result,
321
+ durationMs: Date.now() - startedAt,
247
322
  };
248
323
  }
249
324
  else {
@@ -272,6 +347,7 @@ export function createSqlEditorSlice({ config = createDefaultSqlEditorConfig(),
272
347
  query,
273
348
  lastQueryStatement,
274
349
  result,
350
+ durationMs: Date.now() - startedAt,
275
351
  };
276
352
  }
277
353
  else if (/^(PRAGMA)/i.test(lastQueryStatement)) {
@@ -281,6 +357,7 @@ export function createSqlEditorSlice({ config = createDefaultSqlEditorConfig(),
281
357
  query,
282
358
  lastQueryStatement,
283
359
  result,
360
+ durationMs: Date.now() - startedAt,
284
361
  };
285
362
  }
286
363
  else {
@@ -289,6 +366,7 @@ export function createSqlEditorSlice({ config = createDefaultSqlEditorConfig(),
289
366
  type: 'exec',
290
367
  query,
291
368
  lastQueryStatement,
369
+ durationMs: Date.now() - startedAt,
292
370
  };
293
371
  }
294
372
  }
@@ -309,26 +387,40 @@ export function createSqlEditorSlice({ config = createDefaultSqlEditorConfig(),
309
387
  const errorMessage = e instanceof Error ? e.message : String(e);
310
388
  if (errorMessage === 'Query aborted' ||
311
389
  queryController.signal.aborted) {
312
- queryResult = { status: 'aborted' };
390
+ queryResult = {
391
+ status: 'aborted',
392
+ durationMs: Date.now() - startedAt,
393
+ };
313
394
  }
314
395
  else {
315
396
  queryResult = {
316
397
  status: 'error',
317
398
  error: errorMessage,
399
+ durationMs: Date.now() - startedAt,
318
400
  };
319
401
  }
320
402
  }
403
+ queryResult = { ...queryResult, completedAt: Date.now() };
321
404
  // Update state without Immer since Arrow Tables don't play well with drafts.
322
- set((state) => ({
323
- ...state,
324
- sqlEditor: {
325
- ...state.sqlEditor,
326
- queryResultsById: {
327
- ...state.sqlEditor.queryResultsById,
328
- [selectedQueryId]: queryResult,
405
+ set((state) => {
406
+ const currentResult = state.sqlEditor.queryResultsById[queryId];
407
+ const queryStillExists = state.sqlEditor.config.queries.some((candidate) => candidate.id === queryId);
408
+ if (!queryStillExists ||
409
+ currentResult?.status !== 'loading' ||
410
+ currentResult.controller !== queryController) {
411
+ return state;
412
+ }
413
+ return {
414
+ ...state,
415
+ sqlEditor: {
416
+ ...state.sqlEditor,
417
+ queryResultsById: {
418
+ ...state.sqlEditor.queryResultsById,
419
+ [queryId]: queryResult,
420
+ },
329
421
  },
330
- },
331
- }));
422
+ };
423
+ });
332
424
  },
333
425
  },
334
426
  };