ts-prorm-orm 1.2.2 → 2.0.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 (129) hide show
  1. package/CHANGELOG.md +209 -0
  2. package/README.md +32 -24
  3. package/dist/compliance/index.d.ts +2 -0
  4. package/dist/compliance/index.js +13 -1
  5. package/dist/connection-manager.js +13 -1
  6. package/dist/core/expressions.d.ts +58 -0
  7. package/dist/core/expressions.js +83 -0
  8. package/dist/core/hooks-manager.d.ts +179 -0
  9. package/dist/core/hooks-manager.js +291 -0
  10. package/dist/core/types.d.ts +235 -0
  11. package/dist/core/types.js +9 -0
  12. package/dist/decorators/index.d.ts +2 -0
  13. package/dist/decorators/index.js +11 -1
  14. package/dist/diagrams/chen-diagram.d.ts +15 -12
  15. package/dist/diagrams/chen-diagram.js +76 -85
  16. package/dist/diagrams/class-diagram.d.ts +9 -8
  17. package/dist/diagrams/class-diagram.js +60 -82
  18. package/dist/diagrams/core/index.d.ts +13 -0
  19. package/dist/diagrams/core/index.js +33 -0
  20. package/dist/diagrams/core/layout.d.ts +104 -0
  21. package/dist/diagrams/core/layout.js +223 -0
  22. package/dist/diagrams/{svg-dom.d.ts → core/svg.d.ts} +8 -20
  23. package/dist/diagrams/{svg-dom.js → core/svg.js} +21 -29
  24. package/dist/diagrams/core/theme.d.ts +84 -0
  25. package/dist/diagrams/core/theme.js +62 -0
  26. package/dist/diagrams/dependency-diagram.d.ts +15 -14
  27. package/dist/diagrams/dependency-diagram.js +71 -105
  28. package/dist/diagrams/er-diagram.d.ts +13 -26
  29. package/dist/diagrams/er-diagram.js +180 -259
  30. package/dist/diagrams/flow-diagram.d.ts +16 -11
  31. package/dist/diagrams/flow-diagram.js +96 -91
  32. package/dist/diagrams/gantt-diagram.d.ts +10 -5
  33. package/dist/diagrams/gantt-diagram.js +77 -103
  34. package/dist/diagrams/index-diagram.d.ts +10 -10
  35. package/dist/diagrams/index-diagram.js +106 -116
  36. package/dist/diagrams/index.d.ts +6 -9
  37. package/dist/diagrams/index.js +9 -24
  38. package/dist/diagrams/migration-diagram.d.ts +8 -4
  39. package/dist/diagrams/migration-diagram.js +189 -161
  40. package/dist/diagrams/model-diagram.d.ts +34 -34
  41. package/dist/diagrams/model-diagram.js +106 -485
  42. package/dist/diagrams/package-diagram.d.ts +9 -6
  43. package/dist/diagrams/package-diagram.js +64 -101
  44. package/dist/diagrams/relational-diagram.d.ts +12 -10
  45. package/dist/diagrams/relational-diagram.js +87 -114
  46. package/dist/diagrams/schemadoc-diagram.d.ts +8 -8
  47. package/dist/diagrams/schemadoc-diagram.js +71 -93
  48. package/dist/diagrams/sequence-diagram.d.ts +10 -10
  49. package/dist/diagrams/sequence-diagram.js +64 -89
  50. package/dist/diagrams/state-diagram.d.ts +10 -9
  51. package/dist/diagrams/state-diagram.js +69 -92
  52. package/dist/diagrams/tree-diagram.d.ts +11 -5
  53. package/dist/diagrams/tree-diagram.js +41 -65
  54. package/dist/dialects/clickhouse/index.js +15 -0
  55. package/dist/dialects/cockroachdb/index.d.ts +1 -1
  56. package/dist/dialects/cockroachdb/index.js +32 -63
  57. package/dist/dialects/db2/index.js +23 -8
  58. package/dist/dialects/dialect.d.ts +29 -5
  59. package/dist/dialects/duckdb/index.js +9 -2
  60. package/dist/dialects/hana/index.js +22 -5
  61. package/dist/dialects/lock-clause-helper.d.ts +167 -0
  62. package/dist/dialects/lock-clause-helper.js +316 -0
  63. package/dist/dialects/mariadb/index.d.ts +7 -4
  64. package/dist/dialects/mariadb/index.js +36 -60
  65. package/dist/dialects/mssql/index.js +34 -3
  66. package/dist/dialects/mysql/index.d.ts +1 -1
  67. package/dist/dialects/mysql/index.js +17 -45
  68. package/dist/dialects/oracle/index.d.ts +14 -3
  69. package/dist/dialects/oracle/index.js +66 -36
  70. package/dist/dialects/order-expression-helper.d.ts +72 -0
  71. package/dist/dialects/order-expression-helper.js +154 -0
  72. package/dist/dialects/partial-index.d.ts +52 -0
  73. package/dist/dialects/partial-index.js +251 -0
  74. package/dist/dialects/postgres/index.d.ts +1 -1
  75. package/dist/dialects/postgres/index.js +43 -68
  76. package/dist/dialects/redshift/index.js +9 -2
  77. package/dist/dialects/snowflake/index.js +17 -0
  78. package/dist/dialects/spanner/index.js +7 -2
  79. package/dist/dialects/sqlite/driver.d.ts +55 -0
  80. package/dist/dialects/sqlite/driver.js +102 -0
  81. package/dist/dialects/sqlite/index.d.ts +1 -1
  82. package/dist/dialects/sqlite/index.js +49 -27
  83. package/dist/graph/base-graph-dialect.d.ts +24 -3
  84. package/dist/graph/base-graph-dialect.js +43 -2
  85. package/dist/graph/dgraph/index.js +6 -0
  86. package/dist/graph/gremlin/index.d.ts +6 -0
  87. package/dist/graph/gremlin/index.js +12 -1
  88. package/dist/graph/index.d.ts +1 -1
  89. package/dist/graph/neo4j/index.d.ts +2 -0
  90. package/dist/graph/neo4j/index.js +6 -1
  91. package/dist/graph/types.d.ts +12 -1
  92. package/dist/index.d.ts +13 -4
  93. package/dist/index.js +54 -7
  94. package/dist/logging/index.d.ts +10 -0
  95. package/dist/logging/index.js +23 -0
  96. package/dist/logging/query-logging.d.ts +82 -0
  97. package/dist/logging/query-logging.js +102 -0
  98. package/dist/models/decorators.d.ts +39 -3
  99. package/dist/models/decorators.js +74 -0
  100. package/dist/models/eager-load.d.ts +99 -0
  101. package/dist/models/eager-load.js +348 -5
  102. package/dist/models/indexes.d.ts +3 -2
  103. package/dist/models/indexes.js +7 -1
  104. package/dist/models/model.js +69 -43
  105. package/dist/prorm.d.ts +175 -409
  106. package/dist/prorm.js +1142 -557
  107. package/dist/query-builders/order-limit-builder.js +12 -10
  108. package/dist/query-builders/sql-compiler.d.ts +10 -3
  109. package/dist/query-builders/sql-compiler.js +14 -43
  110. package/dist/query-interface.d.ts +2 -1
  111. package/dist/sql-constants.d.ts +44 -0
  112. package/dist/sql-constants.js +138 -1
  113. package/dist/types/index.d.ts +53 -17
  114. package/dist/types/index.js +10 -15
  115. package/dist/types/query-types.d.ts +26 -0
  116. package/dist/types/query-types.js +30 -0
  117. package/package.json +32 -5
  118. package/dist/decorators/belongs-to-many.js +0 -115
  119. package/dist/decorators/belongs-to.js +0 -115
  120. package/dist/decorators/has-many.js +0 -127
  121. package/dist/decorators/has-one.js +0 -116
  122. package/dist/diagrams/palette.d.ts +0 -138
  123. package/dist/diagrams/palette.js +0 -194
  124. package/dist/hooks/hooks-manager.d.ts +0 -189
  125. package/dist/hooks/hooks-manager.js +0 -350
  126. package/dist/hooks/index.d.ts +0 -8
  127. package/dist/hooks/index.js +0 -37
  128. /package/dist/{logging.d.ts → logging/logger.d.ts} +0 -0
  129. /package/dist/{logging.js → logging/logger.js} +0 -0
