@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.
@@ -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=143878A615D384D864756E2164756E21
4670
+ //# debugId=8784FDF6327C16F164756E2164756E21