@sqlrooms/sql-editor 0.6.0 → 0.8.0

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 (58) hide show
  1. package/README.md +409 -0
  2. package/dist/CreateTableModal.d.ts.map +1 -1
  3. package/dist/CreateTableModal.js +10 -4
  4. package/dist/CreateTableModal.js.map +1 -1
  5. package/dist/SqlEditor.d.ts +1 -14
  6. package/dist/SqlEditor.d.ts.map +1 -1
  7. package/dist/SqlEditor.js +111 -183
  8. package/dist/SqlEditor.js.map +1 -1
  9. package/dist/SqlEditorModal.d.ts.map +1 -1
  10. package/dist/SqlEditorModal.js +8 -4
  11. package/dist/SqlEditorModal.js.map +1 -1
  12. package/dist/SqlEditorSlice.d.ts +102 -5
  13. package/dist/SqlEditorSlice.d.ts.map +1 -1
  14. package/dist/SqlEditorSlice.js +107 -30
  15. package/dist/SqlEditorSlice.js.map +1 -1
  16. package/dist/SqlMonacoEditor.d.ts +30 -0
  17. package/dist/SqlMonacoEditor.d.ts.map +1 -0
  18. package/dist/SqlMonacoEditor.js +205 -0
  19. package/dist/SqlMonacoEditor.js.map +1 -0
  20. package/dist/SqlQueryDataSourcesPanel.js +2 -3
  21. package/dist/SqlQueryDataSourcesPanel.js.map +1 -1
  22. package/dist/components/internal/SqlMonacoEditor.d.ts +36 -0
  23. package/dist/components/internal/SqlMonacoEditor.d.ts.map +1 -0
  24. package/dist/components/internal/SqlMonacoEditor.js +219 -0
  25. package/dist/components/internal/SqlMonacoEditor.js.map +1 -0
  26. package/dist/constants/duckdb-dialect.d.ts +73 -0
  27. package/dist/constants/duckdb-dialect.d.ts.map +1 -0
  28. package/dist/constants/duckdb-dialect.js +392 -0
  29. package/dist/constants/duckdb-dialect.js.map +1 -0
  30. package/dist/constants/duckdb.d.ts +73 -0
  31. package/dist/constants/duckdb.d.ts.map +1 -0
  32. package/dist/constants/duckdb.js +392 -0
  33. package/dist/constants/duckdb.js.map +1 -0
  34. package/dist/hooks/index.d.ts +5 -0
  35. package/dist/hooks/index.d.ts.map +1 -0
  36. package/dist/hooks/index.js +5 -0
  37. package/dist/hooks/index.js.map +1 -0
  38. package/dist/hooks/useMonacoEditor.d.ts +13 -0
  39. package/dist/hooks/useMonacoEditor.d.ts.map +1 -0
  40. package/dist/hooks/useMonacoEditor.js +78 -0
  41. package/dist/hooks/useMonacoEditor.js.map +1 -0
  42. package/dist/hooks/useQueryExecution.d.ts +17 -0
  43. package/dist/hooks/useQueryExecution.d.ts.map +1 -0
  44. package/dist/hooks/useQueryExecution.js +61 -0
  45. package/dist/hooks/useQueryExecution.js.map +1 -0
  46. package/dist/hooks/useQueryTabManagement.d.ts +41 -0
  47. package/dist/hooks/useQueryTabManagement.d.ts.map +1 -0
  48. package/dist/hooks/useQueryTabManagement.js +95 -0
  49. package/dist/hooks/useQueryTabManagement.js.map +1 -0
  50. package/dist/hooks/useTableManagement.d.ts +14 -0
  51. package/dist/hooks/useTableManagement.d.ts.map +1 -0
  52. package/dist/hooks/useTableManagement.js +46 -0
  53. package/dist/hooks/useTableManagement.js.map +1 -0
  54. package/dist/index.d.ts +6 -2
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +6 -2
  57. package/dist/index.js.map +1 -1
  58. package/package.json +12 -9
@@ -1,15 +1,112 @@
1
1
  import { ProjectState, StateCreator } from '@sqlrooms/project-builder';
2
2
  import { BaseProjectConfig } from '@sqlrooms/project-config';
3
- import { SqlEditorSliceConfig } from './SqlEditorSliceConfig';
3
+ import { Table } from 'apache-arrow';
4
+ import { z } from 'zod';
5
+ export declare const SqlEditorSliceConfig: z.ZodObject<{
6
+ sqlEditor: z.ZodObject<{
7
+ queries: z.ZodArray<z.ZodObject<{
8
+ id: z.ZodString;
9
+ name: z.ZodString;
10
+ query: z.ZodString;
11
+ }, "strip", z.ZodTypeAny, {
12
+ query: string;
13
+ name: string;
14
+ id: string;
15
+ }, {
16
+ query: string;
17
+ name: string;
18
+ id: string;
19
+ }>, "many">;
20
+ selectedQueryId: z.ZodDefault<z.ZodString>;
21
+ }, "strip", z.ZodTypeAny, {
22
+ queries: {
23
+ query: string;
24
+ name: string;
25
+ id: string;
26
+ }[];
27
+ selectedQueryId: string;
28
+ }, {
29
+ queries: {
30
+ query: string;
31
+ name: string;
32
+ id: string;
33
+ }[];
34
+ selectedQueryId?: string | undefined;
35
+ }>;
36
+ }, "strip", z.ZodTypeAny, {
37
+ sqlEditor: {
38
+ queries: {
39
+ query: string;
40
+ name: string;
41
+ id: string;
42
+ }[];
43
+ selectedQueryId: string;
44
+ };
45
+ }, {
46
+ sqlEditor: {
47
+ queries: {
48
+ query: string;
49
+ name: string;
50
+ id: string;
51
+ }[];
52
+ selectedQueryId?: string | undefined;
53
+ };
54
+ }>;
55
+ export type SqlEditorSliceConfig = z.infer<typeof SqlEditorSliceConfig>;
4
56
  export declare function createDefaultSqlEditorConfig(): SqlEditorSliceConfig;
