@zenstackhq/runtime 1.0.0-beta.20 → 1.0.0-beta.22

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.
Files changed (55) hide show
  1. package/constants.d.ts +9 -13
  2. package/constants.js +10 -14
  3. package/constants.js.map +1 -1
  4. package/enhancements/enhance.js +2 -2
  5. package/enhancements/enhance.js.map +1 -1
  6. package/enhancements/index.d.ts +1 -1
  7. package/enhancements/index.js +1 -1
  8. package/enhancements/index.js.map +1 -1
  9. package/enhancements/model-meta.d.ts +0 -4
  10. package/enhancements/model-meta.js +3 -28
  11. package/enhancements/model-meta.js.map +1 -1
  12. package/enhancements/{nested-write-vistor.js → nested-write-visitor.js} +1 -1
  13. package/enhancements/nested-write-visitor.js.map +1 -0
  14. package/enhancements/omit.d.ts +3 -3
  15. package/enhancements/omit.js +2 -1
  16. package/enhancements/omit.js.map +1 -1
  17. package/enhancements/password.d.ts +4 -4
  18. package/enhancements/password.js +4 -4
  19. package/enhancements/password.js.map +1 -1
  20. package/enhancements/policy/handler.d.ts +13 -7
  21. package/enhancements/policy/handler.js +134 -107
  22. package/enhancements/policy/handler.js.map +1 -1
  23. package/enhancements/policy/index.d.ts +4 -4
  24. package/enhancements/policy/index.js +4 -40
  25. package/enhancements/policy/index.js.map +1 -1
  26. package/enhancements/policy/policy-utils.d.ts +10 -5
  27. package/enhancements/policy/policy-utils.js +306 -276
  28. package/enhancements/policy/policy-utils.js.map +1 -1
  29. package/enhancements/policy/promise.d.ts +5 -0
  30. package/enhancements/policy/promise.js +42 -0
  31. package/enhancements/policy/promise.js.map +1 -0
  32. package/enhancements/proxy.js +27 -21
  33. package/enhancements/proxy.js.map +1 -1
  34. package/enhancements/types.d.ts +9 -0
  35. package/enhancements/utils.d.ts +1 -1
  36. package/enhancements/utils.js +3 -4
  37. package/enhancements/utils.js.map +1 -1
  38. package/index.d.ts +1 -0
  39. package/index.js +1 -0
  40. package/index.js.map +1 -1
  41. package/loader.d.ts +22 -0
  42. package/loader.js +86 -0
  43. package/loader.js.map +1 -0
  44. package/package.json +1 -1
  45. package/types.d.ts +14 -13
  46. package/types.js.map +1 -1
  47. package/validation.d.ts +5 -0
  48. package/validation.js +13 -1
  49. package/validation.js.map +1 -1
  50. package/zod/index.d.ts +1 -0
  51. package/zod/index.js +1 -0
  52. package/zod/objects.d.ts +1 -0
  53. package/zod/objects.js +8 -0
  54. package/enhancements/nested-write-vistor.js.map +0 -1
  55. /package/enhancements/{nested-write-vistor.d.ts → nested-write-visitor.d.ts} +0 -0
