meadow-endpoints 2.0.22 → 2.0.24

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/.babelrc ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "presets": [
3
+ "@babel/preset-env"
4
+ ],
5
+ "sourceMaps": "both"
6
+ }
@@ -0,0 +1 @@
1
+ since 2022
@@ -0,0 +1 @@
1
+ since 2022
@@ -0,0 +1,8 @@
1
+ {
2
+ "EntrypointInputSourceFile": "/home/alex/dev/retold/modules/meadow/meadow-endpoints/source/Meadow-Endpoints.js",
3
+ "LibraryObjectName": "MeadowEndpoints",
4
+ "LibraryOutputFolder": "/home/alex/dev/retold/modules/meadow/meadow-endpoints/dist/",
5
+ "LibraryUniminifiedFileName": "meadow-endpoints.js",
6
+ "LibraryMinifiedFileName": "meadow-endpoints.min.js",
7
+ "BrowserifyIgnore": []
8
+ }
@@ -0,0 +1,2 @@
1
+ require('/home/alex/dev/retold/modules/meadow/meadow-endpoints/node_modules/quackage/gulp/Quackage-Gulpfile.js');
2
+ require('/home/alex/dev/retold/modules/meadow/meadow-endpoints/node_modules/quackage/gulp/Quackage-Gulpfile.js');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "meadow-endpoints",
3
- "version": "2.0.22",
3
+ "version": "2.0.24",
4
4
  "description": "Automatic API endpoints for Meadow data.",
5
5
  "main": "source/Meadow-Endpoints.js",
6
6
  "scripts": {
@@ -44,7 +44,7 @@
44
44
  "homepage": "https://github.com/stevenvelozo/meadow-endpoints",
45
45
  "devDependencies": {
46
46
  "chai": "4.1.2",
47
- "chance": "^1.1.8",
47
+ "chance": "^1.1.13",
48
48
  "fable": "^2.0.1",
49
49
  "mocha": "9.2.2",
50
50
  "mysql2": "1.6.1",
@@ -55,7 +55,7 @@
55
55
  "async": "2.6.1",
56
56
  "JSONStream": "^1.3.5",
57
57
  "meadow": "~1.0.32",
58
- "meadow-filter": "^1.0.5",
58
+ "meadow-filter": "^1.0.10",
59
59
  "orator": "~2.0.2",
60
60
  "underscore": "1.9.1"
61
61
  }
@@ -54,6 +54,9 @@ var MeadowEndpoints = function()
54
54
  Reads: require('./crud/Meadow-Endpoint-Reads.js'),
55
55
  ReadsBy: require('./crud/Meadow-Endpoint-ReadsBy.js'),
56
56
 
57
+ // Body-driven read: filter/pagination/mode travel in a JSON POST body
58
+ Query: require('./crud/Meadow-Endpoint-Query.js'),
59
+
57
60
  ReadSelectList: require('./crud/Meadow-Endpoint-ReadSelectList.js'),
58
61
  ReadLiteList: require('./crud/Meadow-Endpoint-ReadLiteList.js'),
59
62
  ReadDistinctList: require('./crud/Meadow-Endpoint-ReadDistinctList.js'),
@@ -343,6 +346,7 @@ var MeadowEndpoints = function()
343
346
  }
344
347
  if (_EnabledBehaviors.Reads)
