fhirsmith 0.11.2 → 0.12.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/tx/tx.js CHANGED
@@ -249,6 +249,10 @@ class TXModule {
249
249
  // cacheTimeout is in minutes, default to 30 minutes
250
250
  const cacheTimeoutMs = cacheTimeoutMinutes * 60 * 1000;
251
251
  const pruneIntervalMs = 5 * 60 * 1000; // Run every 5 minutes
252
+ // Tell the cache what timeout it is being pruned against, so $cache-control?mode=check
253
+ // can report it and a client can size its keepalive interval to this server rather
254
+ // than guessing at a number it has no way to see.
255
+ endpointInfo.resourceCache.setIdleTimeout(cacheTimeoutMs);
252
256
  if (this.stats) {
253
257
  this.stats.addTask("Client Cache", "5 min");
254
258
  }
@@ -491,7 +495,7 @@ class TXModule {
491
495
  app.use(express.urlencoded({ extended: true }));
492
496
 
493
497
  // Set up routes
494
- this.setupRoutes(router);
498
+ this.setupRoutes(router, endpointInfo.path);
495
499
 
496
500
  // Redirect /r5 → /r5/
497
501
  app.use((req, res, next) => {
@@ -512,8 +516,11 @@ class TXModule {
512
516
  /**
513
517
  * Set up routes for an endpoint
514
518
  * @param {express.Router} router - Express router
519
+ * @param {string} endpointPath - the endpoint these routes belong to, e.g.
520
+ * '/tx/r4'. Captured by the handlers below so that every request is
521
+ * counted against the endpoint that served it, not just the operation.
515
522
  */
516
- setupRoutes(router) {
523
+ setupRoutes(router, endpointPath) {
517
524
  const resourceTypes = ['CodeSystem', 'ValueSet', 'ConceptMap'];
518
525
 
519
526
  // ===== Operations =====
@@ -526,7 +533,7 @@ class TXModule {
526
533
  let worker = new LookupWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
527
534
  await worker.handle(req, res);
528
535
  } finally {
529
- this.countRequest('$lookup', Date.now() - start);
536
+ this.countRequest(endpointPath, '$lookup', Date.now() - start);
530
537
  }
531
538
  });
532
539
  router.post('/CodeSystem/\\$lookup', async (req, res) => {
@@ -535,7 +542,7 @@ class TXModule {
535
542
  let worker = new LookupWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
536
543
  await worker.handle(req, res);
537
544
  } finally {
538
- this.countRequest('$lookup', Date.now() - start);
545
+ this.countRequest(endpointPath, '$lookup', Date.now() - start);
539
546
  }
540
547
  });
541
548
 
@@ -546,7 +553,7 @@ class TXModule {
546
553
  let worker = new SubsumesWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
547
554
  await worker.handle(req, res);
548
555
  } finally {
549
- this.countRequest('$subsumes', Date.now() - start);
556
+ this.countRequest(endpointPath, '$subsumes', Date.now() - start);
550
557
  }
551
558
  });
552
559
  router.post('/CodeSystem/\\$subsumes', async (req, res) => {
@@ -555,7 +562,7 @@ class TXModule {
555
562
  let worker = new SubsumesWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
556
563
  await worker.handle(req, res);
557
564
  } finally {
558
- this.countRequest('$subsumes', Date.now() - start);
565
+ this.countRequest(endpointPath, '$subsumes', Date.now() - start);
559
566
  }
560
567
  });
561
568
 
@@ -566,7 +573,7 @@ class TXModule {
566
573
  let worker = new ValidateWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
567
574
  await worker.handleCodeSystem(req, res);
568
575
  } finally {
569
- this.countRequest('$validate', Date.now() - start);
576
+ this.countRequest(endpointPath, '$validate', Date.now() - start);
570
577
  }
571
578
  });
572
579
  router.post('/CodeSystem/\\$validate-code', async (req, res) => {
@@ -575,7 +582,7 @@ class TXModule {
575
582
  let worker = new ValidateWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
576
583
  await worker.handleCodeSystem(req, res);
577
584
  } finally {
578
- this.countRequest('$validate', Date.now() - start);
585
+ this.countRequest(endpointPath, '$validate', Date.now() - start);
579
586
  }
580
587
  });
581
588
 
@@ -586,7 +593,7 @@ class TXModule {
586
593
  let worker = new BatchValidateWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
587
594
  await worker.handleCodeSystem(req, res);
588
595
  } finally {
589
- this.countRequest('$batch', Date.now() - start);
596
+ this.countRequest(endpointPath, '$batch', Date.now() - start);
590
597
  }
591
598
  });
