prisma-guard 1.19.0 → 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.
@@ -1451,13 +1451,6 @@ function createWhereBuilder(typeMap, enumMap, scalarBase) {
1451
1451
  }
1452
1452
  }
1453
1453
  function processScalarField(fieldName, operators, model, fieldMeta, fieldSchemas, scalarConditions) {
1454
- if (operators === true) {
1455
- const supportedOps = getSupportedOperators(fieldMeta);
1456
- const expanded = {};
1457
- for (const op of supportedOps)
1458
- expanded[op] = true;
1459
- operators = expanded;
1460
- }
1461
1454
  if (!isPlainObject(operators)) {
1462
1455
  throw new ShapeError(
1463
1456
  `Where config for scalar field "${fieldName}" on model "${model}" must be an object of operators`
@@ -1570,7 +1563,82 @@ function createWhereBuilder(typeMap, enumMap, scalarBase) {
1570
1563
  scalarConditions[fieldName] = fieldForced;
1571
1564
  }
1572
1565
  }
1573
- 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 };
1574
1642
  }
1575
1643
 
1576
1644
  // src/runtime/query-builder-args.ts
@@ -2656,6 +2724,7 @@ function createQueryBuilder(typeMap, enumMap, uniqueMap, scalarBase) {
2656
2724
  buildQuerySchema,
2657
2725
  buildShapeZodSchema,
2658
2726
  buildWhereSchema: whereBuilder.buildWhereSchema,
2727
+ buildUniqueWhereSchema: whereBuilder.buildUniqueWhereSchema,
2659
2728
  buildIncludeSchema: projectionBuilder.buildIncludeSchema,
2660
2729
  buildSelectSchema: projectionBuilder.buildSelectSchema
2661
2730
  };
@@ -3801,17 +3870,6 @@ var PROJECTION_MUTATION_METHODS = /* @__PURE__ */ new Set([
3801
3870
  "updateManyAndReturn"
3802
3871
  ]);
3803
3872
  var BATCH_CREATE_METHODS = /* @__PURE__ */ new Set(["createMany", "createManyAndReturn"]);
