@drzl/analyzer 1.17.1 → 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[]";
@@ -658,6 +673,15 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
658
673
  return { tsType: "[number, number]", dbType: "POINT" };
659
674
  case "PgLineTuple":
660
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" };
661
685
  case "PgJson":
662
686
  case "PgJsonb":
663
687
  return { tsType: "any", dbType: ctor === "PgJsonb" ? "JSONB" : "JSON" };
@@ -678,7 +702,6 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
678
702
  if (/^Pg/.test(ctor)) {
679
703
  if (/Text|Varchar|Char|Uuid/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
680
704
  if (/Inet|Cidr|Macaddr8?|Uuid/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
681
- if (/Point|Line/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
682
705
  if (/TimestampString|DateString/i.test(ctor))
683
706
  return { tsType: "string", dbType: "TIMESTAMP" };
684
707
  if (/Timestamptz/i.test(ctor)) return { tsType: "Date", dbType: "TIMESTAMPTZ" };
@@ -800,7 +823,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
800
823
  const sqlType = sqlKind.startsWith("MySql") && typeof col?.getSQLType === "function" ? String(col.getSQLType()).toLowerCase() : void 0;
801
824
  const byteCap = sqlType ? MYSQL_TEXT_CAPS[sqlType] : void 0;
802
825
  const ctorName = String(col?.constructor?.name ?? "");
803
- 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];
804
827
  if (fallbackShape?.kind === "byteString") delete constraints.maxLength;
805
828
  const shape = (v1?.shape ?? fallbackShape)?.kind;
806
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[]";
@@ -617,6 +632,15 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
617
632
  return { tsType: "[number, number]", dbType: "POINT" };
618
633
  case "PgLineTuple":
619
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" };
620
644
  case "PgJson":
621
645
  case "PgJsonb":
622
646
  return { tsType: "any", dbType: ctor === "PgJsonb" ? "JSONB" : "JSON" };
@@ -637,7 +661,6 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
637
661
  if (/^Pg/.test(ctor)) {
638
662
  if (/Text|Varchar|Char|Uuid/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
639
663
  if (/Inet|Cidr|Macaddr8?|Uuid/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
640
- if (/Point|Line/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
641
664
  if (/TimestampString|DateString/i.test(ctor))
642
665
  return { tsType: "string", dbType: "TIMESTAMP" };
643
666
  if (/Timestamptz/i.test(ctor)) return { tsType: "Date", dbType: "TIMESTAMPTZ" };
@@ -759,7 +782,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
759
782
  const sqlType = sqlKind.startsWith("MySql") && typeof col?.getSQLType === "function" ? String(col.getSQLType()).toLowerCase() : void 0;
760
783
  const byteCap = sqlType ? MYSQL_TEXT_CAPS[sqlType] : void 0;
761
784
  const ctorName = String(col?.constructor?.name ?? "");
762
- 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];
763
786
  if (fallbackShape?.kind === "byteString") delete constraints.maxLength;
764
787
  const shape = (v1?.shape ?? fallbackShape)?.kind;
765
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.1",
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": [