@drzl/analyzer 1.12.0 → 1.14.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
@@ -206,7 +206,7 @@ function describeV1Column(column) {
206
206
  out.dbType = codec === "varchar" ? "VARCHAR" : codec === "char" ? "CHAR" : "TEXT";
207
207
  const kind = String(column?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
208
208
  const cap = codec && kind.startsWith("MySql") ? MYSQL_TEXT_CAPS[codec] : void 0;
209
- if (cap) out.maxLength = cap;
209
+ if (cap) out.maxBytes = cap;
210
210
  } else {
211
211
  return null;
212
212
  }
@@ -215,6 +215,15 @@ function describeV1Column(column) {
215
215
  if (typeof dims === "number" && dims >= 1) out.arrayDimensions = dims;
216
216
  return out;
217
217
  }
218
+ function unwrapArrayColumn(column) {
219
+ let element = column;
220
+ let dimensions = 0;
221
+ while (element?.baseColumn && String(element?.constructor?.name ?? "").endsWith("Array")) {
222
+ element = element.baseColumn;
223
+ dimensions++;
224
+ }
225
+ return { element, dimensions };
226
+ }
218
227
  function declaredLength(column) {
219
228
  const n = column?.length ?? column?.config?.length ?? column?.config?.dimensions;
220
229
  return typeof n === "number" && Number.isFinite(n) && n > 0 ? n : void 0;
@@ -515,10 +524,23 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
515
524
  return { tsType: "number", dbType: "REAL" };
516
525
  case "SQLiteBlob":
517
526
  return { tsType: "Uint8Array", dbType: "BLOB" };
527
+ // SQLite spells a mode as a distinct class rather than as config, so `text({mode:'json'})`
528
+ // is a `SQLiteTextJson` and matched no arm at all: the column came back UNKNOWN, which is
529
+ // wider than the `any` a json column at least used to get.
530
+ case "SQLiteTextJson":
531
+ case "SQLiteBlobJson":
532
+ return { tsType: "any", dbType: "JSON" };
533
+ case "SQLiteBigInt":
534
+ return { tsType: "bigint", dbType: "BIGINT" };
518
535
  case "SQLiteNumeric":
519
536
  return { tsType: "string", dbType: "NUMERIC" };
520
537
  case "SQLiteBoolean":
521
538
  return { tsType: "boolean", dbType: "INTEGER" };
539
+ // 0.4x gives an enum its own class, which had no arm here at all, so an enum column came
540
+ // back `unknown` and every generator emitted a schema that accepted anything. The values
541
+ // were on the column the whole time, in `enumValues`, waiting for a type to attach to.
542
+ case "PgEnumColumn":
543
+ return { tsType: "string", dbType: "TEXT" };
522
544
  case "PgInteger":
523
545
  case "PgSmallInt":
524
546
  return { tsType: "number", dbType: "INTEGER" };
@@ -650,25 +672,26 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
650
672
  const foreignKeys = [];
651
673
  const pkCols = [];
652
674
  const uniqueGroups = /* @__PURE__ */ new Map();
653
- for (const [colName, col] of Object.entries(columnsObj)) {
675
+ for (const [colName, outerCol] of Object.entries(columnsObj)) {
676
+ const { element: col, dimensions: arrayDims } = unwrapArrayColumn(outerCol);
654
677
  let { tsType, dbType } = this.mapColumnType(col);
655
678
  if (tsType === "unknown" && /At$/.test(colName)) {
656
679
  tsType = "Date";
657
680
  dbType = "INTEGER";
658
681
  }
659
682
  const ev = col?.enumValues;
660
- const nullable = !col?.notNull && !col?.config?.notNull;
661
- const rawDefault = col?.default;
683
+ const nullable = !outerCol?.notNull && !outerCol?.config?.notNull;
684
+ const rawDefault = outerCol?.default;
662
685
  const defaultValue = rawDefault !== void 0 && !(rawDefault && typeof rawDefault === "object" && "queryChunks" in rawDefault) ? rawDefault : void 0;
663
- const generatedIdentity = col?.generatedIdentity;
664
- const isGenerated = !!(col?.generated || generatedIdentity?.type === "always" || col?.isGenerated);
665
- const hasDefault = col?.hasDefault === true || col?.default !== void 0 || col?.config?.default !== void 0 || col?.defaultFn !== void 0 || isGenerated;
686
+ const generatedIdentity = outerCol?.generatedIdentity;
687
+ const isGenerated = !!(outerCol?.generated || generatedIdentity?.type === "always" || outerCol?.isGenerated);
688
+ const hasDefault = outerCol?.hasDefault === true || outerCol?.default !== void 0 || outerCol?.config?.default !== void 0 || outerCol?.defaultFn !== void 0 || isGenerated;
666
689
  const references = void 0;
667
- const isUnique = !!(col?.isUnique || col?.config?.isUnique);
668
- const isPk = !!(col?.primary || col?.config?.primaryKey);
690
+ const isUnique = !!(outerCol?.isUnique || outerCol?.config?.isUnique);
691
+ const isPk = !!(outerCol?.primary || outerCol?.config?.primaryKey);
669
692
  if (isPk) pkCols.push(colName);
670
693
  if (isUnique) unique.push({ columns: [colName] });
671
- const uName = col?.uniqueName || col?.config?.uniqueName;
694
+ const uName = outerCol?.uniqueName || outerCol?.config?.uniqueName;
672
695
  if (uName) {
673
696
  const arr = uniqueGroups.get(uName) ?? [];
674
697
  arr.push(colName);
@@ -677,6 +700,22 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
677
700
  const v1 = describeV1Column(col);
678
701
  const constraints = this.columnConstraints(col);
679
702
  if (v1?.shape) delete constraints.maxLength;
703
+ const sqlKind = String(outerCol?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
704
+ const sqlType = sqlKind.startsWith("MySql") && typeof col?.getSQLType === "function" ? String(col.getSQLType()).toLowerCase() : void 0;
705
+ 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;
708
+ const finalTs = v1?.tsType ?? tsType;
709
+ const wide = (finalTs === "unknown" || finalTs === "any") && (!shape || shape === "custom");
710
+ if (wide) {
711
+ const sqlType2 = typeof col?.getSQLType === "function" ? col.getSQLType() : void 0;
712
+ issues.push({
713
+ code: "DRZL_ANL_UNKNOWN_COLUMN",
714
+ level: "warn",
715
+ message: `Column "${colName}" on table "${tsName}" has no known type${sqlType2 ? ` (SQL type ${sqlType2})` : ""}, so its validator will accept any value.`,
716
+ hint: shape === "custom" ? "A customType has no runtime shape to read. Declare it with .$type<T>() and turn on typedColumns to give the validator the type." : "Open an issue naming the column type so it can be modelled, or declare it with .$type<T>() and turn on typedColumns."
717
+ });
718
+ }
680
719
  columns.push({
681
720
  name: colName,
682
721
  tsType,
@@ -689,7 +728,13 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
689
728
  enumValues: Array.isArray(ev) ? ev : void 0,
690
729
  ...defaultValue !== void 0 ? { defaultValue } : {},
691
730
  ...constraints,
692
- ...v1 ?? {}
731
+ ...v1 ?? {},
732
+ // After the v1 spread, which sets its own `arrayDimensions` from `dimensions`. On 0.4x
733
+ // that spread has nothing to say and this is the only source.
734
+ ...arrayDims ? { arrayDimensions: arrayDims } : {},
735
+ // Only where v1 did not already describe the value, so a shaped column keeps its shape.
736
+ ...jsonShape && !v1?.shape ? { shape: jsonShape } : {},
737
+ ...byteCap && v1?.maxBytes === void 0 ? { maxBytes: byteCap } : {}
693
738
  });
694
739
  }
695
740
  const name = this.getSymbol(tbl, "drizzle:Name") || tsName;
@@ -729,6 +774,11 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
729
774
  const cfg = entry?.config ?? entry ?? {};
730
775
  const cols = (cfg.columns ?? []).map((c) => toTs(c?.name)).filter(Boolean);
731
776
  if (!cols.length) continue;
777
+ const entityKind = String(entry?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
778
+ if (entityKind.endsWith("UniqueConstraintBuilder")) {
779
+ unique.push({ columns: cols, name: entry?.name });
780
+ continue;
781
+ }
732
782
  if (cfg.unique === void 0) {
733
783
  pkCols.splice(0, pkCols.length, ...cols);
734
784
  continue;
package/dist/index.d.cts CHANGED
@@ -81,6 +81,16 @@ interface Column {
81
81
  * value than the one actually stored, so only the literal case is carried.
82
82
  */
83
83
  defaultValue?: unknown;
84
+ /**
85
+ * A cap measured in bytes rather than characters.
86
+ *
87
+ * MySQL's TEXT and BLOB families carry their limit in the type itself, and that limit is a byte
88
+ * budget: `tinytext` is 255 bytes, which on utf8mb4 is 255 ascii characters or 63 emoji.
89
+ * `maxLength` cannot express that, and using it applied the number as a character count.
90
+ *
91
+ * Only MySQL sets this. Postgres `text` has no limit and its `varchar(n)` counts characters.
92
+ */
93
+ maxBytes?: number;
84
94
  /**
85
95
  * Array depth for a column declared with `.array()`, absent when the column is a scalar.
86
96
  *
package/dist/index.d.ts CHANGED
@@ -81,6 +81,16 @@ interface Column {
81
81
  * value than the one actually stored, so only the literal case is carried.
82
82
  */
83
83
  defaultValue?: unknown;
84
+ /**
85
+ * A cap measured in bytes rather than characters.
86
+ *
87
+ * MySQL's TEXT and BLOB families carry their limit in the type itself, and that limit is a byte
88
+ * budget: `tinytext` is 255 bytes, which on utf8mb4 is 255 ascii characters or 63 emoji.
89
+ * `maxLength` cannot express that, and using it applied the number as a character count.
90
+ *
91
+ * Only MySQL sets this. Postgres `text` has no limit and its `varchar(n)` counts characters.
92
+ */
93
+ maxBytes?: number;
84
94
  /**
85
95
  * Array depth for a column declared with `.array()`, absent when the column is a scalar.
86
96
  *
package/dist/index.js CHANGED
@@ -166,7 +166,7 @@ function describeV1Column(column) {
166
166
  out.dbType = codec === "varchar" ? "VARCHAR" : codec === "char" ? "CHAR" : "TEXT";
167
167
  const kind = String(column?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
168
168
  const cap = codec && kind.startsWith("MySql") ? MYSQL_TEXT_CAPS[codec] : void 0;
169
- if (cap) out.maxLength = cap;
169
+ if (cap) out.maxBytes = cap;
170
170
  } else {
171
171
  return null;
172
172
  }
@@ -175,6 +175,15 @@ function describeV1Column(column) {
175
175
  if (typeof dims === "number" && dims >= 1) out.arrayDimensions = dims;
176
176
  return out;
177
177
  }
178
+ function unwrapArrayColumn(column) {
179
+ let element = column;
180
+ let dimensions = 0;
181
+ while (element?.baseColumn && String(element?.constructor?.name ?? "").endsWith("Array")) {
182
+ element = element.baseColumn;
183
+ dimensions++;
184
+ }
185
+ return { element, dimensions };
186
+ }
178
187
  function declaredLength(column) {
179
188
  const n = column?.length ?? column?.config?.length ?? column?.config?.dimensions;
180
189
  return typeof n === "number" && Number.isFinite(n) && n > 0 ? n : void 0;
@@ -475,10 +484,23 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
475
484
  return { tsType: "number", dbType: "REAL" };
476
485
  case "SQLiteBlob":
477
486
  return { tsType: "Uint8Array", dbType: "BLOB" };
487
+ // SQLite spells a mode as a distinct class rather than as config, so `text({mode:'json'})`
488
+ // is a `SQLiteTextJson` and matched no arm at all: the column came back UNKNOWN, which is
489
+ // wider than the `any` a json column at least used to get.
490
+ case "SQLiteTextJson":
491
+ case "SQLiteBlobJson":
492
+ return { tsType: "any", dbType: "JSON" };
493
+ case "SQLiteBigInt":
494
+ return { tsType: "bigint", dbType: "BIGINT" };
478
495
  case "SQLiteNumeric":
479
496
  return { tsType: "string", dbType: "NUMERIC" };
480
497
  case "SQLiteBoolean":
481
498
  return { tsType: "boolean", dbType: "INTEGER" };
499
+ // 0.4x gives an enum its own class, which had no arm here at all, so an enum column came
500
+ // back `unknown` and every generator emitted a schema that accepted anything. The values
501
+ // were on the column the whole time, in `enumValues`, waiting for a type to attach to.
502
+ case "PgEnumColumn":
503
+ return { tsType: "string", dbType: "TEXT" };
482
504
  case "PgInteger":
483
505
  case "PgSmallInt":
484
506
  return { tsType: "number", dbType: "INTEGER" };
@@ -610,25 +632,26 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
610
632
  const foreignKeys = [];
611
633
  const pkCols = [];
612
634
  const uniqueGroups = /* @__PURE__ */ new Map();
613
- for (const [colName, col] of Object.entries(columnsObj)) {
635
+ for (const [colName, outerCol] of Object.entries(columnsObj)) {
636
+ const { element: col, dimensions: arrayDims } = unwrapArrayColumn(outerCol);
614
637
  let { tsType, dbType } = this.mapColumnType(col);
615
638
  if (tsType === "unknown" && /At$/.test(colName)) {
616
639
  tsType = "Date";
617
640
  dbType = "INTEGER";
618
641
  }
619
642
  const ev = col?.enumValues;
620
- const nullable = !col?.notNull && !col?.config?.notNull;
621
- const rawDefault = col?.default;
643
+ const nullable = !outerCol?.notNull && !outerCol?.config?.notNull;
644
+ const rawDefault = outerCol?.default;
622
645
  const defaultValue = rawDefault !== void 0 && !(rawDefault && typeof rawDefault === "object" && "queryChunks" in rawDefault) ? rawDefault : void 0;
623
- const generatedIdentity = col?.generatedIdentity;
624
- const isGenerated = !!(col?.generated || generatedIdentity?.type === "always" || col?.isGenerated);
625
- const hasDefault = col?.hasDefault === true || col?.default !== void 0 || col?.config?.default !== void 0 || col?.defaultFn !== void 0 || isGenerated;
646
+ const generatedIdentity = outerCol?.generatedIdentity;
647
+ const isGenerated = !!(outerCol?.generated || generatedIdentity?.type === "always" || outerCol?.isGenerated);
648
+ const hasDefault = outerCol?.hasDefault === true || outerCol?.default !== void 0 || outerCol?.config?.default !== void 0 || outerCol?.defaultFn !== void 0 || isGenerated;
626
649
  const references = void 0;
627
- const isUnique = !!(col?.isUnique || col?.config?.isUnique);
628
- const isPk = !!(col?.primary || col?.config?.primaryKey);
650
+ const isUnique = !!(outerCol?.isUnique || outerCol?.config?.isUnique);
651
+ const isPk = !!(outerCol?.primary || outerCol?.config?.primaryKey);
629
652
  if (isPk) pkCols.push(colName);
630
653
  if (isUnique) unique.push({ columns: [colName] });
631
- const uName = col?.uniqueName || col?.config?.uniqueName;
654
+ const uName = outerCol?.uniqueName || outerCol?.config?.uniqueName;
632
655
  if (uName) {
633
656
  const arr = uniqueGroups.get(uName) ?? [];
634
657
  arr.push(colName);
@@ -637,6 +660,22 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
637
660
  const v1 = describeV1Column(col);
638
661
  const constraints = this.columnConstraints(col);
639
662
  if (v1?.shape) delete constraints.maxLength;
663
+ const sqlKind = String(outerCol?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
664
+ const sqlType = sqlKind.startsWith("MySql") && typeof col?.getSQLType === "function" ? String(col.getSQLType()).toLowerCase() : void 0;
665
+ 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;
668
+ const finalTs = v1?.tsType ?? tsType;
669
+ const wide = (finalTs === "unknown" || finalTs === "any") && (!shape || shape === "custom");
670
+ if (wide) {
671
+ const sqlType2 = typeof col?.getSQLType === "function" ? col.getSQLType() : void 0;
672
+ issues.push({
673
+ code: "DRZL_ANL_UNKNOWN_COLUMN",
674
+ level: "warn",
675
+ message: `Column "${colName}" on table "${tsName}" has no known type${sqlType2 ? ` (SQL type ${sqlType2})` : ""}, so its validator will accept any value.`,
676
+ hint: shape === "custom" ? "A customType has no runtime shape to read. Declare it with .$type<T>() and turn on typedColumns to give the validator the type." : "Open an issue naming the column type so it can be modelled, or declare it with .$type<T>() and turn on typedColumns."
677
+ });
678
+ }
640
679
  columns.push({
641
680
  name: colName,
642
681
  tsType,
@@ -649,7 +688,13 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
649
688
  enumValues: Array.isArray(ev) ? ev : void 0,
650
689
  ...defaultValue !== void 0 ? { defaultValue } : {},
651
690
  ...constraints,
652
- ...v1 ?? {}
691
+ ...v1 ?? {},
692
+ // After the v1 spread, which sets its own `arrayDimensions` from `dimensions`. On 0.4x
693
+ // that spread has nothing to say and this is the only source.
694
+ ...arrayDims ? { arrayDimensions: arrayDims } : {},
695
+ // Only where v1 did not already describe the value, so a shaped column keeps its shape.
696
+ ...jsonShape && !v1?.shape ? { shape: jsonShape } : {},
697
+ ...byteCap && v1?.maxBytes === void 0 ? { maxBytes: byteCap } : {}
653
698
  });
654
699
  }
655
700
  const name = this.getSymbol(tbl, "drizzle:Name") || tsName;
@@ -689,6 +734,11 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
689
734
  const cfg = entry?.config ?? entry ?? {};
690
735
  const cols = (cfg.columns ?? []).map((c) => toTs(c?.name)).filter(Boolean);
691
736
  if (!cols.length) continue;
737
+ const entityKind = String(entry?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
738
+ if (entityKind.endsWith("UniqueConstraintBuilder")) {
739
+ unique.push({ columns: cols, name: entry?.name });
740
+ continue;
741
+ }
692
742
  if (cfg.unique === void 0) {
693
743
  pkCols.splice(0, pkCols.length, ...cols);
694
744
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drzl/analyzer",
3
- "version": "1.12.0",
3
+ "version": "1.14.0",
4
4
  "private": false,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -45,6 +45,6 @@
45
45
  "scripts": {
46
46
  "build": "tsup src/index.ts --dts --format esm,cjs",
47
47
  "lint": "eslint . --ext .ts",
48
- "test": "vitest run"
48
+ "test": "vitest run --testTimeout=20000"
49
49
  }
50
50
  }