5
57
  export type SqlEditorSliceState = {
6
58
  sqlEditor: {
7
- setSqlEditorConfig: (config: SqlEditorSliceConfig['sqlEditor']) => void;
8
59
  /**
9
- * Pin a panel.
10
- * @param panel - The panel to pin.
60
+ * Execute a SQL query and return the results.
61
+ * @param query - The SQL query to execute.
62
+ * @param schema - The schema to use (default: main).
63
+ */
64
+ executeQuery(query: string, schema?: string): Promise<{
65
+ results?: Table;
66
+ error?: string;
67
+ }>;
68
+ /**
69
+ * Export query results to CSV.
70
+ * @param results - The query results to export.
71
+ * @param filename - Optional filename (default is generated).
72
+ */
73
+ exportResultsToCsv(results: Table, filename?: string): void;
74
+ /**
75
+ * Create a new query tab.
76
+ * @param initialQuery - Optional initial query text.
77
+ */
78
+ createQueryTab(initialQuery?: string): {
79
+ id: string;
80
+ name: string;
81
+ query: string;
82
+ };
83
+ /**
84
+ * Delete a query tab.
85
+ * @param queryId - The ID of the query to delete.
86
+ */
87
+ deleteQueryTab(queryId: string): void;
88
+ /**
89
+ * Rename a query tab.
90
+ * @param queryId - The ID of the query to rename.
91
+ * @param newName - The new name for the query.
92
+ */
93
+ renameQueryTab(queryId: string, newName: string): void;
94
+ /**
95
+ * Update the SQL text for a query.
96
+ * @param queryId - The ID of the query to update.
97
+ * @param queryText - The new SQL text.
98
+ */
99
+ updateQueryText(queryId: string, queryText: string): void;
100
+ /**
101
+ * Set the selected query tab.
102
+ * @param queryId - The ID of the query to select.
103
+ */
104
+ setSelectedQueryId(queryId: string): void;
105
+ /**
106
+ * Get the currently selected query's SQL text.
107
+ * @param defaultQuery - Optional default query text to return if no query is found.
11
108
  */
12
- addOrUpdateSqlQuery(tableName: string, query: string, oldTableName?: string): Promise<void>;
109
+ getCurrentQuery(defaultQuery?: string): string;
13
110
  };
14
111
  };
15
112
  export declare function createSqlEditorSlice<PC extends BaseProjectConfig & SqlEditorSliceConfig>(): StateCreator<SqlEditorSliceState>;
@@ -1 +1 @@
1
- {"version":3,"file":"SqlEditorSlice.d.ts","sourceRoot":"","sources":["../src/SqlEditorSlice.tsx"],"names":[],"mappings":"AAKA,OAAO,EAGL,YAAY,EACZ,YAAY,EAEb,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAC,iBAAiB,EAAkB,MAAM,0BAA0B,CAAC;AAG5E,OAAO,EAAC,oBAAoB,EAAC,MAAM,wBAAwB,CAAC;AAE5D,wBAAgB,4BAA4B,IAAI,oBAAoB,CAOnE;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,EAAE;QACT,kBAAkB,EAAE,CAAC,MAAM,EAAE,oBAAoB,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;QACxE;;;WAGG;QACH,mBAAmB,CACjB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC,CAAC;KAClB,CAAC;CACH,CAAC;AAEF,wBAAgB,oBAAoB,CAClC,EAAE,SAAS,iBAAiB,GAAG,oBAAoB,KAChD,YAAY,CAAC,mBAAmB,CAAC,CA8CrC;AAED,KAAK,0BAA0B,GAAG,iBAAiB,GAAG,oBAAoB,CAAC;AAC3E,KAAK,yBAAyB,GAAG,YAAY,CAAC,0BAA0B,CAAC,GACvE,mBAAmB,CAAC;AAEtB,wBAAgB,qBAAqB,CAAC,CAAC,EACrC,QAAQ,EAAE,CAAC,KAAK,EAAE,yBAAyB,KAAK,CAAC,GAChD,CAAC,CAMH"}
1
+ {"version":3,"file":"SqlEditorSlice.d.ts","sourceRoot":"","sources":["../src/SqlEditorSlice.tsx"],"names":[],"mappings":"AACA,OAAO,EAEL,YAAY,EACZ,YAAY,EAEb,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAC,iBAAiB,EAAC,MAAM,0BAA0B,CAAC;AAE3D,OAAO,EAAC,KAAK,EAAC,MAAM,cAAc,CAAC;AAInC,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAc/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE,wBAAgB,4BAA4B,IAAI,oBAAoB,CAOnE;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,EAAE;QACT;;;;WAIG;QACH,YAAY,CACV,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC;YACT,OAAO,CAAC,EAAE,KAAK,CAAC;YAChB,KAAK,CAAC,EAAE,MAAM,CAAC;SAChB,CAAC,CAAC;QAEH;;;;WAIG;QACH,kBAAkB,CAAC,OAAO,EAAE,KAAK,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAE5D;;;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;;;;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;;;WAGG;QACH,eAAe,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;KAChD,CAAC;CACH,CAAC;AAEF,wBAAgB,oBAAoB,CAClC,EAAE,SAAS,iBAAiB,GAAG,oBAAoB,KAChD,YAAY,CAAC,mBAAmB,CAAC,CAuIrC;AAED,KAAK,0BAA0B,GAAG,iBAAiB,GAAG,oBAAoB,CAAC;AAC3E,KAAK,yBAAyB,GAAG,YAAY,CAAC,0BAA0B,CAAC,GACvE,mBAAmB,CAAC;AAEtB,wBAAgB,qBAAqB,CAAC,CAAC,EACrC,QAAQ,EAAE,CAAC,KAAK,EAAE,yBAAyB,KAAK,CAAC,GAChD,CAAC,CAMH"}
@@ -1,8 +1,23 @@
1
- import { createTableFromQuery, getDuckTables, getDuckTableSchemas, } from '@sqlrooms/duckdb';
2
- import { createSlice, DataSourceStatus, useBaseProjectStore, } from '@sqlrooms/project-builder';
3
- import { DataSourceTypes } from '@sqlrooms/project-config';
4
- import { generateUniqueName } from '@sqlrooms/utils';
1
+ import { DuckQueryError, getDuckDb } from '@sqlrooms/duckdb';
2
+ import { createSlice, useBaseProjectStore, } from '@sqlrooms/project-builder';
3
+ import { generateUniqueName, genRandomStr } from '@sqlrooms/utils';
4
+ import { csvFormat } from 'd3-dsv';
5
+ import { saveAs } from 'file-saver';
5
6
  import { produce } from 'immer';
