openfigi-sdk 1.0.3
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/LICENSE +19 -0
- package/README.md +239 -0
- package/dist/index.cjs +434 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +352 -0
- package/dist/index.d.ts +352 -0
- package/dist/index.js +405 -0
- package/dist/index.js.map +1 -0
- package/package.json +79 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# openfigi-sdk
|
|
2
|
+
|
|
3
|
+
A modern, type-safe TypeScript SDK for the [OpenFIGI API](https://www.openfigi.com/api) - the free and open standard for financial instrument identification.
|
|
4
|
+
|
|
5
|
+
[](https://github.com/viktorlarsson/openfigi/actions/workflows/ci.yml)
|
|
6
|
+
[](https://www.npmjs.com/package/openfigi-sdk)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
|
|
9
|
+
> **⚠️ Community Project Disclaimer**
|
|
10
|
+
> This is an **unofficial, community-maintained** TypeScript SDK for OpenFIGI. It is **not affiliated with or endorsed by OpenFIGI or Bloomberg**. This library is developed and maintained by the open-source community. For official support, please refer to the [OpenFIGI official documentation](https://www.openfigi.com/api).
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- **Type-safe** - Full TypeScript support with comprehensive type definitions
|
|
15
|
+
- **Modern** - Built with latest technologies (Bun, Vitest, Biome, tsup)
|
|
16
|
+
- **Validated** - Runtime validation using Zod schemas
|
|
17
|
+
- **Resilient** - Built-in retry logic with exponential backoff
|
|
18
|
+
- **Rate Limiting** - Automatic rate limit handling
|
|
19
|
+
- **Developer Friendly** - Simple API with convenience methods
|
|
20
|
+
- **Well Tested** - Comprehensive test coverage
|
|
21
|
+
- **Tree-shakeable** - Optimized bundle size with ESM/CJS support
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Using bun
|
|
27
|
+
bun add openfigi-sdk
|
|
28
|
+
|
|
29
|
+
# Using npm
|
|
30
|
+
npm install openfigi-sdk
|
|
31
|
+
|
|
32
|
+
# Using pnpm
|
|
33
|
+
pnpm add openfigi-sdk
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { searchByISIN, searchByCUSIP, searchByTicker, searchBySEDOL, searchByBloombergId, createClient } from 'openfigi-sdk'
|
|
40
|
+
|
|
41
|
+
// Use standalone functions (no API key required for public access)
|
|
42
|
+
const response = await searchByISIN('US0378331005')
|
|
43
|
+
console.log(response.data)
|
|
44
|
+
|
|
45
|
+
// Search by CUSIP
|
|
46
|
+
await searchByCUSIP('037833100')
|
|
47
|
+
|
|
48
|
+
// Search by ticker symbol
|
|
49
|
+
await searchByTicker('AAPL', 'US')
|
|
50
|
+
|
|
51
|
+
// Search by SEDOL
|
|
52
|
+
await searchBySEDOL('2046251')
|
|
53
|
+
|
|
54
|
+
// Search by Bloomberg ID
|
|
55
|
+
await searchByBloombergId('BBG000B9XRY4')
|
|
56
|
+
|
|
57
|
+
// Or create a client with custom configuration (e.g., API key for higher rate limits)
|
|
58
|
+
const client = createClient({
|
|
59
|
+
apiKey: 'your-api-key-here',
|
|
60
|
+
timeout: 60000
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
const result = await client.searchByISIN('US0378331005')
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## API Reference
|
|
67
|
+
|
|
68
|
+
### Client Configuration
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
interface ClientConfig {
|
|
72
|
+
apiKey?: string // OpenFIGI API key for higher rate limits
|
|
73
|
+
baseUrl?: string // API base URL (default: https://api.openfigi.com)
|
|
74
|
+
timeout?: number // Request timeout in ms (default: 30000)
|
|
75
|
+
retryLimit?: number // Number of retry attempts (default: 3)
|
|
76
|
+
retryDelay?: number // Initial retry delay in ms (default: 1000)
|
|
77
|
+
userAgent?: string // Custom user agent
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Batch Mapping
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { mapping, createClient } from 'openfigi-sdk'
|
|
85
|
+
|
|
86
|
+
// Map multiple identifiers in a single request (max 100)
|
|
87
|
+
const requests = [
|
|
88
|
+
{ idType: 'ID_ISIN', idValue: 'US0378331005' },
|
|
89
|
+
{ idType: 'ID_CUSIP', idValue: '037833100' },
|
|
90
|
+
{ idType: 'ID_SEDOL', idValue: '2046251' }
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
// Using standalone function
|
|
94
|
+
const responses = await mapping(requests)
|
|
95
|
+
|
|
96
|
+
// Or with custom client
|
|
97
|
+
const client = createClient({ apiKey: 'your-key' })
|
|
98
|
+
const responses = await client.mapping(requests)
|
|
99
|
+
|
|
100
|
+
responses.forEach((response, index) => {
|
|
101
|
+
if (response.data) {
|
|
102
|
+
console.log(`Request ${index}:`, response.data)
|
|
103
|
+
} else if (response.error) {
|
|
104
|
+
console.error(`Request ${index} failed:`, response.error)
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Advanced Search Options
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { searchByISIN, mappingSingle } from 'openfigi-sdk'
|
|
113
|
+
|
|
114
|
+
// Search with additional filters
|
|
115
|
+
const response = await searchByISIN('US0378331005', {
|
|
116
|
+
exchCode: 'US',
|
|
117
|
+
currency: 'USD',
|
|
118
|
+
marketSecDes: 'Equity',
|
|
119
|
+
includeUnlistedEquities: false
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
// Custom mapping request
|
|
123
|
+
const response = await mappingSingle({
|
|
124
|
+
idType: 'ID_BB_GLOBAL',
|
|
125
|
+
idValue: 'BBG000B9XRY4',
|
|
126
|
+
currency: 'USD',
|
|
127
|
+
micCode: 'XNGS'
|
|
128
|
+
})
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Error Handling
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
import { searchByISIN, OpenFigiError, RateLimitError, ValidationError } from 'openfigi-sdk'
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
const response = await searchByISIN('invalid-isin')
|
|
138
|
+
} catch (error) {
|
|
139
|
+
if (error instanceof RateLimitError) {
|
|
140
|
+
console.log('Rate limited. Retry after:', error.retryAfter)
|
|
141
|
+
} else if (error instanceof ValidationError) {
|
|
142
|
+
console.log('Invalid request:', error.message)
|
|
143
|
+
} else if (error instanceof OpenFigiError) {
|
|
144
|
+
console.log('API error:', error.message, error.statusCode)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Rate Limit Information
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
import { searchByISIN, getRateLimitInfo } from 'openfigi-sdk'
|
|
153
|
+
|
|
154
|
+
// Check current rate limit status after any request
|
|
155
|
+
const response = await searchByISIN('US0378331005')
|
|
156
|
+
const rateLimitInfo = getRateLimitInfo()
|
|
157
|
+
|
|
158
|
+
if (rateLimitInfo) {
|
|
159
|
+
console.log('Rate limit:', rateLimitInfo.limit)
|
|
160
|
+
console.log('Remaining:', rateLimitInfo.remaining)
|
|
161
|
+
console.log('Reset time:', rateLimitInfo.reset)
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Supported Identifier Types
|
|
166
|
+
|
|
167
|
+
- `ID_ISIN` - International Securities Identification Number
|
|
168
|
+
- `ID_CUSIP` - Committee on Uniform Securities Identification Procedures
|
|
169
|
+
- `ID_SEDOL` - Stock Exchange Daily Official List
|
|
170
|
+
- `ID_BB_GLOBAL` - Bloomberg Global Identifier
|
|
171
|
+
- `ID_EXCH_SYMBOL` - Exchange symbol/ticker
|
|
172
|
+
- `ID_BB_UNIQUE` - Bloomberg Unique Identifier
|
|
173
|
+
- `ID_WERTPAPIER` - Wertpapierkennnummer (German securities code)
|
|
174
|
+
- And many more...
|
|
175
|
+
|
|
176
|
+
## Development
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
# Install dependencies
|
|
180
|
+
bun install
|
|
181
|
+
|
|
182
|
+
# Run tests
|
|
183
|
+
bun test
|
|
184
|
+
|
|
185
|
+
# Run tests with coverage
|
|
186
|
+
bun run test:coverage
|
|
187
|
+
|
|
188
|
+
# Build the library
|
|
189
|
+
bun run build
|
|
190
|
+
|
|
191
|
+
# Lint code
|
|
192
|
+
bun run lint
|
|
193
|
+
|
|
194
|
+
# Type check
|
|
195
|
+
bun run typecheck
|
|
196
|
+
|
|
197
|
+
# Generate documentation
|
|
198
|
+
bun run docs
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## API Rate Limits
|
|
202
|
+
|
|
203
|
+
- **Without API Key**: Lower rate limit for public access
|
|
204
|
+
- **With API Key**: Higher rate limit for authenticated requests
|
|
205
|
+
|
|
206
|
+
To get an API key, sign up at [OpenFIGI.com](https://www.openfigi.com/).
|
|
207
|
+
|
|
208
|
+
## Contributing
|
|
209
|
+
|
|
210
|
+
This is a **community-driven project** and contributions are welcome! Please feel free to submit a Pull Request.
|
|
211
|
+
|
|
212
|
+
1. Fork the repository
|
|
213
|
+
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
|
|
214
|
+
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
|
|
215
|
+
4. Push to the branch (`git push origin feature/AmazingFeature`)
|
|
216
|
+
5. Open a Pull Request
|
|
217
|
+
|
|
218
|
+
**Note**: This library is maintained by the community and is not affiliated with OpenFIGI or Bloomberg. For official API support, please contact OpenFIGI directly.
|
|
219
|
+
|
|
220
|
+
## License
|
|
221
|
+
|
|
222
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
223
|
+
|
|
224
|
+
## Links
|
|
225
|
+
|
|
226
|
+
- [OpenFIGI API Documentation](https://www.openfigi.com/api/documentation)
|
|
227
|
+
- [OpenFIGI Website](https://www.openfigi.com/)
|
|
228
|
+
- [GitHub Repository](https://github.com/viktorlarsson/openfigi)
|
|
229
|
+
- [npm Package](https://www.npmjs.com/package/openfigi-sdk)
|
|
230
|
+
|
|
231
|
+
## Acknowledgments
|
|
232
|
+
|
|
233
|
+
Built with modern tools and technologies:
|
|
234
|
+
- [Bun](https://bun.sh/) - Fast all-in-one JavaScript runtime
|
|
235
|
+
- [TypeScript](https://www.typescriptlang.org/) - Type-safe JavaScript
|
|
236
|
+
- [Vitest](https://vitest.dev/) - Fast unit testing framework
|
|
237
|
+
- [Biome](https://biomejs.dev/) - Fast formatter and linter
|
|
238
|
+
- [tsup](https://tsup.egoist.dev/) - Zero-config TypeScript bundler
|
|
239
|
+
- [Zod](https://zod.dev/) - TypeScript-first validation
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var ky = require('ky');
|
|
4
|
+
var zod = require('zod');
|
|
5
|
+
|
|
6
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
7
|
+
|
|
8
|
+
var ky__default = /*#__PURE__*/_interopDefault(ky);
|
|
9
|
+
|
|
10
|
+
// src/client/client.ts
|
|
11
|
+
|
|
12
|
+
// src/utils/errors.ts
|
|
13
|
+
var OpenFigiError = class extends Error {
|
|
14
|
+
constructor(message, statusCode, response) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.statusCode = statusCode;
|
|
17
|
+
this.response = response;
|
|
18
|
+
this.name = "OpenFigiError";
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
var RateLimitError = class extends OpenFigiError {
|
|
22
|
+
constructor(message, retryAfter, statusCode) {
|
|
23
|
+
super(message, statusCode);
|
|
24
|
+
this.retryAfter = retryAfter;
|
|
25
|
+
this.name = "RateLimitError";
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var ValidationError = class extends OpenFigiError {
|
|
29
|
+
constructor(message, errors) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.errors = errors;
|
|
32
|
+
this.name = "ValidationError";
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// src/utils/helpers.ts
|
|
37
|
+
function sleep(ms) {
|
|
38
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
39
|
+
}
|
|
40
|
+
function parseRateLimitHeaders(headers) {
|
|
41
|
+
const limit = headers.get("x-ratelimit-limit");
|
|
42
|
+
const remaining = headers.get("x-ratelimit-remaining");
|
|
43
|
+
const reset = headers.get("x-ratelimit-reset");
|
|
44
|
+
return {
|
|
45
|
+
limit: limit ? parseInt(limit, 10) : void 0,
|
|
46
|
+
remaining: remaining ? parseInt(remaining, 10) : void 0,
|
|
47
|
+
reset: reset ? new Date(parseInt(reset, 10) * 1e3) : void 0
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function exponentialBackoff(attempt, baseDelay = 1e3, maxDelay = 3e4) {
|
|
51
|
+
const delay = Math.min(baseDelay * 2 ** attempt, maxDelay);
|
|
52
|
+
const jitter = Math.random() * 0.1 * delay;
|
|
53
|
+
return Math.floor(delay + jitter);
|
|
54
|
+
}
|
|
55
|
+
var IdTypeSchema = zod.z.enum([
|
|
56
|
+
"ID_ISIN",
|
|
57
|
+
"ID_BB_UNIQUE",
|
|
58
|
+
"ID_SEDOL",
|
|
59
|
+
"ID_COMMON",
|
|
60
|
+
"ID_WERTPAPIER",
|
|
61
|
+
"ID_CUSIP",
|
|
62
|
+
"ID_BB",
|
|
63
|
+
"ID_ITALY",
|
|
64
|
+
"ID_EXCH_SYMBOL",
|
|
65
|
+
"ID_FULL_EXCHANGE_SYMBOL",
|
|
66
|
+
"COMPOSITE_ID_BB_GLOBAL",
|
|
67
|
+
"ID_BB_GLOBAL_SHARE_CLASS_LEVEL",
|
|
68
|
+
"ID_BB_GLOBAL",
|
|
69
|
+
"ID_BB_SEC_NUM_DES",
|
|
70
|
+
"ID_BB_SEC_NUM",
|
|
71
|
+
"ID_CINS",
|
|
72
|
+
"ID_BELGIUM",
|
|
73
|
+
"ID_DENMARK",
|
|
74
|
+
"ID_FRANCE",
|
|
75
|
+
"ID_JAPAN",
|
|
76
|
+
"ID_LUXEMBOURG",
|
|
77
|
+
"ID_NETHERLANDS",
|
|
78
|
+
"ID_POLAND",
|
|
79
|
+
"ID_PORTUGAL",
|
|
80
|
+
"ID_SWEDEN",
|
|
81
|
+
"ID_SHORT_CODE"
|
|
82
|
+
]);
|
|
83
|
+
var SecurityTypeSchema = zod.z.enum([
|
|
84
|
+
"Common Stock",
|
|
85
|
+
"Preference",
|
|
86
|
+
"ADR",
|
|
87
|
+
"Open-End Fund",
|
|
88
|
+
"Closed-End Fund",
|
|
89
|
+
"ETF",
|
|
90
|
+
"ETN",
|
|
91
|
+
"Unit",
|
|
92
|
+
"Mutual Fund",
|
|
93
|
+
"Money Market",
|
|
94
|
+
"Commodity",
|
|
95
|
+
"Currency",
|
|
96
|
+
"Option",
|
|
97
|
+
"Index"
|
|
98
|
+
]);
|
|
99
|
+
var MarketSectorSchema = zod.z.enum([
|
|
100
|
+
"All",
|
|
101
|
+
"Comdty",
|
|
102
|
+
"Curncy",
|
|
103
|
+
"Equity",
|
|
104
|
+
"Govt",
|
|
105
|
+
"Corp",
|
|
106
|
+
"Index",
|
|
107
|
+
"Money",
|
|
108
|
+
"Mtge",
|
|
109
|
+
"Muni",
|
|
110
|
+
"Pref"
|
|
111
|
+
]);
|
|
112
|
+
var MappingRequestSchema = zod.z.object({
|
|
113
|
+
idType: IdTypeSchema,
|
|
114
|
+
idValue: zod.z.string().min(1),
|
|
115
|
+
exchCode: zod.z.string().optional(),
|
|
116
|
+
micCode: zod.z.string().optional(),
|
|
117
|
+
currency: zod.z.string().length(3).optional(),
|
|
118
|
+
marketSecDes: MarketSectorSchema.optional(),
|
|
119
|
+
securityType: SecurityTypeSchema.optional(),
|
|
120
|
+
securityType2: zod.z.string().optional(),
|
|
121
|
+
includeUnlistedEquities: zod.z.boolean().optional(),
|
|
122
|
+
optionType: zod.z.enum(["Put", "Call"]).optional(),
|
|
123
|
+
strike: zod.z.array(zod.z.number()).optional(),
|
|
124
|
+
contractSize: zod.z.number().optional(),
|
|
125
|
+
coupon: zod.z.array(zod.z.number()).optional(),
|
|
126
|
+
expiration: zod.z.array(zod.z.number()).optional(),
|
|
127
|
+
maturity: zod.z.array(zod.z.number()).optional(),
|
|
128
|
+
stateCode: zod.z.string().length(2).optional()
|
|
129
|
+
});
|
|
130
|
+
var FigiResultSchema = zod.z.object({
|
|
131
|
+
figi: zod.z.string(),
|
|
132
|
+
securityType: SecurityTypeSchema.optional(),
|
|
133
|
+
marketSector: MarketSectorSchema.optional(),
|
|
134
|
+
ticker: zod.z.string().optional(),
|
|
135
|
+
name: zod.z.string().optional(),
|
|
136
|
+
exchCode: zod.z.string().optional(),
|
|
137
|
+
shareClassFIGI: zod.z.string().optional(),
|
|
138
|
+
compositeFIGI: zod.z.string().optional(),
|
|
139
|
+
securityType2: zod.z.string().optional(),
|
|
140
|
+
securityDescription: zod.z.string().optional(),
|
|
141
|
+
metadata: zod.z.string().optional()
|
|
142
|
+
});
|
|
143
|
+
var MappingResponseSchema = zod.z.object({
|
|
144
|
+
data: zod.z.array(FigiResultSchema).optional(),
|
|
145
|
+
warning: zod.z.string().optional(),
|
|
146
|
+
error: zod.z.string().optional()
|
|
147
|
+
});
|
|
148
|
+
zod.z.object({
|
|
149
|
+
error: zod.z.string(),
|
|
150
|
+
message: zod.z.string().optional(),
|
|
151
|
+
statusCode: zod.z.number().optional()
|
|
152
|
+
});
|
|
153
|
+
var ClientConfigSchema = zod.z.object({
|
|
154
|
+
apiKey: zod.z.string().optional(),
|
|
155
|
+
baseUrl: zod.z.string().url().optional(),
|
|
156
|
+
timeout: zod.z.number().positive().optional(),
|
|
157
|
+
retryLimit: zod.z.number().min(0).max(10).optional(),
|
|
158
|
+
retryDelay: zod.z.number().positive().optional(),
|
|
159
|
+
userAgent: zod.z.string().optional()
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// src/client/client.ts
|
|
163
|
+
var DEFAULT_CONFIG = {
|
|
164
|
+
apiKey: "",
|
|
165
|
+
baseUrl: "https://api.openfigi.com",
|
|
166
|
+
timeout: 3e4,
|
|
167
|
+
retryLimit: 3,
|
|
168
|
+
retryDelay: 1e3,
|
|
169
|
+
userAgent: "@openfigi/sdk"
|
|
170
|
+
};
|
|
171
|
+
var currentRateLimitInfo;
|
|
172
|
+
var debugMode = false;
|
|
173
|
+
var setDebugMode = (enabled) => {
|
|
174
|
+
debugMode = enabled;
|
|
175
|
+
};
|
|
176
|
+
var log = (level, message, ...args) => {
|
|
177
|
+
if (!debugMode) return;
|
|
178
|
+
const prefix = `[@openfigi/sdk] [${level.toUpperCase()}]`;
|
|
179
|
+
console[level](prefix, message, ...args);
|
|
180
|
+
};
|
|
181
|
+
var createHttpClient = (config) => {
|
|
182
|
+
return ky__default.default.create({
|
|
183
|
+
prefixUrl: config.baseUrl,
|
|
184
|
+
timeout: config.timeout,
|
|
185
|
+
headers: {
|
|
186
|
+
"Content-Type": "application/json",
|
|
187
|
+
"User-Agent": config.userAgent,
|
|
188
|
+
...config.apiKey && { "X-OPENFIGI-APIKEY": config.apiKey }
|
|
189
|
+
},
|
|
190
|
+
retry: {
|
|
191
|
+
limit: config.retryLimit,
|
|
192
|
+
methods: ["get", "post"],
|
|
193
|
+
statusCodes: [408, 413, 429, 500, 502, 503, 504]
|
|
194
|
+
},
|
|
195
|
+
hooks: {
|
|
196
|
+
beforeRequest: [
|
|
197
|
+
(request) => {
|
|
198
|
+
log("info", `Request: ${request.method} ${request.url}`);
|
|
199
|
+
}
|
|
200
|
+
],
|
|
201
|
+
beforeRetry: [
|
|
202
|
+
async ({ error, retryCount }) => {
|
|
203
|
+
const delay = exponentialBackoff(retryCount, config.retryDelay);
|
|
204
|
+
log("warn", `Retry attempt ${retryCount} after ${delay}ms`, error.message);
|
|
205
|
+
await sleep(delay);
|
|
206
|
+
}
|
|
207
|
+
],
|
|
208
|
+
afterResponse: [
|
|
209
|
+
(_request, _options, response) => {
|
|
210
|
+
const { limit, remaining, reset } = parseRateLimitHeaders(response.headers);
|
|
211
|
+
if (limit !== void 0 && remaining !== void 0 && reset) {
|
|
212
|
+
currentRateLimitInfo = { limit, remaining, reset };
|
|
213
|
+
log("info", `Rate limit: ${remaining}/${limit} (resets: ${reset.toISOString()})`);
|
|
214
|
+
}
|
|
215
|
+
log("info", `Response: ${response.status} ${response.statusText}`);
|
|
216
|
+
return response;
|
|
217
|
+
}
|
|
218
|
+
]
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
};
|
|
222
|
+
var createClient = (config = {}) => {
|
|
223
|
+
const validation = ClientConfigSchema.safeParse(config);
|
|
224
|
+
if (!validation.success) {
|
|
225
|
+
const errors = validation.error.issues.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ");
|
|
226
|
+
throw new ValidationError(`Invalid client configuration: ${errors}`, validation.error);
|
|
227
|
+
}
|
|
228
|
+
const finalConfig = { ...DEFAULT_CONFIG, ...config };
|
|
229
|
+
const httpClient = createHttpClient(finalConfig);
|
|
230
|
+
const mapping2 = async (requests) => {
|
|
231
|
+
if (!Array.isArray(requests) || requests.length === 0) {
|
|
232
|
+
throw new ValidationError(
|
|
233
|
+
"Requests must be a non-empty array. Provide at least one mapping request."
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
if (requests.length > 100) {
|
|
237
|
+
throw new ValidationError(
|
|
238
|
+
`Too many requests: ${requests.length}. Maximum 100 requests allowed per call. Split into multiple batches.`
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
const validatedRequests = requests.map((req, index) => {
|
|
242
|
+
const validation2 = MappingRequestSchema.safeParse(req);
|
|
243
|
+
if (!validation2.success) {
|
|
244
|
+
const errors = validation2.error.issues.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ");
|
|
245
|
+
throw new ValidationError(
|
|
246
|
+
`Invalid mapping request at index ${index}: ${errors}`,
|
|
247
|
+
validation2.error
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
return validation2.data;
|
|
251
|
+
});
|
|
252
|
+
try {
|
|
253
|
+
log("info", `Mapping ${validatedRequests.length} identifier(s)`);
|
|
254
|
+
const response = await httpClient.post("v3/mapping", {
|
|
255
|
+
json: validatedRequests
|
|
256
|
+
});
|
|
257
|
+
const data = await response.json();
|
|
258
|
+
if (!Array.isArray(data)) {
|
|
259
|
+
throw new OpenFigiError(
|
|
260
|
+
`Invalid API response: expected array but got ${typeof data}`,
|
|
261
|
+
response.status,
|
|
262
|
+
data
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
const validatedResponses = data.map((item, index) => {
|
|
266
|
+
const validation2 = MappingResponseSchema.safeParse(item);
|
|
267
|
+
if (!validation2.success) {
|
|
268
|
+
log("warn", `Invalid response at index ${index}:`, item, validation2.error);
|
|
269
|
+
return item;
|
|
270
|
+
}
|
|
271
|
+
return validation2.data;
|
|
272
|
+
});
|
|
273
|
+
log("info", `Mapped ${validatedResponses.length} identifier(s) successfully`);
|
|
274
|
+
return validatedResponses;
|
|
275
|
+
} catch (error) {
|
|
276
|
+
if (error instanceof OpenFigiError) {
|
|
277
|
+
throw error;
|
|
278
|
+
}
|
|
279
|
+
if (error instanceof Error && "response" in error) {
|
|
280
|
+
const httpError = error;
|
|
281
|
+
const status = httpError.response?.status;
|
|
282
|
+
if (status === 429) {
|
|
283
|
+
const retryAfter = httpError.response?.headers?.get("retry-after");
|
|
284
|
+
throw new RateLimitError(
|
|
285
|
+
"Rate limit exceeded. Please wait before making more requests.",
|
|
286
|
+
retryAfter ? parseInt(retryAfter, 10) : void 0,
|
|
287
|
+
status
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
if (status === 400) {
|
|
291
|
+
const body = await httpError.response?.text();
|
|
292
|
+
throw new ValidationError(`Bad request: ${body || "Invalid request format"}`);
|
|
293
|
+
}
|
|
294
|
+
if (status === 401) {
|
|
295
|
+
throw new OpenFigiError("Authentication failed. Check your API key.", status);
|
|
296
|
+
}
|
|
297
|
+
if (status === 404) {
|
|
298
|
+
throw new OpenFigiError("Endpoint not found. Please check the API version.", status);
|
|
299
|
+
}
|
|
300
|
+
throw new OpenFigiError(
|
|
301
|
+
`Request failed with status ${status}: ${httpError.message}`,
|
|
302
|
+
status,
|
|
303
|
+
await httpError.response?.text()
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
throw new OpenFigiError("Unexpected error occurred", void 0, error);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
const mappingSingle2 = async (request) => {
|
|
310
|
+
const responses = await mapping2([request]);
|
|
311
|
+
return responses[0];
|
|
312
|
+
};
|
|
313
|
+
const searchByISIN2 = async (isin, options) => {
|
|
314
|
+
if (!isin || typeof isin !== "string") {
|
|
315
|
+
throw new ValidationError("ISIN must be a non-empty string");
|
|
316
|
+
}
|
|
317
|
+
return mappingSingle2({
|
|
318
|
+
idType: "ID_ISIN",
|
|
319
|
+
idValue: isin.trim(),
|
|
320
|
+
...options
|
|
321
|
+
});
|
|
322
|
+
};
|
|
323
|
+
const searchByCUSIP2 = async (cusip, options) => {
|
|
324
|
+
if (!cusip || typeof cusip !== "string") {
|
|
325
|
+
throw new ValidationError("CUSIP must be a non-empty string");
|
|
326
|
+
}
|
|
327
|
+
return mappingSingle2({
|
|
328
|
+
idType: "ID_CUSIP",
|
|
329
|
+
idValue: cusip.trim(),
|
|
330
|
+
...options
|
|
331
|
+
});
|
|
332
|
+
};
|
|
333
|
+
const searchBySEDOL2 = async (sedol, options) => {
|
|
334
|
+
if (!sedol || typeof sedol !== "string") {
|
|
335
|
+
throw new ValidationError("SEDOL must be a non-empty string");
|
|
336
|
+
}
|
|
337
|
+
return mappingSingle2({
|
|
338
|
+
idType: "ID_SEDOL",
|
|
339
|
+
idValue: sedol.trim(),
|
|
340
|
+
...options
|
|
341
|
+
});
|
|
342
|
+
};
|
|
343
|
+
const searchByTicker2 = async (ticker, exchCode, options) => {
|
|
344
|
+
if (!ticker || typeof ticker !== "string") {
|
|
345
|
+
throw new ValidationError("Ticker must be a non-empty string");
|
|
346
|
+
}
|
|
347
|
+
return mappingSingle2({
|
|
348
|
+
idType: "ID_EXCH_SYMBOL",
|
|
349
|
+
idValue: ticker.trim().toUpperCase(),
|
|
350
|
+
exchCode: exchCode?.trim(),
|
|
351
|
+
...options
|
|
352
|
+
});
|
|
353
|
+
};
|
|
354
|
+
const searchByBloombergId2 = async (bbgId, options) => {
|
|
355
|
+
if (!bbgId || typeof bbgId !== "string") {
|
|
356
|
+
throw new ValidationError("Bloomberg ID must be a non-empty string");
|
|
357
|
+
}
|
|
358
|
+
return mappingSingle2({
|
|
359
|
+
idType: "ID_BB_GLOBAL",
|
|
360
|
+
idValue: bbgId.trim(),
|
|
361
|
+
...options
|
|
362
|
+
});
|
|
363
|
+
};
|
|
364
|
+
const getRateLimitInfo2 = () => {
|
|
365
|
+
return currentRateLimitInfo;
|
|
366
|
+
};
|
|
367
|
+
return {
|
|
368
|
+
mapping: mapping2,
|
|
369
|
+
mappingSingle: mappingSingle2,
|
|
370
|
+
searchByISIN: searchByISIN2,
|
|
371
|
+
searchByCUSIP: searchByCUSIP2,
|
|
372
|
+
searchBySEDOL: searchBySEDOL2,
|
|
373
|
+
searchByTicker: searchByTicker2,
|
|
374
|
+
searchByBloombergId: searchByBloombergId2,
|
|
375
|
+
getRateLimitInfo: getRateLimitInfo2
|
|
376
|
+
};
|
|
377
|
+
};
|
|
378
|
+
var defaultClient = createClient();
|
|
379
|
+
var mapping = defaultClient.mapping;
|
|
380
|
+
var mappingSingle = defaultClient.mappingSingle;
|
|
381
|
+
var searchByISIN = defaultClient.searchByISIN;
|
|
382
|
+
var searchByCUSIP = defaultClient.searchByCUSIP;
|
|
383
|
+
var searchBySEDOL = defaultClient.searchBySEDOL;
|
|
384
|
+
var searchByTicker = defaultClient.searchByTicker;
|
|
385
|
+
var searchByBloombergId = defaultClient.searchByBloombergId;
|
|
386
|
+
var getRateLimitInfo = defaultClient.getRateLimitInfo;
|
|
387
|
+
|
|
388
|
+
// src/utils/validators.ts
|
|
389
|
+
var isValidISIN = (isin) => {
|
|
390
|
+
return /^[A-Z]{2}[A-Z0-9]{9}[0-9]$/.test(isin);
|
|
391
|
+
};
|
|
392
|
+
var isValidCUSIP = (cusip) => {
|
|
393
|
+
return /^[A-Z0-9]{9}$/.test(cusip);
|
|
394
|
+
};
|
|
395
|
+
var isValidSEDOL = (sedol) => {
|
|
396
|
+
return /^[A-Z0-9]{7}$/.test(sedol);
|
|
397
|
+
};
|
|
398
|
+
var isValidBloombergId = (bbgId) => {
|
|
399
|
+
return /^BBG[A-Z0-9]{9}$/.test(bbgId);
|
|
400
|
+
};
|
|
401
|
+
var batchArray = (array, batchSize) => {
|
|
402
|
+
const batches = [];
|
|
403
|
+
for (let i = 0; i < array.length; i += batchSize) {
|
|
404
|
+
batches.push(array.slice(i, i + batchSize));
|
|
405
|
+
}
|
|
406
|
+
return batches;
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
exports.FigiResultSchema = FigiResultSchema;
|
|
410
|
+
exports.IdTypeSchema = IdTypeSchema;
|
|
411
|
+
exports.MappingRequestSchema = MappingRequestSchema;
|
|
412
|
+
exports.MappingResponseSchema = MappingResponseSchema;
|
|
413
|
+
exports.MarketSectorSchema = MarketSectorSchema;
|
|
414
|
+
exports.OpenFigiError = OpenFigiError;
|
|
415
|
+
exports.RateLimitError = RateLimitError;
|
|
416
|
+
exports.SecurityTypeSchema = SecurityTypeSchema;
|
|
417
|
+
exports.ValidationError = ValidationError;
|
|
418
|
+
exports.batchArray = batchArray;
|
|
419
|
+
exports.createClient = createClient;
|
|
420
|
+
exports.getRateLimitInfo = getRateLimitInfo;
|
|
421
|
+
exports.isValidBloombergId = isValidBloombergId;
|
|
422
|
+
exports.isValidCUSIP = isValidCUSIP;
|
|
423
|
+
exports.isValidISIN = isValidISIN;
|
|
424
|
+
exports.isValidSEDOL = isValidSEDOL;
|
|
425
|
+
exports.mapping = mapping;
|
|
426
|
+
exports.mappingSingle = mappingSingle;
|
|
427
|
+
exports.searchByBloombergId = searchByBloombergId;
|
|
428
|
+
exports.searchByCUSIP = searchByCUSIP;
|
|
429
|
+
exports.searchByISIN = searchByISIN;
|
|
430
|
+
exports.searchBySEDOL = searchBySEDOL;
|
|
431
|
+
exports.searchByTicker = searchByTicker;
|
|
432
|
+
exports.setDebugMode = setDebugMode;
|
|
433
|
+
//# sourceMappingURL=index.cjs.map
|
|
434
|
+
//# sourceMappingURL=index.cjs.map
|