From 6b0e7fc221997a663ef36c7c720fea0905c509fa Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Fri, 3 Oct 2025 21:47:28 +0600 Subject: [PATCH 1/4] [FSSDK-11879] flush events without closing client on page unload --- lib/client_factory.ts | 2 +- lib/event_processor/batch_event_processor.ts | 8 ++++++-- lib/event_processor/event_processor.ts | 1 + .../forwarding_event_processor.ts | 4 ++++ lib/index.browser.ts | 2 +- lib/odp/event_manager/odp_event_manager.ts | 5 +++++ lib/odp/odp_manager.spec.ts | 1 + lib/odp/odp_manager.ts | 5 +++++ lib/optimizely/index.ts | 18 ++++++++++++++++++ 9 files changed, 42 insertions(+), 4 deletions(-) diff --git a/lib/client_factory.ts b/lib/client_factory.ts index 7307075cf..87a239246 100644 --- a/lib/client_factory.ts +++ b/lib/client_factory.ts @@ -31,7 +31,7 @@ export type OptimizelyFactoryConfig = Config & { requestHandler: RequestHandler; } -export const getOptimizelyInstance = (config: OptimizelyFactoryConfig): Client => { +export const getOptimizelyInstance = (config: OptimizelyFactoryConfig): Optimizely => { const { clientEngine, clientVersion, diff --git a/lib/event_processor/batch_event_processor.ts b/lib/event_processor/batch_event_processor.ts index 6ad19eaf8..89cea6fe4 100644 --- a/lib/event_processor/batch_event_processor.ts +++ b/lib/event_processor/batch_event_processor.ts @@ -230,14 +230,14 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { }); } - private async flush(closing = false): Promise { + private async flush(useClosingDispatcher = false): Promise { const batch = this.createNewBatch(); if (!batch) { return; } this.dispatchRepeater.reset(); - this.dispatchBatch(batch, closing); + this.dispatchBatch(batch, useClosingDispatcher); } async process(event: ProcessableEvent): Promise { @@ -332,6 +332,10 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { } } + flushImmediately(): Promise { + return this.flush(true); + } + stop(): void { if (this.isDone()) { return; diff --git a/lib/event_processor/event_processor.ts b/lib/event_processor/event_processor.ts index 3589ce3a5..585c71f68 100644 --- a/lib/event_processor/event_processor.ts +++ b/lib/event_processor/event_processor.ts @@ -28,4 +28,5 @@ export interface EventProcessor extends Service { process(event: ProcessableEvent): Promise; onDispatch(handler: Consumer): Fn; setLogger(logger: LoggerFacade): void; + flushImmediately(): Promise; } diff --git a/lib/event_processor/forwarding_event_processor.ts b/lib/event_processor/forwarding_event_processor.ts index 80ce1c763..f578992c7 100644 --- a/lib/event_processor/forwarding_event_processor.ts +++ b/lib/event_processor/forwarding_event_processor.ts @@ -69,4 +69,8 @@ export class ForwardingEventProcessor extends BaseService implements EventProces onDispatch(handler: Consumer): Fn { return this.eventEmitter.on('dispatch', handler); } + + flushImmediately(): Promise { + return Promise.resolve(); + } } diff --git a/lib/index.browser.ts b/lib/index.browser.ts index 4cbfc7c69..0f644a844 100644 --- a/lib/index.browser.ts +++ b/lib/index.browser.ts @@ -37,7 +37,7 @@ export const createInstance = function(config: Config): Client { window.addEventListener( unloadEvent, () => { - client.close(); + client.flushImmediately(); }, ); } diff --git a/lib/odp/event_manager/odp_event_manager.ts b/lib/odp/event_manager/odp_event_manager.ts index 3a9c591cc..ad1cd9b95 100644 --- a/lib/odp/event_manager/odp_event_manager.ts +++ b/lib/odp/event_manager/odp_event_manager.ts @@ -42,6 +42,7 @@ export interface OdpEventManager extends Service { updateConfig(odpIntegrationConfig: OdpIntegrationConfig): void; sendEvent(event: OdpEvent): void; setLogger(logger: LoggerFacade): void; + flushImmediately(): Promise; } export type RetryConfig = { @@ -160,6 +161,10 @@ export class DefaultOdpEventManager extends BaseService implements OdpEventManag this.startPromise.resolve(); } + flushImmediately(): Promise { + return this.flush(); + } + stop(): void { if (this.isDone()) { return; diff --git a/lib/odp/odp_manager.spec.ts b/lib/odp/odp_manager.spec.ts index 376a663cf..d715f1856 100644 --- a/lib/odp/odp_manager.spec.ts +++ b/lib/odp/odp_manager.spec.ts @@ -53,6 +53,7 @@ const getMockOdpEventManager = () => { sendEvent: vi.fn(), makeDisposable: vi.fn(), setLogger: vi.fn(), + flushImmediately: vi.fn(), }; }; diff --git a/lib/odp/odp_manager.ts b/lib/odp/odp_manager.ts index 2f8256c38..68e624176 100644 --- a/lib/odp/odp_manager.ts +++ b/lib/odp/odp_manager.ts @@ -40,6 +40,7 @@ export interface OdpManager extends Service { setClientInfo(clientEngine: string, clientVersion: string): void; setVuid(vuid: string): void; setLogger(logger: LoggerFacade): void; + flushImmediately(): Promise; } export type OdpManagerConfig = { @@ -145,6 +146,10 @@ export class DefaultOdpManager extends BaseService implements OdpManager { this.stopPromise.reject(error); } + flushImmediately(): Promise { + return this.eventManager.flushImmediately(); + } + stop(): void { if (this.isDone()) { return; diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index f6e2b4f35..93f2ed23e 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -1243,6 +1243,24 @@ export default class Optimizely extends BaseService implements Client { } } + flushImmediately(): Promise { + const ret = []; + + if (!this.isRunning()) { + return Promise.resolve(); + } + + if (this.eventProcessor) { + ret.push(this.eventProcessor.flushImmediately()); + } + + if(this.odpManager) { + ret.push(this.odpManager.flushImmediately()); + } + + return Promise.all(ret); + } + /** * Stop background processes belonging to this instance, including: * From 31153e2f42d9af35647ca3ac68b0ae5cb9015f87 Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Mon, 6 Oct 2025 18:43:22 +0600 Subject: [PATCH 2/4] tests --- .../batch_event_processor.spec.ts | 73 +++++++++++++++++++ lib/event_processor/batch_event_processor.ts | 3 + .../event_manager/odp_event_manager.spec.ts | 41 +++++++++++ lib/odp/event_manager/odp_event_manager.ts | 3 + lib/odp/odp_manager.spec.ts | 23 ++++++ lib/odp/odp_manager.ts | 3 + lib/optimizely/index.spec.ts | 33 +++++++++ 7 files changed, 179 insertions(+) diff --git a/lib/event_processor/batch_event_processor.spec.ts b/lib/event_processor/batch_event_processor.spec.ts index a95dd262f..3451fa1d9 100644 --- a/lib/event_processor/batch_event_processor.spec.ts +++ b/lib/event_processor/batch_event_processor.spec.ts @@ -1408,4 +1408,77 @@ describe('BatchEventProcessor', async () => { await expect(processor.onTerminated()).resolves.not.toThrow(); }); }); + + describe('flushImmediately', () => { + it('should disptach the events in queue using the closing dispatcher if available', async () => { + const eventDispatcher = getMockDispatcher(); + const closingEventDispatcher = getMockDispatcher(); + closingEventDispatcher.dispatchEvent.mockResolvedValue({}); + + const dispatchRepeater = getMockRepeater(); + const failedEventRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + closingEventDispatcher, + dispatchRepeater, + failedEventRepeater, + batchSize: 100, + }); + + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + for(let i = 0; i < 10; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + expect(closingEventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + + processor.flushImmediately(); + expect(closingEventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); + expect(closingEventDispatcher.dispatchEvent).toHaveBeenCalledWith(buildLogEvent(events)); + + expect(processor.isRunning()).toBe(true); + }); + + + it('should disptach the events in queue using eventDispatcher if closingEventDispatcher is not available', async () => { + const eventDispatcher = getMockDispatcher(); + eventDispatcher.dispatchEvent.mockResolvedValue({}); + + const dispatchRepeater = getMockRepeater(); + const failedEventRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + failedEventRepeater, + batchSize: 100, + }); + + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + for(let i = 0; i < 10; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + + processor.flushImmediately(); + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledWith(buildLogEvent(events)); + + expect(processor.isRunning()).toBe(true); + }); + }); }); diff --git a/lib/event_processor/batch_event_processor.ts b/lib/event_processor/batch_event_processor.ts index 89cea6fe4..86f7ff148 100644 --- a/lib/event_processor/batch_event_processor.ts +++ b/lib/event_processor/batch_event_processor.ts @@ -333,6 +333,9 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { } flushImmediately(): Promise { + if (!this.isRunning()) { + return Promise.resolve(); + } return this.flush(true); } diff --git a/lib/odp/event_manager/odp_event_manager.spec.ts b/lib/odp/event_manager/odp_event_manager.spec.ts index 6fb5db08a..68484d788 100644 --- a/lib/odp/event_manager/odp_event_manager.spec.ts +++ b/lib/odp/event_manager/odp_event_manager.spec.ts @@ -1010,4 +1010,45 @@ describe('DefaultOdpEventManager', () => { await expect(odpEventManager.onTerminated()).resolves.not.toThrow(); expect(odpEventManager.getState()).toBe(ServiceState.Terminated); }); + + it('should flush the queue when flushImmediately() is called in running state', async () => { + const repeater = getMockRepeater(); + + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + + const odpEventManager = new DefaultOdpEventManager({ + repeater: repeater, + apiManager: apiManager, + batchSize: 30, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + + const events: OdpEvent[] = []; + for(let i = 0; i < 10; i++) { + events.push(makeEvent(i)); + odpEventManager.sendEvent(events[i]); + } + + await exhaustMicrotasks(); + expect(apiManager.sendEvents).not.toHaveBeenCalled(); + + odpEventManager.flushImmediately(); + await exhaustMicrotasks(); + + expect(apiManager.sendEvents).toHaveBeenCalledTimes(1); + expect(apiManager.sendEvents).toHaveBeenCalledWith(config, events); + expect(odpEventManager.isRunning()).toBe(true); + }); }); diff --git a/lib/odp/event_manager/odp_event_manager.ts b/lib/odp/event_manager/odp_event_manager.ts index ad1cd9b95..d1a30d3ff 100644 --- a/lib/odp/event_manager/odp_event_manager.ts +++ b/lib/odp/event_manager/odp_event_manager.ts @@ -162,6 +162,9 @@ export class DefaultOdpEventManager extends BaseService implements OdpEventManag } flushImmediately(): Promise { + if (!this.isRunning()) { + return Promise.resolve(); + } return this.flush(); } diff --git a/lib/odp/odp_manager.spec.ts b/lib/odp/odp_manager.spec.ts index d715f1856..9ae0daf69 100644 --- a/lib/odp/odp_manager.spec.ts +++ b/lib/odp/odp_manager.spec.ts @@ -781,6 +781,29 @@ describe('DefaultOdpManager', () => { odpManager.makeDisposable(); expect(eventManager.makeDisposable).toHaveBeenCalled(); + }); + + it('should call flushImmediately() on eventManager when flushImmediately() is called on odpManager', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockResolvedValue({}); + const segmentManager = getMockOdpSegmentManager(); + + eventManager.flushImmediately.mockResolvedValue({}); + + const odpManager = new DefaultOdpManager({ + segmentManager, + eventManager, + }); + + odpManager.updateConfig({ integrated: true, odpConfig: config }); + odpManager.start(); + + await odpManager.onRunning(); + + odpManager.flushImmediately(); + + expect(eventManager.flushImmediately).toHaveBeenCalledOnce(); + expect(odpManager.isRunning()).toBe(true); }) }); diff --git a/lib/odp/odp_manager.ts b/lib/odp/odp_manager.ts index 68e624176..feaca24b9 100644 --- a/lib/odp/odp_manager.ts +++ b/lib/odp/odp_manager.ts @@ -147,6 +147,9 @@ export class DefaultOdpManager extends BaseService implements OdpManager { } flushImmediately(): Promise { + if (!this.isRunning()) { + return Promise.resolve(); + } return this.eventManager.flushImmediately(); } diff --git a/lib/optimizely/index.spec.ts b/lib/optimizely/index.spec.ts index 81509fd1e..4548ffbb7 100644 --- a/lib/optimizely/index.spec.ts +++ b/lib/optimizely/index.spec.ts @@ -872,4 +872,37 @@ describe('Optimizely', () => { }); }); }); + + it('should flush eventProcessor and odpManager on flushImmediately()', async () => { + const projectConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }); + + const eventProcessor = getForwardingEventProcessor(eventDispatcher); + const odpManager = extractOdpManager(createOdpManager({})); + + const optimizely = new Optimizely({ + clientEngine: 'node-sdk', + projectConfigManager, + jsonSchemaValidator, + logger, + eventProcessor, + odpManager, + disposable: true, + cmabService: {} as any + }); + + odpManager?.updateConfig({ integrated: false }); + await optimizely.onReady(); + + const eventProcessorFlushSpy = vi.spyOn(eventProcessor, 'flushImmediately').mockResolvedValue(Promise.resolve()); + const odpManagerFlushSpy = vi.spyOn(odpManager!, 'flushImmediately').mockResolvedValue(Promise.resolve()); + + await optimizely.flushImmediately(); + + expect(eventProcessorFlushSpy).toHaveBeenCalled(); + expect(odpManagerFlushSpy).toHaveBeenCalled(); + + expect(optimizely.isRunning()).toBe(true); + }); }); From 737d322cf60481585e6cc5d002f73b9037503a0f Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Tue, 7 Oct 2025 01:08:40 +0600 Subject: [PATCH 3/4] update --- .../batch_event_processor.spec.ts | 19 +++++++++---------- .../event_builder/log_event.ts | 2 +- lib/optimizely/index.ts | 8 ++++---- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/lib/event_processor/batch_event_processor.spec.ts b/lib/event_processor/batch_event_processor.spec.ts index 3451fa1d9..6d7674fd5 100644 --- a/lib/event_processor/batch_event_processor.spec.ts +++ b/lib/event_processor/batch_event_processor.spec.ts @@ -875,7 +875,7 @@ describe('BatchEventProcessor', async () => { }); describe('retryFailedEvents', () => { - it('should disptach only failed events from the store and not dispatch queued events', async () => { + it('should dispatch only failed events from the store and not dispatch queued events', async () => { const eventDispatcher = getMockDispatcher(); const mockDispatch: MockInstance = eventDispatcher.dispatchEvent; mockDispatch.mockResolvedValue({}); @@ -921,7 +921,7 @@ describe('BatchEventProcessor', async () => { ])); }); - it('should disptach only failed events from the store and not dispatch events that are being dispatched', async () => { + it('should dispatch only failed events from the store and not dispatch events that are being dispatched', async () => { const eventDispatcher = getMockDispatcher(); const mockDispatch: MockInstance = eventDispatcher.dispatchEvent; const mockResult1 = resolvablePromise(); @@ -977,7 +977,7 @@ describe('BatchEventProcessor', async () => { ])); }); - it('should disptach events in correct batch size and separate events with differnt contexts in separate batch', async () => { + it('should dispatch events in correct batch size and separate events with differnt contexts in separate batch', async () => { const eventDispatcher = getMockDispatcher(); const mockDispatch: MockInstance = eventDispatcher.dispatchEvent; mockDispatch.mockResolvedValue({}); @@ -1023,7 +1023,7 @@ describe('BatchEventProcessor', async () => { }); describe('when failedEventRepeater is fired', () => { - it('should disptach only failed events from the store and not dispatch queued events', async () => { + it('should dispatch only failed events from the store and not dispatch queued events', async () => { const eventDispatcher = getMockDispatcher(); const mockDispatch: MockInstance = eventDispatcher.dispatchEvent; mockDispatch.mockResolvedValue({}); @@ -1071,7 +1071,7 @@ describe('BatchEventProcessor', async () => { ])); }); - it('should disptach only failed events from the store and not dispatch events that are being dispatched', async () => { + it('should dispatch only failed events from the store and not dispatch events that are being dispatched', async () => { const eventDispatcher = getMockDispatcher(); const mockDispatch: MockInstance = eventDispatcher.dispatchEvent; const mockResult1 = resolvablePromise(); @@ -1129,7 +1129,7 @@ describe('BatchEventProcessor', async () => { ])); }); - it('should disptach events in correct batch size and separate events with differnt contexts in separate batch', async () => { + it('should dispatch events in correct batch size and separate events with differnt contexts in separate batch', async () => { const eventDispatcher = getMockDispatcher(); const mockDispatch: MockInstance = eventDispatcher.dispatchEvent; mockDispatch.mockResolvedValue({}); @@ -1277,7 +1277,7 @@ describe('BatchEventProcessor', async () => { expect(failedEventRepeater.stop).toHaveBeenCalledOnce(); }); - it('should disptach the events in queue using the closing dispatcher if available', async () => { + it('should dispatch the events in queue using the closing dispatcher if available', async () => { const eventDispatcher = getMockDispatcher(); const closingEventDispatcher = getMockDispatcher(); closingEventDispatcher.dispatchEvent.mockResolvedValue({}); @@ -1410,7 +1410,7 @@ describe('BatchEventProcessor', async () => { }); describe('flushImmediately', () => { - it('should disptach the events in queue using the closing dispatcher if available', async () => { + it('should dispatch the events in queue using the closing dispatcher if available', async () => { const eventDispatcher = getMockDispatcher(); const closingEventDispatcher = getMockDispatcher(); closingEventDispatcher.dispatchEvent.mockResolvedValue({}); @@ -1447,7 +1447,7 @@ describe('BatchEventProcessor', async () => { }); - it('should disptach the events in queue using eventDispatcher if closingEventDispatcher is not available', async () => { + it('should dispatch the events in queue using eventDispatcher if closingEventDispatcher is not available', async () => { const eventDispatcher = getMockDispatcher(); eventDispatcher.dispatchEvent.mockResolvedValue({}); @@ -1471,7 +1471,6 @@ describe('BatchEventProcessor', async () => { await processor.process(event); } - expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); processor.flushImmediately(); diff --git a/lib/event_processor/event_builder/log_event.ts b/lib/event_processor/event_builder/log_event.ts index d3ec940fa..4d4048950 100644 --- a/lib/event_processor/event_builder/log_event.ts +++ b/lib/event_processor/event_builder/log_event.ts @@ -222,7 +222,7 @@ function makeVisitor(data: ImpressionEvent | ConversionEvent): Visitor { export function buildLogEvent(events: UserEvent[]): LogEvent { const region = events[0]?.context.region || 'US'; - const url = logxEndpoint[region]; + const url = logxEndpoint[region] || logxEndpoint['US']; return { url, diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index 93f2ed23e..b8707a006 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -1244,21 +1244,21 @@ export default class Optimizely extends BaseService implements Client { } flushImmediately(): Promise { - const ret = []; + const flushPromises = []; if (!this.isRunning()) { return Promise.resolve(); } if (this.eventProcessor) { - ret.push(this.eventProcessor.flushImmediately()); + flushPromises.push(this.eventProcessor.flushImmediately()); } if(this.odpManager) { - ret.push(this.odpManager.flushImmediately()); + flushPromises.push(this.odpManager.flushImmediately()); } - return Promise.all(ret); + return Promise.all(flushPromises); } /** From 4fafcdc6005dd9bc0a2abec3390140eee35e9ae4 Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Tue, 7 Oct 2025 01:16:28 +0600 Subject: [PATCH 4/4] dep --- package-lock.json | 11 ++--------- package.json | 2 -- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4e6e462c9..4820c783c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,6 @@ "eslint-plugin-prettier": "^3.1.2", "happy-dom": "^16.6.0", "jiti": "^2.4.1", - "json-loader": "^0.5.4", "karma": "^6.4.0", "karma-browserstack-launcher": "^1.5.1", "karma-chai": "^0.1.0", @@ -57,7 +56,6 @@ "ts-loader": "^9.3.1", "ts-node": "^8.10.2", "tsconfig-paths": "^4.2.0", - "tslib": "^2.4.0", "typescript": "^4.7.4", "vitest": "^2.0.5", "webpack": "^5.74.0" @@ -9109,12 +9107,6 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, - "node_modules/json-loader": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", - "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==", - "dev": true - }, "node_modules/json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -13685,7 +13677,8 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true + "dev": true, + "peer": true }, "node_modules/tsutils": { "version": "3.21.0", diff --git a/package.json b/package.json index 9aa609e6c..675bb7d4f 100644 --- a/package.json +++ b/package.json @@ -117,7 +117,6 @@ "eslint-plugin-prettier": "^3.1.2", "happy-dom": "^16.6.0", "jiti": "^2.4.1", - "json-loader": "^0.5.4", "karma": "^6.4.0", "karma-browserstack-launcher": "^1.5.1", "karma-chai": "^0.1.0", @@ -139,7 +138,6 @@ "ts-loader": "^9.3.1", "ts-node": "^8.10.2", "tsconfig-paths": "^4.2.0", - "tslib": "^2.4.0", "typescript": "^4.7.4", "vitest": "^2.0.5", "webpack": "^5.74.0"