fhirsmith 0.11.1 → 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/CHANGELOG.md +85 -0
- package/config-template.json +18 -1
- package/library/folder-content-loader.js +111 -21
- package/package.json +3 -3
- package/publisher/publisher.js +153 -5
- package/publisher/validation.js +198 -0
- package/server.js +12 -12
- package/stats-db.js +127 -0
- package/stats.js +172 -6
- package/translations/Messages.properties +4 -1
- package/tx/cs/cs-api.js +9 -3
- package/tx/cs/cs-api.md +1 -1
- package/tx/cs/cs-snomed.js +131 -65
- package/tx/library/conceptmap.js +39 -0
- package/tx/library/designations.js +9 -2
- package/tx/library/parameter-check.js +139 -0
- package/tx/library.js +62 -9
- package/tx/operation-context.js +220 -10
- package/tx/params.js +226 -175
- package/tx/sct/expressions.js +485 -0
- package/tx/tx.fhir.org.yml +1 -1
- package/tx/tx.js +57 -50
- package/tx/workers/batch-validate.js +5 -2
- package/tx/workers/cache-control.js +82 -6
- package/tx/workers/expand.js +80 -23
- package/tx/workers/lookup.js +14 -1
- package/tx/workers/translate.js +461 -30
- package/tx/workers/validate.js +7 -1
- package/tx/workers/worker.js +22 -10
- package/tx/xml/xml-base.js +21 -1
- package/tx/xversion/xv-conceptmap.js +81 -0
- package/tx/xversion/xv-parameters.js +8 -0
- package/utilities/merge-audit.py +178 -0
- package/utilities/why-merge-audit.md +39 -0
- package/tx/data/snomed-subset.txt +0 -84
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
|
-
|
|
191
|
-
|
|
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
|
-
//
|
|
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 '
|
|
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
|