@objectstack/driver-sql 6.0.0 → 6.2.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.d.mts +25 -1
- package/dist/index.d.ts +25 -1
- package/dist/index.js +96 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +96 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -12,6 +12,8 @@ var SqlDriver = class {
|
|
|
12
12
|
this.version = "1.0.0";
|
|
13
13
|
this.jsonFields = {};
|
|
14
14
|
this.booleanFields = {};
|
|
15
|
+
this.dateFields = {};
|
|
16
|
+
this.datetimeFields = {};
|
|
15
17
|
this.tablesWithTimestamps = /* @__PURE__ */ new Set();
|
|
16
18
|
/**
|
|
17
19
|
* Autonumber field configs per table, captured during initObjects.
|
|
@@ -746,6 +748,7 @@ var SqlDriver = class {
|
|
|
746
748
|
* Batch-initialise tables from an array of object definitions.
|
|
747
749
|
*/
|
|
748
750
|
async initObjects(objects) {
|
|
751
|
+
var _a, _b;
|
|
749
752
|
await this.ensureDatabaseExists();
|
|
750
753
|
for (const obj of objects) {
|
|
751
754
|
const tableName = StorageNameMapping.resolveTableName(obj);
|
|
@@ -773,6 +776,12 @@ var SqlDriver = class {
|
|
|
773
776
|
if (type === "boolean") {
|
|
774
777
|
booleanCols.push(name);
|
|
775
778
|
}
|
|
779
|
+
if (type === "date") {
|
|
780
|
+
((_a = this.dateFields)[tableName] ?? (_a[tableName] = /* @__PURE__ */ new Set())).add(name);
|
|
781
|
+
}
|
|
782
|
+
if (type === "datetime") {
|
|
783
|
+
((_b = this.datetimeFields)[tableName] ?? (_b[tableName] = /* @__PURE__ */ new Set())).add(name);
|
|
784
|
+
}
|
|
776
785
|
if (type === "auto_number" || type === "autonumber") {
|
|
777
786
|
const fmt = typeof field.format === "string" && field.format ? field.format : "{0000}";
|
|
778
787
|
const m = fmt.match(/\{(0+)\}/);
|
|
@@ -960,19 +969,83 @@ var SqlDriver = class {
|
|
|
960
969
|
);
|
|
961
970
|
}
|
|
962
971
|
// ── Filter helpers ──────────────────────────────────────────────────────────
|
|
972
|
+
/**
|
|
973
|
+
* Resolve the underlying table name for a Knex query builder so we can
|
|
974
|
+
* look up column type metadata (date/datetime maps populated during
|
|
975
|
+
* `initObjects`). Returns null when the builder is not table-scoped yet.
|
|
976
|
+
*/
|
|
977
|
+
tableNameForBuilder(builder) {
|
|
978
|
+
const t = builder?._single?.table;
|
|
979
|
+
if (typeof t === "string") return t;
|
|
980
|
+
return null;
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* Normalise a filter value for a single column so the comparison the
|
|
984
|
+
* driver sends to SQLite matches the on-disk representation.
|
|
985
|
+
*
|
|
986
|
+
* The platform stores `Field.datetime()` values as INTEGER milliseconds
|
|
987
|
+
* (the result of passing a JS `Date` through better-sqlite3) but date
|
|
988
|
+
* macros like `{last_quarter_start}` expand to an ISO `YYYY-MM-DD` string
|
|
989
|
+
* client-side. Without coercion the SQL becomes `published_at >= '2026-…'`
|
|
990
|
+
* which collapses to a TEXT-vs-INTEGER affinity compare and never
|
|
991
|
+
* matches. We translate the ISO/Date/numeric inputs into the storage
|
|
992
|
+
* type so the comparison works.
|
|
993
|
+
*
|
|
994
|
+
* For `Field.date()` we keep ISO TEXT but normalise Date objects to
|
|
995
|
+
* `YYYY-MM-DD` for the same reason.
|
|
996
|
+
*/
|
|
997
|
+
coerceFilterValue(table, field, value) {
|
|
998
|
+
if (value == null || !table) return value;
|
|
999
|
+
if (Array.isArray(value)) return value.map((v) => this.coerceFilterValue(table, field, v));
|
|
1000
|
+
const isDatetime = this.datetimeFields[table]?.has(field);
|
|
1001
|
+
const isDate = this.dateFields[table]?.has(field);
|
|
1002
|
+
if (!isDatetime && !isDate) return value;
|
|
1003
|
+
const toMs = (v) => {
|
|
1004
|
+
if (v instanceof Date) return v.getTime();
|
|
1005
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
1006
|
+
if (typeof v === "string") {
|
|
1007
|
+
const trimmed = v.trim();
|
|
1008
|
+
if (trimmed === "") return null;
|
|
1009
|
+
if (/^-?\d+$/.test(trimmed)) {
|
|
1010
|
+
const n2 = Number(trimmed);
|
|
1011
|
+
if (Number.isFinite(n2)) return n2;
|
|
1012
|
+
}
|
|
1013
|
+
const iso = /^\d{4}-\d{2}-\d{2}$/.test(trimmed) ? `${trimmed}T00:00:00.000Z` : trimmed;
|
|
1014
|
+
const n = Date.parse(iso);
|
|
1015
|
+
return Number.isFinite(n) ? n : null;
|
|
1016
|
+
}
|
|
1017
|
+
return null;
|
|
1018
|
+
};
|
|
1019
|
+
if (isDatetime) {
|
|
1020
|
+
const ms = toMs(value);
|
|
1021
|
+
return ms == null ? value : ms;
|
|
1022
|
+
}
|
|
1023
|
+
if (value instanceof Date) {
|
|
1024
|
+
const y = value.getUTCFullYear();
|
|
1025
|
+
const m = String(value.getUTCMonth() + 1).padStart(2, "0");
|
|
1026
|
+
const d = String(value.getUTCDate()).padStart(2, "0");
|
|
1027
|
+
return `${y}-${m}-${d}`;
|
|
1028
|
+
}
|
|
1029
|
+
if (typeof value === "string") {
|
|
1030
|
+
const trimmed = value.trim();
|
|
1031
|
+
if (/^\d{4}-\d{2}-\d{2}/.test(trimmed)) return trimmed.slice(0, 10);
|
|
1032
|
+
}
|
|
1033
|
+
return value;
|
|
1034
|
+
}
|
|
963
1035
|
applyFilters(builder, filters) {
|
|
964
1036
|
if (!filters) return;
|
|
1037
|
+
const table = this.tableNameForBuilder(builder);
|
|
965
1038
|
if (!Array.isArray(filters) && typeof filters === "object") {
|
|
966
1039
|
const hasMongoOperators = Object.keys(filters).some(
|
|
967
1040
|
(k) => k.startsWith("$") || typeof filters[k] === "object" && filters[k] !== null && Object.keys(filters[k]).some((op) => op.startsWith("$"))
|
|
968
1041
|
);
|
|
969
1042
|
if (hasMongoOperators) {
|
|
970
|
-
this.applyFilterCondition(builder, filters);
|
|
1043
|
+
this.applyFilterCondition(builder, filters, "and", table);
|
|
971
1044
|
return;
|
|
972
1045
|
}
|
|
973
1046
|
for (const [key, value] of Object.entries(filters)) {
|
|
974
1047
|
if (["limit", "offset", "fields", "orderBy"].includes(key)) continue;
|
|
975
|
-
builder.where(key, value);
|
|
1048
|
+
builder.where(key, this.coerceFilterValue(table, key, value));
|
|
976
1049
|
}
|
|
977
1050
|
return;
|
|
978
1051
|
}
|
|
@@ -989,6 +1062,7 @@ var SqlDriver = class {
|
|
|
989
1062
|
const isCriterion = typeof fieldRaw === "string" && typeof op === "string";
|
|
990
1063
|
if (isCriterion) {
|
|
991
1064
|
const field = this.mapSortField(fieldRaw);
|
|
1065
|
+
const coerced = this.coerceFilterValue(table, field, value);
|
|
992
1066
|
const apply = (b) => {
|
|
993
1067
|
const method = nextJoin === "or" ? "orWhere" : "where";
|
|
994
1068
|
const methodIn = nextJoin === "or" ? "orWhereIn" : "whereIn";
|
|
@@ -999,19 +1073,19 @@ var SqlDriver = class {
|
|
|
999
1073
|
}
|
|
1000
1074
|
switch (op) {
|
|
1001
1075
|
case "=":
|
|
1002
|
-
b[method](field,
|
|
1076
|
+
b[method](field, coerced);
|
|
1003
1077
|
break;
|
|
1004
1078
|
case "!=":
|
|
1005
|
-
b[method](field, "<>",
|
|
1079
|
+
b[method](field, "<>", coerced);
|
|
1006
1080
|
break;
|
|
1007
1081
|
case "in":
|
|
1008
|
-
b[methodIn](field,
|
|
1082
|
+
b[methodIn](field, coerced);
|
|
1009
1083
|
break;
|
|
1010
1084
|
case "nin":
|
|
1011
|
-
b[methodNotIn](field,
|
|
1085
|
+
b[methodNotIn](field, coerced);
|
|
1012
1086
|
break;
|
|
1013
1087
|
default:
|
|
1014
|
-
b[method](field, op,
|
|
1088
|
+
b[method](field, op, coerced);
|
|
1015
1089
|
}
|
|
1016
1090
|
};
|
|
1017
1091
|
apply(builder);
|
|
@@ -1025,14 +1099,15 @@ var SqlDriver = class {
|
|
|
1025
1099
|
}
|
|
1026
1100
|
}
|
|
1027
1101
|
}
|
|
1028
|
-
applyFilterCondition(builder, condition, logicalOp = "and") {
|
|
1102
|
+
applyFilterCondition(builder, condition, logicalOp = "and", tableHint) {
|
|
1029
1103
|
if (!condition || typeof condition !== "object") return;
|
|
1104
|
+
const table = tableHint ?? this.tableNameForBuilder(builder);
|
|
1030
1105
|
for (const [key, value] of Object.entries(condition)) {
|
|
1031
1106
|
if (key === "$and" && Array.isArray(value)) {
|
|
1032
1107
|
builder.where((qb) => {
|
|
1033
1108
|
for (const sub of value) {
|
|
1034
1109
|
qb.where((subQb) => {
|
|
1035
|
-
this.applyFilterCondition(subQb, sub, "and");
|
|
1110
|
+
this.applyFilterCondition(subQb, sub, "and", table);
|
|
1036
1111
|
});
|
|
1037
1112
|
}
|
|
1038
1113
|
});
|
|
@@ -1041,7 +1116,7 @@ var SqlDriver = class {
|
|
|
1041
1116
|
builder[method]((qb) => {
|
|
1042
1117
|
for (const sub of value) {
|
|
1043
1118
|
qb.orWhere((subQb) => {
|
|
1044
|
-
this.applyFilterCondition(subQb, sub, "or");
|
|
1119
|
+
this.applyFilterCondition(subQb, sub, "or", table);
|
|
1045
1120
|
});
|
|
1046
1121
|
}
|
|
1047
1122
|
});
|
|
@@ -1049,46 +1124,47 @@ var SqlDriver = class {
|
|
|
1049
1124
|
const field = this.mapSortField(key);
|
|
1050
1125
|
for (const [op, opValue] of Object.entries(value)) {
|
|
1051
1126
|
const method = logicalOp === "or" ? "orWhere" : "where";
|
|
1127
|
+
const coerced = this.coerceFilterValue(table, field, opValue);
|
|
1052
1128
|
switch (op) {
|
|
1053
1129
|
case "$eq":
|
|
1054
|
-
builder[method](field,
|
|
1130
|
+
builder[method](field, coerced);
|
|
1055
1131
|
break;
|
|
1056
1132
|
case "$ne":
|
|
1057
|
-
builder[method](field, "<>",
|
|
1133
|
+
builder[method](field, "<>", coerced);
|
|
1058
1134
|
break;
|
|
1059
1135
|
case "$gt":
|
|
1060
|
-
builder[method](field, ">",
|
|
1136
|
+
builder[method](field, ">", coerced);
|
|
1061
1137
|
break;
|
|
1062
1138
|
case "$gte":
|
|
1063
|
-
builder[method](field, ">=",
|
|
1139
|
+
builder[method](field, ">=", coerced);
|
|
1064
1140
|
break;
|
|
1065
1141
|
case "$lt":
|
|
1066
|
-
builder[method](field, "<",
|
|
1142
|
+
builder[method](field, "<", coerced);
|
|
1067
1143
|
break;
|
|
1068
1144
|
case "$lte":
|
|
1069
|
-
builder[method](field, "<=",
|
|
1145
|
+
builder[method](field, "<=", coerced);
|
|
1070
1146
|
break;
|
|
1071
1147
|
case "$in": {
|
|
1072
1148
|
const mIn = logicalOp === "or" ? "orWhereIn" : "whereIn";
|
|
1073
|
-
builder[mIn](field,
|
|
1149
|
+
builder[mIn](field, coerced);
|
|
1074
1150
|
break;
|
|
1075
1151
|
}
|
|
1076
1152
|
case "$nin": {
|
|
1077
1153
|
const mNotIn = logicalOp === "or" ? "orWhereNotIn" : "whereNotIn";
|
|
1078
|
-
builder[mNotIn](field,
|
|
1154
|
+
builder[mNotIn](field, coerced);
|
|
1079
1155
|
break;
|
|
1080
1156
|
}
|
|
1081
1157
|
case "$contains":
|
|
1082
1158
|
builder[method](field, "like", `%${opValue}%`);
|
|
1083
1159
|
break;
|
|
1084
1160
|
default:
|
|
1085
|
-
builder[method](field,
|
|
1161
|
+
builder[method](field, coerced);
|
|
1086
1162
|
}
|
|
1087
1163
|
}
|
|
1088
1164
|
} else {
|
|
1089
1165
|
const field = this.mapSortField(key);
|
|
1090
1166
|
const method = logicalOp === "or" ? "orWhere" : "where";
|
|
1091
|
-
builder[method](field, value);
|
|
1167
|
+
builder[method](field, this.coerceFilterValue(table, field, value));
|
|
1092
1168
|
}
|
|
1093
1169
|
}
|
|
1094
1170
|
}
|