prisma-guard 1.18.1 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1087,13 +1087,9 @@ function collectEqualityFields(where, model, typeMap) {
1087
1087
  continue;
1088
1088
  if (typeMap && model && typeMap[model]?.[key]?.isRelation)
1089
1089
  continue;
1090
- if (value !== null && value !== void 0 && !isPlainObject(value) && !Array.isArray(value)) {
1091
- fields.add(key);
1092
- continue;
1093
- }
1094
1090
  if (!value || !isPlainObject(value))
1095
1091
  continue;
1096
- if (Object.keys(value).every((op) => op === "equals")) {
1092
+ if ("equals" in value) {
1097
1093
  fields.add(key);
1098
1094
  }
1099
1095
  }
@@ -1536,7 +1532,82 @@ function createWhereBuilder(typeMap, enumMap, scalarBase) {
1536
1532
  scalarConditions[fieldName] = fieldForced;
1537
1533
  }
1538
1534
  }
1539
- return { buildWhereSchema };
1535
+ function buildUniqueWhereSchema(model, whereConfig) {
1536
+ const modelFields = typeMap[model];
1537
+ if (!modelFields)
1538
+ throw new ShapeError(`Unknown model: ${model}`);
1539
+ const fieldSchemas = {};
1540
+ const forced = {};
1541
+ const forcedOnlyKeys = /* @__PURE__ */ new Set();
1542
+ for (const [key, value] of Object.entries(whereConfig)) {
1543
+ if (COMBINATOR_KEYS.has(key)) {
1544
+ throw new ShapeError(
1545
+ `Combinator "${key}" is not supported in unique where for model "${model}"`
1546
+ );
1547
+ }
1548
+ const fieldMeta = modelFields[key];
1549
+ if (!fieldMeta)
1550
+ throw new ShapeError(`Unknown field "${key}" on model "${model}"`);
1551
+ if (fieldMeta.isRelation)
1552
+ throw new ShapeError(
1553
+ `Relation field "${key}" cannot be used in unique where for model "${model}"`
1554
+ );
1555
+ const base = createBaseType(fieldMeta, enumMap, scalarBase);
1556
+ let directSchema;
1557
+ if (!fieldMeta.isEnum && !fieldMeta.isRelation && !fieldMeta.isUnsupported) {
1558
+ directSchema = wrapWithInputCoercion(
1559
+ fieldMeta.type,
1560
+ fieldMeta.isList,
1561
+ base
1562
+ );
1563
+ } else {
1564
+ directSchema = base;
1565
+ }
1566
+ const equalsWrapper = z4.object({ equals: directSchema }).strict().transform((v) => v.equals);
1567
+ if (value === true) {
1568
+ fieldSchemas[key] = z4.union([directSchema, equalsWrapper]);
1569
+ continue;
1570
+ }
1571
+ if (isPlainObject(value)) {
1572
+ const keys = Object.keys(value);
1573
+ if (keys.length === 1 && keys[0] === "equals") {
1574
+ const equalsVal = value.equals;
1575
+ if (equalsVal === true) {
1576
+ fieldSchemas[key] = z4.union([directSchema, equalsWrapper]);
1577
+ } else {
1578
+ const actual2 = isForcedValue(equalsVal) ? equalsVal.value : equalsVal;
1579
+ try {
1580
+ forced[key] = directSchema.parse(actual2);
1581
+ } catch (err) {
1582
+ throw new ShapeError(
1583
+ `Invalid forced value for unique where "${model}.${key}": ${err.message}`
1584
+ );
1585
+ }
1586
+ forcedOnlyKeys.add(key);
1587
+ }
1588
+ continue;
1589
+ }
1590
+ throw new ShapeError(
1591
+ `Unique where field "${key}" on model "${model}" only accepts true or { equals: true/value }. Got operators: ${keys.join(", ")}`
1592
+ );
1593
+ }
1594
+ const actual = isForcedValue(value) ? value.value : value;
1595
+ try {
1596
+ forced[key] = directSchema.parse(actual);
1597
+ } catch (err) {
1598
+ throw new ShapeError(
1599
+ `Invalid forced value for unique where "${model}.${key}": ${err.message}`
1600
+ );
1601
+ }
1602
+ forcedOnlyKeys.add(key);
1603
+ }
1604
+ return {
1605
+ schema: Object.keys(fieldSchemas).length > 0 ? z4.object(fieldSchemas).strict() : null,
1606
+ forced,
1607
+ forcedOnlyKeys
1608
+ };
1609
+ }
1610
+ return { buildWhereSchema, buildUniqueWhereSchema };
1540
1611
  }
