@marimo-team/frontend 0.23.16-dev51 → 0.23.16-dev53
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.
- package/dist/assets/{CellStatus-DciB5rbr.js → CellStatus-BfPw99P2.js} +1 -1
- package/dist/assets/{JsonOutput-BM3vve9F.js → JsonOutput-E3o1Uk0e.js} +12 -12
- package/dist/assets/{MarimoErrorOutput-Bh0y53Du.js → MarimoErrorOutput-D9Dypu7Q.js} +4 -4
- package/dist/assets/{RenderHTML-B9jx7Dsm.js → RenderHTML-DRJxD5FQ.js} +1 -1
- package/dist/assets/{RunButton-DMtHbFuA.js → RunButton-y7Y9rjmF.js} +1 -1
- package/dist/assets/{add-cell-with-ai-BcRYriMM.js → add-cell-with-ai-ByfOWv0Z.js} +12 -12
- package/dist/assets/add-connection-dialog-DVSFAYXK.js +178 -0
- package/dist/assets/{agent-panel-B31W_VwU.js → agent-panel-B2OKzocQ.js} +4 -4
- package/dist/assets/{ai-model-dropdown-B3D05oQh.js → ai-model-dropdown-Dr2QbDlZ.js} +3 -3
- package/dist/assets/{app-config-button-D_mjovwU.js → app-config-button-DScmpqcV.js} +1 -1
- package/dist/assets/{blob-BJREJiQp.js → blob-BXcsY70H.js} +1 -1
- package/dist/assets/{cell-editor-BOhZHoLm.js → cell-editor-BuGvBHQI.js} +11 -11
- package/dist/assets/cell-link-DGv7Q4Mc.js +1 -0
- package/dist/assets/{cells-Jr-5Pc9f.js → cells-CMc2NsXm.js} +61 -61
- package/dist/assets/{chat-display-Djy1Jt6b.js → chat-display-CeQftKrl.js} +1 -1
- package/dist/assets/{chat-panel-B2nYSE_l.js → chat-panel-BP5hfvS7.js} +4 -4
- package/dist/assets/{chat-ui-DlG0En7w.js → chat-ui-DoA0lexh.js} +4 -4
- package/dist/assets/{column-preview-BUHlI5va.js → column-preview-BeR1SI6Z.js} +1 -1
- package/dist/assets/{command-palette-rEJmXCZM.js → command-palette-DZSYeVCO.js} +1 -1
- package/dist/assets/{common-CZc3fQI4.js → common-NWgkp5L1.js} +1 -1
- package/dist/assets/{components-GAYCj6Mx.js → components-Mbf0MEIC.js} +1 -1
- package/dist/assets/{datasource-AWkkAeiS.js → datasource-Wt8yUynT.js} +2 -2
- package/dist/assets/{dependency-graph-panel-wcqTPq6b.js → dependency-graph-panel-B7TXWKYs.js} +4 -4
- package/dist/assets/{documentation-panel-Eu8MIkoU.js → documentation-panel-D9MZUKaD.js} +1 -1
- package/dist/assets/{download-CQ7iZFJL.js → download-U_wgQ8mC.js} +2 -2
- package/dist/assets/{edit-page-Cw7nN6HC.js → edit-page-BBc9BGYT.js} +6 -6
- package/dist/assets/error-entries-Bs30Z6uG.js +8 -0
- package/dist/assets/{error-panel-BZzr0PA1.js → error-panel-KY679tDx.js} +1 -1
- package/dist/assets/{file-explorer-panel-C1W7g_Wp.js → file-explorer-panel-9y0hzNAa.js} +4 -4
- package/dist/assets/{file-icons-BAqZR08W.js → file-icons-D5l3kSYq.js} +1 -1
- package/dist/assets/{file-name-input-0MiZVw5r.js → file-name-input-D_xhjKFp.js} +1 -1
- package/dist/assets/{floating-outline-C6g75zrP.js → floating-outline-BdgJT2B9.js} +1 -1
- package/dist/assets/{focus-C0HXXg3V.js → focus-D64hL1tZ.js} +1 -1
- package/dist/assets/{form-DT44Rmhp.js → form-CoqwGbkO.js} +1 -1
- package/dist/assets/{form-C051zAev.js → form-b4jQBV3T.js} +1 -1
- package/dist/assets/{home-page-CJz9IHZ2.js → home-page-tVWY9YdF.js} +2 -2
- package/dist/assets/{html-to-image-CfjhKLA4.js → html-to-image-Dm7huiQj.js} +2 -2
- package/dist/assets/index-8Nlyq3mV.css +2 -0
- package/dist/assets/{index-CrEgp1mR.js → index-Dz8dLbnI.js} +6 -6
- package/dist/assets/{layout-D4bpsgVD.js → layout-DKUs0ozP.js} +5 -5
- package/dist/assets/{logs-panel-CsaY3O1m.js → logs-panel-BzS7VSh0.js} +1 -1
- package/dist/assets/{markdown-renderer-BgqeO4JR.js → markdown-renderer-uWTrQId1.js} +2 -2
- package/dist/assets/{name-cell-input-DcU8Rfrz.js → name-cell-input-BgxcWTfV.js} +1 -1
- package/dist/assets/{outline-panel-D-CHPZQM.js → outline-panel-BT3zzwfa.js} +1 -1
- package/dist/assets/{packages-panel-dq7QVZl1.js → packages-panel-BKfeGuve.js} +1 -1
- package/dist/assets/panels-4jSJxEYp.js +4 -0
- package/dist/assets/{process-output-NbwnUu05.js → process-output-DE8TTXPC.js} +1 -1
- package/dist/assets/{radio-group-rhQpGwU-.js → radio-group-Da80KtoT.js} +1 -1
- package/dist/assets/readonly-code-display-B8zhuSov.js +1 -0
- package/dist/assets/{readonly-python-code-Q_1Y3VyZ.js → readonly-python-code-Bmo6cMSq.js} +1 -1
- package/dist/assets/{reveal-component-CedpDwM8.js → reveal-component-Cv7WZ4r6.js} +2 -2
- package/dist/assets/{run-page-CGoux5cj.js → run-page-CA21zsME.js} +1 -1
- package/dist/assets/scratchpad-panel-Br7RFJkK.js +1 -0
- package/dist/assets/{session-panel-OIot3eyB.js → session-panel-BiUht4zM.js} +1 -1
- package/dist/assets/{skeleton-BRvjFlRu.js → skeleton-CY-B1asm.js} +1 -1
- package/dist/assets/{snippets-panel-YFyH2hUM.js → snippets-panel-CjiTlCpD.js} +1 -1
- package/dist/assets/{state-bHyw5u3R.js → state-BJ7IdtXW.js} +1 -1
- package/dist/assets/state-TgAqts79.js +3 -0
- package/dist/assets/{tracing-CDMM1BB2.js → tracing-DmQ7KQ3A.js} +1 -1
- package/dist/assets/{tracing-panel-BH5Zbvl9.js → tracing-panel-C1TOV9kj.js} +2 -2
- package/dist/assets/{tree-actions-DDveUIHK.js → tree-actions-enwITSy7.js} +1 -1
- package/dist/assets/useCellActionButton-CXvU9GmI.js +1 -0
- package/dist/assets/{useDeleteCell-BIopCngo.js → useDeleteCell-Bp3S6pL8.js} +1 -1
- package/dist/assets/{useDependencyPanelTab-snM337f9.js → useDependencyPanelTab-CewYv8cP.js} +1 -1
- package/dist/assets/{useNotebookActions-CsZfWFKy.js → useNotebookActions-l9NAqPKm.js} +4 -4
- package/dist/assets/useRunCells-B-KIwrVZ.js +1 -0
- package/dist/assets/{useSplitCell-BTmKY1CU.js → useSplitCell-B0jvSFsl.js} +1 -1
- package/dist/index.html +24 -24
- package/package.json +1 -1
- package/src/__mocks__/requests.ts +1 -0
- package/src/components/editor/connections/__tests__/quick-add-data-sources.test.tsx +113 -0
- package/src/components/editor/connections/add-connection-dialog.tsx +2 -0
- package/src/components/editor/connections/quick-add-data-sources.tsx +106 -0
- package/src/core/codemirror/language/__tests__/sql.test.ts +191 -7
- package/src/core/codemirror/language/languages/sql/sql.ts +41 -13
- package/src/core/datasets/data-source-discovery.ts +5 -0
- package/src/core/datasets/request-registry.ts +11 -0
- package/src/core/islands/bootstrap.ts +1 -0
- package/src/core/islands/bridge.ts +1 -0
- package/src/core/kernel/messages.ts +2 -0
- package/src/core/network/__tests__/requests-lazy.test.ts +15 -0
- package/src/core/network/__tests__/requests-network.test.ts +14 -0
- package/src/core/network/requests-lazy.ts +1 -0
- package/src/core/network/requests-network.ts +8 -0
- package/src/core/network/requests-static.ts +1 -0
- package/src/core/network/requests-toasting.tsx +1 -0
- package/src/core/network/types.ts +2 -0
- package/src/core/wasm/bridge.ts +10 -0
- package/src/core/websocket/useMarimoKernelConnection.tsx +4 -0
- package/src/hooks/__tests__/useDataSourceDiscovery.test.ts +80 -0
- package/src/hooks/useDataSourceDiscovery.ts +18 -0
- package/dist/assets/add-connection-dialog-syjRSNPT.js +0 -178
- package/dist/assets/cell-link-DqNEv3f9.js +0 -1
- package/dist/assets/error-entries-Cr8uVukN.js +0 -8
- package/dist/assets/index-D6Dp_PFj.css +0 -2
- package/dist/assets/panels-DWoniKhE.js +0 -4
- package/dist/assets/readonly-code-display-CVY2f7EZ.js +0 -1
- package/dist/assets/scratchpad-panel-BLuF25yR.js +0 -1
- package/dist/assets/state-DB_fxbhn.js +0 -3
- package/dist/assets/useCellActionButton-D5ix_g2a.js +0 -1
- package/dist/assets/useRunCells-CF_V8X2K.js +0 -1
|
@@ -66,7 +66,7 @@ import { getSQLMode, type SQLMode } from "./sql-mode";
|
|
|
66
66
|
import { isKnownDialect } from "./utils";
|
|
67
67
|
|
|
68
68
|
const DEFAULT_DIALECT = DuckDBDialect;
|
|
69
|
-
const DEFAULT_PARSER_DIALECT = "DuckDB";
|
|
69
|
+
const DEFAULT_PARSER_DIALECT: ParserDialects = "DuckDB";
|
|
70
70
|
|
|
71
71
|
// A compartment for the SQL config, so we can update the config of codemirror
|
|
72
72
|
const sqlConfigCompartment = new Compartment();
|
|
@@ -372,6 +372,8 @@ class DialectAwareSqlStructureAnalyzer extends SqlStructureAnalyzer {
|
|
|
372
372
|
|
|
373
373
|
class CustomSqlParser extends NodeSqlParser {
|
|
374
374
|
private validationTimeout: number | null = null;
|
|
375
|
+
private pendingValidationResolve: ((errors: SqlParseError[]) => void) | null =
|
|
376
|
+
null;
|
|
375
377
|
private readonly VALIDATION_DELAY_MS = 300; // Wait 300ms after user stops typing
|
|
376
378
|
private isFocused = false; // Only validate if the editor is focused
|
|
377
379
|
|
|
@@ -379,37 +381,51 @@ class CustomSqlParser extends NodeSqlParser {
|
|
|
379
381
|
this.isFocused = focused;
|
|
380
382
|
}
|
|
381
383
|
|
|
384
|
+
private resolvePendingValidation(errors: SqlParseError[]): void {
|
|
385
|
+
this.pendingValidationResolve?.(errors);
|
|
386
|
+
this.pendingValidationResolve = null;
|
|
387
|
+
}
|
|
388
|
+
|
|
382
389
|
private async validateWithDelay(
|
|
383
390
|
sql: string,
|
|
384
|
-
engine:
|
|
391
|
+
engine: ConnectionName,
|
|
385
392
|
dialect: ParserDialects | null,
|
|
386
393
|
): Promise<SqlParseError[]> {
|
|
387
|
-
// Clear any existing delay call
|
|
394
|
+
// Clear any existing delay call, resolving its promise so a superseded
|
|
395
|
+
// request doesn't hang forever.
|
|
388
396
|
if (this.validationTimeout) {
|
|
389
397
|
window.clearTimeout(this.validationTimeout);
|
|
398
|
+
this.resolvePendingValidation([]);
|
|
390
399
|
}
|
|
391
400
|
|
|
392
401
|
// Set up a new request to be called after the delay
|
|
393
402
|
return new Promise((resolve) => {
|
|
403
|
+
this.pendingValidationResolve = resolve;
|
|
394
404
|
this.validationTimeout = window.setTimeout(async () => {
|
|
405
|
+
this.validationTimeout = null;
|
|
406
|
+
|
|
395
407
|
// Only validate if the editor is still focused
|
|
396
408
|
if (!this.isFocused) {
|
|
397
|
-
|
|
409
|
+
this.resolvePendingValidation([]);
|
|
398
410
|
return;
|
|
399
411
|
}
|
|
400
412
|
|
|
401
413
|
try {
|
|
402
|
-
|
|
414
|
+
// For validate mode, we run EXPLAIN queries on the engine, which can be
|
|
415
|
+
// expensive for remote databases. So, we only run for internal engines.
|
|
416
|
+
const sqlMode = INTERNAL_SQL_ENGINES.has(engine)
|
|
417
|
+
? getSQLMode()
|
|
418
|
+
: "default";
|
|
403
419
|
const result = await validateSQL(sql, engine, dialect, sqlMode);
|
|
404
420
|
if (result.error) {
|
|
405
421
|
Logger.error("Failed to validate SQL", { error: result.error });
|
|
406
|
-
|
|
422
|
+
this.resolvePendingValidation([]);
|
|
407
423
|
return;
|
|
408
424
|
}
|
|
409
|
-
|
|
425
|
+
this.resolvePendingValidation(result.parse_result?.errors ?? []);
|
|
410
426
|
} catch (error) {
|
|
411
427
|
Logger.error("Failed to validate SQL", { error });
|
|
412
|
-
|
|
428
|
+
this.resolvePendingValidation([]);
|
|
413
429
|
}
|
|
414
430
|
}, this.VALIDATION_DELAY_MS);
|
|
415
431
|
});
|
|
@@ -420,19 +436,23 @@ class CustomSqlParser extends NodeSqlParser {
|
|
|
420
436
|
opts: { state: EditorState },
|
|
421
437
|
): Promise<SqlParseError[]> {
|
|
422
438
|
const metadata = getSQLMetadata(opts.state);
|
|
439
|
+
const dialect = connectionNameToParserDialect(metadata.engine);
|
|
423
440
|
|
|
424
441
|
// Only validate if the editor is focused
|
|
425
442
|
if (!this.isFocused) {
|
|
426
443
|
return [];
|
|
427
444
|
}
|
|
428
445
|
|
|
429
|
-
// Only perform custom validation for DuckDB
|
|
430
|
-
if (!
|
|
446
|
+
// Only perform custom validation for DuckDB as we have a custom validation endpoint for it.
|
|
447
|
+
if (!isDuckDBConnection(metadata.engine, dialect)) {
|
|
431
448
|
return super.validateSql(sql, opts);
|
|
432
449
|
}
|
|
433
450
|
|
|
434
|
-
|
|
435
|
-
|
|
451
|
+
return this.validateWithDelay(
|
|
452
|
+
sql,
|
|
453
|
+
metadata.engine,
|
|
454
|
+
dialect ?? DEFAULT_PARSER_DIALECT,
|
|
455
|
+
);
|
|
436
456
|
}
|
|
437
457
|
|
|
438
458
|
override async parse(
|
|
@@ -441,9 +461,10 @@ class CustomSqlParser extends NodeSqlParser {
|
|
|
441
461
|
): Promise<NodeSqlParserResult> {
|
|
442
462
|
const metadata = getSQLMetadata(opts.state);
|
|
443
463
|
const engine = metadata.engine;
|
|
464
|
+
const dialect = connectionNameToParserDialect(engine);
|
|
444
465
|
|
|
445
466
|
// For now, always return success for DuckDB
|
|
446
|
-
if (engine
|
|
467
|
+
if (isDuckDBConnection(engine, dialect)) {
|
|
447
468
|
return { success: true, errors: [] };
|
|
448
469
|
}
|
|
449
470
|
|
|
@@ -451,6 +472,13 @@ class CustomSqlParser extends NodeSqlParser {
|
|
|
451
472
|
}
|
|
452
473
|
}
|
|
453
474
|
|
|
475
|
+
function isDuckDBConnection(
|
|
476
|
+
engine: ConnectionName,
|
|
477
|
+
dialect: ParserDialects | null,
|
|
478
|
+
): boolean {
|
|
479
|
+
return engine === DUCKDB_ENGINE || dialect === "DuckDB";
|
|
480
|
+
}
|
|
481
|
+
|
|
454
482
|
/**
|
|
455
483
|
* Update the SQL dialect in the editor view.
|
|
456
484
|
*/
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
import type {
|
|
3
|
+
DataSourceDiscoveryResult,
|
|
3
4
|
SQLSchemaListPreview,
|
|
4
5
|
SQLTableListPreview,
|
|
5
6
|
SQLTablePreview,
|
|
@@ -19,6 +20,16 @@ import type {
|
|
|
19
20
|
// The backend returns data tables, which could also exist in other engines, dbs, schemas
|
|
20
21
|
// Thus, we use the request ID pattern to match the response to the request
|
|
21
22
|
|
|
23
|
+
export const DiscoverDataSources = new DeferredRequestRegistry<
|
|
24
|
+
{},
|
|
25
|
+
DataSourceDiscoveryResult
|
|
26
|
+
>("data-source-discovery-result", async (requestId) => {
|
|
27
|
+
const client = getRequestClient();
|
|
28
|
+
await client.discoverDataSources({
|
|
29
|
+
requestId,
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
22
33
|
export const PreviewSQLTable = new DeferredRequestRegistry<
|
|
23
34
|
Omit<PreviewSQLTableRequest, "requestId">,
|
|
24
35
|
SQLTablePreview
|
|
@@ -341,6 +341,7 @@ export class IslandsPyodideBridge implements RunRequests, EditRequests {
|
|
|
341
341
|
previewSQLTableList = throwNotImplemented;
|
|
342
342
|
previewSQLSchemaList = throwNotImplemented;
|
|
343
343
|
previewDataSourceConnection = throwNotImplemented;
|
|
344
|
+
discoverDataSources = throwNotImplemented;
|
|
344
345
|
validateSQL = throwNotImplemented;
|
|
345
346
|
openFile = throwNotImplemented;
|
|
346
347
|
sendListFiles = throwNotImplemented;
|
|
@@ -44,6 +44,8 @@ export type SQLTableListPreview =
|
|
|
44
44
|
export type SQLSchemaListPreview =
|
|
45
45
|
NotificationMessageData<"sql-schema-list-preview">;
|
|
46
46
|
export type ValidateSQLResult = NotificationMessageData<"validate-sql-result">;
|
|
47
|
+
export type DataSourceDiscoveryResult =
|
|
48
|
+
NotificationMessageData<"data-source-discovery-result">;
|
|
47
49
|
export type SecretKeysResult = NotificationMessageData<"secret-keys-result">;
|
|
48
50
|
export type StartupLogs = NotificationMessageData<"startup-logs">;
|
|
49
51
|
export type CellMessage = NotificationMessageData<"cell-op">;
|
|
@@ -114,6 +114,21 @@ describe("createLazyRequests", () => {
|
|
|
114
114
|
expect(mockInit).toHaveBeenCalledTimes(1);
|
|
115
115
|
});
|
|
116
116
|
|
|
117
|
+
it("starts the kernel for datasource discovery", async () => {
|
|
118
|
+
mockDelegate.discoverDataSources = vi.fn().mockResolvedValue(null);
|
|
119
|
+
const lazyRequests = createLazyRequests(
|
|
120
|
+
mockDelegate,
|
|
121
|
+
mockGetRuntimeManager,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
await lazyRequests.discoverDataSources({
|
|
125
|
+
requestId: requestId("discovery"),
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
expect(mockInit).toHaveBeenCalledOnce();
|
|
129
|
+
expect(mockDelegate.discoverDataSources).toHaveBeenCalledOnce();
|
|
130
|
+
});
|
|
131
|
+
|
|
117
132
|
it("should only call init once across multiple requests", async () => {
|
|
118
133
|
const lazyRequests = createLazyRequests(
|
|
119
134
|
mockDelegate,
|
|
@@ -130,6 +130,20 @@ describe("createNetworkRequests", () => {
|
|
|
130
130
|
expect(mockClient.GET).toHaveBeenCalledWith("/api/export/availability");
|
|
131
131
|
});
|
|
132
132
|
|
|
133
|
+
it("discoverDataSources should POST to the discovery endpoint", async () => {
|
|
134
|
+
const requests = createNetworkRequests();
|
|
135
|
+
const request = { requestId: "discovery-request" } as any;
|
|
136
|
+
await requests.discoverDataSources(request);
|
|
137
|
+
|
|
138
|
+
expect(mockClient.POST).toHaveBeenCalledWith(
|
|
139
|
+
"/api/datasources/discover",
|
|
140
|
+
expect.objectContaining({
|
|
141
|
+
body: request,
|
|
142
|
+
params: expect.anything(),
|
|
143
|
+
}),
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
|
|
133
147
|
it("getPackageList should not require a kernel connection", async () => {
|
|
134
148
|
const { waitForConnectionOpen, waitForConnectionOpenIfNotebook } =
|
|
135
149
|
await import("../connection");
|
|
@@ -51,6 +51,7 @@ const ACTIONS: Record<keyof AllRequests, Action> = {
|
|
|
51
51
|
sendRunScratchpad: "startConnection",
|
|
52
52
|
saveAppConfig: "startConnection",
|
|
53
53
|
saveCellConfig: "startConnection",
|
|
54
|
+
discoverDataSources: "startConnection",
|
|
54
55
|
|
|
55
56
|
// Export operations start a connection
|
|
56
57
|
exportAsHTML: "startConnection",
|
|
@@ -267,6 +267,14 @@ export function createNetworkRequests(): EditRequests & RunRequests {
|
|
|
267
267
|
})
|
|
268
268
|
.then(handleResponseReturnNull);
|
|
269
269
|
},
|
|
270
|
+
discoverDataSources: (request) => {
|
|
271
|
+
return getClient()
|
|
272
|
+
.POST("/api/datasources/discover", {
|
|
273
|
+
body: request,
|
|
274
|
+
params: getParams(),
|
|
275
|
+
})
|
|
276
|
+
.then(handleResponseReturnNull);
|
|
277
|
+
},
|
|
270
278
|
validateSQL: (request) => {
|
|
271
279
|
return getClient()
|
|
272
280
|
.POST("/api/sql/validate", {
|
|
@@ -62,6 +62,7 @@ export function createStaticRequests(): EditRequests & RunRequests {
|
|
|
62
62
|
previewSQLTableList: throwNotInEditMode,
|
|
63
63
|
previewSQLSchemaList: throwNotInEditMode,
|
|
64
64
|
previewDataSourceConnection: throwNotInEditMode,
|
|
65
|
+
discoverDataSources: throwNotInEditMode,
|
|
65
66
|
validateSQL: throwNotInEditMode,
|
|
66
67
|
openFile: throwNotInEditMode,
|
|
67
68
|
getUsageStats: throwNotInEditMode,
|
|
@@ -43,6 +43,7 @@ export function createErrorToastingRequests(
|
|
|
43
43
|
previewSQLTableList: "Failed to fetch SQL table list",
|
|
44
44
|
previewSQLSchemaList: "Failed to fetch SQL schema list",
|
|
45
45
|
previewDataSourceConnection: "Failed to preview data source connection",
|
|
46
|
+
discoverDataSources: "Failed to discover data sources",
|
|
46
47
|
validateSQL: "Failed to validate SQL",
|
|
47
48
|
openFile: "Failed to open file",
|
|
48
49
|
getUsageStats: "", // No toast
|
|
@@ -75,6 +75,7 @@ export type ListSQLTablesRequest = schemas["ListSQLTablesRequest"];
|
|
|
75
75
|
export type ListSQLSchemasRequest = schemas["ListSQLSchemasRequest"];
|
|
76
76
|
export type ListDataSourceConnectionRequest =
|
|
77
77
|
schemas["ListDataSourceConnectionRequest"];
|
|
78
|
+
export type DiscoverDataSourcesRequest = schemas["DiscoverDataSourcesRequest"];
|
|
78
79
|
export type ValidateSQLRequest = schemas["ValidateSQLRequest"];
|
|
79
80
|
export type DebugCellRequest = schemas["DebugCellRequest"];
|
|
80
81
|
export type SetBreakpointsRequest = schemas["SetBreakpointsRequest"];
|
|
@@ -181,6 +182,7 @@ export interface EditRequests {
|
|
|
181
182
|
previewDataSourceConnection: (
|
|
182
183
|
request: ListDataSourceConnectionRequest,
|
|
183
184
|
) => Promise<null>;
|
|
185
|
+
discoverDataSources: (request: DiscoverDataSourcesRequest) => Promise<null>;
|
|
184
186
|
validateSQL: (request: ValidateSQLRequest) => Promise<null>;
|
|
185
187
|
openFile: (request: { path: string; lineNumber?: number }) => Promise<null>;
|
|
186
188
|
getUsageStats: () => Promise<UsageResponse>;
|
package/src/core/wasm/bridge.ts
CHANGED
|
@@ -630,6 +630,16 @@ export class PyodideBridge implements RunRequests, EditRequests {
|
|
|
630
630
|
return null;
|
|
631
631
|
};
|
|
632
632
|
|
|
633
|
+
discoverDataSources: EditRequests["discoverDataSources"] = async (
|
|
634
|
+
request,
|
|
635
|
+
) => {
|
|
636
|
+
await this.putControlRequest({
|
|
637
|
+
type: "discover-data-sources",
|
|
638
|
+
...request,
|
|
639
|
+
});
|
|
640
|
+
return null;
|
|
641
|
+
};
|
|
642
|
+
|
|
633
643
|
getUsageStats = throwNotImplemented;
|
|
634
644
|
getEnvironmentInfo: EditRequests["getEnvironmentInfo"] = async () => {
|
|
635
645
|
const response = await this.rpc.proxy.request.bridge({
|
|
@@ -42,6 +42,7 @@ import { connectionTransportTypeAtom, useSetAppConfig } from "../config/config";
|
|
|
42
42
|
import { useDataSourceActions } from "../datasets/data-source-connections";
|
|
43
43
|
import type { ConnectionName } from "../datasets/engines";
|
|
44
44
|
import {
|
|
45
|
+
DiscoverDataSources,
|
|
45
46
|
PreviewSQLSchemaList,
|
|
46
47
|
PreviewSQLTable,
|
|
47
48
|
PreviewSQLTableList,
|
|
@@ -394,6 +395,9 @@ export function useMarimoKernelConnection(opts: {
|
|
|
394
395
|
case "validate-sql-result":
|
|
395
396
|
ValidateSQL.resolve(msg.data.request_id as RequestId, msg.data);
|
|
396
397
|
return;
|
|
398
|
+
case "data-source-discovery-result":
|
|
399
|
+
DiscoverDataSources.resolve(msg.data.request_id as RequestId, msg.data);
|
|
400
|
+
return;
|
|
397
401
|
case "secret-keys-result":
|
|
398
402
|
SECRETS_REGISTRY.resolve(msg.data.request_id, msg.data);
|
|
399
403
|
return;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { DiscoverDataSources } from "@/core/datasets/request-registry";
|
|
5
|
+
import { loadDataSourceDiscovery } from "../useDataSourceDiscovery";
|
|
6
|
+
|
|
7
|
+
describe("loadDataSourceDiscovery", () => {
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
vi.restoreAllMocks();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("returns the kernel's secret-free discovery model", async () => {
|
|
13
|
+
const sources = [
|
|
14
|
+
{
|
|
15
|
+
id: "pyiceberg-prod",
|
|
16
|
+
integration: "pyiceberg",
|
|
17
|
+
category: "catalog" as const,
|
|
18
|
+
displayName: "PyIceberg (prod)",
|
|
19
|
+
confidence: "high" as const,
|
|
20
|
+
origins: [
|
|
21
|
+
{
|
|
22
|
+
type: "configuration" as const,
|
|
23
|
+
label: "Resolved PyIceberg configuration",
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
configuration: [
|
|
27
|
+
{
|
|
28
|
+
field: "Catalog",
|
|
29
|
+
value: {
|
|
30
|
+
kind: "safe-literal" as const,
|
|
31
|
+
value: "prod",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
code: 'catalog = load_catalog("prod")',
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
const request = vi.spyOn(DiscoverDataSources, "request").mockResolvedValue({
|
|
39
|
+
request_id: "request-id",
|
|
40
|
+
sources,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const detected = await loadDataSourceDiscovery();
|
|
44
|
+
|
|
45
|
+
expect({ detected, requests: request.mock.calls }).toMatchInlineSnapshot(`
|
|
46
|
+
{
|
|
47
|
+
"detected": [
|
|
48
|
+
{
|
|
49
|
+
"category": "catalog",
|
|
50
|
+
"code": "catalog = load_catalog("prod")",
|
|
51
|
+
"confidence": "high",
|
|
52
|
+
"configuration": [
|
|
53
|
+
{
|
|
54
|
+
"field": "Catalog",
|
|
55
|
+
"value": {
|
|
56
|
+
"kind": "safe-literal",
|
|
57
|
+
"value": "prod",
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
"displayName": "PyIceberg (prod)",
|
|
62
|
+
"id": "pyiceberg-prod",
|
|
63
|
+
"integration": "pyiceberg",
|
|
64
|
+
"origins": [
|
|
65
|
+
{
|
|
66
|
+
"label": "Resolved PyIceberg configuration",
|
|
67
|
+
"type": "configuration",
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
"requests": [
|
|
73
|
+
[
|
|
74
|
+
{},
|
|
75
|
+
],
|
|
76
|
+
],
|
|
77
|
+
}
|
|
78
|
+
`);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type { DetectedDataSource } from "@/core/datasets/data-source-discovery";
|
|
4
|
+
import { DiscoverDataSources } from "@/core/datasets/request-registry";
|
|
5
|
+
import { useAsyncData } from "./useAsyncData";
|
|
6
|
+
|
|
7
|
+
export async function loadDataSourceDiscovery(): Promise<DetectedDataSource[]> {
|
|
8
|
+
const result = await DiscoverDataSources.request({});
|
|
9
|
+
return result.sources;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Reusable UI-facing hook for kernel-managed datasource discovery.
|
|
14
|
+
* Consumers decide how to render, filter, or act on the detected model.
|
|
15
|
+
*/
|
|
16
|
+
export function useDataSourceDiscovery() {
|
|
17
|
+
return useAsyncData(loadDataSourceDiscovery, []);
|
|
18
|
+
}
|