From 60b086b985bc7f471215c4f0f4c122371660074b Mon Sep 17 00:00:00 2001
From: mtyszczak <mateusz.tyszczak@gmail.com>
Date: Mon, 13 Jan 2025 13:35:42 +0100
Subject: [PATCH] Create only one provider per type when called multiple on
 methods in queen bee

---
 src/chain-observers/observer-mediator.ts      |   4 +-
 .../providers/account-provider.ts             |  14 +-
 .../providers/alarm-provider.ts               |  14 +-
 .../providers/mention-provider.ts             |  14 +-
 .../providers/provider-base.ts                |   4 +-
 .../providers/rc-account-provider.ts          |  14 +-
 .../providers/transaction-provider.ts         |  15 ++-
 .../providers/whale-alert-provider.ts         | 123 +++++++++---------
 .../providers/witness-provider.ts             |  14 +-
 src/queen.ts                                  |  42 +++---
 10 files changed, 152 insertions(+), 106 deletions(-)

diff --git a/src/chain-observers/observer-mediator.ts b/src/chain-observers/observer-mediator.ts
index 86e90b1..5697352 100644
--- a/src/chain-observers/observer-mediator.ts
+++ b/src/chain-observers/observer-mediator.ts
@@ -20,7 +20,7 @@ export class ObserverMediator {
     this.factory = new factory(worker);
   }
 
-  private filters = new Map<Partial<Observer<any>>, { filter: FilterBase; providers: ProviderBase[]; }>();
+  private filters = new Map<Partial<Observer<any>>, { filter: FilterBase; providers: Iterable<ProviderBase>; }>();
 
   public notify() {
     const context = this.factory.collect();
@@ -46,7 +46,7 @@ export class ObserverMediator {
       }).catch(error => listener.error?.(error));
   }
 