1541
1612
 
1542
1613
  // src/runtime/query-builder-args.ts
@@ -2622,6 +2693,7 @@ function createQueryBuilder(typeMap, enumMap, uniqueMap, scalarBase) {
2622
2693
  buildQuerySchema,
2623
2694
  buildShapeZodSchema,
2624
2695
  buildWhereSchema: whereBuilder.buildWhereSchema,
2696
+ buildUniqueWhereSchema: whereBuilder.buildUniqueWhereSchema,
2625
2697
  buildIncludeSchema: projectionBuilder.buildIncludeSchema,
2626
2698
  buildSelectSchema: projectionBuilder.buildSelectSchema
2627
2699
  };
@@ -3767,17 +3839,6 @@ var PROJECTION_MUTATION_METHODS = /* @__PURE__ */ new Set([
3767
3839
  "updateManyAndReturn"
3768
3840
  ]);
3769
3841
  var BATCH_CREATE_METHODS = /* @__PURE__ */ new Set(["createMany", "createManyAndReturn"]);
3770
- function normalizeUniqueWhere(where) {
3771
- const result = {};
3772
- for (const [key, value] of Object.entries(where)) {
3773
- if (key !== "AND" && key !== "OR" && key !== "NOT" && isPlainObject(value) && "equals" in value && Object.keys(value).length === 1) {
3774
- result[key] = value.equals;
3775
- } else {
3776
- result[key] = value;
3777
- }
3778
- }
3779
- return result;
3780
- }
3781
3842
  function buildDefaultSelectInput(config) {
3782
3843
  const result = {};
3783
3844
  for (const [key, value] of Object.entries(config)) {
@@ -3997,6 +4058,36 @@ function createModelGuardExtension(config) {
3997
4058
  return;
3998
4059
  validateUniqueEquality(modelName, shape.where, method, uniqueMap, typeMap);
3999
4060
  }
4061
+ function validateUniqueWhereShapeConfig(modelName, where, method) {
4062
+ const constraints = uniqueMap[modelName];
4063
+ if (!constraints || constraints.length === 0)
4064
+ return;
4065
+ const equalityFields = /* @__PURE__ */ new Set();
4066
+ for (const [key, value] of Object.entries(where)) {
4067
+ if (key === "AND" || key === "OR" || key === "NOT")
4068
+ continue;
4069
+ if (value === true) {
4070
+ equalityFields.add(key);
4071
+ continue;
4072
+ }
4073
+ if (isPlainObject(value) && "equals" in value) {
4074
+ equalityFields.add(key);
4075
+ continue;
4076
+ }
4077
+ if (value !== null && value !== void 0) {
4078
+ equalityFields.add(key);
4079
+ }
4080
+ }
4081
+ const valid = constraints.some(
4082
+ (constraint) => constraint.every((field) => equalityFields.has(field))
4083
+ );
4084
+ if (!valid) {
4085
+ const constraintDesc = constraints.map((c) => `(${c.join(", ")})`).join(" | ");
4086
+ throw new ShapeError(
4087
+ `${method} on model "${modelName}" requires where to cover a unique constraint with equality operators only: ${constraintDesc}`
4088
+ );
4089
+ }
4090
+ }
4000
4091
  function createGuardedMethods(modelName, modelDelegate, input, explicitCaller) {
4001
4092
  function callDelegate(method, args) {
4002
4093
  if (typeof modelDelegate[method] !== "function") {
@@ -4019,6 +4110,7 @@ function createModelGuardExtension(config) {
4019
4110
  const dataSchemaCache = /* @__PURE__ */ new Map();
4020
4111
  const whereBuiltCache = /* @__PURE__ */ new Map();
4021
4112
  const projectionCache = /* @__PURE__ */ new Map();
4113
+ const uniqueWhereCache = /* @__PURE__ */ new Map();
4022
4114
  function getReadShape(method, queryShape, matchedKey, wasDynamic) {
4023
4115
  if (!wasDynamic) {
4024
4116
  const cacheKey = `${method}\0${matchedKey}`;
@@ -4076,6 +4168,18 @@ function createModelGuardExtension(config) {
4076
4168
  }
4077
4169
  return queryBuilder.buildWhereSchema(modelName, whereConfig);
4078
4170
  }
4171
+ function getUniqueWhereBuilt(whereConfig, matchedKey, wasDynamic) {
4172
+ if (!wasDynamic) {
4173
+ const cacheKey = `unique\0${matchedKey}`;
4174
+ const cached = uniqueWhereCache.get(cacheKey);
4175
+ if (cached)
4176
+ return cached;
4177
+ const built = queryBuilder.buildUniqueWhereSchema(modelName, whereConfig);
4178
+ uniqueWhereCache.set(cacheKey, built);
4179
+ return built;
4180
+ }
4181
+ return queryBuilder.buildUniqueWhereSchema(modelName, whereConfig);
4182
+ }
4079
4183
  function buildProjectionSchema(shape) {
4080
4184
  const schemaFields = {};
4081
4185
  let forcedIncludeTree = {};
@@ -4226,6 +4330,49 @@ function createModelGuardExtension(config) {
4226
4330
  }
4227
4331
  return where;
4228
4332
  }
4333
+ function buildUniqueWhereFromShape(shape, bodyWhere, matchedKey, wasDynamic) {
4334
+ if (!shape.where)
4335
+ return {};
4336
+ const built = getUniqueWhereBuilt(shape.where, matchedKey, wasDynamic);
4337
+ let result = {};
4338
+ if (built.schema) {
4339
+ if (bodyWhere !== void 0 && bodyWhere !== null) {
4340
+ if (!isPlainObject(bodyWhere)) {
4341
+ throw new ShapeError("Unique where must be an object");
4342
+ }
4343
+ const sanitized = { ...bodyWhere };
4344
+ for (const key of built.forcedOnlyKeys) {
4345
+ delete sanitized[key];
4346
+ }
4347
+ result = built.schema.parse(sanitized);
4348
+ }
4349
+ } else if (bodyWhere !== void 0 && bodyWhere !== null) {
4350
+ if (isPlainObject(bodyWhere)) {
4351
+ const hasOnlyForcedKeys = built.forcedOnlyKeys.size > 0 && Object.keys(bodyWhere).every((k) => built.forcedOnlyKeys.has(k));
4352
+ if (!hasOnlyForcedKeys && Object.keys(bodyWhere).length > 0) {
4353
+ throw new ShapeError(
4354
+ "Unique where shape contains only forced values. Client where input is not accepted."
4355
+ );
4356
+ }
4357
+ }
4358
+ }
4359
+ for (const [key, value] of Object.entries(built.forced)) {
4360
+ result[key] = value;
4361
+ }
4362
+ return result;
4363
+ }
4364
+ function requireUniqueWhere(shape, bodyWhere, method, matchedKey, wasDynamic) {
4365
+ const where = buildUniqueWhereFromShape(
4366
+ shape,
4367
+ bodyWhere,
4368
+ matchedKey,
4369
+ wasDynamic
4370
+ );
4371
+ if (Object.keys(where).length === 0) {
4372
+ throw new ShapeError(`${method} requires a where condition`);
4373
+ }
4374
+ return where;
4375
+ }
4229
4376
  function buildEffectiveReadBody(resolved) {
4230
4377
  const hasShapeProjection = !!resolved.shape.select || !!resolved.shape.include;
4231
4378
  if (!hasShapeProjection)
@@ -4375,7 +4522,6 @@ function createModelGuardExtension(config) {
4375
4522
  `Guard shape requires "where" for ${method} to prevent unconstrained bulk mutations`
4376
4523
  );
4377
4524
  }
4378
- maybeValidateUniqueWhere(modelName, resolved.shape, method);
4379
4525
  const dataSchema = getDataSchema(
4380
4526
  "update",
4381
4527
  resolved.shape.data,
@@ -4387,28 +4533,37 @@ function createModelGuardExtension(config) {
4387
4533
  dataSchema,
4388
4534
  method
4389
4535
  );
4390
- let where = isUniqueWhere ? requireWhere(
4391
- resolved.shape,
4392
- resolved.body.where,
4393
- method,
4394
- true,
4395
- resolved.matchedKey,
4396
- resolved.wasDynamic
4397
- ) : buildWhereFromShape(
4398
- resolved.shape,
4399
- resolved.body.where,
4400
- false,
4401
- resolved.matchedKey,
4402
- resolved.wasDynamic
4403
- );
4404
- if (isBulk && Object.keys(where).length === 0) {
4405
- throw new ShapeError(
4406
- `${method} requires at least one where condition`
4407
- );
4408
- }
4536
+ let where;
4409
4537
  if (isUniqueWhere) {
4538
+ if (resolved.shape.where) {
4539
+ validateUniqueWhereShapeConfig(
4540
+ modelName,
4541
+ resolved.shape.where,
4542
+ method
4543
+ );
4544
+ }
4545
+ where = requireUniqueWhere(
4546
+ resolved.shape,
4547
+ resolved.body.where,
4548
+ method,
4549
+ resolved.matchedKey,
4550
+ resolved.wasDynamic
4551
+ );
4410
4552
  validateResolvedUniqueWhere(modelName, where, method, uniqueMap);
4411
- where = normalizeUniqueWhere(where);
4553
+ } else {
4554
+ maybeValidateUniqueWhere(modelName, resolved.shape, method);
4555
+ where = buildWhereFromShape(
4556
+ resolved.shape,
4557
+ resolved.body.where,
4558
+ false,
4559
+ resolved.matchedKey,
4560
+ resolved.wasDynamic
4561
+ );
4562
+ if (isBulk && Object.keys(where).length === 0) {
4563
+ throw new ShapeError(
4564
+ `${method} requires at least one where condition`
4565
+ );
4566
+ }
4412
4567
  }
4413
4568
  const args = { data, where };
4414
4569
  if (supportsProjection) {
@@ -4446,29 +4601,37 @@ function createModelGuardExtension(config) {
4446
4601
  `Guard shape requires "where" for ${method} to prevent unconstrained bulk mutations`
4447
4602
  );
4448
4603
  }
4449
- maybeValidateUniqueWhere(modelName, resolved.shape, method);
4450
- let where = isUniqueWhere ? requireWhere(
4451
- resolved.shape,
4452
- resolved.body.where,
4453
- method,
4454
- true,
4455
- resolved.matchedKey,
4456
- resolved.wasDynamic
4457
- ) : buildWhereFromShape(
4458
- resolved.shape,
4459
- resolved.body.where,
4460
- false,
4461
- resolved.matchedKey,
4462
- resolved.wasDynamic
4463
- );
4464
- if (isBulk && Object.keys(where).length === 0) {
4465
- throw new ShapeError(
4466
- `${method} requires at least one where condition`
4467
- );
4468
- }
4604
+ let where;
4469
4605
  if (isUniqueWhere) {
4606
+ if (resolved.shape.where) {
4607
+ validateUniqueWhereShapeConfig(
4608
+ modelName,
4609
+ resolved.shape.where,
4610
+ method
4611
+ );
4612
+ }
4613
+ where = requireUniqueWhere(
4614
+ resolved.shape,
4615
+ resolved.body.where,
4616
+ method,
4617
+ resolved.matchedKey,
4618
+ resolved.wasDynamic
4619
+ );
4470
4620
  validateResolvedUniqueWhere(modelName, where, method, uniqueMap);
4471
- where = normalizeUniqueWhere(where);
4621
+ } else {
4622
+ maybeValidateUniqueWhere(modelName, resolved.shape, method);
4623
+ where = buildWhereFromShape(
4624
+ resolved.shape,
4625
+ resolved.body.where,
4626
+ false,
4627
+ resolved.matchedKey,
4628
+ resolved.wasDynamic
4629
+ );
4630
+ if (isBulk && Object.keys(where).length === 0) {
4631
+ throw new ShapeError(
4632
+ `${method} requires at least one where condition`
4633
+ );
4634
+ }
4472
4635
  }
4473
4636
  const args = { where };
4474
4637
  if (supportsProjection) {
@@ -4512,7 +4675,11 @@ function createModelGuardExtension(config) {
4512
4675
  ALLOWED_BODY_KEYS_UPSERT,
4513
4676
  "upsert"
4514
4677
  );
4515
- maybeValidateUniqueWhere(modelName, resolved.shape, "upsert");
4678
+ validateUniqueWhereShapeConfig(
4679
+ modelName,
4680
+ resolved.shape.where,
4681
+ "upsert"
4682
+ );
4516
4683
  const fks = modelScopeFks.get(modelName) ?? /* @__PURE__ */ new Set();
4517
4684
  validateCreateCompleteness(
4518
4685
  modelName,
@@ -4543,17 +4710,16 @@ function createModelGuardExtension(config) {
4543
4710
  updateDataSchema,
4544
4711
  "upsert (update)"
4545
4712
  );
4546
- const where = requireWhere(
4713
+ const where = requireUniqueWhere(
4547
4714
  resolved.shape,
4548
4715
  resolved.body.where,
4549
4716
  "upsert",
4550
- true,
4551
4717
  resolved.matchedKey,
4552
4718
  resolved.wasDynamic
4553
4719
  );
4554
4720
  validateResolvedUniqueWhere(modelName, where, "upsert", uniqueMap);
4555
4721
  const args = {
4556
- where: normalizeUniqueWhere(where),
4722
+ where,
4557
4723
  create: createData,
4558
4724
  update: updateData
4559
4725
  };