@maykonpaulo/maestro-provider-mongodb 1.0.0-next.1

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/README.md ADDED
@@ -0,0 +1,86 @@
1
+ # @maykonpaulo/maestro-provider-mongodb
2
+
3
+ A MongoDB provider for [`@maykonpaulo/maestro-core`](https://www.npmjs.com/package/@maykonpaulo/maestro-core) — the first **document/schema-less family** provider, per [ADR 0007 — Universal Provider Strategy](../../docs/adr/0007-universal-provider-strategy.md). It implements all three of the core's provider contracts against a real MongoDB database:
4
+
5
+ - `IntrospectionProvider` — discovers collections and infers each one's shape by **sampling** documents (there is no fixed schema to read). Every entity is reported with `schemaless: true`.
6
+ - `DatasourceProvider` — real `list`/`findById`/`create`/`update`/`delete`/`count`, translated to native MongoDB queries (filters, sort, search, pagination).
7
+ - `SchemaWriteProvider` — `ensureEntity` creates the collection if it does not exist yet (idempotent).
8
+
9
+ ## Why a separate package instead of a dialect of `@maykonpaulo/maestro-provider-sql`
10
+
11
+ MongoDB's query model (documents, no fixed columns, no `information_schema`) is genuinely different from the relational family — it cannot share the SQL query-building layer the relational dialects reuse. ADR 0007 groups providers by *paradigm*, not by exact product: this package is the whole document-store family for now, the way `@maykonpaulo/maestro-provider-sql` is the whole relational family.
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install @maykonpaulo/maestro-provider-mongodb @maykonpaulo/maestro-core
17
+ ```
18
+
19
+ `@maykonpaulo/maestro-core` and `mongodb` are peer dependencies — install the versions your project already uses.
20
+
21
+ ## Usage
22
+
23
+ ```ts
24
+ import { runIntrospectionProvider } from '@maykonpaulo/maestro-core';
25
+ import { createMongoProvider } from '@maykonpaulo/maestro-provider-mongodb';
26
+
27
+ const provider = createMongoProvider({ uri: 'mongodb://localhost:27017', database: 'app' });
28
+
29
+ // Introspection — samples documents, validated/normalized via the core
30
+ const { result } = await runIntrospectionProvider(provider);
31
+
32
+ // Real CRUD against the same database
33
+ const created = await provider.create({ table: 'users', primaryKey: '_id', data: { name: 'Ada' } });
34
+ const page = await provider.list({ table: 'users', primaryKey: '_id', pagination: { strategy: 'page', page: 1, pageSize: 20 } });
35
+
36
+ // Create the collection if it isn't there yet
37
+ await provider.ensureEntity({ entity: { table: 'tags', fields: [] } });
38
+ ```
39
+
40
+ ### Configuration
41
+
42
+ ```ts
43
+ export interface MongoProviderOptions {
44
+ uri?: string; // a mongodb://... connection string
45
+ client?: MongoClient; // an already-connected client — for tests/embedding; caller owns its lifecycle
46
+ database: string; // required — Mongo has no "current database" to infer
47
+ maxDocuments?: number; // caps documents read per collection during introspection — unset by default (scans the whole collection)
48
+ }
49
+ ```
50
+
51
+ Exactly one of `uri` or `client` must be provided.
52
+
53
+ ## `_id` handling
54
+
55
+ MongoDB always assigns a native `ObjectId` to `_id` unless the caller supplies one. This provider follows that default:
56
+
57
+ - `create()` with `primaryKey: '_id'` and no `_id` in `data` lets Mongo generate a native `ObjectId` — the shape any pre-existing real collection almost certainly already uses. The returned record's `_id` is the `ObjectId`'s string form.
58
+ - `findById`/`update`/`delete` with `primaryKey: '_id'` accept a 24-hex-character id and match it against **both** a stored `ObjectId` and a stored plain string, so records created by this provider (which may hold either shape) and records that already existed in a real database (`ObjectId`) both resolve correctly.
59
+ - A non-`_id` primary key (e.g. `slug`) is always treated as a plain value — Mongo has no special type for it. `create()` still generates a `randomUUID()` string when the caller doesn't supply one, matching every other provider in this monorepo.
60
+
61
+ ## What is introspected
62
+
63
+ Since there is no system catalog, introspection **reads every document in every collection by default**
64
+ (`maxDocuments` is unset unless you explicitly cap it for a very large collection) and infers:
65
+
66
+ - **Fields** — the union of keys observed across all scanned documents, each with the most-recently-seen non-null type.
67
+ - **Nullability** — `true` whenever a scanned document omitted the field or held `null`/`undefined` for it, or whenever fewer documents than were scanned actually carried the field.
68
+ - **Primary key** — `_id` is always marked `isPrimaryKey: true`.
69
+ - **`schemaless: true`** on every entity (ADR 0007, DA-13) — signals to any consumer (CLI, declarative config generator, a future admin UI) that these fields are a likely shape, not a guarantee, unlike a relational provider's catalog-read columns. This is not about incomplete coverage of *existing* data — a full scan sees every document currently in the collection — it is an honest acknowledgment that MongoDB itself enforces no shape, so a document written a moment later can still introduce a field or type this result never saw. That is a property of the database, not a shortcut this provider takes.
70
+
71
+ ## What is *not* introspected (known limitations)
72
+
73
+ - **Relations** — MongoDB has no foreign-key constraint to read. Guessing one from a field-naming convention (e.g. `userId`) would claim a confidence level (`'definite'`/`'inferred'`) this provider cannot honestly back for a schemaless source, so `relations` is always `[]`.
74
+ - **Indices** — not introspected in this version.
75
+ - **Nested/embedded document shape** — a field holding a sub-document is reported as a single `'json'`-typed field; its inner structure is not recursively sampled.
76
+
77
+ ## Schema creation (`ensureEntity`)
78
+
79
+ Unlike a relational `CREATE TABLE`, a MongoDB collection carries no column definitions — `entity.fields` is not applied to the created collection, since there is nothing in Mongo's own model to apply it to. `ensureEntity` simply creates the (empty) collection if it doesn't already exist; the collection then accepts documents of whatever shape is written to it, per MongoDB's schemaless model.
80
+
81
+ ## Testing
82
+
83
+ Tests run against a **real, ephemeral MongoDB server** started per test run via
84
+ [`mongodb-memory-server`](https://github.com/typegoose/mongodb-memory-server) (downloads a real `mongod`
85
+ binary once, then runs fully offline/in-process) — no Docker, no shared external database. See
86
+ `tests/mongo-provider.test.ts`.
@@ -0,0 +1,34 @@
1
+ import { IntrospectionProvider, DatasourceProvider, SchemaWriteProvider } from '@maykonpaulo/maestro-core';
2
+ import { MongoClient } from 'mongodb';
3
+
4
+ /**
5
+ * Configuration for `createMongoProvider`. Either `uri` (a `mongodb://...` connection string) or an
6
+ * already-connected `client` must be provided — `client` exists mainly for tests/embedding, mirroring
7
+ * the `database`-instance pattern used by the `sqlite` dialect in `@maykonpaulo/maestro-provider-sql`.
8
+ */
9
+ interface MongoProviderOptions {
10
+ uri?: string;
11
+ client?: MongoClient;
12
+ database: string;
13
+ /**
14
+ * Caps how many documents introspection reads per collection. **Unset by default — the entire
15
+ * collection is scanned**, since MongoDB has no catalog to substitute for actually reading the data.
16
+ * Only set this for a very large collection where a full scan is genuinely too slow; doing so trades
17
+ * completeness for speed and should be a deliberate choice, not the default.
18
+ */
19
+ maxDocuments?: number;
20
+ }
21
+
22
+ interface MongoProvider extends IntrospectionProvider, DatasourceProvider, SchemaWriteProvider {
23
+ /**
24
+ * Closes the underlying `MongoClient`, but only when this provider opened it itself (from `uri`) —
25
+ * a caller-supplied `client` is never closed here, since the caller owns its lifecycle. Call this
26
+ * when you're done with the provider (e.g. before the process exits, or in a test's teardown) so the
27
+ * connection doesn't linger.
28
+ */
29
+ close(): Promise<void>;
30
+ }
31
+ /** Factory for {@link MongoProvider}, mirroring `createSqlProvider`'s shape. */
32
+ declare function createMongoProvider(options: MongoProviderOptions): MongoProvider;
33
+
34
+ export { type MongoProvider, type MongoProviderOptions, createMongoProvider };
package/dist/index.js ADDED
@@ -0,0 +1,305 @@
1
+ // src/mongoConnection.ts
2
+ var mongoModulePromise;
3
+ function loadMongo() {
4
+ mongoModulePromise ??= import("mongodb");
5
+ return mongoModulePromise;
6
+ }
7
+ async function openMongoDb(options) {
8
+ if (!options.uri && !options.client) {
9
+ throw new Error('MongoProvider: either "uri" or "client" must be provided.');
10
+ }
11
+ if (!options.database) {
12
+ throw new Error('MongoProvider: "database" is required.');
13
+ }
14
+ const maxDocuments = options.maxDocuments;
15
+ if (options.client) {
16
+ return { db: options.client.db(options.database), maxDocuments };
17
+ }
18
+ const mongodb = await loadMongo();
19
+ const client = new mongodb.MongoClient(options.uri);
20
+ await client.connect();
21
+ return { db: client.db(options.database), maxDocuments, ownedClient: client };
22
+ }
23
+
24
+ // src/MongoIntrospection.ts
25
+ import { ObjectId } from "mongodb";
26
+ var INTERNAL_COLLECTION_PREFIX = "system.";
27
+ function inferValueType(value) {
28
+ if (value instanceof ObjectId) return "string";
29
+ if (value instanceof Date) return "datetime";
30
+ if (Array.isArray(value)) return "array";
31
+ if (typeof value === "boolean") return "boolean";
32
+ if (typeof value === "number") return Number.isInteger(value) ? "integer" : "number";
33
+ if (typeof value === "object" && value !== null) return "json";
34
+ return "string";
35
+ }
36
+ async function scanEntity(db, collectionName, maxDocuments) {
37
+ let cursor = db.collection(collectionName).find({});
38
+ if (maxDocuments !== void 0) cursor = cursor.limit(maxDocuments);
39
+ const documents = await cursor.toArray();
40
+ const fields = /* @__PURE__ */ new Map();
41
+ let scannedCount = 0;
42
+ for (const doc of documents) {
43
+ scannedCount += 1;
44
+ const seenKeys = /* @__PURE__ */ new Set();
45
+ for (const [key, value] of Object.entries(doc)) {
46
+ seenKeys.add(key);
47
+ const isNull = value === null || value === void 0;
48
+ const existing = fields.get(key);
49
+ if (!existing) {
50
+ fields.set(key, { type: isNull ? "string" : inferValueType(value), seenNull: isNull, seenCount: 1 });
51
+ continue;
52
+ }
53
+ existing.seenCount += 1;
54
+ if (isNull) existing.seenNull = true;
55
+ else existing.type = inferValueType(value);
56
+ }
57
+ for (const [key, sample] of fields) {
58
+ if (!seenKeys.has(key)) sample.seenNull = true;
59
+ }
60
+ }
61
+ const fieldSchemas = [...fields.entries()].map(([name, sample]) => ({
62
+ name,
63
+ nativeType: sample.type,
64
+ type: sample.type,
65
+ nullable: sample.seenNull || sample.seenCount < scannedCount,
66
+ isPrimaryKey: name === "_id"
67
+ }));
68
+ return { table: collectionName, fields: fieldSchemas, schemaless: true };
69
+ }
70
+ async function introspectMongo(db, maxDocuments) {
71
+ const collections = await db.listCollections(void 0, { nameOnly: true }).toArray();
72
+ const names = collections.map((c) => c.name).filter((name) => !name.startsWith(INTERNAL_COLLECTION_PREFIX)).sort();
73
+ const entities = await Promise.all(names.map((name) => scanEntity(db, name, maxDocuments)));
74
+ return { entities, relations: [] };
75
+ }
76
+ async function mongoCollectionExists(db, name) {
77
+ const collections = await db.listCollections({ name }, { nameOnly: true }).toArray();
78
+ return collections.length > 0;
79
+ }
80
+
81
+ // src/MongoDatasource.ts
82
+ import { randomUUID } from "crypto";
83
+ import { ObjectId as ObjectId2 } from "mongodb";
84
+ import { MaestroError, ErrorCode } from "@maykonpaulo/maestro-core";
85
+ var OBJECT_ID_PATTERN = /^[0-9a-fA-F]{24}$/;
86
+ function idFilterValue(primaryKey, id) {
87
+ if (primaryKey === "_id" && OBJECT_ID_PATTERN.test(id)) {
88
+ return { $in: [new ObjectId2(id), id] };
89
+ }
90
+ return id;
91
+ }
92
+ function escapeRegex(value) {
93
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
94
+ }
95
+ function buildFilterClause(filter) {
96
+ const { field, value } = filter;
97
+ switch (filter.operator) {
98
+ case "equals":
99
+ return { [field]: value };
100
+ case "notEquals":
101
+ return { [field]: { $ne: value } };
102
+ case "contains":
103
+ return { [field]: { $regex: escapeRegex(String(value ?? "")), $options: "i" } };
104
+ case "startsWith":
105
+ return { [field]: { $regex: `^${escapeRegex(String(value ?? ""))}`, $options: "i" } };
106
+ case "endsWith":
107
+ return { [field]: { $regex: `${escapeRegex(String(value ?? ""))}$`, $options: "i" } };
108
+ case "gt":
109
+ return { [field]: { $gt: value } };
110
+ case "gte":
111
+ return { [field]: { $gte: value } };
112
+ case "lt":
113
+ return { [field]: { $lt: value } };
114
+ case "lte":
115
+ return { [field]: { $lte: value } };
116
+ case "between": {
117
+ const [min, max] = value;
118
+ return { [field]: { $gte: min, $lte: max } };
119
+ }
120
+ case "in":
121
+ return { [field]: { $in: value } };
122
+ case "notIn":
123
+ return { [field]: { $nin: value } };
124
+ case "isNull":
125
+ return { [field]: null };
126
+ case "isNotNull":
127
+ return { [field]: { $ne: null } };
128
+ case "isTrue":
129
+ return { [field]: true };
130
+ case "isFalse":
131
+ return { [field]: false };
132
+ default:
133
+ return {};
134
+ }
135
+ }
136
+ function buildFilter(context) {
137
+ const clauses = (context.filters ?? []).map(buildFilterClause);
138
+ if (context.search?.term && context.search.fields.length) {
139
+ clauses.push({
140
+ $or: context.search.fields.map((field) => ({
141
+ [field]: { $regex: escapeRegex(context.search.term), $options: "i" }
142
+ }))
143
+ });
144
+ }
145
+ if (!clauses.length) return {};
146
+ return clauses.length === 1 ? clauses[0] : { $and: clauses };
147
+ }
148
+ function buildSort(sort) {
149
+ if (!sort?.length) return void 0;
150
+ return Object.fromEntries(sort.map((s) => [s.field, s.direction === "desc" ? -1 : 1]));
151
+ }
152
+ function serializeDocument(doc) {
153
+ const record = {};
154
+ for (const [key, value] of Object.entries(doc)) {
155
+ record[key] = value instanceof ObjectId2 ? value.toString() : value;
156
+ }
157
+ return record;
158
+ }
159
+ async function listDocuments(db, context) {
160
+ const collection = db.collection(context.table);
161
+ const filter = buildFilter(context);
162
+ const sort = buildSort(context.sort);
163
+ const total = await collection.countDocuments(filter);
164
+ let cursor = collection.find(filter);
165
+ if (sort) cursor = cursor.sort(sort);
166
+ const pagination = context.pagination;
167
+ let page;
168
+ let pageSize;
169
+ let cursorOffset;
170
+ let cursorLimit;
171
+ if (pagination?.strategy === "page") {
172
+ page = pagination.page;
173
+ pageSize = pagination.pageSize;
174
+ cursor = cursor.skip((page - 1) * pageSize).limit(pageSize);
175
+ } else if (pagination?.strategy === "offset") {
176
+ cursor = cursor.skip(pagination.offset).limit(pagination.limit);
177
+ } else if (pagination?.strategy === "cursor") {
178
+ cursorOffset = pagination.cursor ? parseInt(pagination.cursor, 10) : 0;
179
+ cursorLimit = pagination.limit;
180
+ cursor = cursor.skip(cursorOffset).limit(cursorLimit);
181
+ }
182
+ const documents = await cursor.toArray();
183
+ const records = documents.map(serializeDocument);
184
+ if (page !== void 0 && pageSize !== void 0) {
185
+ return { records, total, page, pageSize, totalPages: Math.ceil(total / pageSize) };
186
+ }
187
+ if (cursorOffset !== void 0 && cursorLimit !== void 0) {
188
+ const nextOffset = cursorOffset + cursorLimit;
189
+ return { records, total, nextCursor: nextOffset < total ? String(nextOffset) : void 0 };
190
+ }
191
+ return { records, total };
192
+ }
193
+ async function countDocumentsForContext(db, context) {
194
+ return db.collection(context.table).countDocuments(buildFilter(context));
195
+ }
196
+ async function findDocumentById(db, context) {
197
+ const doc = await db.collection(context.table).findOne({ [context.primaryKey]: idFilterValue(context.primaryKey, context.id) });
198
+ return doc ? serializeDocument(doc) : null;
199
+ }
200
+ async function createDocument(db, context) {
201
+ const collection = db.collection(context.table);
202
+ const providedId = context.data[context.primaryKey];
203
+ if (context.primaryKey === "_id" && providedId === void 0) {
204
+ const { insertedId } = await collection.insertOne({ ...context.data });
205
+ return { ...context.data, _id: insertedId.toString() };
206
+ }
207
+ const id = String(providedId ?? randomUUID());
208
+ const record = { ...context.data, [context.primaryKey]: id };
209
+ await collection.insertOne(record);
210
+ return serializeDocument(record);
211
+ }
212
+ async function updateDocument(db, context) {
213
+ const existing = await findDocumentById(db, {
214
+ table: context.table,
215
+ primaryKey: context.primaryKey,
216
+ id: context.id
217
+ });
218
+ if (!existing) {
219
+ throw new MaestroError(ErrorCode.NOT_FOUND, `Record '${context.id}' not found in collection '${context.table}'.`);
220
+ }
221
+ const updates = { ...context.data };
222
+ delete updates[context.primaryKey];
223
+ if (Object.keys(updates).length) {
224
+ await db.collection(context.table).updateOne(
225
+ { [context.primaryKey]: idFilterValue(context.primaryKey, context.id) },
226
+ { $set: updates }
227
+ );
228
+ }
229
+ return { ...existing, ...context.data, [context.primaryKey]: context.id };
230
+ }
231
+ async function deleteDocument(db, context) {
232
+ const result = await db.collection(context.table).deleteOne({ [context.primaryKey]: idFilterValue(context.primaryKey, context.id) });
233
+ if (result.deletedCount === 0) {
234
+ throw new MaestroError(ErrorCode.NOT_FOUND, `Record '${context.id}' not found in collection '${context.table}'.`);
235
+ }
236
+ }
237
+
238
+ // src/MongoSchemaWrite.ts
239
+ async function ensureMongoEntity(db, entity) {
240
+ if (await mongoCollectionExists(db, entity.table)) {
241
+ return { created: false };
242
+ }
243
+ await db.createCollection(entity.table);
244
+ return { created: true };
245
+ }
246
+
247
+ // src/MongoProvider.ts
248
+ var MongoProviderImpl = class {
249
+ constructor(options) {
250
+ this.options = options;
251
+ if (!options.uri && !options.client) {
252
+ throw new Error('MongoProvider: either "uri" or "client" must be provided.');
253
+ }
254
+ if (!options.database) {
255
+ throw new Error('MongoProvider: "database" is required.');
256
+ }
257
+ }
258
+ options;
259
+ name = "mongodb";
260
+ handlePromise;
261
+ getHandle() {
262
+ this.handlePromise ??= openMongoDb(this.options);
263
+ return this.handlePromise;
264
+ }
265
+ async getDb() {
266
+ return (await this.getHandle()).db;
267
+ }
268
+ async introspect(_context) {
269
+ const { db, maxDocuments } = await this.getHandle();
270
+ return introspectMongo(db, maxDocuments);
271
+ }
272
+ async list(context) {
273
+ return listDocuments(await this.getDb(), context);
274
+ }
275
+ async findById(context) {
276
+ return findDocumentById(await this.getDb(), context);
277
+ }
278
+ async create(context) {
279
+ return createDocument(await this.getDb(), context);
280
+ }
281
+ async update(context) {
282
+ return updateDocument(await this.getDb(), context);
283
+ }
284
+ async delete(context) {
285
+ await deleteDocument(await this.getDb(), context);
286
+ }
287
+ async count(context) {
288
+ return countDocumentsForContext(await this.getDb(), context);
289
+ }
290
+ async ensureEntity(context) {
291
+ return ensureMongoEntity(await this.getDb(), context.entity);
292
+ }
293
+ async close() {
294
+ if (!this.handlePromise) return;
295
+ const { ownedClient } = await this.handlePromise;
296
+ if (ownedClient) await ownedClient.close();
297
+ }
298
+ };
299
+ function createMongoProvider(options) {
300
+ return new MongoProviderImpl(options);
301
+ }
302
+ export {
303
+ createMongoProvider
304
+ };
305
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/mongoConnection.ts","../src/MongoIntrospection.ts","../src/MongoDatasource.ts","../src/MongoSchemaWrite.ts","../src/MongoProvider.ts"],"sourcesContent":["import type * as MongoModule from 'mongodb';\nimport type { Db, MongoClient } from 'mongodb';\n\n/**\n * Configuration for `createMongoProvider`. Either `uri` (a `mongodb://...` connection string) or an\n * already-connected `client` must be provided — `client` exists mainly for tests/embedding, mirroring\n * the `database`-instance pattern used by the `sqlite` dialect in `@maykonpaulo/maestro-provider-sql`.\n */\nexport interface MongoProviderOptions {\n uri?: string;\n client?: MongoClient;\n database: string;\n /**\n * Caps how many documents introspection reads per collection. **Unset by default — the entire\n * collection is scanned**, since MongoDB has no catalog to substitute for actually reading the data.\n * Only set this for a very large collection where a full scan is genuinely too slow; doing so trades\n * completeness for speed and should be a deliberate choice, not the default.\n */\n maxDocuments?: number;\n}\n\nlet mongoModulePromise: Promise<typeof MongoModule> | undefined;\n\n/** Loads the `mongodb` driver lazily — never imported at this package's build/publish time, only when the provider is actually used. */\nfunction loadMongo(): Promise<typeof MongoModule> {\n mongoModulePromise ??= import('mongodb');\n return mongoModulePromise;\n}\n\nexport interface MongoHandle {\n db: Db;\n maxDocuments: number | undefined;\n /** Present only when this provider opened its own client (not a caller-supplied `client`) — closed on demand by the caller if desired. Never closed automatically. */\n ownedClient?: MongoClient;\n}\n\nexport async function openMongoDb(options: MongoProviderOptions): Promise<MongoHandle> {\n if (!options.uri && !options.client) {\n throw new Error('MongoProvider: either \"uri\" or \"client\" must be provided.');\n }\n if (!options.database) {\n throw new Error('MongoProvider: \"database\" is required.');\n }\n const maxDocuments = options.maxDocuments;\n if (options.client) {\n return { db: options.client.db(options.database), maxDocuments };\n }\n const mongodb = await loadMongo();\n const client = new mongodb.MongoClient(options.uri!);\n await client.connect();\n return { db: client.db(options.database), maxDocuments, ownedClient: client };\n}\n","import type { Db } from 'mongodb';\nimport { ObjectId } from 'mongodb';\nimport type {\n IntrospectionResult,\n EntityIntrospectionSchema,\n FieldIntrospectionSchema,\n FieldType,\n} from '@maykonpaulo/maestro-core';\n\nconst INTERNAL_COLLECTION_PREFIX = 'system.';\n\n/** Infers a `FieldType` from a sampled JS/BSON value — never from a schema, since documents have none. */\nfunction inferValueType(value: unknown): FieldType {\n if (value instanceof ObjectId) return 'string';\n if (value instanceof Date) return 'datetime';\n if (Array.isArray(value)) return 'array';\n if (typeof value === 'boolean') return 'boolean';\n if (typeof value === 'number') return Number.isInteger(value) ? 'integer' : 'number';\n if (typeof value === 'object' && value !== null) return 'json';\n return 'string';\n}\n\ninterface FieldSample {\n type: FieldType;\n seenNull: boolean;\n seenCount: number;\n}\n\n/**\n * Scans every document in `collection` (or up to `maxDocuments`, when the caller explicitly caps it for\n * a very large collection) and infers a likely shape: the union of keys observed, each field's\n * most-recently-seen non-null type, and `nullable: true` whenever a document either omitted the field\n * or held `null`/`undefined` for it. This reads the whole collection by default — not a partial guess\n * from a handful of documents — precisely because MongoDB has no catalog to read instead; every\n * returned field still carries `schemaless: true` at the entity level (ADR 0007, DA-13), since a\n * document written *after* this scan could still introduce a field or type this result never saw.\n */\nasync function scanEntity(db: Db, collectionName: string, maxDocuments: number | undefined): Promise<EntityIntrospectionSchema> {\n let cursor = db.collection(collectionName).find({});\n if (maxDocuments !== undefined) cursor = cursor.limit(maxDocuments);\n const documents = await cursor.toArray();\n const fields = new Map<string, FieldSample>();\n let scannedCount = 0;\n\n for (const doc of documents) {\n scannedCount += 1;\n const seenKeys = new Set<string>();\n for (const [key, value] of Object.entries(doc)) {\n seenKeys.add(key);\n const isNull = value === null || value === undefined;\n const existing = fields.get(key);\n if (!existing) {\n fields.set(key, { type: isNull ? 'string' : inferValueType(value), seenNull: isNull, seenCount: 1 });\n continue;\n }\n existing.seenCount += 1;\n if (isNull) existing.seenNull = true;\n else existing.type = inferValueType(value);\n }\n // Any field seen in an earlier document but absent from this one is nullable (missing == null in Mongo semantics).\n for (const [key, sample] of fields) {\n if (!seenKeys.has(key)) sample.seenNull = true;\n }\n }\n\n const fieldSchemas: FieldIntrospectionSchema[] = [...fields.entries()].map(([name, sample]) => ({\n name,\n nativeType: sample.type,\n type: sample.type,\n nullable: sample.seenNull || sample.seenCount < scannedCount,\n isPrimaryKey: name === '_id',\n }));\n\n return { table: collectionName, fields: fieldSchemas, schemaless: true };\n}\n\n/**\n * Discovers collections and infers each one's field shape by scanning documents — MongoDB has no\n * system catalog describing a fixed schema, so this is the only way to introspect it. Every document in\n * every collection is read by default (`maxDocuments` is `undefined` unless the caller explicitly caps\n * it), not a small fixed sample — the result is as complete as a schemaless source can honestly be. No\n * relations are inferred: Mongo has no foreign-key constraint to read, and guessing one from\n * field-naming conventions (e.g. `userId`) would be a confidence level this contract does not represent\n * for a schemaless source.\n */\nexport async function introspectMongo(db: Db, maxDocuments: number | undefined): Promise<IntrospectionResult> {\n const collections = await db.listCollections(undefined, { nameOnly: true }).toArray();\n const names = collections\n .map((c) => c.name)\n .filter((name) => !name.startsWith(INTERNAL_COLLECTION_PREFIX))\n .sort();\n const entities = await Promise.all(names.map((name) => scanEntity(db, name, maxDocuments)));\n return { entities, relations: [] };\n}\n\nexport async function mongoCollectionExists(db: Db, name: string): Promise<boolean> {\n const collections = await db.listCollections({ name }, { nameOnly: true }).toArray();\n return collections.length > 0;\n}\n","import { randomUUID } from 'node:crypto';\nimport { ObjectId } from 'mongodb';\nimport type { Db, Document, Filter, Sort } from 'mongodb';\nimport type {\n DatasourceQueryContext,\n DatasourceFindContext,\n DatasourceWriteContext,\n DatasourceUpdateContext,\n DatasourceDeleteContext,\n ListResult,\n FilterDescriptor,\n SortDescriptor,\n} from '@maykonpaulo/maestro-core';\nimport { MaestroError, ErrorCode } from '@maykonpaulo/maestro-core';\n\nconst OBJECT_ID_PATTERN = /^[0-9a-fA-F]{24}$/;\n\n/**\n * Renders `_id` lookups tolerant of both representations a collection may hold: a native `ObjectId`\n * (the Mongo default, and the shape any pre-existing real collection almost certainly already uses) or\n * a plain string (what this provider's own `create()` assigns when the caller doesn't supply one and\n * the primary key isn't `_id`). Non-`_id` primary keys are always plain values — Mongo has no special\n * type for them.\n */\nfunction idFilterValue(primaryKey: string, id: string): unknown {\n if (primaryKey === '_id' && OBJECT_ID_PATTERN.test(id)) {\n return { $in: [new ObjectId(id), id] };\n }\n return id;\n}\n\nfunction escapeRegex(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nfunction buildFilterClause(filter: FilterDescriptor): Document {\n const { field, value } = filter;\n switch (filter.operator) {\n case 'equals':\n return { [field]: value };\n case 'notEquals':\n return { [field]: { $ne: value } };\n case 'contains':\n return { [field]: { $regex: escapeRegex(String(value ?? '')), $options: 'i' } };\n case 'startsWith':\n return { [field]: { $regex: `^${escapeRegex(String(value ?? ''))}`, $options: 'i' } };\n case 'endsWith':\n return { [field]: { $regex: `${escapeRegex(String(value ?? ''))}$`, $options: 'i' } };\n case 'gt':\n return { [field]: { $gt: value } };\n case 'gte':\n return { [field]: { $gte: value } };\n case 'lt':\n return { [field]: { $lt: value } };\n case 'lte':\n return { [field]: { $lte: value } };\n case 'between': {\n const [min, max] = value as [unknown, unknown];\n return { [field]: { $gte: min, $lte: max } };\n }\n case 'in':\n return { [field]: { $in: value as unknown[] } };\n case 'notIn':\n return { [field]: { $nin: value as unknown[] } };\n case 'isNull':\n return { [field]: null };\n case 'isNotNull':\n return { [field]: { $ne: null } };\n case 'isTrue':\n return { [field]: true };\n case 'isFalse':\n return { [field]: false };\n default:\n return {};\n }\n}\n\nfunction buildFilter(context: Pick<DatasourceQueryContext, 'filters' | 'search'>): Filter<Document> {\n const clauses: Document[] = (context.filters ?? []).map(buildFilterClause);\n if (context.search?.term && context.search.fields.length) {\n clauses.push({\n $or: context.search.fields.map((field) => ({\n [field]: { $regex: escapeRegex(context.search!.term), $options: 'i' },\n })),\n });\n }\n if (!clauses.length) return {};\n return clauses.length === 1 ? clauses[0] : { $and: clauses };\n}\n\nfunction buildSort(sort?: SortDescriptor[]): Sort | undefined {\n if (!sort?.length) return undefined;\n return Object.fromEntries(sort.map((s) => [s.field, s.direction === 'desc' ? -1 : 1]));\n}\n\n/** Converts a Mongo `ObjectId` value at any top-level field (typically `_id`) to its string form, so records match the plain `Record<string, unknown>` shape every other provider returns. */\nfunction serializeDocument(doc: Document): Record<string, unknown> {\n const record: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(doc)) {\n record[key] = value instanceof ObjectId ? value.toString() : value;\n }\n return record;\n}\n\nexport async function listDocuments(\n db: Db,\n context: DatasourceQueryContext,\n): Promise<ListResult<Record<string, unknown>>> {\n const collection = db.collection(context.table);\n const filter = buildFilter(context);\n const sort = buildSort(context.sort);\n const total = await collection.countDocuments(filter);\n\n let cursor = collection.find(filter);\n if (sort) cursor = cursor.sort(sort);\n\n const pagination = context.pagination;\n let page: number | undefined;\n let pageSize: number | undefined;\n let cursorOffset: number | undefined;\n let cursorLimit: number | undefined;\n\n if (pagination?.strategy === 'page') {\n page = pagination.page;\n pageSize = pagination.pageSize;\n cursor = cursor.skip((page - 1) * pageSize).limit(pageSize);\n } else if (pagination?.strategy === 'offset') {\n cursor = cursor.skip(pagination.offset).limit(pagination.limit);\n } else if (pagination?.strategy === 'cursor') {\n cursorOffset = pagination.cursor ? parseInt(pagination.cursor, 10) : 0;\n cursorLimit = pagination.limit;\n cursor = cursor.skip(cursorOffset).limit(cursorLimit);\n }\n\n const documents = await cursor.toArray();\n const records = documents.map(serializeDocument);\n\n if (page !== undefined && pageSize !== undefined) {\n return { records, total, page, pageSize, totalPages: Math.ceil(total / pageSize) };\n }\n if (cursorOffset !== undefined && cursorLimit !== undefined) {\n const nextOffset = cursorOffset + cursorLimit;\n return { records, total, nextCursor: nextOffset < total ? String(nextOffset) : undefined };\n }\n return { records, total };\n}\n\nexport async function countDocumentsForContext(db: Db, context: DatasourceQueryContext): Promise<number> {\n return db.collection(context.table).countDocuments(buildFilter(context));\n}\n\nexport async function findDocumentById(\n db: Db,\n context: DatasourceFindContext,\n): Promise<Record<string, unknown> | null> {\n const doc = await db\n .collection(context.table)\n .findOne({ [context.primaryKey]: idFilterValue(context.primaryKey, context.id) } as Filter<Document>);\n return doc ? serializeDocument(doc) : null;\n}\n\nexport async function createDocument(\n db: Db,\n context: DatasourceWriteContext,\n): Promise<Record<string, unknown>> {\n const collection = db.collection(context.table);\n const providedId = context.data[context.primaryKey];\n\n if (context.primaryKey === '_id' && providedId === undefined) {\n // Let Mongo assign a native ObjectId — the idiomatic default, and what every other collection in\n // a real, pre-existing database already does.\n const { insertedId } = await collection.insertOne({ ...context.data } as Document);\n return { ...context.data, _id: insertedId.toString() };\n }\n\n const id = String(providedId ?? randomUUID());\n const record = { ...context.data, [context.primaryKey]: id };\n // insertOne mutates its argument, adding a native `_id` ObjectId when the document doesn't already\n // carry one (true here whenever `primaryKey` isn't `_id`) — serialize before returning so the shape\n // matches what findById/list report for the same document.\n await collection.insertOne(record as Document);\n return serializeDocument(record);\n}\n\nexport async function updateDocument(\n db: Db,\n context: DatasourceUpdateContext,\n): Promise<Record<string, unknown>> {\n const existing = await findDocumentById(db, {\n table: context.table,\n primaryKey: context.primaryKey,\n id: context.id,\n });\n if (!existing) {\n throw new MaestroError(ErrorCode.NOT_FOUND, `Record '${context.id}' not found in collection '${context.table}'.`);\n }\n const updates = { ...context.data };\n delete updates[context.primaryKey];\n if (Object.keys(updates).length) {\n await db\n .collection(context.table)\n .updateOne(\n { [context.primaryKey]: idFilterValue(context.primaryKey, context.id) } as Filter<Document>,\n { $set: updates },\n );\n }\n return { ...existing, ...context.data, [context.primaryKey]: context.id };\n}\n\nexport async function deleteDocument(db: Db, context: DatasourceDeleteContext): Promise<void> {\n const result = await db\n .collection(context.table)\n .deleteOne({ [context.primaryKey]: idFilterValue(context.primaryKey, context.id) } as Filter<Document>);\n if (result.deletedCount === 0) {\n throw new MaestroError(ErrorCode.NOT_FOUND, `Record '${context.id}' not found in collection '${context.table}'.`);\n }\n}\n","import type { Db } from 'mongodb';\nimport type { EntityIntrospectionSchema, SchemaWriteResult } from '@maykonpaulo/maestro-core';\nimport { mongoCollectionExists } from './MongoIntrospection.js';\n\n/**\n * Creates the collection for `entity` if it does not already exist. Idempotent. Unlike a relational\n * `CREATE TABLE`, Mongo collections carry no column definitions — `entity.fields` is not applied to the\n * created collection (there is nothing to apply it to); the collection simply starts accepting\n * documents of whatever shape is written to it, per MongoDB's schemaless model.\n */\nexport async function ensureMongoEntity(db: Db, entity: EntityIntrospectionSchema): Promise<SchemaWriteResult> {\n if (await mongoCollectionExists(db, entity.table)) {\n return { created: false };\n }\n await db.createCollection(entity.table);\n return { created: true };\n}\n","import type { Db } from 'mongodb';\nimport type {\n IntrospectionProvider,\n IntrospectionContext,\n IntrospectionResult,\n DatasourceProvider,\n DatasourceQueryContext,\n DatasourceFindContext,\n DatasourceWriteContext,\n DatasourceUpdateContext,\n DatasourceDeleteContext,\n ListResult,\n SchemaWriteProvider,\n SchemaWriteContext,\n SchemaWriteResult,\n} from '@maykonpaulo/maestro-core';\nimport type { MongoProviderOptions, MongoHandle } from './mongoConnection.js';\nimport { openMongoDb } from './mongoConnection.js';\nimport { introspectMongo } from './MongoIntrospection.js';\nimport { listDocuments, findDocumentById, createDocument, updateDocument, deleteDocument, countDocumentsForContext } from './MongoDatasource.js';\nimport { ensureMongoEntity } from './MongoSchemaWrite.js';\n\nexport interface MongoProvider extends IntrospectionProvider, DatasourceProvider, SchemaWriteProvider {\n /**\n * Closes the underlying `MongoClient`, but only when this provider opened it itself (from `uri`) —\n * a caller-supplied `client` is never closed here, since the caller owns its lifecycle. Call this\n * when you're done with the provider (e.g. before the process exits, or in a test's teardown) so the\n * connection doesn't linger.\n */\n close(): Promise<void>;\n}\n\n/**\n * The first document/schema-less family provider (ADR 0007, DA-13): implements `IntrospectionProvider`\n * (sampling-based, `schemaless: true`), `DatasourceProvider` (real CRUD via the native `mongodb`\n * driver) and `SchemaWriteProvider` (`ensureEntity` creates the collection if missing) over the same\n * `Db` handle, opened lazily once and reused for the life of this instance. The `mongodb` driver is\n * loaded dynamically — it is never a mandatory dependency of consumers who don't install this package.\n */\nclass MongoProviderImpl implements MongoProvider {\n readonly name = 'mongodb';\n\n private handlePromise: Promise<MongoHandle> | undefined;\n\n constructor(private readonly options: MongoProviderOptions) {\n if (!options.uri && !options.client) {\n throw new Error('MongoProvider: either \"uri\" or \"client\" must be provided.');\n }\n if (!options.database) {\n throw new Error('MongoProvider: \"database\" is required.');\n }\n }\n\n private getHandle(): Promise<MongoHandle> {\n this.handlePromise ??= openMongoDb(this.options);\n return this.handlePromise;\n }\n\n private async getDb(): Promise<Db> {\n return (await this.getHandle()).db;\n }\n\n async introspect(_context?: IntrospectionContext): Promise<IntrospectionResult> {\n const { db, maxDocuments } = await this.getHandle();\n return introspectMongo(db, maxDocuments);\n }\n\n async list(context: DatasourceQueryContext): Promise<ListResult<Record<string, unknown>>> {\n return listDocuments(await this.getDb(), context);\n }\n\n async findById(context: DatasourceFindContext): Promise<Record<string, unknown> | null> {\n return findDocumentById(await this.getDb(), context);\n }\n\n async create(context: DatasourceWriteContext): Promise<Record<string, unknown>> {\n return createDocument(await this.getDb(), context);\n }\n\n async update(context: DatasourceUpdateContext): Promise<Record<string, unknown>> {\n return updateDocument(await this.getDb(), context);\n }\n\n async delete(context: DatasourceDeleteContext): Promise<void> {\n await deleteDocument(await this.getDb(), context);\n }\n\n async count(context: DatasourceQueryContext): Promise<number> {\n return countDocumentsForContext(await this.getDb(), context);\n }\n\n async ensureEntity(context: SchemaWriteContext): Promise<SchemaWriteResult> {\n return ensureMongoEntity(await this.getDb(), context.entity);\n }\n\n async close(): Promise<void> {\n if (!this.handlePromise) return;\n const { ownedClient } = await this.handlePromise;\n if (ownedClient) await ownedClient.close();\n }\n}\n\n/** Factory for {@link MongoProvider}, mirroring `createSqlProvider`'s shape. */\nexport function createMongoProvider(options: MongoProviderOptions): MongoProvider {\n return new MongoProviderImpl(options);\n}\n"],"mappings":"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","names":["ObjectId"]}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@maykonpaulo/maestro-provider-mongodb",
3
+ "version": "1.0.0-next.1",
4
+ "description": "MongoDB provider for @maykonpaulo/maestro-core — schema-inferred introspection (sampling), real CRUD, and missing-collection creation, per ADR 0007 (the first document/schema-less family provider).",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "peerDependencies": {
23
+ "mongodb": "^6.0.0 || ^7.0.0",
24
+ "@maykonpaulo/maestro-core": "0.7.0-next.2"
25
+ },
26
+ "devDependencies": {
27
+ "@types/node": "^22.0.0",
28
+ "mongodb": "^7.4.0",
29
+ "mongodb-memory-server": "^11.2.0",
30
+ "rimraf": "^6.0.0",
31
+ "tsup": "^8.0.0",
32
+ "typescript": "^5.6.0",
33
+ "vitest": "^2.0.0",
34
+ "@maykonpaulo/maestro-core": "0.7.0-next.2"
35
+ },
36
+ "scripts": {
37
+ "build": "tsup",
38
+ "test": "vitest run",
39
+ "typecheck": "tsc --noEmit",
40
+ "lint": "eslint src tests",
41
+ "clean": "rimraf dist"
42
+ }
43
+ }