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.
@@ -1118,13 +1118,9 @@ function collectEqualityFields(where, model, typeMap) {
1118
1118
  continue;
1119
1119
  if (typeMap && model && typeMap[model]?.[key]?.isRelation)
1120
1120
  continue;
1121
- if (value !== null && value !== void 0 && !isPlainObject(value) && !Array.isArray(value)) {
1122
- fields.add(key);
1123
- continue;
1124
- }
1125
1121
  if (!value || !isPlainObject(value))
1126
1122
  continue;
1127
- if (Object.keys(value).every((op) => op === "equals")) {
1123
+ if ("equals" in value) {
1128
1124
  fields.add(key);
1129
1125
  }
1130
1126
  }
@@ -1567,7 +1563,82 @@ function createWhereBuilder(typeMap, enumMap, scalarBase) {
1567
1563
  scalarConditions[fieldName] = fieldForced;
1568
1564
  }
1569
1565
  }
1570
- return { buildWhereSchema };
1566
+ function buildUniqueWhereSchema(model, whereConfig) {
1567
+ const modelFields = typeMap[model];
1568
+ if (!modelFields)
1569
+ throw new ShapeError(`Unknown model: ${model}`);
1570
+ const fieldSchemas = {};
1571
+ const forced = {};
1572
+ const forcedOnlyKeys = /* @__PURE__ */ new Set();
1573
+ for (const [key, value] of Object.entries(whereConfig)) {
1574
+ if (COMBINATOR_KEYS.has(key)) {
1575
+ throw new ShapeError(
1576
+ `Combinator "${key}" is not supported in unique where for model "${model}"`
1577
+ );
1578
+ }
1579
+ const fieldMeta = modelFields[key];
1580
+ if (!fieldMeta)
1581
+ throw new ShapeError(`Unknown field "${key}" on model "${model}"`);
1582
+ if (fieldMeta.isRelation)
1583
+ throw new ShapeError(
1584
+ `Relation field "${key}" cannot be used in unique where for model "${model}"`
1585
+ );
1586
+ const base = createBaseType(fieldMeta, enumMap, scalarBase);
1587
+ let directSchema;
1588
+ if (!fieldMeta.isEnum && !fieldMeta.isRelation && !fieldMeta.isUnsupported) {
1589
+ directSchema = wrapWithInputCoercion(
1590
+ fieldMeta.type,
1591
+ fieldMeta.isList,
1592
+ base
1593
+ );
1594
+ } else {
1595
+ directSchema = base;
1596
+ }
1597
+ const equalsWrapper = import_zod4.z.object({ equals: directSchema }).strict().transform((v) => v.equals);
1598
+ if (value === true) {
1599
+ fieldSchemas[key] = import_zod4.z.union([directSchema, equalsWrapper]);
1600
+ continue;
1601
+ }
1602
+ if (isPlainObject(value)) {
1603
+ const keys = Object.keys(value);
1604
+ if (keys.length === 1 && keys[0] === "equals") {
1605
+ const equalsVal = value.equals;
1606
+ if (equalsVal === true) {
1607
+ fieldSchemas[key] = import_zod4.z.union([directSchema, equalsWrapper]);
1608
+ } else {
1609
+ const actual2 = isForcedValue(equalsVal) ? equalsVal.value : equalsVal;
1610
+ try {
1611
+ forced[key] = directSchema.parse(actual2);
1612
+ } catch (err) {
1613
+ throw new ShapeError(
1614
+ `Invalid forced value for unique where "${model}.${key}": ${err.message}`
1615
+ );
1616
+ }
1617
+ forcedOnlyKeys.add(key);
1618
+ }
1619
+ continue;
1620
+ }
1621
+ throw new ShapeError(
1622
+ `Unique where field "${key}" on model "${model}" only accepts true or { equals: true/value }. Got operators: ${keys.join(", ")}`
1623
+ );
1624
+ }
1625
+ const actual = isForcedValue(value) ? value.value : value;
1626
+ try {
1627
+ forced[key] = directSchema.parse(actual);
1628
+ } catch (err) {
1629
+ throw new ShapeError(
1630
+ `Invalid forced value for unique where "${model}.${key}": ${err.message}`
1631
+ );
1632
+ }
1633
+ forcedOnlyKeys.add(key);
1634
+ }
1635
+ return {
1636
+ schema: Object.keys(fieldSchemas).length > 0 ? import_zod4.z.object(fieldSchemas).strict() : null,
1637
+ forced,
1638
+ forcedOnlyKeys
1639
+ };
1640
+ }
1641
+ return { buildWhereSchema, buildUniqueWhereSchema };
1571
1642
  }
