@drzl/analyzer 1.21.5 → 1.22.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
@@ -417,6 +417,18 @@ function getSymbolOf(target, key) {
417
417
  function isDrizzleView(val) {
418
418
  return !!val && typeof val === "object" && !!getSymbolOf(val, "drizzle:ViewBaseConfig");
419
419
  }
420
+ function isPolicy(val) {
421
+ return /Policy$/.test(String(val?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? ""));
422
+ }
423
+ function readRoleNames(to) {
424
+ const one = (v) => {
425
+ if (typeof v === "string") return v || void 0;
426
+ const name = v?.name;
427
+ return typeof name === "string" && name ? name : void 0;
428
+ };
429
+ const list = Array.isArray(to) ? to : to == null ? [] : [to];
430
+ return Array.from(new Set(list.map(one).filter((v) => !!v)));
431
+ }
420
432
  function isReadOnlyRelation(val) {
421
433
  if (!val || typeof val !== "object") return false;
422
434
  return Object.getOwnPropertySymbols(val).some(
@@ -627,6 +639,30 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
627
639
  return "?";
628
640
  }).join("").trim();
629
641
  }
642
+ /**
643
+ * Normalise one `pgPolicy` into the reported shape.
644
+ *
645
+ * Everything is read off the instance directly. Measured 2026-08-12 against drizzle-orm 0.45.2, a
646
+ * `PgPolicy` carries `as`, `for`, `to`, `using`, `withCheck`, `_linkedTable` and `name` as own
647
+ * keys, and the ones the declaration omitted are present as `undefined`. So presence has to be
648
+ * tested by value: `'withCheck' in policy` is true for every policy ever declared and would report
649
+ * each of them as constraining its writes.
650
+ */
651
+ readPolicy(entry, toTs) {
652
+ const text = (v) => v == null ? void 0 : this.renderSql(v, toTs);
653
+ const str = (v) => typeof v === "string" && v ? v : void 0;
654
+ return {
655
+ name: String(entry?.name ?? ""),
656
+ ...str(entry?.as) ? { as: entry.as } : {},
657
+ ...str(entry?.for) ? { for: entry.for } : {},
658
+ ...(() => {
659
+ const to = readRoleNames(entry?.to);
660
+ return to.length ? { to } : {};
661
+ })(),
662
+ ...entry?.using != null ? { using: text(entry.using) } : {},
663
+ ...entry?.withCheck != null ? { withCheck: text(entry.withCheck) } : {}
664
+ };
665
+ }
630
666
  /** A value produced by Drizzle's `relations()` helper: a source table plus a callback. */
631
667
  isRelationsObject(val) {
632
668
  return !!val && typeof val === "object" && typeof val.config === "function" && !!this.getSymbol(val.table, "drizzle:Columns");
@@ -1033,6 +1069,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1033
1069
  const unique = [];
1034
1070
  const indexes = [];
1035
1071
  const checks = [];
1072
+ const policies = [];
1036
1073
  const foreignKeys = [];
1037
1074
  const pkCols = [];
1038
1075
  const uniqueGroups = /* @__PURE__ */ new Map();
@@ -1121,6 +1158,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1121
1158
  }
1122
1159
  const name = this.getSymbol(tbl, "drizzle:Name") || tsName;
1123
1160
  const schema = this.getSymbol(tbl, "drizzle:Schema");
1161
+ const rlsEnabled = this.getSymbol(tbl, "drizzle:EnableRLS");
1124
1162
  const toTs = this.dbToTsNames(columnsObj);
1125
1163
  try {
1126
1164
  const pkDef = this.getSymbol(tbl, "drizzle:PrimaryKey");
@@ -1153,6 +1191,10 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1153
1191
  checks.push({ name: entry.name, expression: this.renderSql(entry.value, toTs) });
1154
1192
  continue;
1155
1193
  }
1194
+ if (isPolicy(entry)) {
1195
+ policies.push(this.readPolicy(entry, toTs));
1196
+ continue;
1197
+ }
1156
1198
  const cfg = entry?.config ?? entry ?? {};
1157
1199
  const cols = (cfg.columns ?? []).map((c) => toTs(c?.name)).filter(Boolean);
1158
1200
  if (!cols.length) continue;
@@ -1197,6 +1239,11 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1197
1239
  indexes: [...pkCols.length ? [{ columns: pkCols }] : [], ...indexes],