592
599
  router.post('/CodeSystem/\\$batch-validate-code', async (req, res) => {
@@ -595,7 +602,7 @@ class TXModule {
595
602
  let worker = new BatchValidateWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
596
603
  await worker.handleCodeSystem(req, res);
597
604
  } finally {
598
- this.countRequest('$batch', Date.now() - start);
605
+ this.countRequest(endpointPath, '$batch', Date.now() - start);
599
606
  }
600
607
  });
601
608
  // ValueSet/$validate-code (GET and POST)
@@ -605,7 +612,7 @@ class TXModule {
605
612
  let worker = new ValidateWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
606
613
  await worker.handleValueSet(req, res);
607
614
  } finally {
608
- this.countRequest('$validate', Date.now() - start);
615
+ this.countRequest(endpointPath, '$validate', Date.now() - start);
609
616
  }
610
617
  });
611
618
  router.post('/ValueSet/\\$validate-code', async (req, res) => {
@@ -614,7 +621,7 @@ class TXModule {
614
621
  let worker = new ValidateWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
615
622
  await worker.handleValueSet(req, res);
616
623
  } finally {
617
- this.countRequest('$validate', Date.now() - start);
624
+ this.countRequest(endpointPath, '$validate', Date.now() - start);
618
625
  }
619
626
  });
620
627
 
@@ -625,7 +632,7 @@ class TXModule {
625
632
  let worker = new CompareWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
626
633
  await worker.handle(req, res);
627
634
  } finally {
628
- this.countRequest('$compare', Date.now() - start);
635
+ this.countRequest(endpointPath, '$compare', Date.now() - start);
629
636
  }
630
637
  });
631
638
  router.post('/ValueSet/\\$compare', async (req, res) => {
@@ -634,7 +641,7 @@ class TXModule {
634
641
  let worker = new CompareWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
635
642
  await worker.handle(req, res);
636
643
  } finally {
637
- this.countRequest('$compare', Date.now() - start);
644
+ this.countRequest(endpointPath, '$compare', Date.now() - start);
638
645
  }
639
646
  });
640
647
 
@@ -645,7 +652,7 @@ class TXModule {
645
652
  let worker = new BatchValidateWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
646
653
  await worker.handleValueSet(req, res);
647
654
  } finally {
648
- this.countRequest('$batch', Date.now() - start);
655
+ this.countRequest(endpointPath, '$batch', Date.now() - start);
649
656
  }
650
657
  });
651
658
  router.post('/ValueSet/\\$batch-validate-code', async (req, res) => {
@@ -654,7 +661,7 @@ class TXModule {
654
661
  let worker = new BatchValidateWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
655
662
  await worker.handleValueSet(req, res);
656
663
  } finally {
657
- this.countRequest('validate', Date.now() - start);
664
+ this.countRequest(endpointPath, 'validate', Date.now() - start);
658
665
  }
659
666
  });
660
667
 
@@ -665,7 +672,7 @@ class TXModule {
665
672
  let worker = new ExpandWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n, this.internalLimit(req), this.externalLimit(req));
666
673
  await worker.handle(req, res, this.log);
667
674
  } finally {
668
- this.countRequest('$expand', Date.now() - start);
675
+ this.countRequest(endpointPath, '$expand', Date.now() - start);
669
676
  }
670
677
  });