@@ -1,10 +1,10 @@
1
1
  "use strict";
2
2
  /**
3
- * MigrationDiagram generates an SVG showing database schema changes.
3
+ * MigrationDiagram - generates an SVG showing database schema changes.
4
4
  *
5
5
  * Features:
6
6
  * - Shows tables with their columns (before/after or just new state)
7
- * - Color-coded change indicators: green (added), red (removed), yellow (modified)
7
+ * - Change indicators per column: added, removed, modified
8
8
  * - Migration metadata header with timestamp and description
9
9
  * - Support for multiple migration steps
10
10
  * - Compact field rows with type badges
@@ -57,46 +57,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
57
57
  exports.MigrationDiagram = void 0;
58
58
  const fs = __importStar(require("fs"));
59
59
  const path = __importStar(require("path"));
60
- const palette_1 = require("./palette");
61
- // ─── Industry design system ────────────────────────────────────────────────
62
- // Barlow / JetBrains Mono typography, hairline borders and square "blueprint"
63
- // corners are fixed by the Industry spec; the banner accent + canvas colours
64
- // route through resolveAccent / resolveTheme so --color / --theme keep working.
65
- // Family names are single-quoted so they nest inside double-quoted SVG attrs.
66
- const IND_FONT_HEAD = "'Barlow Condensed', system-ui, sans-serif";
67
- const IND_FONT_BODY = "'Barlow', system-ui, sans-serif";
68
- const IND_FONT_MONO = "ui-monospace, 'JetBrains Mono', 'SF Mono', Menlo, monospace";
69
- const IND_HAIRLINE = 'rgba(29,31,32,0.16)';
70
- const IND_FONT_IMPORT = "@import url('https://fonts.googleapis.com/css2?family=Barlow:wght@400;500;700&family=Barlow+Condensed:wght@400;600&family=JetBrains+Mono&display=swap');";
71
- // ─── Constants ─────────────────────────────────────────────────────────────
72
- const CHANGE_COLORS = {
73
- add: { header: '#065F46', mid: '#059669', light: '#D1FAE5', text: '#065F46', icon: '#059669' },
74
- remove: { header: '#7F1D1D', mid: '#DC2626', light: '#FEE2E2', text: '#7F1D1D', icon: '#DC2626' },
75
- modify: { header: '#92400E', mid: '#D97706', light: '#FEF3C7', text: '#92400E', icon: '#D97706' },
60
+ const svg_1 = require("./core/svg");
61
+ const theme_1 = require("./core/theme");
62
+ const layout_1 = require("./core/layout");
63
+ /** Short ASCII label per change/action kind. No colour distinction. */
64
+ const ACTION_LABEL = {
65
+ add: 'ADD',
66
+ remove: 'DEL',
67
+ modify: 'MOD',
76
68
  };
