tina4-nodejs 3.13.124 → 3.13.125

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/CLAUDE.md CHANGED
@@ -13,13 +13,13 @@ Even if the skill text is not currently loaded, these are non-negotiable:
13
13
 
14
14
  The full discipline lives in `.claude/skills/tina4-maintainer/SKILL.md`; this block is the always-on floor.
15
15
 
16
- # CLAUDE.md - AI Developer Guide for tina4-nodejs (v3.13.124)
16
+ # CLAUDE.md - AI Developer Guide for tina4-nodejs (v3.13.125)
17
17
 
18
18
  > This file helps AI assistants (Claude, Copilot, Cursor, etc.) understand and work on this codebase effectively.
19
19
 
20
20
  ## What This Project Is
21
21
 
22
- Tina4 for Node.js/TypeScript v3.13.124 - The Intelligent Native Application 4ramework. A convention-over-configuration structural paradigm. The developer writes TypeScript; Tina4 is invisible infrastructure.
22
+ Tina4 for Node.js/TypeScript v3.13.125 - The Intelligent Native Application 4ramework. A convention-over-configuration structural paradigm. The developer writes TypeScript; Tina4 is invisible infrastructure.
23
23
 
24
24
  The philosophy: zero ceremony, batteries included, file system as source of truth.
25
25
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tina4-nodejs",
3
- "version": "3.13.124",
3
+ "version": "3.13.125",
4
4
  "type": "module",
5
5
  "description": "Tina4 for Node.js/TypeScript - native TypeScript conventions and shared Tina4 contracts",
