@squadbase/vite-server 0.1.10 → 0.1.12-dev.822582a

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.
@@ -0,0 +1,720 @@
1
+ // ../connectors/src/connectors/powerbi-oauth/sdk/index.ts
2
+ var BASE_URL = "https://api.powerbi.com/v1.0/myorg";
3
+ function createClient(_params, fetchFn = fetch) {
4
+ function request(path2, init) {
5
+ const url = `${BASE_URL}${path2.startsWith("/") ? "" : "/"}${path2}`;
6
+ return fetchFn(url, init);
7
+ }
8
+ async function getJson(path2) {
9
+ const res = await request(path2);
10
+ if (!res.ok) {
11
+ const body = await res.text();
12
+ throw new Error(
13
+ `powerbi-oauth: GET ${path2} failed (${res.status}): ${body}`
14
+ );
15
+ }
16
+ return await res.json();
17
+ }
18
+ return {
19
+ request,
20
+ async listGroups(options) {
21
+ const qs = new URLSearchParams();
22
+ if (options?.top != null) qs.set("$top", String(options.top));
23
+ if (options?.skip != null) qs.set("$skip", String(options.skip));
24
+ if (options?.filter) qs.set("$filter", options.filter);
25
+ const query = qs.toString();
26
+ return getJson(
27
+ `/groups${query ? `?${query}` : ""}`
28
+ );
29
+ },
30
+ async listDatasets(groupId) {
31
+ return getJson(
32
+ `/groups/${encodeURIComponent(groupId)}/datasets`
33
+ );
34
+ },
35
+ async listReports(groupId) {
36
+ return getJson(
37
+ `/groups/${encodeURIComponent(groupId)}/reports`
38
+ );
39
+ },
40
+ async getDataset(groupId, datasetId) {
41
+ return getJson(
42
+ `/groups/${encodeURIComponent(groupId)}/datasets/${encodeURIComponent(datasetId)}`
43
+ );
44
+ },
45
+ async executeQueries(groupId, datasetId, queries, options) {
46
+ const res = await request(
47
+ `/groups/${encodeURIComponent(groupId)}/datasets/${encodeURIComponent(datasetId)}/executeQueries`,
48
+ {
49
+ method: "POST",
50
+ headers: { "Content-Type": "application/json" },
51
+ body: JSON.stringify({
52
+ queries: queries.map((q) => ({ query: q })),
53
+ serializerSettings: { includeNulls: options?.includeNulls ?? true },
54
+ ...options?.impersonatedUserName ? { impersonatedUserName: options.impersonatedUserName } : {}
55
+ })
56
+ }
57
+ );
58
+ if (!res.ok) {
59
+ const body = await res.text();
60
+ throw new Error(
61
+ `powerbi-oauth: executeQueries failed (${res.status}): ${body}`
62
+ );
63
+ }
64
+ return await res.json();
65
+ },
66
+ async refreshDataset(groupId, datasetId, options) {
67
+ const res = await request(
68
+ `/groups/${encodeURIComponent(groupId)}/datasets/${encodeURIComponent(datasetId)}/refreshes`,
69
+ {
70
+ method: "POST",
71
+ headers: { "Content-Type": "application/json" },
72
+ body: JSON.stringify({
73
+ notifyOption: options?.notifyOption ?? "NoNotification"
74
+ })
75
+ }
76
+ );
77
+ if (!res.ok) {
78
+ const body = await res.text();
79
+ throw new Error(
80
+ `powerbi-oauth: refreshDataset failed (${res.status}): ${body}`
81
+ );
82
+ }
83
+ }
84
+ };
85
+ }
86
+
87
+ // ../connectors/src/connector-onboarding.ts
88
+ var ConnectorOnboarding = class {
89
+ /** Phase 1: Connection setup instructions (optional — some connectors don't need this) */
90
+ connectionSetupInstructions;
91
+ /** Phase 2: Data overview instructions */
92
+ dataOverviewInstructions;
93
+ constructor(config) {
94
+ this.connectionSetupInstructions = config.connectionSetupInstructions;
95
+ this.dataOverviewInstructions = config.dataOverviewInstructions;
96
+ }
97
+ getConnectionSetupPrompt(language) {
98
+ return this.connectionSetupInstructions?.[language] ?? null;
99
+ }
100
+ getDataOverviewInstructions(language) {
101
+ return this.dataOverviewInstructions[language];
102
+ }
103
+ };
104
+
105
+ // ../connectors/src/connector-tool.ts
106
+ var ConnectorTool = class {
107
+ name;
108
+ description;
109
+ inputSchema;
110
+ outputSchema;
111
+ _execute;
112
+ constructor(config) {
113
+ this.name = config.name;
114
+ this.description = config.description;
115
+ this.inputSchema = config.inputSchema;
116
+ this.outputSchema = config.outputSchema;
117
+ this._execute = config.execute;
118
+ }
119
+ createTool(connections, config) {
120
+ return {
121
+ description: this.description,
122
+ inputSchema: this.inputSchema,
123
+ outputSchema: this.outputSchema,
124
+ execute: (input) => this._execute(input, connections, config)
125
+ };
126
+ }
127
+ };
128
+
129
+ // ../connectors/src/connector-plugin.ts
130
+ var ConnectorPlugin = class _ConnectorPlugin {
131
+ slug;
132
+ authType;
133
+ name;
134
+ description;
135
+ iconUrl;
136
+ parameters;
137
+ releaseFlag;
138
+ proxyPolicy;
139
+ experimentalAttributes;
140
+ categories;
141
+ onboarding;
142
+ systemPrompt;
143
+ tools;
144
+ query;
145
+ checkConnection;
146
+ constructor(config) {
147
+ this.slug = config.slug;
148
+ this.authType = config.authType;
149
+ this.name = config.name;
150
+ this.description = config.description;
151
+ this.iconUrl = config.iconUrl;
152
+ this.parameters = config.parameters;
153
+ this.releaseFlag = config.releaseFlag;
154
+ this.proxyPolicy = config.proxyPolicy;
155
+ this.experimentalAttributes = config.experimentalAttributes;
156
+ this.categories = config.categories ?? [];
157
+ this.onboarding = config.onboarding;
158
+ this.systemPrompt = config.systemPrompt;
159
+ this.tools = config.tools;
160
+ this.query = config.query;
161
+ this.checkConnection = config.checkConnection;
162
+ }
163
+ get connectorKey() {
164
+ return _ConnectorPlugin.deriveKey(this.slug, this.authType);
165
+ }
166
+ /**
167
+ * Create tools for connections that belong to this connector.
168
+ * Filters connections by connectorKey internally.
169
+ * Returns tools keyed as `${connectorKey}_${toolName}`.
170
+ */
171
+ createTools(connections, config, opts) {
172
+ const myConnections = connections.filter(
173
+ (c) => _ConnectorPlugin.deriveKey(c.connector.slug, c.connector.authType) === this.connectorKey
174
+ );
175
+ const result = {};
176
+ for (const t of Object.values(this.tools)) {
177
+ const tool = t.createTool(myConnections, config);
178
+ const originalToModelOutput = tool.toModelOutput;
179
+ result[`${this.connectorKey}_${t.name}`] = {
180
+ ...tool,
181
+ toModelOutput: async (options) => {
182
+ if (!originalToModelOutput) {
183
+ return opts.truncateOutput(options.output);
184
+ }
185
+ const modelOutput = await originalToModelOutput(options);
186
+ if (modelOutput.type === "text" || modelOutput.type === "json") {
187
+ return opts.truncateOutput(modelOutput.value);
188
+ }
189
+ return modelOutput;
190
+ }
191
+ };
192
+ }
193
+ return result;
194
+ }
195
+ static deriveKey(slug, authType) {
196
+ if (authType) return `${slug}-${authType}`;
197
+ const LEGACY_NULL_AUTH_TYPE_MAP = {
198
+ // user-password
199
+ "postgresql": "user-password",
200
+ "mysql": "user-password",
201
+ "clickhouse": "user-password",
202
+ "kintone": "user-password",
203
+ "squadbase-db": "user-password",
204
+ // service-account
205
+ "snowflake": "service-account",
206
+ "bigquery": "service-account",
207
+ "google-analytics": "service-account",
208
+ "google-calendar": "service-account",
209
+ "aws-athena": "service-account",
210
+ "redshift": "service-account",
211
+ // api-key
212
+ "databricks": "api-key",
213
+ "dbt": "api-key",
214
+ "airtable": "api-key",
215
+ "openai": "api-key",
216
+ "gemini": "api-key",
217
+ "anthropic": "api-key",
218
+ "wix-store": "api-key"
219
+ };
220
+ const fallbackAuthType = LEGACY_NULL_AUTH_TYPE_MAP[slug];
221
+ if (fallbackAuthType) return `${slug}-${fallbackAuthType}`;
222
+ return slug;
223
+ }
224
+ };
225
+
226
+ // ../connectors/src/auth-types.ts
227
+ var AUTH_TYPES = {
228
+ OAUTH: "oauth",
229
+ API_KEY: "api-key",
230
+ JWT: "jwt",
231
+ SERVICE_ACCOUNT: "service-account",
232
+ PAT: "pat",
233
+ USER_PASSWORD: "user-password"
234
+ };
235
+
236
+ // ../connectors/src/lib/normalize-path.ts
237
+ function normalizeRequestPath(path2, basePathSegment) {
238
+ let p = path2.trim();
239
+ if (!p.startsWith("/")) p = "/" + p;
240
+ if (p === basePathSegment || p.startsWith(basePathSegment + "/")) {
241
+ p = p.slice(basePathSegment.length) || "/";
242
+ }
243
+ return p;
244
+ }
245
+
246
+ // ../connectors/src/connectors/powerbi-oauth/tools/request.ts
247
+ import { z } from "zod";
248
+ var BASE_HOST = "https://api.powerbi.com";
249
+ var BASE_PATH_SEGMENT = "/v1.0/myorg";
250
+ var BASE_URL2 = `${BASE_HOST}${BASE_PATH_SEGMENT}`;
251
+ var REQUEST_TIMEOUT_MS = 6e4;
252
+ var cachedToken = null;
253
+ async function getProxyToken(config) {
254
+ if (cachedToken && cachedToken.expiresAt > Date.now() + 6e4) {
255
+ return cachedToken.token;
256
+ }
257
+ const url = `${config.appApiBaseUrl}/v0/database/${config.projectId}/environment/${config.environmentId}/oauth-request-proxy-token`;
258
+ const res = await fetch(url, {
259
+ method: "POST",
260
+ headers: {
261
+ "Content-Type": "application/json",
262
+ "x-api-key": config.appApiKey,
263
+ "project-id": config.projectId
264
+ },
265
+ body: JSON.stringify({
266
+ sandboxId: config.sandboxId,
267
+ issuedBy: "coding-agent"
268
+ })
269
+ });
270
+ if (!res.ok) {
271
+ const errorText = await res.text().catch(() => res.statusText);
272
+ throw new Error(
273
+ `Failed to get proxy token: HTTP ${res.status} ${errorText}`
274
+ );
275
+ }
276
+ const data = await res.json();
277
+ cachedToken = {
278
+ token: data.token,
279
+ expiresAt: new Date(data.expiresAt).getTime()
280
+ };
281
+ return data.token;
282
+ }
283
+ var inputSchema = z.object({
284
+ toolUseIntent: z.string().optional().describe(
285
+ "Brief description of what you intend to accomplish with this tool call"
286
+ ),
287
+ connectionId: z.string().describe("ID of the Power BI OAuth connection to use"),
288
+ method: z.enum(["GET", "POST", "PATCH", "PUT", "DELETE"]).describe(
289
+ "HTTP method. GET for reading workspaces/datasets/reports, POST for executeQueries / refresh, PATCH/PUT for updates, DELETE for removal."
290
+ ),
291
+ path: z.string().describe(
292
+ "API path appended to https://api.powerbi.com/v1.0/myorg (e.g., '/groups', '/groups/{groupId}/datasets'). The authenticated user must have access to the workspace."
293
+ ),
294
+ queryParams: z.record(z.string(), z.string()).optional().describe(
295
+ `Query parameters to append (e.g., { $top: '50', $filter: "name eq 'Sales'" }).`
296
+ ),
297
+ body: z.record(z.string(), z.unknown()).optional().describe(
298
+ "JSON request body for POST/PATCH/PUT/DELETE. Example executeQueries body: { queries: [{ query: 'EVALUATE TOPN(10, Sales)' }], serializerSettings: { includeNulls: true } }."
299
+ )
300
+ });
301
+ var outputSchema = z.discriminatedUnion("success", [
302
+ z.object({
303
+ success: z.literal(true),
304
+ status: z.number(),
305
+ data: z.unknown()
306
+ }),
307
+ z.object({
308
+ success: z.literal(false),
309
+ error: z.string()
310
+ })
311
+ ]);
312
+ var requestTool = new ConnectorTool({
313
+ name: "request",
314
+ description: `Send authenticated requests to the Power BI REST API v1.0 using the user's Microsoft Entra OAuth token (proxied automatically).
315
+ All paths are relative to https://api.powerbi.com/v1.0/myorg. Use the executeQueries endpoint to run DAX against a dataset.
316
+ The signed-in user must have access to the workspace; unlike Service Principals, OAuth users can also access "My workspace" via the \`/datasets\` (without \`/groups/\`) endpoints.`,
317
+ inputSchema,
318
+ outputSchema,
319
+ async execute({ connectionId, method, path: path2, queryParams, body }, connections, config) {
320
+ const connection2 = connections.find((c) => c.id === connectionId);
321
+ if (!connection2) {
322
+ return {
323
+ success: false,
324
+ error: `Connection ${connectionId} not found`
325
+ };
326
+ }
327
+ console.log(
328
+ `[connector-request] powerbi-oauth/${connection2.name}: ${method} ${path2}`
329
+ );
330
+ try {
331
+ const normalizedPath = normalizeRequestPath(path2, BASE_PATH_SEGMENT);
332
+ let url = `${BASE_URL2}${normalizedPath}`;
333
+ if (queryParams) {
334
+ const searchParams = new URLSearchParams(queryParams);
335
+ url += `?${searchParams.toString()}`;
336
+ }
337
+ const token = await getProxyToken(config.oauthProxy);
338
+ const proxyUrl = `https://${config.oauthProxy.sandboxId}.${config.oauthProxy.previewBaseDomain}/_sqcore/connections/${connectionId}/request`;
339
+ const controller = new AbortController();
340
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
341
+ try {
342
+ const response = await fetch(proxyUrl, {
343
+ method: "POST",
344
+ headers: {
345
+ "Content-Type": "application/json",
346
+ Authorization: `Bearer ${token}`
347
+ },
348
+ body: JSON.stringify({
349
+ url,
350
+ method,
351
+ ...body !== void 0 ? { body } : {}
352
+ }),
353
+ signal: controller.signal
354
+ });
355
+ const text = await response.text();
356
+ const data = text ? (() => {
357
+ try {
358
+ return JSON.parse(text);
359
+ } catch {
360
+ return text;
361
+ }
362
+ })() : null;
363
+ if (!response.ok) {
364
+ const errorMessage = data && typeof data === "object" && "error" in data ? JSON.stringify(data.error) : typeof data === "string" && data ? data : `HTTP ${response.status} ${response.statusText}`;
365
+ return { success: false, error: errorMessage };
366
+ }
367
+ return { success: true, status: response.status, data };
368
+ } finally {
369
+ clearTimeout(timeout);
370
+ }
371
+ } catch (err) {
372
+ const msg = err instanceof Error ? err.message : String(err);
373
+ return { success: false, error: msg };
374
+ }
375
+ }
376
+ });
377
+
378
+ // ../connectors/src/connectors/powerbi-oauth/setup.ts
379
+ var requestToolName = `powerbi-oauth_${requestTool.name}`;
380
+ var powerbiOauthOnboarding = new ConnectorOnboarding({
381
+ connectionSetupInstructions: {
382
+ ja: `Power BI OAuth \u30B3\u30CD\u30AF\u30B7\u30E7\u30F3\u306E\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u306F\u4EE5\u4E0B\u306E\u624B\u9806\u3067\u884C\u3063\u3066\u304F\u3060\u3055\u3044\u3002
383
+
384
+ 1. \`${requestToolName}\` \u3092 \`method: "GET"\`\u3001\`path: "/groups"\` \u3067\u547C\u3073\u51FA\u3057\u3001\u30E6\u30FC\u30B6\u30FC\u304C\u30A2\u30AF\u30BB\u30B9\u53EF\u80FD\u306A\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u4E00\u89A7\u3092\u53D6\u5F97\u3059\u308B
385
+ 2. \u30A8\u30E9\u30FC\u304C\u8FD4\u3055\u308C\u305F\u5834\u5408\u3001OAuth \u8A8D\u8A3C\u304C\u6B63\u3057\u304F\u5B8C\u4E86\u3057\u3066\u3044\u308B\u304B\u78BA\u8A8D\u3059\u308B\u3088\u3046\u30E6\u30FC\u30B6\u30FC\u306B\u4F1D\u3048\u308B
386
+
387
+ #### \u5236\u7D04
388
+ - **\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u4E2D\u306B DAX \u30AF\u30A8\u30EA\u306E\u5B9F\u884C\u3084\u30C7\u30FC\u30BF\u30BB\u30C3\u30C8\u306E\u66F4\u65B0\u3092\u884C\u308F\u306A\u3044\u3053\u3068**\u3002\u4E0A\u8A18\u306E\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u4E00\u89A7\u53D6\u5F97\u306E\u307F\u8A31\u53EF
389
+ - \u30C4\u30FC\u30EB\u9593\u306F1\u6587\u3060\u3051\u66F8\u3044\u3066\u5373\u6B21\u306E\u30C4\u30FC\u30EB\u547C\u3073\u51FA\u3057`,
390
+ en: `Follow these steps to set up the Power BI OAuth connection.
391
+
392
+ 1. Call \`${requestToolName}\` with \`method: "GET"\` and \`path: "/groups"\` to list the workspaces the user has access to
393
+ 2. If an error is returned, ask the user to verify that OAuth authentication completed correctly
394
+
395
+ #### Constraints
396
+ - **Do NOT execute DAX queries or refresh datasets during setup**. Only the workspace listing above is allowed
397
+ - Write only 1 sentence between tool calls, then immediately call the next tool`
398
+ },
399
+ dataOverviewInstructions: {
400
+ en: `1. Call powerbi-oauth_request with GET /groups to list accessible workspaces
401
+ 2. For a target workspace, call powerbi-oauth_request with GET /groups/{groupId}/datasets to list datasets
402
+ 3. Call powerbi-oauth_request with GET /groups/{groupId}/reports to list reports
403
+ 4. For each interesting dataset call powerbi-oauth_request with GET /groups/{groupId}/datasets/{datasetId}/tables to inspect tables`,
404
+ ja: `1. powerbi-oauth_request \u3067 GET /groups \u3092\u547C\u3073\u51FA\u3057\u3001\u30A2\u30AF\u30BB\u30B9\u53EF\u80FD\u306A\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u4E00\u89A7\u3092\u53D6\u5F97
405
+ 2. \u5BFE\u8C61\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u306B\u5BFE\u3057\u3066 powerbi-oauth_request \u3067 GET /groups/{groupId}/datasets \u3092\u547C\u3073\u51FA\u3057\u3001\u30C7\u30FC\u30BF\u30BB\u30C3\u30C8\u4E00\u89A7\u3092\u53D6\u5F97
406
+ 3. powerbi-oauth_request \u3067 GET /groups/{groupId}/reports \u3092\u547C\u3073\u51FA\u3057\u3001\u30EC\u30DD\u30FC\u30C8\u4E00\u89A7\u3092\u53D6\u5F97
407
+ 4. \u8208\u5473\u306E\u3042\u308B\u30C7\u30FC\u30BF\u30BB\u30C3\u30C8\u306B\u3064\u3044\u3066 powerbi-oauth_request \u3067 GET /groups/{groupId}/datasets/{datasetId}/tables \u3092\u547C\u3073\u51FA\u3057\u3001\u30C6\u30FC\u30D6\u30EB\u69CB\u9020\u3092\u78BA\u8A8D`
408
+ }
409
+ });
410
+
411
+ // ../connectors/src/connectors/powerbi-oauth/parameters.ts
412
+ var parameters = {};
413
+
414
+ // ../connectors/src/connectors/powerbi-oauth/index.ts
415
+ var tools = { request: requestTool };
416
+ var powerbiOauthConnector = new ConnectorPlugin({
417
+ slug: "powerbi",
418
+ authType: AUTH_TYPES.OAUTH,
419
+ name: "Power BI",
420
+ description: "Connect to Microsoft Power BI using OAuth (Microsoft Entra ID). Use it to enumerate workspaces, datasets, and reports the signed-in user has access to, and to run DAX queries.",
421
+ iconUrl: "https://images.ctfassets.net/9ncizv60xc5y/2vXQCKGpMJ9kGSaqkZl9IS/cc5669c267fc5d11e7b1f8c01723e461/power-bi-icon.png",
422
+ parameters,
423
+ releaseFlag: { dev1: true, dev2: false, prod: false },
424
+ categories: ["bi"],
425
+ onboarding: powerbiOauthOnboarding,
426
+ proxyPolicy: {
427
+ allowlist: [
428
+ {
429
+ host: "api.powerbi.com",
430
+ methods: ["GET", "POST", "PATCH", "PUT", "DELETE"]
431
+ }
432
+ ]
433
+ },
434
+ systemPrompt: {
435
+ en: `### Tools
436
+
437
+ - \`powerbi-oauth_request\`: The only way to call the Power BI REST API v1.0. Use it to list workspaces (\`/groups\`), datasets, reports, and to run DAX via the \`executeQueries\` endpoint. Authentication is configured automatically via OAuth (Microsoft Entra ID).
438
+
439
+ ### Business Logic
440
+
441
+ The business logic type for this connector is "typescript". Use the connector SDK in your handler. Do NOT access credentials directly from environment variables and do NOT read \`INTERNAL_SQUADBASE_*\` env vars \u2014 the SDK takes care of OAuth.
442
+
443
+ SDK surface (client created via \`connection(connectionId)\`):
444
+ - \`client.request(path, init?)\` \u2014 low-level authenticated fetch (path appended to \`https://api.powerbi.com/v1.0/myorg\`)
445
+ - \`client.listGroups(options?)\` \u2014 list workspaces (\`$top\`, \`$skip\`, \`$filter\`)
446
+ - \`client.listDatasets(groupId)\` / \`client.listReports(groupId)\` \u2014 list datasets / reports in a workspace
447
+ - \`client.getDataset(groupId, datasetId)\` \u2014 fetch a single dataset
448
+ - \`client.executeQueries(groupId, datasetId, queries, options?)\` \u2014 run DAX queries
449
+ - \`client.refreshDataset(groupId, datasetId, options?)\` \u2014 trigger an async refresh
450
+
451
+ If a handler test fails with \`Connection proxy is not configured\`, retry \u2014 the sandbox is still initializing. Do NOT abandon the SDK and construct OAuth proxy URLs manually.
452
+
453
+ \`\`\`ts
454
+ import type { Context } from "hono";
455
+ import { connection } from "@squadbase/vite-server/connectors/powerbi-oauth";
456
+
457
+ const powerbi = connection("<connectionId>");
458
+
459
+ export default async function handler(c: Context) {
460
+ const { groupId, datasetId, dax } = await c.req.json<{
461
+ groupId: string;
462
+ datasetId: string;
463
+ dax: string;
464
+ }>();
465
+
466
+ const result = await powerbi.executeQueries(groupId, datasetId, [dax]);
467
+ const rows = result.results[0]?.tables[0]?.rows ?? [];
468
+ return c.json({ rows });
469
+ }
470
+ \`\`\`
471
+
472
+ ### Power BI REST API Reference
473
+
474
+ - Base URL: \`https://api.powerbi.com/v1.0/myorg\`
475
+ - Authentication: Microsoft Entra OAuth (delegated; handled automatically)
476
+ - Unlike Service Principals, OAuth users can also access "My workspace" via the \`/datasets\` (without \`/groups/\`) endpoints
477
+
478
+ #### Common Endpoints
479
+ - GET \`/groups\` \u2014 List workspaces the signed-in user has access to
480
+ - GET \`/groups/{groupId}/datasets\` \u2014 List datasets in a workspace
481
+ - GET \`/groups/{groupId}/datasets/{datasetId}\` \u2014 Get dataset metadata
482
+ - GET \`/groups/{groupId}/reports\` \u2014 List reports
483
+ - POST \`/groups/{groupId}/datasets/{datasetId}/executeQueries\` \u2014 Run DAX (body: \`{ queries: [{ query: "EVALUATE ..." }], serializerSettings: { includeNulls: true } }\`)
484
+ - POST \`/groups/{groupId}/datasets/{datasetId}/refreshes\` \u2014 Trigger a dataset refresh
485
+ - GET \`/groups/{groupId}/datasets/{datasetId}/refreshes\` \u2014 List recent refresh history
486
+
487
+ #### DAX Tips
488
+ - \`EVALUATE TOPN(10, 'TableName')\` \u2014 sample 10 rows from a table
489
+ - \`EVALUATE SUMMARIZECOLUMNS('Date'[Year], "Sales", SUM('Sales'[Amount]))\` \u2014 aggregate
490
+ - Each \`executeQueries\` call accepts one query in the \`queries\` array (per current Power BI API limits)`,
491
+ ja: `### \u30C4\u30FC\u30EB
492
+
493
+ - \`powerbi-oauth_request\`: Power BI REST API v1.0 \u3092\u547C\u3073\u51FA\u3059\u552F\u4E00\u306E\u624B\u6BB5\u3067\u3059\u3002\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9 (\`/groups\`)\u3001\u30C7\u30FC\u30BF\u30BB\u30C3\u30C8\u3001\u30EC\u30DD\u30FC\u30C8\u306E\u4E00\u89A7\u53D6\u5F97\u3084\u3001\`executeQueries\` \u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u306B\u3088\u308B DAX \u306E\u5B9F\u884C\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002OAuth (Microsoft Entra ID) \u7D4C\u7531\u3067\u8A8D\u8A3C\u306F\u81EA\u52D5\u8A2D\u5B9A\u3055\u308C\u307E\u3059\u3002
494
+
495
+ ### Business Logic
496
+
497
+ \u3053\u306E\u30B3\u30CD\u30AF\u30BF\u306E\u30D3\u30B8\u30CD\u30B9\u30ED\u30B8\u30C3\u30AF\u30BF\u30A4\u30D7\u306F "typescript" \u3067\u3059\u3002\u4EE5\u4E0B\u306B\u793A\u3059\u30B3\u30CD\u30AF\u30BF SDK \u3092\u4F7F\u7528\u3057\u3066\u30CF\u30F3\u30C9\u30E9\u30B3\u30FC\u30C9\u3092\u8A18\u8FF0\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u74B0\u5883\u5909\u6570\u304B\u3089\u76F4\u63A5\u8A8D\u8A3C\u60C5\u5831\u306B\u30A2\u30AF\u30BB\u30B9\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002\`INTERNAL_SQUADBASE_*\` \u306E\u74B0\u5883\u5909\u6570\u3092\u4F7F\u3063\u3066\u624B\u52D5\u3067 OAuth \u30D7\u30ED\u30AD\u30B7\u3092\u53E9\u304F\u3053\u3068\u3082\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044 \u2014 SDK \u304C OAuth \u3092\u51E6\u7406\u3057\u307E\u3059\u3002
498
+
499
+ SDK (\`connection(connectionId)\` \u3067\u4F5C\u6210\u3057\u305F\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8):
500
+ - \`client.request(path, init?)\` \u2014 \u4F4E\u30EC\u30D9\u30EB\u8A8D\u8A3C\u4ED8\u304D fetch (path \u306F \`https://api.powerbi.com/v1.0/myorg\` \u306B\u8FFD\u52A0\u3055\u308C\u307E\u3059)
501
+ - \`client.listGroups(options?)\` \u2014 \u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u4E00\u89A7 (\`$top\`, \`$skip\`, \`$filter\`)
502
+ - \`client.listDatasets(groupId)\` / \`client.listReports(groupId)\` \u2014 \u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u5185\u306E\u30C7\u30FC\u30BF\u30BB\u30C3\u30C8/\u30EC\u30DD\u30FC\u30C8\u4E00\u89A7
503
+ - \`client.getDataset(groupId, datasetId)\` \u2014 \u30C7\u30FC\u30BF\u30BB\u30C3\u30C8\u5358\u4F53\u53D6\u5F97
504
+ - \`client.executeQueries(groupId, datasetId, queries, options?)\` \u2014 DAX \u30AF\u30A8\u30EA\u5B9F\u884C
505
+ - \`client.refreshDataset(groupId, datasetId, options?)\` \u2014 \u30C7\u30FC\u30BF\u30BB\u30C3\u30C8\u66F4\u65B0\u3092\u975E\u540C\u671F\u30C8\u30EA\u30AC\u30FC
506
+
507
+ \u30CF\u30F3\u30C9\u30E9\u306E\u30C6\u30B9\u30C8\u304C \`Connection proxy is not configured\` \u3067\u5931\u6557\u3059\u308B\u5834\u5408\u306F\u518D\u8A66\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002SDK \u3092\u8AE6\u3081\u3066 OAuth \u30D7\u30ED\u30AD\u30B7\u306E URL \u3092\u81EA\u5206\u3067\u7D44\u307F\u7ACB\u3066\u308B\u3053\u3068\u306F **\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044**\u3002
508
+
509
+ \`\`\`ts
510
+ import type { Context } from "hono";
511
+ import { connection } from "@squadbase/vite-server/connectors/powerbi-oauth";
512
+
513
+ const powerbi = connection("<connectionId>");
514
+
515
+ export default async function handler(c: Context) {
516
+ const { groupId, datasetId, dax } = await c.req.json<{
517
+ groupId: string;
518
+ datasetId: string;
519
+ dax: string;
520
+ }>();
521
+
522
+ const result = await powerbi.executeQueries(groupId, datasetId, [dax]);
523
+ const rows = result.results[0]?.tables[0]?.rows ?? [];
524
+ return c.json({ rows });
525
+ }
526
+ \`\`\`
527
+
528
+ ### Power BI REST API \u30EA\u30D5\u30A1\u30EC\u30F3\u30B9
529
+
530
+ - \u30D9\u30FC\u30B9 URL: \`https://api.powerbi.com/v1.0/myorg\`
531
+ - \u8A8D\u8A3C: Microsoft Entra OAuth (\u59D4\u4EFB\u578B\u3001\u81EA\u52D5\u8A2D\u5B9A)
532
+ - Service Principal \u3068\u7570\u306A\u308A\u3001OAuth \u30E6\u30FC\u30B6\u30FC\u306F \`/datasets\` (\`/groups/\` \u7121\u3057) \u3067\u300C\u30DE\u30A4 \u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u300D\u306B\u3082\u30A2\u30AF\u30BB\u30B9\u53EF\u80FD
533
+
534
+ #### \u4E3B\u8981\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8
535
+ - GET \`/groups\` \u2014 \u30E6\u30FC\u30B6\u30FC\u304C\u30A2\u30AF\u30BB\u30B9\u53EF\u80FD\u306A\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u4E00\u89A7
536
+ - GET \`/groups/{groupId}/datasets\` \u2014 \u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u5185\u306E\u30C7\u30FC\u30BF\u30BB\u30C3\u30C8\u4E00\u89A7
537
+ - GET \`/groups/{groupId}/datasets/{datasetId}\` \u2014 \u30C7\u30FC\u30BF\u30BB\u30C3\u30C8 \u30E1\u30BF\u30C7\u30FC\u30BF
538
+ - GET \`/groups/{groupId}/reports\` \u2014 \u30EC\u30DD\u30FC\u30C8\u4E00\u89A7
539
+ - POST \`/groups/{groupId}/datasets/{datasetId}/executeQueries\` \u2014 DAX \u5B9F\u884C (body: \`{ queries: [{ query: "EVALUATE ..." }], serializerSettings: { includeNulls: true } }\`)
540
+ - POST \`/groups/{groupId}/datasets/{datasetId}/refreshes\` \u2014 \u30C7\u30FC\u30BF\u30BB\u30C3\u30C8\u66F4\u65B0\u30C8\u30EA\u30AC\u30FC
541
+ - GET \`/groups/{groupId}/datasets/{datasetId}/refreshes\` \u2014 \u76F4\u8FD1\u306E\u66F4\u65B0\u5C65\u6B74
542
+
543
+ #### DAX Tips
544
+ - \`EVALUATE TOPN(10, 'TableName')\` \u2014 \u30C6\u30FC\u30D6\u30EB\u304B\u3089 10 \u884C\u30B5\u30F3\u30D7\u30EA\u30F3\u30B0
545
+ - \`EVALUATE SUMMARIZECOLUMNS('Date'[Year], "Sales", SUM('Sales'[Amount]))\` \u2014 \u96C6\u8A08
546
+ - \`executeQueries\` \u306F\u73FE\u72B6 1 \u30EA\u30AF\u30A8\u30B9\u30C8\u306B\u3064\u304D 1 \u30AF\u30A8\u30EA\u306E\u307F`
547
+ },
548
+ tools,
549
+ async checkConnection(_params, config) {
550
+ const { proxyFetch } = config;
551
+ const url = "https://api.powerbi.com/v1.0/myorg/groups?$top=1";
552
+ try {
553
+ const res = await proxyFetch(url, { method: "GET" });
554
+ if (!res.ok) {
555
+ const errorText = await res.text().catch(() => res.statusText);
556
+ return {
557
+ success: false,
558
+ error: `Power BI API failed: HTTP ${res.status} ${errorText}`
559
+ };
560
+ }
561
+ return { success: true };
562
+ } catch (error) {
563
+ return {
564
+ success: false,
565
+ error: error instanceof Error ? error.message : String(error)
566
+ };
567
+ }
568
+ }
569
+ });
570
+
571
+ // src/connectors/create-connector-sdk.ts
572
+ import { readFileSync } from "fs";
573
+ import path from "path";
574
+
575
+ // src/connector-client/env.ts
576
+ function resolveEnvVar(entry, key, connectionId) {
577
+ const envVarName = entry.envVars[key];
578
+ if (!envVarName) {
579
+ throw new Error(`Connection "${connectionId}" is missing envVars mapping for key "${key}"`);
580
+ }
581
+ const value = process.env[envVarName];
582
+ if (!value) {
583
+ throw new Error(`Environment variable "${envVarName}" (for connection "${connectionId}", key "${key}") is not set`);
584
+ }
585
+ return value;
586
+ }
587
+ function resolveEnvVarOptional(entry, key) {
588
+ const envVarName = entry.envVars[key];
589
+ if (!envVarName) return void 0;
590
+ return process.env[envVarName] || void 0;
591
+ }
592
+
593
+ // src/connector-client/proxy-fetch.ts
594
+ import { getContext } from "hono/context-storage";
595
+ import { getCookie } from "hono/cookie";
596
+ var APP_SESSION_COOKIE_NAME = "__Host-squadbase-session";
597
+ function normalizeHeaders(input) {
598
+ const out = {};
599
+ if (!input) return out;
600
+ new Headers(input).forEach((value, key) => {
601
+ out[key] = value;
602
+ });
603
+ return out;
604
+ }
605
+ function createSandboxProxyFetch(connectionId) {
606
+ return async (input, init) => {
607
+ const token = process.env.INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL;
608
+ const sandboxId = process.env.INTERNAL_SQUADBASE_SANDBOX_ID;
609
+ if (!token || !sandboxId) {
610
+ throw new Error(
611
+ "Connection proxy is not configured. Please check your deployment settings."
612
+ );
613
+ }
614
+ const originalUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
615
+ const originalMethod = init?.method ?? "GET";
616
+ const originalBody = init?.body ? JSON.parse(init.body) : void 0;
617
+ const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
618
+ const proxyUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
619
+ return fetch(proxyUrl, {
620
+ method: "POST",
621
+ headers: {
622
+ "Content-Type": "application/json",
623
+ Authorization: `Bearer ${token}`
624
+ },
625
+ body: JSON.stringify({
626
+ url: originalUrl,
627
+ method: originalMethod,
628
+ headers: normalizeHeaders(init?.headers),
629
+ body: originalBody
630
+ })
631
+ });
632
+ };
633
+ }
634
+ function createDeployedAppProxyFetch(connectionId) {
635
+ const projectId = process.env["SQUADBASE_PROJECT_ID"];
636
+ if (!projectId) {
637
+ throw new Error(
638
+ "Connection proxy is not configured. Please check your deployment settings."
639
+ );
640
+ }
641
+ const baseDomain = process.env["SQUADBASE_APP_BASE_DOMAIN"] ?? "squadbase.app";
642
+ const proxyUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
643
+ return async (input, init) => {
644
+ const originalUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
645
+ const originalMethod = init?.method ?? "GET";
646
+ const originalBody = init?.body ? JSON.parse(init.body) : void 0;
647
+ const c = getContext();
648
+ const appSession = getCookie(c, APP_SESSION_COOKIE_NAME);
649
+ if (!appSession) {
650
+ throw new Error(
651
+ "No authentication method available for connection proxy."
652
+ );
653
+ }
654
+ return fetch(proxyUrl, {
655
+ method: "POST",
656
+ headers: {
657
+ "Content-Type": "application/json",
658
+ Authorization: `Bearer ${appSession}`
659
+ },
660
+ body: JSON.stringify({
661
+ url: originalUrl,
662
+ method: originalMethod,
663
+ headers: normalizeHeaders(init?.headers),
664
+ body: originalBody
665
+ })
666
+ });
667
+ };
668
+ }
669
+ function createProxyFetch(connectionId) {
670
+ if (process.env.INTERNAL_SQUADBASE_SANDBOX_ID) {
671
+ return createSandboxProxyFetch(connectionId);
672
+ }
673
+ return createDeployedAppProxyFetch(connectionId);
674
+ }
675
+
676
+ // src/connectors/create-connector-sdk.ts
677
+ function loadConnectionsSync() {
678
+ const filePath = process.env.CONNECTIONS_PATH ?? path.join(process.cwd(), ".squadbase/connections.json");
679
+ try {
680
+ const raw = readFileSync(filePath, "utf-8");
681
+ return JSON.parse(raw);
682
+ } catch {
683
+ return {};
684
+ }
685
+ }
686
+ function createConnectorSdk(plugin, createClient2) {
687
+ return (connectionId) => {
688
+ const connections = loadConnectionsSync();
689
+ const entry = connections[connectionId];
690
+ if (!entry) {
691
+ throw new Error(
692
+ `Connection "${connectionId}" not found in .squadbase/connections.json`
693
+ );
694
+ }
695
+ if (entry.connector.slug !== plugin.slug) {
696
+ throw new Error(
697
+ `Connection "${connectionId}" is not a ${plugin.slug} connection (got "${entry.connector.slug}")`
698
+ );
699
+ }
700
+ const params = {};
701
+ for (const param of Object.values(plugin.parameters)) {
702
+ if (param.required) {
703
+ params[param.slug] = resolveEnvVar(entry, param.slug, connectionId);
704
+ } else {
705
+ const val = resolveEnvVarOptional(entry, param.slug);
706
+ if (val !== void 0) params[param.slug] = val;
707
+ }
708
+ }
709
+ return createClient2(params, createProxyFetch(connectionId));
710
+ };
711
+ }
712
+
713
+ // src/connectors/entries/powerbi-oauth.ts
714
+ var connection = createConnectorSdk(
715
+ powerbiOauthConnector,
716
+ createClient
717
+ );
718
+ export {
719
+ connection
720
+ };