├── packages ├── example │ ├── src │ │ ├── vite-env.d.ts │ │ ├── sdk │ │ │ └── index.ts │ │ ├── main.tsx │ │ ├── hooks │ │ │ ├── useMarkets.tsx │ │ │ ├── useMarketPool.tsx │ │ │ └── useAssetsForPool.tsx │ │ ├── App.tsx │ │ └── components │ │ │ └── Market.tsx │ ├── tsconfig.node.json │ ├── .gitignore │ ├── index.html │ ├── tsconfig.json │ ├── package.json │ ├── vite.config.ts │ └── public │ │ └── vite.svg ├── devtools │ ├── src │ │ ├── index.ts │ │ ├── events.ts │ │ ├── state.ts │ │ └── devtools.ts │ ├── tsconfig.json │ ├── package.json │ ├── rollup.config.mjs │ ├── CHANGELOG.md │ ├── README.md │ └── tsconfig.tsbuildinfo ├── batshit │ ├── tsconfig.json │ ├── package.json │ ├── src │ │ ├── deferred.ts │ │ └── index.ts │ ├── rollup.config.mjs │ ├── tests │ │ ├── mock.ts │ │ └── index.test.ts │ ├── CHANGELOG.md │ └── README.md └── devtools-react │ ├── tsconfig.json │ ├── src │ ├── styles.ts │ ├── hooks │ │ ├── useDevtoolsState.ts │ │ └── useLocalState.ts │ ├── components │ │ ├── Batcher.tsx │ │ └── Sequence.tsx │ └── index.tsx │ ├── rollup.config.mjs │ ├── package.json │ └── CHANGELOG.md ├── .gitattributes ├── .yarnrc.yml ├── .editorconfig ├── .gitignore ├── .changeset ├── config.json └── README.md ├── turbo.json ├── tsconfig.json ├── .github └── workflows │ └── ci.yml ├── package.json ├── LICENSE └── README.md /packages/example/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | yarnPath: .yarn/releases/yarn-3.3.1.cjs 2 | nodeLinker: node-modules 3 | -------------------------------------------------------------------------------- /packages/devtools/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./devtools"; 2 | export * from "./events"; 3 | export * from "./state"; 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*.{js,json,yml}] 8 | charset = utf-8 9 | indent_style = space 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /packages/example/src/sdk/index.ts: -------------------------------------------------------------------------------- 1 | import * as Ztg from "@zeitgeistpm/indexer"; 2 | 3 | export const zeitgeist = Ztg.create({ 4 | uri: "https://processor.rpc-0.zeitgeist.pm/graphql", 5 | }); 6 | -------------------------------------------------------------------------------- /packages/example/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pnp.* 2 | .yarn/* 3 | !.yarn/patches 4 | !.yarn/plugins 5 | !.yarn/releases 6 | !.yarn/sdks 7 | !.yarn/versions 8 | 9 | # Swap the comments on the following lines if you don't wish to use zero-installs 10 | # Documentation here: https://yarnpkg.com/features/zero-installs 11 | .yarn/cache 12 | #.pnp.* 13 | 14 | node_modules 15 | coverage 16 | .rollup.cache 17 | .turbo 18 | dist -------------------------------------------------------------------------------- /packages/example/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "restricted", 8 | "baseBranch": "master", 9 | "updateInternalDependencies": "patch", 10 | "updateInternalDependents": "allways", 11 | "ignore": ["example"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/batshit/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Default", 4 | "extends": "../../tsconfig.json", 5 | "compilerOptions": { 6 | "rootDir": "./src", 7 | "outDir": "./dist", 8 | "emitDeclarationOnly": true, 9 | "declaration": true, 10 | "declarationMap": true 11 | }, 12 | "exclude": ["node_modules", "dist", "tests"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/devtools/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Default", 4 | "extends": "../../tsconfig.json", 5 | "compilerOptions": { 6 | "rootDir": "./src", 7 | "outDir": "./dist", 8 | "emitDeclarationOnly": true, 9 | "declaration": true, 10 | "declarationMap": true 11 | }, 12 | "exclude": ["node_modules", "dist", "tests"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/devtools-react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Default", 4 | "extends": "../../tsconfig.json", 5 | "compilerOptions": { 6 | "rootDir": "./src", 7 | "outDir": "./dist", 8 | "emitDeclarationOnly": true, 9 | "declaration": true, 10 | "declarationMap": true 11 | }, 12 | "exclude": ["node_modules", "dist", "tests"] 13 | } 14 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "tasks": { 4 | "build": { 5 | "outputs": [ 6 | "./dist/**" 7 | ] 8 | }, 9 | "build-example": { 10 | "outputs": [ 11 | "./dist/**" 12 | ] 13 | }, 14 | "test": { 15 | "outputs": [ 16 | "./dist/**" 17 | ] 18 | }, 19 | "lint": { 20 | "outputs": [] 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /packages/example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | @yornaath/batshit + devtools example 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/example/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 4 | import App from "./App"; 5 | 6 | const client = new QueryClient(); 7 | 8 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( 9 | 10 | 11 | 12 | 13 | 14 | ); 15 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /packages/example/src/hooks/useMarkets.tsx: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import * as Ztg from "@zeitgeistpm/indexer"; 3 | import { zeitgeist } from "../sdk"; 4 | 5 | export const useMarkets = () => { 6 | return useQuery(["markets"], async () => { 7 | const markets = await zeitgeist.markets({ 8 | offset: 20, 9 | limit: 100, 10 | order: Ztg.MarketOrderByInput.CreationDesc, 11 | }); 12 | return markets.markets.filter((market) => 13 | Boolean(market.pool?.poolId && market.slug?.length! > 5) 14 | ); 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /packages/example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /packages/devtools-react/src/styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = ` 2 | .batshit-osx-scrollbars::-webkit-scrollbar { 3 | background-color: #fff; 4 | width: 14px; 5 | } 6 | 7 | /* background of the scrollbar except button or resizer */ 8 | .batshit-osx-scrollbars::-webkit-scrollbar-track { 9 | background-color: rgb(30, 30, 30); 10 | } 11 | 12 | /* scrollbar itself */ 13 | .batshit-osx-scrollbars::-webkit-scrollbar-thumb { 14 | background-color: rgba(255,255,255, 0.5); 15 | border-radius: 16px; 16 | border: 4px solid rgb(30, 30, 30); 17 | } 18 | 19 | /* set button(top and bottom of the scrollbar) */ 20 | .batshit-osx-scrollbars::-webkit-scrollbar-button { 21 | display:none; 22 | } 23 | `; 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Default", 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "inlineSources": false, 9 | "declaration": true, 10 | "declarationMap": true, 11 | "isolatedModules": true, 12 | "module": "ESNext", 13 | "moduleResolution": "node", 14 | "target": "ES2020", 15 | "noUnusedLocals": false, 16 | "noUnusedParameters": false, 17 | "preserveWatchOutput": true, 18 | "skipLibCheck": true, 19 | "strict": true, 20 | "resolveJsonModule": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "exclude": ["node_modules", "dist", "tests"] 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [22.x] 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | cache: 'yarn' 25 | cache-dependency-path: yarn.lock 26 | 27 | - name: Build 28 | run: | 29 | yarn install 30 | yarn build 31 | 32 | 33 | - name: Test 34 | run: | 35 | yarn test 36 | -------------------------------------------------------------------------------- /packages/devtools-react/src/hooks/useDevtoolsState.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BatshitDevtoolsState, 3 | injectDevtools, 4 | reduce, 5 | } from "@yornaath/batshit-devtools"; 6 | import React from "react"; 7 | 8 | let subscribe: (() => void) | undefined; 9 | let state: BatshitDevtoolsState = {}; 10 | 11 | const getSnapshot = () => state; 12 | const getServerSnapshot = () => state; 13 | 14 | injectDevtools((event) => { 15 | state = reduce([event], state); 16 | subscribe?.(); 17 | }); 18 | 19 | export const useDevtoolsState = () => { 20 | return React.useSyncExternalStore>( 21 | (callback) => { 22 | subscribe = callback; 23 | return () => { 24 | subscribe = undefined; 25 | }; 26 | }, 27 | getSnapshot, 28 | getServerSnapshot 29 | ); 30 | }; 31 | -------------------------------------------------------------------------------- /packages/devtools-react/src/hooks/useLocalState.ts: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useMemo } from "react"; 2 | 3 | export const useLocalState = ( 4 | key: string, 5 | defaultValue?: T 6 | ): [T, React.Dispatch>] => { 7 | const hasPersistedState = useMemo(() => { 8 | return globalThis.localStorage?.getItem(key) !== null; 9 | }, []); 10 | 11 | const persistedState = useMemo(() => { 12 | return hasPersistedState 13 | ? JSON.parse(globalThis.localStorage?.getItem(key) as string) 14 | : null; 15 | }, []); 16 | 17 | const [value, setValue] = React.useState( 18 | hasPersistedState ? persistedState : defaultValue ?? false 19 | ); 20 | 21 | useEffect(() => { 22 | globalThis.localStorage?.setItem(key, JSON.stringify(value)); 23 | }, [value]); 24 | 25 | return [value, setValue]; 26 | }; 27 | -------------------------------------------------------------------------------- /packages/example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "description": "Example project for batshit", 4 | "private": true, 5 | "version": "0.0.0", 6 | "type": "module", 7 | "scripts": { 8 | "dev": "vite", 9 | "preview": "vite preview", 10 | "build-example": "vite build" 11 | }, 12 | "dependencies": { 13 | "@tanstack/react-query": "^4.20.4", 14 | "@yornaath/batshit": "*", 15 | "@yornaath/batshit-devtools": "*", 16 | "@yornaath/batshit-devtools-react": "*", 17 | "@zeitgeistpm/indexer": "^4.4.2", 18 | "delay": "^5.0.0", 19 | "react": "^18.2.0", 20 | "react-dom": "^18.2.0", 21 | "rollup-plugin-node-polyfills": "^0.2.1" 22 | }, 23 | "devDependencies": { 24 | "@types/react": "^18.0.26", 25 | "@types/react-dom": "^18.0.9", 26 | "@vitejs/plugin-react": "^3.0.0", 27 | "typescript": "^4.9.3", 28 | "vite": "^4.0.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/devtools/src/events.ts: -------------------------------------------------------------------------------- 1 | export type BatshitEvent = 2 | | CreateEvent 3 | | QueueEvent 4 | | FetchEvent 5 | | DataEvent 6 | | ErrorEvent; 7 | 8 | export type CreateEvent = { 9 | type: "create"; 10 | name: string; 11 | seq: number; 12 | }; 13 | 14 | export type QueueEvent = { 15 | type: "queue"; 16 | name: string; 17 | seq: number; 18 | batch: Q[]; 19 | start: number; 20 | latest: number; 21 | scheduled: number | "immediate" | "never"; 22 | }; 23 | 24 | export type FetchEvent = { 25 | type: "fetch"; 26 | name: string; 27 | seq: number; 28 | batch: Q[]; 29 | }; 30 | 31 | export type DataEvent = { 32 | type: "data"; 33 | name: string; 34 | seq: number; 35 | data: T; 36 | }; 37 | 38 | export type ErrorEvent = { 39 | type: "error"; 40 | name: string; 41 | seq: number; 42 | error: Error; 43 | }; 44 | -------------------------------------------------------------------------------- /packages/devtools/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@yornaath/batshit-devtools", 3 | "version": "1.7.1", 4 | "description": "A batch manager that will deduplicate and batch requests for a certain data type made within a window.", 5 | "author": { 6 | "name": "Jørn Andre", 7 | "url": "https://github.com/yornaath/batshit" 8 | }, 9 | "license": "MIT", 10 | "publishConfig": { 11 | "access": "public" 12 | }, 13 | "main": "./dist/index.js", 14 | "module": "./dist/index.esm.js", 15 | "types": "./dist/index.d.ts", 16 | "exports": { 17 | ".": { 18 | "types": "./dist/index.d.ts", 19 | "import": "./dist/index.mjs", 20 | "default": "./dist/index.js" 21 | }, 22 | "./package.json": "./package.json" 23 | }, 24 | "files": [ 25 | "dist/*", 26 | "src" 27 | ], 28 | "repository": { 29 | "type": "git", 30 | "url": "https://github.com/yornaath/batshit.git" 31 | }, 32 | "packageManager": "yarn@3.3.1", 33 | "scripts": { 34 | "coverage": "vitest run --coverage", 35 | "build": "rimraf ./dist && rollup -c && tsc" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "batshit-repo", 3 | "version": "0.0.4", 4 | "description": "A batch manager that will deduplicate and batch requests for a certain data type made within a window.", 5 | "author": { 6 | "name": "Jørn Andre", 7 | "url": "https://github.com/yornaath" 8 | }, 9 | "workspaces": [ 10 | "packages/*" 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/yornaath/batshit" 15 | }, 16 | "packageManager": "yarn@3.3.1", 17 | "scripts": { 18 | "test": "turbo test", 19 | "coverage": "turbo coverage", 20 | "build": "turbo build", 21 | "build-example": "turbo build && turbo build-example" 22 | }, 23 | "devDependencies": { 24 | "@changesets/cli": "^2.26.0", 25 | "@rollup/plugin-commonjs": "^24.0.0", 26 | "@rollup/plugin-typescript": "^10.0.1", 27 | "@vitest/coverage-c8": "^0.26.2", 28 | "rimraf": "^6.1.0", 29 | "rollup": "^3.8.0", 30 | "rollup-plugin-typescript2": "^0.34.1", 31 | "tslib": "^2.4.1", 32 | "turbo": "^2.5.8", 33 | "typescript": "^4.9.4", 34 | "vitest": "^0.26.2" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/example/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import * as path from "path"; 3 | import react from "@vitejs/plugin-react"; 4 | console.log(path.resolve(process.cwd(), "../batshit/src/index.ts")); 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [react()], 8 | resolve: { 9 | alias: { 10 | "@yornaath/batshit/*": path.resolve(process.cwd(), "../batshit/src/*"), 11 | "@yornaath/batshit-devtools/*": path.resolve( 12 | process.cwd(), 13 | "../devtools/src/*" 14 | ), 15 | "@yornaath/batshit-devtools-react/*": path.resolve( 16 | process.cwd(), 17 | "../devtools-react/src/*" 18 | ), 19 | "@yornaath/batshit": path.resolve( 20 | process.cwd(), 21 | "../batshit/src/index.ts" 22 | ), 23 | "@yornaath/batshit-devtools": path.resolve( 24 | process.cwd(), 25 | "../devtools/src/index.ts" 26 | ), 27 | "@yornaath/batshit-devtools-react": path.resolve( 28 | process.cwd(), 29 | "../devtools-react/src/index.tsx" 30 | ), 31 | }, 32 | }, 33 | }); 34 | -------------------------------------------------------------------------------- /packages/example/src/hooks/useMarketPool.tsx: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import * as batshit from "@yornaath/batshit"; 3 | import * as Ztg from "@zeitgeistpm/indexer"; 4 | import { FullMarketFragment } from "@zeitgeistpm/indexer"; 5 | import delay from "delay"; 6 | import { zeitgeist } from "../sdk"; 7 | 8 | const poolsBatcher = batshit.create({ 9 | fetcher: async (ids: number[]) => { 10 | await delay(200); 11 | const { pools } = await zeitgeist.pools({ 12 | where: { 13 | poolId_in: ids, 14 | }, 15 | }); 16 | return pools; 17 | }, 18 | resolver: batshit.keyResolver("poolId"), 19 | scheduler: batshit.bufferScheduler(1000), 20 | name: "pools", 21 | }); 22 | 23 | export const useMarketPool = ( 24 | market: FullMarketFragment, 25 | options: { enabled: boolean } 26 | ) => { 27 | return useQuery( 28 | ["pool-for-market", market], 29 | async () => { 30 | return poolsBatcher.fetch(market.pool?.poolId!); 31 | }, 32 | { 33 | keepPreviousData: true, 34 | enabled: Boolean(market.pool?.poolId && options.enabled), 35 | } 36 | ); 37 | }; 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-present Jørn Andre 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/example/src/hooks/useAssetsForPool.tsx: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import * as batshit from "@yornaath/batshit"; 3 | import * as Ztg from "@zeitgeistpm/indexer"; 4 | import delay from "delay"; 5 | import { zeitgeist } from "../sdk"; 6 | 7 | const assetsBatcher = batshit.create({ 8 | fetcher: async (ids: number[]) => { 9 | await delay(200); 10 | const { assets } = await zeitgeist.assets({ 11 | where: { 12 | market: { 13 | pool: { 14 | poolId_in: ids, 15 | }, 16 | }, 17 | }, 18 | }); 19 | return assets; 20 | }, 21 | resolver: (assets, query) => 22 | assets.filter((a) => a.market.pool?.poolId === query), 23 | scheduler: batshit.bufferScheduler(1000), 24 | name: "assets", 25 | }); 26 | 27 | export const useAssetsForPool = (pool?: Ztg.FullPoolFragment) => { 28 | return useQuery( 29 | ["assets-for-pool", pool], 30 | async () => { 31 | if (pool) { 32 | return assetsBatcher.fetch(pool?.poolId); 33 | } 34 | }, 35 | { 36 | keepPreviousData: true, 37 | enabled: Boolean(pool), 38 | } 39 | ); 40 | }; 41 | -------------------------------------------------------------------------------- /packages/batshit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@yornaath/batshit", 3 | "version": "0.12.0", 4 | "description": "A batch manager that will deduplicate and batch requests for a certain data type made within a window.", 5 | "author": { 6 | "name": "Jørn Andre", 7 | "url": "https://github.com/yornaath/batshit" 8 | }, 9 | "license": "MIT", 10 | "publishConfig": { 11 | "access": "public" 12 | }, 13 | "main": "./dist/index.js", 14 | "module": "./dist/index.esm.js", 15 | "types": "./dist/index.d.ts", 16 | "exports": { 17 | ".": { 18 | "types": "./dist/index.d.ts", 19 | "import": "./dist/index.mjs", 20 | "default": "./dist/index.js" 21 | }, 22 | "./package.json": "./package.json" 23 | }, 24 | "files": [ 25 | "dist/*", 26 | "src" 27 | ], 28 | "repository": { 29 | "type": "git", 30 | "url": "https://github.com/yornaath/batshit.git" 31 | }, 32 | "packageManager": "yarn@3.3.1", 33 | "dependencies": { 34 | "@yornaath/batshit-devtools": "^1.7.1" 35 | }, 36 | "scripts": { 37 | "test": "vitest run", 38 | "coverage": "vitest run --coverage", 39 | "build": "rimraf ./dist && rollup -c && tsc" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/batshit/src/deferred.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A deffered value that can be resolved at a later time outside of its closure. 3 | * @generic T - value of the deffered 4 | */ 5 | export type Deferred = { 6 | resolve: (value: T | PromiseLike) => void 7 | reject: (reason?: any) => void 8 | value: Promise 9 | } 10 | 11 | /** 12 | * Create a new Deffered 13 | * 14 | * @generic T - value of the deffered 15 | * @returns Deferred 16 | */ 17 | export const deferred = (): Deferred => { 18 | let resolve!: Deferred['resolve'] 19 | let reject!: Deferred['reject'] 20 | 21 | const value = new Promise((_resolve, _reject) => { 22 | resolve = _resolve 23 | reject = _reject 24 | }) 25 | 26 | return { 27 | resolve, 28 | reject, 29 | value, 30 | } 31 | } 32 | 33 | /** 34 | * Type guard for Deffered values. 35 | * 36 | * @generic T - value of the deffered 37 | * @param value any 38 | * @returns value is Deferred 39 | */ 40 | export const isDeferred = (value: any): value is Deferred => 41 | typeof value === 'object' && 42 | value !== null && 43 | 'resolve' in value && 44 | 'reject' in value && 45 | 'value' in value && 46 | 'then' in value.value -------------------------------------------------------------------------------- /packages/example/src/App.tsx: -------------------------------------------------------------------------------- 1 | import BatshitDevtools from "@yornaath/batshit-devtools-react"; 2 | import { Market } from "./components/Market"; 3 | import { useMarkets } from "./hooks/useMarkets"; 4 | 5 | function App() { 6 | const { data: markets } = useMarkets(); 7 | 8 | return ( 9 |
13 |

Zeitgeist Prediction Markets

14 | 15 |

16 | Loading liquidity for a pool will queue a fetch and stagger the fetching 17 | by 1 second. Clicking another load within the staggered window will 18 | stagger by another 1 second.

19 | 20 | Use the batshit devtools in the bottom right corner to inspect the 21 | batching process. 22 | 23 |

