prisma-guard 1.30.0 → 1.32.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 +190 -15
- package/dist/generator/index.js +6 -0
- package/dist/generator/index.js.map +1 -1
- package/dist/runtime/index.cjs +157 -103
- 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 +157 -103
- package/dist/runtime/index.js.map +1 -1
- package/package.json +1 -1
package/dist/runtime/index.cjs
CHANGED
|
@@ -975,40 +975,75 @@ function unsupported() {
|
|
|
975
975
|
return marker;
|
|
976
976
|
}
|
|
977
977
|
|
|
978
|
-
// src/shared/
|
|
979
|
-
function
|
|
980
|
-
|
|
981
|
-
return null;
|
|
982
|
-
if (patterns.includes(caller))
|
|
983
|
-
return caller;
|
|
978
|
+
// src/shared/guard-variant-routing.ts
|
|
979
|
+
function matchVariantPatterns(keys, caller) {
|
|
980
|
+
const callerParts = caller.split("/");
|
|
984
981
|
const matches = [];
|
|
985
|
-
for (const pattern of
|
|
982
|
+
for (const pattern of keys) {
|
|
986
983
|
if (!pattern.includes(":"))
|
|
987
984
|
continue;
|
|
988
985
|
const patternParts = pattern.split("/");
|
|
989
|
-
const callerParts = caller.split("/");
|
|
990
986
|
if (patternParts.length !== callerParts.length)
|
|
991
987
|
continue;
|
|
992
|
-
let
|
|
993
|
-
for (let
|
|
994
|
-
if (patternParts[
|
|
988
|
+
let matchesCaller = true;
|
|
989
|
+
for (let index = 0; index < patternParts.length; index++) {
|
|
990
|
+
if (patternParts[index].startsWith(":"))
|
|
995
991
|
continue;
|
|
996
|
-
if (patternParts[
|
|
997
|
-
|
|
992
|
+
if (patternParts[index] !== callerParts[index]) {
|
|
993
|
+
matchesCaller = false;
|
|
998
994
|
break;
|
|
999
995
|
}
|
|
1000
996
|
}
|
|
1001
|
-
if (
|
|
997
|
+
if (matchesCaller)
|
|
1002
998
|
matches.push(pattern);
|
|
1003
999
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1000
|
+
return matches;
|
|
1001
|
+
}
|
|
1002
|
+
function resolveGuardVariantKey(input) {
|
|
1003
|
+
if (input.kind === "single") {
|
|
1004
|
+
return { ok: true, key: "_default" };
|
|
1005
|
+
}
|
|
1006
|
+
const { keys, caller, reservedKeys } = input;
|
|
1007
|
+
for (const key of keys) {
|
|
1008
|
+
if (reservedKeys.has(key)) {
|
|
1009
|
+
return {
|
|
1010
|
+
ok: false,
|
|
1011
|
+
code: "reserved-key",
|
|
1012
|
+
key,
|
|
1013
|
+
keys
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
const hasDefault = keys.includes("default");
|
|
1018
|
+
if (typeof caller !== "string") {
|
|
1019
|
+
if (hasDefault)
|
|
1020
|
+
return { ok: true, key: "default" };
|
|
1021
|
+
return { ok: false, code: "missing-caller", keys };
|
|
1022
|
+
}
|
|
1023
|
+
if (caller.trim().length === 0) {
|
|
1024
|
+
if (hasDefault)
|
|
1025
|
+
return { ok: true, key: "default" };
|
|
1026
|
+
return { ok: false, code: "unknown-caller", caller, keys };
|
|
1027
|
+
}
|
|
1028
|
+
if (keys.includes(caller)) {
|
|
1029
|
+
return { ok: true, key: caller };
|
|
1030
|
+
}
|
|
1031
|
+
const matches = matchVariantPatterns(keys, caller);
|
|
1032
|
+
if (matches.length === 1) {
|
|
1033
|
+
return { ok: true, key: matches[0] };
|
|
1034
|
+
}
|
|
1006
1035
|
if (matches.length > 1) {
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1036
|
+
return {
|
|
1037
|
+
ok: false,
|
|
1038
|
+
code: "ambiguous-caller",
|
|
1039
|
+
caller,
|
|
1040
|
+
keys,
|
|
1041
|
+
matches
|
|
1042
|
+
};
|
|
1010
1043
|
}
|
|
1011
|
-
|
|
1044
|
+
if (hasDefault)
|
|
1045
|
+
return { ok: true, key: "default" };
|
|
1046
|
+
return { ok: false, code: "unknown-caller", caller, keys };
|
|
1012
1047
|
}
|
|
1013
1048
|
|
|
1014
1049
|
// src/runtime/policy.ts
|
|
@@ -3441,22 +3476,27 @@ function createQueryBuilder(typeMap, enumMap, uniqueMap, scalarBase) {
|
|
|
3441
3476
|
forcedSelectCountWhere
|
|
3442
3477
|
};
|
|
3443
3478
|
}
|
|
3444
|
-
function
|
|
3445
|
-
const
|
|
3446
|
-
if (
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
built = buildShapeZodSchema(model, method, resolved);
|
|
3456
|
-
} else {
|
|
3457
|
-
built = builtCache.get("default");
|
|
3479
|
+
function throwQueryVariantResolution(resolution) {
|
|
3480
|
+
const keys = resolution.keys.map((key) => `"${key}"`).join(", ");
|
|
3481
|
+
if (resolution.code === "reserved-key") {
|
|
3482
|
+
throw new ShapeError(
|
|
3483
|
+
`Caller key "${resolution.key}" collides with reserved shape config key. Rename the caller path.`
|
|
3484
|
+
);
|
|
3485
|
+
}
|
|
3486
|
+
if (resolution.code === "missing-caller") {
|
|
3487
|
+
throw new CallerError(
|
|
3488
|
+
`Missing caller. This query uses named shape routing with keys: ${keys}. Provide caller via opts.caller.`
|
|
3489
|
+
);
|
|
3458
3490
|
}
|
|
3459
|
-
|
|
3491
|
+
if (resolution.code === "ambiguous-caller") {
|
|
3492
|
+
const matches = (resolution.matches ?? []).map((pattern) => `"${pattern}"`).join(", ");
|
|
3493
|
+
throw new CallerError(
|
|
3494
|
+
`Ambiguous caller "${resolution.caller}" matches multiple patterns: ${matches}`
|
|
3495
|
+
);
|
|
3496
|
+
}
|
|
3497
|
+
throw new CallerError(
|
|
3498
|
+
`Unknown caller: "${resolution.caller}". Allowed: ${keys}`
|
|
3499
|
+
);
|
|
3460
3500
|
}
|
|
3461
3501
|
function buildQuerySchema(model, method, config) {
|
|
3462
3502
|
const isSingle = typeof config === "function" || isShapeConfig(config);
|
|
@@ -3468,12 +3508,15 @@ function createQueryBuilder(typeMap, enumMap, uniqueMap, scalarBase) {
|
|
|
3468
3508
|
);
|
|
3469
3509
|
}
|
|
3470
3510
|
if (!isSingle) {
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3511
|
+
const keys = Object.keys(config);
|
|
3512
|
+
const validation = resolveGuardVariantKey({
|
|
3513
|
+
kind: "named",
|
|
3514
|
+
keys,
|
|
3515
|
+
caller: void 0,
|
|
3516
|
+
reservedKeys: SHAPE_CONFIG_KEYS
|
|
3517
|
+
});
|
|
3518
|
+
if (!validation.ok && validation.code === "reserved-key") {
|
|
3519
|
+
throwQueryVariantResolution(validation);
|
|
3477
3520
|
}
|
|
3478
3521
|
for (const [key, shapeOrFn] of Object.entries(
|
|
3479
3522
|
config
|
|
@@ -3511,47 +3554,20 @@ function createQueryBuilder(typeMap, enumMap, uniqueMap, scalarBase) {
|
|
|
3511
3554
|
);
|
|
3512
3555
|
}
|
|
3513
3556
|
const namedConfig = config;
|
|
3514
|
-
const
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
);
|
|
3526
|
-
}
|
|
3527
|
-
const allowed = Object.keys(namedConfig);
|
|
3528
|
-
throw new CallerError(
|
|
3529
|
-
`Missing caller. This query uses named shape routing with keys: ${allowed.map((k) => `"${k}"`).join(", ")}. Provide caller via opts.caller.`
|
|
3530
|
-
);
|
|
3531
|
-
}
|
|
3532
|
-
const matched = matchCaller(namedConfig, caller);
|
|
3533
|
-
if (!matched) {
|
|
3534
|
-
if ("default" in namedConfig) {
|
|
3535
|
-
return resolveDefaultShape(
|
|
3536
|
-
namedConfig,
|
|
3537
|
-
model,
|
|
3538
|
-
method,
|
|
3539
|
-
normalizedBody,
|
|
3540
|
-
opts,
|
|
3541
|
-
builtCache,
|
|
3542
|
-
isUnique
|
|
3543
|
-
);
|
|
3544
|
-
}
|
|
3545
|
-
const allowed = Object.keys(namedConfig);
|
|
3546
|
-
throw new CallerError(
|
|
3547
|
-
`Unknown caller: "${caller}". Allowed: ${allowed.map((k) => `"${k}"`).join(", ")}`
|
|
3548
|
-
);
|
|
3549
|
-
}
|
|
3550
|
-
if (typeof matched.shape === "function") {
|
|
3551
|
-
const resolved = resolveAndValidateShape(matched.shape, opts?.ctx);
|
|
3557
|
+
const resolution = resolveGuardVariantKey({
|
|
3558
|
+
kind: "named",
|
|
3559
|
+
keys: Object.keys(namedConfig),
|
|
3560
|
+
caller: opts?.caller,
|
|
3561
|
+
reservedKeys: SHAPE_CONFIG_KEYS
|
|
3562
|
+
});
|
|
3563
|
+
if (!resolution.ok)
|
|
3564
|
+
throwQueryVariantResolution(resolution);
|
|
3565
|
+
const shapeOrFn = namedConfig[resolution.key];
|
|
3566
|
+
if (typeof shapeOrFn === "function") {
|
|
3567
|
+
const resolved = resolveAndValidateShape(shapeOrFn, opts?.ctx);
|
|
3552
3568
|
built = buildShapeZodSchema(model, method, resolved);
|
|
3553
3569
|
} else {
|
|
3554
|
-
built = builtCache.get(
|
|
3570
|
+
built = builtCache.get(resolution.key);
|
|
3555
3571
|
}
|
|
3556
3572
|
}
|
|
3557
3573
|
return applyBuiltShape(built, normalizedBody, isUnique);
|
|
@@ -4141,12 +4157,39 @@ function validateAllowedKeys(value, allowed, method, kind) {
|
|
|
4141
4157
|
return `Shape key "${key}" not valid for ${method}. Allowed: ${[...allowed].join(", ")}`;
|
|
4142
4158
|
});
|
|
4143
4159
|
}
|
|
4160
|
+
var RELATION_OPS_COVERING_FK = /* @__PURE__ */ new Set([
|
|
4161
|
+
"connect",
|
|
4162
|
+
"connectOrCreate",
|
|
4163
|
+
"create"
|
|
4164
|
+
]);
|
|
4165
|
+
function collectRelationCoveredFks(modelFields, dataConfig) {
|
|
4166
|
+
const covered = /* @__PURE__ */ new Set();
|
|
4167
|
+
for (const [fieldName, value] of Object.entries(dataConfig)) {
|
|
4168
|
+
const meta = modelFields[fieldName];
|
|
4169
|
+
if (!meta || !meta.isRelation)
|
|
4170
|
+
continue;
|
|
4171
|
+
const fks = meta.relationFromFields;
|
|
4172
|
+
if (!fks || fks.length === 0)
|
|
4173
|
+
continue;
|
|
4174
|
+
if (!isPlainObject(value))
|
|
4175
|
+
continue;
|
|
4176
|
+
const covers = Object.keys(value).some(
|
|
4177
|
+
(op) => RELATION_OPS_COVERING_FK.has(op)
|
|
4178
|
+
);
|
|
4179
|
+
if (!covers)
|
|
4180
|
+
continue;
|
|
4181
|
+
for (const fk of fks)
|
|
4182
|
+
covered.add(fk);
|
|
4183
|
+
}
|
|
4184
|
+
return covered;
|
|
4185
|
+
}
|
|
4144
4186
|
function validateCreateCompleteness(modelName, dataConfig, typeMap, scopeFks, zodDefaults) {
|
|
4145
4187
|
const modelFields = typeMap[modelName];
|
|
4146
4188
|
if (!modelFields)
|
|
4147
4189
|
return;
|
|
4148
4190
|
const zodDefaultFields = zodDefaults[modelName];
|
|
4149
4191
|
const zodDefaultSet = zodDefaultFields ? new Set(zodDefaultFields) : void 0;
|
|
4192
|
+
const relationCoveredFks = collectRelationCoveredFks(modelFields, dataConfig);
|
|
4150
4193
|
for (const [fieldName, meta] of Object.entries(modelFields)) {
|
|
4151
4194
|
if (meta.isRelation)
|
|
4152
4195
|
continue;
|
|
@@ -4160,10 +4203,12 @@ function validateCreateCompleteness(modelName, dataConfig, typeMap, scopeFks, zo
|
|
|
4160
4203
|
continue;
|
|
4161
4204
|
if (scopeFks.has(fieldName))
|
|
4162
4205
|
continue;
|
|
4206
|
+
if (relationCoveredFks.has(fieldName))
|
|
4207
|
+
continue;
|
|
4163
4208
|
if (zodDefaultSet && zodDefaultSet.has(fieldName))
|
|
4164
4209
|
continue;
|
|
4165
4210
|
throw new ShapeError(
|
|
4166
|
-
`Required field "${fieldName}" on model "${modelName}" is missing from create data shape, has no default, and is not a
|
|
4211
|
+
`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`
|
|
4167
4212
|
);
|
|
4168
4213
|
}
|
|
4169
4214
|
}
|
|
@@ -4757,34 +4802,43 @@ function resolveDynamicShape(shapeFn, ctx, context) {
|
|
|
4757
4802
|
}
|
|
4758
4803
|
return result;
|
|
4759
4804
|
}
|
|
4760
|
-
function
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
throw new ShapeError(
|
|
4767
|
-
`Caller key "${key}" collides with reserved guard shape key. Rename the caller path.`
|
|
4768
|
-
);
|
|
4769
|
-
}
|
|
4805
|
+
function throwVariantResolution(resolution) {
|
|
4806
|
+
const keys = resolution.keys.map((key) => `"${key}"`).join(", ");
|
|
4807
|
+
if (resolution.code === "reserved-key") {
|
|
4808
|
+
throw new ShapeError(
|
|
4809
|
+
`Caller key "${resolution.key}" collides with reserved guard shape key. Rename the caller path.`
|
|
4810
|
+
);
|
|
4770
4811
|
}
|
|
4771
|
-
if (
|
|
4772
|
-
if ("default" in input) {
|
|
4773
|
-
return resolveShapeEntry(input.default, body, contextFn, "default");
|
|
4774
|
-
}
|
|
4812
|
+
if (resolution.code === "missing-caller") {
|
|
4775
4813
|
throw new CallerError(
|
|
4776
|
-
`Missing caller. This guard uses named shape routing with keys: ${keys
|
|
4814
|
+
`Missing caller. This guard uses named shape routing with keys: ${keys}. Provide caller via guard(input, caller).`
|
|
4777
4815
|
);
|
|
4778
4816
|
}
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
return resolveShapeEntry(input.default, body, contextFn, "default");
|
|
4817
|
+
if (resolution.code === "ambiguous-caller") {
|
|
4818
|
+
const matches = (resolution.matches ?? []).map((pattern) => `"${pattern}"`).join(", ");
|
|
4819
|
+
throw new CallerError(
|
|
4820
|
+
`Ambiguous caller "${resolution.caller}" matches multiple patterns: ${matches}`
|
|
4821
|
+
);
|
|
4785
4822
|
}
|
|
4786
4823
|
throw new CallerError(
|
|
4787
|
-
`Unknown caller: "${caller}". Allowed: ${keys
|
|
4824
|
+
`Unknown caller: "${resolution.caller}". Allowed: ${keys}`
|
|
4825
|
+
);
|
|
4826
|
+
}
|
|
4827
|
+
function resolveNamedShape(input, body, contextFn, explicitCaller) {
|
|
4828
|
+
assertNoCallerInBody(body);
|
|
4829
|
+
const resolution = resolveGuardVariantKey({
|
|
4830
|
+
kind: "named",
|
|
4831
|
+
keys: Object.keys(input),
|
|
4832
|
+
caller: explicitCaller,
|
|
4833
|
+
reservedKeys: GUARD_SHAPE_KEYS
|
|
4834
|
+
});
|
|
4835
|
+
if (!resolution.ok)
|
|
4836
|
+
throwVariantResolution(resolution);
|
|
4837
|
+
return resolveShapeEntry(
|
|
4838
|
+
input[resolution.key],
|
|
4839
|
+
body,
|
|
4840
|
+
contextFn,
|
|
4841
|
+
resolution.key
|
|
4788
4842
|
);
|
|
4789
4843
|
}
|
|
4790
4844
|
function resolveShapeEntry(entry, body, contextFn, matchedKey) {
|