tina4-nodejs 3.13.123 → 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.123)
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.123 - 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.123",
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
  /**
@@ -22383,8 +22421,10 @@ var init_databaseHandler = __esm({
22383
22421
  // A VARCHAR rather than BLOB SUB_TYPE TEXT on purpose: node-firebird hands a
22384
22422
  // blob back as a reader function rather than a string, which the read path
22385
22423
  // here would not understand. The cost is a session payload ceiling of 8191
22386
- // characters on this engine alone. Still unverified: the node-firebird DRIVER
22387
- // path end to end - this measurement was taken at the SQL level via isql.
22424
+ // characters on this engine alone. VERIFIED end to end through the
22425
+ // node-firebird DRIVER against the lab's live Firebird 5.0.4: a nested payload
22426
+ // written by one handler and read back by a fresh one round-tripped intact
22427
+ // through this VARCHAR column - not just the isql-level SQL measured above.
22388
22428
  firebird: `
22389
22429
  EXECUTE BLOCK AS BEGIN
22390
22430
  IF (NOT EXISTS(SELECT 1 FROM RDB$RELATIONS WHERE RDB$RELATION_NAME = 'TINA4_SESSION')) THEN
@@ -25046,7 +25086,7 @@ function generateMigration(name, flags, fieldsOverride, tableOverride, emitTest
25046
25086
  const defaultClause = info.defaultVal !== "NULL" ? ` DEFAULT ${info.defaultVal}` : "";
25047
25087
  colLines.push(` ${fname} ${info.sql}${defaultClause}`);
25048
25088
  }
25049
- colLines.push(" created_at TEXT DEFAULT CURRENT_TIMESTAMP");
25089
+ colLines.push(" created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP");
25050
25090
  upSql = `CREATE TABLE IF NOT EXISTS ${table2} (
25051
25091
  -- tina4:edit add columns beyond id + created_at
25052
25092
  ${colLines.join(",\n")}
@@ -26189,8 +26229,8 @@ var init_generate = __esm({
26189
26229
  "src/commands/generate.ts"() {
26190
26230
  "use strict";
26191
26231
  FIELD_TYPE_MAP = {
26192
- string: { orm: '"string"', sql: "TEXT", defaultVal: "''" },
26193
- str: { orm: '"string"', sql: "TEXT", defaultVal: "''" },
26232
+ string: { orm: '"string"', sql: "VARCHAR(255)", defaultVal: "''" },
26233
+ str: { orm: '"string"', sql: "VARCHAR(255)", defaultVal: "''" },
26194
26234
  int: { orm: '"integer"', sql: "INTEGER", defaultVal: "0" },
26195
26235
  integer: { orm: '"integer"', sql: "INTEGER", defaultVal: "0" },
26196
26236
  float: { orm: '"number"', sql: "REAL", defaultVal: "0" },
@@ -26200,7 +26240,7 @@ var init_generate = __esm({
26200
26240
  bool: { orm: '"boolean"', sql: "INTEGER", defaultVal: "0" },
26201
26241
  boolean: { orm: '"boolean"', sql: "INTEGER", defaultVal: "0" },
26202
26242
  text: { orm: '"string"', sql: "TEXT", defaultVal: "''" },
26203
- datetime: { orm: '"datetime"', sql: "TEXT", defaultVal: "NULL" },
26243
+ datetime: { orm: '"datetime"', sql: "TIMESTAMP", defaultVal: "NULL" },
26204
26244
  blob: { orm: '"string"', sql: "BLOB", defaultVal: "NULL" }
26205
26245
  };
26206
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
  /**
@@ -22362,8 +22400,10 @@ var init_databaseHandler = __esm({
22362
22400
  // A VARCHAR rather than BLOB SUB_TYPE TEXT on purpose: node-firebird hands a
22363
22401
  // blob back as a reader function rather than a string, which the read path
22364
22402
  // here would not understand. The cost is a session payload ceiling of 8191
22365
- // characters on this engine alone. Still unverified: the node-firebird DRIVER
22366
- // path end to end - this measurement was taken at the SQL level via isql.
22403
+ // characters on this engine alone. VERIFIED end to end through the
22404
+ // node-firebird DRIVER against the lab's live Firebird 5.0.4: a nested payload
22405
+ // written by one handler and read back by a fresh one round-tripped intact
22406
+ // through this VARCHAR column - not just the isql-level SQL measured above.
22367
22407
  firebird: `
22368
22408
  EXECUTE BLOCK AS BEGIN
22369
22409
  IF (NOT EXISTS(SELECT 1 FROM RDB$RELATIONS WHERE RDB$RELATION_NAME = 'TINA4_SESSION')) THEN
@@ -25025,7 +25065,7 @@ function generateMigration(name, flags, fieldsOverride, tableOverride, emitTest
25025
25065
  const defaultClause = info.defaultVal !== "NULL" ? ` DEFAULT ${info.defaultVal}` : "";
25026
25066
  colLines.push(` ${fname} ${info.sql}${defaultClause}`);
25027
25067
  }
25028
- colLines.push(" created_at TEXT DEFAULT CURRENT_TIMESTAMP");
25068
+ colLines.push(" created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP");
25029
25069
  upSql = `CREATE TABLE IF NOT EXISTS ${table2} (
25030
25070
  -- tina4:edit add columns beyond id + created_at
25031
25071
  ${colLines.join(",\n")}
@@ -26168,8 +26208,8 @@ var init_generate = __esm({
26168
26208
  "../cli/src/commands/generate.ts"() {
26169
26209
  "use strict";
26170
26210
  FIELD_TYPE_MAP = {
26171
- string: { orm: '"string"', sql: "TEXT", defaultVal: "''" },
26172
- str: { orm: '"string"', sql: "TEXT", defaultVal: "''" },
26211
+ string: { orm: '"string"', sql: "VARCHAR(255)", defaultVal: "''" },
26212
+ str: { orm: '"string"', sql: "VARCHAR(255)", defaultVal: "''" },
26173
26213
  int: { orm: '"integer"', sql: "INTEGER", defaultVal: "0" },
26174
26214
  integer: { orm: '"integer"', sql: "INTEGER", defaultVal: "0" },
26175
26215
  float: { orm: '"number"', sql: "REAL", defaultVal: "0" },
@@ -26179,7 +26219,7 @@ var init_generate = __esm({
26179
26219
  bool: { orm: '"boolean"', sql: "INTEGER", defaultVal: "0" },
26180
26220
  boolean: { orm: '"boolean"', sql: "INTEGER", defaultVal: "0" },
26181
26221
  text: { orm: '"string"', sql: "TEXT", defaultVal: "''" },
26182
- datetime: { orm: '"datetime"', sql: "TEXT", defaultVal: "NULL" },
26222
+ datetime: { orm: '"datetime"', sql: "TIMESTAMP", defaultVal: "NULL" },
26183
26223
  blob: { orm: '"string"', sql: "BLOB", defaultVal: "NULL" }
26184
26224
  };
26185
26225
  SQL_RESERVED_TABLE_NAMES = /* @__PURE__ */ new Set([
@@ -140,8 +140,10 @@ const CREATE_TABLE: Record<string, string> = {
140
140
  // A VARCHAR rather than BLOB SUB_TYPE TEXT on purpose: node-firebird hands a
141
141
  // blob back as a reader function rather than a string, which the read path
142
142
  // here would not understand. The cost is a session payload ceiling of 8191
143
- // characters on this engine alone. Still unverified: the node-firebird DRIVER
144
- // path end to end - this measurement was taken at the SQL level via isql.
143
+ // characters on this engine alone. VERIFIED end to end through the
144
+ // node-firebird DRIVER against the lab's live Firebird 5.0.4: a nested payload
145
+ // written by one handler and read back by a fresh one round-tripped intact
146
+ // through this VARCHAR column - not just the isql-level SQL measured above.
145
147
  firebird: `
146
148
  EXECUTE BLOCK AS BEGIN
147
149
  IF (NOT EXISTS(SELECT 1 FROM RDB$RELATIONS WHERE RDB$RELATION_NAME = 'TINA4_SESSION')) THEN
@@ -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
  *
@@ -10721,8 +10753,10 @@ var init_databaseHandler = __esm({
10721
10753
  // A VARCHAR rather than BLOB SUB_TYPE TEXT on purpose: node-firebird hands a
10722
10754
  // blob back as a reader function rather than a string, which the read path
10723
10755
  // here would not understand. The cost is a session payload ceiling of 8191
10724
- // characters on this engine alone. Still unverified: the node-firebird DRIVER
10725
- // path end to end - this measurement was taken at the SQL level via isql.
10756
+ // characters on this engine alone. VERIFIED end to end through the
10757
+ // node-firebird DRIVER against the lab's live Firebird 5.0.4: a nested payload
10758
+ // written by one handler and read back by a fresh one round-tripped intact
10759
+ // through this VARCHAR column - not just the isql-level SQL measured above.
10726
10760
  firebird: `
10727
10761
  EXECUTE BLOCK AS BEGIN
10728
10762
  IF (NOT EXISTS(SELECT 1 FROM RDB$RELATIONS WHERE RDB$RELATION_NAME = 'TINA4_SESSION')) THEN
@@ -13384,7 +13418,7 @@ function generateMigration(name, flags, fieldsOverride, tableOverride, emitTest
13384
13418
  const defaultClause = info.defaultVal !== "NULL" ? ` DEFAULT ${info.defaultVal}` : "";
13385
13419
  colLines.push(` ${fname} ${info.sql}${defaultClause}`);
13386
13420
  }
13387
- colLines.push(" created_at TEXT DEFAULT CURRENT_TIMESTAMP");
13421
+ colLines.push(" created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP");
13388
13422
  upSql = `CREATE TABLE IF NOT EXISTS ${table2} (
13389
13423
  -- tina4:edit add columns beyond id + created_at
13390
13424
  ${colLines.join(",\n")}
@@ -14527,8 +14561,8 @@ var init_generate = __esm({
14527
14561
  "../cli/src/commands/generate.ts"() {
14528
14562
  "use strict";
14529
14563
  FIELD_TYPE_MAP = {
14530
- string: { orm: '"string"', sql: "TEXT", defaultVal: "''" },
14531
- str: { orm: '"string"', sql: "TEXT", defaultVal: "''" },
14564
+ string: { orm: '"string"', sql: "VARCHAR(255)", defaultVal: "''" },
14565
+ str: { orm: '"string"', sql: "VARCHAR(255)", defaultVal: "''" },
14532
14566
  int: { orm: '"integer"', sql: "INTEGER", defaultVal: "0" },
14533
14567
  integer: { orm: '"integer"', sql: "INTEGER", defaultVal: "0" },
14534
14568
  float: { orm: '"number"', sql: "REAL", defaultVal: "0" },
@@ -14538,7 +14572,7 @@ var init_generate = __esm({
14538
14572
  bool: { orm: '"boolean"', sql: "INTEGER", defaultVal: "0" },
14539
14573
  boolean: { orm: '"boolean"', sql: "INTEGER", defaultVal: "0" },
14540
14574
  text: { orm: '"string"', sql: "TEXT", defaultVal: "''" },
14541
- datetime: { orm: '"datetime"', sql: "TEXT", defaultVal: "NULL" },
14575
+ datetime: { orm: '"datetime"', sql: "TIMESTAMP", defaultVal: "NULL" },
14542
14576
  blob: { orm: '"string"', sql: "BLOB", defaultVal: "NULL" }
14543
14577
  };
14544
14578
  SQL_RESERVED_TABLE_NAMES = /* @__PURE__ */ new Set([
@@ -37308,6 +37342,8 @@ var init_mysql = __esm({
37308
37342
  translateSql(sql) {
37309
37343
  let translated = SQLTranslator.concatPipesToFunc(sql);
37310
37344
  translated = SQLTranslator.ilikeToLike(translated);
37345
+ translated = SQLTranslator.autoIncrementSyntax(translated, "mysql");
37346
+ translated = SQLTranslator.ddlTypes(translated, "mysql");
37311
37347
  return translated;
37312
37348
  }
37313
37349
  execute(sql, params) {
@@ -37729,6 +37765,8 @@ var init_mssql = __esm({
37729
37765
  let translated = SQLTranslator.limitToTop(sql);
37730
37766
  translated = SQLTranslator.concatPipesToFunc(translated);
37731
37767
  translated = SQLTranslator.ilikeToLike(translated);
37768
+ translated = SQLTranslator.autoIncrementSyntax(translated, "mssql");
37769
+ translated = SQLTranslator.ddlTypes(translated, "mssql");
37732
37770
  return translated;
37733
37771
  }
37734
37772
  execSqlPromise(sql, params) {
@@ -38359,6 +38397,8 @@ var init_firebird = __esm({
38359
38397
  let translated = SQLTranslator.limitToRows(sql);
38360
38398
  translated = SQLTranslator.booleanToInt(translated);
38361
38399
  translated = SQLTranslator.ilikeToLike(translated);
38400
+ translated = SQLTranslator.autoIncrementSyntax(translated, "firebird");
38401
+ translated = SQLTranslator.ddlTypes(translated, "firebird");
38362
38402
  return translated;
38363
38403
  }
38364
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
  *