🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@dxfeed/dxlink-feed

Package Overview
Dependencies
Maintainers
3
Versions
15
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@dxfeed/dxlink-feed - npm Package Compare versions

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

@@ -30,3 +30,3 @@ "sideEffects": false,

"dependencies": {
"@dxfeed/dxlink-core": "0.8.1"
"@dxfeed/dxlink-core": "0.9.0"
},

@@ -33,0 +33,0 @@ "author": "Dmitry Petrov <dmitry.petrov@devexperts.com>",