meadow-endpoints 4.0.23 → 4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "meadow-endpoints",
3
- "version": "4.0.23",
3
+ "version": "4.0.24",
4
4
  "description": "Automatic API endpoints for Meadow data.",
5
5
  "main": "source/Meadow-Endpoints.js",
6
6
  "scripts": {
@@ -73,6 +73,9 @@ class MeadowEndpoints
73
73
  Reads: require('./endpoints/read/Meadow-Endpoint-Reads.js'),
74
74
  ReadsBy: require('./endpoints/read/Meadow-Endpoint-ReadsBy.js'),
75
75
 
76
+ // Body-driven read: filter/pagination/mode travel in a JSON POST body
77
+ Query: require('./endpoints/read/Meadow-Endpoint-Query.js'),
78
+
76
79
  ReadSelectList: require('./endpoints/read/Meadow-Endpoint-ReadSelectList.js'),
77
80
  ReadLiteList: require('./endpoints/read/Meadow-Endpoint-ReadLiteList.js'),
78
81
  ReadDistinctList: require('./endpoints/read/Meadow-Endpoint-ReadDistinctList.js'),
@@ -183,6 +186,7 @@ class MeadowEndpoints
183
186
  }
184
187
  if (this._EnabledBehaviorSets.Reads)
185
188
  {
189
+ this.connectRoute(pServiceServer, 'postWithBodyParser', `s/Query`, this._Endpoints.Query, `the internal behavior _Endpoints.Query`);
186
190
  this.connectRoute(pServiceServer, 'get', `s`, this._Endpoints.Reads, `the internal behavior _Endpoints.Reads`);
187
191
  this.connectRoute(pServiceServer, 'get', `s/By/:ByField/:ByValue`, this._Endpoints.ReadsBy, `the internal behavior _Endpoints.ReadsBy`);
188
192
  this.connectRoute(pServiceServer, 'get', `s/By/:ByField/:ByValue/:Begin/:Cap`, this._Endpoints.ReadsBy, `the internal behavior _Endpoints.ReadsBy`);
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Meadow Endpoint - Body-driven Read
3
+ *
4
+ * A single POST endpoint that carries the filter, pagination and read-mode
5
+ * selection in a JSON body instead of the URI. This sidesteps URI length
6
+ * limits hit by complex filters and large IN-lists, while reusing the exact
7
+ * GET read handlers (and therefore their behavior hooks, marshalling and
8
+ * response shapes) by mapping the body onto pRequest.params and delegating.
9
+ *
10
+ * Body envelope:
11
+ * {
12
+ * "Filter": "FBV~Genre~EQ~Books~...", // meadow-filter string
13
+ * "Begin": 0,
14
+ * "Cap": 250,
15
+ * "ExtraColumns": "ColumnA,ColumnB", // Lite read
16
+ * "Columns": "ColumnA,ColumnB", // Distinct read
17
+ * "Lite": true, "Distinct": true, "Count": true
18
+ * }
19
+ *
20
+ * The read mode is selected by the flags, resolved by precedence:
21
+ * Count > Distinct > Lite > Reads (the default). This lets a caller compose a
22
+ * read query (filter, pagination, Lite/Distinct shaping) and flip Count on to
23
+ * get the count of that same query.
24
+ */
25
+ const doReads = require('./Meadow-Endpoint-Reads.js');
26
+ const doReadLite = require('./Meadow-Endpoint-ReadLiteList.js');
27
+ const doReadDistinct = require('./Meadow-Endpoint-ReadDistinctList.js');
28
+ const doCount = require('../count/Meadow-Endpoint-Count.js');
29
+
30
+ // Body keys hydrated onto pRequest.params so the delegated GET handlers see
31
+ // the same inputs they read from the URI.
32
+ const PARAM_KEYS = [ 'Filter', 'Begin', 'Cap', 'ExtraColumns', 'Columns' ];
33
+
34
+ /**
35
+ * Resolve the read mode from the request body flags, by precedence:
36
+ * Count > Distinct > Lite > Reads (the default).
37
+ *
38
+ * @param {Record<string, any>} pBody - the parsed request body
39
+ *
40
+ * @return {string} one of 'Count', 'Distinct', 'Lite', 'Reads'
41
+ */
42
+ const resolveMode = function(pBody)
43
+ {
44
+ if (pBody.Count)
45
+ {
46
+ return 'Count';
47
+ }
48
+ if (pBody.Distinct)
49
+ {
50
+ return 'Distinct';
51
+ }
52
+ if (pBody.Lite)
53
+ {
54
+ return 'Lite';
55
+ }
56
+ return 'Reads';
57
+ };
58
+
59
+ const doAPIEndpointQuery = function(pRequest, pResponse, fNext)
60
+ {
61
+ const tmpBody = (pRequest.body && typeof(pRequest.body) === 'object') ? pRequest.body : {};
62
+ pRequest.params = (pRequest.params && typeof(pRequest.params) === 'object') ? pRequest.params : {};
63
+
64
+ for (let i = 0; i < PARAM_KEYS.length; i++)
65
+ {
66
+ if (typeof(tmpBody[PARAM_KEYS[i]]) !== 'undefined')
67
+ {
68
+ pRequest.params[PARAM_KEYS[i]] = tmpBody[PARAM_KEYS[i]];
69
+ }
70
+ }
71
+
72
+ switch (resolveMode(tmpBody))
73
+ {
74
+ case 'Count':
75
+ return doCount.call(this, pRequest, pResponse, fNext);
76
+ case 'Distinct':
77
+ return doReadDistinct.call(this, pRequest, pResponse, fNext);
78
+ case 'Lite':
79
+ return doReadLite.call(this, pRequest, pResponse, fNext);
80
+ case 'Reads':
81
+ default:
82
+ return doReads.call(this, pRequest, pResponse, fNext);
83
+ }
84
+ };
85
+
86
+ module.exports = doAPIEndpointQuery;
@@ -505,6 +505,103 @@ suite
505
505
  }
