@bdkinc/knex-ibmi 0.5.9 → 0.5.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -36,6 +36,9 @@ var import_fs2 = require("fs");
36
36
  var import_fs = __toESM(require("fs"));
37
37
  var import_path = __toESM(require("path"));
38
38
  var import_url = require("url");
39
+ function buildTsRuntimeHelpMessage(fileName) {
40
+ return `TypeScript migration '${fileName}' requires a TypeScript runtime loader. Run with a TS-capable runtime (for example: \`node --import tsx\`) or precompile migrations to JavaScript.`;
41
+ }
39
42
  var IBMiMigrationRunner = class {
40
43
  constructor(knex2, config) {
41
44
  __publicField(this, "knex");
@@ -45,9 +48,13 @@ var IBMiMigrationRunner = class {
45
48
  directory: "./migrations",
46
49
  tableName: "KNEX_MIGRATIONS",
47
50
  schemaName: void 0,
48
- extension: "js",
49
51
  ...config
50
52
  };
53
+ if (typeof config?.extension === "string") {
54
+ console.warn(
55
+ "\u26A0\uFE0F IBMiMigrationRunner config 'extension' is ignored for discovery. The runner always discovers .js/.ts/.mjs/.cjs migration files."
56
+ );
57
+ }
51
58
  }
52
59
  getFullTableName() {
53
60
  return this.config.schemaName ? `${this.config.schemaName}.${this.config.tableName}` : this.config.tableName;
@@ -94,7 +101,18 @@ var IBMiMigrationRunner = class {
94
101
  try {
95
102
  const migrationPath = this.getMigrationPath(migrationFile);
96
103
  const fileUrl = (0, import_url.pathToFileURL)(migrationPath).href;
97
- const migration = await import(`${fileUrl}?t=${Date.now()}`);
104
+ let migration;
105
+ try {
106
+ const moduleNs = await import(`${fileUrl}?t=${Date.now()}`);
107
+ migration = moduleNs.default ?? moduleNs;
108
+ } catch (importError) {
109
+ const isTsMigration = migrationFile.toLowerCase().endsWith(".ts");
110
+ const message = String(importError?.message || importError || "");
111
+ if (isTsMigration && (message.includes("Unknown file extension") || message.includes("Cannot use import statement") || message.includes("Unexpected token"))) {
112
+ throw new Error(buildTsRuntimeHelpMessage(migrationFile));
113
+ }
114
+ throw importError;
115
+ }
98
116
  if (!migration.up || typeof migration.up !== "function") {
99
117
  throw new Error(`Migration ${migrationFile} has no 'up' function`);
100
118
  }
@@ -142,7 +160,18 @@ var IBMiMigrationRunner = class {
142
160
  try {
143
161
  const migrationPath = this.getMigrationPath(migrationFile);
144
162
  const fileUrl = (0, import_url.pathToFileURL)(migrationPath).href;
145
- const migration = await import(`${fileUrl}?t=${Date.now()}`);
163
+ let migration;
164
+ try {
165
+ const moduleNs = await import(`${fileUrl}?t=${Date.now()}`);
166
+ migration = moduleNs.default ?? moduleNs;
167
+ } catch (importError) {
168
+ const isTsMigration = migrationFile.toLowerCase().endsWith(".ts");
169
+ const message = String(importError?.message || importError || "");
170
+ if (isTsMigration && (message.includes("Unknown file extension") || message.includes("Cannot use import statement") || message.includes("Unexpected token"))) {
171
+ throw new Error(buildTsRuntimeHelpMessage(migrationFile));
172
+ }
173
+ throw importError;
174
+ }
146
175
  if (migration.down && typeof migration.down === "function") {
147
176
  console.log(` \u26A1 Executing rollback...`);
148
177
  await migration.down(this.knex);
@@ -213,17 +242,12 @@ var IBMiMigrationRunner = class {
213
242
  }
214
243
  }
215
244
  getMigrationFiles() {
216
- const { directory, extension } = this.config;
245
+ const { directory } = this.config;
217
246
  if (!import_fs.default.existsSync(directory)) {
218
247
  throw new Error(`Migration directory does not exist: ${directory}`);
219
248
  }
220
249
  const validExtensions = ["js", "ts", "mjs", "cjs"];
221
- return import_fs.default.readdirSync(directory).filter((file) => {
222
- if (extension && extension !== "js") {
223
- return file.endsWith(`.${extension}`);
224
- }
225
- return validExtensions.some((ext) => file.endsWith(`.${ext}`));
226
- }).sort();
250
+ return import_fs.default.readdirSync(directory).filter((file) => validExtensions.some((ext) => file.endsWith(`.${ext}`))).sort();
227
251
  }
228
252
  getMigrationPath(filename) {
229
253
  return import_path.default.resolve(this.config.directory, filename);
@@ -258,17 +282,19 @@ function showHelp() {
258
282
  console.log(
259
283
  " --knexfile <file> - Specify knexfile path (default: ./knexfile.js)"
260
284
  );
285
+ console.log(" - Supports both .js and .ts knexfiles");
261
286
  console.log(
262
- " - Supports both .js and .ts knexfiles"
287
+ " -x <extension> - File extension for new migrations (js|ts)"
263
288
  );
264
289
  console.log(
265
- " -x <extension> - File extension for new migrations (js|ts)"
290
+ " --steps <number> - Number of migration batches to rollback"
266
291
  );
267
292
  console.log(" --help - Show this help message");
268
293
  console.log("");
269
294
  console.log("Examples:");
270
295
  console.log(" ibmi-migrations migrate:latest");
271
296
  console.log(" ibmi-migrations migrate:rollback");
297
+ console.log(" ibmi-migrations migrate:rollback --steps 2");
272
298
  console.log(" ibmi-migrations migrate:status --env production");
273
299
  console.log(" ibmi-migrations migrate:latest --knexfile knexfile.ts");
274
300
  console.log(" ibmi-migrations migrate:make create_users_table");
@@ -283,6 +309,7 @@ function parseArgs() {
283
309
  knexfile: "./knexfile.js",
284
310
  help: false,
285
311
  extension: "js",
312
+ steps: 1,
286
313
  migrationName: null
287
314
  };
288
315
  for (let i = 0; i < args.length; i++) {
@@ -298,8 +325,16 @@ function parseArgs() {
298
325
  } else if (arg === "-x" && args[i + 1]) {
299
326
  parsed.extension = args[i + 1];
300
327
  i++;
328
+ } else if ((arg === "--steps" || arg === "-s") && args[i + 1]) {
329
+ const parsedSteps = Number.parseInt(args[i + 1], 10);
330
+ if (!Number.isNaN(parsedSteps) && parsedSteps > 0) {
331
+ parsed.steps = parsedSteps;
332
+ }
333
+ i++;
301
334
  } else if (!parsed.command) {
302
335
  parsed.command = arg;
336
+ } else if (parsed.command === "migrate:rollback" && /^\d+$/.test(arg) && parsed.steps === 1) {
337
+ parsed.steps = Number.parseInt(arg, 10);
303
338
  } else if (parsed.command === "migrate:make" && !parsed.migrationName) {
304
339
  parsed.migrationName = arg;
305
340
  }
@@ -385,6 +420,14 @@ async function loadKnexfile(knexfilePath, environment) {
385
420
  return envConfig;
386
421
  } catch (error) {
387
422
  const message = error instanceof Error ? error.message : String(error);
423
+ const tsFile = knexfilePath.toLowerCase().endsWith(".ts");
424
+ if (tsFile && (message.includes("Unknown file extension") || message.includes("Cannot use import statement") || message.includes("Unexpected token"))) {
425
+ console.error("\u274C Failed to load TypeScript knexfile:", knexfilePath);
426
+ console.error(
427
+ "Run with a TS-capable runtime loader (for example: `node --import tsx`) or use a compiled JavaScript knexfile."
428
+ );
429
+ process.exit(1);
430
+ }
388
431
  console.error("\u274C Failed to load knexfile:", message);
389
432
  console.error(`Make sure you have a valid knexfile at: ${knexfilePath}`);
390
433
  process.exit(1);
@@ -455,8 +498,7 @@ async function main() {
455
498
  const migrationConfig = {
456
499
  directory: config.migrations?.directory || "./migrations",
457
500
  tableName: config.migrations?.tableName || "KNEX_MIGRATIONS",
458
- schemaName: config.migrations?.schemaName,
459
- extension: config.migrations?.extension || "js"
501
+ schemaName: config.migrations?.schemaName
460
502
  };
461
503
  const migrationRunner = createIBMiMigrationRunner(db, migrationConfig);
462
504
  switch (command) {
@@ -465,11 +507,8 @@ async function main() {
465
507
  await migrationRunner.latest();
466
508
  break;
467
509
  case "migrate:rollback":
468
- const steps = parseInt(
469
- process.argv.find((_arg, i) => process.argv[i - 1] === command)?.split(" ")[1] || "1"
470
- ) || 1;
471
- console.log(`\u{1F504} Rolling back ${steps} migration batch(es)...`);
472
- await migrationRunner.rollback(steps);
510
+ console.log(`\u{1F504} Rolling back ${args.steps} migration batch(es)...`);
511
+ await migrationRunner.rollback(args.steps);
473
512
  break;
474
513
  case "migrate:status":
475
514
  console.log("\u{1F4CB} Migration Status Report");
package/dist/index.d.mts CHANGED
@@ -5,6 +5,10 @@ interface IBMiMigrationConfig {
5
5
  directory: string;
6
6
  tableName: string;
7
7
  schemaName?: string;
8
+ /**
9
+ * Deprecated for runner discovery. Kept for backward compatibility.
10
+ * The runner discovers .js/.ts/.mjs/.cjs migrations regardless of this value.
11
+ */
8
12
  extension?: string;
9
13
  }
10
14
  declare class IBMiMigrationRunner {
package/dist/index.d.ts CHANGED
@@ -5,6 +5,10 @@ interface IBMiMigrationConfig {
5
5
  directory: string;
6
6
  tableName: string;
7
7
  schemaName?: string;
8
+ /**
9
+ * Deprecated for runner discovery. Kept for backward compatibility.
10
+ * The runner discovers .js/.ts/.mjs/.cjs migrations regardless of this value.
11
+ */
8
12
  extension?: string;
9
13
  }
10
14
  declare class IBMiMigrationRunner {
package/dist/index.js CHANGED
@@ -508,7 +508,8 @@ var IBMiQueryCompiler = class extends import_querycompiler.default {
508
508
  updateSql: baseUpdateSql,
509
509
  selectColumns,
510
510
  whereClause: where,
511
- tableName: this.tableName
511
+ tableName: this.tableName,
512
+ setBindingCount: updates.map((fragment) => (fragment.match(/\?/g) || []).length).reduce((sum, count) => sum + count, 0)
512
513
  }
513
514
  };
514
515
  }
@@ -573,6 +574,9 @@ var import_node_stream = require("stream");
573
574
  var import_fs = __toESM(require("fs"));
574
575
  var import_path = __toESM(require("path"));
575
576
  var import_url = require("url");
577
+ function buildTsRuntimeHelpMessage(fileName) {
578
+ return `TypeScript migration '${fileName}' requires a TypeScript runtime loader. Run with a TS-capable runtime (for example: \`node --import tsx\`) or precompile migrations to JavaScript.`;
579
+ }
576
580
  var IBMiMigrationRunner = class {
577
581
  constructor(knex2, config) {
578
582
  __publicField(this, "knex");
@@ -582,9 +586,13 @@ var IBMiMigrationRunner = class {
582
586
  directory: "./migrations",
583
587
  tableName: "KNEX_MIGRATIONS",
584
588
  schemaName: void 0,
585
- extension: "js",
586
589
  ...config
587
590
  };
591
+ if (typeof config?.extension === "string") {
592
+ console.warn(
593
+ "\u26A0\uFE0F IBMiMigrationRunner config 'extension' is ignored for discovery. The runner always discovers .js/.ts/.mjs/.cjs migration files."
594
+ );
595
+ }
588
596
  }
589
597
  getFullTableName() {
590
598
  return this.config.schemaName ? `${this.config.schemaName}.${this.config.tableName}` : this.config.tableName;
@@ -631,7 +639,18 @@ var IBMiMigrationRunner = class {
631
639
  try {
632
640
  const migrationPath = this.getMigrationPath(migrationFile);
633
641
  const fileUrl = (0, import_url.pathToFileURL)(migrationPath).href;
634
- const migration = await import(`${fileUrl}?t=${Date.now()}`);
642
+ let migration;
643
+ try {
644
+ const moduleNs = await import(`${fileUrl}?t=${Date.now()}`);
645
+ migration = moduleNs.default ?? moduleNs;
646
+ } catch (importError) {
647
+ const isTsMigration = migrationFile.toLowerCase().endsWith(".ts");
648
+ const message = String(importError?.message || importError || "");
649
+ if (isTsMigration && (message.includes("Unknown file extension") || message.includes("Cannot use import statement") || message.includes("Unexpected token"))) {
650
+ throw new Error(buildTsRuntimeHelpMessage(migrationFile));
651
+ }
652
+ throw importError;
653
+ }
635
654
  if (!migration.up || typeof migration.up !== "function") {
636
655
  throw new Error(`Migration ${migrationFile} has no 'up' function`);
637
656
  }
@@ -679,7 +698,18 @@ var IBMiMigrationRunner = class {
679
698
  try {
680
699
  const migrationPath = this.getMigrationPath(migrationFile);
681
700
  const fileUrl = (0, import_url.pathToFileURL)(migrationPath).href;
682
- const migration = await import(`${fileUrl}?t=${Date.now()}`);
701
+ let migration;
702
+ try {
703
+ const moduleNs = await import(`${fileUrl}?t=${Date.now()}`);
704
+ migration = moduleNs.default ?? moduleNs;
705
+ } catch (importError) {
706
+ const isTsMigration = migrationFile.toLowerCase().endsWith(".ts");
707
+ const message = String(importError?.message || importError || "");
708
+ if (isTsMigration && (message.includes("Unknown file extension") || message.includes("Cannot use import statement") || message.includes("Unexpected token"))) {
709
+ throw new Error(buildTsRuntimeHelpMessage(migrationFile));
710
+ }
711
+ throw importError;
712
+ }
683
713
  if (migration.down && typeof migration.down === "function") {
684
714
  console.log(` \u26A1 Executing rollback...`);
685
715
  await migration.down(this.knex);
@@ -750,17 +780,12 @@ var IBMiMigrationRunner = class {
750
780
  }
751
781
  }
752
782
  getMigrationFiles() {
753
- const { directory, extension } = this.config;
783
+ const { directory } = this.config;
754
784
  if (!import_fs.default.existsSync(directory)) {
755
785
  throw new Error(`Migration directory does not exist: ${directory}`);
756
786
  }
757
787
  const validExtensions = ["js", "ts", "mjs", "cjs"];
758
- return import_fs.default.readdirSync(directory).filter((file) => {
759
- if (extension && extension !== "js") {
760
- return file.endsWith(`.${extension}`);
761
- }
762
- return validExtensions.some((ext) => file.endsWith(`.${ext}`));
763
- }).sort();
788
+ return import_fs.default.readdirSync(directory).filter((file) => validExtensions.some((ext) => file.endsWith(`.${ext}`))).sort();
764
789
  }
765
790
  getMigrationPath(filename) {
766
791
  return import_path.default.resolve(this.config.directory, filename);
@@ -1004,7 +1029,13 @@ var DB2Client = class extends import_knex.default.Client {
1004
1029
  */
1005
1030
  async executeUpdateReturning(connection, obj) {
1006
1031
  const { _ibmiUpdateReturning } = obj;
1007
- const { updateSql, selectColumns, whereClause, tableName } = _ibmiUpdateReturning;
1032
+ const {
1033
+ updateSql,
1034
+ selectColumns,
1035
+ whereClause,
1036
+ tableName,
1037
+ setBindingCount
1038
+ } = _ibmiUpdateReturning;
1008
1039
  this.printDebug(
1009
1040
  "Executing UPDATE with returning using transaction approach"
1010
1041
  );
@@ -1016,10 +1047,8 @@ var DB2Client = class extends import_knex.default.Client {
1016
1047
  };
1017
1048
  await this.executeStatementQuery(connection, updateObj);
1018
1049
  const selectSql = whereClause ? `select ${selectColumns} from ${tableName} ${whereClause}` : `select ${selectColumns} from ${tableName}`;
1019
- const updateSqlParts = updateSql.split(" where ");
1020
- const setClausePart = updateSqlParts[0];
1021
- const setBindingCount = (setClausePart.match(/\?/g) || []).length;
1022
- const whereBindings = obj.bindings ? obj.bindings.slice(setBindingCount) : [];
1050
+ const inferredSetBindingCount = typeof setBindingCount === "number" ? setBindingCount : (updateSql.split(" where ")[0].match(/\?/g) || []).length;
1051
+ const whereBindings = obj.bindings ? obj.bindings.slice(inferredSetBindingCount) : [];
1023
1052
  const selectObj = {
1024
1053
  sql: selectSql,
1025
1054
  bindings: whereBindings,
@@ -1425,11 +1454,25 @@ var DB2Client = class extends import_knex.default.Client {
1425
1454
  validateResponse(obj) {
1426
1455
  if (!obj.response) {
1427
1456
  this.printDebug("response undefined " + this.safeStringify(obj));
1428
- return null;
1457
+ return this.processSqlMethod({
1458
+ ...obj,
1459
+ response: { rows: [], rowCount: 0 }
1460
+ });
1429
1461
  }
1430
1462
  if (!obj.response.rows) {
1431
- this.printError("rows undefined " + this.safeStringify(obj));
1432
- return null;
1463
+ const usesRowCountOnly = !obj.select && (obj.sqlMethod === "del" /* DELETE */ || obj.sqlMethod === "delete" /* DELETE_ALT */ || obj.sqlMethod === "update" /* UPDATE */ || obj.sqlMethod === "counter" /* COUNTER */);
1464
+ if (usesRowCountOnly) {
1465
+ return null;
1466
+ }
1467
+ this.printWarn("rows undefined " + this.safeStringify(obj));
1468
+ return this.processSqlMethod({
1469
+ ...obj,
1470
+ response: {
1471
+ ...obj.response,
1472
+ rows: [],
1473
+ rowCount: obj.response.rowCount ?? 0
1474
+ }
1475
+ });
1433
1476
  }
1434
1477
  return null;
1435
1478
  }
@@ -1474,8 +1517,7 @@ var DB2Client = class extends import_knex.default.Client {
1474
1517
  return queryObject;
1475
1518
  } catch (retryError) {
1476
1519
  this.printError(`Retry failed: ${retryError.message}`);
1477
- queryObject.response = { rows: [], rowCount: 0 };
1478
- return queryObject;
1520
+ throw this.wrapError(retryError, `${method}_retry`, queryObject);
1479
1521
  }
1480
1522
  }
1481
1523
  /**
@@ -1520,7 +1562,8 @@ var DB2Client = class extends import_knex.default.Client {
1520
1562
  return errorMessage.includes("sql") || errorMessage.includes("syntax") || errorMessage.includes("table") || errorMessage.includes("column");
1521
1563
  }
1522
1564
  processSqlMethod(obj) {
1523
- const { rows, rowCount } = obj.response;
1565
+ const rows = obj.response?.rows ?? [];
1566
+ const rowCount = obj.response?.rowCount;
1524
1567
  switch (obj.sqlMethod) {
1525
1568
  case "select" /* SELECT */:
1526
1569
  return rows;