From 8c6780a6afb7b2ab94b7e5854e328db6fe4ba397 Mon Sep 17 00:00:00 2001 From: Maciej Pyrc Date: Sun, 29 Dec 2024 14:06:19 +0100 Subject: [PATCH] feat: changes --- jest.config.ts | 2 +- jest.polyfills.js | 41 +++--- package.json | 2 +- .../features/adapter/adapter.browser.spec.ts | 3 +- packages/core/__tests__/e2e/ada.client.jpg | Bin 94450 -> 0 bytes packages/core/__tests__/e2e/ada.jpg | 0 .../core/__tests__/e2e/client.streams.spec.ts | 77 ----------- .../core/__tests__/e2e/server.streams.spec.ts | 78 ----------- .../features/adapter/adapter.browser.spec.ts | 23 +--- .../features/adapter/adapter.server.spec.ts | 6 +- .../cache/cache.garbage-collector.spec.ts | 19 +-- .../dispatcher/dispatcher.utils.spec.ts | 6 +- .../managers/logger/logger.base.spec.ts | 6 +- packages/core/__tests__/jest.setup.ts | 5 + packages/core/__tests__/utils/cache.utils.ts | 2 +- packages/core/src/adapter/adapter.bindings.ts | 15 +- packages/core/src/cache/cache.ts | 75 ++++++---- packages/core/src/client/client.ts | 51 +++++-- packages/core/src/client/client.types.ts | 8 +- packages/core/src/dispatcher/dispatcher.ts | 102 +++++++------- .../core/src/dispatcher/dispatcher.types.ts | 9 +- .../core/src/dispatcher/dispatcher.utils.ts | 4 +- packages/core/src/managers/app/app.manager.ts | 11 ++ .../src/managers/logger/logger.manager.ts | 11 +- packages/core/src/plugin/plugin.ts | 82 ++++++++++- packages/core/src/plugin/plugin.types.ts | 130 +++++++++++++++++- .../src/sockets/devtools.socket.wrapper.tsx | 2 +- .../utils/use-request-events.utils.ts | 4 +- .../utils/use-tracked-state.utils.ts | 4 +- .../src/hooks/use-cache/use-cache.hooks.ts | 2 +- .../src/hooks/use-fetch/use-fetch.hooks.ts | 2 +- .../src/hooks/use-queue/use-queue.hooks.ts | 4 +- .../src/hooks/use-queue/use-queue.types.ts | 4 +- .../src/hooks/use-submit/use-submit.hooks.ts | 2 +- .../sockets/src/adapter/adapter.bindings.ts | 2 +- packages/sockets/src/socket/socket.ts | 4 +- packages/testing/src/http/index.ts | 9 -- yarn.lock | 10 +- 38 files changed, 450 insertions(+), 367 deletions(-) delete mode 100644 packages/core/__tests__/e2e/ada.client.jpg delete mode 100644 packages/core/__tests__/e2e/ada.jpg delete mode 100644 packages/core/__tests__/e2e/client.streams.spec.ts delete mode 100644 packages/core/__tests__/e2e/server.streams.spec.ts diff --git a/jest.config.ts b/jest.config.ts index 532466f8e..d88e5f5cd 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -8,7 +8,7 @@ export default { export const getJestConfig = (): Config.InitialOptions => ({ cache: false, verbose: true, - testEnvironment: "jest-fixed-jsdom", + testEnvironment: "jsdom", testTimeout: 1000000, preset: "ts-jest", testRegex: [".spec.ts"], diff --git a/jest.polyfills.js b/jest.polyfills.js index c3eabf583..03f516d98 100644 --- a/jest.polyfills.js +++ b/jest.polyfills.js @@ -8,25 +8,28 @@ * you don't want to deal with this. */ -const { TextDecoder, TextEncoder } = require("node:util"); -const { ReadableStream, TransformStream } = require("node:stream/web"); +if (typeof globalThis === "object") { + const { TextDecoder, TextEncoder } = require("node:util"); + const { ReadableStream, TransformStream } = require("node:stream/web"); -Object.defineProperties(globalThis, { - TextDecoder: { value: TextDecoder }, - TextEncoder: { value: TextEncoder }, - ReadableStream: { value: ReadableStream }, - TransformStream: { value: TransformStream }, -}); + Object.defineProperties(globalThis, { + TextDecoder: { value: TextDecoder }, + TextEncoder: { value: TextEncoder }, + ReadableStream: { value: ReadableStream }, + TransformStream: { value: TransformStream }, + }); -const { Blob, File } = require("node:buffer"); -const { fetch, Headers, FormData, Request, Response } = require("undici"); + const { Blob, File } = require("node:buffer"); + const { Headers, FormData, Request, Response } = require("undici"); + const { BroadcastChannel } = require("node:worker_threads"); -Object.defineProperties(globalThis, { - fetch: { value: fetch, writable: true }, - Blob: { value: Blob }, - File: { value: File }, - Headers: { value: Headers }, - FormData: { value: FormData }, - Request: { value: Request }, - Response: { value: Response }, -}); + Object.defineProperties(globalThis, { + Blob: { value: Blob }, + File: { value: File }, + Headers: { value: Headers }, + FormData: { value: FormData }, + Request: { value: Request, configurable: true }, + Response: { value: Response, configurable: true }, + BroadcastChannel: { value: BroadcastChannel }, + }); +} diff --git a/package.json b/package.json index 1f696246c..c091ab649 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "jest-watch-typeahead": "^2.2.2", "jest-websocket-mock": "2.5.0", "lint-staged": "^13.0.1", - "msw": "^2.6.8", + "msw": "^2.7.0", "next": "14.2.3", "notistack": "^2.0.5", "prettier": "^3.2.5", diff --git a/packages/adapter-graphql/__tests__/features/adapter/adapter.browser.spec.ts b/packages/adapter-graphql/__tests__/features/adapter/adapter.browser.spec.ts index 915eb2d25..ac7641ef6 100644 --- a/packages/adapter-graphql/__tests__/features/adapter/adapter.browser.spec.ts +++ b/packages/adapter-graphql/__tests__/features/adapter/adapter.browser.spec.ts @@ -146,8 +146,7 @@ describe("Graphql Adapter [ Browser ]", () => { const timeoutRequest = request.setOptions({ timeout: 50 }); mockRequest(timeoutRequest, { delay: 20 }); await timeoutRequest.send(); - // @ts-ignore TODO: check this - expect(instance?.timeout).toBe(50); + expect(instance!.timeout).toBe(50); window.XMLHttpRequest = xml; }); diff --git a/packages/core/__tests__/e2e/ada.client.jpg b/packages/core/__tests__/e2e/ada.client.jpg deleted file mode 100644 index c42edbc9860e6ec6eef124a73d89c75ad967096b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 94450 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 U@qmGu{e5o{`9VWmvpG)sFD#Y? { - let client = new Client({ url: "https://lh3.googleusercontent.com" }); - let request = client.createRequest<{ response: any }>()({ - endpoint: - "/iXmJ9aWblkGDpg-_jpcqaY10KmA8HthjZ7F15U7mJ9PQK6vZEStMlathz1FfQQWV5XeeF-A1tZ0UpDjx3q6vEm2BWZn5k1btVSuBk9ad=s660", - }); - beforeEach(() => { - client = new Client({ url: "https://lh3.googleusercontent.com" }); - request = client.createRequest<{ response: any }>()({ - endpoint: - "/iXmJ9aWblkGDpg-_jpcqaY10KmA8HthjZ7F15U7mJ9PQK6vZEStMlathz1FfQQWV5XeeF-A1tZ0UpDjx3q6vEm2BWZn5k1btVSuBk9ad=s660", - }); - }); - it("should fetch data client", async () => { - const res = await request.send(); - fs.writeFileSync(path.join(__dirname, "ada.client.jpg"), res.data); - // const f = fs.createWriteStream(path.join(__dirname, "ada.jpg")); - // const req = https.get( - // "https://lh3.googleusercontent.com/iXmJ9aWblkGDpg-_jpcqaY10KmA8HthjZ7F15U7mJ9PQK6vZEStMlathz1FfQQWV5XeeF-A1tZ0UpDjx3q6vEm2BWZn5k1btVSuBk9ad=s660", - // (response) => { - // response.pipe(f); - // f.on("finish", () => { - // f.close(); - // }); - // }, - // ); - // let whole = ""; - // const req = https.request( - // "https://lh3.googleusercontent.com/iXmJ9aWblkGDpg-_jpcqaY10KmA8HthjZ7F15U7mJ9PQK6vZEStMlathz1FfQQWV5XeeF-A1tZ0UpDjx3q6vEm2BWZn5k1btVSuBk9ad=s660", - // { method: "GET" }, - // (response) => { - // response.on("data", (chunk) => { - // console.log(">>>>>", typeof chunk); - // whole += chunk; - // f.write(chunk); - // }); - // f.on("finish", () => { - // console.log("WHAT THE"); - // f.write(whole); - // f.close(); - // }); - // }, - // ); - // req.end(); - // console.log("DID IT HAPPEN?", whole, "KURWA CO"); - }); -}); diff --git a/packages/core/__tests__/e2e/server.streams.spec.ts b/packages/core/__tests__/e2e/server.streams.spec.ts deleted file mode 100644 index 74b08946d..000000000 --- a/packages/core/__tests__/e2e/server.streams.spec.ts +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @jest-environment node - */ -// TODO - zapisywanie do pliku z całego data - obsługa różnych responseTypes na backendzie -// TODO - typy serwerowego adaptera muszą być inne - ?? Nie wiadomo -// TODO - streamy - przekazywać stream -// TODO - poprawić dokumentację - -// TODO Obsługa Http.Agent + Proxy -// TODO obsługa RedisCache -// TODO cacheKey dynamiczna funkcja - zastanowić się czy ma to sens - -// TODO Router - Dokumentacja -// TODO Investigate - cacheFalse -// TODO Mini paczucha - HF vcr - -/* -* // `responseType` indicates the type of data that the server will respond with - // options are: 'arraybuffer', 'document', 'json', 'text', 'stream' - // browser only: 'blob' - responseType: 'json', // default -* -* */ - -import * as fs from "fs"; -import * as path from "path"; - -import { Client } from "client"; - -describe("Fetch Adapter [ Server ]", () => { - let client = new Client({ url: "https://lh3.googleusercontent.com" }); - let request = client.createRequest<{ response: any }>()({ - endpoint: - "/iXmJ9aWblkGDpg-_jpcqaY10KmA8HthjZ7F15U7mJ9PQK6vZEStMlathz1FfQQWV5XeeF-A1tZ0UpDjx3q6vEm2BWZn5k1btVSuBk9ad=s660", - }); - beforeEach(() => { - client = new Client({ url: "https://lh3.googleusercontent.com" }); - request = client.createRequest<{ response: any }>()({ - options: { responseType: "arraybuffer" }, - endpoint: - "/iXmJ9aWblkGDpg-_jpcqaY10KmA8HthjZ7F15U7mJ9PQK6vZEStMlathz1FfQQWV5XeeF-A1tZ0UpDjx3q6vEm2BWZn5k1btVSuBk9ad=s660", - }); - }); - it("should fetch data", async () => { - const res = await request.setOptions({ responseType: "stream" }).send(); - // fs.writeFileSync(path.join(__dirname, "ada.jpg"), res.data); - const f = fs.createWriteStream(path.join(__dirname, "ada.jpg")); - res.data.pipe(f); - // const req = https.get( - // "https://lh3.googleusercontent.com/iXmJ9aWblkGDpg-_jpcqaY10KmA8HthjZ7F15U7mJ9PQK6vZEStMlathz1FfQQWV5XeeF-A1tZ0UpDjx3q6vEm2BWZn5k1btVSuBk9ad=s660", - // (response) => { - // response.pipe(f); - // f.on("finish", () => { - // f.close(); - // }); - // }, - // ); - // let whole = ""; - // const req = https.request( - // "https://lh3.googleusercontent.com/iXmJ9aWblkGDpg-_jpcqaY10KmA8HthjZ7F15U7mJ9PQK6vZEStMlathz1FfQQWV5XeeF-A1tZ0UpDjx3q6vEm2BWZn5k1btVSuBk9ad=s660", - // { method: "GET" }, - // (response) => { - // response.on("data", (chunk) => { - // console.log(">>>>>", typeof chunk); - // whole += chunk; - // f.write(chunk); - // }); - // f.on("finish", () => { - // console.log("WHAT THE"); - // f.write(whole); - // f.close(); - // }); - // }, - // ); - // req.end(); - // console.log("DID IT HAPPEN?", whole, "KURWA CO"); - }); -}); diff --git a/packages/core/__tests__/features/adapter/adapter.browser.spec.ts b/packages/core/__tests__/features/adapter/adapter.browser.spec.ts index 7f2c0d6d0..75fe6fa89 100644 --- a/packages/core/__tests__/features/adapter/adapter.browser.spec.ts +++ b/packages/core/__tests__/features/adapter/adapter.browser.spec.ts @@ -86,28 +86,7 @@ describe("Fetch Adapter [ Browser ]", () => { expect(response).toStrictEqual(data); expect(status).toBe(200); expect(error).toBe(null); - expect(extra).toStrictEqual({ headers: { "content-type": "application/json", "content-length": "2" } }); - window.XMLHttpRequest = xml; - }); - - it("should allow to set options", async () => { - const xml = window.XMLHttpRequest; - let instance: null | XMLHttpRequest = null; - class ExtendedXml extends XMLHttpRequest { - constructor() { - super(); - // eslint-disable-next-line @typescript-eslint/no-this-alias - instance = this; - } - } - - window.XMLHttpRequest = ExtendedXml; - - const timeoutRequest = request.setOptions({ timeout: 50 }); - mockRequest(timeoutRequest, { delay: 20 }); - await adapter(timeoutRequest, requestId); - expect(instance!.timeout).toBe(50); - + expect(extra).toEqual({ headers: { "content-type": "application/json", "content-length": "2" } }); window.XMLHttpRequest = xml; }); }); diff --git a/packages/core/__tests__/features/adapter/adapter.server.spec.ts b/packages/core/__tests__/features/adapter/adapter.server.spec.ts index 93dd7f9f9..4453133a2 100644 --- a/packages/core/__tests__/features/adapter/adapter.server.spec.ts +++ b/packages/core/__tests__/features/adapter/adapter.server.spec.ts @@ -60,7 +60,7 @@ describe("Fetch Adapter [ Server ]", () => { expect(response).toStrictEqual(data); expect(status).toBe(200); expect(error).toBe(null); - expect(extra).toStrictEqual({ headers: { "content-type": "application/json", "content-length": "15" } }); + expect(extra).toEqual({ headers: { "content-type": "application/json", "content-length": "15" } }); }); it("should make a request and return error data with status", async () => { @@ -71,7 +71,7 @@ describe("Fetch Adapter [ Server ]", () => { expect(response).toBe(null); expect(status).toBe(400); expect(error).toStrictEqual(data); - expect(extra).toStrictEqual({ headers: { "content-type": "application/json", "content-length": "19" } }); + expect(extra).toEqual({ headers: { "content-type": "application/json", "content-length": "19" } }); }); it("should allow to cancel request and return error", async () => { @@ -115,7 +115,7 @@ describe("Fetch Adapter [ Server ]", () => { expect(response).toEqual(mock); expect(error).toBeNull(); expect(status).toEqual(200); - expect(extra).toStrictEqual({ headers: { "content-type": "application/json", "content-length": "2" } }); + expect(extra).toEqual({ headers: { "content-type": "application/json", "content-length": "2" } }); }); it("should allow to calculate payload size", async () => { diff --git a/packages/core/__tests__/features/cache/cache.garbage-collector.spec.ts b/packages/core/__tests__/features/cache/cache.garbage-collector.spec.ts index 4d3a02552..472aa63f2 100644 --- a/packages/core/__tests__/features/cache/cache.garbage-collector.spec.ts +++ b/packages/core/__tests__/features/cache/cache.garbage-collector.spec.ts @@ -8,9 +8,9 @@ jest.useFakeTimers().setSystemTime(new Date()); describe("Cache [ Garbage Collector ]", () => { const cacheKey = "test"; - const staleTime = 30; + const staleTime = 30000; const version = "test"; - const cacheTime = 10; + const cacheTime = 10000; const cacheData: CacheValueType = { data: null, error: null, @@ -30,22 +30,16 @@ describe("Cache [ Garbage Collector ]", () => { cacheKey, }; - let lazyStorage = new Map(); - let client = new Client({ url: "shared-base-url" }); let request = client.createRequest()({ endpoint: "shared-endpoint", cacheKey, staleTime, cacheTime }); let cache = createCache(client, { - lazyStorage: createLazyCacheAdapter(lazyStorage), version, }); beforeEach(async () => { - lazyStorage.clear(); - lazyStorage = new Map(); client = new Client({ url: "shared-base-url" }); request = client.createRequest()({ endpoint: "shared-endpoint", cacheKey, staleTime, cacheTime }); cache = createCache(client, { - lazyStorage: createLazyCacheAdapter(lazyStorage), version, }); jest.resetAllMocks(); @@ -54,7 +48,7 @@ describe("Cache [ Garbage Collector ]", () => { describe("when garbage collector is triggered", () => { it("should garbage collect data from sync storage", async () => { - cache.set(request, cacheData); + cache.set(request.setCacheTime(10), cacheData); await waitFor(() => { expect(cache.get(cacheKey)).not.toBeDefined(); }); @@ -68,17 +62,16 @@ describe("Cache [ Garbage Collector ]", () => { }); }); it("should schedule garbage collection on mount", async () => { - const storage = new Map(); - storage.set("cacheKey", cacheData); + const storage = new Map().set("cacheKey", cacheData); const cacheInstance = createCache(new Client({ url: "shared-base-url" }), { storage, version, }); - expect(Array.from(cacheInstance.garbageCollectors.keys())).toHaveLength(1); }); it("should schedule lazy storage garbage collection on mount", async () => { + const lazyStorage = new Map(); lazyStorage.set(cacheKey, cacheData); const cacheInstance = createCache(client, { lazyStorage: createLazyCacheAdapter(lazyStorage), @@ -95,6 +88,7 @@ describe("Cache [ Garbage Collector ]", () => { expect(spy).toHaveBeenCalledTimes(1); }); it("should remove resource with not matching lazy version", async () => { + const lazyStorage = new Map(); const data = { ...cacheData, cacheTime: Time.MIN }; lazyStorage.set(request.cacheKey, data); createCache(client, { @@ -109,6 +103,7 @@ describe("Cache [ Garbage Collector ]", () => { }); }); it("should remove resource with not matching sync version", async () => { + const lazyStorage = new Map(); const data = { ...cacheData, cacheTime: Time.MIN }; lazyStorage.set(request.cacheKey, data); createCache(client, { diff --git a/packages/core/__tests__/features/dispatcher/dispatcher.utils.spec.ts b/packages/core/__tests__/features/dispatcher/dispatcher.utils.spec.ts index 33e75fc1d..24f685707 100644 --- a/packages/core/__tests__/features/dispatcher/dispatcher.utils.spec.ts +++ b/packages/core/__tests__/features/dispatcher/dispatcher.utils.spec.ts @@ -2,7 +2,7 @@ import { createHttpMockingServer } from "@hyper-fetch/testing"; import { canRetryRequest, - QueueElementType, + QueueItemType, DispatcherRequestType, getDispatcherChangeByKey, getDispatcherDrainedByKey, @@ -111,13 +111,13 @@ describe("Dispatcher [ Utils ]", () => { describe("When using getRequestType util", () => { it("should return deduplicated type", async () => { const request = client.createRequest()({ endpoint: "shared-base-endpoint", deduplicate: true }); - const duplicated: QueueElementType = dispatcher.createStorageElement(request); + const duplicated: QueueItemType = dispatcher.createStorageElement(request); const type = getRequestType(request, duplicated); expect(type).toBe(DispatcherRequestType.DEDUPLICATED); }); it("should return cancelable type", async () => { const request = client.createRequest()({ endpoint: "shared-base-endpoint", cancelable: true }); - const duplicated: QueueElementType = dispatcher.createStorageElement(request); + const duplicated: QueueItemType = dispatcher.createStorageElement(request); const type = getRequestType(request, duplicated); expect(type).toBe(DispatcherRequestType.PREVIOUS_CANCELED); }); diff --git a/packages/core/__tests__/features/managers/logger/logger.base.spec.ts b/packages/core/__tests__/features/managers/logger/logger.base.spec.ts index 25a0d192b..9b4cbf871 100644 --- a/packages/core/__tests__/features/managers/logger/logger.base.spec.ts +++ b/packages/core/__tests__/features/managers/logger/logger.base.spec.ts @@ -3,16 +3,16 @@ import { LoggerManager } from "managers"; describe("Logger [ Base ]", () => { let client = new Client({ url: "shared-base-url" }); - let loggerManager = new LoggerManager(client); + let loggerManager = new LoggerManager(); beforeEach(() => { client = new Client({ url: "shared-base-url" }); - loggerManager = new LoggerManager(client); + loggerManager = new LoggerManager(); }); describe("When using config methods", () => { it("should allow to initialize logger with module name", async () => { - const logger = loggerManager.init("Test Module"); + const logger = loggerManager.initialize(client, "Test Module"); expect(logger).toBeDefined(); }); }); diff --git a/packages/core/__tests__/jest.setup.ts b/packages/core/__tests__/jest.setup.ts index a9fd23adf..33cff8926 100644 --- a/packages/core/__tests__/jest.setup.ts +++ b/packages/core/__tests__/jest.setup.ts @@ -3,9 +3,14 @@ import "jest-extended"; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import { AbortController } from "abortcontroller-polyfill/dist/cjs-ponyfill"; +import { BroadcastChannel } from "node:worker_threads"; if (!global.AbortController) { global.AbortController = AbortController as any; } +Object.defineProperties(globalThis, { + BroadcastChannel: { value: BroadcastChannel }, +}); + jest.retryTimes(2); diff --git a/packages/core/__tests__/utils/cache.utils.ts b/packages/core/__tests__/utils/cache.utils.ts index fda62036b..785748b3b 100644 --- a/packages/core/__tests__/utils/cache.utils.ts +++ b/packages/core/__tests__/utils/cache.utils.ts @@ -2,7 +2,7 @@ import { Cache, CacheAsyncStorageType, CacheOptionsType } from "cache"; import { ClientInstance } from "client"; export const createCache = (client: ClientInstance, options?: CacheOptionsType) => { - return new Cache(client, options); + return new Cache(options).initialize(client); }; export const createLazyCacheAdapter = (storage: Map): CacheAsyncStorageType => { diff --git a/packages/core/src/adapter/adapter.bindings.ts b/packages/core/src/adapter/adapter.bindings.ts index edae86e45..890fd45e0 100644 --- a/packages/core/src/adapter/adapter.bindings.ts +++ b/packages/core/src/adapter/adapter.bindings.ts @@ -30,7 +30,7 @@ export const getAdapterBindings = async { const { url, requestManager, loggerManager, headerMapper, payloadMapper } = req.client; - const logger = loggerManager.init("Adapter"); + const logger = loggerManager.initialize(req.client, "Adapter"); let processingError: Error | null = null; @@ -66,7 +66,6 @@ export const getAdapterBindings = async { logger.debug(`Request ready to send`, { requestId, request }); - plugins.forEach((plugin) => plugin.triggerMethod("onRequestTrigger", { request })); + client.triggerPlugins("onRequestTrigger", { request }); }; // Request @@ -143,7 +142,7 @@ export const getAdapterBindings = async plugin.triggerMethod("onRequestStart", { request })); + client.triggerPlugins("onRequestStart", { request }); if (progress?.total) { requestTotal = getTotal(requestTotal, progress); @@ -252,8 +251,8 @@ export const getAdapterBindings = async plugin.triggerMethod("onRequestSuccess", { response, request })); - plugins.forEach((plugin) => plugin.triggerMethod("onRequestFinished", { response, request })); + client.triggerPlugins("onRequestSuccess", { response, request }); + client.triggerPlugins("onRequestFinished", { response, request }); resolve(response); @@ -287,8 +286,8 @@ export const getAdapterBindings = async plugin.triggerMethod("onRequestError", { response, request })); - plugins.forEach((plugin) => plugin.triggerMethod("onRequestFinished", { response, request })); + client.triggerPlugins("onRequestError", { response, request }); + client.triggerPlugins("onRequestFinished", { response, request }); resolve(response); diff --git a/packages/core/src/cache/cache.ts b/packages/core/src/cache/cache.ts index c69bc07cd..468f37e76 100644 --- a/packages/core/src/cache/cache.ts +++ b/packages/core/src/cache/cache.ts @@ -1,6 +1,6 @@ import { AdapterInstance, ResponseType } from "adapter"; -import { ClientInstance } from "client"; import { ResponseDetailsType, LoggerType } from "managers"; +import { ClientInstance } from "client"; import { CacheOptionsType, CacheAsyncStorageType, @@ -31,37 +31,42 @@ export class Cache { public version: string; public garbageCollectors = new Map>(); private logger: LoggerType; + public client: C; - constructor( - public client: C, - public options?: CacheOptionsType, - ) { - const { storage = new Map() } = this.options || {}; + constructor(public options?: CacheOptionsType) { + const { storage = new Map(), lazyStorage, version = "0.0.1" } = options ?? {}; - this.storage = storage; this.emitter?.setMaxListeners(1000); this.events = getCacheEvents(this.emitter); - this.options?.onInitialization?.(this); - this.version = this.options?.version || "0.0.1"; - this.lazyStorage = this.options?.lazyStorage; - this.logger = this.client.loggerManager.init("Cache"); + this.storage = storage; + this.version = version; + this.lazyStorage = lazyStorage; + } - const scheduleGarbageCollector = (keys: string[]) => { - keys.forEach(this.scheduleGarbageCollector); + initialize = (client: C) => { + this.client = client; + this.logger = client.loggerManager.initialize(client, "Cache"); - // Going back from offline should re-trigger garbage collection - this.client.appManager.events.onOnline(() => { - keys.forEach(this.scheduleGarbageCollector); - }); + // Going back from offline should re-trigger garbage collection + client.appManager.events.onOnline(() => { + [...this.storage.keys()].forEach(this.scheduleGarbageCollector); + }); + + const scheduleGarbageCollection = (keys: string[]) => { + keys.forEach(this.scheduleGarbageCollector); }; - scheduleGarbageCollector([...this.storage.keys()]); + scheduleGarbageCollection([...this.storage.keys()]); // Schedule garbage collection for lazy storage // To make sure we do not store some data that is no longer needed - this.getLazyKeys().then(scheduleGarbageCollector); - } + this.getLazyKeys().then(scheduleGarbageCollection); + + this.client.triggerPlugins("onCacheMount", { cache: this as unknown as Cache }); + + return this; + }; /** * Set the cache data to the storage @@ -78,7 +83,7 @@ export class Cache { ): void => { this.logger.debug("Processing cache response", { request, response }); const { cacheKey, cache, staleTime, cacheTime } = request; - const cachedData = this.storage.get< + const previousCacheData = this.storage.get< ExtractResponseType, ExtractErrorType, ExtractAdapterType @@ -86,8 +91,8 @@ export class Cache { // Once refresh error occurs we don't want to override already valid data in our cache with the thrown error // We need to check it against cache and return last valid data we have - const processedResponse = typeof response === "function" ? response(cachedData || null) : response; - const data = getCacheData(cachedData, processedResponse); + const processedResponse = typeof response === "function" ? response(previousCacheData || null) : response; + const data = getCacheData(previousCacheData, processedResponse); const newCacheData: CacheValueType> = { ...data, @@ -103,6 +108,13 @@ export class Cache { this.storage.set>(cacheKey, newCacheData); this.lazyStorage?.set>(cacheKey, newCacheData); this.options?.onChange?.(cacheKey, newCacheData); + this.client.triggerPlugins("onCacheItemChange", { + cache: this as unknown as Cache, + cacheKey, + prevData: previousCacheData as CacheValueType, + newData: newCacheData as CacheValueType, + }); + this.scheduleGarbageCollector(cacheKey); } else { // If request should not use cache - just emit response data @@ -176,8 +188,13 @@ export class Cache { delete = (cacheKey: string): void => { this.logger.debug("Deleting cache element", { cacheKey }); this.storage.delete(cacheKey); - this.options?.onDelete?.(cacheKey); this.lazyStorage?.delete(cacheKey); + + this.client.triggerPlugins("onCacheItemDelete", { + cache: this as unknown as Cache, + cacheKey, + }); + this.events.emitDelete(cacheKey); }; @@ -196,6 +213,12 @@ export class Cache { if (value) { this.storage.set(cacheKey, { ...value, staleTime: 0 }); } + + this.client.triggerPlugins("onCacheItemInvalidate", { + cache: this as unknown as Cache, + cacheKey, + }); + this.events.emitInvalidation(cacheKey); }; @@ -297,13 +320,13 @@ export class Cache { this.garbageCollectors.set( cacheKey, setTimeout(() => { - if (this.client.appManager.isOnline) { + if (this.client?.appManager.isOnline) { this.logger.info("Garbage collecting cache element", { cacheKey }); this.delete(cacheKey); } }, timeLeft), ); - } else if (this.client.appManager.isOnline) { + } else if (this.client?.appManager.isOnline) { this.logger.info("Garbage collecting cache element", { cacheKey }); this.delete(cacheKey); } diff --git a/packages/core/src/client/client.ts b/packages/core/src/client/client.ts index b48f2ceb0..69c57dddf 100644 --- a/packages/core/src/client/client.ts +++ b/packages/core/src/client/client.ts @@ -30,7 +30,7 @@ import { } from "client"; import { Cache } from "cache"; import { Dispatcher } from "dispatcher"; -import { PluginInstance } from "plugin"; +import { PluginInstance, PluginMethodParameters, PluginMethods } from "plugin"; import { getRequestKey, getSimpleKey, Request, RequestInstance, RequestOptionsType } from "request"; import { AppManager, LoggerManager, RequestManager, SeverityType } from "managers"; import { interceptRequest, interceptResponse } from "./client.utils"; @@ -62,7 +62,7 @@ export class Client< // Managers requestManager: RequestManager = new RequestManager(); appManager: AppManager; - loggerManager: LoggerManager = new LoggerManager(this); + loggerManager: LoggerManager = new LoggerManager(); // Config adapter: Adapter; @@ -131,7 +131,7 @@ export class Client< endpointMapper = ((endpoint) => endpoint) as EndpointMapper; // Logger - logger = this.loggerManager.init("Client"); + logger = this.loggerManager.initialize(this, "Client"); constructor(public options: ClientOptionsType>) { const { url, adapter, appManager, cache, fetchDispatcher, submitDispatcher } = this.options; @@ -139,10 +139,15 @@ export class Client< this.adapter = (adapter || defaultAdapter) as Adapter; // IMPORTANT: Do not change initialization order as it's crucial for dependencies injection - this.appManager = appManager?.(this) || new AppManager(); - this.cache = (cache?.(this) || new Cache(this)) as Cache>; - this.fetchDispatcher = fetchDispatcher?.(this) || new Dispatcher(this); - this.submitDispatcher = submitDispatcher?.(this) || new Dispatcher(this); + this.appManager = appManager?.() || new AppManager(); + this.cache = (cache?.() || new Cache()) as Cache>; + this.fetchDispatcher = fetchDispatcher?.() || new Dispatcher(); + this.submitDispatcher = submitDispatcher?.() || new Dispatcher(); + + this.appManager.initialize(this); + this.cache.initialize(this); + this.fetchDispatcher.initialize(this); + this.submitDispatcher.initialize(this); } /** @@ -398,7 +403,8 @@ export class Client< addPlugin = (plugin: PluginInstance) => { this.plugins.push(plugin); - plugin.triggerMethod("onMount", { client: this }); + plugin.initialize(this); + plugin.trigger("onMount", { client: this }); return this; }; @@ -411,12 +417,24 @@ export class Client< this.plugins = this.plugins.filter((p) => p !== plugin); if (this.plugins.length !== pluginCount) { - plugin.triggerMethod("onUnmount", { client: this }); + plugin.trigger("onUnmount", { client: this }); } return this; }; + triggerPlugins = >(key: Key, data: PluginMethodParameters) => { + if (!this.plugins.length) { + return this; + } + + this.plugins.forEach((plugin) => { + plugin.trigger(key, data); + }); + + return this; + }; + /** * Key setters */ @@ -519,10 +537,15 @@ export class Client< this.submitDispatcher.emitter.removeAllListeners(); this.cache.emitter.removeAllListeners(); - this.appManager = appManager?.(this) || new AppManager(); - this.cache = (cache?.(this) || new Cache(this)) as Cache>; - this.fetchDispatcher = fetchDispatcher?.(this) || new Dispatcher(this); - this.submitDispatcher = submitDispatcher?.(this) || new Dispatcher(this); + this.appManager = appManager?.() || new AppManager(); + this.cache = (cache?.() || new Cache()) as Cache>; + this.fetchDispatcher = fetchDispatcher?.() || new Dispatcher(); + this.submitDispatcher = submitDispatcher?.() || new Dispatcher(); + + this.appManager.initialize(this); + this.cache.initialize(this); + this.fetchDispatcher.initialize(this); + this.submitDispatcher.initialize(this); }; /** @@ -634,7 +657,7 @@ export class Client< Client >(this as unknown as Client, mappedParams); - this.plugins.forEach((plugin) => plugin.triggerMethod("onRequestCreate", { request })); + this.plugins.forEach((plugin) => plugin.trigger("onRequestCreate", { request })); return request; }; diff --git a/packages/core/src/client/client.types.ts b/packages/core/src/client/client.types.ts index f2b2dd325..92866dcb7 100644 --- a/packages/core/src/client/client.types.ts +++ b/packages/core/src/client/client.types.ts @@ -34,19 +34,19 @@ export type ClientOptionsType = { /** * Custom cache initialization prop */ - cache?: (client: C) => C["cache"]; + cache?: () => C["cache"]; /** * Custom app manager initialization prop */ - appManager?: (client: C) => C["appManager"]; + appManager?: () => C["appManager"]; /** * Custom fetch dispatcher initialization prop */ - fetchDispatcher?: (client: C) => C["submitDispatcher"]; + fetchDispatcher?: () => C["submitDispatcher"]; /** * Custom submit dispatcher initialization prop */ - submitDispatcher?: (client: C) => C["fetchDispatcher"]; + submitDispatcher?: () => C["fetchDispatcher"]; }; // Interceptors diff --git a/packages/core/src/dispatcher/dispatcher.ts b/packages/core/src/dispatcher/dispatcher.ts index bde84d245..2d97433c9 100644 --- a/packages/core/src/dispatcher/dispatcher.ts +++ b/packages/core/src/dispatcher/dispatcher.ts @@ -6,7 +6,7 @@ import { DispatcherRequestType, DispatcherOptionsType, DispatcherStorageType, - QueueElementType, + QueueItemType, RunningRequestValueType, } from "dispatcher"; import { ClientInstance } from "client"; @@ -29,25 +29,27 @@ export class Dispatcher { private runningRequests = new Map(); private logger: LoggerType; + private client: ClientInstance; - constructor( - public client: ClientInstance, - public options?: DispatcherOptionsType, - ) { + constructor(public options?: DispatcherOptionsType) { this.emitter?.setMaxListeners(1000); - this.logger = client.loggerManager.init("Dispatcher"); if (this.options?.storage) { this.storage = this.options.storage; } + } + + initialize = (client: ClientInstance) => { + this.client = client; + this.logger = client.loggerManager.initialize(client, "Dispatcher"); // Going back from offline should re-trigger all requests this.client.appManager.events.onOnline(() => { this.flush(); }); - this.options?.onInitialization?.(this); - } + this.client.triggerPlugins("onDispatcherMount", { dispatcher: this }); + }; // ********************************************************************* // ********************************************************************* @@ -66,11 +68,12 @@ export class Dispatcher { queue.stopped = false; this.setQueue(queryKey, queue); this.flushQueue(queryKey); + this.client.triggerPlugins("onDispatcherQueueRunning", { dispatcher: this, queue, status: "running" }); this.events.setQueueStatusChanged(queue); }; /** - * Pause request queue, but not cancel already started requests + * Pause request queue, but do not cancel already started requests */ pause = (queryKey: string) => { // Change state to stopped @@ -78,6 +81,7 @@ export class Dispatcher { queue.stopped = true; this.setQueue(queryKey, queue); + this.client.triggerPlugins("onDispatcherQueueRunning", { dispatcher: this, queue, status: "paused" }); this.events.setQueueStatusChanged(queue); }; @@ -93,6 +97,7 @@ export class Dispatcher { // Cancel running requests this.cancelRunningRequests(queryKey); + this.client.triggerPlugins("onDispatcherQueueRunning", { dispatcher: this, queue, status: "stopped" }); this.events.setQueueStatusChanged(queue); }; @@ -136,12 +141,15 @@ export class Dispatcher { /** * Add new element to storage */ - addQueueElement = ( + addQueueItem = ( queryKey: string, - element: QueueElementType, + element: QueueItemType, ) => { const queue = this.getQueue(queryKey); queue.requests.push(element); + + this.client.triggerPlugins("onDispatcherItemAdded", { dispatcher: this, queue, queueItem: element }); + this.setQueue(queryKey, queue); }; @@ -152,7 +160,7 @@ export class Dispatcher { this.storage.set(queryKey, queue); // Emit Queue Changes - this.options?.onUpdateStorage?.(queue); + this.client.triggerPlugins("onDispatcherQueueCreated", { dispatcher: this, queue }); this.events.setQueueChanged(queue); return queue; @@ -167,7 +175,7 @@ export class Dispatcher { this.storage.set(queryKey, newQueue); // Emit Queue Changes - this.options?.onDeleteFromStorage?.(newQueue); + this.client.triggerPlugins("onDispatcherQueueCleared", { dispatcher: this, queue }); this.events.setQueueChanged(newQueue); return newQueue; @@ -179,13 +187,13 @@ export class Dispatcher { flushQueue = async (queryKey: string) => { const queue = this.getQueue(queryKey); const runningRequests = this.getRunningRequests(queryKey); - const queueElement = queue.requests.find((request) => !request.stopped); + const queueItem = queue.requests.find((request) => !request.stopped); const isStopped = queue && queue.stopped; const isOffline = !this.client.appManager.isOnline; - const isConcurrent = !queueElement?.request.queued; + const isConcurrent = !queueItem?.request.queued; const isInactive = !runningRequests.length; - const isEmpty = !queueElement; + const isEmpty = !queueItem; // When there are no requests to flush, when its stopped, there is running request // or there is no request to trigger - we don't want to perform actions @@ -198,7 +206,7 @@ export class Dispatcher { } }); } else if (isInactive) { - await this.performRequest(queueElement); + await this.performRequest(queueItem); this.flushQueue(queryKey); } }; @@ -211,9 +219,9 @@ export class Dispatcher { // eslint-disable-next-line no-restricted-syntax for (const key of keys) { - const storageElement = this.getQueue(key); + const storageItem = this.getQueue(key); - if (storageElement) { + if (storageItem) { this.flushQueue(key); } } @@ -228,7 +236,7 @@ export class Dispatcher { this.runningRequests.clear(); this.storage.clear(); - this.options?.onClearStorage?.(this); + this.client.triggerPlugins("onDispatcherCleared", { dispatcher: this }); }; // ********************************************************************* @@ -381,9 +389,9 @@ export class Dispatcher { * Create storage element from request */ // eslint-disable-next-line class-methods-use-this - createStorageElement = (request: Request) => { + createStorageItem = (request: Request) => { const requestId = getUniqueRequestId(request.queryKey); - const storageElement: QueueElementType = { + const storageItem: QueueItemType = { requestId, timestamp: +new Date(), request, @@ -391,7 +399,7 @@ export class Dispatcher { stopped: false, resolved: false, }; - return storageElement; + return storageItem; }; // ********************************************************************* @@ -408,8 +416,8 @@ export class Dispatcher { // Create dump of the request to allow storing it in localStorage, AsyncStorage or any other // This way we don't save the Class but the instruction of the request to be done - const storageElement = this.createStorageElement(request); - const { requestId } = storageElement; + const storageItem = this.createStorageItem(request); + const { requestId } = storageItem; const queue = this.getQueue(queryKey); const [latestRequest] = queue.requests.slice(-1); @@ -420,7 +428,7 @@ export class Dispatcher { switch (requestType) { case DispatcherRequestType.ONE_BY_ONE: { // Requests will go one by one - this.addQueueElement(queryKey, storageElement); + this.addQueueItem(queryKey, storageItem); this.flushQueue(queryKey); return requestId; } @@ -428,7 +436,7 @@ export class Dispatcher { // Cancel all previous on-going requests this.cancelRunningRequests(queryKey); this.clearQueue(queryKey); - this.addQueueElement(queryKey, storageElement); + this.addQueueItem(queryKey, storageItem); this.flushQueue(queryKey); return requestId; } @@ -437,7 +445,7 @@ export class Dispatcher { return queue.requests[0].requestId; } default: { - this.addQueueElement(queryKey, storageElement); + this.addQueueItem(queryKey, storageItem); this.flushQueue(queryKey); return requestId; } @@ -450,9 +458,9 @@ export class Dispatcher { delete = (queryKey: string, requestId: string, abortKey: string) => { this.logger.debug("Deleting request", { queryKey, requestId, abortKey }); const queue = this.getQueue(queryKey); - const queueElement = queue.requests.find((req) => req.requestId === requestId); + const queueItem = queue.requests.find((req) => req.requestId === requestId); - if (!queueElement) return; + if (!queueItem) return; queue.requests = queue.requests.filter((req) => req.requestId !== requestId); this.storage.set(queryKey, queue); @@ -464,15 +472,17 @@ export class Dispatcher { } // Emit Queue Changes - this.options?.onDeleteFromStorage?.(queue); + this.client.triggerPlugins("onDispatcherItemDeleted", { queue, dispatcher: this, queueItem }); + this.events.setQueueChanged(queue); this.client.requestManager.events.emitRemove({ requestId, - request: queueElement.request, - resolved: queueElement.resolved, + request: queueItem.request, + resolved: queueItem.resolved, }); if (!queue.requests.length) { + this.client.triggerPlugins("onDispatcherQueueDrained", { queue, dispatcher: this }); this.events.setDrained(queue); } @@ -483,19 +493,19 @@ export class Dispatcher { * Request can run for some time, once it's done, we have to check if it's successful or if it was aborted * It can be different once the previous call was set as cancelled and removed from queue before this request got resolved */ - performRequest = async (storageElement: QueueElementType) => { - const { request, requestId } = storageElement; + performRequest = async (storageItem: QueueItemType) => { + const { request, requestId } = storageItem; this.logger.info("Performing request", { request, requestId }); const { retry, retryTime, queryKey, abortKey, offline } = request; const { adapter, requestManager, cache, appManager } = this.client; - const canRetry = canRetryRequest(storageElement.retries, retry); + const canRetry = canRetryRequest(storageItem.retries, retry); // When offline not perform any request const isOffline = !appManager.isOnline && offline; // When request with this id was triggered again const isAlreadyRunning = this.hasRunningRequest(queryKey, requestId); - const isStopped = storageElement.stopped; + const isStopped = storageItem.stopped; if (isOffline || isAlreadyRunning || isStopped) { return this.logger.warning("Unable to perform request", { isOffline, isAlreadyRunning, isStopped }); @@ -509,7 +519,7 @@ export class Dispatcher { request, requestId, loading: true, - isRetry: !!storageElement.retries, + isRetry: !!storageItem.retries, isOffline, }); @@ -521,7 +531,7 @@ export class Dispatcher { const response: RequestResponseType = await adapter(request, requestId); // eslint-disable-next-line no-param-reassign - storageElement.resolved = true; + storageItem.resolved = true; // Stop listening for aborting requestManager.removeAbortController(abortKey, requestId); // Do not continue the request handling when it got stopped and request was unsuccessful @@ -537,8 +547,8 @@ export class Dispatcher { const requestDetails: ResponseDetailsType = { isCanceled, isOffline: isOfflineResponseStatus, - retries: storageElement.retries, - addedTimestamp: storageElement.timestamp, + retries: storageItem.retries, + addedTimestamp: storageItem.timestamp, triggerTimestamp: runningRequest.timestamp, requestTimestamp: response.requestTimestamp, responseTimestamp: response.responseTimestamp, @@ -552,7 +562,7 @@ export class Dispatcher { request, requestId, loading: false, - isRetry: !!storageElement.retries, + isRetry: !!storageItem.retries, isOffline, }); @@ -563,11 +573,11 @@ export class Dispatcher { // On cancelled if (isCanceled) { const queue = this.getQueue(queryKey); - const queueElement = queue.requests.find((req) => req.requestId === requestId); + const queueItem = queue.requests.find((req) => req.requestId === requestId); // do not remove cancelled request as it may be result of manual queue pause // if abort was done without stop action we can remove request - if (!queue.stopped && !queueElement?.stopped) { + if (!queue.stopped && !queueItem?.stopped) { this.logger.debug("Request paused", { response, requestDetails, request }); return this.delete(queryKey, requestId, abortKey); } @@ -599,8 +609,8 @@ export class Dispatcher { setTimeout(() => { this.logger.warning("Error response, performing retry"); this.performRequest({ - ...storageElement, - retries: storageElement.retries + 1, + ...storageItem, + retries: storageItem.retries + 1, }); }, retryTime || 0); } else { diff --git a/packages/core/src/dispatcher/dispatcher.types.ts b/packages/core/src/dispatcher/dispatcher.types.ts index 303730d16..34b7ece8d 100644 --- a/packages/core/src/dispatcher/dispatcher.types.ts +++ b/packages/core/src/dispatcher/dispatcher.types.ts @@ -1,16 +1,11 @@ -import { Dispatcher } from "dispatcher"; import { RequestInstance } from "request"; export type DispatcherOptionsType = { storage?: DispatcherStorageType; - onInitialization?: (dispatcherInstance: Dispatcher) => void; - onUpdateStorage?: (data: QueueDataType) => void; - onDeleteFromStorage?: (data: QueueDataType) => void; - onClearStorage?: (dispatcherInstance: Dispatcher) => void; }; // Values -export type QueueElementType = { +export type QueueItemType = { requestId: string; request: Request; retries: number; @@ -21,7 +16,7 @@ export type QueueElementType }; export type QueueDataType = { queryKey: string; - requests: QueueElementType[]; + requests: QueueItemType[]; stopped: boolean; }; diff --git a/packages/core/src/dispatcher/dispatcher.utils.ts b/packages/core/src/dispatcher/dispatcher.utils.ts index a77a77551..bd98ec0a5 100644 --- a/packages/core/src/dispatcher/dispatcher.utils.ts +++ b/packages/core/src/dispatcher/dispatcher.utils.ts @@ -1,5 +1,5 @@ import { RequestInstance } from "request"; -import { DispatcherRequestType, QueueElementType } from "dispatcher"; +import { DispatcherRequestType, QueueItemType } from "dispatcher"; // Events @@ -38,7 +38,7 @@ export const canRetryRequest = (currentRetries: number, retry: number | undefine return false; }; -export const getRequestType = (request: RequestInstance, latestRequest: QueueElementType | undefined) => { +export const getRequestType = (request: RequestInstance, latestRequest: QueueItemType | undefined) => { const { queued, cancelable, deduplicate } = request; const canDeduplicate = latestRequest ? +new Date() - latestRequest.timestamp <= request.deduplicateTime : false; diff --git a/packages/core/src/managers/app/app.manager.ts b/packages/core/src/managers/app/app.manager.ts index c43dbec2a..7c73f4aba 100644 --- a/packages/core/src/managers/app/app.manager.ts +++ b/packages/core/src/managers/app/app.manager.ts @@ -1,5 +1,6 @@ import { EventEmitter } from "utils"; import { appManagerInitialOptions, AppManagerOptionsType, getAppManagerEvents, hasDocument } from "managers"; +import { ClientInstance } from "client"; /** * App manager handles main application states - focus and online. Those two values can answer questions: @@ -14,6 +15,8 @@ export class AppManager { emitter = new EventEmitter(); events = getAppManagerEvents(this.emitter); + private client: ClientInstance; + isBrowser: boolean; isOnline: boolean; isFocused: boolean; @@ -36,6 +39,10 @@ export class AppManager { this.isBrowser = hasDocument(); } + initialize = (client: ClientInstance) => { + this.client = client; + }; + private setInitialFocus = async (initValue: Exclude) => { if (typeof initValue === "function") { this.isFocused = false; @@ -57,6 +64,8 @@ export class AppManager { setFocused = (isFocused: boolean) => { this.isFocused = isFocused; + this.client.triggerPlugins("onAppFocusChange", { client: this.client, isFocused }); + if (isFocused) { this.events.emitFocus(); } else { @@ -67,6 +76,8 @@ export class AppManager { setOnline = (isOnline: boolean) => { this.isOnline = isOnline; + this.client.triggerPlugins("onAppOnlineChange", { client: this.client, isOnline }); + if (isOnline) { this.events.emitOnline(); } else { diff --git a/packages/core/src/managers/logger/logger.manager.ts b/packages/core/src/managers/logger/logger.manager.ts index 518f50194..82c53946f 100644 --- a/packages/core/src/managers/logger/logger.manager.ts +++ b/packages/core/src/managers/logger/logger.manager.ts @@ -14,10 +14,9 @@ export class LoggerManager { public emitter = new EventEmitter(); - constructor( - private client: Pick, - private options?: LoggerOptionsType, - ) { + private client: Pick; + + constructor(private options?: LoggerOptionsType) { this.emitter?.setMaxListeners(1000); this.logger = this.options?.logger || logger; this.severity = this.options?.severity || 2; @@ -27,7 +26,9 @@ export class LoggerManager { this.severity = severity; }; - init = (module: string): LoggerType => { + initialize = (client: Pick, module: string): LoggerType => { + this.client = client; + return { error: (message: LoggerMessageType, extra?: Record) => { this.logger({ diff --git a/packages/core/src/plugin/plugin.ts b/packages/core/src/plugin/plugin.ts index c7667b1d3..67c3b8cdc 100644 --- a/packages/core/src/plugin/plugin.ts +++ b/packages/core/src/plugin/plugin.ts @@ -16,12 +16,16 @@ export class Plugin { + initialize = (client: Client) => { this.client = client; + return this; }; - triggerMethod = >(method: Key, data: PluginMethodParameters) => { - this.pluginMethods[method]?.(data as any); + trigger = >(method: Key, data: PluginMethodParameters) => { + const callback = this.pluginMethods[method]; + if (callback) { + callback(data as any); + } }; /* ------------------------------------------------------------------------------------------------- @@ -90,4 +94,76 @@ export class Plugin["onDispatcherMount"]) => { + this.pluginMethods.onDispatcherMount = callback; + return this; + }; + + onDispatcherCleared = (callback: PluginMethods["onDispatcherCleared"]) => { + this.pluginMethods.onDispatcherCleared = callback; + return this; + }; + + onDispatcherQueueDrained = (callback: PluginMethods["onDispatcherQueueDrained"]) => { + this.pluginMethods.onDispatcherQueueDrained = callback; + return this; + }; + + onDispatcherItemAdded = (callback: PluginMethods["onDispatcherItemAdded"]) => { + this.pluginMethods.onDispatcherItemAdded = callback; + return this; + }; + + onDispatcherItemDeleted = (callback: PluginMethods["onDispatcherItemDeleted"]) => { + this.pluginMethods.onDispatcherItemDeleted = callback; + return this; + }; + + onDispatcherQueueCreated = (callback: PluginMethods["onDispatcherQueueCreated"]) => { + this.pluginMethods.onDispatcherQueueCreated = callback; + return this; + }; + + onDispatcherQueueCleared = (callback: PluginMethods["onDispatcherQueueCleared"]) => { + this.pluginMethods.onDispatcherQueueCleared = callback; + return this; + }; + + /* ------------------------------------------------------------------------------------------------- + * Cache lifecycle + * -----------------------------------------------------------------------------------------------*/ + + onCacheMount = (callback: PluginMethods["onCacheMount"]) => { + this.pluginMethods.onCacheMount = callback; + return this; + }; + + onCacheItemChange = (callback: PluginMethods["onCacheItemChange"]) => { + this.pluginMethods.onCacheItemChange = callback; + return this; + }; + + onCacheItemDelete = (callback: PluginMethods["onCacheItemDelete"]) => { + this.pluginMethods.onCacheItemDelete = callback; + return this; + }; + + /* ------------------------------------------------------------------------------------------------- + * App lifecycle + * -----------------------------------------------------------------------------------------------*/ + + onAppFocusChange = (callback: PluginMethods["onAppFocusChange"]) => { + this.pluginMethods.onAppFocusChange = callback; + return this; + }; + + onAppOnlineChange = (callback: PluginMethods["onAppOnlineChange"]) => { + this.pluginMethods.onAppOnlineChange = callback; + return this; + }; } diff --git a/packages/core/src/plugin/plugin.types.ts b/packages/core/src/plugin/plugin.types.ts index e3424b0bc..7cac24d75 100644 --- a/packages/core/src/plugin/plugin.types.ts +++ b/packages/core/src/plugin/plugin.types.ts @@ -1,8 +1,16 @@ import { RequestInstance } from "request"; import { ResponseErrorType, ResponseType, ResponseSuccessType } from "adapter"; import { Plugin } from "plugin"; -import { ExtractAdapterType, ExtractErrorType, ExtractResponseType, ExtendRequest } from "types"; +import { + ExtractAdapterType, + ExtractErrorType, + ExtractResponseType, + ExtendRequest, + ExtractClientAdapterType, +} from "types"; import { ClientInstance } from "client"; +import { Cache, CacheValueType } from "cache"; +import { Dispatcher, QueueDataType, QueueItemType } from "dispatcher"; export type PluginLifecycle = "trigger" | "start" | "success" | "error" | "finished"; export type PluginInstance = Plugin; @@ -25,8 +33,17 @@ export type PluginOptionsType = { }; export type PluginMethods = { + /* ------------------------------------------------------------------------------------------------- + * Plugin lifecycle + * -----------------------------------------------------------------------------------------------*/ + onMount?: (data: { client: Client }) => void; onUnmount?: (data: { client: Client }) => void; + + /* ------------------------------------------------------------------------------------------------- + * Request lifecycle + * -----------------------------------------------------------------------------------------------*/ + onRequestCreate?: (data: { request: PluginRequest }) => void; onRequestTrigger?: (data: { request: PluginRequest }) => void; onRequestStart?: (data: { request: PluginRequest }) => void; @@ -49,6 +66,117 @@ export type PluginMethods = { >; request: PluginRequest; }) => void; + + /* ------------------------------------------------------------------------------------------------- + * Cache lifecycle + * -----------------------------------------------------------------------------------------------*/ + + onCacheMount?: (data: { cache: Cache }) => void; + onCacheItemChange?: (data: { + cacheKey: Requests["cacheKey"]; + prevData: CacheValueType> | null; + newData: CacheValueType>; + cache: Cache; + }) => void; + onCacheItemDelete?: (data: { cacheKey: string; cache: Cache }) => void; + onCacheItemInvalidate?: (data: { cacheKey: string; cache: Cache }) => void; + + /* ------------------------------------------------------------------------------------------------- + * Dispatcher lifecycle + * -----------------------------------------------------------------------------------------------*/ + + onDispatcherMount?: (data: { dispatcher: Dispatcher }) => void; + onDispatcherCleared?: (data: { dispatcher: Dispatcher }) => void; + onDispatcherQueueDrained?: (data: { + dispatcher: Dispatcher; + queue: QueueDataType< + ExtendRequest< + RequestInstance, + { + client: Client; + } + > + >; + }) => void; + onDispatcherItemAdded?: (data: { + dispatcher: Dispatcher; + queue: QueueDataType< + ExtendRequest< + RequestInstance, + { + client: Client; + } + > + >; + queueItem: QueueItemType< + ExtendRequest< + RequestInstance, + { + client: Client; + } + > + >; + }) => void; + onDispatcherItemDeleted?: (data: { + dispatcher: Dispatcher; + queue: QueueDataType< + ExtendRequest< + RequestInstance, + { + client: Client; + } + > + >; + queueItem: QueueItemType< + ExtendRequest< + RequestInstance, + { + client: Client; + } + > + >; + }) => void; + onDispatcherQueueRunning?: (data: { + dispatcher: Dispatcher; + queue: QueueDataType< + ExtendRequest< + RequestInstance, + { + client: Client; + } + > + >; + status: "paused" | "stopped" | "running"; + }) => void; + onDispatcherQueueCreated?: (data: { + dispatcher: Dispatcher; + queue: QueueDataType< + ExtendRequest< + RequestInstance, + { + client: Client; + } + > + >; + }) => void; + onDispatcherQueueCleared?: (data: { + dispatcher: Dispatcher; + queue: QueueDataType< + ExtendRequest< + RequestInstance, + { + client: Client; + } + > + >; + }) => void; + + /* ------------------------------------------------------------------------------------------------- + * App lifecycle + * -----------------------------------------------------------------------------------------------*/ + + onAppFocusChange?: (data: { isFocused: boolean; client: Client }) => void; + onAppOnlineChange?: (data: { isOnline: boolean; client: Client }) => void; }; export type PluginMethodParameters, Client extends ClientInstance> = Parameters< diff --git a/packages/devtools-standalone/src/sockets/devtools.socket.wrapper.tsx b/packages/devtools-standalone/src/sockets/devtools.socket.wrapper.tsx index acf2d891f..a3189d8f3 100644 --- a/packages/devtools-standalone/src/sockets/devtools.socket.wrapper.tsx +++ b/packages/devtools-standalone/src/sockets/devtools.socket.wrapper.tsx @@ -68,7 +68,7 @@ const handleEvent = ( export const DevtoolsSocketWrapper = ({ workspace, client }: { workspace: string; client: ClientInstance }) => { const { setRequestList } = useDevtoolsWorkspaces("Devtools"); - const logger = client.loggerManager.init(`DevtoolsStandalone`); + const logger = client.loggerManager.initialize(client, `DevtoolsStandalone`); const { onEvent } = useListener(clientSpecificReceiveMessage, {}); onEvent((eventData) => { handleEvent(client, eventData, logger, setRequestList, workspace); diff --git a/packages/react/__tests__/utils/use-request-events.utils.ts b/packages/react/__tests__/utils/use-request-events.utils.ts index b410d23d2..6080dbebd 100644 --- a/packages/react/__tests__/utils/use-request-events.utils.ts +++ b/packages/react/__tests__/utils/use-request-events.utils.ts @@ -12,14 +12,14 @@ export const renderUseRequestEvents = ( const { client } = request; const { fetchDispatcher: dispatcher, loggerManager } = client; - const logger = loggerManager.init("test"); + const logger = loggerManager.initialize(client, "test"); const { result } = renderHook(() => { return useTrackedState({ logger, request, dispatcher, - initialData: null, + initialResponse: null, deepCompare: isEqual, dependencyTracking: false, ...trackedOptions, diff --git a/packages/react/__tests__/utils/use-tracked-state.utils.ts b/packages/react/__tests__/utils/use-tracked-state.utils.ts index 1c52c53f5..edd8f8885 100644 --- a/packages/react/__tests__/utils/use-tracked-state.utils.ts +++ b/packages/react/__tests__/utils/use-tracked-state.utils.ts @@ -11,13 +11,13 @@ export const renderUseTrackedState = ( const { client } = request; const { fetchDispatcher: dispatcher, loggerManager } = client; - const logger = loggerManager.init("test"); + const logger = loggerManager.initialize(client, "test"); return renderHook(() => { return useTrackedState({ logger, request, dispatcher, - initialData: null, + initialResponse: null, deepCompare: isEqual, dependencyTracking: false, ...options, diff --git a/packages/react/src/hooks/use-cache/use-cache.hooks.ts b/packages/react/src/hooks/use-cache/use-cache.hooks.ts index 879e53714..447ac5760 100644 --- a/packages/react/src/hooks/use-cache/use-cache.hooks.ts +++ b/packages/react/src/hooks/use-cache/use-cache.hooks.ts @@ -13,7 +13,7 @@ export const useCache = ( const { cacheKey, client } = request; const { cache, loggerManager } = client; - const logger = useRef(loggerManager.init("useCache")).current; + const logger = useRef(loggerManager.initialize(client, "useCache")).current; const [dispatcher] = getRequestDispatcher(request); const updateKey = JSON.stringify(request.toJSON()); diff --git a/packages/react/src/hooks/use-fetch/use-fetch.hooks.ts b/packages/react/src/hooks/use-fetch/use-fetch.hooks.ts index e6dd6450c..504cbef19 100644 --- a/packages/react/src/hooks/use-fetch/use-fetch.hooks.ts +++ b/packages/react/src/hooks/use-fetch/use-fetch.hooks.ts @@ -60,7 +60,7 @@ export const useFetch = ( const { cache, fetchDispatcher: dispatcher, appManager, loggerManager } = client; const ignoreReact18DoubleRender = useRef(true); - const logger = useRef(loggerManager.init("useFetch")).current; + const logger = useRef(loggerManager.initialize(client, "useFetch")).current; const bounceData = bounceType === "throttle" ? requestThrottle : requestDebounce; const bounceFunction = bounceType === "throttle" ? requestThrottle.throttle : requestDebounce.debounce; diff --git a/packages/react/src/hooks/use-queue/use-queue.hooks.ts b/packages/react/src/hooks/use-queue/use-queue.hooks.ts index 4a0bd4b7a..0368136f6 100644 --- a/packages/react/src/hooks/use-queue/use-queue.hooks.ts +++ b/packages/react/src/hooks/use-queue/use-queue.hooks.ts @@ -1,5 +1,5 @@ import { useState, useEffect, useCallback } from "react"; -import { RequestInstance, getRequestDispatcher, QueueElementType, QueueDataType } from "@hyper-fetch/core"; +import { RequestInstance, getRequestDispatcher, QueueItemType, QueueDataType } from "@hyper-fetch/core"; import { UseQueueOptionsType, useQueueDefaultOptions, QueueRequest, UseQueueReturnType } from "hooks/use-queue"; import { useProvider } from "provider"; @@ -35,7 +35,7 @@ export const useQueue = ( // ****************** const createRequestsArray = useCallback( - (queueElements: QueueElementType[]): QueueRequest[] => { + (queueElements: QueueItemType[]): QueueRequest[] => { return queueElements.map>((req) => ({ ...req, stopRequest: () => dispatcher.stopRequest(queryKey, req.requestId), diff --git a/packages/react/src/hooks/use-queue/use-queue.types.ts b/packages/react/src/hooks/use-queue/use-queue.types.ts index c635a25cf..9c928658d 100644 --- a/packages/react/src/hooks/use-queue/use-queue.types.ts +++ b/packages/react/src/hooks/use-queue/use-queue.types.ts @@ -1,10 +1,10 @@ -import { ProgressType, RequestInstance, QueueElementType, Dispatcher } from "@hyper-fetch/core"; +import { ProgressType, RequestInstance, QueueItemType, Dispatcher } from "@hyper-fetch/core"; export type UseQueueOptionsType = { queueType?: "auto" | "fetch" | "submit"; }; -export type QueueRequest = QueueElementType & { +export type QueueRequest = QueueItemType & { /** * Uploading progress for given request */ diff --git a/packages/react/src/hooks/use-submit/use-submit.hooks.ts b/packages/react/src/hooks/use-submit/use-submit.hooks.ts index b0a2b9799..a4518341e 100644 --- a/packages/react/src/hooks/use-submit/use-submit.hooks.ts +++ b/packages/react/src/hooks/use-submit/use-submit.hooks.ts @@ -56,7 +56,7 @@ export const useSubmit = ( const { client } = request; const { cache, submitDispatcher: dispatcher, loggerManager } = client; - const logger = useRef(loggerManager.init("useSubmit")).current; + const logger = useRef(loggerManager.initialize(client, "useSubmit")).current; const requestDebounce = useDebounce({ delay: bounceTime }); const requestThrottle = useThrottle({ interval: bounceTime, diff --git a/packages/sockets/src/adapter/adapter.bindings.ts b/packages/sockets/src/adapter/adapter.bindings.ts index 026ab1b31..3c84ffe66 100644 --- a/packages/sockets/src/adapter/adapter.bindings.ts +++ b/packages/sockets/src/adapter/adapter.bindings.ts @@ -13,7 +13,7 @@ export const getSocketAdapterBindings = ( reconnectionAttempts?: number; }, ) => { - const logger = socket.loggerManager.init("Socket Adapter"); + const logger = socket.loggerManager.initialize(socket, "Socket Adapter"); const listeners: Map, VoidFunction>> = new Map(); const state = { diff --git a/packages/sockets/src/socket/socket.ts b/packages/sockets/src/socket/socket.ts index 124729146..10773ab0c 100644 --- a/packages/sockets/src/socket/socket.ts +++ b/packages/sockets/src/socket/socket.ts @@ -49,12 +49,12 @@ export class Socket; - loggerManager = new LoggerManager(this); + loggerManager = new LoggerManager(); appManager = new AppManager(); queryParamsConfig?: QueryStringifyOptionsType; // Logger - logger = this.loggerManager.init("Socket"); + logger = this.loggerManager.initialize(this, "Socket"); constructor(public options: SocketOptionsType) { const { url, adapter, queryParams, reconnect, reconnectTime, queryParamsConfig, queryParamsStringify } = diff --git a/packages/testing/src/http/index.ts b/packages/testing/src/http/index.ts index 515ba8f0e..4905434e9 100644 --- a/packages/testing/src/http/index.ts +++ b/packages/testing/src/http/index.ts @@ -1,11 +1,2 @@ -function channelMock() {} -channelMock.prototype.onmessage = function () {}; -channelMock.prototype.postMessage = function (data: any) { - this.onmessage({ data }); -}; -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -global.BroadcastChannel = channelMock; - export * from "./http"; export * from "./http.constants"; diff --git a/yarn.lock b/yarn.lock index adb0043bf..90f4dd18a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16067,10 +16067,10 @@ msw@2.6.5: type-fest "^4.26.1" yargs "^17.7.2" -msw@^2.6.8: - version "2.6.8" - resolved "https://registry.yarnpkg.com/msw/-/msw-2.6.8.tgz#0cc4d92526444f958829f3fb263ab55ca7437b1d" - integrity sha512-nxXxnH6WALZ9a7rsQp4HU2AaD4iGAiouMmE/MY4al7pXTibgA6OZOuKhmN2WBIM6w9qMKwRtX8p2iOb45B2M/Q== +msw@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/msw/-/msw-2.7.0.tgz#d13ff87f7e018fc4c359800ff72ba5017033fb56" + integrity sha512-BIodwZ19RWfCbYTxWTUfTXc+sg4OwjCAgxU1ZsgmggX/7S3LdUifsbUPJs61j0rWb19CZRGY5if77duhc0uXzw== dependencies: "@bundled-es-modules/cookie" "^2.0.1" "@bundled-es-modules/statuses" "^1.0.1" @@ -16081,12 +16081,12 @@ msw@^2.6.8: "@open-draft/until" "^2.1.0" "@types/cookie" "^0.6.0" "@types/statuses" "^2.0.4" - chalk "^4.1.2" graphql "^16.8.1" headers-polyfill "^4.0.2" is-node-process "^1.2.0" outvariant "^1.4.3" path-to-regexp "^6.3.0" + picocolors "^1.1.1" strict-event-emitter "^0.5.1" type-fest "^4.26.1" yargs "^17.7.2"