@holo-js/db-mysql 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +75 -0
- package/dist/index.mjs +224 -0
- package/package.json +37 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { PoolOptions } from 'mysql2/promise';
|
|
2
|
+
|
|
3
|
+
interface DriverQueryResult<TRow extends Record<string, unknown> = Record<string, unknown>> {
|
|
4
|
+
rows: TRow[];
|
|
5
|
+
rowCount: number;
|
|
6
|
+
}
|
|
7
|
+
interface DriverExecutionResult {
|
|
8
|
+
affectedRows?: number;
|
|
9
|
+
lastInsertId?: number | string;
|
|
10
|
+
}
|
|
11
|
+
interface DriverAdapter {
|
|
12
|
+
initialize(): Promise<void>;
|
|
13
|
+
disconnect(): Promise<void>;
|
|
14
|
+
isConnected(): boolean;
|
|
15
|
+
runWithTransactionScope?<T>(callback: () => Promise<T>): Promise<T>;
|
|
16
|
+
query<TRow extends Record<string, unknown> = Record<string, unknown>>(sql: string, bindings?: readonly unknown[]): Promise<DriverQueryResult<TRow>>;
|
|
17
|
+
execute(sql: string, bindings?: readonly unknown[]): Promise<DriverExecutionResult>;
|
|
18
|
+
beginTransaction(): Promise<void>;
|
|
19
|
+
commit(): Promise<void>;
|
|
20
|
+
rollback(): Promise<void>;
|
|
21
|
+
createSavepoint?(name: string): Promise<void>;
|
|
22
|
+
rollbackToSavepoint?(name: string): Promise<void>;
|
|
23
|
+
releaseSavepoint?(name: string): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
interface MySQLQueryableLike {
|
|
26
|
+
query(sql: string, bindings?: readonly unknown[]): Promise<readonly [unknown, unknown]>;
|
|
27
|
+
}
|
|
28
|
+
interface MySQLClientLike extends MySQLQueryableLike {
|
|
29
|
+
release?(): void;
|
|
30
|
+
end?(): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
interface MySQLPoolLike extends MySQLQueryableLike {
|
|
33
|
+
getConnection(): Promise<MySQLClientLike>;
|
|
34
|
+
end(): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
interface MySQLAdapterOptions {
|
|
37
|
+
uri?: string;
|
|
38
|
+
config?: PoolOptions;
|
|
39
|
+
client?: MySQLClientLike;
|
|
40
|
+
pool?: MySQLPoolLike;
|
|
41
|
+
createPool?: (config: PoolOptions) => MySQLPoolLike;
|
|
42
|
+
}
|
|
43
|
+
declare class MySQLAdapter implements DriverAdapter {
|
|
44
|
+
private pool?;
|
|
45
|
+
private readonly directClient?;
|
|
46
|
+
private readonly createPoolInstance?;
|
|
47
|
+
private readonly config;
|
|
48
|
+
private connected;
|
|
49
|
+
private transactionClient?;
|
|
50
|
+
private leasedTransactionClient;
|
|
51
|
+
private readonly transactionScope;
|
|
52
|
+
constructor(options?: MySQLAdapterOptions);
|
|
53
|
+
initialize(): Promise<void>;
|
|
54
|
+
disconnect(): Promise<void>;
|
|
55
|
+
isConnected(): boolean;
|
|
56
|
+
runWithTransactionScope<T>(callback: () => Promise<T>): Promise<T>;
|
|
57
|
+
query<TRow extends Record<string, unknown> = Record<string, unknown>>(sql: string, bindings?: readonly unknown[]): Promise<DriverQueryResult<TRow>>;
|
|
58
|
+
introspect<TRow extends Record<string, unknown> = Record<string, unknown>>(sql: string, bindings?: readonly unknown[]): Promise<DriverQueryResult<TRow>>;
|
|
59
|
+
execute(sql: string, bindings?: readonly unknown[]): Promise<DriverExecutionResult>;
|
|
60
|
+
beginTransaction(): Promise<void>;
|
|
61
|
+
commit(): Promise<void>;
|
|
62
|
+
rollback(): Promise<void>;
|
|
63
|
+
createSavepoint(name: string): Promise<void>;
|
|
64
|
+
rollbackToSavepoint(name: string): Promise<void>;
|
|
65
|
+
releaseSavepoint(name: string): Promise<void>;
|
|
66
|
+
private getQueryable;
|
|
67
|
+
private leaseTransactionClient;
|
|
68
|
+
private requireTransactionClient;
|
|
69
|
+
private releaseTransactionClient;
|
|
70
|
+
private releaseScopedTransaction;
|
|
71
|
+
private normalizeSavepointName;
|
|
72
|
+
}
|
|
73
|
+
declare function createMySQLAdapter(options?: MySQLAdapterOptions): MySQLAdapter;
|
|
74
|
+
|
|
75
|
+
export { type DriverAdapter, type DriverExecutionResult, type DriverQueryResult, MySQLAdapter, type MySQLAdapterOptions, type MySQLClientLike, type MySQLPoolLike, type MySQLQueryableLike, createMySQLAdapter };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
3
|
+
import mysql from "mysql2/promise";
|
|
4
|
+
var TransactionError = class extends Error {
|
|
5
|
+
};
|
|
6
|
+
function toMutableBindings(bindings = []) {
|
|
7
|
+
return [...bindings];
|
|
8
|
+
}
|
|
9
|
+
function wrapMySQLClient(client) {
|
|
10
|
+
const rawClient = client;
|
|
11
|
+
return {
|
|
12
|
+
async query(sql, bindings = []) {
|
|
13
|
+
return rawClient.query(sql, toMutableBindings(bindings));
|
|
14
|
+
},
|
|
15
|
+
release: rawClient.release?.bind(rawClient),
|
|
16
|
+
end: rawClient.end?.bind(rawClient)
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function wrapMySQLPool(pool) {
|
|
20
|
+
const rawPool = pool;
|
|
21
|
+
return {
|
|
22
|
+
async query(sql, bindings = []) {
|
|
23
|
+
return rawPool.query(sql, toMutableBindings(bindings));
|
|
24
|
+
},
|
|
25
|
+
async getConnection() {
|
|
26
|
+
return wrapMySQLClient(await rawPool.getConnection());
|
|
27
|
+
},
|
|
28
|
+
end: rawPool.end.bind(rawPool)
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
var MySQLAdapter = class {
|
|
32
|
+
pool;
|
|
33
|
+
directClient;
|
|
34
|
+
createPoolInstance;
|
|
35
|
+
config;
|
|
36
|
+
connected;
|
|
37
|
+
transactionClient;
|
|
38
|
+
leasedTransactionClient = false;
|
|
39
|
+
transactionScope = new AsyncLocalStorage();
|
|
40
|
+
constructor(options = {}) {
|
|
41
|
+
this.directClient = options.client ? wrapMySQLClient(options.client) : void 0;
|
|
42
|
+
this.pool = options.pool ? wrapMySQLPool(options.pool) : void 0;
|
|
43
|
+
this.createPoolInstance = options.createPool ?? (options.client || options.pool ? void 0 : (config) => wrapMySQLPool(mysql.createPool(config)));
|
|
44
|
+
this.config = options.config ?? (options.uri ? { uri: options.uri } : {});
|
|
45
|
+
this.connected = !!(options.client || options.pool);
|
|
46
|
+
}
|
|
47
|
+
async initialize() {
|
|
48
|
+
if (this.connected) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (this.createPoolInstance) {
|
|
52
|
+
this.pool = this.createPoolInstance(this.config);
|
|
53
|
+
}
|
|
54
|
+
this.connected = true;
|
|
55
|
+
}
|
|
56
|
+
async disconnect() {
|
|
57
|
+
if (!this.connected) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (this.transactionClient && this.leasedTransactionClient) {
|
|
61
|
+
this.transactionClient.release?.();
|
|
62
|
+
this.transactionClient = void 0;
|
|
63
|
+
this.leasedTransactionClient = false;
|
|
64
|
+
}
|
|
65
|
+
if (this.pool) {
|
|
66
|
+
await this.pool.end();
|
|
67
|
+
this.pool = void 0;
|
|
68
|
+
} else if (this.directClient?.end) {
|
|
69
|
+
await this.directClient.end();
|
|
70
|
+
}
|
|
71
|
+
this.connected = false;
|
|
72
|
+
}
|
|
73
|
+
isConnected() {
|
|
74
|
+
return this.connected;
|
|
75
|
+
}
|
|
76
|
+
async runWithTransactionScope(callback) {
|
|
77
|
+
const active = this.transactionScope.getStore();
|
|
78
|
+
if (active) {
|
|
79
|
+
return callback();
|
|
80
|
+
}
|
|
81
|
+
await this.initialize();
|
|
82
|
+
if (this.directClient) {
|
|
83
|
+
return this.transactionScope.run({
|
|
84
|
+
client: this.directClient,
|
|
85
|
+
leased: false,
|
|
86
|
+
released: false
|
|
87
|
+
}, callback);
|
|
88
|
+
}
|
|
89
|
+
if (!this.pool) {
|
|
90
|
+
throw new TransactionError("MySQL adapter is not initialized with a pool or client.");
|
|
91
|
+
}
|
|
92
|
+
const state = {
|
|
93
|
+
client: await this.pool.getConnection(),
|
|
94
|
+
leased: true,
|
|
95
|
+
released: false
|
|
96
|
+
};
|
|
97
|
+
return this.transactionScope.run(state, async () => {
|
|
98
|
+
try {
|
|
99
|
+
return await callback();
|
|
100
|
+
} finally {
|
|
101
|
+
this.releaseScopedTransaction(state);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
async query(sql, bindings = []) {
|
|
106
|
+
const queryable = await this.getQueryable();
|
|
107
|
+
const [rows] = await queryable.query(sql, bindings);
|
|
108
|
+
const normalized = rows;
|
|
109
|
+
return {
|
|
110
|
+
rows: Array.isArray(normalized) ? [...normalized] : [],
|
|
111
|
+
rowCount: Array.isArray(normalized) ? normalized.length : 0
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
async introspect(sql, bindings = []) {
|
|
115
|
+
return this.query(sql, bindings);
|
|
116
|
+
}
|
|
117
|
+
async execute(sql, bindings = []) {
|
|
118
|
+
const queryable = await this.getQueryable();
|
|
119
|
+
const [result] = await queryable.query(sql, bindings);
|
|
120
|
+
const execution = result;
|
|
121
|
+
return {
|
|
122
|
+
affectedRows: typeof execution.affectedRows === "number" ? execution.affectedRows : 0,
|
|
123
|
+
lastInsertId: execution.insertId
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
async beginTransaction() {
|
|
127
|
+
const client = await this.leaseTransactionClient();
|
|
128
|
+
await client.query("START TRANSACTION");
|
|
129
|
+
}
|
|
130
|
+
async commit() {
|
|
131
|
+
const client = this.requireTransactionClient();
|
|
132
|
+
await client.query("COMMIT");
|
|
133
|
+
this.releaseTransactionClient();
|
|
134
|
+
}
|
|
135
|
+
async rollback() {
|
|
136
|
+
const client = this.requireTransactionClient();
|
|
137
|
+
await client.query("ROLLBACK");
|
|
138
|
+
this.releaseTransactionClient();
|
|
139
|
+
}
|
|
140
|
+
async createSavepoint(name) {
|
|
141
|
+
await this.requireTransactionClient().query(`SAVEPOINT ${this.normalizeSavepointName(name)}`);
|
|
142
|
+
}
|
|
143
|
+
async rollbackToSavepoint(name) {
|
|
144
|
+
await this.requireTransactionClient().query(`ROLLBACK TO SAVEPOINT ${this.normalizeSavepointName(name)}`);
|
|
145
|
+
}
|
|
146
|
+
async releaseSavepoint(name) {
|
|
147
|
+
await this.requireTransactionClient().query(`RELEASE SAVEPOINT ${this.normalizeSavepointName(name)}`);
|
|
148
|
+
}
|
|
149
|
+
async getQueryable() {
|
|
150
|
+
const scoped = this.transactionScope.getStore();
|
|
151
|
+
if (scoped) {
|
|
152
|
+
return scoped.client;
|
|
153
|
+
}
|
|
154
|
+
if (this.transactionClient) {
|
|
155
|
+
return this.transactionClient;
|
|
156
|
+
}
|
|
157
|
+
await this.initialize();
|
|
158
|
+
if (this.directClient) {
|
|
159
|
+
return this.directClient;
|
|
160
|
+
}
|
|
161
|
+
if (!this.pool) {
|
|
162
|
+
throw new TransactionError("MySQL adapter is not initialized with a pool or client.");
|
|
163
|
+
}
|
|
164
|
+
return this.pool;
|
|
165
|
+
}
|
|
166
|
+
async leaseTransactionClient() {
|
|
167
|
+
const scoped = this.transactionScope.getStore();
|
|
168
|
+
if (scoped) {
|
|
169
|
+
return scoped.client;
|
|
170
|
+
}
|
|
171
|
+
if (this.transactionClient) {
|
|
172
|
+
return this.transactionClient;
|
|
173
|
+
}
|
|
174
|
+
await this.initialize();
|
|
175
|
+
if (this.directClient) {
|
|
176
|
+
this.transactionClient = this.directClient;
|
|
177
|
+
this.leasedTransactionClient = false;
|
|
178
|
+
return this.transactionClient;
|
|
179
|
+
}
|
|
180
|
+
if (!this.pool) {
|
|
181
|
+
throw new TransactionError("MySQL adapter is not initialized with a pool or client.");
|
|
182
|
+
}
|
|
183
|
+
this.transactionClient = await this.pool.getConnection();
|
|
184
|
+
this.leasedTransactionClient = true;
|
|
185
|
+
return this.transactionClient;
|
|
186
|
+
}
|
|
187
|
+
requireTransactionClient() {
|
|
188
|
+
const scoped = this.transactionScope.getStore();
|
|
189
|
+
if (scoped) {
|
|
190
|
+
return scoped.client;
|
|
191
|
+
}
|
|
192
|
+
if (!this.transactionClient) {
|
|
193
|
+
throw new TransactionError("No active MySQL transaction client is available.");
|
|
194
|
+
}
|
|
195
|
+
return this.transactionClient;
|
|
196
|
+
}
|
|
197
|
+
releaseTransactionClient() {
|
|
198
|
+
if (this.transactionClient && this.leasedTransactionClient) {
|
|
199
|
+
this.transactionClient.release?.();
|
|
200
|
+
}
|
|
201
|
+
this.transactionClient = void 0;
|
|
202
|
+
this.leasedTransactionClient = false;
|
|
203
|
+
}
|
|
204
|
+
releaseScopedTransaction(state) {
|
|
205
|
+
if (!state.leased || state.released) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
state.client.release?.();
|
|
209
|
+
state.released = true;
|
|
210
|
+
}
|
|
211
|
+
normalizeSavepointName(name) {
|
|
212
|
+
if (!/^[A-Z_]\w*$/i.test(name)) {
|
|
213
|
+
throw new TransactionError(`Invalid savepoint name "${name}".`);
|
|
214
|
+
}
|
|
215
|
+
return name;
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
function createMySQLAdapter(options = {}) {
|
|
219
|
+
return new MySQLAdapter(options);
|
|
220
|
+
}
|
|
221
|
+
export {
|
|
222
|
+
MySQLAdapter,
|
|
223
|
+
createMySQLAdapter
|
|
224
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@holo-js/db-mysql",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "Holo-JS Framework - MySQL database adapter",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"default": "./dist/index.mjs"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"main": "./dist/index.mjs",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"stub": "tsup --watch",
|
|
22
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
23
|
+
"test": "vitest --run"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"@holo-js/db": "^0.1.3"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"mysql2": "^3.17.1"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@holo-js/db": "workspace:*",
|
|
33
|
+
"tsup": "^8.3.5",
|
|
34
|
+
"typescript": "^5.7.2",
|
|
35
|
+
"vitest": "^2.1.8"
|
|
36
|
+
}
|
|
37
|
+
}
|