@farming-labs/orm 0.0.1 → 0.0.3

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 CHANGED
@@ -4,17 +4,38 @@ function createModelClient(schema, driver, model2) {
4
4
  findMany(args) {
5
5
  return driver.findMany(schema, model2, args ?? {});
6
6
  },
7
+ findOne(args) {
8
+ return driver.findFirst(schema, model2, args ?? {});
9
+ },
7
10
  findFirst(args) {
8
11
  return driver.findFirst(schema, model2, args ?? {});
9
12
  },
13
+ findUnique(args) {
14
+ return driver.findUnique(schema, model2, args);
15
+ },
16
+ count(args) {
17
+ return driver.count(schema, model2, args);
18
+ },
10
19
  create(args) {
11
20
  return driver.create(schema, model2, args);
12
21
  },
22
+ createMany(args) {
23
+ return driver.createMany(schema, model2, args);
24
+ },
13
25
  update(args) {
14
26
  return driver.update(schema, model2, args);
15
27
  },
28
+ updateMany(args) {
29
+ return driver.updateMany(schema, model2, args);
30
+ },
31
+ upsert(args) {
32
+ return driver.upsert(schema, model2, args);
33
+ },
16
34
  delete(args) {
17
35
  return driver.delete(schema, model2, args);
36
+ },
37
+ deleteMany(args) {
38
+ return driver.deleteMany(schema, model2, args);
18
39
  }
19
40
  };
20
41
  }
@@ -32,6 +53,13 @@ function createOrm(options) {
32
53
  });
33
54
  return run(tx);
34
55
  });
56
+ orm.batch = async (tasks) => orm.transaction(async (tx) => {
57
+ const results = [];
58
+ for (const task of tasks) {
59
+ results.push(await task(tx));
60
+ }
61
+ return results;
62
+ });
35
63
  return orm;
36
64
  }
37
65
 
