@squadbase/vite-server 0.1.12-dev.a9ac647 → 0.1.17-dev.3b633bb

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 (77) hide show
  1. package/dist/cli/index.js +14375 -1652
  2. package/dist/connectors/airtable-oauth.js +282 -46
  3. package/dist/connectors/airtable.js +319 -51
  4. package/dist/connectors/amplitude.js +322 -47
  5. package/dist/connectors/anthropic.js +135 -47
  6. package/dist/connectors/asana.js +327 -49
  7. package/dist/connectors/attio.js +302 -49
  8. package/dist/connectors/aws-billing.js +287 -46
  9. package/dist/connectors/azure-sql.js +421 -102
  10. package/dist/connectors/backlog-api-key.js +317 -47
  11. package/dist/connectors/clickup.js +338 -49
  12. package/dist/connectors/cosmosdb.js +305 -50
  13. package/dist/connectors/customerio.js +319 -47
  14. package/dist/connectors/dbt.js +340 -47
  15. package/dist/connectors/freshdesk.js +342 -53
  16. package/dist/connectors/freshsales.js +333 -52
  17. package/dist/connectors/freshservice.js +361 -53
  18. package/dist/connectors/gamma.js +327 -52
  19. package/dist/connectors/gemini.js +134 -47
  20. package/dist/connectors/github.js +386 -49
  21. package/dist/connectors/gmail-oauth.js +204 -7
  22. package/dist/connectors/gmail.js +350 -47
  23. package/dist/connectors/google-ads.js +288 -46
  24. package/dist/connectors/google-analytics-oauth.js +310 -46
  25. package/dist/connectors/google-analytics.js +547 -87
  26. package/dist/connectors/google-audit-log.js +438 -47
  27. package/dist/connectors/google-calendar-oauth.js +259 -46
  28. package/dist/connectors/google-calendar.js +359 -47
  29. package/dist/connectors/google-docs.js +220 -6
  30. package/dist/connectors/google-drive.js +262 -5
  31. package/dist/connectors/google-search-console-oauth.js +256 -46
  32. package/dist/connectors/google-sheets.js +272 -47
  33. package/dist/connectors/google-slides.js +205 -6
  34. package/dist/connectors/grafana.js +332 -49
  35. package/dist/connectors/hubspot-oauth.js +208 -5
  36. package/dist/connectors/hubspot.js +306 -49
  37. package/dist/connectors/influxdb.js +416 -51
  38. package/dist/connectors/intercom-oauth.js +210 -5
  39. package/dist/connectors/intercom.js +302 -49
  40. package/dist/connectors/jdbc.js +762 -110
  41. package/dist/connectors/jira-api-key.js +326 -47
  42. package/dist/connectors/kintone-api-token.js +281 -47
  43. package/dist/connectors/kintone.js +328 -47
  44. package/dist/connectors/linear.js +330 -49
  45. package/dist/connectors/linkedin-ads.js +268 -50
  46. package/dist/connectors/mailchimp-oauth.js +268 -46
  47. package/dist/connectors/mailchimp.js +320 -49
  48. package/dist/connectors/meta-ads-oauth.js +273 -48
  49. package/dist/connectors/meta-ads.js +285 -50
  50. package/dist/connectors/mixpanel.js +338 -47
  51. package/dist/connectors/monday.js +360 -49
  52. package/dist/connectors/mongodb.js +319 -57
  53. package/dist/connectors/notion-oauth.js +231 -5
  54. package/dist/connectors/notion.js +323 -51
  55. package/dist/connectors/openai.js +134 -47
  56. package/dist/connectors/oracle.js +454 -103
  57. package/dist/connectors/outlook-oauth.js +204 -5
  58. package/dist/connectors/powerbi-oauth.js +498 -5
  59. package/dist/connectors/salesforce.js +384 -49
  60. package/dist/connectors/semrush.js +609 -49
  61. package/dist/connectors/sentry.js +289 -50
  62. package/dist/connectors/shopify-oauth.js +187 -5
  63. package/dist/connectors/shopify.js +357 -47
  64. package/dist/connectors/sqlserver.js +415 -102
  65. package/dist/connectors/stripe-api-key.js +269 -46
  66. package/dist/connectors/stripe-oauth.js +202 -5
  67. package/dist/connectors/supabase.js +303 -48
  68. package/dist/connectors/tableau.js +536 -163
  69. package/dist/connectors/tiktok-ads.js +279 -48
  70. package/dist/connectors/wix-store.js +320 -49
  71. package/dist/connectors/zendesk-oauth.js +239 -5
  72. package/dist/connectors/zendesk.js +358 -47
  73. package/dist/index.d.ts +149 -1
  74. package/dist/index.js +15057 -2117
  75. package/dist/main.js +15005 -2073
  76. package/dist/vite-plugin.js +14752 -2019
  77. package/package.json +1 -1
