@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.
- package/dist/cli/index.js +14375 -1652
- package/dist/connectors/airtable-oauth.js +282 -46
- package/dist/connectors/airtable.js +319 -51
- package/dist/connectors/amplitude.js +322 -47
- package/dist/connectors/anthropic.js +135 -47
- package/dist/connectors/asana.js +327 -49
- package/dist/connectors/attio.js +302 -49
- package/dist/connectors/aws-billing.js +287 -46
- package/dist/connectors/azure-sql.js +421 -102
- package/dist/connectors/backlog-api-key.js +317 -47
- package/dist/connectors/clickup.js +338 -49
- package/dist/connectors/cosmosdb.js +305 -50
- package/dist/connectors/customerio.js +319 -47
- package/dist/connectors/dbt.js +340 -47
- package/dist/connectors/freshdesk.js +342 -53
- package/dist/connectors/freshsales.js +333 -52
- package/dist/connectors/freshservice.js +361 -53
- package/dist/connectors/gamma.js +327 -52
- package/dist/connectors/gemini.js +134 -47
- package/dist/connectors/github.js +386 -49
- package/dist/connectors/gmail-oauth.js +204 -7
- package/dist/connectors/gmail.js +350 -47
- package/dist/connectors/google-ads.js +288 -46
- package/dist/connectors/google-analytics-oauth.js +310 -46
- package/dist/connectors/google-analytics.js +547 -87
- package/dist/connectors/google-audit-log.js +438 -47
- package/dist/connectors/google-calendar-oauth.js +259 -46
- package/dist/connectors/google-calendar.js +359 -47
- package/dist/connectors/google-docs.js +220 -6
- package/dist/connectors/google-drive.js +262 -5
- package/dist/connectors/google-search-console-oauth.js +256 -46
- package/dist/connectors/google-sheets.js +272 -47
- package/dist/connectors/google-slides.js +205 -6
- package/dist/connectors/grafana.js +332 -49
- package/dist/connectors/hubspot-oauth.js +208 -5
- package/dist/connectors/hubspot.js +306 -49
- package/dist/connectors/influxdb.js +416 -51
- package/dist/connectors/intercom-oauth.js +210 -5
- package/dist/connectors/intercom.js +302 -49
- package/dist/connectors/jdbc.js +762 -110
- package/dist/connectors/jira-api-key.js +326 -47
- package/dist/connectors/kintone-api-token.js +281 -47
- package/dist/connectors/kintone.js +328 -47
- package/dist/connectors/linear.js +330 -49
- package/dist/connectors/linkedin-ads.js +268 -50
- package/dist/connectors/mailchimp-oauth.js +268 -46
- package/dist/connectors/mailchimp.js +320 -49
- package/dist/connectors/meta-ads-oauth.js +273 -48
- package/dist/connectors/meta-ads.js +285 -50
- package/dist/connectors/mixpanel.js +338 -47
- package/dist/connectors/monday.js +360 -49
- package/dist/connectors/mongodb.js +319 -57
- package/dist/connectors/notion-oauth.js +231 -5
- package/dist/connectors/notion.js +323 -51
- package/dist/connectors/openai.js +134 -47
- package/dist/connectors/oracle.js +454 -103
- package/dist/connectors/outlook-oauth.js +204 -5
- package/dist/connectors/powerbi-oauth.js +498 -5
- package/dist/connectors/salesforce.js +384 -49
- package/dist/connectors/semrush.js +609 -49
- package/dist/connectors/sentry.js +289 -50
- package/dist/connectors/shopify-oauth.js +187 -5
- package/dist/connectors/shopify.js +357 -47
- package/dist/connectors/sqlserver.js +415 -102
- package/dist/connectors/stripe-api-key.js +269 -46
- package/dist/connectors/stripe-oauth.js +202 -5
- package/dist/connectors/supabase.js +303 -48
- package/dist/connectors/tableau.js +536 -163
- package/dist/connectors/tiktok-ads.js +279 -48
- package/dist/connectors/wix-store.js +320 -49
- package/dist/connectors/zendesk-oauth.js +239 -5
- package/dist/connectors/zendesk.js +358 -47
- package/dist/index.d.ts +149 -1
- package/dist/index.js +15057 -2117
- package/dist/main.js +15005 -2073
- package/dist/vite-plugin.js +14752 -2019
- 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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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: {
|