metal-orm 1.0.49 → 1.0.51
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/dist/index.cjs +151 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +151 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/scripts/generate-entities/cli.mjs +101 -0
- package/scripts/generate-entities/render.mjs +130 -17
- package/scripts/inflection/compound.mjs +72 -0
- package/scripts/inflection/en.mjs +26 -0
- package/scripts/inflection/index.mjs +29 -0
- package/scripts/inflection/pt-br.mjs +391 -0
- package/scripts/naming-strategy.mjs +27 -63
- package/scripts/pt-pluralizer.mjs +19 -0
- package/src/core/ddl/introspect/mssql.ts +74 -2
- package/src/core/ddl/introspect/postgres.ts +69 -39
- package/src/core/ddl/introspect/sqlite.ts +69 -5
- package/src/schema/column-types.ts +14 -9
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import {
|
|
2
|
+
applyToCompoundHead,
|
|
3
|
+
normalizeLookup,
|
|
4
|
+
stripDiacritics
|
|
5
|
+
} from './compound.mjs';
|
|
6
|
+
|
|
7
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
8
|
+
// PATTERNS
|
|
9
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Precompiled regex patterns for performance.
|
|
13
|
+
* @type {Readonly<Record<string, RegExp>>}
|
|
14
|
+
*/
|
|
15
|
+
const PATTERNS = Object.freeze({
|
|
16
|
+
consonantEnding: /[rzn]$/,
|
|
17
|
+
consonantEsEnding: /[rzn]es$/,
|
|
18
|
+
endsInX: /x$/,
|
|
19
|
+
vowelBeforeS: /[aeiou]s$/,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
23
|
+
// IRREGULAR DICTIONARIES (all normalized - no diacritics)
|
|
24
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Default irregular plurals for Brazilian Portuguese.
|
|
28
|
+
* Keys AND values are normalized (no diacritics, lowercase).
|
|
29
|
+
* @type {Readonly<Record<string, string>>}
|
|
30
|
+
*/
|
|
31
|
+
export const PT_BR_DEFAULT_IRREGULARS = Object.freeze({
|
|
32
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
33
|
+
// -ão → -ães (irregular, must memorize)
|
|
34
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
35
|
+
'pao': 'paes',
|
|
36
|
+
'cao': 'caes',
|
|
37
|
+
'alemao': 'alemaes',
|
|
38
|
+
'capitao': 'capitaes',
|
|
39
|
+
'charlatao': 'charlataes',
|
|
40
|
+
'escrivao': 'escrivaes',
|
|
41
|
+
'tabeliao': 'tabeliaes',
|
|
42
|
+
'guardiao': 'guardiaes',
|
|
43
|
+
'sacristao': 'sacristaes',
|
|
44
|
+
|
|
45
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
46
|
+
// -ão → -ãos (irregular, must memorize)
|
|
47
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
48
|
+
'mao': 'maos',
|
|
49
|
+
'cidadao': 'cidadaos',
|
|
50
|
+
'cristao': 'cristaos',
|
|
51
|
+
'irmao': 'irmaos',
|
|
52
|
+
'orgao': 'orgaos',
|
|
53
|
+
'bencao': 'bencaos',
|
|
54
|
+
'grao': 'graos',
|
|
55
|
+
'orfao': 'orfaos',
|
|
56
|
+
'sotao': 'sotaos',
|
|
57
|
+
'acordao': 'acordaos',
|
|
58
|
+
'cortesao': 'cortesaos',
|
|
59
|
+
'pagao': 'pagaos',
|
|
60
|
+
'chao': 'chaos',
|
|
61
|
+
'vao': 'vaos',
|
|
62
|
+
|
|
63
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
64
|
+
// -l special cases
|
|
65
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
66
|
+
'mal': 'males',
|
|
67
|
+
'consul': 'consules',
|
|
68
|
+
|
|
69
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
70
|
+
// Unstressed -il → -eis (paroxytones)
|
|
71
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
72
|
+
'fossil': 'fosseis',
|
|
73
|
+
'reptil': 'repteis',
|
|
74
|
+
'facil': 'faceis',
|
|
75
|
+
'dificil': 'dificeis',
|
|
76
|
+
'util': 'uteis',
|
|
77
|
+
'inutil': 'inuteis',
|
|
78
|
+
'agil': 'ageis',
|
|
79
|
+
'fragil': 'frageis',
|
|
80
|
+
'projetil': 'projeteis',
|
|
81
|
+
'volatil': 'volateis',
|
|
82
|
+
'docil': 'doceis',
|
|
83
|
+
'portatil': 'portateis',
|
|
84
|
+
'textil': 'texteis',
|
|
85
|
+
|
|
86
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
87
|
+
// Invariable words (paroxytone/proparoxytone ending in -s/-x)
|
|
88
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
89
|
+
'onibus': 'onibus',
|
|
90
|
+
'lapis': 'lapis',
|
|
91
|
+
'virus': 'virus',
|
|
92
|
+
'atlas': 'atlas',
|
|
93
|
+
'pires': 'pires',
|
|
94
|
+
'cais': 'cais',
|
|
95
|
+
'torax': 'torax',
|
|
96
|
+
'fenix': 'fenix',
|
|
97
|
+
'xerox': 'xerox',
|
|
98
|
+
'latex': 'latex',
|
|
99
|
+
'index': 'index',
|
|
100
|
+
'duplex': 'duplex',
|
|
101
|
+
'telex': 'telex',
|
|
102
|
+
'climax': 'climax',
|
|
103
|
+
'simples': 'simples',
|
|
104
|
+
'oasis': 'oasis',
|
|
105
|
+
'tenis': 'tenis',
|
|
106
|
+
|
|
107
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
108
|
+
// -ês → -eses (nationalities, months, etc.)
|
|
109
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
110
|
+
'portugues': 'portugueses',
|
|
111
|
+
'ingles': 'ingleses',
|
|
112
|
+
'frances': 'franceses',
|
|
113
|
+
'holandes': 'holandeses',
|
|
114
|
+
'japones': 'japoneses',
|
|
115
|
+
'chines': 'chineses',
|
|
116
|
+
'irlandes': 'irlandeses',
|
|
117
|
+
'escoces': 'escoceses',
|
|
118
|
+
'mes': 'meses',
|
|
119
|
+
'burges': 'burgueses',
|
|
120
|
+
'fregues': 'fregueses',
|
|
121
|
+
'marques': 'marqueses',
|
|
122
|
+
|
|
123
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
124
|
+
// Other irregulars
|
|
125
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
126
|
+
'qualquer': 'quaisquer',
|
|
127
|
+
'carater': 'caracteres',
|
|
128
|
+
'junior': 'juniores',
|
|
129
|
+
'senior': 'seniores',
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Builds reverse irregular mapping (plural → singular).
|
|
134
|
+
* @param {Record<string, string>} irregulars
|
|
135
|
+
* @returns {Record<string, string>}
|
|
136
|
+
*/
|
|
137
|
+
const buildSingularIrregulars = (irregulars) => {
|
|
138
|
+
const result = {};
|
|
139
|
+
for (const [singular, plural] of Object.entries(irregulars)) {
|
|
140
|
+
if (plural !== singular) {
|
|
141
|
+
result[plural] = singular;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return result;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Default irregular singulars (auto-generated reverse mapping).
|
|
149
|
+
* @type {Readonly<Record<string, string>>}
|
|
150
|
+
*/
|
|
151
|
+
export const PT_BR_DEFAULT_SINGULAR_IRREGULARS = Object.freeze(
|
|
152
|
+
buildSingularIrregulars(PT_BR_DEFAULT_IRREGULARS)
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
156
|
+
// CONNECTORS
|
|
157
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Portuguese connector words used in compound expressions.
|
|
161
|
+
* @type {ReadonlySet<string>}
|
|
162
|
+
*/
|
|
163
|
+
export const PT_BR_CONNECTORS = Object.freeze(new Set(
|
|
164
|
+
[
|
|
165
|
+
'de', 'da', 'do', 'das', 'dos',
|
|
166
|
+
'em', 'na', 'no', 'nas', 'nos',
|
|
167
|
+
'a', 'ao', 'as', 'aos',
|
|
168
|
+
'com', 'sem', 'sob', 'sobre',
|
|
169
|
+
'para', 'por', 'pela', 'pelo', 'pelas', 'pelos',
|
|
170
|
+
'entre', 'contra', 'perante',
|
|
171
|
+
'e', 'ou'
|
|
172
|
+
].map(normalizeLookup)
|
|
173
|
+
));
|
|
174
|
+
|
|
175
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
176
|
+
// INFLECTION RULES
|
|
177
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Pluralization rules for Portuguese words.
|
|
181
|
+
* Format: [suffix, replacement, suffixLength]
|
|
182
|
+
* @type {ReadonlyArray<Readonly<[string, string, number]>>}
|
|
183
|
+
*/
|
|
184
|
+
const PLURAL_RULES = Object.freeze([
|
|
185
|
+
// -ão → -ões (default; -ães and -ãos handled via irregulars)
|
|
186
|
+
['ao', 'oes', 2],
|
|
187
|
+
// -m → -ns
|
|
188
|
+
['m', 'ns', 1],
|
|
189
|
+
// -l endings
|
|
190
|
+
['al', 'ais', 2],
|
|
191
|
+
['el', 'eis', 2],
|
|
192
|
+
['ol', 'ois', 2],
|
|
193
|
+
['ul', 'uis', 2],
|
|
194
|
+
['il', 'is', 2], // Stressed -il; unstressed in irregulars
|
|
195
|
+
]);
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Singularization rules for Portuguese words.
|
|
199
|
+
* Format: [suffix, replacement, suffixLength]
|
|
200
|
+
* @type {ReadonlyArray<Readonly<[string, string, number]>>}
|
|
201
|
+
*/
|
|
202
|
+
const SINGULAR_RULES = Object.freeze([
|
|
203
|
+
// -ões/-ães/-ãos → -ão
|
|
204
|
+
['oes', 'ao', 3],
|
|
205
|
+
['aes', 'ao', 3],
|
|
206
|
+
['aos', 'ao', 3],
|
|
207
|
+
// -ns → -m
|
|
208
|
+
['ns', 'm', 2],
|
|
209
|
+
// -l endings reverse
|
|
210
|
+
['ais', 'al', 3],
|
|
211
|
+
['eis', 'el', 3],
|
|
212
|
+
['ois', 'ol', 3],
|
|
213
|
+
['uis', 'ul', 3],
|
|
214
|
+
['is', 'il', 2],
|
|
215
|
+
// -eses → -es
|
|
216
|
+
['eses', 'es', 4],
|
|
217
|
+
]);
|
|
218
|
+
|
|
219
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
220
|
+
// UTILITY FUNCTIONS
|
|
221
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Normalizes a word for rule matching and irregular lookup.
|
|
225
|
+
* @param {string} word - The word to normalize
|
|
226
|
+
* @returns {string} Normalized word (lowercase, no diacritics)
|
|
227
|
+
*/
|
|
228
|
+
const normalizeWord = (word) =>
|
|
229
|
+
stripDiacritics((word ?? '').toString()).toLowerCase().trim();
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Applies suffix rules to a word.
|
|
233
|
+
* @param {string} word - Normalized word
|
|
234
|
+
* @param {ReadonlyArray<Readonly<[string, string, number]>>} rules
|
|
235
|
+
* @returns {string|null} Transformed word or null if no rule matched
|
|
236
|
+
*/
|
|
237
|
+
const applyRules = (word, rules) => {
|
|
238
|
+
for (const [suffix, replacement, length] of rules) {
|
|
239
|
+
if (word.endsWith(suffix)) {
|
|
240
|
+
return word.slice(0, -length) + replacement;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return null;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
247
|
+
// PLURALIZATION
|
|
248
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Converts a Portuguese word to its plural form.
|
|
252
|
+
* Output is normalized (no diacritics, lowercase).
|
|
253
|
+
*
|
|
254
|
+
* @param {string} word - The word to pluralize
|
|
255
|
+
* @param {Record<string, string>} [irregulars=PT_BR_DEFAULT_IRREGULARS]
|
|
256
|
+
* @returns {string} The pluralized word (normalized)
|
|
257
|
+
*/
|
|
258
|
+
export const pluralizeWordPtBr = (
|
|
259
|
+
word,
|
|
260
|
+
irregulars = PT_BR_DEFAULT_IRREGULARS
|
|
261
|
+
) => {
|
|
262
|
+
const normalized = normalizeWord(word);
|
|
263
|
+
if (!normalized) return '';
|
|
264
|
+
|
|
265
|
+
// 1. Check irregulars first
|
|
266
|
+
const irregular = irregulars[normalized];
|
|
267
|
+
if (irregular !== undefined) {
|
|
268
|
+
return irregular;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// 2. Apply suffix-based rules
|
|
272
|
+
const ruleResult = applyRules(normalized, PLURAL_RULES);
|
|
273
|
+
if (ruleResult !== null) {
|
|
274
|
+
return ruleResult;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// 3. Words ending in -x are typically invariable
|
|
278
|
+
if (PATTERNS.endsInX.test(normalized)) {
|
|
279
|
+
return normalized;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// 4. Consonants r, z, n require -es
|
|
283
|
+
if (PATTERNS.consonantEnding.test(normalized)) {
|
|
284
|
+
return normalized + 'es';
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// 5. Words ending in -s (invariable or already plural)
|
|
288
|
+
if (normalized.endsWith('s')) {
|
|
289
|
+
return normalized;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// 6. Default: add -s
|
|
293
|
+
return normalized + 's';
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
297
|
+
// SINGULARIZATION
|
|
298
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Converts a Portuguese word to its singular form.
|
|
302
|
+
* Output is normalized (no diacritics, lowercase).
|
|
303
|
+
*
|
|
304
|
+
* @param {string} word - The word to singularize
|
|
305
|
+
* @param {Record<string, string>} [irregulars=PT_BR_DEFAULT_SINGULAR_IRREGULARS]
|
|
306
|
+
* @returns {string} The singularized word (normalized)
|
|
307
|
+
*/
|
|
308
|
+
export const singularizeWordPtBr = (
|
|
309
|
+
word,
|
|
310
|
+
irregulars = PT_BR_DEFAULT_SINGULAR_IRREGULARS
|
|
311
|
+
) => {
|
|
312
|
+
const normalized = normalizeWord(word);
|
|
313
|
+
if (!normalized) return '';
|
|
314
|
+
|
|
315
|
+
// 1. Check irregulars first
|
|
316
|
+
const irregular = irregulars[normalized];
|
|
317
|
+
if (irregular !== undefined) {
|
|
318
|
+
return irregular;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// 2. Apply suffix-based rules
|
|
322
|
+
const ruleResult = applyRules(normalized, SINGULAR_RULES);
|
|
323
|
+
if (ruleResult !== null) {
|
|
324
|
+
return ruleResult;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// 3. Handle consonant + es pattern
|
|
328
|
+
if (PATTERNS.consonantEsEnding.test(normalized)) {
|
|
329
|
+
return normalized.slice(0, -2);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// 4. Words ending in vowel+s: remove s
|
|
333
|
+
if (PATTERNS.vowelBeforeS.test(normalized)) {
|
|
334
|
+
return normalized.slice(0, -1);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// 5. Already singular or invariable
|
|
338
|
+
return normalized;
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
342
|
+
// COMPOUND TERM HANDLING
|
|
343
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Pluralizes a compound property/relation name in Portuguese.
|
|
347
|
+
*/
|
|
348
|
+
export const pluralizeRelationPropertyPtBr = (
|
|
349
|
+
term,
|
|
350
|
+
{ pluralizeWord = pluralizeWordPtBr, connectors = PT_BR_CONNECTORS } = {}
|
|
351
|
+
) => applyToCompoundHead(term, { connectors, transformWord: pluralizeWord });
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Singularizes a compound property/relation name in Portuguese.
|
|
355
|
+
*/
|
|
356
|
+
export const singularizeRelationPropertyPtBr = (
|
|
357
|
+
term,
|
|
358
|
+
{ singularizeWord = singularizeWordPtBr, connectors = PT_BR_CONNECTORS } = {}
|
|
359
|
+
) => applyToCompoundHead(term, { connectors, transformWord: singularizeWord });
|
|
360
|
+
|
|
361
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
362
|
+
// INFLECTOR FACTORY
|
|
363
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Creates a Brazilian Portuguese inflector instance.
|
|
367
|
+
*/
|
|
368
|
+
export const createPtBrInflector = ({ customIrregulars = {} } = {}) => {
|
|
369
|
+
const irregularPlurals = Object.freeze({
|
|
370
|
+
...PT_BR_DEFAULT_IRREGULARS,
|
|
371
|
+
...customIrregulars
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
const irregularSingulars = Object.freeze({
|
|
375
|
+
...PT_BR_DEFAULT_SINGULAR_IRREGULARS,
|
|
376
|
+
...buildSingularIrregulars(customIrregulars)
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
return Object.freeze({
|
|
380
|
+
locale: 'pt-BR',
|
|
381
|
+
irregularPlurals,
|
|
382
|
+
irregularSingulars,
|
|
383
|
+
pluralizeWord: (w) => pluralizeWordPtBr(w, irregularPlurals),
|
|
384
|
+
singularizeWord: (w) => singularizeWordPtBr(w, irregularSingulars),
|
|
385
|
+
pluralizeRelationProperty: pluralizeRelationPropertyPtBr,
|
|
386
|
+
singularizeRelationProperty: singularizeRelationPropertyPtBr,
|
|
387
|
+
normalizeForLookup: normalizeWord
|
|
388
|
+
});
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
export default createPtBrInflector;
|
|
@@ -1,18 +1,23 @@
|
|
|
1
|
+
import { resolveInflector } from './inflection/index.mjs';
|
|
2
|
+
|
|
1
3
|
export class BaseNamingStrategy {
|
|
2
|
-
constructor(irregulars = {}) {
|
|
4
|
+
constructor(irregulars = {}, inflector = resolveInflector('en')) {
|
|
3
5
|
this.irregulars = new Map();
|
|
4
6
|
this.inverseIrregulars = new Map();
|
|
7
|
+
this.inflector = inflector;
|
|
5
8
|
for (const [singular, plural] of Object.entries(irregulars)) {
|
|
6
9
|
if (!singular || !plural) continue;
|
|
7
|
-
const
|
|
8
|
-
const
|
|
10
|
+
const normalize = this.inflector.normalizeForIrregularKey || (value => String(value).toLowerCase());
|
|
11
|
+
const singularKey = normalize(singular);
|
|
12
|
+
const pluralValue = normalize(plural);
|
|
9
13
|
this.irregulars.set(singularKey, pluralValue);
|
|
10
14
|
this.inverseIrregulars.set(pluralValue, singularKey);
|
|
11
15
|
}
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
applyIrregular(word, direction) {
|
|
15
|
-
const
|
|
19
|
+
const normalize = this.inflector.normalizeForIrregularKey || (value => String(value).toLowerCase());
|
|
20
|
+
const lower = normalize(word);
|
|
16
21
|
if (direction === 'plural' && this.irregulars.has(lower)) {
|
|
17
22
|
return this.irregulars.get(lower);
|
|
18
23
|
}
|
|
@@ -49,20 +54,13 @@ export class BaseNamingStrategy {
|
|
|
49
54
|
pluralize(word) {
|
|
50
55
|
const irregular = this.applyIrregular(word, 'plural');
|
|
51
56
|
if (irregular) return irregular;
|
|
52
|
-
|
|
53
|
-
if (lower.endsWith('y')) return `${lower.slice(0, -1)}ies`;
|
|
54
|
-
if (lower.endsWith('s')) return `${lower}es`;
|
|
55
|
-
return `${lower}s`;
|
|
57
|
+
return this.inflector.pluralizeWord(word);
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
singularize(word) {
|
|
59
61
|
const irregular = this.applyIrregular(word, 'singular');
|
|
60
62
|
if (irregular) return irregular;
|
|
61
|
-
|
|
62
|
-
if (lower.endsWith('ies')) return `${lower.slice(0, -3)}y`;
|
|
63
|
-
if (lower.endsWith('ses')) return lower.slice(0, -2);
|
|
64
|
-
if (lower.endsWith('s')) return lower.slice(0, -1);
|
|
65
|
-
return lower;
|
|
63
|
+
return this.inflector.singularizeWord(word);
|
|
66
64
|
}
|
|
67
65
|
|
|
68
66
|
classNameFromTable(tableName) {
|
|
@@ -76,7 +74,11 @@ export class BaseNamingStrategy {
|
|
|
76
74
|
}
|
|
77
75
|
|
|
78
76
|
hasManyProperty(targetTable) {
|
|
79
|
-
|
|
77
|
+
const base = this.singularize(targetTable);
|
|
78
|
+
const plural = this.inflector.pluralizeRelationProperty
|
|
79
|
+
? this.inflector.pluralizeRelationProperty(base, { pluralizeWord: word => this.pluralize(word) })
|
|
80
|
+
: this.pluralize(base);
|
|
81
|
+
return this.toCamelCase(plural);
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
hasOneProperty(targetTable) {
|
|
@@ -84,7 +86,7 @@ export class BaseNamingStrategy {
|
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
belongsToManyProperty(targetTable) {
|
|
87
|
-
return this.
|
|
89
|
+
return this.hasManyProperty(targetTable);
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
defaultTableNameFromClass(className) {
|
|
@@ -94,59 +96,21 @@ export class BaseNamingStrategy {
|
|
|
94
96
|
}
|
|
95
97
|
}
|
|
96
98
|
|
|
97
|
-
export class EnglishNamingStrategy extends BaseNamingStrategy {
|
|
98
|
-
|
|
99
|
-
const DEFAULT_PT_IRREGULARS = {
|
|
100
|
-
mao: 'maos',
|
|
101
|
-
pao: 'paes',
|
|
102
|
-
cao: 'caes',
|
|
103
|
-
mal: 'males',
|
|
104
|
-
consul: 'consules'
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
export class PortugueseNamingStrategy extends BaseNamingStrategy {
|
|
99
|
+
export class EnglishNamingStrategy extends BaseNamingStrategy {
|
|
108
100
|
constructor(irregulars = {}) {
|
|
109
|
-
super(
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
pluralize(word) {
|
|
113
|
-
const irregular = this.applyIrregular(word, 'plural');
|
|
114
|
-
if (irregular) return irregular;
|
|
115
|
-
const lower = word.toLowerCase();
|
|
116
|
-
if (lower.endsWith('cao')) return `${lower.slice(0, -3)}coes`;
|
|
117
|
-
if (lower.endsWith('ao')) return `${lower.slice(0, -2)}oes`;
|
|
118
|
-
if (lower.endsWith('m')) return `${lower.slice(0, -1)}ns`;
|
|
119
|
-
if (lower.endsWith('al')) return `${lower.slice(0, -2)}ais`;
|
|
120
|
-
if (lower.endsWith('el')) return `${lower.slice(0, -2)}eis`;
|
|
121
|
-
if (lower.endsWith('ol')) return `${lower.slice(0, -2)}ois`;
|
|
122
|
-
if (lower.endsWith('ul')) return `${lower.slice(0, -2)}uis`;
|
|
123
|
-
if (lower.endsWith('il')) return `${lower.slice(0, -2)}is`;
|
|
124
|
-
if (/[rznsx]$/.test(lower)) return `${lower}es`;
|
|
125
|
-
if (lower.endsWith('s')) return lower;
|
|
126
|
-
return `${lower}s`;
|
|
101
|
+
super(irregulars, resolveInflector('en'));
|
|
127
102
|
}
|
|
103
|
+
}
|
|
128
104
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (lower.endsWith('coes')) return `${lower.slice(0, -4)}cao`;
|
|
134
|
-
if (lower.endsWith('oes')) return `${lower.slice(0, -3)}ao`;
|
|
135
|
-
if (lower.endsWith('ns')) return `${lower.slice(0, -2)}m`;
|
|
136
|
-
if (lower.endsWith('ais')) return `${lower.slice(0, -3)}al`;
|
|
137
|
-
if (lower.endsWith('eis')) return `${lower.slice(0, -3)}el`;
|
|
138
|
-
if (lower.endsWith('ois')) return `${lower.slice(0, -3)}ol`;
|
|
139
|
-
if (lower.endsWith('uis')) return `${lower.slice(0, -3)}ul`;
|
|
140
|
-
if (lower.endsWith('is')) return `${lower.slice(0, -2)}il`;
|
|
141
|
-
if (/[rznsx]es$/.test(lower)) return lower.replace(/es$/, '');
|
|
142
|
-
if (lower.endsWith('s')) return lower.slice(0, -1);
|
|
143
|
-
return lower;
|
|
105
|
+
export class PortugueseNamingStrategy extends BaseNamingStrategy {
|
|
106
|
+
constructor(irregulars = {}) {
|
|
107
|
+
const inflector = resolveInflector('pt-BR');
|
|
108
|
+
super({ ...inflector.defaultIrregulars, ...irregulars }, inflector);
|
|
144
109
|
}
|
|
145
110
|
}
|
|
146
111
|
|
|
147
112
|
export const createNamingStrategy = (locale = 'en', irregulars) => {
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
return new EnglishNamingStrategy(irregulars);
|
|
113
|
+
const inflector = resolveInflector(locale);
|
|
114
|
+
const mergedIrregulars = { ...(inflector.defaultIrregulars || {}), ...(irregulars || {}) };
|
|
115
|
+
return new BaseNamingStrategy(mergedIrregulars, inflector);
|
|
152
116
|
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export {
|
|
2
|
+
stripDiacritics,
|
|
3
|
+
normalizeLookup,
|
|
4
|
+
detectTextFormat,
|
|
5
|
+
splitIntoWords,
|
|
6
|
+
rebuildFromWords,
|
|
7
|
+
applyToCompoundHead
|
|
8
|
+
} from './inflection/compound.mjs';
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
PT_BR_CONNECTORS as DEFAULT_CONNECTORS,
|
|
12
|
+
pluralizeWordPtBr,
|
|
13
|
+
pluralizeRelationPropertyPtBr as pluralizeCompoundHead
|
|
14
|
+
} from './inflection/pt-br.mjs';
|
|
15
|
+
|
|
16
|
+
export { DEFAULT_CONNECTORS, pluralizeWordPtBr, pluralizeCompoundHead };
|
|
17
|
+
|
|
18
|
+
export const pluralizeTerm = pluralizeCompoundHead;
|
|
19
|
+
export default pluralizeTerm;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ReferentialAction } from '../../../schema/column-types.js';
|
|
2
2
|
import { SchemaIntrospector, IntrospectOptions } from './types.js';
|
|
3
|
-
import { shouldIncludeTable } from './utils.js';
|
|
3
|
+
import { shouldIncludeTable, queryRows } from './utils.js';
|
|
4
4
|
import { DatabaseSchema, DatabaseTable, DatabaseIndex, DatabaseColumn } from '../schema-types.js';
|
|
5
5
|
import type { IntrospectContext } from './context.js';
|
|
6
6
|
import { runSelectNode } from './run-select.js';
|
|
@@ -67,6 +67,19 @@ type MssqlForeignKeyRow = {
|
|
|
67
67
|
update_rule: string | null;
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
+
type MssqlTableCommentRow = {
|
|
71
|
+
table_schema: string;
|
|
72
|
+
table_name: string;
|
|
73
|
+
comment: string | null;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
type MssqlColumnCommentRow = {
|
|
77
|
+
table_schema: string;
|
|
78
|
+
table_name: string;
|
|
79
|
+
column_name: string;
|
|
80
|
+
comment: string | null;
|
|
81
|
+
};
|
|
82
|
+
|
|
70
83
|
type ForeignKeyEntry = {
|
|
71
84
|
table: string;
|
|
72
85
|
column: string;
|
|
@@ -107,6 +120,60 @@ export const mssqlIntrospector: SchemaIntrospector = {
|
|
|
107
120
|
async introspect(ctx: IntrospectContext, options: IntrospectOptions): Promise<DatabaseSchema> {
|
|
108
121
|
const schema = options.schema;
|
|
109
122
|
const schemaCondition = schema ? eq(columnNode('sch', 'name'), schema) : undefined;
|
|
123
|
+
const schemaFilter = schema ? 'AND sch.name = @p1' : '';
|
|
124
|
+
const schemaParams = schema ? [schema] : [];
|
|
125
|
+
const tableCommentRows = (await queryRows(
|
|
126
|
+
ctx.executor,
|
|
127
|
+
`
|
|
128
|
+
SELECT
|
|
129
|
+
sch.name AS table_schema,
|
|
130
|
+
t.name AS table_name,
|
|
131
|
+
CONVERT(nvarchar(4000), ep.value) AS comment
|
|
132
|
+
FROM sys.extended_properties ep
|
|
133
|
+
JOIN sys.tables t ON t.object_id = ep.major_id
|
|
134
|
+
JOIN sys.schemas sch ON sch.schema_id = t.schema_id
|
|
135
|
+
WHERE ep.class = 1
|
|
136
|
+
AND ep.minor_id = 0
|
|
137
|
+
AND ep.name = 'MS_Description'
|
|
138
|
+
${schemaFilter}
|
|
139
|
+
`,
|
|
140
|
+
schemaParams
|
|
141
|
+
)) as MssqlTableCommentRow[];
|
|
142
|
+
const columnCommentRows = (await queryRows(
|
|
143
|
+
ctx.executor,
|
|
144
|
+
`
|
|
145
|
+
SELECT
|
|
146
|
+
sch.name AS table_schema,
|
|
147
|
+
t.name AS table_name,
|
|
148
|
+
col.name AS column_name,
|
|
149
|
+
CONVERT(nvarchar(4000), ep.value) AS comment
|
|
150
|
+
FROM sys.extended_properties ep
|
|
151
|
+
JOIN sys.columns col ON col.object_id = ep.major_id AND col.column_id = ep.minor_id
|
|
152
|
+
JOIN sys.tables t ON t.object_id = col.object_id
|
|
153
|
+
JOIN sys.schemas sch ON sch.schema_id = t.schema_id
|
|
154
|
+
WHERE ep.class = 1
|
|
155
|
+
AND ep.minor_id > 0
|
|
156
|
+
AND ep.name = 'MS_Description'
|
|
157
|
+
${schemaFilter}
|
|
158
|
+
`,
|
|
159
|
+
schemaParams
|
|
160
|
+
)) as MssqlColumnCommentRow[];
|
|
161
|
+
const tableComments = new Map<string, string>();
|
|
162
|
+
tableCommentRows.forEach(r => {
|
|
163
|
+
if (!shouldIncludeTable(r.table_name, options)) return;
|
|
164
|
+
if (!r.comment) return;
|
|
165
|
+
const trimmed = r.comment.trim();
|
|
166
|
+
if (!trimmed) return;
|
|
167
|
+
tableComments.set(`${r.table_schema}.${r.table_name}`, trimmed);
|
|
168
|
+
});
|
|
169
|
+
const columnComments = new Map<string, string>();
|
|
170
|
+
columnCommentRows.forEach(r => {
|
|
171
|
+
if (!shouldIncludeTable(r.table_name, options)) return;
|
|
172
|
+
if (!r.comment) return;
|
|
173
|
+
const trimmed = r.comment.trim();
|
|
174
|
+
if (!trimmed) return;
|
|
175
|
+
columnComments.set(`${r.table_schema}.${r.table_name}.${r.column_name}`, trimmed);
|
|
176
|
+
});
|
|
110
177
|
|
|
111
178
|
const dataTypeExpression = buildMssqlDataType(
|
|
112
179
|
{ table: 'ty', name: 'name' },
|
|
@@ -425,7 +492,8 @@ export const mssqlIntrospector: SchemaIntrospector = {
|
|
|
425
492
|
schema: r.table_schema,
|
|
426
493
|
columns: [],
|
|
427
494
|
primaryKey: pkMap.get(key) || [],
|
|
428
|
-
indexes: []
|
|
495
|
+
indexes: [],
|
|
496
|
+
comment: tableComments.get(key)
|
|
429
497
|
});
|
|
430
498
|
}
|
|
431
499
|
const table = tablesByKey.get(key)!;
|
|
@@ -436,6 +504,10 @@ export const mssqlIntrospector: SchemaIntrospector = {
|
|
|
436
504
|
default: r.column_default ?? undefined,
|
|
437
505
|
autoIncrement: !!r.is_identity
|
|
438
506
|
};
|
|
507
|
+
const columnComment = columnComments.get(`${key}.${r.column_name}`);
|
|
508
|
+
if (columnComment) {
|
|
509
|
+
column.comment = columnComment;
|
|
510
|
+
}
|
|
439
511
|
const fk = fkMap.get(`${key}.${r.column_name}`)?.[0];
|
|
440
512
|
if (fk) {
|
|
441
513
|
column.references = {
|