7
+ import { z } from 'zod';
8
+ export const SqlEditorSliceConfig = z.object({
9
+ sqlEditor: z.object({
10
+ queries: z.array(z.object({
11
+ id: z.string().describe('Query identifier.'),
12
+ name: z.string().describe('Query name.'),
13
+ query: z.string().describe('SQL query to execute.'),
14
+ })),
15
+ selectedQueryId: z
16
+ .string()
17
+ .default('default')
18
+ .describe('The id of the currently selected query.'),
19
+ }),
20
+ });
6
21
  export function createDefaultSqlEditorConfig() {
7
22
  return {
8
23
  sqlEditor: {
@@ -14,39 +29,101 @@ export function createDefaultSqlEditorConfig() {
14
29
  export function createSqlEditorSlice() {
15
30
  return createSlice((set, get) => ({
16
31
  sqlEditor: {
17
- setSqlEditorConfig: (config) => {
32
+ executeQuery: async (query, schema = 'main') => {
33
+ const duckDb = await getDuckDb();
34
+ if (!duckDb.conn) {
35
+ return { error: 'No DuckDB connection available' };
36
+ }
37
+ try {
38
+ await duckDb.conn.query(`SET search_path = ${schema}`);
39
+ const results = await duckDb.conn.query(query);
40
+ await duckDb.conn.query(`SET search_path = main`);
41
+ // Refresh table schemas after query execution
42
+ await get().project.refreshTableSchemas();
43
+ return { results };
44
+ }
45
+ catch (e) {
46
+ console.error(e);
47
+ const errorMessage = e instanceof DuckQueryError
48
+ ? e.getMessageForUser()
49
+ : 'Query failed';
50
+ return { error: errorMessage || String(e) };
51
+ }
52
+ },
53
+ exportResultsToCsv: (results, filename) => {
54
+ if (!results)
55
+ return;
56
+ const blob = new Blob([csvFormat(results.toArray())], {
57
+ type: 'text/plain;charset=utf-8',
58
+ });
59
+ saveAs(blob, filename || `export-${genRandomStr(5)}.csv`);
60
+ },
61
+ createQueryTab: (initialQuery = '') => {
62
+ const sqlEditorConfig = get().config.sqlEditor;
63
+ const newQuery = {
64
+ id: genRandomStr(8),
65
+ name: generateUniqueName('Untitled', sqlEditorConfig.queries.map((q) => q.name)),
66
+ query: initialQuery,
67
+ };
18
68
  set((state) => produce(state, (draft) => {
19
- draft.project.config.sqlEditor = config;
69
+ draft.config.sqlEditor.queries.push(newQuery);
70
+ draft.config.sqlEditor.selectedQueryId = newQuery.id;
20
71
  }));
72
+ return newQuery;
21
73
  },
22
- addOrUpdateSqlQuery: async (tableName, query, oldTableName) => {
23
- const { schema } = get().project;
24
- const newTableName = tableName !== oldTableName
25
- ? generateUniqueName(tableName, await getDuckTables(schema))
26
- : tableName;
27
- const { rowCount } = await createTableFromQuery(newTableName, query);
28
- get().project.setTableRowCount(newTableName, rowCount);
74
+ deleteQueryTab: (queryId) => {
75
+ const sqlEditorConfig = get().config.sqlEditor;
76
+ const queries = sqlEditorConfig.queries;
77
+ if (queries.length <= 1) {
78
+ // Don't delete the last query
79
+ return;
80
+ }
81
+ const index = queries.findIndex((q) => q.id === queryId);
82
+ if (index === -1)
83
+ return;
84
+ const isSelected = sqlEditorConfig.selectedQueryId === queryId;
85
+ const filteredQueries = queries.filter((q) => q.id !== queryId);
29
86
  set((state) => produce(state, (draft) => {
30
- const newDataSource = {
31
- type: DataSourceTypes.enum.sql,
32
- sqlQuery: query,
33
- tableName: newTableName,
34
- };
35
- if (oldTableName) {
36
- draft.project.config.dataSources =
37
- draft.project.config.dataSources.map((dataSource) => dataSource.tableName === oldTableName
38
- ? newDataSource
39
- : dataSource);
40
- delete draft.project.dataSourceStates[oldTableName];
87
+ draft.config.sqlEditor.queries = filteredQueries;
88
+ // If we're deleting the selected tab, select the previous one or the first one
89
+ if (isSelected && filteredQueries.length > 0) {
90
+ const newSelectedIndex = Math.max(0, index - 1);
91
+ // Safely access the ID with fallback to the first query if needed
92
+ const newSelectedId = filteredQueries[newSelectedIndex]?.id ?? filteredQueries[0]?.id;
93
+ if (newSelectedId) {
94
+ draft.config.sqlEditor.selectedQueryId = newSelectedId;
95
+ }
41
96
  }
42
- else {
43
- draft.project.config.dataSources.push(newDataSource);
97
+ }));
98
+ },
99
+ renameQueryTab: (queryId, newName) => {
100
+ set((state) => produce(state, (draft) => {
101
+ const query = draft.config.sqlEditor.queries.find((q) => q.id === queryId);
102
+ if (query) {
103
+ query.name = newName || query.name;
44
104
  }
45
- draft.project.dataSourceStates[newTableName] = {
46
- status: DataSourceStatus.READY,
47
- };
48
105
  }));
49
- await get().project.setTables(await getDuckTableSchemas());
106
+ },
107
+ updateQueryText: (queryId, queryText) => {
108
+ set((state) => produce(state, (draft) => {
109
+ const query = draft.config.sqlEditor.queries.find((q) => q.id === queryId);
110
+ if (query) {
111
+ query.query = queryText;
112
+ }
113
+ }));
114
+ },
115
+ setSelectedQueryId: (queryId) => {
116
+ set((state) => produce(state, (draft) => {
117
+ draft.config.sqlEditor.selectedQueryId = queryId;
118
+ }));
119
+ },
120
+ getCurrentQuery: (defaultQuery = '') => {
121
+ const sqlEditorConfig = get().config.sqlEditor;
122
+ const selectedId = sqlEditorConfig.selectedQueryId;
123
+ // Find query by ID
124
+ const query = sqlEditorConfig.queries.find((q) => q.id === selectedId);
125
+ // If found, return its query text, otherwise default
126
+ return query?.query || defaultQuery;
50
127
  },
51
128
  },
52
129
  }));
@@ -1 +1 @@
1
- {"version":3,"file":"SqlEditorSlice.js","sourceRoot":"","sources":["../src/SqlEditorSlice.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n createTableFromQuery,\n getDuckTables,\n getDuckTableSchemas,\n} from '@sqlrooms/duckdb';\nimport {\n createSlice,\n DataSourceStatus,\n ProjectState,\n StateCreator,\n useBaseProjectStore,\n} from '@sqlrooms/project-builder';\nimport {BaseProjectConfig, DataSourceTypes} from '@sqlrooms/project-config';\nimport {generateUniqueName} from '@sqlrooms/utils';\nimport {produce} from 'immer';\nimport {SqlEditorSliceConfig} from './SqlEditorSliceConfig';\n\nexport function createDefaultSqlEditorConfig(): SqlEditorSliceConfig {\n return {\n sqlEditor: {\n queries: [{id: 'default', name: 'Untitled', query: ''}],\n selectedQueryId: 'default',\n },\n };\n}\n\nexport type SqlEditorSliceState = {\n sqlEditor: {\n setSqlEditorConfig: (config: SqlEditorSliceConfig['sqlEditor']) => void;\n /**\n * Pin a panel.\n * @param panel - The panel to pin.\n */\n addOrUpdateSqlQuery(\n tableName: string,\n query: string,\n oldTableName?: string,\n ): Promise<void>;\n };\n};\n\nexport function createSqlEditorSlice<\n PC extends BaseProjectConfig & SqlEditorSliceConfig,\n>(): StateCreator<SqlEditorSliceState> {\n return createSlice<PC, SqlEditorSliceState>((set, get) => ({\n sqlEditor: {\n setSqlEditorConfig: (config: SqlEditorSliceConfig['sqlEditor']) => {\n set((state) =>\n produce(state, (draft) => {\n draft.project.config.sqlEditor = config;\n }),\n );\n },\n\n addOrUpdateSqlQuery: async (tableName, query, oldTableName) => {\n const {schema} = get().project;\n const newTableName =\n tableName !== oldTableName\n ? generateUniqueName(tableName, await getDuckTables(schema))\n : tableName;\n const {rowCount} = await createTableFromQuery(newTableName, query);\n get().project.setTableRowCount(newTableName, rowCount);\n set((state) =>\n produce(state, (draft) => {\n const newDataSource = {\n type: DataSourceTypes.enum.sql,\n sqlQuery: query,\n tableName: newTableName,\n };\n if (oldTableName) {\n draft.project.config.dataSources =\n draft.project.config.dataSources.map((dataSource) =>\n dataSource.tableName === oldTableName\n ? newDataSource\n : dataSource,\n );\n delete draft.project.dataSourceStates[oldTableName];\n } else {\n draft.project.config.dataSources.push(newDataSource);\n }\n draft.project.dataSourceStates[newTableName] = {\n status: DataSourceStatus.READY,\n };\n }),\n );\n await get().project.setTables(await getDuckTableSchemas());\n },\n },\n }));\n}\n\ntype ProjectConfigWithSqlEditor = BaseProjectConfig & SqlEditorSliceConfig;\ntype ProjectStateWithSqlEditor = ProjectState<ProjectConfigWithSqlEditor> &\n SqlEditorSliceState;\n\nexport function useStoreWithSqlEditor<T>(\n selector: (state: ProjectStateWithSqlEditor) => T,\n): T {\n return useBaseProjectStore<\n BaseProjectConfig & SqlEditorSliceConfig,\n ProjectState<ProjectConfigWithSqlEditor>,\n T\n >((state) => selector(state as unknown as ProjectStateWithSqlEditor));\n}\n"]}
1
+ {"version":3,"file":"SqlEditorSlice.js","sourceRoot":"","sources":["../src/SqlEditorSlice.tsx"],"names":[],"mappings":"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","sourcesContent":["import {DuckQueryError, getDuckDb, getDuckTableSchemas} from '@sqlrooms/duckdb';\nimport {\n createSlice,\n ProjectState,\n StateCreator,\n useBaseProjectStore,\n} from '@sqlrooms/project-builder';\nimport {BaseProjectConfig} from '@sqlrooms/project-config';\nimport {generateUniqueName, genRandomStr} from '@sqlrooms/utils';\nimport {Table} from 'apache-arrow';\nimport {csvFormat} from 'd3-dsv';\nimport {saveAs} from 'file-saver';\nimport {produce} from 'immer';\nimport {z} from 'zod';\n\nexport const SqlEditorSliceConfig = z.object({\n sqlEditor: z.object({\n queries: z.array(\n z.object({\n id: z.string().describe('Query identifier.'),\n name: z.string().describe('Query name.'),\n query: z.string().describe('SQL query to execute.'),\n }),\n ),\n selectedQueryId: z\n .string()\n .default('default')\n .describe('The id of the currently selected query.'),\n }),\n});\nexport type SqlEditorSliceConfig = z.infer<typeof SqlEditorSliceConfig>;\n\nexport function createDefaultSqlEditorConfig(): SqlEditorSliceConfig {\n return {\n sqlEditor: {\n queries: [{id: 'default', name: 'Untitled', query: ''}],\n selectedQueryId: 'default',\n },\n };\n}\n\nexport type SqlEditorSliceState = {\n sqlEditor: {\n /**\n * Execute a SQL query and return the results.\n * @param query - The SQL query to execute.\n * @param schema - The schema to use (default: main).\n */\n executeQuery(\n query: string,\n schema?: string,\n ): Promise<{\n results?: Table;\n error?: string;\n }>;\n\n /**\n * Export query results to CSV.\n * @param results - The query results to export.\n * @param filename - Optional filename (default is generated).\n */\n exportResultsToCsv(results: Table, filename?: string): void;\n\n /**\n * Create a new query tab.\n * @param initialQuery - Optional initial query text.\n */\n createQueryTab(initialQuery?: string): {\n id: string;\n name: string;\n query: string;\n };\n\n /**\n * Delete a query tab.\n * @param queryId - The ID of the query to delete.\n */\n deleteQueryTab(queryId: string): void;\n\n /**\n * Rename a query tab.\n * @param queryId - The ID of the query to rename.\n * @param newName - The new name for the query.\n */\n renameQueryTab(queryId: string, newName: string): void;\n\n /**\n * Update the SQL text for a query.\n * @param queryId - The ID of the query to update.\n * @param queryText - The new SQL text.\n */\n updateQueryText(queryId: string, queryText: string): void;\n\n /**\n * Set the selected query tab.\n * @param queryId - The ID of the query to select.\n */\n setSelectedQueryId(queryId: string): void;\n\n /**\n * Get the currently selected query's SQL text.\n * @param defaultQuery - Optional default query text to return if no query is found.\n */\n getCurrentQuery(defaultQuery?: string): string;\n };\n};\n\nexport function createSqlEditorSlice<\n PC extends BaseProjectConfig & SqlEditorSliceConfig,\n>(): StateCreator<SqlEditorSliceState> {\n return createSlice<PC, SqlEditorSliceState>((set, get) => ({\n sqlEditor: {\n executeQuery: async (query, schema = 'main') => {\n const duckDb = await getDuckDb();\n if (!duckDb.conn) {\n return {error: 'No DuckDB connection available'};\n }\n\n try {\n await duckDb.conn.query(`SET search_path = ${schema}`);\n const results = await duckDb.conn.query(query);\n await duckDb.conn.query(`SET search_path = main`);\n\n // Refresh table schemas after query execution\n await get().project.refreshTableSchemas();\n\n return {results};\n } catch (e) {\n console.error(e);\n const errorMessage =\n e instanceof DuckQueryError\n ? e.getMessageForUser()\n : 'Query failed';\n\n return {error: errorMessage || String(e)};\n }\n },\n\n exportResultsToCsv: (results, filename) => {\n if (!results) return;\n const blob = new Blob([csvFormat(results.toArray())], {\n type: 'text/plain;charset=utf-8',\n });\n saveAs(blob, filename || `export-${genRandomStr(5)}.csv`);\n },\n\n createQueryTab: (initialQuery = '') => {\n const sqlEditorConfig = get().config.sqlEditor;\n const newQuery = {\n id: genRandomStr(8),\n name: generateUniqueName(\n 'Untitled',\n sqlEditorConfig.queries.map((q) => q.name),\n ),\n query: initialQuery,\n };\n\n set((state) =>\n produce(state, (draft) => {\n draft.config.sqlEditor.queries.push(newQuery);\n draft.config.sqlEditor.selectedQueryId = newQuery.id;\n }),\n );\n\n return newQuery;\n },\n\n deleteQueryTab: (queryId) => {\n const sqlEditorConfig = get().config.sqlEditor;\n const queries = sqlEditorConfig.queries;\n\n if (queries.length <= 1) {\n // Don't delete the last query\n return;\n }\n\n const index = queries.findIndex((q) => q.id === queryId);\n if (index === -1) return;\n\n const isSelected = sqlEditorConfig.selectedQueryId === queryId;\n const filteredQueries = queries.filter((q) => q.id !== queryId);\n\n set((state) =>\n produce(state, (draft) => {\n draft.config.sqlEditor.queries = filteredQueries;\n\n // If we're deleting the selected tab, select the previous one or the first one\n if (isSelected && filteredQueries.length > 0) {\n const newSelectedIndex = Math.max(0, index - 1);\n // Safely access the ID with fallback to the first query if needed\n const newSelectedId =\n filteredQueries[newSelectedIndex]?.id ?? filteredQueries[0]?.id;\n if (newSelectedId) {\n draft.config.sqlEditor.selectedQueryId = newSelectedId;\n }\n }\n }),\n );\n },\n\n renameQueryTab: (queryId, newName) => {\n set((state) =>\n produce(state, (draft) => {\n const query = draft.config.sqlEditor.queries.find(\n (q) => q.id === queryId,\n );\n if (query) {\n query.name = newName || query.name;\n }\n }),\n );\n },\n\n updateQueryText: (queryId, queryText) => {\n set((state) =>\n produce(state, (draft) => {\n const query = draft.config.sqlEditor.queries.find(\n (q) => q.id === queryId,\n );\n if (query) {\n query.query = queryText;\n }\n }),\n );\n },\n\n setSelectedQueryId: (queryId) => {\n set((state) =>\n produce(state, (draft) => {\n draft.config.sqlEditor.selectedQueryId = queryId;\n }),\n );\n },\n\n getCurrentQuery: (defaultQuery = '') => {\n const sqlEditorConfig = get().config.sqlEditor;\n const selectedId = sqlEditorConfig.selectedQueryId;\n // Find query by ID\n const query = sqlEditorConfig.queries.find((q) => q.id === selectedId);\n // If found, return its query text, otherwise default\n return query?.query || defaultQuery;\n },\n },\n }));\n}\n\ntype ProjectConfigWithSqlEditor = BaseProjectConfig & SqlEditorSliceConfig;\ntype ProjectStateWithSqlEditor = ProjectState<ProjectConfigWithSqlEditor> &\n SqlEditorSliceState;\n\nexport function useStoreWithSqlEditor<T>(\n selector: (state: ProjectStateWithSqlEditor) => T,\n): T {\n return useBaseProjectStore<\n BaseProjectConfig & SqlEditorSliceConfig,\n ProjectState<ProjectConfigWithSqlEditor>,\n T\n >((state) => selector(state as unknown as ProjectStateWithSqlEditor));\n}\n"]}
@@ -0,0 +1,30 @@
1
+ import React from 'react';
2
+ import type { MonacoEditorProps } from '@sqlrooms/monaco-editor';
3
+ import type { DataTable } from '@sqlrooms/duckdb';
4
+ export interface SqlMonacoEditorProps extends Omit<MonacoEditorProps, 'language'> {
5
+ /**
6
+ * Custom SQL keywords to add to the completion provider
7
+ */
8
+ customKeywords?: string[];
9
+ /**
10
+ * Custom SQL functions to add to the completion provider
11
+ */
12
+ customFunctions?: string[];
13
+ /**
14
+ * Table schemas for autocompletion
15
+ */
16
+ tableSchemas?: DataTable[];
17
+ /**
18
+ * Callback to get the latest table schemas
19
+ * This is called from within provideCompletionItems to ensure we have the latest data
20
+ */
21
+ getLatestSchemas?: () => {
22
+ tableSchemas: DataTable[];
23
+ };
24
+ }
25
+ /**
26
+ * A Monaco editor for editing SQL with DuckDB syntax highlighting and autocompletion
27
+ * This is an internal component used by SqlEditor
28
+ */
29
+ export declare const SqlMonacoEditor: React.FC<SqlMonacoEditorProps>;
30
+ //# sourceMappingURL=SqlMonacoEditor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SqlMonacoEditor.d.ts","sourceRoot":"","sources":["../src/SqlMonacoEditor.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAE5D,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,yBAAyB,CAAC;AAQ/D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,kBAAkB,CAAC;AAEhD,MAAM,WAAW,oBACf,SAAQ,IAAI,CAAC,iBAAiB,EAAE,UAAU,CAAC;IAC3C;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B;;OAEG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B;;OAEG;IACH,YAAY,CAAC,EAAE,SAAS,EAAE,CAAC;IAC3B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM;QACvB,YAAY,EAAE,SAAS,EAAE,CAAC;KAC3B,CAAC;CACH;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAsP1D,CAAC"}
@@ -0,0 +1,205 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useRef } from 'react';
3
+ import { MonacoEditor } from '@sqlrooms/monaco-editor';
4
+ import { DUCKDB_KEYWORDS, DUCKDB_FUNCTIONS, SQL_LANGUAGE_CONFIGURATION, } from './constants/duckdb-dialect';
5
+ /**
6
+ * A Monaco editor for editing SQL with DuckDB syntax highlighting and autocompletion
7
+ * This is an internal component used by SqlEditor
8
+ */
9
+ export const SqlMonacoEditor = ({ customKeywords = [], customFunctions = [], tableSchemas = [], getLatestSchemas, onMount, className, ...props }) => {
10
+ // Store references to editor and monaco
11
+ const editorRef = useRef(null);
12
+ const monacoRef = useRef(null);
13
+ const disposableRef = useRef(null);
14
+ // Function to register the completion provider
15
+ const registerCompletionProvider = useCallback(() => {
16
+ if (!editorRef.current || !monacoRef.current)
17
+ return;
18
+ const monaco = monacoRef.current;
19
+ // Dispose previous provider if it exists
20
+ if (disposableRef.current) {
21
+ disposableRef.current.dispose();
22
+ }
23
+ // Register SQL completion provider
24
+ const disposable = monaco.languages.registerCompletionItemProvider('sql', {
25
+ triggerCharacters: [' ', '.', ',', '(', '='],
26
+ provideCompletionItems: (model, position) => {
27
+ try {
28
+ // Get the latest schemas if the callback is provided
29
+ let currentSchemas = tableSchemas;
30
+ if (getLatestSchemas) {
31
+ const latest = getLatestSchemas();
32
+ currentSchemas = latest.tableSchemas;
33
+ }
34
+ const suggestions = [];
35
+ const word = model.getWordUntilPosition(position);
36
+ const range = {
37
+ startLineNumber: position.lineNumber,
38
+ endLineNumber: position.lineNumber,
39
+ startColumn: word.startColumn,
40
+ endColumn: word.endColumn,
41
+ };
42
+ // Get the text before the cursor to determine context
43
+ const lineContent = model.getLineContent(position.lineNumber);
44
+ const textBeforeCursor = lineContent
45
+ .substring(0, position.column - 1)
46
+ .trim()
47
+ .toLowerCase();
48
+ // Check if we're after a FROM, JOIN, or similar clause to prioritize table suggestions
49
+ const isTableContext = /\b(from|join|into|update|table)\s+\w*$/.test(textBeforeCursor);
50
+ // Check if we're after a table name and period to prioritize column suggestions
51
+ const isColumnContext = /\b(\w+)\.\w*$/.test(textBeforeCursor);
52
+ // Combine keywords and functions with custom ones
53
+ const keywords = [...DUCKDB_KEYWORDS, ...customKeywords];
54
+ const functions = [...DUCKDB_FUNCTIONS, ...customFunctions];
55
+ // Add keyword suggestions (if not in a specific context)
56
+ if (!isColumnContext) {
57
+ keywords.forEach((keyword) => {
58
+ suggestions.push({
59
+ label: keyword,
60
+ kind: monaco.languages.CompletionItemKind.Keyword,
61
+ insertText: keyword,
62
+ range: range,
63
+ detail: 'Keyword',
64
+ sortText: isTableContext ? 'z' + keyword : 'a' + keyword, // Lower priority in table context
65
+ });
66
+ });
67
+ }
68
+ // Add function suggestions (if not in a specific context)
69
+ if (!isColumnContext) {
70
+ functions.forEach((func) => {
71
+ suggestions.push({
72
+ label: func,
73
+ kind: monaco.languages.CompletionItemKind.Function,
74
+ insertText: func,
75
+ range: range,
76
+ detail: 'Function',
77
+ sortText: isTableContext ? 'z' + func : 'b' + func, // Lower priority in table context
78
+ });
79
+ });
80
+ }
81
+ // Add table and column suggestions from schemas
82
+ currentSchemas.forEach((table) => {
83
+ const tableName = table.tableName;
84
+ // Add table suggestion
85
+ suggestions.push({
86
+ label: tableName,
87
+ kind: monaco.languages.CompletionItemKind.Class,
88
+ insertText: tableName,
89
+ range: range,
90
+ detail: 'Table',
91
+ documentation: {
92
+ value: `Table: ${tableName}`,
93
+ isTrusted: true,
94
+ },
95
+ sortText: isTableContext ? 'a' + tableName : 'c' + tableName, // Higher priority in table context
96
+ });
97
+ // Extract table name from context if we're in a column context
98
+ let contextTableName = '';
99
+ if (isColumnContext) {
100
+ const match = textBeforeCursor.match(/\b(\w+)\.\w*$/);
101
+ if (match && match[1]) {
102
+ contextTableName = match[1];
103
+ }
104
+ }
105
+ // Only add columns for the current table if we're in a column context
106
+ if (!isColumnContext || contextTableName === tableName) {
107
+ // Add column suggestions
108
+ table.columns.forEach((column) => {
109
+ const columnName = column.name;
110
+ const columnType = column.type;
111
+ suggestions.push({
112
+ label: columnName,
113
+ kind: monaco.languages.CompletionItemKind.Field,
114
+ insertText: columnName,
115
+ range: range,
116
+ detail: `Column (${columnType})`,
117
+ documentation: {
118
+ value: `Column from table ${tableName}`,
119
+ isTrusted: true,
120
+ },
121
+ sortText: isColumnContext && contextTableName === tableName
122
+ ? 'a' + columnName
123
+ : 'd' + columnName,
124
+ });
125
+ // Only add table.column suggestions if not in a column context
126
+ if (!isColumnContext) {
127
+ suggestions.push({
128
+ label: `${tableName}.${columnName}`,
129
+ kind: monaco.languages.CompletionItemKind.Field,
130
+ insertText: `${tableName}.${columnName}`,
131
+ range: range,
132
+ detail: `Column (${columnType})`,
133
+ documentation: {
134
+ value: `Column from table ${tableName}`,
135
+ isTrusted: true,
136
+ },
137
+ sortText: 'e' + tableName + columnName,
138
+ });
139
+ }
140
+ });
141
+ }
142
+ });
143
+ return {
144
+ suggestions,
145
+ };
146
+ }
147
+ catch (error) {
148
+ console.error('Error in SQL completion provider:', error);
149
+ return { suggestions: [] };
150
+ }
151
+ },
152
+ });
153
+ // Store the disposable to clean up later
154
+ disposableRef.current = disposable;
155
+ }, [customKeywords, customFunctions, tableSchemas, getLatestSchemas]);
156
+ // Re-register completion provider when tableSchemas change
157
+ useEffect(() => {
158
+ if (editorRef.current && monacoRef.current) {
159
+ registerCompletionProvider();
160
+ }
161
+ }, [tableSchemas, registerCompletionProvider]);
162
+ // Handle editor mounting to configure SQL language features
163
+ const handleEditorDidMount = useCallback((editor, monaco) => {
164
+ // Store references
165
+ editorRef.current = editor;
166
+ monacoRef.current = monaco;
167
+ // Register SQL language if not already registered
168
+ if (!monaco.languages.getLanguages().some((lang) => lang.id === 'sql')) {
169
+ monaco.languages.register({ id: 'sql' });
170
+ }
171
+ // Combine keywords and functions with custom ones
172
+ const keywords = [...DUCKDB_KEYWORDS, ...customKeywords];
173
+ const functions = [...DUCKDB_FUNCTIONS, ...customFunctions];
174
+ // Set the language configuration
175
+ monaco.languages.setMonarchTokensProvider('sql', {
176
+ ...SQL_LANGUAGE_CONFIGURATION,
177
+ keywords,
178
+ builtinFunctions: functions,
179
+ }); // Using 'as any' to bypass the type checking issue
180
+ // Register the completion provider
181
+ registerCompletionProvider();
182
+ // Store the disposable to clean up later if needed
183
+ editor.onDidDispose(() => {
184
+ if (disposableRef.current) {
185
+ disposableRef.current.dispose();
186
+ }
187
+ });
188
+ // Call the original onMount if provided
189
+ if (onMount) {
190
+ onMount(editor, monaco);
191
+ }
192
+ }, [
193
+ customKeywords,
194
+ customFunctions,
195
+ tableSchemas,
196
+ onMount,
197
+ registerCompletionProvider,
198
+ ]);
199
+ return (_jsx(MonacoEditor, { language: "sql", onMount: handleEditorDidMount, className: className, options: {
200
+ formatOnPaste: true,
201
+ formatOnType: true,
202
+ wordWrap: 'on',
203
+ }, ...props }));
204
+ };
205
+ //# sourceMappingURL=SqlMonacoEditor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SqlMonacoEditor.js","sourceRoot":"","sources":["../src/SqlMonacoEditor.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, {useCallback, useEffect, useRef} from 'react';\nimport {MonacoEditor} from '@sqlrooms/monaco-editor';\nimport type {MonacoEditorProps} from '@sqlrooms/monaco-editor';\nimport type {OnMount} from '@monaco-editor/react';\nimport type * as Monaco from 'monaco-editor';\nimport {\n DUCKDB_KEYWORDS,\n DUCKDB_FUNCTIONS,\n SQL_LANGUAGE_CONFIGURATION,\n} from './constants/duckdb-dialect';\nimport type {DataTable} from '@sqlrooms/duckdb';\n\nexport interface SqlMonacoEditorProps\n extends Omit<MonacoEditorProps, 'language'> {\n /**\n * Custom SQL keywords to add to the completion provider\n */\n customKeywords?: string[];\n /**\n * Custom SQL functions to add to the completion provider\n */\n customFunctions?: string[];\n /**\n * Table schemas for autocompletion\n */\n tableSchemas?: DataTable[];\n /**\n * Callback to get the latest table schemas\n * This is called from within provideCompletionItems to ensure we have the latest data\n */\n getLatestSchemas?: () => {\n tableSchemas: DataTable[];\n };\n}\n\n/**\n * A Monaco editor for editing SQL with DuckDB syntax highlighting and autocompletion\n * This is an internal component used by SqlEditor\n */\nexport const SqlMonacoEditor: React.FC<SqlMonacoEditorProps> = ({\n customKeywords = [],\n customFunctions = [],\n tableSchemas = [],\n getLatestSchemas,\n onMount,\n className,\n ...props\n}) => {\n // Store references to editor and monaco\n const editorRef = useRef<any>(null);\n const monacoRef = useRef<any>(null);\n const disposableRef = useRef<any>(null);\n\n // Function to register the completion provider\n const registerCompletionProvider = useCallback(() => {\n if (!editorRef.current || !monacoRef.current) return;\n\n const monaco = monacoRef.current;\n\n // Dispose previous provider if it exists\n if (disposableRef.current) {\n disposableRef.current.dispose();\n }\n\n // Register SQL completion provider\n const disposable = monaco.languages.registerCompletionItemProvider('sql', {\n triggerCharacters: [' ', '.', ',', '(', '='],\n provideCompletionItems: (model: any, position: any) => {\n try {\n // Get the latest schemas if the callback is provided\n let currentSchemas = tableSchemas;\n\n if (getLatestSchemas) {\n const latest = getLatestSchemas();\n currentSchemas = latest.tableSchemas;\n }\n\n const suggestions: Monaco.languages.CompletionItem[] = [];\n const word = model.getWordUntilPosition(position);\n const range = {\n startLineNumber: position.lineNumber,\n endLineNumber: position.lineNumber,\n startColumn: word.startColumn,\n endColumn: word.endColumn,\n };\n\n // Get the text before the cursor to determine context\n const lineContent = model.getLineContent(position.lineNumber);\n const textBeforeCursor = lineContent\n .substring(0, position.column - 1)\n .trim()\n .toLowerCase();\n\n // Check if we're after a FROM, JOIN, or similar clause to prioritize table suggestions\n const isTableContext = /\\b(from|join|into|update|table)\\s+\\w*$/.test(\n textBeforeCursor,\n );\n\n // Check if we're after a table name and period to prioritize column suggestions\n const isColumnContext = /\\b(\\w+)\\.\\w*$/.test(textBeforeCursor);\n\n // Combine keywords and functions with custom ones\n const keywords = [...DUCKDB_KEYWORDS, ...customKeywords];\n const functions = [...DUCKDB_FUNCTIONS, ...customFunctions];\n\n // Add keyword suggestions (if not in a specific context)\n if (!isColumnContext) {\n keywords.forEach((keyword) => {\n suggestions.push({\n label: keyword,\n kind: monaco.languages.CompletionItemKind.Keyword,\n insertText: keyword,\n range: range,\n detail: 'Keyword',\n sortText: isTableContext ? 'z' + keyword : 'a' + keyword, // Lower priority in table context\n });\n });\n }\n\n // Add function suggestions (if not in a specific context)\n if (!isColumnContext) {\n functions.forEach((func) => {\n suggestions.push({\n label: func,\n kind: monaco.languages.CompletionItemKind.Function,\n insertText: func,\n range: range,\n detail: 'Function',\n sortText: isTableContext ? 'z' + func : 'b' + func, // Lower priority in table context\n });\n });\n }\n\n // Add table and column suggestions from schemas\n currentSchemas.forEach((table) => {\n const tableName = table.tableName;\n\n // Add table suggestion\n suggestions.push({\n label: tableName,\n kind: monaco.languages.CompletionItemKind.Class,\n insertText: tableName,\n range: range,\n detail: 'Table',\n documentation: {\n value: `Table: ${tableName}`,\n isTrusted: true,\n },\n sortText: isTableContext ? 'a' + tableName : 'c' + tableName, // Higher priority in table context\n });\n\n // Extract table name from context if we're in a column context\n let contextTableName = '';\n if (isColumnContext) {\n const match = textBeforeCursor.match(/\\b(\\w+)\\.\\w*$/);\n if (match && match[1]) {\n contextTableName = match[1];\n }\n }\n\n // Only add columns for the current table if we're in a column context\n if (!isColumnContext || contextTableName === tableName) {\n // Add column suggestions\n table.columns.forEach((column) => {\n const columnName = column.name;\n const columnType = column.type;\n\n suggestions.push({\n label: columnName,\n kind: monaco.languages.CompletionItemKind.Field,\n insertText: columnName,\n range: range,\n detail: `Column (${columnType})`,\n documentation: {\n value: `Column from table ${tableName}`,\n isTrusted: true,\n },\n sortText:\n isColumnContext && contextTableName === tableName\n ? 'a' + columnName\n : 'd' + columnName,\n });\n\n // Only add table.column suggestions if not in a column context\n if (!isColumnContext) {\n suggestions.push({\n label: `${tableName}.${columnName}`,\n kind: monaco.languages.CompletionItemKind.Field,\n insertText: `${tableName}.${columnName}`,\n range: range,\n detail: `Column (${columnType})`,\n documentation: {\n value: `Column from table ${tableName}`,\n isTrusted: true,\n },\n sortText: 'e' + tableName + columnName,\n });\n }\n });\n }\n });\n\n return {\n suggestions,\n };\n } catch (error) {\n console.error('Error in SQL completion provider:', error);\n return {suggestions: []};\n }\n },\n });\n\n // Store the disposable to clean up later\n disposableRef.current = disposable;\n }, [customKeywords, customFunctions, tableSchemas, getLatestSchemas]);\n\n // Re-register completion provider when tableSchemas change\n useEffect(() => {\n if (editorRef.current && monacoRef.current) {\n registerCompletionProvider();\n }\n }, [tableSchemas, registerCompletionProvider]);\n\n // Handle editor mounting to configure SQL language features\n const handleEditorDidMount = useCallback<OnMount>(\n (editor, monaco) => {\n // Store references\n editorRef.current = editor;\n monacoRef.current = monaco;\n\n // Register SQL language if not already registered\n if (\n !monaco.languages.getLanguages().some((lang: any) => lang.id === 'sql')\n ) {\n monaco.languages.register({id: 'sql'});\n }\n\n // Combine keywords and functions with custom ones\n const keywords = [...DUCKDB_KEYWORDS, ...customKeywords];\n const functions = [...DUCKDB_FUNCTIONS, ...customFunctions];\n\n // Set the language configuration\n monaco.languages.setMonarchTokensProvider('sql', {\n ...SQL_LANGUAGE_CONFIGURATION,\n keywords,\n builtinFunctions: functions,\n } as any); // Using 'as any' to bypass the type checking issue\n\n // Register the completion provider\n registerCompletionProvider();\n\n // Store the disposable to clean up later if needed\n editor.onDidDispose(() => {\n if (disposableRef.current) {\n disposableRef.current.dispose();\n }\n });\n\n // Call the original onMount if provided\n if (onMount) {\n onMount(editor, monaco);\n }\n },\n [\n customKeywords,\n customFunctions,\n tableSchemas,\n onMount,\n registerCompletionProvider,\n ],\n );\n\n return (\n <MonacoEditor\n language=\"sql\"\n onMount={handleEditorDidMount}\n className={className}\n options={{\n formatOnPaste: true,\n formatOnType: true,\n wordWrap: 'on',\n }}\n {...props}\n />\n );\n};\n"]}
@@ -4,7 +4,6 @@ import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMe
4
4
  import { EllipsisIcon, FileIcon, PencilIcon, PlusIcon, RefreshCcwIcon, XIcon, } from 'lucide-react';
5
5
  import { useCallback, useState } from 'react';
6
6
  import CreateTableModal from './CreateTableModal';
7
- import { useStoreWithSqlEditor } from './SqlEditorSlice';
8
7
  const SqlQueryDataSourcesPanel = (props) => {
9
8
  const { queryDataSources } = props;
10
9
  const [selectedDataSource, setSelectedDataSource] = useState();
@@ -23,9 +22,9 @@ const SqlQueryDataSourcesPanel = (props) => {
23
22
  const { tableName } = dataSource;
24
23
  removeSqlQueryDataSource(tableName);
25
24
  }, [removeSqlQueryDataSource]);
26
- const addOrUpdateSqlQuery = useStoreWithSqlEditor((state) => state.sqlEditor.addOrUpdateSqlQuery);
25
+ const addOrUpdateSqlQueryDataSource = useBaseProjectStore((state) => state.project.addOrUpdateSqlQueryDataSource);
27
26
  const isReadOnly = useBaseProjectStore((state) => state.project.isReadOnly);
28
- return (_jsxs("div", { className: "flex flex-col overflow-auto flex-grow", children: [_jsx("div", { className: "flex flex-col items-stretch", children: _jsxs(Button, { variant: "secondary", size: "sm", onClick: () => setIsOpen(true), disabled: isReadOnly, children: [_jsx(PlusIcon, { className: "mr-2 h-4 w-4" }), "Add"] }) }), _jsx(CreateTableModal, { isOpen: isOpen, onClose: handleClose, editDataSource: selectedDataSource, query: "", onAddOrUpdateSqlQuery: addOrUpdateSqlQuery }), _jsx("div", { className: "flex flex-col overflow-auto flex-grow", children: queryDataSources.map((dataSource) => (_jsxs("div", { className: "p-2 flex flex-col gap-1", children: [_jsxs("div", { className: "flex gap-1 cursor-pointer flex-row items-center", children: [_jsx("div", { className: "flex-none w-[15px]", children: _jsx(FileIcon, { className: "w-[15px]" }) }), _jsx("div", { className: "flex-1 overflow-hidden text-ellipsis", children: _jsx("span", { className: "text-xs break-words", children: dataSource.tableName }) }), _jsx("div", { className: "flex-none", children: !isReadOnly ? (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { size: "icon", variant: "ghost", className: "h-6 w-6 text-muted-foreground", children: _jsx(EllipsisIcon, { className: "h-5 w-5" }) }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsxs(DropdownMenuItem, { onClick: () => handleEdit(dataSource), children: [_jsx(PencilIcon, { className: "mr-2 h-4 w-4" }), "Edit"] }), _jsxs(DropdownMenuItem, { disabled: true, children: [_jsx(RefreshCcwIcon, { className: "mr-2 h-4 w-4" }), "Refresh"] }), _jsxs(DropdownMenuItem, { onClick: () => handleRemove(dataSource), children: [_jsx(XIcon, { className: "mr-2 h-4 w-4" }), "Remove from project"] })] })] })) : null })] }), _jsx("div", { className: "flex flex-row gap-1 items-center", children: dataSourceStates[dataSource.tableName]?.status ===
27
+ return (_jsxs("div", { className: "flex flex-col overflow-auto flex-grow", children: [_jsx("div", { className: "flex flex-col items-stretch", children: _jsxs(Button, { variant: "secondary", size: "sm", onClick: () => setIsOpen(true), disabled: isReadOnly, children: [_jsx(PlusIcon, { className: "mr-2 h-4 w-4" }), "Add"] }) }), _jsx(CreateTableModal, { isOpen: isOpen, onClose: handleClose, editDataSource: selectedDataSource, query: "", onAddOrUpdateSqlQuery: addOrUpdateSqlQueryDataSource }), _jsx("div", { className: "flex flex-col overflow-auto flex-grow", children: queryDataSources.map((dataSource) => (_jsxs("div", { className: "p-2 flex flex-col gap-1", children: [_jsxs("div", { className: "flex gap-1 cursor-pointer flex-row items-center", children: [_jsx("div", { className: "flex-none w-[15px]", children: _jsx(FileIcon, { className: "w-[15px]" }) }), _jsx("div", { className: "flex-1 overflow-hidden text-ellipsis", children: _jsx("span", { className: "text-xs break-words", children: dataSource.tableName }) }), _jsx("div", { className: "flex-none", children: !isReadOnly ? (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { size: "icon", variant: "ghost", className: "h-6 w-6 text-muted-foreground", children: _jsx(EllipsisIcon, { className: "h-5 w-5" }) }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsxs(DropdownMenuItem, { onClick: () => handleEdit(dataSource), children: [_jsx(PencilIcon, { className: "mr-2 h-4 w-4" }), "Edit"] }), _jsxs(DropdownMenuItem, { disabled: true, children: [_jsx(RefreshCcwIcon, { className: "mr-2 h-4 w-4" }), "Refresh"] }), _jsxs(DropdownMenuItem, { onClick: () => handleRemove(dataSource), children: [_jsx(XIcon, { className: "mr-2 h-4 w-4" }), "Remove from project"] })] })] })) : null })] }), _jsx("div", { className: "flex flex-row gap-1 items-center", children: dataSourceStates[dataSource.tableName]?.status ===
29
28
  DataSourceStatus.ERROR ? (_jsx("div", { className: "flex-1 bg-destructive/15 text-destructive text-xs p-1 rounded", children: dataSourceStates[dataSource.tableName]?.message })) : dataSourceStates[dataSource.tableName]?.status ===
30
29
  DataSourceStatus.FETCHING ? (_jsx("div", { className: "w-full bg-secondary h-1 rounded overflow-hidden", children: _jsx("div", { className: "h-full bg-primary animate-pulse" }) })) : null })] }, dataSource.tableName))) })] }));
31
30
  };