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,23 +1,10 @@
1
1
  "use strict";
2
2
  /**
3
- * ModelDiagram generates a fully-rendered SVG card for a single ORM model,
4
- * styled to the "Industry" blueprint design system.
3
+ * ModelDiagram - a single model as one panel.
5
4
  *
6
- * Look:
7
- * - Solid accent (#5980a6) header bar with the model name in Barlow Condensed
8
- * - White card body, 1px hairline dividers, SQUARE corners, no drop shadow
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 palette_1 = require("./palette");
60
- const svg_dom_1 = require("./svg-dom");
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
- /** Cardinality marks per association type (mono, blueprint). */
74
- const ASSOC_META = {
75
- hasOne: { mark: '1:1', label: 'hasOne' },
76
- hasMany: { mark: '1:N', label: 'hasMany' },
77
- belongsTo: { mark: 'N:1', label: 'belongsTo' },
78
- belongsToMany: { mark: 'N:N', label: 'belongsToMany' },
79
- };
80
- // ─── Main class ────────────────────────────────────────────────────────────
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
- // NOTE: spread `...options` FIRST so the defaults below win. Spreading last
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
- // ── Row badge helper ────────────────────────────────────────────────────
101
- badges(field, isTS) {
102
- const b = [];
103
- const A = palette_1.BADGE_TINTS.accent;
104
- const A2 = palette_1.BADGE_TINTS.accent2;
105
- const N = palette_1.BADGE_TINTS.neutral;
106
- if (field.primaryKey)
107
- b.push({ text: 'PK', bg: A.bg, fg: A.fg });
108
- if (field.foreignKey || field.references)
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
- parent
134
- .child('text', {
135
- x: x + w / 2,
136
- y: y + 13,
137
- 'font-family': palette_1.FONTS.heading,
138
- 'font-size': 11,
139
- 'font-weight': 600,
140
- 'letter-spacing': 0.4,
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
- // ── L-shaped corner registration marks around the frame ──────────────────
148
- cornerMarks(svg, x, y, w, h) {
149
- const len = 9;
150
- const col = palette_1.INDUSTRY.neutral[500];
151
- const sw = 1;
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
- // ── Generate full SVG string (built via a real DOM document) ──────────────
167
- generate() {
168
- const { name, tableName, fields, associations, timestamps, paranoid, comment, theme, color } = this.opts;
169
- const accent = (0, palette_1.resolveAccent)(color);
170
- const a2 = (0, palette_1.accent2Of)(accent);
171
- const t = (0, palette_1.resolveTheme)(theme);
172
- const W = ModelDiagram.W;
173
- const HDR = ModelDiagram.HDR;
174
- const COL_HDR = ModelDiagram.COL_HDR;
175
- const ROW_H = ModelDiagram.ROW_H;
176
- const ASSOC_HDR = ModelDiagram.ASSOC_HDR;
177
- const ASSOC_ROW = ModelDiagram.ASSOC_ROW;
178
- const FOOTER = ModelDiagram.FOOTER;
179
- const PAD = ModelDiagram.PAD;
180
- // ── Build field list (add timestamps if requested) ──────────────────
181
- const fieldEntries = Object.entries(fields);
182
- const tsFields = ['createdAt', 'updatedAt'];
183
- if (paranoid)
184
- tsFields.push('deletedAt');
185
- if (timestamps) {
186
- tsFields.forEach((tf) => {
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
- svg
521
- .child('text', {
522
- x: cx + W - 14,
523
- y: footerY + 25,
524
- 'font-family': palette_1.FONTS.body,
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
- const svg = this.generate();
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 a UML package / namespace grouping diagram.
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. «import») connect packages.
8
+ * (e.g. <<import>>) connect packages.
9
9
  *
10
- * Each package receives a distinct accent from `rotatedPalettes`; the theme
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
- /** Named palette or hex accent for the first/primary package. */
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;