@zeniai/client-epic-state 5.3.22 → 5.3.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (19) hide show
  1. package/lib/esm/view/invoicing/createInvoice/createInvoiceSelector.js +2 -1
  2. package/lib/esm/view/invoicing/createInvoice/epics/fetchCreateInvoiceFormPageEpic.js +10 -3
  3. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.js +2 -1
  4. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/fetchInvoicingSubscriptionFormPageEpic.js +8 -1
  5. package/lib/esm/view/invoicing/invoicingCustomerListView/epics/fetchInvoicingCustomerListEpic.js +15 -3
  6. package/lib/esm/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewReducer.js +1 -0
  7. package/lib/esm/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewSelector.js +2 -0
  8. package/lib/view/invoicing/createInvoice/createInvoiceSelector.d.ts +2 -0
  9. package/lib/view/invoicing/createInvoice/createInvoiceSelector.js +1 -0
  10. package/lib/view/invoicing/createInvoice/epics/fetchCreateInvoiceFormPageEpic.js +10 -3
  11. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.d.ts +2 -0
  12. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.js +1 -0
  13. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/fetchInvoicingSubscriptionFormPageEpic.js +8 -1
  14. package/lib/view/invoicing/invoicingCustomerListView/epics/fetchInvoicingCustomerListEpic.js +15 -3
  15. package/lib/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewReducer.d.ts +3 -0
  16. package/lib/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewReducer.js +1 -0
  17. package/lib/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewSelector.d.ts +2 -0
  18. package/lib/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewSelector.js +4 -1
  19. package/package.json +1 -1
@@ -3,7 +3,7 @@ import { getAddressByAddressId } from '../../../entity/address/addressSelector';
3
3
  import { invoicingPricePointDisplayAmount } from '../../../entity/invoicing/invoicingCatalog/invoicingCatalogSelector';
4
4
  import { getInvoicingCatalogListView, getInvoicingCatalogPrices, } from '../invoicingCatalogListView/invoicingCatalogListViewSelector';
5
5
  import { invoicingFieldHelpForGroup } from '../invoicingConfigView/invoicingConfigViewSelector';
6
- import { getInvoicingAllCustomers } from '../invoicingCustomerListView/invoicingCustomerListViewSelector';
6
+ import { getInvoicingAllCustomers, isInvoicingAllCustomersLoading, } from '../invoicingCustomerListView/invoicingCustomerListViewSelector';
7
7
  import { getInvoicingSettings } from '../settingsView/settingsViewSelector';
