prisma-guard 1.29.0 → 1.31.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.
@@ -307,6 +307,9 @@ function isPlainObject(v) {
307
307
  const proto = Object.getPrototypeOf(v);
308
308
  return proto === Object.prototype || proto === null;
309
309
  }
310
+ function isObjectLike(v) {
311
+ return typeof v === "object" && v !== null && !Array.isArray(v);
312
+ }
310
313
  function schemaProducesValueForUndefined(schema) {
311
314
  const result = schema.safeParse(void 0);
312
315
  return result.success && result.data !== void 0;
@@ -4138,12 +4141,39 @@ function validateAllowedKeys(value, allowed, method, kind) {
4138
4141
  return `Shape key "${key}" not valid for ${method}. Allowed: ${[...allowed].join(", ")}`;
4139
4142
  });
4140
4143
  }
4144
+ var RELATION_OPS_COVERING_FK = /* @__PURE__ */ new Set([
4145
+ "connect",
4146
+ "connectOrCreate",
4147
+ "create"
4148
+ ]);
4149
+ function collectRelationCoveredFks(modelFields, dataConfig) {
4150
+ const covered = /* @__PURE__ */ new Set();
4151
+ for (const [fieldName, value] of Object.entries(dataConfig)) {
4152
+ const meta = modelFields[fieldName];
4153
+ if (!meta || !meta.isRelation)
4154
+ continue;
4155
+ const fks = meta.relationFromFields;
4156
+ if (!fks || fks.length === 0)
4157
+ continue;
4158
+ if (!isPlainObject(value))
4159
+ continue;
4160
+ const covers = Object.keys(value).some(
4161
+ (op) => RELATION_OPS_COVERING_FK.has(op)
4162
+ );
4163
+ if (!covers)
4164
+ continue;
4165
+ for (const fk of fks)
4166
+ covered.add(fk);
4167
+ }
4168
+ return covered;
4169
+ }
4141
4170
  function validateCreateCompleteness(modelName, dataConfig, typeMap, scopeFks, zodDefaults) {
4142
4171
  const modelFields = typeMap[modelName];
4143
4172
  if (!modelFields)
4144
4173
  return;
4145
4174
  const zodDefaultFields = zodDefaults[modelName];
4146
4175
  const zodDefaultSet = zodDefaultFields ? new Set(zodDefaultFields) : void 0;
4176
+ const relationCoveredFks = collectRelationCoveredFks(modelFields, dataConfig);
4147
4177
  for (const [fieldName, meta] of Object.entries(modelFields)) {
4148
4178
  if (meta.isRelation)
4149
4179
  continue;
@@ -4157,10 +4187,12 @@ function validateCreateCompleteness(modelName, dataConfig, typeMap, scopeFks, zo
4157
4187
  continue;
4158
4188
  if (scopeFks.has(fieldName))
4159
4189
  continue;
4190
+ if (relationCoveredFks.has(fieldName))
4191
+ continue;
4160
4192
  if (zodDefaultSet && zodDefaultSet.has(fieldName))
4161
4193
  continue;
4162
4194
  throw new ShapeError(
4163
- `Required field "${fieldName}" on model "${modelName}" is missing from create data shape, has no default, and is not a scope FK`
4195
+ `Required field "${fieldName}" on model "${modelName}" is missing from create data shape, has no default, is not a scope FK, and is not covered by a relation write in the shape`
4164
4196
  );
4165
4197
  }
4166
4198
  }
@@ -4880,12 +4912,23 @@ function walkForClientContent(obj, predicate, depth) {
4880
4912
  const nested = value;
4881
4913
  if (nested.orderBy || nested.cursor || nested.take || nested.skip)
4882
4914
  return true;
4883
- if (nested.where && isPlainObject(nested.where) && hasClientControlledValues(nested.where, depth + 1)) {
4915
+ if (nested.where && isPlainObject(nested.where) && hasClientControlledValues(
4916
+ nested.where,
4917
+ depth + 1
4918
+ )) {
4884
4919
  return true;
4885
4920
  }
4886
- if (nested.include && walkForClientContent(nested.include, predicate, depth + 1))
4921
+ if (nested.include && walkForClientContent(
4922
+ nested.include,
4923
+ predicate,
4924
+ depth + 1
4925
+ ))
4887
4926
  return true;
4888
- if (nested.select && walkForClientContent(nested.select, predicate, depth + 1))
4927
+ if (nested.select && walkForClientContent(
4928
+ nested.select,
4929
+ predicate,
4930
+ depth + 1
4931
+ ))
4889
4932
  return true;
4890
4933
  }
4891
4934
  return false;
@@ -4907,11 +4950,19 @@ function hasClientControlledValues(obj, depth = 0) {
4907
4950
  function hasNestedClientControlledArgs(shape) {
4908
4951
  const predicate = () => false;
4909
4952
  if (shape.include) {
4910
- if (walkForClientContent(shape.include, predicate, 0))
4953
+ if (walkForClientContent(
4954
+ shape.include,
4955
+ predicate,
4956
+ 0
4957
+ ))
4911
4958
  return true;
4912
4959
  }
4913
4960
  if (shape.select) {
4914
- if (walkForClientContent(shape.select, predicate, 0))
4961
+ if (walkForClientContent(
4962
+ shape.select,
4963
+ predicate,
4964
+ 0
4965
+ ))
4915
4966
  return true;
4916
4967
  }
4917
4968
  return false;
@@ -5209,15 +5260,13 @@ function createModelGuardExtension(config) {
5209
5260
  let result = {};
5210
5261
  if (built.schema) {
5211
5262
  if (bodyWhere !== void 0 && bodyWhere !== null) {
5212
- if (!isPlainObject(bodyWhere)) {
5263
+ if (!isObjectLike(bodyWhere)) {
5213
5264
  throw new ShapeError(
5214
- `Invalid "where" on model "${modelName}": unique where must be a plain object`
5265
+ `Invalid "where" on model "${modelName}": unique where must be an object`
5215
5266
  );
5216
5267
  }
5217
- const sanitized = hasWhereForced(built.forced) ? stripUniqueWhereForcedInput(
5218
- bodyWhere,
5219
- built.forced
5220
- ) : { ...bodyWhere };
5268
+ const bodyWhereObj = { ...bodyWhere };
5269
+ const sanitized = hasWhereForced(built.forced) ? stripUniqueWhereForcedInput(bodyWhereObj, built.forced) : bodyWhereObj;
5221
5270
  try {
5222
5271
  result = built.schema.parse(sanitized);
5223
5272
  } catch (err) {
@@ -5229,11 +5278,9 @@ function createModelGuardExtension(config) {
5229
5278
  }
5230
5279
  }
5231
5280
  } else if (bodyWhere !== void 0 && bodyWhere !== null) {
5232
- if (isPlainObject(bodyWhere)) {
5233
- const sanitized = hasWhereForced(built.forced) ? stripUniqueWhereForcedInput(
5234
- bodyWhere,
5235
- built.forced
5236
- ) : { ...bodyWhere };
5281
+ if (isObjectLike(bodyWhere)) {
5282
+ const bodyWhereObj = { ...bodyWhere };
5283
+ const sanitized = hasWhereForced(built.forced) ? stripUniqueWhereForcedInput(bodyWhereObj, built.forced) : bodyWhereObj;
5237
5284
  if (Object.keys(sanitized).length > 0) {
5238
5285
  throw new ShapeError(
5239
5286
  `Unique where on model "${modelName}" contains only forced values. Client where input is not accepted.`
@@ -5241,7 +5288,7 @@ function createModelGuardExtension(config) {
5241
5288
  }
5242
5289
  } else {
5243
5290
  throw new ShapeError(
5244
- `Invalid "where" on model "${modelName}": unique where must be a plain object`
5291
+ `Invalid "where" on model "${modelName}": unique where must be an object`
5245
5292
  );
5246
5293
  }
5247
5294
  }
@@ -5594,12 +5641,7 @@ function createModelGuardExtension(config) {
5594
5641
  "upsert",
5595
5642
  "shape"
5596
5643
  );
5597
- validateAllowedKeys(
5598
- resolved.body,
5599
- allowedBodyKeys,
5600
- "upsert",
5601
- "body"
5602
- );
5644
+ validateAllowedKeys(resolved.body, allowedBodyKeys, "upsert", "body");
5603
5645
  validateUniqueWhereShapeConfig(
5604
5646
  modelName,
5605
5647
  resolved.shape.where,