@zenstackhq/orm 3.0.0-beta.27 → 3.0.0-beta.29

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.js CHANGED
@@ -52,10 +52,12 @@ __export(query_utils_exports, {
52
52
  isInheritedField: () => isInheritedField,
53
53
  isRelationField: () => isRelationField,
54
54
  isScalarField: () => isScalarField,
55
+ isTypeDef: () => isTypeDef,
55
56
  makeDefaultOrderBy: () => makeDefaultOrderBy,
56
57
  requireField: () => requireField,
57
58
  requireIdFields: () => requireIdFields,
58
59
  requireModel: () => requireModel,
60
+ requireTypeDef: () => requireTypeDef,
59
61
  stripAlias: () => stripAlias
60
62
  });
61
63
  import { invariant } from "@zenstackhq/common-helpers";
@@ -206,6 +208,14 @@ function requireModel(schema, model) {
206
208
  return modelDef;
207
209
  }
208
210
  __name(requireModel, "requireModel");
211
+ function requireTypeDef(schema, type) {
212
+ const typeDef = getTypeDef(schema, type);
213
+ if (!typeDef) {
214
+ throw createInternalError(`Type "${type}" not found in schema`, type);
215
+ }
216
+ return typeDef;
217
+ }
218
+ __name(requireTypeDef, "requireTypeDef");
209
219
  function getField(schema, model, field) {
210
220
  const modelDef = getModel(schema, model);
211
221
  return modelDef?.fields[field];
@@ -353,6 +363,10 @@ function getEnum(schema, type) {
353
363
  return schema.enums?.[type];
354
364
  }
355
365
  __name(getEnum, "getEnum");
366
+ function isTypeDef(schema, type) {
367
+ return !!schema.typeDefs?.[type];
368
+ }
369
+ __name(isTypeDef, "isTypeDef");
356
370
  function buildJoinPairs(schema, model, modelAlias, relationField, relationModelAlias) {
357
371
  const { keyPairs, ownedByModel } = getRelationForeignKeyFieldPairs(schema, model, relationField);
358
372
  return keyPairs.map(({ fk, pk }) => {
@@ -604,6 +618,32 @@ import { sql as sql2 } from "kysely";
604
618
  import { match as match3 } from "ts-pattern";
605
619
  import z from "zod";
606
620
 
621
+ // src/common-types.ts
622
+ var DbNullClass = class {
623
+ static {
624
+ __name(this, "DbNullClass");
625
+ }
626
+ // @ts-ignore
627
+ __brand = "DbNull";
628
+ };
629
+ var DbNull = new DbNullClass();
630
+ var JsonNullClass = class {
631
+ static {
632
+ __name(this, "JsonNullClass");
633
+ }
634
+ // @ts-ignore
635
+ __brand = "JsonNull";
636
+ };
637
+ var JsonNull = new JsonNullClass();
638
+ var AnyNullClass = class {
639
+ static {
640
+ __name(this, "AnyNullClass");
641
+ }
642
+ // @ts-ignore
643
+ __brand = "AnyNull";
644
+ };
645
+ var AnyNull = new AnyNullClass();
646
+
607
647
  // src/client/crud/dialects/base-dialect.ts
608
648
  import { enumerate, invariant as invariant2, isPlainObject } from "@zenstackhq/common-helpers";
609
649
  import { expressionBuilder, sql } from "kysely";
@@ -875,12 +915,174 @@ var BaseCrudDialect = class {
875
915
  if (isEnum(this.schema, fieldDef.type)) {
876
916
  return this.buildEnumFilter(fieldRef, fieldDef, payload);
877
917
  }
878
- return match2(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", () => {
879
- throw createNotSupportedError("JSON filters are not supported yet");
880
- }).with("Unsupported", () => {
918
+ if (isTypeDef(this.schema, fieldDef.type)) {
919
+ return this.buildJsonFilter(fieldRef, payload, fieldDef);
920
+ }
921
+ return match2(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", () => {
881
922
  throw createInvalidInputError(`Unsupported field cannot be used in filters`);
882
923
  }).exhaustive();
883
924
  }
925
+ buildJsonFilter(receiver, filter, fieldDef) {
926
+ invariant2(filter && typeof filter === "object", "Json filter payload must be an object");
927
+ if ([
928
+ "path",
929
+ "equals",
930
+ "not",
931
+ "string_contains",
932
+ "string_starts_with",
933
+ "string_ends_with",
934
+ "array_contains",
935
+ "array_starts_with",
936
+ "array_ends_with"
937
+ ].some((k) => k in filter)) {
938
+ return this.buildPlainJsonFilter(receiver, filter);
939
+ } else if (isTypeDef(this.schema, fieldDef.type)) {
940
+ return this.buildTypedJsonFilter(receiver, filter, fieldDef.type, !!fieldDef.array);
941
+ } else {
942
+ throw createInvalidInputError(`Invalid JSON filter payload`);
943
+ }
944
+ }
945
+ buildPlainJsonFilter(receiver, filter) {
946
+ const clauses = [];
947
+ const path = filter.path;
948
+ const jsonReceiver = this.buildJsonPathSelection(receiver, path);
949
+ const stringReceiver = this.eb.cast(jsonReceiver, "text");
950
+ const mode = filter.mode ?? "default";
951
+ invariant2(mode === "default" || mode === "insensitive", "Invalid JSON filter mode");
952
+ for (const [key, value] of Object.entries(filter)) {
953
+ switch (key) {
954
+ case "equals": {
955
+ clauses.push(this.buildJsonValueFilterClause(jsonReceiver, value));
956
+ break;
957
+ }
958
+ case "not": {
959
+ clauses.push(this.eb.not(this.buildJsonValueFilterClause(jsonReceiver, value)));
960
+ break;
961
+ }
962
+ case "string_contains": {
963
+ invariant2(typeof value === "string", "string_contains value must be a string");
964
+ clauses.push(this.buildJsonStringFilter(stringReceiver, key, value, mode));
965
+ break;
966
+ }
967
+ case "string_starts_with": {
968
+ invariant2(typeof value === "string", "string_starts_with value must be a string");
969
+ clauses.push(this.buildJsonStringFilter(stringReceiver, key, value, mode));
970
+ break;
971
+ }
972
+ case "string_ends_with": {
973
+ invariant2(typeof value === "string", "string_ends_with value must be a string");
974
+ clauses.push(this.buildJsonStringFilter(stringReceiver, key, value, mode));
975
+ break;
976
+ }
977
+ case "array_contains": {
978
+ clauses.push(this.buildJsonArrayFilter(jsonReceiver, key, value));
979
+ break;
980
+ }
981
+ case "array_starts_with": {
982
+ clauses.push(this.buildJsonArrayFilter(jsonReceiver, key, value));
983
+ break;
984
+ }
985
+ case "array_ends_with": {
986
+ clauses.push(this.buildJsonArrayFilter(jsonReceiver, key, value));
987
+ break;
988
+ }
989
+ case "path":
990
+ case "mode":
991
+ break;
992
+ default:
993
+ throw createInvalidInputError(`Invalid JSON filter key: ${key}`);
994
+ }
995
+ }
996
+ return this.and(...clauses);
997
+ }
998
+ buildTypedJsonFilter(receiver, filter, typeDefName, array) {
999
+ if (array) {
1000
+ return this.buildTypedJsonArrayFilter(receiver, filter, typeDefName);
1001
+ } else {
1002
+ return this.buildTypeJsonNonArrayFilter(receiver, filter, typeDefName);
1003
+ }
1004
+ }
1005
+ buildTypedJsonArrayFilter(receiver, filter, typeDefName) {
1006
+ invariant2(filter && typeof filter === "object", "Typed JSON array filter payload must be an object");
1007
+ const makeExistsPred = /* @__PURE__ */ __name((filter2) => this.buildJsonArrayExistsPredicate(receiver, (elem) => this.buildTypedJsonFilter(elem, filter2, typeDefName, false)), "makeExistsPred");
1008
+ const makeExistsNegatedPred = /* @__PURE__ */ __name((filter2) => this.buildJsonArrayExistsPredicate(receiver, (elem) => this.eb.not(this.buildTypedJsonFilter(elem, filter2, typeDefName, false))), "makeExistsNegatedPred");
1009
+ const clauses = [];
1010
+ for (const [key, value] of Object.entries(filter)) {
1011
+ if (!value || typeof value !== "object") {
1012
+ continue;
1013
+ }
1014
+ switch (key) {
1015
+ case "some":
1016
+ clauses.push(makeExistsPred(value));
1017
+ break;
1018
+ case "none":
1019
+ clauses.push(this.eb.not(makeExistsPred(value)));
1020
+ break;
1021
+ case "every":
1022
+ clauses.push(this.eb.not(makeExistsNegatedPred(value)));
1023
+ break;
1024
+ default:
1025
+ invariant2(false, `Invalid typed JSON array filter key: ${key}`);
1026
+ }
1027
+ }
1028
+ return this.and(...clauses);
1029
+ }
1030
+ buildTypeJsonNonArrayFilter(receiver, filter, typeDefName) {
1031
+ const clauses = [];
1032
+ if (filter === null) {
1033
+ return this.eb(receiver, "=", "null");
1034
+ }
1035
+ invariant2(filter && typeof filter === "object", "Typed JSON filter payload must be an object");
1036
+ if ("is" in filter || "isNot" in filter) {
1037
+ if ("is" in filter && filter.is && typeof filter.is === "object") {
1038
+ clauses.push(this.buildTypedJsonFilter(receiver, filter.is, typeDefName, false));
1039
+ }
1040
+ if ("isNot" in filter && filter.isNot && typeof filter.isNot === "object") {
1041
+ clauses.push(this.eb.not(this.buildTypedJsonFilter(receiver, filter.isNot, typeDefName, false)));
1042
+ }
1043
+ } else {
1044
+ const typeDef = requireTypeDef(this.schema, typeDefName);
1045
+ for (const [key, value] of Object.entries(filter)) {
1046
+ const fieldDef = typeDef.fields[key];
1047
+ invariant2(fieldDef, `Field "${key}" not found in type definition "${typeDefName}"`);
1048
+ const fieldReceiver = this.buildJsonPathSelection(receiver, `$.${key}`);
1049
+ if (isTypeDef(this.schema, fieldDef.type)) {
1050
+ clauses.push(this.buildTypedJsonFilter(fieldReceiver, value, fieldDef.type, !!fieldDef.array));
1051
+ } else {
1052
+ if (fieldDef.array) {
1053
+ clauses.push(this.buildArrayFilter(fieldReceiver, fieldDef, value));
1054
+ } else {
1055
+ let _receiver = fieldReceiver;
1056
+ if (fieldDef.type === "String") {
1057
+ _receiver = this.eb.fn("trim", [
1058
+ this.eb.cast(fieldReceiver, "text"),
1059
+ sql.lit('"')
1060
+ ]);
1061
+ }
1062
+ clauses.push(this.buildPrimitiveFilter(_receiver, fieldDef, value));
1063
+ }
1064
+ }
1065
+ }
1066
+ }
1067
+ return this.and(...clauses);
1068
+ }
1069
+ buildJsonValueFilterClause(lhs, value) {
1070
+ if (value instanceof DbNullClass) {
1071
+ return this.eb(lhs, "is", null);
1072
+ } else if (value instanceof JsonNullClass) {
1073
+ return this.eb.and([
1074
+ this.eb(lhs, "=", "null"),
1075
+ this.eb(lhs, "is not", null)
1076
+ ]);
1077
+ } else if (value instanceof AnyNullClass) {
1078
+ return this.eb.or([
1079
+ this.eb(lhs, "is", null),
1080
+ this.eb(lhs, "=", "null")
1081
+ ]);
1082
+ } else {
1083
+ return this.buildLiteralFilter(lhs, "Json", value);
1084
+ }
1085
+ }
884
1086
  buildLiteralFilter(lhs, type, rhs) {
885
1087
  return this.eb(lhs, "=", rhs !== null && rhs !== void 0 ? this.transformPrimitive(rhs, type, false) : rhs);
886
1088
  }
@@ -951,7 +1153,9 @@ var BaseCrudDialect = class {
951
1153
  if (key === "mode" || consumedKeys.includes(key)) {
952
1154
  continue;
953
1155
  }
954
- const condition = match2(key).with("contains", () => mode === "insensitive" ? this.eb(fieldRef, "ilike", sql.val(`%${value}%`)) : this.eb(fieldRef, "like", sql.val(`%${value}%`))).with("startsWith", () => mode === "insensitive" ? this.eb(fieldRef, "ilike", sql.val(`${value}%`)) : this.eb(fieldRef, "like", sql.val(`${value}%`))).with("endsWith", () => mode === "insensitive" ? this.eb(fieldRef, "ilike", sql.val(`%${value}`)) : this.eb(fieldRef, "like", sql.val(`%${value}`))).otherwise(() => {
1156
+ invariant2(typeof value === "string", `${key} value must be a string`);
1157
+ const escapedValue = this.escapeLikePattern(value);
1158
+ const condition = match2(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(() => {
955
1159
  throw createInvalidInputError(`Invalid string filter key: ${key}`);
956
1160
  });
957
1161
  if (condition) {
@@ -961,6 +1165,19 @@ var BaseCrudDialect = class {
961
1165
  }
962
1166
  return this.and(...conditions);
963
1167
  }
1168
+ buildJsonStringFilter(receiver, operation, value, mode) {
1169
+ const escapedValue = this.escapeLikePattern(value);
1170
+ const pattern = match2(operation).with("string_contains", () => `"%${escapedValue}%"`).with("string_starts_with", () => `"${escapedValue}%"`).with("string_ends_with", () => `"%${escapedValue}"`).exhaustive();
1171
+ return this.buildStringLike(receiver, pattern, mode === "insensitive");
1172
+ }
1173
+ escapeLikePattern(pattern) {
1174
+ return pattern.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
1175
+ }
1176
+ buildStringLike(receiver, pattern, insensitive) {
1177
+ const { supportsILike } = this.getStringCasingBehavior();
1178
+ const op = insensitive && supportsILike ? "ilike" : "like";
1179
+ return sql`${receiver} ${sql.raw(op)} ${sql.val(pattern)} escape '\\'`;
1180
+ }
964
1181
  prepStringCasing(eb, value, mode) {
965
1182
  if (!mode || mode === "default") {
966
1183
  return value === null ? value : sql.val(value);
@@ -1286,6 +1503,13 @@ var PostgresCrudDialect = class extends BaseCrudDialect {
1286
1503
  if (value === void 0) {
1287
1504
  return value;
1288
1505
  }
1506
+ if (value instanceof JsonNullClass) {
1507
+ return "null";
1508
+ } else if (value instanceof DbNullClass) {
1509
+ return null;
1510
+ } else if (value instanceof AnyNullClass) {
1511
+ invariant3(false, "should not reach here: AnyNull is not a valid input value");
1512
+ }
1289
1513
  if (Array.isArray(value)) {
1290
1514
  if (type === "Json" && !forArrayField) {
1291
1515
  return JSON.stringify(value);
@@ -1482,6 +1706,35 @@ var PostgresCrudDialect = class extends BaseCrudDialect {
1482
1706
  return `ARRAY[${values.map((v) => typeof v === "string" ? `'${v}'` : v)}]`;
1483
1707
  }
1484
1708
  }
1709
+ buildJsonPathSelection(receiver, path) {
1710
+ if (path) {
1711
+ return this.eb.fn("jsonb_path_query_first", [
1712
+ receiver,
1713
+ this.eb.val(path)
1714
+ ]);
1715
+ } else {
1716
+ return receiver;
1717
+ }
1718
+ }
1719
+ buildJsonArrayFilter(lhs, operation, value) {
1720
+ return match3(operation).with("array_contains", () => {
1721
+ const v = Array.isArray(value) ? value : [
1722
+ value
1723
+ ];
1724
+ return sql2`${lhs} @> ${sql2.val(JSON.stringify(v))}::jsonb`;
1725
+ }).with("array_starts_with", () => this.eb(this.eb.fn("jsonb_extract_path", [
1726
+ lhs,
1727
+ this.eb.val("0")
1728
+ ]), "=", this.transformPrimitive(value, "Json", false))).with("array_ends_with", () => this.eb(this.eb.fn("jsonb_extract_path", [
1729
+ lhs,
1730
+ sql2`(jsonb_array_length(${lhs}) - 1)::text`
1731
+ ]), "=", this.transformPrimitive(value, "Json", false))).exhaustive();
1732
+ }
1733
+ buildJsonArrayExistsPredicate(receiver, buildFilter) {
1734
+ return this.eb.exists(this.eb.selectFrom(this.eb.fn("jsonb_array_elements", [
1735
+ receiver
1736
+ ]).as("$items")).select(this.eb.lit(1).as("$t")).where(buildFilter(this.eb.ref("$items.value"))));
1737
+ }
1485
1738
  get supportInsertWithDefault() {
1486
1739
  return true;
1487
1740
  }
@@ -1524,14 +1777,20 @@ var SqliteCrudDialect = class extends BaseCrudDialect {
1524
1777
  if (value === void 0) {
1525
1778
  return value;
1526
1779
  }
1780
+ if (value instanceof JsonNullClass) {
1781
+ return "null";
1782
+ } else if (value instanceof DbNullClass) {
1783
+ return null;
1784
+ } else if (value instanceof AnyNullClass) {
1785
+ invariant4(false, "should not reach here: AnyNull is not a valid input value");
1786
+ }
1787
+ if (type === "Json" || this.schema.typeDefs && type in this.schema.typeDefs) {
1788
+ return JSON.stringify(value);
1789
+ }
1527
1790
  if (Array.isArray(value)) {
1528
1791
  return value.map((v) => this.transformPrimitive(v, type, false));
1529
1792
  } else {
1530
- if (this.schema.typeDefs && type in this.schema.typeDefs) {
1531
- return JSON.stringify(value);
1532
- } else {
1533
- return match4(type).with("Boolean", () => value ? 1 : 0).with("DateTime", () => value instanceof Date ? value.toISOString() : typeof value === "string" ? new Date(value).toISOString() : value).with("Decimal", () => value.toString()).with("Bytes", () => Buffer.from(value)).with("Json", () => JSON.stringify(value)).otherwise(() => value);
1534
- }
1793
+ return match4(type).with("Boolean", () => value ? 1 : 0).with("DateTime", () => value instanceof Date ? value.toISOString() : typeof value === "string" ? new Date(value).toISOString() : value).with("Decimal", () => value.toString()).with("Bytes", () => Buffer.from(value)).otherwise(() => value);
1535
1794
  }
1536
1795
  }
1537
1796
  transformOutput(value, type) {
@@ -1701,6 +1960,30 @@ var SqliteCrudDialect = class extends BaseCrudDialect {
1701
1960
  value2
1702
1961
  ]));
1703
1962
  }
1963
+ buildJsonPathSelection(receiver, path) {
1964
+ if (!path) {
1965
+ return receiver;
1966
+ } else {
1967
+ return sql3`${receiver} -> ${this.eb.val(path)}`;
1968
+ }
1969
+ }
1970
+ buildJsonArrayFilter(lhs, operation, value) {
1971
+ return match4(operation).with("array_contains", () => {
1972
+ if (Array.isArray(value)) {
1973
+ throw createNotSupportedError('SQLite "array_contains" only supports checking for a single value, not an array of values');
1974
+ } else {
1975
+ return sql3`EXISTS (SELECT 1 FROM json_each(${lhs}) WHERE value = ${value})`;
1976
+ }
1977
+ }).with("array_starts_with", () => this.eb(this.eb.fn("json_extract", [
1978
+ lhs,
1979
+ this.eb.val("$[0]")
1980
+ ]), "=", value)).with("array_ends_with", () => this.eb(sql3`json_extract(${lhs}, '$[' || (json_array_length(${lhs}) - 1) || ']')`, "=", value)).exhaustive();
1981
+ }
1982
+ buildJsonArrayExistsPredicate(receiver, buildFilter) {
1983
+ return this.eb.exists(this.eb.selectFrom(this.eb.fn("json_each", [
1984
+ receiver
1985
+ ]).as("$items")).select(this.eb.lit(1).as("$t")).where(buildFilter(this.eb.ref("$items.value"))));
1986
+ }
1704
1987
  get supportsUpdateWithLimit() {
1705
1988
  return false;
1706
1989
  }
@@ -2319,8 +2602,16 @@ var BaseOperationHandler = class {
2319
2602
  autoUpdatedFields.push(fieldName);
2320
2603
  }
2321
2604
  }
2605
+ const thisEntity = await this.getEntityIds(kysely, model, combinedWhere);
2606
+ if (!thisEntity) {
2607
+ if (throwIfNotFound) {
2608
+ throw createNotFoundError(model);
2609
+ } else {
2610
+ return null;
2611
+ }
2612
+ }
2322
2613
  if (Object.keys(finalData).length === 0) {
2323
- return combinedWhere;
2614
+ return thisEntity;
2324
2615
  }
2325
2616
  let needIdRead = false;
2326
2617
  if (modelDef.baseModel && !this.isIdFilter(model, combinedWhere)) {
@@ -2340,9 +2631,15 @@ var BaseOperationHandler = class {
2340
2631
  const baseUpdateResult = await this.processBaseModelUpdate(kysely, modelDef.baseModel, combinedWhere, finalData, throwIfNotFound);
2341
2632
  finalData = baseUpdateResult.remainingFields;
2342
2633
  combinedWhere = baseUpdateResult.baseEntity;
2634
+ if (baseUpdateResult.baseEntity) {
2635
+ for (const [key, value] of Object.entries(baseUpdateResult.baseEntity)) {
2636
+ if (key in thisEntity) {
2637
+ thisEntity[key] = value;
2638
+ }
2639
+ }
2640
+ }
2343
2641
  }
2344
2642
  const updateFields = {};
2345
- let thisEntity = void 0;
2346
2643
  for (const field in finalData) {
2347
2644
  const fieldDef = this.requireField(model, field);
2348
2645
  if (isScalarField(this.schema, model, field) || isForeignKeyField(this.schema, model, field)) {
@@ -2351,17 +2648,7 @@ var BaseOperationHandler = class {
2351
2648
  if (!allowRelationUpdate) {
2352
2649
  throw createNotSupportedError(`Relation update not allowed for field "${field}"`);
2353
2650
  }
2354
- if (!thisEntity) {
2355
- thisEntity = await this.getEntityIds(kysely, model, combinedWhere);
2356
- if (!thisEntity) {
2357
- if (throwIfNotFound) {
2358
- throw createNotFoundError(model);
2359
- } else {
2360
- return null;
2361
- }
2362
- }
2363
- }
2364
- const parentUpdates = await this.processRelationUpdates(kysely, model, field, fieldDef, thisEntity, finalData[field], throwIfNotFound);
2651
+ const parentUpdates = await this.processRelationUpdates(kysely, model, field, fieldDef, thisEntity, finalData[field]);
2365
2652
  if (Object.keys(parentUpdates).length > 0) {
2366
2653
  Object.assign(updateFields, parentUpdates);
2367
2654
  }
@@ -2372,7 +2659,7 @@ var BaseOperationHandler = class {
2372
2659
  hasFieldUpdate = Object.keys(updateFields).some((f) => !autoUpdatedFields.includes(f));
2373
2660
  }
2374
2661
  if (!hasFieldUpdate) {
2375
- return combinedWhere;
2662
+ return thisEntity;
2376
2663
  } else {
2377
2664
  fieldsToReturn = fieldsToReturn ?? requireIdFields(this.schema, model);
2378
2665
  const query = kysely.updateTable(model).where(() => this.dialect.buildFilter(model, model, combinedWhere)).set(updateFields).returning(fieldsToReturn).modifyEnd(this.makeContextComment({
@@ -2551,7 +2838,7 @@ var BaseOperationHandler = class {
2551
2838
  const idFields = requireIdFields(this.schema, model);
2552
2839
  return idFields.map((f) => kysely.dynamic.ref(`${model}.${f}`));
2553
2840
  }
2554
- async processRelationUpdates(kysely, model, field, fieldDef, parentIds, args, throwIfNotFound) {
2841
+ async processRelationUpdates(kysely, model, field, fieldDef, parentIds, args) {
2555
2842
  const fieldModel = fieldDef.type;
2556
2843
  const fromRelationContext = {
2557
2844
  model,
@@ -2602,6 +2889,7 @@ var BaseOperationHandler = class {
2602
2889
  where = void 0;
2603
2890
  data = item;
2604
2891
  }
2892
+ const throwIfNotFound = !fieldDef.array || !!where;
2605
2893
  await this.update(kysely, fieldModel, where, data, fromRelationContext, true, throwIfNotFound);
2606
2894
  }
2607
2895
  break;
@@ -4106,7 +4394,7 @@ var InputValidator = class {
4106
4394
  }
4107
4395
  return result;
4108
4396
  }
4109
- makePrimitiveSchema(type, attributes) {
4397
+ makeScalarSchema(type, attributes) {
4110
4398
  if (this.schema.typeDefs && type in this.schema.typeDefs) {
4111
4399
  return this.makeTypeDefSchema(type);
4112
4400
  } else if (this.schema.enums && type in this.schema.enums) {
@@ -4123,8 +4411,8 @@ var InputValidator = class {
4123
4411
  ]);
4124
4412
  }).with("DateTime", () => z3.union([
4125
4413
  z3.date(),
4126
- z3.string().datetime()
4127
- ])).with("Bytes", () => z3.instanceof(Uint8Array)).otherwise(() => z3.unknown());
4414
+ z3.iso.datetime()
4415
+ ])).with("Bytes", () => z3.instanceof(Uint8Array)).with("Json", () => this.makeJsonValueSchema(false, false)).otherwise(() => z3.unknown());
4128
4416
  }
4129
4417
  }
4130
4418
  makeEnumSchema(type) {
@@ -4155,20 +4443,23 @@ var InputValidator = class {
4155
4443
  const typeDef = getTypeDef(this.schema, type);
4156
4444
  invariant7(typeDef, `Type definition "${type}" not found in schema`);
4157
4445
  schema = z3.looseObject(Object.fromEntries(Object.entries(typeDef.fields).map(([field, def]) => {
4158
- let fieldSchema = this.makePrimitiveSchema(def.type);
4446
+ let fieldSchema = this.makeScalarSchema(def.type);
4159
4447
  if (def.array) {
4160
4448
  fieldSchema = fieldSchema.array();
4161
4449
  }
4162
4450
  if (def.optional) {
4163
- fieldSchema = fieldSchema.optional();
4451
+ fieldSchema = fieldSchema.nullish();
4164
4452
  }
4165
4453
  return [
4166
4454
  field,
4167
4455
  fieldSchema
4168
4456
  ];
4169
4457
  })));
4170
- this.setSchemaCache(key, schema);
4171
- return schema;
4458
+ const finalSchema = z3.custom((v) => {
4459
+ return schema.safeParse(v).success;
4460
+ });
4461
+ this.setSchemaCache(key, finalSchema);
4462
+ return finalSchema;
4172
4463
  }
4173
4464
  makeWhereSchema(model, unique, withoutRelationFields = false, withAggregations = false) {
4174
4465
  const modelDef = requireModel(this.schema, model);
@@ -4208,6 +4499,8 @@ var InputValidator = class {
4208
4499
  }
4209
4500
  } else if (fieldDef.array) {
4210
4501
  fieldSchema = this.makeArrayFilterSchema(fieldDef.type);
4502
+ } else if (this.isTypeDefType(fieldDef.type)) {
4503
+ fieldSchema = this.makeTypedJsonFilterSchema(fieldDef.type, !!fieldDef.optional, !!fieldDef.array);
4211
4504
  } else {
4212
4505
  fieldSchema = this.makePrimitiveFilterSchema(fieldDef.type, !!fieldDef.optional, withAggregations);
4213
4506
  }
@@ -4264,6 +4557,52 @@ var InputValidator = class {
4264
4557
  }
4265
4558
  return result;
4266
4559
  }
4560
+ makeTypedJsonFilterSchema(type, optional, array) {
4561
+ const typeDef = getTypeDef(this.schema, type);
4562
+ invariant7(typeDef, `Type definition "${type}" not found in schema`);
4563
+ const candidates = [];
4564
+ if (!array) {
4565
+ const fieldSchemas = {};
4566
+ for (const [fieldName, fieldDef] of Object.entries(typeDef.fields)) {
4567
+ if (this.isTypeDefType(fieldDef.type)) {
4568
+ fieldSchemas[fieldName] = this.makeTypedJsonFilterSchema(fieldDef.type, !!fieldDef.optional, !!fieldDef.array).optional();
4569
+ } else {
4570
+ if (fieldDef.array) {
4571
+ fieldSchemas[fieldName] = this.makeArrayFilterSchema(fieldDef.type).optional();
4572
+ } else {
4573
+ const enumDef = getEnum(this.schema, fieldDef.type);
4574
+ if (enumDef) {
4575
+ fieldSchemas[fieldName] = this.makeEnumFilterSchema(enumDef, !!fieldDef.optional, false).optional();
4576
+ } else {
4577
+ fieldSchemas[fieldName] = this.makePrimitiveFilterSchema(fieldDef.type, !!fieldDef.optional, false).optional();
4578
+ }
4579
+ }
4580
+ }
4581
+ }
4582
+ candidates.push(z3.strictObject(fieldSchemas));
4583
+ }
4584
+ const recursiveSchema = z3.lazy(() => this.makeTypedJsonFilterSchema(type, optional, false)).optional();
4585
+ if (array) {
4586
+ candidates.push(z3.strictObject({
4587
+ some: recursiveSchema,
4588
+ every: recursiveSchema,
4589
+ none: recursiveSchema
4590
+ }));
4591
+ } else {
4592
+ candidates.push(z3.strictObject({
4593
+ is: recursiveSchema,
4594
+ isNot: recursiveSchema
4595
+ }));
4596
+ }
4597
+ candidates.push(this.makeJsonFilterSchema(optional));
4598
+ if (optional) {
4599
+ candidates.push(z3.null());
4600
+ }
4601
+ return z3.union(candidates);
4602
+ }
4603
+ isTypeDefType(type) {
4604
+ return this.schema.typeDefs && type in this.schema.typeDefs;
4605
+ }
4267
4606
  makeEnumFilterSchema(enumDef, optional, withAggregations) {
4268
4607
  const baseSchema = z3.enum(Object.keys(enumDef.values));
4269
4608
  const components = this.makeCommonPrimitiveFilterComponents(baseSchema, optional, () => z3.lazy(() => this.makeEnumFilterSchema(enumDef, optional, withAggregations)), [
@@ -4283,25 +4622,64 @@ var InputValidator = class {
4283
4622
  }
4284
4623
  makeArrayFilterSchema(type) {
4285
4624
  return z3.strictObject({
4286
- equals: this.makePrimitiveSchema(type).array().optional(),
4287
- has: this.makePrimitiveSchema(type).optional(),
4288
- hasEvery: this.makePrimitiveSchema(type).array().optional(),
4289
- hasSome: this.makePrimitiveSchema(type).array().optional(),
4625
+ equals: this.makeScalarSchema(type).array().optional(),
4626
+ has: this.makeScalarSchema(type).optional(),
4627
+ hasEvery: this.makeScalarSchema(type).array().optional(),
4628
+ hasSome: this.makeScalarSchema(type).array().optional(),
4290
4629
  isEmpty: z3.boolean().optional()
4291
4630
  });
4292
4631
  }
4293
4632
  makePrimitiveFilterSchema(type, optional, withAggregations) {
4294
- if (this.schema.typeDefs && type in this.schema.typeDefs) {
4295
- return this.makeTypeDefFilterSchema(type, optional);
4296
- }
4297
- return match13(type).with("String", () => this.makeStringFilterSchema(optional, withAggregations)).with(P3.union("Int", "Float", "Decimal", "BigInt"), (type2) => this.makeNumberFilterSchema(this.makePrimitiveSchema(type2), optional, withAggregations)).with("Boolean", () => this.makeBooleanFilterSchema(optional, withAggregations)).with("DateTime", () => this.makeDateTimeFilterSchema(optional, withAggregations)).with("Bytes", () => this.makeBytesFilterSchema(optional, withAggregations)).with("Json", () => z3.any()).with("Unsupported", () => z3.never()).exhaustive();
4633
+ return match13(type).with("String", () => this.makeStringFilterSchema(optional, withAggregations)).with(P3.union("Int", "Float", "Decimal", "BigInt"), (type2) => this.makeNumberFilterSchema(this.makeScalarSchema(type2), optional, withAggregations)).with("Boolean", () => this.makeBooleanFilterSchema(optional, withAggregations)).with("DateTime", () => this.makeDateTimeFilterSchema(optional, withAggregations)).with("Bytes", () => this.makeBytesFilterSchema(optional, withAggregations)).with("Json", () => this.makeJsonFilterSchema(optional)).with("Unsupported", () => z3.never()).exhaustive();
4298
4634
  }
4299
- makeTypeDefFilterSchema(_type, _optional) {
4300
- return z3.never();
4635
+ makeJsonValueSchema(nullable, forFilter) {
4636
+ const options = [
4637
+ z3.string(),
4638
+ z3.number(),
4639
+ z3.boolean(),
4640
+ z3.instanceof(JsonNullClass)
4641
+ ];
4642
+ if (forFilter) {
4643
+ options.push(z3.instanceof(DbNullClass));
4644
+ } else {
4645
+ if (nullable) {
4646
+ options.push(z3.instanceof(DbNullClass));
4647
+ }
4648
+ }
4649
+ if (forFilter) {
4650
+ options.push(z3.instanceof(AnyNullClass));
4651
+ }
4652
+ const schema = z3.union([
4653
+ ...options,
4654
+ z3.lazy(() => z3.union([
4655
+ this.makeJsonValueSchema(false, false),
4656
+ z3.null()
4657
+ ]).array()),
4658
+ z3.record(z3.string(), z3.lazy(() => z3.union([
4659
+ this.makeJsonValueSchema(false, false),
4660
+ z3.null()
4661
+ ])))
4662
+ ]);
4663
+ return this.nullableIf(schema, nullable);
4664
+ }
4665
+ makeJsonFilterSchema(optional) {
4666
+ const valueSchema = this.makeJsonValueSchema(optional, true);
4667
+ return z3.strictObject({
4668
+ path: z3.string().optional(),
4669
+ equals: valueSchema.optional(),
4670
+ not: valueSchema.optional(),
4671
+ string_contains: z3.string().optional(),
4672
+ string_starts_with: z3.string().optional(),
4673
+ string_ends_with: z3.string().optional(),
4674
+ mode: this.makeStringModeSchema().optional(),
4675
+ array_contains: valueSchema.optional(),
4676
+ array_starts_with: valueSchema.optional(),
4677
+ array_ends_with: valueSchema.optional()
4678
+ });
4301
4679
  }
4302
4680
  makeDateTimeFilterSchema(optional, withAggregations) {
4303
4681
  return this.makeCommonPrimitiveFilterSchema(z3.union([
4304
- z3.string().datetime(),
4682
+ z3.iso.datetime(),
4305
4683
  z3.date()
4306
4684
  ]), optional, () => z3.lazy(() => this.makeDateTimeFilterSchema(optional, withAggregations)), withAggregations ? [
4307
4685
  "_count",
@@ -4646,7 +5024,7 @@ var InputValidator = class {
4646
5024
  uncheckedVariantFields[field] = fieldSchema;
4647
5025
  }
4648
5026
  } else {
4649
- let fieldSchema = this.makePrimitiveSchema(fieldDef.type, fieldDef.attributes);
5027
+ let fieldSchema = this.makeScalarSchema(fieldDef.type, fieldDef.attributes);
4650
5028
  if (fieldDef.array) {
4651
5029
  fieldSchema = addListValidation(fieldSchema.array(), fieldDef.attributes);
4652
5030
  fieldSchema = z3.union([
@@ -4660,7 +5038,14 @@ var InputValidator = class {
4660
5038
  fieldSchema = fieldSchema.optional();
4661
5039
  }
4662
5040
  if (fieldDef.optional) {
4663
- fieldSchema = fieldSchema.nullable();
5041
+ if (fieldDef.type === "Json") {
5042
+ fieldSchema = z3.union([
5043
+ fieldSchema,
5044
+ z3.instanceof(DbNullClass)
5045
+ ]);
5046
+ } else {
5047
+ fieldSchema = fieldSchema.nullable();
5048
+ }
4664
5049
  }
4665
5050
  uncheckedVariantFields[field] = fieldSchema;
4666
5051
  if (!fieldDef.foreignKeyFor) {
@@ -4709,11 +5094,11 @@ var InputValidator = class {
4709
5094
  fields["delete"] = this.makeDeleteRelationDataSchema(fieldType, array, true).optional();
4710
5095
  }
4711
5096
  fields["update"] = array ? this.orArray(z3.strictObject({
4712
- where: this.makeWhereSchema(fieldType, true).optional(),
5097
+ where: this.makeWhereSchema(fieldType, true),
4713
5098
  data: this.makeUpdateDataSchema(fieldType, withoutFields)
4714
5099
  }), true).optional() : z3.union([
4715
5100
  z3.strictObject({
4716
- where: this.makeWhereSchema(fieldType, true).optional(),
5101
+ where: this.makeWhereSchema(fieldType, false).optional(),
4717
5102
  data: this.makeUpdateDataSchema(fieldType, withoutFields)
4718
5103
  }),
4719
5104
  this.makeUpdateDataSchema(fieldType, withoutFields)
@@ -4849,7 +5234,7 @@ var InputValidator = class {
4849
5234
  uncheckedVariantFields[field] = fieldSchema;
4850
5235
  }
4851
5236
  } else {
4852
- let fieldSchema = this.makePrimitiveSchema(fieldDef.type, fieldDef.attributes);
5237
+ let fieldSchema = this.makeScalarSchema(fieldDef.type, fieldDef.attributes);
4853
5238
  if (this.isNumericField(fieldDef)) {
4854
5239
  fieldSchema = z3.union([
4855
5240
  fieldSchema,
@@ -4876,7 +5261,14 @@ var InputValidator = class {
4876
5261
  ]);
4877
5262
  }
4878
5263
  if (fieldDef.optional) {
4879
- fieldSchema = fieldSchema.nullable();
5264
+ if (fieldDef.type === "Json") {
5265
+ fieldSchema = z3.union([
5266
+ fieldSchema,
5267
+ z3.instanceof(DbNullClass)
5268
+ ]);
5269
+ } else {
5270
+ fieldSchema = fieldSchema.nullable();
5271
+ }
4880
5272
  }
4881
5273
  fieldSchema = fieldSchema.optional();
4882
5274
  uncheckedVariantFields[field] = fieldSchema;
@@ -6160,18 +6552,19 @@ var textMatch = /* @__PURE__ */ __name((eb, args, { dialect }, method) => {
6160
6552
  } else {
6161
6553
  op = "like";
6162
6554
  }
6555
+ const escapedSearch = sql5`REPLACE(REPLACE(REPLACE(CAST(${searchExpr} as text), '\\', '\\\\'), '%', '\\%'), '_', '\\_')`;
6163
6556
  searchExpr = match15(method).with("contains", () => eb.fn("CONCAT", [
6164
6557
  sql5.lit("%"),
6165
- sql5`CAST(${searchExpr} as text)`,
6558
+ escapedSearch,
6166
6559
  sql5.lit("%")
6167
6560
  ])).with("startsWith", () => eb.fn("CONCAT", [
6168
- sql5`CAST(${searchExpr} as text)`,
6561
+ escapedSearch,
6169
6562
  sql5.lit("%")
6170
6563
  ])).with("endsWith", () => eb.fn("CONCAT", [
6171
6564
  sql5.lit("%"),
6172
- sql5`CAST(${searchExpr} as text)`
6565
+ escapedSearch
6173
6566
  ])).exhaustive();
6174
- return eb(fieldExpr, op, searchExpr);
6567
+ return sql5`${fieldExpr} ${sql5.raw(op)} ${searchExpr} escape '\\'`;
6175
6568
  }, "textMatch");
6176
6569
  var has = /* @__PURE__ */ __name((eb, args) => {
6177
6570
  const [field, search2] = args;
@@ -7401,9 +7794,15 @@ var MatchingExpressionVisitor = class extends ExpressionVisitor {
7401
7794
  }
7402
7795
  };
7403
7796
  export {
7797
+ AnyNull,
7798
+ AnyNullClass,
7404
7799
  BaseCrudDialect,
7405
7800
  CRUD,
7406
7801
  CRUD_EXT,
7802
+ DbNull,
7803
+ DbNullClass,
7804
+ JsonNull,
7805
+ JsonNullClass,
7407
7806
  kysely_utils_exports as KyselyUtils,
7408
7807
  ORMError,
7409
7808
  ORMErrorReason,