meadow-endpoints 4.0.22 → 4.0.23

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.
Files changed (51) hide show
  1. package/README.md +8 -15
  2. package/diagrams/how-it-works.excalidraw +1462 -0
  3. package/diagrams/how-it-works.mmd +9 -0
  4. package/diagrams/how-it-works.svg +2 -0
  5. package/dist/meadow-endpoints.js +516 -221
  6. package/dist/meadow-endpoints.js.map +1 -1
  7. package/dist/meadow-endpoints.min.js +18 -18
  8. package/dist/meadow-endpoints.min.js.map +1 -1
  9. package/docs/README.md +9 -25
  10. package/docs/_cover.md +9 -0
  11. package/docs/_sidebar.md +1 -0
  12. package/docs/_version.json +3 -3
  13. package/docs/architecture.md +119 -0
  14. package/docs/crud/README.md +6 -6
  15. package/docs/diagrams/behavior-injection-hook-points.excalidraw +677 -0
  16. package/docs/diagrams/behavior-injection-hook-points.mmd +9 -0
  17. package/docs/diagrams/behavior-injection-hook-points.svg +2 -0
  18. package/docs/diagrams/how-it-works.excalidraw +2777 -0
  19. package/docs/diagrams/how-it-works.mmd +16 -0
  20. package/docs/diagrams/how-it-works.svg +2 -0
  21. package/docs/diagrams/how-routes-are-generated.excalidraw +1237 -0
  22. package/docs/diagrams/how-routes-are-generated.mmd +13 -0
  23. package/docs/diagrams/how-routes-are-generated.svg +2 -0
  24. package/docs/diagrams/request-lifecycle.excalidraw +1897 -0
  25. package/docs/diagrams/request-lifecycle.mmd +22 -0
  26. package/docs/diagrams/request-lifecycle.svg +2 -0
  27. package/docs/diagrams/where-it-sits-in-the-stack.excalidraw +2068 -0
  28. package/docs/diagrams/where-it-sits-in-the-stack.mmd +19 -0
  29. package/docs/diagrams/where-it-sits-in-the-stack.svg +2 -0
  30. package/docs/index.html +2 -2
  31. package/docs/quickstart.md +151 -0
  32. package/docs/retold-catalog.json +155 -174
  33. package/docs/retold-keyword-index.json +6593 -2920
  34. package/package.json +8 -8
  35. package/source/controller/Meadow-Endpoints-Controller-Base.js +40 -0
  36. package/source/endpoints/count/Meadow-Endpoint-Count.js +1 -0
  37. package/source/endpoints/count/Meadow-Endpoint-CountBy.js +1 -0
  38. package/source/endpoints/create/Meadow-Operation-Create.js +1 -0
  39. package/source/endpoints/delete/Meadow-Endpoint-Delete.js +1 -0
  40. package/source/endpoints/delete/Meadow-Endpoint-Undelete.js +1 -0
  41. package/source/endpoints/read/Meadow-Endpoint-Read.js +1 -0
  42. package/source/endpoints/read/Meadow-Endpoint-ReadDistinctList.js +1 -0
  43. package/source/endpoints/read/Meadow-Endpoint-ReadLiteList.js +1 -0
  44. package/source/endpoints/read/Meadow-Endpoint-ReadMax.js +1 -0
  45. package/source/endpoints/read/Meadow-Endpoint-ReadSelectList.js +1 -0
  46. package/source/endpoints/read/Meadow-Endpoint-Reads.js +1 -0
  47. package/source/endpoints/read/Meadow-Endpoint-ReadsBy.js +1 -0
  48. package/source/endpoints/update/Meadow-Operation-Update.js +2 -0
  49. package/source/endpoints/upsert/Meadow-Operation-Upsert.js +1 -0
  50. package/test/MeadowEndpoints_SessionOverride_tests.js +75 -0
  51. package/dist/indoctrinate_content_staging/Indoctrinate-Catalog-AppData.json +0 -4808
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "meadow-endpoints",
3
- "version": "4.0.22",
3
+ "version": "4.0.23",
4
4
  "description": "Automatic API endpoints for Meadow data.",
