@dxfeed/dxlink-feed
Advanced tools
@@ -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 * 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, { contract })\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","id","channel","acceptConfig","config","aggregationPeriod","NaN","dataFormat","FULL","eventFields","this","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","isFeedCompactData","Array","isArray","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","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 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 * 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, { contract })\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","id","channel","acceptConfig","config","aggregationPeriod","NaN","dataFormat","FULL","eventFields","this","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","isFeedCompactData","Array","isArray","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","addMessageListener","addErrorListener","addSubscriptions","bind","removeSubscriptions","Logger","name","args","slice","call","arguments","inputs","input","schedule"],"mappings":"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"} |
@@ -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 * 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, { contract })\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","id","channel","acceptConfig","config","aggregationPeriod","NaN","dataFormat","FULL","eventFields","this","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","isFeedCompactData","Array","isArray","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","addMessageListener","addErrorListener","addSubscriptions","bind","removeSubscriptions","Logger","name","args","slice","call","arguments","inputs","input","schedule"],"mappings":"oFAEY,IAAAA,aAOAC,gBAPZ,SAAYD,cACVA,aAAA,KAAA,OACAA,aAAA,OAAA,SACAA,aAAA,QAAA,UACAA,aAAA,OAAA,QACD,CALD,CAAYA,eAAAA,aAKX,CAAA,IAED,SAAYC,gBACVA,eAAA,KAAA,OACAA,eAAA,QAAA,SACD,CAHD,CAAYA,iBAAAA,eAGX,CAAA,IAmEY,MCgBPC,mBAAsBC,cACvB,GAAAA,aAAaC,OAAO,WAAYD,aAAmB,IAAAA,aAAaE,SAAW,MAC5EF,aAAaG,eAkIJC,WAuEXC,WAAAA,CAAYC,OAAsBC,SAAoBC,QAAsC,CAAA,GAnE5EC,KAAAA,QAMTF,EAAAA,KAAAA,cAEUC,EAAAA,KAAAA,aAKAE,EAAAA,KAAAA,aAKTC,EAAAA,KAAAA,aAAiC,CAAA,EAKjCC,KAAAA,OAAqB,CAC3BC,kBAAmBC,IACnBC,WAAYjB,eAAekB,KAC3BC,YAAa,IACdC,KAGgBC,gBAAkB,IAAIC,IAAqCF,KAC3DG,eAAiB,IAAID,IAKrBE,KAAAA,WAAa,IAAIC,IAA8BL,KAI/CM,cAAgB,IAAID,IAI7BE,KAAAA,cAAe,EAMNC,KAAAA,cAAgB,IAAIH,IAKpBI,KAAAA,cAAgB,IAAIP,IAAaF,KAK1CU,aAA0B,IAAIC,UAErBC,KAAAA,YA0BjBC,EAAAA,KAAAA,WAAa,IAAMb,KAAKR,QAExBsB,KAAAA,SAAW,IAAMd,KAAKR,QAAQsB,WAC9BC,KAAAA,uBAA0BC,UACxBhB,KAAKR,QAAQuB,uBAAuBC,eACtCC,0BAA6BD,UAC3BhB,KAAKR,QAAQyB,0BAA0BD,UAEzCE,KAAAA,UAAY,IAAMlB,KAAKN,OACvByB,KAAAA,wBAA2BH,WACzBhB,KAAKC,gBAAgBmB,IAAIJ,SAAQ,EAClChB,KACDqB,2BAA8BL,WAC5BhB,KAAKC,gBAAgBqB,OAAON,SAC9B,EAAChB,KAEDuB,MAAQ,KACNvB,KAAKP,aAAe,CAAA,EAEpBO,KAAKC,gBAAgBuB,QACrBxB,KAAKG,eAAeqB,QAEpBxB,KAAKI,WAAWoB,QAChBxB,KAAKM,cAAckB,QACnBxB,KAAKS,cAAce,QACnBxB,KAAKQ,cAAcgB,QACnBxB,KAAKS,cAAce,QAEnBxB,KAAKU,aAAac,QAElBxB,KAAKR,QAAQ+B,OACf,EAACvB,KAEDyB,UAAahC,eACXO,KAAKP,aAAeA,aAGhBO,KAAKR,QAAQsB,aAAeY,mBAAmBC,QACjD3B,KAAK4B,iBAAiB5B,KAAKS,cAC5B,EACFT,KAgCD6B,mBAAqB,KACnB7B,KAAKI,WAAWoB,QAChBxB,KAAKM,cAAckB,QACnBxB,KAAKO,cAAe,EAEpBP,KAAK8B,yBAAuB,EAG9BC,KAAAA,iBAAoBf,WAClBhB,KAAKG,eAAeiB,IAAIJ,SAC1B,EACAgB,KAAAA,oBAAuBhB,WACrBhB,KAAKG,eAAemB,OAAON,SAAQ,EACpChB,KAMOiC,kBACNnD,eAEA,GAAIkB,KAAKX,WAAaV,aAAauD,OAAQ,CACzC,MAAMnD,KAAEA,KAAIE,OAAEA,UAAWkD,OAAUrD,aAOnC,OANIsD,OAAOC,KAAKF,OAAOG,OAAS,GAC9BtC,KAAKY,OAAO2B,KACV,6EACAzD,cAGG,CAAEC,UAAME,cAChB,CAED,OAAOH,mBAMD0D,eAAkBC,UAExB,GDxV0BA,UACX,eAAjBA,QAAQ1D,MACS,gBAAjB0D,QAAQ1D,MACS,sBAAjB0D,QAAQ1D,MACS,cAAjB0D,QAAQ1D,KCoVF2D,CAAcD,SAChB,OAAQA,QAAQ1D,MACd,IAAK,cAEH,YADAiB,KAAK2C,cAAcF,SAErB,IAAK,YAEH,YADAzC,KAAK4C,YAAYH,SAKvBzC,KAAKY,OAAO2B,KAAK,kBAAmBE,QACtC,EAKQE,KAAAA,cAAiBjD,SAEvB,MAAMmD,UAAwB,CAC5BlD,kBAAmBD,OAAOC,mBAAqBK,KAAKN,OAAOC,kBAC3DE,WAAYH,OAAOG,YAAcG,KAAKN,OAAOG,WAC7CE,YAAa,IACPC,KAAKN,OAAOK,aAAe,CAAA,KAC3BL,OAAOK,aAAe,KAI9BC,KAAKN,OAASmD,UAGd,IAAK,MAAM7B,YAAgBhB,KAACC,gBAC1B,IACEe,SAAS6B,UACV,CAAC,MAAOC,OACP9C,KAAKY,OAAOkC,MAAM,2BAA4BA,MAC/C,CACF,EAMKC,KAAAA,eAAkBC,OACxB,MAAMnD,WAAEA,WAAUE,YAAEA,aAAgBC,KAAKN,OACzC,GAAmB,SAAfG,YACF,GD9YJmD,OAC+C,iBAAZA,KAAK,GC6YhCC,CAAeD,MAEjB,OAAOA,UAEAE,GD9YbF,OAEAA,KAAKV,QAAU,GAAwB,iBAAZU,KAAK,IAAmBG,MAAMC,QAAQJ,KAAK,IC4YzDE,CAAkBF,MAAO,CAElC,MAAMK,OAA0B,IAEzBC,UAAWC,QAAUP,KACtBQ,mBAAqBzD,YAAYuD,WACvC,QAA2BG,IAAvBD,mBACF,MAAU,IAAAE,MAAM,yDAIlB,IAAIC,OAAS,EACb,KAAOA,OAASJ,OAAOjB,QAAQ,CAC7B,MAAMsB,MAAuB,CAC3BN,qBAIF,IAAK,MAAMO,SAASL,mBAAoB,CACtC,MAAMM,MAAQP,OAAOI,QACrB,QAAcF,IAAVK,MACF,MAAM,IAAIJ,MAAM,sCAElBE,MAAMC,OAASC,MACfH,QACD,CACDN,OAAOU,KAAKH,MACb,CAED,OAAOP,MACR,CAED,MAAM,IAAIK,MAAM,kEAAiE,EAClF1D,KAKO4C,YAAc,EAAGI,cACvB,IACE,MAAMK,OAASrD,KAAK+C,eAAeC,MAEnC,IAAK,MAAMhC,YAAYhB,KAAKG,eAC1B,IACEa,SAASqC,OACV,CAAC,MAAOP,OACP9C,KAAKY,OAAOkC,MAAM,0BAA2BA,MAC9C,CAEJ,CAAC,MAAOA,OAEP,YADA9C,KAAKY,OAAOkC,MAAM,oBAAqBA,MAExC,QAMKkB,cAAiBA,gBACvB,OAAQA,eACN,KAAKtC,mBAAmBC,OAGtB,OAFA3B,KAAK4B,iBAAiB5B,KAAKS,eAAe,GAEnCT,KAAKiE,cAEd,KAAKvC,mBAAmBwC,UAGtB,YADAlE,KAAKU,aAAac,QAGpB,KAAKE,mBAAmByC,OAEtB,YAAY5C,QACf,OASK6C,+BAAiC,CACvCC,MACAC,oBAEItE,KAAKR,QAAQsB,aAAeY,mBAAmBC,cAE1B8B,IAArBa,kBAAkCA,iBAAiBC,KAAO,GAC5DvE,KAAK4B,iBAAiB0C,kBAGxBtE,KAAKR,QAAQgF,KAAK,CAChBzF,KAAM,uBACHsF,QAC8B,EAM7BI,KAAAA,aAAgBA,eACtBzE,KAAKY,OAAOkC,MAAM,mBAAoB2B,aACxC,EAuCQC,KAAAA,gBAAkB,KACxB,MAAMJ,iBAAmB,IAAIpE,IAC7B,IAAImE,MAA+B,CAAE,EACjCM,UAAY,EAGZ3E,KAAKO,eACP8D,MAAMO,OAAQ,EACdD,WAAa,GACb3E,KAAKO,cAAe,GAItB,IAAK,MAAOsE,IAAK/F,gBAAqBkB,KAACM,cAAcwE,WACjDT,MAAMU,SAAW,IAAIhB,KAAKjF,cAC5B6F,WAAaE,IAAIvC,QAAU,aAAcxD,aAAe,GAAK,IAG7DkB,KAAKQ,cAAcc,OAAOzC,mBAAmBC,eAGzC6F,WAAa3E,KAAKV,QAAQ0F,+BAC5BhF,KAAKoE,+BAA+BC,OACpCA,MAAQ,CAAE,EACVM,UAAY,GAGhB3E,KAAKM,cAAckB,QAGnB,IAAK,MAAOqD,IAAK/F,qBAAsBsB,WAAW0E,WAC9CT,MAAMjD,MAAQ,IAAI2C,KAAKjF,cACzB6F,WAAaE,IAAIvC,QAAU,aAAcxD,aAAe,GAAK,IAGxDkB,KAAKS,cAAcwE,IAAInG,aAAaC,QACvCuF,iBAAiBlD,IAAItC,aAAaC,MAClCiB,KAAKS,cAAcW,IAAItC,aAAaC,OAItCiB,KAAKQ,cAAc0E,IAAIL,IAAK/F,cAGxB6F,WAAa3E,KAAKV,QAAQ0F,+BAC5BhF,KAAKoE,+BAA+BC,MAAOC,kBAC3CA,iBAAiB9C,QACjB6C,MAAQ,CAAA,EACRM,UAAY,GAGhB3E,KAAKI,WAAWoB,QAGZmD,UAAY,GACd3E,KAAKoE,+BAA+BC,MAAOC,iBAC5C,EAQK1C,KAAAA,iBAAmB,CAACuD,WAAyBC,OAAiB,KACpE,IAAIC,kBAGJ,QAA4C5B,IAAxCzD,KAAKP,aAAa4F,kBACpB,IAAK,MAAM/B,aAAa6B,WAAY,CAClC,MAAMpF,YAAcC,KAAKP,aAAa4F,kBAAkB/B,gBACpCG,IAAhB1D,cACFsF,oBAAsB,CAAE,EACxBA,kBAAkB/B,WAAavD,YAElC,CAGH,MAAMuF,wBAAEA,wBAAuBC,iBAAEA,kBAAqBvF,KAAKP,kBAInCgE,IAAtB4B,wBAC4B5B,IAA5B6B,8BACqB7B,IAArB8B,mBAQAH,YACsB3B,IAAtB4B,wBAC6B5B,IAA5B6B,yBACCA,0BAA4BtF,KAAKN,OAAOC,wBACpB8D,IAArB8B,kBAAkCA,mBAAqBvF,KAAKN,OAAOG,aAEpEG,KAAKR,QAAQgF,KAAK,CAChBzF,KAAM,aACNuG,gDACAC,kCACAF,qCAEH,EA7aDrF,KAAKV,QAAU,CACbkG,SAAUC,eAAeC,KACzBC,uBAAwB,IACxBX,6BAA8B,QAC3B1F,SAGLU,KAAKR,QAAUJ,OAAOwG,YApFA,OAoF+B,CAAEvG,oBACvDW,KAAKT,GAAKS,KAAKR,QAAQD,GACvBS,KAAKX,SAAWA,SAChBW,KAAKR,QAAQqG,mBAAmB7F,KAAKwC,gBACrCxC,KAAKR,QAAQuB,uBAAuBf,KAAKgE,eACzChE,KAAKR,QAAQsG,iBAAiB9F,KAAKyE,cAEnCzE,KAAK+F,iBAAmB/F,KAAK+F,iBAAiBC,KAAKhG,MACnDA,KAAKiG,oBAAsBjG,KAAKiG,oBAAoBD,KAAKhG,MAEzDA,KAAKY,OAAS,IAAIsF,OAAO,GAAGhH,WAAWiH,QAAQnG,KAAKT,KAAMS,KAAKV,QAAQkG,SACzE,CA8CAO,gBAAAA,GAAmC,IAAfK,KAAe,GAAAC,MAAAC,KAAAC,WACjC,MAAMC,OAA6CrD,MAAMC,QAAQgD,KAAK,IAAMA,KAAK,GAAKA,KAEtF,IAAK,MAAMK,SAASD,OAAQ,CAC1B,MAAM1H,aAAekB,KAAKiC,kBAAkBwE,OAE5CzG,KAAKI,WAAW8E,IAAIrG,mBAAmBC,cAAeA,aACvD,CAEDkB,KAAK8B,yBACP,CAIAmE,mBAAAA,GAAsC,IAAfG,KAAeC,GAAAA,MAAAC,KAAAC,WACpC,MAAMC,OAA6CrD,MAAMC,QAAQgD,KAAK,IAAMA,KAAK,GAAKA,KAEtF,IAAK,MAAMK,SAASD,OAAQ,CAC1B,MAAM1H,aAAekB,KAAKiC,kBAAkBwE,OAEtC5B,IAAMhG,mBAAmBC,cAC/BkB,KAAKM,cAAc4E,IAAIL,IAAK/F,cAC5BkB,KAAKI,WAAWkB,OAAOuD,IACxB,CAED7E,KAAK8B,yBACP,CAyMQmC,WAAAA,GAEN,IAAK,MAAOY,IAAK/F,gBAAiBkB,KAAKQ,cACrCR,KAAKI,WAAW8E,IAAIL,IAAK/F,cAGE,IAAzBkB,KAAKI,WAAWmE,OAGpBvE,KAAKM,cAAckB,QACnBxB,KAAKO,cAAe,EAGpBP,KAAK0E,kBACP,CAKQ5C,uBAAAA,GACF9B,KAAKU,aAAauE,IAAI,oBAI1BjF,KAAKU,aAAagG,SAChB1G,KAAK0E,gBACL1E,KAAKV,QAAQqG,uBACb,kBAEJ"} | ||
| {"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 * 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, { contract })\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","id","channel","acceptConfig","config","aggregationPeriod","NaN","dataFormat","FULL","eventFields","this","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","isFeedCompactData","Array","isArray","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","addMessageListener","addErrorListener","addSubscriptions","bind","removeSubscriptions","Logger","name","args","slice","call","arguments","inputs","input","schedule"],"mappings":"oFAEY,IAAAA,aAOAC,gBAPZ,SAAYD,cACVA,aAAA,KAAA,OACAA,aAAA,OAAA,SACAA,aAAA,QAAA,UACAA,aAAA,OAAA,QACD,CALD,CAAYA,eAAAA,aAKX,CAAA,IAED,SAAYC,gBACVA,eAAA,KAAA,OACAA,eAAA,QAAA,SACD,CAHD,CAAYA,iBAAAA,eAGX,CAAA,IAmEY,MCgBPC,mBAAsBC,cAC1B,GAAGA,aAAaC,OAAO,WAAYD,aAAe,IAAIA,aAAaE,SAAW,MAC5EF,aAAaG,eAkIJC,WAuEXC,WAAAA,CAAYC,OAAsBC,SAAoBC,QAAsC,CAAA,GAnE5EC,KAAAA,QAMTF,EAAAA,KAAAA,cAEUC,EAAAA,KAAAA,aAKAE,EAAAA,KAAAA,aAKTC,EAAAA,KAAAA,aAAiC,CAAA,EAKjCC,KAAAA,OAAqB,CAC3BC,kBAAmBC,IACnBC,WAAYjB,eAAekB,KAC3BC,YAAa,IACdC,KAGgBC,gBAAkB,IAAIC,IAAqCF,KAC3DG,eAAiB,IAAID,IAKrBE,KAAAA,WAAa,IAAIC,IAA8BL,KAI/CM,cAAgB,IAAID,IAI7BE,KAAAA,cAAe,EAMNC,KAAAA,cAAgB,IAAIH,IAKpBI,KAAAA,cAAgB,IAAIP,IAAaF,KAK1CU,aAA0B,IAAIC,UAErBC,KAAAA,YA0BjBC,EAAAA,KAAAA,WAAa,IAAMb,KAAKR,QAExBsB,KAAAA,SAAW,IAAMd,KAAKR,QAAQsB,WAC9BC,KAAAA,uBAA0BC,UACxBhB,KAAKR,QAAQuB,uBAAuBC,eACtCC,0BAA6BD,UAC3BhB,KAAKR,QAAQyB,0BAA0BD,UAEzCE,KAAAA,UAAY,IAAMlB,KAAKN,OACvByB,KAAAA,wBAA2BH,WACzBhB,KAAKC,gBAAgBmB,IAAIJ,SAAQ,EAClChB,KACDqB,2BAA8BL,WAC5BhB,KAAKC,gBAAgBqB,OAAON,SAC9B,EAAChB,KAEDuB,MAAQ,KACNvB,KAAKP,aAAe,CAAA,EAEpBO,KAAKC,gBAAgBuB,QACrBxB,KAAKG,eAAeqB,QAEpBxB,KAAKI,WAAWoB,QAChBxB,KAAKM,cAAckB,QACnBxB,KAAKS,cAAce,QACnBxB,KAAKQ,cAAcgB,QACnBxB,KAAKS,cAAce,QAEnBxB,KAAKU,aAAac,QAElBxB,KAAKR,QAAQ+B,OACf,EAACvB,KAEDyB,UAAahC,eACXO,KAAKP,aAAeA,aAGhBO,KAAKR,QAAQsB,aAAeY,mBAAmBC,QACjD3B,KAAK4B,iBAAiB5B,KAAKS,cAC5B,EACFT,KAgCD6B,mBAAqB,KACnB7B,KAAKI,WAAWoB,QAChBxB,KAAKM,cAAckB,QACnBxB,KAAKO,cAAe,EAEpBP,KAAK8B,yBAAuB,EAG9BC,KAAAA,iBAAoBf,WAClBhB,KAAKG,eAAeiB,IAAIJ,SAC1B,EACAgB,KAAAA,oBAAuBhB,WACrBhB,KAAKG,eAAemB,OAAON,SAAQ,EACpChB,KAMOiC,kBACNnD,eAEA,GAAIkB,KAAKX,WAAaV,aAAauD,OAAQ,CACzC,MAAMnD,KAAEA,KAAIE,OAAEA,UAAWkD,OAAUrD,aAOnC,OANIsD,OAAOC,KAAKF,OAAOG,OAAS,GAC9BtC,KAAKY,OAAO2B,KACV,6EACAzD,cAGG,CAAEC,UAAME,cAChB,CAED,OAAOH,mBAMD0D,eAAkBC,UAExB,GDxV0BA,UACX,eAAjBA,QAAQ1D,MACS,gBAAjB0D,QAAQ1D,MACS,sBAAjB0D,QAAQ1D,MACS,cAAjB0D,QAAQ1D,KCoVF2D,CAAcD,SAChB,OAAQA,QAAQ1D,MACd,IAAK,cAEH,YADAiB,KAAK2C,cAAcF,SAErB,IAAK,YAEH,YADAzC,KAAK4C,YAAYH,SAKvBzC,KAAKY,OAAO2B,KAAK,kBAAmBE,QACtC,EAKQE,KAAAA,cAAiBjD,SAEvB,MAAMmD,UAAwB,CAC5BlD,kBAAmBD,OAAOC,mBAAqBK,KAAKN,OAAOC,kBAC3DE,WAAYH,OAAOG,YAAcG,KAAKN,OAAOG,WAC7CE,YAAa,IACPC,KAAKN,OAAOK,aAAe,CAAA,KAC3BL,OAAOK,aAAe,KAI9BC,KAAKN,OAASmD,UAGd,IAAK,MAAM7B,YAAgBhB,KAACC,gBAC1B,IACEe,SAAS6B,UACV,CAAC,MAAOC,OACP9C,KAAKY,OAAOkC,MAAM,2BAA4BA,MAC/C,CACF,EAMKC,KAAAA,eAAkBC,OACxB,MAAMnD,WAAEA,WAAUE,YAAEA,aAAgBC,KAAKN,OACzC,GAAmB,SAAfG,YACF,GD9YJmD,OAC+C,iBAAZA,KAAK,GC6YhCC,CAAeD,MAEjB,OAAOA,UAEAE,GD9YbF,OAEAA,KAAKV,QAAU,GAAwB,iBAAZU,KAAK,IAAmBG,MAAMC,QAAQJ,KAAK,IC4YzDE,CAAkBF,MAAO,CAElC,MAAMK,OAA0B,IAEzBC,UAAWC,QAAUP,KACtBQ,mBAAqBzD,YAAYuD,WACvC,QAA2BG,IAAvBD,mBACF,MAAU,IAAAE,MAAM,yDAIlB,IAAIC,OAAS,EACb,KAAOA,OAASJ,OAAOjB,QAAQ,CAC7B,MAAMsB,MAAuB,CAC3BN,qBAIF,IAAK,MAAMO,SAASL,mBAAoB,CACtC,MAAMM,MAAQP,OAAOI,QACrB,QAAcF,IAAVK,MACF,MAAM,IAAIJ,MAAM,sCAElBE,MAAMC,OAASC,MACfH,QACD,CACDN,OAAOU,KAAKH,MACb,CAED,OAAOP,MACR,CAED,MAAM,IAAIK,MAAM,kEAAiE,EAClF1D,KAKO4C,YAAc,EAAGI,cACvB,IACE,MAAMK,OAASrD,KAAK+C,eAAeC,MAEnC,IAAK,MAAMhC,YAAYhB,KAAKG,eAC1B,IACEa,SAASqC,OACV,CAAC,MAAOP,OACP9C,KAAKY,OAAOkC,MAAM,0BAA2BA,MAC9C,CAEJ,CAAC,MAAOA,OAEP,YADA9C,KAAKY,OAAOkC,MAAM,oBAAqBA,MAExC,QAMKkB,cAAiBA,gBACvB,OAAQA,eACN,KAAKtC,mBAAmBC,OAGtB,OAFA3B,KAAK4B,iBAAiB5B,KAAKS,eAAe,GAEnCT,KAAKiE,cAEd,KAAKvC,mBAAmBwC,UAGtB,YADAlE,KAAKU,aAAac,QAGpB,KAAKE,mBAAmByC,OAEtB,YAAY5C,QACf,OASK6C,+BAAiC,CACvCC,MACAC,oBAEItE,KAAKR,QAAQsB,aAAeY,mBAAmBC,cAE1B8B,IAArBa,kBAAkCA,iBAAiBC,KAAO,GAC5DvE,KAAK4B,iBAAiB0C,kBAGxBtE,KAAKR,QAAQgF,KAAK,CAChBzF,KAAM,uBACHsF,QAC8B,EAM7BI,KAAAA,aAAgBA,eACtBzE,KAAKY,OAAOkC,MAAM,mBAAoB2B,aACxC,EAuCQC,KAAAA,gBAAkB,KACxB,MAAMJ,iBAAmB,IAAIpE,IAC7B,IAAImE,MAA+B,CAAE,EACjCM,UAAY,EAGZ3E,KAAKO,eACP8D,MAAMO,OAAQ,EACdD,WAAa,GACb3E,KAAKO,cAAe,GAItB,IAAK,MAAOsE,IAAK/F,gBAAqBkB,KAACM,cAAcwE,WACjDT,MAAMU,SAAW,IAAIhB,KAAKjF,cAC5B6F,WAAaE,IAAIvC,QAAU,aAAcxD,aAAe,GAAK,IAG7DkB,KAAKQ,cAAcc,OAAOzC,mBAAmBC,eAGzC6F,WAAa3E,KAAKV,QAAQ0F,+BAC5BhF,KAAKoE,+BAA+BC,OACpCA,MAAQ,CAAE,EACVM,UAAY,GAGhB3E,KAAKM,cAAckB,QAGnB,IAAK,MAAOqD,IAAK/F,qBAAsBsB,WAAW0E,WAC9CT,MAAMjD,MAAQ,IAAI2C,KAAKjF,cACzB6F,WAAaE,IAAIvC,QAAU,aAAcxD,aAAe,GAAK,IAGxDkB,KAAKS,cAAcwE,IAAInG,aAAaC,QACvCuF,iBAAiBlD,IAAItC,aAAaC,MAClCiB,KAAKS,cAAcW,IAAItC,aAAaC,OAItCiB,KAAKQ,cAAc0E,IAAIL,IAAK/F,cAGxB6F,WAAa3E,KAAKV,QAAQ0F,+BAC5BhF,KAAKoE,+BAA+BC,MAAOC,kBAC3CA,iBAAiB9C,QACjB6C,MAAQ,CAAA,EACRM,UAAY,GAGhB3E,KAAKI,WAAWoB,QAGZmD,UAAY,GACd3E,KAAKoE,+BAA+BC,MAAOC,iBAC5C,EAQK1C,KAAAA,iBAAmB,CAACuD,WAAyBC,OAAiB,KACpE,IAAIC,kBAGJ,QAA4C5B,IAAxCzD,KAAKP,aAAa4F,kBACpB,IAAK,MAAM/B,aAAa6B,WAAY,CAClC,MAAMpF,YAAcC,KAAKP,aAAa4F,kBAAkB/B,gBACpCG,IAAhB1D,cACFsF,oBAAsB,CAAE,EACxBA,kBAAkB/B,WAAavD,YAElC,CAGH,MAAMuF,wBAAEA,wBAAuBC,iBAAEA,kBAAqBvF,KAAKP,kBAInCgE,IAAtB4B,wBAC4B5B,IAA5B6B,8BACqB7B,IAArB8B,mBAQAH,YACsB3B,IAAtB4B,wBAC6B5B,IAA5B6B,yBACCA,0BAA4BtF,KAAKN,OAAOC,wBACpB8D,IAArB8B,kBAAkCA,mBAAqBvF,KAAKN,OAAOG,aAEpEG,KAAKR,QAAQgF,KAAK,CAChBzF,KAAM,aACNuG,gDACAC,kCACAF,qCAEH,EA7aDrF,KAAKV,QAAU,CACbkG,SAAUC,eAAeC,KACzBC,uBAAwB,IACxBX,6BAA8B,QAC3B1F,SAGLU,KAAKR,QAAUJ,OAAOwG,YApFA,OAoF+B,CAAEvG,oBACvDW,KAAKT,GAAKS,KAAKR,QAAQD,GACvBS,KAAKX,SAAWA,SAChBW,KAAKR,QAAQqG,mBAAmB7F,KAAKwC,gBACrCxC,KAAKR,QAAQuB,uBAAuBf,KAAKgE,eACzChE,KAAKR,QAAQsG,iBAAiB9F,KAAKyE,cAEnCzE,KAAK+F,iBAAmB/F,KAAK+F,iBAAiBC,KAAKhG,MACnDA,KAAKiG,oBAAsBjG,KAAKiG,oBAAoBD,KAAKhG,MAEzDA,KAAKY,OAAS,IAAIsF,OAAO,GAAGhH,WAAWiH,QAAQnG,KAAKT,KAAMS,KAAKV,QAAQkG,SACzE,CA8CAO,gBAAAA,GAAmC,IAAfK,KAAe,GAAAC,MAAAC,KAAAC,WACjC,MAAMC,OAA6CrD,MAAMC,QAAQgD,KAAK,IAAMA,KAAK,GAAKA,KAEtF,IAAK,MAAMK,SAASD,OAAQ,CAC1B,MAAM1H,aAAekB,KAAKiC,kBAAkBwE,OAE5CzG,KAAKI,WAAW8E,IAAIrG,mBAAmBC,cAAeA,aACvD,CAEDkB,KAAK8B,yBACP,CAIAmE,mBAAAA,GAAsC,IAAfG,KAAeC,GAAAA,MAAAC,KAAAC,WACpC,MAAMC,OAA6CrD,MAAMC,QAAQgD,KAAK,IAAMA,KAAK,GAAKA,KAEtF,IAAK,MAAMK,SAASD,OAAQ,CAC1B,MAAM1H,aAAekB,KAAKiC,kBAAkBwE,OAEtC5B,IAAMhG,mBAAmBC,cAC/BkB,KAAKM,cAAc4E,IAAIL,IAAK/F,cAC5BkB,KAAKI,WAAWkB,OAAOuD,IACxB,CAED7E,KAAK8B,yBACP,CAyMQmC,WAAAA,GAEN,IAAK,MAAOY,IAAK/F,gBAAiBkB,KAAKQ,cACrCR,KAAKI,WAAW8E,IAAIL,IAAK/F,cAGE,IAAzBkB,KAAKI,WAAWmE,OAGpBvE,KAAKM,cAAckB,QACnBxB,KAAKO,cAAe,EAGpBP,KAAK0E,kBACP,CAKQ5C,uBAAAA,GACF9B,KAAKU,aAAauE,IAAI,oBAI1BjF,KAAKU,aAAagG,SAChB1G,KAAK0E,gBACL1E,KAAKV,QAAQqG,uBACb,kBAEJ"} |
+2
-2
| { | ||
| "name": "@dxfeed/dxlink-feed", | ||
| "version": "0.3.0", | ||
| "version": "0.4.0", | ||
| "private": false, | ||
@@ -30,3 +30,3 @@ "sideEffects": false, | ||
| "dependencies": { | ||
| "@dxfeed/dxlink-core": "0.3.0" | ||
| "@dxfeed/dxlink-core": "0.4.0" | ||
| }, | ||
@@ -33,0 +33,0 @@ "author": "Dmitry Petrov <dmitry.petrov@devexperts.com>", |
| import { type DXLinkChannelMessage } from '@dxfeed/dxlink-core'; | ||
| export declare enum FeedContract { | ||
| 'TICKER' = "TICKER", | ||
| 'HISTORY' = "HISTORY", | ||
| 'STREAM' = "STREAM", | ||
| 'AUTO' = "AUTO" | ||
| } | ||
| export declare enum FeedDataFormat { | ||
| 'FULL' = "FULL", | ||
| 'COMPACT' = "COMPACT" | ||
| } | ||
| export interface FeedParameters { | ||
| readonly contract: FeedContract; | ||
| } | ||
| export interface FeedEventFields { | ||
| [eventType: string]: string[]; | ||
| } | ||
| export interface FeedSetupMessage { | ||
| readonly type: 'FEED_SETUP'; | ||
| readonly acceptAggregationPeriod?: number; | ||
| readonly acceptDataFormat?: FeedDataFormat; | ||
| readonly acceptEventFields?: FeedEventFields; | ||
| } | ||
| export interface FeedConfigMessage { | ||
| readonly type: 'FEED_CONFIG'; | ||
| readonly aggregationPeriod: number; | ||
| readonly dataFormat: FeedDataFormat; | ||
| readonly eventFields?: FeedEventFields; | ||
| } | ||
| export type Subscription = { | ||
| readonly type: string; | ||
| readonly symbol: string; | ||
| }; | ||
| export type TimeSeriesSubscription = { | ||
| readonly type: string; | ||
| readonly symbol: string; | ||
| readonly fromTime: number; | ||
| }; | ||
| export type IndexedEventSubscription = { | ||
| readonly type: string; | ||
| readonly symbol: string; | ||
| readonly source: string; | ||
| }; | ||
| export interface FeedSubscriptionMessage { | ||
| readonly type: 'FEED_SUBSCRIPTION'; | ||
| readonly add?: (Subscription | TimeSeriesSubscription | IndexedEventSubscription)[]; | ||
| readonly remove?: (Subscription | TimeSeriesSubscription | IndexedEventSubscription)[]; | ||
| readonly reset?: boolean; | ||
| } | ||
| export type FeedEventValue = number | string | boolean; | ||
| export interface FeedEventData { | ||
| [key: string]: FeedEventValue; | ||
| } | ||
| export type FeedCompactEventData = [string, FeedEventValue[]]; | ||
| export interface FeedDataMessage { | ||
| readonly type: 'FEED_DATA'; | ||
| readonly data: FeedEventData[] | FeedCompactEventData; | ||
| } | ||
| export type FeedMessage = FeedSetupMessage | FeedConfigMessage | FeedSubscriptionMessage | FeedDataMessage; | ||
| export declare const isFeedFullData: (data: FeedEventData[] | FeedCompactEventData) => data is FeedEventData[]; | ||
| export declare const isFeedCompactData: (data: FeedEventData[] | FeedCompactEventData) => data is FeedCompactEventData; | ||
| export declare const isFeedMessage: (message: DXLinkChannelMessage) => message is FeedMessage; |
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAA"} |
| /** | ||
| * A logger interface that can be used to log messages. | ||
| */ | ||
| export interface DXLinkLogger { | ||
| debug(message: string, ...args: unknown[]): void; | ||
| info(message: string, ...args: unknown[]): void; | ||
| warn(message: string, ...args: unknown[]): void; | ||
| error(message: string, ...args: unknown[]): void; | ||
| } | ||
| /** | ||
| * Level of logging that can be used to filter out messages. | ||
| */ | ||
| export declare enum DXLinkLogLevel { | ||
| DEBUG = 0, | ||
| INFO = 1, | ||
| WARN = 2, | ||
| ERROR = 3 | ||
| } | ||
| /** | ||
| * A logger that uses the console to log messages. | ||
| * @internal | ||
| */ | ||
| export declare class Logger implements DXLinkLogger { | ||
| private readonly prefix; | ||
| private readonly level; | ||
| constructor(prefix: string, level: DXLinkLogLevel); | ||
| debug(message: string, ...args: unknown[]): void; | ||
| info(message: string, ...args: unknown[]): void; | ||
| warn(message: string, ...args: unknown[]): void; | ||
| error(message: string, ...args: unknown[]): void; | ||
| } | ||
| //# sourceMappingURL=logger.d.ts.map |
| {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;IAChD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;IAC/C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;IAC/C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;CACjD;AAED;;GAEG;AACH,oBAAY,cAAc;IACxB,KAAK,IAAI;IACT,IAAI,IAAI;IACR,IAAI,IAAI;IACR,KAAK,IAAI;CACV;AAED;;;GAGG;AACH,qBAAa,MAAO,YAAW,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAAU,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAtC,MAAM,EAAE,MAAM,EAAmB,KAAK,EAAE,cAAc;IAEnF,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAKhD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAK/C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAK/C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;CAKjD"} |
98111
-4.2%8
-33.33%423
-18.02%+ Added
- Removed
Updated