stabilize-orm 1.1.1 → 1.1.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/client.ts CHANGED
@@ -4,12 +4,10 @@ import {
4
4
  type DBConfig,
5
5
  StabilizeError,
6
6
  type PoolMetrics,
7
- DBType, // Added DBType for type checking
8
- type LoggerConfig, // Kept, but not used in logic
7
+ DBType,
9
8
  } from "./types";
10
9
  import { type Logger, ConsoleLogger } from "./logger";
11
10
 
12
- // Helper to determine if we are in Bun SQLite mode based on config
13
11
  function isSQLiteConfig(config: DBConfig): boolean {
14
12
  return (
15
13
  config.type === DBType.SQLite || config.connectionString.includes("sqlite")
@@ -17,14 +15,12 @@ function isSQLiteConfig(config: DBConfig): boolean {
17
15
  }
18
16
 
19
17
  export class DBClient {
20
- // Renamed to 'client' for clarity; stores the actual connection (Bun.Database or external driver)
21
- // We use 'any' for the SQL client instance since Bun's SQL client is complex (callable function with methods)
22
18
  private client: Database | any | null = null;
23
19
  private logger: Logger;
24
20
  private retryAttempts: number;
25
21
  private retryDelay: number;
26
22
  private maxJitter: number;
27
- // Map stores Bun SQLite Statement objects when in SQLite mode
23
+
28
24
  private preparedStatements: Map<string, Statement> = new Map();
29
25
  private config: DBConfig;
30
26
 
@@ -38,10 +34,8 @@ export class DBClient {
38
34
  this.initializeClient(config);
39
35
  }
40
36
 
41
- // Initializes the connection based on config
42
37
  private initializeClient(config: DBConfig) {
43
38
  if (isSQLiteConfig(config)) {
44
- // Use Bun's native SQLite client, which is synchronous to construct
45
39
  try {
46
40
  // new Database(path, { create: true }) is the standard Bun method
47
41
  this.client = new Database(config.connectionString, { create: true });
@@ -56,9 +50,6 @@ export class DBClient {
56
50
  );
57
51
  }
58
52
  } else {
59
- // For other DB types (Postgres/MySQL), initialize a Bun SQL client instance.
60
- // This is necessary because the bare `sql` tag is for the default connection,
61
- // and we need an instance with specific connection settings, using `SQL` as the constructor.
62
53
  this.client = new SQL(config.connectionString);
63
54
  this.logger.logDebug(
64
55
  `Initialized Bun SQL client for: ${config.connectionString}`,
@@ -70,7 +61,6 @@ export class DBClient {
70
61
  return Math.random() * this.maxJitter;
71
62
  }
72
63
 
73
- // Pool metrics are largely irrelevant for single-connection Bun SQLite
74
64
  getPoolMetrics(): PoolMetrics {
75
65
  return {
76
66
  activeConnections: 0,
@@ -112,13 +102,9 @@ export class DBClient {
112
102
  let result: T[];
113
103
 
114
104
  if (stmt) {
115
- // Bun SQLite Statement objects use .all() for fetching results
116
105
  result = stmt.all(...params) as T[];
117
106
  } else if (this.client) {
118
- // Path for non-SQLite Bun SQL clients (Postgres/MySQL)
119
- // Bun SQL clients do not expose a standard .query() method.
120
- // We must use the 'unsafe' helper to execute a raw string query with positional parameters.
121
- result = (await (this.client as any).unsafe(query, params)) as T[];
107
+ result = (await (this.client as any).unsafe(query, params)) as T[];
122
108
  } else {
123
109
  throw new StabilizeError(
124
110
  "Database client is not initialized or does not support query execution.",
@@ -137,7 +123,6 @@ export class DBClient {
137
123
  "QUERY_ERROR",
138
124
  );
139
125
  }
140
- // Exponential backoff with jitter
141
126
  await new Promise((resolve) =>
142
127
  setTimeout(
143
128
  resolve,
@@ -150,12 +135,10 @@ export class DBClient {
150
135
  }
151
136
 
152
137
  async transaction<T>(callback: () => Promise<T>): Promise<T> {
153
- // Use native Bun SQLite transaction wrapper for safer, faster transactions
154
138
  if (this.client instanceof Database) {
155
139
  const start = Date.now();
156
140
  this.logger.logDebug("Starting native SQLite transaction");
157
141
 
158
- // Bun's .transaction() automatically handles BEGIN/COMMIT/ROLLBACK
159
142
  const tx = this.client.transaction(async () => {
160
143
  return callback();
161
144
  });
@@ -168,7 +151,6 @@ export class DBClient {
168
151
  return result;
169
152
  } catch (error) {
170
153
  this.logger.logError(error as Error);
171
- // The transaction automatically rolls back on error
172
154
  throw new StabilizeError(
173
155
  `Native transaction failed: ${(error as Error).message}`,
174
156
  "TX_ERROR",
@@ -176,13 +158,11 @@ export class DBClient {
176
158
  }
177
159
  }
178
160
 
179
- // Fallback to manual transaction with retry logic for non-SQLite
180
161
  const start = Date.now();
181
162
  this.logger.logDebug("Starting manual transaction (non-SQLite)");
182
163
  for (let attempt = 1; attempt <= this.retryAttempts; attempt++) {
183
164
  try {
184
- // Use this.query, which now correctly handles execution via .unsafe() for non-SQLite
185
- await this.query("BEGIN", []);
165
+ await this.query("BEGIN", []);
186
166
  const result = await callback();
187
167
  await this.query("COMMIT", []);
188
168
  this.logger.logDebug(
@@ -190,7 +170,6 @@ export class DBClient {
190
170
  );
191
171
  return result;
192
172
  } catch (error) {
193
- // Attempt rollback, but ignore errors if rollback fails
194
173
  await this.query("ROLLBACK", []).catch(() => {
195
174
  this.logger.logDebug("Rollback failed. Connection may be invalid.");
196
175
  });
@@ -201,7 +180,6 @@ export class DBClient {
201
180
  "TX_ERROR",
202
181
  );
203
182
  }
204
- // Exponential backoff with jitter
205
183
  await new Promise((resolve) =>
206
184
  setTimeout(
207
185
  resolve,
@@ -228,7 +206,6 @@ export class DBClient {
228
206
  );
229
207
  return result;
230
208
  } catch (error) {
231
- // Attempt rollback, but ignore errors if rollback fails
232
209
  await this.query(`ROLLBACK TO SAVEPOINT ${name}`, []).catch(() => {
233
210
  this.logger.logDebug(
234
211
  `Rollback to savepoint ${name} failed. Connection may be invalid.`,
@@ -241,15 +218,13 @@ export class DBClient {
241
218
 
242
219
  async close() {
243
220
  this.preparedStatements.clear();
244
- // Check if the client is a Bun Database instance before closing (Bun SQLite close is sync)
245
- if (this.client instanceof Database) {
221
+ if (this.client instanceof Database) {
246
222
  this.client.close();
247
223
  this.client = null;
248
224
  } else if (
249
225
  this.client &&
250
226
  typeof (this.client as any).close === "function"
251
227
  ) {
252
- // Assume external/Bun SQL driver has an async close method
253
228
  await (this.client as any).close();
254
229
  this.client = null;
255
230
  }
package/migrations.ts CHANGED
@@ -1,14 +1,43 @@
1
1
  import { DBClient } from "./client";
2
2
  import { ModelKey, ColumnKey, ValidatorKey, SoftDeleteKey } from "./decorators";
3
- import { type DBConfig, type Migration, StabilizeError } from "./types";
3
+ import { type DBConfig, type Migration, StabilizeError, DBType } from "./types";
4
4
 
5
5
  type ColumnData = { name: string; type: string };
6
6
  type ColumnMetadata = Record<string, ColumnData>;
7
7
  type ValidatorMetadata = Record<string, string[]>;
8
8
 
9
+ // Helper to get SQL type for auto-increment PK
10
+ function getAutoIncrementPK(dbType: DBType) {
11
+ switch (dbType) {
12
+ case DBType.Postgres:
13
+ return "SERIAL PRIMARY KEY";
14
+ case DBType.MySQL:
15
+ return "INT AUTO_INCREMENT PRIMARY KEY";
16
+ case DBType.SQLite:
17
+ return "INTEGER PRIMARY KEY AUTOINCREMENT";
18
+ default: // Default to Postgres
19
+ return "SERIAL PRIMARY KEY";
20
+ }
21
+ }
22
+
23
+ // Helper to get SQL type for timestamps
24
+ function getTimestampType(dbType: DBType) {
25
+ switch (dbType) {
26
+ case DBType.Postgres:
27
+ return "TIMESTAMP";
28
+ case DBType.MySQL:
29
+ return "DATETIME";
30
+ case DBType.SQLite:
31
+ return "TEXT";
32
+ default: // Default to Postgres
33
+ return "TIMESTAMP";
34
+ }
35
+ }
36
+
9
37
  export async function generateMigration(
10
38
  model: new (...args: any[]) => any,
11
39
  name: string,
40
+ dbType: DBType = DBType.Postgres, // default to Postgres
12
41
  ): Promise<Migration> {
13
42
  const tableName = Reflect.getMetadata(ModelKey, model);
14
43
  if (!tableName)
@@ -24,10 +53,19 @@ export async function generateMigration(
24
53
  const softDeleteField = Reflect.getMetadata(SoftDeleteKey, model.prototype);
25
54
 
26
55
  const columnDefs = Object.entries(columns).map(([key, col]) => {
27
- let def = `${col.name} ${col.type}`;
28
- if (col.name === "id") def += " PRIMARY KEY AUTOINCREMENT";
56
+ let def: string;
57
+ if (col.name === "id") {
58
+ // Use correct PK syntax for each DB
59
+ def = getAutoIncrementPK(dbType);
60
+ return def;
61
+ }
62
+ def = `${col.name} ${col.type}`;
29
63
  if (validators[key]?.includes("required")) def += " NOT NULL";
30
64
  if (validators[key]?.includes("unique")) def += " UNIQUE";
65
+ // Handle timestamps
66
+ if (["createdAt", "updatedAt"].includes(col.name)) {
67
+ def = `${col.name} ${getTimestampType(dbType)}`;
68
+ }
31
69
  return def;
32
70
  });
33
71
 
@@ -45,16 +83,43 @@ export async function generateMigration(
45
83
  return { up, down };
46
84
  }
47
85
 
48
- export async function runMigrations(config: DBConfig, migrations: Migration[]) {
49
- const client = new DBClient(config);
50
- try {
51
- await client.query(`
52
- CREATE TABLE IF NOT EXISTS migrations (
86
+ // Create migrations table with correct types for each DB
87
+ function getMigrationsTableSQL(dbType: DBType) {
88
+ switch (dbType) {
89
+ case DBType.Postgres:
90
+ return `CREATE TABLE IF NOT EXISTS migrations (
91
+ id SERIAL PRIMARY KEY,
92
+ name TEXT NOT NULL,
93
+ applied_at TIMESTAMP NOT NULL
94
+ )`;
95
+ case DBType.MySQL:
96
+ return `CREATE TABLE IF NOT EXISTS migrations (
97
+ id INT AUTO_INCREMENT PRIMARY KEY,
98
+ name VARCHAR(255) NOT NULL,
99
+ applied_at DATETIME NOT NULL
100
+ )`;
101
+ case DBType.SQLite:
102
+ return `CREATE TABLE IF NOT EXISTS migrations (
53
103
  id INTEGER PRIMARY KEY AUTOINCREMENT,
54
104
  name TEXT NOT NULL,
55
105
  applied_at TEXT NOT NULL
56
- )
57
- `);
106
+ )`;
107
+ default: // Default to Postgres
108
+ return `CREATE TABLE IF NOT EXISTS migrations (
109
+ id SERIAL PRIMARY KEY,
110
+ name TEXT NOT NULL,
111
+ applied_at TIMESTAMP NOT NULL
112
+ )`;
113
+ }
114
+ }
115
+
116
+ export async function runMigrations(config: DBConfig, migrations: Migration[]) {
117
+ const client = new DBClient(config);
118
+ try {
119
+ // Detect DB type from config
120
+ const dbType = config.type ?? DBType.Postgres;
121
+
122
+ await client.query(getMigrationsTableSQL(dbType));
58
123
 
59
124
  for (const [index, migration] of migrations.entries()) {
60
125
  const name = `migration_${index}_${new Date().toISOString().replace(/[-:T.]/g, "")}`;
@@ -78,4 +143,4 @@ export async function runMigrations(config: DBConfig, migrations: Migration[]) {
78
143
  }
79
144
  }
80
145
 
81
- export type { Migration };
146
+ export type { Migration };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stabilize-orm",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "A lightweight, type-safe ORM for Bun.js with support for SQLite, MySQL, PostgreSQL, and Redis caching",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -25,14 +25,16 @@
25
25
  "typescript",
26
26
  "database"
27
27
  ],
28
- "author": "th3b0tk1ill3r <lwazicd@icloud.com>",
28
+ "author": "ElectronSz <lwazicd@icloud.com>",
29
29
  "license": "MIT",
30
30
  "dependencies": {
31
+ "@types/uuid": "^11.0.0",
31
32
  "commander": "^12.1.0",
32
33
  "figlet": "^1.9.3",
33
34
  "glob": "^11.0.0",
34
35
  "ioredis": "^5.4.1",
35
- "reflect-metadata": "^0.2.2"
36
+ "reflect-metadata": "^0.2.2",
37
+ "uuid": "^13.0.0"
36
38
  },
37
39
  "devDependencies": {
38
40
  "@typescript-eslint/eslint-plugin": "^8.7.0",