@flexemarkets/fm-sdk 0.0.2 → 0.0.3

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.
@@ -0,0 +1,440 @@
1
+ /**
2
+ * MarketView — always-current view of a single marketplace, hiding
3
+ * transport details (WebSocket subscribe, snapshot+delta
4
+ * reconciliation, sequence-gap recovery, reconnect) behind a small
5
+ * read-side surface. Mirrors the Java + Python SDKs; same staged
6
+ * roadmap. See project_fm_sdk_canonical_client.
7
+ *
8
+ * Phase 1 scope: API surface + skeleton that delegates to the
9
+ * existing listen() / callback pipe. No reconciliation, no
10
+ * snapshot seed, no sharing, no reconnect — those land in Phase 2.
11
+ */
12
+ import { OrderBooks } from "./orderbook.js";
13
+ import { MarketplaceTrades } from "./trades.js";
14
+ import { NO_SEQ } from "./stomp.js";
15
+ export class DefaultMarketView {
16
+ marketplaceId;
17
+ markets;
18
+ _flexemarkets;
19
+ _orderBooks;
20
+ _trades;
21
+ _events = null;
22
+ _session = null;
23
+ _holding = null;
24
+ _sessionHandlers = [];
25
+ _holdingHandlers = [];
26
+ _bookHandlers = [];
27
+ _gapHandlers = [];
28
+ _reconnectHandlers = [];
29
+ _closed = false;
30
+ /**
31
+ * Highest ORDERS-UPDATE seq this view has applied so far. Deltas
32
+ * with `seq <= _lastAppliedSeq` are skipped — they've already been
33
+ * folded into the local state via the initial REST snapshot or a
34
+ * previously-applied delta. Initial value comes from the snapshot's
35
+ * `asOfSeq`; `NO_SEQ` disables filtering (older fm-server).
36
+ */
37
+ _lastAppliedSeq = NO_SEQ;
38
+ static async open(flexemarkets, marketplaceId) {
39
+ const markets = await flexemarkets.markets(marketplaceId);
40
+ const view = new DefaultMarketView(flexemarkets, marketplaceId, markets);
41
+ // Subscribe WS first (so deltas start buffering on the callback
42
+ // path), then fetch + apply the snapshot, only THEN allow live
43
+ // dispatch. _seedFromSnapshot flips _seedComplete=true atomically
44
+ // with the buffer drain on its synchronous tail.
45
+ //
46
+ // Phase 2d: own the EventListener directly rather than calling
47
+ // flexemarkets.listen() — that would clobber the singleton
48
+ // _eventListener and prevent multiple shared views from
49
+ // coexisting in one Flexemarkets.
50
+ view._events = await flexemarkets._connectEvents(marketplaceId, (event) => view._dispatch(event));
51
+ await view._seedFromSnapshot();
52
+ return view;
53
+ }
54
+ _seedComplete = false;
55
+ _seedBuffer = [];
56
+ // 100 matches the default per-market Trades capacity. Plumb through to
57
+ // observe() later if a caller needs deeper trade scrollback.
58
+ constructor(flexemarkets, marketplaceId, markets) {
59
+ this._flexemarkets = flexemarkets;
60
+ this.marketplaceId = marketplaceId;
61
+ this.markets = markets;
62
+ this._orderBooks = new OrderBooks(markets);
63
+ this._trades = new MarketplaceTrades(markets, 100);
64
+ }
65
+ /**
66
+ * Phase 2a snapshot seeding. Fetches the V1 active-orders and
67
+ * recent-trades snapshots, applies them to the local aggregators,
68
+ * and records the `asOfSeq` so subsequent WS deltas can be
69
+ * filtered to avoid double-applying anything the snapshot already
70
+ * reflects.
71
+ *
72
+ * Known race window: a delta whose order persisted between the
73
+ * server's seq capture and its order read can appear both in the
74
+ * snapshot and in a delta with `seq > asOfSeq`, leading to a
75
+ * double-apply. Same caveat as the existing V0 WS snapshot path.
76
+ * Phase 2b (gap recovery + ID-based dedup) closes the window.
77
+ */
78
+ async _seedFromSnapshot() {
79
+ const orders = await this._flexemarkets.activeOrdersV1(this.marketplaceId);
80
+ const trades = await this._flexemarkets.recentTradesV1(this.marketplaceId);
81
+ // Clear before reseeding so a resync (Phase 2b) doesn't double-add
82
+ // against existing price levels. Initial seed hits empty books so
83
+ // clear() is a no-op there.
84
+ this._orderBooks.clear();
85
+ this._trades.clear();
86
+ if (orders.body.length > 0)
87
+ this._orderBooks.update(orders.body);
88
+ if (trades.body.length > 0)
89
+ this._trades.update(trades.body);
90
+ this._lastAppliedSeq = orders.asOfSeq;
91
+ // Flip state and drain in a single synchronous block — no
92
+ // microtask boundary, so no concurrent dispatch can squeeze in
93
+ // between "buffer drained" and "_seedComplete=true" and leave its
94
+ // delta orphaned in the buffer.
95
+ const buffered = this._seedBuffer;
96
+ this._seedBuffer = [];
97
+ this._seedComplete = true;
98
+ this._resyncInFlight = false;
99
+ for (const update of buffered)
100
+ this._applyOrdersUpdate(update);
101
+ }
102
+ orderBook(marketId) {
103
+ this._ensureOpen();
104
+ return this._orderBooks.get(marketId) ?? null;
105
+ }
106
+ session() {
107
+ this._ensureOpen();
108
+ return this._session;
109
+ }
110
+ holding() {
111
+ this._ensureOpen();
112
+ return this._holding;
113
+ }
114
+ onSessionChange(handler) {
115
+ this._ensureOpen();
116
+ this._sessionHandlers.push(handler);
117
+ return () => {
118
+ const i = this._sessionHandlers.indexOf(handler);
119
+ if (i >= 0)
120
+ this._sessionHandlers.splice(i, 1);
121
+ };
122
+ }
123
+ onOrderBookChange(marketId, handler) {
124
+ this._ensureOpen();
125
+ const entry = { marketId, handler };
126
+ this._bookHandlers.push(entry);
127
+ return () => {
128
+ const i = this._bookHandlers.indexOf(entry);
129
+ if (i >= 0)
130
+ this._bookHandlers.splice(i, 1);
131
+ };
132
+ }
133
+ onHoldingChange(handler) {
134
+ this._ensureOpen();
135
+ this._holdingHandlers.push(handler);
136
+ return () => {
137
+ const i = this._holdingHandlers.indexOf(handler);
138
+ if (i >= 0)
139
+ this._holdingHandlers.splice(i, 1);
140
+ };
141
+ }
142
+ onGap(handler) {
143
+ this._ensureOpen();
144
+ this._gapHandlers.push(handler);
145
+ return () => {
146
+ const i = this._gapHandlers.indexOf(handler);
147
+ if (i >= 0)
148
+ this._gapHandlers.splice(i, 1);
149
+ };
150
+ }
151
+ onReconnect(handler) {
152
+ this._ensureOpen();
153
+ this._reconnectHandlers.push(handler);
154
+ return () => {
155
+ const i = this._reconnectHandlers.indexOf(handler);
156
+ if (i >= 0)
157
+ this._reconnectHandlers.splice(i, 1);
158
+ };
159
+ }
160
+ submitLimit(marketId, side, units, price) {
161
+ this._ensureOpen();
162
+ return this._flexemarkets.submitLimit(this.marketplaceId, marketId, side, units, price);
163
+ }
164
+ submitCancel(marketId, originalId) {
165
+ this._ensureOpen();
166
+ return this._flexemarkets.submitCancel(this.marketplaceId, marketId, originalId);
167
+ }
168
+ close() {
169
+ if (this._closed)
170
+ return;
171
+ this._closed = true;
172
+ if (this._events !== null) {
173
+ try {
174
+ this._events.close();
175
+ }
176
+ catch { /* best-effort */ }
177
+ this._events = null;
178
+ }
179
+ // Flexemarkets is owned by the caller; we don't close it. If
180
+ // observe() was the only consumer, the caller can close
181
+ // Flexemarkets themselves.
182
+ }
183
+ _resyncInFlight = false;
184
+ _dispatch(event) {
185
+ if (this._closed)
186
+ return;
187
+ // FmEvent is a discriminated union.
188
+ if (_isOrdersUpdate(event)) {
189
+ if (!this._seedComplete || this._resyncInFlight) {
190
+ // Buffer until the REST snapshot has landed so we don't
191
+ // apply deltas before the seq watermark is established.
192
+ this._seedBuffer.push(event);
193
+ return;
194
+ }
195
+ // Phase 2b gap detection: a delta with seq > lastAppliedSeq+1
196
+ // means one or more frames were dropped. Refetch the snapshot
197
+ // and let the seq filter skip whatever the new asOfSeq covers.
198
+ if (event.seq !== NO_SEQ &&
199
+ this._lastAppliedSeq !== NO_SEQ &&
200
+ event.seq > this._lastAppliedSeq + 1) {
201
+ const expectedSeq = this._lastAppliedSeq + 1;
202
+ console.warn(`[MarketView] ORDERS-UPDATE seq gap on marketplace ${this.marketplaceId} ` +
203
+ `— expected ${expectedSeq}, got ${event.seq}; resyncing from snapshot`);
204
+ const gap = {
205
+ marketplaceId: this.marketplaceId,
206
+ expectedSeq,
207
+ receivedSeq: event.seq,
208
+ };
209
+ for (const h of this._gapHandlers) {
210
+ try {
211
+ h(gap);
212
+ }
213
+ catch { /* one bad handler can't stop recovery */ }
214
+ }
215
+ this._resyncInFlight = true;
216
+ this._seedComplete = false;
217
+ this._seedBuffer.push(event);
218
+ // _seedFromSnapshot flips both flags back atomically on its
219
+ // synchronous tail; nothing more to do in the .then().
220
+ void this._seedFromSnapshot();
221
+ return;
222
+ }
223
+ this._applyOrdersUpdate(event);
224
+ return;
225
+ }
226
+ if (_isSession(event)) {
227
+ this._session = event;
228
+ for (const h of this._sessionHandlers)
229
+ h(event);
230
+ return;
231
+ }
232
+ if (_isHolding(event)) {
233
+ this._holding = event;
234
+ for (const h of this._holdingHandlers)
235
+ h(event);
236
+ return;
237
+ }
238
+ if (_isTransportError(event)) {
239
+ this._handleTransportError();
240
+ return;
241
+ }
242
+ if (_isWsException(event)) {
243
+ // STOMP ERROR / parse failure. Logged for visibility;
244
+ // reconnecting won't help with a malformed frame, so we leave
245
+ // the view as-is.
246
+ console.warn(`[MarketView] WS error on marketplace ${this.marketplaceId}: ${event.command} ${event.body}`);
247
+ return;
248
+ }
249
+ // VERSION, SESSION-LIST — not reflected in the public surface yet.
250
+ }
251
+ /**
252
+ * Phase 2c auto-reconnect. On a WsTransportError, reconnect the
253
+ * underlying WS and re-seed from the V1 snapshot — reconnect is
254
+ * just the largest possible gap, so 2b's _seedFromSnapshot()
255
+ * (clear + REST snapshot + reapply + seq watermark) handles state
256
+ * convergence. One reconnect attempt; if it fails the view is left
257
+ * stale until the caller close()s and observe()s again.
258
+ */
259
+ _handleTransportError() {
260
+ if (this._resyncInFlight)
261
+ return; // already handling
262
+ console.warn(`[MarketView] WS transport error on marketplace ${this.marketplaceId}; reconnecting`);
263
+ this._resyncInFlight = true;
264
+ this._seedComplete = false;
265
+ void (async () => {
266
+ let outcome;
267
+ try {
268
+ if (this._events !== null)
269
+ await this._events.reconnect();
270
+ await this._seedFromSnapshot();
271
+ outcome = { marketplaceId: this.marketplaceId, success: true, reason: null };
272
+ }
273
+ catch (err) {
274
+ const reason = err instanceof Error ? err.message : String(err);
275
+ console.error(`[MarketView] Reconnect failed on marketplace ${this.marketplaceId}; view is stale: ${reason}`);
276
+ outcome = { marketplaceId: this.marketplaceId, success: false, reason };
277
+ // Leave _resyncInFlight=true so subsequent dispatches keep
278
+ // buffering rather than corrupting state; caller can close()
279
+ // and observe() to recover.
280
+ }
281
+ for (const h of this._reconnectHandlers) {
282
+ try {
283
+ h(outcome);
284
+ }
285
+ catch { /* don't let one bad handler stop dispatch */ }
286
+ }
287
+ })();
288
+ }
289
+ _applyOrdersUpdate(update) {
290
+ // Phase 2a seq filter: drop deltas the snapshot already reflects.
291
+ // NO_SEQ disables filtering for older fm-server builds that don't
292
+ // stamp the header.
293
+ if (update.seq !== NO_SEQ &&
294
+ this._lastAppliedSeq !== NO_SEQ &&
295
+ update.seq <= this._lastAppliedSeq) {
296
+ return;
297
+ }
298
+ const orders = update.orders;
299
+ const touched = _marketIdsTouched(orders);
300
+ this._orderBooks.update(orders);
301
+ this._trades.update(orders);
302
+ for (const marketId of touched) {
303
+ const book = this._orderBooks.get(marketId);
304
+ if (!book)
305
+ continue;
306
+ for (const h of this._bookHandlers) {
307
+ if (h.marketId === marketId)
308
+ h.handler(book);
309
+ }
310
+ }
311
+ if (update.seq !== NO_SEQ)
312
+ this._lastAppliedSeq = update.seq;
313
+ }
314
+ _ensureOpen() {
315
+ if (this._closed) {
316
+ throw new Error(`MarketView for marketplace ${this.marketplaceId} is closed`);
317
+ }
318
+ }
319
+ }
320
+ function _marketIdsTouched(orders) {
321
+ const seen = new Set();
322
+ for (const o of orders)
323
+ seen.add(o.marketId);
324
+ return [...seen];
325
+ }
326
+ function _isOrdersUpdate(event) {
327
+ return typeof event === "object" && event !== null && event.kind === "orders-update";
328
+ }
329
+ function _isSession(event) {
330
+ return typeof event === "object" && event !== null && "status" in event && "marketplaceId" in event;
331
+ }
332
+ function _isHolding(event) {
333
+ return typeof event === "object" && event !== null && "securities" in event;
334
+ }
335
+ function _isTransportError(event) {
336
+ return typeof event === "object" && event !== null && event.kind === "transport-error";
337
+ }
338
+ function _isWsException(event) {
339
+ return typeof event === "object" && event !== null && event.kind === "ws-exception";
340
+ }
341
+ /**
342
+ * Reader-side handle on a refcounted DefaultMarketView. Returned by
343
+ * Flexemarkets.observe(); multiple handles for the same marketplaceId
344
+ * share one underlying view + WS subscription + materialized state.
345
+ * Each handle's close() decrements the shared refcount and tears down
346
+ * the shared resources on the last close.
347
+ *
348
+ * Subscriptions registered via on*Change on this handle are tracked
349
+ * locally and closed when the handle closes — so a handler doesn't
350
+ * keep firing into stale state after the handle is gone.
351
+ */
352
+ export class MarketViewHandle {
353
+ _shared;
354
+ _onClose;
355
+ _mySubscriptions = [];
356
+ _closed = false;
357
+ constructor(shared, onClose) {
358
+ this._shared = shared;
359
+ this._onClose = onClose;
360
+ }
361
+ get marketplaceId() {
362
+ return this._shared.marketplaceId;
363
+ }
364
+ get markets() {
365
+ this._check();
366
+ return this._shared.markets;
367
+ }
368
+ orderBook(marketId) {
369
+ this._check();
370
+ return this._shared.orderBook(marketId);
371
+ }
372
+ session() {
373
+ this._check();
374
+ return this._shared.session();
375
+ }
376
+ holding() {
377
+ this._check();
378
+ return this._shared.holding();
379
+ }
380
+ onSessionChange(handler) {
381
+ this._check();
382
+ const sub = this._shared.onSessionChange(handler);
383
+ this._mySubscriptions.push(sub);
384
+ return sub;
385
+ }
386
+ onOrderBookChange(marketId, handler) {
387
+ this._check();
388
+ const sub = this._shared.onOrderBookChange(marketId, handler);
389
+ this._mySubscriptions.push(sub);
390
+ return sub;
391
+ }
392
+ onHoldingChange(handler) {
393
+ this._check();
394
+ const sub = this._shared.onHoldingChange(handler);
395
+ this._mySubscriptions.push(sub);
396
+ return sub;
397
+ }
398
+ onGap(handler) {
399
+ this._check();
400
+ const sub = this._shared.onGap(handler);
401
+ this._mySubscriptions.push(sub);
402
+ return sub;
403
+ }
404
+ onReconnect(handler) {
405
+ this._check();
406
+ const sub = this._shared.onReconnect(handler);
407
+ this._mySubscriptions.push(sub);
408
+ return sub;
409
+ }
410
+ submitLimit(marketId, side, units, price) {
411
+ this._check();
412
+ return this._shared.submitLimit(marketId, side, units, price);
413
+ }
414
+ submitCancel(marketId, originalId) {
415
+ this._check();
416
+ return this._shared.submitCancel(marketId, originalId);
417
+ }
418
+ close() {
419
+ if (this._closed)
420
+ return;
421
+ this._closed = true;
422
+ // Close subscriptions this handle registered so handlers don't
423
+ // fire into a closed handle. Subscription is a () => void; calling
424
+ // twice is idempotent per the existing TS Subscription contract.
425
+ for (const sub of this._mySubscriptions) {
426
+ try {
427
+ sub();
428
+ }
429
+ catch { /* best-effort */ }
430
+ }
431
+ this._mySubscriptions.length = 0;
432
+ this._onClose();
433
+ }
434
+ _check() {
435
+ if (this._closed) {
436
+ throw new Error(`MarketView handle for marketplace ${this._shared.marketplaceId} is closed`);
437
+ }
438
+ }
439
+ }
440
+ //# sourceMappingURL=market-view.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"market-view.js","sourceRoot":"","sources":["../src/market-view.ts"],"names":[],"mappings":"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"}
@@ -33,6 +33,13 @@ export declare class OrderBook {
33
33
  buyLevels(): [number, number][];
34
34
  /** Price levels sorted lowest-first (best ask first). */
35
35
  sellLevels(): [number, number][];
36
+ /**
37
+ * Reset to just-constructed state — empty levels, initialized=false.
38
+ * Used by MarketView's Phase 2b gap-recovery: refetch the snapshot,
39
+ * clear(), reapply via update(), so the next delta with
40
+ * isAvailable=false doesn't underflow against stale levels.
41
+ */
42
+ clear(): void;
36
43
  }