345
348
  {
349
+ pRestServer.post(`${tmpEndpointPrefix}s/Query`, _CommonServices.bodyParser(), _EndpointAuthenticators.Reads, wireState, _Endpoints.Query);
346
350
  pRestServer.get(`${tmpEndpointPrefix}s`, _EndpointAuthenticators.Reads, wireState, _Endpoints.Reads);
347
351
  pRestServer.get(`${tmpEndpointPrefix}s/By/:ByField/:ByValue`, _EndpointAuthenticators.Reads, wireState, _Endpoints.ReadsBy);
348
352
  pRestServer.get(`${tmpEndpointPrefix}s/By/:ByField/:ByValue/:Begin/:Cap`, _EndpointAuthenticators.Reads, wireState, _Endpoints.ReadsBy);
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Meadow Endpoint - Body-driven Read
3
+ *
4
+ * @license MIT
5
+ *
6
+ * @author Steven Velozo <steven@velozo.com>
7
+ * @module Meadow
8
+ */
9
+
10
+ /**
11
+ * A single POST endpoint that carries the filter, pagination and read-mode
12
+ * selection in a JSON body instead of the URI. This sidesteps URI length
13
+ * limits hit by complex filters and large IN-lists, while reusing the exact
14
+ * GET read handlers (and therefore their authorizers, marshalling and response
15
+ * shapes) by mapping the body onto pRequest.params and delegating.
16
+ *
17
+ * Body envelope:
18
+ * {
19
+ * "Filter": "FBV~Genre~EQ~Books~...", // meadow-filter string
20
+ * "Begin": 0,
21
+ * "Cap": 250,
22
+ * "ExtraColumns": "ColumnA,ColumnB", // Lite read
23
+ * "Columns": "ColumnA,ColumnB", // Distinct read
24
+ * "Lite": true, "Distinct": true, "Count": true
25
+ * }
26
+ *
27
+ * The read mode is selected by the flags, resolved by precedence:
28
+ * Count > Distinct > Lite > Reads (the default). This lets a caller compose a
29
+ * read query (filter, pagination, Lite/Distinct shaping) and flip Count on to
30
+ * get the count of that same query.
31
+ */
32
+ var doReads = require('./Meadow-Endpoint-Reads.js');
33
+ var doReadLite = require('./Meadow-Endpoint-ReadLiteList.js');
34
+ var doReadDistinct = require('./Meadow-Endpoint-ReadDistinctList.js');
35
+ var doCount = require('./Meadow-Endpoint-Count.js');
36
+
37
+ // Body keys hydrated onto pRequest.params so the delegated GET handlers see
38
+ // the same inputs they read from the URI.
39
+ var PARAM_KEYS = [ 'Filter', 'Begin', 'Cap', 'ExtraColumns', 'Columns' ];
40
+
41
+ /**
42
+ * Resolve the read mode from the request body flags, by precedence:
43
+ * Count > Distinct > Lite > Reads (the default).
44
+ *
45
+ * @param {Object} pBody - the parsed request body
46
+ *
47
+ * @return {String} one of 'Count', 'Distinct', 'Lite', 'Reads'
48
+ */
49
+ var resolveMode = function(pBody)
50
+ {
51
+ if (pBody.Count)
52
+ {
53
+ return 'Count';
54
+ }
55
+ if (pBody.Distinct)
56
+ {
57
+ return 'Distinct';
58
+ }
59
+ if (pBody.Lite)
60
+ {
61
+ return 'Lite';
62
+ }
63
+ return 'Reads';
64
+ };
65
+
66
+ var doAPIQueryEndpoint = function(pRequest, pResponse, fNext)
67
+ {
68
+ var tmpBody = (pRequest.body && typeof(pRequest.body) === 'object') ? pRequest.body : {};
69
+ pRequest.params = (pRequest.params && typeof(pRequest.params) === 'object') ? pRequest.params : {};
70
+
71
+ for (var i = 0; i < PARAM_KEYS.length; i++)
72
+ {
73
+ if (typeof(tmpBody[PARAM_KEYS[i]]) !== 'undefined')
74
+ {
75
+ pRequest.params[PARAM_KEYS[i]] = tmpBody[PARAM_KEYS[i]];
76
+ }
77
+ }
78
+
79
+ switch (resolveMode(tmpBody))
80
+ {
81
+ case 'Count':
82
+ return doCount(pRequest, pResponse, fNext);
83
+ case 'Distinct':
84
+ return doReadDistinct(pRequest, pResponse, fNext);
85
+ case 'Lite':
86
+ return doReadLite(pRequest, pResponse, fNext);
87
+ case 'Reads':
88
+ default:
89
+ return doReads(pRequest, pResponse, fNext);
90
+ }
91
+ };
92
+
93
+ module.exports = doAPIQueryEndpoint;
@@ -111,6 +111,11 @@ var doAPIReadLiteEndpoint = function(pRequest, pResponse, fNext)
111
111
  // It looks like this record was not authorized. Send an error.
112
112
  return fStageComplete({Code:405,Message:'UNAUTHORIZED ACCESS IS NOT ALLOWED'});
113
113
  },
