@squadbase/vite-server 0.1.9-dev.f236b23 → 0.1.10-dev.7e3b16f

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.
@@ -701,9 +701,16 @@ A Filter is an Expression. Leaves are \`Dimensions\`, \`Tags\`, or \`CostCategor
701
701
 
702
702
  ### Business Logic
703
703
 
704
- The business logic type for this connector is "typescript". The connector exposes resolved AWS credentials via \`connection(connectionId)\`; pass them to \`@aws-sdk/client-cost-explorer\` directly inside the handler. Do NOT read AWS credentials from environment variables.
704
+ The business logic type for this connector is "typescript". The connector exposes resolved AWS credentials via \`connection(connectionId)\` (already typed as \`{ accessKeyId, secretAccessKey, region }\` \u2014 do NOT add an \`as\` cast). Pass them to \`@aws-sdk/client-cost-explorer\` directly inside the handler. Do NOT read AWS credentials from environment variables.
705
705
 
706
- #### Example
706
+ #### Server logic slug naming
707
+
708
+ When creating a server logic for this connector, the \`slug\` (file name) MUST be lowercase kebab-case or snake_case \u2014 only \`[a-z0-9-_]\` is allowed. camelCase and uppercase will fail validation.
709
+
710
+ - OK: \`monthly-cost-trend\`, \`cost_by_service\`, \`top10-services\`
711
+ - NG: \`monthlyCostTrend\`, \`MonthlyCostTrend\`, \`monthly cost trend\`
712
+
713
+ #### Example (slug: \`monthly-cost-trend\`)
707
714
 
708
715
  \`\`\`ts
709
716
  import type { Context } from "hono";
@@ -779,9 +786,16 @@ Filter \u306F Expression \u3067\u3059\u3002\u30EA\u30FC\u30D5\u306F \`Dimensions
779
786
 
780
787
  ### Business Logic
781
788
 
782
- \u3053\u306E\u30B3\u30CD\u30AF\u30BF\u306E\u30D3\u30B8\u30CD\u30B9\u30ED\u30B8\u30C3\u30AF\u30BF\u30A4\u30D7\u306F "typescript" \u3067\u3059\u3002\`connection(connectionId)\` \u3067\u89E3\u6C7A\u6E08\u307F\u306E AWS \u8A8D\u8A3C\u60C5\u5831\u3092\u53D6\u5F97\u3057\u3001\`@aws-sdk/client-cost-explorer\` \u306B\u76F4\u63A5\u6E21\u3057\u3066\u30CF\u30F3\u30C9\u30E9\u5185\u3067\u5229\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u74B0\u5883\u5909\u6570\u304B\u3089 AWS \u8A8D\u8A3C\u60C5\u5831\u3092\u8AAD\u307F\u53D6\u3089\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002
789
+ \u3053\u306E\u30B3\u30CD\u30AF\u30BF\u306E\u30D3\u30B8\u30CD\u30B9\u30ED\u30B8\u30C3\u30AF\u30BF\u30A4\u30D7\u306F "typescript" \u3067\u3059\u3002\`connection(connectionId)\` \u3067\u89E3\u6C7A\u6E08\u307F\u306E AWS \u8A8D\u8A3C\u60C5\u5831\uFF08\`{ accessKeyId, secretAccessKey, region }\` \u3068\u3057\u3066\u578B\u4ED8\u3051\u6E08\u307F \u2014 \`as\` \u30AD\u30E3\u30B9\u30C8\u306F\u4E0D\u8981\uFF09\u3092\u53D6\u5F97\u3057\u3001\`@aws-sdk/client-cost-explorer\` \u306B\u76F4\u63A5\u6E21\u3057\u3066\u30CF\u30F3\u30C9\u30E9\u5185\u3067\u5229\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u74B0\u5883\u5909\u6570\u304B\u3089 AWS \u8A8D\u8A3C\u60C5\u5831\u3092\u8AAD\u307F\u53D6\u3089\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002
790
+
791
+ #### \u30B5\u30FC\u30D0\u30FC\u30ED\u30B8\u30C3\u30AF\u306E slug \u547D\u540D\u898F\u5247
792
+
793
+ \u3053\u306E\u30B3\u30CD\u30AF\u30BF\u7528\u306E\u30B5\u30FC\u30D0\u30FC\u30ED\u30B8\u30C3\u30AF\u3092\u4F5C\u6210\u3059\u308B\u969B\u3001\`slug\`\uFF08\u30D5\u30A1\u30A4\u30EB\u540D\uFF09\u306F **\u5C0F\u6587\u5B57\u306E kebab-case \u307E\u305F\u306F snake_case** \u306B\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u8A31\u5BB9\u6587\u5B57\u306F \`[a-z0-9-_]\` \u306E\u307F\uFF09\u3002camelCase \u3084\u5927\u6587\u5B57\u3092\u542B\u3080\u3068\u30D0\u30EA\u30C7\u30FC\u30B7\u30E7\u30F3\u30A8\u30E9\u30FC\u306B\u306A\u308A\u307E\u3059\u3002
794
+
795
+ - OK: \`monthly-cost-trend\`\u3001\`cost_by_service\`\u3001\`top10-services\`
796
+ - NG: \`monthlyCostTrend\`\u3001\`MonthlyCostTrend\`\u3001\`monthly cost trend\`
783
797
 
784
- #### Example
798
+ #### Example\uFF08slug: \`monthly-cost-trend\`\uFF09
785
799
 
786
800
  \`\`\`ts
787
801
  import type { Context } from "hono";
@@ -42,6 +42,131 @@ var ParameterDefinition = class {
42
42
  }
43
43
  };
44
44
 
45
+ // ../connectors/src/lib/ssh-tunnel.ts
46
+ var sshTunnelParameters = {
47
+ sshHost: new ParameterDefinition({
48
+ slug: "ssh-host",
49
+ name: "SSH Tunnel Host",
50
+ description: "Optional. Hostname of the SSH bastion to tunnel through. Leave empty to connect directly.",
51
+ envVarBaseKey: "SSH_TUNNEL_HOST",
52
+ type: "text",
53
+ secret: false,
54
+ required: false
55
+ }),
56
+ sshPort: new ParameterDefinition({
57
+ slug: "ssh-port",
58
+ name: "SSH Tunnel Port",
59
+ description: "Optional. SSH port of the bastion host (default: 22).",
60
+ envVarBaseKey: "SSH_TUNNEL_PORT",
61
+ type: "text",
62
+ secret: false,
63
+ required: false
64
+ }),
65
+ sshUsername: new ParameterDefinition({
66
+ slug: "ssh-username",
67
+ name: "SSH Tunnel Username",
68
+ description: "Optional. Username for SSH authentication. Required when SSH Tunnel Host is set.",
69
+ envVarBaseKey: "SSH_TUNNEL_USERNAME",
70
+ type: "text",
71
+ secret: false,
72
+ required: false
73
+ }),
74
+ sshPrivateKeyBase64: new ParameterDefinition({
75
+ slug: "ssh-private-key-base64",
76
+ name: "SSH Private Key",
77
+ description: "Optional. Private key (PEM, base64-encoded) used for SSH authentication. Required when SSH Tunnel Host is set.",
78
+ envVarBaseKey: "SSH_TUNNEL_PRIVATE_KEY_BASE64",
79
+ type: "base64EncodedText",
80
+ secret: true,
81
+ required: false
82
+ }),
83
+ sshPassphrase: new ParameterDefinition({
84
+ slug: "ssh-passphrase",
85
+ name: "SSH Private Key Passphrase",
86
+ description: "Optional. Passphrase for the SSH private key, if it is encrypted.",
87
+ envVarBaseKey: "SSH_TUNNEL_PASSPHRASE",
88
+ type: "text",
89
+ secret: true,
90
+ required: false
91
+ })
92
+ };
93
+ var NOOP_TUNNEL_HOSTPORT = (host, port) => ({
94
+ host,
95
+ port,
96
+ close: async () => {
97
+ }
98
+ });
99
+ function connectionParamsToRecord(connection2) {
100
+ const out = {};
101
+ for (const p of connection2.parameters) {
102
+ if (p.value != null) out[p.parameterSlug] = p.value;
103
+ }
104
+ return out;
105
+ }
106
+ async function maybeOpenSshTunnelHostPort(params, dbHost, dbPort) {
107
+ const sshHost = params[sshTunnelParameters.sshHost.slug];
108
+ if (!sshHost) return NOOP_TUNNEL_HOSTPORT(dbHost, dbPort);
109
+ const sshUsername = params[sshTunnelParameters.sshUsername.slug];
110
+ const sshPrivateKeyBase64 = params[sshTunnelParameters.sshPrivateKeyBase64.slug];
111
+ if (!sshUsername || !sshPrivateKeyBase64) {
112
+ throw new Error(
113
+ "SSH tunnel requires `ssh-username` and `ssh-private-key-base64` when `ssh-host` is set."
114
+ );
115
+ }
116
+ const sshPort = Number(params[sshTunnelParameters.sshPort.slug] || "22") || 22;
117
+ const sshPassphrase = params[sshTunnelParameters.sshPassphrase.slug];
118
+ const [{ Client }, net] = await Promise.all([
119
+ import("ssh2"),
120
+ import("net")
121
+ ]);
122
+ const sshClient = new Client();
123
+ await new Promise((resolve, reject) => {
124
+ sshClient.once("ready", () => resolve());
125
+ sshClient.once("error", reject);
126
+ sshClient.connect({
127
+ host: sshHost,
128
+ port: sshPort,
129
+ username: sshUsername,
130
+ privateKey: Buffer.from(sshPrivateKeyBase64, "base64"),
131
+ passphrase: sshPassphrase || void 0,
132
+ readyTimeout: 1e4
133
+ });
134
+ });
135
+ const server = net.createServer((socket) => {
136
+ sshClient.forwardOut(
137
+ socket.remoteAddress ?? "127.0.0.1",
138
+ socket.remotePort ?? 0,
139
+ dbHost,
140
+ dbPort,
141
+ (err, stream) => {
142
+ if (err) {
143
+ socket.destroy(err);
144
+ return;
145
+ }
146
+ socket.pipe(stream).pipe(socket);
147
+ }
148
+ );
149
+ });
150
+ await new Promise((resolve, reject) => {
151
+ server.once("error", reject);
152
+ server.listen(0, "127.0.0.1", () => resolve());
153
+ });
154
+ const address = server.address();
155
+ if (!address || typeof address === "string") {
156
+ server.close();
157
+ sshClient.end();
158
+ throw new Error("Failed to allocate local port for SSH tunnel.");
159
+ }
160
+ return {
161
+ host: "127.0.0.1",
162
+ port: address.port,
163
+ close: async () => {
164
+ await new Promise((resolve) => server.close(() => resolve()));
165
+ sshClient.end();
166
+ }
167
+ };
168
+ }
169
+
45
170
  // ../connectors/src/connectors/sqlserver/utils.ts
46
171
  var SQLSERVER_PREFIX_RE = /^(?:jdbc:)?sqlserver:\/\//i;
47
172
  var TRUE_VALUES = /* @__PURE__ */ new Set(["true", "1", "yes"]);
@@ -120,17 +245,27 @@ async function importMssql() {
120
245
  }
121
246
  async function runMssqlQuery(parsed, sql, options = {}) {
122
247
  const sqlMod = await importMssql();
123
- const config = toMssqlConfig(parsed, {
124
- encrypt: options.forceEncrypt
125
- });
126
- const pool = new sqlMod.ConnectionPool(config);
127
- await pool.connect();
248
+ const tunnel = options.tunnelParams ? await maybeOpenSshTunnelHostPort(
249
+ options.tunnelParams,
250
+ parsed.server,
251
+ parsed.port
252
+ ) : null;
128
253
  try {
129
- const result = await pool.request().query(sql);
130
- const recordset = result.recordset ?? [];
131
- return { rows: recordset };
254
+ const tunneled = tunnel ? { ...parsed, server: tunnel.host, port: tunnel.port } : parsed;
255
+ const config = toMssqlConfig(tunneled, {
256
+ encrypt: options.forceEncrypt
257
+ });
258
+ const pool = new sqlMod.ConnectionPool(config);
259
+ await pool.connect();
260
+ try {
261
+ const result = await pool.request().query(sql);
262
+ const recordset = result.recordset ?? [];
263
+ return { rows: recordset };
264
+ } finally {
265
+ await pool.close();
266
+ }
132
267
  } finally {
133
- await pool.close();
268
+ await tunnel?.close();
134
269
  }
135
270
  }
136
271
  async function checkMssqlConnection(url, credentials, options = {}) {
@@ -181,7 +316,8 @@ var parameters = {
181
316
  type: "text",
182
317
  secret: true,
183
318
  required: false
184
- })
319
+ }),
320
+ ...sshTunnelParameters
185
321
  };
186
322
 
187
323
  // ../connectors/src/connectors/azure-sql/sdk/index.ts
@@ -197,7 +333,10 @@ function createClient(params) {
197
333
  const parsed = parseSqlServerJdbcUrl(jdbcUrl, { username, password });
198
334
  async function runQuery(sql) {
199
335
  try {
200
- const { rows } = await runMssqlQuery(parsed, sql, { forceEncrypt: true });
336
+ const { rows } = await runMssqlQuery(parsed, sql, {
337
+ forceEncrypt: true,
338
+ tunnelParams: params
339
+ });
201
340
  return rows;
202
341
  } catch (err) {
203
342
  const msg = err instanceof Error ? err.message : String(err);
@@ -442,7 +581,10 @@ Avoid loading large amounts of data; always include \`TOP\` in queries.`,
442
581
  };
