@squadbase/vite-server 0.1.12-dev.a9ac647 → 0.1.17-dev.24af54e
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 +12374 -883
- package/dist/connectors/airtable-oauth.js +257 -46
- package/dist/connectors/airtable.js +294 -51
- package/dist/connectors/amplitude.js +297 -47
- package/dist/connectors/anthropic.js +135 -47
- package/dist/connectors/asana.js +302 -49
- package/dist/connectors/attio.js +277 -49
- package/dist/connectors/aws-billing.js +262 -46
- package/dist/connectors/azure-sql.js +396 -102
- package/dist/connectors/backlog-api-key.js +292 -47
- package/dist/connectors/clickup.js +313 -49
- package/dist/connectors/cosmosdb.js +280 -50
- package/dist/connectors/customerio.js +294 -47
- package/dist/connectors/dbt.js +315 -47
- package/dist/connectors/freshdesk.js +317 -53
- package/dist/connectors/freshsales.js +308 -52
- package/dist/connectors/freshservice.js +336 -53
- package/dist/connectors/gamma.js +302 -52
- package/dist/connectors/gemini.js +134 -47
- package/dist/connectors/github.js +361 -49
- package/dist/connectors/gmail-oauth.js +179 -7
- package/dist/connectors/gmail.js +325 -47
- package/dist/connectors/google-ads.js +263 -46
- package/dist/connectors/google-analytics-oauth.js +285 -46
- package/dist/connectors/google-analytics.js +387 -49
- package/dist/connectors/google-audit-log.js +413 -47
- package/dist/connectors/google-calendar-oauth.js +234 -46
- package/dist/connectors/google-calendar.js +334 -47
- package/dist/connectors/google-docs.js +195 -6
- package/dist/connectors/google-drive.js +237 -5
- package/dist/connectors/google-search-console-oauth.js +231 -46
- package/dist/connectors/google-sheets.js +247 -47
- package/dist/connectors/google-slides.js +180 -6
- package/dist/connectors/grafana.js +307 -49
- package/dist/connectors/hubspot-oauth.js +183 -5
- package/dist/connectors/hubspot.js +281 -49
- package/dist/connectors/influxdb.js +391 -51
- package/dist/connectors/intercom-oauth.js +185 -5
- package/dist/connectors/intercom.js +277 -49
- package/dist/connectors/jdbc.js +737 -110
- package/dist/connectors/jira-api-key.js +301 -47
- package/dist/connectors/kintone-api-token.js +256 -47
- package/dist/connectors/kintone.js +303 -47
- package/dist/connectors/linear.js +305 -49
- package/dist/connectors/linkedin-ads.js +243 -50
- package/dist/connectors/mailchimp-oauth.js +243 -46
- package/dist/connectors/mailchimp.js +295 -49
- package/dist/connectors/meta-ads-oauth.js +248 -48
- package/dist/connectors/meta-ads.js +260 -50
- package/dist/connectors/mixpanel.js +313 -47
- package/dist/connectors/monday.js +335 -49
- package/dist/connectors/mongodb.js +294 -57
- package/dist/connectors/notion-oauth.js +206 -5
- package/dist/connectors/notion.js +298 -51
- package/dist/connectors/openai.js +134 -47
- package/dist/connectors/oracle.js +414 -103
- package/dist/connectors/outlook-oauth.js +179 -5
- package/dist/connectors/powerbi-oauth.js +226 -5
- package/dist/connectors/salesforce.js +359 -49
- package/dist/connectors/semrush.js +289 -49
- package/dist/connectors/sentry.js +264 -50
- package/dist/connectors/shopify-oauth.js +162 -5
- package/dist/connectors/shopify.js +332 -47
- package/dist/connectors/sqlserver.js +390 -102
- package/dist/connectors/stripe-api-key.js +244 -46
- package/dist/connectors/stripe-oauth.js +177 -5
- package/dist/connectors/supabase.js +278 -48
- package/dist/connectors/tableau.js +389 -184
- package/dist/connectors/tiktok-ads.js +254 -48
- package/dist/connectors/wix-store.js +295 -49
- package/dist/connectors/zendesk-oauth.js +214 -5
- package/dist/connectors/zendesk.js +333 -47
- package/dist/index.d.ts +149 -1
- package/dist/index.js +13677 -1969
- package/dist/main.js +13627 -1927
- package/dist/vite-plugin.js +12391 -890
- package/package.json +1 -1
|
@@ -1,48 +1,60 @@
|
|
|
1
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
2
|
+
var __esm = (fn, res) => function __init() {
|
|
3
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
4
|
+
};
|
|
5
|
+
|
|
1
6
|
// ../connectors/src/parameter-definition.ts
|
|
2
|
-
var ParameterDefinition
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
7
|
+
var ParameterDefinition;
|
|
8
|
+
var init_parameter_definition = __esm({
|
|
9
|
+
"../connectors/src/parameter-definition.ts"() {
|
|
10
|
+
"use strict";
|
|
11
|
+
ParameterDefinition = class {
|
|
12
|
+
slug;
|
|
13
|
+
name;
|
|
14
|
+
description;
|
|
15
|
+
envVarBaseKey;
|
|
16
|
+
type;
|
|
17
|
+
secret;
|
|
18
|
+
required;
|
|
19
|
+
constructor(config) {
|
|
20
|
+
this.slug = config.slug;
|
|
21
|
+
this.name = config.name;
|
|
22
|
+
this.description = config.description;
|
|
23
|
+
this.envVarBaseKey = config.envVarBaseKey;
|
|
24
|
+
this.type = config.type;
|
|
25
|
+
this.secret = config.secret;
|
|
26
|
+
this.required = config.required;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get the parameter value from a ConnectorConnectionObject.
|
|
30
|
+
*/
|
|
31
|
+
getValue(connection2) {
|
|
32
|
+
const param = connection2.parameters.find(
|
|
33
|
+
(p) => p.parameterSlug === this.slug
|
|
34
|
+
);
|
|
35
|
+
if (!param || param.value == null) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`Parameter "${this.slug}" not found or has no value in connection "${connection2.id}"`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
return param.value;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Try to get the parameter value. Returns undefined if not found (for optional params).
|
|
44
|
+
*/
|
|
45
|
+
tryGetValue(connection2) {
|
|
46
|
+
const param = connection2.parameters.find(
|
|
47
|
+
(p) => p.parameterSlug === this.slug
|
|
48
|
+
);
|
|
49
|
+
if (!param || param.value == null) return void 0;
|
|
50
|
+
return param.value;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
42
53
|
}
|
|
43
|
-
};
|
|
54
|
+
});
|
|
44
55
|
|
|
45
56
|
// ../connectors/src/connectors/sentry/parameters.ts
|
|
57
|
+
init_parameter_definition();
|
|
46
58
|
var parameters = {
|
|
47
59
|
organizationSlug: new ParameterDefinition({
|
|
48
60
|
slug: "organization-slug",
|
|
@@ -94,7 +106,7 @@ function createClient(params) {
|
|
|
94
106
|
const url = `${BASE_URL}${resolved.startsWith("/") ? "" : "/"}${resolved}`;
|
|
95
107
|
return authenticatedFetch(url, init);
|
|
96
108
|
}
|
|
97
|
-
async function
|
|
109
|
+
async function listProjects2() {
|
|
98
110
|
const url = `${BASE_URL}/organizations/${organizationSlug}/projects/`;
|
|
99
111
|
const response = await authenticatedFetch(url);
|
|
100
112
|
if (!response.ok) {
|
|
@@ -160,7 +172,7 @@ function createClient(params) {
|
|
|
160
172
|
}
|
|
161
173
|
return {
|
|
162
174
|
request,
|
|
163
|
-
listProjects,
|
|
175
|
+
listProjects: listProjects2,
|
|
164
176
|
listIssues,
|
|
165
177
|
getIssue,
|
|
166
178
|
listIssueEvents,
|
|
@@ -227,6 +239,28 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
227
239
|
tools;
|
|
228
240
|
query;
|
|
229
241
|
checkConnection;
|
|
242
|
+
/**
|
|
243
|
+
* SQPD-1212: Logic-based, rule-driven connection setup. Connectors that
|
|
244
|
+
* implement this expose a step-by-step exploration flow (database/schema/
|
|
245
|
+
* table/etc. discovery) that the dashboard backend drives via the
|
|
246
|
+
* `/connections/:connectionId/setup` endpoint. Implement by delegating to
|
|
247
|
+
* `runSetupFlow` from `setup-flow.ts`.
|
|
248
|
+
*/
|
|
249
|
+
setup;
|
|
250
|
+
/**
|
|
251
|
+
* Opt-out of the default "verify before save" behavior on connection
|
|
252
|
+
* creation. The backend invokes `checkConnection` synchronously while
|
|
253
|
+
* creating the connection and aborts (no row inserted) if it fails — this
|
|
254
|
+
* flag disables that for connectors where the check cannot succeed pre-save:
|
|
255
|
+
*
|
|
256
|
+
* - `squadbase-db` populates `connection-url` only after Neon provisioning
|
|
257
|
+
* - OAuth connectors require an OAuth-aware proxyFetch keyed by the
|
|
258
|
+
* connectionId, which doesn't exist until the row is saved
|
|
259
|
+
*
|
|
260
|
+
* Exceptions are the explicit position; new credential-input connectors get
|
|
261
|
+
* the default verify-on-create behavior without opt-in.
|
|
262
|
+
*/
|
|
263
|
+
skipConnectionCheckOnCreate;
|
|
230
264
|
constructor(config) {
|
|
231
265
|
this.slug = config.slug;
|
|
232
266
|
this.authType = config.authType;
|
|
@@ -243,6 +277,8 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
243
277
|
this.tools = config.tools;
|
|
244
278
|
this.query = config.query;
|
|
245
279
|
this.checkConnection = config.checkConnection;
|
|
280
|
+
this.setup = config.setup;
|
|
281
|
+
this.skipConnectionCheckOnCreate = config.skipConnectionCheckOnCreate;
|
|
246
282
|
}
|
|
247
283
|
get connectorKey() {
|
|
248
284
|
return _ConnectorPlugin.deriveKey(this.slug, this.authType);
|
|
@@ -307,6 +343,51 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
307
343
|
}
|
|
308
344
|
};
|
|
309
345
|
|
|
346
|
+
// ../connectors/src/setup-flow.ts
|
|
347
|
+
async function runSetupFlow(flow, params, ctx, config) {
|
|
348
|
+
const runtime = {
|
|
349
|
+
params,
|
|
350
|
+
language: ctx.language,
|
|
351
|
+
config
|
|
352
|
+
};
|
|
353
|
+
let state = flow.initialState();
|
|
354
|
+
let answerIdx = 0;
|
|
355
|
+
for (const step of flow.steps) {
|
|
356
|
+
const ans = ctx.answers[answerIdx];
|
|
357
|
+
if (ans && ans.questionSlug === step.slug) {
|
|
358
|
+
state = step.applyAnswer(state, ans.answer);
|
|
359
|
+
answerIdx += 1;
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
if (step.type === "text") {
|
|
363
|
+
return {
|
|
364
|
+
type: "nextQuestion",
|
|
365
|
+
questionSlug: step.slug,
|
|
366
|
+
question: step.question[ctx.language],
|
|
367
|
+
questionType: "text"
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
const options = step.fetchOptions ? await step.fetchOptions(state, runtime) : [];
|
|
371
|
+
if (options.length === 0) {
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
type: "nextQuestion",
|
|
376
|
+
questionSlug: step.slug,
|
|
377
|
+
question: step.question[ctx.language],
|
|
378
|
+
questionType: step.type,
|
|
379
|
+
options
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
const dataInvestigationResult = await flow.finalize(state, runtime);
|
|
383
|
+
return { type: "fulfilled", dataInvestigationResult };
|
|
384
|
+
}
|
|
385
|
+
async function resolveSetupSelection(params) {
|
|
386
|
+
const { selected, allSentinel, fetchAll, limit } = params;
|
|
387
|
+
const resolved = selected.includes(allSentinel) ? await fetchAll() : selected.filter((v) => v !== allSentinel);
|
|
388
|
+
return resolved.slice(0, limit);
|
|
389
|
+
}
|
|
390
|
+
|
|
310
391
|
// ../connectors/src/auth-types.ts
|
|
311
392
|
var AUTH_TYPES = {
|
|
312
393
|
OAUTH: "oauth",
|
|
@@ -329,9 +410,121 @@ var sentryOnboarding = new ConnectorOnboarding({
|
|
|
329
410
|
}
|
|
330
411
|
});
|
|
331
412
|
|
|
413
|
+
// ../connectors/src/connectors/sentry/utils.ts
|
|
414
|
+
var BASE_URL2 = "https://sentry.io/api/0";
|
|
415
|
+
async function apiFetch(params, path2, init) {
|
|
416
|
+
const authToken = params[parameters.authToken.slug];
|
|
417
|
+
if (!authToken) {
|
|
418
|
+
throw new Error("sentry: missing required parameter: auth-token");
|
|
419
|
+
}
|
|
420
|
+
const url = `${BASE_URL2}${path2.startsWith("/") ? "" : "/"}${path2}`;
|
|
421
|
+
const headers = new Headers(init?.headers);
|
|
422
|
+
headers.set("Authorization", `Bearer ${authToken}`);
|
|
423
|
+
if (!headers.has("Accept")) headers.set("Accept", "application/json");
|
|
424
|
+
return fetch(url, { ...init, headers });
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// ../connectors/src/connectors/sentry/setup-flow.ts
|
|
428
|
+
var ALL_PROJECTS = "__ALL_PROJECTS__";
|
|
429
|
+
var SENTRY_SETUP_MAX_PROJECTS = 20;
|
|
430
|
+
async function listOrganizations(params) {
|
|
431
|
+
const res = await apiFetch(params, "/organizations/");
|
|
432
|
+
if (!res.ok) {
|
|
433
|
+
const body = await res.text().catch(() => res.statusText);
|
|
434
|
+
throw new Error(`sentry: listOrganizations failed (${res.status}): ${body}`);
|
|
435
|
+
}
|
|
436
|
+
const data = await res.json();
|
|
437
|
+
return data ?? [];
|
|
438
|
+
}
|
|
439
|
+
async function listProjects(params, orgSlug) {
|
|
440
|
+
const res = await apiFetch(params, `/organizations/${orgSlug}/projects/`);
|
|
441
|
+
if (!res.ok) {
|
|
442
|
+
const body = await res.text().catch(() => res.statusText);
|
|
443
|
+
throw new Error(`sentry: listProjects failed (${res.status}): ${body}`);
|
|
444
|
+
}
|
|
445
|
+
const data = await res.json();
|
|
446
|
+
return data ?? [];
|
|
447
|
+
}
|
|
448
|
+
var sentrySetupFlow = {
|
|
449
|
+
initialState: () => ({}),
|
|
450
|
+
steps: [
|
|
451
|
+
{
|
|
452
|
+
slug: "organization",
|
|
453
|
+
type: "select",
|
|
454
|
+
question: {
|
|
455
|
+
ja: "\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u306B\u4F7F\u3046\u7D44\u7E54\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044",
|
|
456
|
+
en: "Select the organization to use for setup"
|
|
457
|
+
},
|
|
458
|
+
async fetchOptions(_state, rt) {
|
|
459
|
+
const orgs = await listOrganizations(rt.params);
|
|
460
|
+
return orgs.filter((o) => o.slug).map((o) => ({ value: o.slug, label: o.name || o.slug }));
|
|
461
|
+
},
|
|
462
|
+
applyAnswer: (state, answer) => ({ ...state, organization: answer[0] })
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
slug: "projects",
|
|
466
|
+
type: "multiSelect",
|
|
467
|
+
question: {
|
|
468
|
+
ja: "\u5BFE\u8C61\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044\uFF08\u8907\u6570\u9078\u629E\u53EF\uFF09",
|
|
469
|
+
en: "Select target projects (multi-select allowed)"
|
|
470
|
+
},
|
|
471
|
+
async fetchOptions(state, rt) {
|
|
472
|
+
if (!state.organization) return [];
|
|
473
|
+
const projects = await listProjects(rt.params, state.organization);
|
|
474
|
+
const opts = projects.filter((p) => p.slug).map((p) => ({ value: p.slug, label: p.name || p.slug }));
|
|
475
|
+
if (opts.length === 0) return [];
|
|
476
|
+
return [
|
|
477
|
+
{
|
|
478
|
+
value: ALL_PROJECTS,
|
|
479
|
+
label: rt.language === "ja" ? "\u3059\u3079\u3066\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8" : "All projects"
|
|
480
|
+
},
|
|
481
|
+
...opts
|
|
482
|
+
];
|
|
483
|
+
},
|
|
484
|
+
applyAnswer: (state, answer) => ({ ...state, projects: answer })
|
|
485
|
+
}
|
|
486
|
+
],
|
|
487
|
+
async finalize(state, rt) {
|
|
488
|
+
if (!state.organization || !state.projects) {
|
|
489
|
+
throw new Error("Sentry setup: incomplete state on finalize");
|
|
490
|
+
}
|
|
491
|
+
const orgSlug = state.organization;
|
|
492
|
+
const projects = await listProjects(rt.params, orgSlug);
|
|
493
|
+
const bySlug = new Map(projects.map((p) => [p.slug, p]));
|
|
494
|
+
const targetSlugs = await resolveSetupSelection({
|
|
495
|
+
selected: state.projects,
|
|
496
|
+
allSentinel: ALL_PROJECTS,
|
|
497
|
+
fetchAll: async () => projects.map((p) => p.slug).filter((s) => s),
|
|
498
|
+
limit: SENTRY_SETUP_MAX_PROJECTS
|
|
499
|
+
});
|
|
500
|
+
const sections = [
|
|
501
|
+
"## Sentry",
|
|
502
|
+
"",
|
|
503
|
+
`### Organization: ${orgSlug}`,
|
|
504
|
+
""
|
|
505
|
+
];
|
|
506
|
+
for (const slug of targetSlugs) {
|
|
507
|
+
const project = bySlug.get(slug);
|
|
508
|
+
if (!project) {
|
|
509
|
+
sections.push(`#### Project: ${slug}`, "", "_Not found._", "");
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
sections.push(`#### Project: ${project.name || project.slug}`, "");
|
|
513
|
+
sections.push("| Field | Value |");
|
|
514
|
+
sections.push("|-------|-------|");
|
|
515
|
+
sections.push(`| Slug | ${project.slug} |`);
|
|
516
|
+
sections.push(`| Platform | ${project.platform ?? "-"} |`);
|
|
517
|
+
sections.push(`| First event | ${project.firstEvent ?? "-"} |`);
|
|
518
|
+
sections.push(`| Created | ${project.dateCreated ?? "-"} |`);
|
|
519
|
+
sections.push("");
|
|
520
|
+
}
|
|
521
|
+
return sections.join("\n");
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
|
|
332
525
|
// ../connectors/src/connectors/sentry/tools/request.ts
|
|
333
526
|
import { z } from "zod";
|
|
334
|
-
var
|
|
527
|
+
var BASE_URL3 = "https://sentry.io/api/0";
|
|
335
528
|
var REQUEST_TIMEOUT_MS = 6e4;
|
|
336
529
|
var inputSchema = z.object({
|
|
337
530
|
toolUseIntent: z.string().optional().describe(
|
|
@@ -381,7 +574,7 @@ Authentication is handled automatically via Bearer token.
|
|
|
381
574
|
/\{organizationSlug\}/g,
|
|
382
575
|
organizationSlug
|
|
383
576
|
);
|
|
384
|
-
const url = `${
|
|
577
|
+
const url = `${BASE_URL3}${resolvedPath.startsWith("/") ? "" : "/"}${resolvedPath}`;
|
|
385
578
|
const controller = new AbortController();
|
|
386
579
|
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
387
580
|
try {
|
|
@@ -590,6 +783,7 @@ await sentry.updateIssue("12345", { status: "resolved" });
|
|
|
590
783
|
\`\`\``
|
|
591
784
|
},
|
|
592
785
|
tools,
|
|
786
|
+
setup: (params, ctx, config) => runSetupFlow(sentrySetupFlow, params, ctx, config),
|
|
593
787
|
async checkConnection(params) {
|
|
594
788
|
const authToken = params[parameters.authToken.slug];
|
|
595
789
|
const organizationSlug = params[parameters.organizationSlug.slug];
|
|
@@ -651,6 +845,7 @@ function resolveEnvVarOptional(entry, key) {
|
|
|
651
845
|
import { getContext } from "hono/context-storage";
|
|
652
846
|
import { getCookie } from "hono/cookie";
|
|
653
847
|
var APP_SESSION_COOKIE_NAME = "__Host-squadbase-session";
|
|
848
|
+
var TABLEAU_SESSION_SENTINEL_URL = "squadbase://tableau-session/";
|
|
654
849
|
function normalizeHeaders(input) {
|
|
655
850
|
const out = {};
|
|
656
851
|
if (!input) return out;
|
|
@@ -659,6 +854,11 @@ function normalizeHeaders(input) {
|
|
|
659
854
|
});
|
|
660
855
|
return out;
|
|
661
856
|
}
|
|
857
|
+
function extractInputUrl(input) {
|
|
858
|
+
if (typeof input === "string") return input;
|
|
859
|
+
if (input instanceof URL) return input.href;
|
|
860
|
+
return input.url;
|
|
861
|
+
}
|
|
662
862
|
function createSandboxProxyFetch(connectionId) {
|
|
663
863
|
return async (input, init) => {
|
|
664
864
|
const token = process.env.INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL;
|
|
@@ -668,10 +868,17 @@ function createSandboxProxyFetch(connectionId) {
|
|
|
668
868
|
"Connection proxy is not configured. Please check your deployment settings."
|
|
669
869
|
);
|
|
670
870
|
}
|
|
671
|
-
const originalUrl =
|
|
871
|
+
const originalUrl = extractInputUrl(input);
|
|
872
|
+
const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
|
|
873
|
+
if (originalUrl === TABLEAU_SESSION_SENTINEL_URL) {
|
|
874
|
+
const sessionUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
|
|
875
|
+
return fetch(sessionUrl, {
|
|
876
|
+
method: "POST",
|
|
877
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
878
|
+
});
|
|
879
|
+
}
|
|
672
880
|
const originalMethod = init?.method ?? "GET";
|
|
673
881
|
const originalBody = init?.body ? JSON.parse(init.body) : void 0;
|
|
674
|
-
const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
|
|
675
882
|
const proxyUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
|
|
676
883
|
return fetch(proxyUrl, {
|
|
677
884
|
method: "POST",
|
|
@@ -697,10 +904,9 @@ function createDeployedAppProxyFetch(connectionId) {
|
|
|
697
904
|
}
|
|
698
905
|
const baseDomain = process.env["SQUADBASE_APP_BASE_DOMAIN"] ?? "squadbase.app";
|
|
699
906
|
const proxyUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
|
|
907
|
+
const sessionUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
|
|
700
908
|
return async (input, init) => {
|
|
701
|
-
const originalUrl =
|
|
702
|
-
const originalMethod = init?.method ?? "GET";
|
|
703
|
-
const originalBody = init?.body ? JSON.parse(init.body) : void 0;
|
|
909
|
+
const originalUrl = extractInputUrl(input);
|
|
704
910
|
const c = getContext();
|
|
705
911
|
const appSession = getCookie(c, APP_SESSION_COOKIE_NAME);
|
|
706
912
|
if (!appSession) {
|
|
@@ -708,6 +914,14 @@ function createDeployedAppProxyFetch(connectionId) {
|
|
|
708
914
|
"No authentication method available for connection proxy."
|
|
709
915
|
);
|
|
710
916
|
}
|
|
917
|
+
if (originalUrl === TABLEAU_SESSION_SENTINEL_URL) {
|
|
918
|
+
return fetch(sessionUrl, {
|
|
919
|
+
method: "POST",
|
|
920
|
+
headers: { Authorization: `Bearer ${appSession}` }
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
const originalMethod = init?.method ?? "GET";
|
|
924
|
+
const originalBody = init?.body ? JSON.parse(init.body) : void 0;
|
|
711
925
|
return fetch(proxyUrl, {
|
|
712
926
|
method: "POST",
|
|
713
927
|
headers: {
|
|
@@ -65,6 +65,28 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
65
65
|
tools;
|
|
66
66
|
query;
|
|
67
67
|
checkConnection;
|
|
68
|
+
/**
|
|
69
|
+
* SQPD-1212: Logic-based, rule-driven connection setup. Connectors that
|
|
70
|
+
* implement this expose a step-by-step exploration flow (database/schema/
|
|
71
|
+
* table/etc. discovery) that the dashboard backend drives via the
|
|
72
|
+
* `/connections/:connectionId/setup` endpoint. Implement by delegating to
|
|
73
|
+
* `runSetupFlow` from `setup-flow.ts`.
|
|
74
|
+
*/
|
|
75
|
+
setup;
|
|
76
|
+
/**
|
|
77
|
+
* Opt-out of the default "verify before save" behavior on connection
|
|
78
|
+
* creation. The backend invokes `checkConnection` synchronously while
|
|
79
|
+
* creating the connection and aborts (no row inserted) if it fails — this
|
|
80
|
+
* flag disables that for connectors where the check cannot succeed pre-save:
|
|
81
|
+
*
|
|
82
|
+
* - `squadbase-db` populates `connection-url` only after Neon provisioning
|
|
83
|
+
* - OAuth connectors require an OAuth-aware proxyFetch keyed by the
|
|
84
|
+
* connectionId, which doesn't exist until the row is saved
|
|
85
|
+
*
|
|
86
|
+
* Exceptions are the explicit position; new credential-input connectors get
|
|
87
|
+
* the default verify-on-create behavior without opt-in.
|
|
88
|
+
*/
|
|
89
|
+
skipConnectionCheckOnCreate;
|
|
68
90
|
constructor(config) {
|
|
69
91
|
this.slug = config.slug;
|
|
70
92
|
this.authType = config.authType;
|
|
@@ -81,6 +103,8 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
81
103
|
this.tools = config.tools;
|
|
82
104
|
this.query = config.query;
|
|
83
105
|
this.checkConnection = config.checkConnection;
|
|
106
|
+
this.setup = config.setup;
|
|
107
|
+
this.skipConnectionCheckOnCreate = config.skipConnectionCheckOnCreate;
|
|
84
108
|
}
|
|
85
109
|
get connectorKey() {
|
|
86
110
|
return _ConnectorPlugin.deriveKey(this.slug, this.authType);
|
|
@@ -145,6 +169,46 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
145
169
|
}
|
|
146
170
|
};
|
|
147
171
|
|
|
172
|
+
// ../connectors/src/setup-flow.ts
|
|
173
|
+
async function runSetupFlow(flow, params, ctx, config) {
|
|
174
|
+
const runtime = {
|
|
175
|
+
params,
|
|
176
|
+
language: ctx.language,
|
|
177
|
+
config
|
|
178
|
+
};
|
|
179
|
+
let state = flow.initialState();
|
|
180
|
+
let answerIdx = 0;
|
|
181
|
+
for (const step of flow.steps) {
|
|
182
|
+
const ans = ctx.answers[answerIdx];
|
|
183
|
+
if (ans && ans.questionSlug === step.slug) {
|
|
184
|
+
state = step.applyAnswer(state, ans.answer);
|
|
185
|
+
answerIdx += 1;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (step.type === "text") {
|
|
189
|
+
return {
|
|
190
|
+
type: "nextQuestion",
|
|
191
|
+
questionSlug: step.slug,
|
|
192
|
+
question: step.question[ctx.language],
|
|
193
|
+
questionType: "text"
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
const options = step.fetchOptions ? await step.fetchOptions(state, runtime) : [];
|
|
197
|
+
if (options.length === 0) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
type: "nextQuestion",
|
|
202
|
+
questionSlug: step.slug,
|
|
203
|
+
question: step.question[ctx.language],
|
|
204
|
+
questionType: step.type,
|
|
205
|
+
options
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
const dataInvestigationResult = await flow.finalize(state, runtime);
|
|
209
|
+
return { type: "fulfilled", dataInvestigationResult };
|
|
210
|
+
}
|
|
211
|
+
|
|
148
212
|
// ../connectors/src/auth-types.ts
|
|
149
213
|
var AUTH_TYPES = {
|
|
150
214
|
OAUTH: "oauth",
|
|
@@ -307,11 +371,83 @@ var shopifyOauthOnboarding = new ConnectorOnboarding({
|
|
|
307
371
|
// ../connectors/src/connectors/shopify-oauth/parameters.ts
|
|
308
372
|
var parameters = {};
|
|
309
373
|
|
|
374
|
+
// ../connectors/src/connectors/shopify-oauth/setup-flow.ts
|
|
375
|
+
var SHOPIFY_API_VERSION = "2024-10";
|
|
376
|
+
var SHOPIFY_OAUTH_SETUP_MAX_ENTITIES = 10;
|
|
377
|
+
var ENTITY_LABELS = {
|
|
378
|
+
products: { en: "Products", ja: "Products (\u5546\u54C1)" },
|
|
379
|
+
orders: { en: "Orders", ja: "Orders (\u6CE8\u6587)" },
|
|
380
|
+
customers: { en: "Customers", ja: "Customers (\u9867\u5BA2)" },
|
|
381
|
+
collections: { en: "Collections", ja: "Collections (\u30B3\u30EC\u30AF\u30B7\u30E7\u30F3)" },
|
|
382
|
+
inventory: { en: "Inventory items", ja: "Inventory items (\u5728\u5EAB)" },
|
|
383
|
+
discounts: { en: "Discounts", ja: "Discounts (\u5272\u5F15)" }
|
|
384
|
+
};
|
|
385
|
+
var ENTITY_VALUES = [
|
|
386
|
+
"products",
|
|
387
|
+
"orders",
|
|
388
|
+
"customers",
|
|
389
|
+
"collections",
|
|
390
|
+
"inventory",
|
|
391
|
+
"discounts"
|
|
392
|
+
];
|
|
393
|
+
var COUNT_PATHS = {
|
|
394
|
+
products: `/admin/api/${SHOPIFY_API_VERSION}/products/count.json`,
|
|
395
|
+
orders: `/admin/api/${SHOPIFY_API_VERSION}/orders/count.json?status=any`,
|
|
396
|
+
customers: `/admin/api/${SHOPIFY_API_VERSION}/customers/count.json`,
|
|
397
|
+
collections: `/admin/api/${SHOPIFY_API_VERSION}/custom_collections/count.json`,
|
|
398
|
+
inventory: null,
|
|
399
|
+
discounts: `/admin/api/${SHOPIFY_API_VERSION}/price_rules/count.json`
|
|
400
|
+
};
|
|
401
|
+
var shopifyOauthSetupFlow = {
|
|
402
|
+
initialState: () => ({}),
|
|
403
|
+
steps: [
|
|
404
|
+
{
|
|
405
|
+
slug: "entities",
|
|
406
|
+
type: "multiSelect",
|
|
407
|
+
question: {
|
|
408
|
+
ja: "\u5BFE\u8C61\u306E\u30A8\u30F3\u30C6\u30A3\u30C6\u30A3\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044\uFF08\u8907\u6570\u9078\u629E\u53EF\uFF09",
|
|
409
|
+
en: "Select target entities (multi-select allowed)"
|
|
410
|
+
},
|
|
411
|
+
async fetchOptions(_state, rt) {
|
|
412
|
+
return ENTITY_VALUES.map((value) => ({
|
|
413
|
+
value,
|
|
414
|
+
label: ENTITY_LABELS[value][rt.language]
|
|
415
|
+
}));
|
|
416
|
+
},
|
|
417
|
+
applyAnswer: (state, answer) => ({ ...state, entities: answer })
|
|
418
|
+
}
|
|
419
|
+
],
|
|
420
|
+
async finalize(state, rt) {
|
|
421
|
+
if (!state.entities) {
|
|
422
|
+
throw new Error("Shopify setup: incomplete state on finalize");
|
|
423
|
+
}
|
|
424
|
+
const selected = state.entities.filter(
|
|
425
|
+
(e) => ENTITY_VALUES.includes(e)
|
|
426
|
+
).slice(0, SHOPIFY_OAUTH_SETUP_MAX_ENTITIES);
|
|
427
|
+
const sections = ["## Shopify", ""];
|
|
428
|
+
for (const entity of selected) {
|
|
429
|
+
const path2 = COUNT_PATHS[entity];
|
|
430
|
+
let count = "available";
|
|
431
|
+
if (path2) {
|
|
432
|
+
const res = await rt.config.proxyFetch(path2, { method: "GET" });
|
|
433
|
+
if (res.ok) {
|
|
434
|
+
const data = await res.json();
|
|
435
|
+
if (typeof data.count === "number") count = String(data.count);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
sections.push(`### ${ENTITY_LABELS[entity].en}`, "");
|
|
439
|
+
sections.push(`Count: ${count}`, "");
|
|
440
|
+
}
|
|
441
|
+
return sections.join("\n");
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
|
|
310
445
|
// ../connectors/src/connectors/shopify-oauth/index.ts
|
|
311
446
|
var tools = { request: requestTool };
|
|
312
447
|
var shopifyOauthConnector = new ConnectorPlugin({
|
|
313
448
|
slug: "shopify",
|
|
314
449
|
authType: AUTH_TYPES.OAUTH,
|
|
450
|
+
skipConnectionCheckOnCreate: true,
|
|
315
451
|
name: "Shopify",
|
|
316
452
|
description: "Connect to Shopify for e-commerce data including products, orders, and customers using OAuth.",
|
|
317
453
|
iconUrl: "https://images.ctfassets.net/9ncizv60xc5y/57KEjZCBskKgSxgKyU4Sm0/117d681a410f48dc36f97cdd9c0593c5/shopify-icon.svg",
|
|
@@ -424,6 +560,7 @@ const data = await res.json();
|
|
|
424
560
|
\`\`\``
|
|
425
561
|
},
|
|
426
562
|
tools,
|
|
563
|
+
setup: (params, ctx, config) => runSetupFlow(shopifyOauthSetupFlow, params, ctx, config),
|
|
427
564
|
async checkConnection(_params, config) {
|
|
428
565
|
const { proxyFetch } = config;
|
|
429
566
|
try {
|
|
@@ -473,6 +610,7 @@ function resolveEnvVarOptional(entry, key) {
|
|
|
473
610
|
import { getContext } from "hono/context-storage";
|
|
474
611
|
import { getCookie } from "hono/cookie";
|
|
475
612
|
var APP_SESSION_COOKIE_NAME = "__Host-squadbase-session";
|
|
613
|
+
var TABLEAU_SESSION_SENTINEL_URL = "squadbase://tableau-session/";
|
|
476
614
|
function normalizeHeaders(input) {
|
|
477
615
|
const out = {};
|
|
478
616
|
if (!input) return out;
|
|
@@ -481,6 +619,11 @@ function normalizeHeaders(input) {
|
|
|
481
619
|
});
|
|
482
620
|
return out;
|
|
483
621
|
}
|
|
622
|
+
function extractInputUrl(input) {
|
|
623
|
+
if (typeof input === "string") return input;
|
|
624
|
+
if (input instanceof URL) return input.href;
|
|
625
|
+
return input.url;
|
|
626
|
+
}
|
|
484
627
|
function createSandboxProxyFetch(connectionId) {
|
|
485
628
|
return async (input, init) => {
|
|
486
629
|
const token = process.env.INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL;
|
|
@@ -490,10 +633,17 @@ function createSandboxProxyFetch(connectionId) {
|
|
|
490
633
|
"Connection proxy is not configured. Please check your deployment settings."
|
|
491
634
|
);
|
|
492
635
|
}
|
|
493
|
-
const originalUrl =
|
|
636
|
+
const originalUrl = extractInputUrl(input);
|
|
637
|
+
const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
|
|
638
|
+
if (originalUrl === TABLEAU_SESSION_SENTINEL_URL) {
|
|
639
|
+
const sessionUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
|
|
640
|
+
return fetch(sessionUrl, {
|
|
641
|
+
method: "POST",
|
|
642
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
643
|
+
});
|
|
644
|
+
}
|
|
494
645
|
const originalMethod = init?.method ?? "GET";
|
|
495
646
|
const originalBody = init?.body ? JSON.parse(init.body) : void 0;
|
|
496
|
-
const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
|
|
497
647
|
const proxyUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
|
|
498
648
|
return fetch(proxyUrl, {
|
|
499
649
|
method: "POST",
|
|
@@ -519,10 +669,9 @@ function createDeployedAppProxyFetch(connectionId) {
|
|
|
519
669
|
}
|
|
520
670
|
const baseDomain = process.env["SQUADBASE_APP_BASE_DOMAIN"] ?? "squadbase.app";
|
|
521
671
|
const proxyUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
|
|
672
|
+
const sessionUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
|
|
522
673
|
return async (input, init) => {
|
|
523
|
-
const originalUrl =
|
|
524
|
-
const originalMethod = init?.method ?? "GET";
|
|
525
|
-
const originalBody = init?.body ? JSON.parse(init.body) : void 0;
|
|
674
|
+
const originalUrl = extractInputUrl(input);
|
|
526
675
|
const c = getContext();
|
|
527
676
|
const appSession = getCookie(c, APP_SESSION_COOKIE_NAME);
|
|
528
677
|
if (!appSession) {
|
|
@@ -530,6 +679,14 @@ function createDeployedAppProxyFetch(connectionId) {
|
|
|
530
679
|
"No authentication method available for connection proxy."
|
|
531
680
|
);
|
|
532
681
|
}
|
|
682
|
+
if (originalUrl === TABLEAU_SESSION_SENTINEL_URL) {
|
|
683
|
+
return fetch(sessionUrl, {
|
|
684
|
+
method: "POST",
|
|
685
|
+
headers: { Authorization: `Bearer ${appSession}` }
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
const originalMethod = init?.method ?? "GET";
|
|
689
|
+
const originalBody = init?.body ? JSON.parse(init.body) : void 0;
|
|
533
690
|
return fetch(proxyUrl, {
|
|
534
691
|
method: "POST",
|
|
535
692
|
headers: {
|