@squadbase/vite-server 0.1.12-dev.93b8799 → 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.
Files changed (77) hide show
  1. package/dist/cli/index.js +12128 -934
  2. package/dist/connectors/airtable-oauth.js +248 -46
  3. package/dist/connectors/airtable.js +285 -51
  4. package/dist/connectors/amplitude.js +288 -47
  5. package/dist/connectors/anthropic.js +126 -47
  6. package/dist/connectors/asana.js +293 -49
  7. package/dist/connectors/attio.js +268 -49
  8. package/dist/connectors/aws-billing.js +253 -46
  9. package/dist/connectors/azure-sql.js +387 -102
  10. package/dist/connectors/backlog-api-key.js +283 -47
  11. package/dist/connectors/clickup.js +304 -49
  12. package/dist/connectors/cosmosdb.js +271 -50
  13. package/dist/connectors/customerio.js +285 -47
  14. package/dist/connectors/dbt.js +306 -47
  15. package/dist/connectors/freshdesk.js +308 -53
  16. package/dist/connectors/freshsales.js +299 -52
  17. package/dist/connectors/freshservice.js +327 -53
  18. package/dist/connectors/gamma.js +293 -52
  19. package/dist/connectors/gemini.js +125 -47
  20. package/dist/connectors/github.js +352 -49
  21. package/dist/connectors/gmail-oauth.js +170 -7
  22. package/dist/connectors/gmail.js +316 -47
  23. package/dist/connectors/google-ads.js +254 -46
  24. package/dist/connectors/google-analytics-oauth.js +276 -46
  25. package/dist/connectors/google-analytics.js +378 -49
  26. package/dist/connectors/google-audit-log.js +404 -47
  27. package/dist/connectors/google-calendar-oauth.js +225 -46
  28. package/dist/connectors/google-calendar.js +325 -47
  29. package/dist/connectors/google-docs.js +186 -6
  30. package/dist/connectors/google-drive.js +228 -5
  31. package/dist/connectors/google-search-console-oauth.js +222 -46
  32. package/dist/connectors/google-sheets.js +238 -47
  33. package/dist/connectors/google-slides.js +171 -6
  34. package/dist/connectors/grafana.js +298 -49
  35. package/dist/connectors/hubspot-oauth.js +174 -5
  36. package/dist/connectors/hubspot.js +272 -49
  37. package/dist/connectors/influxdb.js +382 -51
  38. package/dist/connectors/intercom-oauth.js +176 -5
  39. package/dist/connectors/intercom.js +268 -49
  40. package/dist/connectors/jdbc.js +728 -110
  41. package/dist/connectors/jira-api-key.js +292 -47
  42. package/dist/connectors/kintone-api-token.js +247 -47
  43. package/dist/connectors/kintone.js +294 -47
  44. package/dist/connectors/linear.js +296 -49
  45. package/dist/connectors/linkedin-ads.js +234 -50
  46. package/dist/connectors/mailchimp-oauth.js +234 -46
  47. package/dist/connectors/mailchimp.js +286 -49
  48. package/dist/connectors/meta-ads-oauth.js +239 -48
  49. package/dist/connectors/meta-ads.js +251 -50
  50. package/dist/connectors/mixpanel.js +304 -47
  51. package/dist/connectors/monday.js +326 -49
  52. package/dist/connectors/mongodb.js +285 -57
  53. package/dist/connectors/notion-oauth.js +197 -5
  54. package/dist/connectors/notion.js +289 -51
  55. package/dist/connectors/openai.js +125 -47
  56. package/dist/connectors/oracle.js +405 -103
  57. package/dist/connectors/outlook-oauth.js +170 -5
  58. package/dist/connectors/powerbi-oauth.js +217 -5
  59. package/dist/connectors/salesforce.js +350 -49
  60. package/dist/connectors/semrush.js +280 -49
  61. package/dist/connectors/sentry.js +255 -50
  62. package/dist/connectors/shopify-oauth.js +153 -5
  63. package/dist/connectors/shopify.js +323 -47
  64. package/dist/connectors/sqlserver.js +381 -102
  65. package/dist/connectors/stripe-api-key.js +235 -46
  66. package/dist/connectors/stripe-oauth.js +168 -5
  67. package/dist/connectors/supabase.js +269 -48
  68. package/dist/connectors/tableau.js +337 -206
  69. package/dist/connectors/tiktok-ads.js +245 -48
  70. package/dist/connectors/wix-store.js +286 -49
  71. package/dist/connectors/zendesk-oauth.js +205 -5
  72. package/dist/connectors/zendesk.js +324 -47
  73. package/dist/index.d.ts +149 -1
  74. package/dist/index.js +18297 -6886
  75. package/dist/main.js +12785 -1382
  76. package/dist/vite-plugin.js +12140 -936
  77. 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 = class {
3
- slug;
4
- name;
5
- description;
6
- envVarBaseKey;
7
- type;
8
- secret;
9
- required;
10
- constructor(config) {
11
- this.slug = config.slug;
12
- this.name = config.name;
13
- this.description = config.description;
14
- this.envVarBaseKey = config.envVarBaseKey;
15
- this.type = config.type;
16
- this.secret = config.secret;
17
- this.required = config.required;
18
- }
19
- /**
20
- * Get the parameter value from a ConnectorConnectionObject.
21
- */
22
- getValue(connection2) {
23
- const param = connection2.parameters.find(
24
- (p) => p.parameterSlug === this.slug
25
- );
26
- if (!param || param.value == null) {
27
- throw new Error(
28
- `Parameter "${this.slug}" not found or has no value in connection "${connection2.id}"`
29
- );
30
- }
31
- return param.value;
32
- }
33
- /**
34
- * Try to get the parameter value. Returns undefined if not found (for optional params).
35
- */
36
- tryGetValue(connection2) {
37
- const param = connection2.parameters.find(
38
- (p) => p.parameterSlug === this.slug
39
- );
40
- if (!param || param.value == null) return void 0;
41
- return param.value;
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/tableau/parameters.ts
57
+ init_parameter_definition();
46
58
  var parameters = {
47
59
  serverUrl: new ParameterDefinition({
48
60
  slug: "server-url",
@@ -93,67 +105,16 @@ var parameters = {
93
105
 
94
106
  // ../connectors/src/connectors/tableau/sdk/index.ts
95
107
  var DEFAULT_API_VERSION = "3.28";
96
- function createClient(params) {
108
+ var TABLEAU_SESSION_SENTINEL_URL = "squadbase://tableau-session/";
109
+ function createClient(params, fetchFn = fetch) {
97
110
  const serverUrl = params[parameters.serverUrl.slug];
98
- const siteContentUrl = params[parameters.siteContentUrl.slug];
99
- const patName = params[parameters.patName.slug];
100
- const patSecret = params[parameters.patSecret.slug];
101
111
  const apiVersion = params[parameters.apiVersion.slug] || DEFAULT_API_VERSION;
102
- if (!serverUrl || siteContentUrl == null || !patName || !patSecret) {
103
- throw new Error(
104
- `tableau: missing required parameters: ${parameters.serverUrl.slug}, ${parameters.siteContentUrl.slug}, ${parameters.patName.slug}, ${parameters.patSecret.slug}`
105
- );
112
+ if (!serverUrl) {
113
+ throw new Error(`tableau: missing required parameter: ${parameters.serverUrl.slug}`);
106
114
  }
107
115
  const baseUrl = `${serverUrl.replace(/\/$/, "")}/api/${apiVersion}`;
108
- let session = null;
109
- let inFlightSignIn = null;
110
- async function signIn2() {
111
- const res = await fetch(`${baseUrl}/auth/signin`, {
112
- method: "POST",
113
- headers: {
114
- "Content-Type": "application/json",
115
- Accept: "application/json"
116
- },
117
- body: JSON.stringify({
118
- credentials: {
119
- personalAccessTokenName: patName,
120
- personalAccessTokenSecret: patSecret,
121
- site: { contentUrl: siteContentUrl }
122
- }
123
- })
124
- });
125
- if (!res.ok) {
126
- const body = await res.text();
127
- throw new Error(`tableau: sign-in failed (${res.status}): ${body}`);
128
- }
129
- const data = await res.json();
130
- return {
131
- authToken: data.credentials.token,
132
- siteId: data.credentials.site.id,
133
- userId: data.credentials.user.id,
134
- expiresAt: Date.now() + 30 * 60 * 1e3
135
- };
136
- }
137
- async function ensureSession(options) {
138
- if (options?.forceRefresh) {
139
- if (session && (!options.invalidateToken || session.authToken === options.invalidateToken)) {
140
- session = null;
141
- }
142
- } else if (session && session.expiresAt > Date.now() + 6e4) {
143
- return session;
144
- }
145
- if (inFlightSignIn) return inFlightSignIn;
146
- inFlightSignIn = signIn2().then((s) => {
147
- session = s;
148
- return s;
149
- }).finally(() => {
150
- inFlightSignIn = null;
151
- });
152
- return inFlightSignIn;
153
- }
154
- function buildRequestInit(s, init) {
116
+ function buildRequestInit(init) {
155
117
  const headers = new Headers(init?.headers);
156
- headers.set("X-Tableau-Auth", s.authToken);
157
118
  if (!headers.has("Accept")) headers.set("Accept", "application/json");
158
119
  if (!headers.has("Content-Type") && init?.body) {
159
120
  headers.set("Content-Type", "application/json");
@@ -162,22 +123,8 @@ function createClient(params) {
162
123
  }
163
124
  async function request(path2, init) {
164
125
  const trimmedPath = path2.replace(/^([^/])/, "/$1");
165
- let attempt = 0;
166
- while (true) {
167
- const s = await ensureSession();
168
- const url = `${baseUrl}${trimmedPath.replace(/\{siteId\}/g, s.siteId)}`;
169
- const response = await fetch(url, buildRequestInit(s, init));
170
- if (response.status === 401 && attempt === 0) {
171
- await response.text().catch(() => void 0);
172
- await ensureSession({
173
- forceRefresh: true,
174
- invalidateToken: s.authToken
175
- });
176
- attempt++;
177
- continue;
178
- }
179
- return response;
180
- }
126
+ const url = `${baseUrl}${trimmedPath}`;
127
+ return fetchFn(url, buildRequestInit(init));
181
128
  }
182
129
  async function getJson(path2) {
183
130
  const res = await request(path2);
@@ -200,8 +147,19 @@ function createClient(params) {
200
147
  return {
201
148
  request,
202
149
  async getSession() {
203
- const s = await ensureSession();
204
- return { authToken: s.authToken, siteId: s.siteId, userId: s.userId };
150
+ const res = await fetchFn(TABLEAU_SESSION_SENTINEL_URL, { method: "POST" });
151
+ if (!res.ok) {
152
+ const body = await res.text().catch(() => "(unreadable)");
153
+ throw new Error(
154
+ `tableau: failed to fetch session (${res.status}): ${body}`
155
+ );
156
+ }
157
+ const data = await res.json();
158
+ return {
159
+ authToken: data.authToken,
160
+ siteId: data.siteId,
161
+ userId: data.userId
162
+ };
205
163
  },
206
164
  async listProjects(options) {
207
165
  return getJson(
@@ -306,6 +264,20 @@ var ConnectorPlugin = class _ConnectorPlugin {
306
264
  * `runSetupFlow` from `setup-flow.ts`.
307
265
  */
308
266
  setup;
267
+ /**
268
+ * Opt-out of the default "verify before save" behavior on connection
269
+ * creation. The backend invokes `checkConnection` synchronously while
270
+ * creating the connection and aborts (no row inserted) if it fails — this
271
+ * flag disables that for connectors where the check cannot succeed pre-save:
272
+ *
273
+ * - `squadbase-db` populates `connection-url` only after Neon provisioning
274
+ * - OAuth connectors require an OAuth-aware proxyFetch keyed by the
275
+ * connectionId, which doesn't exist until the row is saved
276
+ *
277
+ * Exceptions are the explicit position; new credential-input connectors get
278
+ * the default verify-on-create behavior without opt-in.
279
+ */
280
+ skipConnectionCheckOnCreate;
309
281
  constructor(config) {
310
282
  this.slug = config.slug;
311
283
  this.authType = config.authType;
@@ -323,6 +295,7 @@ var ConnectorPlugin = class _ConnectorPlugin {
323
295
  this.query = config.query;
324
296
  this.checkConnection = config.checkConnection;
325
297
  this.setup = config.setup;
298
+ this.skipConnectionCheckOnCreate = config.skipConnectionCheckOnCreate;
326
299
  }
327
300
  get connectorKey() {
328
301
  return _ConnectorPlugin.deriveKey(this.slug, this.authType);
@@ -387,6 +360,51 @@ var ConnectorPlugin = class _ConnectorPlugin {
387
360
  }
388
361
  };
389
362
 
363
+ // ../connectors/src/setup-flow.ts
364
+ async function runSetupFlow(flow, params, ctx, config) {
365
+ const runtime = {
366
+ params,
367
+ language: ctx.language,
368
+ config
369
+ };
370
+ let state = flow.initialState();
371
+ let answerIdx = 0;
372
+ for (const step of flow.steps) {
373
+ const ans = ctx.answers[answerIdx];
374
+ if (ans && ans.questionSlug === step.slug) {
375
+ state = step.applyAnswer(state, ans.answer);
376
+ answerIdx += 1;
377
+ continue;
378
+ }
379
+ if (step.type === "text") {
380
+ return {
381
+ type: "nextQuestion",
382
+ questionSlug: step.slug,
383
+ question: step.question[ctx.language],
384
+ questionType: "text"
385
+ };
386
+ }
387
+ const options = step.fetchOptions ? await step.fetchOptions(state, runtime) : [];
388
+ if (options.length === 0) {
389
+ continue;
390
+ }
391
+ return {
392
+ type: "nextQuestion",
393
+ questionSlug: step.slug,
394
+ question: step.question[ctx.language],
395
+ questionType: step.type,
396
+ options
397
+ };
398
+ }
399
+ const dataInvestigationResult = await flow.finalize(state, runtime);
400
+ return { type: "fulfilled", dataInvestigationResult };
401
+ }
402
+ async function resolveSetupSelection(params) {
403
+ const { selected, allSentinel, fetchAll, limit } = params;
404
+ const resolved = selected.includes(allSentinel) ? await fetchAll() : selected.filter((v) => v !== allSentinel);
405
+ return resolved.slice(0, limit);
406
+ }
407
+
390
408
  // ../connectors/src/auth-types.ts
391
409
  var AUTH_TYPES = {
392
410
  OAUTH: "oauth",
@@ -429,82 +447,190 @@ var tableauOnboarding = new ConnectorOnboarding({
429
447
  }
430
448
  });
431
449
 
432
- // ../connectors/src/connectors/tableau/tools/request.ts
433
- import { z } from "zod";
450
+ // ../connectors/src/connectors/tableau/utils.ts
434
451
  var DEFAULT_API_VERSION2 = "3.28";
435
- var REQUEST_TIMEOUT_MS = 6e4;
436
- var sessionCache = /* @__PURE__ */ new Map();
437
- var inFlightSignIns = /* @__PURE__ */ new Map();
438
- function sessionCacheKey(serverUrl, siteContentUrl, patName) {
439
- return `${serverUrl}|${siteContentUrl}|${patName}`;
440
- }
441
- function buildBaseUrl(serverUrl, apiVersion) {
452
+ function buildTableauBaseUrl(params) {
453
+ const serverUrl = params[parameters.serverUrl.slug];
454
+ const apiVersion = params[parameters.apiVersion.slug] || DEFAULT_API_VERSION2;
455
+ if (!serverUrl) {
456
+ throw new Error(
457
+ `tableau: missing required parameter: ${parameters.serverUrl.slug}`
458
+ );
459
+ }
442
460
  return `${serverUrl.replace(/\/$/, "")}/api/${apiVersion}`;
443
461
  }
444
- async function signIn(serverUrl, apiVersion, siteContentUrl, patName, patSecret) {
445
- const url = `${buildBaseUrl(serverUrl, apiVersion)}/auth/signin`;
446
- const body = {
447
- credentials: {
448
- personalAccessTokenName: patName,
449
- personalAccessTokenSecret: patSecret,
450
- site: { contentUrl: siteContentUrl }
462
+ async function tableauProxyApiFetch(proxyFetch, params, path2, init) {
463
+ const baseUrl = buildTableauBaseUrl(params);
464
+ const trimmedPath = path2.replace(/^([^/])/, "/$1");
465
+ return proxyFetch(`${baseUrl}${trimmedPath}`, init);
466
+ }
467
+
468
+ // ../connectors/src/connectors/tableau/setup-flow.ts
469
+ var ALL_PROJECTS = "__ALL_PROJECTS__";
470
+ var TABLEAU_SETUP_MAX_PROJECTS = 10;
471
+ var PAGE_SIZE = 100;
472
+ async function listAllProjects(rt) {
473
+ const all = [];
474
+ let pageNumber = 1;
475
+ while (all.length < 500) {
476
+ const res = await tableauProxyApiFetch(
477
+ rt.config.proxyFetch,
478
+ rt.params,
479
+ `/sites/{siteId}/projects?pageSize=${PAGE_SIZE}&pageNumber=${pageNumber}`
480
+ );
481
+ if (!res.ok) {
482
+ const body = await res.text().catch(() => res.statusText);
483
+ throw new Error(`tableau: listProjects failed (${res.status}): ${body}`);
451
484
  }
452
- };
485
+ const data = await res.json();
486
+ const batch = data.projects?.project ?? [];
487
+ all.push(...batch);
488
+ const total = Number(data.pagination?.totalAvailable ?? "0");
489
+ if (!batch.length || all.length >= total) break;
490
+ pageNumber += 1;
491
+ }
492
+ return all;
493
+ }
494
+ async function listProjectResources(rt, resource) {
495
+ const all = [];
496
+ let pageNumber = 1;
497
+ while (all.length < 1e3) {
498
+ const res = await tableauProxyApiFetch(
499
+ rt.config.proxyFetch,
500
+ rt.params,
501
+ `/sites/{siteId}/${resource}?pageSize=${PAGE_SIZE}&pageNumber=${pageNumber}`
502
+ );
503
+ if (!res.ok) {
504
+ const body = await res.text().catch(() => res.statusText);
505
+ throw new Error(
506
+ `tableau: list ${resource} failed (${res.status}): ${body}`
507
+ );
508
+ }
509
+ const data = await res.json();
510
+ const container = data[resource];
511
+ const batch = (resource === "workbooks" ? container?.workbook : container?.datasource) ?? [];
512
+ all.push(...batch);
513
+ const total = Number(data.pagination?.totalAvailable ?? "0");
514
+ if (!batch.length || all.length >= total) break;
515
+ pageNumber += 1;
516
+ }
517
+ return all;
518
+ }
519
+ var tableauSetupFlow = {
520
+ initialState: () => ({}),
521
+ steps: [
522
+ {
523
+ slug: "projects",
524
+ type: "multiSelect",
525
+ question: {
526
+ ja: "\u5BFE\u8C61\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044\uFF08\u8907\u6570\u9078\u629E\u53EF\uFF09",
527
+ en: "Select target projects (multi-select allowed)"
528
+ },
529
+ async fetchOptions(_state, rt) {
530
+ const projects = await listAllProjects(rt);
531
+ const projectOptions = projects.filter((p) => p.id && p.name).map((p) => ({ value: p.id, label: p.name }));
532
+ return [
533
+ {
534
+ value: ALL_PROJECTS,
535
+ label: rt.language === "ja" ? "\u3059\u3079\u3066\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8" : "All projects"
536
+ },
537
+ ...projectOptions
538
+ ];
539
+ },
540
+ applyAnswer: (state, answer) => ({ ...state, projects: answer })
541
+ }
542
+ ],
543
+ async finalize(state, rt) {
544
+ if (!state.projects) {
545
+ throw new Error("Tableau setup: incomplete state on finalize");
546
+ }
547
+ const allProjects = await listAllProjects(rt);
548
+ const projectById = new Map(allProjects.map((p) => [p.id, p]));
549
+ const targetIds = await resolveSetupSelection({
550
+ selected: state.projects,
551
+ allSentinel: ALL_PROJECTS,
552
+ fetchAll: async () => allProjects.map((p) => p.id).filter((id) => id),
553
+ limit: TABLEAU_SETUP_MAX_PROJECTS
554
+ });
555
+ const sections = ["## Tableau", ""];
556
+ if (!targetIds.length) {
557
+ sections.push("_No projects selected._", "");
558
+ return sections.join("\n");
559
+ }
560
+ const [workbooks, datasources] = await Promise.all([
561
+ listProjectResources(rt, "workbooks"),
562
+ listProjectResources(rt, "datasources")
563
+ ]);
564
+ const workbooksByProject = /* @__PURE__ */ new Map();
565
+ for (const wb of workbooks) {
566
+ const pid = wb.project?.id;
567
+ if (!pid) continue;
568
+ const bucket = workbooksByProject.get(pid) ?? [];
569
+ bucket.push(wb);
570
+ workbooksByProject.set(pid, bucket);
571
+ }
572
+ const datasourcesByProject = /* @__PURE__ */ new Map();
573
+ for (const ds of datasources) {
574
+ const pid = ds.project?.id;
575
+ if (!pid) continue;
576
+ const bucket = datasourcesByProject.get(pid) ?? [];
577
+ bucket.push(ds);
578
+ datasourcesByProject.set(pid, bucket);
579
+ }
580
+ for (const id of targetIds) {
581
+ const project = projectById.get(id);
582
+ const name = project?.name ?? id;
583
+ sections.push(`### Project: ${name}`, "", `- id: \`${id}\``);
584
+ const projectWorkbooks = workbooksByProject.get(id) ?? [];
585
+ sections.push(`- Workbooks (${projectWorkbooks.length}):`);
586
+ for (const wb of projectWorkbooks.slice(0, 25)) {
587
+ sections.push(` - ${wb.name ?? wb.id ?? "(unknown)"}`);
588
+ }
589
+ if (projectWorkbooks.length > 25) {
590
+ sections.push(` - \u2026and ${projectWorkbooks.length - 25} more`);
591
+ }
592
+ const projectDatasources = datasourcesByProject.get(id) ?? [];
593
+ sections.push(`- Datasources (${projectDatasources.length}):`);
594
+ for (const ds of projectDatasources.slice(0, 25)) {
595
+ sections.push(` - ${ds.name ?? ds.id ?? "(unknown)"}`);
596
+ }
597
+ if (projectDatasources.length > 25) {
598
+ sections.push(` - \u2026and ${projectDatasources.length - 25} more`);
599
+ }
600
+ sections.push("");
601
+ }
602
+ return sections.join("\n");
603
+ }
604
+ };
605
+
606
+ // ../connectors/src/connectors/tableau/tools/request.ts
607
+ import { z } from "zod";
608
+ var DEFAULT_API_VERSION3 = "3.28";
609
+ var REQUEST_TIMEOUT_MS = 6e4;
610
+ async function fetchTableauSession(connectionId) {
611
+ const token = process.env.INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL;
612
+ const sandboxId = process.env.INTERNAL_SQUADBASE_SANDBOX_ID;
613
+ if (!token || !sandboxId) {
614
+ throw new Error(
615
+ "Tableau session manager is not configured. Missing INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL or INTERNAL_SQUADBASE_SANDBOX_ID."
616
+ );
617
+ }
618
+ const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
619
+ const url = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
453
620
  const res = await fetch(url, {
454
621
  method: "POST",
455
- headers: {
456
- "Content-Type": "application/json",
457
- Accept: "application/json"
458
- },
459
- body: JSON.stringify(body)
622
+ headers: { Authorization: `Bearer ${token}` }
460
623
  });
461
624
  if (!res.ok) {
462
- const errorText = await res.text().catch(() => res.statusText);
625
+ const errBody = await res.text().catch(() => res.statusText);
463
626
  throw new Error(
464
- `Tableau sign-in failed: HTTP ${res.status} ${errorText}`
627
+ `Failed to fetch Tableau session from backend-api (HTTP ${res.status}): ${errBody}`
465
628
  );
466
629
  }
467
- const data = await res.json();
468
- return {
469
- authToken: data.credentials.token,
470
- siteId: data.credentials.site.id,
471
- userId: data.credentials.user.id,
472
- expiresAt: Date.now() + 30 * 60 * 1e3
473
- };
630
+ return await res.json();
474
631
  }
475
- async function getSession(serverUrl, apiVersion, siteContentUrl, patName, patSecret, { forceRefresh = false } = {}) {
476
- const cacheKey = sessionCacheKey(serverUrl, siteContentUrl, patName);
477
- if (forceRefresh) {
478
- sessionCache.delete(cacheKey);
479
- } else {
480
- const cached = sessionCache.get(cacheKey);
481
- if (cached && cached.expiresAt > Date.now() + 6e4) {
482
- return cached;
483
- }
484
- }
485
- const existing = inFlightSignIns.get(cacheKey);
486
- if (existing) return existing;
487
- const promise = signIn(
488
- serverUrl,
489
- apiVersion,
490
- siteContentUrl,
491
- patName,
492
- patSecret
493
- ).then((session) => {
494
- sessionCache.set(cacheKey, session);
495
- return session;
496
- }).finally(() => {
497
- inFlightSignIns.delete(cacheKey);
498
- });
499
- inFlightSignIns.set(cacheKey, promise);
500
- return promise;
501
- }
502
- function invalidateSession(serverUrl, siteContentUrl, patName, staleAuthToken) {
503
- const cacheKey = sessionCacheKey(serverUrl, siteContentUrl, patName);
504
- const current = sessionCache.get(cacheKey);
505
- if (current && current.authToken === staleAuthToken) {
506
- sessionCache.delete(cacheKey);
507
- }
632
+ function buildBaseUrl(serverUrl, apiVersion) {
633
+ return `${serverUrl.replace(/\/$/, "")}/api/${apiVersion}`;
508
634
  }
509
635
  var inputSchema = z.object({
510
636
  toolUseIntent: z.string().optional().describe(
@@ -538,8 +664,8 @@ var outputSchema = z.discriminatedUnion("success", [
538
664
  var requestTool = new ConnectorTool({
539
665
  name: "request",
540
666
  description: `Send authenticated requests to the Tableau REST API.
541
- The tool signs in once with the configured Personal Access Token (PAT) to obtain an X-Tableau-Auth token (cached per connection), then forwards the request with the token attached.
542
- All paths are relative to {serverUrl}/api/{apiVersion}. Use the literal placeholder \`{siteId}\` in the path \u2014 it is automatically substituted with the site ID returned from sign-in.
667
+ The X-Tableau-Auth token is issued and managed centrally by the Squadbase backend so concurrent processes (chat & deployed app) share a single PAT sign-in.
668
+ All paths are relative to {serverUrl}/api/{apiVersion}. Use the literal placeholder \`{siteId}\` in the path \u2014 it is automatically substituted with the signed-in site ID.
543
669
  Accept and Content-Type headers default to application/json so list responses come back as JSON instead of Tableau's default XML.`,
544
670
  inputSchema,
545
671
  outputSchema,
@@ -556,10 +682,7 @@ Accept and Content-Type headers default to application/json so list responses co
556
682
  );
557
683
  try {
558
684
  const serverUrl = parameters.serverUrl.getValue(connection2);
559
- const siteContentUrl = parameters.siteContentUrl.getValue(connection2);
560
- const patName = parameters.patName.getValue(connection2);
561
- const patSecret = parameters.patSecret.getValue(connection2);
562
- const apiVersion = parameters.apiVersion.tryGetValue(connection2) || DEFAULT_API_VERSION2;
685
+ const apiVersion = parameters.apiVersion.tryGetValue(connection2) || DEFAULT_API_VERSION3;
563
686
  const trimmedPath = path2.trim().replace(/^([^/])/, "/$1");
564
687
  const queryString = queryParams ? `?${new URLSearchParams(queryParams).toString()}` : "";
565
688
  const baseUrl = buildBaseUrl(serverUrl, apiVersion);
@@ -568,14 +691,7 @@ Accept and Content-Type headers default to application/json so list responses co
568
691
  try {
569
692
  let attempt = 0;
570
693
  while (true) {
571
- const session = await getSession(
572
- serverUrl,
573
- apiVersion,
574
- siteContentUrl,
575
- patName,
576
- patSecret,
577
- { forceRefresh: attempt > 0 }
578
- );
694
+ const session = await fetchTableauSession(connectionId);
579
695
  const resolvedPath = trimmedPath.replace(
580
696
  /\{siteId\}/g,
581
697
  session.siteId
@@ -603,12 +719,6 @@ Accept and Content-Type headers default to application/json so list responses co
603
719
  }
604
720
  })() : null;
605
721
  if (response.status === 401 && attempt === 0) {
606
- invalidateSession(
607
- serverUrl,
608
- siteContentUrl,
609
- patName,
610
- session.authToken
611
- );
612
722
  attempt++;
613
723
  continue;
614
724
  }
@@ -643,7 +753,7 @@ var tableauConnector = new ConnectorPlugin({
643
753
  systemPrompt: {
644
754
  en: `### Tools
645
755
 
646
- - \`tableau_request\`: The only way to call the Tableau REST API. Use it for projects, workbooks, views, data sources, users, and permissions. The tool signs in once with the configured PAT to obtain an \`X-Tableau-Auth\` token (cached), then forwards each request with the token attached. Use the literal \`{siteId}\` placeholder in the path \u2014 it is replaced with the session's site ID automatically.
756
+ - \`tableau_request\`: The only way to call the Tableau REST API. Use it for projects, workbooks, views, data sources, users, and permissions. The \`X-Tableau-Auth\` token is issued and managed centrally by the Squadbase backend so concurrent processes (chat & deployed app) share a single PAT sign-in. Use the literal \`{siteId}\` placeholder in the path \u2014 it is replaced with the session's site ID automatically.
647
757
 
648
758
  ### Business Logic
649
759
 
@@ -700,7 +810,7 @@ export default async function handler(c: Context) {
700
810
  - Combine: \`filter=ownerName:eq:alice,tags:in:[finance]\``,
701
811
  ja: `### \u30C4\u30FC\u30EB
702
812
 
703
- - \`tableau_request\`: Tableau REST API \u3092\u547C\u3073\u51FA\u3059\u552F\u4E00\u306E\u624B\u6BB5\u3067\u3059\u3002\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3001\u30EF\u30FC\u30AF\u30D6\u30C3\u30AF\u3001\u30D3\u30E5\u30FC\u3001\u30C7\u30FC\u30BF\u30BD\u30FC\u30B9\u3001\u30E6\u30FC\u30B6\u30FC\u3001\u6A29\u9650\u306A\u3069\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002\u8A2D\u5B9A\u3055\u308C\u305F PAT \u3067\u4E00\u5EA6\u30B5\u30A4\u30F3\u30A4\u30F3\u3057\u3066 \`X-Tableau-Auth\` \u30C8\u30FC\u30AF\u30F3\u3092\u53D6\u5F97 (\u30AD\u30E3\u30C3\u30B7\u30E5) \u3057\u3001\u5404\u30EA\u30AF\u30A8\u30B9\u30C8\u306B\u305D\u306E\u30C8\u30FC\u30AF\u30F3\u3092\u4ED8\u3051\u3066\u9001\u4FE1\u3057\u307E\u3059\u3002\u30D1\u30B9\u5185\u306B\u306F \`{siteId}\` \u30D7\u30EC\u30FC\u30B9\u30DB\u30EB\u30C0\u30FC\u3092\u4F7F\u3063\u3066\u304F\u3060\u3055\u3044 \u2014 \u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u30B5\u30A4\u30C8 ID \u3067\u81EA\u52D5\u7F6E\u63DB\u3055\u308C\u307E\u3059\u3002
813
+ - \`tableau_request\`: Tableau REST API \u3092\u547C\u3073\u51FA\u3059\u552F\u4E00\u306E\u624B\u6BB5\u3067\u3059\u3002\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3001\u30EF\u30FC\u30AF\u30D6\u30C3\u30AF\u3001\u30D3\u30E5\u30FC\u3001\u30C7\u30FC\u30BF\u30BD\u30FC\u30B9\u3001\u30E6\u30FC\u30B6\u30FC\u3001\u6A29\u9650\u306A\u3069\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002\`X-Tableau-Auth\` \u30C8\u30FC\u30AF\u30F3\u306F Squadbase \u30D0\u30C3\u30AF\u30A8\u30F3\u30C9\u3067\u4E00\u5143\u7BA1\u7406\u3055\u308C\u3001\u8907\u6570\u30D7\u30ED\u30BB\u30B9 (\u30C1\u30E3\u30C3\u30C8\u30FB\u30C7\u30D7\u30ED\u30A4\u6E08\u307F\u30A2\u30D7\u30EA) \u3067\u540C\u3058 PAT \u30B5\u30A4\u30F3\u30A4\u30F3\u3092\u5171\u6709\u3057\u307E\u3059\u3002\u30D1\u30B9\u5185\u306B\u306F \`{siteId}\` \u30D7\u30EC\u30FC\u30B9\u30DB\u30EB\u30C0\u30FC\u3092\u4F7F\u3063\u3066\u304F\u3060\u3055\u3044 \u2014 \u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u30B5\u30A4\u30C8 ID \u3067\u81EA\u52D5\u7F6E\u63DB\u3055\u308C\u307E\u3059\u3002
704
814
 
705
815
  ### Business Logic
706
816
 
@@ -757,6 +867,7 @@ export default async function handler(c: Context) {
757
867
  - \u7D44\u307F\u5408\u308F\u305B: \`filter=ownerName:eq:alice,tags:in:[finance]\``
758
868
  },
759
869
  tools,
870
+ setup: (params, ctx, config) => runSetupFlow(tableauSetupFlow, params, ctx, config),
760
871
  async checkConnection(params) {
761
872
  const serverUrl = params[parameters.serverUrl.slug];
762
873
  const siteContentUrl = params[parameters.siteContentUrl.slug];
@@ -830,6 +941,7 @@ function resolveEnvVarOptional(entry, key) {
830
941
  import { getContext } from "hono/context-storage";
831
942
  import { getCookie } from "hono/cookie";
832
943
  var APP_SESSION_COOKIE_NAME = "__Host-squadbase-session";
944
+ var TABLEAU_SESSION_SENTINEL_URL2 = "squadbase://tableau-session/";
833
945
  function normalizeHeaders(input) {
834
946
  const out = {};
835
947
  if (!input) return out;
@@ -838,6 +950,11 @@ function normalizeHeaders(input) {
838
950
  });
839
951
  return out;
840
952
  }
953
+ function extractInputUrl(input) {
954
+ if (typeof input === "string") return input;
955
+ if (input instanceof URL) return input.href;
956
+ return input.url;
957
+ }
841
958
  function createSandboxProxyFetch(connectionId) {
842
959
  return async (input, init) => {
843
960
  const token = process.env.INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL;
@@ -847,10 +964,17 @@ function createSandboxProxyFetch(connectionId) {
847
964
  "Connection proxy is not configured. Please check your deployment settings."
848
965
  );
849
966
  }
850
- const originalUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
967
+ const originalUrl = extractInputUrl(input);
968
+ const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
969
+ if (originalUrl === TABLEAU_SESSION_SENTINEL_URL2) {
970
+ const sessionUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
971
+ return fetch(sessionUrl, {
972
+ method: "POST",
973
+ headers: { Authorization: `Bearer ${token}` }
974
+ });
975
+ }
851
976
  const originalMethod = init?.method ?? "GET";
852
977
  const originalBody = init?.body ? JSON.parse(init.body) : void 0;
853
- const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
854
978
  const proxyUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
855
979
  return fetch(proxyUrl, {
856
980
  method: "POST",
@@ -876,10 +1000,9 @@ function createDeployedAppProxyFetch(connectionId) {
876
1000
  }
877
1001
  const baseDomain = process.env["SQUADBASE_APP_BASE_DOMAIN"] ?? "squadbase.app";
878
1002
  const proxyUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
1003
+ const sessionUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
879
1004
  return async (input, init) => {
880
- const originalUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
881
- const originalMethod = init?.method ?? "GET";
882
- const originalBody = init?.body ? JSON.parse(init.body) : void 0;
1005
+ const originalUrl = extractInputUrl(input);
883
1006
  const c = getContext();
884
1007
  const appSession = getCookie(c, APP_SESSION_COOKIE_NAME);
885
1008
  if (!appSession) {
@@ -887,6 +1010,14 @@ function createDeployedAppProxyFetch(connectionId) {
887
1010
  "No authentication method available for connection proxy."
888
1011
  );
889
1012
  }
1013
+ if (originalUrl === TABLEAU_SESSION_SENTINEL_URL2) {
1014
+ return fetch(sessionUrl, {
1015
+ method: "POST",
1016
+ headers: { Authorization: `Bearer ${appSession}` }
1017
+ });
1018
+ }
1019
+ const originalMethod = init?.method ?? "GET";
1020
+ const originalBody = init?.body ? JSON.parse(init.body) : void 0;
890
1021
  return fetch(proxyUrl, {
891
1022
  method: "POST",
892
1023
  headers: {