8
8
  /**
9
9
  * Pricing models whose single-unit amount is a true per-unit rate — the only
@@ -89,6 +89,7 @@ export const getCreateInvoiceFormView = (state) => {
89
89
  id: customer.id,
90
90
  name: customerDisplayName(customer),
91
91
  })),
92
+ isCustomerListLoading: isInvoicingAllCustomersLoading(state),
92
93
  localData: createInvoiceState.formDraft,
93
94
  planNames: getCreateInvoicePlanNames(state),
94
95
  products: getInvoicingCatalogPrices(catalogListView.products),
@@ -24,13 +24,20 @@ const PICKER_PRODUCT_FILTERS = { type: 'addon|charge' };
24
24
  export const fetchCreateInvoiceFormPageEpic = (actions$, state$) => actions$.pipe(filter(fetchCreateInvoiceFormPage.match), mergeMap((action) => {
25
25
  const { cacheOverride } = action.payload;
26
26
  const state = state$.value;
27
- // The customer picker reads the unfiltered `all` cache.
28
- const customerTab = getInvoicingListTab(state.invoicingCustomerListViewState.byTab, INVOICING_LIST_ALL_TAB);
27
+ // The customer picker filters the unfiltered `all` cache client-side.
28
+ const customerListState = state.invoicingCustomerListViewState;
29
+ const customerTab = getInvoicingListTab(customerListState.byTab, INVOICING_LIST_ALL_TAB);
29
30
  const catalogState = state.invoicingCatalogListViewState;
30
31
  const settingsState = state.invoicingSettingsViewState;
31
32
  const pageActions = [];
32
- if (shouldFetch({ fetchState: customerTab.fetchState, error: customerTab.error }, cacheOverride)) {
33
+ if (shouldFetchFullInvoicingList({
34
+ fetchState: customerTab.fetchState,
35
+ filters: customerListState.filters,
36
+ nextCursor: customerTab.nextCursor,
37
+ sort: customerListState.sort,
38
+ }, {}, cacheOverride)) {
33
39
  pageActions.push(fetchInvoicingCustomerList({
40
+ fetchAll: true,
34
41
  filters: {},
35
42
  keepExistingListItems: false,
36
43
  tabId: INVOICING_LIST_ALL_TAB,
@@ -5,7 +5,7 @@ import { getFormattedAddress } from '../../addressView/addressViewSelector';
5
5
  import { getInvoicingCouponListItems } from '../couponView/couponViewSelector';
6
6
  import { getInvoicingCatalogListView, getInvoicingCatalogPrices, } from '../invoicingCatalogListView/invoicingCatalogListViewSelector';
7
7
  import { getInvoicingConfigView, invoicingFieldHelpForGroup, } from '../invoicingConfigView/invoicingConfigViewSelector';
8
- import { getInvoicingAllCustomers } from '../invoicingCustomerListView/invoicingCustomerListViewSelector';
8
+ import { getInvoicingAllCustomers, isInvoicingAllCustomersLoading, } from '../invoicingCustomerListView/invoicingCustomerListViewSelector';
9
9
  import { getInvoicingFormDraftLocalData, isInvoicingFormDraftPending, } from '../invoicingFormDraft';
10
10
  import { initialInvoicingSubscriptionFormLocalData, invoicingPriceHasQuantity, } from './invoicingSubscriptionFormConfig';
11
11
  import { INVOICING_SUBSCRIPTION_SHIPPING_ADDRESS_TYPE } from './invoicingSubscriptionFormConfig';
@@ -283,6 +283,7 @@ export const getInvoicingSubscriptionFormView = (state, invoicingSubscriptionID)
283
283
  charges: catalogItemOptionsOfType('charge'),
284
284
  coupons: getInvoicingCouponListItems(state).map(toInvoicingCouponOption),
285
285
  customers: getInvoicingAllCustomers(state).map(toInvoicingCustomerOption),
286
+ isCustomerListLoading: isInvoicingAllCustomersLoading(state),
286
287
  localData,
287
288
  mode,
288
289
  plans: planPrices.map(toInvoicingPlanOption),
@@ -3,6 +3,7 @@ import { filter, mergeMap } from 'rxjs/operators';
3
3
  import { fetchInvoicingCouponList } from '../couponView/couponViewReducer';
4
4
  import { fetchInvoicingCatalogPlanList } from '../invoicingCatalogListView/invoicingCatalogListViewReducer';
5
5
  import { fetchInvoicingCustomerList } from '../invoicingCustomerListView/invoicingCustomerListViewReducer';
6
+ import { INVOICING_LIST_ALL_TAB } from '../invoicingListTabCache';
6
7
  import { fetchInvoicingSubscriptionDetail } from './editInvoicingSubscriptionDetailViewReducer';
7
8
  import { fetchInvoicingSubscriptionFormPage } from './editInvoicingSubscriptionDetailViewReducer';
8
9
  /**
@@ -15,7 +16,13 @@ import { fetchInvoicingSubscriptionFormPage } from './editInvoicingSubscriptionD
15
16
  export const fetchInvoicingSubscriptionFormPageEpic = (actions$) => actions$.pipe(filter(fetchInvoicingSubscriptionFormPage.match), mergeMap((action) => {
16
17
  const { subscriptionId } = action.payload;
17
18
  const pageActions = [
18
- fetchInvoicingCustomerList({ filters: {}, keepExistingListItems: false }),
19
+ // The customer picker filters the unfiltered `all` cache client-side.
20
+ fetchInvoicingCustomerList({
21
+ fetchAll: true,
22
+ filters: {},
23
+ keepExistingListItems: false,
24
+ tabId: INVOICING_LIST_ALL_TAB,
25
+ }),
19
26
  // Plans only: the form's plan picker is the one consumer, and an
20
27
  // unfiltered list would replace the store's plans with every catalog
21
28
  // item.
@@ -30,21 +30,33 @@ export const fetchInvoicingCustomerListEpic = (actions$, state$, zeniAPI) => act
30
30
  // Per tab: a newer fetch of the same tab replaces its in-flight one, while
31
31
  // another tab's request keeps running and lands in its own cache.
32
32
  groupBy(({ tabId }) => tabId), mergeMap((tabFetches$) => tabFetches$.pipe(switchMap(({ action, tabId }) => {
33
- const { filters, cursor, keepExistingListItems, sort } = action.payload;
33
+ const { filters, cursor, fetchAll, keepExistingListItems, sort } = action.payload;
34
34
  const apiUrl = withQuery(`${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/customers`, buildQuery(filters, cursor, sort, invoicingListStatusParam(tabId)));
35
35
  return zeniAPI.getJSON(apiUrl).pipe(switchMap((response) => {
36
36
  if (isSuccessResponse(response)) {
37
37
  const items = response.data?.items ?? [];
38
- return [
38
+ const nextCursor = response.data?.next_cursor ?? null;
39
+ const pageActions = [
39
40
  updateInvoicingCustomers(items, 'merge'),
40
41
  updateAddresses(extractInvoicingCustomerAddresses(items)),
41
42
  updateInvoicingCustomerList({
42
43
  ids: items.map((item) => item.id),
43
- nextCursor: response.data?.next_cursor ?? null,
44
+ nextCursor,
44
45
  keepExistingListItems,
45
46
  tabId,
46
47
  }),
47
48
  ];
49
+ if (fetchAll && nextCursor != null && nextCursor !== cursor) {
50
+ pageActions.push(fetchInvoicingCustomerList({
51
+ cursor: nextCursor,
52
+ fetchAll,
53
+ filters,
54
+ keepExistingListItems: true,
55
+ sort,
56
+ tabId,
57
+ }));
58
+ }
59
+ return pageActions;
48
60
  }
49
61
  return of(updateInvoicingCustomerListFailure({
50
62
  status: response.status,
@@ -24,6 +24,7 @@ const invoicingCustomerListView = createSlice({
24
24
  payload: {
25
25
  filters: payload.filters ?? {},
26
26
  cursor: payload.cursor,
27
+ fetchAll: payload.fetchAll ?? false,
27
28
  keepExistingListItems: payload.keepExistingListItems ?? false,
28
29
  sort: payload.sort,
29
30
  tabId: payload.tabId,
@@ -41,3 +41,5 @@ export const getInvoicingCustomerListView = createSelector((state) => state.invo
41
41
  * view so they never narrow to whichever tab the customers list was left on.
42
42
  */