37
44
  /**
38
45
  * Container of OrderBook instances, one per market.
@@ -45,4 +52,6 @@ export declare class OrderBooks {
45
52
  bestPrice(marketId: number, side: string): number;
46
53
  collection(): OrderBook[];
47
54
  get(marketId: number): OrderBook;
55
+ /** Clear every contained book — see {@link OrderBook.clear}. */
56
+ clear(): void;
48
57
  }
package/dist/orderbook.js CHANGED
@@ -118,6 +118,17 @@ export class OrderBook {
118
118
  sellLevels() {
119
119
  return [...this._sells.entries()].sort((a, b) => a[0] - b[0]);
120
120
  }
121
+ /**
122
+ * Reset to just-constructed state — empty levels, initialized=false.
123
+ * Used by MarketView's Phase 2b gap-recovery: refetch the snapshot,
124
+ * clear(), reapply via update(), so the next delta with
125
+ * isAvailable=false doesn't underflow against stale levels.
126
+ */
127
+ clear() {
128
+ this._buys.clear();
129
+ this._sells.clear();
130
+ this._initialized = false;
131
+ }
121
132
  }
122
133
  /**
123
134
  * Container of OrderBook instances, one per market.
@@ -146,5 +157,10 @@ export class OrderBooks {
146
157
  get(marketId) {
147
158
  return this._books.get(marketId);
148
159
  }
160
+ /** Clear every contained book — see {@link OrderBook.clear}. */
161
+ clear() {
162
+ for (const book of this._books.values())
163
+ book.clear();
164
+ }
149
165
  }