671
678
  router.post('/ValueSet/\\$expand', async (req, res) => {
@@ -674,7 +681,7 @@ class TXModule {
674
681
  let worker = new ExpandWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n, this.internalLimit(req), this.externalLimit(req));
675
682
  await worker.handle(req, res, this.log);
676
683
  } finally {
677
- this.countRequest('$expand', Date.now() - start);
684
+ this.countRequest(endpointPath, '$expand', Date.now() - start);
678
685
  }
679
686
  });
680
687
 
@@ -685,7 +692,7 @@ class TXModule {
685
692
  let worker = new CacheControlWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
686
693
  await worker.handle(req, res, this.log);
687
694
  } finally {
688
- this.countRequest('$cache-control', Date.now() - start);
695
+ this.countRequest(endpointPath, '$cache-control', Date.now() - start);
689
696
  }
690
697
  });
691
698
  router.post('/\\$cache-control', async (req, res) => {
@@ -694,7 +701,7 @@ class TXModule {
694
701
  let worker = new CacheControlWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
695
702
  await worker.handle(req, res, this.log);
696
703
  } finally {
697
- this.countRequest('$cache-control', Date.now() - start);
704
+ this.countRequest(endpointPath, '$cache-control', Date.now() - start);
698
705
  }
699
706
  });
700
707
 
@@ -705,7 +712,7 @@ class TXModule {
705
712
  let worker = new TranslateWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
706
713
  await worker.handle(req, res, this.log);
707
714
  } finally {
708
- this.countRequest('$translate', Date.now() - start);
715
+ this.countRequest(endpointPath, '$translate', Date.now() - start);
709
716
  }
710
717
  });
711
718
  router.post('/ConceptMap/\\$translate', async (req, res) => {
@@ -714,7 +721,7 @@ class TXModule {
714
721
  let worker = new TranslateWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
715
722
  await worker.handle(req, res, this.log);
716
723
  } finally {
717
- this.countRequest('$translate', Date.now() - start);
724
+ this.countRequest(endpointPath, '$translate', Date.now() - start);
718
725
  }
719
726
  });
720
727
 
@@ -725,7 +732,7 @@ class TXModule {
725
732
  let worker = new ClosureWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
726
733
  await worker.handle(req, res, this.log);
727
734
  } finally {
728
- this.countRequest('$closure', Date.now() - start);
735
+ this.countRequest(endpointPath, '$closure', Date.now() - start);
729
736
  }
730
737
  });
731
738
  router.post('/ConceptMap/\\$closure', async (req, res) => {
@@ -734,7 +741,7 @@ class TXModule {
734
741
  let worker = new ClosureWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
735
742
  await worker.handle(req, res, this.log);
736
743
  } finally {
737
- this.countRequest('$closure', Date.now() - start);
744
+ this.countRequest(endpointPath, '$closure', Date.now() - start);
738
745
  }
739
746
  });
740
747
 
@@ -747,7 +754,7 @@ class TXModule {
747
754
  let worker = new LookupWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
748
755
  await worker.handleInstance(req, res);
749
756
  } finally {
750
- this.countRequest('$lookup', Date.now() - start);
757
+ this.countRequest(endpointPath, '$lookup', Date.now() - start);
751
758
  }
752
759
  });
753
760
  router.post('/CodeSystem/:id/\\$lookup', async (req, res) => {
@@ -756,7 +763,7 @@ class TXModule {
756
763
  let worker = new LookupWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
757
764
  await worker.handleInstance(req, res);
758
765
  } finally {
759
- this.countRequest('$lookup', Date.now() - start);
766
+ this.countRequest(endpointPath, '$lookup', Date.now() - start);
760
767
  }
761
768
  });
762
769
 
@@ -767,7 +774,7 @@ class TXModule {
767
774
  let worker = new SubsumesWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
768
775
  await worker.handleInstance(req, res);
769
776
  } finally {
770
- this.countRequest('$subsumes', Date.now() - start);
777
+ this.countRequest(endpointPath, '$subsumes', Date.now() - start);
771
778
  }
772
779
  });
