@drzl/analyzer 1.17.7 → 1.20.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/index.cjs +81 -22
- package/dist/index.d.cts +106 -1
- package/dist/index.d.ts +106 -1
- package/dist/index.js +79 -22
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -36,10 +36,18 @@ __export(index_exports, {
|
|
|
36
36
|
isDrizzleView: () => isDrizzleView,
|
|
37
37
|
isReadOnlyRelation: () => isReadOnlyRelation,
|
|
38
38
|
isRelationsV2: () => isRelationsV2,
|
|
39
|
+
qualifiedForeignTable: () => qualifiedForeignTable,
|
|
40
|
+
qualifiedTableName: () => qualifiedTableName,
|
|
39
41
|
readRelationsV2: () => readRelationsV2
|
|
40
42
|
});
|
|
41
43
|
module.exports = __toCommonJS(index_exports);
|
|
42
44
|
var import_meta = {};
|
|
45
|
+
function qualifiedTableName(table) {
|
|
46
|
+
return table.schema ? `${table.schema}.${table.name}` : table.name;
|
|
47
|
+
}
|
|
48
|
+
function qualifiedForeignTable(fk) {
|
|
49
|
+
return fk.foreignSchema ? `${fk.foreignSchema}.${fk.foreignTable}` : fk.foreignTable;
|
|
50
|
+
}
|
|
43
51
|
function renderSqlLiteral(v) {
|
|
44
52
|
if (v === null || v === void 0) return "NULL";
|
|
45
53
|
if (typeof v === "number" || typeof v === "bigint") return String(v);
|
|
@@ -385,27 +393,43 @@ function isRelationsV2(val) {
|
|
|
385
393
|
)
|
|
386
394
|
);
|
|
387
395
|
}
|
|
396
|
+
function qualifiedNameOfDrizzleTable(tbl) {
|
|
397
|
+
const name = getSymbolOf(tbl, "drizzle:Name");
|
|
398
|
+
if (typeof name !== "string" || !name) return void 0;
|
|
399
|
+
const schema = getSymbolOf(tbl, "drizzle:Schema");
|
|
400
|
+
return typeof schema === "string" && schema ? `${schema}.${name}` : name;
|
|
401
|
+
}
|
|
388
402
|
function readRelationsV2(val, issues = []) {
|
|
389
403
|
const out = [];
|
|
390
404
|
for (const [tableKey, entry] of Object.entries(val)) {
|
|
391
|
-
const from =
|
|
405
|
+
const from = qualifiedNameOfDrizzleTable(entry.table) ?? entry.name ?? tableKey;
|
|
392
406
|
for (const [fieldName, r] of Object.entries(entry.relations ?? {})) {
|
|
393
|
-
const to = r?.targetTableName;
|
|
407
|
+
const to = qualifiedNameOfDrizzleTable(r?.targetTable) ?? r?.targetTableName;
|
|
394
408
|
if (typeof to !== "string" || !to) {
|
|
395
409
|
issues.push({
|
|
396
410
|
code: "DRZL_ANL_REL_V2",
|
|
397
411
|
level: "warn",
|
|
398
|
-
message: `Relation "${fieldName}" on "${from}" names no target table and was skipped
|
|
412
|
+
message: `Relation "${fieldName}" on "${from}" names no target table and was skipped.`,
|
|
413
|
+
path: from
|
|
399
414
|
});
|
|
400
415
|
continue;
|
|
401
416
|
}
|
|
402
|
-
const via =
|
|
417
|
+
const via = qualifiedNameOfDrizzleTable(r.throughTable) ?? qualifiedNameOfDrizzleTable(r.through?.sourceTable) ?? void 0;
|
|
403
418
|
if (via) out.push({ kind: "manyToMany", from, to, via });
|
|
404
419
|
else out.push({ kind: r.relationType === "many" ? "many" : "one", from, to });
|
|
405
420
|
}
|
|
406
421
|
}
|
|
407
422
|
return out;
|
|
408
423
|
}
|
|
424
|
+
function unknownColumnHint(reason) {
|
|
425
|
+
if (reason === "custom") {
|
|
426
|
+
return "A customType has no runtime shape to read. Declare it with .$type<T>() and turn on typedColumns to give the validator the type.";
|
|
427
|
+
}
|
|
428
|
+
if (reason === "gel-temporal") {
|
|
429
|
+
return "A Gel temporal column holds an instance of a class from the `gel` package, which DRZL cannot import, so it is left untyped on purpose rather than guessed at. Turn on typedColumns to recover the declared type, and validate the value yourself.";
|
|
430
|
+
}
|
|
431
|
+
return "Open an issue naming the column type so it can be modelled, or declare it with .$type<T>() and turn on typedColumns.";
|
|
432
|
+
}
|
|
409
433
|
var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
410
434
|
constructor(schemaPath) {
|
|
411
435
|
this.schemaPath = schemaPath;
|
|
@@ -456,6 +480,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
456
480
|
code: "DRZL_ANL_EXTRACONFIG",
|
|
457
481
|
level: "warn",
|
|
458
482
|
message: `Could not evaluate the extra-config callback for table "${tableName}": ${e.message}`,
|
|
483
|
+
path: tableName,
|
|
459
484
|
hint: "Indexes, composite keys, checks and table-level foreign keys will be missing for this table."
|
|
460
485
|
});
|
|
461
486
|
return [];
|
|
@@ -509,9 +534,11 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
509
534
|
};
|
|
510
535
|
const foreignColumnsObj = this.getSymbol(ref.foreignTable, "drizzle:Columns") ?? {};
|
|
511
536
|
const toForeignTs = this.dbToTsNames(foreignColumnsObj);
|
|
537
|
+
const foreignSchema = this.getSymbol(ref.foreignTable, "drizzle:Schema");
|
|
512
538
|
return {
|
|
513
539
|
columns: (ref.columns ?? []).map((c) => toTs(c?.name)),
|
|
514
540
|
foreignTable: this.getSymbol(ref.foreignTable, "drizzle:Name") ?? "unknown",
|
|
541
|
+
...foreignSchema ? { foreignSchema } : {},
|
|
515
542
|
foreignColumns: (ref.foreignColumns ?? []).map((c) => toForeignTs(c?.name)),
|
|
516
543
|
onDelete: action(fk?.onDelete, fk?._onDelete),
|
|
517
544
|
onUpdate: action(fk?.onUpdate, fk?._onUpdate),
|
|
@@ -560,7 +587,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
560
587
|
* on each returned value, so the stand-in results must carry that method or the call throws.
|
|
561
588
|
*/
|
|
562
589
|
readRelationsObject(val, exportName, issues) {
|
|
563
|
-
const from =
|
|
590
|
+
const from = qualifiedNameOfDrizzleTable(val.table) ?? exportName;
|
|
564
591
|
const make = (kind) => (table, cfg) => ({
|
|
565
592
|
kind,
|
|
566
593
|
referencedTable: table,
|
|
@@ -574,7 +601,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
574
601
|
const built = val.config({ one: make("one"), many: make("many") });
|
|
575
602
|
const out = [];
|
|
576
603
|
for (const rel of Object.values(built ?? {})) {
|
|
577
|
-
const to =
|
|
604
|
+
const to = qualifiedNameOfDrizzleTable(rel?.referencedTable);
|
|
578
605
|
if (to) out.push({ kind: rel.kind, from, to });
|
|
579
606
|
}
|
|
580
607
|
return out;
|
|
@@ -583,6 +610,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
583
610
|
code: "DRZL_ANL_RELATIONS",
|
|
584
611
|
level: "warn",
|
|
585
612
|
message: `Could not read the relations declared in "${exportName}": ${e.message}`,
|
|
613
|
+
path: from,
|
|
586
614
|
hint: "Relations for this table will be missing from the analysis."
|
|
587
615
|
});
|
|
588
616
|
return [];
|
|
@@ -603,10 +631,11 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
603
631
|
if (fks.length < 2) continue;
|
|
604
632
|
const fkCols = new Set(fks.flatMap((f) => f.columns));
|
|
605
633
|
if (!t.columns.every((c) => fkCols.has(c.name))) continue;
|
|
606
|
-
const targets = [...new Set(fks.map(
|
|
634
|
+
const targets = [...new Set(fks.map(qualifiedForeignTable))];
|
|
607
635
|
if (targets.length !== 2) continue;
|
|
608
|
-
|
|
609
|
-
out.push({ kind: "manyToMany", from: targets[
|
|
636
|
+
const via = qualifiedTableName(t);
|
|
637
|
+
out.push({ kind: "manyToMany", from: targets[0], to: targets[1], via });
|
|
638
|
+
out.push({ kind: "manyToMany", from: targets[1], to: targets[0], via });
|
|
610
639
|
}
|
|
611
640
|
return out;
|
|
612
641
|
}
|
|
@@ -928,7 +957,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
928
957
|
if (/Bool/i.test(ctor)) return { tsType: "boolean", dbType: "BOOLEAN" };
|
|
929
958
|
if (/TimestampTz/i.test(ctor)) return { tsType: "Date", dbType: "TIMESTAMPTZ" };
|
|
930
959
|
if (/Timestamp|LocalDateString|LocalTime|DateDuration|RelDuration|Duration/i.test(ctor))
|
|
931
|
-
return { tsType: "unknown", dbType: "UNKNOWN" };
|
|
960
|
+
return { tsType: "unknown", dbType: "UNKNOWN", unnameable: "gel-temporal" };
|
|
932
961
|
}
|
|
933
962
|
return { tsType: "unknown", dbType: "UNKNOWN" };
|
|
934
963
|
}
|
|
@@ -944,7 +973,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
944
973
|
const uniqueGroups = /* @__PURE__ */ new Map();
|
|
945
974
|
for (const [colName, outerCol] of Object.entries(columnsObj)) {
|
|
946
975
|
const { element: col, dimensions: arrayDims } = unwrapArrayColumn(outerCol);
|
|
947
|
-
|
|
976
|
+
const mapped = this.mapColumnType(col);
|
|
977
|
+
let { tsType, dbType } = mapped;
|
|
948
978
|
if (tsType === "unknown" && /At$/.test(colName)) {
|
|
949
979
|
tsType = "Date";
|
|
950
980
|
dbType = "INTEGER";
|
|
@@ -987,13 +1017,26 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
987
1017
|
code: "DRZL_ANL_UNKNOWN_COLUMN",
|
|
988
1018
|
level: "warn",
|
|
989
1019
|
message: `Column "${colName}" on table "${tsName}" has no known type${sqlType2 ? ` (SQL type ${sqlType2})` : ""}, so its validator will accept any value.`,
|
|
990
|
-
|
|
1020
|
+
path: `${tsName}.${colName}`,
|
|
1021
|
+
hint: unknownColumnHint(shape === "custom" ? "custom" : mapped.unnameable)
|
|
991
1022
|
});
|
|
992
1023
|
}
|
|
1024
|
+
const dims = arrayDims || v1?.arrayDimensions || 0;
|
|
1025
|
+
const declaredSqlType = (() => {
|
|
1026
|
+
let raw;
|
|
1027
|
+
try {
|
|
1028
|
+
raw = typeof outerCol?.getSQLType === "function" ? outerCol.getSQLType() : void 0;
|
|
1029
|
+
} catch {
|
|
1030
|
+
return void 0;
|
|
1031
|
+
}
|
|
1032
|
+
if (typeof raw !== "string" || !raw) return void 0;
|
|
1033
|
+
return raw.endsWith("]") ? raw : raw + "[]".repeat(dims);
|
|
1034
|
+
})();
|
|
993
1035
|
columns.push({
|
|
994
1036
|
name: colName,
|
|
995
1037
|
tsType,
|
|
996
1038
|
dbType,
|
|
1039
|
+
...declaredSqlType ? { sqlType: declaredSqlType } : {},
|
|
997
1040
|
nullable,
|
|
998
1041
|
hasDefault,
|
|
999
1042
|
isGenerated,
|
|
@@ -1070,6 +1113,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
1070
1113
|
if (!col) continue;
|
|
1071
1114
|
col.references = {
|
|
1072
1115
|
table: fk.foreignTable,
|
|
1116
|
+
...fk.foreignSchema ? { schema: fk.foreignSchema } : {},
|
|
1073
1117
|
column: fk.foreignColumns[0],
|
|
1074
1118
|
onDelete: fk.onDelete,
|
|
1075
1119
|
onUpdate: fk.onUpdate
|
|
@@ -1142,9 +1186,11 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
1142
1186
|
}
|
|
1143
1187
|
}
|
|
1144
1188
|
if (opts.includeRelations) {
|
|
1189
|
+
const self = qualifiedTableName(table);
|
|
1145
1190
|
for (const fk of table.foreignKeys ?? []) {
|
|
1146
|
-
|
|
1147
|
-
relations.push({ kind: "
|
|
1191
|
+
const target = qualifiedForeignTable(fk);
|
|
1192
|
+
relations.push({ kind: "one", from: self, to: target });
|
|
1193
|
+
relations.push({ kind: "many", from: target, to: self });
|
|
1148
1194
|
}
|
|
1149
1195
|
}
|
|
1150
1196
|
} else if (this.isRelationsObject(val)) {
|
|
@@ -1172,7 +1218,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
1172
1218
|
issues.push({
|
|
1173
1219
|
code: "DRZL_ANL_TABLE",
|
|
1174
1220
|
level: "warn",
|
|
1175
|
-
message: `Failed to analyze export ${name}: ${String(e)}
|
|
1221
|
+
message: `Failed to analyze export ${name}: ${String(e)}`,
|
|
1222
|
+
path: name
|
|
1176
1223
|
});
|
|
1177
1224
|
}
|
|
1178
1225
|
}
|
|
@@ -1217,11 +1264,21 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
1217
1264
|
relations.push(...this.inferManyToMany(tables));
|
|
1218
1265
|
}
|
|
1219
1266
|
if (opts.includeRelations && opts.includeHeuristicRelations) {
|
|
1220
|
-
const
|
|
1221
|
-
const
|
|
1222
|
-
|
|
1223
|
-
if (
|
|
1224
|
-
|
|
1267
|
+
const byBareName = /* @__PURE__ */ new Map();
|
|
1268
|
+
for (const t of tables) {
|
|
1269
|
+
const list = byBareName.get(t.name);
|
|
1270
|
+
if (list) list.push(t);
|
|
1271
|
+
else byBareName.set(t.name, [t]);
|
|
1272
|
+
}
|
|
1273
|
+
const findTarget = (base, from) => {
|
|
1274
|
+
for (const candidate of [base, base + "s", base + "es"]) {
|
|
1275
|
+
const hits = byBareName.get(candidate);
|
|
1276
|
+
if (!hits?.length) continue;
|
|
1277
|
+
const sameSchema = hits.filter((t) => t.schema === from.schema);
|
|
1278
|
+
if (sameSchema.length === 1) return qualifiedTableName(sameSchema[0]);
|
|
1279
|
+
if (hits.length === 1) return qualifiedTableName(hits[0]);
|
|
1280
|
+
return void 0;
|
|
1281
|
+
}
|
|
1225
1282
|
return void 0;
|
|
1226
1283
|
};
|
|
1227
1284
|
for (const t of tables) {
|
|
@@ -1229,8 +1286,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
1229
1286
|
if (c.references) continue;
|
|
1230
1287
|
if (c.name.endsWith("Id")) {
|
|
1231
1288
|
const base = c.name.slice(0, -2);
|
|
1232
|
-
const target = findTarget(base);
|
|
1233
|
-
if (target) relations.push({ kind: "one", from: t
|
|
1289
|
+
const target = findTarget(base, t);
|
|
1290
|
+
if (target) relations.push({ kind: "one", from: qualifiedTableName(t), to: target });
|
|
1234
1291
|
}
|
|
1235
1292
|
}
|
|
1236
1293
|
}
|
|
@@ -1401,5 +1458,7 @@ var index_default = SchemaAnalyzer;
|
|
|
1401
1458
|
isDrizzleView,
|
|
1402
1459
|
isReadOnlyRelation,
|
|
1403
1460
|
isRelationsV2,
|
|
1461
|
+
qualifiedForeignTable,
|
|
1462
|
+
qualifiedTableName,
|
|
1404
1463
|
readRelationsV2
|
|
1405
1464
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -8,28 +8,91 @@ interface Issue {
|
|
|
8
8
|
level: 'info' | 'warn' | 'error';
|
|
9
9
|
message: string;
|
|
10
10
|
hint?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Where the issue is, as `table` or `table.column`.
|
|
13
|
+
*
|
|
14
|
+
* Declared since this interface existed and set by nothing, so every consumer wanting to group
|
|
15
|
+
* warnings by table had to read the names back out of the English in `message`. `drzl doctor` is
|
|
16
|
+
* the first such consumer and a report built by regex over prose breaks the first time a message
|
|
17
|
+
* is reworded, so the names are stated here instead.
|
|
18
|
+
*
|
|
19
|
+
* Still optional: an issue about the schema as a whole, such as an unidentifiable dialect, is
|
|
20
|
+
* about no table and says so by omitting this.
|
|
21
|
+
*/
|
|
11
22
|
path?: string;
|
|
12
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Why a column has no type, where "nobody has modelled it" is not the answer.
|
|
26
|
+
*
|
|
27
|
+
* The distinction exists because it changes the advice. A column class this file has no arm for is
|
|
28
|
+
* a gap someone can close, and the warning tells its author to say so. A Gel temporal column is
|
|
29
|
+
* not: the value is an instance of a class from the `gel` package, DRZL cannot import that package,
|
|
30
|
+
* and no generator could emit a check for it even knowing the name. Leaving it `unknown` is the
|
|
31
|
+
* measured answer rather than an omission, and its warning should say that instead of asking for a
|
|
32
|
+
* bug report that is already closed.
|
|
33
|
+
*/
|
|
34
|
+
type UnnameableReason = 'gel-temporal';
|
|
13
35
|
interface ColumnRef {
|
|
14
36
|
table: string;
|
|
15
37
|
column: string;
|
|
16
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* A link between two tables, each end named by `qualifiedTableName`.
|
|
41
|
+
*
|
|
42
|
+
* Qualified rather than bare, because a bare database name identifies a table only while no two
|
|
43
|
+
* SQL schemas hold it: `to: 'users'` cannot say whether it means `public.users` or
|
|
44
|
+
* `reporting.users`, and every consumer resolves these strings back to a table object. A table in
|
|
45
|
+
* the default schema has no prefix, so nothing about a single-schema analysis changes.
|
|
46
|
+
*/
|
|
17
47
|
interface Relation {
|
|
18
48
|
kind: 'one' | 'many' | 'manyToMany';
|
|
49
|
+
/** Qualified table name: `users`, or `reporting.users`. */
|
|
19
50
|
from: string;
|
|
51
|
+
/** Qualified table name: `users`, or `reporting.users`. */
|
|
20
52
|
to: string;
|
|
53
|
+
/** Qualified name of the join table, for m2m. */
|
|
21
54
|
via?: string;
|
|
22
55
|
}
|
|
23
56
|
interface Column {
|
|
24
57
|
name: string;
|
|
25
58
|
tsType: string;
|
|
59
|
+
/**
|
|
60
|
+
* A coarse label for the column's kind, not its type.
|
|
61
|
+
*
|
|
62
|
+
* `varchar`, `char` and `text` are all `TEXT` here, deliberately: exactly one consumer reads
|
|
63
|
+
* this, `isIntegerColumn`, and it only asks whether a number is whole. Use `sqlType` for the
|
|
64
|
+
* question this name suggests it answers.
|
|
65
|
+
*/
|
|
26
66
|
dbType: string;
|
|
67
|
+
/**
|
|
68
|
+
* The column's type as the database declares it, from Drizzle's own `getSQLType()`:
|
|
69
|
+
* `varchar(255)`, `numeric(10, 2)`, `timestamp with time zone`, `text[]`, or an enum's type
|
|
70
|
+
* name.
|
|
71
|
+
*
|
|
72
|
+
* The one fact about a column that no validator schema can carry, and the one every other fact
|
|
73
|
+
* here is a consequence of. A generator that emits metadata beside its schemas has nothing else
|
|
74
|
+
* to put in it: `dbType` is a label rather than a type, and the declared width lives in
|
|
75
|
+
* `maxLength`, the precision in `min`/`max`, and neither of those says which type produced it.
|
|
76
|
+
*
|
|
77
|
+
* The two Drizzle majors disagree about an array and are reconciled here, measured rather than
|
|
78
|
+
* assumed: 0.4x wraps the column in a `PgArray` whose own answer is already `text[]`, while v1
|
|
79
|
+
* leaves the class alone and raises `dimensions`, so its answer is the bare `text`. The suffix
|
|
80
|
+
* is added from `arrayDimensions` when the type does not already carry one, so a consumer
|
|
81
|
+
* cannot tell which major produced its metadata.
|
|
82
|
+
*
|
|
83
|
+
* Absent where the builder has no `getSQLType` or it throws. Nothing is guessed from the class
|
|
84
|
+
* name: an invented type string reads exactly like a real one, and there is no way for a
|
|
85
|
+
* consumer to tell them apart.
|
|
86
|
+
*/
|
|
87
|
+
sqlType?: string;
|
|
27
88
|
nullable: boolean;
|
|
28
89
|
hasDefault: boolean;
|
|
29
90
|
isGenerated: boolean;
|
|
30
91
|
defaultExpression?: string;
|
|
31
92
|
references?: {
|
|
32
93
|
table: string;
|
|
94
|
+
/** SQL schema of the referenced table, absent for the default one. See `ForeignKey`. */
|
|
95
|
+
schema?: string;
|
|
33
96
|
column: string;
|
|
34
97
|
onDelete?: string;
|
|
35
98
|
onUpdate?: string;
|
|
@@ -272,6 +335,17 @@ interface ForeignKey {
|
|
|
272
335
|
name?: string;
|
|
273
336
|
columns: string[];
|
|
274
337
|
foreignTable: string;
|
|
338
|
+
/**
|
|
339
|
+
* The SQL schema the referenced table lives in, absent for the default one, exactly as
|
|
340
|
+
* `Table.schema` is.
|
|
341
|
+
*
|
|
342
|
+
* `foreignTable` is a bare database name and Postgres lets two schemas hold the same one, so a
|
|
343
|
+
* key pointing at `reporting.users` recorded the identical string a key pointing at
|
|
344
|
+
* `public.users` records. Every consumer that resolves a key back to a table object did so by
|
|
345
|
+
* that string, and therefore resolved to whichever of the two it saw first. Use
|
|
346
|
+
* `qualifiedForeignTable` rather than reading the two fields apart.
|
|
347
|
+
*/
|
|
348
|
+
foreignSchema?: string;
|
|
275
349
|
foreignColumns: string[];
|
|
276
350
|
onDelete?: string;
|
|
277
351
|
onUpdate?: string;
|
|
@@ -279,6 +353,15 @@ interface ForeignKey {
|
|
|
279
353
|
interface Table {
|
|
280
354
|
name: string;
|
|
281
355
|
tsName: string;
|
|
356
|
+
/**
|
|
357
|
+
* The SQL schema the table was declared in, from `pgSchema('reporting').table(...)` and the
|
|
358
|
+
* MySQL and SingleStore equivalents. Absent for a table declared with plain `pgTable`, which is
|
|
359
|
+
* the only spelling of the default schema there is: Drizzle refuses `pgSchema('public')`
|
|
360
|
+
* outright, with "Postgres is using public schema by default".
|
|
361
|
+
*
|
|
362
|
+
* `name` stays bare, so two tables in two schemas share one. `qualifiedTableName` is what tells
|
|
363
|
+
* them apart, and is what every name-addressed surface in DRZL matches against.
|
|
364
|
+
*/
|
|
282
365
|
schema?: string;
|
|
283
366
|
columns: Column[];
|
|
284
367
|
primaryKey?: Key;
|
|
@@ -298,6 +381,28 @@ interface Enum {
|
|
|
298
381
|
name: string;
|
|
299
382
|
values: string[];
|
|
300
383
|
}
|
|
384
|
+
/**
|
|
385
|
+
* The one name that identifies a table across every SQL schema in an analysis.
|
|
386
|
+
*
|
|
387
|
+
* `reporting.users` where the table names a schema, and the bare `users` where it does not. The
|
|
388
|
+
* bare form for the default schema is deliberate and is what makes this safe to reach for
|
|
389
|
+
* everywhere: on a schema module that never calls `pgSchema`, and that is nearly all of them, this
|
|
390
|
+
* returns exactly `table.name`, so every file name, every export, every config pattern and every
|
|
391
|
+
* emitted path is byte for byte what it was.
|
|
392
|
+
*
|
|
393
|
+
* `public.users` is not produced here. Drizzle refuses `pgSchema('public')`, so no analysis can
|
|
394
|
+
* ever carry `schema: 'public'`, and a table with no schema *is* the public one. `public.` exists
|
|
395
|
+
* only as a spelling a config may use, resolved by `@drzl/cli`.
|
|
396
|
+
*/
|
|
397
|
+
declare function qualifiedTableName(table: {
|
|
398
|
+
name: string;
|
|
399
|
+
schema?: string;
|
|
400
|
+
}): string;
|
|
401
|
+
/** The same name, for the far end of a foreign key. */
|
|
402
|
+
declare function qualifiedForeignTable(fk: {
|
|
403
|
+
foreignTable: string;
|
|
404
|
+
foreignSchema?: string;
|
|
405
|
+
}): string;
|
|
301
406
|
interface Analysis {
|
|
302
407
|
drizzleVersion?: string;
|
|
303
408
|
dialect: Dialect;
|
|
@@ -532,4 +637,4 @@ declare class SchemaAnalyzer {
|
|
|
532
637
|
analyze(opts?: AnalyzeOptions): Promise<Analysis>;
|
|
533
638
|
}
|
|
534
639
|
|
|
535
|
-
export { type Analysis, type AnalyzeOptions, type Check, type Column, type ColumnRef, type ColumnShape, type Dialect, type Enum, type ForeignKey, type Index, type Issue, type Key, type Relation, SchemaAnalyzer, type Table, SchemaAnalyzer as default, describeV1Column, isDrizzleView, isReadOnlyRelation, isRelationsV2, readRelationsV2 };
|
|
640
|
+
export { type Analysis, type AnalyzeOptions, type Check, type Column, type ColumnRef, type ColumnShape, type Dialect, type Enum, type ForeignKey, type Index, type Issue, type Key, type Relation, SchemaAnalyzer, type Table, type UnnameableReason, SchemaAnalyzer as default, describeV1Column, isDrizzleView, isReadOnlyRelation, isRelationsV2, qualifiedForeignTable, qualifiedTableName, readRelationsV2 };
|
package/dist/index.d.ts
CHANGED
|
@@ -8,28 +8,91 @@ interface Issue {
|
|
|
8
8
|
level: 'info' | 'warn' | 'error';
|
|
9
9
|
message: string;
|
|
10
10
|
hint?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Where the issue is, as `table` or `table.column`.
|
|
13
|
+
*
|
|
14
|
+
* Declared since this interface existed and set by nothing, so every consumer wanting to group
|
|
15
|
+
* warnings by table had to read the names back out of the English in `message`. `drzl doctor` is
|
|
16
|
+
* the first such consumer and a report built by regex over prose breaks the first time a message
|
|
17
|
+
* is reworded, so the names are stated here instead.
|
|
18
|
+
*
|
|
19
|
+
* Still optional: an issue about the schema as a whole, such as an unidentifiable dialect, is
|
|
20
|
+
* about no table and says so by omitting this.
|
|
21
|
+
*/
|
|
11
22
|
path?: string;
|
|
12
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Why a column has no type, where "nobody has modelled it" is not the answer.
|
|
26
|
+
*
|
|
27
|
+
* The distinction exists because it changes the advice. A column class this file has no arm for is
|
|
28
|
+
* a gap someone can close, and the warning tells its author to say so. A Gel temporal column is
|
|
29
|
+
* not: the value is an instance of a class from the `gel` package, DRZL cannot import that package,
|
|
30
|
+
* and no generator could emit a check for it even knowing the name. Leaving it `unknown` is the
|
|
31
|
+
* measured answer rather than an omission, and its warning should say that instead of asking for a
|
|
32
|
+
* bug report that is already closed.
|
|
33
|
+
*/
|
|
34
|
+
type UnnameableReason = 'gel-temporal';
|
|
13
35
|
interface ColumnRef {
|
|
14
36
|
table: string;
|
|
15
37
|
column: string;
|
|
16
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* A link between two tables, each end named by `qualifiedTableName`.
|
|
41
|
+
*
|
|
42
|
+
* Qualified rather than bare, because a bare database name identifies a table only while no two
|
|
43
|
+
* SQL schemas hold it: `to: 'users'` cannot say whether it means `public.users` or
|
|
44
|
+
* `reporting.users`, and every consumer resolves these strings back to a table object. A table in
|
|
45
|
+
* the default schema has no prefix, so nothing about a single-schema analysis changes.
|
|
46
|
+
*/
|
|
17
47
|
interface Relation {
|
|
18
48
|
kind: 'one' | 'many' | 'manyToMany';
|
|
49
|
+
/** Qualified table name: `users`, or `reporting.users`. */
|
|
19
50
|
from: string;
|
|
51
|
+
/** Qualified table name: `users`, or `reporting.users`. */
|
|
20
52
|
to: string;
|
|
53
|
+
/** Qualified name of the join table, for m2m. */
|
|
21
54
|
via?: string;
|
|
22
55
|
}
|
|
23
56
|
interface Column {
|
|
24
57
|
name: string;
|
|
25
58
|
tsType: string;
|
|
59
|
+
/**
|
|
60
|
+
* A coarse label for the column's kind, not its type.
|
|
61
|
+
*
|
|
62
|
+
* `varchar`, `char` and `text` are all `TEXT` here, deliberately: exactly one consumer reads
|
|
63
|
+
* this, `isIntegerColumn`, and it only asks whether a number is whole. Use `sqlType` for the
|
|
64
|
+
* question this name suggests it answers.
|
|
65
|
+
*/
|
|
26
66
|
dbType: string;
|
|
67
|
+
/**
|
|
68
|
+
* The column's type as the database declares it, from Drizzle's own `getSQLType()`:
|
|
69
|
+
* `varchar(255)`, `numeric(10, 2)`, `timestamp with time zone`, `text[]`, or an enum's type
|
|
70
|
+
* name.
|
|
71
|
+
*
|
|
72
|
+
* The one fact about a column that no validator schema can carry, and the one every other fact
|
|
73
|
+
* here is a consequence of. A generator that emits metadata beside its schemas has nothing else
|
|
74
|
+
* to put in it: `dbType` is a label rather than a type, and the declared width lives in
|
|
75
|
+
* `maxLength`, the precision in `min`/`max`, and neither of those says which type produced it.
|
|
76
|
+
*
|
|
77
|
+
* The two Drizzle majors disagree about an array and are reconciled here, measured rather than
|
|
78
|
+
* assumed: 0.4x wraps the column in a `PgArray` whose own answer is already `text[]`, while v1
|
|
79
|
+
* leaves the class alone and raises `dimensions`, so its answer is the bare `text`. The suffix
|
|
80
|
+
* is added from `arrayDimensions` when the type does not already carry one, so a consumer
|
|
81
|
+
* cannot tell which major produced its metadata.
|
|
82
|
+
*
|
|
83
|
+
* Absent where the builder has no `getSQLType` or it throws. Nothing is guessed from the class
|
|
84
|
+
* name: an invented type string reads exactly like a real one, and there is no way for a
|
|
85
|
+
* consumer to tell them apart.
|
|
86
|
+
*/
|
|
87
|
+
sqlType?: string;
|
|
27
88
|
nullable: boolean;
|
|
28
89
|
hasDefault: boolean;
|
|
29
90
|
isGenerated: boolean;
|
|
30
91
|
defaultExpression?: string;
|
|
31
92
|
references?: {
|
|
32
93
|
table: string;
|
|
94
|
+
/** SQL schema of the referenced table, absent for the default one. See `ForeignKey`. */
|
|
95
|
+
schema?: string;
|
|
33
96
|
column: string;
|
|
34
97
|
onDelete?: string;
|
|
35
98
|
onUpdate?: string;
|
|
@@ -272,6 +335,17 @@ interface ForeignKey {
|
|
|
272
335
|
name?: string;
|
|
273
336
|
columns: string[];
|
|
274
337
|
foreignTable: string;
|
|
338
|
+
/**
|
|
339
|
+
* The SQL schema the referenced table lives in, absent for the default one, exactly as
|
|
340
|
+
* `Table.schema` is.
|
|
341
|
+
*
|
|
342
|
+
* `foreignTable` is a bare database name and Postgres lets two schemas hold the same one, so a
|
|
343
|
+
* key pointing at `reporting.users` recorded the identical string a key pointing at
|
|
344
|
+
* `public.users` records. Every consumer that resolves a key back to a table object did so by
|
|
345
|
+
* that string, and therefore resolved to whichever of the two it saw first. Use
|
|
346
|
+
* `qualifiedForeignTable` rather than reading the two fields apart.
|
|
347
|
+
*/
|
|
348
|
+
foreignSchema?: string;
|
|
275
349
|
foreignColumns: string[];
|
|
276
350
|
onDelete?: string;
|
|
277
351
|
onUpdate?: string;
|
|
@@ -279,6 +353,15 @@ interface ForeignKey {
|
|
|
279
353
|
interface Table {
|
|
280
354
|
name: string;
|
|
281
355
|
tsName: string;
|
|
356
|
+
/**
|
|
357
|
+
* The SQL schema the table was declared in, from `pgSchema('reporting').table(...)` and the
|
|
358
|
+
* MySQL and SingleStore equivalents. Absent for a table declared with plain `pgTable`, which is
|
|
359
|
+
* the only spelling of the default schema there is: Drizzle refuses `pgSchema('public')`
|
|
360
|
+
* outright, with "Postgres is using public schema by default".
|
|
361
|
+
*
|
|
362
|
+
* `name` stays bare, so two tables in two schemas share one. `qualifiedTableName` is what tells
|
|
363
|
+
* them apart, and is what every name-addressed surface in DRZL matches against.
|
|
364
|
+
*/
|
|
282
365
|
schema?: string;
|
|
283
366
|
columns: Column[];
|
|
284
367
|
primaryKey?: Key;
|
|
@@ -298,6 +381,28 @@ interface Enum {
|
|
|
298
381
|
name: string;
|
|
299
382
|
values: string[];
|
|
300
383
|
}
|
|
384
|
+
/**
|
|
385
|
+
* The one name that identifies a table across every SQL schema in an analysis.
|
|
386
|
+
*
|
|
387
|
+
* `reporting.users` where the table names a schema, and the bare `users` where it does not. The
|
|
388
|
+
* bare form for the default schema is deliberate and is what makes this safe to reach for
|
|
389
|
+
* everywhere: on a schema module that never calls `pgSchema`, and that is nearly all of them, this
|
|
390
|
+
* returns exactly `table.name`, so every file name, every export, every config pattern and every
|
|
391
|
+
* emitted path is byte for byte what it was.
|
|
392
|
+
*
|
|
393
|
+
* `public.users` is not produced here. Drizzle refuses `pgSchema('public')`, so no analysis can
|
|
394
|
+
* ever carry `schema: 'public'`, and a table with no schema *is* the public one. `public.` exists
|
|
395
|
+
* only as a spelling a config may use, resolved by `@drzl/cli`.
|
|
396
|
+
*/
|
|
397
|
+
declare function qualifiedTableName(table: {
|
|
398
|
+
name: string;
|
|
399
|
+
schema?: string;
|
|
400
|
+
}): string;
|
|
401
|
+
/** The same name, for the far end of a foreign key. */
|
|
402
|
+
declare function qualifiedForeignTable(fk: {
|
|
403
|
+
foreignTable: string;
|
|
404
|
+
foreignSchema?: string;
|
|
405
|
+
}): string;
|
|
301
406
|
interface Analysis {
|
|
302
407
|
drizzleVersion?: string;
|
|
303
408
|
dialect: Dialect;
|
|
@@ -532,4 +637,4 @@ declare class SchemaAnalyzer {
|
|
|
532
637
|
analyze(opts?: AnalyzeOptions): Promise<Analysis>;
|
|
533
638
|
}
|
|
534
639
|
|
|
535
|
-
export { type Analysis, type AnalyzeOptions, type Check, type Column, type ColumnRef, type ColumnShape, type Dialect, type Enum, type ForeignKey, type Index, type Issue, type Key, type Relation, SchemaAnalyzer, type Table, SchemaAnalyzer as default, describeV1Column, isDrizzleView, isReadOnlyRelation, isRelationsV2, readRelationsV2 };
|
|
640
|
+
export { type Analysis, type AnalyzeOptions, type Check, type Column, type ColumnRef, type ColumnShape, type Dialect, type Enum, type ForeignKey, type Index, type Issue, type Key, type Relation, SchemaAnalyzer, type Table, type UnnameableReason, SchemaAnalyzer as default, describeV1Column, isDrizzleView, isReadOnlyRelation, isRelationsV2, qualifiedForeignTable, qualifiedTableName, readRelationsV2 };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
+
function qualifiedTableName(table) {
|
|
3
|
+
return table.schema ? `${table.schema}.${table.name}` : table.name;
|
|
4
|
+
}
|
|
5
|
+
function qualifiedForeignTable(fk) {
|
|
6
|
+
return fk.foreignSchema ? `${fk.foreignSchema}.${fk.foreignTable}` : fk.foreignTable;
|
|
7
|
+
}
|
|
2
8
|
function renderSqlLiteral(v) {
|
|
3
9
|
if (v === null || v === void 0) return "NULL";
|
|
4
10
|
if (typeof v === "number" || typeof v === "bigint") return String(v);
|
|
@@ -344,27 +350,43 @@ function isRelationsV2(val) {
|
|
|
344
350
|
)
|
|
345
351
|
);
|
|
346
352
|
}
|
|
353
|
+
function qualifiedNameOfDrizzleTable(tbl) {
|
|
354
|
+
const name = getSymbolOf(tbl, "drizzle:Name");
|
|
355
|
+
if (typeof name !== "string" || !name) return void 0;
|
|
356
|
+
const schema = getSymbolOf(tbl, "drizzle:Schema");
|
|
357
|
+
return typeof schema === "string" && schema ? `${schema}.${name}` : name;
|
|
358
|
+
}
|
|
347
359
|
function readRelationsV2(val, issues = []) {
|
|
348
360
|
const out = [];
|
|
349
361
|
for (const [tableKey, entry] of Object.entries(val)) {
|
|
350
|
-
const from =
|
|
362
|
+
const from = qualifiedNameOfDrizzleTable(entry.table) ?? entry.name ?? tableKey;
|
|
351
363
|
for (const [fieldName, r] of Object.entries(entry.relations ?? {})) {
|
|
352
|
-
const to = r?.targetTableName;
|
|
364
|
+
const to = qualifiedNameOfDrizzleTable(r?.targetTable) ?? r?.targetTableName;
|
|
353
365
|
if (typeof to !== "string" || !to) {
|
|
354
366
|
issues.push({
|
|
355
367
|
code: "DRZL_ANL_REL_V2",
|
|
356
368
|
level: "warn",
|
|
357
|
-
message: `Relation "${fieldName}" on "${from}" names no target table and was skipped
|
|
369
|
+
message: `Relation "${fieldName}" on "${from}" names no target table and was skipped.`,
|
|
370
|
+
path: from
|
|
358
371
|
});
|
|
359
372
|
continue;
|
|
360
373
|
}
|
|
361
|
-
const via =
|
|
374
|
+
const via = qualifiedNameOfDrizzleTable(r.throughTable) ?? qualifiedNameOfDrizzleTable(r.through?.sourceTable) ?? void 0;
|
|
362
375
|
if (via) out.push({ kind: "manyToMany", from, to, via });
|
|
363
376
|
else out.push({ kind: r.relationType === "many" ? "many" : "one", from, to });
|
|
364
377
|
}
|
|
365
378
|
}
|
|
366
379
|
return out;
|
|
367
380
|
}
|
|
381
|
+
function unknownColumnHint(reason) {
|
|
382
|
+
if (reason === "custom") {
|
|
383
|
+
return "A customType has no runtime shape to read. Declare it with .$type<T>() and turn on typedColumns to give the validator the type.";
|
|
384
|
+
}
|
|
385
|
+
if (reason === "gel-temporal") {
|
|
386
|
+
return "A Gel temporal column holds an instance of a class from the `gel` package, which DRZL cannot import, so it is left untyped on purpose rather than guessed at. Turn on typedColumns to recover the declared type, and validate the value yourself.";
|
|
387
|
+
}
|
|
388
|
+
return "Open an issue naming the column type so it can be modelled, or declare it with .$type<T>() and turn on typedColumns.";
|
|
389
|
+
}
|
|
368
390
|
var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
369
391
|
constructor(schemaPath) {
|
|
370
392
|
this.schemaPath = schemaPath;
|
|
@@ -415,6 +437,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
415
437
|
code: "DRZL_ANL_EXTRACONFIG",
|
|
416
438
|
level: "warn",
|
|
417
439
|
message: `Could not evaluate the extra-config callback for table "${tableName}": ${e.message}`,
|
|
440
|
+
path: tableName,
|
|
418
441
|
hint: "Indexes, composite keys, checks and table-level foreign keys will be missing for this table."
|
|
419
442
|
});
|
|
420
443
|
return [];
|
|
@@ -468,9 +491,11 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
468
491
|
};
|
|
469
492
|
const foreignColumnsObj = this.getSymbol(ref.foreignTable, "drizzle:Columns") ?? {};
|
|
470
493
|
const toForeignTs = this.dbToTsNames(foreignColumnsObj);
|
|
494
|
+
const foreignSchema = this.getSymbol(ref.foreignTable, "drizzle:Schema");
|
|
471
495
|
return {
|
|
472
496
|
columns: (ref.columns ?? []).map((c) => toTs(c?.name)),
|
|
473
497
|
foreignTable: this.getSymbol(ref.foreignTable, "drizzle:Name") ?? "unknown",
|
|
498
|
+
...foreignSchema ? { foreignSchema } : {},
|
|
474
499
|
foreignColumns: (ref.foreignColumns ?? []).map((c) => toForeignTs(c?.name)),
|
|
475
500
|
onDelete: action(fk?.onDelete, fk?._onDelete),
|
|
476
501
|
onUpdate: action(fk?.onUpdate, fk?._onUpdate),
|
|
@@ -519,7 +544,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
519
544
|
* on each returned value, so the stand-in results must carry that method or the call throws.
|
|
520
545
|
*/
|
|
521
546
|
readRelationsObject(val, exportName, issues) {
|
|
522
|
-
const from =
|
|
547
|
+
const from = qualifiedNameOfDrizzleTable(val.table) ?? exportName;
|
|
523
548
|
const make = (kind) => (table, cfg) => ({
|
|
524
549
|
kind,
|
|
525
550
|
referencedTable: table,
|
|
@@ -533,7 +558,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
533
558
|
const built = val.config({ one: make("one"), many: make("many") });
|
|
534
559
|
const out = [];
|
|
535
560
|
for (const rel of Object.values(built ?? {})) {
|
|
536
|
-
const to =
|
|
561
|
+
const to = qualifiedNameOfDrizzleTable(rel?.referencedTable);
|
|
537
562
|
if (to) out.push({ kind: rel.kind, from, to });
|
|
538
563
|
}
|
|
539
564
|
return out;
|
|
@@ -542,6 +567,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
542
567
|
code: "DRZL_ANL_RELATIONS",
|
|
543
568
|
level: "warn",
|
|
544
569
|
message: `Could not read the relations declared in "${exportName}": ${e.message}`,
|
|
570
|
+
path: from,
|
|
545
571
|
hint: "Relations for this table will be missing from the analysis."
|
|
546
572
|
});
|
|
547
573
|
return [];
|
|
@@ -562,10 +588,11 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
562
588
|
if (fks.length < 2) continue;
|
|
563
589
|
const fkCols = new Set(fks.flatMap((f) => f.columns));
|
|
564
590
|
if (!t.columns.every((c) => fkCols.has(c.name))) continue;
|
|
565
|
-
const targets = [...new Set(fks.map(
|
|
591
|
+
const targets = [...new Set(fks.map(qualifiedForeignTable))];
|
|
566
592
|
if (targets.length !== 2) continue;
|
|
567
|
-
|
|
568
|
-
out.push({ kind: "manyToMany", from: targets[
|
|
593
|
+
const via = qualifiedTableName(t);
|
|
594
|
+
out.push({ kind: "manyToMany", from: targets[0], to: targets[1], via });
|
|
595
|
+
out.push({ kind: "manyToMany", from: targets[1], to: targets[0], via });
|
|
569
596
|
}
|
|
570
597
|
return out;
|
|
571
598
|
}
|
|
@@ -887,7 +914,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
887
914
|
if (/Bool/i.test(ctor)) return { tsType: "boolean", dbType: "BOOLEAN" };
|
|
888
915
|
if (/TimestampTz/i.test(ctor)) return { tsType: "Date", dbType: "TIMESTAMPTZ" };
|
|
889
916
|
if (/Timestamp|LocalDateString|LocalTime|DateDuration|RelDuration|Duration/i.test(ctor))
|
|
890
|
-
return { tsType: "unknown", dbType: "UNKNOWN" };
|
|
917
|
+
return { tsType: "unknown", dbType: "UNKNOWN", unnameable: "gel-temporal" };
|
|
891
918
|
}
|
|
892
919
|
return { tsType: "unknown", dbType: "UNKNOWN" };
|
|
893
920
|
}
|
|
@@ -903,7 +930,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
903
930
|
const uniqueGroups = /* @__PURE__ */ new Map();
|
|
904
931
|
for (const [colName, outerCol] of Object.entries(columnsObj)) {
|
|
905
932
|
const { element: col, dimensions: arrayDims } = unwrapArrayColumn(outerCol);
|
|
906
|
-
|
|
933
|
+
const mapped = this.mapColumnType(col);
|
|
934
|
+
let { tsType, dbType } = mapped;
|
|
907
935
|
if (tsType === "unknown" && /At$/.test(colName)) {
|
|
908
936
|
tsType = "Date";
|
|
909
937
|
dbType = "INTEGER";
|
|
@@ -946,13 +974,26 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
946
974
|
code: "DRZL_ANL_UNKNOWN_COLUMN",
|
|
947
975
|
level: "warn",
|
|
948
976
|
message: `Column "${colName}" on table "${tsName}" has no known type${sqlType2 ? ` (SQL type ${sqlType2})` : ""}, so its validator will accept any value.`,
|
|
949
|
-
|
|
977
|
+
path: `${tsName}.${colName}`,
|
|
978
|
+
hint: unknownColumnHint(shape === "custom" ? "custom" : mapped.unnameable)
|
|
950
979
|
});
|
|
951
980
|
}
|
|
981
|
+
const dims = arrayDims || v1?.arrayDimensions || 0;
|
|
982
|
+
const declaredSqlType = (() => {
|
|
983
|
+
let raw;
|
|
984
|
+
try {
|
|
985
|
+
raw = typeof outerCol?.getSQLType === "function" ? outerCol.getSQLType() : void 0;
|
|
986
|
+
} catch {
|
|
987
|
+
return void 0;
|
|
988
|
+
}
|
|
989
|
+
if (typeof raw !== "string" || !raw) return void 0;
|
|
990
|
+
return raw.endsWith("]") ? raw : raw + "[]".repeat(dims);
|
|
991
|
+
})();
|
|
952
992
|
columns.push({
|
|
953
993
|
name: colName,
|
|
954
994
|
tsType,
|
|
955
995
|
dbType,
|
|
996
|
+
...declaredSqlType ? { sqlType: declaredSqlType } : {},
|
|
956
997
|
nullable,
|
|
957
998
|
hasDefault,
|
|
958
999
|
isGenerated,
|
|
@@ -1029,6 +1070,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
1029
1070
|
if (!col) continue;
|
|
1030
1071
|
col.references = {
|
|
1031
1072
|
table: fk.foreignTable,
|
|
1073
|
+
...fk.foreignSchema ? { schema: fk.foreignSchema } : {},
|
|
1032
1074
|
column: fk.foreignColumns[0],
|
|
1033
1075
|
onDelete: fk.onDelete,
|
|
1034
1076
|
onUpdate: fk.onUpdate
|
|
@@ -1101,9 +1143,11 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
1101
1143
|
}
|
|
1102
1144
|
}
|
|
1103
1145
|
if (opts.includeRelations) {
|
|
1146
|
+
const self = qualifiedTableName(table);
|
|
1104
1147
|
for (const fk of table.foreignKeys ?? []) {
|
|
1105
|
-
|
|
1106
|
-
relations.push({ kind: "
|
|
1148
|
+
const target = qualifiedForeignTable(fk);
|
|
1149
|
+
relations.push({ kind: "one", from: self, to: target });
|
|
1150
|
+
relations.push({ kind: "many", from: target, to: self });
|
|
1107
1151
|
}
|
|
1108
1152
|
}
|
|
1109
1153
|
} else if (this.isRelationsObject(val)) {
|
|
@@ -1131,7 +1175,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
1131
1175
|
issues.push({
|
|
1132
1176
|
code: "DRZL_ANL_TABLE",
|
|
1133
1177
|
level: "warn",
|
|
1134
|
-
message: `Failed to analyze export ${name}: ${String(e)}
|
|
1178
|
+
message: `Failed to analyze export ${name}: ${String(e)}`,
|
|
1179
|
+
path: name
|
|
1135
1180
|
});
|
|
1136
1181
|
}
|
|
1137
1182
|
}
|
|
@@ -1176,11 +1221,21 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
1176
1221
|
relations.push(...this.inferManyToMany(tables));
|
|
1177
1222
|
}
|
|
1178
1223
|
if (opts.includeRelations && opts.includeHeuristicRelations) {
|
|
1179
|
-
const
|
|
1180
|
-
const
|
|
1181
|
-
|
|
1182
|
-
if (
|
|
1183
|
-
|
|
1224
|
+
const byBareName = /* @__PURE__ */ new Map();
|
|
1225
|
+
for (const t of tables) {
|
|
1226
|
+
const list = byBareName.get(t.name);
|
|
1227
|
+
if (list) list.push(t);
|
|
1228
|
+
else byBareName.set(t.name, [t]);
|
|
1229
|
+
}
|
|
1230
|
+
const findTarget = (base, from) => {
|
|
1231
|
+
for (const candidate of [base, base + "s", base + "es"]) {
|
|
1232
|
+
const hits = byBareName.get(candidate);
|
|
1233
|
+
if (!hits?.length) continue;
|
|
1234
|
+
const sameSchema = hits.filter((t) => t.schema === from.schema);
|
|
1235
|
+
if (sameSchema.length === 1) return qualifiedTableName(sameSchema[0]);
|
|
1236
|
+
if (hits.length === 1) return qualifiedTableName(hits[0]);
|
|
1237
|
+
return void 0;
|
|
1238
|
+
}
|
|
1184
1239
|
return void 0;
|
|
1185
1240
|
};
|
|
1186
1241
|
for (const t of tables) {
|
|
@@ -1188,8 +1243,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
1188
1243
|
if (c.references) continue;
|
|
1189
1244
|
if (c.name.endsWith("Id")) {
|
|
1190
1245
|
const base = c.name.slice(0, -2);
|
|
1191
|
-
const target = findTarget(base);
|
|
1192
|
-
if (target) relations.push({ kind: "one", from: t
|
|
1246
|
+
const target = findTarget(base, t);
|
|
1247
|
+
if (target) relations.push({ kind: "one", from: qualifiedTableName(t), to: target });
|
|
1193
1248
|
}
|
|
1194
1249
|
}
|
|
1195
1250
|
}
|
|
@@ -1360,5 +1415,7 @@ export {
|
|
|
1360
1415
|
isDrizzleView,
|
|
1361
1416
|
isReadOnlyRelation,
|
|
1362
1417
|
isRelationsV2,
|
|
1418
|
+
qualifiedForeignTable,
|
|
1419
|
+
qualifiedTableName,
|
|
1363
1420
|
readRelationsV2
|
|
1364
1421
|
};
|