@iskra-bun/db-kit 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ # @iskra-bun/db-kit
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Initial public release.
package/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # @iskra-bun/db-kit
2
+
3
+ Base de datos SQL de Iskra con [Drizzle ORM](https://orm.drizzle.team). Soporta PostgreSQL, MySQL, SQLite y LibSQL, e incluye CLI de migraciones.
4
+
5
+ ## Instalacion
6
+
7
+ ```bash
8
+ bun add @iskra-bun/db-kit @iskra-bun/core
9
+ ```
10
+
11
+ ## Uso rapido
12
+
13
+ ```typescript
14
+ import { App } from '@iskra-bun/core'
15
+ import { DbDriver } from '@iskra-bun/db-kit'
16
+
17
+ const app = new App({ name: 'mi-app' })
18
+ app.register(new DbDriver({ dialect: 'sqlite', url: 'app.db' }))
19
+
20
+ await app.start()
21
+ ```
22
+
23
+ ## Documentacion
24
+
25
+ Guia completa: [docs/db-kit.md](../../docs/db-kit.md) · Migraciones: [docs/migraciones.md](../../docs/migraciones.md)
26
+
27
+ ## Licencia
28
+
29
+ AGPL-3.0-or-later
@@ -0,0 +1,106 @@
1
+ import { Driver, App, IskraError } from '@iskra-bun/core';
2
+ import * as drizzle_kit from 'drizzle-kit';
3
+
4
+ declare class DbDriver implements Driver {
5
+ name: string;
6
+ private client;
7
+ db: any;
8
+ private app;
9
+ init(app: App): Promise<void>;
10
+ start(): Promise<void>;
11
+ /**
12
+ * Ejecuta migraciones pendientes usando Drizzle Kit.
13
+ */
14
+ runMigrations(schemaPath: string, migrationsDir?: string): Promise<void>;
15
+ stop(): Promise<void>;
16
+ }
17
+
18
+ declare class ConnectionError extends IskraError {
19
+ constructor(message: string, options?: {
20
+ cause?: Error;
21
+ context?: Record<string, unknown>;
22
+ });
23
+ }
24
+ declare class QueryError extends IskraError {
25
+ constructor(message: string, options?: {
26
+ cause?: Error;
27
+ context?: Record<string, unknown>;
28
+ });
29
+ }
30
+ declare class MigrationError extends IskraError {
31
+ constructor(message: string, options?: {
32
+ cause?: Error;
33
+ context?: Record<string, unknown>;
34
+ });
35
+ }
36
+
37
+ interface MigrationConfig {
38
+ /** Dialecto de la base de datos */
39
+ dialect: 'postgresql' | 'mysql' | 'sqlite';
40
+ /** URL de conexión a la base de datos */
41
+ dbUrl: string;
42
+ /** Ruta al archivo de schema Drizzle (ej: './src/db/schema.ts') */
43
+ schemaPath: string;
44
+ /** Directorio donde se generan las migraciones (ej: './drizzle') */
45
+ migrationsDir: string;
46
+ }
47
+ /**
48
+ * Helper para ejecutar migraciones de Drizzle Kit.
49
+ * Usa `bunx drizzle-kit` como subproceso para generar y aplicar migraciones.
50
+ */
51
+ declare class MigrationHelper {
52
+ private config;
53
+ private app?;
54
+ constructor(config: MigrationConfig, app?: App);
55
+ /**
56
+ * Genera archivos de migración basados en los cambios del schema.
57
+ */
58
+ generate(name?: string): Promise<void>;
59
+ /**
60
+ * Aplica las migraciones pendientes a la base de datos.
61
+ */
62
+ migrate(): Promise<void>;
63
+ /**
64
+ * Empuja el schema directamente a la base de datos (sin generar archivos de migración).
65
+ * Útil para desarrollo rápido.
66
+ */
67
+ push(): Promise<void>;
68
+ /**
69
+ * Elimina todas las tablas de la base de datos.
70
+ */
71
+ drop(): Promise<void>;
72
+ private exec;
73
+ }
74
+ /**
75
+ * Mapea el driver de Iskra al dialecto de Drizzle Kit.
76
+ */
77
+ declare function mapDialect(driver: string): MigrationConfig['dialect'];
78
+
79
+ interface DrizzleConfigOptions {
80
+ /** Dialecto: 'postgresql', 'mysql', 'sqlite' */
81
+ dialect: 'postgresql' | 'mysql' | 'sqlite';
82
+ /** URL de conexión a la base de datos */
83
+ dbUrl: string;
84
+ /** Ruta al archivo de schema (ej: './src/db/schema.ts') */
85
+ schemaPath: string;
86
+ /** Directorio de migraciones (ej: './drizzle') */
87
+ migrationsDir?: string;
88
+ }
89
+ /**
90
+ * Crea una configuración de drizzle-kit reutilizable.
91
+ *
92
+ * Uso en tu proyecto:
93
+ * ```ts
94
+ * // drizzle.config.ts
95
+ * import { createDrizzleConfig } from '@iskra-bun/db-kit';
96
+ *
97
+ * export default createDrizzleConfig({
98
+ * dialect: 'sqlite',
99
+ * dbUrl: process.env.DATABASE_URL || 'app.db',
100
+ * schemaPath: './src/db/schema.ts',
101
+ * });
102
+ * ```
103
+ */
104
+ declare function createDrizzleConfig(options: DrizzleConfigOptions): drizzle_kit.Config;
105
+
106
+ export { ConnectionError, DbDriver, type DrizzleConfigOptions, type MigrationConfig, MigrationError, MigrationHelper, QueryError, createDrizzleConfig, mapDialect };
package/dist/index.js ADDED
@@ -0,0 +1,227 @@
1
+ // src/driver.ts
2
+ import { DriverError } from "@iskra-bun/core";
3
+ import { drizzle } from "drizzle-orm/postgres-js";
4
+ import { drizzle as drizzleMysql } from "drizzle-orm/mysql2";
5
+ import postgres from "postgres";
6
+ import mysql from "mysql2/promise";
7
+
8
+ // src/errors.ts
9
+ import { IskraError, ErrorCodes } from "@iskra-bun/core";
10
+ var ConnectionError = class extends IskraError {
11
+ constructor(message, options) {
12
+ super(message, { code: ErrorCodes.CONNECTION_ERROR, ...options });
13
+ this.name = "ConnectionError";
14
+ }
15
+ };
16
+ var QueryError = class extends IskraError {
17
+ constructor(message, options) {
18
+ super(message, { code: ErrorCodes.QUERY_ERROR, ...options });
19
+ this.name = "QueryError";
20
+ }
21
+ };
22
+ var MigrationError = class extends IskraError {
23
+ constructor(message, options) {
24
+ super(message, { code: ErrorCodes.MIGRATION_ERROR, ...options });
25
+ this.name = "MigrationError";
26
+ }
27
+ };
28
+
29
+ // src/migrations.ts
30
+ var MigrationHelper = class {
31
+ config;
32
+ app;
33
+ constructor(config, app) {
34
+ this.config = config;
35
+ this.app = app;
36
+ }
37
+ /**
38
+ * Genera archivos de migración basados en los cambios del schema.
39
+ */
40
+ async generate(name) {
41
+ const args = ["drizzle-kit", "generate"];
42
+ if (name) args.push("--name", name);
43
+ await this.exec(args, "generate");
44
+ }
45
+ /**
46
+ * Aplica las migraciones pendientes a la base de datos.
47
+ */
48
+ async migrate() {
49
+ await this.exec(["drizzle-kit", "migrate"], "migrate");
50
+ }
51
+ /**
52
+ * Empuja el schema directamente a la base de datos (sin generar archivos de migración).
53
+ * Útil para desarrollo rápido.
54
+ */
55
+ async push() {
56
+ await this.exec(["drizzle-kit", "push"], "push");
57
+ }
58
+ /**
59
+ * Elimina todas las tablas de la base de datos.
60
+ */
61
+ async drop() {
62
+ await this.exec(["drizzle-kit", "drop"], "drop");
63
+ }
64
+ async exec(args, operation) {
65
+ const env = {
66
+ ...process.env,
67
+ DATABASE_URL: this.config.dbUrl
68
+ };
69
+ this.app?.logger.info(`Running migration: ${operation}`);
70
+ try {
71
+ const proc = Bun.spawn(["bunx", ...args], {
72
+ cwd: process.cwd(),
73
+ env,
74
+ stdout: "pipe",
75
+ stderr: "pipe"
76
+ });
77
+ const exitCode = await proc.exited;
78
+ const stdout = await new Response(proc.stdout).text();
79
+ const stderr = await new Response(proc.stderr).text();
80
+ if (stdout) this.app?.logger.info(stdout.trim());
81
+ if (exitCode !== 0) {
82
+ throw new MigrationError(`Migration ${operation} failed with exit code ${exitCode}`, {
83
+ context: { operation, exitCode, stderr: stderr.trim() }
84
+ });
85
+ }
86
+ this.app?.logger.info(`Migration ${operation} completed successfully`);
87
+ } catch (error) {
88
+ if (error instanceof MigrationError) throw error;
89
+ throw new MigrationError(`Migration ${operation} failed`, {
90
+ cause: error instanceof Error ? error : new Error(String(error)),
91
+ context: { operation }
92
+ });
93
+ }
94
+ }
95
+ };
96
+ function mapDialect(driver) {
97
+ switch (driver) {
98
+ case "postgres":
99
+ return "postgresql";
100
+ case "mysql":
101
+ return "mysql";
102
+ case "sqlite":
103
+ case "libsql":
104
+ return "sqlite";
105
+ default:
106
+ throw new MigrationError(`Cannot map driver "${driver}" to a Drizzle dialect`, {
107
+ context: { driver }
108
+ });
109
+ }
110
+ }
111
+
112
+ // src/driver.ts
113
+ var DbDriver = class {
114
+ name = "db";
115
+ client;
116
+ db;
117
+ app;
118
+ async init(app) {
119
+ this.app = app;
120
+ app.context.set("db", this);
121
+ }
122
+ async start() {
123
+ if (!this.app) return;
124
+ const config = this.app.config.db;
125
+ if (!config) {
126
+ this.app.logger.warn("No DB configuration found. Skipping DB initialization.");
127
+ return;
128
+ }
129
+ this.app.logger.info(`Initializing DB driver: ${config.driver}`);
130
+ try {
131
+ switch (config.driver) {
132
+ case "postgres":
133
+ this.client = postgres(config.url);
134
+ this.db = drizzle(this.client);
135
+ break;
136
+ case "mysql":
137
+ this.client = await mysql.createConnection(config.url);
138
+ this.db = drizzleMysql(this.client);
139
+ break;
140
+ case "sqlite": {
141
+ const { Database } = await import("bun:sqlite");
142
+ const { drizzle: drizzleSqlite } = await import("drizzle-orm/bun-sqlite");
143
+ this.client = new Database(config.url);
144
+ this.db = drizzleSqlite(this.client);
145
+ break;
146
+ }
147
+ case "libsql": {
148
+ const { createClient } = await import("@libsql/client");
149
+ const { drizzle: drizzleLibsql } = await import("drizzle-orm/libsql");
150
+ this.client = createClient({ url: config.url, authToken: config.authToken });
151
+ this.db = drizzleLibsql(this.client);
152
+ break;
153
+ }
154
+ default:
155
+ throw new DriverError(`Unsupported DB driver: ${config.driver}`, {
156
+ code: "DRIVER_START_FAILED",
157
+ context: { driver: config.driver }
158
+ });
159
+ }
160
+ this.app.logger.info("DB connected successfully.");
161
+ } catch (error) {
162
+ if (error instanceof DriverError) throw error;
163
+ this.app.logger.error({ error }, "Failed to connect to DB");
164
+ throw new ConnectionError("Failed to connect to DB", {
165
+ cause: error instanceof Error ? error : new Error(String(error)),
166
+ context: { driver: config.driver, url: config.url }
167
+ });
168
+ }
169
+ }
170
+ /**
171
+ * Ejecuta migraciones pendientes usando Drizzle Kit.
172
+ */
173
+ async runMigrations(schemaPath, migrationsDir = "./drizzle") {
174
+ if (!this.app?.config.db) {
175
+ throw new DriverError("Cannot run migrations: no DB configuration found", {
176
+ code: "DRIVER_START_FAILED"
177
+ });
178
+ }
179
+ const config = this.app.config.db;
180
+ const helper = new MigrationHelper(
181
+ {
182
+ dialect: mapDialect(config.driver),
183
+ dbUrl: config.url,
184
+ schemaPath,
185
+ migrationsDir
186
+ },
187
+ this.app
188
+ );
189
+ await helper.migrate();
190
+ }
191
+ async stop() {
192
+ if (this.client) {
193
+ if (this.client.end) {
194
+ await this.client.end();
195
+ } else if (this.client.close) {
196
+ this.client.close();
197
+ }
198
+ }
199
+ }
200
+ };
201
+
202
+ // ../../node_modules/drizzle-kit/index.mjs
203
+ function defineConfig(config) {
204
+ return config;
205
+ }
206
+
207
+ // src/drizzle.config.template.ts
208
+ function createDrizzleConfig(options) {
209
+ return defineConfig({
210
+ dialect: options.dialect,
211
+ schema: options.schemaPath,
212
+ out: options.migrationsDir || "./drizzle",
213
+ dbCredentials: {
214
+ url: options.dbUrl
215
+ }
216
+ });
217
+ }
218
+ export {
219
+ ConnectionError,
220
+ DbDriver,
221
+ MigrationError,
222
+ MigrationHelper,
223
+ QueryError,
224
+ createDrizzleConfig,
225
+ mapDialect
226
+ };
227
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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":[]}
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@iskra-bun/db-kit",
3
+ "version": "0.1.0",
4
+ "description": "Base de datos SQL de Iskra con Drizzle ORM (PostgreSQL, MySQL, SQLite, LibSQL).",
5
+ "keywords": [
6
+ "iskra",
7
+ "bun",
8
+ "typescript",
9
+ "drizzle",
10
+ "sql",
11
+ "database",
12
+ "postgresql",
13
+ "mysql",
14
+ "sqlite"
15
+ ],
16
+ "author": "Joan Lascano",
17
+ "license": "AGPL-3.0-or-later",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/fearful/iskra.git",
21
+ "directory": "packages/db-kit"
22
+ },
23
+ "homepage": "https://github.com/fearful/iskra/tree/main/packages/db-kit#readme",
24
+ "bugs": "https://github.com/fearful/iskra/issues",
25
+ "type": "module",
26
+ "main": "./dist/index.js",
27
+ "module": "./dist/index.js",
28
+ "types": "./dist/index.d.ts",
29
+ "exports": {
30
+ ".": {
31
+ "source": "./src/index.ts",
32
+ "bun": "./src/index.ts",
33
+ "types": "./dist/index.d.ts",
34
+ "import": "./dist/index.js",
35
+ "default": "./dist/index.js"
36
+ }
37
+ },
38
+ "files": [
39
+ "dist",
40
+ "src",
41
+ "README.md",
42
+ "CHANGELOG.md"
43
+ ],
44
+ "publishConfig": {
45
+ "access": "public"
46
+ },
47
+ "scripts": {
48
+ "test": "bun test",
49
+ "build": "tsup --config ../../tsup.config.ts"
50
+ },
51
+ "dependencies": {
52
+ "@iskra-bun/core": "0.1.0",
53
+ "drizzle-orm": "^0.30.0",
54
+ "postgres": "^3.4.4",
55
+ "mysql2": "^3.9.2",
56
+ "@libsql/client": "^0.6.0"
57
+ },
58
+ "devDependencies": {
59
+ "@types/node": "^22.10.2",
60
+ "drizzle-kit": "^0.30.0"
61
+ }
62
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * CLI para migraciones de Iskra DB Kit.
4
+ *
5
+ * Uso:
6
+ * bun run packages/db-kit/src/cli.ts generate [nombre]
7
+ * bun run packages/db-kit/src/cli.ts migrate
8
+ * bun run packages/db-kit/src/cli.ts push
9
+ * bun run packages/db-kit/src/cli.ts drop
10
+ *
11
+ * Requiere un archivo drizzle.config.ts en el directorio actual.
12
+ */
13
+
14
+ const [command, ...rest] = process.argv.slice(2);
15
+
16
+ const validCommands = ['generate', 'migrate', 'push', 'drop'];
17
+
18
+ if (!command || !validCommands.includes(command)) {
19
+ console.log(`
20
+ Iskra DB Kit — CLI de Migraciones
21
+
22
+ Uso:
23
+ bun run packages/db-kit/src/cli.ts <comando> [opciones]
24
+
25
+ Comandos:
26
+ generate [nombre] Genera archivos de migración a partir del schema
27
+ migrate Aplica migraciones pendientes
28
+ push Empuja el schema directo a la DB (sin migración)
29
+ drop Elimina todas las tablas
30
+ `);
31
+ process.exit(command ? 1 : 0);
32
+ }
33
+
34
+ const args = ['bunx', 'drizzle-kit', command, ...rest];
35
+
36
+ console.log(`> ${args.join(' ')}`);
37
+
38
+ const proc = Bun.spawn(args, {
39
+ cwd: process.cwd(),
40
+ stdout: 'inherit',
41
+ stderr: 'inherit',
42
+ env: process.env as Record<string, string>,
43
+ });
44
+
45
+ const exitCode = await proc.exited;
46
+ process.exit(exitCode);
47
+
48
+ export {};
package/src/driver.ts ADDED
@@ -0,0 +1,108 @@
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';
4
+ import postgres from 'postgres';
5
+ import mysql from 'mysql2/promise';
6
+ import { ConnectionError } from './errors';
7
+ import { MigrationHelper, mapDialect } from './migrations';
8
+
9
+ export class DbDriver implements Driver {
10
+ name = 'db';
11
+ private client: any;
12
+ public db: any;
13
+
14
+ private app: App | undefined;
15
+
16
+ async init(app: App) {
17
+ this.app = app;
18
+ app.context.set('db', this);
19
+ }
20
+
21
+ async start() {
22
+ if (!this.app) return;
23
+ const config = this.app.config.db;
24
+ if (!config) {
25
+ this.app!.logger.warn('No DB configuration found. Skipping DB initialization.');
26
+ return;
27
+ }
28
+
29
+ this.app!.logger.info(`Initializing DB driver: ${config.driver}`);
30
+
31
+ try {
32
+ switch (config.driver) {
33
+ case 'postgres':
34
+ this.client = postgres(config.url);
35
+ this.db = drizzle(this.client);
36
+ break;
37
+ case 'mysql':
38
+ this.client = await mysql.createConnection(config.url);
39
+ this.db = drizzleMysql(this.client);
40
+ break;
41
+ case 'sqlite': {
42
+ const { Database } = await import("bun:sqlite");
43
+ const { drizzle: drizzleSqlite } = await import("drizzle-orm/bun-sqlite");
44
+ this.client = new Database(config.url);
45
+ this.db = drizzleSqlite(this.client);
46
+ break;
47
+ }
48
+ case 'libsql': {
49
+ const { createClient } = await import('@libsql/client');
50
+ const { drizzle: drizzleLibsql } = await import('drizzle-orm/libsql');
51
+ this.client = createClient({ url: config.url, authToken: config.authToken });
52
+ this.db = drizzleLibsql(this.client);
53
+ break;
54
+ }
55
+ default:
56
+ throw new DriverError(`Unsupported DB driver: ${config.driver}`, {
57
+ code: 'DRIVER_START_FAILED',
58
+ context: { driver: config.driver },
59
+ });
60
+ }
61
+ this.app!.logger.info('DB connected successfully.');
62
+ } catch (error) {
63
+ if (error instanceof DriverError) throw error;
64
+ this.app!.logger.error({ error }, 'Failed to connect to DB');
65
+ throw new ConnectionError('Failed to connect to DB', {
66
+ cause: error instanceof Error ? error : new Error(String(error)),
67
+ context: { driver: config.driver, url: config.url },
68
+ });
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Ejecuta migraciones pendientes usando Drizzle Kit.
74
+ */
75
+ async runMigrations(schemaPath: string, migrationsDir: string = './drizzle'): Promise<void> {
76
+ if (!this.app?.config.db) {
77
+ throw new DriverError('Cannot run migrations: no DB configuration found', {
78
+ code: 'DRIVER_START_FAILED',
79
+ });
80
+ }
81
+
82
+ const config = this.app.config.db;
83
+ const helper = new MigrationHelper(
84
+ {
85
+ dialect: mapDialect(config.driver),
86
+ dbUrl: config.url,
87
+ schemaPath,
88
+ migrationsDir,
89
+ },
90
+ this.app,
91
+ );
92
+
93
+ await helper.migrate();
94
+ }
95
+
96
+ async stop() {
97
+ if (this.client) {
98
+ // Close connections based on client type
99
+ if (this.client.end) { // Postgres usage with postgres.js usually handles itself or has end.
100
+ // mysql2 has end()
101
+ await this.client.end();
102
+ } else if (this.client.close) { // bun:sqlite / libsql
103
+ this.client.close();
104
+ }
105
+ // postgres.js handles cleanup usually but explicit close might be needed depending on version/usage
106
+ }
107
+ }
108
+ }
@@ -0,0 +1,38 @@
1
+ import { defineConfig } from 'drizzle-kit';
2
+
3
+ export interface DrizzleConfigOptions {
4
+ /** Dialecto: 'postgresql', 'mysql', 'sqlite' */
5
+ dialect: 'postgresql' | 'mysql' | 'sqlite';
6
+ /** URL de conexión a la base de datos */
7
+ dbUrl: string;
8
+ /** Ruta al archivo de schema (ej: './src/db/schema.ts') */
9
+ schemaPath: string;
10
+ /** Directorio de migraciones (ej: './drizzle') */
11
+ migrationsDir?: string;
12
+ }
13
+
14
+ /**
15
+ * Crea una configuración de drizzle-kit reutilizable.
16
+ *
17
+ * Uso en tu proyecto:
18
+ * ```ts
19
+ * // drizzle.config.ts
20
+ * import { createDrizzleConfig } from '@iskra-bun/db-kit';
21
+ *
22
+ * export default createDrizzleConfig({
23
+ * dialect: 'sqlite',
24
+ * dbUrl: process.env.DATABASE_URL || 'app.db',
25
+ * schemaPath: './src/db/schema.ts',
26
+ * });
27
+ * ```
28
+ */
29
+ export function createDrizzleConfig(options: DrizzleConfigOptions) {
30
+ return defineConfig({
31
+ dialect: options.dialect,
32
+ schema: options.schemaPath,
33
+ out: options.migrationsDir || './drizzle',
34
+ dbCredentials: {
35
+ url: options.dbUrl,
36
+ },
37
+ });
38
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,28 @@
1
+ import { IskraError, ErrorCodes, type ErrorCode } from '@iskra-bun/core';
2
+
3
+ // ─── Connection Error ────────────────────────────────────────────────────────
4
+
5
+ export class ConnectionError extends IskraError {
6
+ constructor(message: string, options?: { cause?: Error; context?: Record<string, unknown> }) {
7
+ super(message, { code: ErrorCodes.CONNECTION_ERROR, ...options });
8
+ this.name = 'ConnectionError';
9
+ }
10
+ }
11
+
12
+ // ─── Query Error ─────────────────────────────────────────────────────────────
13
+
14
+ export class QueryError extends IskraError {
15
+ constructor(message: string, options?: { cause?: Error; context?: Record<string, unknown> }) {
16
+ super(message, { code: ErrorCodes.QUERY_ERROR, ...options });
17
+ this.name = 'QueryError';
18
+ }
19
+ }
20
+
21
+ // ─── Migration Error ─────────────────────────────────────────────────────────
22
+
23
+ export class MigrationError extends IskraError {
24
+ constructor(message: string, options?: { cause?: Error; context?: Record<string, unknown> }) {
25
+ super(message, { code: ErrorCodes.MIGRATION_ERROR, ...options });
26
+ this.name = 'MigrationError';
27
+ }
28
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './driver';
2
+ export * from './errors';
3
+ export * from './migrations';
4
+ export { createDrizzleConfig, type DrizzleConfigOptions } from './drizzle.config.template';
@@ -0,0 +1,115 @@
1
+ import { type App } from '@iskra-bun/core';
2
+ import { MigrationError } from './errors';
3
+
4
+ export interface MigrationConfig {
5
+ /** Dialecto de la base de datos */
6
+ dialect: 'postgresql' | 'mysql' | 'sqlite';
7
+ /** URL de conexión a la base de datos */
8
+ dbUrl: string;
9
+ /** Ruta al archivo de schema Drizzle (ej: './src/db/schema.ts') */
10
+ schemaPath: string;
11
+ /** Directorio donde se generan las migraciones (ej: './drizzle') */
12
+ migrationsDir: string;
13
+ }
14
+
15
+ /**
16
+ * Helper para ejecutar migraciones de Drizzle Kit.
17
+ * Usa `bunx drizzle-kit` como subproceso para generar y aplicar migraciones.
18
+ */
19
+ export class MigrationHelper {
20
+ private config: MigrationConfig;
21
+ private app?: App;
22
+
23
+ constructor(config: MigrationConfig, app?: App) {
24
+ this.config = config;
25
+ this.app = app;
26
+ }
27
+
28
+ /**
29
+ * Genera archivos de migración basados en los cambios del schema.
30
+ */
31
+ async generate(name?: string): Promise<void> {
32
+ const args = ['drizzle-kit', 'generate'];
33
+ if (name) args.push('--name', name);
34
+ await this.exec(args, 'generate');
35
+ }
36
+
37
+ /**
38
+ * Aplica las migraciones pendientes a la base de datos.
39
+ */
40
+ async migrate(): Promise<void> {
41
+ await this.exec(['drizzle-kit', 'migrate'], 'migrate');
42
+ }
43
+
44
+ /**
45
+ * Empuja el schema directamente a la base de datos (sin generar archivos de migración).
46
+ * Útil para desarrollo rápido.
47
+ */
48
+ async push(): Promise<void> {
49
+ await this.exec(['drizzle-kit', 'push'], 'push');
50
+ }
51
+
52
+ /**
53
+ * Elimina todas las tablas de la base de datos.
54
+ */
55
+ async drop(): Promise<void> {
56
+ await this.exec(['drizzle-kit', 'drop'], 'drop');
57
+ }
58
+
59
+ private async exec(args: string[], operation: string): Promise<void> {
60
+ const env: Record<string, string> = {
61
+ ...process.env as Record<string, string>,
62
+ DATABASE_URL: this.config.dbUrl,
63
+ };
64
+
65
+ this.app?.logger.info(`Running migration: ${operation}`);
66
+
67
+ try {
68
+ const proc = Bun.spawn(['bunx', ...args], {
69
+ cwd: process.cwd(),
70
+ env,
71
+ stdout: 'pipe',
72
+ stderr: 'pipe',
73
+ });
74
+
75
+ const exitCode = await proc.exited;
76
+ const stdout = await new Response(proc.stdout).text();
77
+ const stderr = await new Response(proc.stderr).text();
78
+
79
+ if (stdout) this.app?.logger.info(stdout.trim());
80
+
81
+ if (exitCode !== 0) {
82
+ throw new MigrationError(`Migration ${operation} failed with exit code ${exitCode}`, {
83
+ context: { operation, exitCode, stderr: stderr.trim() },
84
+ });
85
+ }
86
+
87
+ this.app?.logger.info(`Migration ${operation} completed successfully`);
88
+ } catch (error) {
89
+ if (error instanceof MigrationError) throw error;
90
+ throw new MigrationError(`Migration ${operation} failed`, {
91
+ cause: error instanceof Error ? error : new Error(String(error)),
92
+ context: { operation },
93
+ });
94
+ }
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Mapea el driver de Iskra al dialecto de Drizzle Kit.
100
+ */
101
+ export function mapDialect(driver: string): MigrationConfig['dialect'] {
102
+ switch (driver) {
103
+ case 'postgres':
104
+ return 'postgresql';
105
+ case 'mysql':
106
+ return 'mysql';
107
+ case 'sqlite':
108
+ case 'libsql':
109
+ return 'sqlite';
110
+ default:
111
+ throw new MigrationError(`Cannot map driver "${driver}" to a Drizzle dialect`, {
112
+ context: { driver },
113
+ });
114
+ }
115
+ }