@@ -113,6 +113,28 @@ var ConnectorPlugin = class _ConnectorPlugin {
113
113
  tools;
114
114
  query;
115
115
  checkConnection;
116
+ /**
117
+ * SQPD-1212: Logic-based, rule-driven connection setup. Connectors that
118
+ * implement this expose a step-by-step exploration flow (database/schema/
119
+ * table/etc. discovery) that the dashboard backend drives via the
120
+ * `/connections/:connectionId/setup` endpoint. Implement by delegating to
121
+ * `runSetupFlow` from `setup-flow.ts`.
122
+ */
123
+ setup;
124
+ /**
125
+ * Opt-out of the default "verify before save" behavior on connection
126
+ * creation. The backend invokes `checkConnection` synchronously while
127
+ * creating the connection and aborts (no row inserted) if it fails — this
128
+ * flag disables that for connectors where the check cannot succeed pre-save:
129
+ *
130
+ * - `squadbase-db` populates `connection-url` only after Neon provisioning
131
+ * - OAuth connectors require an OAuth-aware proxyFetch keyed by the
132
+ * connectionId, which doesn't exist until the row is saved
133
+ *
134
+ * Exceptions are the explicit position; new credential-input connectors get
135
+ * the default verify-on-create behavior without opt-in.
136
+ */
137
+ skipConnectionCheckOnCreate;
116
138
  constructor(config) {
117
139
  this.slug = config.slug;
118
140
  this.authType = config.authType;
@@ -129,6 +151,8 @@ var ConnectorPlugin = class _ConnectorPlugin {
129
151
  this.tools = config.tools;
130
152
  this.query = config.query;
131
153
  this.checkConnection = config.checkConnection;
154
+ this.setup = config.setup;
155
+ this.skipConnectionCheckOnCreate = config.skipConnectionCheckOnCreate;
132
156
  }
133
157
  get connectorKey() {
134
158
  return _ConnectorPlugin.deriveKey(this.slug, this.authType);
@@ -193,6 +217,76 @@ var ConnectorPlugin = class _ConnectorPlugin {
193
217
  }
194
218
  };
195
219
 
220
+ // ../connectors/src/setup-flow.ts
221
+ async function runSetupFlow(flow, params, ctx, config) {
222
+ const runtime = {
223
+ params,
224
+ language: ctx.language,
225
+ config
226
+ };
227
+ let state = flow.initialState();
228
+ let answerIdx = 0;
229
+ const pendingParameterUpdates = [];
230
+ for (const step of flow.steps) {
231
+ const ans = ctx.answers[answerIdx];
232
+ if (ans && ans.questionSlug === step.slug) {
233
+ state = step.applyAnswer(state, ans.answer);
234
+ if (step.toParameterUpdates) {
235
+ pendingParameterUpdates.push(...step.toParameterUpdates(state));
236
+ }
237
+ answerIdx += 1;
238
+ continue;
239
+ }
240
+ const resolvedAllowFreeText = step.allowFreeText !== void 0 ? step.allowFreeText : true;
241
+ if (step.type === "text") {
242
+ if (step.fetchOptions) {
243
+ const options2 = await step.fetchOptions(state, runtime);
244
+ if (options2.length === 0) {
245
+ continue;
246
+ }
247
+ }
248
+ return {
249
+ type: "nextQuestion",
250
+ questionSlug: step.slug,
251
+ question: step.question[ctx.language],
252
+ questionType: "text",
253
+ allowFreeText: resolvedAllowFreeText,
254
+ ...pendingParameterUpdates.length > 0 && {
255
+ parameterUpdates: pendingParameterUpdates
256
+ }
257
+ };
258
+ }
259
+ const options = step.fetchOptions ? await step.fetchOptions(state, runtime) : [];
260
+ if (options.length === 0) {
261
+ continue;
262
+ }
263
+ return {
264
+ type: "nextQuestion",
265
+ questionSlug: step.slug,
266
+ question: step.question[ctx.language],
267
+ questionType: step.type,
268
+ options,
269
+ allowFreeText: resolvedAllowFreeText,
270
+ ...pendingParameterUpdates.length > 0 && {
271
+ parameterUpdates: pendingParameterUpdates
272
+ }
273
+ };
274
+ }
275
+ const dataInvestigationResult = await flow.finalize(state, runtime);
276
+ return {
277
+ type: "fulfilled",
278
+ dataInvestigationResult,
279
+ ...pendingParameterUpdates.length > 0 && {
280
+ parameterUpdates: pendingParameterUpdates
281
+ }
282
+ };
283
+ }
284
+ async function resolveSetupSelection(params) {
285
+ const { selected, allSentinel, fetchAll, limit } = params;
286
+ const resolved = selected.includes(allSentinel) ? await fetchAll() : selected.filter((v) => v !== allSentinel);
287
+ return resolved.slice(0, limit);
288
+ }
289
+
196
290
  // ../connectors/src/auth-types.ts
197
291
  var AUTH_TYPES = {
198
292
  OAUTH: "oauth",
@@ -223,6 +317,104 @@ var googleDocsOnboarding = new ConnectorOnboarding({
223
317
  }
224
318
  });
225
319
 
320
+ // ../connectors/src/connectors/google-docs/utils.ts
321
+ async function googleApiFetch(config, url) {
322
+ const res = await config.proxyFetch(url, { method: "GET" });
323
+ if (!res.ok) {
324
+ const text = await res.text().catch(() => res.statusText);
325
+ throw new Error(`Google Docs ${url} failed: HTTP ${res.status} ${text}`);
326
+ }
327
+ return await res.json();
328
+ }
329
+
330
+ // ../connectors/src/connectors/google-docs/setup-flow.ts
331
+ var ALL_DOCUMENTS = "__ALL_DOCUMENTS__";
332
+ var GOOGLE_DOCS_SETUP_MAX_DOCUMENTS = 10;
333
+ var DRIVE_LIST_PAGE_SIZE = 50;
334
+ async function listDocuments(config) {
335
+ const q = encodeURIComponent(
336
+ "mimeType='application/vnd.google-apps.document' and trashed=false"
337
+ );
338
+ const url = `https://www.googleapis.com/drive/v3/files?q=${q}&pageSize=${DRIVE_LIST_PAGE_SIZE}&orderBy=modifiedTime%20desc&fields=files(id,name,modifiedTime)`;
339
+ const data = await googleApiFetch(config, url);
340
+ return data.files ?? [];
341
+ }
342
+ function extractHeadings(meta, max) {
343
+ const out = [];
344
+ for (const c of meta.body?.content ?? []) {
345
+ const p = c.paragraph;
346
+ if (!p) continue;
347
+ const style = p.paragraphStyle?.namedStyleType ?? "";
348
+ if (!style.startsWith("HEADING_")) continue;
349
+ const text = (p.elements ?? []).map((el) => el.textRun?.content ?? "").join("").trim();
350
+ if (text) out.push(`${style}: ${text}`);
351
+ if (out.length >= max) break;
352
+ }
353
+ return out;
354
+ }
355
+ var googleDocsSetupFlow = {
356
+ initialState: () => ({}),
357
+ steps: [
358
+ {
359
+ slug: "documents",
360
+ type: "multiSelect",
361
+ question: {
362
+ ja: "\u5BFE\u8C61\u306E\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044\uFF08\u8907\u6570\u9078\u629E\u53EF\uFF09",
363
+ en: "Select target documents (multi-select allowed)"
364
+ },
365
+ async fetchOptions(_state, rt) {
366
+ const files = await listDocuments(rt.config);
367
+ return [
368
+ {
369
+ value: ALL_DOCUMENTS,
370
+ label: rt.language === "ja" ? "\u30A2\u30AF\u30BB\u30B9\u53EF\u80FD\u306A\u3059\u3079\u3066\u306E\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8" : "All accessible documents"
371
+ },
372
+ ...files.map((f) => ({ value: f.id, label: f.name }))
373
+ ];
374
+ },
375
+ applyAnswer: (state, answer) => ({ ...state, documents: answer })
376
+ }
377
+ ],
378
+ async finalize(state, rt) {
379
+ if (!state.documents) {
380
+ throw new Error("Google Docs setup: incomplete state on finalize");
381
+ }
382
+ const targetIds = await resolveSetupSelection({
383
+ selected: state.documents,
384
+ allSentinel: ALL_DOCUMENTS,
385
+ fetchAll: async () => {
386
+ const files = await listDocuments(rt.config);
387
+ return files.map((f) => f.id);
388
+ },
389
+ limit: GOOGLE_DOCS_SETUP_MAX_DOCUMENTS
390
+ });
391
+ const sections = ["## Google Docs", ""];
392
+ if (targetIds.length === 0) {
393
+ sections.push(
394
+ rt.language === "ja" ? "\u5BFE\u8C61\u306E\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8\u304C\u9078\u629E\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002" : "No documents selected."
395
+ );
396
+ return sections.join("\n");
397
+ }
398
+ for (const id of targetIds) {
399
+ const url = `https://docs.googleapis.com/v1/documents/${encodeURIComponent(
400
+ id
401
+ )}`;
402
+ const meta = await googleApiFetch(rt.config, url);
403
+ const title = meta.title ?? id;
404
+ sections.push(`### Document: ${title}`, "");
405
+ sections.push(`- ID: ${id}`);
406
+ if (meta.revisionId) sections.push(`- Revision: ${meta.revisionId}`);
407
+ const headings = extractHeadings(meta, 5);
408
+ if (headings.length > 0) {
409
+ sections.push("- Outline:");
410
+ for (const h of headings) sections.push(` - ${h}`);
411
+ }
412
+ sections.push("");
413
+ }
414
+ return sections.join("\n");
415
+ }
416
+ };
417
+
226
418
  // ../connectors/src/connectors/google-docs/parameters.ts
227
419
  var parameters = {};
228
420
 
@@ -355,6 +547,7 @@ var tools = { request: requestTool };
355
547
  var googleDocsConnector = new ConnectorPlugin({
356
548
  slug: "google-docs",
357
549
  authType: AUTH_TYPES.OAUTH,
550
+ skipConnectionCheckOnCreate: true,
358
551
  name: "Google Docs",
359
552
  description: "Connect to Google Docs for document data access and creation using OAuth.",
360
553
  iconUrl: "https://images.ctfassets.net/9ncizv60xc5y/6vvcGJisvXjOumeTvswjzf/e9bb39e453cc0b71a20f26019b23b0d2/google_docs.png",
@@ -510,7 +703,8 @@ await docs.batchUpdate(documentId, [
510
703
  ]);
511
704
  \`\`\``
512
705
  },
513
- tools
706
+ tools,
707
+ setup: (params, ctx, config) => runSetupFlow(googleDocsSetupFlow, params, ctx, config)
514
708
  });
515
709
 
516
710
  // src/connectors/create-connector-sdk.ts
@@ -539,6 +733,7 @@ function resolveEnvVarOptional(entry, key) {
539
733
  import { getContext } from "hono/context-storage";
540
734
  import { getCookie } from "hono/cookie";
541
735
  var APP_SESSION_COOKIE_NAME = "__Host-squadbase-session";
736
+ var TABLEAU_SESSION_SENTINEL_URL = "squadbase://tableau-session/";
542
737
  function normalizeHeaders(input) {
543
738
  const out = {};
544
739
  if (!input) return out;
@@ -547,6 +742,11 @@ function normalizeHeaders(input) {
547
742
  });
548
743
  return out;
549
744
  }
745
+ function extractInputUrl(input) {
746
+ if (typeof input === "string") return input;
747
+ if (input instanceof URL) return input.href;
748
+ return input.url;
749
+ }
550
750
  function createSandboxProxyFetch(connectionId) {
551
751
  return async (input, init) => {
552
752
  const token = process.env.INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL;
@@ -556,10 +756,17 @@ function createSandboxProxyFetch(connectionId) {
556
756
  "Connection proxy is not configured. Please check your deployment settings."
557
757
  );
558
758
  }
559
- const originalUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
759
+ const originalUrl = extractInputUrl(input);
760
+ const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
761
+ if (originalUrl === TABLEAU_SESSION_SENTINEL_URL) {
762
+ const sessionUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
763
+ return fetch(sessionUrl, {
764
+ method: "POST",
765
+ headers: { Authorization: `Bearer ${token}` }
766
+ });
767
+ }
560
768
  const originalMethod = init?.method ?? "GET";
561
769
  const originalBody = init?.body ? JSON.parse(init.body) : void 0;
562
- const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
563
770
  const proxyUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
564
771
  return fetch(proxyUrl, {
565
772
  method: "POST",
@@ -585,10 +792,9 @@ function createDeployedAppProxyFetch(connectionId) {
585
792
  }
586
793
  const baseDomain = process.env["SQUADBASE_APP_BASE_DOMAIN"] ?? "squadbase.app";
587
794
  const proxyUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
795
+ const sessionUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
588
796
  return async (input, init) => {
589
- const originalUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
590
- const originalMethod = init?.method ?? "GET";
591
- const originalBody = init?.body ? JSON.parse(init.body) : void 0;
797
+ const originalUrl = extractInputUrl(input);
592
798
  const c = getContext();
593
799
  const appSession = getCookie(c, APP_SESSION_COOKIE_NAME);
594
800
  if (!appSession) {
@@ -596,6 +802,14 @@ function createDeployedAppProxyFetch(connectionId) {
596
802
  "No authentication method available for connection proxy."
597
803
  );
598
804
  }
805
+ if (originalUrl === TABLEAU_SESSION_SENTINEL_URL) {
806
+ return fetch(sessionUrl, {
807
+ method: "POST",
808
+ headers: { Authorization: `Bearer ${appSession}` }
809
+ });
810
+ }
811
+ const originalMethod = init?.method ?? "GET";
812
+ const originalBody = init?.body ? JSON.parse(init.body) : void 0;
599
813
  return fetch(proxyUrl, {
600
814
  method: "POST",
601
815
  headers: {
@@ -202,6 +202,28 @@ var ConnectorPlugin = class _ConnectorPlugin {
202
202
  tools;
203
203
  query;
204
204
  checkConnection;
205
+ /**
206
+ * SQPD-1212: Logic-based, rule-driven connection setup. Connectors that
207
+ * implement this expose a step-by-step exploration flow (database/schema/
208
+ * table/etc. discovery) that the dashboard backend drives via the
209
+ * `/connections/:connectionId/setup` endpoint. Implement by delegating to
210
+ * `runSetupFlow` from `setup-flow.ts`.
211
+ */
212
+ setup;
213
+ /**
214
+ * Opt-out of the default "verify before save" behavior on connection
215
+ * creation. The backend invokes `checkConnection` synchronously while
216
+ * creating the connection and aborts (no row inserted) if it fails — this
217
+ * flag disables that for connectors where the check cannot succeed pre-save:
218
+ *
219
+ * - `squadbase-db` populates `connection-url` only after Neon provisioning
220
+ * - OAuth connectors require an OAuth-aware proxyFetch keyed by the
221
+ * connectionId, which doesn't exist until the row is saved
222
+ *
223
+ * Exceptions are the explicit position; new credential-input connectors get
224
+ * the default verify-on-create behavior without opt-in.
225
+ */
226
+ skipConnectionCheckOnCreate;
205
227
  constructor(config) {
206
228
  this.slug = config.slug;
207
229
  this.authType = config.authType;
@@ -218,6 +240,8 @@ var ConnectorPlugin = class _ConnectorPlugin {
218
240
  this.tools = config.tools;
219
241
  this.query = config.query;
220
242
  this.checkConnection = config.checkConnection;
243
+ this.setup = config.setup;
244
+ this.skipConnectionCheckOnCreate = config.skipConnectionCheckOnCreate;
221
245
  }
222
246
  get connectorKey() {
223
247
  return _ConnectorPlugin.deriveKey(this.slug, this.authType);
@@ -282,6 +306,76 @@ var ConnectorPlugin = class _ConnectorPlugin {
282
306
  }
283
307
  };
284
308
 
309
+ // ../connectors/src/setup-flow.ts
310
+ async function runSetupFlow(flow, params, ctx, config) {
311
+ const runtime = {
312
+ params,
313
+ language: ctx.language,
314
+ config
315
+ };
316
+ let state = flow.initialState();
317
+ let answerIdx = 0;
318
+ const pendingParameterUpdates = [];
319
+ for (const step of flow.steps) {
320
+ const ans = ctx.answers[answerIdx];
321
+ if (ans && ans.questionSlug === step.slug) {
322
+ state = step.applyAnswer(state, ans.answer);
323
+ if (step.toParameterUpdates) {
324
+ pendingParameterUpdates.push(...step.toParameterUpdates(state));
325
+ }
326
+ answerIdx += 1;
327
+ continue;
328
+ }
329
+ const resolvedAllowFreeText = step.allowFreeText !== void 0 ? step.allowFreeText : true;
330
+ if (step.type === "text") {
331
+ if (step.fetchOptions) {
332
+ const options2 = await step.fetchOptions(state, runtime);
333
+ if (options2.length === 0) {
334
+ continue;
335
+ }
336
+ }
337
+ return {
338
+ type: "nextQuestion",
339
+ questionSlug: step.slug,
340
+ question: step.question[ctx.language],
341
+ questionType: "text",
342
+ allowFreeText: resolvedAllowFreeText,
343
+ ...pendingParameterUpdates.length > 0 && {
344
+ parameterUpdates: pendingParameterUpdates
345
+ }
346
+ };
347
+ }
348
+ const options = step.fetchOptions ? await step.fetchOptions(state, runtime) : [];
349
+ if (options.length === 0) {
350
+ continue;
351
+ }
352
+ return {
353
+ type: "nextQuestion",
354
+ questionSlug: step.slug,
355
+ question: step.question[ctx.language],
356
+ questionType: step.type,
357
+ options,
358
+ allowFreeText: resolvedAllowFreeText,
359
+ ...pendingParameterUpdates.length > 0 && {
360
+ parameterUpdates: pendingParameterUpdates
361
+ }
362
+ };
363
+ }
364
+ const dataInvestigationResult = await flow.finalize(state, runtime);
365
+ return {
366
+ type: "fulfilled",
367
+ dataInvestigationResult,
368
+ ...pendingParameterUpdates.length > 0 && {
369
+ parameterUpdates: pendingParameterUpdates
370
+ }
371
+ };
372
+ }
373
+ async function resolveSetupSelection(params) {
374
+ const { selected, allSentinel, fetchAll, limit } = params;
375
+ const resolved = selected.includes(allSentinel) ? await fetchAll() : selected.filter((v) => v !== allSentinel);
376
+ return resolved.slice(0, limit);
377
+ }
378
+
285
379
  // ../connectors/src/auth-types.ts
286
380
  var AUTH_TYPES = {
287
381
  OAUTH: "oauth",
@@ -312,6 +406,147 @@ var googleDriveOnboarding = new ConnectorOnboarding({
312
406
  }
313
407
  });
314
408
 
409
+ // ../connectors/src/connectors/google-drive/utils.ts
410
+ async function googleApiFetch(config, url) {
411
+ const res = await config.proxyFetch(url, { method: "GET" });
412
+ if (!res.ok) {
413
+ const text = await res.text().catch(() => res.statusText);
414
+ throw new Error(`Google Drive ${url} failed: HTTP ${res.status} ${text}`);
415
+ }
416
+ return await res.json();
417
+ }
418
+
419
+ // ../connectors/src/connectors/google-drive/setup-flow.ts
420
+ var MY_DRIVE = "__MY_DRIVE__";
421
+ var ALL_ITEMS = "__ALL_ITEMS__";
422
+ var GOOGLE_DRIVE_SETUP_MAX_ITEMS = 25;
423
+ var PAGE_SIZE = 50;
424
+ var FOLDER_MIME = "application/vnd.google-apps.folder";
425
+ async function listSharedDrives(config) {
426
+ const url = `https://www.googleapis.com/drive/v3/drives?pageSize=100&fields=drives(id,name)`;
427
+ try {
428
+ const data = await googleApiFetch(config, url);
429
+ return data.drives ?? [];
430
+ } catch {
431
+ return [];
432
+ }
433
+ }
434
+ function listItemsUrl(driveId) {
435
+ const params = new URLSearchParams({
436
+ pageSize: String(PAGE_SIZE),
437
+ fields: "files(id,name,mimeType,modifiedTime)",
438
+ orderBy: "modifiedTime desc"
439
+ });
440
+ if (driveId) {
441
+ params.set("corpora", "drive");
442
+ params.set("driveId", driveId);
443
+ params.set("includeItemsFromAllDrives", "true");
444
+ params.set("supportsAllDrives", "true");
445
+ params.set("q", `'${driveId}' in parents and trashed=false`);
446
+ } else {
447
+ params.set("q", `'root' in parents and trashed=false`);
448
+ }
449
+ return `https://www.googleapis.com/drive/v3/files?${params.toString()}`;
450
+ }
451
+ async function listItems(config, driveId) {
452
+ const data = await googleApiFetch(
453
+ config,
454
+ listItemsUrl(driveId)
455
+ );
456
+ return data.files ?? [];
457
+ }
458
+ var googleDriveSetupFlow = {
459
+ initialState: () => ({}),
460
+ steps: [
461
+ {
462
+ slug: "drive",
463
+ type: "select",
464
+ question: {
465
+ ja: "\u5BFE\u8C61\u306E\u30C9\u30E9\u30A4\u30D6\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044",
466
+ en: "Select the drive to use"
467
+ },
468
+ async fetchOptions(_state, rt) {
469
+ const shared = await listSharedDrives(rt.config);
470
+ const myDriveLabel = rt.language === "ja" ? "\u30DE\u30A4\u30C9\u30E9\u30A4\u30D6" : "My Drive";
471
+ return [
472
+ { value: MY_DRIVE, label: myDriveLabel },
473
+ ...shared.filter((d) => d.id).map((d) => ({ value: d.id, label: d.name ?? d.id }))
474
+ ];
475
+ },
476
+ applyAnswer: (state, answer) => ({ ...state, drive: answer[0] })
477
+ },
478
+ {
479
+ slug: "items",
480
+ type: "multiSelect",
481
+ question: {
482
+ ja: "\u5BFE\u8C61\u306E\u30D5\u30A9\u30EB\u30C0\u30FB\u30D5\u30A1\u30A4\u30EB\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044\uFF08\u8907\u6570\u9078\u629E\u53EF\uFF09",
483
+ en: "Select target folders or files (multi-select allowed)"
484
+ },
485
+ async fetchOptions(state, rt) {
486
+ if (!state.drive) return [];
487
+ const driveId = state.drive === MY_DRIVE ? null : state.drive;
488
+ const files = await listItems(rt.config, driveId);
489
+ const fileOptions = files.map((f) => {
490
+ const isFolder = f.mimeType === FOLDER_MIME;
491
+ const prefix = isFolder ? "[folder] " : "";
492
+ return { value: f.id, label: `${prefix}${f.name}` };
493
+ });
494
+ return [
495
+ {
496
+ value: ALL_ITEMS,
497
+ label: rt.language === "ja" ? "\u3053\u306E\u30C9\u30E9\u30A4\u30D6\u306E\u3059\u3079\u3066\u306E\u30A2\u30A4\u30C6\u30E0" : "All items in this drive"
498
+ },
499
+ ...fileOptions
500
+ ];
501
+ },
502
+ applyAnswer: (state, answer) => ({ ...state, items: answer })
503
+ }
504
+ ],
505
+ async finalize(state, rt) {
506
+ if (!state.drive || !state.items) {
507
+ throw new Error("Google Drive setup: incomplete state on finalize");
508
+ }
509
+ const driveId = state.drive === MY_DRIVE ? null : state.drive;
510
+ const allFiles = await listItems(rt.config, driveId);
511
+ const filesById = new Map(allFiles.map((f) => [f.id, f]));
512
+ const targetIds = await resolveSetupSelection({
513
+ selected: state.items,
514
+ allSentinel: ALL_ITEMS,
515
+ fetchAll: async () => allFiles.map((f) => f.id),
516
+ limit: GOOGLE_DRIVE_SETUP_MAX_ITEMS
517
+ });
518
+ const driveLabel = state.drive === MY_DRIVE ? rt.language === "ja" ? "\u30DE\u30A4\u30C9\u30E9\u30A4\u30D6" : "My Drive" : state.drive;
519
+ const sections = [
520
+ "## Google Drive",
521
+ "",
522
+ `### Drive: ${driveLabel}`,
523
+ ""
524
+ ];
525
+ if (targetIds.length === 0) {
526
+ sections.push(
527
+ rt.language === "ja" ? "- \u9078\u629E\u3055\u308C\u305F\u30A2\u30A4\u30C6\u30E0\u306F\u3042\u308A\u307E\u305B\u3093\u3002" : "- No items selected."
528
+ );
529
+ return sections.join("\n");
530
+ }
531
+ sections.push("| Name | Type | Modified |");
532
+ sections.push("|------|------|----------|");
533
+ for (const id of targetIds) {
534
+ const f = filesById.get(id);
535
+ if (!f) {
536
+ sections.push(`| ${id} | (unknown) | - |`);
537
+ continue;
538
+ }
539
+ const isFolder = f.mimeType === FOLDER_MIME;
540
+ const typeLabel = isFolder ? "folder" : f.mimeType ?? "file";
541
+ sections.push(
542
+ `| ${f.name} | ${typeLabel} | ${f.modifiedTime ?? "-"} |`
543
+ );
544
+ }
545
+ sections.push("");
546
+ return sections.join("\n");
547
+ }
548
+ };
549
+
315
550
  // ../connectors/src/connectors/google-drive/parameters.ts
316
551
  var parameters = {};
317
552
 
@@ -439,6 +674,7 @@ var tools = { request: requestTool };
439
674
  var googleDriveConnector = new ConnectorPlugin({
440
675
  slug: "google-drive",
441
676
  authType: AUTH_TYPES.OAUTH,
677
+ skipConnectionCheckOnCreate: true,
442
678
  name: "Google Drive",
443
679
  description: "Connect to Google Drive for file management, sharing, and collaboration using OAuth.",
444
680
  iconUrl: "https://images.ctfassets.net/9ncizv60xc5y/4GJX5yQTogUgar1buWxXbv/4b43a65353319c508111489f834d22c4/google_drive.png",
@@ -730,6 +966,7 @@ await drive.updateFile("fileId123", {}, "newFolderId", "oldFolderId");
730
966
  \`\`\``
731
967
  },
732
968
  tools,
969
+ setup: (params, ctx, config) => runSetupFlow(googleDriveSetupFlow, params, ctx, config),
733
970
  async checkConnection(_params, config) {
734
971
  const { proxyFetch } = config;
735
972
  const url = "https://www.googleapis.com/drive/v3/about?fields=user";
@@ -778,6 +1015,7 @@ function resolveEnvVarOptional(entry, key) {
778
1015
  import { getContext } from "hono/context-storage";
779
1016
  import { getCookie } from "hono/cookie";
780
1017
  var APP_SESSION_COOKIE_NAME = "__Host-squadbase-session";
1018
+ var TABLEAU_SESSION_SENTINEL_URL = "squadbase://tableau-session/";
781
1019
  function normalizeHeaders(input) {
782
1020
  const out = {};
783
1021
  if (!input) return out;
@@ -786,6 +1024,11 @@ function normalizeHeaders(input) {
786
1024
  });
787
1025
  return out;
788
1026
  }
1027
+ function extractInputUrl(input) {
1028
+ if (typeof input === "string") return input;
1029
+ if (input instanceof URL) return input.href;
1030
+ return input.url;
1031
+ }
789
1032
  function createSandboxProxyFetch(connectionId) {
790
1033
  return async (input, init) => {
791
1034
  const token = process.env.INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL;
@@ -795,10 +1038,17 @@ function createSandboxProxyFetch(connectionId) {
795
1038
  "Connection proxy is not configured. Please check your deployment settings."
796
1039
  );
797
1040
  }
798
- const originalUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
1041
+ const originalUrl = extractInputUrl(input);
1042
+ const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
1043
+ if (originalUrl === TABLEAU_SESSION_SENTINEL_URL) {
1044
+ const sessionUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
1045
+ return fetch(sessionUrl, {
1046
+ method: "POST",
1047
+ headers: { Authorization: `Bearer ${token}` }
1048
+ });
1049
+ }
799
1050
  const originalMethod = init?.method ?? "GET";
800
1051
  const originalBody = init?.body ? JSON.parse(init.body) : void 0;
801
- const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
802
1052
  const proxyUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
803
1053
  return fetch(proxyUrl, {
804
1054
  method: "POST",
@@ -824,10 +1074,9 @@ function createDeployedAppProxyFetch(connectionId) {
824
1074
  }
825
1075
  const baseDomain = process.env["SQUADBASE_APP_BASE_DOMAIN"] ?? "squadbase.app";
826
1076
  const proxyUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
1077
+ const sessionUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
827
1078
  return async (input, init) => {
828
- const originalUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
829
- const originalMethod = init?.method ?? "GET";
830
- const originalBody = init?.body ? JSON.parse(init.body) : void 0;
1079
+ const originalUrl = extractInputUrl(input);
831
1080
  const c = getContext();
832
1081
  const appSession = getCookie(c, APP_SESSION_COOKIE_NAME);
833
1082
  if (!appSession) {
@@ -835,6 +1084,14 @@ function createDeployedAppProxyFetch(connectionId) {
835
1084
  "No authentication method available for connection proxy."
836
1085
  );
837
1086
  }
1087
+ if (originalUrl === TABLEAU_SESSION_SENTINEL_URL) {
1088
+ return fetch(sessionUrl, {
1089
+ method: "POST",
1090
+ headers: { Authorization: `Bearer ${appSession}` }
1091
+ });
1092
+ }
1093
+ const originalMethod = init?.method ?? "GET";
1094
+ const originalBody = init?.body ? JSON.parse(init.body) : void 0;
838
1095
  return fetch(proxyUrl, {
839
1096
  method: "POST",
840
1097
  headers: {