peta-orm 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,6 +23,7 @@ bun add -d kysely-bun-sqlite
23
23
  // db.ts
24
24
  import { Database } from "bun:sqlite"
25
25
  import { BunSqliteDialect } from "kysely-bun-sqlite"
26
+ import type { ColumnShape } from "peta-orm"
26
27
  import { Peta, $t, ArkTypeSchemaConfig, Model, HasMany } from "peta-orm"
27
28
 
28
29
  const t = $t({ schema: new ArkTypeSchemaConfig() })
@@ -32,8 +33,8 @@ class User extends Model {
32
33
  static override columns = {
33
34
  id: t.integer().primaryKey(),
34
35
  name: t.string(255).min(2),
35
- email: t.text().email(),
36
- }
36
+ email: t.text().email().unique(),
37
+ } satisfies ColumnShape
37
38
  static override relations = {
38
39
  posts: new HasMany(() => Post),
39
40
  }
@@ -43,9 +44,9 @@ class Post extends Model {
43
44
  static override table = "posts"
44
45
  static override columns = {
45
46
  id: t.integer().primaryKey(),
46
- userId: t.integer(),
47
+ userId: t.integer().references(() => User, ["id"]),
47
48
  title: t.string(255),
48
- }
49
+ } satisfies ColumnShape
49
50
  }
50
51
 
51
52
  const database = new Database("my-app.db")
@@ -88,18 +89,30 @@ export { peta, User, Post }
88
89
  ### Column Types & Validation
89
90
 
90
91
  ```ts
92
+ import type { ColumnShape } from "peta-orm"
93
+
91
94
  const t = $t({ schema: new ArkTypeSchemaConfig() })
92
95
 
93
96
  class User extends Model {
94
97
  static override columns = {
95
98
  id: t.integer().primaryKey(),
96
99
  name: t.string(255).min(2), // min length
97
- email: t.text().email(), // email format
100
+ email: t.text().email().unique(), // email format + unique constraint
98
101
  age: t.integer().nullable().min(0).max(150).default(0),
99
102
  role: t.enum("admin", "user").default("user"),
100
103
  score: t.double().nullable(),
101
104
  ...t.timestamps(), // createdAt, updatedAt
102
- }
105
+ } satisfies ColumnShape
106
+ }
107
+
108
+ class Post extends Model {
109
+ static override columns = {
110
+ id: t.integer().primaryKey(),
111
+ userId: t.integer().references(() => User, ["id"]), // foreign key
112
+ title: t.string(255),
113
+ slug: t.string().unique(),
114
+ published: t.boolean().default(false),
115
+ } satisfies ColumnShape
103
116
  }
104
117
  ```
105
118
 
@@ -132,6 +145,36 @@ const authors = await User.query().has("posts").execute()
132
145
  const active = await User.query().whereHas("posts", (q) => q.where("published", true)).execute()
133
146
  ```
134
147
 
148
+ ### ManyToMany
149
+
150
+ ```ts
151
+ class Post extends Model {
152
+ static override columns = { id: t.integer().primaryKey(), title: t.string(255) } satisfies ColumnShape
153
+ static override relations = {
154
+ tags: new ManyToMany(() => Tag, {
155
+ through: "post_tags",
156
+ foreignPivotKey: "postId",
157
+ relatedPivotKey: "tagId",
158
+ }),
159
+ }
160
+ }
161
+
162
+ class Tag extends Model {
163
+ static override columns = { id: t.integer().primaryKey(), name: t.string(255) } satisfies ColumnShape
164
+ }
165
+
166
+ // Pivot tables are regular Models — register them so the migration
167
+ // generator includes the pivot table automatically.
168
+ class PostTag extends Model {
169
+ static override table = "post_tags"
170
+ static override columns = {
171
+ id: t.integer().primaryKey(),
172
+ postId: t.integer().references(() => Post, ["id"]),
173
+ tagId: t.integer().references(() => Tag, ["id"]),
174
+ } satisfies ColumnShape
175
+ }
176
+ ```
177
+
135
178
  ### CRUD & Pagination
136
179
 
137
180
  ```ts
