@better-media/mongodb-adapter 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 abenezeratnafu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
20
+ ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,22 @@
1
+ # @better-media/mongodb-adapter
2
+
3
+ MongoDB database adapter for Better Media.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @better-media/mongodb-adapter
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import { MongoClient } from "mongodb";
15
+ import { mongodbAdapter } from "@better-media/mongodb-adapter";
16
+
17
+ const client = new MongoClient("mongodb://localhost:27017");
18
+ const adapter = mongodbAdapter(client, {
19
+ config: { databaseName: "my_db" },
20
+ schema: mySchema,
21
+ });
22
+ ```
@@ -0,0 +1,51 @@
1
+ import { MongoClient, ClientSession } from 'mongodb';
2
+ import { DatabaseAdapter, BmSchema, DbHooks, CreateOptions, FindOptions, UpdateOptions, DeleteOptions, CountOptions, DatabaseTransactionAdapter, ModelDefinition } from '@better-media/core';
3
+
4
+ interface MongoDbConfig {
5
+ /** MongoDB database name */
6
+ databaseName: string;
7
+ }
8
+
9
+ interface MongoDbOptions {
10
+ config: MongoDbConfig;
11
+ schema: BmSchema;
12
+ hooks?: DbHooks;
13
+ session?: ClientSession;
14
+ }
15
+ /**
16
+ * MongoDB database adapter.
17
+ */
18
+ declare class MongoDbAdapter implements DatabaseAdapter {
19
+ private readonly client;
20
+ private readonly db;
21
+ private readonly schema;
22
+ private readonly hooks?;
23
+ private readonly session?;
24
+ constructor(client: MongoClient, options: MongoDbOptions);
25
+ private getCollection;
26
+ private getModelFields;
27
+ private getModelDefinition;
28
+ private getHookContext;
29
+ private buildFilter;
30
+ private escapeRegex;
31
+ private mapToMongo;
32
+ private mapFromMongo;
33
+ create<T extends Record<string, unknown>>(options: CreateOptions<T>): Promise<T>;
34
+ private applyPopulate;
35
+ findOne<T extends Record<string, unknown>>(options: FindOptions<T>): Promise<T | null>;
36
+ findMany<T extends Record<string, unknown>>(options: FindOptions<T>): Promise<T[]>;
37
+ update<T extends Record<string, unknown>>(options: UpdateOptions<T>): Promise<T | null>;
38
+ updateMany<T extends Record<string, unknown>>(options: UpdateOptions<T>): Promise<number>;
39
+ delete(options: DeleteOptions): Promise<void>;
40
+ deleteMany(options: DeleteOptions): Promise<number>;
41
+ count(options: CountOptions): Promise<number>;
42
+ raw<T = unknown>(query: string, _params?: unknown[]): Promise<T>;
43
+ transaction<R>(callback: (trx: DatabaseTransactionAdapter) => Promise<R>): Promise<R>;
44
+ __initCollection(model: string, definition: ModelDefinition, options: {
45
+ mode: "safe" | "diff" | "force";
46
+ }): Promise<void>;
47
+ }
48
+
49
+ declare function mongodbAdapter(client: MongoClient, options: MongoDbOptions): DatabaseAdapter;
50
+
51
+ export { MongoDbAdapter, type MongoDbConfig, type MongoDbOptions, mongodbAdapter };
@@ -0,0 +1,51 @@
1
+ import { MongoClient, ClientSession } from 'mongodb';
2
+ import { DatabaseAdapter, BmSchema, DbHooks, CreateOptions, FindOptions, UpdateOptions, DeleteOptions, CountOptions, DatabaseTransactionAdapter, ModelDefinition } from '@better-media/core';
3
+
4
+ interface MongoDbConfig {
5
+ /** MongoDB database name */
6
+ databaseName: string;
7
+ }
8
+
9
+ interface MongoDbOptions {
10
+ config: MongoDbConfig;
11
+ schema: BmSchema;
12
+ hooks?: DbHooks;
13
+ session?: ClientSession;
14
+ }
15
+ /**
16
+ * MongoDB database adapter.
17
+ */
18
+ declare class MongoDbAdapter implements DatabaseAdapter {
19
+ private readonly client;
20
+ private readonly db;
21
+ private readonly schema;
22
+ private readonly hooks?;
23
+ private readonly session?;
24
+ constructor(client: MongoClient, options: MongoDbOptions);
25
+ private getCollection;
26
+ private getModelFields;
27
+ private getModelDefinition;
28
+ private getHookContext;
29
+ private buildFilter;
30
+ private escapeRegex;
31
+ private mapToMongo;
32
+ private mapFromMongo;
33
+ create<T extends Record<string, unknown>>(options: CreateOptions<T>): Promise<T>;
34
+ private applyPopulate;
35
+ findOne<T extends Record<string, unknown>>(options: FindOptions<T>): Promise<T | null>;
36
+ findMany<T extends Record<string, unknown>>(options: FindOptions<T>): Promise<T[]>;
37
+ update<T extends Record<string, unknown>>(options: UpdateOptions<T>): Promise<T | null>;
38
+ updateMany<T extends Record<string, unknown>>(options: UpdateOptions<T>): Promise<number>;
39
+ delete(options: DeleteOptions): Promise<void>;
40
+ deleteMany(options: DeleteOptions): Promise<number>;
41
+ count(options: CountOptions): Promise<number>;
42
+ raw<T = unknown>(query: string, _params?: unknown[]): Promise<T>;
43
+ transaction<R>(callback: (trx: DatabaseTransactionAdapter) => Promise<R>): Promise<R>;
44
+ __initCollection(model: string, definition: ModelDefinition, options: {
45
+ mode: "safe" | "diff" | "force";
46
+ }): Promise<void>;
47
+ }
48
+
49
+ declare function mongodbAdapter(client: MongoClient, options: MongoDbOptions): DatabaseAdapter;
50
+
51
+ export { MongoDbAdapter, type MongoDbConfig, type MongoDbOptions, mongodbAdapter };
package/dist/index.js ADDED
@@ -0,0 +1,391 @@
1
+ 'use strict';
2
+
3
+ var core = require('@better-media/core');
4
+
5
+ // src/mongodb-db.adapter.ts
6
+ var MongoDbAdapter = class _MongoDbAdapter {
7
+ client;
8
+ db;
9
+ schema;
10
+ hooks;
11
+ session;
12
+ constructor(client, options) {
13
+ this.client = client;
14
+ this.db = this.client.db(options.config.databaseName);
15
+ this.schema = options.schema;
16
+ this.hooks = options.hooks;
17
+ this.session = options.session;
18
+ }
19
+ getCollection(model) {
20
+ return this.db.collection(model);
21
+ }
22
+ getModelFields(model) {
23
+ return this.schema[model]?.fields ?? {};
24
+ }
25
+ getModelDefinition(model) {
26
+ return this.schema[model];
27
+ }
28
+ getHookContext(model, trx) {
29
+ return {
30
+ model,
31
+ adapter: this,
32
+ transaction: trx
33
+ };
34
+ }
35
+ buildFilter(where, model, options) {
36
+ let filter = {};
37
+ const definition = model ? this.getModelDefinition(model) : void 0;
38
+ if (definition?.softDelete && !options?.withDeleted) {
39
+ filter = { deletedAt: null };
40
+ }
41
+ if (!where || where.length === 0) return filter;
42
+ let whereFilter = {};
43
+ for (let i = 0; i < where.length; i++) {
44
+ const condition = where[i];
45
+ const connector = i > 0 ? where[i - 1]?.connector ?? "AND" : "AND";
46
+ const field = condition.field === "id" ? "_id" : condition.field;
47
+ const value = condition.value;
48
+ const conditionFilter = {};
49
+ switch (condition.operator) {
50
+ case "!=":
51
+ conditionFilter[field] = { $ne: value };
52
+ break;
53
+ case "<":
54
+ conditionFilter[field] = { $lt: value };
55
+ break;
56
+ case "<=":
57
+ conditionFilter[field] = { $lte: value };
58
+ break;
59
+ case ">":
60
+ conditionFilter[field] = { $gt: value };
61
+ break;
62
+ case ">=":
63
+ conditionFilter[field] = { $gte: value };
64
+ break;
65
+ case "in":
66
+ conditionFilter[field] = { $in: value };
67
+ break;
68
+ case "not_in":
69
+ conditionFilter[field] = { $nin: value };
70
+ break;
71
+ case "starts_with":
72
+ conditionFilter[field] = {
73
+ $regex: new RegExp(`^${this.escapeRegex(String(value))}`, "i")
74
+ };
75
+ break;
76
+ case "ends_with":
77
+ conditionFilter[field] = {
78
+ $regex: new RegExp(`${this.escapeRegex(String(value))}$`, "i")
79
+ };
80
+ break;
81
+ case "contains":
82
+ case "like":
83
+ conditionFilter[field] = { $regex: new RegExp(this.escapeRegex(String(value)), "i") };
84
+ break;
85
+ case "=":
86
+ default:
87
+ conditionFilter[field] = value;
88
+ break;
89
+ }
90
+ if (i === 0) {
91
+ whereFilter = conditionFilter;
92
+ } else if (connector === "OR") {
93
+ whereFilter = {
94
+ $or: [whereFilter, conditionFilter]
95
+ };
96
+ } else {
97
+ whereFilter = {
98
+ $and: [whereFilter, conditionFilter]
99
+ };
100
+ }
101
+ }
102
+ if (Object.keys(filter).length > 0 && Object.keys(whereFilter).length > 0) {
103
+ return { $and: [filter, whereFilter] };
104
+ }
105
+ return Object.keys(whereFilter).length > 0 ? whereFilter : filter;
106
+ }
107
+ escapeRegex(string) {
108
+ return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
109
+ }
110
+ mapToMongo(data) {
111
+ const { id, ...rest } = data;
112
+ return id !== void 0 ? { _id: id, ...rest } : rest;
113
+ }
114
+ mapFromMongo(doc) {
115
+ if (!doc) return null;
116
+ const { _id, ...rest } = doc;
117
+ return { id: typeof _id === "string" ? _id : String(_id), ...rest };
118
+ }
119
+ async create(options) {
120
+ const fields = this.getModelFields(options.model);
121
+ const collection = this.getCollection(options.model);
122
+ const context = this.getHookContext(options.model);
123
+ let dataToInsert = options.data;
124
+ dataToInsert = await core.runHooks.beforeCreate(this.hooks, dataToInsert, context);
125
+ const serializedData = core.serializeData(fields, dataToInsert);
126
+ const mongoDoc = this.mapToMongo(serializedData);
127
+ await collection.insertOne(mongoDoc, { session: this.session });
128
+ const resultRecord = core.deserializeData(fields, this.mapFromMongo(mongoDoc));
129
+ await core.runHooks.afterCreate(this.hooks, resultRecord, context);
130
+ return resultRecord;
131
+ }
132
+ async applyPopulate(model, pipeline, populate) {
133
+ const sortedPopulate = [...populate].sort((a, b) => a.split(".").length - b.split(".").length);
134
+ for (const path of sortedPopulate) {
135
+ const parts = path.split(".");
136
+ let currentModel = model;
137
+ let currentPath = "";
138
+ for (let i = 0; i < parts.length; i++) {
139
+ const part = parts[i];
140
+ const fieldDef = this.schema[currentModel]?.fields[part];
141
+ if (fieldDef?.references) {
142
+ const localField = currentPath ? `${currentPath}.${part}` : part;
143
+ const asField = localField;
144
+ pipeline.push({
145
+ $lookup: {
146
+ from: fieldDef.references.model,
147
+ localField,
148
+ foreignField: fieldDef.references.field === "id" ? "_id" : fieldDef.references.field,
149
+ as: asField
150
+ }
151
+ });
152
+ pipeline.push({ $unwind: { path: `$${asField}`, preserveNullAndEmptyArrays: true } });
153
+ currentModel = fieldDef.references.model;
154
+ currentPath = asField;
155
+ } else {
156
+ break;
157
+ }
158
+ }
159
+ }
160
+ }
161
+ async findOne(options) {
162
+ const fields = this.getModelFields(options.model);
163
+ const collection = this.getCollection(options.model);
164
+ const filter = this.buildFilter(options.where, options.model, {
165
+ withDeleted: options.withDeleted
166
+ });
167
+ if (options.populate && options.populate.length > 0) {
168
+ const pipeline = [{ $match: filter }];
169
+ await this.applyPopulate(options.model, pipeline, options.populate);
170
+ const results = await collection.aggregate(pipeline, { session: this.session }).toArray();
171
+ const doc2 = results[0];
172
+ if (!doc2) return null;
173
+ return core.deserializeData(fields, this.mapFromMongo(doc2));
174
+ }
175
+ const projection = {};
176
+ if (options.select) {
177
+ for (const field of options.select) projection[field === "id" ? "_id" : field] = 1;
178
+ }
179
+ const doc = await collection.findOne(filter, {
180
+ projection: Object.keys(projection).length > 0 ? projection : void 0,
181
+ session: this.session
182
+ });
183
+ if (!doc) return null;
184
+ return core.deserializeData(fields, this.mapFromMongo(doc));
185
+ }
186
+ async findMany(options) {
187
+ const fields = this.getModelFields(options.model);
188
+ const collection = this.getCollection(options.model);
189
+ const filter = this.buildFilter(options.where, options.model, {
190
+ withDeleted: options.withDeleted
191
+ });
192
+ if (options.populate && options.populate.length > 0) {
193
+ const pipeline = [{ $match: filter }];
194
+ await this.applyPopulate(options.model, pipeline, options.populate);
195
+ if (options.sortBy) {
196
+ const field = options.sortBy.field === "id" ? "_id" : options.sortBy.field;
197
+ pipeline.push({ $sort: { [field]: options.sortBy.direction === "asc" ? 1 : -1 } });
198
+ }
199
+ if (options.offset) pipeline.push({ $skip: options.offset });
200
+ if (options.limit) pipeline.push({ $limit: options.limit });
201
+ const docs2 = await collection.aggregate(pipeline, { session: this.session }).toArray();
202
+ return docs2.map((doc) => core.deserializeData(fields, this.mapFromMongo(doc)));
203
+ }
204
+ let cursor = collection.find(filter, { session: this.session });
205
+ if (options.select) {
206
+ const projection = {};
207
+ for (const field of options.select) projection[field === "id" ? "_id" : field] = 1;
208
+ cursor = cursor.project(projection);
209
+ }
210
+ if (options.sortBy) {
211
+ const field = options.sortBy.field === "id" ? "_id" : options.sortBy.field;
212
+ cursor = cursor.sort({ [field]: options.sortBy.direction === "asc" ? 1 : -1 });
213
+ }
214
+ if (options.offset) cursor = cursor.skip(options.offset);
215
+ if (options.limit) cursor = cursor.limit(options.limit);
216
+ const docs = await cursor.toArray();
217
+ return docs.map((doc) => core.deserializeData(fields, this.mapFromMongo(doc)));
218
+ }
219
+ async update(options) {
220
+ const fields = this.getModelFields(options.model);
221
+ const collection = this.getCollection(options.model);
222
+ const context = this.getHookContext(options.model);
223
+ const filter = this.buildFilter(options.where, options.model);
224
+ const doc = await collection.findOne(filter, { session: this.session });
225
+ if (!doc) return null;
226
+ const currentRecord = core.deserializeData(fields, this.mapFromMongo(doc));
227
+ let updatedData = { ...currentRecord, ...options.update };
228
+ updatedData = await core.runHooks.beforeUpdate(this.hooks, updatedData, context);
229
+ const serializedUpdate = core.serializeData(fields, options.update);
230
+ const mongoUpdate = this.mapToMongo(serializedUpdate);
231
+ delete mongoUpdate._id;
232
+ await collection.updateOne(filter, { $set: mongoUpdate }, { session: this.session });
233
+ const resultRecord = core.deserializeData(fields, core.serializeData(fields, updatedData));
234
+ await core.runHooks.afterUpdate(this.hooks, resultRecord, context);
235
+ return resultRecord;
236
+ }
237
+ async updateMany(options) {
238
+ const fields = this.getModelFields(options.model);
239
+ const collection = this.getCollection(options.model);
240
+ const filter = this.buildFilter(options.where, options.model);
241
+ const serializedUpdate = core.serializeData(fields, options.update);
242
+ const mongoUpdate = this.mapToMongo(serializedUpdate);
243
+ delete mongoUpdate._id;
244
+ const result = await collection.updateMany(
245
+ filter,
246
+ { $set: mongoUpdate },
247
+ { session: this.session }
248
+ );
249
+ return result.modifiedCount;
250
+ }
251
+ async delete(options) {
252
+ const collection = this.getCollection(options.model);
253
+ const context = this.getHookContext(options.model);
254
+ const definition = this.getModelDefinition(options.model);
255
+ await core.runHooks.beforeDelete(this.hooks, options.where, context);
256
+ const filter = this.buildFilter(options.where, options.model);
257
+ if (definition?.softDelete) {
258
+ await this.updateMany({
259
+ model: options.model,
260
+ where: options.where,
261
+ update: { deletedAt: /* @__PURE__ */ new Date() }
262
+ });
263
+ } else {
264
+ if (options.where?.length === 1 && options.where[0]?.field === "id" && options.where[0]?.operator !== "!=") {
265
+ await collection.deleteOne(filter, { session: this.session });
266
+ } else {
267
+ await collection.deleteMany(filter, { session: this.session });
268
+ }
269
+ }
270
+ await core.runHooks.afterDelete(this.hooks, options.where, context);
271
+ }
272
+ async deleteMany(options) {
273
+ const collection = this.getCollection(options.model);
274
+ const filter = this.buildFilter(options.where, options.model);
275
+ const definition = this.getModelDefinition(options.model);
276
+ if (definition?.softDelete) {
277
+ return await this.updateMany({
278
+ model: options.model,
279
+ where: options.where,
280
+ update: { deletedAt: /* @__PURE__ */ new Date() }
281
+ });
282
+ }
283
+ const result = await collection.deleteMany(filter, { session: this.session });
284
+ return result.deletedCount;
285
+ }
286
+ async count(options) {
287
+ const collection = this.getCollection(options.model);
288
+ const filter = this.buildFilter(options.where, options.model);
289
+ return collection.countDocuments(filter, { session: this.session });
290
+ }
291
+ async raw(query, _params) {
292
+ if (query.startsWith("{")) {
293
+ return await this.db.command(
294
+ JSON.parse(query)
295
+ );
296
+ }
297
+ throw new Error("MongoDB 'raw' requires a JSON command string.");
298
+ }
299
+ async transaction(callback) {
300
+ const session = this.client.startSession();
301
+ try {
302
+ return await session.withTransaction(async () => {
303
+ const trxAdapter = new _MongoDbAdapter(this.client, {
304
+ config: { databaseName: this.db.databaseName },
305
+ schema: this.schema,
306
+ hooks: this.hooks,
307
+ session
308
+ });
309
+ return await callback(trxAdapter);
310
+ });
311
+ } finally {
312
+ await session.endSession();
313
+ }
314
+ }
315
+ async __initCollection(model, definition, options) {
316
+ if (options.mode === "force") {
317
+ await this.getCollection(model).drop().catch(() => {
318
+ });
319
+ }
320
+ const collections = await this.db.listCollections({ name: model }).toArray();
321
+ if (collections.length === 0) {
322
+ await this.db.createCollection(model);
323
+ }
324
+ const collection = this.getCollection(model);
325
+ const existingIndexes = await collection.listIndexes().toArray();
326
+ const existingIndexNames = new Set(existingIndexes.map((idx) => idx.name));
327
+ for (const [fieldName, fieldDef] of Object.entries(definition.fields)) {
328
+ if (fieldDef?.unique && !fieldDef?.primaryKey) {
329
+ const indexName = `${fieldName}_1`;
330
+ if (!existingIndexNames.has(indexName)) {
331
+ await collection.createIndex({ [fieldName]: 1 }, { unique: true });
332
+ }
333
+ }
334
+ if (fieldDef?.references) {
335
+ const indexName = `${fieldName}_1`;
336
+ if (!existingIndexNames.has(indexName)) {
337
+ await collection.createIndex({ [fieldName]: 1 });
338
+ }
339
+ }
340
+ }
341
+ if (definition.indexes) {
342
+ for (const index of definition.indexes) {
343
+ const indexSpec = {};
344
+ for (const field of index.fields) {
345
+ indexSpec[field === "id" ? "_id" : field] = 1;
346
+ }
347
+ const indexName = `idx_${index.fields.join("_")}`;
348
+ if (!existingIndexNames.has(indexName)) {
349
+ await collection.createIndex(indexSpec, {
350
+ name: indexName,
351
+ unique: !!index.unique
352
+ });
353
+ }
354
+ }
355
+ }
356
+ }
357
+ };
358
+
359
+ // src/index.ts
360
+ function mongodbAdapter(client, options) {
361
+ const adapter = new MongoDbAdapter(client, options);
362
+ return new Proxy(adapter, {
363
+ get(target, prop, receiver) {
364
+ if (prop === "get") {
365
+ return async (key) => {
366
+ const res = await adapter.findOne({
367
+ model: "legacy",
368
+ where: [{ field: "id", value: key }]
369
+ });
370
+ return res;
371
+ };
372
+ }
373
+ if (prop === "put") {
374
+ return async (key, data) => {
375
+ await adapter.create({ model: "legacy", data: { id: key, ...data } });
376
+ };
377
+ }
378
+ if (prop === "delete") {
379
+ return async (key) => {
380
+ await adapter.delete({ model: "legacy", where: [{ field: "id", value: key }] });
381
+ };
382
+ }
383
+ return Reflect.get(target, prop, receiver);
384
+ }
385
+ });
386
+ }
387
+
388
+ exports.MongoDbAdapter = MongoDbAdapter;
389
+ exports.mongodbAdapter = mongodbAdapter;
390
+ //# sourceMappingURL=index.js.map
391
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/mongodb-db.adapter.ts","../src/index.ts"],"names":["runHooks","serializeData","deserializeData","doc","docs"],"mappings":"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","file":"index.js","sourcesContent":["import { MongoClient, Db, Collection, Document, Filter, ClientSession } from \"mongodb\";\nimport type {\n DatabaseAdapter,\n DatabaseTransactionAdapter,\n WhereClause,\n CreateOptions,\n FindOptions,\n UpdateOptions,\n DeleteOptions,\n CountOptions,\n} from \"@better-media/core\";\nimport type {\n FieldType,\n BmSchema,\n DbHooks,\n DatabaseHookContext,\n ModelDefinition,\n} from \"@better-media/core\";\nimport { serializeData, deserializeData, runHooks } from \"@better-media/core\";\nimport type { MongoDbConfig } from \"./mongodb-db-config.interface\";\n\nexport interface MongoDbOptions {\n config: MongoDbConfig;\n schema: BmSchema;\n hooks?: DbHooks;\n session?: ClientSession;\n}\n\n/**\n * MongoDB database adapter.\n */\nexport class MongoDbAdapter implements DatabaseAdapter {\n private readonly client: MongoClient;\n private readonly db: Db;\n private readonly schema: BmSchema;\n private readonly hooks?: DbHooks;\n private readonly session?: ClientSession;\n\n constructor(client: MongoClient, options: MongoDbOptions) {\n this.client = client;\n this.db = this.client.db(options.config.databaseName);\n this.schema = options.schema;\n this.hooks = options.hooks;\n this.session = options.session;\n }\n\n private getCollection(model: string): Collection<Document> {\n return this.db.collection(model);\n }\n\n private getModelFields(model: string): Record<string, { type: FieldType }> {\n return this.schema[model]?.fields ?? {};\n }\n\n private getModelDefinition(model: string): ModelDefinition | undefined {\n return this.schema[model];\n }\n\n private getHookContext(model: string, trx?: DatabaseTransactionAdapter): DatabaseHookContext {\n return {\n model,\n adapter: this,\n transaction: trx,\n };\n }\n\n private buildFilter(\n where?: WhereClause,\n model?: string,\n options?: { withDeleted?: boolean }\n ): Filter<Document> {\n let filter: Filter<Document> = {};\n const definition = model ? this.getModelDefinition(model) : undefined;\n\n // Soft delete filtering\n if (definition?.softDelete && !options?.withDeleted) {\n filter = { deletedAt: null };\n }\n\n if (!where || where.length === 0) return filter;\n\n let whereFilter: Filter<Document> = {};\n\n for (let i = 0; i < where.length; i++) {\n const condition = where[i]!;\n const connector = i > 0 ? (where[i - 1]?.connector ?? \"AND\") : \"AND\";\n const field = condition.field === \"id\" ? \"_id\" : condition.field;\n const value = condition.value;\n const conditionFilter: Record<string, unknown> = {};\n\n switch (condition.operator) {\n case \"!=\":\n conditionFilter[field] = { $ne: value };\n break;\n case \"<\":\n conditionFilter[field] = { $lt: value };\n break;\n case \"<=\":\n conditionFilter[field] = { $lte: value };\n break;\n case \">\":\n conditionFilter[field] = { $gt: value };\n break;\n case \">=\":\n conditionFilter[field] = { $gte: value };\n break;\n case \"in\":\n conditionFilter[field] = { $in: value as unknown[] };\n break;\n case \"not_in\":\n conditionFilter[field] = { $nin: value as unknown[] };\n break;\n case \"starts_with\":\n conditionFilter[field] = {\n $regex: new RegExp(`^${this.escapeRegex(String(value))}`, \"i\"),\n };\n break;\n case \"ends_with\":\n conditionFilter[field] = {\n $regex: new RegExp(`${this.escapeRegex(String(value))}$`, \"i\"),\n };\n break;\n case \"contains\":\n case \"like\":\n conditionFilter[field] = { $regex: new RegExp(this.escapeRegex(String(value)), \"i\") };\n break;\n case \"=\":\n default:\n conditionFilter[field] = value;\n break;\n }\n\n if (i === 0) {\n whereFilter = conditionFilter;\n } else if (connector === \"OR\") {\n whereFilter = {\n $or: [whereFilter as Record<string, unknown>, conditionFilter],\n } as Filter<Document>;\n } else {\n whereFilter = {\n $and: [whereFilter as Record<string, unknown>, conditionFilter],\n } as Filter<Document>;\n }\n }\n\n // Combine with soft delete filter\n if (Object.keys(filter).length > 0 && Object.keys(whereFilter).length > 0) {\n return { $and: [filter, whereFilter] } as Filter<Document>;\n }\n return Object.keys(whereFilter).length > 0 ? whereFilter : filter;\n }\n\n private escapeRegex(string: string): string {\n return string.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n }\n\n private mapToMongo(data: Record<string, unknown>): Document {\n const { id, ...rest } = data;\n return id !== undefined ? { _id: id, ...rest } : (rest as Document);\n }\n\n private mapFromMongo(doc: Document | null): Record<string, unknown> | null {\n if (!doc) return null;\n const { _id, ...rest } = doc as { _id: unknown } & Record<string, unknown>;\n return { id: typeof _id === \"string\" ? _id : String(_id), ...rest };\n }\n\n async create<T extends Record<string, unknown>>(options: CreateOptions<T>): Promise<T> {\n const fields = this.getModelFields(options.model);\n const collection = this.getCollection(options.model);\n const context = this.getHookContext(options.model);\n\n let dataToInsert = options.data as Record<string, unknown>;\n dataToInsert = await runHooks.beforeCreate(this.hooks, dataToInsert, context);\n\n const serializedData = serializeData(fields, dataToInsert);\n const mongoDoc = this.mapToMongo(serializedData);\n\n await collection.insertOne(mongoDoc, { session: this.session });\n const resultRecord = deserializeData(fields, this.mapFromMongo(mongoDoc)!) as T;\n\n await runHooks.afterCreate(this.hooks, resultRecord as Record<string, unknown>, context);\n return resultRecord;\n }\n\n private async applyPopulate(\n model: string,\n pipeline: Record<string, unknown>[],\n populate: string[]\n ): Promise<void> {\n const sortedPopulate = [...populate].sort((a, b) => a.split(\".\").length - b.split(\".\").length);\n\n for (const path of sortedPopulate) {\n const parts = path.split(\".\");\n let currentModel = model;\n let currentPath = \"\";\n\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i]!;\n const fieldDef = this.schema[currentModel]?.fields[part];\n\n if (fieldDef?.references) {\n const localField = currentPath ? `${currentPath}.${part}` : part;\n const asField = localField;\n\n pipeline.push({\n $lookup: {\n from: fieldDef.references.model,\n localField: localField,\n foreignField: fieldDef.references.field === \"id\" ? \"_id\" : fieldDef.references.field,\n as: asField,\n },\n });\n pipeline.push({ $unwind: { path: `$${asField}`, preserveNullAndEmptyArrays: true } });\n\n currentModel = fieldDef.references.model;\n currentPath = asField;\n } else {\n // If a part doesn't have references, we can't populate further deep from here\n break;\n }\n }\n }\n }\n\n async findOne<T extends Record<string, unknown>>(options: FindOptions<T>): Promise<T | null> {\n const fields = this.getModelFields(options.model);\n const collection = this.getCollection(options.model);\n const filter = this.buildFilter(options.where, options.model, {\n withDeleted: options.withDeleted,\n });\n\n if (options.populate && options.populate.length > 0) {\n const pipeline: Record<string, unknown>[] = [{ $match: filter }];\n await this.applyPopulate(options.model, pipeline, options.populate);\n\n const results = await (\n collection as unknown as {\n aggregate: (p: unknown[], o: unknown) => { toArray: () => Promise<Document[]> };\n }\n )\n .aggregate(pipeline, { session: this.session })\n .toArray();\n const doc = results[0];\n if (!doc) return null;\n return deserializeData(fields, this.mapFromMongo(doc)!) as T;\n }\n\n const projection: Record<string, 1> = {};\n if (options.select) {\n for (const field of options.select) projection[field === \"id\" ? \"_id\" : field] = 1;\n }\n\n const doc = await collection.findOne(filter, {\n projection: Object.keys(projection).length > 0 ? projection : undefined,\n session: this.session,\n });\n\n if (!doc) return null;\n return deserializeData(fields, this.mapFromMongo(doc)!) as T;\n }\n\n async findMany<T extends Record<string, unknown>>(options: FindOptions<T>): Promise<T[]> {\n const fields = this.getModelFields(options.model);\n const collection = this.getCollection(options.model);\n const filter = this.buildFilter(options.where, options.model, {\n withDeleted: options.withDeleted,\n });\n\n if (options.populate && options.populate.length > 0) {\n // Aggregate population\n const pipeline: Record<string, unknown>[] = [{ $match: filter }];\n await this.applyPopulate(options.model, pipeline, options.populate);\n\n if (options.sortBy) {\n const field = options.sortBy.field === \"id\" ? \"_id\" : options.sortBy.field;\n pipeline.push({ $sort: { [field]: options.sortBy.direction === \"asc\" ? 1 : -1 } });\n }\n if (options.offset) pipeline.push({ $skip: options.offset });\n if (options.limit) pipeline.push({ $limit: options.limit });\n\n const docs = await (\n collection as unknown as {\n aggregate: (p: unknown[], o: unknown) => { toArray: () => Promise<Document[]> };\n }\n )\n .aggregate(pipeline, { session: this.session })\n .toArray();\n return docs.map((doc: Document) => deserializeData(fields, this.mapFromMongo(doc)!)) as T[];\n }\n\n let cursor = collection.find(filter, { session: this.session });\n if (options.select) {\n const projection: Record<string, 1> = {};\n for (const field of options.select) projection[field === \"id\" ? \"_id\" : field] = 1;\n cursor = cursor.project(projection);\n }\n if (options.sortBy) {\n const field = options.sortBy.field === \"id\" ? \"_id\" : options.sortBy.field;\n cursor = cursor.sort({ [field]: options.sortBy.direction === \"asc\" ? 1 : -1 });\n }\n if (options.offset) cursor = cursor.skip(options.offset);\n if (options.limit) cursor = cursor.limit(options.limit);\n\n const docs = await cursor.toArray();\n return docs.map((doc: Document) => deserializeData(fields, this.mapFromMongo(doc)!)) as T[];\n }\n\n async update<T extends Record<string, unknown>>(options: UpdateOptions<T>): Promise<T | null> {\n const fields = this.getModelFields(options.model);\n const collection = this.getCollection(options.model);\n const context = this.getHookContext(options.model);\n\n const filter = this.buildFilter(options.where, options.model);\n const doc = await collection.findOne(filter, { session: this.session });\n if (!doc) return null;\n\n const currentRecord = deserializeData(fields, this.mapFromMongo(doc)!);\n let updatedData = { ...currentRecord, ...(options.update as Record<string, unknown>) };\n updatedData = await runHooks.beforeUpdate(this.hooks, updatedData, context);\n\n const serializedUpdate = serializeData(fields, options.update as Record<string, unknown>);\n const mongoUpdate = this.mapToMongo(serializedUpdate);\n delete mongoUpdate._id;\n\n await collection.updateOne(filter, { $set: mongoUpdate }, { session: this.session });\n const resultRecord = deserializeData(fields, serializeData(fields, updatedData)) as T;\n\n await runHooks.afterUpdate(this.hooks, resultRecord as Record<string, unknown>, context);\n return resultRecord;\n }\n\n async updateMany<T extends Record<string, unknown>>(options: UpdateOptions<T>): Promise<number> {\n const fields = this.getModelFields(options.model);\n const collection = this.getCollection(options.model);\n const filter = this.buildFilter(options.where, options.model);\n\n const serializedUpdate = serializeData(fields, options.update as Record<string, unknown>);\n const mongoUpdate = this.mapToMongo(serializedUpdate);\n delete mongoUpdate._id;\n\n const result = await collection.updateMany(\n filter,\n { $set: mongoUpdate },\n { session: this.session }\n );\n return result.modifiedCount;\n }\n\n async delete(options: DeleteOptions): Promise<void> {\n const collection = this.getCollection(options.model);\n const context = this.getHookContext(options.model);\n const definition = this.getModelDefinition(options.model);\n\n await runHooks.beforeDelete(this.hooks, options.where, context);\n\n const filter = this.buildFilter(options.where, options.model);\n\n if (definition?.softDelete) {\n await this.updateMany({\n model: options.model,\n where: options.where,\n update: { deletedAt: new Date() } as unknown as Record<string, unknown>,\n });\n } else {\n if (\n options.where?.length === 1 &&\n options.where[0]?.field === \"id\" &&\n options.where[0]?.operator !== \"!=\"\n ) {\n await collection.deleteOne(filter, { session: this.session });\n } else {\n await collection.deleteMany(filter, { session: this.session });\n }\n }\n\n await runHooks.afterDelete(this.hooks, options.where, context);\n }\n\n async deleteMany(options: DeleteOptions): Promise<number> {\n const collection = this.getCollection(options.model);\n const filter = this.buildFilter(options.where, options.model);\n const definition = this.getModelDefinition(options.model);\n\n if (definition?.softDelete) {\n return await this.updateMany({\n model: options.model,\n where: options.where,\n update: { deletedAt: new Date() } as unknown as Record<string, unknown>,\n });\n }\n\n const result = await collection.deleteMany(filter, { session: this.session });\n return result.deletedCount;\n }\n\n async count(options: CountOptions): Promise<number> {\n const collection = this.getCollection(options.model);\n const filter = this.buildFilter(options.where, options.model);\n return collection.countDocuments(filter, { session: this.session });\n }\n\n async raw<T = unknown>(query: string, _params?: unknown[]): Promise<T> {\n // For Mongo, 'raw' can mean a direct command or aggregate\n if (query.startsWith(\"{\")) {\n return (await (this.db as unknown as { command: (c: unknown) => Promise<unknown> }).command(\n JSON.parse(query)\n )) as unknown as T;\n }\n throw new Error(\"MongoDB 'raw' requires a JSON command string.\");\n }\n\n async transaction<R>(callback: (trx: DatabaseTransactionAdapter) => Promise<R>): Promise<R> {\n const session = this.client.startSession();\n try {\n return (await session.withTransaction(async () => {\n const trxAdapter = new MongoDbAdapter(this.client, {\n config: { databaseName: this.db.databaseName },\n schema: this.schema,\n hooks: this.hooks,\n session,\n });\n return await callback(trxAdapter);\n })) as R;\n } finally {\n await session.endSession();\n }\n }\n\n async __initCollection(\n model: string,\n definition: ModelDefinition,\n options: { mode: \"safe\" | \"diff\" | \"force\" }\n ): Promise<void> {\n if (options.mode === \"force\") {\n await (\n this.getCollection(model) as unknown as {\n drop: () => Promise<void>;\n }\n )\n .drop()\n .catch(() => {});\n }\n\n const collections = await this.db.listCollections({ name: model }).toArray();\n if (collections.length === 0) {\n await this.db.createCollection(model);\n }\n\n const collection = this.getCollection(model);\n const existingIndexes = await (\n collection as unknown as {\n listIndexes: () => { toArray: () => Promise<{ name: string }[]> };\n }\n )\n .listIndexes()\n .toArray();\n const existingIndexNames = new Set(existingIndexes.map((idx) => idx.name));\n\n // Fields-based indexes (implicitly unique/foreign key)\n for (const [fieldName, fieldDef] of Object.entries(definition.fields)) {\n if (fieldDef?.unique && !fieldDef?.primaryKey) {\n const indexName = `${fieldName}_1`;\n if (!existingIndexNames.has(indexName)) {\n await collection.createIndex({ [fieldName]: 1 }, { unique: true });\n }\n }\n if (fieldDef?.references) {\n const indexName = `${fieldName}_1`;\n if (!existingIndexNames.has(indexName)) {\n await collection.createIndex({ [fieldName]: 1 });\n }\n }\n }\n\n // Explicit indexes from definition.indexes\n if (definition.indexes) {\n for (const index of definition.indexes) {\n const indexSpec: Record<string, 1> = {};\n for (const field of index.fields) {\n indexSpec[field === \"id\" ? \"_id\" : field] = 1;\n }\n\n const indexName = `idx_${index.fields.join(\"_\")}`;\n if (!existingIndexNames.has(indexName)) {\n await (\n collection as unknown as {\n createIndex: (s: unknown, o: unknown) => Promise<void>;\n }\n ).createIndex(indexSpec, {\n name: indexName,\n unique: !!index.unique,\n });\n }\n }\n }\n }\n}\n","export * from \"./mongodb-db-config.interface\";\nexport * from \"./mongodb-db.adapter\";\n\nimport { MongoDbAdapter, type MongoDbOptions } from \"./mongodb-db.adapter\";\nimport type { DatabaseAdapter } from \"@better-media/core\";\nimport type { MongoClient } from \"mongodb\";\n\nexport function mongodbAdapter(client: MongoClient, options: MongoDbOptions): DatabaseAdapter {\n const adapter = new MongoDbAdapter(client, options);\n return new Proxy(adapter, {\n get(target, prop, receiver) {\n if (prop === \"get\") {\n return async (key: string) => {\n const res = await adapter.findOne({\n model: \"legacy\",\n where: [{ field: \"id\", value: key }],\n });\n return res;\n };\n }\n if (prop === \"put\") {\n return async (key: string, data: Record<string, unknown>) => {\n await adapter.create({ model: \"legacy\", data: { id: key, ...data } });\n };\n }\n if (prop === \"delete\") {\n return async (key: string) => {\n await adapter.delete({ model: \"legacy\", where: [{ field: \"id\", value: key }] });\n };\n }\n return Reflect.get(target, prop, receiver);\n },\n });\n}\n"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,388 @@
1
+ import { runHooks, serializeData, deserializeData } from '@better-media/core';
2
+
3
+ // src/mongodb-db.adapter.ts
4
+ var MongoDbAdapter = class _MongoDbAdapter {
5
+ client;
6
+ db;
7
+ schema;
8
+ hooks;
9
+ session;
10
+ constructor(client, options) {
11
+ this.client = client;
12
+ this.db = this.client.db(options.config.databaseName);
13
+ this.schema = options.schema;
14
+ this.hooks = options.hooks;
15
+ this.session = options.session;
16
+ }
17
+ getCollection(model) {
18
+ return this.db.collection(model);
19
+ }
20
+ getModelFields(model) {
21
+ return this.schema[model]?.fields ?? {};
22
+ }
23
+ getModelDefinition(model) {
24
+ return this.schema[model];
25
+ }
26
+ getHookContext(model, trx) {
27
+ return {
28
+ model,
29
+ adapter: this,
30
+ transaction: trx
31
+ };
32
+ }
33
+ buildFilter(where, model, options) {
34
+ let filter = {};
35
+ const definition = model ? this.getModelDefinition(model) : void 0;
36
+ if (definition?.softDelete && !options?.withDeleted) {
37
+ filter = { deletedAt: null };
38
+ }
39
+ if (!where || where.length === 0) return filter;
40
+ let whereFilter = {};
41
+ for (let i = 0; i < where.length; i++) {
42
+ const condition = where[i];
43
+ const connector = i > 0 ? where[i - 1]?.connector ?? "AND" : "AND";
44
+ const field = condition.field === "id" ? "_id" : condition.field;
45
+ const value = condition.value;
46
+ const conditionFilter = {};
47
+ switch (condition.operator) {
48
+ case "!=":
49
+ conditionFilter[field] = { $ne: value };
50
+ break;
51
+ case "<":
52
+ conditionFilter[field] = { $lt: value };
53
+ break;
54
+ case "<=":
55
+ conditionFilter[field] = { $lte: value };
56
+ break;
57
+ case ">":
58
+ conditionFilter[field] = { $gt: value };
59
+ break;
60
+ case ">=":
61
+ conditionFilter[field] = { $gte: value };
62
+ break;
63
+ case "in":
64
+ conditionFilter[field] = { $in: value };
65
+ break;
66
+ case "not_in":
67
+ conditionFilter[field] = { $nin: value };
68
+ break;
69
+ case "starts_with":
70
+ conditionFilter[field] = {
71
+ $regex: new RegExp(`^${this.escapeRegex(String(value))}`, "i")
72
+ };
73
+ break;
74
+ case "ends_with":
75
+ conditionFilter[field] = {
76
+ $regex: new RegExp(`${this.escapeRegex(String(value))}$`, "i")
77
+ };
78
+ break;
79
+ case "contains":
80
+ case "like":
81
+ conditionFilter[field] = { $regex: new RegExp(this.escapeRegex(String(value)), "i") };
82
+ break;
83
+ case "=":
84
+ default:
85
+ conditionFilter[field] = value;
86
+ break;
87
+ }
88
+ if (i === 0) {
89
+ whereFilter = conditionFilter;
90
+ } else if (connector === "OR") {
91
+ whereFilter = {
92
+ $or: [whereFilter, conditionFilter]
93
+ };
94
+ } else {
95
+ whereFilter = {
96
+ $and: [whereFilter, conditionFilter]
97
+ };
98
+ }
99
+ }
100
+ if (Object.keys(filter).length > 0 && Object.keys(whereFilter).length > 0) {
101
+ return { $and: [filter, whereFilter] };
102
+ }
103
+ return Object.keys(whereFilter).length > 0 ? whereFilter : filter;
104
+ }
105
+ escapeRegex(string) {
106
+ return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
107
+ }
108
+ mapToMongo(data) {
109
+ const { id, ...rest } = data;
110
+ return id !== void 0 ? { _id: id, ...rest } : rest;
111
+ }
112
+ mapFromMongo(doc) {
113
+ if (!doc) return null;
114
+ const { _id, ...rest } = doc;
115
+ return { id: typeof _id === "string" ? _id : String(_id), ...rest };
116
+ }
117
+ async create(options) {
118
+ const fields = this.getModelFields(options.model);
119
+ const collection = this.getCollection(options.model);
120
+ const context = this.getHookContext(options.model);
121
+ let dataToInsert = options.data;
122
+ dataToInsert = await runHooks.beforeCreate(this.hooks, dataToInsert, context);
123
+ const serializedData = serializeData(fields, dataToInsert);
124
+ const mongoDoc = this.mapToMongo(serializedData);
125
+ await collection.insertOne(mongoDoc, { session: this.session });
126
+ const resultRecord = deserializeData(fields, this.mapFromMongo(mongoDoc));
127
+ await runHooks.afterCreate(this.hooks, resultRecord, context);
128
+ return resultRecord;
129
+ }
130
+ async applyPopulate(model, pipeline, populate) {
131
+ const sortedPopulate = [...populate].sort((a, b) => a.split(".").length - b.split(".").length);
132
+ for (const path of sortedPopulate) {
133
+ const parts = path.split(".");
134
+ let currentModel = model;
135
+ let currentPath = "";
136
+ for (let i = 0; i < parts.length; i++) {
137
+ const part = parts[i];
138
+ const fieldDef = this.schema[currentModel]?.fields[part];
139
+ if (fieldDef?.references) {
140
+ const localField = currentPath ? `${currentPath}.${part}` : part;
141
+ const asField = localField;
142
+ pipeline.push({
143
+ $lookup: {
144
+ from: fieldDef.references.model,
145
+ localField,
146
+ foreignField: fieldDef.references.field === "id" ? "_id" : fieldDef.references.field,
147
+ as: asField
148
+ }
149
+ });
150
+ pipeline.push({ $unwind: { path: `$${asField}`, preserveNullAndEmptyArrays: true } });
151
+ currentModel = fieldDef.references.model;
152
+ currentPath = asField;
153
+ } else {
154
+ break;
155
+ }
156
+ }
157
+ }
158
+ }
159
+ async findOne(options) {
160
+ const fields = this.getModelFields(options.model);
161
+ const collection = this.getCollection(options.model);
162
+ const filter = this.buildFilter(options.where, options.model, {
163
+ withDeleted: options.withDeleted
164
+ });
165
+ if (options.populate && options.populate.length > 0) {
166
+ const pipeline = [{ $match: filter }];
167
+ await this.applyPopulate(options.model, pipeline, options.populate);
168
+ const results = await collection.aggregate(pipeline, { session: this.session }).toArray();
169
+ const doc2 = results[0];
170
+ if (!doc2) return null;
171
+ return deserializeData(fields, this.mapFromMongo(doc2));
172
+ }
173
+ const projection = {};
174
+ if (options.select) {
175
+ for (const field of options.select) projection[field === "id" ? "_id" : field] = 1;
176
+ }
177
+ const doc = await collection.findOne(filter, {
178
+ projection: Object.keys(projection).length > 0 ? projection : void 0,
179
+ session: this.session
180
+ });
181
+ if (!doc) return null;
182
+ return deserializeData(fields, this.mapFromMongo(doc));
183
+ }
184
+ async findMany(options) {
185
+ const fields = this.getModelFields(options.model);
186
+ const collection = this.getCollection(options.model);
187
+ const filter = this.buildFilter(options.where, options.model, {
188
+ withDeleted: options.withDeleted
189
+ });
190
+ if (options.populate && options.populate.length > 0) {
191
+ const pipeline = [{ $match: filter }];
192
+ await this.applyPopulate(options.model, pipeline, options.populate);
193
+ if (options.sortBy) {
194
+ const field = options.sortBy.field === "id" ? "_id" : options.sortBy.field;
195
+ pipeline.push({ $sort: { [field]: options.sortBy.direction === "asc" ? 1 : -1 } });
196
+ }
197
+ if (options.offset) pipeline.push({ $skip: options.offset });
198
+ if (options.limit) pipeline.push({ $limit: options.limit });
199
+ const docs2 = await collection.aggregate(pipeline, { session: this.session }).toArray();
200
+ return docs2.map((doc) => deserializeData(fields, this.mapFromMongo(doc)));
201
+ }
202
+ let cursor = collection.find(filter, { session: this.session });
203
+ if (options.select) {
204
+ const projection = {};
205
+ for (const field of options.select) projection[field === "id" ? "_id" : field] = 1;
206
+ cursor = cursor.project(projection);
207
+ }
208
+ if (options.sortBy) {
209
+ const field = options.sortBy.field === "id" ? "_id" : options.sortBy.field;
210
+ cursor = cursor.sort({ [field]: options.sortBy.direction === "asc" ? 1 : -1 });
211
+ }
212
+ if (options.offset) cursor = cursor.skip(options.offset);
213
+ if (options.limit) cursor = cursor.limit(options.limit);
214
+ const docs = await cursor.toArray();
215
+ return docs.map((doc) => deserializeData(fields, this.mapFromMongo(doc)));
216
+ }
217
+ async update(options) {
218
+ const fields = this.getModelFields(options.model);
219
+ const collection = this.getCollection(options.model);
220
+ const context = this.getHookContext(options.model);
221
+ const filter = this.buildFilter(options.where, options.model);
222
+ const doc = await collection.findOne(filter, { session: this.session });
223
+ if (!doc) return null;
224
+ const currentRecord = deserializeData(fields, this.mapFromMongo(doc));
225
+ let updatedData = { ...currentRecord, ...options.update };
226
+ updatedData = await runHooks.beforeUpdate(this.hooks, updatedData, context);
227
+ const serializedUpdate = serializeData(fields, options.update);
228
+ const mongoUpdate = this.mapToMongo(serializedUpdate);
229
+ delete mongoUpdate._id;
230
+ await collection.updateOne(filter, { $set: mongoUpdate }, { session: this.session });
231
+ const resultRecord = deserializeData(fields, serializeData(fields, updatedData));
232
+ await runHooks.afterUpdate(this.hooks, resultRecord, context);
233
+ return resultRecord;
234
+ }
235
+ async updateMany(options) {
236
+ const fields = this.getModelFields(options.model);
237
+ const collection = this.getCollection(options.model);
238
+ const filter = this.buildFilter(options.where, options.model);
239
+ const serializedUpdate = serializeData(fields, options.update);
240
+ const mongoUpdate = this.mapToMongo(serializedUpdate);
241
+ delete mongoUpdate._id;
242
+ const result = await collection.updateMany(
243
+ filter,
244
+ { $set: mongoUpdate },
245
+ { session: this.session }
246
+ );
247
+ return result.modifiedCount;
248
+ }
249
+ async delete(options) {
250
+ const collection = this.getCollection(options.model);
251
+ const context = this.getHookContext(options.model);
252
+ const definition = this.getModelDefinition(options.model);
253
+ await runHooks.beforeDelete(this.hooks, options.where, context);
254
+ const filter = this.buildFilter(options.where, options.model);
255
+ if (definition?.softDelete) {
256
+ await this.updateMany({
257
+ model: options.model,
258
+ where: options.where,
259
+ update: { deletedAt: /* @__PURE__ */ new Date() }
260
+ });
261
+ } else {
262
+ if (options.where?.length === 1 && options.where[0]?.field === "id" && options.where[0]?.operator !== "!=") {
263
+ await collection.deleteOne(filter, { session: this.session });
264
+ } else {
265
+ await collection.deleteMany(filter, { session: this.session });
266
+ }
267
+ }
268
+ await runHooks.afterDelete(this.hooks, options.where, context);
269
+ }
270
+ async deleteMany(options) {
271
+ const collection = this.getCollection(options.model);
272
+ const filter = this.buildFilter(options.where, options.model);
273
+ const definition = this.getModelDefinition(options.model);
274
+ if (definition?.softDelete) {
275
+ return await this.updateMany({
276
+ model: options.model,
277
+ where: options.where,
278
+ update: { deletedAt: /* @__PURE__ */ new Date() }
279
+ });
280
+ }
281
+ const result = await collection.deleteMany(filter, { session: this.session });
282
+ return result.deletedCount;
283
+ }
284
+ async count(options) {
285
+ const collection = this.getCollection(options.model);
286
+ const filter = this.buildFilter(options.where, options.model);
287
+ return collection.countDocuments(filter, { session: this.session });
288
+ }
289
+ async raw(query, _params) {
290
+ if (query.startsWith("{")) {
291
+ return await this.db.command(
292
+ JSON.parse(query)
293
+ );
294
+ }
295
+ throw new Error("MongoDB 'raw' requires a JSON command string.");
296
+ }
297
+ async transaction(callback) {
298
+ const session = this.client.startSession();
299
+ try {
300
+ return await session.withTransaction(async () => {
301
+ const trxAdapter = new _MongoDbAdapter(this.client, {
302
+ config: { databaseName: this.db.databaseName },
303
+ schema: this.schema,
304
+ hooks: this.hooks,
305
+ session
306
+ });
307
+ return await callback(trxAdapter);
308
+ });
309
+ } finally {
310
+ await session.endSession();
311
+ }
312
+ }
313
+ async __initCollection(model, definition, options) {
314
+ if (options.mode === "force") {
315
+ await this.getCollection(model).drop().catch(() => {
316
+ });
317
+ }
318
+ const collections = await this.db.listCollections({ name: model }).toArray();
319
+ if (collections.length === 0) {
320
+ await this.db.createCollection(model);
321
+ }
322
+ const collection = this.getCollection(model);
323
+ const existingIndexes = await collection.listIndexes().toArray();
324
+ const existingIndexNames = new Set(existingIndexes.map((idx) => idx.name));
325
+ for (const [fieldName, fieldDef] of Object.entries(definition.fields)) {
326
+ if (fieldDef?.unique && !fieldDef?.primaryKey) {
327
+ const indexName = `${fieldName}_1`;
328
+ if (!existingIndexNames.has(indexName)) {
329
+ await collection.createIndex({ [fieldName]: 1 }, { unique: true });
330
+ }
331
+ }
332
+ if (fieldDef?.references) {
333
+ const indexName = `${fieldName}_1`;
334
+ if (!existingIndexNames.has(indexName)) {
335
+ await collection.createIndex({ [fieldName]: 1 });
336
+ }
337
+ }
338
+ }
339
+ if (definition.indexes) {
340
+ for (const index of definition.indexes) {
341
+ const indexSpec = {};
342
+ for (const field of index.fields) {
343
+ indexSpec[field === "id" ? "_id" : field] = 1;
344
+ }
345
+ const indexName = `idx_${index.fields.join("_")}`;
346
+ if (!existingIndexNames.has(indexName)) {
347
+ await collection.createIndex(indexSpec, {
348
+ name: indexName,
349
+ unique: !!index.unique
350
+ });
351
+ }
352
+ }
353
+ }
354
+ }
355
+ };
356
+
357
+ // src/index.ts
358
+ function mongodbAdapter(client, options) {
359
+ const adapter = new MongoDbAdapter(client, options);
360
+ return new Proxy(adapter, {
361
+ get(target, prop, receiver) {
362
+ if (prop === "get") {
363
+ return async (key) => {
364
+ const res = await adapter.findOne({
365
+ model: "legacy",
366
+ where: [{ field: "id", value: key }]
367
+ });
368
+ return res;
369
+ };
370
+ }
371
+ if (prop === "put") {
372
+ return async (key, data) => {
373
+ await adapter.create({ model: "legacy", data: { id: key, ...data } });
374
+ };
375
+ }
376
+ if (prop === "delete") {
377
+ return async (key) => {
378
+ await adapter.delete({ model: "legacy", where: [{ field: "id", value: key }] });
379
+ };
380
+ }
381
+ return Reflect.get(target, prop, receiver);
382
+ }
383
+ });
384
+ }
385
+
386
+ export { MongoDbAdapter, mongodbAdapter };
387
+ //# sourceMappingURL=index.mjs.map
388
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/mongodb-db.adapter.ts","../src/index.ts"],"names":["doc","docs"],"mappings":"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","file":"index.mjs","sourcesContent":["import { MongoClient, Db, Collection, Document, Filter, ClientSession } from \"mongodb\";\nimport type {\n DatabaseAdapter,\n DatabaseTransactionAdapter,\n WhereClause,\n CreateOptions,\n FindOptions,\n UpdateOptions,\n DeleteOptions,\n CountOptions,\n} from \"@better-media/core\";\nimport type {\n FieldType,\n BmSchema,\n DbHooks,\n DatabaseHookContext,\n ModelDefinition,\n} from \"@better-media/core\";\nimport { serializeData, deserializeData, runHooks } from \"@better-media/core\";\nimport type { MongoDbConfig } from \"./mongodb-db-config.interface\";\n\nexport interface MongoDbOptions {\n config: MongoDbConfig;\n schema: BmSchema;\n hooks?: DbHooks;\n session?: ClientSession;\n}\n\n/**\n * MongoDB database adapter.\n */\nexport class MongoDbAdapter implements DatabaseAdapter {\n private readonly client: MongoClient;\n private readonly db: Db;\n private readonly schema: BmSchema;\n private readonly hooks?: DbHooks;\n private readonly session?: ClientSession;\n\n constructor(client: MongoClient, options: MongoDbOptions) {\n this.client = client;\n this.db = this.client.db(options.config.databaseName);\n this.schema = options.schema;\n this.hooks = options.hooks;\n this.session = options.session;\n }\n\n private getCollection(model: string): Collection<Document> {\n return this.db.collection(model);\n }\n\n private getModelFields(model: string): Record<string, { type: FieldType }> {\n return this.schema[model]?.fields ?? {};\n }\n\n private getModelDefinition(model: string): ModelDefinition | undefined {\n return this.schema[model];\n }\n\n private getHookContext(model: string, trx?: DatabaseTransactionAdapter): DatabaseHookContext {\n return {\n model,\n adapter: this,\n transaction: trx,\n };\n }\n\n private buildFilter(\n where?: WhereClause,\n model?: string,\n options?: { withDeleted?: boolean }\n ): Filter<Document> {\n let filter: Filter<Document> = {};\n const definition = model ? this.getModelDefinition(model) : undefined;\n\n // Soft delete filtering\n if (definition?.softDelete && !options?.withDeleted) {\n filter = { deletedAt: null };\n }\n\n if (!where || where.length === 0) return filter;\n\n let whereFilter: Filter<Document> = {};\n\n for (let i = 0; i < where.length; i++) {\n const condition = where[i]!;\n const connector = i > 0 ? (where[i - 1]?.connector ?? \"AND\") : \"AND\";\n const field = condition.field === \"id\" ? \"_id\" : condition.field;\n const value = condition.value;\n const conditionFilter: Record<string, unknown> = {};\n\n switch (condition.operator) {\n case \"!=\":\n conditionFilter[field] = { $ne: value };\n break;\n case \"<\":\n conditionFilter[field] = { $lt: value };\n break;\n case \"<=\":\n conditionFilter[field] = { $lte: value };\n break;\n case \">\":\n conditionFilter[field] = { $gt: value };\n break;\n case \">=\":\n conditionFilter[field] = { $gte: value };\n break;\n case \"in\":\n conditionFilter[field] = { $in: value as unknown[] };\n break;\n case \"not_in\":\n conditionFilter[field] = { $nin: value as unknown[] };\n break;\n case \"starts_with\":\n conditionFilter[field] = {\n $regex: new RegExp(`^${this.escapeRegex(String(value))}`, \"i\"),\n };\n break;\n case \"ends_with\":\n conditionFilter[field] = {\n $regex: new RegExp(`${this.escapeRegex(String(value))}$`, \"i\"),\n };\n break;\n case \"contains\":\n case \"like\":\n conditionFilter[field] = { $regex: new RegExp(this.escapeRegex(String(value)), \"i\") };\n break;\n case \"=\":\n default:\n conditionFilter[field] = value;\n break;\n }\n\n if (i === 0) {\n whereFilter = conditionFilter;\n } else if (connector === \"OR\") {\n whereFilter = {\n $or: [whereFilter as Record<string, unknown>, conditionFilter],\n } as Filter<Document>;\n } else {\n whereFilter = {\n $and: [whereFilter as Record<string, unknown>, conditionFilter],\n } as Filter<Document>;\n }\n }\n\n // Combine with soft delete filter\n if (Object.keys(filter).length > 0 && Object.keys(whereFilter).length > 0) {\n return { $and: [filter, whereFilter] } as Filter<Document>;\n }\n return Object.keys(whereFilter).length > 0 ? whereFilter : filter;\n }\n\n private escapeRegex(string: string): string {\n return string.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n }\n\n private mapToMongo(data: Record<string, unknown>): Document {\n const { id, ...rest } = data;\n return id !== undefined ? { _id: id, ...rest } : (rest as Document);\n }\n\n private mapFromMongo(doc: Document | null): Record<string, unknown> | null {\n if (!doc) return null;\n const { _id, ...rest } = doc as { _id: unknown } & Record<string, unknown>;\n return { id: typeof _id === \"string\" ? _id : String(_id), ...rest };\n }\n\n async create<T extends Record<string, unknown>>(options: CreateOptions<T>): Promise<T> {\n const fields = this.getModelFields(options.model);\n const collection = this.getCollection(options.model);\n const context = this.getHookContext(options.model);\n\n let dataToInsert = options.data as Record<string, unknown>;\n dataToInsert = await runHooks.beforeCreate(this.hooks, dataToInsert, context);\n\n const serializedData = serializeData(fields, dataToInsert);\n const mongoDoc = this.mapToMongo(serializedData);\n\n await collection.insertOne(mongoDoc, { session: this.session });\n const resultRecord = deserializeData(fields, this.mapFromMongo(mongoDoc)!) as T;\n\n await runHooks.afterCreate(this.hooks, resultRecord as Record<string, unknown>, context);\n return resultRecord;\n }\n\n private async applyPopulate(\n model: string,\n pipeline: Record<string, unknown>[],\n populate: string[]\n ): Promise<void> {\n const sortedPopulate = [...populate].sort((a, b) => a.split(\".\").length - b.split(\".\").length);\n\n for (const path of sortedPopulate) {\n const parts = path.split(\".\");\n let currentModel = model;\n let currentPath = \"\";\n\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i]!;\n const fieldDef = this.schema[currentModel]?.fields[part];\n\n if (fieldDef?.references) {\n const localField = currentPath ? `${currentPath}.${part}` : part;\n const asField = localField;\n\n pipeline.push({\n $lookup: {\n from: fieldDef.references.model,\n localField: localField,\n foreignField: fieldDef.references.field === \"id\" ? \"_id\" : fieldDef.references.field,\n as: asField,\n },\n });\n pipeline.push({ $unwind: { path: `$${asField}`, preserveNullAndEmptyArrays: true } });\n\n currentModel = fieldDef.references.model;\n currentPath = asField;\n } else {\n // If a part doesn't have references, we can't populate further deep from here\n break;\n }\n }\n }\n }\n\n async findOne<T extends Record<string, unknown>>(options: FindOptions<T>): Promise<T | null> {\n const fields = this.getModelFields(options.model);\n const collection = this.getCollection(options.model);\n const filter = this.buildFilter(options.where, options.model, {\n withDeleted: options.withDeleted,\n });\n\n if (options.populate && options.populate.length > 0) {\n const pipeline: Record<string, unknown>[] = [{ $match: filter }];\n await this.applyPopulate(options.model, pipeline, options.populate);\n\n const results = await (\n collection as unknown as {\n aggregate: (p: unknown[], o: unknown) => { toArray: () => Promise<Document[]> };\n }\n )\n .aggregate(pipeline, { session: this.session })\n .toArray();\n const doc = results[0];\n if (!doc) return null;\n return deserializeData(fields, this.mapFromMongo(doc)!) as T;\n }\n\n const projection: Record<string, 1> = {};\n if (options.select) {\n for (const field of options.select) projection[field === \"id\" ? \"_id\" : field] = 1;\n }\n\n const doc = await collection.findOne(filter, {\n projection: Object.keys(projection).length > 0 ? projection : undefined,\n session: this.session,\n });\n\n if (!doc) return null;\n return deserializeData(fields, this.mapFromMongo(doc)!) as T;\n }\n\n async findMany<T extends Record<string, unknown>>(options: FindOptions<T>): Promise<T[]> {\n const fields = this.getModelFields(options.model);\n const collection = this.getCollection(options.model);\n const filter = this.buildFilter(options.where, options.model, {\n withDeleted: options.withDeleted,\n });\n\n if (options.populate && options.populate.length > 0) {\n // Aggregate population\n const pipeline: Record<string, unknown>[] = [{ $match: filter }];\n await this.applyPopulate(options.model, pipeline, options.populate);\n\n if (options.sortBy) {\n const field = options.sortBy.field === \"id\" ? \"_id\" : options.sortBy.field;\n pipeline.push({ $sort: { [field]: options.sortBy.direction === \"asc\" ? 1 : -1 } });\n }\n if (options.offset) pipeline.push({ $skip: options.offset });\n if (options.limit) pipeline.push({ $limit: options.limit });\n\n const docs = await (\n collection as unknown as {\n aggregate: (p: unknown[], o: unknown) => { toArray: () => Promise<Document[]> };\n }\n )\n .aggregate(pipeline, { session: this.session })\n .toArray();\n return docs.map((doc: Document) => deserializeData(fields, this.mapFromMongo(doc)!)) as T[];\n }\n\n let cursor = collection.find(filter, { session: this.session });\n if (options.select) {\n const projection: Record<string, 1> = {};\n for (const field of options.select) projection[field === \"id\" ? \"_id\" : field] = 1;\n cursor = cursor.project(projection);\n }\n if (options.sortBy) {\n const field = options.sortBy.field === \"id\" ? \"_id\" : options.sortBy.field;\n cursor = cursor.sort({ [field]: options.sortBy.direction === \"asc\" ? 1 : -1 });\n }\n if (options.offset) cursor = cursor.skip(options.offset);\n if (options.limit) cursor = cursor.limit(options.limit);\n\n const docs = await cursor.toArray();\n return docs.map((doc: Document) => deserializeData(fields, this.mapFromMongo(doc)!)) as T[];\n }\n\n async update<T extends Record<string, unknown>>(options: UpdateOptions<T>): Promise<T | null> {\n const fields = this.getModelFields(options.model);\n const collection = this.getCollection(options.model);\n const context = this.getHookContext(options.model);\n\n const filter = this.buildFilter(options.where, options.model);\n const doc = await collection.findOne(filter, { session: this.session });\n if (!doc) return null;\n\n const currentRecord = deserializeData(fields, this.mapFromMongo(doc)!);\n let updatedData = { ...currentRecord, ...(options.update as Record<string, unknown>) };\n updatedData = await runHooks.beforeUpdate(this.hooks, updatedData, context);\n\n const serializedUpdate = serializeData(fields, options.update as Record<string, unknown>);\n const mongoUpdate = this.mapToMongo(serializedUpdate);\n delete mongoUpdate._id;\n\n await collection.updateOne(filter, { $set: mongoUpdate }, { session: this.session });\n const resultRecord = deserializeData(fields, serializeData(fields, updatedData)) as T;\n\n await runHooks.afterUpdate(this.hooks, resultRecord as Record<string, unknown>, context);\n return resultRecord;\n }\n\n async updateMany<T extends Record<string, unknown>>(options: UpdateOptions<T>): Promise<number> {\n const fields = this.getModelFields(options.model);\n const collection = this.getCollection(options.model);\n const filter = this.buildFilter(options.where, options.model);\n\n const serializedUpdate = serializeData(fields, options.update as Record<string, unknown>);\n const mongoUpdate = this.mapToMongo(serializedUpdate);\n delete mongoUpdate._id;\n\n const result = await collection.updateMany(\n filter,\n { $set: mongoUpdate },\n { session: this.session }\n );\n return result.modifiedCount;\n }\n\n async delete(options: DeleteOptions): Promise<void> {\n const collection = this.getCollection(options.model);\n const context = this.getHookContext(options.model);\n const definition = this.getModelDefinition(options.model);\n\n await runHooks.beforeDelete(this.hooks, options.where, context);\n\n const filter = this.buildFilter(options.where, options.model);\n\n if (definition?.softDelete) {\n await this.updateMany({\n model: options.model,\n where: options.where,\n update: { deletedAt: new Date() } as unknown as Record<string, unknown>,\n });\n } else {\n if (\n options.where?.length === 1 &&\n options.where[0]?.field === \"id\" &&\n options.where[0]?.operator !== \"!=\"\n ) {\n await collection.deleteOne(filter, { session: this.session });\n } else {\n await collection.deleteMany(filter, { session: this.session });\n }\n }\n\n await runHooks.afterDelete(this.hooks, options.where, context);\n }\n\n async deleteMany(options: DeleteOptions): Promise<number> {\n const collection = this.getCollection(options.model);\n const filter = this.buildFilter(options.where, options.model);\n const definition = this.getModelDefinition(options.model);\n\n if (definition?.softDelete) {\n return await this.updateMany({\n model: options.model,\n where: options.where,\n update: { deletedAt: new Date() } as unknown as Record<string, unknown>,\n });\n }\n\n const result = await collection.deleteMany(filter, { session: this.session });\n return result.deletedCount;\n }\n\n async count(options: CountOptions): Promise<number> {\n const collection = this.getCollection(options.model);\n const filter = this.buildFilter(options.where, options.model);\n return collection.countDocuments(filter, { session: this.session });\n }\n\n async raw<T = unknown>(query: string, _params?: unknown[]): Promise<T> {\n // For Mongo, 'raw' can mean a direct command or aggregate\n if (query.startsWith(\"{\")) {\n return (await (this.db as unknown as { command: (c: unknown) => Promise<unknown> }).command(\n JSON.parse(query)\n )) as unknown as T;\n }\n throw new Error(\"MongoDB 'raw' requires a JSON command string.\");\n }\n\n async transaction<R>(callback: (trx: DatabaseTransactionAdapter) => Promise<R>): Promise<R> {\n const session = this.client.startSession();\n try {\n return (await session.withTransaction(async () => {\n const trxAdapter = new MongoDbAdapter(this.client, {\n config: { databaseName: this.db.databaseName },\n schema: this.schema,\n hooks: this.hooks,\n session,\n });\n return await callback(trxAdapter);\n })) as R;\n } finally {\n await session.endSession();\n }\n }\n\n async __initCollection(\n model: string,\n definition: ModelDefinition,\n options: { mode: \"safe\" | \"diff\" | \"force\" }\n ): Promise<void> {\n if (options.mode === \"force\") {\n await (\n this.getCollection(model) as unknown as {\n drop: () => Promise<void>;\n }\n )\n .drop()\n .catch(() => {});\n }\n\n const collections = await this.db.listCollections({ name: model }).toArray();\n if (collections.length === 0) {\n await this.db.createCollection(model);\n }\n\n const collection = this.getCollection(model);\n const existingIndexes = await (\n collection as unknown as {\n listIndexes: () => { toArray: () => Promise<{ name: string }[]> };\n }\n )\n .listIndexes()\n .toArray();\n const existingIndexNames = new Set(existingIndexes.map((idx) => idx.name));\n\n // Fields-based indexes (implicitly unique/foreign key)\n for (const [fieldName, fieldDef] of Object.entries(definition.fields)) {\n if (fieldDef?.unique && !fieldDef?.primaryKey) {\n const indexName = `${fieldName}_1`;\n if (!existingIndexNames.has(indexName)) {\n await collection.createIndex({ [fieldName]: 1 }, { unique: true });\n }\n }\n if (fieldDef?.references) {\n const indexName = `${fieldName}_1`;\n if (!existingIndexNames.has(indexName)) {\n await collection.createIndex({ [fieldName]: 1 });\n }\n }\n }\n\n // Explicit indexes from definition.indexes\n if (definition.indexes) {\n for (const index of definition.indexes) {\n const indexSpec: Record<string, 1> = {};\n for (const field of index.fields) {\n indexSpec[field === \"id\" ? \"_id\" : field] = 1;\n }\n\n const indexName = `idx_${index.fields.join(\"_\")}`;\n if (!existingIndexNames.has(indexName)) {\n await (\n collection as unknown as {\n createIndex: (s: unknown, o: unknown) => Promise<void>;\n }\n ).createIndex(indexSpec, {\n name: indexName,\n unique: !!index.unique,\n });\n }\n }\n }\n }\n}\n","export * from \"./mongodb-db-config.interface\";\nexport * from \"./mongodb-db.adapter\";\n\nimport { MongoDbAdapter, type MongoDbOptions } from \"./mongodb-db.adapter\";\nimport type { DatabaseAdapter } from \"@better-media/core\";\nimport type { MongoClient } from \"mongodb\";\n\nexport function mongodbAdapter(client: MongoClient, options: MongoDbOptions): DatabaseAdapter {\n const adapter = new MongoDbAdapter(client, options);\n return new Proxy(adapter, {\n get(target, prop, receiver) {\n if (prop === \"get\") {\n return async (key: string) => {\n const res = await adapter.findOne({\n model: \"legacy\",\n where: [{ field: \"id\", value: key }],\n });\n return res;\n };\n }\n if (prop === \"put\") {\n return async (key: string, data: Record<string, unknown>) => {\n await adapter.create({ model: \"legacy\", data: { id: key, ...data } });\n };\n }\n if (prop === \"delete\") {\n return async (key: string) => {\n await adapter.delete({ model: \"legacy\", where: [{ field: \"id\", value: key }] });\n };\n }\n return Reflect.get(target, prop, receiver);\n },\n });\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@better-media/mongodb-adapter",
3
+ "version": "0.1.0",
4
+ "description": "MongoDB database adapter for Better Media",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "dependencies": {
20
+ "@better-media/core": "0.1.0",
21
+ "better-media": "1.0.0"
22
+ },
23
+ "peerDependencies": {
24
+ "mongodb": "^6.8.0"
25
+ },
26
+ "devDependencies": {
27
+ "@types/mongodb": "^4.0.7",
28
+ "mongodb": "^6.8.0",
29
+ "@types/node": "^22.10.0",
30
+ "tsup": "^8.5.1",
31
+ "typescript": "^5.9.3",
32
+ "jest": "^30.2.0",
33
+ "@types/jest": "^29.5.11"
34
+ },
35
+ "keywords": [
36
+ "better-media",
37
+ "mongodb",
38
+ "adapter",
39
+ "database"
40
+ ],
41
+ "author": "Abenezer Atnafu",
42
+ "license": "MIT",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/AbenezerAtnafu/better-media.git"
46
+ },
47
+ "homepage": "https://better-media.dev",
48
+ "bugs": {
49
+ "url": "https://github.com/AbenezerAtnafu/better-media/issues"
50
+ },
51
+ "publishConfig": {
52
+ "access": "public"
53
+ },
54
+ "scripts": {
55
+ "build": "tsup",
56
+ "dev": "tsup --watch",
57
+ "typecheck": "tsc --noEmit",
58
+ "lint": "eslint .",
59
+ "test": "jest"
60
+ }
61
+ }