@objectstack/driver-sql 9.9.0 → 9.10.0

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/index.mjs CHANGED
@@ -11,9 +11,12 @@ var JSON_COLUMN_TYPES = /* @__PURE__ */ new Set([
11
11
  "json",
12
12
  "object",
13
13
  "array",
14
+ "record",
14
15
  "image",
15
16
  "file",
16
17
  "avatar",
18
+ "video",
19
+ "audio",
17
20
  "location",
18
21
  "address",
19
22
  "composite",
@@ -23,6 +26,18 @@ var JSON_COLUMN_TYPES = /* @__PURE__ */ new Set([
23
26
  "repeater",
24
27
  "vector"
25
28
  ]);
29
+ var NUMERIC_SCALAR_TYPES = /* @__PURE__ */ new Set([
30
+ "integer",
31
+ "int",
32
+ "float",
33
+ "number",
34
+ "currency",
35
+ "percent",
36
+ "summary",
37
+ "rating",
38
+ "slider",
39
+ "progress"
40
+ ]);
26
41
  var SqlDriver = class {
27
42
  constructor(config) {
28
43
  // IDataDriver metadata
@@ -30,6 +45,7 @@ var SqlDriver = class {
30
45
  this.version = "1.0.0";
31
46
  this.jsonFields = {};
32
47
  this.booleanFields = {};
48
+ this.numericFields = {};
33
49
  this.dateFields = {};
34
50
  this.datetimeFields = {};
35
51
  this.tablesWithTimestamps = /* @__PURE__ */ new Set();
@@ -810,6 +826,7 @@ var SqlDriver = class {
810
826
  const tableName = StorageNameMapping.resolveTableName(obj);
811
827
  const jsonCols = [];
812
828
  const booleanCols = [];
829
+ const numericCols = [];
813
830
  const autoNumberCols = [];
814
831
  const tenancyDecl = obj?.tenancy;
815
832
  let tenantField = null;
@@ -829,9 +846,12 @@ var SqlDriver = class {
829
846
  if (this.isJsonField(type, field)) {
830
847
  jsonCols.push(name);
831
848
  }
832
- if (type === "boolean") {
849
+ if (type === "boolean" || type === "toggle") {
833
850
  booleanCols.push(name);
834
851
  }
852
+ if (NUMERIC_SCALAR_TYPES.has(type) && !field.multiple) {
853
+ numericCols.push(name);
854
+ }
835
855
  if (type === "date") {
836
856
  ((_a = this.dateFields)[tableName] ?? (_a[tableName] = /* @__PURE__ */ new Set())).add(name);
837
857
  }
@@ -850,6 +870,7 @@ var SqlDriver = class {
850
870
  }
851
871
  this.jsonFields[tableName] = jsonCols;
852
872
  this.booleanFields[tableName] = booleanCols;
873
+ this.numericFields[tableName] = numericCols;
853
874
  this.autoNumberFields[tableName] = autoNumberCols;
854
875
  this.tenantFieldByTable[tableName] = tenantField;
855
876
  let exists = await this.knex.schema.hasTable(tableName);
@@ -1201,11 +1222,35 @@ var SqlDriver = class {
1201
1222
  return null;
1202
1223
  };
1203
1224
  if (isDatetime) {
1225
+ if (!this.isSqlite) return value;
1204
1226
  const ms = toMs(value);
1205
1227
  return ms == null ? value : ms;
1206
1228
  }
1207
1229
  return this.toDateOnly(value);
1208
1230
  }
1231
+ /**
1232
+ * Public, dialect-correct temporal filter-value coercion for callers that
1233
+ * build SQL *outside* the normal `find()`/`applyFilters()` path — chiefly the
1234
+ * analytics native-SQL strategy, which compiles a raw `SELECT … WHERE col >= $N`
1235
+ * and binds the value directly, bypassing `coerceFilterValue`.
1236
+ *
1237
+ * Given a logical object (table) name, a field name and a filter value
1238
+ * (typically an ISO date/datetime string from a dashboard relative-date
1239
+ * token like `{12_months_ago}`), this returns the value in the column's
1240
+ * on-disk storage form:
1241
+ * - SQLite `Field.datetime` → epoch milliseconds (INTEGER), so the
1242
+ * comparison matches the stored integer rather than failing a
1243
+ * TEXT-vs-INTEGER affinity compare.
1244
+ * - `Field.date` (any dialect) → `YYYY-MM-DD` text.
1245
+ * - Native-timestamp dialects / non-temporal fields → value unchanged.
1246
+ *
1247
+ * This is a thin, intentionally narrow wrapper over the same `coerceFilterValue`
1248
+ * the driver already uses, so there is exactly one source of truth for the
1249
+ * storage convention and the analytics path can never drift from CRUD.
1250
+ */
1251
+ temporalFilterValue(objectName, field, value) {
1252
+ return this.coerceFilterValue(objectName, field, value);
1253
+ }
1209
1254
  applyFilters(builder, filters) {
1210
1255
  if (!filters) return;
1211
1256
  const table = this.tableNameForBuilder(builder);
@@ -1427,9 +1472,23 @@ var SqlDriver = class {
1427
1472
  case "number":
1428
1473
  case "currency":
1429
1474
  case "percent":
1475
+ // `rating`/`slider`/`progress` are authored as numeric scalars (a star
1476
+ // count, a slider position, a percent-of-completion). Without an explicit
1477
+ // case they fell to `default → table.string`, giving the column TEXT
1478
+ // affinity so SQLite coerced the written number to a string ('4' not 4) —
1479
+ // a silent type-fidelity leak the value-loss tests didn't catch. REAL
1480
+ // affinity round-trips them as JS numbers (#field-zoo).
1481
+ case "rating":
1482
+ case "slider":
1483
+ case "progress":
1430
1484
  col = table.float(name);
1431
1485
  break;
1486
+ // `toggle` is a boolean rendered as a switch. Same leak as above (TEXT
1487
+ // affinity stored '1'); a boolean column gives NUMERIC affinity and the
1488
+ // `booleanFields` read-coercion below converts the stored 1/0 back to a
1489
+ // real JS boolean.
1432
1490
  case "boolean":
1491
+ case "toggle":
1433
1492
  col = table.boolean(name);
1434
1493
  break;
1435
1494
  case "date":
@@ -1441,22 +1500,6 @@ var SqlDriver = class {
1441
1500
  case "time":
1442
1501
  col = table.time(name);
1443
1502
  break;
1444
- case "json":
1445
- case "object":
1446
- case "array":
1447
- case "image":
1448
- case "file":
1449
- case "avatar":
1450
- case "location":
1451
- case "address":
1452
- case "composite":
1453
- case "multiselect":
1454
- case "checkboxes":
1455
- case "tags":
1456
- case "repeater":
1457
- case "vector":
1458
- col = table.json(name);
1459
- break;
1460
1503
  case "lookup":
1461
1504
  col = table.string(name);
1462
1505
  if (field.reference_to) {
@@ -1474,7 +1517,7 @@ var SqlDriver = class {
1474
1517
  return;
1475
1518
  // Virtual — no column
1476
1519
  default:
1477
- col = table.string(name);
1520
+ col = JSON_COLUMN_TYPES.has(type) ? table.json(name) : table.string(name);
1478
1521
  }
1479
1522
  if (col) {
1480
1523
  if (field.unique) col.unique();
@@ -1639,6 +1682,16 @@ var SqlDriver = class {
1639
1682
  }
1640
1683
  }
1641
1684
  }
1685
+ const numericFields = this.numericFields[object];
1686
+ if (numericFields && numericFields.length > 0) {
1687
+ for (const field of numericFields) {
1688
+ const v = data[field];
1689
+ if (typeof v === "string" && v.trim() !== "") {
1690
+ const n = Number(v);
1691
+ if (!Number.isNaN(n)) data[field] = n;
1692
+ }
1693
+ }
1694
+ }
1642
1695
  }
1643
1696
  const dateFields = this.dateFields[object];
1644
1697
  if (dateFields && dateFields.size > 0) {