@dxfeed/dxlink-feed
Version:
dxLink FEED provides access to the real-time and historical data of dxFeed
3 lines (2 loc) • 8.63 kB
JavaScript
var FeedContract,FeedDataFormat,dxlinkCore=require("@dxfeed/dxlink-core");exports.FeedContract=void 0,(FeedContract=exports.FeedContract||(exports.FeedContract={})).AUTO="AUTO",FeedContract.TICKER="TICKER",FeedContract.HISTORY="HISTORY",FeedContract.STREAM="STREAM",exports.FeedDataFormat=void 0,(FeedDataFormat=exports.FeedDataFormat||(exports.FeedDataFormat={})).FULL="FULL",FeedDataFormat.COMPACT="COMPACT";const getSubscriptionKey=subscription=>`${subscription.type}${"source"in subscription?`#${subscription.source}`:""}:${subscription.symbol}`;class DXLinkFeed{constructor(client,contract,options={}){this.id=void 0,this.contract=void 0,this.options=void 0,this.channel=void 0,this.acceptConfig={},this.config={aggregationPeriod:NaN,dataFormat:exports.FeedDataFormat.FULL,eventFields:{}},this.configListeners=new Set,this.eventListeners=new Set,this.pendingAdd=new Map,this.pendingRemove=new Map,this.pengingReset=!1,this.subscriptions=new Map,this.touchedEvents=new Set,this.subScheduler=new dxlinkCore.Scheduler,this.logger=void 0,this.getChannel=()=>this.channel,this.getState=()=>this.channel.getState(),this.addStateChangeListener=listener=>this.channel.addStateChangeListener(listener),this.removeStateChangeListener=listener=>this.channel.removeStateChangeListener(listener),this.getConfig=()=>this.config,this.addConfigChangeListener=listener=>{this.configListeners.add(listener)},this.removeConfigChangeListener=listener=>{this.configListeners.delete(listener)},this.close=()=>{this.acceptConfig={},this.configListeners.clear(),this.eventListeners.clear(),this.pendingAdd.clear(),this.pendingRemove.clear(),this.touchedEvents.clear(),this.subscriptions.clear(),this.touchedEvents.clear(),this.subScheduler.clear(),this.channel.close()},this.configure=acceptConfig=>{this.acceptConfig=acceptConfig,this.channel.getState()===dxlinkCore.DXLinkChannelState.OPENED&&this.sendAcceptConfig(this.touchedEvents)},this.clearSubscriptions=()=>{this.pendingAdd.clear(),this.pendingRemove.clear(),this.pengingReset=!0,this.scheduleProcessPendings()},this.addEventListener=listener=>{this.eventListeners.add(listener)},this.removeEventListener=listener=>{this.eventListeners.delete(listener)},this.cleanSubscription=subscription=>{if(this.contract===exports.FeedContract.TICKER){const{type:type,symbol:symbol,...other}=subscription;return Object.keys(other).length>0&&this.logger.warn("Subscription for the TICKER contract should not have any additional fields",subscription),{type:type,symbol:symbol}}return subscription},this.processMessage=message=>{if((message=>"FEED_SETUP"===message.type||"FEED_CONFIG"===message.type||"FEED_SUBSCRIPTION"===message.type||"FEED_DATA"===message.type)(message))switch(message.type){case"FEED_CONFIG":return void this.processConfig(message);case"FEED_DATA":return void this.processData(message)}this.logger.warn("Unknown message",message)},this.processConfig=config=>{const newConfig={aggregationPeriod:config.aggregationPeriod??this.config.aggregationPeriod,dataFormat:config.dataFormat??this.config.dataFormat,eventFields:{...this.config.eventFields??{},...config.eventFields??{}}};this.config=newConfig;for(const listener of this.configListeners)try{listener(newConfig)}catch(error){this.logger.error("Error in config listener",error)}},this.parseEventData=data=>{const{dataFormat:dataFormat,eventFields:eventFields}=this.config;if("FULL"===dataFormat){if((data=>"object"==typeof data[0])(data))return data}else if((data=>data.length>=2&&"string"==typeof data[0]&&Array.isArray(data[1]))(data)){const events=[],[eventType,values]=data,eventFieldsForType=eventFields[eventType];if(void 0===eventFieldsForType)throw new Error("Cannot find event fields for event type in the config");let cursor=0;for(;cursor<values.length;){const event={eventType:eventType};for(const field of eventFieldsForType){const value=values[cursor];if(void 0===value)throw new Error("Not enough values in compact event");event[field]=value,cursor++}events.push(event)}return events}throw new Error("Incoming data does not match the format specified in the config")},this.processData=({data:data})=>{try{const events=this.parseEventData(data);for(const listener of this.eventListeners)try{listener(events)}catch(error){this.logger.error("Error in event listener",error)}}catch(error){return void this.logger.error("Cannot parse data",error)}},this.processStatus=processStatus=>{switch(processStatus){case dxlinkCore.DXLinkChannelState.OPENED:return this.sendAcceptConfig(this.touchedEvents,!0),this.resubscribe();case dxlinkCore.DXLinkChannelState.REQUESTED:return void this.subScheduler.clear();case dxlinkCore.DXLinkChannelState.CLOSED:return this.close()}},this.sendSubscriptionChunkAndSchema=(chunk,newTouchedEvents)=>{this.channel.getState()===dxlinkCore.DXLinkChannelState.OPENED&&(void 0!==newTouchedEvents&&newTouchedEvents.size>0&&this.sendAcceptConfig(newTouchedEvents),this.channel.send({type:"FEED_SUBSCRIPTION",...chunk}))},this.processError=processError=>{this.logger.error("Error in channel",processError)},this.processPendings=()=>{const newTouchedEvents=new Set;let chunk={},chunkSize=0;this.pengingReset&&(chunk.reset=!0,chunkSize+=13,this.pengingReset=!1);for(const[key,subscription]of this.pendingRemove.entries())(chunk.remove??=[]).push(subscription),chunkSize+=key.length+("fromTime"in subscription?34:21),this.subscriptions.delete(getSubscriptionKey(subscription)),chunkSize>=this.options.maxSendSubscriptionChunkSize&&(this.sendSubscriptionChunkAndSchema(chunk),chunk={},chunkSize=0);this.pendingRemove.clear();for(const[key,subscription]of this.pendingAdd.entries())(chunk.add??=[]).push(subscription),chunkSize+=key.length+("fromTime"in subscription?34:21),this.touchedEvents.has(subscription.type)||(newTouchedEvents.add(subscription.type),this.touchedEvents.add(subscription.type)),this.subscriptions.set(key,subscription),chunkSize>=this.options.maxSendSubscriptionChunkSize&&(this.sendSubscriptionChunkAndSchema(chunk,newTouchedEvents),newTouchedEvents.clear(),chunk={},chunkSize=0);this.pendingAdd.clear(),chunkSize>0&&this.sendSubscriptionChunkAndSchema(chunk,newTouchedEvents)},this.sendAcceptConfig=(eventTypes,force=!1)=>{let acceptEventFields;if(void 0!==this.acceptConfig.acceptEventFields)for(const eventType of eventTypes){const eventFields=this.acceptConfig.acceptEventFields[eventType];void 0!==eventFields&&(acceptEventFields??={},acceptEventFields[eventType]=eventFields)}const{acceptAggregationPeriod:acceptAggregationPeriod,acceptDataFormat:acceptDataFormat}=this.acceptConfig;void 0===acceptEventFields&&void 0===acceptAggregationPeriod&&void 0===acceptDataFormat||(force||void 0!==acceptEventFields||void 0!==acceptAggregationPeriod&&acceptAggregationPeriod!==this.config.aggregationPeriod||void 0!==acceptDataFormat&&acceptDataFormat!==this.config.dataFormat)&&this.channel.send({type:"FEED_SETUP",acceptAggregationPeriod:acceptAggregationPeriod,acceptDataFormat:acceptDataFormat,acceptEventFields:acceptEventFields})},this.options={logLevel:dxlinkCore.DXLinkLogLevel.WARN,batchSubscriptionsTime:100,maxSendSubscriptionChunkSize:8192,...options},this.channel=client.openChannel("FEED",{contract:contract,space:options.space}),this.id=this.channel.id,this.contract=contract,this.channel.addMessageListener(this.processMessage),this.channel.addStateChangeListener(this.processStatus),this.channel.addErrorListener(this.processError),this.addSubscriptions=this.addSubscriptions.bind(this),this.removeSubscriptions=this.removeSubscriptions.bind(this),this.logger=new dxlinkCore.Logger(`${DXLinkFeed.name}#${this.id}`,this.options.logLevel)}addSubscriptions(){var args=[].slice.call(arguments);const inputs=Array.isArray(args[0])?args[0]:args;for(const input of inputs){const subscription=this.cleanSubscription(input);this.pendingAdd.set(getSubscriptionKey(subscription),subscription)}this.scheduleProcessPendings()}removeSubscriptions(){var args=[].slice.call(arguments);const inputs=Array.isArray(args[0])?args[0]:args;for(const input of inputs){const subscription=this.cleanSubscription(input),key=getSubscriptionKey(subscription);this.pendingRemove.set(key,subscription),this.pendingAdd.delete(key)}this.scheduleProcessPendings()}resubscribe(){for(const[key,subscription]of this.subscriptions)this.pendingAdd.set(key,subscription);0!==this.pendingAdd.size&&(this.pendingRemove.clear(),this.pengingReset=!0,this.processPendings())}scheduleProcessPendings(){this.subScheduler.has("processPendings")||this.subScheduler.schedule(this.processPendings,this.options.batchSubscriptionsTime,"processPendings")}}exports.DXLinkFeed=DXLinkFeed;
//# sourceMappingURL=index.js.map