114
+ // 2.7: INJECT: Post-operation behavior (e.g. field cleansing)
115
+ function (fStageComplete)
116
+ {
117
+ pRequest.BehaviorModifications.runBehavior('ReadsLite-PostOperation', pRequest, fStageComplete);
118
+ },
114
119
  // 3. Marshalling of records into the hash list, using underscore templates.
115
120
  function (fStageComplete)
116
121
  {
@@ -588,6 +588,102 @@ suite
588
588
  }
589
589
  );
590
590
  test
591
+ (
592
+ 'query: reads all records via JSON body (default mode)',
593
+ function(fDone)
594
+ {
595
+ libSuperTest('http://localhost:9080/')
596
+ .post('1.0/FableTests/Query')
597
+ .send({})
598
+ .end(
599
+ function (pError, pResponse)
600
+ {
601
+ var tmpResults = JSON.parse(pResponse.text);
602
+ Expect(tmpResults).to.be.an('array');
603
+ Expect(tmpResults.length).to.equal(6);
604
+ fDone();
605
+ }
606
+ );
607
+ }
608
+ );
609
+ test
610
+ (
611
+ 'query: reads with filter in the body',
612
+ function(fDone)
613
+ {
614
+ libSuperTest('http://localhost:9080/')
615
+ .post('1.0/FableTests/Query')
616
+ .send({ Filter: 'FBV~Type~EQ~Dog' })
617
+ .end(
618
+ function (pError, pResponse)
619
+ {
620
+ var tmpResults = JSON.parse(pResponse.text);
621
+ Expect(tmpResults).to.be.an('array');
622
+ Expect(tmpResults.length).to.equal(2);
623
+ Expect(tmpResults[0].Type).to.equal('Dog');
624
+ fDone();
625
+ }
626
+ );
627
+ }
628
+ );
629
+ test
630
+ (
631
+ 'query: Lite read via boolean flag with pagination',
632
+ function(fDone)
633
+ {
634
+ libSuperTest('http://localhost:9080/')
635
+ .post('1.0/FableTests/Query')
636
+ .send({ Lite: true, Begin: 0, Cap: 3 })
637
+ .end(
638
+ function (pError, pResponse)
639
+ {
640
+ var tmpResults = JSON.parse(pResponse.text);
641
+ Expect(tmpResults).to.be.an('array');
642
+ Expect(tmpResults.length).to.equal(3);
643
+ fDone();
644
+ }
645
+ );
646
+ }
647
+ );
648
+ test
649
+ (
650
+ 'query: Distinct read via boolean flag and Columns',
651
+ function(fDone)
652
+ {
653
+ libSuperTest('http://localhost:9080/')
654
+ .post('1.0/FableTests/Query')
655
+ .send({ Distinct: true, Columns: 'Type' })
656
+ .end(
657
+ function (pError, pResponse)
658
+ {
659
+ var tmpResults = JSON.parse(pResponse.text);
660
+ Expect(tmpResults).to.be.an('array');
661
+ Expect(tmpResults.length).to.be.at.least(1);
662
+ fDone();
663
+ }
664
+ );
665
+ }
666
+ );
667
+ test
668
+ (
669
+ 'query: Count flag takes precedence and honors the filter',
670
+ function(fDone)
671
+ {
672
+ libSuperTest('http://localhost:9080/')
673
+ .post('1.0/FableTests/Query')
674
+ .send({ Lite: true, Count: true, Filter: 'FBV~Type~EQ~Dog' })
675
+ .end(
676
+ function (pError, pResponse)
677
+ {
678
+ var tmpResults = JSON.parse(pResponse.text);
679
+ Expect(tmpResults).to.have.property('Count');
680
+ Expect(tmpResults.Count).to.equal(2);
681
+ fDone();
682
+ }
683
+ );
684
+ }
685
+ );
686
+ test
591
687
  (
592
688
  'readsLiteExtended: get all records',
593
689
  function(fDone)
@@ -608,6 +704,91 @@ suite
608
704
  }
