@xubylele/schema-forge 1.11.0 → 1.12.1

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/api.js CHANGED
@@ -34,6 +34,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
34
34
  function parseSchema(source) {
35
35
  const lines = source.split("\n");
36
36
  const tables = {};
37
+ const policyList = [];
37
38
  let currentLine = 0;
38
39
  const validBaseColumnTypes = /* @__PURE__ */ new Set([
39
40
  "uuid",
@@ -258,6 +259,85 @@ function parseSchema(source) {
258
259
  }
259
260
  return column;
260
261
  }
262
+ function parsePolicyBlock(startLine) {
263
+ const firstLine = cleanLine(lines[startLine]);
264
+ const declMatch = firstLine.match(/^policy\s+"([^"]*)"\s+on\s+(\w+)\s*$/);
265
+ if (!declMatch) {
266
+ throw new Error(`Line ${startLine + 1}: Invalid policy declaration. Expected: policy "<name>" on <table>`);
267
+ }
268
+ const policyName = declMatch[1];
269
+ const tableIdent = declMatch[2];
270
+ validateIdentifier(tableIdent, startLine + 1, "table");
271
+ let command;
272
+ let using;
273
+ let withCheck;
274
+ let toRoles;
275
+ let lineIdx = startLine + 1;
276
+ while (lineIdx < lines.length) {
277
+ const cleaned = cleanLine(lines[lineIdx]);
278
+ if (!cleaned) {
279
+ lineIdx++;
280
+ continue;
281
+ }
282
+ if (cleaned.startsWith("for ")) {
283
+ const rest = cleaned.slice(4).trim().toLowerCase();
284
+ const parts = rest.split(/\s+/);
285
+ const cmd = parts[0];
286
+ if (!POLICY_COMMANDS.includes(cmd)) {
287
+ throw new Error(`Line ${lineIdx + 1}: Invalid policy command '${cmd}'. Expected: select, insert, update, delete, or all`);
288
+ }
289
+ if (command !== void 0) {
290
+ throw new Error(`Line ${lineIdx + 1}: Duplicate 'for' in policy`);
291
+ }
292
+ command = cmd;
293
+ if (parts.length > 1 && parts[1] === "to" && parts.length > 2) {
294
+ toRoles = parts.slice(2);
295
+ }
296
+ lineIdx++;
297
+ continue;
298
+ }
299
+ if (cleaned.startsWith("to ")) {
300
+ if (toRoles !== void 0) {
301
+ throw new Error(`Line ${lineIdx + 1}: Duplicate 'to' in policy`);
302
+ }
303
+ const roles = cleaned.slice(3).trim().split(/\s+/).filter(Boolean);
304
+ if (roles.length > 0) {
305
+ toRoles = roles;
306
+ }
307
+ lineIdx++;
308
+ continue;
309
+ }
310
+ if (cleaned.startsWith("using ")) {
311
+ if (using !== void 0) {
312
+ throw new Error(`Line ${lineIdx + 1}: Duplicate 'using' in policy`);
313
+ }
314
+ using = cleaned.slice(6).trim();
315
+ lineIdx++;
316
+ continue;
317
+ }
318
+ if (cleaned.startsWith("with check ")) {
319
+ if (withCheck !== void 0) {
320
+ throw new Error(`Line ${lineIdx + 1}: Duplicate 'with check' in policy`);
321
+ }
322
+ withCheck = cleaned.slice(11).trim();
323
+ lineIdx++;
324
+ continue;
325
+ }
326
+ break;
327
+ }
328
+ if (command === void 0) {
329
+ throw new Error(`Line ${startLine + 1}: Policy is missing 'for <command>'`);
330
+ }
331
+ const policy = {
332
+ name: policyName,
333
+ table: tableIdent,
334
+ command,
335
+ ...using !== void 0 && { using },
336
+ ...withCheck !== void 0 && { withCheck },
337
+ ...toRoles !== void 0 && toRoles.length > 0 && { to: toRoles }
338
+ };
339
+ return { policy, nextLineIndex: lineIdx };
340
+ }
261
341
  function parseTableBlock(startLine) {
262
342
  const firstLine = cleanLine(lines[startLine]);
263
343
  const match = firstLine.match(/^table\s+(\w+)\s*\{\s*$/);
@@ -301,6 +381,7 @@ function parseSchema(source) {
301
381
  };
302
382
  return lineIdx;
303
383
  }
384
+ const policyDeclRegex = /^policy\s+"([^"]*)"\s+on\s+\w+/;
304
385
  while (currentLine < lines.length) {
305
386
  const cleaned = cleanLine(lines[currentLine]);
306
387
  if (!cleaned) {
@@ -309,16 +390,31 @@ function parseSchema(source) {
309
390
  }
310
391
  if (cleaned.startsWith("table ")) {
311
392
  currentLine = parseTableBlock(currentLine);
393
+ currentLine++;
394
+ } else if (policyDeclRegex.test(cleaned)) {
395
+ const { policy, nextLineIndex } = parsePolicyBlock(currentLine);
396
+ policyList.push({ policy, startLine: currentLine + 1 });
397
+ currentLine = nextLineIndex;
312
398
  } else {
313
- throw new Error(`Line ${currentLine + 1}: Unexpected content '${cleaned}'. Expected table definition.`);
399
+ throw new Error(`Line ${currentLine + 1}: Unexpected content '${cleaned}'. Expected table or policy definition.`);
400
+ }
401
+ }
402
+ for (const { policy, startLine } of policyList) {
403
+ if (!tables[policy.table]) {
404
+ throw new Error(`Line ${startLine}: Policy "${policy.name}" references undefined table "${policy.table}"`);
314
405
  }
315
- currentLine++;
406
+ if (!tables[policy.table].policies) {
407
+ tables[policy.table].policies = [];
408
+ }
409
+ tables[policy.table].policies.push(policy);
316
410
  }
317
411
  return { tables };
318
412
  }