150
166
  //# sourceMappingURL=orderbook.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"orderbook.js","sourceRoot":"","sources":["../src/orderbook.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAG9F;;;;;GAKG;AACH,MAAM,OAAO,SAAS;IACX,MAAM,CAAS;IACP,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC,CAAC,gBAAgB;IACnD,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC,CAAC,gBAAgB;IAC7D,YAAY,GAAG,KAAK,CAAC;IAE7B,YAAY,MAAc;QACxB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,MAAM,CAAC,MAAO,CAAC;IAC7B,CAAC;IAED,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;IACxB,CAAC;IAED,6EAA6E;IAE7E,MAAM,CAAC,MAAe;QACpB,IAAI,YAAY,GAAG,KAAK,CAAC;QAEzB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;gBAAE,SAAS;YAE5C,MAAM,IAAI,GAAG,KAAK,CAAC,IAAK,CAAC;YACzB,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;YAC1B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;YAE1B,6CAA6C;YAC7C,IAAI,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;gBACvB,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;gBAC9B,SAAS;YACX,CAAC;YAED,4DAA4D;YAC5D,IAAI,CAAC,IAAI,CAAC,YAAY;gBAAE,SAAS;YAEjC,uBAAuB;YACvB,IAAI,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;gBACpB,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;gBACjC,SAAS;YACX,CAAC;YAED,gCAAgC;YAChC,IAAI,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;gBACnB,YAAY,GAAG,IAAI,CAAC;gBACpB,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;gBACjC,SAAS;YACX,CAAC;YAED,gDAAgD;YAChD,IAAI,CAAC,YAAY,IAAI,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC;gBAC9C,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;gBACjC,SAAS;YACX,CAAC;QACH,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;YACvB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QAC3B,CAAC;IACH,CAAC;IAEO,IAAI,CAAC,IAAY,EAAE,KAAa,EAAE,KAAa;QACrD,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QACvC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC;IACtD,CAAC;IAEO,OAAO,CAAC,IAAY,EAAE,KAAa,EAAE,KAAa;QACxD,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QACvC,MAAM,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,KAAK,CAAC;QACjD,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;YAChB,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;IAEO,YAAY,CAAC,IAAY;QAC/B,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC;IAChD,CAAC;IAED,6EAA6E;IAE7E,QAAQ,CAAC,IAAY;QACnB,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC;IAC1C,CAAC;IAED,SAAS,CAAC,IAAY;QACpB,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO,CAAC,CAAC,CAAC;QACjC,MAAM,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;QAClC,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC;IACjE,CAAC;IAED,SAAS,CAAC,IAAY;QACpB,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO,CAAC,CAAC,CAAC;QACjC,MAAM,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;QAClC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC;QACrE,OAAO,MAAM,CAAC,GAAG,CAAC,IAAI,CAAE,CAAC;IAC3B,CAAC;IAED,YAAY;QACV,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAED,YAAY;QACV,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAED,aAAa;QACX,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAChC,CAAC;IAED,aAAa;QACX,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAChC,CAAC;IAED,0DAA0D;IAC1D,SAAS;QACP,OAAO,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/D,CAAC;IAED,yDAAyD;IACzD,UAAU;QACR,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAChE,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,UAAU;IACJ,MAAM,GAAG,IAAI,GAAG,EAAqB,CAAC;IAEvD,YAAY,OAAiB;QAC3B,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACxB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;IAED,MAAM,CAAC,MAAe;QACpB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;YACxC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IAED,QAAQ,CAAC,QAAgB,EAAE,IAAY;QACrC,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACnD,CAAC;IAED,SAAS,CAAC,QAAgB,EAAE,IAAY;QACtC,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACpD,CAAC;IAED,UAAU;QACR,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IACnC,CAAC;IAED,GAAG,CAAC,QAAgB;QAClB,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAE,CAAC;IACpC,CAAC;CACF"}