3804
- function normalizeUniqueWhere(where) {
3805
- const result = {};
3806
- for (const [key, value] of Object.entries(where)) {
3807
- if (key !== "AND" && key !== "OR" && key !== "NOT" && isPlainObject(value) && "equals" in value && Object.keys(value).length === 1) {
3808
- result[key] = value.equals;
3809
- } else {
3810
- result[key] = value;
3811
- }
3812
- }
3813
- return result;
3814
- }
3815
3873
  function buildDefaultSelectInput(config) {
3816
3874
  const result = {};
3817
3875
  for (const [key, value] of Object.entries(config)) {
@@ -4031,6 +4089,36 @@ function createModelGuardExtension(config) {
4031
4089
  return;
4032
4090
  validateUniqueEquality(modelName, shape.where, method, uniqueMap, typeMap);
4033
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
+ }
4034
4122
  function createGuardedMethods(modelName, modelDelegate, input, explicitCaller) {
4035
4123
  function callDelegate(method, args) {
4036
4124
  if (typeof modelDelegate[method] !== "function") {
@@ -4053,6 +4141,7 @@ function createModelGuardExtension(config) {
4053
4141
  const dataSchemaCache = /* @__PURE__ */ new Map();
4054
4142
  const whereBuiltCache = /* @__PURE__ */ new Map();
4055
4143
  const projectionCache = /* @__PURE__ */ new Map();
4144
+ const uniqueWhereCache = /* @__PURE__ */ new Map();
4056
4145
  function getReadShape(method, queryShape, matchedKey, wasDynamic) {
4057
4146
  if (!wasDynamic) {
4058
4147
  const cacheKey = `${method}\0${matchedKey}`;
@@ -4110,6 +4199,18 @@ function createModelGuardExtension(config) {
4110
4199
  }
4111
4200
  return queryBuilder.buildWhereSchema(modelName, whereConfig);
4112
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
+ }
4113
4214
  function buildProjectionSchema(shape) {
4114
4215
  const schemaFields = {};
4115
4216
  let forcedIncludeTree = {};
@@ -4260,6 +4361,49 @@ function createModelGuardExtension(config) {
4260
4361
  }
4261
4362
  return where;
4262
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
+ }
4263
4407
  function buildEffectiveReadBody(resolved) {
4264
4408
  const hasShapeProjection = !!resolved.shape.select || !!resolved.shape.include;
4265
4409
  if (!hasShapeProjection)
@@ -4409,7 +4553,6 @@ function createModelGuardExtension(config) {
4409
4553
  `Guard shape requires "where" for ${method} to prevent unconstrained bulk mutations`
4410
4554
  );
4411
4555
  }
4412
- maybeValidateUniqueWhere(modelName, resolved.shape, method);
4413
4556
  const dataSchema = getDataSchema(
4414
4557
  "update",
4415
4558
  resolved.shape.data,
@@ -4421,28 +4564,37 @@ function createModelGuardExtension(config) {
4421
4564
  dataSchema,
4422
4565
  method
4423
4566
  );
4424
- let where = isUniqueWhere ? requireWhere(
4425
- resolved.shape,
4426
- resolved.body.where,
4427
- method,
4428
- true,
4429
- resolved.matchedKey,
4430
- resolved.wasDynamic
4431
- ) : buildWhereFromShape(
4432
- resolved.shape,
4433
- resolved.body.where,
4434
- false,
4435
- resolved.matchedKey,
4436
- resolved.wasDynamic
4437
- );
4438
- if (isBulk && Object.keys(where).length === 0) {
4439
- throw new ShapeError(
4440
- `${method} requires at least one where condition`
4441
- );
4442
- }
4567
+ let where;
4443
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
+ );
4444
4583
  validateResolvedUniqueWhere(modelName, where, method, uniqueMap);
4445
- 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
+ }
4446
4598
  }
4447
4599
  const args = { data, where };
4448
4600
  if (supportsProjection) {
@@ -4480,29 +4632,37 @@ function createModelGuardExtension(config) {
4480
4632
  `Guard shape requires "where" for ${method} to prevent unconstrained bulk mutations`
4481
4633
  );
4482
4634
  }
4483
- maybeValidateUniqueWhere(modelName, resolved.shape, method);
4484
- let where = isUniqueWhere ? requireWhere(
4485
- resolved.shape,
4486
- resolved.body.where,
4487
- method,
4488
- true,
4489
- resolved.matchedKey,
4490
- resolved.wasDynamic
4491
- ) : buildWhereFromShape(
4492
- resolved.shape,
4493
- resolved.body.where,
4494
- false,
4495
- resolved.matchedKey,
4496
- resolved.wasDynamic
4497
- );
4498
- if (isBulk && Object.keys(where).length === 0) {
4499
- throw new ShapeError(
4500
- `${method} requires at least one where condition`
4501
- );
4502
- }
4635
+ let where;
4503
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
+ );
4504
4651
  validateResolvedUniqueWhere(modelName, where, method, uniqueMap);
4505
- 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
+ }
4506
4666
  }
4507
4667
  const args = { where };
4508
4668
  if (supportsProjection) {
@@ -4546,7 +4706,11 @@ function createModelGuardExtension(config) {
4546
4706
  ALLOWED_BODY_KEYS_UPSERT,
4547
4707
  "upsert"
4548
4708
  );
4549
- maybeValidateUniqueWhere(modelName, resolved.shape, "upsert");
4709
+ validateUniqueWhereShapeConfig(
4710
+ modelName,
4711
+ resolved.shape.where,
4712
+ "upsert"
4713
+ );
4550
4714
  const fks = modelScopeFks.get(modelName) ?? /* @__PURE__ */ new Set();
4551
4715
  validateCreateCompleteness(
4552
4716
  modelName,
@@ -4577,17 +4741,16 @@ function createModelGuardExtension(config) {
4577
4741
  updateDataSchema,
4578
4742
  "upsert (update)"
4579
4743
  );
4580
- const where = requireWhere(
4744
+ const where = requireUniqueWhere(
4581
4745
  resolved.shape,
4582
4746
  resolved.body.where,
4583
4747
  "upsert",
4584
- true,
4585
4748
  resolved.matchedKey,
4586
4749
  resolved.wasDynamic
4587
4750
  );
4588
4751
  validateResolvedUniqueWhere(modelName, where, "upsert", uniqueMap);
4589
4752
  const args = {
4590
- where: normalizeUniqueWhere(where),
4753
+ where,
4591
4754
  create: createData,
4592
4755
  update: updateData
4593
4756
  };