413
+ var POLICY_COMMANDS;
319
414
  var init_parser = __esm({
320
415
  "node_modules/@xubylele/schema-forge-core/dist/core/parser.js"() {
321
416
  "use strict";
417
+ POLICY_COMMANDS = ["select", "insert", "update", "delete", "all"];
322
418
  }
323
419
  });
324
420
 
@@ -498,6 +594,22 @@ function resolveSchemaPrimaryKey(table) {
498
594
  function normalizeNullable(nullable) {
499
595
  return nullable ?? true;
500
596
  }
597
+ function normalizePolicyExpression(s) {
598
+ return (s ?? "").trim().replace(/\s+/g, " ");
599
+ }
600
+ function policyEquals(oldP, newP) {
601
+ if (oldP.command !== newP.command)
602
+ return false;
603
+ if (normalizePolicyExpression(oldP.using) !== normalizePolicyExpression(newP.using))
604
+ return false;
605
+ if (normalizePolicyExpression(oldP.withCheck) !== normalizePolicyExpression(newP.withCheck))
606
+ return false;
607
+ const oldTo = (oldP.to ?? []).slice().sort().join(",");
608
+ const newTo = (newP.to ?? []).slice().sort().join(",");
609
+ if (oldTo !== newTo)
610
+ return false;
611
+ return true;
612
+ }
501
613
  function diffSchemas(oldState, newSchema) {
502
614
  const operations = [];
503
615
  const oldTableNames = getTableNamesFromState(oldState);
@@ -674,6 +786,37 @@ function diffSchemas(oldState, newSchema) {
674
786
  }
675
787
  }
676
788
  }