1
+ {"version":3,"file":"orderbook.js","sourceRoot":"","sources":["../src/orderbook.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * REST snapshot response bundle: parsed body + the `x-fm-as-of-seq`
3
+ * header value the snapshot was read at. Returned by `Flexemarkets`
4
+ * V1 snapshot methods so `MarketView` (and any other caller that
5
+ * mixes REST snapshots with WS deltas) can reconcile the two
6
+ * streams: apply deltas whose `seq` is greater than `asOfSeq` and
7
+ * skip those whose seq is less than or equal.
8
+ *
9
+ * `asOfSeq` is `-1` when the server didn't stamp the response
10
+ * (older fm-server). Callers that depend on the seq for correctness
11
+ * should treat `-1` as "snapshot reconciliation unavailable" and
12
+ * either wholesale-replace state on every snapshot fetch or fall
13
+ * back to a periodic poll.
14
+ */
15
+ export interface Snapshot<T> {
16
+ readonly body: T;
17
+ readonly asOfSeq: number;
18
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=snapshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../src/snapshot.ts"],"names":[],"mappings":""}
package/dist/stomp.d.ts CHANGED
@@ -23,8 +23,25 @@ export interface WsException {
23
23
  body: string;
24
24
  exception: Error;
25
25
  }
26
- export type FmEvent = Version | Session | Session[] | Holding | Order[] | WsTransportError | WsException;
26
+ /**
27
+ * ORDERS-UPDATE delta from the WS stream, carrying the parsed orders
28
+ * plus the per-marketplace `seq` header fm-server stamps each frame
29
+ * with. Consumers reconcile this against a REST `Snapshot`: apply
30
+ * deltas whose `seq` is greater than the snapshot's `asOfSeq` and
31
+ * skip those whose seq is less than or equal.
32
+ *
33
+ * `seq` is `-1` when the server didn't stamp the header (older
34
+ * fm-server).
35
+ */
36
+ export interface OrdersUpdate {
37
+ readonly kind: "orders-update";
38
+ readonly orders: Order[];
39
+ readonly seq: number;
40
+ }
41
+ export type FmEvent = Version | Session | Session[] | Holding | OrdersUpdate | WsTransportError | WsException;
27
42
  export type EventCallback = (event: FmEvent) => void;
43
+ /** Sentinel for "no seq header" — see OrdersUpdate.seq docstring. */
44
+ export declare const NO_SEQ = -1;
28
45
  export declare class EventListener {
29
46
  private readonly _wsUrl;
30
47
  private readonly _bearerToken;
package/dist/stomp.js CHANGED
@@ -7,6 +7,27 @@
7
7
  import WebSocket from "ws";
8
8
  const HEARTBEAT_MS = 30_000;
9
9
  const NULL = "\x00";
10
+ /**
11
+ * Prefix on the `/app` SUBSCRIBE destination selecting fm-server's WS
12
+ * API version. Empty string → V0 (`/app/marketplaces/{id}`); `"/v1"` →
13
+ * V1 (`/app/v1/marketplaces/{id}`). V1 omits the bulk ORDERS-UPDATE
14
+ * snapshot on subscribe, keeping inbound frames small. V1 is the
15
+ * default; override with `FM_WS_API_VERSION=v0` if talking to an old
16
+ * fm-server that doesn't speak V1.
17
+ *
18
+ * NB: V1 SUBSCRIBE delivers an empty ORDERS-UPDATE; consumers that
19
+ * need the active book at startup should fetch it via REST
20
+ * (`GET /api/v1/marketplaces/{id}/orders/active`) and reconcile
21
+ * against incoming deltas using the `seq` header.
22
+ */
23
+ const API_VERSION_PREFIX = (() => {
24
+ const v = (process.env.FM_WS_API_VERSION ?? "v1").trim().toLowerCase();
25
+ if (v === "v0")
26
+ return "";
27
+ if (v === "v1")
28
+ return "/v1";
29
+ throw new Error(`FM_WS_API_VERSION must be 'v0' or 'v1', got: ${v}`);
30
+ })();
10
31
  export function encodeFrame(frame) {
11
32
  const lines = [frame.command];
12
33
  for (const [k, v] of Object.entries(frame.headers)) {
@@ -46,6 +67,9 @@ export function decodeFrame(raw) {
46
67
  // Event parsing — mirrors Python _parse_event / Java EventParser
47
68
  // ---------------------------------------------------------------------------
48
69
  const MESSAGE_TYPE = "message-type";
70
+ const SEQ_HEADER = "seq";
71
+ /** Sentinel for "no seq header" — see OrdersUpdate.seq docstring. */
72
+ export const NO_SEQ = -1;
49
73
  function parseEvent(frame, parseHolding, parseOrder) {
50
74
  const msgType = frame.headers[MESSAGE_TYPE];
51
75
  if (msgType === undefined)
@@ -66,12 +90,22 @@ function parseEvent(frame, parseHolding, parseOrder) {
66
90
  return parseSession(data);
67
91
  case "HOLDING-UPDATE":
68
92
  return parseHolding(data);
69
- case "ORDERS-UPDATE":
70
- return parseOrders(data, parseOrder);
93
+ case "ORDERS-UPDATE": {
94
+ const seq = _parseSeq(frame.headers[SEQ_HEADER]);
95
+ return { kind: "orders-update", orders: parseOrders(data, parseOrder), seq };
96
+ }
71
97
  default:
72
98
  return null;
73
99
  }
74
100
  }
101
+ /** Parse the per-frame `seq` header; falls back to NO_SEQ on absent
102
+ * or malformed values. */
103
+ function _parseSeq(value) {
104
+ if (value === undefined)
105
+ return NO_SEQ;
106
+ const n = Number.parseInt(value, 10);
107
+ return Number.isFinite(n) ? n : NO_SEQ;
108
+ }
75
109
  function parseVersion(data) {
76
110
  if (typeof data === "object" && data !== null && "version" in data) {
77
111
  return { version: data.version ?? 0 };
@@ -214,11 +248,18 @@ export class EventListener {
214
248
  this._ws.send(encodeFrame(frame));
215
249
  }
216
250
  _subscribe() {
251
+ // fm-server publishes broadcasts on the V0 destination paths
252
+ // (/topic/marketplaces/{id}, /user/queue/marketplaces/{id}) for
253
+ // both V0 and V1 clients — only the @SubscribeMapping gating the
254
+ // initial snapshot lives at the /v1 prefix. So pub/sub subscriptions
255
+ // stay on V0 paths regardless of the chosen api-version; only the
256
+ // /app destination flips. Mirrors fm-ui's web-socket.service.ts
257
+ // and the Java SDK Events.java pattern.
217
258
  const resource = `/marketplaces/${this._marketplaceId}`;
218
259
  for (const dest of [
219
260
  `/user/queue${resource}`,
220
261
  `/topic${resource}`,
221
- `/app${resource}`,
262
+ `/app${API_VERSION_PREFIX}${resource}`,
222
263
  ]) {
223
264
  const frame = {
224
265
  command: "SUBSCRIBE",