@engjts/nexus 0.1.8 → 0.1.10
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/dist/advanced/playground/playground.js.map +1 -1
- package/dist/advanced/static/generateDirectoryListing.d.ts +1 -1
- package/dist/advanced/static/generateDirectoryListing.d.ts.map +1 -1
- package/dist/advanced/static/generateDirectoryListing.js +12 -6
- package/dist/advanced/static/generateDirectoryListing.js.map +1 -1
- package/dist/advanced/static/index.d.ts +2 -0
- package/dist/advanced/static/index.d.ts.map +1 -1
- package/dist/advanced/static/index.js +4 -1
- package/dist/advanced/static/index.js.map +1 -1
- package/dist/advanced/static/serveStatic.d.ts.map +1 -1
- package/dist/advanced/static/serveStatic.js +7 -1
- package/dist/advanced/static/serveStatic.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/BENCHMARK_REPORT.md +0 -343
- package/documentation/01-getting-started.md +0 -240
- package/documentation/02-context.md +0 -335
- package/documentation/03-routing.md +0 -397
- package/documentation/04-middleware.md +0 -483
- package/documentation/05-validation.md +0 -514
- package/documentation/06-error-handling.md +0 -465
- package/documentation/07-performance.md +0 -364
- package/documentation/08-adapters.md +0 -470
- package/documentation/09-api-reference.md +0 -548
- package/documentation/10-examples.md +0 -582
- package/documentation/11-deployment.md +0 -477
- package/documentation/12-sentry.md +0 -620
- package/documentation/13-sentry-data-storage.md +0 -996
- package/documentation/14-sentry-data-reference.md +0 -457
- package/documentation/15-sentry-summary.md +0 -409
- package/documentation/16-alerts-system.md +0 -745
- package/documentation/17-alert-adapters.md +0 -696
- package/documentation/18-alerts-implementation-summary.md +0 -385
- package/documentation/19-class-based-routing.md +0 -840
- package/documentation/20-websocket-realtime.md +0 -813
- package/documentation/21-cache-system.md +0 -510
- package/documentation/22-job-queue.md +0 -772
- package/documentation/23-sentry-plugin.md +0 -551
- package/documentation/24-testing-utilities.md +0 -1287
- package/documentation/25-api-versioning.md +0 -533
- package/documentation/26-context-store.md +0 -607
- package/documentation/27-dependency-injection.md +0 -329
- package/documentation/28-lifecycle-hooks.md +0 -521
- package/documentation/29-package-structure.md +0 -196
- package/documentation/30-plugin-system.md +0 -414
- package/documentation/31-jwt-authentication.md +0 -597
- package/documentation/32-cli.md +0 -268
- package/documentation/ALERTS-COMPLETE-SUMMARY.md +0 -429
- package/documentation/ALERTS-INDEX.md +0 -330
- package/documentation/ALERTS-QUICK-REFERENCE.md +0 -286
- package/documentation/README.md +0 -178
- package/documentation/index.html +0 -34
- package/modern_framework_paper.md +0 -1870
- package/public/css/style.css +0 -87
- package/public/index.html +0 -34
- package/public/js/app.js +0 -27
- package/src/advanced/cache/InMemoryCacheStore.ts +0 -68
- package/src/advanced/cache/MultiTierCache.ts +0 -194
- package/src/advanced/cache/RedisCacheStore.ts +0 -341
- package/src/advanced/cache/index.ts +0 -5
- package/src/advanced/cache/types.ts +0 -40
- package/src/advanced/graphql/SimpleDataLoader.ts +0 -42
- package/src/advanced/graphql/index.ts +0 -22
- package/src/advanced/graphql/server.ts +0 -252
- package/src/advanced/graphql/types.ts +0 -42
- package/src/advanced/jobs/InMemoryQueueStore.ts +0 -68
- package/src/advanced/jobs/JobQueue.ts +0 -556
- package/src/advanced/jobs/RedisQueueStore.ts +0 -367
- package/src/advanced/jobs/index.ts +0 -5
- package/src/advanced/jobs/types.ts +0 -70
- package/src/advanced/observability/APMManager.ts +0 -163
- package/src/advanced/observability/AlertManager.ts +0 -109
- package/src/advanced/observability/MetricRegistry.ts +0 -151
- package/src/advanced/observability/ObservabilityCenter.ts +0 -304
- package/src/advanced/observability/StructuredLogger.ts +0 -154
- package/src/advanced/observability/TracingManager.ts +0 -117
- package/src/advanced/observability/adapters.ts +0 -304
- package/src/advanced/observability/createObservabilityMiddleware.ts +0 -63
- package/src/advanced/observability/index.ts +0 -11
- package/src/advanced/observability/types.ts +0 -174
- package/src/advanced/playground/extractPathParams.ts +0 -6
- package/src/advanced/playground/generateFieldExample.ts +0 -31
- package/src/advanced/playground/generatePlaygroundHTML.ts +0 -1956
- package/src/advanced/playground/generateSummary.ts +0 -19
- package/src/advanced/playground/getTagFromPath.ts +0 -9
- package/src/advanced/playground/index.ts +0 -8
- package/src/advanced/playground/playground.ts +0 -250
- package/src/advanced/playground/types.ts +0 -49
- package/src/advanced/playground/zodToExample.ts +0 -16
- package/src/advanced/playground/zodToParams.ts +0 -15
- package/src/advanced/postman/buildAuth.ts +0 -31
- package/src/advanced/postman/buildBody.ts +0 -15
- package/src/advanced/postman/buildQueryParams.ts +0 -27
- package/src/advanced/postman/buildRequestItem.ts +0 -36
- package/src/advanced/postman/buildResponses.ts +0 -11
- package/src/advanced/postman/buildUrl.ts +0 -33
- package/src/advanced/postman/capitalize.ts +0 -4
- package/src/advanced/postman/generateCollection.ts +0 -59
- package/src/advanced/postman/generateEnvironment.ts +0 -34
- package/src/advanced/postman/generateExampleFromZod.ts +0 -21
- package/src/advanced/postman/generateFieldExample.ts +0 -45
- package/src/advanced/postman/generateName.ts +0 -20
- package/src/advanced/postman/generateUUID.ts +0 -11
- package/src/advanced/postman/getTagFromPath.ts +0 -10
- package/src/advanced/postman/index.ts +0 -28
- package/src/advanced/postman/postman.ts +0 -156
- package/src/advanced/postman/slugify.ts +0 -7
- package/src/advanced/postman/types.ts +0 -140
- package/src/advanced/realtime/index.ts +0 -18
- package/src/advanced/realtime/websocket.ts +0 -231
- package/src/advanced/sentry/index.ts +0 -1236
- package/src/advanced/sentry/types.ts +0 -355
- package/src/advanced/static/generateDirectoryListing.ts +0 -47
- package/src/advanced/static/generateETag.ts +0 -7
- package/src/advanced/static/getMimeType.ts +0 -9
- package/src/advanced/static/index.ts +0 -32
- package/src/advanced/static/isSafePath.ts +0 -13
- package/src/advanced/static/publicDir.ts +0 -21
- package/src/advanced/static/serveStatic.ts +0 -225
- package/src/advanced/static/spa.ts +0 -24
- package/src/advanced/static/types.ts +0 -159
- package/src/advanced/swagger/SwaggerGenerator.ts +0 -66
- package/src/advanced/swagger/buildOperation.ts +0 -61
- package/src/advanced/swagger/buildParameters.ts +0 -61
- package/src/advanced/swagger/buildRequestBody.ts +0 -21
- package/src/advanced/swagger/buildResponses.ts +0 -54
- package/src/advanced/swagger/capitalize.ts +0 -5
- package/src/advanced/swagger/convertPath.ts +0 -9
- package/src/advanced/swagger/createSwagger.ts +0 -12
- package/src/advanced/swagger/generateOperationId.ts +0 -21
- package/src/advanced/swagger/generateSpec.ts +0 -105
- package/src/advanced/swagger/generateSummary.ts +0 -24
- package/src/advanced/swagger/generateSwaggerUI.ts +0 -70
- package/src/advanced/swagger/generateThemeCss.ts +0 -53
- package/src/advanced/swagger/index.ts +0 -25
- package/src/advanced/swagger/swagger.ts +0 -237
- package/src/advanced/swagger/types.ts +0 -206
- package/src/advanced/swagger/zodFieldToOpenAPI.ts +0 -94
- package/src/advanced/swagger/zodSchemaToOpenAPI.ts +0 -50
- package/src/advanced/swagger/zodToOpenAPI.ts +0 -22
- package/src/advanced/testing/factory.ts +0 -509
- package/src/advanced/testing/harness.ts +0 -612
- package/src/advanced/testing/index.ts +0 -430
- package/src/advanced/testing/load-test.ts +0 -618
- package/src/advanced/testing/mock-server.ts +0 -498
- package/src/advanced/testing/mock.ts +0 -670
- package/src/cli/bin.ts +0 -9
- package/src/cli/cli.ts +0 -158
- package/src/cli/commands/add.ts +0 -178
- package/src/cli/commands/build.ts +0 -73
- package/src/cli/commands/create.ts +0 -166
- package/src/cli/commands/dev.ts +0 -85
- package/src/cli/commands/generate.ts +0 -99
- package/src/cli/commands/help.ts +0 -95
- package/src/cli/commands/init.ts +0 -91
- package/src/cli/commands/version.ts +0 -38
- package/src/cli/index.ts +0 -6
- package/src/cli/templates/generators.ts +0 -359
- package/src/cli/templates/index.ts +0 -680
- package/src/cli/utils/exec.ts +0 -52
- package/src/cli/utils/file-system.ts +0 -78
- package/src/cli/utils/logger.ts +0 -111
- package/src/core/adapter.ts +0 -88
- package/src/core/application.ts +0 -1453
- package/src/core/context-pool.ts +0 -79
- package/src/core/context.ts +0 -856
- package/src/core/index.ts +0 -94
- package/src/core/middleware.ts +0 -272
- package/src/core/performance/buffer-pool.ts +0 -108
- package/src/core/performance/middleware-optimizer.ts +0 -162
- package/src/core/plugin/PluginManager.ts +0 -435
- package/src/core/plugin/builder.ts +0 -358
- package/src/core/plugin/index.ts +0 -50
- package/src/core/plugin/types.ts +0 -214
- package/src/core/router/file-router.ts +0 -623
- package/src/core/router/index.ts +0 -260
- package/src/core/router/radix-tree.ts +0 -242
- package/src/core/serializer.ts +0 -397
- package/src/core/store/index.ts +0 -30
- package/src/core/store/registry.ts +0 -178
- package/src/core/store/request-store.ts +0 -240
- package/src/core/store/types.ts +0 -233
- package/src/core/types.ts +0 -616
- package/src/database/adapter.ts +0 -35
- package/src/database/adapters/index.ts +0 -1
- package/src/database/adapters/mysql.ts +0 -669
- package/src/database/database.ts +0 -70
- package/src/database/dialect.ts +0 -388
- package/src/database/index.ts +0 -12
- package/src/database/migrations.ts +0 -86
- package/src/database/optimizer.ts +0 -125
- package/src/database/query-builder.ts +0 -404
- package/src/database/realtime.ts +0 -53
- package/src/database/schema.ts +0 -71
- package/src/database/transactions.ts +0 -56
- package/src/database/types.ts +0 -87
- package/src/deployment/cluster.ts +0 -471
- package/src/deployment/config.ts +0 -454
- package/src/deployment/docker.ts +0 -599
- package/src/deployment/graceful-shutdown.ts +0 -373
- package/src/deployment/index.ts +0 -56
- package/src/index.ts +0 -281
- package/src/security/adapter.ts +0 -318
- package/src/security/auth/JWTPlugin.ts +0 -234
- package/src/security/auth/JWTProvider.ts +0 -316
- package/src/security/auth/adapter.ts +0 -12
- package/src/security/auth/jwt.ts +0 -234
- package/src/security/auth/middleware.ts +0 -188
- package/src/security/csrf.ts +0 -220
- package/src/security/headers.ts +0 -108
- package/src/security/index.ts +0 -60
- package/src/security/rate-limit/adapter.ts +0 -7
- package/src/security/rate-limit/memory.ts +0 -108
- package/src/security/rate-limit/middleware.ts +0 -181
- package/src/security/sanitization.ts +0 -75
- package/src/security/types.ts +0 -240
- package/src/security/utils.ts +0 -52
- package/tsconfig.json +0 -39
|
@@ -1,669 +0,0 @@
|
|
|
1
|
-
import { EventEmitter } from 'events';
|
|
2
|
-
import { DatabaseAdapter, MigrationAdapter, QueryResult } from '../adapter';
|
|
3
|
-
import { QueryContext } from '../types';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* MySQL connection configuration
|
|
7
|
-
*/
|
|
8
|
-
export interface MySQLConnectionConfig {
|
|
9
|
-
host: string;
|
|
10
|
-
port?: number;
|
|
11
|
-
user: string;
|
|
12
|
-
password: string;
|
|
13
|
-
database: string;
|
|
14
|
-
charset?: string;
|
|
15
|
-
timezone?: string;
|
|
16
|
-
connectTimeout?: number;
|
|
17
|
-
ssl?: {
|
|
18
|
-
ca?: string;
|
|
19
|
-
cert?: string;
|
|
20
|
-
key?: string;
|
|
21
|
-
rejectUnauthorized?: boolean;
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* MySQL pool configuration
|
|
27
|
-
*/
|
|
28
|
-
export interface MySQLPoolConfig extends MySQLConnectionConfig {
|
|
29
|
-
pool?: {
|
|
30
|
-
min?: number;
|
|
31
|
-
max?: number;
|
|
32
|
-
idleTimeout?: number;
|
|
33
|
-
acquireTimeout?: number;
|
|
34
|
-
createTimeout?: number;
|
|
35
|
-
destroyTimeout?: number;
|
|
36
|
-
reapInterval?: number;
|
|
37
|
-
createRetryInterval?: number;
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* MySQL replica configuration for read scaling
|
|
43
|
-
*/
|
|
44
|
-
export interface MySQLReplicaConfig {
|
|
45
|
-
primary: MySQLPoolConfig;
|
|
46
|
-
replicas?: MySQLPoolConfig[];
|
|
47
|
-
readPreference?: 'primary' | 'replica' | 'nearest';
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Internal connection interface (abstracted from mysql2)
|
|
52
|
-
*/
|
|
53
|
-
interface MySQLConnection {
|
|
54
|
-
query(sql: string, params?: unknown[]): Promise<[unknown[], unknown]>;
|
|
55
|
-
execute(sql: string, params?: unknown[]): Promise<[unknown[], unknown]>;
|
|
56
|
-
beginTransaction(): Promise<void>;
|
|
57
|
-
commit(): Promise<void>;
|
|
58
|
-
rollback(): Promise<void>;
|
|
59
|
-
release(): void;
|
|
60
|
-
end(): Promise<void>;
|
|
61
|
-
ping(): Promise<void>;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Internal pool interface (abstracted from mysql2)
|
|
66
|
-
*/
|
|
67
|
-
interface MySQLPool {
|
|
68
|
-
getConnection(): Promise<MySQLConnection>;
|
|
69
|
-
query(sql: string, params?: unknown[]): Promise<[unknown[], unknown]>;
|
|
70
|
-
execute(sql: string, params?: unknown[]): Promise<[unknown[], unknown]>;
|
|
71
|
-
end(): Promise<void>;
|
|
72
|
-
on(event: string, listener: (...args: unknown[]) => void): void;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* MySQL2 library interface for dynamic import
|
|
77
|
-
*/
|
|
78
|
-
interface MySQL2Library {
|
|
79
|
-
createPool(config: Record<string, unknown>): MySQLPool;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Pool statistics for monitoring
|
|
84
|
-
*/
|
|
85
|
-
export interface MySQLPoolStats {
|
|
86
|
-
totalConnections: number;
|
|
87
|
-
idleConnections: number;
|
|
88
|
-
pendingRequests: number;
|
|
89
|
-
maxConnections: number;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* MySQL Adapter implementing DatabaseAdapter and MigrationAdapter interfaces.
|
|
94
|
-
* Provides connection pooling, replica support, and MySQL-specific features.
|
|
95
|
-
*/
|
|
96
|
-
export class MySQLAdapter implements DatabaseAdapter, MigrationAdapter {
|
|
97
|
-
readonly name = 'mysql';
|
|
98
|
-
|
|
99
|
-
readonly capabilities = {
|
|
100
|
-
json: true,
|
|
101
|
-
streaming: true,
|
|
102
|
-
realtime: false, // MySQL doesn't have native LISTEN/NOTIFY like PostgreSQL
|
|
103
|
-
transactional: true
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
private pool: MySQLPool | null = null;
|
|
107
|
-
private replicaPools: MySQLPool[] = [];
|
|
108
|
-
private currentReplicaIndex = 0;
|
|
109
|
-
private mysql2: MySQL2Library | null = null;
|
|
110
|
-
private activeConnection: MySQLConnection | null = null;
|
|
111
|
-
private readonly emitter = new EventEmitter();
|
|
112
|
-
private connected = false;
|
|
113
|
-
|
|
114
|
-
constructor(private readonly config: MySQLReplicaConfig | MySQLPoolConfig) {}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Establish connection to MySQL database with connection pooling
|
|
118
|
-
*/
|
|
119
|
-
async connect(): Promise<void> {
|
|
120
|
-
if (this.connected) {
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Load MySQL driver first - throw immediately if not installed
|
|
125
|
-
// Don't emit error event for missing dependency
|
|
126
|
-
this.mysql2 = await this.loadMySQLDriver();
|
|
127
|
-
|
|
128
|
-
try {
|
|
129
|
-
const primaryConfig = this.isPrimaryReplicaConfig(this.config)
|
|
130
|
-
? this.config.primary
|
|
131
|
-
: this.config;
|
|
132
|
-
|
|
133
|
-
this.pool = this.createPool(primaryConfig);
|
|
134
|
-
|
|
135
|
-
// Setup replica pools if configured
|
|
136
|
-
if (this.isPrimaryReplicaConfig(this.config) && this.config.replicas) {
|
|
137
|
-
for (const replicaConfig of this.config.replicas) {
|
|
138
|
-
const replicaPool = this.createPool(replicaConfig);
|
|
139
|
-
this.replicaPools.push(replicaPool);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Test connection
|
|
144
|
-
await this.pool.query('SELECT 1');
|
|
145
|
-
this.connected = true;
|
|
146
|
-
this.emitter.emit('connected');
|
|
147
|
-
} catch (error) {
|
|
148
|
-
this.emitter.emit('error', error);
|
|
149
|
-
throw new Error(`Failed to connect to MySQL: ${(error as Error).message}`);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Gracefully disconnect from MySQL database
|
|
155
|
-
*/
|
|
156
|
-
async disconnect(): Promise<void> {
|
|
157
|
-
if (!this.connected) {
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
try {
|
|
162
|
-
// Close replica pools
|
|
163
|
-
for (const replicaPool of this.replicaPools) {
|
|
164
|
-
await replicaPool.end();
|
|
165
|
-
}
|
|
166
|
-
this.replicaPools = [];
|
|
167
|
-
|
|
168
|
-
// Close primary pool
|
|
169
|
-
if (this.pool) {
|
|
170
|
-
await this.pool.end();
|
|
171
|
-
this.pool = null;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
this.connected = false;
|
|
175
|
-
this.emitter.emit('disconnected');
|
|
176
|
-
} catch (error) {
|
|
177
|
-
this.emitter.emit('error', error);
|
|
178
|
-
throw error;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Execute a query against the database
|
|
184
|
-
*/
|
|
185
|
-
async query<T = unknown>(
|
|
186
|
-
sql: string,
|
|
187
|
-
params?: unknown[],
|
|
188
|
-
context?: QueryContext
|
|
189
|
-
): Promise<QueryResult<T>> {
|
|
190
|
-
this.ensureConnected();
|
|
191
|
-
|
|
192
|
-
const pool = this.selectPool(sql);
|
|
193
|
-
const normalizedSql = this.normalizeQuery(sql);
|
|
194
|
-
const normalizedParams = this.normalizeParams(params);
|
|
195
|
-
|
|
196
|
-
try {
|
|
197
|
-
const startTime = Date.now();
|
|
198
|
-
const [rows, fields] = await pool.execute(normalizedSql, normalizedParams);
|
|
199
|
-
const duration = Date.now() - startTime;
|
|
200
|
-
|
|
201
|
-
this.emitter.emit('query', {
|
|
202
|
-
sql: normalizedSql,
|
|
203
|
-
params: normalizedParams,
|
|
204
|
-
duration,
|
|
205
|
-
context
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
// Handle different result types
|
|
209
|
-
if (Array.isArray(rows)) {
|
|
210
|
-
return {
|
|
211
|
-
rows: rows as T[],
|
|
212
|
-
rowCount: rows.length,
|
|
213
|
-
fields: this.extractFieldNames(fields),
|
|
214
|
-
meta: { duration }
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// For INSERT/UPDATE/DELETE, mysql2 returns ResultSetHeader
|
|
219
|
-
const resultHeader = rows as {
|
|
220
|
-
affectedRows?: number;
|
|
221
|
-
insertId?: number;
|
|
222
|
-
changedRows?: number;
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
return {
|
|
226
|
-
rows: [] as T[],
|
|
227
|
-
rowCount: resultHeader.affectedRows ?? 0,
|
|
228
|
-
meta: {
|
|
229
|
-
insertId: resultHeader.insertId,
|
|
230
|
-
affectedRows: resultHeader.affectedRows,
|
|
231
|
-
changedRows: resultHeader.changedRows,
|
|
232
|
-
duration
|
|
233
|
-
}
|
|
234
|
-
};
|
|
235
|
-
} catch (error) {
|
|
236
|
-
this.emitter.emit('error', { error, sql: normalizedSql, params: normalizedParams });
|
|
237
|
-
throw this.wrapError(error as Error, normalizedSql);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Stream query results for large datasets
|
|
243
|
-
*/
|
|
244
|
-
async *stream<T = unknown>(
|
|
245
|
-
sql: string,
|
|
246
|
-
params?: unknown[],
|
|
247
|
-
context?: QueryContext
|
|
248
|
-
): AsyncGenerator<T, void, unknown> {
|
|
249
|
-
this.ensureConnected();
|
|
250
|
-
|
|
251
|
-
const pool = this.selectPool(sql);
|
|
252
|
-
const normalizedSql = this.normalizeQuery(sql);
|
|
253
|
-
const normalizedParams = this.normalizeParams(params);
|
|
254
|
-
|
|
255
|
-
const connection = await pool.getConnection();
|
|
256
|
-
|
|
257
|
-
try {
|
|
258
|
-
this.emitter.emit('stream:start', { sql: normalizedSql, context });
|
|
259
|
-
|
|
260
|
-
// Execute query and yield rows one by one
|
|
261
|
-
const [rows] = await connection.execute(normalizedSql, normalizedParams);
|
|
262
|
-
|
|
263
|
-
if (Array.isArray(rows)) {
|
|
264
|
-
for (const row of rows) {
|
|
265
|
-
yield row as T;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
this.emitter.emit('stream:end', { sql: normalizedSql, context });
|
|
270
|
-
} catch (error) {
|
|
271
|
-
this.emitter.emit('error', { error, sql: normalizedSql, params: normalizedParams });
|
|
272
|
-
throw this.wrapError(error as Error, normalizedSql);
|
|
273
|
-
} finally {
|
|
274
|
-
connection.release();
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Begin a new transaction
|
|
280
|
-
*/
|
|
281
|
-
async beginTransaction(): Promise<void> {
|
|
282
|
-
this.ensureConnected();
|
|
283
|
-
|
|
284
|
-
if (this.activeConnection) {
|
|
285
|
-
throw new Error('Transaction already in progress');
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
this.activeConnection = await this.pool!.getConnection();
|
|
289
|
-
await this.activeConnection.beginTransaction();
|
|
290
|
-
this.emitter.emit('transaction:begin');
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Commit the current transaction
|
|
295
|
-
*/
|
|
296
|
-
async commitTransaction(): Promise<void> {
|
|
297
|
-
if (!this.activeConnection) {
|
|
298
|
-
throw new Error('No active transaction to commit');
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
try {
|
|
302
|
-
await this.activeConnection.commit();
|
|
303
|
-
this.emitter.emit('transaction:commit');
|
|
304
|
-
} finally {
|
|
305
|
-
this.activeConnection.release();
|
|
306
|
-
this.activeConnection = null;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* Rollback the current transaction
|
|
312
|
-
*/
|
|
313
|
-
async rollbackTransaction(savepoint?: string): Promise<void> {
|
|
314
|
-
if (!this.activeConnection) {
|
|
315
|
-
throw new Error('No active transaction to rollback');
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
try {
|
|
319
|
-
if (savepoint) {
|
|
320
|
-
await this.activeConnection.query(`ROLLBACK TO SAVEPOINT ${this.quote(savepoint)}`);
|
|
321
|
-
} else {
|
|
322
|
-
await this.activeConnection.rollback();
|
|
323
|
-
}
|
|
324
|
-
this.emitter.emit('transaction:rollback', { savepoint });
|
|
325
|
-
} finally {
|
|
326
|
-
if (!savepoint) {
|
|
327
|
-
this.activeConnection.release();
|
|
328
|
-
this.activeConnection = null;
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Create a savepoint within a transaction
|
|
335
|
-
*/
|
|
336
|
-
async createSavepoint(name: string): Promise<void> {
|
|
337
|
-
if (!this.activeConnection) {
|
|
338
|
-
throw new Error('Cannot create savepoint outside of transaction');
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
await this.activeConnection.query(`SAVEPOINT ${this.quote(name)}`);
|
|
342
|
-
this.emitter.emit('savepoint:create', { name });
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* Release a savepoint
|
|
347
|
-
*/
|
|
348
|
-
async releaseSavepoint(name: string): Promise<void> {
|
|
349
|
-
if (!this.activeConnection) {
|
|
350
|
-
throw new Error('Cannot release savepoint outside of transaction');
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
await this.activeConnection.query(`RELEASE SAVEPOINT ${this.quote(name)}`);
|
|
354
|
-
this.emitter.emit('savepoint:release', { name });
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* Run a migration SQL statement
|
|
359
|
-
*/
|
|
360
|
-
async runMigration(sql: string): Promise<void> {
|
|
361
|
-
this.ensureConnected();
|
|
362
|
-
|
|
363
|
-
// Split multiple statements and execute them separately
|
|
364
|
-
const statements = this.splitStatements(sql);
|
|
365
|
-
|
|
366
|
-
for (const statement of statements) {
|
|
367
|
-
const trimmed = statement.trim();
|
|
368
|
-
if (trimmed) {
|
|
369
|
-
await this.pool!.query(trimmed);
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* Ensure migrations table exists
|
|
376
|
-
*/
|
|
377
|
-
async ensureMigrationsTable(): Promise<void> {
|
|
378
|
-
this.ensureConnected();
|
|
379
|
-
|
|
380
|
-
const sql = `
|
|
381
|
-
CREATE TABLE IF NOT EXISTS \`migrations\` (
|
|
382
|
-
\`id\` INT AUTO_INCREMENT PRIMARY KEY,
|
|
383
|
-
\`name\` VARCHAR(255) NOT NULL,
|
|
384
|
-
\`executed_at\` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
385
|
-
\`batch\` INT NOT NULL DEFAULT 1,
|
|
386
|
-
UNIQUE KEY \`unique_migration_name\` (\`name\`)
|
|
387
|
-
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
388
|
-
`;
|
|
389
|
-
|
|
390
|
-
await this.pool!.query(sql);
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
/**
|
|
394
|
-
* Get pool statistics for monitoring
|
|
395
|
-
*/
|
|
396
|
-
getPoolStats(): MySQLPoolStats {
|
|
397
|
-
// Note: mysql2 pool doesn't expose these directly
|
|
398
|
-
// This is a placeholder for monitoring integration
|
|
399
|
-
return {
|
|
400
|
-
totalConnections: 0,
|
|
401
|
-
idleConnections: 0,
|
|
402
|
-
pendingRequests: 0,
|
|
403
|
-
maxConnections: this.getPoolConfig().pool?.max ?? 10
|
|
404
|
-
};
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
/**
|
|
408
|
-
* Health check - ping the database
|
|
409
|
-
*/
|
|
410
|
-
async ping(): Promise<boolean> {
|
|
411
|
-
if (!this.connected || !this.pool) {
|
|
412
|
-
return false;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
try {
|
|
416
|
-
await this.pool.query('SELECT 1');
|
|
417
|
-
return true;
|
|
418
|
-
} catch {
|
|
419
|
-
return false;
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
/**
|
|
424
|
-
* Subscribe to adapter events
|
|
425
|
-
*/
|
|
426
|
-
on(event: string, listener: (...args: unknown[]) => void): void {
|
|
427
|
-
this.emitter.on(event, listener);
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
/**
|
|
431
|
-
* Unsubscribe from adapter events
|
|
432
|
-
*/
|
|
433
|
-
off(event: string, listener: (...args: unknown[]) => void): void {
|
|
434
|
-
this.emitter.off(event, listener);
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
/**
|
|
438
|
-
* Quote an identifier (table name, column name, etc.)
|
|
439
|
-
*/
|
|
440
|
-
quote(identifier: string): string {
|
|
441
|
-
// MySQL uses backticks for identifiers
|
|
442
|
-
if (identifier.includes('.')) {
|
|
443
|
-
return identifier
|
|
444
|
-
.split('.')
|
|
445
|
-
.map((part) => `\`${part.replace(/`/g, '``')}\``)
|
|
446
|
-
.join('.');
|
|
447
|
-
}
|
|
448
|
-
return `\`${identifier.replace(/`/g, '``')}\``;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
/**
|
|
452
|
-
* Escape a string value
|
|
453
|
-
*/
|
|
454
|
-
escape(value: string): string {
|
|
455
|
-
return value
|
|
456
|
-
.replace(/\\/g, '\\\\')
|
|
457
|
-
.replace(/'/g, "\\'")
|
|
458
|
-
.replace(/"/g, '\\"')
|
|
459
|
-
.replace(/\n/g, '\\n')
|
|
460
|
-
.replace(/\r/g, '\\r')
|
|
461
|
-
.replace(/\x00/g, '\\0')
|
|
462
|
-
.replace(/\x1a/g, '\\Z');
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// ==================== Private Methods ====================
|
|
466
|
-
|
|
467
|
-
private async loadMySQLDriver(): Promise<MySQL2Library> {
|
|
468
|
-
try {
|
|
469
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
470
|
-
const mysql2 = require('mysql2/promise');
|
|
471
|
-
return mysql2.default || mysql2;
|
|
472
|
-
} catch (error) {
|
|
473
|
-
throw new Error(
|
|
474
|
-
`mysql2 package is required for MySQLAdapter. Install it with: npm install mysql2`
|
|
475
|
-
);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
private createPool(config: MySQLPoolConfig): MySQLPool {
|
|
480
|
-
if (!this.mysql2) {
|
|
481
|
-
throw new Error('MySQL driver not loaded');
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
const poolConfig = {
|
|
485
|
-
host: config.host,
|
|
486
|
-
port: config.port ?? 3306,
|
|
487
|
-
user: config.user,
|
|
488
|
-
password: config.password,
|
|
489
|
-
database: config.database,
|
|
490
|
-
charset: config.charset ?? 'utf8mb4',
|
|
491
|
-
timezone: config.timezone ?? 'Z',
|
|
492
|
-
connectTimeout: config.connectTimeout ?? 10000,
|
|
493
|
-
waitForConnections: true,
|
|
494
|
-
connectionLimit: config.pool?.max ?? 10,
|
|
495
|
-
queueLimit: 0,
|
|
496
|
-
enableKeepAlive: true,
|
|
497
|
-
keepAliveInitialDelay: 10000,
|
|
498
|
-
...(config.ssl && {
|
|
499
|
-
ssl: {
|
|
500
|
-
ca: config.ssl.ca,
|
|
501
|
-
cert: config.ssl.cert,
|
|
502
|
-
key: config.ssl.key,
|
|
503
|
-
rejectUnauthorized: config.ssl.rejectUnauthorized ?? true
|
|
504
|
-
}
|
|
505
|
-
})
|
|
506
|
-
};
|
|
507
|
-
|
|
508
|
-
const pool = this.mysql2.createPool(poolConfig);
|
|
509
|
-
|
|
510
|
-
// Setup error handling
|
|
511
|
-
pool.on('error', (err: unknown) => {
|
|
512
|
-
this.emitter.emit('pool:error', err);
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
return pool;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
private isPrimaryReplicaConfig(config: MySQLReplicaConfig | MySQLPoolConfig): config is MySQLReplicaConfig {
|
|
519
|
-
return 'primary' in config;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
private getPoolConfig(): MySQLPoolConfig {
|
|
523
|
-
return this.isPrimaryReplicaConfig(this.config) ? this.config.primary : this.config;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
private selectPool(sql: string): MySQLPool {
|
|
527
|
-
// Use primary for write operations or if no replicas
|
|
528
|
-
if (this.replicaPools.length === 0 || this.isWriteQuery(sql) || this.activeConnection) {
|
|
529
|
-
return this.pool!;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
// Round-robin selection for read queries
|
|
533
|
-
const readPreference = this.isPrimaryReplicaConfig(this.config)
|
|
534
|
-
? this.config.readPreference ?? 'replica'
|
|
535
|
-
: 'primary';
|
|
536
|
-
|
|
537
|
-
if (readPreference === 'primary') {
|
|
538
|
-
return this.pool!;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
// Select replica using round-robin
|
|
542
|
-
const replica = this.replicaPools[this.currentReplicaIndex];
|
|
543
|
-
this.currentReplicaIndex = (this.currentReplicaIndex + 1) % this.replicaPools.length;
|
|
544
|
-
return replica;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
private isWriteQuery(sql: string): boolean {
|
|
548
|
-
const normalized = sql.trim().toUpperCase();
|
|
549
|
-
return (
|
|
550
|
-
normalized.startsWith('INSERT') ||
|
|
551
|
-
normalized.startsWith('UPDATE') ||
|
|
552
|
-
normalized.startsWith('DELETE') ||
|
|
553
|
-
normalized.startsWith('CREATE') ||
|
|
554
|
-
normalized.startsWith('ALTER') ||
|
|
555
|
-
normalized.startsWith('DROP') ||
|
|
556
|
-
normalized.startsWith('TRUNCATE') ||
|
|
557
|
-
normalized.startsWith('REPLACE')
|
|
558
|
-
);
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
private normalizeQuery(sql: string): string {
|
|
562
|
-
// Convert PostgreSQL-style parameters ($1, $2, etc.) to MySQL-style (?)
|
|
563
|
-
let paramIndex = 0;
|
|
564
|
-
return sql.replace(/\$(\d+)/g, () => {
|
|
565
|
-
paramIndex++;
|
|
566
|
-
return '?';
|
|
567
|
-
});
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
private normalizeParams(params?: unknown[]): unknown[] {
|
|
571
|
-
if (!params) return [];
|
|
572
|
-
|
|
573
|
-
return params.map((param) => {
|
|
574
|
-
// Handle undefined as null
|
|
575
|
-
if (param === undefined) return null;
|
|
576
|
-
|
|
577
|
-
// Handle Date objects
|
|
578
|
-
if (param instanceof Date) {
|
|
579
|
-
return param.toISOString().slice(0, 19).replace('T', ' ');
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
// Handle objects/arrays as JSON
|
|
583
|
-
if (typeof param === 'object' && param !== null) {
|
|
584
|
-
return JSON.stringify(param);
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
return param;
|
|
588
|
-
});
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
private extractFieldNames(fields: unknown): string[] {
|
|
592
|
-
if (!Array.isArray(fields)) return [];
|
|
593
|
-
return fields.map((field: { name?: string }) => field.name ?? '');
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
private splitStatements(sql: string): string[] {
|
|
597
|
-
// Simple statement splitter - handles basic cases
|
|
598
|
-
// For complex migrations, consider using a proper SQL parser
|
|
599
|
-
const statements: string[] = [];
|
|
600
|
-
let current = '';
|
|
601
|
-
let inString = false;
|
|
602
|
-
let stringChar = '';
|
|
603
|
-
|
|
604
|
-
for (let i = 0; i < sql.length; i++) {
|
|
605
|
-
const char = sql[i];
|
|
606
|
-
const prevChar = sql[i - 1];
|
|
607
|
-
|
|
608
|
-
// Track string literals
|
|
609
|
-
if ((char === "'" || char === '"') && prevChar !== '\\') {
|
|
610
|
-
if (!inString) {
|
|
611
|
-
inString = true;
|
|
612
|
-
stringChar = char;
|
|
613
|
-
} else if (char === stringChar) {
|
|
614
|
-
inString = false;
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
// Split on semicolon if not in string
|
|
619
|
-
if (char === ';' && !inString) {
|
|
620
|
-
const stmt = current.trim();
|
|
621
|
-
if (stmt) {
|
|
622
|
-
statements.push(stmt);
|
|
623
|
-
}
|
|
624
|
-
current = '';
|
|
625
|
-
} else {
|
|
626
|
-
current += char;
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
// Don't forget the last statement
|
|
631
|
-
const lastStmt = current.trim();
|
|
632
|
-
if (lastStmt) {
|
|
633
|
-
statements.push(lastStmt);
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
return statements;
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
private ensureConnected(): void {
|
|
640
|
-
if (!this.connected || !this.pool) {
|
|
641
|
-
throw new Error('MySQLAdapter is not connected. Call connect() first.');
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
private wrapError(error: Error, sql: string): Error {
|
|
646
|
-
const mysqlError = error as Error & { code?: string; errno?: number; sqlState?: string };
|
|
647
|
-
|
|
648
|
-
const enhancedError = new Error(
|
|
649
|
-
`MySQL Error: ${mysqlError.message}\nQuery: ${sql.slice(0, 200)}...`
|
|
650
|
-
);
|
|
651
|
-
|
|
652
|
-
// Preserve original error properties
|
|
653
|
-
Object.assign(enhancedError, {
|
|
654
|
-
code: mysqlError.code,
|
|
655
|
-
errno: mysqlError.errno,
|
|
656
|
-
sqlState: mysqlError.sqlState,
|
|
657
|
-
originalError: error
|
|
658
|
-
});
|
|
659
|
-
|
|
660
|
-
return enhancedError;
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
/**
|
|
665
|
-
* Factory function to create a MySQL adapter
|
|
666
|
-
*/
|
|
667
|
-
export function createMySQLAdapter(config: MySQLReplicaConfig | MySQLPoolConfig): MySQLAdapter {
|
|
668
|
-
return new MySQLAdapter(config);
|
|
669
|
-
}
|