@dxfeed/dxlink-feed
Advanced tools
+2
-1
@@ -223,3 +223,4 @@ import { type DXLinkChannel, DXLinkChannelState, type DXLinkChannelStateChangeListener, type DXLinkClient, DXLinkLogLevel } from '@dxfeed/dxlink-core'; | ||
| */ | ||
| private subScheduler; | ||
| private readonly scheduler; | ||
| private readonly processPendingsSchedulerKey; | ||
| private readonly logger; | ||
@@ -226,0 +227,0 @@ /** |
+1
-1
@@ -1,2 +0,2 @@ | ||
| 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,feed:options.feed}),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; | ||
| 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.scheduler=void 0,this.processPendingsSchedulerKey=void 0,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.scheduler.cancel(this.processPendingsSchedulerKey),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.scheduler.cancel(this.processPendingsSchedulerKey);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.scheduler=client.getScheduler(),this.channel=client.openChannel("FEED",{contract:contract,space:options.space,feed:options.feed}),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),this.processPendingsSchedulerKey=`FEED#${this.id}:PROCESS_PENDINGS`}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.scheduler.has(this.processPendingsSchedulerKey)||this.scheduler.schedule(this.processPendings,this.options.batchSubscriptionsTime,this.processPendingsSchedulerKey)}}exports.DXLinkFeed=DXLinkFeed; | ||
| //# sourceMappingURL=index.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.js","sources":["../src/messages.ts","../src/feed.ts"],"sourcesContent":["import { type DXLinkChannelMessage } from '@dxfeed/dxlink-core'\n\nexport enum FeedContract {\n 'AUTO' = 'AUTO',\n 'TICKER' = 'TICKER',\n 'HISTORY' = 'HISTORY',\n 'STREAM' = 'STREAM',\n}\n\nexport enum FeedDataFormat {\n 'FULL' = 'FULL',\n 'COMPACT' = 'COMPACT',\n}\n\nexport interface FeedParameters {\n readonly contract: FeedContract\n}\n\nexport interface FeedEventFields {\n [eventType: string]: string[]\n}\n\nexport interface FeedSetupMessage {\n readonly type: 'FEED_SETUP'\n readonly acceptAggregationPeriod?: number\n readonly acceptDataFormat?: FeedDataFormat\n readonly acceptEventFields?: FeedEventFields\n}\n\nexport interface FeedConfigMessage {\n readonly type: 'FEED_CONFIG'\n readonly aggregationPeriod: number\n readonly dataFormat: FeedDataFormat\n readonly eventFields?: FeedEventFields\n}\n\nexport type Subscription = {\n readonly type: string\n readonly symbol: string\n}\n\nexport type TimeSeriesSubscription = {\n readonly type: string\n readonly symbol: string\n readonly fromTime: number\n}\n\nexport type IndexedEventSubscription = {\n readonly type: string\n readonly symbol: string\n readonly source: string\n}\n\nexport interface FeedSubscriptionMessage {\n readonly type: 'FEED_SUBSCRIPTION'\n readonly add?: (Subscription | TimeSeriesSubscription | IndexedEventSubscription)[]\n readonly remove?: (Subscription | TimeSeriesSubscription | IndexedEventSubscription)[]\n readonly reset?: boolean\n}\n\nexport type FeedEventValue = number | string | boolean\n\nexport interface FeedEventData {\n [key: string]: FeedEventValue\n}\n\nexport type FeedCompactEventData = [string, FeedEventValue[]]\n\nexport interface FeedDataMessage {\n readonly type: 'FEED_DATA'\n readonly data: FeedEventData[] | FeedCompactEventData\n}\n\nexport type FeedMessage =\n | FeedSetupMessage\n | FeedConfigMessage\n | FeedSubscriptionMessage\n | FeedDataMessage\n\nexport const isFeedFullData = (\n data: FeedEventData[] | FeedCompactEventData\n): data is FeedEventData[] => typeof data[0] === 'object'\n\nexport const isFeedCompactData = (\n data: FeedEventData[] | FeedCompactEventData\n): data is FeedCompactEventData =>\n data.length >= 2 && typeof data[0] === 'string' && Array.isArray(data[1])\n\nexport const isFeedMessage = (message: DXLinkChannelMessage): message is FeedMessage =>\n message.type === 'FEED_SETUP' ||\n message.type === 'FEED_CONFIG' ||\n message.type === 'FEED_SUBSCRIPTION' ||\n message.type === 'FEED_DATA'\n","import {\n type DXLinkChannel,\n type DXLinkChannelMessage,\n DXLinkChannelState,\n type DXLinkChannelStateChangeListener,\n type DXLinkError,\n type DXLinkClient,\n DXLinkLogLevel,\n type DXLinkLogger,\n Logger,\n Scheduler,\n} from '@dxfeed/dxlink-core'\n\nimport {\n type FeedEventFields,\n FeedContract,\n FeedDataFormat,\n type IndexedEventSubscription,\n type Subscription,\n type TimeSeriesSubscription,\n type FeedSetupMessage,\n type FeedSubscriptionMessage,\n isFeedMessage,\n isFeedFullData,\n type FeedDataMessage,\n isFeedCompactData,\n type FeedEventData,\n type FeedConfigMessage,\n} from './messages'\n\n/**\n * Prefered configuration for the feed channel.\n * Server can ignore some of the parameters and use own defaults.\n * @see {DXLinkFeed.configure}\n */\nexport interface FeedAcceptConfig {\n /**\n * Aggregation period in seconds.\n * If not specified, the channel will use the default value.\n * If specified as 0, the channel will try not aggregate events.\n */\n acceptAggregationPeriod?: number\n /**\n * Data format to be used for received events.\n * If not specified, the channel will use the default value `FULL`.\n */\n acceptDataFormat?: FeedDataFormat\n /**\n * Event fields to be included in received events.\n * If not specified, the channel will use the default value.\n * If specified as an empty array, the channel will try to send events with default fields.\n */\n acceptEventFields?: FeedEventFields\n}\n\n/**\n * Configuration of the feed channel.\n */\nexport interface FeedConfig {\n /**\n * Aggregation period in seconds.\n * @example 0.5 - 500 milliseconds.\n * @default `NaN`\n * @see {FeedAcceptConfig.acceptAggregationPeriod}\n */\n readonly aggregationPeriod: number\n /**\n * Data format to be used for received events.\n * @example `FULL` - object with keys and values.\n * @example `COMPACT` - array of values.\n * @default `FULL`\n * @see {FeedAcceptConfig.acceptDataFormat}\n */\n readonly dataFormat: FeedDataFormat\n /**\n * Event fields to be included in received events.\n * You can specify fields for all event types or for specific event types @see {FeedAcceptConfig.acceptEventFields}.\n * @example ```json\n * { \"Quote\": [\"eventSymbol\", \"askPrice\", \"bidPrice\"] }\n * ```\n * @default `{}`\n */\n readonly eventFields: FeedEventFields\n}\n\n/**\n * Listener for the feed channel config changes.\n */\nexport type DXLinkFeedConfigChangeListener = (config: FeedConfig) => void\n\ntype AnySubscription = Subscription | TimeSeriesSubscription | IndexedEventSubscription\n\n/**\n * Get a unique key for the subscription.\n */\nconst getSubscriptionKey = (subscription: AnySubscription) =>\n `${subscription.type}${'source' in subscription ? `#${subscription.source}` : ''}:${\n subscription.symbol\n }`\n\n/**\n * Subscription type by the contract.\n */\nexport type SubscriptionByContract = {\n [FeedContract.AUTO]: Subscription | TimeSeriesSubscription | IndexedEventSubscription\n [FeedContract.TICKER]: Subscription\n [FeedContract.HISTORY]: TimeSeriesSubscription | IndexedEventSubscription\n [FeedContract.STREAM]: Subscription | TimeSeriesSubscription | IndexedEventSubscription\n}\n\n/**\n * Listener for the feed channel events received from the channel.\n */\nexport type DXLinkFeedEventListener = (event: FeedEventData[]) => void\n\n/**\n * Chunk of the subscriptions to be sent to the channel.\n */\ninterface FeedSubscriptionChunk {\n add?: AnySubscription[]\n remove?: AnySubscription[]\n reset?: boolean\n}\n\n/**\n * dxLink FEED service instance for the specified {@link FeedContract}.\n */\nexport interface DXLinkFeedRequester<Contract extends FeedContract = FeedContract.AUTO> {\n /**\n * Unique identifier of the feed channel.\n */\n readonly id: number\n /**\n * Contract of the feed channel.\n * @see {FeedContract}\n */\n readonly contract: Contract\n\n /**\n * Get current channel of the feed.\n * Note: inaproppriate usage of the channel can lead to unexpected behavior.\n * @see {DXLinkChannel}\n */\n getChannel(): DXLinkChannel\n\n /**\n * Configure desired configuration of the feed channel.\n * @see {FeedAcceptConfig}\n */\n configure(acceptConfig: FeedAcceptConfig): void\n\n /**\n * Get current configuration of the feed channel as received from the channel.\n */\n getConfig(): FeedConfig\n /**\n * Add a listener for the feed channel config changes.\n */\n addConfigChangeListener(listener: DXLinkFeedConfigChangeListener): void\n /**\n * Remove a listener for the feed channel config changes.\n */\n removeConfigChangeListener(listener: DXLinkFeedConfigChangeListener): void\n\n /**\n * Add subscriptions to the feed channel.\n * @param subscriptions - Subscriptions to be added.\n */\n addSubscriptions(subscriptions: SubscriptionByContract[Contract][]): void\n /**\n * Add subscriptions to the feed channel.\n * @param subscriptions - Subscriptions to be added.\n */\n addSubscriptions(...subscriptions: SubscriptionByContract[Contract][]): void\n /**\n * Remove subscriptions from the feed channel.\n * @param subscriptions - Subscriptions to be removed.\n */\n removeSubscriptions(subscriptions: SubscriptionByContract[Contract][]): void\n /**\n * Remove subscriptions from the feed channel.\n * @param subscriptions - Subscriptions to be removed.\n */\n removeSubscriptions(...subscriptions: SubscriptionByContract[Contract][]): void\n /**\n * Remove all active subscriptions from the feed channel.\n */\n clearSubscriptions(): void\n\n /**\n * Add a listener for the feed channel events received from the channel.\n */\n addEventListener(listener: DXLinkFeedEventListener): void\n /**\n * Remove a listener for the feed channel events received from the channel.\n */\n removeEventListener(listener: DXLinkFeedEventListener): void\n\n /**\n * Close the feed channel.\n */\n close(): void\n}\n\n/**\n * Options for the {@link DXLinkFeed} instance.\n */\nexport interface DXLinkFeedOptions {\n /**\n * Space to be used for the service.\n */\n space?: string\n /**\n * Feed name to be used for the service.\n */\n feed?: string\n /**\n * Time in milliseconds to wait for more pending subscriptions before sending them to the channel.\n */\n batchSubscriptionsTime: number\n /**\n * Maximum size of the subscription chunk to be sent to the channel.\n */\n maxSendSubscriptionChunkSize: number\n /**\n * Log level for the feed.\n */\n logLevel: DXLinkLogLevel\n}\n\nconst FEED_SERVICE_NAME = 'FEED'\n\n/**\n * dxLink FEED provides access to the real-time and historical data of dxFeed.\n */\nexport class DXLinkFeed<Contract extends FeedContract> implements DXLinkFeedRequester<Contract> {\n /**\n * Unique identifier of the feed channel.\n */\n public readonly id: number\n\n /**\n * Contract of the feed channel.\n * @see {FeedContract}\n */\n public contract: Contract\n\n private readonly options: DXLinkFeedOptions\n\n /**\n * dxLink channel instance.\n */\n private readonly channel: DXLinkChannel\n\n /**\n * Current accept config of the feed channel.\n */\n private acceptConfig: FeedAcceptConfig = {}\n\n /**\n * Current config of the feed channel.\n */\n private config: FeedConfig = {\n aggregationPeriod: NaN,\n dataFormat: FeedDataFormat.FULL,\n eventFields: {},\n }\n\n // Listeners\n private readonly configListeners = new Set<DXLinkFeedConfigChangeListener>()\n private readonly eventListeners = new Set<DXLinkFeedEventListener>()\n\n /**\n * Pending add subscriptions to be sent to the channel.\n */\n private readonly pendingAdd = new Map<string, AnySubscription>()\n /**\n * Pending remove subscriptions to be sent to the channel.\n */\n private readonly pendingRemove = new Map<string, AnySubscription>()\n /**\n * Pending reset flag to be sent to the channel.\n */\n private pengingReset = false\n\n /**\n * List of active subscriptions.\n * Used to avoid sending the same subscription twice and re-subscribe on the channel re-open.\n */\n private readonly subscriptions = new Map<string, AnySubscription>()\n\n /**\n * List of event types which schema was sent to the channel.\n */\n private readonly touchedEvents = new Set<string>()\n\n /**\n * Scheduler for scheduling subscriptions sending to the channel.\n */\n private subScheduler: Scheduler = new Scheduler()\n\n private readonly logger: DXLinkLogger\n\n /**\n * Allows to create {@link DXLinkFeed} instance with the specified {@link FeedContract} for the given {@link DXLinkWebSocketClient}.\n */\n constructor(client: DXLinkClient, contract: Contract, options: Partial<DXLinkFeedOptions> = {}) {\n this.options = {\n logLevel: DXLinkLogLevel.WARN,\n batchSubscriptionsTime: 100,\n maxSendSubscriptionChunkSize: 4096 * 2,\n ...options,\n }\n\n this.channel = client.openChannel(FEED_SERVICE_NAME, { \n contract,\n // Optional parameters for FEED source\n space: options.space,\n feed: options.feed\n })\n this.id = this.channel.id\n this.contract = contract\n this.channel.addMessageListener(this.processMessage)\n this.channel.addStateChangeListener(this.processStatus)\n this.channel.addErrorListener(this.processError)\n\n this.addSubscriptions = this.addSubscriptions.bind(this)\n this.removeSubscriptions = this.removeSubscriptions.bind(this)\n\n this.logger = new Logger(`${DXLinkFeed.name}#${this.id}`, this.options.logLevel)\n }\n\n getChannel = () => this.channel\n\n getState = () => this.channel.getState()\n addStateChangeListener = (listener: DXLinkChannelStateChangeListener) =>\n this.channel.addStateChangeListener(listener)\n removeStateChangeListener = (listener: DXLinkChannelStateChangeListener) =>\n this.channel.removeStateChangeListener(listener)\n\n getConfig = () => this.config\n addConfigChangeListener = (listener: DXLinkFeedConfigChangeListener) => {\n this.configListeners.add(listener)\n }\n removeConfigChangeListener = (listener: DXLinkFeedConfigChangeListener) => {\n this.configListeners.delete(listener)\n }\n\n close = () => {\n this.acceptConfig = {}\n\n this.configListeners.clear()\n this.eventListeners.clear()\n\n this.pendingAdd.clear()\n this.pendingRemove.clear()\n this.touchedEvents.clear()\n this.subscriptions.clear()\n this.touchedEvents.clear()\n\n this.subScheduler.clear()\n\n this.channel.close()\n }\n\n configure = (acceptConfig: FeedAcceptConfig) => {\n this.acceptConfig = acceptConfig\n\n // Update touched events list\n if (this.channel.getState() === DXLinkChannelState.OPENED) {\n this.sendAcceptConfig(this.touchedEvents)\n }\n }\n\n addSubscriptions(subscriptions: SubscriptionByContract[Contract][]): void\n addSubscriptions(...subscriptions: SubscriptionByContract[Contract][]): void\n addSubscriptions(...args: unknown[]): void {\n const inputs: SubscriptionByContract[Contract][] = Array.isArray(args[0]) ? args[0] : args\n\n for (const input of inputs) {\n const subscription = this.cleanSubscription(input)\n\n this.pendingAdd.set(getSubscriptionKey(subscription), subscription)\n }\n\n this.scheduleProcessPendings()\n }\n\n removeSubscriptions(subscriptions: SubscriptionByContract[Contract][]): void\n removeSubscriptions(...subscriptions: SubscriptionByContract[Contract][]): void\n removeSubscriptions(...args: unknown[]): void {\n const inputs: SubscriptionByContract[Contract][] = Array.isArray(args[0]) ? args[0] : args\n\n for (const input of inputs) {\n const subscription = this.cleanSubscription(input)\n\n const key = getSubscriptionKey(subscription)\n this.pendingRemove.set(key, subscription)\n this.pendingAdd.delete(key)\n }\n\n this.scheduleProcessPendings()\n }\n\n clearSubscriptions = () => {\n this.pendingAdd.clear()\n this.pendingRemove.clear()\n this.pengingReset = true\n\n this.scheduleProcessPendings()\n }\n\n addEventListener = (listener: DXLinkFeedEventListener) => {\n this.eventListeners.add(listener)\n }\n removeEventListener = (listener: DXLinkFeedEventListener) => {\n this.eventListeners.delete(listener)\n }\n\n /**\n * Clean the subscription from the fields which are not allowed for the specified contract.\n * Note: coze of the TypeScript limitations, we need to clean the subscription from the fields which are not allowed for the specified contract.\n */\n private cleanSubscription = (\n subscription: SubscriptionByContract[Contract]\n ): SubscriptionByContract[Contract] => {\n if (this.contract === FeedContract.TICKER) {\n const { type, symbol, ...other } = subscription\n if (Object.keys(other).length > 0) {\n this.logger.warn(\n 'Subscription for the TICKER contract should not have any additional fields',\n subscription\n )\n }\n return { type, symbol } as unknown as SubscriptionByContract[Contract]\n }\n\n return subscription\n }\n\n /**\n * Process message received in the channel.\n */\n private processMessage = (message: DXLinkChannelMessage) => {\n // Parse message\n if (isFeedMessage(message)) {\n switch (message.type) {\n case 'FEED_CONFIG':\n this.processConfig(message)\n return\n case 'FEED_DATA':\n this.processData(message)\n return\n }\n }\n\n this.logger.warn('Unknown message', message)\n }\n\n /**\n * Process config received from the channel.\n */\n private processConfig = (config: FeedConfigMessage) => {\n // Update config with the new values from the channel\n const newConfig: FeedConfig = {\n aggregationPeriod: config.aggregationPeriod ?? this.config.aggregationPeriod,\n dataFormat: config.dataFormat ?? this.config.dataFormat,\n eventFields: {\n ...(this.config.eventFields ?? {}),\n ...(config.eventFields ?? {}),\n },\n }\n\n this.config = newConfig\n\n // Notify listeners\n for (const listener of this.configListeners) {\n try {\n listener(newConfig)\n } catch (error) {\n this.logger.error('Error in config listener', error)\n }\n }\n }\n\n /**\n * Parse data received from the channel.\n */\n private parseEventData = (data: FeedDataMessage['data']) => {\n const { dataFormat, eventFields } = this.config\n if (dataFormat === 'FULL') {\n if (isFeedFullData(data)) {\n // If data is already in the full format, just return it\n return data\n }\n } else if (isFeedCompactData(data)) {\n // If data is in the compact format, parse it\n const events: FeedEventData[] = []\n\n const [eventType, values] = data\n const eventFieldsForType = eventFields[eventType]\n if (eventFieldsForType === undefined) {\n throw new Error('Cannot find event fields for event type in the config')\n }\n\n // Split values into events\n let cursor = 0\n while (cursor < values.length) {\n const event: FeedEventData = {\n eventType,\n }\n\n // Build event object from the values\n for (const field of eventFieldsForType) {\n const value = values[cursor]\n if (value === undefined) {\n throw new Error('Not enough values in compact event')\n }\n event[field] = value\n cursor++\n }\n events.push(event)\n }\n\n return events\n }\n\n throw new Error('Incoming data does not match the format specified in the config')\n }\n\n /**\n * Process data received from the channel.\n */\n private processData = ({ data }: FeedDataMessage) => {\n try {\n const events = this.parseEventData(data)\n\n for (const listener of this.eventListeners) {\n try {\n listener(events)\n } catch (error) {\n this.logger.error('Error in event listener', error)\n }\n }\n } catch (error) {\n this.logger.error('Cannot parse data', error)\n return\n }\n }\n\n /**\n * Process channel status changes from the channel.\n */\n private processStatus = (processStatus: DXLinkChannelState) => {\n switch (processStatus) {\n case DXLinkChannelState.OPENED: {\n this.sendAcceptConfig(this.touchedEvents, true)\n\n return this.resubscribe()\n }\n case DXLinkChannelState.REQUESTED: {\n // Clear the timeout if it is set to avoid sending the subscriptions while the channel is not ready\n this.subScheduler.clear()\n return\n }\n case DXLinkChannelState.CLOSED:\n // Destroy the channel if it is closed by the channel\n return this.close()\n }\n }\n\n /**\n * Send the subscription chunk to the channel.\n * @param chunk Subscription chunk to be sent to the channel.\n * @param newTouchedEvents List of event types which schema should be sent to the channel before the chunk.\n * @returns\n */\n private sendSubscriptionChunkAndSchema = (\n chunk: FeedSubscriptionChunk,\n newTouchedEvents?: Set<string>\n ) => {\n if (this.channel.getState() !== DXLinkChannelState.OPENED) return // If the channel is not ready, just exit\n\n if (newTouchedEvents !== undefined && newTouchedEvents.size > 0) {\n this.sendAcceptConfig(newTouchedEvents)\n }\n\n this.channel.send({\n type: 'FEED_SUBSCRIPTION',\n ...chunk,\n } satisfies FeedSubscriptionMessage)\n }\n\n /**\n * Process error received from the channel.\n */\n private processError = (processError: DXLinkError) => {\n this.logger.error('Error in channel', processError)\n }\n\n /**\n * Resubscribe to the feed channel subscriptions after the channel re-open.\n */\n private resubscribe() {\n // Merge pending add subscriptions with current subscriptions\n for (const [key, subscription] of this.subscriptions) {\n this.pendingAdd.set(key, subscription)\n }\n\n if (this.pendingAdd.size === 0) return // If there is no subscriptions to send, just exit\n\n // Clear pending remove subscriptions\n this.pendingRemove.clear()\n this.pengingReset = true\n\n // Send the subscriptions to the channel imidiately\n this.processPendings()\n }\n\n /**\n * Schedule sending pending subscriptions to the channel to batch them together to reduce the number of messages.\n */\n private scheduleProcessPendings() {\n if (this.subScheduler.has('processPendings')) {\n return\n }\n\n this.subScheduler.schedule(\n this.processPendings,\n this.options.batchSubscriptionsTime,\n 'processPendings'\n )\n }\n\n /**\n * Process pending subscriptions and send them to the channel.\n */\n private processPendings = () => {\n const newTouchedEvents = new Set<string>() // New events to be sent to the channel\n let chunk: FeedSubscriptionChunk = {} // Chunk to be sent to the channel\n let chunkSize = 0 // Approximate size of the chunk in bytes\n\n // Add `reset` flag to the chunk\n if (this.pengingReset) {\n chunk.reset = true\n chunkSize += 13 // ',\"reset\":true'.length\n this.pengingReset = false\n }\n\n // Add `remove` subscriptions to the chunk\n for (const [key, subscription] of this.pendingRemove.entries()) {\n ;(chunk.remove ??= []).push(subscription)\n chunkSize += key.length + ('fromTime' in subscription ? 34 : 21) // Approximate size of the subscription in bytes\n\n // Remove the subscription from the active subscriptions\n this.subscriptions.delete(getSubscriptionKey(subscription))\n\n // Send the chunk if it is too big already\n if (chunkSize >= this.options.maxSendSubscriptionChunkSize) {\n this.sendSubscriptionChunkAndSchema(chunk)\n chunk = {}\n chunkSize = 0\n }\n }\n this.pendingRemove.clear()\n\n // Add `add` subscriptions to the chunk\n for (const [key, subscription] of this.pendingAdd.entries()) {\n ;(chunk.add ??= []).push(subscription)\n chunkSize += key.length + ('fromTime' in subscription ? 34 : 21) // Approximate size of the subscription in bytes\n\n // Add the event type to the new touched events if it is not touched yet\n if (!this.touchedEvents.has(subscription.type)) {\n newTouchedEvents.add(subscription.type)\n this.touchedEvents.add(subscription.type)\n }\n\n // Add the subscription to the active subscriptions\n this.subscriptions.set(key, subscription)\n\n // Send the chunk if it is too big already\n if (chunkSize >= this.options.maxSendSubscriptionChunkSize) {\n this.sendSubscriptionChunkAndSchema(chunk, newTouchedEvents)\n newTouchedEvents.clear()\n chunk = {}\n chunkSize = 0\n }\n }\n this.pendingAdd.clear()\n\n // Send the last chunk\n if (chunkSize > 0) {\n this.sendSubscriptionChunkAndSchema(chunk, newTouchedEvents)\n }\n }\n\n /**\n * Send the `FEED_SETUP` message to the channel with the event fields for the specified event types.\n * @param eventTypes List of event type fields to be sent to the channel.\n * @param force If `true`, the config will be sent to the channel even if there is no event fields to send.\n */\n private sendAcceptConfig = (eventTypes: Set<string>, force: boolean = false) => {\n let acceptEventFields: FeedEventFields | undefined\n\n // Get event fields for the specified event types\n if (this.acceptConfig.acceptEventFields !== undefined) {\n for (const eventType of eventTypes) {\n const eventFields = this.acceptConfig.acceptEventFields[eventType]\n if (eventFields !== undefined) {\n acceptEventFields ??= {}\n acceptEventFields[eventType] = eventFields\n }\n }\n }\n\n const { acceptAggregationPeriod, acceptDataFormat } = this.acceptConfig\n\n // Check if there is anything to send\n if (\n acceptEventFields === undefined &&\n acceptAggregationPeriod === undefined &&\n acceptDataFormat === undefined\n ) {\n return // Nothing to send\n }\n\n // Send the config to the channel if there is event fields to send or if it is forced to send\n // or if the aggregation period or data format was changed\n if (\n force ||\n acceptEventFields !== undefined ||\n (acceptAggregationPeriod !== undefined &&\n acceptAggregationPeriod !== this.config.aggregationPeriod) ||\n (acceptDataFormat !== undefined && acceptDataFormat !== this.config.dataFormat)\n ) {\n this.channel.send({\n type: 'FEED_SETUP',\n acceptAggregationPeriod,\n acceptDataFormat,\n acceptEventFields,\n } satisfies FeedSetupMessage)\n }\n }\n}\n"],"names":["FeedContract","FeedDataFormat","getSubscriptionKey","subscription","type","source","symbol","DXLinkFeed","constructor","client","contract","options","this","id","channel","acceptConfig","config","aggregationPeriod","NaN","dataFormat","FULL","eventFields","configListeners","Set","eventListeners","pendingAdd","Map","pendingRemove","pengingReset","subscriptions","touchedEvents","subScheduler","Scheduler","logger","getChannel","getState","addStateChangeListener","listener","removeStateChangeListener","getConfig","addConfigChangeListener","add","removeConfigChangeListener","delete","close","clear","configure","DXLinkChannelState","OPENED","sendAcceptConfig","clearSubscriptions","scheduleProcessPendings","addEventListener","removeEventListener","cleanSubscription","TICKER","other","Object","keys","length","warn","processMessage","message","isFeedMessage","processConfig","processData","newConfig","error","parseEventData","data","isFeedFullData","Array","isArray","isFeedCompactData","events","eventType","values","eventFieldsForType","undefined","Error","cursor","event","field","value","push","processStatus","resubscribe","REQUESTED","CLOSED","sendSubscriptionChunkAndSchema","chunk","newTouchedEvents","size","send","processError","processPendings","chunkSize","reset","key","entries","remove","maxSendSubscriptionChunkSize","has","set","eventTypes","force","acceptEventFields","acceptAggregationPeriod","acceptDataFormat","logLevel","DXLinkLogLevel","WARN","batchSubscriptionsTime","openChannel","space","feed","addMessageListener","addErrorListener","addSubscriptions","bind","removeSubscriptions","Logger","name","args","slice","call","arguments","inputs","input","schedule"],"mappings":"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"} | ||
| {"version":3,"file":"index.js","sources":["../src/messages.ts","../src/feed.ts"],"sourcesContent":["import { type DXLinkChannelMessage } from '@dxfeed/dxlink-core'\n\nexport enum FeedContract {\n 'AUTO' = 'AUTO',\n 'TICKER' = 'TICKER',\n 'HISTORY' = 'HISTORY',\n 'STREAM' = 'STREAM',\n}\n\nexport enum FeedDataFormat {\n 'FULL' = 'FULL',\n 'COMPACT' = 'COMPACT',\n}\n\nexport interface FeedParameters {\n readonly contract: FeedContract\n}\n\nexport interface FeedEventFields {\n [eventType: string]: string[]\n}\n\nexport interface FeedSetupMessage {\n readonly type: 'FEED_SETUP'\n readonly acceptAggregationPeriod?: number\n readonly acceptDataFormat?: FeedDataFormat\n readonly acceptEventFields?: FeedEventFields\n}\n\nexport interface FeedConfigMessage {\n readonly type: 'FEED_CONFIG'\n readonly aggregationPeriod: number\n readonly dataFormat: FeedDataFormat\n readonly eventFields?: FeedEventFields\n}\n\nexport type Subscription = {\n readonly type: string\n readonly symbol: string\n}\n\nexport type TimeSeriesSubscription = {\n readonly type: string\n readonly symbol: string\n readonly fromTime: number\n}\n\nexport type IndexedEventSubscription = {\n readonly type: string\n readonly symbol: string\n readonly source: string\n}\n\nexport interface FeedSubscriptionMessage {\n readonly type: 'FEED_SUBSCRIPTION'\n readonly add?: (Subscription | TimeSeriesSubscription | IndexedEventSubscription)[]\n readonly remove?: (Subscription | TimeSeriesSubscription | IndexedEventSubscription)[]\n readonly reset?: boolean\n}\n\nexport type FeedEventValue = number | string | boolean\n\nexport interface FeedEventData {\n [key: string]: FeedEventValue\n}\n\nexport type FeedCompactEventData = [string, FeedEventValue[]]\n\nexport interface FeedDataMessage {\n readonly type: 'FEED_DATA'\n readonly data: FeedEventData[] | FeedCompactEventData\n}\n\nexport type FeedMessage =\n | FeedSetupMessage\n | FeedConfigMessage\n | FeedSubscriptionMessage\n | FeedDataMessage\n\nexport const isFeedFullData = (\n data: FeedEventData[] | FeedCompactEventData\n): data is FeedEventData[] => typeof data[0] === 'object'\n\nexport const isFeedCompactData = (\n data: FeedEventData[] | FeedCompactEventData\n): data is FeedCompactEventData =>\n data.length >= 2 && typeof data[0] === 'string' && Array.isArray(data[1])\n\nexport const isFeedMessage = (message: DXLinkChannelMessage): message is FeedMessage =>\n message.type === 'FEED_SETUP' ||\n message.type === 'FEED_CONFIG' ||\n message.type === 'FEED_SUBSCRIPTION' ||\n message.type === 'FEED_DATA'\n","import {\n type DXLinkChannel,\n type DXLinkChannelMessage,\n DXLinkChannelState,\n type DXLinkChannelStateChangeListener,\n type DXLinkError,\n type DXLinkClient,\n DXLinkLogLevel,\n type DXLinkLogger,\n Logger,\n type DXLinkScheduler,\n} from '@dxfeed/dxlink-core'\n\nimport {\n type FeedEventFields,\n FeedContract,\n FeedDataFormat,\n type IndexedEventSubscription,\n type Subscription,\n type TimeSeriesSubscription,\n type FeedSetupMessage,\n type FeedSubscriptionMessage,\n isFeedMessage,\n isFeedFullData,\n type FeedDataMessage,\n isFeedCompactData,\n type FeedEventData,\n type FeedConfigMessage,\n} from './messages'\n\n/**\n * Prefered configuration for the feed channel.\n * Server can ignore some of the parameters and use own defaults.\n * @see {DXLinkFeed.configure}\n */\nexport interface FeedAcceptConfig {\n /**\n * Aggregation period in seconds.\n * If not specified, the channel will use the default value.\n * If specified as 0, the channel will try not aggregate events.\n */\n acceptAggregationPeriod?: number\n /**\n * Data format to be used for received events.\n * If not specified, the channel will use the default value `FULL`.\n */\n acceptDataFormat?: FeedDataFormat\n /**\n * Event fields to be included in received events.\n * If not specified, the channel will use the default value.\n * If specified as an empty array, the channel will try to send events with default fields.\n */\n acceptEventFields?: FeedEventFields\n}\n\n/**\n * Configuration of the feed channel.\n */\nexport interface FeedConfig {\n /**\n * Aggregation period in seconds.\n * @example 0.5 - 500 milliseconds.\n * @default `NaN`\n * @see {FeedAcceptConfig.acceptAggregationPeriod}\n */\n readonly aggregationPeriod: number\n /**\n * Data format to be used for received events.\n * @example `FULL` - object with keys and values.\n * @example `COMPACT` - array of values.\n * @default `FULL`\n * @see {FeedAcceptConfig.acceptDataFormat}\n */\n readonly dataFormat: FeedDataFormat\n /**\n * Event fields to be included in received events.\n * You can specify fields for all event types or for specific event types @see {FeedAcceptConfig.acceptEventFields}.\n * @example ```json\n * { \"Quote\": [\"eventSymbol\", \"askPrice\", \"bidPrice\"] }\n * ```\n * @default `{}`\n */\n readonly eventFields: FeedEventFields\n}\n\n/**\n * Listener for the feed channel config changes.\n */\nexport type DXLinkFeedConfigChangeListener = (config: FeedConfig) => void\n\ntype AnySubscription = Subscription | TimeSeriesSubscription | IndexedEventSubscription\n\n/**\n * Get a unique key for the subscription.\n */\nconst getSubscriptionKey = (subscription: AnySubscription) =>\n `${subscription.type}${'source' in subscription ? `#${subscription.source}` : ''}:${\n subscription.symbol\n }`\n\n/**\n * Subscription type by the contract.\n */\nexport type SubscriptionByContract = {\n [FeedContract.AUTO]: Subscription | TimeSeriesSubscription | IndexedEventSubscription\n [FeedContract.TICKER]: Subscription\n [FeedContract.HISTORY]: TimeSeriesSubscription | IndexedEventSubscription\n [FeedContract.STREAM]: Subscription | TimeSeriesSubscription | IndexedEventSubscription\n}\n\n/**\n * Listener for the feed channel events received from the channel.\n */\nexport type DXLinkFeedEventListener = (event: FeedEventData[]) => void\n\n/**\n * Chunk of the subscriptions to be sent to the channel.\n */\ninterface FeedSubscriptionChunk {\n add?: AnySubscription[]\n remove?: AnySubscription[]\n reset?: boolean\n}\n\n/**\n * dxLink FEED service instance for the specified {@link FeedContract}.\n */\nexport interface DXLinkFeedRequester<Contract extends FeedContract = FeedContract.AUTO> {\n /**\n * Unique identifier of the feed channel.\n */\n readonly id: number\n /**\n * Contract of the feed channel.\n * @see {FeedContract}\n */\n readonly contract: Contract\n\n /**\n * Get current channel of the feed.\n * Note: inaproppriate usage of the channel can lead to unexpected behavior.\n * @see {DXLinkChannel}\n */\n getChannel(): DXLinkChannel\n\n /**\n * Configure desired configuration of the feed channel.\n * @see {FeedAcceptConfig}\n */\n configure(acceptConfig: FeedAcceptConfig): void\n\n /**\n * Get current configuration of the feed channel as received from the channel.\n */\n getConfig(): FeedConfig\n /**\n * Add a listener for the feed channel config changes.\n */\n addConfigChangeListener(listener: DXLinkFeedConfigChangeListener): void\n /**\n * Remove a listener for the feed channel config changes.\n */\n removeConfigChangeListener(listener: DXLinkFeedConfigChangeListener): void\n\n /**\n * Add subscriptions to the feed channel.\n * @param subscriptions - Subscriptions to be added.\n */\n addSubscriptions(subscriptions: SubscriptionByContract[Contract][]): void\n /**\n * Add subscriptions to the feed channel.\n * @param subscriptions - Subscriptions to be added.\n */\n addSubscriptions(...subscriptions: SubscriptionByContract[Contract][]): void\n /**\n * Remove subscriptions from the feed channel.\n * @param subscriptions - Subscriptions to be removed.\n */\n removeSubscriptions(subscriptions: SubscriptionByContract[Contract][]): void\n /**\n * Remove subscriptions from the feed channel.\n * @param subscriptions - Subscriptions to be removed.\n */\n removeSubscriptions(...subscriptions: SubscriptionByContract[Contract][]): void\n /**\n * Remove all active subscriptions from the feed channel.\n */\n clearSubscriptions(): void\n\n /**\n * Add a listener for the feed channel events received from the channel.\n */\n addEventListener(listener: DXLinkFeedEventListener): void\n /**\n * Remove a listener for the feed channel events received from the channel.\n */\n removeEventListener(listener: DXLinkFeedEventListener): void\n\n /**\n * Close the feed channel.\n */\n close(): void\n}\n\n/**\n * Options for the {@link DXLinkFeed} instance.\n */\nexport interface DXLinkFeedOptions {\n /**\n * Space to be used for the service.\n */\n space?: string\n /**\n * Feed name to be used for the service.\n */\n feed?: string\n /**\n * Time in milliseconds to wait for more pending subscriptions before sending them to the channel.\n */\n batchSubscriptionsTime: number\n /**\n * Maximum size of the subscription chunk to be sent to the channel.\n */\n maxSendSubscriptionChunkSize: number\n /**\n * Log level for the feed.\n */\n logLevel: DXLinkLogLevel\n}\n\nconst FEED_SERVICE_NAME = 'FEED'\n\n/**\n * dxLink FEED provides access to the real-time and historical data of dxFeed.\n */\nexport class DXLinkFeed<Contract extends FeedContract> implements DXLinkFeedRequester<Contract> {\n /**\n * Unique identifier of the feed channel.\n */\n public readonly id: number\n\n /**\n * Contract of the feed channel.\n * @see {FeedContract}\n */\n public contract: Contract\n\n private readonly options: DXLinkFeedOptions\n\n /**\n * dxLink channel instance.\n */\n private readonly channel: DXLinkChannel\n\n /**\n * Current accept config of the feed channel.\n */\n private acceptConfig: FeedAcceptConfig = {}\n\n /**\n * Current config of the feed channel.\n */\n private config: FeedConfig = {\n aggregationPeriod: NaN,\n dataFormat: FeedDataFormat.FULL,\n eventFields: {},\n }\n\n // Listeners\n private readonly configListeners = new Set<DXLinkFeedConfigChangeListener>()\n private readonly eventListeners = new Set<DXLinkFeedEventListener>()\n\n /**\n * Pending add subscriptions to be sent to the channel.\n */\n private readonly pendingAdd = new Map<string, AnySubscription>()\n /**\n * Pending remove subscriptions to be sent to the channel.\n */\n private readonly pendingRemove = new Map<string, AnySubscription>()\n /**\n * Pending reset flag to be sent to the channel.\n */\n private pengingReset = false\n\n /**\n * List of active subscriptions.\n * Used to avoid sending the same subscription twice and re-subscribe on the channel re-open.\n */\n private readonly subscriptions = new Map<string, AnySubscription>()\n\n /**\n * List of event types which schema was sent to the channel.\n */\n private readonly touchedEvents = new Set<string>()\n\n /**\n * Scheduler for scheduling subscriptions sending to the channel.\n */\n private readonly scheduler: DXLinkScheduler\n private readonly processPendingsSchedulerKey: string\n\n private readonly logger: DXLinkLogger\n\n /**\n * Allows to create {@link DXLinkFeed} instance with the specified {@link FeedContract} for the given {@link DXLinkWebSocketClient}.\n */\n constructor(client: DXLinkClient, contract: Contract, options: Partial<DXLinkFeedOptions> = {}) {\n this.options = {\n logLevel: DXLinkLogLevel.WARN,\n batchSubscriptionsTime: 100,\n maxSendSubscriptionChunkSize: 4096 * 2,\n ...options,\n }\n this.scheduler = client.getScheduler()\n\n this.channel = client.openChannel(FEED_SERVICE_NAME, {\n contract,\n // Optional parameters for FEED source\n space: options.space,\n feed: options.feed,\n })\n this.id = this.channel.id\n this.contract = contract\n this.channel.addMessageListener(this.processMessage)\n this.channel.addStateChangeListener(this.processStatus)\n this.channel.addErrorListener(this.processError)\n\n this.addSubscriptions = this.addSubscriptions.bind(this)\n this.removeSubscriptions = this.removeSubscriptions.bind(this)\n\n this.logger = new Logger(`${DXLinkFeed.name}#${this.id}`, this.options.logLevel)\n\n this.processPendingsSchedulerKey = `${FEED_SERVICE_NAME}#${this.id}:PROCESS_PENDINGS`\n }\n\n getChannel = () => this.channel\n\n getState = () => this.channel.getState()\n addStateChangeListener = (listener: DXLinkChannelStateChangeListener) =>\n this.channel.addStateChangeListener(listener)\n removeStateChangeListener = (listener: DXLinkChannelStateChangeListener) =>\n this.channel.removeStateChangeListener(listener)\n\n getConfig = () => this.config\n addConfigChangeListener = (listener: DXLinkFeedConfigChangeListener) => {\n this.configListeners.add(listener)\n }\n removeConfigChangeListener = (listener: DXLinkFeedConfigChangeListener) => {\n this.configListeners.delete(listener)\n }\n\n close = () => {\n this.acceptConfig = {}\n\n this.configListeners.clear()\n this.eventListeners.clear()\n\n this.pendingAdd.clear()\n this.pendingRemove.clear()\n this.touchedEvents.clear()\n this.subscriptions.clear()\n this.touchedEvents.clear()\n\n this.scheduler.cancel(this.processPendingsSchedulerKey)\n\n this.channel.close()\n }\n\n configure = (acceptConfig: FeedAcceptConfig) => {\n this.acceptConfig = acceptConfig\n\n // Update touched events list\n if (this.channel.getState() === DXLinkChannelState.OPENED) {\n this.sendAcceptConfig(this.touchedEvents)\n }\n }\n\n addSubscriptions(subscriptions: SubscriptionByContract[Contract][]): void\n addSubscriptions(...subscriptions: SubscriptionByContract[Contract][]): void\n addSubscriptions(...args: unknown[]): void {\n const inputs: SubscriptionByContract[Contract][] = Array.isArray(args[0]) ? args[0] : args\n\n for (const input of inputs) {\n const subscription = this.cleanSubscription(input)\n\n this.pendingAdd.set(getSubscriptionKey(subscription), subscription)\n }\n\n this.scheduleProcessPendings()\n }\n\n removeSubscriptions(subscriptions: SubscriptionByContract[Contract][]): void\n removeSubscriptions(...subscriptions: SubscriptionByContract[Contract][]): void\n removeSubscriptions(...args: unknown[]): void {\n const inputs: SubscriptionByContract[Contract][] = Array.isArray(args[0]) ? args[0] : args\n\n for (const input of inputs) {\n const subscription = this.cleanSubscription(input)\n\n const key = getSubscriptionKey(subscription)\n this.pendingRemove.set(key, subscription)\n this.pendingAdd.delete(key)\n }\n\n this.scheduleProcessPendings()\n }\n\n clearSubscriptions = () => {\n this.pendingAdd.clear()\n this.pendingRemove.clear()\n this.pengingReset = true\n\n this.scheduleProcessPendings()\n }\n\n addEventListener = (listener: DXLinkFeedEventListener) => {\n this.eventListeners.add(listener)\n }\n removeEventListener = (listener: DXLinkFeedEventListener) => {\n this.eventListeners.delete(listener)\n }\n\n /**\n * Clean the subscription from the fields which are not allowed for the specified contract.\n * Note: coze of the TypeScript limitations, we need to clean the subscription from the fields which are not allowed for the specified contract.\n */\n private cleanSubscription = (\n subscription: SubscriptionByContract[Contract]\n ): SubscriptionByContract[Contract] => {\n if (this.contract === FeedContract.TICKER) {\n const { type, symbol, ...other } = subscription\n if (Object.keys(other).length > 0) {\n this.logger.warn(\n 'Subscription for the TICKER contract should not have any additional fields',\n subscription\n )\n }\n return { type, symbol } as unknown as SubscriptionByContract[Contract]\n }\n\n return subscription\n }\n\n /**\n * Process message received in the channel.\n */\n private processMessage = (message: DXLinkChannelMessage) => {\n // Parse message\n if (isFeedMessage(message)) {\n switch (message.type) {\n case 'FEED_CONFIG':\n this.processConfig(message)\n return\n case 'FEED_DATA':\n this.processData(message)\n return\n }\n }\n\n this.logger.warn('Unknown message', message)\n }\n\n /**\n * Process config received from the channel.\n */\n private processConfig = (config: FeedConfigMessage) => {\n // Update config with the new values from the channel\n const newConfig: FeedConfig = {\n aggregationPeriod: config.aggregationPeriod ?? this.config.aggregationPeriod,\n dataFormat: config.dataFormat ?? this.config.dataFormat,\n eventFields: {\n ...(this.config.eventFields ?? {}),\n ...(config.eventFields ?? {}),\n },\n }\n\n this.config = newConfig\n\n // Notify listeners\n for (const listener of this.configListeners) {\n try {\n listener(newConfig)\n } catch (error) {\n this.logger.error('Error in config listener', error)\n }\n }\n }\n\n /**\n * Parse data received from the channel.\n */\n private parseEventData = (data: FeedDataMessage['data']) => {\n const { dataFormat, eventFields } = this.config\n if (dataFormat === 'FULL') {\n if (isFeedFullData(data)) {\n // If data is already in the full format, just return it\n return data\n }\n } else if (isFeedCompactData(data)) {\n // If data is in the compact format, parse it\n const events: FeedEventData[] = []\n\n const [eventType, values] = data\n const eventFieldsForType = eventFields[eventType]\n if (eventFieldsForType === undefined) {\n throw new Error('Cannot find event fields for event type in the config')\n }\n\n // Split values into events\n let cursor = 0\n while (cursor < values.length) {\n const event: FeedEventData = {\n eventType,\n }\n\n // Build event object from the values\n for (const field of eventFieldsForType) {\n const value = values[cursor]\n if (value === undefined) {\n throw new Error('Not enough values in compact event')\n }\n event[field] = value\n cursor++\n }\n events.push(event)\n }\n\n return events\n }\n\n throw new Error('Incoming data does not match the format specified in the config')\n }\n\n /**\n * Process data received from the channel.\n */\n private processData = ({ data }: FeedDataMessage) => {\n try {\n const events = this.parseEventData(data)\n\n for (const listener of this.eventListeners) {\n try {\n listener(events)\n } catch (error) {\n this.logger.error('Error in event listener', error)\n }\n }\n } catch (error) {\n this.logger.error('Cannot parse data', error)\n return\n }\n }\n\n /**\n * Process channel status changes from the channel.\n */\n private processStatus = (processStatus: DXLinkChannelState) => {\n switch (processStatus) {\n case DXLinkChannelState.OPENED: {\n this.sendAcceptConfig(this.touchedEvents, true)\n\n return this.resubscribe()\n }\n case DXLinkChannelState.REQUESTED: {\n // Clear the timeout if it is set to avoid sending the subscriptions while the channel is not ready\n this.scheduler.cancel(this.processPendingsSchedulerKey)\n return\n }\n case DXLinkChannelState.CLOSED:\n // Destroy the channel if it is closed by the channel\n return this.close()\n }\n }\n\n /**\n * Send the subscription chunk to the channel.\n * @param chunk Subscription chunk to be sent to the channel.\n * @param newTouchedEvents List of event types which schema should be sent to the channel before the chunk.\n * @returns\n */\n private sendSubscriptionChunkAndSchema = (\n chunk: FeedSubscriptionChunk,\n newTouchedEvents?: Set<string>\n ) => {\n if (this.channel.getState() !== DXLinkChannelState.OPENED) return // If the channel is not ready, just exit\n\n if (newTouchedEvents !== undefined && newTouchedEvents.size > 0) {\n this.sendAcceptConfig(newTouchedEvents)\n }\n\n this.channel.send({\n type: 'FEED_SUBSCRIPTION',\n ...chunk,\n } satisfies FeedSubscriptionMessage)\n }\n\n /**\n * Process error received from the channel.\n */\n private processError = (processError: DXLinkError) => {\n this.logger.error('Error in channel', processError)\n }\n\n /**\n * Resubscribe to the feed channel subscriptions after the channel re-open.\n */\n private resubscribe() {\n // Merge pending add subscriptions with current subscriptions\n for (const [key, subscription] of this.subscriptions) {\n this.pendingAdd.set(key, subscription)\n }\n\n if (this.pendingAdd.size === 0) return // If there is no subscriptions to send, just exit\n\n // Clear pending remove subscriptions\n this.pendingRemove.clear()\n this.pengingReset = true\n\n // Send the subscriptions to the channel imidiately\n this.processPendings()\n }\n\n /**\n * Schedule sending pending subscriptions to the channel to batch them together to reduce the number of messages.\n */\n private scheduleProcessPendings() {\n if (this.scheduler.has(this.processPendingsSchedulerKey)) {\n return\n }\n\n this.scheduler.schedule(\n this.processPendings,\n this.options.batchSubscriptionsTime,\n this.processPendingsSchedulerKey\n )\n }\n\n /**\n * Process pending subscriptions and send them to the channel.\n */\n private processPendings = () => {\n const newTouchedEvents = new Set<string>() // New events to be sent to the channel\n let chunk: FeedSubscriptionChunk = {} // Chunk to be sent to the channel\n let chunkSize = 0 // Approximate size of the chunk in bytes\n\n // Add `reset` flag to the chunk\n if (this.pengingReset) {\n chunk.reset = true\n chunkSize += 13 // ',\"reset\":true'.length\n this.pengingReset = false\n }\n\n // Add `remove` subscriptions to the chunk\n for (const [key, subscription] of this.pendingRemove.entries()) {\n ;(chunk.remove ??= []).push(subscription)\n chunkSize += key.length + ('fromTime' in subscription ? 34 : 21) // Approximate size of the subscription in bytes\n\n // Remove the subscription from the active subscriptions\n this.subscriptions.delete(getSubscriptionKey(subscription))\n\n // Send the chunk if it is too big already\n if (chunkSize >= this.options.maxSendSubscriptionChunkSize) {\n this.sendSubscriptionChunkAndSchema(chunk)\n chunk = {}\n chunkSize = 0\n }\n }\n this.pendingRemove.clear()\n\n // Add `add` subscriptions to the chunk\n for (const [key, subscription] of this.pendingAdd.entries()) {\n ;(chunk.add ??= []).push(subscription)\n chunkSize += key.length + ('fromTime' in subscription ? 34 : 21) // Approximate size of the subscription in bytes\n\n // Add the event type to the new touched events if it is not touched yet\n if (!this.touchedEvents.has(subscription.type)) {\n newTouchedEvents.add(subscription.type)\n this.touchedEvents.add(subscription.type)\n }\n\n // Add the subscription to the active subscriptions\n this.subscriptions.set(key, subscription)\n\n // Send the chunk if it is too big already\n if (chunkSize >= this.options.maxSendSubscriptionChunkSize) {\n this.sendSubscriptionChunkAndSchema(chunk, newTouchedEvents)\n newTouchedEvents.clear()\n chunk = {}\n chunkSize = 0\n }\n }\n this.pendingAdd.clear()\n\n // Send the last chunk\n if (chunkSize > 0) {\n this.sendSubscriptionChunkAndSchema(chunk, newTouchedEvents)\n }\n }\n\n /**\n * Send the `FEED_SETUP` message to the channel with the event fields for the specified event types.\n * @param eventTypes List of event type fields to be sent to the channel.\n * @param force If `true`, the config will be sent to the channel even if there is no event fields to send.\n */\n private sendAcceptConfig = (eventTypes: Set<string>, force: boolean = false) => {\n let acceptEventFields: FeedEventFields | undefined\n\n // Get event fields for the specified event types\n if (this.acceptConfig.acceptEventFields !== undefined) {\n for (const eventType of eventTypes) {\n const eventFields = this.acceptConfig.acceptEventFields[eventType]\n if (eventFields !== undefined) {\n acceptEventFields ??= {}\n acceptEventFields[eventType] = eventFields\n }\n }\n }\n\n const { acceptAggregationPeriod, acceptDataFormat } = this.acceptConfig\n\n // Check if there is anything to send\n if (\n acceptEventFields === undefined &&\n acceptAggregationPeriod === undefined &&\n acceptDataFormat === undefined\n ) {\n return // Nothing to send\n }\n\n // Send the config to the channel if there is event fields to send or if it is forced to send\n // or if the aggregation period or data format was changed\n if (\n force ||\n acceptEventFields !== undefined ||\n (acceptAggregationPeriod !== undefined &&\n acceptAggregationPeriod !== this.config.aggregationPeriod) ||\n (acceptDataFormat !== undefined && acceptDataFormat !== this.config.dataFormat)\n ) {\n this.channel.send({\n type: 'FEED_SETUP',\n acceptAggregationPeriod,\n acceptDataFormat,\n acceptEventFields,\n } satisfies FeedSetupMessage)\n }\n }\n}\n"],"names":["FeedContract","FeedDataFormat","getSubscriptionKey","subscription","type","source","symbol","DXLinkFeed","constructor","client","contract","options","id","channel","acceptConfig","this","config","aggregationPeriod","NaN","dataFormat","FULL","eventFields","configListeners","Set","eventListeners","pendingAdd","Map","pendingRemove","pengingReset","subscriptions","touchedEvents","scheduler","processPendingsSchedulerKey","logger","getChannel","getState","addStateChangeListener","listener","removeStateChangeListener","getConfig","addConfigChangeListener","add","removeConfigChangeListener","delete","close","clear","cancel","configure","DXLinkChannelState","OPENED","sendAcceptConfig","clearSubscriptions","scheduleProcessPendings","addEventListener","removeEventListener","cleanSubscription","TICKER","other","Object","keys","length","warn","processMessage","message","isFeedMessage","processConfig","processData","newConfig","error","parseEventData","data","isFeedFullData","Array","isArray","isFeedCompactData","events","eventType","values","eventFieldsForType","undefined","Error","cursor","event","field","value","push","processStatus","resubscribe","REQUESTED","CLOSED","sendSubscriptionChunkAndSchema","chunk","newTouchedEvents","size","send","processError","processPendings","chunkSize","reset","key","entries","remove","maxSendSubscriptionChunkSize","has","set","eventTypes","force","acceptEventFields","acceptAggregationPeriod","acceptDataFormat","logLevel","DXLinkLogLevel","WARN","batchSubscriptionsTime","getScheduler","openChannel","space","feed","addMessageListener","addErrorListener","addSubscriptions","bind","removeSubscriptions","Logger","name","args","slice","call","arguments","inputs","input","schedule"],"mappings":"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"} |
@@ -1,2 +0,2 @@ | ||
| import{Scheduler,DXLinkChannelState,DXLinkLogLevel,Logger}from"@dxfeed/dxlink-core";var FeedContract,FeedDataFormat;!function(FeedContract){FeedContract.AUTO="AUTO",FeedContract.TICKER="TICKER",FeedContract.HISTORY="HISTORY",FeedContract.STREAM="STREAM"}(FeedContract||(FeedContract={})),function(FeedDataFormat){FeedDataFormat.FULL="FULL",FeedDataFormat.COMPACT="COMPACT"}(FeedDataFormat||(FeedDataFormat={}));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: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 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()===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===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 DXLinkChannelState.OPENED:return this.sendAcceptConfig(this.touchedEvents,!0),this.resubscribe();case DXLinkChannelState.REQUESTED:return void this.subScheduler.clear();case DXLinkChannelState.CLOSED:return this.close()}},this.sendSubscriptionChunkAndSchema=(chunk,newTouchedEvents)=>{this.channel.getState()===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:DXLinkLogLevel.WARN,batchSubscriptionsTime:100,maxSendSubscriptionChunkSize:8192,...options},this.channel=client.openChannel("FEED",{contract:contract,space:options.space,feed:options.feed}),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 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")}}export{DXLinkFeed,FeedContract,FeedDataFormat}; | ||
| import{DXLinkChannelState,DXLinkLogLevel,Logger}from"@dxfeed/dxlink-core";var FeedContract,FeedDataFormat;!function(FeedContract){FeedContract.AUTO="AUTO",FeedContract.TICKER="TICKER",FeedContract.HISTORY="HISTORY",FeedContract.STREAM="STREAM"}(FeedContract||(FeedContract={})),function(FeedDataFormat){FeedDataFormat.FULL="FULL",FeedDataFormat.COMPACT="COMPACT"}(FeedDataFormat||(FeedDataFormat={}));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: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.scheduler=void 0,this.processPendingsSchedulerKey=void 0,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.scheduler.cancel(this.processPendingsSchedulerKey),this.channel.close()},this.configure=acceptConfig=>{this.acceptConfig=acceptConfig,this.channel.getState()===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===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 DXLinkChannelState.OPENED:return this.sendAcceptConfig(this.touchedEvents,!0),this.resubscribe();case DXLinkChannelState.REQUESTED:return void this.scheduler.cancel(this.processPendingsSchedulerKey);case DXLinkChannelState.CLOSED:return this.close()}},this.sendSubscriptionChunkAndSchema=(chunk,newTouchedEvents)=>{this.channel.getState()===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:DXLinkLogLevel.WARN,batchSubscriptionsTime:100,maxSendSubscriptionChunkSize:8192,...options},this.scheduler=client.getScheduler(),this.channel=client.openChannel("FEED",{contract:contract,space:options.space,feed:options.feed}),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 Logger(`${DXLinkFeed.name}#${this.id}`,this.options.logLevel),this.processPendingsSchedulerKey=`FEED#${this.id}:PROCESS_PENDINGS`}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.scheduler.has(this.processPendingsSchedulerKey)||this.scheduler.schedule(this.processPendings,this.options.batchSubscriptionsTime,this.processPendingsSchedulerKey)}}export{DXLinkFeed,FeedContract,FeedDataFormat}; | ||
| //# sourceMappingURL=index.module.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.module.js","sources":["../src/messages.ts","../src/feed.ts"],"sourcesContent":["import { type DXLinkChannelMessage } from '@dxfeed/dxlink-core'\n\nexport enum FeedContract {\n 'AUTO' = 'AUTO',\n 'TICKER' = 'TICKER',\n 'HISTORY' = 'HISTORY',\n 'STREAM' = 'STREAM',\n}\n\nexport enum FeedDataFormat {\n 'FULL' = 'FULL',\n 'COMPACT' = 'COMPACT',\n}\n\nexport interface FeedParameters {\n readonly contract: FeedContract\n}\n\nexport interface FeedEventFields {\n [eventType: string]: string[]\n}\n\nexport interface FeedSetupMessage {\n readonly type: 'FEED_SETUP'\n readonly acceptAggregationPeriod?: number\n readonly acceptDataFormat?: FeedDataFormat\n readonly acceptEventFields?: FeedEventFields\n}\n\nexport interface FeedConfigMessage {\n readonly type: 'FEED_CONFIG'\n readonly aggregationPeriod: number\n readonly dataFormat: FeedDataFormat\n readonly eventFields?: FeedEventFields\n}\n\nexport type Subscription = {\n readonly type: string\n readonly symbol: string\n}\n\nexport type TimeSeriesSubscription = {\n readonly type: string\n readonly symbol: string\n readonly fromTime: number\n}\n\nexport type IndexedEventSubscription = {\n readonly type: string\n readonly symbol: string\n readonly source: string\n}\n\nexport interface FeedSubscriptionMessage {\n readonly type: 'FEED_SUBSCRIPTION'\n readonly add?: (Subscription | TimeSeriesSubscription | IndexedEventSubscription)[]\n readonly remove?: (Subscription | TimeSeriesSubscription | IndexedEventSubscription)[]\n readonly reset?: boolean\n}\n\nexport type FeedEventValue = number | string | boolean\n\nexport interface FeedEventData {\n [key: string]: FeedEventValue\n}\n\nexport type FeedCompactEventData = [string, FeedEventValue[]]\n\nexport interface FeedDataMessage {\n readonly type: 'FEED_DATA'\n readonly data: FeedEventData[] | FeedCompactEventData\n}\n\nexport type FeedMessage =\n | FeedSetupMessage\n | FeedConfigMessage\n | FeedSubscriptionMessage\n | FeedDataMessage\n\nexport const isFeedFullData = (\n data: FeedEventData[] | FeedCompactEventData\n): data is FeedEventData[] => typeof data[0] === 'object'\n\nexport const isFeedCompactData = (\n data: FeedEventData[] | FeedCompactEventData\n): data is FeedCompactEventData =>\n data.length >= 2 && typeof data[0] === 'string' && Array.isArray(data[1])\n\nexport const isFeedMessage = (message: DXLinkChannelMessage): message is FeedMessage =>\n message.type === 'FEED_SETUP' ||\n message.type === 'FEED_CONFIG' ||\n message.type === 'FEED_SUBSCRIPTION' ||\n message.type === 'FEED_DATA'\n","import {\n type DXLinkChannel,\n type DXLinkChannelMessage,\n DXLinkChannelState,\n type DXLinkChannelStateChangeListener,\n type DXLinkError,\n type DXLinkClient,\n DXLinkLogLevel,\n type DXLinkLogger,\n Logger,\n Scheduler,\n} from '@dxfeed/dxlink-core'\n\nimport {\n type FeedEventFields,\n FeedContract,\n FeedDataFormat,\n type IndexedEventSubscription,\n type Subscription,\n type TimeSeriesSubscription,\n type FeedSetupMessage,\n type FeedSubscriptionMessage,\n isFeedMessage,\n isFeedFullData,\n type FeedDataMessage,\n isFeedCompactData,\n type FeedEventData,\n type FeedConfigMessage,\n} from './messages'\n\n/**\n * Prefered configuration for the feed channel.\n * Server can ignore some of the parameters and use own defaults.\n * @see {DXLinkFeed.configure}\n */\nexport interface FeedAcceptConfig {\n /**\n * Aggregation period in seconds.\n * If not specified, the channel will use the default value.\n * If specified as 0, the channel will try not aggregate events.\n */\n acceptAggregationPeriod?: number\n /**\n * Data format to be used for received events.\n * If not specified, the channel will use the default value `FULL`.\n */\n acceptDataFormat?: FeedDataFormat\n /**\n * Event fields to be included in received events.\n * If not specified, the channel will use the default value.\n * If specified as an empty array, the channel will try to send events with default fields.\n */\n acceptEventFields?: FeedEventFields\n}\n\n/**\n * Configuration of the feed channel.\n */\nexport interface FeedConfig {\n /**\n * Aggregation period in seconds.\n * @example 0.5 - 500 milliseconds.\n * @default `NaN`\n * @see {FeedAcceptConfig.acceptAggregationPeriod}\n */\n readonly aggregationPeriod: number\n /**\n * Data format to be used for received events.\n * @example `FULL` - object with keys and values.\n * @example `COMPACT` - array of values.\n * @default `FULL`\n * @see {FeedAcceptConfig.acceptDataFormat}\n */\n readonly dataFormat: FeedDataFormat\n /**\n * Event fields to be included in received events.\n * You can specify fields for all event types or for specific event types @see {FeedAcceptConfig.acceptEventFields}.\n * @example ```json\n * { \"Quote\": [\"eventSymbol\", \"askPrice\", \"bidPrice\"] }\n * ```\n * @default `{}`\n */\n readonly eventFields: FeedEventFields\n}\n\n/**\n * Listener for the feed channel config changes.\n */\nexport type DXLinkFeedConfigChangeListener = (config: FeedConfig) => void\n\ntype AnySubscription = Subscription | TimeSeriesSubscription | IndexedEventSubscription\n\n/**\n * Get a unique key for the subscription.\n */\nconst getSubscriptionKey = (subscription: AnySubscription) =>\n `${subscription.type}${'source' in subscription ? `#${subscription.source}` : ''}:${\n subscription.symbol\n }`\n\n/**\n * Subscription type by the contract.\n */\nexport type SubscriptionByContract = {\n [FeedContract.AUTO]: Subscription | TimeSeriesSubscription | IndexedEventSubscription\n [FeedContract.TICKER]: Subscription\n [FeedContract.HISTORY]: TimeSeriesSubscription | IndexedEventSubscription\n [FeedContract.STREAM]: Subscription | TimeSeriesSubscription | IndexedEventSubscription\n}\n\n/**\n * Listener for the feed channel events received from the channel.\n */\nexport type DXLinkFeedEventListener = (event: FeedEventData[]) => void\n\n/**\n * Chunk of the subscriptions to be sent to the channel.\n */\ninterface FeedSubscriptionChunk {\n add?: AnySubscription[]\n remove?: AnySubscription[]\n reset?: boolean\n}\n\n/**\n * dxLink FEED service instance for the specified {@link FeedContract}.\n */\nexport interface DXLinkFeedRequester<Contract extends FeedContract = FeedContract.AUTO> {\n /**\n * Unique identifier of the feed channel.\n */\n readonly id: number\n /**\n * Contract of the feed channel.\n * @see {FeedContract}\n */\n readonly contract: Contract\n\n /**\n * Get current channel of the feed.\n * Note: inaproppriate usage of the channel can lead to unexpected behavior.\n * @see {DXLinkChannel}\n */\n getChannel(): DXLinkChannel\n\n /**\n * Configure desired configuration of the feed channel.\n * @see {FeedAcceptConfig}\n */\n configure(acceptConfig: FeedAcceptConfig): void\n\n /**\n * Get current configuration of the feed channel as received from the channel.\n */\n getConfig(): FeedConfig\n /**\n * Add a listener for the feed channel config changes.\n */\n addConfigChangeListener(listener: DXLinkFeedConfigChangeListener): void\n /**\n * Remove a listener for the feed channel config changes.\n */\n removeConfigChangeListener(listener: DXLinkFeedConfigChangeListener): void\n\n /**\n * Add subscriptions to the feed channel.\n * @param subscriptions - Subscriptions to be added.\n */\n addSubscriptions(subscriptions: SubscriptionByContract[Contract][]): void\n /**\n * Add subscriptions to the feed channel.\n * @param subscriptions - Subscriptions to be added.\n */\n addSubscriptions(...subscriptions: SubscriptionByContract[Contract][]): void\n /**\n * Remove subscriptions from the feed channel.\n * @param subscriptions - Subscriptions to be removed.\n */\n removeSubscriptions(subscriptions: SubscriptionByContract[Contract][]): void\n /**\n * Remove subscriptions from the feed channel.\n * @param subscriptions - Subscriptions to be removed.\n */\n removeSubscriptions(...subscriptions: SubscriptionByContract[Contract][]): void\n /**\n * Remove all active subscriptions from the feed channel.\n */\n clearSubscriptions(): void\n\n /**\n * Add a listener for the feed channel events received from the channel.\n */\n addEventListener(listener: DXLinkFeedEventListener): void\n /**\n * Remove a listener for the feed channel events received from the channel.\n */\n removeEventListener(listener: DXLinkFeedEventListener): void\n\n /**\n * Close the feed channel.\n */\n close(): void\n}\n\n/**\n * Options for the {@link DXLinkFeed} instance.\n */\nexport interface DXLinkFeedOptions {\n /**\n * Space to be used for the service.\n */\n space?: string\n /**\n * Feed name to be used for the service.\n */\n feed?: string\n /**\n * Time in milliseconds to wait for more pending subscriptions before sending them to the channel.\n */\n batchSubscriptionsTime: number\n /**\n * Maximum size of the subscription chunk to be sent to the channel.\n */\n maxSendSubscriptionChunkSize: number\n /**\n * Log level for the feed.\n */\n logLevel: DXLinkLogLevel\n}\n\nconst FEED_SERVICE_NAME = 'FEED'\n\n/**\n * dxLink FEED provides access to the real-time and historical data of dxFeed.\n */\nexport class DXLinkFeed<Contract extends FeedContract> implements DXLinkFeedRequester<Contract> {\n /**\n * Unique identifier of the feed channel.\n */\n public readonly id: number\n\n /**\n * Contract of the feed channel.\n * @see {FeedContract}\n */\n public contract: Contract\n\n private readonly options: DXLinkFeedOptions\n\n /**\n * dxLink channel instance.\n */\n private readonly channel: DXLinkChannel\n\n /**\n * Current accept config of the feed channel.\n */\n private acceptConfig: FeedAcceptConfig = {}\n\n /**\n * Current config of the feed channel.\n */\n private config: FeedConfig = {\n aggregationPeriod: NaN,\n dataFormat: FeedDataFormat.FULL,\n eventFields: {},\n }\n\n // Listeners\n private readonly configListeners = new Set<DXLinkFeedConfigChangeListener>()\n private readonly eventListeners = new Set<DXLinkFeedEventListener>()\n\n /**\n * Pending add subscriptions to be sent to the channel.\n */\n private readonly pendingAdd = new Map<string, AnySubscription>()\n /**\n * Pending remove subscriptions to be sent to the channel.\n */\n private readonly pendingRemove = new Map<string, AnySubscription>()\n /**\n * Pending reset flag to be sent to the channel.\n */\n private pengingReset = false\n\n /**\n * List of active subscriptions.\n * Used to avoid sending the same subscription twice and re-subscribe on the channel re-open.\n */\n private readonly subscriptions = new Map<string, AnySubscription>()\n\n /**\n * List of event types which schema was sent to the channel.\n */\n private readonly touchedEvents = new Set<string>()\n\n /**\n * Scheduler for scheduling subscriptions sending to the channel.\n */\n private subScheduler: Scheduler = new Scheduler()\n\n private readonly logger: DXLinkLogger\n\n /**\n * Allows to create {@link DXLinkFeed} instance with the specified {@link FeedContract} for the given {@link DXLinkWebSocketClient}.\n */\n constructor(client: DXLinkClient, contract: Contract, options: Partial<DXLinkFeedOptions> = {}) {\n this.options = {\n logLevel: DXLinkLogLevel.WARN,\n batchSubscriptionsTime: 100,\n maxSendSubscriptionChunkSize: 4096 * 2,\n ...options,\n }\n\n this.channel = client.openChannel(FEED_SERVICE_NAME, { \n contract,\n // Optional parameters for FEED source\n space: options.space,\n feed: options.feed\n })\n this.id = this.channel.id\n this.contract = contract\n this.channel.addMessageListener(this.processMessage)\n this.channel.addStateChangeListener(this.processStatus)\n this.channel.addErrorListener(this.processError)\n\n this.addSubscriptions = this.addSubscriptions.bind(this)\n this.removeSubscriptions = this.removeSubscriptions.bind(this)\n\n this.logger = new Logger(`${DXLinkFeed.name}#${this.id}`, this.options.logLevel)\n }\n\n getChannel = () => this.channel\n\n getState = () => this.channel.getState()\n addStateChangeListener = (listener: DXLinkChannelStateChangeListener) =>\n this.channel.addStateChangeListener(listener)\n removeStateChangeListener = (listener: DXLinkChannelStateChangeListener) =>\n this.channel.removeStateChangeListener(listener)\n\n getConfig = () => this.config\n addConfigChangeListener = (listener: DXLinkFeedConfigChangeListener) => {\n this.configListeners.add(listener)\n }\n removeConfigChangeListener = (listener: DXLinkFeedConfigChangeListener) => {\n this.configListeners.delete(listener)\n }\n\n close = () => {\n this.acceptConfig = {}\n\n this.configListeners.clear()\n this.eventListeners.clear()\n\n this.pendingAdd.clear()\n this.pendingRemove.clear()\n this.touchedEvents.clear()\n this.subscriptions.clear()\n this.touchedEvents.clear()\n\n this.subScheduler.clear()\n\n this.channel.close()\n }\n\n configure = (acceptConfig: FeedAcceptConfig) => {\n this.acceptConfig = acceptConfig\n\n // Update touched events list\n if (this.channel.getState() === DXLinkChannelState.OPENED) {\n this.sendAcceptConfig(this.touchedEvents)\n }\n }\n\n addSubscriptions(subscriptions: SubscriptionByContract[Contract][]): void\n addSubscriptions(...subscriptions: SubscriptionByContract[Contract][]): void\n addSubscriptions(...args: unknown[]): void {\n const inputs: SubscriptionByContract[Contract][] = Array.isArray(args[0]) ? args[0] : args\n\n for (const input of inputs) {\n const subscription = this.cleanSubscription(input)\n\n this.pendingAdd.set(getSubscriptionKey(subscription), subscription)\n }\n\n this.scheduleProcessPendings()\n }\n\n removeSubscriptions(subscriptions: SubscriptionByContract[Contract][]): void\n removeSubscriptions(...subscriptions: SubscriptionByContract[Contract][]): void\n removeSubscriptions(...args: unknown[]): void {\n const inputs: SubscriptionByContract[Contract][] = Array.isArray(args[0]) ? args[0] : args\n\n for (const input of inputs) {\n const subscription = this.cleanSubscription(input)\n\n const key = getSubscriptionKey(subscription)\n this.pendingRemove.set(key, subscription)\n this.pendingAdd.delete(key)\n }\n\n this.scheduleProcessPendings()\n }\n\n clearSubscriptions = () => {\n this.pendingAdd.clear()\n this.pendingRemove.clear()\n this.pengingReset = true\n\n this.scheduleProcessPendings()\n }\n\n addEventListener = (listener: DXLinkFeedEventListener) => {\n this.eventListeners.add(listener)\n }\n removeEventListener = (listener: DXLinkFeedEventListener) => {\n this.eventListeners.delete(listener)\n }\n\n /**\n * Clean the subscription from the fields which are not allowed for the specified contract.\n * Note: coze of the TypeScript limitations, we need to clean the subscription from the fields which are not allowed for the specified contract.\n */\n private cleanSubscription = (\n subscription: SubscriptionByContract[Contract]\n ): SubscriptionByContract[Contract] => {\n if (this.contract === FeedContract.TICKER) {\n const { type, symbol, ...other } = subscription\n if (Object.keys(other).length > 0) {\n this.logger.warn(\n 'Subscription for the TICKER contract should not have any additional fields',\n subscription\n )\n }\n return { type, symbol } as unknown as SubscriptionByContract[Contract]\n }\n\n return subscription\n }\n\n /**\n * Process message received in the channel.\n */\n private processMessage = (message: DXLinkChannelMessage) => {\n // Parse message\n if (isFeedMessage(message)) {\n switch (message.type) {\n case 'FEED_CONFIG':\n this.processConfig(message)\n return\n case 'FEED_DATA':\n this.processData(message)\n return\n }\n }\n\n this.logger.warn('Unknown message', message)\n }\n\n /**\n * Process config received from the channel.\n */\n private processConfig = (config: FeedConfigMessage) => {\n // Update config with the new values from the channel\n const newConfig: FeedConfig = {\n aggregationPeriod: config.aggregationPeriod ?? this.config.aggregationPeriod,\n dataFormat: config.dataFormat ?? this.config.dataFormat,\n eventFields: {\n ...(this.config.eventFields ?? {}),\n ...(config.eventFields ?? {}),\n },\n }\n\n this.config = newConfig\n\n // Notify listeners\n for (const listener of this.configListeners) {\n try {\n listener(newConfig)\n } catch (error) {\n this.logger.error('Error in config listener', error)\n }\n }\n }\n\n /**\n * Parse data received from the channel.\n */\n private parseEventData = (data: FeedDataMessage['data']) => {\n const { dataFormat, eventFields } = this.config\n if (dataFormat === 'FULL') {\n if (isFeedFullData(data)) {\n // If data is already in the full format, just return it\n return data\n }\n } else if (isFeedCompactData(data)) {\n // If data is in the compact format, parse it\n const events: FeedEventData[] = []\n\n const [eventType, values] = data\n const eventFieldsForType = eventFields[eventType]\n if (eventFieldsForType === undefined) {\n throw new Error('Cannot find event fields for event type in the config')\n }\n\n // Split values into events\n let cursor = 0\n while (cursor < values.length) {\n const event: FeedEventData = {\n eventType,\n }\n\n // Build event object from the values\n for (const field of eventFieldsForType) {\n const value = values[cursor]\n if (value === undefined) {\n throw new Error('Not enough values in compact event')\n }\n event[field] = value\n cursor++\n }\n events.push(event)\n }\n\n return events\n }\n\n throw new Error('Incoming data does not match the format specified in the config')\n }\n\n /**\n * Process data received from the channel.\n */\n private processData = ({ data }: FeedDataMessage) => {\n try {\n const events = this.parseEventData(data)\n\n for (const listener of this.eventListeners) {\n try {\n listener(events)\n } catch (error) {\n this.logger.error('Error in event listener', error)\n }\n }\n } catch (error) {\n this.logger.error('Cannot parse data', error)\n return\n }\n }\n\n /**\n * Process channel status changes from the channel.\n */\n private processStatus = (processStatus: DXLinkChannelState) => {\n switch (processStatus) {\n case DXLinkChannelState.OPENED: {\n this.sendAcceptConfig(this.touchedEvents, true)\n\n return this.resubscribe()\n }\n case DXLinkChannelState.REQUESTED: {\n // Clear the timeout if it is set to avoid sending the subscriptions while the channel is not ready\n this.subScheduler.clear()\n return\n }\n case DXLinkChannelState.CLOSED:\n // Destroy the channel if it is closed by the channel\n return this.close()\n }\n }\n\n /**\n * Send the subscription chunk to the channel.\n * @param chunk Subscription chunk to be sent to the channel.\n * @param newTouchedEvents List of event types which schema should be sent to the channel before the chunk.\n * @returns\n */\n private sendSubscriptionChunkAndSchema = (\n chunk: FeedSubscriptionChunk,\n newTouchedEvents?: Set<string>\n ) => {\n if (this.channel.getState() !== DXLinkChannelState.OPENED) return // If the channel is not ready, just exit\n\n if (newTouchedEvents !== undefined && newTouchedEvents.size > 0) {\n this.sendAcceptConfig(newTouchedEvents)\n }\n\n this.channel.send({\n type: 'FEED_SUBSCRIPTION',\n ...chunk,\n } satisfies FeedSubscriptionMessage)\n }\n\n /**\n * Process error received from the channel.\n */\n private processError = (processError: DXLinkError) => {\n this.logger.error('Error in channel', processError)\n }\n\n /**\n * Resubscribe to the feed channel subscriptions after the channel re-open.\n */\n private resubscribe() {\n // Merge pending add subscriptions with current subscriptions\n for (const [key, subscription] of this.subscriptions) {\n this.pendingAdd.set(key, subscription)\n }\n\n if (this.pendingAdd.size === 0) return // If there is no subscriptions to send, just exit\n\n // Clear pending remove subscriptions\n this.pendingRemove.clear()\n this.pengingReset = true\n\n // Send the subscriptions to the channel imidiately\n this.processPendings()\n }\n\n /**\n * Schedule sending pending subscriptions to the channel to batch them together to reduce the number of messages.\n */\n private scheduleProcessPendings() {\n if (this.subScheduler.has('processPendings')) {\n return\n }\n\n this.subScheduler.schedule(\n this.processPendings,\n this.options.batchSubscriptionsTime,\n 'processPendings'\n )\n }\n\n /**\n * Process pending subscriptions and send them to the channel.\n */\n private processPendings = () => {\n const newTouchedEvents = new Set<string>() // New events to be sent to the channel\n let chunk: FeedSubscriptionChunk = {} // Chunk to be sent to the channel\n let chunkSize = 0 // Approximate size of the chunk in bytes\n\n // Add `reset` flag to the chunk\n if (this.pengingReset) {\n chunk.reset = true\n chunkSize += 13 // ',\"reset\":true'.length\n this.pengingReset = false\n }\n\n // Add `remove` subscriptions to the chunk\n for (const [key, subscription] of this.pendingRemove.entries()) {\n ;(chunk.remove ??= []).push(subscription)\n chunkSize += key.length + ('fromTime' in subscription ? 34 : 21) // Approximate size of the subscription in bytes\n\n // Remove the subscription from the active subscriptions\n this.subscriptions.delete(getSubscriptionKey(subscription))\n\n // Send the chunk if it is too big already\n if (chunkSize >= this.options.maxSendSubscriptionChunkSize) {\n this.sendSubscriptionChunkAndSchema(chunk)\n chunk = {}\n chunkSize = 0\n }\n }\n this.pendingRemove.clear()\n\n // Add `add` subscriptions to the chunk\n for (const [key, subscription] of this.pendingAdd.entries()) {\n ;(chunk.add ??= []).push(subscription)\n chunkSize += key.length + ('fromTime' in subscription ? 34 : 21) // Approximate size of the subscription in bytes\n\n // Add the event type to the new touched events if it is not touched yet\n if (!this.touchedEvents.has(subscription.type)) {\n newTouchedEvents.add(subscription.type)\n this.touchedEvents.add(subscription.type)\n }\n\n // Add the subscription to the active subscriptions\n this.subscriptions.set(key, subscription)\n\n // Send the chunk if it is too big already\n if (chunkSize >= this.options.maxSendSubscriptionChunkSize) {\n this.sendSubscriptionChunkAndSchema(chunk, newTouchedEvents)\n newTouchedEvents.clear()\n chunk = {}\n chunkSize = 0\n }\n }\n this.pendingAdd.clear()\n\n // Send the last chunk\n if (chunkSize > 0) {\n this.sendSubscriptionChunkAndSchema(chunk, newTouchedEvents)\n }\n }\n\n /**\n * Send the `FEED_SETUP` message to the channel with the event fields for the specified event types.\n * @param eventTypes List of event type fields to be sent to the channel.\n * @param force If `true`, the config will be sent to the channel even if there is no event fields to send.\n */\n private sendAcceptConfig = (eventTypes: Set<string>, force: boolean = false) => {\n let acceptEventFields: FeedEventFields | undefined\n\n // Get event fields for the specified event types\n if (this.acceptConfig.acceptEventFields !== undefined) {\n for (const eventType of eventTypes) {\n const eventFields = this.acceptConfig.acceptEventFields[eventType]\n if (eventFields !== undefined) {\n acceptEventFields ??= {}\n acceptEventFields[eventType] = eventFields\n }\n }\n }\n\n const { acceptAggregationPeriod, acceptDataFormat } = this.acceptConfig\n\n // Check if there is anything to send\n if (\n acceptEventFields === undefined &&\n acceptAggregationPeriod === undefined &&\n acceptDataFormat === undefined\n ) {\n return // Nothing to send\n }\n\n // Send the config to the channel if there is event fields to send or if it is forced to send\n // or if the aggregation period or data format was changed\n if (\n force ||\n acceptEventFields !== undefined ||\n (acceptAggregationPeriod !== undefined &&\n acceptAggregationPeriod !== this.config.aggregationPeriod) ||\n (acceptDataFormat !== undefined && acceptDataFormat !== this.config.dataFormat)\n ) {\n this.channel.send({\n type: 'FEED_SETUP',\n acceptAggregationPeriod,\n acceptDataFormat,\n acceptEventFields,\n } satisfies FeedSetupMessage)\n }\n }\n}\n"],"names":["FeedContract","FeedDataFormat","getSubscriptionKey","subscription","type","source","symbol","DXLinkFeed","constructor","client","contract","options","this","id","channel","acceptConfig","config","aggregationPeriod","NaN","dataFormat","FULL","eventFields","configListeners","Set","eventListeners","pendingAdd","Map","pendingRemove","pengingReset","subscriptions","touchedEvents","subScheduler","Scheduler","logger","getChannel","getState","addStateChangeListener","listener","removeStateChangeListener","getConfig","addConfigChangeListener","add","removeConfigChangeListener","delete","close","clear","configure","DXLinkChannelState","OPENED","sendAcceptConfig","clearSubscriptions","scheduleProcessPendings","addEventListener","removeEventListener","cleanSubscription","TICKER","other","Object","keys","length","warn","processMessage","message","isFeedMessage","processConfig","processData","newConfig","error","parseEventData","data","isFeedFullData","Array","isArray","isFeedCompactData","events","eventType","values","eventFieldsForType","undefined","Error","cursor","event","field","value","push","processStatus","resubscribe","REQUESTED","CLOSED","sendSubscriptionChunkAndSchema","chunk","newTouchedEvents","size","send","processError","processPendings","chunkSize","reset","key","entries","remove","maxSendSubscriptionChunkSize","has","set","eventTypes","force","acceptEventFields","acceptAggregationPeriod","acceptDataFormat","logLevel","DXLinkLogLevel","WARN","batchSubscriptionsTime","openChannel","space","feed","addMessageListener","addErrorListener","addSubscriptions","bind","removeSubscriptions","Logger","name","args","slice","call","arguments","inputs","input","schedule"],"mappings":"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"} | ||
| {"version":3,"file":"index.module.js","sources":["../src/messages.ts","../src/feed.ts"],"sourcesContent":["import { type DXLinkChannelMessage } from '@dxfeed/dxlink-core'\n\nexport enum FeedContract {\n 'AUTO' = 'AUTO',\n 'TICKER' = 'TICKER',\n 'HISTORY' = 'HISTORY',\n 'STREAM' = 'STREAM',\n}\n\nexport enum FeedDataFormat {\n 'FULL' = 'FULL',\n 'COMPACT' = 'COMPACT',\n}\n\nexport interface FeedParameters {\n readonly contract: FeedContract\n}\n\nexport interface FeedEventFields {\n [eventType: string]: string[]\n}\n\nexport interface FeedSetupMessage {\n readonly type: 'FEED_SETUP'\n readonly acceptAggregationPeriod?: number\n readonly acceptDataFormat?: FeedDataFormat\n readonly acceptEventFields?: FeedEventFields\n}\n\nexport interface FeedConfigMessage {\n readonly type: 'FEED_CONFIG'\n readonly aggregationPeriod: number\n readonly dataFormat: FeedDataFormat\n readonly eventFields?: FeedEventFields\n}\n\nexport type Subscription = {\n readonly type: string\n readonly symbol: string\n}\n\nexport type TimeSeriesSubscription = {\n readonly type: string\n readonly symbol: string\n readonly fromTime: number\n}\n\nexport type IndexedEventSubscription = {\n readonly type: string\n readonly symbol: string\n readonly source: string\n}\n\nexport interface FeedSubscriptionMessage {\n readonly type: 'FEED_SUBSCRIPTION'\n readonly add?: (Subscription | TimeSeriesSubscription | IndexedEventSubscription)[]\n readonly remove?: (Subscription | TimeSeriesSubscription | IndexedEventSubscription)[]\n readonly reset?: boolean\n}\n\nexport type FeedEventValue = number | string | boolean\n\nexport interface FeedEventData {\n [key: string]: FeedEventValue\n}\n\nexport type FeedCompactEventData = [string, FeedEventValue[]]\n\nexport interface FeedDataMessage {\n readonly type: 'FEED_DATA'\n readonly data: FeedEventData[] | FeedCompactEventData\n}\n\nexport type FeedMessage =\n | FeedSetupMessage\n | FeedConfigMessage\n | FeedSubscriptionMessage\n | FeedDataMessage\n\nexport const isFeedFullData = (\n data: FeedEventData[] | FeedCompactEventData\n): data is FeedEventData[] => typeof data[0] === 'object'\n\nexport const isFeedCompactData = (\n data: FeedEventData[] | FeedCompactEventData\n): data is FeedCompactEventData =>\n data.length >= 2 && typeof data[0] === 'string' && Array.isArray(data[1])\n\nexport const isFeedMessage = (message: DXLinkChannelMessage): message is FeedMessage =>\n message.type === 'FEED_SETUP' ||\n message.type === 'FEED_CONFIG' ||\n message.type === 'FEED_SUBSCRIPTION' ||\n message.type === 'FEED_DATA'\n","import {\n type DXLinkChannel,\n type DXLinkChannelMessage,\n DXLinkChannelState,\n type DXLinkChannelStateChangeListener,\n type DXLinkError,\n type DXLinkClient,\n DXLinkLogLevel,\n type DXLinkLogger,\n Logger,\n type DXLinkScheduler,\n} from '@dxfeed/dxlink-core'\n\nimport {\n type FeedEventFields,\n FeedContract,\n FeedDataFormat,\n type IndexedEventSubscription,\n type Subscription,\n type TimeSeriesSubscription,\n type FeedSetupMessage,\n type FeedSubscriptionMessage,\n isFeedMessage,\n isFeedFullData,\n type FeedDataMessage,\n isFeedCompactData,\n type FeedEventData,\n type FeedConfigMessage,\n} from './messages'\n\n/**\n * Prefered configuration for the feed channel.\n * Server can ignore some of the parameters and use own defaults.\n * @see {DXLinkFeed.configure}\n */\nexport interface FeedAcceptConfig {\n /**\n * Aggregation period in seconds.\n * If not specified, the channel will use the default value.\n * If specified as 0, the channel will try not aggregate events.\n */\n acceptAggregationPeriod?: number\n /**\n * Data format to be used for received events.\n * If not specified, the channel will use the default value `FULL`.\n */\n acceptDataFormat?: FeedDataFormat\n /**\n * Event fields to be included in received events.\n * If not specified, the channel will use the default value.\n * If specified as an empty array, the channel will try to send events with default fields.\n */\n acceptEventFields?: FeedEventFields\n}\n\n/**\n * Configuration of the feed channel.\n */\nexport interface FeedConfig {\n /**\n * Aggregation period in seconds.\n * @example 0.5 - 500 milliseconds.\n * @default `NaN`\n * @see {FeedAcceptConfig.acceptAggregationPeriod}\n */\n readonly aggregationPeriod: number\n /**\n * Data format to be used for received events.\n * @example `FULL` - object with keys and values.\n * @example `COMPACT` - array of values.\n * @default `FULL`\n * @see {FeedAcceptConfig.acceptDataFormat}\n */\n readonly dataFormat: FeedDataFormat\n /**\n * Event fields to be included in received events.\n * You can specify fields for all event types or for specific event types @see {FeedAcceptConfig.acceptEventFields}.\n * @example ```json\n * { \"Quote\": [\"eventSymbol\", \"askPrice\", \"bidPrice\"] }\n * ```\n * @default `{}`\n */\n readonly eventFields: FeedEventFields\n}\n\n/**\n * Listener for the feed channel config changes.\n */\nexport type DXLinkFeedConfigChangeListener = (config: FeedConfig) => void\n\ntype AnySubscription = Subscription | TimeSeriesSubscription | IndexedEventSubscription\n\n/**\n * Get a unique key for the subscription.\n */\nconst getSubscriptionKey = (subscription: AnySubscription) =>\n `${subscription.type}${'source' in subscription ? `#${subscription.source}` : ''}:${\n subscription.symbol\n }`\n\n/**\n * Subscription type by the contract.\n */\nexport type SubscriptionByContract = {\n [FeedContract.AUTO]: Subscription | TimeSeriesSubscription | IndexedEventSubscription\n [FeedContract.TICKER]: Subscription\n [FeedContract.HISTORY]: TimeSeriesSubscription | IndexedEventSubscription\n [FeedContract.STREAM]: Subscription | TimeSeriesSubscription | IndexedEventSubscription\n}\n\n/**\n * Listener for the feed channel events received from the channel.\n */\nexport type DXLinkFeedEventListener = (event: FeedEventData[]) => void\n\n/**\n * Chunk of the subscriptions to be sent to the channel.\n */\ninterface FeedSubscriptionChunk {\n add?: AnySubscription[]\n remove?: AnySubscription[]\n reset?: boolean\n}\n\n/**\n * dxLink FEED service instance for the specified {@link FeedContract}.\n */\nexport interface DXLinkFeedRequester<Contract extends FeedContract = FeedContract.AUTO> {\n /**\n * Unique identifier of the feed channel.\n */\n readonly id: number\n /**\n * Contract of the feed channel.\n * @see {FeedContract}\n */\n readonly contract: Contract\n\n /**\n * Get current channel of the feed.\n * Note: inaproppriate usage of the channel can lead to unexpected behavior.\n * @see {DXLinkChannel}\n */\n getChannel(): DXLinkChannel\n\n /**\n * Configure desired configuration of the feed channel.\n * @see {FeedAcceptConfig}\n */\n configure(acceptConfig: FeedAcceptConfig): void\n\n /**\n * Get current configuration of the feed channel as received from the channel.\n */\n getConfig(): FeedConfig\n /**\n * Add a listener for the feed channel config changes.\n */\n addConfigChangeListener(listener: DXLinkFeedConfigChangeListener): void\n /**\n * Remove a listener for the feed channel config changes.\n */\n removeConfigChangeListener(listener: DXLinkFeedConfigChangeListener): void\n\n /**\n * Add subscriptions to the feed channel.\n * @param subscriptions - Subscriptions to be added.\n */\n addSubscriptions(subscriptions: SubscriptionByContract[Contract][]): void\n /**\n * Add subscriptions to the feed channel.\n * @param subscriptions - Subscriptions to be added.\n */\n addSubscriptions(...subscriptions: SubscriptionByContract[Contract][]): void\n /**\n * Remove subscriptions from the feed channel.\n * @param subscriptions - Subscriptions to be removed.\n */\n removeSubscriptions(subscriptions: SubscriptionByContract[Contract][]): void\n /**\n * Remove subscriptions from the feed channel.\n * @param subscriptions - Subscriptions to be removed.\n */\n removeSubscriptions(...subscriptions: SubscriptionByContract[Contract][]): void\n /**\n * Remove all active subscriptions from the feed channel.\n */\n clearSubscriptions(): void\n\n /**\n * Add a listener for the feed channel events received from the channel.\n */\n addEventListener(listener: DXLinkFeedEventListener): void\n /**\n * Remove a listener for the feed channel events received from the channel.\n */\n removeEventListener(listener: DXLinkFeedEventListener): void\n\n /**\n * Close the feed channel.\n */\n close(): void\n}\n\n/**\n * Options for the {@link DXLinkFeed} instance.\n */\nexport interface DXLinkFeedOptions {\n /**\n * Space to be used for the service.\n */\n space?: string\n /**\n * Feed name to be used for the service.\n */\n feed?: string\n /**\n * Time in milliseconds to wait for more pending subscriptions before sending them to the channel.\n */\n batchSubscriptionsTime: number\n /**\n * Maximum size of the subscription chunk to be sent to the channel.\n */\n maxSendSubscriptionChunkSize: number\n /**\n * Log level for the feed.\n */\n logLevel: DXLinkLogLevel\n}\n\nconst FEED_SERVICE_NAME = 'FEED'\n\n/**\n * dxLink FEED provides access to the real-time and historical data of dxFeed.\n */\nexport class DXLinkFeed<Contract extends FeedContract> implements DXLinkFeedRequester<Contract> {\n /**\n * Unique identifier of the feed channel.\n */\n public readonly id: number\n\n /**\n * Contract of the feed channel.\n * @see {FeedContract}\n */\n public contract: Contract\n\n private readonly options: DXLinkFeedOptions\n\n /**\n * dxLink channel instance.\n */\n private readonly channel: DXLinkChannel\n\n /**\n * Current accept config of the feed channel.\n */\n private acceptConfig: FeedAcceptConfig = {}\n\n /**\n * Current config of the feed channel.\n */\n private config: FeedConfig = {\n aggregationPeriod: NaN,\n dataFormat: FeedDataFormat.FULL,\n eventFields: {},\n }\n\n // Listeners\n private readonly configListeners = new Set<DXLinkFeedConfigChangeListener>()\n private readonly eventListeners = new Set<DXLinkFeedEventListener>()\n\n /**\n * Pending add subscriptions to be sent to the channel.\n */\n private readonly pendingAdd = new Map<string, AnySubscription>()\n /**\n * Pending remove subscriptions to be sent to the channel.\n */\n private readonly pendingRemove = new Map<string, AnySubscription>()\n /**\n * Pending reset flag to be sent to the channel.\n */\n private pengingReset = false\n\n /**\n * List of active subscriptions.\n * Used to avoid sending the same subscription twice and re-subscribe on the channel re-open.\n */\n private readonly subscriptions = new Map<string, AnySubscription>()\n\n /**\n * List of event types which schema was sent to the channel.\n */\n private readonly touchedEvents = new Set<string>()\n\n /**\n * Scheduler for scheduling subscriptions sending to the channel.\n */\n private readonly scheduler: DXLinkScheduler\n private readonly processPendingsSchedulerKey: string\n\n private readonly logger: DXLinkLogger\n\n /**\n * Allows to create {@link DXLinkFeed} instance with the specified {@link FeedContract} for the given {@link DXLinkWebSocketClient}.\n */\n constructor(client: DXLinkClient, contract: Contract, options: Partial<DXLinkFeedOptions> = {}) {\n this.options = {\n logLevel: DXLinkLogLevel.WARN,\n batchSubscriptionsTime: 100,\n maxSendSubscriptionChunkSize: 4096 * 2,\n ...options,\n }\n this.scheduler = client.getScheduler()\n\n this.channel = client.openChannel(FEED_SERVICE_NAME, {\n contract,\n // Optional parameters for FEED source\n space: options.space,\n feed: options.feed,\n })\n this.id = this.channel.id\n this.contract = contract\n this.channel.addMessageListener(this.processMessage)\n this.channel.addStateChangeListener(this.processStatus)\n this.channel.addErrorListener(this.processError)\n\n this.addSubscriptions = this.addSubscriptions.bind(this)\n this.removeSubscriptions = this.removeSubscriptions.bind(this)\n\n this.logger = new Logger(`${DXLinkFeed.name}#${this.id}`, this.options.logLevel)\n\n this.processPendingsSchedulerKey = `${FEED_SERVICE_NAME}#${this.id}:PROCESS_PENDINGS`\n }\n\n getChannel = () => this.channel\n\n getState = () => this.channel.getState()\n addStateChangeListener = (listener: DXLinkChannelStateChangeListener) =>\n this.channel.addStateChangeListener(listener)\n removeStateChangeListener = (listener: DXLinkChannelStateChangeListener) =>\n this.channel.removeStateChangeListener(listener)\n\n getConfig = () => this.config\n addConfigChangeListener = (listener: DXLinkFeedConfigChangeListener) => {\n this.configListeners.add(listener)\n }\n removeConfigChangeListener = (listener: DXLinkFeedConfigChangeListener) => {\n this.configListeners.delete(listener)\n }\n\n close = () => {\n this.acceptConfig = {}\n\n this.configListeners.clear()\n this.eventListeners.clear()\n\n this.pendingAdd.clear()\n this.pendingRemove.clear()\n this.touchedEvents.clear()\n this.subscriptions.clear()\n this.touchedEvents.clear()\n\n this.scheduler.cancel(this.processPendingsSchedulerKey)\n\n this.channel.close()\n }\n\n configure = (acceptConfig: FeedAcceptConfig) => {\n this.acceptConfig = acceptConfig\n\n // Update touched events list\n if (this.channel.getState() === DXLinkChannelState.OPENED) {\n this.sendAcceptConfig(this.touchedEvents)\n }\n }\n\n addSubscriptions(subscriptions: SubscriptionByContract[Contract][]): void\n addSubscriptions(...subscriptions: SubscriptionByContract[Contract][]): void\n addSubscriptions(...args: unknown[]): void {\n const inputs: SubscriptionByContract[Contract][] = Array.isArray(args[0]) ? args[0] : args\n\n for (const input of inputs) {\n const subscription = this.cleanSubscription(input)\n\n this.pendingAdd.set(getSubscriptionKey(subscription), subscription)\n }\n\n this.scheduleProcessPendings()\n }\n\n removeSubscriptions(subscriptions: SubscriptionByContract[Contract][]): void\n removeSubscriptions(...subscriptions: SubscriptionByContract[Contract][]): void\n removeSubscriptions(...args: unknown[]): void {\n const inputs: SubscriptionByContract[Contract][] = Array.isArray(args[0]) ? args[0] : args\n\n for (const input of inputs) {\n const subscription = this.cleanSubscription(input)\n\n const key = getSubscriptionKey(subscription)\n this.pendingRemove.set(key, subscription)\n this.pendingAdd.delete(key)\n }\n\n this.scheduleProcessPendings()\n }\n\n clearSubscriptions = () => {\n this.pendingAdd.clear()\n this.pendingRemove.clear()\n this.pengingReset = true\n\n this.scheduleProcessPendings()\n }\n\n addEventListener = (listener: DXLinkFeedEventListener) => {\n this.eventListeners.add(listener)\n }\n removeEventListener = (listener: DXLinkFeedEventListener) => {\n this.eventListeners.delete(listener)\n }\n\n /**\n * Clean the subscription from the fields which are not allowed for the specified contract.\n * Note: coze of the TypeScript limitations, we need to clean the subscription from the fields which are not allowed for the specified contract.\n */\n private cleanSubscription = (\n subscription: SubscriptionByContract[Contract]\n ): SubscriptionByContract[Contract] => {\n if (this.contract === FeedContract.TICKER) {\n const { type, symbol, ...other } = subscription\n if (Object.keys(other).length > 0) {\n this.logger.warn(\n 'Subscription for the TICKER contract should not have any additional fields',\n subscription\n )\n }\n return { type, symbol } as unknown as SubscriptionByContract[Contract]\n }\n\n return subscription\n }\n\n /**\n * Process message received in the channel.\n */\n private processMessage = (message: DXLinkChannelMessage) => {\n // Parse message\n if (isFeedMessage(message)) {\n switch (message.type) {\n case 'FEED_CONFIG':\n this.processConfig(message)\n return\n case 'FEED_DATA':\n this.processData(message)\n return\n }\n }\n\n this.logger.warn('Unknown message', message)\n }\n\n /**\n * Process config received from the channel.\n */\n private processConfig = (config: FeedConfigMessage) => {\n // Update config with the new values from the channel\n const newConfig: FeedConfig = {\n aggregationPeriod: config.aggregationPeriod ?? this.config.aggregationPeriod,\n dataFormat: config.dataFormat ?? this.config.dataFormat,\n eventFields: {\n ...(this.config.eventFields ?? {}),\n ...(config.eventFields ?? {}),\n },\n }\n\n this.config = newConfig\n\n // Notify listeners\n for (const listener of this.configListeners) {\n try {\n listener(newConfig)\n } catch (error) {\n this.logger.error('Error in config listener', error)\n }\n }\n }\n\n /**\n * Parse data received from the channel.\n */\n private parseEventData = (data: FeedDataMessage['data']) => {\n const { dataFormat, eventFields } = this.config\n if (dataFormat === 'FULL') {\n if (isFeedFullData(data)) {\n // If data is already in the full format, just return it\n return data\n }\n } else if (isFeedCompactData(data)) {\n // If data is in the compact format, parse it\n const events: FeedEventData[] = []\n\n const [eventType, values] = data\n const eventFieldsForType = eventFields[eventType]\n if (eventFieldsForType === undefined) {\n throw new Error('Cannot find event fields for event type in the config')\n }\n\n // Split values into events\n let cursor = 0\n while (cursor < values.length) {\n const event: FeedEventData = {\n eventType,\n }\n\n // Build event object from the values\n for (const field of eventFieldsForType) {\n const value = values[cursor]\n if (value === undefined) {\n throw new Error('Not enough values in compact event')\n }\n event[field] = value\n cursor++\n }\n events.push(event)\n }\n\n return events\n }\n\n throw new Error('Incoming data does not match the format specified in the config')\n }\n\n /**\n * Process data received from the channel.\n */\n private processData = ({ data }: FeedDataMessage) => {\n try {\n const events = this.parseEventData(data)\n\n for (const listener of this.eventListeners) {\n try {\n listener(events)\n } catch (error) {\n this.logger.error('Error in event listener', error)\n }\n }\n } catch (error) {\n this.logger.error('Cannot parse data', error)\n return\n }\n }\n\n /**\n * Process channel status changes from the channel.\n */\n private processStatus = (processStatus: DXLinkChannelState) => {\n switch (processStatus) {\n case DXLinkChannelState.OPENED: {\n this.sendAcceptConfig(this.touchedEvents, true)\n\n return this.resubscribe()\n }\n case DXLinkChannelState.REQUESTED: {\n // Clear the timeout if it is set to avoid sending the subscriptions while the channel is not ready\n this.scheduler.cancel(this.processPendingsSchedulerKey)\n return\n }\n case DXLinkChannelState.CLOSED:\n // Destroy the channel if it is closed by the channel\n return this.close()\n }\n }\n\n /**\n * Send the subscription chunk to the channel.\n * @param chunk Subscription chunk to be sent to the channel.\n * @param newTouchedEvents List of event types which schema should be sent to the channel before the chunk.\n * @returns\n */\n private sendSubscriptionChunkAndSchema = (\n chunk: FeedSubscriptionChunk,\n newTouchedEvents?: Set<string>\n ) => {\n if (this.channel.getState() !== DXLinkChannelState.OPENED) return // If the channel is not ready, just exit\n\n if (newTouchedEvents !== undefined && newTouchedEvents.size > 0) {\n this.sendAcceptConfig(newTouchedEvents)\n }\n\n this.channel.send({\n type: 'FEED_SUBSCRIPTION',\n ...chunk,\n } satisfies FeedSubscriptionMessage)\n }\n\n /**\n * Process error received from the channel.\n */\n private processError = (processError: DXLinkError) => {\n this.logger.error('Error in channel', processError)\n }\n\n /**\n * Resubscribe to the feed channel subscriptions after the channel re-open.\n */\n private resubscribe() {\n // Merge pending add subscriptions with current subscriptions\n for (const [key, subscription] of this.subscriptions) {\n this.pendingAdd.set(key, subscription)\n }\n\n if (this.pendingAdd.size === 0) return // If there is no subscriptions to send, just exit\n\n // Clear pending remove subscriptions\n this.pendingRemove.clear()\n this.pengingReset = true\n\n // Send the subscriptions to the channel imidiately\n this.processPendings()\n }\n\n /**\n * Schedule sending pending subscriptions to the channel to batch them together to reduce the number of messages.\n */\n private scheduleProcessPendings() {\n if (this.scheduler.has(this.processPendingsSchedulerKey)) {\n return\n }\n\n this.scheduler.schedule(\n this.processPendings,\n this.options.batchSubscriptionsTime,\n this.processPendingsSchedulerKey\n )\n }\n\n /**\n * Process pending subscriptions and send them to the channel.\n */\n private processPendings = () => {\n const newTouchedEvents = new Set<string>() // New events to be sent to the channel\n let chunk: FeedSubscriptionChunk = {} // Chunk to be sent to the channel\n let chunkSize = 0 // Approximate size of the chunk in bytes\n\n // Add `reset` flag to the chunk\n if (this.pengingReset) {\n chunk.reset = true\n chunkSize += 13 // ',\"reset\":true'.length\n this.pengingReset = false\n }\n\n // Add `remove` subscriptions to the chunk\n for (const [key, subscription] of this.pendingRemove.entries()) {\n ;(chunk.remove ??= []).push(subscription)\n chunkSize += key.length + ('fromTime' in subscription ? 34 : 21) // Approximate size of the subscription in bytes\n\n // Remove the subscription from the active subscriptions\n this.subscriptions.delete(getSubscriptionKey(subscription))\n\n // Send the chunk if it is too big already\n if (chunkSize >= this.options.maxSendSubscriptionChunkSize) {\n this.sendSubscriptionChunkAndSchema(chunk)\n chunk = {}\n chunkSize = 0\n }\n }\n this.pendingRemove.clear()\n\n // Add `add` subscriptions to the chunk\n for (const [key, subscription] of this.pendingAdd.entries()) {\n ;(chunk.add ??= []).push(subscription)\n chunkSize += key.length + ('fromTime' in subscription ? 34 : 21) // Approximate size of the subscription in bytes\n\n // Add the event type to the new touched events if it is not touched yet\n if (!this.touchedEvents.has(subscription.type)) {\n newTouchedEvents.add(subscription.type)\n this.touchedEvents.add(subscription.type)\n }\n\n // Add the subscription to the active subscriptions\n this.subscriptions.set(key, subscription)\n\n // Send the chunk if it is too big already\n if (chunkSize >= this.options.maxSendSubscriptionChunkSize) {\n this.sendSubscriptionChunkAndSchema(chunk, newTouchedEvents)\n newTouchedEvents.clear()\n chunk = {}\n chunkSize = 0\n }\n }\n this.pendingAdd.clear()\n\n // Send the last chunk\n if (chunkSize > 0) {\n this.sendSubscriptionChunkAndSchema(chunk, newTouchedEvents)\n }\n }\n\n /**\n * Send the `FEED_SETUP` message to the channel with the event fields for the specified event types.\n * @param eventTypes List of event type fields to be sent to the channel.\n * @param force If `true`, the config will be sent to the channel even if there is no event fields to send.\n */\n private sendAcceptConfig = (eventTypes: Set<string>, force: boolean = false) => {\n let acceptEventFields: FeedEventFields | undefined\n\n // Get event fields for the specified event types\n if (this.acceptConfig.acceptEventFields !== undefined) {\n for (const eventType of eventTypes) {\n const eventFields = this.acceptConfig.acceptEventFields[eventType]\n if (eventFields !== undefined) {\n acceptEventFields ??= {}\n acceptEventFields[eventType] = eventFields\n }\n }\n }\n\n const { acceptAggregationPeriod, acceptDataFormat } = this.acceptConfig\n\n // Check if there is anything to send\n if (\n acceptEventFields === undefined &&\n acceptAggregationPeriod === undefined &&\n acceptDataFormat === undefined\n ) {\n return // Nothing to send\n }\n\n // Send the config to the channel if there is event fields to send or if it is forced to send\n // or if the aggregation period or data format was changed\n if (\n force ||\n acceptEventFields !== undefined ||\n (acceptAggregationPeriod !== undefined &&\n acceptAggregationPeriod !== this.config.aggregationPeriod) ||\n (acceptDataFormat !== undefined && acceptDataFormat !== this.config.dataFormat)\n ) {\n this.channel.send({\n type: 'FEED_SETUP',\n acceptAggregationPeriod,\n acceptDataFormat,\n acceptEventFields,\n } satisfies FeedSetupMessage)\n }\n }\n}\n"],"names":["FeedContract","FeedDataFormat","getSubscriptionKey","subscription","type","source","symbol","DXLinkFeed","constructor","client","contract","options","id","channel","acceptConfig","this","config","aggregationPeriod","NaN","dataFormat","FULL","eventFields","configListeners","Set","eventListeners","pendingAdd","Map","pendingRemove","pengingReset","subscriptions","touchedEvents","scheduler","processPendingsSchedulerKey","logger","getChannel","getState","addStateChangeListener","listener","removeStateChangeListener","getConfig","addConfigChangeListener","add","removeConfigChangeListener","delete","close","clear","cancel","configure","DXLinkChannelState","OPENED","sendAcceptConfig","clearSubscriptions","scheduleProcessPendings","addEventListener","removeEventListener","cleanSubscription","TICKER","other","Object","keys","length","warn","processMessage","message","isFeedMessage","processConfig","processData","newConfig","error","parseEventData","data","isFeedFullData","Array","isArray","isFeedCompactData","events","eventType","values","eventFieldsForType","undefined","Error","cursor","event","field","value","push","processStatus","resubscribe","REQUESTED","CLOSED","sendSubscriptionChunkAndSchema","chunk","newTouchedEvents","size","send","processError","processPendings","chunkSize","reset","key","entries","remove","maxSendSubscriptionChunkSize","has","set","eventTypes","force","acceptEventFields","acceptAggregationPeriod","acceptDataFormat","logLevel","DXLinkLogLevel","WARN","batchSubscriptionsTime","getScheduler","openChannel","space","feed","addMessageListener","addErrorListener","addSubscriptions","bind","removeSubscriptions","Logger","name","args","slice","call","arguments","inputs","input","schedule"],"mappings":"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"} |
+2
-2
| { | ||
| "name": "@dxfeed/dxlink-feed", | ||
| "version": "0.6.1", | ||
| "version": "0.7.0", | ||
| "private": false, | ||
@@ -30,3 +30,3 @@ "sideEffects": false, | ||
| "dependencies": { | ||
| "@dxfeed/dxlink-core": "0.6.1" | ||
| "@dxfeed/dxlink-core": "0.7.0" | ||
| }, | ||
@@ -33,0 +33,0 @@ "author": "Dmitry Petrov <dmitry.petrov@devexperts.com>", |
100413
1.35%434
0.46%+ Added
- Removed
Updated