@vibeorm/migrate 2.0.0-alpha.1 → 2.0.0-alpha.2

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/index.js CHANGED
@@ -74,9 +74,12 @@ function detectImplicitM2MPairs(params) {
74
74
  const target = modelsByName.get(relation.targetModel);
75
75
  if (target === undefined || target.isView)
76
76
  continue;
77
- const counterpart = target.relations.find((candidate) => candidate !== relation && candidate.targetModel === model.name && isFieldlessListSide({ relation: candidate }) && candidate.relationName === relation.relationName);
78
- if (counterpart === undefined)
77
+ const counterpart = soleCounterpart({ relation, ownModel: model.name, target });
78
+ if (counterpart === undefined || !isFieldlessListSide({ relation: counterpart }))
79
79
  continue;
80
+ if (soleCounterpart({ relation: counterpart, ownModel: target.name, target: model }) !== relation) {
81
+ continue;
82
+ }
80
83
  const joinTable = implicitJoinTable({
81
84
  modelA: model.name,
82
85
  modelB: relation.targetModel,
@@ -99,6 +102,11 @@ function isFieldlessListSide(params) {
99
102
  const { relation } = params;
100
103
  return relation.isList && (relation.fields === undefined || relation.fields.length === 0);
101
104
  }
105
+ function soleCounterpart(params) {
106
+ const { relation, ownModel, target } = params;
107
+ const candidates = target.relations.filter((candidate) => candidate !== relation && candidate.targetModel === ownModel && candidate.relationName === relation.relationName);
108
+ return candidates.length === 1 ? candidates[0] : undefined;
109
+ }
102
110
 
103
111
  // packages/migrate/src/postgres-types.ts
104
112
  import { VibeError, isEnumType } from "@vibeorm/schema";
@@ -110,10 +118,13 @@ var SCALAR_TO_PG = {
110
118
  BigInt: "bigint",
111
119
  Float: "double precision",
112
120
  Decimal: "numeric",
113
- DateTime: "timestamptz(3)",
121
+ DateTime: "timestamptz",
114
122
  Json: "jsonb",
115
123
  Bytes: "bytea"
116
124
  };
125
+ var CANONICAL_EQUIVALENTS = {
126
+ DateTime: ["timestamptz(3)", "timestamptz(6)"]
127
+ };
117
128
  var NATIVE_ALIASES = {
118
129
  charactervarying: "varchar",
119
130
  character: "char",
@@ -155,7 +166,8 @@ function columnTypeKey(params) {
155
166
  if (field.nativeType !== undefined) {
156
167
  const normalized = normalizeNativeType({ raw: field.nativeType });
157
168
  const canonical = normalizeNativeType({ raw: SCALAR_TO_PG[field.type] });
158
- if (normalized !== canonical)
169
+ const isCanonical = normalized === canonical || (CANONICAL_EQUIVALENTS[field.type]?.includes(normalized) ?? false);
170
+ if (!isCanonical)
159
171
  return `n:${normalized}${listSuffix}`;
160
172
  }
161
173
  return `s:${field.type}${listSuffix}`;
@@ -219,6 +231,9 @@ function defaultFingerprint(params) {
219
231
  return `num:${value}`;
220
232
  if (isEnumType(field.type))
221
233
  return `enum:${value}`;
234
+ if (isNumericScalar({ type: field.type }) && NUMERIC_LITERAL.test(value)) {
235
+ return `num:${Number(value)}`;
236
+ }
222
237
  return `str:${value}`;
223
238
  }
224
239
  case "enumValue":
@@ -254,6 +269,11 @@ function defaultSql(params) {
254
269
  return def.sql;
255
270
  }
256
271
  }
272
+ var NUMERIC_LITERAL = /^-?\d+(\.\d+)?$/;
273
+ function isNumericScalar(params) {
274
+ const { type } = params;
275
+ return type === "Int" || type === "BigInt" || type === "Float" || type === "Decimal";
276
+ }
257
277
  var PG_CHAR_TO_CASCADE_ACTION = {
258
278
  a: "NoAction",
259
279
  r: "Restrict",
@@ -2330,6 +2350,7 @@ function addFkRelations(params) {
2330
2350
  const modelsByName = new Map(models.map((model) => [model.name, model]));
2331
2351
  const relationsByModel = new Map;
2332
2352
  const takeName = nameAllocator({ models });
2353
+ const ambiguousPairs = detectAmbiguousPairs({ fkGroups, joinTables, modelsByName });
2333
2354
  for (const group of fkGroups) {
2334
2355
  if (joinTables.has(group.table))
2335
2356
  continue;
@@ -2337,6 +2358,7 @@ function addFkRelations(params) {
2337
2358
  const target = modelsByName.get(group.refTable);
2338
2359
  if (owner === undefined || target === undefined)
2339
2360
  continue;
2361
+ const relationName = ambiguousPairs.has(modelPairKey({ a: group.table, b: group.refTable })) ? `${group.table}_${group.columns.join("_")}To${group.refTable}` : undefined;
2340
2362
  const fkSide = {
2341
2363
  name: takeName({ model: owner.name, base: lowerFirst(group.refTable) }),
2342
2364
  model: owner.name,
@@ -2347,7 +2369,8 @@ function addFkRelations(params) {
2347
2369
  onDelete: group.onDelete,
2348
2370
  onUpdate: group.onUpdate,
2349
2371
  isList: false,
2350
- isOptional: !group.required
2372
+ isOptional: !group.required,
2373
+ ...relationName === undefined ? {} : { relationName }
2351
2374
  };
2352
2375
  pushRelation({ relationsByModel, relation: fkSide });
2353
2376
  const listSide = {
@@ -2356,7 +2379,8 @@ function addFkRelations(params) {
2356
2379
  targetModel: group.table,
2357
2380
  type: "oneToMany",
2358
2381
  isList: true,
2359
- isOptional: false
2382
+ isOptional: false,
2383
+ ...relationName === undefined ? {} : { relationName }
2360
2384
  };
2361
2385
  pushRelation({ relationsByModel, relation: listSide });
2362
2386
  }
@@ -2400,6 +2424,30 @@ function addJoinTableRelations(params) {
2400
2424
  }
2401
2425
  applyRelations({ models, relationsByModel });
2402
2426
  }
2427
+ function modelPairKey(params) {
2428
+ const { a, b } = params;
2429
+ return [a, b].sort().join("\x00");
2430
+ }
2431
+ function detectAmbiguousPairs(params) {
2432
+ const { fkGroups, joinTables, modelsByName } = params;
2433
+ const counts = new Map;
2434
+ const bump = (key) => {
2435
+ counts.set(key, (counts.get(key) ?? 0) + 1);
2436
+ };
2437
+ for (const group of fkGroups) {
2438
+ if (joinTables.has(group.table))
2439
+ continue;
2440
+ if (!modelsByName.has(group.table) || !modelsByName.has(group.refTable))
2441
+ continue;
2442
+ bump(modelPairKey({ a: group.table, b: group.refTable }));
2443
+ }
2444
+ for (const [, joinTable] of joinTables) {
2445
+ if (joinTable.relationName !== undefined)
2446
+ continue;
2447
+ bump(modelPairKey({ a: joinTable.tableA, b: joinTable.tableB }));
2448
+ }
2449
+ return new Set([...counts.entries()].filter(([, count]) => count > 1).map(([key]) => key));
2450
+ }
2403
2451
  function pushRelation(params) {
2404
2452
  const { relationsByModel, relation } = params;
2405
2453
  const list = relationsByModel.get(relation.model);
@@ -2500,17 +2548,18 @@ var COLUMNS_QUERY = `
2500
2548
  `;
2501
2549
  var CONSTRAINTS_QUERY = `
2502
2550
  SELECT
2503
- tc.constraint_name,
2504
- tc.constraint_type,
2505
- tc.table_name,
2506
- kcu.column_name
2507
- FROM information_schema.table_constraints tc
2508
- JOIN information_schema.key_column_usage kcu
2509
- ON tc.constraint_name = kcu.constraint_name
2510
- AND tc.table_schema = kcu.table_schema
2511
- WHERE tc.table_schema = 'public'
2512
- AND tc.constraint_type IN ('PRIMARY KEY', 'UNIQUE')
2513
- ORDER BY tc.table_name, tc.constraint_name, kcu.ordinal_position;
2551
+ c.conname AS constraint_name,
2552
+ CASE c.contype WHEN 'p' THEN 'PRIMARY KEY' ELSE 'UNIQUE' END AS constraint_type,
2553
+ cl.relname AS table_name,
2554
+ att.attname AS column_name
2555
+ FROM pg_catalog.pg_constraint c
2556
+ JOIN pg_catalog.pg_class cl ON cl.oid = c.conrelid
2557
+ JOIN pg_catalog.pg_namespace n ON n.oid = cl.relnamespace
2558
+ JOIN unnest(c.conkey) WITH ORDINALITY AS k(attnum, ord) ON true
2559
+ JOIN pg_catalog.pg_attribute att ON att.attrelid = c.conrelid AND att.attnum = k.attnum
2560
+ WHERE c.contype IN ('p', 'u')
2561
+ AND n.nspname = 'public'
2562
+ ORDER BY cl.relname, c.conname, k.ord;
2514
2563
  `;
2515
2564
  var FOREIGN_KEYS_QUERY = `
2516
2565
  SELECT
@@ -2821,7 +2870,7 @@ function nonCanonicalModifier(params) {
2821
2870
  const { column, udt, scalar } = params;
2822
2871
  if (scalar === "DateTime" && udt === "timestamptz") {
2823
2872
  const precision = column.datetimePrecision;
2824
- return precision === null || precision === 3 ? undefined : `timestamptz(${precision})`;
2873
+ return precision === null || precision === 6 ? undefined : `timestamptz(${precision})`;
2825
2874
  }
2826
2875
  if (scalar === "Decimal" && udt === "numeric") {
2827
2876
  if (column.numericPrecision === null)
@@ -2876,10 +2925,6 @@ function parseColumnDefault(params) {
2876
2925
  }
2877
2926
  return { kind: "dbgenerated", sql: raw };
2878
2927
  }
2879
- function isNumericScalar(params) {
2880
- const { type } = params;
2881
- return type === "Int" || type === "BigInt" || type === "Float" || type === "Decimal";
2882
- }
2883
2928
  function parseIndexDef(params) {
2884
2929
  const { table, name, def } = params;
2885
2930
  const head = def.match(/^CREATE (UNIQUE )?INDEX .*? USING (\w+) \(/);
@@ -3810,4 +3855,4 @@ export {
3810
3855
  MIGRATIONS_TABLE_NAME
3811
3856
  };
3812
3857
 
3813
- //# debugId=3CDFCCD15C21DD9464756E2164756E21
3858
+ //# debugId=5088DA7EA679FA1464756E2164756E21