43
43
  export const getInvoicingAllCustomers = (state) => getInvoicingCustomersByIds(state.invoicingCustomerState, getInvoicingListTab(state.invoicingCustomerListViewState.byTab, INVOICING_LIST_ALL_TAB).ids);
44
+ /** True while the pickers' customer list is still loading pages. */
45
+ export const isInvoicingAllCustomersLoading = (state) => getInvoicingListTab(state.invoicingCustomerListViewState.byTab, INVOICING_LIST_ALL_TAB).fetchState === 'In-Progress';
@@ -37,6 +37,8 @@ export interface CreateInvoiceFormView extends SelectorView {
37
37
  * Folded in here so the screen never reaches for the config view itself.
38
38
  */
39
39
  fieldHelp: InvoicingFieldHelpByCode;
40
+ /** More customer pages are still on their way; `customers` is partial. */
41
+ isCustomerListLoading: boolean;
40
42
  localData: InvoicingCreateInvoiceFormLocalData;
41
43
  planNames: InvoicingPlanNameOption[];
42
44
  products: InvoicingCatalogPricePoint[];
@@ -92,6 +92,7 @@ const getCreateInvoiceFormView = (state) => {
92
92
  id: customer.id,
93
93
  name: customerDisplayName(customer),
94
94
  })),