@@ -5,6 +5,7 @@ import {
5
5
  CreateViewNode,
6
6
  IdentifierNode,
7
7
  InsertQueryNode,
8
+ ManyToMany,
8
9
  MergeQueryNode,
9
10
  NOOP_QUERY_EXECUTOR,
10
11
  OperatorNode,
@@ -30,7 +31,7 @@ import {
30
31
  parseTable,
31
32
  parseValueExpression,
32
33
  preventAwait
33
- } from "./index-qwps5bne.js";
34
+ } from "./index-gacxptb4.js";
34
35
  import {
35
36
  __require
36
37
  } from "./index-k18nf2r7.js";
@@ -2091,6 +2092,8 @@ class MigrationGenerator {
2091
2092
  const timestamp = new Date().toISOString().replace(/[-:T.Z]/g, "").slice(0, 14);
2092
2093
  const parts = [];
2093
2094
  const indexParts = [];
2095
+ const warnings = [];
2096
+ const registeredTables = new Set([...models.values()].map((m) => m.table).filter(Boolean));
2094
2097
  for (const [, modelClass] of models) {
2095
2098
  const table = modelClass.table;
2096
2099
  if (!table)
@@ -2102,16 +2105,30 @@ class MigrationGenerator {
2102
2105
  indexParts.push(this.#generateCreateIndex(table, colName));
2103
2106
  }
2104
2107
  }
2108
+ for (const [relName, rel] of Object.entries(modelClass.relations ?? {})) {
2109
+ if (rel instanceof ManyToMany) {
2110
+ const through = rel.throughTable;
2111
+ if (!registeredTables.has(through)) {
2112
+ warnings.push(`// \u26A0 Detected ManyToMany "${modelClass.name}.${relName}" references table "${through}" ` + `but no model is registered for it.
2113
+ ` + `// Add a model class with static override table = "${through}" to include the pivot ` + `table in the generated migration.`);
2114
+ }
2115
+ }
2116
+ }
2105
2117
  }
2106
2118
  const upBody = [...parts, ...indexParts].join(`
2107
2119
 
2108
2120
  `);
2109
2121
  const downTables = [...models.values()].filter((m) => m.table).map((m) => ` await db.schema.dropTable("${m.table}").ifExists().execute()`);
2122
+ const warningBlock = warnings.length > 0 ? ` // Warnings:
2123
+ ${warnings.join(`
2124
+ `)}
2125
+
2126
+ ` : "";
2110
2127
  return `import type { Kysely } from "kysely"
2111
2128
  import { sql } from "kysely"
2112
2129
 
2113
2130
  export async function up(db: Kysely<any>): Promise<void> {
2114
- ${upBody}
2131
+ ${warningBlock}${upBody}
2115
2132
  }
2116
2133
 
2117
2134
  export async function down(db: Kysely<any>): Promise<void> {
@@ -2122,7 +2139,7 @@ ${downTables.join(`
2122
2139
  }
2123
2140
  #generateCreateTable(table, columns) {
2124
2141
  const lines = [];
2125
- lines.push(` await db.schema.createTable("${table}")`);
2142
+ lines.push(` await db.schema.createTable("${table}").ifNotExists()`);
2126
2143
  for (const [name, col] of Object.entries(columns)) {
2127
2144
  const typeSql = this.#mapType(col);
2128
2145
  const cb = this.#columnCallback(col);
@@ -7531,4 +7531,289 @@ class Peta {
7531
7531
  }
7532
7532
  }
7533
7533
 