-  public registerListener(listener: Partial<Observer<any>>, filter: FilterBase, providers: ProviderBase[]) {
+  public registerListener(listener: Partial<Observer<any>>, filter: FilterBase, providers: Iterable<ProviderBase>) {
     this.filters.set(listener, { filter, providers });
 
     for(const classifier of filter.usedContexts())
diff --git a/src/chain-observers/providers/account-provider.ts b/src/chain-observers/providers/account-provider.ts
index 68a1bb5..b12526f 100644
--- a/src/chain-observers/providers/account-provider.ts
+++ b/src/chain-observers/providers/account-provider.ts
@@ -13,11 +13,15 @@ export interface IAccountProviderData<TAccounts extends Array<TAccountName>> {
   accounts: TAccountProvided<TAccounts>;
 };
 
-export class AccountProvider<TAccounts extends Array<TAccountName> = Array<TAccountName>> extends ProviderBase {
-  public constructor(
-    private readonly accounts: TAccounts
-  ) {
-    super();
+export interface IAccountProviderOptions {
+  accounts: string[];
+}
+
+export class AccountProvider<TAccounts extends Array<TAccountName> = Array<TAccountName>> extends ProviderBase<IAccountProviderOptions> {
+  public readonly accounts: string[] = [];
+
+  public pushOptions(options: IAccountProviderOptions): void {
+    this.accounts.push(...options.accounts);
   }
 
   public usedContexts(): Array<TRegisterEvaluationContext> {
diff --git a/src/chain-observers/providers/alarm-provider.ts b/src/chain-observers/providers/alarm-provider.ts
index 40c23e6..a8d55f0 100644
--- a/src/chain-observers/providers/alarm-provider.ts
+++ b/src/chain-observers/providers/alarm-provider.ts
@@ -21,11 +21,15 @@ export interface IAlarmAccountsData<TAccounts extends Array<TAccountName>> {
   alarmsPerAccount: TAlarmAccounts<TAccounts>;
 };
 
-export class AlarmProvider<TAccounts extends Array<TAccountName> = Array<TAccountName>> extends ProviderBase {
-  public constructor(
-    private readonly accounts: TAccounts
-  ) {
-    super();
+export interface IAlarmProviderOptions {
+  accounts: string[];
+}
+
+export class AlarmProvider<TAccounts extends Array<TAccountName> = Array<TAccountName>> extends ProviderBase<IAlarmProviderOptions> {
+  public readonly accounts: string[] = [];
+
+  public pushOptions(options: IAlarmProviderOptions): void {
+    this.accounts.push(...options.accounts);
   }
 
   public usedContexts(): Array<TRegisterEvaluationContext> {
diff --git a/src/chain-observers/providers/mention-provider.ts b/src/chain-observers/providers/mention-provider.ts
index 162c930..beb917f 100644
--- a/src/chain-observers/providers/mention-provider.ts
+++ b/src/chain-observers/providers/mention-provider.ts
@@ -13,11 +13,15 @@ export interface IMentionedAccountProviderData<TMentions extends Array<TAccountN
   mentioned: TMentionedAccountProvided<TMentions>;
 };
 
-export class MentionedAccountProvider<TMentions extends Array<TAccountName> = Array<TAccountName>> extends ProviderBase {
-  public constructor(
-    private readonly accounts: TMentions
-  ) {
-    super();
+export interface IMentionedAccountProviderOptions {
+  accounts: string[];
+}
+
+export class MentionedAccountProvider<TMentions extends Array<TAccountName> = Array<TAccountName>> extends ProviderBase<IMentionedAccountProviderOptions> {
+  public readonly accounts: string[] = [];
+
+  public pushOptions(options: IMentionedAccountProviderOptions): void {
+    this.accounts.push(...options.accounts);
   }
 
   public usedContexts(): Array<TRegisterEvaluationContext> {
diff --git a/src/chain-observers/providers/provider-base.ts b/src/chain-observers/providers/provider-base.ts
index bd689da..eaf7334 100644
--- a/src/chain-observers/providers/provider-base.ts
+++ b/src/chain-observers/providers/provider-base.ts
@@ -1,8 +1,10 @@
 import { TRegisterEvaluationContext } from "../classifiers/collector-classifier-base";
 import { DataEvaluationContext } from "../factories/data-evaluation-context";
 
-export abstract class ProviderBase {
+export abstract class ProviderBase<IOptions extends object = {}> {
   public abstract usedContexts(): Array<TRegisterEvaluationContext>;
 
+  public pushOptions?(options: IOptions): void;
+
   public abstract provide(data: DataEvaluationContext): Promise<any>;
 }
diff --git a/src/chain-observers/providers/rc-account-provider.ts b/src/chain-observers/providers/rc-account-provider.ts
index 590cb31..e1b6549 100644
--- a/src/chain-observers/providers/rc-account-provider.ts
+++ b/src/chain-observers/providers/rc-account-provider.ts
@@ -12,11 +12,15 @@ export interface IRcAccountProviderData<TAccounts extends Array<TAccountName>> {
   rcAccounts: TRcAccountProvided<TAccounts>;
 };
 
-export class RcAccountProvider<TAccounts extends Array<TAccountName> = Array<TAccountName>> extends ProviderBase {
-  public constructor(
-    private readonly rcAccounts: TAccounts
-  ) {
-    super();
+export interface IRcAccountsProviderOptions {
+  accounts: string[];
+}
+
+export class RcAccountProvider<TAccounts extends Array<TAccountName> = Array<TAccountName>> extends ProviderBase<IRcAccountsProviderOptions> {
+  public readonly rcAccounts: string[] = [];
+
+  public pushOptions(options: IRcAccountsProviderOptions): void {
+    this.rcAccounts.push(...options.accounts);
   }
 
   public usedContexts(): Array<TRegisterEvaluationContext> {
diff --git a/src/chain-observers/providers/transaction-provider.ts b/src/chain-observers/providers/transaction-provider.ts
index 744ef35..9a11809 100644
--- a/src/chain-observers/providers/transaction-provider.ts
+++ b/src/chain-observers/providers/transaction-provider.ts
@@ -12,11 +12,16 @@ export interface ITransactionProviderData<TIdOfTx extends Array<string>> {
   transactions: Partial<TTransactionProvider<TIdOfTx>>;
 };
 
-export class TransactionByIdProvider<TIdOfTx extends Array<string> = Array<string>> extends ProviderBase {
-  public constructor(
-    private readonly transactionIds: TIdOfTx
-  ) {
-    super();
+export interface ITransactionByIdProviderOptions {
+  transactionIds: string[];
+}
+
+export class TransactionByIdProvider<TIdOfTx extends Array<string> = Array<string>> extends ProviderBase<ITransactionByIdProviderOptions> {
+  public readonly transactionIds = new Set<string>();
+
+  public pushOptions(options: ITransactionByIdProviderOptions): void {
+    for(const id of options.transactionIds)
+      this.transactionIds.add(id);
   }
 
   public usedContexts(): Array<TRegisterEvaluationContext> {
diff --git a/src/chain-observers/providers/whale-alert-provider.ts b/src/chain-observers/providers/whale-alert-provider.ts
index f9c9740..b2b8767 100644
--- a/src/chain-observers/providers/whale-alert-provider.ts
+++ b/src/chain-observers/providers/whale-alert-provider.ts
@@ -16,11 +16,16 @@ export interface IWhaleAlertProviderData {
   whaleOperations: WorkerBeeIterable<IOperationTransactionPair<IWhaleAlertMetadata>>;
 };
 
-export class WhaleAlertProvider extends ProviderBase {
-  public constructor(
-    private readonly asset: asset
-  ) {
-    super();
+export interface IWhaleAlertProviderOptions {
+  assets: asset[];
+}
+
+export class WhaleAlertProvider extends ProviderBase<IWhaleAlertProviderOptions> {
+  public readonly assets = new Map<string, asset>();
+
+  public pushOptions(options: IWhaleAlertProviderOptions): void {
+    for(const asset of options.assets)
+      this.assets.set(asset.nai, asset);
   }
 
   public usedContexts(): Array<TRegisterEvaluationContext> {
@@ -39,62 +44,64 @@ export class WhaleAlertProvider extends ProviderBase {
 
     const whaleOperations: IOperationTransactionPair<IWhaleAlertMetadata>[] = [];
 
-    if(transfer)
-      for(const op of transfer)
-        if(isGreaterThan(this.asset, op.operation.amount!))
-          whaleOperations.push({
-            operation: {
-              from: op.operation.from_account,
-              to: op.operation.to_account,
-              amount: op.operation.amount!
-            },
-            transaction: op.transaction
-          });
+    for(const asset of this.assets.values()) {
+      if(transfer)
+        for(const op of transfer)
+          if(isGreaterThan(asset, op.operation.amount!))
+            whaleOperations.push({
+              operation: {
+                from: op.operation.from_account,
+                to: op.operation.to_account,
+                amount: op.operation.amount!
+              },
+              transaction: op.transaction
+            });
 
-    if(fromSavings)
-      for(const op of fromSavings)
-        if(isGreaterThan(this.asset, op.operation.amount!))
-          whaleOperations.push({
-            operation: {
-              from: op.operation.from_account,
-              to: op.operation.to_account,
-              amount: op.operation.amount!
-            },
-            transaction: op.transaction
-          });
+      if(fromSavings)
+        for(const op of fromSavings)
+          if(isGreaterThan(asset, op.operation.amount!))
+            whaleOperations.push({
+              operation: {
+                from: op.operation.from_account,
+                to: op.operation.to_account,
+                amount: op.operation.amount!
+              },
+              transaction: op.transaction
+            });
 
-    if(escrow)
-      for(const op of escrow)
-        if(isGreaterThan(this.asset, op.operation.hbd_amount!))
-          whaleOperations.push({
-            operation: {
-              from: op.operation.from_account,
-              to: op.operation.to_account,
-              amount: op.operation.hbd_amount!
-            },
-            transaction: op.transaction
-          });
-        else if(isGreaterThan(this.asset, op.operation.hive_amount!))
-          whaleOperations.push({
-            operation: {
-              from: op.operation.from_account,
-              to: op.operation.to_account,
-              amount: op.operation.hive_amount!
-            },
-            transaction: op.transaction
-          });
+      if(escrow)
+        for(const op of escrow)
+          if(isGreaterThan(asset, op.operation.hbd_amount!))
+            whaleOperations.push({
+              operation: {
+                from: op.operation.from_account,
+                to: op.operation.to_account,
+                amount: op.operation.hbd_amount!
+              },
+              transaction: op.transaction
+            });
+          else if(isGreaterThan(asset, op.operation.hive_amount!))
+            whaleOperations.push({
+              operation: {
+                from: op.operation.from_account,
+                to: op.operation.to_account,
+                amount: op.operation.hive_amount!
+              },
+              transaction: op.transaction
+            });
 
-    if(recurrent)
-      for(const op of recurrent)
-        if(isGreaterThan(this.asset, op.operation.amount!))
-          whaleOperations.push({
-            operation: {
-              from: op.operation.from_account,
-              to: op.operation.to_account,
-              amount: op.operation.amount!
-            },
-            transaction: op.transaction
-          });
+      if(recurrent)
+        for(const op of recurrent)
+          if(isGreaterThan(asset, op.operation.amount!))
+            whaleOperations.push({
+              operation: {
+                from: op.operation.from_account,
+                to: op.operation.to_account,
+                amount: op.operation.amount!
+              },
+              transaction: op.transaction
+            });
+    }
 
     return {
       whaleOperations: new WorkerBeeIterable(whaleOperations)
diff --git a/src/chain-observers/providers/witness-provider.ts b/src/chain-observers/providers/witness-provider.ts
index 1f6ce04..9677b53 100644
--- a/src/chain-observers/providers/witness-provider.ts
+++ b/src/chain-observers/providers/witness-provider.ts
@@ -12,11 +12,15 @@ export interface IWitnessProviderData<TAccounts extends Array<TAccountName>> {
   witnesses: TWitnessProvider<TAccounts>;
 };
 
-export class WitnessProvider<TAccounts extends Array<TAccountName> = Array<TAccountName>> extends ProviderBase {
-  public constructor(
-    private readonly witnesses: TAccounts
-  ) {
-    super();
+export interface IWitnessProviderOptions {
+  accounts: string[];
+}
+
+export class WitnessProvider<TAccounts extends Array<TAccountName> = Array<TAccountName>> extends ProviderBase<IWitnessProviderOptions> {
+  public readonly witnesses: string[] = [];
+
+  public pushOptions(options: IWitnessProviderOptions): void {
+    this.witnesses.push(...options.accounts);
   }
 
   public usedContexts(): Array<TRegisterEvaluationContext> {
diff --git a/src/queen.ts b/src/queen.ts
index b6dabb1..a537920 100644
--- a/src/queen.ts
+++ b/src/queen.ts
@@ -44,7 +44,7 @@ export class QueenBee<TPreviousSubscriberData extends object = {}> {
     private readonly worker: WorkerBee
   ) {}
 
-  private providers: ProviderBase[] = [];
+  private providers = new Map<new () => ProviderBase, ProviderBase>();
   private operands: FilterBase[] = [];
   private filterContainers: FilterBase[] = [];
 
@@ -61,10 +61,10 @@ export class QueenBee<TPreviousSubscriberData extends object = {}> {
     // Optimize by not creating a logical OR filter for only one filter
     const orFilter: FilterBase = committedFilters.length === 1 ? committedFilters[0] : new LogicalOrFilter(this.worker, committedFilters);
 
-    this.worker.mediator.registerListener(observer, orFilter, this.providers);
+    this.worker.mediator.registerListener(observer, orFilter, this.providers.values());
 
     this.filterContainers = [];
-    this.providers = [];
+    this.providers = new Map();
 
     return {
       unsubscribe: () => {
@@ -86,6 +86,18 @@ export class QueenBee<TPreviousSubscriberData extends object = {}> {
     return this as unknown as QueenBee<TPreviousSubscriberData>;
   }
 
+  private pushProvider<T extends new () => ProviderBase>(
+    provider: T,
+    options: InstanceType<T>["pushOptions"] extends undefined ? {} : Parameters<Exclude<InstanceType<T>["pushOptions"], undefined>>[0] = {}
+  ) {
+    let instance = this.providers.get(provider);
+
+    if (!instance)
+      this.providers.set(provider, instance = new provider());
+
+    instance.pushOptions?.(options);
+  }
+
   public onBlockNumber(number: number): QueenBee<TPreviousSubscriberData> {
     this.operands.push(new BlockNumberFilter(this.worker, number));
 
@@ -96,7 +108,7 @@ export class QueenBee<TPreviousSubscriberData extends object = {}> {
     TIdTx extends string
   >(transactionId: TIdTx): QueenBee<TPreviousSubscriberData & Awaited<ReturnType<TransactionByIdProvider<[TIdTx]>["provide"]>>> {
     this.operands.push(new TransactionIdFilter(this.worker, transactionId));
-    this.providers.push(new TransactionByIdProvider<[TIdTx]>([transactionId]));
+    this.pushProvider(TransactionByIdProvider, { transactionIds: [transactionId] });
 
     return this;
   }
@@ -156,7 +168,7 @@ export class QueenBee<TPreviousSubscriberData extends object = {}> {
   }
 
   public provideFeedPriceData(): QueenBee<TPreviousSubscriberData & Awaited<ReturnType<FeedPriceProvider["provide"]>>> {
-    this.providers.push(new FeedPriceProvider());
+    this.pushProvider(FeedPriceProvider);
 
     return this;
   }
@@ -177,7 +189,7 @@ export class QueenBee<TPreviousSubscriberData extends object = {}> {
     TMention extends TAccountName
   >(mentionedAccount: TMention): QueenBee<TPreviousSubscriberData & Awaited<ReturnType<MentionedAccountProvider<[TMention]>["provide"]>>> {
     this.operands.push(new PostMentionFilter(this.worker, mentionedAccount));
-    this.providers.push(new MentionedAccountProvider<[TMention]>([mentionedAccount]));
+    this.pushProvider(MentionedAccountProvider, { accounts: [mentionedAccount] });
 
     return this;
   }
@@ -186,7 +198,7 @@ export class QueenBee<TPreviousSubscriberData extends object = {}> {
     TAccount extends TAccountName
   >(watchAccount: TAccount): QueenBee<TPreviousSubscriberData & Awaited<ReturnType<AlarmProvider<[TAccount]>["provide"]>>> {
     this.operands.push(new AlarmFilter(this.worker, watchAccount));
-    this.providers.push(new AlarmProvider<[TAccount]>([watchAccount]));
+    this.pushProvider(AlarmProvider, { accounts: [watchAccount] });
 
     return this;
   }
@@ -205,14 +217,14 @@ export class QueenBee<TPreviousSubscriberData extends object = {}> {
 
   public onInternalMarketOperation(): QueenBee<TPreviousSubscriberData & Awaited<ReturnType<InternalMarketProvider["provide"]>>> {
     this.operands.push(new InternalMarketFilter(this.worker));
-    this.providers.push(new InternalMarketProvider());
+    this.pushProvider(InternalMarketProvider);
 
     return this;
   }
 
   public onBlock(): QueenBee<TPreviousSubscriberData & Awaited<ReturnType<BlockHeaderProvider["provide"]>>> {
     this.operands.push(new BlockChangedFilter(this.worker));
-    this.providers.push(new BlockHeaderProvider());
+    this.pushProvider(BlockHeaderProvider);
 
     return this;
   }
@@ -220,7 +232,7 @@ export class QueenBee<TPreviousSubscriberData extends object = {}> {
   public provideAccounts<
     TAccounts extends Array<TAccountName>
   >(...accounts: TAccounts): QueenBee<TPreviousSubscriberData & Awaited<ReturnType<AccountProvider<TAccounts>["provide"]>>> {
-    this.providers.push(new AccountProvider<TAccounts>(accounts));
+    this.pushProvider(AccountProvider, { accounts });
 
     return this;
   }
@@ -228,7 +240,7 @@ export class QueenBee<TPreviousSubscriberData extends object = {}> {
   public provideWitnesses<
     TAccounts extends Array<TAccountName>
   >(...witnesses: TAccounts): QueenBee<TPreviousSubscriberData & Awaited<ReturnType<WitnessProvider<TAccounts>["provide"]>>> {
-    this.providers.push(new WitnessProvider<TAccounts>(witnesses));
+    this.pushProvider(WitnessProvider, { accounts: witnesses });
 
     return this;
   }
@@ -236,27 +248,27 @@ export class QueenBee<TPreviousSubscriberData extends object = {}> {
   public provideRcAccounts<
     TAccounts extends Array<TAccountName>
   >(...accounts: TAccounts): QueenBee<TPreviousSubscriberData & Awaited<ReturnType<RcAccountProvider<TAccounts>["provide"]>>> {
-    this.providers.push(new RcAccountProvider<TAccounts>(accounts));
+    this.pushProvider(RcAccountProvider, { accounts });
 
     return this;
   }
 
   public onWhaleAlert(asset: asset): QueenBee<TPreviousSubscriberData & Awaited<ReturnType<WhaleAlertProvider["provide"]>>> {
     this.operands.push(new WhaleAlertFilter(this.worker, asset));
-    this.providers.push(new WhaleAlertProvider(asset));
+    this.pushProvider(WhaleAlertProvider, { assets: [asset] });
 
     return this;
   }
 
   public onExchangeTransfer(): QueenBee<TPreviousSubscriberData & Awaited<ReturnType<ExchangeTransferProvider["provide"]>>> {
     this.operands.push(new ExchangeTransferFilter(this.worker));
-    this.providers.push(new ExchangeTransferProvider());
+    this.pushProvider(ExchangeTransferProvider);
 
     return this;
   }
 
   public provideBlockData(): QueenBee<TPreviousSubscriberData & Awaited<ReturnType<BlockProvider["provide"]>>> {
-    this.providers.push(new BlockProvider());
+    this.pushProvider(BlockProvider);
 
     return this;
   }
-- 
GitLab