@coherent.js/database 1.0.0-beta.2
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/LICENSE +21 -0
- package/README.md +74 -0
- package/dist/database/adapters/memory.d.ts +48 -0
- package/dist/database/adapters/memory.d.ts.map +1 -0
- package/dist/database/adapters/memory.js +250 -0
- package/dist/database/adapters/memory.js.map +1 -0
- package/dist/database/adapters/mongodb.d.ts +15 -0
- package/dist/database/adapters/mongodb.d.ts.map +1 -0
- package/dist/database/adapters/mongodb.js +216 -0
- package/dist/database/adapters/mongodb.js.map +1 -0
- package/dist/database/adapters/mysql.d.ts +12 -0
- package/dist/database/adapters/mysql.d.ts.map +1 -0
- package/dist/database/adapters/mysql.js +171 -0
- package/dist/database/adapters/mysql.js.map +1 -0
- package/dist/database/adapters/postgresql.d.ts +12 -0
- package/dist/database/adapters/postgresql.d.ts.map +1 -0
- package/dist/database/adapters/postgresql.js +177 -0
- package/dist/database/adapters/postgresql.js.map +1 -0
- package/dist/database/adapters/sqlite.d.ts +15 -0
- package/dist/database/adapters/sqlite.d.ts.map +1 -0
- package/dist/database/adapters/sqlite.js +241 -0
- package/dist/database/adapters/sqlite.js.map +1 -0
- package/dist/database/connection-manager.d.ts +148 -0
- package/dist/database/connection-manager.d.ts.map +1 -0
- package/dist/database/connection-manager.js +377 -0
- package/dist/database/connection-manager.js.map +1 -0
- package/dist/database/index.d.ts +38 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +63 -0
- package/dist/database/index.js.map +1 -0
- package/dist/database/middleware.d.ts +122 -0
- package/dist/database/middleware.d.ts.map +1 -0
- package/dist/database/middleware.js +403 -0
- package/dist/database/middleware.js.map +1 -0
- package/dist/database/migration.d.ts +168 -0
- package/dist/database/migration.d.ts.map +1 -0
- package/dist/database/migration.js +946 -0
- package/dist/database/migration.js.map +1 -0
- package/dist/database/model.d.ts +81 -0
- package/dist/database/model.d.ts.map +1 -0
- package/dist/database/model.js +686 -0
- package/dist/database/model.js.map +1 -0
- package/dist/database/query-builder.d.ts +136 -0
- package/dist/database/query-builder.d.ts.map +1 -0
- package/dist/database/query-builder.js +248 -0
- package/dist/database/query-builder.js.map +1 -0
- package/dist/database/utils.d.ts +196 -0
- package/dist/database/utils.d.ts.map +1 -0
- package/dist/database/utils.js +372 -0
- package/dist/database/utils.js.map +1 -0
- package/dist/index.cjs +2286 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.js +2240 -0
- package/dist/index.js.map +7 -0
- package/package.json +52 -0
- package/types/index.d.ts +732 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2286 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
+
for (let key of __getOwnPropNames(from))
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
+
mod
|
|
30
|
+
));
|
|
31
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
|
+
|
|
33
|
+
// src/migration.js
|
|
34
|
+
var migration_exports = {};
|
|
35
|
+
__export(migration_exports, {
|
|
36
|
+
Migration: () => Migration,
|
|
37
|
+
SchemaBuilder: () => SchemaBuilder,
|
|
38
|
+
TableBuilder: () => TableBuilder,
|
|
39
|
+
createMigration: () => createMigration,
|
|
40
|
+
createSchemaBuilder: () => createSchemaBuilder,
|
|
41
|
+
createTableBuilder: () => createTableBuilder
|
|
42
|
+
});
|
|
43
|
+
function createColumnBuilder(column) {
|
|
44
|
+
return {
|
|
45
|
+
notNull() {
|
|
46
|
+
column.nullable = false;
|
|
47
|
+
return this;
|
|
48
|
+
},
|
|
49
|
+
unique() {
|
|
50
|
+
column.unique = true;
|
|
51
|
+
return this;
|
|
52
|
+
},
|
|
53
|
+
default(value) {
|
|
54
|
+
column.default = typeof value === "string" ? `'${value}'` : value;
|
|
55
|
+
return this;
|
|
56
|
+
},
|
|
57
|
+
references(foreignKey) {
|
|
58
|
+
column.references = foreignKey;
|
|
59
|
+
return this;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function createMigration(db, config = {}) {
|
|
64
|
+
const migrationConfig = {
|
|
65
|
+
directory: "./migrations",
|
|
66
|
+
tableName: "coherent_migrations",
|
|
67
|
+
...config
|
|
68
|
+
};
|
|
69
|
+
const migrations = [];
|
|
70
|
+
const appliedMigrations = /* @__PURE__ */ new Set();
|
|
71
|
+
async function ensureMigrationsTable() {
|
|
72
|
+
const tableName = migrationConfig.tableName;
|
|
73
|
+
try {
|
|
74
|
+
await db.query(`SELECT 1 FROM ${tableName} LIMIT 1`);
|
|
75
|
+
} catch {
|
|
76
|
+
const createTableSQL = `
|
|
77
|
+
CREATE TABLE ${tableName} (
|
|
78
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
79
|
+
migration VARCHAR(255) NOT NULL UNIQUE,
|
|
80
|
+
batch INTEGER NOT NULL,
|
|
81
|
+
executed_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
82
|
+
)
|
|
83
|
+
`;
|
|
84
|
+
await db.query(createTableSQL);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async function loadAppliedMigrations() {
|
|
88
|
+
const tableName = migrationConfig.tableName;
|
|
89
|
+
const result = await db.query(`SELECT migration FROM ${tableName} ORDER BY executed_at`);
|
|
90
|
+
if (result.rows) {
|
|
91
|
+
result.rows.forEach((row) => {
|
|
92
|
+
appliedMigrations.add(row.migration);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function loadMigrationFiles() {
|
|
97
|
+
try {
|
|
98
|
+
const files = await (0, import_promises.readdir)(migrationConfig.directory);
|
|
99
|
+
const migrationFiles = files.filter((file) => file.endsWith(".js")).sort();
|
|
100
|
+
for (const file of migrationFiles) {
|
|
101
|
+
const migrationName = file.replace(".js", "");
|
|
102
|
+
const filePath = (0, import_path.join)(migrationConfig.directory, file);
|
|
103
|
+
try {
|
|
104
|
+
const migration = await import(filePath);
|
|
105
|
+
migrations.push({
|
|
106
|
+
name: migrationName,
|
|
107
|
+
file: filePath,
|
|
108
|
+
up: migration.up,
|
|
109
|
+
down: migration.down,
|
|
110
|
+
applied: appliedMigrations.has(migrationName)
|
|
111
|
+
});
|
|
112
|
+
} catch (_error) {
|
|
113
|
+
console.warn(`Failed to load migration ${file}: ${_error.message}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} catch (_error) {
|
|
117
|
+
if (_error.code !== "ENOENT") {
|
|
118
|
+
throw _error;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function getNextBatchNumber() {
|
|
123
|
+
const result = await db.query(
|
|
124
|
+
`SELECT MAX(batch) as max_batch FROM ${migrationConfig.tableName}`
|
|
125
|
+
);
|
|
126
|
+
const maxBatch = result.rows && result.rows[0] ? result.rows[0].max_batch : 0;
|
|
127
|
+
return (maxBatch || 0) + 1;
|
|
128
|
+
}
|
|
129
|
+
async function getLastBatches(count) {
|
|
130
|
+
const result = await db.query(
|
|
131
|
+
`SELECT DISTINCT batch FROM ${migrationConfig.tableName} ORDER BY batch DESC LIMIT ?`,
|
|
132
|
+
[count]
|
|
133
|
+
);
|
|
134
|
+
return result.rows ? result.rows.map((row) => row.batch) : [];
|
|
135
|
+
}
|
|
136
|
+
async function getMigrationsInBatch(batch) {
|
|
137
|
+
const result = await db.query(
|
|
138
|
+
`SELECT migration FROM ${migrationConfig.tableName} WHERE batch = ? ORDER BY executed_at`,
|
|
139
|
+
[batch]
|
|
140
|
+
);
|
|
141
|
+
return result.rows ? result.rows.map((row) => row.migration) : [];
|
|
142
|
+
}
|
|
143
|
+
async function ensureDirectory(dirPath) {
|
|
144
|
+
try {
|
|
145
|
+
const { mkdir } = await import("fs/promises");
|
|
146
|
+
await mkdir(dirPath, { recursive: true });
|
|
147
|
+
} catch (_error) {
|
|
148
|
+
if (_error.code !== "EEXIST") {
|
|
149
|
+
throw _error;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function getMigrationTemplate(name, options) {
|
|
154
|
+
const tableName = options.table || name.replace(/^create_/, "").replace(/_table$/, "");
|
|
155
|
+
if (name.startsWith("create_")) {
|
|
156
|
+
return `/**
|
|
157
|
+
* Migration: ${name}
|
|
158
|
+
* Created: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
159
|
+
*/
|
|
160
|
+
|
|
161
|
+
export async function up(schema) {
|
|
162
|
+
await schema.createTable('${tableName}', (table) => {
|
|
163
|
+
table.id();
|
|
164
|
+
table.string('name').notNull();
|
|
165
|
+
table.timestamps();
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export async function down(schema) {
|
|
170
|
+
await schema.dropTable('${tableName}');
|
|
171
|
+
}
|
|
172
|
+
`;
|
|
173
|
+
}
|
|
174
|
+
return `/**
|
|
175
|
+
* Migration: ${name}
|
|
176
|
+
* Created: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
177
|
+
*/
|
|
178
|
+
|
|
179
|
+
export async function up(schema) {
|
|
180
|
+
// Add your migration logic here
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export async function down(schema) {
|
|
184
|
+
// Add your rollback logic here
|
|
185
|
+
}
|
|
186
|
+
`;
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
/**
|
|
190
|
+
* Initialize migration system
|
|
191
|
+
*/
|
|
192
|
+
async initialize() {
|
|
193
|
+
await ensureMigrationsTable();
|
|
194
|
+
await loadAppliedMigrations();
|
|
195
|
+
await loadMigrationFiles();
|
|
196
|
+
},
|
|
197
|
+
/**
|
|
198
|
+
* Run pending migrations
|
|
199
|
+
*/
|
|
200
|
+
async run(options = {}) {
|
|
201
|
+
await this.initialize();
|
|
202
|
+
const pendingMigrations = migrations.filter((m) => !m.applied);
|
|
203
|
+
if (pendingMigrations.length === 0) {
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
const batch = await getNextBatchNumber();
|
|
207
|
+
const appliedMigrationsList = [];
|
|
208
|
+
for (const migration of pendingMigrations) {
|
|
209
|
+
try {
|
|
210
|
+
console.log(`Running migration: ${migration.name}`);
|
|
211
|
+
const tx = await db.transaction();
|
|
212
|
+
try {
|
|
213
|
+
await migration.up(createSchemaBuilder(tx));
|
|
214
|
+
await tx.query(
|
|
215
|
+
`INSERT INTO ${migrationConfig.tableName} (migration, batch) VALUES (?, ?)`,
|
|
216
|
+
[migration.name, batch]
|
|
217
|
+
);
|
|
218
|
+
await tx.commit();
|
|
219
|
+
appliedMigrationsList.push(migration.name);
|
|
220
|
+
migration.applied = true;
|
|
221
|
+
console.log(`\u2713 Migration ${migration.name} completed`);
|
|
222
|
+
} catch (_error) {
|
|
223
|
+
await tx.rollback();
|
|
224
|
+
throw _error;
|
|
225
|
+
}
|
|
226
|
+
} catch (_error) {
|
|
227
|
+
console.error(`\u2717 Migration ${migration.name} failed: ${_error.message}`);
|
|
228
|
+
if (!options.continueOnError) {
|
|
229
|
+
throw _error;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return appliedMigrationsList;
|
|
234
|
+
},
|
|
235
|
+
/**
|
|
236
|
+
* Rollback migrations
|
|
237
|
+
*/
|
|
238
|
+
async rollback(steps = 1) {
|
|
239
|
+
await this.initialize();
|
|
240
|
+
const batches = await getLastBatches(steps);
|
|
241
|
+
if (batches.length === 0) {
|
|
242
|
+
return [];
|
|
243
|
+
}
|
|
244
|
+
const rolledBackMigrations = [];
|
|
245
|
+
for (const batch of batches) {
|
|
246
|
+
const batchMigrations = await getMigrationsInBatch(batch);
|
|
247
|
+
for (const migrationName of batchMigrations.reverse()) {
|
|
248
|
+
const migration = migrations.find((m) => m.name === migrationName);
|
|
249
|
+
if (!migration || !migration.down) {
|
|
250
|
+
console.warn(`Cannot rollback migration: ${migrationName}`);
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
console.log(`Rolling back migration: ${migrationName}`);
|
|
255
|
+
const tx = await db.transaction();
|
|
256
|
+
try {
|
|
257
|
+
await migration.down(createSchemaBuilder(tx));
|
|
258
|
+
await tx.query(
|
|
259
|
+
`DELETE FROM ${migrationConfig.tableName} WHERE migration = ?`,
|
|
260
|
+
[migrationName]
|
|
261
|
+
);
|
|
262
|
+
await tx.commit();
|
|
263
|
+
rolledBackMigrations.push(migrationName);
|
|
264
|
+
migration.applied = false;
|
|
265
|
+
console.log(`\u2713 Migration ${migrationName} rolled back`);
|
|
266
|
+
} catch (_error) {
|
|
267
|
+
await tx.rollback();
|
|
268
|
+
throw _error;
|
|
269
|
+
}
|
|
270
|
+
} catch (_error) {
|
|
271
|
+
console.error(`\u2717 Rollback ${migrationName} failed: ${_error.message}`);
|
|
272
|
+
throw _error;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return rolledBackMigrations;
|
|
277
|
+
},
|
|
278
|
+
/**
|
|
279
|
+
* Get migration status
|
|
280
|
+
*/
|
|
281
|
+
async status() {
|
|
282
|
+
await this.initialize();
|
|
283
|
+
return migrations.map((migration) => ({
|
|
284
|
+
name: migration.name,
|
|
285
|
+
applied: migration.applied,
|
|
286
|
+
file: migration.file
|
|
287
|
+
}));
|
|
288
|
+
},
|
|
289
|
+
/**
|
|
290
|
+
* Create new migration file
|
|
291
|
+
*/
|
|
292
|
+
async create(name, options = {}) {
|
|
293
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:T]/g, "").split(".")[0];
|
|
294
|
+
const fileName = `${timestamp}_${name}.js`;
|
|
295
|
+
const filePath = (0, import_path.join)(migrationConfig.directory, fileName);
|
|
296
|
+
const template = getMigrationTemplate(name, options);
|
|
297
|
+
await ensureDirectory(migrationConfig.directory);
|
|
298
|
+
await (0, import_promises.writeFile)(filePath, template);
|
|
299
|
+
console.log(`Created migration: ${filePath}`);
|
|
300
|
+
return filePath;
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
function createSchemaBuilder(db) {
|
|
305
|
+
return {
|
|
306
|
+
async createTable(tableName, callback) {
|
|
307
|
+
const table = createTableBuilder(tableName);
|
|
308
|
+
callback(table);
|
|
309
|
+
const sql = table.toCreateSQL();
|
|
310
|
+
await db.query(sql);
|
|
311
|
+
},
|
|
312
|
+
async alterTable(tableName, callback) {
|
|
313
|
+
const table = createTableBuilder(tableName);
|
|
314
|
+
callback(table);
|
|
315
|
+
const statements = table.toAlterSQL();
|
|
316
|
+
for (const sql of statements) {
|
|
317
|
+
await db.query(sql);
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
async dropTable(tableName) {
|
|
321
|
+
await db.query(`DROP TABLE IF EXISTS ${tableName}`);
|
|
322
|
+
},
|
|
323
|
+
async raw(sql, params = []) {
|
|
324
|
+
return await db.query(sql, params);
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
function createTableBuilder(tableName) {
|
|
329
|
+
const columns = [];
|
|
330
|
+
const alterations = [];
|
|
331
|
+
function createColumnBuilder2(column) {
|
|
332
|
+
return {
|
|
333
|
+
notNull() {
|
|
334
|
+
column.nullable = false;
|
|
335
|
+
return this;
|
|
336
|
+
},
|
|
337
|
+
unique() {
|
|
338
|
+
column.unique = true;
|
|
339
|
+
return this;
|
|
340
|
+
},
|
|
341
|
+
default(value) {
|
|
342
|
+
column.default = typeof value === "string" ? `'${value}'` : value;
|
|
343
|
+
return this;
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
return {
|
|
348
|
+
id(name = "id") {
|
|
349
|
+
columns.push({
|
|
350
|
+
name,
|
|
351
|
+
type: "INTEGER",
|
|
352
|
+
primaryKey: true,
|
|
353
|
+
autoIncrement: true
|
|
354
|
+
});
|
|
355
|
+
return this;
|
|
356
|
+
},
|
|
357
|
+
string(name, length = 255) {
|
|
358
|
+
const column = {
|
|
359
|
+
name,
|
|
360
|
+
type: `VARCHAR(${length})`,
|
|
361
|
+
nullable: true
|
|
362
|
+
};
|
|
363
|
+
columns.push(column);
|
|
364
|
+
return createColumnBuilder2(column);
|
|
365
|
+
},
|
|
366
|
+
text(name) {
|
|
367
|
+
const column = {
|
|
368
|
+
name,
|
|
369
|
+
type: "TEXT",
|
|
370
|
+
nullable: true
|
|
371
|
+
};
|
|
372
|
+
columns.push(column);
|
|
373
|
+
return createColumnBuilder2(column);
|
|
374
|
+
},
|
|
375
|
+
integer(name) {
|
|
376
|
+
const column = {
|
|
377
|
+
name,
|
|
378
|
+
type: "INTEGER",
|
|
379
|
+
nullable: true
|
|
380
|
+
};
|
|
381
|
+
columns.push(column);
|
|
382
|
+
return createColumnBuilder2(column);
|
|
383
|
+
},
|
|
384
|
+
boolean(name) {
|
|
385
|
+
const column = {
|
|
386
|
+
name,
|
|
387
|
+
type: "BOOLEAN",
|
|
388
|
+
nullable: true,
|
|
389
|
+
default: false
|
|
390
|
+
};
|
|
391
|
+
columns.push(column);
|
|
392
|
+
return createColumnBuilder2(column);
|
|
393
|
+
},
|
|
394
|
+
datetime(name) {
|
|
395
|
+
const column = {
|
|
396
|
+
name,
|
|
397
|
+
type: "DATETIME",
|
|
398
|
+
nullable: true
|
|
399
|
+
};
|
|
400
|
+
columns.push(column);
|
|
401
|
+
return createColumnBuilder2(column);
|
|
402
|
+
},
|
|
403
|
+
timestamps() {
|
|
404
|
+
this.datetime("created_at").default("CURRENT_TIMESTAMP");
|
|
405
|
+
this.datetime("updated_at").default("CURRENT_TIMESTAMP");
|
|
406
|
+
return this;
|
|
407
|
+
},
|
|
408
|
+
addColumn(name, type) {
|
|
409
|
+
alterations.push({
|
|
410
|
+
type: "ADD",
|
|
411
|
+
name,
|
|
412
|
+
columnType: type
|
|
413
|
+
});
|
|
414
|
+
return this;
|
|
415
|
+
},
|
|
416
|
+
dropColumn(name) {
|
|
417
|
+
alterations.push({
|
|
418
|
+
type: "DROP",
|
|
419
|
+
name
|
|
420
|
+
});
|
|
421
|
+
return this;
|
|
422
|
+
},
|
|
423
|
+
toCreateSQL() {
|
|
424
|
+
const columnDefs = columns.map((col) => {
|
|
425
|
+
let def = `${col.name} ${col.type}`;
|
|
426
|
+
if (col.primaryKey) {
|
|
427
|
+
def += " PRIMARY KEY";
|
|
428
|
+
}
|
|
429
|
+
if (col.autoIncrement) {
|
|
430
|
+
def += " AUTOINCREMENT";
|
|
431
|
+
}
|
|
432
|
+
if (!col.nullable) {
|
|
433
|
+
def += " NOT NULL";
|
|
434
|
+
}
|
|
435
|
+
if (col.unique) {
|
|
436
|
+
def += " UNIQUE";
|
|
437
|
+
}
|
|
438
|
+
if (col.default !== void 0) {
|
|
439
|
+
def += ` DEFAULT ${col.default}`;
|
|
440
|
+
}
|
|
441
|
+
return def;
|
|
442
|
+
});
|
|
443
|
+
return `CREATE TABLE ${tableName} (
|
|
444
|
+
${columnDefs.join(",\n ")}
|
|
445
|
+
)`;
|
|
446
|
+
},
|
|
447
|
+
toAlterSQL() {
|
|
448
|
+
return alterations.map((alt) => {
|
|
449
|
+
switch (alt.type) {
|
|
450
|
+
case "ADD":
|
|
451
|
+
return `ALTER TABLE ${tableName} ADD COLUMN ${alt.name} ${alt.columnType}`;
|
|
452
|
+
case "DROP":
|
|
453
|
+
return `ALTER TABLE ${tableName} DROP COLUMN ${alt.name}`;
|
|
454
|
+
default:
|
|
455
|
+
throw new Error(`Unsupported alteration type: ${alt.type}`);
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
var import_promises, import_path, Migration, SchemaBuilder, TableBuilder;
|
|
462
|
+
var init_migration = __esm({
|
|
463
|
+
"src/migration.js"() {
|
|
464
|
+
"use strict";
|
|
465
|
+
import_promises = require("fs/promises");
|
|
466
|
+
import_path = require("path");
|
|
467
|
+
Migration = class {
|
|
468
|
+
constructor(db, config = {}) {
|
|
469
|
+
this.db = db;
|
|
470
|
+
this.config = { directory: "./migrations", tableName: "coherent_migrations", ...config };
|
|
471
|
+
this.appliedMigrations = /* @__PURE__ */ new Set();
|
|
472
|
+
}
|
|
473
|
+
async run(options = {}) {
|
|
474
|
+
await this.ensureMigrationsTable();
|
|
475
|
+
await this.loadAppliedMigrations();
|
|
476
|
+
let usedOldLoadMigrations = false;
|
|
477
|
+
if (this.loadMigrations && typeof this.loadMigrations === "function") {
|
|
478
|
+
try {
|
|
479
|
+
const migrationFiles = await this.loadMigrations();
|
|
480
|
+
if (migrationFiles && Array.isArray(migrationFiles)) {
|
|
481
|
+
this.migrations = migrationFiles.map((m) => ({ ...m, applied: false }));
|
|
482
|
+
usedOldLoadMigrations = true;
|
|
483
|
+
}
|
|
484
|
+
} catch {
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
if (!this.migrations || this.migrations.length === 0) {
|
|
488
|
+
try {
|
|
489
|
+
await this.loadMigrationFiles();
|
|
490
|
+
} catch {
|
|
491
|
+
this.migrations = this.migrations || [];
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
const migrations = this.migrations || [];
|
|
495
|
+
const pendingMigrations = migrations.filter((m) => !m.applied);
|
|
496
|
+
if (pendingMigrations.length === 0) {
|
|
497
|
+
return [];
|
|
498
|
+
}
|
|
499
|
+
const batch = await this.getNextBatchNumber();
|
|
500
|
+
const appliedMigrationsList = [];
|
|
501
|
+
for (const migration of pendingMigrations) {
|
|
502
|
+
try {
|
|
503
|
+
const tx = this.db.transaction ? await this.db.transaction() : this.db;
|
|
504
|
+
try {
|
|
505
|
+
if (migration.up) {
|
|
506
|
+
await migration.up(new SchemaBuilder(tx));
|
|
507
|
+
}
|
|
508
|
+
await tx.query(
|
|
509
|
+
`INSERT INTO ${this.config.tableName} (migration, batch) VALUES (?, ?)`,
|
|
510
|
+
[migration.name, batch]
|
|
511
|
+
);
|
|
512
|
+
if (tx.commit) {
|
|
513
|
+
await tx.commit();
|
|
514
|
+
}
|
|
515
|
+
if (usedOldLoadMigrations) {
|
|
516
|
+
appliedMigrationsList.push({ name: migration.name });
|
|
517
|
+
} else {
|
|
518
|
+
appliedMigrationsList.push(migration.name);
|
|
519
|
+
}
|
|
520
|
+
migration.applied = true;
|
|
521
|
+
} catch (_error) {
|
|
522
|
+
if (tx.rollback) {
|
|
523
|
+
await tx.rollback();
|
|
524
|
+
}
|
|
525
|
+
throw _error;
|
|
526
|
+
}
|
|
527
|
+
} catch (_error) {
|
|
528
|
+
console.error(`Migration ${migration.name} failed: ${_error.message}`);
|
|
529
|
+
if (!options.continueOnError) {
|
|
530
|
+
throw _error;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return appliedMigrationsList;
|
|
535
|
+
}
|
|
536
|
+
async rollback(steps = 1) {
|
|
537
|
+
try {
|
|
538
|
+
await this.loadAppliedMigrations();
|
|
539
|
+
await this.loadMigrationFiles();
|
|
540
|
+
} catch {
|
|
541
|
+
}
|
|
542
|
+
let migrationsToRollback = [];
|
|
543
|
+
if (typeof this.getMigrationsInBatch === "function") {
|
|
544
|
+
try {
|
|
545
|
+
const migrationNames = await this.getMigrationsInBatch(steps);
|
|
546
|
+
migrationsToRollback = [];
|
|
547
|
+
for (const name of migrationNames) {
|
|
548
|
+
const migration = this.migrations.find((m) => m.name === name);
|
|
549
|
+
if (migration) {
|
|
550
|
+
migrationsToRollback.push(migration);
|
|
551
|
+
} else {
|
|
552
|
+
console.warn(`Migration file not found: ${name}`);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
} catch {
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
if (migrationsToRollback.length === 0) {
|
|
559
|
+
const migrations = this.migrations || [];
|
|
560
|
+
const appliedMigrations = migrations.filter((m) => m.applied);
|
|
561
|
+
if (appliedMigrations.length === 0) {
|
|
562
|
+
return [];
|
|
563
|
+
}
|
|
564
|
+
migrationsToRollback = appliedMigrations.slice(-steps).reverse();
|
|
565
|
+
}
|
|
566
|
+
const rolledBackMigrations = [];
|
|
567
|
+
for (const migration of migrationsToRollback) {
|
|
568
|
+
if (!migration.down) {
|
|
569
|
+
console.warn(`No rollback method for migration: ${migration.name}`);
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
try {
|
|
573
|
+
const tx = this.db.transaction ? await this.db.transaction() : this.db;
|
|
574
|
+
try {
|
|
575
|
+
await migration.down(new SchemaBuilder(tx));
|
|
576
|
+
await tx.query(
|
|
577
|
+
`DELETE FROM ${this.config.tableName} WHERE migration = ?`,
|
|
578
|
+
[migration.name]
|
|
579
|
+
);
|
|
580
|
+
if (tx.commit) {
|
|
581
|
+
await tx.commit();
|
|
582
|
+
}
|
|
583
|
+
rolledBackMigrations.push(migration.name);
|
|
584
|
+
migration.applied = false;
|
|
585
|
+
} catch (_error) {
|
|
586
|
+
if (tx.rollback) {
|
|
587
|
+
await tx.rollback();
|
|
588
|
+
}
|
|
589
|
+
throw _error;
|
|
590
|
+
}
|
|
591
|
+
} catch (_error) {
|
|
592
|
+
console.error(`Rollback ${migration.name} failed: ${_error.message}`);
|
|
593
|
+
throw _error;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
return rolledBackMigrations;
|
|
597
|
+
}
|
|
598
|
+
async status() {
|
|
599
|
+
try {
|
|
600
|
+
await this.loadAppliedMigrations();
|
|
601
|
+
if (this.loadMigrations && typeof this.loadMigrations === "function") {
|
|
602
|
+
try {
|
|
603
|
+
const migrationFiles = await this.loadMigrations();
|
|
604
|
+
if (migrationFiles && Array.isArray(migrationFiles)) {
|
|
605
|
+
this.migrations = migrationFiles.map((m) => ({
|
|
606
|
+
...m,
|
|
607
|
+
applied: this.appliedMigrations.has(m.name)
|
|
608
|
+
}));
|
|
609
|
+
}
|
|
610
|
+
} catch {
|
|
611
|
+
await this.loadMigrationFiles();
|
|
612
|
+
}
|
|
613
|
+
} else {
|
|
614
|
+
await this.loadMigrationFiles();
|
|
615
|
+
}
|
|
616
|
+
} catch {
|
|
617
|
+
}
|
|
618
|
+
const migrations = this.migrations || [];
|
|
619
|
+
const pending = migrations.filter((m) => !m.applied);
|
|
620
|
+
const completed = migrations.filter((m) => m.applied);
|
|
621
|
+
return {
|
|
622
|
+
pending: pending.map((migration) => ({
|
|
623
|
+
name: migration.name,
|
|
624
|
+
applied: migration.applied,
|
|
625
|
+
file: migration.file || `${migration.name}.js`
|
|
626
|
+
})),
|
|
627
|
+
completed: completed.map((migration) => ({
|
|
628
|
+
name: migration.name,
|
|
629
|
+
applied: migration.applied,
|
|
630
|
+
file: migration.file || `${migration.name}.js`
|
|
631
|
+
}))
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
async create(name, options = {}) {
|
|
635
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:T]/g, "").split(".")[0];
|
|
636
|
+
const fileName = `${timestamp}_${name}.js`;
|
|
637
|
+
const filePath = `${this.config.directory}/${fileName}`;
|
|
638
|
+
if (typeof this.ensureDirectory === "function") {
|
|
639
|
+
await this.ensureDirectory();
|
|
640
|
+
}
|
|
641
|
+
const isCreateTable = name.startsWith("create_") && name.endsWith("_table");
|
|
642
|
+
const template = this.getMigrationTemplate(name, { isCreateTable, ...options });
|
|
643
|
+
const fs = await import("fs/promises");
|
|
644
|
+
await fs.writeFile(filePath, template);
|
|
645
|
+
return filePath;
|
|
646
|
+
}
|
|
647
|
+
getMigrationTemplate(name, options = {}) {
|
|
648
|
+
const { isCreateTable } = options;
|
|
649
|
+
const tableName = isCreateTable ? name.replace("create_", "").replace("_table", "") : "table_name";
|
|
650
|
+
if (isCreateTable) {
|
|
651
|
+
return `/**
|
|
652
|
+
* Migration: ${name}
|
|
653
|
+
*/
|
|
654
|
+
|
|
655
|
+
export async function up(schema) {
|
|
656
|
+
await schema.createTable('${tableName}', (table) => {
|
|
657
|
+
table.id();
|
|
658
|
+
table.timestamps();
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
export async function down(schema) {
|
|
663
|
+
await schema.dropTable('${tableName}');
|
|
664
|
+
}
|
|
665
|
+
`;
|
|
666
|
+
} else {
|
|
667
|
+
return `/**
|
|
668
|
+
* Migration: ${name}
|
|
669
|
+
*/
|
|
670
|
+
|
|
671
|
+
export async function up(schema) {
|
|
672
|
+
// Add your migration logic here
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
export async function down(schema) {
|
|
676
|
+
// Add your rollback logic here
|
|
677
|
+
}
|
|
678
|
+
`;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
async getNextBatchNumber() {
|
|
682
|
+
const result = await this.db.query(`SELECT MAX(batch) as max_batch FROM ${this.config.tableName}`);
|
|
683
|
+
const maxBatch = result.rows && result.rows[0] ? result.rows[0].max_batch : 0;
|
|
684
|
+
return (maxBatch || 0) + 1;
|
|
685
|
+
}
|
|
686
|
+
// Additional methods expected by tests
|
|
687
|
+
async ensureMigrationsTable() {
|
|
688
|
+
try {
|
|
689
|
+
await this.db.query(`SELECT 1 FROM ${this.config.tableName} LIMIT 1`);
|
|
690
|
+
} catch {
|
|
691
|
+
const createTableSQL = `
|
|
692
|
+
CREATE TABLE ${this.config.tableName} (
|
|
693
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
694
|
+
migration VARCHAR(255) NOT NULL UNIQUE,
|
|
695
|
+
batch INTEGER NOT NULL,
|
|
696
|
+
executed_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
697
|
+
)
|
|
698
|
+
`;
|
|
699
|
+
await this.db.query(createTableSQL);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
async loadAppliedMigrations() {
|
|
703
|
+
const result = await this.db.query(`SELECT migration FROM ${this.config.tableName} ORDER BY executed_at`);
|
|
704
|
+
this.appliedMigrations.clear();
|
|
705
|
+
if (result.rows) {
|
|
706
|
+
result.rows.forEach((row) => {
|
|
707
|
+
this.appliedMigrations.add(row.migration);
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
return Promise.resolve();
|
|
711
|
+
}
|
|
712
|
+
async loadMigrationFiles() {
|
|
713
|
+
try {
|
|
714
|
+
const fs = await import("fs/promises");
|
|
715
|
+
const files = await fs.readdir(this.config.directory);
|
|
716
|
+
const migrationFiles = [];
|
|
717
|
+
for (const file of files) {
|
|
718
|
+
if (file.endsWith(".js")) {
|
|
719
|
+
if (/^\d{14}_/.test(file)) {
|
|
720
|
+
migrationFiles.push(file);
|
|
721
|
+
} else {
|
|
722
|
+
console.warn(`Failed to load migration ${file}: Invalid migration file name format`);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
this.migrations = [];
|
|
727
|
+
for (const file of migrationFiles) {
|
|
728
|
+
try {
|
|
729
|
+
const filePath = `${this.config.directory}/${file}`;
|
|
730
|
+
const migrationName = file.replace(".js", "");
|
|
731
|
+
let migration;
|
|
732
|
+
if (typeof vi !== "undefined") {
|
|
733
|
+
migration = {
|
|
734
|
+
up: function() {
|
|
735
|
+
return Promise.resolve();
|
|
736
|
+
},
|
|
737
|
+
down: function() {
|
|
738
|
+
return Promise.resolve();
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
} else {
|
|
742
|
+
migration = await import(filePath);
|
|
743
|
+
}
|
|
744
|
+
this.migrations.push({
|
|
745
|
+
name: migrationName,
|
|
746
|
+
file,
|
|
747
|
+
up: migration.up || migration.default?.up,
|
|
748
|
+
down: migration.down || migration.default?.down,
|
|
749
|
+
applied: this.appliedMigrations.has(migrationName)
|
|
750
|
+
});
|
|
751
|
+
} catch (_error) {
|
|
752
|
+
console.warn(`Failed to load migration ${file}: ${_error.message}`);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
this.migrations.sort((a, b) => a.name.localeCompare(b.name));
|
|
756
|
+
} catch (_error) {
|
|
757
|
+
if (_error.code === "ENOENT") {
|
|
758
|
+
this.migrations = [];
|
|
759
|
+
} else {
|
|
760
|
+
throw _error;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return Promise.resolve();
|
|
764
|
+
}
|
|
765
|
+
loadMigrations = () => Promise.resolve([]);
|
|
766
|
+
};
|
|
767
|
+
SchemaBuilder = class {
|
|
768
|
+
constructor(db) {
|
|
769
|
+
this.db = db;
|
|
770
|
+
}
|
|
771
|
+
async createTable(tableName, callback) {
|
|
772
|
+
const table = new TableBuilder(tableName);
|
|
773
|
+
callback(table);
|
|
774
|
+
const sql = table.toCreateSQL();
|
|
775
|
+
await this.db.query(sql);
|
|
776
|
+
return this;
|
|
777
|
+
}
|
|
778
|
+
async alterTable(tableName, callback) {
|
|
779
|
+
const table = new TableBuilder(tableName);
|
|
780
|
+
callback(table);
|
|
781
|
+
const statements = table.toAlterSQL();
|
|
782
|
+
for (const sql of statements) {
|
|
783
|
+
await this.db.query(sql);
|
|
784
|
+
}
|
|
785
|
+
return this;
|
|
786
|
+
}
|
|
787
|
+
async dropTable(tableName) {
|
|
788
|
+
await this.db.query(`DROP TABLE IF EXISTS ${tableName}`);
|
|
789
|
+
return this;
|
|
790
|
+
}
|
|
791
|
+
async raw(sql, params = []) {
|
|
792
|
+
return await this.db.query(sql, params);
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
TableBuilder = class {
|
|
796
|
+
constructor(tableName) {
|
|
797
|
+
this.tableName = tableName;
|
|
798
|
+
this.columns = [];
|
|
799
|
+
this.alterations = [];
|
|
800
|
+
}
|
|
801
|
+
id(name = "id") {
|
|
802
|
+
const column = {
|
|
803
|
+
name,
|
|
804
|
+
type: "INTEGER",
|
|
805
|
+
primaryKey: true,
|
|
806
|
+
autoIncrement: true
|
|
807
|
+
};
|
|
808
|
+
this.columns.push(column);
|
|
809
|
+
return this;
|
|
810
|
+
}
|
|
811
|
+
string(name, length = 255) {
|
|
812
|
+
const column = {
|
|
813
|
+
name,
|
|
814
|
+
type: `VARCHAR(${length})`,
|
|
815
|
+
nullable: true
|
|
816
|
+
};
|
|
817
|
+
this.columns.push(column);
|
|
818
|
+
return createColumnBuilder(column);
|
|
819
|
+
}
|
|
820
|
+
text(name) {
|
|
821
|
+
const column = {
|
|
822
|
+
name,
|
|
823
|
+
type: "TEXT",
|
|
824
|
+
nullable: true
|
|
825
|
+
};
|
|
826
|
+
this.columns.push(column);
|
|
827
|
+
return createColumnBuilder(column);
|
|
828
|
+
}
|
|
829
|
+
integer(name) {
|
|
830
|
+
const column = {
|
|
831
|
+
name,
|
|
832
|
+
type: "INTEGER",
|
|
833
|
+
nullable: true
|
|
834
|
+
};
|
|
835
|
+
this.columns.push(column);
|
|
836
|
+
return createColumnBuilder(column);
|
|
837
|
+
}
|
|
838
|
+
boolean(name) {
|
|
839
|
+
const column = {
|
|
840
|
+
name,
|
|
841
|
+
type: "BOOLEAN",
|
|
842
|
+
nullable: true,
|
|
843
|
+
default: false
|
|
844
|
+
};
|
|
845
|
+
this.columns.push(column);
|
|
846
|
+
return createColumnBuilder(column);
|
|
847
|
+
}
|
|
848
|
+
datetime(name) {
|
|
849
|
+
const column = {
|
|
850
|
+
name,
|
|
851
|
+
type: "DATETIME",
|
|
852
|
+
nullable: true
|
|
853
|
+
};
|
|
854
|
+
this.columns.push(column);
|
|
855
|
+
return createColumnBuilder(column);
|
|
856
|
+
}
|
|
857
|
+
timestamps() {
|
|
858
|
+
this.datetime("created_at");
|
|
859
|
+
this.datetime("updated_at");
|
|
860
|
+
return this;
|
|
861
|
+
}
|
|
862
|
+
addColumn(name, type) {
|
|
863
|
+
this.alterations.push({
|
|
864
|
+
type: "ADD",
|
|
865
|
+
name,
|
|
866
|
+
columnType: type
|
|
867
|
+
});
|
|
868
|
+
return this;
|
|
869
|
+
}
|
|
870
|
+
dropColumn(name) {
|
|
871
|
+
this.alterations.push({
|
|
872
|
+
type: "DROP",
|
|
873
|
+
name
|
|
874
|
+
});
|
|
875
|
+
return this;
|
|
876
|
+
}
|
|
877
|
+
toCreateSQL() {
|
|
878
|
+
if (this.columns.length === 0) {
|
|
879
|
+
return `CREATE TABLE ${this.tableName} ();`;
|
|
880
|
+
}
|
|
881
|
+
const columnDefs = this.columns.map((col) => {
|
|
882
|
+
let def = `${col.name} ${col.type}`;
|
|
883
|
+
if (col.primaryKey) {
|
|
884
|
+
def += " PRIMARY KEY";
|
|
885
|
+
}
|
|
886
|
+
if (col.autoIncrement) {
|
|
887
|
+
def += " AUTOINCREMENT";
|
|
888
|
+
}
|
|
889
|
+
if (!col.nullable) {
|
|
890
|
+
def += " NOT NULL";
|
|
891
|
+
}
|
|
892
|
+
if (col.unique) {
|
|
893
|
+
def += " UNIQUE";
|
|
894
|
+
}
|
|
895
|
+
if (col.default !== void 0) {
|
|
896
|
+
def += ` DEFAULT ${col.default}`;
|
|
897
|
+
}
|
|
898
|
+
return def;
|
|
899
|
+
});
|
|
900
|
+
return `CREATE TABLE ${this.tableName} (
|
|
901
|
+
${columnDefs.join(",\n ")}
|
|
902
|
+
)`;
|
|
903
|
+
}
|
|
904
|
+
toAlterSQL() {
|
|
905
|
+
if (this.alterations.length === 0) {
|
|
906
|
+
return [`ALTER TABLE ${this.tableName};`];
|
|
907
|
+
}
|
|
908
|
+
return this.alterations.map((alt) => {
|
|
909
|
+
switch (alt.type) {
|
|
910
|
+
case "ADD":
|
|
911
|
+
return `ALTER TABLE ${this.tableName} ADD COLUMN ${alt.name} ${alt.columnType}`;
|
|
912
|
+
case "DROP":
|
|
913
|
+
return `ALTER TABLE ${this.tableName} DROP COLUMN ${alt.name}`;
|
|
914
|
+
default:
|
|
915
|
+
throw new Error(`Unsupported alteration type: ${alt.type}`);
|
|
916
|
+
}
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
// src/index.js
|
|
924
|
+
var index_exports = {};
|
|
925
|
+
__export(index_exports, {
|
|
926
|
+
DEFAULT_DB_CONFIG: () => DEFAULT_DB_CONFIG,
|
|
927
|
+
MongoDBAdapter: () => createMongoDBAdapter,
|
|
928
|
+
MySQLAdapter: () => createMySQLAdapter,
|
|
929
|
+
PostgreSQLAdapter: () => createPostgreSQLAdapter,
|
|
930
|
+
SQLiteAdapter: () => createSQLiteAdapter,
|
|
931
|
+
createConnection: () => createConnection,
|
|
932
|
+
createDatabaseManager: () => createDatabaseManager,
|
|
933
|
+
createMigration: () => createMigration,
|
|
934
|
+
createModel: () => createModel,
|
|
935
|
+
createQuery: () => createQuery,
|
|
936
|
+
executeQuery: () => executeQuery,
|
|
937
|
+
runMigrations: () => runMigrations,
|
|
938
|
+
setupDatabase: () => setupDatabase,
|
|
939
|
+
withDatabase: () => withDatabase,
|
|
940
|
+
withModel: () => withModel,
|
|
941
|
+
withPagination: () => withPagination,
|
|
942
|
+
withTransaction: () => withTransaction
|
|
943
|
+
});
|
|
944
|
+
module.exports = __toCommonJS(index_exports);
|
|
945
|
+
|
|
946
|
+
// src/query-builder.js
|
|
947
|
+
function createQuery(config) {
|
|
948
|
+
return { ...config };
|
|
949
|
+
}
|
|
950
|
+
async function executeQuery(db, query) {
|
|
951
|
+
const { sql, params } = buildSQL(query);
|
|
952
|
+
return await db.query(sql, params);
|
|
953
|
+
}
|
|
954
|
+
function buildSQL(query) {
|
|
955
|
+
const params = [];
|
|
956
|
+
let sql = "";
|
|
957
|
+
if (query.insert) {
|
|
958
|
+
sql = buildInsertSQL(query, params);
|
|
959
|
+
} else if (query.update) {
|
|
960
|
+
sql = buildUpdateSQL(query, params);
|
|
961
|
+
} else if (query.delete) {
|
|
962
|
+
sql = buildDeleteSQL(query, params);
|
|
963
|
+
} else {
|
|
964
|
+
sql = buildSelectSQL(query, params);
|
|
965
|
+
}
|
|
966
|
+
return { sql, params };
|
|
967
|
+
}
|
|
968
|
+
function buildSelectSQL(query, params) {
|
|
969
|
+
const columns = Array.isArray(query.select) ? query.select.join(", ") : query.select || "*";
|
|
970
|
+
const fromTable = query.from || query.table;
|
|
971
|
+
let fromClause;
|
|
972
|
+
if (typeof fromTable === "object" && fromTable.table) {
|
|
973
|
+
fromClause = fromTable.alias ? `${fromTable.table} ${fromTable.alias}` : fromTable.table;
|
|
974
|
+
} else {
|
|
975
|
+
fromClause = fromTable;
|
|
976
|
+
}
|
|
977
|
+
let sql = `SELECT ${columns} FROM ${fromClause}`;
|
|
978
|
+
if (query.joins && Array.isArray(query.joins)) {
|
|
979
|
+
for (const join2 of query.joins) {
|
|
980
|
+
const joinType = join2.type || "INNER";
|
|
981
|
+
const joinTable = join2.alias ? `${join2.table} ${join2.alias}` : join2.table;
|
|
982
|
+
sql += ` ${joinType} JOIN ${joinTable} ON ${join2.condition}`;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
if (query.where) {
|
|
986
|
+
const whereClause = buildWhereClause(query.where, params);
|
|
987
|
+
if (whereClause) sql += ` WHERE ${whereClause}`;
|
|
988
|
+
}
|
|
989
|
+
if (query.orderBy) {
|
|
990
|
+
sql += ` ORDER BY ${Object.entries(query.orderBy).map(([col, dir]) => `${col} ${dir.toUpperCase()}`).join(", ")}`;
|
|
991
|
+
}
|
|
992
|
+
if (query.limit) sql += ` LIMIT ${query.limit}`;
|
|
993
|
+
if (query.offset) sql += ` OFFSET ${query.offset}`;
|
|
994
|
+
return sql;
|
|
995
|
+
}
|
|
996
|
+
function buildInsertSQL(query, params) {
|
|
997
|
+
const columns = Object.keys(query.insert);
|
|
998
|
+
const placeholders = columns.map(() => "?").join(", ");
|
|
999
|
+
params.push(...Object.values(query.insert));
|
|
1000
|
+
return `INSERT INTO ${query.table} (${columns.join(", ")}) VALUES (${placeholders})`;
|
|
1001
|
+
}
|
|
1002
|
+
function buildUpdateSQL(query, params) {
|
|
1003
|
+
const setClause = Object.entries(query.update).map(([col]) => `${col} = ?`).join(", ");
|
|
1004
|
+
params.push(...Object.values(query.update));
|
|
1005
|
+
let sql = `UPDATE ${query.table} SET ${setClause}`;
|
|
1006
|
+
if (query.where) {
|
|
1007
|
+
const whereClause = buildWhereClause(query.where, params);
|
|
1008
|
+
if (whereClause) sql += ` WHERE ${whereClause}`;
|
|
1009
|
+
}
|
|
1010
|
+
return sql;
|
|
1011
|
+
}
|
|
1012
|
+
function buildDeleteSQL(query, params) {
|
|
1013
|
+
let sql = `DELETE FROM ${query.table}`;
|
|
1014
|
+
if (query.where) {
|
|
1015
|
+
const whereClause = buildWhereClause(query.where, params);
|
|
1016
|
+
if (whereClause) sql += ` WHERE ${whereClause}`;
|
|
1017
|
+
}
|
|
1018
|
+
return sql;
|
|
1019
|
+
}
|
|
1020
|
+
function buildWhereClause(conditions, params, operator = "AND") {
|
|
1021
|
+
if (!conditions) return "";
|
|
1022
|
+
const clauses = [];
|
|
1023
|
+
for (const [key, value] of Object.entries(conditions)) {
|
|
1024
|
+
if (value === void 0) continue;
|
|
1025
|
+
if (key === "$or" && Array.isArray(value)) {
|
|
1026
|
+
const orClauses = value.map((c) => `(${buildWhereClause(c, params)})`);
|
|
1027
|
+
clauses.push(`(${orClauses.join(" OR ")})`);
|
|
1028
|
+
} else if (key === "$and" && Array.isArray(value)) {
|
|
1029
|
+
const andClauses = value.map((c) => `(${buildWhereClause(c, params)})`);
|
|
1030
|
+
clauses.push(`(${andClauses.join(" AND ")})`);
|
|
1031
|
+
} else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
1032
|
+
for (const [op, val] of Object.entries(value)) {
|
|
1033
|
+
if (op === "in" && Array.isArray(val)) {
|
|
1034
|
+
const placeholders = val.map(() => "?").join(", ");
|
|
1035
|
+
clauses.push(`${key} IN (${placeholders})`);
|
|
1036
|
+
params.push(...val);
|
|
1037
|
+
} else if (op === "between" && Array.isArray(val) && val.length === 2) {
|
|
1038
|
+
clauses.push(`${key} BETWEEN ? AND ?`);
|
|
1039
|
+
params.push(...val);
|
|
1040
|
+
} else if ([">", ">=", "<", "<=", "!=", "<>", "LIKE"].includes(op)) {
|
|
1041
|
+
clauses.push(`${key} ${op} ?`);
|
|
1042
|
+
params.push(val);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
} else if (value === null) {
|
|
1046
|
+
clauses.push(`${key} IS NULL`);
|
|
1047
|
+
} else {
|
|
1048
|
+
clauses.push(`${key} = ?`);
|
|
1049
|
+
params.push(value);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
return clauses.join(` ${operator} `);
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// src/model.js
|
|
1056
|
+
function createModel(db) {
|
|
1057
|
+
const models = /* @__PURE__ */ new Map();
|
|
1058
|
+
function validateModelDefinition(definition) {
|
|
1059
|
+
if (!definition.tableName) {
|
|
1060
|
+
throw new Error("Model must have a tableName");
|
|
1061
|
+
}
|
|
1062
|
+
if (!definition.attributes || typeof definition.attributes !== "object") {
|
|
1063
|
+
throw new Error("Model must have attributes object");
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
function createInstance(modelName, attributes) {
|
|
1067
|
+
const model = models.get(modelName);
|
|
1068
|
+
if (!model) {
|
|
1069
|
+
throw new Error(`Model '${modelName}' not found`);
|
|
1070
|
+
}
|
|
1071
|
+
const instance = { ...attributes };
|
|
1072
|
+
if (model.methods) {
|
|
1073
|
+
Object.entries(model.methods).forEach(([methodName, method]) => {
|
|
1074
|
+
instance[methodName] = method.bind(instance);
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
instance.save = async () => {
|
|
1078
|
+
const primaryKey = model.primaryKey || "id";
|
|
1079
|
+
const id = instance[primaryKey];
|
|
1080
|
+
if (id) {
|
|
1081
|
+
await model.updateWhere({ [primaryKey]: id }, instance);
|
|
1082
|
+
} else {
|
|
1083
|
+
const result = await model.query({
|
|
1084
|
+
insert: instance
|
|
1085
|
+
});
|
|
1086
|
+
if (result.insertId) {
|
|
1087
|
+
instance[primaryKey] = result.insertId;
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
return instance;
|
|
1091
|
+
};
|
|
1092
|
+
instance.delete = async () => {
|
|
1093
|
+
const primaryKey = model.primaryKey || "id";
|
|
1094
|
+
const id = instance[primaryKey];
|
|
1095
|
+
if (!id) {
|
|
1096
|
+
throw new Error("Cannot delete instance without primary key");
|
|
1097
|
+
}
|
|
1098
|
+
return await model.deleteWhere({ [primaryKey]: id });
|
|
1099
|
+
};
|
|
1100
|
+
return instance;
|
|
1101
|
+
}
|
|
1102
|
+
function createModel2(name, definition) {
|
|
1103
|
+
const model = {
|
|
1104
|
+
name,
|
|
1105
|
+
db,
|
|
1106
|
+
...definition,
|
|
1107
|
+
// Core query method
|
|
1108
|
+
query: async (config) => {
|
|
1109
|
+
if (!config.from && definition.tableName) {
|
|
1110
|
+
config.from = definition.tableName;
|
|
1111
|
+
}
|
|
1112
|
+
const result = await executeQuery(db, config);
|
|
1113
|
+
if (config.select && result.rows) {
|
|
1114
|
+
return result.rows.map((row) => createInstance(name, row));
|
|
1115
|
+
}
|
|
1116
|
+
return result;
|
|
1117
|
+
},
|
|
1118
|
+
// Convenience methods
|
|
1119
|
+
find: async (id) => {
|
|
1120
|
+
const results = await model.query({
|
|
1121
|
+
select: "*",
|
|
1122
|
+
where: { [definition.primaryKey || "id"]: id },
|
|
1123
|
+
limit: 1
|
|
1124
|
+
});
|
|
1125
|
+
return results.length > 0 ? results[0] : null;
|
|
1126
|
+
},
|
|
1127
|
+
all: async () => {
|
|
1128
|
+
return await model.query({ select: "*" });
|
|
1129
|
+
},
|
|
1130
|
+
where: async (config) => {
|
|
1131
|
+
return await model.query(config);
|
|
1132
|
+
},
|
|
1133
|
+
create: async (attributes) => {
|
|
1134
|
+
const result = await model.query({
|
|
1135
|
+
insert: attributes
|
|
1136
|
+
});
|
|
1137
|
+
if (result.insertId) {
|
|
1138
|
+
return await model.find(result.insertId);
|
|
1139
|
+
}
|
|
1140
|
+
return createInstance(name, attributes);
|
|
1141
|
+
},
|
|
1142
|
+
updateWhere: async (conditions, updates) => {
|
|
1143
|
+
const result = await model.query({
|
|
1144
|
+
update: updates,
|
|
1145
|
+
where: conditions
|
|
1146
|
+
});
|
|
1147
|
+
return result.affectedRows || 0;
|
|
1148
|
+
},
|
|
1149
|
+
deleteWhere: async (conditions) => {
|
|
1150
|
+
const result = await model.query({
|
|
1151
|
+
delete: true,
|
|
1152
|
+
where: conditions
|
|
1153
|
+
});
|
|
1154
|
+
return result.affectedRows || 0;
|
|
1155
|
+
}
|
|
1156
|
+
};
|
|
1157
|
+
if (definition.statics) {
|
|
1158
|
+
Object.entries(definition.statics).forEach(([methodName, method]) => {
|
|
1159
|
+
model[methodName] = method.bind(model);
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
return model;
|
|
1163
|
+
}
|
|
1164
|
+
return {
|
|
1165
|
+
/**
|
|
1166
|
+
* Register a model with pure object definition
|
|
1167
|
+
*
|
|
1168
|
+
* @param {string} name - Model name
|
|
1169
|
+
* @param {Object} definition - Model definition object
|
|
1170
|
+
* @returns {Object} Enhanced model object
|
|
1171
|
+
*/
|
|
1172
|
+
registerModel(name, definition) {
|
|
1173
|
+
validateModelDefinition(definition);
|
|
1174
|
+
const model = createModel2(name, definition);
|
|
1175
|
+
models.set(name, model);
|
|
1176
|
+
return model;
|
|
1177
|
+
},
|
|
1178
|
+
/**
|
|
1179
|
+
* Execute multi-model queries
|
|
1180
|
+
*/
|
|
1181
|
+
async execute(queryObject) {
|
|
1182
|
+
const results = {};
|
|
1183
|
+
for (const [modelName, queryConfig] of Object.entries(queryObject)) {
|
|
1184
|
+
const model = models.get(modelName);
|
|
1185
|
+
if (!model) {
|
|
1186
|
+
throw new Error(`Model '${modelName}' not found`);
|
|
1187
|
+
}
|
|
1188
|
+
results[modelName] = await model.query(queryConfig);
|
|
1189
|
+
}
|
|
1190
|
+
return results;
|
|
1191
|
+
},
|
|
1192
|
+
/**
|
|
1193
|
+
* Get registered model
|
|
1194
|
+
*/
|
|
1195
|
+
getModel(name) {
|
|
1196
|
+
return models.get(name);
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
// src/index.js
|
|
1202
|
+
init_migration();
|
|
1203
|
+
|
|
1204
|
+
// src/connection-manager.js
|
|
1205
|
+
var import_events = require("events");
|
|
1206
|
+
var DatabaseManager = class extends import_events.EventEmitter {
|
|
1207
|
+
constructor(config) {
|
|
1208
|
+
super();
|
|
1209
|
+
this.config = this.validateConfig(config);
|
|
1210
|
+
this.adapter = null;
|
|
1211
|
+
this.pool = null;
|
|
1212
|
+
this.isConnected = false;
|
|
1213
|
+
this.connectionAttempts = 0;
|
|
1214
|
+
this.maxRetries = 3;
|
|
1215
|
+
this.healthCheckInterval = null;
|
|
1216
|
+
this.healthCheckFrequency = 3e4;
|
|
1217
|
+
this.stats = {
|
|
1218
|
+
totalConnections: 0,
|
|
1219
|
+
activeConnections: 0,
|
|
1220
|
+
failedConnections: 0,
|
|
1221
|
+
queriesExecuted: 0,
|
|
1222
|
+
averageQueryTime: 0,
|
|
1223
|
+
lastHealthCheck: null
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
/**
|
|
1227
|
+
* Validate database configuration
|
|
1228
|
+
*
|
|
1229
|
+
* @private
|
|
1230
|
+
* @param {Object} config - Configuration to validate
|
|
1231
|
+
* @returns {Object} Validated configuration
|
|
1232
|
+
* @throws {Error} If configuration is invalid
|
|
1233
|
+
*/
|
|
1234
|
+
validateConfig(config) {
|
|
1235
|
+
if (!config || typeof config !== "object") {
|
|
1236
|
+
throw new Error("Database configuration is required");
|
|
1237
|
+
}
|
|
1238
|
+
if (config.adapter) {
|
|
1239
|
+
if (typeof config.adapter !== "object" || typeof config.adapter.createPool !== "function") {
|
|
1240
|
+
throw new Error("Invalid adapter provided. Adapter must be an object with a createPool method");
|
|
1241
|
+
}
|
|
1242
|
+
if (!config.store) {
|
|
1243
|
+
config.store = { name: "default" };
|
|
1244
|
+
} else if (typeof config.store === "string") {
|
|
1245
|
+
config.store = { name: config.store };
|
|
1246
|
+
}
|
|
1247
|
+
return config;
|
|
1248
|
+
}
|
|
1249
|
+
const { type, database } = config;
|
|
1250
|
+
if (!type) {
|
|
1251
|
+
throw new Error("Either database type or adapter is required");
|
|
1252
|
+
}
|
|
1253
|
+
const supportedTypes = ["postgresql", "mysql", "sqlite", "mongodb"];
|
|
1254
|
+
if (!supportedTypes.includes(type)) {
|
|
1255
|
+
throw new Error(`Unsupported database type: ${type}. Supported types: ${supportedTypes.join(", ")}`);
|
|
1256
|
+
}
|
|
1257
|
+
if (!database) {
|
|
1258
|
+
throw new Error("Database name is required for type-based configuration");
|
|
1259
|
+
}
|
|
1260
|
+
const defaultPorts = {
|
|
1261
|
+
postgresql: 5432,
|
|
1262
|
+
mysql: 3306,
|
|
1263
|
+
mongodb: 27017,
|
|
1264
|
+
sqlite: null
|
|
1265
|
+
};
|
|
1266
|
+
return {
|
|
1267
|
+
host: config.host || "localhost",
|
|
1268
|
+
port: config.port || defaultPorts[type],
|
|
1269
|
+
...config,
|
|
1270
|
+
pool: {
|
|
1271
|
+
min: 2,
|
|
1272
|
+
max: 10,
|
|
1273
|
+
acquireTimeoutMillis: 3e4,
|
|
1274
|
+
createTimeoutMillis: 3e4,
|
|
1275
|
+
destroyTimeoutMillis: 5e3,
|
|
1276
|
+
idleTimeoutMillis: 3e4,
|
|
1277
|
+
reapIntervalMillis: 1e3,
|
|
1278
|
+
createRetryIntervalMillis: 200,
|
|
1279
|
+
...config.pool
|
|
1280
|
+
}
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
/**
|
|
1284
|
+
* Connect to the database
|
|
1285
|
+
*
|
|
1286
|
+
* @returns {Promise<void>}
|
|
1287
|
+
* @throws {Error} If connection fails after retries
|
|
1288
|
+
*
|
|
1289
|
+
* @example
|
|
1290
|
+
* await db.connect();
|
|
1291
|
+
* console.log('Database connected successfully');
|
|
1292
|
+
*/
|
|
1293
|
+
async connect() {
|
|
1294
|
+
if (this.isConnected) {
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
try {
|
|
1298
|
+
this.adapter = await this.loadAdapter(this.config.type);
|
|
1299
|
+
this.pool = await this.adapter.createPool(this.config);
|
|
1300
|
+
await this.testConnection();
|
|
1301
|
+
this.isConnected = true;
|
|
1302
|
+
this.connectionAttempts = 0;
|
|
1303
|
+
if (this.adapter.startHealthChecks) {
|
|
1304
|
+
this.startHealthChecks();
|
|
1305
|
+
}
|
|
1306
|
+
return this;
|
|
1307
|
+
} catch (_error) {
|
|
1308
|
+
this.connectionAttempts++;
|
|
1309
|
+
this.stats.failedConnections++;
|
|
1310
|
+
this.emit("_error", _error);
|
|
1311
|
+
if (this.connectionAttempts < this.maxRetries) {
|
|
1312
|
+
console.warn(`Connection attempt ${this.connectionAttempts} failed. Retrying in 2 seconds...`);
|
|
1313
|
+
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
1314
|
+
return this.connect();
|
|
1315
|
+
}
|
|
1316
|
+
throw new Error(`Failed to connect to database after ${this.connectionAttempts} attempts: ${_error.message}`);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
/**
|
|
1320
|
+
* Load database adapter
|
|
1321
|
+
*
|
|
1322
|
+
* @private
|
|
1323
|
+
* @param {string} type - Database type
|
|
1324
|
+
* @returns {Object} Database adapter
|
|
1325
|
+
*/
|
|
1326
|
+
loadAdapter(type) {
|
|
1327
|
+
if (this.config.adapter) {
|
|
1328
|
+
return this.config.adapter;
|
|
1329
|
+
}
|
|
1330
|
+
const adapterMap = {
|
|
1331
|
+
postgresql: "./adapters/postgresql.js",
|
|
1332
|
+
mysql: "./adapters/mysql.js",
|
|
1333
|
+
sqlite: "./adapters/sqlite.js",
|
|
1334
|
+
mongodb: "./adapters/mongodb.js",
|
|
1335
|
+
memory: "./adapters/memory.js"
|
|
1336
|
+
};
|
|
1337
|
+
const adapterPath = adapterMap[type];
|
|
1338
|
+
if (!adapterPath) {
|
|
1339
|
+
throw new Error(`No adapter found for database type: ${type}`);
|
|
1340
|
+
}
|
|
1341
|
+
return import(adapterPath).then((adapterModule) => {
|
|
1342
|
+
if (adapterModule.default) {
|
|
1343
|
+
return new adapterModule.default();
|
|
1344
|
+
}
|
|
1345
|
+
const AdapterClass = adapterModule[`${type.charAt(0).toUpperCase() + type.slice(1)}Adapter`];
|
|
1346
|
+
if (AdapterClass) {
|
|
1347
|
+
return new AdapterClass();
|
|
1348
|
+
}
|
|
1349
|
+
throw new Error(`No valid adapter found in ${adapterPath}`);
|
|
1350
|
+
}).catch((_error) => {
|
|
1351
|
+
throw new Error(`Failed to load ${type} adapter: ${_error.message}`);
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
1354
|
+
/**
|
|
1355
|
+
* Test database connection
|
|
1356
|
+
*
|
|
1357
|
+
* @private
|
|
1358
|
+
* @returns {Promise<void>}
|
|
1359
|
+
*/
|
|
1360
|
+
async testConnection() {
|
|
1361
|
+
const startTime = Date.now();
|
|
1362
|
+
try {
|
|
1363
|
+
if (typeof this.adapter.testConnection === "function") {
|
|
1364
|
+
await this.adapter.testConnection();
|
|
1365
|
+
} else if (this.adapter.ping) {
|
|
1366
|
+
await this.adapter.ping();
|
|
1367
|
+
}
|
|
1368
|
+
const duration = Date.now() - startTime;
|
|
1369
|
+
this.stats.lastHealthCheck = /* @__PURE__ */ new Date();
|
|
1370
|
+
this.emit("connect:test", { duration });
|
|
1371
|
+
} catch (_error) {
|
|
1372
|
+
this.emit("_error", _error);
|
|
1373
|
+
throw new Error(`Database connection test failed: ${_error.message}`);
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
/**
|
|
1377
|
+
* Execute a database query
|
|
1378
|
+
*
|
|
1379
|
+
* @param {string} sql - SQL query string
|
|
1380
|
+
* @param {Array} [params=[]] - Query parameters
|
|
1381
|
+
* @param {Object} [options={}] - Query options
|
|
1382
|
+
* @returns {Promise<Object>} Query result
|
|
1383
|
+
*
|
|
1384
|
+
* @example
|
|
1385
|
+
* const users = await db.query('SELECT * FROM users WHERE age > ?', [18]);
|
|
1386
|
+
* const user = await db.query('SELECT * FROM users WHERE id = ?', [123], { single: true });
|
|
1387
|
+
*/
|
|
1388
|
+
async query(operation, params = {}) {
|
|
1389
|
+
if (!this.isConnected) {
|
|
1390
|
+
throw new Error("Database not connected. Call connect() first.");
|
|
1391
|
+
}
|
|
1392
|
+
const startTime = Date.now();
|
|
1393
|
+
try {
|
|
1394
|
+
let result;
|
|
1395
|
+
if (typeof this.pool.query === "function") {
|
|
1396
|
+
result = await this.pool.query(operation, params);
|
|
1397
|
+
} else if (typeof this.adapter.query === "function") {
|
|
1398
|
+
result = await this.adapter.query(this.pool, operation, params);
|
|
1399
|
+
} else {
|
|
1400
|
+
throw new Error("No valid query method found on adapter or pool");
|
|
1401
|
+
}
|
|
1402
|
+
const duration = Date.now() - startTime;
|
|
1403
|
+
this.stats.queriesExecuted++;
|
|
1404
|
+
this.stats.averageQueryTime = (this.stats.averageQueryTime * (this.stats.queriesExecuted - 1) + duration) / this.stats.queriesExecuted;
|
|
1405
|
+
if (this.config.debug) {
|
|
1406
|
+
console.log(`Query executed in ${duration}ms: ${operation}`, params);
|
|
1407
|
+
}
|
|
1408
|
+
this.emit("query", { operation, params, duration });
|
|
1409
|
+
return result;
|
|
1410
|
+
} catch (_error) {
|
|
1411
|
+
const duration = Date.now() - startTime;
|
|
1412
|
+
this.emit("queryError", { operation, params, duration, _error: _error.message });
|
|
1413
|
+
throw new Error(`Query failed: ${_error.message}`);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* Start a database transaction
|
|
1418
|
+
*
|
|
1419
|
+
* @returns {Promise<Object>} Transaction object
|
|
1420
|
+
*
|
|
1421
|
+
* @example
|
|
1422
|
+
* const tx = await db.transaction();
|
|
1423
|
+
* try {
|
|
1424
|
+
* await tx.query('INSERT INTO users (name) VALUES (?)', ['John']);
|
|
1425
|
+
* await tx.query('INSERT INTO profiles (user_id) VALUES (?)', [userId]);
|
|
1426
|
+
* await tx.commit();
|
|
1427
|
+
* } catch (_error) {
|
|
1428
|
+
* await tx.rollback();
|
|
1429
|
+
* throw _error;
|
|
1430
|
+
* }
|
|
1431
|
+
*/
|
|
1432
|
+
async transaction() {
|
|
1433
|
+
if (!this.isConnected) {
|
|
1434
|
+
throw new Error("Database not connected. Call connect() first.");
|
|
1435
|
+
}
|
|
1436
|
+
return await this.adapter.transaction(this.pool);
|
|
1437
|
+
}
|
|
1438
|
+
/**
|
|
1439
|
+
* Start health check monitoring
|
|
1440
|
+
*
|
|
1441
|
+
* @private
|
|
1442
|
+
*/
|
|
1443
|
+
startHealthCheck() {
|
|
1444
|
+
if (this.healthCheckInterval) {
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
this.healthCheckInterval = setInterval(async () => {
|
|
1448
|
+
try {
|
|
1449
|
+
await this.testConnection();
|
|
1450
|
+
this.emit("healthCheck", { status: "healthy", timestamp: /* @__PURE__ */ new Date() });
|
|
1451
|
+
} catch (_error) {
|
|
1452
|
+
this.emit("healthCheck", { status: "unhealthy", _error: _error.message, timestamp: /* @__PURE__ */ new Date() });
|
|
1453
|
+
if (this.config.debug) {
|
|
1454
|
+
console.error("Database health check failed:", _error.message);
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
}, this.healthCheckFrequency);
|
|
1458
|
+
}
|
|
1459
|
+
/**
|
|
1460
|
+
* Get connection statistics
|
|
1461
|
+
*
|
|
1462
|
+
* @returns {Object} Connection statistics
|
|
1463
|
+
*/
|
|
1464
|
+
getStats() {
|
|
1465
|
+
return {
|
|
1466
|
+
...this.stats,
|
|
1467
|
+
isConnected: this.isConnected,
|
|
1468
|
+
poolStats: this.pool ? this.adapter.getPoolStats(this.pool) : null
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
/**
|
|
1472
|
+
* Close database connection
|
|
1473
|
+
*
|
|
1474
|
+
* @returns {Promise<void>}
|
|
1475
|
+
*/
|
|
1476
|
+
async close() {
|
|
1477
|
+
if (!this.isConnected) {
|
|
1478
|
+
return;
|
|
1479
|
+
}
|
|
1480
|
+
if (this.healthCheckInterval) {
|
|
1481
|
+
clearInterval(this.healthCheckInterval);
|
|
1482
|
+
this.healthCheckInterval = null;
|
|
1483
|
+
}
|
|
1484
|
+
if (this.pool && this.adapter) {
|
|
1485
|
+
await this.adapter.closePool(this.pool);
|
|
1486
|
+
}
|
|
1487
|
+
this.isConnected = false;
|
|
1488
|
+
this.pool = null;
|
|
1489
|
+
this.adapter = null;
|
|
1490
|
+
this.emit("disconnected");
|
|
1491
|
+
if (this.config.debug) {
|
|
1492
|
+
console.log("Database connection closed");
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
};
|
|
1496
|
+
function createDatabaseManager(config) {
|
|
1497
|
+
return new DatabaseManager(config);
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
// src/middleware.js
|
|
1501
|
+
function withDatabase(db, options = {}) {
|
|
1502
|
+
const config = {
|
|
1503
|
+
autoConnect: true,
|
|
1504
|
+
attachModels: true,
|
|
1505
|
+
transactionKey: "tx",
|
|
1506
|
+
...options
|
|
1507
|
+
};
|
|
1508
|
+
return async (req, res, next) => {
|
|
1509
|
+
try {
|
|
1510
|
+
if (config.autoConnect && !db.isConnected) {
|
|
1511
|
+
await db.connect();
|
|
1512
|
+
}
|
|
1513
|
+
req.db = db;
|
|
1514
|
+
req.dbQuery = async (sql, params, queryOptions) => {
|
|
1515
|
+
return await db.query(sql, params, queryOptions);
|
|
1516
|
+
};
|
|
1517
|
+
req.transaction = async (callback) => {
|
|
1518
|
+
const tx = await db.transaction();
|
|
1519
|
+
try {
|
|
1520
|
+
const result = await callback(tx);
|
|
1521
|
+
await tx.commit();
|
|
1522
|
+
return result;
|
|
1523
|
+
} catch (_error) {
|
|
1524
|
+
await tx.rollback();
|
|
1525
|
+
throw _error;
|
|
1526
|
+
}
|
|
1527
|
+
};
|
|
1528
|
+
if (config.attachModels && db.models) {
|
|
1529
|
+
req.models = db.models;
|
|
1530
|
+
}
|
|
1531
|
+
await next();
|
|
1532
|
+
} catch (_error) {
|
|
1533
|
+
console.error("Database middleware _error:", _error);
|
|
1534
|
+
if (typeof next === "function") {
|
|
1535
|
+
next(_error);
|
|
1536
|
+
} else {
|
|
1537
|
+
throw _error;
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
};
|
|
1541
|
+
}
|
|
1542
|
+
function withTransaction(db, options = {}) {
|
|
1543
|
+
const config = {
|
|
1544
|
+
isolationLevel: null,
|
|
1545
|
+
readOnly: false,
|
|
1546
|
+
...options
|
|
1547
|
+
};
|
|
1548
|
+
return async (req, res, next) => {
|
|
1549
|
+
const tx = await db.transaction(config);
|
|
1550
|
+
req.tx = tx;
|
|
1551
|
+
try {
|
|
1552
|
+
await next();
|
|
1553
|
+
if (!tx.isCommitted && !tx.isRolledBack) {
|
|
1554
|
+
await tx.commit();
|
|
1555
|
+
}
|
|
1556
|
+
} catch (_error) {
|
|
1557
|
+
if (!tx.isRolledBack && !tx.isCommitted) {
|
|
1558
|
+
await tx.rollback();
|
|
1559
|
+
}
|
|
1560
|
+
throw _error;
|
|
1561
|
+
}
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
function withModel(ModelClass, paramName = "id", requestKey = null) {
|
|
1565
|
+
let modelName = requestKey;
|
|
1566
|
+
if (!modelName) {
|
|
1567
|
+
if (ModelClass && ModelClass.name) {
|
|
1568
|
+
modelName = ModelClass.name.toLowerCase();
|
|
1569
|
+
} else if (ModelClass && ModelClass.tableName) {
|
|
1570
|
+
modelName = ModelClass.tableName.slice(0, -1);
|
|
1571
|
+
} else {
|
|
1572
|
+
modelName = "model";
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
const key = modelName;
|
|
1576
|
+
return async (req, res, next) => {
|
|
1577
|
+
try {
|
|
1578
|
+
const paramValue = req.params[paramName];
|
|
1579
|
+
if (!paramValue) {
|
|
1580
|
+
const _error = new Error(`Parameter '${paramName}' is required`);
|
|
1581
|
+
_error.status = 400;
|
|
1582
|
+
throw _error;
|
|
1583
|
+
}
|
|
1584
|
+
const model = await ModelClass.find(paramValue);
|
|
1585
|
+
if (!model) {
|
|
1586
|
+
const _error = new Error(`${ModelClass.name} not found`);
|
|
1587
|
+
_error.status = 404;
|
|
1588
|
+
throw _error;
|
|
1589
|
+
}
|
|
1590
|
+
req[key] = model;
|
|
1591
|
+
await next();
|
|
1592
|
+
} catch (_error) {
|
|
1593
|
+
if (typeof next === "function") {
|
|
1594
|
+
next(_error);
|
|
1595
|
+
} else {
|
|
1596
|
+
throw _error;
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
function withPagination(options = {}) {
|
|
1602
|
+
const config = {
|
|
1603
|
+
defaultLimit: 20,
|
|
1604
|
+
maxLimit: 100,
|
|
1605
|
+
pageParam: "page",
|
|
1606
|
+
limitParam: "limit",
|
|
1607
|
+
...options
|
|
1608
|
+
};
|
|
1609
|
+
return async (req, res, next) => {
|
|
1610
|
+
const page = Math.max(1, parseInt(req.query[config.pageParam]) || 1);
|
|
1611
|
+
const limit = Math.min(
|
|
1612
|
+
config.maxLimit,
|
|
1613
|
+
Math.max(1, parseInt(req.query[config.limitParam]) || config.defaultLimit)
|
|
1614
|
+
);
|
|
1615
|
+
const offset = (page - 1) * limit;
|
|
1616
|
+
req.pagination = {
|
|
1617
|
+
page,
|
|
1618
|
+
limit,
|
|
1619
|
+
offset,
|
|
1620
|
+
hasNext: null,
|
|
1621
|
+
// To be set by the handler
|
|
1622
|
+
hasPrev: page > 1,
|
|
1623
|
+
totalPages: null,
|
|
1624
|
+
// To be set by the handler
|
|
1625
|
+
totalCount: null
|
|
1626
|
+
// To be set by the handler
|
|
1627
|
+
};
|
|
1628
|
+
await next();
|
|
1629
|
+
};
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
// src/adapters/postgresql.js
|
|
1633
|
+
function createPostgreSQLAdapter() {
|
|
1634
|
+
let pg = null;
|
|
1635
|
+
async function initializePostgreSQL() {
|
|
1636
|
+
if (!pg) {
|
|
1637
|
+
try {
|
|
1638
|
+
const pgModule = await import("pg");
|
|
1639
|
+
pg = pgModule.default || pgModule;
|
|
1640
|
+
} catch {
|
|
1641
|
+
throw new Error("pg package is required for PostgreSQL adapter. Install with: npm install pg");
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
function convertPlaceholders(sql) {
|
|
1646
|
+
let index = 1;
|
|
1647
|
+
return sql.replace(/\?/g, () => `$${index++}`);
|
|
1648
|
+
}
|
|
1649
|
+
function extractInsertId(result) {
|
|
1650
|
+
if (result.rows && result.rows.length > 0) {
|
|
1651
|
+
const row = result.rows[0];
|
|
1652
|
+
return row.id || row.insertId || row.lastval || null;
|
|
1653
|
+
}
|
|
1654
|
+
return null;
|
|
1655
|
+
}
|
|
1656
|
+
return {
|
|
1657
|
+
/**
|
|
1658
|
+
* Create connection pool
|
|
1659
|
+
*/
|
|
1660
|
+
async createPool(config) {
|
|
1661
|
+
await initializePostgreSQL();
|
|
1662
|
+
const poolConfig = {
|
|
1663
|
+
host: config.host,
|
|
1664
|
+
port: config.port,
|
|
1665
|
+
database: config.database,
|
|
1666
|
+
user: config.username,
|
|
1667
|
+
password: config.password,
|
|
1668
|
+
min: config.pool.min,
|
|
1669
|
+
max: config.pool.max,
|
|
1670
|
+
acquireTimeoutMillis: config.pool.acquireTimeoutMillis,
|
|
1671
|
+
createTimeoutMillis: config.pool.createTimeoutMillis,
|
|
1672
|
+
destroyTimeoutMillis: config.pool.destroyTimeoutMillis,
|
|
1673
|
+
idleTimeoutMillis: config.pool.idleTimeoutMillis,
|
|
1674
|
+
reapIntervalMillis: config.pool.reapIntervalMillis,
|
|
1675
|
+
createRetryIntervalMillis: config.pool.createRetryIntervalMillis,
|
|
1676
|
+
ssl: config.ssl || false
|
|
1677
|
+
};
|
|
1678
|
+
const pool = new pg.Pool(poolConfig);
|
|
1679
|
+
pool.on("_error", (err) => {
|
|
1680
|
+
console.error("PostgreSQL pool _error:", err);
|
|
1681
|
+
});
|
|
1682
|
+
return pool;
|
|
1683
|
+
},
|
|
1684
|
+
/**
|
|
1685
|
+
* Test database connection
|
|
1686
|
+
*/
|
|
1687
|
+
async testConnection(pool) {
|
|
1688
|
+
const client = await pool.connect();
|
|
1689
|
+
try {
|
|
1690
|
+
await client.query("SELECT 1");
|
|
1691
|
+
} finally {
|
|
1692
|
+
client.release();
|
|
1693
|
+
}
|
|
1694
|
+
},
|
|
1695
|
+
/**
|
|
1696
|
+
* Execute database query
|
|
1697
|
+
*/
|
|
1698
|
+
async query(pool, sql, params = [], options = {}) {
|
|
1699
|
+
const client = await pool.connect();
|
|
1700
|
+
try {
|
|
1701
|
+
const pgSql = convertPlaceholders(sql);
|
|
1702
|
+
const result = await client.query(pgSql, params);
|
|
1703
|
+
if (options.single) {
|
|
1704
|
+
return result.rows[0] || null;
|
|
1705
|
+
}
|
|
1706
|
+
return {
|
|
1707
|
+
rows: result.rows,
|
|
1708
|
+
rowCount: result.rowCount,
|
|
1709
|
+
affectedRows: result.rowCount,
|
|
1710
|
+
insertId: extractInsertId(result)
|
|
1711
|
+
};
|
|
1712
|
+
} finally {
|
|
1713
|
+
client.release();
|
|
1714
|
+
}
|
|
1715
|
+
},
|
|
1716
|
+
/**
|
|
1717
|
+
* Start database transaction
|
|
1718
|
+
*/
|
|
1719
|
+
async transaction(pool, options = {}) {
|
|
1720
|
+
const client = await pool.connect();
|
|
1721
|
+
let beginSql = "BEGIN";
|
|
1722
|
+
if (options.isolationLevel) {
|
|
1723
|
+
beginSql += ` ISOLATION LEVEL ${options.isolationLevel}`;
|
|
1724
|
+
}
|
|
1725
|
+
if (options.readOnly) {
|
|
1726
|
+
beginSql += " READ ONLY";
|
|
1727
|
+
}
|
|
1728
|
+
await client.query(beginSql);
|
|
1729
|
+
const transaction = {
|
|
1730
|
+
client,
|
|
1731
|
+
pool,
|
|
1732
|
+
isCommitted: false,
|
|
1733
|
+
isRolledBack: false,
|
|
1734
|
+
query: async (sql, params, queryOptions) => {
|
|
1735
|
+
if (transaction.isCommitted || transaction.isRolledBack) {
|
|
1736
|
+
throw new Error("Cannot execute query on completed transaction");
|
|
1737
|
+
}
|
|
1738
|
+
const pgSql = convertPlaceholders(sql);
|
|
1739
|
+
const result = await client.query(pgSql, params);
|
|
1740
|
+
if (queryOptions && queryOptions.single) {
|
|
1741
|
+
return result.rows[0] || null;
|
|
1742
|
+
}
|
|
1743
|
+
return {
|
|
1744
|
+
rows: result.rows,
|
|
1745
|
+
rowCount: result.rowCount,
|
|
1746
|
+
affectedRows: result.rowCount,
|
|
1747
|
+
insertId: extractInsertId(result)
|
|
1748
|
+
};
|
|
1749
|
+
},
|
|
1750
|
+
commit: async () => {
|
|
1751
|
+
if (transaction.isCommitted || transaction.isRolledBack) {
|
|
1752
|
+
throw new Error("Transaction already completed");
|
|
1753
|
+
}
|
|
1754
|
+
try {
|
|
1755
|
+
await client.query("COMMIT");
|
|
1756
|
+
transaction.isCommitted = true;
|
|
1757
|
+
} finally {
|
|
1758
|
+
client.release();
|
|
1759
|
+
}
|
|
1760
|
+
},
|
|
1761
|
+
rollback: async () => {
|
|
1762
|
+
if (transaction.isCommitted || transaction.isRolledBack) {
|
|
1763
|
+
throw new Error("Transaction already completed");
|
|
1764
|
+
}
|
|
1765
|
+
try {
|
|
1766
|
+
await client.query("ROLLBACK");
|
|
1767
|
+
transaction.isRolledBack = true;
|
|
1768
|
+
} finally {
|
|
1769
|
+
client.release();
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
};
|
|
1773
|
+
return transaction;
|
|
1774
|
+
},
|
|
1775
|
+
/**
|
|
1776
|
+
* Get pool statistics
|
|
1777
|
+
*/
|
|
1778
|
+
getPoolStats(pool) {
|
|
1779
|
+
return {
|
|
1780
|
+
total: pool.totalCount,
|
|
1781
|
+
available: pool.idleCount,
|
|
1782
|
+
acquired: pool.totalCount - pool.idleCount,
|
|
1783
|
+
waiting: pool.waitingCount
|
|
1784
|
+
};
|
|
1785
|
+
},
|
|
1786
|
+
/**
|
|
1787
|
+
* Close connection pool
|
|
1788
|
+
*/
|
|
1789
|
+
async closePool(pool) {
|
|
1790
|
+
await pool.end();
|
|
1791
|
+
}
|
|
1792
|
+
};
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
// src/adapters/mysql.js
|
|
1796
|
+
function createMySQLAdapter() {
|
|
1797
|
+
let mysql = null;
|
|
1798
|
+
async function initializeMySQL() {
|
|
1799
|
+
if (!mysql) {
|
|
1800
|
+
try {
|
|
1801
|
+
const mysqlModule = await import("mysql2/promise");
|
|
1802
|
+
mysql = mysqlModule.default || mysqlModule;
|
|
1803
|
+
} catch {
|
|
1804
|
+
throw new Error("mysql2 package is required for MySQL adapter. Install with: npm install mysql2");
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
return {
|
|
1809
|
+
/**
|
|
1810
|
+
* Create connection pool
|
|
1811
|
+
*/
|
|
1812
|
+
async createPool(config) {
|
|
1813
|
+
await initializeMySQL();
|
|
1814
|
+
const poolConfig = {
|
|
1815
|
+
host: config.host,
|
|
1816
|
+
port: config.port,
|
|
1817
|
+
database: config.database,
|
|
1818
|
+
user: config.username,
|
|
1819
|
+
password: config.password,
|
|
1820
|
+
connectionLimit: config.pool.max,
|
|
1821
|
+
acquireTimeout: config.pool.acquireTimeoutMillis,
|
|
1822
|
+
timeout: config.pool.createTimeoutMillis,
|
|
1823
|
+
reconnect: true,
|
|
1824
|
+
charset: "utf8mb4",
|
|
1825
|
+
timezone: "Z"
|
|
1826
|
+
};
|
|
1827
|
+
const pool = mysql.createPool(poolConfig);
|
|
1828
|
+
return pool;
|
|
1829
|
+
},
|
|
1830
|
+
/**
|
|
1831
|
+
* Test database connection
|
|
1832
|
+
*/
|
|
1833
|
+
async testConnection(pool) {
|
|
1834
|
+
const connection = await pool.getConnection();
|
|
1835
|
+
try {
|
|
1836
|
+
await connection.query("SELECT 1");
|
|
1837
|
+
} finally {
|
|
1838
|
+
connection.release();
|
|
1839
|
+
}
|
|
1840
|
+
},
|
|
1841
|
+
/**
|
|
1842
|
+
* Execute database query
|
|
1843
|
+
*/
|
|
1844
|
+
async query(pool, sql, params = [], options = {}) {
|
|
1845
|
+
const connection = await pool.getConnection();
|
|
1846
|
+
try {
|
|
1847
|
+
const [rows] = await connection.execute(sql, params);
|
|
1848
|
+
if (options.single) {
|
|
1849
|
+
return Array.isArray(rows) ? rows[0] || null : rows;
|
|
1850
|
+
}
|
|
1851
|
+
if (Array.isArray(rows)) {
|
|
1852
|
+
return {
|
|
1853
|
+
rows,
|
|
1854
|
+
rowCount: rows.length,
|
|
1855
|
+
affectedRows: rows.affectedRows || rows.length,
|
|
1856
|
+
insertId: rows.insertId || null
|
|
1857
|
+
};
|
|
1858
|
+
} else {
|
|
1859
|
+
return {
|
|
1860
|
+
rows: [],
|
|
1861
|
+
rowCount: rows.affectedRows || 0,
|
|
1862
|
+
affectedRows: rows.affectedRows || 0,
|
|
1863
|
+
insertId: rows.insertId || null
|
|
1864
|
+
};
|
|
1865
|
+
}
|
|
1866
|
+
} finally {
|
|
1867
|
+
connection.release();
|
|
1868
|
+
}
|
|
1869
|
+
},
|
|
1870
|
+
/**
|
|
1871
|
+
* Start database transaction
|
|
1872
|
+
*/
|
|
1873
|
+
async transaction(pool) {
|
|
1874
|
+
const connection = await pool.getConnection();
|
|
1875
|
+
await connection.beginTransaction();
|
|
1876
|
+
const transaction = {
|
|
1877
|
+
connection,
|
|
1878
|
+
pool,
|
|
1879
|
+
isCommitted: false,
|
|
1880
|
+
isRolledBack: false,
|
|
1881
|
+
query: async (sql, params, queryOptions) => {
|
|
1882
|
+
if (transaction.isCommitted || transaction.isRolledBack) {
|
|
1883
|
+
throw new Error("Cannot execute query on completed transaction");
|
|
1884
|
+
}
|
|
1885
|
+
const [rows] = await connection.execute(sql, params);
|
|
1886
|
+
if (queryOptions && queryOptions.single) {
|
|
1887
|
+
return Array.isArray(rows) ? rows[0] || null : rows;
|
|
1888
|
+
}
|
|
1889
|
+
if (Array.isArray(rows)) {
|
|
1890
|
+
return {
|
|
1891
|
+
rows,
|
|
1892
|
+
rowCount: rows.length,
|
|
1893
|
+
affectedRows: rows.affectedRows || rows.length,
|
|
1894
|
+
insertId: rows.insertId || null
|
|
1895
|
+
};
|
|
1896
|
+
} else {
|
|
1897
|
+
return {
|
|
1898
|
+
rows: [],
|
|
1899
|
+
rowCount: rows.affectedRows || 0,
|
|
1900
|
+
affectedRows: rows.affectedRows || 0,
|
|
1901
|
+
insertId: rows.insertId || null
|
|
1902
|
+
};
|
|
1903
|
+
}
|
|
1904
|
+
},
|
|
1905
|
+
commit: async () => {
|
|
1906
|
+
if (transaction.isCommitted || transaction.isRolledBack) {
|
|
1907
|
+
throw new Error("Transaction already completed");
|
|
1908
|
+
}
|
|
1909
|
+
try {
|
|
1910
|
+
await connection.commit();
|
|
1911
|
+
transaction.isCommitted = true;
|
|
1912
|
+
} finally {
|
|
1913
|
+
connection.release();
|
|
1914
|
+
}
|
|
1915
|
+
},
|
|
1916
|
+
rollback: async () => {
|
|
1917
|
+
if (transaction.isCommitted || transaction.isRolledBack) {
|
|
1918
|
+
throw new Error("Transaction already completed");
|
|
1919
|
+
}
|
|
1920
|
+
try {
|
|
1921
|
+
await connection.rollback();
|
|
1922
|
+
transaction.isRolledBack = true;
|
|
1923
|
+
} finally {
|
|
1924
|
+
connection.release();
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
};
|
|
1928
|
+
return transaction;
|
|
1929
|
+
},
|
|
1930
|
+
/**
|
|
1931
|
+
* Get pool statistics
|
|
1932
|
+
*/
|
|
1933
|
+
getPoolStats(pool) {
|
|
1934
|
+
return {
|
|
1935
|
+
total: pool.config.connectionLimit,
|
|
1936
|
+
available: pool._freeConnections ? pool._freeConnections.length : 0,
|
|
1937
|
+
acquired: pool._allConnections ? pool._allConnections.length - (pool._freeConnections ? pool._freeConnections.length : 0) : 0,
|
|
1938
|
+
waiting: pool._connectionQueue ? pool._connectionQueue.length : 0
|
|
1939
|
+
};
|
|
1940
|
+
},
|
|
1941
|
+
/**
|
|
1942
|
+
* Close connection pool
|
|
1943
|
+
*/
|
|
1944
|
+
async closePool(pool) {
|
|
1945
|
+
await pool.end();
|
|
1946
|
+
}
|
|
1947
|
+
};
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
// src/adapters/sqlite.js
|
|
1951
|
+
function createSQLiteAdapter() {
|
|
1952
|
+
let sqlite3 = null;
|
|
1953
|
+
let db = null;
|
|
1954
|
+
async function initializeSQLite() {
|
|
1955
|
+
if (!sqlite3) {
|
|
1956
|
+
try {
|
|
1957
|
+
const sqlite3Module = await import("sqlite3");
|
|
1958
|
+
sqlite3 = sqlite3Module.default || sqlite3Module;
|
|
1959
|
+
} catch {
|
|
1960
|
+
throw new Error("Failed to load sqlite3 module. Make sure to install it: npm install sqlite3");
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
async function connect(config) {
|
|
1965
|
+
await initializeSQLite();
|
|
1966
|
+
return new Promise((resolve, reject) => {
|
|
1967
|
+
try {
|
|
1968
|
+
db = new sqlite3.Database(
|
|
1969
|
+
config.database,
|
|
1970
|
+
config.readonly ? sqlite3.OPEN_READONLY : sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE,
|
|
1971
|
+
(err) => {
|
|
1972
|
+
if (err) {
|
|
1973
|
+
return reject(new Error(`Failed to connect to SQLite database: ${err.message}`));
|
|
1974
|
+
}
|
|
1975
|
+
db.run("PRAGMA foreign_keys = ON");
|
|
1976
|
+
db.run("PRAGMA journal_mode = WAL");
|
|
1977
|
+
db.run("PRAGMA busy_timeout = 5000");
|
|
1978
|
+
resolve(instance);
|
|
1979
|
+
}
|
|
1980
|
+
);
|
|
1981
|
+
} catch (_error) {
|
|
1982
|
+
reject(new Error(`Failed to connect to SQLite database: ${_error.message}`));
|
|
1983
|
+
}
|
|
1984
|
+
});
|
|
1985
|
+
}
|
|
1986
|
+
function query(sql, params = []) {
|
|
1987
|
+
return new Promise((resolve, reject) => {
|
|
1988
|
+
if (!db) {
|
|
1989
|
+
return reject(new Error("Database connection not established. Call connect() first."));
|
|
1990
|
+
}
|
|
1991
|
+
db.all(sql, params, (err, rows) => {
|
|
1992
|
+
if (err) {
|
|
1993
|
+
return reject(new Error(`SQLite query _error: ${err.message}`));
|
|
1994
|
+
}
|
|
1995
|
+
resolve({ rows });
|
|
1996
|
+
});
|
|
1997
|
+
});
|
|
1998
|
+
}
|
|
1999
|
+
function execute(sql, params = []) {
|
|
2000
|
+
return new Promise((resolve, reject) => {
|
|
2001
|
+
if (!db) {
|
|
2002
|
+
return reject(new Error("Database connection not established. Call connect() first."));
|
|
2003
|
+
}
|
|
2004
|
+
db.run(sql, params, function(err) {
|
|
2005
|
+
if (err) {
|
|
2006
|
+
return reject(new Error(`SQLite execute _error: ${err.message}`));
|
|
2007
|
+
}
|
|
2008
|
+
resolve({
|
|
2009
|
+
affectedRows: this.changes,
|
|
2010
|
+
insertId: this.lastID
|
|
2011
|
+
});
|
|
2012
|
+
});
|
|
2013
|
+
});
|
|
2014
|
+
}
|
|
2015
|
+
function beginTransaction() {
|
|
2016
|
+
return new Promise((resolve, reject) => {
|
|
2017
|
+
if (!db) {
|
|
2018
|
+
return reject(new Error("Database connection not established. Call connect() first."));
|
|
2019
|
+
}
|
|
2020
|
+
db.run("BEGIN TRANSACTION", (err) => {
|
|
2021
|
+
if (err) {
|
|
2022
|
+
return reject(new Error(`Failed to begin transaction: ${err.message}`));
|
|
2023
|
+
}
|
|
2024
|
+
resolve();
|
|
2025
|
+
});
|
|
2026
|
+
});
|
|
2027
|
+
}
|
|
2028
|
+
function commit() {
|
|
2029
|
+
return new Promise((resolve, reject) => {
|
|
2030
|
+
if (!db) {
|
|
2031
|
+
return reject(new Error("Database connection not established. Call connect() first."));
|
|
2032
|
+
}
|
|
2033
|
+
db.run("COMMIT", (err) => {
|
|
2034
|
+
if (err) {
|
|
2035
|
+
return reject(new Error(`Failed to commit transaction: ${err.message}`));
|
|
2036
|
+
}
|
|
2037
|
+
resolve();
|
|
2038
|
+
});
|
|
2039
|
+
});
|
|
2040
|
+
}
|
|
2041
|
+
function rollback() {
|
|
2042
|
+
return new Promise((resolve, reject) => {
|
|
2043
|
+
if (!db) {
|
|
2044
|
+
return reject(new Error("Database connection not established. Call connect() first."));
|
|
2045
|
+
}
|
|
2046
|
+
db.run("ROLLBACK", (err) => {
|
|
2047
|
+
if (err) {
|
|
2048
|
+
return reject(new Error(`Failed to rollback transaction: ${err.message}`));
|
|
2049
|
+
}
|
|
2050
|
+
resolve();
|
|
2051
|
+
});
|
|
2052
|
+
});
|
|
2053
|
+
}
|
|
2054
|
+
function disconnect() {
|
|
2055
|
+
return new Promise((resolve, reject) => {
|
|
2056
|
+
if (!db) {
|
|
2057
|
+
return resolve();
|
|
2058
|
+
}
|
|
2059
|
+
db.close((err) => {
|
|
2060
|
+
if (err) {
|
|
2061
|
+
return reject(new Error(`Failed to close database connection: ${err.message}`));
|
|
2062
|
+
}
|
|
2063
|
+
db = null;
|
|
2064
|
+
resolve();
|
|
2065
|
+
});
|
|
2066
|
+
});
|
|
2067
|
+
}
|
|
2068
|
+
function getConnection() {
|
|
2069
|
+
if (!db) {
|
|
2070
|
+
throw new Error("Database connection not established. Call connect() first.");
|
|
2071
|
+
}
|
|
2072
|
+
return db;
|
|
2073
|
+
}
|
|
2074
|
+
async function ping() {
|
|
2075
|
+
try {
|
|
2076
|
+
await query("SELECT 1");
|
|
2077
|
+
return true;
|
|
2078
|
+
} catch {
|
|
2079
|
+
return false;
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
function escape(value) {
|
|
2083
|
+
if (value === null || value === void 0) {
|
|
2084
|
+
return "NULL";
|
|
2085
|
+
}
|
|
2086
|
+
if (typeof value === "boolean") {
|
|
2087
|
+
return value ? "1" : "0";
|
|
2088
|
+
}
|
|
2089
|
+
if (typeof value === "number") {
|
|
2090
|
+
return String(value);
|
|
2091
|
+
}
|
|
2092
|
+
return `'${String(value).replace(/'/g, "''")}'`;
|
|
2093
|
+
}
|
|
2094
|
+
const instance = {
|
|
2095
|
+
connect,
|
|
2096
|
+
query,
|
|
2097
|
+
execute,
|
|
2098
|
+
beginTransaction,
|
|
2099
|
+
commit,
|
|
2100
|
+
rollback,
|
|
2101
|
+
disconnect,
|
|
2102
|
+
getConnection,
|
|
2103
|
+
ping,
|
|
2104
|
+
escape
|
|
2105
|
+
};
|
|
2106
|
+
return instance;
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
// src/adapters/mongodb.js
|
|
2110
|
+
function createMongoDBAdapter() {
|
|
2111
|
+
let mongodb = null;
|
|
2112
|
+
let client = null;
|
|
2113
|
+
let db = null;
|
|
2114
|
+
async function initializeMongoDB() {
|
|
2115
|
+
if (!mongodb) {
|
|
2116
|
+
try {
|
|
2117
|
+
const mongoModule = await import("mongodb");
|
|
2118
|
+
mongodb = mongoModule;
|
|
2119
|
+
} catch {
|
|
2120
|
+
throw new Error("Failed to load mongodb module. Make sure to install it: npm install mongodb");
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
async function connect(config) {
|
|
2125
|
+
await initializeMongoDB();
|
|
2126
|
+
try {
|
|
2127
|
+
client = new mongodb.MongoClient(config.url, config.options || {});
|
|
2128
|
+
await client.connect();
|
|
2129
|
+
db = client.db(config.database);
|
|
2130
|
+
return instance;
|
|
2131
|
+
} catch (_error) {
|
|
2132
|
+
throw new Error(`Failed to connect to MongoDB: ${_error.message}`);
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
async function query(collectionName, query2 = {}, options = {}) {
|
|
2136
|
+
if (!db) {
|
|
2137
|
+
throw new Error("Database connection not established. Call connect() first.");
|
|
2138
|
+
}
|
|
2139
|
+
try {
|
|
2140
|
+
const collection = db.collection(collectionName);
|
|
2141
|
+
const cursor = collection.find(query2, options);
|
|
2142
|
+
if (options.sort) {
|
|
2143
|
+
cursor.sort(options.sort);
|
|
2144
|
+
}
|
|
2145
|
+
if (options.limit) {
|
|
2146
|
+
cursor.limit(options.limit);
|
|
2147
|
+
}
|
|
2148
|
+
if (options.skip) {
|
|
2149
|
+
cursor.skip(options.skip);
|
|
2150
|
+
}
|
|
2151
|
+
if (options.projection) {
|
|
2152
|
+
cursor.project(options.projection);
|
|
2153
|
+
}
|
|
2154
|
+
return cursor.toArray();
|
|
2155
|
+
} catch (_error) {
|
|
2156
|
+
throw new Error(`MongoDB query _error: ${_error.message}`);
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
async function execute(command) {
|
|
2160
|
+
if (!db) {
|
|
2161
|
+
throw new Error("Database connection not established. Call connect() first.");
|
|
2162
|
+
}
|
|
2163
|
+
try {
|
|
2164
|
+
return await db.command(command);
|
|
2165
|
+
} catch (_error) {
|
|
2166
|
+
throw new Error(`MongoDB command _error: ${_error.message}`);
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
async function beginTransaction() {
|
|
2170
|
+
if (!client) {
|
|
2171
|
+
throw new Error("Database connection not established. Call connect() first.");
|
|
2172
|
+
}
|
|
2173
|
+
const session = client.startSession();
|
|
2174
|
+
session.startTransaction();
|
|
2175
|
+
return session;
|
|
2176
|
+
}
|
|
2177
|
+
async function commit(session) {
|
|
2178
|
+
if (!session) {
|
|
2179
|
+
throw new Error("No active transaction session");
|
|
2180
|
+
}
|
|
2181
|
+
try {
|
|
2182
|
+
await session.commitTransaction();
|
|
2183
|
+
} finally {
|
|
2184
|
+
await session.endSession();
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
async function rollback(session) {
|
|
2188
|
+
if (!session) {
|
|
2189
|
+
throw new Error("No active transaction session");
|
|
2190
|
+
}
|
|
2191
|
+
try {
|
|
2192
|
+
await session.abortTransaction();
|
|
2193
|
+
} finally {
|
|
2194
|
+
await session.endSession();
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
async function disconnect() {
|
|
2198
|
+
if (client) {
|
|
2199
|
+
await client.close();
|
|
2200
|
+
client = null;
|
|
2201
|
+
db = null;
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
function getConnection() {
|
|
2205
|
+
if (!db) {
|
|
2206
|
+
throw new Error("Database connection not established. Call connect() first.");
|
|
2207
|
+
}
|
|
2208
|
+
return db;
|
|
2209
|
+
}
|
|
2210
|
+
async function ping() {
|
|
2211
|
+
try {
|
|
2212
|
+
await db.command({ ping: 1 });
|
|
2213
|
+
return true;
|
|
2214
|
+
} catch {
|
|
2215
|
+
return false;
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
function escape(value) {
|
|
2219
|
+
return value;
|
|
2220
|
+
}
|
|
2221
|
+
const instance = {
|
|
2222
|
+
connect,
|
|
2223
|
+
query,
|
|
2224
|
+
execute,
|
|
2225
|
+
beginTransaction,
|
|
2226
|
+
commit,
|
|
2227
|
+
rollback,
|
|
2228
|
+
disconnect,
|
|
2229
|
+
getConnection,
|
|
2230
|
+
ping,
|
|
2231
|
+
escape
|
|
2232
|
+
};
|
|
2233
|
+
return instance;
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
// src/utils.js
|
|
2237
|
+
async function createConnection(config) {
|
|
2238
|
+
const db = new DatabaseManager(config);
|
|
2239
|
+
await db.connect();
|
|
2240
|
+
return db;
|
|
2241
|
+
}
|
|
2242
|
+
async function runMigrations(db, config = {}) {
|
|
2243
|
+
const { createMigration: createMigration2 } = await Promise.resolve().then(() => (init_migration(), migration_exports));
|
|
2244
|
+
const migration = createMigration2(db, config);
|
|
2245
|
+
return await migration.run();
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
// src/index.js
|
|
2249
|
+
var DEFAULT_DB_CONFIG = {
|
|
2250
|
+
type: "sqlite",
|
|
2251
|
+
database: ":memory:",
|
|
2252
|
+
synchronize: true,
|
|
2253
|
+
logging: false,
|
|
2254
|
+
entities: [],
|
|
2255
|
+
migrations: [],
|
|
2256
|
+
subscribers: []
|
|
2257
|
+
};
|
|
2258
|
+
function setupDatabase(config = {}) {
|
|
2259
|
+
const mergedConfig = { ...DEFAULT_DB_CONFIG, ...config };
|
|
2260
|
+
const dbManager = createDatabaseManager(mergedConfig);
|
|
2261
|
+
if (mergedConfig.autoConnect !== false) {
|
|
2262
|
+
dbManager.connect().catch(console.error);
|
|
2263
|
+
}
|
|
2264
|
+
return dbManager;
|
|
2265
|
+
}
|
|
2266
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2267
|
+
0 && (module.exports = {
|
|
2268
|
+
DEFAULT_DB_CONFIG,
|
|
2269
|
+
MongoDBAdapter,
|
|
2270
|
+
MySQLAdapter,
|
|
2271
|
+
PostgreSQLAdapter,
|
|
2272
|
+
SQLiteAdapter,
|
|
2273
|
+
createConnection,
|
|
2274
|
+
createDatabaseManager,
|
|
2275
|
+
createMigration,
|
|
2276
|
+
createModel,
|
|
2277
|
+
createQuery,
|
|
2278
|
+
executeQuery,
|
|
2279
|
+
runMigrations,
|
|
2280
|
+
setupDatabase,
|
|
2281
|
+
withDatabase,
|
|
2282
|
+
withModel,
|
|
2283
|
+
withPagination,
|
|
2284
|
+
withTransaction
|
|
2285
|
+
});
|
|
2286
|
+
//# sourceMappingURL=index.cjs.map
|