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.
- package/CHANGELOG.md +209 -0
- package/README.md +32 -24
- package/dist/compliance/index.d.ts +2 -0
- package/dist/compliance/index.js +13 -1
- package/dist/connection-manager.js +13 -1
- package/dist/core/expressions.d.ts +58 -0
- package/dist/core/expressions.js +83 -0
- package/dist/core/hooks-manager.d.ts +179 -0
- package/dist/core/hooks-manager.js +291 -0
- package/dist/core/types.d.ts +235 -0
- package/dist/core/types.js +9 -0
- package/dist/decorators/index.d.ts +2 -0
- package/dist/decorators/index.js +11 -1
- package/dist/diagrams/chen-diagram.d.ts +15 -12
- package/dist/diagrams/chen-diagram.js +76 -85
- package/dist/diagrams/class-diagram.d.ts +9 -8
- package/dist/diagrams/class-diagram.js +60 -82
- package/dist/diagrams/core/index.d.ts +13 -0
- package/dist/diagrams/core/index.js +33 -0
- package/dist/diagrams/core/layout.d.ts +104 -0
- package/dist/diagrams/core/layout.js +223 -0
- package/dist/diagrams/{svg-dom.d.ts → core/svg.d.ts} +8 -20
- package/dist/diagrams/{svg-dom.js → core/svg.js} +21 -29
- package/dist/diagrams/core/theme.d.ts +84 -0
- package/dist/diagrams/core/theme.js +62 -0
- package/dist/diagrams/dependency-diagram.d.ts +15 -14
- package/dist/diagrams/dependency-diagram.js +71 -105
- package/dist/diagrams/er-diagram.d.ts +13 -26
- package/dist/diagrams/er-diagram.js +180 -259
- package/dist/diagrams/flow-diagram.d.ts +16 -11
- package/dist/diagrams/flow-diagram.js +96 -91
- package/dist/diagrams/gantt-diagram.d.ts +10 -5
- package/dist/diagrams/gantt-diagram.js +77 -103
- package/dist/diagrams/index-diagram.d.ts +10 -10
- package/dist/diagrams/index-diagram.js +106 -116
- package/dist/diagrams/index.d.ts +6 -9
- package/dist/diagrams/index.js +9 -24
- package/dist/diagrams/migration-diagram.d.ts +8 -4
- package/dist/diagrams/migration-diagram.js +189 -161
- package/dist/diagrams/model-diagram.d.ts +34 -34
- package/dist/diagrams/model-diagram.js +106 -485
- package/dist/diagrams/package-diagram.d.ts +9 -6
- package/dist/diagrams/package-diagram.js +64 -101
- package/dist/diagrams/relational-diagram.d.ts +12 -10
- package/dist/diagrams/relational-diagram.js +87 -114
- package/dist/diagrams/schemadoc-diagram.d.ts +8 -8
- package/dist/diagrams/schemadoc-diagram.js +71 -93
- package/dist/diagrams/sequence-diagram.d.ts +10 -10
- package/dist/diagrams/sequence-diagram.js +64 -89
- package/dist/diagrams/state-diagram.d.ts +10 -9
- package/dist/diagrams/state-diagram.js +69 -92
- package/dist/diagrams/tree-diagram.d.ts +11 -5
- package/dist/diagrams/tree-diagram.js +41 -65
- package/dist/dialects/clickhouse/index.js +15 -0
- package/dist/dialects/cockroachdb/index.d.ts +1 -1
- package/dist/dialects/cockroachdb/index.js +32 -63
- package/dist/dialects/db2/index.js +23 -8
- package/dist/dialects/dialect.d.ts +29 -5
- package/dist/dialects/duckdb/index.js +9 -2
- package/dist/dialects/hana/index.js +22 -5
- package/dist/dialects/lock-clause-helper.d.ts +167 -0
- package/dist/dialects/lock-clause-helper.js +316 -0
- package/dist/dialects/mariadb/index.d.ts +7 -4
- package/dist/dialects/mariadb/index.js +36 -60
- package/dist/dialects/mssql/index.js +34 -3
- package/dist/dialects/mysql/index.d.ts +1 -1
- package/dist/dialects/mysql/index.js +17 -45
- package/dist/dialects/oracle/index.d.ts +14 -3
- package/dist/dialects/oracle/index.js +66 -36
- package/dist/dialects/order-expression-helper.d.ts +72 -0
- package/dist/dialects/order-expression-helper.js +154 -0
- package/dist/dialects/partial-index.d.ts +52 -0
- package/dist/dialects/partial-index.js +251 -0
- package/dist/dialects/postgres/index.d.ts +1 -1
- package/dist/dialects/postgres/index.js +43 -68
- package/dist/dialects/redshift/index.js +9 -2
- package/dist/dialects/snowflake/index.js +17 -0
- package/dist/dialects/spanner/index.js +7 -2
- package/dist/dialects/sqlite/driver.d.ts +55 -0
- package/dist/dialects/sqlite/driver.js +102 -0
- package/dist/dialects/sqlite/index.d.ts +1 -1
- package/dist/dialects/sqlite/index.js +49 -27
- package/dist/graph/base-graph-dialect.d.ts +24 -3
- package/dist/graph/base-graph-dialect.js +43 -2
- package/dist/graph/dgraph/index.js +6 -0
- package/dist/graph/gremlin/index.d.ts +6 -0
- package/dist/graph/gremlin/index.js +12 -1
- package/dist/graph/index.d.ts +1 -1
- package/dist/graph/neo4j/index.d.ts +2 -0
- package/dist/graph/neo4j/index.js +6 -1
- package/dist/graph/types.d.ts +12 -1
- package/dist/index.d.ts +13 -4
- package/dist/index.js +54 -7
- package/dist/logging/index.d.ts +10 -0
- package/dist/logging/index.js +23 -0
- package/dist/logging/query-logging.d.ts +82 -0
- package/dist/logging/query-logging.js +102 -0
- package/dist/models/decorators.d.ts +39 -3
- package/dist/models/decorators.js +74 -0
- package/dist/models/eager-load.d.ts +99 -0
- package/dist/models/eager-load.js +348 -5
- package/dist/models/indexes.d.ts +3 -2
- package/dist/models/indexes.js +7 -1
- package/dist/models/model.js +69 -43
- package/dist/prorm.d.ts +175 -409
- package/dist/prorm.js +1142 -557
- package/dist/query-builders/order-limit-builder.js +12 -10
- package/dist/query-builders/sql-compiler.d.ts +10 -3
- package/dist/query-builders/sql-compiler.js +14 -43
- package/dist/query-interface.d.ts +2 -1
- package/dist/sql-constants.d.ts +44 -0
- package/dist/sql-constants.js +138 -1
- package/dist/types/index.d.ts +53 -17
- package/dist/types/index.js +10 -15
- package/dist/types/query-types.d.ts +26 -0
- package/dist/types/query-types.js +30 -0
- package/package.json +32 -5
- package/dist/decorators/belongs-to-many.js +0 -115
- package/dist/decorators/belongs-to.js +0 -115
- package/dist/decorators/has-many.js +0 -127
- package/dist/decorators/has-one.js +0 -116
- package/dist/diagrams/palette.d.ts +0 -138
- package/dist/diagrams/palette.js +0 -194
- package/dist/hooks/hooks-manager.d.ts +0 -189
- package/dist/hooks/hooks-manager.js +0 -350
- package/dist/hooks/index.d.ts +0 -8
- package/dist/hooks/index.js +0 -37
- /package/dist/{logging.d.ts → logging/logger.d.ts} +0 -0
- /package/dist/{logging.js → logging/logger.js} +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* MigrationDiagram
|
|
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
|
-
* -
|
|
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
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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, '<')
|
|
90
|
-
.replace(/>/g, '>')
|
|
91
|
-
.replace(/"/g, '"');
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
//
|
|
160
|
-
renderChangeBox(item) {
|
|
161
|
-
const { change, x, y, w
|
|
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
|
-
|
|
176
|
-
// Box background
|
|
177
|
-
|
|
178
|
-
// Header
|
|
179
|
-
|
|
180
|
-
// Change type badge
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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 ?
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
207
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
217
|
-
|
|
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
|
|
221
|
-
|
|
222
|
-
|
|
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
|
|
225
|
-
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
//
|
|
234
|
-
renderLegend(x, y) {
|
|
262
|
+
// -- Render legend ---------------------------------------------------------
|
|
263
|
+
renderLegend(svg, x, y) {
|
|
235
264
|
const items = [
|
|
236
|
-
{ label: 'Added'
|
|
237
|
-
{ label: 'Removed'
|
|
238
|
-
{ label: 'Modified'
|
|
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
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
//
|
|
250
|
-
|
|
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
|
|
263
|
-
const svg =
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
301
|
+
this.renderChangeBox(svg, item);
|
|
275
302
|
});
|
|
276
|
-
// Legend
|
|
277
303
|
if (layout.length > 0) {
|
|
278
|
-
|
|
304
|
+
this.renderLegend(svg, MARGIN, totalH - 50);
|
|
279
305
|
}
|
|
280
|
-
|
|
281
|
-
|
|
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
|
|
3
|
-
* styled to the "Industry" blueprint design system.
|
|
2
|
+
* ModelDiagram - a single model as one panel.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
|
|
78
|
-
private
|
|
79
|
-
private
|
|
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 };
|