773
780
  router.post('/CodeSystem/:id/\\$subsumes', async (req, res) => {
@@ -776,7 +783,7 @@ class TXModule {
776
783
  let worker = new SubsumesWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
777
784
  await worker.handleInstance(req, res);
778
785
  } finally {
779
- this.countRequest('$subsumes', Date.now() - start);
786
+ this.countRequest(endpointPath, '$subsumes', Date.now() - start);
780
787
  }
781
788
  });
782
789
 
@@ -787,7 +794,7 @@ class TXModule {
787
794
  let worker = new ValidateWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
788
795
  await worker.handleCodeSystemInstance(req, res, this.log);
789
796
  } finally {
790
- this.countRequest('$validate', Date.now() - start);
797
+ this.countRequest(endpointPath, '$validate', Date.now() - start);
791
798
  }
792
799
  });
793
800
  router.post('/CodeSystem/:id/\\$validate-code', async (req, res) => {
@@ -796,7 +803,7 @@ class TXModule {
796
803
  let worker = new ValidateWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
797
804
  await worker.handleCodeSystemInstance(req, res, this.log);
798
805
  } finally {
799
- this.countRequest('$validate', Date.now() - start);
806
+ this.countRequest(endpointPath, '$validate', Date.now() - start);
800
807
  }
801
808
 
802
809
  });
@@ -808,7 +815,7 @@ class TXModule {
808
815
  let worker = new ValidateWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
809
816
  await worker.handleValueSetInstance(req, res, this.log);
810
817
  } finally {
811
- this.countRequest('$validate', Date.now() - start);
818
+ this.countRequest(endpointPath, '$validate', Date.now() - start);
812
819
  }
813
820
  });
814
821
  router.post('/ValueSet/:id/\\$validate-code', async (req, res) => {
@@ -817,7 +824,7 @@ class TXModule {
817
824
  let worker = new ValidateWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
818
825
  await worker.handleValueSetInstance(req, res, this.log);
819
826
  } finally {
820
- this.countRequest('$validate', Date.now() - start);
827
+ this.countRequest(endpointPath, '$validate', Date.now() - start);
821
828
  }
822
829
  });
823
830
 
@@ -829,7 +836,7 @@ class TXModule {
829
836
  let worker = new CompareWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
830
837
  await worker.handleInstance(req, res, this.log);
831
838
  } finally {
832
- this.countRequest('$compare', Date.now() - start);
839
+ this.countRequest(endpointPath, '$compare', Date.now() - start);
833
840
  }
834
841
  });
835
842
  router.post('/ValueSet/:id/\\$compare', async (req, res) => {
@@ -838,7 +845,7 @@ class TXModule {
838
845
  let worker = new CompareWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
839
846
  await worker.handleInstance(req, res, this.log);
840
847
  } finally {
841
- this.countRequest('$compare', Date.now() - start);
848
+ this.countRequest(endpointPath, '$compare', Date.now() - start);
842
849
  }
843
850
  });
844
851
 
@@ -849,7 +856,7 @@ class TXModule {
849
856
  let worker = new ExpandWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n, this.internalLimit(req), this.externalLimit(req));
850
857
  await worker.handleInstance(req, res, this.log);
851
858
  } finally {
852
- this.countRequest('$expand', Date.now() - start);
859
+ this.countRequest(endpointPath, '$expand', Date.now() - start);
853
860
  }
854
861
  });
855
862
  router.post('/ValueSet/:id/\\$expand', async (req, res) => {
@@ -858,7 +865,7 @@ class TXModule {
858
865
  let worker = new ExpandWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n, this.internalLimit(req), this.externalLimit(req));
859
866
  await worker.handleInstance(req, res, this.log);
860
867
  } finally {
861
- this.countRequest('$expand', Date.now() - start);
868
+ this.countRequest(endpointPath, '$expand', Date.now() - start);
862
869
  }
863
870
  });
864
871
 
@@ -869,7 +876,7 @@ class TXModule {
869
876
  let worker = new TranslateWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
870
877
  await worker.handleInstance(req, res, this.log);
871
878
  } finally {
872
- this.countRequest('$translate', Date.now() - start);
879
+ this.countRequest(endpointPath, '$translate', Date.now() - start);
873
880
  }
