@marimo-team/islands 0.24.1-dev3 → 0.24.1-dev31

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 (72) hide show
  1. package/dist/{ConnectedDataExplorerComponent-BUC1MrsF.js → ConnectedDataExplorerComponent-yEsovh7C.js} +4 -4
  2. package/dist/arrays-Ct6XviUd.js +59 -0
  3. package/dist/{capabilities-BilXeZ7p.js → capabilities-DNKeBoT5.js} +1 -1
  4. package/dist/{chat-ui-ChBRy1iT.js → chat-ui-ChbH2I73.js} +3 -3
  5. package/dist/{common-BvTQu-LJ.js → common-ZoATHjIZ.js} +17 -11
  6. package/dist/{formats-Dz0QNUEa.js → formats-BZtMpeQe.js} +1 -1
  7. package/dist/{glide-data-editor-BI2i_IEE.js → glide-data-editor-aimC_hok.js} +3 -1
  8. package/dist/{html-to-image-BtNr4zrO.js → html-to-image-BzT-fb14.js} +2357 -2325
  9. package/dist/{input-BRE2a7Ay.js → input-CqV2ZPgL.js} +1 -1
  10. package/dist/{loader-DTnbNSVZ.js → loader-Chm6nOqy.js} +1 -1
  11. package/dist/main.js +1196 -1088
  12. package/dist/{once-Boutk_9X.js → once-D1_qYl2e.js} +1 -1
  13. package/dist/{process-output-Dch8KCi8.js → process-output-C8Q8G6-g.js} +1 -1
  14. package/dist/{react-vega-zyfTF9SY.js → react-vega-BE2Uv1cq.js} +1 -1
  15. package/dist/{reveal-component-qNkzuPp3.js → reveal-component-CBpmFHpU.js} +5 -5
  16. package/dist/{spec-BL1GSUUF.js → spec-DR1CyTOo.js} +2 -0
  17. package/dist/style.css +1 -1
  18. package/dist/{swiper-component-Cx0cRWLk.js → swiper-component-UYicacis.js} +1 -1
  19. package/dist/{useIframeCapabilities-CxuXXwjj.js → useIframeCapabilities-BbnoLKxG.js} +1 -1
  20. package/dist/{vega-component-CioKuDut.js → vega-component-xrHju6oo.js} +5 -5
  21. package/dist/{vega-embed-container-BRjjqRGz.js → vega-embed-container-B1tc-DoN.js} +2 -2
  22. package/dist/{vega-loader.browser-CGb-2Kwi.js → vega-loader.browser-VFQfHaoV.js} +1 -1
  23. package/package.json +1 -1
  24. package/src/components/app-config/optional-features.test.ts +78 -0
  25. package/src/components/app-config/optional-features.tsx +33 -6
  26. package/src/components/data-table/__tests__/column_formatting.test.ts +7 -0
  27. package/src/components/data-table/charts/__tests__/schemas.test.ts +17 -2
  28. package/src/components/data-table/charts/chart-spec/types.ts +2 -0
  29. package/src/components/data-table/column-formatting/feature.ts +1 -0
  30. package/src/components/data-table/column-formatting/types.ts +1 -0
  31. package/src/components/data-table/column-summary/chart-spec-model.tsx +1 -0
  32. package/src/components/data-table/column-summary/column-summary.tsx +1 -0
  33. package/src/components/data-table/columns.tsx +4 -3
  34. package/src/components/datasets/__tests__/icons.test.ts +10 -2
  35. package/src/components/datasets/icons.tsx +4 -0
  36. package/src/components/datasources/__tests__/components.test.tsx +14 -1
  37. package/src/components/datasources/datasources.tsx +2 -40
  38. package/src/components/editor/chrome/panels/components.tsx +35 -0
  39. package/src/components/editor/chrome/panels/file-explorer-panel.tsx +10 -0
  40. package/src/components/editor/chrome/panels/session-panel.tsx +12 -4
  41. package/src/components/editor/connections/__tests__/quick-add-data-sources.test.tsx +2 -0
  42. package/src/components/editor/connections/add-connection-dialog.tsx +5 -1
  43. package/src/components/editor/connections/quick-add-data-sources.tsx +11 -7
  44. package/src/components/editor/connections/storage/__tests__/__snapshots__/as-code.test.ts.snap +43 -0
  45. package/src/components/editor/connections/storage/__tests__/as-code.test.ts +127 -0
  46. package/src/components/editor/connections/storage/add-storage-form.tsx +11 -1
  47. package/src/components/editor/connections/storage/as-code.ts +30 -1
  48. package/src/components/editor/connections/storage/schemas.ts +63 -0
  49. package/src/core/codemirror/code-lens/__tests__/popover.test.ts +13 -0
  50. package/src/core/codemirror/code-lens/popover.tsx +11 -5
  51. package/src/core/codemirror/language/languages/sql/__tests__/renderers.test.tsx +15 -4
  52. package/src/core/codemirror/language/languages/sql/renderers.tsx +1 -0
  53. package/src/core/datasets/__tests__/data-source-discovery.test.ts +277 -0
  54. package/src/core/datasets/data-source-connections.ts +36 -0
  55. package/src/core/datasets/data-source-discovery.ts +92 -0
  56. package/src/core/kernel/messages.ts +1 -0
  57. package/src/core/websocket/useMarimoKernelConnection.tsx +11 -3
  58. package/src/hooks/useDataSourceDiscovery.ts +50 -9
  59. package/src/plugins/impl/MatrixPlugin.tsx +256 -35
  60. package/src/plugins/impl/__tests__/DataEditorPlugin.test.ts +13 -1
  61. package/src/plugins/impl/__tests__/MatrixPlugin.test.tsx +540 -3
  62. package/src/plugins/impl/data-editor/glide-data-editor.tsx +1 -0
  63. package/src/plugins/impl/data-editor/glide-utils.ts +1 -0
  64. package/src/plugins/impl/data-frames/__tests__/schema.test.ts +9 -1
  65. package/src/plugins/impl/data-frames/utils/__tests__/operators.test.ts +8 -0
  66. package/src/plugins/impl/data-frames/utils/operators.ts +1 -1
  67. package/src/plugins/impl/matplotlib/__tests__/matplotlib-renderer.test.ts +184 -24
  68. package/src/plugins/impl/matplotlib/matplotlib-renderer.ts +40 -7
  69. package/src/plugins/impl/vega/__tests__/vega.test.ts +22 -4
  70. package/src/plugins/impl/vega/utils.ts +3 -0
  71. package/dist/arrays-C56grfjL.js +0 -55
  72. package/src/hooks/__tests__/useDataSourceDiscovery.test.ts +0 -80