@@ -41,13 +41,31 @@ class PolicyUtil {
41
41
  * Creates a conjunction of a list of query conditions.
42
42
  */
43
43
  and(...conditions) {
44
- return this.reduce({ AND: conditions });
44
+ const filtered = conditions.filter((c) => c !== undefined);
45
+ if (filtered.length === 0) {
46
+ return this.makeTrue();
47
+ }
48
+ else if (filtered.length === 1) {
49
+ return this.reduce(filtered[0]);
50
+ }
51
+ else {
52
+ return this.reduce({ AND: filtered });
53
+ }
45
54
  }
46
55
  /**
47
56
  * Creates a disjunction of a list of query conditions.
48
57
  */
49
58
  or(...conditions) {
50
- return this.reduce({ OR: conditions });
59
+ const filtered = conditions.filter((c) => c !== undefined);
60
+ if (filtered.length === 0) {
61
+ return this.makeFalse();
62
+ }
63
+ else if (filtered.length === 1) {
64
+ return this.reduce(filtered[0]);
65
+ }
66
+ else {
67
+ return this.reduce({ OR: filtered });
68
+ }
51
69
  }
52
70
  /**
53
71
  * Creates a negation of a query condition.
@@ -95,49 +113,72 @@ class PolicyUtil {
95
113
  if (condition === false) {
96
114
  return this.makeFalse();
97
115
  }
98
- if ('AND' in condition && Array.isArray(condition.AND)) {
99
- const children = condition.AND.map((c) => this.reduce(c)).filter((c) => c !== undefined && !this.isTrue(c));
100
- if (children.length === 0) {
101
- return this.makeTrue();
102
- }
103
- else if (children.some((c) => this.isFalse(c))) {
104
- return this.makeFalse();
105
- }
106
- else if (children.length === 1) {
107
- return children[0];
108
- }
109
- else {
110
- return { AND: children };
111
- }
116
+ if (condition === null) {
117
+ return condition;
112
118
  }
113
- if ('OR' in condition && Array.isArray(condition.OR)) {
114
- const children = condition.OR.map((c) => this.reduce(c)).filter((c) => c !== undefined && !this.isFalse(c));
115
- if (children.length === 0) {
116
- return this.makeFalse();
117
- }
118
- else if (children.some((c) => this.isTrue(c))) {
119
- return this.makeTrue();
120
- }
121
- else if (children.length === 1) {
122
- return children[0];
123
- }
124
- else {
125
- return { OR: children };
126
- }
127
- }
128
- if ('NOT' in condition && condition.NOT !== null && typeof condition.NOT === 'object') {
129
- const child = this.reduce(condition.NOT);
130
- if (this.isTrue(child)) {
131
- return this.makeFalse();
132
- }
133
- else if (this.isFalse(child)) {
134
- return this.makeTrue();
119
+ const result = {};
120
+ for (const [key, value] of Object.entries(condition)) {
121
+ if (value === null || value === undefined) {
122
+ result[key] = value;
123
+ continue;
135
124
  }
136
- else {
137
- return { NOT: child };
125
+ switch (key) {
126
+ case 'AND': {
127
+ const children = (0, utils_1.enumerate)(value)
128
+ .map((c) => this.reduce(c))
129
+ .filter((c) => c !== undefined && !this.isTrue(c));
130
+ if (children.length === 0) {
131
+ result[key] = []; // true
132
+ }
133
+ else if (children.some((c) => this.isFalse(c))) {
134
+ result['OR'] = []; // false
135
+ }
136
+ else {
137
+ if (!this.isTrue({ AND: result[key] })) {
138
+ // use AND only if it's not already true
139
+ result[key] = !Array.isArray(value) && children.length === 1 ? children[0] : children;
140
+ }
141
+ }
142
+ break;
143
+ }
144
+ case 'OR': {
145
+ const children = (0, utils_1.enumerate)(value)
146
+ .map((c) => this.reduce(c))
147
+ .filter((c) => c !== undefined && !this.isFalse(c));
148
+ if (children.length === 0) {
149
+ result[key] = []; // false
150
+ }
151
+ else if (children.some((c) => this.isTrue(c))) {
152
+ result['AND'] = []; // true
153
+ }
154
+ else {
155
+ if (!this.isFalse({ OR: result[key] })) {
156
+ // use OR only if it's not already false
157
+ result[key] = !Array.isArray(value) && children.length === 1 ? children[0] : children;
158
+ }
159
+ }
160
+ break;
161
+ }
162
+ case 'NOT': {
163
+ result[key] = this.reduce(value);
164
+ break;
165
+ }
166
+ default: {
167
+ const booleanKeys = ['AND', 'OR', 'NOT', 'is', 'isNot', 'none', 'every', 'some'];
168
+ if (typeof value === 'object' &&
169
+ value &&
170
+ // recurse only if the value has at least one boolean key
171
+ Object.keys(value).some((k) => booleanKeys.includes(k))) {
172
+ result[key] = this.reduce(value);
173
+ }
174
+ else {
175
+ result[key] = value;
176
+ }
177
+ break;
178
+ }
138
179
  }
139
180
  }
140
- return condition;
181
+ return result;
141
182
  }
142
183
  //#endregion
143
184
  //# Auth guard
@@ -214,143 +255,131 @@ class PolicyUtil {
214
255
  * Injects model auth guard as where clause.
215
256
  */
216
257
  injectAuthGuard(db, args, model, operation) {
217
- return __awaiter(this, void 0, void 0, function* () {
218
- let guard = this.getAuthGuard(db, model, operation);
219
- if (this.isFalse(guard)) {
258
+ let guard = this.getAuthGuard(db, model, operation);
259
+ if (this.isFalse(guard)) {
260
+ args.where = this.makeFalse();
261
+ return false;
262
+ }
263
+ if (operation === 'update' && args) {
264
+ // merge field-level policy guards
265
+ const fieldUpdateGuard = this.getFieldUpdateGuards(db, model, args);
266
+ if (fieldUpdateGuard.rejectedByField) {
267
+ // rejected
220
268
  args.where = this.makeFalse();
221
269
  return false;
222
270
  }
223
- if (operation === 'update' && args) {
224
- // merge field-level policy guards
225
- const fieldUpdateGuard = this.getFieldUpdateGuards(db, model, args);
226
- if (fieldUpdateGuard.rejectedByField) {
227
- // rejected
228
- args.where = this.makeFalse();
229
- return false;
230
- }
231
- else if (fieldUpdateGuard.guard) {
232
- // merge
233
- guard = this.and(guard, fieldUpdateGuard.guard);
234
- }
235
- }
236
- if (args.where) {
237
- // inject into relation fields:
238
- // to-many: some/none/every
239
- // to-one: direct-conditions/is/isNot
240
- yield this.injectGuardForRelationFields(db, model, args.where, operation);
271
+ else if (fieldUpdateGuard.guard) {
272
+ // merge
273
+ guard = this.and(guard, fieldUpdateGuard.guard);
241
274
  }
242
- args.where = this.and(args.where, guard);
243
- return true;
244
- });
275
+ }
276
+ if (args.where) {
277
+ // inject into relation fields:
278
+ // to-many: some/none/every
279
+ // to-one: direct-conditions/is/isNot
280
+ this.injectGuardForRelationFields(db, model, args.where, operation);
281
+ }
282
+ args.where = this.and(args.where, guard);
283
+ return true;
245
284
  }
246
285
  injectGuardForRelationFields(db, model, payload, operation) {
247
- return __awaiter(this, void 0, void 0, function* () {
248
- for (const [field, subPayload] of Object.entries(payload)) {
249
- if (!subPayload) {
250
- continue;
251
- }
252
- const fieldInfo = yield (0, model_meta_1.resolveField)(this.modelMeta, model, field);
253
- if (!fieldInfo || !fieldInfo.isDataModel) {
254
- continue;
255
- }
256
- if (fieldInfo.isArray) {
257
- yield this.injectGuardForToManyField(db, fieldInfo, subPayload, operation);
258
- }
259
- else {
260
- yield this.injectGuardForToOneField(db, fieldInfo, subPayload, operation);
261
- }
286
+ for (const [field, subPayload] of Object.entries(payload)) {
287
+ if (!subPayload) {
288
+ continue;
262
289
  }
263
- });
290
+ const fieldInfo = (0, model_meta_1.resolveField)(this.modelMeta, model, field);
291
+ if (!fieldInfo || !fieldInfo.isDataModel) {
292
+ continue;
293
+ }
294
+ if (fieldInfo.isArray) {
295
+ this.injectGuardForToManyField(db, fieldInfo, subPayload, operation);
296
+ }
297
+ else {
298
+ this.injectGuardForToOneField(db, fieldInfo, subPayload, operation);
299
+ }
300
+ }
264
301
  }
265
302
  injectGuardForToManyField(db, fieldInfo, payload, operation) {
266
- return __awaiter(this, void 0, void 0, function* () {
267
- const guard = this.getAuthGuard(db, fieldInfo.type, operation);
268
- if (payload.some) {
269
- yield this.injectGuardForRelationFields(db, fieldInfo.type, payload.some, operation);
270
- // turn "some" into: { some: { AND: [guard, payload.some] } }
271
- payload.some = this.and(payload.some, guard);
272
- }
273
- if (payload.none) {
274
- yield this.injectGuardForRelationFields(db, fieldInfo.type, payload.none, operation);
275
- // turn none into: { none: { AND: [guard, payload.none] } }
276
- payload.none = this.and(payload.none, guard);
277
- }
278
- if (payload.every &&
279
- typeof payload.every === 'object' &&
280
- // ignore empty every clause
281
- Object.keys(payload.every).length > 0) {
282
- yield this.injectGuardForRelationFields(db, fieldInfo.type, payload.every, operation);
283
- // turn "every" into: { none: { AND: [guard, { NOT: payload.every }] } }
284
- if (!payload.none) {
285
- payload.none = {};
286
- }
287
- payload.none = this.and(payload.none, guard, this.not(payload.every));
288
- delete payload.every;
289
- }
290
- });
303
+ const guard = this.getAuthGuard(db, fieldInfo.type, operation);
304
+ if (payload.some) {
305
+ this.injectGuardForRelationFields(db, fieldInfo.type, payload.some, operation);
306
+ // turn "some" into: { some: { AND: [guard, payload.some] } }
307
+ payload.some = this.and(payload.some, guard);
308
+ }
309
+ if (payload.none) {
310
+ this.injectGuardForRelationFields(db, fieldInfo.type, payload.none, operation);
311
+ // turn none into: { none: { AND: [guard, payload.none] } }
312
+ payload.none = this.and(payload.none, guard);
313
+ }
314
+ if (payload.every &&
315
+ typeof payload.every === 'object' &&
316
+ // ignore empty every clause
317
+ Object.keys(payload.every).length > 0) {
318
+ this.injectGuardForRelationFields(db, fieldInfo.type, payload.every, operation);
319
+ // turn "every" into: { none: { AND: [guard, { NOT: payload.every }] } }
320
+ if (!payload.none) {
321
+ payload.none = {};
322
+ }
323
+ payload.none = this.and(payload.none, guard, this.not(payload.every));
324
+ delete payload.every;
325
+ }
291
326
  }
292
327
  injectGuardForToOneField(db, fieldInfo, payload, operation) {
293
- return __awaiter(this, void 0, void 0, function* () {
294
- const guard = this.getAuthGuard(db, fieldInfo.type, operation);
295
- if (payload.is || payload.isNot) {
296
- if (payload.is) {
297
- yield this.injectGuardForRelationFields(db, fieldInfo.type, payload.is, operation);
298
- // turn "is" into: { is: { AND: [ originalIs, guard ] }
299
- payload.is = this.and(payload.is, guard);
300
- }
301
- if (payload.isNot) {
302
- yield this.injectGuardForRelationFields(db, fieldInfo.type, payload.isNot, operation);
303
- // turn "isNot" into: { isNot: { AND: [ originalIsNot, { NOT: guard } ] } }
304
- payload.isNot = this.and(payload.isNot, this.not(guard));
305
- delete payload.isNot;
306
- }
328
+ const guard = this.getAuthGuard(db, fieldInfo.type, operation);
329
+ // is|isNot and flat fields conditions are mutually exclusive
330
+ if (payload.is || payload.isNot) {
331
+ if (payload.is) {
332
+ this.injectGuardForRelationFields(db, fieldInfo.type, payload.is, operation);
307
333
  }
308
- else {
309
- yield this.injectGuardForRelationFields(db, fieldInfo.type, payload, operation);
310
- // turn direct conditions into: { is: { AND: [ originalConditions, guard ] } }
311
- const combined = this.and((0, deepcopy_1.default)(payload), guard);
312
- Object.keys(payload).forEach((key) => delete payload[key]);
313
- payload.is = combined;
334
+ if (payload.isNot) {
335
+ this.injectGuardForRelationFields(db, fieldInfo.type, payload.isNot, operation);
314
336
  }
315
- });
337
+ // merge guard with existing "is": { is: [originalIs, guard] }
338
+ payload.is = this.and(payload.is, guard);
339
+ }
340
+ else {
341
+ this.injectGuardForRelationFields(db, fieldInfo.type, payload, operation);
342
+ // turn direct conditions into: { is: { AND: [ originalConditions, guard ] } }
343
+ const combined = this.and((0, deepcopy_1.default)(payload), guard);
344
+ Object.keys(payload).forEach((key) => delete payload[key]);
345
+ payload.is = combined;
346
+ }
316
347
  }
317
348
  /**
318
349
  * Injects auth guard for read operations.
319
350
  */
320
351
  injectForRead(db, model, args) {
321
- return __awaiter(this, void 0, void 0, function* () {
322
- const injected = {};
323
- if (!(yield this.injectAuthGuard(db, injected, model, 'read'))) {
324
- return false;
352
+ const injected = {};
353
+ if (!this.injectAuthGuard(db, injected, model, 'read')) {
354
+ return false;
355
+ }
356
+ if (args.where) {
357
+ // inject into relation fields:
358
+ // to-many: some/none/every
359
+ // to-one: direct-conditions/is/isNot
360
+ this.injectGuardForRelationFields(db, model, args.where, 'read');
361
+ }
362
+ if (injected.where && Object.keys(injected.where).length > 0 && !this.isTrue(injected.where)) {
363
+ if (!args.where) {
364
+ args.where = injected.where;
325
365
  }
326
- if (args.where) {
327
- // inject into relation fields:
328
- // to-many: some/none/every
329
- // to-one: direct-conditions/is/isNot
330
- yield this.injectGuardForRelationFields(db, model, args.where, 'read');
366
+ else {
367
+ this.mergeWhereClause(args.where, injected.where);
331
368
  }
332
- if (injected.where && Object.keys(injected.where).length > 0 && !this.isTrue(injected.where)) {
333
- if (!args.where) {
334
- args.where = injected.where;
335
- }
336
- else {
337
- this.mergeWhereClause(args.where, injected.where);
338
- }
369
+ }
370
+ // recursively inject read guard conditions into nested select, include, and _count
371
+ const hoistedConditions = this.injectNestedReadConditions(db, model, args);
372
+ // the injection process may generate conditions that need to be hoisted to the toplevel,
373
+ // if so, merge it with the existing where
374
+ if (hoistedConditions.length > 0) {
375
+ if (!args.where) {
376
+ args.where = this.and(...hoistedConditions);
339
377
  }
340
- // recursively inject read guard conditions into nested select, include, and _count
341
- const hoistedConditions = yield this.injectNestedReadConditions(db, model, args);
342
- // the injection process may generate conditions that need to be hoisted to the toplevel,
343
- // if so, merge it with the existing where
344
- if (hoistedConditions.length > 0) {
345
- if (!args.where) {
346
- args.where = this.and(...hoistedConditions);
347
- }
348
- else {
349
- this.mergeWhereClause(args.where, this.and(...hoistedConditions));
350
- }
378
+ else {
379
+ this.mergeWhereClause(args.where, this.and(...hoistedConditions));
351
380
  }
352
- return true;
353
- });
381
+ }
382
+ return true;
354
383
  }
355
384
  // flatten unique constraint filters
356
385
  flattenGeneratedUniqueField(model, args) {
@@ -382,130 +411,126 @@ class PolicyUtil {
382
411
  * Builds a reversed query for the given nested path.
383
412
  */
384
413
  buildReversedQuery(context) {
385
- return __awaiter(this, void 0, void 0, function* () {
386
- let result, currQuery;
387
- let currField;
388
- for (let i = context.nestingPath.length - 1; i >= 0; i--) {
389
- const { field, model, where } = context.nestingPath[i];
390
- // never modify the original where because it's shared in the structure
391
- const visitWhere = Object.assign({}, where);
392
- if (model && where) {
393
- // make sure composite unique condition is flattened
394
- this.flattenGeneratedUniqueField(model, visitWhere);
414
+ let result, currQuery;
415
+ let currField;
416
+ for (let i = context.nestingPath.length - 1; i >= 0; i--) {
417
+ const { field, model, where } = context.nestingPath[i];
418
+ // never modify the original where because it's shared in the structure
419
+ const visitWhere = Object.assign({}, where);
420
+ if (model && where) {
421
+ // make sure composite unique condition is flattened
422
+ this.flattenGeneratedUniqueField(model, visitWhere);
423
+ }
424
+ if (!result) {
425
+ // first segment (bottom), just use its where clause
426
+ result = currQuery = Object.assign({}, visitWhere);
427
+ currField = field;
428
+ }
429
+ else {
430
+ if (!currField) {
431
+ throw this.unknownError(`missing field in nested path`);
395
432
  }
396
- if (!result) {
397
- // first segment (bottom), just use its where clause
398
- result = currQuery = Object.assign({}, visitWhere);
399
- currField = field;
433
+ if (!currField.backLink) {
434
+ throw this.unknownError(`field ${currField.type}.${currField.name} doesn't have a backLink`);
435
+ }
436
+ const backLinkField = this.getModelField(currField.type, currField.backLink);
437
+ if (backLinkField === null || backLinkField === void 0 ? void 0 : backLinkField.isArray) {
438
+ // many-side of relationship, wrap with "some" query
439
+ currQuery[currField.backLink] = { some: Object.assign({}, visitWhere) };
400
440
  }
401
441
  else {
402
- if (!currField) {
403
- throw this.unknownError(`missing field in nested path`);
404
- }
405
- if (!currField.backLink) {
406
- throw this.unknownError(`field ${currField.type}.${currField.name} doesn't have a backLink`);
407
- }
408
- const backLinkField = this.getModelField(currField.type, currField.backLink);
409
- if (backLinkField === null || backLinkField === void 0 ? void 0 : backLinkField.isArray) {
410
- // many-side of relationship, wrap with "some" query
411
- currQuery[currField.backLink] = { some: Object.assign({}, visitWhere) };
412
- }
413
- else {
414
- if (where && backLinkField.isRelationOwner && backLinkField.foreignKeyMapping) {
415
- for (const [r, fk] of Object.entries(backLinkField.foreignKeyMapping)) {
416
- currQuery[fk] = visitWhere[r];
417
- }
418
- if (i > 0) {
419
- currQuery[currField.backLink] = {};
420
- }
442
+ if (where && backLinkField.isRelationOwner && backLinkField.foreignKeyMapping) {
443
+ for (const [r, fk] of Object.entries(backLinkField.foreignKeyMapping)) {
444
+ currQuery[fk] = visitWhere[r];
421
445
  }
422
- else {
423
- currQuery[currField.backLink] = Object.assign({}, visitWhere);
446
+ if (i > 0) {
447
+ currQuery[currField.backLink] = {};
424
448
  }
425
449
  }
426
- currQuery = currQuery[currField.backLink];
427
- currField = field;
450
+ else {
451
+ currQuery[currField.backLink] = Object.assign({}, visitWhere);
452
+ }
428
453
  }
454
+ currQuery = currQuery[currField.backLink];
455
+ currField = field;
429
456
  }
430
- return result;
431
- });
457
+ }
458
+ return result;
432
459
  }
433
460
  injectNestedReadConditions(db, model, args) {
434
461
  var _a;
435
- return __awaiter(this, void 0, void 0, function* () {
436
- const injectTarget = (_a = args.select) !== null && _a !== void 0 ? _a : args.include;
437
- if (!injectTarget) {
438
- return [];
439
- }
440
- if (injectTarget._count !== undefined) {
441
- // _count needs to respect read policies of related models
442
- if (injectTarget._count === true) {
443
- // include count for all relations, expand to all fields
444
- // so that we can inject guard conditions for each of them
445
- injectTarget._count = { select: {} };
446
- const modelFields = (0, model_meta_1.getFields)(this.modelMeta, model);
447
- if (modelFields) {
448
- for (const [k, v] of Object.entries(modelFields)) {
449
- if (v.isDataModel && v.isArray) {
450
- // create an entry for to-many relation
451
- injectTarget._count.select[k] = {};
452
- }
462
+ const injectTarget = (_a = args.select) !== null && _a !== void 0 ? _a : args.include;
463
+ if (!injectTarget) {
464
+ return [];
465
+ }
466
+ if (injectTarget._count !== undefined) {
467
+ // _count needs to respect read policies of related models
468
+ if (injectTarget._count === true) {
469
+ // include count for all relations, expand to all fields
470
+ // so that we can inject guard conditions for each of them
471
+ injectTarget._count = { select: {} };
472
+ const modelFields = (0, model_meta_1.getFields)(this.modelMeta, model);
473
+ if (modelFields) {
474
+ for (const [k, v] of Object.entries(modelFields)) {
475
+ if (v.isDataModel && v.isArray) {
476
+ // create an entry for to-many relation
477
+ injectTarget._count.select[k] = {};
453
478
  }
454
479
  }
455
480
  }
456
- // inject conditions for each relation
457
- for (const field of Object.keys(injectTarget._count.select)) {
458
- if (typeof injectTarget._count.select[field] !== 'object') {
459
- injectTarget._count.select[field] = {};
460
- }
461
- const fieldInfo = (0, model_meta_1.resolveField)(this.modelMeta, model, field);
462
- if (!fieldInfo) {
463
- continue;
464
- }
465
- // inject into the "where" clause inside select
466
- yield this.injectAuthGuard(db, injectTarget._count.select[field], fieldInfo.type, 'read');
467
- }
468
481
  }
469
- // collect filter conditions that should be hoisted to the toplevel
470
- const hoistedConditions = [];
471
- for (const field of (0, utils_1.getModelFields)(injectTarget)) {
482
+ // inject conditions for each relation
483
+ for (const field of Object.keys(injectTarget._count.select)) {
484
+ if (typeof injectTarget._count.select[field] !== 'object') {
485
+ injectTarget._count.select[field] = {};
486
+ }
472
487
  const fieldInfo = (0, model_meta_1.resolveField)(this.modelMeta, model, field);
473
- if (!fieldInfo || !fieldInfo.isDataModel) {
474
- // only care about relation fields
488
+ if (!fieldInfo) {
475
489
  continue;
476
490
  }
477
- let hoisted;
478
- if (fieldInfo.isArray ||
479
- // Injecting where at include/select level for nullable to-one relation is supported since Prisma 4.8.0
480
- // https://github.com/prisma/prisma/discussions/20350
481
- fieldInfo.isOptional) {
482
- if (typeof injectTarget[field] !== 'object') {
483
- injectTarget[field] = {};
484
- }
485
- // inject extra condition for to-many or nullable to-one relation
486
- yield this.injectAuthGuard(db, injectTarget[field], fieldInfo.type, 'read');
487
- // recurse
488
- const subHoisted = yield this.injectNestedReadConditions(db, fieldInfo.type, injectTarget[field]);
489
- if (subHoisted.length > 0) {
490
- // we can convert it to a where at this level
491
- injectTarget[field].where = this.and(injectTarget[field].where, ...subHoisted);
492
- }
491
+ // inject into the "where" clause inside select
492
+ this.injectAuthGuard(db, injectTarget._count.select[field], fieldInfo.type, 'read');
493
+ }
494
+ }
495
+ // collect filter conditions that should be hoisted to the toplevel
496
+ const hoistedConditions = [];
497
+ for (const field of (0, utils_1.getModelFields)(injectTarget)) {
498
+ const fieldInfo = (0, model_meta_1.resolveField)(this.modelMeta, model, field);
499
+ if (!fieldInfo || !fieldInfo.isDataModel) {
500
+ // only care about relation fields
501
+ continue;
502
+ }
503
+ let hoisted;
504
+ if (fieldInfo.isArray ||
505
+ // Injecting where at include/select level for nullable to-one relation is supported since Prisma 4.8.0
506
+ // https://github.com/prisma/prisma/discussions/20350
507
+ fieldInfo.isOptional) {
508
+ if (typeof injectTarget[field] !== 'object') {
509
+ injectTarget[field] = {};
493
510
  }
494
- else {
495
- // hoist non-nullable to-one filter to the parent level
496
- hoisted = this.getAuthGuard(db, fieldInfo.type, 'read');
497
- // recurse
498
- const subHoisted = yield this.injectNestedReadConditions(db, fieldInfo.type, injectTarget[field]);
499
- if (subHoisted.length > 0) {
500
- hoisted = this.and(hoisted, ...subHoisted);
501
- }
511
+ // inject extra condition for to-many or nullable to-one relation
512
+ this.injectAuthGuard(db, injectTarget[field], fieldInfo.type, 'read');
513
+ // recurse
514
+ const subHoisted = this.injectNestedReadConditions(db, fieldInfo.type, injectTarget[field]);
515
+ if (subHoisted.length > 0) {
516
+ // we can convert it to a where at this level
517
+ injectTarget[field].where = this.and(injectTarget[field].where, ...subHoisted);
502
518
  }
503
- if (hoisted && !this.isTrue(hoisted)) {
504
- hoistedConditions.push({ [field]: hoisted });
519
+ }
520
+ else {
521
+ // hoist non-nullable to-one filter to the parent level
522
+ hoisted = this.getAuthGuard(db, fieldInfo.type, 'read');
523
+ // recurse
524
+ const subHoisted = this.injectNestedReadConditions(db, fieldInfo.type, injectTarget[field]);
525
+ if (subHoisted.length > 0) {
526
+ hoisted = this.and(hoisted, ...subHoisted);
505
527
  }
506
528
  }
507
- return hoistedConditions;
508
- });
529
+ if (hoisted && !this.isTrue(hoisted)) {
530
+ hoistedConditions.push({ [field]: hoisted });
531
+ }
532
+ }
533
+ return hoistedConditions;
509
534
  }
510
535
  /**
511
536
  * Given a model and a unique filter, checks the operation is allowed by policies and field validations.
@@ -515,14 +540,14 @@ class PolicyUtil {
515
540
  return __awaiter(this, void 0, void 0, function* () {
516
541
  let guard = this.getAuthGuard(db, model, operation, preValue);
517
542
  if (this.isFalse(guard)) {
518
- throw this.deniedByPolicy(model, operation, `entity ${(0, utils_1.formatObject)(uniqueFilter)} failed policy check`);
543
+ throw this.deniedByPolicy(model, operation, `entity ${(0, utils_1.formatObject)(uniqueFilter)} failed policy check`, constants_1.CrudFailureReason.ACCESS_POLICY_VIOLATION);
519
544
  }
520
545
  if (operation === 'update' && args) {
521
546
  // merge field-level policy guards
522
547
  const fieldUpdateGuard = this.getFieldUpdateGuards(db, model, args);
523
548
  if (fieldUpdateGuard.rejectedByField) {
524
549
  // rejected
525
- throw this.deniedByPolicy(model, 'update', `entity ${(0, utils_1.formatObject)(uniqueFilter)} failed update policy check for field "${fieldUpdateGuard.rejectedByField}"`);
550
+ throw this.deniedByPolicy(model, 'update', `entity ${(0, utils_1.formatObject)(uniqueFilter)} failed update policy check for field "${fieldUpdateGuard.rejectedByField}"`, constants_1.CrudFailureReason.ACCESS_POLICY_VIOLATION);
526
551
  }
527
552
  else if (fieldUpdateGuard.guard) {
528
553
  // merge
@@ -551,7 +576,7 @@ class PolicyUtil {
551
576
  }
552
577
  const result = yield db[model].findFirst(query);
553
578
  if (!result) {
554
- throw this.deniedByPolicy(model, operation, `entity ${(0, utils_1.formatObject)(uniqueFilter)} failed policy check`);
579
+ throw this.deniedByPolicy(model, operation, `entity ${(0, utils_1.formatObject)(uniqueFilter)} failed policy check`, constants_1.CrudFailureReason.ACCESS_POLICY_VIOLATION);
555
580
  }
556
581
  if (schema) {
557
582
  // TODO: push down schema check to the database
@@ -561,7 +586,7 @@ class PolicyUtil {
561
586
  if (this.logger.enabled('info')) {
562
587
  this.logger.info(`entity ${model} failed validation for operation ${operation}: ${error}`);
563
588
  }
564
- throw this.deniedByPolicy(model, operation, `entities ${JSON.stringify(uniqueFilter)} failed validation: [${error}]`, constants_1.CrudFailureReason.DATA_VALIDATION_VIOLATION);
589
+ throw this.deniedByPolicy(model, operation, `entities ${JSON.stringify(uniqueFilter)} failed validation: [${error}]`, constants_1.CrudFailureReason.DATA_VALIDATION_VIOLATION, parseResult.error);
565
590
  }
566
591
  }
567
592
  });
@@ -587,7 +612,7 @@ class PolicyUtil {
587
612
  tryReject(db, model, operation) {
588
613
  const guard = this.getAuthGuard(db, model, operation);
589
614
  if (this.isFalse(guard)) {
590
- throw this.deniedByPolicy(model, operation);
615
+ throw this.deniedByPolicy(model, operation, undefined, constants_1.CrudFailureReason.ACCESS_POLICY_VIOLATION);
591
616
  }
592
617
  }
593
618
  /**
@@ -619,7 +644,7 @@ class PolicyUtil {
619
644
  this.flattenGeneratedUniqueField(model, uniqueFilter);
620
645
  const readArgs = { select: selectInclude.select, include: selectInclude.include, where: uniqueFilter };
621
646
  const error = this.deniedByPolicy(model, operation, 'result is not allowed to be read back', constants_1.CrudFailureReason.RESULT_NOT_READABLE);
622
- const injectResult = yield this.injectForRead(db, model, readArgs);
647
+ const injectResult = this.injectForRead(db, model, readArgs);
623
648
  if (!injectResult) {
624
649
  return { error, result: undefined };
625
650
  }
@@ -712,8 +737,15 @@ class PolicyUtil {
712
737
  }
713
738
  //#endregion
714
739
  //#region Errors
715
- deniedByPolicy(model, operation, extra, reason) {
716
- return (0, utils_1.prismaClientKnownRequestError)(this.db, `denied by policy: ${model} entities failed '${operation}' check${extra ? ', ' + extra : ''}`, { clientVersion: (0, version_1.getVersion)(), code: constants_1.PrismaErrorCode.CONSTRAINED_FAILED, meta: { reason } });
740
+ deniedByPolicy(model, operation, extra, reason, zodErrors) {
741
+ const args = { clientVersion: (0, version_1.getVersion)(), code: constants_1.PrismaErrorCode.CONSTRAINED_FAILED, meta: {} };
742
+ if (reason) {
743
+ args.meta.reason = reason;
744
+ }
745
+ if (zodErrors) {
746
+ args.meta.zodErrors = zodErrors;
747
+ }
748
+ return (0, utils_1.prismaClientKnownRequestError)(this.db, `denied by policy: ${model} entities failed '${operation}' check${extra ? ', ' + extra : ''}`, args);
717
749
  }
718
750
  notFound(model) {
719
751
  return (0, utils_1.prismaClientKnownRequestError)(this.db, `entity not found for model ${model}`, {
@@ -722,9 +754,7 @@ class PolicyUtil {
722
754
  });
723
755
  }
724
756
  validationError(message) {
725
- return (0, utils_1.prismaClientValidationError)(this.db, message, {
726
- clientVersion: (0, version_1.getVersion)(),
727
- });
757
+ return (0, utils_1.prismaClientValidationError)(this.db, message);
728
758
  }
729
759
  unknownError(message) {
730
760
  return (0, utils_1.prismaClientUnknownRequestError)(this.db, message, {
@@ -805,12 +835,6 @@ class PolicyUtil {
805
835
  if (typeof entityData !== 'object' || !entityData) {
806
836
  return;
807
837
  }
808
- // strip auxiliary fields
809
- for (const auxField of constants_1.AUXILIARY_FIELDS) {
810
- if (auxField in entityData) {
811
- delete entityData[auxField];
812
- }
813
- }
814
838
  for (const [field, fieldData] of Object.entries(entityData)) {
815
839
  if (fieldData === undefined) {
816
840
  continue;
@@ -859,6 +883,13 @@ class PolicyUtil {
859
883
  }
860
884
  }
861
885
  }
886
+ /**
887
+ * Gets information for all fields of a model.
888
+ */
889
+ getModelFields(model) {
890
+ model = (0, lower_case_first_1.lowerCaseFirst)(model);
891
+ return this.modelMeta.fields[model];
892
+ }
862
893
  /**
863
894
  * Gets information for a specific model field.
864
895
  */
@@ -902,7 +933,6 @@ class PolicyUtil {
902
933
  if (!where) {
903
934
  throw new Error('invalid where clause');
904
935
  }
905
- extra = this.reduce(extra);
906
936
  if (this.isTrue(extra)) {
907
937
  return;
908
938
  }