@drzl/analyzer 1.12.0 → 1.13.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
@@ -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,19 @@ 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 jsonShape = dbType === "JSON" || dbType === "JSONB" || col?.config?.mode === "json" ? { kind: "json" } : void 0;
704
+ const shape = (v1?.shape ?? jsonShape)?.kind;
705
+ const finalTs = v1?.tsType ?? tsType;
706
+ const wide = (finalTs === "unknown" || finalTs === "any") && (!shape || shape === "custom");
707
+ if (wide) {
708
+ const sqlType = typeof col?.getSQLType === "function" ? col.getSQLType() : void 0;
709
+ issues.push({
710
+ code: "DRZL_ANL_UNKNOWN_COLUMN",
711
+ level: "warn",
712
+ message: `Column "${colName}" on table "${tsName}" has no known type${sqlType ? ` (SQL type ${sqlType})` : ""}, so its validator will accept any value.`,
713
+ 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."
714
+ });
715
+ }
680
716
  columns.push({
681
717
  name: colName,
682
718
  tsType,
@@ -689,7 +725,12 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
689
725
  enumValues: Array.isArray(ev) ? ev : void 0,
690
726
  ...defaultValue !== void 0 ? { defaultValue } : {},
691
727
  ...constraints,
692
- ...v1 ?? {}
728
+ ...v1 ?? {},
729
+ // After the v1 spread, which sets its own `arrayDimensions` from `dimensions`. On 0.4x
730
+ // that spread has nothing to say and this is the only source.
731
+ ...arrayDims ? { arrayDimensions: arrayDims } : {},
732
+ // Only where v1 did not already describe the value, so a shaped column keeps its shape.
733
+ ...jsonShape && !v1?.shape ? { shape: jsonShape } : {}
693
734
  });
694
735
  }
695
736
  const name = this.getSymbol(tbl, "drizzle:Name") || tsName;
package/dist/index.js CHANGED
@@ -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,19 @@ 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 jsonShape = dbType === "JSON" || dbType === "JSONB" || col?.config?.mode === "json" ? { kind: "json" } : void 0;
664
+ const shape = (v1?.shape ?? jsonShape)?.kind;
665
+ const finalTs = v1?.tsType ?? tsType;
666
+ const wide = (finalTs === "unknown" || finalTs === "any") && (!shape || shape === "custom");
667
+ if (wide) {
668
+ const sqlType = typeof col?.getSQLType === "function" ? col.getSQLType() : void 0;
669
+ issues.push({
670
+ code: "DRZL_ANL_UNKNOWN_COLUMN",
671
+ level: "warn",
672
+ message: `Column "${colName}" on table "${tsName}" has no known type${sqlType ? ` (SQL type ${sqlType})` : ""}, so its validator will accept any value.`,
673
+ 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."
674
+ });
675
+ }
640
676
  columns.push({
641
677
  name: colName,
642
678
  tsType,
@@ -649,7 +685,12 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
649
685
  enumValues: Array.isArray(ev) ? ev : void 0,
650
686
  ...defaultValue !== void 0 ? { defaultValue } : {},
651
687
  ...constraints,
652
- ...v1 ?? {}
688
+ ...v1 ?? {},
689
+ // After the v1 spread, which sets its own `arrayDimensions` from `dimensions`. On 0.4x
690
+ // that spread has nothing to say and this is the only source.
691
+ ...arrayDims ? { arrayDimensions: arrayDims } : {},
692
+ // Only where v1 did not already describe the value, so a shaped column keeps its shape.
693
+ ...jsonShape && !v1?.shape ? { shape: jsonShape } : {}
653
694
  });
654
695
  }
655
696
  const name = this.getSymbol(tbl, "drizzle:Name") || tsName;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drzl/analyzer",
3
- "version": "1.12.0",
3
+ "version": "1.13.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
  }