@flexemarkets/fm-sdk 0.0.10 → 0.1.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/dist/client.d.ts CHANGED
@@ -3,9 +3,10 @@
3
3
  *
4
4
  * Port of fm.client (Python) / fm.Flexemarkets (Java).
5
5
  */
6
- import type { Account, Allotment, ClientConnection, Holding, Market, Marketplace, Order, Person, Session } from "./types.js";
6
+ import type { Account, Allotment, ClientConnection, Holding, ManagerOtpBundle, Market, Marketplace, Order, Person, Security, Session, TickGrid, Token } from "./types.js";
7
7
  import { EventListener, type EventCallback } from "./stomp.js";
8
- import { type MarketView } from "./market-view.js";
8
+ import type { ApiRoot } from "./hal.js";
9
+ import { type MarketView, type Subscription } from "./market-view.js";
9
10
  import type { Snapshot } from "./snapshot.js";
10
11
  export declare class FlexemarketsError extends Error {
11
12
  }
@@ -19,9 +20,91 @@ export declare class ConnectionFailedError extends FlexemarketsError {
19
20
  }
20
21
  export declare class ConfigurationError extends FlexemarketsError {
21
22
  }
23
+ /**
24
+ * A response the SDK has no better name for, carrying its status and body.
25
+ *
26
+ * The fallback. A status with a meaning worth acting on gets its own type —
27
+ * {@link AuthenticationError}, {@link ConflictError} — and this is what is
28
+ * left, so a caller can read the status rather than parse a message.
29
+ */
30
+ export declare class HttpError extends FlexemarketsError {
31
+ readonly statusCode: number;
32
+ readonly body: string;
33
+ constructor(statusCode: number, body: string);
34
+ }
35
+ /**
36
+ * The call could not be completed: the transport failed, or the response was
37
+ * not something the SDK could read.
38
+ *
39
+ * Distinct from {@link HttpError}, which means the server answered and the
40
+ * answer was an error. This means there was no usable answer at all — a
41
+ * malformed body, or a link the API root does not carry.
42
+ */
43
+ export declare class ApiError extends FlexemarketsError {
44
+ }
45
+ /** A 409. The Java and Python SDKs have raised this since the admin surface landed. */
46
+ export declare class ConflictError extends FlexemarketsError {
47
+ }
48
+ /**
49
+ * An account name was taken, and the server proposed another.
50
+ *
51
+ * A subclass of {@link ConflictError} rather than a sibling, so a caller that
52
+ * handles conflicts generally still catches this one. The suggestion is worth
53
+ * surfacing rather than retrying blindly: it is the name the account would end
54
+ * up known by.
55
+ */
56
+ export declare class AccountNameConflictError extends ConflictError {
57
+ readonly requestedName: string;
58
+ readonly suggestedName: string | null;
59
+ constructor(message: string, requestedName: string, suggestedName: string | null);
60
+ }
61
+ /**
62
+ * A user could not be deleted because they still own marketplace data —
63
+ * orders or allotments. Deleting them would orphan it, so the server refuses;
64
+ * the caller has to decide what happens to the data first.
65
+ */
66
+ export declare class PersonHasMarketplaceDataError extends ConflictError {
67
+ readonly userId: number;
68
+ constructor(message: string, userId: number);
69
+ }
22
70
  type JsonObject = Record<string, unknown>;
71
+ export declare function parsePerson(data: JsonObject | null | undefined): Person | null;
72
+ export declare function parseAccount(data: JsonObject | null | undefined): Account | null;
73
+ export declare function parseToken(data: JsonObject): Token;
74
+ export declare function parseSecurity(data: JsonObject): Security;
75
+ export declare function parseMarket(data: JsonObject): Market;
76
+ export declare function parseMarketplace(data: JsonObject): Marketplace;
77
+ export declare function parseSession(data: JsonObject): Session;
23
78
  export declare function parseOrder(data: JsonObject): Order;
24
79
  export declare function parseHolding(data: JsonObject): Holding;
