@drzl/analyzer 1.16.0 → 1.17.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
@@ -70,6 +70,12 @@ var TUPLE_CLASS_SHAPES = {
70
70
  PgLineTuple: { kind: "tuple", length: 3 }
71
71
  };
72
72
  var V1_ONLY_ENTITY_KINDS = /^(?:MsSql|Cockroach)/;
73
+ var BYTE_STRING_CLASSES = /* @__PURE__ */ new Set([
74
+ "MySqlBinary",
75
+ "MySqlVarBinary",
76
+ "SingleStoreBinary",
77
+ "SingleStoreVarBinary"
78
+ ]);
73
79
  function describeV1Column(column) {
74
80
  const codec = column?.codec;
75
81
  const dataType = column?.dataType;
@@ -170,17 +176,20 @@ function describeV1Column(column) {
170
176
  out.tsType = "string";
171
177
  out.dbType = semantic.toUpperCase();
172
178
  break;
173
- case "binary":
179
+ case "binary": {
180
+ const entity = String(column?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
181
+ const bytes = entity.startsWith("MySql") || entity.startsWith("SingleStore");
174
182
  out.tsType = "string";
175
183
  out.dbType = codec === "bit" ? "BIT" : "BINARY";
176
- out.shape = {
184
+ out.shape = bytes ? { kind: "byteString", length: declaredLength(column) } : {
177
185
  kind: "bitstring",
178
186
  length: declaredLength(column),
179
- // A Postgres `bit(3)` holds exactly three digits; a MySQL `binary(4)`/`varbinary(16)`
180
- // holds at most that many, which is why `''` is valid there and not here.
187
+ // A Postgres `bit(3)` holds exactly three digits; a Cockroach `varbit(16)` holds at
188
+ // most that many, which is why `''` is valid there and not here.
181
189
  exact: codec === "bit"
182
190
  };
183
191
  break;
192
+ }
184
193
  case "point":
185
194
  case "geometry":
186
195
  out.tsType = "[number, number]";
@@ -556,6 +565,14 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
556
565
  return { tsType: "bigint", dbType: "BIGINT" };
557
566
  case "SQLiteNumeric":
558
567
  return { tsType: "string", dbType: "NUMERIC" };
568
+ // The other two modes of the same column, which matched no arm here and no dialect regex
569
+ // below either, so both came back UNKNOWN and every generator emitted a schema that
570
+ // accepted anything at all. Read back through better-sqlite3 3.53.4 on a `numeric` column,
571
+ // `db.select()` hands back a number in number mode and a bigint in bigint mode.
572
+ case "SQLiteNumericNumber":
573
+ return { tsType: "number", dbType: "NUMERIC" };
574
+ case "SQLiteNumericBigInt":
575
+ return { tsType: "bigint", dbType: "NUMERIC" };
559
576
  case "SQLiteBoolean":
560
577
  return { tsType: "boolean", dbType: "INTEGER" };
561
578
  // 0.4x gives an enum its own class, which had no arm here at all, so an enum column came
@@ -600,6 +617,15 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
600
617
  return { tsType: "Date", dbType: "TIMESTAMP" };
601
618
  case "PgNumeric":
602
619
  return { tsType: "string", dbType: "NUMERIC" };
620
+ // The other two modes, each its own class. `PgNumericNumber` reached the coarse
621
+ // `/Numeric|Float|Double|Real/i` arm and got the right answer there; `PgNumericBigInt` got
622
+ // its `bigint` from the `/BigInt/i` arm meant for `bigint` columns, and the SQL label that
623
+ // comes with it, so a `numeric(20,0)` was reported as a BIGINT column. Read back through
624
+ // PGlite, `db.select()` hands back a number in number mode and a bigint in bigint mode.
625
+ case "PgNumericNumber":
626
+ return { tsType: "number", dbType: "NUMERIC" };
627
+ case "PgNumericBigInt":
628
+ return { tsType: "bigint", dbType: "NUMERIC" };
603
629
  case "PgDoublePrecision":
604
630
  return { tsType: "number", dbType: "DOUBLE" };
605
631
  // `real()` builds a `PgReal`, which matched no arm and fell through to the coarse
@@ -622,6 +648,16 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
622
648
  case "PgJson":
623
649
  case "PgJsonb":
624
650
  return { tsType: "any", dbType: ctor === "PgJsonb" ? "JSONB" : "JSON" };
651
+ // The four builders in `BYTE_STRING_CLASSES`, which the coarse `/Blob|Binary|VarBinary/i`
652
+ // arms below would otherwise call a `Uint8Array` on the strength of the word "Binary" in
653
+ // the class name. Asked of MySQL 8.4 through drizzle 0.45.2 instead: the driver hands up a
654
+ // Buffer, `mapFromDriverValue` decodes it, and the caller receives a string. Every one of
655
+ // the four declares `dataType: 'string'` and every one of the four defines that method.
656
+ case "MySqlBinary":
657
+ case "MySqlVarBinary":
658
+ case "SingleStoreBinary":
659
+ case "SingleStoreVarBinary":
660
+ return { tsType: "string", dbType: "BINARY" };
625
661
  default:
626
662
  if (column?.config?.mode === "timestamp" || column?.mode === "timestamp") {
627
663
  return { tsType: "Date", dbType: "INTEGER" };
@@ -649,7 +685,12 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
649
685
  if (/BigInt64/i.test(ctor)) return { tsType: "bigint", dbType: "BIGINT" };
650
686
  if (/BigInt53/i.test(ctor)) return { tsType: "number", dbType: "BIGINT" };
651
687
  if (/\bBigInt\b/i.test(ctor)) return { tsType: "bigint", dbType: "BIGINT" };
652
- if (/Decimal|Numeric|Float|Double|Real/i.test(ctor))
688
+ if (/Decimal/i.test(ctor)) {
689
+ if (/DecimalNumber$/.test(ctor)) return { tsType: "number", dbType: "NUMERIC" };
690
+ if (/DecimalBigInt$/.test(ctor)) return { tsType: "bigint", dbType: "NUMERIC" };
691
+ return { tsType: "string", dbType: "NUMERIC" };
692
+ }
693
+ if (/Numeric|Float|Double|Real/i.test(ctor))
653
694
  return { tsType: "number", dbType: "NUMERIC" };
654
695
  if (/Int|Serial|TinyInt|SmallInt|MediumInt/i.test(ctor))
655
696
  return { tsType: "number", dbType: "INTEGER" };
@@ -668,7 +709,12 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
668
709
  if (/Vector/i.test(ctor)) return { tsType: "any", dbType: "VECTOR" };
669
710
  if (/BigInt64/i.test(ctor)) return { tsType: "bigint", dbType: "BIGINT" };
670
711
  if (/BigInt53/i.test(ctor)) return { tsType: "number", dbType: "BIGINT" };
671
- if (/Decimal|Numeric|Float|Double|Real/i.test(ctor))
712
+ if (/Decimal/i.test(ctor)) {
713
+ if (/DecimalNumber$/.test(ctor)) return { tsType: "number", dbType: "NUMERIC" };
714
+ if (/DecimalBigInt$/.test(ctor)) return { tsType: "bigint", dbType: "NUMERIC" };
715
+ return { tsType: "string", dbType: "NUMERIC" };
716
+ }
717
+ if (/Numeric|Float|Double|Real/i.test(ctor))
672
718
  return { tsType: "number", dbType: "NUMERIC" };
673
719
  if (/Int|Serial|TinyInt|SmallInt|MediumInt/i.test(ctor))
674
720
  return { tsType: "number", dbType: "INTEGER" };
@@ -740,7 +786,9 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
740
786
  const sqlKind = String(outerCol?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
741
787
  const sqlType = sqlKind.startsWith("MySql") && typeof col?.getSQLType === "function" ? String(col.getSQLType()).toLowerCase() : void 0;
742
788
  const byteCap = sqlType ? MYSQL_TEXT_CAPS[sqlType] : void 0;
743
- const fallbackShape = dbType === "JSON" || dbType === "JSONB" || col?.config?.mode === "json" ? { kind: "json" } : TUPLE_CLASS_SHAPES[String(col?.constructor?.name ?? "")];
789
+ const ctorName = String(col?.constructor?.name ?? "");
790
+ const fallbackShape = dbType === "JSON" || dbType === "JSONB" || col?.config?.mode === "json" ? { kind: "json" } : BYTE_STRING_CLASSES.has(ctorName) ? { kind: "byteString", length: declaredLength(col) } : TUPLE_CLASS_SHAPES[ctorName];
791
+ if (fallbackShape?.kind === "byteString") delete constraints.maxLength;
744
792
  const shape = (v1?.shape ?? fallbackShape)?.kind;
745
793
  const finalTs = v1?.tsType ?? tsType;
746
794
  const wide = (finalTs === "unknown" || finalTs === "any") && (!shape || shape === "custom");
package/dist/index.d.cts CHANGED
@@ -144,16 +144,44 @@ type ColumnShape =
144
144
  sqlType?: string;
145
145
  }
146
146
  /**
147
- * A string of `0`/`1`: Postgres `bit(n)`, MySQL `binary(n)`/`varbinary(n)`.
147
+ * A string of `0`/`1`: Postgres `bit(n)`, Cockroach `bit(n)`/`varbit(n)`.
148
148
  *
149
- * `exact` separates the two. A Postgres `bit(3)` is always three digits, while a MySQL
150
- * `varbinary(16)` is at most sixteen, so treating both as exact rejected the empty string on
151
- * every MySQL binary column.
149
+ * `exact` separates the two. A Postgres `bit(3)` is always three digits, while a Cockroach
150
+ * `varbit(16)` is at most sixteen, so treating both as exact rejects the empty string on every
151
+ * varying column.
152
+ *
153
+ * MySQL `binary(n)`/`varbinary(n)` used to be listed here and is not a bit string at all; see
154
+ * `byteString`.
152
155
  */
153
156
  | {
154
157
  kind: 'bitstring';
155
158
  length?: number;
156
159
  exact?: boolean;
160
+ }
161
+ /**
162
+ * MySQL/SingleStore `binary(n)`/`varbinary(n)`: arbitrary bytes, handed to the caller as a
163
+ * string.
164
+ *
165
+ * Asked of MySQL 8.4 through drizzle on both majors, on the same row of the same table: the
166
+ * driver hands up a Buffer, drizzle decodes it, and what the caller receives is a string on
167
+ * 0.45.2 and on 1.0.0-rc.4 alike. `instanceof Uint8Array` is false for all four column builders
168
+ * on both majors.
169
+ *
170
+ * `length` is the declared width and means two different things depending on direction, which
171
+ * is why it is carried raw here rather than as a `maxLength` or a `maxBytes`:
172
+ *
173
+ * out the decode is lossy, so n bytes become at most n code points. Measured: `<ff ff ff>`
174
+ * in a varbinary(3) comes back as 3 code points that re-encode to 9 UTF-8 bytes, so a
175
+ * byte cap applied to a select schema rejects a row the column itself returned.
176
+ * in the server counts the encoded bytes. Measured on varbinary(8): 8 ascii accepted, 9
177
+ * refused, 2 emoji (8 bytes) accepted, 3 emoji (12 bytes) refused, so a code-point cap
178
+ * applied to an insert schema accepts a write the server refuses.
179
+ *
180
+ * The generators pick which by mode.
181
+ */
182
+ | {
183
+ kind: 'byteString';
184
+ length?: number;
157
185
  };
158
186
  interface Key {
159
187
  name?: string;
package/dist/index.d.ts CHANGED
@@ -144,16 +144,44 @@ type ColumnShape =
144
144
  sqlType?: string;
145
145
  }
146
146
  /**
147
- * A string of `0`/`1`: Postgres `bit(n)`, MySQL `binary(n)`/`varbinary(n)`.
147
+ * A string of `0`/`1`: Postgres `bit(n)`, Cockroach `bit(n)`/`varbit(n)`.
148
148
  *
149
- * `exact` separates the two. A Postgres `bit(3)` is always three digits, while a MySQL
150
- * `varbinary(16)` is at most sixteen, so treating both as exact rejected the empty string on
151
- * every MySQL binary column.
149
+ * `exact` separates the two. A Postgres `bit(3)` is always three digits, while a Cockroach
150
+ * `varbit(16)` is at most sixteen, so treating both as exact rejects the empty string on every
151
+ * varying column.
152
+ *
153
+ * MySQL `binary(n)`/`varbinary(n)` used to be listed here and is not a bit string at all; see
154
+ * `byteString`.
152
155
  */
153
156
  | {
154
157
  kind: 'bitstring';
155
158
  length?: number;
156
159
  exact?: boolean;
160
+ }
161
+ /**
162
+ * MySQL/SingleStore `binary(n)`/`varbinary(n)`: arbitrary bytes, handed to the caller as a
163
+ * string.
164
+ *
165
+ * Asked of MySQL 8.4 through drizzle on both majors, on the same row of the same table: the
166
+ * driver hands up a Buffer, drizzle decodes it, and what the caller receives is a string on
167
+ * 0.45.2 and on 1.0.0-rc.4 alike. `instanceof Uint8Array` is false for all four column builders
168
+ * on both majors.
169
+ *
170
+ * `length` is the declared width and means two different things depending on direction, which
171
+ * is why it is carried raw here rather than as a `maxLength` or a `maxBytes`:
172
+ *
173
+ * out the decode is lossy, so n bytes become at most n code points. Measured: `<ff ff ff>`
174
+ * in a varbinary(3) comes back as 3 code points that re-encode to 9 UTF-8 bytes, so a
175
+ * byte cap applied to a select schema rejects a row the column itself returned.
176
+ * in the server counts the encoded bytes. Measured on varbinary(8): 8 ascii accepted, 9
177
+ * refused, 2 emoji (8 bytes) accepted, 3 emoji (12 bytes) refused, so a code-point cap
178
+ * applied to an insert schema accepts a write the server refuses.
179
+ *
180
+ * The generators pick which by mode.
181
+ */
182
+ | {
183
+ kind: 'byteString';
184
+ length?: number;
157
185
  };
158
186
  interface Key {
159
187
  name?: string;
package/dist/index.js CHANGED
@@ -29,6 +29,12 @@ var TUPLE_CLASS_SHAPES = {
29
29
  PgLineTuple: { kind: "tuple", length: 3 }
30
30
  };
31
31
  var V1_ONLY_ENTITY_KINDS = /^(?:MsSql|Cockroach)/;
32
+ var BYTE_STRING_CLASSES = /* @__PURE__ */ new Set([
33
+ "MySqlBinary",
34
+ "MySqlVarBinary",
35
+ "SingleStoreBinary",
36
+ "SingleStoreVarBinary"
37
+ ]);
32
38
  function describeV1Column(column) {
33
39
  const codec = column?.codec;
34
40
  const dataType = column?.dataType;
@@ -129,17 +135,20 @@ function describeV1Column(column) {
129
135
  out.tsType = "string";
130
136
  out.dbType = semantic.toUpperCase();
131
137
  break;
132
- case "binary":
138
+ case "binary": {
139
+ const entity = String(column?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
140
+ const bytes = entity.startsWith("MySql") || entity.startsWith("SingleStore");
133
141
  out.tsType = "string";
134
142
  out.dbType = codec === "bit" ? "BIT" : "BINARY";
135
- out.shape = {
143
+ out.shape = bytes ? { kind: "byteString", length: declaredLength(column) } : {
136
144
  kind: "bitstring",
137
145
  length: declaredLength(column),
138
- // A Postgres `bit(3)` holds exactly three digits; a MySQL `binary(4)`/`varbinary(16)`
139
- // holds at most that many, which is why `''` is valid there and not here.
146
+ // A Postgres `bit(3)` holds exactly three digits; a Cockroach `varbit(16)` holds at
147
+ // most that many, which is why `''` is valid there and not here.
140
148
  exact: codec === "bit"
141
149
  };
142
150
  break;
151
+ }
143
152
  case "point":
144
153
  case "geometry":
145
154
  out.tsType = "[number, number]";
@@ -515,6 +524,14 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
515
524
  return { tsType: "bigint", dbType: "BIGINT" };
516
525
  case "SQLiteNumeric":
517
526
  return { tsType: "string", dbType: "NUMERIC" };
527
+ // The other two modes of the same column, which matched no arm here and no dialect regex
528
+ // below either, so both came back UNKNOWN and every generator emitted a schema that
529
+ // accepted anything at all. Read back through better-sqlite3 3.53.4 on a `numeric` column,
530
+ // `db.select()` hands back a number in number mode and a bigint in bigint mode.
531
+ case "SQLiteNumericNumber":
532
+ return { tsType: "number", dbType: "NUMERIC" };
533
+ case "SQLiteNumericBigInt":
534
+ return { tsType: "bigint", dbType: "NUMERIC" };
518
535
  case "SQLiteBoolean":
519
536
  return { tsType: "boolean", dbType: "INTEGER" };
520
537
  // 0.4x gives an enum its own class, which had no arm here at all, so an enum column came
@@ -559,6 +576,15 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
559
576
  return { tsType: "Date", dbType: "TIMESTAMP" };
560
577
  case "PgNumeric":
561
578
  return { tsType: "string", dbType: "NUMERIC" };
579
+ // The other two modes, each its own class. `PgNumericNumber` reached the coarse
580
+ // `/Numeric|Float|Double|Real/i` arm and got the right answer there; `PgNumericBigInt` got
581
+ // its `bigint` from the `/BigInt/i` arm meant for `bigint` columns, and the SQL label that
582
+ // comes with it, so a `numeric(20,0)` was reported as a BIGINT column. Read back through
583
+ // PGlite, `db.select()` hands back a number in number mode and a bigint in bigint mode.
584
+ case "PgNumericNumber":
585
+ return { tsType: "number", dbType: "NUMERIC" };
586
+ case "PgNumericBigInt":
587
+ return { tsType: "bigint", dbType: "NUMERIC" };
562
588
  case "PgDoublePrecision":
563
589
  return { tsType: "number", dbType: "DOUBLE" };
564
590
  // `real()` builds a `PgReal`, which matched no arm and fell through to the coarse
@@ -581,6 +607,16 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
581
607
  case "PgJson":
582
608
  case "PgJsonb":
583
609
  return { tsType: "any", dbType: ctor === "PgJsonb" ? "JSONB" : "JSON" };
610
+ // The four builders in `BYTE_STRING_CLASSES`, which the coarse `/Blob|Binary|VarBinary/i`
611
+ // arms below would otherwise call a `Uint8Array` on the strength of the word "Binary" in
612
+ // the class name. Asked of MySQL 8.4 through drizzle 0.45.2 instead: the driver hands up a
613
+ // Buffer, `mapFromDriverValue` decodes it, and the caller receives a string. Every one of
614
+ // the four declares `dataType: 'string'` and every one of the four defines that method.
615
+ case "MySqlBinary":
616
+ case "MySqlVarBinary":
617
+ case "SingleStoreBinary":
618
+ case "SingleStoreVarBinary":
619
+ return { tsType: "string", dbType: "BINARY" };
584
620
  default:
585
621
  if (column?.config?.mode === "timestamp" || column?.mode === "timestamp") {
586
622
  return { tsType: "Date", dbType: "INTEGER" };
@@ -608,7 +644,12 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
608
644
  if (/BigInt64/i.test(ctor)) return { tsType: "bigint", dbType: "BIGINT" };
609
645
  if (/BigInt53/i.test(ctor)) return { tsType: "number", dbType: "BIGINT" };
610
646
  if (/\bBigInt\b/i.test(ctor)) return { tsType: "bigint", dbType: "BIGINT" };
611
- if (/Decimal|Numeric|Float|Double|Real/i.test(ctor))
647
+ if (/Decimal/i.test(ctor)) {
648
+ if (/DecimalNumber$/.test(ctor)) return { tsType: "number", dbType: "NUMERIC" };
649
+ if (/DecimalBigInt$/.test(ctor)) return { tsType: "bigint", dbType: "NUMERIC" };
650
+ return { tsType: "string", dbType: "NUMERIC" };
651
+ }
652
+ if (/Numeric|Float|Double|Real/i.test(ctor))
612
653
  return { tsType: "number", dbType: "NUMERIC" };
613
654
  if (/Int|Serial|TinyInt|SmallInt|MediumInt/i.test(ctor))
614
655
  return { tsType: "number", dbType: "INTEGER" };
@@ -627,7 +668,12 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
627
668
  if (/Vector/i.test(ctor)) return { tsType: "any", dbType: "VECTOR" };
628
669
  if (/BigInt64/i.test(ctor)) return { tsType: "bigint", dbType: "BIGINT" };
629
670
  if (/BigInt53/i.test(ctor)) return { tsType: "number", dbType: "BIGINT" };
630
- if (/Decimal|Numeric|Float|Double|Real/i.test(ctor))
671
+ if (/Decimal/i.test(ctor)) {
672
+ if (/DecimalNumber$/.test(ctor)) return { tsType: "number", dbType: "NUMERIC" };
673
+ if (/DecimalBigInt$/.test(ctor)) return { tsType: "bigint", dbType: "NUMERIC" };
674
+ return { tsType: "string", dbType: "NUMERIC" };
675
+ }
676
+ if (/Numeric|Float|Double|Real/i.test(ctor))
631
677
  return { tsType: "number", dbType: "NUMERIC" };
632
678
  if (/Int|Serial|TinyInt|SmallInt|MediumInt/i.test(ctor))
633
679
  return { tsType: "number", dbType: "INTEGER" };
@@ -699,7 +745,9 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
699
745
  const sqlKind = String(outerCol?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
700
746
  const sqlType = sqlKind.startsWith("MySql") && typeof col?.getSQLType === "function" ? String(col.getSQLType()).toLowerCase() : void 0;
701
747
  const byteCap = sqlType ? MYSQL_TEXT_CAPS[sqlType] : void 0;
702
- const fallbackShape = dbType === "JSON" || dbType === "JSONB" || col?.config?.mode === "json" ? { kind: "json" } : TUPLE_CLASS_SHAPES[String(col?.constructor?.name ?? "")];
748
+ const ctorName = String(col?.constructor?.name ?? "");
749
+ const fallbackShape = dbType === "JSON" || dbType === "JSONB" || col?.config?.mode === "json" ? { kind: "json" } : BYTE_STRING_CLASSES.has(ctorName) ? { kind: "byteString", length: declaredLength(col) } : TUPLE_CLASS_SHAPES[ctorName];
750
+ if (fallbackShape?.kind === "byteString") delete constraints.maxLength;
703
751
  const shape = (v1?.shape ?? fallbackShape)?.kind;
704
752
  const finalTs = v1?.tsType ?? tsType;
705
753
  const wide = (finalTs === "unknown" || finalTs === "any") && (!shape || shape === "custom");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drzl/analyzer",
3
- "version": "1.16.0",
3
+ "version": "1.17.0",
4
4
  "private": false,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",