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