@zenstackhq/orm 3.6.4 → 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 CHANGED
@@ -30,6 +30,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
30
  enumerable: true
31
31
  }) : target, mod));
32
32
  //#endregion
33
+ const require_common_types = require("./common-types.cjs");
33
34
  let _zenstackhq_common_helpers = require("@zenstackhq/common-helpers");
34
35
  let kysely = require("kysely");
35
36
  let zod = require("zod");
@@ -567,6 +568,8 @@ const FILTER_PROPERTY_TO_KIND = {
567
568
  array_contains: "Json",
568
569
  array_starts_with: "Json",
569
570
  array_ends_with: "Json",
571
+ fuzzy: "Fuzzy",
572
+ fts: "FullText",
570
573
  has: "List",
571
574
  hasEvery: "List",
572
575
  hasSome: "List",
@@ -586,6 +589,19 @@ let TransactionIsolationLevel = /* @__PURE__ */ function(TransactionIsolationLev
586
589
  return TransactionIsolationLevel;
587
590
  }({});
588
591
  /**
592
+ * Symbol used as a type-only key on `ClientContract` to brand the `ExtQueryArgs`
593
+ * generic slot. Hidden from member-access autocomplete since symbol keys are
594
+ * not surfaced. Consumed by `InferExtQueryArgs` to recover the slot.
595
+ * @internal
596
+ */
597
+ const ExtQueryArgsMarker = Symbol("zenstack.client.extQueryArgs");
598
+ /**
599
+ * Symbol used as a type-only key on `ClientContract` to brand the `ExtResult`
600
+ * generic slot. Consumed by `InferExtResult` to recover the slot.
601
+ * @internal
602
+ */
603
+ const ExtResultMarker = Symbol("zenstack.client.extResult");
604
+ /**
589
605
  * CRUD operations.
590
606
  */
591
607
  const CRUD = [
@@ -599,20 +615,6 @@ const CRUD = [
599
615
  */
600
616
  const CRUD_EXT = [...CRUD, "post-update"];
601
617
  //#endregion
602
- //#region src/common-types.ts
603
- var DbNullClass = class {
604
- __brand = "DbNull";
605
- };
606
- const DbNull = new DbNullClass();
607
- var JsonNullClass = class {
608
- __brand = "JsonNull";
609
- };
610
- const JsonNull = new JsonNullClass();
611
- var AnyNullClass = class {
612
- __brand = "AnyNull";
613
- };
614
- const AnyNull = new AnyNullClass();
615
- //#endregion
616
618
  //#region src/client/crud/dialects/base-dialect.ts
617
619
  var BaseCrudDialect = class {
618
620
  eb = (0, kysely.expressionBuilder)();
@@ -622,8 +624,13 @@ var BaseCrudDialect = class {
622
624
  }
623
625
  /**
624
626
  * Transforms input value before sending to database.
627
+ *
628
+ * `fieldDef` is optional so existing callers that don't have it stay
629
+ * source-compatible. Dialects can use it to inspect `@db.*` native-type
630
+ * attributes (e.g. to format `@db.Time` values as `HH:MM:SS` rather than
631
+ * full ISO timestamps).
625
632
  */
626
- transformInput(value, _type, _forArrayField) {
633
+ transformInput(value, _type, _forArrayField, _fieldDef) {
627
634
  return value;
628
635
  }
629
636
  /**
@@ -666,7 +673,17 @@ var BaseCrudDialect = class {
666
673
  if (existingOrderBy.length > 0 && !alreadySatisfied) effectiveOrderBy = [...distinctFields.map((f) => ({ [f]: "asc" })), ...existingOrderBy];
667
674
  }
668
675
  result = this.buildOrderBy(result, model, modelAlias, effectiveOrderBy, negateOrderBy, take);
669
- if (args.cursor) result = this.buildCursorFilter(model, result, args.cursor, effectiveOrderBy, negateOrderBy, modelAlias);
676
+ if (args.cursor) {
677
+ if (effectiveOrderBy) {
678
+ const offendingKey = (0, _zenstackhq_common_helpers.enumerate)(effectiveOrderBy).map((ob) => {
679
+ if (typeof ob !== "object" || ob === null) return void 0;
680
+ if ("_fuzzyRelevance" in ob) return "_fuzzyRelevance";
681
+ if ("_ftsRelevance" in ob) return "_ftsRelevance";
682
+ }).find((k) => k !== void 0);
683
+ if (offendingKey) throw createNotSupportedError(`cursor pagination cannot be combined with "${offendingKey}" ordering`);
684
+ }
685
+ result = this.buildCursorFilter(model, result, args.cursor, effectiveOrderBy, negateOrderBy, modelAlias);
686
+ }
670
687
  return result;
671
688
  }
672
689
  buildFilter(model, modelAlias, where) {
@@ -806,7 +823,7 @@ var BaseCrudDialect = class {
806
823
  for (const [key, _value] of Object.entries(payload)) {
807
824
  if (_value === void 0) continue;
808
825
  (0, _zenstackhq_common_helpers.invariant)(fieldDef.array, "Field must be an array type to build array filter");
809
- const value = this.transformInput(_value, fieldType, true);
826
+ const value = this.transformInput(_value, fieldType, true, fieldDef);
810
827
  let receiver = fieldRef;
811
828
  if (isEnum(this.schema, fieldType)) receiver = this.eb.cast(fieldRef, kysely.sql.raw("text[]"));
812
829
  const buildArray = (value) => {
@@ -838,10 +855,10 @@ var BaseCrudDialect = class {
838
855
  if (payload === null) return this.eb(fieldRef, "is", null);
839
856
  if (isEnum(this.schema, fieldDef.type)) return this.buildEnumFilter(fieldRef, fieldDef, payload);
840
857
  if (isTypeDef(this.schema, fieldDef.type)) {
841
- if (payload instanceof DbNullClass || payload instanceof JsonNullClass || payload instanceof AnyNullClass) return this.buildJsonValueFilterClause(fieldRef, payload);
858
+ if (payload instanceof require_common_types.DbNullClass || payload instanceof require_common_types.JsonNullClass || payload instanceof require_common_types.AnyNullClass) return this.buildJsonValueFilterClause(fieldRef, payload);
842
859
  return this.buildJsonFilter(fieldRef, payload, fieldDef);
843
860
  }
844
- return (0, ts_pattern.match)(fieldDef.type).with("String", () => this.buildStringFilter(fieldRef, payload)).with(ts_pattern.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", () => {
861
+ return (0, ts_pattern.match)(fieldDef.type).with("String", () => this.buildStringFilter(fieldRef, payload, fieldDef)).with(ts_pattern.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", () => {
845
862
  throw createInvalidInputError(`Unsupported field cannot be used in filters`);
846
863
  }).exhaustive();
847
864
  }
@@ -953,9 +970,9 @@ var BaseCrudDialect = class {
953
970
  return this.and(...clauses);
954
971
  }
955
972
  buildJsonValueFilterClause(lhs, value) {
956
- if (value instanceof DbNullClass) return this.eb(lhs, "is", null);
957
- else if (value instanceof JsonNullClass) return this.eb.and([this.eb(lhs, "=", this.transformInput(null, "Json", false)), this.eb(lhs, "is not", null)]);
958
- else if (value instanceof AnyNullClass) return this.eb.or([this.eb(lhs, "is", null), this.eb(lhs, "=", this.transformInput(null, "Json", false))]);
973
+ if (value instanceof require_common_types.DbNullClass) return this.eb(lhs, "is", null);
974
+ else if (value instanceof require_common_types.JsonNullClass) return this.eb.and([this.eb(lhs, "=", this.transformInput(null, "Json", false)), this.eb(lhs, "is not", null)]);
975
+ else if (value instanceof require_common_types.AnyNullClass) return this.eb.or([this.eb(lhs, "is", null), this.eb(lhs, "=", this.transformInput(null, "Json", false))]);
959
976
  else return this.buildJsonEqualityFilter(lhs, value);
960
977
  }
961
978
  buildJsonEqualityFilter(lhs, rhs) {
@@ -1009,13 +1026,23 @@ var BaseCrudDialect = class {
1009
1026
  consumedKeys
1010
1027
  };
1011
1028
  }
1012
- buildStringFilter(fieldRef, payload) {
1029
+ buildStringFilter(fieldRef, payload, fieldDef) {
1013
1030
  let mode;
1014
1031
  if (payload && typeof payload === "object" && "mode" in payload) mode = payload.mode;
1015
- 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));
1032
+ 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));
1016
1033
  if (payload && typeof payload === "object") for (const [key, value] of Object.entries(payload)) {
1017
1034
  if (key === "mode" || consumedKeys.includes(key)) continue;
1018
1035
  if (value === void 0) continue;
1036
+ if (key === "fuzzy") {
1037
+ (0, _zenstackhq_common_helpers.invariant)(fieldDef?.fuzzy === true, `field "${fieldDef?.name ?? "<unknown>"}" is not fuzzy-searchable; add the \`@fuzzy\` attribute to use the \`fuzzy\` filter`);
1038
+ conditions.push(this.buildFuzzyFilter(fieldRef, this.normalizeFuzzyOptions(value)));
1039
+ continue;
1040
+ }
1041
+ if (key === "fts") {
1042
+ (0, _zenstackhq_common_helpers.invariant)(fieldDef?.fullText === true, `field "${fieldDef?.name ?? "<unknown>"}" is not full-text-searchable; add the \`@fullText\` attribute to use the \`fts\` filter`);
1043
+ conditions.push(this.buildFullTextFilter(fieldRef, value));
1044
+ continue;
1045
+ }
1019
1046
  (0, _zenstackhq_common_helpers.invariant)(typeof value === "string", `${key} value must be a string`);
1020
1047
  const escapedValue = this.escapeLikePattern(value);
1021
1048
  const condition = (0, ts_pattern.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(() => {
@@ -1084,6 +1111,14 @@ var BaseCrudDialect = class {
1084
1111
  (0, _zenstackhq_common_helpers.enumerate)(orderBy).forEach((orderBy, index) => {
1085
1112
  for (const [field, value] of Object.entries(orderBy)) {
1086
1113
  if (!value) continue;
1114
+ if (field === "_fuzzyRelevance") {
1115
+ result = this.applyFuzzyRelevanceOrderBy(result, model, modelAlias, value, negated, buildFieldRef);
1116
+ continue;
1117
+ }
1118
+ if (field === "_ftsRelevance") {
1119
+ result = this.applyFtsRelevanceOrderBy(result, model, modelAlias, value, negated, buildFieldRef);
1120
+ continue;
1121
+ }
1087
1122
  if ([
1088
1123
  "_count",
1089
1124
  "_avg",
@@ -1091,47 +1126,84 @@ var BaseCrudDialect = class {
1091
1126
  "_min",
1092
1127
  "_max"
1093
1128
  ].includes(field)) {
1094
- (0, _zenstackhq_common_helpers.invariant)(typeof value === "object", `invalid orderBy value for field "${field}"`);
1095
- for (const [k, v] of Object.entries(value)) {
1096
- (0, _zenstackhq_common_helpers.invariant)(v === "asc" || v === "desc", `invalid orderBy value for field "${field}"`);
1097
- result = result.orderBy((eb) => aggregate(eb, buildFieldRef(model, k, modelAlias), field), this.negateSort(v, negated));
1098
- }
1129
+ result = this.applyAggregationOrderBy(result, model, modelAlias, field, value, negated, buildFieldRef);
1099
1130
  continue;
1100
1131
  }
1101
1132
  const fieldDef = requireField(this.schema, model, field);
1102
- if (!fieldDef.relation) {
1103
- const fieldRef = buildFieldRef(model, field, modelAlias);
1104
- if (value === "asc" || value === "desc") result = result.orderBy(fieldRef, this.negateSort(value, negated));
1105
- 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);
1106
- } else {
1107
- const relationModel = fieldDef.type;
1108
- if (fieldDef.array) {
1109
- if (typeof value !== "object") throw createInvalidInputError(`invalid orderBy value for field "${field}"`);
1110
- if ("_count" in value) {
1111
- (0, _zenstackhq_common_helpers.invariant)(value._count === "asc" || value._count === "desc", "invalid orderBy value for field \"_count\"");
1112
- const sort = this.negateSort(value._count, negated);
1113
- result = result.orderBy((eb) => {
1114
- const subQueryAlias = tmpAlias(`${modelAlias}$ob$${field}$ct`);
1115
- let subQuery = this.buildSelectModel(relationModel, subQueryAlias);
1116
- const joinPairs = buildJoinPairs(this.schema, model, modelAlias, field, subQueryAlias);
1117
- subQuery = subQuery.where(() => this.and(...joinPairs.map(([left, right]) => eb(this.eb.ref(left), "=", this.eb.ref(right)))));
1118
- subQuery = subQuery.select(() => eb.fn.count(eb.lit(1)).as("_count"));
1119
- return subQuery;
1120
- }, sort);
1121
- }
1122
- } else {
1123
- const joinAlias = tmpAlias(`${modelAlias}$ob$${index}`);
1124
- result = result.leftJoin(`${relationModel} as ${joinAlias}`, (join) => {
1125
- const joinPairs = buildJoinPairs(this.schema, model, modelAlias, field, joinAlias);
1126
- return join.on((eb) => this.and(...joinPairs.map(([left, right]) => eb(this.eb.ref(left), "=", this.eb.ref(right)))));
1127
- });
1128
- result = this.buildOrderBy(result, relationModel, joinAlias, value, negated, take);
1129
- }
1130
- }
1133
+ if (!fieldDef.relation) result = this.applyScalarOrderBy(result, model, modelAlias, field, value, negated, buildFieldRef);
1134
+ else result = this.applyRelationOrderBy(result, model, modelAlias, field, fieldDef, value, negated, take, index);
1131
1135
  }
1132
1136
  });
1133
1137
  return result;
1134
1138
  }
1139
+ applyRelationOrderBy(query, model, modelAlias, field, fieldDef, value, negated, take, index) {
1140
+ const relationModel = fieldDef.type;
1141
+ if (fieldDef.array) {
1142
+ if (typeof value !== "object") throw createInvalidInputError(`invalid orderBy value for field "${field}"`);
1143
+ if ("_count" in value) {
1144
+ (0, _zenstackhq_common_helpers.invariant)(value._count === "asc" || value._count === "desc", "invalid orderBy value for field \"_count\"");
1145
+ const sort = this.negateSort(value._count, negated);
1146
+ return query.orderBy((eb) => {
1147
+ const subQueryAlias = tmpAlias(`${modelAlias}$ob$${field}$ct`);
1148
+ let subQuery = this.buildSelectModel(relationModel, subQueryAlias);
1149
+ const joinPairs = buildJoinPairs(this.schema, model, modelAlias, field, subQueryAlias);
1150
+ subQuery = subQuery.where(() => this.and(...joinPairs.map(([left, right]) => eb(this.eb.ref(left), "=", this.eb.ref(right)))));
1151
+ subQuery = subQuery.select(() => eb.fn.count(eb.lit(1)).as("_count"));
1152
+ return subQuery;
1153
+ }, sort);
1154
+ }
1155
+ return query;
1156
+ }
1157
+ const joinAlias = tmpAlias(`${modelAlias}$ob$${index}`);
1158
+ const joined = query.leftJoin(`${relationModel} as ${joinAlias}`, (join) => {
1159
+ const joinPairs = buildJoinPairs(this.schema, model, modelAlias, field, joinAlias);
1160
+ return join.on((eb) => this.and(...joinPairs.map(([left, right]) => eb(this.eb.ref(left), "=", this.eb.ref(right)))));
1161
+ });
1162
+ return this.buildOrderBy(joined, relationModel, joinAlias, value, negated, take);
1163
+ }
1164
+ applyScalarOrderBy(query, model, modelAlias, field, value, negated, buildFieldRef) {
1165
+ const fieldRef = buildFieldRef(model, field, modelAlias);
1166
+ if (value === "asc" || value === "desc") return query.orderBy(fieldRef, this.negateSort(value, negated));
1167
+ if (typeof value === "object" && "sort" in value && (value.sort === "asc" || value.sort === "desc")) {
1168
+ const sort = this.negateSort(value.sort, negated);
1169
+ if (value.nulls === "first" || value.nulls === "last") return this.buildOrderByField(query, fieldRef, sort, value.nulls);
1170
+ else return query.orderBy(fieldRef, sort);
1171
+ }
1172
+ return query;
1173
+ }
1174
+ applyAggregationOrderBy(query, model, modelAlias, field, value, negated, buildFieldRef) {
1175
+ (0, _zenstackhq_common_helpers.invariant)(typeof value === "object", `invalid orderBy value for field "${field}"`);
1176
+ let result = query;
1177
+ for (const [k, v] of Object.entries(value)) {
1178
+ (0, _zenstackhq_common_helpers.invariant)(v === "asc" || v === "desc", `invalid orderBy value for field "${field}"`);
1179
+ result = result.orderBy((eb) => aggregate(eb, buildFieldRef(model, k, modelAlias), field), this.negateSort(v, negated));
1180
+ }
1181
+ return result;
1182
+ }
1183
+ applyFuzzyRelevanceOrderBy(query, model, modelAlias, value, negated, buildFieldRef) {
1184
+ (0, _zenstackhq_common_helpers.invariant)(typeof value === "object" && "fields" in value && "search" in value && "sort" in value, "invalid orderBy value for \"_fuzzyRelevance\"");
1185
+ (0, _zenstackhq_common_helpers.invariant)(Array.isArray(value.fields) && value.fields.length > 0, "_fuzzyRelevance.fields must be a non-empty array");
1186
+ (0, _zenstackhq_common_helpers.invariant)(value.sort === "asc" || value.sort === "desc", "invalid sort value for \"_fuzzyRelevance\"");
1187
+ (0, _zenstackhq_common_helpers.invariant)(typeof value.search === "string" && value.search.length > 0, "_fuzzyRelevance.search must be a non-empty string");
1188
+ const mode = value.mode ?? "simple";
1189
+ (0, _zenstackhq_common_helpers.invariant)(mode === "simple" || mode === "word" || mode === "strictWord", "_fuzzyRelevance.mode must be \"simple\", \"word\" or \"strictWord\"");
1190
+ const unaccent = value.unaccent ?? false;
1191
+ (0, _zenstackhq_common_helpers.invariant)(typeof unaccent === "boolean", "_fuzzyRelevance.unaccent must be a boolean");
1192
+ for (const fieldName of value.fields) (0, _zenstackhq_common_helpers.invariant)(requireField(this.schema, model, fieldName).fuzzy === true, `field "${fieldName}" is not fuzzy-searchable; add the \`@fuzzy\` attribute to use it in \`_fuzzyRelevance\``);
1193
+ const fieldRefs = value.fields.map((f) => buildFieldRef(model, f, modelAlias));
1194
+ return this.buildFuzzyRelevanceOrderBy(query, fieldRefs, value.search, this.negateSort(value.sort, negated), mode, unaccent);
1195
+ }
1196
+ applyFtsRelevanceOrderBy(query, model, modelAlias, value, negated, buildFieldRef) {
1197
+ (0, _zenstackhq_common_helpers.invariant)(typeof value === "object" && "fields" in value && "search" in value && "sort" in value, "invalid orderBy value for \"_ftsRelevance\"");
1198
+ (0, _zenstackhq_common_helpers.invariant)(Array.isArray(value.fields) && value.fields.length > 0, "_ftsRelevance.fields must be a non-empty array");
1199
+ (0, _zenstackhq_common_helpers.invariant)(value.sort === "asc" || value.sort === "desc", "invalid sort value for \"_ftsRelevance\"");
1200
+ (0, _zenstackhq_common_helpers.invariant)(typeof value.search === "string" && value.search.length > 0, "_ftsRelevance.search must be a non-empty string");
1201
+ if (value.config !== void 0) (0, _zenstackhq_common_helpers.invariant)(typeof value.config === "string" && value.config.length > 0, "_ftsRelevance.config must be a non-empty string");
1202
+ const config = value.config;
1203
+ for (const fieldName of value.fields) (0, _zenstackhq_common_helpers.invariant)(requireField(this.schema, model, fieldName).fullText === true, `field "${fieldName}" is not full-text-searchable; add the \`@fullText\` attribute to use it in \`_ftsRelevance\``);
1204
+ const fieldRefs = value.fields.map((f) => buildFieldRef(model, f, modelAlias));
1205
+ return this.buildFtsRelevanceOrderBy(query, fieldRefs, value.search, config, this.negateSort(value.sort, negated));
1206
+ }
1135
1207
  buildSelectAllFields(model, query, omit, modelAlias) {
1136
1208
  let result = query;
1137
1209
  for (const fieldDef of getModelFields(this.schema, model, {
@@ -1274,6 +1346,27 @@ var BaseCrudDialect = class {
1274
1346
  buildComparison(left, _leftFieldDef, op, right, _rightFieldDef) {
1275
1347
  return this.eb(left, op, right);
1276
1348
  }
1349
+ /**
1350
+ * Validate the user-provided fuzzy filter payload and apply defaults so dialects
1351
+ * always receive a fully-resolved {@link FuzzyFilterOptions} value.
1352
+ */
1353
+ normalizeFuzzyOptions(value) {
1354
+ (0, _zenstackhq_common_helpers.invariant)(value !== null && typeof value === "object" && !Array.isArray(value), "fuzzy filter must be an object with at least a \"search\" field");
1355
+ const raw = value;
1356
+ (0, _zenstackhq_common_helpers.invariant)(typeof raw["search"] === "string" && raw["search"].length > 0, "fuzzy.search must be a non-empty string");
1357
+ const mode = raw["mode"] ?? "simple";
1358
+ (0, _zenstackhq_common_helpers.invariant)(mode === "simple" || mode === "word" || mode === "strictWord", "fuzzy.mode must be \"simple\", \"word\" or \"strictWord\"");
1359
+ const threshold = raw["threshold"];
1360
+ if (threshold !== void 0) (0, _zenstackhq_common_helpers.invariant)(typeof threshold === "number" && threshold >= 0 && threshold <= 1, "fuzzy.threshold must be a number between 0 and 1");
1361
+ const unaccent = raw["unaccent"] ?? false;
1362
+ (0, _zenstackhq_common_helpers.invariant)(typeof unaccent === "boolean", "fuzzy.unaccent must be a boolean");
1363
+ return {
1364
+ search: raw["search"],
1365
+ mode,
1366
+ threshold,
1367
+ unaccent
1368
+ };
1369
+ }
1277
1370
  };
1278
1371
  //#endregion
1279
1372
  //#region src/client/crud/dialects/lateral-join-dialect-base.ts
@@ -1422,9 +1515,9 @@ var MySqlCrudDialect = class extends LateralJoinDialectBase {
1422
1515
  }
1423
1516
  transformInput(value, type, forArrayField) {
1424
1517
  if (value === void 0) return value;
1425
- if (value instanceof JsonNullClass) return this.eb.cast(kysely.sql.lit("null"), "json");
1426
- else if (value instanceof DbNullClass) return null;
1427
- else if (value instanceof AnyNullClass) (0, _zenstackhq_common_helpers.invariant)(false, "should not reach here: AnyNull is not a valid input value");
1518
+ if (value instanceof require_common_types.JsonNullClass) return this.eb.cast(kysely.sql.lit("null"), "json");
1519
+ else if (value instanceof require_common_types.DbNullClass) return null;
1520
+ else if (value instanceof require_common_types.AnyNullClass) (0, _zenstackhq_common_helpers.invariant)(false, "should not reach here: AnyNull is not a valid input value");
1428
1521
  if (isTypeDef(this.schema, type)) if (typeof value !== "string") return this.transformInput(value, "Json", forArrayField);
1429
1522
  else return value;
1430
1523
  else if (Array.isArray(value)) if (type === "Json") return JSON.stringify(value);
@@ -1558,9 +1651,32 @@ var MySqlCrudDialect = class extends LateralJoinDialectBase {
1558
1651
  }
1559
1652
  return result;
1560
1653
  }
1654
+ buildFuzzyFilter(_fieldRef, _options) {
1655
+ throw createNotSupportedError("\"fuzzy\" filter is not supported by the \"mysql\" provider");
1656
+ }
1657
+ buildFuzzyRelevanceOrderBy(_query, _fieldRefs, _search, _sort, _mode, _unaccent) {
1658
+ throw createNotSupportedError("\"_fuzzyRelevance\" ordering is not supported by the \"mysql\" provider");
1659
+ }
1660
+ buildFullTextFilter(_fieldRef, _payload) {
1661
+ throw createNotSupportedError("\"fts\" filter is not supported by the \"mysql\" provider");
1662
+ }
1663
+ buildFtsRelevanceOrderBy(_query, _fieldRefs, _search, _config, _sort) {
1664
+ throw createNotSupportedError("\"_ftsRelevance\" ordering is not supported by the \"mysql\" provider");
1665
+ }
1561
1666
  };
1562
1667
  //#endregion
1563
1668
  //#region src/client/crud/dialects/postgresql.ts
1669
+ /**
1670
+ * Formats a JS `Date` as a Postgres TIME / TIMETZ literal (`HH:MM:SS.fff`,
1671
+ * optionally with `+ZZ:ZZ` for TIMETZ). Reads UTC components so the value
1672
+ * round-trips with ISO-input parsing — callers anchor time-only inputs to
1673
+ * the Unix epoch.
1674
+ */
1675
+ function formatTimeOfDay(date, withTimezone) {
1676
+ const pad = (n, w = 2) => String(n).padStart(w, "0");
1677
+ const time = `${pad(date.getUTCHours())}:${pad(date.getUTCMinutes())}:${pad(date.getUTCSeconds())}.${pad(date.getUTCMilliseconds(), 3)}`;
1678
+ return withTimezone ? `${time}+00:00` : time;
1679
+ }
1564
1680
  var PostgresCrudDialect = class PostgresCrudDialect extends LateralJoinDialectBase {
1565
1681
  static typeParserOverrideApplied = false;
1566
1682
  zmodelToSqlTypeMap = {
@@ -1654,17 +1770,23 @@ var PostgresCrudDialect = class PostgresCrudDialect extends LateralJoinDialectBa
1654
1770
  get insertIgnoreMethod() {
1655
1771
  return "onConflict";
1656
1772
  }
1657
- transformInput(value, type, forArrayField) {
1773
+ transformInput(value, type, forArrayField, fieldDef) {
1658
1774
  if (value === void 0) return value;
1659
- if (value instanceof JsonNullClass) return "null";
1660
- else if (value instanceof DbNullClass) return null;
1661
- else if (value instanceof AnyNullClass) (0, _zenstackhq_common_helpers.invariant)(false, "should not reach here: AnyNull is not a valid input value");
1775
+ if (value instanceof require_common_types.JsonNullClass) return "null";
1776
+ else if (value instanceof require_common_types.DbNullClass) return null;
1777
+ else if (value instanceof require_common_types.AnyNullClass) (0, _zenstackhq_common_helpers.invariant)(false, "should not reach here: AnyNull is not a valid input value");
1662
1778
  if (isTypeDef(this.schema, type)) if (typeof value !== "string") return JSON.stringify(value);
1663
1779
  else return value;
1664
1780
  else if (Array.isArray(value)) if (type === "Json" && !forArrayField) return JSON.stringify(value);
1665
- else return value.map((v) => this.transformInput(v, type, false));
1781
+ else return value.map((v) => this.transformInput(v, type, false, fieldDef));
1666
1782
  else switch (type) {
1667
- case "DateTime": return value instanceof Date ? value.toISOString() : typeof value === "string" ? new Date(value).toISOString() : value;
1783
+ case "DateTime": {
1784
+ const date = value instanceof Date ? value : typeof value === "string" ? new Date(value) : null;
1785
+ if (date === null || isNaN(date.getTime())) return value;
1786
+ const dbAttrName = fieldDef?.attributes?.find((a) => a.name.startsWith("@db."))?.name;
1787
+ if (dbAttrName === "@db.Time" || dbAttrName === "@db.Timetz") return formatTimeOfDay(date, dbAttrName === "@db.Timetz");
1788
+ return date.toISOString();
1789
+ }
1668
1790
  case "Decimal": return value !== null ? value.toString() : value;
1669
1791
  case "Json": if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") return JSON.stringify(value);
1670
1792
  else return value;
@@ -1836,6 +1958,72 @@ var PostgresCrudDialect = class PostgresCrudDialect extends LateralJoinDialectBa
1836
1958
  return ob;
1837
1959
  });
1838
1960
  }
1961
+ /**
1962
+ * Wraps an expression with `unaccent(lower(...))` or just `lower(...)` depending on
1963
+ * whether the user opted into accent-insensitive matching. The lowering is always
1964
+ * applied so trigram comparisons are case-insensitive on both sides.
1965
+ */
1966
+ normalizeForTrigram(expr, applyUnaccent) {
1967
+ return applyUnaccent ? kysely.sql`unaccent(lower(${expr}))` : kysely.sql`lower(${expr})`;
1968
+ }
1969
+ buildFuzzyFilter(fieldRef, options) {
1970
+ const fieldExpr = this.normalizeForTrigram(fieldRef, options.unaccent);
1971
+ const valueExpr = this.normalizeForTrigram(kysely.sql.val(options.search), options.unaccent);
1972
+ if (options.threshold === void 0) switch (options.mode) {
1973
+ case "simple": return kysely.sql`${fieldExpr} % ${valueExpr}`;
1974
+ case "word": return kysely.sql`${valueExpr} <% ${fieldExpr}`;
1975
+ case "strictWord": return kysely.sql`${valueExpr} <<% ${fieldExpr}`;
1976
+ }
1977
+ const threshold = kysely.sql.val(options.threshold);
1978
+ switch (options.mode) {
1979
+ case "simple": return kysely.sql`similarity(${fieldExpr}, ${valueExpr}) > ${threshold}`;
1980
+ case "word": return kysely.sql`word_similarity(${valueExpr}, ${fieldExpr}) > ${threshold}`;
1981
+ case "strictWord": return kysely.sql`strict_word_similarity(${valueExpr}, ${fieldExpr}) > ${threshold}`;
1982
+ }
1983
+ }
1984
+ buildFuzzyRelevanceOrderBy(query, fieldRefs, search, sort, mode, unaccent) {
1985
+ const valueExpr = this.normalizeForTrigram(kysely.sql.val(search), unaccent);
1986
+ const buildSimilarity = (fieldRef) => {
1987
+ const fieldExpr = this.normalizeForTrigram(fieldRef, unaccent);
1988
+ switch (mode) {
1989
+ case "simple": return kysely.sql`similarity(${fieldExpr}, ${valueExpr})`;
1990
+ case "word": return kysely.sql`word_similarity(${valueExpr}, ${fieldExpr})`;
1991
+ case "strictWord": return kysely.sql`strict_word_similarity(${valueExpr}, ${fieldExpr})`;
1992
+ }
1993
+ };
1994
+ if (fieldRefs.length === 1) return query.orderBy(buildSimilarity(fieldRefs[0]), sort);
1995
+ const similarities = fieldRefs.map((ref) => buildSimilarity(ref));
1996
+ return query.orderBy(kysely.sql`GREATEST(${kysely.sql.join(similarities)})`, sort);
1997
+ }
1998
+ buildFullTextFilter(fieldRef, payload) {
1999
+ const options = this.normalizeFullTextOptions(payload);
2000
+ const query = kysely.sql.val(options.search);
2001
+ if (options.config === void 0) return kysely.sql`to_tsvector(${fieldRef}) @@ to_tsquery(${query})`;
2002
+ const cfg = kysely.sql.val(options.config);
2003
+ return kysely.sql`to_tsvector(${cfg}::regconfig, ${fieldRef}) @@ to_tsquery(${cfg}::regconfig, ${query})`;
2004
+ }
2005
+ /**
2006
+ * Validate the user-provided `fts` filter payload. When `config` is omitted
2007
+ * it stays `undefined` so {@link buildFullTextFilter} can emit the no-regconfig
2008
+ * SQL form and let Postgres fall back to `default_text_search_config`.
2009
+ */
2010
+ normalizeFullTextOptions(value) {
2011
+ (0, _zenstackhq_common_helpers.invariant)(value !== null && typeof value === "object" && !Array.isArray(value), "fts filter must be an object with at least a \"search\" field");
2012
+ const raw = value;
2013
+ (0, _zenstackhq_common_helpers.invariant)(typeof raw["search"] === "string" && raw["search"].length > 0, "fts.search must be a non-empty string");
2014
+ if (raw["config"] !== void 0) (0, _zenstackhq_common_helpers.invariant)(typeof raw["config"] === "string" && raw["config"].length > 0, "fts.config must be a non-empty string");
2015
+ return {
2016
+ search: raw["search"],
2017
+ config: raw["config"]
2018
+ };
2019
+ }
2020
+ buildFtsRelevanceOrderBy(query, fieldRefs, search, config, sort) {
2021
+ const q = kysely.sql.val(search);
2022
+ const document = fieldRefs.length === 1 ? kysely.sql`coalesce(${fieldRefs[0]}, '')` : kysely.sql`concat_ws(' ', ${kysely.sql.join(fieldRefs)})`;
2023
+ if (config === void 0) return query.orderBy(kysely.sql`ts_rank(to_tsvector(${document}), to_tsquery(${q}))`, sort);
2024
+ const cfg = kysely.sql.val(config);
2025
+ return query.orderBy(kysely.sql`ts_rank(to_tsvector(${cfg}::regconfig, ${document}), to_tsquery(${cfg}::regconfig, ${q}))`, sort);
2026
+ }
1839
2027
  };
1840
2028
  //#endregion
1841
2029
  //#region src/client/crud/dialects/sqlite.ts
@@ -1866,9 +2054,9 @@ var SqliteCrudDialect = class extends BaseCrudDialect {
1866
2054
  }
1867
2055
  transformInput(value, type, _forArrayField) {
1868
2056
  if (value === void 0) return value;
1869
- if (value instanceof JsonNullClass) return "null";
1870
- else if (value instanceof DbNullClass) return null;
1871
- else if (value instanceof AnyNullClass) (0, _zenstackhq_common_helpers.invariant)(false, "should not reach here: AnyNull is not a valid input value");
2057
+ if (value instanceof require_common_types.JsonNullClass) return "null";
2058
+ else if (value instanceof require_common_types.DbNullClass) return null;
2059
+ else if (value instanceof require_common_types.AnyNullClass) (0, _zenstackhq_common_helpers.invariant)(false, "should not reach here: AnyNull is not a valid input value");
1872
2060
  if (type === "Json" || this.schema.typeDefs && type in this.schema.typeDefs) return JSON.stringify(value);
1873
2061
  if (Array.isArray(value)) return value.map((v) => this.transformInput(v, type, false));
1874
2062
  else switch (type) {
@@ -2053,6 +2241,18 @@ var SqliteCrudDialect = class extends BaseCrudDialect {
2053
2241
  return ob;
2054
2242
  });
2055
2243
  }
2244
+ buildFuzzyFilter(_fieldRef, _options) {
2245
+ throw createNotSupportedError("\"fuzzy\" filter is not supported by the \"sqlite\" provider");
2246
+ }
2247
+ buildFuzzyRelevanceOrderBy(_query, _fieldRefs, _search, _sort, _mode, _unaccent) {
2248
+ throw createNotSupportedError("\"_fuzzyRelevance\" ordering is not supported by the \"sqlite\" provider");
2249
+ }
2250
+ buildFullTextFilter(_fieldRef, _payload) {
2251
+ throw createNotSupportedError("\"fts\" filter is not supported by the \"sqlite\" provider");
2252
+ }
2253
+ buildFtsRelevanceOrderBy(_query, _fieldRefs, _search, _config, _sort) {
2254
+ throw createNotSupportedError("\"_ftsRelevance\" ordering is not supported by the \"sqlite\" provider");
2255
+ }
2056
2256
  };
2057
2257
  //#endregion
2058
2258
  //#region src/client/crud/dialects/index.ts
@@ -2108,6 +2308,33 @@ function createQuerySchemaFactory(clientOrSchema, options) {
2108
2308
  return new ZodSchemaFactory(clientOrSchema, options);
2109
2309
  }
2110
2310
  /**
2311
+ * Builds a `DateTime` value schema that accepts a `Date` object or any string
2312
+ * the JS `Date` constructor parses, and coerces it to a `Date`. ISO datetime,
2313
+ * ISO date, and time-only strings (e.g. `"09:00:00"` for `@db.Time` fields,
2314
+ * anchored to the Unix epoch) are the documented happy paths; other formats
2315
+ * accepted by `new Date(...)` also pass through, mirroring Prisma's pre-3.5
2316
+ * behaviour. Strings the engine can't parse fall through and are rejected by
2317
+ * `z.date()` with the standard error.
2318
+ *
2319
+ * @see https://github.com/zenstackhq/zenstack/issues/2631
2320
+ */
2321
+ function coercedDateTimeSchema() {
2322
+ return zod.z.preprocess((val) => {
2323
+ if (typeof val !== "string") return val;
2324
+ if (/^\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d\d(?::\d\d)?)?$/.test(val)) {
2325
+ const hasTz = val.endsWith("Z") || /[+-]\d\d(?::\d\d)?$/.test(val);
2326
+ const d = /* @__PURE__ */ new Date(`1970-01-01T${val}${hasTz ? "" : "Z"}`);
2327
+ return isNaN(d.getTime()) ? val : d;
2328
+ }
2329
+ const d = new Date(val);
2330
+ return isNaN(d.getTime()) ? val : d;
2331
+ }, zod.z.union([
2332
+ zod.z.iso.datetime(),
2333
+ zod.z.iso.date(),
2334
+ zod.z.date()
2335
+ ]));
2336
+ }
2337
+ /**
2111
2338
  * Factory class responsible for creating and caching Zod schemas for ORM input validation.
2112
2339
  */
2113
2340
  var ZodSchemaFactory = class {
@@ -2276,7 +2503,7 @@ var ZodSchemaFactory = class {
2276
2503
  const typeDef = getTypeDef(this.schema, type);
2277
2504
  (0, _zenstackhq_common_helpers.invariant)(typeDef, `Type definition "${type}" not found in schema`);
2278
2505
  const schema = zod.z.looseObject(Object.fromEntries(Object.entries(typeDef.fields).map(([field, def]) => {
2279
- let fieldSchema = this.makeScalarSchema(def.type);
2506
+ let fieldSchema = isTypeDef(this.schema, def.type) ? zod.z.lazy(() => this.makeTypeDefSchema(def.type)) : this.makeScalarSchema(def.type);
2280
2507
  if (def.array) fieldSchema = fieldSchema.array();
2281
2508
  if (def.optional) fieldSchema = fieldSchema.nullish();
2282
2509
  return [field, fieldSchema];
@@ -2320,7 +2547,7 @@ var ZodSchemaFactory = class {
2320
2547
  if (Object.keys(enumDef.values).length > 0) fieldSchema = this.makeEnumFilterSchema(fieldDef.type, !!fieldDef.optional, !!fieldDef.array, withAggregations, allowedFilterKinds);
2321
2548
  } else if (fieldDef.array) fieldSchema = this.makeArrayFilterSchema(fieldDef.type, allowedFilterKinds);
2322
2549
  else if (this.isTypeDefType(fieldDef.type)) fieldSchema = this.makeTypedJsonFilterSchema(fieldDef.type, !!fieldDef.optional, !!fieldDef.array, allowedFilterKinds);
2323
- else fieldSchema = this.makePrimitiveFilterSchema(fieldDef.type, !!fieldDef.optional, withAggregations, allowedFilterKinds);
2550
+ else fieldSchema = this.makePrimitiveFilterSchema(fieldDef.type, !!fieldDef.optional, withAggregations, allowedFilterKinds, !!fieldDef.fuzzy, !!fieldDef.fullText);
2324
2551
  }
2325
2552
  if (fieldSchema) fields[field] = fieldSchema.optional();
2326
2553
  }
@@ -2381,9 +2608,9 @@ var ZodSchemaFactory = class {
2381
2608
  candidates.push(this.makeJsonFilterSchema(optional, allowedFilterKinds));
2382
2609
  if (optional) {
2383
2610
  candidates.push(zod.z.null());
2384
- candidates.push(zod.z.instanceof(DbNullClass));
2385
- candidates.push(zod.z.instanceof(JsonNullClass));
2386
- candidates.push(zod.z.instanceof(AnyNullClass));
2611
+ candidates.push(zod.z.instanceof(require_common_types.DbNullClass));
2612
+ candidates.push(zod.z.instanceof(require_common_types.JsonNullClass));
2613
+ candidates.push(zod.z.instanceof(require_common_types.AnyNullClass));
2387
2614
  }
2388
2615
  const result = zod.z.union(candidates);
2389
2616
  this.registerSchema(`${type}Filter${this.filterSchemaSuffix({
@@ -2395,7 +2622,7 @@ var ZodSchemaFactory = class {
2395
2622
  }
2396
2623
  makeNullableTypedJsonMutationSchema(fieldSchema) {
2397
2624
  return zod.z.any().superRefine((value, ctx) => {
2398
- if (value instanceof DbNullClass || value instanceof JsonNullClass || value === null || value === void 0) return;
2625
+ if (value instanceof require_common_types.DbNullClass || value instanceof require_common_types.JsonNullClass || value === null || value === void 0) return;
2399
2626
  const parseResult = fieldSchema.safeParse(value);
2400
2627
  if (!parseResult.success) parseResult.error.issues.forEach((issue) => ctx.addIssue(issue));
2401
2628
  }).optional();
@@ -2449,15 +2676,15 @@ var ZodSchemaFactory = class {
2449
2676
  const filteredOperators = this.trimFilterOperators(operators, allowedFilterKinds);
2450
2677
  return zod.z.strictObject(filteredOperators);
2451
2678
  }
2452
- makePrimitiveFilterSchema(type, optional, withAggregations, allowedFilterKinds) {
2453
- return (0, ts_pattern.match)(type).with("String", () => this.makeStringFilterSchema(optional, withAggregations, allowedFilterKinds)).with(ts_pattern.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", () => zod.z.never()).exhaustive();
2679
+ makePrimitiveFilterSchema(type, optional, withAggregations, allowedFilterKinds, withFuzzy = false, withFullText = false) {
2680
+ return (0, ts_pattern.match)(type).with("String", () => this.makeStringFilterSchema(optional, withAggregations, allowedFilterKinds, withFuzzy, withFullText)).with(ts_pattern.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", () => zod.z.never()).exhaustive();
2454
2681
  }
2455
2682
  makeJsonValueSchema() {
2456
2683
  const schema = zod.z.union([
2457
2684
  zod.z.string(),
2458
2685
  zod.z.number(),
2459
2686
  zod.z.boolean(),
2460
- zod.z.instanceof(JsonNullClass),
2687
+ zod.z.instanceof(require_common_types.JsonNullClass),
2461
2688
  zod.z.lazy(() => zod.z.union([this.makeJsonValueSchema(), zod.z.null()]).array()),
2462
2689
  zod.z.record(zod.z.string(), zod.z.lazy(() => zod.z.union([this.makeJsonValueSchema(), zod.z.null()])))
2463
2690
  ]);
@@ -2468,8 +2695,8 @@ var ZodSchemaFactory = class {
2468
2695
  if (allowedFilterKinds && !allowedFilterKinds.includes("Json")) return zod.z.never();
2469
2696
  const filterMembers = [
2470
2697
  this.makeJsonValueSchema(),
2471
- zod.z.instanceof(DbNullClass),
2472
- zod.z.instanceof(AnyNullClass)
2698
+ zod.z.instanceof(require_common_types.DbNullClass),
2699
+ zod.z.instanceof(require_common_types.AnyNullClass)
2473
2700
  ];
2474
2701
  if (optional) filterMembers.push(zod.z.null());
2475
2702
  const filterValueSchema = zod.z.union(filterMembers);
@@ -2492,11 +2719,7 @@ var ZodSchemaFactory = class {
2492
2719
  return schema;
2493
2720
  }
2494
2721
  makeDateTimeValueSchema() {
2495
- const schema = zod.z.union([
2496
- zod.z.iso.datetime(),
2497
- zod.z.iso.date(),
2498
- zod.z.date()
2499
- ]);
2722
+ const schema = coercedDateTimeSchema();
2500
2723
  this.registerSchema("DateTime", schema);
2501
2724
  return schema;
2502
2725
  }
@@ -2592,8 +2815,8 @@ var ZodSchemaFactory = class {
2592
2815
  })}`, schema);
2593
2816
  return schema;
2594
2817
  }
2595
- makeStringFilterSchema(optional, withAggregations, allowedFilterKinds) {
2596
- const baseComponents = this.makeCommonPrimitiveFilterComponents(zod.z.string(), optional, () => zod.z.lazy(() => this.makeStringFilterSchema(optional, withAggregations, allowedFilterKinds)), void 0, withAggregations ? [
2818
+ makeStringFilterSchema(optional, withAggregations, allowedFilterKinds, withFuzzy = false, withFullText = false) {
2819
+ const baseComponents = this.makeCommonPrimitiveFilterComponents(zod.z.string(), optional, () => zod.z.lazy(() => this.makeStringFilterSchema(optional, withAggregations, allowedFilterKinds, withFuzzy, withFullText)), void 0, withAggregations ? [
2597
2820
  "_count",
2598
2821
  "_min",
2599
2822
  "_max"
@@ -2602,6 +2825,8 @@ var ZodSchemaFactory = class {
2602
2825
  startsWith: zod.z.string().optional(),
2603
2826
  endsWith: zod.z.string().optional(),
2604
2827
  contains: zod.z.string().optional(),
2828
+ ...withFuzzy && this.providerSupportsFuzzySearch ? { fuzzy: this.makeFuzzyFilterSchema().optional() } : {},
2829
+ ...withFullText && this.providerSupportsFullTextSearch ? { fts: this.makeFullTextFilterSchema().optional() } : {},
2605
2830
  ...this.providerSupportsCaseSensitivity ? { mode: this.makeStringModeSchema().optional() } : {}
2606
2831
  };
2607
2832
  const filteredStringOperators = this.trimFilterOperators(stringSpecificOperators, allowedFilterKinds);
@@ -2610,16 +2835,35 @@ var ZodSchemaFactory = class {
2610
2835
  ...filteredStringOperators
2611
2836
  };
2612
2837
  const schema = this.createUnionFilterSchema(zod.z.string(), optional, allComponents, allowedFilterKinds);
2838
+ const featureSuffix = `${withFuzzy ? "Fuzzy" : ""}${withFullText ? "FullText" : ""}`;
2613
2839
  this.registerSchema(`StringFilter${this.filterSchemaSuffix({
2614
2840
  optional,
2615
2841
  allowedFilterKinds,
2616
2842
  withAggregations
2617
- })}`, schema);
2843
+ })}${featureSuffix}`, schema);
2618
2844
  return schema;
2619
2845
  }
2620
2846
  makeStringModeSchema() {
2621
2847
  return zod.z.union([zod.z.literal("default"), zod.z.literal("insensitive")]);
2622
2848
  }
2849
+ makeFuzzyFilterSchema() {
2850
+ return zod.z.strictObject({
2851
+ search: zod.z.string().min(1),
2852
+ mode: zod.z.union([
2853
+ zod.z.literal("simple"),
2854
+ zod.z.literal("word"),
2855
+ zod.z.literal("strictWord")
2856
+ ]).default("simple"),
2857
+ threshold: zod.z.number().min(0).max(1).optional(),
2858
+ unaccent: zod.z.boolean().default(false)
2859
+ });
2860
+ }
2861
+ makeFullTextFilterSchema() {
2862
+ return zod.z.strictObject({
2863
+ search: zod.z.string().min(1),
2864
+ config: zod.z.string().min(1).optional()
2865
+ });
2866
+ }
2623
2867
  makeSelectSchema(model, options) {
2624
2868
  const fields = {};
2625
2869
  for (const [field, fieldDef] of this.getModelFields(model)) if (fieldDef.relation) {
@@ -2720,7 +2964,7 @@ var ZodSchemaFactory = class {
2720
2964
  }).optional();
2721
2965
  } else if (fieldDef.optional) fields[field] = zod.z.union([sort, zod.z.strictObject({
2722
2966
  sort,
2723
- nulls: zod.z.union([zod.z.literal("first"), zod.z.literal("last")])
2967
+ nulls: zod.z.union([zod.z.literal("first"), zod.z.literal("last")]).optional()
2724
2968
  })]).optional();
2725
2969
  else fields[field] = sort.optional();
2726
2970
  if (WithAggregation) for (const agg of [
@@ -2730,6 +2974,29 @@ var ZodSchemaFactory = class {
2730
2974
  "_min",
2731
2975
  "_max"
2732
2976
  ]) fields[agg] = zod.z.lazy(() => this.makeOrderBySchema(model, true, false, options).optional());
2977
+ if (this.providerSupportsFuzzySearch) {
2978
+ const fuzzyFieldNames = this.getModelFields(model).filter(([, def]) => !def.relation && def.type === "String" && def.fuzzy === true).map(([name]) => name);
2979
+ if (fuzzyFieldNames.length > 0) fields["_fuzzyRelevance"] = zod.z.strictObject({
2980
+ fields: zod.z.array(zod.z.enum(fuzzyFieldNames)).min(1),
2981
+ search: zod.z.string(),
2982
+ mode: zod.z.union([
2983
+ zod.z.literal("simple"),
2984
+ zod.z.literal("word"),
2985
+ zod.z.literal("strictWord")
2986
+ ]).default("simple"),
2987
+ unaccent: zod.z.boolean().default(false),
2988
+ sort
2989
+ }).optional();
2990
+ }
2991
+ if (this.providerSupportsFullTextSearch) {
2992
+ const fullTextFieldNames = this.getModelFields(model).filter(([, def]) => !def.relation && def.type === "String" && def.fullText === true).map(([name]) => name);
2993
+ if (fullTextFieldNames.length > 0) fields["_ftsRelevance"] = zod.z.strictObject({
2994
+ fields: zod.z.array(zod.z.enum(fullTextFieldNames)).min(1),
2995
+ search: zod.z.string().min(1),
2996
+ config: zod.z.string().min(1).optional(),
2997
+ sort
2998
+ }).optional();
2999
+ }
2733
3000
  const schema = refineAtMostOneKey(zod.z.strictObject(fields));
2734
3001
  let schemaId = `${model}OrderBy`;
2735
3002
  if (withRelation) schemaId += "WithRelation";
@@ -2820,7 +3087,7 @@ var ZodSchemaFactory = class {
2820
3087
  fieldSchema = zod.z.union([fieldSchema, zod.z.strictObject({ set: fieldSchema })]).optional();
2821
3088
  }
2822
3089
  if (fieldDef.optional || fieldHasDefaultValue(fieldDef)) fieldSchema = fieldSchema.optional();
2823
- if (fieldDef.optional) if (fieldDef.type === "Json") fieldSchema = zod.z.union([fieldSchema, zod.z.instanceof(DbNullClass)]);
3090
+ if (fieldDef.optional) if (fieldDef.type === "Json") fieldSchema = zod.z.union([fieldSchema, zod.z.instanceof(require_common_types.DbNullClass)]);
2824
3091
  else if (this.isTypeDefType(fieldDef.type)) fieldSchema = this.makeNullableTypedJsonMutationSchema(fieldSchema);
2825
3092
  else fieldSchema = fieldSchema.nullable();
2826
3093
  uncheckedVariantFields[field] = fieldSchema;
@@ -3007,7 +3274,7 @@ var ZodSchemaFactory = class {
3007
3274
  push: zod.z.union([fieldSchema, fieldSchema.array()]).optional()
3008
3275
  }).refine((v) => Object.keys(v).length === 1, "Only one of \"set\", \"push\" can be provided")]);
3009
3276
  }
3010
- if (fieldDef.optional) if (fieldDef.type === "Json") fieldSchema = zod.z.union([fieldSchema, zod.z.instanceof(DbNullClass)]);
3277
+ if (fieldDef.optional) if (fieldDef.type === "Json") fieldSchema = zod.z.union([fieldSchema, zod.z.instanceof(require_common_types.DbNullClass)]);
3011
3278
  else if (this.isTypeDefType(fieldDef.type)) fieldSchema = this.makeNullableTypedJsonMutationSchema(fieldSchema);
3012
3279
  else fieldSchema = fieldSchema.nullable();
3013
3280
  fieldSchema = fieldSchema.optional();
@@ -3231,6 +3498,12 @@ var ZodSchemaFactory = class {
3231
3498
  get providerSupportsCaseSensitivity() {
3232
3499
  return this.schema.provider.type === "postgresql";
3233
3500
  }
3501
+ get providerSupportsFullTextSearch() {
3502
+ return this.schema.provider.type === "postgresql";
3503
+ }
3504
+ get providerSupportsFuzzySearch() {
3505
+ return this.schema.provider.type === "postgresql";
3506
+ }
3234
3507
  /**
3235
3508
  * Gets the effective set of allowed FilterKind values for a specific model and field.
3236
3509
  * Respects the precedence: model[field] > model.$all > $all[field] > $all.$all.
@@ -3392,6 +3665,8 @@ __decorate([
3392
3665
  Object,
3393
3666
  Boolean,
3394
3667
  Boolean,
3668
+ Object,
3669
+ Object,
3395
3670
  Object
3396
3671
  ]),
3397
3672
  __decorateMetadata("design:returntype", void 0)
@@ -3461,6 +3736,8 @@ __decorate([
3461
3736
  __decorateMetadata("design:paramtypes", [
3462
3737
  Boolean,
3463
3738
  Boolean,
3739
+ Object,
3740
+ Object,
3464
3741
  Object
3465
3742
  ]),
3466
3743
  __decorateMetadata("design:returntype", typeof (_ref10 = typeof zod.ZodType !== "undefined" && zod.ZodType) === "function" ? _ref10 : Object)
@@ -3950,7 +4227,7 @@ var BaseOperationHandler = class {
3950
4227
  return new this.constructor(client, this.model, this.inputValidator);
3951
4228
  }
3952
4229
  get hasPolicyEnabled() {
3953
- return this.options.plugins?.some((plugin) => plugin.constructor.name === "PolicyPlugin");
4230
+ return this.options.plugins?.some((plugin) => plugin.id === "policy") ?? false;
3954
4231
  }
3955
4232
  requireModel(model) {
3956
4233
  return requireModel(this.schema, model);
@@ -4051,8 +4328,8 @@ var BaseOperationHandler = class {
4051
4328
  const postCreateRelations = {};
4052
4329
  for (const [field, value] of Object.entries(data)) {
4053
4330
  const fieldDef = this.requireField(model, field);
4054
- 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);
4055
- else createFields[field] = this.dialect.transformInput(value, fieldDef.type, !!fieldDef.array);
4331
+ 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);
4332
+ else createFields[field] = this.dialect.transformInput(value, fieldDef.type, !!fieldDef.array, fieldDef);
4056
4333
  else if (!getManyToManyRelation(this.schema, model, field) && fieldDef.relation?.fields && fieldDef.relation?.references) {
4057
4334
  const fkValues = await this.processOwnedRelationForCreate(kysely$7, fieldDef, value);
4058
4335
  for (let i = 0; i < fieldDef.relation.fields.length; i++) createFields[fieldDef.relation.fields[i]] = fkValues[fieldDef.relation.references[i]];
@@ -4252,7 +4529,7 @@ var BaseOperationHandler = class {
4252
4529
  for (const [name, value] of Object.entries(item)) {
4253
4530
  const fieldDef = this.requireField(model, name);
4254
4531
  (0, _zenstackhq_common_helpers.invariant)(!fieldDef.relation, "createMany does not support relations");
4255
- newItem[name] = this.dialect.transformInput(value, fieldDef.type, !!fieldDef.array);
4532
+ newItem[name] = this.dialect.transformInput(value, fieldDef.type, !!fieldDef.array, fieldDef);
4256
4533
  }
4257
4534
  if (fromRelation) for (const { fk, pk } of relationKeyPairs) newItem[fk] = fromRelation.ids[pk];
4258
4535
  return this.fillGeneratedAndDefaultValues(modelDef, newItem);
@@ -4268,7 +4545,7 @@ var BaseOperationHandler = class {
4268
4545
  if (Object.keys(item).length === allPassedFields.length) continue;
4269
4546
  for (const field of allPassedFields) if (!(field in item)) {
4270
4547
  const fieldDef = this.requireField(model, field);
4271
- if (fieldDef.default !== void 0 && fieldDef.default !== null && typeof fieldDef.default !== "object") item[field] = this.dialect.transformInput(fieldDef.default, fieldDef.type, !!fieldDef.array);
4548
+ if (fieldDef.default !== void 0 && fieldDef.default !== null && typeof fieldDef.default !== "object") item[field] = this.dialect.transformInput(fieldDef.default, fieldDef.type, !!fieldDef.array, fieldDef);
4272
4549
  }
4273
4550
  }
4274
4551
  }
@@ -4331,15 +4608,15 @@ var BaseOperationHandler = class {
4331
4608
  if (!(field in data)) {
4332
4609
  if (typeof fieldDef?.default === "object" && "kind" in fieldDef.default) {
4333
4610
  const generated = this.evalGenerator(fieldDef.default);
4334
- if (generated !== void 0) values[field] = this.dialect.transformInput(generated, fieldDef.type, !!fieldDef.array);
4335
- } else if (fieldDef?.updatedAt) values[field] = this.dialect.transformInput(/* @__PURE__ */ new Date(), "DateTime", false);
4611
+ if (generated !== void 0) values[field] = this.dialect.transformInput(generated, fieldDef.type, !!fieldDef.array, fieldDef);
4612
+ } else if (fieldDef?.updatedAt) values[field] = this.dialect.transformInput(/* @__PURE__ */ new Date(), "DateTime", false, fieldDef);
4336
4613
  else if (fieldDef?.default !== void 0) {
4337
4614
  let value = fieldDef.default;
4338
4615
  if (fieldDef.type === "Json") {
4339
4616
  if (fieldDef.array && Array.isArray(value)) value = value.map((v) => typeof v === "string" ? JSON.parse(v) : v);
4340
4617
  else if (typeof value === "string") value = JSON.parse(value);
4341
4618
  }
4342
- values[field] = this.dialect.transformInput(value, fieldDef.type, !!fieldDef.array);
4619
+ values[field] = this.dialect.transformInput(value, fieldDef.type, !!fieldDef.array, fieldDef);
4343
4620
  }
4344
4621
  }
4345
4622
  }
@@ -4389,7 +4666,7 @@ var BaseOperationHandler = class {
4389
4666
  const ignoredFields = new Set(typeof fieldDef.updatedAt === "boolean" ? [] : fieldDef.updatedAt.ignore);
4390
4667
  if (Object.keys(data).some((field) => (isScalarField(this.schema, modelDef.name, field) || isForeignKeyField(this.schema, modelDef.name, field)) && !ignoredFields.has(field))) {
4391
4668
  if (finalData === data) finalData = (0, _zenstackhq_common_helpers.clone)(data);
4392
- finalData[fieldName] = this.dialect.transformInput(/* @__PURE__ */ new Date(), "DateTime", false);
4669
+ finalData[fieldName] = this.dialect.transformInput(/* @__PURE__ */ new Date(), "DateTime", false, fieldDef);
4393
4670
  autoUpdatedFields.push(fieldName);
4394
4671
  }
4395
4672
  }
@@ -4493,7 +4770,7 @@ var BaseOperationHandler = class {
4493
4770
  const fieldDef = this.requireField(model, field);
4494
4771
  if (this.isNumericIncrementalUpdate(fieldDef, data[field])) return this.transformIncrementalUpdate(model, field, fieldDef, data[field]);
4495
4772
  if (fieldDef.array && typeof data[field] === "object" && !Array.isArray(data[field]) && data[field]) return this.transformScalarListUpdate(model, field, fieldDef, data[field]);
4496
- return this.dialect.transformInput(data[field], fieldDef.type, !!fieldDef.array);
4773
+ return this.dialect.transformInput(data[field], fieldDef.type, !!fieldDef.array, fieldDef);
4497
4774
  }
4498
4775
  isNumericIncrementalUpdate(fieldDef, value) {
4499
4776
  if (!this.isNumericField(fieldDef)) return false;
@@ -4521,7 +4798,7 @@ var BaseOperationHandler = class {
4521
4798
  transformIncrementalUpdate(model, field, fieldDef, payload) {
4522
4799
  (0, _zenstackhq_common_helpers.invariant)(Object.keys(payload).length === 1, "Only one of \"set\", \"increment\", \"decrement\", \"multiply\", or \"divide\" can be provided");
4523
4800
  const key = Object.keys(payload)[0];
4524
- const value = this.dialect.transformInput(payload[key], fieldDef.type, false);
4801
+ const value = this.dialect.transformInput(payload[key], fieldDef.type, false, fieldDef);
4525
4802
  const eb = (0, kysely.expressionBuilder)();
4526
4803
  const fieldRef = this.dialect.fieldRef(model, field);
4527
4804
  return (0, ts_pattern.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(() => {
@@ -4531,7 +4808,7 @@ var BaseOperationHandler = class {
4531
4808
  transformScalarListUpdate(model, field, fieldDef, payload) {
4532
4809
  (0, _zenstackhq_common_helpers.invariant)(Object.keys(payload).length === 1, "Only one of \"set\", \"push\" can be provided");
4533
4810
  const key = Object.keys(payload)[0];
4534
- const value = this.dialect.transformInput(payload[key], fieldDef.type, true);
4811
+ const value = this.dialect.transformInput(payload[key], fieldDef.type, true, fieldDef);
4535
4812
  const eb = (0, kysely.expressionBuilder)();
4536
4813
  const fieldRef = this.dialect.fieldRef(model, field);
4537
4814
  return (0, ts_pattern.match)(key).with("set", () => value).with("push", () => {
@@ -6942,12 +7219,21 @@ var ClientImpl = class ClientImpl {
6942
7219
  });
6943
7220
  }
6944
7221
  }
7222
+ getPromiseCallback(promise) {
7223
+ (0, _zenstackhq_common_helpers.invariant)(promise.cb, "Invalid ZenStackPromise, missing cb property");
7224
+ const cb = promise.cb;
7225
+ (0, _zenstackhq_common_helpers.invariant)(typeof cb === "function", "Invalid ZenStackPromise, cb property is not a function");
7226
+ return promise.cb;
7227
+ }
6945
7228
  async sequentialTransaction(arg, options) {
6946
7229
  const execute = async (tx) => {
6947
7230
  const txClient = new ClientImpl(this.schema, this.$options, this);
6948
7231
  txClient.kysely = tx;
6949
7232
  const result = [];
6950
- for (const promise of arg) result.push(await promise.cb(txClient));
7233
+ for (const promise of arg) {
7234
+ const cb = this.getPromiseCallback(promise);
7235
+ result.push(await cb(txClient));
7236
+ }
6951
7237
  return result;
6952
7238
  };
6953
7239
  if (this.kysely.isTransaction) return execute(this.kysely);
@@ -7734,6 +8020,15 @@ var DefaultOperationNodeVisitor = class extends kysely.OperationNodeVisitor {
7734
8020
  visitCollate(node) {
7735
8021
  this.defaultVisit(node);
7736
8022
  }
8023
+ visitAlterType(node) {
8024
+ this.defaultVisit(node);
8025
+ }
8026
+ visitAddValue(node) {
8027
+ this.defaultVisit(node);
8028
+ }
8029
+ visitRenameValue(node) {
8030
+ this.defaultVisit(node);
8031
+ }
7737
8032
  };
7738
8033
  //#endregion
7739
8034
  //#region src/utils/schema-utils.ts
@@ -7796,8 +8091,8 @@ var MatchingExpressionVisitor = class extends ExpressionVisitor {
7796
8091
  exports.AllCrudOperations = AllCrudOperations;
7797
8092
  exports.AllReadOperations = AllReadOperations;
7798
8093
  exports.AllWriteOperations = AllWriteOperations;
7799
- exports.AnyNull = AnyNull;
7800
- exports.AnyNullClass = AnyNullClass;
8094
+ exports.AnyNull = require_common_types.AnyNull;
8095
+ exports.AnyNullClass = require_common_types.AnyNullClass;
7801
8096
  exports.BaseCrudDialect = BaseCrudDialect;
7802
8097
  exports.CRUD = CRUD;
7803
8098
  exports.CRUD_EXT = CRUD_EXT;
@@ -7807,11 +8102,13 @@ exports.CoreDeleteOperations = CoreDeleteOperations;
7807
8102
  exports.CoreReadOperations = CoreReadOperations;
7808
8103
  exports.CoreUpdateOperations = CoreUpdateOperations;
7809
8104
  exports.CoreWriteOperations = CoreWriteOperations;
7810
- exports.DbNull = DbNull;
7811
- exports.DbNullClass = DbNullClass;
8105
+ exports.DbNull = require_common_types.DbNull;
8106
+ exports.DbNullClass = require_common_types.DbNullClass;
8107
+ exports.ExtQueryArgsMarker = ExtQueryArgsMarker;
8108
+ exports.ExtResultMarker = ExtResultMarker;
7812
8109
  exports.InputValidator = InputValidator;
7813
- exports.JsonNull = JsonNull;
7814
- exports.JsonNullClass = JsonNullClass;
8110
+ exports.JsonNull = require_common_types.JsonNull;
8111
+ exports.JsonNullClass = require_common_types.JsonNullClass;
7815
8112
  Object.defineProperty(exports, "KyselyUtils", {
7816
8113
  enumerable: true,
7817
8114
  get: function() {