@drzl/analyzer 1.17.0 → 1.18.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/README.md CHANGED
@@ -51,7 +51,7 @@ Per column, beyond the type: `nullable`, `hasDefault`, `defaultValue` (literal d
51
51
  `isGenerated`, `enumValues`, `maxLength` (characters), `maxBytes` (MySQL's TEXT family is a byte
52
52
  budget, which is a different measurement on the same kind of column), `min`/`max`,
53
53
  `arrayDimensions`, `format`, and `shape` for values that are not scalars (json, buffer, tuple,
54
- vector, bitstring, customType).
54
+ numberObject, vector, bitstring, customType).
55
55
 
56
56
  `DRZL_ANL_UNKNOWN_COLUMN` is reported for any column whose validator would accept anything, which
57
57
  is the shape a missing type mapping takes: nothing throws, and every row passes.
package/dist/index.cjs CHANGED
@@ -63,11 +63,15 @@ var PG_FLOAT4_INPUT_MAX = "340282356779733661637539395458142568448";
63
63
  var PG_FLOAT4_RANGE = [`-${PG_FLOAT4_INPUT_MAX}`, PG_FLOAT4_INPUT_MAX];
64
64
  var MYSQL_FLOAT_RANGE = [`-${FLOAT32_MAX}`, FLOAT32_MAX];
65
65
  var JS_SAFE_INTEGER_BOUNDS = ["-9007199254740991", "9007199254740991"];