1198
1240
  checks,
1199
1241
  foreignKeys,
1242
+ // Only where the dialect has row-level security at all. `drizzle:EnableRLS` is absent on
1243
+ // MySQL and SQLite tables rather than false, and reporting `rlsEnabled: false` for every
1244
+ // SQLite table would answer a question that dialect cannot be asked. Both fields are decided
1245
+ // by the same test so a table never carries one without the other.
1246
+ ...typeof rlsEnabled === "boolean" ? { policies, rlsEnabled } : {},
1200
1247
  // A materialized view refuses every write, so the generators skip its insert and update
1201
1248
  // schemas rather than describe an operation the database will always reject.
1202
1249
  ...isReadOnlyRelation(tbl) ? { readOnly: true } : {},
@@ -1295,12 +1342,15 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1295
1342
  const enums = [];
1296
1343
  const columnEnums = [];
1297
1344
  const viewTables = [];
1345
+ const linkedPolicies = [];
1346
+ const tableSources = /* @__PURE__ */ new Map();
1298
1347
  for (const [name, val] of Object.entries(exportsObj)) {
1299
1348
  try {
1300
1349
  const cols = this.getSymbol(val, "drizzle:Columns");
1301
1350
  if (cols && typeof cols === "object") {
1302
1351
  const table = this.analyzeTable(name, val, issues);
1303
1352
  tables.push(table);
1353
+ tableSources.set(val, table);
1304
1354
  if (isDrizzleView(val)) viewTables.push(table);
1305
1355
  for (const col of table.columns) {
1306
1356
  const enumVals = cols[col.name]?.enumValues;
@@ -1324,6 +1374,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1324
1374
  if (opts.includeRelations) {
1325
1375
  relations.push(...readRelationsV2(val, issues));
1326
1376
  }
1377
+ } else if (isPolicy(val)) {
1378
+ linkedPolicies.push(val);
1327
1379
  } else {
1328
1380
  const ev = val?.enumValues;
1329
1381
  if (Array.isArray(ev) && ev.every((x) => typeof x === "string")) {
@@ -1352,6 +1404,22 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1352
1404
  if (enums.some((e) => e.name === candidate.name)) continue;
1353
1405
  enums.push(candidate);
1354
1406
  }
1407
+ for (const raw of linkedPolicies) {
1408
+ const target = raw?._linkedTable;
1409
+ const table = target ? tableSources.get(target) : void 0;
1410
+ if (!table || !table.policies) {
1411
+ issues.push({
1412
+ code: "DRZL_ANL_POLICY_UNLINKED",
1413
+ level: "warn",
1414
+ message: `Policy "${String(raw?.name ?? "")}" is linked to a table this schema does not export.`,
1415
+ path: String(raw?.name ?? ""),
1416
+ hint: "Export the table it links to, so DRZL can report the policy against it."
1417
+ });
1418
+ continue;
1419
+ }
1420
+ const toTs = this.dbToTsNames(this.getSymbol(target, "drizzle:Columns") ?? {});
1421
+ table.policies.push({ ...this.readPolicy(raw, toTs), linked: true });
1422
+ }
1355
1423
  let dialect = "unknown";
1356
1424
  const marks = /* @__PURE__ */ new Set();
1357
1425
  for (const [_, val] of Object.entries(exportsObj)) {
package/dist/index.d.cts CHANGED
@@ -380,6 +380,45 @@ interface ForeignKey {
380
380
  onDelete?: string;
381
381
  onUpdate?: string;
382
382
  }
383
+ /**
384
+ * A row-level security policy, from Drizzle's `pgPolicy`.
385
+ *
386
+ * Postgres only. There is no MySQL or SQLite equivalent, and those tables do not carry the
387
+ * `drizzle:EnableRLS` symbol at all rather than carrying it as `false`, which is why
388
+ * `Table.rlsEnabled` is absent there instead of reading as "RLS is off".
389
+ */
390
+ interface Policy {
391
+ name: string;
392
+ /**
393
+ * `permissive`, where policies OR together, or `restrictive`, where they AND. Absent where the
394
+ * declaration did not say, which Postgres reads as permissive.
395
+ */
396
+ as?: string;
397
+ /** The command it applies to: `all`, `select`, `insert`, `update` or `delete`. */
398
+ for?: string;
399
+ /**
400
+ * The roles it applies to, always as a list.
401
+ *
402
+ * `to` is polymorphic in Drizzle: a bare string, a `pgRole` object, or an array mixing the two.
403
+ * Measured 2026-08-12 against drizzle-orm 0.45.2. Normalised to role names here, so no reader has
404
+ * to ask which of the three it got and none of them stringifies to `[object Object]`.
405
+ */
406
+ to?: string[];
407
+ /** The `USING` expression, which decides the rows a read can see. Rendered as text. */
408
+ using?: string;
409
+ /** The `WITH CHECK` expression, which decides the rows a write may produce. Rendered as text. */
410
+ withCheck?: string;
411
+ /**
412
+ * Set when the policy reached this table through `pgPolicy(...).link(table)` rather than through
413
+ * the table's own third argument.
414
+ *
415
+ * A linked policy is not reachable from the table object: measured, the table it links to gains
416
+ * no extra-config entry and carries no reference to it. It is found as a module export instead,
417
+ * which means one linked from a module the schema never exports is invisible to DRZL. That is the
418
+ * one gap in this list, and it is why the flag is reported rather than dropped.
419
+ */
420
+ linked?: boolean;
421
+ }
383
422
  interface Table {
384
423
  name: string;
385
424
  tsName: string;
@@ -409,6 +448,24 @@ interface Table {
409
448
  indexes: Index[];
410
449
  checks?: Check[];
411
450
  foreignKeys?: ForeignKey[];
451
+ /**
452
+ * The row-level security policies declared on the table, in declaration order.
453
+ *
454
+ * Absent where the dialect has none to declare. Present and empty is a different fact from
455
+ * absent: it says this is a Postgres table that declares no policy, which is what
456
+ * `rlsEnabled: true` beside it turns into a defect.
457
+ */
458
+ policies?: Policy[];
459
+ /**
460
+ * Whether the table calls `.enableRLS()`, from `drizzle:EnableRLS`.
461
+ *
462
+ * Absent on every dialect but Postgres, which does not carry the symbol at all. Present and
463
+ * `false` therefore means "a Postgres table that did not call it", which is **not** the same as
464
+ * "row-level security is off in the database": measured 2026-08-12, declaring any policy makes
465
+ * drizzle-kit emit `ALTER TABLE ... ENABLE ROW LEVEL SECURITY` regardless of this flag. Nothing
466
+ * should report a table as unprotected on the strength of this alone.
467
+ */
468
+ rlsEnabled?: boolean;
412
469
  /**
413
470
  * Set when the relation refuses writes, which today means a materialized view. Insert and
414
471
  * update schemas are not emitted for one, because the database will always refuse the
@@ -577,6 +634,16 @@ declare class SchemaAnalyzer {
577
634
  * Anything unrecognised becomes `?`, which is honest about the gap without inventing SQL.
578
635
  */
579
636
  private renderSql;
637
+ /**
638
+ * Normalise one `pgPolicy` into the reported shape.
639
+ *
640
+ * Everything is read off the instance directly. Measured 2026-08-12 against drizzle-orm 0.45.2, a
641
+ * `PgPolicy` carries `as`, `for`, `to`, `using`, `withCheck`, `_linkedTable` and `name` as own
642
+ * keys, and the ones the declaration omitted are present as `undefined`. So presence has to be
643
+ * tested by value: `'withCheck' in policy` is true for every policy ever declared and would report
644
+ * each of them as constraining its writes.
645
+ */
646
+ private readPolicy;
580
647
  /** A value produced by Drizzle's `relations()` helper: a source table plus a callback. */
581
648
  private isRelationsObject;
582
649
  /**
@@ -730,4 +797,4 @@ declare class SchemaAnalyzer {
730
797
  analyze(opts?: AnalyzeOptions): Promise<Analysis>;
731
798
  }
732
799
 
733
- 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, type UnnameableReason, SchemaAnalyzer as default, describeV1Column, isDrizzleView, isReadOnlyRelation, isRelationsV2, qualifiedForeignTable, qualifiedTableName, readRelationsV2 };
800
+ 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 Policy, type Relation, SchemaAnalyzer, type Table, type UnnameableReason, SchemaAnalyzer as default, describeV1Column, isDrizzleView, isReadOnlyRelation, isRelationsV2, qualifiedForeignTable, qualifiedTableName, readRelationsV2 };
package/dist/index.d.ts CHANGED
@@ -380,6 +380,45 @@ interface ForeignKey {
380
380
  onDelete?: string;
381
381
  onUpdate?: string;
382
382
  }
383
+ /**
384
+ * A row-level security policy, from Drizzle's `pgPolicy`.
385
+ *
386
+ * Postgres only. There is no MySQL or SQLite equivalent, and those tables do not carry the
387
+ * `drizzle:EnableRLS` symbol at all rather than carrying it as `false`, which is why
388
+ * `Table.rlsEnabled` is absent there instead of reading as "RLS is off".
389
+ */
390
+ interface Policy {
391
+ name: string;
392
+ /**
393
+ * `permissive`, where policies OR together, or `restrictive`, where they AND. Absent where the
394
+ * declaration did not say, which Postgres reads as permissive.
395
+ */
396
+ as?: string;
397
+ /** The command it applies to: `all`, `select`, `insert`, `update` or `delete`. */
398
+ for?: string;
399
+ /**
400
+ * The roles it applies to, always as a list.
401
+ *
402
+ * `to` is polymorphic in Drizzle: a bare string, a `pgRole` object, or an array mixing the two.
403
+ * Measured 2026-08-12 against drizzle-orm 0.45.2. Normalised to role names here, so no reader has
404
+ * to ask which of the three it got and none of them stringifies to `[object Object]`.
405
+ */
406
+ to?: string[];
407
+ /** The `USING` expression, which decides the rows a read can see. Rendered as text. */
408
+ using?: string;
409
+ /** The `WITH CHECK` expression, which decides the rows a write may produce. Rendered as text. */
410
+ withCheck?: string;
411
+ /**
412
+ * Set when the policy reached this table through `pgPolicy(...).link(table)` rather than through
413
+ * the table's own third argument.
414
+ *
415
+ * A linked policy is not reachable from the table object: measured, the table it links to gains
416
+ * no extra-config entry and carries no reference to it. It is found as a module export instead,
417
+ * which means one linked from a module the schema never exports is invisible to DRZL. That is the
418
+ * one gap in this list, and it is why the flag is reported rather than dropped.
419
+ */
420
+ linked?: boolean;
421
+ }
383
422
  interface Table {
384
423
  name: string;
385
424
  tsName: string;
@@ -409,6 +448,24 @@ interface Table {
409
448
  indexes: Index[];
410
449
  checks?: Check[];
411
450
  foreignKeys?: ForeignKey[];
451
+ /**
452
+ * The row-level security policies declared on the table, in declaration order.
453
+ *
454
+ * Absent where the dialect has none to declare. Present and empty is a different fact from
455
+ * absent: it says this is a Postgres table that declares no policy, which is what
456
+ * `rlsEnabled: true` beside it turns into a defect.
457
+ */
458
+ policies?: Policy[];
459
+ /**
460
+ * Whether the table calls `.enableRLS()`, from `drizzle:EnableRLS`.
461
+ *
462
+ * Absent on every dialect but Postgres, which does not carry the symbol at all. Present and
463
+ * `false` therefore means "a Postgres table that did not call it", which is **not** the same as
464
+ * "row-level security is off in the database": measured 2026-08-12, declaring any policy makes
465
+ * drizzle-kit emit `ALTER TABLE ... ENABLE ROW LEVEL SECURITY` regardless of this flag. Nothing
466
+ * should report a table as unprotected on the strength of this alone.
467
+ */
468
+ rlsEnabled?: boolean;
412
469
  /**
413
470
  * Set when the relation refuses writes, which today means a materialized view. Insert and
414
471
  * update schemas are not emitted for one, because the database will always refuse the
@@ -577,6 +634,16 @@ declare class SchemaAnalyzer {
577
634
  * Anything unrecognised becomes `?`, which is honest about the gap without inventing SQL.
578
635
  */
579
636
  private renderSql;
637
+ /**
638
+ * Normalise one `pgPolicy` into the reported shape.
639
+ *
640
+ * Everything is read off the instance directly. Measured 2026-08-12 against drizzle-orm 0.45.2, a
641
+ * `PgPolicy` carries `as`, `for`, `to`, `using`, `withCheck`, `_linkedTable` and `name` as own
642
+ * keys, and the ones the declaration omitted are present as `undefined`. So presence has to be
643
+ * tested by value: `'withCheck' in policy` is true for every policy ever declared and would report
644
+ * each of them as constraining its writes.
645
+ */
646
+ private readPolicy;
580
647
  /** A value produced by Drizzle's `relations()` helper: a source table plus a callback. */
581
648
  private isRelationsObject;
582
649
  /**
@@ -730,4 +797,4 @@ declare class SchemaAnalyzer {
730
797
  analyze(opts?: AnalyzeOptions): Promise<Analysis>;
731
798
  }
732
799
 
733
- 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, type UnnameableReason, SchemaAnalyzer as default, describeV1Column, isDrizzleView, isReadOnlyRelation, isRelationsV2, qualifiedForeignTable, qualifiedTableName, readRelationsV2 };
800
+ 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 Policy, type Relation, SchemaAnalyzer, type Table, type UnnameableReason, SchemaAnalyzer as default, describeV1Column, isDrizzleView, isReadOnlyRelation, isRelationsV2, qualifiedForeignTable, qualifiedTableName, readRelationsV2 };
package/dist/index.js CHANGED
@@ -374,6 +374,18 @@ function getSymbolOf(target, key) {
374
374
  function isDrizzleView(val) {
375
375
  return !!val && typeof val === "object" && !!getSymbolOf(val, "drizzle:ViewBaseConfig");
376
376
  }
377
+ function isPolicy(val) {
378
+ return /Policy$/.test(String(val?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? ""));
379
+ }
380
+ function readRoleNames(to) {
381
+ const one = (v) => {
382
+ if (typeof v === "string") return v || void 0;
383
+ const name = v?.name;
384
+ return typeof name === "string" && name ? name : void 0;
385
+ };
386
+ const list = Array.isArray(to) ? to : to == null ? [] : [to];
387
+ return Array.from(new Set(list.map(one).filter((v) => !!v)));
388
+ }
377
389
  function isReadOnlyRelation(val) {
378
390
  if (!val || typeof val !== "object") return false;
379
391
  return Object.getOwnPropertySymbols(val).some(
@@ -584,6 +596,30 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
584
596
  return "?";
585
597
  }).join("").trim();
586
598
  }
599
+ /**
600
+ * Normalise one `pgPolicy` into the reported shape.
601
+ *
602
+ * Everything is read off the instance directly. Measured 2026-08-12 against drizzle-orm 0.45.2, a
603
+ * `PgPolicy` carries `as`, `for`, `to`, `using`, `withCheck`, `_linkedTable` and `name` as own
604
+ * keys, and the ones the declaration omitted are present as `undefined`. So presence has to be
605
+ * tested by value: `'withCheck' in policy` is true for every policy ever declared and would report
606
+ * each of them as constraining its writes.
607
+ */
608
+ readPolicy(entry, toTs) {
609
+ const text = (v) => v == null ? void 0 : this.renderSql(v, toTs);
610
+ const str = (v) => typeof v === "string" && v ? v : void 0;
611
+ return {
612
+ name: String(entry?.name ?? ""),
613
+ ...str(entry?.as) ? { as: entry.as } : {},
614
+ ...str(entry?.for) ? { for: entry.for } : {},
615
+ ...(() => {
616
+ const to = readRoleNames(entry?.to);
617
+ return to.length ? { to } : {};
618
+ })(),
619
+ ...entry?.using != null ? { using: text(entry.using) } : {},
620
+ ...entry?.withCheck != null ? { withCheck: text(entry.withCheck) } : {}
621
+ };
622
+ }
587
623
  /** A value produced by Drizzle's `relations()` helper: a source table plus a callback. */
588
624
  isRelationsObject(val) {
589
625
  return !!val && typeof val === "object" && typeof val.config === "function" && !!this.getSymbol(val.table, "drizzle:Columns");
@@ -990,6 +1026,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
990
1026
  const unique = [];
991
1027
  const indexes = [];
992
1028
  const checks = [];
1029
+ const policies = [];
993
1030
  const foreignKeys = [];
994
1031
  const pkCols = [];
995
1032
  const uniqueGroups = /* @__PURE__ */ new Map();
@@ -1078,6 +1115,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1078
1115
  }
1079
1116
  const name = this.getSymbol(tbl, "drizzle:Name") || tsName;
1080
1117
  const schema = this.getSymbol(tbl, "drizzle:Schema");
1118
+ const rlsEnabled = this.getSymbol(tbl, "drizzle:EnableRLS");
1081
1119
  const toTs = this.dbToTsNames(columnsObj);
1082
1120
  try {
1083
1121
  const pkDef = this.getSymbol(tbl, "drizzle:PrimaryKey");
@@ -1110,6 +1148,10 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1110
1148
  checks.push({ name: entry.name, expression: this.renderSql(entry.value, toTs) });
1111
1149
  continue;
1112
1150
  }
1151
+ if (isPolicy(entry)) {
1152
+ policies.push(this.readPolicy(entry, toTs));
1153
+ continue;
1154
+ }
1113
1155
  const cfg = entry?.config ?? entry ?? {};
1114
1156
  const cols = (cfg.columns ?? []).map((c) => toTs(c?.name)).filter(Boolean);
1115
1157
  if (!cols.length) continue;
@@ -1154,6 +1196,11 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1154
1196
  indexes: [...pkCols.length ? [{ columns: pkCols }] : [], ...indexes],
1155
1197
  checks,
1156
1198
  foreignKeys,
1199
+ // Only where the dialect has row-level security at all. `drizzle:EnableRLS` is absent on
1200
+ // MySQL and SQLite tables rather than false, and reporting `rlsEnabled: false` for every
1201
+ // SQLite table would answer a question that dialect cannot be asked. Both fields are decided
1202
+ // by the same test so a table never carries one without the other.
1203
+ ...typeof rlsEnabled === "boolean" ? { policies, rlsEnabled } : {},
1157
1204
  // A materialized view refuses every write, so the generators skip its insert and update
1158
1205
  // schemas rather than describe an operation the database will always reject.
1159
1206
  ...isReadOnlyRelation(tbl) ? { readOnly: true } : {},
@@ -1252,12 +1299,15 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1252
1299
  const enums = [];
1253
1300
  const columnEnums = [];
1254
1301
  const viewTables = [];
1302
+ const linkedPolicies = [];
1303
+ const tableSources = /* @__PURE__ */ new Map();
1255
1304
  for (const [name, val] of Object.entries(exportsObj)) {
1256
1305
  try {
1257
1306
  const cols = this.getSymbol(val, "drizzle:Columns");
1258
1307
  if (cols && typeof cols === "object") {
1259
1308
  const table = this.analyzeTable(name, val, issues);
1260
1309
  tables.push(table);
1310
+ tableSources.set(val, table);
1261
1311
  if (isDrizzleView(val)) viewTables.push(table);
1262
1312
  for (const col of table.columns) {
1263
1313
  const enumVals = cols[col.name]?.enumValues;
@@ -1281,6 +1331,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1281
1331
  if (opts.includeRelations) {
1282
1332
  relations.push(...readRelationsV2(val, issues));
1283
1333
  }
1334
+ } else if (isPolicy(val)) {
1335
+ linkedPolicies.push(val);
1284
1336
  } else {
1285
1337
  const ev = val?.enumValues;
1286
1338
  if (Array.isArray(ev) && ev.every((x) => typeof x === "string")) {
@@ -1309,6 +1361,22 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1309
1361
  if (enums.some((e) => e.name === candidate.name)) continue;
1310
1362
  enums.push(candidate);
1311
1363
  }
1364
+ for (const raw of linkedPolicies) {
1365
+ const target = raw?._linkedTable;
1366
+ const table = target ? tableSources.get(target) : void 0;
1367
+ if (!table || !table.policies) {
1368
+ issues.push({
1369
+ code: "DRZL_ANL_POLICY_UNLINKED",
1370
+ level: "warn",
1371
+ message: `Policy "${String(raw?.name ?? "")}" is linked to a table this schema does not export.`,
1372
+ path: String(raw?.name ?? ""),
1373
+ hint: "Export the table it links to, so DRZL can report the policy against it."
1374
+ });
1375
+ continue;
1376
+ }
1377
+ const toTs = this.dbToTsNames(this.getSymbol(target, "drizzle:Columns") ?? {});
1378
+ table.policies.push({ ...this.readPolicy(raw, toTs), linked: true });
1379
+ }
1312
1380
  let dialect = "unknown";
1313
1381
  const marks = /* @__PURE__ */ new Set();
1314
1382
  for (const [_, val] of Object.entries(exportsObj)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drzl/analyzer",
3
- "version": "1.21.5",
3
+ "version": "1.22.0",
4
4
  "private": false,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",