874
881
  });
875
882
  router.post('/ConceptMap/:id/\\$translate', async (req, res) => {
@@ -878,7 +885,7 @@ class TXModule {
878
885
  let worker = new TranslateWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
879
886
  await worker.handleInstance(req, res, this.log);
880
887
  } finally {
881
- this.countRequest('$translate', Date.now() - start);
888
+ this.countRequest(endpointPath, '$translate', Date.now() - start);
882
889
  }
883
890
  });
884
891
 
@@ -900,7 +907,7 @@ class TXModule {
900
907
  let worker = new ReadWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
901
908
  await worker.handle(req, res, resourceType);
902
909
  } finally {
903
- this.countRequest('read', Date.now() - start);
910
+ this.countRequest(endpointPath, 'read', Date.now() - start);
904
911
  }
905
912
  });
906
913
  }
@@ -913,7 +920,7 @@ class TXModule {
913
920
  let worker = new SearchWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
914
921
  await worker.handle(req, res, resourceType);
915
922
  } finally {
916
- this.countRequest('search', Date.now() - start);
923
+ this.countRequest(endpointPath, 'search', Date.now() - start);
917
924
  }
918
925
  });
919
926
  router.post(`/${resourceType}/_search`, async (req, res) => {
@@ -922,7 +929,7 @@ class TXModule {
922
929
  let worker = new SearchWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
923
930
  await worker.handle(req, res, resourceType);
924
931
  } finally {
925
- this.countRequest('search', Date.now() - start);
932
+ this.countRequest(endpointPath, 'search', Date.now() - start);
926
933
  }
927
934
  });
928
935
  }
@@ -940,7 +947,7 @@ class TXModule {
940
947
  ));
941
948
  }
942
949
  } finally {
943
- this.countRequest('$read', Date.now() - start);
950
+ this.countRequest(endpointPath, '$read', Date.now() - start);
944
951
  }
945
952
  });
946
953
  }
@@ -951,7 +958,7 @@ class TXModule {
951
958
  let worker = new OperationsWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
952
959
  await worker.handle(req, res);
953
960
  } finally {
954
- this.countRequest('$op', Date.now() - start);
961
+ this.countRequest(endpointPath, '$op', Date.now() - start);
955
962
  }
956
963
  });
957
964
 
@@ -965,7 +972,7 @@ class TXModule {
965
972
  res.setHeader('Content-Type', 'text/html');
966
973
  res.send(html);
967
974
  } finally {
968
- this.countRequest('problems', Date.now() - start);
975
+ this.countRequest(endpointPath, 'problems', Date.now() - start);
969
976
  }
970
977
  });
971
978
 
@@ -980,7 +987,7 @@ class TXModule {
980
987
  res.status(500).json(this.operationOutcome('error', 'exception', error.message));
981
988
  }
982
989
  } finally {
983
- this.countRequest('metadata', Date.now() - start);
990
+ this.countRequest(endpointPath, 'metadata', Date.now() - start);
984
991
  }
985
992
  });
986
993
 
@@ -995,7 +1002,7 @@ class TXModule {
995
1002
  res.status(500).json(this.operationOutcome('error', 'exception', error.message));
996
1003
  }
997
1004
  } finally {
998
- this.countRequest('$versions', Date.now() - start);
1005
+ this.countRequest(endpointPath, '$versions', Date.now() - start);
999
1006
  }
1000
1007
  });
1001
1008
 
@@ -1012,7 +1019,7 @@ class TXModule {
1012
1019
  }]
1013
1020
  });
1014
1021
  } finally {
1015
- this.countRequest('home', Date.now() - start);
1022
+ this.countRequest(endpointPath, 'home', Date.now() - start);
1016
1023
  }
1017
1024
  });
1018
1025
 
