@coinbase-sample/prime-sdk-ts 0.6.2 → 0.6.4

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 (77) hide show
  1. package/README.md +207 -67
  2. package/dist/addressBooks/index.js +2 -2
  3. package/dist/allocations/index.js +3 -3
  4. package/dist/client-manual.js +84 -0
  5. package/dist/client-modular.js +57 -0
  6. package/dist/client-only.js +31 -0
  7. package/dist/{client.js → clients/client.js} +12 -2
  8. package/dist/clients/clientWithServices.js +323 -0
  9. package/dist/{types/client.d.ts → clients/clientWithServicesTypes.js} +3 -6
  10. package/dist/clients/index.js +28 -0
  11. package/dist/clients/types.js +2 -0
  12. package/dist/constants.js +1 -1
  13. package/dist/futures/index.js +4 -4
  14. package/dist/index.js +27 -18
  15. package/dist/model/enumPrefixes.js +330 -0
  16. package/dist/onchainAddressBook/index.js +4 -4
  17. package/dist/orders/index.js +6 -6
  18. package/dist/paymentMethods/index.js +2 -1
  19. package/dist/services.js +75 -0
  20. package/dist/shared/dynamicEnumValidation.js +184 -0
  21. package/dist/shared/dynamicEnumValidation.old.js +746 -0
  22. package/dist/shared/enumHelpers.js +219 -0
  23. package/dist/shared/enumRegistry.js +153 -0
  24. package/dist/shared/enumValidationCore.js +194 -0
  25. package/dist/shared/enumValidators.js +115 -0
  26. package/dist/shared/envUtils.js +66 -0
  27. package/dist/shared/fieldMapping.js +242 -0
  28. package/dist/shared/serviceContext.js +157 -0
  29. package/dist/staking/index.js +5 -5
  30. package/dist/transactions/index.js +5 -5
  31. package/dist/types/activities/index.d.ts +2 -3
  32. package/dist/types/addressBooks/index.d.ts +2 -3
  33. package/dist/types/allocations/index.d.ts +2 -3
  34. package/dist/types/assets/index.d.ts +2 -3
  35. package/dist/types/balances/index.d.ts +2 -3
  36. package/dist/types/balances/types.d.ts +1 -1
  37. package/dist/types/client-manual.d.ts +58 -0
  38. package/dist/types/client-modular.d.ts +39 -0
  39. package/dist/types/client-only.d.ts +18 -0
  40. package/dist/types/clients/client.d.ts +27 -0
  41. package/dist/types/clients/clientWithServices.d.ts +229 -0
  42. package/dist/types/clients/clientWithServicesTypes.d.ts +115 -0
  43. package/dist/types/clients/index.d.ts +19 -0
  44. package/dist/types/clients/types.d.ts +48 -0
  45. package/dist/types/commission/index.d.ts +2 -3
  46. package/dist/types/constants.d.ts +1 -1
  47. package/dist/types/financing/index.d.ts +2 -3
  48. package/dist/types/futures/index.d.ts +2 -3
  49. package/dist/types/index.d.ts +2 -1
  50. package/dist/types/invoices/index.d.ts +2 -3
  51. package/dist/types/model/enumPrefixes.d.ts +206 -0
  52. package/dist/types/onchainAddressBook/index.d.ts +2 -3
  53. package/dist/types/orders/index.d.ts +2 -3
  54. package/dist/types/paymentMethods/index.d.ts +2 -3
  55. package/dist/types/paymentMethods/types.d.ts +1 -0
  56. package/dist/types/portfolios/index.d.ts +2 -3
  57. package/dist/types/positions/index.d.ts +2 -3
  58. package/dist/types/products/index.d.ts +2 -3
  59. package/dist/types/services.d.ts +39 -0
  60. package/dist/types/shared/dynamicEnumValidation.d.ts +48 -0
  61. package/dist/types/shared/dynamicEnumValidation.old.d.ts +143 -0
  62. package/dist/types/shared/enumHelpers.d.ts +135 -0
  63. package/dist/types/shared/enumRegistry.d.ts +74 -0
  64. package/dist/types/shared/enumValidationCore.d.ts +68 -0
  65. package/dist/types/shared/enumValidators.d.ts +117 -0
  66. package/dist/types/shared/envUtils.d.ts +36 -0
  67. package/dist/types/shared/fieldMapping.d.ts +35 -0
  68. package/dist/types/shared/paginatedResponse.d.ts +3 -4
  69. package/dist/types/shared/serviceContext.d.ts +46 -0
  70. package/dist/types/staking/index.d.ts +2 -3
  71. package/dist/types/transactions/index.d.ts +2 -3
  72. package/dist/types/types.d.ts +37 -0
  73. package/dist/types/users/index.d.ts +2 -3
  74. package/dist/types/wallets/index.d.ts +2 -3
  75. package/dist/types.js +39 -0
  76. package/dist/wallets/index.js +3 -3
  77. package/package.json +33 -2
