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.
@@ -13,6 +13,7 @@ interface FieldMeta {
13
13
  isEnum?: boolean;
14
14
  isUnique?: boolean;
15
15
  isUnsupported?: boolean;
16
+ relationFromFields?: readonly string[];
16
17
  }
17
18
  interface GuardLogger {
18
19
  warn(message: string): void;
@@ -228,6 +229,7 @@ interface FieldMetaConst {
228
229
  readonly isEnum?: boolean;
229
230
  readonly isUnique?: boolean;
230
231
  readonly isUnsupported?: boolean;
232
+ readonly relationFromFields?: readonly string[];
231
233
  }
232
234
  interface UniqueConstraintConst {
233
235
  readonly selector: string;
@@ -13,6 +13,7 @@ interface FieldMeta {
13
13
  isEnum?: boolean;
14
14
  isUnique?: boolean;
15
15
  isUnsupported?: boolean;
16
+ relationFromFields?: readonly string[];
16
17
  }
17
18
  interface GuardLogger {
18
19
  warn(message: string): void;
@@ -228,6 +229,7 @@ interface FieldMetaConst {
228
229
  readonly isEnum?: boolean;
229
230
  readonly isUnique?: boolean;
230
231
  readonly isUnsupported?: boolean;
232
+ readonly relationFromFields?: readonly string[];
231
233
  }
232
234
  interface UniqueConstraintConst {
233
235
  readonly selector: string;
@@ -276,6 +276,9 @@ function isPlainObject(v) {
276
276
  const proto = Object.getPrototypeOf(v);
277
277
  return proto === Object.prototype || proto === null;
278
278
  }
279
+ function isObjectLike(v) {
280
+ return typeof v === "object" && v !== null && !Array.isArray(v);
281
+ }
279
282
  function schemaProducesValueForUndefined(schema) {
280
283
  const result = schema.safeParse(void 0);
281
284
  return result.success && result.data !== void 0;
@@ -4107,12 +4110,39 @@ function validateAllowedKeys(value, allowed, method, kind) {
4107
4110
  return `Shape key "${key}" not valid for ${method}. Allowed: ${[...allowed].join(", ")}`;
4108
4111
  });
4109
4112
  }
4113
+ var RELATION_OPS_COVERING_FK = /* @__PURE__ */ new Set([
4114
+ "connect",
4115
+ "connectOrCreate",
4116
+ "create"
4117
+ ]);
4118
+ function collectRelationCoveredFks(modelFields, dataConfig) {
4119
+ const covered = /* @__PURE__ */ new Set();
4120
+ for (const [fieldName, value] of Object.entries(dataConfig)) {
4121
+ const meta = modelFields[fieldName];
4122
+ if (!meta || !meta.isRelation)
4123
+ continue;
4124
+ const fks = meta.relationFromFields;
4125
+ if (!fks || fks.length === 0)
4126
+ continue;
4127
+ if (!isPlainObject(value))
4128
+ continue;
4129
+ const covers = Object.keys(value).some(
4130
+ (op) => RELATION_OPS_COVERING_FK.has(op)
4131
+ );
4132
+ if (!covers)
4133
+ continue;
4134
+ for (const fk of fks)
4135
+ covered.add(fk);
4136
+ }
4137
+ return covered;
4138
+ }
4110
4139
  function validateCreateCompleteness(modelName, dataConfig, typeMap, scopeFks, zodDefaults) {
4111
4140
  const modelFields = typeMap[modelName];
4112
4141
  if (!modelFields)
4113
4142
  return;
4114
4143
  const zodDefaultFields = zodDefaults[modelName];
4115
4144
  const zodDefaultSet = zodDefaultFields ? new Set(zodDefaultFields) : void 0;
4145
+ const relationCoveredFks = collectRelationCoveredFks(modelFields, dataConfig);
4116
4146
  for (const [fieldName, meta] of Object.entries(modelFields)) {
4117
4147
  if (meta.isRelation)
4118
4148
  continue;
@@ -4126,10 +4156,12 @@ function validateCreateCompleteness(modelName, dataConfig, typeMap, scopeFks, zo
4126
4156
  continue;
4127
4157
  if (scopeFks.has(fieldName))
4128
4158
  continue;
4159
+ if (relationCoveredFks.has(fieldName))
4160
+ continue;
4129
4161
  if (zodDefaultSet && zodDefaultSet.has(fieldName))
4130
4162
  continue;
4131
4163
  throw new ShapeError(
4132
- `Required field "${fieldName}" on model "${modelName}" is missing from create data shape, has no default, and is not a scope FK`
4164
+ `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`
4133
4165
  );
4134
4166
  }
4135
4167
  }
@@ -4849,12 +4881,23 @@ function walkForClientContent(obj, predicate, depth) {
4849
4881
  const nested = value;
4850
4882
  if (nested.orderBy || nested.cursor || nested.take || nested.skip)
4851
4883
  return true;
4852
- if (nested.where && isPlainObject(nested.where) && hasClientControlledValues(nested.where, depth + 1)) {
4884
+ if (nested.where && isPlainObject(nested.where) && hasClientControlledValues(
4885
+ nested.where,
4886
+ depth + 1
4887
+ )) {
4853
4888
  return true;
4854
4889
  }
4855
- if (nested.include && walkForClientContent(nested.include, predicate, depth + 1))
4890
+ if (nested.include && walkForClientContent(
4891
+ nested.include,
4892
+ predicate,
4893
+ depth + 1
4894
+ ))
4856
4895
  return true;
4857
- if (nested.select && walkForClientContent(nested.select, predicate, depth + 1))
4896
+ if (nested.select && walkForClientContent(
4897
+ nested.select,
4898
+ predicate,
4899
+ depth + 1
4900
+ ))
4858
4901
  return true;
4859
4902
  }
4860
4903
  return false;
@@ -4876,11 +4919,19 @@ function hasClientControlledValues(obj, depth = 0) {
4876
4919
  function hasNestedClientControlledArgs(shape) {
4877
4920
  const predicate = () => false;
4878
4921
  if (shape.include) {
4879
- if (walkForClientContent(shape.include, predicate, 0))
4922
+ if (walkForClientContent(
4923
+ shape.include,
4924
+ predicate,
4925
+ 0
4926
+ ))
4880
4927
  return true;
4881
4928
  }
4882
4929
  if (shape.select) {
4883
- if (walkForClientContent(shape.select, predicate, 0))
4930
+ if (walkForClientContent(
4931
+ shape.select,
4932
+ predicate,
4933
+ 0
4934
+ ))
4884
4935
  return true;
4885
4936
  }
4886
4937
  return false;
@@ -5178,15 +5229,13 @@ function createModelGuardExtension(config) {
5178
5229
  let result = {};
5179
5230
  if (built.schema) {
5180
5231
  if (bodyWhere !== void 0 && bodyWhere !== null) {
5181
- if (!isPlainObject(bodyWhere)) {
5232
+ if (!isObjectLike(bodyWhere)) {
5182
5233
  throw new ShapeError(
5183
- `Invalid "where" on model "${modelName}": unique where must be a plain object`
5234
+ `Invalid "where" on model "${modelName}": unique where must be an object`
5184
5235
  );
5185
5236
  }
5186
- const sanitized = hasWhereForced(built.forced) ? stripUniqueWhereForcedInput(
5187
- bodyWhere,
5188
- built.forced
5189
- ) : { ...bodyWhere };
5237
+ const bodyWhereObj = { ...bodyWhere };
5238
+ const sanitized = hasWhereForced(built.forced) ? stripUniqueWhereForcedInput(bodyWhereObj, built.forced) : bodyWhereObj;
5190
5239
  try {
5191
5240
  result = built.schema.parse(sanitized);
5192
5241
  } catch (err) {
@@ -5198,11 +5247,9 @@ function createModelGuardExtension(config) {
5198
5247
  }
5199
5248
  }
5200
5249
  } else if (bodyWhere !== void 0 && bodyWhere !== null) {
5201
- if (isPlainObject(bodyWhere)) {
5202
- const sanitized = hasWhereForced(built.forced) ? stripUniqueWhereForcedInput(
5203
- bodyWhere,
5204
- built.forced
5205
- ) : { ...bodyWhere };
5250
+ if (isObjectLike(bodyWhere)) {
5251
+ const bodyWhereObj = { ...bodyWhere };
5252
+ const sanitized = hasWhereForced(built.forced) ? stripUniqueWhereForcedInput(bodyWhereObj, built.forced) : bodyWhereObj;
5206
5253
  if (Object.keys(sanitized).length > 0) {
5207
5254
  throw new ShapeError(
5208
5255
  `Unique where on model "${modelName}" contains only forced values. Client where input is not accepted.`
@@ -5210,7 +5257,7 @@ function createModelGuardExtension(config) {
5210
5257
  }
5211
5258
  } else {
5212
5259
  throw new ShapeError(
5213
- `Invalid "where" on model "${modelName}": unique where must be a plain object`
5260
+ `Invalid "where" on model "${modelName}": unique where must be an object`
5214
5261
  );
5215
5262
  }
5216
5263
  }
@@ -5563,12 +5610,7 @@ function createModelGuardExtension(config) {
5563
5610
  "upsert",
5564
5611
  "shape"
5565
5612
  );
5566
- validateAllowedKeys(
5567
- resolved.body,
5568
- allowedBodyKeys,
5569
- "upsert",
5570
- "body"
5571
- );
5613
+ validateAllowedKeys(resolved.body, allowedBodyKeys, "upsert", "body");
5572
5614
  validateUniqueWhereShapeConfig(
5573
5615
  modelName,
5574
5616
  resolved.shape.where,