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.
- package/README.md +8 -15
- package/diagrams/how-it-works.excalidraw +1462 -0
- package/diagrams/how-it-works.mmd +9 -0
- package/diagrams/how-it-works.svg +2 -0
- package/dist/meadow-endpoints.js +516 -221
- package/dist/meadow-endpoints.js.map +1 -1
- package/dist/meadow-endpoints.min.js +18 -18
- package/dist/meadow-endpoints.min.js.map +1 -1
- package/docs/README.md +9 -25
- package/docs/_cover.md +9 -0
- package/docs/_sidebar.md +1 -0
- package/docs/_version.json +3 -3
- package/docs/architecture.md +119 -0
- package/docs/crud/README.md +6 -6
- package/docs/diagrams/behavior-injection-hook-points.excalidraw +677 -0
- package/docs/diagrams/behavior-injection-hook-points.mmd +9 -0
- package/docs/diagrams/behavior-injection-hook-points.svg +2 -0
- package/docs/diagrams/how-it-works.excalidraw +2777 -0
- package/docs/diagrams/how-it-works.mmd +16 -0
- package/docs/diagrams/how-it-works.svg +2 -0
- package/docs/diagrams/how-routes-are-generated.excalidraw +1237 -0
- package/docs/diagrams/how-routes-are-generated.mmd +13 -0
- package/docs/diagrams/how-routes-are-generated.svg +2 -0
- package/docs/diagrams/request-lifecycle.excalidraw +1897 -0
- package/docs/diagrams/request-lifecycle.mmd +22 -0
- package/docs/diagrams/request-lifecycle.svg +2 -0
- package/docs/diagrams/where-it-sits-in-the-stack.excalidraw +2068 -0
- package/docs/diagrams/where-it-sits-in-the-stack.mmd +19 -0
- package/docs/diagrams/where-it-sits-in-the-stack.svg +2 -0
- package/docs/index.html +2 -2
- package/docs/quickstart.md +151 -0
- package/docs/retold-catalog.json +155 -174
- package/docs/retold-keyword-index.json +6593 -2920
- package/package.json +8 -8
- package/source/controller/Meadow-Endpoints-Controller-Base.js +40 -0
- package/source/endpoints/count/Meadow-Endpoint-Count.js +1 -0
- package/source/endpoints/count/Meadow-Endpoint-CountBy.js +1 -0
- package/source/endpoints/create/Meadow-Operation-Create.js +1 -0
- package/source/endpoints/delete/Meadow-Endpoint-Delete.js +1 -0
- package/source/endpoints/delete/Meadow-Endpoint-Undelete.js +1 -0
- package/source/endpoints/read/Meadow-Endpoint-Read.js +1 -0
- package/source/endpoints/read/Meadow-Endpoint-ReadDistinctList.js +1 -0
- package/source/endpoints/read/Meadow-Endpoint-ReadLiteList.js +1 -0
- package/source/endpoints/read/Meadow-Endpoint-ReadMax.js +1 -0
- package/source/endpoints/read/Meadow-Endpoint-ReadSelectList.js +1 -0
- package/source/endpoints/read/Meadow-Endpoint-Reads.js +1 -0
- package/source/endpoints/read/Meadow-Endpoint-ReadsBy.js +1 -0
- package/source/endpoints/update/Meadow-Operation-Update.js +2 -0
- package/source/endpoints/upsert/Meadow-Operation-Upsert.js +1 -0
- package/test/MeadowEndpoints_SessionOverride_tests.js +75 -0
- 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.
|
|
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/
|
|
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/
|
|
47
|
+
"url": "https://github.com/fable-retold/meadow-endpoints/issues"
|
|
48
48
|
},
|
|
49
|
-
"homepage": "https://github.com/
|
|
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.
|
|
59
|
-
"quackage": "^1.
|
|
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.
|
|
66
|
+
"fable": "^3.1.78",
|
|
67
67
|
"fable-serviceproviderbase": "^3.0.19",
|
|
68
68
|
"JSONStream": "^1.3.5",
|
|
69
|
-
"meadow": "^2.0.
|
|
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} */
|
|
@@ -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
|
+
});
|