@fin.cx/opendata 3.1.0 → 3.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.handelsregister.js +2 -2
- package/dist_ts/plugins.d.ts +1 -1
- package/dist_ts/plugins.js +2 -2
- package/dist_ts/stocks/classes.baseproviderservice.d.ts +129 -0
- package/dist_ts/stocks/classes.baseproviderservice.js +204 -0
- package/dist_ts/stocks/classes.stockdataservice.d.ts +121 -0
- package/dist_ts/stocks/classes.stockdataservice.js +495 -0
- package/dist_ts/stocks/index.d.ts +3 -0
- package/dist_ts/stocks/index.js +5 -1
- package/dist_ts/stocks/interfaces/stockdata.d.ts +57 -0
- package/dist_ts/stocks/interfaces/stockdata.js +2 -0
- package/dist_ts/stocks/providers/provider.secedgar.d.ts +3 -0
- package/dist_ts/stocks/providers/provider.secedgar.js +72 -41
- package/package.json +4 -4
- package/readme.md +133 -61
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.handelsregister.ts +1 -1
- package/ts/plugins.ts +1 -1
- package/ts/stocks/classes.baseproviderservice.ts +296 -0
- package/ts/stocks/classes.stockdataservice.ts +647 -0
- package/ts/stocks/index.ts +5 -0
- package/ts/stocks/interfaces/stockdata.ts +65 -0
- package/ts/stocks/providers/provider.secedgar.ts +81 -43
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Base provider entry for tracking provider state
|
|
5
|
+
*/
|
|
6
|
+
export interface IBaseProviderEntry<TProvider> {
|
|
7
|
+
provider: TProvider;
|
|
8
|
+
config: IBaseProviderConfig;
|
|
9
|
+
lastError?: Error;
|
|
10
|
+
lastErrorTime?: Date;
|
|
11
|
+
successCount: number;
|
|
12
|
+
errorCount: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Base provider configuration
|
|
17
|
+
*/
|
|
18
|
+
export interface IBaseProviderConfig {
|
|
19
|
+
enabled: boolean;
|
|
20
|
+
priority: number;
|
|
21
|
+
timeout?: number;
|
|
22
|
+
retryAttempts?: number;
|
|
23
|
+
retryDelay?: number;
|
|
24
|
+
cacheTTL?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Base provider interface
|
|
29
|
+
*/
|
|
30
|
+
export interface IBaseProvider {
|
|
31
|
+
name: string;
|
|
32
|
+
priority: number;
|
|
33
|
+
isAvailable(): Promise<boolean>;
|
|
34
|
+
readonly requiresAuth: boolean;
|
|
35
|
+
readonly rateLimit?: {
|
|
36
|
+
requestsPerMinute: number;
|
|
37
|
+
requestsPerDay?: number;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Cache entry for any data type
|
|
43
|
+
*/
|
|
44
|
+
export interface IBaseCacheEntry<TData> {
|
|
45
|
+
data: TData;
|
|
46
|
+
timestamp: Date;
|
|
47
|
+
ttl: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Base service for managing data providers with caching
|
|
52
|
+
* Shared logic extracted from StockPriceService and FundamentalsService
|
|
53
|
+
*/
|
|
54
|
+
export abstract class BaseProviderService<TProvider extends IBaseProvider, TData> {
|
|
55
|
+
protected providers = new Map<string, IBaseProviderEntry<TProvider>>();
|
|
56
|
+
protected cache = new Map<string, IBaseCacheEntry<TData>>();
|
|
57
|
+
protected logger = console;
|
|
58
|
+
|
|
59
|
+
protected cacheConfig = {
|
|
60
|
+
ttl: 60000, // Default 60 seconds
|
|
61
|
+
maxEntries: 10000
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
constructor(cacheConfig?: { ttl?: number; maxEntries?: number }) {
|
|
65
|
+
if (cacheConfig) {
|
|
66
|
+
this.cacheConfig = { ...this.cacheConfig, ...cacheConfig };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Register a provider
|
|
72
|
+
*/
|
|
73
|
+
public register(provider: TProvider, config?: Partial<IBaseProviderConfig>): void {
|
|
74
|
+
const defaultConfig: IBaseProviderConfig = {
|
|
75
|
+
enabled: true,
|
|
76
|
+
priority: provider.priority,
|
|
77
|
+
timeout: 30000,
|
|
78
|
+
retryAttempts: 2,
|
|
79
|
+
retryDelay: 1000,
|
|
80
|
+
cacheTTL: this.cacheConfig.ttl
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const mergedConfig = { ...defaultConfig, ...config };
|
|
84
|
+
|
|
85
|
+
this.providers.set(provider.name, {
|
|
86
|
+
provider,
|
|
87
|
+
config: mergedConfig,
|
|
88
|
+
successCount: 0,
|
|
89
|
+
errorCount: 0
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
console.log(`Registered provider: ${provider.name}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Unregister a provider
|
|
97
|
+
*/
|
|
98
|
+
public unregister(providerName: string): void {
|
|
99
|
+
this.providers.delete(providerName);
|
|
100
|
+
console.log(`Unregistered provider: ${providerName}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Get a specific provider by name
|
|
105
|
+
*/
|
|
106
|
+
public getProvider(name: string): TProvider | undefined {
|
|
107
|
+
return this.providers.get(name)?.provider;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Get all registered providers
|
|
112
|
+
*/
|
|
113
|
+
public getAllProviders(): TProvider[] {
|
|
114
|
+
return Array.from(this.providers.values()).map(entry => entry.provider);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get enabled providers sorted by priority
|
|
119
|
+
*/
|
|
120
|
+
public getEnabledProviders(): TProvider[] {
|
|
121
|
+
return Array.from(this.providers.values())
|
|
122
|
+
.filter(entry => entry.config.enabled)
|
|
123
|
+
.sort((a, b) => (b.config.priority || 0) - (a.config.priority || 0))
|
|
124
|
+
.map(entry => entry.provider);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Check health of all providers
|
|
129
|
+
*/
|
|
130
|
+
public async checkProvidersHealth(): Promise<Map<string, boolean>> {
|
|
131
|
+
const health = new Map<string, boolean>();
|
|
132
|
+
|
|
133
|
+
for (const [name, entry] of this.providers) {
|
|
134
|
+
if (!entry.config.enabled) {
|
|
135
|
+
health.set(name, false);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const isAvailable = await entry.provider.isAvailable();
|
|
141
|
+
health.set(name, isAvailable);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
health.set(name, false);
|
|
144
|
+
console.error(`Health check failed for ${name}:`, error);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return health;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Get provider statistics
|
|
153
|
+
*/
|
|
154
|
+
public getProviderStats(): Map<
|
|
155
|
+
string,
|
|
156
|
+
{
|
|
157
|
+
successCount: number;
|
|
158
|
+
errorCount: number;
|
|
159
|
+
lastError?: string;
|
|
160
|
+
lastErrorTime?: Date;
|
|
161
|
+
}
|
|
162
|
+
> {
|
|
163
|
+
const stats = new Map();
|
|
164
|
+
|
|
165
|
+
for (const [name, entry] of this.providers) {
|
|
166
|
+
stats.set(name, {
|
|
167
|
+
successCount: entry.successCount,
|
|
168
|
+
errorCount: entry.errorCount,
|
|
169
|
+
lastError: entry.lastError?.message,
|
|
170
|
+
lastErrorTime: entry.lastErrorTime
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return stats;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Clear all cached data
|
|
179
|
+
*/
|
|
180
|
+
public clearCache(): void {
|
|
181
|
+
this.cache.clear();
|
|
182
|
+
console.log('Cache cleared');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Set cache TTL
|
|
187
|
+
*/
|
|
188
|
+
public setCacheTTL(ttl: number): void {
|
|
189
|
+
this.cacheConfig.ttl = ttl;
|
|
190
|
+
console.log(`Cache TTL set to ${ttl}ms`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Get cache statistics
|
|
195
|
+
*/
|
|
196
|
+
public getCacheStats(): {
|
|
197
|
+
size: number;
|
|
198
|
+
maxEntries: number;
|
|
199
|
+
ttl: number;
|
|
200
|
+
} {
|
|
201
|
+
return {
|
|
202
|
+
size: this.cache.size,
|
|
203
|
+
maxEntries: this.cacheConfig.maxEntries,
|
|
204
|
+
ttl: this.cacheConfig.ttl
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Fetch with retry logic
|
|
210
|
+
*/
|
|
211
|
+
protected async fetchWithRetry<T>(
|
|
212
|
+
fetchFn: () => Promise<T>,
|
|
213
|
+
config: IBaseProviderConfig
|
|
214
|
+
): Promise<T> {
|
|
215
|
+
const maxAttempts = config.retryAttempts || 1;
|
|
216
|
+
let lastError: Error | undefined;
|
|
217
|
+
|
|
218
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
219
|
+
try {
|
|
220
|
+
return await fetchFn();
|
|
221
|
+
} catch (error) {
|
|
222
|
+
lastError = error as Error;
|
|
223
|
+
|
|
224
|
+
if (attempt < maxAttempts) {
|
|
225
|
+
const delay = (config.retryDelay || 1000) * attempt;
|
|
226
|
+
console.log(`Retry attempt ${attempt} after ${delay}ms`);
|
|
227
|
+
await plugins.smartdelay.delayFor(delay);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
throw lastError || new Error('Unknown error during fetch');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Get from cache if not expired
|
|
237
|
+
*/
|
|
238
|
+
protected getFromCache(key: string): TData | null {
|
|
239
|
+
const entry = this.cache.get(key);
|
|
240
|
+
|
|
241
|
+
if (!entry) {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Check if cache entry has expired
|
|
246
|
+
const age = Date.now() - entry.timestamp.getTime();
|
|
247
|
+
if (entry.ttl !== Infinity && age > entry.ttl) {
|
|
248
|
+
this.cache.delete(key);
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return entry.data;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Add to cache with TTL
|
|
257
|
+
*/
|
|
258
|
+
protected addToCache(key: string, data: TData, ttl?: number): void {
|
|
259
|
+
// Enforce max entries limit
|
|
260
|
+
if (this.cache.size >= this.cacheConfig.maxEntries) {
|
|
261
|
+
// Remove oldest entry
|
|
262
|
+
const oldestKey = this.cache.keys().next().value;
|
|
263
|
+
if (oldestKey) {
|
|
264
|
+
this.cache.delete(oldestKey);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
this.cache.set(key, {
|
|
269
|
+
data,
|
|
270
|
+
timestamp: new Date(),
|
|
271
|
+
ttl: ttl || this.cacheConfig.ttl
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Track successful fetch for provider
|
|
277
|
+
*/
|
|
278
|
+
protected trackSuccess(providerName: string): void {
|
|
279
|
+
const entry = this.providers.get(providerName);
|
|
280
|
+
if (entry) {
|
|
281
|
+
entry.successCount++;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Track failed fetch for provider
|
|
287
|
+
*/
|
|
288
|
+
protected trackError(providerName: string, error: Error): void {
|
|
289
|
+
const entry = this.providers.get(providerName);
|
|
290
|
+
if (entry) {
|
|
291
|
+
entry.errorCount++;
|
|
292
|
+
entry.lastError = error;
|
|
293
|
+
entry.lastErrorTime = new Date();
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|