@zenstackhq/orm 3.7.0-beta.1 → 3.7.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.cjs +379 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +334 -37
- package/dist/index.d.mts +334 -37
- package/dist/index.mjs +378 -70
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -10
package/dist/index.mjs
CHANGED
|
@@ -531,6 +531,8 @@ const FILTER_PROPERTY_TO_KIND = {
|
|
|
531
531
|
array_contains: "Json",
|
|
532
532
|
array_starts_with: "Json",
|
|
533
533
|
array_ends_with: "Json",
|
|
534
|
+
fuzzy: "Fuzzy",
|
|
535
|
+
fts: "FullText",
|
|
534
536
|
has: "List",
|
|
535
537
|
hasEvery: "List",
|
|
536
538
|
hasSome: "List",
|
|
@@ -550,6 +552,19 @@ let TransactionIsolationLevel = /* @__PURE__ */ function(TransactionIsolationLev
|
|
|
550
552
|
return TransactionIsolationLevel;
|
|
551
553
|
}({});
|
|
552
554
|
/**
|
|
555
|
+
* Symbol used as a type-only key on `ClientContract` to brand the `ExtQueryArgs`
|
|
556
|
+
* generic slot. Hidden from member-access autocomplete since symbol keys are
|
|
557
|
+
* not surfaced. Consumed by `InferExtQueryArgs` to recover the slot.
|
|
558
|
+
* @internal
|
|
559
|
+
*/
|
|
560
|
+
const ExtQueryArgsMarker = Symbol("zenstack.client.extQueryArgs");
|
|
561
|
+
/**
|
|
562
|
+
* Symbol used as a type-only key on `ClientContract` to brand the `ExtResult`
|
|
563
|
+
* generic slot. Consumed by `InferExtResult` to recover the slot.
|
|
564
|
+
* @internal
|
|
565
|
+
*/
|
|
566
|
+
const ExtResultMarker = Symbol("zenstack.client.extResult");
|
|
567
|
+
/**
|
|
553
568
|
* CRUD operations.
|
|
554
569
|
*/
|
|
555
570
|
const CRUD = [
|
|
@@ -572,8 +587,13 @@ var BaseCrudDialect = class {
|
|
|
572
587
|
}
|
|
573
588
|
/**
|
|
574
589
|
* Transforms input value before sending to database.
|
|
590
|
+
*
|
|
591
|
+
* `fieldDef` is optional so existing callers that don't have it stay
|
|
592
|
+
* source-compatible. Dialects can use it to inspect `@db.*` native-type
|
|
593
|
+
* attributes (e.g. to format `@db.Time` values as `HH:MM:SS` rather than
|
|
594
|
+
* full ISO timestamps).
|
|
575
595
|
*/
|
|
576
|
-
transformInput(value, _type, _forArrayField) {
|
|
596
|
+
transformInput(value, _type, _forArrayField, _fieldDef) {
|
|
577
597
|
return value;
|
|
578
598
|
}
|
|
579
599
|
/**
|
|
@@ -616,7 +636,17 @@ var BaseCrudDialect = class {
|
|
|
616
636
|
if (existingOrderBy.length > 0 && !alreadySatisfied) effectiveOrderBy = [...distinctFields.map((f) => ({ [f]: "asc" })), ...existingOrderBy];
|
|
617
637
|
}
|
|
618
638
|
result = this.buildOrderBy(result, model, modelAlias, effectiveOrderBy, negateOrderBy, take);
|
|
619
|
-
if (args.cursor)
|
|
639
|
+
if (args.cursor) {
|
|
640
|
+
if (effectiveOrderBy) {
|
|
641
|
+
const offendingKey = enumerate(effectiveOrderBy).map((ob) => {
|
|
642
|
+
if (typeof ob !== "object" || ob === null) return void 0;
|
|
643
|
+
if ("_fuzzyRelevance" in ob) return "_fuzzyRelevance";
|
|
644
|
+
if ("_ftsRelevance" in ob) return "_ftsRelevance";
|
|
645
|
+
}).find((k) => k !== void 0);
|
|
646
|
+
if (offendingKey) throw createNotSupportedError(`cursor pagination cannot be combined with "${offendingKey}" ordering`);
|
|
647
|
+
}
|
|
648
|
+
result = this.buildCursorFilter(model, result, args.cursor, effectiveOrderBy, negateOrderBy, modelAlias);
|
|
649
|
+
}
|
|
620
650
|
return result;
|
|
621
651
|
}
|
|
622
652
|
buildFilter(model, modelAlias, where) {
|
|
@@ -756,7 +786,7 @@ var BaseCrudDialect = class {
|
|
|
756
786
|
for (const [key, _value] of Object.entries(payload)) {
|
|
757
787
|
if (_value === void 0) continue;
|
|
758
788
|
invariant(fieldDef.array, "Field must be an array type to build array filter");
|
|
759
|
-
const value = this.transformInput(_value, fieldType, true);
|
|
789
|
+
const value = this.transformInput(_value, fieldType, true, fieldDef);
|
|
760
790
|
let receiver = fieldRef;
|
|
761
791
|
if (isEnum(this.schema, fieldType)) receiver = this.eb.cast(fieldRef, sql.raw("text[]"));
|
|
762
792
|
const buildArray = (value) => {
|
|
@@ -791,7 +821,7 @@ var BaseCrudDialect = class {
|
|
|
791
821
|
if (payload instanceof DbNullClass || payload instanceof JsonNullClass || payload instanceof AnyNullClass) return this.buildJsonValueFilterClause(fieldRef, payload);
|
|
792
822
|
return this.buildJsonFilter(fieldRef, payload, fieldDef);
|
|
793
823
|
}
|
|
794
|
-
return match(fieldDef.type).with("String", () => this.buildStringFilter(fieldRef, payload)).with(P.union("Int", "Float", "Decimal", "BigInt"), (type) => this.buildNumberFilter(fieldRef, type, payload)).with("Boolean", () => this.buildBooleanFilter(fieldRef, payload)).with("DateTime", () => this.buildDateTimeFilter(fieldRef, payload)).with("Bytes", () => this.buildBytesFilter(fieldRef, payload)).with("Json", () => this.buildJsonFilter(fieldRef, payload, fieldDef)).with("Unsupported", () => {
|
|
824
|
+
return match(fieldDef.type).with("String", () => this.buildStringFilter(fieldRef, payload, fieldDef)).with(P.union("Int", "Float", "Decimal", "BigInt"), (type) => this.buildNumberFilter(fieldRef, type, payload)).with("Boolean", () => this.buildBooleanFilter(fieldRef, payload)).with("DateTime", () => this.buildDateTimeFilter(fieldRef, payload)).with("Bytes", () => this.buildBytesFilter(fieldRef, payload)).with("Json", () => this.buildJsonFilter(fieldRef, payload, fieldDef)).with("Unsupported", () => {
|
|
795
825
|
throw createInvalidInputError(`Unsupported field cannot be used in filters`);
|
|
796
826
|
}).exhaustive();
|
|
797
827
|
}
|
|
@@ -959,13 +989,23 @@ var BaseCrudDialect = class {
|
|
|
959
989
|
consumedKeys
|
|
960
990
|
};
|
|
961
991
|
}
|
|
962
|
-
buildStringFilter(fieldRef, payload) {
|
|
992
|
+
buildStringFilter(fieldRef, payload, fieldDef) {
|
|
963
993
|
let mode;
|
|
964
994
|
if (payload && typeof payload === "object" && "mode" in payload) mode = payload.mode;
|
|
965
|
-
const { conditions, consumedKeys } = this.buildStandardFilter("String", payload, mode === "insensitive" ? this.eb.fn("lower", [fieldRef]) : fieldRef, (value) => this.prepStringCasing(this.eb, value, mode), (value) => this.buildStringFilter(fieldRef, value));
|
|
995
|
+
const { conditions, consumedKeys } = this.buildStandardFilter("String", payload, mode === "insensitive" ? this.eb.fn("lower", [fieldRef]) : fieldRef, (value) => this.prepStringCasing(this.eb, value, mode), (value) => this.buildStringFilter(fieldRef, value, fieldDef));
|
|
966
996
|
if (payload && typeof payload === "object") for (const [key, value] of Object.entries(payload)) {
|
|
967
997
|
if (key === "mode" || consumedKeys.includes(key)) continue;
|
|
968
998
|
if (value === void 0) continue;
|
|
999
|
+
if (key === "fuzzy") {
|
|
1000
|
+
invariant(fieldDef?.fuzzy === true, `field "${fieldDef?.name ?? "<unknown>"}" is not fuzzy-searchable; add the \`@fuzzy\` attribute to use the \`fuzzy\` filter`);
|
|
1001
|
+
conditions.push(this.buildFuzzyFilter(fieldRef, this.normalizeFuzzyOptions(value)));
|
|
1002
|
+
continue;
|
|
1003
|
+
}
|
|
1004
|
+
if (key === "fts") {
|
|
1005
|
+
invariant(fieldDef?.fullText === true, `field "${fieldDef?.name ?? "<unknown>"}" is not full-text-searchable; add the \`@fullText\` attribute to use the \`fts\` filter`);
|
|
1006
|
+
conditions.push(this.buildFullTextFilter(fieldRef, value));
|
|
1007
|
+
continue;
|
|
1008
|
+
}
|
|
969
1009
|
invariant(typeof value === "string", `${key} value must be a string`);
|
|
970
1010
|
const escapedValue = this.escapeLikePattern(value);
|
|
971
1011
|
const condition = match(key).with("contains", () => this.buildStringLike(fieldRef, `%${escapedValue}%`, mode === "insensitive")).with("startsWith", () => this.buildStringLike(fieldRef, `${escapedValue}%`, mode === "insensitive")).with("endsWith", () => this.buildStringLike(fieldRef, `%${escapedValue}`, mode === "insensitive")).otherwise(() => {
|
|
@@ -1034,6 +1074,14 @@ var BaseCrudDialect = class {
|
|
|
1034
1074
|
enumerate(orderBy).forEach((orderBy, index) => {
|
|
1035
1075
|
for (const [field, value] of Object.entries(orderBy)) {
|
|
1036
1076
|
if (!value) continue;
|
|
1077
|
+
if (field === "_fuzzyRelevance") {
|
|
1078
|
+
result = this.applyFuzzyRelevanceOrderBy(result, model, modelAlias, value, negated, buildFieldRef);
|
|
1079
|
+
continue;
|
|
1080
|
+
}
|
|
1081
|
+
if (field === "_ftsRelevance") {
|
|
1082
|
+
result = this.applyFtsRelevanceOrderBy(result, model, modelAlias, value, negated, buildFieldRef);
|
|
1083
|
+
continue;
|
|
1084
|
+
}
|
|
1037
1085
|
if ([
|
|
1038
1086
|
"_count",
|
|
1039
1087
|
"_avg",
|
|
@@ -1041,47 +1089,84 @@ var BaseCrudDialect = class {
|
|
|
1041
1089
|
"_min",
|
|
1042
1090
|
"_max"
|
|
1043
1091
|
].includes(field)) {
|
|
1044
|
-
|
|
1045
|
-
for (const [k, v] of Object.entries(value)) {
|
|
1046
|
-
invariant(v === "asc" || v === "desc", `invalid orderBy value for field "${field}"`);
|
|
1047
|
-
result = result.orderBy((eb) => aggregate(eb, buildFieldRef(model, k, modelAlias), field), this.negateSort(v, negated));
|
|
1048
|
-
}
|
|
1092
|
+
result = this.applyAggregationOrderBy(result, model, modelAlias, field, value, negated, buildFieldRef);
|
|
1049
1093
|
continue;
|
|
1050
1094
|
}
|
|
1051
1095
|
const fieldDef = requireField(this.schema, model, field);
|
|
1052
|
-
if (!fieldDef.relation)
|
|
1053
|
-
|
|
1054
|
-
if (value === "asc" || value === "desc") result = result.orderBy(fieldRef, this.negateSort(value, negated));
|
|
1055
|
-
else if (typeof value === "object" && "nulls" in value && "sort" in value && (value.sort === "asc" || value.sort === "desc") && (value.nulls === "first" || value.nulls === "last")) result = this.buildOrderByField(result, fieldRef, this.negateSort(value.sort, negated), value.nulls);
|
|
1056
|
-
} else {
|
|
1057
|
-
const relationModel = fieldDef.type;
|
|
1058
|
-
if (fieldDef.array) {
|
|
1059
|
-
if (typeof value !== "object") throw createInvalidInputError(`invalid orderBy value for field "${field}"`);
|
|
1060
|
-
if ("_count" in value) {
|
|
1061
|
-
invariant(value._count === "asc" || value._count === "desc", "invalid orderBy value for field \"_count\"");
|
|
1062
|
-
const sort = this.negateSort(value._count, negated);
|
|
1063
|
-
result = result.orderBy((eb) => {
|
|
1064
|
-
const subQueryAlias = tmpAlias(`${modelAlias}$ob$${field}$ct`);
|
|
1065
|
-
let subQuery = this.buildSelectModel(relationModel, subQueryAlias);
|
|
1066
|
-
const joinPairs = buildJoinPairs(this.schema, model, modelAlias, field, subQueryAlias);
|
|
1067
|
-
subQuery = subQuery.where(() => this.and(...joinPairs.map(([left, right]) => eb(this.eb.ref(left), "=", this.eb.ref(right)))));
|
|
1068
|
-
subQuery = subQuery.select(() => eb.fn.count(eb.lit(1)).as("_count"));
|
|
1069
|
-
return subQuery;
|
|
1070
|
-
}, sort);
|
|
1071
|
-
}
|
|
1072
|
-
} else {
|
|
1073
|
-
const joinAlias = tmpAlias(`${modelAlias}$ob$${index}`);
|
|
1074
|
-
result = result.leftJoin(`${relationModel} as ${joinAlias}`, (join) => {
|
|
1075
|
-
const joinPairs = buildJoinPairs(this.schema, model, modelAlias, field, joinAlias);
|
|
1076
|
-
return join.on((eb) => this.and(...joinPairs.map(([left, right]) => eb(this.eb.ref(left), "=", this.eb.ref(right)))));
|
|
1077
|
-
});
|
|
1078
|
-
result = this.buildOrderBy(result, relationModel, joinAlias, value, negated, take);
|
|
1079
|
-
}
|
|
1080
|
-
}
|
|
1096
|
+
if (!fieldDef.relation) result = this.applyScalarOrderBy(result, model, modelAlias, field, value, negated, buildFieldRef);
|
|
1097
|
+
else result = this.applyRelationOrderBy(result, model, modelAlias, field, fieldDef, value, negated, take, index);
|
|
1081
1098
|
}
|
|
1082
1099
|
});
|
|
1083
1100
|
return result;
|
|
1084
1101
|
}
|
|
1102
|
+
applyRelationOrderBy(query, model, modelAlias, field, fieldDef, value, negated, take, index) {
|
|
1103
|
+
const relationModel = fieldDef.type;
|
|
1104
|
+
if (fieldDef.array) {
|
|
1105
|
+
if (typeof value !== "object") throw createInvalidInputError(`invalid orderBy value for field "${field}"`);
|
|
1106
|
+
if ("_count" in value) {
|
|
1107
|
+
invariant(value._count === "asc" || value._count === "desc", "invalid orderBy value for field \"_count\"");
|
|
1108
|
+
const sort = this.negateSort(value._count, negated);
|
|
1109
|
+
return query.orderBy((eb) => {
|
|
1110
|
+
const subQueryAlias = tmpAlias(`${modelAlias}$ob$${field}$ct`);
|
|
1111
|
+
let subQuery = this.buildSelectModel(relationModel, subQueryAlias);
|
|
1112
|
+
const joinPairs = buildJoinPairs(this.schema, model, modelAlias, field, subQueryAlias);
|
|
1113
|
+
subQuery = subQuery.where(() => this.and(...joinPairs.map(([left, right]) => eb(this.eb.ref(left), "=", this.eb.ref(right)))));
|
|
1114
|
+
subQuery = subQuery.select(() => eb.fn.count(eb.lit(1)).as("_count"));
|
|
1115
|
+
return subQuery;
|
|
1116
|
+
}, sort);
|
|
1117
|
+
}
|
|
1118
|
+
return query;
|
|
1119
|
+
}
|
|
1120
|
+
const joinAlias = tmpAlias(`${modelAlias}$ob$${index}`);
|
|
1121
|
+
const joined = query.leftJoin(`${relationModel} as ${joinAlias}`, (join) => {
|
|
1122
|
+
const joinPairs = buildJoinPairs(this.schema, model, modelAlias, field, joinAlias);
|
|
1123
|
+
return join.on((eb) => this.and(...joinPairs.map(([left, right]) => eb(this.eb.ref(left), "=", this.eb.ref(right)))));
|
|
1124
|
+
});
|
|
1125
|
+
return this.buildOrderBy(joined, relationModel, joinAlias, value, negated, take);
|
|
1126
|
+
}
|
|
1127
|
+
applyScalarOrderBy(query, model, modelAlias, field, value, negated, buildFieldRef) {
|
|
1128
|
+
const fieldRef = buildFieldRef(model, field, modelAlias);
|
|
1129
|
+
if (value === "asc" || value === "desc") return query.orderBy(fieldRef, this.negateSort(value, negated));
|
|
1130
|
+
if (typeof value === "object" && "sort" in value && (value.sort === "asc" || value.sort === "desc")) {
|
|
1131
|
+
const sort = this.negateSort(value.sort, negated);
|
|
1132
|
+
if (value.nulls === "first" || value.nulls === "last") return this.buildOrderByField(query, fieldRef, sort, value.nulls);
|
|
1133
|
+
else return query.orderBy(fieldRef, sort);
|
|
1134
|
+
}
|
|
1135
|
+
return query;
|
|
1136
|
+
}
|
|
1137
|
+
applyAggregationOrderBy(query, model, modelAlias, field, value, negated, buildFieldRef) {
|
|
1138
|
+
invariant(typeof value === "object", `invalid orderBy value for field "${field}"`);
|
|
1139
|
+
let result = query;
|
|
1140
|
+
for (const [k, v] of Object.entries(value)) {
|
|
1141
|
+
invariant(v === "asc" || v === "desc", `invalid orderBy value for field "${field}"`);
|
|
1142
|
+
result = result.orderBy((eb) => aggregate(eb, buildFieldRef(model, k, modelAlias), field), this.negateSort(v, negated));
|
|
1143
|
+
}
|
|
1144
|
+
return result;
|
|
1145
|
+
}
|
|
1146
|
+
applyFuzzyRelevanceOrderBy(query, model, modelAlias, value, negated, buildFieldRef) {
|
|
1147
|
+
invariant(typeof value === "object" && "fields" in value && "search" in value && "sort" in value, "invalid orderBy value for \"_fuzzyRelevance\"");
|
|
1148
|
+
invariant(Array.isArray(value.fields) && value.fields.length > 0, "_fuzzyRelevance.fields must be a non-empty array");
|
|
1149
|
+
invariant(value.sort === "asc" || value.sort === "desc", "invalid sort value for \"_fuzzyRelevance\"");
|
|
1150
|
+
invariant(typeof value.search === "string" && value.search.length > 0, "_fuzzyRelevance.search must be a non-empty string");
|
|
1151
|
+
const mode = value.mode ?? "simple";
|
|
1152
|
+
invariant(mode === "simple" || mode === "word" || mode === "strictWord", "_fuzzyRelevance.mode must be \"simple\", \"word\" or \"strictWord\"");
|
|
1153
|
+
const unaccent = value.unaccent ?? false;
|
|
1154
|
+
invariant(typeof unaccent === "boolean", "_fuzzyRelevance.unaccent must be a boolean");
|
|
1155
|
+
for (const fieldName of value.fields) invariant(requireField(this.schema, model, fieldName).fuzzy === true, `field "${fieldName}" is not fuzzy-searchable; add the \`@fuzzy\` attribute to use it in \`_fuzzyRelevance\``);
|
|
1156
|
+
const fieldRefs = value.fields.map((f) => buildFieldRef(model, f, modelAlias));
|
|
1157
|
+
return this.buildFuzzyRelevanceOrderBy(query, fieldRefs, value.search, this.negateSort(value.sort, negated), mode, unaccent);
|
|
1158
|
+
}
|
|
1159
|
+
applyFtsRelevanceOrderBy(query, model, modelAlias, value, negated, buildFieldRef) {
|
|
1160
|
+
invariant(typeof value === "object" && "fields" in value && "search" in value && "sort" in value, "invalid orderBy value for \"_ftsRelevance\"");
|
|
1161
|
+
invariant(Array.isArray(value.fields) && value.fields.length > 0, "_ftsRelevance.fields must be a non-empty array");
|
|
1162
|
+
invariant(value.sort === "asc" || value.sort === "desc", "invalid sort value for \"_ftsRelevance\"");
|
|
1163
|
+
invariant(typeof value.search === "string" && value.search.length > 0, "_ftsRelevance.search must be a non-empty string");
|
|
1164
|
+
if (value.config !== void 0) invariant(typeof value.config === "string" && value.config.length > 0, "_ftsRelevance.config must be a non-empty string");
|
|
1165
|
+
const config = value.config;
|
|
1166
|
+
for (const fieldName of value.fields) invariant(requireField(this.schema, model, fieldName).fullText === true, `field "${fieldName}" is not full-text-searchable; add the \`@fullText\` attribute to use it in \`_ftsRelevance\``);
|
|
1167
|
+
const fieldRefs = value.fields.map((f) => buildFieldRef(model, f, modelAlias));
|
|
1168
|
+
return this.buildFtsRelevanceOrderBy(query, fieldRefs, value.search, config, this.negateSort(value.sort, negated));
|
|
1169
|
+
}
|
|
1085
1170
|
buildSelectAllFields(model, query, omit, modelAlias) {
|
|
1086
1171
|
let result = query;
|
|
1087
1172
|
for (const fieldDef of getModelFields(this.schema, model, {
|
|
@@ -1224,6 +1309,27 @@ var BaseCrudDialect = class {
|
|
|
1224
1309
|
buildComparison(left, _leftFieldDef, op, right, _rightFieldDef) {
|
|
1225
1310
|
return this.eb(left, op, right);
|
|
1226
1311
|
}
|
|
1312
|
+
/**
|
|
1313
|
+
* Validate the user-provided fuzzy filter payload and apply defaults so dialects
|
|
1314
|
+
* always receive a fully-resolved {@link FuzzyFilterOptions} value.
|
|
1315
|
+
*/
|
|
1316
|
+
normalizeFuzzyOptions(value) {
|
|
1317
|
+
invariant(value !== null && typeof value === "object" && !Array.isArray(value), "fuzzy filter must be an object with at least a \"search\" field");
|
|
1318
|
+
const raw = value;
|
|
1319
|
+
invariant(typeof raw["search"] === "string" && raw["search"].length > 0, "fuzzy.search must be a non-empty string");
|
|
1320
|
+
const mode = raw["mode"] ?? "simple";
|
|
1321
|
+
invariant(mode === "simple" || mode === "word" || mode === "strictWord", "fuzzy.mode must be \"simple\", \"word\" or \"strictWord\"");
|
|
1322
|
+
const threshold = raw["threshold"];
|
|
1323
|
+
if (threshold !== void 0) invariant(typeof threshold === "number" && threshold >= 0 && threshold <= 1, "fuzzy.threshold must be a number between 0 and 1");
|
|
1324
|
+
const unaccent = raw["unaccent"] ?? false;
|
|
1325
|
+
invariant(typeof unaccent === "boolean", "fuzzy.unaccent must be a boolean");
|
|
1326
|
+
return {
|
|
1327
|
+
search: raw["search"],
|
|
1328
|
+
mode,
|
|
1329
|
+
threshold,
|
|
1330
|
+
unaccent
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1227
1333
|
};
|
|
1228
1334
|
//#endregion
|
|
1229
1335
|
//#region src/client/crud/dialects/lateral-join-dialect-base.ts
|
|
@@ -1508,9 +1614,32 @@ var MySqlCrudDialect = class extends LateralJoinDialectBase {
|
|
|
1508
1614
|
}
|
|
1509
1615
|
return result;
|
|
1510
1616
|
}
|
|
1617
|
+
buildFuzzyFilter(_fieldRef, _options) {
|
|
1618
|
+
throw createNotSupportedError("\"fuzzy\" filter is not supported by the \"mysql\" provider");
|
|
1619
|
+
}
|
|
1620
|
+
buildFuzzyRelevanceOrderBy(_query, _fieldRefs, _search, _sort, _mode, _unaccent) {
|
|
1621
|
+
throw createNotSupportedError("\"_fuzzyRelevance\" ordering is not supported by the \"mysql\" provider");
|
|
1622
|
+
}
|
|
1623
|
+
buildFullTextFilter(_fieldRef, _payload) {
|
|
1624
|
+
throw createNotSupportedError("\"fts\" filter is not supported by the \"mysql\" provider");
|
|
1625
|
+
}
|
|
1626
|
+
buildFtsRelevanceOrderBy(_query, _fieldRefs, _search, _config, _sort) {
|
|
1627
|
+
throw createNotSupportedError("\"_ftsRelevance\" ordering is not supported by the \"mysql\" provider");
|
|
1628
|
+
}
|
|
1511
1629
|
};
|
|
1512
1630
|
//#endregion
|
|
1513
1631
|
//#region src/client/crud/dialects/postgresql.ts
|
|
1632
|
+
/**
|
|
1633
|
+
* Formats a JS `Date` as a Postgres TIME / TIMETZ literal (`HH:MM:SS.fff`,
|
|
1634
|
+
* optionally with `+ZZ:ZZ` for TIMETZ). Reads UTC components so the value
|
|
1635
|
+
* round-trips with ISO-input parsing — callers anchor time-only inputs to
|
|
1636
|
+
* the Unix epoch.
|
|
1637
|
+
*/
|
|
1638
|
+
function formatTimeOfDay(date, withTimezone) {
|
|
1639
|
+
const pad = (n, w = 2) => String(n).padStart(w, "0");
|
|
1640
|
+
const time = `${pad(date.getUTCHours())}:${pad(date.getUTCMinutes())}:${pad(date.getUTCSeconds())}.${pad(date.getUTCMilliseconds(), 3)}`;
|
|
1641
|
+
return withTimezone ? `${time}+00:00` : time;
|
|
1642
|
+
}
|
|
1514
1643
|
var PostgresCrudDialect = class PostgresCrudDialect extends LateralJoinDialectBase {
|
|
1515
1644
|
static typeParserOverrideApplied = false;
|
|
1516
1645
|
zmodelToSqlTypeMap = {
|
|
@@ -1604,7 +1733,7 @@ var PostgresCrudDialect = class PostgresCrudDialect extends LateralJoinDialectBa
|
|
|
1604
1733
|
get insertIgnoreMethod() {
|
|
1605
1734
|
return "onConflict";
|
|
1606
1735
|
}
|
|
1607
|
-
transformInput(value, type, forArrayField) {
|
|
1736
|
+
transformInput(value, type, forArrayField, fieldDef) {
|
|
1608
1737
|
if (value === void 0) return value;
|
|
1609
1738
|
if (value instanceof JsonNullClass) return "null";
|
|
1610
1739
|
else if (value instanceof DbNullClass) return null;
|
|
@@ -1612,9 +1741,15 @@ var PostgresCrudDialect = class PostgresCrudDialect extends LateralJoinDialectBa
|
|
|
1612
1741
|
if (isTypeDef(this.schema, type)) if (typeof value !== "string") return JSON.stringify(value);
|
|
1613
1742
|
else return value;
|
|
1614
1743
|
else if (Array.isArray(value)) if (type === "Json" && !forArrayField) return JSON.stringify(value);
|
|
1615
|
-
else return value.map((v) => this.transformInput(v, type, false));
|
|
1744
|
+
else return value.map((v) => this.transformInput(v, type, false, fieldDef));
|
|
1616
1745
|
else switch (type) {
|
|
1617
|
-
case "DateTime":
|
|
1746
|
+
case "DateTime": {
|
|
1747
|
+
const date = value instanceof Date ? value : typeof value === "string" ? new Date(value) : null;
|
|
1748
|
+
if (date === null || isNaN(date.getTime())) return value;
|
|
1749
|
+
const dbAttrName = fieldDef?.attributes?.find((a) => a.name.startsWith("@db."))?.name;
|
|
1750
|
+
if (dbAttrName === "@db.Time" || dbAttrName === "@db.Timetz") return formatTimeOfDay(date, dbAttrName === "@db.Timetz");
|
|
1751
|
+
return date.toISOString();
|
|
1752
|
+
}
|
|
1618
1753
|
case "Decimal": return value !== null ? value.toString() : value;
|
|
1619
1754
|
case "Json": if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") return JSON.stringify(value);
|
|
1620
1755
|
else return value;
|
|
@@ -1786,6 +1921,72 @@ var PostgresCrudDialect = class PostgresCrudDialect extends LateralJoinDialectBa
|
|
|
1786
1921
|
return ob;
|
|
1787
1922
|
});
|
|
1788
1923
|
}
|
|
1924
|
+
/**
|
|
1925
|
+
* Wraps an expression with `unaccent(lower(...))` or just `lower(...)` depending on
|
|
1926
|
+
* whether the user opted into accent-insensitive matching. The lowering is always
|
|
1927
|
+
* applied so trigram comparisons are case-insensitive on both sides.
|
|
1928
|
+
*/
|
|
1929
|
+
normalizeForTrigram(expr, applyUnaccent) {
|
|
1930
|
+
return applyUnaccent ? sql`unaccent(lower(${expr}))` : sql`lower(${expr})`;
|
|
1931
|
+
}
|
|
1932
|
+
buildFuzzyFilter(fieldRef, options) {
|
|
1933
|
+
const fieldExpr = this.normalizeForTrigram(fieldRef, options.unaccent);
|
|
1934
|
+
const valueExpr = this.normalizeForTrigram(sql.val(options.search), options.unaccent);
|
|
1935
|
+
if (options.threshold === void 0) switch (options.mode) {
|
|
1936
|
+
case "simple": return sql`${fieldExpr} % ${valueExpr}`;
|
|
1937
|
+
case "word": return sql`${valueExpr} <% ${fieldExpr}`;
|
|
1938
|
+
case "strictWord": return sql`${valueExpr} <<% ${fieldExpr}`;
|
|
1939
|
+
}
|
|
1940
|
+
const threshold = sql.val(options.threshold);
|
|
1941
|
+
switch (options.mode) {
|
|
1942
|
+
case "simple": return sql`similarity(${fieldExpr}, ${valueExpr}) > ${threshold}`;
|
|
1943
|
+
case "word": return sql`word_similarity(${valueExpr}, ${fieldExpr}) > ${threshold}`;
|
|
1944
|
+
case "strictWord": return sql`strict_word_similarity(${valueExpr}, ${fieldExpr}) > ${threshold}`;
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
buildFuzzyRelevanceOrderBy(query, fieldRefs, search, sort, mode, unaccent) {
|
|
1948
|
+
const valueExpr = this.normalizeForTrigram(sql.val(search), unaccent);
|
|
1949
|
+
const buildSimilarity = (fieldRef) => {
|
|
1950
|
+
const fieldExpr = this.normalizeForTrigram(fieldRef, unaccent);
|
|
1951
|
+
switch (mode) {
|
|
1952
|
+
case "simple": return sql`similarity(${fieldExpr}, ${valueExpr})`;
|
|
1953
|
+
case "word": return sql`word_similarity(${valueExpr}, ${fieldExpr})`;
|
|
1954
|
+
case "strictWord": return sql`strict_word_similarity(${valueExpr}, ${fieldExpr})`;
|
|
1955
|
+
}
|
|
1956
|
+
};
|
|
1957
|
+
if (fieldRefs.length === 1) return query.orderBy(buildSimilarity(fieldRefs[0]), sort);
|
|
1958
|
+
const similarities = fieldRefs.map((ref) => buildSimilarity(ref));
|
|
1959
|
+
return query.orderBy(sql`GREATEST(${sql.join(similarities)})`, sort);
|
|
1960
|
+
}
|
|
1961
|
+
buildFullTextFilter(fieldRef, payload) {
|
|
1962
|
+
const options = this.normalizeFullTextOptions(payload);
|
|
1963
|
+
const query = sql.val(options.search);
|
|
1964
|
+
if (options.config === void 0) return sql`to_tsvector(${fieldRef}) @@ to_tsquery(${query})`;
|
|
1965
|
+
const cfg = sql.val(options.config);
|
|
1966
|
+
return sql`to_tsvector(${cfg}::regconfig, ${fieldRef}) @@ to_tsquery(${cfg}::regconfig, ${query})`;
|
|
1967
|
+
}
|
|
1968
|
+
/**
|
|
1969
|
+
* Validate the user-provided `fts` filter payload. When `config` is omitted
|
|
1970
|
+
* it stays `undefined` so {@link buildFullTextFilter} can emit the no-regconfig
|
|
1971
|
+
* SQL form and let Postgres fall back to `default_text_search_config`.
|
|
1972
|
+
*/
|
|
1973
|
+
normalizeFullTextOptions(value) {
|
|
1974
|
+
invariant(value !== null && typeof value === "object" && !Array.isArray(value), "fts filter must be an object with at least a \"search\" field");
|
|
1975
|
+
const raw = value;
|
|
1976
|
+
invariant(typeof raw["search"] === "string" && raw["search"].length > 0, "fts.search must be a non-empty string");
|
|
1977
|
+
if (raw["config"] !== void 0) invariant(typeof raw["config"] === "string" && raw["config"].length > 0, "fts.config must be a non-empty string");
|
|
1978
|
+
return {
|
|
1979
|
+
search: raw["search"],
|
|
1980
|
+
config: raw["config"]
|
|
1981
|
+
};
|
|
1982
|
+
}
|
|
1983
|
+
buildFtsRelevanceOrderBy(query, fieldRefs, search, config, sort) {
|
|
1984
|
+
const q = sql.val(search);
|
|
1985
|
+
const document = fieldRefs.length === 1 ? sql`coalesce(${fieldRefs[0]}, '')` : sql`concat_ws(' ', ${sql.join(fieldRefs)})`;
|
|
1986
|
+
if (config === void 0) return query.orderBy(sql`ts_rank(to_tsvector(${document}), to_tsquery(${q}))`, sort);
|
|
1987
|
+
const cfg = sql.val(config);
|
|
1988
|
+
return query.orderBy(sql`ts_rank(to_tsvector(${cfg}::regconfig, ${document}), to_tsquery(${cfg}::regconfig, ${q}))`, sort);
|
|
1989
|
+
}
|
|
1789
1990
|
};
|
|
1790
1991
|
//#endregion
|
|
1791
1992
|
//#region src/client/crud/dialects/sqlite.ts
|
|
@@ -2003,6 +2204,18 @@ var SqliteCrudDialect = class extends BaseCrudDialect {
|
|
|
2003
2204
|
return ob;
|
|
2004
2205
|
});
|
|
2005
2206
|
}
|
|
2207
|
+
buildFuzzyFilter(_fieldRef, _options) {
|
|
2208
|
+
throw createNotSupportedError("\"fuzzy\" filter is not supported by the \"sqlite\" provider");
|
|
2209
|
+
}
|
|
2210
|
+
buildFuzzyRelevanceOrderBy(_query, _fieldRefs, _search, _sort, _mode, _unaccent) {
|
|
2211
|
+
throw createNotSupportedError("\"_fuzzyRelevance\" ordering is not supported by the \"sqlite\" provider");
|
|
2212
|
+
}
|
|
2213
|
+
buildFullTextFilter(_fieldRef, _payload) {
|
|
2214
|
+
throw createNotSupportedError("\"fts\" filter is not supported by the \"sqlite\" provider");
|
|
2215
|
+
}
|
|
2216
|
+
buildFtsRelevanceOrderBy(_query, _fieldRefs, _search, _config, _sort) {
|
|
2217
|
+
throw createNotSupportedError("\"_ftsRelevance\" ordering is not supported by the \"sqlite\" provider");
|
|
2218
|
+
}
|
|
2006
2219
|
};
|
|
2007
2220
|
//#endregion
|
|
2008
2221
|
//#region src/client/crud/dialects/index.ts
|
|
@@ -2058,6 +2271,33 @@ function createQuerySchemaFactory(clientOrSchema, options) {
|
|
|
2058
2271
|
return new ZodSchemaFactory(clientOrSchema, options);
|
|
2059
2272
|
}
|
|
2060
2273
|
/**
|
|
2274
|
+
* Builds a `DateTime` value schema that accepts a `Date` object or any string
|
|
2275
|
+
* the JS `Date` constructor parses, and coerces it to a `Date`. ISO datetime,
|
|
2276
|
+
* ISO date, and time-only strings (e.g. `"09:00:00"` for `@db.Time` fields,
|
|
2277
|
+
* anchored to the Unix epoch) are the documented happy paths; other formats
|
|
2278
|
+
* accepted by `new Date(...)` also pass through, mirroring Prisma's pre-3.5
|
|
2279
|
+
* behaviour. Strings the engine can't parse fall through and are rejected by
|
|
2280
|
+
* `z.date()` with the standard error.
|
|
2281
|
+
*
|
|
2282
|
+
* @see https://github.com/zenstackhq/zenstack/issues/2631
|
|
2283
|
+
*/
|
|
2284
|
+
function coercedDateTimeSchema() {
|
|
2285
|
+
return z.preprocess((val) => {
|
|
2286
|
+
if (typeof val !== "string") return val;
|
|
2287
|
+
if (/^\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d\d(?::\d\d)?)?$/.test(val)) {
|
|
2288
|
+
const hasTz = val.endsWith("Z") || /[+-]\d\d(?::\d\d)?$/.test(val);
|
|
2289
|
+
const d = /* @__PURE__ */ new Date(`1970-01-01T${val}${hasTz ? "" : "Z"}`);
|
|
2290
|
+
return isNaN(d.getTime()) ? val : d;
|
|
2291
|
+
}
|
|
2292
|
+
const d = new Date(val);
|
|
2293
|
+
return isNaN(d.getTime()) ? val : d;
|
|
2294
|
+
}, z.union([
|
|
2295
|
+
z.iso.datetime(),
|
|
2296
|
+
z.iso.date(),
|
|
2297
|
+
z.date()
|
|
2298
|
+
]));
|
|
2299
|
+
}
|
|
2300
|
+
/**
|
|
2061
2301
|
* Factory class responsible for creating and caching Zod schemas for ORM input validation.
|
|
2062
2302
|
*/
|
|
2063
2303
|
var ZodSchemaFactory = class {
|
|
@@ -2226,7 +2466,7 @@ var ZodSchemaFactory = class {
|
|
|
2226
2466
|
const typeDef = getTypeDef(this.schema, type);
|
|
2227
2467
|
invariant(typeDef, `Type definition "${type}" not found in schema`);
|
|
2228
2468
|
const schema = z.looseObject(Object.fromEntries(Object.entries(typeDef.fields).map(([field, def]) => {
|
|
2229
|
-
let fieldSchema = this.makeScalarSchema(def.type);
|
|
2469
|
+
let fieldSchema = isTypeDef(this.schema, def.type) ? z.lazy(() => this.makeTypeDefSchema(def.type)) : this.makeScalarSchema(def.type);
|
|
2230
2470
|
if (def.array) fieldSchema = fieldSchema.array();
|
|
2231
2471
|
if (def.optional) fieldSchema = fieldSchema.nullish();
|
|
2232
2472
|
return [field, fieldSchema];
|
|
@@ -2270,7 +2510,7 @@ var ZodSchemaFactory = class {
|
|
|
2270
2510
|
if (Object.keys(enumDef.values).length > 0) fieldSchema = this.makeEnumFilterSchema(fieldDef.type, !!fieldDef.optional, !!fieldDef.array, withAggregations, allowedFilterKinds);
|
|
2271
2511
|
} else if (fieldDef.array) fieldSchema = this.makeArrayFilterSchema(fieldDef.type, allowedFilterKinds);
|
|
2272
2512
|
else if (this.isTypeDefType(fieldDef.type)) fieldSchema = this.makeTypedJsonFilterSchema(fieldDef.type, !!fieldDef.optional, !!fieldDef.array, allowedFilterKinds);
|
|
2273
|
-
else fieldSchema = this.makePrimitiveFilterSchema(fieldDef.type, !!fieldDef.optional, withAggregations, allowedFilterKinds);
|
|
2513
|
+
else fieldSchema = this.makePrimitiveFilterSchema(fieldDef.type, !!fieldDef.optional, withAggregations, allowedFilterKinds, !!fieldDef.fuzzy, !!fieldDef.fullText);
|
|
2274
2514
|
}
|
|
2275
2515
|
if (fieldSchema) fields[field] = fieldSchema.optional();
|
|
2276
2516
|
}
|
|
@@ -2399,8 +2639,8 @@ var ZodSchemaFactory = class {
|
|
|
2399
2639
|
const filteredOperators = this.trimFilterOperators(operators, allowedFilterKinds);
|
|
2400
2640
|
return z.strictObject(filteredOperators);
|
|
2401
2641
|
}
|
|
2402
|
-
makePrimitiveFilterSchema(type, optional, withAggregations, allowedFilterKinds) {
|
|
2403
|
-
return match(type).with("String", () => this.makeStringFilterSchema(optional, withAggregations, allowedFilterKinds)).with(P.union("Int", "Float", "Decimal", "BigInt"), (type) => this.makeNumberFilterSchema(type, optional, withAggregations, allowedFilterKinds)).with("Boolean", () => this.makeBooleanFilterSchema(optional, withAggregations, allowedFilterKinds)).with("DateTime", () => this.makeDateTimeFilterSchema(optional, withAggregations, allowedFilterKinds)).with("Bytes", () => this.makeBytesFilterSchema(optional, withAggregations, allowedFilterKinds)).with("Json", () => this.makeJsonFilterSchema(optional, allowedFilterKinds)).with("Unsupported", () => z.never()).exhaustive();
|
|
2642
|
+
makePrimitiveFilterSchema(type, optional, withAggregations, allowedFilterKinds, withFuzzy = false, withFullText = false) {
|
|
2643
|
+
return match(type).with("String", () => this.makeStringFilterSchema(optional, withAggregations, allowedFilterKinds, withFuzzy, withFullText)).with(P.union("Int", "Float", "Decimal", "BigInt"), (type) => this.makeNumberFilterSchema(type, optional, withAggregations, allowedFilterKinds)).with("Boolean", () => this.makeBooleanFilterSchema(optional, withAggregations, allowedFilterKinds)).with("DateTime", () => this.makeDateTimeFilterSchema(optional, withAggregations, allowedFilterKinds)).with("Bytes", () => this.makeBytesFilterSchema(optional, withAggregations, allowedFilterKinds)).with("Json", () => this.makeJsonFilterSchema(optional, allowedFilterKinds)).with("Unsupported", () => z.never()).exhaustive();
|
|
2404
2644
|
}
|
|
2405
2645
|
makeJsonValueSchema() {
|
|
2406
2646
|
const schema = z.union([
|
|
@@ -2442,11 +2682,7 @@ var ZodSchemaFactory = class {
|
|
|
2442
2682
|
return schema;
|
|
2443
2683
|
}
|
|
2444
2684
|
makeDateTimeValueSchema() {
|
|
2445
|
-
const schema =
|
|
2446
|
-
z.iso.datetime(),
|
|
2447
|
-
z.iso.date(),
|
|
2448
|
-
z.date()
|
|
2449
|
-
]);
|
|
2685
|
+
const schema = coercedDateTimeSchema();
|
|
2450
2686
|
this.registerSchema("DateTime", schema);
|
|
2451
2687
|
return schema;
|
|
2452
2688
|
}
|
|
@@ -2542,8 +2778,8 @@ var ZodSchemaFactory = class {
|
|
|
2542
2778
|
})}`, schema);
|
|
2543
2779
|
return schema;
|
|
2544
2780
|
}
|
|
2545
|
-
makeStringFilterSchema(optional, withAggregations, allowedFilterKinds) {
|
|
2546
|
-
const baseComponents = this.makeCommonPrimitiveFilterComponents(z.string(), optional, () => z.lazy(() => this.makeStringFilterSchema(optional, withAggregations, allowedFilterKinds)), void 0, withAggregations ? [
|
|
2781
|
+
makeStringFilterSchema(optional, withAggregations, allowedFilterKinds, withFuzzy = false, withFullText = false) {
|
|
2782
|
+
const baseComponents = this.makeCommonPrimitiveFilterComponents(z.string(), optional, () => z.lazy(() => this.makeStringFilterSchema(optional, withAggregations, allowedFilterKinds, withFuzzy, withFullText)), void 0, withAggregations ? [
|
|
2547
2783
|
"_count",
|
|
2548
2784
|
"_min",
|
|
2549
2785
|
"_max"
|
|
@@ -2552,6 +2788,8 @@ var ZodSchemaFactory = class {
|
|
|
2552
2788
|
startsWith: z.string().optional(),
|
|
2553
2789
|
endsWith: z.string().optional(),
|
|
2554
2790
|
contains: z.string().optional(),
|
|
2791
|
+
...withFuzzy && this.providerSupportsFuzzySearch ? { fuzzy: this.makeFuzzyFilterSchema().optional() } : {},
|
|
2792
|
+
...withFullText && this.providerSupportsFullTextSearch ? { fts: this.makeFullTextFilterSchema().optional() } : {},
|
|
2555
2793
|
...this.providerSupportsCaseSensitivity ? { mode: this.makeStringModeSchema().optional() } : {}
|
|
2556
2794
|
};
|
|
2557
2795
|
const filteredStringOperators = this.trimFilterOperators(stringSpecificOperators, allowedFilterKinds);
|
|
@@ -2560,16 +2798,35 @@ var ZodSchemaFactory = class {
|
|
|
2560
2798
|
...filteredStringOperators
|
|
2561
2799
|
};
|
|
2562
2800
|
const schema = this.createUnionFilterSchema(z.string(), optional, allComponents, allowedFilterKinds);
|
|
2801
|
+
const featureSuffix = `${withFuzzy ? "Fuzzy" : ""}${withFullText ? "FullText" : ""}`;
|
|
2563
2802
|
this.registerSchema(`StringFilter${this.filterSchemaSuffix({
|
|
2564
2803
|
optional,
|
|
2565
2804
|
allowedFilterKinds,
|
|
2566
2805
|
withAggregations
|
|
2567
|
-
})}`, schema);
|
|
2806
|
+
})}${featureSuffix}`, schema);
|
|
2568
2807
|
return schema;
|
|
2569
2808
|
}
|
|
2570
2809
|
makeStringModeSchema() {
|
|
2571
2810
|
return z.union([z.literal("default"), z.literal("insensitive")]);
|
|
2572
2811
|
}
|
|
2812
|
+
makeFuzzyFilterSchema() {
|
|
2813
|
+
return z.strictObject({
|
|
2814
|
+
search: z.string().min(1),
|
|
2815
|
+
mode: z.union([
|
|
2816
|
+
z.literal("simple"),
|
|
2817
|
+
z.literal("word"),
|
|
2818
|
+
z.literal("strictWord")
|
|
2819
|
+
]).default("simple"),
|
|
2820
|
+
threshold: z.number().min(0).max(1).optional(),
|
|
2821
|
+
unaccent: z.boolean().default(false)
|
|
2822
|
+
});
|
|
2823
|
+
}
|
|
2824
|
+
makeFullTextFilterSchema() {
|
|
2825
|
+
return z.strictObject({
|
|
2826
|
+
search: z.string().min(1),
|
|
2827
|
+
config: z.string().min(1).optional()
|
|
2828
|
+
});
|
|
2829
|
+
}
|
|
2573
2830
|
makeSelectSchema(model, options) {
|
|
2574
2831
|
const fields = {};
|
|
2575
2832
|
for (const [field, fieldDef] of this.getModelFields(model)) if (fieldDef.relation) {
|
|
@@ -2670,7 +2927,7 @@ var ZodSchemaFactory = class {
|
|
|
2670
2927
|
}).optional();
|
|
2671
2928
|
} else if (fieldDef.optional) fields[field] = z.union([sort, z.strictObject({
|
|
2672
2929
|
sort,
|
|
2673
|
-
nulls: z.union([z.literal("first"), z.literal("last")])
|
|
2930
|
+
nulls: z.union([z.literal("first"), z.literal("last")]).optional()
|
|
2674
2931
|
})]).optional();
|
|
2675
2932
|
else fields[field] = sort.optional();
|
|
2676
2933
|
if (WithAggregation) for (const agg of [
|
|
@@ -2680,6 +2937,29 @@ var ZodSchemaFactory = class {
|
|
|
2680
2937
|
"_min",
|
|
2681
2938
|
"_max"
|
|
2682
2939
|
]) fields[agg] = z.lazy(() => this.makeOrderBySchema(model, true, false, options).optional());
|
|
2940
|
+
if (this.providerSupportsFuzzySearch) {
|
|
2941
|
+
const fuzzyFieldNames = this.getModelFields(model).filter(([, def]) => !def.relation && def.type === "String" && def.fuzzy === true).map(([name]) => name);
|
|
2942
|
+
if (fuzzyFieldNames.length > 0) fields["_fuzzyRelevance"] = z.strictObject({
|
|
2943
|
+
fields: z.array(z.enum(fuzzyFieldNames)).min(1),
|
|
2944
|
+
search: z.string(),
|
|
2945
|
+
mode: z.union([
|
|
2946
|
+
z.literal("simple"),
|
|
2947
|
+
z.literal("word"),
|
|
2948
|
+
z.literal("strictWord")
|
|
2949
|
+
]).default("simple"),
|
|
2950
|
+
unaccent: z.boolean().default(false),
|
|
2951
|
+
sort
|
|
2952
|
+
}).optional();
|
|
2953
|
+
}
|
|
2954
|
+
if (this.providerSupportsFullTextSearch) {
|
|
2955
|
+
const fullTextFieldNames = this.getModelFields(model).filter(([, def]) => !def.relation && def.type === "String" && def.fullText === true).map(([name]) => name);
|
|
2956
|
+
if (fullTextFieldNames.length > 0) fields["_ftsRelevance"] = z.strictObject({
|
|
2957
|
+
fields: z.array(z.enum(fullTextFieldNames)).min(1),
|
|
2958
|
+
search: z.string().min(1),
|
|
2959
|
+
config: z.string().min(1).optional(),
|
|
2960
|
+
sort
|
|
2961
|
+
}).optional();
|
|
2962
|
+
}
|
|
2683
2963
|
const schema = refineAtMostOneKey(z.strictObject(fields));
|
|
2684
2964
|
let schemaId = `${model}OrderBy`;
|
|
2685
2965
|
if (withRelation) schemaId += "WithRelation";
|
|
@@ -3181,6 +3461,12 @@ var ZodSchemaFactory = class {
|
|
|
3181
3461
|
get providerSupportsCaseSensitivity() {
|
|
3182
3462
|
return this.schema.provider.type === "postgresql";
|
|
3183
3463
|
}
|
|
3464
|
+
get providerSupportsFullTextSearch() {
|
|
3465
|
+
return this.schema.provider.type === "postgresql";
|
|
3466
|
+
}
|
|
3467
|
+
get providerSupportsFuzzySearch() {
|
|
3468
|
+
return this.schema.provider.type === "postgresql";
|
|
3469
|
+
}
|
|
3184
3470
|
/**
|
|
3185
3471
|
* Gets the effective set of allowed FilterKind values for a specific model and field.
|
|
3186
3472
|
* Respects the precedence: model[field] > model.$all > $all[field] > $all.$all.
|
|
@@ -3342,6 +3628,8 @@ __decorate([
|
|
|
3342
3628
|
Object,
|
|
3343
3629
|
Boolean,
|
|
3344
3630
|
Boolean,
|
|
3631
|
+
Object,
|
|
3632
|
+
Object,
|
|
3345
3633
|
Object
|
|
3346
3634
|
]),
|
|
3347
3635
|
__decorateMetadata("design:returntype", void 0)
|
|
@@ -3411,6 +3699,8 @@ __decorate([
|
|
|
3411
3699
|
__decorateMetadata("design:paramtypes", [
|
|
3412
3700
|
Boolean,
|
|
3413
3701
|
Boolean,
|
|
3702
|
+
Object,
|
|
3703
|
+
Object,
|
|
3414
3704
|
Object
|
|
3415
3705
|
]),
|
|
3416
3706
|
__decorateMetadata("design:returntype", typeof (_ref10 = typeof ZodType !== "undefined" && ZodType) === "function" ? _ref10 : Object)
|
|
@@ -3900,7 +4190,7 @@ var BaseOperationHandler = class {
|
|
|
3900
4190
|
return new this.constructor(client, this.model, this.inputValidator);
|
|
3901
4191
|
}
|
|
3902
4192
|
get hasPolicyEnabled() {
|
|
3903
|
-
return this.options.plugins?.some((plugin) => plugin.
|
|
4193
|
+
return this.options.plugins?.some((plugin) => plugin.id === "policy") ?? false;
|
|
3904
4194
|
}
|
|
3905
4195
|
requireModel(model) {
|
|
3906
4196
|
return requireModel(this.schema, model);
|
|
@@ -4001,8 +4291,8 @@ var BaseOperationHandler = class {
|
|
|
4001
4291
|
const postCreateRelations = {};
|
|
4002
4292
|
for (const [field, value] of Object.entries(data)) {
|
|
4003
4293
|
const fieldDef = this.requireField(model, field);
|
|
4004
|
-
if (isScalarField(this.schema, model, field) || isForeignKeyField(this.schema, model, field)) if (fieldDef.array && value && typeof value === "object" && "set" in value && Array.isArray(value.set)) createFields[field] = this.dialect.transformInput(value.set, fieldDef.type, true);
|
|
4005
|
-
else createFields[field] = this.dialect.transformInput(value, fieldDef.type, !!fieldDef.array);
|
|
4294
|
+
if (isScalarField(this.schema, model, field) || isForeignKeyField(this.schema, model, field)) if (fieldDef.array && value && typeof value === "object" && "set" in value && Array.isArray(value.set)) createFields[field] = this.dialect.transformInput(value.set, fieldDef.type, true, fieldDef);
|
|
4295
|
+
else createFields[field] = this.dialect.transformInput(value, fieldDef.type, !!fieldDef.array, fieldDef);
|
|
4006
4296
|
else if (!getManyToManyRelation(this.schema, model, field) && fieldDef.relation?.fields && fieldDef.relation?.references) {
|
|
4007
4297
|
const fkValues = await this.processOwnedRelationForCreate(kysely, fieldDef, value);
|
|
4008
4298
|
for (let i = 0; i < fieldDef.relation.fields.length; i++) createFields[fieldDef.relation.fields[i]] = fkValues[fieldDef.relation.references[i]];
|
|
@@ -4202,7 +4492,7 @@ var BaseOperationHandler = class {
|
|
|
4202
4492
|
for (const [name, value] of Object.entries(item)) {
|
|
4203
4493
|
const fieldDef = this.requireField(model, name);
|
|
4204
4494
|
invariant(!fieldDef.relation, "createMany does not support relations");
|
|
4205
|
-
newItem[name] = this.dialect.transformInput(value, fieldDef.type, !!fieldDef.array);
|
|
4495
|
+
newItem[name] = this.dialect.transformInput(value, fieldDef.type, !!fieldDef.array, fieldDef);
|
|
4206
4496
|
}
|
|
4207
4497
|
if (fromRelation) for (const { fk, pk } of relationKeyPairs) newItem[fk] = fromRelation.ids[pk];
|
|
4208
4498
|
return this.fillGeneratedAndDefaultValues(modelDef, newItem);
|
|
@@ -4218,7 +4508,7 @@ var BaseOperationHandler = class {
|
|
|
4218
4508
|
if (Object.keys(item).length === allPassedFields.length) continue;
|
|
4219
4509
|
for (const field of allPassedFields) if (!(field in item)) {
|
|
4220
4510
|
const fieldDef = this.requireField(model, field);
|
|
4221
|
-
if (fieldDef.default !== void 0 && fieldDef.default !== null && typeof fieldDef.default !== "object") item[field] = this.dialect.transformInput(fieldDef.default, fieldDef.type, !!fieldDef.array);
|
|
4511
|
+
if (fieldDef.default !== void 0 && fieldDef.default !== null && typeof fieldDef.default !== "object") item[field] = this.dialect.transformInput(fieldDef.default, fieldDef.type, !!fieldDef.array, fieldDef);
|
|
4222
4512
|
}
|
|
4223
4513
|
}
|
|
4224
4514
|
}
|
|
@@ -4281,15 +4571,15 @@ var BaseOperationHandler = class {
|
|
|
4281
4571
|
if (!(field in data)) {
|
|
4282
4572
|
if (typeof fieldDef?.default === "object" && "kind" in fieldDef.default) {
|
|
4283
4573
|
const generated = this.evalGenerator(fieldDef.default);
|
|
4284
|
-
if (generated !== void 0) values[field] = this.dialect.transformInput(generated, fieldDef.type, !!fieldDef.array);
|
|
4285
|
-
} else if (fieldDef?.updatedAt) values[field] = this.dialect.transformInput(/* @__PURE__ */ new Date(), "DateTime", false);
|
|
4574
|
+
if (generated !== void 0) values[field] = this.dialect.transformInput(generated, fieldDef.type, !!fieldDef.array, fieldDef);
|
|
4575
|
+
} else if (fieldDef?.updatedAt) values[field] = this.dialect.transformInput(/* @__PURE__ */ new Date(), "DateTime", false, fieldDef);
|
|
4286
4576
|
else if (fieldDef?.default !== void 0) {
|
|
4287
4577
|
let value = fieldDef.default;
|
|
4288
4578
|
if (fieldDef.type === "Json") {
|
|
4289
4579
|
if (fieldDef.array && Array.isArray(value)) value = value.map((v) => typeof v === "string" ? JSON.parse(v) : v);
|
|
4290
4580
|
else if (typeof value === "string") value = JSON.parse(value);
|
|
4291
4581
|
}
|
|
4292
|
-
values[field] = this.dialect.transformInput(value, fieldDef.type, !!fieldDef.array);
|
|
4582
|
+
values[field] = this.dialect.transformInput(value, fieldDef.type, !!fieldDef.array, fieldDef);
|
|
4293
4583
|
}
|
|
4294
4584
|
}
|
|
4295
4585
|
}
|
|
@@ -4339,7 +4629,7 @@ var BaseOperationHandler = class {
|
|
|
4339
4629
|
const ignoredFields = new Set(typeof fieldDef.updatedAt === "boolean" ? [] : fieldDef.updatedAt.ignore);
|
|
4340
4630
|
if (Object.keys(data).some((field) => (isScalarField(this.schema, modelDef.name, field) || isForeignKeyField(this.schema, modelDef.name, field)) && !ignoredFields.has(field))) {
|
|
4341
4631
|
if (finalData === data) finalData = clone(data);
|
|
4342
|
-
finalData[fieldName] = this.dialect.transformInput(/* @__PURE__ */ new Date(), "DateTime", false);
|
|
4632
|
+
finalData[fieldName] = this.dialect.transformInput(/* @__PURE__ */ new Date(), "DateTime", false, fieldDef);
|
|
4343
4633
|
autoUpdatedFields.push(fieldName);
|
|
4344
4634
|
}
|
|
4345
4635
|
}
|
|
@@ -4443,7 +4733,7 @@ var BaseOperationHandler = class {
|
|
|
4443
4733
|
const fieldDef = this.requireField(model, field);
|
|
4444
4734
|
if (this.isNumericIncrementalUpdate(fieldDef, data[field])) return this.transformIncrementalUpdate(model, field, fieldDef, data[field]);
|
|
4445
4735
|
if (fieldDef.array && typeof data[field] === "object" && !Array.isArray(data[field]) && data[field]) return this.transformScalarListUpdate(model, field, fieldDef, data[field]);
|
|
4446
|
-
return this.dialect.transformInput(data[field], fieldDef.type, !!fieldDef.array);
|
|
4736
|
+
return this.dialect.transformInput(data[field], fieldDef.type, !!fieldDef.array, fieldDef);
|
|
4447
4737
|
}
|
|
4448
4738
|
isNumericIncrementalUpdate(fieldDef, value) {
|
|
4449
4739
|
if (!this.isNumericField(fieldDef)) return false;
|
|
@@ -4471,7 +4761,7 @@ var BaseOperationHandler = class {
|
|
|
4471
4761
|
transformIncrementalUpdate(model, field, fieldDef, payload) {
|
|
4472
4762
|
invariant(Object.keys(payload).length === 1, "Only one of \"set\", \"increment\", \"decrement\", \"multiply\", or \"divide\" can be provided");
|
|
4473
4763
|
const key = Object.keys(payload)[0];
|
|
4474
|
-
const value = this.dialect.transformInput(payload[key], fieldDef.type, false);
|
|
4764
|
+
const value = this.dialect.transformInput(payload[key], fieldDef.type, false, fieldDef);
|
|
4475
4765
|
const eb = expressionBuilder();
|
|
4476
4766
|
const fieldRef = this.dialect.fieldRef(model, field);
|
|
4477
4767
|
return match(key).with("set", () => value).with("increment", () => eb(fieldRef, "+", value)).with("decrement", () => eb(fieldRef, "-", value)).with("multiply", () => eb(fieldRef, "*", value)).with("divide", () => eb(fieldRef, "/", value)).otherwise(() => {
|
|
@@ -4481,7 +4771,7 @@ var BaseOperationHandler = class {
|
|
|
4481
4771
|
transformScalarListUpdate(model, field, fieldDef, payload) {
|
|
4482
4772
|
invariant(Object.keys(payload).length === 1, "Only one of \"set\", \"push\" can be provided");
|
|
4483
4773
|
const key = Object.keys(payload)[0];
|
|
4484
|
-
const value = this.dialect.transformInput(payload[key], fieldDef.type, true);
|
|
4774
|
+
const value = this.dialect.transformInput(payload[key], fieldDef.type, true, fieldDef);
|
|
4485
4775
|
const eb = expressionBuilder();
|
|
4486
4776
|
const fieldRef = this.dialect.fieldRef(model, field);
|
|
4487
4777
|
return match(key).with("set", () => value).with("push", () => {
|
|
@@ -6892,12 +7182,21 @@ var ClientImpl = class ClientImpl {
|
|
|
6892
7182
|
});
|
|
6893
7183
|
}
|
|
6894
7184
|
}
|
|
7185
|
+
getPromiseCallback(promise) {
|
|
7186
|
+
invariant(promise.cb, "Invalid ZenStackPromise, missing cb property");
|
|
7187
|
+
const cb = promise.cb;
|
|
7188
|
+
invariant(typeof cb === "function", "Invalid ZenStackPromise, cb property is not a function");
|
|
7189
|
+
return promise.cb;
|
|
7190
|
+
}
|
|
6895
7191
|
async sequentialTransaction(arg, options) {
|
|
6896
7192
|
const execute = async (tx) => {
|
|
6897
7193
|
const txClient = new ClientImpl(this.schema, this.$options, this);
|
|
6898
7194
|
txClient.kysely = tx;
|
|
6899
7195
|
const result = [];
|
|
6900
|
-
for (const promise of arg)
|
|
7196
|
+
for (const promise of arg) {
|
|
7197
|
+
const cb = this.getPromiseCallback(promise);
|
|
7198
|
+
result.push(await cb(txClient));
|
|
7199
|
+
}
|
|
6901
7200
|
return result;
|
|
6902
7201
|
};
|
|
6903
7202
|
if (this.kysely.isTransaction) return execute(this.kysely);
|
|
@@ -7684,6 +7983,15 @@ var DefaultOperationNodeVisitor = class extends OperationNodeVisitor {
|
|
|
7684
7983
|
visitCollate(node) {
|
|
7685
7984
|
this.defaultVisit(node);
|
|
7686
7985
|
}
|
|
7986
|
+
visitAlterType(node) {
|
|
7987
|
+
this.defaultVisit(node);
|
|
7988
|
+
}
|
|
7989
|
+
visitAddValue(node) {
|
|
7990
|
+
this.defaultVisit(node);
|
|
7991
|
+
}
|
|
7992
|
+
visitRenameValue(node) {
|
|
7993
|
+
this.defaultVisit(node);
|
|
7994
|
+
}
|
|
7687
7995
|
};
|
|
7688
7996
|
//#endregion
|
|
7689
7997
|
//#region src/utils/schema-utils.ts
|
|
@@ -7743,6 +8051,6 @@ var MatchingExpressionVisitor = class extends ExpressionVisitor {
|
|
|
7743
8051
|
}
|
|
7744
8052
|
};
|
|
7745
8053
|
//#endregion
|
|
7746
|
-
export { AllCrudOperations, AllReadOperations, AllWriteOperations, AnyNull, AnyNullClass, BaseCrudDialect, CRUD, CRUD_EXT, CoreCreateOperations, CoreCrudOperations, CoreDeleteOperations, CoreReadOperations, CoreUpdateOperations, CoreWriteOperations, DbNull, DbNullClass, InputValidator, JsonNull, JsonNullClass, kysely_utils_exports as KyselyUtils, ORMError, ORMErrorReason, query_utils_exports as QueryUtils, RejectedByPolicyReason, schema_utils_exports as SchemaUtils, TransactionIsolationLevel, ZenStackClient, createQuerySchemaFactory, definePlugin, getCrudDialect };
|
|
8054
|
+
export { AllCrudOperations, AllReadOperations, AllWriteOperations, AnyNull, AnyNullClass, BaseCrudDialect, CRUD, CRUD_EXT, CoreCreateOperations, CoreCrudOperations, CoreDeleteOperations, CoreReadOperations, CoreUpdateOperations, CoreWriteOperations, DbNull, DbNullClass, ExtQueryArgsMarker, ExtResultMarker, InputValidator, JsonNull, JsonNullClass, kysely_utils_exports as KyselyUtils, ORMError, ORMErrorReason, query_utils_exports as QueryUtils, RejectedByPolicyReason, schema_utils_exports as SchemaUtils, TransactionIsolationLevel, ZenStackClient, createQuerySchemaFactory, definePlugin, getCrudDialect };
|
|
7747
8055
|
|
|
7748
8056
|
//# sourceMappingURL=index.mjs.map
|