66
- var TUPLE_CLASS_SHAPES = {
66
+ var GEOMETRIC_CLASS_SHAPES = {
67
67
  // `line()` is the trap here: its `drizzle:entityKind` is `PgLine` while its constructor is
68
68
  // `PgLineTuple`, and this path matches on the constructor.
69
69
  PgPointTuple: { kind: "tuple", length: 2 },
70
- PgLineTuple: { kind: "tuple", length: 3 }
70
+ PgLineTuple: { kind: "tuple", length: 3 },
71
+ // The object modes. `line({ mode: 'abc' })` is a `PgLineABC` and not a `PgLineObject`, and any
72
+ // mode but `'tuple'` builds the object class: `point({ mode: 'abc' })` is a `PgPointObject` too.
73
+ PgPointObject: { kind: "numberObject", fields: ["x", "y"] },
74
+ PgLineABC: { kind: "numberObject", fields: ["a", "b", "c"] }
71
75
  };
72
76
  var V1_ONLY_ENTITY_KINDS = /^(?:MsSql|Cockroach)/;
73
77
  var BYTE_STRING_CLASSES = /* @__PURE__ */ new Set([
@@ -190,16 +194,27 @@ function describeV1Column(column) {
190
194
  };
191
195
  break;
192
196
  }
197
+ // Two modes per builder, and v1 states which in the JS half of the dataType it already
198
+ // carries: the tuple modes say `array point` / `array line` / `array geometry` and the object
199
+ // modes say `object point` / `object line` / `object geometry`. Read off real 1.0.0-rc.4
200
+ // columns rather than assumed. Both used to reach these arms and be called tuples, so a select
201
+ // schema for `point({ mode: 'xy' })` rejected every row the driver returned, on the major that
202
+ // describes the column outright.
203
+ //
204
+ // `js` rather than the codec, which also distinguishes them here (`point:tuple` against
205
+ // `point`): three dialects state a semantic with no codec at all on this release, so the codec
206
+ // is the weaker of the two signals and this file already prefers `js` elsewhere for the same
207
+ // reason.
193
208
  case "point":
194
209
  case "geometry":
195
- out.tsType = "[number, number]";
210
+ out.tsType = js === "object" ? "{ x: number; y: number }" : "[number, number]";
196
211
  out.dbType = semantic.toUpperCase();
197
- out.shape = { kind: "tuple", length: 2 };
212
+ out.shape = js === "object" ? { kind: "numberObject", fields: ["x", "y"] } : { kind: "tuple", length: 2 };
198
213
  break;
199
214
  case "line":
200
- out.tsType = "[number, number, number]";
215
+ out.tsType = js === "object" ? "{ a: number; b: number; c: number }" : "[number, number, number]";
201
216
  out.dbType = "LINE";
202
- out.shape = { kind: "tuple", length: 3 };
217
+ out.shape = js === "object" ? { kind: "numberObject", fields: ["a", "b", "c"] } : { kind: "tuple", length: 3 };
203
218
  break;
204
219
  case "vector":
205
220
  out.tsType = "number[]";
@@ -578,7 +593,20 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
578
593
  // 0.4x gives an enum its own class, which had no arm here at all, so an enum column came
579
594
  // back `unknown` and every generator emitted a schema that accepted anything. The values
580
595
  // were on the column the whole time, in `enumValues`, waiting for a type to attach to.
596
+ //
597
+ // The MySQL and SingleStore classes were added later than the Postgres one, and their
598
+ // absence showed up somewhere unexpected: the emitted validator was already right, because
599
+ // every generator reads `enumValues` before it reads `tsType`, so the only thing wrong was
600
+ // the description. That description reached the user anyway, through the untyped-column
601
+ // warning, which said an enum column "will accept any value" while the emitted schema
602
+ // accepted exactly three. A warning that is wrong about the one thing it names is worse
603
+ // than no warning, because it teaches the reader to skip the true ones.
604
+ //
605
+ // v1 already answered `string` here, so this is also the two majors agreeing again rather
606
+ // than a new opinion; the cross-major diff carried the disagreement as a filed defect.
581
607
  case "PgEnumColumn":
608
+ case "MySqlEnumColumn":
609
+ case "SingleStoreEnumColumn":
582
610
  return { tsType: "string", dbType: "TEXT" };
583
611
  case "PgInteger":
584
612
  case "PgSmallInt":
@@ -645,6 +673,15 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
645
673
  return { tsType: "[number, number]", dbType: "POINT" };
646
674
  case "PgLineTuple":
647
675
  return { tsType: "[number, number, number]", dbType: "LINE" };
676
+ // The object modes, which the same regex answered `string` for and which are not tuples
677
+ // either. `point({ mode: 'xy' })` returns `{ x, y }` and `line({ mode: 'abc' })` returns
678
+ // `{ a, b, c }`, read back from PGlite through drizzle 0.45.2; the same column refuses
679
+ // `[1, 2]` and `'1,2'`, both of which `mapToDriverValue` renders `(undefined,undefined)`.
680
+ // v1 says the same thing about the same two columns through `dataType: 'object point'`.
681
+ case "PgPointObject":
682
+ return { tsType: "{ x: number; y: number }", dbType: "POINT" };
683
+ case "PgLineABC":
684
+ return { tsType: "{ a: number; b: number; c: number }", dbType: "LINE" };
648
685
  case "PgJson":
649
686
  case "PgJsonb":
650
687
  return { tsType: "any", dbType: ctor === "PgJsonb" ? "JSONB" : "JSON" };
@@ -665,7 +702,6 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
665
702
  if (/^Pg/.test(ctor)) {
666
703
  if (/Text|Varchar|Char|Uuid/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
667
704
  if (/Inet|Cidr|Macaddr8?|Uuid/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
668
- if (/Point|Line/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
669
705
  if (/TimestampString|DateString/i.test(ctor))
670
706
  return { tsType: "string", dbType: "TIMESTAMP" };
671
707
  if (/Timestamptz/i.test(ctor)) return { tsType: "Date", dbType: "TIMESTAMPTZ" };
@@ -787,7 +823,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
787
823
  const sqlType = sqlKind.startsWith("MySql") && typeof col?.getSQLType === "function" ? String(col.getSQLType()).toLowerCase() : void 0;
788
824
  const byteCap = sqlType ? MYSQL_TEXT_CAPS[sqlType] : void 0;
789
825
  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];
826
+ const fallbackShape = dbType === "JSON" || dbType === "JSONB" || col?.config?.mode === "json" ? { kind: "json" } : BYTE_STRING_CLASSES.has(ctorName) ? { kind: "byteString", length: declaredLength(col) } : GEOMETRIC_CLASS_SHAPES[ctorName];
791
827
  if (fallbackShape?.kind === "byteString") delete constraints.maxLength;
792
828
  const shape = (v1?.shape ?? fallbackShape)?.kind;
793
829
  const finalTs = v1?.tsType ?? tsType;
package/dist/index.d.cts CHANGED
@@ -121,11 +121,33 @@ type ColumnShape =
121
121
  | {
122
122
  kind: 'json';
123
123
  }
124
- /** `point`, `line`, `geometry`: a fixed-length tuple of numbers. */
124
+ /** `point`, `line`, `geometry` in their tuple modes: a fixed-length tuple of numbers. */
125
125
  | {
126
126
  kind: 'tuple';
127
127
  length: number;
128
128
  }
129
+ /**
130
+ * The same three columns in their object modes: an object of named number fields.
131
+ *
132
+ * `point({ mode: 'xy' })` hands back `{ x, y }`, `line({ mode: 'abc' })` hands back
133
+ * `{ a, b, c }`, and `geometry({ type: 'point', mode: 'xy' })` hands back `{ x, y }`. The names
134
+ * are carried rather than derived from a length, because the two arities have different keys and
135
+ * nothing else on the column states them.
136
+ *
137
+ * Not a tuple, on either major, and the difference is a value the database refuses. Asked of
138
+ * PGlite through drizzle 0.45.2 and again through 1.0.0-rc.4: `mapToDriverValue` reads `.x`/`.y`
139
+ * off whatever it is handed, so `[1, 2]` and `'1,2'` both become the literal
140
+ * `(undefined,undefined)` and Postgres answers `invalid input syntax for type point`, while
141
+ * `{ x: 1.5, y: -2.25 }` is stored as `(1.5,-2.25)` and read back unchanged.
142
+ *
143
+ * Every field is required and unlisted keys are ignored, both measured on the same server:
144
+ * `{ x: 1 }` renders `(1,undefined)` and is refused, and `{ x: 1, y: 2, z: 3 }` is accepted and
145
+ * stored as `(1,2)`.
146
+ */
147
+ | {
148
+ kind: 'numberObject';
149
+ fields: string[];
150
+ }
129
151
  /** `vector`, `halfvec`: a numeric vector, with a fixed length where one is declared. */
130
152
  | {
131
153
  kind: 'numberVector';
package/dist/index.d.ts CHANGED
@@ -121,11 +121,33 @@ type ColumnShape =
121
121
  | {
122
122
  kind: 'json';
123
123
  }
124
- /** `point`, `line`, `geometry`: a fixed-length tuple of numbers. */
124
+ /** `point`, `line`, `geometry` in their tuple modes: a fixed-length tuple of numbers. */
125
125
  | {
126
126
  kind: 'tuple';
127
127
  length: number;
128
128
  }
129
+ /**
130
+ * The same three columns in their object modes: an object of named number fields.
131
+ *
132
+ * `point({ mode: 'xy' })` hands back `{ x, y }`, `line({ mode: 'abc' })` hands back
133
+ * `{ a, b, c }`, and `geometry({ type: 'point', mode: 'xy' })` hands back `{ x, y }`. The names
134
+ * are carried rather than derived from a length, because the two arities have different keys and
135
+ * nothing else on the column states them.
136
+ *
137
+ * Not a tuple, on either major, and the difference is a value the database refuses. Asked of
138
+ * PGlite through drizzle 0.45.2 and again through 1.0.0-rc.4: `mapToDriverValue` reads `.x`/`.y`
139
+ * off whatever it is handed, so `[1, 2]` and `'1,2'` both become the literal
140
+ * `(undefined,undefined)` and Postgres answers `invalid input syntax for type point`, while
141
+ * `{ x: 1.5, y: -2.25 }` is stored as `(1.5,-2.25)` and read back unchanged.
142
+ *
143
+ * Every field is required and unlisted keys are ignored, both measured on the same server:
144
+ * `{ x: 1 }` renders `(1,undefined)` and is refused, and `{ x: 1, y: 2, z: 3 }` is accepted and
145
+ * stored as `(1,2)`.
146
+ */
147
+ | {
148
+ kind: 'numberObject';
149
+ fields: string[];
150
+ }
129
151
  /** `vector`, `halfvec`: a numeric vector, with a fixed length where one is declared. */
130
152
  | {
131
153
  kind: 'numberVector';
package/dist/index.js CHANGED
@@ -22,11 +22,15 @@ var PG_FLOAT4_INPUT_MAX = "340282356779733661637539395458142568448";
22
22
  var PG_FLOAT4_RANGE = [`-${PG_FLOAT4_INPUT_MAX}`, PG_FLOAT4_INPUT_MAX];
23
23
  var MYSQL_FLOAT_RANGE = [`-${FLOAT32_MAX}`, FLOAT32_MAX];
24
24
  var JS_SAFE_INTEGER_BOUNDS = ["-9007199254740991", "9007199254740991"];
25
- var TUPLE_CLASS_SHAPES = {
25
+ var GEOMETRIC_CLASS_SHAPES = {
26
26
  // `line()` is the trap here: its `drizzle:entityKind` is `PgLine` while its constructor is
27
27
  // `PgLineTuple`, and this path matches on the constructor.
28
28
  PgPointTuple: { kind: "tuple", length: 2 },
29
- PgLineTuple: { kind: "tuple", length: 3 }
29
+ PgLineTuple: { kind: "tuple", length: 3 },
30
+ // The object modes. `line({ mode: 'abc' })` is a `PgLineABC` and not a `PgLineObject`, and any
31
+ // mode but `'tuple'` builds the object class: `point({ mode: 'abc' })` is a `PgPointObject` too.
32
+ PgPointObject: { kind: "numberObject", fields: ["x", "y"] },
33
+ PgLineABC: { kind: "numberObject", fields: ["a", "b", "c"] }
30
34
  };
31
35
  var V1_ONLY_ENTITY_KINDS = /^(?:MsSql|Cockroach)/;
32
36
  var BYTE_STRING_CLASSES = /* @__PURE__ */ new Set([
@@ -149,16 +153,27 @@ function describeV1Column(column) {
149
153
  };
150
154
  break;
151
155
  }
156
+ // Two modes per builder, and v1 states which in the JS half of the dataType it already
157
+ // carries: the tuple modes say `array point` / `array line` / `array geometry` and the object
158
+ // modes say `object point` / `object line` / `object geometry`. Read off real 1.0.0-rc.4
159
+ // columns rather than assumed. Both used to reach these arms and be called tuples, so a select
160
+ // schema for `point({ mode: 'xy' })` rejected every row the driver returned, on the major that
161
+ // describes the column outright.
162
+ //
163
+ // `js` rather than the codec, which also distinguishes them here (`point:tuple` against
164
+ // `point`): three dialects state a semantic with no codec at all on this release, so the codec
165
+ // is the weaker of the two signals and this file already prefers `js` elsewhere for the same
166
+ // reason.
152
167
  case "point":
153
168
  case "geometry":
154
- out.tsType = "[number, number]";
169
+ out.tsType = js === "object" ? "{ x: number; y: number }" : "[number, number]";
155
170
  out.dbType = semantic.toUpperCase();
156
- out.shape = { kind: "tuple", length: 2 };
171
+ out.shape = js === "object" ? { kind: "numberObject", fields: ["x", "y"] } : { kind: "tuple", length: 2 };
157
172
  break;
158
173
  case "line":
159
- out.tsType = "[number, number, number]";
174
+ out.tsType = js === "object" ? "{ a: number; b: number; c: number }" : "[number, number, number]";
160
175
  out.dbType = "LINE";
161
- out.shape = { kind: "tuple", length: 3 };
176
+ out.shape = js === "object" ? { kind: "numberObject", fields: ["a", "b", "c"] } : { kind: "tuple", length: 3 };
162
177
  break;
163
178
  case "vector":
164
179
  out.tsType = "number[]";
@@ -537,7 +552,20 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
537
552
  // 0.4x gives an enum its own class, which had no arm here at all, so an enum column came
538
553
  // back `unknown` and every generator emitted a schema that accepted anything. The values
539
554
  // were on the column the whole time, in `enumValues`, waiting for a type to attach to.
555
+ //
556
+ // The MySQL and SingleStore classes were added later than the Postgres one, and their
557
+ // absence showed up somewhere unexpected: the emitted validator was already right, because
558
+ // every generator reads `enumValues` before it reads `tsType`, so the only thing wrong was
559
+ // the description. That description reached the user anyway, through the untyped-column
560
+ // warning, which said an enum column "will accept any value" while the emitted schema
561
+ // accepted exactly three. A warning that is wrong about the one thing it names is worse
562
+ // than no warning, because it teaches the reader to skip the true ones.
563
+ //
564
+ // v1 already answered `string` here, so this is also the two majors agreeing again rather
565
+ // than a new opinion; the cross-major diff carried the disagreement as a filed defect.
540
566
  case "PgEnumColumn":
567
+ case "MySqlEnumColumn":
568
+ case "SingleStoreEnumColumn":
541
569
  return { tsType: "string", dbType: "TEXT" };
542
570
  case "PgInteger":
543
571
  case "PgSmallInt":
@@ -604,6 +632,15 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
604
632
  return { tsType: "[number, number]", dbType: "POINT" };
605
633
  case "PgLineTuple":
606
634
  return { tsType: "[number, number, number]", dbType: "LINE" };
635
+ // The object modes, which the same regex answered `string` for and which are not tuples
636
+ // either. `point({ mode: 'xy' })` returns `{ x, y }` and `line({ mode: 'abc' })` returns
637
+ // `{ a, b, c }`, read back from PGlite through drizzle 0.45.2; the same column refuses
638
+ // `[1, 2]` and `'1,2'`, both of which `mapToDriverValue` renders `(undefined,undefined)`.
639
+ // v1 says the same thing about the same two columns through `dataType: 'object point'`.
640
+ case "PgPointObject":
641
+ return { tsType: "{ x: number; y: number }", dbType: "POINT" };
642
+ case "PgLineABC":
643
+ return { tsType: "{ a: number; b: number; c: number }", dbType: "LINE" };
607
644
  case "PgJson":
608
645
  case "PgJsonb":
609
646
  return { tsType: "any", dbType: ctor === "PgJsonb" ? "JSONB" : "JSON" };
@@ -624,7 +661,6 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
624
661
  if (/^Pg/.test(ctor)) {
625
662
  if (/Text|Varchar|Char|Uuid/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
626
663
  if (/Inet|Cidr|Macaddr8?|Uuid/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
627
- if (/Point|Line/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
628
664
  if (/TimestampString|DateString/i.test(ctor))
629
665
  return { tsType: "string", dbType: "TIMESTAMP" };
630
666
  if (/Timestamptz/i.test(ctor)) return { tsType: "Date", dbType: "TIMESTAMPTZ" };
@@ -746,7 +782,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
746
782
  const sqlType = sqlKind.startsWith("MySql") && typeof col?.getSQLType === "function" ? String(col.getSQLType()).toLowerCase() : void 0;
747
783
  const byteCap = sqlType ? MYSQL_TEXT_CAPS[sqlType] : void 0;
748
784
  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];
785
+ const fallbackShape = dbType === "JSON" || dbType === "JSONB" || col?.config?.mode === "json" ? { kind: "json" } : BYTE_STRING_CLASSES.has(ctorName) ? { kind: "byteString", length: declaredLength(col) } : GEOMETRIC_CLASS_SHAPES[ctorName];
750
786
  if (fallbackShape?.kind === "byteString") delete constraints.maxLength;
751
787
  const shape = (v1?.shape ?? fallbackShape)?.kind;
752
788
  const finalTs = v1?.tsType ?? tsType;
package/package.json CHANGED
@@ -1,16 +1,22 @@
1
1
  {
2
2
  "name": "@drzl/analyzer",
3
- "version": "1.17.0",
3
+ "version": "1.18.0",
4
4
  "private": false,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
7
- "main": "dist/index.js",
8
- "types": "dist/index.d.ts",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
9
10
  "exports": {
10
11
  ".": {
11
- "types": "./dist/index.d.ts",
12
- "import": "./dist/index.js",
13
- "require": "./dist/index.cjs"
12
+ "import": {
13
+ "types": "./dist/index.d.ts",
14
+ "default": "./dist/index.js"
15
+ },
16
+ "require": {
17
+ "types": "./dist/index.d.cts",
18
+ "default": "./dist/index.cjs"
19
+ }
14
20
  }
15
21
  },
16
22
  "files": [