@drzl/analyzer 1.6.0 → 1.8.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 CHANGED
@@ -31,7 +31,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  SchemaAnalyzer: () => SchemaAnalyzer,
34
- default: () => index_default
34
+ default: () => index_default,
35
+ describeV1Column: () => describeV1Column
35
36
  });
36
37
  module.exports = __toCommonJS(index_exports);
37
38
  var import_meta = {};
@@ -43,6 +44,175 @@ function renderSqlLiteral(v) {
43
44
  if (Array.isArray(v)) return `(${v.map(renderSqlLiteral).join(", ")})`;
44
45
  return `'${String(v).replace(/'/g, "''")}'`;
45
46
  }
47
+ var MYSQL_TEXT_CAPS = {
48
+ tinytext: 255,
49
+ text: 65535,
50
+ mediumtext: 16777215,
51
+ longtext: 4294967295,
52
+ tinyblob: 255,
53
+ blob: 65535,
54
+ mediumblob: 16777215,
55
+ longblob: 4294967295
56
+ };
57
+ var V1_FLOAT_BOUNDS = {
58
+ float: ["-8388608", "8388607"],
59
+ // real / float4, 2^23
60
+ double: ["-140737488355328", "140737488355327"]
61
+ // double precision / float8, 2^47
62
+ };
63
+ function describeV1Column(column) {
64
+ const codec = column?.codec;
65
+ const dataType = column?.dataType;
66
+ if (typeof dataType !== "string") return null;
67
+ if (dataType === "custom") {
68
+ const sqlType = typeof column?.getSQLType === "function" ? column.getSQLType() : void 0;
69
+ return {
70
+ tsType: "unknown",
71
+ dbType: typeof sqlType === "string" && sqlType ? sqlType.toUpperCase() : "UNKNOWN",
72
+ shape: { kind: "custom", sqlType: typeof sqlType === "string" ? sqlType : void 0 }
73
+ };
74
+ }
75
+ const [js, semantic = ""] = dataType.split(" ");
76
+ if (typeof codec !== "string" && !semantic) return null;
77
+ const out = {};
78
+ switch (semantic) {
79
+ case "int8":
80
+ case "int16":
81
+ case "int24":
82
+ case "int32":
83
+ case "int53":
84
+ case "uint53":
85
+ case "int64": {
86
+ const range = {
87
+ int8: ["-128", "127"],
88
+ int16: ["-32768", "32767"],
89
+ int24: ["-8388608", "8388607"],
90
+ int32: ["-2147483648", "2147483647"],
91
+ int53: ["-9007199254740991", "9007199254740991"],
92
+ // MySQL `serial` is `bigint unsigned auto_increment`, so it starts at 0 rather than
93
+ // spanning the signed range.
94
+ uint53: ["0", "9007199254740991"],
95
+ int64: ["-9223372036854775808", "9223372036854775807"]
96
+ }[semantic];
97
+ [out.min, out.max] = range;
98
+ out.integer = true;
99
+ out.tsType = js === "bigint" ? "bigint" : "number";
100
+ out.dbType = semantic === "int8" ? "TINYINT" : semantic === "int16" ? "SMALLINT" : semantic === "int24" ? "MEDIUMINT" : semantic === "int32" ? "INTEGER" : "BIGINT";
101
+ break;
102
+ }
103
+ case "year":
104
+ out.tsType = "number";
105
+ out.dbType = "YEAR";
106
+ out.integer = true;
107
+ [out.min, out.max] = ["1901", "2155"];
108
+ break;
109
+ case "float":
110
+ case "double": {
111
+ [out.min, out.max] = V1_FLOAT_BOUNDS[semantic];
112
+ out.integer = false;
113
+ out.tsType = "number";
114
+ out.dbType = semantic === "float" ? "REAL" : "DOUBLE";
115
+ break;
116
+ }
117
+ case "uuid":
118
+ out.tsType = "string";
119
+ out.dbType = "UUID";
120
+ out.format = "uuid";
121
+ break;
122
+ case "numeric":
123
+ out.tsType = "string";
124
+ out.dbType = "NUMERIC";
125
+ break;
126
+ case "json":
127
+ out.tsType = "any";
128
+ out.dbType = codec === "jsonb" ? "JSONB" : "JSON";
129
+ out.shape = { kind: "json" };
130
+ break;
131
+ case "buffer":
132
+ out.tsType = "Buffer";
133
+ out.dbType = "BYTEA";
134
+ out.shape = { kind: "buffer" };
135
+ break;
136
+ case "date":
137
+ out.tsType = js === "string" ? "string" : "Date";
138
+ out.dbType = codec?.startsWith("timestamp") ? "TIMESTAMP" : "DATE";
139
+ break;
140
+ case "timestamp":
141
+ out.tsType = js === "string" ? "string" : "Date";
142
+ out.dbType = "TIMESTAMP";
143
+ break;
144
+ case "time":
145
+ out.tsType = "string";
146
+ out.dbType = "TIME";
147
+ break;
148
+ case "interval":
149
+ out.tsType = "string";
150
+ out.dbType = "INTERVAL";
151
+ break;
152
+ case "inet":
153
+ case "cidr":
154
+ case "macaddr":
155
+ out.tsType = "string";
156
+ out.dbType = semantic.toUpperCase();
157
+ break;
158
+ case "binary":
159
+ out.tsType = "string";
160
+ out.dbType = codec === "bit" ? "BIT" : "BINARY";
161
+ out.shape = {
162
+ kind: "bitstring",
163
+ length: declaredLength(column),
164
+ // A Postgres `bit(3)` holds exactly three digits; a MySQL `binary(4)`/`varbinary(16)`
165
+ // holds at most that many, which is why `''` is valid there and not here.
166
+ exact: codec === "bit"
167
+ };
168
+ break;
169
+ case "point":
170
+ case "geometry":
171
+ out.tsType = "[number, number]";
172
+ out.dbType = semantic.toUpperCase();
173
+ out.shape = { kind: "tuple", length: 2 };
174
+ break;
175
+ case "line":
176
+ out.tsType = "[number, number, number]";
177
+ out.dbType = "LINE";
178
+ out.shape = { kind: "tuple", length: 3 };
179
+ break;
180
+ case "vector":
181
+ out.tsType = "number[]";
182
+ out.dbType = "VECTOR";
183
+ out.shape = { kind: "numberVector", length: declaredLength(column) };
184
+ break;
185
+ case "enum":
186
+ out.tsType = "string";
187
+ out.dbType = "TEXT";
188
+ break;
189
+ default:
190
+ if (js === "boolean") {
191
+ out.tsType = "boolean";
192
+ out.dbType = "BOOLEAN";
193
+ } else if (js === "number") {
194
+ out.tsType = "number";
195
+ out.dbType = "NUMERIC";
196
+ out.integer = false;
197
+ [out.min, out.max] = ["-9007199254740991", "9007199254740991"];
198
+ } else if (js === "string") {
199
+ out.tsType = "string";
200
+ out.dbType = codec === "varchar" ? "VARCHAR" : codec === "char" ? "CHAR" : "TEXT";
201
+ const kind = String(column?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
202
+ const cap = codec && kind.startsWith("MySql") ? MYSQL_TEXT_CAPS[codec] : void 0;
203
+ if (cap) out.maxLength = cap;
204
+ } else {
205
+ return null;
206
+ }
207
+ }
208
+ const dims = column?.dimensions;
209
+ if (typeof dims === "number" && dims >= 1) out.arrayDimensions = dims;
210
+ return out;
211
+ }
212
+ function declaredLength(column) {
213
+ const n = column?.length ?? column?.config?.length ?? column?.config?.dimensions;
214
+ return typeof n === "number" && Number.isFinite(n) && n > 0 ? n : void 0;
215
+ }
46
216
  var _SchemaAnalyzer = class _SchemaAnalyzer {
47
217
  constructor(schemaPath) {
48
218
  this.schemaPath = schemaPath;
@@ -273,6 +443,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
273
443
  const range = _SchemaAnalyzer.INT_RANGES[ctor];
274
444
  if (range) {
275
445
  [out.min, out.max] = range;
446
+ out.integer = true;
276
447
  }
277
448
  if (/^(Pg)?UUID$/i.test(ctor) || /Uuid$/i.test(ctor)) out.format = "uuid";
278
449
  return out;
@@ -346,7 +517,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
346
517
  if (/Text|Varchar|Char|Uuid/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
347
518
  if (/Inet|Cidr|Macaddr8?|Uuid/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
348
519
  if (/Point|Line/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
349
- if (/TimestampString|DateString/i.test(ctor)) return { tsType: "string", dbType: "TIMESTAMP" };
520
+ if (/TimestampString|DateString/i.test(ctor))
521
+ return { tsType: "string", dbType: "TIMESTAMP" };
350
522
  if (/Timestamptz/i.test(ctor)) return { tsType: "Date", dbType: "TIMESTAMPTZ" };
351
523
  if (/Timestamp/i.test(ctor)) return { tsType: "Date", dbType: "TIMESTAMP" };
352
524
  if (/Date/i.test(ctor)) return { tsType: "Date", dbType: "TIMESTAMP" };
@@ -355,8 +527,10 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
355
527
  if (/\bInt(eger)?\b|Serial/i.test(ctor)) return { tsType: "number", dbType: "INTEGER" };
356
528
  if (/BigInt/i.test(ctor)) return { tsType: "bigint", dbType: "BIGINT" };
357
529
  if (/Bool/i.test(ctor)) return { tsType: "boolean", dbType: "BOOLEAN" };
358
- if (/Jsonb?/i.test(ctor)) return { tsType: "any", dbType: /Jsonb/i.test(ctor) ? "JSONB" : "JSON" };
359
- if (/Numeric|Float|Double|Real/i.test(ctor)) return { tsType: "number", dbType: "NUMERIC" };
530
+ if (/Jsonb?/i.test(ctor))
531
+ return { tsType: "any", dbType: /Jsonb/i.test(ctor) ? "JSONB" : "JSON" };
532
+ if (/Numeric|Float|Double|Real/i.test(ctor))
533
+ return { tsType: "number", dbType: "NUMERIC" };
360
534
  }
361
535
  if (/^MySql/i.test(ctor)) {
362
536
  if (/BigInt64/i.test(ctor)) return { tsType: "bigint", dbType: "BIGINT" };
@@ -408,7 +582,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
408
582
  if (/TimestampTz/i.test(ctor)) return { tsType: "Date", dbType: "TIMESTAMPTZ" };
409
583
  if (/Timestamp/i.test(ctor)) return { tsType: "string", dbType: "TIMESTAMP" };
410
584
  if (/LocalDateString|LocalTime/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
411
- if (/DateDuration|RelDuration|Duration/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
585
+ if (/DateDuration|RelDuration|Duration/i.test(ctor))
586
+ return { tsType: "string", dbType: "TEXT" };
412
587
  }
413
588
  return { tsType: "unknown", dbType: "UNKNOWN" };
414
589
  }
@@ -430,7 +605,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
430
605
  }
431
606
  const ev = col?.enumValues;
432
607
  const nullable = !col?.notNull && !col?.config?.notNull;
433
- const isGenerated = !!(col?.autoIncrement || col?.isGenerated);
608
+ const generatedIdentity = col?.generatedIdentity;
609
+ const isGenerated = !!(col?.generated || generatedIdentity?.type === "always" || col?.isGenerated);
434
610
  const hasDefault = col?.hasDefault === true || col?.default !== void 0 || col?.config?.default !== void 0 || col?.defaultFn !== void 0 || isGenerated;
435
611
  const references = void 0;
436
612
  const isUnique = !!(col?.isUnique || col?.config?.isUnique);
@@ -443,6 +619,9 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
443
619
  arr.push(colName);
444
620
  uniqueGroups.set(uName, arr);
445
621
  }
622
+ const v1 = describeV1Column(col);
623
+ const constraints = this.columnConstraints(col);
624
+ if (v1?.shape) delete constraints.maxLength;
446
625
  columns.push({
447
626
  name: colName,
448
627
  tsType,
@@ -453,7 +632,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
453
632
  defaultExpression: void 0,
454
633
  references,
455
634
  enumValues: Array.isArray(ev) ? ev : void 0,
456
- ...this.columnConstraints(col)
635
+ ...constraints,
636
+ ...v1 ?? {}
457
637
  });
458
638
  }
459
639
  const name = this.getSymbol(tbl, "drizzle:Name") || tsName;
@@ -693,24 +873,28 @@ _SchemaAnalyzer.INT_RANGES = {
693
873
  SQLiteInteger: ["-9223372036854775808", "9223372036854775807"],
694
874
  // 16 bit
695
875
  PgSmallInt: ["-32768", "32767"],
696
- PgSmallSerial: ["1", "32767"],
876
+ // A serial is an ordinary integer column that happens to default from a sequence. The
877
+ // sequence starts at 1, the column does not: `INSERT ... (id) VALUES (-5)` is accepted by
878
+ // Postgres and is how backfills and sentinel rows get written. Lower-bounding these at 1
879
+ // rejected valid rows, and `drizzle-orm/zod` bounds them by the integer width too.
880
+ PgSmallSerial: ["-32768", "32767"],
697
881
  MySqlSmallInt: ["-32768", "32767"],
698
882
  SingleStoreSmallInt: ["-32768", "32767"],
699
883
  // 24 bit
700
884
  MySqlMediumInt: ["-8388608", "8388607"],
701
885
  // 32 bit
702
886
  PgInteger: ["-2147483648", "2147483647"],
703
- PgSerial: ["1", "2147483647"],
887
+ PgSerial: ["-2147483648", "2147483647"],
704
888
  MySqlInt: ["-2147483648", "2147483647"],
705
889
  SingleStoreInt: ["-2147483648", "2147483647"],
706
890
  // 53 bit, the JS safe-integer ceiling rather than the column's
707
891
  PgBigInt53: ["-9007199254740991", "9007199254740991"],
708
- PgBigSerial53: ["1", "9007199254740991"],
892
+ PgBigSerial53: ["-9007199254740991", "9007199254740991"],
709
893
  MySqlBigInt53: ["-9007199254740991", "9007199254740991"],
710
894
  SingleStoreBigInt53: ["-9007199254740991", "9007199254740991"],
711
895
  // 64 bit, representable because the value is a bigint
712
896
  PgBigInt64: ["-9223372036854775808", "9223372036854775807"],
713
- PgBigSerial64: ["1", "9223372036854775807"],
897
+ PgBigSerial64: ["-9223372036854775808", "9223372036854775807"],
714
898
  MySqlBigInt64: ["-9223372036854775808", "9223372036854775807"],
715
899
  SingleStoreBigInt64: ["-9223372036854775808", "9223372036854775807"]
716
900
  };
@@ -718,5 +902,6 @@ var SchemaAnalyzer = _SchemaAnalyzer;
718
902
  var index_default = SchemaAnalyzer;
719
903
  // Annotate the CommonJS export names for ESM import in node:
720
904
  0 && (module.exports = {
721
- SchemaAnalyzer
905
+ SchemaAnalyzer,
906
+ describeV1Column
722
907
  });
package/dist/index.d.cts CHANGED
@@ -53,9 +53,81 @@ interface Column {
53
53
  */
54
54
  min?: string;
55
55
  max?: string;
56
+ /**
57
+ * Whether a numeric column holds whole numbers only.
58
+ *
59
+ * Stated rather than inferred. Generators used to read "has both bounds" as "is an integer",
60
+ * which held only while integers were the sole bounded type; bounding `real` and `double
61
+ * precision` promptly made every float schema reject `1.5`. Absent on a pre-1.7 analysis, and
62
+ * generators fall back to the old inference there.
63
+ */
64
+ integer?: boolean;
56
65
  /** A string column with a known shape, currently only `uuid`. */
57
66
  format?: 'uuid';
67
+ /**
68
+ * Array depth for a column declared with `.array()`, absent when the column is a scalar.
69
+ *
70
+ * Drizzle does not give an array its own column class: `text().array()` is still a `PgText`,
71
+ * distinguished only by `dimensions`. Reading the class alone therefore produced a schema for
72
+ * the *element*, which rejected every row the database returned and accepted a bare string in
73
+ * its place.
74
+ *
75
+ * `.array(3)` sets a size rather than a dimension and Drizzle itself treats the result as a
76
+ * scalar, so it is deliberately not an array here either.
77
+ */
78
+ arrayDimensions?: number;
79
+ /**
80
+ * A structured value that cannot be expressed as a scalar plus constraints.
81
+ *
82
+ * These all used to fall through to `any`/`unknown` or, worse, to `string`. A `point` really
83
+ * arrives as `[number, number]`, so a string schema rejected every row; a `bytea` typed as
84
+ * `unknown` accepted `null` on a NOT NULL column.
85
+ */
86
+ shape?: ColumnShape;
87
+ }
88
+ type ColumnShape =
89
+ /** `bytea`, `blob`: a binary payload, carried as a Buffer/Uint8Array. */
90
+ {
91
+ kind: 'buffer';
92
+ }
93
+ /** `json`, `jsonb`: any value that survives a JSON round trip, checked recursively. */
94
+ | {
95
+ kind: 'json';
96
+ }
97
+ /** `point`, `line`, `geometry`: a fixed-length tuple of numbers. */
98
+ | {
99
+ kind: 'tuple';
100
+ length: number;
58
101
  }
102
+ /** `vector`, `halfvec`: a numeric vector, with a fixed length where one is declared. */
103
+ | {
104
+ kind: 'numberVector';
105
+ length?: number;
106
+ }
107
+ /**
108
+ * A `customType` column, whose JavaScript type exists only at compile time.
109
+ *
110
+ * `getSQLType()` gives the declared SQL type, but that is the *database* side: `fromDriver` may
111
+ * map it to anything, so a `numeric(12,2)` custom column can perfectly well hand back a number
112
+ * where a plain numeric hands back a string. Guessing from the SQL type would reject the real
113
+ * value, so the type is taken from Drizzle's own inference or not at all.
114
+ */
115
+ | {
116
+ kind: 'custom';
117
+ sqlType?: string;
118
+ }
119
+ /**
120
+ * A string of `0`/`1`: Postgres `bit(n)`, MySQL `binary(n)`/`varbinary(n)`.
121
+ *
122
+ * `exact` separates the two. A Postgres `bit(3)` is always three digits, while a MySQL
123
+ * `varbinary(16)` is at most sixteen, so treating both as exact rejected the empty string on
124
+ * every MySQL binary column.
125
+ */
126
+ | {
127
+ kind: 'bitstring';
128
+ length?: number;
129
+ exact?: boolean;
130
+ };
59
131
  interface Key {
60
132
  name?: string;
61
133
  columns: string[];
@@ -112,6 +184,20 @@ interface AnalyzeOptions {
112
184
  validateConstraints?: boolean;
113
185
  includeHeuristicRelations?: boolean;
114
186
  }
187
+ /**
188
+ * Everything Drizzle v1 states about a column outright, or `null` on an older Drizzle.
189
+ *
190
+ * v1 stamps each column with a `dataType` of the form `"<js type> <semantic>"` (`"number
191
+ * int32"`, `"object buffer"`, `"array point"`) alongside a `codec` naming the SQL side. That
192
+ * is a far better key than the constructor name the analyzer used to match on: the class list
193
+ * ran to dozens of names per dialect, drifted between releases, and a miss fell through to a
194
+ * regex that guessed from the name. `PgBinaryVector`, for one, is a bit string and not a
195
+ * vector at all.
196
+ *
197
+ * Gated on `codec`, which 0.4x columns do not carry, so an older schema keeps the class-name
198
+ * path below untouched.
199
+ */
200
+ declare function describeV1Column(column: any): Partial<Column> | null;
115
201
  declare class SchemaAnalyzer {
116
202
  private readonly schemaPath;
117
203
  constructor(schemaPath: string);
@@ -216,4 +302,4 @@ declare class SchemaAnalyzer {
216
302
  analyze(opts?: AnalyzeOptions): Promise<Analysis>;
217
303
  }
218
304
 
219
- export { type Analysis, type AnalyzeOptions, type Check, type Column, type ColumnRef, type Dialect, type Enum, type ForeignKey, type Index, type Issue, type Key, type Relation, SchemaAnalyzer, type Table, SchemaAnalyzer as default };
305
+ 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 };
package/dist/index.d.ts CHANGED
@@ -53,9 +53,81 @@ interface Column {
53
53
  */
54
54
  min?: string;
55
55
  max?: string;
56
+ /**
57
+ * Whether a numeric column holds whole numbers only.
58
+ *
59
+ * Stated rather than inferred. Generators used to read "has both bounds" as "is an integer",
60
+ * which held only while integers were the sole bounded type; bounding `real` and `double
61
+ * precision` promptly made every float schema reject `1.5`. Absent on a pre-1.7 analysis, and
62
+ * generators fall back to the old inference there.
63
+ */
64
+ integer?: boolean;
56
65
  /** A string column with a known shape, currently only `uuid`. */
57
66
  format?: 'uuid';
67
+ /**
68
+ * Array depth for a column declared with `.array()`, absent when the column is a scalar.
69
+ *
70
+ * Drizzle does not give an array its own column class: `text().array()` is still a `PgText`,
71
+ * distinguished only by `dimensions`. Reading the class alone therefore produced a schema for
72
+ * the *element*, which rejected every row the database returned and accepted a bare string in
73
+ * its place.
74
+ *
75
+ * `.array(3)` sets a size rather than a dimension and Drizzle itself treats the result as a
76
+ * scalar, so it is deliberately not an array here either.
77
+ */
78
+ arrayDimensions?: number;
79
+ /**
80
+ * A structured value that cannot be expressed as a scalar plus constraints.
81
+ *
82
+ * These all used to fall through to `any`/`unknown` or, worse, to `string`. A `point` really
83
+ * arrives as `[number, number]`, so a string schema rejected every row; a `bytea` typed as
84
+ * `unknown` accepted `null` on a NOT NULL column.
85
+ */
86
+ shape?: ColumnShape;
87
+ }
88
+ type ColumnShape =
89
+ /** `bytea`, `blob`: a binary payload, carried as a Buffer/Uint8Array. */
90
+ {
91
+ kind: 'buffer';
92
+ }
93
+ /** `json`, `jsonb`: any value that survives a JSON round trip, checked recursively. */
94
+ | {
95
+ kind: 'json';
96
+ }
97
+ /** `point`, `line`, `geometry`: a fixed-length tuple of numbers. */
98
+ | {
99
+ kind: 'tuple';
100
+ length: number;
58
101
  }
102
+ /** `vector`, `halfvec`: a numeric vector, with a fixed length where one is declared. */
103
+ | {
104
+ kind: 'numberVector';
105
+ length?: number;
106
+ }
107
+ /**
108
+ * A `customType` column, whose JavaScript type exists only at compile time.
109
+ *
110
+ * `getSQLType()` gives the declared SQL type, but that is the *database* side: `fromDriver` may
111
+ * map it to anything, so a `numeric(12,2)` custom column can perfectly well hand back a number
112
+ * where a plain numeric hands back a string. Guessing from the SQL type would reject the real
113
+ * value, so the type is taken from Drizzle's own inference or not at all.
114
+ */
115
+ | {
116
+ kind: 'custom';
117
+ sqlType?: string;
118
+ }
119
+ /**
120
+ * A string of `0`/`1`: Postgres `bit(n)`, MySQL `binary(n)`/`varbinary(n)`.
121
+ *
122
+ * `exact` separates the two. A Postgres `bit(3)` is always three digits, while a MySQL
123
+ * `varbinary(16)` is at most sixteen, so treating both as exact rejected the empty string on
124
+ * every MySQL binary column.
125
+ */
126
+ | {
127
+ kind: 'bitstring';
128
+ length?: number;
129
+ exact?: boolean;
130
+ };
59
131
  interface Key {
60
132
  name?: string;
61
133
  columns: string[];
@@ -112,6 +184,20 @@ interface AnalyzeOptions {
112
184
  validateConstraints?: boolean;
113
185
  includeHeuristicRelations?: boolean;
114
186
  }
187
+ /**
188
+ * Everything Drizzle v1 states about a column outright, or `null` on an older Drizzle.
189
+ *
190
+ * v1 stamps each column with a `dataType` of the form `"<js type> <semantic>"` (`"number
191
+ * int32"`, `"object buffer"`, `"array point"`) alongside a `codec` naming the SQL side. That
192
+ * is a far better key than the constructor name the analyzer used to match on: the class list
193
+ * ran to dozens of names per dialect, drifted between releases, and a miss fell through to a
194
+ * regex that guessed from the name. `PgBinaryVector`, for one, is a bit string and not a
195
+ * vector at all.
196
+ *
197
+ * Gated on `codec`, which 0.4x columns do not carry, so an older schema keeps the class-name
198
+ * path below untouched.
199
+ */
200
+ declare function describeV1Column(column: any): Partial<Column> | null;
115
201
  declare class SchemaAnalyzer {
116
202
  private readonly schemaPath;
117
203
  constructor(schemaPath: string);
@@ -216,4 +302,4 @@ declare class SchemaAnalyzer {
216
302
  analyze(opts?: AnalyzeOptions): Promise<Analysis>;
217
303
  }
218
304
 
219
- export { type Analysis, type AnalyzeOptions, type Check, type Column, type ColumnRef, type Dialect, type Enum, type ForeignKey, type Index, type Issue, type Key, type Relation, SchemaAnalyzer, type Table, SchemaAnalyzer as default };
305
+ 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 };
package/dist/index.js CHANGED
@@ -7,6 +7,175 @@ function renderSqlLiteral(v) {
7
7
  if (Array.isArray(v)) return `(${v.map(renderSqlLiteral).join(", ")})`;
8
8
  return `'${String(v).replace(/'/g, "''")}'`;
9
9
  }
10
+ var MYSQL_TEXT_CAPS = {
11
+ tinytext: 255,
12
+ text: 65535,
13
+ mediumtext: 16777215,
14
+ longtext: 4294967295,
15
+ tinyblob: 255,
16
+ blob: 65535,
17
+ mediumblob: 16777215,
18
+ longblob: 4294967295
19
+ };
20
+ var V1_FLOAT_BOUNDS = {
21
+ float: ["-8388608", "8388607"],
22
+ // real / float4, 2^23
23
+ double: ["-140737488355328", "140737488355327"]
24
+ // double precision / float8, 2^47
25
+ };
26
+ function describeV1Column(column) {
27
+ const codec = column?.codec;
28
+ const dataType = column?.dataType;
29
+ if (typeof dataType !== "string") return null;
30
+ if (dataType === "custom") {
31
+ const sqlType = typeof column?.getSQLType === "function" ? column.getSQLType() : void 0;
32
+ return {
33
+ tsType: "unknown",
34
+ dbType: typeof sqlType === "string" && sqlType ? sqlType.toUpperCase() : "UNKNOWN",
35
+ shape: { kind: "custom", sqlType: typeof sqlType === "string" ? sqlType : void 0 }
36
+ };
37
+ }
38
+ const [js, semantic = ""] = dataType.split(" ");
39
+ if (typeof codec !== "string" && !semantic) return null;
40
+ const out = {};
41
+ switch (semantic) {
42
+ case "int8":
43
+ case "int16":
44
+ case "int24":
45
+ case "int32":
46
+ case "int53":
47
+ case "uint53":
48
+ case "int64": {
49
+ const range = {
50
+ int8: ["-128", "127"],
51
+ int16: ["-32768", "32767"],
52
+ int24: ["-8388608", "8388607"],
53
+ int32: ["-2147483648", "2147483647"],
54
+ int53: ["-9007199254740991", "9007199254740991"],
55
+ // MySQL `serial` is `bigint unsigned auto_increment`, so it starts at 0 rather than
56
+ // spanning the signed range.
57
+ uint53: ["0", "9007199254740991"],
58
+ int64: ["-9223372036854775808", "9223372036854775807"]
59
+ }[semantic];
60
+ [out.min, out.max] = range;
61
+ out.integer = true;
62
+ out.tsType = js === "bigint" ? "bigint" : "number";
63
+ out.dbType = semantic === "int8" ? "TINYINT" : semantic === "int16" ? "SMALLINT" : semantic === "int24" ? "MEDIUMINT" : semantic === "int32" ? "INTEGER" : "BIGINT";
64
+ break;
65
+ }
66
+ case "year":
67
+ out.tsType = "number";
68
+ out.dbType = "YEAR";
69
+ out.integer = true;
70
+ [out.min, out.max] = ["1901", "2155"];
71
+ break;
72
+ case "float":
73
+ case "double": {
74
+ [out.min, out.max] = V1_FLOAT_BOUNDS[semantic];
75
+ out.integer = false;
76
+ out.tsType = "number";
77
+ out.dbType = semantic === "float" ? "REAL" : "DOUBLE";
78
+ break;
79
+ }
80
+ case "uuid":
81
+ out.tsType = "string";
82
+ out.dbType = "UUID";
83
+ out.format = "uuid";
84
+ break;
85
+ case "numeric":
86
+ out.tsType = "string";
87
+ out.dbType = "NUMERIC";
88
+ break;
89
+ case "json":
90
+ out.tsType = "any";
91
+ out.dbType = codec === "jsonb" ? "JSONB" : "JSON";
92
+ out.shape = { kind: "json" };
93
+ break;
94
+ case "buffer":
95
+ out.tsType = "Buffer";
96
+ out.dbType = "BYTEA";
97
+ out.shape = { kind: "buffer" };
98
+ break;
99
+ case "date":
100
+ out.tsType = js === "string" ? "string" : "Date";
101
+ out.dbType = codec?.startsWith("timestamp") ? "TIMESTAMP" : "DATE";
102
+ break;
103
+ case "timestamp":
104
+ out.tsType = js === "string" ? "string" : "Date";
105
+ out.dbType = "TIMESTAMP";
106
+ break;
107
+ case "time":
108
+ out.tsType = "string";
109
+ out.dbType = "TIME";
110
+ break;
111
+ case "interval":
112
+ out.tsType = "string";
113
+ out.dbType = "INTERVAL";
114
+ break;
115
+ case "inet":
116
+ case "cidr":
117
+ case "macaddr":
118
+ out.tsType = "string";
119
+ out.dbType = semantic.toUpperCase();
120
+ break;
121
+ case "binary":
122
+ out.tsType = "string";
123
+ out.dbType = codec === "bit" ? "BIT" : "BINARY";
124
+ out.shape = {
125
+ kind: "bitstring",
126
+ length: declaredLength(column),
127
+ // A Postgres `bit(3)` holds exactly three digits; a MySQL `binary(4)`/`varbinary(16)`
128
+ // holds at most that many, which is why `''` is valid there and not here.
129
+ exact: codec === "bit"
130
+ };
131
+ break;
132
+ case "point":
133
+ case "geometry":
134
+ out.tsType = "[number, number]";
135
+ out.dbType = semantic.toUpperCase();
136
+ out.shape = { kind: "tuple", length: 2 };
137
+ break;
138
+ case "line":
139
+ out.tsType = "[number, number, number]";
140
+ out.dbType = "LINE";
141
+ out.shape = { kind: "tuple", length: 3 };
142
+ break;
143
+ case "vector":
144
+ out.tsType = "number[]";
145
+ out.dbType = "VECTOR";
146
+ out.shape = { kind: "numberVector", length: declaredLength(column) };
147
+ break;
148
+ case "enum":
149
+ out.tsType = "string";
150
+ out.dbType = "TEXT";
151
+ break;
152
+ default:
153
+ if (js === "boolean") {
154
+ out.tsType = "boolean";
155
+ out.dbType = "BOOLEAN";
156
+ } else if (js === "number") {
157
+ out.tsType = "number";
158
+ out.dbType = "NUMERIC";
159
+ out.integer = false;
160
+ [out.min, out.max] = ["-9007199254740991", "9007199254740991"];
161
+ } else if (js === "string") {
162
+ out.tsType = "string";
163
+ out.dbType = codec === "varchar" ? "VARCHAR" : codec === "char" ? "CHAR" : "TEXT";
164
+ const kind = String(column?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
165
+ const cap = codec && kind.startsWith("MySql") ? MYSQL_TEXT_CAPS[codec] : void 0;
166
+ if (cap) out.maxLength = cap;
167
+ } else {
168
+ return null;
169
+ }
170
+ }
171
+ const dims = column?.dimensions;
172
+ if (typeof dims === "number" && dims >= 1) out.arrayDimensions = dims;
173
+ return out;
174
+ }
175
+ function declaredLength(column) {
176
+ const n = column?.length ?? column?.config?.length ?? column?.config?.dimensions;
177
+ return typeof n === "number" && Number.isFinite(n) && n > 0 ? n : void 0;
178
+ }
10
179
  var _SchemaAnalyzer = class _SchemaAnalyzer {
11
180
  constructor(schemaPath) {
12
181
  this.schemaPath = schemaPath;
@@ -237,6 +406,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
237
406
  const range = _SchemaAnalyzer.INT_RANGES[ctor];
238
407
  if (range) {
239
408
  [out.min, out.max] = range;
409
+ out.integer = true;
240
410
  }
241
411
  if (/^(Pg)?UUID$/i.test(ctor) || /Uuid$/i.test(ctor)) out.format = "uuid";
242
412
  return out;
@@ -310,7 +480,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
310
480
  if (/Text|Varchar|Char|Uuid/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
311
481
  if (/Inet|Cidr|Macaddr8?|Uuid/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
312
482
  if (/Point|Line/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
313
- if (/TimestampString|DateString/i.test(ctor)) return { tsType: "string", dbType: "TIMESTAMP" };
483
+ if (/TimestampString|DateString/i.test(ctor))
484
+ return { tsType: "string", dbType: "TIMESTAMP" };
314
485
  if (/Timestamptz/i.test(ctor)) return { tsType: "Date", dbType: "TIMESTAMPTZ" };
315
486
  if (/Timestamp/i.test(ctor)) return { tsType: "Date", dbType: "TIMESTAMP" };
316
487
  if (/Date/i.test(ctor)) return { tsType: "Date", dbType: "TIMESTAMP" };
@@ -319,8 +490,10 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
319
490
  if (/\bInt(eger)?\b|Serial/i.test(ctor)) return { tsType: "number", dbType: "INTEGER" };
320
491
  if (/BigInt/i.test(ctor)) return { tsType: "bigint", dbType: "BIGINT" };
321
492
  if (/Bool/i.test(ctor)) return { tsType: "boolean", dbType: "BOOLEAN" };
322
- if (/Jsonb?/i.test(ctor)) return { tsType: "any", dbType: /Jsonb/i.test(ctor) ? "JSONB" : "JSON" };
323
- if (/Numeric|Float|Double|Real/i.test(ctor)) return { tsType: "number", dbType: "NUMERIC" };
493
+ if (/Jsonb?/i.test(ctor))
494
+ return { tsType: "any", dbType: /Jsonb/i.test(ctor) ? "JSONB" : "JSON" };
495
+ if (/Numeric|Float|Double|Real/i.test(ctor))
496
+ return { tsType: "number", dbType: "NUMERIC" };
324
497
  }
325
498
  if (/^MySql/i.test(ctor)) {
326
499
  if (/BigInt64/i.test(ctor)) return { tsType: "bigint", dbType: "BIGINT" };
@@ -372,7 +545,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
372
545
  if (/TimestampTz/i.test(ctor)) return { tsType: "Date", dbType: "TIMESTAMPTZ" };
373
546
  if (/Timestamp/i.test(ctor)) return { tsType: "string", dbType: "TIMESTAMP" };
374
547
  if (/LocalDateString|LocalTime/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
375
- if (/DateDuration|RelDuration|Duration/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
548
+ if (/DateDuration|RelDuration|Duration/i.test(ctor))
549
+ return { tsType: "string", dbType: "TEXT" };
376
550
  }
377
551
  return { tsType: "unknown", dbType: "UNKNOWN" };
378
552
  }
@@ -394,7 +568,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
394
568
  }
395
569
  const ev = col?.enumValues;
396
570
  const nullable = !col?.notNull && !col?.config?.notNull;
397
- const isGenerated = !!(col?.autoIncrement || col?.isGenerated);
571
+ const generatedIdentity = col?.generatedIdentity;
572
+ const isGenerated = !!(col?.generated || generatedIdentity?.type === "always" || col?.isGenerated);
398
573
  const hasDefault = col?.hasDefault === true || col?.default !== void 0 || col?.config?.default !== void 0 || col?.defaultFn !== void 0 || isGenerated;
399
574
  const references = void 0;
400
575
  const isUnique = !!(col?.isUnique || col?.config?.isUnique);
@@ -407,6 +582,9 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
407
582
  arr.push(colName);
408
583
  uniqueGroups.set(uName, arr);
409
584
  }
585
+ const v1 = describeV1Column(col);
586
+ const constraints = this.columnConstraints(col);
587
+ if (v1?.shape) delete constraints.maxLength;
410
588
  columns.push({
411
589
  name: colName,
412
590
  tsType,
@@ -417,7 +595,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
417
595
  defaultExpression: void 0,
418
596
  references,
419
597
  enumValues: Array.isArray(ev) ? ev : void 0,
420
- ...this.columnConstraints(col)
598
+ ...constraints,
599
+ ...v1 ?? {}
421
600
  });
422
601
  }
423
602
  const name = this.getSymbol(tbl, "drizzle:Name") || tsName;
@@ -657,24 +836,28 @@ _SchemaAnalyzer.INT_RANGES = {
657
836
  SQLiteInteger: ["-9223372036854775808", "9223372036854775807"],
658
837
  // 16 bit
659
838
  PgSmallInt: ["-32768", "32767"],
660
- PgSmallSerial: ["1", "32767"],
839
+ // A serial is an ordinary integer column that happens to default from a sequence. The
840
+ // sequence starts at 1, the column does not: `INSERT ... (id) VALUES (-5)` is accepted by
841
+ // Postgres and is how backfills and sentinel rows get written. Lower-bounding these at 1
842
+ // rejected valid rows, and `drizzle-orm/zod` bounds them by the integer width too.
843
+ PgSmallSerial: ["-32768", "32767"],
661
844
  MySqlSmallInt: ["-32768", "32767"],
662
845
  SingleStoreSmallInt: ["-32768", "32767"],
663
846
  // 24 bit
664
847
  MySqlMediumInt: ["-8388608", "8388607"],
665
848
  // 32 bit
666
849
  PgInteger: ["-2147483648", "2147483647"],
667
- PgSerial: ["1", "2147483647"],
850
+ PgSerial: ["-2147483648", "2147483647"],
668
851
  MySqlInt: ["-2147483648", "2147483647"],
669
852
  SingleStoreInt: ["-2147483648", "2147483647"],
670
853
  // 53 bit, the JS safe-integer ceiling rather than the column's
671
854
  PgBigInt53: ["-9007199254740991", "9007199254740991"],
672
- PgBigSerial53: ["1", "9007199254740991"],
855
+ PgBigSerial53: ["-9007199254740991", "9007199254740991"],
673
856
  MySqlBigInt53: ["-9007199254740991", "9007199254740991"],
674
857
  SingleStoreBigInt53: ["-9007199254740991", "9007199254740991"],
675
858
  // 64 bit, representable because the value is a bigint
676
859
  PgBigInt64: ["-9223372036854775808", "9223372036854775807"],
677
- PgBigSerial64: ["1", "9223372036854775807"],
860
+ PgBigSerial64: ["-9223372036854775808", "9223372036854775807"],
678
861
  MySqlBigInt64: ["-9223372036854775808", "9223372036854775807"],
679
862
  SingleStoreBigInt64: ["-9223372036854775808", "9223372036854775807"]
680
863
  };
@@ -682,5 +865,6 @@ var SchemaAnalyzer = _SchemaAnalyzer;
682
865
  var index_default = SchemaAnalyzer;
683
866
  export {
684
867
  SchemaAnalyzer,
685
- index_default as default
868
+ index_default as default,
869
+ describeV1Column
686
870
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drzl/analyzer",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "private": false,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",