506
506
  );
507
507
  test
508
+ (
509
+ "query: reads all records via JSON body (default mode)",
510
+ function (fDone)
511
+ {
512
+ _SuperTest
513
+ .post("1.0/Books/Query")
514
+ .send({})
515
+ .end(
516
+ (pError, pResponse) =>
517
+ {
518
+ Expect(pError).to.not.exist;
519
+ let tmpResult = JSON.parse(pResponse.text);
520
+ Expect(tmpResult).to.be.an("array");
521
+ Expect(tmpResult.length).to.be.at.least(5);
522
+ fDone();
523
+ }
524
+ );
525
+ }
526
+ );
527
+ test
528
+ (
529
+ "query: reads with filter and pagination in the body",
530
+ function (fDone)
531
+ {
532
+ _SuperTest
533
+ .post("1.0/Books/Query")
534
+ .send({ Filter: "FBV~Genre~LK~%Science%", Begin: 0, Cap: 1 })
535
+ .end(
536
+ (pError, pResponse) =>
537
+ {
538
+ let tmpResult = JSON.parse(pResponse.text);
539
+ Expect(tmpResult).to.be.an("array");
540
+ Expect(tmpResult.length).to.equal(1);
541
+ Expect(tmpResult[0].Genre).to.contain("Science");
542
+ fDone();
543
+ }
544
+ );
545
+ }
546
+ );
547
+ test
548
+ (
549
+ "query: Lite read via boolean flag",
550
+ function (fDone)
551
+ {
552
+ _SuperTest
553
+ .post("1.0/Books/Query")
554
+ .send({ Lite: true, Begin: 0, Cap: 3 })
555
+ .end(
556
+ (pError, pResponse) =>
557
+ {
558
+ let tmpResult = JSON.parse(pResponse.text);
559
+ Expect(tmpResult).to.be.an("array");
560
+ Expect(tmpResult.length).to.equal(3);
561
+ fDone();
562
+ }
563
+ );
564
+ }
565
+ );
566
+ test
567
+ (
568
+ "query: Distinct read via boolean flag and Columns",
569
+ function (fDone)
570
+ {
571
+ _SuperTest
572
+ .post("1.0/Books/Query")
573
+ .send({ Distinct: true, Columns: "Genre" })
574
+ .end(
575
+ (pError, pResponse) =>
576
+ {
577
+ let tmpResult = JSON.parse(pResponse.text);
578
+ Expect(tmpResult).to.be.an("array");
579
+ Expect(tmpResult.length).to.be.at.least(1);
580
+ fDone();
581
+ }
582
+ );
583
+ }
584
+ );
585
+ test
586
+ (
587
+ "query: Count flag takes precedence and honors the filter",
588
+ function (fDone)
589
+ {
590
+ _SuperTest
591
+ .post("1.0/Books/Query")
592
+ .send({ Lite: true, Count: true, Filter: "FBV~Genre~LK~%Science%" })
593
+ .end(
594
+ (pError, pResponse) =>
595
+ {
596
+ let tmpResult = JSON.parse(pResponse.text);
597
+ Expect(tmpResult).to.have.property("Count");
598
+ Expect(tmpResult.Count).to.be.at.least(3);
599
+ fDone();
600
+ }
601
+ );
602
+ }
603
+ );
604
+ test
508
605
  (
509
606
  'reads by: get records filtered by a field value',
510
607
  function (fDone)