@squadbase/vite-server 0.1.17-dev.a9ddcfa → 0.1.17-dev.d4fff69

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 (80) hide show
  1. package/dist/cli/index.js +4299 -821
  2. package/dist/connectors/airtable-oauth.js +48 -8
  3. package/dist/connectors/airtable.js +44 -8
  4. package/dist/connectors/amplitude.js +8 -8
  5. package/dist/connectors/anthropic.js +2 -2
  6. package/dist/connectors/asana.js +37 -10
  7. package/dist/connectors/attio.js +30 -13
  8. package/dist/connectors/aws-billing.js +8 -8
  9. package/dist/connectors/azure-sql.js +47 -10
  10. package/dist/connectors/backlog-api-key.js +40 -15
  11. package/dist/connectors/clickup.js +50 -10
  12. package/dist/connectors/cosmosdb.js +12 -12
  13. package/dist/connectors/customerio.js +8 -8
  14. package/dist/connectors/dbt.js +686 -25
  15. package/dist/connectors/freshdesk.js +82 -8
  16. package/dist/connectors/freshsales.js +8 -8
  17. package/dist/connectors/freshservice.js +8 -8
  18. package/dist/connectors/gamma.js +15 -15
  19. package/dist/connectors/gemini.js +2 -2
  20. package/dist/connectors/github.js +12 -12
  21. package/dist/connectors/gmail-oauth.js +10 -10
  22. package/dist/connectors/gmail.js +4 -4
  23. package/dist/connectors/google-ads.js +8 -8
  24. package/dist/connectors/google-analytics-oauth.js +152 -25
  25. package/dist/connectors/google-analytics.js +490 -96
  26. package/dist/connectors/google-audit-log.js +4 -4
  27. package/dist/connectors/google-calendar-oauth.js +61 -15
  28. package/dist/connectors/google-calendar.js +61 -11
  29. package/dist/connectors/google-docs.js +10 -10
  30. package/dist/connectors/google-drive.js +32 -10
  31. package/dist/connectors/google-search-console-oauth.js +126 -17
  32. package/dist/connectors/google-sheets.js +6 -6
  33. package/dist/connectors/google-slides.js +10 -10
  34. package/dist/connectors/grafana.js +45 -10
  35. package/dist/connectors/hackernews.d.ts +5 -0
  36. package/dist/connectors/hackernews.js +890 -0
  37. package/dist/connectors/hubspot-oauth.js +41 -9
  38. package/dist/connectors/hubspot.js +25 -9
  39. package/dist/connectors/influxdb.js +8 -8
  40. package/dist/connectors/intercom-oauth.js +72 -12
  41. package/dist/connectors/intercom.js +12 -12
  42. package/dist/connectors/jdbc.js +37 -10
  43. package/dist/connectors/jira-api-key.js +68 -11
  44. package/dist/connectors/kintone-api-token.js +66 -18
  45. package/dist/connectors/kintone.js +54 -11
  46. package/dist/connectors/linear.js +54 -12
  47. package/dist/connectors/linkedin-ads.js +41 -14
  48. package/dist/connectors/mailchimp-oauth.js +6 -6
  49. package/dist/connectors/mailchimp.js +6 -6
  50. package/dist/connectors/meta-ads-oauth.js +33 -14
  51. package/dist/connectors/meta-ads.js +35 -14
  52. package/dist/connectors/mixpanel.js +8 -8
  53. package/dist/connectors/monday.js +9 -9
  54. package/dist/connectors/mongodb.js +8 -8
  55. package/dist/connectors/notion-oauth.js +60 -11
  56. package/dist/connectors/notion.js +60 -11
  57. package/dist/connectors/openai.js +2 -2
  58. package/dist/connectors/oracle.js +39 -11
  59. package/dist/connectors/outlook-oauth.js +21 -21
  60. package/dist/connectors/powerbi-oauth.js +13 -13
  61. package/dist/connectors/salesforce.js +42 -9
  62. package/dist/connectors/semrush.js +6 -6
  63. package/dist/connectors/sentry.js +36 -10
  64. package/dist/connectors/shopify-oauth.js +43 -10
  65. package/dist/connectors/shopify.js +8 -8
  66. package/dist/connectors/sqlserver.js +47 -10
  67. package/dist/connectors/stripe-api-key.js +66 -15
  68. package/dist/connectors/stripe-oauth.js +70 -19
  69. package/dist/connectors/supabase.js +31 -6
  70. package/dist/connectors/tableau.js +15 -15
  71. package/dist/connectors/tiktok-ads.js +37 -16
  72. package/dist/connectors/wix-store.js +8 -8
  73. package/dist/connectors/x.d.ts +5 -0
  74. package/dist/connectors/x.js +927 -0
  75. package/dist/connectors/zendesk-oauth.js +55 -12
  76. package/dist/connectors/zendesk.js +12 -12
  77. package/dist/index.js +4317 -819
  78. package/dist/main.js +4317 -819
  79. package/dist/vite-plugin.js +4297 -819
  80. package/package.json +9 -1
