@drzl/analyzer 1.21.4 → 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(
@@ -470,6 +482,17 @@ function unknownColumnHint(reason) {
470
482
  }
471
483
  return "Open an issue naming the column type so it can be modelled, or declare it with .$type<T>() and turn on typedColumns.";
472
484
  }
485
+ async function jitiCacheDir(fs, path) {
486
+ try {
487
+ const modules = path.join(process.cwd(), "node_modules");
488
+ await fs.stat(modules);
489
+ const dir = path.join(modules, ".cache", "jiti");
490
+ await fs.mkdir(dir, { recursive: true });
491
+ return dir;
492
+ } catch {
493
+ return void 0;
494
+ }
495
+ }
473
496
  var _SchemaAnalyzer = class _SchemaAnalyzer {
474
497
  /**
475
498
  * One path or several. The plural exists for drizzle-kit interop: kit's `schema` key names
@@ -616,6 +639,30 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
616
639
  return "?";
617
640
  }).join("").trim();
618
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
+ }
619
666
  /** A value produced by Drizzle's `relations()` helper: a source table plus a callback. */
620
667
  isRelationsObject(val) {
621
668
  return !!val && typeof val === "object" && typeof val.config === "function" && !!this.getSymbol(val.table, "drizzle:Columns");
@@ -1022,6 +1069,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1022
1069
  const unique = [];
1023
1070
  const indexes = [];
1024
1071
  const checks = [];
1072
+ const policies = [];
1025
1073
  const foreignKeys = [];
1026
1074
  const pkCols = [];
1027
1075
  const uniqueGroups = /* @__PURE__ */ new Map();
@@ -1110,6 +1158,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1110
1158
  }
1111
1159
  const name = this.getSymbol(tbl, "drizzle:Name") || tsName;
1112
1160
  const schema = this.getSymbol(tbl, "drizzle:Schema");
1161
+ const rlsEnabled = this.getSymbol(tbl, "drizzle:EnableRLS");
1113
1162
  const toTs = this.dbToTsNames(columnsObj);
1114
1163
  try {
1115
1164
  const pkDef = this.getSymbol(tbl, "drizzle:PrimaryKey");
@@ -1142,6 +1191,10 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1142
1191
  checks.push({ name: entry.name, expression: this.renderSql(entry.value, toTs) });
1143
1192
  continue;
1144
1193
  }
1194
+ if (isPolicy(entry)) {
1195
+ policies.push(this.readPolicy(entry, toTs));
1196
+ continue;
1197
+ }
1145
1198
  const cfg = entry?.config ?? entry ?? {};
1146
1199
  const cols = (cfg.columns ?? []).map((c) => toTs(c?.name)).filter(Boolean);
1147
1200
  if (!cols.length) continue;
@@ -1186,6 +1239,11 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1186
1239
  indexes: [...pkCols.length ? [{ columns: pkCols }] : [], ...indexes],
1187
1240
  checks,
1188
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 } : {},
1189
1247
  // A materialized view refuses every write, so the generators skip its insert and update
1190
1248
  // schemas rather than describe an operation the database will always reject.
1191
1249
  ...isReadOnlyRelation(tbl) ? { readOnly: true } : {},
@@ -1216,7 +1274,11 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1216
1274
  return { dialect: "unknown", tables: [], enums: [], relations: [], issues };
1217
1275
  }
1218
1276
  const { default: jiti } = await import("jiti");
1219
- const jit = jiti(import_meta.url, { moduleCache: false });
1277
+ const cacheDir = await jitiCacheDir(fs, path);
1278
+ const jit = jiti(import_meta.url, {
1279
+ moduleCache: false,
1280
+ ...cacheDir ? { fsCache: cacheDir } : {}
1281
+ });
1220
1282
  const exportsObj = {};
1221
1283
  const exportOrigin = /* @__PURE__ */ new Map();
