@bizjaya/finclient 0.0.0-stage → 0.2.0
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 +21 -0
- package/README.md +78 -2
- package/dist/index.cjs +727 -0
- package/dist/index.d.cts +7511 -0
- package/dist/index.d.ts +7511 -0
- package/dist/index.js +691 -0
- package/dist/webhook.cjs +49 -0
- package/dist/webhook.d.cts +11 -0
- package/dist/webhook.d.ts +11 -0
- package/dist/webhook.js +24 -0
- package/package.json +67 -4
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,727 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AccountsResource: () => AccountsResource,
|
|
24
|
+
ChangesResource: () => ChangesResource,
|
|
25
|
+
FinApiError: () => FinApiError,
|
|
26
|
+
FinClient: () => FinClient,
|
|
27
|
+
FinHttpTransport: () => FinHttpTransport,
|
|
28
|
+
FxResource: () => FxResource,
|
|
29
|
+
OrdersResource: () => OrdersResource,
|
|
30
|
+
WebhooksResource: () => WebhooksResource,
|
|
31
|
+
readAllChanges: () => readAllChanges,
|
|
32
|
+
readChangePages: () => readChangePages
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(index_exports);
|
|
35
|
+
|
|
36
|
+
// src/change-feed.ts
|
|
37
|
+
async function* readChangePages(readPage, cursor, signal) {
|
|
38
|
+
const seen = /* @__PURE__ */ new Set();
|
|
39
|
+
if (cursor) seen.add(cursor);
|
|
40
|
+
for (; ; ) {
|
|
41
|
+
if (signal?.aborted) throw signal.reason;
|
|
42
|
+
const page = await readPage(cursor, signal);
|
|
43
|
+
yield page;
|
|
44
|
+
if (!page.More) return;
|
|
45
|
+
if (!page.Cursor || page.Cursor === cursor || seen.has(page.Cursor)) {
|
|
46
|
+
throw new Error("The FinCopy change feed returned More without a new cursor.");
|
|
47
|
+
}
|
|
48
|
+
seen.add(page.Cursor);
|
|
49
|
+
cursor = page.Cursor;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function* readAllChanges(readPage, cursor, signal) {
|
|
53
|
+
for await (const page of readChangePages(readPage, cursor, signal)) {
|
|
54
|
+
for (const change of page.Changes ?? []) yield change;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/api-error.ts
|
|
59
|
+
var FinApiError = class extends Error {
|
|
60
|
+
/** The HTTP status code returned by the API, or 0 when no response arrived (timeout or network failure). */
|
|
61
|
+
statusCode;
|
|
62
|
+
/** The FinCopy response code, when present. */
|
|
63
|
+
code;
|
|
64
|
+
/** Validation and service errors returned by the API. */
|
|
65
|
+
errors;
|
|
66
|
+
/** Set by the SDK for timeouts, network failures, rate limits and malformed responses; null for other API errors. */
|
|
67
|
+
errorCode;
|
|
68
|
+
/** How long the API asked the caller to wait (the Retry-After header), in milliseconds, when it said. */
|
|
69
|
+
retryAfterMs;
|
|
70
|
+
constructor(statusCode, message, code = null, errors = [], details = {}) {
|
|
71
|
+
super(message || "The FinCopy API request failed.", details.cause === void 0 ? void 0 : { cause: details.cause });
|
|
72
|
+
this.name = "FinApiError";
|
|
73
|
+
this.statusCode = statusCode;
|
|
74
|
+
this.code = code;
|
|
75
|
+
this.errors = errors;
|
|
76
|
+
this.errorCode = details.errorCode ?? null;
|
|
77
|
+
this.retryAfterMs = details.retryAfterMs ?? null;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// src/sse.ts
|
|
82
|
+
var FinSseParser = class {
|
|
83
|
+
eventName = null;
|
|
84
|
+
data = [];
|
|
85
|
+
/** Adds one line (without its line break). Returns the event the line completed, if any. */
|
|
86
|
+
add(line) {
|
|
87
|
+
if (!line) return this.flush();
|
|
88
|
+
if (line.startsWith(":")) return null;
|
|
89
|
+
const separator = line.indexOf(":");
|
|
90
|
+
const field = separator < 0 ? line : line.slice(0, separator);
|
|
91
|
+
const value = separator < 0 ? "" : line.slice(separator + 1).replace(/^ /, "");
|
|
92
|
+
if (field === "event") this.eventName = value;
|
|
93
|
+
else if (field === "data") this.data.push(value);
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
/** Ends the stream: an event still being read is returned when it carries data. */
|
|
97
|
+
complete() {
|
|
98
|
+
return this.data.length ? this.flush() : null;
|
|
99
|
+
}
|
|
100
|
+
flush() {
|
|
101
|
+
const item = this.data.length ? { event: this.eventName ?? "message", data: this.data.join("\n") } : null;
|
|
102
|
+
this.eventName = null;
|
|
103
|
+
this.data = [];
|
|
104
|
+
return item;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// src/http-transport.ts
|
|
109
|
+
var retryableStatuses = /* @__PURE__ */ new Set([429, 502, 503, 504]);
|
|
110
|
+
var writeMethods = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
111
|
+
var FinHttpTransport = class {
|
|
112
|
+
apiKey;
|
|
113
|
+
baseUrl;
|
|
114
|
+
credentials;
|
|
115
|
+
timeoutMs;
|
|
116
|
+
maxRetries;
|
|
117
|
+
fetcher;
|
|
118
|
+
delay;
|
|
119
|
+
random;
|
|
120
|
+
constructor(options) {
|
|
121
|
+
this.apiKey = options.apiKey?.trim() ?? "";
|
|
122
|
+
if (!this.apiKey && !options.fetch) throw new TypeError("apiKey is required unless a custom fetch authenticates the member session.");
|
|
123
|
+
this.baseUrl = (options.baseUrl ?? "https://api.fincopy.pro").replace(/\/+$/, "");
|
|
124
|
+
const base = new URL(this.baseUrl);
|
|
125
|
+
if (!/^https?:$/.test(base.protocol) || base.search || base.hash) {
|
|
126
|
+
throw new TypeError("baseUrl must be an absolute HTTP or HTTPS URL without a query or fragment.");
|
|
127
|
+
}
|
|
128
|
+
this.timeoutMs = options.timeoutMs ?? 3e4;
|
|
129
|
+
this.maxRetries = options.maxRetries ?? 3;
|
|
130
|
+
if (!Number.isFinite(this.timeoutMs) || this.timeoutMs <= 0) throw new RangeError("timeoutMs must be positive.");
|
|
131
|
+
if (!Number.isInteger(this.maxRetries) || this.maxRetries < 0) throw new RangeError("maxRetries cannot be negative.");
|
|
132
|
+
this.fetcher = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
133
|
+
this.credentials = options.credentials ?? (this.apiKey ? "same-origin" : "include");
|
|
134
|
+
this.delay = options.retryDelay ?? ((milliseconds, signal) => new Promise((resolve, reject) => {
|
|
135
|
+
if (signal?.aborted) {
|
|
136
|
+
reject(signal.reason);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const onAbort = () => {
|
|
140
|
+
clearTimeout(timer);
|
|
141
|
+
reject(signal?.reason);
|
|
142
|
+
};
|
|
143
|
+
const timer = setTimeout(() => {
|
|
144
|
+
signal?.removeEventListener("abort", onAbort);
|
|
145
|
+
resolve();
|
|
146
|
+
}, milliseconds);
|
|
147
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
148
|
+
if (signal?.aborted) onAbort();
|
|
149
|
+
}));
|
|
150
|
+
this.random = options.random ?? Math.random;
|
|
151
|
+
}
|
|
152
|
+
async request(method, path, body, requestOptions = {}) {
|
|
153
|
+
const envelope = await this.sendEnvelope(method, path, body, requestOptions);
|
|
154
|
+
return envelope.data;
|
|
155
|
+
}
|
|
156
|
+
async sendEnvelope(method, path, body, requestOptions = {}) {
|
|
157
|
+
const verb = method.toUpperCase();
|
|
158
|
+
const writes = writeMethods.has(verb);
|
|
159
|
+
const bodyText = body === void 0 ? void 0 : JSON.stringify(body, (_key, value) => value === null ? void 0 : value);
|
|
160
|
+
const idempotencyKey = writes ? idempotencyKeyOf(requestOptions.idempotencyKey) : void 0;
|
|
161
|
+
const url = new URL(path.replace(/^\//, ""), `${this.baseUrl}/`);
|
|
162
|
+
for (let retry = 0; ; retry++) {
|
|
163
|
+
if (requestOptions.signal?.aborted) throw requestOptions.signal.reason;
|
|
164
|
+
const controller = new AbortController();
|
|
165
|
+
const onAbort = () => controller.abort(requestOptions.signal?.reason);
|
|
166
|
+
requestOptions.signal?.addEventListener("abort", onAbort, { once: true });
|
|
167
|
+
if (requestOptions.signal?.aborted) onAbort();
|
|
168
|
+
let timedOut = false;
|
|
169
|
+
const timer = setTimeout(() => {
|
|
170
|
+
timedOut = true;
|
|
171
|
+
controller.abort(new Error("FinCopy request timed out."));
|
|
172
|
+
}, this.timeoutMs);
|
|
173
|
+
let response;
|
|
174
|
+
let raw;
|
|
175
|
+
try {
|
|
176
|
+
const headers = new Headers({ Accept: "application/json" });
|
|
177
|
+
if (this.apiKey) headers.set("X-Api-Key", this.apiKey);
|
|
178
|
+
if (bodyText !== void 0) headers.set("Content-Type", "application/json");
|
|
179
|
+
if (idempotencyKey !== void 0) headers.set("Idempotency-Key", idempotencyKey);
|
|
180
|
+
response = await this.fetcher(url, {
|
|
181
|
+
method: verb,
|
|
182
|
+
headers,
|
|
183
|
+
body: bodyText,
|
|
184
|
+
signal: controller.signal,
|
|
185
|
+
credentials: this.credentials
|
|
186
|
+
});
|
|
187
|
+
raw = await response.text();
|
|
188
|
+
} catch (error) {
|
|
189
|
+
if (requestOptions.signal?.aborted) throw error;
|
|
190
|
+
if (retry >= this.maxRetries) {
|
|
191
|
+
throw timedOut ? new FinApiError(0, "The FinCopy API did not answer in time.", null, [], { errorCode: "timeout", cause: error }) : new FinApiError(0, "The FinCopy API could not be reached.", null, [], { errorCode: "network", cause: error });
|
|
192
|
+
}
|
|
193
|
+
await this.pause(this.backoff(retry), requestOptions.signal);
|
|
194
|
+
continue;
|
|
195
|
+
} finally {
|
|
196
|
+
clearTimeout(timer);
|
|
197
|
+
requestOptions.signal?.removeEventListener("abort", onAbort);
|
|
198
|
+
}
|
|
199
|
+
const serverWait = retryAfterMsOf(response.headers.get("Retry-After"));
|
|
200
|
+
if (retryableStatuses.has(response.status)) {
|
|
201
|
+
if (retry < this.maxRetries && (serverWait === null || serverWait <= maxServerRetryAfterMs)) {
|
|
202
|
+
await this.pause(serverWait ?? (response.status === 429 ? rateLimitBackoff(retry) : this.backoff(retry)), requestOptions.signal);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
let envelope = null;
|
|
207
|
+
try {
|
|
208
|
+
envelope = raw ? JSON.parse(raw) : null;
|
|
209
|
+
} catch {
|
|
210
|
+
}
|
|
211
|
+
if (!response.ok || !envelope || envelope.isOk !== true) {
|
|
212
|
+
const fallback = `The FinCopy API request failed with HTTP ${response.status}.`;
|
|
213
|
+
const message = typeof envelope?.message === "string" && envelope.message ? envelope.message : fallback;
|
|
214
|
+
const errorCode = response.status === 429 ? "rate_limited" : response.ok ? "invalid_response" : null;
|
|
215
|
+
throw new FinApiError(
|
|
216
|
+
response.status,
|
|
217
|
+
message,
|
|
218
|
+
envelope?.code ?? null,
|
|
219
|
+
errorsOf(envelope?.errors),
|
|
220
|
+
{ errorCode, retryAfterMs: serverWait }
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
if (!Object.prototype.hasOwnProperty.call(envelope, "data") || envelope.data === null) {
|
|
224
|
+
throw new FinApiError(
|
|
225
|
+
response.status,
|
|
226
|
+
"The FinCopy API returned an empty response.",
|
|
227
|
+
envelope.code ?? null,
|
|
228
|
+
errorsOf(envelope.errors),
|
|
229
|
+
{ errorCode: "empty_response" }
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
return envelope;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
async requestPage(method, path, requestOptions = {}) {
|
|
236
|
+
const envelope = await this.sendEnvelope(method, path, void 0, requestOptions);
|
|
237
|
+
if (!Array.isArray(envelope.data)) {
|
|
238
|
+
throw new FinApiError(200, "The FinCopy API returned an invalid page.", envelope.code ?? null, envelope.errors ?? []);
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
items: envelope.data,
|
|
242
|
+
paging: envelope.paging ?? null,
|
|
243
|
+
code: envelope.code ?? 200,
|
|
244
|
+
isOk: true,
|
|
245
|
+
message: envelope.message ?? null,
|
|
246
|
+
errors: envelope.errors ?? [],
|
|
247
|
+
chksum: envelope.chksum ?? null
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
/** Reads the event stream until the server closes it or the caller aborts. It does not reconnect. */
|
|
251
|
+
async *requestEvents(path, requestOptions = {}) {
|
|
252
|
+
const url = new URL(path.replace(/^\//, ""), `${this.baseUrl}/`);
|
|
253
|
+
const headers = new Headers({ Accept: "text/event-stream" });
|
|
254
|
+
if (this.apiKey) headers.set("X-Api-Key", this.apiKey);
|
|
255
|
+
let response;
|
|
256
|
+
try {
|
|
257
|
+
response = await this.fetcher(url, {
|
|
258
|
+
method: "GET",
|
|
259
|
+
headers,
|
|
260
|
+
signal: requestOptions.signal,
|
|
261
|
+
credentials: this.credentials
|
|
262
|
+
});
|
|
263
|
+
} catch (error) {
|
|
264
|
+
if (requestOptions.signal?.aborted) throw error;
|
|
265
|
+
throw new FinApiError(0, "The FinCopy event stream could not be opened.", null, [], { errorCode: "network", cause: error });
|
|
266
|
+
}
|
|
267
|
+
if (!response.ok) {
|
|
268
|
+
const raw = await response.text();
|
|
269
|
+
let envelope = null;
|
|
270
|
+
try {
|
|
271
|
+
envelope = raw ? JSON.parse(raw) : null;
|
|
272
|
+
} catch {
|
|
273
|
+
}
|
|
274
|
+
const fallback = `The FinCopy API request failed with HTTP ${response.status}.`;
|
|
275
|
+
throw new FinApiError(
|
|
276
|
+
response.status,
|
|
277
|
+
envelope?.message || fallback,
|
|
278
|
+
envelope?.code ?? null,
|
|
279
|
+
errorsOf(envelope?.errors),
|
|
280
|
+
{
|
|
281
|
+
errorCode: response.status === 429 ? "rate_limited" : null,
|
|
282
|
+
retryAfterMs: retryAfterMsOf(response.headers.get("Retry-After"))
|
|
283
|
+
}
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
if (!response.body) throw new FinApiError(response.status, "The FinCopy API returned an empty event stream.", null, [], { errorCode: "empty_response" });
|
|
287
|
+
const reader = response.body.getReader();
|
|
288
|
+
const decoder = new TextDecoder();
|
|
289
|
+
const parser = new FinSseParser();
|
|
290
|
+
let buffer = "";
|
|
291
|
+
try {
|
|
292
|
+
while (true) {
|
|
293
|
+
if (requestOptions.signal?.aborted) throw requestOptions.signal.reason;
|
|
294
|
+
let next;
|
|
295
|
+
try {
|
|
296
|
+
next = await reader.read();
|
|
297
|
+
} catch (error) {
|
|
298
|
+
if (requestOptions.signal?.aborted) throw error;
|
|
299
|
+
throw new FinApiError(0, "The FinCopy event stream was interrupted.", null, [], { errorCode: "network", cause: error });
|
|
300
|
+
}
|
|
301
|
+
buffer += decoder.decode(next.value, { stream: !next.done });
|
|
302
|
+
const lines = buffer.split(/\r?\n/);
|
|
303
|
+
buffer = lines.pop() ?? "";
|
|
304
|
+
for (const line of lines) {
|
|
305
|
+
const item = parser.add(line);
|
|
306
|
+
if (item) yield item;
|
|
307
|
+
}
|
|
308
|
+
if (next.done) {
|
|
309
|
+
if (buffer) {
|
|
310
|
+
const item = parser.add(buffer);
|
|
311
|
+
if (item) yield item;
|
|
312
|
+
}
|
|
313
|
+
const final = parser.complete();
|
|
314
|
+
if (final) yield final;
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
} finally {
|
|
319
|
+
await reader.cancel().catch(() => void 0);
|
|
320
|
+
reader.releaseLock();
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
backoff(retry) {
|
|
324
|
+
return Math.min(maxBackoffMs, 250 * 2 ** retry * (0.75 + this.random() * 0.5));
|
|
325
|
+
}
|
|
326
|
+
async pause(milliseconds, signal) {
|
|
327
|
+
if (signal?.aborted) throw signal.reason;
|
|
328
|
+
if (milliseconds > 0) await this.delay(milliseconds, signal);
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
var maxServerRetryAfterMs = 6e4;
|
|
332
|
+
var maxBackoffMs = 3e4;
|
|
333
|
+
function rateLimitBackoff(retry) {
|
|
334
|
+
return Math.min(maxBackoffMs, Math.max(1e3, 1e3 * 2 ** retry));
|
|
335
|
+
}
|
|
336
|
+
function retryAfterMsOf(header) {
|
|
337
|
+
const value = header?.trim();
|
|
338
|
+
if (!value) return null;
|
|
339
|
+
if (/^\d+$/.test(value)) return Number(value) * 1e3;
|
|
340
|
+
const at = Date.parse(value);
|
|
341
|
+
if (Number.isNaN(at)) return null;
|
|
342
|
+
const wait = at - Date.now();
|
|
343
|
+
return wait > 0 ? wait : null;
|
|
344
|
+
}
|
|
345
|
+
function errorsOf(errors) {
|
|
346
|
+
return Array.isArray(errors) ? errors.map((error) => typeof error === "string" ? error : JSON.stringify(error)) : [];
|
|
347
|
+
}
|
|
348
|
+
function idempotencyKeyOf(supplied) {
|
|
349
|
+
const key = supplied?.trim();
|
|
350
|
+
if (!key) return newIdempotencyKey();
|
|
351
|
+
if (key.length > 120 || /[\u0000-\u001f\u007f]/.test(key)) {
|
|
352
|
+
throw new TypeError("idempotencyKey must be 1-120 characters without control characters.");
|
|
353
|
+
}
|
|
354
|
+
return key;
|
|
355
|
+
}
|
|
356
|
+
function newIdempotencyKey() {
|
|
357
|
+
const webCrypto = globalThis.crypto;
|
|
358
|
+
if (typeof webCrypto?.randomUUID === "function") return webCrypto.randomUUID();
|
|
359
|
+
const bytes = new Uint8Array(16);
|
|
360
|
+
if (typeof webCrypto?.getRandomValues === "function") webCrypto.getRandomValues(bytes);
|
|
361
|
+
else for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);
|
|
362
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
363
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
364
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
365
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// src/resources.ts
|
|
369
|
+
function pageQuery(query = {}) {
|
|
370
|
+
const params = new URLSearchParams();
|
|
371
|
+
for (const key of ["PageNo", "PageSize"]) {
|
|
372
|
+
const value = query[key];
|
|
373
|
+
if (value !== void 0) params.append(key, String(value));
|
|
374
|
+
}
|
|
375
|
+
if (query.Search !== void 0) params.append("search", query.Search);
|
|
376
|
+
for (const key of ["SortBy", "SrchBy"])
|
|
377
|
+
(query[key] ?? []).forEach((clause, index) => clause.forEach((value, part) => params.append(`${key}[${index}][${part}]`, value)));
|
|
378
|
+
for (const value of query.Include ?? []) params.append("Include", value);
|
|
379
|
+
return params.toString();
|
|
380
|
+
}
|
|
381
|
+
function queryPath(path, page, filters = {}) {
|
|
382
|
+
const params = new URLSearchParams(pageQuery(page));
|
|
383
|
+
for (const [key, value] of Object.entries(filters)) if (value !== void 0 && value !== null) params.set(key, String(value));
|
|
384
|
+
const suffix = params.toString();
|
|
385
|
+
return suffix ? `${path}?${suffix}` : path;
|
|
386
|
+
}
|
|
387
|
+
function accountPath(acno) {
|
|
388
|
+
if (!Number.isSafeInteger(acno) || acno <= 0) throw new RangeError("acno must be a positive safe integer.");
|
|
389
|
+
return `v1/fxs/accounts/${acno}`;
|
|
390
|
+
}
|
|
391
|
+
function numericId(value, name, allowZero = true) {
|
|
392
|
+
if (!Number.isSafeInteger(value) || value < (allowZero ? 0 : 1)) throw new RangeError(`${name} is out of range.`);
|
|
393
|
+
return String(value);
|
|
394
|
+
}
|
|
395
|
+
function dateValue(value) {
|
|
396
|
+
return value instanceof Date ? value.toISOString() : value;
|
|
397
|
+
}
|
|
398
|
+
function dateFilters(query) {
|
|
399
|
+
return {
|
|
400
|
+
fromDt: query.fromDt === void 0 ? void 0 : dateValue(query.fromDt),
|
|
401
|
+
toDt: query.toDt === void 0 ? void 0 : dateValue(query.toDt)
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
var AccountsResource = class {
|
|
405
|
+
constructor(http) {
|
|
406
|
+
this.http = http;
|
|
407
|
+
}
|
|
408
|
+
http;
|
|
409
|
+
/** Lists reachable accounts, optionally filtered by external reference. */
|
|
410
|
+
list(page = {}, externalRef, options = {}) {
|
|
411
|
+
return this.http.requestPage("GET", queryPath("v1/fxs/accounts", page, { externalRef }), options);
|
|
412
|
+
}
|
|
413
|
+
/** Adds a hosted account. */
|
|
414
|
+
make(command, options = {}) {
|
|
415
|
+
return this.http.request("POST", "v1/fxs/accounts", command, options);
|
|
416
|
+
}
|
|
417
|
+
/** Creates a demo account. */
|
|
418
|
+
makeDemo(command, options = {}) {
|
|
419
|
+
return this.http.request("POST", "v1/fxs/accounts/demo", command, options);
|
|
420
|
+
}
|
|
421
|
+
/** Reads demo availability for one platform. */
|
|
422
|
+
demoAvailability(platform, options = {}) {
|
|
423
|
+
return this.http.request("GET", queryPath("v1/fxs/accounts/demo-availability", void 0, { platform }), void 0, options);
|
|
424
|
+
}
|
|
425
|
+
/** Reads one account by ACNO. */
|
|
426
|
+
get(acno, options = {}) {
|
|
427
|
+
return this.http.request("GET", accountPath(acno), void 0, options);
|
|
428
|
+
}
|
|
429
|
+
/** Sets or clears the account's external reference. */
|
|
430
|
+
setExternalRef(acno, command, options = {}) {
|
|
431
|
+
return this.http.request("PUT", `${accountPath(acno)}/external-ref`, command, options);
|
|
432
|
+
}
|
|
433
|
+
/** Reads account settings. */
|
|
434
|
+
getSettings(acno, options = {}) {
|
|
435
|
+
return this.http.request("GET", `${accountPath(acno)}/settings`, void 0, options);
|
|
436
|
+
}
|
|
437
|
+
/** Changes account settings. */
|
|
438
|
+
patchSettings(acno, command, options = {}) {
|
|
439
|
+
return this.http.request("PATCH", `${accountPath(acno)}/settings`, command, options);
|
|
440
|
+
}
|
|
441
|
+
/** Lists pairs for one account. */
|
|
442
|
+
pairs(acno, page = {}, search, options = {}) {
|
|
443
|
+
return this.http.requestPage("GET", queryPath(`${accountPath(acno)}/pairs`, page, { search }), options);
|
|
444
|
+
}
|
|
445
|
+
/** Reads one pair by PRNO. */
|
|
446
|
+
pair(acno, prno, options = {}) {
|
|
447
|
+
return this.http.request("GET", `${accountPath(acno)}/pairs/${numericId(prno, "prno")}`, void 0, options);
|
|
448
|
+
}
|
|
449
|
+
/** Changes one pair's settings. */
|
|
450
|
+
patchPair(acno, prno, command, options = {}) {
|
|
451
|
+
return this.http.request("PATCH", `${accountPath(acno)}/pairs/${numericId(prno, "prno")}`, command, options);
|
|
452
|
+
}
|
|
453
|
+
/** Reads the broker symbols and enabled list. */
|
|
454
|
+
symbols(acno, options = {}) {
|
|
455
|
+
return this.http.request("GET", `${accountPath(acno)}/symbols`, void 0, options);
|
|
456
|
+
}
|
|
457
|
+
/** Adds or removes enabled symbols. */
|
|
458
|
+
editSymbols(acno, command, options = {}) {
|
|
459
|
+
return this.http.request("PUT", `${accountPath(acno)}/symbols`, command, options);
|
|
460
|
+
}
|
|
461
|
+
/** Reads a symbol's broker specification and latest quote. */
|
|
462
|
+
quote(acno, symbol, options = {}) {
|
|
463
|
+
if (!symbol.trim()) throw new TypeError("symbol is required.");
|
|
464
|
+
return this.http.request("GET", `${accountPath(acno)}/quotes/${encodeURIComponent(symbol)}`, void 0, options);
|
|
465
|
+
}
|
|
466
|
+
/** Lists live orders for one account. */
|
|
467
|
+
orders(acno, page = {}, search, options = {}) {
|
|
468
|
+
return this.http.requestPage("GET", queryPath(`${accountPath(acno)}/orders`, page, { search }), options);
|
|
469
|
+
}
|
|
470
|
+
/** Reads one live or settled order. */
|
|
471
|
+
order(acno, oid, options = {}) {
|
|
472
|
+
return this.http.request("GET", `${accountPath(acno)}/orders/${numericId(oid, "oid")}`, void 0, options);
|
|
473
|
+
}
|
|
474
|
+
/** Lists settled orders. */
|
|
475
|
+
history(acno, page = {}, search, options = {}) {
|
|
476
|
+
return this.http.requestPage("GET", queryPath(`${accountPath(acno)}/history`, page, { search }), options);
|
|
477
|
+
}
|
|
478
|
+
/** Reads one settled order by OID. */
|
|
479
|
+
historyItem(acno, oid, options = {}) {
|
|
480
|
+
return this.http.request("GET", `${accountPath(acno)}/history/${numericId(oid, "oid")}`, void 0, options);
|
|
481
|
+
}
|
|
482
|
+
/** Lists HYBRID DCA proposals waiting for an answer. */
|
|
483
|
+
dcaQueue(acno, options = {}) {
|
|
484
|
+
return this.http.request("GET", `${accountPath(acno)}/dca`, void 0, options);
|
|
485
|
+
}
|
|
486
|
+
/** Lists daily performance rows. */
|
|
487
|
+
performance(acno, query = {}) {
|
|
488
|
+
return this.http.request("GET", queryPath(`${accountPath(acno)}/performance`, void 0, dateFilters(query)), void 0, query);
|
|
489
|
+
}
|
|
490
|
+
/** Reads the account's performance window. */
|
|
491
|
+
performanceWindow(acno, options = {}) {
|
|
492
|
+
return this.http.request("GET", `${accountPath(acno)}/performance/window`, void 0, options);
|
|
493
|
+
}
|
|
494
|
+
/** Reads performance bars for an optional UTC date range. */
|
|
495
|
+
performanceBars(acno, query = {}) {
|
|
496
|
+
return this.http.request("GET", queryPath(`${accountPath(acno)}/performance/bars`, void 0, dateFilters(query)), void 0, query);
|
|
497
|
+
}
|
|
498
|
+
/** Lists paged account activity. */
|
|
499
|
+
activity(acno, page = {}, search, options = {}) {
|
|
500
|
+
return this.http.requestPage("GET", queryPath(`${accountPath(acno)}/activity`, page, { search }), options);
|
|
501
|
+
}
|
|
502
|
+
/** Previews applying a strategy (the account owner's or a FinCopy preset): what each symbol would change. Nothing changes. */
|
|
503
|
+
previewStrategy(acno, command, options = {}) {
|
|
504
|
+
return this.http.request("POST", `${accountPath(acno)}/strategy/preview`, command, options);
|
|
505
|
+
}
|
|
506
|
+
/** Applies a strategy to the account and its symbols; a side holding orders keeps what it must keep. */
|
|
507
|
+
applyStrategy(acno, command, options = {}) {
|
|
508
|
+
return this.http.request("POST", `${accountPath(acno)}/strategy/apply`, command, options);
|
|
509
|
+
}
|
|
510
|
+
/** Lists recent terminal operations. */
|
|
511
|
+
operations(acno, page = {}, options = {}) {
|
|
512
|
+
return this.http.requestPage("GET", queryPath(`${accountPath(acno)}/operations`, page), options);
|
|
513
|
+
}
|
|
514
|
+
/** Queues a broker-password change. */
|
|
515
|
+
changePassword(acno, command, options = {}) {
|
|
516
|
+
return this.http.request("POST", `${accountPath(acno)}/password`, command, options);
|
|
517
|
+
}
|
|
518
|
+
/** Queues a terminal start. */
|
|
519
|
+
start(acno, options = {}) {
|
|
520
|
+
return this.operation(acno, "start", options);
|
|
521
|
+
}
|
|
522
|
+
/** Queues a terminal stop. */
|
|
523
|
+
stop(acno, options = {}) {
|
|
524
|
+
return this.operation(acno, "stop", options);
|
|
525
|
+
}
|
|
526
|
+
/** Queues a terminal restart. */
|
|
527
|
+
restart(acno, options = {}) {
|
|
528
|
+
return this.operation(acno, "restart", options);
|
|
529
|
+
}
|
|
530
|
+
/** Queues account removal. */
|
|
531
|
+
remove(acno, options = {}) {
|
|
532
|
+
return this.http.request("DELETE", accountPath(acno), void 0, options);
|
|
533
|
+
}
|
|
534
|
+
operation(acno, action, options) {
|
|
535
|
+
return this.http.request("POST", `${accountPath(acno)}/${action}`, void 0, options);
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
var OrdersResource = class {
|
|
539
|
+
constructor(http) {
|
|
540
|
+
this.http = http;
|
|
541
|
+
}
|
|
542
|
+
http;
|
|
543
|
+
/** Preflights a trade request. */
|
|
544
|
+
preInfo(acno, command, options = {}) {
|
|
545
|
+
return this.http.request("POST", `${accountPath(acno)}/trade/preinfo`, command, options);
|
|
546
|
+
}
|
|
547
|
+
/** Sends one trade action. */
|
|
548
|
+
make(acno, command, options = {}) {
|
|
549
|
+
return this.http.request("POST", `${accountPath(acno)}/trade`, command, options);
|
|
550
|
+
}
|
|
551
|
+
/** Sends paired BUY and SELL actions. */
|
|
552
|
+
hedge(acno, command, options = {}) {
|
|
553
|
+
return this.http.request("POST", `${accountPath(acno)}/trade/hedge`, command, options);
|
|
554
|
+
}
|
|
555
|
+
/** Modifies a trade or pending order. */
|
|
556
|
+
modify(acno, command, options = {}) {
|
|
557
|
+
return this.http.request("POST", `${accountPath(acno)}/trade/modify`, command, options);
|
|
558
|
+
}
|
|
559
|
+
/** Cancels an order that has not reached the terminal. */
|
|
560
|
+
cancelWaiting(acno, command, options = {}) {
|
|
561
|
+
return this.http.request("POST", `${accountPath(acno)}/trade/cancel`, command, options);
|
|
562
|
+
}
|
|
563
|
+
/** Accepts or rejects one DCA proposal. */
|
|
564
|
+
answerDca(acno, oid, command, options = {}) {
|
|
565
|
+
return this.http.request("POST", `${accountPath(acno)}/dca/${numericId(oid, "oid")}`, command, options);
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
var FxResource = class {
|
|
569
|
+
constructor(http) {
|
|
570
|
+
this.http = http;
|
|
571
|
+
}
|
|
572
|
+
http;
|
|
573
|
+
/** Lists pairs across reachable accounts. */
|
|
574
|
+
pairs(query = {}, options = {}) {
|
|
575
|
+
return this.http.requestPage("GET", listPath("v1/fxs/pairs", query), options);
|
|
576
|
+
}
|
|
577
|
+
/** Lists open orders across reachable accounts. */
|
|
578
|
+
orders(query = {}, options = {}) {
|
|
579
|
+
return this.http.requestPage("GET", listPath("v1/fxs/orders", query), options);
|
|
580
|
+
}
|
|
581
|
+
/** Lists settled orders across reachable accounts. */
|
|
582
|
+
history(query = {}, options = {}) {
|
|
583
|
+
return this.http.requestPage("GET", listPath("v1/fxs/history", query), options);
|
|
584
|
+
}
|
|
585
|
+
/** Lists activity across reachable accounts. */
|
|
586
|
+
activity(query = {}, options = {}) {
|
|
587
|
+
return this.http.requestPage("GET", listPath("v1/fxs/activity", query), options);
|
|
588
|
+
}
|
|
589
|
+
/** HYBRID DCA proposals waiting for an answer across reachable accounts, newest first. */
|
|
590
|
+
dca(acno, options = {}) {
|
|
591
|
+
return this.http.request("GET", queryPath("v1/fxs/dca", void 0, { acno }), void 0, options);
|
|
592
|
+
}
|
|
593
|
+
/** The newest terminal operations across reachable accounts. */
|
|
594
|
+
operations(page = {}, acno, options = {}) {
|
|
595
|
+
return this.http.requestPage("GET", queryPath("v1/fxs/operations", page, { acno }), options);
|
|
596
|
+
}
|
|
597
|
+
/** The last 30 days by currency book across reachable accounts. */
|
|
598
|
+
performanceWindow(query = {}) {
|
|
599
|
+
return this.http.request("GET", queryPath("v1/fxs/performance/window", void 0, { acno: query.acno, demo: query.demo }), void 0, query);
|
|
600
|
+
}
|
|
601
|
+
/** Daily performance bars across reachable accounts for an optional UTC date range. */
|
|
602
|
+
performanceBars(query = {}) {
|
|
603
|
+
return this.http.request("GET", queryPath(
|
|
604
|
+
"v1/fxs/performance/bars",
|
|
605
|
+
void 0,
|
|
606
|
+
{ acno: query.acno, demo: query.demo, ...dateFilters(query) }
|
|
607
|
+
), void 0, query);
|
|
608
|
+
}
|
|
609
|
+
/** Lists the strategies you can apply: your own and FinCopy's presets; with `acno`, that account owner's. */
|
|
610
|
+
strategies(acno, options = {}) {
|
|
611
|
+
return this.http.request("GET", queryPath("v1/fxs/strategies", void 0, { acno }), void 0, options);
|
|
612
|
+
}
|
|
613
|
+
/** Reads one strategy. */
|
|
614
|
+
strategy(isno, options = {}) {
|
|
615
|
+
return this.http.request("GET", strategyPath(isno), void 0, options);
|
|
616
|
+
}
|
|
617
|
+
/** Creates one of your own strategies. */
|
|
618
|
+
makeStrategy(command, options = {}) {
|
|
619
|
+
return this.http.request("POST", "v1/fxs/strategies", command, options);
|
|
620
|
+
}
|
|
621
|
+
/** Changes one of your own strategies; fields left unset keep their values. */
|
|
622
|
+
patchStrategy(isno, command, options = {}) {
|
|
623
|
+
return this.http.request("PATCH", strategyPath(isno), command, options);
|
|
624
|
+
}
|
|
625
|
+
/** Deletes one of your own strategies. */
|
|
626
|
+
deleteStrategy(isno, options = {}) {
|
|
627
|
+
return this.http.request("DELETE", strategyPath(isno), void 0, options);
|
|
628
|
+
}
|
|
629
|
+
/** Reads the signals shown for enabled symbols. */
|
|
630
|
+
signals(options = {}) {
|
|
631
|
+
return this.http.request("GET", "v1/fxs/signals", void 0, options);
|
|
632
|
+
}
|
|
633
|
+
/** Lists warning and error activity. */
|
|
634
|
+
alerts(page = {}, options = {}) {
|
|
635
|
+
return this.http.requestPage("GET", queryPath("v1/fxs/alerts", page), options);
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Opens the live event stream for reachable accounts. It does not reconnect: it ends when the server closes it (about
|
|
639
|
+
* every 30 minutes) or the connection breaks, so open it again in a loop, waiting a few seconds between attempts.
|
|
640
|
+
*/
|
|
641
|
+
stream(options = {}) {
|
|
642
|
+
return this.http.requestEvents("v1/fxs/stream", options);
|
|
643
|
+
}
|
|
644
|
+
/** Searches broker servers available when adding an account. */
|
|
645
|
+
brokers(query, platform, demosOnly, options = {}) {
|
|
646
|
+
return this.http.request("GET", queryPath("v1/brokers", void 0, { Q: query, Platform: platform, DemosOnly: demosOnly }), void 0, options);
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
var ChangesResource = class {
|
|
650
|
+
constructor(http) {
|
|
651
|
+
this.http = http;
|
|
652
|
+
}
|
|
653
|
+
http;
|
|
654
|
+
/** Reads one changes page using an optional cursor. */
|
|
655
|
+
get(cursor, take = 500, options = {}) {
|
|
656
|
+
if (!Number.isInteger(take) || take < 1 || take > 1e3) throw new RangeError("take must be between 1 and 1000.");
|
|
657
|
+
return this.http.request("GET", queryPath("v1/changes", void 0, { cursor, take }), void 0, options);
|
|
658
|
+
}
|
|
659
|
+
/** Reads changes page by page, following each cursor. */
|
|
660
|
+
readPages(cursor, take = 500, signal) {
|
|
661
|
+
return readChangePages((next, nextSignal) => this.page(next, take, nextSignal), cursor, signal);
|
|
662
|
+
}
|
|
663
|
+
/** Streams all available changes. Use readPages to retain a resume cursor. */
|
|
664
|
+
readAll(cursor, take = 500, signal) {
|
|
665
|
+
return readAllChanges((next, nextSignal) => this.page(next, take, nextSignal), cursor, signal);
|
|
666
|
+
}
|
|
667
|
+
async page(cursor, take, signal) {
|
|
668
|
+
const page = await this.get(cursor, take, { signal });
|
|
669
|
+
return { Changes: page.Changes ?? [], Cursor: page.Cursor ?? null, More: page.More ?? false };
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
var WebhooksResource = class {
|
|
673
|
+
constructor(http) {
|
|
674
|
+
this.http = http;
|
|
675
|
+
}
|
|
676
|
+
http;
|
|
677
|
+
/** Reads this key's webhook configuration. */
|
|
678
|
+
get(options = {}) {
|
|
679
|
+
return this.http.request("GET", "v1/webhook", void 0, options);
|
|
680
|
+
}
|
|
681
|
+
/** Sets the webhook destination and returns its signing secret once. */
|
|
682
|
+
set(command, options = {}) {
|
|
683
|
+
return this.http.request("PUT", "v1/webhook", command, options);
|
|
684
|
+
}
|
|
685
|
+
/** Removes this key's webhook. */
|
|
686
|
+
delete(options = {}) {
|
|
687
|
+
return this.http.request("DELETE", "v1/webhook", void 0, options);
|
|
688
|
+
}
|
|
689
|
+
/** Sends a signed test delivery. */
|
|
690
|
+
ping(options = {}) {
|
|
691
|
+
return this.http.request("POST", "v1/webhook/ping", void 0, options);
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
function strategyPath(isno) {
|
|
695
|
+
return `v1/fxs/strategies/${numericId(isno, "isno")}`;
|
|
696
|
+
}
|
|
697
|
+
function listPath(path, query) {
|
|
698
|
+
return queryPath(path, query.page, { acno: query.acno, prno: query.prno, demo: query.demo, platform: query.platform, search: query.search });
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// src/client.ts
|
|
702
|
+
var FinClient = class {
|
|
703
|
+
copy;
|
|
704
|
+
constructor(options) {
|
|
705
|
+
const transport = new FinHttpTransport(options);
|
|
706
|
+
this.copy = {
|
|
707
|
+
accounts: new AccountsResource(transport),
|
|
708
|
+
orders: new OrdersResource(transport),
|
|
709
|
+
fx: new FxResource(transport),
|
|
710
|
+
changes: new ChangesResource(transport),
|
|
711
|
+
webhooks: new WebhooksResource(transport)
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
716
|
+
0 && (module.exports = {
|
|
717
|
+
AccountsResource,
|
|
718
|
+
ChangesResource,
|
|
719
|
+
FinApiError,
|
|
720
|
+
FinClient,
|
|
721
|
+
FinHttpTransport,
|
|
722
|
+
FxResource,
|
|
723
|
+
OrdersResource,
|
|
724
|
+
WebhooksResource,
|
|
725
|
+
readAllChanges,
|
|
726
|
+
readChangePages
|
|
727
|
+
});
|