@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 +84 -1
- package/dist/index.d.cts +68 -1
- package/dist/index.d.ts +68 -1
- package/dist/index.js +84 -1
- package/package.json +1 -1
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
|
|
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
|
|
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)) {
|