stabilize-orm 1.1.7 → 1.2.0
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 +150 -76
- package/client.ts +110 -65
- package/decorators.ts +19 -14
- package/hooks.ts +33 -0
- package/index.ts +5 -2
- package/migrations.ts +147 -70
- package/package.json +1 -1
- package/repository.ts +378 -60
package/decorators.ts
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
import "reflect-metadata";
|
|
8
8
|
import { RelationType, DataTypes } from "./types";
|
|
9
9
|
|
|
10
|
-
|
|
11
10
|
export const ModelKey = Symbol("model");
|
|
12
11
|
export const ColumnKey = Symbol("column");
|
|
13
12
|
export const ValidatorKey = Symbol("validator");
|
|
@@ -15,7 +14,7 @@ export const RelationKey = Symbol("relation");
|
|
|
15
14
|
export const SoftDeleteKey = Symbol("softDelete");
|
|
16
15
|
export const DefaultKey = Symbol("default");
|
|
17
16
|
export const IndexKey = Symbol("index");
|
|
18
|
-
|
|
17
|
+
export const VersionedKey = Symbol("versioned");
|
|
19
18
|
|
|
20
19
|
export interface ColumnOptions {
|
|
21
20
|
name?: string;
|
|
@@ -42,11 +41,9 @@ export function Model(tableName: string) {
|
|
|
42
41
|
export function Column(options: ColumnOptions | DataTypes) {
|
|
43
42
|
return function (target: any, propertyKey: string) {
|
|
44
43
|
const columns = Reflect.getMetadata(ColumnKey, target) || {};
|
|
45
|
-
|
|
46
44
|
const columnOptions: ColumnOptions = typeof options === 'object' ? options : { type: options };
|
|
47
|
-
|
|
48
45
|
columns[propertyKey] = {
|
|
49
|
-
name: columnOptions.name || propertyKey,
|
|
46
|
+
name: columnOptions.name || propertyKey,
|
|
50
47
|
...columnOptions,
|
|
51
48
|
};
|
|
52
49
|
Reflect.defineMetadata(ColumnKey, columns, target);
|
|
@@ -80,9 +77,9 @@ export function Unique() {
|
|
|
80
77
|
* @param value The default value.
|
|
81
78
|
*/
|
|
82
79
|
export function Default(value: any) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
return function (target: any, propertyKey: string) {
|
|
81
|
+
Reflect.defineMetadata(DefaultKey, value, target, propertyKey);
|
|
82
|
+
};
|
|
86
83
|
}
|
|
87
84
|
|
|
88
85
|
/**
|
|
@@ -90,14 +87,13 @@ export function Default(value: any) {
|
|
|
90
87
|
* @param indexName Optional: A custom name for the index.
|
|
91
88
|
*/
|
|
92
89
|
export function Index(indexName?: string) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
90
|
+
return function (target: any, propertyKey: string) {
|
|
91
|
+
const indexes = Reflect.getMetadata(IndexKey, target) || {};
|
|
92
|
+
indexes[propertyKey] = indexName || `idx_${propertyKey}`;
|
|
93
|
+
Reflect.defineMetadata(IndexKey, indexes, target);
|
|
94
|
+
};
|
|
98
95
|
}
|
|
99
96
|
|
|
100
|
-
|
|
101
97
|
/**
|
|
102
98
|
* Decorator to enable soft-delete functionality on a model.
|
|
103
99
|
* The decorated property will store the deletion timestamp.
|
|
@@ -108,6 +104,15 @@ export function SoftDelete() {
|
|
|
108
104
|
};
|
|
109
105
|
}
|
|
110
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Decorator to enable versioning (history, snapshot & time-travel) on a model.
|
|
109
|
+
*/
|
|
110
|
+
export function Versioned() {
|
|
111
|
+
return function (target: any) {
|
|
112
|
+
Reflect.defineMetadata(VersionedKey, true, target);
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
111
116
|
|
|
112
117
|
export function OneToOne(model: () => any, foreignKey: string) {
|
|
113
118
|
return function (target: any, propertyKey: string) {
|
package/hooks.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
|
|
3
|
+
export type HookType =
|
|
4
|
+
| 'beforeCreate' | 'afterCreate'
|
|
5
|
+
| 'beforeUpdate' | 'afterUpdate'
|
|
6
|
+
| 'beforeDelete' | 'afterDelete'
|
|
7
|
+
| 'beforeSave' | 'afterSave';
|
|
8
|
+
|
|
9
|
+
const HOOK_METADATA_KEY = Symbol('stabilize:hooks');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Decorator to mark a method as a lifecycle hook.
|
|
13
|
+
* Usage: @Hook('beforeCreate')
|
|
14
|
+
*/
|
|
15
|
+
export function Hook(type: HookType) {
|
|
16
|
+
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
|
17
|
+
const hooks: Record<HookType, string[]> =
|
|
18
|
+
Reflect.getMetadata(HOOK_METADATA_KEY, target) || {};
|
|
19
|
+
hooks[type] = hooks[type] || [];
|
|
20
|
+
hooks[type].push(propertyKey);
|
|
21
|
+
Reflect.defineMetadata(HOOK_METADATA_KEY, hooks, target);
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get hooks of a specific type for a model instance.
|
|
27
|
+
*/
|
|
28
|
+
export function getHooks(instance: any, type: HookType): Array<() => Promise<void> | void> {
|
|
29
|
+
const proto = Object.getPrototypeOf(instance);
|
|
30
|
+
const hooks: Record<HookType, string[]> =
|
|
31
|
+
Reflect.getMetadata(HOOK_METADATA_KEY, proto) || {};
|
|
32
|
+
return (hooks[type] || []).map((methodName) => instance[methodName].bind(instance));
|
|
33
|
+
}
|
package/index.ts
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* @file stabilize.ts
|
|
3
3
|
* @description The main entry point for the Stabilize ORM, tying together the client, cache, and repositories.
|
|
4
4
|
* @author ElectronSz
|
|
5
|
-
* @date 2025-10-15 20:35:34
|
|
6
5
|
*/
|
|
7
6
|
import { Cache } from "./cache";
|
|
8
7
|
import { DBClient } from "./client";
|
|
@@ -10,6 +9,7 @@ import { type Logger, ConsoleLogger } from "./logger";
|
|
|
10
9
|
import { QueryBuilder } from "./query-builder";
|
|
11
10
|
import { Repository } from "./repository";
|
|
12
11
|
import { runMigrations, generateMigration, type Migration } from "./migrations";
|
|
12
|
+
import { Hook } from "./hooks";
|
|
13
13
|
import {
|
|
14
14
|
Model,
|
|
15
15
|
Column,
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
ModelKey,
|
|
24
24
|
ColumnKey,
|
|
25
25
|
ValidatorKey,
|
|
26
|
+
Versioned,
|
|
26
27
|
RelationKey,
|
|
27
28
|
SoftDeleteKey,
|
|
28
29
|
} from "./decorators";
|
|
@@ -31,7 +32,7 @@ import {
|
|
|
31
32
|
type CacheConfig,
|
|
32
33
|
type LoggerConfig,
|
|
33
34
|
DBType,
|
|
34
|
-
DataTypes,
|
|
35
|
+
DataTypes,
|
|
35
36
|
StabilizeError,
|
|
36
37
|
type PoolMetrics,
|
|
37
38
|
type QueryHint,
|
|
@@ -159,6 +160,8 @@ export {
|
|
|
159
160
|
Required,
|
|
160
161
|
Unique,
|
|
161
162
|
SoftDelete,
|
|
163
|
+
Versioned,
|
|
164
|
+
Hook,
|
|
162
165
|
OneToOne,
|
|
163
166
|
ManyToOne,
|
|
164
167
|
OneToMany,
|
package/migrations.ts
CHANGED
|
@@ -6,14 +6,13 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { DBClient } from "./client";
|
|
9
|
-
import { ModelKey, ColumnKey, ValidatorKey, SoftDeleteKey } from "./decorators";
|
|
10
|
-
import { type DBConfig, type Migration, StabilizeError, DBType } from "./types";
|
|
9
|
+
import { ModelKey, ColumnKey, ValidatorKey, SoftDeleteKey, VersionedKey } from "./decorators";
|
|
10
|
+
import { type DBConfig, type Migration, StabilizeError, DBType, DataTypes } from "./types";
|
|
11
11
|
|
|
12
12
|
type ColumnData = { name: string; type: string };
|
|
13
13
|
type ColumnMetadata = Record<string, ColumnData>;
|
|
14
14
|
type ValidatorMetadata = Record<string, string[]>;
|
|
15
15
|
|
|
16
|
-
// --- FIX: New Helper Function to format queries for different DBs ---
|
|
17
16
|
/**
|
|
18
17
|
* @internal
|
|
19
18
|
* Formats a SQL query with placeholders for the target database dialect.
|
|
@@ -31,56 +30,99 @@ function formatQuery(query: string, dbType: DBType): string {
|
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
33
|
+
* Maps an abstract data type (from DataTypes enum or a string) to the correct SQL type string
|
|
34
|
+
* for the specified database dialect (Postgres, MySQL, or SQLite).
|
|
35
|
+
*
|
|
36
|
+
* This function enables model definitions to be portable across different databases by
|
|
37
|
+
* converting each logical type to its proper SQL type in CREATE TABLE migrations.
|
|
38
|
+
*
|
|
39
|
+
* @param dt - The data type to map. Accepts either a value from the DataTypes enum, or a string
|
|
40
|
+
* (e.g., "string", "integer", "boolean", etc.).
|
|
41
|
+
* @param dbType - The target database dialect (DBType.Postgres, DBType.MySQL, or DBType.SQLite).
|
|
42
|
+
* @returns The SQL column type string appropriate for the database and logical type.
|
|
43
|
+
*
|
|
38
44
|
*/
|
|
39
|
-
function
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
case DBType.SQLite:
|
|
46
|
-
default:
|
|
47
|
-
return "INTEGER PRIMARY KEY AUTOINCREMENT";
|
|
45
|
+
function mapDataTypeToSql(dt: DataTypes | string, dbType: DBType): string {
|
|
46
|
+
let type: string;
|
|
47
|
+
if (typeof dt === "string") {
|
|
48
|
+
type = dt.toLowerCase();
|
|
49
|
+
} else {
|
|
50
|
+
type = DataTypes[dt].toLowerCase();
|
|
48
51
|
}
|
|
49
|
-
}
|
|
50
52
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return "
|
|
61
|
-
|
|
62
|
-
return "
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return "
|
|
53
|
+
if (dbType === DBType.Postgres) {
|
|
54
|
+
switch (type) {
|
|
55
|
+
case "string": return "TEXT";
|
|
56
|
+
case "text": return "TEXT";
|
|
57
|
+
case "integer": return "INTEGER";
|
|
58
|
+
case "bigint": return "BIGINT";
|
|
59
|
+
case "float": return "REAL";
|
|
60
|
+
case "double": return "DOUBLE PRECISION";
|
|
61
|
+
case "decimal": return "DECIMAL";
|
|
62
|
+
case "boolean": return "BOOLEAN";
|
|
63
|
+
case "date": return "DATE";
|
|
64
|
+
case "datetime": return "TIMESTAMP";
|
|
65
|
+
case "json": return "JSONB";
|
|
66
|
+
case "uuid": return "UUID";
|
|
67
|
+
case "blob": return "BYTEA";
|
|
68
|
+
default: return "TEXT";
|
|
69
|
+
}
|
|
66
70
|
}
|
|
71
|
+
if (dbType === DBType.MySQL) {
|
|
72
|
+
switch (type) {
|
|
73
|
+
case "string": return "VARCHAR(255)";
|
|
74
|
+
case "text": return "TEXT";
|
|
75
|
+
case "integer": return "INT";
|
|
76
|
+
case "bigint": return "BIGINT";
|
|
77
|
+
case "float": return "FLOAT";
|
|
78
|
+
case "double": return "DOUBLE";
|
|
79
|
+
case "decimal": return "DECIMAL(10,2)";
|
|
80
|
+
case "boolean": return "TINYINT(1)";
|
|
81
|
+
case "date": return "DATE";
|
|
82
|
+
case "datetime": return "DATETIME";
|
|
83
|
+
case "json": return "JSON";
|
|
84
|
+
case "uuid": return "CHAR(36)";
|
|
85
|
+
case "blob": return "BLOB";
|
|
86
|
+
default: return "TEXT";
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// SQLite
|
|
90
|
+
if (dbType === DBType.SQLite) {
|
|
91
|
+
switch (type) {
|
|
92
|
+
case "string": return "TEXT";
|
|
93
|
+
case "text": return "TEXT";
|
|
94
|
+
case "integer": return "INTEGER";
|
|
95
|
+
case "bigint": return "INTEGER";
|
|
96
|
+
case "float": return "REAL";
|
|
97
|
+
case "double": return "REAL";
|
|
98
|
+
case "decimal": return "NUMERIC";
|
|
99
|
+
case "boolean": return "INTEGER";
|
|
100
|
+
case "date": return "TEXT";
|
|
101
|
+
case "datetime": return "TEXT";
|
|
102
|
+
case "json": return "TEXT";
|
|
103
|
+
case "uuid": return "TEXT";
|
|
104
|
+
case "blob": return "BLOB";
|
|
105
|
+
default: return "TEXT";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return "TEXT";
|
|
67
109
|
}
|
|
68
110
|
|
|
69
111
|
/**
|
|
70
112
|
* @internal
|
|
71
|
-
* Gets the database-specific SQL for
|
|
113
|
+
* Gets the database-specific SQL for an auto-incrementing primary key.
|
|
72
114
|
* @param dbType The target database dialect.
|
|
73
|
-
* @returns The SQL string for the
|
|
115
|
+
* @returns The SQL string for the primary key column definition.
|
|
74
116
|
*/
|
|
75
|
-
function
|
|
117
|
+
function getAutoIncrementPK(dbType: DBType): string {
|
|
76
118
|
switch (dbType) {
|
|
77
119
|
case DBType.Postgres:
|
|
78
|
-
|
|
79
|
-
return "DEFAULT CURRENT_TIMESTAMP";
|
|
120
|
+
return "SERIAL PRIMARY KEY";
|
|
80
121
|
case DBType.MySQL:
|
|
81
|
-
return "
|
|
122
|
+
return "INT AUTO_INCREMENT PRIMARY KEY";
|
|
123
|
+
case DBType.SQLite:
|
|
82
124
|
default:
|
|
83
|
-
return "
|
|
125
|
+
return "INTEGER PRIMARY KEY AUTOINCREMENT";
|
|
84
126
|
}
|
|
85
127
|
}
|
|
86
128
|
|
|
@@ -88,6 +130,8 @@ function getTimestampDefault(dbType: DBType): string {
|
|
|
88
130
|
* Generates SQL migration scripts (`up` and `down`) based on a model's decorators.
|
|
89
131
|
* This function reads the metadata from a model class to create a `CREATE TABLE` statement.
|
|
90
132
|
*
|
|
133
|
+
* If the model is versioned (has @Versioned), also generates a history table for time-travel queries.
|
|
134
|
+
*
|
|
91
135
|
* @param model The model class decorated with `@Model` and `@Column`.
|
|
92
136
|
* @param name A descriptive name for the migration (used for the migration object).
|
|
93
137
|
* @param dbType The target database dialect to generate SQL for. Defaults to Postgres.
|
|
@@ -95,8 +139,8 @@ function getTimestampDefault(dbType: DBType): string {
|
|
|
95
139
|
*/
|
|
96
140
|
export async function generateMigration(
|
|
97
141
|
model: new (...args: any[]) => any,
|
|
98
|
-
name: string,
|
|
99
|
-
dbType: DBType
|
|
142
|
+
name: string,
|
|
143
|
+
dbType: DBType,
|
|
100
144
|
): Promise<Migration> {
|
|
101
145
|
const tableName = Reflect.getMetadata(ModelKey, model);
|
|
102
146
|
if (!tableName) {
|
|
@@ -105,19 +149,19 @@ export async function generateMigration(
|
|
|
105
149
|
|
|
106
150
|
const columns: ColumnMetadata = Reflect.getMetadata(ColumnKey, model.prototype) || {};
|
|
107
151
|
const validators: ValidatorMetadata = Reflect.getMetadata(ValidatorKey, model.prototype) || {};
|
|
108
|
-
const
|
|
152
|
+
const versioned: boolean = !!Reflect.getMetadata(VersionedKey, model);
|
|
109
153
|
|
|
110
|
-
const columnDefs =
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
154
|
+
const columnDefs: string[] = [];
|
|
155
|
+
|
|
156
|
+
for (const [key, col] of Object.entries(columns)) {
|
|
157
|
+
const defParts: string[] = [];
|
|
114
158
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
defParts.push(getTimestampType(dbType));
|
|
159
|
+
if (col.name === "id") {
|
|
160
|
+
defParts.push("id");
|
|
161
|
+
defParts.push(getAutoIncrementPK(dbType));
|
|
119
162
|
} else {
|
|
120
|
-
defParts.push(col.
|
|
163
|
+
defParts.push(col.name);
|
|
164
|
+
defParts.push(mapDataTypeToSql(col.type, dbType));
|
|
121
165
|
}
|
|
122
166
|
|
|
123
167
|
if (validators[key]?.includes("required")) {
|
|
@@ -127,21 +171,54 @@ export async function generateMigration(
|
|
|
127
171
|
defParts.push("UNIQUE");
|
|
128
172
|
}
|
|
129
173
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
174
|
+
columnDefs.push(defParts.join(" "));
|
|
175
|
+
}
|
|
133
176
|
|
|
134
|
-
|
|
135
|
-
}
|
|
177
|
+
const up: string[] = [`CREATE TABLE IF NOT EXISTS ${tableName} (${columnDefs.join(", ")})`];
|
|
178
|
+
const down: string[] = [`DROP TABLE IF EXISTS ${tableName}`];
|
|
136
179
|
|
|
137
|
-
|
|
138
|
-
|
|
180
|
+
// If model is versioned, add history table migration
|
|
181
|
+
if (versioned) {
|
|
182
|
+
const [historyUp, historyDown] = generateHistoryMigration(tableName, columnDefs, dbType);
|
|
183
|
+
up.push(historyUp);
|
|
184
|
+
down.push(historyDown);
|
|
139
185
|
}
|
|
140
186
|
|
|
141
|
-
|
|
142
|
-
|
|
187
|
+
return { up, down, name };
|
|
188
|
+
}
|
|
143
189
|
|
|
144
|
-
|
|
190
|
+
/**
|
|
191
|
+
* Generates SQL for a version/audit history table for time-travel queries.
|
|
192
|
+
* @param tableName The name of the main table.
|
|
193
|
+
* @param columnDefs The column definitions (from the main table).
|
|
194
|
+
* @param dbType The target database dialect.
|
|
195
|
+
*/
|
|
196
|
+
function generateHistoryMigration(
|
|
197
|
+
tableName: string,
|
|
198
|
+
columnDefs: string[],
|
|
199
|
+
dbType: DBType,
|
|
200
|
+
): [string, string] {
|
|
201
|
+
const historyTable = `${tableName}_history`;
|
|
202
|
+
let opType = "VARCHAR(10) NOT NULL";
|
|
203
|
+
let versionType = "INT NOT NULL";
|
|
204
|
+
let tsType = dbType === DBType.MySQL ? "DATETIME" :
|
|
205
|
+
dbType === DBType.SQLite ? "TEXT" : "TIMESTAMP";
|
|
206
|
+
let modByType = dbType === DBType.MySQL ? "VARCHAR(255)" : "TEXT";
|
|
207
|
+
let modAtType = tsType + (dbType === DBType.Postgres ? " DEFAULT CURRENT_TIMESTAMP" : "");
|
|
208
|
+
|
|
209
|
+
const historyColumns = [
|
|
210
|
+
...columnDefs,
|
|
211
|
+
`operation ${opType}`,
|
|
212
|
+
`version ${versionType}`,
|
|
213
|
+
`valid_from ${tsType} NOT NULL`,
|
|
214
|
+
`valid_to ${tsType}`,
|
|
215
|
+
`modified_by ${modByType}`,
|
|
216
|
+
`modified_at ${modAtType}`
|
|
217
|
+
];
|
|
218
|
+
return [
|
|
219
|
+
`CREATE TABLE IF NOT EXISTS ${historyTable} (${historyColumns.join(", ")})`,
|
|
220
|
+
`DROP TABLE IF EXISTS ${historyTable}`
|
|
221
|
+
];
|
|
145
222
|
}
|
|
146
223
|
|
|
147
224
|
/**
|
|
@@ -153,20 +230,20 @@ export async function generateMigration(
|
|
|
153
230
|
function getMigrationsTableSQL(dbType: DBType): string {
|
|
154
231
|
switch (dbType) {
|
|
155
232
|
case DBType.Postgres:
|
|
156
|
-
return `CREATE TABLE IF NOT EXISTS
|
|
233
|
+
return `CREATE TABLE IF NOT EXISTS stabilize_migrations (
|
|
157
234
|
id SERIAL PRIMARY KEY,
|
|
158
235
|
name VARCHAR(255) UNIQUE NOT NULL,
|
|
159
236
|
applied_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
160
237
|
)`;
|
|
161
238
|
case DBType.MySQL:
|
|
162
|
-
return `CREATE TABLE IF NOT EXISTS
|
|
239
|
+
return `CREATE TABLE IF NOT EXISTS stabilize_migrations (
|
|
163
240
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
164
241
|
name VARCHAR(255) UNIQUE NOT NULL,
|
|
165
242
|
applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
166
243
|
)`;
|
|
167
244
|
case DBType.SQLite:
|
|
168
245
|
default:
|
|
169
|
-
return `CREATE TABLE IF NOT EXISTS
|
|
246
|
+
return `CREATE TABLE IF NOT EXISTS stabilize_migrations (
|
|
170
247
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
171
248
|
name TEXT UNIQUE NOT NULL,
|
|
172
249
|
applied_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
@@ -190,8 +267,8 @@ export async function runMigrations(config: DBConfig, migrations: Migration[]) {
|
|
|
190
267
|
|
|
191
268
|
for (const [index, migration] of migrations.entries()) {
|
|
192
269
|
const name = migration.name || `migration_${index}_${new Date().getTime()}`;
|
|
193
|
-
|
|
194
|
-
const selectQuery = formatQuery(`SELECT id FROM
|
|
270
|
+
|
|
271
|
+
const selectQuery = formatQuery(`SELECT id FROM stabilize_migrations WHERE name = ?`, dbType);
|
|
195
272
|
const applied = await client.query<{ id: number }>(selectQuery, [name]);
|
|
196
273
|
|
|
197
274
|
if (applied.length === 0) {
|
|
@@ -200,17 +277,17 @@ export async function runMigrations(config: DBConfig, migrations: Migration[]) {
|
|
|
200
277
|
for (const query of migration.up) {
|
|
201
278
|
await txClient.query(query);
|
|
202
279
|
}
|
|
203
|
-
|
|
204
|
-
const insertQuery = formatQuery(`INSERT INTO
|
|
280
|
+
|
|
281
|
+
const insertQuery = formatQuery(`INSERT INTO stabilize_migrations (name, applied_at) VALUES (?, ?)`, dbType);
|
|
205
282
|
await txClient.query(insertQuery, [name, new Date().toISOString()]);
|
|
206
|
-
|
|
283
|
+
|
|
207
284
|
console.log(`Migration ${name} applied successfully.`);
|
|
208
285
|
});
|
|
209
286
|
}
|
|
210
287
|
}
|
|
211
288
|
} catch (error) {
|
|
212
289
|
console.error("Migration failed:", error);
|
|
213
|
-
throw error;
|
|
290
|
+
throw error;
|
|
214
291
|
} finally {
|
|
215
292
|
await client.close();
|
|
216
293
|
}
|
package/package.json
CHANGED