@bunnykit/orm 0.1.13 → 0.1.15

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
@@ -354,6 +354,8 @@ await Schema.create("products", (table) => {
354
354
  | `jsonb(name)` | JSONB (Postgres) |
355
355
  | `binary(name)` | BLOB / BYTEA |
356
356
  | `uuid(name)` | UUID |
357
+ | `foreignId(name)` | Unsigned big integer foreign key |
358
+ | `foreignUuid(name)` | UUID foreign key |
357
359
  | `enum(name, values)` | ENUM |
358
360
 
359
361
  ### Column Modifiers
@@ -376,6 +378,11 @@ await Schema.table("users", (table) => {
376
378
  table.timestamp("last_login").nullable();
377
379
  });
378
380
 
381
+ // Change columns on MySQL/PostgreSQL
382
+ await Schema.table("users", (table) => {
383
+ table.string("name", 150).nullable().change();
384
+ });
385
+
379
386
  // Rename
380
387
  await Schema.rename("users", "customers");
381
388
 
@@ -413,6 +420,32 @@ await Schema.create("posts", (table) => {
413
420
  });
414
421
  ```
415
422
 
423
+ Shortcut form:
424
+
425
+ ```ts
426
+ await Schema.create("posts", (table) => {
427
+ table.increments("id");
428
+ table.foreignId("user_id").constrained().cascadeOnDelete();
429
+ table.string("title");
430
+ table.timestamps();
431
+ });
432
+ ```
433
+
434
+ Polymorphic column shortcuts:
435
+
436
+ ```ts
437
+ await Schema.create("comments", (table) => {
438
+ table.increments("id");
439
+ table.uuidMorphs("commentable"); // commentable_type + UUID commentable_id + index
440
+ table.text("body");
441
+ });
442
+
443
+ await Schema.create("activity", (table) => {
444
+ table.increments("id");
445
+ table.nullableMorphs("subject"); // nullable subject_type + subject_id + index
446
+ });
447
+ ```
448
+
416
449
  ---
417
450
 
418
451
  ## Query Builder
@@ -996,6 +1029,20 @@ class Video extends Model {
996
1029
  }
997
1030
  ```
998
1031
 
1032
+ `morphTo` relations can be eager loaded:
1033
+
1034
+ ```ts
1035
+ const comments = await Comment.with("commentable").get();
1036
+
1037
+ comments[0].getRelation("commentable"); // Post | Video | null
1038
+ ```
1039
+
1040
+ Relation names are inferred for `with(...)` when your model methods return relation objects, while raw strings still work for dynamic relation names:
1041
+
1042
+ ```ts
1043
+ await Post.with("comments").get();
1044
+ ```
1045
+
999
1046
  ### Many-to-Many Polymorphic
1000
1047
 
1001
1048
  ```ts
@@ -1080,6 +1127,12 @@ bun run bunny migrate:rollback
1080
1127
 
1081
1128
  # Show migration status
1082
1129
  bun run bunny migrate:status
1130
+
1131
+ # Dump the current database schema
1132
+ bun run bunny schema:dump ./database/schema.sql
1133
+
1134
+ # Dump schema and mark configured migrations as ran
1135
+ bun run bunny schema:squash ./database/schema.sql
1083
1136
  ```
1084
1137
 
1085
1138
  ### Migration File Structure
@@ -1105,6 +1158,37 @@ export default class CreateUsersTable extends Migration {
1105
1158
 
1106
1159
  Migrations are tracked in a `migrations` table (auto-created on first run).
1107
1160
 
1161
+ ### Migration Events
1162
+
1163
+ Listen to migration lifecycle events when running migrations programmatically:
1164
+
1165
+ ```ts
1166
+ import { Migrator } from "@bunnykit/orm";
1167
+
1168
+ Migrator.on("migrating", ({ migration }) => {
1169
+ console.log(`Starting ${migration}`);
1170
+ });
1171
+
1172
+ Migrator.on("migrated", ({ migration }) => {
1173
+ console.log(`Finished ${migration}`);
1174
+ });
1175
+ ```
1176
+
1177
+ Available events: `migrating`, `migrated`, `rollingBack`, `rolledBack`, `schemaDumped`, and `schemaSquashed`.
1178
+
1179
+ ### Schema Dumps
1180
+
1181
+ Programmatic schema dumps are available through the migrator:
1182
+
1183
+ ```ts
1184
+ const migrator = new Migrator(connection, "./database/migrations");
1185
+
1186
+ await migrator.dumpSchema("./database/schema.sql");
1187
+ await migrator.squash("./database/schema.sql");
1188
+ ```
1189
+
1190
+ `squash()` writes the schema dump and marks the configured migration files as already ran in the migrations table.
1191
+
1108
1192
  ### Auto Type Generation
1109
1193
 
1110
1194
  If you set `typesOutDir` in your config, types are **automatically regenerated** after every `migrate` and `migrate:rollback`.
package/dist/bin/bunny.js CHANGED
@@ -414,7 +414,19 @@ async function main() {
414
414
  }
415
415
  const config = await loadConfig();
416
416
  const { connection } = configureBunny(config);
417
- if (command === "migrate") {
417
+ if (command === "schema:dump" || command === "schema:squash") {
418
+ const outputPath = args[1] || "./database/schema.sql";
419
+ const migrator = new Migrator(connection, getDefaultMigrationsPath(config), config.typesOutDir, createTypeGeneratorOptions(config));
420
+ if (command === "schema:dump") {
421
+ await migrator.dumpSchema(outputPath);
422
+ console.log(`Schema dumped to ${outputPath}`);
423
+ }
424
+ else {
425
+ await migrator.squash(outputPath);
426
+ console.log(`Schema squashed to ${outputPath}`);
427
+ }
428
+ }
429
+ else if (command === "migrate") {
418
430
  await runConfiguredMigrationCommand(command, config, connection, parseMigrationTarget(args.slice(1)));
419
431
  }
420
432
  else if (command === "migrate:rollback") {
@@ -432,6 +444,8 @@ async function main() {
432
444
  console.log(" bun run bunny migrate:make <name> [dir] Create a new migration");
433
445
  console.log(" bun run bunny migrate:rollback Rollback the last batch");
434
446
  console.log(" bun run bunny migrate:status Show migration status");
447
+ console.log(" bun run bunny schema:dump [path] Dump the current database schema");
448
+ console.log(" bun run bunny schema:squash [path] Dump schema and mark configured migrations as ran");
435
449
  console.log(" bun run bunny types:generate [dir] Generate model type declarations from DB schema");
436
450
  console.log(" bun run bunny repl Start a Bunny REPL with Model, Schema, and db loaded");
437
451
  console.log(" Falls back to in-memory SQLite when no config is present");
@@ -76,7 +76,6 @@ export class Connection {
76
76
  return `"${value.replace(/"/g, '""')}"`;
77
77
  }
78
78
  async query(sqlString) {
79
- // Use unsafe for generated SQL strings
80
79
  return (await this.driver.unsafe(sqlString));
81
80
  }
82
81
  async run(sqlString) {
@@ -21,15 +21,28 @@ export type TenantResolution = {
21
21
  setting?: string;
22
22
  };
23
23
  export type TenantResolver = (tenantId: string) => TenantResolution | Promise<TenantResolution>;
24
+ export interface PoolConfig {
25
+ maxConnections?: number;
26
+ minConnections?: number;
27
+ idleTimeout?: number;
28
+ }
24
29
  export declare class ConnectionManager {
25
30
  private static defaultConnection?;
26
31
  private static connections;
32
+ private static pools;
33
+ private static poolConfigs;
27
34
  private static tenantResolver?;
28
35
  private static tenantCache;
29
36
  static setDefault(connection: Connection): void;
30
37
  static getDefault(): Connection | undefined;
38
+ static setPoolConfig(name: string, config: PoolConfig): void;
39
+ static getPoolConfig(name: string): PoolConfig | undefined;
40
+ private static getPooledConnection;
41
+ private static releasePooledConnection;
31
42
  static add(name: string, connection: Connection | ConnectionConfig): Connection;
32
43
  static get(name: string): Connection | undefined;
44
+ static getPooled(name: string, config?: ConnectionConfig): Promise<Connection>;
45
+ static release(name: string, connection: Connection): void;
33
46
  static require(name: string): Connection;
34
47
  static setTenantResolver(resolver: TenantResolver): void;
35
48
  static resolveTenant(tenantId: string): Promise<ActiveTenantContext>;
@@ -2,6 +2,8 @@ import { Connection } from "./Connection.js";
2
2
  export class ConnectionManager {
3
3
  static defaultConnection;
4
4
  static connections = new Map();
5
+ static pools = new Map();
6
+ static poolConfigs = new Map();
5
7
  static tenantResolver;
6
8
  static tenantCache = new Map();
7
9
  static setDefault(connection) {
@@ -10,6 +12,67 @@ export class ConnectionManager {
10
12
  static getDefault() {
11
13
  return this.defaultConnection;
12
14
  }
15
+ static setPoolConfig(name, config) {
16
+ this.poolConfigs.set(name, { maxConnections: 10, minConnections: 1, idleTimeout: 30000, ...config });
17
+ }
18
+ static getPoolConfig(name) {
19
+ return this.poolConfigs.get(name);
20
+ }
21
+ static async getPooledConnection(name, config) {
22
+ const poolConfig = this.poolConfigs.get(name) || { maxConnections: 10, minConnections: 1, idleTimeout: 30000 };
23
+ let pool = this.pools.get(name);
24
+ if (!pool) {
25
+ pool = [];
26
+ this.pools.set(name, pool);
27
+ }
28
+ const now = Date.now();
29
+ const idleTimeout = poolConfig.idleTimeout || 30000;
30
+ while (pool.length > 0) {
31
+ const idx = pool.findIndex((c) => !c.inUse && (now - c.lastUsed) < idleTimeout);
32
+ if (idx === -1)
33
+ break;
34
+ const pooled = pool[idx];
35
+ pool.splice(idx, 1);
36
+ try {
37
+ pooled.connection.query("SELECT 1").catch(() => null);
38
+ pooled.inUse = true;
39
+ return pooled.connection;
40
+ }
41
+ catch {
42
+ await pooled.connection.close().catch(() => null);
43
+ }
44
+ }
45
+ if (pool.length < (poolConfig.maxConnections || 10)) {
46
+ const connection = new Connection(config);
47
+ pool.push({ connection, lastUsed: Date.now(), inUse: true });
48
+ return connection;
49
+ }
50
+ return new Promise((resolve, reject) => {
51
+ const checkInterval = setInterval(() => {
52
+ const available = pool.find((c) => !c.inUse);
53
+ if (available) {
54
+ clearInterval(checkInterval);
55
+ available.inUse = true;
56
+ available.lastUsed = Date.now();
57
+ resolve(available.connection);
58
+ }
59
+ }, 50);
60
+ setTimeout(() => {
61
+ clearInterval(checkInterval);
62
+ reject(new Error(`Connection pool exhausted for "${name}"`));
63
+ }, 30000);
64
+ });
65
+ }
66
+ static releasePooledConnection(name, connection) {
67
+ const pool = this.pools.get(name);
68
+ if (!pool)
69
+ return;
70
+ const pooled = pool.find((p) => p.connection === connection);
71
+ if (pooled) {
72
+ pooled.inUse = false;
73
+ pooled.lastUsed = Date.now();
74
+ }
75
+ }
13
76
  static add(name, connection) {
14
77
  const resolved = connection instanceof Connection ? connection : new Connection(connection);
15
78
  this.connections.set(name, resolved);
@@ -18,6 +81,18 @@ export class ConnectionManager {
18
81
  static get(name) {
19
82
  return this.connections.get(name);
20
83
  }
84
+ static async getPooled(name, config) {
85
+ if (config) {
86
+ return this.getPooledConnection(name, config);
87
+ }
88
+ const existing = this.connections.get(name);
89
+ if (existing)
90
+ return existing;
91
+ throw new Error(`No connection registered for "${name}". Use add() first or provide config.`);
92
+ }
93
+ static release(name, connection) {
94
+ this.releasePooledConnection(name, connection);
95
+ }
21
96
  static require(name) {
22
97
  const connection = this.get(name);
23
98
  if (!connection) {
@@ -93,9 +168,15 @@ export class ConnectionManager {
93
168
  }
94
169
  static async closeAll() {
95
170
  const connections = new Set(this.connections.values());
171
+ for (const pool of this.pools.values()) {
172
+ for (const { connection } of pool) {
173
+ connections.add(connection);
174
+ }
175
+ }
96
176
  if (this.defaultConnection)
97
177
  connections.add(this.defaultConnection);
98
178
  this.connections.clear();
179
+ this.pools.clear();
99
180
  this.tenantCache.clear();
100
181
  for (const connection of connections) {
101
182
  await connection.close();
@@ -14,7 +14,7 @@ export { MySqlGrammar } from "./schema/grammars/MySqlGrammar.js";
14
14
  export { PostgresGrammar } from "./schema/grammars/PostgresGrammar.js";
15
15
  export { Builder } from "./query/Builder.js";
16
16
  export { Model, HasMany, BelongsTo, HasOne, HasManyThrough, HasOneThrough } from "./model/Model.js";
17
- export type { ModelAttributeInput, ModelAttributes, ModelColumn, ModelColumnValue, ModelConstructor, GlobalScope, CastDefinition, CastsAttributes, } from "./model/Model.js";
17
+ export type { ModelAttributeInput, ModelAttributes, ModelColumn, ModelColumnValue, ModelConstructor, ModelRelationName, GlobalScope, CastDefinition, CastsAttributes, } from "./model/Model.js";
18
18
  export { ModelNotFoundError } from "./model/ModelNotFoundError.js";
19
19
  export { ObserverRegistry, type ObserverContract } from "./model/Observer.js";
20
20
  export { MorphMap } from "./model/MorphMap.js";
@@ -22,6 +22,7 @@ export { MorphTo, MorphOne, MorphMany, MorphToMany } from "./model/MorphRelation
22
22
  export { BelongsToMany } from "./model/BelongsToMany.js";
23
23
  export { Migration } from "./migration/Migration.js";
24
24
  export { Migrator } from "./migration/Migrator.js";
25
+ export type { MigrationEvent, MigrationEventListener, MigrationEventPayload } from "./migration/Migrator.js";
25
26
  export { MigrationCreator } from "./migration/MigrationCreator.js";
26
27
  export { TypeGenerator } from "./typegen/TypeGenerator.js";
27
28
  export { TypeMapper } from "./typegen/TypeMapper.js";
@@ -1,13 +1,24 @@
1
1
  import { Connection } from "../connection/Connection.js";
2
2
  import type { TypeGeneratorOptions } from "../typegen/TypeGenerator.js";
3
+ export type MigrationEvent = "migrating" | "migrated" | "rollingBack" | "rolledBack" | "schemaDumped" | "schemaSquashed";
4
+ export interface MigrationEventPayload {
5
+ migration?: string;
6
+ batch?: number;
7
+ path?: string;
8
+ }
9
+ export type MigrationEventListener = (payload: MigrationEventPayload) => void | Promise<void>;
3
10
  export declare class Migrator {
4
11
  private connection;
5
12
  private path;
6
13
  private typesOutDir?;
7
14
  private typeGeneratorOptions;
15
+ private static listeners;
8
16
  constructor(connection: Connection, path: string | string[], typesOutDir?: string | undefined, typeGeneratorOptions?: Omit<TypeGeneratorOptions, "outDir">);
9
17
  private getPaths;
10
18
  private ensureMigrationsTable;
19
+ static on(event: MigrationEvent, listener: MigrationEventListener): () => void;
20
+ static clearListeners(event?: MigrationEvent): void;
21
+ private emit;
11
22
  private getLastBatchNumber;
12
23
  private getMigrationFiles;
13
24
  run(): Promise<void>;
@@ -17,6 +28,9 @@ export declare class Migrator {
17
28
  migration: string;
18
29
  status: string;
19
30
  }[]>;
31
+ dumpSchema(path: string): Promise<string>;
32
+ squash(path: string): Promise<string>;
33
+ private getSchemaDumpSql;
20
34
  private resolve;
21
35
  private getRan;
22
36
  }
@@ -1,5 +1,5 @@
1
1
  import { existsSync } from "fs";
2
- import { readdir } from "fs/promises";
2
+ import { mkdir, readdir, writeFile } from "fs/promises";
3
3
  import { basename, join, relative, resolve } from "path";
4
4
  import { Schema } from "../schema/Schema.js";
5
5
  import { Builder } from "../query/Builder.js";
@@ -10,6 +10,7 @@ export class Migrator {
10
10
  path;
11
11
  typesOutDir;
12
12
  typeGeneratorOptions;
13
+ static listeners = new Map();
13
14
  constructor(connection, path, typesOutDir, typeGeneratorOptions = {}) {
14
15
  this.connection = connection;
15
16
  this.path = path;
@@ -30,6 +31,23 @@ export class Migrator {
30
31
  });
31
32
  }
32
33
  }
34
+ static on(event, listener) {
35
+ const listeners = this.listeners.get(event) || new Set();
36
+ listeners.add(listener);
37
+ this.listeners.set(event, listeners);
38
+ return () => listeners.delete(listener);
39
+ }
40
+ static clearListeners(event) {
41
+ if (event)
42
+ this.listeners.delete(event);
43
+ else
44
+ this.listeners.clear();
45
+ }
46
+ async emit(event, payload) {
47
+ for (const listener of Migrator.listeners.get(event) || []) {
48
+ await listener(payload);
49
+ }
50
+ }
33
51
  async getLastBatchNumber() {
34
52
  const result = await new Builder(this.connection, "migrations")
35
53
  .select("MAX(batch) as batch")
@@ -69,11 +87,13 @@ export class Migrator {
69
87
  for (const file of pending) {
70
88
  const migration = await this.resolve(file.id);
71
89
  console.log(`Migrating: ${file.id}`);
90
+ await this.emit("migrating", { migration: file.id, batch });
72
91
  await migration.up();
73
92
  await new Builder(this.connection, "migrations").insert({
74
93
  migration: file.id,
75
94
  batch,
76
95
  });
96
+ await this.emit("migrated", { migration: file.id, batch });
77
97
  console.log(`Migrated: ${file.id}`);
78
98
  }
79
99
  await this.connection.commit();
@@ -103,10 +123,12 @@ export class Migrator {
103
123
  for (const record of records) {
104
124
  const migration = await this.resolve(record.migration);
105
125
  console.log(`Rolling back: ${record.migration}`);
126
+ await this.emit("rollingBack", { migration: record.migration, batch });
106
127
  await migration.down();
107
128
  await new Builder(this.connection, "migrations")
108
129
  .where("id", record.id)
109
130
  .delete();
131
+ await this.emit("rolledBack", { migration: record.migration, batch });
110
132
  console.log(`Rolled back: ${record.migration}`);
111
133
  }
112
134
  await this.connection.commit();
@@ -139,6 +161,87 @@ export class Migrator {
139
161
  status: ran.has(file.id) || ran.has(file.fileName) ? "Ran" : "Pending",
140
162
  }));
141
163
  }
164
+ async dumpSchema(path) {
165
+ const sql = await this.getSchemaDumpSql();
166
+ await mkdir(resolve(path, ".."), { recursive: true });
167
+ await writeFile(path, sql, "utf-8");
168
+ await this.emit("schemaDumped", { path });
169
+ return sql;
170
+ }
171
+ async squash(path) {
172
+ const sql = await this.dumpSchema(path);
173
+ const files = await this.getMigrationFiles();
174
+ await this.ensureMigrationsTable();
175
+ const batch = (await this.getLastBatchNumber()) + 1;
176
+ await new Builder(this.connection, "migrations").delete();
177
+ for (const file of files) {
178
+ await new Builder(this.connection, "migrations").insert({
179
+ migration: file.id,
180
+ batch,
181
+ });
182
+ }
183
+ await this.emit("schemaSquashed", { path, batch });
184
+ return sql;
185
+ }
186
+ async getSchemaDumpSql() {
187
+ const driver = this.connection.getDriverName();
188
+ if (driver === "sqlite") {
189
+ const rows = await this.connection.query("SELECT sql FROM sqlite_master WHERE sql IS NOT NULL AND type IN ('table', 'index', 'trigger', 'view') AND name NOT LIKE 'sqlite_%' ORDER BY type = 'table' DESC, name");
190
+ return rows.map((row) => `${String(row.sql).trim()};`).join("\n\n") + "\n";
191
+ }
192
+ if (driver === "mysql") {
193
+ const tables = await this.connection.query("SHOW TABLES");
194
+ const key = Object.keys(tables[0] ?? {})[0];
195
+ const statements = [];
196
+ for (const row of tables) {
197
+ const table = row[key];
198
+ const createRows = await this.connection.query(`SHOW CREATE TABLE ${table}`);
199
+ statements.push(`${createRows[0]["Create Table"]};`);
200
+ }
201
+ return statements.join("\n\n") + "\n";
202
+ }
203
+ const schema = this.connection.getSchema() || "public";
204
+ const tables = await this.connection.query(`SELECT table_name FROM information_schema.tables WHERE table_schema = '${schema}' AND table_type = 'BASE TABLE' ORDER BY table_name`);
205
+ const statements = [];
206
+ for (const tableRow of tables) {
207
+ const table = tableRow.table_name;
208
+ const columns = await this.connection.query(`SELECT column_name, data_type, is_nullable, column_default, character_maximum_length, numeric_precision, numeric_scale
209
+ FROM information_schema.columns
210
+ WHERE table_schema = '${schema}' AND table_name = '${table}'
211
+ ORDER BY ordinal_position`);
212
+ const primaryKeys = await this.connection.query(`SELECT kcu.column_name
213
+ FROM information_schema.table_constraints tc
214
+ JOIN information_schema.key_column_usage kcu
215
+ ON tc.constraint_name = kcu.constraint_name
216
+ AND tc.table_schema = kcu.table_schema
217
+ AND tc.table_name = kcu.table_name
218
+ WHERE tc.table_schema = '${schema}'
219
+ AND tc.table_name = '${table}'
220
+ AND tc.constraint_type = 'PRIMARY KEY'
221
+ ORDER BY kcu.ordinal_position`);
222
+ const pkColumns = primaryKeys.map((row) => row.column_name);
223
+ const columnSql = columns.map((column) => {
224
+ let type = String(column.data_type).toUpperCase();
225
+ if ((type === "CHARACTER VARYING" || type === "CHARACTER") && column.character_maximum_length) {
226
+ type = `${type}(${column.character_maximum_length})`;
227
+ }
228
+ else if ((type === "NUMERIC" || type === "DECIMAL") && column.numeric_precision) {
229
+ type = `${type}(${column.numeric_precision}${column.numeric_scale ? `, ${column.numeric_scale}` : ""})`;
230
+ }
231
+ let sql = ` "${column.column_name}" ${type}`;
232
+ if (column.is_nullable === "NO")
233
+ sql += " NOT NULL";
234
+ if (column.column_default !== null && column.column_default !== undefined)
235
+ sql += ` DEFAULT ${column.column_default}`;
236
+ return sql;
237
+ });
238
+ if (pkColumns.length > 0) {
239
+ columnSql.push(` PRIMARY KEY (${pkColumns.map((column) => `"${column}"`).join(", ")})`);
240
+ }
241
+ statements.push(`CREATE TABLE "${schema}"."${table}" (\n${columnSql.join(",\n")}\n);`);
242
+ }
243
+ return statements.join("\n\n") + "\n";
244
+ }
142
245
  async resolve(file) {
143
246
  const normalized = toPosixPath(file);
144
247
  const candidates = new Set();
@@ -13,6 +13,10 @@ export type ModelAttributes<T> = T extends {
13
13
  export type ModelColumn<T> = LiteralUnion<Extract<keyof ModelAttributes<T>, string>>;
14
14
  export type ModelColumnValue<T, K> = K extends keyof ModelAttributes<T> ? ModelAttributes<T>[K] : any;
15
15
  export type ModelAttributeInput<T> = Partial<ModelAttributes<T>> & Record<string, any>;
16
+ export type ModelRelationValue = Relation<any> | MorphTo<any> | MorphOne<any> | MorphMany<any> | MorphToMany<any> | BelongsToMany<any>;
17
+ export type ModelRelationName<T> = LiteralUnion<Extract<{
18
+ [K in keyof T]-?: T[K] extends (...args: any[]) => ModelRelationValue ? K : never;
19
+ }[keyof T], string>>;
16
20
  export type CastDefinition = string | CastsAttributes | (new (...args: any[]) => CastsAttributes);
17
21
  export interface CastsAttributes {
18
22
  get(model: Model, key: string, value: any, attributes: Record<string, any>): any;
@@ -160,7 +164,7 @@ export declare class Model<T extends Record<string, any> = Record<string, any>>
160
164
  static inRandomOrder<M extends ModelConstructor>(this: M): Builder<InstanceType<M>>;
161
165
  static lockForUpdate<M extends ModelConstructor>(this: M): Builder<InstanceType<M>>;
162
166
  static sharedLock<M extends ModelConstructor>(this: M): Builder<InstanceType<M>>;
163
- static with<M extends ModelConstructor>(this: M, ...relations: string[]): Builder<InstanceType<M>>;
167
+ static with<M extends ModelConstructor>(this: M, ...relations: ModelRelationName<InstanceType<M>>[]): Builder<InstanceType<M>>;
164
168
  static withTrashed<M extends ModelConstructor>(this: M): Builder<InstanceType<M>>;
165
169
  static onlyTrashed<M extends ModelConstructor>(this: M): Builder<InstanceType<M>>;
166
170
  static withoutGlobalScope<M extends ModelConstructor>(this: M, scope: string): Builder<InstanceType<M>>;
@@ -6,12 +6,18 @@ export declare class MorphTo<T extends Model = Model> {
6
6
  protected typeColumn: string;
7
7
  protected idColumn: string;
8
8
  protected typeMap?: Record<string, ModelConstructor>;
9
+ protected eagerModels: Model[];
9
10
  constructor(parent: Model, name: string, typeMap?: Record<string, ModelConstructor>);
10
11
  getResults(): Promise<T | null>;
12
+ private resolveRelated;
11
13
  private resolveAndFind;
14
+ private throwMissingMorph;
12
15
  addEagerConstraints(models: Model[]): void;
13
16
  getEager(): Promise<any[]>;
14
- match(models: Model[], _results: any[], relationName: string): void;
17
+ match(models: Model[], results: Array<{
18
+ __morphType: string;
19
+ model: Model;
20
+ }>, relationName: string): void;
15
21
  }
16
22
  export declare class MorphOne<T extends Model = Model> {
17
23
  protected builder: Builder<T>;
@@ -6,6 +6,7 @@ export class MorphTo {
6
6
  typeColumn;
7
7
  idColumn;
8
8
  typeMap;
9
+ eagerModels = [];
9
10
  constructor(parent, name, typeMap) {
10
11
  this.parent = parent;
11
12
  this.name = name;
@@ -20,7 +21,7 @@ export class MorphTo {
20
21
  return null;
21
22
  return this.resolveAndFind(type, id);
22
23
  }
23
- async resolveAndFind(type, id) {
24
+ resolveRelated(type) {
24
25
  let Related;
25
26
  if (this.typeMap) {
26
27
  Related = this.typeMap[type];
@@ -28,43 +29,55 @@ export class MorphTo {
28
29
  if (!Related) {
29
30
  Related = MorphMap.get(type);
30
31
  }
31
- if (!Related) {
32
- throw new Error(`No morph mapping found for type: ${type}. Register it with MorphMap.register() or pass a typeMap.`);
33
- }
32
+ return Related;
33
+ }
34
+ async resolveAndFind(type, id) {
35
+ const Related = this.resolveRelated(type);
36
+ if (!Related)
37
+ this.throwMissingMorph(type);
34
38
  return Related.on(this.parent.getConnection()).find(id);
35
39
  }
40
+ throwMissingMorph(type) {
41
+ throw new Error(`No morph mapping found for type: ${type}. Register it with MorphMap.register() or pass a typeMap.`);
42
+ }
36
43
  addEagerConstraints(models) {
37
- // MorphTo eager loading is handled separately in getEager
44
+ this.eagerModels = models;
38
45
  }
39
46
  async getEager() {
40
- return []; // Results are assembled in match
41
- }
42
- match(models, _results, relationName) {
43
- // Group models by type
44
- const typeGroups = {};
45
- for (const model of models) {
47
+ const results = [];
48
+ const groups = {};
49
+ for (const model of this.eagerModels) {
46
50
  const type = model.getAttribute(this.typeColumn);
47
51
  if (!type)
48
52
  continue;
49
- if (!typeGroups[type])
50
- typeGroups[type] = [];
51
- typeGroups[type].push(model);
53
+ if (!groups[type])
54
+ groups[type] = [];
55
+ groups[type].push(model);
52
56
  }
53
- // For each type, find the related models
54
- for (const [type, groupModels] of Object.entries(typeGroups)) {
55
- const ids = groupModels.map((m) => m.getAttribute(this.idColumn)).filter((id) => id !== null && id !== undefined);
56
- if (ids.length === 0)
57
- continue;
58
- let Related;
59
- if (this.typeMap)
60
- Related = this.typeMap[type];
61
- if (!Related)
62
- Related = MorphMap.get(type);
57
+ for (const [type, models] of Object.entries(groups)) {
58
+ const Related = this.resolveRelated(type);
63
59
  if (!Related)
60
+ this.throwMissingMorph(type);
61
+ const ids = [...new Set(models.map((model) => model.getAttribute(this.idColumn)).filter((id) => id !== null && id !== undefined))];
62
+ if (ids.length === 0)
64
63
  continue;
65
- const relatedModels = Related.whereIn(Related.primaryKey, ids).get();
66
- // We need to execute this synchronously-ish... but it's async
67
- // Actually, we can't do async in for...of without awaiting
64
+ const relatedModels = await Related.on(models[0].getConnection()).whereIn(Related.primaryKey, ids).get();
65
+ for (const model of relatedModels) {
66
+ results.push({ __morphType: type, model });
67
+ }
68
+ }
69
+ return results;
70
+ }
71
+ match(models, results, relationName) {
72
+ const dictionary = {};
73
+ for (const result of results) {
74
+ const key = result.model.getAttribute(result.model.constructor.primaryKey);
75
+ dictionary[`${result.__morphType}:${String(key)}`] = result.model;
76
+ }
77
+ for (const model of models) {
78
+ const type = model.getAttribute(this.typeColumn);
79
+ const id = model.getAttribute(this.idColumn);
80
+ model.setRelation(relationName, dictionary[`${type}:${String(id)}`] || null);
68
81
  }
69
82
  }
70
83
  }
@@ -1,6 +1,6 @@
1
1
  import { Connection } from "../connection/Connection.js";
2
2
  import type { WhereClause, OrderClause, HavingClause, UnionClause } from "../types/index.js";
3
- import type { ModelAttributeInput, ModelColumn, ModelColumnValue, ModelConstructor } from "../model/Model.js";
3
+ import type { ModelAttributeInput, ModelColumn, ModelColumnValue, ModelConstructor, ModelRelationName } from "../model/Model.js";
4
4
  type RelationConstraint = (query: Builder<any>) => void | Builder<any>;
5
5
  export interface Paginator<T> {
6
6
  data: T[];
@@ -30,6 +30,7 @@ export declare class Builder<T = Record<string, any>> {
30
30
  unions: UnionClause[];
31
31
  fromRaw?: string;
32
32
  updateJoins: string[];
33
+ bindings: any[];
33
34
  constructor(connection: Connection, table: string);
34
35
  private get grammar();
35
36
  setModel(model: ModelConstructor): this;
@@ -102,7 +103,7 @@ export declare class Builder<T = Record<string, any>> {
102
103
  crossJoin(table: string): this;
103
104
  union(query: Builder<T> | string, all?: boolean): this;
104
105
  unionAll(query: Builder<T> | string): this;
105
- with(...relations: string[]): this;
106
+ with(...relations: ModelRelationName<T>[]): this;
106
107
  withoutGlobalScope(scope: string): this;
107
108
  withoutGlobalScopes(): this;
108
109
  withTrashed(): this;
@@ -157,7 +158,7 @@ export declare class Builder<T = Record<string, any>> {
157
158
  paginate(perPage?: number, page?: number): Promise<Paginator<T>>;
158
159
  chunk(count: number, callback: (items: T[]) => void | Promise<void>): Promise<void>;
159
160
  each(count: number, callback: (item: T) => void | Promise<void>): Promise<void>;
160
- cursor(): AsyncGenerator<T>;
161
+ cursor(keyset?: Record<string, any>): AsyncGenerator<T>;
161
162
  lazy(count?: number): AsyncGenerator<T>;
162
163
  insert(data: ModelAttributeInput<T> | ModelAttributeInput<T>[]): Promise<any>;
163
164
  insertGetId(data: ModelAttributeInput<T>, idColumn?: ModelColumn<T>): Promise<any>;
@@ -18,6 +18,7 @@ export class Builder {
18
18
  unions = [];
19
19
  fromRaw;
20
20
  updateJoins = [];
21
+ bindings = [];
21
22
  constructor(connection, table) {
22
23
  this.connection = connection;
23
24
  this.tableName = table;
@@ -455,6 +456,7 @@ export class Builder {
455
456
  cloned.unions = [...this.unions];
456
457
  cloned.fromRaw = this.fromRaw;
457
458
  cloned.updateJoins = [...this.updateJoins];
459
+ cloned.bindings = [...this.bindings];
458
460
  return cloned;
459
461
  }
460
462
  wrapColumn(value) {
@@ -697,14 +699,34 @@ export class Builder {
697
699
  }
698
700
  });
699
701
  }
700
- async *cursor() {
701
- let offset = 0;
702
- while (true) {
703
- const items = await this.clone().offset(offset).limit(1).get();
704
- if (items.length === 0)
705
- break;
706
- yield items[0];
707
- offset++;
702
+ async *cursor(keyset) {
703
+ const model = this.model;
704
+ const primaryKey = model ? model.primaryKey || "id" : "id";
705
+ const orderColumn = this.orders[0]?.column || primaryKey;
706
+ const orderDirection = this.orders[0]?.direction || "asc";
707
+ const builder = this.clone();
708
+ builder.orders = [{ column: orderColumn, direction: orderDirection }];
709
+ builder.offsetValue = undefined;
710
+ if (keyset) {
711
+ const op = orderDirection === "asc" ? ">" : "<";
712
+ builder.wheres.push({
713
+ type: "basic",
714
+ column: orderColumn,
715
+ operator: op,
716
+ value: keyset[orderColumn],
717
+ boolean: "and",
718
+ scope: undefined,
719
+ });
720
+ }
721
+ const items = await builder.limit(1).get();
722
+ if (items.length === 0)
723
+ return;
724
+ yield items[0];
725
+ const nextKeyset = items[0] && typeof items[0] === "object"
726
+ ? { [orderColumn]: items[0][orderColumn] }
727
+ : undefined;
728
+ if (nextKeyset) {
729
+ yield* this.cursor(nextKeyset);
708
730
  }
709
731
  }
710
732
  async *lazy(count = 1000) {
@@ -14,6 +14,10 @@ export declare class ForeignKeyBuilder {
14
14
  on(table: string): this;
15
15
  onDelete(action: string): this;
16
16
  onUpdate(action: string): this;
17
+ cascadeOnDelete(): this;
18
+ restrictOnDelete(): this;
19
+ nullOnDelete(): this;
20
+ cascadeOnUpdate(): this;
17
21
  }
18
22
  export declare class Blueprint {
19
23
  readonly table: string;
@@ -47,17 +51,29 @@ export declare class Blueprint {
47
51
  jsonb(name: string): this;
48
52
  binary(name: string): this;
49
53
  uuid(name: string): this;
54
+ foreignId(name: string): this;
55
+ foreignUuid(name: string): this;
50
56
  enum(name: string, values: string[]): this;
51
57
  nullable(): this;
52
58
  default(value: any): this;
53
59
  unique(): this;
54
60
  index(): this;
61
+ index(columns: string | string[], name?: string): this;
55
62
  primary(): this;
56
63
  unsigned(): this;
57
64
  comment(text: string): this;
65
+ change(): void;
58
66
  timestamps(): void;
59
67
  softDeletes(): void;
68
+ morphs(name: string): void;
69
+ nullableMorphs(name: string): void;
70
+ uuidMorphs(name: string): void;
71
+ nullableUuidMorphs(name: string): void;
60
72
  foreign(columns: string | string[], name?: string): ForeignKeyBuilder;
73
+ constrained(table?: string, column?: string): ForeignKeyBuilder;
74
+ cascadeOnDelete(): ForeignKeyBuilder;
75
+ uniqueIndex(columns: string | string[], name?: string): this;
76
+ private guessConstrainedTable;
61
77
  dropColumn(column: string | string[]): void;
62
78
  renameColumn(from: string, to: string): void;
63
79
  dropIndex(name: string): void;
@@ -22,6 +22,18 @@ export class ForeignKeyBuilder {
22
22
  this.fk.onUpdate = action;
23
23
  return this;
24
24
  }
25
+ cascadeOnDelete() {
26
+ return this.onDelete("cascade");
27
+ }
28
+ restrictOnDelete() {
29
+ return this.onDelete("restrict");
30
+ }
31
+ nullOnDelete() {
32
+ return this.onDelete("set null");
33
+ }
34
+ cascadeOnUpdate() {
35
+ return this.onUpdate("cascade");
36
+ }
25
37
  }
26
38
  export class Blueprint {
27
39
  table;
@@ -126,6 +138,12 @@ export class Blueprint {
126
138
  uuid(name) {
127
139
  return this.addColumn("uuid", name);
128
140
  }
141
+ foreignId(name) {
142
+ return this.bigInteger(name).unsigned();
143
+ }
144
+ foreignUuid(name) {
145
+ return this.uuid(name);
146
+ }
129
147
  enum(name, values) {
130
148
  this.addColumn("enum", name);
131
149
  this.currentColumn.values = values;
@@ -147,10 +165,24 @@ export class Blueprint {
147
165
  }
148
166
  return this;
149
167
  }
150
- index() {
151
- if (this.currentColumn) {
152
- this.currentColumn.index = true;
168
+ index(columns, name) {
169
+ if (columns === undefined) {
170
+ if (this.currentColumn) {
171
+ this.currentColumn.index = true;
172
+ this.indexes.push({
173
+ name: `${this.table}_${this.currentColumn.name}_index`,
174
+ columns: [this.currentColumn.name],
175
+ unique: false,
176
+ });
177
+ }
178
+ return this;
153
179
  }
180
+ const cols = Array.isArray(columns) ? columns : [columns];
181
+ this.indexes.push({
182
+ name: name || `${this.table}_${cols.join("_")}_index`,
183
+ columns: cols,
184
+ unique: false,
185
+ });
154
186
  return this;
155
187
  }
156
188
  primary() {
@@ -171,6 +203,12 @@ export class Blueprint {
171
203
  }
172
204
  return this;
173
205
  }
206
+ change() {
207
+ if (!this.currentColumn) {
208
+ throw new Error("change() must be called after a column definition.");
209
+ }
210
+ this.commands.push({ name: "change", parameters: { column: this.currentColumn } });
211
+ }
174
212
  timestamps() {
175
213
  this.timestamp("created_at").nullable();
176
214
  this.timestamp("updated_at").nullable();
@@ -178,10 +216,54 @@ export class Blueprint {
178
216
  softDeletes() {
179
217
  this.timestamp("deleted_at").nullable();
180
218
  }
219
+ morphs(name) {
220
+ this.string(`${name}_type`);
221
+ this.bigInteger(`${name}_id`).unsigned();
222
+ this.index([`${name}_type`, `${name}_id`], `${this.table}_${name}_type_${name}_id_index`);
223
+ }
224
+ nullableMorphs(name) {
225
+ this.string(`${name}_type`).nullable();
226
+ this.bigInteger(`${name}_id`).unsigned().nullable();
227
+ this.index([`${name}_type`, `${name}_id`], `${this.table}_${name}_type_${name}_id_index`);
228
+ }
229
+ uuidMorphs(name) {
230
+ this.string(`${name}_type`);
231
+ this.uuid(`${name}_id`);
232
+ this.index([`${name}_type`, `${name}_id`], `${this.table}_${name}_type_${name}_id_index`);
233
+ }
234
+ nullableUuidMorphs(name) {
235
+ this.string(`${name}_type`).nullable();
236
+ this.uuid(`${name}_id`).nullable();
237
+ this.index([`${name}_type`, `${name}_id`], `${this.table}_${name}_type_${name}_id_index`);
238
+ }
181
239
  foreign(columns, name) {
182
240
  const cols = Array.isArray(columns) ? columns : [columns];
183
241
  return new ForeignKeyBuilder(this, cols, name);
184
242
  }
243
+ constrained(table, column = "id") {
244
+ if (!this.currentColumn) {
245
+ throw new Error("constrained() must be called after a column definition.");
246
+ }
247
+ const localColumn = this.currentColumn.name;
248
+ const foreignTable = table || this.guessConstrainedTable(localColumn);
249
+ return this.foreign(localColumn).references(column).on(foreignTable);
250
+ }
251
+ cascadeOnDelete() {
252
+ return this.constrained().cascadeOnDelete();
253
+ }
254
+ uniqueIndex(columns, name) {
255
+ const cols = Array.isArray(columns) ? columns : [columns];
256
+ this.indexes.push({
257
+ name: name || `${this.table}_${cols.join("_")}_unique`,
258
+ columns: cols,
259
+ unique: true,
260
+ });
261
+ return this;
262
+ }
263
+ guessConstrainedTable(column) {
264
+ const base = column.endsWith("_id") ? column.slice(0, -3) : column;
265
+ return `${base}s`;
266
+ }
185
267
  dropColumn(column) {
186
268
  this.commands.push({ name: "dropColumn", parameters: { column: Array.isArray(column) ? column : [column] } });
187
269
  }
@@ -40,6 +40,10 @@ export class Schema {
40
40
  for (const indexSql of indexes) {
41
41
  await this.getConnection().run(indexSql);
42
42
  }
43
+ const fks = grammar.compileForeignKeys(blueprint, connection.qualifyTable(table));
44
+ for (const fkSql of fks) {
45
+ await this.getConnection().run(fkSql);
46
+ }
43
47
  }
44
48
  static async createIfNotExists(table, callback) {
45
49
  const blueprint = new Blueprint(table);
@@ -52,6 +56,10 @@ export class Schema {
52
56
  for (const indexSql of indexes) {
53
57
  await this.getConnection().run(indexSql);
54
58
  }
59
+ const fks = grammar.compileForeignKeys(blueprint, connection.qualifyTable(table));
60
+ for (const fkSql of fks) {
61
+ await this.getConnection().run(fkSql);
62
+ }
55
63
  }
56
64
  static async table(table, callback) {
57
65
  const blueprint = new Blueprint(table);
@@ -83,6 +91,16 @@ export class Schema {
83
91
  else if (command.name === "dropForeign") {
84
92
  await this.getConnection().run(`ALTER TABLE ${grammar.wrap(table)} DROP CONSTRAINT ${grammar.wrap(command.parameters.name)}`);
85
93
  }
94
+ else if (command.name === "change") {
95
+ const sql = grammar.compileChange(qualifiedTable, command.parameters.column);
96
+ if (Array.isArray(sql)) {
97
+ for (const s of sql)
98
+ await this.getConnection().run(s);
99
+ }
100
+ else {
101
+ await this.getConnection().run(sql);
102
+ }
103
+ }
86
104
  }
87
105
  const addSqls = grammar.compileAdd(blueprint, qualifiedTable);
88
106
  for (const sql of addSqls) {
@@ -23,4 +23,5 @@ export declare abstract class Grammar {
23
23
  protected compileForeignKey(table: string, fk: ForeignKeyDefinition): string;
24
24
  abstract compileColumnRename(table: string, from: string, to: string): string;
25
25
  abstract compileDropColumn(table: string, columns: string[]): string | string[];
26
+ abstract compileChange(table: string, column: ColumnDefinition): string | string[];
26
27
  }
@@ -84,7 +84,8 @@ export class Grammar {
84
84
  return blueprint.foreignKeys.map((fk) => this.compileForeignKey(table, fk));
85
85
  }
86
86
  compileForeignKey(table, fk) {
87
- const sql = `ALTER TABLE ${this.wrap(table)} ADD CONSTRAINT ${this.wrap(fk.name || "")} FOREIGN KEY (${this.wrapArray(fk.columns).join(", ")}) REFERENCES ${this.wrap(fk.onTable)} (${this.wrapArray(fk.references).join(", ")})`;
87
+ const constraint = fk.name ? ` CONSTRAINT ${this.wrap(fk.name)}` : "";
88
+ const sql = `ALTER TABLE ${this.wrap(table)} ADD${constraint} FOREIGN KEY (${this.wrapArray(fk.columns).join(", ")}) REFERENCES ${this.wrap(fk.onTable)} (${this.wrapArray(fk.references).join(", ")})`;
88
89
  let full = sql;
89
90
  if (fk.onDelete)
90
91
  full += ` ON DELETE ${fk.onDelete}`;
@@ -12,6 +12,7 @@ export declare class MySqlGrammar extends Grammar {
12
12
  protected getColumn(_blueprint: any, column: ColumnDefinition): string;
13
13
  compileColumnRename(table: string, from: string, to: string): string;
14
14
  compileDropColumn(table: string, columns: string[]): string;
15
+ compileChange(table: string, column: ColumnDefinition): string;
15
16
  compileIndex(table: string, index: any): string;
16
17
  compileCreate(blueprint: any, table: string): string;
17
18
  compileCreateIfNotExists(blueprint: any, table: string): string;
@@ -79,6 +79,9 @@ export class MySqlGrammar extends Grammar {
79
79
  compileDropColumn(table, columns) {
80
80
  return `ALTER TABLE ${this.wrap(table)} ${columns.map((col) => `DROP COLUMN ${this.wrap(col)}`).join(", ")}`;
81
81
  }
82
+ compileChange(table, column) {
83
+ return `ALTER TABLE ${this.wrap(table)} MODIFY COLUMN ${this.getColumn({}, column)}`;
84
+ }
82
85
  compileIndex(table, index) {
83
86
  const type = index.unique ? "UNIQUE INDEX" : "INDEX";
84
87
  return `ALTER TABLE ${this.wrap(table)} ADD ${type} ${this.wrap(index.name)} (${this.wrapArray(index.columns).join(", ")})`;
@@ -10,6 +10,7 @@ export declare class PostgresGrammar extends Grammar {
10
10
  protected getColumn(_blueprint: any, column: ColumnDefinition): string;
11
11
  compileColumnRename(table: string, from: string, to: string): string;
12
12
  compileDropColumn(table: string, columns: string[]): string;
13
+ compileChange(table: string, column: ColumnDefinition): string[];
13
14
  compileIndex(table: string, index: any): string;
14
15
  compileCreate(blueprint: any, table: string): string;
15
16
  compileCreateIfNotExists(blueprint: any, table: string): string;
@@ -71,6 +71,16 @@ export class PostgresGrammar extends Grammar {
71
71
  compileDropColumn(table, columns) {
72
72
  return `ALTER TABLE ${this.wrap(table)} ${columns.map((col) => `DROP COLUMN ${this.wrap(col)}`).join(", ")}`;
73
73
  }
74
+ compileChange(table, column) {
75
+ const statements = [
76
+ `ALTER TABLE ${this.wrap(table)} ALTER COLUMN ${this.wrap(column.name)} TYPE ${this.getType(column)}`,
77
+ `ALTER TABLE ${this.wrap(table)} ALTER COLUMN ${this.wrap(column.name)} ${column.nullable ? "DROP" : "SET"} NOT NULL`,
78
+ ];
79
+ if (column.default !== undefined) {
80
+ statements.push(`ALTER TABLE ${this.wrap(table)} ALTER COLUMN ${this.wrap(column.name)} SET DEFAULT ${this.getDefaultValue(column.default)}`);
81
+ }
82
+ return statements;
83
+ }
74
84
  compileIndex(table, index) {
75
85
  const type = index.unique ? "UNIQUE INDEX" : "INDEX";
76
86
  return `CREATE ${type} ${this.wrap(index.name)} ON ${this.wrap(table)} (${this.wrapArray(index.columns).join(", ")})`;
@@ -10,6 +10,7 @@ export declare class SQLiteGrammar extends Grammar {
10
10
  protected getColumn(_blueprint: any, column: ColumnDefinition): string;
11
11
  compileColumnRename(table: string, from: string, to: string): string;
12
12
  compileDropColumn(table: string, columns: string[]): string | string[];
13
+ compileChange(_table: string, _column: ColumnDefinition): string | string[];
13
14
  compileForeignKeys(blueprint: any, table: string): string[];
14
15
  compileCreate(blueprint: any, table: string): string;
15
16
  compileCreateIfNotExists(blueprint: any, table: string): string;
@@ -65,6 +65,9 @@ export class SQLiteGrammar extends Grammar {
65
65
  // SQLite 3.35.0+ supports dropping columns.
66
66
  return columns.map((col) => `ALTER TABLE ${this.wrap(table)} DROP COLUMN ${this.wrap(col)}`);
67
67
  }
68
+ compileChange(_table, _column) {
69
+ throw new Error("Changing existing columns is not supported by the SQLite grammar.");
70
+ }
68
71
  compileForeignKeys(blueprint, table) {
69
72
  // SQLite supports foreign keys inside CREATE TABLE only.
70
73
  // For simplicity, ALTER TABLE ADD CONSTRAINT is not supported in SQLite.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bunnykit/orm",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "An Eloquent-inspired ORM for Bun's native SQL client supporting SQLite, MySQL, and PostgreSQL.",
5
5
  "license": "MIT",
6
6
  "packageManager": "bun@1.3.12",