metal-orm 1.1.11 → 1.1.16

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 (182) hide show
  1. package/README.md +758 -769
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.js.map +1 -1
  4. package/package.json +100 -96
  5. package/scripts/generate-entities/schema.mjs +122 -122
  6. package/scripts/generate-entities/tree-detection.mjs +92 -92
  7. package/scripts/inflection/pt-br.mjs +468 -468
  8. package/scripts/run-eslint.mjs +33 -34
  9. package/scripts/show-sql-seed.sql +102 -0
  10. package/scripts/show-sql.mjs +321 -325
  11. package/src/codegen/naming-strategy.ts +54 -54
  12. package/src/codegen/typescript.ts +482 -482
  13. package/src/core/ast/adapters.ts +1 -1
  14. package/src/core/ast/builders.ts +123 -123
  15. package/src/core/ast/expression-builders.ts +745 -745
  16. package/src/core/ast/expression-nodes.ts +304 -304
  17. package/src/core/ast/expression-visitor.ts +212 -212
  18. package/src/core/ast/expression.ts +20 -20
  19. package/src/core/ast/helpers.ts +14 -14
  20. package/src/core/ast/join.ts +18 -18
  21. package/src/core/ast/query.ts +200 -200
  22. package/src/core/ddl/dialects/base-schema-dialect.ts +1 -1
  23. package/src/core/ddl/dialects/mssql-schema-dialect.ts +1 -1
  24. package/src/core/ddl/dialects/mysql-schema-dialect.ts +1 -1
  25. package/src/core/ddl/dialects/postgres-schema-dialect.ts +1 -1
  26. package/src/core/ddl/dialects/render-reference.test.ts +70 -70
  27. package/src/core/ddl/dialects/sqlite-schema-dialect.ts +1 -1
  28. package/src/core/ddl/introspect/catalogs/index.ts +5 -5
  29. package/src/core/ddl/introspect/catalogs/postgres.ts +146 -146
  30. package/src/core/ddl/introspect/context.ts +15 -15
  31. package/src/core/ddl/introspect/functions/postgres.ts +35 -35
  32. package/src/core/ddl/introspect/registry.ts +40 -40
  33. package/src/core/ddl/introspect/run-select.ts +35 -35
  34. package/src/core/ddl/introspect/utils.ts +56 -56
  35. package/src/core/ddl/naming-strategy.ts +16 -16
  36. package/src/core/ddl/schema-dialect.ts +56 -56
  37. package/src/core/ddl/schema-diff.ts +234 -234
  38. package/src/core/ddl/schema-generator.ts +1 -1
  39. package/src/core/ddl/schema-introspect.ts +25 -25
  40. package/src/core/ddl/sql-writing.ts +171 -171
  41. package/src/core/dialect/abstract.ts +541 -541
  42. package/src/core/dialect/base/cte-compiler.ts +33 -33
  43. package/src/core/dialect/base/function-table-formatter.ts +103 -103
  44. package/src/core/dialect/base/groupby-compiler.ts +21 -21
  45. package/src/core/dialect/base/join-compiler.ts +25 -25
  46. package/src/core/dialect/base/orderby-compiler.ts +35 -35
  47. package/src/core/dialect/base/pagination-strategy.ts +32 -32
  48. package/src/core/dialect/base/returning-strategy.ts +57 -57
  49. package/src/core/dialect/base/sql-dialect.ts +294 -294
  50. package/src/core/dialect/dialect-factory.ts +91 -91
  51. package/src/core/dialect/mssql/functions.ts +120 -120
  52. package/src/core/dialect/mssql/index.ts +317 -317
  53. package/src/core/dialect/mysql/functions.ts +103 -103
  54. package/src/core/dialect/mysql/index.ts +105 -105
  55. package/src/core/dialect/postgres/functions.ts +108 -108
  56. package/src/core/dialect/postgres/index.ts +91 -91
  57. package/src/core/dialect/sqlite/functions.ts +129 -129
  58. package/src/core/dialect/sqlite/index.ts +67 -67
  59. package/src/core/driver/database-driver.ts +19 -19
  60. package/src/core/driver/mssql-driver.ts +23 -23
  61. package/src/core/driver/mysql-driver.ts +23 -23
  62. package/src/core/driver/postgres-driver.ts +23 -23
  63. package/src/core/driver/sqlite-driver.ts +23 -23
  64. package/src/core/execution/db-executor.ts +92 -92
  65. package/src/core/execution/executors/better-sqlite3-executor.ts +90 -90
  66. package/src/core/execution/executors/mssql-executor.ts +184 -184
  67. package/src/core/execution/executors/mysql-executor.ts +88 -88
  68. package/src/core/execution/executors/postgres-executor.ts +26 -26
  69. package/src/core/execution/executors/sqlite-executor.ts +42 -42
  70. package/src/core/execution/pooling/pool-types.ts +30 -30
  71. package/src/core/execution/pooling/pool.ts +275 -275
  72. package/src/core/functions/array.ts +35 -35
  73. package/src/core/functions/control-flow.ts +79 -79
  74. package/src/core/functions/datetime.ts +237 -237
  75. package/src/core/functions/json.ts +70 -70
  76. package/src/core/functions/numeric.ts +297 -297
  77. package/src/core/functions/text.ts +334 -334
  78. package/src/core/functions/types.ts +33 -33
  79. package/src/core/hydration/types.ts +51 -51
  80. package/src/core/sql/sql-operator-config.ts +39 -39
  81. package/src/core/sql/sql.ts +152 -152
  82. package/src/decorators/column-decorator.ts +87 -87
  83. package/src/decorators/decorator-metadata.ts +143 -143
  84. package/src/decorators/entity.ts +93 -93
  85. package/src/decorators/index.ts +39 -39
  86. package/src/decorators/relations.ts +231 -231
  87. package/src/decorators/transformers/built-in/string-transformers.ts +231 -231
  88. package/src/decorators/transformers/transformer-decorators.ts +134 -134
  89. package/src/decorators/transformers/transformer-executor.ts +171 -171
  90. package/src/decorators/transformers/transformer-metadata.ts +73 -73
  91. package/src/decorators/validators/built-in/br-cep-validator.ts +55 -55
  92. package/src/decorators/validators/built-in/br-cnpj-validator.ts +105 -105
  93. package/src/decorators/validators/built-in/br-cpf-validator.ts +103 -103
  94. package/src/decorators/validators/country-validator-registry.ts +64 -64
  95. package/src/decorators/validators/country-validators-decorators.ts +203 -203
  96. package/src/decorators/validators/country-validators.ts +105 -105
  97. package/src/decorators/validators/validator-metadata.ts +59 -59
  98. package/src/dto/dto-types.ts +226 -226
  99. package/src/dto/filter-types.ts +141 -141
  100. package/src/dto/index.ts +95 -95
  101. package/src/dto/openapi/generators/base.ts +29 -29
  102. package/src/dto/openapi/generators/column.ts +37 -37
  103. package/src/dto/openapi/generators/dto.ts +91 -91
  104. package/src/dto/openapi/generators/filter.ts +75 -75
  105. package/src/dto/openapi/generators/nested-dto.ts +618 -618
  106. package/src/dto/openapi/generators/pagination.ts +111 -111
  107. package/src/dto/openapi/generators/relation-filter.ts +228 -228
  108. package/src/dto/openapi/index.ts +12 -12
  109. package/src/dto/openapi/types.ts +101 -101
  110. package/src/dto/openapi/utilities.ts +95 -95
  111. package/src/dto/pagination-utils.ts +150 -150
  112. package/src/dto/transform.ts +197 -197
  113. package/src/index.ts +86 -86
  114. package/src/orm/als.ts +58 -58
  115. package/src/orm/domain-event-bus.ts +119 -119
  116. package/src/orm/entity-context.ts +86 -86
  117. package/src/orm/entity-materializer.ts +159 -159
  118. package/src/orm/entity-meta.ts +97 -97
  119. package/src/orm/entity-registry.ts +39 -39
  120. package/src/orm/entity-relation-cache.ts +39 -39
  121. package/src/orm/execute.ts +194 -194
  122. package/src/orm/execution-context.ts +18 -18
  123. package/src/orm/hydration-context.ts +26 -26
  124. package/src/orm/hydration.ts +112 -112
  125. package/src/orm/identity-map.ts +61 -61
  126. package/src/orm/interceptor-pipeline.ts +16 -16
  127. package/src/orm/lazy-batch/belongs-to-many.ts +119 -119
  128. package/src/orm/lazy-batch/belongs-to.ts +108 -108
  129. package/src/orm/lazy-batch/has-many.ts +69 -69
  130. package/src/orm/lazy-batch/has-one.ts +68 -68
  131. package/src/orm/lazy-batch/shared.ts +125 -125
  132. package/src/orm/orm-session.ts +620 -620
  133. package/src/orm/orm.ts +34 -34
  134. package/src/orm/pooled-executor-factory.ts +121 -121
  135. package/src/orm/query-logger.ts +40 -40
  136. package/src/orm/relation-change-processor.ts +420 -420
  137. package/src/orm/relations/belongs-to.ts +143 -143
  138. package/src/orm/relations/has-many.ts +204 -204
  139. package/src/orm/relations/has-one.ts +174 -174
  140. package/src/orm/relations/many-to-many.ts +288 -288
  141. package/src/orm/relations/morph-many.ts +156 -156
  142. package/src/orm/relations/morph-one.ts +151 -151
  143. package/src/orm/relations/morph-to.ts +162 -162
  144. package/src/orm/runtime-types.ts +92 -92
  145. package/src/orm/save-graph.ts +561 -561
  146. package/src/orm/transaction-runner.ts +24 -24
  147. package/src/orm/unit-of-work.ts +426 -426
  148. package/src/query/index.ts +69 -69
  149. package/src/query/target.ts +46 -46
  150. package/src/query-builder/column-selector.ts +81 -81
  151. package/src/query-builder/delete.ts +141 -141
  152. package/src/query-builder/insert-query-state.ts +149 -149
  153. package/src/query-builder/insert.ts +117 -117
  154. package/src/query-builder/query-ast-service.ts +287 -287
  155. package/src/query-builder/query-resolution.ts +78 -78
  156. package/src/query-builder/raw-column-parser.ts +38 -38
  157. package/src/query-builder/relation-filter-utils.ts +153 -153
  158. package/src/query-builder/relation-join-planner.ts +10 -10
  159. package/src/query-builder/relation-manager.ts +87 -87
  160. package/src/query-builder/relation-projection-helper.ts +102 -102
  161. package/src/query-builder/relation-types.ts +9 -9
  162. package/src/query-builder/relation-utils.ts +15 -15
  163. package/src/query-builder/select/cte-facet.ts +40 -40
  164. package/src/query-builder/select/from-facet.ts +80 -80
  165. package/src/query-builder/select/join-facet.ts +62 -62
  166. package/src/query-builder/select/predicate-facet.ts +104 -104
  167. package/src/query-builder/select/projection-facet.ts +70 -70
  168. package/src/query-builder/select/relation-facet.ts +81 -81
  169. package/src/query-builder/select/setop-facet.ts +36 -36
  170. package/src/query-builder/select-helpers.ts +1 -1
  171. package/src/query-builder/select-query-builder-deps.ts +130 -130
  172. package/src/query-builder/select-query-state.ts +207 -207
  173. package/src/query-builder/update.ts +151 -151
  174. package/src/schema/column-types.ts +322 -322
  175. package/src/schema/table-guards.ts +37 -37
  176. package/src/schema/table.ts +281 -281
  177. package/src/tree/index.ts +13 -13
  178. package/src/tree/nested-set-strategy.ts +545 -545
  179. package/src/tree/tree-decorator.ts +397 -397
  180. package/src/tree/tree-manager.ts +754 -754
  181. package/src/tree/tree-query.ts +427 -427
  182. package/src/tree/tree-types.ts +299 -299