789
+ const oldPolicyNamesByTable = (t) => new Set(Object.keys(t.policies ?? {}));
790
+ const newPolicyListByTable = (t) => t.policies ?? [];
791
+ for (const tableName of commonTableNames) {
792
+ const newTable = newSchema.tables[tableName];
793
+ const oldTable = oldState.tables[tableName];
794
+ if (!newTable || !oldTable)
795
+ continue;
796
+ const oldPolicyNames = oldPolicyNamesByTable(oldTable);
797
+ const newPolicies = newPolicyListByTable(newTable);
798
+ const newPolicyNames = new Set(newPolicies.map((p) => p.name));
799
+ const sortedNewPolicyNames = Array.from(newPolicyNames).sort((a, b) => a.localeCompare(b));
800
+ const sortedOldPolicyNames = Array.from(oldPolicyNames).sort((a, b) => a.localeCompare(b));
801
+ for (const policyName of sortedNewPolicyNames) {
802
+ const policy = newPolicies.find((p) => p.name === policyName);
803
+ if (!policy)
804
+ continue;
805
+ if (!oldPolicyNames.has(policyName)) {
806
+ operations.push({ kind: "create_policy", tableName, policy });
807
+ } else {
808
+ const oldP = oldTable.policies?.[policyName];
809
+ if (oldP && !policyEquals(oldP, policy)) {
810
+ operations.push({ kind: "modify_policy", tableName, policyName, policy });
811
+ }
812
+ }
813
+ }
814
+ for (const policyName of sortedOldPolicyNames) {
815
+ if (!newPolicyNames.has(policyName)) {
816
+ operations.push({ kind: "drop_policy", tableName, policyName });
817
+ }
818
+ }
819
+ }
677
820
  for (const tableName of sortedOldTableNames) {
678
821
  if (!newTableNames.has(tableName)) {
679
822
  operations.push({
@@ -774,6 +917,7 @@ function validateSchema(schema) {
774
917
  const table = schema.tables[tableName];
775
918
  validateTableColumns(tableName, table, schema.tables);
776
919
  }
920
+ validatePolicies(schema);
777
921
  }
778
922
  function validateDuplicateTables(schema) {
779
923
  const tableNames = Object.keys(schema.tables);
@@ -829,10 +973,37 @@ function validateTableColumns(tableName, table, allTables) {
829
973
  }
830
974
  }
831
975
  }
832
- var VALID_BASE_COLUMN_TYPES;
976
+ function validatePolicies(schema) {
977
+ for (const tableName in schema.tables) {
978
+ const table = schema.tables[tableName];
979
+ if (!table.policies?.length)
980
+ continue;
981
+ for (const policy of table.policies) {
982
+ if (!schema.tables[policy.table]) {
983
+ throw new Error(`Policy "${policy.name}" on table "${tableName}": referenced table "${policy.table}" does not exist`);
984
+ }
985
+ if (!VALID_POLICY_COMMANDS.includes(policy.command)) {
986
+ throw new Error(`Policy "${policy.name}" on table "${tableName}": invalid command "${policy.command}". Expected: select, insert, update, delete, or all`);
987
+ }
988
+ const hasUsing = policy.using !== void 0 && String(policy.using).trim() !== "";
989
+ const hasWithCheck = policy.withCheck !== void 0 && String(policy.withCheck).trim() !== "";
990
+ if (!hasUsing && !hasWithCheck) {
991
+ throw new Error(`Policy "${policy.name}" on table "${tableName}": must have at least one of "using" or "with check"`);
992
+ }
993
+ }
994
+ }
995
+ }
996
+ var VALID_POLICY_COMMANDS, VALID_BASE_COLUMN_TYPES;
833
997
  var init_validator = __esm({
834
998
  "node_modules/@xubylele/schema-forge-core/dist/core/validator.js"() {
835
999
  "use strict";
1000
+ VALID_POLICY_COMMANDS = [
1001
+ "select",
1002
+ "insert",
1003
+ "update",
1004
+ "delete",
1005
+ "all"
1006
+ ];
836
1007
  VALID_BASE_COLUMN_TYPES = [
837
1008
  "uuid",
838
1009
  "varchar",
@@ -926,6 +1097,12 @@ function classifyOperation(operation) {
926
1097
  return "DESTRUCTIVE";
927
1098
  case "add_primary_key_constraint":
928
1099
  return "SAFE";
1100
+ case "create_policy":
1101
+ return "SAFE";
1102
+ case "drop_policy":
1103
+ return "DESTRUCTIVE";
1104
+ case "modify_policy":
1105
+ return "WARNING";
929
1106
  default:
930
1107
  const _exhaustive = operation;
931
1108
  return _exhaustive;
@@ -1048,6 +1225,22 @@ function checkOperationSafety(operation) {
1048
1225
  message: "Primary key constraint removed",
1049
1226
  operationKind: operation.kind
1050
1227
  };
1228
+ case "drop_policy":
1229
+ return {
1230
+ safetyLevel,
1231
+ code: "DROP_POLICY",
1232
+ table: operation.tableName,
1233
+ message: "Policy removed",
1234
+ operationKind: operation.kind
1235
+ };
1236
+ case "modify_policy":
1237
+ return {
1238
+ safetyLevel,
1239
+ code: "MODIFY_POLICY",
1240
+ table: operation.tableName,
1241
+ message: "Policy expression changed",
1242
+ operationKind: operation.kind
1243
+ };
1051
1244
  default:
1052
1245
  return null;
1053
1246
  }
@@ -1222,9 +1415,21 @@ async function schemaToState(schema) {
1222
1415
  ...column.foreignKey !== void 0 && { foreignKey: column.foreignKey }
1223
1416
  };
1224
1417
  }
1418
+ let policies;
1419
+ if (table.policies?.length) {
1420
+ policies = {};
1421
+ for (const p of table.policies) {
1422
+ policies[p.name] = {
1423
+ command: p.command,
1424
+ ...p.using !== void 0 && { using: p.using },
1425
+ ...p.withCheck !== void 0 && { withCheck: p.withCheck }
1426
+ };
1427
+ }
1428
+ }
1225
1429
  tables[tableName] = {
1226
1430
  columns,
1227
- ...primaryKeyColumn !== null && { primaryKey: primaryKeyColumn }
1431
+ ...primaryKeyColumn !== null && { primaryKey: primaryKeyColumn },
1432
+ ...policies !== void 0 && { policies }
1228
1433
  };
1229
1434
  }
1230
1435
  return {
@@ -1259,9 +1464,20 @@ var init_state_manager = __esm({
1259
1464
  });
1260
1465
 
1261
1466
  // node_modules/@xubylele/schema-forge-core/dist/generator/sql-generator.js
1467
+ function generateEnableRls(tableName) {
1468
+ return `ALTER TABLE ${tableName} ENABLE ROW LEVEL SECURITY;`;
1469
+ }
1262
1470
  function generateSql(diff2, provider, sqlConfig) {
1263
1471
  const statements = [];
1472
+ const enabledRlsTables = /* @__PURE__ */ new Set();
1264
1473
  for (const operation of diff2.operations) {
1474
+ if (operation.kind === "create_policy" || operation.kind === "modify_policy") {
1475
+ const tableName = operation.tableName;
1476
+ if (!enabledRlsTables.has(tableName)) {
1477
+ statements.push(generateEnableRls(tableName));
1478
+ enabledRlsTables.add(tableName);
1479
+ }
1480
+ }
1265
1481
  const sql = generateOperation(operation, provider, sqlConfig);
1266
1482
  if (sql) {
1267
1483
  statements.push(sql);
@@ -1291,6 +1507,12 @@ function generateOperation(operation, provider, sqlConfig) {
1291
1507
  return generateDropPrimaryKeyConstraint(operation.tableName);
1292
1508
  case "add_primary_key_constraint":
1293
1509
  return generateAddPrimaryKeyConstraint(operation.tableName, operation.columnName);
1510
+ case "create_policy":
1511
+ return generateCreatePolicy(operation.tableName, operation.policy);
1512
+ case "drop_policy":
1513
+ return generateDropPolicy(operation.tableName, operation.policyName);
1514
+ case "modify_policy":
1515
+ return generateModifyPolicy(operation.tableName, operation.policyName, operation.policy);
1294
1516
  }
1295
1517
  }
1296
1518
  function generateCreateTable(table, provider, sqlConfig) {
@@ -1371,6 +1593,28 @@ function generateAlterColumnNullability(tableName, columnName, toNullable) {
1371
1593
  }
1372
1594
  return `ALTER TABLE ${tableName} ALTER COLUMN ${columnName} SET NOT NULL;`;
1373
1595
  }
1596
+ function generateCreatePolicy(tableName, policy) {
1597
+ const command = policy.command === "all" ? "ALL" : policy.command.toUpperCase();
1598
+ const parts = [`CREATE POLICY "${policy.name}" ON ${tableName} FOR ${command}`];
1599
+ if (policy.to !== void 0 && policy.to.length > 0) {
1600
+ parts.push(`TO ${policy.to.join(", ")}`);
1601
+ }
1602
+ if (policy.using !== void 0 && policy.using !== "") {
1603
+ parts.push(`USING (${policy.using})`);
1604
+ }
1605
+ if (policy.withCheck !== void 0 && policy.withCheck !== "") {
1606
+ parts.push(`WITH CHECK (${policy.withCheck})`);
1607
+ }
1608
+ return parts.join(" ") + ";";
1609
+ }
1610
+ function generateDropPolicy(tableName, policyName) {
1611
+ return `DROP POLICY "${policyName}" ON ${tableName};`;
1612
+ }
1613
+ function generateModifyPolicy(tableName, policyName, policy) {
1614
+ const drop = generateDropPolicy(tableName, policyName);
1615
+ const create = generateCreatePolicy(tableName, policy);
1616
+ return drop + "\n\n" + create;
1617
+ }
1374
1618
  var init_sql_generator = __esm({
1375
1619
  "node_modules/@xubylele/schema-forge-core/dist/generator/sql-generator.js"() {
1376
1620
  "use strict";
@@ -1924,6 +2168,100 @@ function parseDropTable(stmt) {
1924
2168
  table: normalizeIdentifier(match[1])
1925
2169
  };
1926
2170
  }
2171
+ function parseEnableRls(stmt) {
2172
+ const s = stmt.replace(/\s+/g, " ").trim();
2173
+ const match = s.match(/^alter\s+table\s+(\S+)\s+enable\s+row\s+level\s+security\s*;?$/i);
2174
+ if (!match) {
2175
+ return null;
2176
+ }
2177
+ return {
2178
+ kind: "ENABLE_RLS",
2179
+ table: normalizeIdentifier(match[1])
2180
+ };
2181
+ }
2182
+ function extractBalancedParen(s, start) {
2183
+ if (start < 0 || s[start] !== "(") {
2184
+ return null;
2185
+ }
2186
+ let depth = 1;
2187
+ let i = start + 1;
2188
+ while (i < s.length && depth > 0) {
2189
+ const c = s[i];
2190
+ if (c === "'" && (i === 0 || s[i - 1] !== "\\")) {
2191
+ i++;
2192
+ while (i < s.length && (s[i] !== "'" || s[i - 1] === "\\")) {
2193
+ i++;
2194
+ }
2195
+ i++;
2196
+ continue;
2197
+ }
2198
+ if (c === "(") {
2199
+ depth++;
2200
+ } else if (c === ")") {
2201
+ depth--;
2202
+ }
2203
+ i++;
2204
+ }
2205
+ if (depth !== 0) {
2206
+ return null;
2207
+ }
2208
+ return { content: s.slice(start + 1, i - 1).trim(), endIndex: i };
2209
+ }
2210
+ function parseCreatePolicy(stmt) {
2211
+ const s = stmt.replace(/\s+/g, " ").trim();
2212
+ const quotedMatch = s.match(/^create\s+policy\s+"([^"]+)"\s+on\s+(\S+)\s+for\s+(all|select|insert|update|delete)/i);
2213
+ const unquotedMatch = quotedMatch ? null : s.match(/^create\s+policy\s+(\S+)\s+on\s+(\S+)\s+for\s+(all|select|insert|update|delete)/i);
2214
+ const match = quotedMatch ?? unquotedMatch;
2215
+ if (!match) {
2216
+ return null;
2217
+ }
2218
+ const name = match[1];
2219
+ const table = normalizeIdentifier(match[2]);
2220
+ const command = match[3].toLowerCase();
2221
+ let rest = s.slice(match[0].length).trim();
2222
+ let toRoles;
2223
+ const toMatch = rest.match(/^\s*to\s+/i) ?? rest.match(/\s+to\s+/i);
2224
+ if (toMatch) {
2225
+ const toIdx = toMatch.index;
2226
+ const afterTo = rest.slice(toIdx + toMatch[0].length);
2227
+ const usingStart = afterTo.toLowerCase().indexOf(" using (");
2228
+ const withCheckStart = afterTo.toLowerCase().indexOf(" with check (");
2229
+ const end = usingStart >= 0 && withCheckStart >= 0 ? Math.min(usingStart, withCheckStart) : usingStart >= 0 ? usingStart : withCheckStart >= 0 ? withCheckStart : afterTo.length;
2230
+ const toPart = afterTo.slice(0, end).trim();
2231
+ if (toPart) {
2232
+ toRoles = toPart.split(/[\s,]+/).map((r) => r.trim()).filter(Boolean);
2233
+ }
2234
+ rest = rest.slice(0, toIdx) + rest.slice(toIdx + toMatch[0].length + end);
2235
+ }
2236
+ let using;
2237
+ let withCheck;
2238
+ const usingIdx = rest.toLowerCase().indexOf("using (");
2239
+ if (usingIdx !== -1) {
2240
+ const openParen = rest.indexOf("(", usingIdx);
2241
+ const parsed = extractBalancedParen(rest, openParen);
2242
+ if (parsed) {
2243
+ using = parsed.content;
2244
+ rest = rest.slice(parsed.endIndex).trim();
2245
+ }
2246
+ }
2247
+ const withCheckIdx = rest.toLowerCase().indexOf("with check (");
2248
+ if (withCheckIdx !== -1) {
2249
+ const openParen = rest.indexOf("(", withCheckIdx);
2250
+ const parsed = extractBalancedParen(rest, openParen);
2251
+ if (parsed) {
2252
+ withCheck = parsed.content;
2253
+ }
2254
+ }
2255
+ return {
2256
+ kind: "CREATE_POLICY",
2257
+ table,
2258
+ name,
2259
+ command,
2260
+ ...using !== void 0 && using !== "" && { using },
2261
+ ...withCheck !== void 0 && withCheck !== "" && { withCheck },
2262
+ ...toRoles !== void 0 && toRoles.length > 0 && { to: toRoles }
2263
+ };
2264
+ }
1927
2265
  function parseMigrationSql(sql) {
1928
2266
  const statements = splitSqlStatements(sql);
1929
2267
  const ops = [];
@@ -1975,7 +2313,9 @@ var init_parse_migration = __esm({
1975
2313
  parseSetDropDefault,
1976
2314
  parseAddDropConstraint,
1977
2315
  parseDropColumn,
1978
- parseDropTable
2316
+ parseDropTable,
2317
+ parseEnableRls,
2318
+ parseCreatePolicy
1979
2319
  ];
1980
2320
  }
1981
2321
  });
@@ -2149,6 +2489,31 @@ function applySqlOps(ops) {
2149
2489
  delete tables[op.table];
2150
2490
  break;
2151
2491
  }
2492
+ case "ENABLE_RLS": {
2493
+ getOrCreateTable(tables, op.table);
2494
+ break;
2495
+ }
2496
+ case "CREATE_POLICY": {
2497
+ const table = getOrCreateTable(tables, op.table);
2498
+ if (!table.policies) {
2499
+ table.policies = [];
2500
+ }
2501
+ const policy = {
2502
+ name: op.name,
2503
+ table: op.table,
2504
+ command: op.command,
2505
+ ...op.using !== void 0 && { using: op.using },
2506
+ ...op.withCheck !== void 0 && { withCheck: op.withCheck },
2507
+ ...op.to !== void 0 && op.to.length > 0 && { to: op.to }
2508
+ };
2509
+ const existing = table.policies.findIndex((p) => p.name === op.name);
2510
+ if (existing >= 0) {
2511
+ table.policies[existing] = policy;
2512
+ } else {
2513
+ table.policies.push(policy);
2514
+ }
2515
+ break;
2516
+ }
2152
2517
  }
2153
2518
  }
2154
2519
  const schema = { tables };
@@ -2177,17 +2542,39 @@ function renderColumn(column) {
2177
2542
  }
2178
2543
  return ` ${parts.join(" ")}`;
2179
2544
  }
2545
+ function renderPolicy(policy) {
2546
+ const lines = [
2547
+ `policy "${policy.name}" on ${policy.table}`,
2548
+ `for ${policy.command}`
2549
+ ];
2550
+ if (policy.to !== void 0 && policy.to.length > 0) {
2551
+ lines.push(`to ${policy.to.join(" ")}`);
2552
+ }
2553
+ if (policy.using !== void 0 && policy.using !== "") {
2554
+ lines.push(`using ${policy.using}`);
2555
+ }
2556
+ if (policy.withCheck !== void 0 && policy.withCheck !== "") {
2557
+ lines.push(`with check ${policy.withCheck}`);
2558
+ }
2559
+ return lines.join("\n");
2560
+ }
2180
2561
  function schemaToDsl(schema) {
2181
2562
  const tableNames = Object.keys(schema.tables).sort((left, right) => left.localeCompare(right));
2182
- const blocks = tableNames.map((tableName) => {
2563
+ const blocks = [];
2564
+ for (const tableName of tableNames) {
2183
2565
  const table = schema.tables[tableName];
2184
- const lines = [`table ${table.name} {`];
2566
+ const tableLines = [`table ${table.name} {`];
2185
2567
  for (const column of table.columns) {
2186
- lines.push(renderColumn(column));
2568
+ tableLines.push(renderColumn(column));
2187
2569
  }
2188
- lines.push("}");
2189
- return lines.join("\n");
2190
- });
2570
+ tableLines.push("}");
2571
+ blocks.push(tableLines.join("\n"));
2572
+ if (table.policies?.length) {
2573
+ for (const policy of table.policies) {
2574
+ blocks.push(renderPolicy(policy));
2575
+ }
2576
+ }
2577
+ }
2191
2578
  if (blocks.length === 0) {
2192
2579
  return "# SchemaForge schema definition\n";
2193
2580
  }
@@ -2680,9 +3067,11 @@ __export(dist_exports, {
2680
3067
  parseAddDropConstraint: () => parseAddDropConstraint,
2681
3068
  parseAlterColumnType: () => parseAlterColumnType,
2682
3069
  parseAlterTableAddColumn: () => parseAlterTableAddColumn,
3070
+ parseCreatePolicy: () => parseCreatePolicy,
2683
3071
  parseCreateTable: () => parseCreateTable,
2684
3072
  parseDropColumn: () => parseDropColumn,
2685
3073
  parseDropTable: () => parseDropTable,
3074
+ parseEnableRls: () => parseEnableRls,
2686
3075
  parseMigrationSql: () => parseMigrationSql,
2687
3076
  parseSchema: () => parseSchema,
2688
3077
  parseSetDropDefault: () => parseSetDropDefault,
@@ -2941,13 +3330,9 @@ async function withPostgresQueryExecutor(connectionString, run) {
2941
3330
 
2942
3331
  // src/utils/exitCodes.ts
2943
3332
  var EXIT_CODES = {
2944
- /** Successful operation */
2945
3333
  SUCCESS: 0,
2946
- /** Validation error (invalid DSL syntax, config errors, missing files, etc.) */
2947
3334
  VALIDATION_ERROR: 1,
2948
- /** Drift detected - Reserved for future use when comparing actual DB state vs schema */
2949
3335
  DRIFT_DETECTED: 2,
2950
- /** Destructive operation detected in CI environment without --force */
2951
3336
  CI_DESTRUCTIVE: 3
2952
3337
  };
2953
3338
  function shouldFailCIDestructive(isCIEnvironment, hasDestructiveFindings2, isForceEnabled) {