443
582
  }
444
583
  try {
445
- const { rows } = await runMssqlQuery(parsed, sql, { forceEncrypt: true });
584
+ const { rows } = await runMssqlQuery(parsed, sql, {
585
+ forceEncrypt: true,
586
+ tunnelParams: connectionParamsToRecord(connection2)
587
+ });
446
588
  const truncated = rows.length > MAX_ROWS;
447
589
  return {
448
590
  success: true,
@@ -467,7 +609,7 @@ var azureSqlConnector = new ConnectorPlugin({
467
609
  description: "Connect to Azure SQL Database (managed) using a JDBC-style URL. Encryption is enforced automatically.",
468
610
  iconUrl: "https://images.ctfassets.net/9ncizv60xc5y/5TL0yBbxoLlk6jFZuiHl8w/55040f52d57bf0b77a2215c985c5a772/azure-sql-icon.png",
469
611
  parameters,
470
- releaseFlag: { dev1: true, dev2: false, prod: false },
612
+ releaseFlag: { dev1: true, dev2: true, prod: true },
471
613
  categories: ["database"],
472
614
  onboarding: azureSqlOnboarding,
473
615
  systemPrompt: {
@@ -514,7 +656,7 @@ The business logic type for this connector is "sql".
514
656
  username: params[parameters.username.slug],
515
657
  password: params[parameters.password.slug]
516
658
  },
517
- { forceEncrypt: true }
659
+ { forceEncrypt: true, tunnelParams: params }
518
660
  );
519
661
  },
520
662
  async query(params, sql, _namedParams) {
@@ -525,11 +667,15 @@ The business logic type for this connector is "sql".
525
667
  const sample = unwrapSampleLimit(sql);
526
668
  if (sample) {
527
669
  const result = await runMssqlQuery(parsed, sample.inner, {
528
- forceEncrypt: true
670
+ forceEncrypt: true,
671
+ tunnelParams: params
529
672
  });
530
673
  return { rows: result.rows.slice(0, sample.limit) };
531
674
  }
532
- return runMssqlQuery(parsed, sql, { forceEncrypt: true });
675
+ return runMssqlQuery(parsed, sql, {
676
+ forceEncrypt: true,
677
+ tunnelParams: params
678
+ });
533
679
  }
534
680
  });
535
681
 
@@ -0,0 +1,5 @@
1
+ import * as _squadbase_connectors_sdk from '@squadbase/connectors/sdk';
2
+
3
+ declare const connection: (connectionId: string) => _squadbase_connectors_sdk.CosmosDbConnectorSdk;
4
+
5
+ export { connection };