24 | 25 |
26 | {markets?.map((market) => ( 27 | 28 | ))} 29 |
30 | 31 | 32 |
33 | ); 34 | } 35 | 36 | export default App; 37 | -------------------------------------------------------------------------------- /packages/batshit/rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import typescript from "@rollup/plugin-typescript"; 2 | import commonjs from "@rollup/plugin-commonjs"; 3 | import { pathToFileURL } from "url"; 4 | import { join } from "path"; 5 | 6 | const pkg = await import(pathToFileURL(join(process.cwd(), "package.json")).href, { 7 | with: { type: "json" }, 8 | }); 9 | 10 | export default { 11 | input: "src/index.ts", 12 | external: [ 13 | ...Object.keys(pkg.peerDependencies || {}), 14 | ...Object.keys(pkg.dependencies || {}), 15 | ], 16 | output: [ 17 | { 18 | dir: "./dist", 19 | format: "esm", 20 | sourcemap: true, 21 | exports: "named", 22 | preserveModules: true, 23 | preserveModulesRoot: "src", 24 | entryFileNames: "[name].mjs", 25 | }, 26 | { 27 | dir: "./dist", 28 | format: "esm", 29 | sourcemap: true, 30 | exports: "named", 31 | preserveModules: true, 32 | preserveModulesRoot: "src", 33 | entryFileNames: "[name].esm.js", 34 | }, 35 | { 36 | dir: "./dist", 37 | format: "cjs", 38 | sourcemap: true, 39 | exports: "named", 40 | preserveModules: true, 41 | preserveModulesRoot: "src", 42 | entryFileNames: "[name].js", 43 | }, 44 | ], 45 | plugins: [typescript(), commonjs({ exclude: "node_modules" })], 46 | }; 47 | -------------------------------------------------------------------------------- /packages/devtools/rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import typescript from "@rollup/plugin-typescript"; 2 | import commonjs from "@rollup/plugin-commonjs"; 3 | import { pathToFileURL } from "url"; 4 | import { join } from "path"; 5 | 6 | const pkg = await import(pathToFileURL(join(process.cwd(), "package.json")).href, { 7 | with: { type: "json" }, 8 | }); 9 | 10 | export default { 11 | input: "src/index.ts", 12 | external: [ 13 | ...Object.keys(pkg.peerDependencies || {}), 14 | ...Object.keys(pkg.dependencies || {}), 15 | ], 16 | output: [ 17 | { 18 | dir: "./dist", 19 | format: "esm", 20 | sourcemap: true, 21 | exports: "named", 22 | preserveModules: true, 23 | preserveModulesRoot: "src", 24 | entryFileNames: "[name].mjs", 25 | }, 26 | { 27 | dir: "./dist", 28 | format: "esm", 29 | sourcemap: true, 30 | exports: "named", 31 | preserveModules: true, 32 | preserveModulesRoot: "src", 33 | entryFileNames: "[name].esm.js", 34 | }, 35 | { 36 | dir: "./dist", 37 | format: "cjs", 38 | sourcemap: true, 39 | exports: "named", 40 | preserveModules: true, 41 | preserveModulesRoot: "src", 42 | entryFileNames: "[name].js", 43 | }, 44 | ], 45 | plugins: [typescript(), commonjs({ exclude: "node_modules" })], 46 | }; 47 | -------------------------------------------------------------------------------- /packages/devtools-react/rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import typescript from "@rollup/plugin-typescript"; 2 | import commonjs from "@rollup/plugin-commonjs"; 3 | import { pathToFileURL } from "url"; 4 | import { join } from "path"; 5 | 6 | const pkg = await import(pathToFileURL(join(process.cwd(), "package.json")).href, { 7 | with: { type: "json" }, 8 | }); 9 | 10 | export default { 11 | input: "./src/index.tsx", 12 | external: [ 13 | ...Object.keys(pkg.peerDependencies || {}), 14 | ...Object.keys(pkg.dependencies || {}), 15 | ], 16 | output: [ 17 | { 18 | dir: "./dist", 19 | format: "esm", 20 | sourcemap: true, 21 | exports: "named", 22 | preserveModules: true, 23 | preserveModulesRoot: "src", 24 | entryFileNames: "[name].mjs", 25 | }, 26 | { 27 | dir: "./dist", 28 | format: "esm", 29 | sourcemap: true, 30 | exports: "named", 31 | preserveModules: true, 32 | preserveModulesRoot: "src", 33 | entryFileNames: "[name].esm.js", 34 | }, 35 | { 36 | dir: "./dist", 37 | format: "cjs", 38 | sourcemap: true, 39 | exports: "named", 40 | preserveModules: true, 41 | preserveModulesRoot: "src", 42 | entryFileNames: "[name].js", 43 | }, 44 | ], 45 | plugins: [typescript(), commonjs({ exclude: "node_modules" })], 46 | }; 47 | -------------------------------------------------------------------------------- /packages/devtools-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@yornaath/batshit-devtools-react", 3 | "version": "0.8.1", 4 | "description": "A batch manager that will deduplicate and batch requests for a certain data type made within a window.", 5 | "author": { 6 | "name": "Jørn Andre", 7 | "url": "https://github.com/yornaath" 8 | }, 9 | "license": "MIT", 10 | "publishConfig": { 11 | "access": "public" 12 | }, 13 | "main": "./dist/index.js", 14 | "module": "./dist/index.esm.js", 15 | "types": "./dist/index.d.ts", 16 | "exports": { 17 | ".": { 18 | "types": "./dist/index.d.ts", 19 | "import": "./dist/index.mjs", 20 | "default": "./dist/index.js" 21 | }, 22 | "./package.json": "./package.json" 23 | }, 24 | "files": [ 25 | "dist/*", 26 | "src" 27 | ], 28 | "repository": { 29 | "type": "git", 30 | "url": "https://github.com/yornaath" 31 | }, 32 | "packageManager": "yarn@3.3.1", 33 | "scripts": { 34 | "build": "rimraf ./dist && rollup -c && tsc" 35 | }, 36 | "dependencies": { 37 | "@yornaath/batshit": "*", 38 | "@yornaath/batshit-devtools": "*", 39 | "lodash-es": "^4.17.21" 40 | }, 41 | "peerDependencies": { 42 | "react": "*" 43 | }, 44 | "devDependencies": { 45 | "@types/lodash-es": "^4.17.6", 46 | "@types/react": "^18.0.26", 47 | "react": "^18.2.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/example/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/batshit/tests/mock.ts: -------------------------------------------------------------------------------- 1 | import { setTimeout } from "node:timers/promises"; 2 | export type User = { 3 | id: number; 4 | name: string; 5 | }; 6 | 7 | export const users: User[] = [ 8 | { id: 1, name: "Bob" }, 9 | { id: 2, name: "Alice" }, 10 | { id: 3, name: "Sally" }, 11 | { id: 4, name: "John" }, 12 | { id: 5, name: "Tim" }, 13 | ]; 14 | 15 | export type Post = { 16 | id: number; 17 | title: string; 18 | authorId: number; 19 | }; 20 | 21 | export const posts: Post[] = [ 22 | { id: 1, title: "Hello", authorId: 1 }, 23 | { id: 2, title: "World", authorId: 1 }, 24 | { id: 3, title: "Hello", authorId: 2 }, 25 | { id: 4, title: "World", authorId: 2 }, 26 | { id: 5, title: "Hello", authorId: 3 }, 27 | { id: 6, title: "World", authorId: 3 }, 28 | ]; 29 | 30 | export const usersByIds = async (ids: number[]) => { 31 | return users.filter((item) => ids.includes(item.id)); 32 | }; 33 | 34 | export const usersByIdsAsync = async (ids: number[], delay: number, abortSignal: AbortSignal) => { 35 | await setTimeout(delay); 36 | if (abortSignal.aborted) { 37 | throw new Error("Aborted"); 38 | } 39 | return users.filter((item) => ids.includes(item.id)); 40 | }; 41 | 42 | export const postsByAuthorId = async (authorIds: number[]) => { 43 | return Object.values(posts).filter((item) => 44 | authorIds.includes(item.authorId) 45 | ); 46 | }; 47 | 48 | 49 | export let bigUserList: User[] = []; 50 | 51 | export const BIG_USER_LIST_LENGTH = 30000; 52 | 53 | for (let i = 1; i <= BIG_USER_LIST_LENGTH; i++) { 54 | bigUserList.push({ id: i, name: `User ${i}` }); 55 | } 56 | 57 | export const bigUserById = async (ids: number[]) => { 58 | return bigUserList.filter((item) => ids.includes(item.id)); 59 | }; -------------------------------------------------------------------------------- /packages/devtools/src/state.ts: -------------------------------------------------------------------------------- 1 | import { BatshitEvent } from "./events"; 2 | 3 | export type BatshitDevtoolsState = { 4 | [batcher: string]: BatcherState; 5 | }; 6 | 7 | export type BatcherState = { 8 | sequences: { 9 | [seq: number]: SequenceState; 10 | }; 11 | }; 12 | 13 | export type SequenceState = { 14 | batch: Q[]; 15 | fetching: boolean; 16 | data: T; 17 | error: Error | null; 18 | }; 19 | 20 | export const reduce = ( 21 | events: BatshitEvent[], 22 | initialState: BatshitDevtoolsState = {} 23 | ): BatshitDevtoolsState => { 24 | return events.reduce( 25 | (state: BatshitDevtoolsState, event: BatshitEvent) => { 26 | return { 27 | ...state, 28 | [event.name]: { 29 | ...state?.[event.name], 30 | sequences: { 31 | ...state?.[event.name]?.sequences, 32 | [event.seq]: { 33 | ...state?.[event.name]?.sequences[event.seq], 34 | batch: 35 | event.type === "queue" 36 | ? event.batch 37 | : state?.[event.name]?.sequences[event.seq]?.batch, 38 | data: 39 | event.type === "data" 40 | ? event.data 41 | : state?.[event.name]?.sequences[event.seq]?.data, 42 | error: 43 | event.type === "error" 44 | ? event.error 45 | : state?.[event.name]?.sequences[event.seq]?.error, 46 | fetching: 47 | event.type === "fetch" 48 | ? true 49 | : event.type === "data" || event.type === "error" 50 | ? false 51 | : state?.[event.name]?.sequences[event.seq]?.fetching, 52 | }, 53 | }, 54 | }, 55 | }; 56 | }, 57 | initialState 58 | ); 59 | }; 60 | -------------------------------------------------------------------------------- /packages/devtools-react/src/components/Batcher.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { Sequence } from "./Sequence"; 3 | import { BatcherState } from "@yornaath/batshit-devtools"; 4 | 5 | export const Batcher = (props: { 6 | name: string; 7 | state: BatcherState; 8 | }) => { 9 | const [expanded, setExpanded] = useState(true); 10 | 11 | const latestSeqNumber = Object.keys(props.state.sequences) 12 | .sort() 13 | .reverse()[0]; 14 | const latest = props.state.sequences[Number(latestSeqNumber)]; 15 | 16 | return ( 17 |
24 |

33 |
41 | {props.name} 42 |
58 |
59 |
setExpanded(!expanded)} 62 | > 63 | ▼ 64 |
65 |

