@finatic/client 0.9.1 → 0.9.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/README.md +243 -47
- package/dist/index.d.ts +12 -6
- package/dist/index.js +95 -69
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +95 -69
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -9
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Client
|
|
1
|
+
# Finatic Client SDK
|
|
2
2
|
|
|
3
|
-
A comprehensive browser SDK for integrating Finatic
|
|
3
|
+
A comprehensive browser SDK for integrating Finatic into your web applications. Connect your users to multiple brokerages through a unified, standardized interface.
|
|
4
4
|
|
|
5
5
|
**Finatic is a brokerage first aggregator. We simplify, standardize and enhance broker data.**
|
|
6
6
|
|
|
@@ -10,80 +10,276 @@ A comprehensive browser SDK for integrating Finatic Client into your web applica
|
|
|
10
10
|
npm install @finatic/client
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Or using yarn:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
yarn add @finatic/client
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start (5 Minutes)
|
|
20
|
+
|
|
21
|
+
### 1. Initialize the SDK
|
|
14
22
|
|
|
15
23
|
```typescript
|
|
16
|
-
import {
|
|
24
|
+
import { FinaticConnect } from '@finatic/client';
|
|
17
25
|
|
|
18
|
-
//
|
|
26
|
+
// Initialize with a one-time token (obtained from your backend)
|
|
27
|
+
const finatic = await FinaticConnect.init('your-one-time-token', undefined, {
|
|
28
|
+
baseUrl: 'https://api.finatic.dev', // Optional, defaults to production
|
|
29
|
+
logLevel: 'info', // Optional: 'debug' | 'info' | 'warn' | 'error'
|
|
30
|
+
});
|
|
19
31
|
```
|
|
20
32
|
|
|
21
|
-
|
|
33
|
+
**Note:** The Client SDK requires a one-time token (not an API key). Get this token from your backend server using the Server SDK's `getToken()` method.
|
|
22
34
|
|
|
23
|
-
|
|
35
|
+
### 2. Open the Portal for Authentication
|
|
24
36
|
|
|
25
|
-
|
|
37
|
+
The portal allows users to connect their broker accounts. You can either:
|
|
26
38
|
|
|
27
|
-
|
|
28
|
-
# Install dependencies
|
|
29
|
-
npm install
|
|
39
|
+
**Option A: Open portal in iframe (recommended)**
|
|
30
40
|
|
|
31
|
-
|
|
32
|
-
|
|
41
|
+
```typescript
|
|
42
|
+
await finatic.openPortal(
|
|
43
|
+
'dark', // Theme: 'light' | 'dark' | theme object
|
|
44
|
+
['alpaca', 'tradier'], // Optional: Filter specific brokers
|
|
45
|
+
'user@example.com', // Optional: Pre-fill email
|
|
46
|
+
'modal', // Mode: 'modal' | 'fullscreen'
|
|
47
|
+
(userId) => {
|
|
48
|
+
// User successfully authenticated
|
|
49
|
+
console.log('User authenticated:', userId);
|
|
50
|
+
},
|
|
51
|
+
(error) => {
|
|
52
|
+
// Handle authentication error
|
|
53
|
+
console.error('Portal error:', error);
|
|
54
|
+
},
|
|
55
|
+
() => {
|
|
56
|
+
// Portal was closed
|
|
57
|
+
console.log('Portal closed');
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Option B: Get portal URL and redirect**
|
|
33
63
|
|
|
34
|
-
|
|
35
|
-
|
|
64
|
+
```typescript
|
|
65
|
+
const portalUrl = await finatic.getPortalUrl(
|
|
66
|
+
'dark', // Theme
|
|
67
|
+
['alpaca'], // Optional: Filter brokers
|
|
68
|
+
'user@example.com', // Optional: Pre-fill email
|
|
69
|
+
'dark' // Mode
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// Redirect user to portal URL
|
|
73
|
+
window.location.href = portalUrl;
|
|
74
|
+
```
|
|
36
75
|
|
|
37
|
-
|
|
38
|
-
npm run lint
|
|
76
|
+
### 3. Check Authentication Status
|
|
39
77
|
|
|
40
|
-
|
|
41
|
-
|
|
78
|
+
```typescript
|
|
79
|
+
// Check if user is authenticated
|
|
80
|
+
const isAuthenticated = finatic.isAuthed();
|
|
81
|
+
|
|
82
|
+
// Get current user ID
|
|
83
|
+
const userId = finatic.getUserId();
|
|
42
84
|
```
|
|
43
85
|
|
|
44
|
-
|
|
86
|
+
### 4. Fetch Data
|
|
45
87
|
|
|
46
|
-
|
|
88
|
+
Once authenticated, you can fetch broker data:
|
|
47
89
|
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
|
|
90
|
+
```typescript
|
|
91
|
+
// Get all orders (automatically paginates through all pages)
|
|
92
|
+
const allOrders = await finatic.getAllOrders();
|
|
93
|
+
|
|
94
|
+
// Get orders with filters (single page)
|
|
95
|
+
const orders = await finatic.getOrders({
|
|
96
|
+
brokerId: 'alpaca',
|
|
97
|
+
accountId: '123456789',
|
|
98
|
+
orderStatus: 'filled',
|
|
99
|
+
limit: 100,
|
|
100
|
+
offset: 0,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Get all positions
|
|
104
|
+
const allPositions = await finatic.getAllPositions();
|
|
105
|
+
|
|
106
|
+
// Get positions with filters
|
|
107
|
+
const positions = await finatic.getPositions({
|
|
108
|
+
brokerId: 'alpaca',
|
|
109
|
+
symbol: 'AAPL',
|
|
110
|
+
limit: 50,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Get all accounts
|
|
114
|
+
const allAccounts = await finatic.getAllAccounts();
|
|
115
|
+
|
|
116
|
+
// Get balances
|
|
117
|
+
const balances = await finatic.getBalances({
|
|
118
|
+
brokerId: 'alpaca',
|
|
119
|
+
accountId: '123456789',
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Complete Example
|
|
51
124
|
|
|
52
|
-
|
|
53
|
-
|
|
125
|
+
```typescript
|
|
126
|
+
import { FinaticConnect } from '@finatic/client';
|
|
127
|
+
|
|
128
|
+
async function setupFinatic() {
|
|
129
|
+
// 1. Initialize SDK (get token from your backend)
|
|
130
|
+
const token = await fetch('/api/finatic/token').then((r) => r.text());
|
|
131
|
+
const finatic = await FinaticConnect.init(token);
|
|
132
|
+
|
|
133
|
+
// 2. Open portal for authentication
|
|
134
|
+
await finatic.openPortal(
|
|
135
|
+
'dark',
|
|
136
|
+
undefined, // Show all brokers
|
|
137
|
+
undefined, // No pre-filled email
|
|
138
|
+
'modal',
|
|
139
|
+
async (userId) => {
|
|
140
|
+
console.log('User authenticated:', userId);
|
|
141
|
+
|
|
142
|
+
// 3. Fetch data after authentication
|
|
143
|
+
const orders = await finatic.getAllOrders();
|
|
144
|
+
const positions = await finatic.getAllPositions();
|
|
145
|
+
const accounts = await finatic.getAllAccounts();
|
|
146
|
+
|
|
147
|
+
console.log('Orders:', orders);
|
|
148
|
+
console.log('Positions:', positions);
|
|
149
|
+
console.log('Accounts:', accounts);
|
|
150
|
+
},
|
|
151
|
+
(error) => {
|
|
152
|
+
console.error('Authentication failed:', error);
|
|
153
|
+
}
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
setupFinatic();
|
|
54
158
|
```
|
|
55
159
|
|
|
56
|
-
|
|
160
|
+
## Available Data Methods
|
|
57
161
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
162
|
+
### Orders
|
|
163
|
+
|
|
164
|
+
- `getOrders(params?)` - Get single page of orders
|
|
165
|
+
- `getAllOrders(params?)` - Get all orders (auto-paginated)
|
|
166
|
+
- `getOrderFills({ orderId, ... })` - Get fills for a specific order
|
|
167
|
+
- `getAllOrderFills({ orderId, ... })` - Get all fills (auto-paginated)
|
|
168
|
+
- `getOrderEvents({ orderId, ... })` - Get events for a specific order
|
|
169
|
+
- `getAllOrderEvents({ orderId, ... })` - Get all events (auto-paginated)
|
|
170
|
+
- `getOrderGroups(params?)` - Get order groups
|
|
171
|
+
- `getAllOrderGroups(params?)` - Get all order groups (auto-paginated)
|
|
172
|
+
|
|
173
|
+
### Positions
|
|
174
|
+
|
|
175
|
+
- `getPositions(params?)` - Get single page of positions
|
|
176
|
+
- `getAllPositions(params?)` - Get all positions (auto-paginated)
|
|
177
|
+
- `getPositionLots(params?)` - Get position lots
|
|
178
|
+
- `getAllPositionLots(params?)` - Get all position lots (auto-paginated)
|
|
179
|
+
- `getPositionLotFills({ lotId, ... })` - Get fills for a specific lot
|
|
180
|
+
- `getAllPositionLotFills({ lotId, ... })` - Get all fills (auto-paginated)
|
|
181
|
+
|
|
182
|
+
### Accounts & Balances
|
|
183
|
+
|
|
184
|
+
- `getAccounts(params?)` - Get single page of accounts
|
|
185
|
+
- `getAllAccounts(params?)` - Get all accounts (auto-paginated)
|
|
186
|
+
- `getBalances(params?)` - Get balances
|
|
187
|
+
- `getAllBalances(params?)` - Get all balances (auto-paginated)
|
|
63
188
|
|
|
64
|
-
|
|
65
|
-
npm run lint
|
|
66
|
-
# Lint fix (modifies files)
|
|
67
|
-
npm run lint:fix
|
|
189
|
+
### Broker Management
|
|
68
190
|
|
|
69
|
-
|
|
70
|
-
|
|
191
|
+
- `getBrokers()` - Get available brokers
|
|
192
|
+
- `getBrokerConnections()` - Get connected broker accounts
|
|
193
|
+
- `disconnectCompanyFromBroker({ connectionId })` - Disconnect a broker
|
|
71
194
|
|
|
72
|
-
|
|
73
|
-
npm run syntax:check
|
|
74
|
-
# Syntax fix (modifies files)
|
|
75
|
-
npm run syntax:fix
|
|
195
|
+
### Company
|
|
76
196
|
|
|
77
|
-
|
|
78
|
-
npm run import:check
|
|
197
|
+
- `getCompany({ companyId })` - Get company information
|
|
79
198
|
|
|
80
|
-
|
|
81
|
-
|
|
199
|
+
## Method Parameters
|
|
200
|
+
|
|
201
|
+
Most data methods accept optional parameters:
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
{
|
|
205
|
+
brokerId?: string; // Filter by broker (e.g., 'alpaca', 'tradier')
|
|
206
|
+
connectionId?: string; // Filter by connection ID
|
|
207
|
+
accountId?: string; // Filter by account ID
|
|
208
|
+
symbol?: string; // Filter by symbol (e.g., 'AAPL')
|
|
209
|
+
limit?: number; // Page size (default: varies by endpoint)
|
|
210
|
+
offset?: number; // Pagination offset (default: 0)
|
|
211
|
+
includeMetadata?: boolean; // Include metadata in response
|
|
212
|
+
// ... other filters specific to each method
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Event Handling
|
|
217
|
+
|
|
218
|
+
The SDK extends EventEmitter, so you can listen to events:
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
// Listen for portal events
|
|
222
|
+
finatic.on('portal:success', (userId: string) => {
|
|
223
|
+
console.log('Portal authentication successful:', userId);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
finatic.on('portal:error', (error: Error) => {
|
|
227
|
+
console.error('Portal error:', error);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
finatic.on('portal:close', () => {
|
|
231
|
+
console.log('Portal closed');
|
|
232
|
+
});
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Response Format
|
|
236
|
+
|
|
237
|
+
All data methods return a `FinaticResponse` object:
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
{
|
|
241
|
+
success: {
|
|
242
|
+
data: T[], // Array of data items
|
|
243
|
+
// ... other metadata
|
|
244
|
+
},
|
|
245
|
+
error?: {
|
|
246
|
+
message: string;
|
|
247
|
+
code?: string;
|
|
248
|
+
},
|
|
249
|
+
warning?: string[]
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Access the data:
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
const result = await finatic.getOrders();
|
|
257
|
+
if (result.success) {
|
|
258
|
+
const orders = result.success.data;
|
|
259
|
+
// Use orders...
|
|
260
|
+
} else {
|
|
261
|
+
console.error('Error:', result.error?.message);
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Configuration Options
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
const finatic = await FinaticConnect.init(token, userId, {
|
|
269
|
+
baseUrl: 'https://api.finatic.dev', // API base URL
|
|
270
|
+
logLevel: 'info', // 'debug' | 'info' | 'warn' | 'error'
|
|
271
|
+
structuredLogging: false, // Enable structured JSON logging
|
|
272
|
+
// ... other options
|
|
273
|
+
});
|
|
82
274
|
```
|
|
83
275
|
|
|
276
|
+
## Documentation
|
|
277
|
+
|
|
278
|
+
Full API documentation is available at [https://finatic.dev/docs](https://finatic.dev/doc).
|
|
279
|
+
|
|
84
280
|
## License
|
|
85
281
|
|
|
86
|
-
|
|
282
|
+
PROPRIETARY
|
|
87
283
|
|
|
88
284
|
## Copyright
|
|
89
285
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as axios from 'axios';
|
|
2
2
|
import { AxiosInstance, RawAxiosRequestConfig, AxiosPromise, AxiosRequestConfig, AxiosResponse } from 'axios';
|
|
3
3
|
import * as z from 'zod';
|
|
4
|
-
import NodeCache from 'node-cache';
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* Finatic FastAPI Backend
|
|
@@ -4375,12 +4374,11 @@ declare const defaultConfig: SdkConfig;
|
|
|
4375
4374
|
declare function getConfig(overrides?: Partial<SdkConfig>): SdkConfig;
|
|
4376
4375
|
|
|
4377
4376
|
/**
|
|
4378
|
-
* Structured logger utility with browser-safe
|
|
4377
|
+
* Structured logger utility with browser-safe console logging (Phase 2C).
|
|
4379
4378
|
*
|
|
4380
4379
|
* Generated - do not edit directly.
|
|
4381
4380
|
*
|
|
4382
|
-
* This logger
|
|
4383
|
-
* in browser environments (Vite, Next.js, etc.).
|
|
4381
|
+
* This logger uses browser console APIs for all logging in browser environments.
|
|
4384
4382
|
*/
|
|
4385
4383
|
|
|
4386
4384
|
type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';
|
|
@@ -5700,15 +5698,23 @@ declare function numberSchema(min?: number, max?: number, defaultVal?: number):
|
|
|
5700
5698
|
declare function stringSchema(min?: number, max?: number, defaultVal?: string): z.ZodString;
|
|
5701
5699
|
|
|
5702
5700
|
/**
|
|
5703
|
-
* Response caching utility with
|
|
5701
|
+
* Response caching utility with browser-compatible Map-based cache (Phase 2B).
|
|
5704
5702
|
*
|
|
5705
5703
|
* Generated - do not edit directly.
|
|
5706
5704
|
*/
|
|
5707
5705
|
|
|
5706
|
+
interface BrowserCache {
|
|
5707
|
+
get(key: string): any | undefined;
|
|
5708
|
+
set(key: string, value: any, ttl?: number): boolean;
|
|
5709
|
+
del(key: string): number;
|
|
5710
|
+
clear(): void;
|
|
5711
|
+
keys(): string[];
|
|
5712
|
+
has(key: string): boolean;
|
|
5713
|
+
}
|
|
5708
5714
|
/**
|
|
5709
5715
|
* Get or create cache instance.
|
|
5710
5716
|
*/
|
|
5711
|
-
declare function getCache(config?: SdkConfig):
|
|
5717
|
+
declare function getCache(config?: SdkConfig): BrowserCache | null;
|
|
5712
5718
|
/**
|
|
5713
5719
|
* Generate cache key from method and parameters.
|
|
5714
5720
|
*/
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var pRetry = require('p-retry');
|
|
4
|
-
var pino = require('pino');
|
|
5
|
-
var NodeCache = require('node-cache');
|
|
6
4
|
var z = require('zod');
|
|
7
5
|
var globalAxios = require('axios');
|
|
8
6
|
|
|
@@ -93,14 +91,12 @@ async function retryApiCall(fn, options = {}, config) {
|
|
|
93
91
|
}
|
|
94
92
|
|
|
95
93
|
/**
|
|
96
|
-
* Structured logger utility with browser-safe
|
|
94
|
+
* Structured logger utility with browser-safe console logging (Phase 2C).
|
|
97
95
|
*
|
|
98
96
|
* Generated - do not edit directly.
|
|
99
97
|
*
|
|
100
|
-
* This logger
|
|
101
|
-
* in browser environments (Vite, Next.js, etc.).
|
|
98
|
+
* This logger uses browser console APIs for all logging in browser environments.
|
|
102
99
|
*/
|
|
103
|
-
// @ts-ignore - pino types available via @types/pino
|
|
104
100
|
let _loggerInstance = null;
|
|
105
101
|
/**
|
|
106
102
|
* Get environment variable from various sources (browser and Node.js).
|
|
@@ -145,30 +141,6 @@ function isProduction() {
|
|
|
145
141
|
return true;
|
|
146
142
|
return false;
|
|
147
143
|
}
|
|
148
|
-
/**
|
|
149
|
-
* Detect if we're in a browser environment.
|
|
150
|
-
*/
|
|
151
|
-
function isBrowser() {
|
|
152
|
-
// Check for window object (browser)
|
|
153
|
-
if (typeof window !== 'undefined') {
|
|
154
|
-
return true;
|
|
155
|
-
}
|
|
156
|
-
// Check for globalThis in browser-like environments
|
|
157
|
-
if (typeof globalThis !== 'undefined') {
|
|
158
|
-
// In browsers, process might be polyfilled but process.stdout won't exist
|
|
159
|
-
try {
|
|
160
|
-
const proc = globalThis.process;
|
|
161
|
-
if (proc && typeof proc.stdout === 'undefined') {
|
|
162
|
-
return true;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
catch {
|
|
166
|
-
// If we can't check, assume browser if window exists
|
|
167
|
-
return typeof window !== 'undefined';
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
return false;
|
|
171
|
-
}
|
|
172
144
|
/**
|
|
173
145
|
* Get or create a logger instance with browser-safe fallback.
|
|
174
146
|
*/
|
|
@@ -176,36 +148,13 @@ function getLogger(config) {
|
|
|
176
148
|
if (_loggerInstance) {
|
|
177
149
|
return _loggerInstance;
|
|
178
150
|
}
|
|
179
|
-
//
|
|
180
|
-
|
|
181
|
-
return getBrowserSafeLogger(config);
|
|
182
|
-
}
|
|
183
|
-
// Try to use pino first (Node.js environments)
|
|
184
|
-
try {
|
|
185
|
-
const logLevel = (config?.logLevel || getEnvVar('FINATIC_LOG_LEVEL') || 'error');
|
|
186
|
-
const pinoConfig = {
|
|
187
|
-
level: logLevel === 'silent' ? 'silent' : logLevel,
|
|
188
|
-
...(config?.structuredLogging !== false && {
|
|
189
|
-
formatters: {
|
|
190
|
-
level: (label) => {
|
|
191
|
-
return { level: label };
|
|
192
|
-
},
|
|
193
|
-
},
|
|
194
|
-
timestamp: true,
|
|
195
|
-
}),
|
|
196
|
-
};
|
|
197
|
-
_loggerInstance = pino(pinoConfig);
|
|
198
|
-
return _loggerInstance;
|
|
199
|
-
}
|
|
200
|
-
catch (error) {
|
|
201
|
-
// Fallback to browser-safe logger if pino fails
|
|
202
|
-
return getBrowserSafeLogger(config, error);
|
|
203
|
-
}
|
|
151
|
+
// Client SDK always uses browser-safe logger (no pino)
|
|
152
|
+
return getBrowserSafeLogger(config);
|
|
204
153
|
}
|
|
205
154
|
/**
|
|
206
|
-
* Browser-safe logger
|
|
155
|
+
* Browser-safe logger for Client SDK.
|
|
207
156
|
*/
|
|
208
|
-
function getBrowserSafeLogger(config
|
|
157
|
+
function getBrowserSafeLogger(config) {
|
|
209
158
|
// Log level hierarchy (matching pino's numeric levels)
|
|
210
159
|
const LOG_LEVELS = {
|
|
211
160
|
silent: 0,
|
|
@@ -236,7 +185,7 @@ function getBrowserSafeLogger(config, pinoError) {
|
|
|
236
185
|
};
|
|
237
186
|
const logLevel = getEffectiveLogLevel();
|
|
238
187
|
const structuredLogging = config?.structuredLogging ?? false;
|
|
239
|
-
|
|
188
|
+
isProduction();
|
|
240
189
|
/**
|
|
241
190
|
* Check if we should log at this level.
|
|
242
191
|
*/
|
|
@@ -369,10 +318,6 @@ function getBrowserSafeLogger(config, pinoError) {
|
|
|
369
318
|
}
|
|
370
319
|
},
|
|
371
320
|
};
|
|
372
|
-
// Only warn about fallback logger if pino failed (not if we're in browser)
|
|
373
|
-
if (pinoError && !isProd) {
|
|
374
|
-
console.warn('[Finatic SDK] Using fallback logger due to pino initialization error:', pinoError);
|
|
375
|
-
}
|
|
376
321
|
_loggerInstance = fallbackLogger;
|
|
377
322
|
return _loggerInstance;
|
|
378
323
|
}
|
|
@@ -440,11 +385,96 @@ function handleError(error, requestId) {
|
|
|
440
385
|
}
|
|
441
386
|
|
|
442
387
|
/**
|
|
443
|
-
* Response caching utility with
|
|
388
|
+
* Response caching utility with browser-compatible Map-based cache (Phase 2B).
|
|
444
389
|
*
|
|
445
390
|
* Generated - do not edit directly.
|
|
446
391
|
*/
|
|
447
|
-
|
|
392
|
+
class MapBasedCache {
|
|
393
|
+
constructor(maxSize = 1000, defaultTtl = 300) {
|
|
394
|
+
this.cleanupInterval = null;
|
|
395
|
+
this.cache = new Map();
|
|
396
|
+
this.maxSize = maxSize;
|
|
397
|
+
this.defaultTtl = defaultTtl;
|
|
398
|
+
this.startCleanup();
|
|
399
|
+
}
|
|
400
|
+
startCleanup() {
|
|
401
|
+
// Clean up expired entries every minute
|
|
402
|
+
if (typeof window !== 'undefined') {
|
|
403
|
+
this.cleanupInterval = window.setInterval(() => {
|
|
404
|
+
this.cleanup();
|
|
405
|
+
}, 60000);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
cleanup() {
|
|
409
|
+
const now = Date.now();
|
|
410
|
+
const keysToDelete = [];
|
|
411
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
412
|
+
if (entry.expires < now) {
|
|
413
|
+
keysToDelete.push(key);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
keysToDelete.forEach(key => this.cache.delete(key));
|
|
417
|
+
}
|
|
418
|
+
evictLRU() {
|
|
419
|
+
if (this.cache.size < this.maxSize) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
// Simple LRU: remove oldest entry (first in Map)
|
|
423
|
+
const firstKey = this.cache.keys().next().value;
|
|
424
|
+
if (firstKey) {
|
|
425
|
+
this.cache.delete(firstKey);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
get(key) {
|
|
429
|
+
const entry = this.cache.get(key);
|
|
430
|
+
if (!entry) {
|
|
431
|
+
return undefined;
|
|
432
|
+
}
|
|
433
|
+
// Check if expired
|
|
434
|
+
if (entry.expires < Date.now()) {
|
|
435
|
+
this.cache.delete(key);
|
|
436
|
+
return undefined;
|
|
437
|
+
}
|
|
438
|
+
return entry.value;
|
|
439
|
+
}
|
|
440
|
+
set(key, value, ttl) {
|
|
441
|
+
// Evict if at max size
|
|
442
|
+
if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
|
|
443
|
+
this.evictLRU();
|
|
444
|
+
}
|
|
445
|
+
const expires = Date.now() + (ttl || this.defaultTtl) * 1000;
|
|
446
|
+
this.cache.set(key, { value, expires });
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
449
|
+
del(key) {
|
|
450
|
+
return this.cache.delete(key) ? 1 : 0;
|
|
451
|
+
}
|
|
452
|
+
clear() {
|
|
453
|
+
this.cache.clear();
|
|
454
|
+
}
|
|
455
|
+
keys() {
|
|
456
|
+
return Array.from(this.cache.keys());
|
|
457
|
+
}
|
|
458
|
+
has(key) {
|
|
459
|
+
const entry = this.cache.get(key);
|
|
460
|
+
if (!entry) {
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
// Check if expired
|
|
464
|
+
if (entry.expires < Date.now()) {
|
|
465
|
+
this.cache.delete(key);
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
470
|
+
destroy() {
|
|
471
|
+
if (this.cleanupInterval !== null && typeof window !== 'undefined') {
|
|
472
|
+
window.clearInterval(this.cleanupInterval);
|
|
473
|
+
this.cleanupInterval = null;
|
|
474
|
+
}
|
|
475
|
+
this.cache.clear();
|
|
476
|
+
}
|
|
477
|
+
}
|
|
448
478
|
let _cacheInstance = null;
|
|
449
479
|
/**
|
|
450
480
|
* Get or create cache instance.
|
|
@@ -456,11 +486,7 @@ function getCache(config) {
|
|
|
456
486
|
if (_cacheInstance) {
|
|
457
487
|
return _cacheInstance;
|
|
458
488
|
}
|
|
459
|
-
_cacheInstance = new
|
|
460
|
-
stdTTL: config.cacheTtl || 300,
|
|
461
|
-
maxKeys: config.cacheMaxSize || 1000,
|
|
462
|
-
useClones: false,
|
|
463
|
-
});
|
|
489
|
+
_cacheInstance = new MapBasedCache(config.cacheMaxSize || 1000, config.cacheTtl || 300);
|
|
464
490
|
return _cacheInstance;
|
|
465
491
|
}
|
|
466
492
|
/**
|