95
+ isCustomerListLoading: (0, invoicingCustomerListViewSelector_1.isInvoicingAllCustomersLoading)(state),
95
96
  localData: createInvoiceState.formDraft,
96
97
  planNames: getCreateInvoicePlanNames(state),
97
98
  products: (0, invoicingCatalogListViewSelector_1.getInvoicingCatalogPrices)(catalogListView.products),
@@ -27,13 +27,20 @@ const PICKER_PRODUCT_FILTERS = { type: 'addon|charge' };
27
27
  const fetchCreateInvoiceFormPageEpic = (actions$, state$) => actions$.pipe((0, operators_1.filter)(createInvoiceReducer_1.fetchCreateInvoiceFormPage.match), (0, operators_1.mergeMap)((action) => {
28
28
  const { cacheOverride } = action.payload;
29
29
  const state = state$.value;
30
- // The customer picker reads the unfiltered `all` cache.
31
- const customerTab = (0, invoicingListTabCache_1.getInvoicingListTab)(state.invoicingCustomerListViewState.byTab, invoicingListTabCache_1.INVOICING_LIST_ALL_TAB);
30
+ // The customer picker filters the unfiltered `all` cache client-side.
31
+ const customerListState = state.invoicingCustomerListViewState;
32
+ const customerTab = (0, invoicingListTabCache_1.getInvoicingListTab)(customerListState.byTab, invoicingListTabCache_1.INVOICING_LIST_ALL_TAB);
32
33
  const catalogState = state.invoicingCatalogListViewState;
33
34
  const settingsState = state.invoicingSettingsViewState;
34
35
  const pageActions = [];
35
- if (shouldFetch({ fetchState: customerTab.fetchState, error: customerTab.error }, cacheOverride)) {
36
+ if ((0, invoicingListTabCache_1.shouldFetchFullInvoicingList)({
37
+ fetchState: customerTab.fetchState,
38
+ filters: customerListState.filters,
39
+ nextCursor: customerTab.nextCursor,
40
+ sort: customerListState.sort,
41
+ }, {}, cacheOverride)) {
36
42
  pageActions.push((0, invoicingCustomerListViewReducer_1.fetchInvoicingCustomerList)({
43
+ fetchAll: true,
37
44
  filters: {},
38
45
  keepExistingListItems: false,
39
46
  tabId: invoicingListTabCache_1.INVOICING_LIST_ALL_TAB,
@@ -63,6 +63,8 @@ export interface InvoicingSubscriptionFormView extends SelectorView {
63
63
  /** The review dialog's cost estimate for saving this draft. */
64
64
  estimate: InvoicingSubscriptionEstimateView;
65
65
  fieldHelp: InvoicingFieldHelpByCode;
66
+ /** More customer pages are still on their way; `customers` is partial. */
67
+ isCustomerListLoading: boolean;
66
68
  /**
67
69
  * True while the subscription being edited is still on its way to the form.
68
70
  * The form holds a skeleton on this rather than mount: it seeds
@@ -290,6 +290,7 @@ const getInvoicingSubscriptionFormView = (state, invoicingSubscriptionID) => {
290
290
  charges: catalogItemOptionsOfType('charge'),
291
291
  coupons: (0, couponViewSelector_1.getInvoicingCouponListItems)(state).map(toInvoicingCouponOption),
292
292
  customers: (0, invoicingCustomerListViewSelector_1.getInvoicingAllCustomers)(state).map(toInvoicingCustomerOption),
293
+ isCustomerListLoading: (0, invoicingCustomerListViewSelector_1.isInvoicingAllCustomersLoading)(state),
293
294
  localData,
294
295
  mode,
295
296
  plans: planPrices.map(toInvoicingPlanOption),
@@ -6,6 +6,7 @@ const operators_1 = require("rxjs/operators");
6
6
  const couponViewReducer_1 = require("../couponView/couponViewReducer");
7
7
  const invoicingCatalogListViewReducer_1 = require("../invoicingCatalogListView/invoicingCatalogListViewReducer");
8
8
  const invoicingCustomerListViewReducer_1 = require("../invoicingCustomerListView/invoicingCustomerListViewReducer");
9
+ const invoicingListTabCache_1 = require("../invoicingListTabCache");
9
10
  const editInvoicingSubscriptionDetailViewReducer_1 = require("./editInvoicingSubscriptionDetailViewReducer");
10
11
  const editInvoicingSubscriptionDetailViewReducer_2 = require("./editInvoicingSubscriptionDetailViewReducer");
11
12
  /**
@@ -18,7 +19,13 @@ const editInvoicingSubscriptionDetailViewReducer_2 = require("./editInvoicingSub
18
19
  const fetchInvoicingSubscriptionFormPageEpic = (actions$) => actions$.pipe((0, operators_1.filter)(editInvoicingSubscriptionDetailViewReducer_2.fetchInvoicingSubscriptionFormPage.match), (0, operators_1.mergeMap)((action) => {
19
20
  const { subscriptionId } = action.payload;
20
21
  const pageActions = [
21
- (0, invoicingCustomerListViewReducer_1.fetchInvoicingCustomerList)({ filters: {}, keepExistingListItems: false }),
22
+ // The customer picker filters the unfiltered `all` cache client-side.
23
+ (0, invoicingCustomerListViewReducer_1.fetchInvoicingCustomerList)({
24
+ fetchAll: true,
25
+ filters: {},
26
+ keepExistingListItems: false,
27
+ tabId: invoicingListTabCache_1.INVOICING_LIST_ALL_TAB,
28
+ }),
22
29
  // Plans only: the form's plan picker is the one consumer, and an
23
30
  // unfiltered list would replace the store's plans with every catalog
24
31
  // item.
@@ -33,21 +33,33 @@ const fetchInvoicingCustomerListEpic = (actions$, state$, zeniAPI) => actions$.p
33
33
  // Per tab: a newer fetch of the same tab replaces its in-flight one, while
34
34
  // another tab's request keeps running and lands in its own cache.
35
35
  (0, operators_1.groupBy)(({ tabId }) => tabId), (0, operators_1.mergeMap)((tabFetches$) => tabFetches$.pipe((0, operators_1.switchMap)(({ action, tabId }) => {
36
- const { filters, cursor, keepExistingListItems, sort } = action.payload;
36
+ const { filters, cursor, fetchAll, keepExistingListItems, sort } = action.payload;
37
37
  const apiUrl = (0, invoicingApiHelper_1.withQuery)(`${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/customers`, buildQuery(filters, cursor, sort, (0, invoicingListTabCache_1.invoicingListStatusParam)(tabId)));
38
38
  return zeniAPI.getJSON(apiUrl).pipe((0, operators_1.switchMap)((response) => {
39
39
  if ((0, responsePayload_1.isSuccessResponse)(response)) {
40
40
  const items = response.data?.items ?? [];
41
- return [
41
+ const nextCursor = response.data?.next_cursor ?? null;
42
+ const pageActions = [
42
43
  (0, invoicingCustomerReducer_1.updateInvoicingCustomers)(items, 'merge'),
43
44
  (0, addressReducer_1.updateAddresses)((0, invoicingCustomerPayload_1.extractInvoicingCustomerAddresses)(items)),
44
45
  (0, invoicingCustomerListViewReducer_1.updateInvoicingCustomerList)({
45
46
  ids: items.map((item) => item.id),
46
- nextCursor: response.data?.next_cursor ?? null,
47
+ nextCursor,
47
48
  keepExistingListItems,
48
49
  tabId,
49
50
  }),
50
51
  ];
52
+ if (fetchAll && nextCursor != null && nextCursor !== cursor) {
53
+ pageActions.push((0, invoicingCustomerListViewReducer_1.fetchInvoicingCustomerList)({
54
+ cursor: nextCursor,
55
+ fetchAll,
56
+ filters,
57
+ keepExistingListItems: true,
58
+ sort,
59
+ tabId,
60
+ }));
61
+ }
62
+ return pageActions;
51
63
  }
52
64
  return (0, rxjs_1.of)((0, invoicingCustomerListViewReducer_1.updateInvoicingCustomerListFailure)({
53
65
  status: response.status,
@@ -6,6 +6,8 @@ import { InvoicingCustomerFilters, InvoicingCustomerListViewState } from './invo
6
6
  export declare const initialState: InvoicingCustomerListViewState;
7
7
  export declare const fetchInvoicingCustomerList: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[payload: {
8
8
  cursor?: string;
9
+ /** Keep requesting the next page until the list is complete. */
10
+ fetchAll?: boolean;
9
11
  filters?: InvoicingCustomerFilters;
10
12
  keepExistingListItems?: boolean;
11
13
  sort?: InvoicingListSort;
@@ -14,6 +16,7 @@ export declare const fetchInvoicingCustomerList: import("@reduxjs/toolkit").Acti
14
16
  }], {
15
17
  filters: InvoicingCustomerFilters;
16
18
  cursor: string | undefined;
19
+ fetchAll: boolean;
17
20
  keepExistingListItems: boolean;
18
21
  sort: InvoicingListSort | undefined;
19
22
  tabId: string | undefined;
@@ -28,6 +28,7 @@ const invoicingCustomerListView = (0, toolkit_1.createSlice)({
28
28
  payload: {
29
29
  filters: payload.filters ?? {},
30
30
  cursor: payload.cursor,
31
+ fetchAll: payload.fetchAll ?? false,
31
32
  keepExistingListItems: payload.keepExistingListItems ?? false,
32
33
  sort: payload.sort,
33
34
  tabId: payload.tabId,
@@ -30,3 +30,5 @@ export declare const getInvoicingCustomerListView: (state: RootState) => Invoici
30
30
  * view so they never narrow to whichever tab the customers list was left on.
31
31
  */
32
32
  export declare const getInvoicingAllCustomers: (state: RootState) => InvoicingCustomer[];
33
+ /** True while the pickers' customer list is still loading pages. */
34
+ export declare const isInvoicingAllCustomersLoading: (state: RootState) => boolean;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getInvoicingAllCustomers = exports.getInvoicingCustomerListView = void 0;
3
+ exports.isInvoicingAllCustomersLoading = exports.getInvoicingAllCustomers = exports.getInvoicingCustomerListView = void 0;
4
4
  const toolkit_1 = require("@reduxjs/toolkit");
5
5
  const invoicingCustomerSelector_1 = require("../../../entity/invoicing/invoicingCustomer/invoicingCustomerSelector");
6
6
  const invoicingListTabCache_1 = require("../invoicingListTabCache");
@@ -45,3 +45,6 @@ exports.getInvoicingCustomerListView = (0, toolkit_1.createSelector)((state) =>
45
45
  */
46
46
  const getInvoicingAllCustomers = (state) => (0, invoicingCustomerSelector_1.getInvoicingCustomersByIds)(state.invoicingCustomerState, (0, invoicingListTabCache_1.getInvoicingListTab)(state.invoicingCustomerListViewState.byTab, invoicingListTabCache_1.INVOICING_LIST_ALL_TAB).ids);
47
47
  exports.getInvoicingAllCustomers = getInvoicingAllCustomers;
48
+ /** True while the pickers' customer list is still loading pages. */
49
+ const isInvoicingAllCustomersLoading = (state) => (0, invoicingListTabCache_1.getInvoicingListTab)(state.invoicingCustomerListViewState.byTab, invoicingListTabCache_1.INVOICING_LIST_ALL_TAB).fetchState === 'In-Progress';
50
+ exports.isInvoicingAllCustomersLoading = isInvoicingAllCustomersLoading;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeniai/client-epic-state",
3
- "version": "5.3.22",
3
+ "version": "5.3.23",
4
4
  "description": "Shared module between Web & Mobile containing required abstractions for state management, async network communication. ",
5
5
  "main": "lib/index.js",
6
6
  "module": "lib/esm/index.js",