@drzl/analyzer 1.15.0 → 1.17.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
@@ -33,6 +33,7 @@ __export(index_exports, {
33
33
  SchemaAnalyzer: () => SchemaAnalyzer,
34
34
  default: () => index_default,
35
35
  describeV1Column: () => describeV1Column,
36
+ isDrizzleView: () => isDrizzleView,
36
37
  isReadOnlyRelation: () => isReadOnlyRelation,
37
38
  isRelationsV2: () => isRelationsV2,
38
39
  readRelationsV2: () => readRelationsV2
@@ -68,6 +69,13 @@ var TUPLE_CLASS_SHAPES = {
68
69
  PgPointTuple: { kind: "tuple", length: 2 },
69
70
  PgLineTuple: { kind: "tuple", length: 3 }
70
71
  };
72
+ var V1_ONLY_ENTITY_KINDS = /^(?:MsSql|Cockroach)/;
73
+ var BYTE_STRING_CLASSES = /* @__PURE__ */ new Set([
74
+ "MySqlBinary",
75
+ "MySqlVarBinary",
76
+ "SingleStoreBinary",
77
+ "SingleStoreVarBinary"
78
+ ]);
71
79
  function describeV1Column(column) {
72
80
  const codec = column?.codec;
73
81
  const dataType = column?.dataType;
@@ -80,8 +88,9 @@ function describeV1Column(column) {
80
88
  shape: { kind: "custom", sqlType: typeof sqlType === "string" ? sqlType : void 0 }
81
89
  };
82
90
  }
91
+ const entityKind = String(column?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
83
92
  const [js, semantic = ""] = dataType.split(" ");
84
- if (typeof codec !== "string" && !semantic) return null;
93
+ if (typeof codec !== "string" && !semantic && !V1_ONLY_ENTITY_KINDS.test(entityKind)) return null;
85
94
  const out = {};
86
95
  switch (semantic) {
87
96
  case "int8":
@@ -117,7 +126,7 @@ function describeV1Column(column) {
117
126
  case "float":
118
127
  case "double": {
119
128
  if (semantic === "float")
120
- [out.min, out.max] = codec === "float4" ? PG_FLOAT4_RANGE : MYSQL_FLOAT_RANGE;
129
+ [out.min, out.max] = codec === "float4" || entityKind.startsWith("Cockroach") ? PG_FLOAT4_RANGE : MYSQL_FLOAT_RANGE;
121
130
  out.integer = false;
122
131
  out.tsType = "number";
123
132
  out.dbType = semantic === "float" ? "REAL" : "DOUBLE";
@@ -167,17 +176,20 @@ function describeV1Column(column) {
167
176
  out.tsType = "string";
168
177
  out.dbType = semantic.toUpperCase();
169
178
  break;
170
- case "binary":
179
+ case "binary": {
180
+ const entity = String(column?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
181
+ const bytes = entity.startsWith("MySql") || entity.startsWith("SingleStore");
171
182
  out.tsType = "string";
172
183
  out.dbType = codec === "bit" ? "BIT" : "BINARY";
173
- out.shape = {
184
+ out.shape = bytes ? { kind: "byteString", length: declaredLength(column) } : {
174
185
  kind: "bitstring",
175
186
  length: declaredLength(column),
176
- // A Postgres `bit(3)` holds exactly three digits; a MySQL `binary(4)`/`varbinary(16)`
177
- // holds at most that many, which is why `''` is valid there and not here.
187
+ // A Postgres `bit(3)` holds exactly three digits; a Cockroach `varbit(16)` holds at
188
+ // most that many, which is why `''` is valid there and not here.
178
189
  exact: codec === "bit"
179
190
  };
180
191
  break;
192
+ }
181
193
  case "point":
182
194
  case "geometry":
183
195
  out.tsType = "[number, number]";
@@ -234,15 +246,33 @@ function declaredLength(column) {
234
246
  const n = column?.length ?? column?.config?.length ?? column?.config?.dimensions;
235
247
  return typeof n === "number" && Number.isFinite(n) && n > 0 ? n : void 0;
236
248
  }
237
- function getSymbolOf(target, key) {
238
- if (!target) return void 0;
249
+ var VIEW_CONFIG_FIELDS = {
250
+ "drizzle:Columns": "selectedFields",
251
+ "drizzle:Name": "name",
252
+ "drizzle:Schema": "schema"
253
+ };
254
+ function ownSymbolOf(target, key) {
239
255
  try {
240
256
  for (const sym of Object.getOwnPropertySymbols(target)) {
241
257
  if (sym.description === key) return target[sym];
242
258
  }
243
259
  } catch {
244
260
  }
245
- return target[Symbol.for(key)];
261
+ return void 0;
262
+ }
263
+ function getSymbolOf(target, key) {
264
+ if (!target) return void 0;
265
+ const own = ownSymbolOf(target, key);
266
+ if (own !== void 0) return own;
267
+ const direct = target[Symbol.for(key)];
268
+ if (direct !== void 0) return direct;
269
+ const field = VIEW_CONFIG_FIELDS[key];
270
+ if (!field) return void 0;
271
+ const cfg = ownSymbolOf(target, "drizzle:ViewBaseConfig");
272
+ return cfg ? cfg[field] : void 0;
273
+ }
274
+ function isDrizzleView(val) {
275
+ return !!val && typeof val === "object" && !!getSymbolOf(val, "drizzle:ViewBaseConfig");
246
276
  }
247
277
  function isReadOnlyRelation(val) {
248
278
  if (!val || typeof val !== "object") return false;
@@ -286,17 +316,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
286
316
  this.schemaPath = schemaPath;
287
317
  }
288
318
  getSymbol(table, key) {
289
- if (!table) return void 0;
290
- try {
291
- const syms = Object.getOwnPropertySymbols(table);
292
- for (const s of syms) {
293
- if (s.description === key) {
294
- return table[s];
295
- }
296
- }
297
- } catch {
298
- }
299
- return table[Symbol.for(key)];
319
+ return getSymbolOf(table, key);
300
320
  }
301
321
  /**
302
322
  * Drizzle keys the Columns object by TypeScript property name, but every other piece of
@@ -545,6 +565,14 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
545
565
  return { tsType: "bigint", dbType: "BIGINT" };
546
566
  case "SQLiteNumeric":
547
567
  return { tsType: "string", dbType: "NUMERIC" };
568
+ // The other two modes of the same column, which matched no arm here and no dialect regex
569
+ // below either, so both came back UNKNOWN and every generator emitted a schema that
570
+ // accepted anything at all. Read back through better-sqlite3 3.53.4 on a `numeric` column,
571
+ // `db.select()` hands back a number in number mode and a bigint in bigint mode.
572
+ case "SQLiteNumericNumber":
573
+ return { tsType: "number", dbType: "NUMERIC" };
574
+ case "SQLiteNumericBigInt":
575
+ return { tsType: "bigint", dbType: "NUMERIC" };
548
576
  case "SQLiteBoolean":
549
577
  return { tsType: "boolean", dbType: "INTEGER" };
550
578
  // 0.4x gives an enum its own class, which had no arm here at all, so an enum column came
@@ -589,6 +617,15 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
589
617
  return { tsType: "Date", dbType: "TIMESTAMP" };
590
618
  case "PgNumeric":
591
619
  return { tsType: "string", dbType: "NUMERIC" };
620
+ // The other two modes, each its own class. `PgNumericNumber` reached the coarse
621
+ // `/Numeric|Float|Double|Real/i` arm and got the right answer there; `PgNumericBigInt` got
622
+ // its `bigint` from the `/BigInt/i` arm meant for `bigint` columns, and the SQL label that
623
+ // comes with it, so a `numeric(20,0)` was reported as a BIGINT column. Read back through
624
+ // PGlite, `db.select()` hands back a number in number mode and a bigint in bigint mode.
625
+ case "PgNumericNumber":
626
+ return { tsType: "number", dbType: "NUMERIC" };
627
+ case "PgNumericBigInt":
628
+ return { tsType: "bigint", dbType: "NUMERIC" };
592
629
  case "PgDoublePrecision":
593
630
  return { tsType: "number", dbType: "DOUBLE" };
594
631
  // `real()` builds a `PgReal`, which matched no arm and fell through to the coarse
@@ -611,6 +648,16 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
611
648
  case "PgJson":
612
649
  case "PgJsonb":
613
650
  return { tsType: "any", dbType: ctor === "PgJsonb" ? "JSONB" : "JSON" };
651
+ // The four builders in `BYTE_STRING_CLASSES`, which the coarse `/Blob|Binary|VarBinary/i`
652
+ // arms below would otherwise call a `Uint8Array` on the strength of the word "Binary" in
653
+ // the class name. Asked of MySQL 8.4 through drizzle 0.45.2 instead: the driver hands up a
654
+ // Buffer, `mapFromDriverValue` decodes it, and the caller receives a string. Every one of
655
+ // the four declares `dataType: 'string'` and every one of the four defines that method.
656
+ case "MySqlBinary":
657
+ case "MySqlVarBinary":
658
+ case "SingleStoreBinary":
659
+ case "SingleStoreVarBinary":
660
+ return { tsType: "string", dbType: "BINARY" };
614
661
  default:
615
662
  if (column?.config?.mode === "timestamp" || column?.mode === "timestamp") {
616
663
  return { tsType: "Date", dbType: "INTEGER" };
@@ -638,7 +685,12 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
638
685
  if (/BigInt64/i.test(ctor)) return { tsType: "bigint", dbType: "BIGINT" };
639
686
  if (/BigInt53/i.test(ctor)) return { tsType: "number", dbType: "BIGINT" };
640
687
  if (/\bBigInt\b/i.test(ctor)) return { tsType: "bigint", dbType: "BIGINT" };
641
- if (/Decimal|Numeric|Float|Double|Real/i.test(ctor))
688
+ if (/Decimal/i.test(ctor)) {
689
+ if (/DecimalNumber$/.test(ctor)) return { tsType: "number", dbType: "NUMERIC" };
690
+ if (/DecimalBigInt$/.test(ctor)) return { tsType: "bigint", dbType: "NUMERIC" };
691
+ return { tsType: "string", dbType: "NUMERIC" };
692
+ }
693
+ if (/Numeric|Float|Double|Real/i.test(ctor))
642
694
  return { tsType: "number", dbType: "NUMERIC" };
643
695
  if (/Int|Serial|TinyInt|SmallInt|MediumInt/i.test(ctor))
644
696
  return { tsType: "number", dbType: "INTEGER" };
@@ -657,7 +709,12 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
657
709
  if (/Vector/i.test(ctor)) return { tsType: "any", dbType: "VECTOR" };
658
710
  if (/BigInt64/i.test(ctor)) return { tsType: "bigint", dbType: "BIGINT" };
659
711
  if (/BigInt53/i.test(ctor)) return { tsType: "number", dbType: "BIGINT" };
660
- if (/Decimal|Numeric|Float|Double|Real/i.test(ctor))
712
+ if (/Decimal/i.test(ctor)) {
713
+ if (/DecimalNumber$/.test(ctor)) return { tsType: "number", dbType: "NUMERIC" };
714
+ if (/DecimalBigInt$/.test(ctor)) return { tsType: "bigint", dbType: "NUMERIC" };
715
+ return { tsType: "string", dbType: "NUMERIC" };
716
+ }
717
+ if (/Numeric|Float|Double|Real/i.test(ctor))
661
718
  return { tsType: "number", dbType: "NUMERIC" };
662
719
  if (/Int|Serial|TinyInt|SmallInt|MediumInt/i.test(ctor))
663
720
  return { tsType: "number", dbType: "INTEGER" };
@@ -681,11 +738,10 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
681
738
  if (/Json/i.test(ctor)) return { tsType: "any", dbType: "JSON" };
682
739
  if (/Text/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
683
740
  if (/Bytes/i.test(ctor)) return { tsType: "Uint8Array", dbType: "BLOB" };
741
+ if (/Bool/i.test(ctor)) return { tsType: "boolean", dbType: "BOOLEAN" };
684
742
  if (/TimestampTz/i.test(ctor)) return { tsType: "Date", dbType: "TIMESTAMPTZ" };
685
- if (/Timestamp/i.test(ctor)) return { tsType: "string", dbType: "TIMESTAMP" };
686
- if (/LocalDateString|LocalTime/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
687
- if (/DateDuration|RelDuration|Duration/i.test(ctor))
688
- return { tsType: "string", dbType: "TEXT" };
743
+ if (/Timestamp|LocalDateString|LocalTime|DateDuration|RelDuration|Duration/i.test(ctor))
744
+ return { tsType: "unknown", dbType: "UNKNOWN" };
689
745
  }
690
746
  return { tsType: "unknown", dbType: "UNKNOWN" };
691
747
  }
@@ -730,7 +786,9 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
730
786
  const sqlKind = String(outerCol?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
731
787
  const sqlType = sqlKind.startsWith("MySql") && typeof col?.getSQLType === "function" ? String(col.getSQLType()).toLowerCase() : void 0;
732
788
  const byteCap = sqlType ? MYSQL_TEXT_CAPS[sqlType] : void 0;
733
- const fallbackShape = dbType === "JSON" || dbType === "JSONB" || col?.config?.mode === "json" ? { kind: "json" } : TUPLE_CLASS_SHAPES[String(col?.constructor?.name ?? "")];
789
+ 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];
791
+ if (fallbackShape?.kind === "byteString") delete constraints.maxLength;
734
792
  const shape = (v1?.shape ?? fallbackShape)?.kind;
735
793
  const finalTs = v1?.tsType ?? tsType;
736
794
  const wide = (finalTs === "unknown" || finalTs === "any") && (!shape || shape === "custom");
@@ -880,12 +938,14 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
880
938
  const relations = [];
881
939
  const enums = [];
882
940
  const columnEnums = [];
941
+ const viewTables = [];
883
942
  for (const [name, val] of Object.entries(exportsObj)) {
884
943
  try {
885
944
  const cols = this.getSymbol(val, "drizzle:Columns");
886
945
  if (cols && typeof cols === "object") {
887
946
  const table = this.analyzeTable(name, val, issues);
888
947
  tables.push(table);
948
+ if (isDrizzleView(val)) viewTables.push(table);
889
949
  for (const col of table.columns) {
890
950
  const enumVals = cols[col.name]?.enumValues;
891
951
  if (enumVals && enumVals.length) {
@@ -936,7 +996,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
936
996
  let dialect = "unknown";
937
997
  const marks = /* @__PURE__ */ new Set();
938
998
  for (const [_, val] of Object.entries(exportsObj)) {
939
- const cols = val?.[/* @__PURE__ */ Symbol.for("drizzle:Columns")];
999
+ const cols = this.getSymbol(val, "drizzle:Columns");
940
1000
  if (!cols) continue;
941
1001
  for (const c of Object.values(cols)) {
942
1002
  const kind = c?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")];
@@ -953,6 +1013,9 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
953
1013
  else if (/MySql|Mysql/i.test(names)) dialect = "mysql";
954
1014
  else if (/Pg|Postgres/i.test(names)) dialect = "postgres";
955
1015
  else if (/Gel/i.test(names)) dialect = "gel";
1016
+ if (dialect === "sqlite") {
1017
+ for (const view of viewTables) view.readOnly = true;
1018
+ }
956
1019
  if (dialect === "unknown" && tables.length) {
957
1020
  issues.push({
958
1021
  code: "DRZL_ANL_DIALECT",
@@ -1098,6 +1161,7 @@ var index_default = SchemaAnalyzer;
1098
1161
  0 && (module.exports = {
1099
1162
  SchemaAnalyzer,
1100
1163
  describeV1Column,
1164
+ isDrizzleView,
1101
1165
  isReadOnlyRelation,
1102
1166
  isRelationsV2,
1103
1167
  readRelationsV2
package/dist/index.d.cts CHANGED
@@ -144,16 +144,44 @@ type ColumnShape =
144
144
  sqlType?: string;
145
145
  }
146
146
  /**
147
- * A string of `0`/`1`: Postgres `bit(n)`, MySQL `binary(n)`/`varbinary(n)`.
147
+ * A string of `0`/`1`: Postgres `bit(n)`, Cockroach `bit(n)`/`varbit(n)`.
148
148
  *
149
- * `exact` separates the two. A Postgres `bit(3)` is always three digits, while a MySQL
150
- * `varbinary(16)` is at most sixteen, so treating both as exact rejected the empty string on
151
- * every MySQL binary column.
149
+ * `exact` separates the two. A Postgres `bit(3)` is always three digits, while a Cockroach
150
+ * `varbit(16)` is at most sixteen, so treating both as exact rejects the empty string on every
151
+ * varying column.
152
+ *
153
+ * MySQL `binary(n)`/`varbinary(n)` used to be listed here and is not a bit string at all; see
154
+ * `byteString`.
152
155
  */
153
156
  | {
154
157
  kind: 'bitstring';
155
158
  length?: number;
156
159
  exact?: boolean;
160
+ }
161
+ /**
162
+ * MySQL/SingleStore `binary(n)`/`varbinary(n)`: arbitrary bytes, handed to the caller as a
163
+ * string.
164
+ *
165
+ * Asked of MySQL 8.4 through drizzle on both majors, on the same row of the same table: the
166
+ * driver hands up a Buffer, drizzle decodes it, and what the caller receives is a string on
167
+ * 0.45.2 and on 1.0.0-rc.4 alike. `instanceof Uint8Array` is false for all four column builders
168
+ * on both majors.
169
+ *
170
+ * `length` is the declared width and means two different things depending on direction, which
171
+ * is why it is carried raw here rather than as a `maxLength` or a `maxBytes`:
172
+ *
173
+ * out the decode is lossy, so n bytes become at most n code points. Measured: `<ff ff ff>`
174
+ * in a varbinary(3) comes back as 3 code points that re-encode to 9 UTF-8 bytes, so a
175
+ * byte cap applied to a select schema rejects a row the column itself returned.
176
+ * in the server counts the encoded bytes. Measured on varbinary(8): 8 ascii accepted, 9
177
+ * refused, 2 emoji (8 bytes) accepted, 3 emoji (12 bytes) refused, so a code-point cap
178
+ * applied to an insert schema accepts a write the server refuses.
179
+ *
180
+ * The generators pick which by mode.
181
+ */
182
+ | {
183
+ kind: 'byteString';
184
+ length?: number;
157
185
  };
158
186
  interface Key {
159
187
  name?: string;
@@ -231,6 +259,17 @@ interface AnalyzeOptions {
231
259
  * path below untouched.
232
260
  */
233
261
  declare function describeV1Column(column: any): Partial<Column> | null;
262
+ /**
263
+ * Whether an export is a Drizzle view, of any dialect and on either major.
264
+ *
265
+ * Asked of `drizzle:ViewBaseConfig` rather than of `drizzle:IsDrizzleView`, which reads like the
266
+ * obvious question and is not there to be asked: the marker was introduced in 0.39.0, and a view
267
+ * built on 0.29.5, 0.33.0 or 0.36.4 answers undefined to it. The config is an own symbol on all
268
+ * eleven releases probed and on every view form measured, on both majors: the query-builder and
269
+ * explicit-column-list forms of pg view, pg materialized view, mysql view and sqlite view,
270
+ * `.existing()`, and the schema-qualified pg view and materialized view.
271
+ */
272
+ declare function isDrizzleView(val: any): boolean;
234
273
  /**
235
274
  * Whether an export is a Relations v2 definition, from `defineRelations(schema, (r) => ...)`.
236
275
  *
@@ -402,4 +441,4 @@ declare class SchemaAnalyzer {
402
441
  analyze(opts?: AnalyzeOptions): Promise<Analysis>;
403
442
  }
404
443
 
405
- export { type Analysis, type AnalyzeOptions, type Check, type Column, type ColumnRef, type ColumnShape, type Dialect, type Enum, type ForeignKey, type Index, type Issue, type Key, type Relation, SchemaAnalyzer, type Table, SchemaAnalyzer as default, describeV1Column, isReadOnlyRelation, isRelationsV2, readRelationsV2 };
444
+ export { type Analysis, type AnalyzeOptions, type Check, type Column, type ColumnRef, type ColumnShape, type Dialect, type Enum, type ForeignKey, type Index, type Issue, type Key, type Relation, SchemaAnalyzer, type Table, SchemaAnalyzer as default, describeV1Column, isDrizzleView, isReadOnlyRelation, isRelationsV2, readRelationsV2 };
package/dist/index.d.ts CHANGED
@@ -144,16 +144,44 @@ type ColumnShape =
144
144
  sqlType?: string;
145
145
  }
146
146
  /**
147
- * A string of `0`/`1`: Postgres `bit(n)`, MySQL `binary(n)`/`varbinary(n)`.
147
+ * A string of `0`/`1`: Postgres `bit(n)`, Cockroach `bit(n)`/`varbit(n)`.
148
148
  *
149
- * `exact` separates the two. A Postgres `bit(3)` is always three digits, while a MySQL
150
- * `varbinary(16)` is at most sixteen, so treating both as exact rejected the empty string on
151
- * every MySQL binary column.
149
+ * `exact` separates the two. A Postgres `bit(3)` is always three digits, while a Cockroach
150
+ * `varbit(16)` is at most sixteen, so treating both as exact rejects the empty string on every
151
+ * varying column.
152
+ *
153
+ * MySQL `binary(n)`/`varbinary(n)` used to be listed here and is not a bit string at all; see
154
+ * `byteString`.
152
155
  */
153
156
  | {
154
157
  kind: 'bitstring';
155
158
  length?: number;
156
159
  exact?: boolean;
160
+ }
161
+ /**
162
+ * MySQL/SingleStore `binary(n)`/`varbinary(n)`: arbitrary bytes, handed to the caller as a
163
+ * string.
164
+ *
165
+ * Asked of MySQL 8.4 through drizzle on both majors, on the same row of the same table: the
166
+ * driver hands up a Buffer, drizzle decodes it, and what the caller receives is a string on
167
+ * 0.45.2 and on 1.0.0-rc.4 alike. `instanceof Uint8Array` is false for all four column builders
168
+ * on both majors.
169
+ *
170
+ * `length` is the declared width and means two different things depending on direction, which
171
+ * is why it is carried raw here rather than as a `maxLength` or a `maxBytes`:
172
+ *
173
+ * out the decode is lossy, so n bytes become at most n code points. Measured: `<ff ff ff>`
174
+ * in a varbinary(3) comes back as 3 code points that re-encode to 9 UTF-8 bytes, so a
175
+ * byte cap applied to a select schema rejects a row the column itself returned.
176
+ * in the server counts the encoded bytes. Measured on varbinary(8): 8 ascii accepted, 9
177
+ * refused, 2 emoji (8 bytes) accepted, 3 emoji (12 bytes) refused, so a code-point cap
178
+ * applied to an insert schema accepts a write the server refuses.
179
+ *
180
+ * The generators pick which by mode.
181
+ */
182
+ | {
183
+ kind: 'byteString';
184
+ length?: number;
157
185
  };
158
186
  interface Key {
159
187
  name?: string;
@@ -231,6 +259,17 @@ interface AnalyzeOptions {
231
259
  * path below untouched.
232
260
  */
233
261
  declare function describeV1Column(column: any): Partial<Column> | null;
262
+ /**
263
+ * Whether an export is a Drizzle view, of any dialect and on either major.
264
+ *
265
+ * Asked of `drizzle:ViewBaseConfig` rather than of `drizzle:IsDrizzleView`, which reads like the
266
+ * obvious question and is not there to be asked: the marker was introduced in 0.39.0, and a view
267
+ * built on 0.29.5, 0.33.0 or 0.36.4 answers undefined to it. The config is an own symbol on all
268
+ * eleven releases probed and on every view form measured, on both majors: the query-builder and
269
+ * explicit-column-list forms of pg view, pg materialized view, mysql view and sqlite view,
270
+ * `.existing()`, and the schema-qualified pg view and materialized view.
271
+ */
272
+ declare function isDrizzleView(val: any): boolean;
234
273
  /**
235
274
  * Whether an export is a Relations v2 definition, from `defineRelations(schema, (r) => ...)`.
236
275
  *
@@ -402,4 +441,4 @@ declare class SchemaAnalyzer {
402
441
  analyze(opts?: AnalyzeOptions): Promise<Analysis>;
403
442
  }
404
443
 
405
- export { type Analysis, type AnalyzeOptions, type Check, type Column, type ColumnRef, type ColumnShape, type Dialect, type Enum, type ForeignKey, type Index, type Issue, type Key, type Relation, SchemaAnalyzer, type Table, SchemaAnalyzer as default, describeV1Column, isReadOnlyRelation, isRelationsV2, readRelationsV2 };
444
+ export { type Analysis, type AnalyzeOptions, type Check, type Column, type ColumnRef, type ColumnShape, type Dialect, type Enum, type ForeignKey, type Index, type Issue, type Key, type Relation, SchemaAnalyzer, type Table, SchemaAnalyzer as default, describeV1Column, isDrizzleView, isReadOnlyRelation, isRelationsV2, readRelationsV2 };
package/dist/index.js CHANGED
@@ -28,6 +28,13 @@ var TUPLE_CLASS_SHAPES = {
28
28
  PgPointTuple: { kind: "tuple", length: 2 },
29
29
  PgLineTuple: { kind: "tuple", length: 3 }
30
30
  };
31
+ var V1_ONLY_ENTITY_KINDS = /^(?:MsSql|Cockroach)/;
32
+ var BYTE_STRING_CLASSES = /* @__PURE__ */ new Set([
33
+ "MySqlBinary",
34
+ "MySqlVarBinary",
35
+ "SingleStoreBinary",
36
+ "SingleStoreVarBinary"
37
+ ]);
31
38
  function describeV1Column(column) {
32
39
  const codec = column?.codec;
33
40
  const dataType = column?.dataType;
@@ -40,8 +47,9 @@ function describeV1Column(column) {
40
47
  shape: { kind: "custom", sqlType: typeof sqlType === "string" ? sqlType : void 0 }
41
48
  };
42
49
  }
50
+ const entityKind = String(column?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
43
51
  const [js, semantic = ""] = dataType.split(" ");
44
- if (typeof codec !== "string" && !semantic) return null;
52
+ if (typeof codec !== "string" && !semantic && !V1_ONLY_ENTITY_KINDS.test(entityKind)) return null;
45
53
  const out = {};
46
54
  switch (semantic) {
47
55
  case "int8":
@@ -77,7 +85,7 @@ function describeV1Column(column) {
77
85
  case "float":
78
86
  case "double": {
79
87
  if (semantic === "float")
80
- [out.min, out.max] = codec === "float4" ? PG_FLOAT4_RANGE : MYSQL_FLOAT_RANGE;
88
+ [out.min, out.max] = codec === "float4" || entityKind.startsWith("Cockroach") ? PG_FLOAT4_RANGE : MYSQL_FLOAT_RANGE;
81
89
  out.integer = false;
82
90
  out.tsType = "number";
83
91
  out.dbType = semantic === "float" ? "REAL" : "DOUBLE";
@@ -127,17 +135,20 @@ function describeV1Column(column) {
127
135
  out.tsType = "string";
128
136
  out.dbType = semantic.toUpperCase();
129
137
  break;
130
- case "binary":
138
+ case "binary": {
139
+ const entity = String(column?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
140
+ const bytes = entity.startsWith("MySql") || entity.startsWith("SingleStore");
131
141
  out.tsType = "string";
132
142
  out.dbType = codec === "bit" ? "BIT" : "BINARY";
133
- out.shape = {
143
+ out.shape = bytes ? { kind: "byteString", length: declaredLength(column) } : {
134
144
  kind: "bitstring",
135
145
  length: declaredLength(column),
136
- // A Postgres `bit(3)` holds exactly three digits; a MySQL `binary(4)`/`varbinary(16)`
137
- // holds at most that many, which is why `''` is valid there and not here.
146
+ // A Postgres `bit(3)` holds exactly three digits; a Cockroach `varbit(16)` holds at
147
+ // most that many, which is why `''` is valid there and not here.
138
148
  exact: codec === "bit"
139
149
  };
140
150
  break;
151
+ }
141
152
  case "point":
142
153
  case "geometry":
143
154
  out.tsType = "[number, number]";
@@ -194,15 +205,33 @@ function declaredLength(column) {
194
205
  const n = column?.length ?? column?.config?.length ?? column?.config?.dimensions;
195
206
  return typeof n === "number" && Number.isFinite(n) && n > 0 ? n : void 0;
196
207
  }
197
- function getSymbolOf(target, key) {
198
- if (!target) return void 0;
208
+ var VIEW_CONFIG_FIELDS = {
209
+ "drizzle:Columns": "selectedFields",
210
+ "drizzle:Name": "name",
211
+ "drizzle:Schema": "schema"
212
+ };
213
+ function ownSymbolOf(target, key) {
199
214
  try {
200
215
  for (const sym of Object.getOwnPropertySymbols(target)) {
201
216
  if (sym.description === key) return target[sym];
202
217
  }
203
218
  } catch {
204
219
  }
205
- return target[Symbol.for(key)];
220
+ return void 0;
221
+ }
222
+ function getSymbolOf(target, key) {
223
+ if (!target) return void 0;
224
+ const own = ownSymbolOf(target, key);
225
+ if (own !== void 0) return own;
226
+ const direct = target[Symbol.for(key)];
227
+ if (direct !== void 0) return direct;
228
+ const field = VIEW_CONFIG_FIELDS[key];
229
+ if (!field) return void 0;
230
+ const cfg = ownSymbolOf(target, "drizzle:ViewBaseConfig");
231
+ return cfg ? cfg[field] : void 0;
232
+ }
233
+ function isDrizzleView(val) {
234
+ return !!val && typeof val === "object" && !!getSymbolOf(val, "drizzle:ViewBaseConfig");
206
235
  }
207
236
  function isReadOnlyRelation(val) {
208
237
  if (!val || typeof val !== "object") return false;
@@ -246,17 +275,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
246
275
  this.schemaPath = schemaPath;
247
276
  }
248
277
  getSymbol(table, key) {
249
- if (!table) return void 0;
250
- try {
251
- const syms = Object.getOwnPropertySymbols(table);
252
- for (const s of syms) {
253
- if (s.description === key) {
254
- return table[s];
255
- }
256
- }
257
- } catch {
258
- }
259
- return table[Symbol.for(key)];
278
+ return getSymbolOf(table, key);
260
279
  }
261
280
  /**
262
281
  * Drizzle keys the Columns object by TypeScript property name, but every other piece of
@@ -505,6 +524,14 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
505
524
  return { tsType: "bigint", dbType: "BIGINT" };
506
525
  case "SQLiteNumeric":
507
526
  return { tsType: "string", dbType: "NUMERIC" };
527
+ // The other two modes of the same column, which matched no arm here and no dialect regex
528
+ // below either, so both came back UNKNOWN and every generator emitted a schema that
529
+ // accepted anything at all. Read back through better-sqlite3 3.53.4 on a `numeric` column,
530
+ // `db.select()` hands back a number in number mode and a bigint in bigint mode.
531
+ case "SQLiteNumericNumber":
532
+ return { tsType: "number", dbType: "NUMERIC" };
533
+ case "SQLiteNumericBigInt":
534
+ return { tsType: "bigint", dbType: "NUMERIC" };
508
535
  case "SQLiteBoolean":
509
536
  return { tsType: "boolean", dbType: "INTEGER" };
510
537
  // 0.4x gives an enum its own class, which had no arm here at all, so an enum column came
@@ -549,6 +576,15 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
549
576
  return { tsType: "Date", dbType: "TIMESTAMP" };
550
577
  case "PgNumeric":
551
578
  return { tsType: "string", dbType: "NUMERIC" };
579
+ // The other two modes, each its own class. `PgNumericNumber` reached the coarse
580
+ // `/Numeric|Float|Double|Real/i` arm and got the right answer there; `PgNumericBigInt` got
581
+ // its `bigint` from the `/BigInt/i` arm meant for `bigint` columns, and the SQL label that
582
+ // comes with it, so a `numeric(20,0)` was reported as a BIGINT column. Read back through
583
+ // PGlite, `db.select()` hands back a number in number mode and a bigint in bigint mode.
584
+ case "PgNumericNumber":
585
+ return { tsType: "number", dbType: "NUMERIC" };
586
+ case "PgNumericBigInt":
587
+ return { tsType: "bigint", dbType: "NUMERIC" };
552
588
  case "PgDoublePrecision":
553
589
  return { tsType: "number", dbType: "DOUBLE" };
554
590
  // `real()` builds a `PgReal`, which matched no arm and fell through to the coarse
@@ -571,6 +607,16 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
571
607
  case "PgJson":
572
608
  case "PgJsonb":
573
609
  return { tsType: "any", dbType: ctor === "PgJsonb" ? "JSONB" : "JSON" };
610
+ // The four builders in `BYTE_STRING_CLASSES`, which the coarse `/Blob|Binary|VarBinary/i`
611
+ // arms below would otherwise call a `Uint8Array` on the strength of the word "Binary" in
612
+ // the class name. Asked of MySQL 8.4 through drizzle 0.45.2 instead: the driver hands up a
613
+ // Buffer, `mapFromDriverValue` decodes it, and the caller receives a string. Every one of
614
+ // the four declares `dataType: 'string'` and every one of the four defines that method.
615
+ case "MySqlBinary":
616
+ case "MySqlVarBinary":
617
+ case "SingleStoreBinary":
618
+ case "SingleStoreVarBinary":
619
+ return { tsType: "string", dbType: "BINARY" };
574
620
  default:
575
621
  if (column?.config?.mode === "timestamp" || column?.mode === "timestamp") {
576
622
  return { tsType: "Date", dbType: "INTEGER" };
@@ -598,7 +644,12 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
598
644
  if (/BigInt64/i.test(ctor)) return { tsType: "bigint", dbType: "BIGINT" };
599
645
  if (/BigInt53/i.test(ctor)) return { tsType: "number", dbType: "BIGINT" };
600
646
  if (/\bBigInt\b/i.test(ctor)) return { tsType: "bigint", dbType: "BIGINT" };
601
- if (/Decimal|Numeric|Float|Double|Real/i.test(ctor))
647
+ if (/Decimal/i.test(ctor)) {
648
+ if (/DecimalNumber$/.test(ctor)) return { tsType: "number", dbType: "NUMERIC" };
649
+ if (/DecimalBigInt$/.test(ctor)) return { tsType: "bigint", dbType: "NUMERIC" };
650
+ return { tsType: "string", dbType: "NUMERIC" };
651
+ }
652
+ if (/Numeric|Float|Double|Real/i.test(ctor))
602
653
  return { tsType: "number", dbType: "NUMERIC" };
603
654
  if (/Int|Serial|TinyInt|SmallInt|MediumInt/i.test(ctor))
604
655
  return { tsType: "number", dbType: "INTEGER" };
@@ -617,7 +668,12 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
617
668
  if (/Vector/i.test(ctor)) return { tsType: "any", dbType: "VECTOR" };
618
669
  if (/BigInt64/i.test(ctor)) return { tsType: "bigint", dbType: "BIGINT" };
619
670
  if (/BigInt53/i.test(ctor)) return { tsType: "number", dbType: "BIGINT" };
620
- if (/Decimal|Numeric|Float|Double|Real/i.test(ctor))
671
+ if (/Decimal/i.test(ctor)) {
672
+ if (/DecimalNumber$/.test(ctor)) return { tsType: "number", dbType: "NUMERIC" };
673
+ if (/DecimalBigInt$/.test(ctor)) return { tsType: "bigint", dbType: "NUMERIC" };
674
+ return { tsType: "string", dbType: "NUMERIC" };
675
+ }
676
+ if (/Numeric|Float|Double|Real/i.test(ctor))
621
677
  return { tsType: "number", dbType: "NUMERIC" };
622
678
  if (/Int|Serial|TinyInt|SmallInt|MediumInt/i.test(ctor))
623
679
  return { tsType: "number", dbType: "INTEGER" };
@@ -641,11 +697,10 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
641
697
  if (/Json/i.test(ctor)) return { tsType: "any", dbType: "JSON" };
642
698
  if (/Text/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
643
699
  if (/Bytes/i.test(ctor)) return { tsType: "Uint8Array", dbType: "BLOB" };
700
+ if (/Bool/i.test(ctor)) return { tsType: "boolean", dbType: "BOOLEAN" };
644
701
  if (/TimestampTz/i.test(ctor)) return { tsType: "Date", dbType: "TIMESTAMPTZ" };
645
- if (/Timestamp/i.test(ctor)) return { tsType: "string", dbType: "TIMESTAMP" };
646
- if (/LocalDateString|LocalTime/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
647
- if (/DateDuration|RelDuration|Duration/i.test(ctor))
648
- return { tsType: "string", dbType: "TEXT" };
702
+ if (/Timestamp|LocalDateString|LocalTime|DateDuration|RelDuration|Duration/i.test(ctor))
703
+ return { tsType: "unknown", dbType: "UNKNOWN" };
649
704
  }
650
705
  return { tsType: "unknown", dbType: "UNKNOWN" };
651
706
  }
@@ -690,7 +745,9 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
690
745
  const sqlKind = String(outerCol?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
691
746
  const sqlType = sqlKind.startsWith("MySql") && typeof col?.getSQLType === "function" ? String(col.getSQLType()).toLowerCase() : void 0;
692
747
  const byteCap = sqlType ? MYSQL_TEXT_CAPS[sqlType] : void 0;
693
- const fallbackShape = dbType === "JSON" || dbType === "JSONB" || col?.config?.mode === "json" ? { kind: "json" } : TUPLE_CLASS_SHAPES[String(col?.constructor?.name ?? "")];
748
+ 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];
750
+ if (fallbackShape?.kind === "byteString") delete constraints.maxLength;
694
751
  const shape = (v1?.shape ?? fallbackShape)?.kind;
695
752
  const finalTs = v1?.tsType ?? tsType;
696
753
  const wide = (finalTs === "unknown" || finalTs === "any") && (!shape || shape === "custom");
@@ -840,12 +897,14 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
840
897
  const relations = [];
841
898
  const enums = [];
842
899
  const columnEnums = [];
900
+ const viewTables = [];
843
901
  for (const [name, val] of Object.entries(exportsObj)) {
844
902
  try {
845
903
  const cols = this.getSymbol(val, "drizzle:Columns");
846
904
  if (cols && typeof cols === "object") {
847
905
  const table = this.analyzeTable(name, val, issues);
848
906
  tables.push(table);
907
+ if (isDrizzleView(val)) viewTables.push(table);
849
908
  for (const col of table.columns) {
850
909
  const enumVals = cols[col.name]?.enumValues;
851
910
  if (enumVals && enumVals.length) {
@@ -896,7 +955,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
896
955
  let dialect = "unknown";
897
956
  const marks = /* @__PURE__ */ new Set();
898
957
  for (const [_, val] of Object.entries(exportsObj)) {
899
- const cols = val?.[/* @__PURE__ */ Symbol.for("drizzle:Columns")];
958
+ const cols = this.getSymbol(val, "drizzle:Columns");
900
959
  if (!cols) continue;
901
960
  for (const c of Object.values(cols)) {
902
961
  const kind = c?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")];
@@ -913,6 +972,9 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
913
972
  else if (/MySql|Mysql/i.test(names)) dialect = "mysql";
914
973
  else if (/Pg|Postgres/i.test(names)) dialect = "postgres";
915
974
  else if (/Gel/i.test(names)) dialect = "gel";
975
+ if (dialect === "sqlite") {
976
+ for (const view of viewTables) view.readOnly = true;
977
+ }
916
978
  if (dialect === "unknown" && tables.length) {
917
979
  issues.push({
918
980
  code: "DRZL_ANL_DIALECT",
@@ -1058,6 +1120,7 @@ export {
1058
1120
  SchemaAnalyzer,
1059
1121
  index_default as default,
1060
1122
  describeV1Column,
1123
+ isDrizzleView,
1061
1124
  isReadOnlyRelation,
1062
1125
  isRelationsV2,
1063
1126
  readRelationsV2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drzl/analyzer",
3
- "version": "1.15.0",
3
+ "version": "1.17.0",
4
4
  "private": false,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",