@vibeorm/schema 5.5.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/collation.d.ts +26 -0
- package/dist/errors.d.ts +5 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +99 -2
- package/dist/index.js.map +8 -7
- package/dist/ir.d.ts +5 -4
- package/dist/referential-actions.d.ts +11 -0
- package/package.json +1 -1
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which dialect renders a field's `@collation`.
|
|
3
|
+
*
|
|
4
|
+
* MySQL and PostgreSQL both have column collations, in disjoint name
|
|
5
|
+
* namespaces: every MySQL collation is `binary` or `<character set>_<suffix>`
|
|
6
|
+
* over a closed list of character sets, while PostgreSQL names are libc
|
|
7
|
+
* locales (`C`, `POSIX`, `en_US.utf8`), ICU tags (`und-x-icu`), `ucs_basic`,
|
|
8
|
+
* `pg_c_utf8` or user-created collations. A portable schema that carries a
|
|
9
|
+
* MySQL collation for its MySQL target therefore stays valid on PostgreSQL,
|
|
10
|
+
* and a PostgreSQL schema's collation reaches PostgreSQL DDL.
|
|
11
|
+
*/
|
|
12
|
+
import type { Dialect, FieldIR } from "./ir.ts";
|
|
13
|
+
/** Whether a collation name is a MySQL collation: `binary`, or `<MySQL character set>_<suffix>` (case-insensitive, as MySQL reads it). */
|
|
14
|
+
export declare function isMysqlCollationName(params: {
|
|
15
|
+
name: string;
|
|
16
|
+
}): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* The collation `dialect` renders for `field`, or `undefined` where the
|
|
19
|
+
* declaration is inert: MySQL renders every `@collation`, PostgreSQL every one
|
|
20
|
+
* that is not a MySQL collation name ({@link isMysqlCollationName}), SQLite none.
|
|
21
|
+
*/
|
|
22
|
+
export declare function dialectCollation(params: {
|
|
23
|
+
field: Pick<FieldIR, "collation">;
|
|
24
|
+
dialect: Dialect;
|
|
25
|
+
}): string | undefined;
|
|
26
|
+
//# sourceMappingURL=collation.d.ts.map
|
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";
|
|
@@ -29,6 +29,7 @@ export { timeOfDayType } from "./time-of-day.ts";
|
|
|
29
29
|
export { DATE_TRUNCATION_UNITS, dateTruncationStorage } from "./date-truncation.ts";
|
|
30
30
|
export type { DateTruncationStorage, DateTruncationUnit } from "./date-truncation.ts";
|
|
31
31
|
export { isLocalDateField, isCanonicalLocalDate, localDateCheckName } from "./local-date.ts";
|
|
32
|
+
export { dialectCollation, isMysqlCollationName } from "./collation.ts";
|
|
32
33
|
export { READ_ONLY_REFUSAL_REASONS, ReadOnlyClientError, SCOPED_REFUSAL_REASONS, ScopedQueryError, VIBE_ERROR_CODES, VibeError, internalError, invalidStoredData, unsupportedCapability, } from "./errors.ts";
|
|
33
34
|
export type { ReadOnlyRefusalReason, ScopedRefusalReason, VibeErrorCode } from "./errors.ts";
|
|
34
35
|
export { decodePrismaString, encodePrismaString, exactDecimalLiteral, exactIntegerLiteral } from "./prisma-literal.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) {
|
|
@@ -2463,6 +2468,71 @@ function dateTruncationStorage(params) {
|
|
|
2463
2468
|
return "timestamp";
|
|
2464
2469
|
return;
|
|
2465
2470
|
}
|
|
2471
|
+
// packages/schema/src/collation.ts
|
|
2472
|
+
var MYSQL_CHARACTER_SETS = new Set([
|
|
2473
|
+
"armscii8",
|
|
2474
|
+
"ascii",
|
|
2475
|
+
"big5",
|
|
2476
|
+
"binary",
|
|
2477
|
+
"cp1250",
|
|
2478
|
+
"cp1251",
|
|
2479
|
+
"cp1256",
|
|
2480
|
+
"cp1257",
|
|
2481
|
+
"cp850",
|
|
2482
|
+
"cp852",
|
|
2483
|
+
"cp866",
|
|
2484
|
+
"cp932",
|
|
2485
|
+
"dec8",
|
|
2486
|
+
"eucjpms",
|
|
2487
|
+
"euckr",
|
|
2488
|
+
"gb18030",
|
|
2489
|
+
"gb2312",
|
|
2490
|
+
"gbk",
|
|
2491
|
+
"geostd8",
|
|
2492
|
+
"greek",
|
|
2493
|
+
"hebrew",
|
|
2494
|
+
"hp8",
|
|
2495
|
+
"keybcs2",
|
|
2496
|
+
"koi8r",
|
|
2497
|
+
"koi8u",
|
|
2498
|
+
"latin1",
|
|
2499
|
+
"latin2",
|
|
2500
|
+
"latin5",
|
|
2501
|
+
"latin7",
|
|
2502
|
+
"macce",
|
|
2503
|
+
"macroman",
|
|
2504
|
+
"sjis",
|
|
2505
|
+
"swe7",
|
|
2506
|
+
"tis620",
|
|
2507
|
+
"ucs2",
|
|
2508
|
+
"ujis",
|
|
2509
|
+
"utf16",
|
|
2510
|
+
"utf16le",
|
|
2511
|
+
"utf32",
|
|
2512
|
+
"utf8",
|
|
2513
|
+
"utf8mb3",
|
|
2514
|
+
"utf8mb4"
|
|
2515
|
+
]);
|
|
2516
|
+
function isMysqlCollationName(params) {
|
|
2517
|
+
const name = params.name.toLowerCase();
|
|
2518
|
+
if (name === "binary")
|
|
2519
|
+
return true;
|
|
2520
|
+
const separator = name.indexOf("_");
|
|
2521
|
+
return separator > 0 && separator < name.length - 1 && MYSQL_CHARACTER_SETS.has(name.slice(0, separator));
|
|
2522
|
+
}
|
|
2523
|
+
function dialectCollation(params) {
|
|
2524
|
+
const { collation } = params.field;
|
|
2525
|
+
if (collation === undefined)
|
|
2526
|
+
return;
|
|
2527
|
+
switch (params.dialect) {
|
|
2528
|
+
case "mysql":
|
|
2529
|
+
return collation;
|
|
2530
|
+
case "postgres":
|
|
2531
|
+
return isMysqlCollationName({ name: collation }) ? undefined : collation;
|
|
2532
|
+
case "sqlite":
|
|
2533
|
+
return;
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2466
2536
|
// packages/schema/src/computed.ts
|
|
2467
2537
|
function buildComputedManifest(params) {
|
|
2468
2538
|
const { computed } = params;
|
|
@@ -2854,6 +2924,9 @@ var RESERVED_ENUM_NAMES = [
|
|
|
2854
2924
|
"createClient",
|
|
2855
2925
|
"DatabaseAdapter",
|
|
2856
2926
|
"DbNow",
|
|
2927
|
+
"JoinedField",
|
|
2928
|
+
"TransactionLocalSettings",
|
|
2929
|
+
"VibeUpdateManySources",
|
|
2857
2930
|
"ClientContext",
|
|
2858
2931
|
"QueryOperation",
|
|
2859
2932
|
"QueryEvent",
|
|
@@ -2917,6 +2990,8 @@ var GENERATED_TYPE_NAMES = {
|
|
|
2917
2990
|
"UpdateInput",
|
|
2918
2991
|
"UncheckedUpdateInput",
|
|
2919
2992
|
"UpdateManyInput",
|
|
2993
|
+
"UpdateManyJoinedInput",
|
|
2994
|
+
"UpdateManyFrom",
|
|
2920
2995
|
"CountAggregateInput",
|
|
2921
2996
|
"AvgAggregateInput",
|
|
2922
2997
|
"SumAggregateInput",
|
|
@@ -3885,6 +3960,16 @@ function validateModel(params) {
|
|
|
3885
3960
|
model: model.name,
|
|
3886
3961
|
field: policyField.name
|
|
3887
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
|
+
}
|
|
3888
3973
|
}
|
|
3889
3974
|
}
|
|
3890
3975
|
const checkNames = new Set;
|
|
@@ -4378,6 +4463,10 @@ function validateNativeRls(params) {
|
|
|
4378
4463
|
else if (slot.type !== field.type)
|
|
4379
4464
|
add({ rule: "rls-context-type-mismatch", message: `Context ${ref.context} does not match ${model.name}.${field.name}`, field: field.name });
|
|
4380
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
|
+
}
|
|
4381
4470
|
}
|
|
4382
4471
|
continue;
|
|
4383
4472
|
}
|
|
@@ -4392,6 +4481,11 @@ function validateNativeRls(params) {
|
|
|
4392
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 });
|
|
4393
4482
|
continue;
|
|
4394
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
|
+
}
|
|
4395
4489
|
if (table === "parent")
|
|
4396
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" });
|
|
4397
4491
|
throughTargets.set(model.name, target.name);
|
|
@@ -4487,6 +4581,7 @@ export {
|
|
|
4487
4581
|
defaultPrimaryKeyName,
|
|
4488
4582
|
defaultRowRuleName,
|
|
4489
4583
|
defineExtension,
|
|
4584
|
+
dialectCollation,
|
|
4490
4585
|
encodePrismaString,
|
|
4491
4586
|
enumStorageOf,
|
|
4492
4587
|
exactDecimalLiteral,
|
|
@@ -4517,6 +4612,7 @@ export {
|
|
|
4517
4612
|
isExpressionIndex,
|
|
4518
4613
|
isImplicitManyToManyRelation,
|
|
4519
4614
|
isLocalDateField,
|
|
4615
|
+
isMysqlCollationName,
|
|
4520
4616
|
isPredicateFieldRef,
|
|
4521
4617
|
isReservedClientName,
|
|
4522
4618
|
isReservedDelegateName,
|
|
@@ -4555,6 +4651,7 @@ export {
|
|
|
4555
4651
|
scalarListDefault,
|
|
4556
4652
|
schemaRowRuleIdentities,
|
|
4557
4653
|
serializeJsonDefault,
|
|
4654
|
+
setDefaultRelationOf,
|
|
4558
4655
|
sqlNamespaceIssue,
|
|
4559
4656
|
sqlObjectKey,
|
|
4560
4657
|
sqlObjectNamespace,
|
|
@@ -4570,4 +4667,4 @@ export {
|
|
|
4570
4667
|
validatorRefIssue
|
|
4571
4668
|
};
|
|
4572
4669
|
|
|
4573
|
-
//# debugId=
|
|
4670
|
+
//# debugId=8784FDF6327C16F164756E2164756E21
|