emulate 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,961 @@
1
+ // ../@emulators/mongoatlas/dist/index.js
2
+ import { randomBytes } from "crypto";
3
+ function getMongoAtlasStore(store) {
4
+ return {
5
+ clusters: store.collection("mongoatlas.clusters", ["cluster_id", "name"]),
6
+ databases: store.collection("mongoatlas.databases", ["cluster_id", "name"]),
7
+ collections: store.collection("mongoatlas.collections", ["cluster_id", "database", "name"]),
8
+ documents: store.collection("mongoatlas.documents", ["cluster_id", "doc_id"]),
9
+ projects: store.collection("mongoatlas.projects", ["group_id"]),
10
+ users: store.collection("mongoatlas.users", ["user_id", "username"])
11
+ };
12
+ }
13
+ function generateObjectId() {
14
+ const timestamp = Math.floor(Date.now() / 1e3).toString(16).padStart(8, "0");
15
+ const random = randomBytes(8).toString("hex").slice(0, 16);
16
+ return (timestamp + random).slice(0, 24);
17
+ }
18
+ function generateClusterId() {
19
+ return randomBytes(12).toString("hex");
20
+ }
21
+ function generateGroupId() {
22
+ return randomBytes(12).toString("hex");
23
+ }
24
+ function generateUserId() {
25
+ return randomBytes(12).toString("hex");
26
+ }
27
+ function mongoOk(c, data, status = 200) {
28
+ return c.json(data, status);
29
+ }
30
+ function mongoError(c, errorCode, detail, status = 400) {
31
+ return c.json({ error: status, errorCode, detail }, status);
32
+ }
33
+ function dataApiRoutes(ctx) {
34
+ const { app, store } = ctx;
35
+ const ms = () => getMongoAtlasStore(store);
36
+ app.post("/app/data-api/v1/action/findOne", async (c) => {
37
+ const body = await c.req.json();
38
+ if (!body.dataSource || !body.database || !body.collection) {
39
+ return mongoError(c, "InvalidParameter", "dataSource, database, and collection are required");
40
+ }
41
+ const cluster = ms().clusters.findOneBy("name", body.dataSource);
42
+ if (!cluster) {
43
+ return mongoError(c, "ClusterNotFound", `Cluster '${body.dataSource}' not found`, 404);
44
+ }
45
+ const docs = ms().documents.all().filter(
46
+ (d) => d.cluster_id === cluster.cluster_id && d.database === body.database && d.collection === body.collection
47
+ );
48
+ const matched = matchFilter(docs, body.filter) ?? docs;
49
+ const doc = matched[0] ?? null;
50
+ const projected = doc ? applyProjection(doc.data, body.projection) : null;
51
+ return mongoOk(c, { document: projected });
52
+ });
53
+ app.post("/app/data-api/v1/action/find", async (c) => {
54
+ const body = await c.req.json();
55
+ if (!body.dataSource || !body.database || !body.collection) {
56
+ return mongoError(c, "InvalidParameter", "dataSource, database, and collection are required");
57
+ }
58
+ const cluster = ms().clusters.findOneBy("name", body.dataSource);
59
+ if (!cluster) {
60
+ return mongoError(c, "ClusterNotFound", `Cluster '${body.dataSource}' not found`, 404);
61
+ }
62
+ let docs = ms().documents.all().filter(
63
+ (d) => d.cluster_id === cluster.cluster_id && d.database === body.database && d.collection === body.collection
64
+ );
65
+ docs = matchFilter(docs, body.filter) ?? docs;
66
+ if (body.sort) {
67
+ docs = sortBySpec(docs, body.sort, (d) => d.data);
68
+ }
69
+ if (body.skip) {
70
+ docs = docs.slice(body.skip);
71
+ }
72
+ if (body.limit) {
73
+ docs = docs.slice(0, body.limit);
74
+ }
75
+ const documents = docs.map((d) => applyProjection(d.data, body.projection));
76
+ return mongoOk(c, { documents });
77
+ });
78
+ app.post("/app/data-api/v1/action/insertOne", async (c) => {
79
+ const body = await c.req.json();
80
+ if (!body.dataSource || !body.database || !body.collection || !body.document) {
81
+ return mongoError(c, "InvalidParameter", "dataSource, database, collection, and document are required");
82
+ }
83
+ const cluster = ms().clusters.findOneBy("name", body.dataSource);
84
+ if (!cluster) {
85
+ return mongoError(c, "ClusterNotFound", `Cluster '${body.dataSource}' not found`, 404);
86
+ }
87
+ ensureCollectionExists(ms, cluster.cluster_id, body.database, body.collection);
88
+ const docId = body.document._id ?? generateObjectId();
89
+ const data = { ...body.document, _id: docId };
90
+ ms().documents.insert({
91
+ cluster_id: cluster.cluster_id,
92
+ database: body.database,
93
+ collection: body.collection,
94
+ doc_id: docId,
95
+ data
96
+ });
97
+ return mongoOk(c, { insertedId: docId }, 201);
98
+ });
99
+ app.post("/app/data-api/v1/action/insertMany", async (c) => {
100
+ const body = await c.req.json();
101
+ if (!body.dataSource || !body.database || !body.collection || !body.documents) {
102
+ return mongoError(c, "InvalidParameter", "dataSource, database, collection, and documents are required");
103
+ }
104
+ const cluster = ms().clusters.findOneBy("name", body.dataSource);
105
+ if (!cluster) {
106
+ return mongoError(c, "ClusterNotFound", `Cluster '${body.dataSource}' not found`, 404);
107
+ }
108
+ ensureCollectionExists(ms, cluster.cluster_id, body.database, body.collection);
109
+ const insertedIds = [];
110
+ for (const doc of body.documents) {
111
+ const docId = doc._id ?? generateObjectId();
112
+ const data = { ...doc, _id: docId };
113
+ ms().documents.insert({
114
+ cluster_id: cluster.cluster_id,
115
+ database: body.database,
116
+ collection: body.collection,
117
+ doc_id: docId,
118
+ data
119
+ });
120
+ insertedIds.push(docId);
121
+ }
122
+ return mongoOk(c, { insertedIds }, 201);
123
+ });
124
+ app.post("/app/data-api/v1/action/updateOne", async (c) => {
125
+ const body = await c.req.json();
126
+ if (!body.dataSource || !body.database || !body.collection || !body.update) {
127
+ return mongoError(c, "InvalidParameter", "dataSource, database, collection, and update are required");
128
+ }
129
+ const cluster = ms().clusters.findOneBy("name", body.dataSource);
130
+ if (!cluster) {
131
+ return mongoError(c, "ClusterNotFound", `Cluster '${body.dataSource}' not found`, 404);
132
+ }
133
+ const docs = ms().documents.all().filter(
134
+ (d) => d.cluster_id === cluster.cluster_id && d.database === body.database && d.collection === body.collection
135
+ );
136
+ const matched = matchFilter(docs, body.filter) ?? docs;
137
+ const doc = matched[0];
138
+ if (doc) {
139
+ const updatedData = applyUpdate(doc.data, body.update);
140
+ ms().documents.update(doc.id, { data: updatedData });
141
+ return mongoOk(c, { matchedCount: 1, modifiedCount: 1 });
142
+ }
143
+ if (body.upsert) {
144
+ ensureCollectionExists(ms, cluster.cluster_id, body.database, body.collection);
145
+ const docId = generateObjectId();
146
+ const baseDoc = extractEqualityFields(body.filter ?? {});
147
+ const data = applyUpdate({ _id: docId, ...baseDoc }, body.update);
148
+ ms().documents.insert({
149
+ cluster_id: cluster.cluster_id,
150
+ database: body.database,
151
+ collection: body.collection,
152
+ doc_id: docId,
153
+ data
154
+ });
155
+ return mongoOk(c, { matchedCount: 0, modifiedCount: 0, upsertedId: docId });
156
+ }
157
+ return mongoOk(c, { matchedCount: 0, modifiedCount: 0 });
158
+ });
159
+ app.post("/app/data-api/v1/action/updateMany", async (c) => {
160
+ const body = await c.req.json();
161
+ if (!body.dataSource || !body.database || !body.collection || !body.update) {
162
+ return mongoError(c, "InvalidParameter", "dataSource, database, collection, and update are required");
163
+ }
164
+ const cluster = ms().clusters.findOneBy("name", body.dataSource);
165
+ if (!cluster) {
166
+ return mongoError(c, "ClusterNotFound", `Cluster '${body.dataSource}' not found`, 404);
167
+ }
168
+ const docs = ms().documents.all().filter(
169
+ (d) => d.cluster_id === cluster.cluster_id && d.database === body.database && d.collection === body.collection
170
+ );
171
+ const matched = matchFilter(docs, body.filter) ?? docs;
172
+ let modifiedCount = 0;
173
+ for (const doc of matched) {
174
+ const updatedData = applyUpdate(doc.data, body.update);
175
+ ms().documents.update(doc.id, { data: updatedData });
176
+ modifiedCount++;
177
+ }
178
+ if (matched.length === 0 && body.upsert) {
179
+ ensureCollectionExists(ms, cluster.cluster_id, body.database, body.collection);
180
+ const docId = generateObjectId();
181
+ const baseDoc = extractEqualityFields(body.filter ?? {});
182
+ const data = applyUpdate({ _id: docId, ...baseDoc }, body.update);
183
+ ms().documents.insert({
184
+ cluster_id: cluster.cluster_id,
185
+ database: body.database,
186
+ collection: body.collection,
187
+ doc_id: docId,
188
+ data
189
+ });
190
+ return mongoOk(c, { matchedCount: 0, modifiedCount: 0, upsertedId: docId });
191
+ }
192
+ return mongoOk(c, { matchedCount: matched.length, modifiedCount });
193
+ });
194
+ app.post("/app/data-api/v1/action/deleteOne", async (c) => {
195
+ const body = await c.req.json();
196
+ if (!body.dataSource || !body.database || !body.collection) {
197
+ return mongoError(c, "InvalidParameter", "dataSource, database, and collection are required");
198
+ }
199
+ const cluster = ms().clusters.findOneBy("name", body.dataSource);
200
+ if (!cluster) {
201
+ return mongoError(c, "ClusterNotFound", `Cluster '${body.dataSource}' not found`, 404);
202
+ }
203
+ const docs = ms().documents.all().filter(
204
+ (d) => d.cluster_id === cluster.cluster_id && d.database === body.database && d.collection === body.collection
205
+ );
206
+ const matched = matchFilter(docs, body.filter) ?? docs;
207
+ const doc = matched[0];
208
+ if (doc) {
209
+ ms().documents.delete(doc.id);
210
+ return mongoOk(c, { deletedCount: 1 });
211
+ }
212
+ return mongoOk(c, { deletedCount: 0 });
213
+ });
214
+ app.post("/app/data-api/v1/action/deleteMany", async (c) => {
215
+ const body = await c.req.json();
216
+ if (!body.dataSource || !body.database || !body.collection) {
217
+ return mongoError(c, "InvalidParameter", "dataSource, database, and collection are required");
218
+ }
219
+ const cluster = ms().clusters.findOneBy("name", body.dataSource);
220
+ if (!cluster) {
221
+ return mongoError(c, "ClusterNotFound", `Cluster '${body.dataSource}' not found`, 404);
222
+ }
223
+ const docs = ms().documents.all().filter(
224
+ (d) => d.cluster_id === cluster.cluster_id && d.database === body.database && d.collection === body.collection
225
+ );
226
+ const matched = matchFilter(docs, body.filter) ?? docs;
227
+ let deletedCount = 0;
228
+ for (const doc of matched) {
229
+ ms().documents.delete(doc.id);
230
+ deletedCount++;
231
+ }
232
+ return mongoOk(c, { deletedCount });
233
+ });
234
+ app.post("/app/data-api/v1/action/aggregate", async (c) => {
235
+ const body = await c.req.json();
236
+ if (!body.dataSource || !body.database || !body.collection) {
237
+ return mongoError(c, "InvalidParameter", "dataSource, database, and collection are required");
238
+ }
239
+ const cluster = ms().clusters.findOneBy("name", body.dataSource);
240
+ if (!cluster) {
241
+ return mongoError(c, "ClusterNotFound", `Cluster '${body.dataSource}' not found`, 404);
242
+ }
243
+ let docs = ms().documents.all().filter(
244
+ (d) => d.cluster_id === cluster.cluster_id && d.database === body.database && d.collection === body.collection
245
+ );
246
+ const pipeline = body.pipeline ?? [];
247
+ let results = docs.map((d) => d.data);
248
+ for (const stage of pipeline) {
249
+ if ("$match" in stage) {
250
+ const filter = stage.$match;
251
+ results = results.filter((d) => matchesFilter(d, filter));
252
+ } else if ("$limit" in stage) {
253
+ results = results.slice(0, stage.$limit);
254
+ } else if ("$skip" in stage) {
255
+ results = results.slice(stage.$skip);
256
+ } else if ("$sort" in stage) {
257
+ const sortSpec = stage.$sort;
258
+ results = sortBySpec(results, sortSpec, (d) => d);
259
+ } else if ("$project" in stage) {
260
+ const projection = stage.$project;
261
+ results = results.map((d) => applyProjection(d, projection));
262
+ } else if ("$count" in stage) {
263
+ const fieldName = stage.$count;
264
+ results = [{ [fieldName]: results.length }];
265
+ }
266
+ }
267
+ return mongoOk(c, { documents: results });
268
+ });
269
+ }
270
+ function ensureCollectionExists(ms, clusterId, database, collection) {
271
+ const existing = ms().collections.all().find(
272
+ (col) => col.cluster_id === clusterId && col.database === database && col.name === collection
273
+ );
274
+ if (!existing) {
275
+ const dbExists = ms().databases.all().find(
276
+ (db) => db.cluster_id === clusterId && db.name === database
277
+ );
278
+ if (!dbExists) {
279
+ ms().databases.insert({ cluster_id: clusterId, name: database });
280
+ }
281
+ ms().collections.insert({ cluster_id: clusterId, database, name: collection });
282
+ }
283
+ }
284
+ function matchesFilter(data, filter) {
285
+ for (const [key, value] of Object.entries(filter)) {
286
+ if (key === "$and") {
287
+ const conditions = value;
288
+ if (!conditions.every((cond) => matchesFilter(data, cond))) return false;
289
+ continue;
290
+ }
291
+ if (key === "$or") {
292
+ const conditions = value;
293
+ if (!conditions.some((cond) => matchesFilter(data, cond))) return false;
294
+ continue;
295
+ }
296
+ if (key === "$nor") {
297
+ const conditions = value;
298
+ if (conditions.some((cond) => matchesFilter(data, cond))) return false;
299
+ continue;
300
+ }
301
+ const docValue = getNestedValue(data, key);
302
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
303
+ const ops = value;
304
+ for (const [op, opVal] of Object.entries(ops)) {
305
+ switch (op) {
306
+ case "$eq":
307
+ if (docValue !== opVal) return false;
308
+ break;
309
+ case "$ne":
310
+ if (docValue === opVal) return false;
311
+ break;
312
+ case "$gt":
313
+ if (typeof docValue !== "number" || typeof opVal !== "number" || docValue <= opVal) return false;
314
+ break;
315
+ case "$gte":
316
+ if (typeof docValue !== "number" || typeof opVal !== "number" || docValue < opVal) return false;
317
+ break;
318
+ case "$lt":
319
+ if (typeof docValue !== "number" || typeof opVal !== "number" || docValue >= opVal) return false;
320
+ break;
321
+ case "$lte":
322
+ if (typeof docValue !== "number" || typeof opVal !== "number" || docValue > opVal) return false;
323
+ break;
324
+ case "$in":
325
+ if (!Array.isArray(opVal) || !opVal.includes(docValue)) return false;
326
+ break;
327
+ case "$nin":
328
+ if (!Array.isArray(opVal) || opVal.includes(docValue)) return false;
329
+ break;
330
+ case "$exists":
331
+ if (opVal && docValue === void 0) return false;
332
+ if (!opVal && docValue !== void 0) return false;
333
+ break;
334
+ case "$regex": {
335
+ const pattern = opVal;
336
+ const flags = ops.$options ?? "";
337
+ try {
338
+ if (pattern.length > 1e3) return false;
339
+ const re = new RegExp(pattern, flags);
340
+ if (typeof docValue !== "string" || !re.test(docValue)) return false;
341
+ } catch {
342
+ return false;
343
+ }
344
+ break;
345
+ }
346
+ }
347
+ }
348
+ } else {
349
+ if (docValue !== value) return false;
350
+ }
351
+ }
352
+ return true;
353
+ }
354
+ function getNestedValue(obj, path) {
355
+ const parts = path.split(".");
356
+ if (hasDangerousKey(parts)) return void 0;
357
+ let current = obj;
358
+ for (const part of parts) {
359
+ if (current === null || current === void 0 || typeof current !== "object") return void 0;
360
+ current = current[part];
361
+ }
362
+ return current;
363
+ }
364
+ function matchFilter(docs, filter) {
365
+ if (!filter || Object.keys(filter).length === 0) return null;
366
+ return docs.filter((d) => matchesFilter(d.data, filter));
367
+ }
368
+ function applyProjection(data, projection) {
369
+ if (!projection || Object.keys(projection).length === 0) return data;
370
+ const hasInclusions = Object.values(projection).some((v) => v === 1 || v === true);
371
+ if (hasInclusions) {
372
+ const result2 = {};
373
+ if (projection._id !== 0 && projection._id !== false) {
374
+ result2._id = data._id;
375
+ }
376
+ for (const [key, val] of Object.entries(projection)) {
377
+ if (key === "_id") continue;
378
+ if (val === 1 || val === true) {
379
+ result2[key] = data[key];
380
+ }
381
+ }
382
+ return result2;
383
+ }
384
+ const result = { ...data };
385
+ for (const [key, val] of Object.entries(projection)) {
386
+ if (val === 0 || val === false) {
387
+ delete result[key];
388
+ }
389
+ }
390
+ return result;
391
+ }
392
+ function applyUpdate(data, update) {
393
+ const result = { ...data };
394
+ if ("$set" in update) {
395
+ const setFields = update.$set;
396
+ for (const [key, value] of Object.entries(setFields)) {
397
+ setNestedValue(result, key, value);
398
+ }
399
+ }
400
+ if ("$unset" in update) {
401
+ const unsetFields = update.$unset;
402
+ for (const key of Object.keys(unsetFields)) {
403
+ const parts = key.split(".");
404
+ if (hasDangerousKey(parts)) continue;
405
+ if (parts.length === 1) {
406
+ delete result[key];
407
+ } else {
408
+ let current = result;
409
+ for (let i = 0; i < parts.length - 1; i++) {
410
+ if (current[parts[i]] === null || current[parts[i]] === void 0 || typeof current[parts[i]] !== "object") break;
411
+ current = current[parts[i]];
412
+ }
413
+ delete current[parts[parts.length - 1]];
414
+ }
415
+ }
416
+ }
417
+ if ("$inc" in update) {
418
+ const incFields = update.$inc;
419
+ for (const [key, value] of Object.entries(incFields)) {
420
+ const current = getNestedValue(result, key) ?? 0;
421
+ setNestedValue(result, key, current + value);
422
+ }
423
+ }
424
+ if ("$push" in update) {
425
+ const pushFields = update.$push;
426
+ for (const [key, value] of Object.entries(pushFields)) {
427
+ const current = getNestedValue(result, key);
428
+ if (!Array.isArray(current)) {
429
+ setNestedValue(result, key, [value]);
430
+ } else {
431
+ setNestedValue(result, key, [...current, value]);
432
+ }
433
+ }
434
+ }
435
+ if ("$pull" in update) {
436
+ const pullFields = update.$pull;
437
+ for (const [key, value] of Object.entries(pullFields)) {
438
+ const current = getNestedValue(result, key);
439
+ if (Array.isArray(current)) {
440
+ setNestedValue(result, key, current.filter((item) => item !== value));
441
+ }
442
+ }
443
+ }
444
+ if ("$rename" in update) {
445
+ const renameFields = update.$rename;
446
+ for (const [oldKey, newKey] of Object.entries(renameFields)) {
447
+ const oldParts = oldKey.split(".");
448
+ const newParts = newKey.split(".");
449
+ if (hasDangerousKey(oldParts) || hasDangerousKey(newParts)) continue;
450
+ const value = getNestedValue(result, oldKey);
451
+ if (value !== void 0) {
452
+ setNestedValue(result, newKey, value);
453
+ if (oldParts.length === 1) {
454
+ delete result[oldKey];
455
+ } else {
456
+ let current = result;
457
+ for (let i = 0; i < oldParts.length - 1; i++) {
458
+ if (typeof current[oldParts[i]] !== "object" || current[oldParts[i]] === null) break;
459
+ current = current[oldParts[i]];
460
+ }
461
+ delete current[oldParts[oldParts.length - 1]];
462
+ }
463
+ }
464
+ }
465
+ }
466
+ const hasOperators = Object.keys(update).some((k) => k.startsWith("$"));
467
+ if (!hasOperators) {
468
+ const id = result._id;
469
+ for (const key of Object.keys(result)) {
470
+ delete result[key];
471
+ }
472
+ result._id = id;
473
+ Object.assign(result, update);
474
+ }
475
+ return result;
476
+ }
477
+ var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
478
+ function hasDangerousKey(parts) {
479
+ return parts.some((p) => DANGEROUS_KEYS.has(p));
480
+ }
481
+ function setNestedValue(obj, path, value) {
482
+ const parts = path.split(".");
483
+ if (hasDangerousKey(parts)) return;
484
+ let current = obj;
485
+ for (let i = 0; i < parts.length - 1; i++) {
486
+ if (!(parts[i] in current) || typeof current[parts[i]] !== "object") {
487
+ current[parts[i]] = {};
488
+ }
489
+ current = current[parts[i]];
490
+ }
491
+ current[parts[parts.length - 1]] = value;
492
+ }
493
+ function sortBySpec(docs, sortSpec, accessor) {
494
+ return [...docs].sort((a, b) => {
495
+ for (const [key, direction] of Object.entries(sortSpec)) {
496
+ const aVal = getNestedValue(accessor(a), key);
497
+ const bVal = getNestedValue(accessor(b), key);
498
+ if (aVal === bVal) continue;
499
+ if (aVal === void 0) return direction;
500
+ if (bVal === void 0) return -direction;
501
+ if (aVal < bVal) return -direction;
502
+ if (aVal > bVal) return direction;
503
+ }
504
+ return 0;
505
+ });
506
+ }
507
+ function extractEqualityFields(filter) {
508
+ const result = {};
509
+ for (const [key, value] of Object.entries(filter)) {
510
+ if (key.startsWith("$")) continue;
511
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
512
+ const ops = value;
513
+ if (Object.keys(ops).some((k) => k.startsWith("$"))) continue;
514
+ }
515
+ result[key] = value;
516
+ }
517
+ return result;
518
+ }
519
+ function adminRoutes(ctx) {
520
+ const { app, store } = ctx;
521
+ const ms = () => getMongoAtlasStore(store);
522
+ app.get("/api/atlas/v2/groups", (c) => {
523
+ const projects = ms().projects.all();
524
+ return mongoOk(c, {
525
+ results: projects.map(formatProject),
526
+ totalCount: projects.length
527
+ });
528
+ });
529
+ app.get("/api/atlas/v2/groups/:groupId", (c) => {
530
+ const groupId = c.req.param("groupId");
531
+ const project = ms().projects.findOneBy("group_id", groupId);
532
+ if (!project) {
533
+ return mongoError(c, "GROUP_NOT_FOUND", `Group '${groupId}' not found.`, 404);
534
+ }
535
+ return mongoOk(c, formatProject(project));
536
+ });
537
+ app.post("/api/atlas/v2/groups", async (c) => {
538
+ const body = await c.req.json();
539
+ if (!body.name?.trim()) {
540
+ return mongoError(c, "INVALID_PARAMETER", "name is required");
541
+ }
542
+ const existing = ms().projects.all().find((p) => p.name === body.name);
543
+ if (existing) {
544
+ return mongoError(c, "DUPLICATE_GROUP_NAME", `Group name '${body.name}' already exists.`, 409);
545
+ }
546
+ const groupId = generateGroupId();
547
+ const project = ms().projects.insert({
548
+ group_id: groupId,
549
+ name: body.name,
550
+ org_id: body.orgId ?? "default_org",
551
+ cluster_count: 0
552
+ });
553
+ return mongoOk(c, formatProject(project), 201);
554
+ });
555
+ app.delete("/api/atlas/v2/groups/:groupId", (c) => {
556
+ const groupId = c.req.param("groupId");
557
+ const project = ms().projects.findOneBy("group_id", groupId);
558
+ if (!project) {
559
+ return mongoError(c, "GROUP_NOT_FOUND", `Group '${groupId}' not found.`, 404);
560
+ }
561
+ const clusters = ms().clusters.all().filter((cl) => cl.group_id === groupId);
562
+ for (const cluster of clusters) {
563
+ deleteClusterData(ms, cluster.cluster_id);
564
+ ms().clusters.delete(cluster.id);
565
+ }
566
+ ms().projects.delete(project.id);
567
+ return c.body(null, 204);
568
+ });
569
+ app.get("/api/atlas/v2/groups/:groupId/clusters", (c) => {
570
+ const groupId = c.req.param("groupId");
571
+ const project = ms().projects.findOneBy("group_id", groupId);
572
+ if (!project) {
573
+ return mongoError(c, "GROUP_NOT_FOUND", `Group '${groupId}' not found.`, 404);
574
+ }
575
+ const clusters = ms().clusters.all().filter((cl) => cl.group_id === groupId);
576
+ return mongoOk(c, {
577
+ results: clusters.map(formatCluster),
578
+ totalCount: clusters.length
579
+ });
580
+ });
581
+ app.get("/api/atlas/v2/groups/:groupId/clusters/:clusterName", (c) => {
582
+ const groupId = c.req.param("groupId");
583
+ const clusterName = c.req.param("clusterName");
584
+ const cluster = ms().clusters.all().find(
585
+ (cl) => cl.group_id === groupId && cl.name === clusterName
586
+ );
587
+ if (!cluster) {
588
+ return mongoError(c, "CLUSTER_NOT_FOUND", `Cluster '${clusterName}' not found.`, 404);
589
+ }
590
+ return mongoOk(c, formatCluster(cluster));
591
+ });
592
+ app.post("/api/atlas/v2/groups/:groupId/clusters", async (c) => {
593
+ const groupId = c.req.param("groupId");
594
+ const project = ms().projects.findOneBy("group_id", groupId);
595
+ if (!project) {
596
+ return mongoError(c, "GROUP_NOT_FOUND", `Group '${groupId}' not found.`, 404);
597
+ }
598
+ const body = await c.req.json();
599
+ if (!body.name?.trim()) {
600
+ return mongoError(c, "INVALID_PARAMETER", "name is required");
601
+ }
602
+ const existing = ms().clusters.all().find(
603
+ (cl) => cl.group_id === groupId && cl.name === body.name
604
+ );
605
+ if (existing) {
606
+ return mongoError(c, "DUPLICATE_CLUSTER_NAME", `Cluster '${body.name}' already exists.`, 409);
607
+ }
608
+ const clusterId = generateClusterId();
609
+ const cluster = ms().clusters.insert({
610
+ cluster_id: clusterId,
611
+ name: body.name,
612
+ group_id: groupId,
613
+ state: "IDLE",
614
+ mongo_uri: `mongodb+srv://${body.name}.emulate.mongodb.net`,
615
+ connection_strings: {
616
+ standard: `mongodb://${body.name}.emulate.mongodb.net:27017`,
617
+ standard_srv: `mongodb+srv://${body.name}.emulate.mongodb.net`
618
+ },
619
+ provider_settings: {
620
+ provider_name: body.providerSettings?.providerName ?? "AWS",
621
+ instance_size_name: body.providerSettings?.instanceSizeName ?? "M10",
622
+ region_name: body.providerSettings?.regionName ?? "US_EAST_1"
623
+ },
624
+ cluster_type: body.clusterType ?? "REPLICASET",
625
+ disk_size_gb: body.diskSizeGB ?? 10,
626
+ mongodb_version: body.mongoDBMajorVersion ?? "8.0"
627
+ });
628
+ ms().projects.update(project.id, { cluster_count: project.cluster_count + 1 });
629
+ return mongoOk(c, formatCluster(cluster), 201);
630
+ });
631
+ app.patch("/api/atlas/v2/groups/:groupId/clusters/:clusterName", async (c) => {
632
+ const groupId = c.req.param("groupId");
633
+ const clusterName = c.req.param("clusterName");
634
+ const cluster = ms().clusters.all().find(
635
+ (cl) => cl.group_id === groupId && cl.name === clusterName
636
+ );
637
+ if (!cluster) {
638
+ return mongoError(c, "CLUSTER_NOT_FOUND", `Cluster '${clusterName}' not found.`, 404);
639
+ }
640
+ const body = await c.req.json();
641
+ const updates = {};
642
+ if (body.providerSettings) {
643
+ updates.provider_settings = {
644
+ provider_name: cluster.provider_settings.provider_name,
645
+ instance_size_name: body.providerSettings.instanceSizeName ?? cluster.provider_settings.instance_size_name,
646
+ region_name: body.providerSettings.regionName ?? cluster.provider_settings.region_name
647
+ };
648
+ }
649
+ if (body.diskSizeGB !== void 0) {
650
+ updates.disk_size_gb = body.diskSizeGB;
651
+ }
652
+ const updated = ms().clusters.update(cluster.id, updates);
653
+ return mongoOk(c, formatCluster(updated));
654
+ });
655
+ app.delete("/api/atlas/v2/groups/:groupId/clusters/:clusterName", (c) => {
656
+ const groupId = c.req.param("groupId");
657
+ const clusterName = c.req.param("clusterName");
658
+ const cluster = ms().clusters.all().find(
659
+ (cl) => cl.group_id === groupId && cl.name === clusterName
660
+ );
661
+ if (!cluster) {
662
+ return mongoError(c, "CLUSTER_NOT_FOUND", `Cluster '${clusterName}' not found.`, 404);
663
+ }
664
+ deleteClusterData(ms, cluster.cluster_id);
665
+ ms().clusters.delete(cluster.id);
666
+ const project = ms().projects.findOneBy("group_id", groupId);
667
+ if (project) {
668
+ ms().projects.update(project.id, { cluster_count: Math.max(0, project.cluster_count - 1) });
669
+ }
670
+ return c.body(null, 204);
671
+ });
672
+ app.get("/api/atlas/v2/groups/:groupId/databaseUsers", (c) => {
673
+ const groupId = c.req.param("groupId");
674
+ const users = ms().users.all().filter((u) => u.group_id === groupId);
675
+ return mongoOk(c, {
676
+ results: users.map(formatUser),
677
+ totalCount: users.length
678
+ });
679
+ });
680
+ app.get("/api/atlas/v2/groups/:groupId/databaseUsers/admin/:username", (c) => {
681
+ const groupId = c.req.param("groupId");
682
+ const username = c.req.param("username");
683
+ const user = ms().users.all().find((u) => u.group_id === groupId && u.username === username);
684
+ if (!user) {
685
+ return mongoError(c, "USER_NOT_FOUND", `Database user '${username}' not found.`, 404);
686
+ }
687
+ return mongoOk(c, formatUser(user));
688
+ });
689
+ app.post("/api/atlas/v2/groups/:groupId/databaseUsers", async (c) => {
690
+ const groupId = c.req.param("groupId");
691
+ const body = await c.req.json();
692
+ if (!body.username?.trim()) {
693
+ return mongoError(c, "INVALID_PARAMETER", "username is required");
694
+ }
695
+ const existing = ms().users.all().find((u) => u.group_id === groupId && u.username === body.username);
696
+ if (existing) {
697
+ return mongoError(c, "DUPLICATE_USER", `User '${body.username}' already exists.`, 409);
698
+ }
699
+ const userId = generateUserId();
700
+ const user = ms().users.insert({
701
+ user_id: userId,
702
+ username: body.username,
703
+ group_id: groupId,
704
+ roles: (body.roles ?? []).map((r) => ({ database_name: r.databaseName, role_name: r.roleName }))
705
+ });
706
+ return mongoOk(c, formatUser(user), 201);
707
+ });
708
+ app.delete("/api/atlas/v2/groups/:groupId/databaseUsers/admin/:username", (c) => {
709
+ const groupId = c.req.param("groupId");
710
+ const username = c.req.param("username");
711
+ const user = ms().users.all().find((u) => u.group_id === groupId && u.username === username);
712
+ if (!user) {
713
+ return mongoError(c, "USER_NOT_FOUND", `Database user '${username}' not found.`, 404);
714
+ }
715
+ ms().users.delete(user.id);
716
+ return c.body(null, 204);
717
+ });
718
+ app.get("/api/atlas/v2/groups/:groupId/clusters/:clusterName/databases", (c) => {
719
+ const groupId = c.req.param("groupId");
720
+ const clusterName = c.req.param("clusterName");
721
+ const cluster = ms().clusters.all().find(
722
+ (cl) => cl.group_id === groupId && cl.name === clusterName
723
+ );
724
+ if (!cluster) {
725
+ return mongoError(c, "CLUSTER_NOT_FOUND", `Cluster '${clusterName}' not found.`, 404);
726
+ }
727
+ const databases = ms().databases.all().filter((db) => db.cluster_id === cluster.cluster_id);
728
+ return mongoOk(c, {
729
+ results: databases.map((db) => ({ databaseName: db.name })),
730
+ totalCount: databases.length
731
+ });
732
+ });
733
+ app.get("/api/atlas/v2/groups/:groupId/clusters/:clusterName/databases/:databaseName/collections", (c) => {
734
+ const groupId = c.req.param("groupId");
735
+ const clusterName = c.req.param("clusterName");
736
+ const databaseName = c.req.param("databaseName");
737
+ const cluster = ms().clusters.all().find(
738
+ (cl) => cl.group_id === groupId && cl.name === clusterName
739
+ );
740
+ if (!cluster) {
741
+ return mongoError(c, "CLUSTER_NOT_FOUND", `Cluster '${clusterName}' not found.`, 404);
742
+ }
743
+ const collections = ms().collections.all().filter(
744
+ (col) => col.cluster_id === cluster.cluster_id && col.database === databaseName
745
+ );
746
+ return mongoOk(c, {
747
+ results: collections.map((col) => ({ collectionName: col.name, databaseName })),
748
+ totalCount: collections.length
749
+ });
750
+ });
751
+ }
752
+ function deleteClusterData(ms, clusterId) {
753
+ const docs = ms().documents.all().filter((d) => d.cluster_id === clusterId);
754
+ for (const doc of docs) ms().documents.delete(doc.id);
755
+ const cols = ms().collections.all().filter((col) => col.cluster_id === clusterId);
756
+ for (const col of cols) ms().collections.delete(col.id);
757
+ const dbs = ms().databases.all().filter((db) => db.cluster_id === clusterId);
758
+ for (const db of dbs) ms().databases.delete(db.id);
759
+ }
760
+ function formatProject(p) {
761
+ return {
762
+ id: p.group_id,
763
+ name: p.name,
764
+ orgId: p.org_id,
765
+ clusterCount: p.cluster_count,
766
+ created: p.created_at
767
+ };
768
+ }
769
+ function formatCluster(cl) {
770
+ return {
771
+ id: cl.cluster_id,
772
+ name: cl.name,
773
+ groupId: cl.group_id,
774
+ stateName: cl.state,
775
+ mongoURI: cl.mongo_uri,
776
+ connectionStrings: {
777
+ standard: cl.connection_strings.standard,
778
+ standardSrv: cl.connection_strings.standard_srv
779
+ },
780
+ providerSettings: {
781
+ providerName: cl.provider_settings.provider_name,
782
+ instanceSizeName: cl.provider_settings.instance_size_name,
783
+ regionName: cl.provider_settings.region_name
784
+ },
785
+ clusterType: cl.cluster_type,
786
+ diskSizeGB: cl.disk_size_gb,
787
+ mongoDBVersion: cl.mongodb_version,
788
+ created: cl.created_at
789
+ };
790
+ }
791
+ function formatUser(u) {
792
+ return {
793
+ username: u.username,
794
+ groupId: u.group_id,
795
+ databaseName: "admin",
796
+ roles: u.roles.map((r) => ({ databaseName: r.database_name, roleName: r.role_name }))
797
+ };
798
+ }
799
+ function seedDefaults(store, _baseUrl) {
800
+ const ms = getMongoAtlasStore(store);
801
+ const groupId = generateGroupId();
802
+ ms.projects.insert({
803
+ group_id: groupId,
804
+ name: "Project0",
805
+ org_id: "default_org",
806
+ cluster_count: 1
807
+ });
808
+ const clusterId = generateClusterId();
809
+ ms.clusters.insert({
810
+ cluster_id: clusterId,
811
+ name: "Cluster0",
812
+ group_id: groupId,
813
+ state: "IDLE",
814
+ mongo_uri: "mongodb+srv://Cluster0.emulate.mongodb.net",
815
+ connection_strings: {
816
+ standard: "mongodb://Cluster0.emulate.mongodb.net:27017",
817
+ standard_srv: "mongodb+srv://Cluster0.emulate.mongodb.net"
818
+ },
819
+ provider_settings: {
820
+ provider_name: "AWS",
821
+ instance_size_name: "M10",
822
+ region_name: "US_EAST_1"
823
+ },
824
+ cluster_type: "REPLICASET",
825
+ disk_size_gb: 10,
826
+ mongodb_version: "8.0"
827
+ });
828
+ ms.users.insert({
829
+ user_id: generateUserId(),
830
+ username: "admin",
831
+ group_id: groupId,
832
+ roles: [{ database_name: "admin", role_name: "atlasAdmin" }]
833
+ });
834
+ ms.databases.insert({ cluster_id: clusterId, name: "test" });
835
+ ms.collections.insert({ cluster_id: clusterId, database: "test", name: "items" });
836
+ }
837
+ function seedFromConfig(store, _baseUrl, config) {
838
+ const ms = getMongoAtlasStore(store);
839
+ const projectIdMap = /* @__PURE__ */ new Map();
840
+ if (config.projects) {
841
+ for (const p of config.projects) {
842
+ const existing = ms.projects.all().find((ep) => ep.name === p.name);
843
+ if (existing) {
844
+ projectIdMap.set(p.name, existing.group_id);
845
+ continue;
846
+ }
847
+ const groupId = generateGroupId();
848
+ ms.projects.insert({
849
+ group_id: groupId,
850
+ name: p.name,
851
+ org_id: p.org_id ?? "default_org",
852
+ cluster_count: 0
853
+ });
854
+ projectIdMap.set(p.name, groupId);
855
+ }
856
+ }
857
+ const defaultProject = ms.projects.all()[0];
858
+ if (defaultProject) {
859
+ projectIdMap.set(defaultProject.name, defaultProject.group_id);
860
+ }
861
+ const clusterIdMap = /* @__PURE__ */ new Map();
862
+ if (config.clusters) {
863
+ for (const cl of config.clusters) {
864
+ const groupId = projectIdMap.get(cl.project);
865
+ if (!groupId) continue;
866
+ const existing = ms.clusters.all().find(
867
+ (ec) => ec.group_id === groupId && ec.name === cl.name
868
+ );
869
+ if (existing) {
870
+ clusterIdMap.set(cl.name, existing.cluster_id);
871
+ continue;
872
+ }
873
+ const clusterId = generateClusterId();
874
+ ms.clusters.insert({
875
+ cluster_id: clusterId,
876
+ name: cl.name,
877
+ group_id: groupId,
878
+ state: "IDLE",
879
+ mongo_uri: `mongodb+srv://${cl.name}.emulate.mongodb.net`,
880
+ connection_strings: {
881
+ standard: `mongodb://${cl.name}.emulate.mongodb.net:27017`,
882
+ standard_srv: `mongodb+srv://${cl.name}.emulate.mongodb.net`
883
+ },
884
+ provider_settings: {
885
+ provider_name: cl.provider ?? "AWS",
886
+ instance_size_name: cl.instance_size ?? "M10",
887
+ region_name: cl.region ?? "US_EAST_1"
888
+ },
889
+ cluster_type: "REPLICASET",
890
+ disk_size_gb: cl.disk_size_gb ?? 10,
891
+ mongodb_version: cl.mongodb_version ?? "8.0"
892
+ });
893
+ clusterIdMap.set(cl.name, clusterId);
894
+ const project = ms.projects.findOneBy("group_id", groupId);
895
+ if (project) {
896
+ ms.projects.update(project.id, { cluster_count: project.cluster_count + 1 });
897
+ }
898
+ }
899
+ }
900
+ const defaultCluster = ms.clusters.all()[0];
901
+ if (defaultCluster) {
902
+ clusterIdMap.set(defaultCluster.name, defaultCluster.cluster_id);
903
+ }
904
+ if (config.database_users) {
905
+ for (const u of config.database_users) {
906
+ const groupId = projectIdMap.get(u.project);
907
+ if (!groupId) continue;
908
+ const existing = ms.users.all().find(
909
+ (eu) => eu.group_id === groupId && eu.username === u.username
910
+ );
911
+ if (existing) continue;
912
+ ms.users.insert({
913
+ user_id: generateUserId(),
914
+ username: u.username,
915
+ group_id: groupId,
916
+ roles: u.roles ?? [{ database_name: "admin", role_name: "readWriteAnyDatabase" }]
917
+ });
918
+ }
919
+ }
920
+ if (config.databases) {
921
+ for (const db of config.databases) {
922
+ const clusterId = clusterIdMap.get(db.cluster);
923
+ if (!clusterId) continue;
924
+ const existingDb = ms.databases.all().find(
925
+ (edb) => edb.cluster_id === clusterId && edb.name === db.name
926
+ );
927
+ if (!existingDb) {
928
+ ms.databases.insert({ cluster_id: clusterId, name: db.name });
929
+ }
930
+ if (db.collections) {
931
+ for (const colName of db.collections) {
932
+ const existingCol = ms.collections.all().find(
933
+ (ec) => ec.cluster_id === clusterId && ec.database === db.name && ec.name === colName
934
+ );
935
+ if (!existingCol) {
936
+ ms.collections.insert({ cluster_id: clusterId, database: db.name, name: colName });
937
+ }
938
+ }
939
+ }
940
+ }
941
+ }
942
+ }
943
+ var mongoatlasPlugin = {
944
+ name: "mongoatlas",
945
+ register(app, store, webhooks, baseUrl, tokenMap) {
946
+ const ctx = { app, store, webhooks, baseUrl, tokenMap };
947
+ adminRoutes(ctx);
948
+ dataApiRoutes(ctx);
949
+ },
950
+ seed(store, baseUrl) {
951
+ seedDefaults(store, baseUrl);
952
+ }
953
+ };
954
+ var index_default = mongoatlasPlugin;
955
+ export {
956
+ index_default as default,
957
+ getMongoAtlasStore,
958
+ mongoatlasPlugin,
959
+ seedFromConfig
960
+ };
961
+ //# sourceMappingURL=dist-B674PYKV.js.map