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.
- package/README.md +130 -5
- package/dist/generator/index.js +6 -0
- package/dist/generator/index.js.map +1 -1
- package/dist/runtime/index.cjs +66 -24
- package/dist/runtime/index.cjs.map +1 -1
- package/dist/runtime/index.d.cts +2 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +66 -24
- package/dist/runtime/index.js.map +1 -1
- package/package.json +1 -1
package/dist/runtime/index.d.cts
CHANGED
|
@@ -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;
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -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;
|
package/dist/runtime/index.js
CHANGED
|
@@ -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
|
|
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(
|
|
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(
|
|
4890
|
+
if (nested.include && walkForClientContent(
|
|
4891
|
+
nested.include,
|
|
4892
|
+
predicate,
|
|
4893
|
+
depth + 1
|
|
4894
|
+
))
|
|
4856
4895
|
return true;
|
|
4857
|
-
if (nested.select && walkForClientContent(
|
|
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(
|
|
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(
|
|
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 (!
|
|
5232
|
+
if (!isObjectLike(bodyWhere)) {
|
|
5182
5233
|
throw new ShapeError(
|
|
5183
|
-
`Invalid "where" on model "${modelName}": unique where must be
|
|
5234
|
+
`Invalid "where" on model "${modelName}": unique where must be an object`
|
|
5184
5235
|
);
|
|
5185
5236
|
}
|
|
5186
|
-
const
|
|
5187
|
-
|
|
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 (
|
|
5202
|
-
const
|
|
5203
|
-
|
|
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
|
|
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,
|