@@ -1037,7 +1044,7 @@ class TXModule {
1037
1044
  this.log.error(`Error rendering info page for ${req.params.id}: ${error.message}`);
1038
1045
  res.status(500).send('Internal server error');
1039
1046
  } finally {
1040
- this.countRequest('info', Date.now() - start);
1047
+ this.countRequest(endpointPath, 'info', Date.now() - start);
1041
1048
  }
1042
1049
  };
1043
1050
  router.get('/info/:id', infoHandler);
@@ -1214,9 +1221,9 @@ class TXModule {
1214
1221
  return data;
1215
1222
  }
1216
1223
 
1217
- countRequest(name, tat) {
1224
+ countRequest(endpoint, name, tat) {
1218
1225
  if (this.stats) {
1219
- this.stats.countRequest(name, tat);
1226
+ this.stats.countRequest(name, tat, endpoint);
1220
1227
  }
1221
1228
  }
1222
1229
 
@@ -187,8 +187,11 @@ class BatchValidateWorker extends TerminologyWorker {
187
187
  return false;
188
188
  }
189
189
  if (!cache.has(cacheId)) {
190
- throw new Issue('error', 'not-found', null, 'CACHE_ID_UNKNOWN',
191
- this.i18n.translate('CACHE_ID_UNKNOWN', this.opContext.langs, [cacheId]),
190
+ // describeMissing names which fate the id met (never issued here / closed by
191
+ // the client / idle-expired); the coding stays cache-id-unknown either way.
192
+ const { messageId, params: msgParams } = cache.describeMissing(cacheId);
193
+ throw new Issue('error', 'not-found', null, messageId,
194
+ this.i18n.translate(messageId, this.opContext.langs, msgParams),
192
195
  'cache-id-unknown', 404);
193
196
  }
194
197
  if (cache.isSealed(cacheId)) {
@@ -5,20 +5,18 @@
5
5
  // POST /$cache-control?mode=start - as above, optionally front-loading resources
6
6
  // GET /$cache-control?mode=end - tell the server it can release the cache now
7
7
  // POST /$cache-control?mode=end
8
- // (mode=check is reserved for later - report whether a cache is still valid + stats)
8
+ // GET /$cache-control?mode=check - is this cache still alive? (and keep it alive)
9
+ // POST /$cache-control?mode=check
9
10
  //
10
11
  // This is the explicit replacement for the implicit `cache-id` parameter protocol:
11
12
  // the server owns the cache-id namespace, so it can authoritatively reject an
12
13
  // unknown/expired cache later instead of failing obscurely deep inside a validation.
13
14
  //
14
- // NOTE: this is scaffolding. start()/end() currently parse the request into a
15
- // Parameters resource (the same way validate.js does) but do not yet create or
16
- // release anything. The behaviour is filled in by later steps.
17
- //
18
15
 
19
16
  const crypto = require('crypto');
20
17
  const { TerminologyWorker, CACHE_ID_HEADER } = require('./worker');
21
18
  const { Parameters } = require('../library/parameters');
19
+ const { Issue } = require('../library/operation-outcome');
22
20
  const { debugLog } = require('../operation-context');
23
21
 
24
22
  class CacheControlWorker extends TerminologyWorker {
@@ -61,9 +59,11 @@ class CacheControlWorker extends TerminologyWorker {
61
59
  return await this.start(req, res);
62
60
  case 'end':
63
61
  return await this.end(req, res);
62
+ case 'check':
63
+ return await this.check(req, res);
64
64
  default:
65
65
  return res.status(400).json(this.operationOutcome('error', 'invalid',
66
- `$cache-control requires a 'mode' of 'start' or 'end'` +
66
+ `$cache-control requires a 'mode' of 'start', 'end' or 'check'` +
67
67
  (mode ? ` (got '${mode}')` : ` (none supplied)`)));
68
68
  }
69
69
  } catch (error) {
@@ -193,6 +193,82 @@ class CacheControlWorker extends TerminologyWorker {
193
193
  return res.status(200).json({ resourceType: 'Parameters', parameter: [] });
194
194
  }
195
195
 
196
+ /**
197
+ * mode=check: report whether the cache named by the `${CACHE_ID_HEADER}` header is
198
+ * still alive, and - if it is - reset its idle clock.
199
+ *
200
+ * This is a keepalive as much as a probe, and deliberately so. A client's own
201
+ * local cache absorbs most of its terminology work, so the server can see nothing
202
+ * from a client that is still very much running and still relying on its cache;
203
+ * the cache then times out mid-job and the next request that finally does reach
204
+ * the server fails. Checking is exactly what a client that still cares about its
205
+ * cache does, so a check counts as use. (The alternative - a read-only `check`
206
+ * plus a separate `touch` - is two operations for one job, and there is no useful
207
+ * case for asking "is my cache alive?" while wanting it to expire anyway.)
208
+ *
209
+ * An unknown cache-id here is a 200 with `valid` = false, NOT the 404 that the
210
+ * validation path returns. A probe must be able to tell "the server is up and
211
+ * says my cache is gone" from "I could not reach the server" - those call for
212
+ * opposite responses, and collapsing them into one failure loses that. This also
213
+ * matches mode=end, which likewise tolerates an id the server doesn't have. The
214
+ * `outcome` parameter carries the same coded issue the validation path would have
215
+ * raised, so a client that wants the reason (never issued here / closed by the
216
+ * client / expired after N minutes idle) has it without a second call.
217
+ *
218
+ * @param {express.Request} req
219
+ * @param {express.Response} res
220
+ */
221
+ async check(req, res) {
222
+ const cache = this.opContext.resourceCache;
223
+ if (!cache) {
224
+ return res.status(500).json(this.operationOutcome('error', 'exception',
225
+ 'No resource cache is available on this endpoint'));
226
+ }
227
+
228
+ const cacheId = req.headers[CACHE_ID_HEADER];
229
+ if (!cacheId) {
230
+ return res.status(400).json(this.operationOutcome('error', 'invalid',
231
+ `$cache-control mode=check requires the cache-id in the '${CACHE_ID_HEADER}' header`));
232
+ }
233
+
234
+ // Read the status BEFORE touching: the idle time is the interesting part of the
235
+ // answer, and refreshing first would report zero every time.
236
+ const status = cache.status(cacheId);
237
+
238
+ if (!status.exists) {
239
+ const { messageId, params } = cache.describeMissing(cacheId);
240
+ const issue = new Issue('error', 'not-found', null, messageId,
241
+ this.i18n.translate(messageId, this.opContext.langs, params),
242
+ 'cache-id-unknown', 404).asIssue();
243
+ this.log.info(`cache-id '${cacheId}': check -> not valid (${messageId})`);
244
+ return res.status(200).json({
245
+ resourceType: 'Parameters',
246
+ parameter: [
247
+ { name: 'cache-id', valueId: cacheId },
248
+ { name: 'valid', valueBoolean: false },
249
+ { name: 'outcome', resource: { resourceType: 'OperationOutcome', issue: [issue] } }
250
+ ]
251
+ });
252
+ }
253
+
254
+ cache.touch(cacheId);
255
+
256
+ const parameter = [
257
+ { name: 'cache-id', valueId: cacheId },
258
+ { name: 'valid', valueBoolean: true },
259
+ { name: 'sealed', valueBoolean: status.sealed },
260
+ { name: 'resource-count', valueUnsignedInt: status.resources },
261
+ { name: 'idle', valueUnsignedInt: Math.floor(status.idleMs / 1000) }
262
+ ];
263
+ // The timeout lets a client work out how often it needs to check, instead of
264
+ // guessing against a number it cannot see. Omitted if this server isn't saying.
265
+ if (status.timeoutMs !== null && status.timeoutMs !== undefined) {
266
+ parameter.push({ name: 'timeout', valueUnsignedInt: Math.floor(status.timeoutMs / 1000) });
267
+ }
268
+
269
+ return res.status(200).json({ resourceType: 'Parameters', parameter });
270
+ }
271
+
196
272
  /**
197
273
  * Build an OperationOutcome.
198
274
  * @param {string} severity - error, warning, information