@@ -1,468 +1,468 @@
1
- import {
2
- applyToCompoundHead,
3
- applyToCompoundWords,
4
- detectTextFormat,
5
- normalizeLookup,
6
- splitIntoWords,
7
- stripDiacritics
8
- } from './compound.mjs';
9
-
10
- // ═══════════════════════════════════════════════════════════════════════════
11
- // PATTERNS
12
- // ═══════════════════════════════════════════════════════════════════════════
13
-
14
- /**
15
- * Precompiled regex patterns for performance.
16
- * @type {Readonly<Record<string, RegExp>>}
17
- */
18
- const PATTERNS = Object.freeze({
19
- consonantEnding: /[rzn]$/,
20
- consonantEsEnding: /[rzn]es$/,
21
- endsInX: /x$/,
22
- vowelBeforeS: /[aeiou]s$/,
23
- });
24
-
25
- // ═══════════════════════════════════════════════════════════════════════════
26
- // IRREGULAR DICTIONARIES (all normalized - no diacritics)
27
- // ═══════════════════════════════════════════════════════════════════════════
28
-
29
- /**
30
- * Default irregular plurals for Brazilian Portuguese.
31
- * Keys AND values are normalized (no diacritics, lowercase).
32
- * @type {Readonly<Record<string, string>>}
33
- */
34
- export const PT_BR_DEFAULT_IRREGULARS = Object.freeze({
35
- // ─────────────────────────────────────────────────────────────────────────
36
- // -ão → -ães (irregular, must memorize)
37
- // ─────────────────────────────────────────────────────────────────────────
38
- 'pao': 'paes',
39
- 'cao': 'caes',
40
- 'alemao': 'alemaes',
41
- 'capitao': 'capitaes',
42
- 'charlatao': 'charlataes',
43
- 'escrivao': 'escrivaes',
44
- 'tabeliao': 'tabeliaes',
45
- 'guardiao': 'guardiaes',
46
- 'sacristao': 'sacristaes',
47
-
48
- // ─────────────────────────────────────────────────────────────────────────
49
- // -ão → -ãos (irregular, must memorize)
50
- // ─────────────────────────────────────────────────────────────────────────
51
- 'mao': 'maos',
52
- 'cidadao': 'cidadaos',
53
- 'cristao': 'cristaos',
54
- 'irmao': 'irmaos',
55
- 'orgao': 'orgaos',
56
- 'bencao': 'bencaos',
57
- 'grao': 'graos',
58
- 'orfao': 'orfaos',
59
- 'sotao': 'sotaos',
60
- 'acordao': 'acordaos',
61
- 'cortesao': 'cortesaos',
62
- 'pagao': 'pagaos',
63
- 'chao': 'chaos',
64
- 'vao': 'vaos',
65
-
66
- // ─────────────────────────────────────────────────────────────────────────
67
- // -l special cases
68
- // ─────────────────────────────────────────────────────────────────────────
69
- 'mal': 'males',
70
- 'consul': 'consules',
71
-
72
- // ─────────────────────────────────────────────────────────────────────────
73
- // Unstressed -il → -eis (paroxytones)
74
- // ─────────────────────────────────────────────────────────────────────────
75
- 'fossil': 'fosseis',
76
- 'reptil': 'repteis',
77
- 'facil': 'faceis',
78
- 'dificil': 'dificeis',
79
- 'util': 'uteis',
80
- 'inutil': 'inuteis',
81
- 'agil': 'ageis',
82
- 'fragil': 'frageis',
83
- 'projetil': 'projeteis',
84
- 'volatil': 'volateis',
85
- 'docil': 'doceis',
86
- 'portatil': 'portateis',
87
- 'textil': 'texteis',
88
-
89
- // ─────────────────────────────────────────────────────────────────────────
90
- // Singular words ending in -s/-us (not invariable, need -es plural)
91
- // ─────────────────────────────────────────────────────────────────────────
92
- 'deus': 'deuses',
93
-
94
- // ─────────────────────────────────────────────────────────────────────────
95
- // Invariable words (paroxytone/proparoxytone ending in -s/-x)
96
- // ─────────────────────────────────────────────────────────────────────────
97
- 'caos': 'caos',
98
- 'onibus': 'onibus',
99
- 'lapis': 'lapis',
100
- 'virus': 'virus',
101
- 'atlas': 'atlas',
102
- 'pires': 'pires',
103
- 'cais': 'cais',
104
- 'torax': 'torax',
105
- 'fenix': 'fenix',
106
- 'xerox': 'xerox',
107
- 'latex': 'latex',
108
- 'index': 'index',
109
- 'duplex': 'duplex',
110
- 'telex': 'telex',
111
- 'climax': 'climax',
112
- 'simples': 'simples',
113
- 'oasis': 'oasis',
114
- 'tenis': 'tenis',
115
-
116
- // ─────────────────────────────────────────────────────────────────────────
117
- // -ês → -eses (nationalities, months, etc.)
118
- // ─────────────────────────────────────────────────────────────────────────
119
- 'portugues': 'portugueses',
120
- 'ingles': 'ingleses',
121
- 'frances': 'franceses',
122
- 'holandes': 'holandeses',
123
- 'japones': 'japoneses',
124
- 'chines': 'chineses',
125
- 'irlandes': 'irlandeses',
126
- 'escoces': 'escoceses',
127
- 'mes': 'meses',
128
- 'burges': 'burgueses',
129
- 'fregues': 'fregueses',
130
- 'marques': 'marqueses',
131
-
132
- // ─────────────────────────────────────────────────────────────────────────
133
- // Other irregulars
134
- // ─────────────────────────────────────────────────────────────────────────
135
- 'qualquer': 'quaisquer',
136
- 'carater': 'caracteres',
137
- 'junior': 'juniores',
138
- 'senior': 'seniores',
139
-
140
- // ─────────────────────────────────────────────────────────────────────────
141
- // Ambiguous normalized forms (context-dependent)
142
- // "pais" (plural of pai) is invariable; "país" → "países" handled by rule
143
- // ─────────────────────────────────────────────────────────────────────────
144
- 'pais': 'paises', // país → países (oxytone in -s)
145
- });
146
-
147
- /**
148
- * Builds reverse irregular mapping (plural → singular).
149
- * @param {Record<string, string>} irregulars
150
- * @returns {Record<string, string>}
151
- */
152
- const buildSingularIrregulars = (irregulars) => {
153
- const result = {};
154
- for (const [singular, plural] of Object.entries(irregulars)) {
155
- if (plural !== singular) {
156
- result[plural] = singular;
157
- }
158
- }
159
- return result;
160
- };
161
-
162
- /**
163
- * Default irregular singulars (auto-generated reverse mapping).
164
- * @type {Readonly<Record<string, string>>}
165
- */
166
- export const PT_BR_DEFAULT_SINGULAR_IRREGULARS = Object.freeze(
167
- buildSingularIrregulars(PT_BR_DEFAULT_IRREGULARS)
168
- );
169
-
170
- // ═══════════════════════════════════════════════════════════════════════════
171
- // CONNECTORS
172
- // ═══════════════════════════════════════════════════════════════════════════
173
-
174
- /**
175
- * Portuguese connector words used in compound expressions.
176
- * @type {ReadonlySet<string>}
177
- */
178
- export const PT_BR_CONNECTORS = Object.freeze(new Set(
179
- [
180
- 'de', 'da', 'do', 'das', 'dos',
181
- 'em', 'na', 'no', 'nas', 'nos',
182
- 'a', 'ao', 'as', 'aos',
183
- 'com', 'sem', 'sob', 'sobre',
184
- 'para', 'por', 'pela', 'pelo', 'pelas', 'pelos',
185
- 'entre', 'contra', 'perante',
186
- 'e', 'ou'
187
- ].map(normalizeLookup)
188
- ));
189
-
190
- const hasConnectorWord = (term, connectors) => {
191
- if (!term || !String(term).trim()) return false;
192
- const original = String(term).trim();
193
- const format = detectTextFormat(original);
194
- const words = splitIntoWords(original, format);
195
- return words.some(word => connectors?.has?.(normalizeLookup(word)));
196
- };
197
-
198
- // ═══════════════════════════════════════════════════════════════════════════
199
- // INFLECTION RULES
200
- // ═══════════════════════════════════════════════════════════════════════════
201
-
202
- /**
203
- * Pluralization rules for Portuguese words.
204
- * Format: [suffix, replacement, suffixLength]
205
- * @type {ReadonlyArray<Readonly<[string, string, number]>>}
206
- */
207
- const PLURAL_RULES = Object.freeze([
208
- // -ão → -ões (default; -ães and -ãos handled via irregulars)
209
- ['ao', 'oes', 2],
210
- // -m → -ns
211
- ['m', 'ns', 1],
212
- // -l endings
213
- ['al', 'ais', 2],
214
- ['el', 'eis', 2],
215
- ['ol', 'ois', 2],
216
- ['ul', 'uis', 2],
217
- ['il', 'is', 2], // Stressed -il; unstressed in irregulars
218
- // Oxytone words ending in -s add -es (gás→gases, ás→ases, ês→eses, etc.)
219
- // Note: normalized form (no diacritics), so "gás" becomes "gas"
220
- ['as', 'ases', 2],
221
- ['es', 'eses', 2],
222
- ['is', 'ises', 2],
223
- ['os', 'oses', 2],
224
- ['us', 'uses', 2],
225
- ]);
226
-
227
- /**
228
- * Singularization rules for Portuguese words.
229
- * Format: [suffix, replacement, suffixLength]
230
- * @type {ReadonlyArray<Readonly<[string, string, number]>>}
231
- */
232
- const SINGULAR_RULES = Object.freeze([
233
- // -ões/-ães/-ãos → -ão
234
- ['oes', 'ao', 3],
235
- ['aes', 'ao', 3],
236
- ['aos', 'ao', 3],
237
- // -ns → -m
238
- ['ns', 'm', 2],
239
- // -l endings reverse
240
- ['ais', 'al', 3],
241
- ['eis', 'el', 3],
242
- ['ois', 'ol', 3],
243
- ['uis', 'ul', 3],
244
- ['is', 'il', 2],
245
- // Oxytone -Vses → -Vs (gases→gas, meses→mes, etc.)
246
- ['ases', 'as', 4],
247
- ['eses', 'es', 4],
248
- ['ises', 'is', 4],
249
- ['oses', 'os', 4],
250
- ['uses', 'us', 4],
251
- ]);
252
-
253
- // ═══════════════════════════════════════════════════════════════════════════
254
- // UTILITY FUNCTIONS
255
- // ═══════════════════════════════════════════════════════════════════════════
256
-
257
- /**
258
- * Normalizes a word for rule matching and irregular lookup.
259
- * @param {string} word - The word to normalize
260
- * @returns {string} Normalized word (lowercase, no diacritics)
261
- */
262
- const normalizeWord = (word) =>
263
- stripDiacritics((word ?? '').toString()).toLowerCase().trim();
264
-
265
- /**
266
- * Applies suffix rules to a word.
267
- * @param {string} word - Normalized word
268
- * @param {ReadonlyArray<Readonly<[string, string, number]>>} rules
269
- * @returns {string|null} Transformed word or null if no rule matched
270
- */
271
- const applyRules = (word, rules) => {
272
- for (const [suffix, replacement, length] of rules) {
273
- if (word.endsWith(suffix)) {
274
- return word.slice(0, -length) + replacement;
275
- }
276
- }
277
- return null;
278
- };
279
-
280
- // ═══════════════════════════════════════════════════════════════════════════
281
- // PLURALIZATION
282
- // ═══════════════════════════════════════════════════════════════════════════
283
-
284
- /**
285
- * Converts a Portuguese word to its plural form.
286
- * Output is normalized (no diacritics, lowercase).
287
- *
288
- * @param {string} word - The word to pluralize
289
- * @param {Record<string, string>} [irregulars=PT_BR_DEFAULT_IRREGULARS]
290
- * @returns {string} The pluralized word (normalized)
291
- */
292
- export const pluralizeWordPtBr = (
293
- word,
294
- irregulars = PT_BR_DEFAULT_IRREGULARS
295
- ) => {
296
- const normalized = normalizeWord(word);
297
- if (!normalized) return '';
298
-
299
- // 1. Check irregulars first
300
- const irregular = irregulars[normalized];
301
- if (irregular !== undefined) {
302
- return irregular;
303
- }
304
-
305
- // 2. Apply suffix-based rules
306
- const ruleResult = applyRules(normalized, PLURAL_RULES);
307
- if (ruleResult !== null) {
308
- return ruleResult;
309
- }
310
-
311
- // 3. Words ending in -x are typically invariable
312
- if (PATTERNS.endsInX.test(normalized)) {
313
- return normalized;
314
- }
315
-
316
- // 4. Consonants r, z, n require -es
317
- if (PATTERNS.consonantEnding.test(normalized)) {
318
- return normalized + 'es';
319
- }
320
-
321
- // 5. Words ending in -s (invariable or already plural)
322
- if (normalized.endsWith('s')) {
323
- return normalized;
324
- }
325
-
326
- // 6. Default: add -s
327
- return normalized + 's';
328
- };
329
-
330
- // ═══════════════════════════════════════════════════════════════════════════
331
- // SINGULARIZATION
332
- // ═══════════════════════════════════════════════════════════════════════════
333
-
334
- /**
335
- * Converts a Portuguese word to its singular form.
336
- * Output is normalized (no diacritics, lowercase).
337
- *
338
- * @param {string} word - The word to singularize
339
- * @param {Record<string, string>} [irregulars=PT_BR_DEFAULT_SINGULAR_IRREGULARS]
340
- * @returns {string} The singularized word (normalized)
341
- */
342
- export const singularizeWordPtBr = (
343
- word,
344
- irregulars = PT_BR_DEFAULT_SINGULAR_IRREGULARS
345
- ) => {
346
- const normalized = normalizeWord(word);
347
- if (!normalized) return '';
348
-
349
- // 1. Check irregulars first
350
- const irregular = irregulars[normalized];
351
- if (irregular !== undefined) {
352
- return irregular;
353
- }
354
-
355
- // 2. Apply suffix-based rules
356
- const ruleResult = applyRules(normalized, SINGULAR_RULES);
357
- if (ruleResult !== null) {
358
- return ruleResult;
359
- }
360
-
361
- // 3. Handle consonant + es pattern
362
- if (PATTERNS.consonantEsEnding.test(normalized)) {
363
- return normalized.slice(0, -2);
364
- }
365
-
366
- // 4. Words ending in vowel+s: remove s
367
- if (PATTERNS.vowelBeforeS.test(normalized)) {
368
- return normalized.slice(0, -1);
369
- }
370
-
371
- // 5. Already singular or invariable
372
- return normalized;
373
- };
374
-
375
- // ═══════════════════════════════════════════════════════════════════════════
376
- // NOUN SPECIFIERS (SUBSTANTIVOS DETERMINANTES)
377
- // ═══════════════════════════════════════════════════════════════════════════
378
-
379
- /**
380
- * Portuguese words that act as specifiers/delimiters in compound nouns.
381
- * When these appear as the second term, only the first term varies.
382
- * @type {ReadonlySet<string>}
383
- */
384
- export const PT_BR_NOUN_SPECIFIERS = Object.freeze(new Set(
385
- [
386
- 'correcao', 'padrao', 'limite', 'chave', 'base', 'chefe',
387
- 'satelite', 'fantasma', 'monstro', 'escola', 'piloto',
388
- 'femea', 'macho', 'geral', 'solicitacao'
389
- ].map(normalizeLookup)
390
- ));
391
-
392
- const isCompoundWithSpecifier = (term, specifiers = PT_BR_NOUN_SPECIFIERS) => {
393
- if (!term || !String(term).trim()) return false;
394
- const original = String(term).trim();
395
- const format = detectTextFormat(original);
396
- const words = splitIntoWords(original, format);
397
-
398
- if (words.length < 2) return false;
399
-
400
- // Check if the last word is a known specifier
401
- const lastWord = words[words.length - 1];
402
- return specifiers.has(normalizeLookup(lastWord));
403
- };
404
-
405
- // ═══════════════════════════════════════════════════════════════════════════
406
- // COMPOUND TERM HANDLING
407
- // ═══════════════════════════════════════════════════════════════════════════
408
-
409
- /**
410
- * Pluralizes a compound property/relation name in Portuguese.
411
- */
412
- export const pluralizeRelationPropertyPtBr = (
413
- term,
414
- { pluralizeWord = pluralizeWordPtBr, connectors = PT_BR_CONNECTORS, specifiers = PT_BR_NOUN_SPECIFIERS } = {}
415
- ) => {
416
- if (hasConnectorWord(term, connectors) || isCompoundWithSpecifier(term, specifiers)) {
417
- return applyToCompoundHead(term, { connectors, transformWord: pluralizeWord });
418
- }
419
- return applyToCompoundWords(term, { connectors, transformWord: pluralizeWord });
420
- }
421
-
422
- /**
423
- * Singularizes a compound property/relation name in Portuguese.
424
- */
425
- export const singularizeRelationPropertyPtBr = (
426
- term,
427
- { singularizeWord = singularizeWordPtBr, connectors = PT_BR_CONNECTORS, specifiers = PT_BR_NOUN_SPECIFIERS } = {}
428
- ) => {
429
- if (hasConnectorWord(term, connectors) || isCompoundWithSpecifier(term, specifiers)) {
430
- return applyToCompoundHead(term, { connectors, transformWord: singularizeWord });
431
- }
432
- return applyToCompoundWords(term, { connectors, transformWord: singularizeWord });
433
- }
434
-
435
- // ═══════════════════════════════════════════════════════════════════════════
436
- // INFLECTOR FACTORY
437
- // ═══════════════════════════════════════════════════════════════════════════
438
-
439
- /**
440
- * Creates a Brazilian Portuguese inflector instance.
441
- */
442
- export const createPtBrInflector = ({ customIrregulars = {} } = {}) => {
443
- const irregularPlurals = Object.freeze({
444
- ...PT_BR_DEFAULT_IRREGULARS,
445
- ...customIrregulars
446
- });
447
-
448
- const irregularSingulars = Object.freeze({
449
- ...PT_BR_DEFAULT_SINGULAR_IRREGULARS,
450
- ...buildSingularIrregulars(customIrregulars)
451
- });
452
-
453
- const pluralizeWord = (w) => pluralizeWordPtBr(w, irregularPlurals);
454
- const singularizeWord = (w) => singularizeWordPtBr(w, irregularSingulars);
455
-
456
- return Object.freeze({
457
- locale: 'pt-BR',
458
- irregularPlurals,
459
- irregularSingulars,
460
- pluralizeWord,
461
- singularizeWord,
462
- pluralizeRelationProperty: (term) => pluralizeRelationPropertyPtBr(term, { pluralizeWord }),
463
- singularizeRelationProperty: (term) => singularizeRelationPropertyPtBr(term, { singularizeWord }),
464
- normalizeForLookup: normalizeWord
465
- });
466
- };
467
-
468
- export default createPtBrInflector;
1
+ import {
2
+ applyToCompoundHead,
3
+ applyToCompoundWords,
4
+ detectTextFormat,
5
+ normalizeLookup,
6
+ splitIntoWords,
7
+ stripDiacritics
8
+ } from './compound.mjs';
9
+
10
+ // ═══════════════════════════════════════════════════════════════════════════
11
+ // PATTERNS
12
+ // ═══════════════════════════════════════════════════════════════════════════
13
+
14
+ /**
15
+ * Precompiled regex patterns for performance.
16
+ * @type {Readonly<Record<string, RegExp>>}
17
+ */
18
+ const PATTERNS = Object.freeze({
19
+ consonantEnding: /[rzn]$/,
20
+ consonantEsEnding: /[rzn]es$/,
21
+ endsInX: /x$/,
22
+ vowelBeforeS: /[aeiou]s$/,
23
+ });
24
+
25
+ // ═══════════════════════════════════════════════════════════════════════════
26
+ // IRREGULAR DICTIONARIES (all normalized - no diacritics)
27
+ // ═══════════════════════════════════════════════════════════════════════════
28
+
29
+ /**
30
+ * Default irregular plurals for Brazilian Portuguese.
31
+ * Keys AND values are normalized (no diacritics, lowercase).
32
+ * @type {Readonly<Record<string, string>>}
33
+ */
34
+ export const PT_BR_DEFAULT_IRREGULARS = Object.freeze({
35
+ // ─────────────────────────────────────────────────────────────────────────
36
+ // -ão → -ães (irregular, must memorize)
37
+ // ─────────────────────────────────────────────────────────────────────────
38
+ 'pao': 'paes',
39
+ 'cao': 'caes',
40
+ 'alemao': 'alemaes',
41
+ 'capitao': 'capitaes',
42
+ 'charlatao': 'charlataes',
43
+ 'escrivao': 'escrivaes',
44
+ 'tabeliao': 'tabeliaes',
45
+ 'guardiao': 'guardiaes',
46
+ 'sacristao': 'sacristaes',
47
+
48
+ // ─────────────────────────────────────────────────────────────────────────
49
+ // -ão → -ãos (irregular, must memorize)
50
+ // ─────────────────────────────────────────────────────────────────────────
51
+ 'mao': 'maos',
52
+ 'cidadao': 'cidadaos',
53
+ 'cristao': 'cristaos',
54
+ 'irmao': 'irmaos',
55
+ 'orgao': 'orgaos',
56
+ 'bencao': 'bencaos',
57
+ 'grao': 'graos',
58
+ 'orfao': 'orfaos',
59
+ 'sotao': 'sotaos',
60
+ 'acordao': 'acordaos',
61
+ 'cortesao': 'cortesaos',
62
+ 'pagao': 'pagaos',
63
+ 'chao': 'chaos',
64
+ 'vao': 'vaos',
65
+
66
+ // ─────────────────────────────────────────────────────────────────────────
67
+ // -l special cases
68
+ // ─────────────────────────────────────────────────────────────────────────
69
+ 'mal': 'males',
70
+ 'consul': 'consules',
71
+
72
+ // ─────────────────────────────────────────────────────────────────────────
73
+ // Unstressed -il → -eis (paroxytones)
74
+ // ─────────────────────────────────────────────────────────────────────────
75
+ 'fossil': 'fosseis',
76
+ 'reptil': 'repteis',
77
+ 'facil': 'faceis',
78
+ 'dificil': 'dificeis',
79
+ 'util': 'uteis',
80
+ 'inutil': 'inuteis',
81
+ 'agil': 'ageis',
82
+ 'fragil': 'frageis',
83
+ 'projetil': 'projeteis',
84
+ 'volatil': 'volateis',
85
+ 'docil': 'doceis',
86
+ 'portatil': 'portateis',
87
+ 'textil': 'texteis',
88
+
89
+ // ─────────────────────────────────────────────────────────────────────────
90
+ // Singular words ending in -s/-us (not invariable, need -es plural)
91
+ // ─────────────────────────────────────────────────────────────────────────
92
+ 'deus': 'deuses',
93
+
94
+ // ─────────────────────────────────────────────────────────────────────────
95
+ // Invariable words (paroxytone/proparoxytone ending in -s/-x)
96
+ // ─────────────────────────────────────────────────────────────────────────
97
+ 'caos': 'caos',
98
+ 'onibus': 'onibus',
99
+ 'lapis': 'lapis',
100
+ 'virus': 'virus',
101
+ 'atlas': 'atlas',
102
+ 'pires': 'pires',
103
+ 'cais': 'cais',
104
+ 'torax': 'torax',
105
+ 'fenix': 'fenix',
106
+ 'xerox': 'xerox',
107
+ 'latex': 'latex',
108
+ 'index': 'index',
109
+ 'duplex': 'duplex',
110
+ 'telex': 'telex',
111
+ 'climax': 'climax',
112
+ 'simples': 'simples',
113
+ 'oasis': 'oasis',
114
+ 'tenis': 'tenis',
115
+
116
+ // ─────────────────────────────────────────────────────────────────────────
117
+ // -ês → -eses (nationalities, months, etc.)
118
+ // ─────────────────────────────────────────────────────────────────────────
119
+ 'portugues': 'portugueses',
120
+ 'ingles': 'ingleses',
121
+ 'frances': 'franceses',
122
+ 'holandes': 'holandeses',
123
+ 'japones': 'japoneses',
124
+ 'chines': 'chineses',
125
+ 'irlandes': 'irlandeses',
126
+ 'escoces': 'escoceses',
127
+ 'mes': 'meses',
128
+ 'burges': 'burgueses',
129
+ 'fregues': 'fregueses',
130
+ 'marques': 'marqueses',
131
+
132
+ // ─────────────────────────────────────────────────────────────────────────
133
+ // Other irregulars
134
+ // ─────────────────────────────────────────────────────────────────────────
135
+ 'qualquer': 'quaisquer',
136
+ 'carater': 'caracteres',
137
+ 'junior': 'juniores',
138
+ 'senior': 'seniores',
139
+
140
+ // ─────────────────────────────────────────────────────────────────────────
141
+ // Ambiguous normalized forms (context-dependent)
142
+ // "pais" (plural of pai) is invariable; "país" → "países" handled by rule
143
+ // ─────────────────────────────────────────────────────────────────────────
144
+ 'pais': 'paises', // país → países (oxytone in -s)
145
+ });
146
+
147
+ /**
148
+ * Builds reverse irregular mapping (plural → singular).
149
+ * @param {Record<string, string>} irregulars
150
+ * @returns {Record<string, string>}
151
+ */
152
+ const buildSingularIrregulars = (irregulars) => {
153
+ const result = {};
154
+ for (const [singular, plural] of Object.entries(irregulars)) {
155
+ if (plural !== singular) {
156
+ result[plural] = singular;
157
+ }
158
+ }
159
+ return result;
160
+ };
161
+
162
+ /**
163
+ * Default irregular singulars (auto-generated reverse mapping).
164
+ * @type {Readonly<Record<string, string>>}
165
+ */
166
+ export const PT_BR_DEFAULT_SINGULAR_IRREGULARS = Object.freeze(
167
+ buildSingularIrregulars(PT_BR_DEFAULT_IRREGULARS)
168
+ );
169
+
170
+ // ═══════════════════════════════════════════════════════════════════════════
171
+ // CONNECTORS
172
+ // ═══════════════════════════════════════════════════════════════════════════
173
+
174
+ /**
175
+ * Portuguese connector words used in compound expressions.
176
+ * @type {ReadonlySet<string>}
177
+ */
178
+ export const PT_BR_CONNECTORS = Object.freeze(new Set(
179
+ [
180
+ 'de', 'da', 'do', 'das', 'dos',
181
+ 'em', 'na', 'no', 'nas', 'nos',
182
+ 'a', 'ao', 'as', 'aos',
183
+ 'com', 'sem', 'sob', 'sobre',
184
+ 'para', 'por', 'pela', 'pelo', 'pelas', 'pelos',
185
+ 'entre', 'contra', 'perante',
186
+ 'e', 'ou'
187
+ ].map(normalizeLookup)
188
+ ));
189
+
190
+ const hasConnectorWord = (term, connectors) => {
191
+ if (!term || !String(term).trim()) return false;
192
+ const original = String(term).trim();
193
+ const format = detectTextFormat(original);
194
+ const words = splitIntoWords(original, format);
195
+ return words.some(word => connectors?.has?.(normalizeLookup(word)));
196
+ };
197
+
198
+ // ═══════════════════════════════════════════════════════════════════════════
199
+ // INFLECTION RULES
200
+ // ═══════════════════════════════════════════════════════════════════════════
201
+
202
+ /**
203
+ * Pluralization rules for Portuguese words.
204
+ * Format: [suffix, replacement, suffixLength]
205
+ * @type {ReadonlyArray<Readonly<[string, string, number]>>}
206
+ */
207
+ const PLURAL_RULES = Object.freeze([
208
+ // -ão → -ões (default; -ães and -ãos handled via irregulars)
209
+ ['ao', 'oes', 2],
210
+ // -m → -ns
211
+ ['m', 'ns', 1],
212
+ // -l endings
213
+ ['al', 'ais', 2],
214
+ ['el', 'eis', 2],
215
+ ['ol', 'ois', 2],
216
+ ['ul', 'uis', 2],
217
+ ['il', 'is', 2], // Stressed -il; unstressed in irregulars
218
+ // Oxytone words ending in -s add -es (gás→gases, ás→ases, ês→eses, etc.)
219
+ // Note: normalized form (no diacritics), so "gás" becomes "gas"
220
+ ['as', 'ases', 2],
221
+ ['es', 'eses', 2],
222
+ ['is', 'ises', 2],
223
+ ['os', 'oses', 2],
224
+ ['us', 'uses', 2],
225
+ ]);
226
+
227
+ /**
228
+ * Singularization rules for Portuguese words.
229
+ * Format: [suffix, replacement, suffixLength]
230
+ * @type {ReadonlyArray<Readonly<[string, string, number]>>}
231
+ */
232
+ const SINGULAR_RULES = Object.freeze([
233
+ // -ões/-ães/-ãos → -ão
234
+ ['oes', 'ao', 3],
235
+ ['aes', 'ao', 3],
236
+ ['aos', 'ao', 3],
237
+ // -ns → -m
238
+ ['ns', 'm', 2],
239
+ // -l endings reverse
240
+ ['ais', 'al', 3],
241
+ ['eis', 'el', 3],
242
+ ['ois', 'ol', 3],
243
+ ['uis', 'ul', 3],
244
+ ['is', 'il', 2],
245
+ // Oxytone -Vses → -Vs (gases→gas, meses→mes, etc.)
246
+ ['ases', 'as', 4],
247
+ ['eses', 'es', 4],
248
+ ['ises', 'is', 4],
249
+ ['oses', 'os', 4],
250
+ ['uses', 'us', 4],
251
+ ]);
252
+
253
+ // ═══════════════════════════════════════════════════════════════════════════
254
+ // UTILITY FUNCTIONS
255
+ // ═══════════════════════════════════════════════════════════════════════════
256
+
257
+ /**
258
+ * Normalizes a word for rule matching and irregular lookup.
259
+ * @param {string} word - The word to normalize
260
+ * @returns {string} Normalized word (lowercase, no diacritics)
261
+ */
262
+ const normalizeWord = (word) =>
263
+ stripDiacritics((word ?? '').toString()).toLowerCase().trim();
264
+
265
+ /**
266
+ * Applies suffix rules to a word.
267
+ * @param {string} word - Normalized word
268
+ * @param {ReadonlyArray<Readonly<[string, string, number]>>} rules
269
+ * @returns {string|null} Transformed word or null if no rule matched
270
+ */
271
+ const applyRules = (word, rules) => {
272
+ for (const [suffix, replacement, length] of rules) {
273
+ if (word.endsWith(suffix)) {
274
+ return word.slice(0, -length) + replacement;
275
+ }
276
+ }
277
+ return null;
278
+ };
279
+
280
+ // ═══════════════════════════════════════════════════════════════════════════
281
+ // PLURALIZATION
282
+ // ═══════════════════════════════════════════════════════════════════════════
283
+
284
+ /**
285
+ * Converts a Portuguese word to its plural form.
286
+ * Output is normalized (no diacritics, lowercase).
287
+ *
288
+ * @param {string} word - The word to pluralize
289
+ * @param {Record<string, string>} [irregulars=PT_BR_DEFAULT_IRREGULARS]
290
+ * @returns {string} The pluralized word (normalized)
291
+ */
292
+ export const pluralizeWordPtBr = (
293
+ word,
294
+ irregulars = PT_BR_DEFAULT_IRREGULARS
295
+ ) => {
296
+ const normalized = normalizeWord(word);
297
+ if (!normalized) return '';
298
+
299
+ // 1. Check irregulars first
300
+ const irregular = irregulars[normalized];
301
+ if (irregular !== undefined) {
302
+ return irregular;
303
+ }
304
+
305
+ // 2. Apply suffix-based rules
306
+ const ruleResult = applyRules(normalized, PLURAL_RULES);
307
+ if (ruleResult !== null) {
308
+ return ruleResult;
309
+ }
310
+
311
+ // 3. Words ending in -x are typically invariable
312
+ if (PATTERNS.endsInX.test(normalized)) {
313
+ return normalized;
314
+ }
315
+
316
+ // 4. Consonants r, z, n require -es
317
+ if (PATTERNS.consonantEnding.test(normalized)) {
318
+ return normalized + 'es';
319
+ }
320
+
321
+ // 5. Words ending in -s (invariable or already plural)
322
+ if (normalized.endsWith('s')) {
323
+ return normalized;
324
+ }
325
+
326
+ // 6. Default: add -s
327
+ return normalized + 's';
328
+ };
329
+
330
+ // ═══════════════════════════════════════════════════════════════════════════
331
+ // SINGULARIZATION
332
+ // ═══════════════════════════════════════════════════════════════════════════
333
+
334
+ /**
335
+ * Converts a Portuguese word to its singular form.
336
+ * Output is normalized (no diacritics, lowercase).
337
+ *
338
+ * @param {string} word - The word to singularize
339
+ * @param {Record<string, string>} [irregulars=PT_BR_DEFAULT_SINGULAR_IRREGULARS]
340
+ * @returns {string} The singularized word (normalized)
341
+ */
342
+ export const singularizeWordPtBr = (
343
+ word,
344
+ irregulars = PT_BR_DEFAULT_SINGULAR_IRREGULARS
345
+ ) => {
346
+ const normalized = normalizeWord(word);
347
+ if (!normalized) return '';
348
+
349
+ // 1. Check irregulars first
350
+ const irregular = irregulars[normalized];
351
+ if (irregular !== undefined) {
352
+ return irregular;
353
+ }
354
+
355
+ // 2. Apply suffix-based rules
356
+ const ruleResult = applyRules(normalized, SINGULAR_RULES);
357
+ if (ruleResult !== null) {
358
+ return ruleResult;
359
+ }
360
+
361
+ // 3. Handle consonant + es pattern
362
+ if (PATTERNS.consonantEsEnding.test(normalized)) {
363
+ return normalized.slice(0, -2);
364
+ }
365
+
366
+ // 4. Words ending in vowel+s: remove s
367
+ if (PATTERNS.vowelBeforeS.test(normalized)) {
368
+ return normalized.slice(0, -1);
369
+ }
370
+
371
+ // 5. Already singular or invariable
372
+ return normalized;
373
+ };
374
+
375
+ // ═══════════════════════════════════════════════════════════════════════════
376
+ // NOUN SPECIFIERS (SUBSTANTIVOS DETERMINANTES)
377
+ // ═══════════════════════════════════════════════════════════════════════════
378
+
379
+ /**
380
+ * Portuguese words that act as specifiers/delimiters in compound nouns.
381
+ * When these appear as the second term, only the first term varies.
382
+ * @type {ReadonlySet<string>}
383
+ */
384
+ export const PT_BR_NOUN_SPECIFIERS = Object.freeze(new Set(
385
+ [
386
+ 'correcao', 'padrao', 'limite', 'chave', 'base', 'chefe',
387
+ 'satelite', 'fantasma', 'monstro', 'escola', 'piloto',
388
+ 'femea', 'macho', 'geral', 'solicitacao'
389
+ ].map(normalizeLookup)
390
+ ));
391
+
392
+ const isCompoundWithSpecifier = (term, specifiers = PT_BR_NOUN_SPECIFIERS) => {
393
+ if (!term || !String(term).trim()) return false;
394
+ const original = String(term).trim();
395
+ const format = detectTextFormat(original);
396
+ const words = splitIntoWords(original, format);
397
+
398
+ if (words.length < 2) return false;
399
+
400
+ // Check if the last word is a known specifier
401
+ const lastWord = words[words.length - 1];
402
+ return specifiers.has(normalizeLookup(lastWord));
403
+ };
404
+
405
+ // ═══════════════════════════════════════════════════════════════════════════
406
+ // COMPOUND TERM HANDLING
407
+ // ═══════════════════════════════════════════════════════════════════════════
408
+
409
+ /**
410
+ * Pluralizes a compound property/relation name in Portuguese.
411
+ */
412
+ export const pluralizeRelationPropertyPtBr = (
413
+ term,
414
+ { pluralizeWord = pluralizeWordPtBr, connectors = PT_BR_CONNECTORS, specifiers = PT_BR_NOUN_SPECIFIERS } = {}
415
+ ) => {
416
+ if (hasConnectorWord(term, connectors) || isCompoundWithSpecifier(term, specifiers)) {
417
+ return applyToCompoundHead(term, { connectors, transformWord: pluralizeWord });
418
+ }
419
+ return applyToCompoundWords(term, { connectors, transformWord: pluralizeWord });
420
+ }
421
+
422
+ /**
423
+ * Singularizes a compound property/relation name in Portuguese.
424
+ */
425
+ export const singularizeRelationPropertyPtBr = (
426
+ term,
427
+ { singularizeWord = singularizeWordPtBr, connectors = PT_BR_CONNECTORS, specifiers = PT_BR_NOUN_SPECIFIERS } = {}
428
+ ) => {
429
+ if (hasConnectorWord(term, connectors) || isCompoundWithSpecifier(term, specifiers)) {
430
+ return applyToCompoundHead(term, { connectors, transformWord: singularizeWord });
431
+ }
432
+ return applyToCompoundWords(term, { connectors, transformWord: singularizeWord });
433
+ }
434
+
435
+ // ═══════════════════════════════════════════════════════════════════════════
436
+ // INFLECTOR FACTORY
437
+ // ═══════════════════════════════════════════════════════════════════════════
438
+
439
+ /**
440
+ * Creates a Brazilian Portuguese inflector instance.
441
+ */
442
+ export const createPtBrInflector = ({ customIrregulars = {} } = {}) => {
443
+ const irregularPlurals = Object.freeze({
444
+ ...PT_BR_DEFAULT_IRREGULARS,
445
+ ...customIrregulars
446
+ });
447
+
448
+ const irregularSingulars = Object.freeze({
449
+ ...PT_BR_DEFAULT_SINGULAR_IRREGULARS,
450
+ ...buildSingularIrregulars(customIrregulars)
451
+ });
452
+
453
+ const pluralizeWord = (w) => pluralizeWordPtBr(w, irregularPlurals);
454
+ const singularizeWord = (w) => singularizeWordPtBr(w, irregularSingulars);
455
+
456
+ return Object.freeze({
457
+ locale: 'pt-BR',
458
+ irregularPlurals,
459
+ irregularSingulars,
460
+ pluralizeWord,
461
+ singularizeWord,
462
+ pluralizeRelationProperty: (term) => pluralizeRelationPropertyPtBr(term, { pluralizeWord }),
463
+ singularizeRelationProperty: (term) => singularizeRelationPropertyPtBr(term, { singularizeWord }),
464
+ normalizeForLookup: normalizeWord
465
+ });
466
+ };
467
+
468
+ export default createPtBrInflector;