77
- const TABLE_COLORS = [
78
- { header: '#1E3A8A', mid: '#2563EB', light: '#DBEAFE', text: '#1E3A8A' },
79
- { header: '#5B21B6', mid: '#7C3AED', light: '#EDE9FE', text: '#5B21B6' },
80
- { header: '#065F46', mid: '#059669', light: '#D1FAE5', text: '#065F46' },
81
- { header: '#92400E', mid: '#D97706', light: '#FEF3C7', text: '#92400E' },
82
- { header: '#831843', mid: '#DB2777', light: '#FCE7F3', text: '#831843' },
83
- { header: '#0C4A6E', mid: '#0284C7', light: '#E0F2FE', text: '#0C4A6E' },
84
- ];
85
- // ─── Helpers ─────────────────────────────────────────────────────────────
86
- function xmlEsc(s) {
87
- return String(s ?? '')
88
- .replace(/&/g, '&')
89
- .replace(/</g, '&lt;')
90
- .replace(/>/g, '&gt;')
91
- .replace(/"/g, '&quot;');
92
- }
93
- function typeBadgeColor(type) {
94
- // Industry blueprint: type badges share one muted accent tint so the box
95
- // stays restrained; the type text itself carries the meaning.
96
- void type;
97
- return { text: '#2c455d', bg: '#eef6ff' };
98
- }
99
- // ─── Main class ────────────────────────────────────────────────────────────
100
69
  class MigrationDiagram {
101
70
  constructor(options) {
102
71
  this.opts = {
@@ -108,177 +77,236 @@ class MigrationDiagram {
108
77
  color: options.color ?? '',
109
78
  };
110
79
  }
111
- // ── Compute layout positions ─────────────────────────────────────────
80
+ // -- Compute layout positions -----------------------------------------
112
81
  computeLayout() {
113
82
  const layout = [];
114
83
  let y = this.opts.changes.length > 0 ? MigrationDiagram.BANNER_H + MigrationDiagram.MARGIN : 0;
115
- let maxW = 0;
116
- this.opts.changes.forEach((change, idx) => {
84
+ this.opts.changes.forEach((change) => {
117
85
  const rowCount = Math.max((change.changes ?? []).length, 1);
118
86
  const h = MigrationDiagram.BOX_HDR + rowCount * MigrationDiagram.BOX_ROW + MigrationDiagram.FOOT_H;
119
87
  const x = MigrationDiagram.MARGIN;
120
- layout.push({
121
- change,
122
- x,
123
- y,
124
- w: MigrationDiagram.BOX_W,
125
- h,
126
- colorIdx: idx % TABLE_COLORS.length,
127
- });
128
- maxW = Math.max(maxW, MigrationDiagram.BOX_W);
88
+ layout.push({ change, x, y, w: MigrationDiagram.BOX_W, h });
129
89
  y += h + MigrationDiagram.V_GAP;
130
90
  });
131
91
  return layout;
132
92
  }
133
- // ── Render banner ────────────────────────────────────────────────────
134
- renderBanner(totalW) {
93
+ // -- Render banner -------------------------------------------------------
94
+ renderBanner(svg, totalW) {
135
95
  const H = MigrationDiagram.BANNER_H;
136
96
  const { title, description, timestamp } = this.opts;
137
- // Solid accent banner (blueprint label bar) no gradient, no drop shadow.
138
- const accent = (0, palette_1.resolveAccent)(this.opts.color);
139
- return `
140
- <!-- ═══ BANNER ═══ -->
141
- <rect width="${totalW}" height="${H}" fill="${accent.mid}"/>
142
-
143
- <!-- Database icon (SVG) -->
144
- <g transform="translate(30, 24)">
145
- <ellipse cx="14" cy="8" rx="12" ry="4" fill="white" opacity="0.3"/>
146
- <path d="M2 12v12c0 2.2 5.4 4 12 4s12-1.8 12-4V12" fill="white" opacity="0.9"/>
147
- <path d="M2 16c0 2.2 5.4 4 12 4s12-1.8 12-4" fill="none" stroke="white" stroke-width="1.5" opacity="0.5"/>
148
- <path d="M2 20c0 2.2 5.4 4 12 4s12-1.8 12-4" fill="none" stroke="white" stroke-width="1.5" opacity="0.3"/>
149
- </g>
150
-
151
- <text x="58" y="36" font-family="${IND_FONT_HEAD}" font-size="22" font-weight="600" fill="white">${xmlEsc(title)}</text>
152
- <text x="58" y="54" font-family="${IND_FONT_BODY}" font-size="12" fill="rgba(255,255,255,0.78)">${xmlEsc(description)}</text>
153
- <text x="${totalW - 30}" y="54" font-family="${IND_FONT_MONO}" font-size="11" fill="rgba(255,255,255,0.7)" text-anchor="end">${timestamp}</text>
154
-
155
- <!-- Migration tag (square blueprint chip) -->
156
- <rect x="${totalW - 130}" y="20" width="100" height="26" fill="rgba(255,255,255,0.15)"/>
157
- <text x="${totalW - 80}" y="38" font-family="${IND_FONT_MONO}" font-size="11" font-weight="700" fill="white" text-anchor="middle">MIGRATION</text>`;
97
+ svg.child('rect', { width: totalW, height: H, fill: theme_1.INK.wash, stroke: theme_1.INK.line, 'stroke-width': theme_1.STROKE.hair });
98
+ // Database icon, drawn with plain hairline strokes.
99
+ const g = svg.child('g', { transform: 'translate(30, 24)' });
100
+ g.child('ellipse', { cx: 14, cy: 8, rx: 12, ry: 4, fill: 'none', stroke: theme_1.INK.line, 'stroke-width': theme_1.STROKE.hair });
101
+ g.child('path', {
102
+ d: 'M2 12v12c0 2.2 5.4 4 12 4s12-1.8 12-4V12',
103
+ fill: 'none',
104
+ stroke: theme_1.INK.line,
105
+ 'stroke-width': theme_1.STROKE.hair,
106
+ });
107
+ g.child('path', {
108
+ d: 'M2 16c0 2.2 5.4 4 12 4s12-1.8 12-4',
109
+ fill: 'none',
110
+ stroke: theme_1.INK.line,
111
+ 'stroke-width': theme_1.STROKE.hair,
112
+ });
113
+ svg
114
+ .child('text', { x: 58, y: 36, 'font-family': theme_1.FONT.family, 'font-size': theme_1.FONT.title + 9, 'font-weight': 'bold', fill: theme_1.INK.line })
115
+ .text(title);
116
+ svg
117
+ .child('text', { x: 58, y: 54, 'font-family': theme_1.FONT.family, 'font-size': theme_1.FONT.small + 3, fill: theme_1.INK.muted })
118
+ .text(description);
119
+ svg
120
+ .child('text', {
121
+ x: totalW - 30,
122
+ y: 54,
123
+ 'font-family': theme_1.FONT.mono,
124
+ 'font-size': theme_1.FONT.small + 2,
125
+ fill: theme_1.INK.muted,
126
+ 'text-anchor': 'end',
127
+ })
128
+ .text(timestamp);
129
+ // Migration tag.
130
+ svg.child('rect', { x: totalW - 130, y: 20, width: 100, height: 26, fill: theme_1.INK.ground, stroke: theme_1.INK.line, 'stroke-width': theme_1.STROKE.hair });
131
+ svg
132
+ .child('text', {
133
+ x: totalW - 80,
134
+ y: 38,
135
+ 'font-family': theme_1.FONT.mono,
136
+ 'font-size': theme_1.FONT.small + 2,
137
+ 'font-weight': 'bold',
138
+ fill: theme_1.INK.line,
139
+ 'text-anchor': 'middle',
140
+ })
141
+ .text('MIGRATION');
158
142
  }
159
- // ── Render a table change box ─────────────────────────────────────────
160
- renderChangeBox(item) {
161
- const { change, x, y, w, h, colorIdx } = item;
162
- // Fallback color for unknown/undefined change types so `colors.header` etc.
163
- // never throw.
164
- const colors = CHANGE_COLORS[change.type] ?? {
165
- header: '#334155',
166
- mid: '#475569',
167
- light: '#E2E8F0',
168
- text: '#334155',
169
- icon: '#475569',
170
- };
171
- // Sanitize the table name for use in an SVG element id (must be a valid,
172
- // unique NCName) and escape it wherever it lands in markup/comments.
173
- const safeId = String(change.table ?? 'table').replace(/\W/g, '') + colorIdx;
143
+ // -- Render a table change box --------------------------------------------
144
+ renderChangeBox(svg, item) {
145
+ const { change, x, y, w } = item;
174
146
  const changes = change.changes ?? [];
175
- let out = `\n <!-- Change: ${xmlEsc(change.table)} (${xmlEsc(change.type)}) -->`;
176
- // Box background (white interior, hairline border, square — blueprint).
177
- out += `\n <rect x="${x}" y="${y}" width="${w}" height="${h}" fill="white" stroke="${IND_HAIRLINE}" stroke-width="1"/>`;
178
- // Header with change type indicator (solid, no radius)
179
- out += `\n <rect x="${x}" y="${y}" width="${w}" height="${MigrationDiagram.BOX_HDR}" fill="${colors.header}"/>`;
180
- // Change type badge
181
- out += `\n <rect x="${x + 12}" y="${y + 10}" width="56" height="22" fill="${colors.light}"/>`;
182
- out += `\n <text x="${x + 40}" y="${y + 25}" font-family="${IND_FONT_MONO}" font-size="10" font-weight="700" fill="${colors.text}" text-anchor="middle">${xmlEsc(String(change.type ?? '').toUpperCase())}</text>`;
183
- // Table name
184
- out += `\n <text x="${x + 80}" y="${y + 27}" font-family="${IND_FONT_HEAD}" font-size="15" font-weight="700" fill="white">${xmlEsc(change.table)}</text>`;
185
- // Column changes
147
+ const label = ACTION_LABEL[change.type] ?? change.type.toString().toUpperCase().slice(0, 3);
148
+ // Box background.
149
+ svg.child('rect', { x, y, width: w, height: item.h, fill: theme_1.INK.ground, stroke: theme_1.INK.line, 'stroke-width': theme_1.STROKE.hair });
150
+ // Header.
151
+ svg.child('rect', { x, y, width: w, height: MigrationDiagram.BOX_HDR, fill: theme_1.INK.wash, stroke: theme_1.INK.line, 'stroke-width': theme_1.STROKE.hair });
152
+ // Change type badge.
153
+ svg.child('rect', { x: x + 12, y: y + 10, width: 56, height: 22, fill: theme_1.INK.ground, stroke: theme_1.INK.line, 'stroke-width': theme_1.STROKE.hair });
154
+ svg
155
+ .child('text', {
156
+ x: x + 40,
157
+ y: y + 25,
158
+ 'font-family': theme_1.FONT.mono,
159
+ 'font-size': theme_1.FONT.small,
160
+ 'font-weight': 'bold',
161
+ fill: theme_1.INK.line,
162
+ 'text-anchor': 'middle',
163
+ })
164
+ .text(label);
165
+ // Table name.
166
+ svg
167
+ .child('text', {
168
+ x: x + 80,
169
+ y: y + 27,
170
+ 'font-family': theme_1.FONT.family,
171
+ 'font-size': theme_1.FONT.body + 3,
172
+ 'font-weight': 'bold',
173
+ fill: theme_1.INK.line,
174
+ })
175
+ .text(change.table);
176
+ // Column changes.
186
177
  changes.forEach((col, i) => {
187
178
  const fy = y + MigrationDiagram.BOX_HDR + i * MigrationDiagram.BOX_ROW;
188
- const rowBg = i % 2 === 0 ? '#FFFFFF' : '#F9FAFB';
189
- const tc = typeBadgeColor(col.type);
190
- out += `\n <rect x="${x}" y="${fy}" width="${w}" height="${MigrationDiagram.BOX_ROW}" fill="${rowBg}"/>`;
191
- // Change indicator stripe
192
- const stripeColor = col.action === 'add' ? '#059669' : col.action === 'remove' ? '#DC2626' : '#D97706';
193
- out += `\n <rect x="${x}" y="${fy}" width="4" height="${MigrationDiagram.BOX_ROW}" fill="${stripeColor}"/>`;
194
- // Action icon (SVG)
179
+ const rowBg = i % 2 === 0 ? theme_1.INK.ground : theme_1.INK.wash;
180
+ svg.child('rect', { x, y: fy, width: w, height: MigrationDiagram.BOX_ROW, fill: rowBg });
181
+ // Change indicator stripe.
182
+ svg.child('rect', { x, y: fy, width: 4, height: MigrationDiagram.BOX_ROW, fill: theme_1.INK.line });
183
+ // Action marker (plain circle + ASCII glyph via strokes).
195
184
  const ix = x + 16;
196
185
  const iy = fy + MigrationDiagram.BOX_ROW / 2;
186
+ svg.child('circle', { cx: ix, cy: iy, r: 6, fill: theme_1.INK.ground, stroke: theme_1.INK.line, 'stroke-width': theme_1.STROKE.hair });
197
187
  if (col.action === 'add') {
198
- out += `\n <circle cx="${ix}" cy="${iy}" r="6" fill="${stripeColor}"/>
199
- <path d="M${ix - 3} ${iy}v6M${ix} ${iy - 3}h6" stroke="white" stroke-width="2" fill="none"/>`;
188
+ svg.child('path', { d: `M${ix - 3} ${iy}h6M${ix} ${iy - 3}v6`, stroke: theme_1.INK.line, 'stroke-width': theme_1.STROKE.hair, fill: 'none' });
200
189
  }
201
190
  else if (col.action === 'remove') {
202
- out += `\n <circle cx="${ix}" cy="${iy}" r="6" fill="${stripeColor}"/>
203
- <path d="M${ix - 3} ${iy}h6" stroke="white" stroke-width="2" fill="none"/>`;
191
+ svg.child('path', { d: `M${ix - 3} ${iy}h6`, stroke: theme_1.INK.line, 'stroke-width': theme_1.STROKE.hair, fill: 'none' });
204
192
  }
205
193
  else {
206
- out += `\n <circle cx="${ix}" cy="${iy}" r="6" fill="${stripeColor}"/>
207
- <path d="M${ix - 3} ${iy}h6" stroke="white" stroke-width="2" fill="none"/>
208
- <path d="M${ix} ${iy - 3}v6" stroke="white" stroke-width="2" fill="none"/>`;
194
+ svg.child('path', { d: `M${ix - 3} ${iy}h6`, stroke: theme_1.INK.line, 'stroke-width': theme_1.STROKE.hair, fill: 'none' });
195
+ svg.child('path', { d: `M${ix} ${iy - 3}v6`, stroke: theme_1.INK.line, 'stroke-width': theme_1.STROKE.hair, fill: 'none' });
209
196
  }
210
- // Column name
197
+ // Column name.
211
198
  const nameX = x + 36;
212
- const nameW = col.action === 'modify' && col.oldType ? '600' : '500';
213
- out += `\n <text x="${nameX}" y="${iy + 4}" font-family="${IND_FONT_MONO}" font-size="12" font-weight="${nameW}" fill="#1d1f20">${xmlEsc(col.name)}</text>`;
214
- // Type badge
199
+ svg
200
+ .child('text', {
201
+ x: nameX,
202
+ y: iy + 4,
203
+ 'font-family': theme_1.FONT.mono,
204
+ 'font-size': theme_1.FONT.body + 1,
205
+ 'font-weight': 'bold',
206
+ fill: theme_1.INK.line,
207
+ })
208
+ .text((0, layout_1.truncate)(col.name, x + w - 12 - nameX, theme_1.FONT.body + 1, true));
209
+ // Type badge.
215
210
  if (col.action === 'modify' && col.oldType) {
216
- // Show old → new type
217
- out += `\n <text x="${x + w - 10}" y="${iy + 4}" font-family="${IND_FONT_MONO}" font-size="9" fill="#7a7a7d" text-anchor="end">${xmlEsc(col.oldType)} → ${xmlEsc(col.type)}</text>`;
211
+ svg
212
+ .child('text', {
213
+ x: x + w - 10,
214
+ y: iy + 4,
215
+ 'font-family': theme_1.FONT.mono,
216
+ 'font-size': theme_1.FONT.small - 1,
217
+ fill: theme_1.INK.muted,
218
+ 'text-anchor': 'end',
219
+ })
220
+ .text(`${col.oldType} -> ${col.type}`);
218
221
  }
219
222
  else {
220
- const tbW = col.type.length * 6 + 12;
221
- out += `\n <rect x="${x + w - tbW - 8}" y="${fy + 6}" width="${tbW}" height="16" fill="${tc.bg}"/>`;
222
- out += `\n <text x="${x + w - tbW / 2 - 8}" y="${fy + 18}" font-family="${IND_FONT_MONO}" font-size="9" font-weight="600" fill="${tc.text}" text-anchor="middle">${xmlEsc(col.type.slice(0, 12))}</text>`;
223
+ const label12 = col.type.slice(0, 12);
224
+ const tbW = label12.length * 6 + 12;
225
+ svg.child('rect', { x: x + w - tbW - 8, y: fy + 6, width: tbW, height: 16, fill: theme_1.INK.wash, stroke: theme_1.INK.line, 'stroke-width': theme_1.STROKE.hair });
226
+ svg
227
+ .child('text', {
228
+ x: x + w - tbW / 2 - 8,
229
+ y: fy + 18,
230
+ 'font-family': theme_1.FONT.mono,
231
+ 'font-size': theme_1.FONT.small - 1,
232
+ 'font-weight': 'bold',
233
+ fill: theme_1.INK.line,
234
+ 'text-anchor': 'middle',
235
+ })
236
+ .text(label12);
223
237
  }
224
- // Row separator (hairline)
225
- out += `\n <line x1="${x}" y1="${fy + MigrationDiagram.BOX_ROW}" x2="${x + w}" y2="${fy + MigrationDiagram.BOX_ROW}" stroke="${IND_HAIRLINE}"/>`;
238
+ // Row separator.
239
+ svg.child('line', {
240
+ x1: x,
241
+ y1: fy + MigrationDiagram.BOX_ROW,
242
+ x2: x + w,
243
+ y2: fy + MigrationDiagram.BOX_ROW,
244
+ stroke: theme_1.INK.line,
245
+ 'stroke-width': theme_1.STROKE.hair,
246
+ });
226
247
  });
227
- // Footer
248
+ // Footer.
228
249
  const footY = y + MigrationDiagram.BOX_HDR + changes.length * MigrationDiagram.BOX_ROW;
229
- out += `\n <rect x="${x}" y="${footY}" width="${w}" height="${MigrationDiagram.FOOT_H}" fill="${colors.light}"/>`;
230
- out += `\n <text x="${x + 12}" y="${footY + 18}" font-family="${IND_FONT_HEAD}" font-size="10" fill="${colors.text}" font-weight="600">${changes.length} change${changes.length !== 1 ? 's' : ''}</text>`;
231
- return out;
250
+ svg.child('rect', { x, y: footY, width: w, height: MigrationDiagram.FOOT_H, fill: theme_1.INK.wash });
251
+ svg
252
+ .child('text', {
253
+ x: x + 12,
254
+ y: footY + 18,
255
+ 'font-family': theme_1.FONT.family,
256
+ 'font-size': theme_1.FONT.small,
257
+ 'font-weight': 'bold',
258
+ fill: theme_1.INK.line,
259
+ })
260
+ .text(`${changes.length} change${changes.length !== 1 ? 's' : ''}`);
232
261
  }
233
- // ── Render legend ────────────────────────────────────────────────────
234
- renderLegend(x, y) {
262
+ // -- Render legend ---------------------------------------------------------
263
+ renderLegend(svg, x, y) {
235
264
  const items = [
236
- { label: 'Added', color: '#059669', bg: '#D1FAE5' },
237
- { label: 'Removed', color: '#DC2626', bg: '#FEE2E2' },
238
- { label: 'Modified', color: '#D97706', bg: '#FEF3C7' },
265
+ { label: 'Added' },
266
+ { label: 'Removed' },
267
+ { label: 'Modified' },
239
268
  ];
240
- let out = `\n <!-- LEGEND -->`;
241
269
  items.forEach((item, i) => {
242
270
  const ix = x + i * 110;
243
- out += `\n <rect x="${ix}" y="${y}" width="90" height="28" fill="${item.bg}"/>`;
244
- out += `\n <circle cx="${ix + 16}" cy="${y + 14}" r="6" fill="${item.color}"/>`;
245
- out += `\n <text x="${ix + 30}" y="${y + 18}" font-family="${IND_FONT_HEAD}" font-size="11" font-weight="600" fill="${item.color}">${item.label}</text>`;
271
+ svg.child('rect', { x: ix, y, width: 90, height: 28, fill: theme_1.INK.wash, stroke: theme_1.INK.line, 'stroke-width': theme_1.STROKE.hair });
272
+ svg.child('circle', { cx: ix + 16, cy: y + 14, r: 6, fill: theme_1.INK.ground, stroke: theme_1.INK.line, 'stroke-width': theme_1.STROKE.hair });
273
+ svg
274
+ .child('text', {
275
+ x: ix + 30,
276
+ y: y + 18,
277
+ 'font-family': theme_1.FONT.family,
278
+ 'font-size': theme_1.FONT.small + 2,
279
+ 'font-weight': 'bold',
280
+ fill: theme_1.INK.line,
281
+ })
282
+ .text(item.label);
246
283
  });
247
- return out;
248
284
  }
249
- // ── Generate full SVG ────────────────────────────────────────────────
250
- generate() {
251
- const { changes } = this.opts;
285
+ // -- Generate full SVG -----------------------------------------------------
286
+ build() {
252
287
  const MARGIN = MigrationDiagram.MARGIN;
253
288
  const BANNER = MigrationDiagram.BANNER_H;
254
289
  const layout = this.computeLayout();
255
- // Calculate dimensions
256
290
  let totalW = MigrationDiagram.BOX_W + 2 * MARGIN + 100;
257
291
  let totalH = BANNER + MARGIN;
258
292
  if (layout.length > 0) {
259
293
  const lastItem = layout[layout.length - 1];
260
294
  totalH = lastItem.y + lastItem.h + MARGIN + 50;
261
295
  }
262
- const theme = (0, palette_1.resolveTheme)(this.opts.theme);
263
- const svg = [
264
- `<?xml version="1.0" encoding="UTF-8"?>`,
265
- `<!-- Generated by ts-prorm-orm MigrationDiagram -->`,
266
- `<svg xmlns="http://www.w3.org/2000/svg" width="${totalW}" height="${totalH}" viewBox="0 0 ${totalW} ${totalH}">`,
267
- ` <style>${IND_FONT_IMPORT}</style>`,
268
- ` <rect width="${totalW}" height="${totalH}" fill="${theme.canvasBg}"/>`,
269
- ];
270
- // Banner
271
- svg.push(this.renderBanner(totalW));
272
- // Change boxes
296
+ const doc = new svg_1.SvgDocument(totalW, totalH);
297
+ const svg = doc.root;
298
+ svg.child('rect', { width: totalW, height: totalH, fill: theme_1.INK.ground });
299
+ this.renderBanner(svg, totalW);
273
300
  layout.forEach((item) => {
274
- svg.push(this.renderChangeBox(item));
301
+ this.renderChangeBox(svg, item);
275
302
  });
276
- // Legend
277
303
  if (layout.length > 0) {
278
- svg.push(this.renderLegend(MARGIN, totalH - 50));
304
+ this.renderLegend(svg, MARGIN, totalH - 50);
279
305
  }
280
- svg.push(`</svg>`);
281
- return svg.join('\n');
306
+ return doc;
307
+ }
308
+ generate() {
309
+ return this.build().toString('Generated by ts-prorm-orm MigrationDiagram');
282
310
  }
283
311
  /**
284
312
  * Write the SVG to a file.
@@ -1,23 +1,11 @@
1
1
  /**
2
- * ModelDiagram generates a fully-rendered SVG card for a single ORM model,
3
- * styled to the "Industry" blueprint design system.
2
+ * ModelDiagram - a single model as one panel.
4
3
  *
5
- * Look:
6
- * - Solid accent (#5980a6) header bar with the model name in Barlow Condensed
7
- * - White card body, 1px hairline dividers, SQUARE corners, no drop shadow
8
- * - L-shaped corner registration marks around the frame (blueprint)
9
- * - One row per column: name (Barlow) + type (mono, neutral-700) + constraint
10
- * badges — PK/UQ (accent tint), FK (accent-2 tint), NN/AI/TS (neutral tint)
11
- * - Associations listed at the bottom with cardinality marks
12
- * - Hairline footer strip with table name + metadata
13
- * - Auto-sized SVG viewBox
14
- *
15
- * Usage:
16
- * const diag = new ModelDiagram({ name: 'User', fields: { … }, associations: […] });
17
- * diag.save('./diagrams/User.svg');
18
- * // or:
19
- * const svgString = diag.generate();
4
+ * Header carries the model name and its table name; rows are the columns, with
5
+ * a key marker on the left and the SQL type on the right; associations follow
6
+ * below a rule. Nothing else is drawn.
20
7
  */
8
+ import { line, measureText, text } from './core/layout';
21
9
  export interface FieldDefinition {
22
10
  name?: string;
23
11
  type: string | {
@@ -52,34 +40,46 @@ export interface ModelDiagramOptions {
52
40
  name: string;
53
41
  /** Override database table name */
54
42
  tableName?: string;
55
- /** Map of column-name field definition */
43
+ /** Map of column-name to field definition */
56
44
  fields: Record<string, FieldDefinition>;
57
45
  associations?: AssociationDefinition[];
58
46
  timestamps?: boolean;
59
47
  paranoid?: boolean;
60
48
  underscored?: boolean;
61
49
  comment?: string;
50
+ /**
51
+ * Accepted for backward compatibility and ignored: diagrams render in a
52
+ * single monochrome theme.
53
+ */
62
54
  theme?: 'light' | 'dark';
63
- /** Named palette (e.g. "industry") or hex color for the accent header */
55
+ /** Accepted for backward compatibility and ignored. */
64
56
  color?: string;
65
57
  }
58
+ /** Render a field's type as a plain SQL-ish string. */
59
+ export declare function resolveType(field: FieldDefinition): string;
60
+ /**
61
+ * Key marker for a column: PK, FK, U for unique, or blank.
62
+ *
63
+ * A single short token rather than a coloured badge - it survives greyscale
64
+ * printing and costs one column of width.
65
+ */
66
+ declare function marker(field: FieldDefinition): string | undefined;
67
+ /** Trailing annotations for a column, e.g. "not null", "default 0". */
68
+ declare function annotations(field: FieldDefinition): string;
69
+ /** Describe one association in a single line. */
70
+ declare function describeAssociation(a: AssociationDefinition): string;
66
71
  export declare class ModelDiagram {
67
- private opts;
68
- private static readonly W;
69
- private static readonly HDR;
70
- private static readonly COL_HDR;
71
- private static readonly ROW_H;
72
- private static readonly ASSOC_HDR;
73
- private static readonly ASSOC_ROW;
74
- private static readonly FOOTER;
75
- private static readonly PAD;
72
+ private readonly opts;
76
73
  constructor(options: ModelDiagramOptions);
77
- private badges;
78
- private drawBadge;
79
- private cornerMarks;
74
+ /** Column rows, including the implicit timestamp columns when enabled. */
75
+ private fieldRows;
76
+ private associationRows;
77
+ /** Build the SVG document. */
78
+ private build;
79
+ /** Generate the SVG source. */
80
80
  generate(): string;
81
- /**
82
- * Write the SVG to a file. Creates parent directories if needed.
83
- */
81
+ /** Write the SVG to `outputPath`, creating parent directories. */
84
82
  save(outputPath: string): string;
85
83
  }
84
+ export { marker as fieldMarker, annotations as fieldAnnotations, describeAssociation };
85
+ export { measureText, line, text };