@@ -0,0 +1,890 @@
1
+ // ../connectors/src/connectors/hackernews/sdk/index.ts
2
+ var FIREBASE_BASE_URL = "https://hacker-news.firebaseio.com/v0";
3
+ var ALGOLIA_BASE_URL = "https://hn.algolia.com/api/v1";
4
+ function createClient(_params) {
5
+ function buildUrl(base, path2, query) {
6
+ if (/^https?:\/\//i.test(path2)) {
7
+ throw new Error("hackernews: absolute URLs are not allowed");
8
+ }
9
+ const url = new URL(`${base}${path2.startsWith("/") ? "" : "/"}${path2}`);
10
+ if (query) {
11
+ for (const [k, v] of Object.entries(query)) {
12
+ if (v !== void 0) url.searchParams.set(k, String(v));
13
+ }
14
+ }
15
+ return url.toString();
16
+ }
17
+ async function parseJson(res) {
18
+ const text = await res.text();
19
+ const data = text ? JSON.parse(text) : null;
20
+ if (!res.ok) {
21
+ throw new Error(`hackernews: ${res.status} ${res.statusText}: ${text}`);
22
+ }
23
+ return data;
24
+ }
25
+ async function requestFirebase(path2) {
26
+ return fetch(buildUrl(FIREBASE_BASE_URL, path2), {
27
+ method: "GET",
28
+ headers: { Accept: "application/json" }
29
+ });
30
+ }
31
+ async function requestAlgolia(path2, options) {
32
+ return fetch(buildUrl(ALGOLIA_BASE_URL, path2, options?.query), {
33
+ method: "GET",
34
+ headers: { Accept: "application/json" }
35
+ });
36
+ }
37
+ async function getStoryIds(kind, options) {
38
+ const pathByKind = {
39
+ top: "/topstories.json",
40
+ new: "/newstories.json",
41
+ best: "/beststories.json",
42
+ ask: "/askstories.json",
43
+ show: "/showstories.json",
44
+ job: "/jobstories.json"
45
+ };
46
+ const ids = await parseJson(
47
+ await requestFirebase(pathByKind[kind])
48
+ );
49
+ return ids.slice(0, options?.limit ?? ids.length);
50
+ }
51
+ async function getItem(id) {
52
+ return parseJson(
53
+ await requestFirebase(`/item/${id}.json`)
54
+ );
55
+ }
56
+ return {
57
+ requestFirebase,
58
+ requestAlgolia,
59
+ getItem,
60
+ async getUser(username) {
61
+ return parseJson(
62
+ await requestFirebase(`/user/${encodeURIComponent(username)}.json`)
63
+ );
64
+ },
65
+ async getMaxItem() {
66
+ return parseJson(await requestFirebase("/maxitem.json"));
67
+ },
68
+ async getUpdates() {
69
+ return parseJson(
70
+ await requestFirebase("/updates.json")
71
+ );
72
+ },
73
+ getStoryIds,
74
+ async getItems(ids, options) {
75
+ const limit = options?.limit ?? ids.length;
76
+ const concurrency = Math.max(1, Math.min(options?.concurrency ?? 5, 10));
77
+ const targetIds = ids.slice(0, limit);
78
+ const results = [];
79
+ for (let i = 0; i < targetIds.length; i += concurrency) {
80
+ const batch = targetIds.slice(i, i + concurrency);
81
+ const items = await Promise.all(batch.map((id) => getItem(id)));
82
+ for (const item of items) {
83
+ if (item) results.push(item);
84
+ }
85
+ }
86
+ return results;
87
+ },
88
+ async search(query, options) {
89
+ const path2 = options?.byDate ? "/search_by_date" : "/search";
90
+ return parseJson(
91
+ await requestAlgolia(path2, {
92
+ query: { query, hitsPerPage: 20, ...options?.query ?? {} }
93
+ })
94
+ );
95
+ }
96
+ };
97
+ }
98
+
99
+ // ../connectors/src/connector-onboarding.ts
100
+ var ConnectorOnboarding = class {
101
+ /** Phase 1: Connection setup instructions (optional — some connectors don't need this) */
102
+ connectionSetupInstructions;
103
+ /** Phase 2: Data overview instructions */
104
+ dataOverviewInstructions;
105
+ constructor(config) {
106
+ this.connectionSetupInstructions = config.connectionSetupInstructions;
107
+ this.dataOverviewInstructions = config.dataOverviewInstructions;
108
+ }
109
+ getConnectionSetupPrompt(language) {
110
+ return this.connectionSetupInstructions?.[language] ?? null;
111
+ }
112
+ getDataOverviewInstructions(language) {
113
+ return this.dataOverviewInstructions[language];
114
+ }
115
+ };
116
+
117
+ // ../connectors/src/connector-tool.ts
118
+ var ConnectorTool = class {
119
+ name;
120
+ description;
121
+ inputSchema;
122
+ outputSchema;
123
+ _execute;
124
+ constructor(config) {
125
+ this.name = config.name;
126
+ this.description = config.description;
127
+ this.inputSchema = config.inputSchema;
128
+ this.outputSchema = config.outputSchema;
129
+ this._execute = config.execute;
130
+ }
131
+ createTool(connections, config) {
132
+ return {
133
+ description: this.description,
134
+ inputSchema: this.inputSchema,
135
+ outputSchema: this.outputSchema,
136
+ execute: (input) => this._execute(input, connections, config)
137
+ };
138
+ }
139
+ };
140
+
141
+ // ../connectors/src/connector-plugin.ts
142
+ var ConnectorPlugin = class _ConnectorPlugin {
143
+ slug;
144
+ authType;
145
+ name;
146
+ description;
147
+ iconUrl;
148
+ parameters;
149
+ releaseFlag;
150
+ proxyPolicy;
151
+ experimentalAttributes;
152
+ categories;
153
+ onboarding;
154
+ systemPrompt;
155
+ tools;
156
+ query;
157
+ checkConnection;
158
+ /**
159
+ * SQPD-1212: Logic-based, rule-driven connection setup. Connectors that
160
+ * implement this expose a step-by-step exploration flow (database/schema/
161
+ * table/etc. discovery) that the dashboard backend drives via the
162
+ * `/connections/:connectionId/setup` endpoint. Implement by delegating to
163
+ * `runSetupFlow` from `setup-flow.ts`.
164
+ */
165
+ setup;
166
+ /**
167
+ * Opt-out of the default "verify before save" behavior on connection
168
+ * creation. The backend invokes `checkConnection` synchronously while
169
+ * creating the connection and aborts (no row inserted) if it fails — this
170
+ * flag disables that for connectors where the check cannot succeed pre-save:
171
+ *
172
+ * - `squadbase-db` populates `connection-url` only after Neon provisioning
173
+ * - OAuth connectors require an OAuth-aware proxyFetch keyed by the
174
+ * connectionId, which doesn't exist until the row is saved
175
+ *
176
+ * Exceptions are the explicit position; new credential-input connectors get
177
+ * the default verify-on-create behavior without opt-in.
178
+ */
179
+ skipConnectionCheckOnCreate;
180
+ constructor(config) {
181
+ this.slug = config.slug;
182
+ this.authType = config.authType;
183
+ this.name = config.name;
184
+ this.description = config.description;
185
+ this.iconUrl = config.iconUrl;
186
+ this.parameters = config.parameters;
187
+ this.releaseFlag = config.releaseFlag;
188
+ this.proxyPolicy = config.proxyPolicy;
189
+ this.experimentalAttributes = config.experimentalAttributes;
190
+ this.categories = config.categories ?? [];
191
+ this.onboarding = config.onboarding;
192
+ this.systemPrompt = config.systemPrompt;
193
+ this.tools = config.tools;
194
+ this.query = config.query;
195
+ this.checkConnection = config.checkConnection;
196
+ this.setup = config.setup;
197
+ this.skipConnectionCheckOnCreate = config.skipConnectionCheckOnCreate;
198
+ }
199
+ get connectorKey() {
200
+ return _ConnectorPlugin.deriveKey(this.slug, this.authType);
201
+ }
202
+ /**
203
+ * Create tools for connections that belong to this connector.
204
+ * Filters connections by connectorKey internally.
205
+ * Returns tools keyed as `connector_${connectorKey}_${toolName}`.
206
+ */
207
+ createTools(connections, config, opts) {
208
+ const myConnections = connections.filter(
209
+ (c) => _ConnectorPlugin.deriveKey(c.connector.slug, c.connector.authType) === this.connectorKey
210
+ );
211
+ const result = {};
212
+ for (const t of Object.values(this.tools)) {
213
+ const tool = t.createTool(myConnections, config);
214
+ const originalToModelOutput = tool.toModelOutput;
215
+ result[`connector_${this.connectorKey}_${t.name}`] = {
216
+ ...tool,
217
+ toModelOutput: async (options) => {
218
+ if (!originalToModelOutput) {
219
+ return opts.truncateOutput(options.output);
220
+ }
221
+ const modelOutput = await originalToModelOutput(options);
222
+ if (modelOutput.type === "text" || modelOutput.type === "json") {
223
+ return opts.truncateOutput(modelOutput.value);
224
+ }
225
+ return modelOutput;
226
+ }
227
+ };
228
+ }
229
+ return result;
230
+ }
231
+ static deriveKey(slug, authType) {
232
+ if (authType) return `${slug}-${authType}`;
233
+ const LEGACY_NULL_AUTH_TYPE_MAP = {
234
+ // user-password
235
+ "postgresql": "user-password",
236
+ "mysql": "user-password",
237
+ "clickhouse": "user-password",
238
+ "kintone": "user-password",
239
+ "squadbase-db": "user-password",
240
+ // service-account
241
+ "snowflake": "service-account",
242
+ "bigquery": "service-account",
243
+ "google-analytics": "service-account",
244
+ "google-calendar": "service-account",
245
+ "aws-athena": "service-account",
246
+ "redshift": "service-account",
247
+ // api-key
248
+ "databricks": "api-key",
249
+ "dbt": "api-key",
250
+ "airtable": "api-key",
251
+ "openai": "api-key",
252
+ "gemini": "api-key",
253
+ "anthropic": "api-key",
254
+ "wix-store": "api-key"
255
+ };
256
+ const fallbackAuthType = LEGACY_NULL_AUTH_TYPE_MAP[slug];
257
+ if (fallbackAuthType) return `${slug}-${fallbackAuthType}`;
258
+ return slug;
259
+ }
260
+ };
261
+
262
+ // ../connectors/src/setup-flow.ts
263
+ async function runSetupFlow(flow, params, ctx, config) {
264
+ const runtime = {
265
+ params,
266
+ language: ctx.language,
267
+ config
268
+ };
269
+ let state = flow.initialState();
270
+ let answerIdx = 0;
271
+ const pendingParameterUpdates = [];
272
+ for (const step of flow.steps) {
273
+ const ans = ctx.answers[answerIdx];
274
+ if (ans && ans.questionSlug === step.slug) {
275
+ state = step.applyAnswer(state, ans.answer);
276
+ if (step.toParameterUpdates) {
277
+ pendingParameterUpdates.push(...step.toParameterUpdates(state));
278
+ }
279
+ answerIdx += 1;
280
+ continue;
281
+ }
282
+ const resolvedAllowFreeText = step.allowFreeText !== void 0 ? step.allowFreeText : true;
283
+ if (step.type === "text") {
284
+ if (step.fetchOptions) {
285
+ const options2 = await step.fetchOptions(state, runtime);
286
+ if (options2.length === 0) {
287
+ continue;
288
+ }
289
+ }
290
+ return {
291
+ type: "nextQuestion",
292
+ questionSlug: step.slug,
293
+ question: step.question[ctx.language],
294
+ questionType: "text",
295
+ allowFreeText: resolvedAllowFreeText,
296
+ ...pendingParameterUpdates.length > 0 && {
297
+ parameterUpdates: pendingParameterUpdates
298
+ }
299
+ };
300
+ }
301
+ const options = step.fetchOptions ? await step.fetchOptions(state, runtime) : [];
302
+ if (options.length === 0) {
303
+ continue;
304
+ }
305
+ return {
306
+ type: "nextQuestion",
307
+ questionSlug: step.slug,
308
+ question: step.question[ctx.language],
309
+ questionType: step.type,
310
+ options,
311
+ allowFreeText: resolvedAllowFreeText,
312
+ ...pendingParameterUpdates.length > 0 && {
313
+ parameterUpdates: pendingParameterUpdates
314
+ }
315
+ };
316
+ }
317
+ const dataInvestigationResult = await flow.finalize(state, runtime);
318
+ return {
319
+ type: "fulfilled",
320
+ dataInvestigationResult,
321
+ ...pendingParameterUpdates.length > 0 && {
322
+ parameterUpdates: pendingParameterUpdates
323
+ }
324
+ };
325
+ }
326
+
327
+ // ../connectors/src/auth-types.ts
328
+ var AUTH_TYPES = {
329
+ OAUTH: "oauth",
330
+ API_KEY: "api-key",
331
+ JWT: "jwt",
332
+ SERVICE_ACCOUNT: "service-account",
333
+ PAT: "pat",
334
+ USER_PASSWORD: "user-password"
335
+ };
336
+
337
+ // ../connectors/src/connectors/hackernews/setup.ts
338
+ var hackerNewsOnboarding = new ConnectorOnboarding({
339
+ dataOverviewInstructions: {
340
+ en: `1. Use connector_hackernews-api-key_requestFirebase with GET /topstories.json to inspect current top story ids
341
+ 2. Fetch a small sample of items with GET /item/{id}.json to understand story fields such as title, url, score, descendants, by, and time
342
+ 3. Use connector_hackernews-api-key_searchAlgolia only when keyword or historical search is required
343
+ 4. Prefer Firebase list endpoints for dashboards that show top, new, best, Ask HN, Show HN, or job stories`,
344
+ ja: `1. connector_hackernews-api-key_requestFirebase \u3067 GET /topstories.json \u3092\u547C\u3073\u51FA\u3057\u3001\u73FE\u5728\u306E\u30C8\u30C3\u30D7\u30B9\u30C8\u30FC\u30EA\u30FCID\u3092\u78BA\u8A8D\u3057\u307E\u3059
345
+ 2. GET /item/{id}.json \u3067\u5C11\u91CF\u306E item \u3092\u53D6\u5F97\u3057\u3001title, url, score, descendants, by, time \u306A\u3069\u306E\u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u628A\u63E1\u3057\u307E\u3059
346
+ 3. \u30AD\u30FC\u30EF\u30FC\u30C9\u691C\u7D22\u3084\u5C65\u6B74\u691C\u7D22\u304C\u5FC5\u8981\u306A\u5834\u5408\u306E\u307F connector_hackernews-api-key_searchAlgolia \u3092\u4F7F\u7528\u3057\u307E\u3059
347
+ 4. top, new, best, Ask HN, Show HN, job stories \u306E\u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9\u3067\u306F Firebase \u306E\u30EA\u30B9\u30C8\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u3092\u512A\u5148\u3057\u307E\u3059`
348
+ }
349
+ });
350
+
351
+ // ../connectors/src/connectors/hackernews/setup-flow.ts
352
+ var hackerNewsSetupFlow = {
353
+ initialState: () => ({}),
354
+ steps: [],
355
+ async finalize(_state, rt) {
356
+ const sections = rt.language === "ja" ? [
357
+ "## Hacker News",
358
+ "",
359
+ "- \u8A8D\u8A3C\u60C5\u5831\u306E\u5165\u529B\u306F\u4E0D\u8981\u3067\u3059\u3002",
360
+ "- \u901A\u5E38\u306E\u30E9\u30F3\u30AD\u30F3\u30B0\u3001item\u3001user\u3001updates \u306E\u53D6\u5F97\u306B\u306F\u516C\u5F0F Firebase API \u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
361
+ "- Algolia API \u306F\u30AD\u30FC\u30EF\u30FC\u30C9\u691C\u7D22\u3084\u671F\u9593\u691C\u7D22\u304C\u5FC5\u8981\u306A\u6642\u3060\u3051\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
362
+ "- \u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9\u5B9F\u88C5\u3067\u306F\u3001item \u8A73\u7D30\u53D6\u5F97\u306E\u4E26\u5217\u6570\u3068\u4EF6\u6570\u3092\u6291\u3048\u3066\u304F\u3060\u3055\u3044\u3002"
363
+ ] : [
364
+ "## Hacker News",
365
+ "",
366
+ "- No credential input is required.",
367
+ "- Use the official Firebase API for rankings, items, users, and updates.",
368
+ "- Use the Algolia API only when keyword or date-range search is required.",
369
+ "- When building dashboards, keep item-detail fetch concurrency and result counts bounded."
370
+ ];
371
+ return sections.join("\n");
372
+ }
373
+ };
374
+
375
+ // ../connectors/src/connectors/hackernews/tools/request-firebase.ts
376
+ import { z } from "zod";
377
+ var BASE_URL = "https://hacker-news.firebaseio.com/v0";
378
+ var REQUEST_TIMEOUT_MS = 6e4;
379
+ var inputSchema = z.object({
380
+ toolUseIntent: z.string().optional().describe(
381
+ "Brief description of what you intend to accomplish with this tool call"
382
+ ),
383
+ connectionId: z.string().describe(
384
+ "ID of the Hacker News connection to use. This connector has no credentials, but the connection ID identifies the selected data source."
385
+ ),
386
+ path: z.string().describe(
387
+ "Firebase API path appended to https://hacker-news.firebaseio.com/v0. Examples: '/topstories.json', '/newstories.json', '/beststories.json', '/askstories.json', '/showstories.json', '/jobstories.json', '/item/8863.json', '/user/jl.json', '/maxitem.json', '/updates.json'."
388
+ ),
389
+ printPretty: z.boolean().optional().describe("Set true to append print=pretty for readable debugging output.")
390
+ });
391
+ var outputSchema = z.discriminatedUnion("success", [
392
+ z.object({
393
+ success: z.literal(true),
394
+ status: z.number(),
395
+ data: z.unknown()
396
+ }),
397
+ z.object({
398
+ success: z.literal(false),
399
+ status: z.number().optional(),
400
+ error: z.string()
401
+ })
402
+ ]);
403
+ var requestFirebaseTool = new ConnectorTool({
404
+ name: "requestFirebase",
405
+ description: `Fetch public Hacker News data from the official Firebase API.
406
+ Use this tool for normal dashboard data: top/new/best/ask/show/job story IDs, individual items, users, max item ID, and updates. The Firebase API is the preferred source whenever you do not need full-text search.
407
+
408
+ This connector does not require API credentials, but a connectionId is still required to select the Hacker News data source. Keep dashboard calls bounded: fetch list IDs first, then fetch only the small slice of item details needed for the current view. Use searchAlgolia only when keyword, tag, or historical search is necessary.`,
409
+ inputSchema,
410
+ outputSchema,
411
+ async execute({ connectionId, path: path2, printPretty }, connections) {
412
+ const connection2 = connections.find((c) => c.id === connectionId);
413
+ if (!connection2) {
414
+ return {
415
+ success: false,
416
+ error: `Connection ${connectionId} not found`
417
+ };
418
+ }
419
+ console.log(
420
+ `[connector-request] hackernews/${connection2.name}: GET ${path2}`
421
+ );
422
+ try {
423
+ if (/^https?:\/\//i.test(path2)) {
424
+ return {
425
+ success: false,
426
+ error: "Absolute URLs are not allowed. Pass a Firebase path such as /topstories.json."
427
+ };
428
+ }
429
+ const url = new URL(
430
+ `${BASE_URL}${path2.startsWith("/") ? "" : "/"}${path2}`
431
+ );
432
+ if (printPretty) url.searchParams.set("print", "pretty");
433
+ const controller = new AbortController();
434
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
435
+ try {
436
+ const response = await fetch(url.toString(), {
437
+ method: "GET",
438
+ headers: { Accept: "application/json" },
439
+ signal: controller.signal
440
+ });
441
+ const data = await response.json();
442
+ if (!response.ok) {
443
+ return {
444
+ success: false,
445
+ status: response.status,
446
+ error: `HTTP ${response.status} ${response.statusText}`
447
+ };
448
+ }
449
+ return {
450
+ success: true,
451
+ status: response.status,
452
+ data
453
+ };
454
+ } finally {
455
+ clearTimeout(timeout);
456
+ }
457
+ } catch (err) {
458
+ const msg = err instanceof Error ? err.message : String(err);
459
+ return { success: false, error: msg };
460
+ }
461
+ }
462
+ });
463
+
464
+ // ../connectors/src/connectors/hackernews/tools/search-algolia.ts
465
+ import { z as z2 } from "zod";
466
+ var BASE_URL2 = "https://hn.algolia.com/api/v1";
467
+ var REQUEST_TIMEOUT_MS2 = 6e4;
468
+ var inputSchema2 = z2.object({
469
+ toolUseIntent: z2.string().optional().describe(
470
+ "Brief description of what you intend to accomplish with this tool call"
471
+ ),
472
+ connectionId: z2.string().describe(
473
+ "ID of the Hacker News connection to use. This connector has no credentials, but the connection ID identifies the selected data source."
474
+ ),
475
+ endpoint: z2.enum(["search", "search_by_date", "items", "users"]).describe(
476
+ "Algolia API endpoint. Use search/search_by_date for keyword queries, items for /items/{id}, and users for /users/{username}. Prefer Firebase for non-search dashboard data."
477
+ ),
478
+ idOrUsername: z2.string().optional().describe(
479
+ "Required when endpoint is items or users. Pass the item id for items or the username for users."
480
+ ),
481
+ queryParams: z2.record(z2.string(), z2.string()).optional().describe(
482
+ "Query parameters for Algolia search. Common params: query, tags (e.g. story,comment,show_hn,ask_hn), numericFilters (e.g. created_at_i>1700000000), page, hitsPerPage. Keep hitsPerPage small for dashboards."
483
+ )
484
+ });
485
+ var outputSchema2 = z2.discriminatedUnion("success", [
486
+ z2.object({
487
+ success: z2.literal(true),
488
+ status: z2.number(),
489
+ data: z2.unknown()
490
+ }),
491
+ z2.object({
492
+ success: z2.literal(false),
493
+ status: z2.number().optional(),
494
+ error: z2.string()
495
+ })
496
+ ]);
497
+ var searchAlgoliaTool = new ConnectorTool({
498
+ name: "searchAlgolia",
499
+ description: `Search Hacker News data through the Algolia-powered HN Search API.
500
+ Use this tool only when Firebase cannot answer the question, such as keyword search, historical search, tag search, or sorting search hits by date. For top/new/best/Ask/Show/job dashboards and individual item/user lookups, prefer requestFirebase because it uses the official Hacker News Firebase API.
501
+
502
+ Avoid frequent repeated Algolia searches from dashboards. Keep hitsPerPage small, page deliberately, and cache or reuse results in server logic when possible. The public search API may rate limit by IP when used heavily.`,
503
+ inputSchema: inputSchema2,
504
+ outputSchema: outputSchema2,
505
+ async execute({ connectionId, endpoint, idOrUsername, queryParams }, connections) {
506
+ const connection2 = connections.find((c) => c.id === connectionId);
507
+ if (!connection2) {
508
+ return {
509
+ success: false,
510
+ error: `Connection ${connectionId} not found`
511
+ };
512
+ }
513
+ console.log(
514
+ `[connector-request] hackernews/${connection2.name}: Algolia ${endpoint}`
515
+ );
516
+ try {
517
+ let path2 = `/${endpoint}`;
518
+ if (endpoint === "items" || endpoint === "users") {
519
+ if (!idOrUsername) {
520
+ return {
521
+ success: false,
522
+ error: `idOrUsername is required for endpoint ${endpoint}`
523
+ };
524
+ }
525
+ path2 += `/${encodeURIComponent(idOrUsername)}`;
526
+ }
527
+ const url = new URL(`${BASE_URL2}${path2}`);
528
+ if (queryParams) {
529
+ for (const [k, v] of Object.entries(queryParams)) {
530
+ url.searchParams.set(k, v);
531
+ }
532
+ }
533
+ const controller = new AbortController();
534
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS2);
535
+ try {
536
+ const response = await fetch(url.toString(), {
537
+ method: "GET",
538
+ headers: { Accept: "application/json" },
539
+ signal: controller.signal
540
+ });
541
+ const data = await response.json();
542
+ if (!response.ok) {
543
+ return {
544
+ success: false,
545
+ status: response.status,
546
+ error: `HTTP ${response.status} ${response.statusText}`
547
+ };
548
+ }
549
+ return {
550
+ success: true,
551
+ status: response.status,
552
+ data
553
+ };
554
+ } finally {
555
+ clearTimeout(timeout);
556
+ }
557
+ } catch (err) {
558
+ const msg = err instanceof Error ? err.message : String(err);
559
+ return { success: false, error: msg };
560
+ }
561
+ }
562
+ });
563
+
564
+ // ../connectors/src/connectors/hackernews/parameters.ts
565
+ var parameters = {};
566
+
567
+ // ../connectors/src/connectors/hackernews/index.ts
568
+ var tools = {
569
+ requestFirebase: requestFirebaseTool,
570
+ searchAlgolia: searchAlgoliaTool
571
+ };
572
+ var hackerNewsConnector = new ConnectorPlugin({
573
+ slug: "hackernews",
574
+ authType: AUTH_TYPES.API_KEY,
575
+ name: "Hacker News",
576
+ description: "Connect to public Hacker News data using the official Firebase API, with optional Algolia-powered search when needed.",
577
+ iconUrl: "https://news.ycombinator.com/y18.svg",
578
+ parameters,
579
+ releaseFlag: { dev1: true, dev2: true, prod: false },
580
+ categories: ["scraping"],
581
+ onboarding: hackerNewsOnboarding,
582
+ systemPrompt: {
583
+ en: `### Tools
584
+
585
+ - \`connector_hackernews-api-key_requestFirebase\`: Fetch public Hacker News data from the official Firebase API. Use this for story rankings, items, users, max item ID, and updates. Prefer this tool whenever keyword search is not required.
586
+ - \`connector_hackernews-api-key_searchAlgolia\`: Search Hacker News data through the Algolia-powered search API. Use this only for keyword search, tag search, date-sorted search, or historical queries that Firebase cannot answer.
587
+
588
+ ### Business Logic
589
+
590
+ The business logic type for this connector is "typescript". Use the connector SDK in your handler. This connector has no credential parameters, but still uses the API Key connector model so users can start immediately without entering a key.
591
+
592
+ SDK methods (client created via \`connection(connectionId)\`):
593
+ - \`client.requestFirebase(path)\` \u2014 low-level fetch against the official Firebase API
594
+ - \`client.requestAlgolia(path, options?)\` \u2014 low-level fetch against HN Search powered by Algolia
595
+ - \`client.getItem(id)\` \u2014 get a story, comment, job, poll, or poll option
596
+ - \`client.getUser(username)\` \u2014 get a public HN user profile
597
+ - \`client.getMaxItem()\` \u2014 get the current largest item ID
598
+ - \`client.getUpdates()\` \u2014 get changed item IDs and profile IDs
599
+ - \`client.getStoryIds(kind, options?)\` \u2014 get top/new/best/ask/show/job story IDs
600
+ - \`client.getItems(ids, options?)\` \u2014 fetch item details with bounded concurrency
601
+ - \`client.search(query, options?)\` \u2014 Algolia keyword search; use only when search is required
602
+
603
+ For dashboards showing current top/new/best/Ask/Show/job stories, use Firebase list endpoints and then fetch only the item details needed for the visible view. Avoid unbounded item-tree traversal and avoid repeatedly hitting Algolia from render-time server logic. If the user asks for "search HN for ..." or historical keyword analysis, use Algolia with a small \`hitsPerPage\`.
604
+
605
+ \`\`\`ts
606
+ import type { Context } from "hono";
607
+ import { connection } from "@squadbase/vite-server/connectors/hackernews";
608
+
609
+ const hn = connection("<connectionId>");
610
+
611
+ export default async function handler(c: Context) {
612
+ const { limit = 20 } = await c.req.json<{ limit?: number }>();
613
+
614
+ const ids = await hn.getStoryIds("top", { limit });
615
+ const stories = await hn.getItems(ids, {
616
+ limit,
617
+ concurrency: 5,
618
+ });
619
+
620
+ return c.json({ stories });
621
+ }
622
+ \`\`\`
623
+
624
+ ### Hacker News API Reference
625
+
626
+ #### Official Firebase API
627
+ - Base URL: \`https://hacker-news.firebaseio.com/v0\`
628
+ - No authentication required
629
+ - Items: \`/item/{id}.json\`
630
+ - Users: \`/user/{username}.json\`
631
+ - Story lists: \`/topstories.json\`, \`/newstories.json\`, \`/beststories.json\`, \`/askstories.json\`, \`/showstories.json\`, \`/jobstories.json\`
632
+ - Live data: \`/maxitem.json\`, \`/updates.json\`
633
+
634
+ #### HN Search powered by Algolia
635
+ - Base URL: \`https://hn.algolia.com/api/v1\`
636
+ - Use \`/search\` and \`/search_by_date\` for keyword queries
637
+ - Common query parameters: \`query\`, \`tags\`, \`numericFilters\`, \`page\`, \`hitsPerPage\`
638
+ - Prefer Firebase unless search or date filtering is genuinely needed`,
639
+ ja: `### \u30C4\u30FC\u30EB
640
+
641
+ - \`connector_hackernews-api-key_requestFirebase\`: \u516C\u5F0F Firebase API \u304B\u3089\u516C\u958B Hacker News \u30C7\u30FC\u30BF\u3092\u53D6\u5F97\u3057\u307E\u3059\u3002\u30E9\u30F3\u30AD\u30F3\u30B0\u3001item\u3001user\u3001max item ID\u3001updates \u306E\u53D6\u5F97\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002\u30AD\u30FC\u30EF\u30FC\u30C9\u691C\u7D22\u304C\u4E0D\u8981\u306A\u5834\u5408\u306F\u3053\u306E\u30C4\u30FC\u30EB\u3092\u512A\u5148\u3057\u3066\u304F\u3060\u3055\u3044\u3002
642
+ - \`connector_hackernews-api-key_searchAlgolia\`: Algolia \u30D9\u30FC\u30B9\u306E\u691C\u7D22 API \u3067 Hacker News \u30C7\u30FC\u30BF\u3092\u691C\u7D22\u3057\u307E\u3059\u3002Firebase \u3067\u306F\u7B54\u3048\u3089\u308C\u306A\u3044\u30AD\u30FC\u30EF\u30FC\u30C9\u691C\u7D22\u3001\u30BF\u30B0\u691C\u7D22\u3001\u65E5\u4ED8\u9806\u691C\u7D22\u3001\u5C65\u6B74\u30AF\u30A8\u30EA\u304C\u5FC5\u8981\u306A\u5834\u5408\u306E\u307F\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002
643
+
644
+ ### Business Logic
645
+
646
+ \u3053\u306E\u30B3\u30CD\u30AF\u30BF\u306E\u30D3\u30B8\u30CD\u30B9\u30ED\u30B8\u30C3\u30AF\u30BF\u30A4\u30D7\u306F "typescript" \u3067\u3059\u3002\u30CF\u30F3\u30C9\u30E9\u5185\u3067\u306F\u30B3\u30CD\u30AF\u30BFSDK\u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u3053\u306E\u30B3\u30CD\u30AF\u30BF\u306B\u306F\u8A8D\u8A3C\u30D1\u30E9\u30E1\u30FC\u30BF\u306F\u3042\u308A\u307E\u305B\u3093\u304C\u3001\u65E2\u5B58\u30E2\u30C7\u30EB\u306B\u5408\u308F\u305B\u3066 API Key \u30B3\u30CD\u30AF\u30BF\u3068\u3057\u3066\u6271\u3044\u3001\u30E6\u30FC\u30B6\u30FC\u306F\u30AD\u30FC\u5165\u529B\u306A\u3057\u3067\u3059\u3050\u958B\u59CB\u3067\u304D\u307E\u3059\u3002
647
+
648
+ SDK\u30E1\u30BD\u30C3\u30C9 (\`connection(connectionId)\` \u3067\u4F5C\u6210\u3057\u305F\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8):
649
+ - \`client.requestFirebase(path)\` \u2014 \u516C\u5F0F Firebase API \u3078\u306E\u4F4E\u30EC\u30D9\u30EBfetch
650
+ - \`client.requestAlgolia(path, options?)\` \u2014 HN Search powered by Algolia \u3078\u306E\u4F4E\u30EC\u30D9\u30EBfetch
651
+ - \`client.getItem(id)\` \u2014 story, comment, job, poll, poll option \u3092\u53D6\u5F97
652
+ - \`client.getUser(username)\` \u2014 \u516C\u958B HN \u30E6\u30FC\u30B6\u30FC\u30D7\u30ED\u30D5\u30A3\u30FC\u30EB\u3092\u53D6\u5F97
653
+ - \`client.getMaxItem()\` \u2014 \u73FE\u5728\u306E\u6700\u5927 item ID \u3092\u53D6\u5F97
654
+ - \`client.getUpdates()\` \u2014 \u66F4\u65B0\u3055\u308C\u305F item ID \u3068 profile ID \u3092\u53D6\u5F97
655
+ - \`client.getStoryIds(kind, options?)\` \u2014 top/new/best/ask/show/job \u306E story ID \u3092\u53D6\u5F97
656
+ - \`client.getItems(ids, options?)\` \u2014 \u4E26\u5217\u6570\u3092\u5236\u9650\u3057\u3066 item \u8A73\u7D30\u3092\u53D6\u5F97
657
+ - \`client.search(query, options?)\` \u2014 Algolia \u30AD\u30FC\u30EF\u30FC\u30C9\u691C\u7D22\u3002\u691C\u7D22\u304C\u5FC5\u8981\u306A\u5834\u5408\u306E\u307F\u4F7F\u7528
658
+
659
+ \u73FE\u5728\u306E top/new/best/Ask/Show/job stories \u3092\u8868\u793A\u3059\u308B\u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9\u3067\u306F\u3001Firebase \u306E\u30EA\u30B9\u30C8\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u3092\u4F7F\u3044\u3001\u8868\u793A\u306B\u5FC5\u8981\u306A item \u8A73\u7D30\u3060\u3051\u3092\u53D6\u5F97\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u7121\u5236\u9650\u306E\u30B3\u30E1\u30F3\u30C8\u30C4\u30EA\u30FC\u8D70\u67FB\u3084\u3001\u30EC\u30F3\u30C0\u30EA\u30F3\u30B0\u6642\u30B5\u30FC\u30D0\u30FC\u30ED\u30B8\u30C3\u30AF\u304B\u3089\u306E Algolia \u9023\u6253\u306F\u907F\u3051\u3066\u304F\u3060\u3055\u3044\u3002\u30E6\u30FC\u30B6\u30FC\u304C\u300CHN\u3067\u691C\u7D22\u3057\u3066\u300D\u3084\u5C65\u6B74\u30AD\u30FC\u30EF\u30FC\u30C9\u5206\u6790\u3092\u6C42\u3081\u305F\u5834\u5408\u306F\u3001\u5C0F\u3055\u306A \`hitsPerPage\` \u3067 Algolia \u3092\u4F7F\u3044\u307E\u3059\u3002
660
+
661
+ \`\`\`ts
662
+ import type { Context } from "hono";
663
+ import { connection } from "@squadbase/vite-server/connectors/hackernews";
664
+
665
+ const hn = connection("<connectionId>");
666
+
667
+ export default async function handler(c: Context) {
668
+ const { limit = 20 } = await c.req.json<{ limit?: number }>();
669
+
670
+ const ids = await hn.getStoryIds("top", { limit });
671
+ const stories = await hn.getItems(ids, {
672
+ limit,
673
+ concurrency: 5,
674
+ });
675
+
676
+ return c.json({ stories });
677
+ }
678
+ \`\`\`
679
+
680
+ ### Hacker News API \u30EA\u30D5\u30A1\u30EC\u30F3\u30B9
681
+
682
+ #### \u516C\u5F0F Firebase API
683
+ - \u30D9\u30FC\u30B9URL: \`https://hacker-news.firebaseio.com/v0\`
684
+ - \u8A8D\u8A3C\u4E0D\u8981
685
+ - Items: \`/item/{id}.json\`
686
+ - Users: \`/user/{username}.json\`
687
+ - Story lists: \`/topstories.json\`, \`/newstories.json\`, \`/beststories.json\`, \`/askstories.json\`, \`/showstories.json\`, \`/jobstories.json\`
688
+ - Live data: \`/maxitem.json\`, \`/updates.json\`
689
+
690
+ #### HN Search powered by Algolia
691
+ - \u30D9\u30FC\u30B9URL: \`https://hn.algolia.com/api/v1\`
692
+ - \u30AD\u30FC\u30EF\u30FC\u30C9\u691C\u7D22\u306B\u306F \`/search\` \u3068 \`/search_by_date\` \u3092\u4F7F\u7528
693
+ - \u4E3B\u8981\u30AF\u30A8\u30EA\u30D1\u30E9\u30E1\u30FC\u30BF: \`query\`, \`tags\`, \`numericFilters\`, \`page\`, \`hitsPerPage\`
694
+ - \u691C\u7D22\u3084\u65E5\u4ED8\u30D5\u30A3\u30EB\u30BF\u304C\u672C\u5F53\u306B\u5FC5\u8981\u306A\u5834\u5408\u3092\u9664\u304D Firebase \u3092\u512A\u5148\u3057\u3066\u304F\u3060\u3055\u3044`
695
+ },
696
+ tools,
697
+ setup: (params, ctx, config) => runSetupFlow(hackerNewsSetupFlow, params, ctx, config),
698
+ async checkConnection() {
699
+ try {
700
+ const res = await fetch(
701
+ "https://hacker-news.firebaseio.com/v0/maxitem.json",
702
+ {
703
+ method: "GET",
704
+ headers: { Accept: "application/json" }
705
+ }
706
+ );
707
+ if (!res.ok) {
708
+ const errorText = await res.text().catch(() => res.statusText);
709
+ return {
710
+ success: false,
711
+ error: `Hacker News API failed: HTTP ${res.status} ${errorText}`
712
+ };
713
+ }
714
+ return { success: true };
715
+ } catch (error) {
716
+ return {
717
+ success: false,
718
+ error: error instanceof Error ? error.message : String(error)
719
+ };
720
+ }
721
+ }
722
+ });
723
+
724
+ // src/connectors/create-connector-sdk.ts
725
+ import { readFileSync } from "fs";
726
+ import path from "path";
727
+
728
+ // src/connector-client/env.ts
729
+ function resolveEnvVar(entry, key, connectionId) {
730
+ const envVarName = entry.envVars[key];
731
+ if (!envVarName) {
732
+ throw new Error(`Connection "${connectionId}" is missing envVars mapping for key "${key}"`);
733
+ }
734
+ const value = process.env[envVarName];
735
+ if (!value) {
736
+ throw new Error(`Environment variable "${envVarName}" (for connection "${connectionId}", key "${key}") is not set`);
737
+ }
738
+ return value;
739
+ }
740
+ function resolveEnvVarOptional(entry, key) {
741
+ const envVarName = entry.envVars[key];
742
+ if (!envVarName) return void 0;
743
+ return process.env[envVarName] || void 0;
744
+ }
745
+
746
+ // src/connector-client/proxy-fetch.ts
747
+ import { getContext } from "hono/context-storage";
748
+ import { getCookie } from "hono/cookie";
749
+ var APP_SESSION_COOKIE_NAME = "__Host-squadbase-session";
750
+ var TABLEAU_SESSION_SENTINEL_URL = "squadbase://tableau-session/";
751
+ function normalizeHeaders(input) {
752
+ const out = {};
753
+ if (!input) return out;
754
+ new Headers(input).forEach((value, key) => {
755
+ out[key] = value;
756
+ });
757
+ return out;
758
+ }
759
+ function extractInputUrl(input) {
760
+ if (typeof input === "string") return input;
761
+ if (input instanceof URL) return input.href;
762
+ return input.url;
763
+ }
764
+ function createSandboxProxyFetch(connectionId) {
765
+ return async (input, init) => {
766
+ const token = process.env.INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL;
767
+ const sandboxId = process.env.INTERNAL_SQUADBASE_SANDBOX_ID;
768
+ if (!token || !sandboxId) {
769
+ throw new Error(
770
+ "Connection proxy is not configured. Please check your deployment settings."
771
+ );
772
+ }
773
+ const originalUrl = extractInputUrl(input);
774
+ const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
775
+ if (originalUrl === TABLEAU_SESSION_SENTINEL_URL) {
776
+ const sessionUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
777
+ return fetch(sessionUrl, {
778
+ method: "POST",
779
+ headers: { Authorization: `Bearer ${token}` }
780
+ });
781
+ }
782
+ const originalMethod = init?.method ?? "GET";
783
+ const originalBody = init?.body ? JSON.parse(init.body) : void 0;
784
+ const proxyUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
785
+ return fetch(proxyUrl, {
786
+ method: "POST",
787
+ headers: {
788
+ "Content-Type": "application/json",
789
+ Authorization: `Bearer ${token}`
790
+ },
791
+ body: JSON.stringify({
792
+ url: originalUrl,
793
+ method: originalMethod,
794
+ headers: normalizeHeaders(init?.headers),
795
+ body: originalBody
796
+ })
797
+ });
798
+ };
799
+ }
800
+ function createDeployedAppProxyFetch(connectionId) {
801
+ const projectId = process.env["SQUADBASE_PROJECT_ID"];
802
+ if (!projectId) {
803
+ throw new Error(
804
+ "Connection proxy is not configured. Please check your deployment settings."
805
+ );
806
+ }
807
+ const baseDomain = process.env["SQUADBASE_APP_BASE_DOMAIN"] ?? "squadbase.app";
808
+ const proxyUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
809
+ const sessionUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
810
+ return async (input, init) => {
811
+ const originalUrl = extractInputUrl(input);
812
+ const c = getContext();
813
+ const appSession = getCookie(c, APP_SESSION_COOKIE_NAME);
814
+ if (!appSession) {
815
+ throw new Error(
816
+ "No authentication method available for connection proxy."
817
+ );
818
+ }
819
+ if (originalUrl === TABLEAU_SESSION_SENTINEL_URL) {
820
+ return fetch(sessionUrl, {
821
+ method: "POST",
822
+ headers: { Authorization: `Bearer ${appSession}` }
823
+ });
824
+ }
825
+ const originalMethod = init?.method ?? "GET";
826
+ const originalBody = init?.body ? JSON.parse(init.body) : void 0;
827
+ return fetch(proxyUrl, {
828
+ method: "POST",
829
+ headers: {
830
+ "Content-Type": "application/json",
831
+ Authorization: `Bearer ${appSession}`
832
+ },
833
+ body: JSON.stringify({
834
+ url: originalUrl,
835
+ method: originalMethod,
836
+ headers: normalizeHeaders(init?.headers),
837
+ body: originalBody
838
+ })
839
+ });
840
+ };
841
+ }
842
+ function createProxyFetch(connectionId) {
843
+ if (process.env.INTERNAL_SQUADBASE_SANDBOX_ID) {
844
+ return createSandboxProxyFetch(connectionId);
845
+ }
846
+ return createDeployedAppProxyFetch(connectionId);
847
+ }
848
+
849
+ // src/connectors/create-connector-sdk.ts
850
+ function loadConnectionsSync() {
851
+ const filePath = process.env.CONNECTIONS_PATH ?? path.join(process.cwd(), ".squadbase/connections.json");
852
+ try {
853
+ const raw = readFileSync(filePath, "utf-8");
854
+ return JSON.parse(raw);
855
+ } catch {
856
+ return {};
857
+ }
858
+ }
859
+ function createConnectorSdk(plugin, createClient2) {
860
+ return (connectionId) => {
861
+ const connections = loadConnectionsSync();
862
+ const entry = connections[connectionId];
863
+ if (!entry) {
864
+ throw new Error(
865
+ `Connection "${connectionId}" not found in .squadbase/connections.json`
866
+ );
867
+ }
868
+ if (entry.connector.slug !== plugin.slug) {
869
+ throw new Error(
870
+ `Connection "${connectionId}" is not a ${plugin.slug} connection (got "${entry.connector.slug}")`
871
+ );
872
+ }
873
+ const params = {};
874
+ for (const param of Object.values(plugin.parameters)) {
875
+ if (param.required) {
876
+ params[param.slug] = resolveEnvVar(entry, param.slug, connectionId);
877
+ } else {
878
+ const val = resolveEnvVarOptional(entry, param.slug);
879
+ if (val !== void 0) params[param.slug] = val;
880
+ }
881
+ }
882
+ return createClient2(params, createProxyFetch(connectionId));
883
+ };
884
+ }
885
+
886
+ // src/connectors/entries/hackernews.ts
887
+ var connection = createConnectorSdk(hackerNewsConnector, createClient);
888
+ export {
889
+ connection
890
+ };