@drzl/analyzer 1.8.0 → 1.10.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 +54 -2
- package/dist/index.d.cts +30 -3
- package/dist/index.d.ts +30 -3
- package/dist/index.js +51 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -32,7 +32,9 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
SchemaAnalyzer: () => SchemaAnalyzer,
|
|
34
34
|
default: () => index_default,
|
|
35
|
-
describeV1Column: () => describeV1Column
|
|
35
|
+
describeV1Column: () => describeV1Column,
|
|
36
|
+
isRelationsV2: () => isRelationsV2,
|
|
37
|
+
readRelationsV2: () => readRelationsV2
|
|
36
38
|
});
|
|
37
39
|
module.exports = __toCommonJS(index_exports);
|
|
38
40
|
var import_meta = {};
|
|
@@ -122,6 +124,9 @@ function describeV1Column(column) {
|
|
|
122
124
|
case "numeric":
|
|
123
125
|
out.tsType = "string";
|
|
124
126
|
out.dbType = "NUMERIC";
|
|
127
|
+
if (!String(column?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "").startsWith("SQLite")) {
|
|
128
|
+
out.format = "numeric";
|
|
129
|
+
}
|
|
125
130
|
break;
|
|
126
131
|
case "json":
|
|
127
132
|
out.tsType = "any";
|
|
@@ -213,6 +218,47 @@ function declaredLength(column) {
|
|
|
213
218
|
const n = column?.length ?? column?.config?.length ?? column?.config?.dimensions;
|
|
214
219
|
return typeof n === "number" && Number.isFinite(n) && n > 0 ? n : void 0;
|
|
215
220
|
}
|
|
221
|
+
function getSymbolOf(target, key) {
|
|
222
|
+
if (!target) return void 0;
|
|
223
|
+
try {
|
|
224
|
+
for (const sym of Object.getOwnPropertySymbols(target)) {
|
|
225
|
+
if (sym.description === key) return target[sym];
|
|
226
|
+
}
|
|
227
|
+
} catch {
|
|
228
|
+
}
|
|
229
|
+
return target[Symbol.for(key)];
|
|
230
|
+
}
|
|
231
|
+
function isRelationsV2(val) {
|
|
232
|
+
if (!val || typeof val !== "object" || Array.isArray(val)) return false;
|
|
233
|
+
const entries = Object.values(val);
|
|
234
|
+
if (!entries.length) return false;
|
|
235
|
+
return entries.every(
|
|
236
|
+
(e) => !!e && typeof e === "object" && !!e.table && !!e.relations && typeof e.relations === "object" && Object.values(e.relations).every(
|
|
237
|
+
(r) => r?.relationType === "one" || r?.relationType === "many"
|
|
238
|
+
)
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
function readRelationsV2(val, issues = []) {
|
|
242
|
+
const out = [];
|
|
243
|
+
for (const [tableKey, entry] of Object.entries(val)) {
|
|
244
|
+
const from = getSymbolOf(entry.table, "drizzle:Name") ?? entry.name ?? tableKey;
|
|
245
|
+
for (const [fieldName, r] of Object.entries(entry.relations ?? {})) {
|
|
246
|
+
const to = r?.targetTableName;
|
|
247
|
+
if (typeof to !== "string" || !to) {
|
|
248
|
+
issues.push({
|
|
249
|
+
code: "DRZL_ANL_REL_V2",
|
|
250
|
+
level: "warn",
|
|
251
|
+
message: `Relation "${fieldName}" on "${from}" names no target table and was skipped.`
|
|
252
|
+
});
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
const via = getSymbolOf(r.throughTable, "drizzle:Name") ?? getSymbolOf(r.through?.sourceTable, "drizzle:Name") ?? void 0;
|
|
256
|
+
if (via) out.push({ kind: "manyToMany", from, to, via });
|
|
257
|
+
else out.push({ kind: r.relationType === "many" ? "many" : "one", from, to });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return out;
|
|
261
|
+
}
|
|
216
262
|
var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
217
263
|
constructor(schemaPath) {
|
|
218
264
|
this.schemaPath = schemaPath;
|
|
@@ -766,6 +812,10 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
766
812
|
if (opts.includeRelations) {
|
|
767
813
|
relations.push(...this.readRelationsObject(val, name, issues));
|
|
768
814
|
}
|
|
815
|
+
} else if (isRelationsV2(val)) {
|
|
816
|
+
if (opts.includeRelations) {
|
|
817
|
+
relations.push(...readRelationsV2(val, issues));
|
|
818
|
+
}
|
|
769
819
|
} else {
|
|
770
820
|
const ev = val?.enumValues;
|
|
771
821
|
if (Array.isArray(ev) && ev.every((x) => typeof x === "string")) {
|
|
@@ -903,5 +953,7 @@ var index_default = SchemaAnalyzer;
|
|
|
903
953
|
// Annotate the CommonJS export names for ESM import in node:
|
|
904
954
|
0 && (module.exports = {
|
|
905
955
|
SchemaAnalyzer,
|
|
906
|
-
describeV1Column
|
|
956
|
+
describeV1Column,
|
|
957
|
+
isRelationsV2,
|
|
958
|
+
readRelationsV2
|
|
907
959
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -62,8 +62,16 @@ interface Column {
|
|
|
62
62
|
* generators fall back to the old inference there.
|
|
63
63
|
*/
|
|
64
64
|
integer?: boolean;
|
|
65
|
-
/**
|
|
66
|
-
|
|
65
|
+
/**
|
|
66
|
+
* A string column whose contents have a shape the database enforces.
|
|
67
|
+
*
|
|
68
|
+
* Only formats checked against Postgres itself appear here, and the list is short because most
|
|
69
|
+
* candidates failed: Postgres reads `'today'` and `'January 8, 1999'` as dates, pads
|
|
70
|
+
* `'2020-01-01'` into a macaddr, and accepts `'10.1/16'` as an inet. A check for any of those
|
|
71
|
+
* would reject input the database accepts, and turning away valid data is worse than not
|
|
72
|
+
* checking at all. See `COLUMN_FORMATS` in `@drzl/validation-core`.
|
|
73
|
+
*/
|
|
74
|
+
format?: 'uuid' | 'numeric';
|
|
67
75
|
/**
|
|
68
76
|
* Array depth for a column declared with `.array()`, absent when the column is a scalar.
|
|
69
77
|
*
|
|
@@ -198,6 +206,25 @@ interface AnalyzeOptions {
|
|
|
198
206
|
* path below untouched.
|
|
199
207
|
*/
|
|
200
208
|
declare function describeV1Column(column: any): Partial<Column> | null;
|
|
209
|
+
/**
|
|
210
|
+
* Whether an export is a Relations v2 definition, from `defineRelations(schema, (r) => ...)`.
|
|
211
|
+
*
|
|
212
|
+
* v2 returns a plain object keyed by table name, each entry `{ table, name, relations }`, with
|
|
213
|
+
* no marker class or symbol to match on. So the shape is what identifies it: every value has a
|
|
214
|
+
* `relations` record whose entries carry a `relationType`. That is specific enough not to
|
|
215
|
+
* collide with a table or an enum, both of which are checked before this.
|
|
216
|
+
*/
|
|
217
|
+
declare function isRelationsV2(val: any): boolean;
|
|
218
|
+
/**
|
|
219
|
+
* Read the relations declared by `defineRelations`.
|
|
220
|
+
*
|
|
221
|
+
* Simpler than the v1 reader, which has to invoke a callback with a stand-in builder to find
|
|
222
|
+
* out what was declared. v2 has already resolved everything: each descriptor states its
|
|
223
|
+
* `relationType`, its `targetTableName`, and for a many-to-many its `through` table, so nothing
|
|
224
|
+
* has to be inferred and the join table is stated rather than guessed at by the heuristic that
|
|
225
|
+
* covers v1.
|
|
226
|
+
*/
|
|
227
|
+
declare function readRelationsV2(val: any, issues?: Issue[]): Relation[];
|
|
201
228
|
declare class SchemaAnalyzer {
|
|
202
229
|
private readonly schemaPath;
|
|
203
230
|
constructor(schemaPath: string);
|
|
@@ -302,4 +329,4 @@ declare class SchemaAnalyzer {
|
|
|
302
329
|
analyze(opts?: AnalyzeOptions): Promise<Analysis>;
|
|
303
330
|
}
|
|
304
331
|
|
|
305
|
-
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 };
|
|
332
|
+
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, isRelationsV2, readRelationsV2 };
|
package/dist/index.d.ts
CHANGED
|
@@ -62,8 +62,16 @@ interface Column {
|
|
|
62
62
|
* generators fall back to the old inference there.
|
|
63
63
|
*/
|
|
64
64
|
integer?: boolean;
|
|
65
|
-
/**
|
|
66
|
-
|
|
65
|
+
/**
|
|
66
|
+
* A string column whose contents have a shape the database enforces.
|
|
67
|
+
*
|
|
68
|
+
* Only formats checked against Postgres itself appear here, and the list is short because most
|
|
69
|
+
* candidates failed: Postgres reads `'today'` and `'January 8, 1999'` as dates, pads
|
|
70
|
+
* `'2020-01-01'` into a macaddr, and accepts `'10.1/16'` as an inet. A check for any of those
|
|
71
|
+
* would reject input the database accepts, and turning away valid data is worse than not
|
|
72
|
+
* checking at all. See `COLUMN_FORMATS` in `@drzl/validation-core`.
|
|
73
|
+
*/
|
|
74
|
+
format?: 'uuid' | 'numeric';
|
|
67
75
|
/**
|
|
68
76
|
* Array depth for a column declared with `.array()`, absent when the column is a scalar.
|
|
69
77
|
*
|
|
@@ -198,6 +206,25 @@ interface AnalyzeOptions {
|
|
|
198
206
|
* path below untouched.
|
|
199
207
|
*/
|
|
200
208
|
declare function describeV1Column(column: any): Partial<Column> | null;
|
|
209
|
+
/**
|
|
210
|
+
* Whether an export is a Relations v2 definition, from `defineRelations(schema, (r) => ...)`.
|
|
211
|
+
*
|
|
212
|
+
* v2 returns a plain object keyed by table name, each entry `{ table, name, relations }`, with
|
|
213
|
+
* no marker class or symbol to match on. So the shape is what identifies it: every value has a
|
|
214
|
+
* `relations` record whose entries carry a `relationType`. That is specific enough not to
|
|
215
|
+
* collide with a table or an enum, both of which are checked before this.
|
|
216
|
+
*/
|
|
217
|
+
declare function isRelationsV2(val: any): boolean;
|
|
218
|
+
/**
|
|
219
|
+
* Read the relations declared by `defineRelations`.
|
|
220
|
+
*
|
|
221
|
+
* Simpler than the v1 reader, which has to invoke a callback with a stand-in builder to find
|
|
222
|
+
* out what was declared. v2 has already resolved everything: each descriptor states its
|
|
223
|
+
* `relationType`, its `targetTableName`, and for a many-to-many its `through` table, so nothing
|
|
224
|
+
* has to be inferred and the join table is stated rather than guessed at by the heuristic that
|
|
225
|
+
* covers v1.
|
|
226
|
+
*/
|
|
227
|
+
declare function readRelationsV2(val: any, issues?: Issue[]): Relation[];
|
|
201
228
|
declare class SchemaAnalyzer {
|
|
202
229
|
private readonly schemaPath;
|
|
203
230
|
constructor(schemaPath: string);
|
|
@@ -302,4 +329,4 @@ declare class SchemaAnalyzer {
|
|
|
302
329
|
analyze(opts?: AnalyzeOptions): Promise<Analysis>;
|
|
303
330
|
}
|
|
304
331
|
|
|
305
|
-
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 };
|
|
332
|
+
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, isRelationsV2, readRelationsV2 };
|
package/dist/index.js
CHANGED
|
@@ -85,6 +85,9 @@ function describeV1Column(column) {
|
|
|
85
85
|
case "numeric":
|
|
86
86
|
out.tsType = "string";
|
|
87
87
|
out.dbType = "NUMERIC";
|
|
88
|
+
if (!String(column?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "").startsWith("SQLite")) {
|
|
89
|
+
out.format = "numeric";
|
|
90
|
+
}
|
|
88
91
|
break;
|
|
89
92
|
case "json":
|
|
90
93
|
out.tsType = "any";
|
|
@@ -176,6 +179,47 @@ function declaredLength(column) {
|
|
|
176
179
|
const n = column?.length ?? column?.config?.length ?? column?.config?.dimensions;
|
|
177
180
|
return typeof n === "number" && Number.isFinite(n) && n > 0 ? n : void 0;
|
|
178
181
|
}
|
|
182
|
+
function getSymbolOf(target, key) {
|
|
183
|
+
if (!target) return void 0;
|
|
184
|
+
try {
|
|
185
|
+
for (const sym of Object.getOwnPropertySymbols(target)) {
|
|
186
|
+
if (sym.description === key) return target[sym];
|
|
187
|
+
}
|
|
188
|
+
} catch {
|
|
189
|
+
}
|
|
190
|
+
return target[Symbol.for(key)];
|
|
191
|
+
}
|
|
192
|
+
function isRelationsV2(val) {
|
|
193
|
+
if (!val || typeof val !== "object" || Array.isArray(val)) return false;
|
|
194
|
+
const entries = Object.values(val);
|
|
195
|
+
if (!entries.length) return false;
|
|
196
|
+
return entries.every(
|
|
197
|
+
(e) => !!e && typeof e === "object" && !!e.table && !!e.relations && typeof e.relations === "object" && Object.values(e.relations).every(
|
|
198
|
+
(r) => r?.relationType === "one" || r?.relationType === "many"
|
|
199
|
+
)
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
function readRelationsV2(val, issues = []) {
|
|
203
|
+
const out = [];
|
|
204
|
+
for (const [tableKey, entry] of Object.entries(val)) {
|
|
205
|
+
const from = getSymbolOf(entry.table, "drizzle:Name") ?? entry.name ?? tableKey;
|
|
206
|
+
for (const [fieldName, r] of Object.entries(entry.relations ?? {})) {
|
|
207
|
+
const to = r?.targetTableName;
|
|
208
|
+
if (typeof to !== "string" || !to) {
|
|
209
|
+
issues.push({
|
|
210
|
+
code: "DRZL_ANL_REL_V2",
|
|
211
|
+
level: "warn",
|
|
212
|
+
message: `Relation "${fieldName}" on "${from}" names no target table and was skipped.`
|
|
213
|
+
});
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
const via = getSymbolOf(r.throughTable, "drizzle:Name") ?? getSymbolOf(r.through?.sourceTable, "drizzle:Name") ?? void 0;
|
|
217
|
+
if (via) out.push({ kind: "manyToMany", from, to, via });
|
|
218
|
+
else out.push({ kind: r.relationType === "many" ? "many" : "one", from, to });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return out;
|
|
222
|
+
}
|
|
179
223
|
var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
180
224
|
constructor(schemaPath) {
|
|
181
225
|
this.schemaPath = schemaPath;
|
|
@@ -729,6 +773,10 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
729
773
|
if (opts.includeRelations) {
|
|
730
774
|
relations.push(...this.readRelationsObject(val, name, issues));
|
|
731
775
|
}
|
|
776
|
+
} else if (isRelationsV2(val)) {
|
|
777
|
+
if (opts.includeRelations) {
|
|
778
|
+
relations.push(...readRelationsV2(val, issues));
|
|
779
|
+
}
|
|
732
780
|
} else {
|
|
733
781
|
const ev = val?.enumValues;
|
|
734
782
|
if (Array.isArray(ev) && ev.every((x) => typeof x === "string")) {
|
|
@@ -866,5 +914,7 @@ var index_default = SchemaAnalyzer;
|
|
|
866
914
|
export {
|
|
867
915
|
SchemaAnalyzer,
|
|
868
916
|
index_default as default,
|
|
869
|
-
describeV1Column
|
|
917
|
+
describeV1Column,
|
|
918
|
+
isRelationsV2,
|
|
919
|
+
readRelationsV2
|
|
870
920
|
};
|