1572
1643
 
1573
1644
  // src/runtime/query-builder-args.ts
@@ -2653,6 +2724,7 @@ function createQueryBuilder(typeMap, enumMap, uniqueMap, scalarBase) {
2653
2724
  buildQuerySchema,
2654
2725
  buildShapeZodSchema,
2655
2726
  buildWhereSchema: whereBuilder.buildWhereSchema,
2727
+ buildUniqueWhereSchema: whereBuilder.buildUniqueWhereSchema,
2656
2728
  buildIncludeSchema: projectionBuilder.buildIncludeSchema,
2657
2729
  buildSelectSchema: projectionBuilder.buildSelectSchema
2658
2730
  };
@@ -3798,17 +3870,6 @@ var PROJECTION_MUTATION_METHODS = /* @__PURE__ */ new Set([
3798
3870
  "updateManyAndReturn"
3799
3871
  ]);
3800
3872
  var BATCH_CREATE_METHODS = /* @__PURE__ */ new Set(["createMany", "createManyAndReturn"]);
3801
- function normalizeUniqueWhere(where) {
3802
- const result = {};
3803
- for (const [key, value] of Object.entries(where)) {
3804
- if (key !== "AND" && key !== "OR" && key !== "NOT" && isPlainObject(value) && "equals" in value && Object.keys(value).length === 1) {
3805
- result[key] = value.equals;
3806
- } else {
3807
- result[key] = value;
3808
- }
3809
- }
3810
- return result;
3811
- }
3812
3873
  function buildDefaultSelectInput(config) {
3813
3874
  const result = {};
3814
3875
  for (const [key, value] of Object.entries(config)) {
@@ -4028,6 +4089,36 @@ function createModelGuardExtension(config) {
4028
4089
  return;
4029
4090
  validateUniqueEquality(modelName, shape.where, method, uniqueMap, typeMap);
4030
4091
  }
4092
+ function validateUniqueWhereShapeConfig(modelName, where, method) {
4093
+ const constraints = uniqueMap[modelName];
4094
+ if (!constraints || constraints.length === 0)
4095
+ return;
4096
+ const equalityFields = /* @__PURE__ */ new Set();
4097
+ for (const [key, value] of Object.entries(where)) {
4098
+ if (key === "AND" || key === "OR" || key === "NOT")
4099
+ continue;
4100
+ if (value === true) {
4101
+ equalityFields.add(key);
4102
+ continue;
4103
+ }
4104
+ if (isPlainObject(value) && "equals" in value) {
4105
+ equalityFields.add(key);
4106
+ continue;
4107
+ }
4108
+ if (value !== null && value !== void 0) {
4109
+ equalityFields.add(key);
4110
+ }
4111
+ }
4112
+ const valid = constraints.some(
4113
+ (constraint) => constraint.every((field) => equalityFields.has(field))
4114
+ );
4115
+ if (!valid) {
4116
+ const constraintDesc = constraints.map((c) => `(${c.join(", ")})`).join(" | ");
4117
+ throw new ShapeError(
4118
+ `${method} on model "${modelName}" requires where to cover a unique constraint with equality operators only: ${constraintDesc}`
4119
+ );
4120
+ }
4121
+ }
4031
4122
  function createGuardedMethods(modelName, modelDelegate, input, explicitCaller) {
4032
4123
  function callDelegate(method, args) {
4033
4124
  if (typeof modelDelegate[method] !== "function") {
@@ -4050,6 +4141,7 @@ function createModelGuardExtension(config) {
4050
4141
  const dataSchemaCache = /* @__PURE__ */ new Map();
4051
4142
  const whereBuiltCache = /* @__PURE__ */ new Map();
4052
4143
  const projectionCache = /* @__PURE__ */ new Map();
4144
+ const uniqueWhereCache = /* @__PURE__ */ new Map();
4053
4145
  function getReadShape(method, queryShape, matchedKey, wasDynamic) {
4054
4146
  if (!wasDynamic) {
4055
4147
  const cacheKey = `${method}\0${matchedKey}`;
@@ -4107,6 +4199,18 @@ function createModelGuardExtension(config) {
4107
4199
  }
4108
4200
  return queryBuilder.buildWhereSchema(modelName, whereConfig);
4109
4201
  }
4202
+ function getUniqueWhereBuilt(whereConfig, matchedKey, wasDynamic) {
4203
+ if (!wasDynamic) {
4204
+ const cacheKey = `unique\0${matchedKey}`;
4205
+ const cached = uniqueWhereCache.get(cacheKey);
4206
+ if (cached)
4207
+ return cached;
4208
+ const built = queryBuilder.buildUniqueWhereSchema(modelName, whereConfig);
4209
+ uniqueWhereCache.set(cacheKey, built);
4210
+ return built;
4211
+ }
4212
+ return queryBuilder.buildUniqueWhereSchema(modelName, whereConfig);
4213
+ }
4110
4214
  function buildProjectionSchema(shape) {
4111
4215
  const schemaFields = {};
4112
4216
  let forcedIncludeTree = {};
@@ -4257,6 +4361,49 @@ function createModelGuardExtension(config) {
4257
4361
  }
4258
4362
  return where;
4259
4363
  }
4364
+ function buildUniqueWhereFromShape(shape, bodyWhere, matchedKey, wasDynamic) {
4365
+ if (!shape.where)
4366
+ return {};
4367
+ const built = getUniqueWhereBuilt(shape.where, matchedKey, wasDynamic);
4368
+ let result = {};
4369
+ if (built.schema) {
4370
+ if (bodyWhere !== void 0 && bodyWhere !== null) {
4371
+ if (!isPlainObject(bodyWhere)) {
4372
+ throw new ShapeError("Unique where must be an object");
4373
+ }
4374
+ const sanitized = { ...bodyWhere };
4375
+ for (const key of built.forcedOnlyKeys) {
4376
+ delete sanitized[key];
4377
+ }
4378
+ result = built.schema.parse(sanitized);
4379
+ }
4380
+ } else if (bodyWhere !== void 0 && bodyWhere !== null) {
4381
+ if (isPlainObject(bodyWhere)) {
4382
+ const hasOnlyForcedKeys = built.forcedOnlyKeys.size > 0 && Object.keys(bodyWhere).every((k) => built.forcedOnlyKeys.has(k));
4383
+ if (!hasOnlyForcedKeys && Object.keys(bodyWhere).length > 0) {
4384
+ throw new ShapeError(
4385
+ "Unique where shape contains only forced values. Client where input is not accepted."
4386
+ );
4387
+ }
4388
+ }
4389
+ }
4390
+ for (const [key, value] of Object.entries(built.forced)) {
4391
+ result[key] = value;
4392
+ }
4393
+ return result;
4394
+ }
4395
+ function requireUniqueWhere(shape, bodyWhere, method, matchedKey, wasDynamic) {
4396
+ const where = buildUniqueWhereFromShape(
4397
+ shape,
4398
+ bodyWhere,
4399
+ matchedKey,
4400
+ wasDynamic
4401
+ );
4402
+ if (Object.keys(where).length === 0) {
4403
+ throw new ShapeError(`${method} requires a where condition`);
4404
+ }
4405
+ return where;
4406
+ }
4260
4407
  function buildEffectiveReadBody(resolved) {
4261
4408
  const hasShapeProjection = !!resolved.shape.select || !!resolved.shape.include;
4262
4409
  if (!hasShapeProjection)
@@ -4406,7 +4553,6 @@ function createModelGuardExtension(config) {
4406
4553
  `Guard shape requires "where" for ${method} to prevent unconstrained bulk mutations`
4407
4554
  );
4408
4555
  }
4409
- maybeValidateUniqueWhere(modelName, resolved.shape, method);
4410
4556
  const dataSchema = getDataSchema(
4411
4557
  "update",
4412
4558
  resolved.shape.data,
@@ -4418,28 +4564,37 @@ function createModelGuardExtension(config) {
4418
4564
  dataSchema,
4419
4565
  method
4420
4566
  );
4421
- let where = isUniqueWhere ? requireWhere(
4422
- resolved.shape,
4423
- resolved.body.where,
4424
- method,
4425
- true,
4426
- resolved.matchedKey,
4427
- resolved.wasDynamic
4428
- ) : buildWhereFromShape(
4429
- resolved.shape,
4430
- resolved.body.where,
4431
- false,
4432
- resolved.matchedKey,
4433
- resolved.wasDynamic
4434
- );
4435
- if (isBulk && Object.keys(where).length === 0) {
4436
- throw new ShapeError(
4437
- `${method} requires at least one where condition`
4438
- );
4439
- }
4567
+ let where;
4440
4568
  if (isUniqueWhere) {
4569
+ if (resolved.shape.where) {
4570
+ validateUniqueWhereShapeConfig(
4571
+ modelName,
4572
+ resolved.shape.where,
4573
+ method
4574
+ );
4575
+ }
4576
+ where = requireUniqueWhere(
4577
+ resolved.shape,
4578
+ resolved.body.where,
4579
+ method,
4580
+ resolved.matchedKey,
4581
+ resolved.wasDynamic
4582
+ );
4441
4583
  validateResolvedUniqueWhere(modelName, where, method, uniqueMap);
4442
- where = normalizeUniqueWhere(where);
4584
+ } else {
4585
+ maybeValidateUniqueWhere(modelName, resolved.shape, method);
4586
+ where = buildWhereFromShape(
4587
+ resolved.shape,
4588
+ resolved.body.where,
4589
+ false,
4590
+ resolved.matchedKey,
4591
+ resolved.wasDynamic
4592
+ );
4593
+ if (isBulk && Object.keys(where).length === 0) {
4594
+ throw new ShapeError(
4595
+ `${method} requires at least one where condition`
4596
+ );
4597
+ }
4443
4598
  }
4444
4599
  const args = { data, where };
4445
4600
  if (supportsProjection) {
@@ -4477,29 +4632,37 @@ function createModelGuardExtension(config) {
4477
4632
  `Guard shape requires "where" for ${method} to prevent unconstrained bulk mutations`
4478
4633
  );
4479
4634
  }
4480
- maybeValidateUniqueWhere(modelName, resolved.shape, method);
4481
- let where = isUniqueWhere ? requireWhere(
4482
- resolved.shape,
4483
- resolved.body.where,
4484
- method,
4485
- true,
4486
- resolved.matchedKey,
4487
- resolved.wasDynamic
4488
- ) : buildWhereFromShape(
4489
- resolved.shape,
4490
- resolved.body.where,
4491
- false,
4492
- resolved.matchedKey,
4493
- resolved.wasDynamic
4494
- );
4495
- if (isBulk && Object.keys(where).length === 0) {
4496
- throw new ShapeError(
4497
- `${method} requires at least one where condition`
4498
- );
4499
- }
4635
+ let where;
4500
4636
  if (isUniqueWhere) {
4637
+ if (resolved.shape.where) {
4638
+ validateUniqueWhereShapeConfig(
4639
+ modelName,
4640
+ resolved.shape.where,
4641
+ method
4642
+ );
4643
+ }
4644
+ where = requireUniqueWhere(
4645
+ resolved.shape,
4646
+ resolved.body.where,
4647
+ method,
4648
+ resolved.matchedKey,
4649
+ resolved.wasDynamic
4650
+ );
4501
4651
  validateResolvedUniqueWhere(modelName, where, method, uniqueMap);
4502
- where = normalizeUniqueWhere(where);
4652
+ } else {
4653
+ maybeValidateUniqueWhere(modelName, resolved.shape, method);
4654
+ where = buildWhereFromShape(
4655
+ resolved.shape,
4656
+ resolved.body.where,
4657
+ false,
4658
+ resolved.matchedKey,
4659
+ resolved.wasDynamic
4660
+ );
4661
+ if (isBulk && Object.keys(where).length === 0) {
4662
+ throw new ShapeError(
4663
+ `${method} requires at least one where condition`
4664
+ );
4665
+ }
4503
4666
  }
4504
4667
  const args = { where };
4505
4668
  if (supportsProjection) {
@@ -4543,7 +4706,11 @@ function createModelGuardExtension(config) {
4543
4706
  ALLOWED_BODY_KEYS_UPSERT,
4544
4707
  "upsert"
4545
4708
  );
4546
- maybeValidateUniqueWhere(modelName, resolved.shape, "upsert");
4709
+ validateUniqueWhereShapeConfig(
4710
+ modelName,
4711
+ resolved.shape.where,
4712
+ "upsert"
4713
+ );
4547
4714
  const fks = modelScopeFks.get(modelName) ?? /* @__PURE__ */ new Set();
4548
4715
  validateCreateCompleteness(
4549
4716
  modelName,
@@ -4574,17 +4741,16 @@ function createModelGuardExtension(config) {
4574
4741
  updateDataSchema,
4575
4742
  "upsert (update)"
4576
4743
  );
4577
- const where = requireWhere(
4744
+ const where = requireUniqueWhere(
4578
4745
  resolved.shape,
4579
4746
  resolved.body.where,
4580
4747
  "upsert",
4581
- true,
4582
4748
  resolved.matchedKey,
4583
4749
  resolved.wasDynamic
4584
4750
  );
4585
4751
  validateResolvedUniqueWhere(modelName, where, "upsert", uniqueMap);
4586
4752
  const args = {
4587
- where: normalizeUniqueWhere(where),
4753
+ where,
4588
4754
  create: createData,
4589
4755
  update: updateData
4590
4756
  };