@selkirk-systems/fetch 1.2.1 → 1.3.0

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.
package/dist/Fetch.js CHANGED
@@ -439,34 +439,87 @@ export const putJsonInCache = (cache, url, json) => {
439
439
  return cache.put(url, response);
440
440
  };
441
441
  const _caches = {};
442
+ const _updatingCache = {};
442
443
  const DATA_METHODS = {
443
444
  "GET": null,
444
445
  "PATCH": null,
445
446
  "POST": null,
446
447
  "PUT": null
447
448
  };
448
- const API_REG_EXP = /p\/.+com\/(.*?)\//;
449
- export const getCacheNameFromUrl = url => {
450
- const matchArray = url.toString().match(API_REG_EXP);
451
- return matchArray && matchArray.length >= 1 ? matchArray[1] : null;
452
- };
453
449
  const _fetch = (url, options = {}) => {
454
450
  const cacheName = getCacheNameFromUrl(url);
451
+
452
+ //HANDLE: Service worker BS, environment is different don't cache if were in a service worker.
455
453
  if (!self || !cacheName) {
456
454
  return Fetch(url, options);
457
455
  }
458
456
  let _cache = _caches[cacheName];
459
- function cacheResponse([response, isAbort]) {
457
+ async function cacheResponse([response, isAbort]) {
460
458
  const status = response.status.code;
461
459
  const headers = response.request.headers;
462
460
  const method = response.request.method;
463
461
  if (status >= 200 && status < 400 && headers.get('content-type') === "application/json") {
464
- if (DATA_METHODS.hasOwnProperty(method) || method === "GET") {
462
+ if (DATA_METHODS.hasOwnProperty(method)) {
465
463
  const data = serializeData(response.data);
464
+
465
+ //HANDLE: List/Array like responses
466
466
  if (data.page && !data.items || data.items && data.items.length === 0) {
467
467
  deleteFromCache(_cache, url, response);
468
468
  return [response, isAbort];
469
469
  }
470
+
471
+ //HANDLE: Record like response
472
+ if (data.id) {
473
+ let uuid = getUUID(url.toString());
474
+ let matchOptions = {};
475
+ if (method === "POST") {
476
+ uuid = {
477
+ id: data.id,
478
+ shortPath: url.toString()
479
+ };
480
+ }
481
+
482
+ /**
483
+ * POST new records are hard, what cached list do we put the new record in? what about sort? blag. Don't have a good answer yet
484
+ * For now, new records will be put in the first cached entry that matches at the 0 index, this 'should' be page=0 cached entries
485
+ * and the records should all show up in UI at the top. This is the most common UX for adding records to a list,
486
+ * one day some how figure out a less fragile robust method.
487
+ *
488
+ * Perhaps we should just refetch all the matched cache urls... but this could be huge and slow and network heavy...
489
+ */
490
+ await cacheFindAllLike({
491
+ cache: _cache,
492
+ url: uuid.shortPath,
493
+ property: "id",
494
+ value: data.id,
495
+ method: method
496
+ }).then(async matches => {
497
+ if (matches.length) {
498
+ const finalOptions = {
499
+ ...responseObjectJson
500
+ };
501
+ finalOptions.headers['Time-Cached'] = new Date().getTime();
502
+ await matches.forEach(async match => {
503
+ const items = extractDataArray(match.data);
504
+
505
+ //This is where we put the new record on the top of the cached list, even if a new network request would place it somewhere
506
+ // else based on sort etc.
507
+ if (method === "POST") {
508
+ items.unshift(data);
509
+ } else {
510
+ items[match.matchIndex] = data;
511
+ }
512
+ const responseObj = new Response(JSON.stringify(match.data), finalOptions);
513
+ dispatch(UPDATE_CACHE, {
514
+ url: new URL(match.request.url),
515
+ response: match.response
516
+ });
517
+ return _cache.put(match.request.url, responseObj);
518
+ });
519
+ }
520
+ });
521
+ return [response, isAbort];
522
+ }
470
523
  const finalOptions = {
471
524
  ...responseObjectJson
472
525
  };
@@ -488,9 +541,37 @@ const _fetch = (url, options = {}) => {
488
541
  }
489
542
  function cacheMatch() {
490
543
  //HANDLE: Data updates, always return fresh data
491
- if (options.method && DATA_METHODS.hasOwnProperty(options.method)) {
544
+ if (options.skipCache || options.method && DATA_METHODS.hasOwnProperty(options.method)) {
492
545
  return Fetch(url, options).then(cacheResponse);
493
546
  }
547
+ const uuid = getUUID(url.toString());
548
+
549
+ //HANDLE: individual records, if we are looking for a record check cache for any array or search results with this record
550
+ if (uuid.id) {
551
+ return cacheFindLike({
552
+ cache: _cache,
553
+ url: uuid.shortPath,
554
+ property: "id",
555
+ value: uuid.id
556
+ }).then(match => {
557
+ if (match) {
558
+ //Behind the scenes still fetch the record and update the cache but don't make the user wait for it.
559
+ Fetch(url, options).then(cacheResponse);
560
+ const responseObj = new Response(JSON.stringify(match.match));
561
+ return Promise.resolve([{
562
+ request: null,
563
+ response: responseObj,
564
+ data: match.match,
565
+ status: {
566
+ code: 200,
567
+ text: '',
568
+ isAbort: false
569
+ }
570
+ }, false]);
571
+ }
572
+ return Fetch(url, options).then(cacheResponse);
573
+ });
574
+ }
494
575
  return _cache.match(url).then(response => {
495
576
  if (response) {
496
577
  const timeCached = response.headers.get('Time-Cached');
@@ -530,6 +611,119 @@ function deleteFromCache(_cache, url, response) {
530
611
  response: response
531
612
  });
532
613
  }
614
+ function cacheFindAllLike(options) {
615
+ const finalOptions = {
616
+ ...options,
617
+ findAll: true
618
+ };
619
+ return cacheFindLike(finalOptions);
620
+ }
621
+ function cacheFindLike(options) {
622
+ const cache = options.cache;
623
+ const url = options.url;
624
+ const property = options.property;
625
+ const value = options.value;
626
+ const findAll = options.findAll;
627
+ const matchOptions = options.matchOptions;
628
+ const method = options.method || "GET";
629
+ return cache.keys().then(keys => {
630
+ const matchingRequests = [];
631
+ for (let i = 0; i < keys.length; i++) {
632
+ const request = keys[i];
633
+ if (looksLikeResultListUrl(request.url, url)) {
634
+ matchingRequests.push(cache.match(request, matchOptions).then(match => {
635
+ match._request = request;
636
+ return match;
637
+ }));
638
+ }
639
+ }
640
+ return Promise.all(matchingRequests).then(responses => {
641
+ let count = 0;
642
+ const ret = [];
643
+ const checkCache = async index => {
644
+ if (index >= responses.length) return ret;
645
+ return serializeResponse(responses[index]).then(json => {
646
+ const items = extractDataArray(json);
647
+ const response = responses[count];
648
+ const retObj = {
649
+ request: response._request,
650
+ response: response,
651
+ data: json,
652
+ match: json,
653
+ matchIndex: -1,
654
+ status: {
655
+ code: response.status,
656
+ text: response.statusText,
657
+ isAbort: false
658
+ }
659
+ };
660
+ if (method === "POST") {
661
+ return findAll ? [retObj] : retObj;
662
+ }
663
+ const matchIndex = items.findIndex(item => item[property] === value);
664
+ count++;
665
+ if (matchIndex >= 0) {
666
+ retObj.match = items[matchIndex];
667
+ retObj.matchIndex = matchIndex;
668
+ if (!findAll) {
669
+ return retObj;
670
+ } else {
671
+ ret.push(retObj);
672
+ }
673
+ return checkCache(count);
674
+ }
675
+ if (count >= responses.length) {
676
+ return findAll ? ret : null;
677
+ }
678
+ return checkCache(count);
679
+ });
680
+ };
681
+ if (!responses || !responses.length) return findAll ? ret : null;
682
+ return checkCache(count);
683
+ });
684
+ });
685
+ }
686
+
687
+ /**
688
+ * Check if request.url looks like a list result type url, if it contains a UUID it is likely not.
689
+ * @param {*} requestURL
690
+ * @param {*} matchURL
691
+ * @returns
692
+ */
693
+ function looksLikeResultListUrl(requestURL, matchURL) {
694
+ const uuid = getUUID(requestURL.toString());
695
+ return !uuid.id && requestURL.indexOf(matchURL) >= 0;
696
+ }
697
+ function extractDataArray(obj) {
698
+ for (var prop in obj._embedded) {
699
+ if (!obj._embedded.hasOwnProperty(prop)) continue;
700
+ return obj._embedded[prop];
701
+ }
702
+ return [obj];
703
+ }
704
+ async function serializeResponse(response) {
705
+ return response.text().then(data => {
706
+ return JSON.parse(data);
707
+ });
708
+ }
709
+ function getUUID(str) {
710
+ const UUID_REG_EXP = /.+([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}).*?/;
711
+ const match = UUID_REG_EXP.exec(str);
712
+ let id, shortPath;
713
+ if (match && match[1]) {
714
+ id = match[1];
715
+ shortPath = str.split(`/${id}`)[0];
716
+ }
717
+ return {
718
+ id: id,
719
+ shortPath: shortPath
720
+ };
721
+ }
722
+ function getCacheNameFromUrl(url) {
723
+ const API_REG_EXP = /p\/.+com\/(.*?)\//;
724
+ const matchArray = url.toString().match(API_REG_EXP);
725
+ return matchArray && matchArray.length >= 1 ? matchArray[1] : null;
726
+ }
533
727
  function expiredCache(timeCached) {
534
728
  const now = new Date().getTime();
535
729
  return Math.abs(now - timeCached) >= CACHED_EXPIRY_TIMESTAMP;
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { default as Download } from './Download';
2
2
  export { default as fetch, applyMiddleware, OnOKResponse } from './Fetch';
3
+ export * from "./utils/FetchUtils";
3
4
  export * from "./constants/FetchConstants";
@@ -0,0 +1,74 @@
1
+ import Fetch from '../Fetch';
2
+ export const createUrlParams = (params, existingParams) => {
3
+ //SORT the params alphabetically, very important for cache lookups as the search a params in different order will cause it to not be matched.
4
+ params = Object.keys(params).sort().reduce((obj, key) => {
5
+ obj[key] = params[key];
6
+ return obj;
7
+ }, {});
8
+ const searchParams = existingParams ? existingParams : new URLSearchParams();
9
+ for (var prop in params) {
10
+ if (!params.hasOwnProperty(prop) || params[prop] === undefined || params[prop] === null) continue;
11
+ if (Array.isArray(params[prop])) {
12
+ if (prop === "sort") {
13
+ searchParams.delete("sort");
14
+ }
15
+ for (let i = 0; i < params[prop].length; i++) {
16
+ const e = params[prop][i];
17
+ if (searchParams.has(prop) && prop !== "sort") {
18
+ searchParams.set(prop, e);
19
+ } else {
20
+ searchParams.append(prop, e);
21
+ }
22
+ }
23
+ continue;
24
+ }
25
+ if (searchParams.has(prop)) {
26
+ searchParams.set(prop, params[prop]);
27
+ } else {
28
+ searchParams.append(prop, params[prop]);
29
+ }
30
+ }
31
+ return searchParams;
32
+ };
33
+ export async function fetchPagedURL(url, callback, options) {
34
+ const finalOptions = {
35
+ skipCache: false,
36
+ ...options
37
+ };
38
+ callback = callback ? callback : () => {};
39
+ let ret = [];
40
+ const proxyUrl = url.toString().split("/p/");
41
+ const [response] = await Fetch(url, finalOptions);
42
+ const {
43
+ data,
44
+ status
45
+ } = response;
46
+ const size = new TextEncoder().encode(JSON.stringify(data)).length;
47
+ const kiloBytes = size / 1024;
48
+ const megaBytes = kiloBytes / 1024;
49
+ if (status.code > 299 || status.code < 200) {
50
+ return [response, null];
51
+ }
52
+ const results = {
53
+ page: data.page.number + 1,
54
+ totalPages: data.page.totalPages,
55
+ totalElements: data.page.totalElements,
56
+ pageSize: data.page.size,
57
+ kiloBytes: kiloBytes,
58
+ megaBytes: megaBytes
59
+ };
60
+ callback(results);
61
+ data.items = data._embedded[[Object.keys(data._embedded)[0]]];
62
+ ret = ret.concat(data.items || []);
63
+ if (data._links && data._links.next) {
64
+ const nextUrl = new URL(`${proxyUrl[0]}/p/${data._links.next.href}`);
65
+ nextUrl.search = createUrlParams({}, nextUrl.searchParams);
66
+ const [err, nextPage] = await fetchPagedURL(nextUrl, callback, options);
67
+ if (err) return [err, nextPage];
68
+ ret = ret.concat(nextPage);
69
+ }
70
+ return [null, ret];
71
+ }
72
+ function timeout(ms) {
73
+ return new Promise(resolve => setTimeout(resolve, ms));
74
+ }
package/lib/Fetch.js CHANGED
@@ -576,6 +576,10 @@ export const putJsonInCache = ( cache, url, json ) => {
576
576
 
577
577
  const _caches = {};
578
578
 
579
+ const _updatingCache = {
580
+
581
+ }
582
+
579
583
  const DATA_METHODS = {
580
584
  "GET": null,
581
585
  "PATCH": null,
@@ -583,24 +587,20 @@ const DATA_METHODS = {
583
587
  "PUT": null
584
588
  }
585
589
 
586
- const API_REG_EXP = /p\/.+com\/(.*?)\//;
587
590
 
588
- export const getCacheNameFromUrl = ( url ) => {
589
- const matchArray = url.toString().match( API_REG_EXP );
590
- return matchArray && matchArray.length >= 1 ? matchArray[1] : null;
591
- }
592
591
 
593
592
  const _fetch = ( url, options = {} ) => {
594
593
 
595
594
  const cacheName = getCacheNameFromUrl( url );
596
595
 
596
+ //HANDLE: Service worker BS, environment is different don't cache if were in a service worker.
597
597
  if ( !self || !cacheName ) {
598
598
  return Fetch( url, options );
599
599
  }
600
600
 
601
601
  let _cache = _caches[cacheName];
602
602
 
603
- function cacheResponse( [response, isAbort] ) {
603
+ async function cacheResponse( [response, isAbort] ) {
604
604
 
605
605
  const status = response.status.code;
606
606
  const headers = response.request.headers;
@@ -609,10 +609,11 @@ const _fetch = ( url, options = {} ) => {
609
609
 
610
610
  if ( status >= 200 && status < 400 && headers.get( 'content-type' ) === "application/json" ) {
611
611
 
612
- if ( DATA_METHODS.hasOwnProperty( method ) || method === "GET" ) {
612
+ if ( DATA_METHODS.hasOwnProperty( method ) ) {
613
613
 
614
614
  const data = serializeData( response.data );
615
615
 
616
+ //HANDLE: List/Array like responses
616
617
  if ( data.page && !data.items || data.items && data.items.length === 0 ) {
617
618
 
618
619
  deleteFromCache( _cache, url, response );
@@ -621,6 +622,65 @@ const _fetch = ( url, options = {} ) => {
621
622
 
622
623
  }
623
624
 
625
+ //HANDLE: Record like response
626
+ if ( data.id ) {
627
+
628
+ let uuid = getUUID( url.toString() );
629
+ let matchOptions = {};
630
+
631
+ if ( method === "POST" ) {
632
+
633
+ uuid = {
634
+ id: data.id,
635
+ shortPath: url.toString()
636
+ }
637
+
638
+ }
639
+
640
+ /**
641
+ * POST new records are hard, what cached list do we put the new record in? what about sort? blag. Don't have a good answer yet
642
+ * For now, new records will be put in the first cached entry that matches at the 0 index, this 'should' be page=0 cached entries
643
+ * and the records should all show up in UI at the top. This is the most common UX for adding records to a list,
644
+ * one day some how figure out a less fragile robust method.
645
+ *
646
+ * Perhaps we should just refetch all the matched cache urls... but this could be huge and slow and network heavy...
647
+ */
648
+ await cacheFindAllLike( { cache: _cache, url: uuid.shortPath, property: "id", value: data.id, method: method } )
649
+ .then( async ( matches ) => {
650
+
651
+ if ( matches.length ) {
652
+
653
+ const finalOptions = { ...responseObjectJson };
654
+ finalOptions.headers['Time-Cached'] = new Date().getTime();
655
+
656
+ await matches.forEach( async match => {
657
+
658
+ const items = extractDataArray( match.data );
659
+
660
+ //This is where we put the new record on the top of the cached list, even if a new network request would place it somewhere
661
+ // else based on sort etc.
662
+ if ( method === "POST" ) {
663
+ items.unshift( data );
664
+ }
665
+ else {
666
+ items[match.matchIndex] = data;
667
+ }
668
+
669
+ const responseObj = new Response( JSON.stringify( match.data ), finalOptions );
670
+
671
+ dispatch( UPDATE_CACHE, { url: new URL( match.request.url ), response: match.response } );
672
+
673
+ return _cache.put( match.request.url, responseObj );
674
+
675
+ } )
676
+
677
+ }
678
+ } )
679
+
680
+ return [response, isAbort];
681
+
682
+ }
683
+
624
684
  const finalOptions = { ...responseObjectJson };
625
685
  finalOptions.headers['Time-Cached'] = new Date().getTime();
626
686
 
@@ -649,12 +709,45 @@ const _fetch = ( url, options = {} ) => {
649
709
  function cacheMatch() {
650
710
 
651
711
  //HANDLE: Data updates, always return fresh data
652
- if ( options.method && DATA_METHODS.hasOwnProperty( options.method ) ) {
712
+ if ( options.skipCache || options.method && DATA_METHODS.hasOwnProperty( options.method ) ) {
653
713
 
654
714
  return Fetch( url, options ).then( cacheResponse );
655
715
 
656
716
  }
657
717
 
718
+ const uuid = getUUID( url.toString() );
719
+
720
+ //HANDLE: individual records, if we are looking for a record check cache for any array or search results with this record
721
+ if ( uuid.id ) {
722
+
723
+ return cacheFindLike( { cache: _cache, url: uuid.shortPath, property: "id", value: uuid.id } ).then( ( match ) => {
724
+
725
+ if ( match ) {
726
+
727
+ //Behind the scenes still fetch the record and update the cache but don't make the user wait for it.
728
+ Fetch( url, options ).then( cacheResponse );
729
+
730
+ const responseObj = new Response( JSON.stringify( match.match ) );
731
+
732
+ return Promise.resolve( [{
733
+ request: null,
734
+ response: responseObj,
735
+ data: match.match,
736
+ status: {
737
+ code: 200,
738
+ text: '',
739
+ isAbort: false
740
+ },
741
+ }, false] )
742
+ }
743
+
744
+
745
+ return Fetch( url, options ).then( cacheResponse );
746
+
747
+ } )
748
+
749
+ }
750
+
658
751
 
659
752
  return _cache.match( url ).then( ( response ) => {
660
753
 
@@ -705,6 +798,7 @@ const _fetch = ( url, options = {} ) => {
705
798
 
706
799
  }
707
800
 
801
+
708
802
  function deleteFromCache( _cache, url, response ) {
709
803
 
710
804
  _cache.delete( url );
@@ -712,10 +806,183 @@ function deleteFromCache( _cache, url, response ) {
712
806
 
713
807
  }
714
808
 
809
+ function cacheFindAllLike( options ) {
810
+
811
+ const finalOptions = {
812
+ ...options,
813
+ findAll: true
814
+ }
815
+
816
+ return cacheFindLike( finalOptions );
817
+ }
818
+
819
+ function cacheFindLike( options ) {
820
+
821
+ const cache = options.cache;
822
+ const url = options.url;
823
+ const property = options.property;
824
+ const value = options.value;
825
+ const findAll = options.findAll;
826
+ const matchOptions = options.matchOptions
827
+ const method = options.method || "GET";
828
+
829
+ return cache.keys().then( ( keys ) => {
830
+
831
+ const matchingRequests = [];
832
+
833
+ for ( let i = 0; i < keys.length; i++ ) {
834
+
835
+ const request = keys[i];
836
+
837
+ if ( looksLikeResultListUrl( request.url, url ) ) {
838
+
839
+ matchingRequests.push( cache.match( request, matchOptions ).then( ( match ) => {
840
+
841
+ match._request = request;
842
+
843
+ return match;
844
+ } ) )
845
+
846
+ }
847
+
848
+ }
849
+
850
+ return Promise.all( matchingRequests ).then( ( responses ) => {
851
+ let count = 0;
852
+
853
+ const ret = [];
854
+
855
+ const checkCache = async ( index ) => {
856
+
857
+ if ( index >= responses.length ) return ret;
858
+
859
+ return serializeResponse( responses[index] ).then( json => {
860
+
861
+ const items = extractDataArray( json );
862
+ const response = responses[count];
863
+
864
+ const retObj = {
865
+ request: response._request,
866
+ response: response,
867
+ data: json,
868
+ match: json,
869
+ matchIndex: -1,
870
+ status: {
871
+ code: response.status,
872
+ text: response.statusText,
873
+ isAbort: false
874
+ }
875
+ }
876
+
877
+ if ( method === "POST" ) {
878
+ return findAll ? [retObj] : retObj;
879
+ }
880
+
881
+ const matchIndex = items.findIndex( item => item[property] === value );
882
+
883
+ count++;
884
+
885
+ if ( matchIndex >= 0 ) {
886
+
887
+ retObj.match = items[matchIndex];
888
+ retObj.matchIndex = matchIndex;
889
+
890
+ if ( !findAll ) {
891
+ return retObj;
892
+ }
893
+ else {
894
+ ret.push( retObj );
895
+ }
896
+
897
+ return checkCache( count );
898
+ }
899
+
900
+
901
+ if ( count >= responses.length ) {
902
+ return findAll ? ret : null;
903
+ }
904
+
905
+ return checkCache( count );
906
+
907
+ } )
908
+ }
909
+
910
+ if ( !responses || !responses.length ) return findAll ? ret : null;
911
+
912
+ return checkCache( count );
913
+ } )
914
+ } );
915
+
916
+
917
+ }
918
+
919
+ /**
920
+ * Check if request.url looks like a list result type url, if it contains a UUID it is likely not.
921
+ * @param {*} requestURL
922
+ * @param {*} matchURL
923
+ * @returns
924
+ */
925
+ function looksLikeResultListUrl( requestURL, matchURL ) {
926
+
927
+ const uuid = getUUID( requestURL.toString() );
928
+
929
+ return !uuid.id && requestURL.indexOf( matchURL ) >= 0;
930
+ }
931
+
932
+
933
+ function extractDataArray( obj ) {
934
+ for ( var prop in obj._embedded ) {
935
+ if ( !obj._embedded.hasOwnProperty( prop ) ) continue;
936
+ return obj._embedded[prop];
937
+ }
938
+
939
+ return [obj];
940
+ }
941
+
942
+ async function serializeResponse( response ) {
943
+
944
+ return response.text().then( data => {
945
+ return JSON.parse( data );
946
+ } )
947
+
948
+ }
949
+
950
+ function getUUID( str ) {
951
+
952
+ const UUID_REG_EXP = /.+([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}).*?/;
953
+ const match = UUID_REG_EXP.exec( str );
954
+
955
+ let id, shortPath;
956
+
957
+ if ( match && match[1] ) {
958
+ id = match[1];
959
+ shortPath = str.split( `/${id}` )[0];
960
+ }
961
+
962
+ return {
963
+ id: id,
964
+ shortPath: shortPath
965
+ }
966
+
967
+ }
968
+
969
+
970
+
971
+ function getCacheNameFromUrl( url ) {
972
+
973
+ const API_REG_EXP = /p\/.+com\/(.*?)\//;
974
+ const matchArray = url.toString().match( API_REG_EXP );
975
+
976
+ return matchArray && matchArray.length >= 1 ? matchArray[1] : null;
977
+
978
+ }
979
+
715
980
  function expiredCache( timeCached ) {
981
+
716
982
  const now = new Date().getTime();
717
983
 
718
984
  return Math.abs( now - timeCached ) >= CACHED_EXPIRY_TIMESTAMP;
985
+
719
986
  }
720
987
 
721
988
  export default _fetch;
package/lib/index.js CHANGED
@@ -1,3 +1,5 @@
1
1
  export { default as Download } from './Download';
2
2
  export { default as fetch, applyMiddleware, OnOKResponse } from './Fetch';
3
+
4
+ export * from "./utils/FetchUtils";
3
5
  export * from "./constants/FetchConstants";
@@ -0,0 +1,118 @@
1
+ import Fetch from '../Fetch';
2
+
3
+
4
+
5
+ export const createUrlParams = ( params, existingParams ) => {
6
+
7
+
8
+ //SORT the params alphabetically, very important for cache lookups as the search a params in different order will cause it to not be matched.
9
+ params = Object.keys( params ).sort().reduce(
10
+ ( obj, key ) => {
11
+ obj[key] = params[key];
12
+ return obj;
13
+ },
14
+ {}
15
+ );
16
+
17
+ const searchParams = existingParams ? existingParams : new URLSearchParams();
18
+
19
+ for ( var prop in params ) {
20
+ if ( !params.hasOwnProperty( prop ) || params[prop] === undefined || params[prop] === null ) continue;
21
+
22
+ if ( Array.isArray( params[prop] ) ) {
23
+
24
+ if ( prop === "sort" ) {
25
+ searchParams.delete( "sort" );
26
+ }
27
+
28
+ for ( let i = 0; i < params[prop].length; i++ ) {
29
+ const e = params[prop][i];
30
+ if ( searchParams.has( prop ) && prop !== "sort" ) {
31
+ searchParams.set( prop, e );
32
+ }
33
+ else {
34
+ searchParams.append( prop, e );
35
+ }
36
+ }
37
+ continue;
38
+ }
39
+
40
+ if ( searchParams.has( prop ) ) {
41
+ searchParams.set( prop, params[prop] );
42
+ }
43
+ else {
44
+ searchParams.append( prop, params[prop] );
45
+ }
46
+ }
47
+
48
+
49
+ return searchParams;
50
+
51
+
52
+ }
53
+
54
+
55
+ export async function fetchPagedURL( url, callback, options ) {
56
+
57
+ const finalOptions = {
58
+ skipCache: false,
59
+ ...options
60
+ }
61
+
62
+ callback = callback ? callback : () => { };
63
+
64
+ let ret = [];
65
+
66
+ const proxyUrl = url.toString().split( "/p/" );
67
+
68
+ const [response] = await Fetch( url, finalOptions );
69
+ const { data, status } = response;
70
+
71
+ const size = new TextEncoder().encode( JSON.stringify( data ) ).length;
72
+ const kiloBytes = size / 1024;
73
+ const megaBytes = kiloBytes / 1024;
74
+
75
+ if ( status.code > 299 || status.code < 200 ) {
76
+ return [response, null];
77
+ }
78
+
79
+ const results = {
80
+ page: data.page.number + 1,
81
+ totalPages: data.page.totalPages,
82
+ totalElements: data.page.totalElements,
83
+ pageSize: data.page.size,
84
+ kiloBytes: kiloBytes,
85
+ megaBytes: megaBytes
86
+ }
87
+
88
+ callback( results );
89
+
90
+ data.items = data._embedded[[Object.keys( data._embedded )[0]]];
91
+
92
+ ret = ret.concat( data.items || [] );
93
+
94
+ if ( data._links && data._links.next ) {
95
+
96
+ const nextUrl = new URL( `${proxyUrl[0]}/p/${data._links.next.href}` );
97
+
98
+ nextUrl.search = createUrlParams( {}, nextUrl.searchParams );
99
+
100
+ const [err, nextPage] = await fetchPagedURL( nextUrl, callback, options );
101
+
102
+ if ( err ) return [err, nextPage];
103
+
104
+ ret = ret.concat( nextPage );
105
+
106
+ }
107
+
108
+ return [null, ret];
109
+
110
+ }
111
+
112
+
113
+ function timeout( ms ) {
114
+
115
+ return new Promise( resolve => setTimeout( resolve, ms ) );
116
+
117
+ }
118
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@selkirk-systems/fetch",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "description": "Abortable fetch library",
5
5
  "keywords": [],
6
6
  "author": "Marcos Bernal <mbernal@selkirksystems.com>",
@@ -36,5 +36,5 @@
36
36
  "peerDependencies": {
37
37
  "@selkirk-systems/state-management": ">=1.0.0"
38
38
  },
39
- "gitHead": "319e3d6212d678f409052a628b9a28455ecc7ecd"
39
+ "gitHead": "d981c2a39edb04be17105f9c328942380ccb32f7"
40
40
  }