@drzl/analyzer 1.14.0 → 1.16.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 +12 -1
- package/dist/index.cjs +128 -31
- package/dist/index.d.cts +48 -1
- package/dist/index.d.ts +48 -1
- package/dist/index.js +127 -31
- package/package.json +2 -2
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
|
@@ -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
|
|
@@ -57,12 +58,18 @@ var MYSQL_TEXT_CAPS = {
|
|
|
57
58
|
mediumblob: 16777215,
|
|
58
59
|
longblob: 4294967295
|
|
59
60
|
};
|
|
60
|
-
var
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
var FLOAT32_MAX = "340282346638528859811704183484516925440";
|
|
62
|
+
var PG_FLOAT4_INPUT_MAX = "340282356779733661637539395458142568448";
|
|
63
|
+
var PG_FLOAT4_RANGE = [`-${PG_FLOAT4_INPUT_MAX}`, PG_FLOAT4_INPUT_MAX];
|
|
64
|
+
var MYSQL_FLOAT_RANGE = [`-${FLOAT32_MAX}`, FLOAT32_MAX];
|
|
65
|
+
var JS_SAFE_INTEGER_BOUNDS = ["-9007199254740991", "9007199254740991"];
|
|
66
|
+
var TUPLE_CLASS_SHAPES = {
|
|
67
|
+
// `line()` is the trap here: its `drizzle:entityKind` is `PgLine` while its constructor is
|
|
68
|
+
// `PgLineTuple`, and this path matches on the constructor.
|
|
69
|
+
PgPointTuple: { kind: "tuple", length: 2 },
|
|
70
|
+
PgLineTuple: { kind: "tuple", length: 3 }
|
|
65
71
|
};
|
|
72
|
+
var V1_ONLY_ENTITY_KINDS = /^(?:MsSql|Cockroach)/;
|
|
66
73
|
function describeV1Column(column) {
|
|
67
74
|
const codec = column?.codec;
|
|
68
75
|
const dataType = column?.dataType;
|
|
@@ -75,8 +82,9 @@ function describeV1Column(column) {
|
|
|
75
82
|
shape: { kind: "custom", sqlType: typeof sqlType === "string" ? sqlType : void 0 }
|
|
76
83
|
};
|
|
77
84
|
}
|
|
85
|
+
const entityKind = String(column?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
|
|
78
86
|
const [js, semantic = ""] = dataType.split(" ");
|
|
79
|
-
if (typeof codec !== "string" && !semantic) return null;
|
|
87
|
+
if (typeof codec !== "string" && !semantic && !V1_ONLY_ENTITY_KINDS.test(entityKind)) return null;
|
|
80
88
|
const out = {};
|
|
81
89
|
switch (semantic) {
|
|
82
90
|
case "int8":
|
|
@@ -111,7 +119,8 @@ function describeV1Column(column) {
|
|
|
111
119
|
break;
|
|
112
120
|
case "float":
|
|
113
121
|
case "double": {
|
|
114
|
-
|
|
122
|
+
if (semantic === "float")
|
|
123
|
+
[out.min, out.max] = codec === "float4" || entityKind.startsWith("Cockroach") ? PG_FLOAT4_RANGE : MYSQL_FLOAT_RANGE;
|
|
115
124
|
out.integer = false;
|
|
116
125
|
out.tsType = "number";
|
|
117
126
|
out.dbType = semantic === "float" ? "REAL" : "DOUBLE";
|
|
@@ -200,7 +209,7 @@ function describeV1Column(column) {
|
|
|
200
209
|
out.tsType = "number";
|
|
201
210
|
out.dbType = "NUMERIC";
|
|
202
211
|
out.integer = false;
|
|
203
|
-
[out.min, out.max] =
|
|
212
|
+
[out.min, out.max] = JS_SAFE_INTEGER_BOUNDS;
|
|
204
213
|
} else if (js === "string") {
|
|
205
214
|
out.tsType = "string";
|
|
206
215
|
out.dbType = codec === "varchar" ? "VARCHAR" : codec === "char" ? "CHAR" : "TEXT";
|
|
@@ -228,15 +237,33 @@ function declaredLength(column) {
|
|
|
228
237
|
const n = column?.length ?? column?.config?.length ?? column?.config?.dimensions;
|
|
229
238
|
return typeof n === "number" && Number.isFinite(n) && n > 0 ? n : void 0;
|
|
230
239
|
}
|
|
231
|
-
|
|
232
|
-
|
|
240
|
+
var VIEW_CONFIG_FIELDS = {
|
|
241
|
+
"drizzle:Columns": "selectedFields",
|
|
242
|
+
"drizzle:Name": "name",
|
|
243
|
+
"drizzle:Schema": "schema"
|
|
244
|
+
};
|
|
245
|
+
function ownSymbolOf(target, key) {
|
|
233
246
|
try {
|
|
234
247
|
for (const sym of Object.getOwnPropertySymbols(target)) {
|
|
235
248
|
if (sym.description === key) return target[sym];
|
|
236
249
|
}
|
|
237
250
|
} catch {
|
|
238
251
|
}
|
|
239
|
-
return
|
|
252
|
+
return void 0;
|
|
253
|
+
}
|
|
254
|
+
function getSymbolOf(target, key) {
|
|
255
|
+
if (!target) return void 0;
|
|
256
|
+
const own = ownSymbolOf(target, key);
|
|
257
|
+
if (own !== void 0) return own;
|
|
258
|
+
const direct = target[Symbol.for(key)];
|
|
259
|
+
if (direct !== void 0) return direct;
|
|
260
|
+
const field = VIEW_CONFIG_FIELDS[key];
|
|
261
|
+
if (!field) return void 0;
|
|
262
|
+
const cfg = ownSymbolOf(target, "drizzle:ViewBaseConfig");
|
|
263
|
+
return cfg ? cfg[field] : void 0;
|
|
264
|
+
}
|
|
265
|
+
function isDrizzleView(val) {
|
|
266
|
+
return !!val && typeof val === "object" && !!getSymbolOf(val, "drizzle:ViewBaseConfig");
|
|
240
267
|
}
|
|
241
268
|
function isReadOnlyRelation(val) {
|
|
242
269
|
if (!val || typeof val !== "object") return false;
|
|
@@ -280,17 +307,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
280
307
|
this.schemaPath = schemaPath;
|
|
281
308
|
}
|
|
282
309
|
getSymbol(table, key) {
|
|
283
|
-
|
|
284
|
-
try {
|
|
285
|
-
const syms = Object.getOwnPropertySymbols(table);
|
|
286
|
-
for (const s of syms) {
|
|
287
|
-
if (s.description === key) {
|
|
288
|
-
return table[s];
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
} catch {
|
|
292
|
-
}
|
|
293
|
-
return table[Symbol.for(key)];
|
|
310
|
+
return getSymbolOf(table, key);
|
|
294
311
|
}
|
|
295
312
|
/**
|
|
296
313
|
* Drizzle keys the Columns object by TypeScript property name, but every other piece of
|
|
@@ -507,6 +524,11 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
507
524
|
[out.min, out.max] = range;
|
|
508
525
|
out.integer = true;
|
|
509
526
|
}
|
|
527
|
+
if (Object.prototype.hasOwnProperty.call(_SchemaAnalyzer.INEXACT_RANGES, ctor)) {
|
|
528
|
+
const inexact = _SchemaAnalyzer.INEXACT_RANGES[ctor];
|
|
529
|
+
if (inexact) [out.min, out.max] = inexact;
|
|
530
|
+
out.integer = false;
|
|
531
|
+
}
|
|
510
532
|
if (/^(Pg)?UUID$/i.test(ctor) || /Uuid$/i.test(ctor)) out.format = "uuid";
|
|
511
533
|
return out;
|
|
512
534
|
}
|
|
@@ -578,9 +600,25 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
578
600
|
return { tsType: "Date", dbType: "TIMESTAMP" };
|
|
579
601
|
case "PgNumeric":
|
|
580
602
|
return { tsType: "string", dbType: "NUMERIC" };
|
|
581
|
-
case "PgFloat":
|
|
582
603
|
case "PgDoublePrecision":
|
|
583
604
|
return { tsType: "number", dbType: "DOUBLE" };
|
|
605
|
+
// `real()` builds a `PgReal`, which matched no arm and fell through to the coarse
|
|
606
|
+
// `/Numeric|Float|Double|Real/i` below, so a real column was labelled NUMERIC while v1
|
|
607
|
+
// called it REAL. The arm above used to name `PgFloat` alongside `PgDoublePrecision`, and
|
|
608
|
+
// no such class exists in pg-core on either major: `float` is MySQL's spelling and Gel's
|
|
609
|
+
// is `GelReal`, both of which are matched elsewhere. Enumerated from the module's own
|
|
610
|
+
// exports on 0.45.2 and on 1.0.0-rc.4, which name only PgReal and PgDoublePrecision.
|
|
611
|
+
case "PgReal":
|
|
612
|
+
return { tsType: "number", dbType: "REAL" };
|
|
613
|
+
// 0.4x names a point and a line by their mode. `point()` is a `PgPointTuple` and `line()` a
|
|
614
|
+
// `PgLineTuple`, whose entity kind is `PgLine` while its constructor is not, and both used
|
|
615
|
+
// to fall through to `/Point|Line/i` and come back `string`. The driver hands back [x, y]
|
|
616
|
+
// and [a, b, c], so a select schema built on 0.4x refused every row, and an insert schema
|
|
617
|
+
// took the one string form `mapToDriverValue` turns into something Postgres rejects.
|
|
618
|
+
case "PgPointTuple":
|
|
619
|
+
return { tsType: "[number, number]", dbType: "POINT" };
|
|
620
|
+
case "PgLineTuple":
|
|
621
|
+
return { tsType: "[number, number, number]", dbType: "LINE" };
|
|
584
622
|
case "PgJson":
|
|
585
623
|
case "PgJsonb":
|
|
586
624
|
return { tsType: "any", dbType: ctor === "PgJsonb" ? "JSONB" : "JSON" };
|
|
@@ -654,11 +692,10 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
654
692
|
if (/Json/i.test(ctor)) return { tsType: "any", dbType: "JSON" };
|
|
655
693
|
if (/Text/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
|
|
656
694
|
if (/Bytes/i.test(ctor)) return { tsType: "Uint8Array", dbType: "BLOB" };
|
|
695
|
+
if (/Bool/i.test(ctor)) return { tsType: "boolean", dbType: "BOOLEAN" };
|
|
657
696
|
if (/TimestampTz/i.test(ctor)) return { tsType: "Date", dbType: "TIMESTAMPTZ" };
|
|
658
|
-
if (/Timestamp/i.test(ctor))
|
|
659
|
-
|
|
660
|
-
if (/DateDuration|RelDuration|Duration/i.test(ctor))
|
|
661
|
-
return { tsType: "string", dbType: "TEXT" };
|
|
697
|
+
if (/Timestamp|LocalDateString|LocalTime|DateDuration|RelDuration|Duration/i.test(ctor))
|
|
698
|
+
return { tsType: "unknown", dbType: "UNKNOWN" };
|
|
662
699
|
}
|
|
663
700
|
return { tsType: "unknown", dbType: "UNKNOWN" };
|
|
664
701
|
}
|
|
@@ -703,8 +740,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
703
740
|
const sqlKind = String(outerCol?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
|
|
704
741
|
const sqlType = sqlKind.startsWith("MySql") && typeof col?.getSQLType === "function" ? String(col.getSQLType()).toLowerCase() : void 0;
|
|
705
742
|
const byteCap = sqlType ? MYSQL_TEXT_CAPS[sqlType] : void 0;
|
|
706
|
-
const
|
|
707
|
-
const shape = (v1?.shape ??
|
|
743
|
+
const fallbackShape = dbType === "JSON" || dbType === "JSONB" || col?.config?.mode === "json" ? { kind: "json" } : TUPLE_CLASS_SHAPES[String(col?.constructor?.name ?? "")];
|
|
744
|
+
const shape = (v1?.shape ?? fallbackShape)?.kind;
|
|
708
745
|
const finalTs = v1?.tsType ?? tsType;
|
|
709
746
|
const wide = (finalTs === "unknown" || finalTs === "any") && (!shape || shape === "custom");
|
|
710
747
|
if (wide) {
|
|
@@ -733,7 +770,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
733
770
|
// that spread has nothing to say and this is the only source.
|
|
734
771
|
...arrayDims ? { arrayDimensions: arrayDims } : {},
|
|
735
772
|
// Only where v1 did not already describe the value, so a shaped column keeps its shape.
|
|
736
|
-
...
|
|
773
|
+
...fallbackShape && !v1?.shape ? { shape: fallbackShape } : {},
|
|
737
774
|
...byteCap && v1?.maxBytes === void 0 ? { maxBytes: byteCap } : {}
|
|
738
775
|
});
|
|
739
776
|
}
|
|
@@ -853,12 +890,14 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
853
890
|
const relations = [];
|
|
854
891
|
const enums = [];
|
|
855
892
|
const columnEnums = [];
|
|
893
|
+
const viewTables = [];
|
|
856
894
|
for (const [name, val] of Object.entries(exportsObj)) {
|
|
857
895
|
try {
|
|
858
896
|
const cols = this.getSymbol(val, "drizzle:Columns");
|
|
859
897
|
if (cols && typeof cols === "object") {
|
|
860
898
|
const table = this.analyzeTable(name, val, issues);
|
|
861
899
|
tables.push(table);
|
|
900
|
+
if (isDrizzleView(val)) viewTables.push(table);
|
|
862
901
|
for (const col of table.columns) {
|
|
863
902
|
const enumVals = cols[col.name]?.enumValues;
|
|
864
903
|
if (enumVals && enumVals.length) {
|
|
@@ -909,7 +948,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
909
948
|
let dialect = "unknown";
|
|
910
949
|
const marks = /* @__PURE__ */ new Set();
|
|
911
950
|
for (const [_, val] of Object.entries(exportsObj)) {
|
|
912
|
-
const cols = val
|
|
951
|
+
const cols = this.getSymbol(val, "drizzle:Columns");
|
|
913
952
|
if (!cols) continue;
|
|
914
953
|
for (const c of Object.values(cols)) {
|
|
915
954
|
const kind = c?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")];
|
|
@@ -926,6 +965,9 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
926
965
|
else if (/MySql|Mysql/i.test(names)) dialect = "mysql";
|
|
927
966
|
else if (/Pg|Postgres/i.test(names)) dialect = "postgres";
|
|
928
967
|
else if (/Gel/i.test(names)) dialect = "gel";
|
|
968
|
+
if (dialect === "sqlite") {
|
|
969
|
+
for (const view of viewTables) view.readOnly = true;
|
|
970
|
+
}
|
|
929
971
|
if (dialect === "unknown" && tables.length) {
|
|
930
972
|
issues.push({
|
|
931
973
|
code: "DRZL_ANL_DIALECT",
|
|
@@ -1011,12 +1053,67 @@ _SchemaAnalyzer.INT_RANGES = {
|
|
|
1011
1053
|
MySqlBigInt64: ["-9223372036854775808", "9223372036854775807"],
|
|
1012
1054
|
SingleStoreBigInt64: ["-9223372036854775808", "9223372036854775807"]
|
|
1013
1055
|
};
|
|
1056
|
+
/**
|
|
1057
|
+
* The numeric column classes that are not exact, and the magnitude each one can really hold.
|
|
1058
|
+
*
|
|
1059
|
+
* Only drizzle v1 states this outright, as a `float` or `double` semantic on `dataType`. On
|
|
1060
|
+
* 0.4x the same columns reach the analyzer by class name, `INT_RANGES` was the only range table
|
|
1061
|
+
* on that path, and so nothing said anything about them at all: not the range, and not that
|
|
1062
|
+
* they are inexact. The parity gate measured seven of the ten classes below and reported DRZL
|
|
1063
|
+
* differing from the first-party validator for the same major on all seven. The three
|
|
1064
|
+
* SingleStore classes are in no fixture either pass carries and are covered by unit tests alone.
|
|
1065
|
+
*
|
|
1066
|
+
* Differing is what the gate reports, not what it forbids. Most of its waivers have DRZL
|
|
1067
|
+
* accepting something official refuses and the run counts them; an earlier version of this
|
|
1068
|
+
* sentence said the gate exists to forbid being looser, which the same commit's own success
|
|
1069
|
+
* banner denies.
|
|
1070
|
+
*
|
|
1071
|
+
* `null` is a value in this table and is not the same as a class it does not name. It says the
|
|
1072
|
+
* column is inexact and that no finite magnitude bound is truthful for it, which is the case for
|
|
1073
|
+
* every 8 byte float: float8 is the JavaScript number's own format, so Postgres accepts every
|
|
1074
|
+
* finite JS number into one, measured to `Number.MAX_VALUE`. Read with an own-property test for
|
|
1075
|
+
* that reason, and because a plain object answers to `constructor` and `toString`.
|
|
1076
|
+
*
|
|
1077
|
+
* `integer: false` travels with every entry, bound or not, and what it decides depends on which.
|
|
1078
|
+
* `isIntegerColumn` falls back to "declares both bounds" when the flag is absent, so on a bounded
|
|
1079
|
+
* entry the flag is the only thing stopping the emitted schema calling `.int()` and refusing 1.5.
|
|
1080
|
+
* On an unbounded one it decides nothing, measured both ways against the real function in
|
|
1081
|
+
* `@drzl/validation-core`'s integer-column.spec.ts. It is stated there because it is true of the
|
|
1082
|
+
* column, not because it guards anything. That spec is where the measurement moved when the
|
|
1083
|
+
* analyzer's copy of it turned out to be a closed loop; this sentence went on naming
|
|
1084
|
+
* floats-and-tuples-0.4x.spec.ts, which asserts the flag is present and nothing about what it
|
|
1085
|
+
* decides.
|
|
1086
|
+
*
|
|
1087
|
+
* The widths are the type's, not the name's: MySQL and SingleStore `real` is a synonym for
|
|
1088
|
+
* `double` unless REAL_AS_FLOAT is set, and SQLite `real` is an 8 byte IEEE float. Both are
|
|
1089
|
+
* `number double` on drizzle v1, which is where these pairings come from.
|
|
1090
|
+
*/
|
|
1091
|
+
_SchemaAnalyzer.INEXACT_RANGES = {
|
|
1092
|
+
// 4 byte floats, the one width a database refuses a magnitude for, and the two that have one
|
|
1093
|
+
// refuse at different values. SingleStore is MySQL wire-compatible and unmeasured here, so it
|
|
1094
|
+
// takes MySQL's rather than the wider of the two.
|
|
1095
|
+
PgReal: PG_FLOAT4_RANGE,
|
|
1096
|
+
MySqlFloat: MYSQL_FLOAT_RANGE,
|
|
1097
|
+
SingleStoreFloat: MYSQL_FLOAT_RANGE,
|
|
1098
|
+
// 8 byte floats, which hold every finite JS number
|
|
1099
|
+
PgDoublePrecision: null,
|
|
1100
|
+
MySqlDouble: null,
|
|
1101
|
+
MySqlReal: null,
|
|
1102
|
+
SQLiteReal: null,
|
|
1103
|
+
SingleStoreDouble: null,
|
|
1104
|
+
SingleStoreReal: null,
|
|
1105
|
+
// `numeric({ mode: 'number' })`, which v1 reaches through the bare-number arm of
|
|
1106
|
+
// `describeV1Column`. This one is about what a JS number can carry rather than about the
|
|
1107
|
+
// column, which Postgres caps far lower: it refuses 2147483648 into a `numeric(10,2)`.
|
|
1108
|
+
PgNumericNumber: JS_SAFE_INTEGER_BOUNDS
|
|
1109
|
+
};
|
|
1014
1110
|
var SchemaAnalyzer = _SchemaAnalyzer;
|
|
1015
1111
|
var index_default = SchemaAnalyzer;
|
|
1016
1112
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1017
1113
|
0 && (module.exports = {
|
|
1018
1114
|
SchemaAnalyzer,
|
|
1019
1115
|
describeV1Column,
|
|
1116
|
+
isDrizzleView,
|
|
1020
1117
|
isReadOnlyRelation,
|
|
1021
1118
|
isRelationsV2,
|
|
1022
1119
|
readRelationsV2
|
package/dist/index.d.cts
CHANGED
|
@@ -231,6 +231,17 @@ interface AnalyzeOptions {
|
|
|
231
231
|
* path below untouched.
|
|
232
232
|
*/
|
|
233
233
|
declare function describeV1Column(column: any): Partial<Column> | null;
|
|
234
|
+
/**
|
|
235
|
+
* Whether an export is a Drizzle view, of any dialect and on either major.
|
|
236
|
+
*
|
|
237
|
+
* Asked of `drizzle:ViewBaseConfig` rather than of `drizzle:IsDrizzleView`, which reads like the
|
|
238
|
+
* obvious question and is not there to be asked: the marker was introduced in 0.39.0, and a view
|
|
239
|
+
* built on 0.29.5, 0.33.0 or 0.36.4 answers undefined to it. The config is an own symbol on all
|
|
240
|
+
* eleven releases probed and on every view form measured, on both majors: the query-builder and
|
|
241
|
+
* explicit-column-list forms of pg view, pg materialized view, mysql view and sqlite view,
|
|
242
|
+
* `.existing()`, and the schema-qualified pg view and materialized view.
|
|
243
|
+
*/
|
|
244
|
+
declare function isDrizzleView(val: any): boolean;
|
|
234
245
|
/**
|
|
235
246
|
* Whether an export is a Relations v2 definition, from `defineRelations(schema, (r) => ...)`.
|
|
236
247
|
*
|
|
@@ -354,6 +365,42 @@ declare class SchemaAnalyzer {
|
|
|
354
365
|
* promise a precision that cannot survive the round trip.
|
|
355
366
|
*/
|
|
356
367
|
private static readonly INT_RANGES;
|
|
368
|
+
/**
|
|
369
|
+
* The numeric column classes that are not exact, and the magnitude each one can really hold.
|
|
370
|
+
*
|
|
371
|
+
* Only drizzle v1 states this outright, as a `float` or `double` semantic on `dataType`. On
|
|
372
|
+
* 0.4x the same columns reach the analyzer by class name, `INT_RANGES` was the only range table
|
|
373
|
+
* on that path, and so nothing said anything about them at all: not the range, and not that
|
|
374
|
+
* they are inexact. The parity gate measured seven of the ten classes below and reported DRZL
|
|
375
|
+
* differing from the first-party validator for the same major on all seven. The three
|
|
376
|
+
* SingleStore classes are in no fixture either pass carries and are covered by unit tests alone.
|
|
377
|
+
*
|
|
378
|
+
* Differing is what the gate reports, not what it forbids. Most of its waivers have DRZL
|
|
379
|
+
* accepting something official refuses and the run counts them; an earlier version of this
|
|
380
|
+
* sentence said the gate exists to forbid being looser, which the same commit's own success
|
|
381
|
+
* banner denies.
|
|
382
|
+
*
|
|
383
|
+
* `null` is a value in this table and is not the same as a class it does not name. It says the
|
|
384
|
+
* column is inexact and that no finite magnitude bound is truthful for it, which is the case for
|
|
385
|
+
* every 8 byte float: float8 is the JavaScript number's own format, so Postgres accepts every
|
|
386
|
+
* finite JS number into one, measured to `Number.MAX_VALUE`. Read with an own-property test for
|
|
387
|
+
* that reason, and because a plain object answers to `constructor` and `toString`.
|
|
388
|
+
*
|
|
389
|
+
* `integer: false` travels with every entry, bound or not, and what it decides depends on which.
|
|
390
|
+
* `isIntegerColumn` falls back to "declares both bounds" when the flag is absent, so on a bounded
|
|
391
|
+
* entry the flag is the only thing stopping the emitted schema calling `.int()` and refusing 1.5.
|
|
392
|
+
* On an unbounded one it decides nothing, measured both ways against the real function in
|
|
393
|
+
* `@drzl/validation-core`'s integer-column.spec.ts. It is stated there because it is true of the
|
|
394
|
+
* column, not because it guards anything. That spec is where the measurement moved when the
|
|
395
|
+
* analyzer's copy of it turned out to be a closed loop; this sentence went on naming
|
|
396
|
+
* floats-and-tuples-0.4x.spec.ts, which asserts the flag is present and nothing about what it
|
|
397
|
+
* decides.
|
|
398
|
+
*
|
|
399
|
+
* The widths are the type's, not the name's: MySQL and SingleStore `real` is a synonym for
|
|
400
|
+
* `double` unless REAL_AS_FLOAT is set, and SQLite `real` is an 8 byte IEEE float. Both are
|
|
401
|
+
* `number double` on drizzle v1, which is where these pairings come from.
|
|
402
|
+
*/
|
|
403
|
+
private static readonly INEXACT_RANGES;
|
|
357
404
|
/**
|
|
358
405
|
* Constraints the column definition already carries, which the analysis used to throw away.
|
|
359
406
|
*
|
|
@@ -366,4 +413,4 @@ declare class SchemaAnalyzer {
|
|
|
366
413
|
analyze(opts?: AnalyzeOptions): Promise<Analysis>;
|
|
367
414
|
}
|
|
368
415
|
|
|
369
|
-
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 };
|
|
416
|
+
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
|
@@ -231,6 +231,17 @@ interface AnalyzeOptions {
|
|
|
231
231
|
* path below untouched.
|
|
232
232
|
*/
|
|
233
233
|
declare function describeV1Column(column: any): Partial<Column> | null;
|
|
234
|
+
/**
|
|
235
|
+
* Whether an export is a Drizzle view, of any dialect and on either major.
|
|
236
|
+
*
|
|
237
|
+
* Asked of `drizzle:ViewBaseConfig` rather than of `drizzle:IsDrizzleView`, which reads like the
|
|
238
|
+
* obvious question and is not there to be asked: the marker was introduced in 0.39.0, and a view
|
|
239
|
+
* built on 0.29.5, 0.33.0 or 0.36.4 answers undefined to it. The config is an own symbol on all
|
|
240
|
+
* eleven releases probed and on every view form measured, on both majors: the query-builder and
|
|
241
|
+
* explicit-column-list forms of pg view, pg materialized view, mysql view and sqlite view,
|
|
242
|
+
* `.existing()`, and the schema-qualified pg view and materialized view.
|
|
243
|
+
*/
|
|
244
|
+
declare function isDrizzleView(val: any): boolean;
|
|
234
245
|
/**
|
|
235
246
|
* Whether an export is a Relations v2 definition, from `defineRelations(schema, (r) => ...)`.
|
|
236
247
|
*
|
|
@@ -354,6 +365,42 @@ declare class SchemaAnalyzer {
|
|
|
354
365
|
* promise a precision that cannot survive the round trip.
|
|
355
366
|
*/
|
|
356
367
|
private static readonly INT_RANGES;
|
|
368
|
+
/**
|
|
369
|
+
* The numeric column classes that are not exact, and the magnitude each one can really hold.
|
|
370
|
+
*
|
|
371
|
+
* Only drizzle v1 states this outright, as a `float` or `double` semantic on `dataType`. On
|
|
372
|
+
* 0.4x the same columns reach the analyzer by class name, `INT_RANGES` was the only range table
|
|
373
|
+
* on that path, and so nothing said anything about them at all: not the range, and not that
|
|
374
|
+
* they are inexact. The parity gate measured seven of the ten classes below and reported DRZL
|
|
375
|
+
* differing from the first-party validator for the same major on all seven. The three
|
|
376
|
+
* SingleStore classes are in no fixture either pass carries and are covered by unit tests alone.
|
|
377
|
+
*
|
|
378
|
+
* Differing is what the gate reports, not what it forbids. Most of its waivers have DRZL
|
|
379
|
+
* accepting something official refuses and the run counts them; an earlier version of this
|
|
380
|
+
* sentence said the gate exists to forbid being looser, which the same commit's own success
|
|
381
|
+
* banner denies.
|
|
382
|
+
*
|
|
383
|
+
* `null` is a value in this table and is not the same as a class it does not name. It says the
|
|
384
|
+
* column is inexact and that no finite magnitude bound is truthful for it, which is the case for
|
|
385
|
+
* every 8 byte float: float8 is the JavaScript number's own format, so Postgres accepts every
|
|
386
|
+
* finite JS number into one, measured to `Number.MAX_VALUE`. Read with an own-property test for
|
|
387
|
+
* that reason, and because a plain object answers to `constructor` and `toString`.
|
|
388
|
+
*
|
|
389
|
+
* `integer: false` travels with every entry, bound or not, and what it decides depends on which.
|
|
390
|
+
* `isIntegerColumn` falls back to "declares both bounds" when the flag is absent, so on a bounded
|
|
391
|
+
* entry the flag is the only thing stopping the emitted schema calling `.int()` and refusing 1.5.
|
|
392
|
+
* On an unbounded one it decides nothing, measured both ways against the real function in
|
|
393
|
+
* `@drzl/validation-core`'s integer-column.spec.ts. It is stated there because it is true of the
|
|
394
|
+
* column, not because it guards anything. That spec is where the measurement moved when the
|
|
395
|
+
* analyzer's copy of it turned out to be a closed loop; this sentence went on naming
|
|
396
|
+
* floats-and-tuples-0.4x.spec.ts, which asserts the flag is present and nothing about what it
|
|
397
|
+
* decides.
|
|
398
|
+
*
|
|
399
|
+
* The widths are the type's, not the name's: MySQL and SingleStore `real` is a synonym for
|
|
400
|
+
* `double` unless REAL_AS_FLOAT is set, and SQLite `real` is an 8 byte IEEE float. Both are
|
|
401
|
+
* `number double` on drizzle v1, which is where these pairings come from.
|
|
402
|
+
*/
|
|
403
|
+
private static readonly INEXACT_RANGES;
|
|
357
404
|
/**
|
|
358
405
|
* Constraints the column definition already carries, which the analysis used to throw away.
|
|
359
406
|
*
|
|
@@ -366,4 +413,4 @@ declare class SchemaAnalyzer {
|
|
|
366
413
|
analyze(opts?: AnalyzeOptions): Promise<Analysis>;
|
|
367
414
|
}
|
|
368
415
|
|
|
369
|
-
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 };
|
|
416
|
+
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
|
@@ -17,12 +17,18 @@ var MYSQL_TEXT_CAPS = {
|
|
|
17
17
|
mediumblob: 16777215,
|
|
18
18
|
longblob: 4294967295
|
|
19
19
|
};
|
|
20
|
-
var
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
};
|
|
31
|
+
var V1_ONLY_ENTITY_KINDS = /^(?:MsSql|Cockroach)/;
|
|
26
32
|
function describeV1Column(column) {
|
|
27
33
|
const codec = column?.codec;
|
|
28
34
|
const dataType = column?.dataType;
|
|
@@ -35,8 +41,9 @@ function describeV1Column(column) {
|
|
|
35
41
|
shape: { kind: "custom", sqlType: typeof sqlType === "string" ? sqlType : void 0 }
|
|
36
42
|
};
|
|
37
43
|
}
|
|
44
|
+
const entityKind = String(column?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
|
|
38
45
|
const [js, semantic = ""] = dataType.split(" ");
|
|
39
|
-
if (typeof codec !== "string" && !semantic) return null;
|
|
46
|
+
if (typeof codec !== "string" && !semantic && !V1_ONLY_ENTITY_KINDS.test(entityKind)) return null;
|
|
40
47
|
const out = {};
|
|
41
48
|
switch (semantic) {
|
|
42
49
|
case "int8":
|
|
@@ -71,7 +78,8 @@ function describeV1Column(column) {
|
|
|
71
78
|
break;
|
|
72
79
|
case "float":
|
|
73
80
|
case "double": {
|
|
74
|
-
|
|
81
|
+
if (semantic === "float")
|
|
82
|
+
[out.min, out.max] = codec === "float4" || entityKind.startsWith("Cockroach") ? PG_FLOAT4_RANGE : MYSQL_FLOAT_RANGE;
|
|
75
83
|
out.integer = false;
|
|
76
84
|
out.tsType = "number";
|
|
77
85
|
out.dbType = semantic === "float" ? "REAL" : "DOUBLE";
|
|
@@ -160,7 +168,7 @@ function describeV1Column(column) {
|
|
|
160
168
|
out.tsType = "number";
|
|
161
169
|
out.dbType = "NUMERIC";
|
|
162
170
|
out.integer = false;
|
|
163
|
-
[out.min, out.max] =
|
|
171
|
+
[out.min, out.max] = JS_SAFE_INTEGER_BOUNDS;
|
|
164
172
|
} else if (js === "string") {
|
|
165
173
|
out.tsType = "string";
|
|
166
174
|
out.dbType = codec === "varchar" ? "VARCHAR" : codec === "char" ? "CHAR" : "TEXT";
|
|
@@ -188,15 +196,33 @@ function declaredLength(column) {
|
|
|
188
196
|
const n = column?.length ?? column?.config?.length ?? column?.config?.dimensions;
|
|
189
197
|
return typeof n === "number" && Number.isFinite(n) && n > 0 ? n : void 0;
|
|
190
198
|
}
|
|
191
|
-
|
|
192
|
-
|
|
199
|
+
var VIEW_CONFIG_FIELDS = {
|
|
200
|
+
"drizzle:Columns": "selectedFields",
|
|
201
|
+
"drizzle:Name": "name",
|
|
202
|
+
"drizzle:Schema": "schema"
|
|
203
|
+
};
|
|
204
|
+
function ownSymbolOf(target, key) {
|
|
193
205
|
try {
|
|
194
206
|
for (const sym of Object.getOwnPropertySymbols(target)) {
|
|
195
207
|
if (sym.description === key) return target[sym];
|
|
196
208
|
}
|
|
197
209
|
} catch {
|
|
198
210
|
}
|
|
199
|
-
return
|
|
211
|
+
return void 0;
|
|
212
|
+
}
|
|
213
|
+
function getSymbolOf(target, key) {
|
|
214
|
+
if (!target) return void 0;
|
|
215
|
+
const own = ownSymbolOf(target, key);
|
|
216
|
+
if (own !== void 0) return own;
|
|
217
|
+
const direct = target[Symbol.for(key)];
|
|
218
|
+
if (direct !== void 0) return direct;
|
|
219
|
+
const field = VIEW_CONFIG_FIELDS[key];
|
|
220
|
+
if (!field) return void 0;
|
|
221
|
+
const cfg = ownSymbolOf(target, "drizzle:ViewBaseConfig");
|
|
222
|
+
return cfg ? cfg[field] : void 0;
|
|
223
|
+
}
|
|
224
|
+
function isDrizzleView(val) {
|
|
225
|
+
return !!val && typeof val === "object" && !!getSymbolOf(val, "drizzle:ViewBaseConfig");
|
|
200
226
|
}
|
|
201
227
|
function isReadOnlyRelation(val) {
|
|
202
228
|
if (!val || typeof val !== "object") return false;
|
|
@@ -240,17 +266,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
240
266
|
this.schemaPath = schemaPath;
|
|
241
267
|
}
|
|
242
268
|
getSymbol(table, key) {
|
|
243
|
-
|
|
244
|
-
try {
|
|
245
|
-
const syms = Object.getOwnPropertySymbols(table);
|
|
246
|
-
for (const s of syms) {
|
|
247
|
-
if (s.description === key) {
|
|
248
|
-
return table[s];
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
} catch {
|
|
252
|
-
}
|
|
253
|
-
return table[Symbol.for(key)];
|
|
269
|
+
return getSymbolOf(table, key);
|
|
254
270
|
}
|
|
255
271
|
/**
|
|
256
272
|
* Drizzle keys the Columns object by TypeScript property name, but every other piece of
|
|
@@ -467,6 +483,11 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
467
483
|
[out.min, out.max] = range;
|
|
468
484
|
out.integer = true;
|
|
469
485
|
}
|
|
486
|
+
if (Object.prototype.hasOwnProperty.call(_SchemaAnalyzer.INEXACT_RANGES, ctor)) {
|
|
487
|
+
const inexact = _SchemaAnalyzer.INEXACT_RANGES[ctor];
|
|
488
|
+
if (inexact) [out.min, out.max] = inexact;
|
|
489
|
+
out.integer = false;
|
|
490
|
+
}
|
|
470
491
|
if (/^(Pg)?UUID$/i.test(ctor) || /Uuid$/i.test(ctor)) out.format = "uuid";
|
|
471
492
|
return out;
|
|
472
493
|
}
|
|
@@ -538,9 +559,25 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
538
559
|
return { tsType: "Date", dbType: "TIMESTAMP" };
|
|
539
560
|
case "PgNumeric":
|
|
540
561
|
return { tsType: "string", dbType: "NUMERIC" };
|
|
541
|
-
case "PgFloat":
|
|
542
562
|
case "PgDoublePrecision":
|
|
543
563
|
return { tsType: "number", dbType: "DOUBLE" };
|
|
564
|
+
// `real()` builds a `PgReal`, which matched no arm and fell through to the coarse
|
|
565
|
+
// `/Numeric|Float|Double|Real/i` below, so a real column was labelled NUMERIC while v1
|
|
566
|
+
// called it REAL. The arm above used to name `PgFloat` alongside `PgDoublePrecision`, and
|
|
567
|
+
// no such class exists in pg-core on either major: `float` is MySQL's spelling and Gel's
|
|
568
|
+
// is `GelReal`, both of which are matched elsewhere. Enumerated from the module's own
|
|
569
|
+
// exports on 0.45.2 and on 1.0.0-rc.4, which name only PgReal and PgDoublePrecision.
|
|
570
|
+
case "PgReal":
|
|
571
|
+
return { tsType: "number", dbType: "REAL" };
|
|
572
|
+
// 0.4x names a point and a line by their mode. `point()` is a `PgPointTuple` and `line()` a
|
|
573
|
+
// `PgLineTuple`, whose entity kind is `PgLine` while its constructor is not, and both used
|
|
574
|
+
// to fall through to `/Point|Line/i` and come back `string`. The driver hands back [x, y]
|
|
575
|
+
// and [a, b, c], so a select schema built on 0.4x refused every row, and an insert schema
|
|
576
|
+
// took the one string form `mapToDriverValue` turns into something Postgres rejects.
|
|
577
|
+
case "PgPointTuple":
|
|
578
|
+
return { tsType: "[number, number]", dbType: "POINT" };
|
|
579
|
+
case "PgLineTuple":
|
|
580
|
+
return { tsType: "[number, number, number]", dbType: "LINE" };
|
|
544
581
|
case "PgJson":
|
|
545
582
|
case "PgJsonb":
|
|
546
583
|
return { tsType: "any", dbType: ctor === "PgJsonb" ? "JSONB" : "JSON" };
|
|
@@ -614,11 +651,10 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
614
651
|
if (/Json/i.test(ctor)) return { tsType: "any", dbType: "JSON" };
|
|
615
652
|
if (/Text/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
|
|
616
653
|
if (/Bytes/i.test(ctor)) return { tsType: "Uint8Array", dbType: "BLOB" };
|
|
654
|
+
if (/Bool/i.test(ctor)) return { tsType: "boolean", dbType: "BOOLEAN" };
|
|
617
655
|
if (/TimestampTz/i.test(ctor)) return { tsType: "Date", dbType: "TIMESTAMPTZ" };
|
|
618
|
-
if (/Timestamp/i.test(ctor))
|
|
619
|
-
|
|
620
|
-
if (/DateDuration|RelDuration|Duration/i.test(ctor))
|
|
621
|
-
return { tsType: "string", dbType: "TEXT" };
|
|
656
|
+
if (/Timestamp|LocalDateString|LocalTime|DateDuration|RelDuration|Duration/i.test(ctor))
|
|
657
|
+
return { tsType: "unknown", dbType: "UNKNOWN" };
|
|
622
658
|
}
|
|
623
659
|
return { tsType: "unknown", dbType: "UNKNOWN" };
|
|
624
660
|
}
|
|
@@ -663,8 +699,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
663
699
|
const sqlKind = String(outerCol?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
|
|
664
700
|
const sqlType = sqlKind.startsWith("MySql") && typeof col?.getSQLType === "function" ? String(col.getSQLType()).toLowerCase() : void 0;
|
|
665
701
|
const byteCap = sqlType ? MYSQL_TEXT_CAPS[sqlType] : void 0;
|
|
666
|
-
const
|
|
667
|
-
const shape = (v1?.shape ??
|
|
702
|
+
const fallbackShape = dbType === "JSON" || dbType === "JSONB" || col?.config?.mode === "json" ? { kind: "json" } : TUPLE_CLASS_SHAPES[String(col?.constructor?.name ?? "")];
|
|
703
|
+
const shape = (v1?.shape ?? fallbackShape)?.kind;
|
|
668
704
|
const finalTs = v1?.tsType ?? tsType;
|
|
669
705
|
const wide = (finalTs === "unknown" || finalTs === "any") && (!shape || shape === "custom");
|
|
670
706
|
if (wide) {
|
|
@@ -693,7 +729,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
693
729
|
// that spread has nothing to say and this is the only source.
|
|
694
730
|
...arrayDims ? { arrayDimensions: arrayDims } : {},
|
|
695
731
|
// Only where v1 did not already describe the value, so a shaped column keeps its shape.
|
|
696
|
-
...
|
|
732
|
+
...fallbackShape && !v1?.shape ? { shape: fallbackShape } : {},
|
|
697
733
|
...byteCap && v1?.maxBytes === void 0 ? { maxBytes: byteCap } : {}
|
|
698
734
|
});
|
|
699
735
|
}
|
|
@@ -813,12 +849,14 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
813
849
|
const relations = [];
|
|
814
850
|
const enums = [];
|
|
815
851
|
const columnEnums = [];
|
|
852
|
+
const viewTables = [];
|
|
816
853
|
for (const [name, val] of Object.entries(exportsObj)) {
|
|
817
854
|
try {
|
|
818
855
|
const cols = this.getSymbol(val, "drizzle:Columns");
|
|
819
856
|
if (cols && typeof cols === "object") {
|
|
820
857
|
const table = this.analyzeTable(name, val, issues);
|
|
821
858
|
tables.push(table);
|
|
859
|
+
if (isDrizzleView(val)) viewTables.push(table);
|
|
822
860
|
for (const col of table.columns) {
|
|
823
861
|
const enumVals = cols[col.name]?.enumValues;
|
|
824
862
|
if (enumVals && enumVals.length) {
|
|
@@ -869,7 +907,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
869
907
|
let dialect = "unknown";
|
|
870
908
|
const marks = /* @__PURE__ */ new Set();
|
|
871
909
|
for (const [_, val] of Object.entries(exportsObj)) {
|
|
872
|
-
const cols = val
|
|
910
|
+
const cols = this.getSymbol(val, "drizzle:Columns");
|
|
873
911
|
if (!cols) continue;
|
|
874
912
|
for (const c of Object.values(cols)) {
|
|
875
913
|
const kind = c?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")];
|
|
@@ -886,6 +924,9 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
886
924
|
else if (/MySql|Mysql/i.test(names)) dialect = "mysql";
|
|
887
925
|
else if (/Pg|Postgres/i.test(names)) dialect = "postgres";
|
|
888
926
|
else if (/Gel/i.test(names)) dialect = "gel";
|
|
927
|
+
if (dialect === "sqlite") {
|
|
928
|
+
for (const view of viewTables) view.readOnly = true;
|
|
929
|
+
}
|
|
889
930
|
if (dialect === "unknown" && tables.length) {
|
|
890
931
|
issues.push({
|
|
891
932
|
code: "DRZL_ANL_DIALECT",
|
|
@@ -971,12 +1012,67 @@ _SchemaAnalyzer.INT_RANGES = {
|
|
|
971
1012
|
MySqlBigInt64: ["-9223372036854775808", "9223372036854775807"],
|
|
972
1013
|
SingleStoreBigInt64: ["-9223372036854775808", "9223372036854775807"]
|
|
973
1014
|
};
|
|
1015
|
+
/**
|
|
1016
|
+
* The numeric column classes that are not exact, and the magnitude each one can really hold.
|
|
1017
|
+
*
|
|
1018
|
+
* Only drizzle v1 states this outright, as a `float` or `double` semantic on `dataType`. On
|
|
1019
|
+
* 0.4x the same columns reach the analyzer by class name, `INT_RANGES` was the only range table
|
|
1020
|
+
* on that path, and so nothing said anything about them at all: not the range, and not that
|
|
1021
|
+
* they are inexact. The parity gate measured seven of the ten classes below and reported DRZL
|
|
1022
|
+
* differing from the first-party validator for the same major on all seven. The three
|
|
1023
|
+
* SingleStore classes are in no fixture either pass carries and are covered by unit tests alone.
|
|
1024
|
+
*
|
|
1025
|
+
* Differing is what the gate reports, not what it forbids. Most of its waivers have DRZL
|
|
1026
|
+
* accepting something official refuses and the run counts them; an earlier version of this
|
|
1027
|
+
* sentence said the gate exists to forbid being looser, which the same commit's own success
|
|
1028
|
+
* banner denies.
|
|
1029
|
+
*
|
|
1030
|
+
* `null` is a value in this table and is not the same as a class it does not name. It says the
|
|
1031
|
+
* column is inexact and that no finite magnitude bound is truthful for it, which is the case for
|
|
1032
|
+
* every 8 byte float: float8 is the JavaScript number's own format, so Postgres accepts every
|
|
1033
|
+
* finite JS number into one, measured to `Number.MAX_VALUE`. Read with an own-property test for
|
|
1034
|
+
* that reason, and because a plain object answers to `constructor` and `toString`.
|
|
1035
|
+
*
|
|
1036
|
+
* `integer: false` travels with every entry, bound or not, and what it decides depends on which.
|
|
1037
|
+
* `isIntegerColumn` falls back to "declares both bounds" when the flag is absent, so on a bounded
|
|
1038
|
+
* entry the flag is the only thing stopping the emitted schema calling `.int()` and refusing 1.5.
|
|
1039
|
+
* On an unbounded one it decides nothing, measured both ways against the real function in
|
|
1040
|
+
* `@drzl/validation-core`'s integer-column.spec.ts. It is stated there because it is true of the
|
|
1041
|
+
* column, not because it guards anything. That spec is where the measurement moved when the
|
|
1042
|
+
* analyzer's copy of it turned out to be a closed loop; this sentence went on naming
|
|
1043
|
+
* floats-and-tuples-0.4x.spec.ts, which asserts the flag is present and nothing about what it
|
|
1044
|
+
* decides.
|
|
1045
|
+
*
|
|
1046
|
+
* The widths are the type's, not the name's: MySQL and SingleStore `real` is a synonym for
|
|
1047
|
+
* `double` unless REAL_AS_FLOAT is set, and SQLite `real` is an 8 byte IEEE float. Both are
|
|
1048
|
+
* `number double` on drizzle v1, which is where these pairings come from.
|
|
1049
|
+
*/
|
|
1050
|
+
_SchemaAnalyzer.INEXACT_RANGES = {
|
|
1051
|
+
// 4 byte floats, the one width a database refuses a magnitude for, and the two that have one
|
|
1052
|
+
// refuse at different values. SingleStore is MySQL wire-compatible and unmeasured here, so it
|
|
1053
|
+
// takes MySQL's rather than the wider of the two.
|
|
1054
|
+
PgReal: PG_FLOAT4_RANGE,
|
|
1055
|
+
MySqlFloat: MYSQL_FLOAT_RANGE,
|
|
1056
|
+
SingleStoreFloat: MYSQL_FLOAT_RANGE,
|
|
1057
|
+
// 8 byte floats, which hold every finite JS number
|
|
1058
|
+
PgDoublePrecision: null,
|
|
1059
|
+
MySqlDouble: null,
|
|
1060
|
+
MySqlReal: null,
|
|
1061
|
+
SQLiteReal: null,
|
|
1062
|
+
SingleStoreDouble: null,
|
|
1063
|
+
SingleStoreReal: null,
|
|
1064
|
+
// `numeric({ mode: 'number' })`, which v1 reaches through the bare-number arm of
|
|
1065
|
+
// `describeV1Column`. This one is about what a JS number can carry rather than about the
|
|
1066
|
+
// column, which Postgres caps far lower: it refuses 2147483648 into a `numeric(10,2)`.
|
|
1067
|
+
PgNumericNumber: JS_SAFE_INTEGER_BOUNDS
|
|
1068
|
+
};
|
|
974
1069
|
var SchemaAnalyzer = _SchemaAnalyzer;
|
|
975
1070
|
var index_default = SchemaAnalyzer;
|
|
976
1071
|
export {
|
|
977
1072
|
SchemaAnalyzer,
|
|
978
1073
|
index_default as default,
|
|
979
1074
|
describeV1Column,
|
|
1075
|
+
isDrizzleView,
|
|
980
1076
|
isReadOnlyRelation,
|
|
981
1077
|
isRelationsV2,
|
|
982
1078
|
readRelationsV2
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drzl/analyzer",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.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
|
}
|