5
5
  "main": "source/Meadow-Endpoints.js",
6
6
  "scripts": {
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "repository": {
37
37
  "type": "git",
38
- "url": "https://github.com/stevenvelozo/meadow-endpoints.git"
38
+ "url": "https://github.com/fable-retold/meadow-endpoints.git"
39
39
  },
40
40
  "keywords": [
41
41
  "crud",
@@ -44,9 +44,9 @@
44
44
  "author": "Steven Velozo <steven@velozo.com> (http://velozo.com/)",
45
45
  "license": "MIT",
46
46
  "bugs": {
47
- "url": "https://github.com/stevenvelozo/meadow-endpoints/issues"
47
+ "url": "https://github.com/fable-retold/meadow-endpoints/issues"
48
48
  },
49
- "homepage": "https://github.com/stevenvelozo/meadow-endpoints",
49
+ "homepage": "https://github.com/fable-retold/meadow-endpoints",
50
50
  "devDependencies": {
51
51
  "alasql": "^4.17.2",
52
52
  "chance": "^1.1.13",
@@ -55,18 +55,18 @@
55
55
  "mysql2": "^3.22.1",
56
56
  "orator-serviceserver-restify": "^2.0.11",
57
57
  "papaparse": "^5.5.3",
58
- "pict-docuserve": "^1.3.2",
59
- "quackage": "^1.2.3",
58
+ "pict-docuserve": "^1.4.19",
59
+ "quackage": "^1.3.0",
60
60
  "supertest": "^7.2.2",
61
61
  "typescript": "^5.9.3",
62
62
  "why-is-node-running": "^3.2.2"
63
63
  },
64
64
  "dependencies": {
65
65
  "async": "3.2.6",
66
- "fable": "^3.1.75",
66
+ "fable": "^3.1.78",
67
67
  "fable-serviceproviderbase": "^3.0.19",
68
68
  "JSONStream": "^1.3.5",
69
- "meadow": "^2.0.41",
69
+ "meadow": "^2.0.46",
70
70
  "meadow-filter": "^1.0.10",
71
71
  "orator": "^6.1.2",
72
72
  "underscore": "^1.13.8"
@@ -155,6 +155,46 @@ class MeadowEndpointControllerBase
155
155
  {
156
156
  return this._SessionMarshaler.getSessionData(pRequest);
157
157
  }
158
+
159
+ /**
160
+ * Stamp the request's session onto a meadow query so a downstream provider
161
+ * can act under the caller's identity.
162
+ *
163
+ * Today only the MeadowEndpoints provider consumes this — when a beacon
164
+ * fronts a remote meadow-endpoints API it presents this session upstream
165
+ * (instead of its bound machine session) so the remote enforces row-level
166
+ * auth as the real caller. SQL providers ignore the parameter. The override
167
+ * rides the per-operation FoxHound query (concurrency-safe).
168
+ *
169
+ * No-op for the default/anonymous session (UserID 0 / SessionID 0x0000) —
170
+ * those carry no identity, so the provider keeps its existing behavior.
171
+ *
172
+ * @param {object} pRequestState - the request state (carries SessionData + Query)
173
+ * @returns {void}
174
+ */
175
+ stampSessionOverrideOnQuery(pRequestState)
176
+ {
177
+ if (!pRequestState || !pRequestState.Query || !pRequestState.SessionData)
178
+ {
179
+ return;
180
+ }
181
+ let tmpSession = pRequestState.SessionData;
182
+ let tmpSessionID = tmpSession.SessionID;
183
+ if (typeof(tmpSessionID) !== 'string' || tmpSessionID.length < 1 || tmpSessionID === '0x0000')
184
+ {
185
+ return;
186
+ }
187
+ if (pRequestState.Query.query && pRequestState.Query.query.parameters)
188
+ {
189
+ pRequestState.Query.query.parameters.MeadowEndpointsSessionOverride = (
190
+ {
191
+ SessionID: tmpSessionID,
192
+ CustomerID: tmpSession.CustomerID,
193
+ UserID: tmpSession.UserID,
194
+ UserRoleIndex: tmpSession.UserRoleIndex
195
+ });
196
+ }
197
+ }
158
198
  }
159
199
 
160
200
  module.exports = MeadowEndpointControllerBase;
@@ -11,6 +11,7 @@ const doAPIEndpointCount = function(pRequest, pResponse, fNext)
11
11
  (fStageComplete) =>
12
12
  {
13
13
  tmpRequestState.Query = this.DAL.query;
14
+ this.stampSessionOverrideOnQuery(tmpRequestState);
14
15
  if (typeof(pRequest.params.Filter) === 'string')
15
16
  {
16
17
  // If a filter has been passed in, parse it and add the values to the query.
@@ -11,6 +11,7 @@ const doAPIEndpointCountBy = function(pRequest, pResponse, fNext)
11
11
  (fStageComplete) =>
12
12
  {
13
13
  tmpRequestState.Query = this.DAL.query;
14
+ this.stampSessionOverrideOnQuery(tmpRequestState);
14
15
  tmpRequestState.Query.addFilter(pRequest.params.ByField, pRequest.params.ByValue, '=', 'AND', 'RequestByField');
15
16
  return fStageComplete();
16
17
  },
@@ -31,6 +31,7 @@ const doCreate = function(pRecord, pRequest, pRequestState, pResponse, fCallback
31
31
  {
32
32
  // Prepare create query
33
33
  tmpRequestState.Query = this.DAL.query;
34
+ this.stampSessionOverrideOnQuery(tmpRequestState);
34
35
  tmpRequestState.Query.setIDUser(tmpRequestState.SessionData.UserID);
35
36
  tmpRequestState.Query.addRecord(tmpRequestState.RecordToCreate);
36
37
  return fStageComplete();
@@ -36,6 +36,7 @@ const doAPIEndpointDelete = function(pRequest, pResponse, fNext)
36
36
  (fStageComplete) =>
37
37
  {
38
38
  tmpRequestState.Query = this.DAL.query;
39
+ this.stampSessionOverrideOnQuery(tmpRequestState);
39
40
  tmpRequestState.Query.addFilter(this.DAL.defaultIdentifier, tmpRequestState.IDRecord);
40
41
  tmpRequestState.Query.setIDUser(tmpRequestState.SessionData.UserID);
41
42
  return fStageComplete();
@@ -54,6 +54,7 @@ const doAPIEndpointUndelete = function(pRequest, pResponse, fNext)
54
54
  {
55
55
  // Now see if the record, with this identifier, for this user, exists with the deleted bit set to 1
56
56
  tmpRequestState.Query = this.DAL.query;
57
+ this.stampSessionOverrideOnQuery(tmpRequestState);
57
58
  tmpRequestState.Query.addFilter(this.DAL.defaultIdentifier, tmpIDRecord);
58
59
  tmpRequestState.Query.addFilter('Deleted', 1);
59
60
  tmpRequestState.Query.setIDUser(tmpRequestState.SessionData.UserID);
@@ -11,6 +11,7 @@ const doAPIEndpointRead = function(pRequest, pResponse, fNext)
11
11
  (fStageComplete) =>
12
12
  {
13
13
  tmpRequestState.Query = this.DAL.query;
14
+ this.stampSessionOverrideOnQuery(tmpRequestState);
14
15
  return fStageComplete();
15
16
  },
16
17
  fBehaviorInjector(`Read-PreOperation`),
@@ -15,6 +15,7 @@ const doAPIEndpointReadDistinct = function(pRequest, pResponse, fNext)
15
15
  (fStageComplete) =>
16
16
  {
17
17
  tmpRequestState.Query = this.DAL.query.setDistinct(true);
18
+ this.stampSessionOverrideOnQuery(tmpRequestState);
18
19
 
19
20
  /** @type {number | boolean} */
20
21
  let tmpCap = false;
@@ -14,6 +14,7 @@ const doAPIEndpointReadLite = function(pRequest, pResponse, fNext)
14
14
  (fStageComplete) =>
15
15
  {
16
16
  tmpRequestState.Query = this.DAL.query;
17
+ this.stampSessionOverrideOnQuery(tmpRequestState);
17
18
  // TODO: Limit the query to the columns we need for the templated expression
18
19
 
19
20
  /** @type {number | boolean} */
@@ -11,6 +11,7 @@ const doAPIEndpointReadMax = function(pRequest, pResponse, fNext)
11
11
  (fStageComplete) =>
12
12
  {
13
13
  tmpRequestState.Query = this.DAL.query;
14
+ this.stampSessionOverrideOnQuery(tmpRequestState);
14
15
  return fStageComplete();
15
16
  },
16
17
  (fStageComplete) =>
@@ -11,6 +11,7 @@ const doAPIEndpointReadSelectList = function(pRequest, pResponse, fNext)
11
11
  (fStageComplete) =>
12
12
  {
13
13
  tmpRequestState.Query = this.DAL.query;
14
+ this.stampSessionOverrideOnQuery(tmpRequestState);
14
15
 
15
16
  /** @type {number | boolean} */
16
17
  var tmpCap = false;
@@ -11,6 +11,7 @@ const doAPIEndpointReads = function(pRequest, pResponse, fNext)
11
11
  (fStageComplete) =>
12
12
  {
13
13
  tmpRequestState.Query = this.DAL.query;
14
+ this.stampSessionOverrideOnQuery(tmpRequestState);
14
15
 
15
16
  /** @type {number | boolean} */
16
17
  var tmpCap = false;
@@ -12,6 +12,7 @@ const doAPIEndpointReadsBy = function(pRequest, pResponse, fNext)
12
12
  (fStageComplete) =>
13
13
  {
14
14
  tmpRequestState.Query = this.DAL.query;
15
+ this.stampSessionOverrideOnQuery(tmpRequestState);
15
16
 
16
17
  /** @type {number | boolean} */
17
18
  var tmpCap = false;
@@ -33,6 +33,7 @@ const doUpdate = function(pRecordToModify, pRequest, pRequestState, pResponse, f
33
33
  else
34
34
  {
35
35
  tmpRequestState.Query = this.DAL.query;
36
+ this.stampSessionOverrideOnQuery(tmpRequestState);
36
37
 
37
38
  tmpRequestState.Query.addFilter(this.DAL.defaultIdentifier, tmpRequestState.RecordToModify[this.DAL.defaultIdentifier]);
38
39
 
@@ -64,6 +65,7 @@ const doUpdate = function(pRecordToModify, pRequest, pRequestState, pResponse, f
64
65
  (fStageComplete) =>
65
66
  {
66
67
  tmpRequestState.Query = this.DAL.query;
68
+ this.stampSessionOverrideOnQuery(tmpRequestState);
67
69
  return fStageComplete();
68
70
  },
69
71
  fBehaviorInjector(`Update-PreOperation`),
@@ -14,6 +14,7 @@ const doUpsert = function(pRecordToUpsert, pRequest, pRequestState, pResponse, f
14
14
  (fStageComplete) =>
15
15
  {
16
16
  tmpRequestState.Query = this.DAL.query;
17
+ this.stampSessionOverrideOnQuery(tmpRequestState);
17
18
 
18
19
  // Prepare to gather requirements for upserting
19
20
  tmpRequestState.Record = pRecordToUpsert;
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Meadow Endpoints — per-request session override stamping.
3
+ *
4
+ * stampSessionOverrideOnQuery copies a real request session onto the meadow
5
+ * query (Query.query.parameters.MeadowEndpointsSessionOverride) so a
6
+ * downstream provider (the MeadowEndpoints provider, when a beacon fronts a
7
+ * remote API) can act under the caller's identity. The default/anonymous
8
+ * session is a no-op.
9
+ *
10
+ * npx mocha test/MeadowEndpoints_SessionOverride_tests.js -u tdd --exit
11
+ */
12
+
13
+ const Chai = require('chai');
14
+ const Expect = Chai.expect;
15
+
16
+ const libFable = require('fable');
17
+ const libMeadow = require('meadow');
18
+ const libMeadowEndpointsControllerBase = require('../source/controller/Meadow-Endpoints-Controller-Base.js');
19
+
20
+ const _BookSchema = require('../test_support/model/meadow_schema/BookStore-MeadowSchema-Book.json');
21
+
22
+ function buildController()
23
+ {
24
+ let tmpFable = new libFable({ Product: 'SessionOverrideTest', LogStreams: [ { streamtype: 'console', level: 'fatal' } ] });
25
+ let tmpMeadow = libMeadow.new(tmpFable, 'Book')
26
+ .setSchema(_BookSchema.Schema)
27
+ .setJsonSchema(_BookSchema.JsonSchema)
28
+ .setDefaultIdentifier(_BookSchema.DefaultIdentifier)
29
+ .setDefault(_BookSchema.DefaultObject);
30
+ return new libMeadowEndpointsControllerBase({ DAL: tmpMeadow, _ControllerOptions: {} });
31
+ }
32
+
33
+ function realSession()
34
+ {
35
+ return { SessionID: 'caller-session-xyz', CustomerID: 182, UserID: 5150, UserRoleIndex: 3, LoggedIn: true };
36
+ }
37
+
38
+ suite('Meadow-Endpoints session override stamping', () =>
39
+ {
40
+ test('a real session is stamped onto the query parameters', () =>
41
+ {
42
+ let tmpController = buildController();
43
+ let tmpRequestState = { SessionData: realSession(), Query: tmpController.DAL.query };
44
+ tmpController.stampSessionOverrideOnQuery(tmpRequestState);
45
+ let tmpOverride = tmpRequestState.Query.query.parameters.MeadowEndpointsSessionOverride;
46
+ Expect(tmpOverride).to.be.an('object');
47
+ Expect(tmpOverride.SessionID).to.equal('caller-session-xyz');
48
+ Expect(tmpOverride.CustomerID).to.equal(182);
49
+ Expect(tmpOverride.UserID).to.equal(5150);
50
+ });
51
+
52
+ test('the default/anonymous session (0x0000) is a no-op', () =>
53
+ {
54
+ let tmpController = buildController();
55
+ let tmpRequestState = { SessionData: { SessionID: '0x0000', UserID: 0 }, Query: tmpController.DAL.query };
56
+ tmpController.stampSessionOverrideOnQuery(tmpRequestState);
57
+ Expect(tmpRequestState.Query.query.parameters.MeadowEndpointsSessionOverride).to.equal(undefined);
58
+ });
59
+
60
+ test('a missing SessionID is a no-op', () =>
61
+ {
62
+ let tmpController = buildController();
63
+ let tmpRequestState = { SessionData: { UserID: 5 }, Query: tmpController.DAL.query };
64
+ tmpController.stampSessionOverrideOnQuery(tmpRequestState);
65
+ Expect(tmpRequestState.Query.query.parameters.MeadowEndpointsSessionOverride).to.equal(undefined);
66
+ });
67
+
68
+ test('missing Query or SessionData does not throw', () =>
69
+ {
70
+ let tmpController = buildController();
71
+ Expect(() => tmpController.stampSessionOverrideOnQuery({})).to.not.throw();
72
+ Expect(() => tmpController.stampSessionOverrideOnQuery({ SessionData: realSession() })).to.not.throw();
73
+ Expect(() => tmpController.stampSessionOverrideOnQuery(null)).to.not.throw();
74
+ });
75
+ });