1222
1284
  const duplicateDisagreement = (a, b) => {
@@ -1280,12 +1342,15 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1280
1342
  const enums = [];
1281
1343
  const columnEnums = [];
1282
1344
  const viewTables = [];
1345
+ const linkedPolicies = [];
1346
+ const tableSources = /* @__PURE__ */ new Map();
1283
1347
  for (const [name, val] of Object.entries(exportsObj)) {
1284
1348
  try {
1285
1349
  const cols = this.getSymbol(val, "drizzle:Columns");
1286
1350
  if (cols && typeof cols === "object") {
1287
1351
  const table = this.analyzeTable(name, val, issues);
1288
1352
  tables.push(table);
1353
+ tableSources.set(val, table);
1289
1354
  if (isDrizzleView(val)) viewTables.push(table);
1290
1355
  for (const col of table.columns) {
1291
1356
  const enumVals = cols[col.name]?.enumValues;
@@ -1309,6 +1374,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1309
1374
  if (opts.includeRelations) {
1310
1375
  relations.push(...readRelationsV2(val, issues));
1311
1376
  }
1377
+ } else if (isPolicy(val)) {
1378
+ linkedPolicies.push(val);
1312
1379
  } else {
1313
1380
  const ev = val?.enumValues;
1314
1381
  if (Array.isArray(ev) && ev.every((x) => typeof x === "string")) {
@@ -1337,6 +1404,22 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1337
1404
  if (enums.some((e) => e.name === candidate.name)) continue;
1338
1405
  enums.push(candidate);
1339
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
+ }
1340
1423
  let dialect = "unknown";
1341
1424
  const marks = /* @__PURE__ */ new Set();
1342
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(
@@ -427,6 +439,17 @@ function unknownColumnHint(reason) {
427
439
  }
428
440
  return "Open an issue naming the column type so it can be modelled, or declare it with .$type<T>() and turn on typedColumns.";
429
441
  }
442
+ async function jitiCacheDir(fs, path) {
443
+ try {
444
+ const modules = path.join(process.cwd(), "node_modules");
445
+ await fs.stat(modules);
446
+ const dir = path.join(modules, ".cache", "jiti");
447
+ await fs.mkdir(dir, { recursive: true });
448
+ return dir;
449
+ } catch {
450
+ return void 0;
451
+ }
452
+ }
430
453
  var _SchemaAnalyzer = class _SchemaAnalyzer {
431
454
  /**
432
455
  * One path or several. The plural exists for drizzle-kit interop: kit's `schema` key names
@@ -573,6 +596,30 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
573
596
  return "?";
574
597
  }).join("").trim();
575
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
+ }
576
623
  /** A value produced by Drizzle's `relations()` helper: a source table plus a callback. */
577
624
  isRelationsObject(val) {
578
625
  return !!val && typeof val === "object" && typeof val.config === "function" && !!this.getSymbol(val.table, "drizzle:Columns");
@@ -979,6 +1026,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
979
1026
  const unique = [];
980
1027
  const indexes = [];
981
1028
  const checks = [];
1029
+ const policies = [];
982
1030
  const foreignKeys = [];
983
1031
  const pkCols = [];
984
1032
  const uniqueGroups = /* @__PURE__ */ new Map();
@@ -1067,6 +1115,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1067
1115
  }
1068
1116
  const name = this.getSymbol(tbl, "drizzle:Name") || tsName;
1069
1117
  const schema = this.getSymbol(tbl, "drizzle:Schema");
1118
+ const rlsEnabled = this.getSymbol(tbl, "drizzle:EnableRLS");
1070
1119
  const toTs = this.dbToTsNames(columnsObj);
1071
1120
  try {
1072
1121
  const pkDef = this.getSymbol(tbl, "drizzle:PrimaryKey");
@@ -1099,6 +1148,10 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1099
1148
  checks.push({ name: entry.name, expression: this.renderSql(entry.value, toTs) });
1100
1149
  continue;
1101
1150
  }
1151
+ if (isPolicy(entry)) {
1152
+ policies.push(this.readPolicy(entry, toTs));
1153
+ continue;
1154
+ }
1102
1155
  const cfg = entry?.config ?? entry ?? {};
1103
1156
  const cols = (cfg.columns ?? []).map((c) => toTs(c?.name)).filter(Boolean);
1104
1157
  if (!cols.length) continue;
@@ -1143,6 +1196,11 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1143
1196
  indexes: [...pkCols.length ? [{ columns: pkCols }] : [], ...indexes],
1144
1197
  checks,
1145
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 } : {},
1146
1204
  // A materialized view refuses every write, so the generators skip its insert and update
1147
1205
  // schemas rather than describe an operation the database will always reject.
1148
1206
  ...isReadOnlyRelation(tbl) ? { readOnly: true } : {},
@@ -1173,7 +1231,11 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1173
1231
  return { dialect: "unknown", tables: [], enums: [], relations: [], issues };
1174
1232
  }
1175
1233
  const { default: jiti } = await import("jiti");
1176
- const jit = jiti(import.meta.url, { moduleCache: false });
1234
+ const cacheDir = await jitiCacheDir(fs, path);
1235
+ const jit = jiti(import.meta.url, {
1236
+ moduleCache: false,
1237
+ ...cacheDir ? { fsCache: cacheDir } : {}
1238
+ });
1177
1239
  const exportsObj = {};
1178
1240
  const exportOrigin = /* @__PURE__ */ new Map();
1179
1241
  const duplicateDisagreement = (a, b) => {
@@ -1237,12 +1299,15 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1237
1299
  const enums = [];
1238
1300
  const columnEnums = [];
1239
1301
  const viewTables = [];
1302
+ const linkedPolicies = [];
1303
+ const tableSources = /* @__PURE__ */ new Map();
1240
1304
  for (const [name, val] of Object.entries(exportsObj)) {
1241
1305
  try {
1242
1306
  const cols = this.getSymbol(val, "drizzle:Columns");
1243
1307
  if (cols && typeof cols === "object") {
1244
1308
  const table = this.analyzeTable(name, val, issues);
1245
1309
  tables.push(table);
1310
+ tableSources.set(val, table);
1246
1311
  if (isDrizzleView(val)) viewTables.push(table);
1247
1312
  for (const col of table.columns) {
1248
1313
  const enumVals = cols[col.name]?.enumValues;
@@ -1266,6 +1331,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1266
1331
  if (opts.includeRelations) {
1267
1332
  relations.push(...readRelationsV2(val, issues));
1268
1333
  }
1334
+ } else if (isPolicy(val)) {
1335
+ linkedPolicies.push(val);
1269
1336
  } else {
1270
1337
  const ev = val?.enumValues;
1271
1338
  if (Array.isArray(ev) && ev.every((x) => typeof x === "string")) {
@@ -1294,6 +1361,22 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1294
1361
  if (enums.some((e) => e.name === candidate.name)) continue;
1295
1362
  enums.push(candidate);
1296
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
+ }
1297
1380
  let dialect = "unknown";
1298
1381
  const marks = /* @__PURE__ */ new Set();
1299
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.4",
3
+ "version": "1.22.0",
4
4
  "private": false,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",