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 +6 -0
- package/.browserslistrc +1 -0
- package/.browserslistrc-BACKUP +1 -0
- package/.gulpfile-quackage-config.json +8 -0
- package/.gulpfile-quackage.js +2 -0
- package/package.json +3 -3
- package/source/Meadow-Endpoints.js +4 -0
- package/source/crud/Meadow-Endpoint-Query.js +93 -0
- package/source/crud/Meadow-Endpoint-ReadLiteList.js +5 -0
- package/test/MeadowEndpoints_basic_tests.js +181 -0
package/.babelrc
ADDED
package/.browserslistrc
ADDED
|
@@ -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
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "meadow-endpoints",
|
|
3
|
-
"version": "2.0.
|
|
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.
|
|
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.
|
|
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)
|