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.
- package/dist/runtime/index.cjs +229 -63
- package/dist/runtime/index.cjs.map +1 -1
- package/dist/runtime/index.js +229 -63
- package/dist/runtime/index.js.map +1 -1
- package/package.json +1 -1
package/dist/runtime/index.cjs
CHANGED
|
@@ -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 (
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
4753
|
+
where,
|
|
4588
4754
|
create: createData,
|
|
4589
4755
|
update: updateData
|
|
4590
4756
|
};
|