{"_id":"@dwarvesf/redux-bundler-async-resources","_rev":"4-c81c2dd2396053e47bc29045898fca74","name":"@dwarvesf/redux-bundler-async-resources","dist-tags":{"latest":"1.2.1"},"versions":{"1.2.1":{"name":"@dwarvesf/redux-bundler-async-resources","version":"1.2.1","keywords":["PWAs","bundler","redux"],"author":{"name":"Arseni Buinitski","email":"arseni.buinitski@gmail.com"},"license":"MIT","_id":"@dwarvesf/redux-bundler-async-resources@1.2.1","maintainers":[{"name":"gnhuy","email":"gnhuy91@gmail.com"},{"name":"thug","email":"thug@dwarvesv.com"}],"homepage":"https://github.com/abuinitski/redux-bundler-async-resources#readme","bugs":{"url":"https://github.com/abuinitski/redux-bundler-async-resources/issues"},"dist":{"shasum":"9241f0708e7e515f04cb4fd06c9e52e5197f9699","tarball":"https://registry.npmjs.org/@dwarvesf/redux-bundler-async-resources/-/redux-bundler-async-resources-1.2.1.tgz","fileCount":9,"integrity":"sha512-LseVqhoiBMYr0u8p3I9dVKkfqgxygZJ3ML3+tGesusFwfuTl+DELK4m6fdF/Jfqzf1BPCZlw1Aeh00uvF2SfBA==","signatures":[{"sig":"MEUCIEpKcrs1FhsS88PeocUAS36cs7TWLxAb4heSxb0IIXFDAiEAkiTuszVjJor5SuM271gEI35HbF2FfU1an+NcxNwZKu8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":157547,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJdHaJpCRA9TVsSAnZWagAAkfkP/RRqeBMRjhgABXki35cL\nGRQj7IQpFknjkkjWhrlmlUXjNrr0N0xdXYRN3UNhPC2BYiTwLZdSjHGkOnOu\ngP41PTWvUJ3G/WfaZli3bDLKOhFEShk49Lz0uZxujR+uH2cfF+y4E7dLc5f6\n0Lo5FTx+8ZZvvd6oEicLZmlSoXfFumqazwZjWx/uXpNgU0t/hQg+5LK3bKmj\nxyd3zancrkQ7AeReNmqmYMycg5ViewkDcThX4vMjtZeYFHzJB732EJ2Bwitx\n9hPfexA1vN46I8MUJGJA7UxKtUncCLSDVmVPa59p2uVGIzTnwfioLwJYSvri\nKQg+WKtS/sWGOkKrXv4eIvo7UfxnMnQJthqAqNRpGe+aBwLtcS4PtEBYWsSx\nABMrQ/8Y2OE/sYvp7Ay3wOzhrUs8mFioI01VozjYdyYnNpQVnX7qLoagDTgA\ngK8GNFV8cFa1l+MmpM+ezpCSEe3w8I/+OEN5qXkQuOeAIq7tECXirvjBVmb6\nOJaGS94Y2x6T/qrLqURRXk1zg0F08tvvcLG2K3VArJJyDo0oNTDHCRVc/DJr\ng5e5lEsnxCpYRqfjmvIW4ch5hBeUkEmYu3JXdorwGEqF/FicJXbARSPaXo0l\nzNK0nbEoZV9/YicrvmlzESVpwypI+lCu5ZWQ7t5NREzuhHIZO+9iAD+P6F6A\nbWP2\r\n=Sdbr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"524f7296937972476d2599bed96ff689291cefbd","scripts":{"dev":"microbundle watch","test":"jest","build":"microbundle","prepublishOnly":"microbundle"},"_npmUser":{"name":"gnhuy","email":"gnhuy91@gmail.com"},"prettier":{"semi":false,"useTabs":false,"tabWidth":2,"printWidth":120,"arrowParens":"avoid","singleQuote":true,"trailingComma":"es5","bracketSpacing":true,"jsxSingleQuote":false,"jsxBracketSameLine":false},"repository":{"url":"git+https://github.com/abuinitski/redux-bundler-async-resources.git","type":"git"},"_npmVersion":"6.9.0","description":"redux-bundler bundle that manages a collection of async resources","directories":{},"_nodeVersion":"10.15.0","dependencies":{},"_hasShrinkwrap":false,"devDependencies":{"jest":"^24.8.0","prettier":"^1.17.1","babel-jest":"^24.8.0","timekeeper":"^2.2.0","microbundle":"^0.11.0","redux-bundler":"^23.1.0","@babel/preset-env":"^7.4.4"},"peerDependencies":{"redux-bundler":"*"},"_npmOperationalInternal":{"tmp":"tmp/redux-bundler-async-resources_1.2.1_1562223208617_0.9237095978566279","host":"s3://npm-registry-packages"}}},"time":{"created":"2019-07-04T06:53:28.550Z","modified":"2026-03-01T10:29:47.303Z","1.2.1":"2019-07-04T06:53:28.755Z"},"bugs":{"url":"https://github.com/abuinitski/redux-bundler-async-resources/issues"},"author":{"name":"Arseni Buinitski","email":"arseni.buinitski@gmail.com"},"license":"MIT","homepage":"https://github.com/abuinitski/redux-bundler-async-resources#readme","keywords":["PWAs","bundler","redux"],"repository":{"url":"git+https://github.com/abuinitski/redux-bundler-async-resources.git","type":"git"},"description":"redux-bundler bundle that manages a collection of async resources","maintainers":[{"email":"thug@d.foundation","name":"thug"},{"email":"nntruonghan@gmail.com","name":"nntruonghan"}],"readme":"# Redux-Bundler Async Resource**s**\n\n![](https://img.shields.io/npm/v/redux-bundler-async-resources.svg) ![](https://img.shields.io/npm/dt/redux-bundler-async-resources.svg) [![CircleCI](https://circleci.com/gh/abuinitski/redux-bundler-hook/tree/master.svg?style=svg)](https://circleci.com/gh/abuinitski/redux-bundler-async-resources/tree/master)\n\nA bundle factory for [redux-bundler](https://reduxbundler.com/) that clearly manages remote resources.\n\n## Motivation\n\nIt is questionable that `createAsyncResourceBundle` should be a native part of redux-bundler in the first place. Either way, it's missing some features that are usually needed and usually re-implemented as extensions.\n\nThis package:\n\n- re-implements `createAsyncResourceBundle` with a bit clearer semantics and few additional missing features\n- adds a new concept: `createAsyncResourcesBundle` (note plural form). Instead of a single resource instance, it manages a collection of async resource instances referenced by ID. Each managed instance has it's own lifecycle in terms of loading, expiration etc.\n\n## Installation\n\n```\nnpm install --save redux-bundler-async-resources\n```\n\n## Usage\n\nIf you use React, take a look at [redux-bundler-async-resources-hooks](https://github.com/abuinitski/redux-bundler-async-resources-hooks/)\n\n### createAsyncResourceBundle\n\n##### bundles/hotCarDeals.js\n\n```javascript\nimport { createSelector } from 'redux-bundler'\nimport { createAsyncResourceBundle } from 'redux-bundler-async-resources'\n\nexport default {\n  ...createAsyncResourceBundle({\n    name: 'hotCarDeals',\n    staleAfter: 180000, // refresh every 3 minutes\n    expireAfter: 60 * 60000, // delete if not refreshed in an hour\n    getPromise: ({ shopApi }) => shopApi.fetchHotCarDeals(),\n  }),\n\n  reactShouldFetchHotCarDeals: createSelector(\n    'selectHotCarDealsIsPendingForFetch',\n    shouldFetch => {\n      if (shouldFetch) {\n        return { actionCreator: 'doFetchHotCarDeals' }\n      }\n    }\n  ),\n}\n```\n\n##### HotCarDeals.js\n\n```javascript\nimport React from 'react'\nimport { useConnect } from 'redux-bundler-hook'\n\n// ... other imports\n\nexport default function HotCarDeals() {\n  const { hotCarDeals, hotCarDealsError } = useConnect('selectHotCarDeals', 'selectHotCarDealsError')\n\n  if (!hotCarDeals && hotCarDealsError) {\n    return <ErrorMessage error={hotCarDealsError} />\n  }\n\n  if (!hotCarDeals) {\n    return <Spinner />\n  }\n\n  return <CarDealsList deals={hotCarDeals} />\n}\n```\n\n#### Options\n\n- **name** (required): bundle name as usual\n- **getPromise** (required): a function to get usual action creator context parameters; should return a promise that would either resolved with item data or rejected with an error.\n- **actionBaseType** _(toUnderscoreNotation(name))_: a prefix to be used with internal action types\n- **retryAfter** _(60000 i.e. one minute)_: an interval after which an `select${Name}IsPendingForFetch` for a failed request will turn back on. Falsie value or `Infinity` will disable retries.\n- **staleAfter** _(900000 i.e. 15 minutes)_: an interval of time after which a successfully fetched item will try to refresh itself (e.g. turn `select${Name}IsPendingForFetch` back on). Falsie value or `Infinity` will disable staling mechanism.\n- **expireAfter** _(`Infinity`)_: similar to `staleAfter` but will hard-remove the item from the store, resetting it to pristine state. Useful with caching to to prevent app user to see really old data when re-opening the page.\n- **persist** _(true)_: will instruct `cacheBundle` to cache on meaningful updates.\n- **dependencyKey** _(null)_: when given, will listen for values of related selectors:\n  - as an example, dependency key `userId` will listen to selector `selectUserId`\n  - when dependency selector resolves with `null` or `undefined`, it will prevent resource from fetching\n  - when dependency selector resolves to a value, this value will be mixed-in into `getPromise` parameters\n  - when resolved value changes, bundle will force-clear itself\n  - example values used in most cases: `currentUserId` or `['myResourceListPage', 'myResourceListPageSize']'`\n  - as shown above, to listen to several selectors, pass an array\n  - rather than a simple string, each selector can be represented as an object with additional parameters (i.e. `{ key: 'userId', staleOnChange: true '}`):\n    - **staleOnChange**: _(false)_ - if `true`, will stale a resource when dependency changes, rather than clearing the store\n    - **allowBlank**: _(false)_ – if `true`, will not lock resource from fetching when resolved value is `null` or `undefined`    \n\n#### Selectors\n\n- `select${Name}Raw` – just get raw bundle state, to be used internally\n- `select${Name}` – returns item data or `undefined` if there's nothing there\n- `select${Name}IsPresent` – returns `true` if there is something to be returned by `select${Name}` (i.e. there was at least one successful load before)\n- `select${Name}IsLoading` – returns `true` if item is currently loading (irrelevant of whether there is some data or not in `select${Name}`)\n- `select${Name}IsPendingForFetch` – returns `true` if resource thinks it should load now (i.e. pristine or stale or there was an error and `retryAfter` has passed or dependencies were specified and changed)\n- `select${Name}Error` – returns whatever `gerPromise` rejected with previously; reset to `null` or new error value after next load is finished\n- `select${Name}IsReadyForRetry` – returns `true` if previous fetch resulted in error and `retryAfter` has passed\n- `select${Name}RetryAt` – returns `null` or a timestamp at which item fetch will be retried\n- `select${Name}ErrorIsPermanent` – returns `true` if previous fetch resulted in error and error object had `permanent` field on\n- `select${Name}IsStale` – returns `true` if item is stale (manually or respective interval has passed) \n\n#### Action Creators\n\n- `doFetch${Name}` – trigger a fetch\n- `doClear${Name}` – force-clear a bundle and reset it to pristine state\n- `doMark${Name}AsStale` – force-mark resource as outdated. Will not remove item from the bundle, but will turn \"refetch me!\" flag on.\n- `doAdjust${Name}(payload)` – if there is some data present, replace item data with specified `payload`. If `payload` is a function, call it a with single parameter (current data value), and replace data with that it returns. Primary use case is when you have some mutation API calls to your resource that always render a predictable change of your resource properties – so you want to save up on re-fetching it and just update in place.\n\n... some other selectors and action creators are present, though mostly technical and are needed for bundle  functioning \n\n### createAsyncResourcesBundle\n\n##### createStore.js\n\n```javascript\nimport { composeBundles, createSelector } from 'redux-bundler'\nimport { createAsyncResourcesBundle } from 'redux-bundler-async-resources'\n\nexport default composeBundles(\n  createAsyncResourcesBundle({\n    name: 'carReviews',\n    staleAfter: 60000, // refresh every a minute\n    expireAfter: 60 * 60000, // delete if not refreshed in an hour\n    getPromise: (carId, { shopApi }) => shopApi.fetchCarReviews(carId),\n  }),\n\n  {\n    name: 'currentCarReviews',\n    reducer: (state = null, action) => {\n      if (action.type === 'currentCarReviews.CHANGED') {\n        return action.payload\n      }\n      return state\n    },\n\n    selectCurrentCarReviewsRaw: state => state.currentCarReviews,\n\n    selectCurrentCarReviews: createSelector(\n      'selectCurrentCarReviewsRaw',\n      reviewsItem => asyncResources.getItemData(reviewsItem)\n    ),\n\n    selectCurrentCarReviewsError: createSelector(\n      'selectCurrentCarReviewsRaw',\n      reviewsItem => asyncResources.getItemError(reviewsItem)\n    ),\n\n    selectCurrentCarReviewsLoading: createSelector(\n      'selectCurrentCarReviewsRaw',\n      reviewsItem => asyncResources.itemIsLoading(reviewsItem)\n    ),\n\n    reactCurrentCarReviewsChanged: createSelector(\n      'selectCurrentCarReviewsRaw',\n      'selectCurrentCarId',\n      'selectItemsOfCarReviews',\n      (prevReviewsItem, carId, carReviews) => {\n        const reviewsItem = carReviews[carId]\n        if (prevReviewsItem !== reviewsItem) {\n          return { type: 'currentCarReviews.CHANGED', payload: reviewsItem }\n        }\n      }\n    ),\n\n    reactShouldFetchCurrentCarReviews: createSelector(\n      'selectCurrentCarId',\n      'selectItemsOfCarReviews',\n      'selectIsOnline',\n      (carId, carReviews, isOnline) => {\n        if (carId && asyncResources.itemIsPendingForFetch(carReviews[carId], { isOnline })) {\n          return { actionCreator: 'doFetchItemOfCarReviews', args: [carId] }\n        }\n      }\n    ),\n  }\n  // ... other bundles of your application\n)\n```\n\n##### CurrentCarReviews.js\n\n```javascript\nimport React from 'react'\nimport { useConnect } from 'redux-bundler-hook'\nimport { asyncResources } from 'redux-bundler-async-resources'\n\n// ... other imports\n\nexport default function CurrentCarReviews() {\n  const { currentCarReviews, currentCarReviewsError, currentCarReviewsLoading } = useConnect(\n    'selectCurrentCarReviews',\n    'selectCurrentCarReviewsError',\n    'selectCurrentCarReviewsLoading'\n  )\n\n  if (currentCarReviewsLoading) {\n    return <Spinner />\n  }\n\n  if (currentCarReviewsError) {\n    return <ErrorMessage error={currentCarReviewsError} />\n  }\n\n  return <ReviewList reviews={currentCarReviews} />\n}\n```\n\n#### Options\n\n- **name** (required): bundle name as usual\n- **getPromise** (required): a function to get item id as first parameter, and usual action creator context parameters as a second; should return a promise that would either resolved with item data or rejected with an error. In both cases result will appear as `asyncResources.getItemData(itemId)` or `asyncResources.getItemError(itemId)`\n- **actionBaseType** _(toUnderscoreNotation(name))_: a prefix to be used with internal action types\n- **retryAfter** _(60000 i.e. one minute)_: an interval after which an `asyncResources.itemIsPendingForFetch` for an item that has failed to fetch will turn back on. Falsie value or `Infinity` will disable retries.\n- **staleAfter** _(900000 i.e. 15 minutes)_: an interval of time after which a successfully fetched item will try to refresh itself (e.g. turn `asyncResources.itemIsPendingForFetch` back on). Falsie value or `Infinity` will disable staling mechanism.\n- **expireAfter** _(`Infinity`)_: similar to `staleAfter` but will hard-remove the item from the store. Useful with caching to to prevent app user to see really old data when re-opening the page.\n- **persist** _(true)_: same behavior as for `createAsyncResource` – will instruct `cacheBundle` to cache on meaningful updates.\n\n#### Selectors\n\n- `select${Name}Raw` – as usual, just get raw bundle state\n- `selectItemsOf${Name}` – returns a hash of `{ [itemId]: item }`; `item` to be used with `asyncResources` helpers to get meaningful information from it.\n\n#### Action Creators\n\n- `doFetchItemOf${Name}(itemId)` – trigger a fetch of a specific item\n- `doClearItemOf${Name}(itemId)` – force-remove a certain item from the bundle, resetting it to pristine state\n- `doMarkItemOf${Name}AsStale(itemId)` – force-mark certain item as outdated. Will not remove item from the bundle, but will turn \"refetch me!\" flag on.\n- `doAdjustItemOf${Name}(itemId, payload)` – if there is some data present, replace item data with specified `payload`. If `payload` is a function, call it a with single parameter (current data value), and replace data with that it returns. Primary use case is when you have some mutation API calls to your resource that always render a predictable change of your resource properties – so you want to save up on re-fetching it and just update in place.\n\n#### `asyncResources` helpers\n\n- `getItemData(item)` – will return anything that `getPromise` previously resolved with or `undefined` if it didn't happen before\n- `itemIsPresent(item)` – `true` if `getItemData` is currently able to return some data to show\n- `itemIsLoading(item)` – `true` if item is currently loading (irrelevant of whether it has some data or not, i.e. of `itemIsPresent` / `getItemData` result)\n- `itemIsPendingForFetch(item, [{ isOnline = undefined }])` – `true` if there are any of mentioned conditions are present that result in necessity to trigger `doFetchItemOf${Name}`:\n  - either this item is in pristine state\n  - or it failed, retry is enabled and `retryAfter` has passed (and error is not permanent)\n  - or it fetched and is stale (either manually or because `staleAfter` has passed)\n  - `isOnline` is an optional check to not even try loading anything if device is offline; may omit if online check is not needed\n- `getItemError(item)` – something that `getPromise` previously rejected with. Will reset on when next fetch will finish (or fail).\n- `itemIsReadyForRetry(item)` – `true` if this item contains an error, and `retryAfter` has passed.\n- `itemRetryAt(item)` – returns a timestamp at which item fetch will be retried (if it will be, otherwise `null`)\n- `itemErrorIsPermanent(item)` – `true` if `getPromise` has rejected with something that had `persistent: true` property in it. Retry behavior will be disabled in this case.\n- `itemIsStale(item)` – `true` if this item is stale (manually or because `staleAfter` has passed since last successful fetch)\n\n### Naming helpers\n\nIn (rare) cases when you need to async resources in a resource-agnostic manner, there are two helpers available: `makeAsyncResourceBundleKeys` and `makeAsyncResourcesBundleKeys` for it's multi-item counterpart.\n\nCalling this with a resource `name` will return you an object of the following shape (assuming resource name `\"myResource\"`):\n\n(similar to)\n\n```json\n{\n  \"selectors\": {\n    \"raw\": \"selectMyResourceRaw\",\n    \"data\": \"selectMyResource\",\n    \"isLoading\": \"selectMyResourceIsLoading\",\n    \"isPresent\": \"selectMyResourceIsPresent\",\n    \"error\": \"selectMyResourceError\",\n    \"isReadyForRetry\": \"selectMyResourceIsReadyForRetry\",\n    \"errorIsPermanent\": \"selectMyResourceErrorIsPermanent\",\n    \"isStale\": \"selectMyResourceIsStale\",\n    \"isPendingForFetch\": \"selectMyResourceIsPendingForFetch\"\n  },\n  \"keys\": {\n    \"raw\": \"myResourceRaw\",\n    \"data\": \"myResource\",\n    \"isLoading\": \"myResourceIsLoading\",\n    \"isPresent\": \"myResourceIsPresent\",\n    \"error\": \"myResourceError\",\n    \"isReadyForRetry\": \"myResourceIsReadyForRetry\",\n    \"errorIsPermanent\": \"myResourceErrorIsPermanent\",\n    \"isStale\": \"myResourceIsStale\",\n    \"isPendingForFetch\": \"myResourceIsPendingForFetch\"\n  },\n  \"actionCreators\": {\n    \"doFetch\": \"doFetchMyResource\",\n    \"doClear\": \"doClearMyResource\",\n    \"doMarkAsStale\": \"doMarkMyResourceAsStale\",\n    \"doAdjust\": \"doAdjustMyResource\"\n  },\n  \"reactors\": {\n    \"shouldExpire\": \"reactMyResourceShouldExpire\",\n    \"shouldRetry\": \"reactMyResourceShouldRetry\",\n    \"shouldBecomeStale\": \"reactMyResourceShouldBecomeStale\"\n  }\n}\n```\n\n... and for `makeAsyncResourcesBundleKeys` it will be similar to:\n\n```json\n{\n  \"selectors\": {\n    \"raw\": \"selectMyResourcesRaw\",\n    \"items\": \"selectItemsOfMyResources\",\n    \"nextExpiringItem\": \"selectNextExpiringItemOfMyResources\",\n    \"nextRetryingItem\": \"selectNextRetryingItemOfMyResources\",\n    \"nextStaleItem\": \"selectNextStaleItemOfMyResources\"\n  },\n  \"keys\": {\n    \"raw\": \"myResourcesRaw\",\n    \"items\": \"itemsOfMyResources\",\n    \"nextExpiringItem\": \"nextExpiringItemOfMyResources\",\n    \"nextRetryingItem\": \"nextRetryingItemOfMyResources\",\n    \"nextStaleItem\": \"nextStaleItemOfMyResources\"\n  },\n  \"actionCreators\": {\n    \"doFetch\": \"doFetchItemOfMyResources\",\n    \"doClear\": \"doClearItemOfMyResources\",\n    \"doMarkAsStale\": \"doMarkItemOfMyResourcesAsStale\",\n    \"doAdjust\": \"doAdjustItemOfMyResources\"\n  },\n  \"reactors\": {\n    \"shouldExpire\": \"reactItemOfMyResourcesShouldExpire\",\n    \"shouldRetry\": \"reactItemOfMyResourcesShouldRetry\",\n    \"shouldBecomeStale\": \"reactItemOfMyResourcesShouldBecomeStale\"\n  }\n}\n```\n","readmeFilename":"README.md"}