@drzl/analyzer 1.7.0 → 1.9.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
@@ -44,6 +44,16 @@ function renderSqlLiteral(v) {
44
44
  if (Array.isArray(v)) return `(${v.map(renderSqlLiteral).join(", ")})`;
45
45
  return `'${String(v).replace(/'/g, "''")}'`;
46
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
+ };
47
57
  var V1_FLOAT_BOUNDS = {
48
58
  float: ["-8388608", "8388607"],
49
59
  // real / float4, 2^23
@@ -53,26 +63,49 @@ var V1_FLOAT_BOUNDS = {
53
63
  function describeV1Column(column) {
54
64
  const codec = column?.codec;
55
65
  const dataType = column?.dataType;
56
- if (typeof codec !== "string" || typeof dataType !== "string") return null;
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
+ }
57
75
  const [js, semantic = ""] = dataType.split(" ");
76
+ if (typeof codec !== "string" && !semantic) return null;
58
77
  const out = {};
59
78
  switch (semantic) {
79
+ case "int8":
60
80
  case "int16":
81
+ case "int24":
61
82
  case "int32":
62
83
  case "int53":
84
+ case "uint53":
63
85
  case "int64": {
64
86
  const range = {
87
+ int8: ["-128", "127"],
65
88
  int16: ["-32768", "32767"],
89
+ int24: ["-8388608", "8388607"],
66
90
  int32: ["-2147483648", "2147483647"],
67
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"],
68
95
  int64: ["-9223372036854775808", "9223372036854775807"]
69
96
  }[semantic];
70
97
  [out.min, out.max] = range;
71
98
  out.integer = true;
72
99
  out.tsType = js === "bigint" ? "bigint" : "number";
73
- out.dbType = semantic === "int16" ? "SMALLINT" : semantic === "int32" ? "INTEGER" : "BIGINT";
100
+ out.dbType = semantic === "int8" ? "TINYINT" : semantic === "int16" ? "SMALLINT" : semantic === "int24" ? "MEDIUMINT" : semantic === "int32" ? "INTEGER" : "BIGINT";
74
101
  break;
75
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;
76
109
  case "float":
77
110
  case "double": {
78
111
  [out.min, out.max] = V1_FLOAT_BOUNDS[semantic];
@@ -89,6 +122,9 @@ function describeV1Column(column) {
89
122
  case "numeric":
90
123
  out.tsType = "string";
91
124
  out.dbType = "NUMERIC";
125
+ if (!String(column?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "").startsWith("SQLite")) {
126
+ out.format = "numeric";
127
+ }
92
128
  break;
93
129
  case "json":
94
130
  out.tsType = "any";
@@ -102,7 +138,7 @@ function describeV1Column(column) {
102
138
  break;
103
139
  case "date":
104
140
  out.tsType = js === "string" ? "string" : "Date";
105
- out.dbType = codec.startsWith("timestamp") ? "TIMESTAMP" : "DATE";
141
+ out.dbType = codec?.startsWith("timestamp") ? "TIMESTAMP" : "DATE";
106
142
  break;
107
143
  case "timestamp":
108
144
  out.tsType = js === "string" ? "string" : "Date";
@@ -124,8 +160,14 @@ function describeV1Column(column) {
124
160
  break;
125
161
  case "binary":
126
162
  out.tsType = "string";
127
- out.dbType = "BIT";
128
- out.shape = { kind: "bitstring", length: declaredLength(column) };
163
+ out.dbType = codec === "bit" ? "BIT" : "BINARY";
164
+ out.shape = {
165
+ kind: "bitstring",
166
+ length: declaredLength(column),
167
+ // A Postgres `bit(3)` holds exactly three digits; a MySQL `binary(4)`/`varbinary(16)`
168
+ // holds at most that many, which is why `''` is valid there and not here.
169
+ exact: codec === "bit"
170
+ };
129
171
  break;
130
172
  case "point":
131
173
  case "geometry":
@@ -159,6 +201,9 @@ function describeV1Column(column) {
159
201
  } else if (js === "string") {
160
202
  out.tsType = "string";
161
203
  out.dbType = codec === "varchar" ? "VARCHAR" : codec === "char" ? "CHAR" : "TEXT";
204
+ const kind = String(column?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
205
+ const cap = codec && kind.startsWith("MySql") ? MYSQL_TEXT_CAPS[codec] : void 0;
206
+ if (cap) out.maxLength = cap;
162
207
  } else {
163
208
  return null;
164
209
  }
@@ -563,7 +608,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
563
608
  }
564
609
  const ev = col?.enumValues;
565
610
  const nullable = !col?.notNull && !col?.config?.notNull;
566
- const isGenerated = !!(col?.autoIncrement || col?.isGenerated);
611
+ const generatedIdentity = col?.generatedIdentity;
612
+ const isGenerated = !!(col?.generated || generatedIdentity?.type === "always" || col?.isGenerated);
567
613
  const hasDefault = col?.hasDefault === true || col?.default !== void 0 || col?.config?.default !== void 0 || col?.defaultFn !== void 0 || isGenerated;
568
614
  const references = void 0;
569
615
  const isUnique = !!(col?.isUnique || col?.config?.isUnique);
package/dist/index.d.cts CHANGED
@@ -62,8 +62,16 @@ interface Column {
62
62
  * generators fall back to the old inference there.
63
63
  */
64
64
  integer?: boolean;
65
- /** A string column with a known shape, currently only `uuid`. */
66
- format?: 'uuid';
65
+ /**
66
+ * A string column whose contents have a shape the database enforces.
67
+ *
68
+ * Only formats checked against Postgres itself appear here, and the list is short because most
69
+ * candidates failed: Postgres reads `'today'` and `'January 8, 1999'` as dates, pads
70
+ * `'2020-01-01'` into a macaddr, and accepts `'10.1/16'` as an inet. A check for any of those
71
+ * would reject input the database accepts, and turning away valid data is worse than not
72
+ * checking at all. See `COLUMN_FORMATS` in `@drzl/validation-core`.
73
+ */
74
+ format?: 'uuid' | 'numeric';
67
75
  /**
68
76
  * Array depth for a column declared with `.array()`, absent when the column is a scalar.
69
77
  *
@@ -104,10 +112,29 @@ type ColumnShape =
104
112
  kind: 'numberVector';
105
113
  length?: number;
106
114
  }
107
- /** `bit`: a string of `0`/`1`, with a fixed length where one is declared. */
115
+ /**
116
+ * A `customType` column, whose JavaScript type exists only at compile time.
117
+ *
118
+ * `getSQLType()` gives the declared SQL type, but that is the *database* side: `fromDriver` may
119
+ * map it to anything, so a `numeric(12,2)` custom column can perfectly well hand back a number
120
+ * where a plain numeric hands back a string. Guessing from the SQL type would reject the real
121
+ * value, so the type is taken from Drizzle's own inference or not at all.
122
+ */
123
+ | {
124
+ kind: 'custom';
125
+ sqlType?: string;
126
+ }
127
+ /**
128
+ * A string of `0`/`1`: Postgres `bit(n)`, MySQL `binary(n)`/`varbinary(n)`.
129
+ *
130
+ * `exact` separates the two. A Postgres `bit(3)` is always three digits, while a MySQL
131
+ * `varbinary(16)` is at most sixteen, so treating both as exact rejected the empty string on
132
+ * every MySQL binary column.
133
+ */
108
134
  | {
109
135
  kind: 'bitstring';
110
136
  length?: number;
137
+ exact?: boolean;
111
138
  };
112
139
  interface Key {
113
140
  name?: string;
package/dist/index.d.ts CHANGED
@@ -62,8 +62,16 @@ interface Column {
62
62
  * generators fall back to the old inference there.
63
63
  */
64
64
  integer?: boolean;
65
- /** A string column with a known shape, currently only `uuid`. */
66
- format?: 'uuid';
65
+ /**
66
+ * A string column whose contents have a shape the database enforces.
67
+ *
68
+ * Only formats checked against Postgres itself appear here, and the list is short because most
69
+ * candidates failed: Postgres reads `'today'` and `'January 8, 1999'` as dates, pads
70
+ * `'2020-01-01'` into a macaddr, and accepts `'10.1/16'` as an inet. A check for any of those
71
+ * would reject input the database accepts, and turning away valid data is worse than not
72
+ * checking at all. See `COLUMN_FORMATS` in `@drzl/validation-core`.
73
+ */
74
+ format?: 'uuid' | 'numeric';
67
75
  /**
68
76
  * Array depth for a column declared with `.array()`, absent when the column is a scalar.
69
77
  *
@@ -104,10 +112,29 @@ type ColumnShape =
104
112
  kind: 'numberVector';
105
113
  length?: number;
106
114
  }
107
- /** `bit`: a string of `0`/`1`, with a fixed length where one is declared. */
115
+ /**
116
+ * A `customType` column, whose JavaScript type exists only at compile time.
117
+ *
118
+ * `getSQLType()` gives the declared SQL type, but that is the *database* side: `fromDriver` may
119
+ * map it to anything, so a `numeric(12,2)` custom column can perfectly well hand back a number
120
+ * where a plain numeric hands back a string. Guessing from the SQL type would reject the real
121
+ * value, so the type is taken from Drizzle's own inference or not at all.
122
+ */
123
+ | {
124
+ kind: 'custom';
125
+ sqlType?: string;
126
+ }
127
+ /**
128
+ * A string of `0`/`1`: Postgres `bit(n)`, MySQL `binary(n)`/`varbinary(n)`.
129
+ *
130
+ * `exact` separates the two. A Postgres `bit(3)` is always three digits, while a MySQL
131
+ * `varbinary(16)` is at most sixteen, so treating both as exact rejected the empty string on
132
+ * every MySQL binary column.
133
+ */
108
134
  | {
109
135
  kind: 'bitstring';
110
136
  length?: number;
137
+ exact?: boolean;
111
138
  };
112
139
  interface Key {
113
140
  name?: string;
package/dist/index.js CHANGED
@@ -7,6 +7,16 @@ 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
+ };
10
20
  var V1_FLOAT_BOUNDS = {
11
21
  float: ["-8388608", "8388607"],
12
22
  // real / float4, 2^23
@@ -16,26 +26,49 @@ var V1_FLOAT_BOUNDS = {
16
26
  function describeV1Column(column) {
17
27
  const codec = column?.codec;
18
28
  const dataType = column?.dataType;
19
- if (typeof codec !== "string" || typeof dataType !== "string") return null;
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
+ }
20
38
  const [js, semantic = ""] = dataType.split(" ");
39
+ if (typeof codec !== "string" && !semantic) return null;
21
40
  const out = {};
22
41
  switch (semantic) {
42
+ case "int8":
23
43
  case "int16":
44
+ case "int24":
24
45
  case "int32":
25
46
  case "int53":
47
+ case "uint53":
26
48
  case "int64": {
27
49
  const range = {
50
+ int8: ["-128", "127"],
28
51
  int16: ["-32768", "32767"],
52
+ int24: ["-8388608", "8388607"],
29
53
  int32: ["-2147483648", "2147483647"],
30
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"],
31
58
  int64: ["-9223372036854775808", "9223372036854775807"]
32
59
  }[semantic];
33
60
  [out.min, out.max] = range;
34
61
  out.integer = true;
35
62
  out.tsType = js === "bigint" ? "bigint" : "number";
36
- out.dbType = semantic === "int16" ? "SMALLINT" : semantic === "int32" ? "INTEGER" : "BIGINT";
63
+ out.dbType = semantic === "int8" ? "TINYINT" : semantic === "int16" ? "SMALLINT" : semantic === "int24" ? "MEDIUMINT" : semantic === "int32" ? "INTEGER" : "BIGINT";
37
64
  break;
38
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;
39
72
  case "float":
40
73
  case "double": {
41
74
  [out.min, out.max] = V1_FLOAT_BOUNDS[semantic];
@@ -52,6 +85,9 @@ function describeV1Column(column) {
52
85
  case "numeric":
53
86
  out.tsType = "string";
54
87
  out.dbType = "NUMERIC";
88
+ if (!String(column?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "").startsWith("SQLite")) {
89
+ out.format = "numeric";
90
+ }
55
91
  break;
56
92
  case "json":
57
93
  out.tsType = "any";
@@ -65,7 +101,7 @@ function describeV1Column(column) {
65
101
  break;
66
102
  case "date":
67
103
  out.tsType = js === "string" ? "string" : "Date";
68
- out.dbType = codec.startsWith("timestamp") ? "TIMESTAMP" : "DATE";
104
+ out.dbType = codec?.startsWith("timestamp") ? "TIMESTAMP" : "DATE";
69
105
  break;
70
106
  case "timestamp":
71
107
  out.tsType = js === "string" ? "string" : "Date";
@@ -87,8 +123,14 @@ function describeV1Column(column) {
87
123
  break;
88
124
  case "binary":
89
125
  out.tsType = "string";
90
- out.dbType = "BIT";
91
- out.shape = { kind: "bitstring", length: declaredLength(column) };
126
+ out.dbType = codec === "bit" ? "BIT" : "BINARY";
127
+ out.shape = {
128
+ kind: "bitstring",
129
+ length: declaredLength(column),
130
+ // A Postgres `bit(3)` holds exactly three digits; a MySQL `binary(4)`/`varbinary(16)`
131
+ // holds at most that many, which is why `''` is valid there and not here.
132
+ exact: codec === "bit"
133
+ };
92
134
  break;
93
135
  case "point":
94
136
  case "geometry":
@@ -122,6 +164,9 @@ function describeV1Column(column) {
122
164
  } else if (js === "string") {
123
165
  out.tsType = "string";
124
166
  out.dbType = codec === "varchar" ? "VARCHAR" : codec === "char" ? "CHAR" : "TEXT";
167
+ const kind = String(column?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
168
+ const cap = codec && kind.startsWith("MySql") ? MYSQL_TEXT_CAPS[codec] : void 0;
169
+ if (cap) out.maxLength = cap;
125
170
  } else {
126
171
  return null;
127
172
  }
@@ -526,7 +571,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
526
571
  }
527
572
  const ev = col?.enumValues;
528
573
  const nullable = !col?.notNull && !col?.config?.notNull;
529
- const isGenerated = !!(col?.autoIncrement || col?.isGenerated);
574
+ const generatedIdentity = col?.generatedIdentity;
575
+ const isGenerated = !!(col?.generated || generatedIdentity?.type === "always" || col?.isGenerated);
530
576
  const hasDefault = col?.hasDefault === true || col?.default !== void 0 || col?.config?.default !== void 0 || col?.defaultFn !== void 0 || isGenerated;
531
577
  const references = void 0;
532
578
  const isUnique = !!(col?.isUnique || col?.config?.isUnique);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drzl/analyzer",
3
- "version": "1.7.0",
3
+ "version": "1.9.0",
4
4
  "private": false,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",