tradestation-client 1.0.11 → 1.0.12
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/authMiddleware.ts +1 -1
- package/cli.ts +2 -3
- package/dist/authMiddleware.d.ts +16 -0
- package/dist/authMiddleware.d.ts.map +1 -0
- package/dist/authMiddleware.js +156 -0
- package/dist/authMiddleware.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +10 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +3 -0
- package/dist/config.js.map +1 -0
- package/dist/downloadOpenAPI.d.ts +2 -0
- package/dist/downloadOpenAPI.d.ts.map +1 -0
- package/dist/downloadOpenAPI.js +25 -0
- package/dist/downloadOpenAPI.js.map +1 -0
- package/dist/spike.d.ts +2 -0
- package/dist/spike.d.ts.map +1 -0
- package/dist/spike.js +20 -0
- package/dist/spike.js.map +1 -0
- package/dist/tradestation-client.d.ts +3 -0
- package/dist/tradestation-client.d.ts.map +1 -0
- package/dist/tradestation-client.js +10 -0
- package/dist/tradestation-client.js.map +1 -0
- package/package.json +3 -4
- package/spike.ts +2 -2
- package/tradestation-client.ts +1 -1
- package/tsconfig.json +44 -8
package/authMiddleware.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { Middleware } from 'openapi-fetch'
|
|
|
5
5
|
import fastify from 'fastify'
|
|
6
6
|
import open from 'open'
|
|
7
7
|
|
|
8
|
-
import { redirectPort, redirectUri } from './config.
|
|
8
|
+
import { redirectPort, redirectUri } from './config.js'
|
|
9
9
|
|
|
10
10
|
type AuthResponse = {
|
|
11
11
|
access_token: string
|
package/cli.ts
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Middleware } from 'openapi-fetch';
|
|
2
|
+
type AuthResponse = {
|
|
3
|
+
access_token: string;
|
|
4
|
+
refresh_token: string;
|
|
5
|
+
id_token: string;
|
|
6
|
+
scope: string;
|
|
7
|
+
expires_in: number;
|
|
8
|
+
token_type: string;
|
|
9
|
+
};
|
|
10
|
+
type AuthData = AuthResponse & {
|
|
11
|
+
timestamp: number;
|
|
12
|
+
};
|
|
13
|
+
export declare function authenticate(clientId: string, clientSecret: string): Promise<AuthData>;
|
|
14
|
+
export declare function createAuthMiddleware(clientId: string, clientSecret: string): Middleware;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=authMiddleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authMiddleware.d.ts","sourceRoot":"","sources":["../authMiddleware.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAM/C,KAAK,YAAY,GAAG;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,KAAK,QAAQ,GAAG,YAAY,GAAG;IAC7B,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAyJD,wBAAsB,YAAY,CAChC,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,QAAQ,CAAC,CAkBnB;AAED,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,GACnB,UAAU,CAmCZ"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import fastify from 'fastify';
|
|
5
|
+
import open from 'open';
|
|
6
|
+
import { redirectPort, redirectUri } from './config.js';
|
|
7
|
+
const authFilePath = join(process.cwd(), 'tradestation-auth.json');
|
|
8
|
+
function generateRandomState() {
|
|
9
|
+
return randomBytes(32).toString('base64url');
|
|
10
|
+
}
|
|
11
|
+
async function tryLoadAuth() {
|
|
12
|
+
try {
|
|
13
|
+
const data = await readFile(authFilePath, 'utf-8');
|
|
14
|
+
return JSON.parse(data);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function saveAuth(authResponse) {
|
|
21
|
+
const authData = {
|
|
22
|
+
...authResponse,
|
|
23
|
+
timestamp: Date.now(),
|
|
24
|
+
};
|
|
25
|
+
await writeFile(authFilePath, JSON.stringify(authData, null, 2), 'utf-8');
|
|
26
|
+
return authData;
|
|
27
|
+
}
|
|
28
|
+
function isTokenValid(token) {
|
|
29
|
+
const now = Date.now();
|
|
30
|
+
const expiresAt = token.timestamp + token.expires_in * 1000;
|
|
31
|
+
// Consider token expired 30 seconds before actual expiration to account for clock skew
|
|
32
|
+
return now < expiresAt - 30000;
|
|
33
|
+
}
|
|
34
|
+
async function requestAccessToken(code, clientId, clientSecret) {
|
|
35
|
+
const authRes = await fetch('https://signin.tradestation.com/oauth/token', {
|
|
36
|
+
method: 'POST',
|
|
37
|
+
headers: {
|
|
38
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
39
|
+
},
|
|
40
|
+
body: new URLSearchParams({
|
|
41
|
+
grant_type: 'authorization_code',
|
|
42
|
+
code,
|
|
43
|
+
redirect_uri: redirectUri,
|
|
44
|
+
client_id: clientId,
|
|
45
|
+
client_secret: clientSecret,
|
|
46
|
+
}),
|
|
47
|
+
});
|
|
48
|
+
const authResponse = (await authRes.json());
|
|
49
|
+
if (!authRes.ok) {
|
|
50
|
+
throw new Error(`Failed to obtain access token: ${authResponse}`);
|
|
51
|
+
}
|
|
52
|
+
return saveAuth(authResponse);
|
|
53
|
+
}
|
|
54
|
+
async function refreshAccessToken(refreshToken, clientId, clientSecret) {
|
|
55
|
+
const authRes = await fetch('https://signin.tradestation.com/oauth/token', {
|
|
56
|
+
method: 'POST',
|
|
57
|
+
headers: {
|
|
58
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
59
|
+
},
|
|
60
|
+
body: new URLSearchParams({
|
|
61
|
+
grant_type: 'refresh_token',
|
|
62
|
+
client_id: clientId,
|
|
63
|
+
client_secret: clientSecret,
|
|
64
|
+
refresh_token: refreshToken,
|
|
65
|
+
}),
|
|
66
|
+
});
|
|
67
|
+
if (!authRes.ok) {
|
|
68
|
+
throw new Error(`Failed to refresh token: ${await authRes.text()}`);
|
|
69
|
+
}
|
|
70
|
+
const authResponse = (await authRes.json());
|
|
71
|
+
// Preserve the original refresh token if a new one wasn't provided
|
|
72
|
+
// (TradeStation doesn't rotate refresh tokens by default)
|
|
73
|
+
if (!authResponse.refresh_token) {
|
|
74
|
+
authResponse.refresh_token = refreshToken;
|
|
75
|
+
}
|
|
76
|
+
return saveAuth(authResponse);
|
|
77
|
+
}
|
|
78
|
+
async function authenticateWithOAuth2(clientId, clientSecret) {
|
|
79
|
+
const app = fastify();
|
|
80
|
+
const state = generateRandomState();
|
|
81
|
+
const authPromise = new Promise((resolve, reject) => {
|
|
82
|
+
app.get('/', async (request, reply) => {
|
|
83
|
+
const { code, state: callbackState } = request.query;
|
|
84
|
+
if (!code) {
|
|
85
|
+
reply.code(400);
|
|
86
|
+
return 'No code provided';
|
|
87
|
+
}
|
|
88
|
+
if (callbackState !== state) {
|
|
89
|
+
reply.code(401);
|
|
90
|
+
reject(new Error('State parameter mismatch - possible CSRF attack'));
|
|
91
|
+
return 'State validation failed';
|
|
92
|
+
}
|
|
93
|
+
resolve(await requestAccessToken(code, clientId, clientSecret));
|
|
94
|
+
setTimeout(() => {
|
|
95
|
+
app.close();
|
|
96
|
+
}, 100);
|
|
97
|
+
return 'Authorization successful! You can close this tab.';
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
await app.listen({ port: redirectPort });
|
|
101
|
+
console.log('Opening browser for authentication...');
|
|
102
|
+
await openAuthUrl(state, clientId);
|
|
103
|
+
return authPromise;
|
|
104
|
+
}
|
|
105
|
+
async function openAuthUrl(state, clientId) {
|
|
106
|
+
const authUrl = new URL('https://signin.tradestation.com/authorize');
|
|
107
|
+
authUrl.searchParams.append('response_type', 'code');
|
|
108
|
+
authUrl.searchParams.append('scope', 'openid offline_access profile MarketData ReadAccount Trade Matrix');
|
|
109
|
+
authUrl.searchParams.append('redirect_uri', redirectUri);
|
|
110
|
+
authUrl.searchParams.append('client_id', clientId);
|
|
111
|
+
authUrl.searchParams.append('state', state);
|
|
112
|
+
authUrl.searchParams.append('audience', 'https://api.tradestation.com');
|
|
113
|
+
await open(authUrl.toString());
|
|
114
|
+
}
|
|
115
|
+
export async function authenticate(clientId, clientSecret) {
|
|
116
|
+
const auth = await tryLoadAuth();
|
|
117
|
+
if (!auth) {
|
|
118
|
+
return authenticateWithOAuth2(clientId, clientSecret);
|
|
119
|
+
}
|
|
120
|
+
if (isTokenValid(auth)) {
|
|
121
|
+
return auth;
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
return await refreshAccessToken(auth.refresh_token, clientId, clientSecret);
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
console.error('Token refresh failed, starting OAuth flow:', error);
|
|
128
|
+
}
|
|
129
|
+
return authenticateWithOAuth2(clientId, clientSecret);
|
|
130
|
+
}
|
|
131
|
+
export function createAuthMiddleware(clientId, clientSecret) {
|
|
132
|
+
return {
|
|
133
|
+
async onRequest({ request }) {
|
|
134
|
+
const auth = await authenticate(clientId, clientSecret);
|
|
135
|
+
request.headers.set('Authorization', `Bearer ${auth.access_token}`);
|
|
136
|
+
return request;
|
|
137
|
+
},
|
|
138
|
+
async onResponse({ response, request }) {
|
|
139
|
+
if (response.status === 401) {
|
|
140
|
+
console.warn(`Received 401 Unauthorized for request to ${request.url}. Access token may be invalid or expired.`);
|
|
141
|
+
let auth = await tryLoadAuth();
|
|
142
|
+
if (!auth) {
|
|
143
|
+
throw new Error('No authentication data available to refresh token.');
|
|
144
|
+
}
|
|
145
|
+
auth = await refreshAccessToken(auth.refresh_token, clientId, clientSecret);
|
|
146
|
+
response = await fetch(new Request(request), {
|
|
147
|
+
headers: {
|
|
148
|
+
Authorization: `Bearer ${auth.access_token}`,
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
return response;
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=authMiddleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authMiddleware.js","sourceRoot":"","sources":["../authMiddleware.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env npx ts-node
|
|
2
|
+
import { program } from 'commander';
|
|
3
|
+
import { authenticate } from './authMiddleware.js';
|
|
4
|
+
program
|
|
5
|
+
.requiredOption('--clientId <clientId>')
|
|
6
|
+
.requiredOption('--clientSecret <clientSecret>');
|
|
7
|
+
program.parse();
|
|
8
|
+
const { clientId, clientSecret } = program.opts();
|
|
9
|
+
console.log(await authenticate(clientId, clientSecret));
|
|
10
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAElD,OAAO;KACJ,cAAc,CAAC,uBAAuB,CAAC;KACvC,cAAc,CAAC,+BAA+B,CAAC,CAAA;AAElD,OAAO,CAAC,KAAK,EAAE,CAAA;AAEf,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAA;AAEjD,OAAO,CAAC,GAAG,CAAC,MAAM,YAAY,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAA"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../config.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,QAAQ,CAAA;AACjC,eAAO,MAAM,WAAW,2BAAqC,CAAA"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../config.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,CAAA;AACjC,MAAM,CAAC,MAAM,WAAW,GAAG,oBAAoB,YAAY,EAAE,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"downloadOpenAPI.d.ts","sourceRoot":"","sources":["../downloadOpenAPI.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { copyFile, unlink } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { chromium } from 'playwright-core';
|
|
4
|
+
const browser = await chromium.launch({
|
|
5
|
+
headless: true,
|
|
6
|
+
channel: 'chrome',
|
|
7
|
+
args: ['--disable-extensions'],
|
|
8
|
+
});
|
|
9
|
+
const page = await browser.newPage();
|
|
10
|
+
await page.goto('https://api.tradestation.com/docs/specification/');
|
|
11
|
+
await page.waitForLoadState('networkidle');
|
|
12
|
+
const downloadPromise = page.waitForEvent('download');
|
|
13
|
+
await page.getByRole('link', { name: 'Download' }).click();
|
|
14
|
+
const download = await downloadPromise;
|
|
15
|
+
const downloadedPath = await download.path();
|
|
16
|
+
if (downloadedPath) {
|
|
17
|
+
await copyFile(downloadedPath, join(process.cwd(), 'openapi.json'));
|
|
18
|
+
await unlink(downloadedPath);
|
|
19
|
+
console.log('✅ OpenAPI spec downloaded to openapi.json');
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
throw new Error('Download failed');
|
|
23
|
+
}
|
|
24
|
+
await browser.close();
|
|
25
|
+
//# sourceMappingURL=downloadOpenAPI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"downloadOpenAPI.js","sourceRoot":"","sources":["../downloadOpenAPI.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAE1C,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC;IACpC,QAAQ,EAAE,IAAI;IACd,OAAO,EAAE,QAAQ;IACjB,IAAI,EAAE,CAAC,sBAAsB,CAAC;CAC/B,CAAC,CAAA;AACF,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAA;AAEpC,MAAM,IAAI,CAAC,IAAI,CAAC,kDAAkD,CAAC,CAAA;AAEnE,MAAM,IAAI,CAAC,gBAAgB,CAAC,aAAa,CAAC,CAAA;AAE1C,MAAM,eAAe,GAAG,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,CAAA;AAErD,MAAM,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,KAAK,EAAE,CAAA;AAE1D,MAAM,QAAQ,GAAG,MAAM,eAAe,CAAA;AACtC,MAAM,cAAc,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;AAE5C,IAAI,cAAc,EAAE,CAAC;IACnB,MAAM,QAAQ,CAAC,cAAc,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,cAAc,CAAC,CAAC,CAAA;IACnE,MAAM,MAAM,CAAC,cAAc,CAAC,CAAA;IAC5B,OAAO,CAAC,GAAG,CAAC,2CAA2C,CAAC,CAAA;AAC1D,CAAC;KAAM,CAAC;IACN,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAA;AACpC,CAAC;AAED,MAAM,OAAO,CAAC,KAAK,EAAE,CAAA"}
|
package/dist/spike.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spike.d.ts","sourceRoot":"","sources":["../spike.ts"],"names":[],"mappings":""}
|
package/dist/spike.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
process.loadEnvFile();
|
|
3
|
+
import { createTradeStationClient } from './tradestation-client.js';
|
|
4
|
+
const tradestationClient = createTradeStationClient(process.env.CLIENT_ID, process.env.CLIENT_SECRET);
|
|
5
|
+
const { data } = await tradestationClient.GET('/v3/marketdata/stream/barcharts/{symbol}', {
|
|
6
|
+
params: {
|
|
7
|
+
path: { symbol: 'SPY' },
|
|
8
|
+
query: {
|
|
9
|
+
interval: '1',
|
|
10
|
+
unit: 'minute',
|
|
11
|
+
barsback: '2',
|
|
12
|
+
sessiontemplate: 'USEQ24Hour',
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
parseAs: 'stream',
|
|
16
|
+
});
|
|
17
|
+
for await (const chunk of data) {
|
|
18
|
+
console.log(new TextDecoder().decode(chunk));
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=spike.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spike.js","sourceRoot":"","sources":["../spike.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,cAAc,CAAA;AAElC,OAAO,CAAC,WAAW,EAAE,CAAA;AAErB,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAA;AAEnE,MAAM,kBAAkB,GAAG,wBAAwB,CACjD,OAAO,CAAC,GAAG,CAAC,SAAU,EACtB,OAAO,CAAC,GAAG,CAAC,aAAc,CAC3B,CAAA;AAED,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,kBAAkB,CAAC,GAAG,CAC3C,0CAA0C,EAC1C;IACE,MAAM,EAAE;QACN,IAAI,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE;QACvB,KAAK,EAAE;YACL,QAAQ,EAAE,GAAG;YACb,IAAI,EAAE,QAAQ;YACd,QAAQ,EAAE,GAAG;YACb,eAAe,EAAE,YAAY;SAC9B;KACF;IACD,OAAO,EAAE,QAAQ;CAClB,CACF,CAAA;AAED,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,IAAK,EAAE,CAAC;IAChC,OAAO,CAAC,GAAG,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;AAC9C,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tradestation-client.d.ts","sourceRoot":"","sources":["../tradestation-client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAA;AAG1C,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,gEAOrB"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import createClient from 'openapi-fetch';
|
|
2
|
+
import { createAuthMiddleware } from './authMiddleware.js';
|
|
3
|
+
export function createTradeStationClient(clientId, clientSecret) {
|
|
4
|
+
const client = createClient({
|
|
5
|
+
baseUrl: 'https://api.tradestation.com',
|
|
6
|
+
});
|
|
7
|
+
client.use(createAuthMiddleware(clientId, clientSecret));
|
|
8
|
+
return client;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=tradestation-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tradestation-client.js","sourceRoot":"","sources":["../tradestation-client.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,eAAe,CAAA;AAGxC,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAE1D,MAAM,UAAU,wBAAwB,CACtC,QAAgB,EAChB,YAAoB;IAEpB,MAAM,MAAM,GAAG,YAAY,CAAQ;QACjC,OAAO,EAAE,8BAA8B;KACxC,CAAC,CAAA;IACF,MAAM,CAAC,GAAG,CAAC,oBAAoB,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAA;IACxD,OAAO,MAAM,CAAA;AACf,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tradestation-client",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.12",
|
|
4
4
|
"description": "A Node.js client for the TradeStation API with OAuth2 authentication and OpenAPI integration.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"bin": "./cli.
|
|
6
|
+
"bin": "./dist/cli.js",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"test:ts": "tsc --noEmit",
|
|
9
9
|
"download-openapi": "node ./downloadOpenAPI.ts",
|
|
@@ -15,8 +15,7 @@
|
|
|
15
15
|
"commander": "^14.0.2",
|
|
16
16
|
"fastify": "^5.6.2",
|
|
17
17
|
"open": "^11.0.0",
|
|
18
|
-
"openapi-fetch": "^0.15.0"
|
|
19
|
-
"ts-node": "^10.9.2"
|
|
18
|
+
"openapi-fetch": "^0.15.0"
|
|
20
19
|
},
|
|
21
20
|
"devDependencies": {
|
|
22
21
|
"@types/node": "^25.0.3",
|
package/spike.ts
CHANGED
|
@@ -2,7 +2,7 @@ import process from 'node:process'
|
|
|
2
2
|
|
|
3
3
|
process.loadEnvFile()
|
|
4
4
|
|
|
5
|
-
import { createTradeStationClient } from './tradestation-client.
|
|
5
|
+
import { createTradeStationClient } from './tradestation-client.js'
|
|
6
6
|
|
|
7
7
|
const tradestationClient = createTradeStationClient(
|
|
8
8
|
process.env.CLIENT_ID!,
|
|
@@ -25,6 +25,6 @@ const { data } = await tradestationClient.GET(
|
|
|
25
25
|
}
|
|
26
26
|
)
|
|
27
27
|
|
|
28
|
-
for await (const chunk of data) {
|
|
28
|
+
for await (const chunk of data!) {
|
|
29
29
|
console.log(new TextDecoder().decode(chunk))
|
|
30
30
|
}
|
package/tradestation-client.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import createClient from 'openapi-fetch'
|
|
2
2
|
|
|
3
3
|
import type { paths } from './client.d.ts'
|
|
4
|
-
import { createAuthMiddleware } from './authMiddleware.
|
|
4
|
+
import { createAuthMiddleware } from './authMiddleware.js'
|
|
5
5
|
|
|
6
6
|
export function createTradeStationClient(
|
|
7
7
|
clientId: string,
|
package/tsconfig.json
CHANGED
|
@@ -1,8 +1,44 @@
|
|
|
1
|
-
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
{
|
|
2
|
+
// Visit https://aka.ms/tsconfig to read more about this file
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
// File Layout
|
|
5
|
+
"rootDir": ".",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
|
|
8
|
+
// Environment Settings
|
|
9
|
+
// See also https://aka.ms/tsconfig/module
|
|
10
|
+
"module": "nodenext",
|
|
11
|
+
"target": "esnext",
|
|
12
|
+
"types": [],
|
|
13
|
+
// For nodejs:
|
|
14
|
+
// "lib": ["esnext"],
|
|
15
|
+
// "types": ["node"],
|
|
16
|
+
// and npm install -D @types/node
|
|
17
|
+
|
|
18
|
+
// Other Outputs
|
|
19
|
+
"sourceMap": true,
|
|
20
|
+
"declaration": true,
|
|
21
|
+
"declarationMap": true,
|
|
22
|
+
|
|
23
|
+
// Stricter Typechecking Options
|
|
24
|
+
"noUncheckedIndexedAccess": true,
|
|
25
|
+
"exactOptionalPropertyTypes": true,
|
|
26
|
+
|
|
27
|
+
// Style Options
|
|
28
|
+
// "noImplicitReturns": true,
|
|
29
|
+
// "noImplicitOverride": true,
|
|
30
|
+
// "noUnusedLocals": true,
|
|
31
|
+
// "noUnusedParameters": true,
|
|
32
|
+
// "noFallthroughCasesInSwitch": true,
|
|
33
|
+
// "noPropertyAccessFromIndexSignature": true,
|
|
34
|
+
|
|
35
|
+
// Recommended Options
|
|
36
|
+
"strict": true,
|
|
37
|
+
"jsx": "react-jsx",
|
|
38
|
+
"verbatimModuleSyntax": true,
|
|
39
|
+
"isolatedModules": true,
|
|
40
|
+
"noUncheckedSideEffectImports": true,
|
|
41
|
+
"moduleDetection": "force",
|
|
42
|
+
"skipLibCheck": true
|
|
43
|
+
}
|
|
44
|
+
}
|