7534
- export { isString, isNumber, isBoolean, isNull, isDate, isBigInt, getLast, freeze, IdentifierNode, CreateTableNode, AliasNode, isOperationNodeSource, OperatorNode, RawNode, parseStringReference, ValueNode, parseValueExpression, ParensNode, preventAwait, InsertQueryNode, MergeQueryNode, QueryNode, createQueryId, WithSchemaPlugin, NOOP_QUERY_EXECUTOR, SetOperationNode, parseTable, CreateViewNode, isModelClass, Peta };
7534
+ // src/relations/relation.ts
7535
+ var thunkCache = new WeakMap;
7536
+ function resolve2(thunk) {
7537
+ let cls = thunkCache.get(thunk);
7538
+ if (!cls) {
7539
+ cls = thunk();
7540
+ thunkCache.set(thunk, cls);
7541
+ }
7542
+ return cls;
7543
+ }
7544
+ function guessForeignKey(modelClass) {
7545
+ const table = modelClass.table;
7546
+ const singular = table.endsWith("s") ? table.slice(0, -1) : table;
7547
+ return `${singular}Id`;
7548
+ }
7549
+ function groupByArray(items, key) {
7550
+ const result = {};
7551
+ for (const item of items) {
7552
+ const v = item.get(key);
7553
+ if (v == null)
7554
+ continue;
7555
+ const k = String(v);
7556
+ if (!result[k])
7557
+ result[k] = [];
7558
+ result[k].push(item);
7559
+ }
7560
+ return result;
7561
+ }
7562
+
7563
+ class Relation {
7564
+ type;
7565
+ #relatedThunk;
7566
+ constructor(type, relatedThunk) {
7567
+ this.type = type;
7568
+ this.#relatedThunk = relatedThunk;
7569
+ }
7570
+ get relatedModelClass() {
7571
+ return resolve2(this.#relatedThunk);
7572
+ }
7573
+ }
7574
+
7575
+ class HasMany extends Relation {
7576
+ #options;
7577
+ constructor(relatedThunk, options = {}) {
7578
+ super("hasMany", relatedThunk);
7579
+ this.#options = options;
7580
+ }
7581
+ get foreignKey() {
7582
+ return this.#options.foreignKey ?? guessForeignKey(this.relatedModelClass);
7583
+ }
7584
+ get localKey() {
7585
+ return this.#options.localKey ?? "id";
7586
+ }
7587
+ query(parent) {
7588
+ return this.relatedModelClass.query().where(this.foreignKey, "=", parent.get(this.localKey));
7589
+ }
7590
+ addEagerConstraints(query, models) {
7591
+ const keys = models.map((m) => m.get(this.localKey)).filter((k) => k != null);
7592
+ if (keys.length > 0) {
7593
+ query.whereIn(this.foreignKey, keys);
7594
+ }
7595
+ }
7596
+ match(models, results, relationName) {
7597
+ const grouped = groupByArray(results, this.foreignKey);
7598
+ for (const model of models) {
7599
+ const key = String(model.get(this.localKey));
7600
+ model.$setRelation(relationName, grouped[key] ?? []);
7601
+ }
7602
+ }
7603
+ async getResults(parent) {
7604
+ return await this.query(parent).execute();
7605
+ }
7606
+ }
7607
+
7608
+ class BelongsTo extends Relation {
7609
+ #options;
7610
+ constructor(relatedThunk, options = {}) {
7611
+ super("belongsTo", relatedThunk);
7612
+ this.#options = options;
7613
+ }
7614
+ get foreignKey() {
7615
+ return this.#options.foreignKey ?? guessForeignKey(this.relatedModelClass);
7616
+ }
7617
+ get localKey() {
7618
+ return this.#options.localKey ?? "id";
7619
+ }
7620
+ query(parent) {
7621
+ return this.relatedModelClass.query().where(this.localKey, "=", parent.get(this.foreignKey));
7622
+ }
7623
+ addEagerConstraints(query, models) {
7624
+ const keys = models.map((m) => m.get(this.foreignKey)).filter((k) => k != null);
7625
+ if (keys.length > 0) {
7626
+ query.whereIn(this.localKey, keys);
7627
+ }
7628
+ }
7629
+ match(models, results, relationName) {
7630
+ const grouped = groupByArray(results, this.localKey);
7631
+ for (const model of models) {
7632
+ const key = String(model.get(this.foreignKey));
7633
+ model.$setRelation(relationName, grouped[key]?.[0] ?? null);
7634
+ }
7635
+ }
7636
+ async getResults(parent) {
7637
+ return await this.query(parent).executeTakeFirst() ?? null;
7638
+ }
7639
+ }
7640
+
7641
+ class HasOne extends Relation {
7642
+ #options;
7643
+ constructor(relatedThunk, options = {}) {
7644
+ super("hasOne", relatedThunk);
7645
+ this.#options = options;
7646
+ }
7647
+ get foreignKey() {
7648
+ return this.#options.foreignKey ?? guessForeignKey(this.relatedModelClass);
7649
+ }
7650
+ get localKey() {
7651
+ return this.#options.localKey ?? "id";
7652
+ }
7653
+ query(parent) {
7654
+ return this.relatedModelClass.query().where(this.foreignKey, "=", parent.get(this.localKey));
7655
+ }
7656
+ addEagerConstraints(query, models) {
7657
+ const keys = models.map((m) => m.get(this.localKey)).filter((k) => k != null);
7658
+ if (keys.length > 0) {
7659
+ query.whereIn(this.foreignKey, keys);
7660
+ }
7661
+ }
7662
+ match(models, results, relationName) {
7663
+ const grouped = groupByArray(results, this.foreignKey);
7664
+ for (const model of models) {
7665
+ const key = String(model.get(this.localKey));
7666
+ model.$setRelation(relationName, grouped[key]?.[0] ?? null);
7667
+ }
7668
+ }
7669
+ async getResults(parent) {
7670
+ return await this.query(parent).executeTakeFirst() ?? null;
7671
+ }
7672
+ }
7673
+
7674
+ class ManyToMany extends Relation {
7675
+ #options;
7676
+ #pivotExtras;
7677
+ constructor(relatedThunk, options) {
7678
+ super("manyToMany", relatedThunk);
7679
+ this.#options = options;
7680
+ this.#pivotExtras = options.pivotExtras ?? [];
7681
+ }
7682
+ get foreignKey() {
7683
+ return this.#options.foreignKey ?? guessForeignKey(this.relatedModelClass);
7684
+ }
7685
+ get localKey() {
7686
+ return this.#options.localKey ?? "id";
7687
+ }
7688
+ get throughTable() {
7689
+ return this.#options.through;
7690
+ }
7691
+ get foreignPivotKey() {
7692
+ return this.#options.foreignPivotKey ?? snakeCase(this.foreignKey);
7693
+ }
7694
+ get relatedPivotKey() {
7695
+ return this.#options.relatedPivotKey ?? snakeCase(guessForeignKey(this.relatedModelClass));
7696
+ }
7697
+ #hasExtras() {
7698
+ return this.#pivotExtras.length > 0;
7699
+ }
7700
+ query(parent) {
7701
+ const parentKey = parent.get(this.localKey);
7702
+ const peta = this.relatedModelClass.peta;
7703
+ if (!peta)
7704
+ return this.relatedModelClass.query();
7705
+ if (this.#hasExtras()) {
7706
+ const _relatedTable = this.relatedModelClass.table;
7707
+ const peta2 = this.relatedModelClass.peta;
7708
+ if (!peta2)
7709
+ return this.relatedModelClass.query();
7710
+ const subQb = peta2.kysely.selectFrom(this.throughTable).select(this.relatedPivotKey).where(this.foreignPivotKey, "=", parentKey);
7711
+ return this.relatedModelClass.query().whereIn("id", subQb);
7712
+ }
7713
+ const subquery = peta.kysely.selectFrom(this.throughTable).select(this.relatedPivotKey).where(this.foreignPivotKey, "=", parentKey);
7714
+ return this.relatedModelClass.query().whereIn("id", subquery);
7715
+ }
7716
+ addEagerConstraints(query, models) {
7717
+ const keys = models.map((m) => m.get(this.localKey)).filter((k) => k != null);
7718
+ if (keys.length === 0) {
7719
+ query.whereIn("id", []);
7720
+ return;
7721
+ }
7722
+ const relatedTable = this.relatedModelClass.table;
7723
+ query.innerJoin(this.throughTable, `${this.throughTable}.${this.relatedPivotKey}`, `${relatedTable}.${this.localKey}`);
7724
+ query.whereIn(`${this.throughTable}.${this.foreignPivotKey}`, keys);
7725
+ }
7726
+ match(models, results, relationName) {
7727
+ const grouped = groupByArray(results, `_pivot_${this.foreignPivotKey}`);
7728
+ for (const model of models) {
7729
+ const key = String(model.get(this.localKey));
7730
+ const items = grouped[key] ?? [];
7731
+ for (const item of items) {
7732
+ const pivotData = {};
7733
+ for (const ek of Object.keys(item.attributes ?? {})) {
7734
+ if (ek.startsWith("_pivot_")) {
7735
+ pivotData[ek.slice(7)] = item.get(ek);
7736
+ }
7737
+ }
7738
+ if (Object.keys(pivotData).length > 0) {
7739
+ item.$setRelation("_pivot", pivotData);
7740
+ }
7741
+ }
7742
+ model.$setRelation(relationName, items);
7743
+ }
7744
+ }
7745
+ async getResults(parent) {
7746
+ return await this.query(parent).execute();
7747
+ }
7748
+ }
7749
+
7750
+ class HasManyThrough extends Relation {
7751
+ #options;
7752
+ #throughThunk;
7753
+ constructor(relatedThunk, throughThunk, options = {}) {
7754
+ super("hasManyThrough", relatedThunk);
7755
+ this.#throughThunk = throughThunk;
7756
+ this.#options = options;
7757
+ }
7758
+ get foreignKey() {
7759
+ return this.#options.foreignKey ?? guessForeignKey(resolve2(this.#throughThunk));
7760
+ }
7761
+ get localKey() {
7762
+ return this.#options.localKey ?? "id";
7763
+ }
7764
+ get throughModelClass() {
7765
+ return resolve2(this.#throughThunk);
7766
+ }
7767
+ get throughForeignKey() {
7768
+ return this.#options.throughForeignKey ?? guessForeignKey(this.relatedModelClass);
7769
+ }
7770
+ get throughLocalKey() {
7771
+ return this.#options.throughLocalKey ?? guessForeignKey(this.throughModelClass);
7772
+ }
7773
+ query(parent) {
7774
+ const parentKey = parent.get(this.localKey);
7775
+ const peta = this.relatedModelClass.peta;
7776
+ if (!peta)
7777
+ return this.relatedModelClass.query();
7778
+ const throughTable = this.throughModelClass.table;
7779
+ const _relatedTable = this.relatedModelClass.table;
7780
+ const subquery = peta.kysely.selectFrom(throughTable).select(this.throughForeignKey).where(this.foreignKey, "=", parentKey);
7781
+ return this.relatedModelClass.query().whereIn("id", subquery);
7782
+ }
7783
+ addEagerConstraints(query, models) {
7784
+ const keys = models.map((m) => m.get(this.localKey)).filter((k) => k != null);
7785
+ if (keys.length === 0) {
7786
+ query.whereIn("id", []);
7787
+ return;
7788
+ }
7789
+ const throughTable = this.throughModelClass.table;
7790
+ const relatedTable = this.relatedModelClass.table;
7791
+ query.innerJoin(throughTable, `${throughTable}.${this.throughForeignKey}`, `${relatedTable}.${this.localKey}`);
7792
+ query.whereIn(`${throughTable}.${this.foreignKey}`, keys);
7793
+ }
7794
+ match(models, results, relationName) {
7795
+ const _throughTable = this.throughModelClass.table;
7796
+ const grouped = groupByArray(results, `_through_${this.foreignKey}`);
7797
+ for (const model of models) {
7798
+ const key = String(model.get(this.localKey));
7799
+ model.$setRelation(relationName, grouped[key] ?? []);
7800
+ }
7801
+ }
7802
+ async getResults(parent) {
7803
+ const parentKey = parent.get(this.localKey);
7804
+ const peta = this.relatedModelClass.peta;
7805
+ if (!peta)
7806
+ return [];
7807
+ const throughTable = this.throughModelClass.table;
7808
+ const rows = await peta.kysely.selectFrom(throughTable).select(this.throughForeignKey).where(this.foreignKey, "=", parentKey).execute();
7809
+ const ids = rows.map((r) => r[this.throughForeignKey]).filter(Boolean);
7810
+ if (ids.length === 0)
7811
+ return [];
7812
+ return await this.relatedModelClass.query().whereIn("id", ids).execute();
7813
+ }
7814
+ }
7815
+ function snakeCase(str) {
7816
+ return str.charAt(0).toLowerCase() + str.slice(1).replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
7817
+ }
7818
+
7819
+ export { isString, isNumber, isBoolean, isNull, isDate, isBigInt, getLast, freeze, IdentifierNode, CreateTableNode, AliasNode, isOperationNodeSource, OperatorNode, RawNode, parseStringReference, ValueNode, parseValueExpression, ParensNode, preventAwait, InsertQueryNode, MergeQueryNode, QueryNode, createQueryId, WithSchemaPlugin, NOOP_QUERY_EXECUTOR, SetOperationNode, parseTable, CreateViewNode, isModelClass, Peta, HasMany, BelongsTo, HasOne, ManyToMany, HasManyThrough };
package/dist/index.js CHANGED
@@ -1,7 +1,12 @@
1
1
  // @bun
2
2
  import {
3
+ BelongsTo,
4
+ HasMany,
5
+ HasManyThrough,
6
+ HasOne,
7
+ ManyToMany,
3
8
  Peta
4
- } from "./index-qwps5bne.js";
9
+ } from "./index-gacxptb4.js";
5
10
  import {
6
11
  ArkTypeSchemaConfig,
7
12
  Collection,
@@ -609,291 +614,6 @@ class Model {
609
614
  };
610
615
  }
