@squadbase/vite-server 0.1.9-dev.f236b23 → 0.1.10-dev.5b0c0a8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +1526 -89508
- package/dist/connectors/aws-billing.js +18 -4
- package/dist/connectors/azure-sql.js +162 -16
- package/dist/connectors/cosmosdb.d.ts +5 -0
- package/dist/connectors/cosmosdb.js +743 -0
- package/dist/connectors/google-ads.js +196 -120
- package/dist/connectors/jdbc.js +311 -20402
- package/dist/connectors/mongodb.d.ts +5 -0
- package/dist/connectors/mongodb.js +879 -0
- package/dist/connectors/oracle.js +205 -28
- package/dist/connectors/semrush.js +109 -21
- package/dist/connectors/sqlserver.js +158 -15
- package/dist/index.js +1595 -89587
- package/dist/main.js +1580 -89572
- package/dist/vite-plugin.js +1475 -89467
- package/package.json +13 -2
- package/dist/cli/cpufeatures-ORCDQN2Y.node +0 -0
- package/dist/cli/sshcrypto-P3UBA7BP.node +0 -0
- package/dist/cpufeatures-ORCDQN2Y.node +0 -0
- package/dist/sshcrypto-P3UBA7BP.node +0 -0
|
@@ -0,0 +1,879 @@
|
|
|
1
|
+
// ../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;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// ../connectors/src/connectors/mongodb/parameters.ts
|
|
46
|
+
var parameters = {
|
|
47
|
+
connectionUrl: new ParameterDefinition({
|
|
48
|
+
slug: "connection-url",
|
|
49
|
+
name: "MongoDB Connection URL",
|
|
50
|
+
description: "The MongoDB connection string (e.g., mongodb+srv://user:password@cluster.mongodb.net or mongodb://user:password@host:27017).",
|
|
51
|
+
envVarBaseKey: "MONGODB_URL",
|
|
52
|
+
type: "text",
|
|
53
|
+
secret: true,
|
|
54
|
+
required: true
|
|
55
|
+
}),
|
|
56
|
+
database: new ParameterDefinition({
|
|
57
|
+
slug: "database",
|
|
58
|
+
name: "MongoDB Database",
|
|
59
|
+
description: "The database name to connect to.",
|
|
60
|
+
envVarBaseKey: "MONGODB_DATABASE",
|
|
61
|
+
type: "text",
|
|
62
|
+
secret: false,
|
|
63
|
+
required: true
|
|
64
|
+
})
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// ../connectors/src/connectors/mongodb/sdk/index.ts
|
|
68
|
+
var CONNECT_TIMEOUT_MS = 1e4;
|
|
69
|
+
var QUERY_TIMEOUT_MS = 6e4;
|
|
70
|
+
function createClient(params) {
|
|
71
|
+
const connectionUrl = params[parameters.connectionUrl.slug];
|
|
72
|
+
if (!connectionUrl) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`mongodb: missing required parameter: ${parameters.connectionUrl.slug}`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
const database = params[parameters.database.slug];
|
|
78
|
+
if (!database) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`mongodb: missing required parameter: ${parameters.database.slug}`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
function redact(message) {
|
|
84
|
+
return message.replaceAll(connectionUrl, "***");
|
|
85
|
+
}
|
|
86
|
+
function serializeDocument(doc) {
|
|
87
|
+
const out = {};
|
|
88
|
+
for (const [key, value] of Object.entries(doc)) {
|
|
89
|
+
out[key] = value !== null && typeof value === "object" && "toHexString" in value && typeof value.toHexString === "function" ? String(value) : value;
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
async function withDb(fn) {
|
|
94
|
+
const { MongoClient } = await import("mongodb");
|
|
95
|
+
const client = new MongoClient(connectionUrl, {
|
|
96
|
+
connectTimeoutMS: CONNECT_TIMEOUT_MS,
|
|
97
|
+
serverSelectionTimeoutMS: CONNECT_TIMEOUT_MS,
|
|
98
|
+
socketTimeoutMS: QUERY_TIMEOUT_MS
|
|
99
|
+
});
|
|
100
|
+
try {
|
|
101
|
+
await client.connect();
|
|
102
|
+
return await fn(client.db(database));
|
|
103
|
+
} catch (err) {
|
|
104
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
105
|
+
throw new Error(redact(msg));
|
|
106
|
+
} finally {
|
|
107
|
+
await client.close();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async function aggregate(collection, pipeline, options) {
|
|
111
|
+
return withDb(async (db) => {
|
|
112
|
+
const cursor = db.collection(collection).aggregate(pipeline, options);
|
|
113
|
+
const documents = await cursor.toArray();
|
|
114
|
+
return documents.map(serializeDocument);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
async function find(collection, filter = {}, options = {}) {
|
|
118
|
+
return withDb(async (db) => {
|
|
119
|
+
let cursor = db.collection(collection).find(filter);
|
|
120
|
+
if (options.projection) cursor = cursor.project(options.projection);
|
|
121
|
+
if (options.sort) cursor = cursor.sort(options.sort);
|
|
122
|
+
if (options.skip != null) cursor = cursor.skip(options.skip);
|
|
123
|
+
if (options.limit != null) cursor = cursor.limit(options.limit);
|
|
124
|
+
const documents = await cursor.toArray();
|
|
125
|
+
return documents.map(serializeDocument);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
async function findOne(collection, filter = {}, options = {}) {
|
|
129
|
+
const docs = await find(collection, filter, { ...options, limit: 1 });
|
|
130
|
+
return docs.length > 0 ? docs[0] : null;
|
|
131
|
+
}
|
|
132
|
+
async function countDocuments(collection, filter = {}) {
|
|
133
|
+
return withDb(async (db) => {
|
|
134
|
+
return db.collection(collection).countDocuments(filter);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
async function listCollections() {
|
|
138
|
+
return withDb(async (db) => {
|
|
139
|
+
const list = await db.listCollections().toArray();
|
|
140
|
+
return list.map((c) => ({ name: c.name, type: c.type ?? "collection" }));
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
aggregate,
|
|
145
|
+
find,
|
|
146
|
+
findOne,
|
|
147
|
+
countDocuments,
|
|
148
|
+
listCollections
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ../connectors/src/connector-onboarding.ts
|
|
153
|
+
var ConnectorOnboarding = class {
|
|
154
|
+
/** Phase 1: Connection setup instructions (optional — some connectors don't need this) */
|
|
155
|
+
connectionSetupInstructions;
|
|
156
|
+
/** Phase 2: Data overview instructions */
|
|
157
|
+
dataOverviewInstructions;
|
|
158
|
+
constructor(config) {
|
|
159
|
+
this.connectionSetupInstructions = config.connectionSetupInstructions;
|
|
160
|
+
this.dataOverviewInstructions = config.dataOverviewInstructions;
|
|
161
|
+
}
|
|
162
|
+
getConnectionSetupPrompt(language) {
|
|
163
|
+
return this.connectionSetupInstructions?.[language] ?? null;
|
|
164
|
+
}
|
|
165
|
+
getDataOverviewInstructions(language) {
|
|
166
|
+
return this.dataOverviewInstructions[language];
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// ../connectors/src/connector-tool.ts
|
|
171
|
+
var ConnectorTool = class {
|
|
172
|
+
name;
|
|
173
|
+
description;
|
|
174
|
+
inputSchema;
|
|
175
|
+
outputSchema;
|
|
176
|
+
_execute;
|
|
177
|
+
constructor(config) {
|
|
178
|
+
this.name = config.name;
|
|
179
|
+
this.description = config.description;
|
|
180
|
+
this.inputSchema = config.inputSchema;
|
|
181
|
+
this.outputSchema = config.outputSchema;
|
|
182
|
+
this._execute = config.execute;
|
|
183
|
+
}
|
|
184
|
+
createTool(connections, config) {
|
|
185
|
+
return {
|
|
186
|
+
description: this.description,
|
|
187
|
+
inputSchema: this.inputSchema,
|
|
188
|
+
outputSchema: this.outputSchema,
|
|
189
|
+
execute: (input) => this._execute(input, connections, config)
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// ../connectors/src/connector-plugin.ts
|
|
195
|
+
var ConnectorPlugin = class _ConnectorPlugin {
|
|
196
|
+
slug;
|
|
197
|
+
authType;
|
|
198
|
+
name;
|
|
199
|
+
description;
|
|
200
|
+
iconUrl;
|
|
201
|
+
parameters;
|
|
202
|
+
releaseFlag;
|
|
203
|
+
proxyPolicy;
|
|
204
|
+
experimentalAttributes;
|
|
205
|
+
categories;
|
|
206
|
+
onboarding;
|
|
207
|
+
systemPrompt;
|
|
208
|
+
tools;
|
|
209
|
+
query;
|
|
210
|
+
checkConnection;
|
|
211
|
+
constructor(config) {
|
|
212
|
+
this.slug = config.slug;
|
|
213
|
+
this.authType = config.authType;
|
|
214
|
+
this.name = config.name;
|
|
215
|
+
this.description = config.description;
|
|
216
|
+
this.iconUrl = config.iconUrl;
|
|
217
|
+
this.parameters = config.parameters;
|
|
218
|
+
this.releaseFlag = config.releaseFlag;
|
|
219
|
+
this.proxyPolicy = config.proxyPolicy;
|
|
220
|
+
this.experimentalAttributes = config.experimentalAttributes;
|
|
221
|
+
this.categories = config.categories ?? [];
|
|
222
|
+
this.onboarding = config.onboarding;
|
|
223
|
+
this.systemPrompt = config.systemPrompt;
|
|
224
|
+
this.tools = config.tools;
|
|
225
|
+
this.query = config.query;
|
|
226
|
+
this.checkConnection = config.checkConnection;
|
|
227
|
+
}
|
|
228
|
+
get connectorKey() {
|
|
229
|
+
return _ConnectorPlugin.deriveKey(this.slug, this.authType);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Create tools for connections that belong to this connector.
|
|
233
|
+
* Filters connections by connectorKey internally.
|
|
234
|
+
* Returns tools keyed as `${connectorKey}_${toolName}`.
|
|
235
|
+
*/
|
|
236
|
+
createTools(connections, config, opts) {
|
|
237
|
+
const myConnections = connections.filter(
|
|
238
|
+
(c) => _ConnectorPlugin.deriveKey(c.connector.slug, c.connector.authType) === this.connectorKey
|
|
239
|
+
);
|
|
240
|
+
const result = {};
|
|
241
|
+
for (const t of Object.values(this.tools)) {
|
|
242
|
+
const tool = t.createTool(myConnections, config);
|
|
243
|
+
const originalToModelOutput = tool.toModelOutput;
|
|
244
|
+
result[`${this.connectorKey}_${t.name}`] = {
|
|
245
|
+
...tool,
|
|
246
|
+
toModelOutput: async (options) => {
|
|
247
|
+
if (!originalToModelOutput) {
|
|
248
|
+
return opts.truncateOutput(options.output);
|
|
249
|
+
}
|
|
250
|
+
const modelOutput = await originalToModelOutput(options);
|
|
251
|
+
if (modelOutput.type === "text" || modelOutput.type === "json") {
|
|
252
|
+
return opts.truncateOutput(modelOutput.value);
|
|
253
|
+
}
|
|
254
|
+
return modelOutput;
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
return result;
|
|
259
|
+
}
|
|
260
|
+
static deriveKey(slug, authType) {
|
|
261
|
+
if (authType) return `${slug}-${authType}`;
|
|
262
|
+
const LEGACY_NULL_AUTH_TYPE_MAP = {
|
|
263
|
+
// user-password
|
|
264
|
+
"postgresql": "user-password",
|
|
265
|
+
"mysql": "user-password",
|
|
266
|
+
"clickhouse": "user-password",
|
|
267
|
+
"kintone": "user-password",
|
|
268
|
+
"squadbase-db": "user-password",
|
|
269
|
+
// service-account
|
|
270
|
+
"snowflake": "service-account",
|
|
271
|
+
"bigquery": "service-account",
|
|
272
|
+
"google-analytics": "service-account",
|
|
273
|
+
"google-calendar": "service-account",
|
|
274
|
+
"aws-athena": "service-account",
|
|
275
|
+
"redshift": "service-account",
|
|
276
|
+
// api-key
|
|
277
|
+
"databricks": "api-key",
|
|
278
|
+
"dbt": "api-key",
|
|
279
|
+
"airtable": "api-key",
|
|
280
|
+
"openai": "api-key",
|
|
281
|
+
"gemini": "api-key",
|
|
282
|
+
"anthropic": "api-key",
|
|
283
|
+
"wix-store": "api-key"
|
|
284
|
+
};
|
|
285
|
+
const fallbackAuthType = LEGACY_NULL_AUTH_TYPE_MAP[slug];
|
|
286
|
+
if (fallbackAuthType) return `${slug}-${fallbackAuthType}`;
|
|
287
|
+
return slug;
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// ../connectors/src/auth-types.ts
|
|
292
|
+
var AUTH_TYPES = {
|
|
293
|
+
OAUTH: "oauth",
|
|
294
|
+
API_KEY: "api-key",
|
|
295
|
+
JWT: "jwt",
|
|
296
|
+
SERVICE_ACCOUNT: "service-account",
|
|
297
|
+
PAT: "pat",
|
|
298
|
+
USER_PASSWORD: "user-password"
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
// ../connectors/src/connectors/mongodb/setup.ts
|
|
302
|
+
var mongodbOnboarding = new ConnectorOnboarding({
|
|
303
|
+
dataOverviewInstructions: {
|
|
304
|
+
en: `1. Use listCollections to list all collections in the database
|
|
305
|
+
2. For key collections, use find with limit to sample documents: collection="users", filter="{}", limit=5
|
|
306
|
+
3. Examine the document structure to understand the schema (MongoDB is schema-flexible, so documents in the same collection may have different fields)
|
|
307
|
+
4. Use aggregate with $group to understand data distribution if needed: pipeline='[{"$group": {"_id": "$fieldName", "count": {"$sum": 1}}}, {"$limit": 20}]'`,
|
|
308
|
+
ja: `1. listCollections \u3067\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u5185\u306E\u30B3\u30EC\u30AF\u30B7\u30E7\u30F3\u4E00\u89A7\u3092\u53D6\u5F97
|
|
309
|
+
2. \u4E3B\u8981\u30B3\u30EC\u30AF\u30B7\u30E7\u30F3\u306B\u3064\u3044\u3066 find \u3067\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8\u3092\u30B5\u30F3\u30D7\u30EA\u30F3\u30B0: collection="users", filter="{}", limit=5
|
|
310
|
+
3. \u30C9\u30AD\u30E5\u30E1\u30F3\u30C8\u69CB\u9020\u3092\u78BA\u8A8D\u3057\u3066\u30B9\u30AD\u30FC\u30DE\u3092\u628A\u63E1\uFF08MongoDB\u306F\u30B9\u30AD\u30FC\u30DE\u304C\u67D4\u8EDF\u306A\u305F\u3081\u3001\u540C\u3058\u30B3\u30EC\u30AF\u30B7\u30E7\u30F3\u5185\u3067\u3082\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8\u306E\u30D5\u30A3\u30FC\u30EB\u30C9\u304C\u7570\u306A\u308B\u5834\u5408\u304C\u3042\u308A\u307E\u3059\uFF09
|
|
311
|
+
4. \u5FC5\u8981\u306B\u5FDC\u3058\u3066 aggregate \u306E $group \u3067\u30C7\u30FC\u30BF\u5206\u5E03\u3092\u78BA\u8A8D: pipeline='[{"$group": {"_id": "$fieldName", "count": {"$sum": 1}}}, {"$limit": 20}]'`
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// ../connectors/src/connectors/mongodb/tools/find-documents.ts
|
|
316
|
+
import { z } from "zod";
|
|
317
|
+
var MAX_DOCUMENTS = 500;
|
|
318
|
+
var CONNECT_TIMEOUT_MS2 = 1e4;
|
|
319
|
+
var QUERY_TIMEOUT_MS2 = 6e4;
|
|
320
|
+
var inputSchema = z.object({
|
|
321
|
+
toolUseIntent: z.string().optional().describe(
|
|
322
|
+
"Brief description of what you intend to accomplish with this tool call"
|
|
323
|
+
),
|
|
324
|
+
connectionId: z.string().describe("ID of the MongoDB connection to use"),
|
|
325
|
+
collection: z.string().describe("The name of the collection to query"),
|
|
326
|
+
filter: z.string().optional().describe(
|
|
327
|
+
`MongoDB filter query as a JSON string (e.g., '{"status": "active"}', '{"age": {"$gt": 25}}'). Defaults to '{}' (all documents).`
|
|
328
|
+
),
|
|
329
|
+
projection: z.string().optional().describe(
|
|
330
|
+
`MongoDB projection as a JSON string to include/exclude fields (e.g., '{"name": 1, "email": 1}' to include only name and email).`
|
|
331
|
+
),
|
|
332
|
+
sort: z.string().optional().describe(
|
|
333
|
+
`MongoDB sort specification as a JSON string (e.g., '{"createdAt": -1}' for descending by createdAt).`
|
|
334
|
+
),
|
|
335
|
+
limit: z.number().optional().describe(
|
|
336
|
+
`Maximum number of documents to return. Defaults to ${MAX_DOCUMENTS}. Cannot exceed ${MAX_DOCUMENTS}.`
|
|
337
|
+
)
|
|
338
|
+
});
|
|
339
|
+
var outputSchema = z.discriminatedUnion("success", [
|
|
340
|
+
z.object({
|
|
341
|
+
success: z.literal(true),
|
|
342
|
+
documentCount: z.number(),
|
|
343
|
+
truncated: z.boolean(),
|
|
344
|
+
documents: z.array(z.record(z.string(), z.unknown()))
|
|
345
|
+
}),
|
|
346
|
+
z.object({
|
|
347
|
+
success: z.literal(false),
|
|
348
|
+
error: z.string()
|
|
349
|
+
})
|
|
350
|
+
]);
|
|
351
|
+
var findDocumentsTool = new ConnectorTool({
|
|
352
|
+
name: "find",
|
|
353
|
+
description: `Find documents in a MongoDB collection. Returns up to ${MAX_DOCUMENTS} documents.
|
|
354
|
+
Use for: querying documents with filters, projecting specific fields, sorting results, and data sampling.
|
|
355
|
+
Pass filter, projection, and sort as JSON strings. Always use limit to avoid loading large amounts of data.
|
|
356
|
+
For listing collections, use the aggregate tool with an empty pipeline on the special collection name "$cmd.listCollections".`,
|
|
357
|
+
inputSchema,
|
|
358
|
+
outputSchema,
|
|
359
|
+
async execute({ connectionId, collection, filter, projection, sort, limit }, connections) {
|
|
360
|
+
const connection2 = connections.find((c) => c.id === connectionId);
|
|
361
|
+
if (!connection2) {
|
|
362
|
+
return {
|
|
363
|
+
success: false,
|
|
364
|
+
error: `Connection ${connectionId} not found`
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
console.log(
|
|
368
|
+
`[connector-query] mongodb/${connection2.name}: find in ${collection}`
|
|
369
|
+
);
|
|
370
|
+
let connectionUrl;
|
|
371
|
+
try {
|
|
372
|
+
const { MongoClient } = await import("mongodb");
|
|
373
|
+
connectionUrl = parameters.connectionUrl.getValue(connection2);
|
|
374
|
+
const database = parameters.database.getValue(connection2);
|
|
375
|
+
const client = new MongoClient(connectionUrl, {
|
|
376
|
+
connectTimeoutMS: CONNECT_TIMEOUT_MS2,
|
|
377
|
+
serverSelectionTimeoutMS: CONNECT_TIMEOUT_MS2,
|
|
378
|
+
socketTimeoutMS: QUERY_TIMEOUT_MS2
|
|
379
|
+
});
|
|
380
|
+
try {
|
|
381
|
+
await client.connect();
|
|
382
|
+
const db = client.db(database);
|
|
383
|
+
const coll = db.collection(collection);
|
|
384
|
+
const parsedFilter = filter ? JSON.parse(filter) : {};
|
|
385
|
+
const parsedProjection = projection ? JSON.parse(projection) : void 0;
|
|
386
|
+
const parsedSort = sort ? JSON.parse(sort) : void 0;
|
|
387
|
+
const effectiveLimit = Math.min(limit ?? MAX_DOCUMENTS, MAX_DOCUMENTS);
|
|
388
|
+
let cursor = coll.find(parsedFilter);
|
|
389
|
+
if (parsedProjection) cursor = cursor.project(parsedProjection);
|
|
390
|
+
if (parsedSort) cursor = cursor.sort(parsedSort);
|
|
391
|
+
cursor = cursor.limit(effectiveLimit + 1);
|
|
392
|
+
const documents = await cursor.toArray();
|
|
393
|
+
const truncated = documents.length > effectiveLimit;
|
|
394
|
+
const resultDocs = documents.slice(0, effectiveLimit).map((doc) => {
|
|
395
|
+
const obj = {};
|
|
396
|
+
for (const [key, value] of Object.entries(doc)) {
|
|
397
|
+
obj[key] = value instanceof Object && "toHexString" in value ? String(value) : value;
|
|
398
|
+
}
|
|
399
|
+
return obj;
|
|
400
|
+
});
|
|
401
|
+
return {
|
|
402
|
+
success: true,
|
|
403
|
+
documentCount: resultDocs.length,
|
|
404
|
+
truncated,
|
|
405
|
+
documents: resultDocs
|
|
406
|
+
};
|
|
407
|
+
} finally {
|
|
408
|
+
await client.close();
|
|
409
|
+
}
|
|
410
|
+
} catch (err) {
|
|
411
|
+
let msg = err instanceof Error ? err.message : String(err);
|
|
412
|
+
if (connectionUrl) {
|
|
413
|
+
msg = msg.replaceAll(connectionUrl, "***");
|
|
414
|
+
}
|
|
415
|
+
return { success: false, error: msg };
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
// ../connectors/src/connectors/mongodb/tools/aggregate.ts
|
|
421
|
+
import { z as z2 } from "zod";
|
|
422
|
+
var MAX_DOCUMENTS2 = 500;
|
|
423
|
+
var CONNECT_TIMEOUT_MS3 = 1e4;
|
|
424
|
+
var QUERY_TIMEOUT_MS3 = 6e4;
|
|
425
|
+
var inputSchema2 = z2.object({
|
|
426
|
+
toolUseIntent: z2.string().optional().describe(
|
|
427
|
+
"Brief description of what you intend to accomplish with this tool call"
|
|
428
|
+
),
|
|
429
|
+
connectionId: z2.string().describe("ID of the MongoDB connection to use"),
|
|
430
|
+
collection: z2.string().describe("The name of the collection to run the aggregation pipeline on"),
|
|
431
|
+
pipeline: z2.string().describe(
|
|
432
|
+
`MongoDB aggregation pipeline as a JSON string. An array of stage objects (e.g., '[{"$match": {"status": "active"}}, {"$group": {"_id": "$category", "count": {"$sum": 1}}}]'). Always include a $limit stage to avoid returning too many documents.`
|
|
433
|
+
)
|
|
434
|
+
});
|
|
435
|
+
var outputSchema2 = z2.discriminatedUnion("success", [
|
|
436
|
+
z2.object({
|
|
437
|
+
success: z2.literal(true),
|
|
438
|
+
documentCount: z2.number(),
|
|
439
|
+
truncated: z2.boolean(),
|
|
440
|
+
documents: z2.array(z2.record(z2.string(), z2.unknown()))
|
|
441
|
+
}),
|
|
442
|
+
z2.object({
|
|
443
|
+
success: z2.literal(false),
|
|
444
|
+
error: z2.string()
|
|
445
|
+
})
|
|
446
|
+
]);
|
|
447
|
+
var aggregateTool = new ConnectorTool({
|
|
448
|
+
name: "aggregate",
|
|
449
|
+
description: `Run a MongoDB aggregation pipeline on a collection. Returns up to ${MAX_DOCUMENTS2} documents.
|
|
450
|
+
Use for: complex data transformations, grouping, filtering, joining (via $lookup), statistical calculations, and data analysis.
|
|
451
|
+
Pass the pipeline as a JSON string representing an array of aggregation stages.
|
|
452
|
+
Always include a $limit stage in the pipeline to avoid loading large amounts of data.
|
|
453
|
+
Common stages: $match (filter), $group (aggregate), $sort (order), $project (reshape), $lookup (join), $unwind (flatten arrays).`,
|
|
454
|
+
inputSchema: inputSchema2,
|
|
455
|
+
outputSchema: outputSchema2,
|
|
456
|
+
async execute({ connectionId, collection, pipeline }, connections) {
|
|
457
|
+
const connection2 = connections.find((c) => c.id === connectionId);
|
|
458
|
+
if (!connection2) {
|
|
459
|
+
return {
|
|
460
|
+
success: false,
|
|
461
|
+
error: `Connection ${connectionId} not found`
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
console.log(
|
|
465
|
+
`[connector-query] mongodb/${connection2.name}: aggregate on ${collection}`
|
|
466
|
+
);
|
|
467
|
+
let connectionUrl;
|
|
468
|
+
try {
|
|
469
|
+
const { MongoClient } = await import("mongodb");
|
|
470
|
+
connectionUrl = parameters.connectionUrl.getValue(connection2);
|
|
471
|
+
const database = parameters.database.getValue(connection2);
|
|
472
|
+
const client = new MongoClient(connectionUrl, {
|
|
473
|
+
connectTimeoutMS: CONNECT_TIMEOUT_MS3,
|
|
474
|
+
serverSelectionTimeoutMS: CONNECT_TIMEOUT_MS3,
|
|
475
|
+
socketTimeoutMS: QUERY_TIMEOUT_MS3
|
|
476
|
+
});
|
|
477
|
+
try {
|
|
478
|
+
await client.connect();
|
|
479
|
+
const db = client.db(database);
|
|
480
|
+
const coll = db.collection(collection);
|
|
481
|
+
const parsedPipeline = JSON.parse(pipeline);
|
|
482
|
+
const documents = await coll.aggregate(parsedPipeline).toArray();
|
|
483
|
+
const truncated = documents.length > MAX_DOCUMENTS2;
|
|
484
|
+
const resultDocs = documents.slice(0, MAX_DOCUMENTS2).map((doc) => {
|
|
485
|
+
const obj = {};
|
|
486
|
+
for (const [key, value] of Object.entries(doc)) {
|
|
487
|
+
obj[key] = value instanceof Object && "toHexString" in value ? String(value) : value;
|
|
488
|
+
}
|
|
489
|
+
return obj;
|
|
490
|
+
});
|
|
491
|
+
return {
|
|
492
|
+
success: true,
|
|
493
|
+
documentCount: resultDocs.length,
|
|
494
|
+
truncated,
|
|
495
|
+
documents: resultDocs
|
|
496
|
+
};
|
|
497
|
+
} finally {
|
|
498
|
+
await client.close();
|
|
499
|
+
}
|
|
500
|
+
} catch (err) {
|
|
501
|
+
let msg = err instanceof Error ? err.message : String(err);
|
|
502
|
+
if (connectionUrl) {
|
|
503
|
+
msg = msg.replaceAll(connectionUrl, "***");
|
|
504
|
+
}
|
|
505
|
+
return { success: false, error: msg };
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
// ../connectors/src/connectors/mongodb/tools/list-collections.ts
|
|
511
|
+
import { z as z3 } from "zod";
|
|
512
|
+
var CONNECT_TIMEOUT_MS4 = 1e4;
|
|
513
|
+
var inputSchema3 = z3.object({
|
|
514
|
+
toolUseIntent: z3.string().optional().describe(
|
|
515
|
+
"Brief description of what you intend to accomplish with this tool call"
|
|
516
|
+
),
|
|
517
|
+
connectionId: z3.string().describe("ID of the MongoDB connection to use")
|
|
518
|
+
});
|
|
519
|
+
var outputSchema3 = z3.discriminatedUnion("success", [
|
|
520
|
+
z3.object({
|
|
521
|
+
success: z3.literal(true),
|
|
522
|
+
collections: z3.array(z3.object({
|
|
523
|
+
name: z3.string(),
|
|
524
|
+
type: z3.string()
|
|
525
|
+
}))
|
|
526
|
+
}),
|
|
527
|
+
z3.object({
|
|
528
|
+
success: z3.literal(false),
|
|
529
|
+
error: z3.string()
|
|
530
|
+
})
|
|
531
|
+
]);
|
|
532
|
+
var listCollectionsTool = new ConnectorTool({
|
|
533
|
+
name: "listCollections",
|
|
534
|
+
description: `List all collections in the MongoDB database.
|
|
535
|
+
Use this as the first step to explore the data structure. Returns collection names and types.
|
|
536
|
+
After listing collections, use the find tool to sample documents and understand the schema of each collection.`,
|
|
537
|
+
inputSchema: inputSchema3,
|
|
538
|
+
outputSchema: outputSchema3,
|
|
539
|
+
async execute({ connectionId }, connections) {
|
|
540
|
+
const connection2 = connections.find((c) => c.id === connectionId);
|
|
541
|
+
if (!connection2) {
|
|
542
|
+
return {
|
|
543
|
+
success: false,
|
|
544
|
+
error: `Connection ${connectionId} not found`
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
console.log(
|
|
548
|
+
`[connector-query] mongodb/${connection2.name}: listCollections`
|
|
549
|
+
);
|
|
550
|
+
let connectionUrl;
|
|
551
|
+
try {
|
|
552
|
+
const { MongoClient } = await import("mongodb");
|
|
553
|
+
connectionUrl = parameters.connectionUrl.getValue(connection2);
|
|
554
|
+
const database = parameters.database.getValue(connection2);
|
|
555
|
+
const client = new MongoClient(connectionUrl, {
|
|
556
|
+
connectTimeoutMS: CONNECT_TIMEOUT_MS4,
|
|
557
|
+
serverSelectionTimeoutMS: CONNECT_TIMEOUT_MS4
|
|
558
|
+
});
|
|
559
|
+
try {
|
|
560
|
+
await client.connect();
|
|
561
|
+
const db = client.db(database);
|
|
562
|
+
const collections = await db.listCollections().toArray();
|
|
563
|
+
return {
|
|
564
|
+
success: true,
|
|
565
|
+
collections: collections.map((c) => ({
|
|
566
|
+
name: c.name,
|
|
567
|
+
type: c.type || "collection"
|
|
568
|
+
}))
|
|
569
|
+
};
|
|
570
|
+
} finally {
|
|
571
|
+
await client.close();
|
|
572
|
+
}
|
|
573
|
+
} catch (err) {
|
|
574
|
+
let msg = err instanceof Error ? err.message : String(err);
|
|
575
|
+
if (connectionUrl) {
|
|
576
|
+
msg = msg.replaceAll(connectionUrl, "***");
|
|
577
|
+
}
|
|
578
|
+
return { success: false, error: msg };
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
// ../connectors/src/connectors/mongodb/index.ts
|
|
584
|
+
var tools = {
|
|
585
|
+
find: findDocumentsTool,
|
|
586
|
+
aggregate: aggregateTool,
|
|
587
|
+
listCollections: listCollectionsTool
|
|
588
|
+
};
|
|
589
|
+
var mongodbConnector = new ConnectorPlugin({
|
|
590
|
+
slug: "mongodb",
|
|
591
|
+
authType: AUTH_TYPES.USER_PASSWORD,
|
|
592
|
+
name: "MongoDB",
|
|
593
|
+
description: "Connect to MongoDB for document-oriented data storage and querying.",
|
|
594
|
+
iconUrl: "https://images.ctfassets.net/9ncizv60xc5y/48JSUx9HE6oSa9JrHGg2E1/12b4cac65924cac3641d4bafcef37dbe/mongodb-icon.webp",
|
|
595
|
+
parameters,
|
|
596
|
+
releaseFlag: { dev1: true, dev2: true, prod: true },
|
|
597
|
+
categories: ["database"],
|
|
598
|
+
onboarding: mongodbOnboarding,
|
|
599
|
+
systemPrompt: {
|
|
600
|
+
en: `### Tools
|
|
601
|
+
|
|
602
|
+
- \`mongodb_listCollections\`: Lists all collections in the database. Use this first to explore available data.
|
|
603
|
+
- \`mongodb_find\`: Finds documents in a collection with optional filter, projection, sort, and limit. Use for data sampling and querying specific documents. Pass filter, projection, and sort as JSON strings.
|
|
604
|
+
- \`mongodb_aggregate\`: Runs a MongoDB aggregation pipeline on a collection. Use for complex data analysis, grouping, joining, and transformations. Pass pipeline as a JSON string.
|
|
605
|
+
|
|
606
|
+
### Business Logic
|
|
607
|
+
|
|
608
|
+
The business logic type for this connector is "typescript". Write handler code using the connector SDK shown below. Do NOT access credentials directly from environment variables, and do NOT \`import { MongoClient } from "mongodb"\` in handler code \u2014 the \`mongodb\` driver is not part of the user dashboard's runtime, only of the connector SDK.
|
|
609
|
+
|
|
610
|
+
\u26A0\uFE0F **The client returned by \`connection(connectionId)\` is NOT a raw \`mongodb\` \`MongoClient\`.** It is a thin wrapper that opens a fresh connection per call, runs the requested operation against the database configured on the connection, and converts BSON \`ObjectId\` values to their hex string for JSON-safe responses. The methods are:
|
|
611
|
+
|
|
612
|
+
- \`client.aggregate<T>(collection, pipeline, options?) => Promise<T[]>\` \u2014 runs an aggregation pipeline and returns the document array. Always include a \`$limit\` stage.
|
|
613
|
+
- \`client.find<T>(collection, filter?, options?) => Promise<T[]>\` \u2014 \`options\` accepts \`{ projection?, sort?, limit?, skip? }\`. Always pass \`limit\`.
|
|
614
|
+
- \`client.findOne<T>(collection, filter?, options?) => Promise<T | null>\` \u2014 convenience wrapper around \`find\` with \`limit: 1\`.
|
|
615
|
+
- \`client.countDocuments(collection, filter?) => Promise<number>\` \u2014 accurate count (not \`estimatedDocumentCount\`).
|
|
616
|
+
- \`client.listCollections() => Promise<{ name: string; type: string }[]>\` \u2014 collections in the configured database.
|
|
617
|
+
|
|
618
|
+
There is **no** \`client.db(...)\`, \`client.connect()\`, or raw cursor / \`MongoClient\` API exposed through this client \u2014 those calls fail with \`X is not a function\`.
|
|
619
|
+
|
|
620
|
+
\`\`\`ts
|
|
621
|
+
import type { Context } from "hono";
|
|
622
|
+
import { connection } from "@squadbase/vite-server/connectors/mongodb";
|
|
623
|
+
|
|
624
|
+
const mongo = connection("<connectionId>");
|
|
625
|
+
|
|
626
|
+
export default async function handler(_c: Context) {
|
|
627
|
+
const rows = await mongo.aggregate<{ country: string; count: number }>(
|
|
628
|
+
"<collectionName>",
|
|
629
|
+
[
|
|
630
|
+
{ $match: { country: { $nin: [null, ""] } } },
|
|
631
|
+
{ $group: { _id: "$country", count: { $sum: 1 } } },
|
|
632
|
+
{ $sort: { count: -1 } },
|
|
633
|
+
{ $limit: 20 },
|
|
634
|
+
{ $project: { _id: 0, country: "$_id", count: 1 } },
|
|
635
|
+
],
|
|
636
|
+
);
|
|
637
|
+
|
|
638
|
+
return new Response(JSON.stringify(rows), {
|
|
639
|
+
headers: { "Content-Type": "application/json" },
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
\`\`\`
|
|
643
|
+
|
|
644
|
+
### MongoDB Reference
|
|
645
|
+
- MongoDB stores data as flexible JSON-like documents (BSON)
|
|
646
|
+
- Documents in the same collection can have different fields
|
|
647
|
+
- Query filter operators: \`$eq\`, \`$gt\`, \`$gte\`, \`$lt\`, \`$lte\`, \`$ne\`, \`$in\`, \`$nin\`, \`$and\`, \`$or\`, \`$not\`, \`$regex\`
|
|
648
|
+
- Aggregation stages: \`$match\` (filter), \`$group\` (aggregate), \`$sort\` (order), \`$project\` (reshape), \`$lookup\` (join), \`$unwind\` (flatten arrays), \`$limit\`, \`$skip\`, \`$count\`
|
|
649
|
+
- Aggregation operators: \`$sum\`, \`$avg\`, \`$min\`, \`$max\`, \`$first\`, \`$last\`, \`$push\`, \`$addToSet\`
|
|
650
|
+
- Data exploration pattern:
|
|
651
|
+
1. listCollections to see available data
|
|
652
|
+
2. find with limit to sample documents and understand schema
|
|
653
|
+
3. aggregate with $group to analyze data distribution
|
|
654
|
+
- Always include limit in find queries and $limit in aggregation pipelines`,
|
|
655
|
+
ja: `### \u30C4\u30FC\u30EB
|
|
656
|
+
|
|
657
|
+
- \`mongodb_listCollections\`: \u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u5185\u306E\u30B3\u30EC\u30AF\u30B7\u30E7\u30F3\u4E00\u89A7\u3092\u53D6\u5F97\u3057\u307E\u3059\u3002\u30C7\u30FC\u30BF\u63A2\u7D22\u306E\u6700\u521D\u306E\u30B9\u30C6\u30C3\u30D7\u3068\u3057\u3066\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002
|
|
658
|
+
- \`mongodb_find\`: \u30B3\u30EC\u30AF\u30B7\u30E7\u30F3\u5185\u306E\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8\u3092\u691C\u7D22\u3057\u307E\u3059\u3002\u30D5\u30A3\u30EB\u30BF\u30FC\u3001\u30D7\u30ED\u30B8\u30A7\u30AF\u30B7\u30E7\u30F3\u3001\u30BD\u30FC\u30C8\u3001\u5236\u9650\u3092\u30AA\u30D7\u30B7\u30E7\u30F3\u3067\u6307\u5B9A\u53EF\u80FD\u3002\u30C7\u30FC\u30BF\u306E\u30B5\u30F3\u30D7\u30EA\u30F3\u30B0\u3084\u7279\u5B9A\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8\u306E\u30AF\u30A8\u30EA\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002filter\u3001projection\u3001sort\u306FJSON\u6587\u5B57\u5217\u3067\u6E21\u3057\u3066\u304F\u3060\u3055\u3044\u3002
|
|
659
|
+
- \`mongodb_aggregate\`: \u30B3\u30EC\u30AF\u30B7\u30E7\u30F3\u4E0A\u3067MongoDB\u96C6\u7D04\u30D1\u30A4\u30D7\u30E9\u30A4\u30F3\u3092\u5B9F\u884C\u3057\u307E\u3059\u3002\u8907\u96D1\u306A\u30C7\u30FC\u30BF\u5206\u6790\u3001\u30B0\u30EB\u30FC\u30D7\u5316\u3001\u7D50\u5408\u3001\u5909\u63DB\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002pipeline\u306FJSON\u6587\u5B57\u5217\u3067\u6E21\u3057\u3066\u304F\u3060\u3055\u3044\u3002
|
|
660
|
+
|
|
661
|
+
### Business Logic
|
|
662
|
+
|
|
663
|
+
\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\u30BFSDK\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\u305F\u308A\u3001\u30CF\u30F3\u30C9\u30E9\u30B3\u30FC\u30C9\u3067 \`import { MongoClient } from "mongodb"\` \u3092\u884C\u3063\u305F\u308A\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044 \u2014 \`mongodb\` \u30C9\u30E9\u30A4\u30D0\u306F\u30E6\u30FC\u30B6\u30FC\u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9\u306E\u30E9\u30F3\u30BF\u30A4\u30E0\u306B\u306F\u542B\u307E\u308C\u305A\u3001\u30B3\u30CD\u30AF\u30BFSDK\u5185\u90E8\u306B\u306E\u307F\u5B58\u5728\u3057\u307E\u3059\u3002
|
|
664
|
+
|
|
665
|
+
\u26A0\uFE0F **\`connection(connectionId)\` \u304C\u8FD4\u3059\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8\u306F \`mongodb\` \u306E \`MongoClient\` \u305D\u306E\u3082\u306E\u3067\u306F\u3042\u308A\u307E\u305B\u3093\u3002** \u547C\u3073\u51FA\u3057\u3054\u3068\u306B\u65B0\u3057\u3044\u63A5\u7D9A\u3092\u958B\u304D\u3001\u30B3\u30CD\u30AF\u30B7\u30E7\u30F3\u306B\u8A2D\u5B9A\u3055\u308C\u305F\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306B\u5BFE\u3057\u3066\u64CD\u4F5C\u3092\u5B9F\u884C\u3057\u3001BSON \u306E \`ObjectId\` \u3092 JSON \u5316\u306B\u5B89\u5168\u306A hex \u6587\u5B57\u5217\u306B\u5909\u63DB\u3059\u308B\u8584\u3044\u30E9\u30C3\u30D1\u30FC\u3067\u3059\u3002\u516C\u958B\u30E1\u30BD\u30C3\u30C9\u306F\u6B21\u306E\u3068\u304A\u308A\u3067\u3059\uFF1A
|
|
666
|
+
|
|
667
|
+
- \`client.aggregate<T>(collection, pipeline, options?) => Promise<T[]>\` \u2014 \u96C6\u7D04\u30D1\u30A4\u30D7\u30E9\u30A4\u30F3\u3092\u5B9F\u884C\u3057\u3066\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8\u914D\u5217\u3092\u8FD4\u3059\u3002\`$limit\` \u30B9\u30C6\u30FC\u30B8\u3092\u5FC5\u305A\u542B\u3081\u308B\u3053\u3068\u3002
|
|
668
|
+
- \`client.find<T>(collection, filter?, options?) => Promise<T[]>\` \u2014 \`options\` \u306F \`{ projection?, sort?, limit?, skip? }\`\u3002\`limit\` \u3092\u5FC5\u305A\u6307\u5B9A\u3059\u308B\u3053\u3068\u3002
|
|
669
|
+
- \`client.findOne<T>(collection, filter?, options?) => Promise<T | null>\` \u2014 \`limit: 1\` \u3067\u306E \`find\` \u306E\u30E9\u30C3\u30D1\u30FC\u3002
|
|
670
|
+
- \`client.countDocuments(collection, filter?) => Promise<number>\` \u2014 \u6B63\u78BA\u306A\u30AB\u30A6\u30F3\u30C8\uFF08\`estimatedDocumentCount\` \u3067\u306F\u306A\u3044\uFF09\u3002
|
|
671
|
+
- \`client.listCollections() => Promise<{ name: string; type: string }[]>\` \u2014 \u8A2D\u5B9A\u3055\u308C\u305F\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u5185\u306E\u30B3\u30EC\u30AF\u30B7\u30E7\u30F3\u4E00\u89A7\u3002
|
|
672
|
+
|
|
673
|
+
\`client.db(...)\`\u3001\`client.connect()\`\u3001\u751F\u306E\u30AB\u30FC\u30BD\u30EB\u3084 \`MongoClient\` API \u306F **\u5B58\u5728\u3057\u306A\u3044** \u2014 \`X is not a function\` \u3067\u5931\u6557\u3059\u308B\u3002
|
|
674
|
+
|
|
675
|
+
\`\`\`ts
|
|
676
|
+
import type { Context } from "hono";
|
|
677
|
+
import { connection } from "@squadbase/vite-server/connectors/mongodb";
|
|
678
|
+
|
|
679
|
+
const mongo = connection("<connectionId>");
|
|
680
|
+
|
|
681
|
+
export default async function handler(_c: Context) {
|
|
682
|
+
const rows = await mongo.aggregate<{ country: string; count: number }>(
|
|
683
|
+
"<collectionName>",
|
|
684
|
+
[
|
|
685
|
+
{ $match: { country: { $nin: [null, ""] } } },
|
|
686
|
+
{ $group: { _id: "$country", count: { $sum: 1 } } },
|
|
687
|
+
{ $sort: { count: -1 } },
|
|
688
|
+
{ $limit: 20 },
|
|
689
|
+
{ $project: { _id: 0, country: "$_id", count: 1 } },
|
|
690
|
+
],
|
|
691
|
+
);
|
|
692
|
+
|
|
693
|
+
return new Response(JSON.stringify(rows), {
|
|
694
|
+
headers: { "Content-Type": "application/json" },
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
\`\`\`
|
|
698
|
+
|
|
699
|
+
### MongoDB \u30EA\u30D5\u30A1\u30EC\u30F3\u30B9
|
|
700
|
+
- MongoDB\u306F\u30D5\u30EC\u30AD\u30B7\u30D6\u30EB\u306AJSON\u5F62\u5F0F\u306E\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8\uFF08BSON\uFF09\u3068\u3057\u3066\u30C7\u30FC\u30BF\u3092\u683C\u7D0D\u3057\u307E\u3059
|
|
701
|
+
- \u540C\u3058\u30B3\u30EC\u30AF\u30B7\u30E7\u30F3\u5185\u306E\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8\u306F\u7570\u306A\u308B\u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u6301\u3064\u3053\u3068\u304C\u3067\u304D\u307E\u3059
|
|
702
|
+
- \u30AF\u30A8\u30EA\u30D5\u30A3\u30EB\u30BF\u30FC\u6F14\u7B97\u5B50: \`$eq\`, \`$gt\`, \`$gte\`, \`$lt\`, \`$lte\`, \`$ne\`, \`$in\`, \`$nin\`, \`$and\`, \`$or\`, \`$not\`, \`$regex\`
|
|
703
|
+
- \u96C6\u7D04\u30B9\u30C6\u30FC\u30B8: \`$match\`\uFF08\u30D5\u30A3\u30EB\u30BF\u30FC\uFF09, \`$group\`\uFF08\u96C6\u7D04\uFF09, \`$sort\`\uFF08\u30BD\u30FC\u30C8\uFF09, \`$project\`\uFF08\u5909\u63DB\uFF09, \`$lookup\`\uFF08\u7D50\u5408\uFF09, \`$unwind\`\uFF08\u914D\u5217\u5C55\u958B\uFF09, \`$limit\`, \`$skip\`, \`$count\`
|
|
704
|
+
- \u96C6\u7D04\u6F14\u7B97\u5B50: \`$sum\`, \`$avg\`, \`$min\`, \`$max\`, \`$first\`, \`$last\`, \`$push\`, \`$addToSet\`
|
|
705
|
+
- \u30C7\u30FC\u30BF\u63A2\u7D22\u30D1\u30BF\u30FC\u30F3:
|
|
706
|
+
1. listCollections \u3067\u5229\u7528\u53EF\u80FD\u306A\u30C7\u30FC\u30BF\u3092\u78BA\u8A8D
|
|
707
|
+
2. find \u3068 limit \u3067\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8\u3092\u30B5\u30F3\u30D7\u30EA\u30F3\u30B0\u3057\u3066\u30B9\u30AD\u30FC\u30DE\u3092\u628A\u63E1
|
|
708
|
+
3. aggregate \u306E $group \u3067\u30C7\u30FC\u30BF\u5206\u5E03\u3092\u5206\u6790
|
|
709
|
+
- find \u30AF\u30A8\u30EA\u306B\u306F\u5FC5\u305A limit \u3092\u3001\u96C6\u7D04\u30D1\u30A4\u30D7\u30E9\u30A4\u30F3\u306B\u306F $limit \u3092\u542B\u3081\u3066\u304F\u3060\u3055\u3044`
|
|
710
|
+
},
|
|
711
|
+
tools,
|
|
712
|
+
async checkConnection(params, _config) {
|
|
713
|
+
try {
|
|
714
|
+
const { MongoClient } = await import("mongodb");
|
|
715
|
+
const client = new MongoClient(params[parameters.connectionUrl.slug], {
|
|
716
|
+
connectTimeoutMS: 1e4,
|
|
717
|
+
serverSelectionTimeoutMS: 1e4
|
|
718
|
+
});
|
|
719
|
+
try {
|
|
720
|
+
await client.connect();
|
|
721
|
+
const db = client.db(params[parameters.database.slug]);
|
|
722
|
+
await db.command({ ping: 1 });
|
|
723
|
+
return { success: true };
|
|
724
|
+
} finally {
|
|
725
|
+
await client.close();
|
|
726
|
+
}
|
|
727
|
+
} catch (error) {
|
|
728
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
// src/connectors/create-connector-sdk.ts
|
|
734
|
+
import { readFileSync } from "fs";
|
|
735
|
+
import path from "path";
|
|
736
|
+
|
|
737
|
+
// src/connector-client/env.ts
|
|
738
|
+
function resolveEnvVar(entry, key, connectionId) {
|
|
739
|
+
const envVarName = entry.envVars[key];
|
|
740
|
+
if (!envVarName) {
|
|
741
|
+
throw new Error(`Connection "${connectionId}" is missing envVars mapping for key "${key}"`);
|
|
742
|
+
}
|
|
743
|
+
const value = process.env[envVarName];
|
|
744
|
+
if (!value) {
|
|
745
|
+
throw new Error(`Environment variable "${envVarName}" (for connection "${connectionId}", key "${key}") is not set`);
|
|
746
|
+
}
|
|
747
|
+
return value;
|
|
748
|
+
}
|
|
749
|
+
function resolveEnvVarOptional(entry, key) {
|
|
750
|
+
const envVarName = entry.envVars[key];
|
|
751
|
+
if (!envVarName) return void 0;
|
|
752
|
+
return process.env[envVarName] || void 0;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// src/connector-client/proxy-fetch.ts
|
|
756
|
+
import { getContext } from "hono/context-storage";
|
|
757
|
+
import { getCookie } from "hono/cookie";
|
|
758
|
+
var APP_SESSION_COOKIE_NAME = "__Host-squadbase-session";
|
|
759
|
+
function normalizeHeaders(input) {
|
|
760
|
+
const out = {};
|
|
761
|
+
if (!input) return out;
|
|
762
|
+
new Headers(input).forEach((value, key) => {
|
|
763
|
+
out[key] = value;
|
|
764
|
+
});
|
|
765
|
+
return out;
|
|
766
|
+
}
|
|
767
|
+
function createSandboxProxyFetch(connectionId) {
|
|
768
|
+
return async (input, init) => {
|
|
769
|
+
const token = process.env.INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL;
|
|
770
|
+
const sandboxId = process.env.INTERNAL_SQUADBASE_SANDBOX_ID;
|
|
771
|
+
if (!token || !sandboxId) {
|
|
772
|
+
throw new Error(
|
|
773
|
+
"Connection proxy is not configured. Please check your deployment settings."
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
const originalUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
777
|
+
const originalMethod = init?.method ?? "GET";
|
|
778
|
+
const originalBody = init?.body ? JSON.parse(init.body) : void 0;
|
|
779
|
+
const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
|
|
780
|
+
const proxyUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
|
|
781
|
+
return fetch(proxyUrl, {
|
|
782
|
+
method: "POST",
|
|
783
|
+
headers: {
|
|
784
|
+
"Content-Type": "application/json",
|
|
785
|
+
Authorization: `Bearer ${token}`
|
|
786
|
+
},
|
|
787
|
+
body: JSON.stringify({
|
|
788
|
+
url: originalUrl,
|
|
789
|
+
method: originalMethod,
|
|
790
|
+
headers: normalizeHeaders(init?.headers),
|
|
791
|
+
body: originalBody
|
|
792
|
+
})
|
|
793
|
+
});
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
function createDeployedAppProxyFetch(connectionId) {
|
|
797
|
+
const projectId = process.env["SQUADBASE_PROJECT_ID"];
|
|
798
|
+
if (!projectId) {
|
|
799
|
+
throw new Error(
|
|
800
|
+
"Connection proxy is not configured. Please check your deployment settings."
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
const baseDomain = process.env["SQUADBASE_APP_BASE_DOMAIN"] ?? "squadbase.app";
|
|
804
|
+
const proxyUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
|
|
805
|
+
return async (input, init) => {
|
|
806
|
+
const originalUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
807
|
+
const originalMethod = init?.method ?? "GET";
|
|
808
|
+
const originalBody = init?.body ? JSON.parse(init.body) : void 0;
|
|
809
|
+
const c = getContext();
|
|
810
|
+
const appSession = getCookie(c, APP_SESSION_COOKIE_NAME);
|
|
811
|
+
if (!appSession) {
|
|
812
|
+
throw new Error(
|
|
813
|
+
"No authentication method available for connection proxy."
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
return fetch(proxyUrl, {
|
|
817
|
+
method: "POST",
|
|
818
|
+
headers: {
|
|
819
|
+
"Content-Type": "application/json",
|
|
820
|
+
Authorization: `Bearer ${appSession}`
|
|
821
|
+
},
|
|
822
|
+
body: JSON.stringify({
|
|
823
|
+
url: originalUrl,
|
|
824
|
+
method: originalMethod,
|
|
825
|
+
headers: normalizeHeaders(init?.headers),
|
|
826
|
+
body: originalBody
|
|
827
|
+
})
|
|
828
|
+
});
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
function createProxyFetch(connectionId) {
|
|
832
|
+
if (process.env.INTERNAL_SQUADBASE_SANDBOX_ID) {
|
|
833
|
+
return createSandboxProxyFetch(connectionId);
|
|
834
|
+
}
|
|
835
|
+
return createDeployedAppProxyFetch(connectionId);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// src/connectors/create-connector-sdk.ts
|
|
839
|
+
function loadConnectionsSync() {
|
|
840
|
+
const filePath = process.env.CONNECTIONS_PATH ?? path.join(process.cwd(), ".squadbase/connections.json");
|
|
841
|
+
try {
|
|
842
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
843
|
+
return JSON.parse(raw);
|
|
844
|
+
} catch {
|
|
845
|
+
return {};
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
function createConnectorSdk(plugin, createClient2) {
|
|
849
|
+
return (connectionId) => {
|
|
850
|
+
const connections = loadConnectionsSync();
|
|
851
|
+
const entry = connections[connectionId];
|
|
852
|
+
if (!entry) {
|
|
853
|
+
throw new Error(
|
|
854
|
+
`Connection "${connectionId}" not found in .squadbase/connections.json`
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
if (entry.connector.slug !== plugin.slug) {
|
|
858
|
+
throw new Error(
|
|
859
|
+
`Connection "${connectionId}" is not a ${plugin.slug} connection (got "${entry.connector.slug}")`
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
const params = {};
|
|
863
|
+
for (const param of Object.values(plugin.parameters)) {
|
|
864
|
+
if (param.required) {
|
|
865
|
+
params[param.slug] = resolveEnvVar(entry, param.slug, connectionId);
|
|
866
|
+
} else {
|
|
867
|
+
const val = resolveEnvVarOptional(entry, param.slug);
|
|
868
|
+
if (val !== void 0) params[param.slug] = val;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
return createClient2(params, createProxyFetch(connectionId));
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// src/connectors/entries/mongodb.ts
|
|
876
|
+
var connection = createConnectorSdk(mongodbConnector, createClient);
|
|
877
|
+
export {
|
|
878
|
+
connection
|
|
879
|
+
};
|