@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.cjs CHANGED
@@ -47,17 +47,38 @@ function createModelClient(schema, driver, model2) {
47
47
  findMany(args) {
48
48
  return driver.findMany(schema, model2, args ?? {});
49
49
  },
50
+ findOne(args) {
51
+ return driver.findFirst(schema, model2, args ?? {});
52
+ },
50
53
  findFirst(args) {
51
54
  return driver.findFirst(schema, model2, args ?? {});
52
55
  },
56
+ findUnique(args) {
57
+ return driver.findUnique(schema, model2, args);
58
+ },
59
+ count(args) {
60
+ return driver.count(schema, model2, args);
61
+ },
53
62
  create(args) {
54
63
  return driver.create(schema, model2, args);
55
64
  },
65
+ createMany(args) {
66
+ return driver.createMany(schema, model2, args);
67
+ },
56
68
  update(args) {
57
69
  return driver.update(schema, model2, args);
58
70
  },
71
+ updateMany(args) {
72
+ return driver.updateMany(schema, model2, args);
73
+ },
74
+ upsert(args) {
75
+ return driver.upsert(schema, model2, args);
76
+ },
59
77
  delete(args) {
60
78
  return driver.delete(schema, model2, args);
79
+ },
80
+ deleteMany(args) {
81
+ return driver.deleteMany(schema, model2, args);
61
82
  }
62
83
  };
63
84
  }
@@ -75,6 +96,13 @@ function createOrm(options) {
75
96
  });
76
97
  return run(tx);
77
98
  });
99
+ orm.batch = async (tasks) => orm.transaction(async (tx) => {
100
+ const results = [];
101
+ for (const task of tasks) {
102
+ results.push(await task(tx));
103
+ }
104
+ return results;
105
+ });
78
106
  return orm;
79
107
  }
80
108
 
