@drzl/analyzer 1.14.0 → 1.15.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
@@ -47,6 +47,17 @@ The CLI consumes this analysis to generate validation, services, and routers.
47
47
  - relations (incl. inferred), enums
48
48
  - issues (warnings/errors) for constraints and shape
49
49
 
50
+ Per column, beyond the type: `nullable`, `hasDefault`, `defaultValue` (literal defaults only),
51
+ `isGenerated`, `enumValues`, `maxLength` (characters), `maxBytes` (MySQL's TEXT family is a byte
52
+ budget, which is a different measurement on the same kind of column), `min`/`max`,
53
+ `arrayDimensions`, `format`, and `shape` for values that are not scalars (json, buffer, tuple,
54
+ vector, bitstring, customType).
55
+
56
+ `DRZL_ANL_UNKNOWN_COLUMN` is reported for any column whose validator would accept anything, which
57
+ is the shape a missing type mapping takes: nothing throws, and every row passes.
58
+
50
59
  ## Notes
51
60
 
52
- - Best‑effort introspection aligned with Drizzle symbols across versions.
61
+ - Best‑effort introspection aligned with Drizzle symbols across versions. Both live majors are
62
+ covered and diffed against each other in CI: 0.4x and v1 model arrays and enums differently, and
63
+ reading only one silently typed every `.array()` column as `unknown` on the other.
package/dist/index.cjs CHANGED
@@ -57,11 +57,16 @@ var MYSQL_TEXT_CAPS = {
57
57
  mediumblob: 16777215,
58
58
  longblob: 4294967295
59
59
  };