@@ -0,0 +1,746 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright 2025-present Coinbase Global, Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ var desc = Object.getOwnPropertyDescriptor(m, k);
20
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
21
+ desc = { enumerable: true, get: function() { return m[k]; } };
22
+ }
23
+ Object.defineProperty(o, k2, desc);
24
+ }) : (function(o, m, k, k2) {
25
+ if (k2 === undefined) k2 = k;
26
+ o[k2] = m[k];
27
+ }));
28
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
29
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
30
+ }) : function(o, v) {
31
+ o["default"] = v;
32
+ });
33
+ var __importStar = (this && this.__importStar) || (function () {
34
+ var ownKeys = function(o) {
35
+ ownKeys = Object.getOwnPropertyNames || function (o) {
36
+ var ar = [];
37
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
38
+ return ar;
39
+ };
40
+ return ownKeys(o);
41
+ };
42
+ return function (mod) {
43
+ if (mod && mod.__esModule) return mod;
44
+ var result = {};
45
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
46
+ __setModuleDefault(result, mod);
47
+ return result;
48
+ };
49
+ })();
50
+ Object.defineProperty(exports, "__esModule", { value: true });
51
+ exports.enumRegistry = exports.DynamicEnumValidationError = void 0;
52
+ exports.validateRequestEnums = validateRequestEnums;
53
+ exports.validateAndNormalizeRequest = validateAndNormalizeRequest;
54
+ exports.setDynamicValidationConfig = setDynamicValidationConfig;
55
+ exports.getDynamicValidationConfig = getDynamicValidationConfig;
56
+ exports.dynamicValidateRequest = dynamicValidateRequest;
57
+ exports.getEnumInfo = getEnumInfo;
58
+ /**
59
+ * Dynamic enum validation system that automatically discovers and validates
60
+ * enum fields in request objects without manual service-specific configuration.
61
+ *
62
+ * This approach is more scalable and maintainable than manual validation rules.
63
+ */
64
+ const enumHelpers_1 = require("./enumHelpers");
65
+ const enumPrefixes_1 = require("../model/enumPrefixes");
66
+ // Import all enums dynamically
67
+ const AllEnums = __importStar(require("../model/enums/"));
68
+ /**
69
+ * Error thrown when a request contains invalid enum values
70
+ */
71
+ class DynamicEnumValidationError extends Error {
72
+ constructor(field, value, enumName, validValues, validKeys) {
73
+ const validValuesStr = validValues.join(', ');
74
+ const validKeysStr = validKeys
75
+ ? ` Valid keys: ${validKeys.join(', ')}.`
76
+ : '';
77
+ super(`Invalid ${field} (${enumName}): "${value}". Valid values: ${validValuesStr}.${validKeysStr} ` +
78
+ `Consider using enum helpers for automatic normalization.`);
79
+ this.field = field;
80
+ this.value = value;
81
+ this.enumName = enumName;
82
+ this.validValues = validValues;
83
+ this.validKeys = validKeys;
84
+ this.name = 'DynamicEnumValidationError';
85
+ }
86
+ }
87
+ exports.DynamicEnumValidationError = DynamicEnumValidationError;
88
+ /**
89
+ * Registry of all available enums for dynamic lookup
90
+ */
91
+ class EnumRegistry {
92
+ constructor() {
93
+ this.enumMap = new Map();
94
+ this.enumNameMap = new Map();
95
+ this.registerAllEnums();
96
+ }
97
+ /**
98
+ * Automatically register all enums from the model/enums directory
99
+ */
100
+ registerAllEnums() {
101
+ Object.entries(AllEnums).forEach(([enumName, enumObject]) => {
102
+ if (this.isValidEnum(enumObject)) {
103
+ this.enumMap.set(enumName, enumObject);
104
+ this.enumNameMap.set(enumObject, enumName);
105
+ }
106
+ });
107
+ }
108
+ /**
109
+ * Check if an object is a valid enum
110
+ */
111
+ isValidEnum(obj) {
112
+ return (obj &&
113
+ typeof obj === 'object' &&
114
+ Object.values(obj).every((val) => typeof val === 'string') &&
115
+ Object.keys(obj).length > 0);
116
+ }
117
+ /**
118
+ * Get enum by name
119
+ */
120
+ getEnum(enumName) {
121
+ return this.enumMap.get(enumName);
122
+ }
123
+ /**
124
+ * Get enum name for a given enum object
125
+ */
126
+ getEnumName(enumObject) {
127
+ return this.enumNameMap.get(enumObject);
128
+ }
129
+ /**
130
+ * Find enum that contains a specific value
131
+ */
132
+ findEnumByValue(value) {
133
+ const normalizedValue = (0, enumHelpers_1.normalizeEnumValue)(value);
134
+ for (const [enumName, enumObject] of this.enumMap) {
135
+ if (Object.values(enumObject).includes(normalizedValue)) {
136
+ return { enum: enumObject, name: enumName };
137
+ }
138
+ }
139
+ return undefined;
140
+ }
141
+ /**
142
+ * Find enum that contains a specific key
143
+ */
144
+ findEnumByKey(key) {
145
+ const normalizedKey = (0, enumHelpers_1.normalizeEnumValue)(key);
146
+ for (const [enumName, enumObject] of this.enumMap) {
147
+ for (const enumKey of Object.keys(enumObject)) {
148
+ if ((0, enumHelpers_1.normalizeEnumValue)(enumKey) === normalizedKey) {
149
+ return { enum: enumObject, name: enumName };
150
+ }
151
+ }
152
+ }
153
+ return undefined;
154
+ }
155
+ /**
156
+ * Get all registered enum names
157
+ */
158
+ getAllEnumNames() {
159
+ return Array.from(this.enumMap.keys());
160
+ }
161
+ /**
162
+ * Validate a value against a specific enum with prefix handling
163
+ */
164
+ validateEnumValue(enumName, value) {
165
+ const enumObject = this.getEnum(enumName);
166
+ if (!enumObject) {
167
+ return { valid: false, error: `Unknown enum: ${enumName}` };
168
+ }
169
+ // Use the enhanced validation with prefix handling
170
+ return validateEnumValueWithPrefix(enumObject, value);
171
+ }
172
+ }
173
+ // Global enum registry instance
174
+ const enumRegistry = new EnumRegistry();
175
+ exports.enumRegistry = enumRegistry;
176
+ /**
177
+ * Cache for storing analyzed request types to avoid repeated reflection
178
+ */
179
+ const requestTypeCache = new Map();
180
+ /**
181
+ * Analyzes a request object to identify enum fields dynamically
182
+ * This uses runtime type inspection and pattern matching
183
+ */
184
+ function analyzeRequestObject(request) {
185
+ // Create a simple cache key based on the field names and types
186
+ const cacheKey = Object.keys(request || {})
187
+ .sort()
188
+ .join(',');
189
+ if (requestTypeCache.has(cacheKey)) {
190
+ return requestTypeCache.get(cacheKey);
191
+ }
192
+ const enumFields = [];
193
+ if (!request || typeof request !== 'object') {
194
+ return enumFields;
195
+ }
196
+ Object.entries(request).forEach(([fieldName, value]) => {
197
+ const fieldInfo = analyzeField(fieldName, value);
198
+ if (fieldInfo) {
199
+ enumFields.push(fieldInfo);
200
+ }
201
+ });
202
+ // Cache the result
203
+ requestTypeCache.set(cacheKey, enumFields);
204
+ return enumFields;
205
+ }
206
+ /**
207
+ * Analyzes a single field to determine if it's an enum
208
+ */
209
+ function analyzeField(fieldName, value) {
210
+ if (value === undefined || value === null) {
211
+ return null;
212
+ }
213
+ // Handle array values
214
+ if (Array.isArray(value)) {
215
+ const firstNonNullValue = value.find((v) => v !== null && v !== undefined);
216
+ if (firstNonNullValue && typeof firstNonNullValue === 'string') {
217
+ const enumInfo = detectEnumType(fieldName, firstNonNullValue);
218
+ if (enumInfo) {
219
+ return {
220
+ fieldName,
221
+ enumName: enumInfo.name,
222
+ isOptional: false,
223
+ isArray: true,
224
+ };
225
+ }
226
+ }
227
+ return null;
228
+ }
229
+ // Handle string values (potential enums)
230
+ if (typeof value === 'string') {
231
+ const enumInfo = detectEnumType(fieldName, value);
232
+ if (enumInfo) {
233
+ return {
234
+ fieldName,
235
+ enumName: enumInfo.name,
236
+ isOptional: false,
237
+ isArray: false,
238
+ };
239
+ }
240
+ }
241
+ return null;
242
+ }
243
+ /**
244
+ * Detects the enum type for a field based on field name patterns and value analysis
245
+ */
246
+ function detectEnumType(fieldName, value) {
247
+ // Strategy 1: Direct field name to enum name mapping
248
+ const directMapping = getDirectEnumMapping(fieldName);
249
+ if (directMapping) {
250
+ const enumObject = enumRegistry.getEnum(directMapping);
251
+ if (enumObject) {
252
+ // If we have a confident field mapping, use it even if value doesn't match
253
+ // This allows validation to catch invalid values
254
+ return { enum: enumObject, name: directMapping };
255
+ }
256
+ }
257
+ // Strategy 2: Find enum by value (exact match)
258
+ const enumByValue = enumRegistry.findEnumByValue(value);
259
+ if (enumByValue) {
260
+ return enumByValue;
261
+ }
262
+ // Strategy 3: Find enum by value with prefix normalization
263
+ const enumByValueWithPrefix = findEnumWithPrefixHandling(value);
264
+ if (enumByValueWithPrefix) {
265
+ return enumByValueWithPrefix;
266
+ }
267
+ // Strategy 4: Find enum by key (treating value as a potential key)
268
+ const enumByKey = enumRegistry.findEnumByKey(value);
269
+ if (enumByKey) {
270
+ return enumByKey;
271
+ }
272
+ return null;
273
+ }
274
+ /**
275
+ * Common enum prefixes that can be automatically added or removed
276
+ * Now sourced from auto-generated analysis of OpenAPI enums
277
+ */
278
+ // Sort prefixes by length (longest first) to prevent collision issues
279
+ // This ensures that "OTHER_ACTIVITY_" is checked before "OTHER_"
280
+ const ENUM_PREFIXES = [...enumPrefixes_1.GENERATED_ENUM_PREFIXES].sort((a, b) => b.length - a.length);
281
+ /**
282
+ * Finds an enum that matches the value with intelligent prefix handling
283
+ */
284
+ function findEnumWithPrefixHandling(value) {
285
+ const normalizedValue = (0, enumHelpers_1.normalizeEnumValue)(value);
286
+ // Try to find enums where adding common prefixes makes a match
287
+ for (const enumName of enumRegistry.getAllEnumNames()) {
288
+ const enumObject = enumRegistry.getEnum(enumName);
289
+ if (!enumObject)
290
+ continue;
291
+ const enumValues = Object.values(enumObject);
292
+ // Check if any enum value matches when we add a prefix to the input
293
+ for (const prefix of ENUM_PREFIXES) {
294
+ const prefixedValue = `${prefix}${normalizedValue}`;
295
+ if (enumValues.includes(prefixedValue)) {
296
+ return { enum: enumObject, name: enumName };
297
+ }
298
+ }
299
+ // Check if any enum value matches when we remove a prefix from enum values
300
+ for (const enumValue of enumValues) {
301
+ for (const prefix of ENUM_PREFIXES) {
302
+ if (enumValue.startsWith(prefix)) {
303
+ const unprefixedEnumValue = enumValue.substring(prefix.length);
304
+ if (unprefixedEnumValue === normalizedValue) {
305
+ return { enum: enumObject, name: enumName };
306
+ }
307
+ }
308
+ }
309
+ }
310
+ }
311
+ return null;
312
+ }
313
+ /**
314
+ * Enhanced enum validation that handles prefix normalization
315
+ */
316
+ function validateEnumValueWithPrefix(enumObject, value) {
317
+ const normalizedInput = (0, enumHelpers_1.normalizeEnumValue)(value);
318
+ // Direct value match (no prefix handling needed)
319
+ if (Object.values(enumObject).includes(normalizedInput)) {
320
+ return { valid: true, normalizedValue: normalizedInput };
321
+ }
322
+ // Key-based lookup (no prefix handling needed)
323
+ for (const [key, enumValue] of Object.entries(enumObject)) {
324
+ if ((0, enumHelpers_1.normalizeEnumValue)(key) === normalizedInput) {
325
+ return { valid: true, normalizedValue: enumValue };
326
+ }
327
+ }
328
+ // Prefix-aware validation
329
+ const enumValues = Object.values(enumObject);
330
+ // Try adding prefixes to the input value
331
+ for (const prefix of ENUM_PREFIXES) {
332
+ const prefixedValue = `${prefix}${normalizedInput}`;
333
+ if (enumValues.includes(prefixedValue)) {
334
+ return { valid: true, normalizedValue: prefixedValue };
335
+ }
336
+ }
337
+ // Try removing prefixes from enum values to match input
338
+ for (const enumValue of enumValues) {
339
+ for (const prefix of ENUM_PREFIXES) {
340
+ if (enumValue.startsWith(prefix)) {
341
+ const unprefixedEnumValue = enumValue.substring(prefix.length);
342
+ if (unprefixedEnumValue === normalizedInput) {
343
+ return { valid: true, normalizedValue: enumValue };
344
+ }
345
+ }
346
+ }
347
+ }
348
+ return {
349
+ valid: false,
350
+ error: `No match found for "${value}" (tried with/without prefixes)`,
351
+ };
352
+ }
353
+ /**
354
+ * Maps common field names to their corresponding enum types
355
+ */
356
+ function getDirectEnumMapping(fieldName) {
357
+ const fieldNameLower = fieldName.toLowerCase();
358
+ // First try dynamic enum name matching
359
+ const dynamicMatch = findEnumByFieldName(fieldNameLower);
360
+ if (dynamicMatch) {
361
+ return dynamicMatch;
362
+ }
363
+ // Fall back to essential manual mappings for common patterns that don't follow naming conventions
364
+ // These should be conservative and only include non-ambiguous cases
365
+ const essentialMappings = {
366
+ // Common trading/order fields (most frequent use case)
367
+ side: 'OrderSide',
368
+ status: 'OrderStatus', // Default to OrderStatus, validation will catch mismatches
369
+ role: 'UserRole',
370
+ action: 'Action',
371
+ direction: 'SortDirection',
372
+ // Specific compound fields to prevent wrong mappings
373
+ orderside: 'OrderSide',
374
+ ordertype: 'OrderType',
375
+ orderstatus: 'OrderStatus',
376
+ wallettype: 'WalletType',
377
+ transactiontype: 'TransactionType',
378
+ activitytype: 'CustodyActivityType',
379
+ activitystatus: 'ActivityStatus',
380
+ // Note: 'type' still removed (too ambiguous - let value-based detection handle it)
381
+ };
382
+ return essentialMappings[fieldNameLower] || null;
383
+ }
384
+ /**
385
+ * Generate intelligent field name variations for plural/singular matching
386
+ */
387
+ function getFieldNameVariations(fieldName) {
388
+ const variations = [fieldName];
389
+ // Handle irregular plurals and common patterns
390
+ const irregularPlurals = {
391
+ categories: 'category',
392
+ statuses: 'status',
393
+ activities: 'activity',
394
+ entities: 'entity',
395
+ currencies: 'currency',
396
+ portfolios: 'portfolio',
397
+ };
398
+ const irregularSingulars = {
399
+ category: 'categories',
400
+ status: 'statuses',
401
+ activity: 'activities',
402
+ entity: 'entities',
403
+ currency: 'currencies',
404
+ portfolio: 'portfolios',
405
+ };
406
+ // Check irregular mappings first
407
+ if (irregularPlurals[fieldName]) {
408
+ variations.push(irregularPlurals[fieldName]);
409
+ }
410
+ if (irregularSingulars[fieldName]) {
411
+ variations.push(irregularSingulars[fieldName]);
412
+ }
413
+ // Handle regular -s plurals only if not already handled by irregular rules
414
+ if (!irregularPlurals[fieldName] && !irregularSingulars[fieldName]) {
415
+ if (fieldName.endsWith('s') && fieldName.length > 3) {
416
+ // Only remove 's' if it's likely a regular plural (not words ending in 's')
417
+ const withoutS = fieldName.slice(0, -1);
418
+ // Avoid false positives like "address" → "addres"
419
+ if (!fieldName.endsWith('ss') && !fieldName.endsWith('us')) {
420
+ variations.push(withoutS);
421
+ }
422
+ }
423
+ else {
424
+ // Add regular plural
425
+ variations.push(fieldName + 's');
426
+ }
427
+ }
428
+ return variations;
429
+ }
430
+ /**
431
+ * Check if a value is valid for a specific enum (includes normalization)
432
+ */
433
+ function isValueValidForEnum(value, enumObject) {
434
+ if (!value || !enumObject)
435
+ return false;
436
+ // Direct value match
437
+ if (Object.values(enumObject).includes(value))
438
+ return true;
439
+ // Direct key match
440
+ if (Object.keys(enumObject).includes(value))
441
+ return true;
442
+ // Normalized value matches
443
+ const normalizedValue = (0, enumHelpers_1.normalizeEnumValue)(value);
444
+ if (Object.values(enumObject).includes(normalizedValue))
445
+ return true;
446
+ // Check with prefix normalization
447
+ for (const prefix of ENUM_PREFIXES) {
448
+ const withPrefix = `${prefix}${normalizedValue}`;
449
+ if (Object.values(enumObject).includes(withPrefix))
450
+ return true;
451
+ }
452
+ return false;
453
+ }
454
+ /**
455
+ * Find enum with context awareness to resolve collisions
456
+ * Handles cases where multiple enums could match (e.g., ActivityStatus vs OrderStatus vs TransactionStatus)
457
+ */
458
+ function findEnumWithContext(fieldName, allEnumNames) {
459
+ // Handle ambiguous suffixes that appear in multiple enums
460
+ const ambiguousSuffixes = ['status', 'type', 'category'];
461
+ const fieldLower = fieldName.toLowerCase();
462
+ // Check if this is an ambiguous field name
463
+ const isAmbiguous = ambiguousSuffixes.some((suffix) => fieldLower.endsWith(suffix) || fieldLower === suffix);
464
+ if (isAmbiguous) {
465
+ // For ambiguous cases, look for context clues in the field name
466
+ const contextClues = extractContextClues(fieldName);
467
+ // Find enum that best matches the context
468
+ const matches = allEnumNames.filter((enumName) => {
469
+ const enumLower = enumName.toLowerCase();
470
+ // Exact compound match (e.g., "activitystatus" → "ActivityStatus")
471
+ if (enumLower === fieldLower)
472
+ return true;
473
+ // Context-based matching
474
+ for (const clue of contextClues) {
475
+ if (enumLower.includes(clue) &&
476
+ enumLower.endsWith(fieldLower.split(/(?=[a-z])/).pop() || '')) {
477
+ return true;
478
+ }
479
+ }
480
+ return false;
481
+ });
482
+ // Return the most specific match
483
+ if (matches.length === 1) {
484
+ return matches[0];
485
+ }
486
+ // If multiple matches, prefer the longest/most specific one
487
+ if (matches.length > 1) {
488
+ return matches.reduce((longest, current) => current.length > longest.length ? current : longest);
489
+ }
490
+ }
491
+ // Non-ambiguous cases: use careful substring matching
492
+ // Sort enums by name length (shortest first) to prefer simpler matches
493
+ const sortedEnumNames = allEnumNames
494
+ .slice()
495
+ .sort((a, b) => a.length - b.length);
496
+ for (const enumName of sortedEnumNames) {
497
+ const enumNameLower = enumName.toLowerCase();
498
+ // Only match if field name is a meaningful part of the enum name
499
+ // Avoid greedy matches like "side" matching "FcmPositionSide"
500
+ if (enumNameLower === fieldLower) {
501
+ return enumName; // Exact match
502
+ }
503
+ // Check if field name appears as a word boundary in enum name
504
+ // e.g., "side" should match "OrderSide" but not "FcmPositionSide"
505
+ const wordBoundaryRegex = new RegExp(`\\b${fieldLower}\\b`, 'i');
506
+ if (wordBoundaryRegex.test(enumName)) {
507
+ return enumName;
508
+ }
509
+ // Check for field name at the end of enum name (most common pattern)
510
+ if (enumNameLower.endsWith(fieldLower) && enumNameLower !== fieldLower) {
511
+ // Ensure it's not a substring within a word
512
+ const beforeField = enumNameLower.slice(0, -fieldLower.length);
513
+ if (beforeField.length > 0 && /[a-z]$/.test(beforeField)) {
514
+ continue; // Skip if it's part of a word
515
+ }
516
+ return enumName;
517
+ }
518
+ }
519
+ return null;
520
+ }
521
+ /**
522
+ * Extract context clues from field names to resolve enum ambiguity
523
+ */
524
+ function extractContextClues(fieldName) {
525
+ const clues = [];
526
+ const fieldLower = fieldName.toLowerCase();
527
+ // Extract prefix context (e.g., "activitystatus" → ["activity"])
528
+ const prefixMatch = fieldLower.match(/^([a-z]+?)(?:status|type|category)$/);
529
+ if (prefixMatch) {
530
+ clues.push(prefixMatch[1]);
531
+ }
532
+ // Extract camelCase components (e.g., "activityStatus" → ["activity"])
533
+ const camelComponents = fieldName
534
+ .replace(/([A-Z])/g, '_$1')
535
+ .toLowerCase()
536
+ .split('_')
537
+ .filter(Boolean);
538
+ clues.push(...camelComponents.slice(0, -1)); // Exclude the last component (status/type/category)
539
+ // Extract underscore/dash separated components
540
+ const separatedComponents = fieldName.toLowerCase().split(/[_-]+/);
541
+ clues.push(...separatedComponents.slice(0, -1));
542
+ return [...new Set(clues)]; // Remove duplicates
543
+ }
544
+ /**
545
+ * Dynamically find enum name based on field name patterns
546
+ * Uses the enum registry to match field names to actual enum names
547
+ */
548
+ function findEnumByFieldName(fieldName) {
549
+ const allEnumNames = enumRegistry.getAllEnumNames();
550
+ // Strategy 1: Direct name match (e.g., "ActivityCategory" field → "ActivityCategory" enum)
551
+ const exactMatch = allEnumNames.find((enumName) => enumName.toLowerCase() === fieldName.toLowerCase());
552
+ if (exactMatch)
553
+ return exactMatch;
554
+ // Strategy 2: Smart plural/singular variations
555
+ const variations = getFieldNameVariations(fieldName);
556
+ for (const enumName of allEnumNames) {
557
+ const enumNameLower = enumName.toLowerCase();
558
+ if (variations.includes(enumNameLower)) {
559
+ return enumName;
560
+ }
561
+ }
562
+ // Strategy 3: Context-aware enum matching (handles collisions)
563
+ const contextMatch = findEnumWithContext(fieldName, allEnumNames);
564
+ if (contextMatch) {
565
+ return contextMatch;
566
+ }
567
+ // Strategy 4: Smart pattern matching for compound field names
568
+ // Handle cases like "activitycategory" → "ActivityCategory"
569
+ for (const enumName of allEnumNames) {
570
+ const enumWords = enumName
571
+ .toLowerCase()
572
+ .replace(/([A-Z])/g, ' $1')
573
+ .trim()
574
+ .split(' ');
575
+ const fieldWords = fieldName
576
+ .replace(/([A-Z])/g, ' $1')
577
+ .trim()
578
+ .split(/[\s_-]+/);
579
+ // Check if all field words are contained in enum words
580
+ const allWordsMatch = fieldWords.every((word) => enumWords.some((enumWord) => enumWord.includes(word) || word.includes(enumWord)));
581
+ if (allWordsMatch && fieldWords.length > 1) {
582
+ return enumName;
583
+ }
584
+ }
585
+ return null;
586
+ }
587
+ /**
588
+ * Validates and normalizes enum fields in a request object
589
+ */
590
+ function validateRequestEnums(request, options = {}) {
591
+ const { strict = true, autoNormalize = true } = options;
592
+ if (!request || typeof request !== 'object') {
593
+ return { validatedRequest: request, errors: [] };
594
+ }
595
+ const validatedRequest = Object.assign({}, request);
596
+ const errors = [];
597
+ // Analyze the request to find enum fields
598
+ const enumFields = analyzeRequestObject(request);
599
+ // Validate each enum field
600
+ enumFields.forEach((fieldInfo) => {
601
+ const { fieldName, enumName, isArray } = fieldInfo;
602
+ const fieldValue = request[fieldName];
603
+ if (fieldValue === undefined || fieldValue === null) {
604
+ return; // Skip undefined/null values
605
+ }
606
+ if (!enumName) {
607
+ return; // Skip if we couldn't determine the enum type
608
+ }
609
+ try {
610
+ if (isArray && Array.isArray(fieldValue)) {
611
+ // Handle array of enum values
612
+ const validatedArray = [];
613
+ fieldValue.forEach((item, index) => {
614
+ if (typeof item === 'string') {
615
+ const validation = enumRegistry.validateEnumValue(enumName, item);
616
+ if (validation.valid && validation.normalizedValue) {
617
+ validatedArray.push(autoNormalize ? validation.normalizedValue : item);
618
+ }
619
+ else if (strict) {
620
+ errors.push(`${fieldName}[${index}]: ${validation.error}`);
621
+ }
622
+ }
623
+ });
624
+ if (autoNormalize) {
625
+ validatedRequest[fieldName] = validatedArray;
626
+ }
627
+ }
628
+ else if (typeof fieldValue === 'string') {
629
+ // Handle single enum value
630
+ const validation = enumRegistry.validateEnumValue(enumName, fieldValue);
631
+ if (validation.valid && validation.normalizedValue) {
632
+ if (autoNormalize) {
633
+ validatedRequest[fieldName] = validation.normalizedValue;
634
+ }
635
+ }
636
+ else if (strict) {
637
+ const enumObject = enumRegistry.getEnum(enumName);
638
+ if (enumObject) {
639
+ throw new DynamicEnumValidationError(fieldName, fieldValue, enumName, Object.values(enumObject), Object.keys(enumObject));
640
+ }
641
+ else {
642
+ errors.push(`${fieldName}: ${validation.error}`);
643
+ }
644
+ }
645
+ }
646
+ }
647
+ catch (error) {
648
+ if (error instanceof DynamicEnumValidationError) {
649
+ throw error; // Re-throw enum validation errors
650
+ }
651
+ errors.push(`${fieldName}: Unexpected validation error: ${error}`);
652
+ }
653
+ });
654
+ return { validatedRequest, errors };
655
+ }
656
+ /**
657
+ * Simplified validation function that throws on first error
658
+ */
659
+ function validateAndNormalizeRequest(request) {
660
+ const result = validateRequestEnums(request, {
661
+ strict: true,
662
+ autoNormalize: true,
663
+ });
664
+ if (result.errors.length > 0) {
665
+ throw new DynamicEnumValidationError('request', 'multiple fields', 'various', [], result.errors);
666
+ }
667
+ return result.validatedRequest;
668
+ }
669
+ const defaultConfig = {
670
+ enabled: true,
671
+ strict: true,
672
+ autoNormalize: true,
673
+ logWarnings: true,
674
+ };
675
+ let globalConfig = Object.assign({}, defaultConfig);
676
+ /**
677
+ * Set global dynamic validation configuration
678
+ */
679
+ function setDynamicValidationConfig(config) {
680
+ globalConfig = Object.assign(Object.assign({}, globalConfig), config);
681
+ }
682
+ /**
683
+ * Get current dynamic validation configuration
684
+ */
685
+ function getDynamicValidationConfig() {
686
+ return Object.assign({}, globalConfig);
687
+ }
688
+ /**
689
+ * Type-safe dynamic enum validation function
690
+ *
691
+ * Automatically validates and normalizes enum values in request objects
692
+ * using the auto-generated prefix patterns from the OpenAPI specification.
693
+ *
694
+ * @param request - The request object to validate and normalize
695
+ * @param config - Optional validation configuration
696
+ * @returns The validated request with normalized enum values
697
+ *
698
+ * @example
699
+ * ```typescript
700
+ * const validated = dynamicValidateRequest<CreateOrderRequest>({
701
+ * portfolioId: 'portfolio-123',
702
+ * side: 'buy', // → 'BUY'
703
+ * type: 'market' // → 'MARKET'
704
+ * });
705
+ * ```
706
+ */
707
+ function dynamicValidateRequest(request, config) {
708
+ const effectiveConfig = Object.assign(Object.assign({}, globalConfig), config);
709
+ if (!effectiveConfig.enabled) {
710
+ return request;
711
+ }
712
+ try {
713
+ return validateAndNormalizeRequest(request);
714
+ }
715
+ catch (error) {
716
+ if (!effectiveConfig.strict &&
717
+ error instanceof DynamicEnumValidationError) {
718
+ if (effectiveConfig.logWarnings) {
719
+ console.warn(`[Prime SDK] Dynamic enum validation warning: ${error.message}`);
720
+ }
721
+ return request; // Return original request with warning
722
+ }
723
+ throw error;
724
+ }
725
+ }
726
+ /**
727
+ * Utility function to get information about available enums
728
+ */
729
+ function getEnumInfo() {
730
+ const enumNames = enumRegistry.getAllEnumNames();
731
+ const enumDetails = {};
732
+ enumNames.forEach((name) => {
733
+ const enumObject = enumRegistry.getEnum(name);
734
+ if (enumObject) {
735
+ enumDetails[name] = {
736
+ values: Object.values(enumObject),
737
+ keys: Object.keys(enumObject),
738
+ };
739
+ }
740
+ });
741
+ return {
742
+ enumNames,
743
+ totalEnums: enumNames.length,
744
+ enumDetails,
745
+ };
746
+ }