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 +2 -2
- package/package.json +1 -1
- package/packages/cli/dist/bin.js +46 -6
- package/packages/cli/src/commands/generate.ts +4 -4
- package/packages/core/dist/index.js +46 -6
- package/packages/core/src/sessionHandlers/databaseHandler.ts +4 -2
- package/packages/orm/dist/index.js +46 -6
- package/packages/orm/src/adapters/firebird.ts +7 -0
- package/packages/orm/src/adapters/mssql.ts +6 -0
- package/packages/orm/src/adapters/mysql.ts +6 -0
- package/packages/orm/src/sqlTranslator.ts +45 -0
- package/types/orm/src/sqlTranslator.d.ts +19 -0
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.
|
|
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.
|
|
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
package/packages/cli/dist/bin.js
CHANGED
|
@@ -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.
|
|
22387
|
-
//
|
|
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
|
|
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: "
|
|
26193
|
-
str: { orm: '"string"', sql: "
|
|
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: "
|
|
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: "
|
|
41
|
-
str: { orm: '"string"', sql: "
|
|
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: "
|
|
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
|
|
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.
|
|
22366
|
-
//
|
|
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
|
|
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: "
|
|
26172
|
-
str: { orm: '"string"', sql: "
|
|
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: "
|
|
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.
|
|
144
|
-
//
|
|
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.
|
|
10725
|
-
//
|
|
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
|
|
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: "
|
|
14531
|
-
str: { orm: '"string"', sql: "
|
|
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: "
|
|
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
|
*
|