From e1700539801b4ea783089a9d3611c51b6bd2e38c Mon Sep 17 00:00:00 2001 From: kuzkokov Date: Wed, 15 Nov 2023 22:13:52 +0300 Subject: [PATCH] MUK001: Implement RTKQueryInfiniteList --- package.json | 4 + .../RTKQueryInfiniteList.tsx | 90 +++++++ .../RTKQueryInfiniteListPage.tsx | 38 +++ yarn.lock | 238 +++++++++++++++++- 4 files changed, 362 insertions(+), 8 deletions(-) create mode 100644 src/components/common/RTKQueryInfiniteList/RTKQueryInfiniteList.tsx create mode 100644 src/components/common/RTKQueryInfiniteList/RTKQueryInfiniteListPage.tsx diff --git a/package.json b/package.json index 9a70567..d8b9b25 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,10 @@ "README.md" ], "peerDependencies": { + "@appello/services": "^0.0.10", "@react-navigation/elements": "^1.3.21", "@react-navigation/native": "^6.1.9", + "@reduxjs/toolkit": "^1.9.7", "@storybook/addon-ondevice-actions": "^6.5.6", "@storybook/addon-ondevice-controls": "^6.5.6", "expo": ">=48.0.0", @@ -40,6 +42,7 @@ }, "devDependencies": { "@appello/eslint-config": "^0.1.12", + "@appello/services": "^0.0.10", "@babel/core": "^7.23.0", "@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/preset-env": "^7.22.20", @@ -48,6 +51,7 @@ "@babel/runtime": "^7.23.1", "@react-native-community/eslint-config": "^3.2.0", "@react-navigation/native-stack": "^6.9.17", + "@reduxjs/toolkit": "^1.9.7", "@rollup/plugin-alias": "^5.0.1", "@rollup/plugin-babel": "^6.0.4", "@rollup/plugin-commonjs": "^25.0.7", diff --git a/src/components/common/RTKQueryInfiniteList/RTKQueryInfiniteList.tsx b/src/components/common/RTKQueryInfiniteList/RTKQueryInfiniteList.tsx new file mode 100644 index 0000000..b66b328 --- /dev/null +++ b/src/components/common/RTKQueryInfiniteList/RTKQueryInfiniteList.tsx @@ -0,0 +1,90 @@ +import React, { ReactElement, useState } from 'react'; +import { FlatList, FlatListProps } from 'react-native'; + +import { + BaseUseQuery, + ResultOfQuery, + RTKQueryInfiniteListPage, + RTKQueryInfiniteListPageProps, +} from './RTKQueryInfiniteListPage'; + +type Props = Omit< + RTKQueryInfiniteListPageProps, + 'index' | 'onQuery' +> & + Omit, 'data' | 'onEndReached'> & { + getItems: (queryData: ResultOfQuery['data']) => TItem[]; + getPaginationParams: (pageIndex: number) => TPaginationParams; + shouldRequestNext?: (queryResults: ResultOfQuery[]) => boolean; + }; + +export const RTKQueryInfiniteList = < + TQuery extends BaseUseQuery, + TItem, + TPaginationParams extends object, +>({ + useQuery, + queryArgs, + getItems, + getPaginationParams, + shouldRequestNext: shouldRequestNextProp, + ...flatListProps +}: Props): ReactElement => { + const [pagesCount, setPagesCount] = React.useState(1); + const [queryResults, setQueryResults] = useState[]>([]); + + const handleQuery = React.useCallback['onQuery']>( + (index, queryResult) => { + setQueryResults(results => { + const newResults = [...results]; + newResults[index] = queryResult; + return newResults; + }); + }, + [], + ); + + const items = React.useMemo(() => { + return queryResults.flatMap(pageData => getItems(pageData.data)); + }, [queryResults, getItems]); + + const isAnyLoading = React.useMemo(() => { + return queryResults.some(result => result.isLoading); + }, [queryResults]); + + const shouldRequestNext = React.useMemo(() => { + const shouldRequest = shouldRequestNextProp?.(queryResults) ?? true; + + return !isAnyLoading && shouldRequest; + }, [isAnyLoading, queryResults, shouldRequestNextProp]); + + const handleEndReached = React.useCallback['onEndReached']>>( + ({ distanceFromEnd }) => { + if (distanceFromEnd >= 0 && shouldRequestNext) { + setPagesCount(index => ++index); + } + }, + [shouldRequestNext], + ); + + const renderPages = React.useMemo(() => { + return new Array(pagesCount) + .fill(null) + .map((_, index) => ( + + )); + }, [pagesCount, useQuery, queryArgs, getPaginationParams, handleQuery]); + + return ( + <> + {renderPages} + + + ); +}; diff --git a/src/components/common/RTKQueryInfiniteList/RTKQueryInfiniteListPage.tsx b/src/components/common/RTKQueryInfiniteList/RTKQueryInfiniteListPage.tsx new file mode 100644 index 0000000..a3dcaad --- /dev/null +++ b/src/components/common/RTKQueryInfiniteList/RTKQueryInfiniteListPage.tsx @@ -0,0 +1,38 @@ +import { QueryArgFrom, QueryDefinition } from '@reduxjs/toolkit/dist/query/endpointDefinitions'; +import { UseQuery } from '@reduxjs/toolkit/dist/query/react/buildHooks'; +import { UseQueryHookResult } from '@reduxjs/toolkit/src/query/react/buildHooks'; +import React from 'react'; + +export type BaseUseQuery = UseQuery>; + +export interface RTKQueryInfiniteListPageProps { + index: number; + useQuery: TQuery; + queryArgs: ArgumentsOfQuery; + onQuery: (pageIndex: number, queryResult: ResultOfQuery) => void; +} + +export type ArgumentsOfQuery = TQuery extends UseQuery + ? QueryArgFrom extends object + ? QueryArgFrom + : never + : unknown; + +export type ResultOfQuery = TQuery extends UseQuery + ? UseQueryHookResult + : unknown; + +export const RTKQueryInfiniteListPage = ({ + index, + useQuery, + queryArgs, + onQuery, +}: RTKQueryInfiniteListPageProps): null => { + const queryResult = useQuery(queryArgs) as ResultOfQuery; + + React.useEffect(() => { + onQuery(index, queryResult); + }, [queryResult, index]); + + return null; +}; diff --git a/yarn.lock b/yarn.lock index fd209e5..849fe75 100644 --- a/yarn.lock +++ b/yarn.lock @@ -34,6 +34,42 @@ __metadata: languageName: node linkType: hard +"@apollo/client@npm:^3.8.7": + version: 3.8.7 + resolution: "@apollo/client@npm:3.8.7" + dependencies: + "@graphql-typed-document-node/core": "npm:^3.1.1" + "@wry/context": "npm:^0.7.3" + "@wry/equality": "npm:^0.5.6" + "@wry/trie": "npm:^0.4.3" + graphql-tag: "npm:^2.12.6" + hoist-non-react-statics: "npm:^3.3.2" + optimism: "npm:^0.17.5" + prop-types: "npm:^15.7.2" + response-iterator: "npm:^0.2.6" + symbol-observable: "npm:^4.0.0" + ts-invariant: "npm:^0.10.3" + tslib: "npm:^2.3.0" + zen-observable-ts: "npm:^1.2.5" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 + graphql-ws: ^5.5.5 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + subscriptions-transport-ws: ^0.9.0 || ^0.11.0 + peerDependenciesMeta: + graphql-ws: + optional: true + react: + optional: true + react-dom: + optional: true + subscriptions-transport-ws: + optional: true + checksum: e88be52f6393ab0625999abf7f6ed6c1035e5f290a738477210f9563d85d369b3dcb86dff7613be84a012192b66c2fae18ee9d5acf55f2b0ca30c1cc77c38559 + languageName: node + linkType: hard + "@appello/common@npm:^1.0.22": version: 1.0.38 resolution: "@appello/common@npm:1.0.38" @@ -54,6 +90,18 @@ __metadata: languageName: node linkType: hard +"@appello/common@npm:^2.0.8": + version: 2.0.8 + resolution: "@appello/common@npm:2.0.8" + dependencies: + date-fns: "npm:~2.29.1" + dot-path-value: "npm:^0.0.10" + peerDependencies: + react: ^17 || ^18 + checksum: 83df1eea84392e7422639d7ea6915b75e6bde1dd1e9600805d11049317d7bbe9c9260fb0025df439afcecc73b073bb9cda00882d8f31983b76e48d2891727d7d + languageName: node + linkType: hard + "@appello/eslint-config@npm:^0.1.12": version: 0.1.12 resolution: "@appello/eslint-config@npm:0.1.12" @@ -68,6 +116,7 @@ __metadata: "@appello/common": "npm:^1.0.22" "@appello/eslint-config": "npm:^0.1.12" "@appello/mobile": "npm:^0.1.8" + "@appello/services": "npm:^0.0.10" "@babel/core": "npm:^7.23.0" "@babel/plugin-proposal-class-properties": "npm:^7.18.6" "@babel/preset-env": "npm:^7.22.20" @@ -79,6 +128,7 @@ __metadata: "@react-navigation/elements": "npm:^1.3.21" "@react-navigation/native": "npm:^6.1.9" "@react-navigation/native-stack": "npm:^6.9.17" + "@reduxjs/toolkit": "npm:^1.9.7" "@rollup/plugin-alias": "npm:^5.0.1" "@rollup/plugin-babel": "npm:^6.0.4" "@rollup/plugin-commonjs": "npm:^25.0.7" @@ -167,6 +217,25 @@ __metadata: languageName: node linkType: hard +"@appello/services@npm:^0.0.10": + version: 0.0.10 + resolution: "@appello/services@npm:0.0.10" + dependencies: + "@apollo/client": "npm:^3.8.7" + "@appello/common": "npm:^2.0.8" + "@reduxjs/toolkit": "npm:^1.9.7" + axios: "npm:^1.6.1" + camelcase-keys: "npm:^9.1.2" + graphql: "npm:^16.8.1" + lodash.merge: "npm:^4.6.2" + react-hook-form: "npm:^7.48.2" + snakecase-keys: "npm:^5.5.0" + peerDependencies: + react: ^17 || ^18 + checksum: 50652a0e1107e67818e4b8b71942f0a01e9bb6822f29cd8b96f6bc454fbee00cd25fbf9f3821d5cd3d8ece6d6387271a7ee7e3202ca0db7d1e37a52afec8427d + languageName: node + linkType: hard + "@aw-web-design/x-default-browser@npm:1.4.126": version: 1.4.126 resolution: "@aw-web-design/x-default-browser@npm:1.4.126" @@ -2522,7 +2591,7 @@ __metadata: languageName: node linkType: hard -"@graphql-typed-document-node/core@npm:^3.1.0": +"@graphql-typed-document-node/core@npm:^3.1.0, @graphql-typed-document-node/core@npm:^3.1.1": version: 3.2.0 resolution: "@graphql-typed-document-node/core@npm:3.2.0" peerDependencies: @@ -3817,7 +3886,7 @@ __metadata: languageName: node linkType: hard -"@reduxjs/toolkit@npm:^1.8.3": +"@reduxjs/toolkit@npm:^1.8.3, @reduxjs/toolkit@npm:^1.9.7": version: 1.9.7 resolution: "@reduxjs/toolkit@npm:1.9.7" dependencies: @@ -6940,6 +7009,33 @@ __metadata: languageName: node linkType: hard +"@wry/context@npm:^0.7.0, @wry/context@npm:^0.7.3": + version: 0.7.4 + resolution: "@wry/context@npm:0.7.4" + dependencies: + tslib: "npm:^2.3.0" + checksum: 6cc8249b8ba195cda7643bffb30969e33d54a99f118a29dd12f1c34064ee0adf04253cfa0ba5b9893afde0a9588745828962877b9585106f7488e8299757638b + languageName: node + linkType: hard + +"@wry/equality@npm:^0.5.6": + version: 0.5.7 + resolution: "@wry/equality@npm:0.5.7" + dependencies: + tslib: "npm:^2.3.0" + checksum: 8503ff6d4eb80f303d1387e71e51da59ccfc2160fa6d464618be80946fe43a654ea73f0c5b90d659fc4dfc3e38cbbdd6650d595fe5865be476636e444470853e + languageName: node + linkType: hard + +"@wry/trie@npm:^0.4.3": + version: 0.4.3 + resolution: "@wry/trie@npm:0.4.3" + dependencies: + tslib: "npm:^2.3.0" + checksum: 1a14edba595b1967d0cf38208c2660b2952a8e8a649bb669b67907df48f602c7f2acbe16c1e1b115afa7d7effb9f1a4dbde38eef16ee92e7521a511262a53281 + languageName: node + linkType: hard + "@xmldom/xmldom@npm:^0.8.8": version: 0.8.10 resolution: "@xmldom/xmldom@npm:0.8.10" @@ -7779,6 +7875,17 @@ __metadata: languageName: node linkType: hard +"axios@npm:^1.6.1": + version: 1.6.2 + resolution: "axios@npm:1.6.2" + dependencies: + follow-redirects: "npm:^1.15.0" + form-data: "npm:^4.0.0" + proxy-from-env: "npm:^1.1.0" + checksum: 9b77e030e85e4f9cbcba7bb52fbff67d6ce906c92d213e0bd932346a50140faf83733bf786f55bd58301bd92f9973885c7b87d6348023e10f7eaf286d0791a1d + languageName: node + linkType: hard + "axios@npm:~0.27.2": version: 0.27.2 resolution: "axios@npm:0.27.2" @@ -8670,6 +8777,18 @@ __metadata: languageName: node linkType: hard +"camelcase-keys@npm:^9.1.2": + version: 9.1.2 + resolution: "camelcase-keys@npm:9.1.2" + dependencies: + camelcase: "npm:^8.0.0" + map-obj: "npm:5.0.0" + quick-lru: "npm:^6.1.1" + type-fest: "npm:^4.3.2" + checksum: be5fa948e7bad6f513ad1387ea2e8fda28f5dada92d94827b20157f57937b06cd5c4248319ff17c8a1dbf3ca06d9554c5f01a0385710084a9cb2b170b4092085 + languageName: node + linkType: hard + "camelcase@npm:^5.0.0, camelcase@npm:^5.3.1": version: 5.3.1 resolution: "camelcase@npm:5.3.1" @@ -8691,6 +8810,13 @@ __metadata: languageName: node linkType: hard +"camelcase@npm:^8.0.0": + version: 8.0.0 + resolution: "camelcase@npm:8.0.0" + checksum: 56c5fe072f0523c9908cdaac21d4a3b3fb0f608fb2e9ba90a60e792b95dd3bb3d1f3523873ab17d86d146e94171305f73ef619e2f538bd759675bc4a14b4bff3 + languageName: node + linkType: hard + "caniuse-lite@npm:^1.0.30001541": version: 1.0.30001553 resolution: "caniuse-lite@npm:1.0.30001553" @@ -10120,6 +10246,13 @@ __metadata: languageName: node linkType: hard +"dot-path-value@npm:^0.0.10": + version: 0.0.10 + resolution: "dot-path-value@npm:0.0.10" + checksum: 563c911fa8c417179c6dd05ed2811e4323f2647f0e4ac763b077582fb4e3e8dc8e09c63e84e2ef8252c5377ab19fec69f548b4506d054e015e9cd5688b7627f7 + languageName: node + linkType: hard + "dotenv-expand@npm:^10.0.0": version: 10.0.0 resolution: "dotenv-expand@npm:10.0.0" @@ -11925,7 +12058,7 @@ __metadata: languageName: node linkType: hard -"follow-redirects@npm:^1.14.9": +"follow-redirects@npm:^1.14.9, follow-redirects@npm:^1.15.0": version: 1.15.3 resolution: "follow-redirects@npm:1.15.3" peerDependenciesMeta: @@ -12627,7 +12760,7 @@ __metadata: languageName: node linkType: hard -"graphql-tag@npm:^2.10.1": +"graphql-tag@npm:^2.10.1, graphql-tag@npm:^2.12.6": version: 2.12.6 resolution: "graphql-tag@npm:2.12.6" dependencies: @@ -12645,6 +12778,13 @@ __metadata: languageName: node linkType: hard +"graphql@npm:^16.8.1": + version: 16.8.1 + resolution: "graphql@npm:16.8.1" + checksum: 129c318156b466f440914de80dbf7bc67d17f776f2a088a40cb0da611d19a97c224b1c6d2b13cbcbc6e5776e45ed7468b8432f9c3536724e079b44f1a3d57a8a + languageName: node + linkType: hard + "gunzip-maybe@npm:^1.4.2": version: 1.4.2 resolution: "gunzip-maybe@npm:1.4.2" @@ -12860,6 +13000,15 @@ __metadata: languageName: node linkType: hard +"hoist-non-react-statics@npm:^3.3.2": + version: 3.3.2 + resolution: "hoist-non-react-statics@npm:3.3.2" + dependencies: + react-is: "npm:^16.7.0" + checksum: fe0889169e845d738b59b64badf5e55fa3cf20454f9203d1eb088df322d49d4318df774828e789898dcb280e8a5521bb59b3203385662ca5e9218a6ca5820e74 + languageName: node + linkType: hard + "hosted-git-info@npm:^2.1.4": version: 2.8.9 resolution: "hosted-git-info@npm:2.8.9" @@ -14846,6 +14995,13 @@ __metadata: languageName: node linkType: hard +"map-obj@npm:5.0.0": + version: 5.0.0 + resolution: "map-obj@npm:5.0.0" + checksum: 8ae0d8a3ce085e3e9eb46d7a4eba03681ffc644920046f7ba8edff37f11d30b0de4dd10e83f492ea6d3ba3b9c51de66d7ca6580c24b7e15d61b845d2f9f688ca + languageName: node + linkType: hard + "map-obj@npm:^4.1.0, map-obj@npm:^4.3.0": version: 4.3.0 resolution: "map-obj@npm:4.3.0" @@ -16457,6 +16613,17 @@ __metadata: languageName: node linkType: hard +"optimism@npm:^0.17.5": + version: 0.17.5 + resolution: "optimism@npm:0.17.5" + dependencies: + "@wry/context": "npm:^0.7.0" + "@wry/trie": "npm:^0.4.3" + tslib: "npm:^2.3.0" + checksum: 0c3a526e2dc1451735e66d2a6bef46a6395e8c7e7f6f323d998361ad281f01d91337a58ad4ad5fa5db18a87d6c9e64ff79a564aff9fb5d3ce614cb3d921c027d + languageName: node + linkType: hard + "optionator@npm:^0.9.3": version: 0.9.3 resolution: "optionator@npm:0.9.3" @@ -17274,7 +17441,7 @@ __metadata: languageName: node linkType: hard -"proxy-from-env@npm:^1.0.0": +"proxy-from-env@npm:^1.0.0, proxy-from-env@npm:^1.1.0": version: 1.1.0 resolution: "proxy-from-env@npm:1.1.0" checksum: fe7dd8b1bdbbbea18d1459107729c3e4a2243ca870d26d34c2c1bcd3e4425b7bcc5112362df2d93cc7fb9746f6142b5e272fd1cc5c86ddf8580175186f6ad42b @@ -17612,6 +17779,15 @@ __metadata: languageName: node linkType: hard +"react-hook-form@npm:^7.48.2": + version: 7.48.2 + resolution: "react-hook-form@npm:7.48.2" + peerDependencies: + react: ^16.8.0 || ^17 || ^18 + checksum: 77663276288d3e240da76c469612fec2bfb2dd34ed0512adb8554b8567e89a316c3df62266e51639f66a9a9d50ce175e8dc965f94bc31afecb460ed48333dafa + languageName: node + linkType: hard + "react-inspector@npm:^5.1.0": version: 5.1.1 resolution: "react-inspector@npm:5.1.1" @@ -17648,7 +17824,7 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^16.13.0, react-is@npm:^16.13.1": +"react-is@npm:^16.13.0, react-is@npm:^16.13.1, react-is@npm:^16.7.0": version: 16.13.1 resolution: "react-is@npm:16.13.1" checksum: 33977da7a5f1a287936a0c85639fec6ca74f4f15ef1e59a6bc20338fc73dc69555381e211f7a3529b8150a1f71e4225525b41b60b52965bda53ce7d47377ada1 @@ -18359,6 +18535,13 @@ __metadata: languageName: node linkType: hard +"response-iterator@npm:^0.2.6": + version: 0.2.6 + resolution: "response-iterator@npm:0.2.6" + checksum: 60e6b552cd610643269d5d916d270cc8a4bea978cbe4779d6ef8083ac6b89006795508034e4c4ebe204eded75ac32bf243589ba82c1184591dde0674f6db785e + languageName: node + linkType: hard + "restore-cursor@npm:^2.0.0": version: 2.0.0 resolution: "restore-cursor@npm:2.0.0" @@ -18999,7 +19182,7 @@ __metadata: languageName: node linkType: hard -"snakecase-keys@npm:^5.4.2": +"snakecase-keys@npm:^5.4.2, snakecase-keys@npm:^5.5.0": version: 5.5.0 resolution: "snakecase-keys@npm:5.5.0" dependencies: @@ -19735,6 +19918,13 @@ __metadata: languageName: node linkType: hard +"symbol-observable@npm:^4.0.0": + version: 4.0.0 + resolution: "symbol-observable@npm:4.0.0" + checksum: 5e9a3ab08263a6be8cbee76587ad5880dcc62a47002787ed5ebea56b1eb30dc87da6f0183d67e88286806799fbe21c69077fbd677be4be2188e92318d6c6f31d + languageName: node + linkType: hard + "symbol.prototype.description@npm:^1.0.0": version: 1.0.5 resolution: "symbol.prototype.description@npm:1.0.5" @@ -20192,6 +20382,15 @@ __metadata: languageName: node linkType: hard +"ts-invariant@npm:^0.10.3": + version: 0.10.3 + resolution: "ts-invariant@npm:0.10.3" + dependencies: + tslib: "npm:^2.1.0" + checksum: 2fbc178d5903d325ee0b87fad38827eac11888b6e86979b06754fd4bcdcf44c2a99b8bcd5d59d149c0464ede55ae810b02a2aee6835ad10efe4dd0e22efd68c0 + languageName: node + linkType: hard + "tsc-alias@npm:^1.8.2": version: 1.8.8 resolution: "tsc-alias@npm:1.8.8" @@ -20227,7 +20426,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.4.0, tslib@npm:^2.5.0, tslib@npm:^2.6.0, tslib@npm:^2.6.2": +"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.3.0, tslib@npm:^2.4.0, tslib@npm:^2.5.0, tslib@npm:^2.6.0, tslib@npm:^2.6.2": version: 2.6.2 resolution: "tslib@npm:2.6.2" checksum: e03a8a4271152c8b26604ed45535954c0a45296e32445b4b87f8a5abdb2421f40b59b4ca437c4346af0f28179780d604094eb64546bee2019d903d01c6c19bdb @@ -20352,6 +20551,13 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:^4.3.2": + version: 4.7.1 + resolution: "type-fest@npm:4.7.1" + checksum: e2cae05a6a5be98e5e5cfecee8782db1eb74594ceb1d85b5bfdb9b9ca5e236342f97435d3ff9edddd8eb92898af7f13555f85d1a53a69c362b0f56f2cceb8151 + languageName: node + linkType: hard + "type-is@npm:~1.6.18": version: 1.6.18 resolution: "type-is@npm:1.6.18" @@ -21616,3 +21822,19 @@ __metadata: checksum: 856117aa15cf5103d2a2fb173f0ab4acb12b4b4d0ed3ab249fdbbf612e55d1cadfd27a6110940e24746fb0a78cf640b522cc8bca76f30a3b00b66e90cf82abe0 languageName: node linkType: hard + +"zen-observable-ts@npm:^1.2.5": + version: 1.2.5 + resolution: "zen-observable-ts@npm:1.2.5" + dependencies: + zen-observable: "npm:0.8.15" + checksum: 21d586f3d0543e1d6f05d9333a137b407dbf337907c1ee1c2fa7a7da044f7e1262e4baf4ef8902f230c6f5acb561047659eb7df73df33307233cc451efe46db1 + languageName: node + linkType: hard + +"zen-observable@npm:0.8.15": + version: 0.8.15 + resolution: "zen-observable@npm:0.8.15" + checksum: 71cc2f2bbb537300c3f569e25693d37b3bc91f225cefce251a71c30bc6bb3e7f8e9420ca0eb57f2ac9e492b085b8dfa075fd1e8195c40b83c951dd59c6e4fbf8 + languageName: node + linkType: hard