@@ -155,6 +183,29 @@ function createManifest(schema) {
155
183
  // src/generators.ts
156
184
  var capitalize = (value) => value.charAt(0).toUpperCase() + value.slice(1);
157
185
  var pluralize = (value) => value.endsWith("s") ? value : `${value}s`;
186
+ function resolveReferenceTarget(manifest, model2, foreignKey, fallbackTarget) {
187
+ const reference = model2.fields[foreignKey]?.references;
188
+ if (!reference) {
189
+ return {
190
+ targetModel: fallbackTarget,
191
+ targetField: "id"
192
+ };
193
+ }
194
+ const [targetModel, targetField = "id"] = reference.split(".");
195
+ return {
196
+ targetModel,
197
+ targetField
198
+ };
199
+ }
200
+ function hasExplicitInverseRelation(manifest, modelName, sourceModel, foreignKey) {
201
+ const model2 = manifest.models[modelName];
202
+ if (!model2) return false;
203
+ return Object.values(model2.relations).some((relation) => {
204
+ if (relation.target !== sourceModel) return false;
205
+ if (relation.kind === "belongsTo" || relation.kind === "manyToMany") return false;
206
+ return relation.foreignKey === foreignKey;
207
+ });
208
+ }
158
209
  function prismaType(field) {
159
210
  switch (field.kind) {
160
211
  case "id":
@@ -226,7 +277,7 @@ function sqlType(field, dialect) {
226
277
  return dialect === "mysql" ? "varchar(191)" : "text";
227
278
  }
228
279
  if (field.kind === "string") {
229
- return dialect === "mysql" && field.unique ? "varchar(191)" : "text";
280
+ return dialect === "mysql" && (field.unique || field.references) ? "varchar(191)" : "text";
230
281
  }
231
282
  if (field.kind === "boolean") {
232
283
  return dialect === "sqlite" ? "integer" : "boolean";
@@ -239,6 +290,12 @@ function sqlType(field, dialect) {
239
290
  }
240
291
  return "timestamp";
241
292
  }
293
+ function sqlIdentifier(dialect, value) {
294
+ if (dialect === "mysql") {
295
+ return `\`${value}\``;
296
+ }
297
+ return `"${value}"`;
298
+ }
242
299
  function renderPrismaSchema(schema, options = {}) {
243
300
  const manifest = createManifest(schema);
244
301
  const provider = options.provider ?? "postgresql";
@@ -253,6 +310,7 @@ function renderPrismaSchema(schema, options = {}) {
253
310
  ...reverseRelations.get(targetModel) ?? [],
254
311
  {
255
312
  sourceModel: model2.name,
313
+ foreignKey: field.name,
256
314
  many: !field.unique
257
315
  }
258
316
  ]);
@@ -261,6 +319,8 @@ function renderPrismaSchema(schema, options = {}) {
261
319
  const blocks = Object.values(manifest.models).map((model2) => {
262
320
  const lines = [];
263
321
  const modelName = capitalize(model2.name);
322
+ const relationFieldNames = /* @__PURE__ */ new Set();
323
+ const handledForeignKeys = /* @__PURE__ */ new Set();
264
324
  for (const field of Object.values(model2.fields)) {
265
325
  const fieldType = prismaType(field);
266
326
  const modifiers = [];
@@ -277,16 +337,45 @@ function renderPrismaSchema(schema, options = {}) {
277
337
  lines.push(
278
338
  ` ${field.name} ${fieldType}${field.nullable ? "?" : ""}${modifiers.length ? ` ${modifiers.join(" ")}` : ""}`
279
339
  );
280
- if (field.references) {
281
- const [targetModel, targetField] = field.references.split(".");
340
+ }
341
+ for (const [relationName, relation] of Object.entries(model2.relations)) {
342
+ if (relation.kind === "manyToMany") continue;
343
+ relationFieldNames.add(relationName);
344
+ if (relation.kind === "belongsTo") {
345
+ const { targetField } = resolveReferenceTarget(
346
+ manifest,
347
+ model2,
348
+ relation.foreignKey,
349
+ relation.target
350
+ );
351
+ handledForeignKeys.add(relation.foreignKey);
282
352
  lines.push(
283
- ` ${targetModel} ${capitalize(targetModel)} @relation(fields: [${field.name}], references: [${targetField}])`
353
+ ` ${relationName} ${capitalize(relation.target)} @relation(fields: [${relation.foreignKey}], references: [${targetField}])`
284
354
  );
355
+ continue;
356
+ }
357
+ if (relation.kind === "hasOne") {
358
+ lines.push(` ${relationName} ${capitalize(relation.target)}?`);
359
+ continue;
285
360
  }
361
+ lines.push(` ${relationName} ${capitalize(relation.target)}[]`);
362
+ }
363
+ for (const field of Object.values(model2.fields)) {
364
+ if (!field.references || handledForeignKeys.has(field.name)) continue;
365
+ const [targetModel, targetField] = field.references.split(".");
366
+ if (relationFieldNames.has(targetModel)) continue;
367
+ lines.push(
368
+ ` ${targetModel} ${capitalize(targetModel)} @relation(fields: [${field.name}], references: [${targetField}])`
369
+ );
286
370
  }
287
371
  for (const relation of reverseRelations.get(model2.name) ?? []) {
372
+ if (hasExplicitInverseRelation(manifest, model2.name, relation.sourceModel, relation.foreignKey)) {
373
+ continue;
374
+ }
375
+ const relationName = relation.many ? pluralize(relation.sourceModel) : relation.sourceModel;
376
+ if (relationFieldNames.has(relationName)) continue;
288
377
  lines.push(
289
- relation.many ? ` ${pluralize(relation.sourceModel)} ${capitalize(relation.sourceModel)}[]` : ` ${relation.sourceModel} ${capitalize(relation.sourceModel)}?`
378
+ relation.many ? ` ${relationName} ${capitalize(relation.sourceModel)}[]` : ` ${relationName} ${capitalize(relation.sourceModel)}?`
290
379
  );
291
380
  }
292
381
  const mapLine = model2.table !== modelName ? `
@@ -309,7 +398,7 @@ ${blocks.join("\n\n")}
309
398
  }
310
399
  function renderDrizzleSchema(schema, options) {
311
400
  const manifest = createManifest(schema);
312
- const imports = drizzleImports(options.dialect, manifest).join(", ");
401
+ const coreImports = drizzleImports(options.dialect, manifest).join(", ");
313
402
  const tableFactory = options.dialect === "pg" ? "pgTable" : options.dialect === "mysql" ? "mysqlTable" : "sqliteTable";
314
403
  const modelBlocks = Object.values(manifest.models).map((model2) => {
315
404
  const lines = Object.values(model2.fields).map((field) => {
@@ -324,16 +413,48 @@ function renderDrizzleSchema(schema, options) {
324
413
  ${lines.join(",\n")}
325
414
  });`;
326
415
  });
327
- return `import { ${imports} } from "drizzle-orm/${options.dialect === "pg" ? "pg-core" : options.dialect === "mysql" ? "mysql-core" : "sqlite-core"}";
416
+ const relationBlocks = Object.values(manifest.models).map((model2) => {
417
+ const lines = Object.entries(model2.relations).flatMap(([relationName, relation]) => {
418
+ if (relation.kind === "manyToMany") {
419
+ return [];
420
+ }
421
+ if (relation.kind === "belongsTo") {
422
+ const { targetField } = resolveReferenceTarget(
423
+ manifest,
424
+ model2,
425
+ relation.foreignKey,
426
+ relation.target
427
+ );
428
+ return [
429
+ ` ${relationName}: one(${relation.target}, { fields: [${model2.name}.${relation.foreignKey}], references: [${relation.target}.${targetField}] })`
430
+ ];
431
+ }
432
+ if (relation.kind === "hasOne") {
433
+ return [` ${relationName}: one(${relation.target})`];
434
+ }
435
+ return [` ${relationName}: many(${relation.target})`];
436
+ }).filter(Boolean);
437
+ if (!lines.length) return null;
438
+ return `export const ${model2.name}Relations = relations(${model2.name}, ({ one, many }) => ({
439
+ ${lines.join(",\n")}
440
+ }));`;
441
+ }).filter(Boolean);
442
+ const imports = [
443
+ `import { ${coreImports} } from "drizzle-orm/${options.dialect === "pg" ? "pg-core" : options.dialect === "mysql" ? "mysql-core" : "sqlite-core"}";`,
444
+ relationBlocks.length ? `import { relations } from "drizzle-orm";` : null
445
+ ].filter(Boolean).join("\n");
446
+ return `${imports}
328
447
 
329
- ${modelBlocks.join("\n\n")}
448
+ ${[...modelBlocks, ...relationBlocks].join("\n\n")}
330
449
  `;
331
450
  }
332
451
  function renderSafeSql(schema, options) {
333
452
  const manifest = createManifest(schema);
334
453
  const statements = Object.values(manifest.models).map((model2) => {
335
454
  const columns = Object.values(model2.fields).map((field) => {
336
- const parts = [`${field.column} ${sqlType(field, options.dialect)}`];
455
+ const parts = [
456
+ `${sqlIdentifier(options.dialect, field.column)} ${sqlType(field, options.dialect)}`
457
+ ];
337
458
  if (field.kind === "id") parts.push("primary key");
338
459
  if (!field.nullable) parts.push("not null");
339
460
  if (field.unique && field.kind !== "id") parts.push("unique");
@@ -345,11 +466,17 @@ function renderSafeSql(schema, options) {
345
466
  if (field.references) {
346
467
  const [targetModel, targetField] = field.references.split(".");
347
468
  const targetTable = manifest.models[targetModel]?.table ?? targetModel;
348
- parts.push(`references ${targetTable}(${targetField})`);
469
+ const targetColumn = manifest.models[targetModel]?.fields[targetField]?.column ?? targetField;
470
+ parts.push(
471
+ `references ${sqlIdentifier(options.dialect, targetTable)}(${sqlIdentifier(
472
+ options.dialect,
473
+ targetColumn
474
+ )})`
475
+ );
349
476
  }
350
477
  return ` ${parts.join(" ")}`;
351
478
  });
352
- return `create table if not exists ${model2.table} (
479
+ return `create table if not exists ${sqlIdentifier(options.dialect, model2.table)} (
353
480
  ${columns.join(",\n")}
354
481
  );`;
355
482
  });
@@ -466,6 +593,11 @@ function pageRows(rows, skip, take) {
466
593
  const end = take === void 0 ? void 0 : start + take;
467
594
  return rows.slice(start, end);
468
595
  }
596
+ function applyQuery(rows, args = {}) {
597
+ const filtered = rows.filter((row) => matchesWhere(row, args.where));
598
+ const sorted = sortRows(filtered, args.orderBy);
599
+ return pageRows(sorted, args.skip, args.take);
600
+ }
469
601
  function createMemoryDriver(seed) {
470
602
  const state = structuredClone(seed ?? {});
471
603
  function getRows(model2) {
@@ -473,14 +605,13 @@ function createMemoryDriver(seed) {
473
605
  state[model2] = rows;
474
606
  return rows;
475
607
  }
476
- function applyQuery(model2, args) {
477
- const rows = getRows(model2);
478
- const filtered = rows.filter((row) => matchesWhere(row, args.where));
479
- const sorted = sortRows(
480
- filtered,
481
- args.orderBy
482
- );
483
- return pageRows(sorted, args.skip, args.take);
608
+ function buildRow(schema, model2, data) {
609
+ const modelDefinition = schema.models[model2];
610
+ const nextRow = {};
611
+ for (const [fieldName, field] of Object.entries(modelDefinition.fields)) {
612
+ nextRow[fieldName] = applyDefault(data[fieldName], field.config);
613
+ }
614
+ return nextRow;
484
615
  }
485
616
  async function projectRow(schema, model2, row, select) {
486
617
  const modelDefinition = schema.models[model2];
@@ -513,28 +644,27 @@ function createMemoryDriver(seed) {
513
644
  const relation = schema.models[model2].relations[relationName];
514
645
  const relationArgs = value === true ? {} : value;
515
646
  if (relation.kind === "belongsTo") {
516
- const targetRows2 = getRows(relation.target);
517
647
  const foreignValue = row[relation.foreignKey];
518
- const target = targetRows2.find((item) => item.id === foreignValue);
648
+ const targetRows2 = getRows(relation.target).filter(
649
+ (item) => item.id === foreignValue
650
+ );
651
+ const target = applyQuery(targetRows2, relationArgs)[0];
519
652
  return target ? projectRow(schema, relation.target, target, relationArgs.select) : null;
520
653
  }
521
654
  if (relation.kind === "hasOne") {
522
- const targetRows2 = getRows(relation.target);
523
- const target = targetRows2.find((item) => item[relation.foreignKey] === row.id);
655
+ const targetRows2 = getRows(relation.target).filter(
656
+ (item) => item[relation.foreignKey] === row.id
657
+ );
658
+ const target = applyQuery(targetRows2, relationArgs)[0];
524
659
  return target ? projectRow(schema, relation.target, target, relationArgs.select) : null;
525
660
  }
526
661
  if (relation.kind === "hasMany") {
527
662
  const targetRows2 = getRows(relation.target).filter(
528
663
  (item) => item[relation.foreignKey] === row.id
529
664
  );
530
- const sorted2 = sortRows(
531
- targetRows2,
532
- relationArgs.orderBy
533
- );
534
- const paged2 = pageRows(sorted2, relationArgs.skip, relationArgs.take);
535
- const filtered2 = paged2.filter((item) => matchesWhere(item, relationArgs.where));
665
+ const matchedRows2 = applyQuery(targetRows2, relationArgs);
536
666
  return Promise.all(
537
- filtered2.map(
667
+ matchedRows2.map(
538
668
  (item) => projectRow(schema, relation.target, item, relationArgs.select)
539
669
  )
540
670
  );
@@ -546,45 +676,72 @@ function createMemoryDriver(seed) {
546
676
  const targetRows = getRows(relation.target).filter(
547
677
  (item) => targetIds.includes(item.id)
548
678
  );
549
- const sorted = sortRows(
550
- targetRows,
551
- relationArgs.orderBy
552
- );
553
- const paged = pageRows(sorted, relationArgs.skip, relationArgs.take);
554
- const filtered = paged.filter((item) => matchesWhere(item, relationArgs.where));
679
+ const matchedRows = applyQuery(targetRows, relationArgs);
555
680
  return Promise.all(
556
- filtered.map(
681
+ matchedRows.map(
557
682
  (item) => projectRow(schema, relation.target, item, relationArgs.select)
558
683
  )
559
684
  );
560
685
  }
561
686
  const driver = {
562
687
  async findMany(schema, model2, args) {
563
- const rows = applyQuery(model2, args);
688
+ const rows = applyQuery(getRows(model2), args);
564
689
  return Promise.all(rows.map((row) => projectRow(schema, model2, row, args.select)));
565
690
  },
566
691
  async findFirst(schema, model2, args) {
567
- const row = applyQuery(model2, args)[0];
692
+ const row = applyQuery(getRows(model2), args)[0];
693
+ if (!row) return null;
694
+ return projectRow(schema, model2, row, args.select);
695
+ },
696
+ async findUnique(schema, model2, args) {
697
+ const row = applyQuery(getRows(model2), args)[0];
568
698
  if (!row) return null;
569
699
  return projectRow(schema, model2, row, args.select);
570
700
  },
701
+ async count(_schema, model2, args) {
702
+ return applyQuery(getRows(model2), args).length;
703
+ },
571
704
  async create(schema, model2, args) {
572
- const modelDefinition = schema.models[model2];
573
- const nextRow = {};
574
- for (const [fieldName, field] of Object.entries(modelDefinition.fields)) {
575
- nextRow[fieldName] = applyDefault(args.data[fieldName], field.config);
576
- }
705
+ const nextRow = buildRow(schema, model2, args.data);
577
706
  getRows(model2).push(nextRow);
578
707
  return projectRow(schema, model2, nextRow, args.select);
579
708
  },
709
+ async createMany(schema, model2, args) {
710
+ const rows = args.data.map((entry) => buildRow(schema, model2, entry));
711
+ getRows(model2).push(...rows);
712
+ return Promise.all(rows.map((row) => projectRow(schema, model2, row, args.select)));
713
+ },
580
714
  async update(schema, model2, args) {
581
- const rows = getRows(model2);
582
- const row = rows.find((item) => matchesWhere(item, args.where));
715
+ const row = getRows(model2).find((item) => matchesWhere(item, args.where));
583
716
  if (!row) return null;
584
717
  Object.assign(row, args.data);
585
718
  return projectRow(schema, model2, row, args.select);
586
719
  },
720
+ async updateMany(_schema, model2, args) {
721
+ const rows = getRows(model2).filter((item) => matchesWhere(item, args.where));
722
+ for (const row of rows) {
723
+ Object.assign(row, args.data);
724
+ }
725
+ return rows.length;
726
+ },
727
+ async upsert(schema, model2, args) {
728
+ const row = getRows(model2).find((item) => matchesWhere(item, args.where));
729
+ if (row) {
730
+ Object.assign(row, args.update);
731
+ return projectRow(schema, model2, row, args.select);
732
+ }
733
+ const created = buildRow(schema, model2, args.create);
734
+ getRows(model2).push(created);
735
+ return projectRow(schema, model2, created, args.select);
736
+ },
587
737
  async delete(_schema, model2, args) {
738
+ const rows = getRows(model2);
739
+ const index = rows.findIndex((item) => matchesWhere(item, args.where));
740
+ if (index === -1) return 0;
741
+ rows.splice(index, 1);
742
+ return 1;
743
+ },
744
+ async deleteMany(_schema, model2, args) {
588
745
  const rows = getRows(model2);
589
746
  const before = rows.length;
590
747
  state[model2] = rows.filter((item) => !matchesWhere(item, args.where));