80
+ export declare function parseConnection(data: JsonObject): ClientConnection;
81
+ /**
82
+ * The most aggressive price this market will accept on `side`.
83
+ *
84
+ * Ticks are anchored at `priceMinimum`, not at zero — the server tests
85
+ * `(price - priceMinimum) % priceTick` — so the top of the range is only legal
86
+ * when the range is a whole number of ticks. The highest legal price is the
87
+ * last tick at or below `priceMaximum`. A tick of zero marks a fixed dimension,
88
+ * where the two bounds are equal and there is one legal price.
89
+ */
90
+ export declare function marketableLimit(market: Market, side: string): number;
91
+ /**
92
+ * Point the API root's links back at the host that was dialled.
93
+ *
94
+ * The server builds these hrefs from the request it believes it received, and
95
+ * behind a proxy that belief can be wrong: an origin reached over a plaintext
96
+ * leg reports `http://` even though the caller arrived on `https://`. Every
97
+ * call that goes through a link — which is most of them — then leaves on plain
98
+ * HTTP and meets the edge's redirect. A GET survives it. A POST does not: a
99
+ * 301 is followed as a GET with the body dropped, so placing an order or
100
+ * opening a session fails with nothing placed and nothing pointing at the
101
+ * scheme.
102
+ *
103
+ * Only the origin is replaced. The path, query and any URI template are the
104
+ * server's to choose; where it is reachable is not, and the token in hand was
105
+ * issued by the origin dialled, not by whatever the links name.
106
+ */
107
+ export declare function rebaseApiRoot(root: ApiRoot, endpoint: string): ApiRoot;
25
108
  /**
26
109
  * Resolve an `--endpoint` value to config overrides. A bare marketplace id
27
110
  * (e.g. "2540") resolves to that marketplace on the default production host; a
@@ -29,11 +112,6 @@ export declare function parseHolding(data: JsonObject): Holding;
29
112
  * URL. Development environments give a full URL when localhost is wanted.
30
113
  */
31
114
  export declare function resolveEndpoint(endpoint: string): Record<string, string>;
