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,23 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* ModelDiagram
|
|
4
|
-
* styled to the "Industry" blueprint design system.
|
|
3
|
+
* ModelDiagram - a single model as one panel.
|
|
5
4
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* - L-shaped corner registration marks around the frame (blueprint)
|
|
10
|
-
* - One row per column: name (Barlow) + type (mono, neutral-700) + constraint
|
|
11
|
-
* badges — PK/UQ (accent tint), FK (accent-2 tint), NN/AI/TS (neutral tint)
|
|
12
|
-
* - Associations listed at the bottom with cardinality marks
|
|
13
|
-
* - Hairline footer strip with table name + metadata
|
|
14
|
-
* - Auto-sized SVG viewBox
|
|
15
|
-
*
|
|
16
|
-
* Usage:
|
|
17
|
-
* const diag = new ModelDiagram({ name: 'User', fields: { … }, associations: […] });
|
|
18
|
-
* diag.save('./diagrams/User.svg');
|
|
19
|
-
* // or:
|
|
20
|
-
* const svgString = diag.generate();
|
|
5
|
+
* Header carries the model name and its table name; rows are the columns, with
|
|
6
|
+
* a key marker on the left and the SQL type on the right; associations follow
|
|
7
|
+
* below a rule. Nothing else is drawn.
|
|
21
8
|
*/
|
|
22
9
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
23
10
|
if (k2 === undefined) k2 = k;
|
|
@@ -53,11 +40,20 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
53
40
|
};
|
|
54
41
|
})();
|
|
55
42
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
56
|
-
exports.ModelDiagram = void 0;
|
|
43
|
+
exports.text = exports.line = exports.measureText = exports.ModelDiagram = void 0;
|
|
44
|
+
exports.resolveType = resolveType;
|
|
45
|
+
exports.fieldMarker = marker;
|
|
46
|
+
exports.fieldAnnotations = annotations;
|
|
47
|
+
exports.describeAssociation = describeAssociation;
|
|
57
48
|
const fs = __importStar(require("fs"));
|
|
58
49
|
const path = __importStar(require("path"));
|
|
59
|
-
const
|
|
60
|
-
const
|
|
50
|
+
const svg_1 = require("./core/svg");
|
|
51
|
+
const theme_1 = require("./core/theme");
|
|
52
|
+
const layout_1 = require("./core/layout");
|
|
53
|
+
Object.defineProperty(exports, "line", { enumerable: true, get: function () { return layout_1.line; } });
|
|
54
|
+
Object.defineProperty(exports, "measureText", { enumerable: true, get: function () { return layout_1.measureText; } });
|
|
55
|
+
Object.defineProperty(exports, "text", { enumerable: true, get: function () { return layout_1.text; } });
|
|
56
|
+
/** Render a field's type as a plain SQL-ish string. */
|
|
61
57
|
function resolveType(field) {
|
|
62
58
|
const t = field.type;
|
|
63
59
|
if (typeof t === 'string')
|
|
@@ -70,485 +66,110 @@ function resolveType(field) {
|
|
|
70
66
|
}
|
|
71
67
|
return 'UNKNOWN';
|
|
72
68
|
}
|
|
73
|
-
/**
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Key marker for a column: PK, FK, U for unique, or blank.
|
|
71
|
+
*
|
|
72
|
+
* A single short token rather than a coloured badge - it survives greyscale
|
|
73
|
+
* printing and costs one column of width.
|
|
74
|
+
*/
|
|
75
|
+
function marker(field) {
|
|
76
|
+
if (field.primaryKey)
|
|
77
|
+
return 'PK';
|
|
78
|
+
if (field.foreignKey || field.references)
|
|
79
|
+
return 'FK';
|
|
80
|
+
if (field.unique)
|
|
81
|
+
return 'U';
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
/** Trailing annotations for a column, e.g. "not null", "default 0". */
|
|
85
|
+
function annotations(field) {
|
|
86
|
+
const parts = [];
|
|
87
|
+
if (field.autoIncrement)
|
|
88
|
+
parts.push('auto');
|
|
89
|
+
if (field.allowNull === false && !field.primaryKey)
|
|
90
|
+
parts.push('not null');
|
|
91
|
+
if (field.defaultValue !== undefined && field.defaultValue !== null) {
|
|
92
|
+
parts.push(`default ${String(field.defaultValue)}`);
|
|
93
|
+
}
|
|
94
|
+
if (field.enum?.length)
|
|
95
|
+
parts.push(`enum(${field.enum.length})`);
|
|
96
|
+
return parts.join(', ');
|
|
97
|
+
}
|
|
98
|
+
/** Describe one association in a single line. */
|
|
99
|
+
function describeAssociation(a) {
|
|
100
|
+
const via = a.through
|
|
101
|
+
? ` through ${a.through}`
|
|
102
|
+
: a.foreignKey
|
|
103
|
+
? ` on ${a.foreignKey}`
|
|
104
|
+
: '';
|
|
105
|
+
const alias = a.as ? ` as ${a.as}` : '';
|
|
106
|
+
return `${a.type} ${a.target}${via}${alias}`;
|
|
107
|
+
}
|
|
81
108
|
class ModelDiagram {
|
|
82
109
|
constructor(options) {
|
|
83
|
-
|
|
84
|
-
// would re-apply raw null/undefined values (e.g. `associations: null`) over
|
|
85
|
-
// the guarded defaults and crash later when they are dereferenced.
|
|
86
|
-
this.opts = {
|
|
87
|
-
...options,
|
|
88
|
-
name: options.name ?? 'Model',
|
|
89
|
-
tableName: options.tableName ?? (options.name ?? 'model').toLowerCase() + 's',
|
|
90
|
-
fields: options.fields ?? {},
|
|
91
|
-
associations: options.associations ?? [],
|
|
92
|
-
timestamps: options.timestamps ?? true,
|
|
93
|
-
paranoid: options.paranoid ?? false,
|
|
94
|
-
underscored: options.underscored ?? false,
|
|
95
|
-
comment: options.comment ?? '',
|
|
96
|
-
theme: options.theme ?? 'light',
|
|
97
|
-
color: options.color ?? '',
|
|
98
|
-
};
|
|
110
|
+
this.opts = options;
|
|
99
111
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
b.push({ text: 'FK', bg: A2.bg, fg: A2.fg });
|
|
110
|
-
if (field.autoIncrement)
|
|
111
|
-
b.push({ text: 'AI', bg: N.bg, fg: N.fg });
|
|
112
|
-
if (field.unique && !field.primaryKey)
|
|
113
|
-
b.push({ text: 'UQ', bg: A.bg, fg: A.fg });
|
|
114
|
-
if (field.allowNull === false)
|
|
115
|
-
b.push({ text: 'NN', bg: N.bg, fg: N.fg });
|
|
116
|
-
if (isTS)
|
|
117
|
-
b.push({ text: 'TS', bg: N.bg, fg: N.fg });
|
|
118
|
-
return b;
|
|
119
|
-
}
|
|
120
|
-
// ── SVG badge (drawn into a parent group) — square, hairline, mono ────────
|
|
121
|
-
drawBadge(parent, b, x, y) {
|
|
122
|
-
const w = b.text.length * 8 + 12;
|
|
123
|
-
parent.child('rect', {
|
|
124
|
-
x,
|
|
125
|
-
y,
|
|
126
|
-
width: w,
|
|
127
|
-
height: 18,
|
|
128
|
-
rx: 0,
|
|
129
|
-
fill: b.bg,
|
|
130
|
-
stroke: palette_1.INDUSTRY.divider,
|
|
131
|
-
'stroke-width': 1,
|
|
112
|
+
/** Column rows, including the implicit timestamp columns when enabled. */
|
|
113
|
+
fieldRows() {
|
|
114
|
+
const rows = Object.entries(this.opts.fields).map(([name, field]) => {
|
|
115
|
+
const note = annotations(field);
|
|
116
|
+
return {
|
|
117
|
+
label: field.name ?? name,
|
|
118
|
+
detail: note ? `${resolveType(field)} ${note}` : resolveType(field),
|
|
119
|
+
marker: marker(field),
|
|
120
|
+
};
|
|
132
121
|
});
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
fill: b.fg,
|
|
142
|
-
'text-anchor': 'middle',
|
|
143
|
-
})
|
|
144
|
-
.text(b.text);
|
|
145
|
-
return w;
|
|
122
|
+
if (this.opts.timestamps) {
|
|
123
|
+
const stamp = (n) => ({ label: n, detail: 'DATE' });
|
|
124
|
+
rows.push({ ...stamp('createdAt'), rule: true });
|
|
125
|
+
rows.push(stamp('updatedAt'));
|
|
126
|
+
if (this.opts.paranoid)
|
|
127
|
+
rows.push(stamp('deletedAt'));
|
|
128
|
+
}
|
|
129
|
+
return rows;
|
|
146
130
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const seg = (x1, y1, x2, y2) => svg.child('line', { x1, y1, x2, y2, stroke: col, 'stroke-width': sw });
|
|
153
|
-
// top-left
|
|
154
|
-
seg(x, y, x + len, y);
|
|
155
|
-
seg(x, y, x, y + len);
|
|
156
|
-
// top-right
|
|
157
|
-
seg(x + w - len, y, x + w, y);
|
|
158
|
-
seg(x + w, y, x + w, y + len);
|
|
159
|
-
// bottom-left
|
|
160
|
-
seg(x, y + h, x + len, y + h);
|
|
161
|
-
seg(x, y + h - len, x, y + h);
|
|
162
|
-
// bottom-right
|
|
163
|
-
seg(x + w - len, y + h, x + w, y + h);
|
|
164
|
-
seg(x + w, y + h - len, x + w, y + h);
|
|
131
|
+
associationRows() {
|
|
132
|
+
return (this.opts.associations ?? []).map((a, i) => ({
|
|
133
|
+
label: describeAssociation(a),
|
|
134
|
+
rule: i === 0,
|
|
135
|
+
}));
|
|
165
136
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
const
|
|
177
|
-
const
|
|
178
|
-
const
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (
|
|
186
|
-
|
|
187
|
-
if (!fieldEntries.find(([k]) => k === tf)) {
|
|
188
|
-
fieldEntries.push([tf, { type: 'DATE', allowNull: tf === 'deletedAt' }]);
|
|
189
|
-
}
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
const hasAssoc = associations.length > 0;
|
|
193
|
-
const assocBlockH = hasAssoc ? ASSOC_HDR + associations.length * ASSOC_ROW : 0;
|
|
194
|
-
const cardH = HDR + COL_HDR + fieldEntries.length * ROW_H + assocBlockH + FOOTER;
|
|
195
|
-
const svgW = W + 2 * PAD;
|
|
196
|
-
const svgH = cardH + 2 * PAD;
|
|
197
|
-
const cx = PAD;
|
|
198
|
-
const cy = PAD;
|
|
199
|
-
const doc = new svg_dom_1.SvgDocument(svgW, svgH);
|
|
200
|
-
const svg = doc.root;
|
|
201
|
-
// Web fonts for browser renderers.
|
|
202
|
-
doc.style(palette_1.FONT_IMPORT);
|
|
203
|
-
// ── Ground ──────────────────────────────────────────────────────────
|
|
204
|
-
svg.child('rect', { width: svgW, height: svgH, fill: t.canvasBg });
|
|
205
|
-
// ── Card surface + hairline frame (square) ──────────────────────────
|
|
206
|
-
svg.child('rect', { x: cx, y: cy, width: W, height: cardH, fill: t.cardBg });
|
|
207
|
-
svg.child('rect', {
|
|
208
|
-
x: cx,
|
|
209
|
-
y: cy,
|
|
210
|
-
width: W,
|
|
211
|
-
height: cardH,
|
|
212
|
-
fill: 'none',
|
|
213
|
-
stroke: t.border,
|
|
214
|
-
'stroke-width': 1,
|
|
215
|
-
});
|
|
216
|
-
// Corner registration marks.
|
|
217
|
-
this.cornerMarks(svg, cx, cy, W, cardH);
|
|
218
|
-
// ── Header (solid accent bar) ───────────────────────────────────────
|
|
219
|
-
svg.child('rect', { x: cx, y: cy, width: W, height: HDR, fill: accent.mid });
|
|
220
|
-
svg
|
|
221
|
-
.child('text', {
|
|
222
|
-
x: cx + 18,
|
|
223
|
-
y: cy + 30,
|
|
224
|
-
'font-family': palette_1.FONTS.heading,
|
|
225
|
-
'font-size': 24,
|
|
226
|
-
'font-weight': 600,
|
|
227
|
-
'letter-spacing': 0.2,
|
|
228
|
-
fill: '#FFFFFF',
|
|
229
|
-
})
|
|
230
|
-
.text(name);
|
|
231
|
-
if (comment) {
|
|
232
|
-
svg
|
|
233
|
-
.child('text', {
|
|
234
|
-
x: cx + 18,
|
|
235
|
-
y: cy + 47,
|
|
236
|
-
'font-family': palette_1.FONTS.body,
|
|
237
|
-
'font-size': 11,
|
|
238
|
-
fill: 'rgba(255,255,255,0.82)',
|
|
239
|
-
})
|
|
240
|
-
.text(comment.length > 60 ? comment.slice(0, 59) + '…' : comment);
|
|
241
|
-
}
|
|
242
|
-
// Right-aligned kicker label.
|
|
243
|
-
svg
|
|
244
|
-
.child('text', {
|
|
245
|
-
x: cx + W - 16,
|
|
246
|
-
y: cy + 22,
|
|
247
|
-
'font-family': palette_1.FONTS.heading,
|
|
248
|
-
'font-size': 11,
|
|
249
|
-
'font-weight': 600,
|
|
250
|
-
'letter-spacing': 1.4,
|
|
251
|
-
fill: 'rgba(255,255,255,0.85)',
|
|
252
|
-
'text-anchor': 'end',
|
|
253
|
-
})
|
|
254
|
-
.text('ORM MODEL');
|
|
255
|
-
svg
|
|
256
|
-
.child('text', {
|
|
257
|
-
x: cx + W - 16,
|
|
258
|
-
y: cy + 40,
|
|
259
|
-
'font-family': palette_1.FONTS.mono,
|
|
260
|
-
'font-size': 11,
|
|
261
|
-
fill: 'rgba(255,255,255,0.72)',
|
|
262
|
-
'text-anchor': 'end',
|
|
263
|
-
})
|
|
264
|
-
.text(tableName.length > 26 ? tableName.slice(0, 25) + '…' : tableName);
|
|
265
|
-
// ── Column header row ───────────────────────────────────────────────
|
|
266
|
-
const colHdrY = cy + HDR;
|
|
267
|
-
svg.child('rect', { x: cx, y: colHdrY, width: W, height: COL_HDR, fill: t.colHeaderBg });
|
|
268
|
-
const colHdr = (x, label) => svg
|
|
269
|
-
.child('text', {
|
|
270
|
-
x,
|
|
271
|
-
y: colHdrY + 20,
|
|
272
|
-
'font-family': palette_1.FONTS.heading,
|
|
273
|
-
'font-size': 11,
|
|
274
|
-
'font-weight': 600,
|
|
275
|
-
fill: t.textSecondary,
|
|
276
|
-
'letter-spacing': 1,
|
|
277
|
-
})
|
|
278
|
-
.text(label);
|
|
279
|
-
colHdr(cx + 18, 'COLUMN');
|
|
280
|
-
colHdr(cx + 230, 'TYPE');
|
|
281
|
-
colHdr(cx + 380, 'CONSTRAINTS');
|
|
282
|
-
svg.child('line', {
|
|
283
|
-
x1: cx,
|
|
284
|
-
y1: colHdrY + COL_HDR,
|
|
285
|
-
x2: cx + W,
|
|
286
|
-
y2: colHdrY + COL_HDR,
|
|
287
|
-
stroke: t.border,
|
|
288
|
-
'stroke-width': 1,
|
|
289
|
-
});
|
|
290
|
-
// ── Field rows ──────────────────────────────────────────────────────
|
|
291
|
-
const fieldSectionY = colHdrY + COL_HDR;
|
|
292
|
-
fieldEntries.forEach(([colName, field], i) => {
|
|
293
|
-
const typeStr = resolveType(field);
|
|
294
|
-
const rowY = fieldSectionY + i * ROW_H;
|
|
295
|
-
const midY = rowY + ROW_H / 2;
|
|
296
|
-
const textBase = midY + 4;
|
|
297
|
-
const rowBg = i % 2 === 0 ? t.cardBg : t.rowAlt;
|
|
298
|
-
const isTS = tsFields.includes(colName);
|
|
299
|
-
const isPK = !!field.primaryKey;
|
|
300
|
-
const isFK = !!(field.foreignKey || field.references);
|
|
301
|
-
svg.child('rect', { x: cx, y: rowY, width: W, height: ROW_H, fill: rowBg });
|
|
302
|
-
// Left accent stripe: PK → accent, FK → accent-2.
|
|
303
|
-
if (isPK)
|
|
304
|
-
svg.child('rect', { x: cx, y: rowY, width: 3, height: ROW_H, fill: accent.mid });
|
|
305
|
-
else if (isFK)
|
|
306
|
-
svg.child('rect', { x: cx, y: rowY, width: 3, height: ROW_H, fill: a2.color });
|
|
307
|
-
// Field name (body font).
|
|
308
|
-
const nameW = isPK || isFK ? 600 : 400;
|
|
309
|
-
const nameC = isPK ? accent.text : isFK ? a2.text : t.textPrimary;
|
|
310
|
-
const dispName = colName.length > 24 ? colName.slice(0, 23) + '…' : colName;
|
|
311
|
-
svg
|
|
312
|
-
.child('text', {
|
|
313
|
-
x: cx + 18,
|
|
314
|
-
y: textBase,
|
|
315
|
-
'font-family': palette_1.FONTS.body,
|
|
316
|
-
'font-size': 13,
|
|
317
|
-
'font-weight': nameW,
|
|
318
|
-
fill: nameC,
|
|
319
|
-
})
|
|
320
|
-
.text(dispName);
|
|
321
|
-
// Type (mono, neutral-700).
|
|
322
|
-
const typTxt = typeStr.length > 22 ? typeStr.slice(0, 21) + '…' : typeStr;
|
|
323
|
-
svg
|
|
324
|
-
.child('text', {
|
|
325
|
-
x: cx + 230,
|
|
326
|
-
y: textBase,
|
|
327
|
-
'font-family': palette_1.FONTS.mono,
|
|
328
|
-
'font-size': 12,
|
|
329
|
-
fill: palette_1.INDUSTRY.neutral[700],
|
|
330
|
-
})
|
|
331
|
-
.text(typTxt);
|
|
332
|
-
// Constraint badges.
|
|
333
|
-
let bx = cx + 380;
|
|
334
|
-
for (const b of this.badges(field, isTS)) {
|
|
335
|
-
bx += this.drawBadge(svg, b, bx, rowY + (ROW_H - 18) / 2) + 5;
|
|
336
|
-
}
|
|
337
|
-
// FK reference label (below the name).
|
|
338
|
-
if (isFK && field.references) {
|
|
339
|
-
svg
|
|
340
|
-
.child('text', {
|
|
341
|
-
x: cx + 18,
|
|
342
|
-
y: rowY + ROW_H - 6,
|
|
343
|
-
'font-family': palette_1.FONTS.mono,
|
|
344
|
-
'font-size': 9,
|
|
345
|
-
fill: t.textSecondary,
|
|
346
|
-
})
|
|
347
|
-
.text(`→ ${field.references.model}.${field.references.key}`);
|
|
348
|
-
}
|
|
349
|
-
svg.child('line', {
|
|
350
|
-
x1: cx,
|
|
351
|
-
y1: rowY + ROW_H,
|
|
352
|
-
x2: cx + W,
|
|
353
|
-
y2: rowY + ROW_H,
|
|
354
|
-
stroke: t.border,
|
|
355
|
-
'stroke-width': 1,
|
|
356
|
-
});
|
|
357
|
-
});
|
|
358
|
-
// ── Associations ────────────────────────────────────────────────────
|
|
359
|
-
const assocBaseY = fieldSectionY + fieldEntries.length * ROW_H;
|
|
360
|
-
if (hasAssoc) {
|
|
361
|
-
svg.child('rect', { x: cx, y: assocBaseY, width: W, height: ASSOC_HDR, fill: palette_1.INDUSTRY.accentLight });
|
|
362
|
-
svg.child('rect', { x: cx, y: assocBaseY, width: 3, height: ASSOC_HDR, fill: accent.mid });
|
|
363
|
-
svg
|
|
364
|
-
.child('text', {
|
|
365
|
-
x: cx + 18,
|
|
366
|
-
y: assocBaseY + 22,
|
|
367
|
-
'font-family': palette_1.FONTS.heading,
|
|
368
|
-
'font-size': 12,
|
|
369
|
-
'font-weight': 600,
|
|
370
|
-
fill: accent.text,
|
|
371
|
-
'letter-spacing': 1,
|
|
372
|
-
})
|
|
373
|
-
.text('ASSOCIATIONS');
|
|
374
|
-
svg.child('line', {
|
|
375
|
-
x1: cx,
|
|
376
|
-
y1: assocBaseY + ASSOC_HDR,
|
|
377
|
-
x2: cx + W,
|
|
378
|
-
y2: assocBaseY + ASSOC_HDR,
|
|
379
|
-
stroke: t.border,
|
|
380
|
-
'stroke-width': 1,
|
|
381
|
-
});
|
|
382
|
-
associations.forEach((assoc, i) => {
|
|
383
|
-
const aY = assocBaseY + ASSOC_HDR + i * ASSOC_ROW;
|
|
384
|
-
const meta = ASSOC_META[assoc.type] ?? { mark: '?', label: assoc.type };
|
|
385
|
-
const rowBg = i % 2 === 0 ? t.cardBg : t.rowAlt;
|
|
386
|
-
const aTextY = aY + 18;
|
|
387
|
-
svg.child('rect', { x: cx, y: aY, width: W, height: ASSOC_ROW, fill: rowBg });
|
|
388
|
-
// Cardinality mark (mono, accent tint chip).
|
|
389
|
-
const cbw = 36;
|
|
390
|
-
svg.child('rect', {
|
|
391
|
-
x: cx + 14,
|
|
392
|
-
y: aY + 5,
|
|
393
|
-
width: cbw,
|
|
394
|
-
height: 18,
|
|
395
|
-
fill: palette_1.INDUSTRY.accentLight,
|
|
396
|
-
stroke: palette_1.INDUSTRY.divider,
|
|
397
|
-
'stroke-width': 1,
|
|
398
|
-
});
|
|
399
|
-
svg
|
|
400
|
-
.child('text', {
|
|
401
|
-
x: cx + 14 + cbw / 2,
|
|
402
|
-
y: aY + 18,
|
|
403
|
-
'font-family': palette_1.FONTS.mono,
|
|
404
|
-
'font-size': 11,
|
|
405
|
-
'font-weight': 600,
|
|
406
|
-
fill: accent.text,
|
|
407
|
-
'text-anchor': 'middle',
|
|
408
|
-
})
|
|
409
|
-
.text(meta.mark);
|
|
410
|
-
svg
|
|
411
|
-
.child('text', {
|
|
412
|
-
x: cx + 60,
|
|
413
|
-
y: aTextY,
|
|
414
|
-
'font-family': palette_1.FONTS.body,
|
|
415
|
-
'font-size': 12,
|
|
416
|
-
'font-weight': 500,
|
|
417
|
-
fill: t.textPrimary,
|
|
418
|
-
})
|
|
419
|
-
.text(meta.label);
|
|
420
|
-
svg
|
|
421
|
-
.child('text', {
|
|
422
|
-
x: cx + 162,
|
|
423
|
-
y: aTextY,
|
|
424
|
-
'font-family': palette_1.FONTS.mono,
|
|
425
|
-
'font-size': 12,
|
|
426
|
-
'font-weight': 600,
|
|
427
|
-
fill: accent.text,
|
|
428
|
-
})
|
|
429
|
-
.text(assoc.target);
|
|
430
|
-
if (assoc.foreignKey) {
|
|
431
|
-
svg
|
|
432
|
-
.child('text', {
|
|
433
|
-
x: cx + 300,
|
|
434
|
-
y: aTextY,
|
|
435
|
-
'font-family': palette_1.FONTS.mono,
|
|
436
|
-
'font-size': 10,
|
|
437
|
-
fill: t.textSecondary,
|
|
438
|
-
})
|
|
439
|
-
.text(`(${assoc.foreignKey})`);
|
|
440
|
-
}
|
|
441
|
-
if (assoc.through) {
|
|
442
|
-
svg
|
|
443
|
-
.child('text', {
|
|
444
|
-
x: cx + 400,
|
|
445
|
-
y: aTextY,
|
|
446
|
-
'font-family': palette_1.FONTS.mono,
|
|
447
|
-
'font-size': 10,
|
|
448
|
-
fill: a2.text,
|
|
449
|
-
})
|
|
450
|
-
.text(`via: ${assoc.through}`);
|
|
451
|
-
}
|
|
452
|
-
if (assoc.as) {
|
|
453
|
-
svg
|
|
454
|
-
.child('text', {
|
|
455
|
-
x: cx + W - 14,
|
|
456
|
-
y: aTextY,
|
|
457
|
-
'font-family': palette_1.FONTS.mono,
|
|
458
|
-
'font-size': 10,
|
|
459
|
-
fill: t.textSecondary,
|
|
460
|
-
'text-anchor': 'end',
|
|
461
|
-
})
|
|
462
|
-
.text(`as: ${assoc.as}`);
|
|
463
|
-
}
|
|
464
|
-
svg.child('line', {
|
|
465
|
-
x1: cx,
|
|
466
|
-
y1: aY + ASSOC_ROW,
|
|
467
|
-
x2: cx + W,
|
|
468
|
-
y2: aY + ASSOC_ROW,
|
|
469
|
-
stroke: t.border,
|
|
470
|
-
'stroke-width': 1,
|
|
471
|
-
});
|
|
472
|
-
});
|
|
473
|
-
}
|
|
474
|
-
// ── Footer (hairline strip) ─────────────────────────────────────────
|
|
475
|
-
const footerY = assocBaseY + assocBlockH;
|
|
476
|
-
svg.child('rect', { x: cx, y: footerY, width: W, height: FOOTER, fill: t.footerBg });
|
|
477
|
-
svg.child('line', {
|
|
478
|
-
x1: cx,
|
|
479
|
-
y1: footerY,
|
|
480
|
-
x2: cx + W,
|
|
481
|
-
y2: footerY,
|
|
482
|
-
stroke: t.border,
|
|
483
|
-
'stroke-width': 1,
|
|
484
|
-
});
|
|
485
|
-
const footTable = svg.child('text', {
|
|
486
|
-
x: cx + 18,
|
|
487
|
-
y: footerY + 25,
|
|
488
|
-
'font-family': palette_1.FONTS.heading,
|
|
489
|
-
'font-size': 11,
|
|
490
|
-
'font-weight': 600,
|
|
491
|
-
'letter-spacing': 0.6,
|
|
492
|
-
fill: t.textSecondary,
|
|
493
|
-
});
|
|
494
|
-
footTable.text('TABLE ');
|
|
495
|
-
footTable
|
|
496
|
-
.child('tspan', { 'font-family': palette_1.FONTS.mono, 'font-weight': 400, fill: accent.text })
|
|
497
|
-
.text(tableName);
|
|
498
|
-
if (timestamps) {
|
|
499
|
-
svg
|
|
500
|
-
.child('text', {
|
|
501
|
-
x: cx + 250,
|
|
502
|
-
y: footerY + 25,
|
|
503
|
-
'font-family': palette_1.FONTS.body,
|
|
504
|
-
'font-size': 11,
|
|
505
|
-
fill: t.textSecondary,
|
|
506
|
-
})
|
|
507
|
-
.text('timestamps');
|
|
508
|
-
}
|
|
509
|
-
if (paranoid) {
|
|
510
|
-
svg
|
|
511
|
-
.child('text', {
|
|
512
|
-
x: cx + 350,
|
|
513
|
-
y: footerY + 25,
|
|
514
|
-
'font-family': palette_1.FONTS.body,
|
|
515
|
-
'font-size': 11,
|
|
516
|
-
fill: t.textSecondary,
|
|
517
|
-
})
|
|
518
|
-
.text('paranoid');
|
|
137
|
+
/** Build the SVG document. */
|
|
138
|
+
build() {
|
|
139
|
+
const rows = [...this.fieldRows(), ...this.associationRows()];
|
|
140
|
+
const opts = {
|
|
141
|
+
title: this.opts.name,
|
|
142
|
+
subtitle: this.opts.tableName ?? this.opts.name.toLowerCase(),
|
|
143
|
+
rows,
|
|
144
|
+
minWidth: 260,
|
|
145
|
+
};
|
|
146
|
+
const size = (0, layout_1.panelSize)(opts);
|
|
147
|
+
const titleH = theme_1.FONT.title + theme_1.SPACE.md;
|
|
148
|
+
const footerH = this.opts.comment ? theme_1.ROW_HEIGHT : 0;
|
|
149
|
+
const width = size.w + theme_1.SPACE.xl * 2;
|
|
150
|
+
const height = titleH + size.h + footerH + theme_1.SPACE.xl * 2;
|
|
151
|
+
const doc = new svg_1.SvgDocument(width, height);
|
|
152
|
+
const body = doc.root.child('g');
|
|
153
|
+
body.child('rect', { x: 0, y: 0, width, height, fill: theme_1.INK.ground });
|
|
154
|
+
const afterTitle = (0, layout_1.documentTitle)(body, theme_1.SPACE.xl, theme_1.SPACE.lg, `Model: ${this.opts.name}`);
|
|
155
|
+
const box = (0, layout_1.panel)(body, theme_1.SPACE.xl, afterTitle, opts);
|
|
156
|
+
if (this.opts.comment) {
|
|
157
|
+
(0, layout_1.footer)(body, theme_1.SPACE.xl, box.y + box.h + theme_1.ROW_HEIGHT, [this.opts.comment]);
|
|
519
158
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
'font-size': 11,
|
|
526
|
-
fill: t.textSecondary,
|
|
527
|
-
'text-anchor': 'end',
|
|
528
|
-
})
|
|
529
|
-
.text(`${fieldEntries.length} column${fieldEntries.length !== 1 ? 's' : ''}`);
|
|
530
|
-
return doc.toString('Generated by ts-prorm-orm ModelDiagram');
|
|
159
|
+
return { doc, body, box };
|
|
160
|
+
}
|
|
161
|
+
/** Generate the SVG source. */
|
|
162
|
+
generate() {
|
|
163
|
+
return this.build().doc.toString(`ModelDiagram ${this.opts.name}`);
|
|
531
164
|
}
|
|
532
|
-
/**
|
|
533
|
-
* Write the SVG to a file. Creates parent directories if needed.
|
|
534
|
-
*/
|
|
165
|
+
/** Write the SVG to `outputPath`, creating parent directories. */
|
|
535
166
|
save(outputPath) {
|
|
536
167
|
const resolved = path.resolve(outputPath);
|
|
537
168
|
const dir = path.dirname(resolved);
|
|
538
169
|
if (!fs.existsSync(dir))
|
|
539
170
|
fs.mkdirSync(dir, { recursive: true });
|
|
540
|
-
|
|
541
|
-
fs.writeFileSync(resolved, svg, 'utf-8');
|
|
171
|
+
fs.writeFileSync(resolved, this.generate(), 'utf-8');
|
|
542
172
|
return resolved;
|
|
543
173
|
}
|
|
544
174
|
}
|
|
545
175
|
exports.ModelDiagram = ModelDiagram;
|
|
546
|
-
// Layout constants (px)
|
|
547
|
-
ModelDiagram.W = 600;
|
|
548
|
-
ModelDiagram.HDR = 56;
|
|
549
|
-
ModelDiagram.COL_HDR = 30;
|
|
550
|
-
ModelDiagram.ROW_H = 38;
|
|
551
|
-
ModelDiagram.ASSOC_HDR = 34;
|
|
552
|
-
ModelDiagram.ASSOC_ROW = 28;
|
|
553
|
-
ModelDiagram.FOOTER = 40;
|
|
554
|
-
ModelDiagram.PAD = 28;
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PackageDiagram
|
|
2
|
+
* PackageDiagram - a UML package / namespace grouping diagram.
|
|
3
3
|
*
|
|
4
4
|
* Groups models/tables into packages (schemas, modules, bounded-contexts).
|
|
5
5
|
* Each package is drawn as the classic UML "folder" shape: a small tab rect
|
|
6
6
|
* on the top-left plus a body rect beneath it. Package members are laid out
|
|
7
7
|
* as small labeled boxes in a grid inside the body. Dashed dependency arrows
|
|
8
|
-
* (e.g.
|
|
8
|
+
* (e.g. <<import>>) connect packages.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
* drives the canvas/text colors. Both `color` and `theme` options genuinely
|
|
12
|
-
* change fill/stroke values in the produced SVG, and the viewBox is auto-sized.
|
|
10
|
+
* The viewBox is auto-sized to the laid-out geometry.
|
|
13
11
|
*
|
|
14
12
|
* Usage:
|
|
15
13
|
* const diag = new PackageDiagram({ packages: [...], dependencies: [...] });
|
|
@@ -34,8 +32,12 @@ export interface PackageDiagramOptions {
|
|
|
34
32
|
packages: PackageBox[];
|
|
35
33
|
dependencies?: PackageDependency[];
|
|
36
34
|
title?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Accepted for backward compatibility and ignored: diagrams render in a
|
|
37
|
+
* single monochrome theme.
|
|
38
|
+
*/
|
|
37
39
|
theme?: 'light' | 'dark';
|
|
38
|
-
/**
|
|
40
|
+
/** Accepted for backward compatibility and ignored. */
|
|
39
41
|
color?: string;
|
|
40
42
|
/** Number of packages per row (auto if omitted). */
|
|
41
43
|
columns?: number;
|
|
@@ -57,6 +59,7 @@ export declare class PackageDiagram {
|
|
|
57
59
|
constructor(options: PackageDiagramOptions);
|
|
58
60
|
/** Compute the pixel size of a single package folder. */
|
|
59
61
|
private packageSize;
|
|
62
|
+
private build;
|
|
60
63
|
generate(): string;
|
|
61
64
|
/** Compute connection points on the borders of two package boxes. */
|
|
62
65
|
private edgePoints;
|