611
616
  }
612
- // src/relations/relation.ts
613
- var thunkCache = new WeakMap;
614
- function resolve(thunk) {
615
- let cls = thunkCache.get(thunk);
616
- if (!cls) {
617
- cls = thunk();
618
- thunkCache.set(thunk, cls);
619
- }
620
- return cls;
621
- }
622
- function guessForeignKey(modelClass) {
623
- const table = modelClass.table;
624
- const singular = table.endsWith("s") ? table.slice(0, -1) : table;
625
- return `${singular}Id`;
626
- }
627
- function groupByArray(items, key) {
628
- const result = {};
629
- for (const item of items) {
630
- const v = item.get(key);
631
- if (v == null)
632
- continue;
633
- const k = String(v);
634
- if (!result[k])
635
- result[k] = [];
636
- result[k].push(item);
637
- }
638
- return result;
639
- }
640
-
641
- class Relation {
642
- type;
643
- #relatedThunk;
644
- constructor(type, relatedThunk) {
645
- this.type = type;
646
- this.#relatedThunk = relatedThunk;
647
- }
648
- get relatedModelClass() {
649
- return resolve(this.#relatedThunk);
650
- }
651
- }
652
-
653
- class HasMany extends Relation {
654
- #options;
655
- constructor(relatedThunk, options = {}) {
656
- super("hasMany", relatedThunk);
657
- this.#options = options;
658
- }
659
- get foreignKey() {
660
- return this.#options.foreignKey ?? guessForeignKey(this.relatedModelClass);
661
- }
662
- get localKey() {
663
- return this.#options.localKey ?? "id";
664
- }
665
- query(parent) {
666
- return this.relatedModelClass.query().where(this.foreignKey, "=", parent.get(this.localKey));
667
- }
668
- addEagerConstraints(query, models) {
669
- const keys = models.map((m) => m.get(this.localKey)).filter((k) => k != null);
670
- if (keys.length > 0) {
671
- query.whereIn(this.foreignKey, keys);
672
- }
673
- }
674
- match(models, results, relationName) {
675
- const grouped = groupByArray(results, this.foreignKey);
676
- for (const model of models) {
677
- const key = String(model.get(this.localKey));
678
- model.$setRelation(relationName, grouped[key] ?? []);
679
- }
680
- }
681
- async getResults(parent) {
682
- return await this.query(parent).execute();
683
- }
684
- }
685
-
686
- class BelongsTo extends Relation {
687
- #options;
688
- constructor(relatedThunk, options = {}) {
689
- super("belongsTo", relatedThunk);
690
- this.#options = options;
691
- }
692
- get foreignKey() {
693
- return this.#options.foreignKey ?? guessForeignKey(this.relatedModelClass);
694
- }
695
- get localKey() {
696
- return this.#options.localKey ?? "id";
697
- }
698
- query(parent) {
699
- return this.relatedModelClass.query().where(this.localKey, "=", parent.get(this.foreignKey));
700
- }
701
- addEagerConstraints(query, models) {
702
- const keys = models.map((m) => m.get(this.foreignKey)).filter((k) => k != null);
703
- if (keys.length > 0) {
704
- query.whereIn(this.localKey, keys);
705
- }
706
- }
707
- match(models, results, relationName) {
708
- const grouped = groupByArray(results, this.localKey);
709
- for (const model of models) {
710
- const key = String(model.get(this.foreignKey));
711
- model.$setRelation(relationName, grouped[key]?.[0] ?? null);
712
- }
713
- }
714
- async getResults(parent) {
715
- return await this.query(parent).executeTakeFirst() ?? null;
716
- }
717
- }
718
-
719
- class HasOne extends Relation {
720
- #options;
721
- constructor(relatedThunk, options = {}) {
722
- super("hasOne", relatedThunk);
723
- this.#options = options;
724
- }
725
- get foreignKey() {
726
- return this.#options.foreignKey ?? guessForeignKey(this.relatedModelClass);
727
- }
728
- get localKey() {
729
- return this.#options.localKey ?? "id";
730
- }
731
- query(parent) {
732
- return this.relatedModelClass.query().where(this.foreignKey, "=", parent.get(this.localKey));
733
- }
734
- addEagerConstraints(query, models) {
735
- const keys = models.map((m) => m.get(this.localKey)).filter((k) => k != null);
736
- if (keys.length > 0) {
737
- query.whereIn(this.foreignKey, keys);
738
- }
739
- }
740
- match(models, results, relationName) {
741
- const grouped = groupByArray(results, this.foreignKey);
742
- for (const model of models) {
743
- const key = String(model.get(this.localKey));
744
- model.$setRelation(relationName, grouped[key]?.[0] ?? null);
745
- }
746
- }
747
- async getResults(parent) {
748
- return await this.query(parent).executeTakeFirst() ?? null;
749
- }
750
- }
751
-
752
- class ManyToMany extends Relation {
753
- #options;
754
- #pivotExtras;
755
- constructor(relatedThunk, options) {
756
- super("manyToMany", relatedThunk);
757
- this.#options = options;
758
- this.#pivotExtras = options.pivotExtras ?? [];
759
- }
760
- get foreignKey() {
761
- return this.#options.foreignKey ?? guessForeignKey(this.relatedModelClass);
762
- }
763
- get localKey() {
764
- return this.#options.localKey ?? "id";
765
- }
766
- get throughTable() {
767
- return this.#options.through;
768
- }
769
- get foreignPivotKey() {
770
- return this.#options.foreignPivotKey ?? snakeCase(this.foreignKey);
771
- }
772
- get relatedPivotKey() {
773
- return this.#options.relatedPivotKey ?? snakeCase(guessForeignKey(this.relatedModelClass));
774
- }
775
- #hasExtras() {
776
- return this.#pivotExtras.length > 0;
777
- }
778
- query(parent) {
779
- const parentKey = parent.get(this.localKey);
780
- const peta = this.relatedModelClass.peta;
781
- if (!peta)
782
- return this.relatedModelClass.query();
783
- if (this.#hasExtras()) {
784
- const _relatedTable = this.relatedModelClass.table;
785
- const peta2 = this.relatedModelClass.peta;
786
- if (!peta2)
787
- return this.relatedModelClass.query();
788
- const subQb = peta2.kysely.selectFrom(this.throughTable).select(this.relatedPivotKey).where(this.foreignPivotKey, "=", parentKey);
789
- return this.relatedModelClass.query().whereIn("id", subQb);
790
- }
791
- const subquery = peta.kysely.selectFrom(this.throughTable).select(this.relatedPivotKey).where(this.foreignPivotKey, "=", parentKey);
792
- return this.relatedModelClass.query().whereIn("id", subquery);
793
- }
794
- addEagerConstraints(query, models) {
795
- const keys = models.map((m) => m.get(this.localKey)).filter((k) => k != null);
796
- if (keys.length === 0) {
797
- query.whereIn("id", []);
798
- return;
799
- }
800
- const relatedTable = this.relatedModelClass.table;
801
- query.innerJoin(this.throughTable, `${this.throughTable}.${this.relatedPivotKey}`, `${relatedTable}.${this.localKey}`);
802
- query.whereIn(`${this.throughTable}.${this.foreignPivotKey}`, keys);
803
- }
804
- match(models, results, relationName) {
805
- const grouped = groupByArray(results, `_pivot_${this.foreignPivotKey}`);
806
- for (const model of models) {
807
- const key = String(model.get(this.localKey));
808
- const items = grouped[key] ?? [];
809
- for (const item of items) {
810
- const pivotData = {};
811
- for (const ek of Object.keys(item.attributes ?? {})) {
812
- if (ek.startsWith("_pivot_")) {
813
- pivotData[ek.slice(7)] = item.get(ek);
814
- }
815
- }
816
- if (Object.keys(pivotData).length > 0) {
817
- item.$setRelation("_pivot", pivotData);
818
- }
819
- }
820
- model.$setRelation(relationName, items);
821
- }
822
- }
823
- async getResults(parent) {
824
- return await this.query(parent).execute();
825
- }
826
- }
827
-
828
- class HasManyThrough extends Relation {
829
- #options;
830
- #throughThunk;
831
- constructor(relatedThunk, throughThunk, options = {}) {
832
- super("hasManyThrough", relatedThunk);
833
- this.#throughThunk = throughThunk;
834
- this.#options = options;
835
- }
836
- get foreignKey() {
837
- return this.#options.foreignKey ?? guessForeignKey(resolve(this.#throughThunk));
838
- }
839
- get localKey() {
840
- return this.#options.localKey ?? "id";
841
- }
842
- get throughModelClass() {
843
- return resolve(this.#throughThunk);
844
- }
845
- get throughForeignKey() {
846
- return this.#options.throughForeignKey ?? guessForeignKey(this.relatedModelClass);
847
- }
848
- get throughLocalKey() {
849
- return this.#options.throughLocalKey ?? guessForeignKey(this.throughModelClass);
850
- }
851
- query(parent) {
852
- const parentKey = parent.get(this.localKey);
853
- const peta = this.relatedModelClass.peta;
854
- if (!peta)
855
- return this.relatedModelClass.query();
856
- const throughTable = this.throughModelClass.table;
857
- const _relatedTable = this.relatedModelClass.table;
858
- const subquery = peta.kysely.selectFrom(throughTable).select(this.throughForeignKey).where(this.foreignKey, "=", parentKey);
859
- return this.relatedModelClass.query().whereIn("id", subquery);
860
- }
861
- addEagerConstraints(query, models) {
862
- const keys = models.map((m) => m.get(this.localKey)).filter((k) => k != null);
863
- if (keys.length === 0) {
864
- query.whereIn("id", []);
865
- return;
866
- }
867
- const throughTable = this.throughModelClass.table;
868
- const relatedTable = this.relatedModelClass.table;
869
- query.innerJoin(throughTable, `${throughTable}.${this.throughForeignKey}`, `${relatedTable}.${this.localKey}`);
870
- query.whereIn(`${throughTable}.${this.foreignKey}`, keys);
871
- }
872
- match(models, results, relationName) {
873
- const _throughTable = this.throughModelClass.table;
874
- const grouped = groupByArray(results, `_through_${this.foreignKey}`);
875
- for (const model of models) {
876
- const key = String(model.get(this.localKey));
877
- model.$setRelation(relationName, grouped[key] ?? []);
878
- }
879
- }
880
- async getResults(parent) {
881
- const parentKey = parent.get(this.localKey);
882
- const peta = this.relatedModelClass.peta;
883
- if (!peta)
884
- return [];
885
- const throughTable = this.throughModelClass.table;
886
- const rows = await peta.kysely.selectFrom(throughTable).select(this.throughForeignKey).where(this.foreignKey, "=", parentKey).execute();
887
- const ids = rows.map((r) => r[this.throughForeignKey]).filter(Boolean);
888
- if (ids.length === 0)
889
- return [];
890
- return await this.relatedModelClass.query().whereIn("id", ids).execute();
891
- }
892
- }
893
- function snakeCase(str) {
894
- return str.charAt(0).toLowerCase() + str.slice(1).replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
895
- }
896
-
897
617
  // src/relations/morph.ts
898
618
  class MorphTo extends BelongsTo {
899
619
  morphType;
@@ -4,8 +4,8 @@ import {
4
4
  MigrationRunner,
5
5
  loadConfig,
6
6
  loadMigrationFiles
7
- } from "../index-zth6eamb.js";
8
- import"../index-qwps5bne.js";
7
+ } from "../index-bv1vk6ec.js";
8
+ import"../index-gacxptb4.js";
9
9
  import {
10
10
  __export
11
11
  } from "../index-k18nf2r7.js";
@@ -1 +1 @@
1
- {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/migrations/generator.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAEhD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,qBAAa,kBAAkB;;IAC7B,wBAAwB,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM;CAqIlG"}
1
+ {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/migrations/generator.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAGhD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,qBAAa,kBAAkB;;IAC7B,wBAAwB,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM;CAyJlG"}
@@ -5,8 +5,8 @@ import {
5
5
  defineConfig,
6
6
  loadConfig,
7
7
  loadMigrationFiles
8
- } from "../index-zth6eamb.js";
9
- import"../index-qwps5bne.js";
8
+ } from "../index-bv1vk6ec.js";
9
+ import"../index-gacxptb4.js";
10
10
  import"../index-k18nf2r7.js";
11
11
  export {
12
12
  loadMigrationFiles,
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "peta-orm",
3
3
  "type": "module",
4
4
  "private": false,
5
- "version": "0.2.2",
5
+ "version": "0.2.4",
6
6
  "description": "ORM for Bun, built on Kysely",
7
7
  "license": "MIT",
8
8
  "module": "src/index.ts",