@@ -198,6 +226,29 @@ function createManifest(schema) {
198
226
  // src/generators.ts
199
227
  var capitalize = (value) => value.charAt(0).toUpperCase() + value.slice(1);
200
228
  var pluralize = (value) => value.endsWith("s") ? value : `${value}s`;
229
+ function resolveReferenceTarget(manifest, model2, foreignKey, fallbackTarget) {
230
+ const reference = model2.fields[foreignKey]?.references;
231
+ if (!reference) {
232
+ return {
233
+ targetModel: fallbackTarget,
234
+ targetField: "id"
235
+ };
236
+ }
237
+ const [targetModel, targetField = "id"] = reference.split(".");
238
+ return {
239
+ targetModel,
240
+ targetField
241
+ };
242
+ }
243
+ function hasExplicitInverseRelation(manifest, modelName, sourceModel, foreignKey) {
244
+ const model2 = manifest.models[modelName];
245
+ if (!model2) return false;
246
+ return Object.values(model2.relations).some((relation) => {
247
+ if (relation.target !== sourceModel) return false;
248
+ if (relation.kind === "belongsTo" || relation.kind === "manyToMany") return false;
249
+ return relation.foreignKey === foreignKey;
250
+ });
251
+ }
201
252
  function prismaType(field) {
202
253
  switch (field.kind) {
203
254
  case "id":
@@ -269,7 +320,7 @@ function sqlType(field, dialect) {
269
320
  return dialect === "mysql" ? "varchar(191)" : "text";
270
321
  }
271
322
  if (field.kind === "string") {
272
- return dialect === "mysql" && field.unique ? "varchar(191)" : "text";
323
+ return dialect === "mysql" && (field.unique || field.references) ? "varchar(191)" : "text";
273
324
  }
274
325
  if (field.kind === "boolean") {
275
326
  return dialect === "sqlite" ? "integer" : "boolean";
@@ -282,6 +333,12 @@ function sqlType(field, dialect) {
282
333
  }
283
334
  return "timestamp";
284
335
  }
336
+ function sqlIdentifier(dialect, value) {
337
+ if (dialect === "mysql") {
338
+ return `\`${value}\``;
339
+ }
340
+ return `"${value}"`;
341
+ }
285
342
  function renderPrismaSchema(schema, options = {}) {
286
343
  const manifest = createManifest(schema);
287
344
  const provider = options.provider ?? "postgresql";
@@ -296,6 +353,7 @@ function renderPrismaSchema(schema, options = {}) {
296
353
  ...reverseRelations.get(targetModel) ?? [],
297
354
  {
298
355
  sourceModel: model2.name,
356
+ foreignKey: field.name,
299
357
  many: !field.unique
300
358
  }
301
359
  ]);
@@ -304,6 +362,8 @@ function renderPrismaSchema(schema, options = {}) {
304
362
  const blocks = Object.values(manifest.models).map((model2) => {
305
363
  const lines = [];
306
364
  const modelName = capitalize(model2.name);
365
+ const relationFieldNames = /* @__PURE__ */ new Set();
366
+ const handledForeignKeys = /* @__PURE__ */ new Set();
307
367
  for (const field of Object.values(model2.fields)) {
308
368
  const fieldType = prismaType(field);
309
369
  const modifiers = [];
@@ -320,16 +380,45 @@ function renderPrismaSchema(schema, options = {}) {
320
380
  lines.push(
321
381
  ` ${field.name} ${fieldType}${field.nullable ? "?" : ""}${modifiers.length ? ` ${modifiers.join(" ")}` : ""}`
322
382
  );
323
- if (field.references) {
324
- const [targetModel, targetField] = field.references.split(".");
383
+ }
384
+ for (const [relationName, relation] of Object.entries(model2.relations)) {
385
+ if (relation.kind === "manyToMany") continue;
386
+ relationFieldNames.add(relationName);
387
+ if (relation.kind === "belongsTo") {
388
+ const { targetField } = resolveReferenceTarget(
389
+ manifest,
390
+ model2,
391
+ relation.foreignKey,
392
+ relation.target
393
+ );
394
+ handledForeignKeys.add(relation.foreignKey);
325
395
  lines.push(
326
- ` ${targetModel} ${capitalize(targetModel)} @relation(fields: [${field.name}], references: [${targetField}])`
396
+ ` ${relationName} ${capitalize(relation.target)} @relation(fields: [${relation.foreignKey}], references: [${targetField}])`
327
397
  );
398
+ continue;
399
+ }
400
+ if (relation.kind === "hasOne") {
401
+ lines.push(` ${relationName} ${capitalize(relation.target)}?`);
402
+ continue;
328
403
  }
404
+ lines.push(` ${relationName} ${capitalize(relation.target)}[]`);
405
+ }
406
+ for (const field of Object.values(model2.fields)) {
407
+ if (!field.references || handledForeignKeys.has(field.name)) continue;
408
+ const [targetModel, targetField] = field.references.split(".");
409
+ if (relationFieldNames.has(targetModel)) continue;
410
+ lines.push(
411
+ ` ${targetModel} ${capitalize(targetModel)} @relation(fields: [${field.name}], references: [${targetField}])`
412
+ );
329
413
  }
330
414
  for (const relation of reverseRelations.get(model2.name) ?? []) {
415
+ if (hasExplicitInverseRelation(manifest, model2.name, relation.sourceModel, relation.foreignKey)) {
416
+ continue;
417
+ }
418
+ const relationName = relation.many ? pluralize(relation.sourceModel) : relation.sourceModel;
419
+ if (relationFieldNames.has(relationName)) continue;
331
420
  lines.push(
332
- relation.many ? ` ${pluralize(relation.sourceModel)} ${capitalize(relation.sourceModel)}[]` : ` ${relation.sourceModel} ${capitalize(relation.sourceModel)}?`
421
+ relation.many ? ` ${relationName} ${capitalize(relation.sourceModel)}[]` : ` ${relationName} ${capitalize(relation.sourceModel)}?`
333
422
  );
334
423
  }
335
424
  const mapLine = model2.table !== modelName ? `
@@ -352,7 +441,7 @@ ${blocks.join("\n\n")}
352
441
  }
353
442
  function renderDrizzleSchema(schema, options) {
354
443
  const manifest = createManifest(schema);
355
- const imports = drizzleImports(options.dialect, manifest).join(", ");
444
+ const coreImports = drizzleImports(options.dialect, manifest).join(", ");
356
445
  const tableFactory = options.dialect === "pg" ? "pgTable" : options.dialect === "mysql" ? "mysqlTable" : "sqliteTable";
357
446
  const modelBlocks = Object.values(manifest.models).map((model2) => {
358
447
  const lines = Object.values(model2.fields).map((field) => {
@@ -367,16 +456,48 @@ function renderDrizzleSchema(schema, options) {
367
456
  ${lines.join(",\n")}
368
457
  });`;
369
458
  });
370
- return `import { ${imports} } from "drizzle-orm/${options.dialect === "pg" ? "pg-core" : options.dialect === "mysql" ? "mysql-core" : "sqlite-core"}";
459
+ const relationBlocks = Object.values(manifest.models).map((model2) => {
460
+ const lines = Object.entries(model2.relations).flatMap(([relationName, relation]) => {
461
+ if (relation.kind === "manyToMany") {
462
+ return [];
463
+ }
464
+ if (relation.kind === "belongsTo") {
465
+ const { targetField } = resolveReferenceTarget(
466
+ manifest,
467
+ model2,
468
+ relation.foreignKey,
469
+ relation.target
470
+ );
471
+ return [
472
+ ` ${relationName}: one(${relation.target}, { fields: [${model2.name}.${relation.foreignKey}], references: [${relation.target}.${targetField}] })`
473
+ ];
474
+ }
475
+ if (relation.kind === "hasOne") {
476
+ return [` ${relationName}: one(${relation.target})`];
477
+ }
478
+ return [` ${relationName}: many(${relation.target})`];
479
+ }).filter(Boolean);
480
+ if (!lines.length) return null;
481
+ return `export const ${model2.name}Relations = relations(${model2.name}, ({ one, many }) => ({
482
+ ${lines.join(",\n")}
483
+ }));`;
484
+ }).filter(Boolean);
485
+ const imports = [
486
+ `import { ${coreImports} } from "drizzle-orm/${options.dialect === "pg" ? "pg-core" : options.dialect === "mysql" ? "mysql-core" : "sqlite-core"}";`,
487
+ relationBlocks.length ? `import { relations } from "drizzle-orm";` : null
488
+ ].filter(Boolean).join("\n");
489
+ return `${imports}
371
490
 
372
- ${modelBlocks.join("\n\n")}
491
+ ${[...modelBlocks, ...relationBlocks].join("\n\n")}
373
492
  `;
374
493
  }
375
494
  function renderSafeSql(schema, options) {
376
495
  const manifest = createManifest(schema);
377
496
  const statements = Object.values(manifest.models).map((model2) => {
378
497
  const columns = Object.values(model2.fields).map((field) => {
379
- const parts = [`${field.column} ${sqlType(field, options.dialect)}`];
498
+ const parts = [
499
+ `${sqlIdentifier(options.dialect, field.column)} ${sqlType(field, options.dialect)}`
500
+ ];
380
501
  if (field.kind === "id") parts.push("primary key");
381
502
  if (!field.nullable) parts.push("not null");
382
503
  if (field.unique && field.kind !== "id") parts.push("unique");
@@ -388,11 +509,17 @@ function renderSafeSql(schema, options) {
388
509
  if (field.references) {
389
510
  const [targetModel, targetField] = field.references.split(".");
390
511
  const targetTable = manifest.models[targetModel]?.table ?? targetModel;
391
- parts.push(`references ${targetTable}(${targetField})`);
512
+ const targetColumn = manifest.models[targetModel]?.fields[targetField]?.column ?? targetField;
513
+ parts.push(
514
+ `references ${sqlIdentifier(options.dialect, targetTable)}(${sqlIdentifier(
515
+ options.dialect,
516
+ targetColumn
517
+ )})`
518
+ );
392
519
  }
393
520
  return ` ${parts.join(" ")}`;
394
521
  });
395
- return `create table if not exists ${model2.table} (
522
+ return `create table if not exists ${sqlIdentifier(options.dialect, model2.table)} (
396
523
  ${columns.join(",\n")}
397
524
  );`;
398
525
  });
@@ -509,6 +636,11 @@ function pageRows(rows, skip, take) {
509
636
  const end = take === void 0 ? void 0 : start + take;
510
637
  return rows.slice(start, end);
511
638
  }
639
+ function applyQuery(rows, args = {}) {
640
+ const filtered = rows.filter((row) => matchesWhere(row, args.where));
641
+ const sorted = sortRows(filtered, args.orderBy);
642
+ return pageRows(sorted, args.skip, args.take);
643
+ }
512
644
  function createMemoryDriver(seed) {
513
645
  const state = structuredClone(seed ?? {});
514
646
  function getRows(model2) {
@@ -516,14 +648,13 @@ function createMemoryDriver(seed) {
516
648
  state[model2] = rows;
517
649
  return rows;
518
650
  }
519
- function applyQuery(model2, args) {
520
- const rows = getRows(model2);
521
- const filtered = rows.filter((row) => matchesWhere(row, args.where));
522
- const sorted = sortRows(
523
- filtered,
524
- args.orderBy
525
- );
526
- return pageRows(sorted, args.skip, args.take);
651
+ function buildRow(schema, model2, data) {
652
+ const modelDefinition = schema.models[model2];
653
+ const nextRow = {};
654
+ for (const [fieldName, field] of Object.entries(modelDefinition.fields)) {
655
+ nextRow[fieldName] = applyDefault(data[fieldName], field.config);
656
+ }
657
+ return nextRow;
527
658
  }
528
659
  async function projectRow(schema, model2, row, select) {
529
660
  const modelDefinition = schema.models[model2];
@@ -556,28 +687,27 @@ function createMemoryDriver(seed) {
556
687
  const relation = schema.models[model2].relations[relationName];
557
688
  const relationArgs = value === true ? {} : value;
558
689
  if (relation.kind === "belongsTo") {
559
- const targetRows2 = getRows(relation.target);
560
690
  const foreignValue = row[relation.foreignKey];
561
- const target = targetRows2.find((item) => item.id === foreignValue);
691
+ const targetRows2 = getRows(relation.target).filter(
692
+ (item) => item.id === foreignValue
693
+ );
694
+ const target = applyQuery(targetRows2, relationArgs)[0];
562
695
  return target ? projectRow(schema, relation.target, target, relationArgs.select) : null;
563
696
  }
564
697
  if (relation.kind === "hasOne") {
565
- const targetRows2 = getRows(relation.target);
566
- const target = targetRows2.find((item) => item[relation.foreignKey] === row.id);
698
+ const targetRows2 = getRows(relation.target).filter(
699
+ (item) => item[relation.foreignKey] === row.id
700
+ );
701
+ const target = applyQuery(targetRows2, relationArgs)[0];
567
702
  return target ? projectRow(schema, relation.target, target, relationArgs.select) : null;
568
703
  }
569
704
  if (relation.kind === "hasMany") {
570
705
  const targetRows2 = getRows(relation.target).filter(
571
706
  (item) => item[relation.foreignKey] === row.id
572
707
  );
573
- const sorted2 = sortRows(
574
- targetRows2,
575
- relationArgs.orderBy
576
- );
577
- const paged2 = pageRows(sorted2, relationArgs.skip, relationArgs.take);
578
- const filtered2 = paged2.filter((item) => matchesWhere(item, relationArgs.where));
708
+ const matchedRows2 = applyQuery(targetRows2, relationArgs);
579
709
  return Promise.all(
580
- filtered2.map(
710
+ matchedRows2.map(
581
711
  (item) => projectRow(schema, relation.target, item, relationArgs.select)
582
712
  )
583
713
  );
@@ -589,45 +719,72 @@ function createMemoryDriver(seed) {
589
719
  const targetRows = getRows(relation.target).filter(
590
720
  (item) => targetIds.includes(item.id)
591
721
  );
592
- const sorted = sortRows(
593
- targetRows,
594
- relationArgs.orderBy
595
- );
596
- const paged = pageRows(sorted, relationArgs.skip, relationArgs.take);
597
- const filtered = paged.filter((item) => matchesWhere(item, relationArgs.where));
722
+ const matchedRows = applyQuery(targetRows, relationArgs);
598
723
  return Promise.all(
599
- filtered.map(
724
+ matchedRows.map(
600
725
  (item) => projectRow(schema, relation.target, item, relationArgs.select)
601
726
  )
602
727
  );
603
728
  }
604
729
  const driver = {
605
730
  async findMany(schema, model2, args) {
606
- const rows = applyQuery(model2, args);
731
+ const rows = applyQuery(getRows(model2), args);
607
732
  return Promise.all(rows.map((row) => projectRow(schema, model2, row, args.select)));
608
733
  },
609
734
  async findFirst(schema, model2, args) {
610
- const row = applyQuery(model2, args)[0];
735
+ const row = applyQuery(getRows(model2), args)[0];
736
+ if (!row) return null;
737
+ return projectRow(schema, model2, row, args.select);
738
+ },
739
+ async findUnique(schema, model2, args) {
740
+ const row = applyQuery(getRows(model2), args)[0];
611
741
  if (!row) return null;
612
742
  return projectRow(schema, model2, row, args.select);
613
743
  },
744
+ async count(_schema, model2, args) {
745
+ return applyQuery(getRows(model2), args).length;
746
+ },
614
747
  async create(schema, model2, args) {
615
- const modelDefinition = schema.models[model2];
616
- const nextRow = {};
617
- for (const [fieldName, field] of Object.entries(modelDefinition.fields)) {
618
- nextRow[fieldName] = applyDefault(args.data[fieldName], field.config);
619
- }
748
+ const nextRow = buildRow(schema, model2, args.data);
620
749
  getRows(model2).push(nextRow);
621
750
  return projectRow(schema, model2, nextRow, args.select);
622
751
  },
752
+ async createMany(schema, model2, args) {
753
+ const rows = args.data.map((entry) => buildRow(schema, model2, entry));
754
+ getRows(model2).push(...rows);
755
+ return Promise.all(rows.map((row) => projectRow(schema, model2, row, args.select)));
756
+ },
623
757
  async update(schema, model2, args) {
624
- const rows = getRows(model2);
625
- const row = rows.find((item) => matchesWhere(item, args.where));
758
+ const row = getRows(model2).find((item) => matchesWhere(item, args.where));
626
759
  if (!row) return null;
627
760
  Object.assign(row, args.data);
628
761
  return projectRow(schema, model2, row, args.select);
629
762
  },
763
+ async updateMany(_schema, model2, args) {
764
+ const rows = getRows(model2).filter((item) => matchesWhere(item, args.where));
765
+ for (const row of rows) {
766
+ Object.assign(row, args.data);
767
+ }
768
+ return rows.length;
769
+ },
770
+ async upsert(schema, model2, args) {
771
+ const row = getRows(model2).find((item) => matchesWhere(item, args.where));
772
+ if (row) {
773
+ Object.assign(row, args.update);
774
+ return projectRow(schema, model2, row, args.select);
775
+ }
776
+ const created = buildRow(schema, model2, args.create);
777
+ getRows(model2).push(created);
778
+ return projectRow(schema, model2, created, args.select);
779
+ },
630
780
  async delete(_schema, model2, args) {
781
+ const rows = getRows(model2);
782
+ const index = rows.findIndex((item) => matchesWhere(item, args.where));
783
+ if (index === -1) return 0;
784
+ rows.splice(index, 1);
785
+ return 1;
786
+ },
787
+ async deleteMany(_schema, model2, args) {
631
788
  const rows = getRows(model2);
632
789
  const before = rows.length;
633
790
  state[model2] = rows.filter((item) => !matchesWhere(item, args.where));