60
- var V1_FLOAT_BOUNDS = {
61
- float: ["-8388608", "8388607"],
62
- // real / float4, 2^23
63
- double: ["-140737488355328", "140737488355327"]
64
- // double precision / float8, 2^47
60
+ var FLOAT32_MAX = "340282346638528859811704183484516925440";
61
+ var PG_FLOAT4_INPUT_MAX = "340282356779733661637539395458142568448";
62
+ var PG_FLOAT4_RANGE = [`-${PG_FLOAT4_INPUT_MAX}`, PG_FLOAT4_INPUT_MAX];
63
+ var MYSQL_FLOAT_RANGE = [`-${FLOAT32_MAX}`, FLOAT32_MAX];
64
+ var JS_SAFE_INTEGER_BOUNDS = ["-9007199254740991", "9007199254740991"];
65
+ var TUPLE_CLASS_SHAPES = {
66
+ // `line()` is the trap here: its `drizzle:entityKind` is `PgLine` while its constructor is
67
+ // `PgLineTuple`, and this path matches on the constructor.
68
+ PgPointTuple: { kind: "tuple", length: 2 },
69
+ PgLineTuple: { kind: "tuple", length: 3 }
65
70
  };
66
71
  function describeV1Column(column) {
67
72
  const codec = column?.codec;
@@ -111,7 +116,8 @@ function describeV1Column(column) {
111
116
  break;
112
117
  case "float":
113
118
  case "double": {
114
- [out.min, out.max] = V1_FLOAT_BOUNDS[semantic];
119
+ if (semantic === "float")
120
+ [out.min, out.max] = codec === "float4" ? PG_FLOAT4_RANGE : MYSQL_FLOAT_RANGE;
115
121
  out.integer = false;
116
122
  out.tsType = "number";
117
123
  out.dbType = semantic === "float" ? "REAL" : "DOUBLE";
@@ -200,7 +206,7 @@ function describeV1Column(column) {
200
206
  out.tsType = "number";
201
207
  out.dbType = "NUMERIC";
202
208
  out.integer = false;
203
- [out.min, out.max] = ["-9007199254740991", "9007199254740991"];
209
+ [out.min, out.max] = JS_SAFE_INTEGER_BOUNDS;
204
210
  } else if (js === "string") {
205
211
  out.tsType = "string";
206
212
  out.dbType = codec === "varchar" ? "VARCHAR" : codec === "char" ? "CHAR" : "TEXT";
@@ -507,6 +513,11 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
507
513
  [out.min, out.max] = range;
508
514
  out.integer = true;
509
515
  }
516
+ if (Object.prototype.hasOwnProperty.call(_SchemaAnalyzer.INEXACT_RANGES, ctor)) {
517
+ const inexact = _SchemaAnalyzer.INEXACT_RANGES[ctor];
518
+ if (inexact) [out.min, out.max] = inexact;
519
+ out.integer = false;
520
+ }
510
521
  if (/^(Pg)?UUID$/i.test(ctor) || /Uuid$/i.test(ctor)) out.format = "uuid";
511
522
  return out;
512
523
  }
@@ -578,9 +589,25 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
578
589
  return { tsType: "Date", dbType: "TIMESTAMP" };
579
590
  case "PgNumeric":
580
591
  return { tsType: "string", dbType: "NUMERIC" };
581
- case "PgFloat":
582
592
  case "PgDoublePrecision":
583
593
  return { tsType: "number", dbType: "DOUBLE" };
594
+ // `real()` builds a `PgReal`, which matched no arm and fell through to the coarse
595
+ // `/Numeric|Float|Double|Real/i` below, so a real column was labelled NUMERIC while v1
596
+ // called it REAL. The arm above used to name `PgFloat` alongside `PgDoublePrecision`, and
597
+ // no such class exists in pg-core on either major: `float` is MySQL's spelling and Gel's
598
+ // is `GelReal`, both of which are matched elsewhere. Enumerated from the module's own
599
+ // exports on 0.45.2 and on 1.0.0-rc.4, which name only PgReal and PgDoublePrecision.
600
+ case "PgReal":
601
+ return { tsType: "number", dbType: "REAL" };
602
+ // 0.4x names a point and a line by their mode. `point()` is a `PgPointTuple` and `line()` a
603
+ // `PgLineTuple`, whose entity kind is `PgLine` while its constructor is not, and both used
604
+ // to fall through to `/Point|Line/i` and come back `string`. The driver hands back [x, y]
605
+ // and [a, b, c], so a select schema built on 0.4x refused every row, and an insert schema
606
+ // took the one string form `mapToDriverValue` turns into something Postgres rejects.
607
+ case "PgPointTuple":
608
+ return { tsType: "[number, number]", dbType: "POINT" };
609
+ case "PgLineTuple":
610
+ return { tsType: "[number, number, number]", dbType: "LINE" };
584
611
  case "PgJson":
585
612
  case "PgJsonb":
586
613
  return { tsType: "any", dbType: ctor === "PgJsonb" ? "JSONB" : "JSON" };
@@ -703,8 +730,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
703
730
  const sqlKind = String(outerCol?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
704
731
  const sqlType = sqlKind.startsWith("MySql") && typeof col?.getSQLType === "function" ? String(col.getSQLType()).toLowerCase() : void 0;
705
732
  const byteCap = sqlType ? MYSQL_TEXT_CAPS[sqlType] : void 0;
706
- const jsonShape = dbType === "JSON" || dbType === "JSONB" || col?.config?.mode === "json" ? { kind: "json" } : void 0;
707
- const shape = (v1?.shape ?? jsonShape)?.kind;
733
+ const fallbackShape = dbType === "JSON" || dbType === "JSONB" || col?.config?.mode === "json" ? { kind: "json" } : TUPLE_CLASS_SHAPES[String(col?.constructor?.name ?? "")];
734
+ const shape = (v1?.shape ?? fallbackShape)?.kind;
708
735
  const finalTs = v1?.tsType ?? tsType;
709
736
  const wide = (finalTs === "unknown" || finalTs === "any") && (!shape || shape === "custom");
710
737
  if (wide) {
@@ -733,7 +760,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
733
760
  // that spread has nothing to say and this is the only source.
734
761
  ...arrayDims ? { arrayDimensions: arrayDims } : {},
735
762
  // Only where v1 did not already describe the value, so a shaped column keeps its shape.
736
- ...jsonShape && !v1?.shape ? { shape: jsonShape } : {},
763
+ ...fallbackShape && !v1?.shape ? { shape: fallbackShape } : {},
737
764
  ...byteCap && v1?.maxBytes === void 0 ? { maxBytes: byteCap } : {}
738
765
  });
739
766
  }
@@ -1011,6 +1038,60 @@ _SchemaAnalyzer.INT_RANGES = {
1011
1038
  MySqlBigInt64: ["-9223372036854775808", "9223372036854775807"],
1012
1039
  SingleStoreBigInt64: ["-9223372036854775808", "9223372036854775807"]
1013
1040
  };
1041
+ /**
1042
+ * The numeric column classes that are not exact, and the magnitude each one can really hold.
1043
+ *
1044
+ * Only drizzle v1 states this outright, as a `float` or `double` semantic on `dataType`. On
1045
+ * 0.4x the same columns reach the analyzer by class name, `INT_RANGES` was the only range table
1046
+ * on that path, and so nothing said anything about them at all: not the range, and not that
1047
+ * they are inexact. The parity gate measured seven of the ten classes below and reported DRZL
1048
+ * differing from the first-party validator for the same major on all seven. The three
1049
+ * SingleStore classes are in no fixture either pass carries and are covered by unit tests alone.
1050
+ *
1051
+ * Differing is what the gate reports, not what it forbids. Most of its waivers have DRZL
1052
+ * accepting something official refuses and the run counts them; an earlier version of this
1053
+ * sentence said the gate exists to forbid being looser, which the same commit's own success
1054
+ * banner denies.
1055
+ *
1056
+ * `null` is a value in this table and is not the same as a class it does not name. It says the
1057
+ * column is inexact and that no finite magnitude bound is truthful for it, which is the case for
1058
+ * every 8 byte float: float8 is the JavaScript number's own format, so Postgres accepts every
1059
+ * finite JS number into one, measured to `Number.MAX_VALUE`. Read with an own-property test for
1060
+ * that reason, and because a plain object answers to `constructor` and `toString`.
1061
+ *
1062
+ * `integer: false` travels with every entry, bound or not, and what it decides depends on which.
1063
+ * `isIntegerColumn` falls back to "declares both bounds" when the flag is absent, so on a bounded
1064
+ * entry the flag is the only thing stopping the emitted schema calling `.int()` and refusing 1.5.
1065
+ * On an unbounded one it decides nothing, measured both ways against the real function in
1066
+ * `@drzl/validation-core`'s integer-column.spec.ts. It is stated there because it is true of the
1067
+ * column, not because it guards anything. That spec is where the measurement moved when the
1068
+ * analyzer's copy of it turned out to be a closed loop; this sentence went on naming
1069
+ * floats-and-tuples-0.4x.spec.ts, which asserts the flag is present and nothing about what it
1070
+ * decides.
1071
+ *
1072
+ * The widths are the type's, not the name's: MySQL and SingleStore `real` is a synonym for
1073
+ * `double` unless REAL_AS_FLOAT is set, and SQLite `real` is an 8 byte IEEE float. Both are
1074
+ * `number double` on drizzle v1, which is where these pairings come from.
1075
+ */
1076
+ _SchemaAnalyzer.INEXACT_RANGES = {
1077
+ // 4 byte floats, the one width a database refuses a magnitude for, and the two that have one
1078
+ // refuse at different values. SingleStore is MySQL wire-compatible and unmeasured here, so it
1079
+ // takes MySQL's rather than the wider of the two.
1080
+ PgReal: PG_FLOAT4_RANGE,
1081
+ MySqlFloat: MYSQL_FLOAT_RANGE,
1082
+ SingleStoreFloat: MYSQL_FLOAT_RANGE,
1083
+ // 8 byte floats, which hold every finite JS number
1084
+ PgDoublePrecision: null,
1085
+ MySqlDouble: null,
1086
+ MySqlReal: null,
1087
+ SQLiteReal: null,
1088
+ SingleStoreDouble: null,
1089
+ SingleStoreReal: null,
1090
+ // `numeric({ mode: 'number' })`, which v1 reaches through the bare-number arm of
1091
+ // `describeV1Column`. This one is about what a JS number can carry rather than about the
1092
+ // column, which Postgres caps far lower: it refuses 2147483648 into a `numeric(10,2)`.
1093
+ PgNumericNumber: JS_SAFE_INTEGER_BOUNDS
1094
+ };
1014
1095
  var SchemaAnalyzer = _SchemaAnalyzer;
1015
1096
  var index_default = SchemaAnalyzer;
1016
1097
  // Annotate the CommonJS export names for ESM import in node:
package/dist/index.d.cts CHANGED
@@ -354,6 +354,42 @@ declare class SchemaAnalyzer {
354
354
  * promise a precision that cannot survive the round trip.
355
355
  */
356
356
  private static readonly INT_RANGES;
357
+ /**
358
+ * The numeric column classes that are not exact, and the magnitude each one can really hold.
359
+ *
360
+ * Only drizzle v1 states this outright, as a `float` or `double` semantic on `dataType`. On
361
+ * 0.4x the same columns reach the analyzer by class name, `INT_RANGES` was the only range table
362
+ * on that path, and so nothing said anything about them at all: not the range, and not that
363
+ * they are inexact. The parity gate measured seven of the ten classes below and reported DRZL
364
+ * differing from the first-party validator for the same major on all seven. The three
365
+ * SingleStore classes are in no fixture either pass carries and are covered by unit tests alone.
366
+ *
367
+ * Differing is what the gate reports, not what it forbids. Most of its waivers have DRZL
368
+ * accepting something official refuses and the run counts them; an earlier version of this
369
+ * sentence said the gate exists to forbid being looser, which the same commit's own success
370
+ * banner denies.
371
+ *
372
+ * `null` is a value in this table and is not the same as a class it does not name. It says the
373
+ * column is inexact and that no finite magnitude bound is truthful for it, which is the case for
374
+ * every 8 byte float: float8 is the JavaScript number's own format, so Postgres accepts every
375
+ * finite JS number into one, measured to `Number.MAX_VALUE`. Read with an own-property test for
376
+ * that reason, and because a plain object answers to `constructor` and `toString`.
377
+ *
378
+ * `integer: false` travels with every entry, bound or not, and what it decides depends on which.
379
+ * `isIntegerColumn` falls back to "declares both bounds" when the flag is absent, so on a bounded
380
+ * entry the flag is the only thing stopping the emitted schema calling `.int()` and refusing 1.5.
381
+ * On an unbounded one it decides nothing, measured both ways against the real function in
382
+ * `@drzl/validation-core`'s integer-column.spec.ts. It is stated there because it is true of the
383
+ * column, not because it guards anything. That spec is where the measurement moved when the
384
+ * analyzer's copy of it turned out to be a closed loop; this sentence went on naming
385
+ * floats-and-tuples-0.4x.spec.ts, which asserts the flag is present and nothing about what it
386
+ * decides.
387
+ *
388
+ * The widths are the type's, not the name's: MySQL and SingleStore `real` is a synonym for
389
+ * `double` unless REAL_AS_FLOAT is set, and SQLite `real` is an 8 byte IEEE float. Both are
390
+ * `number double` on drizzle v1, which is where these pairings come from.
391
+ */
392
+ private static readonly INEXACT_RANGES;
357
393
  /**
358
394
  * Constraints the column definition already carries, which the analysis used to throw away.
359
395
  *
package/dist/index.d.ts CHANGED
@@ -354,6 +354,42 @@ declare class SchemaAnalyzer {
354
354
  * promise a precision that cannot survive the round trip.
355
355
  */
356
356
  private static readonly INT_RANGES;
357
+ /**
358
+ * The numeric column classes that are not exact, and the magnitude each one can really hold.
359
+ *
360
+ * Only drizzle v1 states this outright, as a `float` or `double` semantic on `dataType`. On
361
+ * 0.4x the same columns reach the analyzer by class name, `INT_RANGES` was the only range table
362
+ * on that path, and so nothing said anything about them at all: not the range, and not that
363
+ * they are inexact. The parity gate measured seven of the ten classes below and reported DRZL
364
+ * differing from the first-party validator for the same major on all seven. The three
365
+ * SingleStore classes are in no fixture either pass carries and are covered by unit tests alone.
366
+ *
367
+ * Differing is what the gate reports, not what it forbids. Most of its waivers have DRZL
368
+ * accepting something official refuses and the run counts them; an earlier version of this
369
+ * sentence said the gate exists to forbid being looser, which the same commit's own success
370
+ * banner denies.
371
+ *
372
+ * `null` is a value in this table and is not the same as a class it does not name. It says the
373
+ * column is inexact and that no finite magnitude bound is truthful for it, which is the case for
374
+ * every 8 byte float: float8 is the JavaScript number's own format, so Postgres accepts every
375
+ * finite JS number into one, measured to `Number.MAX_VALUE`. Read with an own-property test for
376
+ * that reason, and because a plain object answers to `constructor` and `toString`.
377
+ *
378
+ * `integer: false` travels with every entry, bound or not, and what it decides depends on which.
379
+ * `isIntegerColumn` falls back to "declares both bounds" when the flag is absent, so on a bounded
380
+ * entry the flag is the only thing stopping the emitted schema calling `.int()` and refusing 1.5.
381
+ * On an unbounded one it decides nothing, measured both ways against the real function in
382
+ * `@drzl/validation-core`'s integer-column.spec.ts. It is stated there because it is true of the
383
+ * column, not because it guards anything. That spec is where the measurement moved when the
384
+ * analyzer's copy of it turned out to be a closed loop; this sentence went on naming
385
+ * floats-and-tuples-0.4x.spec.ts, which asserts the flag is present and nothing about what it
386
+ * decides.
387
+ *
388
+ * The widths are the type's, not the name's: MySQL and SingleStore `real` is a synonym for
389
+ * `double` unless REAL_AS_FLOAT is set, and SQLite `real` is an 8 byte IEEE float. Both are
390
+ * `number double` on drizzle v1, which is where these pairings come from.
391
+ */
392
+ private static readonly INEXACT_RANGES;
357
393
  /**
358
394
  * Constraints the column definition already carries, which the analysis used to throw away.
359
395
  *
package/dist/index.js CHANGED
@@ -17,11 +17,16 @@ var MYSQL_TEXT_CAPS = {
17
17
  mediumblob: 16777215,
18
18
  longblob: 4294967295
19
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
20
+ var FLOAT32_MAX = "340282346638528859811704183484516925440";
21
+ var PG_FLOAT4_INPUT_MAX = "340282356779733661637539395458142568448";
22
+ var PG_FLOAT4_RANGE = [`-${PG_FLOAT4_INPUT_MAX}`, PG_FLOAT4_INPUT_MAX];
23
+ var MYSQL_FLOAT_RANGE = [`-${FLOAT32_MAX}`, FLOAT32_MAX];
24
+ var JS_SAFE_INTEGER_BOUNDS = ["-9007199254740991", "9007199254740991"];
25
+ var TUPLE_CLASS_SHAPES = {
26
+ // `line()` is the trap here: its `drizzle:entityKind` is `PgLine` while its constructor is
27
+ // `PgLineTuple`, and this path matches on the constructor.
28
+ PgPointTuple: { kind: "tuple", length: 2 },
29
+ PgLineTuple: { kind: "tuple", length: 3 }
25
30
  };
26
31
  function describeV1Column(column) {
27
32
  const codec = column?.codec;
@@ -71,7 +76,8 @@ function describeV1Column(column) {
71
76
  break;
72
77
  case "float":
73
78
  case "double": {
74
- [out.min, out.max] = V1_FLOAT_BOUNDS[semantic];
79
+ if (semantic === "float")
80
+ [out.min, out.max] = codec === "float4" ? PG_FLOAT4_RANGE : MYSQL_FLOAT_RANGE;
75
81
  out.integer = false;
76
82
  out.tsType = "number";
77
83
  out.dbType = semantic === "float" ? "REAL" : "DOUBLE";
@@ -160,7 +166,7 @@ function describeV1Column(column) {
160
166
  out.tsType = "number";
161
167
  out.dbType = "NUMERIC";
162
168
  out.integer = false;
163
- [out.min, out.max] = ["-9007199254740991", "9007199254740991"];
169
+ [out.min, out.max] = JS_SAFE_INTEGER_BOUNDS;
164
170
  } else if (js === "string") {
165
171
  out.tsType = "string";
166
172
  out.dbType = codec === "varchar" ? "VARCHAR" : codec === "char" ? "CHAR" : "TEXT";
@@ -467,6 +473,11 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
467
473
  [out.min, out.max] = range;
468
474
  out.integer = true;
469
475
  }
476
+ if (Object.prototype.hasOwnProperty.call(_SchemaAnalyzer.INEXACT_RANGES, ctor)) {
477
+ const inexact = _SchemaAnalyzer.INEXACT_RANGES[ctor];
478
+ if (inexact) [out.min, out.max] = inexact;
479
+ out.integer = false;
480
+ }
470
481
  if (/^(Pg)?UUID$/i.test(ctor) || /Uuid$/i.test(ctor)) out.format = "uuid";
471
482
  return out;
472
483
  }
@@ -538,9 +549,25 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
538
549
  return { tsType: "Date", dbType: "TIMESTAMP" };
539
550
  case "PgNumeric":
540
551
  return { tsType: "string", dbType: "NUMERIC" };
541
- case "PgFloat":
542
552
  case "PgDoublePrecision":
543
553
  return { tsType: "number", dbType: "DOUBLE" };
554
+ // `real()` builds a `PgReal`, which matched no arm and fell through to the coarse
555
+ // `/Numeric|Float|Double|Real/i` below, so a real column was labelled NUMERIC while v1
556
+ // called it REAL. The arm above used to name `PgFloat` alongside `PgDoublePrecision`, and
557
+ // no such class exists in pg-core on either major: `float` is MySQL's spelling and Gel's
558
+ // is `GelReal`, both of which are matched elsewhere. Enumerated from the module's own
559
+ // exports on 0.45.2 and on 1.0.0-rc.4, which name only PgReal and PgDoublePrecision.
560
+ case "PgReal":
561
+ return { tsType: "number", dbType: "REAL" };
562
+ // 0.4x names a point and a line by their mode. `point()` is a `PgPointTuple` and `line()` a
563
+ // `PgLineTuple`, whose entity kind is `PgLine` while its constructor is not, and both used
564
+ // to fall through to `/Point|Line/i` and come back `string`. The driver hands back [x, y]
565
+ // and [a, b, c], so a select schema built on 0.4x refused every row, and an insert schema
566
+ // took the one string form `mapToDriverValue` turns into something Postgres rejects.
567
+ case "PgPointTuple":
568
+ return { tsType: "[number, number]", dbType: "POINT" };
569
+ case "PgLineTuple":
570
+ return { tsType: "[number, number, number]", dbType: "LINE" };
544
571
  case "PgJson":
545
572
  case "PgJsonb":
546
573
  return { tsType: "any", dbType: ctor === "PgJsonb" ? "JSONB" : "JSON" };
@@ -663,8 +690,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
663
690
  const sqlKind = String(outerCol?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
664
691
  const sqlType = sqlKind.startsWith("MySql") && typeof col?.getSQLType === "function" ? String(col.getSQLType()).toLowerCase() : void 0;
665
692
  const byteCap = sqlType ? MYSQL_TEXT_CAPS[sqlType] : void 0;
666
- const jsonShape = dbType === "JSON" || dbType === "JSONB" || col?.config?.mode === "json" ? { kind: "json" } : void 0;
667
- const shape = (v1?.shape ?? jsonShape)?.kind;
693
+ const fallbackShape = dbType === "JSON" || dbType === "JSONB" || col?.config?.mode === "json" ? { kind: "json" } : TUPLE_CLASS_SHAPES[String(col?.constructor?.name ?? "")];
694
+ const shape = (v1?.shape ?? fallbackShape)?.kind;
668
695
  const finalTs = v1?.tsType ?? tsType;
669
696
  const wide = (finalTs === "unknown" || finalTs === "any") && (!shape || shape === "custom");
670
697
  if (wide) {
@@ -693,7 +720,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
693
720
  // that spread has nothing to say and this is the only source.
694
721
  ...arrayDims ? { arrayDimensions: arrayDims } : {},
695
722
  // Only where v1 did not already describe the value, so a shaped column keeps its shape.
696
- ...jsonShape && !v1?.shape ? { shape: jsonShape } : {},
723
+ ...fallbackShape && !v1?.shape ? { shape: fallbackShape } : {},
697
724
  ...byteCap && v1?.maxBytes === void 0 ? { maxBytes: byteCap } : {}
698
725
  });
699
726
  }
@@ -971,6 +998,60 @@ _SchemaAnalyzer.INT_RANGES = {
971
998
  MySqlBigInt64: ["-9223372036854775808", "9223372036854775807"],
972
999
  SingleStoreBigInt64: ["-9223372036854775808", "9223372036854775807"]
973
1000
  };
1001
+ /**
1002
+ * The numeric column classes that are not exact, and the magnitude each one can really hold.
1003
+ *
1004
+ * Only drizzle v1 states this outright, as a `float` or `double` semantic on `dataType`. On
1005
+ * 0.4x the same columns reach the analyzer by class name, `INT_RANGES` was the only range table
1006
+ * on that path, and so nothing said anything about them at all: not the range, and not that
1007
+ * they are inexact. The parity gate measured seven of the ten classes below and reported DRZL
1008
+ * differing from the first-party validator for the same major on all seven. The three
1009
+ * SingleStore classes are in no fixture either pass carries and are covered by unit tests alone.
1010
+ *
1011
+ * Differing is what the gate reports, not what it forbids. Most of its waivers have DRZL
1012
+ * accepting something official refuses and the run counts them; an earlier version of this
1013
+ * sentence said the gate exists to forbid being looser, which the same commit's own success
1014
+ * banner denies.
1015
+ *
1016
+ * `null` is a value in this table and is not the same as a class it does not name. It says the
1017
+ * column is inexact and that no finite magnitude bound is truthful for it, which is the case for
1018
+ * every 8 byte float: float8 is the JavaScript number's own format, so Postgres accepts every
1019
+ * finite JS number into one, measured to `Number.MAX_VALUE`. Read with an own-property test for
1020
+ * that reason, and because a plain object answers to `constructor` and `toString`.
1021
+ *
1022
+ * `integer: false` travels with every entry, bound or not, and what it decides depends on which.
1023
+ * `isIntegerColumn` falls back to "declares both bounds" when the flag is absent, so on a bounded
1024
+ * entry the flag is the only thing stopping the emitted schema calling `.int()` and refusing 1.5.
1025
+ * On an unbounded one it decides nothing, measured both ways against the real function in
1026
+ * `@drzl/validation-core`'s integer-column.spec.ts. It is stated there because it is true of the
1027
+ * column, not because it guards anything. That spec is where the measurement moved when the
1028
+ * analyzer's copy of it turned out to be a closed loop; this sentence went on naming
1029
+ * floats-and-tuples-0.4x.spec.ts, which asserts the flag is present and nothing about what it
1030
+ * decides.
1031
+ *
1032
+ * The widths are the type's, not the name's: MySQL and SingleStore `real` is a synonym for
1033
+ * `double` unless REAL_AS_FLOAT is set, and SQLite `real` is an 8 byte IEEE float. Both are
1034
+ * `number double` on drizzle v1, which is where these pairings come from.
1035
+ */
1036
+ _SchemaAnalyzer.INEXACT_RANGES = {
1037
+ // 4 byte floats, the one width a database refuses a magnitude for, and the two that have one
1038
+ // refuse at different values. SingleStore is MySQL wire-compatible and unmeasured here, so it
1039
+ // takes MySQL's rather than the wider of the two.
1040
+ PgReal: PG_FLOAT4_RANGE,
1041
+ MySqlFloat: MYSQL_FLOAT_RANGE,
1042
+ SingleStoreFloat: MYSQL_FLOAT_RANGE,
1043
+ // 8 byte floats, which hold every finite JS number
1044
+ PgDoublePrecision: null,
1045
+ MySqlDouble: null,
1046
+ MySqlReal: null,
1047
+ SQLiteReal: null,
1048
+ SingleStoreDouble: null,
1049
+ SingleStoreReal: null,
1050
+ // `numeric({ mode: 'number' })`, which v1 reaches through the bare-number arm of
1051
+ // `describeV1Column`. This one is about what a JS number can carry rather than about the
1052
+ // column, which Postgres caps far lower: it refuses 2147483648 into a `numeric(10,2)`.
1053
+ PgNumericNumber: JS_SAFE_INTEGER_BOUNDS
1054
+ };
974
1055
  var SchemaAnalyzer = _SchemaAnalyzer;
975
1056
  var index_default = SchemaAnalyzer;
976
1057
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drzl/analyzer",
3
- "version": "1.14.0",
3
+ "version": "1.15.0",
4
4
  "private": false,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -43,7 +43,7 @@
43
43
  "url": "https://github.com/sponsors/omar-dulaimi"
44
44
  },
45
45
  "scripts": {
46
- "build": "tsup src/index.ts --dts --format esm,cjs",
46
+ "build": "tsup src/index.ts --dts --format esm,cjs --clean",
47
47
  "lint": "eslint . --ext .ts",
48
48
  "test": "vitest run --testTimeout=20000"
49
49
  }