6
6
  "keywords": [
@@ -6375,6 +6375,38 @@ var init_sqlTranslator = __esm({
6375
6375
  return sql;
6376
6376
  }
6377
6377
  }
6378
+ /**
6379
+ * Translate SQLite-canonical DDL column TYPES + CREATE-TABLE options to the
6380
+ * target engine.
6381
+ *
6382
+ * ONLY acts on `CREATE TABLE` / `ALTER TABLE` statements, so a query or INSERT
6383
+ * that happens to contain the word `TEXT` (a column name, a string literal) is
6384
+ * never rewritten. Complements `autoIncrementSyntax` (which maps the id
6385
+ * keyword) so ONE portable migration — and every `Model.createTable()` DDL,
6386
+ * which is also SQLite-canonical — applies on every engine instead of failing
6387
+ * on Firebird/MSSQL.
6388
+ *
6389
+ * * Firebird has no `TEXT` (-607), no `REAL`, and no `CREATE TABLE IF NOT
6390
+ * EXISTS`.
6391
+ * * MSSQL has no `CREATE TABLE IF NOT EXISTS` and its `TIMESTAMP` is a
6392
+ * rowversion, not a datetime — a `created_at TIMESTAMP` there is wrong.
6393
+ * * MySQL's `TIMESTAMP` carries auto-update / 2038 surprises, so a datetime
6394
+ * column maps to `DATETIME` (matching the adapters' createTableAsync).
6395
+ */
6396
+ static ddlTypes(sql, engine) {
6397
+ const head = sql.replace(/^(?:\s*--[^\n]*\n)+/, "");
6398
+ if (!/^\s*(?:CREATE\s+TABLE|ALTER\s+TABLE)\b/i.test(head)) return sql;
6399
+ switch ((engine ?? "").toLowerCase()) {
6400
+ case "firebird":
6401
+ return sql.replace(/\bIF\s+NOT\s+EXISTS\b/gi, "").replace(/\bBLOB\s+SUB_TYPE\s+TEXT\b/gi, "\0FBTEXT\0").replace(/\bTEXT\b/gi, "BLOB SUB_TYPE TEXT").replaceAll("\0FBTEXT\0", "BLOB SUB_TYPE TEXT").replace(/\bREAL\b/gi, "DOUBLE PRECISION");
6402
+ case "mssql":
6403
+ return sql.replace(/\bIF\s+NOT\s+EXISTS\b/gi, "").replace(/\bTIMESTAMP\b/gi, "DATETIME2");
6404
+ case "mysql":
6405
+ return sql.replace(/\bTIMESTAMP\b/gi, "DATETIME");
6406
+ default:
6407
+ return sql;
6408
+ }
6409
+ }
6378
6410
  /**
6379
6411
  * Convert ? placeholders to engine-specific style.
6380
6412
  *
@@ -8416,6 +8448,8 @@ var init_mysql = __esm({
8416
8448
  translateSql(sql) {
8417
8449
  let translated = SQLTranslator.concatPipesToFunc(sql);
8418
8450
  translated = SQLTranslator.ilikeToLike(translated);
8451
+ translated = SQLTranslator.autoIncrementSyntax(translated, "mysql");
8452
+ translated = SQLTranslator.ddlTypes(translated, "mysql");
8419
8453
  return translated;
8420
8454
  }
8421
8455
  execute(sql, params) {
@@ -8837,6 +8871,8 @@ var init_mssql = __esm({
8837
8871
  let translated = SQLTranslator.limitToTop(sql);
8838
8872
  translated = SQLTranslator.concatPipesToFunc(translated);
8839
8873
  translated = SQLTranslator.ilikeToLike(translated);
8874
+ translated = SQLTranslator.autoIncrementSyntax(translated, "mssql");
8875
+ translated = SQLTranslator.ddlTypes(translated, "mssql");
8840
8876
  return translated;
8841
8877
  }
8842
8878
  execSqlPromise(sql, params) {
@@ -9467,6 +9503,8 @@ var init_firebird = __esm({
9467
9503
  let translated = SQLTranslator.limitToRows(sql);
9468
9504
  translated = SQLTranslator.booleanToInt(translated);
9469
9505
  translated = SQLTranslator.ilikeToLike(translated);
9506
+ translated = SQLTranslator.autoIncrementSyntax(translated, "firebird");
9507
+ translated = SQLTranslator.ddlTypes(translated, "firebird");
9470
9508
  return translated;
9471
9509
  }
9472
9510
  /**
@@ -25048,7 +25086,7 @@ function generateMigration(name, flags, fieldsOverride, tableOverride, emitTest
25048
25086
  const defaultClause = info.defaultVal !== "NULL" ? ` DEFAULT ${info.defaultVal}` : "";
25049
25087
  colLines.push(` ${fname} ${info.sql}${defaultClause}`);
25050
25088
  }
25051
- colLines.push(" created_at TEXT DEFAULT CURRENT_TIMESTAMP");
25089
+ colLines.push(" created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP");
25052
25090
  upSql = `CREATE TABLE IF NOT EXISTS ${table2} (
25053
25091
  -- tina4:edit add columns beyond id + created_at
25054
25092
  ${colLines.join(",\n")}
@@ -26191,8 +26229,8 @@ var init_generate = __esm({
26191
26229
  "src/commands/generate.ts"() {
26192
26230
  "use strict";
26193
26231
  FIELD_TYPE_MAP = {
26194
- string: { orm: '"string"', sql: "TEXT", defaultVal: "''" },
26195
- str: { orm: '"string"', sql: "TEXT", defaultVal: "''" },
26232
+ string: { orm: '"string"', sql: "VARCHAR(255)", defaultVal: "''" },
26233
+ str: { orm: '"string"', sql: "VARCHAR(255)", defaultVal: "''" },
26196
26234
  int: { orm: '"integer"', sql: "INTEGER", defaultVal: "0" },
26197
26235
  integer: { orm: '"integer"', sql: "INTEGER", defaultVal: "0" },
26198
26236
  float: { orm: '"number"', sql: "REAL", defaultVal: "0" },
@@ -26202,7 +26240,7 @@ var init_generate = __esm({
26202
26240
  bool: { orm: '"boolean"', sql: "INTEGER", defaultVal: "0" },
26203
26241
  boolean: { orm: '"boolean"', sql: "INTEGER", defaultVal: "0" },
26204
26242
  text: { orm: '"string"', sql: "TEXT", defaultVal: "''" },
26205
- datetime: { orm: '"datetime"', sql: "TEXT", defaultVal: "NULL" },
26243
+ datetime: { orm: '"datetime"', sql: "TIMESTAMP", defaultVal: "NULL" },
26206
26244
  blob: { orm: '"string"', sql: "BLOB", defaultVal: "NULL" }
26207
26245
  };
26208
26246
  SQL_RESERVED_TABLE_NAMES = /* @__PURE__ */ new Set([
@@ -37,8 +37,8 @@ import { join, relative, resolve, sep } from "node:path";
37
37
 
38
38
  // ── Field type mapping ──────────────────────────────────────────────
39
39
  const FIELD_TYPE_MAP: Record<string, { orm: string; sql: string; defaultVal: string }> = {
40
- string: { orm: '"string"', sql: "TEXT", defaultVal: "''" },
41
- str: { orm: '"string"', sql: "TEXT", defaultVal: "''" },
40
+ string: { orm: '"string"', sql: "VARCHAR(255)", defaultVal: "''" },
41
+ str: { orm: '"string"', sql: "VARCHAR(255)", defaultVal: "''" },
42
42
  int: { orm: '"integer"', sql: "INTEGER", defaultVal: "0" },
43
43
  integer: { orm: '"integer"', sql: "INTEGER", defaultVal: "0" },
44
44
  float: { orm: '"number"', sql: "REAL", defaultVal: "0" },
@@ -48,7 +48,7 @@ const FIELD_TYPE_MAP: Record<string, { orm: string; sql: string; defaultVal: str
48
48
  bool: { orm: '"boolean"', sql: "INTEGER", defaultVal: "0" },
49
49
  boolean: { orm: '"boolean"', sql: "INTEGER", defaultVal: "0" },
50
50
  text: { orm: '"string"', sql: "TEXT", defaultVal: "''" },
51
- datetime: { orm: '"datetime"', sql: "TEXT", defaultVal: "NULL" },
51
+ datetime: { orm: '"datetime"', sql: "TIMESTAMP", defaultVal: "NULL" },
52
52
  blob: { orm: '"string"', sql: "BLOB", defaultVal: "NULL" },
53
53
  };
54
54
 
@@ -1269,7 +1269,7 @@ export function generateMigration(
1269
1269
  const defaultClause = info.defaultVal !== "NULL" ? ` DEFAULT ${info.defaultVal}` : "";
1270
1270
  colLines.push(` ${fname} ${info.sql}${defaultClause}`);
1271
1271
  }
1272
- colLines.push(" created_at TEXT DEFAULT CURRENT_TIMESTAMP");
1272
+ colLines.push(" created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP");
1273
1273
 
1274
1274
  upSql =
1275
1275
  `CREATE TABLE IF NOT EXISTS ${table} (\n` +
@@ -6374,6 +6374,38 @@ var init_sqlTranslator = __esm({
6374
6374
  return sql;
6375
6375
  }
6376
6376
  }
6377
+ /**
6378
+ * Translate SQLite-canonical DDL column TYPES + CREATE-TABLE options to the
6379
+ * target engine.
6380
+ *
6381
+ * ONLY acts on `CREATE TABLE` / `ALTER TABLE` statements, so a query or INSERT
6382
+ * that happens to contain the word `TEXT` (a column name, a string literal) is
6383
+ * never rewritten. Complements `autoIncrementSyntax` (which maps the id
6384
+ * keyword) so ONE portable migration — and every `Model.createTable()` DDL,
6385
+ * which is also SQLite-canonical — applies on every engine instead of failing
6386
+ * on Firebird/MSSQL.
6387
+ *
6388
+ * * Firebird has no `TEXT` (-607), no `REAL`, and no `CREATE TABLE IF NOT
6389
+ * EXISTS`.
6390
+ * * MSSQL has no `CREATE TABLE IF NOT EXISTS` and its `TIMESTAMP` is a
6391
+ * rowversion, not a datetime — a `created_at TIMESTAMP` there is wrong.
6392
+ * * MySQL's `TIMESTAMP` carries auto-update / 2038 surprises, so a datetime
6393
+ * column maps to `DATETIME` (matching the adapters' createTableAsync).
6394
+ */
6395
+ static ddlTypes(sql, engine) {
6396
+ const head = sql.replace(/^(?:\s*--[^\n]*\n)+/, "");
6397
+ if (!/^\s*(?:CREATE\s+TABLE|ALTER\s+TABLE)\b/i.test(head)) return sql;
6398
+ switch ((engine ?? "").toLowerCase()) {
6399
+ case "firebird":
6400
+ return sql.replace(/\bIF\s+NOT\s+EXISTS\b/gi, "").replace(/\bBLOB\s+SUB_TYPE\s+TEXT\b/gi, "\0FBTEXT\0").replace(/\bTEXT\b/gi, "BLOB SUB_TYPE TEXT").replaceAll("\0FBTEXT\0", "BLOB SUB_TYPE TEXT").replace(/\bREAL\b/gi, "DOUBLE PRECISION");
6401
+ case "mssql":
6402
+ return sql.replace(/\bIF\s+NOT\s+EXISTS\b/gi, "").replace(/\bTIMESTAMP\b/gi, "DATETIME2");
6403
+ case "mysql":
6404
+ return sql.replace(/\bTIMESTAMP\b/gi, "DATETIME");
6405
+ default:
6406
+ return sql;
6407
+ }
6408
+ }
6377
6409
  /**
6378
6410
  * Convert ? placeholders to engine-specific style.
6379
6411
  *
@@ -8415,6 +8447,8 @@ var init_mysql = __esm({
8415
8447
  translateSql(sql) {
8416
8448
  let translated = SQLTranslator.concatPipesToFunc(sql);
8417
8449
  translated = SQLTranslator.ilikeToLike(translated);
8450
+ translated = SQLTranslator.autoIncrementSyntax(translated, "mysql");
8451
+ translated = SQLTranslator.ddlTypes(translated, "mysql");
8418
8452
  return translated;
8419
8453
  }
8420
8454
  execute(sql, params) {
@@ -8836,6 +8870,8 @@ var init_mssql = __esm({
8836
8870
  let translated = SQLTranslator.limitToTop(sql);
8837
8871
  translated = SQLTranslator.concatPipesToFunc(translated);
8838
8872
  translated = SQLTranslator.ilikeToLike(translated);
8873
+ translated = SQLTranslator.autoIncrementSyntax(translated, "mssql");
8874
+ translated = SQLTranslator.ddlTypes(translated, "mssql");
8839
8875
  return translated;
8840
8876
  }
8841
8877
  execSqlPromise(sql, params) {
@@ -9466,6 +9502,8 @@ var init_firebird = __esm({
9466
9502
  let translated = SQLTranslator.limitToRows(sql);
9467
9503
  translated = SQLTranslator.booleanToInt(translated);
9468
9504
  translated = SQLTranslator.ilikeToLike(translated);
9505
+ translated = SQLTranslator.autoIncrementSyntax(translated, "firebird");
9506
+ translated = SQLTranslator.ddlTypes(translated, "firebird");
9469
9507
  return translated;
9470
9508
  }
9471
9509
  /**
@@ -25027,7 +25065,7 @@ function generateMigration(name, flags, fieldsOverride, tableOverride, emitTest
25027
25065
  const defaultClause = info.defaultVal !== "NULL" ? ` DEFAULT ${info.defaultVal}` : "";
25028
25066
  colLines.push(` ${fname} ${info.sql}${defaultClause}`);
25029
25067
  }
25030
- colLines.push(" created_at TEXT DEFAULT CURRENT_TIMESTAMP");
25068
+ colLines.push(" created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP");
25031
25069
  upSql = `CREATE TABLE IF NOT EXISTS ${table2} (
25032
25070
  -- tina4:edit add columns beyond id + created_at
25033
25071
  ${colLines.join(",\n")}
@@ -26170,8 +26208,8 @@ var init_generate = __esm({
26170
26208
  "../cli/src/commands/generate.ts"() {
26171
26209
  "use strict";
26172
26210
  FIELD_TYPE_MAP = {
26173
- string: { orm: '"string"', sql: "TEXT", defaultVal: "''" },
26174
- str: { orm: '"string"', sql: "TEXT", defaultVal: "''" },
26211
+ string: { orm: '"string"', sql: "VARCHAR(255)", defaultVal: "''" },
26212
+ str: { orm: '"string"', sql: "VARCHAR(255)", defaultVal: "''" },
26175
26213
  int: { orm: '"integer"', sql: "INTEGER", defaultVal: "0" },
26176
26214
  integer: { orm: '"integer"', sql: "INTEGER", defaultVal: "0" },
26177
26215
  float: { orm: '"number"', sql: "REAL", defaultVal: "0" },
@@ -26181,7 +26219,7 @@ var init_generate = __esm({
26181
26219
  bool: { orm: '"boolean"', sql: "INTEGER", defaultVal: "0" },
26182
26220
  boolean: { orm: '"boolean"', sql: "INTEGER", defaultVal: "0" },
26183
26221
  text: { orm: '"string"', sql: "TEXT", defaultVal: "''" },
26184
- datetime: { orm: '"datetime"', sql: "TEXT", defaultVal: "NULL" },
26222
+ datetime: { orm: '"datetime"', sql: "TIMESTAMP", defaultVal: "NULL" },
26185
26223
  blob: { orm: '"string"', sql: "BLOB", defaultVal: "NULL" }
26186
26224
  };
26187
26225
  SQL_RESERVED_TABLE_NAMES = /* @__PURE__ */ new Set([
@@ -915,6 +915,38 @@ var init_sqlTranslator = __esm({
915
915
  return sql;
916
916
  }
917
917
  }
918
+ /**
919
+ * Translate SQLite-canonical DDL column TYPES + CREATE-TABLE options to the
920
+ * target engine.
921
+ *
922
+ * ONLY acts on `CREATE TABLE` / `ALTER TABLE` statements, so a query or INSERT
923
+ * that happens to contain the word `TEXT` (a column name, a string literal) is
924
+ * never rewritten. Complements `autoIncrementSyntax` (which maps the id
925
+ * keyword) so ONE portable migration — and every `Model.createTable()` DDL,
926
+ * which is also SQLite-canonical — applies on every engine instead of failing
927
+ * on Firebird/MSSQL.
928
+ *
929
+ * * Firebird has no `TEXT` (-607), no `REAL`, and no `CREATE TABLE IF NOT
930
+ * EXISTS`.
931
+ * * MSSQL has no `CREATE TABLE IF NOT EXISTS` and its `TIMESTAMP` is a
932
+ * rowversion, not a datetime — a `created_at TIMESTAMP` there is wrong.
933
+ * * MySQL's `TIMESTAMP` carries auto-update / 2038 surprises, so a datetime
934
+ * column maps to `DATETIME` (matching the adapters' createTableAsync).
935
+ */
936
+ static ddlTypes(sql, engine) {
937
+ const head = sql.replace(/^(?:\s*--[^\n]*\n)+/, "");
938
+ if (!/^\s*(?:CREATE\s+TABLE|ALTER\s+TABLE)\b/i.test(head)) return sql;
939
+ switch ((engine ?? "").toLowerCase()) {
940
+ case "firebird":
941
+ return sql.replace(/\bIF\s+NOT\s+EXISTS\b/gi, "").replace(/\bBLOB\s+SUB_TYPE\s+TEXT\b/gi, "\0FBTEXT\0").replace(/\bTEXT\b/gi, "BLOB SUB_TYPE TEXT").replaceAll("\0FBTEXT\0", "BLOB SUB_TYPE TEXT").replace(/\bREAL\b/gi, "DOUBLE PRECISION");
942
+ case "mssql":
943
+ return sql.replace(/\bIF\s+NOT\s+EXISTS\b/gi, "").replace(/\bTIMESTAMP\b/gi, "DATETIME2");
944
+ case "mysql":
945
+ return sql.replace(/\bTIMESTAMP\b/gi, "DATETIME");
946
+ default:
947
+ return sql;
948
+ }
949
+ }
918
950
  /**
919
951
  * Convert ? placeholders to engine-specific style.
920
952
  *
@@ -13386,7 +13418,7 @@ function generateMigration(name, flags, fieldsOverride, tableOverride, emitTest
13386
13418
  const defaultClause = info.defaultVal !== "NULL" ? ` DEFAULT ${info.defaultVal}` : "";
13387
13419
  colLines.push(` ${fname} ${info.sql}${defaultClause}`);
13388
13420
  }
13389
- colLines.push(" created_at TEXT DEFAULT CURRENT_TIMESTAMP");
13421
+ colLines.push(" created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP");
13390
13422
  upSql = `CREATE TABLE IF NOT EXISTS ${table2} (
13391
13423
  -- tina4:edit add columns beyond id + created_at
13392
13424
  ${colLines.join(",\n")}
@@ -14529,8 +14561,8 @@ var init_generate = __esm({
14529
14561
  "../cli/src/commands/generate.ts"() {
14530
14562
  "use strict";
14531
14563
  FIELD_TYPE_MAP = {
14532
- string: { orm: '"string"', sql: "TEXT", defaultVal: "''" },
14533
- str: { orm: '"string"', sql: "TEXT", defaultVal: "''" },
14564
+ string: { orm: '"string"', sql: "VARCHAR(255)", defaultVal: "''" },
14565
+ str: { orm: '"string"', sql: "VARCHAR(255)", defaultVal: "''" },
14534
14566
  int: { orm: '"integer"', sql: "INTEGER", defaultVal: "0" },
14535
14567
  integer: { orm: '"integer"', sql: "INTEGER", defaultVal: "0" },
14536
14568
  float: { orm: '"number"', sql: "REAL", defaultVal: "0" },
@@ -14540,7 +14572,7 @@ var init_generate = __esm({
14540
14572
  bool: { orm: '"boolean"', sql: "INTEGER", defaultVal: "0" },
14541
14573
  boolean: { orm: '"boolean"', sql: "INTEGER", defaultVal: "0" },
14542
14574
  text: { orm: '"string"', sql: "TEXT", defaultVal: "''" },
14543
- datetime: { orm: '"datetime"', sql: "TEXT", defaultVal: "NULL" },
14575
+ datetime: { orm: '"datetime"', sql: "TIMESTAMP", defaultVal: "NULL" },
14544
14576
  blob: { orm: '"string"', sql: "BLOB", defaultVal: "NULL" }
14545
14577
  };
14546
14578
  SQL_RESERVED_TABLE_NAMES = /* @__PURE__ */ new Set([
@@ -37310,6 +37342,8 @@ var init_mysql = __esm({
37310
37342
  translateSql(sql) {
37311
37343
  let translated = SQLTranslator.concatPipesToFunc(sql);
37312
37344
  translated = SQLTranslator.ilikeToLike(translated);
37345
+ translated = SQLTranslator.autoIncrementSyntax(translated, "mysql");
37346
+ translated = SQLTranslator.ddlTypes(translated, "mysql");
37313
37347
  return translated;
37314
37348
  }
37315
37349
  execute(sql, params) {
@@ -37731,6 +37765,8 @@ var init_mssql = __esm({
37731
37765
  let translated = SQLTranslator.limitToTop(sql);
37732
37766
  translated = SQLTranslator.concatPipesToFunc(translated);
37733
37767
  translated = SQLTranslator.ilikeToLike(translated);
37768
+ translated = SQLTranslator.autoIncrementSyntax(translated, "mssql");
37769
+ translated = SQLTranslator.ddlTypes(translated, "mssql");
37734
37770
  return translated;
37735
37771
  }
37736
37772
  execSqlPromise(sql, params) {
@@ -38361,6 +38397,8 @@ var init_firebird = __esm({
38361
38397
  let translated = SQLTranslator.limitToRows(sql);
38362
38398
  translated = SQLTranslator.booleanToInt(translated);
38363
38399
  translated = SQLTranslator.ilikeToLike(translated);
38400
+ translated = SQLTranslator.autoIncrementSyntax(translated, "firebird");
38401
+ translated = SQLTranslator.ddlTypes(translated, "firebird");
38364
38402
  return translated;
38365
38403
  }
38366
38404
  /**
@@ -395,6 +395,13 @@ export class FirebirdAdapter implements DatabaseAdapter {
395
395
  let translated = SQLTranslator.limitToRows(sql);
396
396
  translated = SQLTranslator.booleanToInt(translated);
397
397
  translated = SQLTranslator.ilikeToLike(translated);
398
+ // DDL: strip AUTOINCREMENT (Firebird uses generators) and rewrite the
399
+ // SQLite-canonical column TYPES so ONE portable migration applies here —
400
+ // TEXT -> BLOB SUB_TYPE TEXT, REAL -> DOUBLE PRECISION, IF NOT EXISTS
401
+ // dropped. Both are DDL-only, so DML is untouched. Mirrors the Python
402
+ // master's firebird.py::_translate_sql.
403
+ translated = SQLTranslator.autoIncrementSyntax(translated, "firebird");
404
+ translated = SQLTranslator.ddlTypes(translated, "firebird");
398
405
  return translated;
399
406
  }
400
407
 
@@ -172,6 +172,12 @@ export class MssqlAdapter implements DatabaseAdapter {
172
172
  let translated = SQLTranslator.limitToTop(sql);
173
173
  translated = SQLTranslator.concatPipesToFunc(translated);
174
174
  translated = SQLTranslator.ilikeToLike(translated);
175
+ // DDL: AUTOINCREMENT -> IDENTITY(1,1), drop IF NOT EXISTS (unsupported), and
176
+ // TIMESTAMP -> DATETIME2 (MSSQL's TIMESTAMP is a rowversion, not a datetime)
177
+ // so ONE portable migration applies here. Both are DDL-only, so DML is
178
+ // untouched. Mirrors the Python master's mssql.py::_translate_sql.
179
+ translated = SQLTranslator.autoIncrementSyntax(translated, "mssql");
180
+ translated = SQLTranslator.ddlTypes(translated, "mssql");
175
181
  return translated;
176
182
  }
177
183
 
@@ -148,6 +148,12 @@ export class MysqlAdapter implements DatabaseAdapter {
148
148
  let translated = SQLTranslator.concatPipesToFunc(sql);
149
149
  // MySQL uses LOWER() LIKE instead of ILIKE
150
150
  translated = SQLTranslator.ilikeToLike(translated);
151
+ // DDL: AUTOINCREMENT -> AUTO_INCREMENT and TIMESTAMP -> DATETIME (MySQL's
152
+ // TIMESTAMP carries auto-update / 2038 surprises) so ONE portable migration
153
+ // applies here. Both are DDL-only, so DML is untouched. Mirrors the Python
154
+ // master's mysql.py::_translate_sql.
155
+ translated = SQLTranslator.autoIncrementSyntax(translated, "mysql");
156
+ translated = SQLTranslator.ddlTypes(translated, "mysql");
151
157
  return translated;
152
158
  }
153
159
 
@@ -269,6 +269,51 @@ export class SQLTranslator {
269
269
  }
270
270
  }
271
271
 
272
+ /**
273
+ * Translate SQLite-canonical DDL column TYPES + CREATE-TABLE options to the
274
+ * target engine.
275
+ *
276
+ * ONLY acts on `CREATE TABLE` / `ALTER TABLE` statements, so a query or INSERT
277
+ * that happens to contain the word `TEXT` (a column name, a string literal) is
278
+ * never rewritten. Complements `autoIncrementSyntax` (which maps the id
279
+ * keyword) so ONE portable migration — and every `Model.createTable()` DDL,
280
+ * which is also SQLite-canonical — applies on every engine instead of failing
281
+ * on Firebird/MSSQL.
282
+ *
283
+ * * Firebird has no `TEXT` (-607), no `REAL`, and no `CREATE TABLE IF NOT
284
+ * EXISTS`.
285
+ * * MSSQL has no `CREATE TABLE IF NOT EXISTS` and its `TIMESTAMP` is a
286
+ * rowversion, not a datetime — a `created_at TIMESTAMP` there is wrong.
287
+ * * MySQL's `TIMESTAMP` carries auto-update / 2038 surprises, so a datetime
288
+ * column maps to `DATETIME` (matching the adapters' createTableAsync).
289
+ */
290
+ static ddlTypes(sql: string, engine: string): string {
291
+ // Gate to DDL only, tolerating leading `-- ...` comment lines / blank lines
292
+ // that a migration file carries before its CREATE TABLE. A SELECT or INSERT
293
+ // that merely mentions a type keyword is never rewritten.
294
+ const head = sql.replace(/^(?:\s*--[^\n]*\n)+/, "");
295
+ if (!/^\s*(?:CREATE\s+TABLE|ALTER\s+TABLE)\b/i.test(head)) return sql;
296
+ switch ((engine ?? "").toLowerCase()) {
297
+ case "firebird":
298
+ return sql
299
+ .replace(/\bIF\s+NOT\s+EXISTS\b/gi, "")
300
+ // Map bare TEXT -> BLOB SUB_TYPE TEXT, but leave an existing
301
+ // "BLOB SUB_TYPE TEXT" intact (it already contains the word TEXT).
302
+ .replace(/\bBLOB\s+SUB_TYPE\s+TEXT\b/gi, "\x00FBTEXT\x00")
303
+ .replace(/\bTEXT\b/gi, "BLOB SUB_TYPE TEXT")
304
+ .replaceAll("\x00FBTEXT\x00", "BLOB SUB_TYPE TEXT")
305
+ .replace(/\bREAL\b/gi, "DOUBLE PRECISION");
306
+ case "mssql":
307
+ return sql
308
+ .replace(/\bIF\s+NOT\s+EXISTS\b/gi, "")
309
+ .replace(/\bTIMESTAMP\b/gi, "DATETIME2");
310
+ case "mysql":
311
+ return sql.replace(/\bTIMESTAMP\b/gi, "DATETIME");
312
+ default:
313
+ return sql;
314
+ }
315
+ }
316
+
272
317
  /**
273
318
  * Convert ? placeholders to engine-specific style.
274
319
  *
@@ -75,6 +75,25 @@ export declare class SQLTranslator {
75
75
  * Translate AUTOINCREMENT across engines in DDL.
76
76
  */
77
77
  static autoIncrementSyntax(sql: string, engine: string): string;
78
+ /**
79
+ * Translate SQLite-canonical DDL column TYPES + CREATE-TABLE options to the
80
+ * target engine.
81
+ *
82
+ * ONLY acts on `CREATE TABLE` / `ALTER TABLE` statements, so a query or INSERT
83
+ * that happens to contain the word `TEXT` (a column name, a string literal) is
84
+ * never rewritten. Complements `autoIncrementSyntax` (which maps the id
85
+ * keyword) so ONE portable migration — and every `Model.createTable()` DDL,
86
+ * which is also SQLite-canonical — applies on every engine instead of failing
87
+ * on Firebird/MSSQL.
88
+ *
89
+ * * Firebird has no `TEXT` (-607), no `REAL`, and no `CREATE TABLE IF NOT
90
+ * EXISTS`.
91
+ * * MSSQL has no `CREATE TABLE IF NOT EXISTS` and its `TIMESTAMP` is a
92
+ * rowversion, not a datetime — a `created_at TIMESTAMP` there is wrong.
93
+ * * MySQL's `TIMESTAMP` carries auto-update / 2038 surprises, so a datetime
94
+ * column maps to `DATETIME` (matching the adapters' createTableAsync).
95
+ */
96
+ static ddlTypes(sql: string, engine: string): string;
78
97
  /**
79
98
  * Convert ? placeholders to engine-specific style.
80
99
  *