@vibeorm/schema 5.6.0 → 5.7.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/errors.d.ts +5 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +27 -2
- package/dist/index.js.map +5 -5
- package/dist/referential-actions.d.ts +11 -0
- package/package.json +1 -1
package/dist/errors.d.ts
CHANGED
|
@@ -194,7 +194,11 @@ export declare const SCOPED_REFUSAL_REASONS: readonly [
|
|
|
194
194
|
/** A create leaves out a column a list scope restricts — the handle never picks a value from the list. */
|
|
195
195
|
"scope_value_required",
|
|
196
196
|
/** `$scoped({ scopes: {} })` — the named form binds at least one scope. */
|
|
197
|
-
"no_scope_bound"
|
|
197
|
+
"no_scope_bound",
|
|
198
|
+
/** A { through } relation's effective onDelete/onUpdate is SetDefault, or SetNull on an all-nullable key: the database could detach the row from its parent. */
|
|
199
|
+
"through_detachable",
|
|
200
|
+
/** A { column } entry's column is part of a foreign key declaring SetDefault: the database could write another tenant's value into it. */
|
|
201
|
+
"column_set_default"];
|
|
198
202
|
export type ScopedRefusalReason = (typeof SCOPED_REFUSAL_REASONS)[number];
|
|
199
203
|
/**
|
|
200
204
|
* A refusal from a tenant-scoped handle (`$scoped`). Carries the model, the
|
package/dist/index.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export { PREDICATE_COMPARISON_OPS, assertPredicateCapabilities, inspectPredicate
|
|
|
21
21
|
export type { PredicateBooleanUsage, PredicateCapabilityInput, PredicateCapabilityIssue, PredicateCapabilityTable, PredicateComparisonOp, PredicateExpression, PredicateFieldRef, PredicateInspection, PredicateIR, PredicateNodeKind, PredicateNumericType, PredicateOperand, PredicateResultType, PredicateUsage, PredicateValue } from "./predicate.ts";
|
|
22
22
|
export { CHECK_RULE_GROUP_MAX, checkRulePredicate } from "./check-rules.ts";
|
|
23
23
|
export type { CheckRule, CheckRuleGroupKind, CheckRuleLiteral } from "./check-rules.ts";
|
|
24
|
-
export { resolveOnDelete, resolveOnUpdate, resolveReferentialActions } from "./referential-actions.ts";
|
|
24
|
+
export { resolveOnDelete, resolveOnUpdate, resolveReferentialActions, setDefaultRelationOf } from "./referential-actions.ts";
|
|
25
25
|
export { assertMigrationObjects, isSqlObjectDefinition, isSqlObjectIdentity, sqlObjectKey, sqlObjectNamespace } from "./migration-objects.ts";
|
|
26
26
|
export type { MigrationObjectIR, QualifiedSqlName, SqlObjectDefinition, SqlObjectDefinitionFormat, SqlObjectFacts, SqlObjectIdentity } from "./migration-objects.ts";
|
|
27
27
|
export type { AdvisoryLockMode, DialectCapabilities, FullTextEngine, RowLockStrength, RowLockWaitPolicy, TimeOfDayType, } from "./capabilities.ts";
|
package/dist/index.js
CHANGED
|
@@ -109,7 +109,9 @@ var SCOPED_REFUSAL_REASONS = [
|
|
|
109
109
|
"conflict_target_unscoped",
|
|
110
110
|
"model_denied",
|
|
111
111
|
"scope_value_required",
|
|
112
|
-
"no_scope_bound"
|
|
112
|
+
"no_scope_bound",
|
|
113
|
+
"through_detachable",
|
|
114
|
+
"column_set_default"
|
|
113
115
|
];
|
|
114
116
|
|
|
115
117
|
class ScopedQueryError extends VibeError {
|
|
@@ -1596,6 +1598,9 @@ function resolveReferentialActions(params) {
|
|
|
1596
1598
|
const required = (relation.fields ?? []).every((name) => params.field(name)?.isOptional !== true);
|
|
1597
1599
|
return { onDelete: resolveOnDelete({ explicit: relation.onDelete, required }), onUpdate: resolveOnUpdate({ explicit: relation.onUpdate }) };
|
|
1598
1600
|
}
|
|
1601
|
+
function setDefaultRelationOf(params) {
|
|
1602
|
+
return params.relations.find((relation) => !relation.isList && (relation.fields ?? []).includes(params.field) && (relation.onDelete === "SetDefault" || relation.onUpdate === "SetDefault"));
|
|
1603
|
+
}
|
|
1599
1604
|
|
|
1600
1605
|
// packages/schema/src/generated-columns.ts
|
|
1601
1606
|
function declarationIssue(params) {
|
|
@@ -3955,6 +3960,16 @@ function validateModel(params) {
|
|
|
3955
3960
|
model: model.name,
|
|
3956
3961
|
field: policyField.name
|
|
3957
3962
|
});
|
|
3963
|
+
} else {
|
|
3964
|
+
const repointing = setDefaultRelationOf({ relations: model.relations, field: policyField.name });
|
|
3965
|
+
if (repointing !== undefined) {
|
|
3966
|
+
issues.push({
|
|
3967
|
+
rule: "policy-set-default",
|
|
3968
|
+
message: `@@policy field "${model.name}.${policyField.name}" is a foreign key the database can re-point: relation "${repointing.name}" declares SetDefault, so deleting or re-keying its ${repointing.targetModel} writes the column's default, which can be another tenant's value. Declare onDelete and onUpdate as Cascade, Restrict or NoAction`,
|
|
3969
|
+
model: model.name,
|
|
3970
|
+
field: policyField.name
|
|
3971
|
+
});
|
|
3972
|
+
}
|
|
3958
3973
|
}
|
|
3959
3974
|
}
|
|
3960
3975
|
const checkNames = new Set;
|
|
@@ -4448,6 +4463,10 @@ function validateNativeRls(params) {
|
|
|
4448
4463
|
else if (slot.type !== field.type)
|
|
4449
4464
|
add({ rule: "rls-context-type-mismatch", message: `Context ${ref.context} does not match ${model.name}.${field.name}`, field: field.name });
|
|
4450
4465
|
}
|
|
4466
|
+
const repointing = field === undefined ? undefined : setDefaultRelationOf({ relations: model.relations, field: field.name });
|
|
4467
|
+
if (repointing !== undefined) {
|
|
4468
|
+
add({ rule: "rls-where-set-default", message: `Native RLS ownership column ${model.name}.${String(fieldName)} is a foreign key the database can re-point: relation "${repointing.name}" declares SetDefault, and referential actions bypass row security, so deleting or re-keying its ${repointing.targetModel} writes the column's default, which can be another tenant's value. Declare onDelete and onUpdate as Cascade, Restrict, NoAction or SetNull`, field: String(fieldName) });
|
|
4469
|
+
}
|
|
4451
4470
|
}
|
|
4452
4471
|
continue;
|
|
4453
4472
|
}
|
|
@@ -4462,6 +4481,11 @@ function validateNativeRls(params) {
|
|
|
4462
4481
|
add({ rule: "rls-nullable-through", message: "Native RLS through requires a non-nullable parent relation and foreign key; NULL parent links cannot satisfy WITH CHECK. Make the link required or use a direct ownership rule", field: relation.name });
|
|
4463
4482
|
continue;
|
|
4464
4483
|
}
|
|
4484
|
+
const actions = resolveReferentialActions({ relation, field: (name) => model.fields.find((field) => field.name === name) });
|
|
4485
|
+
if (actions.onDelete === "SetDefault" || actions.onUpdate === "SetDefault") {
|
|
4486
|
+
add({ rule: "rls-through-set-default", message: `Native RLS through requires a foreign key the database cannot re-point: ON ${actions.onDelete === "SetDefault" ? "DELETE" : "UPDATE"} SET DEFAULT moves the row to its key's default parent, which can belong to another tenant, and referential actions bypass row security. Declare onDelete and onUpdate as Cascade, Restrict or NoAction`, field: relation.name });
|
|
4487
|
+
continue;
|
|
4488
|
+
}
|
|
4465
4489
|
if (table === "parent")
|
|
4466
4490
|
add({ rule: "rls-through-alias-conflict", message: "A through table mapped to parent would shadow the fixed policy parent alias; use another table name" });
|
|
4467
4491
|
throughTargets.set(model.name, target.name);
|
|
@@ -4627,6 +4651,7 @@ export {
|
|
|
4627
4651
|
scalarListDefault,
|
|
4628
4652
|
schemaRowRuleIdentities,
|
|
4629
4653
|
serializeJsonDefault,
|
|
4654
|
+
setDefaultRelationOf,
|
|
4630
4655
|
sqlNamespaceIssue,
|
|
4631
4656
|
sqlObjectKey,
|
|
4632
4657
|
sqlObjectNamespace,
|
|
@@ -4642,4 +4667,4 @@ export {
|
|
|
4642
4667
|
validatorRefIssue
|
|
4643
4668
|
};
|
|
4644
4669
|
|
|
4645
|
-
//# debugId=
|
|
4670
|
+
//# debugId=8784FDF6327C16F164756E2164756E21
|