32
- export interface FlexemarketsOptions {
33
- credential?: string;
34
- endpoint?: string;
35
- clientDescription?: string;
36
- }
37
115
  export declare class Flexemarkets {
38
116
  private readonly _clientDescription;
39
117
  private readonly _endpoint;
@@ -42,6 +120,7 @@ export declare class Flexemarkets {
42
120
  private _apiRoot;
43
121
  private _account;
44
122
  private _user;
123
+ private _tokenObj;
45
124
  private _eventListener;
46
125
  private constructor();
47
126
  /** Connect to the Flexemarkets API. */
@@ -74,29 +153,125 @@ export declare class Flexemarkets {
74
153
  */
75
154
  private _getText;
76
155
  private _post;
156
+ /**
157
+ * Register a new account and its owner, returning the owner's token.
158
+ *
159
+ * The owner's credentials go out as `ownerEmail`/`ownerPassword`. Sending
160
+ * `email`/`password` instead creates an account with an owner the server
161
+ * cannot sign in as.
162
+ */
163
+ signup(accountName: string, email: string, password: string, firstName?: string | null, lastName?: string | null): Promise<Token>;
164
+ /** Approve an account by name, returning it as it now stands. */
165
+ approveAccount(accountName: string): Promise<Account | null>;
166
+ /** One account by id. */
167
+ accountById(accountId: number): Promise<Account | null>;
168
+ /** One user by id. */
169
+ userById(userId: number): Promise<Person>;
170
+ /** The marketplace's private-trader identifiers. */
171
+ identifiers(marketplaceId: number): Promise<string[]>;
172
+ /** Delete the caller's own account. Its own route, not accounts/{yourId}. */
173
+ deleteMyAccount(): Promise<void>;
174
+ /** Every account on the server. Admin-only. */
175
+ accounts(): Promise<Account[]>;
176
+ /** Delete an account. Destructive, and takes its users with it. */
177
+ deleteAccount(accountId: number): Promise<void>;
178
+ /** Create a user in the caller's account. */
179
+ createUser(email: string, password: string, firstName: string, lastName: string, roles?: string[]): Promise<Person>;
180
+ /** Delete a user. Destructive. */
181
+ deleteUser(userId: number): Promise<void>;
182
+ /** Create an empty marketplace. See also {@link createMarketplaceFromJson}. */
183
+ /** Delete a marketplace, and with it its sessions and their history. */
184
+ deleteMarketplace(marketplaceId: number): Promise<void>;
185
+ /**
186
+ * Add a market to a marketplace.
187
+ *
188
+ * Both dimensions are the caller's. Unit bounds used to be fixed at 1/100/1
189
+ * with no way to say otherwise, on a call that set the price grid three
190
+ * arguments earlier — and the server enforces the two identically, refusing
191
+ * an order for "units is not on a tic" exactly as for a price. Omitting
192
+ * `units` keeps the old default.
193
+ */
194
+ createMarket(marketplaceId: number, symbol: string, name: string, price: TickGrid, units?: TickGrid, privateMarket?: boolean): Promise<Market>;
195
+ /**
196
+ * Mint one-time passcodes for the given users.
197
+ *
198
+ * These are credentials: not to be logged, not to be persisted, and
199
+ * delivered to the person they belong to.
200
+ */
201
+ managerOtpBundle(userIds: number[]): Promise<ManagerOtpBundle>;
202
+ /** DELETE, whose answer is a status and nothing worth parsing. */
203
+ private _delete;
77
204
  private _fetchApiRoot;
78
205
  marketplaces(): Promise<Marketplace[]>;
79
206
  marketplace(marketplaceId: number): Promise<Marketplace>;
80
207
  markets(marketplaceId: number): Promise<Market[]>;
208
+ /**
209
+ * The token this connection signed in with.
210
+ *
211
+ * Exposed so a caller can open a sibling connection on the same identity
212
+ * without holding the password again.
213
+ */
214
+ token(): Token;
215
+ /** Whether this connection's user holds ROLE_ADMIN. */
216
+ isAdmin(): boolean;
217
+ /**
218
+ * Whether this connection's user holds ROLE_MANAGER — the role that runs a
219
+ * study: opening and closing sessions, staging allocations, minting
220
+ * passcodes. Python has had it since the management surface landed.
221
+ */
222
+ isManager(): boolean;
223
+ hasRole(role: string): boolean;
81
224
  symbols(marketplaceId: number): Promise<string[]>;
82
- sessions(marketplaceId: number, sessionIds?: number[] | null): Promise<Session[]>;
225
+ /**
226
+ * The marketplace's sessions — all of them.
227
+ *
228
+ * There is no server-side filter. The route takes no session argument, so
229
+ * the `sessionIds` this used to accept was silently ignored: it returned the
230
+ * whole history and looked like it had filtered. Filter the result; fm-ui
231
+ * already does.
232
+ *
233
+ * On `GET /api/v1/marketplaces/{id}/sessions`, which answers with the same
234
+ * fields as the V0 route it replaces — verified against a running server,
235
+ * not assumed — and needs no `format=application/json` to avoid HAL.
236
+ */
237
+ sessions(marketplaceId: number): Promise<Session[]>;
83
238
  session(marketplaceId: number): Promise<Session>;
84
239
  submitLimit(marketplaceId: number, marketId: number, side: string, units: number, price: number): Promise<Order>;
240
+ /**
241
+ * Cross the book: buy at the highest price this market allows, sell at the
242
+ * lowest. Immediate or cancel — whatever does not fill is cancelled.
243
+ *
244
+ * There is no market order on the server. Its type switch falls through to
245
+ * `LIMIT`, so every submission is bounds-checked against the market and must
246
+ * sit on a tick — which is why this asks the marketplace for the market
247
+ * first, and costs a round trip {@link submitLimit} does not.
248
+ *
249
+ * The cancel is unconditional: the exchange consumes a cancel by itself when
250
+ * no units remain, so a complete fill costs a harmless round trip rather than
251
+ * an inspection that would race the book. Without it, a market order that did
252
+ * not fill would rest at the market's extreme — the best price in the book,
253
+ * standing, for anyone to take.
254
+ *
255
+ * Returns the limit order as submitted. What it filled is a property of the
256
+ * book afterwards, not of this value.
257
+ */
258
+ submitMarket(marketplaceId: number, marketId: number, side: string, units: number): Promise<Order>;
259
+ private _market;
85
260
  submitCancel(marketplaceId: number, marketId: number, originalId: number): Promise<Order>;
86
261
  /**
87
- * V1 active-orders snapshot: every resting limit order on the
262
+ * The active-orders snapshot: every resting limit order on the
88
263
  * marketplace's current session, plus the `x-fm-as-of-seq` sequence
89
- * the snapshot was read at. Used by `MarketView` Phase 2a seeding
264
+ * the snapshot was read at. Used by `MarketView` seeding
90
265
  * — clients apply WS deltas whose seq is greater than the returned
91
266
  * value and skip those whose seq is less than or equal.
92
267
  */
93
- activeOrdersV1(marketplaceId: number): Promise<Snapshot<Order[]>>;
268
+ activeOrders(marketplaceId: number): Promise<Snapshot<Order[]>>;
94
269
  /**
95
- * V1 recent-trades snapshot for seeding the trade-history tape.
96
- * Same `x-fm-as-of-seq` contract as `activeOrdersV1`. Server caps
270
+ * The recent-trades snapshot, for seeding the trade-history tape.
271
+ * Same `x-fm-as-of-seq` contract as `activeOrders`. Server caps
97
272
  * at 5000; default size is 1000.
98
273
  */
99
- recentTradesV1(marketplaceId: number, size?: number): Promise<Snapshot<Order[]>>;
274
+ recentTrades(marketplaceId: number, size?: number): Promise<Snapshot<Order[]>>;
100
275
  orders(marketplaceId: number, options?: {
101
276
  symbol?: string;
102
277
  sessionIds?: number[];
@@ -104,7 +279,14 @@ export declare class Flexemarkets {
104
279
  trades(marketplaceId: number, symbol: string): Promise<Order[]>;
105
280
  holdings(marketplaceId: number, sessionIds?: number[] | null): Promise<Holding[]>;
106
281
  holding(marketplaceId: number): Promise<Holding>;
107
- connections(marketplaceId: number, sessionIds?: number[] | null): Promise<ClientConnection[]>;
282
+ /**
283
+ * Who is attached to the marketplace — all of them.
284
+ *
285
+ * No server-side filter, for the reason {@link sessions} gives. A connection
286
+ * carries the session it belonged to, so "who was present in that run" is a
287
+ * filter on the result.
288
+ */
289
+ connections(marketplaceId: number): Promise<ClientConnection[]>;
108
290
  /**
109
291
  * Create a marketplace from its JSON definition, returning what was made.
110
292
  *
@@ -137,8 +319,14 @@ export declare class Flexemarkets {
137
319
  * computes with; the allotment encoding is applied here.
138
320
  */
139
321
  allocate(marketplaceId: number, holdings: Holding[]): Promise<Holding[]>;
140
- /** The holdings CSV, verbatim, as the server renders it. */
141
- downloadHoldings(marketplaceId: number): Promise<string>;
322
+ /**
323
+ * The holdings CSV, verbatim, for the current session or for given ones.
324
+ *
325
+ * The filter is spelled `sessions=` on this route and `sessionIds=` on
326
+ * sessions and connections. Using the wrong one is not an error — it is an
327
+ * unfiltered answer.
328
+ */
329
+ downloadHoldings(marketplaceId: number, sessionIds?: number[] | null): Promise<string>;
142
330
  /**
143
331
  * Load opening positions from a holdings CSV. Stages the next allocation on
144
332
  * the same terms as {@link allocate}.
@@ -162,6 +350,22 @@ export declare class Flexemarkets {
162
350
  _releaseSharedView(marketplaceId: number): void;
163
351
  /** Start receiving real-time events via WebSocket STOMP. */
164
352
  listen(marketplaceId: number, callback: EventCallback): Promise<void>;
353
+ /**
354
+ * Open an *independent* event subscription, delivering to `callback` until
355
+ * the returned unsubscribe function is invoked.
356
+ *
357
+ * Unlike {@link listen}, which is one per connection and replaces itself,
358
+ * several of these coexist: each has its own stream and its own lifetime.
359
+ * That is what lets more than one MarketView live in one connection without
360
+ * trampling each other — the mechanism was already here for exactly that, as
361
+ * the package-private `_connectEvents`, but a caller who wanted a second
362
+ * stream of their own had no way to ask for one.
363
+ *
364
+ * Returns an unsubscribe function rather than an object with `close()`,
365
+ * matching what MarketView's `on*` handlers already return here. Java
366
+ * returns a `Subscription`; both names describe the same lifetime.
367
+ */
368
+ subscribe(marketplaceId: number, callback: EventCallback): Promise<Subscription>;
165
369
  /**
166
370
  * Package-private helper used by {@link DefaultMarketView} (Phase 2d)
167
371
  * to own its own EventListener subscription rather than clobbering