orchid-orm 1.70.0 → 1.71.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.
@@ -1336,28 +1336,42 @@ const dropCheck = ({ changeTableAst: { drop }, changingColumns }, dbCheck, name)
1336
1336
  };
1337
1337
  const defaultRlsState = {
1338
1338
  enable: false,
1339
- force: false
1339
+ force: true
1340
1340
  };
1341
1341
  const normalizeRlsFlag = (value, defaultValue) => {
1342
1342
  if (value === void 0) return defaultValue;
1343
1343
  return value === true || value === "true" || value === "t";
1344
1344
  };
1345
- const processTableRls = (ast, dbStructure, tables, currentSchema) => {
1345
+ const processTableRls = async (adapter, ast, dbStructure, tables, currentSchema, generatorIgnore) => {
1346
1346
  const projectRlsDefaults = tables[0]?.internal.rls?.tableRlsDefaults;
1347
+ const ignore = generatorIgnore;
1348
+ const ignoredTableSet = toIgnoredTableSet(ignore?.tables, currentSchema);
1349
+ const ignoredRlsTableSet = toIgnoredTableSet(ignore?.rls?.tables, currentSchema);
1350
+ const ignoredPolicyMap = toIgnoredPolicyMap(ignore?.rls?.policies, currentSchema);
1347
1351
  for (const table of tables) {
1348
1352
  const tableRls = table.internal.tableRls;
1349
1353
  if (!tableRls) continue;
1350
1354
  const schemaName = table.q.schema ?? currentSchema;
1355
+ const tableId = `${schemaName}.${table.table}`;
1356
+ if (ignoredTableSet.has(tableId) || ignoredRlsTableSet.has(tableId)) continue;
1351
1357
  const dbTable = dbStructure.tables.find((item) => item.schemaName === schemaName && item.name === table.table);
1352
- if (!dbTable) continue;
1358
+ const ignoredPolicyNames = ignoredPolicyMap.get(tableId);
1353
1359
  const codeRls = {
1354
1360
  enable: normalizeRlsFlag(tableRls.enable ?? projectRlsDefaults?.enable, defaultRlsState.enable),
1355
1361
  force: normalizeRlsFlag(tableRls.force ?? projectRlsDefaults?.force, defaultRlsState.force)
1356
1362
  };
1357
1363
  const dbRls = {
1358
- enable: normalizeRlsFlag(dbTable.rls?.enable, defaultRlsState.enable),
1359
- force: normalizeRlsFlag(dbTable.rls?.force, defaultRlsState.force)
1364
+ enable: normalizeRlsFlag(dbTable?.rls?.enable, defaultRlsState.enable),
1365
+ force: normalizeRlsFlag(dbTable?.rls?.force, false)
1360
1366
  };
1367
+ const policyExpressionComparisons = [];
1368
+ const policyChanges = collectPolicyChanges(schemaName, table.table, normalizeCodePolicies(tableRls, ignoredPolicyNames), normalizeDbPolicies(dbTable?.rls?.policies, ignoredPolicyNames), policyExpressionComparisons, (0, rake_db.concatSchemaAndName)({
1369
+ schema: schemaName,
1370
+ name: table.table
1371
+ }));
1372
+ if (policyExpressionComparisons.length) await compareSqlExpressions(policyExpressionComparisons, adapter);
1373
+ const disableFirst = !codeRls.enable && dbRls.enable;
1374
+ if (!disableFirst) pushPolicyChanges(ast, policyChanges);
1361
1375
  if (codeRls.enable !== dbRls.enable) ast.push({
1362
1376
  type: "tableRls",
1363
1377
  action: codeRls.enable ? "enable" : "disable",
@@ -1370,7 +1384,250 @@ const processTableRls = (ast, dbStructure, tables, currentSchema) => {
1370
1384
  schema: schemaName,
1371
1385
  table: table.table
1372
1386
  });
1387
+ if (disableFirst) pushPolicyChanges(ast, policyChanges);
1388
+ }
1389
+ };
1390
+ const toIgnoredTableSet = (tables, currentSchema) => {
1391
+ const ignored = /* @__PURE__ */ new Set();
1392
+ if (!tables) return ignored;
1393
+ for (const name of tables) {
1394
+ const [schema = currentSchema, table] = (0, rake_db.getSchemaAndTableFromName)(currentSchema, name);
1395
+ ignored.add(`${schema}.${table}`);
1396
+ }
1397
+ return ignored;
1398
+ };
1399
+ const toIgnoredPolicyMap = (items, currentSchema) => {
1400
+ const result = /* @__PURE__ */ new Map();
1401
+ if (!items) return result;
1402
+ for (const item of items) {
1403
+ const [schema = currentSchema, table] = (0, rake_db.getSchemaAndTableFromName)(currentSchema, item.table);
1404
+ const key = `${schema}.${table}`;
1405
+ let set = result.get(key);
1406
+ if (!set) {
1407
+ set = /* @__PURE__ */ new Set();
1408
+ result.set(key, set);
1409
+ }
1410
+ for (const name of item.names) set.add(name);
1411
+ }
1412
+ return result;
1413
+ };
1414
+ const normalizeCodePolicies = (tableRls, ignoredPolicyNames) => {
1415
+ const result = [];
1416
+ const permit = tableRls.permit ?? [];
1417
+ const restrict = tableRls.restrict ?? [];
1418
+ for (const policy of permit) {
1419
+ const normalized = normalizeCodePolicy("PERMISSIVE", policy);
1420
+ if (normalized && !ignoredPolicyNames?.has(normalized.name)) result.push(normalized);
1421
+ }
1422
+ for (const policy of restrict) {
1423
+ const normalized = normalizeCodePolicy("RESTRICTIVE", policy);
1424
+ if (normalized && !ignoredPolicyNames?.has(normalized.name)) result.push(normalized);
1373
1425
  }
1426
+ return result;
1427
+ };
1428
+ const normalizeCodePolicy = (as, policy) => {
1429
+ if (!policy?.name) return;
1430
+ return {
1431
+ name: policy.name,
1432
+ as,
1433
+ for: policy.for ?? "ALL",
1434
+ to: normalizePolicyRoles(policy.to),
1435
+ using: toSqlText(policy.using),
1436
+ withCheck: toSqlText(policy.withCheck)
1437
+ };
1438
+ };
1439
+ const normalizeDbPolicies = (policies, ignoredPolicyNames) => {
1440
+ if (!policies) return [];
1441
+ const result = [];
1442
+ for (const policy of policies) {
1443
+ if (ignoredPolicyNames?.has(policy.name)) continue;
1444
+ result.push({
1445
+ name: policy.name,
1446
+ as: policy.mode,
1447
+ for: policy.command,
1448
+ to: normalizePolicyRoles(policy.roles),
1449
+ using: policy.using,
1450
+ withCheck: policy.withCheck
1451
+ });
1452
+ }
1453
+ return result;
1454
+ };
1455
+ const normalizePolicyRoles = (roles) => {
1456
+ const result = (Array.isArray(roles) ? roles : roles ? [roles] : ["public"]).map((role) => role.toLowerCase() === "public" ? "public" : role);
1457
+ return result.length ? result : ["public"];
1458
+ };
1459
+ const toSqlText = (value) => {
1460
+ if (!value) return;
1461
+ return value.toSQL({ values: [] });
1462
+ };
1463
+ const collectPolicyChanges = (schema, table, codePolicies, dbPolicies, compareExpressions, source) => {
1464
+ const changes = [];
1465
+ const dbPolicyMap = /* @__PURE__ */ new Map();
1466
+ for (const policy of dbPolicies) dbPolicyMap.set(policy.name, policy);
1467
+ const codePolicyMap = /* @__PURE__ */ new Map();
1468
+ for (const policy of codePolicies) codePolicyMap.set(policy.name, policy);
1469
+ const unmatchedCodePolicies = [];
1470
+ const unmatchedDbPolicies = [];
1471
+ for (const policy of codePolicies) {
1472
+ const dbPolicy = dbPolicyMap.get(policy.name);
1473
+ if (!dbPolicy) {
1474
+ unmatchedCodePolicies.push(policy);
1475
+ continue;
1476
+ }
1477
+ const recreate = policy.as !== dbPolicy.as || policy.for !== dbPolicy.for;
1478
+ const toChanged = !isSameStringArray(policy.to, dbPolicy.to);
1479
+ const from = {};
1480
+ const to = {};
1481
+ const codeUsing = policy.using;
1482
+ const dbUsing = dbPolicy.using;
1483
+ const codeWithCheck = policy.withCheck;
1484
+ const dbWithCheck = dbPolicy.withCheck;
1485
+ const hasUsing = codeUsing !== void 0 || dbUsing !== void 0;
1486
+ const hasWithCheck = codeWithCheck !== void 0 || dbWithCheck !== void 0;
1487
+ if (toChanged) {
1488
+ from.to = dbPolicy.to;
1489
+ to.to = policy.to;
1490
+ }
1491
+ const usingCanCompare = codeUsing !== void 0 && dbUsing !== void 0;
1492
+ const withCheckCanCompare = codeWithCheck !== void 0 && dbWithCheck !== void 0;
1493
+ const applyExpressionDiff = (matched) => {
1494
+ const usingChanged = usingCanCompare ? !matched : hasUsing && !usingCanCompare;
1495
+ const withCheckChanged = withCheckCanCompare ? !matched : hasWithCheck && !withCheckCanCompare;
1496
+ if (usingChanged) {
1497
+ from.using = dbPolicy.using;
1498
+ to.using = policy.using;
1499
+ }
1500
+ if (withCheckChanged) {
1501
+ from.withCheck = dbPolicy.withCheck;
1502
+ to.withCheck = policy.withCheck;
1503
+ }
1504
+ if (recreate) {
1505
+ changes.push({
1506
+ type: "changePolicy",
1507
+ schema,
1508
+ table,
1509
+ name: policy.name,
1510
+ from: {
1511
+ as: dbPolicy.as,
1512
+ for: dbPolicy.for,
1513
+ to: dbPolicy.to,
1514
+ using: dbPolicy.using,
1515
+ withCheck: dbPolicy.withCheck
1516
+ },
1517
+ to: {
1518
+ as: policy.as,
1519
+ for: policy.for,
1520
+ to: policy.to,
1521
+ using: policy.using,
1522
+ withCheck: policy.withCheck
1523
+ }
1524
+ });
1525
+ return;
1526
+ }
1527
+ if (!Object.keys(from).length) return;
1528
+ changes.push({
1529
+ type: "changePolicy",
1530
+ schema,
1531
+ table,
1532
+ name: policy.name,
1533
+ from,
1534
+ to
1535
+ });
1536
+ };
1537
+ if (!hasUsing && !hasWithCheck) {
1538
+ applyExpressionDiff(true);
1539
+ continue;
1540
+ }
1541
+ const compare = [];
1542
+ if (usingCanCompare) compare.push({
1543
+ inDb: dbUsing,
1544
+ inCode: [codeUsing]
1545
+ });
1546
+ if (withCheckCanCompare) compare.push({
1547
+ inDb: dbWithCheck,
1548
+ inCode: [codeWithCheck]
1549
+ });
1550
+ if (!compare.length) {
1551
+ applyExpressionDiff(false);
1552
+ continue;
1553
+ }
1554
+ compareExpressions.push({
1555
+ source,
1556
+ compare,
1557
+ handle(i) {
1558
+ applyExpressionDiff(i !== void 0);
1559
+ }
1560
+ });
1561
+ }
1562
+ for (const policy of dbPolicies) {
1563
+ if (codePolicyMap.has(policy.name)) continue;
1564
+ unmatchedDbPolicies.push(policy);
1565
+ }
1566
+ const pairedDbPolicyNames = /* @__PURE__ */ new Set();
1567
+ const pairedCodePolicyNames = /* @__PURE__ */ new Set();
1568
+ for (const policy of unmatchedCodePolicies) {
1569
+ const renamedFrom = unmatchedDbPolicies.find((item) => !pairedDbPolicyNames.has(item.name) && item.as === policy.as && item.for === policy.for);
1570
+ if (!renamedFrom) continue;
1571
+ pairedDbPolicyNames.add(renamedFrom.name);
1572
+ pairedCodePolicyNames.add(policy.name);
1573
+ changes.push({
1574
+ type: "changePolicy",
1575
+ schema,
1576
+ table,
1577
+ name: renamedFrom.name,
1578
+ from: {
1579
+ name: renamedFrom.name,
1580
+ to: renamedFrom.to,
1581
+ using: renamedFrom.using,
1582
+ withCheck: renamedFrom.withCheck
1583
+ },
1584
+ to: {
1585
+ name: policy.name,
1586
+ to: policy.to,
1587
+ using: policy.using,
1588
+ withCheck: policy.withCheck
1589
+ }
1590
+ });
1591
+ }
1592
+ for (const policy of unmatchedCodePolicies) {
1593
+ if (pairedCodePolicyNames.has(policy.name)) continue;
1594
+ changes.push({
1595
+ type: "policy",
1596
+ action: "create",
1597
+ schema,
1598
+ table,
1599
+ name: policy.name,
1600
+ as: policy.as,
1601
+ for: policy.for,
1602
+ to: policy.to,
1603
+ using: policy.using,
1604
+ withCheck: policy.withCheck
1605
+ });
1606
+ }
1607
+ for (const policy of unmatchedDbPolicies) {
1608
+ if (pairedDbPolicyNames.has(policy.name)) continue;
1609
+ changes.push({
1610
+ type: "policy",
1611
+ action: "drop",
1612
+ schema,
1613
+ table,
1614
+ name: policy.name,
1615
+ as: policy.as,
1616
+ for: policy.for,
1617
+ to: policy.to,
1618
+ using: policy.using,
1619
+ withCheck: policy.withCheck
1620
+ });
1621
+ }
1622
+ return changes;
1623
+ };
1624
+ const pushPolicyChanges = (ast, changes) => {
1625
+ for (const change of changes) ast.push(change);
1626
+ };
1627
+ const isSameStringArray = (a, b) => {
1628
+ if (a.length !== b.length) return false;
1629
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
1630
+ return true;
1374
1631
  };
1375
1632
  const processTables = async (ast, domainsMap, adapter, dbStructure, config, { structureToAstCtx, codeItems: { tables }, currentSchema, internal: { generatorIgnore }, verifying }, pendingDbTypes) => {
1376
1633
  const createTables = collectCreateTables(tables, dbStructure, currentSchema);
@@ -1385,7 +1642,7 @@ const processTables = async (ast, domainsMap, adapter, dbStructure, config, { st
1385
1642
  await applyChangeTables(adapter, changeTables, structureToAstCtx, dbStructure, domainsMap, ast, currentSchema, config, compareSql, tableExpressions, verifying, pendingDbTypes);
1386
1643
  processForeignKeys(config, ast, changeTables, currentSchema, tableShapes);
1387
1644
  await Promise.all([applyCompareSql(compareSql, adapter), compareSqlExpressions(tableExpressions, adapter)]);
1388
- processTableRls(ast, dbStructure, tables, currentSchema);
1645
+ await processTableRls(adapter, ast, dbStructure, tables, currentSchema, generatorIgnore);
1389
1646
  for (const dbTable of dropTables) ast.push((0, rake_db.tableToAst)(structureToAstCtx, dbStructure, dbTable, "drop", domainsMap));
1390
1647
  };
1391
1648
  const collectCreateTables = (tables, dbStructure, currentSchema) => {
@@ -1745,7 +2002,7 @@ const privilegeConfigsMatch = (a, b, objectType, objectTypeToAllPrivileges) => {
1745
2002
  if (b.privilege === "ALL" && a.isGrantable === b.isGrantable) return expectedPrivs.includes(a.privilege);
1746
2003
  return false;
1747
2004
  };
1748
- const normalizeRoleName = (name) => {
2005
+ const normalizeRoleName$1 = (name) => {
1749
2006
  if (name.startsWith("\"") && name.endsWith("\"")) return name.slice(1, -1);
1750
2007
  return name;
1751
2008
  };
@@ -1815,7 +2072,7 @@ const processDefaultPrivileges = (ast, dbStructure, { internal: { roles } }) =>
1815
2072
  }
1816
2073
  const found = /* @__PURE__ */ new Set();
1817
2074
  for (const dbPrivilege of dbStructure.defaultPrivileges) {
1818
- const grantee = normalizeRoleName(dbPrivilege.grantee);
2075
+ const grantee = normalizeRoleName$1(dbPrivilege.grantee);
1819
2076
  if (grantee === "postgres") continue;
1820
2077
  const key = `${grantee}.${dbPrivilege.schema}`;
1821
2078
  const codePrivilege = codePrivileges.get(key);
@@ -1891,6 +2148,280 @@ const processDefaultPrivileges = (ast, dbStructure, { internal: { roles } }) =>
1891
2148
  });
1892
2149
  }
1893
2150
  };
2151
+ const targetKeys = [
2152
+ "schemas",
2153
+ "tables",
2154
+ "sequences",
2155
+ "routines",
2156
+ "types",
2157
+ "domains",
2158
+ "databases"
2159
+ ];
2160
+ const schemaWideTargetKeys = [
2161
+ "allTablesIn",
2162
+ "allSequencesIn",
2163
+ "allRoutinesIn"
2164
+ ];
2165
+ const supportedPrivileges = {
2166
+ schemas: ["USAGE", "CREATE"],
2167
+ tables: [
2168
+ "SELECT",
2169
+ "INSERT",
2170
+ "UPDATE",
2171
+ "DELETE",
2172
+ "TRUNCATE",
2173
+ "REFERENCES",
2174
+ "TRIGGER",
2175
+ "MAINTAIN"
2176
+ ],
2177
+ sequences: [
2178
+ "USAGE",
2179
+ "SELECT",
2180
+ "UPDATE"
2181
+ ],
2182
+ routines: ["EXECUTE"],
2183
+ types: ["USAGE"],
2184
+ domains: ["USAGE"],
2185
+ databases: [
2186
+ "CREATE",
2187
+ "CONNECT",
2188
+ "TEMPORARY"
2189
+ ]
2190
+ };
2191
+ const schemaWideToConcreteTarget = {
2192
+ allTablesIn: "tables",
2193
+ allSequencesIn: "sequences",
2194
+ allRoutinesIn: "routines"
2195
+ };
2196
+ const processGrants = (ast, dbStructure, params) => {
2197
+ const { grants } = params.internal;
2198
+ if (!grants || !dbStructure.grants) return;
2199
+ const codeStates = collectCodeGrants(dbStructure, params).filter((grant) => !isIgnoredGrant(grant, params));
2200
+ const dbStates = collectDbGrants(dbStructure, params.currentSchema).filter((grant) => !isIgnoredGrant(grant, params));
2201
+ const managedTargets = getManagedGrantTargets(params);
2202
+ for (const code of codeStates) {
2203
+ const actual = dbStates.filter((db) => isSameGrantTarget(code, db));
2204
+ addGrantAst(ast, "grant", code, missingPrivileges(code.grantablePrivileges, actual, "grantablePrivileges"), "grantablePrivileges");
2205
+ addGrantAst(ast, "revoke", code, grantOptionsToRevoke(code.privileges, code.grantablePrivileges, actual), "grantablePrivileges");
2206
+ addGrantAst(ast, "grant", code, missingPrivileges(code.privileges, actual, "privileges"), "privileges");
2207
+ }
2208
+ for (const actual of dbStates) {
2209
+ const configured = codeStates.filter((code) => isSameGrantTarget(code, actual));
2210
+ if (!shouldRevokeActualGrant(actual, configured, managedTargets)) continue;
2211
+ const revokeGrant = getRevokeGrantState(actual, configured);
2212
+ addGrantAst(ast, "revoke", revokeGrant, privilegesToRevoke(actual.privileges, configured), "privileges");
2213
+ addGrantAst(ast, "revoke", revokeGrant, privilegesToRevoke(actual.grantablePrivileges, configured), "grantablePrivileges");
2214
+ }
2215
+ };
2216
+ const getManagedGrantTargets = ({ codeItems, currentSchema }) => {
2217
+ const targets = {
2218
+ tables: /* @__PURE__ */ new Set(),
2219
+ domains: /* @__PURE__ */ new Set(),
2220
+ types: /* @__PURE__ */ new Set()
2221
+ };
2222
+ for (const table of codeItems.tables) targets.tables.add(`${table.q.schema ?? currentSchema}.${table.table}`);
2223
+ for (const domain of codeItems.domains) targets.domains.add(`${domain.schemaName}.${domain.name}`);
2224
+ for (const enumItem of codeItems.enums.values()) targets.types.add(`${enumItem.schema ?? currentSchema}.${enumItem.name}`);
2225
+ return targets;
2226
+ };
2227
+ const shouldRevokeActualGrant = (actual, configured, managedTargets) => {
2228
+ if (configured.length) return true;
2229
+ if (actual.targetKey === "tables") return managedTargets.tables.has(actual.target);
2230
+ if (actual.targetKey === "domains") return managedTargets.domains.has(actual.target);
2231
+ if (actual.targetKey === "types") return managedTargets.types.has(actual.target);
2232
+ return false;
2233
+ };
2234
+ const getRevokeGrantState = (actual, configured) => {
2235
+ if (configured.some((grant) => grant.grantedBy)) return actual;
2236
+ return {
2237
+ ...actual,
2238
+ grantedBy: void 0
2239
+ };
2240
+ };
2241
+ const collectCodeGrants = (dbStructure, { currentSchema, internal }) => {
2242
+ const states = [];
2243
+ for (const grant of internal.grants ?? []) {
2244
+ for (const targetKey of targetKeys) {
2245
+ const values = grant[targetKey];
2246
+ if (!values?.length) continue;
2247
+ addStates(states, withEffectiveGrantor(grant, internal), targetKey, values, currentSchema);
2248
+ }
2249
+ for (const targetKey of schemaWideTargetKeys) {
2250
+ const values = grant[targetKey];
2251
+ if (!values?.length) continue;
2252
+ const concreteTargetKey = schemaWideToConcreteTarget[targetKey];
2253
+ addStates(states, withEffectiveGrantor(grant, internal), concreteTargetKey, getSchemaWideTargets(dbStructure, concreteTargetKey, values), currentSchema);
2254
+ }
2255
+ }
2256
+ return states;
2257
+ };
2258
+ const collectDbGrants = (dbStructure, currentSchema) => {
2259
+ const states = [];
2260
+ for (const grant of dbStructure.grants ?? []) for (const targetKey of targetKeys) {
2261
+ const values = grant[targetKey];
2262
+ if (!values?.length) continue;
2263
+ addStates(states, grant, targetKey, values, currentSchema);
2264
+ }
2265
+ return states;
2266
+ };
2267
+ const withEffectiveGrantor = (grant, internal) => {
2268
+ return {
2269
+ ...grant,
2270
+ grantedBy: grant.grantedBy ?? internal.defaultGrantedBy
2271
+ };
2272
+ };
2273
+ const isIgnoredGrant = (grant, { currentSchema, internal: { generatorIgnore } }) => {
2274
+ const { grants } = generatorIgnore ?? {};
2275
+ if (matchesSelector(grants?.roles, grant.to)) return true;
2276
+ const names = getGrantTargetNames(grant, currentSchema);
2277
+ if (matchesSelector(grants?.[grant.targetKey], names)) return true;
2278
+ if (grant.targetKey === "tables" && names.schema && matchesSelector(grants?.allTablesIn, names.schema)) return true;
2279
+ if (grant.targetKey === "sequences" && names.schema && matchesSelector(grants?.allSequencesIn, names.schema)) return true;
2280
+ if (grant.targetKey === "routines" && names.schema && matchesSelector(grants?.allRoutinesIn, names.schema)) return true;
2281
+ return isTopLevelIgnoredGrant(grant, names, generatorIgnore);
2282
+ };
2283
+ const isTopLevelIgnoredGrant = (grant, names, generatorIgnore) => {
2284
+ if (grant.targetKey === "schemas") return !!generatorIgnore?.schemas?.includes(grant.target);
2285
+ if (names.schema && generatorIgnore?.schemas?.includes(names.schema)) return true;
2286
+ if (grant.targetKey === "tables") return isIgnoredByName(generatorIgnore?.tables, names);
2287
+ if (grant.targetKey === "domains") return isIgnoredByName(generatorIgnore?.domains, names);
2288
+ return false;
2289
+ };
2290
+ const getGrantTargetNames = (grant, currentSchema) => {
2291
+ if (grant.targetKey === "schemas" || grant.targetKey === "databases") return {
2292
+ name: grant.target,
2293
+ qualified: grant.target
2294
+ };
2295
+ const [schema, name] = (0, rake_db.getSchemaAndTableFromName)(currentSchema, grant.target);
2296
+ return {
2297
+ schema,
2298
+ name,
2299
+ qualified: schema ? `${schema}.${name}` : name
2300
+ };
2301
+ };
2302
+ const isIgnoredByName = (ignored, names) => {
2303
+ return !!ignored?.some((name) => name === names.qualified || name === names.name || (names.schema ? name === `${names.schema}.${names.name}` : false));
2304
+ };
2305
+ const matchesSelector = (selector, value) => {
2306
+ if (!selector) return false;
2307
+ const values = typeof value === "string" ? [value] : [
2308
+ value.qualified,
2309
+ value.name,
2310
+ value.schema
2311
+ ].filter(isString);
2312
+ return (Array.isArray(selector) ? selector : [selector]).some((item) => values.some((name) => typeof item === "string" ? item === name : item.test(name)));
2313
+ };
2314
+ const isString = (value) => {
2315
+ return !!value;
2316
+ };
2317
+ const addStates = (states, grant, targetKey, values, currentSchema) => {
2318
+ for (const to of grant.to) for (const value of values) {
2319
+ const target = typeof value === "string" ? normalizeTarget(targetKey, value, currentSchema) : value.target;
2320
+ addOrMergeState(states, {
2321
+ targetKey,
2322
+ target,
2323
+ outputTarget: typeof value === "string" ? normalizeOutputTarget(target, currentSchema) : value.outputTarget,
2324
+ to: normalizeRoleName(to),
2325
+ grantedBy: grant.grantedBy ? normalizeRoleName(grant.grantedBy) : void 0,
2326
+ privileges: expandPrivileges(targetKey, grant.privileges),
2327
+ grantablePrivileges: expandPrivileges(targetKey, grant.grantablePrivileges)
2328
+ });
2329
+ }
2330
+ };
2331
+ const addOrMergeState = (states, state) => {
2332
+ const existing = states.find((item) => isSameExactGrantTarget(item, state));
2333
+ if (!existing) {
2334
+ removeGrantableFromOrdinary(state);
2335
+ states.push(state);
2336
+ return;
2337
+ }
2338
+ for (const privilege of state.privileges) if (!existing.grantablePrivileges.has(privilege)) existing.privileges.add(privilege);
2339
+ for (const privilege of state.grantablePrivileges) {
2340
+ existing.grantablePrivileges.add(privilege);
2341
+ existing.privileges.delete(privilege);
2342
+ }
2343
+ };
2344
+ const removeGrantableFromOrdinary = (state) => {
2345
+ for (const privilege of state.grantablePrivileges) state.privileges.delete(privilege);
2346
+ };
2347
+ const normalizeRoleName = (name) => {
2348
+ return name.startsWith("\"") && name.endsWith("\"") ? name.slice(1, -1) : name;
2349
+ };
2350
+ const getSchemaWideTargets = (dbStructure, targetKey, schemas) => {
2351
+ const selectedSchemas = new Set(schemas);
2352
+ if (targetKey === "tables") return [...dbStructure.tables.map((table) => ({
2353
+ target: `${table.schemaName}.${table.name}`,
2354
+ outputTarget: `${table.schemaName}.${table.name}`
2355
+ })), ...dbStructure.views.map((view) => ({
2356
+ target: `${view.schemaName}.${view.name}`,
2357
+ outputTarget: `${view.schemaName}.${view.name}`
2358
+ }))].filter((item) => selectedSchemas.has(item.target.split(".")[0]));
2359
+ if (targetKey === "sequences" || targetKey === "routines") return getSchemaWideGrantTargets(dbStructure, targetKey, selectedSchemas);
2360
+ return [];
2361
+ };
2362
+ const getSchemaWideGrantTargets = (dbStructure, targetKey, selectedSchemas) => {
2363
+ const targets = /* @__PURE__ */ new Map();
2364
+ for (const grant of dbStructure.grants ?? []) for (const target of grant[targetKey] ?? []) {
2365
+ const [schema] = target.split(".");
2366
+ if (!selectedSchemas.has(schema)) continue;
2367
+ targets.set(target, {
2368
+ target,
2369
+ outputTarget: target
2370
+ });
2371
+ }
2372
+ return [...targets.values()];
2373
+ };
2374
+ const normalizeTarget = (targetKey, value, currentSchema) => {
2375
+ if (targetKey === "schemas" || targetKey === "databases") return value;
2376
+ const [schema, name] = (0, rake_db.getSchemaAndTableFromName)(currentSchema, value);
2377
+ return `${schema ?? currentSchema}.${name}`;
2378
+ };
2379
+ const normalizeOutputTarget = (target, currentSchema) => {
2380
+ const [schema, name] = target.split(".");
2381
+ if (!name) return target;
2382
+ return schema === currentSchema ? name : target;
2383
+ };
2384
+ const expandPrivileges = (targetKey, privileges) => {
2385
+ const set = /* @__PURE__ */ new Set();
2386
+ for (const privilege of privileges ?? []) if (privilege === "ALL") for (const supported of supportedPrivileges[targetKey]) set.add(supported);
2387
+ else set.add(privilege === "TEMP" ? "TEMPORARY" : privilege);
2388
+ return set;
2389
+ };
2390
+ const isSameGrantTarget = (a, b) => {
2391
+ return a.targetKey === b.targetKey && a.target === b.target && a.to === b.to && (!a.grantedBy || !b.grantedBy || a.grantedBy === b.grantedBy);
2392
+ };
2393
+ const isSameExactGrantTarget = (a, b) => {
2394
+ return a.targetKey === b.targetKey && a.target === b.target && a.to === b.to && a.grantedBy === b.grantedBy;
2395
+ };
2396
+ const missingPrivileges = (expected, actual, key) => {
2397
+ const result = [];
2398
+ for (const privilege of expected) if (!actual.some((state) => state[key].has(privilege))) result.push(privilege);
2399
+ return result;
2400
+ };
2401
+ const grantOptionsToRevoke = (expectedOrdinary, expectedGrantable, actual) => {
2402
+ const result = [];
2403
+ for (const privilege of expectedOrdinary) {
2404
+ if (expectedGrantable.has(privilege)) continue;
2405
+ if (actual.some((state) => state.grantablePrivileges.has(privilege))) result.push(privilege);
2406
+ }
2407
+ return result;
2408
+ };
2409
+ const privilegesToRevoke = (actual, configured) => {
2410
+ const result = [];
2411
+ for (const privilege of actual) if (!configured.some((state) => state.privileges.has(privilege) || state.grantablePrivileges.has(privilege))) result.push(privilege);
2412
+ return result;
2413
+ };
2414
+ const addGrantAst = (ast, action, grant, privileges, privilegeKey) => {
2415
+ if (!privileges.length) return;
2416
+ ast.push({
2417
+ type: "grant",
2418
+ action,
2419
+ to: [grant.to],
2420
+ [grant.targetKey]: [grant.outputTarget],
2421
+ [privilegeKey]: privileges,
2422
+ grantedBy: grant.grantedBy
2423
+ });
2424
+ };
1894
2425
  /**
1895
2426
  * This is needed to compare SQLs of table expressions.
1896
2427
  * Need to exclude table columns of pending types, such as enums or domains,
@@ -1909,6 +2440,7 @@ const composeMigration = async (adapter, config, ast, dbStructure, params) => {
1909
2440
  const { structureToAstCtx, currentSchema } = params;
1910
2441
  await processRoles(ast, dbStructure, params);
1911
2442
  processDefaultPrivileges(ast, dbStructure, params);
2443
+ processGrants(ast, dbStructure, params);
1912
2444
  const domainsMap = (0, rake_db.makeDomainsMap)(structureToAstCtx, dbStructure);
1913
2445
  await processSchemas(ast, dbStructure, params);
1914
2446
  processExtensions(ast, dbStructure, params);
@@ -1919,7 +2451,7 @@ const composeMigration = async (adapter, config, ast, dbStructure, params) => {
1919
2451
  return (0, rake_db.astToMigration)(currentSchema, config, ast);
1920
2452
  };
1921
2453
  const rollbackErr = /* @__PURE__ */ new Error("Rollback");
1922
- const verifyMigration = async (adapter, config, migrationCode, generateMigrationParams, roles, defaultPrivileges) => {
2454
+ const verifyMigration = async (adapter, config, migrationCode, generateMigrationParams, roles, structureParams) => {
1923
2455
  const migrationFn = new Function("change", migrationCode);
1924
2456
  let code;
1925
2457
  try {
@@ -1936,7 +2468,7 @@ const verifyMigration = async (adapter, config, migrationCode, generateMigration
1936
2468
  const dbStructure = await (0, rake_db.introspectDbSchema)(trx, {
1937
2469
  rls: generateMigrationParams.codeItems.tables.some((table) => !!table.internal.tableRls),
1938
2470
  roles,
1939
- loadDefaultPrivileges: defaultPrivileges?.loadDefaultPrivileges
2471
+ ...structureParams
1940
2472
  });
1941
2473
  generateMigrationParams.verifying = true;
1942
2474
  try {
@@ -2122,6 +2654,14 @@ const report = (ast, config, currentSchema) => {
2122
2654
  if (parts.length) code.push(parts.join("\n"));
2123
2655
  break;
2124
2656
  }
2657
+ case "grant": {
2658
+ const parts = [];
2659
+ const target = grantTargetToReport(a, currentSchema);
2660
+ if (a.privileges?.length) parts.push(`${a.action === "grant" ? green("+ grant privileges") : red("- revoke privileges")} ${a.privileges.map(mapGrantPrivilege).join(", ")} on ${target} ${a.action === "grant" ? "to" : "from"} ${a.to.join(", ")}`);
2661
+ if (a.grantablePrivileges?.length) parts.push(`${a.action === "grant" ? green("+ grant privileges") : red("- revoke privileges")} ${a.grantablePrivileges.map(mapGrantPrivilege).join(", ")} on ${target} with grant option ${a.action === "grant" ? "to" : "from"} ${a.to.join(", ")}`);
2662
+ if (parts.length) code.push(parts.join("\n"));
2663
+ break;
2664
+ }
2125
2665
  case "tableRls": {
2126
2666
  const table = dbItemName({
2127
2667
  schema: a.schema,
@@ -2131,6 +2671,28 @@ const report = (ast, config, currentSchema) => {
2131
2671
  code.push(message);
2132
2672
  break;
2133
2673
  }
2674
+ case "policy": {
2675
+ const summary = formatPolicySummary(a, dbItemName({
2676
+ schema: a.schema,
2677
+ name: a.table
2678
+ }, currentSchema));
2679
+ const message = a.action === "create" ? `${green("+ create policy")} ${a.name}: ${summary}` : `${red("- drop policy")} ${a.name}: ${summary}`;
2680
+ code.push(message);
2681
+ break;
2682
+ }
2683
+ case "changePolicy": {
2684
+ const table = dbItemName({
2685
+ schema: a.schema,
2686
+ name: a.table
2687
+ }, currentSchema);
2688
+ const fromName = a.from.name ?? a.name;
2689
+ const toName = a.to.name ?? a.name;
2690
+ const message = fromName === toName ? `${yellow("~ change policy")} ${a.name} on ${table}:` : `${yellow("~ rename policy")} ${fromName} ${yellow("=>")} ${toName} on ${table}:`;
2691
+ const inner = [`${yellow("from")}: ${formatPolicyChangeDefinition(a.from)}`, `${yellow("to")}: ${formatPolicyChangeDefinition(a.to)}`];
2692
+ code.push(message);
2693
+ code.push(inner);
2694
+ break;
2695
+ }
2134
2696
  default: (0, pqb_internal.exhaustive)(a);
2135
2697
  }
2136
2698
  const result = (0, pqb_internal.codeToString)(code, "", " ");
@@ -2139,6 +2701,64 @@ const report = (ast, config, currentSchema) => {
2139
2701
  const dbItemName = ({ schema, name }, currentSchema) => {
2140
2702
  return schema && schema !== currentSchema ? `${schema}.${name}` : name;
2141
2703
  };
2704
+ const grantTargetKeys = [
2705
+ "schemas",
2706
+ "tables",
2707
+ "sequences",
2708
+ "routines",
2709
+ "types",
2710
+ "domains",
2711
+ "databases"
2712
+ ];
2713
+ const mapGrantPrivilege = (privilege) => {
2714
+ return privilege === "ALL" ? "ALL PRIVILEGES" : privilege;
2715
+ };
2716
+ const grantTargetToReport = (grant, currentSchema) => {
2717
+ for (const key of grantTargetKeys) {
2718
+ const targets = grant[key];
2719
+ if (!targets?.length) continue;
2720
+ return `${key} ${targets.map((target) => formatGrantTarget(key, target, currentSchema)).join(", ")}`;
2721
+ }
2722
+ return "unknown target";
2723
+ };
2724
+ const formatGrantTarget = (key, target, currentSchema) => {
2725
+ if (key === "schemas" || key === "databases") return target;
2726
+ const [schema, name] = (0, rake_db.getSchemaAndTableFromName)(currentSchema, target);
2727
+ return dbItemName({
2728
+ schema,
2729
+ name
2730
+ }, currentSchema);
2731
+ };
2732
+ const formatPolicyRoles = (roles) => {
2733
+ return roles?.length ? roles.join(", ") : "public";
2734
+ };
2735
+ const formatPolicyCommand = (command) => {
2736
+ return (command ?? "ALL").toLowerCase();
2737
+ };
2738
+ const formatPolicyMode = (mode) => {
2739
+ return mode === "PERMISSIVE" ? "permit" : "restrict";
2740
+ };
2741
+ const formatPolicySummary = (policy, table) => {
2742
+ const parts = [
2743
+ `${formatPolicyMode(policy.as)} access on ${table}`,
2744
+ `to ${formatPolicyRoles(policy.to)}`,
2745
+ `for ${formatPolicyCommand(policy.for)}`
2746
+ ];
2747
+ if (policy.using) parts.push(`using (${policy.using})`);
2748
+ if (policy.withCheck) parts.push(`with check (${policy.withCheck})`);
2749
+ return parts.join(", ");
2750
+ };
2751
+ const formatPolicyChangeDefinition = (policy) => {
2752
+ const parts = [];
2753
+ if (policy.name) parts.push(`name ${policy.name}`);
2754
+ if (policy.table) parts.push(`table ${policy.table}`);
2755
+ if (policy.as) parts.push(`as ${policy.as.toLowerCase()}`);
2756
+ if (policy.for) parts.push(`for ${policy.for.toLowerCase()}`);
2757
+ if ("to" in policy) parts.push(`to ${formatPolicyRoles(policy.to)}`);
2758
+ if ("using" in policy && policy.using) parts.push(`using (${policy.using})`);
2759
+ if ("withCheck" in policy && policy.withCheck) parts.push(`with check (${policy.withCheck})`);
2760
+ return parts.join(", ");
2761
+ };
2142
2762
  var AbortSignal = class extends Error {};
2143
2763
  const generate = async (adapters, config, args, afterPull) => {
2144
2764
  let { dbPath } = config;
@@ -2154,17 +2774,25 @@ const generate = async (adapters, config, args, afterPull) => {
2154
2774
  if (afterPull) adapters = [afterPull.adapter];
2155
2775
  const db = await getDbFromConfig(config, dbPath);
2156
2776
  const { columnTypes, internal } = db.$qb;
2777
+ const structureParams = {
2778
+ loadDefaultPrivileges: internal.roles?.some((role) => role.defaultPrivileges !== void 0) ?? false,
2779
+ loadGrants: !!internal.grants || hasCodeTablesWithGrants(db)
2780
+ };
2157
2781
  const rolesDbStructureParam = internal.roles ? internal.managedRolesSql ? { whereSql: internal.managedRolesSql } : pqb_internal.emptyObject : void 0;
2158
- const { dbStructure } = await migrateAndPullStructures(adapters, config, db, rolesDbStructureParam, internal.roles ? { loadDefaultPrivileges: true } : void 0, afterPull);
2782
+ const { dbStructure } = await migrateAndPullStructures(adapters, config, db, rolesDbStructureParam, structureParams, afterPull);
2159
2783
  const [adapter] = adapters;
2160
2784
  const adapterSchema = adapter.getSchema();
2161
2785
  const currentSchema = (typeof adapterSchema === "function" ? adapterSchema() : adapterSchema) ?? "public";
2162
2786
  const codeItems = await getActualItems(db, currentSchema, internal, columnTypes);
2787
+ const effectiveGrants = getEffectiveGrants(internal.grants, codeItems);
2163
2788
  const generateMigrationParams = {
2164
2789
  structureToAstCtx: (0, rake_db.makeStructureToAstCtx)(config, currentSchema),
2165
2790
  codeItems,
2166
2791
  currentSchema,
2167
- internal
2792
+ internal: {
2793
+ ...internal,
2794
+ grants: effectiveGrants
2795
+ }
2168
2796
  };
2169
2797
  const ast = [];
2170
2798
  let migrationCode;
@@ -2178,7 +2806,7 @@ const generate = async (adapters, config, args, afterPull) => {
2178
2806
  throw err;
2179
2807
  }
2180
2808
  if (migrationCode && !afterPull) {
2181
- const result = await verifyMigration(adapter, config, migrationCode, generateMigrationParams, rolesDbStructureParam, internal.roles ? { loadDefaultPrivileges: true } : void 0);
2809
+ const result = await verifyMigration(adapter, config, migrationCode, generateMigrationParams, rolesDbStructureParam, structureParams);
2182
2810
  if (result !== void 0) throw new Error(`Failed to verify generated migration: some of database changes were not applied properly. This is a bug, please open an issue, attach the following migration code:\n${migrationCode}${result === false ? "" : `\nAfter applying:\n${result}`}`);
2183
2811
  }
2184
2812
  const { logger } = config;
@@ -2207,7 +2835,7 @@ const getDbFromConfig = async (config, dbPath) => {
2207
2835
  if (!db?.$qb) throw new Error(`Unable to import OrchidORM instance as ${config.dbExportedAs ?? "db"} from ${config.dbPath}`);
2208
2836
  return db;
2209
2837
  };
2210
- const migrateAndPullStructures = async (adapters, config, db, roles, defaultPrivileges, afterPull) => {
2838
+ const migrateAndPullStructures = async (adapters, config, db, roles, structureParams, afterPull) => {
2211
2839
  if (afterPull) return { dbStructure: {
2212
2840
  version: await (0, rake_db.getDbVersion)(adapters[0]),
2213
2841
  schemas: [],
@@ -2226,7 +2854,8 @@ const migrateAndPullStructures = async (adapters, config, db, roles, defaultPriv
2226
2854
  const dbStructures = await Promise.all(adapters.map((adapter) => (0, rake_db.introspectDbSchema)(adapter, {
2227
2855
  rls: hasCodeTablesWithRls(db),
2228
2856
  roles,
2229
- loadDefaultPrivileges: defaultPrivileges?.loadDefaultPrivileges
2857
+ loadDefaultPrivileges: structureParams?.loadDefaultPrivileges,
2858
+ loadGrants: structureParams?.loadGrants
2230
2859
  })));
2231
2860
  const dbStructure = dbStructures[0];
2232
2861
  for (let i = 1; i < dbStructures.length; i++) compareDbStructures(dbStructure, dbStructures[i], i);
@@ -2239,6 +2868,30 @@ const hasCodeTablesWithRls = (db) => {
2239
2868
  }
2240
2869
  return false;
2241
2870
  };
2871
+ const hasCodeTablesWithGrants = (db) => {
2872
+ for (const key in db) {
2873
+ if (key[0] === "$") continue;
2874
+ if (db[key].internal.tableGrants?.length) return true;
2875
+ }
2876
+ return false;
2877
+ };
2878
+ const getEffectiveGrants = (grants, codeItems) => {
2879
+ const effectiveGrants = grants ? [...grants] : [];
2880
+ for (const table of codeItems.tables) {
2881
+ const tableGrants = table.internal.tableGrants;
2882
+ if (!tableGrants?.length) continue;
2883
+ const tableTarget = table.q.schema ? `${table.q.schema}.${table.table}` : table.table;
2884
+ for (const grant of tableGrants) {
2885
+ const internalGrant = {
2886
+ ...grant,
2887
+ to: (0, pqb_internal.toArray)(grant.to),
2888
+ tables: [tableTarget]
2889
+ };
2890
+ effectiveGrants.push(internalGrant);
2891
+ }
2892
+ }
2893
+ return effectiveGrants.length ? effectiveGrants : void 0;
2894
+ };
2242
2895
  const compareDbStructures = (a, b, i, path) => {
2243
2896
  let err;
2244
2897
  if (typeof a !== typeof b) err = true;
@@ -2274,7 +2927,10 @@ const getActualItems = async (db, currentSchema, internal, columnTypes) => {
2274
2927
  codeItems.tables.push({
2275
2928
  table: table.table,
2276
2929
  shape: table.shape,
2277
- internal: table.internal,
2930
+ internal: {
2931
+ ...table.internal,
2932
+ rls: internal.rls
2933
+ },
2278
2934
  q: { schema: (0, pqb_internal.getQuerySchema)(table) }
2279
2935
  });
2280
2936
  for (const key in table.relations) {
@@ -2319,8 +2975,27 @@ const getActualItems = async (db, currentSchema, internal, columnTypes) => {
2319
2975
  for (const privilege of role.defaultPrivileges) if (privilege.schema) codeItems.schemas.add(privilege.schema);
2320
2976
  }
2321
2977
  }
2978
+ if (internal.grants) for (const grant of internal.grants) addGrantSchemas(codeItems.schemas, currentSchema, grant);
2322
2979
  return codeItems;
2323
2980
  };
2981
+ const addGrantSchemas = (schemas, currentSchema, grant) => {
2982
+ for (const schema of [
2983
+ ...grant.schemas ?? [],
2984
+ ...grant.allTablesIn ?? [],
2985
+ ...grant.allSequencesIn ?? [],
2986
+ ...grant.allRoutinesIn ?? []
2987
+ ]) schemas.add(schema);
2988
+ for (const target of [
2989
+ ...grant.tables ?? [],
2990
+ ...grant.sequences ?? [],
2991
+ ...grant.routines ?? [],
2992
+ ...grant.types ?? [],
2993
+ ...grant.domains ?? []
2994
+ ]) {
2995
+ const [schema] = (0, rake_db.getSchemaAndTableFromName)(currentSchema, target);
2996
+ if (schema) schemas.add(schema);
2997
+ }
2998
+ };
2324
2999
  const processEnumColumn = (column, currentSchema, codeItems) => {
2325
3000
  const { enumName, options } = column;
2326
3001
  const [schema, name] = (0, rake_db.getSchemaAndTableFromName)(currentSchema, enumName);