ddlforge 0.1.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.
Files changed (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +326 -0
  3. package/dist/bin/ddlforge.d.ts +6 -0
  4. package/dist/bin/ddlforge.d.ts.map +1 -0
  5. package/dist/bin/ddlforge.js +14 -0
  6. package/dist/bin/ddlforge.js.map +1 -0
  7. package/dist/src/cli.d.ts +21 -0
  8. package/dist/src/cli.d.ts.map +1 -0
  9. package/dist/src/cli.js +262 -0
  10. package/dist/src/cli.js.map +1 -0
  11. package/dist/src/engine/analyzer.d.ts +30 -0
  12. package/dist/src/engine/analyzer.d.ts.map +1 -0
  13. package/dist/src/engine/analyzer.js +76 -0
  14. package/dist/src/engine/analyzer.js.map +1 -0
  15. package/dist/src/engine/locks.d.ts +26 -0
  16. package/dist/src/engine/locks.d.ts.map +1 -0
  17. package/dist/src/engine/locks.js +136 -0
  18. package/dist/src/engine/locks.js.map +1 -0
  19. package/dist/src/index.d.ts +12 -0
  20. package/dist/src/index.d.ts.map +1 -0
  21. package/dist/src/index.js +17 -0
  22. package/dist/src/index.js.map +1 -0
  23. package/dist/src/lexer/sqlTokenizer.d.ts +40 -0
  24. package/dist/src/lexer/sqlTokenizer.d.ts.map +1 -0
  25. package/dist/src/lexer/sqlTokenizer.js +494 -0
  26. package/dist/src/lexer/sqlTokenizer.js.map +1 -0
  27. package/dist/src/lexer/tokens.d.ts +35 -0
  28. package/dist/src/lexer/tokens.d.ts.map +1 -0
  29. package/dist/src/lexer/tokens.js +81 -0
  30. package/dist/src/lexer/tokens.js.map +1 -0
  31. package/dist/src/reporters/index.d.ts +7 -0
  32. package/dist/src/reporters/index.d.ts.map +1 -0
  33. package/dist/src/reporters/index.js +7 -0
  34. package/dist/src/reporters/index.js.map +1 -0
  35. package/dist/src/reporters/json.d.ts +20 -0
  36. package/dist/src/reporters/json.d.ts.map +1 -0
  37. package/dist/src/reporters/json.js +35 -0
  38. package/dist/src/reporters/json.js.map +1 -0
  39. package/dist/src/reporters/markdown.d.ts +6 -0
  40. package/dist/src/reporters/markdown.d.ts.map +1 -0
  41. package/dist/src/reporters/markdown.js +70 -0
  42. package/dist/src/reporters/markdown.js.map +1 -0
  43. package/dist/src/reporters/terminal.d.ts +10 -0
  44. package/dist/src/reporters/terminal.d.ts.map +1 -0
  45. package/dist/src/reporters/terminal.js +109 -0
  46. package/dist/src/reporters/terminal.js.map +1 -0
  47. package/dist/src/rules/addColumnNotNull.d.ts +9 -0
  48. package/dist/src/rules/addColumnNotNull.d.ts.map +1 -0
  49. package/dist/src/rules/addColumnNotNull.js +131 -0
  50. package/dist/src/rules/addColumnNotNull.js.map +1 -0
  51. package/dist/src/rules/foreignKeyNotValid.d.ts +9 -0
  52. package/dist/src/rules/foreignKeyNotValid.d.ts.map +1 -0
  53. package/dist/src/rules/foreignKeyNotValid.js +119 -0
  54. package/dist/src/rules/foreignKeyNotValid.js.map +1 -0
  55. package/dist/src/rules/index.d.ts +14 -0
  56. package/dist/src/rules/index.d.ts.map +1 -0
  57. package/dist/src/rules/index.js +28 -0
  58. package/dist/src/rules/index.js.map +1 -0
  59. package/dist/src/rules/indexConcurrently.d.ts +9 -0
  60. package/dist/src/rules/indexConcurrently.d.ts.map +1 -0
  61. package/dist/src/rules/indexConcurrently.js +92 -0
  62. package/dist/src/rules/indexConcurrently.js.map +1 -0
  63. package/dist/src/rules/prismaRenameDropAdd.d.ts +11 -0
  64. package/dist/src/rules/prismaRenameDropAdd.d.ts.map +1 -0
  65. package/dist/src/rules/prismaRenameDropAdd.js +144 -0
  66. package/dist/src/rules/prismaRenameDropAdd.js.map +1 -0
  67. package/dist/src/rules/setNotNullFullScan.d.ts +9 -0
  68. package/dist/src/rules/setNotNullFullScan.d.ts.map +1 -0
  69. package/dist/src/rules/setNotNullFullScan.js +93 -0
  70. package/dist/src/rules/setNotNullFullScan.js.map +1 -0
  71. package/dist/src/rules/transactionTrap.d.ts +9 -0
  72. package/dist/src/rules/transactionTrap.d.ts.map +1 -0
  73. package/dist/src/rules/transactionTrap.js +104 -0
  74. package/dist/src/rules/transactionTrap.js.map +1 -0
  75. package/dist/src/rules/types.d.ts +36 -0
  76. package/dist/src/rules/types.d.ts.map +1 -0
  77. package/dist/src/rules/types.js +5 -0
  78. package/dist/src/rules/types.js.map +1 -0
  79. package/dist/src/rules/unbatchedBackfill.d.ts +9 -0
  80. package/dist/src/rules/unbatchedBackfill.d.ts.map +1 -0
  81. package/dist/src/rules/unbatchedBackfill.js +58 -0
  82. package/dist/src/rules/unbatchedBackfill.js.map +1 -0
  83. package/package.json +44 -0
@@ -0,0 +1,58 @@
1
+ /**
2
+ * ddlforge Rule: Unbatched DML in Migration (Bare UPDATE/DELETE)
3
+ *
4
+ * Severity: WARNING
5
+ * Lock: ROW EXCLUSIVE
6
+ */
7
+ import { PostgresLockLevel } from '../engine/locks.js';
8
+ export const unbatchedBackfillRule = {
9
+ id: 'unbatched-dml',
10
+ name: 'Unbatched DML in Migration',
11
+ description: 'Bare UPDATE and DELETE statements in migrations can lock millions of rows, bloat WAL, and block autovacuum.',
12
+ defaultSeverity: 'WARNING',
13
+ lockLevel: PostgresLockLevel.ROW_EXCLUSIVE,
14
+ check(context) {
15
+ const findings = [];
16
+ for (const stmt of context.statements) {
17
+ if (stmt.hasIgnore(this.id))
18
+ continue;
19
+ const tokens = stmt.tokens;
20
+ if (tokens.length < 2)
21
+ continue;
22
+ const firstVal = tokens[0].value;
23
+ const isUpdate = firstVal === 'UPDATE';
24
+ const isDelete = firstVal === 'DELETE' && tokens[1]?.value === 'FROM';
25
+ if (!isUpdate && !isDelete)
26
+ continue;
27
+ // Check for batching comments: -- batch, -- batched, -- batch-size
28
+ const hasBatchComment = stmt.comments.some(c => /batch/i.test(c));
29
+ if (hasBatchComment)
30
+ continue;
31
+ // Check if statement contains LIMIT inside a subquery
32
+ const hasLimit = tokens.some(t => t.value === 'LIMIT');
33
+ if (hasLimit)
34
+ continue;
35
+ // Check if WHERE clause exists
36
+ const hasWhere = tokens.some(t => t.value === 'WHERE');
37
+ const opName = isUpdate ? 'UPDATE' : 'DELETE';
38
+ const tableName = isUpdate ? (tokens[1]?.raw ?? 'table') : (tokens[2]?.raw ?? 'table');
39
+ findings.push({
40
+ ruleId: this.id,
41
+ ruleName: this.name,
42
+ severity: !hasWhere ? 'BLOCKER' : this.defaultSeverity,
43
+ lockLevel: this.lockLevel,
44
+ message: `Bare ${opName} statement on table "${tableName}" without batching or LIMIT.`,
45
+ detail: `Unbatched ${opName} statements in migrations hold ROW EXCLUSIVE locks on affected rows for the duration of the migration transaction, ` +
46
+ 'generating massive WAL volume and risking transaction timeouts.',
47
+ suggestion: 'Migrate large backfills out-of-band using asynchronous background jobs in small batches (1,000–5,000 rows). ' +
48
+ 'If this operation targets a small static/lookup table, add `-- ddlforge-ignore unbatched-dml`.',
49
+ file: context.filePath,
50
+ line: stmt.startLine,
51
+ column: stmt.startColumn,
52
+ codeSnippet: stmt.raw,
53
+ });
54
+ }
55
+ return findings;
56
+ },
57
+ };
58
+ //# sourceMappingURL=unbatchedBackfill.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unbatchedBackfill.js","sourceRoot":"","sources":["../../../src/rules/unbatchedBackfill.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,CAAC,MAAM,qBAAqB,GAAS;IACzC,EAAE,EAAE,eAAe;IACnB,IAAI,EAAE,4BAA4B;IAClC,WAAW,EAAE,6GAA6G;IAC1H,eAAe,EAAE,SAAS;IAC1B,SAAS,EAAE,iBAAiB,CAAC,aAAa;IAE1C,KAAK,CAAC,OAAoB;QACxB,MAAM,QAAQ,GAAc,EAAE,CAAC;QAE/B,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;YACtC,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;gBAAE,SAAS;YAEtC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;YAC3B,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;gBAAE,SAAS;YAEhC,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;YACjC,MAAM,QAAQ,GAAG,QAAQ,KAAK,QAAQ,CAAC;YACvC,MAAM,QAAQ,GAAG,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,MAAM,CAAC;YAEtE,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ;gBAAE,SAAS;YAErC,mEAAmE;YACnE,MAAM,eAAe,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YAClE,IAAI,eAAe;gBAAE,SAAS;YAE9B,sDAAsD;YACtD,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,OAAO,CAAC,CAAC;YACvD,IAAI,QAAQ;gBAAE,SAAS;YAEvB,+BAA+B;YAC/B,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,OAAO,CAAC,CAAC;YACvD,MAAM,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;YAE9C,MAAM,SAAS,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,OAAO,CAAC,CAAC;YAEvF,QAAQ,CAAC,IAAI,CAAC;gBACZ,MAAM,EAAE,IAAI,CAAC,EAAE;gBACf,QAAQ,EAAE,IAAI,CAAC,IAAI;gBACnB,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe;gBACtD,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,OAAO,EAAE,QAAQ,MAAM,wBAAwB,SAAS,8BAA8B;gBACtF,MAAM,EACJ,aAAa,MAAM,qHAAqH;oBACxI,iEAAiE;gBACnE,UAAU,EACR,8GAA8G;oBAC9G,gGAAgG;gBAClG,IAAI,EAAE,OAAO,CAAC,QAAQ;gBACtB,IAAI,EAAE,IAAI,CAAC,SAAS;gBACpB,MAAM,EAAE,IAAI,CAAC,WAAW;gBACxB,WAAW,EAAE,IAAI,CAAC,GAAG;aACtB,CAAC,CAAC;QACL,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "ddlforge",
3
+ "version": "0.1.0",
4
+ "description": "Ultra-fast, zero-runtime-dependency Postgres migration lock linter and data-loss prevention engine CLI in Node.js 20+ and TypeScript",
5
+ "type": "module",
6
+ "main": "./dist/src/index.js",
7
+ "types": "./dist/src/index.d.ts",
8
+ "bin": {
9
+ "ddlforge": "dist/bin/ddlforge.js"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc && node -e \"const fs=require('fs'); try{fs.chmodSync('dist/bin/ddlforge.js', 0o755)}catch{}\"",
18
+ "test:build": "tsc -p tsconfig.test.json",
19
+ "test": "npm run test:build && node --test dist/test/analyzer.test.js",
20
+ "test:watch": "node --test --watch dist/test/analyzer.test.js",
21
+ "prepack": "node -e \"const fs=require('fs'); try{fs.rmSync('dist/test', {recursive:true, force:true})}catch{}\"",
22
+ "prepublishOnly": "npm run build && node -e \"const fs=require('fs'); try{fs.rmSync('dist/test', {recursive:true, force:true})}catch{}\""
23
+ },
24
+ "keywords": [
25
+ "postgres",
26
+ "postgresql",
27
+ "migration",
28
+ "ddl",
29
+ "lock",
30
+ "linter",
31
+ "prisma",
32
+ "drizzle",
33
+ "zero-downtime"
34
+ ],
35
+ "author": "",
36
+ "license": "MIT",
37
+ "engines": {
38
+ "node": ">=20.0.0"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^22.13.10",
42
+ "typescript": "^5.8.2"
43
+ }
44
+ }