@zenstackhq/runtime 3.0.0-beta.4 → 3.0.0-beta.5

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.
@@ -2,46 +2,50 @@ var __defProp = Object.defineProperty;
2
2
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
3
 
4
4
  // src/plugins/policy/errors.ts
5
+ var RejectedByPolicyReason = /* @__PURE__ */ function(RejectedByPolicyReason2) {
6
+ RejectedByPolicyReason2["NO_ACCESS"] = "no-access";
7
+ RejectedByPolicyReason2["CANNOT_READ_BACK"] = "cannot-read-back";
8
+ RejectedByPolicyReason2["OTHER"] = "other";
9
+ return RejectedByPolicyReason2;
10
+ }({});
5
11
  var RejectedByPolicyError = class extends Error {
6
12
  static {
7
13
  __name(this, "RejectedByPolicyError");
8
14
  }
9
15
  model;
10
16
  reason;
11
- constructor(model, reason) {
12
- super(reason ?? `Operation rejected by policy${model ? ": " + model : ""}`), this.model = model, this.reason = reason;
17
+ constructor(model, reason = "no-access", message) {
18
+ super(message ?? `Operation rejected by policy${model ? ": " + model : ""}`), this.model = model, this.reason = reason;
13
19
  }
14
20
  };
15
21
 
16
- // src/plugins/policy/policy-handler.ts
17
- import { invariant as invariant6 } from "@zenstackhq/common-helpers";
18
- import { AliasNode as AliasNode3, BinaryOperationNode as BinaryOperationNode3, ColumnNode as ColumnNode2, DeleteQueryNode, FromNode as FromNode2, FunctionNode as FunctionNode3, IdentifierNode as IdentifierNode2, InsertQueryNode, OperationNodeTransformer, OperatorNode as OperatorNode3, ParensNode as ParensNode2, PrimitiveValueListNode, RawNode, ReturningNode, SelectionNode as SelectionNode2, SelectQueryNode as SelectQueryNode2, TableNode as TableNode3, UpdateQueryNode, ValueListNode as ValueListNode2, ValueNode as ValueNode3, ValuesNode, WhereNode as WhereNode2 } from "kysely";
19
- import { match as match8 } from "ts-pattern";
20
-
21
- // src/client/crud/dialects/index.ts
22
- import { match as match5 } from "ts-pattern";
22
+ // src/plugins/policy/functions.ts
23
+ import { invariant as invariant8 } from "@zenstackhq/common-helpers";
24
+ import { ExpressionWrapper as ExpressionWrapper2, ValueNode as ValueNode4 } from "kysely";
23
25
 
24
- // src/client/crud/dialects/postgresql.ts
25
- import { invariant as invariant2 } from "@zenstackhq/common-helpers";
26
- import { sql as sql2 } from "kysely";
27
- import { match as match3 } from "ts-pattern";
28
-
29
- // src/client/constants.ts
30
- var DELEGATE_JOINED_FIELD_PREFIX = "$delegate$";
31
- var LOGICAL_COMBINATORS = [
32
- "AND",
33
- "OR",
34
- "NOT"
35
- ];
36
- var AGGREGATE_OPERATORS = [
37
- "_count",
38
- "_sum",
39
- "_avg",
40
- "_min",
41
- "_max"
26
+ // src/client/contract.ts
27
+ var CRUD = [
28
+ "create",
29
+ "read",
30
+ "update",
31
+ "delete"
42
32
  ];
43
33
 
34
+ // src/client/kysely-utils.ts
35
+ import { AliasNode, ColumnNode, ReferenceNode, TableNode } from "kysely";
36
+ function extractFieldName(node) {
37
+ if (ReferenceNode.is(node) && ColumnNode.is(node.column)) {
38
+ return node.column.column.name;
39
+ } else if (ColumnNode.is(node)) {
40
+ return node.column.name;
41
+ } else {
42
+ return void 0;
43
+ }
44
+ }
45
+ __name(extractFieldName, "extractFieldName");
46
+
44
47
  // src/client/query-utils.ts
48
+ import { invariant } from "@zenstackhq/common-helpers";
45
49
  import { match } from "ts-pattern";
46
50
 
47
51
  // src/schema/expression.ts
@@ -188,11 +192,15 @@ function requireField(schema, modelOrType, field) {
188
192
  throw new QueryError(`Model or type "${modelOrType}" not found in schema`);
189
193
  }
190
194
  __name(requireField, "requireField");
191
- function getIdFields(schema, model) {
195
+ function requireIdFields(schema, model) {
192
196
  const modelDef = requireModel(schema, model);
193
- return modelDef?.idFields;
197
+ const result = modelDef?.idFields;
198
+ if (!result) {
199
+ throw new InternalError(`Model "${model}" does not have ID field(s)`);
200
+ }
201
+ return result;
194
202
  }
195
- __name(getIdFields, "getIdFields");
203
+ __name(requireIdFields, "requireIdFields");
196
204
  function getRelationForeignKeyFieldPairs(schema, model, relationField) {
197
205
  const fieldDef = requireField(schema, model, relationField);
198
206
  if (!fieldDef?.relation) {
@@ -285,7 +293,7 @@ function buildFieldRef(schema, model, field, options, eb, modelAlias, inlineComp
285
293
  throw new QueryError(`Computed field "${field}" implementation not provided for model "${model}"`);
286
294
  }
287
295
  return computer(eb, {
288
- currentModel: modelAlias
296
+ modelAlias
289
297
  });
290
298
  }
291
299
  }
@@ -312,7 +320,7 @@ function buildJoinPairs(schema, model, modelAlias, relationField, relationModelA
312
320
  }
313
321
  __name(buildJoinPairs, "buildJoinPairs");
314
322
  function makeDefaultOrderBy(schema, model) {
315
- const idFields = getIdFields(schema, model);
323
+ const idFields = requireIdFields(schema, model);
316
324
  return idFields.map((f) => ({
317
325
  [f]: "asc"
318
326
  }));
@@ -351,11 +359,17 @@ function getManyToManyRelation(schema, model, field) {
351
359
  "A"
352
360
  ];
353
361
  }
362
+ const modelIdFields = requireIdFields(schema, model);
363
+ invariant(modelIdFields.length === 1, "Only single-field ID is supported for many-to-many relation");
364
+ const otherIdFields = requireIdFields(schema, fieldDef.type);
365
+ invariant(otherIdFields.length === 1, "Only single-field ID is supported for many-to-many relation");
354
366
  return {
355
367
  parentFkName: orderedFK[0],
368
+ parentPKName: modelIdFields[0],
356
369
  otherModel: fieldDef.type,
357
370
  otherField: fieldDef.relation.opposite,
358
371
  otherFkName: orderedFK[1],
372
+ otherPKName: otherIdFields[0],
359
373
  joinTable: fieldDef.relation.name ? `_${fieldDef.relation.name}` : `_${sortedModelNames[0]}To${sortedModelNames[1]}`
360
374
  };
361
375
  } else {
@@ -411,8 +425,37 @@ function aggregate(eb, expr2, op) {
411
425
  }
412
426
  __name(aggregate, "aggregate");
413
427
 
428
+ // src/plugins/policy/policy-handler.ts
429
+ import { invariant as invariant7 } from "@zenstackhq/common-helpers";
430
+ import { AliasNode as AliasNode4, BinaryOperationNode as BinaryOperationNode3, ColumnNode as ColumnNode3, DeleteQueryNode, expressionBuilder as expressionBuilder3, ExpressionWrapper, FromNode as FromNode2, FunctionNode as FunctionNode3, IdentifierNode as IdentifierNode2, InsertQueryNode, OperationNodeTransformer, OperatorNode as OperatorNode3, ParensNode as ParensNode2, PrimitiveValueListNode, RawNode, ReturningNode, SelectionNode as SelectionNode2, SelectQueryNode as SelectQueryNode2, sql as sql4, TableNode as TableNode4, UpdateQueryNode, ValueListNode as ValueListNode2, ValueNode as ValueNode3, ValuesNode, WhereNode as WhereNode2 } from "kysely";
431
+ import { match as match8 } from "ts-pattern";
432
+
433
+ // src/client/crud/dialects/index.ts
434
+ import { match as match5 } from "ts-pattern";
435
+
436
+ // src/client/crud/dialects/postgresql.ts
437
+ import { invariant as invariant3 } from "@zenstackhq/common-helpers";
438
+ import Decimal from "decimal.js";
439
+ import { sql as sql2 } from "kysely";
440
+ import { match as match3 } from "ts-pattern";
441
+
442
+ // src/client/constants.ts
443
+ var DELEGATE_JOINED_FIELD_PREFIX = "$delegate$";
444
+ var LOGICAL_COMBINATORS = [
445
+ "AND",
446
+ "OR",
447
+ "NOT"
448
+ ];
449
+ var AGGREGATE_OPERATORS = [
450
+ "_count",
451
+ "_sum",
452
+ "_avg",
453
+ "_min",
454
+ "_max"
455
+ ];
456
+
414
457
  // src/client/crud/dialects/base-dialect.ts
415
- import { invariant, isPlainObject } from "@zenstackhq/common-helpers";
458
+ import { invariant as invariant2, isPlainObject } from "@zenstackhq/common-helpers";
416
459
  import { expressionBuilder, sql } from "kysely";
417
460
  import { match as match2, P } from "ts-pattern";
418
461
 
@@ -444,6 +487,9 @@ var BaseCrudDialect = class {
444
487
  transformPrimitive(value, _type, _forArrayField) {
445
488
  return value;
446
489
  }
490
+ transformOutput(value, _type) {
491
+ return value;
492
+ }
447
493
  // #region common query builders
448
494
  buildSelectModel(eb, model, modelAlias) {
449
495
  const modelDef = requireModel(this.schema, model);
@@ -611,9 +657,11 @@ var BaseCrudDialect = class {
611
657
  const buildPkFkWhereRefs = /* @__PURE__ */ __name((eb2) => {
612
658
  const m2m = getManyToManyRelation(this.schema, model, field);
613
659
  if (m2m) {
614
- const modelIdField = getIdFields(this.schema, model)[0];
615
- const relationIdField = getIdFields(this.schema, relationModel)[0];
616
- return eb2(sql.ref(`${relationFilterSelectAlias}.${relationIdField}`), "in", eb2.selectFrom(m2m.joinTable).select(`${m2m.joinTable}.${m2m.otherFkName}`).whereRef(sql.ref(`${m2m.joinTable}.${m2m.parentFkName}`), "=", sql.ref(`${modelAlias}.${modelIdField}`)));
660
+ const modelIdFields = requireIdFields(this.schema, model);
661
+ invariant2(modelIdFields.length === 1, "many-to-many relation must have exactly one id field");
662
+ const relationIdFields = requireIdFields(this.schema, relationModel);
663
+ invariant2(relationIdFields.length === 1, "many-to-many relation must have exactly one id field");
664
+ return eb2(sql.ref(`${relationFilterSelectAlias}.${relationIdFields[0]}`), "in", eb2.selectFrom(m2m.joinTable).select(`${m2m.joinTable}.${m2m.otherFkName}`).whereRef(sql.ref(`${m2m.joinTable}.${m2m.parentFkName}`), "=", sql.ref(`${modelAlias}.${modelIdFields[0]}`)));
617
665
  } else {
618
666
  const relationKeyPairs = getRelationForeignKeyFieldPairs(this.schema, model, field);
619
667
  let result2 = this.true(eb2);
@@ -723,14 +771,14 @@ var BaseCrudDialect = class {
723
771
  }
724
772
  const rhs = Array.isArray(value) ? value.map(getRhs) : getRhs(value);
725
773
  const condition = match2(op).with("equals", () => rhs === null ? eb(lhs, "is", null) : eb(lhs, "=", rhs)).with("in", () => {
726
- invariant(Array.isArray(rhs), "right hand side must be an array");
774
+ invariant2(Array.isArray(rhs), "right hand side must be an array");
727
775
  if (rhs.length === 0) {
728
776
  return this.false(eb);
729
777
  } else {
730
778
  return eb(lhs, "in", rhs);
731
779
  }
732
780
  }).with("notIn", () => {
733
- invariant(Array.isArray(rhs), "right hand side must be an array");
781
+ invariant2(Array.isArray(rhs), "right hand side must be an array");
734
782
  if (rhs.length === 0) {
735
783
  return this.true(eb);
736
784
  } else {
@@ -848,18 +896,18 @@ var BaseCrudDialect = class {
848
896
  "_min",
849
897
  "_max"
850
898
  ].includes(field)) {
851
- invariant(value && typeof value === "object", `invalid orderBy value for field "${field}"`);
899
+ invariant2(value && typeof value === "object", `invalid orderBy value for field "${field}"`);
852
900
  for (const [k, v] of Object.entries(value)) {
853
- invariant(v === "asc" || v === "desc", `invalid orderBy value for field "${field}"`);
901
+ invariant2(v === "asc" || v === "desc", `invalid orderBy value for field "${field}"`);
854
902
  result = result.orderBy((eb) => aggregate(eb, this.fieldRef(model, k, eb, modelAlias), field), sql.raw(this.negateSort(v, negated)));
855
903
  }
856
904
  continue;
857
905
  }
858
906
  switch (field) {
859
907
  case "_count": {
860
- invariant(value && typeof value === "object", 'invalid orderBy value for field "_count"');
908
+ invariant2(value && typeof value === "object", 'invalid orderBy value for field "_count"');
861
909
  for (const [k, v] of Object.entries(value)) {
862
- invariant(v === "asc" || v === "desc", `invalid orderBy value for field "${field}"`);
910
+ invariant2(v === "asc" || v === "desc", `invalid orderBy value for field "${field}"`);
863
911
  result = result.orderBy((eb) => eb.fn.count(this.fieldRef(model, k, eb, modelAlias)), sql.raw(this.negateSort(v, negated)));
864
912
  }
865
913
  continue;
@@ -882,7 +930,7 @@ var BaseCrudDialect = class {
882
930
  throw new QueryError(`invalid orderBy value for field "${field}"`);
883
931
  }
884
932
  if ("_count" in value) {
885
- invariant(value._count === "asc" || value._count === "desc", 'invalid orderBy value for field "_count"');
933
+ invariant2(value._count === "asc" || value._count === "desc", 'invalid orderBy value for field "_count"');
886
934
  const sort = this.negateSort(value._count, negated);
887
935
  result = result.orderBy((eb) => {
888
936
  const subQueryAlias = `${modelAlias}$orderBy$${field}$count`;
@@ -954,7 +1002,7 @@ var BaseCrudDialect = class {
954
1002
  }
955
1003
  }
956
1004
  buildDelegateJoin(thisModel, thisModelAlias, otherModelAlias, query) {
957
- const idFields = getIdFields(this.schema, thisModel);
1005
+ const idFields = requireIdFields(this.schema, thisModel);
958
1006
  query = query.leftJoin(otherModelAlias, (qb) => {
959
1007
  for (const idField of idFields) {
960
1008
  qb = qb.onRef(`${thisModelAlias}.${idField}`, "=", `${otherModelAlias}.${idField}`);
@@ -976,10 +1024,16 @@ var BaseCrudDialect = class {
976
1024
  for (const [field, value] of Object.entries(selections.select)) {
977
1025
  const fieldDef = requireField(this.schema, model, field);
978
1026
  const fieldModel = fieldDef.type;
979
- const joinPairs = buildJoinPairs(this.schema, model, parentAlias, field, fieldModel);
980
- let fieldCountQuery = eb.selectFrom(fieldModel).select(eb.fn.countAll().as(`_count$${field}`));
981
- for (const [left, right] of joinPairs) {
982
- fieldCountQuery = fieldCountQuery.whereRef(left, "=", right);
1027
+ let fieldCountQuery;
1028
+ const m2m = getManyToManyRelation(this.schema, model, field);
1029
+ if (m2m) {
1030
+ fieldCountQuery = eb.selectFrom(fieldModel).innerJoin(m2m.joinTable, (join) => join.onRef(`${m2m.joinTable}.${m2m.otherFkName}`, "=", `${fieldModel}.${m2m.otherPKName}`).onRef(`${m2m.joinTable}.${m2m.parentFkName}`, "=", `${parentAlias}.${m2m.parentPKName}`)).select(eb.fn.countAll().as(`_count$${field}`));
1031
+ } else {
1032
+ fieldCountQuery = eb.selectFrom(fieldModel).select(eb.fn.countAll().as(`_count$${field}`));
1033
+ const joinPairs = buildJoinPairs(this.schema, model, parentAlias, field, fieldModel);
1034
+ for (const [left, right] of joinPairs) {
1035
+ fieldCountQuery = fieldCountQuery.whereRef(left, "=", right);
1036
+ }
983
1037
  }
984
1038
  if (value && typeof value === "object" && "where" in value && value.where && typeof value.where === "object") {
985
1039
  const filter = this.buildFilter(eb, fieldModel, fieldModel, value.where);
@@ -1059,6 +1113,9 @@ var PostgresCrudDialect = class extends BaseCrudDialect {
1059
1113
  static {
1060
1114
  __name(this, "PostgresCrudDialect");
1061
1115
  }
1116
+ constructor(schema, options) {
1117
+ super(schema, options);
1118
+ }
1062
1119
  get provider() {
1063
1120
  return "postgresql";
1064
1121
  }
@@ -1073,8 +1130,40 @@ var PostgresCrudDialect = class extends BaseCrudDialect {
1073
1130
  return value.map((v) => this.transformPrimitive(v, type, false));
1074
1131
  }
1075
1132
  } else {
1076
- return match3(type).with("DateTime", () => value instanceof Date ? value : typeof value === "string" ? new Date(value) : value).with("Decimal", () => value !== null ? value.toString() : value).otherwise(() => value);
1133
+ return match3(type).with("DateTime", () => value instanceof Date ? value.toISOString() : typeof value === "string" ? new Date(value).toISOString() : value).with("Decimal", () => value !== null ? value.toString() : value).otherwise(() => value);
1134
+ }
1135
+ }
1136
+ transformOutput(value, type) {
1137
+ if (value === null || value === void 0) {
1138
+ return value;
1077
1139
  }
1140
+ return match3(type).with("DateTime", () => this.transformOutputDate(value)).with("Bytes", () => this.transformOutputBytes(value)).with("BigInt", () => this.transformOutputBigInt(value)).with("Decimal", () => this.transformDecimal(value)).otherwise(() => super.transformOutput(value, type));
1141
+ }
1142
+ transformOutputBigInt(value) {
1143
+ if (typeof value === "bigint") {
1144
+ return value;
1145
+ }
1146
+ invariant3(typeof value === "string" || typeof value === "number", `Expected string or number, got ${typeof value}`);
1147
+ return BigInt(value);
1148
+ }
1149
+ transformDecimal(value) {
1150
+ if (value instanceof Decimal) {
1151
+ return value;
1152
+ }
1153
+ invariant3(typeof value === "string" || typeof value === "number" || value instanceof Decimal, `Expected string, number or Decimal, got ${typeof value}`);
1154
+ return new Decimal(value);
1155
+ }
1156
+ transformOutputDate(value) {
1157
+ if (typeof value === "string") {
1158
+ return new Date(value);
1159
+ } else if (value instanceof Date && this.options.fixPostgresTimezone !== false) {
1160
+ return new Date(value.getTime() - value.getTimezoneOffset() * 60 * 1e3);
1161
+ } else {
1162
+ return value;
1163
+ }
1164
+ }
1165
+ transformOutputBytes(value) {
1166
+ return Buffer.isBuffer(value) ? Uint8Array.from(value) : value;
1078
1167
  }
1079
1168
  buildRelationSelection(query, model, relationField, parentAlias, payload) {
1080
1169
  const relationResultName = `${parentAlias}$${relationField}`;
@@ -1106,10 +1195,10 @@ var PostgresCrudDialect = class extends BaseCrudDialect {
1106
1195
  buildRelationJoinFilter(query, model, relationField, relationModel, relationModelAlias, parentAlias) {
1107
1196
  const m2m = getManyToManyRelation(this.schema, model, relationField);
1108
1197
  if (m2m) {
1109
- const parentIds = getIdFields(this.schema, model);
1110
- const relationIds = getIdFields(this.schema, relationModel);
1111
- invariant2(parentIds.length === 1, "many-to-many relation must have exactly one id field");
1112
- invariant2(relationIds.length === 1, "many-to-many relation must have exactly one id field");
1198
+ const parentIds = requireIdFields(this.schema, model);
1199
+ const relationIds = requireIdFields(this.schema, relationModel);
1200
+ invariant3(parentIds.length === 1, "many-to-many relation must have exactly one id field");
1201
+ invariant3(relationIds.length === 1, "many-to-many relation must have exactly one id field");
1113
1202
  query = query.where((eb) => eb(eb.ref(`${relationModelAlias}.${relationIds[0]}`), "in", eb.selectFrom(m2m.joinTable).select(`${m2m.joinTable}.${m2m.otherFkName}`).whereRef(`${parentAlias}.${parentIds[0]}`, "=", `${m2m.joinTable}.${m2m.parentFkName}`)));
1114
1203
  } else {
1115
1204
  const joinPairs = buildJoinPairs(this.schema, model, parentAlias, relationField, relationModelAlias);
@@ -1221,10 +1310,32 @@ var PostgresCrudDialect = class extends BaseCrudDialect {
1221
1310
  get supportInsertWithDefault() {
1222
1311
  return true;
1223
1312
  }
1313
+ getFieldSqlType(fieldDef) {
1314
+ if (fieldDef.relation) {
1315
+ throw new QueryError("Cannot get SQL type of a relation field");
1316
+ }
1317
+ let result;
1318
+ if (this.schema.enums?.[fieldDef.type]) {
1319
+ result = "text";
1320
+ } else {
1321
+ result = match3(fieldDef.type).with("String", () => "text").with("Boolean", () => "boolean").with("Int", () => "integer").with("BigInt", () => "bigint").with("Float", () => "double precision").with("Decimal", () => "decimal").with("DateTime", () => "timestamp").with("Bytes", () => "bytea").with("Json", () => "jsonb").otherwise(() => "text");
1322
+ }
1323
+ if (fieldDef.array) {
1324
+ result += "[]";
1325
+ }
1326
+ return result;
1327
+ }
1328
+ getStringCasingBehavior() {
1329
+ return {
1330
+ supportsILike: true,
1331
+ likeCaseSensitive: true
1332
+ };
1333
+ }
1224
1334
  };
1225
1335
 
1226
1336
  // src/client/crud/dialects/sqlite.ts
1227
- import { invariant as invariant3 } from "@zenstackhq/common-helpers";
1337
+ import { invariant as invariant4 } from "@zenstackhq/common-helpers";
1338
+ import Decimal2 from "decimal.js";
1228
1339
  import { sql as sql3 } from "kysely";
1229
1340
  import { match as match4 } from "ts-pattern";
1230
1341
  var SqliteCrudDialect = class extends BaseCrudDialect {
@@ -1244,9 +1355,57 @@ var SqliteCrudDialect = class extends BaseCrudDialect {
1244
1355
  if (this.schema.typeDefs && type in this.schema.typeDefs) {
1245
1356
  return JSON.stringify(value);
1246
1357
  } else {
1247
- return match4(type).with("Boolean", () => value ? 1 : 0).with("DateTime", () => value instanceof Date ? value.toISOString() : value).with("Decimal", () => value.toString()).with("Bytes", () => Buffer.from(value)).with("Json", () => JSON.stringify(value)).otherwise(() => value);
1358
+ 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);
1359
+ }
1360
+ }
1361
+ }
1362
+ transformOutput(value, type) {
1363
+ if (value === null || value === void 0) {
1364
+ return value;
1365
+ } else if (this.schema.typeDefs && type in this.schema.typeDefs) {
1366
+ return this.transformOutputJson(value);
1367
+ } else {
1368
+ return match4(type).with("Boolean", () => this.transformOutputBoolean(value)).with("DateTime", () => this.transformOutputDate(value)).with("Bytes", () => this.transformOutputBytes(value)).with("Decimal", () => this.transformOutputDecimal(value)).with("BigInt", () => this.transformOutputBigInt(value)).with("Json", () => this.transformOutputJson(value)).otherwise(() => super.transformOutput(value, type));
1369
+ }
1370
+ }
1371
+ transformOutputDecimal(value) {
1372
+ if (value instanceof Decimal2) {
1373
+ return value;
1374
+ }
1375
+ invariant4(typeof value === "string" || typeof value === "number" || value instanceof Decimal2, `Expected string, number or Decimal, got ${typeof value}`);
1376
+ return new Decimal2(value);
1377
+ }
1378
+ transformOutputBigInt(value) {
1379
+ if (typeof value === "bigint") {
1380
+ return value;
1381
+ }
1382
+ invariant4(typeof value === "string" || typeof value === "number", `Expected string or number, got ${typeof value}`);
1383
+ return BigInt(value);
1384
+ }
1385
+ transformOutputBoolean(value) {
1386
+ return !!value;
1387
+ }
1388
+ transformOutputDate(value) {
1389
+ if (typeof value === "number") {
1390
+ return new Date(value);
1391
+ } else if (typeof value === "string") {
1392
+ return new Date(value);
1393
+ } else {
1394
+ return value;
1395
+ }
1396
+ }
1397
+ transformOutputBytes(value) {
1398
+ return Buffer.isBuffer(value) ? Uint8Array.from(value) : value;
1399
+ }
1400
+ transformOutputJson(value) {
1401
+ if (typeof value === "string") {
1402
+ try {
1403
+ return JSON.parse(value);
1404
+ } catch (e) {
1405
+ throw new QueryError("Invalid JSON returned", e);
1248
1406
  }
1249
1407
  }
1408
+ return value;
1250
1409
  }
1251
1410
  buildRelationSelection(query, model, relationField, parentAlias, payload) {
1252
1411
  return query.select((eb) => this.buildRelationJSON(model, eb, relationField, parentAlias, payload).as(relationField));
@@ -1329,10 +1488,10 @@ var SqliteCrudDialect = class extends BaseCrudDialect {
1329
1488
  const relationModel = fieldDef.type;
1330
1489
  const m2m = getManyToManyRelation(this.schema, model, relationField);
1331
1490
  if (m2m) {
1332
- const parentIds = getIdFields(this.schema, model);
1333
- const relationIds = getIdFields(this.schema, relationModel);
1334
- invariant3(parentIds.length === 1, "many-to-many relation must have exactly one id field");
1335
- invariant3(relationIds.length === 1, "many-to-many relation must have exactly one id field");
1491
+ const parentIds = requireIdFields(this.schema, model);
1492
+ const relationIds = requireIdFields(this.schema, relationModel);
1493
+ invariant4(parentIds.length === 1, "many-to-many relation must have exactly one id field");
1494
+ invariant4(relationIds.length === 1, "many-to-many relation must have exactly one id field");
1336
1495
  selectModelQuery = selectModelQuery.where((eb) => eb(eb.ref(`${relationModelAlias}.${relationIds[0]}`), "in", eb.selectFrom(m2m.joinTable).select(`${m2m.joinTable}.${m2m.otherFkName}`).whereRef(`${parentAlias}.${parentIds[0]}`, "=", `${m2m.joinTable}.${m2m.parentFkName}`)));
1337
1496
  } else {
1338
1497
  const { keyPairs, ownedByModel } = getRelationForeignKeyFieldPairs(this.schema, model, relationField);
@@ -1384,6 +1543,24 @@ var SqliteCrudDialect = class extends BaseCrudDialect {
1384
1543
  get supportInsertWithDefault() {
1385
1544
  return false;
1386
1545
  }
1546
+ getFieldSqlType(fieldDef) {
1547
+ if (fieldDef.relation) {
1548
+ throw new QueryError("Cannot get SQL type of a relation field");
1549
+ }
1550
+ if (fieldDef.array) {
1551
+ throw new QueryError("SQLite does not support scalar list type");
1552
+ }
1553
+ if (this.schema.enums?.[fieldDef.type]) {
1554
+ return "text";
1555
+ }
1556
+ return match4(fieldDef.type).with("String", () => "text").with("Boolean", () => "integer").with("Int", () => "integer").with("BigInt", () => "integer").with("Float", () => "real").with("Decimal", () => "decimal").with("DateTime", () => "numeric").with("Bytes", () => "blob").with("Json", () => "jsonb").otherwise(() => "text");
1557
+ }
1558
+ getStringCasingBehavior() {
1559
+ return {
1560
+ supportsILike: false,
1561
+ likeCaseSensitive: false
1562
+ };
1563
+ }
1387
1564
  };
1388
1565
 
1389
1566
  // src/client/crud/dialects/index.ts
@@ -1711,12 +1888,12 @@ var ColumnCollector = class extends DefaultOperationNodeVisitor {
1711
1888
  };
1712
1889
 
1713
1890
  // src/plugins/policy/expression-transformer.ts
1714
- import { invariant as invariant5 } from "@zenstackhq/common-helpers";
1715
- import { AliasNode as AliasNode2, BinaryOperationNode as BinaryOperationNode2, ColumnNode, expressionBuilder as expressionBuilder2, FromNode, FunctionNode as FunctionNode2, IdentifierNode, OperatorNode as OperatorNode2, ReferenceNode as ReferenceNode2, SelectionNode, SelectQueryNode, TableNode as TableNode2, ValueListNode, ValueNode as ValueNode2, WhereNode } from "kysely";
1891
+ import { invariant as invariant6 } from "@zenstackhq/common-helpers";
1892
+ import { AliasNode as AliasNode3, BinaryOperationNode as BinaryOperationNode2, ColumnNode as ColumnNode2, expressionBuilder as expressionBuilder2, FromNode, FunctionNode as FunctionNode2, IdentifierNode, OperatorNode as OperatorNode2, ReferenceNode as ReferenceNode3, SelectionNode, SelectQueryNode, TableNode as TableNode3, ValueListNode, ValueNode as ValueNode2, WhereNode } from "kysely";
1716
1893
  import { match as match7 } from "ts-pattern";
1717
1894
 
1718
1895
  // src/plugins/policy/expression-evaluator.ts
1719
- import { invariant as invariant4 } from "@zenstackhq/common-helpers";
1896
+ import { invariant as invariant5 } from "@zenstackhq/common-helpers";
1720
1897
  import { match as match6 } from "ts-pattern";
1721
1898
  var ExpressionEvaluator = class {
1722
1899
  static {
@@ -1760,18 +1937,18 @@ var ExpressionEvaluator = class {
1760
1937
  const right = this.evaluate(expr2.right, context);
1761
1938
  return match6(expr2.op).with("==", () => left === right).with("!=", () => left !== right).with(">", () => left > right).with(">=", () => left >= right).with("<", () => left < right).with("<=", () => left <= right).with("&&", () => left && right).with("||", () => left || right).with("in", () => {
1762
1939
  const _right = right ?? [];
1763
- invariant4(Array.isArray(_right), 'expected array for "in" operator');
1940
+ invariant5(Array.isArray(_right), 'expected array for "in" operator');
1764
1941
  return _right.includes(left);
1765
1942
  }).exhaustive();
1766
1943
  }
1767
1944
  evaluateCollectionPredicate(expr2, context) {
1768
1945
  const op = expr2.op;
1769
- invariant4(op === "?" || op === "!" || op === "^", 'expected "?" or "!" or "^" operator');
1946
+ invariant5(op === "?" || op === "!" || op === "^", 'expected "?" or "!" or "^" operator');
1770
1947
  const left = this.evaluate(expr2.left, context);
1771
1948
  if (!left) {
1772
1949
  return false;
1773
1950
  }
1774
- invariant4(Array.isArray(left), "expected array");
1951
+ invariant5(Array.isArray(left), "expected array");
1775
1952
  return match6(op).with("?", () => left.some((item) => this.evaluate(expr2.right, {
1776
1953
  ...context,
1777
1954
  thisValue: item
@@ -1786,7 +1963,7 @@ var ExpressionEvaluator = class {
1786
1963
  };
1787
1964
 
1788
1965
  // src/plugins/policy/utils.ts
1789
- import { AliasNode, AndNode, BinaryOperationNode, FunctionNode, OperatorNode, OrNode, ParensNode, ReferenceNode, TableNode, UnaryOperationNode, ValueNode } from "kysely";
1966
+ import { AliasNode as AliasNode2, AndNode, BinaryOperationNode, FunctionNode, OperatorNode, OrNode, ParensNode, ReferenceNode as ReferenceNode2, TableNode as TableNode2, UnaryOperationNode, ValueNode } from "kysely";
1790
1967
  function trueNode(dialect) {
1791
1968
  return ValueNode.createImmediate(dialect.transformPrimitive(true, "Boolean", false));
1792
1969
  }
@@ -1872,11 +2049,11 @@ function getTableName(node) {
1872
2049
  if (!node) {
1873
2050
  return node;
1874
2051
  }
1875
- if (TableNode.is(node)) {
2052
+ if (TableNode2.is(node)) {
1876
2053
  return node.table.identifier.name;
1877
- } else if (AliasNode.is(node)) {
2054
+ } else if (AliasNode2.is(node)) {
1878
2055
  return getTableName(node.node);
1879
- } else if (ReferenceNode.is(node) && node.table) {
2056
+ } else if (ReferenceNode2.is(node) && node.table) {
1880
2057
  return getTableName(node.table);
1881
2058
  }
1882
2059
  return void 0;
@@ -1909,16 +2086,21 @@ var ExpressionTransformer = class {
1909
2086
  static {
1910
2087
  __name(this, "ExpressionTransformer");
1911
2088
  }
1912
- schema;
1913
- clientOptions;
1914
- auth;
2089
+ client;
1915
2090
  dialect;
1916
- constructor(schema, clientOptions, auth) {
1917
- this.schema = schema;
1918
- this.clientOptions = clientOptions;
1919
- this.auth = auth;
2091
+ constructor(client) {
2092
+ this.client = client;
1920
2093
  this.dialect = getCrudDialect(this.schema, this.clientOptions);
1921
2094
  }
2095
+ get schema() {
2096
+ return this.client.$schema;
2097
+ }
2098
+ get clientOptions() {
2099
+ return this.client.$options;
2100
+ }
2101
+ get auth() {
2102
+ return this.client.$auth;
2103
+ }
1922
2104
  get authType() {
1923
2105
  if (!this.schema.authType) {
1924
2106
  throw new InternalError('Schema does not have an "authType" specified');
@@ -1988,8 +2170,9 @@ var ExpressionTransformer = class {
1988
2170
  if (op === "?" || op === "!" || op === "^") {
1989
2171
  return this.transformCollectionPredicate(expr2, context);
1990
2172
  }
1991
- const left = this.transform(expr2.left, context);
1992
- const right = this.transform(expr2.right, context);
2173
+ const { normalizedLeft, normalizedRight } = this.normalizeBinaryOperationOperands(expr2, context);
2174
+ const left = this.transform(normalizedLeft, context);
2175
+ const right = this.transform(normalizedRight, context);
1993
2176
  if (op === "in") {
1994
2177
  if (this.isNullNode(left)) {
1995
2178
  return this.transformValue(false, "Boolean");
@@ -2004,29 +2187,65 @@ var ExpressionTransformer = class {
2004
2187
  }
2005
2188
  }
2006
2189
  if (this.isNullNode(right)) {
2007
- return expr2.op === "==" ? BinaryOperationNode2.create(left, OperatorNode2.create("is"), right) : BinaryOperationNode2.create(left, OperatorNode2.create("is not"), right);
2190
+ return this.transformNullCheck(left, expr2.op);
2008
2191
  } else if (this.isNullNode(left)) {
2009
- return expr2.op === "==" ? BinaryOperationNode2.create(right, OperatorNode2.create("is"), ValueNode2.createImmediate(null)) : BinaryOperationNode2.create(right, OperatorNode2.create("is not"), ValueNode2.createImmediate(null));
2192
+ return this.transformNullCheck(right, expr2.op);
2193
+ } else {
2194
+ return BinaryOperationNode2.create(left, this.transformOperator(op), right);
2010
2195
  }
2011
- return BinaryOperationNode2.create(left, this.transformOperator(op), right);
2196
+ }
2197
+ transformNullCheck(expr2, operator) {
2198
+ invariant6(operator === "==" || operator === "!=", 'operator must be "==" or "!=" for null comparison');
2199
+ if (ValueNode2.is(expr2)) {
2200
+ if (expr2.value === null) {
2201
+ return operator === "==" ? trueNode(this.dialect) : falseNode(this.dialect);
2202
+ } else {
2203
+ return operator === "==" ? falseNode(this.dialect) : trueNode(this.dialect);
2204
+ }
2205
+ } else {
2206
+ return operator === "==" ? BinaryOperationNode2.create(expr2, OperatorNode2.create("is"), ValueNode2.createImmediate(null)) : BinaryOperationNode2.create(expr2, OperatorNode2.create("is not"), ValueNode2.createImmediate(null));
2207
+ }
2208
+ }
2209
+ normalizeBinaryOperationOperands(expr2, context) {
2210
+ let normalizedLeft = expr2.left;
2211
+ if (this.isRelationField(expr2.left, context.model)) {
2212
+ invariant6(ExpressionUtils.isNull(expr2.right), "only null comparison is supported for relation field");
2213
+ const leftRelDef = this.getFieldDefFromFieldRef(expr2.left, context.model);
2214
+ invariant6(leftRelDef, "failed to get relation field definition");
2215
+ const idFields = requireIdFields(this.schema, leftRelDef.type);
2216
+ normalizedLeft = this.makeOrAppendMember(normalizedLeft, idFields[0]);
2217
+ }
2218
+ let normalizedRight = expr2.right;
2219
+ if (this.isRelationField(expr2.right, context.model)) {
2220
+ invariant6(ExpressionUtils.isNull(expr2.left), "only null comparison is supported for relation field");
2221
+ const rightRelDef = this.getFieldDefFromFieldRef(expr2.right, context.model);
2222
+ invariant6(rightRelDef, "failed to get relation field definition");
2223
+ const idFields = requireIdFields(this.schema, rightRelDef.type);
2224
+ normalizedRight = this.makeOrAppendMember(normalizedRight, idFields[0]);
2225
+ }
2226
+ return {
2227
+ normalizedLeft,
2228
+ normalizedRight
2229
+ };
2012
2230
  }
2013
2231
  transformCollectionPredicate(expr2, context) {
2014
- invariant5(expr2.op === "?" || expr2.op === "!" || expr2.op === "^", 'expected "?" or "!" or "^" operator');
2232
+ invariant6(expr2.op === "?" || expr2.op === "!" || expr2.op === "^", 'expected "?" or "!" or "^" operator');
2015
2233
  if (this.isAuthCall(expr2.left) || this.isAuthMember(expr2.left)) {
2016
2234
  const value = new ExpressionEvaluator().evaluate(expr2, {
2017
2235
  auth: this.auth
2018
2236
  });
2019
2237
  return this.transformValue(value, "Boolean");
2020
2238
  }
2021
- invariant5(ExpressionUtils.isField(expr2.left) || ExpressionUtils.isMember(expr2.left), "left operand must be field or member access");
2239
+ invariant6(ExpressionUtils.isField(expr2.left) || ExpressionUtils.isMember(expr2.left), "left operand must be field or member access");
2022
2240
  let newContextModel;
2023
- if (ExpressionUtils.isField(expr2.left)) {
2024
- const fieldDef = requireField(this.schema, context.model, expr2.left.field);
2241
+ const fieldDef = this.getFieldDefFromFieldRef(expr2.left, context.model);
2242
+ if (fieldDef) {
2243
+ invariant6(fieldDef.relation, `field is not a relation: ${JSON.stringify(expr2.left)}`);
2025
2244
  newContextModel = fieldDef.type;
2026
2245
  } else {
2027
- invariant5(ExpressionUtils.isField(expr2.left.receiver));
2028
- const fieldDef = requireField(this.schema, context.model, expr2.left.receiver.field);
2029
- newContextModel = fieldDef.type;
2246
+ invariant6(ExpressionUtils.isMember(expr2.left) && ExpressionUtils.isField(expr2.left.receiver), "left operand must be member access with field receiver");
2247
+ const fieldDef2 = requireField(this.schema, context.model, expr2.left.receiver.field);
2248
+ newContextModel = fieldDef2.type;
2030
2249
  for (const member of expr2.left.members) {
2031
2250
  const memberDef = requireField(this.schema, newContextModel, member);
2032
2251
  newContextModel = memberDef.type;
@@ -2046,7 +2265,7 @@ var ExpressionTransformer = class {
2046
2265
  const predicateResult = match7(expr2.op).with("?", () => BinaryOperationNode2.create(count, OperatorNode2.create(">"), ValueNode2.createImmediate(0))).with("!", () => BinaryOperationNode2.create(count, OperatorNode2.create("="), ValueNode2.createImmediate(0))).with("^", () => BinaryOperationNode2.create(count, OperatorNode2.create("="), ValueNode2.createImmediate(0))).exhaustive();
2047
2266
  return this.transform(expr2.left, {
2048
2267
  ...context,
2049
- memberSelect: SelectionNode.create(AliasNode2.create(predicateResult, IdentifierNode.create("$t"))),
2268
+ memberSelect: SelectionNode.create(AliasNode3.create(predicateResult, IdentifierNode.create("$t"))),
2050
2269
  memberFilter: predicateFilter
2051
2270
  });
2052
2271
  }
@@ -2071,12 +2290,10 @@ var ExpressionTransformer = class {
2071
2290
  throw new QueryError(`Unsupported use of \`auth()\` in policy of model "${context.model}", comparing with \`auth()\` is only possible when auth type is a model`);
2072
2291
  }
2073
2292
  const idFields = Object.values(authModel.fields).filter((f) => f.id).map((f) => f.name);
2074
- invariant5(idFields.length > 0, "auth type model must have at least one id field");
2293
+ invariant6(idFields.length > 0, "auth type model must have at least one id field");
2075
2294
  const conditions = idFields.map((fieldName) => ExpressionUtils.binary(ExpressionUtils.member(authExpr, [
2076
2295
  fieldName
2077
- ]), "==", ExpressionUtils.member(other, [
2078
- fieldName
2079
- ])));
2296
+ ]), "==", this.makeOrAppendMember(other, fieldName)));
2080
2297
  let result = this.buildAnd(conditions);
2081
2298
  if (expr2.op === "!=") {
2082
2299
  result = this.buildLogicalNot(result);
@@ -2084,11 +2301,29 @@ var ExpressionTransformer = class {
2084
2301
  return this.transform(result, context);
2085
2302
  }
2086
2303
  }
2304
+ makeOrAppendMember(other, fieldName) {
2305
+ if (ExpressionUtils.isMember(other)) {
2306
+ return ExpressionUtils.member(other.receiver, [
2307
+ ...other.members,
2308
+ fieldName
2309
+ ]);
2310
+ } else {
2311
+ return ExpressionUtils.member(other, [
2312
+ fieldName
2313
+ ]);
2314
+ }
2315
+ }
2087
2316
  transformValue(value, type) {
2088
- return ValueNode2.create(this.dialect.transformPrimitive(value, type, false) ?? null);
2317
+ if (value === true) {
2318
+ return trueNode(this.dialect);
2319
+ } else if (value === false) {
2320
+ return falseNode(this.dialect);
2321
+ } else {
2322
+ return ValueNode2.create(this.dialect.transformPrimitive(value, type, false) ?? null);
2323
+ }
2089
2324
  }
2090
2325
  _unary(expr2, context) {
2091
- invariant5(expr2.op === "!", 'only "!" operator is supported');
2326
+ invariant6(expr2.op === "!", 'only "!" operator is supported');
2092
2327
  return logicalNot(this.dialect, this.transform(expr2.operand, context));
2093
2328
  }
2094
2329
  transformOperator(op) {
@@ -2100,17 +2335,31 @@ var ExpressionTransformer = class {
2100
2335
  return result.toOperationNode();
2101
2336
  }
2102
2337
  transformCall(expr2, context) {
2103
- const func = this.clientOptions.functions?.[expr2.function];
2338
+ const func = this.getFunctionImpl(expr2.function);
2104
2339
  if (!func) {
2105
2340
  throw new QueryError(`Function not implemented: ${expr2.function}`);
2106
2341
  }
2107
2342
  const eb = expressionBuilder2();
2108
2343
  return func(eb, (expr2.args ?? []).map((arg) => this.transformCallArg(eb, arg, context)), {
2344
+ client: this.client,
2109
2345
  dialect: this.dialect,
2110
2346
  model: context.model,
2347
+ modelAlias: context.alias ?? context.model,
2111
2348
  operation: context.operation
2112
2349
  });
2113
2350
  }
2351
+ getFunctionImpl(functionName) {
2352
+ let func = this.clientOptions.functions?.[functionName];
2353
+ if (!func) {
2354
+ for (const plugin of this.clientOptions.plugins ?? []) {
2355
+ if (plugin.functions?.[functionName]) {
2356
+ func = plugin.functions[functionName];
2357
+ break;
2358
+ }
2359
+ }
2360
+ }
2361
+ return func;
2362
+ }
2114
2363
  transformCallArg(eb, arg, context) {
2115
2364
  if (ExpressionUtils.isLiteral(arg)) {
2116
2365
  return eb.val(arg.value);
@@ -2131,23 +2380,22 @@ var ExpressionTransformer = class {
2131
2380
  if (this.isAuthCall(expr2.receiver)) {
2132
2381
  return this.valueMemberAccess(this.auth, expr2, this.authType);
2133
2382
  }
2134
- invariant5(ExpressionUtils.isField(expr2.receiver) || ExpressionUtils.isThis(expr2.receiver), 'expect receiver to be field expression or "this"');
2383
+ invariant6(ExpressionUtils.isField(expr2.receiver) || ExpressionUtils.isThis(expr2.receiver), 'expect receiver to be field expression or "this"');
2135
2384
  let members = expr2.members;
2136
2385
  let receiver;
2137
2386
  const { memberFilter, memberSelect, ...restContext } = context;
2138
2387
  if (ExpressionUtils.isThis(expr2.receiver)) {
2139
2388
  if (expr2.members.length === 1) {
2140
- const fieldDef = requireField(this.schema, context.model, expr2.members[0]);
2141
- invariant5(!fieldDef.relation, "this.relation access should have been transformed into relation access");
2142
- return this.createColumnRef(expr2.members[0], restContext);
2389
+ return this._field(ExpressionUtils.field(expr2.members[0]), context);
2390
+ } else {
2391
+ const firstMemberFieldDef = requireField(this.schema, context.model, expr2.members[0]);
2392
+ receiver = this.transformRelationAccess(expr2.members[0], firstMemberFieldDef.type, restContext);
2393
+ members = expr2.members.slice(1);
2143
2394
  }
2144
- const firstMemberFieldDef = requireField(this.schema, context.model, expr2.members[0]);
2145
- receiver = this.transformRelationAccess(expr2.members[0], firstMemberFieldDef.type, restContext);
2146
- members = expr2.members.slice(1);
2147
2395
  } else {
2148
2396
  receiver = this.transform(expr2.receiver, restContext);
2149
2397
  }
2150
- invariant5(SelectQueryNode.is(receiver), "expected receiver to be select query");
2398
+ invariant6(SelectQueryNode.is(receiver), "expected receiver to be select query");
2151
2399
  let startType;
2152
2400
  if (ExpressionUtils.isField(expr2.receiver)) {
2153
2401
  const receiverField = requireField(this.schema, context.model, expr2.receiver.field);
@@ -2176,11 +2424,11 @@ var ExpressionTransformer = class {
2176
2424
  alias: void 0
2177
2425
  });
2178
2426
  if (currNode) {
2179
- invariant5(SelectQueryNode.is(currNode), "expected select query node");
2427
+ invariant6(SelectQueryNode.is(currNode), "expected select query node");
2180
2428
  currNode = {
2181
2429
  ...relation,
2182
2430
  selections: [
2183
- SelectionNode.create(AliasNode2.create(currNode, IdentifierNode.create(members[i + 1])))
2431
+ SelectionNode.create(AliasNode3.create(currNode, IdentifierNode.create(members[i + 1])))
2184
2432
  ]
2185
2433
  };
2186
2434
  } else {
@@ -2193,15 +2441,15 @@ var ExpressionTransformer = class {
2193
2441
  };
2194
2442
  }
2195
2443
  } else {
2196
- invariant5(i === members.length - 1, "plain field access must be the last segment");
2197
- invariant5(!currNode, "plain field access must be the last segment");
2198
- currNode = ColumnNode.create(member);
2444
+ invariant6(i === members.length - 1, "plain field access must be the last segment");
2445
+ invariant6(!currNode, "plain field access must be the last segment");
2446
+ currNode = ColumnNode2.create(member);
2199
2447
  }
2200
2448
  }
2201
2449
  return {
2202
2450
  ...receiver,
2203
2451
  selections: [
2204
- SelectionNode.create(AliasNode2.create(currNode, IdentifierNode.create("$t")))
2452
+ SelectionNode.create(AliasNode3.create(currNode, IdentifierNode.create("$t")))
2205
2453
  ]
2206
2454
  };
2207
2455
  }
@@ -2218,24 +2466,33 @@ var ExpressionTransformer = class {
2218
2466
  return this.transformValue(fieldValue, fieldDef.type);
2219
2467
  }
2220
2468
  transformRelationAccess(field, relationModel, context) {
2469
+ const m2m = getManyToManyRelation(this.schema, context.model, field);
2470
+ if (m2m) {
2471
+ return this.transformManyToManyRelationAccess(m2m, context);
2472
+ }
2221
2473
  const fromModel = context.model;
2222
2474
  const { keyPairs, ownedByModel } = getRelationForeignKeyFieldPairs(this.schema, fromModel, field);
2223
2475
  let condition;
2224
2476
  if (ownedByModel) {
2225
- condition = conjunction(this.dialect, keyPairs.map(({ fk, pk }) => BinaryOperationNode2.create(ReferenceNode2.create(ColumnNode.create(fk), TableNode2.create(context.alias ?? fromModel)), OperatorNode2.create("="), ReferenceNode2.create(ColumnNode.create(pk), TableNode2.create(relationModel)))));
2477
+ condition = conjunction(this.dialect, keyPairs.map(({ fk, pk }) => BinaryOperationNode2.create(ReferenceNode3.create(ColumnNode2.create(fk), TableNode3.create(context.alias ?? fromModel)), OperatorNode2.create("="), ReferenceNode3.create(ColumnNode2.create(pk), TableNode3.create(relationModel)))));
2226
2478
  } else {
2227
- condition = conjunction(this.dialect, keyPairs.map(({ fk, pk }) => BinaryOperationNode2.create(ReferenceNode2.create(ColumnNode.create(pk), TableNode2.create(context.alias ?? fromModel)), OperatorNode2.create("="), ReferenceNode2.create(ColumnNode.create(fk), TableNode2.create(relationModel)))));
2479
+ condition = conjunction(this.dialect, keyPairs.map(({ fk, pk }) => BinaryOperationNode2.create(ReferenceNode3.create(ColumnNode2.create(pk), TableNode3.create(context.alias ?? fromModel)), OperatorNode2.create("="), ReferenceNode3.create(ColumnNode2.create(fk), TableNode3.create(relationModel)))));
2228
2480
  }
2229
2481
  return {
2230
2482
  kind: "SelectQueryNode",
2231
2483
  from: FromNode.create([
2232
- TableNode2.create(relationModel)
2484
+ TableNode3.create(relationModel)
2233
2485
  ]),
2234
2486
  where: WhereNode.create(condition)
2235
2487
  };
2236
2488
  }
2489
+ transformManyToManyRelationAccess(m2m, context) {
2490
+ const eb = expressionBuilder2();
2491
+ const relationQuery = eb.selectFrom(m2m.otherModel).innerJoin(m2m.joinTable, (join) => join.onRef(`${m2m.otherModel}.${m2m.otherPKName}`, "=", `${m2m.joinTable}.${m2m.otherFkName}`).onRef(`${m2m.joinTable}.${m2m.parentFkName}`, "=", `${context.alias ?? context.model}.${m2m.parentPKName}`));
2492
+ return relationQuery.toOperationNode();
2493
+ }
2237
2494
  createColumnRef(column, context) {
2238
- return ReferenceNode2.create(ColumnNode.create(column), TableNode2.create(context.alias ?? context.model));
2495
+ return ReferenceNode3.create(ColumnNode2.create(column), TableNode3.create(context.alias ?? context.model));
2239
2496
  }
2240
2497
  isAuthCall(value) {
2241
2498
  return ExpressionUtils.isCall(value) && value.function === "auth";
@@ -2258,6 +2515,19 @@ var ExpressionTransformer = class {
2258
2515
  return conditions.reduce((acc, condition) => ExpressionUtils.binary(acc, "&&", condition));
2259
2516
  }
2260
2517
  }
2518
+ isRelationField(expr2, model) {
2519
+ const fieldDef = this.getFieldDefFromFieldRef(expr2, model);
2520
+ return !!fieldDef?.relation;
2521
+ }
2522
+ getFieldDefFromFieldRef(expr2, model) {
2523
+ if (ExpressionUtils.isField(expr2)) {
2524
+ return requireField(this.schema, model, expr2.field);
2525
+ } else if (ExpressionUtils.isMember(expr2) && expr2.members.length === 1 && ExpressionUtils.isThis(expr2.receiver)) {
2526
+ return requireField(this.schema, model, expr2.members[0]);
2527
+ } else {
2528
+ return void 0;
2529
+ }
2530
+ }
2261
2531
  };
2262
2532
  _ts_decorate([
2263
2533
  expr("literal"),
@@ -2344,86 +2614,249 @@ var PolicyHandler = class extends OperationNodeTransformer {
2344
2614
  }
2345
2615
  async handle(node, proceed) {
2346
2616
  if (!this.isCrudQueryNode(node)) {
2347
- throw new RejectedByPolicyError(void 0, "non-CRUD queries are not allowed");
2617
+ throw new RejectedByPolicyError(void 0, RejectedByPolicyReason.OTHER, "non-CRUD queries are not allowed");
2348
2618
  }
2349
2619
  if (!this.isMutationQueryNode(node)) {
2350
2620
  return proceed(this.transformNode(node));
2351
2621
  }
2352
- let mutationRequiresTransaction = false;
2353
- const mutationModel = this.getMutationModel(node);
2622
+ const { mutationModel } = this.getMutationModel(node);
2354
2623
  if (InsertQueryNode.is(node)) {
2355
- const constCondition = this.tryGetConstantPolicy(mutationModel, "create");
2356
- if (constCondition === false) {
2357
- throw new RejectedByPolicyError(mutationModel);
2358
- } else if (constCondition === void 0) {
2359
- mutationRequiresTransaction = true;
2624
+ const isManyToManyJoinTable = this.isManyToManyJoinTable(mutationModel);
2625
+ let needCheckPreCreate = true;
2626
+ if (!isManyToManyJoinTable) {
2627
+ const constCondition = this.tryGetConstantPolicy(mutationModel, "create");
2628
+ if (constCondition === true) {
2629
+ needCheckPreCreate = false;
2630
+ } else if (constCondition === false) {
2631
+ throw new RejectedByPolicyError(mutationModel);
2632
+ }
2633
+ }
2634
+ if (needCheckPreCreate) {
2635
+ await this.enforcePreCreatePolicy(node, mutationModel, isManyToManyJoinTable, proceed);
2360
2636
  }
2361
2637
  }
2362
- if (!mutationRequiresTransaction && !node.returning) {
2363
- return proceed(this.transformNode(node));
2364
- }
2365
- if (InsertQueryNode.is(node)) {
2366
- await this.enforcePreCreatePolicy(node, proceed);
2367
- }
2368
- const transformedNode = this.transformNode(node);
2369
- const result = await proceed(transformedNode);
2370
- if (!this.onlyReturningId(node)) {
2638
+ const result = await proceed(this.transformNode(node));
2639
+ if (!node.returning || this.onlyReturningId(node)) {
2640
+ return result;
2641
+ } else {
2371
2642
  const readBackResult = await this.processReadBack(node, result, proceed);
2372
2643
  if (readBackResult.rows.length !== result.rows.length) {
2373
- throw new RejectedByPolicyError(mutationModel, "result is not allowed to be read back");
2644
+ throw new RejectedByPolicyError(mutationModel, RejectedByPolicyReason.CANNOT_READ_BACK, "result is not allowed to be read back");
2374
2645
  }
2375
2646
  return readBackResult;
2376
- } else {
2647
+ }
2648
+ }
2649
+ // #region overrides
2650
+ transformSelectQuery(node) {
2651
+ let whereNode = this.transformNode(node.where);
2652
+ const policyFilter = this.createPolicyFilterForFrom(node.from);
2653
+ if (policyFilter) {
2654
+ whereNode = WhereNode2.create(whereNode?.where ? conjunction(this.dialect, [
2655
+ whereNode.where,
2656
+ policyFilter
2657
+ ]) : policyFilter);
2658
+ }
2659
+ const baseResult = super.transformSelectQuery({
2660
+ ...node,
2661
+ where: void 0
2662
+ });
2663
+ return {
2664
+ ...baseResult,
2665
+ where: whereNode
2666
+ };
2667
+ }
2668
+ transformJoin(node) {
2669
+ const table = this.extractTableName(node.table);
2670
+ if (!table) {
2671
+ return super.transformJoin(node);
2672
+ }
2673
+ const filter = this.buildPolicyFilter(table.model, table.alias, "read");
2674
+ const nestedSelect = {
2675
+ kind: "SelectQueryNode",
2676
+ from: FromNode2.create([
2677
+ node.table
2678
+ ]),
2679
+ selections: [
2680
+ SelectionNode2.createSelectAll()
2681
+ ],
2682
+ where: WhereNode2.create(filter)
2683
+ };
2684
+ return {
2685
+ ...node,
2686
+ table: AliasNode4.create(ParensNode2.create(nestedSelect), IdentifierNode2.create(table.alias ?? table.model))
2687
+ };
2688
+ }
2689
+ transformInsertQuery(node) {
2690
+ let onConflict = node.onConflict;
2691
+ if (onConflict?.updates) {
2692
+ const { mutationModel, alias } = this.getMutationModel(node);
2693
+ const filter = this.buildPolicyFilter(mutationModel, alias, "update");
2694
+ if (onConflict.updateWhere) {
2695
+ onConflict = {
2696
+ ...onConflict,
2697
+ updateWhere: WhereNode2.create(conjunction(this.dialect, [
2698
+ onConflict.updateWhere.where,
2699
+ filter
2700
+ ]))
2701
+ };
2702
+ } else {
2703
+ onConflict = {
2704
+ ...onConflict,
2705
+ updateWhere: WhereNode2.create(filter)
2706
+ };
2707
+ }
2708
+ }
2709
+ const processedNode = onConflict ? {
2710
+ ...node,
2711
+ onConflict
2712
+ } : node;
2713
+ const result = super.transformInsertQuery(processedNode);
2714
+ if (!node.returning) {
2715
+ return result;
2716
+ }
2717
+ if (this.onlyReturningId(node)) {
2377
2718
  return result;
2719
+ } else {
2720
+ const { mutationModel } = this.getMutationModel(node);
2721
+ const idFields = requireIdFields(this.client.$schema, mutationModel);
2722
+ return {
2723
+ ...result,
2724
+ returning: ReturningNode.create(idFields.map((field) => SelectionNode2.create(ColumnNode3.create(field))))
2725
+ };
2726
+ }
2727
+ }
2728
+ transformUpdateQuery(node) {
2729
+ const result = super.transformUpdateQuery(node);
2730
+ const { mutationModel, alias } = this.getMutationModel(node);
2731
+ let filter = this.buildPolicyFilter(mutationModel, alias, "update");
2732
+ if (node.from) {
2733
+ const joinFilter = this.createPolicyFilterForFrom(node.from);
2734
+ if (joinFilter) {
2735
+ filter = conjunction(this.dialect, [
2736
+ filter,
2737
+ joinFilter
2738
+ ]);
2739
+ }
2740
+ }
2741
+ return {
2742
+ ...result,
2743
+ where: WhereNode2.create(result.where ? conjunction(this.dialect, [
2744
+ result.where.where,
2745
+ filter
2746
+ ]) : filter)
2747
+ };
2748
+ }
2749
+ transformDeleteQuery(node) {
2750
+ const result = super.transformDeleteQuery(node);
2751
+ const { mutationModel, alias } = this.getMutationModel(node);
2752
+ let filter = this.buildPolicyFilter(mutationModel, alias, "delete");
2753
+ if (node.using) {
2754
+ const joinFilter = this.createPolicyFilterForTables(node.using.tables);
2755
+ if (joinFilter) {
2756
+ filter = conjunction(this.dialect, [
2757
+ filter,
2758
+ joinFilter
2759
+ ]);
2760
+ }
2378
2761
  }
2762
+ return {
2763
+ ...result,
2764
+ where: WhereNode2.create(result.where ? conjunction(this.dialect, [
2765
+ result.where.where,
2766
+ filter
2767
+ ]) : filter)
2768
+ };
2379
2769
  }
2770
+ // #endregion
2771
+ // #region helpers
2380
2772
  onlyReturningId(node) {
2381
2773
  if (!node.returning) {
2382
2774
  return true;
2383
2775
  }
2384
- const idFields = getIdFields(this.client.$schema, this.getMutationModel(node));
2776
+ const { mutationModel } = this.getMutationModel(node);
2777
+ const idFields = requireIdFields(this.client.$schema, mutationModel);
2385
2778
  const collector = new ColumnCollector();
2386
2779
  const selectedColumns = collector.collect(node.returning);
2387
2780
  return selectedColumns.every((c) => idFields.includes(c));
2388
2781
  }
2389
- async enforcePreCreatePolicy(node, proceed) {
2390
- const model = this.getMutationModel(node);
2782
+ async enforcePreCreatePolicy(node, mutationModel, isManyToManyJoinTable, proceed) {
2391
2783
  const fields = node.columns?.map((c) => c.column.name) ?? [];
2392
- const valueRows = node.values ? this.unwrapCreateValueRows(node.values, model, fields) : [
2784
+ const valueRows = node.values ? this.unwrapCreateValueRows(node.values, mutationModel, fields, isManyToManyJoinTable) : [
2393
2785
  []
2394
2786
  ];
2395
2787
  for (const values of valueRows) {
2396
- await this.enforcePreCreatePolicyForOne(model, fields, values.map((v) => v.node), proceed);
2788
+ if (isManyToManyJoinTable) {
2789
+ await this.enforcePreCreatePolicyForManyToManyJoinTable(mutationModel, fields, values.map((v) => v.node), proceed);
2790
+ } else {
2791
+ await this.enforcePreCreatePolicyForOne(mutationModel, fields, values.map((v) => v.node), proceed);
2792
+ }
2793
+ }
2794
+ }
2795
+ async enforcePreCreatePolicyForManyToManyJoinTable(tableName, fields, values, proceed) {
2796
+ const m2m = this.resolveManyToManyJoinTable(tableName);
2797
+ invariant7(m2m);
2798
+ invariant7(fields.includes("A") && fields.includes("B"), "many-to-many join table must have A and B fk fields");
2799
+ const aIndex = fields.indexOf("A");
2800
+ const aNode = values[aIndex];
2801
+ const bIndex = fields.indexOf("B");
2802
+ const bNode = values[bIndex];
2803
+ invariant7(ValueNode3.is(aNode) && ValueNode3.is(bNode), "A and B values must be ValueNode");
2804
+ const aValue = aNode.value;
2805
+ const bValue = bNode.value;
2806
+ invariant7(aValue !== null && aValue !== void 0, "A value cannot be null or undefined");
2807
+ invariant7(bValue !== null && bValue !== void 0, "B value cannot be null or undefined");
2808
+ const eb = expressionBuilder3();
2809
+ const filterA = this.buildPolicyFilter(m2m.firstModel, void 0, "update");
2810
+ const queryA = eb.selectFrom(m2m.firstModel).where(eb(eb.ref(`${m2m.firstModel}.${m2m.firstIdField}`), "=", aValue)).select(() => new ExpressionWrapper(filterA).as("$t"));
2811
+ const filterB = this.buildPolicyFilter(m2m.secondModel, void 0, "update");
2812
+ const queryB = eb.selectFrom(m2m.secondModel).where(eb(eb.ref(`${m2m.secondModel}.${m2m.secondIdField}`), "=", bValue)).select(() => new ExpressionWrapper(filterB).as("$t"));
2813
+ const queryNode = {
2814
+ kind: "SelectQueryNode",
2815
+ selections: [
2816
+ SelectionNode2.create(AliasNode4.create(queryA.toOperationNode(), IdentifierNode2.create("$conditionA"))),
2817
+ SelectionNode2.create(AliasNode4.create(queryB.toOperationNode(), IdentifierNode2.create("$conditionB")))
2818
+ ]
2819
+ };
2820
+ const result = await proceed(queryNode);
2821
+ if (!result.rows[0]?.$conditionA) {
2822
+ throw new RejectedByPolicyError(m2m.firstModel, RejectedByPolicyReason.CANNOT_READ_BACK, `many-to-many relation participant model "${m2m.firstModel}" not updatable`);
2823
+ }
2824
+ if (!result.rows[0]?.$conditionB) {
2825
+ throw new RejectedByPolicyError(m2m.secondModel, RejectedByPolicyReason.NO_ACCESS, `many-to-many relation participant model "${m2m.secondModel}" not updatable`);
2397
2826
  }
2398
2827
  }
2399
2828
  async enforcePreCreatePolicyForOne(model, fields, values, proceed) {
2400
- const allFields = Object.keys(requireModel(this.client.$schema, model).fields);
2829
+ const allFields = Object.entries(requireModel(this.client.$schema, model).fields).filter(([, def]) => !def.relation);
2401
2830
  const allValues = [];
2402
- for (const fieldName of allFields) {
2403
- const index = fields.indexOf(fieldName);
2831
+ for (const [name, _def] of allFields) {
2832
+ const index = fields.indexOf(name);
2404
2833
  if (index >= 0) {
2405
2834
  allValues.push(values[index]);
2406
2835
  } else {
2407
2836
  allValues.push(ValueNode3.createImmediate(null));
2408
2837
  }
2409
2838
  }
2839
+ const eb = expressionBuilder3();
2410
2840
  const constTable = {
2411
2841
  kind: "SelectQueryNode",
2412
2842
  from: FromNode2.create([
2413
- AliasNode3.create(ParensNode2.create(ValuesNode.create([
2843
+ AliasNode4.create(ParensNode2.create(ValuesNode.create([
2414
2844
  ValueListNode2.create(allValues)
2415
2845
  ])), IdentifierNode2.create("$t"))
2416
2846
  ]),
2417
- selections: allFields.map((field, index) => SelectionNode2.create(AliasNode3.create(ColumnNode2.create(`column${index + 1}`), IdentifierNode2.create(field))))
2847
+ selections: allFields.map(([name, def], index) => {
2848
+ const castedColumnRef = sql4`CAST(${eb.ref(`column${index + 1}`)} as ${sql4.raw(this.dialect.getFieldSqlType(def))})`.as(name);
2849
+ return SelectionNode2.create(castedColumnRef.toOperationNode());
2850
+ })
2418
2851
  };
2419
2852
  const filter = this.buildPolicyFilter(model, void 0, "create");
2420
2853
  const preCreateCheck = {
2421
2854
  kind: "SelectQueryNode",
2422
2855
  from: FromNode2.create([
2423
- AliasNode3.create(constTable, IdentifierNode2.create(model))
2856
+ AliasNode4.create(constTable, IdentifierNode2.create(model))
2424
2857
  ]),
2425
2858
  selections: [
2426
- SelectionNode2.create(AliasNode3.create(BinaryOperationNode3.create(FunctionNode3.create("COUNT", [
2859
+ SelectionNode2.create(AliasNode4.create(BinaryOperationNode3.create(FunctionNode3.create("COUNT", [
2427
2860
  ValueNode3.createImmediate(1)
2428
2861
  ]), OperatorNode3.create(">"), ValueNode3.createImmediate(0)), IdentifierNode2.create("$condition")))
2429
2862
  ],
@@ -2434,31 +2867,35 @@ var PolicyHandler = class extends OperationNodeTransformer {
2434
2867
  throw new RejectedByPolicyError(model);
2435
2868
  }
2436
2869
  }
2437
- unwrapCreateValueRows(node, model, fields) {
2870
+ unwrapCreateValueRows(node, model, fields, isManyToManyJoinTable) {
2438
2871
  if (ValuesNode.is(node)) {
2439
- return node.values.map((v) => this.unwrapCreateValueRow(v.values, model, fields));
2872
+ return node.values.map((v) => this.unwrapCreateValueRow(v.values, model, fields, isManyToManyJoinTable));
2440
2873
  } else if (PrimitiveValueListNode.is(node)) {
2441
2874
  return [
2442
- this.unwrapCreateValueRow(node.values, model, fields)
2875
+ this.unwrapCreateValueRow(node.values, model, fields, isManyToManyJoinTable)
2443
2876
  ];
2444
2877
  } else {
2445
2878
  throw new InternalError(`Unexpected node kind: ${node.kind} for unwrapping create values`);
2446
2879
  }
2447
2880
  }
2448
- unwrapCreateValueRow(data, model, fields) {
2449
- invariant6(data.length === fields.length, "data length must match fields length");
2881
+ unwrapCreateValueRow(data, model, fields, isImplicitManyToManyJoinTable) {
2882
+ invariant7(data.length === fields.length, "data length must match fields length");
2450
2883
  const result = [];
2451
2884
  for (let i = 0; i < data.length; i++) {
2452
2885
  const item = data[i];
2453
- const fieldDef = requireField(this.client.$schema, model, fields[i]);
2454
2886
  if (typeof item === "object" && item && "kind" in item) {
2455
- invariant6(item.kind === "ValueNode", "expecting a ValueNode");
2887
+ const fieldDef = requireField(this.client.$schema, model, fields[i]);
2888
+ invariant7(item.kind === "ValueNode", "expecting a ValueNode");
2456
2889
  result.push({
2457
2890
  node: ValueNode3.create(this.dialect.transformPrimitive(item.value, fieldDef.type, !!fieldDef.array)),
2458
2891
  raw: item.value
2459
2892
  });
2460
2893
  } else {
2461
- const value = this.dialect.transformPrimitive(item, fieldDef.type, !!fieldDef.array);
2894
+ let value = item;
2895
+ if (!isImplicitManyToManyJoinTable) {
2896
+ const fieldDef = requireField(this.client.$schema, model, fields[i]);
2897
+ value = this.dialect.transformPrimitive(item, fieldDef.type, !!fieldDef.array);
2898
+ }
2462
2899
  if (Array.isArray(value)) {
2463
2900
  result.push({
2464
2901
  node: RawNode.createWithSql(this.dialect.buildArrayLiteralSQL(value)),
@@ -2502,16 +2939,13 @@ var PolicyHandler = class extends OperationNodeTransformer {
2502
2939
  if (!this.isMutationQueryNode(node) || !node.returning) {
2503
2940
  return result;
2504
2941
  }
2505
- const table = this.getMutationModel(node);
2506
- if (!table) {
2507
- throw new InternalError(`Unable to get table name for query node: ${node}`);
2508
- }
2509
- const idConditions = this.buildIdConditions(table, result.rows);
2510
- const policyFilter = this.buildPolicyFilter(table, void 0, "read");
2942
+ const { mutationModel } = this.getMutationModel(node);
2943
+ const idConditions = this.buildIdConditions(mutationModel, result.rows);
2944
+ const policyFilter = this.buildPolicyFilter(mutationModel, void 0, "read");
2511
2945
  const select = {
2512
2946
  kind: "SelectQueryNode",
2513
2947
  from: FromNode2.create([
2514
- TableNode3.create(table)
2948
+ TableNode4.create(mutationModel)
2515
2949
  ]),
2516
2950
  where: WhereNode2.create(conjunction(this.dialect, [
2517
2951
  idConditions,
@@ -2523,15 +2957,31 @@ var PolicyHandler = class extends OperationNodeTransformer {
2523
2957
  return selectResult;
2524
2958
  }
2525
2959
  buildIdConditions(table, rows) {
2526
- const idFields = getIdFields(this.client.$schema, table);
2527
- return disjunction(this.dialect, rows.map((row) => conjunction(this.dialect, idFields.map((field) => BinaryOperationNode3.create(ColumnNode2.create(field), OperatorNode3.create("="), ValueNode3.create(row[field]))))));
2960
+ const idFields = requireIdFields(this.client.$schema, table);
2961
+ return disjunction(this.dialect, rows.map((row) => conjunction(this.dialect, idFields.map((field) => BinaryOperationNode3.create(ColumnNode3.create(field), OperatorNode3.create("="), ValueNode3.create(row[field]))))));
2528
2962
  }
2529
2963
  getMutationModel(node) {
2530
- const r = match8(node).when(InsertQueryNode.is, (node2) => getTableName(node2.into)).when(UpdateQueryNode.is, (node2) => getTableName(node2.table)).when(DeleteQueryNode.is, (node2) => {
2964
+ const r = match8(node).when(InsertQueryNode.is, (node2) => ({
2965
+ mutationModel: getTableName(node2.into),
2966
+ alias: void 0
2967
+ })).when(UpdateQueryNode.is, (node2) => {
2968
+ if (!node2.table) {
2969
+ throw new QueryError("Update query must have a table");
2970
+ }
2971
+ const r2 = this.extractTableName(node2.table);
2972
+ return r2 ? {
2973
+ mutationModel: r2.model,
2974
+ alias: r2.alias
2975
+ } : void 0;
2976
+ }).when(DeleteQueryNode.is, (node2) => {
2531
2977
  if (node2.from.froms.length !== 1) {
2532
- throw new InternalError("Only one from table is supported for delete");
2978
+ throw new QueryError("Only one from table is supported for delete");
2533
2979
  }
2534
- return getTableName(node2.from.froms[0]);
2980
+ const r2 = this.extractTableName(node2.from.froms[0]);
2981
+ return r2 ? {
2982
+ mutationModel: r2.model,
2983
+ alias: r2.alias
2984
+ } : void 0;
2535
2985
  }).exhaustive();
2536
2986
  if (!r) {
2537
2987
  throw new InternalError(`Unable to get table name for query node: ${node}`);
@@ -2545,12 +2995,16 @@ var PolicyHandler = class extends OperationNodeTransformer {
2545
2995
  return InsertQueryNode.is(node) || UpdateQueryNode.is(node) || DeleteQueryNode.is(node);
2546
2996
  }
2547
2997
  buildPolicyFilter(model, alias, operation) {
2998
+ const m2mFilter = this.getModelPolicyFilterForManyToManyJoinTable(model, alias, operation);
2999
+ if (m2mFilter) {
3000
+ return m2mFilter;
3001
+ }
2548
3002
  const policies = this.getModelPolicies(model, operation);
2549
3003
  if (policies.length === 0) {
2550
3004
  return falseNode(this.dialect);
2551
3005
  }
2552
- const allows = policies.filter((policy) => policy.kind === "allow").map((policy) => this.transformPolicyCondition(model, alias, operation, policy));
2553
- const denies = policies.filter((policy) => policy.kind === "deny").map((policy) => this.transformPolicyCondition(model, alias, operation, policy));
3006
+ const allows = policies.filter((policy) => policy.kind === "allow").map((policy) => this.compilePolicyCondition(model, alias, operation, policy));
3007
+ const denies = policies.filter((policy) => policy.kind === "deny").map((policy) => this.compilePolicyCondition(model, alias, operation, policy));
2554
3008
  let combinedPolicy;
2555
3009
  if (allows.length === 0) {
2556
3010
  combinedPolicy = falseNode(this.dialect);
@@ -2566,100 +3020,59 @@ var PolicyHandler = class extends OperationNodeTransformer {
2566
3020
  }
2567
3021
  return combinedPolicy;
2568
3022
  }
2569
- transformSelectQuery(node) {
2570
- let whereNode = node.where;
2571
- node.from?.froms.forEach((from) => {
2572
- const extractResult = this.extractTableName(from);
2573
- if (extractResult) {
2574
- const { model, alias } = extractResult;
2575
- const filter = this.buildPolicyFilter(model, alias, "read");
2576
- whereNode = WhereNode2.create(whereNode?.where ? conjunction(this.dialect, [
2577
- whereNode.where,
2578
- filter
2579
- ]) : filter);
2580
- }
2581
- });
2582
- const baseResult = super.transformSelectQuery({
2583
- ...node,
2584
- where: void 0
2585
- });
2586
- return {
2587
- ...baseResult,
2588
- where: whereNode
2589
- };
2590
- }
2591
- transformInsertQuery(node) {
2592
- const result = super.transformInsertQuery(node);
2593
- if (!node.returning) {
2594
- return result;
2595
- }
2596
- if (this.onlyReturningId(node)) {
2597
- return result;
2598
- } else {
2599
- const idFields = getIdFields(this.client.$schema, this.getMutationModel(node));
3023
+ extractTableName(node) {
3024
+ if (TableNode4.is(node)) {
2600
3025
  return {
2601
- ...result,
2602
- returning: ReturningNode.create(idFields.map((field) => SelectionNode2.create(ColumnNode2.create(field))))
3026
+ model: node.table.identifier.name
2603
3027
  };
2604
3028
  }
2605
- }
2606
- transformUpdateQuery(node) {
2607
- const result = super.transformUpdateQuery(node);
2608
- const mutationModel = this.getMutationModel(node);
2609
- const filter = this.buildPolicyFilter(mutationModel, void 0, "update");
2610
- return {
2611
- ...result,
2612
- where: WhereNode2.create(result.where ? conjunction(this.dialect, [
2613
- result.where.where,
2614
- filter
2615
- ]) : filter)
2616
- };
2617
- }
2618
- transformDeleteQuery(node) {
2619
- const result = super.transformDeleteQuery(node);
2620
- const mutationModel = this.getMutationModel(node);
2621
- const filter = this.buildPolicyFilter(mutationModel, void 0, "delete");
2622
- return {
2623
- ...result,
2624
- where: WhereNode2.create(result.where ? conjunction(this.dialect, [
2625
- result.where.where,
2626
- filter
2627
- ]) : filter)
2628
- };
2629
- }
2630
- extractTableName(from) {
2631
- if (TableNode3.is(from)) {
2632
- return {
2633
- model: from.table.identifier.name
2634
- };
2635
- }
2636
- if (AliasNode3.is(from)) {
2637
- const inner = this.extractTableName(from.node);
3029
+ if (AliasNode4.is(node)) {
3030
+ const inner = this.extractTableName(node.node);
2638
3031
  if (!inner) {
2639
3032
  return void 0;
2640
3033
  }
2641
3034
  return {
2642
3035
  model: inner.model,
2643
- alias: IdentifierNode2.is(from.alias) ? from.alias.name : void 0
3036
+ alias: IdentifierNode2.is(node.alias) ? node.alias.name : void 0
2644
3037
  };
2645
3038
  } else {
2646
3039
  return void 0;
2647
3040
  }
2648
3041
  }
2649
- transformPolicyCondition(model, alias, operation, policy) {
2650
- return new ExpressionTransformer(this.client.$schema, this.client.$options, this.client.$auth).transform(policy.condition, {
3042
+ createPolicyFilterForFrom(node) {
3043
+ if (!node) {
3044
+ return void 0;
3045
+ }
3046
+ return this.createPolicyFilterForTables(node.froms);
3047
+ }
3048
+ createPolicyFilterForTables(tables) {
3049
+ return tables.reduce((acc, table) => {
3050
+ const extractResult = this.extractTableName(table);
3051
+ if (extractResult) {
3052
+ const { model, alias } = extractResult;
3053
+ const filter = this.buildPolicyFilter(model, alias, "read");
3054
+ return acc ? conjunction(this.dialect, [
3055
+ acc,
3056
+ filter
3057
+ ]) : filter;
3058
+ }
3059
+ return acc;
3060
+ }, void 0);
3061
+ }
3062
+ compilePolicyCondition(model, alias, operation, policy) {
3063
+ return new ExpressionTransformer(this.client).transform(policy.condition, {
2651
3064
  model,
2652
3065
  alias,
2653
3066
  operation,
2654
3067
  auth: this.client.$auth
2655
3068
  });
2656
3069
  }
2657
- getModelPolicies(modelName, operation) {
2658
- const modelDef = requireModel(this.client.$schema, modelName);
3070
+ getModelPolicies(model, operation) {
3071
+ const modelDef = requireModel(this.client.$schema, model);
2659
3072
  const result = [];
2660
3073
  const extractOperations = /* @__PURE__ */ __name((expr2) => {
2661
- invariant6(ExpressionUtils.isLiteral(expr2), "expecting a literal");
2662
- invariant6(typeof expr2.value === "string", "expecting a string literal");
3074
+ invariant7(ExpressionUtils.isLiteral(expr2), "expecting a literal");
3075
+ invariant7(typeof expr2.value === "string", "expecting a string literal");
2663
3076
  return expr2.value.split(",").filter((v) => !!v).map((v) => v.trim());
2664
3077
  }, "extractOperations");
2665
3078
  if (modelDef.attributes) {
@@ -2671,8 +3084,84 @@ var PolicyHandler = class extends OperationNodeTransformer {
2671
3084
  }
2672
3085
  return result;
2673
3086
  }
3087
+ resolveManyToManyJoinTable(tableName) {
3088
+ for (const model of Object.values(this.client.$schema.models)) {
3089
+ for (const field of Object.values(model.fields)) {
3090
+ const m2m = getManyToManyRelation(this.client.$schema, model.name, field.name);
3091
+ if (m2m?.joinTable === tableName) {
3092
+ const sortedRecord = [
3093
+ {
3094
+ model: model.name,
3095
+ field: field.name
3096
+ },
3097
+ {
3098
+ model: m2m.otherModel,
3099
+ field: m2m.otherField
3100
+ }
3101
+ ].sort(this.manyToManySorter);
3102
+ const firstIdFields = requireIdFields(this.client.$schema, sortedRecord[0].model);
3103
+ const secondIdFields = requireIdFields(this.client.$schema, sortedRecord[1].model);
3104
+ invariant7(firstIdFields.length === 1 && secondIdFields.length === 1, "only single-field id is supported for implicit many-to-many join table");
3105
+ return {
3106
+ firstModel: sortedRecord[0].model,
3107
+ firstField: sortedRecord[0].field,
3108
+ firstIdField: firstIdFields[0],
3109
+ secondModel: sortedRecord[1].model,
3110
+ secondField: sortedRecord[1].field,
3111
+ secondIdField: secondIdFields[0]
3112
+ };
3113
+ }
3114
+ }
3115
+ }
3116
+ return void 0;
3117
+ }
3118
+ manyToManySorter(a, b) {
3119
+ return a.model !== b.model ? a.model.localeCompare(b.model) : a.field.localeCompare(b.field);
3120
+ }
3121
+ isManyToManyJoinTable(tableName) {
3122
+ return !!this.resolveManyToManyJoinTable(tableName);
3123
+ }
3124
+ getModelPolicyFilterForManyToManyJoinTable(tableName, alias, operation) {
3125
+ const m2m = this.resolveManyToManyJoinTable(tableName);
3126
+ if (!m2m) {
3127
+ return void 0;
3128
+ }
3129
+ const checkForOperation = operation === "read" ? "read" : "update";
3130
+ const eb = expressionBuilder3();
3131
+ const joinTable = alias ?? tableName;
3132
+ const aQuery = eb.selectFrom(m2m.firstModel).whereRef(`${m2m.firstModel}.${m2m.firstIdField}`, "=", `${joinTable}.A`).select(() => new ExpressionWrapper(this.buildPolicyFilter(m2m.firstModel, void 0, checkForOperation)).as("$conditionA"));
3133
+ const bQuery = eb.selectFrom(m2m.secondModel).whereRef(`${m2m.secondModel}.${m2m.secondIdField}`, "=", `${joinTable}.B`).select(() => new ExpressionWrapper(this.buildPolicyFilter(m2m.secondModel, void 0, checkForOperation)).as("$conditionB"));
3134
+ return eb.and([
3135
+ aQuery,
3136
+ bQuery
3137
+ ]).toOperationNode();
3138
+ }
2674
3139
  };
2675
3140
 
3141
+ // src/plugins/policy/functions.ts
3142
+ var check = /* @__PURE__ */ __name((eb, args, { client, model, modelAlias, operation }) => {
3143
+ invariant8(args.length === 1 || args.length === 2, '"check" function requires 1 or 2 arguments');
3144
+ const arg1Node = args[0].toOperationNode();
3145
+ const arg2Node = args.length === 2 ? args[1].toOperationNode() : void 0;
3146
+ if (arg2Node) {
3147
+ invariant8(ValueNode4.is(arg2Node) && typeof arg2Node.value === "string", '"operation" parameter must be a string literal when provided');
3148
+ invariant8(CRUD.includes(arg2Node.value), '"operation" parameter must be one of "create", "read", "update", "delete"');
3149
+ }
3150
+ const fieldName = extractFieldName(arg1Node);
3151
+ invariant8(fieldName, 'Failed to extract field name from the first argument of "check" function');
3152
+ const fieldDef = requireField(client.$schema, model, fieldName);
3153
+ invariant8(fieldDef.relation, `Field "${fieldName}" is not a relation field in model "${model}"`);
3154
+ invariant8(!fieldDef.array, `Field "${fieldName}" is a to-many relation, which is not supported by "check"`);
3155
+ const relationModel = fieldDef.type;
3156
+ const op = arg2Node ? arg2Node.value : operation;
3157
+ const policyHandler = new PolicyHandler(client);
3158
+ const joinPairs = buildJoinPairs(client.$schema, model, modelAlias, fieldName, relationModel);
3159
+ const joinCondition = joinPairs.length === 1 ? eb(eb.ref(joinPairs[0][0]), "=", eb.ref(joinPairs[0][1])) : eb.and(joinPairs.map(([left, right]) => eb(eb.ref(left), "=", eb.ref(right))));
3160
+ const policyCondition = policyHandler.buildPolicyFilter(relationModel, void 0, op);
3161
+ const result = eb.selectFrom(relationModel).where(joinCondition).select(new ExpressionWrapper2(policyCondition).as("$condition"));
3162
+ return result;
3163
+ }, "check");
3164
+
2676
3165
  // src/plugins/policy/plugin.ts
2677
3166
  var PolicyPlugin = class {
2678
3167
  static {
@@ -2687,6 +3176,11 @@ var PolicyPlugin = class {
2687
3176
  get description() {
2688
3177
  return "Enforces access policies defined in the schema.";
2689
3178
  }
3179
+ get functions() {
3180
+ return {
3181
+ check
3182
+ };
3183
+ }
2690
3184
  onKyselyQuery({
2691
3185
  query,
2692
3186
  client,
@@ -2703,6 +3197,7 @@ var PolicyPlugin = class {
2703
3197
  };
2704
3198
  export {
2705
3199
  PolicyPlugin,
2706
- RejectedByPolicyError
3200
+ RejectedByPolicyError,
3201
+ RejectedByPolicyReason
2707
3202
  };
2708
3203
  //# sourceMappingURL=index.js.map