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.
@@ -1313,28 +1313,42 @@ const dropCheck = ({ changeTableAst: { drop }, changingColumns }, dbCheck, name)
1313
1313
  };
1314
1314
  const defaultRlsState = {
1315
1315
  enable: false,
1316
- force: false
1316
+ force: true
1317
1317
  };
1318
1318
  const normalizeRlsFlag = (value, defaultValue) => {
1319
1319
  if (value === void 0) return defaultValue;
1320
1320
  return value === true || value === "true" || value === "t";
1321
1321
  };
1322
- const processTableRls = (ast, dbStructure, tables, currentSchema) => {
1322
+ const processTableRls = async (adapter, ast, dbStructure, tables, currentSchema, generatorIgnore) => {
1323
1323
  const projectRlsDefaults = tables[0]?.internal.rls?.tableRlsDefaults;
1324
+ const ignore = generatorIgnore;
1325
+ const ignoredTableSet = toIgnoredTableSet(ignore?.tables, currentSchema);
1326
+ const ignoredRlsTableSet = toIgnoredTableSet(ignore?.rls?.tables, currentSchema);
1327
+ const ignoredPolicyMap = toIgnoredPolicyMap(ignore?.rls?.policies, currentSchema);
1324
1328
  for (const table of tables) {
1325
1329
  const tableRls = table.internal.tableRls;
1326
1330
  if (!tableRls) continue;
1327
1331
  const schemaName = table.q.schema ?? currentSchema;
1332
+ const tableId = `${schemaName}.${table.table}`;
1333
+ if (ignoredTableSet.has(tableId) || ignoredRlsTableSet.has(tableId)) continue;
1328
1334
  const dbTable = dbStructure.tables.find((item) => item.schemaName === schemaName && item.name === table.table);
1329
- if (!dbTable) continue;
1335
+ const ignoredPolicyNames = ignoredPolicyMap.get(tableId);
1330
1336
  const codeRls = {
1331
1337
  enable: normalizeRlsFlag(tableRls.enable ?? projectRlsDefaults?.enable, defaultRlsState.enable),
1332
1338
  force: normalizeRlsFlag(tableRls.force ?? projectRlsDefaults?.force, defaultRlsState.force)
1333
1339
  };
1334
1340
  const dbRls = {
1335
- enable: normalizeRlsFlag(dbTable.rls?.enable, defaultRlsState.enable),
1336
- force: normalizeRlsFlag(dbTable.rls?.force, defaultRlsState.force)
1341
+ enable: normalizeRlsFlag(dbTable?.rls?.enable, defaultRlsState.enable),
1342
+ force: normalizeRlsFlag(dbTable?.rls?.force, false)
1337
1343
  };
1344
+ const policyExpressionComparisons = [];
1345
+ const policyChanges = collectPolicyChanges(schemaName, table.table, normalizeCodePolicies(tableRls, ignoredPolicyNames), normalizeDbPolicies(dbTable?.rls?.policies, ignoredPolicyNames), policyExpressionComparisons, concatSchemaAndName({
1346
+ schema: schemaName,
1347
+ name: table.table
1348
+ }));
1349
+ if (policyExpressionComparisons.length) await compareSqlExpressions(policyExpressionComparisons, adapter);
1350
+ const disableFirst = !codeRls.enable && dbRls.enable;
1351
+ if (!disableFirst) pushPolicyChanges(ast, policyChanges);
1338
1352
  if (codeRls.enable !== dbRls.enable) ast.push({
1339
1353
  type: "tableRls",
1340
1354
  action: codeRls.enable ? "enable" : "disable",
@@ -1347,7 +1361,250 @@ const processTableRls = (ast, dbStructure, tables, currentSchema) => {
1347
1361
  schema: schemaName,
1348
1362
  table: table.table
1349
1363
  });
1364
+ if (disableFirst) pushPolicyChanges(ast, policyChanges);
1365
+ }
1366
+ };
1367
+ const toIgnoredTableSet = (tables, currentSchema) => {
1368
+ const ignored = /* @__PURE__ */ new Set();
1369
+ if (!tables) return ignored;
1370
+ for (const name of tables) {
1371
+ const [schema = currentSchema, table] = getSchemaAndTableFromName(currentSchema, name);
1372
+ ignored.add(`${schema}.${table}`);
1373
+ }
1374
+ return ignored;
1375
+ };
1376
+ const toIgnoredPolicyMap = (items, currentSchema) => {
1377
+ const result = /* @__PURE__ */ new Map();
1378
+ if (!items) return result;
1379
+ for (const item of items) {
1380
+ const [schema = currentSchema, table] = getSchemaAndTableFromName(currentSchema, item.table);
1381
+ const key = `${schema}.${table}`;
1382
+ let set = result.get(key);
1383
+ if (!set) {
1384
+ set = /* @__PURE__ */ new Set();
1385
+ result.set(key, set);
1386
+ }
1387
+ for (const name of item.names) set.add(name);
1388
+ }
1389
+ return result;
1390
+ };
1391
+ const normalizeCodePolicies = (tableRls, ignoredPolicyNames) => {
1392
+ const result = [];
1393
+ const permit = tableRls.permit ?? [];
1394
+ const restrict = tableRls.restrict ?? [];
1395
+ for (const policy of permit) {
1396
+ const normalized = normalizeCodePolicy("PERMISSIVE", policy);
1397
+ if (normalized && !ignoredPolicyNames?.has(normalized.name)) result.push(normalized);
1398
+ }
1399
+ for (const policy of restrict) {
1400
+ const normalized = normalizeCodePolicy("RESTRICTIVE", policy);
1401
+ if (normalized && !ignoredPolicyNames?.has(normalized.name)) result.push(normalized);
1350
1402
  }
1403
+ return result;
1404
+ };
1405
+ const normalizeCodePolicy = (as, policy) => {
1406
+ if (!policy?.name) return;
1407
+ return {
1408
+ name: policy.name,
1409
+ as,
1410
+ for: policy.for ?? "ALL",
1411
+ to: normalizePolicyRoles(policy.to),
1412
+ using: toSqlText(policy.using),
1413
+ withCheck: toSqlText(policy.withCheck)
1414
+ };
1415
+ };
1416
+ const normalizeDbPolicies = (policies, ignoredPolicyNames) => {
1417
+ if (!policies) return [];
1418
+ const result = [];
1419
+ for (const policy of policies) {
1420
+ if (ignoredPolicyNames?.has(policy.name)) continue;
1421
+ result.push({
1422
+ name: policy.name,
1423
+ as: policy.mode,
1424
+ for: policy.command,
1425
+ to: normalizePolicyRoles(policy.roles),
1426
+ using: policy.using,
1427
+ withCheck: policy.withCheck
1428
+ });
1429
+ }
1430
+ return result;
1431
+ };
1432
+ const normalizePolicyRoles = (roles) => {
1433
+ const result = (Array.isArray(roles) ? roles : roles ? [roles] : ["public"]).map((role) => role.toLowerCase() === "public" ? "public" : role);
1434
+ return result.length ? result : ["public"];
1435
+ };
1436
+ const toSqlText = (value) => {
1437
+ if (!value) return;
1438
+ return value.toSQL({ values: [] });
1439
+ };
1440
+ const collectPolicyChanges = (schema, table, codePolicies, dbPolicies, compareExpressions, source) => {
1441
+ const changes = [];
1442
+ const dbPolicyMap = /* @__PURE__ */ new Map();
1443
+ for (const policy of dbPolicies) dbPolicyMap.set(policy.name, policy);
1444
+ const codePolicyMap = /* @__PURE__ */ new Map();
1445
+ for (const policy of codePolicies) codePolicyMap.set(policy.name, policy);
1446
+ const unmatchedCodePolicies = [];
1447
+ const unmatchedDbPolicies = [];
1448
+ for (const policy of codePolicies) {
1449
+ const dbPolicy = dbPolicyMap.get(policy.name);
1450
+ if (!dbPolicy) {
1451
+ unmatchedCodePolicies.push(policy);
1452
+ continue;
1453
+ }
1454
+ const recreate = policy.as !== dbPolicy.as || policy.for !== dbPolicy.for;
1455
+ const toChanged = !isSameStringArray(policy.to, dbPolicy.to);
1456
+ const from = {};
1457
+ const to = {};
1458
+ const codeUsing = policy.using;
1459
+ const dbUsing = dbPolicy.using;
1460
+ const codeWithCheck = policy.withCheck;
1461
+ const dbWithCheck = dbPolicy.withCheck;
1462
+ const hasUsing = codeUsing !== void 0 || dbUsing !== void 0;
1463
+ const hasWithCheck = codeWithCheck !== void 0 || dbWithCheck !== void 0;
1464
+ if (toChanged) {
1465
+ from.to = dbPolicy.to;
1466
+ to.to = policy.to;
1467
+ }
1468
+ const usingCanCompare = codeUsing !== void 0 && dbUsing !== void 0;
1469
+ const withCheckCanCompare = codeWithCheck !== void 0 && dbWithCheck !== void 0;
1470
+ const applyExpressionDiff = (matched) => {
1471
+ const usingChanged = usingCanCompare ? !matched : hasUsing && !usingCanCompare;
1472
+ const withCheckChanged = withCheckCanCompare ? !matched : hasWithCheck && !withCheckCanCompare;
1473
+ if (usingChanged) {
1474
+ from.using = dbPolicy.using;
1475
+ to.using = policy.using;
1476
+ }
1477
+ if (withCheckChanged) {
1478
+ from.withCheck = dbPolicy.withCheck;
1479
+ to.withCheck = policy.withCheck;
1480
+ }
1481
+ if (recreate) {
1482
+ changes.push({
1483
+ type: "changePolicy",
1484
+ schema,
1485
+ table,
1486
+ name: policy.name,
1487
+ from: {
1488
+ as: dbPolicy.as,
1489
+ for: dbPolicy.for,
1490
+ to: dbPolicy.to,
1491
+ using: dbPolicy.using,
1492
+ withCheck: dbPolicy.withCheck
1493
+ },
1494
+ to: {
1495
+ as: policy.as,
1496
+ for: policy.for,
1497
+ to: policy.to,
1498
+ using: policy.using,
1499
+ withCheck: policy.withCheck
1500
+ }
1501
+ });
1502
+ return;
1503
+ }
1504
+ if (!Object.keys(from).length) return;
1505
+ changes.push({
1506
+ type: "changePolicy",
1507
+ schema,
1508
+ table,
1509
+ name: policy.name,
1510
+ from,
1511
+ to
1512
+ });
1513
+ };
1514
+ if (!hasUsing && !hasWithCheck) {
1515
+ applyExpressionDiff(true);
1516
+ continue;
1517
+ }
1518
+ const compare = [];
1519
+ if (usingCanCompare) compare.push({
1520
+ inDb: dbUsing,
1521
+ inCode: [codeUsing]
1522
+ });
1523
+ if (withCheckCanCompare) compare.push({
1524
+ inDb: dbWithCheck,
1525
+ inCode: [codeWithCheck]
1526
+ });
1527
+ if (!compare.length) {
1528
+ applyExpressionDiff(false);
1529
+ continue;
1530
+ }
1531
+ compareExpressions.push({
1532
+ source,
1533
+ compare,
1534
+ handle(i) {
1535
+ applyExpressionDiff(i !== void 0);
1536
+ }
1537
+ });
1538
+ }
1539
+ for (const policy of dbPolicies) {
1540
+ if (codePolicyMap.has(policy.name)) continue;
1541
+ unmatchedDbPolicies.push(policy);
1542
+ }
1543
+ const pairedDbPolicyNames = /* @__PURE__ */ new Set();
1544
+ const pairedCodePolicyNames = /* @__PURE__ */ new Set();
1545
+ for (const policy of unmatchedCodePolicies) {
1546
+ const renamedFrom = unmatchedDbPolicies.find((item) => !pairedDbPolicyNames.has(item.name) && item.as === policy.as && item.for === policy.for);
1547
+ if (!renamedFrom) continue;
1548
+ pairedDbPolicyNames.add(renamedFrom.name);
1549
+ pairedCodePolicyNames.add(policy.name);
1550
+ changes.push({
1551
+ type: "changePolicy",
1552
+ schema,
1553
+ table,
1554
+ name: renamedFrom.name,
1555
+ from: {
1556
+ name: renamedFrom.name,
1557
+ to: renamedFrom.to,
1558
+ using: renamedFrom.using,
1559
+ withCheck: renamedFrom.withCheck
1560
+ },
1561
+ to: {
1562
+ name: policy.name,
1563
+ to: policy.to,
1564
+ using: policy.using,
1565
+ withCheck: policy.withCheck
1566
+ }
1567
+ });
1568
+ }
1569
+ for (const policy of unmatchedCodePolicies) {
1570
+ if (pairedCodePolicyNames.has(policy.name)) continue;
1571
+ changes.push({
1572
+ type: "policy",
1573
+ action: "create",
1574
+ schema,
1575
+ table,
1576
+ name: policy.name,
1577
+ as: policy.as,
1578
+ for: policy.for,
1579
+ to: policy.to,
1580
+ using: policy.using,
1581
+ withCheck: policy.withCheck
1582
+ });
1583
+ }
1584
+ for (const policy of unmatchedDbPolicies) {
1585
+ if (pairedDbPolicyNames.has(policy.name)) continue;
1586
+ changes.push({
1587
+ type: "policy",
1588
+ action: "drop",
1589
+ schema,
1590
+ table,
1591
+ name: policy.name,
1592
+ as: policy.as,
1593
+ for: policy.for,
1594
+ to: policy.to,
1595
+ using: policy.using,
1596
+ withCheck: policy.withCheck
1597
+ });
1598
+ }
1599
+ return changes;
1600
+ };
1601
+ const pushPolicyChanges = (ast, changes) => {
1602
+ for (const change of changes) ast.push(change);
1603
+ };
1604
+ const isSameStringArray = (a, b) => {
1605
+ if (a.length !== b.length) return false;
1606
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
1607
+ return true;
1351
1608
  };
1352
1609
  const processTables = async (ast, domainsMap, adapter, dbStructure, config, { structureToAstCtx, codeItems: { tables }, currentSchema, internal: { generatorIgnore }, verifying }, pendingDbTypes) => {
1353
1610
  const createTables = collectCreateTables(tables, dbStructure, currentSchema);
@@ -1362,7 +1619,7 @@ const processTables = async (ast, domainsMap, adapter, dbStructure, config, { st
1362
1619
  await applyChangeTables(adapter, changeTables, structureToAstCtx, dbStructure, domainsMap, ast, currentSchema, config, compareSql, tableExpressions, verifying, pendingDbTypes);
1363
1620
  processForeignKeys(config, ast, changeTables, currentSchema, tableShapes);
1364
1621
  await Promise.all([applyCompareSql(compareSql, adapter), compareSqlExpressions(tableExpressions, adapter)]);
1365
- processTableRls(ast, dbStructure, tables, currentSchema);
1622
+ await processTableRls(adapter, ast, dbStructure, tables, currentSchema, generatorIgnore);
1366
1623
  for (const dbTable of dropTables) ast.push(tableToAst(structureToAstCtx, dbStructure, dbTable, "drop", domainsMap));
1367
1624
  };
1368
1625
  const collectCreateTables = (tables, dbStructure, currentSchema) => {
@@ -1722,7 +1979,7 @@ const privilegeConfigsMatch = (a, b, objectType, objectTypeToAllPrivileges) => {
1722
1979
  if (b.privilege === "ALL" && a.isGrantable === b.isGrantable) return expectedPrivs.includes(a.privilege);
1723
1980
  return false;
1724
1981
  };
1725
- const normalizeRoleName = (name) => {
1982
+ const normalizeRoleName$1 = (name) => {
1726
1983
  if (name.startsWith("\"") && name.endsWith("\"")) return name.slice(1, -1);
1727
1984
  return name;
1728
1985
  };
@@ -1792,7 +2049,7 @@ const processDefaultPrivileges = (ast, dbStructure, { internal: { roles } }) =>
1792
2049
  }
1793
2050
  const found = /* @__PURE__ */ new Set();
1794
2051
  for (const dbPrivilege of dbStructure.defaultPrivileges) {
1795
- const grantee = normalizeRoleName(dbPrivilege.grantee);
2052
+ const grantee = normalizeRoleName$1(dbPrivilege.grantee);
1796
2053
  if (grantee === "postgres") continue;
1797
2054
  const key = `${grantee}.${dbPrivilege.schema}`;
1798
2055
  const codePrivilege = codePrivileges.get(key);
@@ -1868,6 +2125,280 @@ const processDefaultPrivileges = (ast, dbStructure, { internal: { roles } }) =>
1868
2125
  });
1869
2126
  }
1870
2127
  };
2128
+ const targetKeys = [
2129
+ "schemas",
2130
+ "tables",
2131
+ "sequences",
2132
+ "routines",
2133
+ "types",
2134
+ "domains",
2135
+ "databases"
2136
+ ];
2137
+ const schemaWideTargetKeys = [
2138
+ "allTablesIn",
2139
+ "allSequencesIn",
2140
+ "allRoutinesIn"
2141
+ ];
2142
+ const supportedPrivileges = {
2143
+ schemas: ["USAGE", "CREATE"],
2144
+ tables: [
2145
+ "SELECT",
2146
+ "INSERT",
2147
+ "UPDATE",
2148
+ "DELETE",
2149
+ "TRUNCATE",
2150
+ "REFERENCES",
2151
+ "TRIGGER",
2152
+ "MAINTAIN"
2153
+ ],
2154
+ sequences: [
2155
+ "USAGE",
2156
+ "SELECT",
2157
+ "UPDATE"
2158
+ ],
2159
+ routines: ["EXECUTE"],
2160
+ types: ["USAGE"],
2161
+ domains: ["USAGE"],
2162
+ databases: [
2163
+ "CREATE",
2164
+ "CONNECT",
2165
+ "TEMPORARY"
2166
+ ]
2167
+ };
2168
+ const schemaWideToConcreteTarget = {
2169
+ allTablesIn: "tables",
2170
+ allSequencesIn: "sequences",
2171
+ allRoutinesIn: "routines"
2172
+ };
2173
+ const processGrants = (ast, dbStructure, params) => {
2174
+ const { grants } = params.internal;
2175
+ if (!grants || !dbStructure.grants) return;
2176
+ const codeStates = collectCodeGrants(dbStructure, params).filter((grant) => !isIgnoredGrant(grant, params));
2177
+ const dbStates = collectDbGrants(dbStructure, params.currentSchema).filter((grant) => !isIgnoredGrant(grant, params));
2178
+ const managedTargets = getManagedGrantTargets(params);
2179
+ for (const code of codeStates) {
2180
+ const actual = dbStates.filter((db) => isSameGrantTarget(code, db));
2181
+ addGrantAst(ast, "grant", code, missingPrivileges(code.grantablePrivileges, actual, "grantablePrivileges"), "grantablePrivileges");
2182
+ addGrantAst(ast, "revoke", code, grantOptionsToRevoke(code.privileges, code.grantablePrivileges, actual), "grantablePrivileges");
2183
+ addGrantAst(ast, "grant", code, missingPrivileges(code.privileges, actual, "privileges"), "privileges");
2184
+ }
2185
+ for (const actual of dbStates) {
2186
+ const configured = codeStates.filter((code) => isSameGrantTarget(code, actual));
2187
+ if (!shouldRevokeActualGrant(actual, configured, managedTargets)) continue;
2188
+ const revokeGrant = getRevokeGrantState(actual, configured);
2189
+ addGrantAst(ast, "revoke", revokeGrant, privilegesToRevoke(actual.privileges, configured), "privileges");
2190
+ addGrantAst(ast, "revoke", revokeGrant, privilegesToRevoke(actual.grantablePrivileges, configured), "grantablePrivileges");
2191
+ }
2192
+ };
2193
+ const getManagedGrantTargets = ({ codeItems, currentSchema }) => {
2194
+ const targets = {
2195
+ tables: /* @__PURE__ */ new Set(),
2196
+ domains: /* @__PURE__ */ new Set(),
2197
+ types: /* @__PURE__ */ new Set()
2198
+ };
2199
+ for (const table of codeItems.tables) targets.tables.add(`${table.q.schema ?? currentSchema}.${table.table}`);
2200
+ for (const domain of codeItems.domains) targets.domains.add(`${domain.schemaName}.${domain.name}`);
2201
+ for (const enumItem of codeItems.enums.values()) targets.types.add(`${enumItem.schema ?? currentSchema}.${enumItem.name}`);
2202
+ return targets;
2203
+ };
2204
+ const shouldRevokeActualGrant = (actual, configured, managedTargets) => {
2205
+ if (configured.length) return true;
2206
+ if (actual.targetKey === "tables") return managedTargets.tables.has(actual.target);
2207
+ if (actual.targetKey === "domains") return managedTargets.domains.has(actual.target);
2208
+ if (actual.targetKey === "types") return managedTargets.types.has(actual.target);
2209
+ return false;
2210
+ };
2211
+ const getRevokeGrantState = (actual, configured) => {
2212
+ if (configured.some((grant) => grant.grantedBy)) return actual;
2213
+ return {
2214
+ ...actual,
2215
+ grantedBy: void 0
2216
+ };
2217
+ };
2218
+ const collectCodeGrants = (dbStructure, { currentSchema, internal }) => {
2219
+ const states = [];
2220
+ for (const grant of internal.grants ?? []) {
2221
+ for (const targetKey of targetKeys) {
2222
+ const values = grant[targetKey];
2223
+ if (!values?.length) continue;
2224
+ addStates(states, withEffectiveGrantor(grant, internal), targetKey, values, currentSchema);
2225
+ }
2226
+ for (const targetKey of schemaWideTargetKeys) {
2227
+ const values = grant[targetKey];
2228
+ if (!values?.length) continue;
2229
+ const concreteTargetKey = schemaWideToConcreteTarget[targetKey];
2230
+ addStates(states, withEffectiveGrantor(grant, internal), concreteTargetKey, getSchemaWideTargets(dbStructure, concreteTargetKey, values), currentSchema);
2231
+ }
2232
+ }
2233
+ return states;
2234
+ };
2235
+ const collectDbGrants = (dbStructure, currentSchema) => {
2236
+ const states = [];
2237
+ for (const grant of dbStructure.grants ?? []) for (const targetKey of targetKeys) {
2238
+ const values = grant[targetKey];
2239
+ if (!values?.length) continue;
2240
+ addStates(states, grant, targetKey, values, currentSchema);
2241
+ }
2242
+ return states;
2243
+ };
2244
+ const withEffectiveGrantor = (grant, internal) => {
2245
+ return {
2246
+ ...grant,
2247
+ grantedBy: grant.grantedBy ?? internal.defaultGrantedBy
2248
+ };
2249
+ };
2250
+ const isIgnoredGrant = (grant, { currentSchema, internal: { generatorIgnore } }) => {
2251
+ const { grants } = generatorIgnore ?? {};
2252
+ if (matchesSelector(grants?.roles, grant.to)) return true;
2253
+ const names = getGrantTargetNames(grant, currentSchema);
2254
+ if (matchesSelector(grants?.[grant.targetKey], names)) return true;
2255
+ if (grant.targetKey === "tables" && names.schema && matchesSelector(grants?.allTablesIn, names.schema)) return true;
2256
+ if (grant.targetKey === "sequences" && names.schema && matchesSelector(grants?.allSequencesIn, names.schema)) return true;
2257
+ if (grant.targetKey === "routines" && names.schema && matchesSelector(grants?.allRoutinesIn, names.schema)) return true;
2258
+ return isTopLevelIgnoredGrant(grant, names, generatorIgnore);
2259
+ };
2260
+ const isTopLevelIgnoredGrant = (grant, names, generatorIgnore) => {
2261
+ if (grant.targetKey === "schemas") return !!generatorIgnore?.schemas?.includes(grant.target);
2262
+ if (names.schema && generatorIgnore?.schemas?.includes(names.schema)) return true;
2263
+ if (grant.targetKey === "tables") return isIgnoredByName(generatorIgnore?.tables, names);
2264
+ if (grant.targetKey === "domains") return isIgnoredByName(generatorIgnore?.domains, names);
2265
+ return false;
2266
+ };
2267
+ const getGrantTargetNames = (grant, currentSchema) => {
2268
+ if (grant.targetKey === "schemas" || grant.targetKey === "databases") return {
2269
+ name: grant.target,
2270
+ qualified: grant.target
2271
+ };
2272
+ const [schema, name] = getSchemaAndTableFromName(currentSchema, grant.target);
2273
+ return {
2274
+ schema,
2275
+ name,
2276
+ qualified: schema ? `${schema}.${name}` : name
2277
+ };
2278
+ };
2279
+ const isIgnoredByName = (ignored, names) => {
2280
+ return !!ignored?.some((name) => name === names.qualified || name === names.name || (names.schema ? name === `${names.schema}.${names.name}` : false));
2281
+ };
2282
+ const matchesSelector = (selector, value) => {
2283
+ if (!selector) return false;
2284
+ const values = typeof value === "string" ? [value] : [
2285
+ value.qualified,
2286
+ value.name,
2287
+ value.schema
2288
+ ].filter(isString);
2289
+ return (Array.isArray(selector) ? selector : [selector]).some((item) => values.some((name) => typeof item === "string" ? item === name : item.test(name)));
2290
+ };
2291
+ const isString = (value) => {
2292
+ return !!value;
2293
+ };
2294
+ const addStates = (states, grant, targetKey, values, currentSchema) => {
2295
+ for (const to of grant.to) for (const value of values) {
2296
+ const target = typeof value === "string" ? normalizeTarget(targetKey, value, currentSchema) : value.target;
2297
+ addOrMergeState(states, {
2298
+ targetKey,
2299
+ target,
2300
+ outputTarget: typeof value === "string" ? normalizeOutputTarget(target, currentSchema) : value.outputTarget,
2301
+ to: normalizeRoleName(to),
2302
+ grantedBy: grant.grantedBy ? normalizeRoleName(grant.grantedBy) : void 0,
2303
+ privileges: expandPrivileges(targetKey, grant.privileges),
2304
+ grantablePrivileges: expandPrivileges(targetKey, grant.grantablePrivileges)
2305
+ });
2306
+ }
2307
+ };
2308
+ const addOrMergeState = (states, state) => {
2309
+ const existing = states.find((item) => isSameExactGrantTarget(item, state));
2310
+ if (!existing) {
2311
+ removeGrantableFromOrdinary(state);
2312
+ states.push(state);
2313
+ return;
2314
+ }
2315
+ for (const privilege of state.privileges) if (!existing.grantablePrivileges.has(privilege)) existing.privileges.add(privilege);
2316
+ for (const privilege of state.grantablePrivileges) {
2317
+ existing.grantablePrivileges.add(privilege);
2318
+ existing.privileges.delete(privilege);
2319
+ }
2320
+ };
2321
+ const removeGrantableFromOrdinary = (state) => {
2322
+ for (const privilege of state.grantablePrivileges) state.privileges.delete(privilege);
2323
+ };
2324
+ const normalizeRoleName = (name) => {
2325
+ return name.startsWith("\"") && name.endsWith("\"") ? name.slice(1, -1) : name;
2326
+ };
2327
+ const getSchemaWideTargets = (dbStructure, targetKey, schemas) => {
2328
+ const selectedSchemas = new Set(schemas);
2329
+ if (targetKey === "tables") return [...dbStructure.tables.map((table) => ({
2330
+ target: `${table.schemaName}.${table.name}`,
2331
+ outputTarget: `${table.schemaName}.${table.name}`
2332
+ })), ...dbStructure.views.map((view) => ({
2333
+ target: `${view.schemaName}.${view.name}`,
2334
+ outputTarget: `${view.schemaName}.${view.name}`
2335
+ }))].filter((item) => selectedSchemas.has(item.target.split(".")[0]));
2336
+ if (targetKey === "sequences" || targetKey === "routines") return getSchemaWideGrantTargets(dbStructure, targetKey, selectedSchemas);
2337
+ return [];
2338
+ };
2339
+ const getSchemaWideGrantTargets = (dbStructure, targetKey, selectedSchemas) => {
2340
+ const targets = /* @__PURE__ */ new Map();
2341
+ for (const grant of dbStructure.grants ?? []) for (const target of grant[targetKey] ?? []) {
2342
+ const [schema] = target.split(".");
2343
+ if (!selectedSchemas.has(schema)) continue;
2344
+ targets.set(target, {
2345
+ target,
2346
+ outputTarget: target
2347
+ });
2348
+ }
2349
+ return [...targets.values()];
2350
+ };
2351
+ const normalizeTarget = (targetKey, value, currentSchema) => {
2352
+ if (targetKey === "schemas" || targetKey === "databases") return value;
2353
+ const [schema, name] = getSchemaAndTableFromName(currentSchema, value);
2354
+ return `${schema ?? currentSchema}.${name}`;
2355
+ };
2356
+ const normalizeOutputTarget = (target, currentSchema) => {
2357
+ const [schema, name] = target.split(".");
2358
+ if (!name) return target;
2359
+ return schema === currentSchema ? name : target;
2360
+ };
2361
+ const expandPrivileges = (targetKey, privileges) => {
2362
+ const set = /* @__PURE__ */ new Set();
2363
+ for (const privilege of privileges ?? []) if (privilege === "ALL") for (const supported of supportedPrivileges[targetKey]) set.add(supported);
2364
+ else set.add(privilege === "TEMP" ? "TEMPORARY" : privilege);
2365
+ return set;
2366
+ };
2367
+ const isSameGrantTarget = (a, b) => {
2368
+ return a.targetKey === b.targetKey && a.target === b.target && a.to === b.to && (!a.grantedBy || !b.grantedBy || a.grantedBy === b.grantedBy);
2369
+ };
2370
+ const isSameExactGrantTarget = (a, b) => {
2371
+ return a.targetKey === b.targetKey && a.target === b.target && a.to === b.to && a.grantedBy === b.grantedBy;
2372
+ };
2373
+ const missingPrivileges = (expected, actual, key) => {
2374
+ const result = [];
2375
+ for (const privilege of expected) if (!actual.some((state) => state[key].has(privilege))) result.push(privilege);
2376
+ return result;
2377
+ };
2378
+ const grantOptionsToRevoke = (expectedOrdinary, expectedGrantable, actual) => {
2379
+ const result = [];
2380
+ for (const privilege of expectedOrdinary) {
2381
+ if (expectedGrantable.has(privilege)) continue;
2382
+ if (actual.some((state) => state.grantablePrivileges.has(privilege))) result.push(privilege);
2383
+ }
2384
+ return result;
2385
+ };
2386
+ const privilegesToRevoke = (actual, configured) => {
2387
+ const result = [];
2388
+ for (const privilege of actual) if (!configured.some((state) => state.privileges.has(privilege) || state.grantablePrivileges.has(privilege))) result.push(privilege);
2389
+ return result;
2390
+ };
2391
+ const addGrantAst = (ast, action, grant, privileges, privilegeKey) => {
2392
+ if (!privileges.length) return;
2393
+ ast.push({
2394
+ type: "grant",
2395
+ action,
2396
+ to: [grant.to],
2397
+ [grant.targetKey]: [grant.outputTarget],
2398
+ [privilegeKey]: privileges,
2399
+ grantedBy: grant.grantedBy
2400
+ });
2401
+ };
1871
2402
  /**
1872
2403
  * This is needed to compare SQLs of table expressions.
1873
2404
  * Need to exclude table columns of pending types, such as enums or domains,
@@ -1886,6 +2417,7 @@ const composeMigration = async (adapter, config, ast, dbStructure, params) => {
1886
2417
  const { structureToAstCtx, currentSchema } = params;
1887
2418
  await processRoles(ast, dbStructure, params);
1888
2419
  processDefaultPrivileges(ast, dbStructure, params);
2420
+ processGrants(ast, dbStructure, params);
1889
2421
  const domainsMap = makeDomainsMap(structureToAstCtx, dbStructure);
1890
2422
  await processSchemas(ast, dbStructure, params);
1891
2423
  processExtensions(ast, dbStructure, params);
@@ -1896,7 +2428,7 @@ const composeMigration = async (adapter, config, ast, dbStructure, params) => {
1896
2428
  return astToMigration(currentSchema, config, ast);
1897
2429
  };
1898
2430
  const rollbackErr = /* @__PURE__ */ new Error("Rollback");
1899
- const verifyMigration = async (adapter, config, migrationCode, generateMigrationParams, roles, defaultPrivileges) => {
2431
+ const verifyMigration = async (adapter, config, migrationCode, generateMigrationParams, roles, structureParams) => {
1900
2432
  const migrationFn = new Function("change", migrationCode);
1901
2433
  let code;
1902
2434
  try {
@@ -1913,7 +2445,7 @@ const verifyMigration = async (adapter, config, migrationCode, generateMigration
1913
2445
  const dbStructure = await introspectDbSchema(trx, {
1914
2446
  rls: generateMigrationParams.codeItems.tables.some((table) => !!table.internal.tableRls),
1915
2447
  roles,
1916
- loadDefaultPrivileges: defaultPrivileges?.loadDefaultPrivileges
2448
+ ...structureParams
1917
2449
  });
1918
2450
  generateMigrationParams.verifying = true;
1919
2451
  try {
@@ -2099,6 +2631,14 @@ const report = (ast, config, currentSchema) => {
2099
2631
  if (parts.length) code.push(parts.join("\n"));
2100
2632
  break;
2101
2633
  }
2634
+ case "grant": {
2635
+ const parts = [];
2636
+ const target = grantTargetToReport(a, currentSchema);
2637
+ 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(", ")}`);
2638
+ 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(", ")}`);
2639
+ if (parts.length) code.push(parts.join("\n"));
2640
+ break;
2641
+ }
2102
2642
  case "tableRls": {
2103
2643
  const table = dbItemName({
2104
2644
  schema: a.schema,
@@ -2108,6 +2648,28 @@ const report = (ast, config, currentSchema) => {
2108
2648
  code.push(message);
2109
2649
  break;
2110
2650
  }
2651
+ case "policy": {
2652
+ const summary = formatPolicySummary(a, dbItemName({
2653
+ schema: a.schema,
2654
+ name: a.table
2655
+ }, currentSchema));
2656
+ const message = a.action === "create" ? `${green("+ create policy")} ${a.name}: ${summary}` : `${red("- drop policy")} ${a.name}: ${summary}`;
2657
+ code.push(message);
2658
+ break;
2659
+ }
2660
+ case "changePolicy": {
2661
+ const table = dbItemName({
2662
+ schema: a.schema,
2663
+ name: a.table
2664
+ }, currentSchema);
2665
+ const fromName = a.from.name ?? a.name;
2666
+ const toName = a.to.name ?? a.name;
2667
+ const message = fromName === toName ? `${yellow("~ change policy")} ${a.name} on ${table}:` : `${yellow("~ rename policy")} ${fromName} ${yellow("=>")} ${toName} on ${table}:`;
2668
+ const inner = [`${yellow("from")}: ${formatPolicyChangeDefinition(a.from)}`, `${yellow("to")}: ${formatPolicyChangeDefinition(a.to)}`];
2669
+ code.push(message);
2670
+ code.push(inner);
2671
+ break;
2672
+ }
2111
2673
  default: exhaustive(a);
2112
2674
  }
2113
2675
  const result = codeToString(code, "", " ");
@@ -2116,6 +2678,64 @@ const report = (ast, config, currentSchema) => {
2116
2678
  const dbItemName = ({ schema, name }, currentSchema) => {
2117
2679
  return schema && schema !== currentSchema ? `${schema}.${name}` : name;
2118
2680
  };
2681
+ const grantTargetKeys = [
2682
+ "schemas",
2683
+ "tables",
2684
+ "sequences",
2685
+ "routines",
2686
+ "types",
2687
+ "domains",
2688
+ "databases"
2689
+ ];
2690
+ const mapGrantPrivilege = (privilege) => {
2691
+ return privilege === "ALL" ? "ALL PRIVILEGES" : privilege;
2692
+ };
2693
+ const grantTargetToReport = (grant, currentSchema) => {
2694
+ for (const key of grantTargetKeys) {
2695
+ const targets = grant[key];
2696
+ if (!targets?.length) continue;
2697
+ return `${key} ${targets.map((target) => formatGrantTarget(key, target, currentSchema)).join(", ")}`;
2698
+ }
2699
+ return "unknown target";
2700
+ };
2701
+ const formatGrantTarget = (key, target, currentSchema) => {
2702
+ if (key === "schemas" || key === "databases") return target;
2703
+ const [schema, name] = getSchemaAndTableFromName(currentSchema, target);
2704
+ return dbItemName({
2705
+ schema,
2706
+ name
2707
+ }, currentSchema);
2708
+ };
2709
+ const formatPolicyRoles = (roles) => {
2710
+ return roles?.length ? roles.join(", ") : "public";
2711
+ };
2712
+ const formatPolicyCommand = (command) => {
2713
+ return (command ?? "ALL").toLowerCase();
2714
+ };
2715
+ const formatPolicyMode = (mode) => {
2716
+ return mode === "PERMISSIVE" ? "permit" : "restrict";
2717
+ };
2718
+ const formatPolicySummary = (policy, table) => {
2719
+ const parts = [
2720
+ `${formatPolicyMode(policy.as)} access on ${table}`,
2721
+ `to ${formatPolicyRoles(policy.to)}`,
2722
+ `for ${formatPolicyCommand(policy.for)}`
2723
+ ];
2724
+ if (policy.using) parts.push(`using (${policy.using})`);
2725
+ if (policy.withCheck) parts.push(`with check (${policy.withCheck})`);
2726
+ return parts.join(", ");
2727
+ };
2728
+ const formatPolicyChangeDefinition = (policy) => {
2729
+ const parts = [];
2730
+ if (policy.name) parts.push(`name ${policy.name}`);
2731
+ if (policy.table) parts.push(`table ${policy.table}`);
2732
+ if (policy.as) parts.push(`as ${policy.as.toLowerCase()}`);
2733
+ if (policy.for) parts.push(`for ${policy.for.toLowerCase()}`);
2734
+ if ("to" in policy) parts.push(`to ${formatPolicyRoles(policy.to)}`);
2735
+ if ("using" in policy && policy.using) parts.push(`using (${policy.using})`);
2736
+ if ("withCheck" in policy && policy.withCheck) parts.push(`with check (${policy.withCheck})`);
2737
+ return parts.join(", ");
2738
+ };
2119
2739
  var AbortSignal = class extends Error {};
2120
2740
  const generate = async (adapters, config, args, afterPull) => {
2121
2741
  let { dbPath } = config;
@@ -2131,17 +2751,25 @@ const generate = async (adapters, config, args, afterPull) => {
2131
2751
  if (afterPull) adapters = [afterPull.adapter];
2132
2752
  const db = await getDbFromConfig(config, dbPath);
2133
2753
  const { columnTypes, internal } = db.$qb;
2754
+ const structureParams = {
2755
+ loadDefaultPrivileges: internal.roles?.some((role) => role.defaultPrivileges !== void 0) ?? false,
2756
+ loadGrants: !!internal.grants || hasCodeTablesWithGrants(db)
2757
+ };
2134
2758
  const rolesDbStructureParam = internal.roles ? internal.managedRolesSql ? { whereSql: internal.managedRolesSql } : emptyObject : void 0;
2135
- const { dbStructure } = await migrateAndPullStructures(adapters, config, db, rolesDbStructureParam, internal.roles ? { loadDefaultPrivileges: true } : void 0, afterPull);
2759
+ const { dbStructure } = await migrateAndPullStructures(adapters, config, db, rolesDbStructureParam, structureParams, afterPull);
2136
2760
  const [adapter] = adapters;
2137
2761
  const adapterSchema = adapter.getSchema();
2138
2762
  const currentSchema = (typeof adapterSchema === "function" ? adapterSchema() : adapterSchema) ?? "public";
2139
2763
  const codeItems = await getActualItems(db, currentSchema, internal, columnTypes);
2764
+ const effectiveGrants = getEffectiveGrants(internal.grants, codeItems);
2140
2765
  const generateMigrationParams = {
2141
2766
  structureToAstCtx: makeStructureToAstCtx(config, currentSchema),
2142
2767
  codeItems,
2143
2768
  currentSchema,
2144
- internal
2769
+ internal: {
2770
+ ...internal,
2771
+ grants: effectiveGrants
2772
+ }
2145
2773
  };
2146
2774
  const ast = [];
2147
2775
  let migrationCode;
@@ -2155,7 +2783,7 @@ const generate = async (adapters, config, args, afterPull) => {
2155
2783
  throw err;
2156
2784
  }
2157
2785
  if (migrationCode && !afterPull) {
2158
- const result = await verifyMigration(adapter, config, migrationCode, generateMigrationParams, rolesDbStructureParam, internal.roles ? { loadDefaultPrivileges: true } : void 0);
2786
+ const result = await verifyMigration(adapter, config, migrationCode, generateMigrationParams, rolesDbStructureParam, structureParams);
2159
2787
  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}`}`);
2160
2788
  }
2161
2789
  const { logger } = config;
@@ -2184,7 +2812,7 @@ const getDbFromConfig = async (config, dbPath) => {
2184
2812
  if (!db?.$qb) throw new Error(`Unable to import OrchidORM instance as ${config.dbExportedAs ?? "db"} from ${config.dbPath}`);
2185
2813
  return db;
2186
2814
  };
2187
- const migrateAndPullStructures = async (adapters, config, db, roles, defaultPrivileges, afterPull) => {
2815
+ const migrateAndPullStructures = async (adapters, config, db, roles, structureParams, afterPull) => {
2188
2816
  if (afterPull) return { dbStructure: {
2189
2817
  version: await getDbVersion(adapters[0]),
2190
2818
  schemas: [],
@@ -2203,7 +2831,8 @@ const migrateAndPullStructures = async (adapters, config, db, roles, defaultPriv
2203
2831
  const dbStructures = await Promise.all(adapters.map((adapter) => introspectDbSchema(adapter, {
2204
2832
  rls: hasCodeTablesWithRls(db),
2205
2833
  roles,
2206
- loadDefaultPrivileges: defaultPrivileges?.loadDefaultPrivileges
2834
+ loadDefaultPrivileges: structureParams?.loadDefaultPrivileges,
2835
+ loadGrants: structureParams?.loadGrants
2207
2836
  })));
2208
2837
  const dbStructure = dbStructures[0];
2209
2838
  for (let i = 1; i < dbStructures.length; i++) compareDbStructures(dbStructure, dbStructures[i], i);
@@ -2216,6 +2845,30 @@ const hasCodeTablesWithRls = (db) => {
2216
2845
  }
2217
2846
  return false;
2218
2847
  };
2848
+ const hasCodeTablesWithGrants = (db) => {
2849
+ for (const key in db) {
2850
+ if (key[0] === "$") continue;
2851
+ if (db[key].internal.tableGrants?.length) return true;
2852
+ }
2853
+ return false;
2854
+ };
2855
+ const getEffectiveGrants = (grants, codeItems) => {
2856
+ const effectiveGrants = grants ? [...grants] : [];
2857
+ for (const table of codeItems.tables) {
2858
+ const tableGrants = table.internal.tableGrants;
2859
+ if (!tableGrants?.length) continue;
2860
+ const tableTarget = table.q.schema ? `${table.q.schema}.${table.table}` : table.table;
2861
+ for (const grant of tableGrants) {
2862
+ const internalGrant = {
2863
+ ...grant,
2864
+ to: toArray(grant.to),
2865
+ tables: [tableTarget]
2866
+ };
2867
+ effectiveGrants.push(internalGrant);
2868
+ }
2869
+ }
2870
+ return effectiveGrants.length ? effectiveGrants : void 0;
2871
+ };
2219
2872
  const compareDbStructures = (a, b, i, path) => {
2220
2873
  let err;
2221
2874
  if (typeof a !== typeof b) err = true;
@@ -2251,7 +2904,10 @@ const getActualItems = async (db, currentSchema, internal, columnTypes) => {
2251
2904
  codeItems.tables.push({
2252
2905
  table: table.table,
2253
2906
  shape: table.shape,
2254
- internal: table.internal,
2907
+ internal: {
2908
+ ...table.internal,
2909
+ rls: internal.rls
2910
+ },
2255
2911
  q: { schema: getQuerySchema(table) }
2256
2912
  });
2257
2913
  for (const key in table.relations) {
@@ -2296,8 +2952,27 @@ const getActualItems = async (db, currentSchema, internal, columnTypes) => {
2296
2952
  for (const privilege of role.defaultPrivileges) if (privilege.schema) codeItems.schemas.add(privilege.schema);
2297
2953
  }
2298
2954
  }
2955
+ if (internal.grants) for (const grant of internal.grants) addGrantSchemas(codeItems.schemas, currentSchema, grant);
2299
2956
  return codeItems;
2300
2957
  };
2958
+ const addGrantSchemas = (schemas, currentSchema, grant) => {
2959
+ for (const schema of [
2960
+ ...grant.schemas ?? [],
2961
+ ...grant.allTablesIn ?? [],
2962
+ ...grant.allSequencesIn ?? [],
2963
+ ...grant.allRoutinesIn ?? []
2964
+ ]) schemas.add(schema);
2965
+ for (const target of [
2966
+ ...grant.tables ?? [],
2967
+ ...grant.sequences ?? [],
2968
+ ...grant.routines ?? [],
2969
+ ...grant.types ?? [],
2970
+ ...grant.domains ?? []
2971
+ ]) {
2972
+ const [schema] = getSchemaAndTableFromName(currentSchema, target);
2973
+ if (schema) schemas.add(schema);
2974
+ }
2975
+ };
2301
2976
  const processEnumColumn = (column, currentSchema, codeItems) => {
2302
2977
  const { enumName, options } = column;
2303
2978
  const [schema, name] = getSchemaAndTableFromName(currentSchema, enumName);