@chidchanun/bcp 0.2.2 → 0.2.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/README.md +62 -20
- package/docs/api-manifest.json +2 -2
- package/docs/api-reference.md +21 -2
- package/docs/database-migrations.md +79 -19
- package/docs/database.md +197 -28
- package/docs/docs-web-manifest.json +4 -3
- package/docs/platform-manifest.json +14 -4
- package/docs/releases/0.2.3.md +77 -0
- package/package.json +1 -1
- package/packages/cli/src/database-migrations.ts +91 -21
- package/packages/client/src/database-mysql.ts +291 -0
- package/packages/client/src/database-postgresql.ts +355 -0
- package/packages/client/src/database-sqlite.ts +445 -0
- package/packages/client/src/database.mjs +709 -57
- package/packages/client/src/database.ts +347 -182
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DatabaseAdapter,
|
|
3
|
+
DatabaseParameters,
|
|
4
|
+
ResolvedDatabaseOptions,
|
|
5
|
+
TransactionDatabase,
|
|
6
|
+
} from "./database.js";
|
|
7
|
+
|
|
8
|
+
interface PostgresqlQueryResult {
|
|
9
|
+
rows: unknown[];
|
|
10
|
+
rowCount: number | null;
|
|
11
|
+
command?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface PostgresqlExecutor {
|
|
15
|
+
query(
|
|
16
|
+
sql: string,
|
|
17
|
+
parameters?: readonly unknown[]
|
|
18
|
+
): Promise<PostgresqlQueryResult>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface PostgresqlConnection
|
|
22
|
+
extends PostgresqlExecutor {
|
|
23
|
+
release(): void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface PostgresqlPool
|
|
27
|
+
extends PostgresqlExecutor {
|
|
28
|
+
connect(): Promise<PostgresqlConnection>;
|
|
29
|
+
end(): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface PostgresqlPoolConstructor {
|
|
33
|
+
new (
|
|
34
|
+
options: Record<string, unknown>
|
|
35
|
+
): PostgresqlPool;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface PostgresqlModule {
|
|
39
|
+
Pool?: PostgresqlPoolConstructor;
|
|
40
|
+
default?: {
|
|
41
|
+
Pool?: PostgresqlPoolConstructor;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const POSTGRESQL_MODULE_SPECIFIER =
|
|
46
|
+
"pg";
|
|
47
|
+
|
|
48
|
+
export function createPostgresqlDatabaseAdapter(
|
|
49
|
+
options: ResolvedDatabaseOptions
|
|
50
|
+
): DatabaseAdapter {
|
|
51
|
+
return new PostgresqlDatabaseAdapter(
|
|
52
|
+
options
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
class PostgresqlDatabaseAdapter
|
|
57
|
+
implements DatabaseAdapter {
|
|
58
|
+
readonly driver = "postgresql";
|
|
59
|
+
|
|
60
|
+
private readonly options:
|
|
61
|
+
ResolvedDatabaseOptions;
|
|
62
|
+
|
|
63
|
+
private poolPromise:
|
|
64
|
+
Promise<PostgresqlPool> | null =
|
|
65
|
+
null;
|
|
66
|
+
|
|
67
|
+
constructor(
|
|
68
|
+
options: ResolvedDatabaseOptions
|
|
69
|
+
) {
|
|
70
|
+
this.options =
|
|
71
|
+
options;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async connect(): Promise<void> {
|
|
75
|
+
if (!this.poolPromise) {
|
|
76
|
+
this.poolPromise =
|
|
77
|
+
createPostgresqlPool(
|
|
78
|
+
this.options
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
await this.poolPromise;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async query<T = unknown>(
|
|
86
|
+
sql: string,
|
|
87
|
+
parameters?: DatabaseParameters
|
|
88
|
+
): Promise<T> {
|
|
89
|
+
assertSql(
|
|
90
|
+
sql
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const pool =
|
|
94
|
+
await this.getPool();
|
|
95
|
+
const result =
|
|
96
|
+
await pool.query(
|
|
97
|
+
sql,
|
|
98
|
+
normalizePostgresqlParameters(
|
|
99
|
+
parameters
|
|
100
|
+
)
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
return result.rows as T;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async execute<T = unknown>(
|
|
107
|
+
sql: string,
|
|
108
|
+
parameters?: DatabaseParameters
|
|
109
|
+
): Promise<T> {
|
|
110
|
+
assertSql(
|
|
111
|
+
sql
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const pool =
|
|
115
|
+
await this.getPool();
|
|
116
|
+
const result =
|
|
117
|
+
await pool.query(
|
|
118
|
+
sql,
|
|
119
|
+
normalizePostgresqlParameters(
|
|
120
|
+
parameters
|
|
121
|
+
)
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
return result as T;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async transaction<T>(
|
|
128
|
+
callback: (
|
|
129
|
+
database: TransactionDatabase
|
|
130
|
+
) => Promise<T>
|
|
131
|
+
): Promise<T> {
|
|
132
|
+
assertTransactionCallback(
|
|
133
|
+
callback
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const pool =
|
|
137
|
+
await this.getPool();
|
|
138
|
+
const connection =
|
|
139
|
+
await pool.connect();
|
|
140
|
+
|
|
141
|
+
await connection.query(
|
|
142
|
+
"BEGIN"
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const transactionDatabase:
|
|
146
|
+
TransactionDatabase = {
|
|
147
|
+
query: async <R = unknown>(
|
|
148
|
+
sql: string,
|
|
149
|
+
parameters?: DatabaseParameters
|
|
150
|
+
): Promise<R> => {
|
|
151
|
+
assertSql(
|
|
152
|
+
sql
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
const result =
|
|
156
|
+
await connection.query(
|
|
157
|
+
sql,
|
|
158
|
+
normalizePostgresqlParameters(
|
|
159
|
+
parameters
|
|
160
|
+
)
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
return result.rows as R;
|
|
164
|
+
},
|
|
165
|
+
execute: async <R = unknown>(
|
|
166
|
+
sql: string,
|
|
167
|
+
parameters?: DatabaseParameters
|
|
168
|
+
): Promise<R> => {
|
|
169
|
+
assertSql(
|
|
170
|
+
sql
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
const result =
|
|
174
|
+
await connection.query(
|
|
175
|
+
sql,
|
|
176
|
+
normalizePostgresqlParameters(
|
|
177
|
+
parameters
|
|
178
|
+
)
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
return result as R;
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
const result =
|
|
187
|
+
await callback(
|
|
188
|
+
transactionDatabase
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
await connection.query(
|
|
192
|
+
"COMMIT"
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
return result;
|
|
196
|
+
} catch (error) {
|
|
197
|
+
try {
|
|
198
|
+
await connection.query(
|
|
199
|
+
"ROLLBACK"
|
|
200
|
+
);
|
|
201
|
+
} catch {
|
|
202
|
+
// Preserve the original transaction error.
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
throw error;
|
|
206
|
+
} finally {
|
|
207
|
+
connection.release();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async disconnect(): Promise<void> {
|
|
212
|
+
const pendingPool =
|
|
213
|
+
this.poolPromise;
|
|
214
|
+
|
|
215
|
+
this.poolPromise =
|
|
216
|
+
null;
|
|
217
|
+
|
|
218
|
+
if (!pendingPool) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
let pool:
|
|
223
|
+
PostgresqlPool;
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
pool =
|
|
227
|
+
await pendingPool;
|
|
228
|
+
} catch {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
await pool.end();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private async getPool():
|
|
236
|
+
Promise<PostgresqlPool> {
|
|
237
|
+
await this.connect();
|
|
238
|
+
|
|
239
|
+
return this.poolPromise as
|
|
240
|
+
Promise<PostgresqlPool>;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function createPostgresqlPool(
|
|
245
|
+
options: ResolvedDatabaseOptions
|
|
246
|
+
): Promise<PostgresqlPool> {
|
|
247
|
+
let postgresqlModule:
|
|
248
|
+
PostgresqlModule;
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
postgresqlModule =
|
|
252
|
+
await import(
|
|
253
|
+
POSTGRESQL_MODULE_SPECIFIER
|
|
254
|
+
) as unknown as PostgresqlModule;
|
|
255
|
+
} catch (error) {
|
|
256
|
+
throw new Error(
|
|
257
|
+
"BCP Database: PostgreSQL requires the optional dependency pg. Install it with `npm install pg` or create the app with the PostgreSQL preset.",
|
|
258
|
+
{
|
|
259
|
+
cause:
|
|
260
|
+
error,
|
|
261
|
+
}
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const Pool =
|
|
266
|
+
postgresqlModule.Pool ??
|
|
267
|
+
postgresqlModule.default?.Pool;
|
|
268
|
+
|
|
269
|
+
if (
|
|
270
|
+
typeof Pool !==
|
|
271
|
+
"function"
|
|
272
|
+
) {
|
|
273
|
+
throw new Error(
|
|
274
|
+
"BCP Database: pg did not expose a compatible Pool constructor."
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const poolOptions:
|
|
279
|
+
Record<string, unknown> =
|
|
280
|
+
options.url
|
|
281
|
+
? {
|
|
282
|
+
connectionString:
|
|
283
|
+
options.url,
|
|
284
|
+
max:
|
|
285
|
+
options.connectionLimit,
|
|
286
|
+
}
|
|
287
|
+
: {
|
|
288
|
+
host:
|
|
289
|
+
options.host,
|
|
290
|
+
port:
|
|
291
|
+
options.port,
|
|
292
|
+
user:
|
|
293
|
+
options.user,
|
|
294
|
+
password:
|
|
295
|
+
options.password,
|
|
296
|
+
database:
|
|
297
|
+
options.database,
|
|
298
|
+
max:
|
|
299
|
+
options.connectionLimit,
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
return new Pool(
|
|
303
|
+
poolOptions
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function normalizePostgresqlParameters(
|
|
308
|
+
parameters: DatabaseParameters | undefined
|
|
309
|
+
): readonly unknown[] | undefined {
|
|
310
|
+
if (
|
|
311
|
+
parameters === undefined
|
|
312
|
+
) {
|
|
313
|
+
return undefined;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (
|
|
317
|
+
Array.isArray(
|
|
318
|
+
parameters
|
|
319
|
+
)
|
|
320
|
+
) {
|
|
321
|
+
return parameters;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
throw new TypeError(
|
|
325
|
+
"BCP Database: PostgreSQL parameters must be an array. Use $1, $2, ... placeholders."
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function assertSql(
|
|
330
|
+
sql: string
|
|
331
|
+
): void {
|
|
332
|
+
if (
|
|
333
|
+
typeof sql !== "string" ||
|
|
334
|
+
sql.trim() === ""
|
|
335
|
+
) {
|
|
336
|
+
throw new TypeError(
|
|
337
|
+
"BCP Database: SQL must be a non-empty string."
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function assertTransactionCallback(
|
|
343
|
+
callback: unknown
|
|
344
|
+
): asserts callback is (
|
|
345
|
+
database: TransactionDatabase
|
|
346
|
+
) => Promise<unknown> {
|
|
347
|
+
if (
|
|
348
|
+
typeof callback !==
|
|
349
|
+
"function"
|
|
350
|
+
) {
|
|
351
|
+
throw new TypeError(
|
|
352
|
+
"BCP Database: transaction callback must be a function."
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
}
|