Skip to content

Commit 438b165

Browse files
hotlongCopilot
andcommitted
fix(driver-sql): translate defaultValue 'NOW()' + literal value to CURRENT_TIMESTAMP
The driver previously only mapped the implicit created_at / updated_at columns to CURRENT_TIMESTAMP and ignored field.defaultValue entirely. Every other datetime field that declared the framework convention defaultValue: 'NOW()' (sys_activity.timestamp, sys_comment threads, sys_presence.last_seen, …) ended up with no DB-level default, and any upstream caller that helpfully passed the literal string 'NOW()' would write that string into the column verbatim — producing 'Invalid Date' in the UI and a class of subtle correctness bugs. Fix has two parts: 1. Schema (createColumn): for datetime/date/time fields, when field.defaultValue === 'NOW()' (case-insensitive), apply .defaultTo(this.knex.fn.now()). For other scalar defaultValues, pass them through verbatim instead of dropping them on the floor. 2. Insert/update (formatInput): scan every value before it hits the DB and replace any string of the form 'NOW()' with a real ISO timestamp. Cross-driver (not just SQLite). Verified via DDL inspection (sys_activity.timestamp now has 'default CURRENT_TIMESTAMP') and a live REST insert that submitted created_at='NOW()' — the persisted row has a proper ISO datetime. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 1cc117e commit 438b165

1 file changed

Lines changed: 40 additions & 3 deletions

File tree

packages/plugins/driver-sql/src/sql-driver.ts

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,6 +1002,25 @@ export class SqlDriver implements IDataDriver {
10021002
if (col) {
10031003
if (field.unique) col.unique();
10041004
if (field.required) col.notNullable();
1005+
// `defaultValue: 'NOW()'` is a framework convention for "use the
1006+
// database clock at insert time". Translate it to the driver-native
1007+
// CURRENT_TIMESTAMP equivalent so the column gets a real default
1008+
// instead of leaving the literal string 'NOW()' for whatever
1009+
// upstream code happens to write.
1010+
if (
1011+
(type === 'datetime' || type === 'date' || type === 'time') &&
1012+
typeof field.defaultValue === 'string' &&
1013+
/^now\(\)$/i.test(field.defaultValue.trim())
1014+
) {
1015+
col.defaultTo(this.knex.fn.now());
1016+
} else if (field.defaultValue !== undefined && field.defaultValue !== null) {
1017+
const dv = field.defaultValue;
1018+
if (typeof dv === 'string' && /^now\(\)$/i.test(dv.trim())) {
1019+
col.defaultTo(this.knex.fn.now());
1020+
} else if (typeof dv !== 'object') {
1021+
col.defaultTo(dv as any);
1022+
}
1023+
}
10051024
}
10061025
}
10071026

@@ -1096,12 +1115,30 @@ export class SqlDriver implements IDataDriver {
10961115
// ── SQLite serialisation ────────────────────────────────────────────────────
10971116

10981117
protected formatInput(object: string, data: any): any {
1099-
if (!this.isSqlite) return data;
1118+
let copy: any = data;
1119+
let copied = false;
1120+
1121+
// Insert/update-time safety net: any caller that passes the literal
1122+
// string 'NOW()' (often because a field defaultValue leaked unresolved)
1123+
// gets it replaced with a real ISO timestamp here, before it hits the
1124+
// wire. Applies to every driver, not just SQLite.
1125+
if (data && typeof data === 'object') {
1126+
const now = new Date().toISOString();
1127+
for (const key of Object.keys(data)) {
1128+
const v = (data as any)[key];
1129+
if (typeof v === 'string' && /^now\(\)$/i.test(v.trim())) {
1130+
if (!copied) { copy = { ...data }; copied = true; }
1131+
copy[key] = now;
1132+
}
1133+
}
1134+
}
1135+
1136+
if (!this.isSqlite) return copy;
11001137

11011138
const fields = this.jsonFields[object];
1102-
if (!fields || fields.length === 0) return data;
1139+
if (!fields || fields.length === 0) return copy;
11031140

1104-
const copy = { ...data };
1141+
if (!copied) { copy = { ...copy }; copied = true; }
11051142
for (const field of fields) {
11061143
if (copy[field] !== undefined && typeof copy[field] === 'object' && copy[field] !== null) {
11071144
copy[field] = JSON.stringify(copy[field]);

0 commit comments

Comments
 (0)