nexusql 0.6.1 → 0.7.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/README.md +19 -14
- package/dist/cli.js +59 -19
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +54 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -60,16 +60,16 @@ nexusql migrate
|
|
|
60
60
|
|
|
61
61
|
## Commands
|
|
62
62
|
|
|
63
|
-
| Command
|
|
64
|
-
|
|
65
|
-
| `nexusql init`
|
|
66
|
-
| `nexusql gen`
|
|
67
|
-
| `nexusql migrate`
|
|
68
|
-
| `nexusql up`
|
|
69
|
-
| `nexusql down`
|
|
70
|
-
| `nexusql status`
|
|
71
|
-
| `nexusql types`
|
|
72
|
-
| `nexusql mark-applied <version>` | Mark migration as applied without running
|
|
63
|
+
| Command | Description |
|
|
64
|
+
| -------------------------------- | ------------------------------------------- |
|
|
65
|
+
| `nexusql init` | Initialize configuration and directories |
|
|
66
|
+
| `nexusql gen` | Generate migration SQL from DBML schema |
|
|
67
|
+
| `nexusql migrate` | Interactive: generate → create file → apply |
|
|
68
|
+
| `nexusql up` | Apply all pending migrations |
|
|
69
|
+
| `nexusql down` | Rollback applied migrations |
|
|
70
|
+
| `nexusql status` | Show migration status |
|
|
71
|
+
| `nexusql types` | Generate TypeScript definitions |
|
|
72
|
+
| `nexusql mark-applied <version>` | Mark migration as applied without running |
|
|
73
73
|
|
|
74
74
|
### `nexusql gen`
|
|
75
75
|
|
|
@@ -79,6 +79,7 @@ Generate migration SQL by diffing your current database against the DBML schema.
|
|
|
79
79
|
nexusql gen # Output to stdout
|
|
80
80
|
nexusql gen -o migration.sql # Write to file
|
|
81
81
|
nexusql gen -v # Verbose output
|
|
82
|
+
nexusql gen -v # Verbose output
|
|
82
83
|
```
|
|
83
84
|
|
|
84
85
|
### `nexusql types`
|
|
@@ -142,19 +143,23 @@ nexusql mark-applied 20240101120000
|
|
|
142
143
|
Create `nexusql.config.js` in your project root:
|
|
143
144
|
|
|
144
145
|
```js
|
|
145
|
-
|
|
146
|
+
module.exports = {
|
|
146
147
|
// Path to DBML schema file
|
|
147
|
-
|
|
148
|
+
dbmlFile: "./schema.dbml",
|
|
148
149
|
|
|
149
150
|
// Migration files directory
|
|
150
|
-
migrations:
|
|
151
|
+
migrations: "./db/migrations",
|
|
151
152
|
|
|
152
153
|
// PostgreSQL extensions to install in temp database
|
|
153
|
-
extensions: [
|
|
154
|
+
extensions: ["uuid-ossp", "vector"],
|
|
155
|
+
|
|
156
|
+
// Restrict to specific PostgreSQL schemas (optional, default: ['public'])
|
|
157
|
+
// targetDbSchemas: ['public'],
|
|
154
158
|
};
|
|
155
159
|
```
|
|
156
160
|
|
|
157
161
|
Environment variables:
|
|
162
|
+
|
|
158
163
|
- `DATABASE_URL` - PostgreSQL connection string
|
|
159
164
|
|
|
160
165
|
## Migration Format
|
package/dist/cli.js
CHANGED
|
@@ -3224,6 +3224,11 @@ function runMigra(fromUrl, toUrl, options = {}) {
|
|
|
3224
3224
|
if (options.unsafe) {
|
|
3225
3225
|
args.push("--unsafe");
|
|
3226
3226
|
}
|
|
3227
|
+
if (options.schemas?.length) {
|
|
3228
|
+
for (const schema of options.schemas) {
|
|
3229
|
+
args.push("--schema", schema);
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3227
3232
|
try {
|
|
3228
3233
|
const output = execFileSync("migra", args, {
|
|
3229
3234
|
encoding: "utf-8",
|
|
@@ -3251,9 +3256,10 @@ import { join } from "path";
|
|
|
3251
3256
|
import { pathToFileURL } from "url";
|
|
3252
3257
|
import { config as loadDotenv } from "dotenv";
|
|
3253
3258
|
var DEFAULT_CONFIG = {
|
|
3254
|
-
|
|
3259
|
+
dbmlFile: "./schema.dbml",
|
|
3255
3260
|
migrations: "./db/migrations",
|
|
3256
|
-
extensions: ["uuid-ossp"]
|
|
3261
|
+
extensions: ["uuid-ossp"],
|
|
3262
|
+
targetDbSchemas: ["public"]
|
|
3257
3263
|
};
|
|
3258
3264
|
async function loadConfig(cwd = process.cwd()) {
|
|
3259
3265
|
loadDotenv();
|
|
@@ -3267,30 +3273,42 @@ async function loadConfig(cwd = process.cwd()) {
|
|
|
3267
3273
|
} catch {
|
|
3268
3274
|
}
|
|
3269
3275
|
}
|
|
3270
|
-
|
|
3276
|
+
const finalConfig = {
|
|
3271
3277
|
...DEFAULT_CONFIG,
|
|
3272
3278
|
...userConfig,
|
|
3273
3279
|
databaseUrl: process.env.DATABASE_URL || userConfig.databaseUrl
|
|
3274
3280
|
};
|
|
3281
|
+
if (userConfig.schema && !userConfig.dbmlFile) {
|
|
3282
|
+
console.warn(
|
|
3283
|
+
"Warning: 'schema' config option is deprecated. Use 'dbmlFile' instead."
|
|
3284
|
+
);
|
|
3285
|
+
finalConfig.dbmlFile = userConfig.schema;
|
|
3286
|
+
}
|
|
3287
|
+
return finalConfig;
|
|
3275
3288
|
}
|
|
3276
3289
|
function getDatabaseUrl(config) {
|
|
3277
3290
|
const url = config.databaseUrl || process.env.DATABASE_URL;
|
|
3278
3291
|
if (!url) {
|
|
3279
|
-
throw new Error(
|
|
3292
|
+
throw new Error(
|
|
3293
|
+
"DATABASE_URL not set. Set it in .env or nexusql.config.js"
|
|
3294
|
+
);
|
|
3280
3295
|
}
|
|
3281
3296
|
return url;
|
|
3282
3297
|
}
|
|
3283
3298
|
function generateConfigTemplate() {
|
|
3284
3299
|
return `/** @type {import('nexusql').NexusqlConfig} */
|
|
3285
|
-
|
|
3300
|
+
module.exports = {
|
|
3286
3301
|
// Path to your DBML schema file
|
|
3287
|
-
|
|
3302
|
+
dbmlFile: './schema.dbml',
|
|
3288
3303
|
|
|
3289
3304
|
// Directory for migration files
|
|
3290
3305
|
migrations: './db/migrations',
|
|
3291
3306
|
|
|
3292
3307
|
// PostgreSQL extensions to install in temp database
|
|
3293
3308
|
extensions: ['uuid-ossp'],
|
|
3309
|
+
|
|
3310
|
+
// Restrict to specific PostgreSQL schemas (optional, default: ['public'])
|
|
3311
|
+
// targetDbSchemas: ['public'],
|
|
3294
3312
|
};
|
|
3295
3313
|
`;
|
|
3296
3314
|
}
|
|
@@ -3323,7 +3341,7 @@ async function gen(options = {}) {
|
|
|
3323
3341
|
const db = new Database(databaseUrl);
|
|
3324
3342
|
const spinner = ora();
|
|
3325
3343
|
spinner.start("Reading DBML schema...");
|
|
3326
|
-
const schemaPath = resolve(config.
|
|
3344
|
+
const schemaPath = resolve(config.dbmlFile);
|
|
3327
3345
|
let dbmlContent;
|
|
3328
3346
|
try {
|
|
3329
3347
|
dbmlContent = readFileSync(schemaPath, "utf-8");
|
|
@@ -3359,10 +3377,17 @@ async function gen(options = {}) {
|
|
|
3359
3377
|
Tables in temp database: ${tables.join(", ")}`));
|
|
3360
3378
|
}
|
|
3361
3379
|
spinner.start("Generating migration diff...");
|
|
3380
|
+
const targetDbSchemas = config.targetDbSchemas;
|
|
3381
|
+
if (targetDbSchemas?.length) {
|
|
3382
|
+
console.log(
|
|
3383
|
+
source_default.dim(`
|
|
3384
|
+
Restricting to schemas: ${targetDbSchemas.join(", ")}`)
|
|
3385
|
+
);
|
|
3386
|
+
}
|
|
3362
3387
|
const migraResult = runMigra(
|
|
3363
3388
|
db.getConnectionUrl(),
|
|
3364
3389
|
tempDb.getConnectionUrl(),
|
|
3365
|
-
{ unsafe: true }
|
|
3390
|
+
{ unsafe: true, schemas: targetDbSchemas }
|
|
3366
3391
|
);
|
|
3367
3392
|
migrationSql = filterSchemaMigrations(migraResult.sql);
|
|
3368
3393
|
if (migraResult.hasChanges) {
|
|
@@ -3371,7 +3396,8 @@ Tables in temp database: ${tables.join(", ")}`));
|
|
|
3371
3396
|
spinner.info("No schema changes detected");
|
|
3372
3397
|
}
|
|
3373
3398
|
spinner.start("Checking comment changes...");
|
|
3374
|
-
|
|
3399
|
+
spinner.start("Checking comment changes...");
|
|
3400
|
+
const commentSql = await getCommentChanges(db, tempDb, targetDbSchemas);
|
|
3375
3401
|
if (commentSql) {
|
|
3376
3402
|
migrationSql = migrationSql ? `${migrationSql}
|
|
3377
3403
|
|
|
@@ -3385,8 +3411,8 @@ ${commentSql}`;
|
|
|
3385
3411
|
try {
|
|
3386
3412
|
await db.dropDatabase(tempDbName);
|
|
3387
3413
|
spinner.succeed("Cleaned up temp database");
|
|
3388
|
-
} catch {
|
|
3389
|
-
spinner.warn(
|
|
3414
|
+
} catch (error) {
|
|
3415
|
+
spinner.warn(`Failed to cleanup temp database: ${error}`);
|
|
3390
3416
|
}
|
|
3391
3417
|
}
|
|
3392
3418
|
const finalSql = migrationSql || "-- No changes detected";
|
|
@@ -3400,7 +3426,8 @@ Migration SQL written to: ${options.output}`));
|
|
|
3400
3426
|
}
|
|
3401
3427
|
return finalSql;
|
|
3402
3428
|
}
|
|
3403
|
-
async function getCommentChanges(currentDb, targetDb) {
|
|
3429
|
+
async function getCommentChanges(currentDb, targetDb, schemas) {
|
|
3430
|
+
const schemaFilter = schemas && schemas.length > 0 ? `AND c.table_schema IN (${schemas.map((s) => `'${s}'`).join(", ")})` : "";
|
|
3404
3431
|
const commentQuery = `
|
|
3405
3432
|
SELECT
|
|
3406
3433
|
format('COMMENT ON COLUMN %I.%I IS %L;',
|
|
@@ -3415,8 +3442,8 @@ async function getCommentChanges(currentDb, targetDb) {
|
|
|
3415
3442
|
JOIN pg_catalog.pg_class pc ON pc.relname = c.table_name
|
|
3416
3443
|
JOIN pg_catalog.pg_namespace pn ON pn.oid = pc.relnamespace AND pn.nspname = c.table_schema
|
|
3417
3444
|
LEFT JOIN pg_catalog.pg_description pgd ON pgd.objoid = pc.oid AND pgd.objsubid = c.ordinal_position
|
|
3418
|
-
WHERE c.
|
|
3419
|
-
|
|
3445
|
+
WHERE c.table_name != 'schema_migrations'
|
|
3446
|
+
${schemaFilter}
|
|
3420
3447
|
ORDER BY c.table_name, c.ordinal_position;
|
|
3421
3448
|
`;
|
|
3422
3449
|
const [currentComments, targetComments] = await Promise.all([
|
|
@@ -3685,7 +3712,11 @@ function getReadline() {
|
|
|
3685
3712
|
if (!rl) {
|
|
3686
3713
|
rl = readline.createInterface({
|
|
3687
3714
|
input: process.stdin,
|
|
3688
|
-
output: process.stdout
|
|
3715
|
+
output: process.stdout,
|
|
3716
|
+
terminal: process.stdin.isTTY ?? false
|
|
3717
|
+
});
|
|
3718
|
+
rl.on("close", () => {
|
|
3719
|
+
rl = null;
|
|
3689
3720
|
});
|
|
3690
3721
|
}
|
|
3691
3722
|
return rl;
|
|
@@ -3697,8 +3728,14 @@ function closePrompts() {
|
|
|
3697
3728
|
}
|
|
3698
3729
|
}
|
|
3699
3730
|
function question(query) {
|
|
3700
|
-
return new Promise((resolve3) => {
|
|
3701
|
-
getReadline()
|
|
3731
|
+
return new Promise((resolve3, reject) => {
|
|
3732
|
+
const readline2 = getReadline();
|
|
3733
|
+
const closeHandler = () => {
|
|
3734
|
+
reject(new Error("Readline closed unexpectedly"));
|
|
3735
|
+
};
|
|
3736
|
+
readline2.once("close", closeHandler);
|
|
3737
|
+
readline2.question(query, (answer) => {
|
|
3738
|
+
readline2.removeListener("close", closeHandler);
|
|
3702
3739
|
resolve3(answer);
|
|
3703
3740
|
});
|
|
3704
3741
|
});
|
|
@@ -4064,7 +4101,7 @@ async function types(options = {}) {
|
|
|
4064
4101
|
const config = await loadConfig();
|
|
4065
4102
|
const spinner = ora("Generating TypeScript definitions...").start();
|
|
4066
4103
|
try {
|
|
4067
|
-
const schemaPath = resolve2(config.
|
|
4104
|
+
const schemaPath = resolve2(config.dbmlFile);
|
|
4068
4105
|
const dbmlContent = readFileSync3(schemaPath, "utf-8");
|
|
4069
4106
|
const tsContent = generateTypes(dbmlContent);
|
|
4070
4107
|
const outputPath = options.output || "src/types/schema.d.ts";
|
|
@@ -4146,7 +4183,10 @@ program.command("mark-applied <version>").description("Mark a migration as appli
|
|
|
4146
4183
|
process.exit(1);
|
|
4147
4184
|
}
|
|
4148
4185
|
});
|
|
4149
|
-
program.command("types").description("Generate TypeScript definitions from DBML").option(
|
|
4186
|
+
program.command("types").description("Generate TypeScript definitions from DBML").option(
|
|
4187
|
+
"-o, --output <file>",
|
|
4188
|
+
"Output file path (default: src/types/schema.d.ts)"
|
|
4189
|
+
).action(async (options) => {
|
|
4150
4190
|
try {
|
|
4151
4191
|
await types(options);
|
|
4152
4192
|
} catch (error) {
|