@farming-labs/orm-mongoose 0.0.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/dist/index.js ADDED
@@ -0,0 +1,634 @@
1
+ // src/index.ts
2
+ import { randomUUID } from "crypto";
3
+ import {
4
+ createManifest
5
+ } from "@farming-labs/orm";
6
+ var manifestCache = /* @__PURE__ */ new WeakMap();
7
+ function getManifest(schema) {
8
+ const cached = manifestCache.get(schema);
9
+ if (cached) return cached;
10
+ const next = createManifest(schema);
11
+ manifestCache.set(schema, next);
12
+ return next;
13
+ }
14
+ function identityField(model) {
15
+ if (model.fields.id) return model.fields.id;
16
+ const uniqueField = Object.values(model.fields).find((field) => field.unique);
17
+ if (uniqueField) return uniqueField;
18
+ throw new Error(
19
+ `Model "${model.name}" requires an "id" field or a unique field for the Mongoose runtime.`
20
+ );
21
+ }
22
+ function applyDefault(value, field) {
23
+ if (value !== void 0) return value;
24
+ if (field.generated === "id") return randomUUID();
25
+ if (field.generated === "now") return /* @__PURE__ */ new Date();
26
+ if (typeof field.defaultValue === "function") {
27
+ return field.defaultValue();
28
+ }
29
+ return field.defaultValue;
30
+ }
31
+ function isFilterObject(value) {
32
+ return !!value && typeof value === "object" && !(value instanceof Date) && !Array.isArray(value);
33
+ }
34
+ function escapeRegex(value) {
35
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
36
+ }
37
+ function mergeWhere(...clauses) {
38
+ const defined = clauses.filter(Boolean);
39
+ if (!defined.length) return void 0;
40
+ if (defined.length === 1) return defined[0];
41
+ return {
42
+ AND: defined
43
+ };
44
+ }
45
+ function parseReference(reference) {
46
+ if (!reference) return null;
47
+ const [model, field] = reference.split(".");
48
+ if (!model || !field) return null;
49
+ return { model, field };
50
+ }
51
+ function extractEqualityValue(filter) {
52
+ if (!isFilterObject(filter)) {
53
+ return {
54
+ supported: true,
55
+ value: filter
56
+ };
57
+ }
58
+ const keys = Object.keys(filter);
59
+ if (keys.length === 1 && "eq" in filter) {
60
+ return {
61
+ supported: true,
62
+ value: filter.eq
63
+ };
64
+ }
65
+ return {
66
+ supported: false,
67
+ value: void 0
68
+ };
69
+ }
70
+ function extractUpsertConflict(model, where) {
71
+ const keys = Object.keys(where).filter((key) => key !== "AND" && key !== "OR" && key !== "NOT");
72
+ if ("AND" in where || "OR" in where || "NOT" in where || keys.length !== 1) {
73
+ throw new Error(
74
+ `Upsert on model "${model.name}" requires a single unique equality filter in "where".`
75
+ );
76
+ }
77
+ const fieldName = keys[0];
78
+ const field = model.fields[fieldName];
79
+ if (!field) {
80
+ throw new Error(`Unknown field "${fieldName}" on model "${model.name}".`);
81
+ }
82
+ if (!(field.kind === "id" || field.unique)) {
83
+ throw new Error(
84
+ `Upsert on model "${model.name}" requires the "where" field "${fieldName}" to be unique or an id field.`
85
+ );
86
+ }
87
+ const { supported, value } = extractEqualityValue(where[fieldName]);
88
+ if (!supported || value === void 0 || value === null) {
89
+ throw new Error(
90
+ `Upsert on model "${model.name}" requires the "where" field "${fieldName}" to use a single non-null equality value.`
91
+ );
92
+ }
93
+ return {
94
+ field,
95
+ value
96
+ };
97
+ }
98
+ function mergeUpsertCreateData(model, createData, conflict) {
99
+ const currentValue = createData[conflict.field.name];
100
+ if (currentValue !== void 0 && currentValue !== conflict.value) {
101
+ throw new Error(
102
+ `Upsert on model "${model.name}" requires create.${conflict.field.name} to match where.${conflict.field.name}.`
103
+ );
104
+ }
105
+ return {
106
+ ...createData,
107
+ [conflict.field.name]: currentValue ?? conflict.value
108
+ };
109
+ }
110
+ function validateUpsertUpdateData(model, updateData, conflict) {
111
+ const nextValue = updateData[conflict.field.name];
112
+ if (nextValue !== void 0 && nextValue !== conflict.value) {
113
+ throw new Error(
114
+ `Upsert on model "${model.name}" cannot change the conflict field "${conflict.field.name}".`
115
+ );
116
+ }
117
+ }
118
+ function isExecLike(value) {
119
+ return !!value && typeof value === "object" && "exec" in value;
120
+ }
121
+ async function execute(operation, session) {
122
+ if (isExecLike(operation)) {
123
+ const query = session && typeof operation.session === "function" ? operation.session(session) : operation;
124
+ return query.exec();
125
+ }
126
+ return operation;
127
+ }
128
+ async function normalizeCreated(doc) {
129
+ const result = await doc;
130
+ return Array.isArray(result) ? result[0] ?? null : result;
131
+ }
132
+ function createMongooseDriverInternal(config, state = {}) {
133
+ function getModel(modelName) {
134
+ const model = config.models[modelName];
135
+ if (!model) {
136
+ throw new Error(`No Mongoose model was provided for schema model "${modelName}".`);
137
+ }
138
+ return model;
139
+ }
140
+ function fieldTransform(modelName, fieldName) {
141
+ return config.transforms?.[modelName]?.[fieldName];
142
+ }
143
+ function encodeValue(modelName, field, value) {
144
+ if (value === void 0) return value;
145
+ if (value === null) return null;
146
+ let next = value;
147
+ if (field.kind === "boolean") {
148
+ next = Boolean(next);
149
+ } else if (field.kind === "datetime") {
150
+ next = next instanceof Date ? next : new Date(String(next));
151
+ }
152
+ const transform = fieldTransform(modelName, field.name);
153
+ return transform?.encode ? transform.encode(next) : next;
154
+ }
155
+ function decodeValue(modelName, field, value) {
156
+ if (value === void 0) return value;
157
+ if (value === null) return null;
158
+ const transform = fieldTransform(modelName, field.name);
159
+ let next = transform?.decode ? transform.decode(value) : value;
160
+ if (field.kind === "boolean") {
161
+ return Boolean(next);
162
+ }
163
+ if (field.kind === "datetime") {
164
+ return next instanceof Date ? next : new Date(String(next));
165
+ }
166
+ if (field.kind === "id") {
167
+ return typeof next === "string" ? next : String(next);
168
+ }
169
+ return next;
170
+ }
171
+ function buildDocument(model, data) {
172
+ const doc = {};
173
+ for (const field of Object.values(model.fields)) {
174
+ const value = applyDefault(data[field.name], field);
175
+ if (value !== void 0) {
176
+ doc[field.column] = encodeValue(model.name, field, value);
177
+ }
178
+ }
179
+ return doc;
180
+ }
181
+ function buildUpdate(model, data) {
182
+ const update = {};
183
+ for (const [fieldName, value] of Object.entries(data)) {
184
+ if (value === void 0) continue;
185
+ const field = model.fields[fieldName];
186
+ if (!field) {
187
+ throw new Error(`Unknown field "${fieldName}" on model "${model.name}".`);
188
+ }
189
+ update[field.column] = encodeValue(model.name, field, value);
190
+ }
191
+ return update;
192
+ }
193
+ function decodeRow(model, doc) {
194
+ const output = {};
195
+ for (const field of Object.values(model.fields)) {
196
+ output[field.name] = decodeValue(model.name, field, doc[field.column]);
197
+ }
198
+ return output;
199
+ }
200
+ function compileFieldFilter(model, fieldName, filter) {
201
+ const field = model.fields[fieldName];
202
+ if (!field) {
203
+ throw new Error(`Unknown field "${fieldName}" on model "${model.name}".`);
204
+ }
205
+ if (!isFilterObject(filter)) {
206
+ return {
207
+ [field.column]: encodeValue(model.name, field, filter)
208
+ };
209
+ }
210
+ const operations = {};
211
+ if ("eq" in filter) {
212
+ operations.$eq = encodeValue(model.name, field, filter.eq);
213
+ }
214
+ if ("not" in filter) {
215
+ operations.$ne = encodeValue(model.name, field, filter.not);
216
+ }
217
+ if ("in" in filter) {
218
+ const values = Array.isArray(filter.in) ? filter.in : [];
219
+ operations.$in = values.map((value) => encodeValue(model.name, field, value));
220
+ }
221
+ if ("contains" in filter) {
222
+ operations.$regex = new RegExp(escapeRegex(String(filter.contains ?? "")));
223
+ }
224
+ if ("gt" in filter) {
225
+ operations.$gt = encodeValue(model.name, field, filter.gt);
226
+ }
227
+ if ("gte" in filter) {
228
+ operations.$gte = encodeValue(model.name, field, filter.gte);
229
+ }
230
+ if ("lt" in filter) {
231
+ operations.$lt = encodeValue(model.name, field, filter.lt);
232
+ }
233
+ if ("lte" in filter) {
234
+ operations.$lte = encodeValue(model.name, field, filter.lte);
235
+ }
236
+ return {
237
+ [field.column]: operations
238
+ };
239
+ }
240
+ function compileWhere(model, where) {
241
+ if (!where) return {};
242
+ const clauses = [];
243
+ for (const [key, value] of Object.entries(where)) {
244
+ if (key === "AND") {
245
+ const nested = (Array.isArray(value) ? value : []).map((item) => compileWhere(model, item)).filter((item) => Object.keys(item).length > 0);
246
+ if (nested.length) clauses.push({ $and: nested });
247
+ continue;
248
+ }
249
+ if (key === "OR") {
250
+ const nested = (Array.isArray(value) ? value : []).map((item) => compileWhere(model, item)).filter((item) => Object.keys(item).length > 0);
251
+ if (nested.length) clauses.push({ $or: nested });
252
+ continue;
253
+ }
254
+ if (key === "NOT") {
255
+ const nested = compileWhere(model, value);
256
+ if (Object.keys(nested).length) clauses.push({ $nor: [nested] });
257
+ continue;
258
+ }
259
+ clauses.push(compileFieldFilter(model, key, value));
260
+ }
261
+ if (!clauses.length) return {};
262
+ if (clauses.length === 1) return clauses[0];
263
+ return {
264
+ $and: clauses
265
+ };
266
+ }
267
+ function compileOrderBy(model, orderBy) {
268
+ if (!orderBy) return void 0;
269
+ const output = {};
270
+ for (const [fieldName, direction] of Object.entries(orderBy)) {
271
+ const field = model.fields[fieldName];
272
+ if (!field) continue;
273
+ output[field.column] = direction === "desc" ? -1 : 1;
274
+ }
275
+ return Object.keys(output).length ? output : void 0;
276
+ }
277
+ async function runFindMany(model, args) {
278
+ const query = getModel(model.name).find(compileWhere(model, args.where));
279
+ const orderBy = compileOrderBy(model, args.orderBy);
280
+ if (orderBy) query.sort(orderBy);
281
+ if (args.skip !== void 0) query.skip(args.skip);
282
+ if (args.take !== void 0) query.limit(args.take);
283
+ if (state.session) query.session(state.session);
284
+ query.lean();
285
+ return query.exec();
286
+ }
287
+ async function runFindOne(model, args) {
288
+ const orderBy = compileOrderBy(model, args.orderBy);
289
+ if (orderBy) {
290
+ const rows = await runFindMany(model, {
291
+ ...args,
292
+ take: 1
293
+ });
294
+ return rows[0] ?? null;
295
+ }
296
+ const query = getModel(model.name).findOne(compileWhere(model, args.where));
297
+ if (state.session) query.session(state.session);
298
+ query.lean();
299
+ return query.exec();
300
+ }
301
+ async function loadRows(schema, modelName, args) {
302
+ const manifest = getManifest(schema);
303
+ const model = manifest.models[modelName];
304
+ const docs = await runFindMany(model, args);
305
+ const rows = docs.map((doc) => decodeRow(model, doc));
306
+ return Promise.all(rows.map((row) => projectRow(schema, modelName, row, args.select)));
307
+ }
308
+ async function loadOneRow(schema, modelName, args) {
309
+ const rows = await loadRows(schema, modelName, {
310
+ ...args,
311
+ take: 1
312
+ });
313
+ return rows[0] ?? null;
314
+ }
315
+ async function loadRawOneRow(schema, modelName, args) {
316
+ const manifest = getManifest(schema);
317
+ const model = manifest.models[modelName];
318
+ const doc = await runFindOne(model, args);
319
+ return doc ? decodeRow(model, doc) : null;
320
+ }
321
+ async function projectRow(schema, modelName, row, select) {
322
+ const manifest = getManifest(schema);
323
+ const model = manifest.models[modelName];
324
+ const output = {};
325
+ if (!select) {
326
+ for (const fieldName of Object.keys(model.fields)) {
327
+ output[fieldName] = row[fieldName];
328
+ }
329
+ return output;
330
+ }
331
+ for (const [key, value] of Object.entries(select)) {
332
+ if (value === void 0) continue;
333
+ if (key in model.fields && value === true) {
334
+ output[key] = row[key];
335
+ continue;
336
+ }
337
+ if (key in schema.models[modelName].relations) {
338
+ output[key] = await resolveRelation(
339
+ schema,
340
+ modelName,
341
+ key,
342
+ row,
343
+ value
344
+ );
345
+ }
346
+ }
347
+ return output;
348
+ }
349
+ async function resolveRelation(schema, modelName, relationName, row, value) {
350
+ const manifest = getManifest(schema);
351
+ const relation = schema.models[modelName].relations[relationName];
352
+ const relationArgs = value === true ? {} : value;
353
+ if (relation.kind === "belongsTo") {
354
+ const foreignField = manifest.models[modelName].fields[relation.foreignKey];
355
+ const targetReference = parseReference(foreignField?.references);
356
+ const targetField2 = targetReference?.field ?? identityField(manifest.models[relation.target]).name;
357
+ const foreignValue = row[relation.foreignKey];
358
+ if (foreignValue == null) return null;
359
+ return loadOneRow(schema, relation.target, {
360
+ where: mergeWhere(
361
+ relationArgs.where,
362
+ {
363
+ [targetField2]: foreignValue
364
+ }
365
+ ),
366
+ orderBy: relationArgs.orderBy,
367
+ select: relationArgs.select
368
+ });
369
+ }
370
+ if (relation.kind === "hasOne") {
371
+ const targetModel = manifest.models[relation.target];
372
+ const foreignField = targetModel.fields[relation.foreignKey];
373
+ const sourceReference = parseReference(foreignField?.references);
374
+ const sourceField2 = sourceReference?.field ?? identityField(manifest.models[modelName]).name;
375
+ const sourceValue2 = row[sourceField2];
376
+ if (sourceValue2 == null) return null;
377
+ return loadOneRow(schema, relation.target, {
378
+ where: mergeWhere(
379
+ relationArgs.where,
380
+ {
381
+ [relation.foreignKey]: sourceValue2
382
+ }
383
+ ),
384
+ orderBy: relationArgs.orderBy,
385
+ select: relationArgs.select
386
+ });
387
+ }
388
+ if (relation.kind === "hasMany") {
389
+ const targetModel = manifest.models[relation.target];
390
+ const foreignField = targetModel.fields[relation.foreignKey];
391
+ const sourceReference = parseReference(foreignField?.references);
392
+ const sourceField2 = sourceReference?.field ?? identityField(manifest.models[modelName]).name;
393
+ const sourceValue2 = row[sourceField2];
394
+ if (sourceValue2 == null) return [];
395
+ return loadRows(schema, relation.target, {
396
+ where: mergeWhere(
397
+ relationArgs.where,
398
+ {
399
+ [relation.foreignKey]: sourceValue2
400
+ }
401
+ ),
402
+ orderBy: relationArgs.orderBy,
403
+ take: relationArgs.take,
404
+ skip: relationArgs.skip,
405
+ select: relationArgs.select
406
+ });
407
+ }
408
+ const throughModel = manifest.models[relation.through];
409
+ const throughFromReference = parseReference(throughModel.fields[relation.from]?.references);
410
+ const throughToReference = parseReference(throughModel.fields[relation.to]?.references);
411
+ const sourceField = throughFromReference?.field ?? identityField(manifest.models[modelName]).name;
412
+ const targetField = throughToReference?.field ?? identityField(manifest.models[relation.target]).name;
413
+ const sourceValue = row[sourceField];
414
+ if (sourceValue == null) return [];
415
+ const throughRows = await loadRows(schema, relation.through, {
416
+ where: {
417
+ [relation.from]: sourceValue
418
+ }
419
+ });
420
+ const targetIds = throughRows.map((item) => item[relation.to]).filter((item) => item != null);
421
+ if (!targetIds.length) return [];
422
+ return loadRows(schema, relation.target, {
423
+ where: mergeWhere(
424
+ relationArgs.where,
425
+ {
426
+ [targetField]: {
427
+ in: targetIds
428
+ }
429
+ }
430
+ ),
431
+ orderBy: relationArgs.orderBy,
432
+ take: relationArgs.take,
433
+ skip: relationArgs.skip,
434
+ select: relationArgs.select
435
+ });
436
+ }
437
+ async function runTransaction(run) {
438
+ if (state.session) {
439
+ return run(createMongooseDriverInternal(config, state));
440
+ }
441
+ const startSession = config.startSession ?? config.connection?.startSession.bind(config.connection);
442
+ if (!startSession) {
443
+ return run(createMongooseDriverInternal(config, state));
444
+ }
445
+ const session = await startSession();
446
+ try {
447
+ if (session.withTransaction) {
448
+ return await session.withTransaction(
449
+ () => run(
450
+ createMongooseDriverInternal(config, {
451
+ session
452
+ })
453
+ )
454
+ );
455
+ }
456
+ if (session.startTransaction && session.commitTransaction && session.abortTransaction) {
457
+ session.startTransaction();
458
+ try {
459
+ const result = await run(
460
+ createMongooseDriverInternal(config, {
461
+ session
462
+ })
463
+ );
464
+ await session.commitTransaction();
465
+ return result;
466
+ } catch (error) {
467
+ await session.abortTransaction();
468
+ throw error;
469
+ }
470
+ }
471
+ return run(
472
+ createMongooseDriverInternal(config, {
473
+ session
474
+ })
475
+ );
476
+ } finally {
477
+ await session.endSession?.();
478
+ }
479
+ }
480
+ const driver = {
481
+ async findMany(schema, model, args) {
482
+ return loadRows(schema, model, args);
483
+ },
484
+ async findFirst(schema, model, args) {
485
+ return loadOneRow(schema, model, args);
486
+ },
487
+ async findUnique(schema, model, args) {
488
+ return loadOneRow(schema, model, args);
489
+ },
490
+ async count(schema, model, args) {
491
+ const manifest = getManifest(schema);
492
+ const result = await execute(
493
+ getModel(model).countDocuments(
494
+ compileWhere(manifest.models[model], args?.where)
495
+ ),
496
+ state.session
497
+ );
498
+ return Number(result);
499
+ },
500
+ async create(schema, model, args) {
501
+ const manifest = getManifest(schema);
502
+ const created = await normalizeCreated(
503
+ getModel(model).create(
504
+ buildDocument(manifest.models[model], args.data),
505
+ state.session ? { session: state.session } : void 0
506
+ )
507
+ );
508
+ if (!created) {
509
+ throw new Error(`Create on model "${String(model)}" did not return a document.`);
510
+ }
511
+ const row = decodeRow(manifest.models[model], created);
512
+ return projectRow(schema, model, row, args.select);
513
+ },
514
+ async createMany(schema, model, args) {
515
+ const results = [];
516
+ for (const entry of args.data) {
517
+ results.push(
518
+ await driver.create(schema, model, {
519
+ data: entry,
520
+ select: args.select
521
+ })
522
+ );
523
+ }
524
+ return results;
525
+ },
526
+ async update(schema, model, args) {
527
+ const manifest = getManifest(schema);
528
+ const updated = await getModel(model).findOneAndUpdate(
529
+ compileWhere(manifest.models[model], args.where),
530
+ {
531
+ $set: buildUpdate(manifest.models[model], args.data)
532
+ },
533
+ {
534
+ new: true,
535
+ returnDocument: "after",
536
+ session: state.session
537
+ }
538
+ ).lean().exec();
539
+ if (!updated) return null;
540
+ return projectRow(
541
+ schema,
542
+ model,
543
+ decodeRow(manifest.models[model], updated),
544
+ args.select
545
+ );
546
+ },
547
+ async updateMany(schema, model, args) {
548
+ const manifest = getManifest(schema);
549
+ const update = buildUpdate(manifest.models[model], args.data);
550
+ if (!Object.keys(update).length) return 0;
551
+ const result = await execute(
552
+ getModel(model).updateMany(
553
+ compileWhere(manifest.models[model], args.where),
554
+ {
555
+ $set: update
556
+ },
557
+ state.session ? { session: state.session } : void 0
558
+ ),
559
+ state.session
560
+ );
561
+ return Number(result.modifiedCount ?? result.matchedCount ?? 0);
562
+ },
563
+ async upsert(schema, model, args) {
564
+ const manifest = getManifest(schema);
565
+ const modelManifest = manifest.models[model];
566
+ const conflict = extractUpsertConflict(modelManifest, args.where);
567
+ validateUpsertUpdateData(
568
+ modelManifest,
569
+ args.update,
570
+ conflict
571
+ );
572
+ const created = buildDocument(
573
+ modelManifest,
574
+ mergeUpsertCreateData(
575
+ modelManifest,
576
+ args.create,
577
+ conflict
578
+ )
579
+ );
580
+ const updated = await getModel(model).findOneAndUpdate(
581
+ compileWhere(modelManifest, args.where),
582
+ {
583
+ $set: buildUpdate(modelManifest, args.update),
584
+ $setOnInsert: created
585
+ },
586
+ {
587
+ upsert: true,
588
+ new: true,
589
+ returnDocument: "after",
590
+ session: state.session
591
+ }
592
+ ).lean().exec();
593
+ if (!updated) {
594
+ throw new Error(`Upsert on model "${String(model)}" did not return a document.`);
595
+ }
596
+ return projectRow(
597
+ schema,
598
+ model,
599
+ decodeRow(modelManifest, updated),
600
+ args.select
601
+ );
602
+ },
603
+ async delete(schema, model, args) {
604
+ const manifest = getManifest(schema);
605
+ const deleted = await getModel(model).findOneAndDelete(
606
+ compileWhere(manifest.models[model], args.where),
607
+ state.session ? { session: state.session } : void 0
608
+ ).lean().exec();
609
+ return deleted ? 1 : 0;
610
+ },
611
+ async deleteMany(schema, model, args) {
612
+ const manifest = getManifest(schema);
613
+ const result = await execute(
614
+ getModel(model).deleteMany(
615
+ compileWhere(manifest.models[model], args.where),
616
+ state.session ? { session: state.session } : void 0
617
+ ),
618
+ state.session
619
+ );
620
+ return Number(result.deletedCount ?? 0);
621
+ },
622
+ async transaction(_schema, run) {
623
+ return runTransaction(run);
624
+ }
625
+ };
626
+ return driver;
627
+ }
628
+ function createMongooseDriver(config) {
629
+ return createMongooseDriverInternal(config);
630
+ }
631
+ export {
632
+ createMongooseDriver
633
+ };
634
+ //# sourceMappingURL=index.js.map