@iskra-bun/db-kit 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/dist/index.d.ts +79 -4
- package/dist/index.js +141 -22
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/driver.ts +190 -25
- package/src/migrations.ts +35 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @iskra-bun/db-kit
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- f9654df: New DB features and a migration fix:
|
|
8
|
+
|
|
9
|
+
- **Fix:** `MigrationHelper` now passes `schemaPath`/`migrationsDir` (and an optional `configPath`) to drizzle-kit as `--schema`/`--out`/`--config` flags per command, instead of silently ignoring them when no `drizzle.config.ts` sits in the cwd.
|
|
10
|
+
- `DbDriver.transaction(fn)` — typed wrapper around Drizzle's transaction so callers don't reach into the raw `db`.
|
|
11
|
+
- `DbDriver.setOnQuery(cb)` — observability hook wired through Drizzle's logger to surface executed SQL + params.
|
|
12
|
+
- `DbDriver.ping()` — runs a trivial liveness query and resolves `true`/`false` (never rejects), suitable for readiness probes.
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- f9654df: `DbDriver` and `DbFeature` now accept an optional schema generic (`DbDriver<TSchema>` / `DbFeature<TSchema>`), so `.db` is a typed Drizzle database instead of `any` — opt-in callers get typed relational queries and autocomplete. The generic defaults preserve existing behavior, so no call site needs changes; consumers that relied on `any` may need to add a type argument or annotation.
|
|
17
|
+
- f9654df: Scrub credentials from the URL placed in `ConnectionError` context so passwords no longer leak into structured logs, and scrub `//user:pass@` credentials out of drizzle-kit stderr before storing it in `MigrationError` context. The MySQL driver now uses a connection pool (`createPool`) instead of a single serialized connection — note that `createPool` changes the MySQL lifecycle (pooled connections vs. a single serialized connection), so teardown now drains the pool via `end()`. `DbDriver.stop()` is hardened to swallow a throwing `end()`/`close()` (logging via `app.logger`) and to null the `client`/`db` handles so a post-stop `ping()`/`transaction()` hits the not-started guard instead of an already-closed connection.
|
|
18
|
+
- Scrub `//user:pass@` credentials out of the drizzle-kit stderr captured in `MigrationError.context.stderr`, so connection passwords no longer leak into migration error logs. Non-credential diagnostic text in stderr is preserved.
|
|
19
|
+
- Updated dependencies [f9654df]
|
|
20
|
+
- Updated dependencies
|
|
21
|
+
- Updated dependencies [f9654df]
|
|
22
|
+
- @iskra-bun/core@0.1.1
|
|
23
|
+
|
|
3
24
|
## 0.1.0
|
|
4
25
|
|
|
5
26
|
### Minor Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,76 @@
|
|
|
1
1
|
import { Driver, App, IskraError } from '@iskra-bun/core';
|
|
2
|
+
import { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
|
|
3
|
+
import { MySql2Database } from 'drizzle-orm/mysql2';
|
|
4
|
+
import { BunSQLiteDatabase } from 'drizzle-orm/bun-sqlite';
|
|
5
|
+
import { LibSQLDatabase } from 'drizzle-orm/libsql';
|
|
2
6
|
import * as drizzle_kit from 'drizzle-kit';
|
|
3
7
|
|
|
4
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Observability callback invoked for every SQL statement Drizzle executes.
|
|
10
|
+
* Receives the rendered query and its bound parameters.
|
|
11
|
+
*/
|
|
12
|
+
type OnQueryHook = (query: string, params: unknown[]) => void;
|
|
13
|
+
/**
|
|
14
|
+
* The transaction handle passed to {@link DbDriver.transaction}. Drizzle types
|
|
15
|
+
* the transaction object per dialect, so — like {@link IskraDrizzleDb} — this is
|
|
16
|
+
* the union of the supported dialect databases for the same schema. Callers can
|
|
17
|
+
* narrow by dialect if they need dialect-specific transaction APIs.
|
|
18
|
+
*/
|
|
19
|
+
type IskraDrizzleTx<TSchema extends Record<string, unknown> = Record<string, never>> = IskraDrizzleDb<TSchema>;
|
|
20
|
+
/**
|
|
21
|
+
* The Drizzle database handle exposed by {@link DbDriver}, parameterized by the
|
|
22
|
+
* caller's schema. Because the concrete dialect is chosen at runtime, this is a
|
|
23
|
+
* union of the supported dialect databases — all four share the same
|
|
24
|
+
* `TSchema extends Record<string, unknown> = Record<string, never>` parameter,
|
|
25
|
+
* so passing a schema types `db.query.*` for opt-in callers while the default
|
|
26
|
+
* `Record<string, never>` reproduces the historical untyped behavior.
|
|
27
|
+
*/
|
|
28
|
+
type IskraDrizzleDb<TSchema extends Record<string, unknown> = Record<string, never>> = PostgresJsDatabase<TSchema> | MySql2Database<TSchema> | BunSQLiteDatabase<TSchema> | LibSQLDatabase<TSchema>;
|
|
29
|
+
/**
|
|
30
|
+
* Redact username and password from a database URL so it is safe to log.
|
|
31
|
+
* Returns the scrubbed URL string, or undefined if parsing fails.
|
|
32
|
+
*
|
|
33
|
+
* e.g. postgres://user:pass@host:5432/db → postgres://***:***@host:5432/db
|
|
34
|
+
*/
|
|
35
|
+
declare function scrubUrl(url: string): string | undefined;
|
|
36
|
+
declare class DbDriver<TSchema extends Record<string, unknown> = Record<string, never>> implements Driver {
|
|
5
37
|
name: string;
|
|
6
38
|
private client;
|
|
7
|
-
db:
|
|
39
|
+
db: IskraDrizzleDb<TSchema> | undefined;
|
|
8
40
|
private app;
|
|
41
|
+
private onQuery;
|
|
42
|
+
/**
|
|
43
|
+
* Register an observability callback that receives every SQL statement (and
|
|
44
|
+
* its bound params) Drizzle executes. Must be called before {@link start},
|
|
45
|
+
* since Drizzle's logger is wired at connection time. A throwing callback is
|
|
46
|
+
* swallowed so observability never breaks a real query.
|
|
47
|
+
*/
|
|
48
|
+
setOnQuery(onQuery: OnQueryHook): void;
|
|
49
|
+
/**
|
|
50
|
+
* Build the Drizzle `logger` option that forwards to {@link onQuery} when a
|
|
51
|
+
* hook is registered, or `undefined` to leave Drizzle's default logging off.
|
|
52
|
+
*/
|
|
53
|
+
private buildLogger;
|
|
9
54
|
init(app: App): Promise<void>;
|
|
10
55
|
start(): Promise<void>;
|
|
11
56
|
/**
|
|
12
57
|
* Ejecuta migraciones pendientes usando Drizzle Kit.
|
|
13
58
|
*/
|
|
14
59
|
runMigrations(schemaPath: string, migrationsDir?: string): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Run `fn` inside a database transaction, delegating to Drizzle's
|
|
62
|
+
* `db.transaction`. Callers receive the transaction-scoped db handle instead
|
|
63
|
+
* of reaching into the raw `db`. The dialect union means `tx` is typed as
|
|
64
|
+
* {@link IskraDrizzleTx}; narrow by dialect if you need dialect-specific APIs.
|
|
65
|
+
* Failures are wrapped in {@link QueryError} (Drizzle rolls back on throw).
|
|
66
|
+
*/
|
|
67
|
+
transaction<R>(fn: (tx: IskraDrizzleTx<TSchema>) => Promise<R>): Promise<R>;
|
|
68
|
+
/**
|
|
69
|
+
* Liveness probe for readiness checks (e.g. web-kit's addReadinessCheck /
|
|
70
|
+
* k8s readiness). Runs a trivial `SELECT 1` against the active dialect and
|
|
71
|
+
* resolves `true` on success or `false` on any failure — it never rejects.
|
|
72
|
+
*/
|
|
73
|
+
ping(): Promise<boolean>;
|
|
15
74
|
stop(): Promise<void>;
|
|
16
75
|
}
|
|
17
76
|
|
|
@@ -34,6 +93,15 @@ declare class MigrationError extends IskraError {
|
|
|
34
93
|
});
|
|
35
94
|
}
|
|
36
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Redacta credenciales `//user:pass@host` embebidas en texto arbitrario (p. ej.
|
|
98
|
+
* el stderr de drizzle-kit, que suele imprimir la cadena de conexión completa al
|
|
99
|
+
* fallar). A diferencia de `scrubUrl`, opera sobre texto libre y no requiere que
|
|
100
|
+
* el contenido sea una URL parseable, dejando intacto el resto del diagnóstico.
|
|
101
|
+
*
|
|
102
|
+
* e.g. "... postgres://user:pass@host:5432/db" → "... postgres://***:***@host:5432/db"
|
|
103
|
+
*/
|
|
104
|
+
declare function scrubCredentials(text: string): string;
|
|
37
105
|
interface MigrationConfig {
|
|
38
106
|
/** Dialecto de la base de datos */
|
|
39
107
|
dialect: 'postgresql' | 'mysql' | 'sqlite';
|
|
@@ -43,6 +111,8 @@ interface MigrationConfig {
|
|
|
43
111
|
schemaPath: string;
|
|
44
112
|
/** Directorio donde se generan las migraciones (ej: './drizzle') */
|
|
45
113
|
migrationsDir: string;
|
|
114
|
+
/** Ruta opcional a un drizzle.config.ts; cuando se define se pasa como --config. */
|
|
115
|
+
configPath?: string;
|
|
46
116
|
}
|
|
47
117
|
/**
|
|
48
118
|
* Helper para ejecutar migraciones de Drizzle Kit.
|
|
@@ -54,19 +124,24 @@ declare class MigrationHelper {
|
|
|
54
124
|
constructor(config: MigrationConfig, app?: App);
|
|
55
125
|
/**
|
|
56
126
|
* Genera archivos de migración basados en los cambios del schema.
|
|
127
|
+
* drizzle-kit generate soporta --schema y --out, así que ambos se reenvían
|
|
128
|
+
* desde la config (antes se ignoraban silenciosamente).
|
|
57
129
|
*/
|
|
58
130
|
generate(name?: string): Promise<void>;
|
|
59
131
|
/**
|
|
60
132
|
* Aplica las migraciones pendientes a la base de datos.
|
|
133
|
+
* `migrate` sólo acepta --config; schema y out no son flags válidos en este
|
|
134
|
+
* comando, por eso únicamente reenviamos configPath cuando está presente.
|
|
61
135
|
*/
|
|
62
136
|
migrate(): Promise<void>;
|
|
63
137
|
/**
|
|
64
138
|
* Empuja el schema directamente a la base de datos (sin generar archivos de migración).
|
|
65
|
-
* Útil para desarrollo rápido.
|
|
139
|
+
* Útil para desarrollo rápido. `push` acepta --schema pero no --out.
|
|
66
140
|
*/
|
|
67
141
|
push(): Promise<void>;
|
|
68
142
|
/**
|
|
69
143
|
* Elimina todas las tablas de la base de datos.
|
|
144
|
+
* `drop` acepta --out (dónde viven las migraciones) pero no --schema.
|
|
70
145
|
*/
|
|
71
146
|
drop(): Promise<void>;
|
|
72
147
|
private exec;
|
|
@@ -103,4 +178,4 @@ interface DrizzleConfigOptions {
|
|
|
103
178
|
*/
|
|
104
179
|
declare function createDrizzleConfig(options: DrizzleConfigOptions): drizzle_kit.Config;
|
|
105
180
|
|
|
106
|
-
export { ConnectionError, DbDriver, type DrizzleConfigOptions, type MigrationConfig, MigrationError, MigrationHelper, QueryError, createDrizzleConfig, mapDialect };
|
|
181
|
+
export { ConnectionError, DbDriver, type DrizzleConfigOptions, type IskraDrizzleDb, type IskraDrizzleTx, type MigrationConfig, MigrationError, MigrationHelper, type OnQueryHook, QueryError, createDrizzleConfig, mapDialect, scrubCredentials, scrubUrl };
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { drizzle } from "drizzle-orm/postgres-js";
|
|
|
4
4
|
import { drizzle as drizzleMysql } from "drizzle-orm/mysql2";
|
|
5
5
|
import postgres from "postgres";
|
|
6
6
|
import mysql from "mysql2/promise";
|
|
7
|
+
import { sql } from "drizzle-orm";
|
|
7
8
|
|
|
8
9
|
// src/errors.ts
|
|
9
10
|
import { IskraError, ErrorCodes } from "@iskra-bun/core";
|
|
@@ -27,6 +28,9 @@ var MigrationError = class extends IskraError {
|
|
|
27
28
|
};
|
|
28
29
|
|
|
29
30
|
// src/migrations.ts
|
|
31
|
+
function scrubCredentials(text) {
|
|
32
|
+
return text.replace(/(\/\/)[^/\s:@]+:[^/\s@]+@/g, "$1***:***@");
|
|
33
|
+
}
|
|
30
34
|
var MigrationHelper = class {
|
|
31
35
|
config;
|
|
32
36
|
app;
|
|
@@ -36,30 +40,46 @@ var MigrationHelper = class {
|
|
|
36
40
|
}
|
|
37
41
|
/**
|
|
38
42
|
* Genera archivos de migración basados en los cambios del schema.
|
|
43
|
+
* drizzle-kit generate soporta --schema y --out, así que ambos se reenvían
|
|
44
|
+
* desde la config (antes se ignoraban silenciosamente).
|
|
39
45
|
*/
|
|
40
46
|
async generate(name) {
|
|
41
47
|
const args = ["drizzle-kit", "generate"];
|
|
48
|
+
if (this.config.schemaPath) args.push("--schema", this.config.schemaPath);
|
|
49
|
+
if (this.config.migrationsDir) args.push("--out", this.config.migrationsDir);
|
|
42
50
|
if (name) args.push("--name", name);
|
|
51
|
+
if (this.config.configPath) args.push("--config", this.config.configPath);
|
|
43
52
|
await this.exec(args, "generate");
|
|
44
53
|
}
|
|
45
54
|
/**
|
|
46
55
|
* Aplica las migraciones pendientes a la base de datos.
|
|
56
|
+
* `migrate` sólo acepta --config; schema y out no son flags válidos en este
|
|
57
|
+
* comando, por eso únicamente reenviamos configPath cuando está presente.
|
|
47
58
|
*/
|
|
48
59
|
async migrate() {
|
|
49
|
-
|
|
60
|
+
const args = ["drizzle-kit", "migrate"];
|
|
61
|
+
if (this.config.configPath) args.push("--config", this.config.configPath);
|
|
62
|
+
await this.exec(args, "migrate");
|
|
50
63
|
}
|
|
51
64
|
/**
|
|
52
65
|
* Empuja el schema directamente a la base de datos (sin generar archivos de migración).
|
|
53
|
-
* Útil para desarrollo rápido.
|
|
66
|
+
* Útil para desarrollo rápido. `push` acepta --schema pero no --out.
|
|
54
67
|
*/
|
|
55
68
|
async push() {
|
|
56
|
-
|
|
69
|
+
const args = ["drizzle-kit", "push"];
|
|
70
|
+
if (this.config.schemaPath) args.push("--schema", this.config.schemaPath);
|
|
71
|
+
if (this.config.configPath) args.push("--config", this.config.configPath);
|
|
72
|
+
await this.exec(args, "push");
|
|
57
73
|
}
|
|
58
74
|
/**
|
|
59
75
|
* Elimina todas las tablas de la base de datos.
|
|
76
|
+
* `drop` acepta --out (dónde viven las migraciones) pero no --schema.
|
|
60
77
|
*/
|
|
61
78
|
async drop() {
|
|
62
|
-
|
|
79
|
+
const args = ["drizzle-kit", "drop"];
|
|
80
|
+
if (this.config.migrationsDir) args.push("--out", this.config.migrationsDir);
|
|
81
|
+
if (this.config.configPath) args.push("--config", this.config.configPath);
|
|
82
|
+
await this.exec(args, "drop");
|
|
63
83
|
}
|
|
64
84
|
async exec(args, operation) {
|
|
65
85
|
const env = {
|
|
@@ -80,7 +100,7 @@ var MigrationHelper = class {
|
|
|
80
100
|
if (stdout) this.app?.logger.info(stdout.trim());
|
|
81
101
|
if (exitCode !== 0) {
|
|
82
102
|
throw new MigrationError(`Migration ${operation} failed with exit code ${exitCode}`, {
|
|
83
|
-
context: { operation, exitCode, stderr: stderr.trim() }
|
|
103
|
+
context: { operation, exitCode, stderr: scrubCredentials(stderr.trim()) }
|
|
84
104
|
});
|
|
85
105
|
}
|
|
86
106
|
this.app?.logger.info(`Migration ${operation} completed successfully`);
|
|
@@ -110,11 +130,47 @@ function mapDialect(driver) {
|
|
|
110
130
|
}
|
|
111
131
|
|
|
112
132
|
// src/driver.ts
|
|
133
|
+
function scrubUrl(url) {
|
|
134
|
+
try {
|
|
135
|
+
const parsed = new URL(url);
|
|
136
|
+
if (parsed.username) parsed.username = "***";
|
|
137
|
+
if (parsed.password) parsed.password = "***";
|
|
138
|
+
return parsed.toString();
|
|
139
|
+
} catch {
|
|
140
|
+
return void 0;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
113
143
|
var DbDriver = class {
|
|
114
144
|
name = "db";
|
|
115
145
|
client;
|
|
116
146
|
db;
|
|
117
147
|
app;
|
|
148
|
+
onQuery;
|
|
149
|
+
/**
|
|
150
|
+
* Register an observability callback that receives every SQL statement (and
|
|
151
|
+
* its bound params) Drizzle executes. Must be called before {@link start},
|
|
152
|
+
* since Drizzle's logger is wired at connection time. A throwing callback is
|
|
153
|
+
* swallowed so observability never breaks a real query.
|
|
154
|
+
*/
|
|
155
|
+
setOnQuery(onQuery) {
|
|
156
|
+
this.onQuery = onQuery;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Build the Drizzle `logger` option that forwards to {@link onQuery} when a
|
|
160
|
+
* hook is registered, or `undefined` to leave Drizzle's default logging off.
|
|
161
|
+
*/
|
|
162
|
+
buildLogger() {
|
|
163
|
+
const hook = this.onQuery;
|
|
164
|
+
if (!hook) return void 0;
|
|
165
|
+
return {
|
|
166
|
+
logQuery: (query, params) => {
|
|
167
|
+
try {
|
|
168
|
+
hook(query, params);
|
|
169
|
+
} catch {
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
118
174
|
async init(app) {
|
|
119
175
|
this.app = app;
|
|
120
176
|
app.context.set("db", this);
|
|
@@ -127,28 +183,35 @@ var DbDriver = class {
|
|
|
127
183
|
return;
|
|
128
184
|
}
|
|
129
185
|
this.app.logger.info(`Initializing DB driver: ${config.driver}`);
|
|
186
|
+
const logger = this.buildLogger();
|
|
130
187
|
try {
|
|
131
188
|
switch (config.driver) {
|
|
132
|
-
case "postgres":
|
|
133
|
-
|
|
134
|
-
this.
|
|
189
|
+
case "postgres": {
|
|
190
|
+
const client = postgres(config.url);
|
|
191
|
+
this.client = client;
|
|
192
|
+
this.db = drizzle(client, { logger });
|
|
135
193
|
break;
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
194
|
+
}
|
|
195
|
+
case "mysql": {
|
|
196
|
+
const client = mysql.createPool(config.url);
|
|
197
|
+
this.client = client;
|
|
198
|
+
this.db = drizzleMysql(client, { logger });
|
|
139
199
|
break;
|
|
200
|
+
}
|
|
140
201
|
case "sqlite": {
|
|
141
202
|
const { Database } = await import("bun:sqlite");
|
|
142
203
|
const { drizzle: drizzleSqlite } = await import("drizzle-orm/bun-sqlite");
|
|
143
|
-
|
|
144
|
-
this.
|
|
204
|
+
const client = new Database(config.url);
|
|
205
|
+
this.client = client;
|
|
206
|
+
this.db = drizzleSqlite(client, { logger });
|
|
145
207
|
break;
|
|
146
208
|
}
|
|
147
209
|
case "libsql": {
|
|
148
210
|
const { createClient } = await import("@libsql/client");
|
|
149
211
|
const { drizzle: drizzleLibsql } = await import("drizzle-orm/libsql");
|
|
150
|
-
|
|
151
|
-
this.
|
|
212
|
+
const client = createClient({ url: config.url, authToken: config.authToken });
|
|
213
|
+
this.client = client;
|
|
214
|
+
this.db = drizzleLibsql(client, { logger });
|
|
152
215
|
break;
|
|
153
216
|
}
|
|
154
217
|
default:
|
|
@@ -161,9 +224,13 @@ var DbDriver = class {
|
|
|
161
224
|
} catch (error) {
|
|
162
225
|
if (error instanceof DriverError) throw error;
|
|
163
226
|
this.app.logger.error({ error }, "Failed to connect to DB");
|
|
227
|
+
const safeUrl = scrubUrl(config.url);
|
|
164
228
|
throw new ConnectionError("Failed to connect to DB", {
|
|
165
229
|
cause: error instanceof Error ? error : new Error(String(error)),
|
|
166
|
-
context: {
|
|
230
|
+
context: {
|
|
231
|
+
driver: config.driver,
|
|
232
|
+
...safeUrl !== void 0 ? { url: safeUrl } : {}
|
|
233
|
+
}
|
|
167
234
|
});
|
|
168
235
|
}
|
|
169
236
|
}
|
|
@@ -188,13 +255,63 @@ var DbDriver = class {
|
|
|
188
255
|
);
|
|
189
256
|
await helper.migrate();
|
|
190
257
|
}
|
|
258
|
+
/**
|
|
259
|
+
* Run `fn` inside a database transaction, delegating to Drizzle's
|
|
260
|
+
* `db.transaction`. Callers receive the transaction-scoped db handle instead
|
|
261
|
+
* of reaching into the raw `db`. The dialect union means `tx` is typed as
|
|
262
|
+
* {@link IskraDrizzleTx}; narrow by dialect if you need dialect-specific APIs.
|
|
263
|
+
* Failures are wrapped in {@link QueryError} (Drizzle rolls back on throw).
|
|
264
|
+
*/
|
|
265
|
+
async transaction(fn) {
|
|
266
|
+
if (!this.db) {
|
|
267
|
+
throw new QueryError("Cannot run transaction: DB is not started", {
|
|
268
|
+
context: { driver: this.app?.config.db?.driver }
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
try {
|
|
272
|
+
return await this.db.transaction((tx) => fn(tx));
|
|
273
|
+
} catch (error) {
|
|
274
|
+
if (error instanceof QueryError) throw error;
|
|
275
|
+
throw new QueryError("Transaction failed", {
|
|
276
|
+
cause: error instanceof Error ? error : new Error(String(error)),
|
|
277
|
+
context: { driver: this.app?.config.db?.driver }
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Liveness probe for readiness checks (e.g. web-kit's addReadinessCheck /
|
|
283
|
+
* k8s readiness). Runs a trivial `SELECT 1` against the active dialect and
|
|
284
|
+
* resolves `true` on success or `false` on any failure — it never rejects.
|
|
285
|
+
*/
|
|
286
|
+
async ping() {
|
|
287
|
+
if (!this.db) return false;
|
|
288
|
+
try {
|
|
289
|
+
const handle = this.db;
|
|
290
|
+
if (typeof handle.run === "function") {
|
|
291
|
+
await handle.run(sql`SELECT 1`);
|
|
292
|
+
} else if (typeof handle.execute === "function") {
|
|
293
|
+
await handle.execute(sql`SELECT 1`);
|
|
294
|
+
} else {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
return true;
|
|
298
|
+
} catch {
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
191
302
|
async stop() {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
303
|
+
const client = this.client;
|
|
304
|
+
try {
|
|
305
|
+
if (client?.end) {
|
|
306
|
+
await client.end();
|
|
307
|
+
} else if (client?.close) {
|
|
308
|
+
client.close();
|
|
197
309
|
}
|
|
310
|
+
} catch (error) {
|
|
311
|
+
this.app?.logger.error({ error }, "Failed to close DB connection cleanly");
|
|
312
|
+
} finally {
|
|
313
|
+
this.client = void 0;
|
|
314
|
+
this.db = void 0;
|
|
198
315
|
}
|
|
199
316
|
}
|
|
200
317
|
};
|
|
@@ -222,6 +339,8 @@ export {
|
|
|
222
339
|
MigrationHelper,
|
|
223
340
|
QueryError,
|
|
224
341
|
createDrizzleConfig,
|
|
225
|
-
mapDialect
|
|
342
|
+
mapDialect,
|
|
343
|
+
scrubCredentials,
|
|
344
|
+
scrubUrl
|
|
226
345
|
};
|
|
227
346
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/driver.ts","../src/errors.ts","../src/migrations.ts","../../../node_modules/drizzle-kit/index.mjs","../src/drizzle.config.template.ts"],"sourcesContent":["import { type App, type Driver, type AppConfig, DriverError } from '@iskra-bun/core';\nimport { drizzle } from 'drizzle-orm/postgres-js';\nimport { drizzle as drizzleMysql } from 'drizzle-orm/mysql2';\nimport postgres from 'postgres';\nimport mysql from 'mysql2/promise';\nimport { ConnectionError } from './errors';\nimport { MigrationHelper, mapDialect } from './migrations';\n\nexport class DbDriver implements Driver {\n name = 'db';\n private client: any;\n public db: any;\n\n private app: App | undefined;\n\n async init(app: App) {\n this.app = app;\n app.context.set('db', this);\n }\n\n async start() {\n if (!this.app) return;\n const config = this.app.config.db;\n if (!config) {\n this.app!.logger.warn('No DB configuration found. Skipping DB initialization.');\n return;\n }\n\n this.app!.logger.info(`Initializing DB driver: ${config.driver}`);\n\n try {\n switch (config.driver) {\n case 'postgres':\n this.client = postgres(config.url);\n this.db = drizzle(this.client);\n break;\n case 'mysql':\n this.client = await mysql.createConnection(config.url);\n this.db = drizzleMysql(this.client);\n break;\n case 'sqlite': {\n const { Database } = await import(\"bun:sqlite\");\n const { drizzle: drizzleSqlite } = await import(\"drizzle-orm/bun-sqlite\");\n this.client = new Database(config.url);\n this.db = drizzleSqlite(this.client);\n break;\n }\n case 'libsql': {\n const { createClient } = await import('@libsql/client');\n const { drizzle: drizzleLibsql } = await import('drizzle-orm/libsql');\n this.client = createClient({ url: config.url, authToken: config.authToken });\n this.db = drizzleLibsql(this.client);\n break;\n }\n default:\n throw new DriverError(`Unsupported DB driver: ${config.driver}`, {\n code: 'DRIVER_START_FAILED',\n context: { driver: config.driver },\n });\n }\n this.app!.logger.info('DB connected successfully.');\n } catch (error) {\n if (error instanceof DriverError) throw error;\n this.app!.logger.error({ error }, 'Failed to connect to DB');\n throw new ConnectionError('Failed to connect to DB', {\n cause: error instanceof Error ? error : new Error(String(error)),\n context: { driver: config.driver, url: config.url },\n });\n }\n }\n\n /**\n * Ejecuta migraciones pendientes usando Drizzle Kit.\n */\n async runMigrations(schemaPath: string, migrationsDir: string = './drizzle'): Promise<void> {\n if (!this.app?.config.db) {\n throw new DriverError('Cannot run migrations: no DB configuration found', {\n code: 'DRIVER_START_FAILED',\n });\n }\n\n const config = this.app.config.db;\n const helper = new MigrationHelper(\n {\n dialect: mapDialect(config.driver),\n dbUrl: config.url,\n schemaPath,\n migrationsDir,\n },\n this.app,\n );\n\n await helper.migrate();\n }\n\n async stop() {\n if (this.client) {\n // Close connections based on client type\n if (this.client.end) { // Postgres usage with postgres.js usually handles itself or has end. \n // mysql2 has end()\n await this.client.end();\n } else if (this.client.close) { // bun:sqlite / libsql\n this.client.close();\n }\n // postgres.js handles cleanup usually but explicit close might be needed depending on version/usage\n }\n }\n}\n","import { IskraError, ErrorCodes, type ErrorCode } from '@iskra-bun/core';\n\n// ─── Connection Error ────────────────────────────────────────────────────────\n\nexport class ConnectionError extends IskraError {\n constructor(message: string, options?: { cause?: Error; context?: Record<string, unknown> }) {\n super(message, { code: ErrorCodes.CONNECTION_ERROR, ...options });\n this.name = 'ConnectionError';\n }\n}\n\n// ─── Query Error ─────────────────────────────────────────────────────────────\n\nexport class QueryError extends IskraError {\n constructor(message: string, options?: { cause?: Error; context?: Record<string, unknown> }) {\n super(message, { code: ErrorCodes.QUERY_ERROR, ...options });\n this.name = 'QueryError';\n }\n}\n\n// ─── Migration Error ─────────────────────────────────────────────────────────\n\nexport class MigrationError extends IskraError {\n constructor(message: string, options?: { cause?: Error; context?: Record<string, unknown> }) {\n super(message, { code: ErrorCodes.MIGRATION_ERROR, ...options });\n this.name = 'MigrationError';\n }\n}\n","import { type App } from '@iskra-bun/core';\nimport { MigrationError } from './errors';\n\nexport interface MigrationConfig {\n /** Dialecto de la base de datos */\n dialect: 'postgresql' | 'mysql' | 'sqlite';\n /** URL de conexión a la base de datos */\n dbUrl: string;\n /** Ruta al archivo de schema Drizzle (ej: './src/db/schema.ts') */\n schemaPath: string;\n /** Directorio donde se generan las migraciones (ej: './drizzle') */\n migrationsDir: string;\n}\n\n/**\n * Helper para ejecutar migraciones de Drizzle Kit.\n * Usa `bunx drizzle-kit` como subproceso para generar y aplicar migraciones.\n */\nexport class MigrationHelper {\n private config: MigrationConfig;\n private app?: App;\n\n constructor(config: MigrationConfig, app?: App) {\n this.config = config;\n this.app = app;\n }\n\n /**\n * Genera archivos de migración basados en los cambios del schema.\n */\n async generate(name?: string): Promise<void> {\n const args = ['drizzle-kit', 'generate'];\n if (name) args.push('--name', name);\n await this.exec(args, 'generate');\n }\n\n /**\n * Aplica las migraciones pendientes a la base de datos.\n */\n async migrate(): Promise<void> {\n await this.exec(['drizzle-kit', 'migrate'], 'migrate');\n }\n\n /**\n * Empuja el schema directamente a la base de datos (sin generar archivos de migración).\n * Útil para desarrollo rápido.\n */\n async push(): Promise<void> {\n await this.exec(['drizzle-kit', 'push'], 'push');\n }\n\n /**\n * Elimina todas las tablas de la base de datos.\n */\n async drop(): Promise<void> {\n await this.exec(['drizzle-kit', 'drop'], 'drop');\n }\n\n private async exec(args: string[], operation: string): Promise<void> {\n const env: Record<string, string> = {\n ...process.env as Record<string, string>,\n DATABASE_URL: this.config.dbUrl,\n };\n\n this.app?.logger.info(`Running migration: ${operation}`);\n\n try {\n const proc = Bun.spawn(['bunx', ...args], {\n cwd: process.cwd(),\n env,\n stdout: 'pipe',\n stderr: 'pipe',\n });\n\n const exitCode = await proc.exited;\n const stdout = await new Response(proc.stdout).text();\n const stderr = await new Response(proc.stderr).text();\n\n if (stdout) this.app?.logger.info(stdout.trim());\n\n if (exitCode !== 0) {\n throw new MigrationError(`Migration ${operation} failed with exit code ${exitCode}`, {\n context: { operation, exitCode, stderr: stderr.trim() },\n });\n }\n\n this.app?.logger.info(`Migration ${operation} completed successfully`);\n } catch (error) {\n if (error instanceof MigrationError) throw error;\n throw new MigrationError(`Migration ${operation} failed`, {\n cause: error instanceof Error ? error : new Error(String(error)),\n context: { operation },\n });\n }\n }\n}\n\n/**\n * Mapea el driver de Iskra al dialecto de Drizzle Kit.\n */\nexport function mapDialect(driver: string): MigrationConfig['dialect'] {\n switch (driver) {\n case 'postgres':\n return 'postgresql';\n case 'mysql':\n return 'mysql';\n case 'sqlite':\n case 'libsql':\n return 'sqlite';\n default:\n throw new MigrationError(`Cannot map driver \"${driver}\" to a Drizzle dialect`, {\n context: { driver },\n });\n }\n}\n","// src/index.ts\nfunction defineConfig(config) {\n return config;\n}\nexport {\n defineConfig\n};\n","import { defineConfig } from 'drizzle-kit';\n\nexport interface DrizzleConfigOptions {\n /** Dialecto: 'postgresql', 'mysql', 'sqlite' */\n dialect: 'postgresql' | 'mysql' | 'sqlite';\n /** URL de conexión a la base de datos */\n dbUrl: string;\n /** Ruta al archivo de schema (ej: './src/db/schema.ts') */\n schemaPath: string;\n /** Directorio de migraciones (ej: './drizzle') */\n migrationsDir?: string;\n}\n\n/**\n * Crea una configuración de drizzle-kit reutilizable.\n *\n * Uso en tu proyecto:\n * ```ts\n * // drizzle.config.ts\n * import { createDrizzleConfig } from '@iskra-bun/db-kit';\n *\n * export default createDrizzleConfig({\n * dialect: 'sqlite',\n * dbUrl: process.env.DATABASE_URL || 'app.db',\n * schemaPath: './src/db/schema.ts',\n * });\n * ```\n */\nexport function createDrizzleConfig(options: DrizzleConfigOptions) {\n return defineConfig({\n dialect: options.dialect,\n schema: options.schemaPath,\n out: options.migrationsDir || './drizzle',\n dbCredentials: {\n url: options.dbUrl,\n },\n });\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/driver.ts","../src/errors.ts","../src/migrations.ts","../../../node_modules/drizzle-kit/index.mjs","../src/drizzle.config.template.ts"],"sourcesContent":["import { type App, type Driver, type AppConfig, DriverError } from '@iskra-bun/core';\nimport { drizzle, type PostgresJsDatabase } from 'drizzle-orm/postgres-js';\nimport { drizzle as drizzleMysql, type MySql2Database } from 'drizzle-orm/mysql2';\nimport type { BunSQLiteDatabase } from 'drizzle-orm/bun-sqlite';\nimport type { LibSQLDatabase } from 'drizzle-orm/libsql';\nimport postgres from 'postgres';\nimport mysql from 'mysql2/promise';\nimport { sql } from 'drizzle-orm';\nimport { ConnectionError, QueryError } from './errors';\nimport { MigrationHelper, mapDialect } from './migrations';\n\n/**\n * Observability callback invoked for every SQL statement Drizzle executes.\n * Receives the rendered query and its bound parameters.\n */\nexport type OnQueryHook = (query: string, params: unknown[]) => void;\n\n/**\n * The transaction handle passed to {@link DbDriver.transaction}. Drizzle types\n * the transaction object per dialect, so — like {@link IskraDrizzleDb} — this is\n * the union of the supported dialect databases for the same schema. Callers can\n * narrow by dialect if they need dialect-specific transaction APIs.\n */\nexport type IskraDrizzleTx<TSchema extends Record<string, unknown> = Record<string, never>> =\n IskraDrizzleDb<TSchema>;\n\n/**\n * The Drizzle database handle exposed by {@link DbDriver}, parameterized by the\n * caller's schema. Because the concrete dialect is chosen at runtime, this is a\n * union of the supported dialect databases — all four share the same\n * `TSchema extends Record<string, unknown> = Record<string, never>` parameter,\n * so passing a schema types `db.query.*` for opt-in callers while the default\n * `Record<string, never>` reproduces the historical untyped behavior.\n */\nexport type IskraDrizzleDb<TSchema extends Record<string, unknown> = Record<string, never>> =\n | PostgresJsDatabase<TSchema>\n | MySql2Database<TSchema>\n | BunSQLiteDatabase<TSchema>\n | LibSQLDatabase<TSchema>;\n\n/**\n * Redact username and password from a database URL so it is safe to log.\n * Returns the scrubbed URL string, or undefined if parsing fails.\n *\n * e.g. postgres://user:pass@host:5432/db → postgres://***:***@host:5432/db\n */\nexport function scrubUrl(url: string): string | undefined {\n try {\n const parsed = new URL(url);\n if (parsed.username) parsed.username = '***';\n if (parsed.password) parsed.password = '***';\n return parsed.toString();\n } catch {\n return undefined;\n }\n}\n\n/**\n * The minimal teardown surface {@link DbDriver.stop} probes on the underlying\n * client. postgres-js and mysql2 expose async `end()`; bun:sqlite and libsql\n * expose synchronous `close()`. Typed as optional so either shape satisfies it.\n */\ninterface DbClient {\n end?(): Promise<void>;\n close?(): void;\n}\n\nexport class DbDriver<TSchema extends Record<string, unknown> = Record<string, never>> implements Driver {\n name = 'db';\n private client: DbClient | undefined;\n public db: IskraDrizzleDb<TSchema> | undefined;\n\n private app: App | undefined;\n private onQuery: OnQueryHook | undefined;\n\n /**\n * Register an observability callback that receives every SQL statement (and\n * its bound params) Drizzle executes. Must be called before {@link start},\n * since Drizzle's logger is wired at connection time. A throwing callback is\n * swallowed so observability never breaks a real query.\n */\n setOnQuery(onQuery: OnQueryHook): void {\n this.onQuery = onQuery;\n }\n\n /**\n * Build the Drizzle `logger` option that forwards to {@link onQuery} when a\n * hook is registered, or `undefined` to leave Drizzle's default logging off.\n */\n private buildLogger(): { logQuery(query: string, params: unknown[]): void } | undefined {\n const hook = this.onQuery;\n if (!hook) return undefined;\n return {\n logQuery: (query: string, params: unknown[]) => {\n try {\n hook(query, params);\n } catch {\n // Observability must never break the underlying query.\n }\n },\n };\n }\n\n async init(app: App) {\n this.app = app;\n app.context.set('db', this);\n }\n\n async start() {\n if (!this.app) return;\n const config = this.app.config.db;\n if (!config) {\n this.app!.logger.warn('No DB configuration found. Skipping DB initialization.');\n return;\n }\n\n this.app!.logger.info(`Initializing DB driver: ${config.driver}`);\n\n const logger = this.buildLogger();\n\n try {\n switch (config.driver) {\n case 'postgres': {\n const client = postgres(config.url);\n this.client = client;\n this.db = drizzle<TSchema>(client, { logger });\n break;\n }\n case 'mysql': {\n const client = mysql.createPool(config.url);\n this.client = client;\n this.db = drizzleMysql<TSchema>(client, { logger });\n break;\n }\n case 'sqlite': {\n const { Database } = await import(\"bun:sqlite\");\n const { drizzle: drizzleSqlite } = await import(\"drizzle-orm/bun-sqlite\");\n const client = new Database(config.url);\n this.client = client;\n this.db = drizzleSqlite<TSchema>(client, { logger });\n break;\n }\n case 'libsql': {\n const { createClient } = await import('@libsql/client');\n const { drizzle: drizzleLibsql } = await import('drizzle-orm/libsql');\n const client = createClient({ url: config.url, authToken: config.authToken });\n this.client = client;\n this.db = drizzleLibsql<TSchema>(client, { logger });\n break;\n }\n default:\n throw new DriverError(`Unsupported DB driver: ${config.driver}`, {\n code: 'DRIVER_START_FAILED',\n context: { driver: config.driver },\n });\n }\n this.app!.logger.info('DB connected successfully.');\n } catch (error) {\n if (error instanceof DriverError) throw error;\n this.app!.logger.error({ error }, 'Failed to connect to DB');\n const safeUrl = scrubUrl(config.url);\n throw new ConnectionError('Failed to connect to DB', {\n cause: error instanceof Error ? error : new Error(String(error)),\n context: {\n driver: config.driver,\n ...(safeUrl !== undefined ? { url: safeUrl } : {}),\n },\n });\n }\n }\n\n /**\n * Ejecuta migraciones pendientes usando Drizzle Kit.\n */\n async runMigrations(schemaPath: string, migrationsDir: string = './drizzle'): Promise<void> {\n if (!this.app?.config.db) {\n throw new DriverError('Cannot run migrations: no DB configuration found', {\n code: 'DRIVER_START_FAILED',\n });\n }\n\n const config = this.app.config.db;\n const helper = new MigrationHelper(\n {\n dialect: mapDialect(config.driver),\n dbUrl: config.url,\n schemaPath,\n migrationsDir,\n },\n this.app,\n );\n\n await helper.migrate();\n }\n\n /**\n * Run `fn` inside a database transaction, delegating to Drizzle's\n * `db.transaction`. Callers receive the transaction-scoped db handle instead\n * of reaching into the raw `db`. The dialect union means `tx` is typed as\n * {@link IskraDrizzleTx}; narrow by dialect if you need dialect-specific APIs.\n * Failures are wrapped in {@link QueryError} (Drizzle rolls back on throw).\n */\n async transaction<R>(fn: (tx: IskraDrizzleTx<TSchema>) => Promise<R>): Promise<R> {\n if (!this.db) {\n throw new QueryError('Cannot run transaction: DB is not started', {\n context: { driver: this.app?.config.db?.driver },\n });\n }\n try {\n // The dialect-specific `transaction` overloads do not unify across the\n // union, so we route through the runtime method with a faithful cast\n // of the public handle types.\n return await (this.db as IskraDrizzleDb<TSchema> & {\n transaction(cb: (tx: IskraDrizzleTx<TSchema>) => Promise<R>): Promise<R>;\n }).transaction((tx) => fn(tx));\n } catch (error) {\n if (error instanceof QueryError) throw error;\n throw new QueryError('Transaction failed', {\n cause: error instanceof Error ? error : new Error(String(error)),\n context: { driver: this.app?.config.db?.driver },\n });\n }\n }\n\n /**\n * Liveness probe for readiness checks (e.g. web-kit's addReadinessCheck /\n * k8s readiness). Runs a trivial `SELECT 1` against the active dialect and\n * resolves `true` on success or `false` on any failure — it never rejects.\n */\n async ping(): Promise<boolean> {\n if (!this.db) return false;\n try {\n // bun-sqlite exposes the synchronous `.run()`; postgres-js, mysql2 and\n // libsql expose the async `.execute()`. Prefer whichever exists.\n const handle = this.db as {\n run?(query: unknown): unknown;\n execute?(query: unknown): Promise<unknown>;\n };\n if (typeof handle.run === 'function') {\n await handle.run(sql`SELECT 1`);\n } else if (typeof handle.execute === 'function') {\n await handle.execute(sql`SELECT 1`);\n } else {\n return false;\n }\n return true;\n } catch {\n return false;\n }\n }\n\n async stop() {\n const client = this.client;\n try {\n // postgres-js / mysql2 expose async end(); bun:sqlite / libsql expose\n // synchronous close(). Probe for whichever this client provides.\n if (client?.end) {\n await client.end();\n } else if (client?.close) {\n client.close();\n }\n } catch (error) {\n // A throwing teardown must never abort the orderly shutdown of other\n // drivers; log and continue so the handles below are still cleared.\n this.app?.logger.error({ error }, 'Failed to close DB connection cleanly');\n } finally {\n // Null the handles so a post-stop ping()/transaction() hits the\n // not-started guard instead of an already-closed connection.\n this.client = undefined;\n this.db = undefined;\n }\n }\n}\n","import { IskraError, ErrorCodes, type ErrorCode } from '@iskra-bun/core';\n\n// ─── Connection Error ────────────────────────────────────────────────────────\n\nexport class ConnectionError extends IskraError {\n constructor(message: string, options?: { cause?: Error; context?: Record<string, unknown> }) {\n super(message, { code: ErrorCodes.CONNECTION_ERROR, ...options });\n this.name = 'ConnectionError';\n }\n}\n\n// ─── Query Error ─────────────────────────────────────────────────────────────\n\nexport class QueryError extends IskraError {\n constructor(message: string, options?: { cause?: Error; context?: Record<string, unknown> }) {\n super(message, { code: ErrorCodes.QUERY_ERROR, ...options });\n this.name = 'QueryError';\n }\n}\n\n// ─── Migration Error ─────────────────────────────────────────────────────────\n\nexport class MigrationError extends IskraError {\n constructor(message: string, options?: { cause?: Error; context?: Record<string, unknown> }) {\n super(message, { code: ErrorCodes.MIGRATION_ERROR, ...options });\n this.name = 'MigrationError';\n }\n}\n","import { type App } from '@iskra-bun/core';\nimport { MigrationError } from './errors';\n\n/**\n * Redacta credenciales `//user:pass@host` embebidas en texto arbitrario (p. ej.\n * el stderr de drizzle-kit, que suele imprimir la cadena de conexión completa al\n * fallar). A diferencia de `scrubUrl`, opera sobre texto libre y no requiere que\n * el contenido sea una URL parseable, dejando intacto el resto del diagnóstico.\n *\n * e.g. \"... postgres://user:pass@host:5432/db\" → \"... postgres://***:***@host:5432/db\"\n */\nexport function scrubCredentials(text: string): string {\n return text.replace(/(\\/\\/)[^/\\s:@]+:[^/\\s@]+@/g, '$1***:***@');\n}\n\nexport interface MigrationConfig {\n /** Dialecto de la base de datos */\n dialect: 'postgresql' | 'mysql' | 'sqlite';\n /** URL de conexión a la base de datos */\n dbUrl: string;\n /** Ruta al archivo de schema Drizzle (ej: './src/db/schema.ts') */\n schemaPath: string;\n /** Directorio donde se generan las migraciones (ej: './drizzle') */\n migrationsDir: string;\n /** Ruta opcional a un drizzle.config.ts; cuando se define se pasa como --config. */\n configPath?: string;\n}\n\n/**\n * Helper para ejecutar migraciones de Drizzle Kit.\n * Usa `bunx drizzle-kit` como subproceso para generar y aplicar migraciones.\n */\nexport class MigrationHelper {\n private config: MigrationConfig;\n private app?: App;\n\n constructor(config: MigrationConfig, app?: App) {\n this.config = config;\n this.app = app;\n }\n\n /**\n * Genera archivos de migración basados en los cambios del schema.\n * drizzle-kit generate soporta --schema y --out, así que ambos se reenvían\n * desde la config (antes se ignoraban silenciosamente).\n */\n async generate(name?: string): Promise<void> {\n const args = ['drizzle-kit', 'generate'];\n if (this.config.schemaPath) args.push('--schema', this.config.schemaPath);\n if (this.config.migrationsDir) args.push('--out', this.config.migrationsDir);\n if (name) args.push('--name', name);\n if (this.config.configPath) args.push('--config', this.config.configPath);\n await this.exec(args, 'generate');\n }\n\n /**\n * Aplica las migraciones pendientes a la base de datos.\n * `migrate` sólo acepta --config; schema y out no son flags válidos en este\n * comando, por eso únicamente reenviamos configPath cuando está presente.\n */\n async migrate(): Promise<void> {\n const args = ['drizzle-kit', 'migrate'];\n if (this.config.configPath) args.push('--config', this.config.configPath);\n await this.exec(args, 'migrate');\n }\n\n /**\n * Empuja el schema directamente a la base de datos (sin generar archivos de migración).\n * Útil para desarrollo rápido. `push` acepta --schema pero no --out.\n */\n async push(): Promise<void> {\n const args = ['drizzle-kit', 'push'];\n if (this.config.schemaPath) args.push('--schema', this.config.schemaPath);\n if (this.config.configPath) args.push('--config', this.config.configPath);\n await this.exec(args, 'push');\n }\n\n /**\n * Elimina todas las tablas de la base de datos.\n * `drop` acepta --out (dónde viven las migraciones) pero no --schema.\n */\n async drop(): Promise<void> {\n const args = ['drizzle-kit', 'drop'];\n if (this.config.migrationsDir) args.push('--out', this.config.migrationsDir);\n if (this.config.configPath) args.push('--config', this.config.configPath);\n await this.exec(args, 'drop');\n }\n\n private async exec(args: string[], operation: string): Promise<void> {\n const env: Record<string, string> = {\n ...process.env as Record<string, string>,\n DATABASE_URL: this.config.dbUrl,\n };\n\n this.app?.logger.info(`Running migration: ${operation}`);\n\n try {\n const proc = Bun.spawn(['bunx', ...args], {\n cwd: process.cwd(),\n env,\n stdout: 'pipe',\n stderr: 'pipe',\n });\n\n const exitCode = await proc.exited;\n const stdout = await new Response(proc.stdout).text();\n const stderr = await new Response(proc.stderr).text();\n\n if (stdout) this.app?.logger.info(stdout.trim());\n\n if (exitCode !== 0) {\n throw new MigrationError(`Migration ${operation} failed with exit code ${exitCode}`, {\n context: { operation, exitCode, stderr: scrubCredentials(stderr.trim()) },\n });\n }\n\n this.app?.logger.info(`Migration ${operation} completed successfully`);\n } catch (error) {\n if (error instanceof MigrationError) throw error;\n throw new MigrationError(`Migration ${operation} failed`, {\n cause: error instanceof Error ? error : new Error(String(error)),\n context: { operation },\n });\n }\n }\n}\n\n/**\n * Mapea el driver de Iskra al dialecto de Drizzle Kit.\n */\nexport function mapDialect(driver: string): MigrationConfig['dialect'] {\n switch (driver) {\n case 'postgres':\n return 'postgresql';\n case 'mysql':\n return 'mysql';\n case 'sqlite':\n case 'libsql':\n return 'sqlite';\n default:\n throw new MigrationError(`Cannot map driver \"${driver}\" to a Drizzle dialect`, {\n context: { driver },\n });\n }\n}\n","// src/index.ts\nfunction defineConfig(config) {\n return config;\n}\nexport {\n defineConfig\n};\n","import { defineConfig } from 'drizzle-kit';\n\nexport interface DrizzleConfigOptions {\n /** Dialecto: 'postgresql', 'mysql', 'sqlite' */\n dialect: 'postgresql' | 'mysql' | 'sqlite';\n /** URL de conexión a la base de datos */\n dbUrl: string;\n /** Ruta al archivo de schema (ej: './src/db/schema.ts') */\n schemaPath: string;\n /** Directorio de migraciones (ej: './drizzle') */\n migrationsDir?: string;\n}\n\n/**\n * Crea una configuración de drizzle-kit reutilizable.\n *\n * Uso en tu proyecto:\n * ```ts\n * // drizzle.config.ts\n * import { createDrizzleConfig } from '@iskra-bun/db-kit';\n *\n * export default createDrizzleConfig({\n * dialect: 'sqlite',\n * dbUrl: process.env.DATABASE_URL || 'app.db',\n * schemaPath: './src/db/schema.ts',\n * });\n * ```\n */\nexport function createDrizzleConfig(options: DrizzleConfigOptions) {\n return defineConfig({\n dialect: options.dialect,\n schema: options.schemaPath,\n out: options.migrationsDir || './drizzle',\n dbCredentials: {\n url: options.dbUrl,\n },\n });\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iskra-bun/db-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Base de datos SQL de Iskra con Drizzle ORM (PostgreSQL, MySQL, SQLite, LibSQL).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"iskra",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"build": "tsup --config ../../tsup.config.ts"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@iskra-bun/core": "0.1.
|
|
52
|
+
"@iskra-bun/core": "0.1.1",
|
|
53
53
|
"drizzle-orm": "^0.30.0",
|
|
54
54
|
"postgres": "^3.4.4",
|
|
55
55
|
"mysql2": "^3.9.2",
|
package/src/driver.ts
CHANGED
|
@@ -1,17 +1,105 @@
|
|
|
1
1
|
import { type App, type Driver, type AppConfig, DriverError } from '@iskra-bun/core';
|
|
2
|
-
import { drizzle } from 'drizzle-orm/postgres-js';
|
|
3
|
-
import { drizzle as drizzleMysql } from 'drizzle-orm/mysql2';
|
|
2
|
+
import { drizzle, type PostgresJsDatabase } from 'drizzle-orm/postgres-js';
|
|
3
|
+
import { drizzle as drizzleMysql, type MySql2Database } from 'drizzle-orm/mysql2';
|
|
4
|
+
import type { BunSQLiteDatabase } from 'drizzle-orm/bun-sqlite';
|
|
5
|
+
import type { LibSQLDatabase } from 'drizzle-orm/libsql';
|
|
4
6
|
import postgres from 'postgres';
|
|
5
7
|
import mysql from 'mysql2/promise';
|
|
6
|
-
import {
|
|
8
|
+
import { sql } from 'drizzle-orm';
|
|
9
|
+
import { ConnectionError, QueryError } from './errors';
|
|
7
10
|
import { MigrationHelper, mapDialect } from './migrations';
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Observability callback invoked for every SQL statement Drizzle executes.
|
|
14
|
+
* Receives the rendered query and its bound parameters.
|
|
15
|
+
*/
|
|
16
|
+
export type OnQueryHook = (query: string, params: unknown[]) => void;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The transaction handle passed to {@link DbDriver.transaction}. Drizzle types
|
|
20
|
+
* the transaction object per dialect, so — like {@link IskraDrizzleDb} — this is
|
|
21
|
+
* the union of the supported dialect databases for the same schema. Callers can
|
|
22
|
+
* narrow by dialect if they need dialect-specific transaction APIs.
|
|
23
|
+
*/
|
|
24
|
+
export type IskraDrizzleTx<TSchema extends Record<string, unknown> = Record<string, never>> =
|
|
25
|
+
IskraDrizzleDb<TSchema>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The Drizzle database handle exposed by {@link DbDriver}, parameterized by the
|
|
29
|
+
* caller's schema. Because the concrete dialect is chosen at runtime, this is a
|
|
30
|
+
* union of the supported dialect databases — all four share the same
|
|
31
|
+
* `TSchema extends Record<string, unknown> = Record<string, never>` parameter,
|
|
32
|
+
* so passing a schema types `db.query.*` for opt-in callers while the default
|
|
33
|
+
* `Record<string, never>` reproduces the historical untyped behavior.
|
|
34
|
+
*/
|
|
35
|
+
export type IskraDrizzleDb<TSchema extends Record<string, unknown> = Record<string, never>> =
|
|
36
|
+
| PostgresJsDatabase<TSchema>
|
|
37
|
+
| MySql2Database<TSchema>
|
|
38
|
+
| BunSQLiteDatabase<TSchema>
|
|
39
|
+
| LibSQLDatabase<TSchema>;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Redact username and password from a database URL so it is safe to log.
|
|
43
|
+
* Returns the scrubbed URL string, or undefined if parsing fails.
|
|
44
|
+
*
|
|
45
|
+
* e.g. postgres://user:pass@host:5432/db → postgres://***:***@host:5432/db
|
|
46
|
+
*/
|
|
47
|
+
export function scrubUrl(url: string): string | undefined {
|
|
48
|
+
try {
|
|
49
|
+
const parsed = new URL(url);
|
|
50
|
+
if (parsed.username) parsed.username = '***';
|
|
51
|
+
if (parsed.password) parsed.password = '***';
|
|
52
|
+
return parsed.toString();
|
|
53
|
+
} catch {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The minimal teardown surface {@link DbDriver.stop} probes on the underlying
|
|
60
|
+
* client. postgres-js and mysql2 expose async `end()`; bun:sqlite and libsql
|
|
61
|
+
* expose synchronous `close()`. Typed as optional so either shape satisfies it.
|
|
62
|
+
*/
|
|
63
|
+
interface DbClient {
|
|
64
|
+
end?(): Promise<void>;
|
|
65
|
+
close?(): void;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export class DbDriver<TSchema extends Record<string, unknown> = Record<string, never>> implements Driver {
|
|
10
69
|
name = 'db';
|
|
11
|
-
private client:
|
|
12
|
-
public db:
|
|
70
|
+
private client: DbClient | undefined;
|
|
71
|
+
public db: IskraDrizzleDb<TSchema> | undefined;
|
|
13
72
|
|
|
14
73
|
private app: App | undefined;
|
|
74
|
+
private onQuery: OnQueryHook | undefined;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Register an observability callback that receives every SQL statement (and
|
|
78
|
+
* its bound params) Drizzle executes. Must be called before {@link start},
|
|
79
|
+
* since Drizzle's logger is wired at connection time. A throwing callback is
|
|
80
|
+
* swallowed so observability never breaks a real query.
|
|
81
|
+
*/
|
|
82
|
+
setOnQuery(onQuery: OnQueryHook): void {
|
|
83
|
+
this.onQuery = onQuery;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Build the Drizzle `logger` option that forwards to {@link onQuery} when a
|
|
88
|
+
* hook is registered, or `undefined` to leave Drizzle's default logging off.
|
|
89
|
+
*/
|
|
90
|
+
private buildLogger(): { logQuery(query: string, params: unknown[]): void } | undefined {
|
|
91
|
+
const hook = this.onQuery;
|
|
92
|
+
if (!hook) return undefined;
|
|
93
|
+
return {
|
|
94
|
+
logQuery: (query: string, params: unknown[]) => {
|
|
95
|
+
try {
|
|
96
|
+
hook(query, params);
|
|
97
|
+
} catch {
|
|
98
|
+
// Observability must never break the underlying query.
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
15
103
|
|
|
16
104
|
async init(app: App) {
|
|
17
105
|
this.app = app;
|
|
@@ -28,28 +116,36 @@ export class DbDriver implements Driver {
|
|
|
28
116
|
|
|
29
117
|
this.app!.logger.info(`Initializing DB driver: ${config.driver}`);
|
|
30
118
|
|
|
119
|
+
const logger = this.buildLogger();
|
|
120
|
+
|
|
31
121
|
try {
|
|
32
122
|
switch (config.driver) {
|
|
33
|
-
case 'postgres':
|
|
34
|
-
|
|
35
|
-
this.
|
|
123
|
+
case 'postgres': {
|
|
124
|
+
const client = postgres(config.url);
|
|
125
|
+
this.client = client;
|
|
126
|
+
this.db = drizzle<TSchema>(client, { logger });
|
|
36
127
|
break;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
128
|
+
}
|
|
129
|
+
case 'mysql': {
|
|
130
|
+
const client = mysql.createPool(config.url);
|
|
131
|
+
this.client = client;
|
|
132
|
+
this.db = drizzleMysql<TSchema>(client, { logger });
|
|
40
133
|
break;
|
|
134
|
+
}
|
|
41
135
|
case 'sqlite': {
|
|
42
136
|
const { Database } = await import("bun:sqlite");
|
|
43
137
|
const { drizzle: drizzleSqlite } = await import("drizzle-orm/bun-sqlite");
|
|
44
|
-
|
|
45
|
-
this.
|
|
138
|
+
const client = new Database(config.url);
|
|
139
|
+
this.client = client;
|
|
140
|
+
this.db = drizzleSqlite<TSchema>(client, { logger });
|
|
46
141
|
break;
|
|
47
142
|
}
|
|
48
143
|
case 'libsql': {
|
|
49
144
|
const { createClient } = await import('@libsql/client');
|
|
50
145
|
const { drizzle: drizzleLibsql } = await import('drizzle-orm/libsql');
|
|
51
|
-
|
|
52
|
-
this.
|
|
146
|
+
const client = createClient({ url: config.url, authToken: config.authToken });
|
|
147
|
+
this.client = client;
|
|
148
|
+
this.db = drizzleLibsql<TSchema>(client, { logger });
|
|
53
149
|
break;
|
|
54
150
|
}
|
|
55
151
|
default:
|
|
@@ -62,9 +158,13 @@ export class DbDriver implements Driver {
|
|
|
62
158
|
} catch (error) {
|
|
63
159
|
if (error instanceof DriverError) throw error;
|
|
64
160
|
this.app!.logger.error({ error }, 'Failed to connect to DB');
|
|
161
|
+
const safeUrl = scrubUrl(config.url);
|
|
65
162
|
throw new ConnectionError('Failed to connect to DB', {
|
|
66
163
|
cause: error instanceof Error ? error : new Error(String(error)),
|
|
67
|
-
context: {
|
|
164
|
+
context: {
|
|
165
|
+
driver: config.driver,
|
|
166
|
+
...(safeUrl !== undefined ? { url: safeUrl } : {}),
|
|
167
|
+
},
|
|
68
168
|
});
|
|
69
169
|
}
|
|
70
170
|
}
|
|
@@ -93,16 +193,81 @@ export class DbDriver implements Driver {
|
|
|
93
193
|
await helper.migrate();
|
|
94
194
|
}
|
|
95
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Run `fn` inside a database transaction, delegating to Drizzle's
|
|
198
|
+
* `db.transaction`. Callers receive the transaction-scoped db handle instead
|
|
199
|
+
* of reaching into the raw `db`. The dialect union means `tx` is typed as
|
|
200
|
+
* {@link IskraDrizzleTx}; narrow by dialect if you need dialect-specific APIs.
|
|
201
|
+
* Failures are wrapped in {@link QueryError} (Drizzle rolls back on throw).
|
|
202
|
+
*/
|
|
203
|
+
async transaction<R>(fn: (tx: IskraDrizzleTx<TSchema>) => Promise<R>): Promise<R> {
|
|
204
|
+
if (!this.db) {
|
|
205
|
+
throw new QueryError('Cannot run transaction: DB is not started', {
|
|
206
|
+
context: { driver: this.app?.config.db?.driver },
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
// The dialect-specific `transaction` overloads do not unify across the
|
|
211
|
+
// union, so we route through the runtime method with a faithful cast
|
|
212
|
+
// of the public handle types.
|
|
213
|
+
return await (this.db as IskraDrizzleDb<TSchema> & {
|
|
214
|
+
transaction(cb: (tx: IskraDrizzleTx<TSchema>) => Promise<R>): Promise<R>;
|
|
215
|
+
}).transaction((tx) => fn(tx));
|
|
216
|
+
} catch (error) {
|
|
217
|
+
if (error instanceof QueryError) throw error;
|
|
218
|
+
throw new QueryError('Transaction failed', {
|
|
219
|
+
cause: error instanceof Error ? error : new Error(String(error)),
|
|
220
|
+
context: { driver: this.app?.config.db?.driver },
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Liveness probe for readiness checks (e.g. web-kit's addReadinessCheck /
|
|
227
|
+
* k8s readiness). Runs a trivial `SELECT 1` against the active dialect and
|
|
228
|
+
* resolves `true` on success or `false` on any failure — it never rejects.
|
|
229
|
+
*/
|
|
230
|
+
async ping(): Promise<boolean> {
|
|
231
|
+
if (!this.db) return false;
|
|
232
|
+
try {
|
|
233
|
+
// bun-sqlite exposes the synchronous `.run()`; postgres-js, mysql2 and
|
|
234
|
+
// libsql expose the async `.execute()`. Prefer whichever exists.
|
|
235
|
+
const handle = this.db as {
|
|
236
|
+
run?(query: unknown): unknown;
|
|
237
|
+
execute?(query: unknown): Promise<unknown>;
|
|
238
|
+
};
|
|
239
|
+
if (typeof handle.run === 'function') {
|
|
240
|
+
await handle.run(sql`SELECT 1`);
|
|
241
|
+
} else if (typeof handle.execute === 'function') {
|
|
242
|
+
await handle.execute(sql`SELECT 1`);
|
|
243
|
+
} else {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
return true;
|
|
247
|
+
} catch {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
96
252
|
async stop() {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
253
|
+
const client = this.client;
|
|
254
|
+
try {
|
|
255
|
+
// postgres-js / mysql2 expose async end(); bun:sqlite / libsql expose
|
|
256
|
+
// synchronous close(). Probe for whichever this client provides.
|
|
257
|
+
if (client?.end) {
|
|
258
|
+
await client.end();
|
|
259
|
+
} else if (client?.close) {
|
|
260
|
+
client.close();
|
|
104
261
|
}
|
|
105
|
-
|
|
262
|
+
} catch (error) {
|
|
263
|
+
// A throwing teardown must never abort the orderly shutdown of other
|
|
264
|
+
// drivers; log and continue so the handles below are still cleared.
|
|
265
|
+
this.app?.logger.error({ error }, 'Failed to close DB connection cleanly');
|
|
266
|
+
} finally {
|
|
267
|
+
// Null the handles so a post-stop ping()/transaction() hits the
|
|
268
|
+
// not-started guard instead of an already-closed connection.
|
|
269
|
+
this.client = undefined;
|
|
270
|
+
this.db = undefined;
|
|
106
271
|
}
|
|
107
272
|
}
|
|
108
273
|
}
|
package/src/migrations.ts
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import { type App } from '@iskra-bun/core';
|
|
2
2
|
import { MigrationError } from './errors';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Redacta credenciales `//user:pass@host` embebidas en texto arbitrario (p. ej.
|
|
6
|
+
* el stderr de drizzle-kit, que suele imprimir la cadena de conexión completa al
|
|
7
|
+
* fallar). A diferencia de `scrubUrl`, opera sobre texto libre y no requiere que
|
|
8
|
+
* el contenido sea una URL parseable, dejando intacto el resto del diagnóstico.
|
|
9
|
+
*
|
|
10
|
+
* e.g. "... postgres://user:pass@host:5432/db" → "... postgres://***:***@host:5432/db"
|
|
11
|
+
*/
|
|
12
|
+
export function scrubCredentials(text: string): string {
|
|
13
|
+
return text.replace(/(\/\/)[^/\s:@]+:[^/\s@]+@/g, '$1***:***@');
|
|
14
|
+
}
|
|
15
|
+
|
|
4
16
|
export interface MigrationConfig {
|
|
5
17
|
/** Dialecto de la base de datos */
|
|
6
18
|
dialect: 'postgresql' | 'mysql' | 'sqlite';
|
|
@@ -10,6 +22,8 @@ export interface MigrationConfig {
|
|
|
10
22
|
schemaPath: string;
|
|
11
23
|
/** Directorio donde se generan las migraciones (ej: './drizzle') */
|
|
12
24
|
migrationsDir: string;
|
|
25
|
+
/** Ruta opcional a un drizzle.config.ts; cuando se define se pasa como --config. */
|
|
26
|
+
configPath?: string;
|
|
13
27
|
}
|
|
14
28
|
|
|
15
29
|
/**
|
|
@@ -27,33 +41,49 @@ export class MigrationHelper {
|
|
|
27
41
|
|
|
28
42
|
/**
|
|
29
43
|
* Genera archivos de migración basados en los cambios del schema.
|
|
44
|
+
* drizzle-kit generate soporta --schema y --out, así que ambos se reenvían
|
|
45
|
+
* desde la config (antes se ignoraban silenciosamente).
|
|
30
46
|
*/
|
|
31
47
|
async generate(name?: string): Promise<void> {
|
|
32
48
|
const args = ['drizzle-kit', 'generate'];
|
|
49
|
+
if (this.config.schemaPath) args.push('--schema', this.config.schemaPath);
|
|
50
|
+
if (this.config.migrationsDir) args.push('--out', this.config.migrationsDir);
|
|
33
51
|
if (name) args.push('--name', name);
|
|
52
|
+
if (this.config.configPath) args.push('--config', this.config.configPath);
|
|
34
53
|
await this.exec(args, 'generate');
|
|
35
54
|
}
|
|
36
55
|
|
|
37
56
|
/**
|
|
38
57
|
* Aplica las migraciones pendientes a la base de datos.
|
|
58
|
+
* `migrate` sólo acepta --config; schema y out no son flags válidos en este
|
|
59
|
+
* comando, por eso únicamente reenviamos configPath cuando está presente.
|
|
39
60
|
*/
|
|
40
61
|
async migrate(): Promise<void> {
|
|
41
|
-
|
|
62
|
+
const args = ['drizzle-kit', 'migrate'];
|
|
63
|
+
if (this.config.configPath) args.push('--config', this.config.configPath);
|
|
64
|
+
await this.exec(args, 'migrate');
|
|
42
65
|
}
|
|
43
66
|
|
|
44
67
|
/**
|
|
45
68
|
* Empuja el schema directamente a la base de datos (sin generar archivos de migración).
|
|
46
|
-
* Útil para desarrollo rápido.
|
|
69
|
+
* Útil para desarrollo rápido. `push` acepta --schema pero no --out.
|
|
47
70
|
*/
|
|
48
71
|
async push(): Promise<void> {
|
|
49
|
-
|
|
72
|
+
const args = ['drizzle-kit', 'push'];
|
|
73
|
+
if (this.config.schemaPath) args.push('--schema', this.config.schemaPath);
|
|
74
|
+
if (this.config.configPath) args.push('--config', this.config.configPath);
|
|
75
|
+
await this.exec(args, 'push');
|
|
50
76
|
}
|
|
51
77
|
|
|
52
78
|
/**
|
|
53
79
|
* Elimina todas las tablas de la base de datos.
|
|
80
|
+
* `drop` acepta --out (dónde viven las migraciones) pero no --schema.
|
|
54
81
|
*/
|
|
55
82
|
async drop(): Promise<void> {
|
|
56
|
-
|
|
83
|
+
const args = ['drizzle-kit', 'drop'];
|
|
84
|
+
if (this.config.migrationsDir) args.push('--out', this.config.migrationsDir);
|
|
85
|
+
if (this.config.configPath) args.push('--config', this.config.configPath);
|
|
86
|
+
await this.exec(args, 'drop');
|
|
57
87
|
}
|
|
58
88
|
|
|
59
89
|
private async exec(args: string[], operation: string): Promise<void> {
|
|
@@ -80,7 +110,7 @@ export class MigrationHelper {
|
|
|
80
110
|
|
|
81
111
|
if (exitCode !== 0) {
|
|
82
112
|
throw new MigrationError(`Migration ${operation} failed with exit code ${exitCode}`, {
|
|
83
|
-
context: { operation, exitCode, stderr: stderr.trim() },
|
|
113
|
+
context: { operation, exitCode, stderr: scrubCredentials(stderr.trim()) },
|
|
84
114
|
});
|
|
85
115
|
}
|
|
86
116
|
|