@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 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
- /** A string column with a known shape, currently only `uuid`. */
66
- format?: 'uuid';
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
- /** A string column with a known shape, currently only `uuid`. */
66
- format?: 'uuid';
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drzl/analyzer",
3
- "version": "1.8.0",
3
+ "version": "1.10.0",
4
4
  "private": false,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",