@zenstackhq/runtime 3.0.0-alpha.7 → 3.0.0-alpha.9

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.
@@ -56,8 +56,10 @@ var QueryError = class extends Error {
56
56
  static {
57
57
  __name(this, "QueryError");
58
58
  }
59
- constructor(message) {
60
- super(message);
59
+ constructor(message, cause) {
60
+ super(message, {
61
+ cause
62
+ });
61
63
  }
62
64
  };
63
65
  var InternalError = class extends Error {
@@ -77,7 +79,7 @@ __name(getModel, "getModel");
77
79
  function requireModel(schema, model) {
78
80
  const matchedName = Object.keys(schema.models).find((k) => k.toLowerCase() === model.toLowerCase());
79
81
  if (!matchedName) {
80
- throw new QueryError(`Model "${model}" not found`);
82
+ throw new QueryError(`Model "${model}" not found in schema`);
81
83
  }
82
84
  return schema.models[matchedName];
83
85
  }
@@ -140,6 +142,11 @@ function getRelationForeignKeyFieldPairs(schema, model, relationField) {
140
142
  }
141
143
  }
142
144
  __name(getRelationForeignKeyFieldPairs, "getRelationForeignKeyFieldPairs");
145
+ function isRelationField(schema, model, field) {
146
+ const fieldDef = requireField(schema, model, field);
147
+ return !!fieldDef.relation;
148
+ }
149
+ __name(isRelationField, "isRelationField");
143
150
  function getUniqueFields(schema, model) {
144
151
  const modelDef = requireModel(schema, model);
145
152
  const result = [];
@@ -176,7 +183,7 @@ function buildFieldRef(schema, model, field, options, eb, modelAlias) {
176
183
  computer = computedFields?.[model]?.[field];
177
184
  }
178
185
  if (!computer) {
179
- throw new QueryError(`Computed field "${field}" implementation not provided`);
186
+ throw new QueryError(`Computed field "${field}" implementation not provided for model "${model}"`);
180
187
  }
181
188
  return computer(eb);
182
189
  }
@@ -284,7 +291,7 @@ var BaseCrudDialect = class {
284
291
  this.schema = schema;
285
292
  this.options = options;
286
293
  }
287
- transformPrimitive(value, _type) {
294
+ transformPrimitive(value, _type, _forArrayField) {
288
295
  return value;
289
296
  }
290
297
  buildFilter(eb, model, modelAlias, where) {
@@ -427,7 +434,7 @@ var BaseCrudDialect = class {
427
434
  if (_value === void 0) {
428
435
  continue;
429
436
  }
430
- const value = this.transformPrimitive(_value, fieldType);
437
+ const value = this.transformPrimitive(_value, fieldType, !!fieldDef.array);
431
438
  switch (key) {
432
439
  case "equals": {
433
440
  clauses.push(this.buildLiteralFilter(eb, fieldRef, fieldType, eb.val(value)));
@@ -465,10 +472,14 @@ var BaseCrudDialect = class {
465
472
  if (isEnum(this.schema, fieldDef.type)) {
466
473
  return this.buildEnumFilter(eb, modelAlias, field, fieldDef, payload);
467
474
  }
468
- return (0, import_ts_pattern.match)(fieldDef.type).with("String", () => this.buildStringFilter(eb, modelAlias, field, payload)).with(import_ts_pattern.P.union("Int", "Float", "Decimal", "BigInt"), (type) => this.buildNumberFilter(eb, model, modelAlias, field, type, payload)).with("Boolean", () => this.buildBooleanFilter(eb, modelAlias, field, payload)).with("DateTime", () => this.buildDateTimeFilter(eb, modelAlias, field, payload)).with("Bytes", () => this.buildBytesFilter(eb, modelAlias, field, payload)).exhaustive();
475
+ return (0, import_ts_pattern.match)(fieldDef.type).with("String", () => this.buildStringFilter(eb, modelAlias, field, payload)).with(import_ts_pattern.P.union("Int", "Float", "Decimal", "BigInt"), (type) => this.buildNumberFilter(eb, model, modelAlias, field, type, payload)).with("Boolean", () => this.buildBooleanFilter(eb, modelAlias, field, payload)).with("DateTime", () => this.buildDateTimeFilter(eb, modelAlias, field, payload)).with("Bytes", () => this.buildBytesFilter(eb, modelAlias, field, payload)).with("Json", () => {
476
+ throw new InternalError("JSON filters are not supported yet");
477
+ }).with("Unsupported", () => {
478
+ throw new QueryError(`Unsupported field cannot be used in filters`);
479
+ }).exhaustive();
469
480
  }
470
481
  buildLiteralFilter(eb, lhs, type, rhs) {
471
- return eb(lhs, "=", rhs !== null && rhs !== void 0 ? this.transformPrimitive(rhs, type) : rhs);
482
+ return eb(lhs, "=", rhs !== null && rhs !== void 0 ? this.transformPrimitive(rhs, type, false) : rhs);
472
483
  }
473
484
  buildStandardFilter(eb, type, payload, lhs, getRhs, recurse, throwIfInvalid = false, onlyForKeys = void 0) {
474
485
  if (payload === null || !(0, import_common_helpers.isPlainObject)(payload)) {
@@ -555,22 +566,22 @@ var BaseCrudDialect = class {
555
566
  }
556
567
  }
557
568
  buildNumberFilter(eb, model, table, field, type, payload) {
558
- const { conditions } = this.buildStandardFilter(eb, type, payload, buildFieldRef(this.schema, model, field, this.options, eb), (value) => this.transformPrimitive(value, type), (value) => this.buildNumberFilter(eb, model, table, field, type, value));
569
+ const { conditions } = this.buildStandardFilter(eb, type, payload, buildFieldRef(this.schema, model, field, this.options, eb), (value) => this.transformPrimitive(value, type, false), (value) => this.buildNumberFilter(eb, model, table, field, type, value));
559
570
  return this.and(eb, ...conditions);
560
571
  }
561
572
  buildBooleanFilter(eb, table, field, payload) {
562
- const { conditions } = this.buildStandardFilter(eb, "Boolean", payload, import_kysely.sql.ref(`${table}.${field}`), (value) => this.transformPrimitive(value, "Boolean"), (value) => this.buildBooleanFilter(eb, table, field, value), true, [
573
+ const { conditions } = this.buildStandardFilter(eb, "Boolean", payload, import_kysely.sql.ref(`${table}.${field}`), (value) => this.transformPrimitive(value, "Boolean", false), (value) => this.buildBooleanFilter(eb, table, field, value), true, [
563
574
  "equals",
564
575
  "not"
565
576
  ]);
566
577
  return this.and(eb, ...conditions);
567
578
  }
568
579
  buildDateTimeFilter(eb, table, field, payload) {
569
- const { conditions } = this.buildStandardFilter(eb, "DateTime", payload, import_kysely.sql.ref(`${table}.${field}`), (value) => this.transformPrimitive(value, "DateTime"), (value) => this.buildDateTimeFilter(eb, table, field, value), true);
580
+ const { conditions } = this.buildStandardFilter(eb, "DateTime", payload, import_kysely.sql.ref(`${table}.${field}`), (value) => this.transformPrimitive(value, "DateTime", false), (value) => this.buildDateTimeFilter(eb, table, field, value), true);
570
581
  return this.and(eb, ...conditions);
571
582
  }
572
583
  buildBytesFilter(eb, table, field, payload) {
573
- const conditions = this.buildStandardFilter(eb, "Bytes", payload, import_kysely.sql.ref(`${table}.${field}`), (value) => this.transformPrimitive(value, "Bytes"), (value) => this.buildBytesFilter(eb, table, field, value), true, [
584
+ const conditions = this.buildStandardFilter(eb, "Bytes", payload, import_kysely.sql.ref(`${table}.${field}`), (value) => this.transformPrimitive(value, "Bytes", false), (value) => this.buildBytesFilter(eb, table, field, value), true, [
574
585
  "equals",
575
586
  "in",
576
587
  "notIn",
@@ -669,10 +680,10 @@ var BaseCrudDialect = class {
669
680
  return negated ? sort === "asc" ? "desc" : "asc" : sort;
670
681
  }
671
682
  true(eb) {
672
- return eb.lit(this.transformPrimitive(true, "Boolean"));
683
+ return eb.lit(this.transformPrimitive(true, "Boolean", false));
673
684
  }
674
685
  false(eb) {
675
- return eb.lit(this.transformPrimitive(false, "Boolean"));
686
+ return eb.lit(this.transformPrimitive(false, "Boolean", false));
676
687
  }
677
688
  isTrue(expression) {
678
689
  const node = expression.toOperationNode();
@@ -721,14 +732,18 @@ var PostgresCrudDialect = class extends BaseCrudDialect {
721
732
  get provider() {
722
733
  return "postgresql";
723
734
  }
724
- transformPrimitive(value, type) {
735
+ transformPrimitive(value, type, forArrayField) {
725
736
  if (value === void 0) {
726
737
  return value;
727
738
  }
728
739
  if (Array.isArray(value)) {
729
- return value.map((v) => this.transformPrimitive(v, type));
740
+ if (type === "Json" && !forArrayField) {
741
+ return JSON.stringify(value);
742
+ } else {
743
+ return value.map((v) => this.transformPrimitive(v, type, false));
744
+ }
730
745
  } else {
731
- return (0, import_ts_pattern2.match)(type).with("DateTime", () => value instanceof Date ? value : typeof value === "string" ? new Date(value) : value).otherwise(() => value);
746
+ return (0, import_ts_pattern2.match)(type).with("DateTime", () => value instanceof Date ? value : typeof value === "string" ? new Date(value) : value).with("Decimal", () => value !== null ? value.toString() : value).otherwise(() => value);
732
747
  }
733
748
  }
734
749
  buildRelationSelection(query, model, relationField, parentAlias, payload) {
@@ -795,25 +810,33 @@ var PostgresCrudDialect = class extends BaseCrudDialect {
795
810
  buildFieldRef(this.schema, relationModel, field, this.options, eb)
796
811
  ]).flatMap((v) => v));
797
812
  } else if (payload.select) {
798
- objArgs.push(...Object.entries(payload.select).filter(([, value]) => value).map(([field]) => [
799
- import_kysely2.sql.lit(field),
800
- buildFieldRef(this.schema, relationModel, field, this.options, eb)
801
- ]).flatMap((v) => v));
813
+ objArgs.push(...Object.entries(payload.select).filter(([, value]) => value).map(([field]) => {
814
+ const fieldDef = requireField(this.schema, relationModel, field);
815
+ const fieldValue = fieldDef.relation ? eb.ref(`${parentName}$${relationField}$${field}.$j`) : buildFieldRef(this.schema, relationModel, field, this.options, eb);
816
+ return [
817
+ import_kysely2.sql.lit(field),
818
+ fieldValue
819
+ ];
820
+ }).flatMap((v) => v));
802
821
  }
803
822
  if (typeof payload === "object" && payload.include && typeof payload.include === "object") {
804
823
  objArgs.push(...Object.entries(payload.include).filter(([, value]) => value).map(([field]) => [
805
824
  import_kysely2.sql.lit(field),
825
+ // reference the synthesized JSON field
806
826
  eb.ref(`${parentName}$${relationField}$${field}.$j`)
807
827
  ]).flatMap((v) => v));
808
828
  }
809
829
  return objArgs;
810
830
  }
811
- buildRelationJoins(model, relationField, qb, payload, parentName) {
831
+ buildRelationJoins(relationModel, relationField, qb, payload, parentName) {
812
832
  let result = qb;
813
- if (typeof payload === "object" && payload.include && typeof payload.include === "object") {
814
- Object.entries(payload.include).filter(([, value]) => value).forEach(([field, value]) => {
815
- result = this.buildRelationJSON(model, result, field, `${parentName}$${relationField}`, value);
816
- });
833
+ if (typeof payload === "object") {
834
+ const selectInclude = payload.include ?? payload.select;
835
+ if (selectInclude && typeof selectInclude === "object") {
836
+ Object.entries(selectInclude).filter(([, value]) => value).filter(([field]) => isRelationField(this.schema, relationModel, field)).forEach(([field, value]) => {
837
+ result = this.buildRelationJSON(relationModel, result, field, `${parentName}$${relationField}`, value);
838
+ });
839
+ }
817
840
  }
818
841
  return result;
819
842
  }
@@ -866,14 +889,14 @@ var SqliteCrudDialect = class extends BaseCrudDialect {
866
889
  get provider() {
867
890
  return "sqlite";
868
891
  }
869
- transformPrimitive(value, type) {
892
+ transformPrimitive(value, type, _forArrayField) {
870
893
  if (value === void 0) {
871
894
  return value;
872
895
  }
873
896
  if (Array.isArray(value)) {
874
- return value.map((v) => this.transformPrimitive(v, type));
897
+ return value.map((v) => this.transformPrimitive(v, type, false));
875
898
  } else {
876
- return (0, import_ts_pattern3.match)(type).with("Boolean", () => value ? 1 : 0).with("DateTime", () => value instanceof Date ? value.toISOString() : value).with("Decimal", () => value.toString()).with("Bytes", () => Buffer.from(value)).otherwise(() => value);
899
+ return (0, import_ts_pattern3.match)(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);
877
900
  }
878
901
  }
879
902
  buildRelationSelection(query, model, relationField, parentAlias, payload) {
@@ -1478,11 +1501,11 @@ var ExpressionEvaluator = class {
1478
1501
  // src/plugins/policy/utils.ts
1479
1502
  var import_kysely5 = require("kysely");
1480
1503
  function trueNode(dialect) {
1481
- return import_kysely5.ValueNode.createImmediate(dialect.transformPrimitive(true, "Boolean"));
1504
+ return import_kysely5.ValueNode.createImmediate(dialect.transformPrimitive(true, "Boolean", false));
1482
1505
  }
1483
1506
  __name(trueNode, "trueNode");
1484
1507
  function falseNode(dialect) {
1485
- return import_kysely5.ValueNode.createImmediate(dialect.transformPrimitive(false, "Boolean"));
1508
+ return import_kysely5.ValueNode.createImmediate(dialect.transformPrimitive(false, "Boolean", false));
1486
1509
  }
1487
1510
  __name(falseNode, "falseNode");
1488
1511
  function isTrueNode(node) {
@@ -1740,7 +1763,7 @@ var ExpressionTransformer = class {
1740
1763
  }
1741
1764
  }
1742
1765
  transformValue(value, type) {
1743
- return import_kysely6.ValueNode.create(this.dialect.transformPrimitive(value, type) ?? null);
1766
+ return import_kysely6.ValueNode.create(this.dialect.transformPrimitive(value, type, false) ?? null);
1744
1767
  }
1745
1768
  _unary(expr2, context) {
1746
1769
  (0, import_common_helpers5.invariant)(expr2.op === "!", 'only "!" operator is supported');
@@ -1983,7 +2006,7 @@ var PolicyHandler = class extends import_kysely7.OperationNodeTransformer {
1983
2006
  get kysely() {
1984
2007
  return this.client.$qb;
1985
2008
  }
1986
- async handle(node, proceed, transaction) {
2009
+ async handle(node, proceed) {
1987
2010
  if (!this.isCrudQueryNode(node)) {
1988
2011
  throw new RejectedByPolicyError(void 0, "non-CRUD queries are not allowed");
1989
2012
  }
@@ -2003,27 +2026,20 @@ var PolicyHandler = class extends import_kysely7.OperationNodeTransformer {
2003
2026
  if (!mutationRequiresTransaction && !node.returning) {
2004
2027
  return proceed(this.transformNode(node));
2005
2028
  }
2006
- let readBackError = false;
2007
- const result = await transaction(async (txProceed) => {
2008
- if (import_kysely7.InsertQueryNode.is(node)) {
2009
- await this.enforcePreCreatePolicy(node, txProceed);
2010
- }
2011
- const transformedNode = this.transformNode(node);
2012
- const result2 = await txProceed(transformedNode);
2013
- if (!this.onlyReturningId(node)) {
2014
- const readBackResult = await this.processReadBack(node, result2, txProceed);
2015
- if (readBackResult.rows.length !== result2.rows.length) {
2016
- readBackError = true;
2017
- }
2018
- return readBackResult;
2019
- } else {
2020
- return result2;
2029
+ if (import_kysely7.InsertQueryNode.is(node)) {
2030
+ await this.enforcePreCreatePolicy(node, proceed);
2031
+ }
2032
+ const transformedNode = this.transformNode(node);
2033
+ const result = await proceed(transformedNode);
2034
+ if (!this.onlyReturningId(node)) {
2035
+ const readBackResult = await this.processReadBack(node, result, proceed);
2036
+ if (readBackResult.rows.length !== result.rows.length) {
2037
+ throw new RejectedByPolicyError(mutationModel, "result is not allowed to be read back");
2021
2038
  }
2022
- });
2023
- if (readBackError) {
2024
- throw new RejectedByPolicyError(mutationModel, "result is not allowed to be read back");
2039
+ return readBackResult;
2040
+ } else {
2041
+ return result;
2025
2042
  }
2026
- return result;
2027
2043
  }
2028
2044
  onlyReturningId(node) {
2029
2045
  if (!node.returning) {
@@ -2084,11 +2100,11 @@ var PolicyHandler = class extends import_kysely7.OperationNodeTransformer {
2084
2100
  if (typeof item === "object" && item && "kind" in item) {
2085
2101
  (0, import_common_helpers6.invariant)(item.kind === "ValueNode", "expecting a ValueNode");
2086
2102
  result.push({
2087
- node: import_kysely7.ValueNode.create(this.dialect.transformPrimitive(item.value, fieldDef.type)),
2103
+ node: import_kysely7.ValueNode.create(this.dialect.transformPrimitive(item.value, fieldDef.type, !!fieldDef.array)),
2088
2104
  raw: item.value
2089
2105
  });
2090
2106
  } else {
2091
- const value = this.dialect.transformPrimitive(item, fieldDef.type);
2107
+ const value = this.dialect.transformPrimitive(item, fieldDef.type, !!fieldDef.array);
2092
2108
  if (Array.isArray(value)) {
2093
2109
  result.push({
2094
2110
  node: import_kysely7.RawNode.createWithSql(this.dialect.buildArrayLiteralSQL(value)),
@@ -2319,9 +2335,18 @@ var PolicyPlugin = class {
2319
2335
  get description() {
2320
2336
  return "Enforces access policies defined in the schema.";
2321
2337
  }
2322
- onKyselyQuery({ query, client, proceed, transaction }) {
2338
+ onKyselyQuery({
2339
+ query,
2340
+ client,
2341
+ proceed
2342
+ /*, transaction*/
2343
+ }) {
2323
2344
  const handler = new PolicyHandler(client);
2324
- return handler.handle(query, proceed, transaction);
2345
+ return handler.handle(
2346
+ query,
2347
+ proceed
2348
+ /*, transaction*/
2349
+ );
2325
2350
  }
2326
2351
  };
2327
2352
  // Annotate the CommonJS export names for ESM import in node: