@drzl/analyzer 1.9.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 = {};
@@ -216,6 +218,47 @@ function declaredLength(column) {
216
218
  const n = column?.length ?? column?.config?.length ?? column?.config?.dimensions;
217
219
  return typeof n === "number" && Number.isFinite(n) && n > 0 ? n : void 0;
218
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
+ }
219
262
  var _SchemaAnalyzer = class _SchemaAnalyzer {
220
263
  constructor(schemaPath) {
221
264
  this.schemaPath = schemaPath;
@@ -769,6 +812,10 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
769
812
  if (opts.includeRelations) {
770
813
  relations.push(...this.readRelationsObject(val, name, issues));
771
814
  }
815
+ } else if (isRelationsV2(val)) {
816
+ if (opts.includeRelations) {
817
+ relations.push(...readRelationsV2(val, issues));
818
+ }
772
819
  } else {
773
820
  const ev = val?.enumValues;
774
821
  if (Array.isArray(ev) && ev.every((x) => typeof x === "string")) {
@@ -906,5 +953,7 @@ var index_default = SchemaAnalyzer;
906
953
  // Annotate the CommonJS export names for ESM import in node:
907
954
  0 && (module.exports = {
908
955
  SchemaAnalyzer,
909
- describeV1Column
956
+ describeV1Column,
957
+ isRelationsV2,
958
+ readRelationsV2
910
959
  });
package/dist/index.d.cts CHANGED
@@ -206,6 +206,25 @@ interface AnalyzeOptions {
206
206
  * path below untouched.
207
207
  */
208
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[];
209
228
  declare class SchemaAnalyzer {
210
229
  private readonly schemaPath;
211
230
  constructor(schemaPath: string);
@@ -310,4 +329,4 @@ declare class SchemaAnalyzer {
310
329
  analyze(opts?: AnalyzeOptions): Promise<Analysis>;
311
330
  }
312
331
 
313
- 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
@@ -206,6 +206,25 @@ interface AnalyzeOptions {
206
206
  * path below untouched.
207
207
  */
208
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[];
209
228
  declare class SchemaAnalyzer {
210
229
  private readonly schemaPath;
211
230
  constructor(schemaPath: string);
@@ -310,4 +329,4 @@ declare class SchemaAnalyzer {
310
329
  analyze(opts?: AnalyzeOptions): Promise<Analysis>;
311
330
  }
312
331
 
313
- 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
@@ -179,6 +179,47 @@ function declaredLength(column) {
179
179
  const n = column?.length ?? column?.config?.length ?? column?.config?.dimensions;
180
180
  return typeof n === "number" && Number.isFinite(n) && n > 0 ? n : void 0;
181
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
+ }
182
223
  var _SchemaAnalyzer = class _SchemaAnalyzer {
183
224
  constructor(schemaPath) {
184
225
  this.schemaPath = schemaPath;
@@ -732,6 +773,10 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
732
773
  if (opts.includeRelations) {
733
774
  relations.push(...this.readRelationsObject(val, name, issues));
734
775
  }
776
+ } else if (isRelationsV2(val)) {
777
+ if (opts.includeRelations) {
778
+ relations.push(...readRelationsV2(val, issues));
779
+ }
735
780
  } else {
736
781
  const ev = val?.enumValues;
737
782
  if (Array.isArray(ev) && ev.every((x) => typeof x === "string")) {
@@ -869,5 +914,7 @@ var index_default = SchemaAnalyzer;
869
914
  export {
870
915
  SchemaAnalyzer,
871
916
  index_default as default,
872
- describeV1Column
917
+ describeV1Column,
918
+ isRelationsV2,
919
+ readRelationsV2
873
920
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drzl/analyzer",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "private": false,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",