66 | {expanded 67 | ? Object.entries(props.state.sequences).map(([seq, seqState]) => ( 68 | 69 | )) 70 | : null} 71 |
72 | ); 73 | }; 74 | -------------------------------------------------------------------------------- /packages/example/src/components/Market.tsx: -------------------------------------------------------------------------------- 1 | import * as Ztg from "@zeitgeistpm/indexer"; 2 | import { useState } from "react"; 3 | import { useAssetsForPool } from "../hooks/useAssetsForPool"; 4 | import { useMarketPool } from "../hooks/useMarketPool"; 5 | 6 | export const Market = (props: { market: Ztg.FullMarketFragment }) => { 7 | const [shouldFetch, setShouldFetch] = useState(false); 8 | 9 | const { 10 | data: pool, 11 | isFetched: poolIsFetched, 12 | isFetching: poolIsFetching, 13 | } = useMarketPool(props.market, { enabled: shouldFetch }); 14 | 15 | const { 16 | data: assets, 17 | isFetched: assetsIsFetched, 18 | isFetching: assetsIsFetching, 19 | } = useAssetsForPool(pool); 20 | 21 | const onClickFetchPool = () => { 22 | setShouldFetch(true); 23 | }; 24 | 25 | const isFetched = poolIsFetched && assetsIsFetched; 26 | const isFetching = poolIsFetching || assetsIsFetching; 27 | 28 | return ( 29 |
37 |
{props.market.slug}
38 | 45 | {(pool || isFetching) && ( 46 |
55 | )} 56 | {pool && ( 57 |
58 | {Number( 59 | pool.account.balances.reduce((acc, balance) => { 60 | return acc + Number(balance.balance); 61 | }, 0) / 62 | 10 ** 10 63 | ).toFixed(2)}{" "} 64 | VOL 65 | {assets && ","} 66 |
67 | )} 68 | {assets && ( 69 |
70 | {assets.length} assets 71 |
72 | )} 73 |
74 | ); 75 | }; 76 | -------------------------------------------------------------------------------- /packages/devtools/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @yornaath/batshit 2 | 3 | ## 1.7.1 4 | 5 | ### Patch Changes 6 | 7 | - keyResolver accepts T extends ReadonlyArray 8 | 9 | ## 1.7.0 10 | 11 | ### Minor Changes 12 | 13 | - Just return null if key or index resolvers cant find item, to make it more out of the box compatible with react-query 14 | 15 | ## 1.6.0 16 | 17 | ### Minor Changes 18 | 19 | - - Scheduler function now takes current batch size as third parameter* Added helper: `windowedFiniteBatchScheduler` to limit within a window or max batch size.* Added helper: `maxBatchSizeScheduler` to make a batch that waits until batch size is met. 20 | 21 | ## 1.5.0 22 | 23 | ### Minor Changes 24 | 25 | - Can now resolve record responses 26 | 27 | ## 1.4.1 28 | 29 | ### Patch Changes 30 | 31 | - Provide getServerSnapshot for devtools state 32 | 33 | ## 1.4.0 34 | 35 | ### Minor Changes 36 | 37 | - yes 38 | 39 | ## 1.3.0 40 | 41 | ### Minor Changes 42 | 43 | - 2980218: Yes 44 | 45 | ## 1.2.1 46 | 47 | ### Patch Changes 48 | 49 | - b582550: Versions 50 | 51 | ## 1.2.0 52 | 53 | ### Minor Changes 54 | 55 | - 681664c: Bump 56 | 57 | ## 1.1.0 58 | 59 | ### Minor Changes 60 | 61 | - ab250a9: Release candidate 1 62 | 63 | ## 1.0.0 64 | 65 | ### Minor Changes 66 | 67 | - eee2a1d: Devtools added. 68 | 69 | ### Patch Changes 70 | 71 | - Updated dependencies [eee2a1d] 72 | - @yornaath/batshit@0.3.0 73 | 74 | ## 0.2.11 75 | 76 | ### Patch Changes 77 | 78 | - 6e194e3: Added .esm.js files to package json module exports. 79 | 80 | ## 0.2.10 81 | 82 | ### Patch Changes 83 | 84 | - e1808bc: Rename .cjs files to .js 85 | 86 | ## 0.2.9 87 | 88 | ### Patch Changes 89 | 90 | - d8c7f25: Modified exports fields in package json. 91 | 92 | ## 0.2.8 93 | 94 | ### Patch Changes 95 | 96 | - 04ce3a0: Remove exports 97 | 98 | ## 0.2.6 99 | 100 | ### Patch Changes 101 | 102 | - 02667ce: Remove exports. 103 | 104 | ## 0.2.5 105 | 106 | ### Patch Changes 107 | 108 | - ef390e3: Readme updates. 109 | 110 | ## 0.2.4 111 | 112 | ### Patch Changes 113 | 114 | - 49103ee: Readme update. 115 | 116 | ## 0.2.3 117 | 118 | ### Patch Changes 119 | 120 | - 889fb11: Readme changes. 121 | 122 | ## 0.2.2 123 | 124 | ### Patch Changes 125 | 126 | - dd23d24: Added readme 127 | 128 | ## 0.2.1 129 | 130 | ### Patch Changes 131 | 132 | - 6a3b251: Exports fixes. 133 | 134 | ## 0.2.0 135 | 136 | ### Minor Changes 137 | 138 | - f79bd5a: Added custom resolvers. 139 | -------------------------------------------------------------------------------- /packages/devtools-react/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect } from "react"; 2 | import { Batcher } from "./components/Batcher"; 3 | import { useDevtoolsState } from "./hooks/useDevtoolsState"; 4 | import { useLocalState } from "./hooks/useLocalState"; 5 | import { styles } from "./styles"; 6 | 7 | export const BatshitDevtools = (props: { defaultOpen?: boolean }) => { 8 | const state = useDevtoolsState(); 9 | 10 | const [open, setOpen] = useLocalState( 11 | "batshit-devtools-open", 12 | props.defaultOpen 13 | ); 14 | 15 | useLayoutEffect(() => { 16 | let style = document.createElement("style"); 17 | style.textContent = styles; 18 | document.head.append(style); 19 | }, []); 20 | 21 | return !open ? ( 22 |
setOpen(true)} 36 | > 37 | 🦇💩 38 |
39 | ) : ( 40 |
57 |
67 |
@yornaath/batshit - DEVTOOLS 🦇💩
68 |
setOpen(false)} 71 | > 72 | ▼ 73 |
74 |
75 |
82 | {Object.entries(state).map(([name, batcherState]) => ( 83 | 84 | ))} 85 |
86 |
87 | ); 88 | }; 89 | 90 | export default BatshitDevtools; 91 | -------------------------------------------------------------------------------- /packages/devtools/src/devtools.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BatshitEvent, 3 | CreateEvent, 4 | DataEvent, 5 | ErrorEvent, 6 | FetchEvent, 7 | QueueEvent, 8 | } from "./events"; 9 | 10 | export type Devtools = { 11 | /** 12 | * Creates a new devtools listener for a batcher by its name. 13 | * 14 | * @param display string - the display name of the batcher 15 | * @returns DevtoolsListener 16 | */ 17 | for: (display: string) => DevtoolsListener; 18 | }; 19 | 20 | /** 21 | * Declare the global devtools variable. 22 | */ 23 | declare global { 24 | var __BATSHIT_DEVTOOLS__: Devtools | undefined; 25 | } 26 | 27 | /** 28 | * A devtools listener for a particualr batcher that picks up the relevant events. 29 | */ 30 | export type DevtoolsListener = { 31 | create: (_event: { seq: number }) => void; 32 | queue: (_event: { 33 | seq: number; 34 | query: Q; 35 | batch: Q[]; 36 | start: number; 37 | latest: number; 38 | scheduled: number | "immediate" | "never"; 39 | }) => void; 40 | fetch: (_event: { seq: number; batch: Q[] }) => void; 41 | data: (_event: { seq: number; data: T }) => void; 42 | error: (_event: { seq: number; error: Error }) => void; 43 | }; 44 | 45 | /** 46 | * Injects devtools into the global scope. 47 | * 48 | * @param onEvent (event: BatshitEvent) => void - the event listener 49 | * @returns void 50 | */ 51 | export const injectDevtools = ( 52 | onEvent: (event: BatshitEvent) => void 53 | ) => { 54 | globalThis.__BATSHIT_DEVTOOLS__ = createDevtools(onEvent); 55 | }; 56 | 57 | /** 58 | * Creates devtools. 59 | * 60 | * @param onEvent (event: BatshitEvent) => void - the event listener 61 | * @returns Devtools 62 | */ 63 | export const createDevtools = ( 64 | onEvent: (event: BatshitEvent) => void 65 | ): Devtools => { 66 | return { 67 | for: (name: string) => ({ 68 | create: (_event) => { 69 | const event: CreateEvent = { ..._event, name, type: "create" }; 70 | onEvent(event); 71 | }, 72 | queue: (_event) => { 73 | const event: QueueEvent = { 74 | ..._event, 75 | name: name, 76 | type: "queue", 77 | }; 78 | onEvent(event); 79 | }, 80 | fetch: (_event) => { 81 | const event: FetchEvent = { ..._event, name, type: "fetch" }; 82 | onEvent(event); 83 | }, 84 | data: (_event) => { 85 | const event: DataEvent = { ..._event, name, type: "data" }; 86 | onEvent(event); 87 | }, 88 | error: (_event) => { 89 | const event: ErrorEvent = { ..._event, name, type: "error" }; 90 | onEvent(event); 91 | }, 92 | }), 93 | }; 94 | }; 95 | -------------------------------------------------------------------------------- /packages/devtools-react/src/components/Sequence.tsx: -------------------------------------------------------------------------------- 1 | import { SequenceState } from "@yornaath/batshit-devtools"; 2 | import React from "react"; 3 | 4 | export const Sequence = (props: { 5 | seq: string; 6 | sequence: SequenceState; 7 | }) => { 8 | const [expanded, setExpanded] = React.useState(false); 9 | const batchStr = JSON.stringify(props.sequence.batch); 10 | 11 | return ( 12 |
13 |
22 |
39 | {props.seq} 40 |
41 |
47 | {props.sequence?.batch?.length} 48 |
49 |
50 | {batchStr?.substring(0, 80)} 51 | {batchStr?.length > 80 ? "..." : ""} 52 |
53 |
54 | {(props.sequence.data || props.sequence.error) && ( 55 |
setExpanded(!expanded)} 57 | style={{ 58 | background: props.sequence.data ? "green" : "red", 59 | padding: "3px 6px", 60 | borderRadius: "4px", 61 | cursor: "pointer", 62 | }} 63 | > 64 | {props.sequence.data ? "data" : "error"} ▼ 65 |
66 | )} 67 |
68 |
69 | {expanded && ( 70 |
74 | {props.sequence.data ? ( 75 |
76 |
77 |
{JSON.stringify(props.sequence.data, null, 2)}
78 |
79 |
80 | ) : props.sequence.error ? ( 81 |
82 | {props.sequence.error.message} 83 |
84 | ) : null} 85 |
86 | )} 87 |
88 | ); 89 | }; 90 | -------------------------------------------------------------------------------- /packages/devtools-react/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @yornaath/batshit-devtools-react 2 | 3 | ## 0.8.1 4 | 5 | ### Patch Changes 6 | 7 | - keyResolver accepts T extends ReadonlyArray 8 | - Updated dependencies 9 | - @yornaath/batshit@0.10.1 10 | - @yornaath/batshit-devtools@1.7.1 11 | 12 | ## 0.8.0 13 | 14 | ### Minor Changes 15 | 16 | - Just return null if key or index resolvers cant find item, to make it more out of the box compatible with react-query 17 | 18 | ### Patch Changes 19 | 20 | - Updated dependencies 21 | - @yornaath/batshit@0.10.0 22 | - @yornaath/batshit-devtools@1.7.0 23 | 24 | ## 0.7.0 25 | 26 | ### Minor Changes 27 | 28 | - - Scheduler function now takes current batch size as third parameter* Added helper: `windowedFiniteBatchScheduler` to limit within a window or max batch size.* Added helper: `maxBatchSizeScheduler` to make a batch that waits until batch size is met. 29 | 30 | ### Patch Changes 31 | 32 | - Updated dependencies 33 | - @yornaath/batshit@0.9.0 34 | - @yornaath/batshit-devtools@1.6.0 35 | 36 | ## 0.6.0 37 | 38 | ### Minor Changes 39 | 40 | - Can now resolve record responses 41 | 42 | ### Patch Changes 43 | 44 | - Updated dependencies 45 | - @yornaath/batshit@0.8.0 46 | - @yornaath/batshit-devtools@1.5.0 47 | 48 | ## 0.5.4 49 | 50 | ### Patch Changes 51 | 52 | - Fix 53 | 54 | ## 0.5.3 55 | 56 | ### Patch Changes 57 | 58 | - Show number of items in batch. 59 | 60 | ## 0.5.2 61 | 62 | ### Patch Changes 63 | 64 | - Fix getServerSnapshot 65 | 66 | ## 0.5.1 67 | 68 | ### Patch Changes 69 | 70 | - Provide getServerSnapshot for devtools state 71 | - Updated dependencies 72 | - @yornaath/batshit-devtools@1.4.1 73 | - @yornaath/batshit@0.7.1 74 | 75 | ## 0.5.0 76 | 77 | ### Minor Changes 78 | 79 | - yes 80 | 81 | ### Patch Changes 82 | 83 | - Updated dependencies 84 | - @yornaath/batshit@0.7.0 85 | - @yornaath/batshit-devtools@1.4.0 86 | 87 | ## 0.4.0 88 | 89 | ### Minor Changes 90 | 91 | - 2980218: Yes 92 | 93 | ### Patch Changes 94 | 95 | - Updated dependencies [2980218] 96 | - @yornaath/batshit@0.6.0 97 | - @yornaath/batshit-devtools@1.3.0 98 | 99 | ## 0.3.1 100 | 101 | ### Patch Changes 102 | 103 | - b582550: Versions 104 | - Updated dependencies [b582550] 105 | - @yornaath/batshit@0.5.1 106 | - @yornaath/batshit-devtools@1.2.1 107 | 108 | ## 0.3.0 109 | 110 | ### Minor Changes 111 | 112 | - 681664c: Bump 113 | 114 | ### Patch Changes 115 | 116 | - Updated dependencies [681664c] 117 | - @yornaath/batshit@0.5.0 118 | - @yornaath/batshit-devtools@1.2.0 119 | 120 | ## 0.2.0 121 | 122 | ### Minor Changes 123 | 124 | - ab250a9: Release candidate 1 125 | 126 | ### Patch Changes 127 | 128 | - Updated dependencies [ab250a9] 129 | - @yornaath/batshit@0.4.0 130 | - @yornaath/batshit-devtools@1.1.0 131 | 132 | ## 0.1.0 133 | 134 | ### Minor Changes 135 | 136 | - eee2a1d: Devtools added. 137 | 138 | ### Patch Changes 139 | 140 | - Updated dependencies [eee2a1d] 141 | - @yornaath/batshit@0.3.0 142 | -------------------------------------------------------------------------------- /packages/batshit/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @yornaath/batshit 2 | 3 | ## 0.12.0 4 | 5 | ### Minor Changes 6 | 7 | - Added batcher.abort() and sending the AbortSignal to the fetcher function. 8 | 9 | ## 0.11.3 10 | 11 | ### Patch Changes 12 | 13 | - Added batcher.next() method for early batch execution 14 | 15 | ## 0.11.2 16 | 17 | ### Patch Changes 18 | 19 | - Added optional indexing to the key resolver 20 | 21 | ## 0.11.1 22 | 23 | ### Patch Changes 24 | 25 | - Fix types 26 | 27 | ## 0.11.0 28 | 29 | ### Minor Changes 30 | 31 | - Key resolver can now return null 32 | 33 | ## 0.10.1 34 | 35 | ### Patch Changes 36 | 37 | - keyResolver accepts T extends ReadonlyArray 38 | - Updated dependencies 39 | - @yornaath/batshit-devtools@1.7.1 40 | 41 | ## 0.10.0 42 | 43 | ### Minor Changes 44 | 45 | - Just return null if key or index resolvers cant find item, to make it more out of the box compatible with react-query 46 | 47 | ### Patch Changes 48 | 49 | - Updated dependencies 50 | - @yornaath/batshit-devtools@1.7.0 51 | 52 | ## 0.9.0 53 | 54 | ### Minor Changes 55 | 56 | - - Scheduler function now takes current batch size as third parameter* Added helper: `windowedFiniteBatchScheduler` to limit within a window or max batch size.* Added helper: `maxBatchSizeScheduler` to make a batch that waits until batch size is met. 57 | 58 | ### Patch Changes 59 | 60 | - Updated dependencies 61 | - @yornaath/batshit-devtools@1.6.0 62 | 63 | ## 0.8.0 64 | 65 | ### Minor Changes 66 | 67 | - Can now resolve record responses 68 | 69 | ### Patch Changes 70 | 71 | - Updated dependencies 72 | - @yornaath/batshit-devtools@1.5.0 73 | 74 | ## 0.7.1 75 | 76 | ### Patch Changes 77 | 78 | - Provide getServerSnapshot for devtools state 79 | - Updated dependencies 80 | - @yornaath/batshit-devtools@1.4.1 81 | 82 | ## 0.7.0 83 | 84 | ### Minor Changes 85 | 86 | - yes 87 | 88 | ### Patch Changes 89 | 90 | - Updated dependencies 91 | - @yornaath/batshit-devtools@1.4.0 92 | 93 | ## 0.6.0 94 | 95 | ### Minor Changes 96 | 97 | - 2980218: Yes 98 | 99 | ### Patch Changes 100 | 101 | - Updated dependencies [2980218] 102 | - @yornaath/batshit-devtools@1.3.0 103 | 104 | ## 0.5.1 105 | 106 | ### Patch Changes 107 | 108 | - b582550: Versions 109 | - Updated dependencies [b582550] 110 | - @yornaath/batshit-devtools@1.2.1 111 | 112 | ## 0.5.0 113 | 114 | ### Minor Changes 115 | 116 | - 681664c: Bump 117 | 118 | ### Patch Changes 119 | 120 | - Updated dependencies [681664c] 121 | - @yornaath/batshit-devtools@1.2.0 122 | 123 | ## 0.4.0 124 | 125 | ### Minor Changes 126 | 127 | - ab250a9: Release candidate 1 128 | 129 | ### Patch Changes 130 | 131 | - Updated dependencies [ab250a9] 132 | - @yornaath/batshit-devtools@1.1.0 133 | 134 | ## 0.3.0 135 | 136 | ### Minor Changes 137 | 138 | - eee2a1d: Devtools added. 139 | 140 | ## 0.2.11 141 | 142 | ### Patch Changes 143 | 144 | - 6e194e3: Added .esm.js files to package json module exports. 145 | 146 | ## 0.2.10 147 | 148 | ### Patch Changes 149 | 150 | - e1808bc: Rename .cjs files to .js 151 | 152 | ## 0.2.9 153 | 154 | ### Patch Changes 155 | 156 | - d8c7f25: Modified exports fields in package json. 157 | 158 | ## 0.2.8 159 | 160 | ### Patch Changes 161 | 162 | - 04ce3a0: Remove exports 163 | 164 | ## 0.2.6 165 | 166 | ### Patch Changes 167 | 168 | - 02667ce: Remove exports. 169 | 170 | ## 0.2.5 171 | 172 | ### Patch Changes 173 | 174 | - ef390e3: Readme updates. 175 | 176 | ## 0.2.4 177 | 178 | ### Patch Changes 179 | 180 | - 49103ee: Readme update. 181 | 182 | ## 0.2.3 183 | 184 | ### Patch Changes 185 | 186 | - 889fb11: Readme changes. 187 | 188 | ## 0.2.2 189 | 190 | ### Patch Changes 191 | 192 | - dd23d24: Added readme 193 | 194 | ## 0.2.1 195 | 196 | ### Patch Changes 197 | 198 | - 6a3b251: Exports fixes. 199 | 200 | ## 0.2.0 201 | 202 | ### Minor Changes 203 | 204 | - f79bd5a: Added custom resolvers. 205 | -------------------------------------------------------------------------------- /packages/devtools/README.md: -------------------------------------------------------------------------------- 1 | # @yornaath/batshit [![CI](https://github.com/yornaath/batshit/actions/workflows/ci.yml/badge.svg)](https://github.com/yornaath/batshit/actions/workflows/ci.yml) 2 | 3 | A batch manager that will deduplicate and batch requests for a given data type made within a window of time (or other custom scheduling). Useful to batch requests made from multiple react components that uses react-query or do batch processing of accumulated tasks. 4 | 5 | ### Codesandbox example 6 | Here is a codesanbox example using react, typescript, vite and the zeitgeist prediction-markets indexer api. 7 | It fetches markets up front and then batches all liquidity pool fetches made from the individual components into one request. 8 | 9 | [Codesandbox](https://codesandbox.io/s/yornaath-batshit-example-8f8q3w?file=/src/App.tsx) 10 | 11 | ## Quickstart 12 | 13 | Here we are creating a simple batcher that will batch all fetches made within a window of 10 ms into one request. 14 | 15 | ```ts 16 | import { Batcher, keyResolver, windowScheduler } from "@yornaath/batshit"; 17 | 18 | let fetchCalls = 0; 19 | 20 | type User = { id: number; name: string }; 21 | 22 | const users = Batcher({ 23 | fetcher: async (ids) => { 24 | fetchCalls++; 25 | return client.users.where({ 26 | userId_in: ids, 27 | }); 28 | }, 29 | resolver: keyResolver("id"), 30 | scheduler: windowScheduler(10), // Default and can be omitted. 31 | }); 32 | 33 | /** 34 | * Requests will be batched to one call since they are done within the same time window of 10 ms. 35 | */ 36 | const bob = users.fetch(1); 37 | const alice = users.fetch(2); 38 | 39 | const bobUndtAlice = await Promise.all([bob, alice]); 40 | 41 | fetchCalls === 1; 42 | 43 | await delay(100); 44 | 45 | /** 46 | * New Requests will be batched in a another call since not within the timeframe. 47 | */ 48 | const joe = users.fetch(3); 49 | const margareth = users.fetch(4); 50 | 51 | const joeUndtMargareth = await Promise.all([joe, margareth]); 52 | 53 | fetchCalls === 2; 54 | ``` 55 | 56 | ## React(query) Example 57 | 58 | Here we are also creating a simple batcher that will batch all fetches made within a window of 10 ms into one request since all UserItem components will be rendered and most likely make their queries within the same window of 10 ms. 59 | 60 | ```ts 61 | import { useQuery } from "react-query"; 62 | import { Batcher, windowScheduler } from "@yornaath/batshit"; 63 | 64 | const users = Batcher({ 65 | fetcher: async (ids) => { 66 | return client.users.where({ 67 | userId_in: ids, 68 | }); 69 | }, 70 | resolver: keyResolver("id"), 71 | scheduler: windowScheduler(10), 72 | }); 73 | 74 | const useUser = (id: number) => { 75 | return useQuery(["users", id], async () => { 76 | return users.fetch(id); 77 | }); 78 | }; 79 | 80 | const UserDetails = (props: { userId: number }) => { 81 | const { isFetching, data } = useUser(props.userId); 82 | return ( 83 | <> 84 | {isFetching ? ( 85 |
Loading user {props.userId}
86 | ) : ( 87 |
User: {data.name}
88 | )} 89 | 90 | ); 91 | }; 92 | 93 | /** 94 | * Since all user details items are rendered within the window there will only be one request made. 95 | */ 96 | const UserList = () => { 97 | const userIds = [1, 2, 3, 4]; 98 | return ( 99 | <> 100 | {userIds.map((id) => ( 101 | 102 | ))} 103 | 104 | ); 105 | }; 106 | ``` 107 | 108 | # Custom Batch Resolver 109 | 110 | This batcher will fetch all posts for multiple users in one request and resolve the correct list of posts for the discrete queries. 111 | 112 | ```ts 113 | const userposts = create({ 114 | fetcher: async (queries) => { 115 | return api.posts.where({ 116 | authorId_in: queries.map((q) => q.authorId), 117 | }); 118 | }, 119 | scheduler: windowScheduler(10), 120 | resolver: (posts, query) => 121 | posts.filter((post) => post.authorId === query.authorId), 122 | }); 123 | 124 | const [alicesPosts, bobsPost] = await Promise.all([ 125 | userposts.fetch({authorId: 1}) 126 | userposts.fetch({authorId: 2}) 127 | ]); 128 | ``` 129 | -------------------------------------------------------------------------------- /packages/batshit/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { DevtoolsListener } from "@yornaath/batshit-devtools"; 2 | import { Deferred, deferred } from "./deferred"; 3 | /** 4 | * Batcher. 5 | * A batch manager that will batch requests for a certain data type within a given window. 6 | * 7 | * @generic T - The type of the data. 8 | * @generic Q - item query type 9 | * @generic C - the context of the batcher passed to the fetcher function 10 | */ 11 | export type Batcher = { 12 | /** 13 | * Schedule a get request for a query. 14 | * 15 | * @generic T - The type of the data. 16 | * @generic Q - item query type 17 | * @param query Q 18 | * @returns Promise 19 | */ 20 | fetch: (query: Q) => Promise; 21 | 22 | /** 23 | * Execute the next batch. 24 | * This is useful to manually execute the next batch when the scheduler is not immediate. 25 | * @returns void 26 | */ 27 | next: () => Promise; 28 | 29 | /** 30 | * Abort the current batch. 31 | * @returns void 32 | */ 33 | abort: () => void; 34 | }; 35 | 36 | /** 37 | * Config needed to create a Batcher 38 | * 39 | * @generic T - The type of the data. 40 | * @generic Q - item query type 41 | * @generic C - the context of the batcher passed to the fetcher function 42 | */ 43 | export type BatcherConfig = { 44 | /** 45 | * The function that makes the batched request for the current batch queries 46 | * 47 | * @param queries Q[] 48 | * @returns Promise Promise; 51 | /** 52 | * The scheduling function. 53 | */ 54 | scheduler?: BatcherScheduler; 55 | /** 56 | * Correlate an item by its query. Used to extract the correct value from the batch of items 57 | * to the correct query used to fetch it. 58 | * 59 | * @param query Q 60 | * @returns string 61 | */ 62 | resolver: Resolver; 63 | /** 64 | * Display name of the batcher. Used for debugging and devtools. 65 | */ 66 | name?: string; 67 | }; 68 | 69 | export type Resolver = { 70 | (items: T, query: Q): R 71 | index?: Record; 72 | } 73 | 74 | /** 75 | * A function to schedule batch execution timing 76 | */ 77 | export type BatcherScheduler = { 78 | /** 79 | * A scheduler function. 80 | * 81 | * @param start number - time stamp when the current batch started queuing fetches. 82 | * @param latest number - time stamp of the latest queued fetch. 83 | * @returns number - the number of ms to wait from latest queued fetch until executing batchh fetch call. 84 | */ 85 | (start: number, latest: number, batchSize: number): Schedule; 86 | }; 87 | 88 | /** 89 | * A schedule for when to execute a batched fetch call. 90 | */ 91 | export type Schedule = number | "immediate" | "never"; 92 | 93 | export type BatcherMemory = { 94 | seq: number; 95 | batch: Set; 96 | currentRequest: Deferred; 97 | timer?: NodeJS.Timeout | undefined; 98 | start?: number | null; 99 | latest?: number | null; 100 | abortController: AbortController; 101 | }; 102 | 103 | /** 104 | * Create a batch manager for a given collection of a data type. 105 | * Will batch all .get calls given inside a scheduled time window into a singel request. 106 | * 107 | * @generic T - The type of the data. 108 | * @generic Q - item query type 109 | * @generic C - the context of the batcher passed to the fetcher function 110 | * @param config BatcherConfig 111 | * @returns Batcher 112 | */ 113 | export const create = ( 114 | config: BatcherConfig, 115 | memory?: BatcherMemory 116 | ): Batcher> => { 117 | const name = config.name ?? `batcher:${Math.random().toString(16).slice(2)})`; 118 | 119 | const scheduler: BatcherScheduler = config.scheduler ?? windowScheduler(10); 120 | 121 | const devtools: DevtoolsListener | undefined = 122 | globalThis.__BATSHIT_DEVTOOLS__?.for(name); 123 | 124 | const mem: BatcherMemory = memory ?? { 125 | seq: 0, 126 | batch: new Set(), 127 | currentRequest: deferred(), 128 | timer: undefined, 129 | start: null, 130 | latest: null, 131 | abortController: new AbortController(), 132 | }; 133 | 134 | devtools?.create({ seq: mem.seq }); 135 | 136 | const nextBatch = () => { 137 | mem.batch = new Set(); 138 | mem.currentRequest = deferred(); 139 | mem.timer = undefined; 140 | mem.start = null; 141 | mem.latest = null; 142 | mem.abortController = new AbortController(); 143 | }; 144 | 145 | const fetchBatch = () => { 146 | const currentSeq = mem.seq; 147 | 148 | const req = config.fetcher([...mem.batch], mem.abortController.signal); 149 | const currentRequest = mem.currentRequest; 150 | 151 | devtools?.fetch({ seq: currentSeq, batch: [...mem.batch] }); 152 | 153 | nextBatch(); 154 | 155 | req 156 | .then((data) => { 157 | devtools?.data({ seq: currentSeq, data }); 158 | currentRequest.resolve(data); 159 | }) 160 | .catch((error) => { 161 | devtools?.error({ seq: currentSeq, error }); 162 | currentRequest.reject(error); 163 | }); 164 | 165 | mem.seq++; 166 | 167 | return req; 168 | }; 169 | 170 | const fetch = (query: Q): Promise => { 171 | if (!mem.start) mem.start = Date.now(); 172 | mem.latest = Date.now(); 173 | 174 | mem.batch.add(query); 175 | clearTimeout(mem.timer); 176 | 177 | const scheduled = scheduler(mem.start, mem.latest, mem.batch.size); 178 | 179 | devtools?.queue({ 180 | seq: mem.seq, 181 | query, 182 | batch: [...mem.batch], 183 | scheduled, 184 | latest: mem.latest, 185 | start: mem.start, 186 | }); 187 | 188 | if (scheduled === "immediate") { 189 | const req = mem.currentRequest; 190 | fetchBatch(); 191 | return req.value.then((items) => config.resolver(items, query)); 192 | } else if (scheduled === "never") { 193 | return mem.currentRequest.value.then((items) => 194 | config.resolver(items, query) 195 | ); 196 | } else { 197 | mem.timer = setTimeout(fetchBatch, scheduled); 198 | return mem.currentRequest.value.then((items) => 199 | config.resolver(items, query) 200 | ); 201 | } 202 | }; 203 | 204 | const next = () => { 205 | return fetchBatch(); 206 | }; 207 | 208 | const abort = () => { 209 | mem.abortController.abort(); 210 | mem.currentRequest.reject(new DOMException("Aborted", "AbortError")); 211 | nextBatch() 212 | }; 213 | 214 | return { fetch, next, abort }; 215 | }; 216 | 217 | /** 218 | * Resolve by item field of items when response is an array. 219 | * Create a euquality check to check if the query matches a given key on the item data. 220 | * 221 | * @param key keyof T 222 | * @returns (item:T extends Array, query: Q) => A | null 223 | */ 224 | export const keyResolver = 225 | , Q, R = T extends ReadonlyArray ? A : never>( 226 | key: T extends ReadonlyArray ? keyof A : never, 227 | config?: { 228 | indexed?: boolean 229 | } 230 | ): Resolver => { 231 | const isIndexex = config?.indexed 232 | 233 | const resolver: Resolver = (items: T, query: Q): R => { 234 | if (isIndexex) { 235 | if (!resolver.index) { 236 | resolver.index = {}; 237 | for (let i = 0; i < items.length; i++) { 238 | const item = items[i]; 239 | const indexHash = item[key] as unknown as string; 240 | resolver.index[indexHash] = item; 241 | } 242 | } 243 | 244 | return (resolver.index[query as unknown as string] as R); 245 | } 246 | 247 | return items.find((item) => item[key] === query) ?? null 248 | } 249 | 250 | return resolver 251 | } 252 | 253 | /** 254 | * Resolve by record index when response is an object. 255 | * Create a euquality check to check if the query matches a given key on the item data. 256 | * 257 | * @returns (item:T extends Record<_, A>, query: Q) => A 258 | */ 259 | export const indexedResolver = 260 | , Q>() => 261 | (itemsIndex: T, query: Q) => 262 | itemsIndex[query] ?? null; 263 | 264 | /** 265 | * Give a window in ms where all queued fetched made within the window will be batched into 266 | * one singler batch fetch call. 267 | * 268 | * @param ms number 269 | * @returns BatcherScheduler 270 | */ 271 | export const windowScheduler: (ms: number) => BatcherScheduler = 272 | (ms) => (start, latest) => { 273 | const spent = latest - start; 274 | return ms - spent; 275 | }; 276 | 277 | /** 278 | * Give a buffer time in ms. Will give another buffer window when queueing a fetch. 279 | * 280 | * @param ms number 281 | * @returns BatcherScheduler 282 | */ 283 | export const bufferScheduler: (ms: number) => BatcherScheduler = (ms) => () => { 284 | return ms; 285 | }; 286 | 287 | /** 288 | * Same as windowScheduler, will batch calls made within a window of time OR when the max batch size is reached. 289 | * 290 | * @param config: {windowMs: number; maxBatchSize: number;} 291 | * @returns BatcherScheduler 292 | */ 293 | export const windowedFiniteBatchScheduler: (config: { 294 | windowMs: number; 295 | maxBatchSize: number; 296 | }) => BatcherScheduler = 297 | ({ windowMs, maxBatchSize }) => 298 | (start, latest, batchSize) => { 299 | if (batchSize >= maxBatchSize) return "immediate"; 300 | const spent = latest - start; 301 | return windowMs - spent; 302 | }; 303 | 304 | /** 305 | * Will batch calls when the max batch size is reached. 306 | * 307 | * @param config: {maxBatchSize: number;} 308 | * @returns BatcherScheduler 309 | */ 310 | export const maxBatchSizeScheduler: (config: { 311 | maxBatchSize: number; 312 | }) => BatcherScheduler = 313 | ({ maxBatchSize }) => 314 | (_start, _latest, batchSize) => { 315 | if (batchSize >= maxBatchSize) return "immediate"; 316 | return "never"; 317 | }; 318 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @yornaath/batshit [![CI](https://github.com/yornaath/batshit/actions/workflows/ci.yml/badge.svg)](https://github.com/yornaath/batshit/actions/workflows/ci.yml) 2 | 3 | A batch manager that will deduplicate and batch requests for a given data type made within a window of time (or other custom scheduling). Useful to batch requests made from multiple react components that uses react-query or do batch processing of accumulated tasks. 4 | 5 | ### Codesandbox example 6 | Here is a codesanbox example using react, typescript, vite and the zeitgeist prediction-markets indexer api. 7 | It fetches markets up front and then batches all liquidity pool fetches made from the individual components into one request. 8 | 9 | [Codesandbox](https://codesandbox.io/s/yornaath-batshit-example-8f8q3w?file=/src/App.tsx) 10 | 11 | ### Example with devtools 12 | Example using zeitgeist market and pool data with included devtools to inspect the batching process. 13 | The working live code for the example linked below can be found in [./packages/example](https://github.com/yornaath/batshit/tree/master/packages/example) 14 | 15 | [Vercel Example app](https://batshit-example.vercel.app/) 16 | 17 | ## Install 18 | ```bash 19 | yarn add @yornaath/batshit 20 | ``` 21 | 22 | ## Quickstart 23 | 24 | Here we are creating a simple batcher that will batch all fetches made within a window of 10 ms into one request. 25 | 26 | ```ts 27 | import { create, keyResolver, windowScheduler } from "@yornaath/batshit"; 28 | 29 | type User = { id: number; name: string }; 30 | 31 | const users = create({ 32 | fetcher: async (ids: number[]) => { 33 | return client.users.where({ 34 | id_in: ids, 35 | }); 36 | }, 37 | resolver: keyResolver("id"), 38 | scheduler: windowScheduler(10), // Default and can be omitted. 39 | }); 40 | 41 | /** 42 | * Requests will be batched to one call since they are done within the same time window of 10 ms. 43 | */ 44 | const bob = users.fetch(1); 45 | const alice = users.fetch(2); 46 | 47 | const bobUndtAlice = await Promise.all([bob, alice]); 48 | 49 | await delay(100); 50 | 51 | /** 52 | * New Requests will be batched in a another call since not within the first timeframe. 53 | */ 54 | const joe = users.fetch(3); 55 | const margareth = users.fetch(4); 56 | 57 | const joeUndtMargareth = await Promise.all([joe, margareth]); 58 | ``` 59 | 60 | ## React(query) Example 61 | 62 | Here we are also creating a simple batcher that will batch all fetches made within a window of 10 ms into one request. Since all items are rendered in one go their individual fetches will be batched into one request. 63 | 64 | **Note: a batcher for a group of items should only be created once. So creating them inside hooks wont work as intended.** 65 | 66 | ```ts 67 | import { useQuery } from "react-query"; 68 | import { create, windowScheduler } from "@yornaath/batshit"; 69 | 70 | const users = create({ 71 | fetcher: async (ids: number[]) => { 72 | return client.users.where({ 73 | userId_in: ids, 74 | }); 75 | }, 76 | resolver: keyResolver("id"), 77 | scheduler: windowScheduler(10), 78 | }); 79 | 80 | const useUser = (id: number) => { 81 | return useQuery(["users", id], async () => { 82 | return users.fetch(id); 83 | }); 84 | }; 85 | 86 | const UserDetails = (props: { userId: number }) => { 87 | const { isFetching, data } = useUser(props.userId); 88 | return ( 89 | <> 90 | {isFetching ? ( 91 |
Loading user {props.userId}
92 | ) : ( 93 |
User: {data.name}
94 | )} 95 | 96 | ); 97 | }; 98 | 99 | /** 100 | * Since all user details items are rendered within the window there will only be one request made. 101 | */ 102 | const UserList = () => { 103 | const userIds = [1, 2, 3, 4]; 104 | return ( 105 | <> 106 | {userIds.map((id) => ( 107 | 108 | ))} 109 | 110 | ); 111 | }; 112 | ``` 113 | 114 | ### Limit batch size 115 | 116 | We provide two helper functions for limiting the number of batched fetch calls. 117 | 118 | #### `windowedFiniteBatchScheduler` 119 | 120 | This will batch all calls made within a certain time frame UP to a certain max batch size before it starts a new batch 121 | 122 | ```ts 123 | const batcher = batshit.create({ 124 | ..., 125 | scheduler: windowedFiniteBatchScheduler({ 126 | windowMs: 10, 127 | maxBatchSize: 100, 128 | }), 129 | }); 130 | ``` 131 | 132 | #### `maxBatchSizeScheduler` 133 | 134 | Same as the one above, but will only wait indefinetly until the batch size is met. 135 | 136 | ```ts 137 | const batcher = batshit.create({ 138 | ..., 139 | scheduler: maxBatchSizeScheduler({ 140 | maxBatchSize: 100, 141 | }), 142 | }); 143 | ``` 144 | 145 | ### Fetching where response is an object of items 146 | 147 | In this example the response is an object/record with the id of the user as the key and the user object as the value. 148 | 149 | **Example:** 150 | ```json 151 | { 152 | "1": {"username": "bob"}, 153 | "2": {"username": "alice"} 154 | } 155 | ``` 156 | 157 | ```ts 158 | import * as batshit from "@yornaath/batshit"; 159 | 160 | const batcher = batshit.create({ 161 | fetcher: async (ids: string[]) => { 162 | const users: Record = await fetchUserRecords(ids) 163 | return users 164 | }, 165 | resolver: batshit.indexedResolver(), 166 | }); 167 | ``` 168 | 169 | ### Fetching with needed context 170 | 171 | If the batch fetcher needs some context like an sdk or client to make its fetching you can use a memoizer to make sure that you reuse a batcher for the given context in the hook calls. 172 | 173 | ```ts 174 | import { useQuery } from "@tanstack/react-query"; 175 | import { memoize } from "lodash-es"; 176 | import * as batshit from "@yornaath/batshit"; 177 | 178 | export const key = "markets"; 179 | 180 | const batcher = memoize((sdk: Sdk) => { 181 | return batshit.create({ 182 | name: key, 183 | fetcher: async (ids: number[]) => { 184 | const { markets } = await sdk.markets({ 185 | where: { 186 | marketId_in: ids, 187 | }, 188 | }); 189 | return markets; 190 | }, 191 | scheduler: batshit.windowScheduler(10), 192 | resolver: batshit.keyResolver("marketId"), 193 | }); 194 | }); 195 | 196 | export const useMarket = (marketId: number) => { 197 | const [sdk, id] = useSdk(); 198 | 199 | const query = useQuery( 200 | [id, key, marketId], 201 | async () => { 202 | if(sdk) { 203 | return batcher(sdk).fetch(marketId); 204 | } 205 | }, 206 | { 207 | enabled: Boolean(sdk), 208 | }, 209 | ); 210 | 211 | return query; 212 | }; 213 | ``` 214 | 215 | # Custom Batch Resolver 216 | 217 | This batcher will fetch all posts for multiple users in one request and resolve the correct list of posts for the discrete queries. 218 | 219 | ```ts 220 | const userposts = create({ 221 | fetcher: async (queries: { authorId: number }) => { 222 | return api.posts.where({ 223 | authorId_in: queries.map((q) => q.authorId), 224 | }); 225 | }, 226 | scheduler: windowScheduler(10), 227 | resolver: (posts, query) => 228 | posts.filter((post) => post.authorId === query.authorId), 229 | }); 230 | 231 | const [alicesPosts, bobsPost] = await Promise.all([ 232 | userposts.fetch({authorId: 1}) 233 | userposts.fetch({authorId: 2}) 234 | ]); 235 | ``` 236 | 237 | # batcher.next() - Early Execution 238 | 239 | Calling batcher.next() will execute the current batch early even if the scheduler hasnt finished. 240 | 241 | 242 | ```ts 243 | const batcher = create({ 244 | fetcher: async (ids: number[]) => { 245 | return mock.usersByIds(ids); 246 | }, 247 | resolver: keyResolver("id"), 248 | scheduler: windowScheduler(33), 249 | }); 250 | 251 | let all = Promise.all([batcher.fetch(1), batcher.fetch(2), batcher.fetch(3), batcher.fetch(4)]); 252 | 253 | bacher.next() 254 | 255 | const users = await all //current batch of users [1,2,3,4] will be fetched immediately 256 | ``` 257 | 258 | # Abort Signals - batcher.abort() 259 | 260 | You can abort the current batch with `batcher.abort()` 261 | 262 | Abort signals are sent down to fetcher that can be passed down to the underlying implementation. 263 | 264 | ```ts 265 | test("aborting", async () => { 266 | const batcher = create({ 267 | fetcher: async (ids: number[], signal: AbortSignal) => { 268 | return fetch(`/users?ids=${ids.join(",")}`, { signal }) 269 | }, 270 | resolver: keyResolver("id"), 271 | }); 272 | 273 | let all = Promise.all([batcher.fetch(1), batcher.fetch(2), batcher.fetch(3), batcher.fetch(4)]); 274 | 275 | setTimeout(() => { 276 | batcher.abort(); 277 | }, 5) 278 | 279 | const error = await all.catch((error) => error); 280 | 281 | expect(error).toBeInstanceOf(DOMException); 282 | expect(error.message).toBe("Aborted"); 283 | }) 284 | ``` 285 | 286 | # Indexed Keyresolver Performance 287 | 288 | If your batches are big arrays( > 30K items) and you use the keyresolver it can give you a performance boost to turn on indexing. 289 | 290 | __Any less than 30K items per batch and the performance gain is negligeble. But anywhere above 15K can be worth it.__ 291 | 292 | ```ts 293 | const batcherIndexed = create({ 294 | fetcher: async (ids: number[]) => { 295 | // returns > 30K items[] 296 | return mock.bigUserById(ids); 297 | }, 298 | resolver: keyResolver("id", { indexed: true }), 299 | scheduler: windowScheduler(1000), 300 | }); 301 | ``` 302 | 303 | # React Devtools 304 | 305 | Tools to debug and inspect the batching process can be found in the [@yornaath/batshit-devtools-react](https://www.npmjs.com/package/@yornaath/batshit-devtools-react) package. 306 | 307 | ```bash 308 | yarn add @yornaath/batshit-devtools @yornaath/batshit-devtools-react 309 | ``` 310 | 311 | ```ts 312 | import { create, keyResolver, windowScheduler } from "@yornaath/batshit"; 313 | import BatshitDevtools from "@yornaath/batshit-devtools-react"; 314 | 315 | const batcher = create({ 316 | fetcher: async (queries: number[]) => {...}, 317 | scheduler: windowScheduler(10), 318 | resolver: keyResolver("id"), 319 | name: "batcher:data" // used in the devtools to identify a particular batcher. 320 | }); 321 | 322 | const App = () => { 323 |
324 | 325 |
326 | } 327 | ``` 328 | -------------------------------------------------------------------------------- /packages/batshit/README.md: -------------------------------------------------------------------------------- 1 | # @yornaath/batshit [![CI](https://github.com/yornaath/batshit/actions/workflows/ci.yml/badge.svg)](https://github.com/yornaath/batshit/actions/workflows/ci.yml) 2 | 3 | A batch manager that will deduplicate and batch requests for a given data type made within a window of time (or other custom scheduling). Useful to batch requests made from multiple react components that uses react-query or do batch processing of accumulated tasks. 4 | 5 | ### Codesandbox example 6 | Here is a codesanbox example using react, typescript, vite and the zeitgeist prediction-markets indexer api. 7 | It fetches markets up front and then batches all liquidity pool fetches made from the individual components into one request. 8 | 9 | [Codesandbox](https://codesandbox.io/s/yornaath-batshit-example-8f8q3w?file=/src/App.tsx) 10 | 11 | ### Example with devtools 12 | Example using zeitgeist market and pool data with included devtools to inspect the batching process. 13 | The working live code for the example linked below can be found in [./packages/example](https://github.com/yornaath/batshit/tree/master/packages/example) 14 | 15 | [Vercel Example app](https://batshit-example.vercel.app/) 16 | 17 | ## Install 18 | ```bash 19 | yarn add @yornaath/batshit 20 | ``` 21 | 22 | ## Quickstart 23 | 24 | Here we are creating a simple batcher that will batch all fetches made within a window of 10 ms into one request. 25 | 26 | ```ts 27 | import { create, keyResolver, windowScheduler } from "@yornaath/batshit"; 28 | 29 | type User = { id: number; name: string }; 30 | 31 | const users = create({ 32 | fetcher: async (ids: number[]) => { 33 | return client.users.where({ 34 | id_in: ids, 35 | }); 36 | }, 37 | resolver: keyResolver("id"), 38 | scheduler: windowScheduler(10), // Default and can be omitted. 39 | }); 40 | 41 | /** 42 | * Requests will be batched to one call since they are done within the same time window of 10 ms. 43 | */ 44 | const bob = users.fetch(1); 45 | const alice = users.fetch(2); 46 | 47 | const bobUndtAlice = await Promise.all([bob, alice]); 48 | 49 | await delay(100); 50 | 51 | /** 52 | * New Requests will be batched in a another call since not within the first timeframe. 53 | */ 54 | const joe = users.fetch(3); 55 | const margareth = users.fetch(4); 56 | 57 | const joeUndtMargareth = await Promise.all([joe, margareth]); 58 | ``` 59 | 60 | ## React(query) Example 61 | 62 | Here we are also creating a simple batcher that will batch all fetches made within a window of 10 ms into one request. Since all items are rendered in one go their individual fetches will be batched into one request. 63 | 64 | **Note: a batcher for a group of items should only be created once. So creating them inside hooks wont work as intended.** 65 | 66 | ```ts 67 | import { useQuery } from "react-query"; 68 | import { create, windowScheduler } from "@yornaath/batshit"; 69 | 70 | const users = create({ 71 | fetcher: async (ids: number[]) => { 72 | return client.users.where({ 73 | userId_in: ids, 74 | }); 75 | }, 76 | resolver: keyResolver("id"), 77 | scheduler: windowScheduler(10), 78 | }); 79 | 80 | const useUser = (id: number) => { 81 | return useQuery(["users", id], async () => { 82 | return users.fetch(id); 83 | }); 84 | }; 85 | 86 | const UserDetails = (props: { userId: number }) => { 87 | const { isFetching, data } = useUser(props.userId); 88 | return ( 89 | <> 90 | {isFetching ? ( 91 |
Loading user {props.userId}
92 | ) : ( 93 |
User: {data.name}
94 | )} 95 | 96 | ); 97 | }; 98 | 99 | /** 100 | * Since all user details items are rendered within the window there will only be one request made. 101 | */ 102 | const UserList = () => { 103 | const userIds = [1, 2, 3, 4]; 104 | return ( 105 | <> 106 | {userIds.map((id) => ( 107 | 108 | ))} 109 | 110 | ); 111 | }; 112 | ``` 113 | 114 | ### Limit batch size 115 | 116 | We provide two helper functions for limiting the number of batched fetch calls. 117 | 118 | #### `windowedFiniteBatchScheduler` 119 | 120 | This will batch all calls made within a certain time frame UP to a certain max batch size before it starts a new batch 121 | 122 | ```ts 123 | const batcher = batshit.create({ 124 | ..., 125 | scheduler: windowedFiniteBatchScheduler({ 126 | windowMs: 10, 127 | maxBatchSize: 100, 128 | }), 129 | }); 130 | ``` 131 | 132 | #### `maxBatchSizeScheduler` 133 | 134 | Same as the one above, but will only wait indefinetly until the batch size is met. 135 | 136 | ```ts 137 | const batcher = batshit.create({ 138 | ..., 139 | scheduler: maxBatchSizeScheduler({ 140 | maxBatchSize: 100, 141 | }), 142 | }); 143 | ``` 144 | 145 | ### Fetching where response is an object of items 146 | 147 | In this example the response is an object/record with the id of the user as the key and the user object as the value. 148 | 149 | **Example:** 150 | ```json 151 | { 152 | "1": {"username": "bob"}, 153 | "2": {"username": "alice"} 154 | } 155 | ``` 156 | 157 | ```ts 158 | import * as batshit from "@yornaath/batshit"; 159 | 160 | const batcher = batshit.create({ 161 | fetcher: async (ids: string[]) => { 162 | const users: Record = await fetchUserRecords(ids) 163 | return users 164 | }, 165 | resolver: batshit.indexedResolver(), 166 | }); 167 | ``` 168 | 169 | ### Fetching with needed context 170 | 171 | If the batch fetcher needs some context like an sdk or client to make its fetching you can use a memoizer to make sure that you reuse a batcher for the given context in the hook calls. 172 | 173 | ```ts 174 | import { useQuery } from "@tanstack/react-query"; 175 | import { memoize } from "lodash-es"; 176 | import * as batshit from "@yornaath/batshit"; 177 | 178 | export const key = "markets"; 179 | 180 | const batcher = memoize((sdk: Sdk) => { 181 | return batshit.create({ 182 | name: key, 183 | fetcher: async (ids: number[]) => { 184 | const { markets } = await sdk.markets({ 185 | where: { 186 | marketId_in: ids, 187 | }, 188 | }); 189 | return markets; 190 | }, 191 | scheduler: batshit.windowScheduler(10), 192 | resolver: batshit.keyResolver("marketId"), 193 | }); 194 | }); 195 | 196 | export const useMarket = (marketId: number) => { 197 | const [sdk, id] = useSdk(); 198 | 199 | const query = useQuery( 200 | [id, key, marketId], 201 | async () => { 202 | if(sdk) { 203 | return batcher(sdk).fetch(marketId); 204 | } 205 | }, 206 | { 207 | enabled: Boolean(sdk), 208 | }, 209 | ); 210 | 211 | return query; 212 | }; 213 | ``` 214 | 215 | # Custom Batch Resolver 216 | 217 | This batcher will fetch all posts for multiple users in one request and resolve the correct list of posts for the discrete queries. 218 | 219 | ```ts 220 | const userposts = create({ 221 | fetcher: async (queries: { authorId: number }) => { 222 | return api.posts.where({ 223 | authorId_in: queries.map((q) => q.authorId), 224 | }); 225 | }, 226 | scheduler: windowScheduler(10), 227 | resolver: (posts, query) => 228 | posts.filter((post) => post.authorId === query.authorId), 229 | }); 230 | 231 | const [alicesPosts, bobsPost] = await Promise.all([ 232 | userposts.fetch({authorId: 1}) 233 | userposts.fetch({authorId: 2}) 234 | ]); 235 | ``` 236 | 237 | # batcher.next() - Early Execution 238 | 239 | Calling batcher.next() will execute the current batch early even if the scheduler hasnt finished. 240 | 241 | 242 | ```ts 243 | const batcher = create({ 244 | fetcher: async (ids: number[]) => { 245 | return mock.usersByIds(ids); 246 | }, 247 | resolver: keyResolver("id"), 248 | scheduler: windowScheduler(33), 249 | }); 250 | 251 | let all = Promise.all([batcher.fetch(1), batcher.fetch(2), batcher.fetch(3), batcher.fetch(4)]); 252 | 253 | bacher.next() 254 | 255 | const users = await all //current batch of users [1,2,3,4] will be fetched immediately 256 | ``` 257 | 258 | # Abort Signals - batcher.abort() 259 | 260 | You can abort the current batch with `batcher.abort()` 261 | 262 | Abort signals are sent down to fetcher that can be passed down to the underlying implementation. 263 | 264 | ```ts 265 | test("aborting", async () => { 266 | const batcher = create({ 267 | fetcher: async (ids: number[], signal: AbortSignal) => { 268 | return fetch(`/users?ids=${ids.join(",")}`, { signal }) 269 | }, 270 | resolver: keyResolver("id"), 271 | }); 272 | 273 | let all = Promise.all([batcher.fetch(1), batcher.fetch(2), batcher.fetch(3), batcher.fetch(4)]); 274 | 275 | setTimeout(() => { 276 | batcher.abort(); 277 | }, 5) 278 | 279 | const error = await all.catch((error) => error); 280 | 281 | expect(error).toBeInstanceOf(DOMException); 282 | expect(error.message).toBe("Aborted"); 283 | }) 284 | ``` 285 | 286 | # Indexed Keyresolver Performance 287 | 288 | If your batches are big arrays( > 30K items) and you use the keyresolver it can give you a performance boost to turn on indexing. 289 | 290 | __Any less than 30K items per batch and the performance gain is negligeble. But anywhere above 15K can be worth it.__ 291 | 292 | ```ts 293 | const batcherIndexed = create({ 294 | fetcher: async (ids: number[]) => { 295 | // returns > 30K items[] 296 | return mock.bigUserById(ids); 297 | }, 298 | resolver: keyResolver("id", { indexed: true }), 299 | scheduler: windowScheduler(1000), 300 | }); 301 | ``` 302 | 303 | # React Devtools 304 | 305 | Tools to debug and inspect the batching process can be found in the [@yornaath/batshit-devtools-react](https://www.npmjs.com/package/@yornaath/batshit-devtools-react) package. 306 | 307 | ```bash 308 | yarn add @yornaath/batshit-devtools @yornaath/batshit-devtools-react 309 | ``` 310 | 311 | ```ts 312 | import { create, keyResolver, windowScheduler } from "@yornaath/batshit"; 313 | import BatshitDevtools from "@yornaath/batshit-devtools-react"; 314 | 315 | const batcher = create({ 316 | fetcher: async (queries: number[]) => {...}, 317 | scheduler: windowScheduler(10), 318 | resolver: keyResolver("id"), 319 | name: "batcher:data" // used in the devtools to identify a particular batcher. 320 | }); 321 | 322 | const App = () => { 323 |
324 | 325 |
326 | } 327 | ``` 328 | -------------------------------------------------------------------------------- /packages/batshit/tests/index.test.ts: -------------------------------------------------------------------------------- 1 | import { injectDevtools } from "@yornaath/batshit-devtools"; 2 | import { setTimeout as setTimeoutP } from "timers/promises"; 3 | import { describe, expect, test } from "vitest"; 4 | import {performance} from "node:perf_hooks" 5 | import { 6 | create, 7 | bufferScheduler, 8 | windowScheduler, 9 | keyResolver, 10 | indexedResolver, 11 | windowedFiniteBatchScheduler, 12 | maxBatchSizeScheduler, 13 | } from "../src/index"; 14 | import * as mock from "./mock"; 15 | 16 | 17 | 18 | const tests = () => { 19 | test("fetching items should work", async () => { 20 | const batcher = create({ 21 | fetcher: async (ids: number[]) => { 22 | return mock.usersByIds(ids); 23 | }, 24 | resolver: keyResolver("id"), 25 | }); 26 | 27 | const two = await batcher.fetch(2); 28 | 29 | expect(two).toEqual({ id: 2, name: "Alice" }); 30 | 31 | const all = await Promise.all([ 32 | batcher.fetch(1), 33 | batcher.fetch(2), 34 | batcher.fetch(3), 35 | batcher.fetch(4), 36 | batcher.fetch(5), 37 | ]); 38 | 39 | expect(all).toEqual(mock.users); 40 | }); 41 | 42 | // test("un-indexed test", async () => { 43 | // const batcher = create({ 44 | // fetcher: async (ids: number[]) => { 45 | // return mock.bigUserById(ids); 46 | // }, 47 | // resolver: keyResolver("id"), 48 | // scheduler: windowScheduler(1000), 49 | // }); 50 | 51 | // console.time("unindexed") 52 | // await Promise.all(range(mock.BIG_USER_LIST_LENGTH).map((i) => batcher.fetch(i))); 53 | // console.timeEnd("unindexed") 54 | 55 | // }); 56 | 57 | // test("indexed test", async () => { 58 | // const batcherIndexed = create({ 59 | // fetcher: async (ids: number[]) => { 60 | // return mock.bigUserById(ids); 61 | // }, 62 | // resolver: keyResolver("id", { indexed: true }), 63 | // scheduler: windowScheduler(1000), 64 | // }); 65 | 66 | // console.time("indexed") 67 | // await Promise.all(range(mock.BIG_USER_LIST_LENGTH).map((i) => batcherIndexed.fetch(i))); 68 | // console.timeEnd("indexed") 69 | // }); 70 | 71 | test("fetching items be batched in the same time window", async () => { 72 | let fetchCounter = 0; 73 | 74 | const batcher = create({ 75 | fetcher: async (ids: number[]) => { 76 | fetchCounter++; 77 | return mock.usersByIds(ids); 78 | }, 79 | resolver: keyResolver("id"), 80 | }); 81 | 82 | const twoItemsR = Promise.all([batcher.fetch(2), batcher.fetch(5)]); 83 | 84 | await setTimeoutP(30); 85 | 86 | const allR = Promise.all([ 87 | batcher.fetch(1), 88 | batcher.fetch(2), 89 | batcher.fetch(3), 90 | batcher.fetch(4), 91 | batcher.fetch(5), 92 | ]); 93 | 94 | const [twoItems, all] = await Promise.all([twoItemsR, allR]); 95 | 96 | expect(twoItems).toEqual([ 97 | { id: 2, name: "Alice" }, 98 | { id: 5, name: "Tim" }, 99 | ]); 100 | 101 | expect(all).toEqual(mock.users); 102 | 103 | expect(fetchCounter).toBe(2); 104 | }); 105 | 106 | test("windowing", async () => { 107 | let fetchCounter = 0; 108 | const batcher = create({ 109 | fetcher: async (ids: number[]) => { 110 | fetchCounter++; 111 | return mock.usersByIds(ids); 112 | }, 113 | resolver: keyResolver("id"), 114 | scheduler: windowScheduler(10), 115 | }); 116 | const one = batcher.fetch(1); 117 | await setTimeoutP(2); 118 | const two = batcher.fetch(2); 119 | await setTimeoutP(3); 120 | const three = batcher.fetch(3); 121 | await setTimeoutP(5); 122 | const four = batcher.fetch(4); 123 | 124 | const all = await Promise.all([one, two, three, four]); 125 | 126 | expect(fetchCounter).toBe(2); 127 | expect(all).toEqual([ 128 | { id: 1, name: "Bob" }, 129 | { id: 2, name: "Alice" }, 130 | { id: 3, name: "Sally" }, 131 | { id: 4, name: "John" }, 132 | ]); 133 | }); 134 | 135 | test("early execution", async () => { 136 | let fetchCounter = 0; 137 | const batcher = create({ 138 | fetcher: async (ids: number[]) => { 139 | fetchCounter++ 140 | return mock.usersByIds(ids); 141 | }, 142 | resolver: keyResolver("id"), 143 | scheduler: windowScheduler(33), 144 | }); 145 | 146 | let all = Promise.all([batcher.fetch(1), batcher.fetch(2), batcher.fetch(3), batcher.fetch(4)]); 147 | 148 | expect(fetchCounter).toBe(0); 149 | 150 | let now = performance.now(); 151 | batcher.next() 152 | await all; 153 | let elapsed = performance.now() - now; 154 | expect(elapsed).toBeLessThan(5); 155 | 156 | expect(fetchCounter).toBe(1); 157 | 158 | 159 | now = performance.now(); 160 | all = Promise.all([batcher.fetch(1), batcher.fetch(2), batcher.fetch(3), batcher.fetch(4)]); 161 | await all; 162 | elapsed = performance.now() - now; 163 | expect(elapsed).toBeGreaterThanOrEqual(30) 164 | expect(fetchCounter).toBe(2); 165 | }) 166 | 167 | test("aborting", async () => { 168 | const batcher = create({ 169 | fetcher: async (ids: number[], abortSignal: AbortSignal) => { 170 | return mock.usersByIdsAsync(ids, 30, abortSignal); 171 | }, 172 | resolver: keyResolver("id"), 173 | }); 174 | 175 | const all = Promise.all([batcher.fetch(1), batcher.fetch(2), batcher.fetch(3), batcher.fetch(4)]); 176 | 177 | setTimeout(() => { 178 | batcher.abort(); 179 | }, 5) 180 | 181 | const error = await all.catch((error) => error); 182 | 183 | expect(error).toBeInstanceOf(DOMException); 184 | expect(error.message).toBe("Aborted"); 185 | 186 | const batch2 = Promise.all([batcher.fetch(1), batcher.fetch(2), batcher.fetch(3), batcher.fetch(4)]); 187 | 188 | expect(await batch2).toEqual([ 189 | { id: 1, name: "Bob" }, 190 | { id: 2, name: "Alice" }, 191 | { id: 3, name: "Sally" }, 192 | { id: 4, name: "John" }, 193 | ]); 194 | }) 195 | 196 | test("debouncing", async () => { 197 | let fetchCounter = 0; 198 | const batcher = create({ 199 | fetcher: async (ids: number[]) => { 200 | fetchCounter++; 201 | return mock.usersByIds(ids); 202 | }, 203 | resolver: keyResolver("id"), 204 | scheduler: bufferScheduler(10), 205 | }); 206 | 207 | const one = batcher.fetch(1); 208 | await setTimeoutP(2); 209 | const two = batcher.fetch(2); 210 | await setTimeoutP(3); 211 | const three = batcher.fetch(3); 212 | await setTimeoutP(5); 213 | const four = batcher.fetch(4); 214 | 215 | const all = await Promise.all([one, two, three, four]); 216 | 217 | expect(fetchCounter).toBe(1); 218 | expect(all).toEqual([ 219 | { id: 1, name: "Bob" }, 220 | { id: 2, name: "Alice" }, 221 | { id: 3, name: "Sally" }, 222 | { id: 4, name: "John" }, 223 | ]); 224 | }); 225 | 226 | test("requests for the same ids should be deduplicates and return the same values", async () => { 227 | let fetchCounter = 0; 228 | let fetchedIds!: number[]; 229 | 230 | const batcher = create({ 231 | fetcher: async (ids: number[]) => { 232 | fetchCounter++; 233 | fetchedIds = ids; 234 | return mock.usersByIds(ids); 235 | }, 236 | resolver: keyResolver("id"), 237 | scheduler: bufferScheduler(15), 238 | }); 239 | 240 | const one = batcher.fetch(1); 241 | await setTimeoutP(2); 242 | const two = batcher.fetch(2); 243 | await setTimeoutP(3); 244 | const three = batcher.fetch(1); 245 | await setTimeoutP(5); 246 | const four = batcher.fetch(2); 247 | 248 | const all = await Promise.all([one, two, three, four]); 249 | 250 | expect(fetchCounter).toBe(1); 251 | 252 | expect(fetchedIds).toEqual([1, 2]); 253 | 254 | expect(all).toEqual([ 255 | { id: 1, name: "Bob" }, 256 | { id: 2, name: "Alice" }, 257 | { id: 1, name: "Bob" }, 258 | { id: 2, name: "Alice" }, 259 | ]); 260 | }); 261 | 262 | test("key resolver", async () => { 263 | const batcher = create({ 264 | fetcher: async (ids: number[]) => { 265 | return mock.usersByIds(ids.filter((id) => id !== 2)); 266 | }, 267 | resolver: keyResolver("id"), 268 | }); 269 | 270 | const two = await batcher.fetch(2); 271 | expect(two).toBeNull(); 272 | 273 | const three = await batcher.fetch(3); 274 | expect(three).toEqual({ id: 3, name: "Sally" }); 275 | }); 276 | 277 | test("custom resolver", async () => { 278 | let fetchCounter = 0; 279 | const batcher = create({ 280 | fetcher: async (queries: { authorId: number }[]) => { 281 | fetchCounter++; 282 | return mock.postsByAuthorId(queries.map((q) => q.authorId)); 283 | }, 284 | scheduler: windowScheduler(10), 285 | resolver: (posts, query) => 286 | posts.filter((post) => post.authorId === query.authorId), 287 | }); 288 | 289 | const alicesPostsRequest = batcher.fetch({ authorId: 1 }); 290 | const bobsPosts = batcher.fetch({ authorId: 2 }); 291 | 292 | const [alicesPosts, bobsPost] = await Promise.all([ 293 | alicesPostsRequest, 294 | bobsPosts, 295 | ]); 296 | 297 | expect(alicesPosts).toEqual([ 298 | { id: 1, title: "Hello", authorId: 1 }, 299 | { id: 2, title: "World", authorId: 1 }, 300 | ]); 301 | 302 | expect(bobsPost).toEqual([ 303 | { id: 3, title: "Hello", authorId: 2 }, 304 | { id: 4, title: "World", authorId: 2 }, 305 | ]); 306 | 307 | expect(fetchCounter).toBe(1); 308 | }); 309 | 310 | test("record responses", async () => { 311 | const batcher = create({ 312 | fetcher: async (ids: number[]) => { 313 | const users = await mock.usersByIds(ids); 314 | const usersRecord = users.reduce>( 315 | (index, user) => { 316 | return { 317 | ...index, 318 | [user.id]: user, 319 | }; 320 | }, 321 | {} 322 | ); 323 | return usersRecord; 324 | }, 325 | resolver: indexedResolver(), 326 | }); 327 | 328 | const two = await batcher.fetch(2); 329 | 330 | expect(two).toEqual({ id: 2, name: "Alice" }); 331 | 332 | const all = await Promise.all([ 333 | batcher.fetch(1), 334 | batcher.fetch(2), 335 | batcher.fetch(3), 336 | batcher.fetch(4), 337 | batcher.fetch(5), 338 | ]); 339 | 340 | expect(all).toEqual(mock.users); 341 | }); 342 | 343 | test("handle undefined responses", async () => { 344 | const batcher = create({ 345 | fetcher: async (ids: number[]) => { 346 | return mock.usersByIds(ids); 347 | }, 348 | resolver: (items, id) => items.find((item) => item.id === id) ?? null, 349 | }); 350 | 351 | const all = await Promise.all([batcher.fetch(2), batcher.fetch(100)]); 352 | 353 | expect(all).toEqual([{ id: 2, name: "Alice" }, null]); 354 | }); 355 | 356 | test("immediate scheduled batchers should work", async () => { 357 | let fetchCounter = 0; 358 | const batcher = create({ 359 | fetcher: async (ids: number[]) => { 360 | fetchCounter++; 361 | return mock.usersByIds(ids); 362 | }, 363 | scheduler: () => "immediate", 364 | resolver: keyResolver("id"), 365 | }); 366 | 367 | const one = batcher.fetch(1); 368 | const two = batcher.fetch(2); 369 | 370 | expect(await one).toEqual({ id: 1, name: "Bob" }); 371 | expect(await two).toEqual({ id: 2, name: "Alice" }); 372 | 373 | expect(fetchCounter).toEqual(2); 374 | }); 375 | 376 | describe("windowedBatchScheduler", () => { 377 | test("should batch within a window, but fetch immediatly if batch size 2 is reached", async () => { 378 | let fetchCounter = 0; 379 | const batcher = create({ 380 | fetcher: async (ids: number[]) => { 381 | fetchCounter++; 382 | return mock.usersByIds(ids); 383 | }, 384 | scheduler: windowedFiniteBatchScheduler({ 385 | windowMs: 10, 386 | maxBatchSize: 2, 387 | }), 388 | resolver: keyResolver("id"), 389 | }); 390 | 391 | const one = batcher.fetch(1); 392 | const two = batcher.fetch(2); 393 | const three = batcher.fetch(3); 394 | const four = batcher.fetch(4); 395 | 396 | const all = await Promise.all([one, two, three, four]); 397 | 398 | expect(fetchCounter).toBe(2); 399 | 400 | expect(all).toEqual([ 401 | { id: 1, name: "Bob" }, 402 | { id: 2, name: "Alice" }, 403 | { id: 3, name: "Sally" }, 404 | { id: 4, name: "John" }, 405 | ]); 406 | }); 407 | 408 | test("should batch within a window, but fetch immediatly if batch size 1 is reached", async () => { 409 | let fetchCounter = 0; 410 | const batcher = create({ 411 | fetcher: async (ids: number[]) => { 412 | fetchCounter++; 413 | return mock.usersByIds(ids); 414 | }, 415 | scheduler: windowedFiniteBatchScheduler({ 416 | windowMs: 10, 417 | maxBatchSize: 1, 418 | }), 419 | resolver: keyResolver("id"), 420 | }); 421 | 422 | const one = batcher.fetch(1); 423 | const two = batcher.fetch(2); 424 | const three = batcher.fetch(3); 425 | const four = batcher.fetch(4); 426 | 427 | const all = await Promise.all([one, two, three, four]); 428 | 429 | expect(fetchCounter).toBe(4); 430 | 431 | expect(all).toEqual([ 432 | { id: 1, name: "Bob" }, 433 | { id: 2, name: "Alice" }, 434 | { id: 3, name: "Sally" }, 435 | { id: 4, name: "John" }, 436 | ]); 437 | }); 438 | 439 | test("should work with the next batch after first batch is made", async () => { 440 | let fetchCounter = 0; 441 | const batcher = create({ 442 | fetcher: async (ids: number[]) => { 443 | fetchCounter++; 444 | return mock.usersByIds(ids); 445 | }, 446 | scheduler: windowedFiniteBatchScheduler({ 447 | windowMs: 10, 448 | maxBatchSize: 1, 449 | }), 450 | resolver: keyResolver("id"), 451 | }); 452 | 453 | const one = batcher.fetch(1); 454 | const two = batcher.fetch(2); 455 | const firstTwo = await Promise.all([one, two]); 456 | 457 | expect(fetchCounter).toBe(2); 458 | 459 | const three = batcher.fetch(3); 460 | const four = batcher.fetch(4); 461 | 462 | const lastTwo = await Promise.all([three, four]); 463 | 464 | expect(fetchCounter).toBe(4); 465 | 466 | expect([...firstTwo, ...lastTwo]).toEqual([ 467 | { id: 1, name: "Bob" }, 468 | { id: 2, name: "Alice" }, 469 | { id: 3, name: "Sally" }, 470 | { id: 4, name: "John" }, 471 | ]); 472 | }); 473 | 474 | test("manuall implementation of windowed batcher", async () => { 475 | let fetchCounter = 0; 476 | const batcher = create({ 477 | fetcher: async (ids: number[]) => { 478 | fetchCounter++; 479 | return mock.usersByIds(ids); 480 | }, 481 | scheduler: (start, latest, batchSize) => { 482 | if (batchSize >= 1) return "immediate"; 483 | const spent = latest - start; 484 | return 10 - spent; 485 | }, 486 | resolver: keyResolver("id"), 487 | }); 488 | 489 | const one = batcher.fetch(1); 490 | const two = batcher.fetch(2); 491 | const three = batcher.fetch(3); 492 | const four = batcher.fetch(4); 493 | 494 | const all = await Promise.all([one, two, three, four]); 495 | 496 | expect(fetchCounter).toBe(4); 497 | 498 | expect(all).toEqual([ 499 | { id: 1, name: "Bob" }, 500 | { id: 2, name: "Alice" }, 501 | { id: 3, name: "Sally" }, 502 | { id: 4, name: "John" }, 503 | ]); 504 | }); 505 | }); 506 | 507 | describe("maxBatchSizeScheduler", () => { 508 | test("should batch calls when max batch size of 1 is reached", async () => { 509 | let fetchCounter = 0; 510 | const batcher = create({ 511 | fetcher: async (ids: number[]) => { 512 | fetchCounter++; 513 | return mock.usersByIds(ids); 514 | }, 515 | scheduler: maxBatchSizeScheduler({ 516 | maxBatchSize: 1, 517 | }), 518 | resolver: keyResolver("id"), 519 | }); 520 | 521 | const one = batcher.fetch(1); 522 | const two = batcher.fetch(2); 523 | const three = batcher.fetch(3); 524 | const four = batcher.fetch(4); 525 | 526 | const all = await Promise.all([one, two, three, four]); 527 | 528 | expect(fetchCounter).toBe(4); 529 | 530 | expect(all).toEqual([ 531 | { id: 1, name: "Bob" }, 532 | { id: 2, name: "Alice" }, 533 | { id: 3, name: "Sally" }, 534 | { id: 4, name: "John" }, 535 | ]); 536 | }); 537 | 538 | test("should batch calls when max batch size of 4 is reached", async () => { 539 | let fetchCounter = 0; 540 | const batcher = create({ 541 | fetcher: async (ids: number[]) => { 542 | fetchCounter++; 543 | return mock.usersByIds(ids); 544 | }, 545 | scheduler: maxBatchSizeScheduler({ 546 | maxBatchSize: 4, 547 | }), 548 | resolver: keyResolver("id"), 549 | }); 550 | 551 | const one = batcher.fetch(1); 552 | const two = batcher.fetch(2); 553 | const three = batcher.fetch(3); 554 | const four = batcher.fetch(4); 555 | 556 | const all = await Promise.all([one, two, three, four]); 557 | 558 | expect(fetchCounter).toBe(1); 559 | 560 | expect(all).toEqual([ 561 | { id: 1, name: "Bob" }, 562 | { id: 2, name: "Alice" }, 563 | { id: 3, name: "Sally" }, 564 | { id: 4, name: "John" }, 565 | ]); 566 | }); 567 | 568 | test("should work with the next batch after first batch is made", async () => { 569 | let fetchCounter = 0; 570 | const batcher = create({ 571 | fetcher: async (ids: number[]) => { 572 | fetchCounter++; 573 | return mock.usersByIds(ids); 574 | }, 575 | scheduler: maxBatchSizeScheduler({ 576 | maxBatchSize: 1, 577 | }), 578 | resolver: keyResolver("id"), 579 | }); 580 | 581 | const one = batcher.fetch(1); 582 | const two = batcher.fetch(2); 583 | const firstTwo = await Promise.all([one, two]); 584 | 585 | expect(fetchCounter).toBe(2); 586 | 587 | const three = batcher.fetch(3); 588 | const four = batcher.fetch(4); 589 | 590 | const lastTwo = await Promise.all([three, four]); 591 | 592 | expect(fetchCounter).toBe(4); 593 | 594 | expect([...firstTwo, ...lastTwo]).toEqual([ 595 | { id: 1, name: "Bob" }, 596 | { id: 2, name: "Alice" }, 597 | { id: 3, name: "Sally" }, 598 | { id: 4, name: "John" }, 599 | ]); 600 | }); 601 | }); 602 | }; 603 | 604 | describe("batcher", tests); 605 | // describe("batcher-with-devtools", () => { 606 | // injectDevtools(() => { }); 607 | // return tests(); 608 | // }); 609 | 610 | 611 | const range = (n: number) => Array.from({ length: n }, (_, i) => i + 1); -------------------------------------------------------------------------------- /packages/devtools/tsconfig.tsbuildinfo: -------------------------------------------------------------------------------- 1 | {"program":{"fileNames":["../../node_modules/typescript/lib/lib.es5.d.ts","../../node_modules/typescript/lib/lib.es2015.d.ts","../../node_modules/typescript/lib/lib.es2016.d.ts","../../node_modules/typescript/lib/lib.es2017.d.ts","../../node_modules/typescript/lib/lib.es2018.d.ts","../../node_modules/typescript/lib/lib.es2019.d.ts","../../node_modules/typescript/lib/lib.es2020.d.ts","../../node_modules/typescript/lib/lib.dom.d.ts","../../node_modules/typescript/lib/lib.dom.iterable.d.ts","../../node_modules/typescript/lib/lib.webworker.importscripts.d.ts","../../node_modules/typescript/lib/lib.scripthost.d.ts","../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../node_modules/typescript/lib/lib.es2020.date.d.ts","../../node_modules/typescript/lib/lib.es2020.promise.d.ts","../../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2020.string.d.ts","../../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.number.d.ts","../../node_modules/typescript/lib/lib.esnext.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.full.d.ts","./src/index.ts","../../node_modules/@types/chai/index.d.ts","../../node_modules/@types/chai-subset/index.d.ts","../../node_modules/@types/estree/index.d.ts","../../node_modules/ci-info/index.d.ts","../../node_modules/@types/is-ci/index.d.ts","../../node_modules/@types/istanbul-lib-coverage/index.d.ts","../../node_modules/@types/lodash/common/common.d.ts","../../node_modules/@types/lodash/common/array.d.ts","../../node_modules/@types/lodash/common/collection.d.ts","../../node_modules/@types/lodash/common/date.d.ts","../../node_modules/@types/lodash/common/function.d.ts","../../node_modules/@types/lodash/common/lang.d.ts","../../node_modules/@types/lodash/common/math.d.ts","../../node_modules/@types/lodash/common/number.d.ts","../../node_modules/@types/lodash/common/object.d.ts","../../node_modules/@types/lodash/common/seq.d.ts","../../node_modules/@types/lodash/common/string.d.ts","../../node_modules/@types/lodash/common/util.d.ts","../../node_modules/@types/lodash/index.d.ts","../../node_modules/@types/lodash-es/add.d.ts","../../node_modules/@types/lodash-es/after.d.ts","../../node_modules/@types/lodash-es/ary.d.ts","../../node_modules/@types/lodash-es/assign.d.ts","../../node_modules/@types/lodash-es/assignin.d.ts","../../node_modules/@types/lodash-es/assigninwith.d.ts","../../node_modules/@types/lodash-es/assignwith.d.ts","../../node_modules/@types/lodash-es/at.d.ts","../../node_modules/@types/lodash-es/attempt.d.ts","../../node_modules/@types/lodash-es/before.d.ts","../../node_modules/@types/lodash-es/bind.d.ts","../../node_modules/@types/lodash-es/bindall.d.ts","../../node_modules/@types/lodash-es/bindkey.d.ts","../../node_modules/@types/lodash-es/camelcase.d.ts","../../node_modules/@types/lodash-es/capitalize.d.ts","../../node_modules/@types/lodash-es/castarray.d.ts","../../node_modules/@types/lodash-es/ceil.d.ts","../../node_modules/@types/lodash-es/chain.d.ts","../../node_modules/@types/lodash-es/chunk.d.ts","../../node_modules/@types/lodash-es/clamp.d.ts","../../node_modules/@types/lodash-es/clone.d.ts","../../node_modules/@types/lodash-es/clonedeep.d.ts","../../node_modules/@types/lodash-es/clonedeepwith.d.ts","../../node_modules/@types/lodash-es/clonewith.d.ts","../../node_modules/@types/lodash-es/compact.d.ts","../../node_modules/@types/lodash-es/concat.d.ts","../../node_modules/@types/lodash-es/cond.d.ts","../../node_modules/@types/lodash-es/conforms.d.ts","../../node_modules/@types/lodash-es/conformsto.d.ts","../../node_modules/@types/lodash-es/constant.d.ts","../../node_modules/@types/lodash-es/countby.d.ts","../../node_modules/@types/lodash-es/create.d.ts","../../node_modules/@types/lodash-es/curry.d.ts","../../node_modules/@types/lodash-es/curryright.d.ts","../../node_modules/@types/lodash-es/debounce.d.ts","../../node_modules/@types/lodash-es/deburr.d.ts","../../node_modules/@types/lodash-es/defaultto.d.ts","../../node_modules/@types/lodash-es/defaults.d.ts","../../node_modules/@types/lodash-es/defaultsdeep.d.ts","../../node_modules/@types/lodash-es/defer.d.ts","../../node_modules/@types/lodash-es/delay.d.ts","../../node_modules/@types/lodash-es/difference.d.ts","../../node_modules/@types/lodash-es/differenceby.d.ts","../../node_modules/@types/lodash-es/differencewith.d.ts","../../node_modules/@types/lodash-es/divide.d.ts","../../node_modules/@types/lodash-es/drop.d.ts","../../node_modules/@types/lodash-es/dropright.d.ts","../../node_modules/@types/lodash-es/droprightwhile.d.ts","../../node_modules/@types/lodash-es/dropwhile.d.ts","../../node_modules/@types/lodash-es/each.d.ts","../../node_modules/@types/lodash-es/eachright.d.ts","../../node_modules/@types/lodash-es/endswith.d.ts","../../node_modules/@types/lodash-es/entries.d.ts","../../node_modules/@types/lodash-es/entriesin.d.ts","../../node_modules/@types/lodash-es/eq.d.ts","../../node_modules/@types/lodash-es/escape.d.ts","../../node_modules/@types/lodash-es/escaperegexp.d.ts","../../node_modules/@types/lodash-es/every.d.ts","../../node_modules/@types/lodash-es/extend.d.ts","../../node_modules/@types/lodash-es/extendwith.d.ts","../../node_modules/@types/lodash-es/fill.d.ts","../../node_modules/@types/lodash-es/filter.d.ts","../../node_modules/@types/lodash-es/find.d.ts","../../node_modules/@types/lodash-es/findindex.d.ts","../../node_modules/@types/lodash-es/findkey.d.ts","../../node_modules/@types/lodash-es/findlast.d.ts","../../node_modules/@types/lodash-es/findlastindex.d.ts","../../node_modules/@types/lodash-es/findlastkey.d.ts","../../node_modules/@types/lodash-es/first.d.ts","../../node_modules/@types/lodash-es/flatmap.d.ts","../../node_modules/@types/lodash-es/flatmapdeep.d.ts","../../node_modules/@types/lodash-es/flatmapdepth.d.ts","../../node_modules/@types/lodash-es/flatten.d.ts","../../node_modules/@types/lodash-es/flattendeep.d.ts","../../node_modules/@types/lodash-es/flattendepth.d.ts","../../node_modules/@types/lodash-es/flip.d.ts","../../node_modules/@types/lodash-es/floor.d.ts","../../node_modules/@types/lodash-es/flow.d.ts","../../node_modules/@types/lodash-es/flowright.d.ts","../../node_modules/@types/lodash-es/foreach.d.ts","../../node_modules/@types/lodash-es/foreachright.d.ts","../../node_modules/@types/lodash-es/forin.d.ts","../../node_modules/@types/lodash-es/forinright.d.ts","../../node_modules/@types/lodash-es/forown.d.ts","../../node_modules/@types/lodash-es/forownright.d.ts","../../node_modules/@types/lodash-es/frompairs.d.ts","../../node_modules/@types/lodash-es/functions.d.ts","../../node_modules/@types/lodash-es/functionsin.d.ts","../../node_modules/@types/lodash-es/get.d.ts","../../node_modules/@types/lodash-es/groupby.d.ts","../../node_modules/@types/lodash-es/gt.d.ts","../../node_modules/@types/lodash-es/gte.d.ts","../../node_modules/@types/lodash-es/has.d.ts","../../node_modules/@types/lodash-es/hasin.d.ts","../../node_modules/@types/lodash-es/head.d.ts","../../node_modules/@types/lodash-es/identity.d.ts","../../node_modules/@types/lodash-es/inrange.d.ts","../../node_modules/@types/lodash-es/includes.d.ts","../../node_modules/@types/lodash-es/indexof.d.ts","../../node_modules/@types/lodash-es/initial.d.ts","../../node_modules/@types/lodash-es/intersection.d.ts","../../node_modules/@types/lodash-es/intersectionby.d.ts","../../node_modules/@types/lodash-es/intersectionwith.d.ts","../../node_modules/@types/lodash-es/invert.d.ts","../../node_modules/@types/lodash-es/invertby.d.ts","../../node_modules/@types/lodash-es/invoke.d.ts","../../node_modules/@types/lodash-es/invokemap.d.ts","../../node_modules/@types/lodash-es/isarguments.d.ts","../../node_modules/@types/lodash-es/isarray.d.ts","../../node_modules/@types/lodash-es/isarraybuffer.d.ts","../../node_modules/@types/lodash-es/isarraylike.d.ts","../../node_modules/@types/lodash-es/isarraylikeobject.d.ts","../../node_modules/@types/lodash-es/isboolean.d.ts","../../node_modules/@types/lodash-es/isbuffer.d.ts","../../node_modules/@types/lodash-es/isdate.d.ts","../../node_modules/@types/lodash-es/iselement.d.ts","../../node_modules/@types/lodash-es/isempty.d.ts","../../node_modules/@types/lodash-es/isequal.d.ts","../../node_modules/@types/lodash-es/isequalwith.d.ts","../../node_modules/@types/lodash-es/iserror.d.ts","../../node_modules/@types/lodash-es/isfinite.d.ts","../../node_modules/@types/lodash-es/isfunction.d.ts","../../node_modules/@types/lodash-es/isinteger.d.ts","../../node_modules/@types/lodash-es/islength.d.ts","../../node_modules/@types/lodash-es/ismap.d.ts","../../node_modules/@types/lodash-es/ismatch.d.ts","../../node_modules/@types/lodash-es/ismatchwith.d.ts","../../node_modules/@types/lodash-es/isnan.d.ts","../../node_modules/@types/lodash-es/isnative.d.ts","../../node_modules/@types/lodash-es/isnil.d.ts","../../node_modules/@types/lodash-es/isnull.d.ts","../../node_modules/@types/lodash-es/isnumber.d.ts","../../node_modules/@types/lodash-es/isobject.d.ts","../../node_modules/@types/lodash-es/isobjectlike.d.ts","../../node_modules/@types/lodash-es/isplainobject.d.ts","../../node_modules/@types/lodash-es/isregexp.d.ts","../../node_modules/@types/lodash-es/issafeinteger.d.ts","../../node_modules/@types/lodash-es/isset.d.ts","../../node_modules/@types/lodash-es/isstring.d.ts","../../node_modules/@types/lodash-es/issymbol.d.ts","../../node_modules/@types/lodash-es/istypedarray.d.ts","../../node_modules/@types/lodash-es/isundefined.d.ts","../../node_modules/@types/lodash-es/isweakmap.d.ts","../../node_modules/@types/lodash-es/isweakset.d.ts","../../node_modules/@types/lodash-es/iteratee.d.ts","../../node_modules/@types/lodash-es/join.d.ts","../../node_modules/@types/lodash-es/kebabcase.d.ts","../../node_modules/@types/lodash-es/keyby.d.ts","../../node_modules/@types/lodash-es/keys.d.ts","../../node_modules/@types/lodash-es/keysin.d.ts","../../node_modules/@types/lodash-es/last.d.ts","../../node_modules/@types/lodash-es/lastindexof.d.ts","../../node_modules/@types/lodash-es/lowercase.d.ts","../../node_modules/@types/lodash-es/lowerfirst.d.ts","../../node_modules/@types/lodash-es/lt.d.ts","../../node_modules/@types/lodash-es/lte.d.ts","../../node_modules/@types/lodash-es/map.d.ts","../../node_modules/@types/lodash-es/mapkeys.d.ts","../../node_modules/@types/lodash-es/mapvalues.d.ts","../../node_modules/@types/lodash-es/matches.d.ts","../../node_modules/@types/lodash-es/matchesproperty.d.ts","../../node_modules/@types/lodash-es/max.d.ts","../../node_modules/@types/lodash-es/maxby.d.ts","../../node_modules/@types/lodash-es/mean.d.ts","../../node_modules/@types/lodash-es/meanby.d.ts","../../node_modules/@types/lodash-es/memoize.d.ts","../../node_modules/@types/lodash-es/merge.d.ts","../../node_modules/@types/lodash-es/mergewith.d.ts","../../node_modules/@types/lodash-es/method.d.ts","../../node_modules/@types/lodash-es/methodof.d.ts","../../node_modules/@types/lodash-es/min.d.ts","../../node_modules/@types/lodash-es/minby.d.ts","../../node_modules/@types/lodash-es/mixin.d.ts","../../node_modules/@types/lodash-es/multiply.d.ts","../../node_modules/@types/lodash-es/negate.d.ts","../../node_modules/@types/lodash-es/noop.d.ts","../../node_modules/@types/lodash-es/now.d.ts","../../node_modules/@types/lodash-es/nth.d.ts","../../node_modules/@types/lodash-es/ntharg.d.ts","../../node_modules/@types/lodash-es/omit.d.ts","../../node_modules/@types/lodash-es/omitby.d.ts","../../node_modules/@types/lodash-es/once.d.ts","../../node_modules/@types/lodash-es/orderby.d.ts","../../node_modules/@types/lodash-es/over.d.ts","../../node_modules/@types/lodash-es/overargs.d.ts","../../node_modules/@types/lodash-es/overevery.d.ts","../../node_modules/@types/lodash-es/oversome.d.ts","../../node_modules/@types/lodash-es/pad.d.ts","../../node_modules/@types/lodash-es/padend.d.ts","../../node_modules/@types/lodash-es/padstart.d.ts","../../node_modules/@types/lodash-es/parseint.d.ts","../../node_modules/@types/lodash-es/partial.d.ts","../../node_modules/@types/lodash-es/partialright.d.ts","../../node_modules/@types/lodash-es/partition.d.ts","../../node_modules/@types/lodash-es/pick.d.ts","../../node_modules/@types/lodash-es/pickby.d.ts","../../node_modules/@types/lodash-es/property.d.ts","../../node_modules/@types/lodash-es/propertyof.d.ts","../../node_modules/@types/lodash-es/pull.d.ts","../../node_modules/@types/lodash-es/pullall.d.ts","../../node_modules/@types/lodash-es/pullallby.d.ts","../../node_modules/@types/lodash-es/pullallwith.d.ts","../../node_modules/@types/lodash-es/pullat.d.ts","../../node_modules/@types/lodash-es/random.d.ts","../../node_modules/@types/lodash-es/range.d.ts","../../node_modules/@types/lodash-es/rangeright.d.ts","../../node_modules/@types/lodash-es/rearg.d.ts","../../node_modules/@types/lodash-es/reduce.d.ts","../../node_modules/@types/lodash-es/reduceright.d.ts","../../node_modules/@types/lodash-es/reject.d.ts","../../node_modules/@types/lodash-es/remove.d.ts","../../node_modules/@types/lodash-es/repeat.d.ts","../../node_modules/@types/lodash-es/replace.d.ts","../../node_modules/@types/lodash-es/rest.d.ts","../../node_modules/@types/lodash-es/result.d.ts","../../node_modules/@types/lodash-es/reverse.d.ts","../../node_modules/@types/lodash-es/round.d.ts","../../node_modules/@types/lodash-es/sample.d.ts","../../node_modules/@types/lodash-es/samplesize.d.ts","../../node_modules/@types/lodash-es/set.d.ts","../../node_modules/@types/lodash-es/setwith.d.ts","../../node_modules/@types/lodash-es/shuffle.d.ts","../../node_modules/@types/lodash-es/size.d.ts","../../node_modules/@types/lodash-es/slice.d.ts","../../node_modules/@types/lodash-es/snakecase.d.ts","../../node_modules/@types/lodash-es/some.d.ts","../../node_modules/@types/lodash-es/sortby.d.ts","../../node_modules/@types/lodash-es/sortedindex.d.ts","../../node_modules/@types/lodash-es/sortedindexby.d.ts","../../node_modules/@types/lodash-es/sortedindexof.d.ts","../../node_modules/@types/lodash-es/sortedlastindex.d.ts","../../node_modules/@types/lodash-es/sortedlastindexby.d.ts","../../node_modules/@types/lodash-es/sortedlastindexof.d.ts","../../node_modules/@types/lodash-es/sorteduniq.d.ts","../../node_modules/@types/lodash-es/sorteduniqby.d.ts","../../node_modules/@types/lodash-es/split.d.ts","../../node_modules/@types/lodash-es/spread.d.ts","../../node_modules/@types/lodash-es/startcase.d.ts","../../node_modules/@types/lodash-es/startswith.d.ts","../../node_modules/@types/lodash-es/stubarray.d.ts","../../node_modules/@types/lodash-es/stubfalse.d.ts","../../node_modules/@types/lodash-es/stubobject.d.ts","../../node_modules/@types/lodash-es/stubstring.d.ts","../../node_modules/@types/lodash-es/stubtrue.d.ts","../../node_modules/@types/lodash-es/subtract.d.ts","../../node_modules/@types/lodash-es/sum.d.ts","../../node_modules/@types/lodash-es/sumby.d.ts","../../node_modules/@types/lodash-es/tail.d.ts","../../node_modules/@types/lodash-es/take.d.ts","../../node_modules/@types/lodash-es/takeright.d.ts","../../node_modules/@types/lodash-es/takerightwhile.d.ts","../../node_modules/@types/lodash-es/takewhile.d.ts","../../node_modules/@types/lodash-es/tap.d.ts","../../node_modules/@types/lodash-es/template.d.ts","../../node_modules/@types/lodash-es/templatesettings.d.ts","../../node_modules/@types/lodash-es/throttle.d.ts","../../node_modules/@types/lodash-es/times.d.ts","../../node_modules/@types/lodash-es/toarray.d.ts","../../node_modules/@types/lodash-es/tofinite.d.ts","../../node_modules/@types/lodash-es/tointeger.d.ts","../../node_modules/@types/lodash-es/tolength.d.ts","../../node_modules/@types/lodash-es/tolower.d.ts","../../node_modules/@types/lodash-es/tonumber.d.ts","../../node_modules/@types/lodash-es/topairs.d.ts","../../node_modules/@types/lodash-es/topairsin.d.ts","../../node_modules/@types/lodash-es/topath.d.ts","../../node_modules/@types/lodash-es/toplainobject.d.ts","../../node_modules/@types/lodash-es/tosafeinteger.d.ts","../../node_modules/@types/lodash-es/tostring.d.ts","../../node_modules/@types/lodash-es/toupper.d.ts","../../node_modules/@types/lodash-es/transform.d.ts","../../node_modules/@types/lodash-es/trim.d.ts","../../node_modules/@types/lodash-es/trimend.d.ts","../../node_modules/@types/lodash-es/trimstart.d.ts","../../node_modules/@types/lodash-es/truncate.d.ts","../../node_modules/@types/lodash-es/unary.d.ts","../../node_modules/@types/lodash-es/unescape.d.ts","../../node_modules/@types/lodash-es/union.d.ts","../../node_modules/@types/lodash-es/unionby.d.ts","../../node_modules/@types/lodash-es/unionwith.d.ts","../../node_modules/@types/lodash-es/uniq.d.ts","../../node_modules/@types/lodash-es/uniqby.d.ts","../../node_modules/@types/lodash-es/uniqwith.d.ts","../../node_modules/@types/lodash-es/uniqueid.d.ts","../../node_modules/@types/lodash-es/unset.d.ts","../../node_modules/@types/lodash-es/unzip.d.ts","../../node_modules/@types/lodash-es/unzipwith.d.ts","../../node_modules/@types/lodash-es/update.d.ts","../../node_modules/@types/lodash-es/updatewith.d.ts","../../node_modules/@types/lodash-es/uppercase.d.ts","../../node_modules/@types/lodash-es/upperfirst.d.ts","../../node_modules/@types/lodash-es/values.d.ts","../../node_modules/@types/lodash-es/valuesin.d.ts","../../node_modules/@types/lodash-es/without.d.ts","../../node_modules/@types/lodash-es/words.d.ts","../../node_modules/@types/lodash-es/wrap.d.ts","../../node_modules/@types/lodash-es/xor.d.ts","../../node_modules/@types/lodash-es/xorby.d.ts","../../node_modules/@types/lodash-es/xorwith.d.ts","../../node_modules/@types/lodash-es/zip.d.ts","../../node_modules/@types/lodash-es/zipobject.d.ts","../../node_modules/@types/lodash-es/zipobjectdeep.d.ts","../../node_modules/@types/lodash-es/zipwith.d.ts","../../node_modules/@types/lodash-es/index.d.ts","../../node_modules/@types/minimist/index.d.ts","../../node_modules/@types/node/assert.d.ts","../../node_modules/@types/node/assert/strict.d.ts","../../node_modules/@types/node/globals.d.ts","../../node_modules/@types/node/async_hooks.d.ts","../../node_modules/@types/node/buffer.d.ts","../../node_modules/@types/node/child_process.d.ts","../../node_modules/@types/node/cluster.d.ts","../../node_modules/@types/node/console.d.ts","../../node_modules/@types/node/constants.d.ts","../../node_modules/@types/node/crypto.d.ts","../../node_modules/@types/node/dgram.d.ts","../../node_modules/@types/node/diagnostics_channel.d.ts","../../node_modules/@types/node/dns.d.ts","../../node_modules/@types/node/dns/promises.d.ts","../../node_modules/@types/node/domain.d.ts","../../node_modules/@types/node/dom-events.d.ts","../../node_modules/@types/node/events.d.ts","../../node_modules/@types/node/fs.d.ts","../../node_modules/@types/node/fs/promises.d.ts","../../node_modules/@types/node/http.d.ts","../../node_modules/@types/node/http2.d.ts","../../node_modules/@types/node/https.d.ts","../../node_modules/@types/node/inspector.d.ts","../../node_modules/@types/node/module.d.ts","../../node_modules/@types/node/net.d.ts","../../node_modules/@types/node/os.d.ts","../../node_modules/@types/node/path.d.ts","../../node_modules/@types/node/perf_hooks.d.ts","../../node_modules/@types/node/process.d.ts","../../node_modules/@types/node/punycode.d.ts","../../node_modules/@types/node/querystring.d.ts","../../node_modules/@types/node/readline.d.ts","../../node_modules/@types/node/readline/promises.d.ts","../../node_modules/@types/node/repl.d.ts","../../node_modules/@types/node/stream.d.ts","../../node_modules/@types/node/stream/promises.d.ts","../../node_modules/@types/node/stream/consumers.d.ts","../../node_modules/@types/node/stream/web.d.ts","../../node_modules/@types/node/string_decoder.d.ts","../../node_modules/@types/node/test.d.ts","../../node_modules/@types/node/timers.d.ts","../../node_modules/@types/node/timers/promises.d.ts","../../node_modules/@types/node/tls.d.ts","../../node_modules/@types/node/trace_events.d.ts","../../node_modules/@types/node/tty.d.ts","../../node_modules/@types/node/url.d.ts","../../node_modules/@types/node/util.d.ts","../../node_modules/@types/node/v8.d.ts","../../node_modules/@types/node/vm.d.ts","../../node_modules/@types/node/wasi.d.ts","../../node_modules/@types/node/worker_threads.d.ts","../../node_modules/@types/node/zlib.d.ts","../../node_modules/@types/node/globals.global.d.ts","../../node_modules/@types/node/index.d.ts","../../node_modules/@types/normalize-package-data/index.d.ts","../../node_modules/@types/prop-types/index.d.ts","../../node_modules/@types/react/global.d.ts","../../node_modules/csstype/index.d.ts","../../node_modules/@types/scheduler/tracing.d.ts","../../node_modules/@types/react/index.d.ts","../../node_modules/@types/react-dom/index.d.ts","../../node_modules/@types/scheduler/index.d.ts","../../node_modules/@types/semver/index.d.ts"],"fileInfos":[{"version":"8730f4bf322026ff5229336391a18bcaa1f94d4f82416c8b2f3954e2ccaae2ba","affectsGlobalScope":true},"dc47c4fa66b9b9890cf076304de2a9c5201e94b740cffdf09f87296d877d71f6","7a387c58583dfca701b6c85e0adaf43fb17d590fb16d5b2dc0a2fbd89f35c467","8a12173c586e95f4433e0c6dc446bc88346be73ffe9ca6eec7aa63c8f3dca7f9","5f4e733ced4e129482ae2186aae29fde948ab7182844c3a5a51dd346182c7b06","4b421cbfb3a38a27c279dec1e9112c3d1da296f77a1a85ddadf7e7a425d45d18","1fc5ab7a764205c68fa10d381b08417795fc73111d6dd16b5b1ed36badb743d9",{"version":"3aafcb693fe5b5c3bd277bd4c3a617b53db474fe498fc5df067c5603b1eebde7","affectsGlobalScope":true},{"version":"f3d4da15233e593eacb3965cde7960f3fddf5878528d882bcedd5cbaba0193c7","affectsGlobalScope":true},{"version":"7fac8cb5fc820bc2a59ae11ef1c5b38d3832c6d0dfaec5acdb5569137d09a481","affectsGlobalScope":true},{"version":"097a57355ded99c68e6df1b738990448e0bf170e606707df5a7c0481ff2427cd","affectsGlobalScope":true},{"version":"adb996790133eb33b33aadb9c09f15c2c575e71fb57a62de8bf74dbf59ec7dfb","affectsGlobalScope":true},{"version":"8cc8c5a3bac513368b0157f3d8b31cfdcfe78b56d3724f30f80ed9715e404af8","affectsGlobalScope":true},{"version":"cdccba9a388c2ee3fd6ad4018c640a471a6c060e96f1232062223063b0a5ac6a","affectsGlobalScope":true},{"version":"c5c05907c02476e4bde6b7e76a79ffcd948aedd14b6a8f56e4674221b0417398","affectsGlobalScope":true},{"version":"5f406584aef28a331c36523df688ca3650288d14f39c5d2e555c95f0d2ff8f6f","affectsGlobalScope":true},{"version":"22f230e544b35349cfb3bd9110b6ef37b41c6d6c43c3314a31bd0d9652fcec72","affectsGlobalScope":true},{"version":"7ea0b55f6b315cf9ac2ad622b0a7813315bb6e97bf4bb3fbf8f8affbca7dc695","affectsGlobalScope":true},{"version":"3013574108c36fd3aaca79764002b3717da09725a36a6fc02eac386593110f93","affectsGlobalScope":true},{"version":"eb26de841c52236d8222f87e9e6a235332e0788af8c87a71e9e210314300410a","affectsGlobalScope":true},{"version":"3be5a1453daa63e031d266bf342f3943603873d890ab8b9ada95e22389389006","affectsGlobalScope":true},{"version":"17bb1fc99591b00515502d264fa55dc8370c45c5298f4a5c2083557dccba5a2a","affectsGlobalScope":true},{"version":"7ce9f0bde3307ca1f944119f6365f2d776d281a393b576a18a2f2893a2d75c98","affectsGlobalScope":true},{"version":"6a6b173e739a6a99629a8594bfb294cc7329bfb7b227f12e1f7c11bc163b8577","affectsGlobalScope":true},{"version":"81cac4cbc92c0c839c70f8ffb94eb61e2d32dc1c3cf6d95844ca099463cf37ea","affectsGlobalScope":true},{"version":"b0124885ef82641903d232172577f2ceb5d3e60aed4da1153bab4221e1f6dd4e","affectsGlobalScope":true},{"version":"0eb85d6c590b0d577919a79e0084fa1744c1beba6fd0d4e951432fa1ede5510a","affectsGlobalScope":true},{"version":"da233fc1c8a377ba9e0bed690a73c290d843c2c3d23a7bd7ec5cd3d7d73ba1e0","affectsGlobalScope":true},{"version":"d154ea5bb7f7f9001ed9153e876b2d5b8f5c2bb9ec02b3ae0d239ec769f1f2ae","affectsGlobalScope":true},{"version":"bb2d3fb05a1d2ffbca947cc7cbc95d23e1d053d6595391bd325deb265a18d36c","affectsGlobalScope":true},{"version":"c80df75850fea5caa2afe43b9949338ce4e2de086f91713e9af1a06f973872b8","affectsGlobalScope":true},{"version":"9d57b2b5d15838ed094aa9ff1299eecef40b190722eb619bac4616657a05f951","affectsGlobalScope":true},{"version":"6c51b5dd26a2c31dbf37f00cfc32b2aa6a92e19c995aefb5b97a3a64f1ac99de","affectsGlobalScope":true},{"version":"6e7997ef61de3132e4d4b2250e75343f487903ddf5370e7ce33cf1b9db9a63ed","affectsGlobalScope":true},{"version":"2ad234885a4240522efccd77de6c7d99eecf9b4de0914adb9a35c0c22433f993","affectsGlobalScope":true},{"version":"5e5e095c4470c8bab227dbbc61374878ecead104c74ab9960d3adcccfee23205","affectsGlobalScope":true},{"version":"09aa50414b80c023553090e2f53827f007a301bc34b0495bfb2c3c08ab9ad1eb","affectsGlobalScope":true},{"version":"d7f680a43f8cd12a6b6122c07c54ba40952b0c8aa140dcfcf32eb9e6cb028596","affectsGlobalScope":true},{"version":"3787b83e297de7c315d55d4a7c546ae28e5f6c0a361b7a1dcec1f1f50a54ef11","affectsGlobalScope":true},{"version":"e7e8e1d368290e9295ef18ca23f405cf40d5456fa9f20db6373a61ca45f75f40","affectsGlobalScope":true},{"version":"faf0221ae0465363c842ce6aa8a0cbda5d9296940a8e26c86e04cc4081eea21e","affectsGlobalScope":true},{"version":"06393d13ea207a1bfe08ec8d7be562549c5e2da8983f2ee074e00002629d1871","affectsGlobalScope":true},{"version":"2768ef564cfc0689a1b76106c421a2909bdff0acbe87da010785adab80efdd5c","affectsGlobalScope":true},{"version":"b248e32ca52e8f5571390a4142558ae4f203ae2f94d5bac38a3084d529ef4e58","affectsGlobalScope":true},{"version":"52d1bb7ab7a3306fd0375c8bff560feed26ed676a5b0457fa8027b563aecb9a4","affectsGlobalScope":true},"f58c6d0669d1ee04b2fcb0e87fb949b6828602f3f29c7bf6c76a214f16fd50d5",{"version":"c97a04ad62c7c3b4749ac2e96bf063bbb57ba6add3bc09e0ec33f571c19fa7ba","signature":"a5f2d01e969ab9e23a8a5f811d7f4205375120812d563b52d8996218594ea098","affectsGlobalScope":true},{"version":"b9734142a4b241cfb505be4a2eb0261d211647df7c73043f817f4fdd8d96c846","affectsGlobalScope":true},{"version":"f4c0db3a49cea9babd5d224ba14243a6a6119bf65a65198994033aaea3a60a71","affectsGlobalScope":true},"946bd1737d9412395a8f24414c70f18660b84a75a12b0b448e6eb1a2161d06dd","812ce645854c0a7dc3319d5c4f976d7bc76026231435e78e6d3e51407b38477a","a3e5b8b86e7bd38d9afdc294875c4445c535319e288d3a13c1e2e41f9af934f2","8b06ac3faeacb8484d84ddb44571d8f410697f98d7bfa86c0fda60373a9f5215","675e702f2032766a91eeadee64f51014c64688525da99dccd8178f0c599f13a8","458111fc89d11d2151277c822dfdc1a28fa5b6b2493cf942e37d4cd0a6ee5f22","d70c026dd2eeaa974f430ea229230a1897fdb897dc74659deebe2afd4feeb08f","187119ff4f9553676a884e296089e131e8cc01691c546273b1d0089c3533ce42","febf0b2de54781102b00f61653b21377390a048fbf5262718c91860d11ff34a6","98f9d826db9cd99d27a01a59ee5f22863df00ccf1aaf43e1d7db80ebf716f7c3","0aaef8cded245bf5036a7a40b65622dd6c4da71f7a35343112edbe112b348a1e","00baffbe8a2f2e4875367479489b5d43b5fc1429ecb4a4cc98cfc3009095f52a","dcd91d3b697cb650b95db5471189b99815af5db2a1cd28760f91e0b12ede8ed5","3c92b6dfd43cc1c2485d9eba5ff0b74a19bb8725b692773ef1d66dac48cda4bd","3cf0d343c2276842a5b617f22ba82af6322c7cfe8bb52238ffc0c491a3c21019","df996e25faa505f85aeb294d15ebe61b399cf1d1e49959cdfaf2cc0815c203f9",{"version":"f2eff8704452659641164876c1ef0df4174659ce7311b0665798ea3f556fa9ad","affectsGlobalScope":true},"cf93e7b09b66e142429611c27ba2cbf330826057e3c793e1e2861e976fae3940","90e727d145feb03695693fdc9f165a4dc10684713ee5f6aa81e97a6086faa0f8","ee2c6ec73c636c9da5ab4ce9227e5197f55a57241d66ea5828f94b69a4a09a2d","afaf64477630c7297e3733765046c95640ab1c63f0dfb3c624691c8445bc3b08","5aa03223a53ad03171988820b81a6cae9647eabcebcb987d1284799de978d8e3","7f50c8914983009c2b940923d891e621db624ba32968a51db46e0bf480e4e1cb","90fc18234b7d2e19d18ac026361aaf2f49d27c98dc30d9f01e033a9c2b01c765","a980e4d46239f344eb4d5442b69dcf1d46bd2acac8d908574b5a507181f7e2a1","bbbfa4c51cdaa6e2ef7f7be3ae199b319de6b31e3b5afa7e5a2229c14bb2568a","bc7bfe8f48fa3067deb3b37d4b511588b01831ba123a785ea81320fe74dd9540","fd60c0aaf7c52115f0e7f367d794657ac18dbb257255777406829ab65ca85746","15c17866d58a19f4a01a125f3f511567bd1c22235b4fd77bf90c793bf28388c3","51301a76264b1e1b4046f803bda44307fba403183bc274fe9e7227252d7315cb","ddef23e8ace6c2b2ddf8d8092d30b1dd313743f7ff47b2cbb43f36c395896008","9e42df47111429042b5e22561849a512ad5871668097664b8fb06a11640140ac","391fcc749c6f94c6c4b7f017c6a6f63296c1c9ae03fa639f99337dddb9cc33fe","ac4706eb1fb167b19f336a93989763ab175cd7cc6227b0dcbfa6a7824c6ba59a","633220dc1e1a5d0ccf11d3c3e8cadc9124daf80fef468f2ff8186a2775229de3","6de22ad73e332e513454f0292275155d6cb77f2f695b73f0744928c4ebb3a128","ebe0e3c77f5114b656d857213698fade968cff1b3a681d1868f3cfdd09d63b75","22c27a87488a0625657b52b9750122814c2f5582cac971484cda0dcd7a46dc3b","7e7a817c8ec57035b2b74df8d5dbcc376a4a60ad870b27ec35463536158e1156","0e2061f86ca739f34feae42fd7cce27cc171788d251a587215b33eaec456e786","91659b2b090cadffdb593736210910508fc5b77046d4ce180b52580b14b075ec","d0f6c657c45faaf576ca1a1dc64484534a8dc74ada36fd57008edc1aab65a02b","ce0c52b1ebc023b71d3c1fe974804a2422cf1d85d4af74bb1bced36ff3bff8b5","9c6acb4a388887f9a5552eda68987ee5d607152163d72f123193a984c48157c9","90d0a9968cbb7048015736299f96a0cceb01cf583fd2e9a9edbc632ac4c81b01","49abec0571c941ab6f095885a76828d50498511c03bb326eec62a852e58000c5","8eeb4a4ff94460051173d561749539bca870422a6400108903af2fb7a1ffe3d7","49e39b284b87452fed1e27ac0748ba698f5a27debe05084bc5066b3ecf4ed762","59dcf835762f8df90fba5a3f8ba87941467604041cf127fb456543c793b71456","33e0c4c683dcaeb66bedf5bb6cc35798d00ac58d7f3bc82aadb50fa475781d60","605839abb6d150b0d83ed3712e1b3ffbeb309e382770e7754085d36bc2d84a4c","30905c89260ca8da4e72345c72f8e80beb9cc39ee2ee48261e63f76ea1874d1e","0f0a16a0e8037c17e28f537028215e87db047eba52281bd33484d5395402f3c1","530192961885d3ddad87bf9c4390e12689fa29ff515df57f17a57c9125fc77c3","cf533aed4c455b526ddccbb10dae7cc77e9269c3d7862f9e5cedbd4f5c92e05e","f8a60ca31702a0209ef217f8f3b4b32f498813927df2304787ac968c78d8560d","165ba9e775dd769749e2177c383d24578e3b212e4774b0a72ad0f6faee103b68","61448f238fdfa94e5ccce1f43a7cced5e548b1ea2d957bec5259a6e719378381","69fa523e48131ced0a52ab1af36c3a922c5fd7a25e474d82117329fe051f5b85","fa10b79cd06f5dd03435e184fb05cc5f0d02713bfb4ee9d343db527501be334c","c6fb591e363ee4dea2b102bb721c0921485459df23a2d2171af8354cacef4bce","ea7e1f1097c2e61ed6e56fa04a9d7beae9d276d87ac6edb0cd39a3ee649cddfe","e8cf2659d87462aae9c7647e2a256ac7dcaf2a565a9681bfb49328a8a52861e8","7e374cb98b705d35369b3c15444ef2ff5ff983bd2fbb77a287f7e3240abf208c","ca75ba1519f9a426b8c512046ebbad58231d8627678d054008c93c51bc0f3fa5","ff63760147d7a60dcfc4ac16e40aa2696d016b9ffe27e296b43655dfa869d66b","4d434123b16f46b290982907a4d24675442eb651ca95a5e98e4c274be16f1220","57263d6ba38046e85f499f3c0ab518cfaf0a5f5d4f53bdae896d045209ab4aff","d3a535f2cd5d17f12b1abf0b19a64e816b90c8c10a030b58f308c0f7f2acfe2c","be26d49bb713c13bd737d00ae8a61aa394f0b76bc2d5a1c93c74f59402eb8db3","c7012003ac0c9e6c9d3a6418128ddebf6219d904095180d4502b19c42f46a186","d58c55750756bcf73f474344e6b4a9376e5381e4ba7d834dc352264b491423b6","01e2aabfabe22b4bf6d715fc54d72d32fa860a3bd1faa8974e0d672c4b565dfe","ba2c489bb2566c16d28f0500b3d98013917e471c40a4417c03991460cb248e88","39f94b619f0844c454a6f912e5d6868d0beb32752587b134c3c858b10ecd7056","0d2d8b0477b1cf16b34088e786e9745c3e8145bc8eea5919b700ad054e70a095","2a5e963b2b8f33a50bb516215ba54a20801cb379a8e9b1ae0b311e900dc7254c","d8307f62b55feeb5858529314761089746dce957d2b8fd919673a4985fa4342a","bf449ec80fc692b2703ad03e64ae007b3513ecd507dc2ab77f39be6f578e6f5c","f780213dd78998daf2511385dd51abf72905f709c839a9457b6ba2a55df57be7","2b7843e8a9a50bdf511de24350b6d429a3ee28430f5e8af7d3599b1e9aa7057f","05d95be6e25b4118c2eb28667e784f0b25882f6a8486147788df675c85391ab7","62d2721e9f2c9197c3e2e5cffeb2f76c6412121ae155153179049890011eb785","ff5668fb7594c02aca5e7ba7be6c238676226e450681ca96b457f4a84898b2d9","59fd37ea08657fef36c55ddea879eae550ffe21d7e3a1f8699314a85a30d8ae9","84e23663776e080e18b25052eb3459b1a0486b5b19f674d59b96347c0cb7312a","43e5934c7355731eec20c5a2aa7a859086f19f60a4e5fcd80e6684228f6fb767","a49c210c136c518a7c08325f6058fc648f59f911c41c93de2026db692bba0e47","1a92f93597ebc451e9ef4b158653c8d31902de5e6c8a574470ecb6da64932df4","256513ad066ac9898a70ca01e6fbdb3898a4e0fe408fbf70608fdc28ac1af224","d9835850b6cc05c21e8d85692a8071ebcf167a4382e5e39bf700c4a1e816437e","e5ab7190f818442e958d0322191c24c2447ddceae393c4e811e79cda6bd49836","91b4b77ef81466ce894f1aade7d35d3589ddd5c9981109d1dea11f55a4b807a0","03abb209bed94c8c893d9872639e3789f0282061c7aa6917888965e4047a8b5f","e97a07901de562219f5cba545b0945a1540d9663bd9abce66495721af3903eec","bf39ed1fdf29bc8178055ec4ff32be6725c1de9f29c252e31bdc71baf5c227e6","985eabf06dac7288fc355435b18641282f86107e48334a83605739a1fe82ac15","6112d33bcf51e3e6f6a81e419f29580e2f8e773529d53958c7c1c99728d4fb2e","89e9f7e87a573504acc2e7e5ad727a110b960330657d1b9a6d3526e77c83d8be","44bbb88abe9958c7c417e8687abf65820385191685009cc4b739c2d270cb02e9","ab4b506b53d2c4aec4cc00452740c540a0e6abe7778063e95c81a5cd557c19eb","858757bde6d615d0d1ee474c972131c6d79c37b0b61897da7fbd7110beb8af12","60b9dea33807b086a1b4b4b89f72d5da27ad0dd36d6436a6e306600c47438ac4","409c963b1166d0c1d49fdad1dfeb4de27fd2d6662d699009857de9baf43ca7c3","b7674ecfeb5753e965404f7b3d31eec8450857d1a23770cb867c82f264f546ab","c9800b9a9ad7fcdf74ed8972a5928b66f0e4ff674d55fd038a3b1c076911dcbe","99864433e35b24c61f8790d2224428e3b920624c01a6d26ea8b27ee1f62836bb","c391317b9ff8f87d28c6bfe4e50ed92e8f8bfab1bb8a03cd1fe104ff13186f83","42bdc3c98446fdd528e2591213f71ce6f7008fb9bb12413bd57df60d892a3fb5","542d2d689b58c25d39a76312ccaea2fcd10a45fb27b890e18015399c8032e2d9","97d1656f0a563dbb361d22b3d7c2487427b0998f347123abd1c69a4991326c96","d4f53ed7960c9fba8378af3fa28e3cc483d6c0b48e4a152a83ff0973d507307d","0665de5280d65ec32776dc55fb37128e259e60f389cde5b9803cf9e81ad23ce0","73b6945448bb3425b764cfe7b1c4b0b56c010cc66e5f438ef320c53e469797eb","b6dc8fd1c6092da86725c338ca6c263d1c6dd3073046d3ec4eb2d68515062da2","d9198a0f01f00870653347560e10494efeca0bfa2de0988bd5d883a9d2c47edb","d4279865b926d7e2cfe8863b2eae270c4c035b6e923af8f9d7e6462d68679e07","cf72fd8ffa5395f4f1a26be60246ec79c5a9ad201579c9ba63fd2607b5daf184","301a458744666096f84580a78cc3f6e8411f8bab92608cdaa33707546ca2906f","711e70c0916ff5f821ea208043ecd3e67ed09434b8a31d5616286802b58ebebe","e1f2fd9f88dd0e40c358fbf8c8f992211ab00a699e7d6823579b615b874a8453","17db3a9dcb2e1689ff7ace9c94fa110c88da64d69f01dc2f3cec698e4fc7e29e","73fb07305106bb18c2230890fcacf910fd1a7a77d93ac12ec40bc04c49ee5b8e","2c5f341625a45530b040d59a4bc2bc83824d258985ede10c67005be72d3e21d0","c4a262730d4277ecaaf6f6553dabecc84dcca8decaebbf2e16f1df8bbd996397","c23c533d85518f3358c55a7f19ab1a05aad290251e8bba0947bd19ea3c259467","5d0322a0b8cdc67b8c71e4ccaa30286b0c8453211d4c955a217ac2d3590e911f","f5e4032b6e4e116e7fec5b2620a2a35d0b6b8b4a1cc9b94a8e5ee76190153110","9ab26cb62a0e86ab7f669c311eb0c4d665457eb70a103508aa39da6ccee663da","5f64d1a11d8d4ce2c7ee3b72471df76b82d178a48964a14cdfdc7c5ef7276d70","24e2fbc48f65814e691d9377399807b9ec22cd54b51d631ba9e48ee18c5939dd","bfa2648b2ee90268c6b6f19e84da3176b4d46329c9ec0555d470e647d0568dfb","75ef3cb4e7b3583ba268a094c1bd16ce31023f2c3d1ac36e75ca65aca9721534","3be6b3304a81d0301838860fd3b4536c2b93390e785808a1f1a30e4135501514","da66c1b3e50ef9908e31ce7a281b137b2db41423c2b143c62524f97a536a53d9","3ada1b216e45bb9e32e30d8179a0a95870576fe949c33d9767823ccf4f4f4c97","1ace2885dffab849f7c98bffe3d1233260fbf07ee62cb58130167fd67a376a65","2126e5989c0ca5194d883cf9e9c10fe3e5224fbd3e4a4a6267677544e8be0aae","41a6738cf3c756af74753c5033e95c5b33dfc1f6e1287fa769a1ac4027335bf5","6e8630be5b0166cbc9f359b9f9e42801626d64ff1702dcb691af811149766154","e36b77c04e00b4a0bb4e1364f2646618a54910c27f6dc3fc558ca2ced8ca5bc5","2c4ea7e9f95a558f46c89726d1fedcb525ef649eb755a3d7d5055e22b80c2904","4875d65190e789fad05e73abd178297b386806b88b624328222d82e455c0f2e7","bf5302ecfaacee37c2316e33703723d62e66590093738c8921773ee30f2ecc38","62684064fe034d54b87f62ad416f41b98a405dee4146d0ec03b198c3634ea93c","be02cbdb1688c8387f8a76a9c6ed9d75d8bb794ec5b9b1d2ba3339a952a00614","cefaff060473a5dbf4939ee1b52eb900f215f8d6249dc7c058d6b869d599983c","b2797235a4c1a7442a6f326f28ffb966226c3419399dbb33634b8159af2c712f","164d633bbd4329794d329219fc173c3de85d5ad866d44e5b5f0fb60c140e98f2","b74300dd0a52eaf564b3757c07d07e1d92def4e3b8708f12eedb40033e4cafe9","a792f80b1e265b06dce1783992dbee2b45815a7bdc030782464b8cf982337cf2","8816b4b3a87d9b77f0355e616b38ed5054f993cc4c141101297f1914976a94b1","0f35e4da974793534c4ca1cdd9491eab6993f8cf47103dadfc048b899ed9b511","0ccdfcaebf297ec7b9dde20bbbc8539d5951a3d8aaa40665ca469da27f5a86e1","7fcb05c8ce81f05499c7b0488ae02a0a1ac6aebc78c01e9f8c42d98f7ba68140","81c376c9e4d227a4629c7fca9dde3bbdfa44bd5bd281aee0ed03801182368dc5","0f2448f95110c3714797e4c043bbc539368e9c4c33586d03ecda166aa9908843","b2f1a443f7f3982d7325775906b51665fe875c82a62be3528a36184852faa0bb","7568ff1f23363d7ee349105eb936e156d61aea8864187a4c5d85c60594b44a25","8c4d1d9a4eba4eac69e6da0f599a424b2689aee55a455f0b5a7f27a807e064db","e1beb9077c100bdd0fc8e727615f5dae2c6e1207de224569421907072f4ec885","3dda13836320ec71b95a68cd3d91a27118b34c05a2bfda3e7e51f1d8ca9b960b","fedc79cb91f2b3a14e832d7a8e3d58eb02b5d5411c843fcbdc79e35041316b36","99f395322ffae908dcdfbaa2624cc7a2a2cb7b0fbf1a1274aca506f7b57ebcb5","5e1f7c43e8d45f2222a5c61cbc88b074f4aaf1ca4b118ac6d6123c858efdcd71","7388273ab71cb8f22b3f25ffd8d44a37d5740077c4d87023da25575204d57872","0a48ceb01a0fdfc506aa20dfd8a3563edbdeaa53a8333ddf261d2ee87669ea7b","3182d06b874f31e8e55f91ea706c85d5f207f16273480f46438781d0bd2a46a1","ccd47cab635e8f71693fa4e2bbb7969f559972dae97bd5dbd1bbfee77a63b410","89770fa14c037f3dc3882e6c56be1c01bb495c81dec96fa29f868185d9555a5d","7048c397f08c54099c52e6b9d90623dc9dc6811ea142f8af3200e40d66a972e1","512120cd6f026ce1d3cf686c6ab5da80caa40ef92aa47466ec60ba61a48b5551","6cd0cb7f999f221e984157a7640e7871960131f6b221d67e4fdc2a53937c6770","f48b84a0884776f1bc5bf0fcf3f69832e97b97dc55d79d7557f344de900d259b","dca490d986411644b0f9edf6ea701016836558e8677c150dca8ad315178ec735","a028a04948cf98c1233166b48887dad324e8fe424a4be368a287c706d9ccd491","3046ed22c701f24272534b293c10cfd17b0f6a89c2ec6014c9a44a90963dfa06","394da10397d272f19a324c95bea7492faadf2263da157831e02ae1107bd410f5","0580595a99248b2d30d03f2307c50f14eb21716a55beb84dd09d240b1b087a42","a7da9510150f36a9bea61513b107b59a423fdff54429ad38547c7475cd390e95","659615f96e64361af7127645bb91f287f7b46c5d03bea7371e6e02099226d818","1f2a42974920476ce46bb666cd9b3c1b82b2072b66ccd0d775aa960532d78176","500b3ae6095cbab92d81de0b40c9129f5524d10ad955643f81fc07d726c5a667","a957ad4bd562be0662fb99599dbcf0e16d1631f857e5e1a83a3f3afb6c226059","e57a4915266a6a751c6c172e8f30f6df44a495608613e1f1c410196207da9641","7a12e57143b7bc5a52a41a8c4e6283a8f8d59a5e302478185fb623a7157fff5e","17b3426162e1d9cb0a843e8d04212aabe461d53548e671236de957ed3ae9471b","f38e86eb00398d63180210c5090ef6ed065004474361146573f98b3c8a96477d","231d9e32382d3971f58325e5a85ba283a2021243651cb650f82f87a1bf62d649","6532e3e87b87c95f0771611afce929b5bad9d2c94855b19b29b3246937c9840b","65704bbb8f0b55c73871335edd3c9cead7c9f0d4b21f64f5d22d0987c45687f0","787232f574af2253ac860f22a445c755d57c73a69a402823ae81ba0dfdd1ce23","5e63903cd5ebce02486b91647d951d61a16ad80d65f9c56581cd624f39a66007","bcc89a120d8f3c02411f4df6b1d989143c01369314e9b0e04794441e6b078d22","d17531ef42b7c76d953f63bd5c5cd927c4723e62a7e0b2badf812d5f35f784eb","6d4ee1a8e3a97168ea4c4cc1c68bb61a3fd77134f15c71bb9f3f63df3d26b54c","1eb04fea6b47b16922ed79625d90431a8b2fc7ba9d5768b255e62df0c96f1e3a","de0c2eece83bd81b8682f4496f558beb728263e17e74cbc4910e5c9ce7bef689","98866542d45306dab48ecc3ddd98ee54fa983353bc3139dfbc619df882f54d90","9e04c7708917af428c165f1e38536ddb2e8ecd576f55ed11a97442dc34b6b010","31fe6f6d02b53c1a7c34b8d8f8c87ee9b6dd4b67f158cbfff3034b4f3f69c409","2e1d853f84188e8e002361f4bfdd892ac31c68acaeac426a63cd4ff7abf150d0","666b5289ec8a01c4cc0977c62e3fd32e89a8e3fd9e97c8d8fd646f632e63c055","a1107bbb2b10982dba1f7958a6a5cf841e1a19d6976d0ecdc4c43269c7b0eaf2","07fa6122f7495331f39167ec9e4ebd990146a20f99c16c17bc0a98aa81f63b27","39c1483481b35c2123eaab5094a8b548a0c3f1e483ab7338102c3291f1ab18bf","b73e6242c13796e7d5fba225bf1c07c8ee66d31b7bb65f45be14226a9ae492d2","f2931608d541145d189390d6cfb74e1b1e88f73c0b9a80c4356a4daa7fa5e005","8684656fe3bf1425a91bd62b8b455a1c7ec18b074fd695793cfae44ae02e381a","ccf0b9057dd65c7fb5e237de34f706966ebc30c6d3669715ed05e76225f54fbd","d930f077da575e8ea761e3d644d4c6279e2d847bae2b3ea893bbd572315acc21","19b0616946cb615abde72c6d69049f136cc4821b784634771c1d73bec8005f73","553312560ad0ef97b344b653931935d6e80840c2de6ab90b8be43cbacf0d04cf","07d4cdb1a845383da7ab950f5855ee441bd8a038c181d57f6b566822db80a972","f7cb9e46bd6ab9d620d68257b525dbbbbc9b0b148adf500b819d756ebc339de0","e46d6c3120aca07ae8ec3189edf518c667d027478810ca67a62431a0fa545434","9d234b7d2f662a135d430d3190fc21074325f296273125244b2bf8328b5839a0","0554ef14d10acea403348c53436b1dd8d61e7c73ef5872e2fe69cc1c433b02f8","2f6ae5538090db60514336bd1441ca208a8fab13108cfa4b311e61eaca5ff716","17bf4ce505a4cff88fb56177a8f7eb48aa55c22ccc4cce3e49cc5c8ddc54b07d","3d735f493d7da48156b79b4d8a406bf2bbf7e3fe379210d8f7c085028143ee40","41de1b3ddd71bd0d9ed7ac217ca1b15b177dd731d5251cde094945c20a715d03","17d9c562a46c6a25bc2f317c9b06dd4e8e0368cbe9bdf89be6117aeafd577b36","ded799031fe18a0bb5e78be38a6ae168458ff41b6c6542392b009d2abe6a6f32","ed48d467a7b25ee1a2769adebc198b647a820e242c96a5f96c1e6c27a40ab131","b914114df05f286897a1ae85d2df39cfd98ed8da68754d73cf830159e85ddd15","73881e647da3c226f21e0b80e216feaf14a5541a861494c744e9fbe1c3b3a6af","d79e1d31b939fa99694f2d6fbdd19870147401dbb3f42214e84c011e7ec359ab","4f71097eae7aa37941bab39beb2e53e624321fd341c12cc1d400eb7a805691ff","58ebb4f21f3a90dda31a01764462aa617849fdb1b592f3a8d875c85019956aff","a8e8d0e6efff70f3c28d3e384f9d64530c7a7596a201e4879a7fd75c7d55cbb5","df5cbb80d8353bf0511a4047cc7b8434b0be12e280b6cf3de919d5a3380912c0","256eb0520e822b56f720962edd7807ed36abdf7ea23bcadf4a25929a3317c8cf","9cf2cbc9ceb5f718c1705f37ce5454f14d3b89f690d9864394963567673c1b5c","07d3dd790cf1e66bb6fc9806d014dd40bb2055f8d6ca3811cf0e12f92ba4cb9a","1f99fd62e9cff9b50c36f368caf3b9fb79fc6f6c75ca5d3c2ec4afaea08d9109","6558faaacba5622ef7f1fdfb843cd967af2c105469b9ff5c18a81ce85178fca7","34e7f17ae9395b0269cd3f2f0af10709e6dc975c5b44a36b6b70442dc5e25a38","a4295111b54f84c02c27e46b0855b02fad3421ae1d2d7e67ecf16cb49538280a","ce9746b2ceae2388b7be9fe1f009dcecbc65f0bdbc16f40c0027fab0fb848c3b","35ce823a59f397f0e85295387778f51467cea137d787df385be57a2099752bfb","2e5acd3ec67bc309e4f679a70c894f809863c33b9572a8da0b78db403edfa106","1872f3fcea0643d5e03b19a19d777704320f857d1be0eb4ee372681357e20c88","9689628941205e40dcbb2706d1833bd00ce7510d333b2ef08be24ecbf3eb1a37","0317a72a0b63094781476cf1d2d27585d00eb2b0ca62b5287124735912f3d048","6ce4c0ab3450a4fff25d60a058a25039cffd03141549589689f5a17055ad0545","9153ec7b0577ae77349d2c5e8c5dd57163f41853b80c4fb5ce342c7a431cbe1e","f490dfa4619e48edd594a36079950c9fca1230efb3a82aaf325047262ba07379","674f00085caff46d2cbc76fc74740fd31f49d53396804558573421e138be0c12","41d029194c4811f09b350a1e858143c191073007a9ee836061090ed0143ad94f","44a6259ffd6febd8510b9a9b13a700e1d022530d8b33663f0735dbb3bee67b3d","6f4322500aff8676d9b8eef7711c7166708d4a0686b792aa4b158e276ed946a7","e829ff9ecffa3510d3a4d2c3e4e9b54d4a4ccfef004bacbb1d6919ce3ccca01f","62e6fec9dbd012460b47af7e727ec4cd34345b6e4311e781f040e6b640d7f93e","4d180dd4d0785f2cd140bc069d56285d0121d95b53e4348feb4f62db2d7035d3","f1142cbba31d7f492d2e7c91d82211a8334e6642efe52b71d9a82cb95ba4e8ae","279cac827be5d48c0f69fe319dc38c876fdd076b66995d9779c43558552d8a50","a70ff3c65dc0e7213bfe0d81c072951db9f5b1e640eb66c1eaed0737879c797b","f75d3303c1750f4fdacd23354657eca09aae16122c344e65b8c14c570ff67df5","3ebae6a418229d4b303f8e0fdb14de83f39fba9f57b39d5f213398bca72137c7","21ba07e33265f59d52dece5ac44f933b2b464059514587e64ad5182ddf34a9b0","2d3d96efba00493059c460fd55e6206b0667fc2e73215c4f1a9eb559b550021f","d23d4a57fff5cec5607521ba3b72f372e3d735d0f6b11a4681655b0bdd0505f4","395c1f3da7e9c87097c8095acbb361541480bf5fd7fa92523985019fef7761dd","d61f3d719293c2f92a04ba73d08536940805938ecab89ac35ceabc8a48ccb648","ca693235a1242bcd97254f43a17592aa84af66ccb7497333ccfea54842fde648","cd41cf040b2e368382f2382ec9145824777233730e3965e9a7ba4523a6a4698e","2e7a9dba6512b0310c037a28d27330520904cf5063ca19f034b74ad280dbfe71","9f2a38baf702e6cb98e0392fa39d25a64c41457a827b935b366c5e0980a6a667","c1dc37f0e7252928f73d03b0d6b46feb26dea3d8737a531ca4c0ec4105e33120","25126b80243fb499517e94fc5afe5c9c5df3a0105618e33581fb5b2f2622f342","d332c2ddcb64012290eb14753c1b49fe3eee9ca067204efba1cf31c1ce1ee020","1be8da453470021f6fe936ba19ee0bfebc7cfa2406953fa56e78940467c90769","d0163ab7b0de6e23b8562af8b5b4adea4182884ca7543488f7ac2a3478f3ae6e","05224e15c6e51c4c6cd08c65f0766723f6b39165534b67546076c226661db691","a5f7158823c7700dd9fc1843a94b9edc309180c969fbfa6d591aeb0b33d3b514","7d30937f8cf9bb0d4b2c2a8fb56a415d7ef393f6252b24e4863f3d7b84285724","e04d074584483dc9c59341f9f36c7220f16eed09f7af1fa3ef9c64c26095faec","619697e06cbc2c77edda949a83a62047e777efacde1433e895b904fe4877c650","88d9a8593d2e6aee67f7b15a25bda62652c77be72b79afbee52bea61d5ffb39e","044d7acfc9bd1af21951e32252cf8f3a11c8b35a704169115ddcbde9fd717de2","a4ca8f13a91bd80e6d7a4f013b8a9e156fbf579bbec981fe724dad38719cfe01","5a216426a68418e37e55c7a4366bc50efc99bda9dc361eae94d7e336da96c027","13b65b640306755096d304e76d4a237d21103de88b474634f7ae13a2fac722d5","7478bd43e449d3ce4e94f3ed1105c65007b21f078b3a791ea5d2c47b30ea6962","601d3e8e71b7d6a24fc003aca9989a6c25fa2b3755df196fd0aaee709d190303","168e0850fcc94011e4477e31eca81a8a8a71e1aed66d056b7b50196b877e86c8","37ba82d63f5f8c6b4fc9b756f24902e47f62ea66aae07e89ace445a54190a86e","f5b66b855f0496bc05f1cd9ba51a6a9de3d989b24aa36f6017257f01c8b65a9f","823b16d378e8456fcc5503d6253c8b13659be44435151c6b9f140c4a38ec98c1","b58b254bf1b586222844c04b3cdec396e16c811463bf187615bb0a1584beb100","a367c2ccfb2460e222c5d10d304e980bd172dd668bcc02f6c2ff626e71e90d75","0718623262ac94b016cb0cfd8d54e4d5b7b1d3941c01d85cf95c25ec1ba5ed8d","d4f3c9a0bd129e9c7cbfac02b6647e34718a2b81a414d914e8bd6b76341172e0","824306df6196f1e0222ff775c8023d399091ada2f10f2995ce53f5e3d4aff7a4","84ca07a8d57f1a6ba8c0cf264180d681f7afae995631c6ca9f2b85ec6ee06c0f","35755e61e9f4ec82d059efdbe3d1abcccc97a8a839f1dbf2e73ac1965f266847","64a918a5aa97a37400ec085ffeea12a14211aa799cd34e5dc828beb1806e95bb","0c8f5489ba6af02a4b1d5ba280e7badd58f30dc8eb716113b679e9d7c31185e5","3334c03c15102700973e3e334954ac1dffb7be7704c67cc272822d5895215c93","7b574ca9ae0417203cdfa621ab1585de5b90c4bc6eea77a465b2eb8b92aa5380","aabcb169451df7f78eb43567fab877a74d134a0a6d9850aa58b38321374ab7c0","1b5effdd8b4e8d9897fc34ab4cd708a446bf79db4cb9a3467e4a30d55b502e14","d772776a7aea246fd72c5818de72c3654f556b2cf0d73b90930c9c187cc055fc","dbd4bd62f433f14a419e4c6130075199eb15f2812d2d8e7c9e1f297f4daac788","427df949f5f10c73bcc77b2999893bc66c17579ad073ee5f5270a2b30651c873","c4c1a5565b9b85abfa1d663ca386d959d55361e801e8d49155a14dd6ca41abe1","7a45a45c277686aaff716db75a8157d0458a0d854bacf072c47fee3d499d7a99","57005b72bce2dc26293e8924f9c6be7ee3a2c1b71028a680f329762fa4439354","8f53b1f97c53c3573c16d0225ee3187d22f14f01421e3c6da1a26a1aace32356","810fdc0e554ed7315c723b91f6fa6ef3a6859b943b4cd82879641563b0e6c390","87a36b177b04d23214aa4502a0011cd65079e208cd60654aefc47d0d65da68ea","28a1c17fcbb9e66d7193caca68bbd12115518f186d90fc729a71869f96e2c07b","cc2d2abbb1cc7d6453c6fee760b04a516aa425187d65e296a8aacff66a49598a","d2413645bc4ab9c3f3688c5281232e6538684e84b49a57d8a1a8b2e5cf9f2041","4e6e21a0f9718282d342e66c83b2cd9aa7cd777dfcf2abd93552da694103b3dc","9006cc15c3a35e49508598a51664aa34ae59fc7ab32d6cc6ea2ec68d1c39448e","74467b184eadee6186a17cac579938d62eceb6d89c923ae67d058e2bcded254e","4169b96bb6309a2619f16d17307da341758da2917ff40c615568217b14357f5e","4a94d6146b38050de0830019a1c6a7820c2e2b90eba1a5ee4e4ab3bc30a72036","903e5ecbc1a8932bff30cc8d38addc4a58108688f9c5c107bd030fd8cccedcdc","209e814e8e71aec74f69686a9506dd7610b97ab59dcee9446266446f72a76d05","7e771891adaa85b690266bc37bd6eb43bc57eecc4b54693ead36467e7369952a","a69c09dbea52352f479d3e7ac949fde3d17b195abe90b045d619f747b38d6d1a",{"version":"ca72190df0eb9b09d4b600821c8c7b6c9747b75a1c700c4d57dc0bb72abc074c","affectsGlobalScope":true},"21a167fec8f933752fb8157f06d28fab6817af3ad9b0bdb1908a10762391eab9",{"version":"bb65c6267c5d6676be61acbf6604cf0a4555ac4b505df58ac15c831fcbff4e3e","affectsGlobalScope":true},"0c0cee62cb619aed81133b904f644515ba3064487002a7da83fd8aa07b1b4abd","5a94487653355b56018122d92392beb2e5f4a6c63ba5cef83bbe1c99775ef713",{"version":"d5135ad93b33adcce80b18f8065087934cdc1730d63db58562edcf017e1aad9b","affectsGlobalScope":true},"82408ed3e959ddc60d3e9904481b5a8dc16469928257af22a3f7d1a3bc7fd8c4","dab86d9604fe40854ef3c0a6f9e8948873dc3509213418e5e457f410fd11200f","bb9c4ffa5e6290c6980b63c815cdd1625876dadb2efaf77edbe82984be93e55e","489532ff54b714f0e0939947a1c560e516d3ae93d51d639ab02e907a0e950114","f30bb836526d930a74593f7b0f5c1c46d10856415a8f69e5e2fc3db80371e362","14b5aa23c5d0ae1907bc696ac7b6915d88f7d85799cc0dc2dcf98fbce2c5a67c","5c439dafdc09abe4d6c260a96b822fa0ba5be7203c71a63ab1f1423cd9e838ea",{"version":"6b526a5ec4a401ca7c26cfe6a48e641d8f30af76673bad3b06a1b4504594a960","affectsGlobalScope":true},{"version":"816ad2e607a96de5bcac7d437f843f5afd8957f1fa5eefa6bba8e4ed7ca8fd84","affectsGlobalScope":true},"cec36af22f514322f870e81d30675c78df82ae8bf4863f5fd4e4424c040c678d","d903fafe96674bc0b2ac38a5be4a8fc07b14c2548d1cdb165a80ea24c44c0c54","5eec82ac21f84d83586c59a16b9b8502d34505d1393393556682fe7e7fde9ef2","04eb6578a588d6a46f50299b55f30e3a04ef27d0c5a46c57d8fcc211cd530faa","8d3c583a07e0c37e876908c2d5da575019f689df8d9fa4c081d99119d53dba22","2c828a5405191d006115ab34e191b8474bc6c86ffdc401d1a9864b1b6e088a58",{"version":"e8b18c6385ff784228a6f369694fcf1a6b475355ba89090a88de13587a9391d5","affectsGlobalScope":true},"d076fede3cb042e7b13fc29442aaa03a57806bc51e2b26a67a01fbc66a7c0c12","7c013aa892414a7fdcfd861ae524a668eaa3ede8c7c0acafaf611948122c8d93","b0973c3cbcdc59b37bf477731d468696ecaf442593ec51bab497a613a580fe30",{"version":"4989e92ba5b69b182d2caaea6295af52b7dc73a4f7a2e336a676722884e7139d","affectsGlobalScope":true},{"version":"b3624aed92dab6da8484280d3cb3e2f4130ec3f4ef3f8201c95144ae9e898bb6","affectsGlobalScope":true},"5153a2fd150e46ce57bb3f8db1318d33f6ad3261ed70ceeff92281c0608c74a3","210d54cd652ec0fec8c8916e4af59bb341065576ecda039842f9ffb2e908507c","36b03690b628eab08703d63f04eaa89c5df202e5f1edf3989f13ad389cd2c091","0effadd232a20498b11308058e334d3339cc5bf8c4c858393e38d9d4c0013dcf","25846d43937c672bab7e8195f3d881f93495df712ee901860effc109918938cc","fd93cee2621ff42dabe57b7be402783fd1aa69ece755bcba1e0290547ae60513","1b952304137851e45bc009785de89ada562d9376177c97e37702e39e60c2f1ff","69ee23dd0d215b09907ad30d23f88b7790c93329d1faf31d7835552a10cf7cbf","44b8b584a338b190a59f4f6929d072431950c7bd92ec2694821c11bce180c8a5","23b89798789dffbd437c0c423f5d02d11f9736aea73d6abf16db4f812ff36eda","223c37f62ce09a3d99e77498acdee7b2705a4ae14552fbdb4093600cd9164f3f",{"version":"970a90f76d4d219ad60819d61f5994514087ba94c985647a3474a5a3d12714ed","affectsGlobalScope":true},"e10177274a35a9d07c825615340b2fcde2f610f53f3fb40269fd196b4288dda6","4c8525f256873c7ba3135338c647eaf0ca7115a1a2805ae2d0056629461186ce","3c13ef48634e7b5012fcf7e8fce7496352c2d779a7201389ca96a2a81ee4314d","5d0a25ec910fa36595f85a67ac992d7a53dd4064a1ba6aea1c9f14ab73a023f2",{"version":"f0900cd5d00fe1263ff41201fb8073dbeb984397e4af3b8002a5c207a30bdc33","affectsGlobalScope":true},{"version":"4c50342e1b65d3bee2ed4ab18f84842d5724ad11083bd666d8705dc7a6079d80","affectsGlobalScope":true},"06d7c42d256f0ce6afe1b2b6cfbc97ab391f29dadb00dd0ae8e8f23f5bc916c3","ec4bd1b200670fb567920db572d6701ed42a9641d09c4ff6869768c8f81b404c","e59a892d87e72733e2a9ca21611b9beb52977be2696c7ba4b216cbbb9a48f5aa",{"version":"da26af7362f53d122283bc69fed862b9a9fe27e01bc6a69d1d682e0e5a4df3e6","affectsGlobalScope":true},"8a300fa9b698845a1f9c41ecbe2c5966634582a8e2020d51abcace9b55aa959e",{"version":"ab9b9a36e5284fd8d3bf2f7d5fcbc60052f25f27e4d20954782099282c60d23e","affectsGlobalScope":true},"8dbe725f8d237e70310977afcfa011629804d101ebaa0266cafda6b61ad72236","6fa0008bf91a4cc9c8963bace4bba0bd6865cbfa29c3e3ccc461155660fb113a","6a386ff939f180ae8ef064699d8b7b6e62bc2731a62d7fbf5e02589383838dea",{"version":"bbdf156fea2fabed31a569445835aeedcc33643d404fcbaa54541f06c109df3f","affectsGlobalScope":true},"1c29793071152b207c01ea1954e343be9a44d85234447b2b236acae9e709a383","f5a8b384f182b3851cec3596ccc96cb7464f8d3469f48c74bf2befb782a19de5",{"version":"ef8a481f9f2205fcc287eef2b4e461d2fc16bc8a0e49a844681f2f742d69747e","affectsGlobalScope":true},"e4dd91dd4789a109aab51d8a0569a282369fcda9ba6f2b2297bc61bacfb1a042","74b0245c42990ed8a849df955db3f4362c81b13f799ebc981b7bec2d5b414a57","87352bb579421f6938177a53bb66e8514067b4872ccaa5fe08ddbca56364570c"],"options":{"composite":true,"declaration":true,"declarationMap":true,"emitDeclarationOnly":true,"esModuleInterop":true,"inlineSources":false,"module":99,"noUnusedLocals":false,"noUnusedParameters":false,"outDir":"./dist","rootDir":"./src","skipLibCheck":true,"strict":true,"target":7},"fileIdsList":[[48,418],[418],[51,418],[66,418],[67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,418],[54,56,57,58,59,60,61,62,63,64,65,66,418],[54,55,57,58,59,60,61,62,63,64,65,66,418],[55,56,57,58,59,60,61,62,63,64,65,66,418],[54,55,56,58,59,60,61,62,63,64,65,66,418],[54,55,56,57,59,60,61,62,63,64,65,66,418],[54,55,56,57,58,60,61,62,63,64,65,66,418],[54,55,56,57,58,59,61,62,63,64,65,66,418],[54,55,56,57,58,59,60,62,63,64,65,66,418],[54,55,56,57,58,59,60,61,63,64,65,66,418],[54,55,56,57,58,59,60,61,62,64,65,66,418],[54,55,56,57,58,59,60,61,62,63,65,66,418],[54,55,56,57,58,59,60,61,62,63,64,66,418],[54,55,56,57,58,59,60,61,62,63,64,65,418],[372,418],[375,418],[376,381,409,418],[377,388,389,396,406,417,418],[377,378,388,396,418],[379,418],[380,381,389,397,418],[381,406,414,418],[382,384,388,396,418],[383,418],[384,385,418],[388,418],[386,388,418],[388,389,390,406,417,418],[388,389,390,403,406,409,418],[418,422],[384,391,396,406,417,418],[388,389,391,392,396,406,414,417,418],[391,393,406,414,417,418],[372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424],[388,394,418],[395,417,418],[384,388,396,406,418],[397,418],[398,418],[375,399,418],[400,416,418,422],[401,418],[402,418],[388,403,404,418],[403,405,418,420],[376,388,406,407,408,409,418],[376,406,408,418],[406,407,418],[409,418],[410,418],[388,412,413,418],[412,413,418],[381,396,406,414,418],[415,418],[396,416,418],[376,391,402,417,418],[381,418],[406,418,419],[418,420],[418,421],[376,381,388,390,399,406,417,418,420,422],[406,418,423],[418,431],[418,427,428,429,430]],"referencedMap":[[49,1],[48,2],[50,2],[52,3],[53,2],[67,4],[68,4],[69,4],[70,4],[71,4],[72,4],[73,4],[74,4],[75,4],[76,4],[77,4],[78,4],[79,4],[80,4],[81,4],[82,4],[83,4],[84,4],[85,4],[86,4],[87,4],[88,4],[89,4],[90,4],[91,4],[92,4],[93,4],[94,4],[95,4],[96,4],[97,4],[98,4],[99,4],[100,4],[101,4],[102,4],[104,4],[105,4],[103,4],[106,4],[107,4],[108,4],[109,4],[110,4],[111,4],[112,4],[113,4],[114,4],[115,4],[116,4],[117,4],[118,4],[119,4],[120,4],[121,4],[122,4],[123,4],[124,4],[125,4],[126,4],[127,4],[128,4],[129,4],[130,4],[131,4],[132,4],[133,4],[134,4],[135,4],[136,4],[137,4],[138,4],[139,4],[140,4],[141,4],[142,4],[143,4],[144,4],[145,4],[146,4],[147,4],[148,4],[149,4],[150,4],[151,4],[152,4],[153,4],[154,4],[155,4],[156,4],[157,4],[158,4],[159,4],[160,4],[161,4],[162,4],[164,4],[370,5],[165,4],[166,4],[163,4],[167,4],[168,4],[169,4],[170,4],[171,4],[172,4],[173,4],[174,4],[175,4],[176,4],[177,4],[178,4],[179,4],[180,4],[181,4],[182,4],[183,4],[184,4],[185,4],[186,4],[187,4],[188,4],[189,4],[190,4],[191,4],[192,4],[193,4],[194,4],[195,4],[196,4],[197,4],[198,4],[199,4],[200,4],[201,4],[202,4],[203,4],[204,4],[205,4],[206,4],[207,4],[208,4],[209,4],[210,4],[211,4],[212,4],[213,4],[214,4],[215,4],[216,4],[217,4],[218,4],[219,4],[220,4],[221,4],[222,4],[223,4],[224,4],[225,4],[226,4],[227,4],[228,4],[229,4],[230,4],[231,4],[232,4],[233,4],[234,4],[235,4],[236,4],[237,4],[238,4],[239,4],[240,4],[241,4],[242,4],[243,4],[244,4],[245,4],[246,4],[247,4],[248,4],[249,4],[250,4],[251,4],[252,4],[253,4],[254,4],[255,4],[256,4],[257,4],[258,4],[259,4],[260,4],[261,4],[262,4],[263,4],[264,4],[265,4],[266,4],[267,4],[268,4],[269,4],[270,4],[271,4],[272,4],[273,4],[274,4],[275,4],[276,4],[277,4],[278,4],[279,4],[280,4],[281,4],[282,4],[283,4],[284,4],[285,4],[286,4],[287,4],[288,4],[289,4],[290,4],[291,4],[292,4],[293,4],[294,4],[295,4],[296,4],[297,4],[298,4],[299,4],[300,4],[301,4],[302,4],[303,4],[304,4],[305,4],[306,4],[307,4],[308,4],[309,4],[310,4],[311,4],[312,4],[313,4],[314,4],[315,4],[316,4],[317,4],[318,4],[319,4],[320,4],[321,4],[322,4],[323,4],[324,4],[325,4],[326,4],[327,4],[328,4],[329,4],[330,4],[331,4],[332,4],[333,4],[334,4],[335,4],[336,4],[337,4],[338,4],[339,4],[340,4],[341,4],[342,4],[343,4],[344,4],[345,4],[346,4],[347,4],[348,4],[350,4],[349,4],[351,4],[352,4],[353,4],[354,4],[355,4],[356,4],[357,4],[358,4],[359,4],[360,4],[361,4],[362,4],[363,4],[364,4],[365,4],[366,4],[367,4],[368,4],[369,4],[55,6],[56,7],[54,8],[57,9],[58,10],[59,11],[60,12],[61,13],[62,14],[63,15],[64,16],[65,17],[66,18],[371,2],[372,19],[373,19],[375,20],[376,21],[377,22],[378,23],[379,24],[380,25],[381,26],[382,27],[383,28],[384,29],[385,29],[387,30],[386,31],[388,30],[389,32],[390,33],[374,34],[424,2],[391,35],[392,36],[393,37],[425,38],[394,39],[395,40],[396,41],[397,42],[398,43],[399,44],[400,45],[401,46],[402,47],[403,48],[404,48],[405,49],[406,50],[408,51],[407,52],[409,53],[410,54],[411,2],[412,55],[413,56],[414,57],[415,58],[416,59],[417,60],[418,61],[419,62],[420,63],[421,64],[422,65],[423,66],[426,2],[427,2],[432,67],[428,2],[431,68],[433,2],[430,2],[434,2],[51,2],[429,2],[8,2],[9,2],[13,2],[12,2],[2,2],[14,2],[15,2],[16,2],[17,2],[18,2],[19,2],[20,2],[21,2],[3,2],[4,2],[25,2],[22,2],[23,2],[24,2],[26,2],[27,2],[28,2],[5,2],[29,2],[30,2],[31,2],[32,2],[6,2],[36,2],[33,2],[34,2],[35,2],[37,2],[7,2],[38,2],[46,2],[43,2],[44,2],[39,2],[40,2],[41,2],[42,2],[1,2],[45,2],[11,2],[10,2],[47,2]],"exportedModulesMap":[[49,1],[48,2],[50,2],[52,3],[53,2],[67,4],[68,4],[69,4],[70,4],[71,4],[72,4],[73,4],[74,4],[75,4],[76,4],[77,4],[78,4],[79,4],[80,4],[81,4],[82,4],[83,4],[84,4],[85,4],[86,4],[87,4],[88,4],[89,4],[90,4],[91,4],[92,4],[93,4],[94,4],[95,4],[96,4],[97,4],[98,4],[99,4],[100,4],[101,4],[102,4],[104,4],[105,4],[103,4],[106,4],[107,4],[108,4],[109,4],[110,4],[111,4],[112,4],[113,4],[114,4],[115,4],[116,4],[117,4],[118,4],[119,4],[120,4],[121,4],[122,4],[123,4],[124,4],[125,4],[126,4],[127,4],[128,4],[129,4],[130,4],[131,4],[132,4],[133,4],[134,4],[135,4],[136,4],[137,4],[138,4],[139,4],[140,4],[141,4],[142,4],[143,4],[144,4],[145,4],[146,4],[147,4],[148,4],[149,4],[150,4],[151,4],[152,4],[153,4],[154,4],[155,4],[156,4],[157,4],[158,4],[159,4],[160,4],[161,4],[162,4],[164,4],[370,5],[165,4],[166,4],[163,4],[167,4],[168,4],[169,4],[170,4],[171,4],[172,4],[173,4],[174,4],[175,4],[176,4],[177,4],[178,4],[179,4],[180,4],[181,4],[182,4],[183,4],[184,4],[185,4],[186,4],[187,4],[188,4],[189,4],[190,4],[191,4],[192,4],[193,4],[194,4],[195,4],[196,4],[197,4],[198,4],[199,4],[200,4],[201,4],[202,4],[203,4],[204,4],[205,4],[206,4],[207,4],[208,4],[209,4],[210,4],[211,4],[212,4],[213,4],[214,4],[215,4],[216,4],[217,4],[218,4],[219,4],[220,4],[221,4],[222,4],[223,4],[224,4],[225,4],[226,4],[227,4],[228,4],[229,4],[230,4],[231,4],[232,4],[233,4],[234,4],[235,4],[236,4],[237,4],[238,4],[239,4],[240,4],[241,4],[242,4],[243,4],[244,4],[245,4],[246,4],[247,4],[248,4],[249,4],[250,4],[251,4],[252,4],[253,4],[254,4],[255,4],[256,4],[257,4],[258,4],[259,4],[260,4],[261,4],[262,4],[263,4],[264,4],[265,4],[266,4],[267,4],[268,4],[269,4],[270,4],[271,4],[272,4],[273,4],[274,4],[275,4],[276,4],[277,4],[278,4],[279,4],[280,4],[281,4],[282,4],[283,4],[284,4],[285,4],[286,4],[287,4],[288,4],[289,4],[290,4],[291,4],[292,4],[293,4],[294,4],[295,4],[296,4],[297,4],[298,4],[299,4],[300,4],[301,4],[302,4],[303,4],[304,4],[305,4],[306,4],[307,4],[308,4],[309,4],[310,4],[311,4],[312,4],[313,4],[314,4],[315,4],[316,4],[317,4],[318,4],[319,4],[320,4],[321,4],[322,4],[323,4],[324,4],[325,4],[326,4],[327,4],[328,4],[329,4],[330,4],[331,4],[332,4],[333,4],[334,4],[335,4],[336,4],[337,4],[338,4],[339,4],[340,4],[341,4],[342,4],[343,4],[344,4],[345,4],[346,4],[347,4],[348,4],[350,4],[349,4],[351,4],[352,4],[353,4],[354,4],[355,4],[356,4],[357,4],[358,4],[359,4],[360,4],[361,4],[362,4],[363,4],[364,4],[365,4],[366,4],[367,4],[368,4],[369,4],[55,6],[56,7],[54,8],[57,9],[58,10],[59,11],[60,12],[61,13],[62,14],[63,15],[64,16],[65,17],[66,18],[371,2],[372,19],[373,19],[375,20],[376,21],[377,22],[378,23],[379,24],[380,25],[381,26],[382,27],[383,28],[384,29],[385,29],[387,30],[386,31],[388,30],[389,32],[390,33],[374,34],[424,2],[391,35],[392,36],[393,37],[425,38],[394,39],[395,40],[396,41],[397,42],[398,43],[399,44],[400,45],[401,46],[402,47],[403,48],[404,48],[405,49],[406,50],[408,51],[407,52],[409,53],[410,54],[411,2],[412,55],[413,56],[414,57],[415,58],[416,59],[417,60],[418,61],[419,62],[420,63],[421,64],[422,65],[423,66],[426,2],[427,2],[432,67],[428,2],[431,68],[433,2],[430,2],[434,2],[51,2],[429,2],[8,2],[9,2],[13,2],[12,2],[2,2],[14,2],[15,2],[16,2],[17,2],[18,2],[19,2],[20,2],[21,2],[3,2],[4,2],[25,2],[22,2],[23,2],[24,2],[26,2],[27,2],[28,2],[5,2],[29,2],[30,2],[31,2],[32,2],[6,2],[36,2],[33,2],[34,2],[35,2],[37,2],[7,2],[38,2],[46,2],[43,2],[44,2],[39,2],[40,2],[41,2],[42,2],[1,2],[45,2],[11,2],[10,2]],"semanticDiagnosticsPerFile":[49,48,50,52,53,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,104,105,103,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,164,370,165,166,163,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,350,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,55,56,54,57,58,59,60,61,62,63,64,65,66,371,372,373,375,376,377,378,379,380,381,382,383,384,385,387,386,388,389,390,374,424,391,392,393,425,394,395,396,397,398,399,400,401,402,403,404,405,406,408,407,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,426,427,432,428,431,433,430,434,51,429,8,9,13,12,2,14,15,16,17,18,19,20,21,3,4,25,22,23,24,26,27,28,5,29,30,31,32,6,36,33,34,35,37,7,38,46,43,44,39,40,41,42,1,45,11,10,47],"latestChangedDtsFile":"./dist/index.d.ts"},"version":"4.9.4"} --------------------------------------------------------------------------------