609
705
  );
610
706
  test
707
+ (
708
+ 'readsLite: ReadsLite-PostOperation behavior fires and can modify records',
709
+ function(fDone)
710
+ {
711
+ _MeadowEndpoints.behaviorModifications.setBehavior('ReadsLite-PostOperation',
712
+ function(pRequest, fComplete)
713
+ {
714
+ // Simulate field cleansing by removing Type from all records
715
+ pRequest.Records.forEach(function(pRecord)
716
+ {
717
+ delete pRecord.Type;
718
+ });
719
+ fComplete(false);
720
+ });
721
+ libSuperTest('http://localhost:9080/')
722
+ .get('1.0/FableTests/LiteExtended/Type,Name')
723
+ .end(
724
+ function (pError, pResponse)
725
+ {
726
+ var tmpResults = JSON.parse(pResponse.text);
727
+ Expect(tmpResults.length).to.equal(6);
728
+ // Type was requested via LiteExtended but removed by PostOperation behavior
729
+ Expect(tmpResults[0]).to.not.have.property('Type');
730
+ Expect(tmpResults[4]).to.not.have.property('Type');
731
+ // Name was not removed and should still be present
732
+ Expect(tmpResults[4].Name).to.equal('Gertrude');
733
+ // Clean up
734
+ _MeadowEndpoints.behaviorModifications.setBehavior('ReadsLite-PostOperation', null);
735
+ fDone();
736
+ }
737
+ );
738
+ }
739
+ );
740
+ test
741
+ (
742
+ 'readsLite: ReadsLite-PostOperation behavior fires for standard Lite endpoint',
743
+ function(fDone)
744
+ {
745
+ var tmpBehaviorFired = false;
746
+ _MeadowEndpoints.behaviorModifications.setBehavior('ReadsLite-PostOperation',
747
+ function(pRequest, fComplete)
748
+ {
749
+ tmpBehaviorFired = true;
750
+ fComplete(false);
751
+ });
752
+ libSuperTest('http://localhost:9080/')
753
+ .get('1.0/FableTests/Lite')
754
+ .end(
755
+ function (pError, pResponse)
756
+ {
757
+ var tmpResults = JSON.parse(pResponse.text);
758
+ Expect(tmpResults.length).to.equal(6);
759
+ Expect(tmpBehaviorFired).to.equal(true);
760
+ // Clean up
761
+ _MeadowEndpoints.behaviorModifications.setBehavior('ReadsLite-PostOperation', null);
762
+ fDone();
763
+ }
764
+ );
765
+ }
766
+ );
767
+ test
768
+ (
769
+ 'readsLite: ReadsLite-PostOperation error halts response',
770
+ function(fDone)
771
+ {
772
+ _MeadowEndpoints.behaviorModifications.setBehavior('ReadsLite-PostOperation',
773
+ function(pRequest, fComplete)
774
+ {
775
+ fComplete({Code:403,Message:'SENSITIVE FIELD ACCESS DENIED'});
776
+ });
777
+ libSuperTest('http://localhost:9080/')
778
+ .get('1.0/FableTests/LiteExtended/Type,Name')
779
+ .end(
780
+ function (pError, pResponse)
781
+ {
782
+ var tmpResult = JSON.parse(pResponse.text);
783
+ Expect(tmpResult).to.have.property('Error');
784
+ // Clean up
785
+ _MeadowEndpoints.behaviorModifications.setBehavior('ReadsLite-PostOperation', null);
786
+ fDone();
787
+ }
788
+ );
789
+ }
790
+ );
791
+ test
611
792
  (
612
793
  'readsby: get all records by Type',
613
794
  function(fDone)