stabilize-orm 1.1.5 → 1.1.7
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 +42 -33
- package/migrations.ts +24 -43
- package/package.json +1 -1
package/client.ts
CHANGED
|
@@ -103,41 +103,50 @@ export class DBClient {
|
|
|
103
103
|
* const users = await dbClient.query('SELECT * FROM users WHERE status = ?', ['active']);
|
|
104
104
|
* ```
|
|
105
105
|
*/
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
106
|
+
|
|
107
|
+
async query<T>(query: string, params: any[] = []): Promise<T[]> {
|
|
108
|
+
const start = Date.now();
|
|
109
|
+
|
|
110
|
+
for (let attempt = 1; attempt <= this.retryAttempts; attempt++) {
|
|
111
|
+
try {
|
|
112
|
+
let result: any;
|
|
113
|
+
|
|
114
|
+
// Log the query before execution
|
|
115
|
+
this.logger.logQuery(query, params);
|
|
116
|
+
|
|
117
|
+
if (this.client instanceof Database) { // SQLite
|
|
118
|
+
let stmt = this.preparedStatements.get(query);
|
|
119
|
+
if (!stmt) {
|
|
120
|
+
stmt = this.client.prepare(query);
|
|
121
|
+
this.preparedStatements.set(query, stmt);
|
|
122
|
+
}
|
|
123
|
+
result = stmt.all(...params);
|
|
124
|
+
} else if (isMySQLPool(this.client) || ('query' in this.client && 'release' in this.client && !(this.client instanceof Pool))) { // mysql2 Pool or Connection
|
|
125
|
+
const [rows] = await (this.client as mysql.Pool).query(query, params);
|
|
126
|
+
result = rows;
|
|
127
|
+
} else { // Postgres Pool or Client
|
|
128
|
+
|
|
129
|
+
// Correctly replace '?' with '$1', '$2', etc.
|
|
130
|
+
let paramIndex = 0;
|
|
131
|
+
const pgQuery = query.replace(/\?/g, () => `$${++paramIndex}`);
|
|
132
|
+
const pgResult = await (this.client as Pool).query(pgQuery, params);
|
|
133
|
+
result = pgResult.rows;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const executionTime = Date.now() - start;
|
|
137
|
+
this.logger.logQuery(query, params, executionTime);
|
|
138
|
+
return result as T[];
|
|
139
|
+
} catch (error) {
|
|
140
|
+
this.logger.logError(error as Error);
|
|
141
|
+
if (attempt === this.retryAttempts) {
|
|
142
|
+
throw new StabilizeError(`Query failed after ${this.retryAttempts} attempts: ${(error as Error).message}`, "QUERY_ERROR");
|
|
143
|
+
}
|
|
144
|
+
await new Promise(res => setTimeout(res, this.retryDelay * Math.pow(2, attempt - 1) + this.getJitter()));
|
|
145
|
+
}
|
|
128
146
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
this.logger.logQuery(query, params, executionTime);
|
|
132
|
-
return result as T[];
|
|
133
|
-
} catch (error) {
|
|
134
|
-
this.logger.logError(error as Error);
|
|
135
|
-
if (attempt === this.retryAttempts) throw new StabilizeError(`Query failed: ${(error as Error).message}`, "QUERY_ERROR");
|
|
136
|
-
await new Promise(res => setTimeout(res, this.retryDelay * Math.pow(2, attempt - 1) + this.getJitter()));
|
|
137
|
-
}
|
|
147
|
+
// This line should theoretically be unreachable if retryAttempts >= 1
|
|
148
|
+
throw new StabilizeError("Query failed: maximum retries reached without success", "QUERY_ERROR");
|
|
138
149
|
}
|
|
139
|
-
throw new StabilizeError("Query failed: no attempts made", "QUERY_ERROR");
|
|
140
|
-
}
|
|
141
150
|
|
|
142
151
|
/**
|
|
143
152
|
* Executes a series of database operations within a single atomic transaction.
|
package/migrations.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* @file migrations.ts
|
|
3
3
|
* @description Contains functions for generating and running database migrations based on model metadata.
|
|
4
4
|
* @author ElectronSz
|
|
5
|
+
* @date 2025-10-15 20:55:49
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import { DBClient } from "./client";
|
|
@@ -12,6 +13,23 @@ type ColumnData = { name: string; type: string };
|
|
|
12
13
|
type ColumnMetadata = Record<string, ColumnData>;
|
|
13
14
|
type ValidatorMetadata = Record<string, string[]>;
|
|
14
15
|
|
|
16
|
+
// --- FIX: New Helper Function to format queries for different DBs ---
|
|
17
|
+
/**
|
|
18
|
+
* @internal
|
|
19
|
+
* Formats a SQL query with placeholders for the target database dialect.
|
|
20
|
+
* Replaces '?' with '$1', '$2', etc. for PostgreSQL.
|
|
21
|
+
* @param query The SQL query string with '?' placeholders.
|
|
22
|
+
* @param dbType The target database dialect.
|
|
23
|
+
* @returns The formatted SQL query string.
|
|
24
|
+
*/
|
|
25
|
+
function formatQuery(query: string, dbType: DBType): string {
|
|
26
|
+
if (dbType === DBType.Postgres) {
|
|
27
|
+
let paramIndex = 1;
|
|
28
|
+
return query.replace(/\?/g, () => `$${paramIndex++}`);
|
|
29
|
+
}
|
|
30
|
+
return query;
|
|
31
|
+
}
|
|
32
|
+
|
|
15
33
|
/**
|
|
16
34
|
* @internal
|
|
17
35
|
* Gets the database-specific SQL for an auto-incrementing primary key.
|
|
@@ -74,23 +92,6 @@ function getTimestampDefault(dbType: DBType): string {
|
|
|
74
92
|
* @param name A descriptive name for the migration (used for the migration object).
|
|
75
93
|
* @param dbType The target database dialect to generate SQL for. Defaults to Postgres.
|
|
76
94
|
* @returns A promise that resolves to a `Migration` object containing the `up` and `down` SQL scripts.
|
|
77
|
-
* @example
|
|
78
|
-
* ```
|
|
79
|
-
* // In a script like 'scripts/generate_user_migration.ts'
|
|
80
|
-
* import { generateMigration, DBType } from 'stabilize-orm';
|
|
81
|
-
* import { User } from './models/user';
|
|
82
|
-
* import fs from 'fs';
|
|
83
|
-
*
|
|
84
|
-
* async function createMigration() {
|
|
85
|
-
* const migration = await generateMigration(User, 'create_users_table', DBType.Postgres);
|
|
86
|
-
* fs.writeFileSync(
|
|
87
|
-
* `migrations/${new Date().getTime()}_create_users.json`,
|
|
88
|
-
* JSON.stringify(migration, null, 2)
|
|
89
|
-
* );
|
|
90
|
-
* }
|
|
91
|
-
*
|
|
92
|
-
* createMigration();
|
|
93
|
-
* ```
|
|
94
95
|
*/
|
|
95
96
|
export async function generateMigration(
|
|
96
97
|
model: new (...args: any[]) => any,
|
|
@@ -180,24 +181,6 @@ function getMigrationsTableSQL(dbType: DBType): string {
|
|
|
180
181
|
*
|
|
181
182
|
* @param config The database configuration object.
|
|
182
183
|
* @param migrations An array of `Migration` objects to be executed.
|
|
183
|
-
* @example
|
|
184
|
-
* ```
|
|
185
|
-
* // In a script like 'scripts/run_all_migrations.ts'
|
|
186
|
-
* import { runMigrations } from 'stabilize-orm';
|
|
187
|
-
* import { dbConfig } from './config';
|
|
188
|
-
* import migration1 from '../migrations/1_create_users.json';
|
|
189
|
-
* import migration2 from '../migrations.ts/2_create_profiles.json';
|
|
190
|
-
*
|
|
191
|
-
* const allMigrations = [migration1, migration2];
|
|
192
|
-
*
|
|
193
|
-
* async function applyMigrations() {
|
|
194
|
-
* console.log('Starting migration process...');
|
|
195
|
-
* await runMigrations(dbConfig, allMigrations);
|
|
196
|
-
* console.log('All pending migrations applied successfully.');
|
|
197
|
-
* }
|
|
198
|
-
*
|
|
199
|
-
* applyMigrations();
|
|
200
|
-
* ```
|
|
201
184
|
*/
|
|
202
185
|
export async function runMigrations(config: DBConfig, migrations: Migration[]) {
|
|
203
186
|
const client = new DBClient(config);
|
|
@@ -208,10 +191,8 @@ export async function runMigrations(config: DBConfig, migrations: Migration[]) {
|
|
|
208
191
|
for (const [index, migration] of migrations.entries()) {
|
|
209
192
|
const name = migration.name || `migration_${index}_${new Date().getTime()}`;
|
|
210
193
|
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
[name],
|
|
214
|
-
);
|
|
194
|
+
const selectQuery = formatQuery(`SELECT id FROM migrations WHERE name = ?`, dbType);
|
|
195
|
+
const applied = await client.query<{ id: number }>(selectQuery, [name]);
|
|
215
196
|
|
|
216
197
|
if (applied.length === 0) {
|
|
217
198
|
await client.transaction(async (txClient) => {
|
|
@@ -219,10 +200,10 @@ export async function runMigrations(config: DBConfig, migrations: Migration[]) {
|
|
|
219
200
|
for (const query of migration.up) {
|
|
220
201
|
await txClient.query(query);
|
|
221
202
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
203
|
+
|
|
204
|
+
const insertQuery = formatQuery(`INSERT INTO migrations (name, applied_at) VALUES (?, ?)`, dbType);
|
|
205
|
+
await txClient.query(insertQuery, [name, new Date().toISOString()]);
|
|
206
|
+
|
|
226
207
|
console.log(`Migration ${name} applied successfully.`);
|
|
227
208
|
});
|
|
228
209
|
}
|
package/package.json
CHANGED