@@ -190,6 +190,62 @@ describe("isInAxes", () => {
190
190
  });
191
191
  });
192
192
 
193
+ // A 100x100 chart whose axes fill the whole figure.
194
+ function createState(): MatplotlibState {
195
+ return {
196
+ ...LINEAR_AXES,
197
+ axesPixelBounds: [0, 0, 100, 100],
198
+ yBounds: [0, 10],
199
+ chartBase64: "first",
200
+ width: 100,
201
+ height: 100,
202
+ selectionColor: "blue",
203
+ selectionOpacity: 0.15,
204
+ strokeWidth: 2,
205
+ debounce: false,
206
+ value: { has_selection: false },
207
+ setValue: vi.fn(),
208
+ };
209
+ }
210
+
211
+ // jsdom has no matchMedia, no animation frames and no canvas context.
212
+ function stubBrowser(ctx: Partial<CanvasRenderingContext2D> | null = null) {
213
+ vi.stubGlobal(
214
+ "matchMedia",
215
+ vi.fn(() => ({ addEventListener: vi.fn() })),
216
+ );
217
+ const requestAnimationFrame = vi.fn(() => 1);
218
+ vi.stubGlobal("requestAnimationFrame", requestAnimationFrame);
219
+ vi.stubGlobal("cancelAnimationFrame", vi.fn());
220
+ vi.spyOn(HTMLCanvasElement.prototype, "getContext").mockReturnValue(
221
+ ctx as CanvasRenderingContext2D | null,
222
+ );
223
+ return { requestAnimationFrame };
224
+ }
225
+
226
+ // jsdom does not fetch images; resolve them synchronously instead.
227
+ function stubSyncImage() {
228
+ vi.stubGlobal(
229
+ "Image",
230
+ class {
231
+ onload: (() => void) | null = null;
232
+ set src(_value: string) {
233
+ this.onload?.();
234
+ }
235
+ },
236
+ );
237
+ }
238
+
239
+ function createRecordingContext() {
240
+ return {
241
+ setTransform: vi.fn(),
242
+ clearRect: vi.fn(),
243
+ drawImage: vi.fn(),
244
+ save: vi.fn(),
245
+ restore: vi.fn(),
246
+ };
247
+ }
248
+
193
249
  describe("MatplotlibRenderer", () => {
194
250
  afterEach(() => {
195
251
  vi.unstubAllGlobals();
@@ -197,36 +253,14 @@ describe("MatplotlibRenderer", () => {
197
253
  });
198
254
 
199
255
  it("restores a new selection when the chart changes", () => {
200
- vi.stubGlobal(
201
- "matchMedia",
202
- vi.fn(() => ({ addEventListener: vi.fn() })),
203
- );
204
- vi.stubGlobal(
205
- "requestAnimationFrame",
206
- vi.fn(() => 1),
207
- );
208
- vi.stubGlobal("cancelAnimationFrame", vi.fn());
209
- vi.spyOn(HTMLCanvasElement.prototype, "getContext").mockReturnValue(null);
256
+ stubBrowser();
210
257
 
211
258
  const value = {
212
259
  type: "box",
213
260
  has_selection: true,
214
261
  data: { x_min: 2, x_max: 4, y_min: 2, y_max: 4 },
215
262
  } as const;
216
- const state: MatplotlibState = {
217
- ...LINEAR_AXES,
218
- axesPixelBounds: [0, 0, 100, 100],
219
- yBounds: [0, 10],
220
- chartBase64: "first",
221
- width: 100,
222
- height: 100,
223
- selectionColor: "blue",
224
- selectionOpacity: 0.15,
225
- strokeWidth: 2,
226
- debounce: false,
227
- value: { has_selection: false },
228
- setValue: vi.fn(),
229
- };
263
+ const state = createState();
230
264
  const container = document.createElement("div");
231
265
  const controller = new AbortController();
232
266
  const renderer = new MatplotlibRenderer(container, {
@@ -252,4 +286,130 @@ describe("MatplotlibRenderer", () => {
252
286
  expect(canvas.style.cursor).toBe("move");
253
287
  controller.abort();
254
288
  });
289
+
290
+ // Safari only fires resize on zoom, not the resolution query (#10625).
291
+ it("re-syncs the backing store on resize, without matchMedia firing", () => {
292
+ stubBrowser();
293
+ vi.stubGlobal("devicePixelRatio", 2);
294
+
295
+ const container = document.createElement("div");
296
+ const controller = new AbortController();
297
+ new MatplotlibRenderer(container, {
298
+ state: createState(),
299
+ signal: controller.signal,
300
+ });
301
+ const canvas = container.querySelector("canvas");
302
+ expect(canvas?.width).toBe(200);
303
+
304
+ vi.stubGlobal("devicePixelRatio", 1);
305
+ window.dispatchEvent(new Event("resize"));
306
+
307
+ expect(canvas?.width).toBe(100);
308
+ controller.abort();
309
+ });
310
+
311
+ // Re-sizing clears the buffer a frame before the repaint, so an over-eager
312
+ // guard flickers. 700 * 1.1 is the case a truncating comparison misses.
313
+ it("leaves the backing store alone when resize fires at an unchanged DPR", () => {
314
+ const { requestAnimationFrame } = stubBrowser();
315
+ vi.stubGlobal("devicePixelRatio", 1.1);
316
+
317
+ const container = document.createElement("div");
318
+ const controller = new AbortController();
319
+ new MatplotlibRenderer(container, {
320
+ state: { ...createState(), width: 700 },
321
+ signal: controller.signal,
322
+ });
323
+ const canvas = container.querySelector("canvas");
324
+ expect(canvas?.width).toBe(770);
325
+
326
+ requestAnimationFrame.mockClear();
327
+ window.dispatchEvent(new Event("resize"));
328
+
329
+ expect(canvas?.width).toBe(770);
330
+ expect(requestAnimationFrame).not.toHaveBeenCalled();
331
+ controller.abort();
332
+ });
333
+
334
+ it("draws with the backing-store scale, not the current devicePixelRatio", () => {
335
+ const ctx = createRecordingContext();
336
+ stubBrowser(ctx);
337
+ stubSyncImage();
338
+ vi.stubGlobal("devicePixelRatio", 2);
339
+
340
+ const state = createState();
341
+ const container = document.createElement("div");
342
+ const controller = new AbortController();
343
+ const renderer = new MatplotlibRenderer(container, {
344
+ state,
345
+ signal: controller.signal,
346
+ });
347
+ expect(container.querySelector("canvas")?.width).toBe(200);
348
+
349
+ // Zoom with nothing re-syncing the buffer: it still holds the old DPR, so
350
+ // the redraw must too, or the image lands scaled by dpr_new / dpr_old.
351
+ vi.stubGlobal("devicePixelRatio", 1);
352
+ ctx.setTransform.mockClear();
353
+ renderer.update({ ...state, strokeWidth: 4 });
354
+
355
+ expect(ctx.setTransform.mock.calls).toEqual([[2, 0, 0, 2, 0, 0]]);
356
+ expect(ctx.drawImage).toHaveBeenLastCalledWith(
357
+ expect.anything(),
358
+ 0,
359
+ 0,
360
+ 100,
361
+ 100,
362
+ );
363
+ controller.abort();
364
+ });
365
+
366
+ it("clears the whole backing store when a re-run swaps the chart", () => {
367
+ const ctx = createRecordingContext();
368
+ stubBrowser(ctx);
369
+ vi.stubGlobal("devicePixelRatio", 2);
370
+
371
+ const state = createState();
372
+ const container = document.createElement("div");
373
+ const controller = new AbortController();
374
+ const renderer = new MatplotlibRenderer(container, {
375
+ state,
376
+ signal: controller.signal,
377
+ });
378
+
379
+ vi.stubGlobal("devicePixelRatio", 1);
380
+ ctx.setTransform.mockClear();
381
+ ctx.clearRect.mockClear();
382
+ renderer.update({ ...state, chartBase64: "second" });
383
+
384
+ // A stale scale here would leave the outer 3/4 of the buffer unwiped.
385
+ expect(ctx.setTransform.mock.calls).toEqual([[2, 0, 0, 2, 0, 0]]);
386
+ expect(ctx.clearRect.mock.calls).toEqual([[0, 0, 100, 100]]);
387
+ controller.abort();
388
+ });
389
+
390
+ it("scales each axis by its own backing-store ratio", () => {
391
+ const ctx = createRecordingContext();
392
+ stubBrowser(ctx);
393
+ stubSyncImage();
394
+ // canvas.width/height are integers, so this truncates the two dimensions
395
+ // by different fractions: 150 * 1.25 -> 187 (0.5 lost), 500 * 1.25 -> 625
396
+ // (exact). One shared, width-derived scale would under-cover the buffer
397
+ // vertically and leave its bottom rows unwiped.
398
+ vi.stubGlobal("devicePixelRatio", 1.25);
399
+
400
+ const state = { ...createState(), width: 150, height: 500 };
401
+ const container = document.createElement("div");
402
+ const controller = new AbortController();
403
+ new MatplotlibRenderer(container, { state, signal: controller.signal });
404
+
405
+ const canvas = container.querySelector("canvas");
406
+ expect(canvas?.width).toBe(187);
407
+ expect(canvas?.height).toBe(625);
408
+ // The pre-load clear and the draw each set up their own transform.
409
+ const transform = [187 / 150, 0, 0, 625 / 500, 0, 0];
410
+ const rect = [0, 0, 150, 500];
411
+ expect(ctx.setTransform.mock.calls).toEqual([transform, transform]);
412
+ expect(ctx.clearRect.mock.calls).toEqual([rect, rect]);
413
+ controller.abort();
414
+ });
255
415
  });
@@ -281,6 +281,8 @@ export class MatplotlibRenderer {
281
281
  #image: HTMLImageElement | null = null;
282
282
  #imageGeneration = 0;
283
283
  #currentChartBase64 = "";
284
+ /** The devicePixelRatio the canvas buffer was last sized with. */
285
+ #backingDpr = 1;
284
286
 
285
287
  constructor(
286
288
  container: HTMLDivElement,
@@ -317,8 +319,12 @@ export class MatplotlibRenderer {
317
319
  });
318
320
 
319
321
  // Watch for devicePixelRatio changes (e.g. browser zoom, moving between
320
- // displays). matchMedia fires exactly once per DPR transition.
322
+ // displays). matchMedia fires exactly once per DPR transition, but Safari
323
+ // doesn't re-evaluate the query on zoom — it only fires resize (#10625).
321
324
  this.#watchDevicePixelRatio(options.signal);
325
+ window.addEventListener("resize", this.#syncForDevicePixelRatio, {
326
+ signal: options.signal,
327
+ });
322
328
 
323
329
  // Clean up on abort
324
330
  options.signal.addEventListener("abort", () => {
@@ -334,6 +340,7 @@ export class MatplotlibRenderer {
334
340
  /** Set the canvas buffer + CSS size to match current logical size and DPR. */
335
341
  #syncCanvasSize(canvas: HTMLCanvasElement = this.#canvas): void {
336
342
  const dpr = globalThis.devicePixelRatio ?? 1;
343
+ this.#backingDpr = dpr;
337
344
  const { width, height } = this.#state;
338
345
  canvas.width = width * dpr;
339
346
  canvas.height = height * dpr;
@@ -343,6 +350,33 @@ export class MatplotlibRenderer {
343
350
  canvas.style.aspectRatio = `${width} / ${height}`;
344
351
  }
345
352
 
353
+ /**
354
+ * Scale to draw at, read back from the buffer rather than from
355
+ * `devicePixelRatio`, so sizing and drawing cannot disagree (#10625).
356
+ * Per-axis: `canvas.width`/`height` are integers, so a fractional DPR
357
+ * truncates each dimension by a different fraction of a device pixel.
358
+ */
359
+ get #backingScale(): { x: number; y: number } {
360
+ const { width, height } = this.#state;
361
+ const dpr = globalThis.devicePixelRatio ?? 1;
362
+ return {
363
+ x: width > 0 ? this.#canvas.width / width : dpr,
364
+ y: height > 0 ? this.#canvas.height / height : dpr,
365
+ };
366
+ }
367
+
368
+ /**
369
+ * Re-size the buffer when the DPR changed. resize fires far more often than
370
+ * the DPR moves, and `canvas.width` truncates, so compare ratios directly.
371
+ */
372
+ #syncForDevicePixelRatio = (): void => {
373
+ if ((globalThis.devicePixelRatio ?? 1) === this.#backingDpr) {
374
+ return;
375
+ }
376
+ this.#syncCanvasSize();
377
+ this.#scheduleRedraw();
378
+ };
379
+
346
380
  /**
347
381
  * Observe devicePixelRatio changes via matchMedia. Each listener fires once
348
382
  * per transition, so we re-register after every change.
@@ -355,8 +389,7 @@ export class MatplotlibRenderer {
355
389
  `(resolution: ${globalThis.devicePixelRatio ?? 1}dppx)`,
356
390
  );
357
391
  const onChange = () => {
358
- this.#syncCanvasSize();
359
- this.#drawCanvas();
392
+ this.#syncForDevicePixelRatio();
360
393
  // Re-register for the next DPR transition
361
394
  this.#watchDevicePixelRatio(signal);
362
395
  };
@@ -416,8 +449,8 @@ export class MatplotlibRenderer {
416
449
  this.#image = null;
417
450
  const ctx = this.#canvas.getContext("2d");
418
451
  if (ctx) {
419
- const dpr = globalThis.devicePixelRatio ?? 1;
420
- ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
452
+ const scale = this.#backingScale;
453
+ ctx.setTransform(scale.x, 0, 0, scale.y, 0, 0);
421
454
  ctx.clearRect(0, 0, this.#state.width, this.#state.height);
422
455
  }
423
456
 
@@ -447,8 +480,8 @@ export class MatplotlibRenderer {
447
480
  const ix = this.#interaction;
448
481
 
449
482
  // Scale for HiDPI: all coordinates remain in logical pixels
450
- const dpr = globalThis.devicePixelRatio ?? 1;
451
- ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
483
+ const scale = this.#backingScale;
484
+ ctx.setTransform(scale.x, 0, 0, scale.y, 0, 0);
452
485
 
453
486
  // Clear and draw the base image
454
487
  ctx.clearRect(0, 0, s.width, s.height);
@@ -7,6 +7,7 @@ import {
7
7
  vegaLoadData,
8
8
  vegaLoader,
9
9
  } from "../loader";
10
+ import { getVegaFieldTypes } from "../utils";
10
11
 
11
12
  const { ZERO_WIDTH_SPACE, replacePeriodsInColumnNames, uniquifyColumnNames } =
12
13
  exportedForTesting;
@@ -102,15 +103,32 @@ yield_error,yield_center
102
103
  `);
103
104
  });
104
105
 
105
- it.each(DATA_TYPES)("should handle %s data-type", async (type) => {
106
- const csvData = "name,value_column\nAlice,1";
106
+ it.each(DATA_TYPES.filter((type) => type !== "geometry"))(
107
+ "should handle %s data-type",
108
+ async (type) => {
109
+ const csvData = "name,value_column\nAlice,1";
110
+ vi.spyOn(vegaLoader, "load").mockReturnValue(Promise.resolve(csvData));
111
+
112
+ const result = await vegaLoadData(csvData, {
113
+ type: "csv",
114
+ parse: { value_column: type },
115
+ });
116
+ expect(result.length).toEqual(1);
117
+ },
118
+ );
119
+
120
+ it("should parse geometry as a WKT string", async () => {
121
+ const csvData = "name,value_column\nAlice,POINT (0 0)";
107
122
  vi.spyOn(vegaLoader, "load").mockReturnValue(Promise.resolve(csvData));
108
123
 
124
+ const fieldTypes = getVegaFieldTypes({ value_column: "geometry" });
125
+ expect(fieldTypes).toEqual({ value_column: "string" });
126
+
109
127
  const result = await vegaLoadData(csvData, {
110
128
  type: "csv",
111
- parse: { value_column: type },
129
+ parse: fieldTypes,
112
130
  });
113
- expect(result.length).toEqual(1);
131
+ expect(result).toEqual([{ name: "Alice", value_column: "POINT (0 0)" }]);
114
132
  });
115
133
 
116
134
  it("should parse csv data with out of bound integers", async () => {
@@ -55,6 +55,9 @@ export function getVegaFieldTypes(
55
55
  // the correct formatting. For example, a date like '2024-01-01' will
56
56
  // be formatted to '2024-01-01T00:00:00.000Z'.
57
57
  return Objects.mapValues(types, (type): VegaDataType => {
58
+ if (type === "geometry") {
59
+ return "string";
60
+ }
58
61
  if (type === "date") {
59
62
  return "string";
60
63
  }
@@ -1,55 +0,0 @@
1
- import { t as invariant } from "./invariant-hOqqBz8A.js";
2
- function clamp(e, o, s) {
3
- return Math.min(Math.max(e, o), s);
4
- }
5
- function arrayDelete(e, o) {
6
- let s = [...e];
7
- return s.splice(o, 1), s;
8
- }
9
- function arrayInsert(e, s, c) {
10
- s = clamp(s, 0, e.length);
11
- let l = [...e];
12
- return l.splice(s, 0, c), l;
13
- }
14
- function arrayMove(e, o, s) {
15
- let c = [...e], [l] = c.splice(o, 1);
16
- return c.splice(s, 0, l), c;
17
- }
18
- function arrayInsertMany(e, s, c) {
19
- if (e.length === 0) return c;
20
- s = clamp(s, 0, e.length);
21
- let l = [...e];
22
- return l.splice(s, 0, ...c), l;
23
- }
24
- function arrayShallowEquals(e, o) {
25
- if (e === o) return true;
26
- if (e.length !== o.length) return false;
27
- for (let s = 0, c = e.length; s < c; s++) if (e[s] !== o[s]) return false;
28
- return true;
29
- }
30
- const Arrays = {
31
- EMPTY: [],
32
- zip: (o, s) => (invariant(o.length === s.length, "Arrays must be the same length"), o.map((e, o2) => [e, s[o2]]))
33
- };
34
- function uniq(e) {
35
- return [...new Set(e)];
36
- }
37
- function uniqueBy(e, o) {
38
- let s = [], c = /* @__PURE__ */ new Set();
39
- for (let l of e) {
40
- let e2 = o(l);
41
- c.has(e2) || (c.add(e2), s.push(l));
42
- }
43
- return s;
44
- }
45
- export {
46
- arrayMove as a,
47
- uniqueBy as c,
48
- arrayInsertMany as i,
49
- clamp as l,
50
- arrayDelete as n,
51
- arrayShallowEquals as o,
52
- arrayInsert as r,
53
- uniq as s,
54
- Arrays as t
55
- };
@@ -1,80 +0,0 @@
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
- });